github.com/mook-as/cf-cli@v7.0.0-beta.28.0.20200120190804-b91c115fae48+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  	SharedActor         command.SharedActor
    58  	AppSummaryDisplayer shared.AppSummaryDisplayer
    59  	PackageDisplayer    shared.PackageDisplayer
    60  	ProgressBar         ProgressBar
    61  
    62  	ZdtActor            V3ZeroDowntimeVersionActor
    63  	V3PushActor         V3PushActor
    64  	OriginalV2PushActor OriginalV2PushActor
    65  }
    66  
    67  func (cmd *V3ZeroDowntimePushCommand) Setup(config command.Config, ui command.UI) error {
    68  	cmd.UI = ui
    69  	cmd.Config = config
    70  	sharedActor := sharedaction.NewActor(config)
    71  
    72  	ccClient, uaaClient, err := shared.NewV3BasedClients(config, ui, true)
    73  	if err != nil {
    74  		return err
    75  	}
    76  	v3actor := v3action.NewActor(ccClient, config, sharedActor, nil)
    77  	cmd.ZdtActor = v3actor
    78  	cmd.V3PushActor = v3actor
    79  
    80  	ccClientV2, uaaClientV2, err := shared.GetNewClientsAndConnectToCF(config, ui)
    81  	if err != nil {
    82  		return err
    83  	}
    84  
    85  	v2Actor := v2action.NewActor(ccClientV2, uaaClientV2, config)
    86  
    87  	cmd.SharedActor = sharedActor
    88  	cmd.OriginalV2PushActor = pushaction.NewActor(v2Actor, v3actor, sharedActor)
    89  
    90  	v2AppActor := v2action.NewActor(ccClientV2, uaaClientV2, config)
    91  	cmd.NOAAClient = shared.NewNOAAClient(ccClient.Info.Logging(), config, uaaClient, ui)
    92  
    93  	cmd.AppSummaryDisplayer = shared.AppSummaryDisplayer{
    94  		UI:         cmd.UI,
    95  		Config:     cmd.Config,
    96  		Actor:      cmd.V3PushActor,
    97  		V2AppActor: v2AppActor,
    98  		AppName:    cmd.RequiredArgs.AppName,
    99  	}
   100  	cmd.PackageDisplayer = shared.NewPackageDisplayer(cmd.UI, cmd.Config)
   101  
   102  	return nil
   103  }
   104  
   105  func (cmd V3ZeroDowntimePushCommand) Execute(args []string) error {
   106  	cmd.UI.DisplayWarning(command.ExperimentalWarning)
   107  
   108  	err := cmd.validateArgs()
   109  	if err != nil {
   110  		return err
   111  	}
   112  
   113  	err = command.MinimumCCAPIVersionCheck(cmd.ZdtActor.CloudControllerAPIVersion(), ccversion.MinVersionZeroDowntimePushV3)
   114  	if err != nil {
   115  		return err
   116  	}
   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  	if !verifyBuildpacks(cmd.Buildpacks) {
   129  		return translatableerror.ConflictingBuildpacksError{}
   130  	}
   131  
   132  	var app v3action.Application
   133  	app, err = cmd.getApplication()
   134  	if _, ok := err.(actionerror.ApplicationNotFoundError); ok {
   135  		app, err = cmd.createApplication(user.Name)
   136  		if err != nil {
   137  			return err
   138  		}
   139  	} else if err != nil {
   140  		return err
   141  	} else {
   142  		app, err = cmd.updateApplication(user.Name, app.GUID)
   143  		if err != nil {
   144  			return err
   145  		}
   146  	}
   147  
   148  	pkg, err := cmd.createPackage()
   149  	if err != nil {
   150  		return err
   151  	}
   152  
   153  	if cmd.NoStart {
   154  		return nil
   155  	}
   156  
   157  	dropletGUID, err := cmd.stagePackage(pkg, user.Name)
   158  	if err != nil {
   159  		return err
   160  	}
   161  
   162  	if !cmd.NoRoute {
   163  		err = cmd.createAndMapRoutes(app)
   164  		if err != nil {
   165  			return err
   166  		}
   167  	}
   168  
   169  	warnings := make(chan v3action.Warnings)
   170  	done := make(chan bool)
   171  	go func() {
   172  		for {
   173  			select {
   174  			case message := <-warnings:
   175  				cmd.UI.DisplayWarnings(message)
   176  			case <-done:
   177  				return
   178  			}
   179  		}
   180  	}()
   181  
   182  	switch app.State {
   183  	case constant.ApplicationStopped:
   184  		err = cmd.setApplicationDroplet(dropletGUID, user.Name)
   185  		if err != nil {
   186  			return err
   187  		}
   188  
   189  		err = cmd.restartApplication(app.GUID, user.Name)
   190  		if err != nil {
   191  			return err
   192  		}
   193  
   194  		cmd.UI.DisplayText("Waiting for app to start...")
   195  		err = cmd.ZdtActor.PollStart(app.GUID, warnings)
   196  
   197  	case constant.ApplicationStarted:
   198  		var deploymentGUID string
   199  		deploymentGUID, err = cmd.createDeployment(app.GUID, user.Name, dropletGUID)
   200  		if err != nil {
   201  			return err
   202  		}
   203  
   204  		cmd.UI.DisplayText("Waiting for app to start...")
   205  		if cmd.WaitUntilDeployed {
   206  			err = cmd.ZdtActor.PollDeployment(deploymentGUID, warnings) //
   207  		} else {
   208  			err = cmd.ZdtActor.ZeroDowntimePollStart(app.GUID, warnings)
   209  		}
   210  	default:
   211  		return fmt.Errorf("inconceivable application state: %s", app.State)
   212  	}
   213  
   214  	done <- true
   215  	if err != nil {
   216  		if _, ok := err.(actionerror.StartupTimeoutError); ok {
   217  			return translatableerror.StartupTimeoutError{
   218  				AppName:    cmd.RequiredArgs.AppName,
   219  				BinaryName: cmd.Config.BinaryName(),
   220  			}
   221  		}
   222  
   223  		return err
   224  	}
   225  
   226  	cmd.UI.DisplayTextWithFlavor("Showing health and status for app {{.AppName}} in org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}...", map[string]interface{}{
   227  		"AppName":   cmd.RequiredArgs.AppName,
   228  		"OrgName":   cmd.Config.TargetedOrganization().Name,
   229  		"SpaceName": cmd.Config.TargetedSpace().Name,
   230  		"Username":  user.Name,
   231  	})
   232  	cmd.UI.DisplayNewline()
   233  
   234  	return cmd.AppSummaryDisplayer.DisplayAppInfo()
   235  }
   236  
   237  func (cmd V3ZeroDowntimePushCommand) validateArgs() error {
   238  	switch {
   239  	case cmd.DockerImage.Path != "" && cmd.AppPath != "":
   240  		return translatableerror.ArgumentCombinationError{
   241  			Args: []string{"--docker-image", "-o", "-p"},
   242  		}
   243  	case cmd.DockerImage.Path != "" && len(cmd.Buildpacks) > 0:
   244  		return translatableerror.ArgumentCombinationError{
   245  			Args: []string{"-b", "--docker-image", "-o"},
   246  		}
   247  	case cmd.DockerUsername != "" && cmd.DockerImage.Path == "":
   248  		return translatableerror.RequiredFlagsError{
   249  			Arg1: "--docker-image, -o", Arg2: "--docker-username",
   250  		}
   251  	case cmd.DockerUsername != "" && cmd.Config.DockerPassword() == "":
   252  		return translatableerror.DockerPasswordNotSetError{}
   253  	}
   254  	return nil
   255  }
   256  
   257  func (cmd V3ZeroDowntimePushCommand) createApplication(userName string) (v3action.Application, error) {
   258  	appToCreate := v3action.Application{
   259  		Name: cmd.RequiredArgs.AppName,
   260  	}
   261  
   262  	if cmd.DockerImage.Path != "" {
   263  		appToCreate.LifecycleType = constant.AppLifecycleTypeDocker
   264  	} else {
   265  		appToCreate.LifecycleType = constant.AppLifecycleTypeBuildpack
   266  		appToCreate.LifecycleBuildpacks = cmd.Buildpacks
   267  		appToCreate.StackName = cmd.StackName
   268  	}
   269  
   270  	app, warnings, err := cmd.ZdtActor.CreateApplicationInSpace(
   271  		appToCreate,
   272  		cmd.Config.TargetedSpace().GUID,
   273  	)
   274  	cmd.UI.DisplayWarnings(warnings)
   275  	if err != nil {
   276  		return v3action.Application{}, err
   277  	}
   278  
   279  	cmd.UI.DisplayTextWithFlavor("Creating app {{.AppName}} in org {{.CurrentOrg}} / space {{.CurrentSpace}} as {{.CurrentUser}}...", map[string]interface{}{
   280  		"AppName":      cmd.RequiredArgs.AppName,
   281  		"CurrentSpace": cmd.Config.TargetedSpace().Name,
   282  		"CurrentOrg":   cmd.Config.TargetedOrganization().Name,
   283  		"CurrentUser":  userName,
   284  	})
   285  
   286  	cmd.UI.DisplayOK()
   287  	return app, nil
   288  }
   289  
   290  func (cmd V3ZeroDowntimePushCommand) getApplication() (v3action.Application, error) {
   291  	app, warnings, err := cmd.ZdtActor.GetApplicationByNameAndSpace(cmd.RequiredArgs.AppName, cmd.Config.TargetedSpace().GUID)
   292  	cmd.UI.DisplayWarnings(warnings)
   293  	if err != nil {
   294  		return v3action.Application{}, err
   295  	}
   296  
   297  	return app, nil
   298  }
   299  
   300  func (cmd V3ZeroDowntimePushCommand) updateApplication(userName string, appGUID string) (v3action.Application, error) {
   301  	cmd.UI.DisplayTextWithFlavor("Updating app {{.AppName}} in org {{.CurrentOrg}} / space {{.CurrentSpace}} as {{.CurrentUser}}...", map[string]interface{}{
   302  		"AppName":      cmd.RequiredArgs.AppName,
   303  		"CurrentSpace": cmd.Config.TargetedSpace().Name,
   304  		"CurrentOrg":   cmd.Config.TargetedOrganization().Name,
   305  		"CurrentUser":  userName,
   306  	})
   307  
   308  	appToUpdate := v3action.Application{
   309  		GUID: appGUID,
   310  	}
   311  
   312  	if cmd.DockerImage.Path != "" {
   313  		appToUpdate.LifecycleType = constant.AppLifecycleTypeDocker
   314  
   315  	} else {
   316  		appToUpdate.LifecycleType = constant.AppLifecycleTypeBuildpack
   317  		appToUpdate.LifecycleBuildpacks = cmd.Buildpacks
   318  		appToUpdate.StackName = cmd.StackName
   319  	}
   320  
   321  	app, warnings, err := cmd.ZdtActor.UpdateApplication(appToUpdate)
   322  	cmd.UI.DisplayWarnings(warnings)
   323  	if err != nil {
   324  		return v3action.Application{}, err
   325  	}
   326  
   327  	cmd.UI.DisplayOK()
   328  	return app, nil
   329  }
   330  
   331  func (cmd V3ZeroDowntimePushCommand) createAndMapRoutes(app v3action.Application) error {
   332  	cmd.UI.DisplayText("Mapping routes...")
   333  	routeWarnings, err := cmd.OriginalV2PushActor.CreateAndMapDefaultApplicationRoute(cmd.Config.TargetedOrganization().GUID, cmd.Config.TargetedSpace().GUID, v2action.Application{Name: app.Name, GUID: app.GUID})
   334  	cmd.UI.DisplayWarnings(routeWarnings)
   335  	if err != nil {
   336  		return err
   337  	}
   338  
   339  	cmd.UI.DisplayOK()
   340  	return nil
   341  }
   342  
   343  func (cmd V3ZeroDowntimePushCommand) createPackage() (v3action.Package, error) {
   344  	isDockerImage := cmd.DockerImage.Path != ""
   345  	err := cmd.PackageDisplayer.DisplaySetupMessage(cmd.RequiredArgs.AppName, isDockerImage)
   346  	if err != nil {
   347  		return v3action.Package{}, err
   348  	}
   349  
   350  	var (
   351  		pkg      v3action.Package
   352  		warnings v3action.Warnings
   353  	)
   354  
   355  	if isDockerImage {
   356  		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()})
   357  	} else {
   358  		pkg, warnings, err = cmd.ZdtActor.CreateAndUploadBitsPackageByApplicationNameAndSpace(cmd.RequiredArgs.AppName, cmd.Config.TargetedSpace().GUID, string(cmd.AppPath))
   359  	}
   360  
   361  	cmd.UI.DisplayWarnings(warnings)
   362  	if err != nil {
   363  		return v3action.Package{}, err
   364  	}
   365  
   366  	cmd.UI.DisplayOK()
   367  	return pkg, nil
   368  }
   369  
   370  func (cmd V3ZeroDowntimePushCommand) stagePackage(pkg v3action.Package, userName string) (string, error) {
   371  	cmd.UI.DisplayTextWithFlavor("Staging package for app {{.AppName}} in org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}...", map[string]interface{}{
   372  		"AppName":   cmd.RequiredArgs.AppName,
   373  		"OrgName":   cmd.Config.TargetedOrganization().Name,
   374  		"SpaceName": cmd.Config.TargetedSpace().Name,
   375  		"Username":  userName,
   376  	})
   377  
   378  	logStream, logErrStream, logWarnings, logErr := cmd.ZdtActor.GetStreamingLogsForApplicationByNameAndSpace(cmd.RequiredArgs.AppName, cmd.Config.TargetedSpace().GUID, cmd.NOAAClient)
   379  	cmd.UI.DisplayWarnings(logWarnings)
   380  	if logErr != nil {
   381  		return "", logErr
   382  	}
   383  
   384  	buildStream, warningsStream, errStream := cmd.ZdtActor.StagePackage(pkg.GUID, cmd.RequiredArgs.AppName)
   385  	droplet, err := shared.PollStage(buildStream, warningsStream, errStream, logStream, logErrStream, cmd.UI)
   386  	if err != nil {
   387  		return "", err
   388  	}
   389  
   390  	cmd.UI.DisplayOK()
   391  	return droplet.GUID, nil
   392  }
   393  
   394  func (cmd V3ZeroDowntimePushCommand) setApplicationDroplet(dropletGUID string, userName string) error {
   395  	cmd.UI.DisplayTextWithFlavor("Setting app {{.AppName}} to droplet {{.DropletGUID}} in org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}...", map[string]interface{}{
   396  		"AppName":     cmd.RequiredArgs.AppName,
   397  		"DropletGUID": dropletGUID,
   398  		"OrgName":     cmd.Config.TargetedOrganization().Name,
   399  		"SpaceName":   cmd.Config.TargetedSpace().Name,
   400  		"Username":    userName,
   401  	})
   402  
   403  	warnings, err := cmd.ZdtActor.SetApplicationDropletByApplicationNameAndSpace(cmd.RequiredArgs.AppName, cmd.Config.TargetedSpace().GUID, dropletGUID)
   404  	cmd.UI.DisplayWarnings(warnings)
   405  	if err != nil {
   406  		return err
   407  	}
   408  
   409  	cmd.UI.DisplayOK()
   410  	return nil
   411  }
   412  
   413  func (cmd V3ZeroDowntimePushCommand) restartApplication(appGUID string, userName string) error {
   414  	cmd.UI.DisplayTextWithFlavor("Starting app {{.AppName}} in org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}...", map[string]interface{}{
   415  		"AppName":   cmd.RequiredArgs.AppName,
   416  		"OrgName":   cmd.Config.TargetedOrganization().Name,
   417  		"SpaceName": cmd.Config.TargetedSpace().Name,
   418  		"Username":  userName,
   419  	})
   420  
   421  	warnings, err := cmd.ZdtActor.RestartApplication(appGUID)
   422  	cmd.UI.DisplayWarnings(warnings)
   423  	if err != nil {
   424  		return err
   425  	}
   426  	cmd.UI.DisplayOK()
   427  	return nil
   428  }
   429  
   430  func (cmd V3ZeroDowntimePushCommand) createDeployment(appGUID string, userName string, dropletGUID string) (string, error) {
   431  	cmd.UI.DisplayTextWithFlavor("Starting deployment for app {{.AppName}} in org {{.CurrentOrg}} / space {{.CurrentSpace}} as {{.CurrentUser}}...", map[string]interface{}{
   432  		"AppName":      cmd.RequiredArgs.AppName,
   433  		"CurrentSpace": cmd.Config.TargetedSpace().Name,
   434  		"CurrentOrg":   cmd.Config.TargetedOrganization().Name,
   435  		"CurrentUser":  userName,
   436  	})
   437  
   438  	deploymentGUID, warnings, err := cmd.ZdtActor.CreateDeployment(appGUID, dropletGUID)
   439  	cmd.UI.DisplayWarnings(warnings)
   440  	if err != nil {
   441  		return "", err
   442  	}
   443  	cmd.UI.DisplayOK()
   444  	return deploymentGUID, nil
   445  }