github.com/wmuizelaar/kpt@v0.0.0-20221018115725-bd564717b2ed/pkg/kptfile/kptfileutil/util_test.go (about)

     1  // Copyright 2020 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package kptfileutil
    16  
    17  import (
    18  	"os"
    19  	"path/filepath"
    20  	"strings"
    21  	"testing"
    22  
    23  	"github.com/GoogleContainerTools/kpt/internal/pkg"
    24  	kptfilev1 "github.com/GoogleContainerTools/kpt/pkg/api/kptfile/v1"
    25  	"github.com/stretchr/testify/assert"
    26  	"sigs.k8s.io/kustomize/kyaml/yaml"
    27  )
    28  
    29  // TestValidateInventory tests the ValidateInventory function.
    30  func TestValidateInventory(t *testing.T) {
    31  	// nil inventory should not validate
    32  	isValid, err := ValidateInventory(nil)
    33  	if isValid || err == nil {
    34  		t.Errorf("nil inventory should not validate")
    35  	}
    36  	// Empty inventory should not validate
    37  	inv := &kptfilev1.Inventory{}
    38  	isValid, err = ValidateInventory(inv)
    39  	if isValid || err == nil {
    40  		t.Errorf("empty inventory should not validate")
    41  	}
    42  	// Empty inventory parameters strings should not validate
    43  	inv = &kptfilev1.Inventory{
    44  		Namespace:   "",
    45  		Name:        "",
    46  		InventoryID: "",
    47  	}
    48  	isValid, err = ValidateInventory(inv)
    49  	if isValid || err == nil {
    50  		t.Errorf("empty inventory parameters strings should not validate")
    51  	}
    52  	// Inventory with non-empty namespace, name, and id should validate.
    53  	inv = &kptfilev1.Inventory{
    54  		Namespace:   "test-namespace",
    55  		Name:        "test-name",
    56  		InventoryID: "test-id",
    57  	}
    58  	isValid, err = ValidateInventory(inv)
    59  	if !isValid || err != nil {
    60  		t.Errorf("inventory with non-empty namespace, name, and id should validate")
    61  	}
    62  }
    63  
    64  func TestUpdateKptfile(t *testing.T) {
    65  	writeKptfileToTemp := func(tt *testing.T, content string) string {
    66  		dir := tt.TempDir()
    67  		err := os.WriteFile(filepath.Join(dir, kptfilev1.KptFileName), []byte(content), 0600)
    68  		if !assert.NoError(t, err) {
    69  			t.FailNow()
    70  		}
    71  		return dir
    72  	}
    73  
    74  	testCases := map[string]struct {
    75  		origin         string
    76  		updated        string
    77  		local          string
    78  		updateUpstream bool
    79  		expected       string
    80  	}{
    81  		"no pipeline and no upstream info": {
    82  			origin: `
    83  apiVersion: kpt.dev/v1
    84  kind: Kptfile
    85  metadata:
    86    name: base
    87  `,
    88  			updated: `
    89  apiVersion: kpt.dev/v1
    90  kind: Kptfile
    91  metadata:
    92    name: base
    93  `,
    94  			local: `
    95  apiVersion: kpt.dev/v1
    96  kind: Kptfile
    97  metadata:
    98    name: foo
    99  `,
   100  			updateUpstream: false,
   101  			expected: `
   102  apiVersion: kpt.dev/v1
   103  kind: Kptfile
   104  metadata:
   105    name: foo
   106  `,
   107  		},
   108  
   109  		"upstream information is not copied from upstream unless updateUpstream is true": {
   110  			origin: `
   111  apiVersion: kpt.dev/v1
   112  kind: Kptfile
   113  metadata:
   114    name: foo
   115  upstream:
   116    type: git
   117    git:
   118      repo: github.com/GoogleContainerTools/kpt
   119      directory: /
   120      ref: v1
   121  `,
   122  			updated: `
   123  apiVersion: kpt.dev/v1
   124  kind: Kptfile
   125  metadata:
   126    name: foo
   127  upstream:
   128    type: git
   129    git:
   130      repo: github.com/GoogleContainerTools/kpt
   131      directory: /
   132      ref: v2
   133  `,
   134  			local: `
   135  apiVersion: kpt.dev/v1
   136  kind: Kptfile
   137  metadata:
   138    name: foo
   139  `,
   140  			updateUpstream: false,
   141  			expected: `
   142  apiVersion: kpt.dev/v1
   143  kind: Kptfile
   144  metadata:
   145    name: foo
   146  `,
   147  		},
   148  
   149  		"upstream information is copied from upstream when updateUpstream is true": {
   150  			origin: `
   151  apiVersion: kpt.dev/v1
   152  kind: Kptfile
   153  metadata:
   154    name: foo
   155  upstream:
   156    type: git
   157    git:
   158      repo: github.com/GoogleContainerTools/kpt
   159      directory: /
   160      ref: v1
   161  `,
   162  			updated: `
   163  apiVersion: kpt.dev/v1
   164  kind: Kptfile
   165  metadata:
   166    name: foo
   167  upstream:
   168    type: git
   169    git:
   170      repo: github.com/GoogleContainerTools/kpt
   171      directory: /
   172      ref: v2
   173  upstreamLock:
   174    type: git
   175    git:
   176      repo: github.com/GoogleContainerTools/kpt
   177      directory: /
   178      ref: v2
   179      commit: abc123
   180  `,
   181  			local: `
   182  apiVersion: kpt.dev/v1
   183  kind: Kptfile
   184  metadata:
   185    name: foo
   186  `,
   187  			updateUpstream: true,
   188  			expected: `
   189  apiVersion: kpt.dev/v1
   190  kind: Kptfile
   191  metadata:
   192    name: foo
   193  upstream:
   194    type: git
   195    git:
   196      repo: github.com/GoogleContainerTools/kpt
   197      directory: /
   198      ref: v2
   199  upstreamLock:
   200    type: git
   201    git:
   202      repo: github.com/GoogleContainerTools/kpt
   203      directory: /
   204      ref: v2
   205      commit: abc123
   206  `,
   207  		},
   208  
   209  		"pipeline in local remains if there are no changes in upstream": {
   210  			origin: `
   211  apiVersion: kpt.dev/v1
   212  kind: Kptfile
   213  metadata:
   214    name: foo
   215  pipeline:
   216    mutators:
   217      - image: foo:bar
   218  `,
   219  			updated: `
   220  apiVersion: kpt.dev/v1
   221  kind: Kptfile
   222  metadata:
   223    name: foo
   224  pipeline:
   225    mutators:
   226      - image: foo:bar
   227  `,
   228  			local: `
   229  apiVersion: kpt.dev/v1
   230  kind: Kptfile
   231  metadata:
   232    name: foo
   233  pipeline:
   234    mutators:
   235      - image: my:image
   236        configMap:
   237          foo: bar
   238      - image: foo:bar
   239  `,
   240  			updateUpstream: true,
   241  			expected: `
   242  apiVersion: kpt.dev/v1
   243  kind: Kptfile
   244  metadata:
   245    name: foo
   246  pipeline:
   247    mutators:
   248      - image: my:image
   249        configMap:
   250          foo: bar
   251      - image: foo:bar
   252  `,
   253  		},
   254  
   255  		"pipeline remains if it is only added locally": {
   256  			origin: `
   257  apiVersion: kpt.dev/v1
   258  kind: Kptfile
   259  metadata:
   260    name: foo
   261  `,
   262  			updated: `
   263  apiVersion: kpt.dev/v1
   264  kind: Kptfile
   265  metadata:
   266    name: foo
   267  `,
   268  			local: `
   269  apiVersion: kpt.dev/v1
   270  kind: Kptfile
   271  metadata:
   272    name: foo
   273  pipeline:
   274    mutators:
   275      - image: my:image
   276      - image: foo:bar
   277  `,
   278  			updateUpstream: true,
   279  			expected: `
   280  apiVersion: kpt.dev/v1
   281  kind: Kptfile
   282  metadata:
   283    name: foo
   284  pipeline:
   285    mutators:
   286      - image: my:image
   287      - image: foo:bar
   288  `,
   289  		},
   290  
   291  		"pipeline in local is emptied if it is gone from upstream": {
   292  			origin: `
   293  apiVersion: kpt.dev/v1
   294  kind: Kptfile
   295  metadata:
   296    name: foo
   297  pipeline:
   298    mutators:
   299      - image: foo:bar
   300  `,
   301  			updated: `
   302  apiVersion: kpt.dev/v1
   303  kind: Kptfile
   304  metadata:
   305    name: foo
   306  `,
   307  			local: `
   308  apiVersion: kpt.dev/v1
   309  kind: Kptfile
   310  metadata:
   311    name: foo
   312  pipeline:
   313    mutators:
   314      - image: my:image
   315      - image: foo:bar
   316  `,
   317  			updateUpstream: false,
   318  			expected: `
   319  apiVersion: kpt.dev/v1
   320  kind: Kptfile
   321  metadata:
   322    name: foo
   323  pipeline: {}
   324  `,
   325  		},
   326  		"first readinessGate and condition added in upstream": {
   327  			origin: `
   328  apiVersion: kpt.dev/v1
   329  kind: Kptfile
   330  metadata:
   331    name: foo
   332  `,
   333  			updated: `
   334  apiVersion: kpt.dev/v1
   335  kind: Kptfile
   336  metadata:
   337    name: foo
   338  info:
   339    readinessGates:
   340    - conditionType: foo
   341  status:
   342    conditions:
   343    - type: foo
   344      status: "True"
   345      reason: reason
   346      message: message
   347  `,
   348  			local: `
   349  apiVersion: kpt.dev/v1
   350  kind: Kptfile
   351  metadata:
   352    name: foo
   353  `,
   354  			updateUpstream: false,
   355  			expected: `
   356  apiVersion: kpt.dev/v1
   357  kind: Kptfile
   358  metadata:
   359    name: foo
   360  info:
   361    readinessGates:
   362      - conditionType: foo
   363  status:
   364    conditions:
   365      - type: foo
   366        status: "True"
   367        reason: reason
   368        message: message
   369  `,
   370  		},
   371  		"additional readinessGate and condition added in upstream": {
   372  			origin: `
   373  apiVersion: kpt.dev/v1
   374  kind: Kptfile
   375  metadata:
   376    name: foo
   377  info:
   378    readinessGates:
   379      - conditionType: foo
   380  status:
   381    conditions:
   382      - type: foo
   383        status: "True"
   384        reason: reason
   385        message: message
   386  `,
   387  			updated: `
   388  apiVersion: kpt.dev/v1
   389  kind: Kptfile
   390  metadata:
   391    name: foo
   392  info:
   393    readinessGates:
   394      - conditionType: foo
   395      - conditionType: bar
   396  status:
   397    conditions:
   398      - type: foo
   399        status: "True"
   400        reason: reason
   401        message: message
   402      - type: bar
   403        status: "False"
   404        reason: reason
   405        message: message
   406  `,
   407  			local: `
   408  apiVersion: kpt.dev/v1
   409  kind: Kptfile
   410  metadata:
   411    name: foo
   412  info:
   413    readinessGates:
   414      - conditionType: foo
   415  status:
   416    conditions:
   417      - type: foo
   418        status: "True"
   419        reason: reason
   420        message: message
   421  `,
   422  			updateUpstream: false,
   423  			expected: `
   424  apiVersion: kpt.dev/v1
   425  kind: Kptfile
   426  metadata:
   427    name: foo
   428  info:
   429    readinessGates:
   430      - conditionType: foo
   431      - conditionType: bar
   432  status:
   433    conditions:
   434      - type: foo
   435        status: "True"
   436        reason: reason
   437        message: message
   438      - type: bar
   439        status: "False"
   440        reason: reason
   441        message: message
   442  		`,
   443  		},
   444  		"readinessGate added removed in upstream": {
   445  			origin: `
   446  apiVersion: kpt.dev/v1
   447  kind: Kptfile
   448  metadata:
   449    name: foo
   450  info:
   451    readinessGates:
   452      - conditionType: foo
   453  status:
   454    conditions:
   455      - type: foo
   456        status: "True"
   457        reason: reason
   458        message: message
   459  `,
   460  			updated: `
   461  apiVersion: kpt.dev/v1
   462  kind: Kptfile
   463  metadata:
   464    name: foo
   465  `,
   466  			local: `
   467  apiVersion: kpt.dev/v1
   468  kind: Kptfile
   469  metadata:
   470    name: foo
   471  info:
   472    readinessGates:
   473      - conditionType: foo
   474  status:
   475    conditions:
   476      - type: foo
   477        status: "True"
   478        reason: reason
   479        message: message
   480  `,
   481  			updateUpstream: false,
   482  			expected: `
   483  apiVersion: kpt.dev/v1
   484  kind: Kptfile
   485  metadata:
   486    name: foo
   487  info: {}
   488  status: {}
   489  `,
   490  		},
   491  		"readinessGates removed and added in both upstream and local": {
   492  			origin: `
   493  apiVersion: kpt.dev/v1
   494  kind: Kptfile
   495  metadata:
   496    name: foo
   497  info:
   498    readinessGates:
   499      - conditionType: foo
   500      - conditionType: bar
   501  status:
   502    conditions:
   503      - type: foo
   504        status: "True"
   505        reason: reason
   506        message: message
   507      - type: bar
   508        status: "False"
   509        reason: reason
   510        message: message
   511  `,
   512  			updated: `
   513  apiVersion: kpt.dev/v1
   514  kind: Kptfile
   515  metadata:
   516    name: foo
   517  info:
   518    readinessGates:
   519      - conditionType: foo
   520      - conditionType: zork
   521  status:
   522    conditions:
   523      - type: foo
   524        status: "True"
   525        reason: reason
   526        message: message
   527      - type: zork
   528        status: "Unknown"
   529        reason: reason
   530        message: message
   531  `,
   532  			local: `
   533  apiVersion: kpt.dev/v1
   534  kind: Kptfile
   535  metadata:
   536    name: foo
   537  info:
   538    readinessGates:
   539      - conditionType: xandar
   540      - conditionType: foo
   541  status:
   542    conditions:
   543      - type: xandar
   544        status: "True"
   545        reason: reason
   546        message: message
   547      - type: foo
   548        status: "True"
   549        reason: reason
   550        message: message  
   551  `,
   552  			updateUpstream: false,
   553  			expected: `
   554  apiVersion: kpt.dev/v1
   555  kind: Kptfile
   556  metadata:
   557    name: foo
   558  info:
   559    readinessGates:
   560      - conditionType: foo
   561      - conditionType: zork
   562  status:
   563    conditions:
   564      - type: foo
   565        status: "True"
   566        reason: reason
   567        message: message
   568      - type: zork
   569        status: Unknown
   570        reason: reason
   571        message: message
   572  `,
   573  		},
   574  	}
   575  
   576  	for tn, tc := range testCases {
   577  		t.Run(tn, func(t *testing.T) {
   578  			files := map[string]string{
   579  				"origin":  tc.origin,
   580  				"updated": tc.updated,
   581  				"local":   tc.local,
   582  			}
   583  			dirs := make(map[string]string)
   584  			for n, content := range files {
   585  				dir := writeKptfileToTemp(t, content)
   586  				dirs[n] = dir
   587  			}
   588  
   589  			err := UpdateKptfile(dirs["local"], dirs["updated"], dirs["origin"], tc.updateUpstream)
   590  			if !assert.NoError(t, err) {
   591  				t.FailNow()
   592  			}
   593  
   594  			c, err := os.ReadFile(filepath.Join(dirs["local"], kptfilev1.KptFileName))
   595  			if !assert.NoError(t, err) {
   596  				t.FailNow()
   597  			}
   598  
   599  			assert.Equal(t, strings.TrimSpace(tc.expected)+"\n", string(c))
   600  		})
   601  	}
   602  }
   603  
   604  func TestMerge(t *testing.T) {
   605  	testCases := map[string]struct {
   606  		origin   string
   607  		update   string
   608  		local    string
   609  		expected string
   610  		err      error
   611  	}{
   612  		// With no associative key, there is no merge, just a replacement
   613  		// of the pipeline with upstream. This is aligned with the general behavior
   614  		// of kyaml merge where in conflicts the upstream version win.s
   615  		"no associative key, additions in both upstream and local": {
   616  			origin: `
   617  apiVersion: kpt.dev/v1
   618  kind: Kptfile
   619  metadata:
   620    name: pipeline
   621  `,
   622  			update: `
   623  apiVersion: kpt.dev/v1
   624  kind: Kptfile
   625  metadata:
   626    name: pipeline
   627  pipeline:
   628    mutators:
   629    - image: gcr.io/kpt/gen-folders
   630  `,
   631  			local: `
   632  apiVersion: kpt.dev/v1
   633  kind: Kptfile
   634  metadata:
   635    name: pipeline
   636  pipeline:
   637    mutators:
   638    - image: gcr.io/kpt/folder-ref
   639  `,
   640  			expected: `
   641  apiVersion: kpt.dev/v1
   642  kind: Kptfile
   643  metadata:
   644    name: pipeline
   645  pipeline:
   646    mutators:
   647    - image: gcr.io/kpt/folder-ref
   648    - image: gcr.io/kpt/gen-folders
   649  `,
   650  		},
   651  
   652  		"exec: no associative key, additions in both upstream and local": {
   653  			origin: `
   654  apiVersion: kpt.dev/v1
   655  kind: Kptfile
   656  metadata:
   657    name: pipeline
   658  `,
   659  			update: `
   660  apiVersion: kpt.dev/v1
   661  kind: Kptfile
   662  metadata:
   663    name: pipeline
   664  pipeline:
   665    mutators:
   666    - exec: gen-folders
   667  `,
   668  			local: `
   669  apiVersion: kpt.dev/v1
   670  kind: Kptfile
   671  metadata:
   672    name: pipeline
   673  pipeline:
   674    mutators:
   675    - exec: folder-ref
   676  `,
   677  			expected: `
   678  apiVersion: kpt.dev/v1
   679  kind: Kptfile
   680  metadata:
   681    name: pipeline
   682  pipeline:
   683    mutators:
   684    - exec: folder-ref
   685    - exec: gen-folders
   686  `,
   687  		},
   688  
   689  		"add new setter in upstream, update local setter value": {
   690  			origin: `
   691  apiVersion: kpt.dev/v1
   692  kind: Kptfile
   693  metadata:
   694    name: pipeline
   695  pipeline:
   696    mutators:
   697      - image: gcr.io/kpt-fn/apply-setters:v0.1
   698        configMap:
   699          image: nginx
   700          tag: 1.0.1
   701  `,
   702  			update: `
   703  apiVersion: kpt.dev/v1
   704  kind: Kptfile
   705  metadata:
   706    name: pipeline
   707  pipeline:
   708    mutators:
   709      - image: gcr.io/kpt-fn/apply-setters:v0.1
   710        configMap:
   711          image: nginx
   712          tag: 1.0.1
   713          new-setter: new-setter-value // new setter is added
   714  `,
   715  			local: `
   716  apiVersion: kpt.dev/v1
   717  kind: Kptfile
   718  metadata:
   719    name: pipeline
   720  pipeline:
   721    mutators:
   722      - image: gcr.io/kpt-fn/apply-setters:v0.1
   723        configMap:
   724          image: nginx
   725          tag: 1.2.0 // value of tag is updated
   726  `,
   727  			expected: `
   728  apiVersion: kpt.dev/v1
   729  kind: Kptfile
   730  metadata:
   731    name: pipeline
   732  pipeline:
   733    mutators:
   734    - image: gcr.io/kpt-fn/apply-setters:v0.1
   735      configMap:
   736        image: nginx
   737        new-setter: new-setter-value // new setter is added
   738        tag: 1.2.0 // value of tag is updated
   739  `,
   740  		},
   741  
   742  		"both upstream and local configPath is updated, take upstream": {
   743  			origin: `
   744  apiVersion: kpt.dev/v1
   745  kind: Kptfile
   746  metadata:
   747    name: pipeline
   748  pipeline:
   749    mutators:
   750      - image: gcr.io/kpt-fn/apply-setters:v0.1
   751        configPath: setters.yaml
   752  `,
   753  			update: `
   754  apiVersion: kpt.dev/v1
   755  kind: Kptfile
   756  metadata:
   757    name: pipeline
   758  pipeline:
   759    mutators:
   760      - image: gcr.io/kpt-fn/apply-setters:v0.1
   761        configPath: setters-updated.yaml
   762  `,
   763  			local: `
   764  apiVersion: kpt.dev/v1
   765  kind: Kptfile
   766  metadata:
   767    name: pipeline
   768  pipeline:
   769    mutators:
   770      - image: gcr.io/kpt-fn/apply-setters:v0.1
   771        configPath: setters-local.yaml
   772  `,
   773  			expected: `
   774  apiVersion: kpt.dev/v1
   775  kind: Kptfile
   776  metadata:
   777    name: pipeline
   778  pipeline:
   779    mutators:
   780    - image: gcr.io/kpt-fn/apply-setters:v0.1
   781      configPath: setters-updated.yaml
   782  `,
   783  		},
   784  
   785  		"both upstream and local version is updated, take upstream": {
   786  			origin: `
   787  apiVersion: kpt.dev/v1
   788  kind: Kptfile
   789  metadata:
   790    name: pipeline
   791  pipeline:
   792    mutators:
   793      - image: gcr.io/kpt-fn/apply-setters:v0.1
   794        configPath: setters.yaml
   795  `,
   796  			update: `
   797  apiVersion: kpt.dev/v1
   798  kind: Kptfile
   799  metadata:
   800    name: pipeline
   801  pipeline:
   802    mutators:
   803      - image: gcr.io/kpt-fn/apply-setters:v0.1.2
   804        configPath: setters.yaml
   805  `,
   806  			local: `
   807  apiVersion: kpt.dev/v1
   808  kind: Kptfile
   809  metadata:
   810    name: pipeline
   811  pipeline:
   812    mutators:
   813      - image: gcr.io/kpt-fn/apply-setters:v0.1.1
   814        configPath: setters.yaml
   815  `,
   816  			expected: `
   817  apiVersion: kpt.dev/v1
   818  kind: Kptfile
   819  metadata:
   820    name: pipeline
   821  pipeline:
   822    mutators:
   823    - image: gcr.io/kpt-fn/apply-setters:v0.1.2
   824      configPath: setters.yaml
   825  `,
   826  		},
   827  
   828  		"newly added upstream function": {
   829  			origin: `
   830  apiVersion: kpt.dev/v1
   831  kind: Kptfile
   832  metadata:
   833    name: pipeline
   834  pipeline:
   835    mutators:
   836      - image: gcr.io/kpt-fn/apply-setters:v0.1
   837        configPath: setters.yaml
   838  `,
   839  			update: `
   840  apiVersion: kpt.dev/v1
   841  kind: Kptfile
   842  metadata:
   843    name: pipeline
   844  pipeline:
   845    mutators:
   846      - image: gcr.io/kpt-fn/apply-setters:v0.1
   847        configPath: setters.yaml
   848      - image: gcr.io/kpt-fn/generate-folders:v0.1
   849  `,
   850  			local: `
   851  apiVersion: kpt.dev/v1
   852  kind: Kptfile
   853  metadata:
   854    name: pipeline
   855  pipeline:
   856    mutators:
   857      - image: gcr.io/kpt-fn/apply-setters:v0.1
   858        configPath: setters.yaml
   859      - image: gcr.io/kpt-fn/set-namespace:v0.1
   860        configMap:
   861          namespace: foo
   862  `,
   863  			expected: `
   864  apiVersion: kpt.dev/v1
   865  kind: Kptfile
   866  metadata:
   867    name: pipeline
   868  pipeline:
   869    mutators:
   870    - image: gcr.io/kpt-fn/apply-setters:v0.1
   871      configPath: setters.yaml
   872    - image: gcr.io/kpt-fn/set-namespace:v0.1
   873      configMap:
   874        namespace: foo
   875    - image: gcr.io/kpt-fn/generate-folders:v0.1
   876  `,
   877  		},
   878  
   879  		"deleted function in the upstream, deleted on local if not changed": {
   880  			origin: `
   881  apiVersion: kpt.dev/v1
   882  kind: Kptfile
   883  metadata:
   884    name: pipeline
   885  pipeline:
   886    validators:
   887      - image: gcr.io/kpt-fn/apply-setters:v0.1
   888        configPath: setters.yaml
   889      - image: gcr.io/kpt-fn/generate-folders:v0.1
   890  `,
   891  			update: `
   892  apiVersion: kpt.dev/v1
   893  kind: Kptfile
   894  metadata:
   895    name: pipeline
   896  pipeline:
   897    validators:
   898      - image: gcr.io/kpt-fn/apply-setters:v0.1
   899        configPath: setters.yaml
   900  `,
   901  			local: `
   902  apiVersion: kpt.dev/v1
   903  kind: Kptfile
   904  metadata:
   905    name: pipeline
   906  pipeline:
   907    validators:
   908      - image: gcr.io/kpt-fn/apply-setters:v0.1
   909        configPath: setters.yaml
   910      - image: gcr.io/kpt-fn/generate-folders:v0.1
   911      - image: gcr.io/kpt-fn/set-namespace:v0.1
   912        configMap:
   913          namespace: foo
   914  `,
   915  			expected: `
   916  apiVersion: kpt.dev/v1
   917  kind: Kptfile
   918  metadata:
   919    name: pipeline
   920  pipeline:
   921    validators:
   922    - image: gcr.io/kpt-fn/apply-setters:v0.1
   923      configPath: setters.yaml
   924    - image: gcr.io/kpt-fn/set-namespace:v0.1
   925      configMap:
   926        namespace: foo
   927  `,
   928  		},
   929  
   930  		"multiple declarations of same function": {
   931  			origin: `
   932  apiVersion: kpt.dev/v1
   933  kind: Kptfile
   934  metadata:
   935    name: pipeline
   936  pipeline:
   937    mutators:
   938      - image: gcr.io/kpt-fn/search-replace:v0.1
   939        configMap:
   940          by-value: foo
   941          put-value: bar
   942      - image: gcr.io/kpt-fn/search-replace:v0.1
   943        configMap:
   944          by-value: abc
   945          put-comment: ${some-setter-name}
   946  `,
   947  			update: `
   948  apiVersion: kpt.dev/v1
   949  kind: Kptfile
   950  metadata:
   951    name: pipeline
   952  pipeline:
   953    mutators:
   954      - image: gcr.io/kpt-fn/search-replace:v0.1
   955        configMap:
   956          by-value: foo
   957          put-value: bar-new
   958      - image: gcr.io/kpt-fn/search-replace:v0.1
   959        configMap:
   960          by-value: abc
   961          put-comment: ${updated-setter-name}
   962  `,
   963  			local: `
   964  apiVersion: kpt.dev/v1
   965  kind: Kptfile
   966  metadata:
   967    name: pipeline
   968  pipeline:
   969    mutators:
   970      - image: gcr.io/kpt-fn/generate-folders:v0.1
   971      - image: gcr.io/kpt-fn/search-replace:v0.1
   972        configMap:
   973          by-value: foo
   974          put-value: bar
   975      - image: gcr.io/kpt-fn/set-labels:v0.1
   976        configMap:
   977          app: db
   978      - image: gcr.io/kpt-fn/search-replace:v0.1
   979        configMap:
   980          by-value: abc
   981          put-comment: ${some-setter-name}
   982      - image: gcr.io/kpt-fn/search-replace:v0.1
   983        configMap:
   984          by-value: YOUR_TEAM
   985          put-value: my-team
   986  `,
   987  			expected: `
   988  apiVersion: kpt.dev/v1
   989  kind: Kptfile
   990  metadata:
   991    name: pipeline
   992  pipeline:
   993    mutators:
   994    - image: gcr.io/kpt-fn/search-replace:v0.1
   995      configMap:
   996        by-value: foo
   997        put-value: bar-new
   998    - image: gcr.io/kpt-fn/search-replace:v0.1
   999      configMap:
  1000        by-value: abc
  1001        put-comment: ${updated-setter-name}
  1002  `,
  1003  		},
  1004  
  1005  		"add function at random location with name specified": {
  1006  			origin: `
  1007  apiVersion: kpt.dev/v1
  1008  kind: Kptfile
  1009  metadata:
  1010    name: pipeline
  1011  pipeline:
  1012    mutators:
  1013      - image: gcr.io/kpt-fn/search-replace:v0.1
  1014        configMap:
  1015          by-value: foo
  1016          put-value: bar
  1017      - image: gcr.io/kpt-fn/search-replace:v0.1
  1018        configMap:
  1019          by-value: abc
  1020          put-comment: ${some-setter-name}
  1021  `,
  1022  			update: `
  1023  apiVersion: kpt.dev/v1
  1024  kind: Kptfile
  1025  metadata:
  1026    name: pipeline
  1027  pipeline:
  1028    mutators:
  1029      - image: gcr.io/kpt-fn/search-replace:v0.1
  1030        configMap:
  1031          by-value: foo
  1032          put-value: bar-new
  1033      - image: gcr.io/kpt-fn/search-replace:v0.1
  1034        configMap:
  1035          by-value: abc
  1036          put-comment: ${updated-setter-name}
  1037  `,
  1038  			local: `
  1039  apiVersion: kpt.dev/v1
  1040  kind: Kptfile
  1041  metadata:
  1042    name: pipeline
  1043  pipeline:
  1044    mutators:
  1045      - image: gcr.io/kpt-fn/search-replace:v0.1
  1046        name: my-new-function
  1047        configMap:
  1048          by-value: YOUR_TEAM
  1049          put-value: my-team
  1050      - image: gcr.io/kpt-fn/generate-folders:v0.1
  1051      - image: gcr.io/kpt-fn/search-replace:v0.1
  1052        configMap:
  1053          by-value: foo
  1054          put-value: bar
  1055      - image: gcr.io/kpt-fn/set-labels:v0.1
  1056        configMap:
  1057          app: db
  1058      - image: gcr.io/kpt-fn/search-replace:v0.1
  1059        configMap:
  1060          by-value: abc
  1061          put-comment: ${some-setter-name}
  1062  `,
  1063  			expected: `
  1064  apiVersion: kpt.dev/v1
  1065  kind: Kptfile
  1066  metadata:
  1067    name: pipeline
  1068  pipeline:
  1069    mutators:
  1070    - image: gcr.io/kpt-fn/search-replace:v0.1
  1071      configMap:
  1072        by-value: foo
  1073        put-value: bar-new
  1074    - image: gcr.io/kpt-fn/search-replace:v0.1
  1075      configMap:
  1076        by-value: abc
  1077        put-comment: ${updated-setter-name}
  1078  `,
  1079  		},
  1080  
  1081  		"Ideal deterministic behavior: add function at random location with name specified in all sources": {
  1082  			origin: `
  1083  apiVersion: kpt.dev/v1
  1084  kind: Kptfile
  1085  metadata:
  1086    name: pipeline
  1087  pipeline:
  1088    mutators:
  1089      - image: gcr.io/kpt-fn/search-replace:v0.1
  1090        name: sr1
  1091        configMap:
  1092          by-value: foo
  1093          put-value: bar
  1094      - image: gcr.io/kpt-fn/search-replace:v0.1
  1095        name: sr2
  1096        configMap:
  1097          by-value: abc
  1098          put-comment: ${some-setter-name}
  1099  `,
  1100  			update: `
  1101  apiVersion: kpt.dev/v1
  1102  kind: Kptfile
  1103  metadata:
  1104    name: pipeline
  1105  pipeline:
  1106    mutators:
  1107      - image: gcr.io/kpt-fn/search-replace:v0.1
  1108        name: sr1
  1109        configMap:
  1110          by-value: foo
  1111          put-value: bar-new
  1112      - image: gcr.io/kpt-fn/search-replace:v0.1
  1113        name: sr2
  1114        configMap:
  1115          by-value: abc
  1116          put-comment: ${updated-setter-name}
  1117  `,
  1118  			local: `
  1119  apiVersion: kpt.dev/v1
  1120  kind: Kptfile
  1121  metadata:
  1122    name: pipeline
  1123  pipeline:
  1124    mutators:
  1125      - image: gcr.io/kpt-fn/search-replace:v0.1
  1126        name: my-new-function
  1127        configMap:
  1128          by-value: YOUR_TEAM
  1129          put-value: my-team
  1130      - image: gcr.io/kpt-fn/generate-folders:v0.1
  1131        name: gf1
  1132      - image: gcr.io/kpt-fn/search-replace:v0.1
  1133        name: sr1
  1134        configMap:
  1135          by-value: foo
  1136          put-value: bar
  1137      - image: gcr.io/kpt-fn/set-labels:v0.1
  1138        name: sl1
  1139        configMap:
  1140          app: db
  1141      - image: gcr.io/kpt-fn/search-replace:v0.1
  1142        name: sr2
  1143        configMap:
  1144          by-value: abc
  1145          put-comment: ${some-setter-name}
  1146  `,
  1147  			expected: `
  1148  apiVersion: kpt.dev/v1
  1149  kind: Kptfile
  1150  metadata:
  1151    name: pipeline
  1152  pipeline:
  1153    mutators:
  1154    - image: gcr.io/kpt-fn/search-replace:v0.1
  1155      configMap:
  1156        by-value: YOUR_TEAM
  1157        put-value: my-team
  1158      name: my-new-function
  1159    - image: gcr.io/kpt-fn/generate-folders:v0.1
  1160      name: gf1
  1161    - image: gcr.io/kpt-fn/search-replace:v0.1
  1162      configMap:
  1163        by-value: foo
  1164        put-value: bar-new
  1165      name: sr1
  1166    - image: gcr.io/kpt-fn/set-labels:v0.1
  1167      configMap:
  1168        app: db
  1169      name: sl1
  1170    - image: gcr.io/kpt-fn/search-replace:v0.1
  1171      configMap:
  1172        by-value: abc
  1173        put-comment: ${updated-setter-name}
  1174      name: sr2
  1175  `,
  1176  		},
  1177  
  1178  		// When adding an associative key, we get a real merge of the pipeline.
  1179  		// In this case, we have an initial empty list in origin and different
  1180  		// functions are added in upstream and local. In this case the element
  1181  		// added in local are placed first in the resulting list.
  1182  		"associative key name, additions in both upstream and local": {
  1183  			origin: `
  1184  apiVersion: kpt.dev/v1
  1185  kind: Kptfile
  1186  metadata:
  1187    name: pipeline
  1188  `,
  1189  			update: `
  1190  apiVersion: kpt.dev/v1
  1191  kind: Kptfile
  1192  metadata:
  1193    name: pipeline
  1194  pipeline:
  1195    mutators:
  1196    - name: gen-folders
  1197      image: gcr.io/kpt/gen-folders
  1198  `,
  1199  			local: `
  1200  apiVersion: kpt.dev/v1
  1201  kind: Kptfile
  1202  metadata:
  1203    name: pipeline
  1204  pipeline:
  1205    mutators:
  1206    - name: folder-ref
  1207      image: gcr.io/kpt/folder-ref
  1208  `,
  1209  			// The reordering of elements in the results is a bug in the
  1210  			// merge logic I think.
  1211  			expected: `
  1212  apiVersion: kpt.dev/v1
  1213  kind: Kptfile
  1214  metadata:
  1215    name: pipeline
  1216  pipeline:
  1217    mutators:
  1218    - image: gcr.io/kpt/folder-ref
  1219      name: folder-ref
  1220    - image: gcr.io/kpt/gen-folders
  1221      name: gen-folders
  1222  `,
  1223  		},
  1224  
  1225  		// Even with multiple elements added in both upstream and local, all
  1226  		// elements from local comes before upstream, and the order of elements
  1227  		// from each source is preserved. There is no lexicographical
  1228  		// ordering.
  1229  		"associative key name, multiple additions in both upstream and local": {
  1230  			origin: `
  1231  apiVersion: kpt.dev/v1
  1232  kind: Kptfile
  1233  metadata:
  1234    name: pipeline
  1235  `,
  1236  			update: `
  1237  apiVersion: kpt.dev/v1
  1238  kind: Kptfile
  1239  metadata:
  1240    name: pipeline
  1241  pipeline:
  1242    mutators:
  1243    - name: z-upstream
  1244      image: z-gcr.io/kpt/gen-folders
  1245    - name: a-upstream
  1246      image: a-gcr.io/kpt/gen-folders
  1247  `,
  1248  			local: `
  1249  apiVersion: kpt.dev/v1
  1250  kind: Kptfile
  1251  metadata:
  1252    name: pipeline
  1253  pipeline:
  1254    mutators:
  1255    - name: x-local
  1256      image: x-gcr.io/kpt/gen-folders
  1257    - name: b-local
  1258      image: b-gcr.io/kpt/gen-folders
  1259  `,
  1260  			expected: `
  1261  apiVersion: kpt.dev/v1
  1262  kind: Kptfile
  1263  metadata:
  1264    name: pipeline
  1265  pipeline:
  1266    mutators:
  1267    - image: x-gcr.io/kpt/gen-folders
  1268      name: x-local
  1269    - image: b-gcr.io/kpt/gen-folders
  1270      name: b-local
  1271    - image: z-gcr.io/kpt/gen-folders
  1272      name: z-upstream
  1273    - image: a-gcr.io/kpt/gen-folders
  1274      name: a-upstream
  1275  `,
  1276  		},
  1277  
  1278  		// If elements with the same associative key are added in both upstream
  1279  		// and local, it will be merged. It will keep the location in the list
  1280  		// from local.
  1281  		"same element in both local and upstream does not create duplicate": {
  1282  			origin: `
  1283  apiVersion: kpt.dev/v1
  1284  kind: Kptfile
  1285  metadata:
  1286    name: pipeline
  1287  `,
  1288  			update: `
  1289  apiVersion: kpt.dev/v1
  1290  kind: Kptfile
  1291  metadata:
  1292    name: pipeline
  1293  pipeline:
  1294    mutators:
  1295    - name: gen-folder-upstream
  1296      image: gcr.io/kpt/gen-folders
  1297    - name: ref-folders
  1298      image: gcr.io/kpt/ref-folders
  1299      configMap:
  1300        foo: bar
  1301  `,
  1302  			local: `
  1303  apiVersion: kpt.dev/v1
  1304  kind: Kptfile
  1305  metadata:
  1306    name: pipeline
  1307  pipeline:
  1308    mutators:
  1309    - name: ref-folders
  1310      image: gcr.io/kpt/ref-folders
  1311      configMap:
  1312        bar: foo
  1313    - name: gen-folder-local
  1314      image: gcr.io/kpt/gen-folders
  1315  `,
  1316  			expected: `
  1317  apiVersion: kpt.dev/v1
  1318  kind: Kptfile
  1319  metadata:
  1320    name: pipeline
  1321  pipeline:
  1322    mutators:
  1323    - image: gcr.io/kpt/ref-folders
  1324      configMap:
  1325        bar: foo
  1326        foo: bar
  1327      name: ref-folders
  1328    - image: gcr.io/kpt/gen-folders
  1329      name: gen-folder-local
  1330    - image: gcr.io/kpt/gen-folders
  1331      name: gen-folder-upstream
  1332  `,
  1333  		},
  1334  
  1335  		// If a field are set in both upstream and local, the value from
  1336  		// upstream will be chosen.
  1337  		"If there is a field-level conflict, upstream will win": {
  1338  			origin: `
  1339  apiVersion: kpt.dev/v1
  1340  kind: Kptfile
  1341  metadata:
  1342    name: pipeline
  1343  `,
  1344  			update: `
  1345  apiVersion: kpt.dev/v1
  1346  kind: Kptfile
  1347  metadata:
  1348    name: pipeline
  1349  pipeline:
  1350    mutators:
  1351    - name: ref-folders
  1352      image: gcr.io/kpt/ref-folders
  1353      configMap:
  1354        band: sleater-kinney
  1355  `,
  1356  			local: `
  1357  apiVersion: kpt.dev/v1
  1358  kind: Kptfile
  1359  metadata:
  1360    name: pipeline
  1361  pipeline:
  1362    mutators:
  1363    - name: ref-folders
  1364      image: gcr.io/kpt/ref-folders
  1365      configMap:
  1366        band: Hüsker Dü
  1367  `,
  1368  			expected: `
  1369  apiVersion: kpt.dev/v1
  1370  kind: Kptfile
  1371  metadata:
  1372    name: pipeline
  1373  pipeline:
  1374    mutators:
  1375    - image: gcr.io/kpt/ref-folders
  1376      configMap:
  1377        band: sleater-kinney
  1378      name: ref-folders
  1379  `,
  1380  		},
  1381  	}
  1382  	for tn, tc := range testCases {
  1383  		t.Run(tn, func(t *testing.T) {
  1384  			localKf, err := pkg.DecodeKptfile(strings.NewReader(tc.local))
  1385  			assert.NoError(t, err)
  1386  			updatedKf, err := pkg.DecodeKptfile(strings.NewReader(tc.update))
  1387  			assert.NoError(t, err)
  1388  			originKf, err := pkg.DecodeKptfile(strings.NewReader(tc.origin))
  1389  			assert.NoError(t, err)
  1390  			err = merge(localKf, updatedKf, originKf)
  1391  			if tc.err == nil {
  1392  				if !assert.NoError(t, err) {
  1393  					t.FailNow()
  1394  				}
  1395  				actual, err := yaml.Marshal(localKf)
  1396  				assert.NoError(t, err)
  1397  				if !assert.Equal(t,
  1398  					strings.TrimSpace(tc.expected), strings.TrimSpace(string(actual))) {
  1399  					t.FailNow()
  1400  				}
  1401  			} else {
  1402  				if !assert.Error(t, err) {
  1403  					t.FailNow()
  1404  				}
  1405  				if !assert.Contains(t, tc.err.Error(), err.Error()) {
  1406  					t.FailNow()
  1407  				}
  1408  			}
  1409  		})
  1410  	}
  1411  }