github.com/replicatedhq/ship@v0.55.0/pkg/lifecycle/kustomize/pre_kustomize_test.go (about)

     1  package kustomize
     2  
     3  import (
     4  	"context"
     5  	"os"
     6  	"path/filepath"
     7  	"testing"
     8  
     9  	"github.com/go-kit/kit/log"
    10  	"github.com/replicatedhq/ship/pkg/api"
    11  	"github.com/replicatedhq/ship/pkg/state"
    12  	"github.com/replicatedhq/ship/pkg/util"
    13  	"github.com/spf13/afero"
    14  	"github.com/spf13/viper"
    15  	"github.com/stretchr/testify/require"
    16  	"sigs.k8s.io/kustomize/pkg/patch"
    17  	"sigs.k8s.io/kustomize/pkg/types"
    18  )
    19  
    20  type testFile struct {
    21  	path     string
    22  	contents string
    23  }
    24  
    25  func addTestFiles(fs afero.Afero, testFiles []testFile) error {
    26  	for _, testFile := range testFiles {
    27  		if err := fs.MkdirAll(filepath.Dir(testFile.path), 0755); err != nil {
    28  			return err
    29  		}
    30  		if err := fs.WriteFile(testFile.path, []byte(testFile.contents), 0644); err != nil {
    31  			return err
    32  		}
    33  	}
    34  	return nil
    35  }
    36  
    37  func readTestFiles(step api.Kustomize, fs afero.Afero) ([]testFile, error) {
    38  	files := []testFile{}
    39  	if err := fs.Walk(step.Base, func(targetPath string, info os.FileInfo, err error) error {
    40  		if filepath.Ext(targetPath) == ".yaml" {
    41  			contents, err := fs.ReadFile(targetPath)
    42  			if err != nil {
    43  				return err
    44  			}
    45  
    46  			files = append(files, testFile{
    47  				path:     targetPath,
    48  				contents: string(contents),
    49  			})
    50  		}
    51  		return nil
    52  	}); err != nil {
    53  		return files, err
    54  	}
    55  
    56  	return files, nil
    57  }
    58  
    59  func TestKustomizer_replaceOriginal(t *testing.T) {
    60  
    61  	tests := []struct {
    62  		name     string
    63  		step     api.Kustomize
    64  		built    []util.PostKustomizeFile
    65  		original []testFile
    66  		expect   []testFile
    67  	}{
    68  		{
    69  			name: "replace single file",
    70  			step: api.Kustomize{
    71  				Base: "",
    72  			},
    73  			built: []util.PostKustomizeFile{
    74  				{
    75  					Minimal: util.MinimalK8sYaml{
    76  						Kind: "Fruit",
    77  						Metadata: util.MinimalK8sMetadata{
    78  							Name: "strawberry",
    79  						},
    80  					},
    81  					Full: map[string]interface{}{
    82  						"kind": "Fruit",
    83  						"metadata": map[string]interface{}{
    84  							"name": "strawberry",
    85  						},
    86  						"spec": map[string]interface{}{
    87  							"modified": "modified",
    88  						},
    89  					},
    90  				},
    91  			},
    92  			original: []testFile{
    93  				{
    94  					path: "strawberry.yaml",
    95  					contents: `kind: Fruit
    96  metadata:
    97    name: strawberry
    98  spec:
    99    original: original
   100  `,
   101  				},
   102  			},
   103  			expect: []testFile{
   104  				{
   105  					path: "strawberry.yaml",
   106  					contents: `kind: Fruit
   107  metadata:
   108    name: strawberry
   109  spec:
   110    modified: modified
   111  `,
   112  				},
   113  			},
   114  		},
   115  		{
   116  			name: "skip CRDs",
   117  			step: api.Kustomize{
   118  				Base: "",
   119  			},
   120  			built: []util.PostKustomizeFile{
   121  				{
   122  					Minimal: util.MinimalK8sYaml{
   123  						Kind: "CustomResourceDefinition",
   124  						Metadata: util.MinimalK8sMetadata{
   125  							Name: "strawberry",
   126  						},
   127  					},
   128  					Full: map[string]interface{}{
   129  						"kind": "CustomResourceDefinition",
   130  						"metadata": map[string]interface{}{
   131  							"name": "strawberry",
   132  						},
   133  						"spec": map[string]interface{}{
   134  							"modified": "modified",
   135  						},
   136  					},
   137  				},
   138  			},
   139  			original: []testFile{
   140  				{
   141  					path: "strawberry.yaml",
   142  					contents: `kind: CustomResourceDefinition
   143  metadata:
   144    name: strawberry
   145  spec:
   146    original: original
   147  `,
   148  				},
   149  			},
   150  			expect: []testFile{
   151  				{
   152  					path: "strawberry.yaml",
   153  					contents: `kind: CustomResourceDefinition
   154  metadata:
   155    name: strawberry
   156  spec:
   157    original: original
   158  `,
   159  				},
   160  			},
   161  		},
   162  		{
   163  			name: "replace nested file",
   164  			step: api.Kustomize{
   165  				Base: "",
   166  			},
   167  			built: []util.PostKustomizeFile{
   168  				{
   169  					Minimal: util.MinimalK8sYaml{
   170  						Kind: "Fruit",
   171  						Metadata: util.MinimalK8sMetadata{
   172  							Name: "banana",
   173  						},
   174  					},
   175  					Full: map[string]interface{}{
   176  						"kind": "Fruit",
   177  						"metadata": map[string]interface{}{
   178  							"name": "banana",
   179  						},
   180  						"spec": map[string]interface{}{
   181  							"modified": "modified",
   182  						},
   183  					},
   184  				},
   185  			},
   186  			original: []testFile{
   187  				{
   188  					path: "somedir/banana.yaml",
   189  					contents: `kind: Fruit
   190  metadata:
   191    name: banana
   192  spec:
   193    original: original
   194  `,
   195  				},
   196  			},
   197  			expect: []testFile{
   198  				{
   199  					path: "somedir/banana.yaml",
   200  					contents: `kind: Fruit
   201  metadata:
   202    name: banana
   203  spec:
   204    modified: modified
   205  `,
   206  				},
   207  			},
   208  		},
   209  		{
   210  			name: "replace multiple files",
   211  			step: api.Kustomize{
   212  				Base: "",
   213  			},
   214  			built: []util.PostKustomizeFile{
   215  				{
   216  					Minimal: util.MinimalK8sYaml{
   217  						Kind: "Fruit",
   218  						Metadata: util.MinimalK8sMetadata{
   219  							Name: "dragonfruit",
   220  						},
   221  					},
   222  					Full: map[string]interface{}{
   223  						"kind": "Fruit",
   224  						"metadata": map[string]interface{}{
   225  							"name": "dragonfruit",
   226  						},
   227  						"spec": map[string]interface{}{
   228  							"modified": "modified dragonfruit",
   229  						},
   230  					},
   231  				},
   232  				{
   233  					Minimal: util.MinimalK8sYaml{
   234  						Kind: "Fruit",
   235  						Metadata: util.MinimalK8sMetadata{
   236  							Name: "pomegranate",
   237  						},
   238  					},
   239  					Full: map[string]interface{}{
   240  						"kind": "Fruit",
   241  						"metadata": map[string]interface{}{
   242  							"name": "pomegranate",
   243  						},
   244  						"spec": map[string]interface{}{
   245  							"modified": "modified pomegranate",
   246  						},
   247  					},
   248  				},
   249  			},
   250  			original: []testFile{
   251  				{
   252  					path: "somedir/dragonfruit.yaml",
   253  					contents: `kind: Fruit
   254  metadata:
   255    name: dragonfruit
   256  spec:
   257    original: original dragonfruit
   258  `,
   259  				},
   260  				{
   261  					path: "pomegranate.yaml",
   262  					contents: `kind: Fruit
   263  metadata:
   264    name: pomegranate
   265  spec:
   266    original: original pomegranate
   267  `,
   268  				},
   269  			},
   270  			expect: []testFile{
   271  				{
   272  					path: "somedir/dragonfruit.yaml",
   273  					contents: `kind: Fruit
   274  metadata:
   275    name: dragonfruit
   276  spec:
   277    modified: modified dragonfruit
   278  `,
   279  				},
   280  				{
   281  					path: "pomegranate.yaml",
   282  					contents: `kind: Fruit
   283  metadata:
   284    name: pomegranate
   285  spec:
   286    modified: modified pomegranate
   287  `,
   288  				},
   289  			},
   290  		},
   291  	}
   292  
   293  	for _, tt := range tests {
   294  		t.Run(tt.name, func(t *testing.T) {
   295  			req := require.New(t)
   296  			mockFs := afero.Afero{Fs: afero.NewMemMapFs()}
   297  			err := addTestFiles(mockFs, tt.original)
   298  			req.NoError(err)
   299  
   300  			l := &Kustomizer{
   301  				Logger: log.NewNopLogger(),
   302  				FS:     mockFs,
   303  			}
   304  
   305  			err = l.replaceOriginal(tt.step.Base, tt.built)
   306  			req.NoError(err)
   307  
   308  			actual, err := readTestFiles(tt.step, mockFs)
   309  			req.NoError(err)
   310  
   311  			req.ElementsMatch(tt.expect, actual)
   312  		})
   313  	}
   314  }
   315  
   316  func TestKustomizer_resolveExistingKustomize(t *testing.T) {
   317  	tests := []struct {
   318  		name       string
   319  		original   []testFile
   320  		InKust     *state.Kustomize
   321  		overlayDir string
   322  		WantKust   *state.Kustomize
   323  		wantErr    bool
   324  	}{
   325  		{
   326  			name: "no files in overlay dir, no current state",
   327  			original: []testFile{
   328  				{
   329  					path:     "test/overlays/ship/placeholder",
   330  					contents: "abc",
   331  				},
   332  			},
   333  			overlayDir: "test/overlays/ship",
   334  			InKust:     nil,
   335  			WantKust:   nil,
   336  		},
   337  		{
   338  			name: "no files in overlay dir, some current state",
   339  			original: []testFile{
   340  				{
   341  					path:     "test/overlays/placeholder",
   342  					contents: "abc",
   343  				},
   344  			},
   345  			overlayDir: "test/overlays",
   346  			InKust: &state.Kustomize{
   347  				Overlays: map[string]state.Overlay{
   348  					"ship": state.Overlay{
   349  						Patches:       map[string]string{"abc": "xyz"},
   350  						Resources:     map[string]string{"abc": "xyz"},
   351  						ExcludedBases: []string{"excludedBase"},
   352  					},
   353  				},
   354  			},
   355  			WantKust: &state.Kustomize{
   356  				Overlays: map[string]state.Overlay{
   357  					"ship": state.Overlay{
   358  						Patches:       map[string]string{"abc": "xyz"},
   359  						Resources:     map[string]string{"abc": "xyz"},
   360  						ExcludedBases: []string{"excludedBase"},
   361  					},
   362  				},
   363  			},
   364  		},
   365  		{
   366  			name: "garbled kustomization in current dir, some current state",
   367  			original: []testFile{
   368  				{
   369  					path:     "test/overlays/kustomization.yaml",
   370  					contents: "abc",
   371  				},
   372  			},
   373  			overlayDir: "test/overlays",
   374  			InKust: &state.Kustomize{
   375  				Overlays: map[string]state.Overlay{
   376  					"ship": state.Overlay{
   377  						Patches:       map[string]string{"abc": "xyz"},
   378  						Resources:     map[string]string{"abc": "xyz"},
   379  						ExcludedBases: []string{"excludedBase"},
   380  					},
   381  				},
   382  			},
   383  			wantErr: true,
   384  			WantKust: &state.Kustomize{
   385  				Overlays: map[string]state.Overlay{
   386  					"ship": state.Overlay{
   387  						Patches:       map[string]string{"abc": "xyz"},
   388  						Resources:     map[string]string{"abc": "xyz"},
   389  						ExcludedBases: []string{"excludedBase"},
   390  					},
   391  				},
   392  			},
   393  		},
   394  		{
   395  			name: "overlays and resources files in overlay dir, some current state",
   396  			original: []testFile{
   397  				{
   398  					path: "test/overlays/kustomization.yaml",
   399  					contents: `kind: ""
   400  apiversion: ""
   401  bases:
   402  - ../abc
   403  resources:
   404  - myresource.yaml
   405  patchesStrategicMerge:
   406  - mypatch.yaml
   407  `,
   408  				},
   409  				{
   410  					path:     "test/overlays/myresource.yaml",
   411  					contents: `this is my resource`,
   412  				},
   413  				{
   414  					path:     "test/overlays/mypatch.yaml",
   415  					contents: `this is my patch`,
   416  				},
   417  			},
   418  			overlayDir: "test/overlays",
   419  			InKust: &state.Kustomize{
   420  				Overlays: map[string]state.Overlay{
   421  					"ship": state.Overlay{
   422  						Patches:       map[string]string{"abc": "xyz"},
   423  						Resources:     map[string]string{"abc": "xyz"},
   424  						ExcludedBases: []string{"excludedBase"},
   425  					},
   426  				},
   427  			},
   428  			WantKust: &state.Kustomize{
   429  				Overlays: map[string]state.Overlay{
   430  					"ship": state.Overlay{
   431  						Patches:       map[string]string{"/mypatch.yaml": "this is my patch"},
   432  						Resources:     map[string]string{"/myresource.yaml": "this is my resource"},
   433  						ExcludedBases: []string{"excludedBase"},
   434  						RawKustomize: types.Kustomization{
   435  							PatchesStrategicMerge: []patch.StrategicMerge{"mypatch.yaml"},
   436  							Resources:             []string{"myresource.yaml"},
   437  							Bases:                 []string{"../abc"},
   438  						},
   439  					},
   440  				},
   441  			},
   442  		},
   443  		{
   444  			name: "resources files in overlay dir, no current state",
   445  			original: []testFile{
   446  				{
   447  					path: "test/overlays/kustomization.yaml",
   448  					contents: `kind: ""
   449  apiversion: ""
   450  bases:
   451  - ../abc
   452  resources:
   453  - myresource.yaml
   454  - myotherresource.yaml
   455  `,
   456  				},
   457  				{
   458  					path:     "test/overlays/myresource.yaml",
   459  					contents: `this is my resource`,
   460  				},
   461  				{
   462  					path:     "test/overlays/myotherresource.yaml",
   463  					contents: `this is my other resource`,
   464  				},
   465  			},
   466  			overlayDir: "test/overlays",
   467  			InKust:     nil,
   468  			WantKust: &state.Kustomize{
   469  				Overlays: map[string]state.Overlay{
   470  					"ship": state.Overlay{
   471  						Patches: map[string]string{},
   472  						Resources: map[string]string{
   473  							"/myresource.yaml":      "this is my resource",
   474  							"/myotherresource.yaml": "this is my other resource",
   475  						},
   476  						ExcludedBases: []string{},
   477  						RawKustomize: types.Kustomization{
   478  							Resources: []string{
   479  								"myresource.yaml",
   480  								"myotherresource.yaml",
   481  							},
   482  							Bases: []string{"../abc"},
   483  						},
   484  					},
   485  				},
   486  			},
   487  		},
   488  	}
   489  	for _, tt := range tests {
   490  		t.Run(tt.name, func(t *testing.T) {
   491  			req := require.New(t)
   492  			fs := afero.Afero{Fs: afero.NewMemMapFs()}
   493  
   494  			err := addTestFiles(fs, tt.original)
   495  			req.NoError(err)
   496  
   497  			manager, err := state.NewDisposableManager(log.NewNopLogger(), fs, viper.New())
   498  			req.NoError(err)
   499  
   500  			err = manager.SaveKustomize(tt.InKust)
   501  			req.NoError(err)
   502  
   503  			l := &Kustomizer{
   504  				Logger: log.NewNopLogger(),
   505  				FS:     fs,
   506  				State:  manager,
   507  			}
   508  
   509  			err = l.resolveExistingKustomize(context.Background(), tt.overlayDir)
   510  			if tt.wantErr {
   511  				req.Error(err)
   512  			} else {
   513  				req.NoError(err)
   514  			}
   515  
   516  			outState, err := manager.CachedState()
   517  			req.NoError(err)
   518  
   519  			outKust := outState.CurrentKustomize()
   520  			req.Equal(tt.WantKust, outKust)
   521  		})
   522  	}
   523  }