github.com/jenkins-x/jx-api@v0.0.24/pkg/config/install_requirements_test.go (about)

     1  package config_test
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"os"
     7  	"path"
     8  	"path/filepath"
     9  	"testing"
    10  
    11  	"github.com/stretchr/testify/require"
    12  
    13  	"github.com/ghodss/yaml"
    14  	"github.com/jenkins-x/jx-api/pkg/cloud"
    15  	"github.com/jenkins-x/jx-api/pkg/config"
    16  	"github.com/jenkins-x/jx-api/pkg/util"
    17  	"github.com/jenkins-x/jx-logging/pkg/log"
    18  	"github.com/stretchr/testify/assert"
    19  )
    20  
    21  const (
    22  	KindGitHub = "github"
    23  )
    24  
    25  var (
    26  	testDataDir = path.Join("test_data")
    27  )
    28  
    29  func TestRequirementsConfigMarshalExistingFile(t *testing.T) {
    30  	t.Parallel()
    31  
    32  	dir, err := ioutil.TempDir("", "test-requirements-config-")
    33  	assert.NoError(t, err, "should create a temporary config dir")
    34  
    35  	expectedClusterName := "my-cluster"
    36  	expectedSecretStorage := config.SecretStorageTypeVault
    37  	expectedDomain := "cheese.co.uk"
    38  	expectedPipelineUserName := "someone"
    39  	expectedPipelineUserEmail := "someone@acme.com"
    40  
    41  	file := filepath.Join(dir, config.RequirementsConfigFileName)
    42  	requirements := config.NewRequirementsConfig()
    43  	requirements.SecretStorage = expectedSecretStorage
    44  	requirements.Cluster.ClusterName = expectedClusterName
    45  	requirements.Ingress.Domain = expectedDomain
    46  	requirements.Kaniko = true
    47  	requirements.PipelineUser = &config.UserNameEmailConfig{
    48  		Username: expectedPipelineUserName,
    49  		Email:    expectedPipelineUserEmail,
    50  	}
    51  
    52  	err = requirements.SaveConfig(file)
    53  	assert.NoError(t, err, "failed to save file %s", file)
    54  
    55  	requirements, fileName, err := config.LoadRequirementsConfig(dir, config.DefaultFailOnValidationError)
    56  	assert.NoError(t, err, "failed to load requirements file in dir %s", dir)
    57  	assert.FileExists(t, fileName)
    58  
    59  	assert.Equal(t, true, requirements.Kaniko, "requirements.Kaniko")
    60  	assert.Equal(t, expectedClusterName, requirements.Cluster.ClusterName, "requirements.ClusterName")
    61  	assert.Equal(t, expectedSecretStorage, requirements.SecretStorage, "requirements.SecretStorage")
    62  	assert.Equal(t, expectedDomain, requirements.Ingress.Domain, "requirements.Domain")
    63  
    64  	// lets check we can load the file from a sub dir
    65  	subDir := filepath.Join(dir, "subdir")
    66  	requirements, fileName, err = config.LoadRequirementsConfig(subDir, config.DefaultFailOnValidationError)
    67  	assert.NoError(t, err, "failed to load requirements file in subDir: %s", subDir)
    68  	assert.FileExists(t, fileName)
    69  
    70  	t.Logf("generated requirements file %s\n", fileName)
    71  
    72  	assert.Equal(t, true, requirements.Kaniko, "requirements.Kaniko")
    73  	assert.Equal(t, expectedClusterName, requirements.Cluster.ClusterName, "requirements.ClusterName")
    74  	assert.Equal(t, expectedSecretStorage, requirements.SecretStorage, "requirements.SecretStorage")
    75  	assert.Equal(t, expectedDomain, requirements.Ingress.Domain, "requirements.Domain")
    76  
    77  	require.NotNil(t, requirements.PipelineUser, "requirements.PipelineUser")
    78  	assert.Equal(t, expectedPipelineUserName, requirements.PipelineUser.Username, "requirements.PipelineUser.Username")
    79  	assert.Equal(t, expectedPipelineUserEmail, requirements.PipelineUser.Email, "requirements.PipelineUser.Email")
    80  
    81  }
    82  
    83  func Test_OverrideRequirementsFromEnvironment_does_not_initialise_nil_structs(t *testing.T) {
    84  	requirements, fileName, err := config.LoadRequirementsConfig(testDataDir, config.DefaultFailOnValidationError)
    85  	assert.NoError(t, err, "failed to load requirements file in dir %s", testDataDir)
    86  	assert.FileExists(t, fileName)
    87  
    88  	requirements.OverrideRequirementsFromEnvironment(func(in string) (string, error) {
    89  		return "", nil
    90  	})
    91  
    92  	tempDir, err := ioutil.TempDir("", "test-requirements-config")
    93  	assert.NoError(t, err, "should create a temporary config dir")
    94  	defer func() {
    95  		_ = os.RemoveAll(tempDir)
    96  	}()
    97  
    98  	err = requirements.SaveConfig(filepath.Join(tempDir, config.RequirementsConfigFileName))
    99  	assert.NoError(t, err, "failed to save requirements file in dir %s", tempDir)
   100  
   101  	overrideRequirements, fileName, err := config.LoadRequirementsConfig(tempDir, config.DefaultFailOnValidationError)
   102  	assert.NoError(t, err, "failed to load requirements file in dir %s", testDataDir)
   103  	assert.FileExists(t, fileName)
   104  
   105  	assert.Nil(t, overrideRequirements.BuildPacks, "nil values should not be populated")
   106  }
   107  
   108  func Test_OverrideRequirementsFromEnvironment_populate_requirements_from_environment_variables(t *testing.T) {
   109  	var overrideTests = []struct {
   110  		envKey               string
   111  		envValue             string
   112  		expectedRequirements config.RequirementsConfig
   113  	}{
   114  		// RequirementsConfig
   115  		{config.RequirementSecretStorageType, "vault", config.RequirementsConfig{SecretStorage: "vault"}},
   116  		{config.RequirementKaniko, "true", config.RequirementsConfig{Kaniko: true}},
   117  		{config.RequirementKaniko, "false", config.RequirementsConfig{Kaniko: false}},
   118  		{config.RequirementKaniko, "", config.RequirementsConfig{Kaniko: false}},
   119  		{config.RequirementRepository, "bucketrepo", config.RequirementsConfig{Repository: "bucketrepo"}},
   120  		{config.RequirementWebhook, "prow", config.RequirementsConfig{Webhook: "prow"}},
   121  		{config.RequirementGitAppEnabled, "true", config.RequirementsConfig{GithubApp: &config.GithubAppConfig{Enabled: true}}},
   122  		{config.RequirementGitAppEnabled, "false", config.RequirementsConfig{GithubApp: &config.GithubAppConfig{Enabled: false}}},
   123  		{config.RequirementGitAppURL, "https://my-github-app", config.RequirementsConfig{GithubApp: &config.GithubAppConfig{URL: "https://my-github-app"}}},
   124  
   125  		// ClusterConfig
   126  		{config.RequirementClusterName, "my-cluster", config.RequirementsConfig{Cluster: config.ClusterConfig{ClusterName: "my-cluster"}}},
   127  		{config.RequirementProject, "my-project", config.RequirementsConfig{Cluster: config.ClusterConfig{ProjectID: "my-project"}}},
   128  		{config.RequirementZone, "my-zone", config.RequirementsConfig{Cluster: config.ClusterConfig{Zone: "my-zone"}}},
   129  		{config.RequirementChartRepository, "my-chart-museum", config.RequirementsConfig{Cluster: config.ClusterConfig{ChartRepository: "my-chart-museum"}}},
   130  		{config.RequirementRegistry, "my-registry", config.RequirementsConfig{Cluster: config.ClusterConfig{Registry: "my-registry"}}},
   131  		{config.RequirementEnvGitOwner, "john-doe", config.RequirementsConfig{Cluster: config.ClusterConfig{EnvironmentGitOwner: "john-doe"}}},
   132  		{config.RequirementKanikoServiceAccountName, "kaniko-sa", config.RequirementsConfig{Cluster: config.ClusterConfig{KanikoSAName: "kaniko-sa"}}},
   133  		{config.RequirementEnvGitPublic, "true", config.RequirementsConfig{Cluster: config.ClusterConfig{EnvironmentGitPublic: true}}},
   134  		{config.RequirementEnvGitPublic, "false", config.RequirementsConfig{Cluster: config.ClusterConfig{EnvironmentGitPublic: false}}},
   135  		{config.RequirementEnvGitPublic, "", config.RequirementsConfig{Cluster: config.ClusterConfig{EnvironmentGitPublic: false}}},
   136  		{config.RequirementGitPublic, "true", config.RequirementsConfig{Cluster: config.ClusterConfig{GitPublic: true}}},
   137  		{config.RequirementGitPublic, "false", config.RequirementsConfig{Cluster: config.ClusterConfig{GitPublic: false}}},
   138  		{config.RequirementGitPublic, "", config.RequirementsConfig{Cluster: config.ClusterConfig{GitPublic: false}}},
   139  		{config.RequirementExternalDNSServiceAccountName, "externaldns-sa", config.RequirementsConfig{Cluster: config.ClusterConfig{ExternalDNSSAName: "externaldns-sa"}}},
   140  
   141  		// VaultConfig
   142  		{config.RequirementVaultName, "my-vault", config.RequirementsConfig{Vault: config.VaultConfig{Name: "my-vault"}}},
   143  		{config.RequirementVaultServiceAccountName, "my-vault-sa", config.RequirementsConfig{Vault: config.VaultConfig{ServiceAccount: "my-vault-sa"}}},
   144  		{config.RequirementVaultKeyringName, "my-keyring", config.RequirementsConfig{Vault: config.VaultConfig{Keyring: "my-keyring"}}},
   145  		{config.RequirementVaultKeyName, "my-key", config.RequirementsConfig{Vault: config.VaultConfig{Key: "my-key"}}},
   146  		{config.RequirementVaultBucketName, "my-bucket", config.RequirementsConfig{Vault: config.VaultConfig{Bucket: "my-bucket"}}},
   147  		{config.RequirementVaultRecreateBucket, "true", config.RequirementsConfig{Vault: config.VaultConfig{RecreateBucket: true}}},
   148  		{config.RequirementVaultRecreateBucket, "false", config.RequirementsConfig{Vault: config.VaultConfig{RecreateBucket: false}}},
   149  		{config.RequirementVaultRecreateBucket, "", config.RequirementsConfig{Vault: config.VaultConfig{RecreateBucket: false}}},
   150  		{config.RequirementVaultDisableURLDiscovery, "true", config.RequirementsConfig{Vault: config.VaultConfig{DisableURLDiscovery: true}}},
   151  		{config.RequirementVaultDisableURLDiscovery, "false", config.RequirementsConfig{Vault: config.VaultConfig{DisableURLDiscovery: false}}},
   152  		{config.RequirementVaultDisableURLDiscovery, "", config.RequirementsConfig{Vault: config.VaultConfig{DisableURLDiscovery: false}}},
   153  
   154  		// VeleroConfig
   155  		{config.RequirementVeleroServiceAccountName, "my-velero-sa", config.RequirementsConfig{Velero: config.VeleroConfig{ServiceAccount: "my-velero-sa"}}},
   156  		{config.RequirementVeleroTTL, "60", config.RequirementsConfig{Velero: config.VeleroConfig{TimeToLive: "60"}}},
   157  		{config.RequirementVeleroSchedule, "0 * * * *", config.RequirementsConfig{Velero: config.VeleroConfig{Schedule: "0 * * * *"}}},
   158  
   159  		// IngressConfig
   160  		{config.RequirementDomainIssuerURL, "my-issuer-url", config.RequirementsConfig{Ingress: config.IngressConfig{DomainIssuerURL: "my-issuer-url"}}},
   161  
   162  		// Storage
   163  		{config.RequirementStorageBackupEnabled, "true", config.RequirementsConfig{Storage: config.StorageConfig{Backup: config.StorageEntryConfig{Enabled: true}}}},
   164  		{config.RequirementStorageBackupEnabled, "false", config.RequirementsConfig{Storage: config.StorageConfig{Backup: config.StorageEntryConfig{Enabled: false}}}},
   165  		{config.RequirementStorageBackupEnabled, "", config.RequirementsConfig{Storage: config.StorageConfig{Backup: config.StorageEntryConfig{Enabled: false}}}},
   166  		{config.RequirementStorageBackupURL, "gs://my-backup", config.RequirementsConfig{Storage: config.StorageConfig{Backup: config.StorageEntryConfig{URL: "gs://my-backup"}}}},
   167  
   168  		{config.RequirementStorageLogsEnabled, "true", config.RequirementsConfig{Storage: config.StorageConfig{Logs: config.StorageEntryConfig{Enabled: true}}}},
   169  		{config.RequirementStorageLogsEnabled, "false", config.RequirementsConfig{Storage: config.StorageConfig{Logs: config.StorageEntryConfig{Enabled: false}}}},
   170  		{config.RequirementStorageLogsEnabled, "", config.RequirementsConfig{Storage: config.StorageConfig{Logs: config.StorageEntryConfig{Enabled: false}}}},
   171  		{config.RequirementStorageLogsURL, "gs://my-logs", config.RequirementsConfig{Storage: config.StorageConfig{Logs: config.StorageEntryConfig{URL: "gs://my-logs"}}}},
   172  
   173  		{config.RequirementStorageReportsEnabled, "true", config.RequirementsConfig{Storage: config.StorageConfig{Reports: config.StorageEntryConfig{Enabled: true}}}},
   174  		{config.RequirementStorageReportsEnabled, "false", config.RequirementsConfig{Storage: config.StorageConfig{Reports: config.StorageEntryConfig{Enabled: false}}}},
   175  		{config.RequirementStorageReportsEnabled, "", config.RequirementsConfig{Storage: config.StorageConfig{Reports: config.StorageEntryConfig{Enabled: false}}}},
   176  		{config.RequirementStorageReportsURL, "gs://my-reports", config.RequirementsConfig{Storage: config.StorageConfig{Reports: config.StorageEntryConfig{URL: "gs://my-reports"}}}},
   177  
   178  		{config.RequirementStorageRepositoryEnabled, "true", config.RequirementsConfig{Storage: config.StorageConfig{Repository: config.StorageEntryConfig{Enabled: true}}}},
   179  		{config.RequirementStorageRepositoryEnabled, "false", config.RequirementsConfig{Storage: config.StorageConfig{Repository: config.StorageEntryConfig{Enabled: false}}}},
   180  		{config.RequirementStorageRepositoryEnabled, "", config.RequirementsConfig{Storage: config.StorageConfig{Repository: config.StorageEntryConfig{Enabled: false}}}},
   181  		{config.RequirementStorageRepositoryURL, "gs://my-repo", config.RequirementsConfig{Storage: config.StorageConfig{Repository: config.StorageEntryConfig{URL: "gs://my-repo"}}}},
   182  
   183  		// GKEConfig
   184  		{config.RequirementGkeProjectNumber, "my-gke-project", config.RequirementsConfig{Cluster: config.ClusterConfig{GKEConfig: &config.GKEConfig{ProjectNumber: "my-gke-project"}}}},
   185  
   186  		// VersionStreamConfig
   187  		{config.RequirementVersionsGitRef, "v1.0.0", config.RequirementsConfig{VersionStream: config.VersionStreamConfig{Ref: "v1.0.0"}}},
   188  	}
   189  
   190  	for _, overrideTest := range overrideTests {
   191  		origEnvValue, origValueSet := os.LookupEnv(overrideTest.envKey)
   192  		err := os.Setenv(overrideTest.envKey, overrideTest.envValue)
   193  		assert.NoError(t, err)
   194  		resetEnvVariable := func() {
   195  			var err error
   196  			if origValueSet {
   197  				err = os.Setenv(overrideTest.envKey, origEnvValue)
   198  			} else {
   199  				err = os.Unsetenv(overrideTest.envKey)
   200  			}
   201  			if err != nil {
   202  				log.Logger().Warnf("error resetting environment after test: %v", err)
   203  			}
   204  		}
   205  
   206  		t.Run(overrideTest.envKey, func(t *testing.T) {
   207  			actualRequirements := config.RequirementsConfig{}
   208  			actualRequirements.OverrideRequirementsFromEnvironment(func(projectId string) (string, error) {
   209  				return "", nil
   210  			})
   211  
   212  			assert.Equal(t, overrideTest.expectedRequirements, actualRequirements)
   213  		})
   214  
   215  		resetEnvVariable()
   216  	}
   217  }
   218  
   219  func TestRequirementsConfigMarshalExistingFileKanikoFalse(t *testing.T) {
   220  	t.Parallel()
   221  
   222  	dir, err := ioutil.TempDir("", "test-requirements-config-")
   223  	assert.NoError(t, err, "should create a temporary config dir")
   224  
   225  	file := filepath.Join(dir, config.RequirementsConfigFileName)
   226  	requirements := config.NewRequirementsConfig()
   227  	requirements.Kaniko = false
   228  
   229  	err = requirements.SaveConfig(file)
   230  	assert.NoError(t, err, "failed to save file %s", file)
   231  
   232  	requirements, fileName, err := config.LoadRequirementsConfig(dir, config.DefaultFailOnValidationError)
   233  	assert.NoError(t, err, "failed to load requirements file in dir %s", dir)
   234  	assert.FileExists(t, fileName)
   235  
   236  	assert.Equal(t, false, requirements.Kaniko, "requirements.Kaniko")
   237  
   238  }
   239  
   240  func TestRequirementsConfigMarshalInEmptyDir(t *testing.T) {
   241  	t.Parallel()
   242  
   243  	dir, err := ioutil.TempDir("", "test-requirements-config-empty-")
   244  	assert.NoError(t, err)
   245  
   246  	requirements, fileName, err := config.LoadRequirementsConfig(dir, config.DefaultFailOnValidationError)
   247  	assert.Error(t, err)
   248  	assert.Empty(t, fileName)
   249  	assert.Nil(t, requirements)
   250  }
   251  
   252  func TestRequirementsConfigIngressAutoDNS(t *testing.T) {
   253  	t.Parallel()
   254  
   255  	requirements := config.NewRequirementsConfig()
   256  
   257  	requirements.Ingress.Domain = "1.2.3.4.nip.io"
   258  	assert.Equal(t, true, requirements.Ingress.IsAutoDNSDomain(), "requirements.Ingress.IsAutoDNSDomain() for domain %s", requirements.Ingress.Domain)
   259  
   260  	requirements.Ingress.Domain = "foo.bar"
   261  	assert.Equal(t, false, requirements.Ingress.IsAutoDNSDomain(), "requirements.Ingress.IsAutoDNSDomain() for domain %s", requirements.Ingress.Domain)
   262  
   263  	requirements.Ingress.Domain = ""
   264  	assert.Equal(t, false, requirements.Ingress.IsAutoDNSDomain(), "requirements.Ingress.IsAutoDNSDomain() for domain %s", requirements.Ingress.Domain)
   265  }
   266  
   267  func Test_unmarshalling_requirements_config_with_build_pack_configuration_succeeds(t *testing.T) {
   268  	t.Parallel()
   269  
   270  	requirements := config.NewRequirementsConfig()
   271  
   272  	content, err := ioutil.ReadFile(path.Join(testDataDir, "build_pack_library.yaml"))
   273  	assert.NoError(t, err)
   274  
   275  	err = yaml.Unmarshal(content, requirements)
   276  	assert.NoError(t, err)
   277  	assert.Equal(t, "Test name", requirements.BuildPacks.BuildPackLibrary.Name, "requirements.buildPacks.BuildPackLibrary.name is not equivalent to test name")
   278  	assert.Equal(t, "github.com", requirements.BuildPacks.BuildPackLibrary.GitURL, "requirements.buildPacks.BuildPackLibrary.gitURL is not equivalent to git url ")
   279  	assert.Equal(t, "master", requirements.BuildPacks.BuildPackLibrary.GitRef, "requirements.buildPacks.BuildPackLibrary.gitRef is not equivalent git Ref")
   280  }
   281  
   282  func Test_marshalling_empty_requirements_config_creates_no_build_pack_configuration(t *testing.T) {
   283  	t.Parallel()
   284  
   285  	requirements := config.NewRequirementsConfig()
   286  	data, err := yaml.Marshal(requirements)
   287  	assert.NoError(t, err)
   288  	assert.NotContains(t, string(data), "buildPacks")
   289  
   290  	err = yaml.Unmarshal(data, requirements)
   291  	assert.NoError(t, err)
   292  	assert.Nil(t, requirements.BuildPacks)
   293  }
   294  
   295  func Test_marshalling_vault_config(t *testing.T) {
   296  	t.Parallel()
   297  
   298  	requirements := config.NewRequirementsConfig()
   299  	requirements.Vault = config.VaultConfig{
   300  		Name:                   "myVault",
   301  		URL:                    "http://myvault",
   302  		ServiceAccount:         "vault-sa",
   303  		Namespace:              "jx",
   304  		KubernetesAuthPath:     "kubernetes",
   305  		SecretEngineMountPoint: "secret",
   306  	}
   307  	data, err := yaml.Marshal(requirements)
   308  	assert.NoError(t, err)
   309  
   310  	assert.Contains(t, string(data), "name: myVault")
   311  	assert.Contains(t, string(data), "url: http://myvault")
   312  	assert.Contains(t, string(data), "serviceAccount: vault-sa")
   313  	assert.Contains(t, string(data), "namespace: jx")
   314  	assert.Contains(t, string(data), "kubernetesAuthPath: kubernetes")
   315  	assert.Contains(t, string(data), "secretEngineMountPoint: secret")
   316  }
   317  
   318  func Test_env_repository_visibility(t *testing.T) {
   319  	t.Parallel()
   320  
   321  	var gitPublicTests = []struct {
   322  		yamlFile          string
   323  		expectedGitPublic bool
   324  	}{
   325  		{"git_public_nil_git_private_true.yaml", false},
   326  		{"git_public_nil_git_private_false.yaml", true},
   327  		{"git_public_false_git_private_nil.yaml", false},
   328  		{"git_public_true_git_private_nil.yaml", true},
   329  	}
   330  
   331  	for _, testCase := range gitPublicTests {
   332  		t.Run(testCase.yamlFile, func(t *testing.T) {
   333  			content, err := ioutil.ReadFile(path.Join(testDataDir, testCase.yamlFile))
   334  			assert.NoError(t, err)
   335  
   336  			config := config.NewRequirementsConfig()
   337  
   338  			_ = log.CaptureOutput(func() {
   339  				err = yaml.Unmarshal(content, config)
   340  				assert.NoError(t, err)
   341  				assert.Equal(t, testCase.expectedGitPublic, config.Cluster.EnvironmentGitPublic, "unexpected value for repository visibility")
   342  			})
   343  		})
   344  	}
   345  }
   346  
   347  func TestMergeSave(t *testing.T) {
   348  	t.Parallel()
   349  	type TestSpec struct {
   350  		Name           string
   351  		Original       *config.RequirementsConfig
   352  		Changed        *config.RequirementsConfig
   353  		ValidationFunc func(orig *config.RequirementsConfig, ch *config.RequirementsConfig)
   354  	}
   355  
   356  	testCases := []TestSpec{
   357  		{
   358  			Name: "Merge Cluster Config Test",
   359  			Original: &config.RequirementsConfig{
   360  				Cluster: config.ClusterConfig{
   361  					EnvironmentGitOwner:  "owner",
   362  					EnvironmentGitPublic: false,
   363  					GitPublic:            false,
   364  					Provider:             cloud.GKE,
   365  					Namespace:            "jx",
   366  					ProjectID:            "project-id",
   367  					ClusterName:          "cluster-name",
   368  					Region:               "region",
   369  					GitKind:              KindGitHub,
   370  					GitServer:            KindGitHub,
   371  				},
   372  			},
   373  			Changed: &config.RequirementsConfig{
   374  				Cluster: config.ClusterConfig{
   375  					EnvironmentGitPublic: true,
   376  					GitPublic:            true,
   377  					Provider:             cloud.GKE,
   378  				},
   379  			},
   380  			ValidationFunc: func(orig *config.RequirementsConfig, ch *config.RequirementsConfig) {
   381  				assert.True(t, orig.Cluster.EnvironmentGitPublic == ch.Cluster.EnvironmentGitPublic &&
   382  					orig.Cluster.GitPublic == ch.Cluster.GitPublic &&
   383  					orig.Cluster.ProjectID != ch.Cluster.ProjectID, "ClusterConfig validation failed")
   384  			},
   385  		},
   386  		{
   387  			Name: "Merge EnvironmentConfig slices Test",
   388  			Original: &config.RequirementsConfig{
   389  				Environments: []config.EnvironmentConfig{
   390  					{
   391  						Key:        "dev",
   392  						Repository: "repo",
   393  					},
   394  					{
   395  						Key: "production",
   396  						Ingress: config.IngressConfig{
   397  							Domain: "domain",
   398  						},
   399  					},
   400  					{
   401  						Key: "staging",
   402  						Ingress: config.IngressConfig{
   403  							Domain: "domainStaging",
   404  							TLS: config.TLSConfig{
   405  								Email: "email",
   406  							},
   407  						},
   408  					},
   409  				},
   410  			},
   411  			Changed: &config.RequirementsConfig{
   412  				Environments: []config.EnvironmentConfig{
   413  					{
   414  						Key:   "dev",
   415  						Owner: "owner",
   416  					},
   417  					{
   418  						Key: "production",
   419  						Ingress: config.IngressConfig{
   420  							CloudDNSSecretName: "secret",
   421  						},
   422  					},
   423  					{
   424  						Key: "staging",
   425  						Ingress: config.IngressConfig{
   426  							Domain:          "newDomain",
   427  							DomainIssuerURL: "issuer",
   428  							TLS: config.TLSConfig{
   429  								Enabled: true,
   430  							},
   431  						},
   432  					},
   433  					{
   434  						Key: "ns2",
   435  					},
   436  				},
   437  			},
   438  			ValidationFunc: func(orig *config.RequirementsConfig, ch *config.RequirementsConfig) {
   439  				assert.True(t, len(orig.Environments) == len(ch.Environments), "the environment slices should be of the same len")
   440  				// -- Dev Environment's asserts
   441  				devOrig, devCh := orig.Environments[0], ch.Environments[0]
   442  				assert.True(t, devOrig.Owner == devCh.Owner && devOrig.Repository != devCh.Repository,
   443  					"the dev environment should've been merged correctly")
   444  				// -- Production Environment's asserts
   445  				prOrig, prCh := orig.Environments[1], ch.Environments[1]
   446  				assert.True(t, prOrig.Ingress.Domain == "domain" &&
   447  					prOrig.Ingress.CloudDNSSecretName == prCh.Ingress.CloudDNSSecretName,
   448  					"the production environment should've been merged correctly")
   449  				// -- Staging Environmnet's asserts
   450  				stgOrig, stgCh := orig.Environments[2], ch.Environments[2]
   451  				assert.True(t, stgOrig.Ingress.Domain == stgCh.Ingress.Domain &&
   452  					stgOrig.Ingress.TLS.Email == "email" && stgOrig.Ingress.TLS.Enabled == stgCh.Ingress.TLS.Enabled,
   453  					"the staging environment should've been merged correctly")
   454  			},
   455  		},
   456  		{
   457  			Name: "Merge StorageConfig test",
   458  			Original: &config.RequirementsConfig{
   459  				Storage: config.StorageConfig{
   460  					Logs: config.StorageEntryConfig{
   461  						Enabled: true,
   462  						URL:     "value1",
   463  					},
   464  					Reports: config.StorageEntryConfig{},
   465  					Repository: config.StorageEntryConfig{
   466  						Enabled: true,
   467  						URL:     "value3",
   468  					},
   469  				},
   470  			},
   471  			Changed: &config.RequirementsConfig{
   472  				Storage: config.StorageConfig{
   473  					Reports: config.StorageEntryConfig{
   474  						Enabled: true,
   475  						URL:     "",
   476  					},
   477  				},
   478  			},
   479  			ValidationFunc: func(orig *config.RequirementsConfig, ch *config.RequirementsConfig) {
   480  				assert.True(t, orig.Storage.Logs.Enabled && orig.Storage.Logs.URL == "value1" &&
   481  					orig.Storage.Repository.Enabled && orig.Storage.Repository.URL == "value3" &&
   482  					orig.Storage.Reports.Enabled == ch.Storage.Reports.Enabled,
   483  					"The storage configuration should've been merged correctly")
   484  			},
   485  		},
   486  	}
   487  	f, err := ioutil.TempFile("", "")
   488  	assert.NoError(t, err)
   489  	defer func() {
   490  		err := util.DeleteFile(f.Name())
   491  		if err != nil {
   492  			t.Logf("unable to clean up, %s", err)
   493  		}
   494  	}()
   495  
   496  	for _, tc := range testCases {
   497  		t.Run(tc.Name, func(t *testing.T) {
   498  			err = tc.Original.MergeSave(tc.Changed, f.Name())
   499  			assert.NoError(t, err, "the merge shouldn't fail for case %s", tc.Name)
   500  			tc.ValidationFunc(tc.Original, tc.Changed)
   501  		})
   502  	}
   503  }
   504  
   505  func Test_EnvironmentGitPublic_and_EnvironmentGitPrivate_specified_together_return_error(t *testing.T) {
   506  	content, err := ioutil.ReadFile(path.Join(testDataDir, "git_public_true_git_private_true.yaml"))
   507  	assert.NoError(t, err)
   508  
   509  	config := config.NewRequirementsConfig()
   510  	err = yaml.Unmarshal(content, config)
   511  	assert.Error(t, err)
   512  	assert.Contains(t, err.Error(), "only EnvironmentGitPublic should be used")
   513  }
   514  
   515  func TestHelmfileRequirementValues(t *testing.T) {
   516  	t.Parallel()
   517  
   518  	dir, err := ioutil.TempDir("", "test-requirements-config-")
   519  	assert.NoError(t, err, "should create a temporary config dir")
   520  
   521  	file := filepath.Join(dir, config.RequirementsConfigFileName)
   522  	requirements := config.NewRequirementsConfig()
   523  	requirements.Cluster.ClusterName = "jx_rocks"
   524  
   525  	err = requirements.SaveConfig(file)
   526  	assert.NoError(t, err, "failed to save file %s", file)
   527  	assert.FileExists(t, file)
   528  	valuesFile := filepath.Join(dir, config.RequirementsValuesFileName)
   529  
   530  	valuesFileExists, err := util.FileExists(valuesFile)
   531  	assert.NoError(t, err)
   532  	assert.False(t, valuesFileExists, "file %s should not exist", valuesFile)
   533  
   534  	requirements.Helmfile = true
   535  	err = requirements.SaveConfig(file)
   536  	assert.NoError(t, err, "failed to save file %s", file)
   537  	assert.FileExists(t, file)
   538  	assert.FileExists(t, valuesFile, "file %s should exist", valuesFile)
   539  }
   540  
   541  func Test_LoadRequirementsConfig(t *testing.T) {
   542  	t.Parallel()
   543  
   544  	var gitPublicTests = []struct {
   545  		requirementsPath   string
   546  		createRequirements bool
   547  	}{
   548  		{"a", false},
   549  		{"a/b", false},
   550  		{"a/b/c", false},
   551  		{"e", true},
   552  		{"e/f", true},
   553  		{"e/f/g", true},
   554  	}
   555  
   556  	for _, testCase := range gitPublicTests {
   557  		t.Run(testCase.requirementsPath, func(t *testing.T) {
   558  			dir, err := ioutil.TempDir("", "jx-test-load-requirements-config")
   559  			require.NoError(t, err, "failed to create tmp directory")
   560  			defer func() {
   561  				_ = os.RemoveAll(dir)
   562  			}()
   563  
   564  			testPath := filepath.Join(dir, testCase.requirementsPath)
   565  			err = os.MkdirAll(testPath, 0700)
   566  			require.NoError(t, err, "unable to create test path %s", testPath)
   567  
   568  			var expectedRequirementsFile string
   569  			if testCase.createRequirements {
   570  				expectedRequirementsFile = filepath.Join(testPath, config.RequirementsConfigFileName)
   571  				dummyRequirementsData := []byte("webhook: prow\n")
   572  				err := ioutil.WriteFile(expectedRequirementsFile, dummyRequirementsData, 0600)
   573  				require.NoError(t, err, "unable to write requirements file %s", expectedRequirementsFile)
   574  			}
   575  
   576  			requirements, requirementsFile, err := config.LoadRequirementsConfig(testPath, config.DefaultFailOnValidationError)
   577  			if testCase.createRequirements {
   578  				require.NoError(t, err)
   579  				assert.Equal(t, expectedRequirementsFile, requirementsFile)
   580  				assert.Equal(t, string(requirements.Webhook), "prow")
   581  			} else {
   582  				require.Error(t, err)
   583  				assert.Equal(t, "", requirementsFile)
   584  				assert.Nil(t, requirements)
   585  			}
   586  		})
   587  	}
   588  }
   589  
   590  func TestLoadRequirementsConfig_load_invalid_yaml(t *testing.T) {
   591  	testDir := path.Join(testDataDir, "jx-requirements-syntax-error")
   592  
   593  	absolute, err := filepath.Abs(testDir)
   594  	assert.NoError(t, err, "could not find absolute path of dir %s", testDataDir)
   595  
   596  	_, _, err = config.LoadRequirementsConfig(testDir, config.DefaultFailOnValidationError)
   597  	requirementsConfigPath := path.Join(absolute, config.RequirementsConfigFileName)
   598  	assert.EqualError(t, err, fmt.Sprintf("validation failures in YAML file %s:\nenvironments.0: Additional property namespace is not allowed", requirementsConfigPath))
   599  }