github.com/ouraigua/jenkins-library@v0.0.0-20231028010029-fbeaf2f3aa9b/cmd/helmExecute_test.go (about)

     1  //go:build unit
     2  // +build unit
     3  
     4  package cmd
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  	"path"
    10  	"testing"
    11  
    12  	"github.com/SAP/jenkins-library/pkg/kubernetes/mocks"
    13  	"github.com/SAP/jenkins-library/pkg/mock"
    14  	"github.com/SAP/jenkins-library/pkg/piperenv"
    15  	"github.com/pkg/errors"
    16  	"github.com/stretchr/testify/assert"
    17  	"github.com/stretchr/testify/require"
    18  )
    19  
    20  type helmMockUtilsBundle struct {
    21  	*mock.ExecMockRunner
    22  	*mock.FilesMock
    23  	*mock.HttpClientMock
    24  }
    25  
    26  func newHelmMockUtilsBundle() helmMockUtilsBundle {
    27  	utils := helmMockUtilsBundle{
    28  		ExecMockRunner: &mock.ExecMockRunner{},
    29  		FilesMock:      &mock.FilesMock{},
    30  		HttpClientMock: &mock.HttpClientMock{
    31  			FileUploads: map[string]string{},
    32  		},
    33  	}
    34  	return utils
    35  }
    36  
    37  func TestRunHelmUpgrade(t *testing.T) {
    38  	t.Parallel()
    39  
    40  	cpe := helmExecuteCommonPipelineEnvironment{}
    41  	testTable := []struct {
    42  		config         helmExecuteOptions
    43  		methodError    error
    44  		expectedErrStr string
    45  	}{
    46  		{
    47  			config: helmExecuteOptions{
    48  				HelmCommand: "upgrade",
    49  			},
    50  			methodError: nil,
    51  		},
    52  		{
    53  			config: helmExecuteOptions{
    54  				HelmCommand: "upgrade",
    55  			},
    56  			methodError:    errors.New("some error"),
    57  			expectedErrStr: "failed to execute upgrade: some error",
    58  		},
    59  	}
    60  
    61  	for i, testCase := range testTable {
    62  		t.Run(fmt.Sprint("case ", i), func(t *testing.T) {
    63  			helmExecute := &mocks.HelmExecutor{}
    64  			helmExecute.On("RunHelmUpgrade").Return(testCase.methodError)
    65  
    66  			err := runHelmExecute(testCase.config, helmExecute, &fileHandlerMock{}, &cpe)
    67  			if err != nil {
    68  				assert.Equal(t, testCase.expectedErrStr, err.Error())
    69  			}
    70  		})
    71  
    72  	}
    73  }
    74  
    75  func TestRunHelmLint(t *testing.T) {
    76  	t.Parallel()
    77  
    78  	cpe := helmExecuteCommonPipelineEnvironment{}
    79  	testTable := []struct {
    80  		config         helmExecuteOptions
    81  		expectedConfig []string
    82  		methodError    error
    83  		expectedErrStr string
    84  	}{
    85  		{
    86  			config: helmExecuteOptions{
    87  				HelmCommand: "lint",
    88  			},
    89  			methodError: nil,
    90  		},
    91  		{
    92  			config: helmExecuteOptions{
    93  				HelmCommand: "lint",
    94  			},
    95  			methodError:    errors.New("some error"),
    96  			expectedErrStr: "failed to execute helm lint: some error",
    97  		},
    98  	}
    99  
   100  	for i, testCase := range testTable {
   101  		t.Run(fmt.Sprint("case ", i), func(t *testing.T) {
   102  			helmExecute := &mocks.HelmExecutor{}
   103  			helmExecute.On("RunHelmLint").Return(testCase.methodError)
   104  
   105  			err := runHelmExecute(testCase.config, helmExecute, &fileHandlerMock{}, &cpe)
   106  			if err != nil {
   107  				assert.Equal(t, testCase.expectedErrStr, err.Error())
   108  			}
   109  		})
   110  
   111  	}
   112  }
   113  
   114  func TestRunHelmInstall(t *testing.T) {
   115  	t.Parallel()
   116  
   117  	cpe := helmExecuteCommonPipelineEnvironment{}
   118  	testTable := []struct {
   119  		config         helmExecuteOptions
   120  		expectedConfig []string
   121  		methodError    error
   122  		expectedErrStr string
   123  	}{
   124  		{
   125  			config: helmExecuteOptions{
   126  				HelmCommand: "install",
   127  			},
   128  			methodError: nil,
   129  		},
   130  		{
   131  			config: helmExecuteOptions{
   132  				HelmCommand: "install",
   133  			},
   134  			methodError:    errors.New("some error"),
   135  			expectedErrStr: "failed to execute helm install: some error",
   136  		},
   137  	}
   138  
   139  	for i, testCase := range testTable {
   140  		t.Run(fmt.Sprint("case ", i), func(t *testing.T) {
   141  			helmExecute := &mocks.HelmExecutor{}
   142  			helmExecute.On("RunHelmInstall").Return(testCase.methodError)
   143  
   144  			err := runHelmExecute(testCase.config, helmExecute, &fileHandlerMock{}, &cpe)
   145  			if err != nil {
   146  				assert.Equal(t, testCase.expectedErrStr, err.Error())
   147  			}
   148  		})
   149  
   150  	}
   151  }
   152  
   153  func TestRunHelmTest(t *testing.T) {
   154  	t.Parallel()
   155  
   156  	cpe := helmExecuteCommonPipelineEnvironment{}
   157  	testTable := []struct {
   158  		config         helmExecuteOptions
   159  		methodError    error
   160  		expectedErrStr string
   161  	}{
   162  		{
   163  			config: helmExecuteOptions{
   164  				HelmCommand: "test",
   165  			},
   166  			methodError: nil,
   167  		},
   168  		{
   169  			config: helmExecuteOptions{
   170  				HelmCommand: "test",
   171  			},
   172  			methodError:    errors.New("some error"),
   173  			expectedErrStr: "failed to execute helm test: some error",
   174  		},
   175  	}
   176  
   177  	for i, testCase := range testTable {
   178  		t.Run(fmt.Sprint("case ", i), func(t *testing.T) {
   179  			helmExecute := &mocks.HelmExecutor{}
   180  			helmExecute.On("RunHelmTest").Return(testCase.methodError)
   181  
   182  			err := runHelmExecute(testCase.config, helmExecute, &fileHandlerMock{}, &cpe)
   183  			if err != nil {
   184  				assert.Equal(t, testCase.expectedErrStr, err.Error())
   185  			}
   186  		})
   187  
   188  	}
   189  }
   190  
   191  func TestRunHelmUninstall(t *testing.T) {
   192  	t.Parallel()
   193  
   194  	cpe := helmExecuteCommonPipelineEnvironment{}
   195  	testTable := []struct {
   196  		config         helmExecuteOptions
   197  		methodError    error
   198  		expectedErrStr string
   199  	}{
   200  		{
   201  			config: helmExecuteOptions{
   202  				HelmCommand: "uninstall",
   203  			},
   204  			methodError: nil,
   205  		},
   206  		{
   207  			config: helmExecuteOptions{
   208  				HelmCommand: "uninstall",
   209  			},
   210  			methodError:    errors.New("some error"),
   211  			expectedErrStr: "failed to execute helm uninstall: some error",
   212  		},
   213  	}
   214  
   215  	for i, testCase := range testTable {
   216  		t.Run(fmt.Sprint("case ", i), func(t *testing.T) {
   217  			helmExecute := &mocks.HelmExecutor{}
   218  			helmExecute.On("RunHelmUninstall").Return(testCase.methodError)
   219  
   220  			err := runHelmExecute(testCase.config, helmExecute, &fileHandlerMock{}, &cpe)
   221  			if err != nil {
   222  				assert.Equal(t, testCase.expectedErrStr, err.Error())
   223  			}
   224  		})
   225  
   226  	}
   227  }
   228  
   229  func TestRunHelmDependency(t *testing.T) {
   230  	t.Parallel()
   231  
   232  	cpe := helmExecuteCommonPipelineEnvironment{}
   233  	testTable := []struct {
   234  		config         helmExecuteOptions
   235  		methodError    error
   236  		expectedErrStr string
   237  	}{
   238  		{
   239  			config: helmExecuteOptions{
   240  				HelmCommand: "dependency",
   241  			},
   242  			methodError: nil,
   243  		},
   244  		{
   245  			config: helmExecuteOptions{
   246  				HelmCommand: "dependency",
   247  			},
   248  			methodError:    errors.New("some error"),
   249  			expectedErrStr: "failed to execute helm dependency: some error",
   250  		},
   251  	}
   252  
   253  	for i, testCase := range testTable {
   254  		t.Run(fmt.Sprint("case ", i), func(t *testing.T) {
   255  			helmExecute := &mocks.HelmExecutor{}
   256  			helmExecute.On("RunHelmDependency").Return(testCase.methodError)
   257  
   258  			err := runHelmExecute(testCase.config, helmExecute, &fileHandlerMock{}, &cpe)
   259  			if err != nil {
   260  				assert.Equal(t, testCase.expectedErrStr, err.Error())
   261  			}
   262  		})
   263  
   264  	}
   265  }
   266  
   267  func TestRunHelmPush(t *testing.T) {
   268  	t.Parallel()
   269  
   270  	cpe := helmExecuteCommonPipelineEnvironment{}
   271  	testTable := []struct {
   272  		config         helmExecuteOptions
   273  		methodString   string
   274  		methodError    error
   275  		expectedErrStr string
   276  	}{
   277  		{
   278  			config: helmExecuteOptions{
   279  				HelmCommand: "publish",
   280  			},
   281  			methodString: "https://my.target.repository",
   282  			methodError:  nil,
   283  		},
   284  		{
   285  			config: helmExecuteOptions{
   286  				HelmCommand: "publish",
   287  			},
   288  			methodError:    errors.New("some error"),
   289  			expectedErrStr: "failed to execute helm publish: some error",
   290  		},
   291  	}
   292  
   293  	for i, testCase := range testTable {
   294  		t.Run(fmt.Sprint("case ", i), func(t *testing.T) {
   295  			helmExecute := &mocks.HelmExecutor{}
   296  			helmExecute.On("RunHelmPublish").Return(testCase.methodString, testCase.methodError)
   297  
   298  			err := runHelmExecute(testCase.config, helmExecute, &fileHandlerMock{}, &cpe)
   299  			if err != nil {
   300  				assert.Equal(t, testCase.expectedErrStr, err.Error())
   301  			}
   302  		})
   303  
   304  	}
   305  }
   306  
   307  func TestRunHelmDefaultCommand(t *testing.T) {
   308  	t.Parallel()
   309  
   310  	cpe := helmExecuteCommonPipelineEnvironment{}
   311  	testTable := []struct {
   312  		config             helmExecuteOptions
   313  		methodLintError    error
   314  		methodPackageError error
   315  		methodPublishError error
   316  		expectedErrStr     string
   317  		fileUtils          fileHandlerMock
   318  		assertFunc         func(fileHandlerMock) error
   319  	}{
   320  		{
   321  			config: helmExecuteOptions{
   322  				HelmCommand: "",
   323  			},
   324  			methodLintError:    nil,
   325  			methodPackageError: nil,
   326  			methodPublishError: nil,
   327  			fileUtils:          fileHandlerMock{},
   328  		},
   329  		{
   330  			// this test checks if parseAndRenderCPETemplate is called properly
   331  			// when config.RenderValuesTemplate is true
   332  			config: helmExecuteOptions{
   333  				HelmCommand:          "",
   334  				RenderValuesTemplate: true,
   335  			},
   336  			methodLintError:    nil,
   337  			methodPackageError: nil,
   338  			methodPublishError: nil,
   339  			fileUtils:          fileHandlerMock{},
   340  			// we expect the values file is traversed since parsing and rendering according to cpe template is active
   341  			assertFunc: func(f fileHandlerMock) error {
   342  				if len(f.fileExistsCalled) == 1 && f.fileExistsCalled[0] == "/values.yaml" {
   343  					return nil
   344  				}
   345  				return fmt.Errorf("expected FileExists called for ['/values.yaml'] but was: %+v", f.fileExistsCalled)
   346  			},
   347  		},
   348  		{
   349  			// this test checks if parseAndRenderCPETemplate is NOT called
   350  			// when config.RenderValuesTemplate is false
   351  			config: helmExecuteOptions{
   352  				HelmCommand:          "",
   353  				RenderValuesTemplate: false,
   354  			},
   355  			methodLintError:    nil,
   356  			methodPackageError: nil,
   357  			methodPublishError: nil,
   358  			fileUtils:          fileHandlerMock{},
   359  			// we expect the values file is not traversed since parsing and rendering according to cpe template is not active
   360  			assertFunc: func(f fileHandlerMock) error {
   361  				if len(f.fileExistsCalled) > 0 {
   362  					return fmt.Errorf("expected FileExists not called, but was for: %+v", f.fileExistsCalled)
   363  				}
   364  				return nil
   365  			},
   366  		},
   367  		{
   368  			config: helmExecuteOptions{
   369  				HelmCommand: "",
   370  			},
   371  			methodLintError: errors.New("some error"),
   372  			expectedErrStr:  "failed to execute helm lint: some error",
   373  			fileUtils:       fileHandlerMock{},
   374  		},
   375  		{
   376  			config: helmExecuteOptions{
   377  				HelmCommand: "",
   378  			},
   379  			methodPackageError: errors.New("some error"),
   380  			expectedErrStr:     "failed to execute helm dependency: some error",
   381  			fileUtils:          fileHandlerMock{},
   382  		},
   383  		{
   384  			config: helmExecuteOptions{
   385  				HelmCommand: "",
   386  			},
   387  			methodPublishError: errors.New("some error"),
   388  			expectedErrStr:     "failed to execute helm publish: some error",
   389  			fileUtils:          fileHandlerMock{},
   390  		},
   391  	}
   392  
   393  	for i, testCase := range testTable {
   394  		t.Run(fmt.Sprint("case ", i), func(t *testing.T) {
   395  			helmExecute := &mocks.HelmExecutor{}
   396  			helmExecute.On("RunHelmLint").Return(testCase.methodLintError)
   397  			helmExecute.On("RunHelmDependency").Return(testCase.methodPackageError)
   398  			helmExecute.On("RunHelmPublish").Return(testCase.methodPublishError)
   399  
   400  			err := runHelmExecute(testCase.config, helmExecute, &testCase.fileUtils, &cpe)
   401  			if err != nil {
   402  				assert.Equal(t, testCase.expectedErrStr, err.Error())
   403  			}
   404  			if testCase.assertFunc != nil {
   405  				assert.NoError(t, testCase.assertFunc(testCase.fileUtils))
   406  			}
   407  
   408  		})
   409  	}
   410  
   411  }
   412  
   413  func TestParseAndRenderCPETemplate(t *testing.T) {
   414  	commonPipelineEnvironment := "commonPipelineEnvironment"
   415  	valuesYaml := []byte(`
   416  image: "image_1"
   417  tag: {{ cpe "artifactVersion" }}
   418  `)
   419  	values1Yaml := []byte(`
   420  image: "image_2"
   421  tag: {{ cpe "artVersion" }}
   422  `)
   423  	values3Yaml := []byte(`
   424  image: "image_3"
   425  tag: {{ .CPE.artVersion
   426  `)
   427  	values4Yaml := []byte(`
   428  image: "test-image"
   429  tag: {{ imageTag "test-image" }}
   430  `)
   431  
   432  	tmpDir := t.TempDir()
   433  	require.DirExists(t, tmpDir)
   434  	err := os.Mkdir(path.Join(tmpDir, commonPipelineEnvironment), 0700)
   435  	require.NoError(t, err)
   436  	cpe := piperenv.CPEMap{
   437  		"artifactVersion":         "1.0.0-123456789",
   438  		"container/imageNameTags": []string{"test-image:1.0.0-123456789"},
   439  	}
   440  	err = cpe.WriteToDisk(tmpDir)
   441  	require.NoError(t, err)
   442  
   443  	defaultValueFile := "values.yaml"
   444  	config := helmExecuteOptions{
   445  		ChartPath: ".",
   446  	}
   447  
   448  	tt := []struct {
   449  		name             string
   450  		defaultValueFile string
   451  		config           helmExecuteOptions
   452  		expectedErr      error
   453  		valueFile        []byte
   454  	}{
   455  		{
   456  			name:             "'artifactVersion' file exists in CPE",
   457  			defaultValueFile: defaultValueFile,
   458  			config:           config,
   459  			expectedErr:      nil,
   460  			valueFile:        valuesYaml,
   461  		},
   462  		{
   463  			name:             "'artVersion' file does not exist in CPE",
   464  			defaultValueFile: defaultValueFile,
   465  			config:           config,
   466  			expectedErr:      nil,
   467  			valueFile:        values1Yaml,
   468  		},
   469  		{
   470  			name:             "Good template ({{ imageTag 'test-image' }})",
   471  			defaultValueFile: defaultValueFile,
   472  			config:           config,
   473  			expectedErr:      nil,
   474  			valueFile:        values4Yaml,
   475  		},
   476  		{
   477  			name:             "Wrong template ({{ .CPE.artVersion)",
   478  			defaultValueFile: defaultValueFile,
   479  			config:           config,
   480  			expectedErr:      fmt.Errorf("failed to parse template: failed to parse cpe template '\nimage: \"image_3\"\ntag: {{ .CPE.artVersion\n': template: cpetemplate:4: unclosed action started at cpetemplate:3"),
   481  			valueFile:        values3Yaml,
   482  		},
   483  		{
   484  			name:             "Multiple value files",
   485  			defaultValueFile: defaultValueFile,
   486  			config: helmExecuteOptions{
   487  				ChartPath:  ".",
   488  				HelmValues: []string{"./values_1.yaml", "./values_2.yaml"},
   489  			},
   490  			expectedErr: nil,
   491  			valueFile:   valuesYaml,
   492  		},
   493  		{
   494  			name:             "No value file is provided",
   495  			defaultValueFile: "",
   496  			config: helmExecuteOptions{
   497  				ChartPath:  ".",
   498  				HelmValues: []string{},
   499  			},
   500  			expectedErr: fmt.Errorf("no value file to proccess, please provide value file(s)"),
   501  			valueFile:   valuesYaml,
   502  		},
   503  		{
   504  			name:             "Wrong path to value file",
   505  			defaultValueFile: defaultValueFile,
   506  			config: helmExecuteOptions{
   507  				ChartPath:  ".",
   508  				HelmValues: []string{"wrong/path/to/values_1.yaml"},
   509  			},
   510  			expectedErr: fmt.Errorf("failed to read file: could not read 'wrong/path/to/values_1.yaml'"),
   511  			valueFile:   valuesYaml,
   512  		},
   513  	}
   514  
   515  	for _, test := range tt {
   516  		t.Run(test.name, func(t *testing.T) {
   517  			utils := newHelmMockUtilsBundle()
   518  			utils.AddFile(fmt.Sprintf("%s/%s", config.ChartPath, test.defaultValueFile), test.valueFile)
   519  
   520  			if len(test.config.HelmValues) == 2 {
   521  				for _, value := range test.config.HelmValues {
   522  					utils.AddFile(value, test.valueFile)
   523  				}
   524  			}
   525  
   526  			err := parseAndRenderCPETemplate(test.config, tmpDir, utils)
   527  			if test.expectedErr != nil {
   528  				assert.EqualError(t, err, test.expectedErr.Error())
   529  			} else {
   530  				assert.NoError(t, err)
   531  			}
   532  		})
   533  	}
   534  }
   535  
   536  type fileHandlerMock struct {
   537  	fileExistsCalled []string
   538  	fileReadCalled   []string
   539  	fileWriteCalled  []string
   540  }
   541  
   542  func (f *fileHandlerMock) FileWrite(name string, content []byte, mode os.FileMode) error {
   543  	f.fileWriteCalled = append(f.fileWriteCalled, name)
   544  	return nil
   545  }
   546  
   547  func (f *fileHandlerMock) FileRead(name string) ([]byte, error) {
   548  	f.fileReadCalled = append(f.fileReadCalled, name)
   549  	return []byte{}, nil
   550  }
   551  
   552  func (f *fileHandlerMock) FileExists(name string) (bool, error) {
   553  	f.fileExistsCalled = append(f.fileExistsCalled, name)
   554  	return true, nil
   555  }