1package cfclient 2 3import ( 4 "encoding/json" 5 "io" 6 "io/ioutil" 7 "mime/multipart" 8 "os" 9 10 "fmt" 11 "net/http" 12 13 "code.cloudfoundry.org/gofileutils/fileutils" 14 "github.com/pkg/errors" 15) 16 17type BuildpackResponse struct { 18 Count int `json:"total_results"` 19 Pages int `json:"total_pages"` 20 NextUrl string `json:"next_url"` 21 Resources []BuildpackResource `json:"resources"` 22} 23 24type BuildpackResource struct { 25 Meta Meta `json:"metadata"` 26 Entity Buildpack `json:"entity"` 27} 28 29type Buildpack struct { 30 Guid string `json:"guid"` 31 CreatedAt string `json:"created_at"` 32 UpdatedAt string `json:"updated_at"` 33 Name string `json:"name"` 34 Enabled bool `json:"enabled"` 35 Locked bool `json:"locked"` 36 Position int `json:"position"` 37 Filename string `json:"filename"` 38 Stack string `json:"stack"` 39 c *Client 40} 41 42type BuildpackRequest struct { 43 // These are all pointers to the values so that we can tell 44 // whether people wanted position 0, or enable/unlock values, 45 // vs whether they didn't specify them and want them unchanged/default. 46 Name *string `json:"name,omitempty"` 47 Enabled *bool `json:"enabled,omitempty"` 48 Locked *bool `json:"locked,omitempty"` 49 Position *int `json:"position,omitempty"` 50 Stack *string `json:"stack,omitempty"` 51} 52 53func (c *Client) CreateBuildpack(bpr *BuildpackRequest) (*Buildpack, error) { 54 if bpr.Name == nil || *bpr.Name == "" { 55 return nil, errors.New("Unable to create a buidlpack with no name") 56 } 57 requestUrl := "/v2/buildpacks" 58 req := c.NewRequest("POST", requestUrl) 59 req.obj = bpr 60 resp, err := c.DoRequest(req) 61 if err != nil { 62 return nil, errors.Wrap(err, "Error creating buildpack:") 63 } 64 bp, err := c.handleBuildpackResp(resp) 65 if err != nil { 66 return nil, errors.Wrap(err, "Error creating buildpack:") 67 } 68 return &bp, nil 69} 70 71func (c *Client) ListBuildpacks() ([]Buildpack, error) { 72 var buildpacks []Buildpack 73 requestUrl := "/v2/buildpacks" 74 for { 75 buildpackResp, err := c.getBuildpackResponse(requestUrl) 76 if err != nil { 77 return []Buildpack{}, err 78 } 79 for _, buildpack := range buildpackResp.Resources { 80 buildpacks = append(buildpacks, c.mergeBuildpackResource(buildpack)) 81 } 82 requestUrl = buildpackResp.NextUrl 83 if requestUrl == "" { 84 break 85 } 86 } 87 return buildpacks, nil 88} 89 90func (c *Client) DeleteBuildpack(guid string, async bool) error { 91 resp, err := c.DoRequest(c.NewRequest("DELETE", fmt.Sprintf("/v2/buildpacks/%s?async=%t", guid, async))) 92 if err != nil { 93 return err 94 } 95 if (async && (resp.StatusCode != http.StatusAccepted)) || (!async && (resp.StatusCode != http.StatusNoContent)) { 96 return errors.Wrapf(err, "Error deleting buildpack %s, response code: %d", guid, resp.StatusCode) 97 } 98 return nil 99} 100 101func (c *Client) getBuildpackResponse(requestUrl string) (BuildpackResponse, error) { 102 var buildpackResp BuildpackResponse 103 r := c.NewRequest("GET", requestUrl) 104 resp, err := c.DoRequest(r) 105 if err != nil { 106 return BuildpackResponse{}, errors.Wrap(err, "Error requesting buildpacks") 107 } 108 resBody, err := ioutil.ReadAll(resp.Body) 109 defer resp.Body.Close() 110 if err != nil { 111 return BuildpackResponse{}, errors.Wrap(err, "Error reading buildpack request") 112 } 113 err = json.Unmarshal(resBody, &buildpackResp) 114 if err != nil { 115 return BuildpackResponse{}, errors.Wrap(err, "Error unmarshalling buildpack") 116 } 117 return buildpackResp, nil 118} 119 120func (c *Client) mergeBuildpackResource(buildpack BuildpackResource) Buildpack { 121 buildpack.Entity.Guid = buildpack.Meta.Guid 122 buildpack.Entity.CreatedAt = buildpack.Meta.CreatedAt 123 buildpack.Entity.UpdatedAt = buildpack.Meta.UpdatedAt 124 buildpack.Entity.c = c 125 return buildpack.Entity 126} 127 128func (c *Client) GetBuildpackByGuid(buildpackGUID string) (Buildpack, error) { 129 requestUrl := fmt.Sprintf("/v2/buildpacks/%s", buildpackGUID) 130 r := c.NewRequest("GET", requestUrl) 131 resp, err := c.DoRequest(r) 132 if err != nil { 133 return Buildpack{}, errors.Wrap(err, "Error requesting buildpack info") 134 } 135 return c.handleBuildpackResp(resp) 136} 137 138func (c *Client) handleBuildpackResp(resp *http.Response) (Buildpack, error) { 139 body, err := ioutil.ReadAll(resp.Body) 140 defer resp.Body.Close() 141 if err != nil { 142 return Buildpack{}, err 143 } 144 var buildpackResource BuildpackResource 145 if err := json.Unmarshal(body, &buildpackResource); err != nil { 146 return Buildpack{}, err 147 } 148 return c.mergeBuildpackResource(buildpackResource), nil 149} 150 151func (b *Buildpack) Upload(file io.Reader, fileName string) error { 152 var capturedErr error 153 fileutils.TempFile("requests", func(requestFile *os.File, err error) { 154 if err != nil { 155 capturedErr = err 156 return 157 } 158 writer := multipart.NewWriter(requestFile) 159 part, err := writer.CreateFormFile("buildpack", fileName) 160 161 if err != nil { 162 _ = writer.Close() 163 capturedErr = err 164 return 165 } 166 167 _, err = io.Copy(part, file) 168 if err != nil { 169 capturedErr = fmt.Errorf("Error creating upload: %s", err.Error()) 170 return 171 } 172 173 err = writer.Close() 174 if err != nil { 175 capturedErr = err 176 return 177 } 178 179 requestFile.Seek(0, 0) 180 fileStats, err := requestFile.Stat() 181 if err != nil { 182 capturedErr = fmt.Errorf("Error getting file info: %s", err) 183 } 184 185 req, err := http.NewRequest("PUT", fmt.Sprintf("%s/v2/buildpacks/%s/bits", b.c.Config.ApiAddress, b.Guid), requestFile) 186 if err != nil { 187 capturedErr = err 188 return 189 } 190 191 req.ContentLength = fileStats.Size() 192 contentType := fmt.Sprintf("multipart/form-data; boundary=%s", writer.Boundary()) 193 req.Header.Set("Content-Type", contentType) 194 resp, err := b.c.Do(req) //client.Do() handles the HTTP status code checking for us 195 if err != nil { 196 capturedErr = err 197 return 198 } 199 defer resp.Body.Close() 200 }) 201 202 return errors.Wrap(capturedErr, "Error uploading buildpack:") 203} 204 205func (b *Buildpack) Update(bpr *BuildpackRequest) error { 206 requestUrl := fmt.Sprintf("/v2/buildpacks/%s", b.Guid) 207 req := b.c.NewRequest("PUT", requestUrl) 208 req.obj = bpr 209 resp, err := b.c.DoRequest(req) 210 if err != nil { 211 return errors.Wrap(err, "Error updating buildpack:") 212 } 213 newBp, err := b.c.handleBuildpackResp(resp) 214 if err != nil { 215 return errors.Wrap(err, "Error updating buildpack:") 216 } 217 b.Name = newBp.Name 218 b.Locked = newBp.Locked 219 b.Enabled = newBp.Enabled 220 return nil 221} 222 223func (bpr *BuildpackRequest) Lock() { 224 b := true 225 bpr.Locked = &b 226} 227func (bpr *BuildpackRequest) Unlock() { 228 b := false 229 bpr.Locked = &b 230} 231func (bpr *BuildpackRequest) Enable() { 232 b := true 233 bpr.Enabled = &b 234} 235func (bpr *BuildpackRequest) Disable() { 236 b := false 237 bpr.Enabled = &b 238} 239func (bpr *BuildpackRequest) SetPosition(i int) { 240 bpr.Position = &i 241} 242func (bpr *BuildpackRequest) SetName(s string) { 243 bpr.Name = &s 244} 245func (bpr *BuildpackRequest) SetStack(s string) { 246 bpr.Stack = &s 247} 248