github.com/jenspinney/cli@v6.42.1-0.20190207184520-7450c600020e+incompatible/command/v6/v3_zdt_push_command.go (about) 1 package v6 2 3 import ( 4 "fmt" 5 6 "code.cloudfoundry.org/cli/actor/actionerror" 7 "code.cloudfoundry.org/cli/actor/pushaction" 8 "code.cloudfoundry.org/cli/actor/sharedaction" 9 "code.cloudfoundry.org/cli/actor/v2action" 10 "code.cloudfoundry.org/cli/actor/v3action" 11 "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant" 12 "code.cloudfoundry.org/cli/api/cloudcontroller/ccversion" 13 "code.cloudfoundry.org/cli/command" 14 "code.cloudfoundry.org/cli/command/flag" 15 "code.cloudfoundry.org/cli/command/translatableerror" 16 "code.cloudfoundry.org/cli/command/v6/shared" 17 ) 18 19 //go:generate counterfeiter . V3ZeroDowntimeVersionActor 20 21 type V3ZeroDowntimeVersionActor interface { 22 ZeroDowntimePollStart(appGUID string, warningsChannel chan<- v3action.Warnings) error 23 CreateDeployment(appGUID string, deploymentGUID string) (string, v3action.Warnings, error) 24 PollDeployment(deploymentGUID string, warningsChannel chan<- v3action.Warnings) error 25 CloudControllerAPIVersion() string 26 CreateAndUploadBitsPackageByApplicationNameAndSpace(appName string, spaceGUID string, bitsPath string) (v3action.Package, v3action.Warnings, error) 27 CreateDockerPackageByApplicationNameAndSpace(appName string, spaceGUID string, dockerImageCredentials v3action.DockerImageCredentials) (v3action.Package, v3action.Warnings, error) 28 CreateApplicationInSpace(app v3action.Application, spaceGUID string) (v3action.Application, v3action.Warnings, error) 29 GetApplicationByNameAndSpace(appName string, spaceGUID string) (v3action.Application, v3action.Warnings, error) 30 GetCurrentDropletByApplication(appGUID string) (v3action.Droplet, v3action.Warnings, error) 31 GetStreamingLogsForApplicationByNameAndSpace(appName string, spaceGUID string, client v3action.NOAAClient) (<-chan *v3action.LogMessage, <-chan error, v3action.Warnings, error) 32 PollStart(appGUID string, warningsChannel chan<- v3action.Warnings) error 33 SetApplicationDropletByApplicationNameAndSpace(appName string, spaceGUID string, dropletGUID string) (v3action.Warnings, error) 34 StagePackage(packageGUID string, appName string) (<-chan v3action.Droplet, <-chan v3action.Warnings, <-chan error) 35 RestartApplication(appGUID string) (v3action.Warnings, error) 36 UpdateApplication(app v3action.Application) (v3action.Application, v3action.Warnings, error) 37 } 38 39 type V3ZeroDowntimePushCommand struct { 40 RequiredArgs flag.AppName `positional-args:"yes"` 41 Buildpacks []string `short:"b" description:"Custom buildpack by name (e.g. my-buildpack) or Git URL (e.g. 'https://github.com/cloudfoundry/java-buildpack.git') or Git URL with a branch or tag (e.g. 'https://github.com/cloudfoundry/java-buildpack.git#v3.3.0' for 'v3.3.0' tag). To use built-in buildpacks only, specify 'default' or 'null'"` 42 StackName string `short:"s" description:"Stack to use (a stack is a pre-built file system, including an operating system, that can run apps)"` 43 DockerImage flag.DockerImage `long:"docker-image" short:"o" description:"Docker image to use (e.g. user/docker-image-name)"` 44 DockerUsername string `long:"docker-username" description:"Repository username; used with password from environment variable CF_DOCKER_PASSWORD"` 45 NoRoute bool `long:"no-route" description:"Do not map a route to this app"` 46 NoStart bool `long:"no-start" description:"Do not stage and start the app after pushing"` 47 WaitUntilDeployed bool `long:"wait-for-deploy-complete" description:"Wait for the entire deployment to complete"` 48 AppPath flag.PathWithExistenceCheck `short:"p" description:"Path to app directory or to a zip file of the contents of the app directory"` 49 dockerPassword interface{} `environmentName:"CF_DOCKER_PASSWORD" environmentDescription:"Password used for private docker repository"` 50 usage interface{} `usage:"CF_NAME v3-zdt-push APP_NAME [-b BUILDPACK]... [-p APP_PATH] [--no-route] [--no-start]\n CF_NAME v3-zdt-push APP_NAME --docker-image [REGISTRY_HOST:PORT/]IMAGE[:TAG] [--docker-username USERNAME] [--no-route] [--no-start]"` 51 envCFStagingTimeout interface{} `environmentName:"CF_STAGING_TIMEOUT" environmentDescription:"Max wait time for buildpack staging, in minutes" environmentDefault:"15"` 52 envCFStartupTimeout interface{} `environmentName:"CF_STARTUP_TIMEOUT" environmentDescription:"Max wait time for app instance startup, in minutes" environmentDefault:"5"` 53 54 UI command.UI 55 Config command.Config 56 NOAAClient v3action.NOAAClient 57 Actor V3PushActor 58 VersionActor V3PushVersionActor 59 SharedActor command.SharedActor 60 AppSummaryDisplayer shared.AppSummaryDisplayer 61 PackageDisplayer shared.PackageDisplayer 62 ProgressBar ProgressBar 63 64 ZdtActor V3ZeroDowntimeVersionActor 65 OriginalV3PushActor OriginalV3PushActor 66 OriginalV2PushActor OriginalV2PushActor 67 } 68 69 func (cmd *V3ZeroDowntimePushCommand) Setup(config command.Config, ui command.UI) error { 70 cmd.UI = ui 71 cmd.Config = config 72 sharedActor := sharedaction.NewActor(config) 73 74 ccClient, uaaClient, err := shared.NewV3BasedClients(config, ui, true, "") 75 if err != nil { 76 return err 77 } 78 v3actor := v3action.NewActor(ccClient, config, sharedActor, nil) 79 cmd.ZdtActor = v3actor 80 cmd.OriginalV3PushActor = v3actor 81 82 ccClientV2, uaaClientV2, err := shared.NewClients(config, ui, true) 83 if err != nil { 84 return err 85 } 86 87 v2Actor := v2action.NewActor(ccClientV2, uaaClientV2, config) 88 89 cmd.SharedActor = sharedActor 90 cmd.OriginalV2PushActor = pushaction.NewActor(v2Actor, v3actor, sharedActor) 91 92 v2AppActor := v2action.NewActor(ccClientV2, uaaClientV2, config) 93 cmd.NOAAClient = shared.NewNOAAClient(ccClient.Info.Logging(), config, uaaClient, ui) 94 95 cmd.AppSummaryDisplayer = shared.AppSummaryDisplayer{ 96 UI: cmd.UI, 97 Config: cmd.Config, 98 Actor: cmd.OriginalV3PushActor, 99 V2AppActor: v2AppActor, 100 AppName: cmd.RequiredArgs.AppName, 101 } 102 cmd.PackageDisplayer = shared.NewPackageDisplayer(cmd.UI, cmd.Config) 103 104 return nil 105 } 106 107 func (cmd V3ZeroDowntimePushCommand) Execute(args []string) error { 108 cmd.UI.DisplayWarning(command.ExperimentalWarning) 109 110 err := cmd.validateArgs() 111 if err != nil { 112 return err 113 } 114 115 err = command.MinimumCCAPIVersionCheck(cmd.ZdtActor.CloudControllerAPIVersion(), ccversion.MinVersionZeroDowntimePushV3) 116 if err != nil { 117 return err 118 } 119 120 err = cmd.SharedActor.CheckTarget(true, true) 121 if err != nil { 122 return err 123 } 124 125 user, err := cmd.Config.CurrentUser() 126 if err != nil { 127 return err 128 } 129 130 if !verifyBuildpacks(cmd.Buildpacks) { 131 return translatableerror.ConflictingBuildpacksError{} 132 } 133 134 var app v3action.Application 135 app, err = cmd.getApplication() 136 if _, ok := err.(actionerror.ApplicationNotFoundError); ok { 137 app, err = cmd.createApplication(user.Name) 138 if err != nil { 139 return err 140 } 141 } else if err != nil { 142 return err 143 } else { 144 app, err = cmd.updateApplication(user.Name, app.GUID) 145 if err != nil { 146 return err 147 } 148 } 149 150 pkg, err := cmd.createPackage() 151 if err != nil { 152 return err 153 } 154 155 if cmd.NoStart { 156 return nil 157 } 158 159 dropletGUID, err := cmd.stagePackage(pkg, user.Name) 160 if err != nil { 161 return err 162 } 163 164 if !cmd.NoRoute { 165 err = cmd.createAndMapRoutes(app) 166 if err != nil { 167 return err 168 } 169 } 170 171 warnings := make(chan v3action.Warnings) 172 done := make(chan bool) 173 go func() { 174 for { 175 select { 176 case message := <-warnings: 177 cmd.UI.DisplayWarnings(message) 178 case <-done: 179 return 180 } 181 } 182 }() 183 184 switch app.State { 185 case constant.ApplicationStopped: 186 err = cmd.setApplicationDroplet(dropletGUID, user.Name) 187 if err != nil { 188 return err 189 } 190 191 err = cmd.restartApplication(app.GUID, user.Name) 192 if err != nil { 193 return err 194 } 195 196 cmd.UI.DisplayText("Waiting for app to start...") 197 err = cmd.ZdtActor.PollStart(app.GUID, warnings) 198 199 case constant.ApplicationStarted: 200 var deploymentGUID string 201 deploymentGUID, err = cmd.createDeployment(app.GUID, user.Name, dropletGUID) 202 if err != nil { 203 return err 204 } 205 206 cmd.UI.DisplayText("Waiting for app to start...") 207 if cmd.WaitUntilDeployed { 208 err = cmd.ZdtActor.PollDeployment(deploymentGUID, warnings) // 209 } else { 210 err = cmd.ZdtActor.ZeroDowntimePollStart(app.GUID, warnings) 211 } 212 default: 213 return fmt.Errorf("inconceivable application state: %s", app.State) 214 } 215 216 done <- true 217 if err != nil { 218 if _, ok := err.(actionerror.StartupTimeoutError); ok { 219 return translatableerror.StartupTimeoutError{ 220 AppName: cmd.RequiredArgs.AppName, 221 BinaryName: cmd.Config.BinaryName(), 222 } 223 } 224 225 return err 226 } 227 228 cmd.UI.DisplayTextWithFlavor("Showing health and status for app {{.AppName}} in org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}...", map[string]interface{}{ 229 "AppName": cmd.RequiredArgs.AppName, 230 "OrgName": cmd.Config.TargetedOrganization().Name, 231 "SpaceName": cmd.Config.TargetedSpace().Name, 232 "Username": user.Name, 233 }) 234 cmd.UI.DisplayNewline() 235 236 return cmd.AppSummaryDisplayer.DisplayAppInfo() 237 } 238 239 func (cmd V3ZeroDowntimePushCommand) validateArgs() error { 240 switch { 241 case cmd.DockerImage.Path != "" && cmd.AppPath != "": 242 return translatableerror.ArgumentCombinationError{ 243 Args: []string{"--docker-image", "-o", "-p"}, 244 } 245 case cmd.DockerImage.Path != "" && len(cmd.Buildpacks) > 0: 246 return translatableerror.ArgumentCombinationError{ 247 Args: []string{"-b", "--docker-image", "-o"}, 248 } 249 case cmd.DockerUsername != "" && cmd.DockerImage.Path == "": 250 return translatableerror.RequiredFlagsError{ 251 Arg1: "--docker-image, -o", Arg2: "--docker-username", 252 } 253 case cmd.DockerUsername != "" && cmd.Config.DockerPassword() == "": 254 return translatableerror.DockerPasswordNotSetError{} 255 } 256 return nil 257 } 258 259 func (cmd V3ZeroDowntimePushCommand) createApplication(userName string) (v3action.Application, error) { 260 appToCreate := v3action.Application{ 261 Name: cmd.RequiredArgs.AppName, 262 } 263 264 if cmd.DockerImage.Path != "" { 265 appToCreate.LifecycleType = constant.AppLifecycleTypeDocker 266 } else { 267 appToCreate.LifecycleType = constant.AppLifecycleTypeBuildpack 268 appToCreate.LifecycleBuildpacks = cmd.Buildpacks 269 appToCreate.StackName = cmd.StackName 270 } 271 272 app, warnings, err := cmd.ZdtActor.CreateApplicationInSpace( 273 appToCreate, 274 cmd.Config.TargetedSpace().GUID, 275 ) 276 cmd.UI.DisplayWarnings(warnings) 277 if err != nil { 278 return v3action.Application{}, err 279 } 280 281 cmd.UI.DisplayTextWithFlavor("Creating app {{.AppName}} in org {{.CurrentOrg}} / space {{.CurrentSpace}} as {{.CurrentUser}}...", map[string]interface{}{ 282 "AppName": cmd.RequiredArgs.AppName, 283 "CurrentSpace": cmd.Config.TargetedSpace().Name, 284 "CurrentOrg": cmd.Config.TargetedOrganization().Name, 285 "CurrentUser": userName, 286 }) 287 288 cmd.UI.DisplayOK() 289 return app, nil 290 } 291 292 func (cmd V3ZeroDowntimePushCommand) getApplication() (v3action.Application, error) { 293 app, warnings, err := cmd.ZdtActor.GetApplicationByNameAndSpace(cmd.RequiredArgs.AppName, cmd.Config.TargetedSpace().GUID) 294 cmd.UI.DisplayWarnings(warnings) 295 if err != nil { 296 return v3action.Application{}, err 297 } 298 299 return app, nil 300 } 301 302 func (cmd V3ZeroDowntimePushCommand) updateApplication(userName string, appGUID string) (v3action.Application, error) { 303 cmd.UI.DisplayTextWithFlavor("Updating app {{.AppName}} in org {{.CurrentOrg}} / space {{.CurrentSpace}} as {{.CurrentUser}}...", map[string]interface{}{ 304 "AppName": cmd.RequiredArgs.AppName, 305 "CurrentSpace": cmd.Config.TargetedSpace().Name, 306 "CurrentOrg": cmd.Config.TargetedOrganization().Name, 307 "CurrentUser": userName, 308 }) 309 310 appToUpdate := v3action.Application{ 311 GUID: appGUID, 312 } 313 314 if cmd.DockerImage.Path != "" { 315 appToUpdate.LifecycleType = constant.AppLifecycleTypeDocker 316 317 } else { 318 appToUpdate.LifecycleType = constant.AppLifecycleTypeBuildpack 319 appToUpdate.LifecycleBuildpacks = cmd.Buildpacks 320 appToUpdate.StackName = cmd.StackName 321 } 322 323 app, warnings, err := cmd.ZdtActor.UpdateApplication(appToUpdate) 324 cmd.UI.DisplayWarnings(warnings) 325 if err != nil { 326 return v3action.Application{}, err 327 } 328 329 cmd.UI.DisplayOK() 330 return app, nil 331 } 332 333 func (cmd V3ZeroDowntimePushCommand) createAndMapRoutes(app v3action.Application) error { 334 cmd.UI.DisplayText("Mapping routes...") 335 routeWarnings, err := cmd.OriginalV2PushActor.CreateAndMapDefaultApplicationRoute(cmd.Config.TargetedOrganization().GUID, cmd.Config.TargetedSpace().GUID, v2action.Application{Name: app.Name, GUID: app.GUID}) 336 cmd.UI.DisplayWarnings(routeWarnings) 337 if err != nil { 338 return err 339 } 340 341 cmd.UI.DisplayOK() 342 return nil 343 } 344 345 func (cmd V3ZeroDowntimePushCommand) createPackage() (v3action.Package, error) { 346 isDockerImage := cmd.DockerImage.Path != "" 347 err := cmd.PackageDisplayer.DisplaySetupMessage(cmd.RequiredArgs.AppName, isDockerImage) 348 if err != nil { 349 return v3action.Package{}, err 350 } 351 352 var ( 353 pkg v3action.Package 354 warnings v3action.Warnings 355 ) 356 357 if isDockerImage { 358 pkg, warnings, err = cmd.ZdtActor.CreateDockerPackageByApplicationNameAndSpace(cmd.RequiredArgs.AppName, cmd.Config.TargetedSpace().GUID, v3action.DockerImageCredentials{Path: cmd.DockerImage.Path, Username: cmd.DockerUsername, Password: cmd.Config.DockerPassword()}) 359 } else { 360 pkg, warnings, err = cmd.ZdtActor.CreateAndUploadBitsPackageByApplicationNameAndSpace(cmd.RequiredArgs.AppName, cmd.Config.TargetedSpace().GUID, string(cmd.AppPath)) 361 } 362 363 cmd.UI.DisplayWarnings(warnings) 364 if err != nil { 365 return v3action.Package{}, err 366 } 367 368 cmd.UI.DisplayOK() 369 return pkg, nil 370 } 371 372 func (cmd V3ZeroDowntimePushCommand) stagePackage(pkg v3action.Package, userName string) (string, error) { 373 cmd.UI.DisplayTextWithFlavor("Staging package for app {{.AppName}} in org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}...", map[string]interface{}{ 374 "AppName": cmd.RequiredArgs.AppName, 375 "OrgName": cmd.Config.TargetedOrganization().Name, 376 "SpaceName": cmd.Config.TargetedSpace().Name, 377 "Username": userName, 378 }) 379 380 logStream, logErrStream, logWarnings, logErr := cmd.ZdtActor.GetStreamingLogsForApplicationByNameAndSpace(cmd.RequiredArgs.AppName, cmd.Config.TargetedSpace().GUID, cmd.NOAAClient) 381 cmd.UI.DisplayWarnings(logWarnings) 382 if logErr != nil { 383 return "", logErr 384 } 385 386 buildStream, warningsStream, errStream := cmd.ZdtActor.StagePackage(pkg.GUID, cmd.RequiredArgs.AppName) 387 droplet, err := shared.PollStage(buildStream, warningsStream, errStream, logStream, logErrStream, cmd.UI) 388 if err != nil { 389 return "", err 390 } 391 392 cmd.UI.DisplayOK() 393 return droplet.GUID, nil 394 } 395 396 func (cmd V3ZeroDowntimePushCommand) setApplicationDroplet(dropletGUID string, userName string) error { 397 cmd.UI.DisplayTextWithFlavor("Setting app {{.AppName}} to droplet {{.DropletGUID}} in org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}...", map[string]interface{}{ 398 "AppName": cmd.RequiredArgs.AppName, 399 "DropletGUID": dropletGUID, 400 "OrgName": cmd.Config.TargetedOrganization().Name, 401 "SpaceName": cmd.Config.TargetedSpace().Name, 402 "Username": userName, 403 }) 404 405 warnings, err := cmd.ZdtActor.SetApplicationDropletByApplicationNameAndSpace(cmd.RequiredArgs.AppName, cmd.Config.TargetedSpace().GUID, dropletGUID) 406 cmd.UI.DisplayWarnings(warnings) 407 if err != nil { 408 return err 409 } 410 411 cmd.UI.DisplayOK() 412 return nil 413 } 414 415 func (cmd V3ZeroDowntimePushCommand) restartApplication(appGUID string, userName string) error { 416 cmd.UI.DisplayTextWithFlavor("Starting app {{.AppName}} in org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}...", map[string]interface{}{ 417 "AppName": cmd.RequiredArgs.AppName, 418 "OrgName": cmd.Config.TargetedOrganization().Name, 419 "SpaceName": cmd.Config.TargetedSpace().Name, 420 "Username": userName, 421 }) 422 423 warnings, err := cmd.ZdtActor.RestartApplication(appGUID) 424 cmd.UI.DisplayWarnings(warnings) 425 if err != nil { 426 return err 427 } 428 cmd.UI.DisplayOK() 429 return nil 430 } 431 432 func (cmd V3ZeroDowntimePushCommand) createDeployment(appGUID string, userName string, dropletGUID string) (string, error) { 433 cmd.UI.DisplayTextWithFlavor("Starting deployment for app {{.AppName}} in org {{.CurrentOrg}} / space {{.CurrentSpace}} as {{.CurrentUser}}...", map[string]interface{}{ 434 "AppName": cmd.RequiredArgs.AppName, 435 "CurrentSpace": cmd.Config.TargetedSpace().Name, 436 "CurrentOrg": cmd.Config.TargetedOrganization().Name, 437 "CurrentUser": userName, 438 }) 439 440 deploymentGUID, warnings, err := cmd.ZdtActor.CreateDeployment(appGUID, dropletGUID) 441 cmd.UI.DisplayWarnings(warnings) 442 if err != nil { 443 return "", err 444 } 445 cmd.UI.DisplayOK() 446 return deploymentGUID, nil 447 }