package integration import ( "context" _ "embed" "fmt" "os" "path" "strings" "testing" "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "edge-infra.dev/pkg/edge/api/mocks" "edge-infra.dev/pkg/f8n/warehouse/cluster" "edge-infra.dev/pkg/f8n/warehouse/oci" "edge-infra.dev/pkg/f8n/warehouse/oci/layer" "edge-infra.dev/pkg/f8n/warehouse/packagelock" "edge-infra.dev/pkg/f8n/warehouse/pallet" "edge-infra.dev/pkg/f8n/warehouse/promote" "edge-infra.dev/pkg/k8s/object" "edge-infra.dev/pkg/k8s/runtime/inventory" "edge-infra.dev/pkg/k8s/unstructured" "edge-infra.dev/test/f2" "edge-infra.dev/test/f2/x/warehouse" ) //go:embed testdata/minimal-pallet-v1/* var Minimalv1 []byte //go:embed testdata/minimal-pallet-v2/* var Minimalv2 []byte //go:embed testdata/minimal-pallet-v3/* var Minimalv3 []byte type testPallet struct { inv *inventory.ResourceInventory digest string name string tag string pallet pallet.Pallet } var ( f f2.Framework lumperctl = "lumper-controller" banner = "banner-infra-cluster" missing = "missingpkg" stage1 = "ret-edge-stage1-foreman" stage2 = "ret-edge-stage2-foreman" fakeHash = "a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e" //nolint:golint,gosec fakeDigest = fmt.Sprintf("sha256:%s", fakeHash) pl packagelock.PackageLock plMissingDigest packagelock.PackageLock plWithMissing packagelock.PackageLock plMixedValidMissing packagelock.PackageLock ) func createMockPubSubService(t *testing.T) *mocks.MockPubSubService { mockCtrl := gomock.NewController(t) return mocks.NewMockPubSubService(mockCtrl) } func TestMain(m *testing.M) { f = f2.New(context.Background(), f2.WithExtensions( &warehouse.Registry{}, ), ).Setup(func(ctx f2.Context) (f2.Context, error) { return ctx, nil }) os.Exit(f.Run(m)) } func TestPromotion(t *testing.T) { var ( lumperctlLatestHash string lumperctl014Hash string lumperctl013Hash string bannerLatestHash string banner014Hash string banner013Hash string lumperTagHashes map[string]string bannerTagHashes map[string]string testSourceRepo = promote.Repository{ Name: promote.WarehouseRepo, ProjectID: stage1, } testDestinationRepo = promote.Repository{ Name: promote.WarehouseRepo, ProjectID: stage2, Registry: "us-east1-docker.pkg.dev", Ref: "us-east1-docker.pkg.dev/ret-edge-stage2-foreman/warehouse", } ) inv := f2.NewFeature("Package promotion"). Setup("Setup test registry", func(ctx f2.Context, t *testing.T) f2.Context { // set source repo to the local registry url := warehouse.FromContextT(ctx, t).URL host := strings.Split(url, "/")[0] testSourceRepo.Registry = host testSourceRepo.Ref = path.Join(url, stage1, promote.WarehouseRepo) // add pallets to local repository prefix := path.Join(stage1, promote.WarehouseRepo) lumperctlLatestHash = createAndPushPallet(ctx, t, path.Join(prefix, lumperctl), promote.Latest, createLayer(t, layer.Runtime, Minimalv1)) lumperctl014Hash = createAndPushPallet(ctx, t, path.Join(prefix, lumperctl), "0.14", createLayer(t, layer.Runtime, Minimalv1)) lumperctl013Hash = createAndPushPallet(ctx, t, path.Join(prefix, lumperctl), "0.13", createLayer(t, layer.Runtime, Minimalv2)) bannerLatestHash = createAndPushPallet(ctx, t, path.Join(prefix, banner), promote.Latest, createLayer(t, layer.Runtime, Minimalv1)) banner014Hash = createAndPushPallet(ctx, t, path.Join(prefix, banner), "0.14", createLayer(t, layer.Runtime, Minimalv1)) banner013Hash = createAndPushPallet(ctx, t, path.Join(prefix, banner), "0.13", createLayer(t, layer.Runtime, Minimalv2)) lumperTagHashes = map[string]string{ "latest": lumperctlLatestHash, "0.14": lumperctl014Hash, "0.13": lumperctl013Hash, } bannerTagHashes = map[string]string{ "latest": bannerLatestHash, "0.14": banner014Hash, "0.13": banner013Hash, } lumperVersions := []packagelock.Version{ {Digest: lumperctl014Hash, Tags: []string{"0.14", "latest"}}, {Digest: lumperctl013Hash, Tags: []string{"0.13"}}, } bannerVersions := []packagelock.Version{ {Digest: banner014Hash, Tags: []string{"0.14", "latest"}}, {Digest: banner013Hash, Tags: []string{"0.13"}}, } bannerVersionsMissingDigest := []packagelock.Version{ {Digest: banner014Hash, Tags: []string{"0.14", "latest"}}, {Digest: banner013Hash, Tags: []string{"0.13"}}, {Digest: fakeDigest, Tags: []string{"0.12"}}, } missingVersions := []packagelock.Version{ {Digest: fakeDigest, Tags: []string{"0.14"}}, } pl = packagelock.PackageLock{ Packages: []packagelock.LockPackage{ {Name: lumperctl, Versions: lumperVersions}, {Name: banner, Versions: bannerVersions}, }, } plMissingDigest = packagelock.PackageLock{ Packages: []packagelock.LockPackage{ {Name: lumperctl, Versions: lumperVersions}, {Name: banner, Versions: bannerVersionsMissingDigest}, }, } plWithMissing = packagelock.PackageLock{ Packages: []packagelock.LockPackage{ {Name: missing, Versions: missingVersions}, }, } plMixedValidMissing = packagelock.PackageLock{ Packages: []packagelock.LockPackage{ {Name: lumperctl, Versions: lumperVersions}, {Name: banner, Versions: bannerVersions}, {Name: missing, Versions: missingVersions}, }, } return ctx }). Test("All packages in a valid package lock file are promoted", func(ctx f2.Context, t *testing.T) f2.Context { testLockFile := "testdata/promote-test-lock/promote-test-lock.yaml" cfg := &promote.Config{ LockFile: testLockFile, SourceRepo: testSourceRepo, DestinationRepo: testDestinationRepo, } // setup mocks ps := createMockPubSubService(t) expectedLumperLatestMessage := fmt.Sprintf( `{"action":"%s","digest":"%s","tag":"%s","projectId":"%s","registry":"%s","promotion":"%s"}`, promote.InsertAction, fmt.Sprintf("%s/%s@%s", cfg.SourceRepo.Ref, lumperctl, lumperTagHashes["latest"]), fmt.Sprintf("%s/%s:%s", cfg.DestinationRepo.Ref, lumperctl, promote.Latest), cfg.DestinationRepo.ProjectID, cfg.DestinationRepo.Name, promote.PromotionTrue, ) expectedLumper013Message := fmt.Sprintf( `{"action":"%s","digest":"%s","tag":"%s","projectId":"%s","registry":"%s","promotion":"%s"}`, promote.InsertAction, fmt.Sprintf("%s/%s@%s", cfg.SourceRepo.Ref, lumperctl, lumperTagHashes["0.13"]), fmt.Sprintf("%s/%s:%s", cfg.DestinationRepo.Ref, lumperctl, "0.13"), cfg.DestinationRepo.ProjectID, cfg.DestinationRepo.Name, promote.PromotionTrue, ) expectedLumper014Message := fmt.Sprintf( `{"action":"%s","digest":"%s","tag":"%s","projectId":"%s","registry":"%s","promotion":"%s"}`, promote.InsertAction, fmt.Sprintf("%s/%s@%s", cfg.SourceRepo.Ref, lumperctl, lumperTagHashes["0.14"]), fmt.Sprintf("%s/%s:%s", cfg.DestinationRepo.Ref, lumperctl, "0.14"), cfg.DestinationRepo.ProjectID, cfg.DestinationRepo.Name, promote.PromotionTrue, ) expectedBannerLatestMessage := fmt.Sprintf( `{"action":"%s","digest":"%s","tag":"%s","projectId":"%s","registry":"%s","promotion":"%s"}`, promote.InsertAction, fmt.Sprintf("%s/%s@%s", cfg.SourceRepo.Ref, banner, bannerTagHashes["latest"]), fmt.Sprintf("%s/%s:%s", cfg.DestinationRepo.Ref, banner, promote.Latest), cfg.DestinationRepo.ProjectID, cfg.DestinationRepo.Name, promote.PromotionTrue, ) expectedBanner013Message := fmt.Sprintf( `{"action":"%s","digest":"%s","tag":"%s","projectId":"%s","registry":"%s","promotion":"%s"}`, promote.InsertAction, fmt.Sprintf("%s/%s@%s", cfg.SourceRepo.Ref, banner, bannerTagHashes["0.13"]), fmt.Sprintf("%s/%s:%s", cfg.DestinationRepo.Ref, banner, "0.13"), cfg.DestinationRepo.ProjectID, cfg.DestinationRepo.Name, promote.PromotionTrue, ) expectedBanner014Message := fmt.Sprintf( `{"action":"%s","digest":"%s","tag":"%s","projectId":"%s","registry":"%s","promotion":"%s"}`, promote.InsertAction, fmt.Sprintf("%s/%s@%s", cfg.SourceRepo.Ref, banner, bannerTagHashes["0.14"]), fmt.Sprintf("%s/%s:%s", cfg.DestinationRepo.Ref, banner, "0.14"), cfg.DestinationRepo.ProjectID, cfg.DestinationRepo.Name, promote.PromotionTrue, ) // if running correctly, order does not matter ps.EXPECT().Send(gomock.Any(), promote.PromotionsTopic, []byte(expectedLumper013Message), promote.Attributes).Return(nil) ps.EXPECT().Send(gomock.Any(), promote.PromotionsTopic, []byte(expectedLumper014Message), promote.Attributes).Return(nil) ps.EXPECT().Send(gomock.Any(), promote.PromotionsTopic, []byte(expectedLumperLatestMessage), promote.Attributes).Return(nil) ps.EXPECT().Send(gomock.Any(), promote.PromotionsTopic, []byte(expectedBanner013Message), promote.Attributes).Return(nil) ps.EXPECT().Send(gomock.Any(), promote.PromotionsTopic, []byte(expectedBanner014Message), promote.Attributes).Return(nil) ps.EXPECT().Send(gomock.Any(), promote.PromotionsTopic, []byte(expectedBannerLatestMessage), promote.Attributes).Return(nil) digests, err := promote.Promote(ctx, cfg, pl, ps) fmt.Println("printing digests") fmt.Println(digests) assert.NoError(t, err) return ctx }). Test("A missing package is caught prior to promotion executing", func(ctx f2.Context, t *testing.T) f2.Context { cfg := &promote.Config{ SourceRepo: testSourceRepo, DestinationRepo: testDestinationRepo, } // setup mocks ps := createMockPubSubService(t) _, err := promote.Promote(ctx, cfg, plWithMissing, ps) fmt.Println("should catch error") fmt.Println(err) assert.Error(t, err) return ctx }). Test("A lockfile with both valid and invalid packages is caught prior to promotion executing", func(ctx f2.Context, t *testing.T) f2.Context { cfg := &promote.Config{ SourceRepo: testSourceRepo, DestinationRepo: testDestinationRepo, } // setup mocks ps := createMockPubSubService(t) _, err := promote.Promote(ctx, cfg, plMixedValidMissing, ps) fmt.Println("should catch error") fmt.Println(err) assert.Error(t, err) return ctx }). Test("A lockfile with a valid package is missing a digest/tag", func(ctx f2.Context, t *testing.T) f2.Context { cfg := &promote.Config{ SourceRepo: testSourceRepo, DestinationRepo: testDestinationRepo, } // setup mocks ps := createMockPubSubService(t) _, err := promote.Promote(ctx, cfg, plMixedValidMissing, ps) fmt.Println("should catch error") fmt.Println(err) assert.Error(t, err) return ctx }). Feature() f.Test(t, inv) } // Creates a mock pallet and pushes it to the registry, returning its hash func createAndPushPallet(ctx f2.Context, t *testing.T, name, tag string, layers ...layer.Layer) string { t.Helper() p := testPallet{name: name, tag: tag} var objs []*unstructured.Unstructured for i, l := range layers { r, err := l.Uncompressed() require.NoError(t, err) lobjs, err := object.ReadObjects(r) require.NoError(t, err, "failed to read test pallet objects", name) objs = append(objs, lobjs...) layers[i] = l // replace updated layer } p.inv = inventory.New(inventory.FromUnstructured(objs...)) a, err := pallet.Image(pallet.Options{ Metadata: palletMeta(name), ClusterProviders: cluster.BuiltInProviders(), }, layers...) require.NoError(t, err, "failed to create test pallet", name) p.pallet = a hash, err := a.Digest() require.NoError(t, err, "failed to get test pallet digest", name) p.digest = hash.String() pushArtifactToRegistry(ctx, t, a, name, tag) return hash.String() } func palletMeta(name string) pallet.Metadata { return pallet.Metadata{ Name: name, Team: "@ncrvoyix-swt-retail/f8n", BuildInfo: pallet.BuildInfo{ Created: "yesterday", Source: "https://github.com/ncrvoyix-swt-retail/edge-infra", Revision: "d34db33f", Version: "0.2.5", }, } } func pushArtifactToRegistry(ctx f2.Context, t *testing.T, a oci.Artifact, name, tag string) { t.Helper() err := warehouse.FromContextT(ctx, t).Push(a, name, tag) require.NoError(t, err, "failed to push test pallet", name) } // linting issues something about unused params func createLayer(t *testing.T, lt layer.Type, yaml []byte, opts ...layer.Option) layer.Layer { //nolint:golint,unparam t.Helper() l, err := layer.New(lt, yaml, opts...) require.NoError(t, err, "failed to create test pallet layer") return l }