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  }