github.com/liamawhite/cli-with-i18n@v6.32.1-0.20171122084555-dede0a5c3448+incompatible/command/v2/v2_push_command.go (about) 1 package v2 2 3 import ( 4 "os" 5 "path/filepath" 6 7 "github.com/cloudfoundry/noaa/consumer" 8 "github.com/liamawhite/cli-with-i18n/actor/pushaction" 9 "github.com/liamawhite/cli-with-i18n/actor/sharedaction" 10 "github.com/liamawhite/cli-with-i18n/actor/v2action" 11 oldcmd "github.com/liamawhite/cli-with-i18n/cf/cmd" 12 "github.com/liamawhite/cli-with-i18n/command" 13 "github.com/liamawhite/cli-with-i18n/command/flag" 14 "github.com/liamawhite/cli-with-i18n/command/translatableerror" 15 "github.com/liamawhite/cli-with-i18n/command/v2/shared" 16 "github.com/liamawhite/cli-with-i18n/util/configv3" 17 "github.com/liamawhite/cli-with-i18n/util/manifest" 18 "github.com/liamawhite/cli-with-i18n/util/progressbar" 19 log "github.com/Sirupsen/logrus" 20 ) 21 22 //go:generate counterfeiter . ProgressBar 23 24 type ProgressBar interface { 25 pushaction.ProgressBar 26 Complete() 27 Ready() 28 } 29 30 //go:generate counterfeiter . V2PushActor 31 32 type V2PushActor interface { 33 Apply(config pushaction.ApplicationConfig, progressBar pushaction.ProgressBar) (<-chan pushaction.ApplicationConfig, <-chan pushaction.Event, <-chan pushaction.Warnings, <-chan error) 34 ConvertToApplicationConfigs(orgGUID string, spaceGUID string, noStart bool, apps []manifest.Application) ([]pushaction.ApplicationConfig, pushaction.Warnings, error) 35 MergeAndValidateSettingsAndManifests(cmdSettings pushaction.CommandLineSettings, apps []manifest.Application) ([]manifest.Application, error) 36 ReadManifest(pathToManifest string) ([]manifest.Application, error) 37 } 38 39 type V2PushCommand struct { 40 OptionalArgs flag.OptionalAppName `positional-args:"yes"` 41 Buildpack flag.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'"` 42 Command flag.Command `short:"c" description:"Startup command, set to null to reset to default start command"` 43 // Domain string `short:"d" description:"Domain (e.g. example.com)"` 44 DockerImage flag.DockerImage `long:"docker-image" short:"o" description:"Docker-image to be used (e.g. user/docker-image-name)"` 45 DockerUsername string `long:"docker-username" description:"Repository username; used with password from environment variable CF_DOCKER_PASSWORD"` 46 PathToManifest flag.PathWithExistenceCheck `short:"f" description:"Path to manifest"` 47 HealthCheckType flag.HealthCheckType `long:"health-check-type" short:"u" description:"Application health check type (Default: 'port', 'none' accepted for 'process', 'http' implies endpoint '/')"` 48 // Hostname string `long:"hostname" short:"n" description:"Hostname (e.g. my-subdomain)"` 49 Instances flag.Instances `short:"i" description:"Number of instances"` 50 DiskQuota flag.Megabytes `short:"k" description:"Disk limit (e.g. 256M, 1024M, 1G)"` 51 Memory flag.Megabytes `short:"m" description:"Memory limit (e.g. 256M, 1024M, 1G)"` 52 // NoHostname bool `long:"no-hostname" description:"Map the root domain to this app"` 53 NoManifest bool `long:"no-manifest" description:"Ignore manifest file"` 54 // NoRoute bool `long:"no-route" description:"Do not map a route to this app and remove routes from previous pushes of this app"` 55 NoStart bool `long:"no-start" description:"Do not start an app after pushing"` 56 AppPath flag.PathWithExistenceCheck `short:"p" description:"Path to app directory or to a zip file of the contents of the app directory"` 57 // RandomRoute bool `long:"random-route" description:"Create a random route for this app"` 58 // RoutePath string `long:"route-path" description:"Path for the route"` 59 StackName string `short:"s" description:"Stack to use (a stack is a pre-built file system, including an operating system, that can run apps)"` 60 HealthCheckTimeout int `short:"t" description:"Time (in seconds) allowed to elapse between starting up an app and the first healthy response from the app"` 61 envCFStagingTimeout interface{} `environmentName:"CF_STAGING_TIMEOUT" environmentDescription:"Max wait time for buildpack staging, in minutes" environmentDefault:"15"` 62 envCFStartupTimeout interface{} `environmentName:"CF_STARTUP_TIMEOUT" environmentDescription:"Max wait time for app instance startup, in minutes" environmentDefault:"5"` 63 dockerPassword interface{} `environmentName:"CF_DOCKER_PASSWORD" environmentDescription:"Password used for private docker repository"` 64 65 usage interface{} `usage:"cf v2-push APP_NAME [-b BUILDPACK_NAME] [-c COMMAND] [-f MANIFEST_PATH | --no-manifest] [--no-start]\n [-i NUM_INSTANCES] [-k DISK] [-m MEMORY] [-p PATH] [-s STACK] [-t HEALTH_TIMEOUT] [-u (process | port | http)]\n [--no-route | --random-route | --hostname HOST | --no-hostname] [-d DOMAIN] [--route-path ROUTE_PATH]\n\n cf v2-push APP_NAME --docker-image [REGISTRY_HOST:PORT/]IMAGE[:TAG] [--docker-username USERNAME]\n [-c COMMAND] [-f MANIFEST_PATH | --no-manifest] [--no-start]\n [-i NUM_INSTANCES] [-k DISK] [-m MEMORY] [-t HEALTH_TIMEOUT] [-u (process | port | http)]\n [--no-route | --random-route | --hostname HOST | --no-hostname] [-d DOMAIN] [--route-path ROUTE_PATH]\n\n cf v2-push -f MANIFEST_WITH_MULTIPLE_APPS_PATH [APP_NAME] [--no-start]"` 66 relatedCommands interface{} `related_commands:"apps, create-app-manifest, logs, ssh, start"` 67 68 UI command.UI 69 Config command.Config 70 SharedActor command.SharedActor 71 Actor V2PushActor 72 ProgressBar ProgressBar 73 74 RestartActor RestartActor 75 NOAAClient *consumer.Consumer 76 } 77 78 func (cmd *V2PushCommand) Setup(config command.Config, ui command.UI) error { 79 cmd.UI = ui 80 cmd.Config = config 81 sharedActor := sharedaction.NewActor(config) 82 83 ccClient, uaaClient, err := shared.NewClients(config, ui, true) 84 if err != nil { 85 return err 86 } 87 v2Actor := v2action.NewActor(ccClient, uaaClient, config) 88 cmd.RestartActor = v2Actor 89 cmd.Actor = pushaction.NewActor(v2Actor, sharedActor) 90 cmd.SharedActor = sharedActor 91 cmd.NOAAClient = shared.NewNOAAClient(ccClient.DopplerEndpoint(), config, uaaClient, ui) 92 93 cmd.ProgressBar = progressbar.NewProgressBar() 94 return nil 95 } 96 97 func (cmd V2PushCommand) Execute(args []string) error { 98 cmd.UI.DisplayWarning(command.ExperimentalWarning) 99 100 err := cmd.SharedActor.CheckTarget(cmd.Config, true, true) 101 if err != nil { 102 return shared.HandleError(err) 103 } 104 105 user, err := cmd.Config.CurrentUser() 106 if err != nil { 107 return shared.HandleError(err) 108 } 109 110 log.Info("collating flags") 111 cliSettings, err := cmd.GetCommandLineSettings() 112 if err != nil { 113 log.Errorln("reading flags:", err) 114 return shared.HandleError(err) 115 } 116 117 log.Info("checking manifest") 118 rawApps, err := cmd.findAndReadManifest(cliSettings) 119 if _, ok := err.(manifest.UnsupportedFieldsError); ok { 120 // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 121 // The following section is not tested as it calls into the old code. 122 // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 123 cmd.UI.DisplayWarning("*** Global attributes/inheritance in app manifest are not supported in v2-push, delegating to old push ***") 124 var args []string 125 for _, arg := range os.Args { 126 if arg == "v2-push" { 127 args = append(args, "push") 128 } else { 129 args = append(args, arg) 130 } 131 } 132 133 oldcmd.Main(os.Getenv("CF_TRACE"), args) 134 return nil 135 } else if err != nil { 136 log.Errorln("reading manifest:", err) 137 return shared.HandleError(err) 138 } 139 140 log.Info("merging manifest and command flags") 141 manifestApplications, err := cmd.Actor.MergeAndValidateSettingsAndManifests(cliSettings, rawApps) 142 if err != nil { 143 log.Errorln("merging manifest:", err) 144 return shared.HandleError(err) 145 } 146 147 cmd.UI.DisplayText("Getting app info...") 148 149 log.Info("converting manifests to ApplicationConfigs") 150 appConfigs, warnings, err := cmd.Actor.ConvertToApplicationConfigs( 151 cmd.Config.TargetedOrganization().GUID, 152 cmd.Config.TargetedSpace().GUID, 153 cmd.NoStart, 154 manifestApplications, 155 ) 156 cmd.UI.DisplayWarnings(warnings) 157 if err != nil { 158 log.Errorln("converting manifest:", err) 159 return shared.HandleError(err) 160 } 161 162 for _, appConfig := range appConfigs { 163 if appConfig.CreatingApplication() { 164 cmd.UI.DisplayText("Creating app with these attributes...") 165 } else { 166 cmd.UI.DisplayText("Updating app with these attributes...") 167 } 168 log.Infoln("starting create/update:", appConfig.DesiredApplication.Name) 169 changes := shared.GetApplicationChanges(appConfig) 170 err := cmd.UI.DisplayChangesForPush(changes) 171 if err != nil { 172 log.Errorln("display changes:", err) 173 return shared.HandleError(err) 174 } 175 cmd.UI.DisplayNewline() 176 } 177 178 for appNumber, appConfig := range appConfigs { 179 if appConfig.CreatingApplication() { 180 cmd.UI.DisplayTextWithFlavor("Creating app {{.AppName}}...", map[string]interface{}{ 181 "AppName": appConfig.DesiredApplication.Name, 182 }) 183 } else { 184 cmd.UI.DisplayTextWithFlavor("Updating app {{.AppName}}...", map[string]interface{}{ 185 "AppName": appConfig.DesiredApplication.Name, 186 }) 187 } 188 189 configStream, eventStream, warningsStream, errorStream := cmd.Actor.Apply(appConfig, cmd.ProgressBar) 190 updatedConfig, err := cmd.processApplyStreams(user, appConfig, configStream, eventStream, warningsStream, errorStream) 191 if err != nil { 192 log.Errorln("process apply stream:", err) 193 return shared.HandleError(err) 194 } 195 196 if !cmd.NoStart { 197 messages, logErrs, appState, apiWarnings, errs := cmd.RestartActor.RestartApplication(updatedConfig.CurrentApplication.Application, cmd.NOAAClient, cmd.Config) 198 err = shared.PollStart(cmd.UI, cmd.Config, messages, logErrs, appState, apiWarnings, errs) 199 if err != nil { 200 return err 201 } 202 } 203 204 cmd.UI.DisplayNewline() 205 appSummary, warnings, err := cmd.RestartActor.GetApplicationSummaryByNameAndSpace(appConfig.DesiredApplication.Name, cmd.Config.TargetedSpace().GUID) 206 cmd.UI.DisplayWarnings(warnings) 207 if err != nil { 208 return shared.HandleError(err) 209 } 210 211 shared.DisplayAppSummary(cmd.UI, appSummary, true) 212 213 if appNumber+1 <= len(appConfigs) { 214 cmd.UI.DisplayNewline() 215 } 216 } 217 218 return nil 219 } 220 221 func (cmd V2PushCommand) GetCommandLineSettings() (pushaction.CommandLineSettings, error) { 222 err := cmd.validateArgs() 223 if err != nil { 224 return pushaction.CommandLineSettings{}, shared.HandleError(err) 225 } 226 227 pwd, err := os.Getwd() 228 if err != nil { 229 return pushaction.CommandLineSettings{}, err 230 } 231 232 config := pushaction.CommandLineSettings{ 233 Buildpack: cmd.Buildpack.FilteredString, 234 Command: cmd.Command.FilteredString, 235 CurrentDirectory: pwd, 236 DiskQuota: cmd.DiskQuota.Value, 237 DockerImage: cmd.DockerImage.Path, 238 DockerUsername: cmd.DockerUsername, 239 DockerPassword: cmd.Config.DockerPassword(), 240 HealthCheckTimeout: cmd.HealthCheckTimeout, 241 HealthCheckType: cmd.HealthCheckType.Type, 242 Instances: cmd.Instances.NullInt, 243 Memory: cmd.Memory.Value, 244 Name: cmd.OptionalArgs.AppName, 245 ProvidedAppPath: string(cmd.AppPath), 246 StackName: cmd.StackName, 247 } 248 249 log.Debugln("Command Line Settings:", config) 250 return config, nil 251 } 252 253 func (cmd V2PushCommand) findAndReadManifest(settings pushaction.CommandLineSettings) ([]manifest.Application, error) { 254 var pathToManifest string 255 256 switch { 257 case cmd.NoManifest: 258 log.Debug("skipping reading of manifest") 259 return nil, nil 260 case cmd.PathToManifest != "": 261 log.Debug("using specified manifest file") 262 pathToManifest = string(cmd.PathToManifest) 263 default: 264 log.Debug("searching for manifest file") 265 pathToManifest = filepath.Join(settings.CurrentDirectory, "manifest.yml") 266 if _, err := os.Stat(pathToManifest); os.IsNotExist(err) { 267 log.WithField("pathToManifest", pathToManifest).Debug("could not find") 268 269 // While this is unlikely to be used, it is kept for backwards 270 // compatibility. 271 pathToManifest = filepath.Join(settings.CurrentDirectory, "manifest.yaml") 272 if _, err := os.Stat(pathToManifest); os.IsNotExist(err) { 273 log.WithField("pathToManifest", pathToManifest).Debug("could not find") 274 return nil, nil 275 } 276 } 277 } 278 279 log.WithField("pathToManifest", pathToManifest).Info("reading manifest") 280 cmd.UI.DisplayText("Using manifest file {{.Path}}", map[string]interface{}{ 281 "Path": pathToManifest, 282 }) 283 return cmd.Actor.ReadManifest(pathToManifest) 284 } 285 286 func (cmd V2PushCommand) processApplyStreams( 287 user configv3.User, 288 appConfig pushaction.ApplicationConfig, 289 configStream <-chan pushaction.ApplicationConfig, 290 eventStream <-chan pushaction.Event, 291 warningsStream <-chan pushaction.Warnings, 292 errorStream <-chan error, 293 ) (pushaction.ApplicationConfig, error) { 294 var configClosed, eventClosed, warningsClosed, complete bool 295 var updatedConfig pushaction.ApplicationConfig 296 297 for { 298 select { 299 case config, ok := <-configStream: 300 if !ok { 301 log.Debug("processing config stream closed") 302 configClosed = true 303 break 304 } 305 updatedConfig = config 306 log.Debugf("updated config received: %#v", updatedConfig) 307 case event, ok := <-eventStream: 308 if !ok { 309 log.Debug("processing event stream closed") 310 eventClosed = true 311 break 312 } 313 complete = cmd.processEvent(user, appConfig, event) 314 case warnings, ok := <-warningsStream: 315 if !ok { 316 log.Debug("processing warnings stream closed") 317 warningsClosed = true 318 break 319 } 320 cmd.UI.DisplayWarnings(warnings) 321 case err, ok := <-errorStream: 322 if !ok { 323 log.Debug("processing error stream closed") 324 warningsClosed = true 325 break 326 } 327 return pushaction.ApplicationConfig{}, err 328 } 329 330 if configClosed && eventClosed && warningsClosed && complete { 331 log.Debug("breaking apply display loop") 332 break 333 } 334 } 335 336 return updatedConfig, nil 337 } 338 339 func (cmd V2PushCommand) processEvent(user configv3.User, appConfig pushaction.ApplicationConfig, event pushaction.Event) bool { 340 log.Infoln("received apply event:", event) 341 342 switch event { 343 case pushaction.ConfiguringRoutes: 344 cmd.UI.DisplayText("Mapping routes...") 345 case pushaction.ConfiguringServices: 346 cmd.UI.DisplayText("Binding services...") 347 case pushaction.ResourceMatching: 348 cmd.UI.DisplayText("Comparing local files to remote cache...") 349 case pushaction.CreatingArchive: 350 cmd.UI.DisplayText("Packaging files to upload...") 351 case pushaction.UploadingApplication: 352 cmd.UI.DisplayText("Uploading files...") 353 log.Debug("starting progress bar") 354 cmd.ProgressBar.Ready() 355 case pushaction.RetryUpload: 356 cmd.UI.DisplayText("Retrying upload due to an error...") 357 case pushaction.UploadComplete: 358 cmd.ProgressBar.Complete() 359 cmd.UI.DisplayNewline() 360 cmd.UI.DisplayText("Waiting for API to complete processing files...") 361 case pushaction.Complete: 362 return true 363 default: 364 log.WithField("event", event).Debug("ignoring event") 365 } 366 return false 367 } 368 369 func (cmd V2PushCommand) validateArgs() error { 370 switch { 371 case cmd.DockerImage.Path != "" && cmd.AppPath != "": 372 return translatableerror.ArgumentCombinationError{ 373 Args: []string{"--docker-image, -o", "-p"}, 374 } 375 case cmd.DockerImage.Path != "" && cmd.Buildpack.IsSet: 376 return translatableerror.ArgumentCombinationError{ 377 Args: []string{"-b", "--docker-image, -o"}, 378 } 379 case cmd.DockerUsername != "" && cmd.DockerImage.Path == "": 380 return translatableerror.RequiredFlagsError{ 381 Arg1: "--docker-image, -o", 382 Arg2: "--docker-username", 383 } 384 case cmd.DockerUsername != "" && cmd.Config.DockerPassword() == "": 385 return translatableerror.DockerPasswordNotSetError{} 386 case cmd.PathToManifest != "" && cmd.NoManifest: 387 return translatableerror.ArgumentCombinationError{ 388 Args: []string{"-f", "--no-manifest"}, 389 } 390 } 391 392 return nil 393 }