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