...

Source file src/sigs.k8s.io/kustomize/api/krusty/remoteloader_test.go

Documentation: sigs.k8s.io/kustomize/api/krusty

     1  // Copyright 2022 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package krusty_test
     5  
     6  import (
     7  	"bytes"
     8  	"encoding/base64"
     9  	"fmt"
    10  	"io"
    11  	"os"
    12  	"os/exec"
    13  	"path/filepath"
    14  	"strings"
    15  	"testing"
    16  
    17  	"github.com/stretchr/testify/assert"
    18  	"github.com/stretchr/testify/require"
    19  	"sigs.k8s.io/kustomize/api/internal/loader"
    20  	"sigs.k8s.io/kustomize/api/krusty"
    21  	"sigs.k8s.io/kustomize/api/resmap"
    22  	kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
    23  	"sigs.k8s.io/kustomize/kyaml/yaml"
    24  )
    25  
    26  func TestRemoteLoad_LocalProtocol(t *testing.T) {
    27  	type testRepos struct {
    28  		root          string
    29  		simple        string
    30  		noSuffix      string
    31  		hash          string
    32  		multiBaseDev  string
    33  		withSubmodule string
    34  	}
    35  
    36  	// creates git repos under a root temporary directory with the following structure
    37  	// root/
    38  	//   simple.git/			- base with just a pod
    39  	//   nosuffix/				- same as simple.git/ without the .git suffix
    40  	//   hash-xx/				- same as simple.git/ with random hash at xx
    41  	//   multibase.git/			- base with a dev overlay
    42  	//   with-submodule.git/	- includes `simple` as a submodule
    43  	//     submodule/			- the submodule referencing `simple`
    44  	createGitRepos := func(t *testing.T) testRepos {
    45  		t.Helper()
    46  
    47  		bash := func(script string) {
    48  			cmd := exec.Command("sh", "-c", script)
    49  			o, err := cmd.CombinedOutput()
    50  			if err != nil {
    51  				t.Fatalf("error running %v\nerr: %v\n%s", script, err, string(o))
    52  			}
    53  		}
    54  		root := t.TempDir()
    55  
    56  		hashPath, err := os.MkdirTemp(root, "hash-")
    57  		require.NoError(t, err)
    58  		hashDir := filepath.Base(hashPath)
    59  
    60  		bash(fmt.Sprintf(`
    61  set -eux
    62  
    63  export ROOT="%s"
    64  export HASH_DIR="%s"
    65  export GIT_AUTHOR_EMAIL=nobody@kustomize.io
    66  export GIT_AUTHOR_NAME=Nobody
    67  export GIT_COMMITTER_EMAIL=nobody@kustomize.io
    68  export GIT_COMMITTER_NAME=Nobody
    69  
    70  cp -r testdata/remoteload/simple $ROOT/simple.git
    71  (
    72  	cd $ROOT/simple.git
    73  	git config --global protocol.file.allow always
    74  	git init --initial-branch=main
    75  	git add .
    76  	git commit -m "import"
    77  	git checkout -b change-image
    78  	cat >>kustomization.yaml <<EOF
    79  
    80  images:
    81  - name: nginx
    82    newName: nginx
    83    newTag: "2"
    84  EOF
    85  	git commit -am "image change"
    86  	git checkout main
    87  )
    88  cp -r $ROOT/simple.git $ROOT/nosuffix
    89  cp -r testdata/remoteload/multibase $ROOT/multibase.git
    90  (
    91  	cd $ROOT/multibase.git
    92  	git init --initial-branch=main
    93  	git add .
    94  	git commit -m "import"
    95  )
    96  cp -r testdata/remoteload/with-submodule $ROOT/with-submodule.git # see README
    97  cp -r $ROOT/simple.git/. $ROOT/$HASH_DIR
    98  (
    99  	cd $ROOT/with-submodule.git
   100  	git init --initial-branch=main
   101  	git add .
   102  	git commit -m "import"
   103  	git checkout -b relative-submodule
   104  	git submodule add ../$HASH_DIR submodule
   105  	git commit -m "relative submodule"
   106  	git checkout main
   107  	git submodule add $ROOT/simple.git submodule
   108  	git commit -m "submodule"	
   109  )
   110  `, root, hashDir))
   111  		return testRepos{
   112  			root: root,
   113  			// The strings below aren't currently used, and more serve as documentation.
   114  			simple:        "simple.git",
   115  			noSuffix:      "nosuffix",
   116  			hash:          hashDir,
   117  			multiBaseDev:  "multibase.git",
   118  			withSubmodule: "with-submodule.git",
   119  		}
   120  	}
   121  
   122  	const simpleBuild = `apiVersion: v1
   123  kind: Pod
   124  metadata:
   125    labels:
   126      app: myapp
   127    name: myapp-pod
   128  spec:
   129    containers:
   130    - image: nginx:1.7.9
   131      name: nginx
   132  `
   133  	var simpleBuildWithNginx2 = strings.ReplaceAll(simpleBuild, "nginx:1.7.9", "nginx:2")
   134  	var multibaseDevExampleBuild = strings.ReplaceAll(simpleBuild, "myapp-pod", "dev-myapp-pod")
   135  
   136  	repos := createGitRepos(t)
   137  	tests := []struct {
   138  		name          string
   139  		kustomization string
   140  		expected      string
   141  		err           string
   142  		skip          bool
   143  	}{
   144  		{
   145  			name: "simple",
   146  			kustomization: `
   147  resources:
   148  - file://$ROOT/simple.git
   149  `,
   150  			expected: simpleBuild,
   151  		},
   152  		{
   153  			name: "without git suffix",
   154  			kustomization: `
   155  resources:
   156  - file://$ROOT/nosuffix
   157  `,
   158  			expected: simpleBuild,
   159  		},
   160  		{
   161  			name: "has path",
   162  			kustomization: `
   163  resources:
   164  - file://$ROOT/multibase.git/dev
   165  `,
   166  			expected: multibaseDevExampleBuild,
   167  		},
   168  		{
   169  			name: "has path without git suffix",
   170  			kustomization: `
   171  resources:
   172  - file://$ROOT/multibase//dev
   173  `,
   174  			expected: multibaseDevExampleBuild,
   175  		},
   176  		{
   177  			name: "has ref",
   178  			kustomization: `
   179  resources: 
   180  - "file://$ROOT/simple.git?ref=change-image"
   181  `,
   182  
   183  			expected: simpleBuildWithNginx2,
   184  		},
   185  		{
   186  			// Version is the same as ref
   187  			name: "has version",
   188  			kustomization: `
   189  resources:
   190  - file://$ROOT/simple.git?version=change-image
   191  `,
   192  			expected: simpleBuildWithNginx2,
   193  		},
   194  		{
   195  			name: "has submodule",
   196  			kustomization: `
   197  resources:
   198  - file://$ROOT/with-submodule.git/submodule
   199  `,
   200  			expected: simpleBuild,
   201  		},
   202  		{
   203  			name: "has relative submodule",
   204  			kustomization: `
   205  resources:
   206  - file://$ROOT/with-submodule.git/submodule?ref=relative-submodule
   207  `,
   208  
   209  			expected: simpleBuild,
   210  		},
   211  		{
   212  			name: "has timeout",
   213  			kustomization: `
   214  resources:
   215  - file://$ROOT/simple.git?timeout=500
   216  `,
   217  			expected: simpleBuild,
   218  		},
   219  		{
   220  			name: "has submodule but not initialized",
   221  			kustomization: `
   222  resources:
   223  - file://$ROOT/with-submodule.git/submodule?submodules=0
   224  `,
   225  			err: "unable to find one of 'kustomization.yaml', 'kustomization.yml' or 'Kustomization' in directory",
   226  		},
   227  		{
   228  			name: "has origin annotation",
   229  			skip: true, // The annotated path should be "pod.yaml" but is "notCloned/pod.yaml"
   230  			kustomization: `
   231  resources:
   232  - file://$ROOT/simple.git
   233  buildMetadata: [originAnnotations]
   234  `,
   235  			expected: `apiVersion: v1
   236  kind: Pod
   237  metadata:
   238    annotations:
   239      config.kubernetes.io/origin: |
   240        path: pod.yaml
   241        repo: file://$ROOT/simple.git
   242    labels:
   243      app: myapp
   244    name: myapp-pod
   245  spec:
   246    containers:
   247    - image: nginx:1.7.9
   248      name: nginx
   249  `,
   250  		},
   251  		{
   252  			name: "has ref path timeout and origin annotation",
   253  			kustomization: `
   254  resources:
   255  - file://$ROOT/multibase.git/dev?version=main&timeout=500
   256  buildMetadata: [originAnnotations]
   257  `,
   258  			expected: `apiVersion: v1
   259  kind: Pod
   260  metadata:
   261    annotations:
   262      config.kubernetes.io/origin: |
   263        path: base/pod.yaml
   264        repo: file://$ROOT/multibase.git
   265        ref: main
   266    labels:
   267      app: myapp
   268    name: dev-myapp-pod
   269  spec:
   270    containers:
   271    - image: nginx:1.7.9
   272      name: nginx
   273  `,
   274  		},
   275  		{
   276  			name: "repo does not exist ",
   277  			kustomization: `
   278  resources:
   279  - file:///not/a/real/repo
   280  `,
   281  			err: "fatal: '/not/a/real/repo' does not appear to be a git repository",
   282  		},
   283  	}
   284  
   285  	for _, test := range tests {
   286  		t.Run(test.name, func(t *testing.T) {
   287  			if test.skip {
   288  				t.SkipNow()
   289  			}
   290  
   291  			kust := strings.ReplaceAll(test.kustomization, "$ROOT", repos.root)
   292  			fSys, tmpDir := kusttest_test.CreateKustDir(t, kust)
   293  
   294  			b := krusty.MakeKustomizer(krusty.MakeDefaultOptions())
   295  			m, err := b.Run(
   296  				fSys,
   297  				tmpDir.String())
   298  
   299  			if test.err != "" {
   300  				require.Error(t, err)
   301  				require.Regexp(t, test.err, err.Error())
   302  			} else {
   303  				require.NoError(t, err)
   304  				checkYaml(t, m, strings.ReplaceAll(test.expected, "$ROOT", repos.root))
   305  			}
   306  		})
   307  	}
   308  }
   309  
   310  func TestRemoteLoad_RemoteProtocols(t *testing.T) {
   311  	// Slow remote tests with long timeouts.
   312  	// TODO: If these end up flaking, they should retry. If not, remove this TODO.
   313  	tests := []struct {
   314  		name          string
   315  		kustomization string
   316  		err           string
   317  		errT          error
   318  		beforeTest    func(t *testing.T)
   319  	}{
   320  		{
   321  			name: "https",
   322  			kustomization: `
   323  resources:
   324  - https://github.com/kubernetes-sigs/kustomize//examples/multibases/dev/?submodules=0&ref=kustomize%2Fv4.5.7&timeout=300
   325  `,
   326  		},
   327  		{
   328  			name: "https with explicit .git suffix",
   329  			kustomization: `
   330  resources:
   331  - https://github.com/kubernetes-sigs/kustomize.git//examples/multibases/dev/?submodules=0&ref=kustomize%2Fv4.5.7&timeout=300
   332  `,
   333  		},
   334  		{
   335  			name: "git double-colon https",
   336  			kustomization: `
   337  resources:
   338  - git::https://github.com/kubernetes-sigs/kustomize//examples/multibases/dev/?submodules=0&ref=kustomize%2Fv4.5.7&timeout=300
   339  `,
   340  		},
   341  		{
   342  			name: "https raw remote file",
   343  			kustomization: `
   344  resources:
   345  - https://raw.githubusercontent.com/kubernetes-sigs/kustomize/v3.1.0/examples/multibases/base/pod.yaml?timeout=300
   346  namePrefix: dev-
   347  `,
   348  		},
   349  		{
   350  			name: "https without scheme",
   351  			kustomization: `
   352  resources:
   353  - github.com/kubernetes-sigs/kustomize/examples/multibases/dev?submodules=0&ref=kustomize%2Fv4.5.7&timeout=300
   354  `,
   355  		},
   356  		{
   357  			name:       "ssh",
   358  			beforeTest: configureGitSSHCommand,
   359  			kustomization: `
   360  resources:
   361  - git@github.com/kubernetes-sigs/kustomize/examples/multibases/dev?submodules=0&ref=kustomize%2Fv4.5.7&timeout=300
   362  `,
   363  		},
   364  		{
   365  			name:       "ssh with colon",
   366  			beforeTest: configureGitSSHCommand,
   367  			kustomization: `
   368  resources:
   369  - git@github.com:kubernetes-sigs/kustomize/examples/multibases/dev?submodules=0&ref=kustomize%2Fv4.5.7&timeout=300
   370  `,
   371  		},
   372  		{
   373  			name:       "ssh scheme",
   374  			beforeTest: configureGitSSHCommand,
   375  			kustomization: `
   376  resources:
   377  - ssh://git@github.com/kubernetes-sigs/kustomize/examples/multibases/dev?submodules=0&ref=kustomize%2Fv4.5.7&timeout=300
   378  `,
   379  		},
   380  		{
   381  			name: "http error",
   382  			kustomization: `
   383  resources:
   384  - https://github.com/thisisa404.yaml
   385  `,
   386  			err:  "accumulating resources: accumulating resources from 'https://github.com/thisisa404.yaml': HTTP Error: status code 404 (Not Found)",
   387  			errT: loader.ErrHTTP,
   388  		},
   389  	}
   390  
   391  	for _, test := range tests {
   392  		t.Run(test.name, func(t *testing.T) {
   393  			if test.beforeTest != nil {
   394  				test.beforeTest(t)
   395  			}
   396  			fSys, tmpDir := kusttest_test.CreateKustDir(t, test.kustomization)
   397  
   398  			b := krusty.MakeKustomizer(krusty.MakeDefaultOptions())
   399  			m, err := b.Run(
   400  				fSys,
   401  				tmpDir.String())
   402  
   403  			if test.err != "" {
   404  				require.Error(t, err)
   405  				assert.Contains(t, err.Error(), test.err)
   406  				if test.errT != nil {
   407  					require.ErrorIs(t, err, test.errT)
   408  				}
   409  			} else {
   410  				require.NoError(t, err)
   411  				const multibaseDevExampleBuild = `apiVersion: v1
   412  kind: Pod
   413  metadata:
   414    labels:
   415      app: myapp
   416    name: dev-myapp-pod
   417  spec:
   418    containers:
   419    - image: nginx:1.7.9
   420      name: nginx
   421  `
   422  				checkYaml(t, m, multibaseDevExampleBuild)
   423  			}
   424  		})
   425  	}
   426  }
   427  
   428  func configureGitSSHCommand(t *testing.T) {
   429  	t.Helper()
   430  
   431  	// This contains a read-only Deploy Key for the kustomize repo.
   432  	node, err := yaml.ReadFile("testdata/repo_read_only_ssh_key.yaml")
   433  	require.NoError(t, err)
   434  	keyB64, err := node.GetString("key")
   435  	require.NoError(t, err)
   436  	key, err := base64.StdEncoding.DecodeString(keyB64)
   437  	require.NoError(t, err)
   438  
   439  	// Write the key to a temp file and use it in SSH
   440  	f, err := os.CreateTemp("", "kustomize_ssh")
   441  	require.NoError(t, err)
   442  	_, err = io.Copy(f, bytes.NewReader(key))
   443  	require.NoError(t, err)
   444  	cmd := fmt.Sprintf("ssh -i %s -o IdentitiesOnly=yes", f.Name())
   445  	const SSHCommandKey = "GIT_SSH_COMMAND"
   446  	t.Setenv(SSHCommandKey, cmd)
   447  	t.Cleanup(func() {
   448  		_ = os.Remove(f.Name())
   449  	})
   450  }
   451  
   452  func checkYaml(t *testing.T, actual resmap.ResMap, expected string) {
   453  	t.Helper()
   454  
   455  	yml, err := actual.AsYaml()
   456  	require.NoError(t, err)
   457  	assert.Equal(t, expected, string(yml))
   458  }
   459  

View as plain text