github.com/willmadison/cli@v6.40.1-0.20181018160101-29d5937903ff+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  	pb "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) UploadBuildpackFromPath(inputPath, buildpackGuid string, progressBar SimpleProgressBar) (Warnings, error) {
    93  	downloader := download.NewDownloader(time.Second * 30)
    94  	tmpDirPath, err := ioutil.TempDir("", "buildpack-dir-")
    95  	if err != nil {
    96  		return Warnings{}, err
    97  	}
    98  	defer os.RemoveAll(tmpDirPath)
    99  
   100  	pathToBuildpackBits, err := actor.PrepareBuildpackBits(inputPath, tmpDirPath, downloader)
   101  	if err != nil {
   102  		return Warnings{}, err
   103  	}
   104  
   105  	return actor.UploadBuildpack(buildpackGuid, pathToBuildpackBits, progressBar)
   106  }
   107  
   108  func (actor *Actor) PrepareBuildpackBits(inputPath string, tmpDirPath string, downloader Downloader) (string, error) {
   109  	if util.IsHTTPScheme(inputPath) {
   110  		pathToDownloadedBits, err := downloader.Download(inputPath, tmpDirPath)
   111  		if err != nil {
   112  			return "", err
   113  		}
   114  		return pathToDownloadedBits, nil
   115  	}
   116  
   117  	if filepath.Ext(inputPath) == ".zip" {
   118  		return inputPath, nil
   119  	}
   120  
   121  	info, err := os.Stat(inputPath)
   122  	if err != nil {
   123  		return "", err
   124  	}
   125  
   126  	if info.IsDir() {
   127  		var empty bool
   128  		empty, err = isEmptyDirectory(inputPath)
   129  		if err != nil {
   130  			return "", err
   131  		}
   132  		if empty {
   133  			return "", actionerror.EmptyBuildpackDirectoryError{Path: inputPath}
   134  		}
   135  		archive := filepath.Join(tmpDirPath, filepath.Base(inputPath)) + ".zip"
   136  
   137  		err = Zipit(inputPath, archive, "")
   138  		if err != nil {
   139  			return "", err
   140  		}
   141  		return archive, nil
   142  	}
   143  
   144  	return inputPath, nil
   145  }
   146  
   147  func isEmptyDirectory(name string) (bool, error) {
   148  	f, err := os.Open(name)
   149  	if err != nil {
   150  		return false, err
   151  	}
   152  	defer f.Close()
   153  
   154  	_, err = f.Readdirnames(1)
   155  	if err == io.EOF {
   156  		return true, nil
   157  	}
   158  	return false, err
   159  }
   160  
   161  func (actor *Actor) UploadBuildpack(GUID string, pathToBuildpackBits string, progBar SimpleProgressBar) (Warnings, error) {
   162  	progressBarReader, size, err := progBar.Initialize(pathToBuildpackBits)
   163  	if err != nil {
   164  		return Warnings{}, err
   165  	}
   166  
   167  	warnings, err := actor.CloudControllerClient.UploadBuildpack(GUID, pathToBuildpackBits, progressBarReader, size)
   168  	if err != nil {
   169  		if e, ok := err.(ccerror.BuildpackAlreadyExistsForStackError); ok {
   170  			return Warnings(warnings), actionerror.BuildpackAlreadyExistsForStackError{Message: e.Message}
   171  		}
   172  		return Warnings(warnings), err
   173  	}
   174  
   175  	progBar.Terminate()
   176  	return Warnings(warnings), nil
   177  }
   178  
   179  // GetBuildpackByName returns a given buildpack with the provided name. It
   180  // assumes the stack name is empty.
   181  func (actor *Actor) GetBuildpackByName(name string) (Buildpack, Warnings, error) {
   182  	bpName := ccv2.Filter{
   183  		Type:     constant.NameFilter,
   184  		Operator: constant.EqualOperator,
   185  		Values:   []string{name},
   186  	}
   187  
   188  	buildpacks, warnings, err := actor.CloudControllerClient.GetBuildpacks(bpName)
   189  	if err != nil {
   190  		return Buildpack{}, Warnings(warnings), err
   191  	}
   192  
   193  	switch len(buildpacks) {
   194  	case 0:
   195  		return Buildpack{}, Warnings(warnings), actionerror.BuildpackNotFoundError{BuildpackName: name}
   196  	case 1:
   197  		return Buildpack(buildpacks[0]), Warnings(warnings), nil
   198  	default:
   199  		for _, bp := range buildpacks {
   200  			if buildpack := Buildpack(bp); buildpack.NoStack() {
   201  				return buildpack, Warnings(warnings), nil
   202  			}
   203  		}
   204  		return Buildpack{}, Warnings(warnings), actionerror.MultipleBuildpacksFoundError{BuildpackName: name}
   205  	}
   206  }
   207  
   208  func (actor *Actor) GetBuildpackByNameAndStack(buildpackName string, stackName string) (Buildpack, Warnings, error) {
   209  	bpFilter := ccv2.Filter{
   210  		Type:     constant.NameFilter,
   211  		Operator: constant.EqualOperator,
   212  		Values:   []string{buildpackName},
   213  	}
   214  
   215  	stackFilter := ccv2.Filter{
   216  		Type:     constant.StackFilter,
   217  		Operator: constant.EqualOperator,
   218  		Values:   []string{stackName},
   219  	}
   220  
   221  	buildpacks, warnings, err := actor.CloudControllerClient.GetBuildpacks(bpFilter, stackFilter)
   222  	if err != nil {
   223  		return Buildpack{}, Warnings(warnings), err
   224  	}
   225  
   226  	switch len(buildpacks) {
   227  	case 0:
   228  		return Buildpack{}, Warnings(warnings), actionerror.BuildpackNotFoundError{BuildpackName: buildpackName, StackName: stackName}
   229  	case 1:
   230  		return Buildpack(buildpacks[0]), Warnings(warnings), nil
   231  	default:
   232  		return Buildpack{}, Warnings(warnings), actionerror.MultipleBuildpacksFoundError{BuildpackName: buildpackName}
   233  	}
   234  }
   235  
   236  func (actor *Actor) RenameBuildpack(oldName string, newName string, stackName string) (Warnings, error) {
   237  	var (
   238  		getWarnings Warnings
   239  		allWarnings Warnings
   240  
   241  		oldBp Buildpack
   242  		err   error
   243  	)
   244  
   245  	if len(stackName) == 0 {
   246  		oldBp, getWarnings, err = actor.GetBuildpackByName(oldName)
   247  	} else {
   248  		oldBp, getWarnings, err = actor.GetBuildpackByNameAndStack(oldName, stackName)
   249  	}
   250  
   251  	allWarnings = append(allWarnings, getWarnings...)
   252  
   253  	if err != nil {
   254  		return Warnings(allWarnings), err
   255  	}
   256  
   257  	oldBp.Name = newName
   258  
   259  	_, updateWarnings, err := actor.UpdateBuildpack(oldBp)
   260  	allWarnings = append(allWarnings, updateWarnings...)
   261  	if err != nil {
   262  		return Warnings(allWarnings), err
   263  	}
   264  
   265  	return Warnings(allWarnings), nil
   266  }
   267  
   268  func (actor *Actor) UpdateBuildpack(buildpack Buildpack) (Buildpack, Warnings, error) {
   269  	updatedBuildpack, warnings, err := actor.CloudControllerClient.UpdateBuildpack(ccv2.Buildpack(buildpack))
   270  	if err != nil {
   271  		switch err.(type) {
   272  		case ccerror.ResourceNotFoundError:
   273  			return Buildpack{}, Warnings(warnings), actionerror.BuildpackNotFoundError{BuildpackName: buildpack.Name}
   274  		case ccerror.BuildpackAlreadyExistsWithoutStackError:
   275  			return Buildpack{}, Warnings(warnings), actionerror.BuildpackAlreadyExistsWithoutStackError{BuildpackName: buildpack.Name}
   276  		case ccerror.BuildpackAlreadyExistsForStackError:
   277  			return Buildpack{}, Warnings(warnings), actionerror.BuildpackAlreadyExistsForStackError{Message: err.Error()}
   278  		default:
   279  			return Buildpack{}, Warnings(warnings), err
   280  		}
   281  	}
   282  
   283  	return Buildpack(updatedBuildpack), Warnings(warnings), nil
   284  }
   285  
   286  func (actor *Actor) UpdateBuildpackByNameAndStack(name, stack string, position types.NullInt, locked types.NullBool, enabled types.NullBool) (string, Warnings, error) {
   287  	warnings := Warnings{}
   288  	var (
   289  		buildpack    Buildpack
   290  		execWarnings Warnings
   291  		err          error
   292  	)
   293  	if len(stack) > 0 {
   294  		buildpack, execWarnings, err = actor.GetBuildpackByNameAndStack(name, stack)
   295  	} else {
   296  		buildpack, execWarnings, err = actor.GetBuildpackByName(name)
   297  	}
   298  	warnings = append(warnings, execWarnings...)
   299  	if err != nil {
   300  		return "", warnings, err
   301  	}
   302  
   303  	if position != buildpack.Position || locked != buildpack.Enabled || enabled != buildpack.Enabled {
   304  		buildpack.Position = position
   305  		buildpack.Locked = locked
   306  		buildpack.Enabled = enabled
   307  		_, execWarnings, err = actor.UpdateBuildpack(buildpack)
   308  		warnings = append(warnings, execWarnings...)
   309  	}
   310  
   311  	if err != nil {
   312  		return "", warnings, err
   313  	}
   314  
   315  	return buildpack.GUID, warnings, err
   316  }
   317  
   318  // Zipit zips the source into a .zip file in the target dir
   319  func Zipit(source, target, prefix string) error {
   320  	// Thanks to Svett Ralchev
   321  	// http://blog.ralch.com/tutorial/golang-working-with-zip/
   322  
   323  	zipfile, err := os.Create(target)
   324  	if err != nil {
   325  		return err
   326  	}
   327  	defer zipfile.Close()
   328  
   329  	if prefix != "" {
   330  		_, err = io.WriteString(zipfile, prefix)
   331  		if err != nil {
   332  			return err
   333  		}
   334  	}
   335  
   336  	archive := zip.NewWriter(zipfile)
   337  	defer archive.Close()
   338  
   339  	err = filepath.Walk(source, func(path string, info os.FileInfo, err error) error {
   340  		if err != nil {
   341  			return err
   342  		}
   343  
   344  		if path == source {
   345  			return nil
   346  		}
   347  
   348  		header, err := zip.FileInfoHeader(info)
   349  		if err != nil {
   350  			return err
   351  		}
   352  		header.Name, err = filepath.Rel(source, path)
   353  		if err != nil {
   354  			return err
   355  		}
   356  
   357  		header.Name = filepath.ToSlash(header.Name)
   358  		if info.IsDir() {
   359  			header.Name += "/"
   360  			header.SetMode(info.Mode())
   361  		} else {
   362  			header.Method = zip.Deflate
   363  			header.SetMode(fixMode(info.Mode()))
   364  		}
   365  
   366  		writer, err := archive.CreateHeader(header)
   367  		if err != nil {
   368  			return err
   369  		}
   370  
   371  		if info.IsDir() {
   372  			return nil
   373  		}
   374  
   375  		file, err := os.Open(path)
   376  		if err != nil {
   377  			return err
   378  		}
   379  		defer file.Close()
   380  
   381  		_, err = io.Copy(writer, file)
   382  		return err
   383  	})
   384  
   385  	return err
   386  }