Merge pull request #11111 from ywk253100/200317_clean_todo

Clean up some TODO items
This commit is contained in:
Wenkai Yin(尹文开) 2020-03-17 20:17:22 +08:00 committed by GitHub
commit f02c5570a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 271 additions and 272 deletions

View File

@ -22,8 +22,8 @@ import (
"github.com/docker/distribution/manifest/schema1" "github.com/docker/distribution/manifest/schema1"
"github.com/docker/distribution/manifest/schema2" "github.com/docker/distribution/manifest/schema2"
"github.com/goharbor/harbor/src/api/artifact/processor" "github.com/goharbor/harbor/src/api/artifact/processor"
"github.com/goharbor/harbor/src/api/artifact/processor/blob"
"github.com/goharbor/harbor/src/pkg/artifact" "github.com/goharbor/harbor/src/pkg/artifact"
"github.com/goharbor/harbor/src/pkg/registry"
v1 "github.com/opencontainers/image-spec/specs-go/v1" v1 "github.com/opencontainers/image-spec/specs-go/v1"
) )
@ -37,18 +37,22 @@ type Abstractor interface {
func NewAbstractor() Abstractor { func NewAbstractor() Abstractor {
return &abstractor{ return &abstractor{
artMgr: artifact.Mgr, artMgr: artifact.Mgr,
blobFetcher: blob.Fcher, regCli: registry.Cli,
} }
} }
type abstractor struct { type abstractor struct {
artMgr artifact.Manager artMgr artifact.Manager
blobFetcher blob.Fetcher regCli registry.Client
} }
func (a *abstractor) AbstractMetadata(ctx context.Context, artifact *artifact.Artifact) error { func (a *abstractor) AbstractMetadata(ctx context.Context, artifact *artifact.Artifact) error {
// read manifest content // read manifest content
manifestMediaType, content, err := a.blobFetcher.FetchManifest(artifact.RepositoryName, artifact.Digest) manifest, _, err := a.regCli.PullManifest(artifact.RepositoryName, artifact.Digest)
if err != nil {
return err
}
manifestMediaType, content, err := manifest.Payload()
if err != nil { if err != nil {
return err return err
} }

View File

@ -15,12 +15,13 @@
package artifact package artifact
import ( import (
"github.com/docker/distribution"
"github.com/docker/distribution/manifest/schema1" "github.com/docker/distribution/manifest/schema1"
"github.com/docker/distribution/manifest/schema2" "github.com/docker/distribution/manifest/schema2"
"github.com/goharbor/harbor/src/api/artifact/processor" "github.com/goharbor/harbor/src/api/artifact/processor"
"github.com/goharbor/harbor/src/pkg/artifact" "github.com/goharbor/harbor/src/pkg/artifact"
"github.com/goharbor/harbor/src/testing/api/artifact/processor/blob"
tart "github.com/goharbor/harbor/src/testing/pkg/artifact" tart "github.com/goharbor/harbor/src/testing/pkg/artifact"
"github.com/goharbor/harbor/src/testing/pkg/registry"
v1 "github.com/opencontainers/image-spec/specs-go/v1" v1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"testing" "testing"
@ -28,46 +29,124 @@ import (
var ( var (
v1Manifest = `{ v1Manifest = `{
"name": "hello-world", "schemaVersion": 1,
"tag": "latest", "name": "library/node",
"tag": "5.5-onbuild",
"architecture": "amd64", "architecture": "amd64",
"fsLayers": [ "fsLayers": [
{ {
"blobSum": "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef" "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
}, },
{ {
"blobSum": "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef" "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
}, },
{ {
"blobSum": "sha256:cc8567d70002e957612902a8e985ea129d831ebe04057d88fb644857caa45d11" "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
}, },
{ {
"blobSum": "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef" "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
},
{
"blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
},
{
"blobSum": "sha256:e2f0af7be4d7ec1946e55d4edddf90f768fd622573b8f1f0a19fa3a087b11936"
},
{
"blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
},
{
"blobSum": "sha256:bb0313a4938416446d43fb6fc25c73d4b495575ae0b537ad2ffa0bb081a99916"
},
{
"blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
},
{
"blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
},
{
"blobSum": "sha256:9e9f27c613944beb01ac418fef42a04eb021787a0eef0126b2c73604a57a1384"
},
{
"blobSum": "sha256:7a0c192d4d2536499ef0c65fa1c60e27ad39b4c4dcb9c703114bb8dc67f8fa5c"
},
{
"blobSum": "sha256:6ecee6444751349ab3731ee4e10f40b93e98af06a70349ca66962b2c80c5cce2"
},
{
"blobSum": "sha256:9269ba3950bb316abe52dc7010b0758b760e887a0d41af177162a55b2722bab7"
},
{
"blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
},
{
"blobSum": "sha256:03e1855d4f316edea9545408dcac38be93e9ea6aba6e85610edf76db7ccbbfa7"
} }
], ],
"history": [ "history": [
{ {
"v1Compatibility": "{\"id\":\"e45a5af57b00862e5ef5782a9925979a02ba2b12dff832fd0991335f4a11e5c5\",\"parent\":\"31cbccb51277105ba3ae35ce33c22b69c9e3f1002e76e4c736a2e8ebff9d7b5d\",\"created\":\"2014-12-31T22:57:59.178729048Z\",\"container\":\"27b45f8fb11795b52e9605b686159729b0d9ca92f76d40fb4f05a62e19c46b4f\",\"container_config\":{\"Hostname\":\"8ce6509d66e2\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) CMD [/hello]\"],\"Image\":\"31cbccb51277105ba3ae35ce33c22b69c9e3f1002e76e4c736a2e8ebff9d7b5d\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[],\"SecurityOpt\":null,\"Labels\":null},\"docker_version\":\"1.4.1\",\"config\":{\"Hostname\":\"8ce6509d66e2\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/hello\"],\"Image\":\"31cbccb51277105ba3ae35ce33c22b69c9e3f1002e76e4c736a2e8ebff9d7b5d\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[],\"SecurityOpt\":null,\"Labels\":null},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":0}\n" "v1Compatibility": "{\"id\":\"1520dbfa834708e58189bd7ad3ddfe5251fbdab020d274e2f2934b193fedce3e\",\"parent\":\"892e1bee0938dd0f1e6cbe4fda1f9d8efb8529c1c7a6469302b2a616541d5c74\",\"created\":\"2016-01-26T16:54:31.506284103Z\",\"container\":\"57db0fd5498375241dfce628a92a28df825f6c6a185119032760f79802477074\",\"container_config\":{\"Hostname\":\"e06f5a03fe1f\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"NPM_CONFIG_LOGLEVEL=info\",\"NODE_VERSION=5.5.0\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) CMD [\\\"npm\\\" \\\"start\\\"]\"],\"Image\":\"892e1bee0938dd0f1e6cbe4fda1f9d8efb8529c1c7a6469302b2a616541d5c74\",\"Volumes\":null,\"WorkingDir\":\"/usr/src/app\",\"Entrypoint\":null,\"OnBuild\":[\"COPY package.json /usr/src/app/\",\"RUN npm install\",\"COPY . /usr/src/app\"],\"Labels\":null},\"docker_version\":\"1.8.3\",\"config\":{\"Hostname\":\"e06f5a03fe1f\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"NPM_CONFIG_LOGLEVEL=info\",\"NODE_VERSION=5.5.0\"],\"Cmd\":[\"npm\",\"start\"],\"Image\":\"892e1bee0938dd0f1e6cbe4fda1f9d8efb8529c1c7a6469302b2a616541d5c74\",\"Volumes\":null,\"WorkingDir\":\"/usr/src/app\",\"Entrypoint\":null,\"OnBuild\":[\"COPY package.json /usr/src/app/\",\"RUN npm install\",\"COPY . /usr/src/app\"],\"Labels\":null},\"architecture\":\"amd64\",\"os\":\"linux\"}"
}, },
{ {
"v1Compatibility": "{\"id\":\"e45a5af57b00862e5ef5782a9925979a02ba2b12dff832fd0991335f4a11e5c5\",\"parent\":\"31cbccb51277105ba3ae35ce33c22b69c9e3f1002e76e4c736a2e8ebff9d7b5d\",\"created\":\"2014-12-31T22:57:59.178729048Z\",\"container\":\"27b45f8fb11795b52e9605b686159729b0d9ca92f76d40fb4f05a62e19c46b4f\",\"container_config\":{\"Hostname\":\"8ce6509d66e2\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) CMD [/hello]\"],\"Image\":\"31cbccb51277105ba3ae35ce33c22b69c9e3f1002e76e4c736a2e8ebff9d7b5d\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[],\"SecurityOpt\":null,\"Labels\":null},\"docker_version\":\"1.4.1\",\"config\":{\"Hostname\":\"8ce6509d66e2\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/hello\"],\"Image\":\"31cbccb51277105ba3ae35ce33c22b69c9e3f1002e76e4c736a2e8ebff9d7b5d\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[],\"SecurityOpt\":null,\"Labels\":null},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":0}\n" "v1Compatibility": "{\"id\":\"892e1bee0938dd0f1e6cbe4fda1f9d8efb8529c1c7a6469302b2a616541d5c74\",\"parent\":\"c138a9cd4a0adb6c81597e39bcd0dba2d2c181b2ca9a1a6c521cfbd159d90d2a\",\"created\":\"2016-01-26T16:54:30.676634208Z\",\"container\":\"772e639526dff6564ea3922abbc05d63604be9fd1f068ab53684df7a949067be\",\"container_config\":{\"Hostname\":\"e06f5a03fe1f\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"NPM_CONFIG_LOGLEVEL=info\",\"NODE_VERSION=5.5.0\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) ONBUILD COPY . /usr/src/app\"],\"Image\":\"c138a9cd4a0adb6c81597e39bcd0dba2d2c181b2ca9a1a6c521cfbd159d90d2a\",\"Volumes\":null,\"WorkingDir\":\"/usr/src/app\",\"Entrypoint\":null,\"OnBuild\":[\"COPY package.json /usr/src/app/\",\"RUN npm install\",\"COPY . /usr/src/app\"],\"Labels\":null},\"docker_version\":\"1.8.3\",\"config\":{\"Hostname\":\"e06f5a03fe1f\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"NPM_CONFIG_LOGLEVEL=info\",\"NODE_VERSION=5.5.0\"],\"Cmd\":[\"node\"],\"Image\":\"c138a9cd4a0adb6c81597e39bcd0dba2d2c181b2ca9a1a6c521cfbd159d90d2a\",\"Volumes\":null,\"WorkingDir\":\"/usr/src/app\",\"Entrypoint\":null,\"OnBuild\":[\"COPY package.json /usr/src/app/\",\"RUN npm install\",\"COPY . /usr/src/app\"],\"Labels\":null},\"architecture\":\"amd64\",\"os\":\"linux\"}"
},
{
"v1Compatibility": "{\"id\":\"c138a9cd4a0adb6c81597e39bcd0dba2d2c181b2ca9a1a6c521cfbd159d90d2a\",\"parent\":\"3484e461ee7551398ff2a5fe7d29a7d8f7c13830f0f7629bd6e0f4d7853f3686\",\"created\":\"2016-01-26T16:54:30.007571536Z\",\"container\":\"2b3d627f133121fb0bdd6e656eb4efa4444a8af832760c59d7942b4b59e3ea18\",\"container_config\":{\"Hostname\":\"e06f5a03fe1f\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"NPM_CONFIG_LOGLEVEL=info\",\"NODE_VERSION=5.5.0\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) ONBUILD RUN npm install\"],\"Image\":\"3484e461ee7551398ff2a5fe7d29a7d8f7c13830f0f7629bd6e0f4d7853f3686\",\"Volumes\":null,\"WorkingDir\":\"/usr/src/app\",\"Entrypoint\":null,\"OnBuild\":[\"COPY package.json /usr/src/app/\",\"RUN npm install\"],\"Labels\":null},\"docker_version\":\"1.8.3\",\"config\":{\"Hostname\":\"e06f5a03fe1f\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"NPM_CONFIG_LOGLEVEL=info\",\"NODE_VERSION=5.5.0\"],\"Cmd\":[\"node\"],\"Image\":\"3484e461ee7551398ff2a5fe7d29a7d8f7c13830f0f7629bd6e0f4d7853f3686\",\"Volumes\":null,\"WorkingDir\":\"/usr/src/app\",\"Entrypoint\":null,\"OnBuild\":[\"COPY package.json /usr/src/app/\",\"RUN npm install\"],\"Labels\":null},\"architecture\":\"amd64\",\"os\":\"linux\"}"
},
{
"v1Compatibility": "{\"id\":\"3484e461ee7551398ff2a5fe7d29a7d8f7c13830f0f7629bd6e0f4d7853f3686\",\"parent\":\"f8858c27980847a58f59954e09a2a9588ca56f878f8a9d9e9ca7f908a0d4424a\",\"created\":\"2016-01-26T16:54:29.347805328Z\",\"container\":\"c11f79f8aa2a23230f9d44618e5a50230f30a585cfc81cc089ce4848ef1ea97b\",\"container_config\":{\"Hostname\":\"e06f5a03fe1f\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"NPM_CONFIG_LOGLEVEL=info\",\"NODE_VERSION=5.5.0\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) ONBUILD COPY package.json /usr/src/app/\"],\"Image\":\"f8858c27980847a58f59954e09a2a9588ca56f878f8a9d9e9ca7f908a0d4424a\",\"Volumes\":null,\"WorkingDir\":\"/usr/src/app\",\"Entrypoint\":null,\"OnBuild\":[\"COPY package.json /usr/src/app/\"],\"Labels\":null},\"docker_version\":\"1.8.3\",\"config\":{\"Hostname\":\"e06f5a03fe1f\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"NPM_CONFIG_LOGLEVEL=info\",\"NODE_VERSION=5.5.0\"],\"Cmd\":[\"node\"],\"Image\":\"f8858c27980847a58f59954e09a2a9588ca56f878f8a9d9e9ca7f908a0d4424a\",\"Volumes\":null,\"WorkingDir\":\"/usr/src/app\",\"Entrypoint\":null,\"OnBuild\":[\"COPY package.json /usr/src/app/\"],\"Labels\":null},\"architecture\":\"amd64\",\"os\":\"linux\"}"
},
{
"v1Compatibility": "{\"id\":\"f8858c27980847a58f59954e09a2a9588ca56f878f8a9d9e9ca7f908a0d4424a\",\"parent\":\"f64ab7978e6acb948555df37760590f64ea93ad1d2c23fce5a5658266d24d432\",\"created\":\"2016-01-26T16:54:28.738290402Z\",\"container\":\"a408ee4aa153f75028302c2459f75e96f20da4b95f4cce7c50252f617c4fc215\",\"container_config\":{\"Hostname\":\"e06f5a03fe1f\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"NPM_CONFIG_LOGLEVEL=info\",\"NODE_VERSION=5.5.0\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) WORKDIR /usr/src/app\"],\"Image\":\"f64ab7978e6acb948555df37760590f64ea93ad1d2c23fce5a5658266d24d432\",\"Volumes\":null,\"WorkingDir\":\"/usr/src/app\",\"Entrypoint\":null,\"OnBuild\":[],\"Labels\":null},\"docker_version\":\"1.8.3\",\"config\":{\"Hostname\":\"e06f5a03fe1f\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"NPM_CONFIG_LOGLEVEL=info\",\"NODE_VERSION=5.5.0\"],\"Cmd\":[\"node\"],\"Image\":\"f64ab7978e6acb948555df37760590f64ea93ad1d2c23fce5a5658266d24d432\",\"Volumes\":null,\"WorkingDir\":\"/usr/src/app\",\"Entrypoint\":null,\"OnBuild\":[],\"Labels\":null},\"architecture\":\"amd64\",\"os\":\"linux\"}"
},
{
"v1Compatibility": "{\"id\":\"f64ab7978e6acb948555df37760590f64ea93ad1d2c23fce5a5658266d24d432\",\"parent\":\"5f8d821c760f574dd96974b4d70bc442f79b9f56ffe23530d2523eef026b152f\",\"created\":\"2016-01-26T16:54:28.066325756Z\",\"container\":\"ec647c177e1391cfa1cd60fa0a58147af0cb4b0b932a4c42b0932cf04444d76c\",\"container_config\":{\"Hostname\":\"e06f5a03fe1f\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"NPM_CONFIG_LOGLEVEL=info\",\"NODE_VERSION=5.5.0\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"mkdir -p /usr/src/app\"],\"Image\":\"5f8d821c760f574dd96974b4d70bc442f79b9f56ffe23530d2523eef026b152f\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":[],\"Labels\":null},\"docker_version\":\"1.8.3\",\"config\":{\"Hostname\":\"e06f5a03fe1f\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"NPM_CONFIG_LOGLEVEL=info\",\"NODE_VERSION=5.5.0\"],\"Cmd\":[\"node\"],\"Image\":\"5f8d821c760f574dd96974b4d70bc442f79b9f56ffe23530d2523eef026b152f\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":[],\"Labels\":null},\"architecture\":\"amd64\",\"os\":\"linux\"}"
},
{
"v1Compatibility": "{\"id\":\"5f8d821c760f574dd96974b4d70bc442f79b9f56ffe23530d2523eef026b152f\",\"parent\":\"ebcf22a55440806b2cf690333dc39f6321deda73a34a790468f3ef58e459eeb6\",\"created\":\"2016-01-26T16:52:50.89027915Z\",\"container\":\"db127a54c3e12c478d144a0e480ee7f4ac3909d1440e9372bf54be82507ec5d7\",\"container_config\":{\"Hostname\":\"e06f5a03fe1f\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"NPM_CONFIG_LOGLEVEL=info\",\"NODE_VERSION=5.5.0\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) CMD [\\\"node\\\"]\"],\"Image\":\"ebcf22a55440806b2cf690333dc39f6321deda73a34a790468f3ef58e459eeb6\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":[],\"Labels\":null},\"docker_version\":\"1.8.3\",\"config\":{\"Hostname\":\"e06f5a03fe1f\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"NPM_CONFIG_LOGLEVEL=info\",\"NODE_VERSION=5.5.0\"],\"Cmd\":[\"node\"],\"Image\":\"ebcf22a55440806b2cf690333dc39f6321deda73a34a790468f3ef58e459eeb6\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":[],\"Labels\":null},\"architecture\":\"amd64\",\"os\":\"linux\"}"
},
{
"v1Compatibility": "{\"id\":\"ebcf22a55440806b2cf690333dc39f6321deda73a34a790468f3ef58e459eeb6\",\"parent\":\"20ed370cdb6e6e36c94a673270155b7edc669e583f870e48833125f828e89e65\",\"created\":\"2016-01-26T16:52:45.83954478Z\",\"container\":\"5cbb1dc61fe39c8f13e41bb72956f8764c828f79c18d5dfcc6b1ff111e88b997\",\"container_config\":{\"Hostname\":\"e06f5a03fe1f\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"NPM_CONFIG_LOGLEVEL=info\",\"NODE_VERSION=5.5.0\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"curl -SLO \\\"https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.gz\\\" \\u0026\\u0026 curl -SLO \\\"https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc\\\" \\u0026\\u0026 gpg --verify SHASUMS256.txt.asc \\u0026\\u0026 grep \\\" node-v$NODE_VERSION-linux-x64.tar.gz\\\\$\\\" SHASUMS256.txt.asc | sha256sum -c - \\u0026\\u0026 tar -xzf \\\"node-v$NODE_VERSION-linux-x64.tar.gz\\\" -C /usr/local --strip-components=1 \\u0026\\u0026 rm \\\"node-v$NODE_VERSION-linux-x64.tar.gz\\\" SHASUMS256.txt.asc\"],\"Image\":\"20ed370cdb6e6e36c94a673270155b7edc669e583f870e48833125f828e89e65\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":[],\"Labels\":null},\"docker_version\":\"1.8.3\",\"config\":{\"Hostname\":\"e06f5a03fe1f\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"NPM_CONFIG_LOGLEVEL=info\",\"NODE_VERSION=5.5.0\"],\"Cmd\":[\"/bin/bash\"],\"Image\":\"20ed370cdb6e6e36c94a673270155b7edc669e583f870e48833125f828e89e65\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":[],\"Labels\":null},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":36385291}"
},
{
"v1Compatibility": "{\"id\":\"20ed370cdb6e6e36c94a673270155b7edc669e583f870e48833125f828e89e65\",\"parent\":\"8ab6f3fcbdb58860b302cb53d3c090d1e4a56cc5a4ac548724be143f292bbd08\",\"created\":\"2016-01-26T16:52:38.484938978Z\",\"container\":\"db88a6dff38e4b3fc8798f5fbe7cff5a9c3a1fa2627fcb7601e627a18ef1359d\",\"container_config\":{\"Hostname\":\"e06f5a03fe1f\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"NPM_CONFIG_LOGLEVEL=info\",\"NODE_VERSION=5.5.0\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) ENV NODE_VERSION=5.5.0\"],\"Image\":\"8ab6f3fcbdb58860b302cb53d3c090d1e4a56cc5a4ac548724be143f292bbd08\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":[],\"Labels\":null},\"docker_version\":\"1.8.3\",\"config\":{\"Hostname\":\"e06f5a03fe1f\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"NPM_CONFIG_LOGLEVEL=info\",\"NODE_VERSION=5.5.0\"],\"Cmd\":[\"/bin/bash\"],\"Image\":\"8ab6f3fcbdb58860b302cb53d3c090d1e4a56cc5a4ac548724be143f292bbd08\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":[],\"Labels\":null},\"architecture\":\"amd64\",\"os\":\"linux\"}"
},
{
"v1Compatibility": "{\"id\":\"8ab6f3fcbdb58860b302cb53d3c090d1e4a56cc5a4ac548724be143f292bbd08\",\"parent\":\"ddfb2360ce1e908d5ecb4b678ee10686ba28a4fdcc68d70177d8fbafcaf2da24\",\"created\":\"2016-01-26T16:44:56.182612087Z\",\"container\":\"c510a06b1df9a9989f02337b1c0cbe0c549381e8d6d2f8f6e660328944c0186e\",\"container_config\":{\"Hostname\":\"e06f5a03fe1f\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"NPM_CONFIG_LOGLEVEL=info\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) ENV NPM_CONFIG_LOGLEVEL=info\"],\"Image\":\"ddfb2360ce1e908d5ecb4b678ee10686ba28a4fdcc68d70177d8fbafcaf2da24\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":[],\"Labels\":null},\"docker_version\":\"1.8.3\",\"config\":{\"Hostname\":\"e06f5a03fe1f\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"NPM_CONFIG_LOGLEVEL=info\"],\"Cmd\":[\"/bin/bash\"],\"Image\":\"ddfb2360ce1e908d5ecb4b678ee10686ba28a4fdcc68d70177d8fbafcaf2da24\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":[],\"Labels\":null},\"architecture\":\"amd64\",\"os\":\"linux\"}"
},
{
"v1Compatibility": "{\"id\":\"ddfb2360ce1e908d5ecb4b678ee10686ba28a4fdcc68d70177d8fbafcaf2da24\",\"parent\":\"9536cbaf1242bbc772d382c828ad4c8d317fcd63ef9cde05f9cb4cd4b6871236\",\"created\":\"2016-01-26T16:38:21.781529683Z\",\"container\":\"f2ef38095c6c3a99e64a14d3d433f0a7ed7ecef2264afe40ccbeb93b38bc77d9\",\"container_config\":{\"Hostname\":\"e06f5a03fe1f\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"set -ex \\u0026\\u0026 for key in 9554F04D7259F04124DE6B476D5A82AC7E37093B 94AE36675C464D64BAFA68DD7434390BDBE9B9C5 0034A06D9D9B0064CE8ADF6BF1747F4AD2306D93 FD3A5288F042B6850C66B31F09FE44734EB7990E 71DCFD284A79C3B38668286BC97EC7A07EDE3FC1 DD8F2338BAE7501E3DD5AC78C273792F7D83545D B9AE9905FFD7803F25714661B63B535A4C206CA9 C4F0DFFF4E8C1A8236409D08E73BC641CC11F4C8 ; do gpg --keyserver ha.pool.sks-keyservers.net --recv-keys \\\"$key\\\"; done\"],\"Image\":\"9536cbaf1242bbc772d382c828ad4c8d317fcd63ef9cde05f9cb4cd4b6871236\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":[],\"Labels\":null},\"docker_version\":\"1.8.3\",\"config\":{\"Hostname\":\"e06f5a03fe1f\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/bin/bash\"],\"Image\":\"9536cbaf1242bbc772d382c828ad4c8d317fcd63ef9cde05f9cb4cd4b6871236\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":[],\"Labels\":null},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":51753}"
},
{
"v1Compatibility": "{\"id\":\"9536cbaf1242bbc772d382c828ad4c8d317fcd63ef9cde05f9cb4cd4b6871236\",\"parent\":\"0288ae931294ce04f5d69c60146faca7d9be8de4004421d650f4227fa60bd92b\",\"created\":\"2016-01-25T22:31:08.823570982Z\",\"container\":\"528b705b36c3a1ae37343eec7824283170b0bffe8b40f16f830eab723ac2f08d\",\"container_config\":{\"Hostname\":\"e06f5a03fe1f\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"apt-get update \\u0026\\u0026 apt-get install -y --no-install-recommends \\t\\tautoconf \\t\\tautomake \\t\\tbzip2 \\t\\tfile \\t\\tg++ \\t\\tgcc \\t\\timagemagick \\t\\tlibbz2-dev \\t\\tlibc6-dev \\t\\tlibcurl4-openssl-dev \\t\\tlibevent-dev \\t\\tlibffi-dev \\t\\tlibgeoip-dev \\t\\tlibglib2.0-dev \\t\\tlibjpeg-dev \\t\\tliblzma-dev \\t\\tlibmagickcore-dev \\t\\tlibmagickwand-dev \\t\\tlibmysqlclient-dev \\t\\tlibncurses-dev \\t\\tlibpng-dev \\t\\tlibpq-dev \\t\\tlibreadline-dev \\t\\tlibsqlite3-dev \\t\\tlibssl-dev \\t\\tlibtool \\t\\tlibwebp-dev \\t\\tlibxml2-dev \\t\\tlibxslt-dev \\t\\tlibyaml-dev \\t\\tmake \\t\\tpatch \\t\\txz-utils \\t\\tzlib1g-dev \\t\\u0026\\u0026 rm -rf /var/lib/apt/lists/*\"],\"Image\":\"0288ae931294ce04f5d69c60146faca7d9be8de4004421d650f4227fa60bd92b\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":[],\"Labels\":null},\"docker_version\":\"1.8.3\",\"config\":{\"Hostname\":\"e06f5a03fe1f\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/bin/bash\"],\"Image\":\"0288ae931294ce04f5d69c60146faca7d9be8de4004421d650f4227fa60bd92b\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":[],\"Labels\":null},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":314656819}"
},
{
"v1Compatibility": "{\"id\":\"0288ae931294ce04f5d69c60146faca7d9be8de4004421d650f4227fa60bd92b\",\"parent\":\"9287fae7a16e8788603ae069270aa825457065062247f4c04d4983f00eba37a6\",\"created\":\"2016-01-25T22:29:12.503492968Z\",\"container\":\"a0533596d15ff539859472684f7e700042f357d02fa0c1fb6c5d8a1feac6c574\",\"container_config\":{\"Hostname\":\"e06f5a03fe1f\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"apt-get update \\u0026\\u0026 apt-get install -y --no-install-recommends \\t\\tbzr \\t\\tgit \\t\\tmercurial \\t\\topenssh-client \\t\\tsubversion \\t\\t\\t\\tprocps \\t\\u0026\\u0026 rm -rf /var/lib/apt/lists/*\"],\"Image\":\"9287fae7a16e8788603ae069270aa825457065062247f4c04d4983f00eba37a6\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":[],\"Labels\":null},\"docker_version\":\"1.8.3\",\"config\":{\"Hostname\":\"e06f5a03fe1f\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/bin/bash\"],\"Image\":\"9287fae7a16e8788603ae069270aa825457065062247f4c04d4983f00eba37a6\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":[],\"Labels\":null},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":122576525}"
},
{
"v1Compatibility": "{\"id\":\"9287fae7a16e8788603ae069270aa825457065062247f4c04d4983f00eba37a6\",\"parent\":\"5eb1402f041415f4d72ec331c9388e4981420dfe88ef4e9bdf904d4687e4de09\",\"created\":\"2016-01-25T22:28:10.88750042Z\",\"container\":\"ce5ccec57f456f36a78b32dad3a696a215ff0201270d47ee1c2f64a52508297a\",\"container_config\":{\"Hostname\":\"e06f5a03fe1f\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"apt-get update \\u0026\\u0026 apt-get install -y --no-install-recommends \\t\\tca-certificates \\t\\tcurl \\t\\twget \\t\\u0026\\u0026 rm -rf /var/lib/apt/lists/*\"],\"Image\":\"5eb1402f041415f4d72ec331c9388e4981420dfe88ef4e9bdf904d4687e4de09\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":[],\"Labels\":null},\"docker_version\":\"1.8.3\",\"config\":{\"Hostname\":\"e06f5a03fe1f\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/bin/bash\"],\"Image\":\"5eb1402f041415f4d72ec331c9388e4981420dfe88ef4e9bdf904d4687e4de09\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":[],\"Labels\":null},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":44300304}"
},
{
"v1Compatibility": "{\"id\":\"5eb1402f041415f4d72ec331c9388e4981420dfe88ef4e9bdf904d4687e4de09\",\"parent\":\"77e39ee8211729e81d1f83f0c64fdef97979b930a97ddc8194b8ea46d49f7b50\",\"created\":\"2016-01-25T22:24:37.914712562Z\",\"container\":\"c59024072143b04b79ac341c51571fc698636e01c13b49c523309c84af4b70fe\",\"container_config\":{\"Hostname\":\"e06f5a03fe1f\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) CMD [\\\"/bin/bash\\\"]\"],\"Image\":\"77e39ee8211729e81d1f83f0c64fdef97979b930a97ddc8194b8ea46d49f7b50\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":null,\"Labels\":null},\"docker_version\":\"1.8.3\",\"config\":{\"Hostname\":\"e06f5a03fe1f\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/bash\"],\"Image\":\"77e39ee8211729e81d1f83f0c64fdef97979b930a97ddc8194b8ea46d49f7b50\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":null,\"Labels\":null},\"architecture\":\"amd64\",\"os\":\"linux\"}"
},
{
"v1Compatibility": "{\"id\":\"77e39ee8211729e81d1f83f0c64fdef97979b930a97ddc8194b8ea46d49f7b50\",\"created\":\"2016-01-25T22:24:35.279128653Z\",\"container\":\"e06f5a03fe1f6755f98fb354799db823a95e6c141ae40a2cb7ad7a6b09d41208\",\"container_config\":{\"Hostname\":\"e06f5a03fe1f\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) ADD file:e5a3d20748c5d3dd5fa11542dfa4ef8b72a0bb78ce09f6dae30eff5d045c67aa in /\"],\"Image\":\"\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":null,\"Labels\":null},\"docker_version\":\"1.8.3\",\"config\":{\"Hostname\":\"e06f5a03fe1f\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":null,\"Image\":\"\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":null,\"Labels\":null},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":125082947}"
} }
], ],
"schemaVersion": 1,
"signatures": [ "signatures": [
{ {
"header": { "header": {
"jwk": { "jwk": {
"crv": "P-256", "crv": "P-256",
"kid": "OD6I:6DRK:JXEJ:KBM4:255X:NSAA:MUSF:E4VM:ZI6W:CUN2:L4Z6:LSF4", "kid": "KG7S:QIPL:FTS3:YAKZ:AADA:4GML:ITLH:7APP:O4F7:2NBA:A4IN:CWVF",
"kty": "EC", "kty": "EC",
"x": "3gAwX48IQ5oaYQAYSxor6rYYc_6yjuLCjtQ9LUakg4A", "x": "PUiT1kV7Xf-U8M54gpCzvPc5mDUX9BjvizdBgy3oTsI",
"y": "t72ge6kIA1XOjqjVoEOiPPAURltJFBMGDSQvEGVB010" "y": "BAgeQchl9QibzPP2Qp_-gJMWr682QVWoHy52hLRHZ04"
}, },
"alg": "ES256" "alg": "ES256"
}, },
"signature": "XREm0L8WNn27Ga_iE_vRnTxVMhhYY0Zst_FfkKopg6gWSoTOZTuW4rK0fg_IqnKkEKlbD83tD46LKEGi5aIVFg", "signature": "mfUOI0pPzkdceAKAFRMkrQVgeE9X7if43LEtfs5XvdyxO7lCG0fiVxmdi-KGaQu4lRsIfRNq6m5agNTm8u5DrA",
"protected": "eyJmb3JtYXRMZW5ndGgiOjY2MjgsImZvcm1hdFRhaWwiOiJDbjAiLCJ0aW1lIjoiMjAxNS0wNC0wOFQxODo1Mjo1OVoifQ" "protected": "eyJmb3JtYXRMZW5ndGgiOjI3NTE4LCJmb3JtYXRUYWlsIjoiQ24wIiwidGltZSI6IjIwMjAtMDMtMTdUMTA6NTk6MDhaIn0"
} }
] ]
}` }`
@ -122,16 +201,16 @@ var (
type abstractorTestSuite struct { type abstractorTestSuite struct {
suite.Suite suite.Suite
argMgr *tart.FakeManager argMgr *tart.FakeManager
fetcher *blob.FakeFetcher regCli *registry.FakeClient
abstractor *abstractor abstractor *abstractor
} }
func (a *abstractorTestSuite) SetupTest() { func (a *abstractorTestSuite) SetupTest() {
a.fetcher = &blob.FakeFetcher{} a.regCli = &registry.FakeClient{}
a.argMgr = &tart.FakeManager{} a.argMgr = &tart.FakeManager{}
a.abstractor = &abstractor{ a.abstractor = &abstractor{
artMgr: a.argMgr, artMgr: a.argMgr,
blobFetcher: a.fetcher, regCli: a.regCli,
} }
// clear all registered processors // clear all registered processors
processor.Registry = map[string]processor.Processor{} processor.Registry = map[string]processor.Processor{}
@ -139,11 +218,13 @@ func (a *abstractorTestSuite) SetupTest() {
// docker manifest v1 // docker manifest v1
func (a *abstractorTestSuite) TestAbstractMetadataOfV1Manifest() { func (a *abstractorTestSuite) TestAbstractMetadataOfV1Manifest() {
a.fetcher.On("FetchManifest").Return(schema1.MediaTypeSignedManifest, []byte(v1Manifest), nil) manifest, _, err := distribution.UnmarshalManifest(schema1.MediaTypeSignedManifest, []byte(v1Manifest))
a.Require().Nil(err)
a.regCli.On("PullManifest").Return(manifest, "", nil)
artifact := &artifact.Artifact{ artifact := &artifact.Artifact{
ID: 1, ID: 1,
} }
err := a.abstractor.AbstractMetadata(nil, artifact) err = a.abstractor.AbstractMetadata(nil, artifact)
a.Require().Nil(err) a.Require().Nil(err)
a.Assert().Equal(int64(1), artifact.ID) a.Assert().Equal(int64(1), artifact.ID)
a.Assert().Equal(schema1.MediaTypeSignedManifest, artifact.ManifestMediaType) a.Assert().Equal(schema1.MediaTypeSignedManifest, artifact.ManifestMediaType)
@ -153,11 +234,13 @@ func (a *abstractorTestSuite) TestAbstractMetadataOfV1Manifest() {
// docker manifest v2 // docker manifest v2
func (a *abstractorTestSuite) TestAbstractMetadataOfV2Manifest() { func (a *abstractorTestSuite) TestAbstractMetadataOfV2Manifest() {
a.fetcher.On("FetchManifest").Return(schema2.MediaTypeManifest, []byte(v2Manifest), nil) manifest, _, err := distribution.UnmarshalManifest(schema2.MediaTypeManifest, []byte(v2Manifest))
a.Require().Nil(err)
a.regCli.On("PullManifest").Return(manifest, "", nil)
artifact := &artifact.Artifact{ artifact := &artifact.Artifact{
ID: 1, ID: 1,
} }
err := a.abstractor.AbstractMetadata(nil, artifact) err = a.abstractor.AbstractMetadata(nil, artifact)
a.Require().Nil(err) a.Require().Nil(err)
a.Assert().Equal(int64(1), artifact.ID) a.Assert().Equal(int64(1), artifact.ID)
a.Assert().Equal(schema2.MediaTypeManifest, artifact.ManifestMediaType) a.Assert().Equal(schema2.MediaTypeManifest, artifact.ManifestMediaType)
@ -169,7 +252,9 @@ func (a *abstractorTestSuite) TestAbstractMetadataOfV2Manifest() {
// OCI index // OCI index
func (a *abstractorTestSuite) TestAbstractMetadataOfIndex() { func (a *abstractorTestSuite) TestAbstractMetadataOfIndex() {
a.fetcher.On("FetchManifest").Return(v1.MediaTypeImageIndex, []byte(index), nil) manifest, _, err := distribution.UnmarshalManifest(v1.MediaTypeImageIndex, []byte(index))
a.Require().Nil(err)
a.regCli.On("PullManifest").Return(manifest, "", nil)
a.argMgr.On("GetByDigest").Return(&artifact.Artifact{ a.argMgr.On("GetByDigest").Return(&artifact.Artifact{
ID: 2, ID: 2,
Size: 10, Size: 10,
@ -177,7 +262,7 @@ func (a *abstractorTestSuite) TestAbstractMetadataOfIndex() {
artifact := &artifact.Artifact{ artifact := &artifact.Artifact{
ID: 1, ID: 1,
} }
err := a.abstractor.AbstractMetadata(nil, artifact) err = a.abstractor.AbstractMetadata(nil, artifact)
a.Require().Nil(err) a.Require().Nil(err)
a.Assert().Equal(int64(1), artifact.ID) a.Assert().Equal(int64(1), artifact.ID)
a.Assert().Equal(v1.MediaTypeImageIndex, artifact.ManifestMediaType) a.Assert().Equal(v1.MediaTypeImageIndex, artifact.ManifestMediaType)
@ -188,9 +273,18 @@ func (a *abstractorTestSuite) TestAbstractMetadataOfIndex() {
a.Len(artifact.References, 2) a.Len(artifact.References, 2)
} }
// OCI index type unknownManifest struct{}
func (u *unknownManifest) References() []distribution.Descriptor {
return nil
}
func (u *unknownManifest) Payload() (mediaType string, payload []byte, err error) {
return "unknown-manifest", nil, nil
}
// unknown
func (a *abstractorTestSuite) TestAbstractMetadataOfUnsupported() { func (a *abstractorTestSuite) TestAbstractMetadataOfUnsupported() {
a.fetcher.On("FetchManifest").Return("unsupported-manifest", []byte{}, nil) a.regCli.On("PullManifest").Return(&unknownManifest{}, "", nil)
artifact := &artifact.Artifact{ artifact := &artifact.Artifact{
ID: 1, ID: 1,
} }

View File

@ -19,11 +19,20 @@ import (
"github.com/goharbor/harbor/src/api/artifact/processor" "github.com/goharbor/harbor/src/api/artifact/processor"
ierror "github.com/goharbor/harbor/src/internal/error" ierror "github.com/goharbor/harbor/src/internal/error"
"github.com/goharbor/harbor/src/pkg/artifact" "github.com/goharbor/harbor/src/pkg/artifact"
"github.com/goharbor/harbor/src/pkg/registry"
) )
// NewIndexProcessor creates a new base index processor.
func NewIndexProcessor() *IndexProcessor {
return &IndexProcessor{
RegCli: registry.Cli,
}
}
// IndexProcessor is a base processor to process artifact enveloped by OCI index or docker manifest list // IndexProcessor is a base processor to process artifact enveloped by OCI index or docker manifest list
// Currently, it is just a null implementation // Currently, it is just a null implementation
type IndexProcessor struct { type IndexProcessor struct {
RegCli registry.Client
} }
// AbstractMetadata abstracts metadata of artifact // AbstractMetadata abstracts metadata of artifact

View File

@ -18,9 +18,9 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"github.com/goharbor/harbor/src/api/artifact/processor" "github.com/goharbor/harbor/src/api/artifact/processor"
"github.com/goharbor/harbor/src/api/artifact/processor/blob"
ierror "github.com/goharbor/harbor/src/internal/error" ierror "github.com/goharbor/harbor/src/internal/error"
"github.com/goharbor/harbor/src/pkg/artifact" "github.com/goharbor/harbor/src/pkg/artifact"
"github.com/goharbor/harbor/src/pkg/registry"
"github.com/opencontainers/image-spec/specs-go/v1" "github.com/opencontainers/image-spec/specs-go/v1"
) )
@ -29,14 +29,14 @@ import (
func NewManifestProcessor(properties ...string) *ManifestProcessor { func NewManifestProcessor(properties ...string) *ManifestProcessor {
return &ManifestProcessor{ return &ManifestProcessor{
properties: properties, properties: properties,
BlobFetcher: blob.Fcher, RegCli: registry.Cli,
} }
} }
// ManifestProcessor is a base processor to process artifact enveloped by OCI manifest or docker v2 manifest // ManifestProcessor is a base processor to process artifact enveloped by OCI manifest or docker v2 manifest
type ManifestProcessor struct { type ManifestProcessor struct {
properties []string properties []string
BlobFetcher blob.Fetcher RegCli registry.Client
} }
// AbstractMetadata abstracts metadata of artifact // AbstractMetadata abstracts metadata of artifact
@ -47,13 +47,14 @@ func (m *ManifestProcessor) AbstractMetadata(ctx context.Context, content []byte
return err return err
} }
// get config layer // get config layer
layer, err := m.BlobFetcher.FetchLayer(artifact.RepositoryName, manifest.Config.Digest.String()) _, blob, err := m.RegCli.PullBlob(artifact.RepositoryName, manifest.Config.Digest.String())
if err != nil { if err != nil {
return err return err
} }
defer blob.Close()
// parse metadata from config layer // parse metadata from config layer
metadata := map[string]interface{}{} metadata := map[string]interface{}{}
if err := json.Unmarshal(layer, &metadata); err != nil { if err := json.NewDecoder(blob).Decode(&metadata); err != nil {
return err return err
} }
// if no properties specified, populate all metadata into the ExtraAttrs // if no properties specified, populate all metadata into the ExtraAttrs

View File

@ -15,10 +15,13 @@
package base package base
import ( import (
"github.com/goharbor/harbor/src/pkg/artifact" "io/ioutil"
"github.com/goharbor/harbor/src/testing/api/artifact/processor/blob" "strings"
"github.com/stretchr/testify/suite"
"testing" "testing"
"github.com/goharbor/harbor/src/pkg/artifact"
"github.com/goharbor/harbor/src/testing/pkg/registry"
"github.com/stretchr/testify/suite"
) )
const ( const (
@ -120,20 +123,21 @@ const (
type manifestTestSuite struct { type manifestTestSuite struct {
suite.Suite suite.Suite
processor *ManifestProcessor processor *ManifestProcessor
blobFetcher *blob.FakeFetcher regCli *registry.FakeClient
} }
func (m *manifestTestSuite) SetupTest() { func (m *manifestTestSuite) SetupTest() {
m.blobFetcher = &blob.FakeFetcher{} m.regCli = &registry.FakeClient{}
m.processor = &ManifestProcessor{ m.processor = &ManifestProcessor{
BlobFetcher: m.blobFetcher, RegCli: m.regCli,
} }
} }
func (m *manifestTestSuite) TestAbstractMetadata() { func (m *manifestTestSuite) TestAbstractMetadata() {
// abstract all properties // abstract all properties
art := &artifact.Artifact{} art := &artifact.Artifact{}
m.blobFetcher.On("FetchLayer").Return([]byte(config), nil)
m.regCli.On("PullBlob").Return(0, ioutil.NopCloser(strings.NewReader(config)), nil)
m.processor.AbstractMetadata(nil, []byte(manifest), art) m.processor.AbstractMetadata(nil, []byte(manifest), art)
m.Len(art.ExtraAttrs, 9) m.Len(art.ExtraAttrs, 9)
@ -143,7 +147,7 @@ func (m *manifestTestSuite) TestAbstractMetadata() {
// abstract the specified properties // abstract the specified properties
m.processor.properties = []string{"os"} m.processor.properties = []string{"os"}
art = &artifact.Artifact{} art = &artifact.Artifact{}
m.blobFetcher.On("FetchLayer").Return([]byte(config), nil) m.regCli.On("PullBlob").Return(0, ioutil.NopCloser(strings.NewReader(config)), nil)
m.processor.AbstractMetadata(nil, []byte(manifest), art) m.processor.AbstractMetadata(nil, []byte(manifest), art)
m.Require().Len(art.ExtraAttrs, 1) m.Require().Len(art.ExtraAttrs, 1)
m.Equal("linux", art.ExtraAttrs["os"]) m.Equal("linux", art.ExtraAttrs["os"])

View File

@ -1,18 +0,0 @@
// Copyright Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package blob
// TODO add cache
// TODO cache content and mediatype

View File

@ -1,81 +0,0 @@
// Copyright Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package blob
import (
"github.com/docker/distribution/manifest/manifestlist"
"github.com/docker/distribution/manifest/schema1"
"github.com/docker/distribution/manifest/schema2"
"github.com/goharbor/harbor/src/pkg/registry"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
"io/ioutil"
)
var (
// Fcher is a global blob fetcher instance
Fcher = NewFetcher()
accept = []string{
schema1.MediaTypeSignedManifest,
schema2.MediaTypeManifest,
v1.MediaTypeImageManifest,
manifestlist.MediaTypeManifestList,
v1.MediaTypeImageIndex,
}
)
// TODO use the registry.Client directly? then the Fetcher can be deleted
// Fetcher fetches the content of blob
type Fetcher interface {
// FetchManifest the content of manifest under the repository
FetchManifest(repository, digest string) (mediaType string, content []byte, err error)
// FetchLayer the content of layer under the repository
FetchLayer(repository, digest string) (content []byte, err error)
}
// NewFetcher returns an instance of the default blob fetcher
func NewFetcher() Fetcher {
return &fetcher{
client: registry.Cli,
}
}
type fetcher struct {
client registry.Client
}
func (f *fetcher) FetchManifest(repository, digest string) (string, []byte, error) {
// TODO read from cache first
manifest, _, err := f.client.PullManifest(repository, digest)
if err != nil {
return "", nil, err
}
mediaType, payload, err := manifest.Payload()
if err != nil {
return "", nil, err
}
return mediaType, payload, err
}
func (f *fetcher) FetchLayer(repository, digest string) ([]byte, error) {
// TODO read from cache first
_, reader, err := f.client.PullBlob(repository, digest)
if err != nil {
return nil, err
}
defer reader.Close()
return ioutil.ReadAll(reader)
}

View File

@ -17,9 +17,10 @@ package chart
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"io/ioutil"
ps "github.com/goharbor/harbor/src/api/artifact/processor" ps "github.com/goharbor/harbor/src/api/artifact/processor"
"github.com/goharbor/harbor/src/api/artifact/processor/base" "github.com/goharbor/harbor/src/api/artifact/processor/base"
"github.com/goharbor/harbor/src/api/artifact/processor/blob"
"github.com/goharbor/harbor/src/common/utils/log" "github.com/goharbor/harbor/src/common/utils/log"
ierror "github.com/goharbor/harbor/src/internal/error" ierror "github.com/goharbor/harbor/src/internal/error"
"github.com/goharbor/harbor/src/pkg/artifact" "github.com/goharbor/harbor/src/pkg/artifact"
@ -35,13 +36,13 @@ const (
AdditionTypeReadme = "README.MD" AdditionTypeReadme = "README.MD"
AdditionTypeDependencies = "DEPENDENCIES" AdditionTypeDependencies = "DEPENDENCIES"
// TODO import it from helm chart repository // as helm put the media type definition under "internal" package, we cannot
// import it, defines it by ourselves
mediaType = "application/vnd.cncf.helm.config.v1+json" mediaType = "application/vnd.cncf.helm.config.v1+json"
) )
func init() { func init() {
pc := &processor{ pc := &processor{
blobFetcher: blob.Fcher,
chartOperator: chart.Optr, chartOperator: chart.Optr,
} }
pc.ManifestProcessor = base.NewManifestProcessor() pc.ManifestProcessor = base.NewManifestProcessor()
@ -53,7 +54,6 @@ func init() {
type processor struct { type processor struct {
*base.ManifestProcessor *base.ManifestProcessor
blobFetcher blob.Fetcher
chartOperator chart.Operator chartOperator chart.Operator
} }
@ -63,12 +63,16 @@ func (p *processor) AbstractAddition(ctx context.Context, artifact *artifact.Art
WithMessage("addition %s isn't supported for %s", addition, ArtifactTypeChart) WithMessage("addition %s isn't supported for %s", addition, ArtifactTypeChart)
} }
_, content, err := p.blobFetcher.FetchManifest(artifact.RepositoryName, artifact.Digest) m, _, err := p.RegCli.PullManifest(artifact.RepositoryName, artifact.Digest)
if err != nil {
return nil, err
}
_, payload, err := m.Payload()
if err != nil { if err != nil {
return nil, err return nil, err
} }
manifest := &v1.Manifest{} manifest := &v1.Manifest{}
if err := json.Unmarshal(content, manifest); err != nil { if err := json.Unmarshal(payload, manifest); err != nil {
return nil, err return nil, err
} }
@ -76,10 +80,15 @@ func (p *processor) AbstractAddition(ctx context.Context, artifact *artifact.Art
// chart do have two layers, one is config, we should resolve the other one. // chart do have two layers, one is config, we should resolve the other one.
layerDgst := layer.Digest.String() layerDgst := layer.Digest.String()
if layerDgst != manifest.Config.Digest.String() { if layerDgst != manifest.Config.Digest.String() {
content, err = p.blobFetcher.FetchLayer(artifact.RepositoryName, layerDgst) _, blob, err := p.RegCli.PullBlob(artifact.RepositoryName, layerDgst)
if err != nil { if err != nil {
return nil, err return nil, err
} }
content, err := ioutil.ReadAll(blob)
if err != nil {
return nil, err
}
blob.Close()
chartDetails, err := p.chartOperator.GetDetails(content) chartDetails, err := p.chartOperator.GetDetails(content)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -15,31 +15,35 @@
package chart package chart
import ( import (
"github.com/docker/distribution"
"github.com/goharbor/harbor/src/api/artifact/processor/base"
ierror "github.com/goharbor/harbor/src/internal/error" ierror "github.com/goharbor/harbor/src/internal/error"
"github.com/goharbor/harbor/src/pkg/artifact" "github.com/goharbor/harbor/src/pkg/artifact"
chartserver "github.com/goharbor/harbor/src/pkg/chart" chartserver "github.com/goharbor/harbor/src/pkg/chart"
"github.com/goharbor/harbor/src/testing/api/artifact/processor/blob"
"github.com/goharbor/harbor/src/testing/pkg/chart" "github.com/goharbor/harbor/src/testing/pkg/chart"
"github.com/goharbor/harbor/src/testing/pkg/registry"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
helm_chart "helm.sh/helm/v3/pkg/chart" helm_chart "helm.sh/helm/v3/pkg/chart"
"io/ioutil"
"strings"
"testing" "testing"
) )
type processorTestSuite struct { type processorTestSuite struct {
suite.Suite suite.Suite
processor *processor processor *processor
blobFetcher *blob.FakeFetcher regCli *registry.FakeClient
chartOptr *chart.FakeOpertaor chartOptr *chart.FakeOpertaor
} }
func (p *processorTestSuite) SetupTest() { func (p *processorTestSuite) SetupTest() {
p.blobFetcher = &blob.FakeFetcher{} p.regCli = &registry.FakeClient{}
p.chartOptr = &chart.FakeOpertaor{} p.chartOptr = &chart.FakeOpertaor{}
p.processor = &processor{ p.processor = &processor{
blobFetcher: p.blobFetcher,
chartOperator: p.chartOptr, chartOperator: p.chartOptr,
} }
p.processor.ManifestProcessor = &base.ManifestProcessor{RegCli: p.regCli}
} }
func (p *processorTestSuite) TestAbstractAddition() { func (p *processorTestSuite) TestAbstractAddition() {
@ -98,8 +102,10 @@ func (p *processorTestSuite) TestAbstractAddition() {
} }
artifact := &artifact.Artifact{} artifact := &artifact.Artifact{}
p.blobFetcher.On("FetchManifest").Return("", []byte(chartManifest), nil) manifest, _, err := distribution.UnmarshalManifest(v1.MediaTypeImageManifest, []byte(chartManifest))
p.blobFetcher.On("FetchLayer").Return([]byte(chartYaml), nil) p.Require().Nil(err)
p.regCli.On("PullManifest").Return(manifest, "", nil)
p.regCli.On("PullBlob").Return(0, ioutil.NopCloser(strings.NewReader(chartYaml)), nil)
p.chartOptr.On("GetDetails").Return(chartDetails, nil) p.chartOptr.On("GetDetails").Return(chartDetails, nil)
// values.yaml // values.yaml

View File

@ -16,9 +16,9 @@ package cnab
import ( import (
"context" "context"
ps "github.com/goharbor/harbor/src/api/artifact/processor" ps "github.com/goharbor/harbor/src/api/artifact/processor"
"github.com/goharbor/harbor/src/api/artifact/processor/base" "github.com/goharbor/harbor/src/api/artifact/processor/base"
"github.com/goharbor/harbor/src/api/artifact/processor/blob"
"github.com/goharbor/harbor/src/common/utils/log" "github.com/goharbor/harbor/src/common/utils/log"
"github.com/goharbor/harbor/src/pkg/artifact" "github.com/goharbor/harbor/src/pkg/artifact"
) )
@ -31,10 +31,9 @@ const (
func init() { func init() {
pc := &processor{ pc := &processor{
blobFetcher: blob.Fcher,
manifestProcessor: base.NewManifestProcessor(), manifestProcessor: base.NewManifestProcessor(),
} }
pc.IndexProcessor = &base.IndexProcessor{} pc.IndexProcessor = base.NewIndexProcessor()
if err := ps.Register(pc, mediaType); err != nil { if err := ps.Register(pc, mediaType); err != nil {
log.Errorf("failed to register processor for media type %s: %v", mediaType, err) log.Errorf("failed to register processor for media type %s: %v", mediaType, err)
return return
@ -44,7 +43,6 @@ func init() {
type processor struct { type processor struct {
*base.IndexProcessor *base.IndexProcessor
manifestProcessor *base.ManifestProcessor manifestProcessor *base.ManifestProcessor
blobFetcher blob.Fetcher
} }
func (p *processor) AbstractMetadata(ctx context.Context, manifest []byte, art *artifact.Artifact) error { func (p *processor) AbstractMetadata(ctx context.Context, manifest []byte, art *artifact.Artifact) error {
@ -61,13 +59,17 @@ func (p *processor) AbstractMetadata(ctx context.Context, manifest []byte, art *
} }
// get the manifest that the config layer is referenced by // get the manifest that the config layer is referenced by
_, cfgMani, err := p.blobFetcher.FetchManifest(art.RepositoryName, cfgManiDgt) mani, _, err := p.RegCli.PullManifest(art.RepositoryName, cfgManiDgt)
if err != nil {
return err
}
_, payload, err := mani.Payload()
if err != nil { if err != nil {
return err return err
} }
// abstract the metadata from config layer // abstract the metadata from config layer
return p.manifestProcessor.AbstractMetadata(ctx, cfgMani, art) return p.manifestProcessor.AbstractMetadata(ctx, payload, art)
} }
func (p *processor) GetArtifactType() string { func (p *processor) GetArtifactType() string {

View File

@ -15,28 +15,31 @@
package cnab package cnab
import ( import (
"github.com/docker/distribution"
"github.com/goharbor/harbor/src/api/artifact/processor/base" "github.com/goharbor/harbor/src/api/artifact/processor/base"
"github.com/goharbor/harbor/src/pkg/artifact" "github.com/goharbor/harbor/src/pkg/artifact"
"github.com/goharbor/harbor/src/testing/api/artifact/processor/blob" "github.com/goharbor/harbor/src/testing/pkg/registry"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"io/ioutil"
"strings"
"testing" "testing"
) )
type processorTestSuite struct { type processorTestSuite struct {
suite.Suite suite.Suite
processor *processor processor *processor
blobFetcher *blob.FakeFetcher regCli *registry.FakeClient
} }
func (p *processorTestSuite) SetupTest() { func (p *processorTestSuite) SetupTest() {
p.blobFetcher = &blob.FakeFetcher{} p.regCli = &registry.FakeClient{}
p.processor = &processor{ p.processor = &processor{
blobFetcher: p.blobFetcher,
manifestProcessor: &base.ManifestProcessor{ manifestProcessor: &base.ManifestProcessor{
BlobFetcher: p.blobFetcher, RegCli: p.regCli,
}, },
} }
p.processor.IndexProcessor = &base.IndexProcessor{RegCli: p.regCli}
} }
func (p *processorTestSuite) TestAbstractMetadata() { func (p *processorTestSuite) TestAbstractMetadata() {
@ -86,9 +89,11 @@ func (p *processorTestSuite) TestAbstractMetadata() {
}, },
}, },
} }
p.blobFetcher.On("FetchManifest").Return("", []byte(manifest), nil) mani, _, err := distribution.UnmarshalManifest(v1.MediaTypeImageManifest, []byte(manifest))
p.blobFetcher.On("FetchLayer").Return([]byte(config), nil) p.Require().Nil(err)
err := p.processor.AbstractMetadata(nil, nil, art) p.regCli.On("PullManifest").Return(mani, "", nil)
p.regCli.On("PullBlob").Return(0, ioutil.NopCloser(strings.NewReader(config)), nil)
err = p.processor.AbstractMetadata(nil, nil, art)
p.Require().Nil(err) p.Require().Nil(err)
p.Len(art.ExtraAttrs, 7) p.Len(art.ExtraAttrs, 7)
p.Equal("0.1.1", art.ExtraAttrs["version"].(string)) p.Equal("0.1.1", art.ExtraAttrs["version"].(string))

View File

@ -20,7 +20,6 @@ import (
"github.com/docker/distribution/manifest/schema2" "github.com/docker/distribution/manifest/schema2"
"github.com/goharbor/harbor/src/api/artifact/processor" "github.com/goharbor/harbor/src/api/artifact/processor"
"github.com/goharbor/harbor/src/api/artifact/processor/base" "github.com/goharbor/harbor/src/api/artifact/processor/base"
"github.com/goharbor/harbor/src/api/artifact/processor/blob"
"github.com/goharbor/harbor/src/common/utils/log" "github.com/goharbor/harbor/src/common/utils/log"
ierror "github.com/goharbor/harbor/src/internal/error" ierror "github.com/goharbor/harbor/src/internal/error"
"github.com/goharbor/harbor/src/pkg/artifact" "github.com/goharbor/harbor/src/pkg/artifact"
@ -35,9 +34,7 @@ const (
) )
func init() { func init() {
pc := &manifestV2Processor{ pc := &manifestV2Processor{}
blobFetcher: blob.Fcher,
}
pc.ManifestProcessor = base.NewManifestProcessor("created", "author", "architecture", "os") pc.ManifestProcessor = base.NewManifestProcessor("created", "author", "architecture", "os")
mediaTypes := []string{ mediaTypes := []string{
v1.MediaTypeImageConfig, v1.MediaTypeImageConfig,
@ -52,7 +49,6 @@ func init() {
// manifestV2Processor processes image with OCI manifest and docker v2 manifest // manifestV2Processor processes image with OCI manifest and docker v2 manifest
type manifestV2Processor struct { type manifestV2Processor struct {
*base.ManifestProcessor *base.ManifestProcessor
blobFetcher blob.Fetcher
} }
func (m *manifestV2Processor) AbstractAddition(ctx context.Context, artifact *artifact.Artifact, addition string) (*processor.Addition, error) { func (m *manifestV2Processor) AbstractAddition(ctx context.Context, artifact *artifact.Artifact, addition string) (*processor.Addition, error) {
@ -60,7 +56,11 @@ func (m *manifestV2Processor) AbstractAddition(ctx context.Context, artifact *ar
return nil, ierror.New(nil).WithCode(ierror.BadRequestCode). return nil, ierror.New(nil).WithCode(ierror.BadRequestCode).
WithMessage("addition %s isn't supported for %s(manifest version 2)", addition, ArtifactTypeImage) WithMessage("addition %s isn't supported for %s(manifest version 2)", addition, ArtifactTypeImage)
} }
_, content, err := m.blobFetcher.FetchManifest(artifact.RepositoryName, artifact.Digest) mani, _, err := m.RegCli.PullManifest(artifact.RepositoryName, artifact.Digest)
if err != nil {
return nil, err
}
_, content, err := mani.Payload()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -68,12 +68,12 @@ func (m *manifestV2Processor) AbstractAddition(ctx context.Context, artifact *ar
if err := json.Unmarshal(content, manifest); err != nil { if err := json.Unmarshal(content, manifest); err != nil {
return nil, err return nil, err
} }
content, err = m.blobFetcher.FetchLayer(artifact.RepositoryName, manifest.Config.Digest.String()) _, blob, err := m.RegCli.PullBlob(artifact.RepositoryName, manifest.Config.Digest.String())
if err != nil { if err != nil {
return nil, err return nil, err
} }
image := &v1.Image{} image := &v1.Image{}
if err := json.Unmarshal(content, image); err != nil { if err := json.NewDecoder(blob).Decode(image); err != nil {
return nil, err return nil, err
} }
content, err = json.Marshal(image.History) content, err = json.Marshal(image.History)

View File

@ -15,10 +15,15 @@
package image package image
import ( import (
"github.com/docker/distribution"
"github.com/docker/distribution/manifest/schema2"
"github.com/goharbor/harbor/src/api/artifact/processor/base"
ierror "github.com/goharbor/harbor/src/internal/error" ierror "github.com/goharbor/harbor/src/internal/error"
"github.com/goharbor/harbor/src/pkg/artifact" "github.com/goharbor/harbor/src/pkg/artifact"
"github.com/goharbor/harbor/src/testing/api/artifact/processor/blob" "github.com/goharbor/harbor/src/testing/pkg/registry"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"io/ioutil"
"strings"
"testing" "testing"
) )
@ -121,14 +126,13 @@ var (
type manifestV2ProcessorTestSuite struct { type manifestV2ProcessorTestSuite struct {
suite.Suite suite.Suite
processor *manifestV2Processor processor *manifestV2Processor
blobFetcher *blob.FakeFetcher regCli *registry.FakeClient
} }
func (m *manifestV2ProcessorTestSuite) SetupTest() { func (m *manifestV2ProcessorTestSuite) SetupTest() {
m.blobFetcher = &blob.FakeFetcher{} m.regCli = &registry.FakeClient{}
m.processor = &manifestV2Processor{ m.processor = &manifestV2Processor{}
blobFetcher: m.blobFetcher, m.processor.ManifestProcessor = &base.ManifestProcessor{RegCli: m.regCli}
}
} }
func (m *manifestV2ProcessorTestSuite) TestAbstractAddition() { func (m *manifestV2ProcessorTestSuite) TestAbstractAddition() {
@ -138,8 +142,10 @@ func (m *manifestV2ProcessorTestSuite) TestAbstractAddition() {
// build history // build history
artifact := &artifact.Artifact{} artifact := &artifact.Artifact{}
m.blobFetcher.On("FetchManifest").Return("", []byte(manifest), nil) manifest, _, err := distribution.UnmarshalManifest(schema2.MediaTypeManifest, []byte(manifest))
m.blobFetcher.On("FetchLayer").Return([]byte(config), nil) m.Require().Nil(err)
m.regCli.On("PullManifest").Return(manifest, "", nil)
m.regCli.On("PullBlob").Return(0, ioutil.NopCloser(strings.NewReader(config)), nil)
addition, err := m.processor.AbstractAddition(nil, artifact, AdditionTypeBuildHistory) addition, err := m.processor.AbstractAddition(nil, artifact, AdditionTypeBuildHistory)
m.Require().Nil(err) m.Require().Nil(err)
m.Equal("application/json; charset=utf-8", addition.ContentType) m.Equal("application/json; charset=utf-8", addition.ContentType)

View File

@ -135,8 +135,6 @@ func (c *controller) GetByName(ctx context.Context, name string) (*models.RepoRe
} }
func (c *controller) Delete(ctx context.Context, id int64) error { func (c *controller) Delete(ctx context.Context, id int64) error {
// TODO how to make sure the logic included by middlewares(immutable, readonly, quota, etc)
// TODO is covered when deleting the artifacts of the repository
artifacts, err := c.artCtl.List(ctx, &q.Query{ artifacts, err := c.artCtl.List(ctx, &q.Query{
Keywords: map[string]interface{}{ Keywords: map[string]interface{}{
"RepositoryID": id, "RepositoryID": id,
@ -151,8 +149,6 @@ func (c *controller) Delete(ctx context.Context, id int64) error {
} }
} }
return c.repoMgr.Delete(ctx, id) return c.repoMgr.Delete(ctx, id)
// TODO fire event
} }
func (c *controller) Update(ctx context.Context, repository *models.RepoRecord, properties ...string) error { func (c *controller) Update(ctx context.Context, repository *models.RepoRecord, properties ...string) error {

View File

@ -18,14 +18,15 @@ import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/docker/distribution"
"github.com/docker/distribution/manifest/manifestlist"
"io" "io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
"strconv" "strconv"
"strings" "strings"
"github.com/docker/distribution"
"github.com/docker/distribution/manifest/manifestlist"
// register oci manifest unmarshal function // register oci manifest unmarshal function
_ "github.com/docker/distribution/manifest/ocischema" _ "github.com/docker/distribution/manifest/ocischema"
"github.com/docker/distribution/manifest/schema1" "github.com/docker/distribution/manifest/schema1"
@ -87,7 +88,7 @@ type Client interface {
Copy(srcRepository, srcReference, dstRepository, dstReference string, override bool) (err error) Copy(srcRepository, srcReference, dstRepository, dstReference string, override bool) (err error)
} }
// TODO TODO support HTTPS // TODO support HTTPS
// NewClient creates a registry client with the default authorizer which determines the auth scheme // NewClient creates a registry client with the default authorizer which determines the auth scheme
// of the registry automatically and calls the corresponding underlying authorizers(basic/bearer) to // of the registry automatically and calls the corresponding underlying authorizers(basic/bearer) to

View File

@ -1,36 +0,0 @@
// Copyright Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package blob
import (
"github.com/stretchr/testify/mock"
)
// FakeFetcher is a fake blob fetcher that implement the src/api/artifact/abstractor/blob.Fetcher interface
type FakeFetcher struct {
mock.Mock
}
// FetchManifest ...
func (f *FakeFetcher) FetchManifest(repoFullName, digest string) (string, []byte, error) {
args := f.Called(mock.Anything)
return args.String(0), args.Get(1).([]byte), args.Error(2)
}
// FetchLayer ...
func (f *FakeFetcher) FetchLayer(repoFullName, digest string) (content []byte, err error) {
args := f.Called(mock.Anything)
return args.Get(0).([]byte), args.Error(1)
}

View File

@ -89,8 +89,8 @@ func (f *FakeClient) BlobExist(repository, digest string) (bool, error) {
func (f *FakeClient) PullBlob(repository, digest string) (int64, io.ReadCloser, error) { func (f *FakeClient) PullBlob(repository, digest string) (int64, io.ReadCloser, error) {
args := f.Called() args := f.Called()
var blob io.ReadCloser var blob io.ReadCloser
if args[0] != nil { if args[1] != nil {
blob = args[0].(io.ReadCloser) blob = args[1].(io.ReadCloser)
} }
return int64(args.Int(0)), blob, args.Error(2) return int64(args.Int(0)), blob, args.Error(2)
} }

View File

@ -9,9 +9,6 @@ Library Process
Default Tags API Default Tags API
*** Test Cases *** *** Test Cases ***
# TODO the cases commented by "###" can be uncommented after implementing the repository python library based on new API
Test Case - LDAP Group Admin Role Test Case - LDAP Group Admin Role
Harbor API Test ./tests/apitests/python/test_ldap_admin_role.py Harbor API Test ./tests/apitests/python/test_ldap_admin_role.py