github.com/cloudfoundry-community/cloudfoundry-cli@v6.44.1-0.20240130060226-cda5ed8e89a5+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.BuildpackAlreadyExistsWithoutStackError); ok {
    82  		return Buildpack{}, Warnings(warnings), actionerror.BuildpackAlreadyExistsWithoutStackError{BuildpackName: name}
    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  		oldBp           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  		oldBp = foundBuildpacks[0]
   200  	} else {
   201  		if stackName == "" {
   202  			for _, bp := range foundBuildpacks {
   203  				if bp.NoStack() {
   204  					oldBp = bp
   205  					found = true
   206  					break
   207  				}
   208  			}
   209  		}
   210  
   211  		if !found {
   212  			return allWarnings, actionerror.MultipleBuildpacksFoundError{BuildpackName: oldName}
   213  		}
   214  	}
   215  
   216  	if err != nil {
   217  		return Warnings(allWarnings), err
   218  	}
   219  
   220  	oldBp.Name = newName
   221  
   222  	_, updateWarnings, err := actor.UpdateBuildpack(oldBp)
   223  	allWarnings = append(allWarnings, updateWarnings...)
   224  	if err != nil {
   225  		return Warnings(allWarnings), err
   226  	}
   227  
   228  	return Warnings(allWarnings), nil
   229  }
   230  
   231  func (actor *Actor) UpdateBuildpack(buildpack Buildpack) (Buildpack, Warnings, error) {
   232  	updatedBuildpack, warnings, err := actor.CloudControllerClient.UpdateBuildpack(ccv2.Buildpack(buildpack))
   233  	if err != nil {
   234  		switch err.(type) {
   235  		case ccerror.ResourceNotFoundError:
   236  			return Buildpack{}, Warnings(warnings), actionerror.BuildpackNotFoundError{BuildpackName: buildpack.Name}
   237  		case ccerror.BuildpackAlreadyExistsWithoutStackError:
   238  			return Buildpack{}, Warnings(warnings), actionerror.BuildpackAlreadyExistsWithoutStackError{BuildpackName: buildpack.Name}
   239  		case ccerror.BuildpackAlreadyExistsForStackError:
   240  			return Buildpack{}, Warnings(warnings), actionerror.BuildpackAlreadyExistsForStackError{Message: err.Error()}
   241  		default:
   242  			return Buildpack{}, Warnings(warnings), err
   243  		}
   244  	}
   245  
   246  	return Buildpack(updatedBuildpack), Warnings(warnings), nil
   247  }
   248  
   249  func (actor *Actor) UpdateBuildpackByNameAndStack(name, currentStack string, position types.NullInt, locked types.NullBool, enabled types.NullBool, newStack string) (string, Warnings, error) {
   250  	warnings := Warnings{}
   251  	var (
   252  		buildpack    Buildpack
   253  		execWarnings Warnings
   254  		err          error
   255  	)
   256  
   257  	execWarnings, err = actor.checkIfNewStackExists(newStack)
   258  	warnings = append(warnings, execWarnings...)
   259  
   260  	if err != nil {
   261  		return "", warnings, err
   262  	}
   263  
   264  	var buildpacks []Buildpack
   265  
   266  	buildpacks, execWarnings, err = actor.getBuildpacks(name, currentStack)
   267  
   268  	warnings = append(warnings, execWarnings...)
   269  	if err != nil {
   270  		return "", warnings, err
   271  	}
   272  
   273  	allBuildpacksHaveStacks := true
   274  	for _, buildpack := range buildpacks {
   275  		if buildpack.NoStack() {
   276  			allBuildpacksHaveStacks = false
   277  		}
   278  	}
   279  	if allBuildpacksHaveStacks && len(newStack) > 0 {
   280  		return "", warnings, actionerror.BuildpackStackChangeError{
   281  			BuildpackName: buildpacks[0].Name,
   282  			BinaryName:    actor.Config.BinaryName(),
   283  		}
   284  	} else if allBuildpacksHaveStacks && len(buildpacks) > 1 {
   285  		return "", Warnings(warnings), actionerror.MultipleBuildpacksFoundError{BuildpackName: name}
   286  	}
   287  
   288  	buildpack = buildpacks[0]
   289  	if len(buildpacks) > 1 && newStack != "" {
   290  		for _, b := range buildpacks {
   291  			if b.NoStack() {
   292  				buildpack = b
   293  			}
   294  		}
   295  	}
   296  
   297  	if position != buildpack.Position || locked != buildpack.Enabled || enabled != buildpack.Enabled || newStack != buildpack.Stack {
   298  		buildpack.Position = position
   299  		buildpack.Locked = locked
   300  		buildpack.Enabled = enabled
   301  		buildpack.Stack = newStack
   302  
   303  		_, execWarnings, err = actor.UpdateBuildpack(buildpack)
   304  		warnings = append(warnings, execWarnings...)
   305  
   306  		if err != nil {
   307  			return "", warnings, err
   308  		}
   309  	}
   310  
   311  	return buildpack.GUID, warnings, err
   312  }
   313  
   314  func (actor *Actor) checkIfNewStackExists(newStack string) (Warnings, error) {
   315  	if len(newStack) > 0 {
   316  		_, execWarnings, err := actor.GetStackByName(newStack)
   317  		if err != nil {
   318  			return execWarnings, err
   319  		}
   320  	}
   321  	return nil, nil
   322  }
   323  
   324  func (actor *Actor) UploadBuildpack(GUID string, pathToBuildpackBits string, progBar SimpleProgressBar) (Warnings, error) {
   325  	progressBarReader, size, err := progBar.Initialize(pathToBuildpackBits)
   326  	if err != nil {
   327  		return Warnings{}, err
   328  	}
   329  
   330  	warnings, err := actor.CloudControllerClient.UploadBuildpack(GUID, pathToBuildpackBits, progressBarReader, size)
   331  	if err != nil {
   332  		if e, ok := err.(ccerror.BuildpackAlreadyExistsForStackError); ok {
   333  			return Warnings(warnings), actionerror.BuildpackAlreadyExistsForStackError{Message: e.Message}
   334  		}
   335  		return Warnings(warnings), err
   336  	}
   337  
   338  	progBar.Terminate()
   339  	return Warnings(warnings), nil
   340  }
   341  
   342  func (actor *Actor) UploadBuildpackFromPath(inputPath, buildpackGUID string, progressBar SimpleProgressBar) (Warnings, error) {
   343  	downloader := download.NewDownloader(time.Second * 30)
   344  	tmpDirPath, err := ioutil.TempDir("", "buildpack-dir-")
   345  	if err != nil {
   346  		return Warnings{}, err
   347  	}
   348  	defer os.RemoveAll(tmpDirPath)
   349  
   350  	pathToBuildpackBits, err := actor.PrepareBuildpackBits(inputPath, tmpDirPath, downloader)
   351  	if err != nil {
   352  		return Warnings{}, err
   353  	}
   354  
   355  	return actor.UploadBuildpack(buildpackGUID, pathToBuildpackBits, progressBar)
   356  }
   357  
   358  // Zipit zips the source into a .zip file in the target dir
   359  func Zipit(source, target, prefix string) error {
   360  	// Thanks to Svett Ralchev
   361  	// http://blog.ralch.com/tutorial/golang-working-with-zip/
   362  
   363  	zipfile, err := os.Create(target)
   364  	if err != nil {
   365  		return err
   366  	}
   367  	defer zipfile.Close()
   368  
   369  	if prefix != "" {
   370  		_, err = io.WriteString(zipfile, prefix)
   371  		if err != nil {
   372  			return err
   373  		}
   374  	}
   375  
   376  	archive := zip.NewWriter(zipfile)
   377  	defer archive.Close()
   378  
   379  	err = filepath.Walk(source, func(path string, info os.FileInfo, err error) error {
   380  		if err != nil {
   381  			return err
   382  		}
   383  
   384  		if path == source {
   385  			return nil
   386  		}
   387  
   388  		header, err := zip.FileInfoHeader(info)
   389  		if err != nil {
   390  			return err
   391  		}
   392  		header.Name, err = filepath.Rel(source, path)
   393  		if err != nil {
   394  			return err
   395  		}
   396  
   397  		header.Name = filepath.ToSlash(header.Name)
   398  		if info.IsDir() {
   399  			header.Name += "/"
   400  			header.SetMode(info.Mode())
   401  		} else {
   402  			header.Method = zip.Deflate
   403  			header.SetMode(fixMode(info.Mode()))
   404  		}
   405  
   406  		writer, err := archive.CreateHeader(header)
   407  		if err != nil {
   408  			return err
   409  		}
   410  
   411  		if info.IsDir() {
   412  			return nil
   413  		}
   414  
   415  		file, err := os.Open(path)
   416  		if err != nil {
   417  			return err
   418  		}
   419  		defer file.Close()
   420  
   421  		_, err = io.Copy(writer, file)
   422  		return err
   423  	})
   424  
   425  	return err
   426  }