github.com/cloudfoundry-attic/cli-with-i18n@v6.32.1-0.20171002233121-7401370d3b85+incompatible/command/v3/v3_push_command.go (about) 1 package v3 2 3 import ( 4 "net/http" 5 6 "code.cloudfoundry.org/cli/actor/pushaction" 7 "code.cloudfoundry.org/cli/actor/sharedaction" 8 "code.cloudfoundry.org/cli/actor/v2action" 9 "code.cloudfoundry.org/cli/actor/v3action" 10 "code.cloudfoundry.org/cli/api/cloudcontroller/ccerror" 11 "code.cloudfoundry.org/cli/api/cloudcontroller/ccversion" 12 "code.cloudfoundry.org/cli/command" 13 "code.cloudfoundry.org/cli/command/flag" 14 "code.cloudfoundry.org/cli/command/translatableerror" 15 sharedV2 "code.cloudfoundry.org/cli/command/v2/shared" 16 "code.cloudfoundry.org/cli/command/v3/shared" 17 ) 18 19 //go:generate counterfeiter . V2PushActor 20 21 type V2PushActor interface { 22 CreateAndBindApplicationRoutes(orgGUID string, spaceGUID string, app v2action.Application) (pushaction.Warnings, error) 23 } 24 25 //go:generate counterfeiter . V3PushActor 26 27 type V3PushActor interface { 28 CloudControllerAPIVersion() string 29 CreateAndUploadBitsPackageByApplicationNameAndSpace(appName string, spaceGUID string, bitsPath string) (v3action.Package, v3action.Warnings, error) 30 CreateDockerPackageByApplicationNameAndSpace(appName string, spaceGUID string, dockerImageCredentials v3action.DockerImageCredentials) (v3action.Package, v3action.Warnings, error) 31 CreateApplicationInSpace(app v3action.Application, spaceGUID string) (v3action.Application, v3action.Warnings, error) 32 GetApplicationByNameAndSpace(appName string, spaceGUID string) (v3action.Application, v3action.Warnings, error) 33 GetApplicationSummaryByNameAndSpace(appName string, spaceGUID string) (v3action.ApplicationSummary, v3action.Warnings, error) 34 GetStreamingLogsForApplicationByNameAndSpace(appName string, spaceGUID string, client v3action.NOAAClient) (<-chan *v3action.LogMessage, <-chan error, v3action.Warnings, error) 35 PollStart(appGUID string, warnings chan<- v3action.Warnings) error 36 SetApplicationDroplet(appName string, spaceGUID string, dropletGUID string) (v3action.Warnings, error) 37 StagePackage(packageGUID string, appName string) (<-chan v3action.Droplet, <-chan v3action.Warnings, <-chan error) 38 StartApplication(appGUID string) (v3action.Application, v3action.Warnings, error) 39 StopApplication(appGUID string) (v3action.Warnings, error) 40 UpdateApplication(app v3action.Application) (v3action.Application, v3action.Warnings, error) 41 } 42 43 type V3PushCommand struct { 44 RequiredArgs flag.AppName `positional-args:"yes"` 45 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'"` 46 DockerImage flag.DockerImage `long:"docker-image" short:"o" description:"Docker image to use (e.g. user/docker-image-name)"` 47 DockerUsername string `long:"docker-username" description:"Repository username; used with password from environment variable CF_DOCKER_PASSWORD"` 48 NoRoute bool `long:"no-route" description:"Do not map a route to this app"` 49 AppPath flag.PathWithExistenceCheck `short:"p" description:"Path to app directory or to a zip file of the contents of the app directory"` 50 dockerPassword interface{} `environmentName:"CF_DOCKER_PASSWORD" environmentDescription:"Password used for private docker repository"` 51 52 usage interface{} `usage:"cf v3-push APP_NAME [-b BUILDPACK]... [-p APP_PATH] [--no-route]\n cf v3-push APP_NAME --docker-image [REGISTRY_HOST:PORT/]IMAGE[:TAG] [--docker-username USERNAME] [--no-route]"` 53 envCFStagingTimeout interface{} `environmentName:"CF_STAGING_TIMEOUT" environmentDescription:"Max wait time for buildpack staging, in minutes" environmentDefault:"15"` 54 envCFStartupTimeout interface{} `environmentName:"CF_STARTUP_TIMEOUT" environmentDescription:"Max wait time for app instance startup, in minutes" environmentDefault:"5"` 55 56 UI command.UI 57 Config command.Config 58 NOAAClient v3action.NOAAClient 59 SharedActor command.SharedActor 60 Actor V3PushActor 61 V2PushActor V2PushActor 62 AppSummaryDisplayer shared.AppSummaryDisplayer 63 PackageDisplayer shared.PackageDisplayer 64 } 65 66 func (cmd *V3PushCommand) Setup(config command.Config, ui command.UI) error { 67 cmd.UI = ui 68 cmd.Config = config 69 sharedActor := sharedaction.NewActor(config) 70 71 ccClient, uaaClient, err := shared.NewClients(config, ui, true) 72 if err != nil { 73 if v3Err, ok := err.(ccerror.V3UnexpectedResponseError); ok && v3Err.ResponseCode == http.StatusNotFound { 74 return translatableerror.MinimumAPIVersionNotMetError{MinimumVersion: ccversion.MinVersionV3} 75 } 76 77 return err 78 } 79 cmd.Actor = v3action.NewActor(sharedActor, ccClient, config) 80 81 ccClientV2, uaaClientV2, err := sharedV2.NewClients(config, ui, true) 82 if err != nil { 83 return err 84 } 85 86 v2Actor := v2action.NewActor(ccClientV2, uaaClientV2, config) 87 88 cmd.SharedActor = sharedActor 89 cmd.V2PushActor = pushaction.NewActor(v2Actor, sharedActor) 90 91 v2AppActor := v2action.NewActor(ccClientV2, uaaClientV2, config) 92 cmd.NOAAClient = shared.NewNOAAClient(ccClient.APIInfo.Logging(), config, uaaClient, ui) 93 94 cmd.AppSummaryDisplayer = shared.AppSummaryDisplayer{ 95 UI: cmd.UI, 96 Config: cmd.Config, 97 Actor: cmd.Actor, 98 V2AppRouteActor: v2AppActor, 99 AppName: cmd.RequiredArgs.AppName, 100 } 101 cmd.PackageDisplayer = shared.NewPackageDisplayer(cmd.UI, cmd.Config) 102 103 return nil 104 } 105 106 func (cmd V3PushCommand) Execute(args []string) error { 107 cmd.UI.DisplayText(command.ExperimentalWarning) 108 cmd.UI.DisplayNewline() 109 110 err := cmd.validateArgs() 111 if err != nil { 112 return err 113 } 114 115 err = command.MinimumAPIVersionCheck(cmd.Actor.CloudControllerAPIVersion(), ccversion.MinVersionV3) 116 if err != nil { 117 return err 118 } 119 120 err = cmd.SharedActor.CheckTarget(cmd.Config, true, true) 121 if err != nil { 122 return shared.HandleError(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.(v3action.ApplicationNotFoundError); ok { 137 app, err = cmd.createApplication(user.Name) 138 if err != nil { 139 return shared.HandleError(err) 140 } 141 } else if err != nil { 142 return shared.HandleError(err) 143 } else { 144 app, err = cmd.updateApplication(user.Name, app.GUID) 145 if err != nil { 146 return shared.HandleError(err) 147 } 148 } 149 150 pkg, err := cmd.uploadPackage() 151 if err != nil { 152 return shared.HandleError(err) 153 } 154 155 dropletGUID, err := cmd.stagePackage(pkg, user.Name) 156 if err != nil { 157 return shared.HandleError(err) 158 } 159 160 if app.Started() { 161 err = cmd.stopApplication(app.GUID, user.Name) 162 if err != nil { 163 return shared.HandleError(err) 164 } 165 } 166 167 err = cmd.setApplicationDroplet(dropletGUID, user.Name) 168 if err != nil { 169 return shared.HandleError(err) 170 } 171 172 if !cmd.NoRoute { 173 err = cmd.createAndBindRoutes(app) 174 if err != nil { 175 return shared.HandleError(err) 176 } 177 } 178 179 err = cmd.startApplication(app.GUID, user.Name) 180 if err != nil { 181 return shared.HandleError(err) 182 } 183 184 cmd.UI.DisplayText("Waiting for app to start...") 185 186 warnings := make(chan v3action.Warnings) 187 done := make(chan bool) 188 go func() { 189 for { 190 select { 191 case message := <-warnings: 192 cmd.UI.DisplayWarnings(message) 193 case <-done: 194 return 195 } 196 } 197 }() 198 199 err = cmd.Actor.PollStart(app.GUID, warnings) 200 done <- true 201 202 if err != nil { 203 if _, ok := err.(v3action.StartupTimeoutError); ok { 204 return translatableerror.StartupTimeoutError{ 205 AppName: cmd.RequiredArgs.AppName, 206 BinaryName: cmd.Config.BinaryName(), 207 } 208 } 209 210 return shared.HandleError(err) 211 } 212 213 cmd.UI.DisplayTextWithFlavor("Showing health and status for app {{.AppName}} in org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}...", map[string]interface{}{ 214 "AppName": cmd.RequiredArgs.AppName, 215 "OrgName": cmd.Config.TargetedOrganization().Name, 216 "SpaceName": cmd.Config.TargetedSpace().Name, 217 "Username": user.Name, 218 }) 219 cmd.UI.DisplayNewline() 220 221 return cmd.AppSummaryDisplayer.DisplayAppInfo() 222 } 223 224 func (cmd V3PushCommand) validateArgs() error { 225 switch { 226 case cmd.DockerImage.Path != "" && cmd.AppPath != "": 227 return translatableerror.ArgumentCombinationError{ 228 Args: []string{"--docker-image", "-o", "-p"}, 229 } 230 case cmd.DockerImage.Path != "" && len(cmd.Buildpacks) > 0: 231 return translatableerror.ArgumentCombinationError{ 232 Args: []string{"-b", "--docker-image", "-o"}, 233 } 234 case cmd.DockerUsername != "" && cmd.DockerImage.Path == "": 235 return translatableerror.RequiredFlagsError{ 236 Arg1: "--docker-image, -o", Arg2: "--docker-username", 237 } 238 case cmd.DockerUsername != "" && cmd.Config.DockerPassword() == "": 239 return translatableerror.DockerPasswordNotSetError{} 240 } 241 return nil 242 } 243 244 func (cmd V3PushCommand) createApplication(userName string) (v3action.Application, error) { 245 appToCreate := v3action.Application{ 246 Name: cmd.RequiredArgs.AppName, 247 } 248 249 if cmd.DockerImage.Path != "" { 250 appToCreate.Lifecycle = v3action.AppLifecycle{ 251 Type: v3action.DockerAppLifecycleType, 252 } 253 } else { 254 appToCreate.Lifecycle = v3action.AppLifecycle{ 255 Type: v3action.BuildpackAppLifecycleType, 256 Data: v3action.AppLifecycleData{ 257 Buildpacks: cmd.Buildpacks, 258 }, 259 } 260 } 261 262 app, warnings, err := cmd.Actor.CreateApplicationInSpace( 263 appToCreate, 264 cmd.Config.TargetedSpace().GUID, 265 ) 266 cmd.UI.DisplayWarnings(warnings) 267 if err != nil { 268 return v3action.Application{}, err 269 } 270 271 cmd.UI.DisplayTextWithFlavor("Creating app {{.AppName}} in org {{.CurrentOrg}} / space {{.CurrentSpace}} as {{.CurrentUser}}...", map[string]interface{}{ 272 "AppName": cmd.RequiredArgs.AppName, 273 "CurrentSpace": cmd.Config.TargetedSpace().Name, 274 "CurrentOrg": cmd.Config.TargetedOrganization().Name, 275 "CurrentUser": userName, 276 }) 277 278 cmd.UI.DisplayOK() 279 cmd.UI.DisplayNewline() 280 return app, nil 281 } 282 283 func (cmd V3PushCommand) getApplication() (v3action.Application, error) { 284 app, warnings, err := cmd.Actor.GetApplicationByNameAndSpace(cmd.RequiredArgs.AppName, cmd.Config.TargetedSpace().GUID) 285 cmd.UI.DisplayWarnings(warnings) 286 if err != nil { 287 return v3action.Application{}, err 288 } 289 290 return app, nil 291 } 292 293 func (cmd V3PushCommand) updateApplication(userName string, appGUID string) (v3action.Application, error) { 294 cmd.UI.DisplayTextWithFlavor("Updating app {{.AppName}} in org {{.CurrentOrg}} / space {{.CurrentSpace}} as {{.CurrentUser}}...", map[string]interface{}{ 295 "AppName": cmd.RequiredArgs.AppName, 296 "CurrentSpace": cmd.Config.TargetedSpace().Name, 297 "CurrentOrg": cmd.Config.TargetedOrganization().Name, 298 "CurrentUser": userName, 299 }) 300 301 appToUpdate := v3action.Application{ 302 GUID: appGUID, 303 } 304 305 if cmd.DockerImage.Path != "" { 306 appToUpdate.Lifecycle = v3action.AppLifecycle{ 307 Type: v3action.DockerAppLifecycleType, 308 } 309 } else { 310 appToUpdate.Lifecycle = v3action.AppLifecycle{ 311 Type: v3action.BuildpackAppLifecycleType, 312 Data: v3action.AppLifecycleData{ 313 Buildpacks: cmd.Buildpacks, 314 }, 315 } 316 } 317 318 app, warnings, err := cmd.Actor.UpdateApplication(appToUpdate) 319 cmd.UI.DisplayWarnings(warnings) 320 if err != nil { 321 return v3action.Application{}, err 322 } 323 324 cmd.UI.DisplayOK() 325 cmd.UI.DisplayNewline() 326 return app, nil 327 } 328 329 func (cmd V3PushCommand) createAndBindRoutes(app v3action.Application) error { 330 cmd.UI.DisplayText("Mapping routes...") 331 routeWarnings, err := cmd.V2PushActor.CreateAndBindApplicationRoutes(cmd.Config.TargetedOrganization().GUID, cmd.Config.TargetedSpace().GUID, v2action.Application{Name: app.Name, GUID: app.GUID}) 332 cmd.UI.DisplayWarnings(routeWarnings) 333 if err != nil { 334 return err 335 } 336 337 cmd.UI.DisplayOK() 338 cmd.UI.DisplayNewline() 339 return nil 340 } 341 342 func (cmd V3PushCommand) uploadPackage() (v3action.Package, error) { 343 isDockerImage := (cmd.DockerImage.Path != "") 344 err := cmd.PackageDisplayer.DisplaySetupMessage(cmd.RequiredArgs.AppName, isDockerImage) 345 if err != nil { 346 return v3action.Package{}, err 347 } 348 349 var ( 350 pkg v3action.Package 351 warnings v3action.Warnings 352 ) 353 354 if isDockerImage { 355 pkg, warnings, err = cmd.Actor.CreateDockerPackageByApplicationNameAndSpace(cmd.RequiredArgs.AppName, cmd.Config.TargetedSpace().GUID, v3action.DockerImageCredentials{Path: cmd.DockerImage.Path, Username: cmd.DockerUsername, Password: cmd.Config.DockerPassword()}) 356 } else { 357 pkg, warnings, err = cmd.Actor.CreateAndUploadBitsPackageByApplicationNameAndSpace(cmd.RequiredArgs.AppName, cmd.Config.TargetedSpace().GUID, string(cmd.AppPath)) 358 } 359 360 cmd.UI.DisplayWarnings(warnings) 361 if err != nil { 362 return v3action.Package{}, err 363 } 364 365 cmd.UI.DisplayOK() 366 cmd.UI.DisplayNewline() 367 return pkg, nil 368 } 369 370 func (cmd V3PushCommand) stagePackage(pkg v3action.Package, userName string) (string, error) { 371 cmd.UI.DisplayTextWithFlavor("Staging package for app {{.AppName}} in org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}...", map[string]interface{}{ 372 "AppName": cmd.RequiredArgs.AppName, 373 "OrgName": cmd.Config.TargetedOrganization().Name, 374 "SpaceName": cmd.Config.TargetedSpace().Name, 375 "Username": userName, 376 }) 377 378 logStream, logErrStream, logWarnings, logErr := cmd.Actor.GetStreamingLogsForApplicationByNameAndSpace(cmd.RequiredArgs.AppName, cmd.Config.TargetedSpace().GUID, cmd.NOAAClient) 379 cmd.UI.DisplayWarnings(logWarnings) 380 if logErr != nil { 381 return "", logErr 382 } 383 384 buildStream, warningsStream, errStream := cmd.Actor.StagePackage(pkg.GUID, cmd.RequiredArgs.AppName) 385 droplet, err := shared.PollStage(buildStream, warningsStream, errStream, logStream, logErrStream, cmd.UI) 386 if err != nil { 387 return "", err 388 } 389 390 cmd.UI.DisplayOK() 391 cmd.UI.DisplayNewline() 392 return droplet.GUID, nil 393 } 394 395 func (cmd V3PushCommand) setApplicationDroplet(dropletGUID string, userName string) error { 396 cmd.UI.DisplayTextWithFlavor("Setting app {{.AppName}} to droplet {{.DropletGUID}} in org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}...", map[string]interface{}{ 397 "AppName": cmd.RequiredArgs.AppName, 398 "DropletGUID": dropletGUID, 399 "OrgName": cmd.Config.TargetedOrganization().Name, 400 "SpaceName": cmd.Config.TargetedSpace().Name, 401 "Username": userName, 402 }) 403 404 warnings, err := cmd.Actor.SetApplicationDroplet(cmd.RequiredArgs.AppName, cmd.Config.TargetedSpace().GUID, dropletGUID) 405 cmd.UI.DisplayWarnings(warnings) 406 if err != nil { 407 return err 408 } 409 410 cmd.UI.DisplayOK() 411 cmd.UI.DisplayNewline() 412 return nil 413 } 414 415 func (cmd V3PushCommand) startApplication(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.Actor.StartApplication(appGUID) 424 cmd.UI.DisplayWarnings(warnings) 425 if err != nil { 426 return err 427 } 428 cmd.UI.DisplayOK() 429 cmd.UI.DisplayNewline() 430 return nil 431 } 432 433 func (cmd V3PushCommand) stopApplication(appGUID string, userName string) error { 434 cmd.UI.DisplayTextWithFlavor("Stopping app {{.AppName}} in org {{.CurrentOrg}} / space {{.CurrentSpace}} as {{.CurrentUser}}...", map[string]interface{}{ 435 "AppName": cmd.RequiredArgs.AppName, 436 "CurrentSpace": cmd.Config.TargetedSpace().Name, 437 "CurrentOrg": cmd.Config.TargetedOrganization().Name, 438 "CurrentUser": userName, 439 }) 440 441 warnings, err := cmd.Actor.StopApplication(appGUID) 442 cmd.UI.DisplayWarnings(warnings) 443 if err != nil { 444 return err 445 } 446 cmd.UI.DisplayOK() 447 cmd.UI.DisplayNewline() 448 return nil 449 } 450 451 func verifyBuildpacks(buildpacks []string) bool { 452 if len(buildpacks) < 2 { 453 return true 454 } 455 456 for _, buildpack := range buildpacks { 457 if buildpack == "default" || buildpack == "null" { 458 return false 459 } 460 } 461 return true 462 }