github.com/verrazzano/verrazzano@v1.7.0/tools/vz/cmd/install/install.go (about) 1 // Copyright (c) 2022, 2023, Oracle and/or its affiliates. 2 // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. 3 4 package install 5 6 import ( 7 "bufio" 8 "context" 9 "fmt" 10 "os" 11 "time" 12 13 "github.com/spf13/cobra" 14 "github.com/verrazzano/verrazzano/pkg/kubectlutil" 15 "github.com/verrazzano/verrazzano/pkg/semver" 16 "github.com/verrazzano/verrazzano/platform-operator/apis/verrazzano/v1beta1" 17 "github.com/verrazzano/verrazzano/platform-operator/apis/verrazzano/validators" 18 "github.com/verrazzano/verrazzano/tools/vz/cmd/bugreport" 19 cmdhelpers "github.com/verrazzano/verrazzano/tools/vz/cmd/helpers" 20 "github.com/verrazzano/verrazzano/tools/vz/cmd/version" 21 "github.com/verrazzano/verrazzano/tools/vz/pkg/constants" 22 "github.com/verrazzano/verrazzano/tools/vz/pkg/helpers" 23 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 24 "k8s.io/apimachinery/pkg/runtime/schema" 25 "k8s.io/apimachinery/pkg/types" 26 "k8s.io/client-go/kubernetes" 27 "k8s.io/kube-openapi/pkg/util/proto/validation" 28 "k8s.io/kubectl/pkg/util/openapi" 29 clipkg "sigs.k8s.io/controller-runtime/pkg/client" 30 ) 31 32 const ( 33 CommandName = "install" 34 helpShort = "Install Verrazzano" 35 helpLong = `Install the Verrazzano Platform Operator and install the Verrazzano components specified by the Verrazzano CR provided on the command line` 36 ) 37 38 var helpExample = fmt.Sprintf(` 39 # Install the latest version of Verrazzano using the prod profile. Stream the logs to the console until the install completes. 40 vz install 41 42 # Install version %[1]s using a dev profile, timeout the command after 20 minutes. 43 vz install --version v%[1]s --set profile=dev --timeout 20m 44 45 # Install version %[1]s using a dev profile with kiali disabled and wait for the install to complete. 46 vz install --version v%[1]s --set profile=dev --set components.kiali.enabled=false 47 48 # Install the latest version of Verrazzano using CR overlays and explicit value sets. Output the logs in json format. 49 # The overlay files can be a comma-separated list or a series of -f options. Both formats are shown. 50 vz install -f base.yaml,custom.yaml --set profile=prod --log-format json 51 vz install -f base.yaml -f custom.yaml --set profile=prod --log-format json 52 # Install the latest version of Verrazzano with progress bar enabled. 53 vz install --progress 54 # Install the latest version of Verrazzano using a Verrazzano CR specified with stdin. 55 vz install -f - <<EOF 56 apiVersion: install.verrazzano.io/v1beta1 57 kind: Verrazzano 58 metadata: 59 namespace: default 60 name: example-verrazzano 61 EOF`, version.GetCLIVersion()) 62 63 var logsEnum = cmdhelpers.LogFormatSimple 64 65 // validateCR functions used for unit-tests 66 type validateCRSig func(cmd *cobra.Command, obj *unstructured.Unstructured, vzHelper helpers.VZHelper) []error 67 68 var ValidateCRFunc validateCRSig = ValidateCR 69 70 func SetValidateCRFunc(f validateCRSig) { 71 ValidateCRFunc = f 72 } 73 74 func SetDefaultValidateCRFunc() { 75 ValidateCRFunc = ValidateCR 76 } 77 78 func FakeValidateCRFunc(cmd *cobra.Command, obj *unstructured.Unstructured, vzHelper helpers.VZHelper) []error { 79 return nil 80 } 81 82 func NewCmdInstall(vzHelper helpers.VZHelper) *cobra.Command { 83 cmd := cmdhelpers.NewCommand(vzHelper, CommandName, helpShort, helpLong) 84 cmd.RunE = func(cmd *cobra.Command, args []string) error { 85 return runCmdInstall(cmd, args, vzHelper) 86 } 87 cmd.Example = helpExample 88 89 cmd.PersistentFlags().Bool(constants.WaitFlag, constants.WaitFlagDefault, constants.WaitFlagHelp) 90 cmd.PersistentFlags().Duration(constants.TimeoutFlag, time.Minute*30, constants.TimeoutFlagHelp) 91 cmd.PersistentFlags().Duration(constants.VPOTimeoutFlag, time.Minute*5, constants.VPOTimeoutFlagHelp) 92 cmd.PersistentFlags().String(constants.VersionFlag, constants.VersionFlagDefault, constants.VersionFlagInstallHelp) 93 cmd.PersistentFlags().StringSliceP(constants.FilenameFlag, constants.FilenameFlagShorthand, []string{}, constants.FilenameFlagHelp) 94 cmd.PersistentFlags().Var(&logsEnum, constants.LogFormatFlag, constants.LogFormatHelp) 95 cmd.PersistentFlags().StringArrayP(constants.SetFlag, constants.SetFlagShorthand, []string{}, constants.SetFlagHelp) 96 cmd.PersistentFlags().Bool(constants.AutoBugReportFlag, constants.AutoBugReportFlagDefault, constants.AutoBugReportFlagHelp) 97 // Private registry support 98 cmd.PersistentFlags().String(constants.ImageRegistryFlag, constants.ImageRegistryFlagDefault, constants.ImageRegistryFlagHelp) 99 cmd.PersistentFlags().String(constants.ImagePrefixFlag, constants.ImagePrefixFlagDefault, constants.ImagePrefixFlagHelp) 100 cmd.PersistentFlags().BoolP(constants.ProgressFlag, constants.ProgressShorthand, constants.ProgressFlagDefault, constants.ProgressFlagHelp) 101 // Flag to skip any confirmation questions 102 cmd.PersistentFlags().BoolP(constants.SkipConfirmationFlag, constants.SkipConfirmationShort, false, constants.SkipConfirmationFlagHelp) 103 // Flag to skip reinstalling the Verrazzano Platform Operator 104 cmd.PersistentFlags().Bool(constants.SkipPlatformOperatorFlag, false, constants.SkipPlatformOperatorFlagHelp) 105 // Add flags related to specifying the platform operator manifests as a local file or a URL 106 cmdhelpers.AddManifestsFlags(cmd) 107 108 // Dry run flag is still being discussed - keep hidden for now 109 cmd.PersistentFlags().Bool(constants.DryRunFlag, false, "Simulate an install.") 110 cmd.PersistentFlags().MarkHidden(constants.DryRunFlag) 111 112 // Hide the flag for overriding the default wait timeout for the platform-operator 113 cmd.PersistentFlags().MarkHidden(constants.VPOTimeoutFlag) 114 115 // Verifies that the CLI args are not set at the creation of a command 116 vzHelper.VerifyCLIArgsNil(cmd) 117 118 return cmd 119 } 120 121 func runCmdInstall(cmd *cobra.Command, args []string, vzHelper helpers.VZHelper) error { 122 // Get the controller runtime client 123 client, err := vzHelper.GetClient(cmd) 124 if err != nil { 125 return err 126 } 127 128 // Validate the command options 129 err = validateCmd(cmd, client) 130 if err != nil { 131 return fmt.Errorf("Command validation failed: %s", err.Error()) 132 } 133 134 // Get the timeout value for the install command 135 timeout, err := cmdhelpers.GetWaitTimeout(cmd, constants.TimeoutFlag) 136 if err != nil { 137 return err 138 } 139 140 // Get the log format value 141 logFormat, err := cmdhelpers.GetLogFormat(cmd) 142 if err != nil { 143 return err 144 } 145 146 // Get the kubernetes clientset. This will validate that the kubeconfig and context are valid. 147 kubeClient, err := vzHelper.GetKubeClient(cmd) 148 if err != nil { 149 return err 150 } 151 152 // When manifests flag is not used, get the version from the command line 153 var version string 154 if !cmdhelpers.ManifestsFlagChanged(cmd) { 155 version, err = cmdhelpers.GetVersion(cmd, vzHelper) 156 if err != nil { 157 return err 158 } 159 fmt.Fprintf(vzHelper.GetOutputStream(), fmt.Sprintf("Installing Verrazzano version %s\n", version)) 160 } 161 162 var vzNamespace string 163 var vzName string 164 165 // Get the VPO timeout 166 vpoTimeout, err := cmdhelpers.GetWaitTimeout(cmd, constants.VPOTimeoutFlag) 167 if err != nil { 168 return err 169 } 170 171 // Check to see if we have a vz resource already deployed 172 existingvz, _ := helpers.FindVerrazzanoResource(client) 173 if existingvz != nil { 174 // Allow install command to continue if an install is in progress and the same version is specified. 175 // For example, control-C was entered and the install command is run again. 176 // Note: "Installing" is a state that was used in pre 1.4.0 installs and replaced with "Reconciling". 177 if existingvz.Status.State != v1beta1.VzStateReconciling && existingvz.Status.State != "Installing" { 178 return fmt.Errorf("Only one install of Verrazzano is allowed") 179 } 180 181 if version != "" { 182 installVersion, err := semver.NewSemVersion(version) 183 if err != nil { 184 return fmt.Errorf("Failed creating semantic version from install version %s: %s", version, err.Error()) 185 } 186 vzVersion, err := semver.NewSemVersion(existingvz.Status.Version) 187 if err != nil { 188 return fmt.Errorf("Failed creating semantic version from Verrazzano status version %s: %s", existingvz.Status.Version, err.Error()) 189 } 190 if !installVersion.IsEqualTo(vzVersion) { 191 return fmt.Errorf("Unable to install version %s, install of version %s is in progress", version, existingvz.Status.Version) 192 } 193 } 194 195 if err := cmdhelpers.ValidatePrivateRegistry(cmd, client); err != nil { 196 skipConfirm, errConfirm := cmd.PersistentFlags().GetBool(constants.SkipConfirmationFlag) 197 if errConfirm != nil { 198 return errConfirm 199 } 200 proceed, err := cmdhelpers.ConfirmWithUser(vzHelper, fmt.Sprintf("%s\nYour new settings will be ignored. Continue?", err.Error()), skipConfirm) 201 if err != nil { 202 return err 203 } 204 if !proceed { 205 fmt.Fprintf(vzHelper.GetOutputStream(), "Operation canceled.") 206 return nil 207 } 208 } 209 fmt.Fprintf(vzHelper.GetOutputStream(), fmt.Sprintf("Install of Verrazzano version %s is already in progress\n", version)) 210 211 vzNamespace = existingvz.Namespace 212 vzName = existingvz.Name 213 } else { 214 // Get the verrazzano install resource to be created 215 vz, obj, err := getVerrazzanoYAML(cmd, vzHelper, version) 216 if err != nil { 217 return err 218 } 219 220 // Determines whether to reapply the Verrazzano Platform Operator 221 continuePlatformOperatorReinstall, err := reapplyPlatformOperator(cmd, client) 222 if err != nil { 223 return err 224 } 225 if continuePlatformOperatorReinstall { 226 // Apply the Verrazzano operator.yaml. 227 err = cmdhelpers.ApplyPlatformOperatorYaml(cmd, client, vzHelper, version) 228 if err != nil { 229 return err 230 } 231 } 232 err = installVerrazzano(cmd, vzHelper, vz, client, version, vpoTimeout, obj, continuePlatformOperatorReinstall) 233 if err != nil { 234 return bugreport.AutoBugReport(cmd, vzHelper, err) 235 } 236 vzNamespace = vz.GetNamespace() 237 vzName = vz.GetName() 238 } 239 progressFlag, _ := cmd.PersistentFlags().GetBool(constants.ProgressFlag) 240 if progressFlag { 241 err = displayInstallationProgress(cmd, vzHelper, timeout) 242 } else { 243 // Wait for the Verrazzano install to complete and show the logs 244 err = waitForInstallToComplete(client, kubeClient, vzHelper, types.NamespacedName{Namespace: vzNamespace, Name: vzName}, timeout, vpoTimeout, logFormat) 245 } 246 if err != nil { 247 return bugreport.AutoBugReport(cmd, vzHelper, err) 248 } 249 return nil 250 } 251 252 func installVerrazzano(cmd *cobra.Command, vzHelper helpers.VZHelper, vz clipkg.Object, client clipkg.Client, version string, vpoTimeout time.Duration, obj *unstructured.Unstructured, continuePlatformOperatorReinstall bool) error { 253 // Determines whether to wait for Platform Operator 254 if continuePlatformOperatorReinstall { 255 // Wait for the platform operator to be ready before we create the Verrazzano resource. 256 _, err := cmdhelpers.WaitForPlatformOperator(client, vzHelper, v1beta1.CondInstallComplete, vpoTimeout) 257 if err != nil { 258 return err 259 } 260 } 261 262 // Validate Custom Resource if present 263 var errorArray = ValidateCRFunc(cmd, obj, vzHelper) 264 if len(errorArray) != 0 { 265 return fmt.Errorf("was unable to validate the given CR, the following error(s) occurred: \"%v\"", errorArray) 266 } 267 268 err := kubectlutil.SetLastAppliedConfigurationAnnotation(vz) 269 if err != nil { 270 return err 271 } 272 273 // Create the Verrazzano install resource, if need be. 274 // We will retry up to 5 times if there is an error. 275 // Sometimes we see intermittent webhook errors due to timeouts. 276 retry := 0 277 for { 278 err = client.Create(context.TODO(), vz) 279 if err != nil { 280 if retry == 5 { 281 return fmt.Errorf("Failed to create the verrazzano install resource: %s", err.Error()) 282 } 283 time.Sleep(time.Second) 284 retry++ 285 fmt.Fprintf(vzHelper.GetOutputStream(), fmt.Sprintf("Retrying after failing to create the verrazzano install resource: %s\n", err.Error())) 286 continue 287 } 288 break 289 } 290 return nil 291 } 292 293 // getVerrazzanoYAML returns the verrazzano install resource to be created 294 func getVerrazzanoYAML(cmd *cobra.Command, vzHelper helpers.VZHelper, version string) (vz clipkg.Object, obj *unstructured.Unstructured, err error) { 295 // Get the list yaml filenames specified 296 filenames, err := cmd.PersistentFlags().GetStringSlice(constants.FilenameFlag) 297 if err != nil { 298 return nil, nil, err 299 } 300 301 // Get the set arguments - returning a list of properties and value 302 pvs, err := cmdhelpers.GetSetArguments(cmd, vzHelper) 303 if err != nil { 304 return nil, nil, err 305 } 306 307 // If no yamls files were passed on the command line then create a minimal verrazzano 308 // resource. The minimal resource is used to create a resource called verrazzano 309 // in the default namespace using the prod profile. 310 var gv schema.GroupVersion 311 if len(filenames) == 0 { 312 gv, vz, err = helpers.NewVerrazzanoForVZVersion(version) 313 if err != nil { 314 return nil, nil, err 315 } 316 } else { 317 // Merge the yaml files passed on the command line 318 obj, err := cmdhelpers.MergeYAMLFiles(filenames, os.Stdin) 319 if err != nil { 320 return nil, nil, err 321 } 322 gv = obj.GroupVersionKind().GroupVersion() 323 vz = obj 324 } 325 326 // Generate yaml for the set flags passed on the command line 327 outYAML, err := cmdhelpers.GenerateYAMLForSetFlags(pvs) 328 if err != nil { 329 return nil, nil, err 330 } 331 332 // Merge the set flags passed on the command line. The set flags take precedence over 333 // the yaml files passed on the command line. 334 vz, unstructuredVZObj, err := cmdhelpers.MergeSetFlags(gv, vz, outYAML) 335 if err != nil { 336 return nil, nil, err 337 } 338 339 // Return the merged verrazzano install resource to be created 340 return vz, unstructuredVZObj, nil 341 } 342 343 // waitForInstallToComplete waits for the Verrazzano install to complete and shows the logs of 344 // the ongoing Verrazzano install. 345 func waitForInstallToComplete(client clipkg.Client, kubeClient kubernetes.Interface, vzHelper helpers.VZHelper, namespacedName types.NamespacedName, timeout time.Duration, vpoTimeout time.Duration, logFormat cmdhelpers.LogFormat) error { 346 return cmdhelpers.WaitForOperationToComplete(client, kubeClient, vzHelper, namespacedName, timeout, vpoTimeout, logFormat, v1beta1.CondInstallComplete) 347 } 348 349 // validateCmd - validate the command line options 350 func validateCmd(cmd *cobra.Command, client clipkg.Client) error { 351 if cmd.PersistentFlags().Changed(constants.VersionFlag) && cmdhelpers.ManifestsFlagChanged(cmd) { 352 return fmt.Errorf("--%s and --%s cannot both be specified", constants.VersionFlag, constants.ManifestsFlag) 353 } 354 if cmd.PersistentFlags().Changed(constants.SkipPlatformOperatorFlag) && cmdhelpers.ManifestsFlagChanged(cmd) { 355 return fmt.Errorf("--%s and --%s cannot both be specified", constants.SkipPlatformOperatorFlag, constants.ManifestsFlag) 356 } 357 if cmd.PersistentFlags().Changed(constants.SkipPlatformOperatorFlag) { 358 // Get the list of platform operator pods currently running 359 vpoList, err := validators.GetPlatformOperatorPodList(client) 360 if err != nil { 361 return err 362 } 363 if len(vpoList.Items) == 0 { 364 return fmt.Errorf("--%s cannot be specified when there is no Verrazzano Platform Operator running", constants.SkipPlatformOperatorFlag) 365 } 366 } 367 prefix, err := cmd.PersistentFlags().GetString(constants.ImagePrefixFlag) 368 if err != nil { 369 return err 370 } 371 reg, err := cmd.PersistentFlags().GetString(constants.ImageRegistryFlag) 372 if err != nil { 373 return err 374 } 375 if prefix != constants.ImagePrefixFlagDefault && reg == constants.ImageRegistryFlagDefault { 376 return fmt.Errorf("%s cannot be specified without also specifying %s", constants.ImagePrefixFlag, constants.ImageRegistryFlag) 377 } 378 return nil 379 } 380 381 // validateCR - validates a Custom Resource before proceeding with an install 382 func ValidateCR(cmd *cobra.Command, obj *unstructured.Unstructured, vzHelper helpers.VZHelper) []error { 383 discoveryClient, err := vzHelper.GetDiscoveryClient(cmd) 384 if err != nil { 385 return []error{err} 386 } 387 doc, err := discoveryClient.OpenAPISchema() 388 if err != nil { 389 return []error{err} 390 } 391 s, err := openapi.NewOpenAPIData(doc) 392 if err != nil { 393 return []error{err} 394 } 395 396 gvk := obj.GroupVersionKind() 397 schema := s.LookupResource(gvk) 398 if schema == nil { 399 return []error{fmt.Errorf("the schema for \"%v\" was not found", gvk.Kind)} 400 } 401 402 // ValidateModel validates a given schema 403 errorArray := validation.ValidateModel(obj.Object, schema, gvk.Kind) 404 if len(errorArray) != 0 { 405 return errorArray 406 } 407 408 return nil 409 } 410 411 // reapplyPlatformOperator - finds a running Verrazzano Platform Operator and determines whether to suppress the reinstall prompt 412 func reapplyPlatformOperator(cmd *cobra.Command, client clipkg.Client) (bool, error) { 413 skipPlatformOperator, _ := cmd.Flags().GetBool(constants.SkipPlatformOperatorFlag) 414 skipConfirmation, _ := cmd.PersistentFlags().GetBool(constants.SkipConfirmationFlag) 415 vpoList, err := validators.GetPlatformOperatorPodList(client) 416 if err != nil { 417 return false, err 418 } 419 // Decide to suppress the reinstall prompt based on the skipPlatformOperatorFlag 420 // this is only valid if there is a Verrazzano Platform Operator already running, 421 // otherwise there would be no prompt to suppress 422 if len(vpoList.Items) > 0 { 423 continuePlatformOperatorReapply, err := continueReapply(skipPlatformOperator, skipConfirmation) 424 if err != nil { 425 return false, err 426 } 427 return continuePlatformOperatorReapply, nil 428 } 429 // DEFAULT scenario is reinstalling the platform operator 430 return true, nil 431 } 432 433 func continueReapply(skipPlatformOperator, skipConfirmation bool) (bool, error) { 434 if skipPlatformOperator { 435 return false, nil 436 } 437 if skipConfirmation { 438 return true, nil 439 } 440 var response string 441 scanner := bufio.NewScanner(os.Stdin) 442 fmt.Print("Do you want to reinstall the Verrazzano Platform Operator? [y/N]: ") 443 if scanner.Scan() { 444 response = scanner.Text() 445 } 446 if err := scanner.Err(); err != nil { 447 return false, err 448 } 449 if response == "y" || response == "Y" { 450 return true, nil 451 } 452 return false, nil 453 }