github.com/cloudfoundry-attic/cli-with-i18n@v6.32.1-0.20171002233121-7401370d3b85+incompatible/command/v3/v3_push_command.go (about)

     1  package v3
     2  
     3  import (
     4  	"net/http"
     5  
     6  	"code.cloudfoundry.org/cli/actor/pushaction"
     7  	"code.cloudfoundry.org/cli/actor/sharedaction"
     8  	"code.cloudfoundry.org/cli/actor/v2action"
     9  	"code.cloudfoundry.org/cli/actor/v3action"
    10  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccerror"
    11  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccversion"
    12  	"code.cloudfoundry.org/cli/command"
    13  	"code.cloudfoundry.org/cli/command/flag"
    14  	"code.cloudfoundry.org/cli/command/translatableerror"
    15  	sharedV2 "code.cloudfoundry.org/cli/command/v2/shared"
    16  	"code.cloudfoundry.org/cli/command/v3/shared"
    17  )
    18  
    19  //go:generate counterfeiter . V2PushActor
    20  
    21  type V2PushActor interface {
    22  	CreateAndBindApplicationRoutes(orgGUID string, spaceGUID string, app v2action.Application) (pushaction.Warnings, error)
    23  }
    24  
    25  //go:generate counterfeiter . V3PushActor
    26  
    27  type V3PushActor interface {
    28  	CloudControllerAPIVersion() string
    29  	CreateAndUploadBitsPackageByApplicationNameAndSpace(appName string, spaceGUID string, bitsPath string) (v3action.Package, v3action.Warnings, error)
    30  	CreateDockerPackageByApplicationNameAndSpace(appName string, spaceGUID string, dockerImageCredentials v3action.DockerImageCredentials) (v3action.Package, v3action.Warnings, error)
    31  	CreateApplicationInSpace(app v3action.Application, spaceGUID string) (v3action.Application, v3action.Warnings, error)
    32  	GetApplicationByNameAndSpace(appName string, spaceGUID string) (v3action.Application, v3action.Warnings, error)
    33  	GetApplicationSummaryByNameAndSpace(appName string, spaceGUID string) (v3action.ApplicationSummary, v3action.Warnings, error)
    34  	GetStreamingLogsForApplicationByNameAndSpace(appName string, spaceGUID string, client v3action.NOAAClient) (<-chan *v3action.LogMessage, <-chan error, v3action.Warnings, error)
    35  	PollStart(appGUID string, warnings chan<- v3action.Warnings) error
    36  	SetApplicationDroplet(appName string, spaceGUID string, dropletGUID string) (v3action.Warnings, error)
    37  	StagePackage(packageGUID string, appName string) (<-chan v3action.Droplet, <-chan v3action.Warnings, <-chan error)
    38  	StartApplication(appGUID string) (v3action.Application, v3action.Warnings, error)
    39  	StopApplication(appGUID string) (v3action.Warnings, error)
    40  	UpdateApplication(app v3action.Application) (v3action.Application, v3action.Warnings, error)
    41  }
    42  
    43  type V3PushCommand struct {
    44  	RequiredArgs   flag.AppName                `positional-args:"yes"`
    45  	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'"`
    46  	DockerImage    flag.DockerImage            `long:"docker-image" short:"o" description:"Docker image to use (e.g. user/docker-image-name)"`
    47  	DockerUsername string                      `long:"docker-username" description:"Repository username; used with password from environment variable CF_DOCKER_PASSWORD"`
    48  	NoRoute        bool                        `long:"no-route" description:"Do not map a route to this app"`
    49  	AppPath        flag.PathWithExistenceCheck `short:"p" description:"Path to app directory or to a zip file of the contents of the app directory"`
    50  	dockerPassword interface{}                 `environmentName:"CF_DOCKER_PASSWORD" environmentDescription:"Password used for private docker repository"`
    51  
    52  	usage               interface{} `usage:"cf v3-push APP_NAME [-b BUILDPACK]... [-p APP_PATH] [--no-route]\n   cf v3-push APP_NAME --docker-image [REGISTRY_HOST:PORT/]IMAGE[:TAG] [--docker-username USERNAME] [--no-route]"`
    53  	envCFStagingTimeout interface{} `environmentName:"CF_STAGING_TIMEOUT" environmentDescription:"Max wait time for buildpack staging, in minutes" environmentDefault:"15"`
    54  	envCFStartupTimeout interface{} `environmentName:"CF_STARTUP_TIMEOUT" environmentDescription:"Max wait time for app instance startup, in minutes" environmentDefault:"5"`
    55  
    56  	UI                  command.UI
    57  	Config              command.Config
    58  	NOAAClient          v3action.NOAAClient
    59  	SharedActor         command.SharedActor
    60  	Actor               V3PushActor
    61  	V2PushActor         V2PushActor
    62  	AppSummaryDisplayer shared.AppSummaryDisplayer
    63  	PackageDisplayer    shared.PackageDisplayer
    64  }
    65  
    66  func (cmd *V3PushCommand) Setup(config command.Config, ui command.UI) error {
    67  	cmd.UI = ui
    68  	cmd.Config = config
    69  	sharedActor := sharedaction.NewActor(config)
    70  
    71  	ccClient, uaaClient, err := shared.NewClients(config, ui, true)
    72  	if err != nil {
    73  		if v3Err, ok := err.(ccerror.V3UnexpectedResponseError); ok && v3Err.ResponseCode == http.StatusNotFound {
    74  			return translatableerror.MinimumAPIVersionNotMetError{MinimumVersion: ccversion.MinVersionV3}
    75  		}
    76  
    77  		return err
    78  	}
    79  	cmd.Actor = v3action.NewActor(sharedActor, ccClient, config)
    80  
    81  	ccClientV2, uaaClientV2, err := sharedV2.NewClients(config, ui, true)
    82  	if err != nil {
    83  		return err
    84  	}
    85  
    86  	v2Actor := v2action.NewActor(ccClientV2, uaaClientV2, config)
    87  
    88  	cmd.SharedActor = sharedActor
    89  	cmd.V2PushActor = pushaction.NewActor(v2Actor, sharedActor)
    90  
    91  	v2AppActor := v2action.NewActor(ccClientV2, uaaClientV2, config)
    92  	cmd.NOAAClient = shared.NewNOAAClient(ccClient.APIInfo.Logging(), config, uaaClient, ui)
    93  
    94  	cmd.AppSummaryDisplayer = shared.AppSummaryDisplayer{
    95  		UI:              cmd.UI,
    96  		Config:          cmd.Config,
    97  		Actor:           cmd.Actor,
    98  		V2AppRouteActor: v2AppActor,
    99  		AppName:         cmd.RequiredArgs.AppName,
   100  	}
   101  	cmd.PackageDisplayer = shared.NewPackageDisplayer(cmd.UI, cmd.Config)
   102  
   103  	return nil
   104  }
   105  
   106  func (cmd V3PushCommand) Execute(args []string) error {
   107  	cmd.UI.DisplayText(command.ExperimentalWarning)
   108  	cmd.UI.DisplayNewline()
   109  
   110  	err := cmd.validateArgs()
   111  	if err != nil {
   112  		return err
   113  	}
   114  
   115  	err = command.MinimumAPIVersionCheck(cmd.Actor.CloudControllerAPIVersion(), ccversion.MinVersionV3)
   116  	if err != nil {
   117  		return err
   118  	}
   119  
   120  	err = cmd.SharedActor.CheckTarget(cmd.Config, true, true)
   121  	if err != nil {
   122  		return shared.HandleError(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.(v3action.ApplicationNotFoundError); ok {
   137  		app, err = cmd.createApplication(user.Name)
   138  		if err != nil {
   139  			return shared.HandleError(err)
   140  		}
   141  	} else if err != nil {
   142  		return shared.HandleError(err)
   143  	} else {
   144  		app, err = cmd.updateApplication(user.Name, app.GUID)
   145  		if err != nil {
   146  			return shared.HandleError(err)
   147  		}
   148  	}
   149  
   150  	pkg, err := cmd.uploadPackage()
   151  	if err != nil {
   152  		return shared.HandleError(err)
   153  	}
   154  
   155  	dropletGUID, err := cmd.stagePackage(pkg, user.Name)
   156  	if err != nil {
   157  		return shared.HandleError(err)
   158  	}
   159  
   160  	if app.Started() {
   161  		err = cmd.stopApplication(app.GUID, user.Name)
   162  		if err != nil {
   163  			return shared.HandleError(err)
   164  		}
   165  	}
   166  
   167  	err = cmd.setApplicationDroplet(dropletGUID, user.Name)
   168  	if err != nil {
   169  		return shared.HandleError(err)
   170  	}
   171  
   172  	if !cmd.NoRoute {
   173  		err = cmd.createAndBindRoutes(app)
   174  		if err != nil {
   175  			return shared.HandleError(err)
   176  		}
   177  	}
   178  
   179  	err = cmd.startApplication(app.GUID, user.Name)
   180  	if err != nil {
   181  		return shared.HandleError(err)
   182  	}
   183  
   184  	cmd.UI.DisplayText("Waiting for app to start...")
   185  
   186  	warnings := make(chan v3action.Warnings)
   187  	done := make(chan bool)
   188  	go func() {
   189  		for {
   190  			select {
   191  			case message := <-warnings:
   192  				cmd.UI.DisplayWarnings(message)
   193  			case <-done:
   194  				return
   195  			}
   196  		}
   197  	}()
   198  
   199  	err = cmd.Actor.PollStart(app.GUID, warnings)
   200  	done <- true
   201  
   202  	if err != nil {
   203  		if _, ok := err.(v3action.StartupTimeoutError); ok {
   204  			return translatableerror.StartupTimeoutError{
   205  				AppName:    cmd.RequiredArgs.AppName,
   206  				BinaryName: cmd.Config.BinaryName(),
   207  			}
   208  		}
   209  
   210  		return shared.HandleError(err)
   211  	}
   212  
   213  	cmd.UI.DisplayTextWithFlavor("Showing health and status for app {{.AppName}} in org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}...", map[string]interface{}{
   214  		"AppName":   cmd.RequiredArgs.AppName,
   215  		"OrgName":   cmd.Config.TargetedOrganization().Name,
   216  		"SpaceName": cmd.Config.TargetedSpace().Name,
   217  		"Username":  user.Name,
   218  	})
   219  	cmd.UI.DisplayNewline()
   220  
   221  	return cmd.AppSummaryDisplayer.DisplayAppInfo()
   222  }
   223  
   224  func (cmd V3PushCommand) validateArgs() error {
   225  	switch {
   226  	case cmd.DockerImage.Path != "" && cmd.AppPath != "":
   227  		return translatableerror.ArgumentCombinationError{
   228  			Args: []string{"--docker-image", "-o", "-p"},
   229  		}
   230  	case cmd.DockerImage.Path != "" && len(cmd.Buildpacks) > 0:
   231  		return translatableerror.ArgumentCombinationError{
   232  			Args: []string{"-b", "--docker-image", "-o"},
   233  		}
   234  	case cmd.DockerUsername != "" && cmd.DockerImage.Path == "":
   235  		return translatableerror.RequiredFlagsError{
   236  			Arg1: "--docker-image, -o", Arg2: "--docker-username",
   237  		}
   238  	case cmd.DockerUsername != "" && cmd.Config.DockerPassword() == "":
   239  		return translatableerror.DockerPasswordNotSetError{}
   240  	}
   241  	return nil
   242  }
   243  
   244  func (cmd V3PushCommand) createApplication(userName string) (v3action.Application, error) {
   245  	appToCreate := v3action.Application{
   246  		Name: cmd.RequiredArgs.AppName,
   247  	}
   248  
   249  	if cmd.DockerImage.Path != "" {
   250  		appToCreate.Lifecycle = v3action.AppLifecycle{
   251  			Type: v3action.DockerAppLifecycleType,
   252  		}
   253  	} else {
   254  		appToCreate.Lifecycle = v3action.AppLifecycle{
   255  			Type: v3action.BuildpackAppLifecycleType,
   256  			Data: v3action.AppLifecycleData{
   257  				Buildpacks: cmd.Buildpacks,
   258  			},
   259  		}
   260  	}
   261  
   262  	app, warnings, err := cmd.Actor.CreateApplicationInSpace(
   263  		appToCreate,
   264  		cmd.Config.TargetedSpace().GUID,
   265  	)
   266  	cmd.UI.DisplayWarnings(warnings)
   267  	if err != nil {
   268  		return v3action.Application{}, err
   269  	}
   270  
   271  	cmd.UI.DisplayTextWithFlavor("Creating app {{.AppName}} in org {{.CurrentOrg}} / space {{.CurrentSpace}} as {{.CurrentUser}}...", map[string]interface{}{
   272  		"AppName":      cmd.RequiredArgs.AppName,
   273  		"CurrentSpace": cmd.Config.TargetedSpace().Name,
   274  		"CurrentOrg":   cmd.Config.TargetedOrganization().Name,
   275  		"CurrentUser":  userName,
   276  	})
   277  
   278  	cmd.UI.DisplayOK()
   279  	cmd.UI.DisplayNewline()
   280  	return app, nil
   281  }
   282  
   283  func (cmd V3PushCommand) getApplication() (v3action.Application, error) {
   284  	app, warnings, err := cmd.Actor.GetApplicationByNameAndSpace(cmd.RequiredArgs.AppName, cmd.Config.TargetedSpace().GUID)
   285  	cmd.UI.DisplayWarnings(warnings)
   286  	if err != nil {
   287  		return v3action.Application{}, err
   288  	}
   289  
   290  	return app, nil
   291  }
   292  
   293  func (cmd V3PushCommand) updateApplication(userName string, appGUID string) (v3action.Application, error) {
   294  	cmd.UI.DisplayTextWithFlavor("Updating app {{.AppName}} in org {{.CurrentOrg}} / space {{.CurrentSpace}} as {{.CurrentUser}}...", map[string]interface{}{
   295  		"AppName":      cmd.RequiredArgs.AppName,
   296  		"CurrentSpace": cmd.Config.TargetedSpace().Name,
   297  		"CurrentOrg":   cmd.Config.TargetedOrganization().Name,
   298  		"CurrentUser":  userName,
   299  	})
   300  
   301  	appToUpdate := v3action.Application{
   302  		GUID: appGUID,
   303  	}
   304  
   305  	if cmd.DockerImage.Path != "" {
   306  		appToUpdate.Lifecycle = v3action.AppLifecycle{
   307  			Type: v3action.DockerAppLifecycleType,
   308  		}
   309  	} else {
   310  		appToUpdate.Lifecycle = v3action.AppLifecycle{
   311  			Type: v3action.BuildpackAppLifecycleType,
   312  			Data: v3action.AppLifecycleData{
   313  				Buildpacks: cmd.Buildpacks,
   314  			},
   315  		}
   316  	}
   317  
   318  	app, warnings, err := cmd.Actor.UpdateApplication(appToUpdate)
   319  	cmd.UI.DisplayWarnings(warnings)
   320  	if err != nil {
   321  		return v3action.Application{}, err
   322  	}
   323  
   324  	cmd.UI.DisplayOK()
   325  	cmd.UI.DisplayNewline()
   326  	return app, nil
   327  }
   328  
   329  func (cmd V3PushCommand) createAndBindRoutes(app v3action.Application) error {
   330  	cmd.UI.DisplayText("Mapping routes...")
   331  	routeWarnings, err := cmd.V2PushActor.CreateAndBindApplicationRoutes(cmd.Config.TargetedOrganization().GUID, cmd.Config.TargetedSpace().GUID, v2action.Application{Name: app.Name, GUID: app.GUID})
   332  	cmd.UI.DisplayWarnings(routeWarnings)
   333  	if err != nil {
   334  		return err
   335  	}
   336  
   337  	cmd.UI.DisplayOK()
   338  	cmd.UI.DisplayNewline()
   339  	return nil
   340  }
   341  
   342  func (cmd V3PushCommand) uploadPackage() (v3action.Package, error) {
   343  	isDockerImage := (cmd.DockerImage.Path != "")
   344  	err := cmd.PackageDisplayer.DisplaySetupMessage(cmd.RequiredArgs.AppName, isDockerImage)
   345  	if err != nil {
   346  		return v3action.Package{}, err
   347  	}
   348  
   349  	var (
   350  		pkg      v3action.Package
   351  		warnings v3action.Warnings
   352  	)
   353  
   354  	if isDockerImage {
   355  		pkg, warnings, err = cmd.Actor.CreateDockerPackageByApplicationNameAndSpace(cmd.RequiredArgs.AppName, cmd.Config.TargetedSpace().GUID, v3action.DockerImageCredentials{Path: cmd.DockerImage.Path, Username: cmd.DockerUsername, Password: cmd.Config.DockerPassword()})
   356  	} else {
   357  		pkg, warnings, err = cmd.Actor.CreateAndUploadBitsPackageByApplicationNameAndSpace(cmd.RequiredArgs.AppName, cmd.Config.TargetedSpace().GUID, string(cmd.AppPath))
   358  	}
   359  
   360  	cmd.UI.DisplayWarnings(warnings)
   361  	if err != nil {
   362  		return v3action.Package{}, err
   363  	}
   364  
   365  	cmd.UI.DisplayOK()
   366  	cmd.UI.DisplayNewline()
   367  	return pkg, nil
   368  }
   369  
   370  func (cmd V3PushCommand) 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.Actor.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.Actor.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  	cmd.UI.DisplayNewline()
   392  	return droplet.GUID, nil
   393  }
   394  
   395  func (cmd V3PushCommand) setApplicationDroplet(dropletGUID string, userName string) error {
   396  	cmd.UI.DisplayTextWithFlavor("Setting app {{.AppName}} to droplet {{.DropletGUID}} in org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}...", map[string]interface{}{
   397  		"AppName":     cmd.RequiredArgs.AppName,
   398  		"DropletGUID": dropletGUID,
   399  		"OrgName":     cmd.Config.TargetedOrganization().Name,
   400  		"SpaceName":   cmd.Config.TargetedSpace().Name,
   401  		"Username":    userName,
   402  	})
   403  
   404  	warnings, err := cmd.Actor.SetApplicationDroplet(cmd.RequiredArgs.AppName, cmd.Config.TargetedSpace().GUID, dropletGUID)
   405  	cmd.UI.DisplayWarnings(warnings)
   406  	if err != nil {
   407  		return err
   408  	}
   409  
   410  	cmd.UI.DisplayOK()
   411  	cmd.UI.DisplayNewline()
   412  	return nil
   413  }
   414  
   415  func (cmd V3PushCommand) startApplication(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.Actor.StartApplication(appGUID)
   424  	cmd.UI.DisplayWarnings(warnings)
   425  	if err != nil {
   426  		return err
   427  	}
   428  	cmd.UI.DisplayOK()
   429  	cmd.UI.DisplayNewline()
   430  	return nil
   431  }
   432  
   433  func (cmd V3PushCommand) stopApplication(appGUID string, userName string) error {
   434  	cmd.UI.DisplayTextWithFlavor("Stopping app {{.AppName}} in org {{.CurrentOrg}} / space {{.CurrentSpace}} as {{.CurrentUser}}...", map[string]interface{}{
   435  		"AppName":      cmd.RequiredArgs.AppName,
   436  		"CurrentSpace": cmd.Config.TargetedSpace().Name,
   437  		"CurrentOrg":   cmd.Config.TargetedOrganization().Name,
   438  		"CurrentUser":  userName,
   439  	})
   440  
   441  	warnings, err := cmd.Actor.StopApplication(appGUID)
   442  	cmd.UI.DisplayWarnings(warnings)
   443  	if err != nil {
   444  		return err
   445  	}
   446  	cmd.UI.DisplayOK()
   447  	cmd.UI.DisplayNewline()
   448  	return nil
   449  }
   450  
   451  func verifyBuildpacks(buildpacks []string) bool {
   452  	if len(buildpacks) < 2 {
   453  		return true
   454  	}
   455  
   456  	for _, buildpack := range buildpacks {
   457  		if buildpack == "default" || buildpack == "null" {
   458  			return false
   459  		}
   460  	}
   461  	return true
   462  }