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 }