github.com/randomtask1155/cli@v6.41.1-0.20181227003417-a98eed78cbde+incompatible/command/v7/push_command.go (about) 1 package v7 2 3 import ( 4 "io/ioutil" 5 "os" 6 "path/filepath" 7 8 "code.cloudfoundry.org/cli/actor/actionerror" 9 "code.cloudfoundry.org/cli/actor/sharedaction" 10 "code.cloudfoundry.org/cli/actor/v2action" 11 "code.cloudfoundry.org/cli/actor/v3action" 12 "code.cloudfoundry.org/cli/actor/v7action" 13 "code.cloudfoundry.org/cli/actor/v7pushaction" 14 "code.cloudfoundry.org/cli/command" 15 "code.cloudfoundry.org/cli/command/flag" 16 "code.cloudfoundry.org/cli/command/translatableerror" 17 v6shared "code.cloudfoundry.org/cli/command/v6/shared" 18 "code.cloudfoundry.org/cli/command/v7/shared" 19 "code.cloudfoundry.org/cli/util/progressbar" 20 21 log "github.com/sirupsen/logrus" 22 ) 23 24 //go:generate counterfeiter . ProgressBar 25 26 type ProgressBar interface { 27 v7pushaction.ProgressBar 28 Complete() 29 Ready() 30 } 31 32 //go:generate counterfeiter . PushActor 33 34 type PushActor interface { 35 // Actualize applies any necessary changes. 36 Actualize(state v7pushaction.PushState, progressBar v7pushaction.ProgressBar) (<-chan v7pushaction.PushState, <-chan v7pushaction.Event, <-chan v7pushaction.Warnings, <-chan error) 37 // Conceptualize figures out the state of the world. 38 Conceptualize(appName string, spaceGUID string, orgGUID string, currentDir string, flagOverrides v7pushaction.FlagOverrides, manifest []byte) ([]v7pushaction.PushState, v7pushaction.Warnings, error) 39 } 40 41 //go:generate counterfeiter . V7ActorForPush 42 43 type V7ActorForPush interface { 44 AppActor 45 GetStreamingLogsForApplicationByNameAndSpace(appName string, spaceGUID string, client v7action.NOAAClient) (<-chan *v7action.LogMessage, <-chan error, v7action.Warnings, error) 46 RestartApplication(appGUID string) (v7action.Warnings, error) 47 } 48 49 type PushCommand struct { 50 RequiredArgs flag.AppName `positional-args:"yes"` 51 Buildpacks []string `long:"buildpack" 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'"` 52 DockerImage flag.DockerImage `long:"docker-image" short:"o" description:"Docker image to use (e.g. user/docker-image-name)"` 53 DockerUsername string `long:"docker-username" description:"Repository username; used with password from environment variable CF_DOCKER_PASSWORD"` 54 HealthCheckType flag.HealthCheckType `long:"health-check-type" short:"u" description:"Application health check type: 'port' (default), 'process', 'http' (implies endpoint '/')"` 55 Instances flag.Instances `long:"instances" short:"i" description:"Number of instances"` 56 PathToManifest flag.PathWithExistenceCheck `long:"manifest" short:"f" description:"Path to manifest"` 57 Memory flag.Megabytes `long:"memory" short:"m" description:"Memory limit (e.g. 256M, 1024M, 1G)"` 58 NoRoute bool `long:"no-route" description:"Do not map a route to this app"` 59 NoStart bool `long:"no-start" description:"Do not stage and start the app after pushing"` 60 AppPath flag.PathWithExistenceCheck `long:"path" short:"p" description:"Path to app directory or to a zip file of the contents of the app directory"` 61 StartCommand flag.Command `long:"start-command" short:"c" description:"Startup command, set to null to reset to default start command"` 62 dockerPassword interface{} `environmentName:"CF_DOCKER_PASSWORD" environmentDescription:"Password used for private docker repository"` 63 usage interface{} `usage:"CF_NAME push APP_NAME [-b BUILDPACK]... [-p APP_PATH] [--no-route] [--no-start]\n CF_NAME push APP_NAME --docker-image [REGISTRY_HOST:PORT/]IMAGE[:TAG] [--docker-username USERNAME] [--no-route] [--no-start]"` 64 envCFStagingTimeout interface{} `environmentName:"CF_STAGING_TIMEOUT" environmentDescription:"Max wait time for buildpack staging, in minutes" environmentDefault:"15"` 65 envCFStartupTimeout interface{} `environmentName:"CF_STARTUP_TIMEOUT" environmentDescription:"Max wait time for app instance startup, in minutes" environmentDefault:"5"` 66 67 UI command.UI 68 Config command.Config 69 NOAAClient v3action.NOAAClient 70 Actor PushActor 71 VersionActor V7ActorForPush 72 SharedActor command.SharedActor 73 RouteActor v7action.RouteActor 74 ProgressBar ProgressBar 75 PWD string 76 } 77 78 func (cmd *PushCommand) Setup(config command.Config, ui command.UI) error { 79 cmd.Config = config 80 cmd.UI = ui 81 cmd.ProgressBar = progressbar.NewProgressBar() 82 83 sharedActor := sharedaction.NewActor(config) 84 cmd.SharedActor = sharedActor 85 86 ccClient, uaaClient, err := v6shared.NewV3BasedClients(config, ui, true, "") 87 if err != nil { 88 return err 89 } 90 91 v7actor := v7action.NewActor(ccClient, config, sharedActor, uaaClient) 92 cmd.VersionActor = v7actor 93 ccClientV2, uaaClientV2, err := v6shared.NewClients(config, ui, true) 94 if err != nil { 95 return err 96 } 97 98 v2Actor := v2action.NewActor(ccClientV2, uaaClientV2, config) 99 cmd.RouteActor = v2Actor 100 cmd.Actor = v7pushaction.NewActor(v2Actor, v7actor, sharedActor) 101 102 cmd.NOAAClient = v6shared.NewNOAAClient(ccClient.Info.Logging(), config, uaaClient, ui) 103 104 currentDir, err := os.Getwd() 105 cmd.PWD = currentDir 106 107 return err 108 } 109 110 func (cmd PushCommand) Execute(args []string) error { 111 cmd.UI.DisplayWarning(command.ExperimentalWarning) 112 113 err := cmd.SharedActor.CheckTarget(true, true) 114 if err != nil { 115 return err 116 } 117 118 err = cmd.ValidateFlags() 119 if err != nil { 120 return err 121 } 122 123 overrides, err := cmd.GetFlagOverrides() 124 if err != nil { 125 return err 126 } 127 128 user, err := cmd.Config.CurrentUser() 129 if err != nil { 130 return err 131 } 132 133 cmd.UI.DisplayTextWithFlavor("Pushing app {{.AppName}} to org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}...", map[string]interface{}{ 134 "AppName": cmd.RequiredArgs.AppName, 135 "OrgName": cmd.Config.TargetedOrganization().Name, 136 "SpaceName": cmd.Config.TargetedSpace().Name, 137 "Username": user.Name, 138 }) 139 140 cmd.UI.DisplayText("Getting app info...") 141 142 manifest, err := cmd.readManifest() 143 if err != nil { 144 return err 145 } 146 147 log.Info("generating the app state") 148 pushState, warnings, err := cmd.Actor.Conceptualize( 149 cmd.RequiredArgs.AppName, 150 cmd.Config.TargetedSpace().GUID, 151 cmd.Config.TargetedOrganization().GUID, 152 cmd.PWD, 153 overrides, 154 manifest, 155 ) 156 cmd.UI.DisplayWarnings(warnings) 157 if err != nil { 158 return err 159 } 160 log.WithField("number of states", len(pushState)).Debug("completed generating state") 161 162 for _, state := range pushState { 163 log.WithField("app_name", state.Application.Name).Info("actualizing") 164 stateStream, eventStream, warningsStream, errorStream := cmd.Actor.Actualize(state, cmd.ProgressBar) 165 updatedState, err := cmd.processApplyStreams(state.Application.Name, stateStream, eventStream, warningsStream, errorStream) 166 if err != nil { 167 return err 168 } 169 170 anyProcessCrashed := false 171 if !cmd.NoStart { 172 cmd.UI.DisplayNewline() 173 cmd.UI.DisplayText("Waiting for app to start...") 174 warnings, restartErr := cmd.VersionActor.RestartApplication(updatedState.Application.GUID) 175 cmd.UI.DisplayWarnings(warnings) 176 177 if restartErr != nil { 178 if _, ok := restartErr.(actionerror.StartupTimeoutError); ok { 179 return translatableerror.StartupTimeoutError{ 180 AppName: cmd.RequiredArgs.AppName, 181 BinaryName: cmd.Config.BinaryName(), 182 } 183 } else if _, ok := restartErr.(actionerror.AllInstancesCrashedError); ok { 184 anyProcessCrashed = true 185 } else { 186 return restartErr 187 } 188 } 189 } 190 log.Info("getting application summary info") 191 summary, warnings, err := cmd.VersionActor.GetApplicationSummaryByNameAndSpace(cmd.RequiredArgs.AppName, cmd.Config.TargetedSpace().GUID, true, cmd.RouteActor) 192 cmd.UI.DisplayWarnings(warnings) 193 if err != nil { 194 return err 195 } 196 197 cmd.UI.DisplayNewline() 198 appSummaryDisplayer := shared.NewAppSummaryDisplayer(cmd.UI) 199 appSummaryDisplayer.AppDisplay(summary, true) 200 201 if anyProcessCrashed { 202 return translatableerror.ApplicationUnableToStartError{ 203 AppName: cmd.RequiredArgs.AppName, 204 BinaryName: cmd.Config.BinaryName(), 205 } 206 } 207 } 208 209 return nil 210 } 211 212 func (cmd PushCommand) processApplyStreams( 213 appName string, 214 stateStream <-chan v7pushaction.PushState, 215 eventStream <-chan v7pushaction.Event, 216 warningsStream <-chan v7pushaction.Warnings, 217 errorStream <-chan error, 218 ) (v7pushaction.PushState, error) { 219 var stateClosed, eventClosed, warningsClosed, errClosed, complete bool 220 var updateState v7pushaction.PushState 221 222 for { 223 select { 224 case state, ok := <-stateStream: 225 if !ok { 226 if !stateClosed { 227 log.Debug("processing config stream closed") 228 } 229 stateClosed = true 230 break 231 } 232 updateState = state 233 case event, ok := <-eventStream: 234 if !ok { 235 if !eventClosed { 236 log.Debug("processing event stream closed") 237 } 238 eventClosed = true 239 break 240 } 241 complete = cmd.processEvent(appName, event) 242 case warnings, ok := <-warningsStream: 243 if !ok { 244 if !warningsClosed { 245 log.Debug("processing warnings stream closed") 246 } 247 warningsClosed = true 248 break 249 } 250 cmd.UI.DisplayWarnings(warnings) 251 case err, ok := <-errorStream: 252 if !ok { 253 if !errClosed { 254 log.Debug("processing error stream closed") 255 } 256 errClosed = true 257 break 258 } 259 return v7pushaction.PushState{}, err 260 } 261 262 if stateClosed && eventClosed && warningsClosed && complete { 263 break 264 } 265 } 266 267 return updateState, nil 268 } 269 270 func (cmd PushCommand) processEvent(appName string, event v7pushaction.Event) bool { 271 log.Infoln("received apply event:", event) 272 273 switch event { 274 case v7pushaction.SkippingApplicationCreation: 275 cmd.UI.DisplayTextWithFlavor("Updating app {{.AppName}}...", map[string]interface{}{ 276 "AppName": appName, 277 }) 278 case v7pushaction.CreatingApplication: 279 cmd.UI.DisplayTextWithFlavor("Creating app {{.AppName}}...", map[string]interface{}{ 280 "AppName": appName, 281 }) 282 case v7pushaction.CreatingAndMappingRoutes: 283 cmd.UI.DisplayText("Mapping routes...") 284 case v7pushaction.CreatingArchive: 285 cmd.UI.DisplayText("Packaging files to upload...") 286 case v7pushaction.UploadingApplicationWithArchive: 287 cmd.UI.DisplayText("Uploading files...") 288 log.Debug("starting progress bar") 289 cmd.ProgressBar.Ready() 290 case v7pushaction.RetryUpload: 291 cmd.UI.DisplayText("Retrying upload due to an error...") 292 case v7pushaction.UploadWithArchiveComplete: 293 cmd.ProgressBar.Complete() 294 cmd.UI.DisplayNewline() 295 cmd.UI.DisplayText("Waiting for API to complete processing files...") 296 case v7pushaction.StoppingApplication: 297 cmd.UI.DisplayText("Stopping Application...") 298 case v7pushaction.StoppingApplicationComplete: 299 cmd.UI.DisplayText("Application Stopped") 300 case v7pushaction.StartingStaging: 301 cmd.UI.DisplayNewline() 302 cmd.UI.DisplayText("Staging app and tracing logs...") 303 logStream, errStream, warnings, _ := cmd.VersionActor.GetStreamingLogsForApplicationByNameAndSpace(appName, cmd.Config.TargetedSpace().GUID, cmd.NOAAClient) 304 cmd.UI.DisplayWarnings(warnings) 305 go cmd.getLogs(logStream, errStream) 306 case v7pushaction.StagingComplete: 307 cmd.NOAAClient.Close() 308 case v7pushaction.Complete: 309 return true 310 default: 311 log.WithField("event", event).Debug("ignoring event") 312 } 313 return false 314 } 315 316 func (cmd PushCommand) getLogs(logStream <-chan *v7action.LogMessage, errStream <-chan error) { 317 for { 318 select { 319 case logMessage, open := <-logStream: 320 if !open { 321 return 322 } 323 if logMessage.Staging() { 324 cmd.UI.DisplayLogMessage(logMessage, false) 325 } 326 case err, open := <-errStream: 327 if !open { 328 return 329 } 330 _, ok := err.(actionerror.NOAATimeoutError) 331 if ok { 332 cmd.UI.DisplayWarning("timeout connecting to log server, no log will be shown") 333 } 334 cmd.UI.DisplayWarning(err.Error()) 335 } 336 } 337 } 338 339 func (cmd PushCommand) readManifest() ([]byte, error) { 340 log.Info("reading manifest if exists") 341 342 if len(cmd.PathToManifest) != 0 { 343 log.WithField("manifestPath", cmd.PathToManifest).Debug("reading '-f' provided manifest") 344 return ioutil.ReadFile(string(cmd.PathToManifest)) 345 } 346 347 manifestPath := filepath.Join(cmd.PWD, "manifest.yml") 348 log.WithField("manifestPath", manifestPath).Debug("path to manifest") 349 350 manifest, err := ioutil.ReadFile(manifestPath) 351 if err != nil && !os.IsNotExist(err) { 352 log.Errorln("reading manifest:", err) 353 return nil, err 354 } else if os.IsNotExist(err) { 355 log.Debug("no manifest exists") 356 } 357 return manifest, nil 358 } 359 360 func (cmd PushCommand) GetFlagOverrides() (v7pushaction.FlagOverrides, error) { 361 var dockerPassword string 362 if cmd.DockerUsername != "" { 363 if cmd.Config.DockerPassword() == "" { 364 var err error 365 cmd.UI.DisplayText("Environment variable CF_DOCKER_PASSWORD not set.") 366 dockerPassword, err = cmd.UI.DisplayPasswordPrompt("Docker password") 367 if err != nil { 368 return v7pushaction.FlagOverrides{}, err 369 } 370 } else { 371 cmd.UI.DisplayText("Using docker repository password from environment variable CF_DOCKER_PASSWORD.") 372 dockerPassword = cmd.Config.DockerPassword() 373 } 374 } 375 376 return v7pushaction.FlagOverrides{ 377 Buildpacks: cmd.Buildpacks, 378 DockerImage: cmd.DockerImage.Path, 379 DockerPassword: dockerPassword, 380 DockerUsername: cmd.DockerUsername, 381 HealthCheckType: cmd.HealthCheckType.Type, 382 Instances: cmd.Instances.NullInt, 383 Memory: cmd.Memory.NullUint64, 384 ProvidedAppPath: string(cmd.AppPath), 385 SkipRouteCreation: cmd.NoRoute, 386 StartCommand: cmd.StartCommand.FilteredString, 387 NoStart: cmd.NoStart, 388 }, nil 389 } 390 391 func (cmd PushCommand) ValidateFlags() error { 392 switch { 393 case cmd.DockerUsername != "" && cmd.DockerImage.Path == "": 394 return translatableerror.RequiredFlagsError{ 395 Arg1: "--docker-image, -o", 396 Arg2: "--docker-username", 397 } 398 399 case cmd.DockerImage.Path != "" && cmd.Buildpacks != nil: 400 return translatableerror.ArgumentCombinationError{ 401 Args: []string{ 402 "--buildpack, -b", 403 "--docker-image, -o", 404 }, 405 } 406 407 case cmd.DockerImage.Path != "" && cmd.AppPath != "": 408 return translatableerror.ArgumentCombinationError{ 409 Args: []string{ 410 "--docker-image, -o", 411 "--path, -p", 412 }, 413 } 414 } 415 return nil 416 }