github.com/randomtask1155/cli@v6.41.1-0.20181227003417-a98eed78cbde+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 position != buildpack.Position || locked != buildpack.Enabled || enabled != buildpack.Enabled || newStack != buildpack.Stack {
   290  		buildpack.Position = position
   291  		buildpack.Locked = locked
   292  		buildpack.Enabled = enabled
   293  		buildpack.Stack = newStack
   294  
   295  		_, execWarnings, err = actor.UpdateBuildpack(buildpack)
   296  		warnings = append(warnings, execWarnings...)
   297  
   298  		if err != nil {
   299  			return "", warnings, err
   300  		}
   301  	}
   302  
   303  	return buildpack.GUID, warnings, err
   304  }
   305  
   306  func (actor *Actor) checkIfNewStackExists(newStack string) (Warnings, error) {
   307  	if len(newStack) > 0 {
   308  		_, execWarnings, err := actor.GetStackByName(newStack)
   309  		if err != nil {
   310  			return execWarnings, err
   311  		}
   312  	}
   313  	return nil, nil
   314  }
   315  
   316  func (actor *Actor) UploadBuildpack(GUID string, pathToBuildpackBits string, progBar SimpleProgressBar) (Warnings, error) {
   317  	progressBarReader, size, err := progBar.Initialize(pathToBuildpackBits)
   318  	if err != nil {
   319  		return Warnings{}, err
   320  	}
   321  
   322  	warnings, err := actor.CloudControllerClient.UploadBuildpack(GUID, pathToBuildpackBits, progressBarReader, size)
   323  	if err != nil {
   324  		if e, ok := err.(ccerror.BuildpackAlreadyExistsForStackError); ok {
   325  			return Warnings(warnings), actionerror.BuildpackAlreadyExistsForStackError{Message: e.Message}
   326  		}
   327  		return Warnings(warnings), err
   328  	}
   329  
   330  	progBar.Terminate()
   331  	return Warnings(warnings), nil
   332  }
   333  
   334  func (actor *Actor) UploadBuildpackFromPath(inputPath, buildpackGuid string, progressBar SimpleProgressBar) (Warnings, error) {
   335  	downloader := download.NewDownloader(time.Second * 30)
   336  	tmpDirPath, err := ioutil.TempDir("", "buildpack-dir-")
   337  	if err != nil {
   338  		return Warnings{}, err
   339  	}
   340  	defer os.RemoveAll(tmpDirPath)
   341  
   342  	pathToBuildpackBits, err := actor.PrepareBuildpackBits(inputPath, tmpDirPath, downloader)
   343  	if err != nil {
   344  		return Warnings{}, err
   345  	}
   346  
   347  	return actor.UploadBuildpack(buildpackGuid, pathToBuildpackBits, progressBar)
   348  }
   349  
   350  // Zipit zips the source into a .zip file in the target dir
   351  func Zipit(source, target, prefix string) error {
   352  	// Thanks to Svett Ralchev
   353  	// http://blog.ralch.com/tutorial/golang-working-with-zip/
   354  
   355  	zipfile, err := os.Create(target)
   356  	if err != nil {
   357  		return err
   358  	}
   359  	defer zipfile.Close()
   360  
   361  	if prefix != "" {
   362  		_, err = io.WriteString(zipfile, prefix)
   363  		if err != nil {
   364  			return err
   365  		}
   366  	}
   367  
   368  	archive := zip.NewWriter(zipfile)
   369  	defer archive.Close()
   370  
   371  	err = filepath.Walk(source, func(path string, info os.FileInfo, err error) error {
   372  		if err != nil {
   373  			return err
   374  		}
   375  
   376  		if path == source {
   377  			return nil
   378  		}
   379  
   380  		header, err := zip.FileInfoHeader(info)
   381  		if err != nil {
   382  			return err
   383  		}
   384  		header.Name, err = filepath.Rel(source, path)
   385  		if err != nil {
   386  			return err
   387  		}
   388  
   389  		header.Name = filepath.ToSlash(header.Name)
   390  		if info.IsDir() {
   391  			header.Name += "/"
   392  			header.SetMode(info.Mode())
   393  		} else {
   394  			header.Method = zip.Deflate
   395  			header.SetMode(fixMode(info.Mode()))
   396  		}
   397  
   398  		writer, err := archive.CreateHeader(header)
   399  		if err != nil {
   400  			return err
   401  		}
   402  
   403  		if info.IsDir() {
   404  			return nil
   405  		}
   406  
   407  		file, err := os.Open(path)
   408  		if err != nil {
   409  			return err
   410  		}
   411  		defer file.Close()
   412  
   413  		_, err = io.Copy(writer, file)
   414  		return err
   415  	})
   416  
   417  	return err
   418  }