code.cloudfoundry.org/cli@v7.1.0+incompatible/actor/v2action/buildpack.go (about)

     1  package v2action
     2  
     3  import (
     4  	"archive/zip"
     5  	"io"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  	"time"
    10  
    11  	"code.cloudfoundry.org/cli/actor/actionerror"
    12  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccerror"
    13  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv2"
    14  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv2/constant"
    15  	"code.cloudfoundry.org/cli/types"
    16  	"code.cloudfoundry.org/cli/util"
    17  	"code.cloudfoundry.org/cli/util/download"
    18  
    19  	"gopkg.in/cheggaaa/pb.v1"
    20  )
    21  
    22  type Buildpack ccv2.Buildpack
    23  
    24  func (buildpack Buildpack) NoStack() bool {
    25  	return len(buildpack.Stack) == 0
    26  }
    27  
    28  //go:generate counterfeiter . Downloader
    29  
    30  type Downloader interface {
    31  	Download(url string, tmpDirPath string) (string, error)
    32  }
    33  
    34  //go:generate counterfeiter . SimpleProgressBar
    35  
    36  type SimpleProgressBar interface {
    37  	Initialize(path string) (io.Reader, int64, error)
    38  	Terminate()
    39  }
    40  
    41  type ProgressBar struct {
    42  	bar *pb.ProgressBar
    43  }
    44  
    45  func NewProgressBar() *ProgressBar {
    46  	return &ProgressBar{}
    47  }
    48  
    49  func (p *ProgressBar) Initialize(path string) (io.Reader, int64, error) {
    50  	file, err := os.Open(path)
    51  	if err != nil {
    52  		return nil, 0, err
    53  	}
    54  
    55  	fileInfo, err := file.Stat()
    56  	if err != nil {
    57  		return nil, 0, err
    58  	}
    59  
    60  	p.bar = pb.New(int(fileInfo.Size())).SetUnits(pb.U_BYTES)
    61  	p.bar.ShowTimeLeft = false
    62  	p.bar.Start()
    63  	return p.bar.NewProxyReader(file), fileInfo.Size(), nil
    64  
    65  }
    66  
    67  func (p *ProgressBar) Terminate() {
    68  	// Adding sleep to ensure UI has finished drawing
    69  	time.Sleep(time.Second)
    70  	p.bar.Finish()
    71  }
    72  
    73  func (actor *Actor) CreateBuildpack(name string, position int, enabled bool) (Buildpack, Warnings, error) {
    74  	buildpack := ccv2.Buildpack{
    75  		Name:     name,
    76  		Position: types.NullInt{IsSet: true, Value: position},
    77  		Enabled:  types.NullBool{IsSet: true, Value: enabled},
    78  	}
    79  
    80  	ccBuildpack, warnings, err := actor.CloudControllerClient.CreateBuildpack(buildpack)
    81  	if _, ok := err.(ccerror.BuildpackInvalidError); ok {
    82  		return Buildpack{}, Warnings(warnings), actionerror.BuildpackInvalidError{Message: err.Error()}
    83  	}
    84  
    85  	if _, ok := err.(ccerror.BuildpackNameTakenError); ok {
    86  		return Buildpack{}, Warnings(warnings), actionerror.BuildpackNameTakenError{Name: name}
    87  	}
    88  
    89  	return Buildpack{GUID: ccBuildpack.GUID}, Warnings(warnings), err
    90  }
    91  
    92  func (actor *Actor) getBuildpacks(name string, stack string) ([]Buildpack, Warnings, error) {
    93  	var filters []ccv2.Filter
    94  
    95  	bpName := ccv2.Filter{
    96  		Type:     constant.NameFilter,
    97  		Operator: constant.EqualOperator,
    98  		Values:   []string{name},
    99  	}
   100  	filters = append(filters, bpName)
   101  
   102  	if len(stack) > 0 {
   103  		stackFilter := ccv2.Filter{
   104  			Type:     constant.StackFilter,
   105  			Operator: constant.EqualOperator,
   106  			Values:   []string{stack},
   107  		}
   108  		filters = append(filters, stackFilter)
   109  	}
   110  
   111  	ccv2Buildpacks, warnings, err := actor.CloudControllerClient.GetBuildpacks(filters...)
   112  	if err != nil {
   113  		return nil, Warnings(warnings), err
   114  	}
   115  
   116  	var buildpacks []Buildpack
   117  	for _, buildpack := range ccv2Buildpacks {
   118  		buildpacks = append(buildpacks, Buildpack(buildpack))
   119  	}
   120  
   121  	if len(buildpacks) == 0 {
   122  		return nil, Warnings(warnings), actionerror.BuildpackNotFoundError{BuildpackName: name, StackName: stack}
   123  	}
   124  
   125  	return buildpacks, Warnings(warnings), nil
   126  }
   127  
   128  func (actor *Actor) PrepareBuildpackBits(inputPath string, tmpDirPath string, downloader Downloader) (string, error) {
   129  	if util.IsHTTPScheme(inputPath) {
   130  		pathToDownloadedBits, err := downloader.Download(inputPath, tmpDirPath)
   131  		if err != nil {
   132  			return "", err
   133  		}
   134  		return pathToDownloadedBits, nil
   135  	}
   136  
   137  	if filepath.Ext(inputPath) == ".zip" {
   138  		return inputPath, nil
   139  	}
   140  
   141  	info, err := os.Stat(inputPath)
   142  	if err != nil {
   143  		return "", err
   144  	}
   145  
   146  	if info.IsDir() {
   147  		var empty bool
   148  		empty, err = isEmptyDirectory(inputPath)
   149  		if err != nil {
   150  			return "", err
   151  		}
   152  		if empty {
   153  			return "", actionerror.EmptyBuildpackDirectoryError{Path: inputPath}
   154  		}
   155  		archive := filepath.Join(tmpDirPath, filepath.Base(inputPath)) + ".zip"
   156  
   157  		err = Zipit(inputPath, archive, "")
   158  		if err != nil {
   159  			return "", err
   160  		}
   161  		return archive, nil
   162  	}
   163  
   164  	return inputPath, nil
   165  }
   166  
   167  func isEmptyDirectory(name string) (bool, error) {
   168  	f, err := os.Open(name)
   169  	if err != nil {
   170  		return false, err
   171  	}
   172  	defer f.Close()
   173  
   174  	_, err = f.Readdirnames(1)
   175  	if err == io.EOF {
   176  		return true, nil
   177  	}
   178  	return false, err
   179  }
   180  
   181  func (actor *Actor) RenameBuildpack(oldName string, newName string, stackName string) (Warnings, error) {
   182  	var (
   183  		getWarnings Warnings
   184  		allWarnings Warnings
   185  
   186  		foundBuildpacks []Buildpack
   187  		oldBuildpack    Buildpack
   188  		err             error
   189  		found           bool
   190  	)
   191  
   192  	foundBuildpacks, getWarnings, err = actor.getBuildpacks(oldName, stackName)
   193  	allWarnings = append(allWarnings, getWarnings...)
   194  	if err != nil {
   195  		return allWarnings, err
   196  	}
   197  
   198  	if len(foundBuildpacks) == 1 {
   199  		oldBuildpack = foundBuildpacks[0]
   200  	} else {
   201  		if stackName == "" {
   202  			for _, buildpack := range foundBuildpacks {
   203  				if buildpack.NoStack() {
   204  					oldBuildpack = buildpack
   205  					found = true
   206  					break
   207  				}
   208  			}
   209  		}
   210  
   211  		if !found {
   212  			return allWarnings, actionerror.MultipleBuildpacksFoundError{BuildpackName: oldName}
   213  		}
   214  	}
   215  	oldBuildpack.Name = newName
   216  
   217  	_, updateWarnings, err := actor.UpdateBuildpack(oldBuildpack)
   218  	allWarnings = append(allWarnings, updateWarnings...)
   219  	if err != nil {
   220  		return Warnings(allWarnings), err
   221  	}
   222  
   223  	return Warnings(allWarnings), nil
   224  }
   225  
   226  func (actor *Actor) UpdateBuildpack(buildpack Buildpack) (Buildpack, Warnings, error) {
   227  	updatedBuildpack, warnings, err := actor.CloudControllerClient.UpdateBuildpack(ccv2.Buildpack(buildpack))
   228  	if err != nil {
   229  		switch err.(type) {
   230  		case ccerror.ResourceNotFoundError:
   231  			return Buildpack{}, Warnings(warnings), actionerror.BuildpackNotFoundError{BuildpackName: buildpack.Name}
   232  		case ccerror.BuildpackInvalidError:
   233  			return Buildpack{}, Warnings(warnings), actionerror.BuildpackInvalidError{Message: err.Error()}
   234  		case ccerror.BuildpackAlreadyExistsForStackError:
   235  			return Buildpack{}, Warnings(warnings), actionerror.BuildpackAlreadyExistsForStackError{Message: err.Error()}
   236  		default:
   237  			return Buildpack{}, Warnings(warnings), err
   238  		}
   239  	}
   240  
   241  	return Buildpack(updatedBuildpack), Warnings(warnings), nil
   242  }
   243  
   244  func (actor *Actor) UpdateBuildpackByNameAndStack(name, currentStack string, position types.NullInt, locked types.NullBool, enabled types.NullBool, newStack string) (string, Warnings, error) {
   245  	warnings := Warnings{}
   246  	var (
   247  		buildpack    Buildpack
   248  		execWarnings Warnings
   249  		err          error
   250  	)
   251  
   252  	execWarnings, err = actor.checkIfNewStackExists(newStack)
   253  	warnings = append(warnings, execWarnings...)
   254  
   255  	if err != nil {
   256  		return "", warnings, err
   257  	}
   258  
   259  	var buildpacks []Buildpack
   260  
   261  	buildpacks, execWarnings, err = actor.getBuildpacks(name, currentStack)
   262  
   263  	warnings = append(warnings, execWarnings...)
   264  	if err != nil {
   265  		return "", warnings, err
   266  	}
   267  
   268  	allBuildpacksHaveStacks := true
   269  	for _, buildpack := range buildpacks {
   270  		if buildpack.NoStack() {
   271  			allBuildpacksHaveStacks = false
   272  		}
   273  	}
   274  	if allBuildpacksHaveStacks && len(newStack) > 0 {
   275  		return "", warnings, actionerror.BuildpackStackChangeError{
   276  			BuildpackName: buildpacks[0].Name,
   277  			BinaryName:    actor.Config.BinaryName(),
   278  		}
   279  	} else if allBuildpacksHaveStacks && len(buildpacks) > 1 {
   280  		return "", Warnings(warnings), actionerror.MultipleBuildpacksFoundError{BuildpackName: name}
   281  	}
   282  
   283  	buildpack = buildpacks[0]
   284  	if len(buildpacks) > 1 && newStack != "" {
   285  		for _, b := range buildpacks {
   286  			if b.NoStack() {
   287  				buildpack = b
   288  			}
   289  		}
   290  	}
   291  
   292  	if position != buildpack.Position || locked != buildpack.Enabled || enabled != buildpack.Enabled || newStack != buildpack.Stack {
   293  		buildpack.Position = position
   294  		buildpack.Locked = locked
   295  		buildpack.Enabled = enabled
   296  		buildpack.Stack = newStack
   297  
   298  		_, execWarnings, err = actor.UpdateBuildpack(buildpack)
   299  		warnings = append(warnings, execWarnings...)
   300  
   301  		if err != nil {
   302  			return "", warnings, err
   303  		}
   304  	}
   305  
   306  	return buildpack.GUID, warnings, err
   307  }
   308  
   309  func (actor *Actor) checkIfNewStackExists(newStack string) (Warnings, error) {
   310  	if len(newStack) > 0 {
   311  		_, execWarnings, err := actor.GetStackByName(newStack)
   312  		if err != nil {
   313  			return execWarnings, err
   314  		}
   315  	}
   316  	return nil, nil
   317  }
   318  
   319  func (actor *Actor) UploadBuildpack(GUID string, pathToBuildpackBits string, progBar SimpleProgressBar) (Warnings, error) {
   320  	progressBarReader, size, err := progBar.Initialize(pathToBuildpackBits)
   321  	if err != nil {
   322  		return Warnings{}, err
   323  	}
   324  	defer progBar.Terminate()
   325  
   326  	warnings, err := actor.CloudControllerClient.UploadBuildpack(GUID, pathToBuildpackBits, progressBarReader, size)
   327  	if err != nil {
   328  		if e, ok := err.(ccerror.BuildpackAlreadyExistsForStackError); ok {
   329  			return Warnings(warnings), actionerror.BuildpackAlreadyExistsForStackError{Message: e.Message}
   330  		}
   331  	}
   332  	return Warnings(warnings), err
   333  }
   334  
   335  func (actor *Actor) UploadBuildpackFromPath(inputPath, buildpackGUID string, progressBar SimpleProgressBar) (Warnings, error) {
   336  	downloader := download.NewDownloader(time.Second * 30)
   337  	tmpDirPath, err := ioutil.TempDir("", "buildpack-dir-")
   338  	if err != nil {
   339  		return Warnings{}, err
   340  	}
   341  	defer os.RemoveAll(tmpDirPath)
   342  
   343  	pathToBuildpackBits, err := actor.PrepareBuildpackBits(inputPath, tmpDirPath, downloader)
   344  	if err != nil {
   345  		return Warnings{}, err
   346  	}
   347  
   348  	return actor.UploadBuildpack(buildpackGUID, pathToBuildpackBits, progressBar)
   349  }
   350  
   351  // Zipit zips the source into a .zip file in the target dir
   352  func Zipit(source, target, prefix string) error {
   353  	// Thanks to Svett Ralchev
   354  	// http://blog.ralch.com/tutorial/golang-working-with-zip/
   355  
   356  	zipfile, err := os.Create(target)
   357  	if err != nil {
   358  		return err
   359  	}
   360  	defer zipfile.Close()
   361  
   362  	if prefix != "" {
   363  		_, err = io.WriteString(zipfile, prefix)
   364  		if err != nil {
   365  			return err
   366  		}
   367  	}
   368  
   369  	archive := zip.NewWriter(zipfile)
   370  	defer archive.Close()
   371  
   372  	err = filepath.Walk(source, func(path string, info os.FileInfo, err error) error {
   373  		if err != nil {
   374  			return err
   375  		}
   376  
   377  		if path == source {
   378  			return nil
   379  		}
   380  
   381  		header, err := zip.FileInfoHeader(info)
   382  		if err != nil {
   383  			return err
   384  		}
   385  		header.Name, err = filepath.Rel(source, path)
   386  		if err != nil {
   387  			return err
   388  		}
   389  
   390  		header.Name = filepath.ToSlash(header.Name)
   391  		if info.IsDir() {
   392  			header.Name += "/"
   393  			header.SetMode(info.Mode())
   394  		} else {
   395  			header.Method = zip.Deflate
   396  			header.SetMode(fixMode(info.Mode()))
   397  		}
   398  
   399  		writer, err := archive.CreateHeader(header)
   400  		if err != nil {
   401  			return err
   402  		}
   403  
   404  		if info.IsDir() {
   405  			return nil
   406  		}
   407  
   408  		file, err := os.Open(path)
   409  		if err != nil {
   410  			return err
   411  		}
   412  		defer file.Close()
   413  
   414  		_, err = io.Copy(writer, file)
   415  		return err
   416  	})
   417  
   418  	return err
   419  }