/
publishing.go
254 lines (206 loc) · 7.17 KB
/
publishing.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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
package gomason
import (
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
"github.com/sirupsen/logrus"
"io"
"net/http"
"os"
"path/filepath"
"strings"
"github.com/pkg/errors"
)
// PublishFile publishes the binary to wherever you have it configured to go
func (g *Gomason) PublishFile(meta Metadata, filePath string) (err error) {
// get creds
username, password, err := g.GetCredentials(meta)
if err != nil {
err = errors.Wrapf(err, "failed to get credentials")
return err
}
logrus.Debugf("Publishing %s", filePath)
client := &http.Client{}
fileName := filepath.Base(filePath)
target, ok := meta.PublishInfo.TargetsMap[fileName]
if ok {
// upload the file
err = UploadFile(client, target.Destination, filePath, meta, username, password)
if err != nil {
err = errors.Wrapf(err, "failed to upload file %s", filePath)
return err
}
// upload the detached signature
if target.Signature {
err := UploadSignature(client, target.Destination, filePath, meta, username, password)
if err != nil {
err = errors.Wrapf(err, "failed to upload signature for %s", filePath)
return err
}
}
// upload the checksums if configured to do so
if target.Checksums {
err := UploadChecksums(client, target.Destination, filePath, meta, username, password)
if err != nil {
err = errors.Wrapf(err, "failed to upload checksums for %s", filePath)
return err
}
}
}
return err
}
// UploadChecksums uploads the checksums for a file. This is useful if the repository is not configured to do so automatically.
func UploadChecksums(client *http.Client, destination, filename string, meta Metadata, username string, password string) (err error) {
md5sum, sha1sum, sha256sum, err := AllChecksumsForFile(filename)
if err != nil {
err = errors.Wrapf(err, "failed to calculate checksum for %s", filename)
return err
}
parsedDestination, err := ParseTemplateForMetadata(destination, meta)
if err != nil {
err = errors.Wrapf(err, "failed to parse destination url %s", destination)
return err
}
// upload Md5Sum
err = UploadChecksum(parsedDestination, md5sum, "md5", client, username, password)
if err != nil {
err = errors.Wrapf(err, "failed to upload md5sum file for %s", filename)
}
// upload Sha1Sum
err = UploadChecksum(parsedDestination, sha1sum, "sha1", client, username, password)
if err != nil {
err = errors.Wrapf(err, "failed to upload sha1sum file for %s", filename)
}
// upload Sha256Sum
err = UploadChecksum(parsedDestination, sha256sum, "sha256", client, username, password)
if err != nil {
err = errors.Wrapf(err, "failed to upload sha256sum file for %s", filename)
}
return err
}
// UploadChecksum uploads the checksum of the given type for the given file
func UploadChecksum(parsedDestination, checksum, sumtype string, client *http.Client, username, password string) (err error) {
target := fmt.Sprintf("%s.%s", parsedDestination, sumtype)
contents := checksum
sumMd5, sumSha1, sumSha256, err := AllChecksumsForBytes([]byte(contents))
if err != nil {
err = errors.Wrapf(err, "failed to generate checksums for %s file with contents %q", sumtype, contents)
return err
}
logrus.Debugf("Uploading checksum to %s", target)
err = Upload(client, target, strings.NewReader(contents), sumMd5, sumSha1, sumSha256, username, password)
if err != nil {
err = errors.Wrapf(err, "failed to upload md5sum file to %s", target)
return err
}
return err
}
// UploadFile uploads a file off the filesystem
func UploadFile(client *http.Client, destination string, filename string, meta Metadata, username string, password string) (err error) {
// get data
data, err := os.Open(filename)
if err != nil {
err = errors.Wrapf(err, "failed to open %s", filename)
return err
}
// get checksums
md5sum, sha1sum, sha256sum, err := AllChecksumsForFile(filename)
if err != nil {
err = errors.Wrapf(err, "failed to calculate checksum for %s", filename)
return err
}
parsedDestination, err := ParseTemplateForMetadata(destination, meta)
if err != nil {
err = errors.Wrapf(err, "failed to parse destination url %s", destination)
return err
}
logrus.Debugf("Attempting to upload %s to %s", filename, parsedDestination)
return Upload(client, parsedDestination, data, md5sum, sha1sum, sha256sum, username, password)
}
// UploadSignature uploads the detached signature for a file
func UploadSignature(client *http.Client, destination string, filename string, meta Metadata, username string, password string) (err error) {
filename += ".asc"
destination += ".asc"
return UploadFile(client, destination, filename, meta, username, password)
}
// Upload actually does the upload. It uploads pure data.
func Upload(client *http.Client, url string, data io.Reader, md5sum string, sha1sum string, sha256sum string, username string, password string) (err error) {
// Check to see if this is an S3 URL
isS3, s3Meta := S3Url(url)
if isS3 {
sess, err := DefaultSession()
if err != nil {
err = errors.Wrap(err, "Failed to create AWS session")
return err
}
uploader := s3manager.NewUploader(sess)
uploadOptions := &s3manager.UploadInput{
Body: data,
Bucket: aws.String(s3Meta.Bucket),
Key: aws.String(s3Meta.Key),
}
_, err = uploader.Upload(uploadOptions)
if err != nil {
err = errors.Wrapf(err, "failed uploading to %s", url)
return err
}
// make the directory paths in s3
dirs, err := DirsForURL(s3Meta.Key)
if err != nil {
err = errors.Wrapf(err, "failed to parse dirs for %s", s3Meta.Key)
return err
}
headSvc := s3.New(sess)
s3Client := s3.New(sess)
// create the 'folders' (0 byte objects) in s3
for _, d := range dirs {
if d != "." {
path := fmt.Sprintf("%s/", d)
// check to see if it doesn't already exist
headOptions := &s3.HeadObjectInput{
Bucket: aws.String(s3Meta.Bucket),
Key: aws.String(path),
}
_, err = headSvc.HeadObject(headOptions)
// if there's an error, it doesn't exist
if err != nil {
// so create it
_, err = s3Client.PutObject(&s3.PutObjectInput{
Bucket: aws.String(s3Meta.Bucket),
Key: aws.String(path),
})
if err != nil {
err = errors.Wrapf(err, "Failed to create %s in s3", path)
return err
}
}
}
}
return err
}
// TODO Check if destination exists
// TODO Create path if not
req, err := http.NewRequest("PUT", url, data)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("failed to create http request for target %s", url))
return err
}
// add headers (Technically these are what Artifactory expects, but should be fine for any REST interface)
req.Header.Add("X-Checksum-Md5", md5sum)
req.Header.Add("X-Checksum-Sha1", sha1sum)
req.Header.Add("X-Checksum-Sha256", sha256sum)
req.SetBasicAuth(username, password)
resp, err := client.Do(req)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("Failed to PUT to url %s", url))
return err
}
logrus.Debugf("Response: %s", resp.Status)
logrus.Debugf("Response Code: %d", resp.StatusCode)
if resp.StatusCode > 299 {
err = errors.New(fmt.Sprintf("response code %d is not indicative of a successful publish", resp.StatusCode))
return err
}
return err
}