github.com/loafoe/cli@v7.1.0+incompatible/command/v7/push_command.go (about)

     1  package v7
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"strings"
     8  
     9  	"code.cloudfoundry.org/cli/actor/actionerror"
    10  	"code.cloudfoundry.org/cli/actor/sharedaction"
    11  	"code.cloudfoundry.org/cli/actor/v7action"
    12  	"code.cloudfoundry.org/cli/actor/v7pushaction"
    13  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant"
    14  	"code.cloudfoundry.org/cli/cf/errors"
    15  	"code.cloudfoundry.org/cli/command"
    16  	"code.cloudfoundry.org/cli/command/flag"
    17  	"code.cloudfoundry.org/cli/command/translatableerror"
    18  	"code.cloudfoundry.org/cli/command/v7/shared"
    19  	"code.cloudfoundry.org/cli/resources"
    20  	"code.cloudfoundry.org/cli/util/configv3"
    21  	"code.cloudfoundry.org/cli/util/manifestparser"
    22  	"code.cloudfoundry.org/cli/util/progressbar"
    23  	"github.com/cloudfoundry/bosh-cli/director/template"
    24  	log "github.com/sirupsen/logrus"
    25  	"gopkg.in/yaml.v2"
    26  )
    27  
    28  //go:generate counterfeiter . ProgressBar
    29  
    30  type ProgressBar interface {
    31  	v7pushaction.ProgressBar
    32  	Complete()
    33  	Ready()
    34  }
    35  
    36  //go:generate counterfeiter . PushActor
    37  
    38  type PushActor interface {
    39  	HandleFlagOverrides(baseManifest manifestparser.Manifest, flagOverrides v7pushaction.FlagOverrides) (manifestparser.Manifest, error)
    40  	CreatePushPlans(spaceGUID string, orgGUID string, manifest manifestparser.Manifest, overrides v7pushaction.FlagOverrides) ([]v7pushaction.PushPlan, v7action.Warnings, error)
    41  	// Actualize applies any necessary changes.
    42  	Actualize(plan v7pushaction.PushPlan, progressBar v7pushaction.ProgressBar) <-chan *v7pushaction.PushEvent
    43  }
    44  
    45  //go:generate counterfeiter . V7ActorForPush
    46  
    47  type V7ActorForPush interface {
    48  	GetApplicationByNameAndSpace(name string, spaceGUID string) (resources.Application, v7action.Warnings, error)
    49  	GetDetailedAppSummary(appName string, spaceGUID string, withObfuscatedValues bool) (v7action.DetailedApplicationSummary, v7action.Warnings, error)
    50  	SetSpaceManifest(spaceGUID string, rawManifest []byte) (v7action.Warnings, error)
    51  	GetStreamingLogsForApplicationByNameAndSpace(appName string, spaceGUID string, client sharedaction.LogCacheClient) (<-chan sharedaction.LogMessage, <-chan error, context.CancelFunc, v7action.Warnings, error)
    52  	RestartApplication(appGUID string, noWait bool) (v7action.Warnings, error)
    53  }
    54  
    55  //go:generate counterfeiter . ManifestParser
    56  
    57  type ManifestParser interface {
    58  	InterpolateAndParse(pathToManifest string, pathsToVarsFiles []string, vars []template.VarKV) (manifestparser.Manifest, error)
    59  	MarshalManifest(manifest manifestparser.Manifest) ([]byte, error)
    60  }
    61  
    62  //go:generate counterfeiter . ManifestLocator
    63  
    64  type ManifestLocator interface {
    65  	Path(filepathOrDirectory string) (string, bool, error)
    66  }
    67  
    68  type PushCommand struct {
    69  	BaseCommand
    70  
    71  	OptionalArgs            flag.OptionalAppName                `positional-args:"yes"`
    72  	HealthCheckTimeout      flag.PositiveInteger                `long:"app-start-timeout" short:"t" description:"Time (in seconds) allowed to elapse between starting up an app and the first healthy response from the app"`
    73  	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'"`
    74  	Disk                    string                              `long:"disk" short:"k" description:"Disk limit (e.g. 256M, 1024M, 1G)"`
    75  	DockerImage             flag.DockerImage                    `long:"docker-image" short:"o" description:"Docker image to use (e.g. user/docker-image-name)"`
    76  	DockerUsername          string                              `long:"docker-username" description:"Repository username; used with password from environment variable CF_DOCKER_PASSWORD"`
    77  	DropletPath             flag.PathWithExistenceCheck         `long:"droplet" description:"Path to a tgz file with a pre-staged app"`
    78  	HealthCheckHTTPEndpoint string                              `long:"endpoint"  description:"Valid path on the app for an HTTP health check. Only used when specifying --health-check-type=http"`
    79  	HealthCheckType         flag.HealthCheckType                `long:"health-check-type" short:"u" description:"Application health check type. Defaults to 'port'. 'http' requires a valid endpoint, for example, '/health'."`
    80  	Instances               flag.Instances                      `long:"instances" short:"i" description:"Number of instances"`
    81  	PathToManifest          flag.ManifestPathWithExistenceCheck `long:"manifest" short:"f" description:"Path to manifest"`
    82  	Memory                  string                              `long:"memory" short:"m" description:"Memory limit (e.g. 256M, 1024M, 1G)"`
    83  	NoManifest              bool                                `long:"no-manifest" description:"Ignore manifest file"`
    84  	NoRoute                 bool                                `long:"no-route" description:"Do not map a route to this app"`
    85  	NoStart                 bool                                `long:"no-start" description:"Do not stage and start the app after pushing"`
    86  	NoWait                  bool                                `long:"no-wait" description:"Exit when the first instance of the web process is healthy"`
    87  	AppPath                 flag.PathWithExistenceCheck         `long:"path" short:"p" description:"Path to app directory or to a zip file of the contents of the app directory"`
    88  	RandomRoute             bool                                `long:"random-route" description:"Create a random route for this app (except when no-route is specified in the manifest)"`
    89  	Stack                   string                              `long:"stack" short:"s" description:"Stack to use (a stack is a pre-built file system, including an operating system, that can run apps)"`
    90  	StartCommand            flag.Command                        `long:"start-command" short:"c" description:"Startup command, set to null to reset to default start command"`
    91  	Strategy                flag.DeploymentStrategy             `long:"strategy" description:"Deployment strategy, either rolling or null."`
    92  	Task                    bool                                `long:"task" description:"Push an app that is used only to execute tasks. The app will be staged, but not started and will have no route assigned."`
    93  	Vars                    []template.VarKV                    `long:"var" description:"Variable key value pair for variable substitution, (e.g., name=app1); can specify multiple times"`
    94  	PathsToVarsFiles        []flag.PathWithExistenceCheck       `long:"vars-file" description:"Path to a variable substitution file for manifest; can specify multiple times"`
    95  	dockerPassword          interface{}                         `environmentName:"CF_DOCKER_PASSWORD" environmentDescription:"Password used for private docker repository"`
    96  	usage                   interface{}                         `usage:"CF_NAME push APP_NAME [-b BUILDPACK_NAME]\n   [-c COMMAND] [-f MANIFEST_PATH | --no-manifest] [--no-start] [--no-wait] [-i NUM_INSTANCES]\n   [-k DISK] [-m MEMORY] [-p PATH] [-s STACK] [-t HEALTH_TIMEOUT] [--task TASK]\n   [-u (process | port | http)] [--no-route | --random-route]\n   [--var KEY=VALUE] [--vars-file VARS_FILE_PATH]...\n \n   CF_NAME push APP_NAME --docker-image [REGISTRY_HOST:PORT/]IMAGE[:TAG] [--docker-username USERNAME]\n   [-c COMMAND] [-f MANIFEST_PATH | --no-manifest] [--no-start] [--no-wait] [-i NUM_INSTANCES]\n   [-k DISK] [-m MEMORY] [-p PATH] [-s STACK] [-t HEALTH_TIMEOUT] [--task TASK]\n   [-u (process | port | http)] [--no-route | --random-route ]\n   [--var KEY=VALUE] [--vars-file VARS_FILE_PATH]..."`
    97  	envCFStagingTimeout     interface{}                         `environmentName:"CF_STAGING_TIMEOUT" environmentDescription:"Max wait time for staging, in minutes" environmentDefault:"15"`
    98  	envCFStartupTimeout     interface{}                         `environmentName:"CF_STARTUP_TIMEOUT" environmentDescription:"Max wait time for app instance startup, in minutes" environmentDefault:"5"`
    99  
   100  	LogCacheClient  sharedaction.LogCacheClient
   101  	PushActor       PushActor
   102  	VersionActor    V7ActorForPush
   103  	ProgressBar     ProgressBar
   104  	CWD             string
   105  	ManifestLocator ManifestLocator
   106  	ManifestParser  ManifestParser
   107  
   108  	stopStreamingFunc func()
   109  }
   110  
   111  func (cmd *PushCommand) Setup(config command.Config, ui command.UI) error {
   112  	err := cmd.BaseCommand.Setup(config, ui)
   113  	if err != nil {
   114  		return err
   115  	}
   116  
   117  	cmd.ProgressBar = progressbar.NewProgressBar()
   118  	cmd.VersionActor = cmd.Actor
   119  	cmd.PushActor = v7pushaction.NewActor(cmd.Actor, sharedaction.NewActor(config))
   120  
   121  	logCacheEndpoint, _, err := cmd.Actor.GetLogCacheEndpoint()
   122  	if err != nil {
   123  		return err
   124  	}
   125  	cmd.LogCacheClient = command.NewLogCacheClient(logCacheEndpoint, config, ui)
   126  
   127  	currentDir, err := os.Getwd()
   128  	cmd.CWD = currentDir
   129  
   130  	cmd.ManifestLocator = manifestparser.NewLocator()
   131  	cmd.ManifestParser = manifestparser.ManifestParser{}
   132  
   133  	return err
   134  }
   135  
   136  func (cmd PushCommand) Execute(args []string) error {
   137  	cmd.stopStreamingFunc = nil
   138  	err := cmd.SharedActor.CheckTarget(true, true)
   139  	if err != nil {
   140  		return err
   141  	}
   142  
   143  	user, err := cmd.Config.CurrentUser()
   144  	if err != nil {
   145  		return err
   146  	}
   147  
   148  	flagOverrides, err := cmd.GetFlagOverrides()
   149  	if err != nil {
   150  		return err
   151  	}
   152  
   153  	err = cmd.ValidateFlags()
   154  	if err != nil {
   155  		return err
   156  	}
   157  
   158  	baseManifest, err := cmd.GetBaseManifest(flagOverrides)
   159  	if err != nil {
   160  		return err
   161  	}
   162  
   163  	transformedManifest, err := cmd.PushActor.HandleFlagOverrides(baseManifest, flagOverrides)
   164  	if err != nil {
   165  		return err
   166  	}
   167  
   168  	flagOverrides.DockerPassword, err = cmd.GetDockerPassword(flagOverrides.DockerUsername, transformedManifest.ContainsPrivateDockerImages())
   169  	if err != nil {
   170  		return err
   171  	}
   172  
   173  	transformedRawManifest, err := cmd.ManifestParser.MarshalManifest(transformedManifest)
   174  	if err != nil {
   175  		return err
   176  	}
   177  
   178  	cmd.announcePushing(transformedManifest.AppNames(), user)
   179  
   180  	hasManifest := transformedManifest.PathToManifest != ""
   181  
   182  	if hasManifest {
   183  		cmd.UI.DisplayText("Applying manifest file {{.Path}}...", map[string]interface{}{
   184  			"Path": transformedManifest.PathToManifest,
   185  		})
   186  	}
   187  
   188  	v7ActionWarnings, err := cmd.VersionActor.SetSpaceManifest(
   189  		cmd.Config.TargetedSpace().GUID,
   190  		transformedRawManifest,
   191  	)
   192  
   193  	cmd.UI.DisplayWarnings(v7ActionWarnings)
   194  	if err != nil {
   195  		return err
   196  	}
   197  	if hasManifest {
   198  		cmd.UI.DisplayText("Manifest applied")
   199  	}
   200  
   201  	pushPlans, warnings, err := cmd.PushActor.CreatePushPlans(
   202  		cmd.Config.TargetedSpace().GUID,
   203  		cmd.Config.TargetedOrganization().GUID,
   204  		transformedManifest,
   205  		flagOverrides,
   206  	)
   207  	cmd.UI.DisplayWarnings(warnings)
   208  	if err != nil {
   209  		return err
   210  	}
   211  
   212  	log.WithField("number of plans", len(pushPlans)).Debug("completed generating plan")
   213  	defer func() {
   214  		if cmd.stopStreamingFunc != nil {
   215  			cmd.stopStreamingFunc()
   216  		}
   217  	}()
   218  
   219  	for _, plan := range pushPlans {
   220  		log.WithField("app_name", plan.Application.Name).Info("actualizing")
   221  		eventStream := cmd.PushActor.Actualize(plan, cmd.ProgressBar)
   222  		err := cmd.eventStreamHandler(eventStream)
   223  
   224  		if cmd.shouldDisplaySummary(err) {
   225  			summaryErr := cmd.displayAppSummary(plan)
   226  			if summaryErr != nil {
   227  				return summaryErr
   228  			}
   229  		}
   230  		if err != nil {
   231  			return cmd.mapErr(plan.Application.Name, err)
   232  		}
   233  	}
   234  
   235  	return nil
   236  }
   237  
   238  func (cmd PushCommand) GetBaseManifest(flagOverrides v7pushaction.FlagOverrides) (manifestparser.Manifest, error) {
   239  	defaultManifest := manifestparser.Manifest{
   240  		Applications: []manifestparser.Application{
   241  			{Name: flagOverrides.AppName},
   242  		},
   243  	}
   244  	if cmd.NoManifest {
   245  		log.Debugf("No manifest given, generating manifest")
   246  		return defaultManifest, nil
   247  	}
   248  
   249  	log.Info("reading manifest if exists")
   250  	readPath := cmd.CWD
   251  	if flagOverrides.ManifestPath != "" {
   252  		log.WithField("manifestPath", flagOverrides.ManifestPath).Debug("reading '-f' provided manifest")
   253  		readPath = flagOverrides.ManifestPath
   254  	}
   255  
   256  	pathToManifest, exists, err := cmd.ManifestLocator.Path(readPath)
   257  	if err != nil {
   258  		return manifestparser.Manifest{}, err
   259  	}
   260  
   261  	if !exists {
   262  		log.Debugf("No manifest given, generating manifest")
   263  		return defaultManifest, nil
   264  	}
   265  
   266  	log.WithField("manifestPath", pathToManifest).Debug("path to manifest")
   267  	manifest, err := cmd.ManifestParser.InterpolateAndParse(pathToManifest, flagOverrides.PathsToVarsFiles, flagOverrides.Vars)
   268  	if err != nil {
   269  		log.Errorln("reading manifest:", err)
   270  		if _, ok := err.(*yaml.TypeError); ok {
   271  			return manifestparser.Manifest{}, errors.New(fmt.Sprintf("Unable to push app because manifest %s is not valid yaml.", pathToManifest))
   272  		}
   273  		return manifestparser.Manifest{}, err
   274  	}
   275  
   276  	return manifest, nil
   277  }
   278  
   279  func (cmd PushCommand) GetDockerPassword(dockerUsername string, containsPrivateDockerImages bool) (string, error) {
   280  	if dockerUsername == "" && !containsPrivateDockerImages { // no need for a password without a username
   281  		return "", nil
   282  	}
   283  
   284  	if cmd.Config.DockerPassword() == "" {
   285  		cmd.UI.DisplayText("Environment variable CF_DOCKER_PASSWORD not set.")
   286  		return cmd.UI.DisplayPasswordPrompt("Docker password")
   287  	}
   288  
   289  	cmd.UI.DisplayText("Using docker repository password from environment variable CF_DOCKER_PASSWORD.")
   290  	return cmd.Config.DockerPassword(), nil
   291  }
   292  
   293  func (cmd PushCommand) GetFlagOverrides() (v7pushaction.FlagOverrides, error) {
   294  	var pathsToVarsFiles []string
   295  	for _, varFilePath := range cmd.PathsToVarsFiles {
   296  		pathsToVarsFiles = append(pathsToVarsFiles, string(varFilePath))
   297  	}
   298  
   299  	return v7pushaction.FlagOverrides{
   300  		AppName:             cmd.OptionalArgs.AppName,
   301  		Buildpacks:          cmd.Buildpacks,
   302  		Stack:               cmd.Stack,
   303  		Disk:                cmd.Disk,
   304  		DropletPath:         string(cmd.DropletPath),
   305  		DockerImage:         cmd.DockerImage.Path,
   306  		DockerUsername:      cmd.DockerUsername,
   307  		HealthCheckEndpoint: cmd.HealthCheckHTTPEndpoint,
   308  		HealthCheckType:     cmd.HealthCheckType.Type,
   309  		HealthCheckTimeout:  cmd.HealthCheckTimeout.Value,
   310  		Instances:           cmd.Instances.NullInt,
   311  		Memory:              cmd.Memory,
   312  		NoStart:             cmd.NoStart,
   313  		NoWait:              cmd.NoWait,
   314  		ProvidedAppPath:     string(cmd.AppPath),
   315  		NoRoute:             cmd.NoRoute,
   316  		RandomRoute:         cmd.RandomRoute,
   317  		StartCommand:        cmd.StartCommand.FilteredString,
   318  		Strategy:            cmd.Strategy.Name,
   319  		ManifestPath:        string(cmd.PathToManifest),
   320  		PathsToVarsFiles:    pathsToVarsFiles,
   321  		Vars:                cmd.Vars,
   322  		NoManifest:          cmd.NoManifest,
   323  		Task:                cmd.Task,
   324  	}, nil
   325  }
   326  
   327  func (cmd PushCommand) ValidateFlags() error {
   328  	switch {
   329  	case cmd.DockerUsername != "" && cmd.DockerImage.Path == "":
   330  		return translatableerror.RequiredFlagsError{
   331  			Arg1: "--docker-image, -o",
   332  			Arg2: "--docker-username",
   333  		}
   334  
   335  	case cmd.DockerImage.Path != "" && cmd.Buildpacks != nil:
   336  		return translatableerror.ArgumentCombinationError{
   337  			Args: []string{
   338  				"--buildpack, -b",
   339  				"--docker-image, -o",
   340  			},
   341  		}
   342  
   343  	case cmd.DockerImage.Path != "" && cmd.AppPath != "":
   344  		return translatableerror.ArgumentCombinationError{
   345  			Args: []string{
   346  				"--docker-image, -o",
   347  				"--path, -p",
   348  			},
   349  		}
   350  
   351  	case cmd.DockerImage.Path != "" && cmd.Stack != "":
   352  		return translatableerror.ArgumentCombinationError{
   353  			Args: []string{
   354  				"--stack, -s",
   355  				"--docker-image, -o",
   356  			},
   357  		}
   358  
   359  	case cmd.NoManifest && cmd.PathToManifest != "":
   360  		return translatableerror.ArgumentCombinationError{
   361  			Args: []string{
   362  				"--no-manifest",
   363  				"--manifest, -f",
   364  			},
   365  		}
   366  
   367  	case cmd.NoManifest && len(cmd.PathsToVarsFiles) > 0:
   368  		return translatableerror.ArgumentCombinationError{
   369  			Args: []string{
   370  				"--no-manifest",
   371  				"--vars-file",
   372  			},
   373  		}
   374  
   375  	case cmd.NoManifest && len(cmd.Vars) > 0:
   376  		return translatableerror.ArgumentCombinationError{
   377  			Args: []string{
   378  				"--no-manifest",
   379  				"--vars",
   380  			},
   381  		}
   382  
   383  	case cmd.HealthCheckType.Type == constant.HTTP && cmd.HealthCheckHTTPEndpoint == "":
   384  		return translatableerror.RequiredFlagsError{
   385  			Arg1: "--endpoint",
   386  			Arg2: "--health-check-type=http, -u=http",
   387  		}
   388  
   389  	case cmd.DropletPath != "" && (cmd.DockerImage.Path != "" || cmd.DockerUsername != "" || cmd.AppPath != ""):
   390  		return translatableerror.ArgumentCombinationError{
   391  			Args: []string{
   392  				"--droplet",
   393  				"--docker-image, -o",
   394  				"--docker-username",
   395  				"-p",
   396  			},
   397  		}
   398  
   399  	case cmd.NoStart && cmd.Strategy == flag.DeploymentStrategy{Name: constant.DeploymentStrategyRolling}:
   400  		return translatableerror.ArgumentCombinationError{
   401  			Args: []string{
   402  				"--no-start",
   403  				"--strategy=rolling",
   404  			},
   405  		}
   406  
   407  	case cmd.Task && cmd.Strategy == flag.DeploymentStrategy{Name: constant.DeploymentStrategyRolling}:
   408  		return translatableerror.ArgumentCombinationError{
   409  			Args: []string{
   410  				"--task",
   411  				"--strategy=rolling",
   412  			},
   413  		}
   414  
   415  	case cmd.NoStart && cmd.NoWait:
   416  		return translatableerror.ArgumentCombinationError{
   417  			Args: []string{
   418  				"--no-start",
   419  				"--no-wait",
   420  			},
   421  		}
   422  
   423  	case cmd.NoRoute && cmd.RandomRoute:
   424  		return translatableerror.ArgumentCombinationError{
   425  			Args: []string{
   426  				"--no-route",
   427  				"--random-route",
   428  			},
   429  		}
   430  	case !cmd.validBuildpacks():
   431  		return translatableerror.InvalidBuildpacksError{}
   432  	}
   433  
   434  	return nil
   435  }
   436  
   437  func (cmd PushCommand) validBuildpacks() bool {
   438  	for _, buildpack := range cmd.Buildpacks {
   439  		if (buildpack == "null" || buildpack == "default") && len(cmd.Buildpacks) > 1 {
   440  			return false
   441  		}
   442  	}
   443  	return true
   444  }
   445  
   446  func (cmd PushCommand) shouldDisplaySummary(err error) bool {
   447  	if err == nil {
   448  		return true
   449  	}
   450  	_, ok := err.(actionerror.AllInstancesCrashedError)
   451  	return ok
   452  }
   453  
   454  func (cmd PushCommand) mapErr(appName string, err error) error {
   455  	switch err.(type) {
   456  	case actionerror.AllInstancesCrashedError:
   457  		return translatableerror.ApplicationUnableToStartError{
   458  			AppName:    appName,
   459  			BinaryName: cmd.Config.BinaryName(),
   460  		}
   461  	case actionerror.StartupTimeoutError:
   462  		return translatableerror.StartupTimeoutError{
   463  			AppName:    appName,
   464  			BinaryName: cmd.Config.BinaryName(),
   465  		}
   466  	}
   467  	return err
   468  }
   469  
   470  func (cmd PushCommand) announcePushing(appNames []string, user configv3.User) {
   471  	tokens := map[string]interface{}{
   472  		"AppName":   strings.Join(appNames, ", "),
   473  		"OrgName":   cmd.Config.TargetedOrganization().Name,
   474  		"SpaceName": cmd.Config.TargetedSpace().Name,
   475  		"Username":  user.Name,
   476  	}
   477  	singular := "Pushing app {{.AppName}} to org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}..."
   478  	plural := "Pushing apps {{.AppName}} to org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}..."
   479  
   480  	if len(appNames) == 1 {
   481  		cmd.UI.DisplayTextWithFlavor(singular, tokens)
   482  	} else {
   483  		cmd.UI.DisplayTextWithFlavor(plural, tokens)
   484  	}
   485  }
   486  
   487  func (cmd PushCommand) displayAppSummary(plan v7pushaction.PushPlan) error {
   488  	log.Info("getting application summary info")
   489  	summary, warnings, err := cmd.VersionActor.GetDetailedAppSummary(
   490  		plan.Application.Name,
   491  		cmd.Config.TargetedSpace().GUID,
   492  		true,
   493  	)
   494  	cmd.UI.DisplayWarnings(warnings)
   495  	if err != nil {
   496  		return err
   497  	}
   498  	cmd.UI.DisplayNewline()
   499  	appSummaryDisplayer := shared.NewAppSummaryDisplayer(cmd.UI)
   500  	appSummaryDisplayer.AppDisplay(summary, true)
   501  	return nil
   502  }
   503  
   504  func (cmd *PushCommand) eventStreamHandler(eventStream <-chan *v7pushaction.PushEvent) error {
   505  	for event := range eventStream {
   506  		cmd.UI.DisplayWarnings(event.Warnings)
   507  		if event.Err != nil {
   508  			return event.Err
   509  		}
   510  		err := cmd.processEvent(event.Event, event.Plan.Application.Name)
   511  		if err != nil {
   512  			return err
   513  		}
   514  	}
   515  	return nil
   516  }
   517  
   518  func (cmd *PushCommand) processEvent(event v7pushaction.Event, appName string) error {
   519  	switch event {
   520  	case v7pushaction.CreatingArchive:
   521  		cmd.UI.DisplayText("Packaging files to upload...")
   522  	case v7pushaction.UploadingApplicationWithArchive:
   523  		cmd.UI.DisplayText("Uploading files...")
   524  		log.Debug("starting progress bar")
   525  		cmd.ProgressBar.Ready()
   526  	case v7pushaction.UploadingApplication:
   527  		cmd.UI.DisplayText("All files found in remote cache; nothing to upload.")
   528  		cmd.UI.DisplayText("Waiting for API to complete processing files...")
   529  	case v7pushaction.RetryUpload:
   530  		cmd.UI.DisplayText("Retrying upload due to an error...")
   531  	case v7pushaction.UploadWithArchiveComplete:
   532  		cmd.ProgressBar.Complete()
   533  		cmd.UI.DisplayNewline()
   534  		cmd.UI.DisplayText("Waiting for API to complete processing files...")
   535  	case v7pushaction.UploadingDroplet:
   536  		cmd.UI.DisplayText("Uploading droplet bits...")
   537  		cmd.ProgressBar.Ready()
   538  	case v7pushaction.UploadDropletComplete:
   539  		cmd.ProgressBar.Complete()
   540  		cmd.UI.DisplayNewline()
   541  		cmd.UI.DisplayText("Waiting for API to complete processing files...")
   542  	case v7pushaction.StoppingApplication:
   543  		cmd.UI.DisplayText("Stopping Application...")
   544  	case v7pushaction.StoppingApplicationComplete:
   545  		cmd.UI.DisplayText("Application Stopped")
   546  	case v7pushaction.ApplyManifest:
   547  		cmd.UI.DisplayText("Applying manifest...")
   548  	case v7pushaction.ApplyManifestComplete:
   549  		cmd.UI.DisplayText("Manifest applied")
   550  	case v7pushaction.StartingStaging:
   551  		cmd.UI.DisplayNewline()
   552  		cmd.UI.DisplayText("Staging app and tracing logs...")
   553  		logStream, errStream, cancelFunc, warnings, err := cmd.VersionActor.GetStreamingLogsForApplicationByNameAndSpace(appName, cmd.Config.TargetedSpace().GUID, cmd.LogCacheClient)
   554  		cmd.UI.DisplayWarnings(warnings)
   555  		if err != nil {
   556  			return err
   557  		}
   558  		if cmd.stopStreamingFunc != nil {
   559  			cmd.stopStreamingFunc()
   560  		}
   561  		cmd.stopStreamingFunc = cancelFunc
   562  		go cmd.getLogs(logStream, errStream)
   563  	case v7pushaction.StagingComplete:
   564  		if cmd.stopStreamingFunc != nil {
   565  			cmd.stopStreamingFunc()
   566  			cmd.stopStreamingFunc = nil
   567  		}
   568  	case v7pushaction.RestartingApplication:
   569  		cmd.UI.DisplayNewline()
   570  		cmd.UI.DisplayTextWithFlavor(
   571  			"Waiting for app {{.AppName}} to start...",
   572  			map[string]interface{}{
   573  				"AppName": appName,
   574  			},
   575  		)
   576  		cmd.UI.DisplayNewline()
   577  	case v7pushaction.StartingDeployment:
   578  		cmd.UI.DisplayNewline()
   579  		cmd.UI.DisplayTextWithFlavor(
   580  			"Starting deployment for app {{.AppName}}...",
   581  			map[string]interface{}{
   582  				"AppName": appName,
   583  			},
   584  		)
   585  	case v7pushaction.WaitingForDeployment:
   586  		cmd.UI.DisplayText("Waiting for app to deploy...")
   587  		cmd.UI.DisplayNewline()
   588  	default:
   589  		log.WithField("event", event).Debug("ignoring event")
   590  	}
   591  
   592  	return nil
   593  }
   594  
   595  func (cmd PushCommand) getLogs(logStream <-chan sharedaction.LogMessage, errStream <-chan error) {
   596  	for {
   597  		select {
   598  		case logMessage, open := <-logStream:
   599  			if !open {
   600  				return
   601  			}
   602  			if logMessage.Staging() {
   603  				cmd.UI.DisplayLogMessage(logMessage, false)
   604  			}
   605  		case err, open := <-errStream:
   606  			if !open {
   607  				return
   608  			}
   609  			_, ok := err.(actionerror.LogCacheTimeoutError)
   610  			if ok {
   611  				cmd.UI.DisplayWarning("timeout connecting to log server, no log will be shown")
   612  			}
   613  			cmd.UI.DisplayWarning("Failed to retrieve logs from Log Cache: " + err.Error())
   614  		}
   615  	}
   616  }