github.com/jenkins-x/jx/v2@v2.1.155/pkg/config/install_requirements.go (about)

     1  package config
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"path"
     9  	"path/filepath"
    10  	"reflect"
    11  	"strings"
    12  
    13  	"github.com/ghodss/yaml"
    14  	"github.com/imdario/mergo"
    15  	"github.com/pkg/errors"
    16  	"github.com/vrischmann/envconfig"
    17  
    18  	v1 "github.com/jenkins-x/jx-api/pkg/apis/jenkins.io/v1"
    19  	"github.com/jenkins-x/jx-logging/pkg/log"
    20  	"github.com/jenkins-x/jx/v2/pkg/cloud"
    21  	"github.com/jenkins-x/jx/v2/pkg/cloud/gke"
    22  	"github.com/jenkins-x/jx/v2/pkg/util"
    23  )
    24  
    25  var (
    26  	// autoDNSSuffixes the DNS suffixes of any auto-DNS services
    27  	autoDNSSuffixes = []string{
    28  		".nip.io",
    29  		".xip.io",
    30  		".beesdns.com",
    31  	}
    32  )
    33  
    34  const (
    35  	// DefaultFailOnValidationError by default fail if validation fails when reading jx-requirements
    36  	DefaultFailOnValidationError = true
    37  )
    38  
    39  const (
    40  	// RequirementsConfigFileName is the name of the requirements configuration file
    41  	RequirementsConfigFileName = "jx-requirements.yml"
    42  	// RequirementsValuesFileName is the name of the requirements configuration file
    43  	RequirementsValuesFileName = "jx-requirements.values.yaml.gotmpl"
    44  	// RequirementDomainIssuerUsername contains the username used for basic auth when requesting a domain
    45  	RequirementDomainIssuerUsername = "JX_REQUIREMENT_DOMAIN_ISSUER_USERNAME"
    46  	// RequirementDomainIssuerPassword contains the password used for basic auth when requesting a domain
    47  	RequirementDomainIssuerPassword = "JX_REQUIREMENT_DOMAIN_ISSUER_PASSWORD"
    48  	// RequirementDomainIssuerURL contains the URL to the service used when requesting a domain
    49  	RequirementDomainIssuerURL = "JX_REQUIREMENT_DOMAIN_ISSUER_URL"
    50  	// RequirementClusterName is the cluster name
    51  	RequirementClusterName = "JX_REQUIREMENT_CLUSTER_NAME"
    52  	// RequirementProject is the cloudprovider project
    53  	RequirementProject = "JX_REQUIREMENT_PROJECT"
    54  	// RequirementZone zone the cluster is in
    55  	RequirementZone = "JX_REQUIREMENT_ZONE"
    56  	// RequirementEnvGitOwner the default git owner for environment repositories if none is specified explicitly
    57  	RequirementEnvGitOwner = "JX_REQUIREMENT_ENV_GIT_OWNER"
    58  	// RequirementEnvGitPublic sets the visibility of the environment repositories as private (subscription required for GitHub Organisations)
    59  	RequirementEnvGitPublic = "JX_REQUIREMENT_ENV_GIT_PUBLIC"
    60  	// RequirementGitPublic sets the visibility of the application repositories as private (subscription required for GitHub Organisations)
    61  	RequirementGitPublic = "JX_REQUIREMENT_GIT_PUBLIC"
    62  	// RequirementExternalDNSServiceAccountName the service account name for external dns
    63  	RequirementExternalDNSServiceAccountName = "JX_REQUIREMENT_EXTERNALDNS_SA_NAME"
    64  	// RequirementVaultName the name for vault
    65  	RequirementVaultName = "JX_REQUIREMENT_VAULT_NAME"
    66  	// RequirementVaultServiceAccountName the service account name for vault
    67  	RequirementVaultServiceAccountName = "JX_REQUIREMENT_VAULT_SA_NAME"
    68  	// RequirementVeleroServiceAccountName the service account name for velero
    69  	RequirementVeleroServiceAccountName = "JX_REQUIREMENT_VELERO_SA_NAME"
    70  	// RequirementVeleroTTL defines the time to live (TTL) for the Velero backups in minutes
    71  	RequirementVeleroTTL = "JX_REQUIREMENT_VELERO_TTL"
    72  	// RequirementVeleroSchedule defines the schedule of the Velero backups in cron notation
    73  	RequirementVeleroSchedule = "JX_REQUIREMENT_VELERO_SCHEDULE"
    74  	// RequirementVaultKeyringName the keyring name for vault
    75  	RequirementVaultKeyringName = "JX_REQUIREMENT_VAULT_KEYRING_NAME"
    76  	// RequirementVaultKeyName the key name for vault
    77  	RequirementVaultKeyName = "JX_REQUIREMENT_VAULT_KEY_NAME"
    78  	// RequirementVaultBucketName the vault name for vault
    79  	RequirementVaultBucketName = "JX_REQUIREMENT_VAULT_BUCKET_NAME"
    80  	// RequirementVaultRecreateBucket recreate the bucket that vault uses
    81  	RequirementVaultRecreateBucket = "JX_REQUIREMENT_VAULT_RECREATE_BUCKET"
    82  	// RequirementVaultDisableURLDiscovery override the default lookup of the Vault URL, could be incluster service or external ingress
    83  	RequirementVaultDisableURLDiscovery = "JX_REQUIREMENT_VAULT_DISABLE_URL_DISCOVERY"
    84  	// RequirementSecretStorageType the secret storage type
    85  	RequirementSecretStorageType = "JX_REQUIREMENT_SECRET_STORAGE_TYPE"
    86  	// RequirementKanikoServiceAccountName the service account name for kaniko
    87  	RequirementKanikoServiceAccountName = "JX_REQUIREMENT_KANIKO_SA_NAME"
    88  	// RequirementKaniko if kaniko is required
    89  	RequirementKaniko = "JX_REQUIREMENT_KANIKO"
    90  	// RequirementIngressTLSProduction use the lets encrypt production server
    91  	RequirementIngressTLSProduction = "JX_REQUIREMENT_INGRESS_TLS_PRODUCTION"
    92  	// RequirementChartRepository the helm chart repository for jx
    93  	RequirementChartRepository = "JX_REQUIREMENT_CHART_REPOSITORY"
    94  	// RequirementRegistry the container registry for jx
    95  	RequirementRegistry = "JX_REQUIREMENT_REGISTRY"
    96  	// RequirementRepository the artifact repository for jx
    97  	RequirementRepository = "JX_REQUIREMENT_REPOSITORY"
    98  	// RequirementWebhook the webhook handler for jx
    99  	RequirementWebhook = "JX_REQUIREMENT_WEBHOOK"
   100  	// RequirementStorageBackupEnabled if backup storage is required
   101  	RequirementStorageBackupEnabled = "JX_REQUIREMENT_STORAGE_BACKUP_ENABLED"
   102  	// RequirementStorageBackupURL backup storage url
   103  	RequirementStorageBackupURL = "JX_REQUIREMENT_STORAGE_BACKUP_URL"
   104  	// RequirementStorageLogsEnabled if log storage is required
   105  	RequirementStorageLogsEnabled = "JX_REQUIREMENT_STORAGE_LOGS_ENABLED"
   106  	// RequirementStorageLogsURL logs storage url
   107  	RequirementStorageLogsURL = "JX_REQUIREMENT_STORAGE_LOGS_URL"
   108  	// RequirementStorageReportsEnabled if report storage is required
   109  	RequirementStorageReportsEnabled = "JX_REQUIREMENT_STORAGE_REPORTS_ENABLED"
   110  	// RequirementStorageReportsURL report storage url
   111  	RequirementStorageReportsURL = "JX_REQUIREMENT_STORAGE_REPORTS_URL"
   112  	// RequirementStorageRepositoryEnabled if repository storage is required
   113  	RequirementStorageRepositoryEnabled = "JX_REQUIREMENT_STORAGE_REPOSITORY_ENABLED"
   114  	// RequirementStorageRepositoryURL repository storage url
   115  	RequirementStorageRepositoryURL = "JX_REQUIREMENT_STORAGE_REPOSITORY_URL"
   116  	// RequirementGkeProjectNumber is the gke project number
   117  	RequirementGkeProjectNumber = "JX_REQUIREMENT_GKE_PROJECT_NUMBER"
   118  	// RequirementGitAppEnabled if the github app should be used for access tokens
   119  	RequirementGitAppEnabled = "JX_REQUIREMENT_GITHUB_APP_ENABLED"
   120  	// RequirementGitAppURL contains the URL to the github app
   121  	RequirementGitAppURL = "JX_REQUIREMENT_GITHUB_APP_URL"
   122  	// RequirementDevEnvApprovers contains the optional list of users to populate the dev env's OWNERS with
   123  	RequirementDevEnvApprovers = "JX_REQUIREMENT_DEV_ENV_APPROVERS"
   124  	// RequirementVersionsGitRef contains the git ref of the version stream
   125  	RequirementVersionsGitRef = "JX_REQUIREMENT_VERSIONS_GIT_REF"
   126  )
   127  
   128  const (
   129  	// BootDeployNamespace environment variable for deployment namespace
   130  	BootDeployNamespace = "DEPLOY_NAMESPACE"
   131  )
   132  
   133  // SecretStorageType is the type of storage used for secrets
   134  type SecretStorageType string
   135  
   136  const (
   137  	// SecretStorageTypeVault specifies that we use vault to store secrets
   138  	SecretStorageTypeVault SecretStorageType = "vault"
   139  	// SecretStorageTypeLocal specifies that we use the local file system in
   140  	// `~/.jx/localSecrets` to store secrets
   141  	SecretStorageTypeLocal SecretStorageType = "local"
   142  )
   143  
   144  // SecretStorageTypeValues the string values for the secret storage
   145  var SecretStorageTypeValues = []string{"local", "vault"}
   146  
   147  // WebhookType is the type of a webhook strategy
   148  type WebhookType string
   149  
   150  const (
   151  	// WebhookTypeNone if we have yet to define a webhook
   152  	WebhookTypeNone WebhookType = ""
   153  	// WebhookTypeProw specifies that we use prow for webhooks
   154  	// see: https://github.com/kubernetes/test-infra/tree/master/prow
   155  	WebhookTypeProw WebhookType = "prow"
   156  	// WebhookTypeLighthouse specifies that we use lighthouse for webhooks
   157  	// see: https://github.com/jenkins-x/lighthouse
   158  	WebhookTypeLighthouse WebhookType = "lighthouse"
   159  	// WebhookTypeJenkins specifies that we use jenkins webhooks
   160  	WebhookTypeJenkins WebhookType = "jenkins"
   161  )
   162  
   163  // WebhookTypeValues the string values for the webhook types
   164  var WebhookTypeValues = []string{"jenkins", "lighthouse", "prow"}
   165  
   166  // RepositoryType is the type of a repository we use to store artifacts (jars, tarballs, npm packages etc)
   167  type RepositoryType string
   168  
   169  const (
   170  	// RepositoryTypeUnknown if we have yet to configure a repository
   171  	RepositoryTypeUnknown RepositoryType = ""
   172  	// RepositoryTypeArtifactory if you wish to use Artifactory as the artifact repository
   173  	RepositoryTypeArtifactory RepositoryType = "artifactory"
   174  	// RepositoryTypeBucketRepo if you wish to use bucketrepo as the artifact repository. see https://github.com/jenkins-x/bucketrepo
   175  	RepositoryTypeBucketRepo RepositoryType = "bucketrepo"
   176  	// RepositoryTypeNone if you do not wish to install an artifact repository
   177  	RepositoryTypeNone RepositoryType = "none"
   178  	// RepositoryTypeNexus if you wish to use Sonatype Nexus as the artifact repository
   179  	RepositoryTypeNexus RepositoryType = "nexus"
   180  )
   181  
   182  // RepositoryTypeValues the string values for the repository types
   183  var RepositoryTypeValues = []string{"none", "bucketrepo", "nexus", "artifactory"}
   184  
   185  const (
   186  	// DefaultProfileFile location of profle config
   187  	DefaultProfileFile = "profile.yaml"
   188  	// OpenSourceProfile constant for OSS profile
   189  	OpenSourceProfile = "oss"
   190  	// CloudBeesProfile constant for CloudBees profile
   191  	CloudBeesProfile = "cloudbees"
   192  )
   193  
   194  // Overrideable at build time - see Makefile
   195  var (
   196  	// DefaultVersionsURL default version stream url
   197  	DefaultVersionsURL = "https://github.com/jenkins-x/jenkins-x-versions.git"
   198  	// DefaultVersionsRef default version stream ref
   199  	DefaultVersionsRef = "master"
   200  	// DefaultBootRepository default git repo for boot
   201  	DefaultBootRepository = "https://github.com/jenkins-x/jenkins-x-boot-config.git"
   202  	// LatestVersionStringsBucket optional bucket name to search in for latest version strings
   203  	LatestVersionStringsBucket = ""
   204  	// BinaryDownloadBaseURL the base URL for downloading the binary from - will always have "VERSION/jx-OS-ARCH.EXTENSION" appended to it when used
   205  	BinaryDownloadBaseURL = "https://github.com/jenkins-x/jx/releases/download/v"
   206  	// TLSDocURL the URL presented by `jx step verify preinstall` for documentation on configuring TLS
   207  	TLSDocURL = "https://jenkins-x.io/docs/getting-started/setup/boot/#ingress"
   208  )
   209  
   210  // EnvironmentConfig configures the organisation and repository name of the git repositories for environments
   211  type EnvironmentConfig struct {
   212  	// Key is the key of the environment configuration
   213  	Key string `json:"key,omitempty"`
   214  	// Owner is the git user or organisation for the repository
   215  	Owner string `json:"owner,omitempty"`
   216  	// Repository is the name of the repository within the owner
   217  	Repository string `json:"repository,omitempty"`
   218  	// GitServer is the URL of the git server
   219  	GitServer string `json:"gitServer,omitempty"`
   220  	// GitKind is the kind of git server (github, bitbucketserver etc)
   221  	GitKind string `json:"gitKind,omitempty"`
   222  	// Ingress contains ingress specific requirements
   223  	Ingress IngressConfig `json:"ingress,omitempty"`
   224  	// RemoteCluster specifies this environment runs on a remote cluster to the development cluster
   225  	RemoteCluster bool `json:"remoteCluster,omitempty"`
   226  	// PromotionStrategy what kind of promotion strategy to use
   227  	PromotionStrategy v1.PromotionStrategyType `json:"promotionStrategy,omitempty"`
   228  	// URLTemplate is the template to use for your environment's exposecontroller generated URLs
   229  	URLTemplate string `json:"urlTemplate,omitempty"`
   230  }
   231  
   232  // IngressConfig contains dns specific requirements
   233  type IngressConfig struct {
   234  	// DNS is enabled
   235  	ExternalDNS bool `json:"externalDNS"`
   236  	// CloudDNSSecretName secret name which contains the service account for external-dns and cert-manager issuer to
   237  	// access the Cloud DNS service to resolve a DNS challenge
   238  	CloudDNSSecretName string `json:"cloud_dns_secret_name,omitempty"`
   239  	// Domain to expose ingress endpoints
   240  	Domain string `json:"domain"`
   241  	// IgnoreLoadBalancer if the nginx-controller LoadBalancer service should not be used to detect and update the
   242  	// domain if you are using a dynamic domain resolver like `.nip.io` rather than a real DNS configuration.
   243  	// With this flag enabled the `Domain` value will be used and never re-created based on the current LoadBalancer IP address.
   244  	IgnoreLoadBalancer bool `json:"ignoreLoadBalancer,omitempty"`
   245  	// Exposer the exposer used to expose ingress endpoints. Defaults to "Ingress"
   246  	Exposer string `json:"exposer,omitempty"`
   247  	// NamespaceSubDomain the sub domain expression to expose ingress. Defaults to ".jx."
   248  	NamespaceSubDomain string `json:"namespaceSubDomain"`
   249  	// TLS enable automated TLS using certmanager
   250  	TLS TLSConfig `json:"tls"`
   251  	// DomainIssuerURL contains a URL used to retrieve a Domain
   252  	DomainIssuerURL string `json:"domainIssuerURL,omitempty" envconfig:"JX_REQUIREMENT_DOMAIN_ISSUER_URL"`
   253  }
   254  
   255  // BuildPackConfig contains build pack info
   256  type BuildPackConfig struct {
   257  	// Location contains location config
   258  	BuildPackLibrary *BuildPackLibrary `json:"buildPackLibrary,omitempty"`
   259  }
   260  
   261  // BuildPackLibrary contains buildpack location
   262  type BuildPackLibrary struct {
   263  	// Name
   264  	Name string `json:"name,omitempty"`
   265  	// GitURL
   266  	GitURL string `json:"gitURL,omitempty"`
   267  	// GitRef
   268  	GitRef string `json:"gitRef,omitempty"`
   269  }
   270  
   271  // TLSConfig contains TLS specific requirements
   272  type TLSConfig struct {
   273  	// TLS enabled
   274  	Enabled bool `json:"enabled"`
   275  	// Email address to register with services like LetsEncrypt
   276  	Email string `json:"email"`
   277  	// Production false uses self-signed certificates from the LetsEncrypt staging server, true enables the production
   278  	// server which incurs higher rate limiting https://letsencrypt.org/docs/rate-limits/
   279  	Production bool `json:"production"`
   280  	// SecretName the name of the secret which contains the TLS certificate
   281  	SecretName string `json:"secretName,omitempty"`
   282  }
   283  
   284  // JxInstallProfile contains the jx profile info
   285  type JxInstallProfile struct {
   286  	InstallType string
   287  }
   288  
   289  // StorageEntryConfig contains dns specific requirements for a kind of storage
   290  type StorageEntryConfig struct {
   291  	// Enabled if the storage is enabled
   292  	Enabled bool `json:"enabled"`
   293  	// URL the cloud storage bucket URL such as 'gs://mybucket' or 's3://foo' or `azblob://thingy'
   294  	// see https://jenkins-x.io/architecture/storage/
   295  	URL string `json:"url"`
   296  }
   297  
   298  // StorageConfig contains dns specific requirements
   299  type StorageConfig struct {
   300  	// Logs for storing build logs
   301  	Logs StorageEntryConfig `json:"logs"`
   302  	// Tests for storing test results, coverage + code quality reports
   303  	Reports StorageEntryConfig `json:"reports"`
   304  	// Repository for storing repository artifacts
   305  	Repository StorageEntryConfig `json:"repository"`
   306  	// Backup for backing up kubernetes resource
   307  	Backup StorageEntryConfig `json:"backup"`
   308  }
   309  
   310  // AzureConfig contains Azure specific requirements
   311  type AzureConfig struct {
   312  	// RegistrySubscription the registry subscription for defaulting the container registry.
   313  	// Not used if you specify a Registry explicitly
   314  	RegistrySubscription string `json:"registrySubscription,omitempty"`
   315  }
   316  
   317  // GKEConfig contains GKE specific requirements
   318  type GKEConfig struct {
   319  	// ProjectNumber the unique project number GKE assigns to a project (required for workload identity).
   320  	ProjectNumber string `json:"projectNumber,omitempty" envconfig:"JX_REQUIREMENT_GKE_PROJECT_NUMBER"`
   321  }
   322  
   323  // ClusterConfig contains cluster specific requirements
   324  type ClusterConfig struct {
   325  	// AzureConfig the azure specific configuration
   326  	AzureConfig *AzureConfig `json:"azure,omitempty"`
   327  	// ChartRepository the repository URL to deploy charts to
   328  	ChartRepository string `json:"chartRepository,omitempty" envconfig:"JX_REQUIREMENT_CHART_REPOSITORY"`
   329  	// GKEConfig the gke specific configuration
   330  	GKEConfig *GKEConfig `json:"gke,omitempty"`
   331  	// EnvironmentGitOwner the default git owner for environment repositories if none is specified explicitly
   332  	EnvironmentGitOwner string `json:"environmentGitOwner,omitempty" envconfig:"JX_REQUIREMENT_ENV_GIT_OWNER"`
   333  	// EnvironmentGitPublic determines whether jx boot create public or private git repos for the environments
   334  	EnvironmentGitPublic bool `json:"environmentGitPublic,omitempty" envconfig:"JX_REQUIREMENT_ENV_GIT_PUBLIC"`
   335  	// GitPublic determines whether jx boot create public or private git repos for the applications
   336  	GitPublic bool `json:"gitPublic,omitempty" envconfig:"JX_REQUIREMENT_GIT_PUBLIC"`
   337  	// Provider the kubernetes provider (e.g. gke)
   338  	Provider string `json:"provider,omitempty"`
   339  	// Namespace the namespace to install the dev environment
   340  	Namespace string `json:"namespace,omitempty"`
   341  	// ProjectID the cloud project ID e.g. on GCP
   342  	ProjectID string `json:"project,omitempty" envconfig:"JX_REQUIREMENT_PROJECT"`
   343  	// ClusterName the logical name of the cluster
   344  	ClusterName string `json:"clusterName,omitempty" envconfig:"JX_REQUIREMENT_CLUSTER_NAME"`
   345  	// VaultName the name of the vault if using vault for secrets
   346  	// Deprecated
   347  	VaultName string `json:"vaultName,omitempty"`
   348  	// Region the cloud region being used
   349  	Region string `json:"region,omitempty"`
   350  	// Zone the cloud zone being used
   351  	Zone string `json:"zone,omitempty" envconfig:"JX_REQUIREMENT_ZONE"`
   352  	// GitName is the name of the default git service
   353  	GitName string `json:"gitName,omitempty"`
   354  	// GitKind is the kind of git server (github, bitbucketserver etc)
   355  	GitKind string `json:"gitKind,omitempty"`
   356  	// GitServer is the URL of the git server
   357  	GitServer string `json:"gitServer,omitempty"`
   358  	// ExternalDNSSAName the service account name for external dns
   359  	ExternalDNSSAName string `json:"externalDNSSAName,omitempty" envconfig:"JX_REQUIREMENT_EXTERNALDNS_SA_NAME"`
   360  	// Registry the host name of the container registry
   361  	Registry string `json:"registry,omitempty" envconfig:"JX_REQUIREMENT_REGISTRY"`
   362  	// VaultSAName the service account name for vault
   363  	// Deprecated
   364  	VaultSAName string `json:"vaultSAName,omitempty"`
   365  	// KanikoSAName the service account name for kaniko
   366  	KanikoSAName string `json:"kanikoSAName,omitempty" envconfig:"JX_REQUIREMENT_KANIKO_SA_NAME"`
   367  	// HelmMajorVersion contains the major helm version number. Assumes helm 2.x with no tiller if no value specified
   368  	HelmMajorVersion string `json:"helmMajorVersion,omitempty"`
   369  	// DevEnvApprovers contains an optional list of approvers to populate the initial OWNERS file in the dev env repo
   370  	DevEnvApprovers []string `json:"devEnvApprovers,omitempty"`
   371  	// DockerRegistryOrg the default organisation used for container images
   372  	DockerRegistryOrg string `json:"dockerRegistryOrg,omitempty"`
   373  	// StrictPermissions lets you decide how to boot the cluster when it comes to permissions
   374  	// If it's false, cluster wide permissions will be used, normal, namespaced permissions will be used otherwise
   375  	// and extra steps will be necessary to get the cluster working
   376  	StrictPermissions bool `json:"strictPermissions,omitempty"`
   377  }
   378  
   379  // VaultConfig contains Vault configuration for Boot
   380  type VaultConfig struct {
   381  	// Name the name of the Vault if using Jenkins X managed Vault instance.
   382  	// Cannot be used in conjunction with the URL attribute
   383  	Name string `json:"name,omitempty"`
   384  
   385  	Bucket         string `json:"bucket,omitempty" envconfig:"JX_REQUIREMENT_VAULT_BUCKET_NAME"`
   386  	RecreateBucket bool   `json:"recreateBucket,omitempty"`
   387  
   388  	Keyring string `json:"keyring,omitempty" envconfig:"JX_REQUIREMENT_VAULT_KEYRING_NAME"`
   389  	Key     string `json:"key,omitempty" envconfig:"JX_REQUIREMENT_VAULT_KEY_NAME"`
   390  
   391  	// DisableURLDiscovery allows us to optionally override the default lookup of the Vault URL, could be incluster service or external ingress
   392  	DisableURLDiscovery bool `json:"disableURLDiscovery,omitempty"`
   393  
   394  	// AWSConfig describes the AWS specific configuration needed for the Vault Operator.
   395  	AWSConfig *VaultAWSConfig `json:"aws,omitempty"`
   396  
   397  	// AzureConfig describes the Azure specific configuration needed for the Vault Operator.
   398  	AzureConfig *VaultAzureConfig `json:"azure,omitempty"`
   399  
   400  	// URL specifies the URL of an Vault instance to use for secret storage.
   401  	// Needs to be specified together with the Service Account and namespace to use for connecting to Vault.
   402  	// This cannot be used in conjunction with the Name attribute.
   403  	URL string `json:"url,omitempty"`
   404  
   405  	// ServiceAccount is the name of the Kubernetes service account allowed to authenticate against Vault.
   406  	ServiceAccount string `json:"serviceAccount,omitempty" envconfig:"JX_REQUIREMENT_VAULT_SA_NAME"`
   407  
   408  	// Namespace of the Kubernetes service account allowed to authenticate against Vault.
   409  	Namespace string `json:"namespace,omitempty"`
   410  
   411  	// SecretEngineMountPoint is the secret engine mount point to be used for writing data into the KV engine of Vault.
   412  	// If not specified the 'secret' is used.
   413  	SecretEngineMountPoint string `json:"secretEngineMountPoint,omitempty"`
   414  
   415  	// KubernetesAuthPath is the auth path of used for this cluster
   416  	// If not specified the 'kubernetes' is used.
   417  	KubernetesAuthPath string `json:"kubernetesAuthPath,omitempty"`
   418  }
   419  
   420  // VaultAWSConfig contains all the Vault configuration needed by Vault to be deployed in AWS
   421  type VaultAWSConfig struct {
   422  	VaultAWSUnsealConfig
   423  	AutoCreate          bool   `json:"autoCreate,omitempty"`
   424  	DynamoDBTable       string `json:"dynamoDBTable,omitempty"`
   425  	DynamoDBRegion      string `json:"dynamoDBRegion,omitempty"`
   426  	ProvidedIAMUsername string `json:"iamUserName,omitempty"`
   427  }
   428  
   429  // VaultAWSUnsealConfig contains references to existing AWS resources that can be used to install Vault
   430  type VaultAWSUnsealConfig struct {
   431  	KMSKeyID  string `json:"kmsKeyId,omitempty"`
   432  	KMSRegion string `json:"kmsRegion,omitempty"`
   433  	S3Bucket  string `json:"s3Bucket,omitempty"`
   434  	S3Prefix  string `json:"s3Prefix,omitempty"`
   435  	S3Region  string `json:"s3Region,omitempty"`
   436  }
   437  
   438  // VaultAzureConfig contains all the Vault configuration needed by Vault to be deployed in Azure
   439  type VaultAzureConfig struct {
   440  	TenantID           string `json:"tenantId,omitempty"`
   441  	VaultName          string `json:"vaultName,omitempty"`
   442  	KeyName            string `json:"keyName,omitempty"`
   443  	StorageAccountName string `json:"storageAccountName,omitempty"`
   444  	ContainerName      string `json:"containerName,omitempty"`
   445  }
   446  
   447  // UnmarshalJSON method handles the rename of EnvironmentGitPrivate to EnvironmentGitPublic.
   448  func (t *ClusterConfig) UnmarshalJSON(data []byte) error {
   449  	// need a type alias to go into infinite loop
   450  	type Alias ClusterConfig
   451  	aux := &struct {
   452  		*Alias
   453  	}{
   454  		Alias: (*Alias)(t),
   455  	}
   456  
   457  	if err := json.Unmarshal(data, &aux); err != nil {
   458  		return err
   459  	}
   460  
   461  	var raw map[string]json.RawMessage
   462  	err := json.Unmarshal(data, &raw)
   463  	if err != nil {
   464  		return err
   465  	}
   466  
   467  	_, gitPublicSet := raw["environmentGitPublic"]
   468  	private, gitPrivateSet := raw["environmentGitPrivate"]
   469  
   470  	if gitPrivateSet && gitPublicSet {
   471  		return errors.New("found settings for EnvironmentGitPublic as well as EnvironmentGitPrivate in ClusterConfig, only EnvironmentGitPublic should be used")
   472  	}
   473  
   474  	if gitPrivateSet {
   475  		log.Logger().Warn("EnvironmentGitPrivate specified in Cluster EnvironmentGitPrivate is deprecated use EnvironmentGitPublic instead.")
   476  		privateString := string(private)
   477  		if privateString == "true" {
   478  			t.EnvironmentGitPublic = false
   479  		} else {
   480  			t.EnvironmentGitPublic = true
   481  		}
   482  	}
   483  	return nil
   484  }
   485  
   486  // VersionStreamConfig contains version stream config
   487  type VersionStreamConfig struct {
   488  	// URL of the version stream to use
   489  	URL string `json:"url"`
   490  	// Ref of the version stream to use
   491  	Ref string `json:"ref" envconfig:"JX_REQUIREMENT_VERSIONS_GIT_REF"`
   492  }
   493  
   494  // VeleroConfig contains the configuration for velero
   495  type VeleroConfig struct {
   496  	// Namespace the namespace to install velero into
   497  	Namespace string `json:"namespace,omitempty"`
   498  	// ServiceAccount the cloud service account used to run velero
   499  	ServiceAccount string `json:"serviceAccount,omitempty" envconfig:"JX_REQUIREMENT_VELERO_SA_NAME"`
   500  	// Schedule of backups
   501  	Schedule string `json:"schedule" envconfig:"JX_REQUIREMENT_VELERO_SCHEDULE"`
   502  	// TimeToLive period for backups to be retained
   503  	TimeToLive string `json:"ttl" envconfig:"JX_REQUIREMENT_VELERO_TTL"`
   504  }
   505  
   506  // AutoUpdateConfig contains auto update config
   507  type AutoUpdateConfig struct {
   508  	// Enabled autoupdate
   509  	Enabled bool `json:"enabled"`
   510  	// Schedule cron of auto updates
   511  	Schedule string `json:"schedule"`
   512  }
   513  
   514  // GithubAppConfig contains github app config
   515  type GithubAppConfig struct {
   516  	// Enabled this determines whether this install should use the jenkins x github app for access tokens
   517  	Enabled bool `json:"enabled" envconfig:"JX_REQUIREMENT_GITHUB_APP_ENABLED"`
   518  	// Schedule cron of the github app token refresher
   519  	Schedule string `json:"schedule,omitempty"`
   520  	// URL contains a URL to the github app
   521  	URL string `json:"url,omitempty" envconfig:"JX_REQUIREMENT_GITHUB_APP_URL"`
   522  }
   523  
   524  // RequirementsValues contains the logical installation requirements in the `jx-requirements.yml` file as helm values
   525  type RequirementsValues struct {
   526  	// RequirementsConfig contains the logical installation requirements
   527  	RequirementsConfig *RequirementsConfig `json:"jxRequirements,omitempty"`
   528  }
   529  
   530  // UserNameEmailConfig contains the user name and email of a user (e.g. pipeline user)
   531  type UserNameEmailConfig struct {
   532  	// Username the username of the user
   533  	Username string `json:"username,omitempty"`
   534  	// Email the email address of the user
   535  	Email string `json:"email,omitempty"`
   536  }
   537  
   538  // RequirementsConfig contains the logical installation requirements in the `jx-requirements.yml` file when
   539  // installing, configuring or upgrading Jenkins X via `jx boot`
   540  type RequirementsConfig struct {
   541  	// AutoUpdate contains auto update config
   542  	AutoUpdate AutoUpdateConfig `json:"autoUpdate,omitempty"`
   543  	// BootConfigURL contains the url to which the dev environment is associated with
   544  	BootConfigURL string `json:"bootConfigURL,omitempty"`
   545  	// BuildPackConfig contains custom build pack settings
   546  	BuildPacks *BuildPackConfig `json:"buildPacks,omitempty"`
   547  	// Cluster contains cluster specific requirements
   548  	Cluster ClusterConfig `json:"cluster"`
   549  	// Environments the requirements for the environments
   550  	Environments []EnvironmentConfig `json:"environments,omitempty"`
   551  	// GithubApp contains github app config
   552  	GithubApp *GithubAppConfig `json:"githubApp,omitempty"`
   553  	// GitOps if enabled we will setup a webhook in the boot configuration git repository so that we can
   554  	// re-run 'jx boot' when changes merge to the master branch
   555  	GitOps bool `json:"gitops,omitempty"`
   556  	// Indicates if we are using helmfile and helm 3 to spin up environments. This is currently an experimental
   557  	// feature flag used to implement better Multi-Cluster support. See https://github.com/jenkins-x/jx/issues/6442
   558  	Helmfile bool `json:"helmfile,omitempty"`
   559  	// Kaniko whether to enable kaniko for building docker images
   560  	Kaniko bool `json:"kaniko,omitempty"`
   561  	// Ingress contains ingress specific requirements
   562  	Ingress IngressConfig `json:"ingress"`
   563  	// PipelineUser the user name and email used for running pipelines
   564  	PipelineUser *UserNameEmailConfig `json:"pipelineUser,omitempty"`
   565  	// Repository specifies what kind of artifact repository you wish to use for storing artifacts (jars, tarballs, npm modules etc)
   566  	Repository RepositoryType `json:"repository,omitempty" envconfig:"JX_REQUIREMENT_REPOSITORY"`
   567  	// SecretStorage how should we store secrets for the cluster
   568  	SecretStorage SecretStorageType `json:"secretStorage,omitempty" envconfig:"JX_REQUIREMENT_SECRET_STORAGE_TYPE"`
   569  	// Storage contains storage requirements
   570  	Storage StorageConfig `json:"storage"`
   571  	// Terraform specifies if  we are managing the kubernetes cluster and cloud resources with Terraform
   572  	Terraform bool `json:"terraform,omitempty"`
   573  	// Vault the configuration for vault
   574  	Vault VaultConfig `json:"vault,omitempty"`
   575  	// Velero the configuration for running velero for backing up the cluster resources
   576  	Velero VeleroConfig `json:"velero,omitempty"`
   577  	// VersionStream contains version stream info
   578  	VersionStream VersionStreamConfig `json:"versionStream"`
   579  	// Webhook specifies what engine we should use for webhooks
   580  	Webhook WebhookType `json:"webhook,omitempty"`
   581  }
   582  
   583  // NewRequirementsConfig creates a default configuration file
   584  func NewRequirementsConfig() *RequirementsConfig {
   585  	return &RequirementsConfig{
   586  		SecretStorage: SecretStorageTypeLocal,
   587  		Webhook:       WebhookTypeProw,
   588  	}
   589  }
   590  
   591  // LoadRequirementsConfig loads the configuration if there is a project configuration file
   592  // if there is not a file called `jx-requirements.yml` in the given dir we will scan up the parent
   593  // directories looking for the requirements file as we often run 'jx' steps in sub directories.
   594  func LoadRequirementsConfig(dir string, failOnValidationErrors bool) (*RequirementsConfig, string, error) {
   595  	absolute, err := filepath.Abs(dir)
   596  	if err != nil {
   597  		return nil, "", errors.Wrap(err, "creating absolute path")
   598  	}
   599  	for absolute != "" && absolute != "." && absolute != "/" {
   600  		fileName := filepath.Join(absolute, RequirementsConfigFileName)
   601  		absolute = filepath.Dir(absolute)
   602  
   603  		exists, err := util.FileExists(fileName)
   604  		if err != nil {
   605  			return nil, "", err
   606  		}
   607  
   608  		if !exists {
   609  			continue
   610  		}
   611  
   612  		config, err := LoadRequirementsConfigFile(fileName, failOnValidationErrors)
   613  		return config, fileName, err
   614  	}
   615  	return nil, "", errors.New("jx-requirements.yml file not found")
   616  }
   617  
   618  // LoadRequirementsConfigFile loads a specific project YAML configuration file
   619  func LoadRequirementsConfigFile(fileName string, failOnValidationErrors bool) (*RequirementsConfig, error) {
   620  	config := &RequirementsConfig{}
   621  	_, err := os.Stat(fileName)
   622  	if err != nil {
   623  		return nil, errors.Wrapf(err, "checking if file %s exists", fileName)
   624  	}
   625  
   626  	data, err := ioutil.ReadFile(fileName)
   627  	if err != nil {
   628  		return nil, fmt.Errorf("failed to load file %s due to %s", fileName, err)
   629  	}
   630  
   631  	validationErrors, err := util.ValidateYaml(config, data)
   632  	if err != nil {
   633  		return nil, fmt.Errorf("failed to validate YAML file %s due to %s", fileName, err)
   634  	}
   635  
   636  	if len(validationErrors) > 0 {
   637  		log.Logger().Warnf("validation failures in YAML file %s: %s", fileName, strings.Join(validationErrors, ", "))
   638  
   639  		if failOnValidationErrors {
   640  			return nil, fmt.Errorf("validation failures in YAML file %s:\n%s", fileName, strings.Join(validationErrors, "\n"))
   641  		}
   642  	}
   643  
   644  	err = yaml.Unmarshal(data, config)
   645  	if err != nil {
   646  		return nil, fmt.Errorf("failed to unmarshal YAML file %s due to %s", fileName, err)
   647  	}
   648  
   649  	config.addDefaults()
   650  	config.handleDeprecation()
   651  	return config, nil
   652  }
   653  
   654  // GetRequirementsConfigFromTeamSettings reads the BootRequirements string from TeamSettings and unmarshals it
   655  func GetRequirementsConfigFromTeamSettings(settings *v1.TeamSettings) (*RequirementsConfig, error) {
   656  	if settings == nil {
   657  		return nil, nil
   658  	}
   659  
   660  	// TeamSettings does not have a real value for BootRequirements, so this is probably not a boot cluster.
   661  	if settings.BootRequirements == "" {
   662  		return nil, nil
   663  	}
   664  
   665  	config := &RequirementsConfig{}
   666  	data := []byte(settings.BootRequirements)
   667  	validationErrors, err := util.ValidateYaml(config, data)
   668  	if err != nil {
   669  		return config, fmt.Errorf("failed to validate requirements from team settings due to %s", err)
   670  	}
   671  	if len(validationErrors) > 0 {
   672  		return config, fmt.Errorf("validation failures in requirements from team settings:\n%s", strings.Join(validationErrors, "\n"))
   673  	}
   674  	err = yaml.Unmarshal(data, config)
   675  	if err != nil {
   676  		return config, fmt.Errorf("failed to unmarshal requirements from team settings due to %s", err)
   677  	}
   678  	return config, nil
   679  }
   680  
   681  // IsEmpty returns true if this configuration is empty
   682  func (c *RequirementsConfig) IsEmpty() bool {
   683  	empty := &RequirementsConfig{}
   684  	return reflect.DeepEqual(empty, c)
   685  }
   686  
   687  // SaveConfig saves the configuration file to the given project directory
   688  func (c *RequirementsConfig) SaveConfig(fileName string) error {
   689  	c.handleDeprecation()
   690  	data, err := yaml.Marshal(c)
   691  	if err != nil {
   692  		return err
   693  	}
   694  	err = ioutil.WriteFile(fileName, data, util.DefaultWritePermissions)
   695  	if err != nil {
   696  		return errors.Wrapf(err, "failed to save file %s", fileName)
   697  	}
   698  
   699  	if c.Helmfile {
   700  		y := RequirementsValues{
   701  			RequirementsConfig: c,
   702  		}
   703  		data, err = yaml.Marshal(y)
   704  		if err != nil {
   705  			return err
   706  		}
   707  
   708  		err = ioutil.WriteFile(path.Join(path.Dir(fileName), RequirementsValuesFileName), data, util.DefaultWritePermissions)
   709  		if err != nil {
   710  			return errors.Wrapf(err, "failed to save file %s", RequirementsValuesFileName)
   711  		}
   712  	}
   713  
   714  	return nil
   715  }
   716  
   717  type environmentsSliceTransformer struct{}
   718  
   719  // environmentsSliceTransformer.Transformer is handling the correct merge of two EnvironmentConfig slices
   720  // so we can both append extra items and merge existing ones so we don't lose any data
   721  func (t environmentsSliceTransformer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error {
   722  	if typ == reflect.TypeOf([]EnvironmentConfig{}) {
   723  		return func(dst, src reflect.Value) error {
   724  			d := dst.Interface().([]EnvironmentConfig)
   725  			s := src.Interface().([]EnvironmentConfig)
   726  			if dst.CanSet() {
   727  				for i, v := range s {
   728  					value := v
   729  					if i > len(d)-1 {
   730  						d = append(d, value)
   731  					} else {
   732  						err := mergo.Merge(&d[i], &value, mergo.WithOverride)
   733  						if err != nil {
   734  							return errors.Wrap(err, "error merging EnvironmentConfig slices")
   735  						}
   736  					}
   737  				}
   738  				dst.Set(reflect.ValueOf(d))
   739  			}
   740  			return nil
   741  		}
   742  	}
   743  	return nil
   744  }
   745  
   746  // MergeSave attempts to merge the provided RequirementsConfig with the caller's data.
   747  // It does so overriding values in the source struct with non-zero values from the provided struct
   748  // it defines non-zero per property and not for a while embedded struct, meaning that nested properties
   749  // in embedded structs should also be merged correctly.
   750  // if a slice is added a transformer will be needed to handle correctly merging the contained values
   751  func (c *RequirementsConfig) MergeSave(src *RequirementsConfig, requirementsFileName string) error {
   752  	err := mergo.Merge(c, src, mergo.WithOverride, mergo.WithTransformers(environmentsSliceTransformer{}))
   753  	if err != nil {
   754  		return errors.Wrap(err, "error merging jx-requirements.yml files")
   755  	}
   756  	err = c.SaveConfig(requirementsFileName)
   757  	if err != nil {
   758  		return errors.Wrapf(err, "error saving the merged jx-requirements.yml files to %s", requirementsFileName)
   759  	}
   760  	return nil
   761  }
   762  
   763  // EnvironmentMap creates a map of maps tree which can be used inside Go templates to access the environment
   764  // configurations
   765  func (c *RequirementsConfig) EnvironmentMap() map[string]interface{} {
   766  	answer := map[string]interface{}{}
   767  	for _, env := range c.Environments {
   768  		e := env
   769  		k := e.Key
   770  		if k == "" {
   771  			log.Logger().Warnf("missing 'key' for Environment requirements %#v", e)
   772  			continue
   773  		}
   774  		m, err := util.ToObjectMap(&e)
   775  		if err == nil {
   776  			ensureHasFields(m, "owner", "repository", "gitServer", "gitKind")
   777  			answer[k] = m
   778  		} else {
   779  			log.Logger().Warnf("failed to turn environment %s with value %#v into a map: %s\n", k, e, err.Error())
   780  		}
   781  	}
   782  	return answer
   783  }
   784  
   785  // Environment looks up the environment configuration based on environment name
   786  func (c *RequirementsConfig) Environment(name string) (*EnvironmentConfig, error) {
   787  	for _, env := range c.Environments {
   788  		if env.Key == name {
   789  			return &env, nil
   790  		}
   791  	}
   792  	return nil, fmt.Errorf("environment %q not found", name)
   793  }
   794  
   795  // ToMap converts this object to a map of maps for use in helm templating
   796  func (c *RequirementsConfig) ToMap() (map[string]interface{}, error) {
   797  	m, err := util.ToObjectMap(c)
   798  	if m != nil {
   799  		ensureHasFields(m, "provider", "project", "environmentGitOwner", "gitops", "webhook")
   800  	}
   801  	return m, err
   802  }
   803  
   804  // IsCloudProvider returns true if the kubenretes provider is a cloud
   805  func (c *RequirementsConfig) IsCloudProvider() bool {
   806  	p := c.Cluster.Provider
   807  	return p == cloud.GKE || p == cloud.AKS || p == cloud.AWS || p == cloud.EKS || p == cloud.ALIBABA
   808  }
   809  
   810  func ensureHasFields(m map[string]interface{}, keys ...string) {
   811  	for _, k := range keys {
   812  		_, ok := m[k]
   813  		if !ok {
   814  			m[k] = ""
   815  		}
   816  	}
   817  }
   818  
   819  // MissingRequirement returns an error if there is a missing property in the requirements
   820  func MissingRequirement(property string, fileName string) error {
   821  	return fmt.Errorf("missing property: %s in file %s", property, fileName)
   822  }
   823  
   824  // IsLazyCreateSecrets returns a boolean whether secrets should be lazily created
   825  func (c *RequirementsConfig) IsLazyCreateSecrets(flag string) (bool, error) {
   826  	if flag != "" {
   827  		if flag == "true" {
   828  			return true, nil
   829  		} else if flag == "false" {
   830  			return false, nil
   831  		} else {
   832  			return false, util.InvalidOption("lazy-create", flag, []string{"true", "false"})
   833  		}
   834  	} else {
   835  		// lets default from the requirements
   836  		if !c.Terraform {
   837  			return true, nil
   838  		}
   839  	}
   840  	// default to false
   841  	return false, nil
   842  }
   843  
   844  // addDefaults lets ensure any missing values have good defaults
   845  func (c *RequirementsConfig) addDefaults() {
   846  	if c.Cluster.Namespace == "" {
   847  		c.Cluster.Namespace = "jx"
   848  	}
   849  	if c.Cluster.GitServer == "" {
   850  		c.Cluster.GitServer = "https://github.com"
   851  	}
   852  	if c.Cluster.GitKind == "" {
   853  		c.Cluster.GitKind = "github"
   854  	}
   855  	if c.Cluster.GitName == "" {
   856  		c.Cluster.GitName = c.Cluster.GitKind
   857  	}
   858  	if c.Ingress.NamespaceSubDomain == "" {
   859  		c.Ingress.NamespaceSubDomain = "-" + c.Cluster.Namespace + "."
   860  	}
   861  	if c.Webhook == WebhookTypeNone {
   862  		if c.Cluster.GitServer == "https://github.com" || c.Cluster.GitServer == "https://github.com/" {
   863  			c.Webhook = WebhookTypeProw
   864  		} else {
   865  			// TODO when lighthouse is GA lets default to it
   866  			// c.Webhook = WebhookTypeLighthouse
   867  		}
   868  	}
   869  	if c.Repository == "" {
   870  		c.Repository = "nexus"
   871  	}
   872  }
   873  
   874  func (c *RequirementsConfig) handleDeprecation() {
   875  	if c.Vault.Name != "" {
   876  		c.Cluster.VaultName = c.Vault.Name
   877  	} else {
   878  		c.Vault.Name = c.Cluster.VaultName
   879  	}
   880  
   881  	if c.Vault.ServiceAccount != "" {
   882  		c.Cluster.VaultSAName = c.Vault.ServiceAccount
   883  	} else {
   884  		c.Vault.ServiceAccount = c.Cluster.VaultSAName
   885  	}
   886  }
   887  
   888  // IsAutoDNSDomain returns true if the domain is configured to use an auto DNS sub domain like
   889  // '.nip.io' or '.xip.io'
   890  func (i *IngressConfig) IsAutoDNSDomain() bool {
   891  	for _, suffix := range autoDNSSuffixes {
   892  		if strings.HasSuffix(i.Domain, suffix) {
   893  			return true
   894  		}
   895  	}
   896  	return false
   897  }
   898  
   899  // OverrideRequirementsFromEnvironment allows properties to be overridden with environment variables
   900  func (c *RequirementsConfig) OverrideRequirementsFromEnvironment(gcloudFn func() gke.GClouder) {
   901  	// struct members need to use explicit 'envconfig:"<var-name>"' unless there is a match between struct member navigation
   902  	// path and env variable name
   903  	err := envconfig.InitWithOptions(&c, envconfig.Options{AllOptional: true, LeaveNil: true, Prefix: "JX_REQUIREMENT"})
   904  	if err != nil {
   905  		log.Logger().Errorf("Unable to init envconfig for override requirements: %s", err)
   906  	}
   907  
   908  	// RequirementIngressTLSProduction applies to more than one environment and needs to be handled explicitly
   909  	if "" != os.Getenv(RequirementIngressTLSProduction) {
   910  		useProduction := os.Getenv(RequirementIngressTLSProduction)
   911  		if envVarBoolean(useProduction) {
   912  			c.Ingress.TLS.Production = true
   913  			for _, e := range c.Environments {
   914  				e.Ingress.TLS.Production = true
   915  			}
   916  		} else {
   917  			c.Ingress.TLS.Production = false
   918  			for _, e := range c.Environments {
   919  				e.Ingress.TLS.Production = false
   920  			}
   921  		}
   922  	}
   923  
   924  	// StorageConfig is reused between multiple storage configuration types and needs to be handled explicitly
   925  	if "" != os.Getenv(RequirementStorageBackupEnabled) {
   926  		storageBackup := os.Getenv(RequirementStorageBackupEnabled)
   927  		if envVarBoolean(storageBackup) && "" != os.Getenv(RequirementStorageBackupURL) {
   928  			c.Storage.Backup.Enabled = true
   929  			c.Storage.Backup.URL = os.Getenv(RequirementStorageBackupURL)
   930  		}
   931  	}
   932  
   933  	if "" != os.Getenv(RequirementStorageLogsEnabled) {
   934  		storageLogs := os.Getenv(RequirementStorageLogsEnabled)
   935  		if envVarBoolean(storageLogs) && "" != os.Getenv(RequirementStorageLogsURL) {
   936  			c.Storage.Logs.Enabled = true
   937  			c.Storage.Logs.URL = os.Getenv(RequirementStorageLogsURL)
   938  		}
   939  	}
   940  
   941  	if "" != os.Getenv(RequirementStorageReportsEnabled) {
   942  		storageReports := os.Getenv(RequirementStorageReportsEnabled)
   943  		if envVarBoolean(storageReports) && "" != os.Getenv(RequirementStorageReportsURL) {
   944  			c.Storage.Reports.Enabled = true
   945  			c.Storage.Reports.URL = os.Getenv(RequirementStorageReportsURL)
   946  		}
   947  	}
   948  
   949  	if "" != os.Getenv(RequirementStorageRepositoryEnabled) {
   950  		storageRepository := os.Getenv(RequirementStorageRepositoryEnabled)
   951  		if envVarBoolean(storageRepository) && "" != os.Getenv(RequirementStorageRepositoryURL) {
   952  			c.Storage.Repository.Enabled = true
   953  			c.Storage.Repository.URL = os.Getenv(RequirementStorageRepositoryURL)
   954  		}
   955  	}
   956  
   957  	if "" != os.Getenv(RequirementDevEnvApprovers) {
   958  		rawApprovers := os.Getenv(RequirementDevEnvApprovers)
   959  		for _, approver := range strings.Split(rawApprovers, ",") {
   960  			c.Cluster.DevEnvApprovers = append(c.Cluster.DevEnvApprovers, strings.TrimSpace(approver))
   961  		}
   962  	}
   963  
   964  	// set this if its not currently configured
   965  	if c.Cluster.Provider == "gke" {
   966  		if c.Cluster.GKEConfig == nil {
   967  			c.Cluster.GKEConfig = &GKEConfig{}
   968  		}
   969  
   970  		if c.Cluster.GKEConfig.ProjectNumber == "" {
   971  			if gcloudFn != nil {
   972  				gcloud := gcloudFn()
   973  				if gcloud != nil {
   974  					projectNumber, err := gcloud.GetProjectNumber(c.Cluster.ProjectID)
   975  					if err != nil {
   976  						log.Logger().Warnf("unable to determine gke project number - %s", err)
   977  					}
   978  					c.Cluster.GKEConfig.ProjectNumber = projectNumber
   979  				}
   980  			}
   981  		}
   982  	}
   983  }
   984  
   985  func envVarBoolean(value string) bool {
   986  	return value == "true" || value == "yes"
   987  }