github.com/cloudfoundry-community/cloudfoundry-cli@v6.44.1-0.20240130060226-cda5ed8e89a5+incompatible/actor/v7action/buildpack.go (about)

     1  package v7action
     2  
     3  import (
     4  	"archive/zip"
     5  	"code.cloudfoundry.org/cli/resources"
     6  	"io"
     7  	"os"
     8  	"path/filepath"
     9  
    10  	"code.cloudfoundry.org/cli/actor/actionerror"
    11  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccerror"
    12  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv3"
    13  	"code.cloudfoundry.org/cli/util"
    14  )
    15  
    16  type Buildpack resources.Buildpack
    17  type JobURL ccv3.JobURL
    18  
    19  //go:generate counterfeiter . Downloader
    20  
    21  type Downloader interface {
    22  	Download(url string, tmpDirPath string) (string, error)
    23  }
    24  
    25  func (actor Actor) GetBuildpacks() ([]Buildpack, Warnings, error) {
    26  	ccv3Buildpacks, warnings, err := actor.CloudControllerClient.GetBuildpacks(ccv3.Query{
    27  		Key:    ccv3.OrderBy,
    28  		Values: []string{ccv3.PositionOrder},
    29  	})
    30  
    31  	var buildpacks []Buildpack
    32  	for _, buildpack := range ccv3Buildpacks {
    33  		buildpacks = append(buildpacks, Buildpack(buildpack))
    34  	}
    35  
    36  	return buildpacks, Warnings(warnings), err
    37  }
    38  
    39  // GetBuildpackByNameAndStack returns a buildpack with the provided name and
    40  // stack. If `buildpackStack` is not specified, and there are multiple
    41  // buildpacks with the same name, it will return the one with no stack, if
    42  // present.
    43  func (actor Actor) GetBuildpackByNameAndStack(buildpackName string, buildpackStack string) (Buildpack, Warnings, error) {
    44  	var (
    45  		ccv3Buildpacks []resources.Buildpack
    46  		warnings       ccv3.Warnings
    47  		err            error
    48  	)
    49  
    50  	if buildpackStack == "" {
    51  		ccv3Buildpacks, warnings, err = actor.CloudControllerClient.GetBuildpacks(ccv3.Query{
    52  			Key:    ccv3.NameFilter,
    53  			Values: []string{buildpackName},
    54  		})
    55  	} else {
    56  		ccv3Buildpacks, warnings, err = actor.CloudControllerClient.GetBuildpacks(
    57  			ccv3.Query{
    58  				Key:    ccv3.NameFilter,
    59  				Values: []string{buildpackName},
    60  			},
    61  			ccv3.Query{
    62  				Key:    ccv3.StackFilter,
    63  				Values: []string{buildpackStack},
    64  			},
    65  		)
    66  	}
    67  
    68  	if err != nil {
    69  		return Buildpack{}, Warnings(warnings), err
    70  	}
    71  
    72  	if len(ccv3Buildpacks) == 0 {
    73  		return Buildpack{}, Warnings(warnings), actionerror.BuildpackNotFoundError{BuildpackName: buildpackName, StackName: buildpackStack}
    74  	}
    75  
    76  	if len(ccv3Buildpacks) > 1 {
    77  		for _, buildpack := range ccv3Buildpacks {
    78  			if buildpack.Stack == "" {
    79  				return Buildpack(buildpack), Warnings(warnings), nil
    80  			}
    81  		}
    82  		return Buildpack{}, Warnings(warnings), actionerror.MultipleBuildpacksFoundError{BuildpackName: buildpackName}
    83  	}
    84  
    85  	return Buildpack(ccv3Buildpacks[0]), Warnings(warnings), err
    86  }
    87  
    88  func (actor Actor) CreateBuildpack(buildpack Buildpack) (Buildpack, Warnings, error) {
    89  	ccv3Buildpack, warnings, err := actor.CloudControllerClient.CreateBuildpack(
    90  		resources.Buildpack(buildpack),
    91  	)
    92  
    93  	return Buildpack(ccv3Buildpack), Warnings(warnings), err
    94  }
    95  
    96  func (actor Actor) UpdateBuildpackByNameAndStack(buildpackName string, buildpackStack string, buildpack Buildpack) (Buildpack, Warnings, error) {
    97  	var warnings Warnings
    98  	foundBuildpack, getWarnings, err := actor.GetBuildpackByNameAndStack(buildpackName, buildpackStack)
    99  	warnings = append(warnings, getWarnings...)
   100  
   101  	if err != nil {
   102  		return Buildpack{}, warnings, err
   103  	}
   104  
   105  	buildpack.GUID = foundBuildpack.GUID
   106  
   107  	updatedBuildpack, updateWarnings, err := actor.CloudControllerClient.UpdateBuildpack(resources.Buildpack(buildpack))
   108  	warnings = append(warnings, updateWarnings...)
   109  	if err != nil {
   110  		return Buildpack{}, warnings, err
   111  	}
   112  
   113  	return Buildpack(updatedBuildpack), warnings, nil
   114  }
   115  
   116  func (actor Actor) UploadBuildpack(guid string, pathToBuildpackBits string, progressBar SimpleProgressBar) (ccv3.JobURL, Warnings, error) {
   117  	wrappedReader, size, err := progressBar.Initialize(pathToBuildpackBits)
   118  	if err != nil {
   119  		return "", Warnings{}, err
   120  	}
   121  
   122  	jobURL, warnings, err := actor.CloudControllerClient.UploadBuildpack(guid, pathToBuildpackBits, wrappedReader, size)
   123  	if err != nil {
   124  		// TODO: Do we actually want to convert this error? Is this the right place?
   125  		if e, ok := err.(ccerror.BuildpackAlreadyExistsForStackError); ok {
   126  			return "", Warnings(warnings), actionerror.BuildpackAlreadyExistsForStackError{Message: e.Message}
   127  		}
   128  		return "", Warnings(warnings), err
   129  	}
   130  
   131  	// TODO: Should we defer the terminate instead?
   132  	progressBar.Terminate()
   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  }