...

Source file src/edge-infra.dev/pkg/f8n/warehouse/promote/integration/promote_test.go

Documentation: edge-infra.dev/pkg/f8n/warehouse/promote/integration

     1  package integration
     2  
     3  import (
     4  	"context"
     5  	_ "embed"
     6  	"fmt"
     7  	"os"
     8  	"path"
     9  	"strings"
    10  	"testing"
    11  
    12  	"github.com/golang/mock/gomock"
    13  	"github.com/stretchr/testify/assert"
    14  	"github.com/stretchr/testify/require"
    15  
    16  	"edge-infra.dev/pkg/edge/api/mocks"
    17  	"edge-infra.dev/pkg/f8n/warehouse/cluster"
    18  	"edge-infra.dev/pkg/f8n/warehouse/oci"
    19  	"edge-infra.dev/pkg/f8n/warehouse/oci/layer"
    20  	"edge-infra.dev/pkg/f8n/warehouse/packagelock"
    21  	"edge-infra.dev/pkg/f8n/warehouse/pallet"
    22  	"edge-infra.dev/pkg/f8n/warehouse/promote"
    23  	"edge-infra.dev/pkg/k8s/object"
    24  	"edge-infra.dev/pkg/k8s/runtime/inventory"
    25  	"edge-infra.dev/pkg/k8s/unstructured"
    26  	"edge-infra.dev/test/f2"
    27  	"edge-infra.dev/test/f2/x/warehouse"
    28  )
    29  
    30  //go:embed testdata/minimal-pallet-v1/*
    31  var Minimalv1 []byte
    32  
    33  //go:embed testdata/minimal-pallet-v2/*
    34  var Minimalv2 []byte
    35  
    36  //go:embed testdata/minimal-pallet-v3/*
    37  var Minimalv3 []byte
    38  
    39  type testPallet struct {
    40  	inv    *inventory.ResourceInventory
    41  	digest string
    42  	name   string
    43  	tag    string
    44  	pallet pallet.Pallet
    45  }
    46  
    47  var (
    48  	f f2.Framework
    49  
    50  	lumperctl = "lumper-controller"
    51  	banner    = "banner-infra-cluster"
    52  	missing   = "missingpkg"
    53  
    54  	stage1 = "ret-edge-stage1-foreman"
    55  	stage2 = "ret-edge-stage2-foreman"
    56  
    57  	fakeHash   = "a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e" //nolint:golint,gosec
    58  	fakeDigest = fmt.Sprintf("sha256:%s", fakeHash)
    59  
    60  	pl                  packagelock.PackageLock
    61  	plMissingDigest     packagelock.PackageLock
    62  	plWithMissing       packagelock.PackageLock
    63  	plMixedValidMissing packagelock.PackageLock
    64  )
    65  
    66  func createMockPubSubService(t *testing.T) *mocks.MockPubSubService {
    67  	mockCtrl := gomock.NewController(t)
    68  	return mocks.NewMockPubSubService(mockCtrl)
    69  }
    70  
    71  func TestMain(m *testing.M) {
    72  	f = f2.New(context.Background(),
    73  		f2.WithExtensions(
    74  			&warehouse.Registry{},
    75  		),
    76  	).Setup(func(ctx f2.Context) (f2.Context, error) {
    77  		return ctx, nil
    78  	})
    79  	os.Exit(f.Run(m))
    80  }
    81  
    82  func TestPromotion(t *testing.T) {
    83  	var (
    84  		lumperctlLatestHash string
    85  		lumperctl014Hash    string
    86  		lumperctl013Hash    string
    87  		bannerLatestHash    string
    88  		banner014Hash       string
    89  		banner013Hash       string
    90  		lumperTagHashes     map[string]string
    91  		bannerTagHashes     map[string]string
    92  
    93  		testSourceRepo = promote.Repository{
    94  			Name:      promote.WarehouseRepo,
    95  			ProjectID: stage1,
    96  		}
    97  		testDestinationRepo = promote.Repository{
    98  			Name:      promote.WarehouseRepo,
    99  			ProjectID: stage2,
   100  			Registry:  "us-east1-docker.pkg.dev",
   101  			Ref:       "us-east1-docker.pkg.dev/ret-edge-stage2-foreman/warehouse",
   102  		}
   103  	)
   104  
   105  	inv := f2.NewFeature("Package promotion").
   106  		Setup("Setup test registry", func(ctx f2.Context, t *testing.T) f2.Context {
   107  			// set source repo to the local registry
   108  			url := warehouse.FromContextT(ctx, t).URL
   109  			host := strings.Split(url, "/")[0]
   110  
   111  			testSourceRepo.Registry = host
   112  			testSourceRepo.Ref = path.Join(url, stage1, promote.WarehouseRepo)
   113  
   114  			// add pallets to local repository
   115  			prefix := path.Join(stage1, promote.WarehouseRepo)
   116  			lumperctlLatestHash = createAndPushPallet(ctx, t, path.Join(prefix, lumperctl), promote.Latest, createLayer(t, layer.Runtime, Minimalv1))
   117  			lumperctl014Hash = createAndPushPallet(ctx, t, path.Join(prefix, lumperctl), "0.14", createLayer(t, layer.Runtime, Minimalv1))
   118  			lumperctl013Hash = createAndPushPallet(ctx, t, path.Join(prefix, lumperctl), "0.13", createLayer(t, layer.Runtime, Minimalv2))
   119  			bannerLatestHash = createAndPushPallet(ctx, t, path.Join(prefix, banner), promote.Latest, createLayer(t, layer.Runtime, Minimalv1))
   120  			banner014Hash = createAndPushPallet(ctx, t, path.Join(prefix, banner), "0.14", createLayer(t, layer.Runtime, Minimalv1))
   121  			banner013Hash = createAndPushPallet(ctx, t, path.Join(prefix, banner), "0.13", createLayer(t, layer.Runtime, Minimalv2))
   122  			lumperTagHashes = map[string]string{
   123  				"latest": lumperctlLatestHash,
   124  				"0.14":   lumperctl014Hash,
   125  				"0.13":   lumperctl013Hash,
   126  			}
   127  			bannerTagHashes = map[string]string{
   128  				"latest": bannerLatestHash,
   129  				"0.14":   banner014Hash,
   130  				"0.13":   banner013Hash,
   131  			}
   132  
   133  			lumperVersions := []packagelock.Version{
   134  				{Digest: lumperctl014Hash, Tags: []string{"0.14", "latest"}},
   135  				{Digest: lumperctl013Hash, Tags: []string{"0.13"}},
   136  			}
   137  
   138  			bannerVersions := []packagelock.Version{
   139  				{Digest: banner014Hash, Tags: []string{"0.14", "latest"}},
   140  				{Digest: banner013Hash, Tags: []string{"0.13"}},
   141  			}
   142  
   143  			bannerVersionsMissingDigest := []packagelock.Version{
   144  				{Digest: banner014Hash, Tags: []string{"0.14", "latest"}},
   145  				{Digest: banner013Hash, Tags: []string{"0.13"}},
   146  				{Digest: fakeDigest, Tags: []string{"0.12"}},
   147  			}
   148  
   149  			missingVersions := []packagelock.Version{
   150  				{Digest: fakeDigest, Tags: []string{"0.14"}},
   151  			}
   152  
   153  			pl = packagelock.PackageLock{
   154  				Packages: []packagelock.LockPackage{
   155  					{Name: lumperctl, Versions: lumperVersions},
   156  					{Name: banner, Versions: bannerVersions},
   157  				},
   158  			}
   159  
   160  			plMissingDigest = packagelock.PackageLock{
   161  				Packages: []packagelock.LockPackage{
   162  					{Name: lumperctl, Versions: lumperVersions},
   163  					{Name: banner, Versions: bannerVersionsMissingDigest},
   164  				},
   165  			}
   166  
   167  			plWithMissing = packagelock.PackageLock{
   168  				Packages: []packagelock.LockPackage{
   169  					{Name: missing, Versions: missingVersions},
   170  				},
   171  			}
   172  
   173  			plMixedValidMissing = packagelock.PackageLock{
   174  				Packages: []packagelock.LockPackage{
   175  					{Name: lumperctl, Versions: lumperVersions},
   176  					{Name: banner, Versions: bannerVersions},
   177  					{Name: missing, Versions: missingVersions},
   178  				},
   179  			}
   180  
   181  			return ctx
   182  		}).
   183  		Test("All packages in a valid package lock file are promoted", func(ctx f2.Context, t *testing.T) f2.Context {
   184  			testLockFile := "testdata/promote-test-lock/promote-test-lock.yaml"
   185  			cfg := &promote.Config{
   186  				LockFile:        testLockFile,
   187  				SourceRepo:      testSourceRepo,
   188  				DestinationRepo: testDestinationRepo,
   189  			}
   190  
   191  			// setup mocks
   192  			ps := createMockPubSubService(t)
   193  			expectedLumperLatestMessage := fmt.Sprintf(
   194  				`{"action":"%s","digest":"%s","tag":"%s","projectId":"%s","registry":"%s","promotion":"%s"}`,
   195  				promote.InsertAction,
   196  				fmt.Sprintf("%s/%s@%s", cfg.SourceRepo.Ref, lumperctl, lumperTagHashes["latest"]),
   197  				fmt.Sprintf("%s/%s:%s", cfg.DestinationRepo.Ref, lumperctl, promote.Latest),
   198  				cfg.DestinationRepo.ProjectID,
   199  				cfg.DestinationRepo.Name,
   200  				promote.PromotionTrue,
   201  			)
   202  			expectedLumper013Message := fmt.Sprintf(
   203  				`{"action":"%s","digest":"%s","tag":"%s","projectId":"%s","registry":"%s","promotion":"%s"}`,
   204  				promote.InsertAction,
   205  				fmt.Sprintf("%s/%s@%s", cfg.SourceRepo.Ref, lumperctl, lumperTagHashes["0.13"]),
   206  				fmt.Sprintf("%s/%s:%s", cfg.DestinationRepo.Ref, lumperctl, "0.13"),
   207  				cfg.DestinationRepo.ProjectID,
   208  				cfg.DestinationRepo.Name,
   209  				promote.PromotionTrue,
   210  			)
   211  			expectedLumper014Message := fmt.Sprintf(
   212  				`{"action":"%s","digest":"%s","tag":"%s","projectId":"%s","registry":"%s","promotion":"%s"}`,
   213  				promote.InsertAction,
   214  				fmt.Sprintf("%s/%s@%s", cfg.SourceRepo.Ref, lumperctl, lumperTagHashes["0.14"]),
   215  				fmt.Sprintf("%s/%s:%s", cfg.DestinationRepo.Ref, lumperctl, "0.14"),
   216  				cfg.DestinationRepo.ProjectID,
   217  				cfg.DestinationRepo.Name,
   218  				promote.PromotionTrue,
   219  			)
   220  			expectedBannerLatestMessage := fmt.Sprintf(
   221  				`{"action":"%s","digest":"%s","tag":"%s","projectId":"%s","registry":"%s","promotion":"%s"}`,
   222  				promote.InsertAction,
   223  				fmt.Sprintf("%s/%s@%s", cfg.SourceRepo.Ref, banner, bannerTagHashes["latest"]),
   224  				fmt.Sprintf("%s/%s:%s", cfg.DestinationRepo.Ref, banner, promote.Latest),
   225  				cfg.DestinationRepo.ProjectID,
   226  				cfg.DestinationRepo.Name,
   227  				promote.PromotionTrue,
   228  			)
   229  			expectedBanner013Message := fmt.Sprintf(
   230  				`{"action":"%s","digest":"%s","tag":"%s","projectId":"%s","registry":"%s","promotion":"%s"}`,
   231  				promote.InsertAction,
   232  				fmt.Sprintf("%s/%s@%s", cfg.SourceRepo.Ref, banner, bannerTagHashes["0.13"]),
   233  				fmt.Sprintf("%s/%s:%s", cfg.DestinationRepo.Ref, banner, "0.13"),
   234  				cfg.DestinationRepo.ProjectID,
   235  				cfg.DestinationRepo.Name,
   236  				promote.PromotionTrue,
   237  			)
   238  			expectedBanner014Message := fmt.Sprintf(
   239  				`{"action":"%s","digest":"%s","tag":"%s","projectId":"%s","registry":"%s","promotion":"%s"}`,
   240  				promote.InsertAction,
   241  				fmt.Sprintf("%s/%s@%s", cfg.SourceRepo.Ref, banner, bannerTagHashes["0.14"]),
   242  				fmt.Sprintf("%s/%s:%s", cfg.DestinationRepo.Ref, banner, "0.14"),
   243  				cfg.DestinationRepo.ProjectID,
   244  				cfg.DestinationRepo.Name,
   245  				promote.PromotionTrue,
   246  			)
   247  
   248  			// if running correctly, order does not matter
   249  			ps.EXPECT().Send(gomock.Any(), promote.PromotionsTopic, []byte(expectedLumper013Message), promote.Attributes).Return(nil)
   250  			ps.EXPECT().Send(gomock.Any(), promote.PromotionsTopic, []byte(expectedLumper014Message), promote.Attributes).Return(nil)
   251  			ps.EXPECT().Send(gomock.Any(), promote.PromotionsTopic, []byte(expectedLumperLatestMessage), promote.Attributes).Return(nil)
   252  			ps.EXPECT().Send(gomock.Any(), promote.PromotionsTopic, []byte(expectedBanner013Message), promote.Attributes).Return(nil)
   253  			ps.EXPECT().Send(gomock.Any(), promote.PromotionsTopic, []byte(expectedBanner014Message), promote.Attributes).Return(nil)
   254  			ps.EXPECT().Send(gomock.Any(), promote.PromotionsTopic, []byte(expectedBannerLatestMessage), promote.Attributes).Return(nil)
   255  
   256  			digests, err := promote.Promote(ctx, cfg, pl, ps)
   257  			fmt.Println("printing digests")
   258  			fmt.Println(digests)
   259  			assert.NoError(t, err)
   260  			return ctx
   261  		}).
   262  		Test("A missing package is caught prior to promotion executing", func(ctx f2.Context, t *testing.T) f2.Context {
   263  			cfg := &promote.Config{
   264  				SourceRepo:      testSourceRepo,
   265  				DestinationRepo: testDestinationRepo,
   266  			}
   267  
   268  			// setup mocks
   269  			ps := createMockPubSubService(t)
   270  
   271  			_, err := promote.Promote(ctx, cfg, plWithMissing, ps)
   272  			fmt.Println("should catch error")
   273  			fmt.Println(err)
   274  			assert.Error(t, err)
   275  			return ctx
   276  		}).
   277  		Test("A lockfile with both valid and invalid packages is caught prior to promotion executing", func(ctx f2.Context, t *testing.T) f2.Context {
   278  			cfg := &promote.Config{
   279  				SourceRepo:      testSourceRepo,
   280  				DestinationRepo: testDestinationRepo,
   281  			}
   282  
   283  			// setup mocks
   284  			ps := createMockPubSubService(t)
   285  
   286  			_, err := promote.Promote(ctx, cfg, plMixedValidMissing, ps)
   287  			fmt.Println("should catch error")
   288  			fmt.Println(err)
   289  			assert.Error(t, err)
   290  			return ctx
   291  		}).
   292  		Test("A lockfile with a valid package is missing a digest/tag", func(ctx f2.Context, t *testing.T) f2.Context {
   293  			cfg := &promote.Config{
   294  				SourceRepo:      testSourceRepo,
   295  				DestinationRepo: testDestinationRepo,
   296  			}
   297  
   298  			// setup mocks
   299  			ps := createMockPubSubService(t)
   300  
   301  			_, err := promote.Promote(ctx, cfg, plMixedValidMissing, ps)
   302  			fmt.Println("should catch error")
   303  			fmt.Println(err)
   304  			assert.Error(t, err)
   305  			return ctx
   306  		}).
   307  		Feature()
   308  
   309  	f.Test(t, inv)
   310  }
   311  
   312  // Creates a mock pallet and pushes it to the registry, returning its hash
   313  func createAndPushPallet(ctx f2.Context, t *testing.T, name, tag string, layers ...layer.Layer) string {
   314  	t.Helper()
   315  
   316  	p := testPallet{name: name, tag: tag}
   317  	var objs []*unstructured.Unstructured
   318  	for i, l := range layers {
   319  		r, err := l.Uncompressed()
   320  		require.NoError(t, err)
   321  		lobjs, err := object.ReadObjects(r)
   322  		require.NoError(t, err, "failed to read test pallet objects", name)
   323  		objs = append(objs, lobjs...)
   324  		layers[i] = l // replace updated layer
   325  	}
   326  	p.inv = inventory.New(inventory.FromUnstructured(objs...))
   327  
   328  	a, err := pallet.Image(pallet.Options{
   329  		Metadata:         palletMeta(name),
   330  		ClusterProviders: cluster.BuiltInProviders(),
   331  	}, layers...)
   332  	require.NoError(t, err, "failed to create test pallet", name)
   333  	p.pallet = a
   334  
   335  	hash, err := a.Digest()
   336  	require.NoError(t, err, "failed to get test pallet digest", name)
   337  	p.digest = hash.String()
   338  
   339  	pushArtifactToRegistry(ctx, t, a, name, tag)
   340  	return hash.String()
   341  }
   342  
   343  func palletMeta(name string) pallet.Metadata {
   344  	return pallet.Metadata{
   345  		Name: name,
   346  		Team: "@ncrvoyix-swt-retail/f8n",
   347  		BuildInfo: pallet.BuildInfo{
   348  			Created:  "yesterday",
   349  			Source:   "https://github.com/ncrvoyix-swt-retail/edge-infra",
   350  			Revision: "d34db33f",
   351  			Version:  "0.2.5",
   352  		},
   353  	}
   354  }
   355  
   356  func pushArtifactToRegistry(ctx f2.Context, t *testing.T, a oci.Artifact, name, tag string) {
   357  	t.Helper()
   358  	err := warehouse.FromContextT(ctx, t).Push(a, name, tag)
   359  	require.NoError(t, err, "failed to push test pallet", name)
   360  }
   361  
   362  // linting issues something about unused params
   363  func createLayer(t *testing.T, lt layer.Type, yaml []byte, opts ...layer.Option) layer.Layer { //nolint:golint,unparam
   364  	t.Helper()
   365  	l, err := layer.New(lt, yaml, opts...)
   366  	require.NoError(t, err, "failed to create test pallet layer")
   367  	return l
   368  }
   369  

View as plain text