github.com/olli-ai/jx/v2@v2.0.400-0.20210921045218-14731b4dd448/pkg/cmd/create/install.go (about) 1 package create 2 3 import ( 4 "encoding/base64" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 "sort" 10 "strings" 11 "time" 12 13 "github.com/olli-ai/jx/v2/pkg/cmd/opts/upgrade" 14 "github.com/olli-ai/jx/v2/pkg/errorutil" 15 "github.com/olli-ai/jx/v2/pkg/vault/create" 16 17 createoptions "github.com/olli-ai/jx/v2/pkg/cmd/create/options" 18 cmdvault "github.com/olli-ai/jx/v2/pkg/cmd/create/vault" 19 20 "github.com/olli-ai/jx/v2/pkg/packages" 21 22 "github.com/olli-ai/jx/v2/pkg/prow" 23 24 "github.com/olli-ai/jx/v2/pkg/platform" 25 26 "github.com/olli-ai/jx/v2/pkg/cmd/opts/step" 27 28 "github.com/olli-ai/jx/v2/pkg/versionstream" 29 30 "github.com/olli-ai/jx/v2/pkg/cmd/edit" 31 "github.com/olli-ai/jx/v2/pkg/cmd/initcmd" 32 "github.com/olli-ai/jx/v2/pkg/kube/naming" 33 34 "github.com/spf13/viper" 35 36 "github.com/olli-ai/jx/v2/pkg/cmd/step/env" 37 38 "github.com/olli-ai/jx/v2/pkg/cmd/helper" 39 40 gkeStorage "github.com/olli-ai/jx/v2/pkg/cloud/gke/storage" 41 "github.com/olli-ai/jx/v2/pkg/kube/cluster" 42 43 "k8s.io/helm/pkg/chartutil" 44 45 "github.com/olli-ai/jx/v2/pkg/cloud" 46 "github.com/olli-ai/jx/v2/pkg/cloud/gke" 47 version2 "github.com/olli-ai/jx/v2/pkg/version" 48 49 "github.com/ghodss/yaml" 50 51 "github.com/Pallinder/go-randomdata" 52 "github.com/olli-ai/jx/v2/pkg/io/secrets" 53 kubevault "github.com/olli-ai/jx/v2/pkg/kube/vault" 54 "github.com/olli-ai/jx/v2/pkg/vault" 55 56 jenkinsio "github.com/jenkins-x/jx-api/pkg/apis/jenkins.io" 57 58 v1 "github.com/jenkins-x/jx-api/pkg/apis/jenkins.io/v1" 59 "github.com/jenkins-x/jx-logging/pkg/log" 60 "github.com/olli-ai/jx/v2/pkg/addon" 61 "github.com/olli-ai/jx/v2/pkg/auth" 62 "github.com/olli-ai/jx/v2/pkg/cloud/aks" 63 "github.com/olli-ai/jx/v2/pkg/cloud/amazon" 64 "github.com/olli-ai/jx/v2/pkg/cloud/iks" 65 "github.com/olli-ai/jx/v2/pkg/cmd/opts" 66 "github.com/olli-ai/jx/v2/pkg/cmd/templates" 67 "github.com/olli-ai/jx/v2/pkg/config" 68 "github.com/olli-ai/jx/v2/pkg/features" 69 "github.com/olli-ai/jx/v2/pkg/gits" 70 "github.com/olli-ai/jx/v2/pkg/helm" 71 configio "github.com/olli-ai/jx/v2/pkg/io" 72 "github.com/olli-ai/jx/v2/pkg/kube" 73 "github.com/olli-ai/jx/v2/pkg/util" 74 pkgvault "github.com/olli-ai/jx/v2/pkg/vault" 75 "github.com/pkg/errors" 76 "github.com/spf13/cobra" 77 "gopkg.in/AlecAivazis/survey.v1" 78 "gopkg.in/src-d/go-git.v4" 79 core_v1 "k8s.io/api/core/v1" 80 storagev1 "k8s.io/api/storage/v1" 81 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 82 "k8s.io/client-go/kubernetes" 83 ) 84 85 // ModifySecretCallback a callback for modifying a Secret for a given name 86 type ModifySecretCallback func(string, func(*core_v1.Secret) error) (*core_v1.Secret, error) 87 88 // ModifyConfigMapCallback a callback for modifying a ConfigMap for a given name 89 type ModifyConfigMapCallback func(string, func(*core_v1.ConfigMap) error) (*core_v1.ConfigMap, error) 90 91 // InstallOptions is the start of the data required to perform the operation. As new fields are added, add them here instead of 92 // referencing the cmd.Flags() 93 type InstallOptions struct { 94 *opts.CommonOptions 95 gits.GitRepositoryOptions 96 CreateJenkinsUserOptions 97 CreateEnvOptions 98 config.AdminSecretsService 99 kubevault.AWSConfig 100 101 InitOptions initcmd.InitOptions 102 Flags InstallFlags `mapstructure:"install"` 103 104 modifyConfigMapCallback ModifyConfigMapCallback 105 modifySecretCallback ModifySecretCallback 106 107 installValues map[string]string 108 } 109 110 // InstallFlags flags for the install command 111 type InstallFlags struct { 112 ConfigFile string 113 InstallOnly bool 114 Domain string 115 ExposeControllerURLTemplate string 116 ExposeControllerPathMode string 117 AzureRegistrySubscription string 118 DockerRegistry string 119 DockerRegistryOrg string 120 Provider string 121 VersionsRepository string 122 VersionsGitRef string 123 Version string 124 LocalHelmRepoName string 125 Namespace string 126 CloudEnvRepository string 127 NoDefaultEnvironments bool 128 RemoteEnvironments bool 129 DefaultEnvironmentPrefix string 130 LocalCloudEnvironment bool 131 EnvironmentGitOwner string 132 Timeout string 133 HelmTLS bool 134 RegisterLocalHelmRepo bool 135 CleanupTempFiles bool 136 Prow bool 137 DisableSetKubeContext bool 138 Dir string 139 Vault bool 140 RecreateVaultBucket bool 141 Tekton bool 142 BuildPackName string 143 Kaniko bool 144 GitOpsMode bool 145 NoGitOpsEnvApply bool 146 NoGitOpsEnvRepo bool 147 NoGitOpsEnvSetup bool 148 NoGitOpsVault bool 149 NextGeneration bool `mapstructure:"next-generation"` 150 StaticJenkins bool 151 LongTermStorage bool `mapstructure:"long-term-storage"` 152 LongTermStorageBucketName string `mapstructure:"lts-bucket"` 153 } 154 155 // Secrets struct for secrets 156 type Secrets struct { 157 Login string 158 Token string 159 } 160 161 const ( 162 JX_GIT_TOKEN = "JX_GIT_TOKEN" // #nosec 163 JX_GIT_USER = "JX_GIT_USER" 164 165 ServerlessJenkins = "Serverless Jenkins X Pipelines with Tekton" 166 167 GitOpsChartYAML = `name: env 168 version: 0.0.1 169 description: GitOps Environment for this Environment 170 maintainers: 171 - name: Team 172 icon: https://www.cloudbees.com/sites/default/files/Jenkins_8.png 173 ` 174 175 devGitOpsGitIgnore = ` 176 # lets not accidentally check in Secret YAMLs! 177 secrets.yaml 178 mysecrets.yaml 179 ` 180 181 devGitOpsReadMe = ` 182 ## Jenkins X Development Environment 183 184 This repository contains the source code for the Jenkins X Development Environment so that it can be managed via GitOps. 185 ` 186 187 devGitOpsJenkinsfile = `pipeline { 188 agent { 189 label "jenkins-jx-base" 190 } 191 environment { 192 DEPLOY_NAMESPACE = "%s" 193 } 194 stages { 195 stage('Validate Environment') { 196 steps { 197 container('jx-base') { 198 dir('env') { 199 sh 'jx step helm build' 200 } 201 } 202 } 203 } 204 stage('Update Environment') { 205 when { 206 branch 'master' 207 } 208 steps { 209 container('jx-base') { 210 dir('env') { 211 sh 'jx step env apply' 212 } 213 } 214 } 215 } 216 } 217 } 218 ` 219 220 devGitOpsJenkinsfileProw = `pipeline { 221 agent any 222 environment { 223 DEPLOY_NAMESPACE = "%s" 224 } 225 stages { 226 stage('Validate Environment') { 227 steps { 228 dir('env') { 229 sh 'jx step helm build' 230 } 231 } 232 } 233 stage('Update Environment') { 234 when { 235 branch 'master' 236 } 237 steps { 238 dir('env') { 239 sh 'jx step env apply' 240 } 241 } 242 } 243 } 244 } 245 ` 246 longTermStorageFlagName = "long-term-storage" 247 ltsBucketFlagName = "lts-bucket" 248 kanikoFlagName = "kaniko" 249 namespaceFlagName = "namespace" 250 tektonFlagName = "tekton" 251 prowFlagName = "prow" 252 staticJenkinsFlagName = "static-jenkins" 253 gitOpsFlagName = "gitops" 254 ) 255 256 var ( 257 InstalLong = templates.LongDesc(` 258 Installs the Jenkins X platform on a Kubernetes cluster 259 260 Requires a --git-username and --git-api-token that can be used to create a new token. 261 This is so the Jenkins X platform can git tag your releases 262 263 For more documentation see: [https://jenkins-x.io/getting-started/install-on-cluster/](https://jenkins-x.io/getting-started/install-on-cluster/) 264 265 The current requirements are: 266 267 *RBAC is enabled on the cluster 268 269 *Insecure Docker registry is enabled for Docker registries running locally inside Kubernetes on the service IP range. See the above documentation for more detail 270 271 `) 272 273 InstalExample = templates.Examples(` 274 # Default installer which uses interactive prompts to generate git secrets 275 jx install 276 277 # Install with a GitHub personal access token 278 jx install --git-username jenkins-x-bot --git-api-token 9fdbd2d070cd81eb12bca87861bcd850 279 280 # If you know the cloud provider you can pass this as a CLI argument. E.g. for AWS 281 jx install --provider=aws 282 `) 283 ) 284 285 // NewCmdInstall creates a command object for the generic "install" action, which 286 // installs the jenkins-x platform on a Kubernetes cluster. 287 func NewCmdInstall(commonOpts *opts.CommonOptions) *cobra.Command { 288 289 options := CreateInstallOptions(commonOpts) 290 291 cmd := &cobra.Command{ 292 Use: "install [flags]", 293 Short: "Install Jenkins X in the current Kubernetes cluster", 294 Long: InstalLong, 295 Example: InstalExample, 296 Run: func(cmd *cobra.Command, args []string) { 297 options.Cmd = cmd 298 options.Args = args 299 err := options.Run() 300 helper.CheckErr(err) 301 }, 302 } 303 304 options.AddInstallFlags(cmd, false) 305 306 cmd.Flags().StringVarP(&options.Flags.Provider, "provider", "", "", "Cloud service providing the Kubernetes cluster. Supported providers: "+cloud.KubernetesProviderOptions()) 307 308 cmd.AddCommand(NewCmdInstallDependencies(commonOpts)) 309 cmdvault.AwsCreateVaultOptions(cmd, &options.AWSConfig) 310 311 return cmd 312 } 313 314 // CreateInstallOptions creates the options for jx install 315 func CreateInstallOptions(commonOpts *opts.CommonOptions) InstallOptions { 316 commonOptsBatch := *commonOpts 317 commonOptsBatch.BatchMode = true 318 options := InstallOptions{ 319 CreateJenkinsUserOptions: CreateJenkinsUserOptions{ 320 Username: "admin", 321 CreateOptions: createoptions.CreateOptions{ 322 CommonOptions: commonOpts, 323 }, 324 }, 325 GitRepositoryOptions: gits.GitRepositoryOptions{}, 326 CommonOptions: commonOpts, 327 CreateEnvOptions: CreateEnvOptions{ 328 HelmValuesConfig: config.HelmValuesConfig{ 329 ExposeController: &config.ExposeController{ 330 Config: config.ExposeControllerConfig{ 331 HTTP: "true", 332 TLSAcme: "false", 333 Exposer: "Ingress", 334 }, 335 }, 336 }, 337 Options: v1.Environment{ 338 ObjectMeta: metav1.ObjectMeta{}, 339 Spec: v1.EnvironmentSpec{ 340 PromotionStrategy: v1.PromotionStrategyTypeAutomatic, 341 }, 342 }, 343 PromotionStrategy: string(v1.PromotionStrategyTypeAutomatic), 344 ForkEnvironmentGitRepo: kube.DefaultEnvironmentGitRepoURL, 345 CreateOptions: createoptions.CreateOptions{ 346 CommonOptions: &commonOptsBatch, 347 }, 348 }, 349 InitOptions: initcmd.InitOptions{ 350 CommonOptions: commonOpts, 351 Flags: initcmd.InitFlags{}, 352 }, 353 AdminSecretsService: config.AdminSecretsService{}, 354 } 355 return options 356 } 357 358 func (options *InstallOptions) AddInstallFlags(cmd *cobra.Command, includesInit bool) { 359 flags := &options.Flags 360 flags.AddCloudEnvOptions(cmd) 361 cmd.Flags().StringVarP(&flags.LocalHelmRepoName, "local-helm-repo-name", "", kube.LocalHelmRepoName, "The name of the helm repository for the installed ChartMuseum") 362 cmd.Flags().BoolVarP(&flags.NoDefaultEnvironments, "no-default-environments", "", false, "Disables the creation of the default Staging and Production environments") 363 cmd.Flags().BoolVarP(&flags.RemoteEnvironments, "remote-environments", "", false, "Indicates you intend Staging and Production environments to run in remote clusters. See https://jenkins-x.io/getting-started/multi-cluster/") 364 cmd.Flags().StringVarP(&flags.DefaultEnvironmentPrefix, "default-environment-prefix", "", "", "Default environment repo prefix, your Git repos will be of the form 'environment-$prefix-$envName'") 365 cmd.Flags().StringVarP(&flags.Namespace, namespaceFlagName, "", "jx", "The namespace the Jenkins X platform should be installed into") 366 cmd.Flags().StringVarP(&flags.Timeout, "timeout", "", opts.DefaultInstallTimeout, "The number of seconds to wait for the helm install to complete") 367 cmd.Flags().StringVarP(&flags.EnvironmentGitOwner, "environment-git-owner", "", "", "The Git provider organisation to create the environment Git repositories in") 368 cmd.Flags().BoolVarP(&flags.RegisterLocalHelmRepo, "register-local-helmrepo", "", false, "Registers the Jenkins X ChartMuseum registry with your helm client [default false]") 369 cmd.Flags().BoolVarP(&flags.CleanupTempFiles, "cleanup-temp-files", "", true, "Cleans up any temporary values.yaml used by helm install [default true]") 370 cmd.Flags().BoolVarP(&flags.HelmTLS, "helm-tls", "", false, "Whether to use TLS with helm") 371 cmd.Flags().BoolVarP(&flags.InstallOnly, "install-only", "", false, "Force the install command to fail if there is already an installation. Otherwise lets update the installation") 372 cmd.Flags().StringVarP(&flags.AzureRegistrySubscription, "azure-acr-subscription", "", "", "The Azure subscription under which the specified docker-registry is located") 373 cmd.Flags().StringVarP(&flags.DockerRegistry, "docker-registry", "", "", "The Docker Registry host or host:port which is used when tagging and pushing images. If not specified it defaults to the internal registry unless there is a better provider default (e.g. ECR on AWS/EKS)") 374 cmd.Flags().StringVarP(&flags.DockerRegistryOrg, "docker-registry-org", "", "", "The Docker Registry organiation/user to create images inside. On GCP this is typically your Google Project ID.") 375 cmd.Flags().StringVarP(&flags.ExposeControllerURLTemplate, "exposecontroller-urltemplate", "", "", "The ExposeController urltemplate for how services should be exposed as URLs. Defaults to being empty, which in turn defaults to \"{{.Service}}.{{.Namespace}}.{{.Domain}}\".") 376 cmd.Flags().StringVarP(&flags.ExposeControllerPathMode, "exposecontroller-pathmode", "", "", "The ExposeController path mode for how services should be exposed as URLs. Defaults to using subnets. Use a value of `path` to use relative paths within the domain host such as when using AWS ELB host names") 377 378 cmd.Flags().StringVarP(&flags.Version, "version", "", "", "The specific platform version to install") 379 cmd.Flags().BoolVarP(&flags.Prow, prowFlagName, "", false, "Enable Prow to implement Serverless Jenkins and support ChatOps on Pull Requests") 380 cmd.Flags().BoolVarP(&flags.Tekton, tektonFlagName, "", false, "Enables the Tekton pipeline engine (which used to be called knative build pipeline) along with Prow to provide Serverless Jenkins. Otherwise we default to use Knative Build if you enable Prow") 381 cmd.Flags().BoolVarP(&flags.GitOpsMode, gitOpsFlagName, "", false, "Creates a git repository for the Dev environment to manage the installation, configuration, upgrade and addition of Apps in Jenkins X all via GitOps") 382 cmd.Flags().BoolVarP(&flags.NoGitOpsEnvApply, "no-gitops-env-apply", "", false, "When using GitOps to create the source code for the development environment and installation, don't run 'jx step env apply' to perform the install") 383 cmd.Flags().BoolVarP(&flags.NoGitOpsEnvRepo, "no-gitops-env-repo", "", false, "When using GitOps to create the source code for the development environment this flag disables the creation of a git repository for the source code") 384 cmd.Flags().BoolVarP(&flags.NoGitOpsVault, "no-gitops-vault", "", false, "When using GitOps to create the source code for the development environment this flag disables the creation of a vault") 385 cmd.Flags().BoolVarP(&flags.NoGitOpsEnvSetup, "no-gitops-env-setup", "", false, "When using GitOps to install the development environment this flag skips the post-install setup") 386 cmd.Flags().BoolVarP(&flags.Vault, "vault", "", false, "Sets up a Hashicorp Vault for storing secrets during installation (supported only for GKE)") 387 cmd.Flags().BoolVarP(&flags.RecreateVaultBucket, "vault-bucket-recreate", "", true, "If the vault bucket already exists delete it then create it empty") 388 cmd.Flags().StringVarP(&flags.BuildPackName, "buildpack", "", "", "The name of the build pack to use for the Team") 389 cmd.Flags().BoolVarP(&flags.Kaniko, kanikoFlagName, "", false, "Use Kaniko for building docker images") 390 cmd.Flags().BoolVarP(&flags.NextGeneration, "ng", "", false, "Use the Next Generation Jenkins X features like Prow, Tekton, No Tiller, Vault, Dev GitOps") 391 cmd.Flags().BoolVarP(&flags.StaticJenkins, staticJenkinsFlagName, "", false, "Install a static Jenkins master to use as the pipeline engine. Note this functionality is deprecated in favour of running serverless Tekton builds") 392 cmd.Flags().BoolVarP(&flags.LongTermStorage, longTermStorageFlagName, "", false, "Enable the Long Term Storage option to save logs and other assets into a GCS bucket (supported only for GKE)") 393 cmd.Flags().StringVarP(&flags.LongTermStorageBucketName, ltsBucketFlagName, "", "", "The bucket to use for Long Term Storage. If the bucket doesn't exist, an attempt will be made to create it, otherwise random naming will be used") 394 cmd.Flags().StringVar(&options.ConfigFile, "config-file", "", "Configuration file used for installation") 395 cmd.Flags().BoolVar(&options.NoBrew, opts.OptionNoBrew, false, "Disables brew package manager on MacOS when installing binary dependencies") 396 cmd.Flags().BoolVar(&options.InstallDependencies, opts.OptionInstallDeps, false, "Enables automatic dependencies installation when required") 397 cmd.Flags().BoolVar(&options.SkipAuthSecretsMerge, opts.OptionSkipAuthSecMerge, false, "Skips merging the secrets from local files with the secrets from Kubernetes cluster") 398 399 bindInstallConfigToFlags(cmd) 400 opts.AddGitRepoOptionsArgumentsWithGithubDefault(cmd, &options.GitRepositoryOptions) 401 options.HelmValuesConfig.AddExposeControllerValues(cmd, true) 402 options.AdminSecretsService.AddAdminSecretsValues(cmd) 403 options.InitOptions.AddInitFlags(cmd) 404 } 405 406 func bindInstallConfigToFlags(cmd *cobra.Command) { 407 _ = viper.BindPFlag(installConfigKey(namespaceFlagName), cmd.Flags().Lookup(namespaceFlagName)) 408 _ = viper.BindPFlag(installConfigKey(kanikoFlagName), cmd.Flags().Lookup(kanikoFlagName)) 409 _ = viper.BindPFlag(installConfigKey(tektonFlagName), cmd.Flags().Lookup(tektonFlagName)) 410 _ = viper.BindPFlag(installConfigKey(prowFlagName), cmd.Flags().Lookup(prowFlagName)) 411 _ = viper.BindPFlag(installConfigKey(gitOpsFlagName), cmd.Flags().Lookup(gitOpsFlagName)) 412 _ = viper.BindPFlag(installConfigKey("next-generation"), cmd.Flags().Lookup("ng")) 413 _ = viper.BindPFlag(installConfigKey(staticJenkinsFlagName), cmd.Flags().Lookup(staticJenkinsFlagName)) 414 } 415 416 func (flags *InstallFlags) AddCloudEnvOptions(cmd *cobra.Command) { 417 cmd.Flags().StringVarP(&flags.CloudEnvRepository, "cloud-environment-repo", "", opts.DefaultCloudEnvironmentsURL, "Cloud Environments Git repo") 418 cmd.Flags().StringVarP(&flags.VersionsRepository, "versions-repo", "", config.DefaultVersionsURL, "Jenkins X versions Git repo") 419 cmd.Flags().StringVarP(&flags.VersionsGitRef, "versions-ref", "", "", "Jenkins X versions Git repository reference (tag, branch, sha etc)") 420 cmd.Flags().BoolVarP(&flags.LocalCloudEnvironment, "local-cloud-environment", "", false, "Ignores default cloud-environment-repo and uses current directory ") 421 } 422 423 // CheckFlags validates & configures install flags 424 func (options *InstallOptions) CheckFlags() error { 425 log.Logger().Debug("checking installation flags") 426 flags := &options.Flags 427 428 if flags.StaticJenkins { 429 return fmt.Errorf("option '--static-jenkins' has been removed") 430 } 431 432 if flags.Prow { 433 flags.Tekton = true 434 } 435 436 if flags.Tekton { 437 flags.Prow = true 438 if !options.InitOptions.Flags.NoTiller { 439 log.Logger().Warnf("note that if using Serverless Jenkins with Tekton we recommend the extra flag: %s", util.ColorInfo("--no-tiller")) 440 } 441 } 442 443 if options.BatchMode && !flags.NextGeneration && !flags.Tekton && !flags.Prow { 444 flags.Tekton = true 445 flags.Prow = true 446 flags.Kaniko = true 447 options.InitOptions.Flags.NoTiller = true 448 } 449 450 if flags.NextGeneration { 451 flags.GitOpsMode = true 452 flags.Vault = true 453 flags.Prow = true 454 flags.Tekton = true 455 flags.Kaniko = true 456 options.InitOptions.Flags.NoTiller = true 457 } 458 459 // only kaniko is supported as a builder in tekton 460 if flags.Tekton { 461 if flags.Provider == cloud.GKE { 462 if !flags.Kaniko { 463 log.Logger().Warnf("When using tekton, only kaniko is supported as a builder") 464 } 465 flags.Kaniko = true 466 } 467 } 468 469 // check some flags combination for GitOps mode 470 if flags.GitOpsMode { 471 options.SkipAuthSecretsMerge = true 472 flags.DisableSetKubeContext = true 473 if !flags.Vault { 474 log.Logger().Warnf("GitOps mode requires %s.", util.ColorInfo("vault")) 475 } 476 initFlags := &options.InitOptions.Flags 477 if !initFlags.NoTiller { 478 log.Logger().Warnf("GitOps mode requires helm without tiller server. %s flag is automatically set", util.ColorInfo("no-tiller")) 479 initFlags.NoTiller = true 480 } 481 } 482 483 // If we're using external-dns then remove the namespace subdomain from the URLTemplate 484 if options.InitOptions.Flags.ExternalDNS { 485 flags.ExposeControllerURLTemplate = `"{{.Service}}-{{.Namespace}}.{{.Domain}}"` 486 } 487 488 // Make sure that the default environment prefix is configured. Typically it is the cluster 489 // name when the install command is called from create cluster. 490 if flags.DefaultEnvironmentPrefix == "" { 491 clusterName := options.installValues[kube.ClusterName] 492 if clusterName == "" { 493 flags.DefaultEnvironmentPrefix = strings.ToLower(randomdata.SillyName()) 494 } else { 495 flags.DefaultEnvironmentPrefix = clusterName 496 } 497 } 498 499 if flags.DockerRegistry == "" { 500 dockerReg, err := options.dockerRegistryValue() 501 if err != nil { 502 log.Logger().Warnf("unable to calculate docker registry values - %s", err) 503 } 504 flags.DockerRegistry = dockerReg 505 } 506 507 // lets default the docker registry org to the project id 508 if flags.DockerRegistryOrg == "" { 509 flags.DockerRegistryOrg = options.installValues[kube.ProjectID] 510 } 511 512 log.Logger().Debugf("flags after checking - %+v", flags) 513 514 if flags.Tekton { 515 kind := options.GitRepositoryOptions.ServerKind 516 if kind != "" && kind != gits.KindGitHub { 517 return fmt.Errorf("Git provider: %s is not yet supported for Tekton.\nYou can work around this with '--static-jenkins'\nFor more details see: https://jenkins-x.io/about/status/", kind) 518 } 519 } 520 return nil 521 } 522 523 // CheckFeatures - determines if the various features have been enabled 524 func (options *InstallOptions) CheckFeatures() error { 525 if options.Flags.Tekton { 526 return features.CheckTektonEnabled() 527 } 528 return nil 529 } 530 531 // Run implements this command 532 func (options *InstallOptions) Run() error { 533 534 err := options.GetConfiguration(&options) 535 if err != nil { 536 return errors.Wrap(err, "getting install configuration") 537 } 538 539 err = options.selectJenkinsInstallation() 540 if err != nil { 541 return errors.Wrap(err, "selecting the Jenkins installation type") 542 } 543 544 // Check the provided flags before starting any installation 545 err = options.CheckFlags() 546 if err != nil { 547 return errors.Wrap(err, "checking the provided flags") 548 } 549 550 configStore := configio.NewFileStore() 551 552 ns, originalNs, err := options.setupNamespace() 553 if err != nil { 554 return errors.Wrap(err, "setting up current namespace") 555 } 556 client, err := options.KubeClient() 557 if err != nil { 558 return errors.Wrap(err, "creating the kube client") 559 } 560 561 err = options.registerAllCRDs() 562 if err != nil { 563 return errors.Wrap(err, "registering all CRDs") 564 } 565 566 gitOpsDir, gitOpsEnvDir, err := options.configureGitOpsMode(configStore, ns) 567 if err != nil { 568 return errors.Wrap(err, "configuring the GitOps mode") 569 } 570 571 options.configureHelm(client, ns) 572 err = options.installHelmBinaries() 573 if err != nil { 574 return errors.Wrap(err, "installing helm binaries") 575 } 576 577 err = options.configureKubectl(ns) 578 if err != nil { 579 return errors.Wrap(err, "configure the kubectl") 580 } 581 582 err = options.installCloudProviderDependencies() 583 if err != nil { 584 return errors.Wrap(err, "installing cloud provider dependencies") 585 } 586 587 options.Flags.Provider, err = options.GetCloudProvider(options.Flags.Provider) 588 if err != nil { 589 return errors.Wrapf(err, "retrieving cloud provider '%s'", options.Flags.Provider) 590 } 591 592 err = options.configureTeamSettings() 593 if err != nil { 594 return errors.Wrap(err, "configuring the team settings in the dev environment") 595 } 596 597 err = options.configureCloudProviderPreInit(client) 598 if err != nil { 599 return errors.Wrap(err, "configuring the cloud provider before initializing the platform") 600 } 601 602 err = options.init() 603 if err != nil { 604 return errors.Wrap(err, "initializing the Jenkins X platform") 605 } 606 607 err = options.configureCloudProivderPostInit(client, ns) 608 if err != nil { 609 return errors.Wrap(err, "configuring the cloud provider after initializing the platform") 610 } 611 612 ic, err := options.saveIngressConfig() 613 if err != nil { 614 return errors.Wrap(err, "saving the ingress configuration in a ConfigMap") 615 } 616 617 err = options.configureLongTermStorageBucket() 618 if err != nil { 619 return errors.Wrap(err, "configuring Long Term Storage") 620 } 621 622 err = options.createSystemVault(client, ns, ic) 623 if err != nil { 624 return errors.Wrap(err, "creating the system vault") 625 } 626 627 err = options.saveClusterConfig() 628 if err != nil { 629 return errors.Wrap(err, "saving the cluster configuration in a ConfigMap") 630 } 631 632 err = options.configureGitAuth() 633 if err != nil { 634 return errors.Wrap(err, "configuring the git auth") 635 } 636 637 err = options.configureDockerRegistry(client, ns) 638 if err != nil { 639 return errors.Wrap(err, "configuring the docker registry") 640 } 641 642 versionsRepoDir, _, err := options.CloneJXVersionsRepo(options.Flags.VersionsRepository, options.Flags.VersionsGitRef) 643 if err != nil { 644 return errors.Wrap(err, "cloning the jx versions repo") 645 } 646 647 cloudEnvDir, err := options.CloneJXCloudEnvironmentsRepo() 648 if err != nil { 649 return errors.Wrap(err, "cloning the jx cloud environments repo") 650 } 651 652 err = options.ConfigureKaniko() 653 if err != nil { 654 return errors.Wrap(err, "unable to generate the Kaniko configuration") 655 } 656 657 err = options.configureHelmValues(ns) 658 if err != nil { 659 return errors.Wrap(err, "configuring helm values") 660 } 661 662 if options.Flags.Provider == "" { 663 return fmt.Errorf("no Kubernetes provider found to match cloud-environment with") 664 } 665 providerEnvDir := filepath.Join(cloudEnvDir, fmt.Sprintf("env-%s", strings.ToLower(options.Flags.Provider))) 666 valuesFiles, secretsFiles, temporaryFiles, err := options.getHelmValuesFiles(configStore, providerEnvDir) 667 if err != nil { 668 return errors.Wrap(err, "getting the helm value files") 669 } 670 671 log.Logger().Debugf("Installing Jenkins X platform helm chart from: %s", providerEnvDir) 672 673 err = options.configureHelmRepo() 674 if err != nil { 675 return errors.Wrap(err, "configuring the Jenkins X helm repository") 676 } 677 678 err = options.configureProwInTeamSettings() 679 if err != nil { 680 return errors.Wrap(err, "configuring Prow in team settings") 681 } 682 683 err = options.configureAndInstallProw(ns, gitOpsEnvDir, valuesFiles) 684 if err != nil { 685 return errors.Wrap(err, "configuring and installing Prow") 686 } 687 688 err = options.verifyTiller(client, ns) 689 if err != nil { 690 return errors.Wrap(err, "verifying Tiller is running") 691 } 692 693 err = options.configureBuildPackMode() 694 if err != nil { 695 return errors.Wrap(err, "configuring the build pack mode") 696 } 697 698 log.Logger().Infof("Installing jx into namespace %s", util.ColorInfo(ns)) 699 700 version, err := options.getPlatformVersion(versionsRepoDir, configStore) 701 if err != nil { 702 return errors.Wrap(err, "getting the platform version") 703 } 704 705 log.Logger().Infof("Installing jenkins-x-platform version: %s", util.ColorInfo(version)) 706 707 if options.Flags.GitOpsMode { 708 err := options.installPlatformGitOpsMode(gitOpsEnvDir, gitOpsDir, configStore, kube.DefaultChartMuseumURL, 709 platform.JenkinsXPlatformChartName, ns, version, valuesFiles, secretsFiles) 710 if err != nil { 711 return errors.Wrap(err, "installing the Jenkins X platform in GitOps mode") 712 } 713 } else { 714 err := options.installPlatform(providerEnvDir, platform.JenkinsXPlatformChart, platform.JenkinsXPlatformRelease, 715 ns, version, valuesFiles, secretsFiles) 716 if err != nil { 717 return errors.Wrap(err, "installing the Jenkins X platform") 718 } 719 } 720 721 if options.Flags.CleanupTempFiles { 722 err := options.cleanupTempFiles(temporaryFiles) 723 if err != nil { 724 return errors.Wrap(err, "cleaning up the temporary files") 725 } 726 } 727 728 err = options.configureImportModeInTeamSettings() 729 if err != nil { 730 return errors.Wrap(err, "configuring ImportMode in team settings") 731 } 732 733 err = options.configureTillerInDevEnvironment() 734 if err != nil { 735 return errors.Wrap(err, "configuring Tiller in the dev environment") 736 } 737 738 err = options.configureHelm3(ns) 739 if err != nil { 740 return errors.Wrap(err, "configuring helm3") 741 } 742 743 err = options.installAddons() 744 if err != nil { 745 return errors.Wrap(err, "installing the Jenkins X Addons") 746 } 747 748 // Jenkins needs to be configured already here if running in non GitOps mode 749 // in order to be able to create the environments 750 if !options.Flags.GitOpsMode { 751 err = options.configureJenkins(ns) 752 if err != nil { 753 return errors.Wrap(err, "configuring Jenkins") 754 } 755 } 756 757 err = options.createEnvironments(ns) 758 if err != nil { 759 if strings.Contains(err.Error(), "com.atlassian.bitbucket.project.NoSuchProjectException") { 760 log.Logger().Infof("\nProject %s cannot be found. If you are using BitBucket Server, please use "+ 761 "a project code instead of a project name (for example 'MYPR' instead of 'myproject'). ", 762 util.ColorInfo(options.CreateEnvOptions.GitRepositoryOptions.Owner)) 763 return nil 764 } 765 return errors.Wrap(err, "creating the environments") 766 } 767 768 err = options.saveChartmuseumAuthConfig() 769 if err != nil { 770 return errors.Wrap(err, "saving the ChartMuseum auth configuration") 771 } 772 773 if options.Flags.RegisterLocalHelmRepo { 774 err = options.RegisterLocalHelmRepo(options.Flags.LocalHelmRepoName, ns) 775 if err != nil { 776 return errors.Wrapf(err, "registering the local helm repo '%s'", options.Flags.LocalHelmRepoName) 777 } 778 } 779 780 gitOpsEnvDir, err = options.generateGitOpsDevEnvironmentConfig(gitOpsDir) 781 if err != nil { 782 return errors.Wrap(err, "generating the GitOps development environment config") 783 } 784 785 err = options.applyGitOpsDevEnvironmentConfig(gitOpsEnvDir, ns) 786 if err != nil { 787 return errors.Wrap(err, "applying the GitOps development environment config") 788 } 789 790 err = options.setupGitOpsPostApply(ns) 791 if err != nil { 792 return errors.Wrap(err, "setting up GitOps post installation") 793 } 794 795 log.Logger().Infof("\nJenkins X installation completed successfully") 796 797 options.logAdminPassword() 798 799 options.logNameServers() 800 801 log.Logger().Infof("Your Kubernetes context is now set to the namespace: %s ", util.ColorInfo(ns)) 802 log.Logger().Infof("To switch back to your original namespace use: %s", util.ColorInfo("jx namespace "+originalNs)) 803 log.Logger().Infof("Or to use this context/namespace in just one terminal use: %s", util.ColorInfo("jx shell")) 804 log.Logger().Infof("For help on switching contexts see: %s\n", util.ColorInfo("https://jenkins-x.io/developing/kube-context/")) 805 806 log.Logger().Infof("To import existing projects into Jenkins X: %s", util.ColorInfo("jx import")) 807 log.Logger().Infof("To create a new Spring Boot microservice: %s", util.ColorInfo("jx create spring -d web -d actuator")) 808 log.Logger().Infof("To create a new microservice from a quickstart: %s", util.ColorInfo("jx create quickstart")) 809 return nil 810 } 811 812 func (options *InstallOptions) configureKubectl(namespace string) error { 813 if !options.Flags.DisableSetKubeContext { 814 context, err := options.GetCommandOutput("", "kubectl", "config", "current-context") 815 if err != nil { 816 return errors.Wrap(err, "failed to retrieve the current context from kube configuration") 817 } 818 err = options.RunCommand("kubectl", "config", "set-context", context, "--namespace", namespace) 819 if err != nil { 820 return errors.Wrapf(err, "failed to set the context '%s' in kube configuration", context) 821 } 822 } 823 824 return nil 825 } 826 827 func (options *InstallOptions) setupNamespace() (string, string, error) { 828 _, originalNs, err := options.KubeClientAndNamespace() 829 if err != nil { 830 return "", "", errors.Wrap(err, "creating kube client") 831 } 832 ns := options.Flags.Namespace 833 if ns == "" { 834 ns = originalNs 835 } 836 options.SetDevNamespace(ns) 837 838 return ns, originalNs, nil 839 } 840 841 func (options *InstallOptions) init() error { 842 initOpts := &options.InitOptions 843 initOpts.Flags.Provider = options.Flags.Provider 844 initOpts.Flags.Namespace = options.Flags.Namespace 845 initOpts.BatchMode = options.BatchMode 846 initOpts.Flags.VersionsRepository = options.Flags.VersionsRepository 847 initOpts.Flags.Http = true 848 exposeController := options.CreateEnvOptions.HelmValuesConfig.ExposeController 849 if exposeController != nil { 850 initOpts.Flags.Http = exposeController.Config.HTTP == "true" 851 } 852 if initOpts.Flags.Domain == "" && options.Flags.Domain != "" { 853 initOpts.Flags.Domain = options.Flags.Domain 854 } 855 if initOpts.Flags.NoTiller { 856 initOpts.SetHelm(nil) 857 } 858 // configure local tiller if this is required 859 if !initOpts.Flags.RemoteTiller && !initOpts.Flags.NoTiller { 860 err := helm.RestartLocalTiller() 861 if err != nil { 862 return errors.Wrap(err, "restarting local tiller") 863 } 864 initOpts.SetHelm(options.Helm()) 865 } 866 867 // configure the helm values for expose controller 868 if exposeController != nil { 869 ecConfig := &exposeController.Config 870 if ecConfig.Domain == "" && options.Flags.Domain != "" { 871 ecConfig.Domain = options.Flags.Domain 872 log.Logger().Infof("set exposeController Config Domain %s", ecConfig.Domain) 873 } 874 if ecConfig.PathMode == "" && options.Flags.ExposeControllerPathMode != "" { 875 ecConfig.PathMode = options.Flags.ExposeControllerPathMode 876 log.Logger().Infof("set exposeController Config PathMode %s", ecConfig.PathMode) 877 } 878 if (ecConfig.URLTemplate == "" && options.Flags.ExposeControllerURLTemplate != "") || 879 (options.Flags.ExposeControllerURLTemplate != "" && options.InitOptions.Flags.ExternalDNS) { 880 ecConfig.URLTemplate = options.Flags.ExposeControllerURLTemplate 881 log.Logger().Infof("set exposeController Config URLTemplate %s", ecConfig.URLTemplate) 882 } 883 if isOpenShiftProvider(options.Flags.Provider) { 884 ecConfig.Exposer = "Route" 885 } 886 } 887 888 err := initOpts.Run() 889 if err != nil { 890 return errors.Wrap(err, "initializing the Jenkins X platform") 891 } 892 893 // update the domain if was modified during the initialization 894 domain := exposeController.Config.Domain 895 if domain == "" { 896 domain = initOpts.Flags.Domain 897 } 898 if domain == "" { 899 client, err := options.KubeClient() 900 if err != nil { 901 return errors.Wrap(err, "getting the kubernetes client") 902 } 903 ingNamespace := initOpts.Flags.IngressNamespace 904 ingService := initOpts.Flags.IngressService 905 extIP := initOpts.Flags.ExternalIP 906 domain, err = options.GetDomain(client, domain, 907 options.Flags.Provider, 908 ingNamespace, 909 ingService, 910 extIP) 911 if err != nil { 912 return errors.Wrapf(err, "getting a domain for ingress service %s/%s", ingNamespace, ingService) 913 } 914 } 915 916 // checking if the domain is by any chance empty and bail out 917 if domain == "" { 918 return fmt.Errorf("the installation cannot proceed with an empty domain. Please provide a domain in the %s option", 919 util.ColorInfo("domain")) 920 } 921 922 options.Flags.Domain = domain 923 exposeController.Config.Domain = domain 924 925 return nil 926 } 927 928 func (options *InstallOptions) getPlatformVersion(cloudEnvDir string, 929 configStore configio.ConfigStore) (string, error) { 930 version := options.Flags.Version 931 var err error 932 if version == "" { 933 version, err = LoadVersionFromCloudEnvironmentsDir(cloudEnvDir, configStore) 934 if err != nil { 935 return "", errors.Wrap(err, "failed to load version from cloud environments dir") 936 } 937 } 938 return version, nil 939 } 940 941 func (options *InstallOptions) installPlatform(providerEnvDir string, jxChart string, jxRelName string, 942 namespace string, version string, valuesFiles []string, secretsFiles []string) error { 943 944 options.Helm().SetCWD(providerEnvDir) 945 946 timeout := options.Flags.Timeout 947 if timeout == "" { 948 timeout = opts.DefaultInstallTimeout 949 } 950 951 allValuesFiles := []string{} 952 allValuesFiles = append(allValuesFiles, valuesFiles...) 953 allValuesFiles = append(allValuesFiles, secretsFiles...) 954 for _, f := range allValuesFiles { 955 log.Logger().Debugf("Adding values file %s", util.ColorInfo(f)) 956 } 957 958 helmOpts := helm.InstallChartOptions{ 959 ReleaseName: jxRelName, 960 Chart: jxChart, 961 Ns: namespace, 962 Version: version, 963 ValueFiles: allValuesFiles, 964 InstallOnly: options.Flags.InstallOnly, 965 NoForce: true, 966 } 967 err := options.InstallChartWithOptionsAndTimeout(helmOpts, timeout) 968 969 if err != nil { 970 return errors.Wrap(err, "failed to install/upgrade the jenkins-x platform chart") 971 } 972 973 err = options.waitForInstallToBeReady(namespace) 974 if err != nil { 975 return errors.Wrap(err, "failed to wait for jenkins-x chart installation to be ready") 976 } 977 log.Logger().Infof("Jenkins X deployments ready in namespace %s", namespace) 978 return nil 979 } 980 981 func (options *InstallOptions) installPlatformGitOpsMode(gitOpsEnvDir string, gitOpsDir string, configStore configio.ConfigStore, 982 chartRepository string, chartName string, namespace string, version string, valuesFiles []string, secretsFiles []string) error { 983 options.CreateEnvOptions.NoDevNamespaceInit = true 984 985 chartFile := filepath.Join(gitOpsEnvDir, helm.ChartFileName) 986 requirementsFile := filepath.Join(gitOpsEnvDir, helm.RequirementsFileName) 987 secretsFile := filepath.Join(gitOpsEnvDir, helm.SecretsFileName) 988 valuesFile := filepath.Join(gitOpsEnvDir, helm.ValuesFileName) 989 990 platformDep := &helm.Dependency{ 991 Name: platform.JenkinsXPlatformChartName, 992 Version: version, 993 Repository: kube.DefaultChartMuseumURL, 994 } 995 requirements := &helm.Requirements{ 996 Dependencies: []*helm.Dependency{platformDep}, 997 } 998 999 // lets handle if the requirements.yaml already exists we may have added some initial apps like prow etc 1000 exists, err := util.FileExists(requirementsFile) 1001 if err != nil { 1002 return err 1003 } 1004 if exists { 1005 requirements, err = helm.LoadRequirementsFile(requirementsFile) 1006 if err != nil { 1007 return errors.Wrapf(err, "failed to load helm requirements file %s", requirementsFile) 1008 } 1009 requirements.Dependencies = append(requirements.Dependencies, platformDep) 1010 } 1011 err = helm.SaveFile(requirementsFile, requirements) 1012 if err != nil { 1013 return errors.Wrapf(err, "failed to save GitOps helm requirements file %s", requirementsFile) 1014 } 1015 1016 err = configStore.Write(chartFile, []byte(GitOpsChartYAML)) 1017 if err != nil { 1018 return errors.Wrapf(err, "failed to save file %s", chartFile) 1019 } 1020 1021 err = helm.CombineValueFilesToFile(secretsFile, secretsFiles, platform.JenkinsXPlatformChartName, nil) 1022 if err != nil { 1023 return errors.Wrapf(err, "failed to generate %s by combining helm Secret YAML files %s", secretsFile, strings.Join(secretsFiles, ", ")) 1024 } 1025 1026 if options.Flags.Vault { 1027 err := options.storeSecretYamlFilesInVault(vault.GitOpsSecretsPath, secretsFile) 1028 if err != nil { 1029 return errors.Wrapf(err, "storing in Vault the secrets files: %s", secretsFile) 1030 } 1031 1032 err = util.DestroyFile(secretsFile) 1033 if err != nil { 1034 return errors.Wrapf(err, "destroying the secrets file '%s' after storing it in Vault", secretsFile) 1035 } 1036 } 1037 1038 extraValues := map[string]interface{}{ 1039 "postinstalljob": map[string]interface{}{"enabled": "true"}, 1040 } 1041 1042 err = options.setValuesFileValue(filepath.Join(gitOpsEnvDir, "jenkins", helm.ValuesFileName), "enabled", !options.Flags.Prow) 1043 if err != nil { 1044 return err 1045 } 1046 err = options.setValuesFileValue(filepath.Join(gitOpsEnvDir, "controllerbuild", helm.ValuesFileName), "enabled", options.Flags.Prow) 1047 if err != nil { 1048 return err 1049 } 1050 err = options.setValuesFileValue(filepath.Join(gitOpsEnvDir, "controllerworkflow", helm.ValuesFileName), "enabled", !options.Flags.Tekton) 1051 if err != nil { 1052 return err 1053 } 1054 1055 // lets load any existing values.yaml data as we may have created this via additional apps like Prow 1056 exists, err = util.FileExists(valuesFile) 1057 if err != nil { 1058 return err 1059 } 1060 if exists { 1061 currentValues, err := chartutil.ReadValuesFile(valuesFile) 1062 if err != nil { 1063 return err 1064 } 1065 util.CombineMapTrees(extraValues, currentValues) 1066 } 1067 1068 err = helm.CombineValueFilesToFile(valuesFile, valuesFiles, platform.JenkinsXPlatformChartName, extraValues) 1069 if err != nil { 1070 return errors.Wrapf(err, "failed to generate %s by combining helm value YAML files %s", valuesFile, strings.Join(valuesFiles, ", ")) 1071 } 1072 1073 gitIgnore := filepath.Join(gitOpsDir, ".gitignore") 1074 err = configStore.Write(gitIgnore, []byte(devGitOpsGitIgnore)) 1075 if err != nil { 1076 return errors.Wrapf(err, "failed to write %s", gitIgnore) 1077 } 1078 1079 readme := filepath.Join(gitOpsDir, "README.md") 1080 err = configStore.Write(readme, []byte(devGitOpsReadMe)) 1081 if err != nil { 1082 return errors.Wrapf(err, "failed to write %s", readme) 1083 } 1084 1085 jenkinsFile := filepath.Join(gitOpsDir, "Jenkinsfile") 1086 jftTmp := devGitOpsJenkinsfile 1087 isProw := options.Flags.Prow 1088 if isProw { 1089 jftTmp = devGitOpsJenkinsfileProw 1090 } 1091 text := fmt.Sprintf(jftTmp, namespace) 1092 err = configStore.Write(jenkinsFile, []byte(text)) 1093 if err != nil { 1094 return errors.Wrapf(err, "failed to write %s", jenkinsFile) 1095 } 1096 return nil 1097 } 1098 1099 func (options *InstallOptions) configureAndInstallProw(namespace string, gitOpsEnvDir string, valuesFiles []string) error { 1100 options.SetCurrentNamespace(namespace) 1101 if options.Flags.Prow { 1102 _, pipelineUser, err := options.GetPipelineGitAuth() 1103 if err != nil || pipelineUser == nil { 1104 return errors.Wrap(err, "retrieving the pipeline Git Auth") 1105 } 1106 options.OAUTHToken = pipelineUser.ApiToken 1107 err = options.InstallProw(options.Flags.Tekton, options.InitOptions.Flags.ExternalDNS, options.Flags.GitOpsMode, gitOpsEnvDir, pipelineUser.Username, valuesFiles) 1108 if err != nil { 1109 return errors.Wrap(err, "installing Prow") 1110 } 1111 } 1112 return nil 1113 } 1114 1115 func (options *InstallOptions) configureHelm3(namespace string) error { 1116 initOpts := &options.InitOptions 1117 helmBinary := initOpts.HelmBinary() 1118 if helmBinary != "helm" { 1119 helmOptions := edit.EditHelmBinOptions{} 1120 helmOptions.CommonOptions = options.CommonOptions 1121 helmOptions.CommonOptions.BatchMode = true 1122 helmOptions.CommonOptions.Args = []string{helmBinary} 1123 helmOptions.SetDevNamespace(namespace) 1124 err := helmOptions.Run() 1125 if err != nil { 1126 return errors.Wrap(err, "failed to edit the helm options") 1127 } 1128 } 1129 return nil 1130 } 1131 1132 func (options *InstallOptions) configureHelm(client kubernetes.Interface, namespace string) { 1133 initOpts := &options.InitOptions 1134 helmBinary := initOpts.HelmBinary() 1135 options.Helm().SetHelmBinary(helmBinary) 1136 if initOpts.Flags.NoTiller { 1137 helmer := options.Helm() 1138 helmCli, ok := helmer.(*helm.HelmCLI) 1139 if ok && helmCli != nil { 1140 helm := helm.NewHelmTemplate(helmCli, helmCli.CWD, client, namespace) 1141 options.SetHelm(helm) 1142 } else { 1143 helmTemplate, ok := helmer.(*helm.HelmTemplate) 1144 if ok { 1145 options.SetHelm(helmTemplate) 1146 } else { 1147 log.Logger().Warnf("Helm facade is not a *helm.HelmCLI or *helm.HelmTemplate: %#v", helmer) 1148 } 1149 } 1150 } 1151 } 1152 1153 func (options *InstallOptions) configureHelmRepo() error { 1154 _, err := options.AddHelmBinaryRepoIfMissing(kube.DefaultChartMuseumURL, "jenkins-x", "", "") 1155 if err != nil { 1156 return errors.Wrap(err, "failed to add the jenkinx-x helm repo") 1157 } 1158 1159 err = options.Helm().UpdateRepo() 1160 if err != nil { 1161 return errors.Wrap(err, "failed to update the helm repo") 1162 } 1163 return nil 1164 } 1165 1166 func (options *InstallOptions) selectJenkinsInstallation() error { 1167 if !options.BatchMode { 1168 //determine which install type is configured 1169 jenkinsInstallOption := ServerlessJenkins 1170 log.Logger().Infof(util.QuestionAnswer("Configured Jenkins installation type", jenkinsInstallOption)) 1171 } 1172 return nil 1173 } 1174 1175 func (options *InstallOptions) configureTillerNamespace() error { 1176 helmConfig := &options.CreateEnvOptions.HelmValuesConfig 1177 initOpts := &options.InitOptions 1178 tillerNameSpace := initOpts.Flags.TillerNamespace 1179 if tillerNameSpace != "" { 1180 if helmConfig.Jenkins.Servers.Global.EnvVars == nil { 1181 helmConfig.Jenkins.Servers.Global.EnvVars = map[string]string{} 1182 } 1183 helmConfig.Jenkins.Servers.Global.EnvVars["TILLER_NAMESPACE"] = tillerNameSpace 1184 err := os.Setenv("TILLER_NAMESPACE", tillerNameSpace) 1185 if err != nil { 1186 return errors.Wrapf(err, "failed to set env variable TILLER_NAMESPACE to %s", tillerNameSpace) 1187 } 1188 } 1189 return nil 1190 } 1191 1192 func (options *InstallOptions) configureHelmValues(namespace string) error { 1193 helmConfig := &options.CreateEnvOptions.HelmValuesConfig 1194 1195 domain := helmConfig.ExposeController.Config.Domain 1196 if domain != "" && addon.IsAddonEnabled("gitea") { 1197 helmConfig.Jenkins.Servers.GetOrCreateFirstGitea().Url = "http://gitea-gitea." + namespace + "." + domain 1198 } 1199 1200 err := options.addGitServersToJenkinsConfig(helmConfig) 1201 if err != nil { 1202 return errors.Wrap(err, "configuring the Git Servers into Jenkins configuration") 1203 } 1204 1205 err = options.configureTillerNamespace() 1206 if err != nil { 1207 return errors.Wrap(err, "configuring the tiller namespace") 1208 } 1209 1210 if !options.Flags.GitOpsMode { 1211 options.SetDevNamespace(namespace) 1212 } 1213 1214 isProw := options.Flags.Prow 1215 if isProw { 1216 enableJenkins := false 1217 helmConfig.Jenkins.Enabled = &enableJenkins 1218 helmConfig.ControllerBuild = &config.EnabledConfig{true} 1219 helmConfig.ControllerWorkflow = &config.EnabledConfig{false} 1220 if options.Flags.Tekton && options.Flags.Provider == cloud.GKE { 1221 helmConfig.DockerRegistryEnabled = &config.EnabledConfig{false} 1222 } 1223 } 1224 return nil 1225 } 1226 1227 func (options *InstallOptions) getHelmValuesFiles(configStore configio.ConfigStore, providerEnvDir string) ([]string, []string, []string, error) { 1228 helmConfig := &options.CreateEnvOptions.HelmValuesConfig 1229 cloudEnvironmentValuesLocation := filepath.Join(providerEnvDir, opts.CloudEnvValuesFile) 1230 cloudEnvironmentSecretsLocation := filepath.Join(providerEnvDir, opts.CloudEnvSecretsFile) 1231 1232 valuesFiles := []string{} 1233 secretsFiles := []string{} 1234 temporaryFiles := []string{} 1235 1236 adminSecretsFileName, adminSecrets, err := options.getAdminSecrets(configStore, 1237 providerEnvDir, cloudEnvironmentSecretsLocation) 1238 if err != nil { 1239 return valuesFiles, secretsFiles, temporaryFiles, 1240 errors.Wrap(err, "creating the admin secrets") 1241 } 1242 1243 dir, err := util.ConfigDir() 1244 if err != nil { 1245 return valuesFiles, secretsFiles, temporaryFiles, 1246 errors.Wrap(err, "creating a temporary config dir for Git credentials") 1247 } 1248 1249 extraValuesFileName := filepath.Join(dir, opts.ExtraValuesFile) 1250 err = configStore.WriteObject(extraValuesFileName, helmConfig) 1251 if err != nil { 1252 return valuesFiles, secretsFiles, temporaryFiles, 1253 errors.Wrapf(err, "writing the helm config in the file '%s'", extraValuesFileName) 1254 } 1255 log.Logger().Debugf("Generated helm values %s", util.ColorInfo(extraValuesFileName)) 1256 1257 err = options.modifySecrets(helmConfig, adminSecrets) 1258 if err != nil { 1259 return valuesFiles, temporaryFiles, secretsFiles, errors.Wrap(err, "updating the secrets data in Kubernetes cluster") 1260 } 1261 1262 valuesFiles = append(valuesFiles, cloudEnvironmentValuesLocation) 1263 valuesFiles, err = helm.AppendMyValues(valuesFiles) 1264 if err != nil { 1265 return valuesFiles, secretsFiles, temporaryFiles, 1266 errors.Wrap(err, "failed to append the myvalues.yaml file") 1267 } 1268 secretsFiles = append(secretsFiles, 1269 []string{adminSecretsFileName, extraValuesFileName, cloudEnvironmentSecretsLocation}...) 1270 1271 if options.Flags.Vault { 1272 temporaryFiles = append(temporaryFiles, adminSecretsFileName, extraValuesFileName, cloudEnvironmentSecretsLocation) 1273 } else { 1274 temporaryFiles = append(temporaryFiles, extraValuesFileName, cloudEnvironmentSecretsLocation) 1275 } 1276 1277 return util.FilterFileExists(valuesFiles), util.FilterFileExists(secretsFiles), util.FilterFileExists(temporaryFiles), nil 1278 } 1279 1280 func (options *InstallOptions) configureGitAuth() error { 1281 log.Logger().Infof("Set up a Git username and API token to be able to perform CI/CD") 1282 gitUsername := options.GitRepositoryOptions.Username 1283 gitServer := options.GitRepositoryOptions.ServerURL 1284 gitAPIToken := options.GitRepositoryOptions.ApiToken 1285 if gitUsername == "" { 1286 gitUsernameEnv := os.Getenv(JX_GIT_USER) 1287 if gitUsernameEnv != "" { 1288 gitUsername = gitUsernameEnv 1289 } 1290 } 1291 1292 if gitAPIToken == "" { 1293 gitAPITokenEnv := os.Getenv(JX_GIT_TOKEN) 1294 if gitAPITokenEnv != "" { 1295 gitAPIToken = gitAPITokenEnv 1296 } 1297 } 1298 1299 authConfigSvc, err := options.GitAuthConfigService() 1300 if err != nil { 1301 return errors.Wrap(err, "creating the git auth config service") 1302 } 1303 1304 authConfig := authConfigSvc.Config() 1305 var userAuth *auth.UserAuth 1306 if gitUsername != "" && gitAPIToken != "" && gitServer != "" { 1307 userAuth = &auth.UserAuth{ 1308 ApiToken: gitAPIToken, 1309 Username: gitUsername, 1310 } 1311 authConfig.SetUserAuth(gitServer, userAuth) 1312 } 1313 1314 var authServer *auth.AuthServer 1315 if gitServer != "" { 1316 kind := "" 1317 if options.GitRepositoryOptions.ServerKind == "" { 1318 kind = gits.SaasGitKind(gitServer) 1319 } else { 1320 kind = options.GitRepositoryOptions.ServerKind 1321 } 1322 authServer = authConfig.GetOrCreateServerName(gitServer, "", kind) 1323 } else { 1324 authServer, err = authConfig.PickServer("Which Git provider:", options.BatchMode, options.GetIOFileHandles()) 1325 if err != nil { 1326 return errors.Wrap(err, "getting the git provider from user") 1327 } 1328 } 1329 1330 message := fmt.Sprintf("local Git user for %s server:", authServer.Label()) 1331 userAuth, err = authConfig.PickServerUserAuth(authServer, message, options.BatchMode, "", options.GetIOFileHandles()) 1332 if err != nil { 1333 return errors.Wrapf(err, "selecting the local user for git server %s", authServer.Label()) 1334 } 1335 1336 if userAuth.IsInvalid() { 1337 log.Logger().Infof("Creating a local Git user for %s server", authServer.Label()) 1338 f := func(username string) error { 1339 options.Git().PrintCreateRepositoryGenerateAccessToken(authServer, username, options.Out) 1340 return nil 1341 } 1342 defaultUserName := "" 1343 err = authConfig.EditUserAuth(authServer.Label(), userAuth, defaultUserName, false, options.BatchMode, f, 1344 options.GetIOFileHandles()) 1345 if err != nil { 1346 return errors.Wrapf(err, "creating a user authentication for git server %s", authServer.Label()) 1347 } 1348 if userAuth.IsInvalid() { 1349 return fmt.Errorf("invalid user authentication for git server %s", authServer.Label()) 1350 } 1351 authConfig.SetUserAuth(gitServer, userAuth) 1352 } 1353 1354 log.Logger().Infof("Select the CI/CD pipelines Git server and user") 1355 var pipelineAuthServer *auth.AuthServer 1356 if options.BatchMode { 1357 pipelineAuthServer = authServer 1358 } else { 1359 surveyOpts := survey.WithStdio(options.In, options.Out, options.Err) 1360 confirm := &survey.Confirm{ 1361 Message: fmt.Sprintf("Do you wish to use %s as the pipelines Git server:", authServer.Label()), 1362 Default: true, 1363 } 1364 yes := false 1365 err = survey.AskOne(confirm, &yes, nil, surveyOpts) 1366 if err != nil { 1367 return errors.Wrap(err, "selecting pipelines Git server") 1368 } 1369 if yes { 1370 pipelineAuthServer = authServer 1371 } else { 1372 pipelineAuthServerURL, err := util.PickValue("Git Service URL:", gits.GitHubURL, true, "", 1373 options.GetIOFileHandles()) 1374 if err != nil { 1375 return errors.Wrap(err, "reading the pipelines Git service URL") 1376 } 1377 pipelineAuthServer, err = authConfig.PickOrCreateServer(gits.GitHubURL, pipelineAuthServerURL, 1378 "Which Git Service do you wish to use:", 1379 options.BatchMode, options.GetIOFileHandles()) 1380 if err != nil { 1381 return errors.Wrap(err, "selecting the pipelines Git Service") 1382 } 1383 } 1384 } 1385 1386 // lets default the values from the CLI arguments 1387 if options.GitRepositoryOptions.Username != "" { 1388 authConfig.PipeLineUsername = options.GitRepositoryOptions.Username 1389 } 1390 if options.GitRepositoryOptions.ServerURL != "" { 1391 authConfig.PipeLineServer = options.GitRepositoryOptions.ServerURL 1392 } 1393 pipelineUserAuth, err := options.PickPipelineUserAuth(authConfig, authServer) 1394 if err != nil { 1395 return errors.Wrapf(err, "selecting the pipeline user for git server %s", authServer.Label()) 1396 } 1397 if pipelineUserAuth.IsInvalid() { 1398 log.Logger().Infof("Creating a pipelines Git user for %s server", authServer.Label()) 1399 f := func(username string) error { 1400 options.Git().PrintCreateRepositoryGenerateAccessToken(pipelineAuthServer, username, options.Out) 1401 return nil 1402 } 1403 defaultUserName := "" 1404 err = authConfig.EditUserAuth(pipelineAuthServer.Label(), pipelineUserAuth, defaultUserName, false, options.BatchMode, 1405 f, options.GetIOFileHandles()) 1406 if err != nil { 1407 return errors.Wrapf(err, "creating a pipeline user authentication for git server %s", authServer.Label()) 1408 } 1409 if userAuth.IsInvalid() { 1410 return fmt.Errorf("invalid pipeline user authentication for git server %s", authServer.Label()) 1411 } 1412 authConfig.SetUserAuth(pipelineAuthServer.URL, pipelineUserAuth) 1413 } 1414 1415 pipelineAuthServerURL := pipelineAuthServer.URL 1416 pipelineAuthUsername := pipelineUserAuth.Username 1417 1418 log.Logger().Infof("Setting the pipelines Git server %s and user name %s.", 1419 util.ColorInfo(pipelineAuthServerURL), util.ColorInfo(pipelineAuthUsername)) 1420 authConfig.UpdatePipelineServer(pipelineAuthServer, pipelineUserAuth) 1421 1422 log.Logger().Debugf("Saving the Git authentication configuration") 1423 err = authConfigSvc.SaveConfig() 1424 if err != nil { 1425 return errors.Wrap(err, "saving the Git authentication configuration") 1426 } 1427 1428 editTeamSettingsCallback := func(env *v1.Environment) error { 1429 teamSettings := &env.Spec.TeamSettings 1430 teamSettings.GitServer = pipelineAuthServerURL 1431 teamSettings.PipelineUsername = pipelineAuthUsername 1432 teamSettings.Organisation = options.Owner 1433 teamSettings.GitPublic = options.GitRepositoryOptions.Public 1434 return nil 1435 } 1436 err = options.ModifyDevEnvironment(editTeamSettingsCallback) 1437 if err != nil { 1438 return errors.Wrap(err, "updating the team settings into the environment configuration") 1439 } 1440 1441 return nil 1442 } 1443 1444 func (options *InstallOptions) buildGitRepositoryOptionsForEnvironments() (*gits.GitRepositoryOptions, error) { 1445 authConfigSvc, err := options.GitAuthConfigService() 1446 if err != nil { 1447 return nil, errors.Wrap(err, "creating Git authentication config service") 1448 } 1449 config := authConfigSvc.Config() 1450 1451 server := config.CurrentAuthServer() 1452 if server == nil { 1453 return nil, fmt.Errorf("no current git server set in the configuration") 1454 } 1455 user := config.CurrentUser(server, false) 1456 if user == nil { 1457 return nil, fmt.Errorf("no current git user set in configuration for server '%s'", server.Label()) 1458 } 1459 1460 org := options.Flags.EnvironmentGitOwner 1461 if org == "" { 1462 if options.BatchMode { 1463 jxClient, _, err := options.JXClientAndDevNamespace() 1464 if err != nil { 1465 return nil, errors.Wrap(err, "determining the git owner for environments") 1466 } 1467 org, _ = kube.GetDevEnvGitOwner(jxClient) 1468 if org == "" { 1469 org = user.Username 1470 } 1471 1472 log.Logger().Infof("Using %s environment git owner in batch mode.", util.ColorInfo(org)) 1473 } else { 1474 provider, err := gits.CreateProvider(server, user, options.Git()) 1475 if err != nil { 1476 return nil, errors.Wrap(err, "creating the Git provider") 1477 } 1478 1479 orgs := gits.GetOrganizations(provider, user.Username) 1480 if len(orgs) == 0 { 1481 return nil, fmt.Errorf("user '%s' has no organizations", user.Username) 1482 } 1483 1484 surveyOpts := survey.WithStdio(options.In, options.Out, options.Err) 1485 sort.Strings(orgs) 1486 prompt := &survey.Select{ 1487 Message: "Select the organization where you want to create the environment repository:", 1488 Options: orgs, 1489 } 1490 err = survey.AskOne(prompt, &org, survey.Required, surveyOpts) 1491 if err != nil { 1492 return nil, errors.Wrap(err, "selecting the organization for environment repository") 1493 } 1494 } 1495 } 1496 1497 //Save selected organisation for Environment repos. 1498 err = options.ModifyDevEnvironment(func(env *v1.Environment) error { 1499 env.Spec.TeamSettings.EnvOrganisation = org 1500 return nil 1501 }) 1502 if err != nil { 1503 return nil, errors.Wrap(err, "updating the TeamSettings with Environments organisation") 1504 } 1505 1506 return &gits.GitRepositoryOptions{ 1507 ServerURL: server.URL, 1508 Username: user.Username, 1509 ApiToken: user.ApiToken, 1510 Owner: org, 1511 Public: options.GitRepositoryOptions.Public, 1512 }, nil 1513 } 1514 1515 func (options *InstallOptions) cleanupTempFiles(temporaryFiles []string) error { 1516 for _, tempFile := range temporaryFiles { 1517 exists, err := util.FileExists(tempFile) 1518 if exists && err == nil { 1519 err := util.DestroyFile(tempFile) 1520 if err != nil { 1521 return errors.Wrapf(err, "removing temporary file '%s'", tempFile) 1522 } 1523 } 1524 } 1525 return nil 1526 } 1527 1528 func (options *InstallOptions) verifyTiller(client kubernetes.Interface, namespace string) error { 1529 initOpts := &options.InitOptions 1530 if !initOpts.Flags.NoTiller { 1531 serviceAccountName := "tiller" 1532 tillerNamespace := options.InitOptions.Flags.TillerNamespace 1533 1534 log.Logger().Infof("Waiting for %s pod to be ready, service account name is %s, namespace is %s, tiller namespace is %s", 1535 util.ColorInfo("tiller"), util.ColorInfo(serviceAccountName), util.ColorInfo(namespace), util.ColorInfo(tillerNamespace)) 1536 1537 clusterRoleBindingName := serviceAccountName + "-role-binding" 1538 role := options.InitOptions.Flags.TillerClusterRole 1539 1540 log.Logger().Infof("Waiting for cluster role binding to be defined, named %s in namespace %s", util.ColorInfo(clusterRoleBindingName), util.ColorInfo(namespace)) 1541 err := options.EnsureClusterRoleBinding(clusterRoleBindingName, role, namespace, serviceAccountName) 1542 if err != nil { 1543 return errors.Wrap(err, "tiller cluster role not defined") 1544 } 1545 log.Logger().Infof("tiller cluster role defined: %s in namespace %s", util.ColorInfo(role), util.ColorInfo(namespace)) 1546 1547 err = kube.WaitForDeploymentToBeReady(client, "tiller-deploy", tillerNamespace, 10*time.Minute) 1548 if err != nil { 1549 msg := fmt.Sprintf("tiller pod (tiller-deploy in namespace %s) is not running after 10 minutes", tillerNamespace) 1550 return errors.Wrap(err, msg) 1551 } 1552 log.Logger().Info("tiller pod running") 1553 } 1554 return nil 1555 } 1556 1557 func (options *InstallOptions) configureTillerInDevEnvironment() error { 1558 initOpts := &options.InitOptions 1559 if !initOpts.Flags.RemoteTiller && !initOpts.Flags.NoTiller { 1560 callback := func(env *v1.Environment) error { 1561 env.Spec.TeamSettings.NoTiller = true 1562 log.Logger().Info("Disabling the server side use of tiller in the TeamSettings") 1563 return nil 1564 } 1565 err := options.ModifyDevEnvironment(callback) 1566 if err != nil { 1567 return err 1568 } 1569 } 1570 return nil 1571 } 1572 1573 func (options *InstallOptions) configureProwInTeamSettings() error { 1574 if options.Flags.Prow { 1575 callback := func(env *v1.Environment) error { 1576 env.Spec.WebHookEngine = v1.WebHookEngineProw 1577 settings := &env.Spec.TeamSettings 1578 settings.PromotionEngine = v1.PromotionEngineProw 1579 settings.ProwEngine = v1.ProwEngineTypeTekton 1580 settings.ImportMode = v1.ImportModeTypeYAML 1581 log.Logger().Debugf("Configuring the TeamSettings for Prow with engine %s", string(settings.ProwEngine)) 1582 return nil 1583 } 1584 err := options.ModifyDevEnvironment(callback) 1585 if err != nil { 1586 return err 1587 } 1588 } 1589 return nil 1590 } 1591 1592 func (options *InstallOptions) configureImportModeInTeamSettings() error { 1593 callback := func(env *v1.Environment) error { 1594 settings := &env.Spec.TeamSettings 1595 if string(settings.ImportMode) == "" { 1596 if options.Flags.Tekton { 1597 settings.ImportMode = v1.ImportModeTypeYAML 1598 } else { 1599 settings.ImportMode = v1.ImportModeTypeJenkinsfile 1600 } 1601 } 1602 log.Logger().Infof("Configuring the TeamSettings for ImportMode %s", string(settings.ImportMode)) 1603 return nil 1604 } 1605 return options.ModifyDevEnvironment(callback) 1606 } 1607 1608 func (options *InstallOptions) configureGitOpsMode(configStore configio.ConfigStore, namespace string) (string, string, error) { 1609 gitOpsDir := "" 1610 gitOpsEnvDir := "" 1611 if options.Flags.GitOpsMode { 1612 var err error 1613 if options.Flags.Dir == "" { 1614 options.Flags.Dir, err = util.ConfigDir() 1615 if err != nil { 1616 return "", "", err 1617 } 1618 } 1619 1620 envName := fmt.Sprintf("environment-%s-dev", options.Flags.DefaultEnvironmentPrefix) 1621 gitOpsDir = filepath.Join(options.Flags.Dir, envName) 1622 gitOpsEnvDir = filepath.Join(gitOpsDir, "env") 1623 templatesDir := filepath.Join(gitOpsEnvDir, "templates") 1624 err = os.MkdirAll(templatesDir, util.DefaultWritePermissions) 1625 if err != nil { 1626 return "", "", errors.Wrapf(err, "Failed to make GitOps templates directory %s", templatesDir) 1627 } 1628 1629 options.ModifyDevEnvironmentFn = func(callback func(env *v1.Environment) error) error { 1630 defaultEnv := kube.CreateDefaultDevEnvironment(namespace) 1631 _, err := gitOpsModifyEnvironment(templatesDir, kube.LabelValueDevEnvironment, defaultEnv, configStore, callback) 1632 return err 1633 } 1634 options.ModifyEnvironmentFn = func(name string, callback func(env *v1.Environment) error) error { 1635 defaultEnv := &v1.Environment{} 1636 defaultEnv.Labels = map[string]string{} 1637 _, err := gitOpsModifyEnvironment(templatesDir, name, defaultEnv, configStore, callback) 1638 return err 1639 } 1640 options.InitOptions.ModifyDevEnvironmentFn = options.ModifyDevEnvironmentFn 1641 options.modifyConfigMapCallback = func(name string, callback func(configMap *core_v1.ConfigMap) error) (*core_v1.ConfigMap, error) { 1642 return gitOpsModifyConfigMap(templatesDir, name, nil, configStore, callback) 1643 } 1644 options.modifySecretCallback = func(name string, callback func(secret *core_v1.Secret) error) (*core_v1.Secret, error) { 1645 if options.Flags.Vault { 1646 _, devNamespace, err := options.KubeClientAndDevNamespace() 1647 if err != nil { 1648 return nil, errors.Wrap(err, "getting team's dev namesapces") 1649 } 1650 vaultClient, err := options.SystemVaultClient(devNamespace) 1651 if err != nil { 1652 return nil, errors.Wrapf(err, "retrieving the system vault client in namespace %s", devNamespace) 1653 } 1654 vaultConfigStore := configio.NewVaultStore(vaultClient, vault.GitOpsSecretsPath) 1655 return gitOpsModifySecret(vault.GitOpsTemplatesPath, name, nil, vaultConfigStore, callback) 1656 } 1657 return gitOpsModifySecret(templatesDir, name, nil, configStore, callback) 1658 } 1659 } 1660 1661 return gitOpsDir, gitOpsEnvDir, nil 1662 } 1663 1664 func (options *InstallOptions) generateGitOpsDevEnvironmentConfig(gitOpsDir string) (string, error) { 1665 if options.Flags.GitOpsMode { 1666 log.Logger().Infof("\n\nGenerated the source code for the GitOps development environment at %s", util.ColorInfo(gitOpsDir)) 1667 log.Logger().Infof("You can apply this to the kubernetes cluster at any time in this directory via: %s\n", util.ColorInfo("jx step env apply")) 1668 1669 if !options.Flags.NoGitOpsEnvRepo { 1670 authConfigSvc, err := options.GitAuthConfigService() 1671 if err != nil { 1672 return "", errors.Wrap(err, "creating git auth config service") 1673 } 1674 config := &v1.Environment{ 1675 Spec: v1.EnvironmentSpec{ 1676 Label: "Development", 1677 PromotionStrategy: v1.PromotionStrategyTypeNever, 1678 Kind: v1.EnvironmentKindTypeDevelopment, 1679 }, 1680 } 1681 config.Name = kube.LabelValueDevEnvironment 1682 var devEnv *v1.Environment 1683 err = options.ModifyDevEnvironment(func(env *v1.Environment) error { 1684 devEnv = env 1685 devEnv.Spec.TeamSettings.UseGitOps = true 1686 return nil 1687 }) 1688 if err != nil { 1689 return "", errors.Wrap(err, "modifying the dev environment configuration") 1690 } 1691 envDir, err := util.EnvironmentsDir() 1692 if err != nil { 1693 return "", errors.Wrap(err, "getting the environments directory") 1694 } 1695 forkEnvGitURL := "" 1696 prefix := options.Flags.DefaultEnvironmentPrefix 1697 1698 git := options.Git() 1699 gitRepoOptions, err := options.buildGitRepositoryOptionsForEnvironments() 1700 if err != nil || gitRepoOptions == nil { 1701 if err == nil { 1702 err = errors.New("empty git repository options") 1703 } 1704 return "", errors.Wrap(err, "building the git repository options for environment") 1705 } 1706 repo, gitProvider, err := kube.CreateEnvGitRepository(options.BatchMode, authConfigSvc, devEnv, devEnv, config, forkEnvGitURL, envDir, 1707 gitRepoOptions, options.CreateEnvOptions.HelmValuesConfig, prefix, git, options.ResolveChartMuseumURL, options.GetIOFileHandles()) 1708 if err != nil || repo == nil || gitProvider == nil { 1709 return "", errors.Wrap(err, "creating git repository for the dev environment source") 1710 } 1711 1712 dir := gitOpsDir 1713 err = git.Init(dir) 1714 if err != nil { 1715 return "", errors.Wrap(err, "initializing the dev environment repository") 1716 } 1717 err = options.ModifyDevEnvironment(func(env *v1.Environment) error { 1718 env.Spec.Source.URL = repo.CloneURL 1719 env.Spec.Source.Ref = "master" 1720 return nil 1721 }) 1722 if err != nil { 1723 return "", errors.Wrap(err, "updating the source in the dev environment") 1724 } 1725 1726 err = git.Add(dir, ".gitignore") 1727 if err != nil { 1728 return "", errors.Wrap(err, "adding gitignore to the dev environment") 1729 } 1730 err = git.Add(dir, "*") 1731 if err != nil { 1732 return "", errors.Wrap(err, "adding all files from dev environment repo to git") 1733 } 1734 err = options.Git().CommitIfChanges(dir, "Initial import of Dev Environment source") 1735 if err != nil { 1736 return "", errors.Wrap(err, "committing in git if there are changes") 1737 } 1738 userAuth := gitProvider.UserAuth() 1739 pushGitURL, err := git.CreateAuthenticatedURL(repo.CloneURL, &userAuth) 1740 if err != nil { 1741 return "", errors.Wrapf(err, "creating push URL for %q", repo.CloneURL) 1742 } 1743 err = git.SetRemoteURL(dir, "origin", pushGitURL) 1744 if err != nil { 1745 return "", errors.Wrapf(err, "setting remote origin to %q", pushGitURL) 1746 } 1747 err = git.PushMaster(dir) 1748 if err != nil { 1749 return "", errors.Wrapf(err, "pushing master from repository %q", dir) 1750 } 1751 log.Logger().Infof("Pushed Git repository to %s\n", util.ColorInfo(repo.HTMLURL)) 1752 1753 dir = filepath.Join(envDir, gitRepoOptions.Owner) 1754 if _, err := os.Stat(dir); os.IsNotExist(err) { 1755 if err := os.MkdirAll(dir, 0755); err != nil { 1756 return "", errors.Wrapf(err, "creating directory %q", dir) 1757 } 1758 } 1759 dir = filepath.Join(dir, repo.Name) 1760 if err := util.RenameDir(gitOpsDir, dir, true); err != nil { 1761 return "", errors.Wrap(err, "renaming dev environment dir") 1762 } 1763 return filepath.Join(dir, "env"), nil 1764 } 1765 } 1766 1767 return "", nil 1768 } 1769 1770 func (options *InstallOptions) applyGitOpsDevEnvironmentConfig(gitOpsEnvDir string, namespace string) error { 1771 if options.Flags.GitOpsMode && !options.Flags.NoGitOpsEnvApply { 1772 applyEnv := true 1773 if !options.BatchMode { 1774 if answer, err := util.Confirm("Would you like to setup the Development Environment from the source code now?", true, "Do you want to apply the development environment helm charts now?", options.GetIOFileHandles()); err != nil { 1775 return err 1776 } else if !answer { 1777 applyEnv = false 1778 } 1779 } 1780 1781 if applyEnv { 1782 // Reset the secret location cached in memory before creating the dev 1783 // environment. The location might have been changed in the cluster configuration. 1784 err := options.ResetSecretsLocation() 1785 if err != nil { 1786 return errors.Wrap(err, "unable to reset the secret location in memory") 1787 } 1788 1789 envApplyOptions := &env.StepEnvApplyOptions{ 1790 StepEnvOptions: env.StepEnvOptions{ 1791 StepOptions: step.StepOptions{ 1792 CommonOptions: options.CommonOptions, 1793 }, 1794 }, 1795 Dir: gitOpsEnvDir, 1796 Namespace: namespace, 1797 ChangeNs: true, 1798 Vault: options.Flags.Vault, 1799 ReleaseName: "jenkins-x", 1800 } 1801 1802 err = envApplyOptions.Run() 1803 if err != nil { 1804 return errors.Wrap(err, "applying the dev environment configuration") 1805 } 1806 } 1807 } 1808 1809 return nil 1810 } 1811 1812 func (options *InstallOptions) setupGitOpsPostApply(ns string) error { 1813 if options.Flags.GitOpsMode && !options.Flags.NoGitOpsEnvSetup { 1814 if !options.Flags.Prow { 1815 err := options.configureJenkins(ns) 1816 if err != nil { 1817 return errors.Wrap(err, "configuring Jenkins") 1818 } 1819 } else { 1820 client, devNamespace, err := options.KubeClientAndDevNamespace() 1821 1822 settings, err := options.TeamSettings() 1823 if err != nil { 1824 return errors.Wrap(err, "reading the team settings") 1825 } 1826 1827 err = prow.AddDummyApplication(client, devNamespace, settings) 1828 if err != nil { 1829 return errors.Wrap(err, "adding dummy application") 1830 } 1831 } 1832 1833 jxClient, devNs, err := options.JXClientAndDevNamespace() 1834 if err != nil { 1835 return errors.Wrap(err, "getting jx client and dev namesapce") 1836 } 1837 1838 envs, err := kube.GetPermanentEnvironments(jxClient, devNs) 1839 if err != nil { 1840 return errors.Wrapf(err, "retrieving the current permanent environments in namespace %q", devNs) 1841 } 1842 devEnv, err := kube.GetDevEnvironment(jxClient, devNs) 1843 if err != nil { 1844 return errors.Wrapf(err, "get the dev environment namespace %q", devNs) 1845 } 1846 if devEnv != nil { 1847 envs = append(envs, devEnv) 1848 } 1849 1850 errs := []error{} 1851 createEnvOpts := CreateEnvOptions{ 1852 CreateOptions: createoptions.CreateOptions{ 1853 CommonOptions: options.CommonOptions, 1854 }, 1855 Prefix: options.Flags.DefaultEnvironmentPrefix, 1856 Prow: options.Flags.Prow, 1857 } 1858 if options.BatchMode { 1859 createEnvOpts.BatchMode = options.BatchMode 1860 } 1861 for _, env := range envs { 1862 err := createEnvOpts.RegisterEnvironment(env, nil, nil) 1863 if err != nil { 1864 errs = append(errs, errors.Wrapf(err, "registering environment %q", env.GetName())) 1865 } 1866 log.Logger().Infof("Registered environment %s", util.ColorInfo(env.GetName())) 1867 } 1868 return errorutil.CombineErrors(errs...) 1869 } 1870 return nil 1871 } 1872 1873 func (options *InstallOptions) installHelmBinaries() error { 1874 initOpts := &options.InitOptions 1875 helmBinary := initOpts.HelmBinary() 1876 dependencies := []string{} 1877 if !initOpts.Flags.RemoteTiller && !initOpts.Flags.NoTiller { 1878 binDir, err := util.JXBinLocation() 1879 if err != nil { 1880 return errors.Wrap(err, "reading jx bin location") 1881 } 1882 install, err := packages.ShouldInstallBinary("tiller") 1883 if !install && err == nil { 1884 confirm := &survey.Confirm{ 1885 Message: "Uninstalling existing tiller binary:", 1886 Default: true, 1887 } 1888 flag := true 1889 err = survey.AskOne(confirm, &flag, nil) 1890 if err != nil || flag == false { 1891 return errors.New("Existing tiller must be uninstalled first in order to use the jx in tiller less mode") 1892 } 1893 // Uninstall helm and tiller first to avoid using some older version 1894 err = packages.UninstallBinary(binDir, "tiller") 1895 if err != nil { 1896 return errors.Wrap(err, "uninstalling existing tiller binary") 1897 } 1898 } 1899 1900 install, err = packages.ShouldInstallBinary(helmBinary) 1901 if !install && err == nil { 1902 confirm := &survey.Confirm{ 1903 Message: "Uninstalling existing helm binary:", 1904 Default: true, 1905 } 1906 flag := true 1907 err = survey.AskOne(confirm, &flag, nil) 1908 if err != nil || flag == false { 1909 return errors.New("Existing helm must be uninstalled first in order to use the jx in tiller less mode") 1910 } 1911 // Uninstall helm and tiller first to avoid using some older version 1912 err = packages.UninstallBinary(binDir, helmBinary) 1913 if err != nil { 1914 return errors.Wrap(err, "uninstalling existing helm binary") 1915 } 1916 } 1917 dependencies = append(dependencies, "tiller") 1918 options.Helm().SetHost(helm.GetTillerAddress()) 1919 } 1920 dependencies = append(dependencies, helmBinary) 1921 return options.InstallMissingDependencies(dependencies) 1922 } 1923 1924 // SetInstallValues sets the install values 1925 func (options *InstallOptions) SetInstallValues(values map[string]string) { 1926 if values != nil { 1927 if options.installValues == nil { 1928 options.installValues = map[string]string{} 1929 } 1930 for k, v := range values { 1931 options.installValues[k] = v 1932 } 1933 } 1934 } 1935 1936 func (options *InstallOptions) configureCloudProviderPreInit(client kubernetes.Interface) error { 1937 switch options.Flags.Provider { 1938 case cloud.AKS: 1939 err := options.CreateClusterAdmin() 1940 if err != nil { 1941 return errors.Wrap(err, "creating cluster admin for AKS cloud provider") 1942 } 1943 log.Logger().Info("created role cluster-admin") 1944 case cloud.AWS: 1945 fallthrough 1946 case cloud.EKS: 1947 err := options.ensureDefaultStorageClass(client, "gp2", "kubernetes.io/aws-ebs", "gp2") 1948 if err != nil { 1949 return errors.Wrap(err, "ensuring default storage for EKS/AWS cloud provider") 1950 } 1951 default: 1952 return nil 1953 } 1954 return nil 1955 } 1956 1957 func (options *InstallOptions) configureCloudProivderPostInit(client kubernetes.Interface, namespace string) error { 1958 switch options.Flags.Provider { 1959 case cloud.OPENSHIFT: 1960 err := options.enableOpenShiftSCC(namespace) 1961 if err != nil { 1962 return errors.Wrap(err, "failed to enable the OpenShiftSCC") 1963 } 1964 case cloud.IKS: 1965 _, err := options.AddHelmBinaryRepoIfMissing(DEFAULT_IBMREPO_URL, "ibm", "", "") 1966 if err != nil { 1967 return errors.Wrap(err, "failed to add the IBM helm repo") 1968 } 1969 err = options.Helm().UpdateRepo() 1970 if err != nil { 1971 return errors.Wrap(err, "failed to update the helm repo") 1972 } 1973 helmOptions := helm.InstallChartOptions{ 1974 Chart: "ibm/ibmcloud-block-storage-plugin", 1975 ReleaseName: "ibmcloud-block-storage-plugin", 1976 NoForce: true, 1977 } 1978 err = options.InstallChartWithOptions(helmOptions) 1979 if err != nil { 1980 return errors.Wrap(err, "failed to install/upgrade the IBM Cloud Block Storage drivers") 1981 } 1982 return options.changeDefaultStorageClass(client, "ibmc-block-bronze") 1983 default: 1984 return nil 1985 } 1986 1987 return nil 1988 } 1989 1990 func (options *InstallOptions) configureDockerRegistry(client kubernetes.Interface, namespace string) error { 1991 helmConfig := &options.CreateEnvOptions.HelmValuesConfig 1992 dockerRegistryConfig, dockerRegistry, err := options.configureCloudProviderRegistry(client, namespace) 1993 if err != nil { 1994 return errors.Wrap(err, "configure cloud provider docker registry") 1995 } 1996 if dockerRegistryConfig != "" { 1997 helmConfig.PipelineSecrets.DockerConfig = dockerRegistryConfig 1998 } 1999 if dockerRegistry != "" { 2000 if !options.Flags.Prow { 2001 if helmConfig.Jenkins.Servers.Global.EnvVars == nil { 2002 helmConfig.Jenkins.Servers.Global.EnvVars = map[string]string{} 2003 } 2004 helmConfig.Jenkins.Servers.Global.EnvVars["DOCKER_REGISTRY"] = dockerRegistry 2005 } else { 2006 helmConfig.DockerRegistry = dockerRegistry 2007 } 2008 } 2009 return nil 2010 } 2011 2012 func (options *InstallOptions) configureCloudProviderRegistry(client kubernetes.Interface, namespace string) (string, string, error) { 2013 dockerRegistry, err := options.dockerRegistryValue() 2014 if err != nil { 2015 return "", "", err 2016 } 2017 kubeConfig, _, err := options.Kube().LoadConfig() 2018 if err != nil { 2019 return "", "", err 2020 } 2021 switch options.Flags.Provider { 2022 case cloud.AKS: 2023 server := kube.CurrentServer(kubeConfig) 2024 azureCLI := aks.NewAzureRunner() 2025 resourceGroup, name, cluster, err := azureCLI.GetClusterClient(server) 2026 if err != nil { 2027 return "", "", errors.Wrap(err, "getting cluster from Azure") 2028 } 2029 registryID := "" 2030 config, dockerRegistry, registryID, err := azureCLI.GetRegistry(options.Flags.AzureRegistrySubscription, resourceGroup, name, dockerRegistry) 2031 if err != nil { 2032 return "", "", errors.Wrap(err, "getting registry configuration from Azure") 2033 } 2034 azureCLI.AssignRole(cluster, registryID) 2035 log.Logger().Infof("Assign AKS %s a reader role for ACR %s", util.ColorInfo(server), util.ColorInfo(dockerRegistry)) 2036 return config, dockerRegistry, nil 2037 case cloud.IKS: 2038 dockerRegistry = iks.GetClusterRegistry(client) 2039 config, err := iks.GetRegistryConfigJSON(dockerRegistry) 2040 if err != nil { 2041 return "", "", errors.Wrap(err, "getting IKS registry configuration") 2042 } 2043 return config, dockerRegistry, nil 2044 case cloud.OPENSHIFT: 2045 if dockerRegistry == "docker-registry.default.svc:5000" { 2046 config, err := options.enableOpenShiftRegistryPermissions(namespace, dockerRegistry) 2047 if err != nil { 2048 return "", "", errors.Wrap(err, "enabling OpenShift registry permissions") 2049 } 2050 return config, dockerRegistry, nil 2051 } 2052 } 2053 2054 helmConfig := &options.CreateEnvOptions.HelmValuesConfig 2055 return helmConfig.PipelineSecrets.DockerConfig, dockerRegistry, nil 2056 } 2057 2058 func (options *InstallOptions) registerAllCRDs() error { 2059 if !options.GitOpsMode { 2060 apisClient, err := options.ApiExtensionsClient() 2061 if err != nil { 2062 return errors.Wrap(err, "failed to create the API extensions client") 2063 } 2064 err = kube.RegisterAllCRDs(apisClient) 2065 if err != nil { 2066 return err 2067 } 2068 } 2069 return nil 2070 } 2071 2072 func (options *InstallOptions) installCloudProviderDependencies() error { 2073 dependencies := []string{} 2074 err := options.InstallRequirements(options.Flags.Provider, dependencies...) 2075 if err != nil { 2076 return errors.Wrap(err, "installing cloud provider dependencies") 2077 } 2078 return nil 2079 } 2080 2081 func (options *InstallOptions) getAdminSecrets(configStore configio.ConfigStore, providerEnvDir string, cloudEnvironmentSecretsLocation string) (string, *config.AdminSecretsConfig, error) { 2082 cloudEnvironmentSopsLocation := filepath.Join(providerEnvDir, opts.CloudEnvSopsConfigFile) 2083 if _, err := os.Stat(providerEnvDir); os.IsNotExist(err) { 2084 return "", nil, fmt.Errorf("cloud environment dir %s not found", providerEnvDir) 2085 } 2086 sopsFileExists, err := util.FileExists(cloudEnvironmentSopsLocation) 2087 if err != nil { 2088 return "", nil, errors.Wrap(err, "failed to look for "+cloudEnvironmentSopsLocation) 2089 } 2090 2091 adminSecretsServiceInit := false 2092 2093 if sopsFileExists { 2094 log.Logger().Infof("Attempting to decrypt secrets file %s", util.ColorInfo(cloudEnvironmentSecretsLocation)) 2095 // need to decrypt secrets now 2096 err = options.Helm().DecryptSecrets(cloudEnvironmentSecretsLocation) 2097 if err != nil { 2098 return "", nil, errors.Wrap(err, "failed to decrypt "+cloudEnvironmentSecretsLocation) 2099 } 2100 2101 cloudEnvironmentSecretsDecryptedLocation := filepath.Join(providerEnvDir, opts.CloudEnvSecretsFile+".dec") 2102 decryptedSecretsFile, err := util.FileExists(cloudEnvironmentSecretsDecryptedLocation) 2103 if err != nil { 2104 return "", nil, errors.Wrap(err, "failed to look for "+cloudEnvironmentSecretsDecryptedLocation) 2105 } 2106 2107 if decryptedSecretsFile { 2108 log.Logger().Infof("Successfully decrypted %s", util.ColorInfo(cloudEnvironmentSecretsDecryptedLocation)) 2109 cloudEnvironmentSecretsLocation = cloudEnvironmentSecretsDecryptedLocation 2110 2111 err = options.AdminSecretsService.NewAdminSecretsConfigFromSecret(cloudEnvironmentSecretsDecryptedLocation) 2112 if err != nil { 2113 return "", nil, errors.Wrap(err, "failed to create the admin secret config service from the decrypted secrets file") 2114 } 2115 adminSecretsServiceInit = true 2116 } 2117 } 2118 2119 if !adminSecretsServiceInit { 2120 err = options.AdminSecretsService.NewAdminSecretsConfig() 2121 if err != nil { 2122 return "", nil, errors.Wrap(err, "failed to create the admin secret config service") 2123 } 2124 } 2125 2126 dir, err := util.ConfigDir() 2127 if err != nil { 2128 return "", nil, errors.Wrap(err, "creating a temporary config dir for Git credentials") 2129 } 2130 2131 adminSecrets := &options.AdminSecretsService.Secrets 2132 adminSecretsFileName := filepath.Join(dir, opts.AdminSecretsFile) 2133 err = configStore.WriteObject(adminSecretsFileName, adminSecrets) 2134 if err != nil { 2135 return "", nil, errors.Wrapf(err, "writing the admin secrets in the secrets file '%s'", adminSecretsFileName) 2136 } 2137 2138 if options.Flags.Vault { 2139 // lets make sure the devNamespace hasn't been overwritten to "default" 2140 if options.Flags.Namespace != "" { 2141 options.SetDevNamespace(options.Flags.Namespace) 2142 } 2143 err := options.storeAdminCredentialsInVault(&options.AdminSecretsService) 2144 if err != nil { 2145 return "", nil, errors.Wrapf(err, "storing the admin credentials in vault") 2146 } 2147 } 2148 2149 return adminSecretsFileName, adminSecrets, nil 2150 } 2151 2152 // ConfigureKaniko configures the kaniko SA and secret 2153 func (options *InstallOptions) ConfigureKaniko() error { 2154 if options.Flags.Kaniko { 2155 if options.Flags.Provider != cloud.GKE { 2156 log.Logger().Infof("we are assuming your IAM roles are setup so that Kaniko can push images to your docker registry\n") 2157 return nil 2158 } 2159 2160 serviceAccountDir, err := ioutil.TempDir("", "gke") 2161 if err != nil { 2162 return errors.Wrap(err, "creating a temporary folder where the service account will be stored") 2163 } 2164 defer os.RemoveAll(serviceAccountDir) //nolint:errcheck 2165 2166 clusterName := options.installValues[kube.ClusterName] 2167 projectID := options.installValues[kube.ProjectID] 2168 if projectID == "" || clusterName == "" { 2169 if kubeClient, ns, err := options.KubeClientAndDevNamespace(); err == nil { 2170 if data, err := kube.ReadInstallValues(kubeClient, ns); err == nil && data != nil { 2171 if projectID == "" { 2172 projectID = data[kube.ProjectID] 2173 } 2174 if clusterName == "" { 2175 clusterName = data[kube.ClusterName] 2176 } 2177 } 2178 } 2179 } 2180 if projectID == "" { 2181 projectID, err = options.GetGoogleProjectID("") 2182 if err != nil { 2183 return errors.Wrap(err, "getting the GCP project ID") 2184 } 2185 } 2186 if clusterName == "" { 2187 clusterName, err = options.GetGKEClusterNameFromContext() 2188 if err != nil { 2189 return errors.Wrap(err, "getting the GKE cluster name from current context") 2190 } 2191 } 2192 2193 serviceAccountName := naming.ToValidGCPServiceAccount(fmt.Sprintf("%s-ko", clusterName)) 2194 log.Logger().Infof("Configuring Kaniko service account %s for project %s", util.ColorInfo(serviceAccountName), util.ColorInfo(projectID)) 2195 serviceAccountPath, err := options.GCloud().GetOrCreateServiceAccount(serviceAccountName, projectID, serviceAccountDir, gke.KanikoServiceAccountRoles) 2196 if err != nil { 2197 return errors.Wrap(err, "creating the service account") 2198 } 2199 2200 serviceAccount, err := ioutil.ReadFile(serviceAccountPath) 2201 if err != nil { 2202 return errors.Wrapf(err, "reading the service account from file '%s'", serviceAccountPath) 2203 } 2204 2205 options.AdminSecretsService.Flags.KanikoSecret = string(serviceAccount) 2206 2207 } 2208 return nil 2209 } 2210 2211 func (options *InstallOptions) createSystemVault(client kubernetes.Interface, namespace string, ic *kube.IngressConfig) error { 2212 if options.Flags.GitOpsMode && !options.Flags.NoGitOpsVault || options.Flags.Vault { 2213 if options.Flags.Provider != cloud.GKE && options.Flags.Provider != cloud.EKS && options.Flags.Provider != cloud.AWS { 2214 return fmt.Errorf("system vault is not supported for %s provider", options.Flags.Provider) 2215 } 2216 2217 if options.installValues == nil { 2218 return errors.New("no install values provided") 2219 } 2220 2221 // Configure the vault flag if only GitOps mode is on 2222 options.Flags.Vault = true 2223 2224 vaultOperatorClient, err := options.VaultOperatorClient() 2225 if err != nil { 2226 return err 2227 } 2228 2229 systemVaultName, err := kubevault.SystemVaultName(options.Kube()) 2230 if err != nil { 2231 return errors.Wrap(err, "building the system vault name from cluster name") 2232 } 2233 2234 options.installValues[vault.SystemVaultName] = systemVaultName 2235 2236 if kubevault.FindVault(vaultOperatorClient, systemVaultName, namespace) { 2237 log.Logger().Infof("System vault named %s in namespace %s already exists", 2238 util.ColorInfo(systemVaultName), util.ColorInfo(namespace)) 2239 } else { 2240 log.Logger().Info("Creating new system vault") 2241 2242 resolver, err := options.CreateVersionResolver("", "") 2243 if err != nil { 2244 return errors.Wrap(err, "creating the docker image version resolver") 2245 } 2246 2247 err = options.installOperator(resolver, namespace) 2248 if err != nil { 2249 return errors.Wrap(err, "installing Vault operator") 2250 } 2251 2252 vaultCreateParam := create.VaultCreationParam{ 2253 VaultName: systemVaultName, 2254 Namespace: namespace, 2255 ClusterName: options.installValues[kube.ClusterName], 2256 SecretsPathPrefix: pkgvault.DefaultSecretsPathPrefix, 2257 KubeProvider: options.Flags.Provider, 2258 KubeClient: client, 2259 VaultOperatorClient: vaultOperatorClient, 2260 VersionResolver: *resolver, 2261 FileHandles: options.GetIOFileHandles(), 2262 CreateCloudResources: true, 2263 Boot: false, 2264 BatchMode: true, 2265 } 2266 2267 if options.Flags.Provider == cloud.GKE { 2268 gkeParam := &create.GKEParam{ 2269 ProjectID: options.installValues[kube.ProjectID], 2270 Zone: options.installValues[kube.Zone], 2271 } 2272 vaultCreateParam.GKE = gkeParam 2273 } 2274 2275 if options.Flags.Provider == cloud.EKS { 2276 awsParam, err := options.createAWSParam(options.installValues[kube.Region]) 2277 if err != nil { 2278 return errors.Wrap(err, "unable to create Vault creation parameter from requirements") 2279 } 2280 vaultCreateParam.AWS = &awsParam 2281 } 2282 2283 vaultCreator := create.NewVaultCreator() 2284 err = vaultCreator.CreateOrUpdateVault(vaultCreateParam) 2285 if err != nil { 2286 return errors.Wrap(err, "unable to create/update Vault") 2287 } 2288 2289 err = options.exposeVault(systemVaultName, namespace, ic) 2290 if err != nil { 2291 return errors.Wrap(err, "unable to expose Vault") 2292 } 2293 2294 log.Logger().Infof("System vault created named %s in namespace %s.", 2295 util.ColorInfo(systemVaultName), util.ColorInfo(namespace)) 2296 } 2297 2298 // Make sure that the dev namespace wasn't overwritten 2299 options.SetDevNamespace(namespace) 2300 2301 err = options.SetSecretsLocation(secrets.VaultLocationKind, false) 2302 if err != nil { 2303 return errors.Wrap(err, "setting the secrets location as vault") 2304 } 2305 } 2306 return nil 2307 } 2308 2309 func (options *InstallOptions) installOperator(resolver *versionstream.VersionResolver, ns string) error { 2310 tag, err := options.vaultOperatorImageTag(resolver) 2311 if err != nil { 2312 return errors.Wrap(err, "unable to determine Vault operator version") 2313 } 2314 2315 values := []string{ 2316 "image.repository=" + kubevault.VaultOperatorImage, 2317 "image.tag=" + tag, 2318 } 2319 log.Logger().Infof("Installing %s operator with helm values: %v", util.ColorInfo(kube.DefaultVaultOperatorReleaseName), util.ColorInfo(values)) 2320 2321 helmOptions := helm.InstallChartOptions{ 2322 Chart: kube.ChartVaultOperator, 2323 ReleaseName: kube.DefaultVaultOperatorReleaseName, 2324 Version: options.Version, 2325 Ns: ns, 2326 SetValues: values, 2327 } 2328 err = options.InstallChartWithOptions(helmOptions) 2329 if err != nil { 2330 return errors.Wrap(err, "unable to install vault operator") 2331 } 2332 2333 log.Logger().Infof("Vault operator installed in namespace %s", ns) 2334 return nil 2335 } 2336 2337 // vaultOperatorImageTag lookups the vault operator image tag in the version stream 2338 func (options *InstallOptions) vaultOperatorImageTag(resolver *versionstream.VersionResolver) (string, error) { 2339 fullImage, err := resolver.ResolveDockerImage(kubevault.VaultOperatorImage) 2340 if err != nil { 2341 return "", errors.Wrapf(err, "looking up the vault-operator %q image into the version stream", 2342 kubevault.VaultOperatorImage) 2343 } 2344 parts := strings.Split(fullImage, ":") 2345 if len(parts) != 2 { 2346 return "", fmt.Errorf("no tag found for image %q in version stream", kubevault.VaultOperatorImage) 2347 } 2348 return parts[1], nil 2349 } 2350 2351 func (options *InstallOptions) createAWSParam(defaultRegion string) (create.AWSParam, error) { 2352 if defaultRegion == "" { 2353 return create.AWSParam{}, errors.New("unable to find cluster region in requirements") 2354 } 2355 2356 dynamoDBRegion := options.DynamoDBRegion 2357 if dynamoDBRegion == "" { 2358 dynamoDBRegion = defaultRegion 2359 log.Logger().Infof("Region not specified for DynamoDB, defaulting to %s", util.ColorInfo(defaultRegion)) 2360 } 2361 2362 kmsRegion := options.KMSRegion 2363 if kmsRegion == "" { 2364 kmsRegion = defaultRegion 2365 log.Logger().Infof("Region not specified for KMS, defaulting to %s", util.ColorInfo(defaultRegion)) 2366 2367 } 2368 2369 s3Region := options.S3Region 2370 if s3Region == "" { 2371 s3Region = defaultRegion 2372 log.Logger().Infof("Region not specified for S3, defaulting to %s", util.ColorInfo(defaultRegion)) 2373 } 2374 2375 awsParam := create.AWSParam{ 2376 IAMUsername: options.ProvidedIAMUsername, 2377 S3Bucket: options.S3Bucket, 2378 S3Region: s3Region, 2379 S3Prefix: options.S3Prefix, 2380 DynamoDBTable: options.DynamoDBTable, 2381 DynamoDBRegion: dynamoDBRegion, 2382 KMSKeyID: options.KMSKeyID, 2383 KMSRegion: kmsRegion, 2384 AccessKeyID: options.AccessKeyID, 2385 SecretAccessKey: options.SecretAccessKey, 2386 AutoCreate: options.AutoCreate, 2387 } 2388 2389 return awsParam, nil 2390 } 2391 2392 func (options *InstallOptions) exposeVault(vaultService string, namespace string, ic *kube.IngressConfig) error { 2393 client, err := options.KubeClient() 2394 if err != nil { 2395 return err 2396 } 2397 svc, err := client.CoreV1().Services(namespace).Get(vaultService, metav1.GetOptions{}) 2398 if err != nil { 2399 return errors.Wrapf(err, "getting the vault service: %s", vaultService) 2400 } 2401 if svc.Annotations == nil { 2402 svc.Annotations = map[string]string{} 2403 } 2404 if svc.Annotations[kube.AnnotationExpose] == "" { 2405 svc.Annotations[kube.AnnotationExpose] = "true" 2406 svc.Annotations[kube.AnnotationExposePort] = vault.DefaultVaultPort 2407 svc, err = client.CoreV1().Services(namespace).Update(svc) 2408 if err != nil { 2409 return errors.Wrapf(err, "updating %s service annotations", vaultService) 2410 } 2411 } 2412 2413 upgradeIngOpts := &upgrade.UpgradeIngressOptions{ 2414 CommonOptions: options.CommonOptions, 2415 Namespaces: []string{namespace}, 2416 Services: []string{vaultService}, 2417 IngressConfig: *ic, 2418 SkipResourcesUpdate: true, 2419 WaitForCerts: true, 2420 } 2421 return upgradeIngOpts.Run() 2422 } 2423 2424 func (options *InstallOptions) storeSecretYamlFilesInVault(path string, files ...string) error { 2425 _, devNamespace, err := options.KubeClientAndDevNamespace() 2426 if err != nil { 2427 return errors.Wrap(err, "getting team's dev namespace") 2428 } 2429 vaultClient, err := options.SystemVaultClient(devNamespace) 2430 if err != nil { 2431 return errors.Wrapf(err, "retrieving the system vault client in namespace %s", devNamespace) 2432 } 2433 2434 err = vault.WriteYamlFiles(vaultClient, path, files...) 2435 if err != nil { 2436 return errors.Wrapf(err, "storing in vault the secret YAML files: %s", strings.Join(files, ",")) 2437 } 2438 2439 return nil 2440 } 2441 2442 func (options *InstallOptions) storeAdminCredentialsInVault(svc *config.AdminSecretsService) error { 2443 _, devNamespace, err := options.KubeClientAndDevNamespace() 2444 if err != nil { 2445 return errors.Wrap(err, "getting team's dev namespace") 2446 } 2447 vaultClient, err := options.SystemVaultClient(devNamespace) 2448 if err != nil { 2449 return errors.Wrapf(err, "retrieving the system vault client in namespace %s", devNamespace) 2450 } 2451 secrets := map[vault.AdminSecret]config.BasicAuth{ 2452 vault.JenkinsAdminSecret: svc.JenkinsAuth(), 2453 vault.IngressAdminSecret: svc.IngressAuth(), 2454 vault.ChartmuseumAdminSecret: svc.ChartMuseumAuth(), 2455 vault.GrafanaAdminSecret: svc.GrafanaAuth(), 2456 vault.NexusAdminSecret: svc.NexusAuth(), 2457 } 2458 for secretName, secret := range secrets { 2459 path := vault.AdminSecretPath(secretName) 2460 err := vault.WriteBasicAuth(vaultClient, path, secret) 2461 if err != nil { 2462 return errors.Wrapf(err, "storing in vault the basic auth credentials for %s", secretName) 2463 } 2464 } 2465 return nil 2466 } 2467 2468 func (options *InstallOptions) configureBuildPackMode() error { 2469 ebp := &edit.EditBuildPackOptions{ 2470 BuildPackName: options.Flags.BuildPackName, 2471 } 2472 ebp.CommonOptions = options.CommonOptions 2473 2474 return ebp.Run() 2475 } 2476 2477 func (options *InstallOptions) configureLongTermStorageBucket() error { 2478 2479 if options.IsConfigExplicitlySet("install", longTermStorageFlagName) && !options.Flags.LongTermStorage { 2480 return nil 2481 } 2482 2483 if !options.BatchMode && !options.Flags.LongTermStorage { 2484 if options.AdvancedMode { 2485 surveyOpts := survey.WithStdio(options.In, options.Out, options.Err) 2486 confirm := &survey.Confirm{ 2487 Message: fmt.Sprintf("Would you like to enable long term logs storage?"+ 2488 " A bucket for provider %s will be created", options.Flags.Provider), 2489 Default: true, 2490 } 2491 err := survey.AskOne(confirm, &options.Flags.LongTermStorage, nil, surveyOpts) 2492 if err != nil { 2493 return errors.Wrap(err, "asking to enable Long Term Storage") 2494 } 2495 } else { 2496 if options.Flags.Provider == cloud.GKE { 2497 options.Flags.LongTermStorage = true 2498 log.Logger().Infof(util.QuestionAnswer("Default enabling long term logs storage", util.YesNo(options.Flags.LongTermStorage))) 2499 } else { 2500 options.Flags.LongTermStorage = false 2501 log.Logger().Debugf("Long Term Storage not supported by provider '%s', disabling this option", options.Flags.Provider) 2502 } 2503 } 2504 } else { 2505 log.Logger().Infof(util.QuestionAnswer("Configured to use long term logs storage", util.YesNo(options.Flags.LongTermStorage))) 2506 } 2507 2508 if options.Flags.LongTermStorage { 2509 2510 var bucketURL string 2511 switch strings.ToUpper(options.Flags.Provider) { 2512 case "GKE": 2513 err := options.ensureGKEInstallValuesAreFilled() 2514 if err != nil { 2515 return errors.Wrap(err, "filling install values with cluster information") 2516 } 2517 bucketURL, err = gkeStorage.EnableLongTermStorage(options.GCloud(), options.installValues, 2518 options.Flags.LongTermStorageBucketName) 2519 if err != nil { 2520 return errors.Wrap(err, "enabling long term storage on GKE") 2521 } 2522 break 2523 default: 2524 return errors.Errorf("long term storage is not yet supported for provider %s", options.Flags.Provider) 2525 } 2526 return options.assignBucketToTeamStorage(bucketURL) 2527 } 2528 return nil 2529 } 2530 2531 func (options *InstallOptions) assignBucketToTeamStorage(bucketURL string) error { 2532 //Enable storage of logs into the bucketURL 2533 eso := edit.EditStorageOptions{ 2534 CommonOptions: options.CommonOptions, 2535 StorageLocation: v1.StorageLocation{ 2536 Classifier: "default", 2537 BucketURL: bucketURL, 2538 }, 2539 } 2540 infoBucketURL := util.ColorInfo(bucketURL) 2541 log.Logger().Debugf("Enabling default storage for current team in the bucket %s", infoBucketURL) 2542 err := eso.Run() 2543 if err != nil { 2544 return errors.Wrapf(err, "there was a problem executing `jx edit -c default --bucket-url=%s", 2545 infoBucketURL) 2546 } 2547 2548 eso.StorageLocation.Classifier = "logs" 2549 log.Logger().Debugf("Enabling logs storage for current team in the bucket %s", infoBucketURL) 2550 //Only GCS seems to be supported atm 2551 err = eso.Run() 2552 if err != nil { 2553 return errors.Wrapf(err, "there was a problem executing `jx edit -c logs --bucket-url=%s", 2554 infoBucketURL) 2555 } 2556 2557 return nil 2558 } 2559 2560 func (options *InstallOptions) ensureGKEInstallValuesAreFilled() error { 2561 if options.installValues == nil { 2562 options.installValues = make(map[string]string) 2563 } 2564 2565 if options.installValues[kube.ProjectID] == "" { 2566 currentProjectID, err := gke.GetCurrentProject() 2567 if err != nil { 2568 return errors.Wrap(err, "obtaining the current project from GKE context") 2569 } 2570 options.installValues[kube.ProjectID] = currentProjectID 2571 } 2572 2573 if options.installValues[kube.Zone] == "" { 2574 gcpCurrentZone, err := options.GetGoogleZone(options.installValues[kube.ProjectID], "") 2575 if err != nil { 2576 return errors.Wrap(err, "asking for the zone to create the bucket into") 2577 } 2578 options.installValues[kube.Zone] = gcpCurrentZone 2579 } 2580 2581 if options.installValues[kube.ClusterName] == "" { 2582 clusterName, err := cluster.Name(options.Kube()) 2583 if err != nil { 2584 return errors.Wrap(err, "obtaining the current cluster name") 2585 } 2586 options.installValues[kube.ClusterName] = clusterName 2587 } 2588 2589 return nil 2590 } 2591 2592 func (options *InstallOptions) saveIngressConfig() (*kube.IngressConfig, error) { 2593 exposeController := options.CreateEnvOptions.HelmValuesConfig.ExposeController 2594 tls, err := util.ParseBool(exposeController.Config.TLSAcme) 2595 if err != nil { 2596 return nil, fmt.Errorf("failed to parse TLS exposecontroller boolean %v", err) 2597 } 2598 domain := exposeController.Config.Domain 2599 ic := kube.IngressConfig{ 2600 Domain: domain, 2601 TLS: tls, 2602 Exposer: exposeController.Config.Exposer, 2603 UrlTemplate: exposeController.Config.URLTemplate, 2604 } 2605 // save ingress config details to a configmap 2606 _, err = options.saveAsConfigMap(kube.IngressConfigConfigmap, ic) 2607 if err != nil { 2608 return nil, err 2609 } 2610 return &ic, nil 2611 } 2612 2613 func (options *InstallOptions) saveClusterConfig() error { 2614 jxInstallConfig := &kube.JXInstallConfig{ 2615 KubeProvider: options.Flags.Provider, 2616 } 2617 kubeConfig, _, err := options.Kube().LoadConfig() 2618 if err != nil { 2619 return errors.Wrap(err, "retrieving the current kube config") 2620 } 2621 if kubeConfig != nil { 2622 kubeConfigContext := kube.CurrentContext(kubeConfig) 2623 if kubeConfigContext != nil { 2624 server := kube.Server(kubeConfig, kubeConfigContext) 2625 certificateAuthorityData := kube.CertificateAuthorityData(kubeConfig, kubeConfigContext) 2626 jxInstallConfig.Server = server 2627 jxInstallConfig.CA = certificateAuthorityData 2628 } 2629 } 2630 2631 if options.installValues == nil { 2632 options.installValues = map[string]string{} 2633 } 2634 installVersionKey := "jx-install-version" 2635 if options.installValues[installVersionKey] == "" { 2636 options.installValues[installVersionKey] = version2.GetVersion() 2637 } 2638 var secretsLocation secrets.SecretsLocationKind 2639 if options.Flags.Vault { 2640 secretsLocation = secrets.VaultLocationKind 2641 } else { 2642 secretsLocation = secrets.FileSystemLocationKind 2643 } 2644 options.installValues[secrets.SecretsLocationKey] = string(secretsLocation) 2645 2646 _, err = options.ModifyConfigMap(kube.ConfigMapNameJXInstallConfig, func(cm *core_v1.ConfigMap) error { 2647 if cm.Data == nil { 2648 cm.Data = make(map[string]string) 2649 } 2650 data := util.ToStringMapStringFromStruct(jxInstallConfig) 2651 for k, v := range data { 2652 cm.Data[k] = v 2653 } 2654 iv := options.installValues 2655 for k, v := range iv { 2656 cm.Data[k] = v 2657 } 2658 return nil 2659 }) 2660 if err != nil { 2661 return errors.Wrapf(err, "saving cluster config into config map %q", kube.ConfigMapNameJXInstallConfig) 2662 } 2663 return nil 2664 } 2665 2666 func (options *InstallOptions) configureJenkins(namespace string) error { 2667 if !options.Flags.Prow { 2668 log.Logger().Info("Configure Jenkins API Token") 2669 if isOpenShiftProvider(options.Flags.Provider) { 2670 options.CreateJenkinsUserOptions.CommonOptions = options.CommonOptions 2671 options.CreateJenkinsUserOptions.Password = options.AdminSecretsService.Flags.DefaultAdminPassword 2672 options.CreateJenkinsUserOptions.Username = "jenkins-admin" 2673 options.CreateJenkinsUserOptions.Verbose = false 2674 jenkinsSaToken, err := options.GetCommandOutput("", "oc", "serviceaccounts", "get-token", "jenkins", "-n", namespace) 2675 if err != nil { 2676 return errors.Wrap(err, "getting token from service account jenkins") 2677 } 2678 options.CreateJenkinsUserOptions.BearerToken = jenkinsSaToken 2679 err = options.CreateJenkinsUserOptions.Run() 2680 if err != nil { 2681 return errors.Wrap(err, "creating Jenkins API token") 2682 } 2683 } else { 2684 err := options.Retry(3, 2*time.Second, func() (err error) { 2685 _, devNamespace, err := options.KubeClientAndDevNamespace() 2686 if err != nil { 2687 return errors.Wrap(err, "getting team's dev namespace") 2688 } 2689 options.CreateJenkinsUserOptions.CommonOptions = options.CommonOptions 2690 options.CreateJenkinsUserOptions.Namespace = devNamespace 2691 options.CreateJenkinsUserOptions.RecreateToken = true 2692 options.CreateJenkinsUserOptions.Username = options.AdminSecretsService.Flags.DefaultAdminUsername 2693 options.CreateJenkinsUserOptions.Password = options.AdminSecretsService.Flags.DefaultAdminPassword 2694 options.CreateJenkinsUserOptions.Verbose = false 2695 options.CreateJenkinsUserOptions.RecreateToken = true 2696 if options.BatchMode { 2697 options.CreateJenkinsUserOptions.BatchMode = true 2698 } 2699 err = options.CreateJenkinsUserOptions.Run() 2700 return 2701 }) 2702 if err != nil { 2703 return errors.Wrap(err, "creating Jenkins API token") 2704 } 2705 } 2706 2707 err := options.UpdateJenkinsURL([]string{namespace}) 2708 if err != nil { 2709 log.Logger().Warnf("Failed to update the Jenkins external URL: %s", err) 2710 } 2711 } 2712 return nil 2713 } 2714 2715 func (options *InstallOptions) installAddons() error { 2716 if !options.Flags.GitOpsMode { 2717 addonConfig, err := addon.LoadAddonsConfig() 2718 if err != nil { 2719 return errors.Wrap(err, "failed to load the addons configuration") 2720 } 2721 2722 for _, ac := range addonConfig.Addons { 2723 if ac.Enabled { 2724 err = options.installAddon(ac.Name) 2725 if err != nil { 2726 return fmt.Errorf("failed to install addon %s: %s", ac.Name, err) 2727 } 2728 } 2729 } 2730 } 2731 return nil 2732 } 2733 2734 func (options *InstallOptions) createEnvironments(namespace string) error { 2735 if !options.Flags.NoDefaultEnvironments { 2736 if options.Flags.GitOpsMode { 2737 options.SetDevNamespace(namespace) 2738 options.CreateEnvOptions.CommonOptions = options.CommonOptions 2739 options.CreateEnvOptions.GitOpsMode = true 2740 options.CreateEnvOptions.ModifyDevEnvironmentFn = options.ModifyDevEnvironmentFn 2741 options.CreateEnvOptions.ModifyEnvironmentFn = options.ModifyEnvironmentFn 2742 } 2743 2744 log.Logger().Info("Creating default staging and production environments") 2745 _, devNamespace, err := options.KubeClientAndDevNamespace() 2746 if err != nil { 2747 return errors.Wrap(err, "getting team's dev namespace") 2748 } 2749 gitRepoOptions, err := options.buildGitRepositoryOptionsForEnvironments() 2750 if err != nil || gitRepoOptions == nil { 2751 return errors.Wrap(err, "building the Git repository options for environments") 2752 } 2753 options.CreateEnvOptions.GitRepositoryOptions = *gitRepoOptions 2754 // lets not fail if environments already exist 2755 options.CreateEnvOptions.Update = true 2756 2757 options.CreateEnvOptions.Prefix = options.Flags.DefaultEnvironmentPrefix 2758 options.CreateEnvOptions.Prow = options.Flags.Prow 2759 if options.BatchMode { 2760 options.CreateEnvOptions.BatchMode = options.BatchMode 2761 } 2762 options.CreateEnvOptions.Options.Name = "staging" 2763 options.CreateEnvOptions.Options.Spec.Label = "Staging" 2764 options.CreateEnvOptions.Options.Spec.Order = 100 2765 options.CreateEnvOptions.Options.Spec.RemoteCluster = options.Flags.RemoteEnvironments 2766 err = options.CreateEnvOptions.Run() 2767 if err != nil { 2768 return errors.Wrapf(err, "failed to create staging environment in namespace %s", devNamespace) 2769 } 2770 options.CreateEnvOptions.Options.Name = "production" 2771 options.CreateEnvOptions.Options.Spec.Label = "Production" 2772 options.CreateEnvOptions.Options.Spec.Order = 200 2773 options.CreateEnvOptions.Options.Spec.RemoteCluster = options.Flags.RemoteEnvironments 2774 options.CreateEnvOptions.Options.Spec.PromotionStrategy = v1.PromotionStrategyTypeManual 2775 options.CreateEnvOptions.PromotionStrategy = string(v1.PromotionStrategyTypeManual) 2776 2777 err = options.CreateEnvOptions.Run() 2778 if err != nil { 2779 return errors.Wrapf(err, "failed to create the production environment in namespace %s", devNamespace) 2780 } 2781 } 2782 return nil 2783 } 2784 2785 func (options *InstallOptions) modifySecrets(helmConfig *config.HelmValuesConfig, adminSecrets *config.AdminSecretsConfig) error { 2786 var err error 2787 data := make(map[string][]byte) 2788 data[opts.ExtraValuesFile], err = yaml.Marshal(helmConfig) 2789 if err != nil { 2790 return err 2791 } 2792 data[opts.AdminSecretsFile], err = yaml.Marshal(adminSecrets) 2793 if err != nil { 2794 return err 2795 } 2796 _, err = options.ModifySecret(opts.JXInstallConfig, func(secret *core_v1.Secret) error { 2797 secret.Data = data 2798 return nil 2799 }) 2800 return err 2801 } 2802 2803 // ModifySecret modifies the Secret either live or via the file system if generating the GitOps source 2804 func (options *InstallOptions) ModifySecret(name string, callback func(*core_v1.Secret) error) (*core_v1.Secret, error) { 2805 if options.modifySecretCallback == nil { 2806 options.modifySecretCallback = func(name string, callback func(*core_v1.Secret) error) (*core_v1.Secret, error) { 2807 kubeClient, ns, err := options.KubeClientAndDevNamespace() 2808 if err != nil { 2809 return nil, err 2810 } 2811 return kube.DefaultModifySecret(kubeClient, ns, name, callback, nil) 2812 } 2813 } 2814 return options.modifySecretCallback(name, callback) 2815 } 2816 2817 // ModifyConfigMap modifies the ConfigMap either live or via the file system if generating the GitOps source 2818 func (options *InstallOptions) ModifyConfigMap(name string, callback func(*core_v1.ConfigMap) error) (*core_v1.ConfigMap, error) { 2819 if options.modifyConfigMapCallback == nil { 2820 options.modifyConfigMapCallback = func(name string, callback func(*core_v1.ConfigMap) error) (*core_v1.ConfigMap, error) { 2821 kubeClient, ns, err := options.KubeClientAndDevNamespace() 2822 if err != nil { 2823 return nil, err 2824 } 2825 return kube.DefaultModifyConfigMap(kubeClient, ns, name, callback, nil) 2826 } 2827 } 2828 return options.modifyConfigMapCallback(name, callback) 2829 } 2830 2831 // gitOpsModifyConfigMap provides a helper function to lazily create, modify and save the YAML file in the given directory 2832 func gitOpsModifyConfigMap(dir string, name string, defaultResource *core_v1.ConfigMap, configStore configio.ConfigStore, 2833 callback func(configMap *core_v1.ConfigMap) error) (*core_v1.ConfigMap, error) { 2834 answer := core_v1.ConfigMap{} 2835 fileName := filepath.Join(dir, name+"-configmap.yaml") 2836 exists, err := util.FileExists(fileName) 2837 if err != nil { 2838 return &answer, errors.Wrapf(err, "Could not check if file exists %s", fileName) 2839 } 2840 if exists { 2841 err = configStore.ReadObject(fileName, &answer) 2842 if err != nil { 2843 return &answer, errors.Wrapf(err, "Failed to unmarshall YAML file %s", fileName) 2844 } 2845 } else if defaultResource != nil { 2846 answer = *defaultResource 2847 } else { 2848 answer.Name = name 2849 } 2850 err = callback(&answer) 2851 if err != nil { 2852 return &answer, err 2853 } 2854 if answer.APIVersion == "" { 2855 answer.APIVersion = "v1" 2856 } 2857 if answer.Kind == "" { 2858 answer.Kind = "ConfigMap" 2859 } 2860 if answer.Data == nil { 2861 answer.Data = make(map[string]string) 2862 } 2863 err = configStore.WriteObject(fileName, &answer) 2864 if err != nil { 2865 return &answer, errors.Wrapf(err, "Could not save file %s", fileName) 2866 } 2867 return &answer, nil 2868 } 2869 2870 // gitOpsModifySecret provides a helper function to lazily create, modify and save the YAML file in the given directory 2871 func gitOpsModifySecret(dir string, name string, defaultResource *core_v1.Secret, configStore configio.ConfigStore, 2872 callback func(secret *core_v1.Secret) error) (*core_v1.Secret, error) { 2873 answer := core_v1.Secret{} 2874 fileName := filepath.Join(dir, name+"-secret.yaml") 2875 exists, err := util.FileExists(fileName) 2876 if err != nil { 2877 return &answer, errors.Wrapf(err, "checking if file exists %s", fileName) 2878 } 2879 if exists { 2880 // lets unmarshall the data 2881 err = configStore.ReadObject(fileName, &answer) 2882 if err != nil { 2883 return &answer, err 2884 } 2885 } else if defaultResource != nil { 2886 answer = *defaultResource 2887 } else { 2888 answer.Name = name 2889 } 2890 err = callback(&answer) 2891 if err != nil { 2892 return &answer, err 2893 } 2894 if answer.APIVersion == "" { 2895 answer.APIVersion = "v1" 2896 } 2897 if answer.Kind == "" { 2898 answer.Kind = "Secret" 2899 } 2900 err = configStore.WriteObject(fileName, &answer) 2901 if err != nil { 2902 return &answer, errors.Wrapf(err, "Could not save file %s", fileName) 2903 } 2904 return &answer, nil 2905 } 2906 2907 // gitOpsModifyEnvironment provides a helper function to lazily create, modify and save the YAML file in the given directory 2908 func gitOpsModifyEnvironment(dir string, name string, defaultEnvironment *v1.Environment, configStore configio.ConfigStore, 2909 callback func(*v1.Environment) error) (*v1.Environment, error) { 2910 answer := v1.Environment{} 2911 fileName := filepath.Join(dir, name+"-env.yaml") 2912 exists, err := util.FileExists(fileName) 2913 if err != nil { 2914 return &answer, errors.Wrapf(err, "Could not check if file exists %s", fileName) 2915 } 2916 if exists { 2917 // lets unmarshal the data 2918 err := configStore.ReadObject(fileName, &answer) 2919 if err != nil { 2920 return &answer, err 2921 } 2922 } else if defaultEnvironment != nil { 2923 answer = *defaultEnvironment 2924 } 2925 err = callback(&answer) 2926 if err != nil { 2927 return &answer, err 2928 } 2929 answer.Name = name 2930 if answer.APIVersion == "" { 2931 answer.APIVersion = jenkinsio.GroupAndVersion 2932 } 2933 if answer.Kind == "" { 2934 answer.Kind = "Environment" 2935 } 2936 err = configStore.WriteObject(fileName, &answer) 2937 if err != nil { 2938 return &answer, errors.Wrapf(err, "Could not save file %s", fileName) 2939 } 2940 return &answer, nil 2941 } 2942 2943 func isOpenShiftProvider(provider string) bool { 2944 switch provider { 2945 case cloud.OPENSHIFT: 2946 return true 2947 default: 2948 return false 2949 } 2950 } 2951 2952 func (options *InstallOptions) enableOpenShiftSCC(ns string) error { 2953 log.Logger().Infof("Enabling anyuid for the Jenkins service account in namespace %s", ns) 2954 err := options.RunCommand("oc", "adm", "policy", "add-scc-to-user", "anyuid", "system:serviceaccount:"+ns+":jenkins") 2955 if err != nil { 2956 return err 2957 } 2958 err = options.RunCommand("oc", "adm", "policy", "add-scc-to-user", "hostaccess", "system:serviceaccount:"+ns+":jenkins") 2959 if err != nil { 2960 return err 2961 } 2962 err = options.RunCommand("oc", "adm", "policy", "add-scc-to-user", "privileged", "system:serviceaccount:"+ns+":jenkins") 2963 if err != nil { 2964 return err 2965 } 2966 // try fix monocular 2967 return options.RunCommand("oc", "adm", "policy", "add-scc-to-user", "anyuid", "system:serviceaccount:"+ns+":default") 2968 } 2969 2970 func (options *InstallOptions) enableOpenShiftRegistryPermissions(ns string, dockerRegistry string) (string, error) { 2971 log.Logger().Infof("Enabling permissions for OpenShift registry in namespace %s", ns) 2972 // Open the registry so any authenticated user can pull images from the jx namespace 2973 err := options.RunCommand("oc", "adm", "policy", "add-role-to-group", "system:image-puller", "system:authenticated", "-n", ns) 2974 if err != nil { 2975 return "", err 2976 } 2977 err = options.EnsureServiceAccount(ns, "jenkins-x-registry") 2978 if err != nil { 2979 return "", err 2980 } 2981 err = options.RunCommand("oc", "adm", "policy", "add-cluster-role-to-user", "registry-admin", "system:serviceaccount:"+ns+":jenkins-x-registry") 2982 if err != nil { 2983 return "", err 2984 } 2985 registryToken, err := options.GetCommandOutput("", "oc", "serviceaccounts", "get-token", "jenkins-x-registry", "-n", ns) 2986 if err != nil { 2987 return "", err 2988 } 2989 return `{"auths": {"` + dockerRegistry + `": {"auth": "` + base64.StdEncoding.EncodeToString([]byte("serviceaccount:"+registryToken)) + `"}}}`, nil 2990 } 2991 2992 func (options *InstallOptions) logAdminPassword() { 2993 astrix := ` 2994 2995 ******************************************************** 2996 2997 NOTE: %s 2998 2999 ******************************************************** 3000 3001 ` 3002 if options.Flags.Vault { 3003 log.Logger().Infof(astrix+"\n", fmt.Sprintf("Your admin password is in vault: %s", util.ColorInfo("eval `jx get vault-config` && vault kv get secret/admin/jenkins"))) 3004 } else { 3005 log.Logger().Infof(astrix+"\n", fmt.Sprintf("Your admin password is: %s", util.ColorInfo(options.AdminSecretsService.Flags.DefaultAdminPassword))) 3006 } 3007 } 3008 3009 func (options *InstallOptions) logNameServers() { 3010 output := ` 3011 ******************************************************** 3012 3013 External DNS: %s 3014 3015 ******************************************************** 3016 ` 3017 if options.InitOptions.Flags.ExternalDNS { 3018 log.Logger().Infof(output, fmt.Sprintf("Please delegate %s via \n\tyour registrar onto the following name servers: \n\t\t%s", 3019 util.ColorInfo(options.Flags.Domain), util.ColorInfo(strings.Join(options.CommonOptions.NameServers, "\n\t\t")))) 3020 } 3021 } 3022 3023 // LoadVersionFromCloudEnvironmentsDir lets load the jenkins-x-platform version 3024 func LoadVersionFromCloudEnvironmentsDir(wrkDir string, configStore configio.ConfigStore) (string, error) { 3025 version, err := versionstream.LoadStableVersionNumber(wrkDir, versionstream.KindChart, platform.JenkinsXPlatformChart) 3026 if err != nil { 3027 return version, errors.Wrapf(err, "failed to load version of chart %s in dir %s", platform.JenkinsXPlatformChart, wrkDir) 3028 } 3029 return version, nil 3030 } 3031 3032 // clones the jenkins-x cloud-environments repo to a local working dir 3033 func (options *InstallOptions) CloneJXCloudEnvironmentsRepo() (string, error) { 3034 surveyOpts := survey.WithStdio(options.In, options.Out, options.Err) 3035 configDir, err := util.ConfigDir() 3036 if err != nil { 3037 return "", fmt.Errorf("error determining config dir %v", err) 3038 } 3039 wrkDir := filepath.Join(configDir, "cloud-environments") 3040 3041 log.Logger().Debugf("Current configuration dir: %s", configDir) 3042 log.Logger().Debugf("options.Flags.CloudEnvRepository: %s", options.Flags.CloudEnvRepository) 3043 log.Logger().Debugf("options.Flags.LocalCloudEnvironment: %t", options.Flags.LocalCloudEnvironment) 3044 3045 if options.Flags.LocalCloudEnvironment { 3046 currentDir, err := os.Getwd() 3047 if err != nil { 3048 return wrkDir, fmt.Errorf("error getting current working directory %v", err) 3049 } 3050 log.Logger().Infof("Copying local dir %s to %s", currentDir, wrkDir) 3051 3052 return wrkDir, util.CopyDir(currentDir, wrkDir, true) 3053 } 3054 if options.Flags.CloudEnvRepository == "" { 3055 options.Flags.CloudEnvRepository = opts.DefaultCloudEnvironmentsURL 3056 } 3057 log.Logger().Debugf("Cloning the Jenkins X cloud environments repo to %s", wrkDir) 3058 _, err = git.PlainClone(wrkDir, false, &git.CloneOptions{ 3059 URL: options.Flags.CloudEnvRepository, 3060 ReferenceName: "refs/heads/master", 3061 SingleBranch: true, 3062 Progress: options.Out, 3063 }) 3064 if err != nil { 3065 if err == git.ErrRepositoryAlreadyExists { 3066 flag := false 3067 if options.BatchMode { 3068 flag = true 3069 } else if options.AdvancedMode { 3070 confirm := &survey.Confirm{ 3071 Message: "A local Jenkins X cloud environments repository already exists, recreate with latest?", 3072 Default: true, 3073 } 3074 err := survey.AskOne(confirm, &flag, nil, surveyOpts) 3075 if err != nil { 3076 return wrkDir, err 3077 } 3078 } else { 3079 flag = true 3080 log.Logger().Infof(util.QuestionAnswer("A local Jenkins X cloud environments repository already exists, recreating with latest", util.YesNo(flag))) 3081 } 3082 3083 if flag { 3084 err := os.RemoveAll(wrkDir) 3085 if err != nil { 3086 return wrkDir, err 3087 } 3088 3089 return options.CloneJXCloudEnvironmentsRepo() 3090 } 3091 } else { 3092 return wrkDir, err 3093 } 3094 } 3095 return wrkDir, nil 3096 } 3097 3098 func (options *InstallOptions) waitForInstallToBeReady(ns string) error { 3099 client, err := options.KubeClient() 3100 if err != nil { 3101 return err 3102 } 3103 3104 log.Logger().Warnf("waiting for install to be ready, if this is the first time then it will take a while to download images") 3105 3106 return kube.WaitForAllDeploymentsToBeReady(client, ns, 30*time.Minute) 3107 3108 } 3109 3110 func (options *InstallOptions) saveChartmuseumAuthConfig() error { 3111 3112 authConfigSvc, err := options.ChartmuseumAuthConfigService() 3113 if err != nil { 3114 return err 3115 } 3116 config := authConfigSvc.Config() 3117 3118 var server *auth.AuthServer 3119 if options.ServerFlags.IsEmpty() { 3120 url := "" 3121 url, err = options.FindService(kube.ServiceChartMuseum) 3122 if err != nil { 3123 log.Logger().Warnf("No service called %s could be found so couldn't wire up the local auth file to talk to chart museum", kube.ServiceChartMuseum) 3124 return nil 3125 } 3126 server = config.GetOrCreateServer(url) 3127 } else { 3128 server, err = options.FindServer(config, &options.ServerFlags, "ChartMuseum server", "Try installing one via: jx create team", false) 3129 if err != nil { 3130 return err 3131 } 3132 } 3133 3134 user := &auth.UserAuth{ 3135 Username: options.AdminSecretsService.Flags.DefaultAdminUsername, 3136 Password: options.AdminSecretsService.Flags.DefaultAdminPassword, 3137 } 3138 3139 server.Users = append(server.Users, user) 3140 3141 config.CurrentServer = server.URL 3142 return authConfigSvc.SaveConfig() 3143 } 3144 3145 func (options *InstallOptions) installAddon(name string) error { 3146 log.Logger().Infof("Installing addon %s", util.ColorInfo(name)) 3147 3148 opts := &CreateAddonOptions{ 3149 CreateOptions: createoptions.CreateOptions{ 3150 CommonOptions: options.CommonOptions, 3151 }, 3152 HelmUpdate: true, 3153 } 3154 if name == "gitea" { 3155 opts.ReleaseName = defaultGiteaReleaseName 3156 giteaOptions := &CreateAddonGiteaOptions{ 3157 CreateAddonOptions: *opts, 3158 Chart: kube.ChartGitea, 3159 } 3160 return giteaOptions.Run() 3161 } 3162 return opts.CreateAddon(name) 3163 } 3164 3165 func (options *InstallOptions) addGitServersToJenkinsConfig(helmConfig *config.HelmValuesConfig) error { 3166 gitAuthCfg, err := options.GitAuthConfigService() 3167 if err != nil { 3168 return errors.Wrap(err, "failed to create the git auth config service") 3169 } 3170 cfg := gitAuthCfg.Config() 3171 for _, server := range cfg.Servers { 3172 if server.Kind == "github" { 3173 u := server.URL 3174 if !gits.IsGitHubServerURL(u) { 3175 sc := config.JenkinsGithubServersValuesConfig{ 3176 Name: server.Name, 3177 Url: gits.GitHubEnterpriseApiEndpointURL(u), 3178 } 3179 helmConfig.Jenkins.Servers.GHE = append(helmConfig.Jenkins.Servers.GHE, sc) 3180 } 3181 } 3182 } 3183 return nil 3184 } 3185 3186 func (options *InstallOptions) ensureDefaultStorageClass(client kubernetes.Interface, name string, provisioner string, typeName string) error { 3187 storageClassInterface := client.StorageV1().StorageClasses() 3188 storageClasses, err := storageClassInterface.List(metav1.ListOptions{}) 3189 if err != nil { 3190 return err 3191 } 3192 var foundSc *storagev1.StorageClass 3193 for idx, sc := range storageClasses.Items { 3194 ann := sc.Annotations 3195 if ann != nil && ann[kube.AnnotationIsDefaultStorageClass] == "true" { 3196 return nil 3197 } 3198 if sc.Name == name { 3199 foundSc = &storageClasses.Items[idx] 3200 } 3201 } 3202 3203 if foundSc != nil { 3204 // lets update the storageclass to be default 3205 if foundSc.Annotations == nil { 3206 foundSc.Annotations = map[string]string{} 3207 } 3208 foundSc.Annotations[kube.AnnotationIsDefaultStorageClass] = "true" 3209 3210 log.Logger().Infof("Updating storageclass %s to be the default", util.ColorInfo(name)) 3211 _, err = storageClassInterface.Update(foundSc) 3212 return err 3213 } 3214 3215 // lets create a default storage class 3216 reclaimPolicy := core_v1.PersistentVolumeReclaimRetain 3217 3218 sc := &storagev1.StorageClass{ 3219 ObjectMeta: metav1.ObjectMeta{ 3220 Name: name, 3221 Annotations: map[string]string{ 3222 kube.AnnotationIsDefaultStorageClass: "true", 3223 }, 3224 }, 3225 Provisioner: provisioner, 3226 Parameters: map[string]string{ 3227 "type": typeName, 3228 }, 3229 ReclaimPolicy: &reclaimPolicy, 3230 MountOptions: []string{"debug"}, 3231 } 3232 log.Logger().Infof("Creating default storageclass %s with provisioner %s", util.ColorInfo(name), util.ColorInfo(provisioner)) 3233 _, err = storageClassInterface.Create(sc) 3234 return err 3235 } 3236 3237 func (options *InstallOptions) changeDefaultStorageClass(client kubernetes.Interface, defaultName string) error { 3238 storageClassInterface := client.StorageV1().StorageClasses() 3239 storageClasses, err := storageClassInterface.List(metav1.ListOptions{}) 3240 if err != nil { 3241 return err 3242 } 3243 var foundSc *storagev1.StorageClass 3244 for idx, sc := range storageClasses.Items { 3245 ann := sc.Annotations 3246 foundSc = &storageClasses.Items[idx] 3247 if sc.Name == defaultName { 3248 if ann == nil { 3249 foundSc.Annotations = map[string]string{} 3250 } 3251 foundSc.Annotations[kube.AnnotationIsDefaultStorageClass] = "true" 3252 _, err = storageClassInterface.Update(foundSc) 3253 } else { 3254 if ann != nil && ann[kube.AnnotationIsDefaultStorageClass] == "true" { 3255 foundSc.Annotations[kube.AnnotationIsDefaultStorageClass] = "false" 3256 _, err = storageClassInterface.Update(foundSc) 3257 } 3258 } 3259 } 3260 return nil 3261 } 3262 3263 // returns the docker registry string for the given provider 3264 func (options *InstallOptions) dockerRegistryValue() (string, error) { 3265 if options.Flags.DockerRegistry != "" { 3266 return options.Flags.DockerRegistry, nil 3267 } 3268 if options.Flags.Provider == cloud.AWS || options.Flags.Provider == cloud.EKS { 3269 return amazon.GetContainerRegistryHost() 3270 } 3271 if options.Flags.Provider == cloud.OPENSHIFT { 3272 return "docker-registry.default.svc:5000", nil 3273 } 3274 if options.Flags.Provider == cloud.GKE { 3275 if options.Flags.Kaniko { 3276 return "gcr.io", nil 3277 } 3278 } 3279 3280 log.Logger().Debugf("unable to determine the dockerRegistryValue - provider=%s, defaulting to in-cluster registry", options.Flags.Provider) 3281 3282 return "", nil 3283 } 3284 3285 func (options *InstallOptions) saveAsConfigMap(name string, config interface{}) (*core_v1.ConfigMap, error) { 3286 return options.ModifyConfigMap(name, func(cm *core_v1.ConfigMap) error { 3287 data := util.ToStringMapStringFromStruct(config) 3288 cm.Data = data 3289 return nil 3290 }) 3291 } 3292 3293 func (options *InstallOptions) configureTeamSettings() error { 3294 initOpts := &options.InitOptions 3295 callback := func(env *v1.Environment) error { 3296 if env.Spec.TeamSettings.KubeProvider == "" { 3297 env.Spec.TeamSettings.KubeProvider = options.Flags.Provider 3298 log.Logger().Debugf("Storing the kubernetes provider %s in the TeamSettings", env.Spec.TeamSettings.KubeProvider) 3299 } 3300 3301 if initOpts.Flags.Helm3 { 3302 env.Spec.TeamSettings.HelmTemplate = false 3303 env.Spec.TeamSettings.HelmBinary = "helm3" 3304 log.Logger().Debugf("Enabling helm3 / non template mode in the TeamSettings") 3305 } else if initOpts.Flags.NoTiller { 3306 env.Spec.TeamSettings.HelmTemplate = true 3307 log.Logger().Debugf("Enabling helm template mode in the TeamSettings") 3308 } 3309 3310 if options.Flags.DockerRegistryOrg != "" { 3311 env.Spec.TeamSettings.DockerRegistryOrg = options.Flags.DockerRegistryOrg 3312 log.Logger().Infof("Setting the docker registry organisation to %s in the TeamSettings", env.Spec.TeamSettings.DockerRegistryOrg) 3313 } 3314 3315 if options.Flags.VersionsRepository != "" { 3316 env.Spec.TeamSettings.VersionStreamURL = options.Flags.VersionsRepository 3317 } 3318 3319 if options.Flags.VersionsGitRef != "" { 3320 env.Spec.TeamSettings.VersionStreamRef = options.Flags.VersionsGitRef 3321 } 3322 return nil 3323 } 3324 err := options.ModifyDevEnvironment(callback) 3325 if err != nil { 3326 return errors.Wrap(err, "updating the team setttings in the dev environment") 3327 } 3328 return nil 3329 } 3330 3331 // setValuesFileValue lazily creates the values.yaml file possibly in a new directory and ensures there is the key in the values with the given value 3332 func (options *InstallOptions) setValuesFileValue(fileName string, key string, value interface{}) error { 3333 dir, _ := filepath.Split(fileName) 3334 err := os.MkdirAll(dir, util.DefaultWritePermissions) 3335 if err != nil { 3336 return err 3337 } 3338 answerMap := map[string]interface{}{} 3339 3340 // lets load any previous values if they exist 3341 exists, err := util.FileExists(fileName) 3342 if err != nil { 3343 return err 3344 } 3345 if exists { 3346 answerMap, err = helm.LoadValuesFile(fileName) 3347 if err != nil { 3348 return err 3349 } 3350 } 3351 answerMap[key] = value 3352 answer := chartutil.Values(answerMap) 3353 text, err := answer.YAML() 3354 if err != nil { 3355 return errors.Wrap(err, "Failed to marshal the updated values YAML files back to YAML") 3356 } 3357 err = ioutil.WriteFile(fileName, []byte(text), util.DefaultWritePermissions) 3358 if err != nil { 3359 return errors.Wrapf(err, "Failed to save updated helm values YAML file %s", fileName) 3360 } 3361 return nil 3362 } 3363 3364 // validateClusterName checks for compliance of a user supplied 3365 // cluster name against GKE's rules for these names. 3366 func validateClusterName(clustername string) error { 3367 // Check for length greater than 27. 3368 if len(clustername) > maxGKEClusterNameLength { 3369 err := fmt.Errorf("cluster name %s is greater than the maximum %d characters", clustername, maxGKEClusterNameLength) 3370 return err 3371 } 3372 // Now we need only make sure that clustername is limited to 3373 // lowercase alphanumerics and dashes. 3374 if util.DisallowedLabelCharacters.MatchString(clustername) { 3375 err := fmt.Errorf("cluster name %v contains invalid characters. Permitted are lowercase alphanumerics and `-`", clustername) 3376 return err 3377 } 3378 return nil 3379 } 3380 3381 func installConfigKey(key string) string { 3382 return fmt.Sprintf("install.%s", key) 3383 }