-
Notifications
You must be signed in to change notification settings - Fork 1
/
bitbucket.go
204 lines (165 loc) · 4.47 KB
/
bitbucket.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
package main
import (
"bytes"
"encoding/json"
"fmt"
"mime/multipart"
"net/http"
)
const (
commitURL = "%s/rest/api/latest/projects/%s/repos/%s/commits?limit=1"
repoURL = "%s/rest/api/latest/projects/%s/repos/%s/browse/%s?at=%s"
)
var (
bitbucketAddess string
bitbucketToken string
)
func getLatestCommitID(t *Task) (string, error) {
// Compose the URL for the given task..
u := fmt.Sprintf(commitURL, bitbucketAddess, t.project, t.repo)
// Create the request.
req, err := http.NewRequest("GET", u, nil)
if err != nil {
return "", err
}
req.Header.Set("Authorization", "Bearer "+bitbucketToken)
// Make the API call to receive the latest commit.
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
// Check the response for any errors.
if err = checkResponse(resp); err != nil {
return "", err
}
var commits struct {
Values []struct {
CommitID string `json:"id"`
} `json:"values"`
}
// Parse the response to retrieve the commit ID.
err = json.NewDecoder(resp.Body).Decode(&commits)
if err != nil {
return "", err
}
if len(commits.Values) != 1 {
return "", fmt.Errorf("could not find latest commit")
}
return commits.Values[0].CommitID, nil
}
func readBitbucketFile(t *Task) (string, error) {
// Compose the URL for the given task..
u := fmt.Sprintf(repoURL, bitbucketAddess, t.project, t.repo, t.configFile, t.branch)
// Create the request.
req, err := http.NewRequest("GET", u, nil)
if err != nil {
return "", err
}
req.Header.Set("Authorization", "Bearer "+bitbucketToken)
// Make the API call to read the file.
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
// Check the response for any errors.
if err = checkResponse(resp); err != nil {
return "", err
}
var content struct {
Lines []struct {
Text string `json:"text"`
} `json:"lines"`
}
// Parse the response to retrieve the file content.
err = json.NewDecoder(resp.Body).Decode(&content)
if err != nil {
return "", err
}
buf := bytes.NewBuffer(nil)
for _, line := range content.Lines {
buf.WriteString(line.Text + "\n")
}
return buf.String(), nil
}
func writeBitbucketFile(t *Task, content string) error {
// First get the current commit.
commitID, err := getLatestCommitID(t)
if err != nil {
return err
}
// Compose the URL for the given task..
u := fmt.Sprintf(repoURL, bitbucketAddess, t.project, t.repo, t.configFile, t.branch)
buf := new(bytes.Buffer)
mw := multipart.NewWriter(buf)
// This (undocumented) API call requires a From MultiPart request body
// containing the branch, commit ID, message and updated file content.
fw, err := mw.CreateFormField("branch")
if err != nil {
return err
}
// Add the branch.
if _, err = fw.Write([]byte("refs/heads/" + t.branch)); err != nil {
return err
}
if fw, err = mw.CreateFormField("sourceCommitId"); err != nil {
return err
}
// Add the commit ID.
if _, err = fw.Write([]byte(commitID)); err != nil {
return err
}
if fw, err = mw.CreateFormField("message"); err != nil {
return err
}
// Add a custom message.
if _, err = fw.Write([]byte("Backend configuration updated by migration tool")); err != nil {
return err
}
if fw, err = mw.CreateFormFile("content", "blob"); err != nil {
return err
}
// Add the updated fiel content.
if _, err = fw.Write([]byte(content)); err != nil {
return err
}
if err := mw.Close(); err != nil {
return err
}
// Create the request.
req, err := http.NewRequest("PUT", u, buf)
if err != nil {
return err
}
req.Header.Set("Authorization", "Bearer "+bitbucketToken)
req.Header.Set("Content-Type", mw.FormDataContentType())
// Make the API call to write and commit the updated file.
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
return checkResponse(resp)
}
func checkResponse(resp *http.Response) error {
if resp.StatusCode == 200 {
return nil
}
var response struct {
Errors []struct {
Message string `json:"message"`
} `json:"errors"`
}
// If we received an unexpected response code, try to parse the error
// in order to get a descriptive error. If that fails, we just return
// the received HTTP status instead.
err := json.NewDecoder(resp.Body).Decode(&response)
if err != nil {
return fmt.Errorf("error decoding response: %v", err)
}
if len(response.Errors) == 0 {
return fmt.Errorf("unexpected response: %s", resp.Status)
}
return fmt.Errorf(response.Errors[0].Message)
}