github.com/mook-as/cf-cli@v7.0.0-beta.28.0.20200120190804-b91c115fae48+incompatible/command/v6/create_buildpack_command.go (about)

     1  package v6
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"os"
     7  	"strings"
     8  	"time"
     9  
    10  	"code.cloudfoundry.org/cli/actor/actionerror"
    11  	"code.cloudfoundry.org/cli/actor/sharedaction"
    12  	"code.cloudfoundry.org/cli/actor/v2action"
    13  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccerror"
    14  	"code.cloudfoundry.org/cli/command"
    15  	"code.cloudfoundry.org/cli/command/flag"
    16  	"code.cloudfoundry.org/cli/command/translatableerror"
    17  	"code.cloudfoundry.org/cli/command/v6/shared"
    18  	"code.cloudfoundry.org/cli/util/download"
    19  )
    20  
    21  //go:generate counterfeiter . Downloader
    22  
    23  type Downloader interface {
    24  	Download(string) (string, error)
    25  }
    26  
    27  //go:generate counterfeiter . CreateBuildpackActor
    28  
    29  type CreateBuildpackActor interface {
    30  	CreateBuildpack(name string, position int, enabled bool) (v2action.Buildpack, v2action.Warnings, error)
    31  	UploadBuildpack(GUID string, path string, progBar v2action.SimpleProgressBar) (v2action.Warnings, error)
    32  	PrepareBuildpackBits(inputPath string, tmpDirPath string, downloader v2action.Downloader) (string, error)
    33  }
    34  
    35  type CreateBuildpackCommand struct {
    36  	RequiredArgs    flag.CreateBuildpackArgs `positional-args:"yes"`
    37  	Disable         bool                     `long:"disable" description:"Disable the buildpack from being used for staging"`
    38  	Enable          bool                     `long:"enable" description:"Enable the buildpack to be used for staging"`
    39  	usage           interface{}              `usage:"CF_NAME create-buildpack BUILDPACK PATH POSITION [--enable|--disable]\n\nTIP:\n   Path should be a zip file, a url to a zip file, or a local directory. Position is a positive integer, sets priority, and is sorted from lowest to highest."`
    40  	relatedCommands interface{}              `related_commands:"buildpacks, push"`
    41  
    42  	UI          command.UI
    43  	Actor       CreateBuildpackActor
    44  	ProgressBar v2action.SimpleProgressBar
    45  	SharedActor command.SharedActor
    46  	Config      command.Config
    47  }
    48  
    49  func (cmd *CreateBuildpackCommand) Setup(config command.Config, ui command.UI) error {
    50  	cmd.UI = ui
    51  	cmd.Config = config
    52  	cmd.SharedActor = sharedaction.NewActor(config)
    53  
    54  	ccClient, uaaClient, err := shared.GetNewClientsAndConnectToCF(config, ui)
    55  	if err != nil {
    56  		return err
    57  	}
    58  	cmd.Actor = v2action.NewActor(ccClient, uaaClient, config)
    59  	cmd.ProgressBar = v2action.NewProgressBar()
    60  
    61  	return nil
    62  }
    63  
    64  func (cmd *CreateBuildpackCommand) Execute(args []string) error {
    65  	if cmd.Enable && cmd.Disable {
    66  		return translatableerror.ArgumentCombinationError{
    67  			Args: []string{"--enable", "--disable"},
    68  		}
    69  	}
    70  
    71  	err := cmd.SharedActor.CheckTarget(false, false)
    72  	if err != nil {
    73  		return err
    74  	}
    75  
    76  	user, err := cmd.Config.CurrentUser()
    77  	if err != nil {
    78  		return err
    79  	}
    80  
    81  	cmd.UI.DisplayTextWithFlavor("Creating buildpack {{.Buildpack}} as {{.Username}}...", map[string]interface{}{
    82  		"Buildpack": cmd.RequiredArgs.Buildpack,
    83  		"Username":  user.Name,
    84  	})
    85  
    86  	downloader := download.NewDownloader(time.Second * 30)
    87  	tmpDirPath, err := ioutil.TempDir("", "buildpack-dir-")
    88  	if err != nil {
    89  		return err
    90  	}
    91  	defer os.RemoveAll(tmpDirPath)
    92  
    93  	pathToBuildpackBits, err := cmd.Actor.PrepareBuildpackBits(string(cmd.RequiredArgs.Path), tmpDirPath, downloader)
    94  	if err != nil {
    95  		return err
    96  	}
    97  
    98  	buildpack, warnings, err := cmd.Actor.CreateBuildpack(cmd.RequiredArgs.Buildpack, cmd.RequiredArgs.Position, !cmd.Disable)
    99  	cmd.UI.DisplayWarnings(warnings)
   100  
   101  	if err != nil {
   102  		return cmd.displayIfKnownBuildpackError(err)
   103  	}
   104  
   105  	cmd.UI.DisplayOK()
   106  
   107  	cmd.UI.DisplayTextWithFlavor("Uploading buildpack {{.Buildpack}} as {{.Username}}...", map[string]interface{}{
   108  		"Buildpack": cmd.RequiredArgs.Buildpack,
   109  		"Username":  user.Name,
   110  	})
   111  
   112  	uploadWarnings, err := cmd.Actor.UploadBuildpack(buildpack.GUID, pathToBuildpackBits, cmd.ProgressBar)
   113  	if _, ok := err.(ccerror.InvalidAuthTokenError); ok {
   114  		cmd.UI.DisplayWarnings([]string{"Failed to upload buildpack due to auth token expiration, retrying..."})
   115  		uploadWarnings, err = cmd.Actor.UploadBuildpack(buildpack.GUID, pathToBuildpackBits, cmd.ProgressBar)
   116  	}
   117  	cmd.UI.DisplayWarnings(uploadWarnings)
   118  	if err != nil {
   119  		cmd.UI.DisplayNewline()
   120  		cmd.UI.DisplayNewline()
   121  		if _, ok := err.(actionerror.BuildpackAlreadyExistsForStackError); ok {
   122  			cmd.displayNameAndStackCollisionError(err)
   123  			return nil
   124  		} else if httpErr, ok := err.(download.RawHTTPStatusError); ok {
   125  			return translatableerror.HTTPStatusError{Status: httpErr.Status}
   126  		}
   127  		return err
   128  	}
   129  
   130  	cmd.UI.DisplayNewline()
   131  	cmd.UI.DisplayText("Done uploading")
   132  	cmd.UI.DisplayOK()
   133  
   134  	return nil
   135  }
   136  
   137  func (cmd CreateBuildpackCommand) displayNameAndStackCollisionError(err error) {
   138  	cmd.UI.DisplayWarning(err.Error())
   139  	cmd.UI.DisplayTextWithFlavor("TIP: use '{{.CfUpdateBuildpackCommand}}' to update this buildpack",
   140  		map[string]interface{}{
   141  			"CfUpdateBuildpackCommand": cmd.Config.BinaryName() + " update-buildpack",
   142  		})
   143  }
   144  
   145  func (cmd CreateBuildpackCommand) displayIfKnownBuildpackError(err error) error {
   146  	if _, ok := err.(actionerror.BuildpackInvalidError); ok {
   147  		if strings.Index(err.Error(), "stack unique") != -1 {
   148  			cmd.displayAlreadyExistingBuildpackWithoutStack(err)
   149  			return nil
   150  		} else if strings.Index(err.Error(), "can only contain alphanumeric characters") != -1 {
   151  			cmd.displayInvalidBuildpackName(err)
   152  			return nil
   153  		}
   154  	} else if _, ok := err.(actionerror.BuildpackNameTakenError); ok {
   155  		cmd.displayAlreadyExistingBuildpack(err)
   156  		return nil
   157  	}
   158  	return err
   159  }
   160  
   161  func (cmd CreateBuildpackCommand) displayInvalidBuildpackName(err error) {
   162  	cmd.UI.DisplayNewline()
   163  	cmd.UI.DisplayWarning(err.Error())
   164  }
   165  
   166  func (cmd CreateBuildpackCommand) displayAlreadyExistingBuildpackWithoutStack(err error) {
   167  	cmd.UI.DisplayNewline()
   168  	cmd.UI.DisplayWarning(fmt.Sprintf("Buildpack %s already exists without a stack", cmd.RequiredArgs.Buildpack))
   169  	cmd.UI.DisplayTextWithFlavor("TIP: use '{{.CfBuildpacksCommand}}' and '{{.CfDeleteBuildpackCommand}}' to delete buildpack {{.BuildpackName}} without a stack",
   170  		map[string]interface{}{
   171  			"CfBuildpacksCommand":      cmd.Config.BinaryName() + " buildpacks",
   172  			"CfDeleteBuildpackCommand": cmd.Config.BinaryName() + " delete-buildpack",
   173  			"BuildpackName":            cmd.RequiredArgs.Buildpack,
   174  		})
   175  }
   176  
   177  func (cmd CreateBuildpackCommand) displayAlreadyExistingBuildpack(err error) {
   178  	cmd.UI.DisplayNewline()
   179  	cmd.UI.DisplayWarning(err.Error())
   180  	cmd.UI.DisplayTextWithFlavor("TIP: use '{{.CfUpdateBuildpackCommand}}' to update this buildpack",
   181  		map[string]interface{}{
   182  			"CfUpdateBuildpackCommand": cmd.Config.BinaryName() + " update-buildpack",
   183  		})
   184  }