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

     1  package v6
     2  
     3  import (
     4  	"fmt"
     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/api/cloudcontroller/ccv3/constant"
    12  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccversion"
    13  	"code.cloudfoundry.org/cli/command"
    14  	"code.cloudfoundry.org/cli/command/flag"
    15  	"code.cloudfoundry.org/cli/command/translatableerror"
    16  	"code.cloudfoundry.org/cli/command/v6/shared"
    17  )
    18  
    19  //go:generate counterfeiter . V3ZeroDowntimeVersionActor
    20  
    21  type V3ZeroDowntimeVersionActor interface {
    22  	ZeroDowntimePollStart(appGUID string, warningsChannel chan<- v3action.Warnings) error
    23  	CreateDeployment(appGUID string, deploymentGUID string) (string, v3action.Warnings, error)
    24  	PollDeployment(deploymentGUID string, warningsChannel chan<- v3action.Warnings) error
    25  	CloudControllerAPIVersion() string
    26  	CreateAndUploadBitsPackageByApplicationNameAndSpace(appName string, spaceGUID string, bitsPath string) (v3action.Package, v3action.Warnings, error)
    27  	CreateDockerPackageByApplicationNameAndSpace(appName string, spaceGUID string, dockerImageCredentials v3action.DockerImageCredentials) (v3action.Package, v3action.Warnings, error)
    28  	CreateApplicationInSpace(app v3action.Application, spaceGUID string) (v3action.Application, v3action.Warnings, error)
    29  	GetApplicationByNameAndSpace(appName string, spaceGUID string) (v3action.Application, v3action.Warnings, error)
    30  	GetCurrentDropletByApplication(appGUID string) (v3action.Droplet, v3action.Warnings, error)
    31  	GetStreamingLogsForApplicationByNameAndSpace(appName string, spaceGUID string, client v3action.NOAAClient) (<-chan *v3action.LogMessage, <-chan error, v3action.Warnings, error)
    32  	PollStart(appGUID string, warningsChannel chan<- v3action.Warnings) error
    33  	SetApplicationDropletByApplicationNameAndSpace(appName string, spaceGUID string, dropletGUID string) (v3action.Warnings, error)
    34  	StagePackage(packageGUID string, appName string) (<-chan v3action.Droplet, <-chan v3action.Warnings, <-chan error)
    35  	RestartApplication(appGUID string) (v3action.Warnings, error)
    36  	UpdateApplication(app v3action.Application) (v3action.Application, v3action.Warnings, error)
    37  }
    38  
    39  type V3ZeroDowntimePushCommand struct {
    40  	RequiredArgs        flag.AppName                `positional-args:"yes"`
    41  	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'"`
    42  	StackName           string                      `short:"s" description:"Stack to use (a stack is a pre-built file system, including an operating system, that can run apps)"`
    43  	DockerImage         flag.DockerImage            `long:"docker-image" short:"o" description:"Docker image to use (e.g. user/docker-image-name)"`
    44  	DockerUsername      string                      `long:"docker-username" description:"Repository username; used with password from environment variable CF_DOCKER_PASSWORD"`
    45  	NoRoute             bool                        `long:"no-route" description:"Do not map a route to this app"`
    46  	NoStart             bool                        `long:"no-start" description:"Do not stage and start the app after pushing"`
    47  	WaitUntilDeployed   bool                        `long:"wait-for-deploy-complete" description:"Wait for the entire deployment to complete"`
    48  	AppPath             flag.PathWithExistenceCheck `short:"p" description:"Path to app directory or to a zip file of the contents of the app directory"`
    49  	dockerPassword      interface{}                 `environmentName:"CF_DOCKER_PASSWORD" environmentDescription:"Password used for private docker repository"`
    50  	usage               interface{}                 `usage:"CF_NAME v3-zdt-push APP_NAME [-b BUILDPACK]... [-p APP_PATH] [--no-route] [--no-start]\n   CF_NAME v3-zdt-push APP_NAME --docker-image [REGISTRY_HOST:PORT/]IMAGE[:TAG] [--docker-username USERNAME] [--no-route] [--no-start]"`
    51  	envCFStagingTimeout interface{}                 `environmentName:"CF_STAGING_TIMEOUT" environmentDescription:"Max wait time for buildpack staging, in minutes" environmentDefault:"15"`
    52  	envCFStartupTimeout interface{}                 `environmentName:"CF_STARTUP_TIMEOUT" environmentDescription:"Max wait time for app instance startup, in minutes" environmentDefault:"5"`
    53  
    54  	UI                  command.UI
    55  	Config              command.Config
    56  	NOAAClient          v3action.NOAAClient
    57  	Actor               V3PushActor
    58  	VersionActor        V3PushVersionActor
    59  	SharedActor         command.SharedActor
    60  	AppSummaryDisplayer shared.AppSummaryDisplayer
    61  	PackageDisplayer    shared.PackageDisplayer
    62  	ProgressBar         ProgressBar
    63  
    64  	ZdtActor            V3ZeroDowntimeVersionActor
    65  	OriginalV3PushActor OriginalV3PushActor
    66  	OriginalV2PushActor OriginalV2PushActor
    67  }
    68  
    69  func (cmd *V3ZeroDowntimePushCommand) Setup(config command.Config, ui command.UI) error {
    70  	cmd.UI = ui
    71  	cmd.Config = config
    72  	sharedActor := sharedaction.NewActor(config)
    73  
    74  	ccClient, uaaClient, err := shared.NewV3BasedClients(config, ui, true, "")
    75  	if err != nil {
    76  		return err
    77  	}
    78  	v3actor := v3action.NewActor(ccClient, config, sharedActor, nil)
    79  	cmd.ZdtActor = v3actor
    80  	cmd.OriginalV3PushActor = v3actor
    81  
    82  	ccClientV2, uaaClientV2, err := shared.NewClients(config, ui, true)
    83  	if err != nil {
    84  		return err
    85  	}
    86  
    87  	v2Actor := v2action.NewActor(ccClientV2, uaaClientV2, config)
    88  
    89  	cmd.SharedActor = sharedActor
    90  	cmd.OriginalV2PushActor = pushaction.NewActor(v2Actor, v3actor, sharedActor)
    91  
    92  	v2AppActor := v2action.NewActor(ccClientV2, uaaClientV2, config)
    93  	cmd.NOAAClient = shared.NewNOAAClient(ccClient.Info.Logging(), config, uaaClient, ui)
    94  
    95  	cmd.AppSummaryDisplayer = shared.AppSummaryDisplayer{
    96  		UI:         cmd.UI,
    97  		Config:     cmd.Config,
    98  		Actor:      cmd.OriginalV3PushActor,
    99  		V2AppActor: v2AppActor,
   100  		AppName:    cmd.RequiredArgs.AppName,
   101  	}
   102  	cmd.PackageDisplayer = shared.NewPackageDisplayer(cmd.UI, cmd.Config)
   103  
   104  	return nil
   105  }
   106  
   107  func (cmd V3ZeroDowntimePushCommand) Execute(args []string) error {
   108  	cmd.UI.DisplayWarning(command.ExperimentalWarning)
   109  
   110  	err := cmd.validateArgs()
   111  	if err != nil {
   112  		return err
   113  	}
   114  
   115  	err = command.MinimumCCAPIVersionCheck(cmd.ZdtActor.CloudControllerAPIVersion(), ccversion.MinVersionZeroDowntimePushV3)
   116  	if err != nil {
   117  		return err
   118  	}
   119  
   120  	err = cmd.SharedActor.CheckTarget(true, true)
   121  	if err != nil {
   122  		return err
   123  	}
   124  
   125  	user, err := cmd.Config.CurrentUser()
   126  	if err != nil {
   127  		return err
   128  	}
   129  
   130  	if !verifyBuildpacks(cmd.Buildpacks) {
   131  		return translatableerror.ConflictingBuildpacksError{}
   132  	}
   133  
   134  	var app v3action.Application
   135  	app, err = cmd.getApplication()
   136  	if _, ok := err.(actionerror.ApplicationNotFoundError); ok {
   137  		app, err = cmd.createApplication(user.Name)
   138  		if err != nil {
   139  			return err
   140  		}
   141  	} else if err != nil {
   142  		return err
   143  	} else {
   144  		app, err = cmd.updateApplication(user.Name, app.GUID)
   145  		if err != nil {
   146  			return err
   147  		}
   148  	}
   149  
   150  	pkg, err := cmd.createPackage()
   151  	if err != nil {
   152  		return err
   153  	}
   154  
   155  	if cmd.NoStart {
   156  		return nil
   157  	}
   158  
   159  	dropletGUID, err := cmd.stagePackage(pkg, user.Name)
   160  	if err != nil {
   161  		return err
   162  	}
   163  
   164  	if !cmd.NoRoute {
   165  		err = cmd.createAndMapRoutes(app)
   166  		if err != nil {
   167  			return err
   168  		}
   169  	}
   170  
   171  	warnings := make(chan v3action.Warnings)
   172  	done := make(chan bool)
   173  	go func() {
   174  		for {
   175  			select {
   176  			case message := <-warnings:
   177  				cmd.UI.DisplayWarnings(message)
   178  			case <-done:
   179  				return
   180  			}
   181  		}
   182  	}()
   183  
   184  	switch app.State {
   185  	case constant.ApplicationStopped:
   186  		err = cmd.setApplicationDroplet(dropletGUID, user.Name)
   187  		if err != nil {
   188  			return err
   189  		}
   190  
   191  		err = cmd.restartApplication(app.GUID, user.Name)
   192  		if err != nil {
   193  			return err
   194  		}
   195  
   196  		cmd.UI.DisplayText("Waiting for app to start...")
   197  		err = cmd.ZdtActor.PollStart(app.GUID, warnings)
   198  
   199  	case constant.ApplicationStarted:
   200  		var deploymentGUID string
   201  		deploymentGUID, err = cmd.createDeployment(app.GUID, user.Name, dropletGUID)
   202  		if err != nil {
   203  			return err
   204  		}
   205  
   206  		cmd.UI.DisplayText("Waiting for app to start...")
   207  		if cmd.WaitUntilDeployed {
   208  			err = cmd.ZdtActor.PollDeployment(deploymentGUID, warnings) //
   209  		} else {
   210  			err = cmd.ZdtActor.ZeroDowntimePollStart(app.GUID, warnings)
   211  		}
   212  	default:
   213  		return fmt.Errorf("inconceivable application state: %s", app.State)
   214  	}
   215  
   216  	done <- true
   217  	if err != nil {
   218  		if _, ok := err.(actionerror.StartupTimeoutError); ok {
   219  			return translatableerror.StartupTimeoutError{
   220  				AppName:    cmd.RequiredArgs.AppName,
   221  				BinaryName: cmd.Config.BinaryName(),
   222  			}
   223  		}
   224  
   225  		return err
   226  	}
   227  
   228  	cmd.UI.DisplayTextWithFlavor("Showing health and status for app {{.AppName}} in org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}...", map[string]interface{}{
   229  		"AppName":   cmd.RequiredArgs.AppName,
   230  		"OrgName":   cmd.Config.TargetedOrganization().Name,
   231  		"SpaceName": cmd.Config.TargetedSpace().Name,
   232  		"Username":  user.Name,
   233  	})
   234  	cmd.UI.DisplayNewline()
   235  
   236  	return cmd.AppSummaryDisplayer.DisplayAppInfo()
   237  }
   238  
   239  func (cmd V3ZeroDowntimePushCommand) validateArgs() error {
   240  	switch {
   241  	case cmd.DockerImage.Path != "" && cmd.AppPath != "":
   242  		return translatableerror.ArgumentCombinationError{
   243  			Args: []string{"--docker-image", "-o", "-p"},
   244  		}
   245  	case cmd.DockerImage.Path != "" && len(cmd.Buildpacks) > 0:
   246  		return translatableerror.ArgumentCombinationError{
   247  			Args: []string{"-b", "--docker-image", "-o"},
   248  		}
   249  	case cmd.DockerUsername != "" && cmd.DockerImage.Path == "":
   250  		return translatableerror.RequiredFlagsError{
   251  			Arg1: "--docker-image, -o", Arg2: "--docker-username",
   252  		}
   253  	case cmd.DockerUsername != "" && cmd.Config.DockerPassword() == "":
   254  		return translatableerror.DockerPasswordNotSetError{}
   255  	}
   256  	return nil
   257  }
   258  
   259  func (cmd V3ZeroDowntimePushCommand) createApplication(userName string) (v3action.Application, error) {
   260  	appToCreate := v3action.Application{
   261  		Name: cmd.RequiredArgs.AppName,
   262  	}
   263  
   264  	if cmd.DockerImage.Path != "" {
   265  		appToCreate.LifecycleType = constant.AppLifecycleTypeDocker
   266  	} else {
   267  		appToCreate.LifecycleType = constant.AppLifecycleTypeBuildpack
   268  		appToCreate.LifecycleBuildpacks = cmd.Buildpacks
   269  		appToCreate.StackName = cmd.StackName
   270  	}
   271  
   272  	app, warnings, err := cmd.ZdtActor.CreateApplicationInSpace(
   273  		appToCreate,
   274  		cmd.Config.TargetedSpace().GUID,
   275  	)
   276  	cmd.UI.DisplayWarnings(warnings)
   277  	if err != nil {
   278  		return v3action.Application{}, err
   279  	}
   280  
   281  	cmd.UI.DisplayTextWithFlavor("Creating app {{.AppName}} in org {{.CurrentOrg}} / space {{.CurrentSpace}} as {{.CurrentUser}}...", map[string]interface{}{
   282  		"AppName":      cmd.RequiredArgs.AppName,
   283  		"CurrentSpace": cmd.Config.TargetedSpace().Name,
   284  		"CurrentOrg":   cmd.Config.TargetedOrganization().Name,
   285  		"CurrentUser":  userName,
   286  	})
   287  
   288  	cmd.UI.DisplayOK()
   289  	return app, nil
   290  }
   291  
   292  func (cmd V3ZeroDowntimePushCommand) getApplication() (v3action.Application, error) {
   293  	app, warnings, err := cmd.ZdtActor.GetApplicationByNameAndSpace(cmd.RequiredArgs.AppName, cmd.Config.TargetedSpace().GUID)
   294  	cmd.UI.DisplayWarnings(warnings)
   295  	if err != nil {
   296  		return v3action.Application{}, err
   297  	}
   298  
   299  	return app, nil
   300  }
   301  
   302  func (cmd V3ZeroDowntimePushCommand) updateApplication(userName string, appGUID string) (v3action.Application, error) {
   303  	cmd.UI.DisplayTextWithFlavor("Updating app {{.AppName}} in org {{.CurrentOrg}} / space {{.CurrentSpace}} as {{.CurrentUser}}...", map[string]interface{}{
   304  		"AppName":      cmd.RequiredArgs.AppName,
   305  		"CurrentSpace": cmd.Config.TargetedSpace().Name,
   306  		"CurrentOrg":   cmd.Config.TargetedOrganization().Name,
   307  		"CurrentUser":  userName,
   308  	})
   309  
   310  	appToUpdate := v3action.Application{
   311  		GUID: appGUID,
   312  	}
   313  
   314  	if cmd.DockerImage.Path != "" {
   315  		appToUpdate.LifecycleType = constant.AppLifecycleTypeDocker
   316  
   317  	} else {
   318  		appToUpdate.LifecycleType = constant.AppLifecycleTypeBuildpack
   319  		appToUpdate.LifecycleBuildpacks = cmd.Buildpacks
   320  		appToUpdate.StackName = cmd.StackName
   321  	}
   322  
   323  	app, warnings, err := cmd.ZdtActor.UpdateApplication(appToUpdate)
   324  	cmd.UI.DisplayWarnings(warnings)
   325  	if err != nil {
   326  		return v3action.Application{}, err
   327  	}
   328  
   329  	cmd.UI.DisplayOK()
   330  	return app, nil
   331  }
   332  
   333  func (cmd V3ZeroDowntimePushCommand) createAndMapRoutes(app v3action.Application) error {
   334  	cmd.UI.DisplayText("Mapping routes...")
   335  	routeWarnings, err := cmd.OriginalV2PushActor.CreateAndMapDefaultApplicationRoute(cmd.Config.TargetedOrganization().GUID, cmd.Config.TargetedSpace().GUID, v2action.Application{Name: app.Name, GUID: app.GUID})
   336  	cmd.UI.DisplayWarnings(routeWarnings)
   337  	if err != nil {
   338  		return err
   339  	}
   340  
   341  	cmd.UI.DisplayOK()
   342  	return nil
   343  }
   344  
   345  func (cmd V3ZeroDowntimePushCommand) createPackage() (v3action.Package, error) {
   346  	isDockerImage := cmd.DockerImage.Path != ""
   347  	err := cmd.PackageDisplayer.DisplaySetupMessage(cmd.RequiredArgs.AppName, isDockerImage)
   348  	if err != nil {
   349  		return v3action.Package{}, err
   350  	}
   351  
   352  	var (
   353  		pkg      v3action.Package
   354  		warnings v3action.Warnings
   355  	)
   356  
   357  	if isDockerImage {
   358  		pkg, warnings, err = cmd.ZdtActor.CreateDockerPackageByApplicationNameAndSpace(cmd.RequiredArgs.AppName, cmd.Config.TargetedSpace().GUID, v3action.DockerImageCredentials{Path: cmd.DockerImage.Path, Username: cmd.DockerUsername, Password: cmd.Config.DockerPassword()})
   359  	} else {
   360  		pkg, warnings, err = cmd.ZdtActor.CreateAndUploadBitsPackageByApplicationNameAndSpace(cmd.RequiredArgs.AppName, cmd.Config.TargetedSpace().GUID, string(cmd.AppPath))
   361  	}
   362  
   363  	cmd.UI.DisplayWarnings(warnings)
   364  	if err != nil {
   365  		return v3action.Package{}, err
   366  	}
   367  
   368  	cmd.UI.DisplayOK()
   369  	return pkg, nil
   370  }
   371  
   372  func (cmd V3ZeroDowntimePushCommand) stagePackage(pkg v3action.Package, userName string) (string, error) {
   373  	cmd.UI.DisplayTextWithFlavor("Staging package for app {{.AppName}} in org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}...", map[string]interface{}{
   374  		"AppName":   cmd.RequiredArgs.AppName,
   375  		"OrgName":   cmd.Config.TargetedOrganization().Name,
   376  		"SpaceName": cmd.Config.TargetedSpace().Name,
   377  		"Username":  userName,
   378  	})
   379  
   380  	logStream, logErrStream, logWarnings, logErr := cmd.ZdtActor.GetStreamingLogsForApplicationByNameAndSpace(cmd.RequiredArgs.AppName, cmd.Config.TargetedSpace().GUID, cmd.NOAAClient)
   381  	cmd.UI.DisplayWarnings(logWarnings)
   382  	if logErr != nil {
   383  		return "", logErr
   384  	}
   385  
   386  	buildStream, warningsStream, errStream := cmd.ZdtActor.StagePackage(pkg.GUID, cmd.RequiredArgs.AppName)
   387  	droplet, err := shared.PollStage(buildStream, warningsStream, errStream, logStream, logErrStream, cmd.UI)
   388  	if err != nil {
   389  		return "", err
   390  	}
   391  
   392  	cmd.UI.DisplayOK()
   393  	return droplet.GUID, nil
   394  }
   395  
   396  func (cmd V3ZeroDowntimePushCommand) setApplicationDroplet(dropletGUID string, userName string) error {
   397  	cmd.UI.DisplayTextWithFlavor("Setting app {{.AppName}} to droplet {{.DropletGUID}} in org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}...", map[string]interface{}{
   398  		"AppName":     cmd.RequiredArgs.AppName,
   399  		"DropletGUID": dropletGUID,
   400  		"OrgName":     cmd.Config.TargetedOrganization().Name,
   401  		"SpaceName":   cmd.Config.TargetedSpace().Name,
   402  		"Username":    userName,
   403  	})
   404  
   405  	warnings, err := cmd.ZdtActor.SetApplicationDropletByApplicationNameAndSpace(cmd.RequiredArgs.AppName, cmd.Config.TargetedSpace().GUID, dropletGUID)
   406  	cmd.UI.DisplayWarnings(warnings)
   407  	if err != nil {
   408  		return err
   409  	}
   410  
   411  	cmd.UI.DisplayOK()
   412  	return nil
   413  }
   414  
   415  func (cmd V3ZeroDowntimePushCommand) restartApplication(appGUID string, userName string) error {
   416  	cmd.UI.DisplayTextWithFlavor("Starting app {{.AppName}} in org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}...", map[string]interface{}{
   417  		"AppName":   cmd.RequiredArgs.AppName,
   418  		"OrgName":   cmd.Config.TargetedOrganization().Name,
   419  		"SpaceName": cmd.Config.TargetedSpace().Name,
   420  		"Username":  userName,
   421  	})
   422  
   423  	warnings, err := cmd.ZdtActor.RestartApplication(appGUID)
   424  	cmd.UI.DisplayWarnings(warnings)
   425  	if err != nil {
   426  		return err
   427  	}
   428  	cmd.UI.DisplayOK()
   429  	return nil
   430  }
   431  
   432  func (cmd V3ZeroDowntimePushCommand) createDeployment(appGUID string, userName string, dropletGUID string) (string, error) {
   433  	cmd.UI.DisplayTextWithFlavor("Starting deployment for app {{.AppName}} in org {{.CurrentOrg}} / space {{.CurrentSpace}} as {{.CurrentUser}}...", map[string]interface{}{
   434  		"AppName":      cmd.RequiredArgs.AppName,
   435  		"CurrentSpace": cmd.Config.TargetedSpace().Name,
   436  		"CurrentOrg":   cmd.Config.TargetedOrganization().Name,
   437  		"CurrentUser":  userName,
   438  	})
   439  
   440  	deploymentGUID, warnings, err := cmd.ZdtActor.CreateDeployment(appGUID, dropletGUID)
   441  	cmd.UI.DisplayWarnings(warnings)
   442  	if err != nil {
   443  		return "", err
   444  	}
   445  	cmd.UI.DisplayOK()
   446  	return deploymentGUID, nil
   447  }