diff --git a/src/api/artifact/abstractor.go b/src/api/artifact/abstractor.go index 5d162c69e..011d159af 100644 --- a/src/api/artifact/abstractor.go +++ b/src/api/artifact/abstractor.go @@ -22,8 +22,8 @@ import ( "github.com/docker/distribution/manifest/schema1" "github.com/docker/distribution/manifest/schema2" "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/registry" v1 "github.com/opencontainers/image-spec/specs-go/v1" ) @@ -36,19 +36,23 @@ type Abstractor interface { // NewAbstractor creates a new abstractor func NewAbstractor() Abstractor { return &abstractor{ - artMgr: artifact.Mgr, - blobFetcher: blob.Fcher, + artMgr: artifact.Mgr, + regCli: registry.Cli, } } type abstractor struct { - artMgr artifact.Manager - blobFetcher blob.Fetcher + artMgr artifact.Manager + regCli registry.Client } func (a *abstractor) AbstractMetadata(ctx context.Context, artifact *artifact.Artifact) error { // 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 { return err } diff --git a/src/api/artifact/abstractor_test.go b/src/api/artifact/abstractor_test.go index 1469e4c68..6b2f4812e 100644 --- a/src/api/artifact/abstractor_test.go +++ b/src/api/artifact/abstractor_test.go @@ -15,12 +15,13 @@ package artifact import ( + "github.com/docker/distribution" "github.com/docker/distribution/manifest/schema1" "github.com/docker/distribution/manifest/schema2" "github.com/goharbor/harbor/src/api/artifact/processor" "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" + "github.com/goharbor/harbor/src/testing/pkg/registry" v1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/stretchr/testify/suite" "testing" @@ -28,48 +29,126 @@ import ( var ( v1Manifest = `{ - "name": "hello-world", - "tag": "latest", - "architecture": "amd64", - "fsLayers": [ - { - "blobSum": "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef" - }, - { - "blobSum": "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef" - }, - { - "blobSum": "sha256:cc8567d70002e957612902a8e985ea129d831ebe04057d88fb644857caa45d11" - }, - { - "blobSum": "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef" - } - ], - "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\":\"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" - } - ], - "schemaVersion": 1, - "signatures": [ - { - "header": { - "jwk": { - "crv": "P-256", - "kid": "OD6I:6DRK:JXEJ:KBM4:255X:NSAA:MUSF:E4VM:ZI6W:CUN2:L4Z6:LSF4", - "kty": "EC", - "x": "3gAwX48IQ5oaYQAYSxor6rYYc_6yjuLCjtQ9LUakg4A", - "y": "t72ge6kIA1XOjqjVoEOiPPAURltJFBMGDSQvEGVB010" - }, - "alg": "ES256" + "schemaVersion": 1, + "name": "library/node", + "tag": "5.5-onbuild", + "architecture": "amd64", + "fsLayers": [ + { + "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4" }, - "signature": "XREm0L8WNn27Ga_iE_vRnTxVMhhYY0Zst_FfkKopg6gWSoTOZTuW4rK0fg_IqnKkEKlbD83tD46LKEGi5aIVFg", - "protected": "eyJmb3JtYXRMZW5ndGgiOjY2MjgsImZvcm1hdFRhaWwiOiJDbjAiLCJ0aW1lIjoiMjAxNS0wNC0wOFQxODo1Mjo1OVoifQ" - } - ] + { + "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4" + }, + { + "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4" + }, + { + "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": [ + { + "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\":\"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}" + } + ], + "signatures": [ + { + "header": { + "jwk": { + "crv": "P-256", + "kid": "KG7S:QIPL:FTS3:YAKZ:AADA:4GML:ITLH:7APP:O4F7:2NBA:A4IN:CWVF", + "kty": "EC", + "x": "PUiT1kV7Xf-U8M54gpCzvPc5mDUX9BjvizdBgy3oTsI", + "y": "BAgeQchl9QibzPP2Qp_-gJMWr682QVWoHy52hLRHZ04" + }, + "alg": "ES256" + }, + "signature": "mfUOI0pPzkdceAKAFRMkrQVgeE9X7if43LEtfs5XvdyxO7lCG0fiVxmdi-KGaQu4lRsIfRNq6m5agNTm8u5DrA", + "protected": "eyJmb3JtYXRMZW5ndGgiOjI3NTE4LCJmb3JtYXRUYWlsIjoiQ24wIiwidGltZSI6IjIwMjAtMDMtMTdUMTA6NTk6MDhaIn0" + } + ] }` v2Manifest = `{ "schemaVersion": 2, @@ -122,16 +201,16 @@ var ( type abstractorTestSuite struct { suite.Suite argMgr *tart.FakeManager - fetcher *blob.FakeFetcher + regCli *registry.FakeClient abstractor *abstractor } func (a *abstractorTestSuite) SetupTest() { - a.fetcher = &blob.FakeFetcher{} + a.regCli = ®istry.FakeClient{} a.argMgr = &tart.FakeManager{} a.abstractor = &abstractor{ - artMgr: a.argMgr, - blobFetcher: a.fetcher, + artMgr: a.argMgr, + regCli: a.regCli, } // clear all registered processors processor.Registry = map[string]processor.Processor{} @@ -139,11 +218,13 @@ func (a *abstractorTestSuite) SetupTest() { // docker manifest v1 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{ ID: 1, } - err := a.abstractor.AbstractMetadata(nil, artifact) + err = a.abstractor.AbstractMetadata(nil, artifact) a.Require().Nil(err) a.Assert().Equal(int64(1), artifact.ID) a.Assert().Equal(schema1.MediaTypeSignedManifest, artifact.ManifestMediaType) @@ -153,11 +234,13 @@ func (a *abstractorTestSuite) TestAbstractMetadataOfV1Manifest() { // docker manifest v2 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{ ID: 1, } - err := a.abstractor.AbstractMetadata(nil, artifact) + err = a.abstractor.AbstractMetadata(nil, artifact) a.Require().Nil(err) a.Assert().Equal(int64(1), artifact.ID) a.Assert().Equal(schema2.MediaTypeManifest, artifact.ManifestMediaType) @@ -169,7 +252,9 @@ func (a *abstractorTestSuite) TestAbstractMetadataOfV2Manifest() { // OCI index 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{ ID: 2, Size: 10, @@ -177,7 +262,7 @@ func (a *abstractorTestSuite) TestAbstractMetadataOfIndex() { artifact := &artifact.Artifact{ ID: 1, } - err := a.abstractor.AbstractMetadata(nil, artifact) + err = a.abstractor.AbstractMetadata(nil, artifact) a.Require().Nil(err) a.Assert().Equal(int64(1), artifact.ID) a.Assert().Equal(v1.MediaTypeImageIndex, artifact.ManifestMediaType) @@ -188,9 +273,18 @@ func (a *abstractorTestSuite) TestAbstractMetadataOfIndex() { 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() { - a.fetcher.On("FetchManifest").Return("unsupported-manifest", []byte{}, nil) + a.regCli.On("PullManifest").Return(&unknownManifest{}, "", nil) artifact := &artifact.Artifact{ ID: 1, } diff --git a/src/api/artifact/processor/base/index.go b/src/api/artifact/processor/base/index.go index 3fae1a2f8..092913f95 100644 --- a/src/api/artifact/processor/base/index.go +++ b/src/api/artifact/processor/base/index.go @@ -19,11 +19,20 @@ import ( "github.com/goharbor/harbor/src/api/artifact/processor" ierror "github.com/goharbor/harbor/src/internal/error" "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 // Currently, it is just a null implementation type IndexProcessor struct { + RegCli registry.Client } // AbstractMetadata abstracts metadata of artifact diff --git a/src/api/artifact/processor/base/manifest.go b/src/api/artifact/processor/base/manifest.go index 766e541e4..af2e12368 100644 --- a/src/api/artifact/processor/base/manifest.go +++ b/src/api/artifact/processor/base/manifest.go @@ -18,9 +18,9 @@ import ( "context" "encoding/json" "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" "github.com/goharbor/harbor/src/pkg/artifact" + "github.com/goharbor/harbor/src/pkg/registry" "github.com/opencontainers/image-spec/specs-go/v1" ) @@ -28,15 +28,15 @@ import ( // All metadata read from config layer will be populated if specifying no "properties" func NewManifestProcessor(properties ...string) *ManifestProcessor { return &ManifestProcessor{ - properties: properties, - BlobFetcher: blob.Fcher, + properties: properties, + RegCli: registry.Cli, } } // ManifestProcessor is a base processor to process artifact enveloped by OCI manifest or docker v2 manifest type ManifestProcessor struct { - properties []string - BlobFetcher blob.Fetcher + properties []string + RegCli registry.Client } // AbstractMetadata abstracts metadata of artifact @@ -47,13 +47,14 @@ func (m *ManifestProcessor) AbstractMetadata(ctx context.Context, content []byte return err } // 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 { return err } + defer blob.Close() // parse metadata from config layer metadata := map[string]interface{}{} - if err := json.Unmarshal(layer, &metadata); err != nil { + if err := json.NewDecoder(blob).Decode(&metadata); err != nil { return err } // if no properties specified, populate all metadata into the ExtraAttrs diff --git a/src/api/artifact/processor/base/manifest_test.go b/src/api/artifact/processor/base/manifest_test.go index 02816458e..47797be7a 100644 --- a/src/api/artifact/processor/base/manifest_test.go +++ b/src/api/artifact/processor/base/manifest_test.go @@ -15,10 +15,13 @@ package base import ( - "github.com/goharbor/harbor/src/pkg/artifact" - "github.com/goharbor/harbor/src/testing/api/artifact/processor/blob" - "github.com/stretchr/testify/suite" + "io/ioutil" + "strings" "testing" + + "github.com/goharbor/harbor/src/pkg/artifact" + "github.com/goharbor/harbor/src/testing/pkg/registry" + "github.com/stretchr/testify/suite" ) const ( @@ -119,21 +122,22 @@ const ( type manifestTestSuite struct { suite.Suite - processor *ManifestProcessor - blobFetcher *blob.FakeFetcher + processor *ManifestProcessor + regCli *registry.FakeClient } func (m *manifestTestSuite) SetupTest() { - m.blobFetcher = &blob.FakeFetcher{} + m.regCli = ®istry.FakeClient{} m.processor = &ManifestProcessor{ - BlobFetcher: m.blobFetcher, + RegCli: m.regCli, } } func (m *manifestTestSuite) TestAbstractMetadata() { // abstract all properties 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.Len(art.ExtraAttrs, 9) @@ -143,7 +147,7 @@ func (m *manifestTestSuite) TestAbstractMetadata() { // abstract the specified properties m.processor.properties = []string{"os"} 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.Require().Len(art.ExtraAttrs, 1) m.Equal("linux", art.ExtraAttrs["os"]) diff --git a/src/api/artifact/processor/blob/cache.go b/src/api/artifact/processor/blob/cache.go deleted file mode 100644 index bb09aaee9..000000000 --- a/src/api/artifact/processor/blob/cache.go +++ /dev/null @@ -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 diff --git a/src/api/artifact/processor/blob/fetcher.go b/src/api/artifact/processor/blob/fetcher.go deleted file mode 100644 index 45ff2f2c5..000000000 --- a/src/api/artifact/processor/blob/fetcher.go +++ /dev/null @@ -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) -} diff --git a/src/api/artifact/processor/chart/chart.go b/src/api/artifact/processor/chart/chart.go index b6013e245..403fa87b4 100644 --- a/src/api/artifact/processor/chart/chart.go +++ b/src/api/artifact/processor/chart/chart.go @@ -17,9 +17,10 @@ package chart import ( "context" "encoding/json" + "io/ioutil" + 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/blob" "github.com/goharbor/harbor/src/common/utils/log" ierror "github.com/goharbor/harbor/src/internal/error" "github.com/goharbor/harbor/src/pkg/artifact" @@ -35,13 +36,13 @@ const ( AdditionTypeReadme = "README.MD" 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" ) func init() { pc := &processor{ - blobFetcher: blob.Fcher, chartOperator: chart.Optr, } pc.ManifestProcessor = base.NewManifestProcessor() @@ -53,7 +54,6 @@ func init() { type processor struct { *base.ManifestProcessor - blobFetcher blob.Fetcher 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) } - _, 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 { return nil, err } manifest := &v1.Manifest{} - if err := json.Unmarshal(content, manifest); err != nil { + if err := json.Unmarshal(payload, manifest); err != nil { 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. layerDgst := layer.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 { return nil, err } + content, err := ioutil.ReadAll(blob) + if err != nil { + return nil, err + } + blob.Close() chartDetails, err := p.chartOperator.GetDetails(content) if err != nil { return nil, err diff --git a/src/api/artifact/processor/chart/chart_test.go b/src/api/artifact/processor/chart/chart_test.go index 416bd193d..770e78106 100644 --- a/src/api/artifact/processor/chart/chart_test.go +++ b/src/api/artifact/processor/chart/chart_test.go @@ -15,31 +15,35 @@ package chart import ( + "github.com/docker/distribution" + "github.com/goharbor/harbor/src/api/artifact/processor/base" ierror "github.com/goharbor/harbor/src/internal/error" "github.com/goharbor/harbor/src/pkg/artifact" 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/registry" + v1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/stretchr/testify/suite" helm_chart "helm.sh/helm/v3/pkg/chart" + "io/ioutil" + "strings" "testing" ) type processorTestSuite struct { suite.Suite - processor *processor - blobFetcher *blob.FakeFetcher - chartOptr *chart.FakeOpertaor + processor *processor + regCli *registry.FakeClient + chartOptr *chart.FakeOpertaor } func (p *processorTestSuite) SetupTest() { - p.blobFetcher = &blob.FakeFetcher{} + p.regCli = ®istry.FakeClient{} p.chartOptr = &chart.FakeOpertaor{} p.processor = &processor{ - blobFetcher: p.blobFetcher, chartOperator: p.chartOptr, } - + p.processor.ManifestProcessor = &base.ManifestProcessor{RegCli: p.regCli} } func (p *processorTestSuite) TestAbstractAddition() { @@ -98,8 +102,10 @@ func (p *processorTestSuite) TestAbstractAddition() { } artifact := &artifact.Artifact{} - p.blobFetcher.On("FetchManifest").Return("", []byte(chartManifest), nil) - p.blobFetcher.On("FetchLayer").Return([]byte(chartYaml), nil) + manifest, _, err := distribution.UnmarshalManifest(v1.MediaTypeImageManifest, []byte(chartManifest)) + 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) // values.yaml diff --git a/src/api/artifact/processor/cnab/cnab.go b/src/api/artifact/processor/cnab/cnab.go index 7748e6335..4b62357a2 100644 --- a/src/api/artifact/processor/cnab/cnab.go +++ b/src/api/artifact/processor/cnab/cnab.go @@ -16,9 +16,9 @@ package cnab import ( "context" + 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/blob" "github.com/goharbor/harbor/src/common/utils/log" "github.com/goharbor/harbor/src/pkg/artifact" ) @@ -31,10 +31,9 @@ const ( func init() { pc := &processor{ - blobFetcher: blob.Fcher, manifestProcessor: base.NewManifestProcessor(), } - pc.IndexProcessor = &base.IndexProcessor{} + pc.IndexProcessor = base.NewIndexProcessor() if err := ps.Register(pc, mediaType); err != nil { log.Errorf("failed to register processor for media type %s: %v", mediaType, err) return @@ -44,7 +43,6 @@ func init() { type processor struct { *base.IndexProcessor manifestProcessor *base.ManifestProcessor - blobFetcher blob.Fetcher } 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 - _, 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 { return err } // 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 { diff --git a/src/api/artifact/processor/cnab/cnab_test.go b/src/api/artifact/processor/cnab/cnab_test.go index 9076ae012..c63d149d4 100644 --- a/src/api/artifact/processor/cnab/cnab_test.go +++ b/src/api/artifact/processor/cnab/cnab_test.go @@ -15,28 +15,31 @@ package cnab import ( + "github.com/docker/distribution" "github.com/goharbor/harbor/src/api/artifact/processor/base" "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" + "io/ioutil" + "strings" "testing" ) type processorTestSuite struct { suite.Suite - processor *processor - blobFetcher *blob.FakeFetcher + processor *processor + regCli *registry.FakeClient } func (p *processorTestSuite) SetupTest() { - p.blobFetcher = &blob.FakeFetcher{} + p.regCli = ®istry.FakeClient{} p.processor = &processor{ - blobFetcher: p.blobFetcher, manifestProcessor: &base.ManifestProcessor{ - BlobFetcher: p.blobFetcher, + RegCli: p.regCli, }, } - + p.processor.IndexProcessor = &base.IndexProcessor{RegCli: p.regCli} } func (p *processorTestSuite) TestAbstractMetadata() { @@ -86,9 +89,11 @@ func (p *processorTestSuite) TestAbstractMetadata() { }, }, } - p.blobFetcher.On("FetchManifest").Return("", []byte(manifest), nil) - p.blobFetcher.On("FetchLayer").Return([]byte(config), nil) - err := p.processor.AbstractMetadata(nil, nil, art) + mani, _, err := distribution.UnmarshalManifest(v1.MediaTypeImageManifest, []byte(manifest)) + p.Require().Nil(err) + 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.Len(art.ExtraAttrs, 7) p.Equal("0.1.1", art.ExtraAttrs["version"].(string)) diff --git a/src/api/artifact/processor/image/manifest_v2.go b/src/api/artifact/processor/image/manifest_v2.go index a0bf3d84c..3b76a4c18 100644 --- a/src/api/artifact/processor/image/manifest_v2.go +++ b/src/api/artifact/processor/image/manifest_v2.go @@ -20,7 +20,6 @@ import ( "github.com/docker/distribution/manifest/schema2" "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/blob" "github.com/goharbor/harbor/src/common/utils/log" ierror "github.com/goharbor/harbor/src/internal/error" "github.com/goharbor/harbor/src/pkg/artifact" @@ -35,9 +34,7 @@ const ( ) func init() { - pc := &manifestV2Processor{ - blobFetcher: blob.Fcher, - } + pc := &manifestV2Processor{} pc.ManifestProcessor = base.NewManifestProcessor("created", "author", "architecture", "os") mediaTypes := []string{ v1.MediaTypeImageConfig, @@ -52,7 +49,6 @@ func init() { // manifestV2Processor processes image with OCI manifest and docker v2 manifest type manifestV2Processor struct { *base.ManifestProcessor - blobFetcher blob.Fetcher } 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). 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 { return nil, err } @@ -68,12 +68,12 @@ func (m *manifestV2Processor) AbstractAddition(ctx context.Context, artifact *ar if err := json.Unmarshal(content, manifest); err != nil { 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 { return nil, err } image := &v1.Image{} - if err := json.Unmarshal(content, image); err != nil { + if err := json.NewDecoder(blob).Decode(image); err != nil { return nil, err } content, err = json.Marshal(image.History) diff --git a/src/api/artifact/processor/image/manifest_v2_test.go b/src/api/artifact/processor/image/manifest_v2_test.go index 04c8fd547..48de596b8 100644 --- a/src/api/artifact/processor/image/manifest_v2_test.go +++ b/src/api/artifact/processor/image/manifest_v2_test.go @@ -15,10 +15,15 @@ package image 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" "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" + "io/ioutil" + "strings" "testing" ) @@ -120,15 +125,14 @@ var ( type manifestV2ProcessorTestSuite struct { suite.Suite - processor *manifestV2Processor - blobFetcher *blob.FakeFetcher + processor *manifestV2Processor + regCli *registry.FakeClient } func (m *manifestV2ProcessorTestSuite) SetupTest() { - m.blobFetcher = &blob.FakeFetcher{} - m.processor = &manifestV2Processor{ - blobFetcher: m.blobFetcher, - } + m.regCli = ®istry.FakeClient{} + m.processor = &manifestV2Processor{} + m.processor.ManifestProcessor = &base.ManifestProcessor{RegCli: m.regCli} } func (m *manifestV2ProcessorTestSuite) TestAbstractAddition() { @@ -138,8 +142,10 @@ func (m *manifestV2ProcessorTestSuite) TestAbstractAddition() { // build history artifact := &artifact.Artifact{} - m.blobFetcher.On("FetchManifest").Return("", []byte(manifest), nil) - m.blobFetcher.On("FetchLayer").Return([]byte(config), nil) + manifest, _, err := distribution.UnmarshalManifest(schema2.MediaTypeManifest, []byte(manifest)) + 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) m.Require().Nil(err) m.Equal("application/json; charset=utf-8", addition.ContentType) diff --git a/src/api/repository/controller.go b/src/api/repository/controller.go index 973b7494c..102225efb 100644 --- a/src/api/repository/controller.go +++ b/src/api/repository/controller.go @@ -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 { - // 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{ Keywords: map[string]interface{}{ "RepositoryID": id, @@ -151,8 +149,6 @@ func (c *controller) Delete(ctx context.Context, id int64) error { } } return c.repoMgr.Delete(ctx, id) - - // TODO fire event } func (c *controller) Update(ctx context.Context, repository *models.RepoRecord, properties ...string) error { diff --git a/src/pkg/registry/client.go b/src/pkg/registry/client.go index ba78134d6..6778540af 100644 --- a/src/pkg/registry/client.go +++ b/src/pkg/registry/client.go @@ -18,14 +18,15 @@ import ( "bytes" "encoding/json" "fmt" - "github.com/docker/distribution" - "github.com/docker/distribution/manifest/manifestlist" "io" "io/ioutil" "net/http" "net/url" "strconv" "strings" + + "github.com/docker/distribution" + "github.com/docker/distribution/manifest/manifestlist" // register oci manifest unmarshal function _ "github.com/docker/distribution/manifest/ocischema" "github.com/docker/distribution/manifest/schema1" @@ -87,7 +88,7 @@ type Client interface { 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 // of the registry automatically and calls the corresponding underlying authorizers(basic/bearer) to diff --git a/src/testing/api/artifact/processor/blob/fetcher.go b/src/testing/api/artifact/processor/blob/fetcher.go deleted file mode 100644 index 0230161f9..000000000 --- a/src/testing/api/artifact/processor/blob/fetcher.go +++ /dev/null @@ -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) -} diff --git a/src/testing/pkg/registry/client.go b/src/testing/pkg/registry/client.go index 1b3a5e5f2..8863daff1 100644 --- a/src/testing/pkg/registry/client.go +++ b/src/testing/pkg/registry/client.go @@ -89,8 +89,8 @@ func (f *FakeClient) BlobExist(repository, digest string) (bool, error) { func (f *FakeClient) PullBlob(repository, digest string) (int64, io.ReadCloser, error) { args := f.Called() var blob io.ReadCloser - if args[0] != nil { - blob = args[0].(io.ReadCloser) + if args[1] != nil { + blob = args[1].(io.ReadCloser) } return int64(args.Int(0)), blob, args.Error(2) } diff --git a/tests/robot-cases/Group0-BAT/API_LDAP.robot b/tests/robot-cases/Group0-BAT/API_LDAP.robot index 761ce946d..2ce9f6948 100644 --- a/tests/robot-cases/Group0-BAT/API_LDAP.robot +++ b/tests/robot-cases/Group0-BAT/API_LDAP.robot @@ -9,9 +9,6 @@ Library Process Default Tags API *** 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 Harbor API Test ./tests/apitests/python/test_ldap_admin_role.py