Skip to content

Commit c8f6d40

Browse files
author
Quanyi Ma
authored
Merge pull request #168 from liangchenye/tuf
add snapshot service to implement scan feature
2 parents 55fbaab + 4e98c6d commit c8f6d40

File tree

9 files changed

+547
-32
lines changed

9 files changed

+547
-32
lines changed

handler/appv1.go

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,9 @@ func AppDeleteFileV1Handler(ctx *macaron.Context) (int, []byte) {
283283
return httpRet("AppV1 Delete data", nil, err)
284284
}
285285

286-
func AppRegistScanHooksHandler(ctx *macaron.Context) (int, []byte) {
286+
// AppRegistScanHooksV1Handler adds a scan plugin to a user repo
287+
// TODO: to make it easier as a start, we assume each repo could only have one scan plugin
288+
func AppRegistScanHooksV1Handler(ctx *macaron.Context) (int, []byte) {
287289
data, err := ctx.Req.Body().Bytes()
288290
if err != nil {
289291
log.Errorf("[%s] Req.Body.Bytes error: %s", ctx.Req.RequestURI, err.Error())
@@ -292,18 +294,22 @@ func AppRegistScanHooksHandler(ctx *macaron.Context) (int, []byte) {
292294
return http.StatusBadRequest, result
293295
}
294296

295-
var reg models.ScanHookRegist
296-
err = json.Unmarshal(data, &reg)
297+
type scanPlugin struct {
298+
Name string
299+
}
300+
var n scanPlugin
301+
err = json.Unmarshal(data, &n)
297302
if err != nil {
298303
log.Errorf("[%s] Invalid body data: %s", ctx.Req.RequestURI, err.Error())
299304

300305
result, _ := json.Marshal(map[string]string{"Error": "Parse Req.Body.Bytes Error"})
301306
return http.StatusBadRequest, result
302307
}
303308

309+
var reg models.ScanHookRegist
304310
namespace := ctx.Params(":namespace")
305311
repository := ctx.Params(":repository")
306-
err = reg.Regist(namespace, repository, reg.ImageName)
312+
err = reg.Regist("appv1", namespace, repository, n.Name)
307313
if err != nil {
308314
log.Errorf("[%s] scan hook regist error: %s", ctx.Req.RequestURI, err.Error())
309315

@@ -314,8 +320,8 @@ func AppRegistScanHooksHandler(ctx *macaron.Context) (int, []byte) {
314320
return httpRet("AppV1 Scan Hook Regist", nil, err)
315321
}
316322

317-
// AppCallbackScanHooksHandler gets callback from container and save the scan result.
318-
func AppCallbackScanHooksHandler(ctx *macaron.Context) (int, []byte) {
323+
// AppCallbackScanHooksV1Handler gets callback from container and save the scan result.
324+
func AppCallbackScanHooksV1Handler(ctx *macaron.Context) (int, []byte) {
319325
data, err := ctx.Req.Body().Bytes()
320326
if err != nil {
321327
log.Errorf("[%s] Req.Body.Bytes error: %s", ctx.Req.RequestURI, err.Error())
@@ -336,3 +342,50 @@ func AppCallbackScanHooksHandler(ctx *macaron.Context) (int, []byte) {
336342

337343
return httpRet("AppV1 Scan Hook Callback", nil, err)
338344
}
345+
346+
// AppActiveScanHooksTaskV1Handler actives a scan task
347+
func AppActiveScanHooksTaskV1Handler(ctx *macaron.Context) (int, []byte) {
348+
namespace := ctx.Params(":namespace")
349+
repository := ctx.Params(":repository")
350+
351+
var r models.ScanHookRegist
352+
rID, err := r.FindID("appv1", namespace, repository)
353+
if err != nil {
354+
log.Errorf("[%s] scan hook callback error: %s", ctx.Req.RequestURI, err.Error())
355+
356+
result, _ := json.Marshal(map[string]string{"Error": "Donnot have registed scan plugin"})
357+
return http.StatusBadRequest, result
358+
}
359+
360+
a := models.ArtifactV1{
361+
OS: ctx.Params(":os"),
362+
Arch: ctx.Params(":arch"),
363+
App: ctx.Params(":app"),
364+
Tag: ctx.Params(":tag"),
365+
}
366+
a, err = a.Get()
367+
if err != nil {
368+
log.Errorf("[%s] scan hook callback error: %s", ctx.Req.RequestURI, err.Error())
369+
370+
result, _ := json.Marshal(map[string]string{"Error": "Cannot find artifactv1"})
371+
return http.StatusBadRequest, result
372+
}
373+
374+
// create a task
375+
var t models.ScanHookTask
376+
tID, err := t.Put(rID, a.Path)
377+
if err != nil {
378+
log.Errorf("[%s] scan hook callback error: %s", ctx.Req.RequestURI, err.Error())
379+
380+
result, _ := json.Marshal(map[string]string{"Error": "Fail to create a scan task"})
381+
return http.StatusBadRequest, result
382+
}
383+
384+
idBytes, err := utils.TokenMarshal(tID, setting.ScanKey)
385+
386+
val := struct {
387+
TaskID string
388+
}{TaskID: string(idBytes)}
389+
390+
return httpRet("AppV1 Active Scan Hook Task", val, nil)
391+
}

models/appv1.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,12 @@ func (app *AppV1) Delete(artifact ArtifactV1) error {
9999
return nil
100100
}
101101

102+
// Get gets full info by os/arch/app/tag
103+
func (a *ArtifactV1) Get() (ArtifactV1, error) {
104+
// TODO
105+
return *a, nil
106+
}
107+
102108
func (a *ArtifactV1) GetName() string {
103109
if ok, _ := a.isValid(); !ok {
104110
return ""

models/hook.go

Lines changed: 62 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,48 +18,66 @@ package models
1818

1919
import (
2020
"errors"
21+
"strconv"
2122
"time"
2223

2324
"github.com/containerops/dockyard/setting"
25+
"github.com/containerops/dockyard/updateservice/snapshot"
2426
"github.com/containerops/dockyard/utils"
2527
)
2628

2729
// ScanHookRegist:
2830
// Namespace/Repository contains images/apps/vms to be scaned
29-
// ImageName is used to scan the images/apps/vms within a repository
30-
// NOTE: no need to record the type of a repository ("docker/v1", "app/v1"),
31-
// a user should regist his/her repository with the right ImageName
31+
// ScanPluginName is a plugin name of 'snapshot'
3232
type ScanHookRegist struct {
33-
ID int64 `json:"id" gorm:"primary_key"`
34-
Namespace string `json:"namespace" sql:"not null;type:varchar(255)"`
35-
Repository string `json:"repository" sql:"not null;type:varchar(255)"`
36-
ImageName string `json:"ImageName" sql:"not null;type:varchar(255)"`
33+
ID int64 `json:"id" gorm:"primary_key"`
34+
Proto string `json:"proto" sql:"not null;type:varchar(255)"`
35+
Namespace string `json:"namespace" sql:"not null;type:varchar(255)"`
36+
Repository string `json:"repository" sql:"not null;type:varchar(255)"`
37+
ScanPluginName string `json:"scanPluginName" sql:"not null;type:varchar(255)"`
3738
}
3839

3940
// Regist regists a repository with a scan image
40-
// A namespace/repository could have multiple ImageNames,
41-
// but should not have multiple records with same Namespace&Repository&ImageName.
42-
func (s *ScanHookRegist) Regist(n, r, image string) error {
43-
if n == "" || r == "" || image == "" {
44-
return errors.New("'Namespace', 'Repository' and 'ImageName' should not be empty")
41+
// A namespace/repository could have multiple ScanPluginName,
42+
// but now we only support one.
43+
func (s *ScanHookRegist) Regist(p, n, r, name string) error {
44+
if p == "" || n == "" || r == "" || name == "" {
45+
return errors.New("'Proto', 'Namespace', 'Repository' and 'ScanPluginName' should not be empty")
4546
}
46-
s.Namespace, s.Repository, s.ImageName = n, r, image
47+
48+
if ok, err := snapshot.IsSnapshotSupported(p, name); !ok {
49+
return err
50+
}
51+
52+
s.Proto, s.Namespace, s.Repository, s.ScanPluginName = p, n, r, name
4753
//TODO: add to db
4854
return nil
4955
}
5056

5157
// UnRegist unregists a repository with a scan image
5258
// if ImageName is nil, unregist all the scan images.
53-
func (s *ScanHookRegist) UnRegist(n, r string) error {
54-
if n == "" || r == "" {
55-
return errors.New("'Namespace', 'Repository' should not be empty")
59+
func (s *ScanHookRegist) UnRegist(p, n, r string) error {
60+
if p == "" || n == "" || r == "" {
61+
return errors.New("'Proto', 'Namespace', 'Repository' should not be empty")
5662
}
57-
s.Namespace, s.Repository = n, r
63+
s.Proto, s.Namespace, s.Repository = p, n, r
5864

5965
//TODO: remove from db
6066
return nil
6167
}
6268

69+
// FindByID finds content by id
70+
func (s *ScanHookRegist) FindByID(id int64) (ScanHookRegist, error) {
71+
//TODO: query db
72+
return *s, nil
73+
}
74+
75+
// FindID finds id by Proto, Namespace and Repository
76+
func (s *ScanHookRegist) FindID(p, n, r string) (int64, error) {
77+
//TODO: query db
78+
return 0, nil
79+
}
80+
6381
// ListScanHooks returns a list of registed scan hooks of a repository
6482
func (s *ScanHookRegist) List(n, r string) ([]ScanHookRegist, error) {
6583
if n == "" || r == "" {
@@ -70,7 +88,8 @@ func (s *ScanHookRegist) List(n, r string) ([]ScanHookRegist, error) {
7088

7189
// ScanHookTask is the scan task
7290
type ScanHookTask struct {
73-
ID int64 `json:"id" gorm:"primary_key"`
91+
ID int64 `json:"id" gorm:"primary_key"`
92+
//Path is image url now
7493
Path string `json:"path" sql:"not null;type:varchar(255)"`
7594
Callback string `json:"callback" sql:"not null;type:varchar(255)"`
7695
// ID of ScanHookRegist
@@ -82,12 +101,31 @@ type ScanHookTask struct {
82101
UpdatedAt time.Time `json:"update_at" sql:""`
83102
}
84103

85-
func (t *ScanHookTask) Put(p, c string, rID int64) error {
86-
if p == "" || c == "" || rID == 0 {
87-
return errors.New("'Namespace', 'Repository' and RegistID should not be empty")
104+
// Put returns task id
105+
func (t *ScanHookTask) Put(rID int64, url string) (int64, error) {
106+
if url == "" || rID == 0 {
107+
return 0, errors.New("'URL' and 'RegistID' should not be empty")
88108
}
109+
//TODO: add to db and get task ID
89110

90-
return nil
111+
var reg ScanHookRegist
112+
reg, err := reg.FindByID(rID)
113+
if err != nil {
114+
return 0, err
115+
}
116+
117+
// Do the real scan work
118+
s, err := snapshot.NewUpdateServiceSnapshot(reg.ScanPluginName, strconv.FormatInt(rID, 10), url, nil)
119+
if err != nil {
120+
return 0, err
121+
}
122+
123+
err = s.Process()
124+
if err != nil {
125+
return 0, err
126+
}
127+
128+
return 0, nil
91129
}
92130

93131
func (t *ScanHookTask) Update(status string) error {
@@ -97,14 +135,14 @@ func (t *ScanHookTask) Update(status string) error {
97135

98136
func (t *ScanHookTask) Find(encodedCallbackID string) error {
99137
//TODO: update status and updatedAt
100-
var id int
138+
var id int64
101139
err := utils.TokenUnmarshal(encodedCallbackID, setting.ScanKey, &id)
102140

103141
return err
104142
}
105143

106144
func (t *ScanHookTask) UpdateResult(encodedCallbackID string, data []byte) error {
107-
var id int
145+
var id int64
108146
err := utils.TokenUnmarshal(encodedCallbackID, setting.ScanKey, &id)
109147
if err != nil {
110148
return err

router/router.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,9 @@ func SetRouters(m *macaron.Macaron) {
140140
m.Delete("/:os/:arch/:type/:app/?:tag", handler.AppDeleteFileV1Handler)
141141

142142
//Content Scan
143-
m.Post("/shook", handler.AppRegistScanHooksHandler)
144-
m.Post("/shook/:callbackID", handler.AppCallbackScanHooksHandler)
143+
m.Post("/shook", handler.AppRegistScanHooksV1Handler)
144+
m.Post("/shook/:callbackID", handler.AppCallbackScanHooksV1Handler)
145+
m.Post("/:os/:arch/:app/shook/?:tag", handler.AppActiveScanHooksTaskV1Handler)
145146
})
146147
})
147148
})

tests/unit/testdata/testmd5

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
this is test md5.
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*
2+
Copyright 2016 The ContainerOps Authors All rights reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
package unittest
17+
18+
import (
19+
"errors"
20+
"fmt"
21+
"path/filepath"
22+
"runtime"
23+
"testing"
24+
25+
"github.com/stretchr/testify/assert"
26+
27+
"github.com/containerops/dockyard/updateservice/snapshot"
28+
)
29+
30+
func TestAppv1New(t *testing.T) {
31+
cases := []struct {
32+
id string
33+
url string
34+
expected bool
35+
}{
36+
{"id", "url", true},
37+
{"", "url", false},
38+
{"id", "", false},
39+
}
40+
41+
var appv1 snapshot.UpdateServiceSnapshotAppv1
42+
for _, c := range cases {
43+
_, err := appv1.New(c.id, c.url, nil)
44+
assert.Equal(t, c.expected, err == nil, "Fail to create new snapshot appv1")
45+
}
46+
}
47+
48+
func TestAppv1Supported(t *testing.T) {
49+
cases := []struct {
50+
proto string
51+
expected bool
52+
}{
53+
{"appv1", true},
54+
{"invalid", false},
55+
}
56+
57+
var appv1 snapshot.UpdateServiceSnapshotAppv1
58+
for _, c := range cases {
59+
assert.Equal(t, c.expected, appv1.Supported(c.proto), "Fail to get supported status")
60+
}
61+
}
62+
63+
var (
64+
cbMap = make(map[string]snapshot.UpdateServiceSnapshotOutput)
65+
)
66+
67+
func testCB(id string, data snapshot.UpdateServiceSnapshotOutput) error {
68+
if id != "1" && id != "2" {
69+
return errors.New("invalid id")
70+
}
71+
72+
cbMap[id] = data
73+
return nil
74+
}
75+
76+
func TestAppv1Process(t *testing.T) {
77+
for n, _ := range cbMap {
78+
delete(cbMap, n)
79+
}
80+
81+
cases := []struct {
82+
id string
83+
url string
84+
cb snapshot.Callback
85+
pExpected bool
86+
idExpected bool
87+
md5 string
88+
}{
89+
{"1", "testmd5", testCB, true, true, "ffe7c736f2aa54531ac6430e3cbf2545"},
90+
{"2", "invalid", testCB, true, true, ""},
91+
{"3", "testmd5", testCB, false, false, ""},
92+
{"4", "testmd5", nil, true, false, ""},
93+
}
94+
95+
var appv1 snapshot.UpdateServiceSnapshotAppv1
96+
_, path, _, _ := runtime.Caller(0)
97+
dir := filepath.Join(filepath.Dir(path), "testdata")
98+
99+
for _, c := range cases {
100+
a, _ := appv1.New(c.id, filepath.Join(dir, c.url), c.cb)
101+
err := a.Process()
102+
assert.Equal(t, c.pExpected, err == nil, "Fail to get correct process output")
103+
104+
data, ok := cbMap[c.id]
105+
assert.Equal(t, c.idExpected, ok, "Fail to call cb")
106+
if ok {
107+
assert.Equal(t, c.md5, fmt.Sprintf("%x", data.Data), "Fail to call cb md5")
108+
}
109+
}
110+
}

0 commit comments

Comments
 (0)