github.com/cloudfoundry/cli@v7.1.0+incompatible/actor/v7action/buildpack.go (about)

     1  package v7action
     2  
     3  import (
     4  	"archive/zip"
     5  	"io"
     6  	"os"
     7  	"path/filepath"
     8  
     9  	"code.cloudfoundry.org/cli/actor/actionerror"
    10  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccerror"
    11  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv3"
    12  	"code.cloudfoundry.org/cli/util"
    13  )
    14  
    15  type Buildpack ccv3.Buildpack
    16  type JobURL ccv3.JobURL
    17  
    18  //go:generate counterfeiter . Downloader
    19  
    20  type Downloader interface {
    21  	Download(url string, tmpDirPath string) (string, error)
    22  }
    23  
    24  func (actor Actor) GetBuildpacks(labelSelector string) ([]Buildpack, Warnings, error) {
    25  	queries := []ccv3.Query{ccv3.Query{Key: ccv3.OrderBy, Values: []string{ccv3.PositionOrder}}}
    26  	if labelSelector != "" {
    27  		queries = append(queries, ccv3.Query{Key: ccv3.LabelSelectorFilter, Values: []string{labelSelector}})
    28  	}
    29  
    30  	ccv3Buildpacks, warnings, err := actor.CloudControllerClient.GetBuildpacks(queries...)
    31  
    32  	var buildpacks []Buildpack
    33  	for _, buildpack := range ccv3Buildpacks {
    34  		buildpacks = append(buildpacks, Buildpack(buildpack))
    35  	}
    36  
    37  	return buildpacks, Warnings(warnings), err
    38  }
    39  
    40  // GetBuildpackByNameAndStack returns a buildpack with the provided name and
    41  // stack. If `buildpackStack` is not specified, and there are multiple
    42  // buildpacks with the same name, it will return the one with no stack, if
    43  // present.
    44  func (actor Actor) GetBuildpackByNameAndStack(buildpackName string, buildpackStack string) (Buildpack, Warnings, error) {
    45  	var (
    46  		ccv3Buildpacks []ccv3.Buildpack
    47  		warnings       ccv3.Warnings
    48  		err            error
    49  	)
    50  
    51  	if buildpackStack == "" {
    52  		ccv3Buildpacks, warnings, err = actor.CloudControllerClient.GetBuildpacks(ccv3.Query{
    53  			Key:    ccv3.NameFilter,
    54  			Values: []string{buildpackName},
    55  		})
    56  	} else {
    57  		ccv3Buildpacks, warnings, err = actor.CloudControllerClient.GetBuildpacks(
    58  			ccv3.Query{
    59  				Key:    ccv3.NameFilter,
    60  				Values: []string{buildpackName},
    61  			},
    62  			ccv3.Query{
    63  				Key:    ccv3.StackFilter,
    64  				Values: []string{buildpackStack},
    65  			},
    66  		)
    67  	}
    68  
    69  	if err != nil {
    70  		return Buildpack{}, Warnings(warnings), err
    71  	}
    72  
    73  	if len(ccv3Buildpacks) == 0 {
    74  		return Buildpack{}, Warnings(warnings), actionerror.BuildpackNotFoundError{BuildpackName: buildpackName, StackName: buildpackStack}
    75  	}
    76  
    77  	if len(ccv3Buildpacks) > 1 {
    78  		for _, buildpack := range ccv3Buildpacks {
    79  			if buildpack.Stack == "" {
    80  				return Buildpack(buildpack), Warnings(warnings), nil
    81  			}
    82  		}
    83  		return Buildpack{}, Warnings(warnings), actionerror.MultipleBuildpacksFoundError{BuildpackName: buildpackName}
    84  	}
    85  
    86  	return Buildpack(ccv3Buildpacks[0]), Warnings(warnings), err
    87  }
    88  
    89  func (actor Actor) CreateBuildpack(buildpack Buildpack) (Buildpack, Warnings, error) {
    90  	ccv3Buildpack, warnings, err := actor.CloudControllerClient.CreateBuildpack(
    91  		ccv3.Buildpack(buildpack),
    92  	)
    93  
    94  	return Buildpack(ccv3Buildpack), Warnings(warnings), err
    95  }
    96  
    97  func (actor Actor) UpdateBuildpackByNameAndStack(buildpackName string, buildpackStack string, buildpack Buildpack) (Buildpack, Warnings, error) {
    98  	var warnings Warnings
    99  	foundBuildpack, getWarnings, err := actor.GetBuildpackByNameAndStack(buildpackName, buildpackStack)
   100  	warnings = append(warnings, getWarnings...)
   101  
   102  	if err != nil {
   103  		return Buildpack{}, warnings, err
   104  	}
   105  
   106  	buildpack.GUID = foundBuildpack.GUID
   107  
   108  	updatedBuildpack, updateWarnings, err := actor.CloudControllerClient.UpdateBuildpack(ccv3.Buildpack(buildpack))
   109  	warnings = append(warnings, updateWarnings...)
   110  	if err != nil {
   111  		return Buildpack{}, warnings, err
   112  	}
   113  
   114  	return Buildpack(updatedBuildpack), warnings, nil
   115  }
   116  
   117  func (actor Actor) UploadBuildpack(guid string, pathToBuildpackBits string, progressBar SimpleProgressBar) (ccv3.JobURL, Warnings, error) {
   118  	wrappedReader, size, err := progressBar.Initialize(pathToBuildpackBits)
   119  	if err != nil {
   120  		return "", Warnings{}, err
   121  	}
   122  
   123  	defer progressBar.Terminate()
   124  
   125  	jobURL, warnings, err := actor.CloudControllerClient.UploadBuildpack(guid, pathToBuildpackBits, wrappedReader, size)
   126  	if err != nil {
   127  		// TODO: Do we actually want to convert this error? Is this the right place?
   128  		if e, ok := err.(ccerror.BuildpackAlreadyExistsForStackError); ok {
   129  			return "", Warnings(warnings), actionerror.BuildpackAlreadyExistsForStackError{Message: e.Message}
   130  		}
   131  		return "", Warnings(warnings), err
   132  	}
   133  
   134  	return jobURL, Warnings(warnings), nil
   135  }
   136  
   137  func (actor *Actor) PrepareBuildpackBits(inputPath string, tmpDirPath string, downloader Downloader) (string, error) {
   138  	if util.IsHTTPScheme(inputPath) {
   139  		pathToDownloadedBits, err := downloader.Download(inputPath, tmpDirPath)
   140  		if err != nil {
   141  			return "", err
   142  		}
   143  		return pathToDownloadedBits, nil
   144  	}
   145  
   146  	if filepath.Ext(inputPath) == ".zip" {
   147  		return inputPath, nil
   148  	}
   149  
   150  	info, err := os.Stat(inputPath)
   151  	if err != nil {
   152  		return "", err
   153  	}
   154  
   155  	if info.IsDir() {
   156  		var empty bool
   157  		empty, err = isEmptyDirectory(inputPath)
   158  		if err != nil {
   159  			return "", err
   160  		}
   161  		if empty {
   162  			return "", actionerror.EmptyBuildpackDirectoryError{Path: inputPath}
   163  		}
   164  		archive := filepath.Join(tmpDirPath, filepath.Base(inputPath)) + ".zip"
   165  
   166  		err = Zipit(inputPath, archive, "")
   167  		if err != nil {
   168  			return "", err
   169  		}
   170  		return archive, nil
   171  	}
   172  
   173  	return inputPath, nil
   174  }
   175  
   176  func isEmptyDirectory(name string) (bool, error) {
   177  	f, err := os.Open(name)
   178  	if err != nil {
   179  		return false, err
   180  	}
   181  	defer f.Close()
   182  
   183  	_, err = f.Readdirnames(1)
   184  	if err == io.EOF {
   185  		return true, nil
   186  	}
   187  	return false, err
   188  }
   189  
   190  // Zipit zips the source into a .zip file in the target dir
   191  func Zipit(source, target, prefix string) error {
   192  	// Thanks to Svett Ralchev
   193  	// http://blog.ralch.com/tutorial/golang-working-with-zip/
   194  
   195  	zipfile, err := os.Create(target)
   196  	if err != nil {
   197  		return err
   198  	}
   199  	defer zipfile.Close()
   200  
   201  	if prefix != "" {
   202  		_, err = io.WriteString(zipfile, prefix)
   203  		if err != nil {
   204  			return err
   205  		}
   206  	}
   207  
   208  	archive := zip.NewWriter(zipfile)
   209  	defer archive.Close()
   210  
   211  	err = filepath.Walk(source, func(path string, info os.FileInfo, err error) error {
   212  		if err != nil {
   213  			return err
   214  		}
   215  
   216  		if path == source {
   217  			return nil
   218  		}
   219  
   220  		header, err := zip.FileInfoHeader(info)
   221  		if err != nil {
   222  			return err
   223  		}
   224  		header.Name, err = filepath.Rel(source, path)
   225  		if err != nil {
   226  			return err
   227  		}
   228  
   229  		header.Name = filepath.ToSlash(header.Name)
   230  		if info.IsDir() {
   231  			header.Name += "/"
   232  			header.SetMode(info.Mode())
   233  		} else {
   234  			header.Method = zip.Deflate
   235  			header.SetMode(fixMode(info.Mode()))
   236  		}
   237  
   238  		writer, err := archive.CreateHeader(header)
   239  		if err != nil {
   240  			return err
   241  		}
   242  
   243  		if info.IsDir() {
   244  			return nil
   245  		}
   246  
   247  		file, err := os.Open(path)
   248  		if err != nil {
   249  			return err
   250  		}
   251  		defer file.Close()
   252  
   253  		_, err = io.Copy(writer, file)
   254  		return err
   255  	})
   256  
   257  	return err
   258  }
   259  
   260  func (actor Actor) DeleteBuildpackByNameAndStack(buildpackName string, buildpackStack string) (Warnings, error) {
   261  	var allWarnings Warnings
   262  	buildpack, getBuildpackWarnings, err := actor.GetBuildpackByNameAndStack(buildpackName, buildpackStack)
   263  	allWarnings = append(allWarnings, getBuildpackWarnings...)
   264  	if err != nil {
   265  		return allWarnings, err
   266  	}
   267  
   268  	jobURL, deleteBuildpackWarnings, err := actor.CloudControllerClient.DeleteBuildpack(buildpack.GUID)
   269  	allWarnings = append(allWarnings, deleteBuildpackWarnings...)
   270  	if err != nil {
   271  		return allWarnings, err
   272  	}
   273  
   274  	pollWarnings, err := actor.CloudControllerClient.PollJob(jobURL)
   275  	allWarnings = append(allWarnings, pollWarnings...)
   276  
   277  	return allWarnings, err
   278  }