github.com/jenspinney/cli@v6.42.1-0.20190207184520-7450c600020e+incompatible/command/v6/v3_push_command.go (about)

     1  package v6
     2  
     3  import (
     4  	"os"
     5  
     6  	"code.cloudfoundry.org/cli/actor/actionerror"
     7  	"code.cloudfoundry.org/cli/actor/pushaction"
     8  	"code.cloudfoundry.org/cli/actor/sharedaction"
     9  	"code.cloudfoundry.org/cli/actor/v2action"
    10  	"code.cloudfoundry.org/cli/actor/v3action"
    11  	"code.cloudfoundry.org/cli/command"
    12  	"code.cloudfoundry.org/cli/command/flag"
    13  	"code.cloudfoundry.org/cli/command/translatableerror"
    14  	"code.cloudfoundry.org/cli/command/v6/shared"
    15  	"code.cloudfoundry.org/cli/util/progressbar"
    16  
    17  	log "github.com/sirupsen/logrus"
    18  )
    19  
    20  //go:generate counterfeiter . V3PushActor
    21  
    22  type V3PushActor interface {
    23  	Actualize(state pushaction.PushState, progressBar pushaction.ProgressBar) (<-chan pushaction.PushState, <-chan pushaction.Event, <-chan pushaction.Warnings, <-chan error)
    24  	Conceptualize(setting pushaction.CommandLineSettings, spaceGUID string) ([]pushaction.PushState, pushaction.Warnings, error)
    25  }
    26  
    27  //go:generate counterfeiter . V3PushVersionActor
    28  
    29  type V3PushVersionActor interface {
    30  	GetStreamingLogsForApplicationByNameAndSpace(appName string, spaceGUID string, client v3action.NOAAClient) (<-chan *v3action.LogMessage, <-chan error, v3action.Warnings, error)
    31  	PollStart(appGUID string, warningsChannel chan<- v3action.Warnings) error
    32  	RestartApplication(appGUID string) (v3action.Warnings, error)
    33  }
    34  
    35  type V3PushCommand struct {
    36  	RequiredArgs flag.AppName `positional-args:"yes"`
    37  	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'"`
    38  	// Command flag.Command
    39  	// Domain string
    40  	DockerImage    flag.DockerImage `long:"docker-image" short:"o" description:"Docker image to use (e.g. user/docker-image-name)"`
    41  	DockerUsername string           `long:"docker-username" description:"Repository username; used with password from environment variable CF_DOCKER_PASSWORD"`
    42  	// DropletPath flag.PathWithExistenceCheck
    43  	// PathToManifest flag.PathWithExistenceCheck
    44  	// HealthCheckType flag.HealthCheckTypeWithDeprecatedValue
    45  	// Hostname string
    46  	// Instances flag.Instances
    47  	// DiskQuota           flag.Megabytes
    48  	// Memory              flag.Megabytes
    49  	// NoHostname          bool
    50  	// NoManifest          bool
    51  	NoRoute bool                        `long:"no-route" description:"Do not map a route to this app"`
    52  	NoStart bool                        `long:"no-start" description:"Do not stage and start the app after pushing"`
    53  	AppPath flag.PathWithExistenceCheck `short:"p" description:"Path to app directory or to a zip file of the contents of the app directory"`
    54  	// RandomRoute         bool
    55  	// RoutePath           flag.RoutePath
    56  	// StackName           string
    57  	// VarsFilePaths []flag.PathWithExistenceCheck
    58  	// Vars []template.VarKV
    59  	// HealthCheckTimeout int
    60  	dockerPassword      interface{} `environmentName:"CF_DOCKER_PASSWORD" environmentDescription:"Password used for private docker repository"`
    61  	usage               interface{} `usage:"CF_NAME v3-push APP_NAME [-b BUILDPACK]... [-p APP_PATH] [--no-route] [--no-start]\n   CF_NAME v3-push APP_NAME --docker-image [REGISTRY_HOST:PORT/]IMAGE[:TAG] [--docker-username USERNAME] [--no-route] [--no-start]"`
    62  	envCFStagingTimeout interface{} `environmentName:"CF_STAGING_TIMEOUT" environmentDescription:"Max wait time for buildpack staging, in minutes" environmentDefault:"15"`
    63  	envCFStartupTimeout interface{} `environmentName:"CF_STARTUP_TIMEOUT" environmentDescription:"Max wait time for app instance startup, in minutes" environmentDefault:"5"`
    64  
    65  	UI                  command.UI
    66  	Config              command.Config
    67  	NOAAClient          v3action.NOAAClient
    68  	Actor               V3PushActor
    69  	VersionActor        V3PushVersionActor
    70  	SharedActor         command.SharedActor
    71  	AppSummaryDisplayer shared.AppSummaryDisplayer
    72  	PackageDisplayer    shared.PackageDisplayer
    73  	ProgressBar         ProgressBar
    74  
    75  	OriginalActor       OriginalV3PushActor
    76  	OriginalV2PushActor OriginalV2PushActor
    77  }
    78  
    79  func (cmd *V3PushCommand) Setup(config command.Config, ui command.UI) error {
    80  	if !config.Experimental() {
    81  		return cmd.OriginalSetup(config, ui)
    82  	}
    83  
    84  	cmd.Config = config
    85  	cmd.UI = ui
    86  	cmd.ProgressBar = progressbar.NewProgressBar()
    87  
    88  	sharedActor := sharedaction.NewActor(config)
    89  	cmd.SharedActor = sharedActor
    90  
    91  	ccClient, uaaClient, err := shared.NewV3BasedClients(config, ui, true, "")
    92  	if err != nil {
    93  		return err
    94  	}
    95  	v3Actor := v3action.NewActor(ccClient, config, sharedActor, uaaClient)
    96  	cmd.VersionActor = v3Actor
    97  
    98  	ccClientV2, uaaClientV2, err := shared.NewClients(config, ui, true)
    99  	if err != nil {
   100  		return err
   101  	}
   102  
   103  	v2Actor := v2action.NewActor(ccClientV2, uaaClientV2, config)
   104  	cmd.Actor = pushaction.NewActor(v2Actor, v3Actor, sharedActor)
   105  
   106  	cmd.NOAAClient = shared.NewNOAAClient(ccClient.Info.Logging(), config, uaaClient, ui)
   107  
   108  	return nil
   109  }
   110  
   111  func (cmd V3PushCommand) Execute(args []string) error {
   112  	if !cmd.Config.Experimental() {
   113  		return cmd.OriginalExecute(args)
   114  	}
   115  
   116  	cmd.UI.DisplayWarning(command.ExperimentalWarning)
   117  
   118  	err := cmd.SharedActor.CheckTarget(true, true)
   119  	if err != nil {
   120  		return err
   121  	}
   122  
   123  	user, err := cmd.Config.CurrentUser()
   124  	if err != nil {
   125  		return err
   126  	}
   127  
   128  	cliSettings, err := cmd.GetCommandLineSettings()
   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":   cliSettings.Name,
   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  	log.Info("generating the app state")
   143  	pushState, warnings, err := cmd.Actor.Conceptualize(cliSettings, cmd.Config.TargetedSpace().GUID)
   144  	cmd.UI.DisplayWarnings(warnings)
   145  	if err != nil {
   146  		return err
   147  	}
   148  	log.WithField("number of states", len(pushState)).Debug("completed generating state")
   149  
   150  	for _, state := range pushState {
   151  		log.WithField("app_name", state.Application.Name).Info("actualizing")
   152  		stateStream, eventStream, warningsStream, errorStream := cmd.Actor.Actualize(state, cmd.ProgressBar)
   153  		updatedState, err := cmd.processApplyStreams(state.Application.Name, stateStream, eventStream, warningsStream, errorStream)
   154  		if err != nil {
   155  			return err
   156  		}
   157  
   158  		cmd.UI.DisplayNewline()
   159  		cmd.UI.DisplayText("Waiting for app to start...")
   160  		warnings, err := cmd.VersionActor.RestartApplication(updatedState.Application.GUID)
   161  		cmd.UI.DisplayWarnings(warnings)
   162  		if err != nil {
   163  			return err
   164  		}
   165  
   166  		pollWarnings := make(chan v3action.Warnings)
   167  		done := make(chan bool)
   168  		go func() {
   169  			for {
   170  				select {
   171  				case message := <-pollWarnings:
   172  					cmd.UI.DisplayWarnings(message)
   173  				case <-done:
   174  					return
   175  				}
   176  			}
   177  		}()
   178  
   179  		err = cmd.VersionActor.PollStart(updatedState.Application.GUID, pollWarnings)
   180  		done <- true
   181  
   182  		if err != nil {
   183  			if _, ok := err.(actionerror.StartupTimeoutError); ok {
   184  				return translatableerror.StartupTimeoutError{
   185  					AppName:    cmd.RequiredArgs.AppName,
   186  					BinaryName: cmd.Config.BinaryName(),
   187  				}
   188  			}
   189  
   190  			return err
   191  		}
   192  	}
   193  
   194  	return nil
   195  }
   196  
   197  func (cmd V3PushCommand) processApplyStreams(
   198  	appName string,
   199  	stateStream <-chan pushaction.PushState,
   200  	eventStream <-chan pushaction.Event,
   201  	warningsStream <-chan pushaction.Warnings,
   202  	errorStream <-chan error,
   203  ) (pushaction.PushState, error) {
   204  	var stateClosed, eventClosed, warningsClosed, complete bool
   205  	var updateState pushaction.PushState
   206  
   207  	for {
   208  		select {
   209  		case state, ok := <-stateStream:
   210  			if !ok {
   211  				log.Debug("processing config stream closed")
   212  				stateClosed = true
   213  				break
   214  			}
   215  			updateState = state
   216  		case event, ok := <-eventStream:
   217  			if !ok {
   218  				log.Debug("processing event stream closed")
   219  				eventClosed = true
   220  				break
   221  			}
   222  			complete = cmd.processEvent(appName, event)
   223  		case warnings, ok := <-warningsStream:
   224  			if !ok {
   225  				log.Debug("processing warnings stream closed")
   226  				warningsClosed = true
   227  				break
   228  			}
   229  			cmd.UI.DisplayWarnings(warnings)
   230  		case err, ok := <-errorStream:
   231  			if !ok {
   232  				log.Debug("processing error stream closed")
   233  				warningsClosed = true
   234  				break
   235  			}
   236  			return pushaction.PushState{}, err
   237  		}
   238  
   239  		if stateClosed && eventClosed && warningsClosed && complete {
   240  			break
   241  		}
   242  	}
   243  
   244  	return updateState, nil
   245  }
   246  
   247  func (cmd V3PushCommand) processEvent(appName string, event pushaction.Event) bool {
   248  	log.Infoln("received apply event:", event)
   249  
   250  	switch event {
   251  	case pushaction.SkippingApplicationCreation:
   252  		cmd.UI.DisplayTextWithFlavor("Updating app {{.AppName}}...", map[string]interface{}{
   253  			"AppName": appName,
   254  		})
   255  	case pushaction.CreatedApplication:
   256  		cmd.UI.DisplayTextWithFlavor("Creating app {{.AppName}}...", map[string]interface{}{
   257  			"AppName": appName,
   258  		})
   259  	case pushaction.CreatingArchive:
   260  		cmd.UI.DisplayTextWithFlavor("Packaging files to upload...")
   261  	case pushaction.UploadingApplicationWithArchive:
   262  		cmd.UI.DisplayTextWithFlavor("Uploading files...")
   263  		log.Debug("starting progress bar")
   264  		cmd.ProgressBar.Ready()
   265  	case pushaction.RetryUpload:
   266  		cmd.UI.DisplayText("Retrying upload due to an error...")
   267  	case pushaction.UploadWithArchiveComplete:
   268  		cmd.ProgressBar.Complete()
   269  		cmd.UI.DisplayNewline()
   270  		cmd.UI.DisplayText("Waiting for API to complete processing files...")
   271  	case pushaction.StartingStaging:
   272  		cmd.UI.DisplayNewline()
   273  		cmd.UI.DisplayText("Staging app and tracing logs...")
   274  		logStream, errStream, warnings, _ := cmd.VersionActor.GetStreamingLogsForApplicationByNameAndSpace(appName, cmd.Config.TargetedSpace().GUID, cmd.NOAAClient)
   275  		cmd.UI.DisplayWarnings(warnings)
   276  		go cmd.getLogs(logStream, errStream)
   277  	case pushaction.StagingComplete:
   278  		cmd.NOAAClient.Close()
   279  	case pushaction.Complete:
   280  		return true
   281  	default:
   282  		log.WithField("event", event).Debug("ignoring event")
   283  	}
   284  	return false
   285  }
   286  
   287  func (cmd V3PushCommand) getLogs(logStream <-chan *v3action.LogMessage, errStream <-chan error) {
   288  	for {
   289  		select {
   290  		case logMessage, open := <-logStream:
   291  			if !open {
   292  				return
   293  			}
   294  			if logMessage.Staging() {
   295  				cmd.UI.DisplayLogMessage(logMessage, false)
   296  			}
   297  		case err, open := <-errStream:
   298  			if !open {
   299  				return
   300  			}
   301  			_, ok := err.(actionerror.NOAATimeoutError)
   302  			if ok {
   303  				cmd.UI.DisplayWarning("timeout connecting to log server, no log will be shown")
   304  			}
   305  			cmd.UI.DisplayWarning(err.Error())
   306  		}
   307  	}
   308  }
   309  
   310  func (cmd V3PushCommand) GetCommandLineSettings() (pushaction.CommandLineSettings, error) {
   311  	pwd, err := os.Getwd()
   312  	if err != nil {
   313  		return pushaction.CommandLineSettings{}, err
   314  	}
   315  	return pushaction.CommandLineSettings{
   316  		Buildpacks:       cmd.Buildpacks,
   317  		CurrentDirectory: pwd,
   318  		Name:             cmd.RequiredArgs.AppName,
   319  		ProvidedAppPath:  string(cmd.AppPath),
   320  	}, nil
   321  }