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