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 }