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

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