diff --git a/src/api/artifact/abstractor/resolver/image/index.go b/src/api/artifact/abstractor/resolver/image/index.go index c1125c1c3..b1cc5d454 100644 --- a/src/api/artifact/abstractor/resolver/image/index.go +++ b/src/api/artifact/abstractor/resolver/image/index.go @@ -12,15 +12,20 @@ package image import ( "context" + "encoding/json" + "fmt" "github.com/docker/distribution/manifest/manifestlist" "github.com/goharbor/harbor/src/api/artifact/abstractor/resolver" "github.com/goharbor/harbor/src/common/utils/log" "github.com/goharbor/harbor/src/pkg/artifact" + "github.com/goharbor/harbor/src/pkg/q" v1 "github.com/opencontainers/image-spec/specs-go/v1" ) func init() { - rslver := &indexResolver{} + rslver := &indexResolver{ + artMgr: artifact.Mgr, + } if err := resolver.Register(rslver, v1.MediaTypeImageIndex, manifestlist.MediaTypeManifestList); err != nil { log.Errorf("failed to register resolver for artifact %s: %v", rslver.ArtifactType(), err) return @@ -29,14 +34,39 @@ func init() { // indexResolver resolves artifact with OCI index and docker manifest list type indexResolver struct { + artMgr artifact.Manager } func (i *indexResolver) ArtifactType() string { return ArtifactTypeImage } -func (i *indexResolver) Resolve(ctx context.Context, manifest []byte, artifact *artifact.Artifact) error { - // TODO implement - // how to make sure the artifact referenced by the index has already been saved in database +func (i *indexResolver) Resolve(ctx context.Context, manifest []byte, art *artifact.Artifact) error { + index := &v1.Index{} + if err := json.Unmarshal(manifest, index); err != nil { + return err + } + // populate the referenced artifacts + for _, mani := range index.Manifests { + digest := mani.Digest.String() + _, arts, err := i.artMgr.List(ctx, &q.Query{ + Keywords: map[string]interface{}{ + "RepositoryID": art.RepositoryID, + "Digest": digest, + }, + }) + if err != nil { + return err + } + // make sure the child artifact exist + if len(arts) == 0 { + return fmt.Errorf("the referenced artifact with digest %s not found under repository %d", + digest, art.RepositoryID) + } + art.References = append(art.References, &artifact.Reference{ + ChildID: arts[0].ID, + Platform: mani.Platform, + }) + } return nil } diff --git a/src/api/artifact/abstractor/resolver/image/index_test.go b/src/api/artifact/abstractor/resolver/image/index_test.go new file mode 100644 index 000000000..151e3244b --- /dev/null +++ b/src/api/artifact/abstractor/resolver/image/index_test.go @@ -0,0 +1,137 @@ +// 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 image + +import ( + "github.com/goharbor/harbor/src/pkg/artifact" + htesting "github.com/goharbor/harbor/src/testing" + "github.com/stretchr/testify/suite" + "testing" +) + +type indexResolverTestSuite struct { + suite.Suite + resolver *indexResolver + artMgr *htesting.FakeArtifactManager +} + +func (i *indexResolverTestSuite) SetupTest() { + i.artMgr = &htesting.FakeArtifactManager{} + i.resolver = &indexResolver{ + artMgr: i.artMgr, + } + +} + +func (i *indexResolverTestSuite) TestArtifactType() { + i.Assert().Equal(ArtifactTypeImage, i.resolver.ArtifactType()) +} + +func (i *indexResolverTestSuite) TestResolve() { + manifest := `{ + "manifests": [ + { + "digest": "sha256:92c7f9c92844bbbb5d0a101b22f7c2a7949e40f8ea90c8b3bc396879d95e899a", + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "platform": { + "architecture": "amd64", + "os": "linux" + }, + "size": 524 + }, + { + "digest": "sha256:e5785cb0c62cebbed4965129bae371f0589cadd6d84798fb58c2c5f9e237efd9", + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "platform": { + "architecture": "arm", + "os": "linux", + "variant": "v5" + }, + "size": 525 + }, + { + "digest": "sha256:50b8560ad574c779908da71f7ce370c0a2471c098d44d1c8f6b513c5a55eeeb1", + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "platform": { + "architecture": "arm", + "os": "linux", + "variant": "v7" + }, + "size": 525 + }, + { + "digest": "sha256:963612c5503f3f1674f315c67089dee577d8cc6afc18565e0b4183ae355fb343", + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "platform": { + "architecture": "arm64", + "os": "linux", + "variant": "v8" + }, + "size": 525 + }, + { + "digest": "sha256:85dc5fbe16214366748ebe9d7cc73bc42d61d19d61fe05f01e317d278c2287ed", + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "platform": { + "architecture": "386", + "os": "linux" + }, + "size": 527 + }, + { + "digest": "sha256:8aaea2a718a29334caeaf225716284ce29dc17418edba98dbe6dafea5afcda16", + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "platform": { + "architecture": "ppc64le", + "os": "linux" + }, + "size": 525 + }, + { + "digest": "sha256:577ad4331d4fac91807308da99ecc107dcc6b2254bc4c7166325fd01113bea2a", + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "platform": { + "architecture": "s390x", + "os": "linux" + }, + "size": 525 + }, + { + "digest": "sha256:351e40a9ab7ca6818dfbf9c967d1dd15599438edc41189e3d4d87eeffba5b8bf", + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "platform": { + "architecture": "amd64", + "os": "windows", + "os.version": "10.0.17763.914" + }, + "size": 1124 + } + ], + "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json", + "schemaVersion": 2 +}` + art := &artifact.Artifact{} + i.artMgr.On("List").Return(1, []*artifact.Artifact{ + { + ID: 1, + }, + }, nil) + err := i.resolver.Resolve(nil, []byte(manifest), art) + i.Require().Nil(err) + i.artMgr.AssertExpectations(i.T()) + i.Assert().Len(art.References, 8) + i.Assert().Equal(int64(1), art.References[0].ChildID) + i.Assert().Equal("amd64", art.References[0].Platform.Architecture) +} + +func TestIndexResolverTestSuite(t *testing.T) { + suite.Run(t, &indexResolverTestSuite{}) +}