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 }