This repository has been archived by the owner on Oct 21, 2023. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathplugin.go
216 lines (184 loc) · 4.48 KB
/
plugin.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
package main
import (
"context"
"io"
"mime"
"os"
"path/filepath"
"strings"
"errors"
log "github.com/sirupsen/logrus"
b2 "github.com/kurin/blazer/b2"
"github.com/mattn/go-zglob"
)
// Plugin defines the S3 plugin parameters.
type Plugin struct {
Key string
Secret string
Bucket string
// Indicates the files ACL, which should be one
// of the following:
// private
// public-read
// public-read-write
// authenticated-read
// bucket-owner-read
// bucket-owner-full-control
Access string
// Copies the files from the specified directory.
// Regexp matching will apply to match multiple
// files
//
// Examples:
// /path/to/file
// /path/to/*.txt
// /path/to/*/*.txt
// /path/to/**
Source string
Target string
// Strip the prefix from the target path
StripPrefix string
// Recursive uploads
Recursive bool
YamlVerified bool
// Exclude files matching this pattern.
Exclude []string
// Dry run without uploading/
DryRun bool
}
// Exec runs the plugin
func (p *Plugin) Exec() error {
// normalize the target URL
if strings.HasPrefix(p.Target, "/") {
p.Target = p.Target[1:]
}
if p.YamlVerified != true {
return errors.New("Security issue: When using instance role you must have the yaml verified")
}
ctx := context.Background()
client, err := b2.NewClient(ctx, p.Key, p.Secret)
if err != nil {
log.WithFields(log.Fields{
"error": err,
}).Error("Could not authenticate to B2")
}
// find the bucket
log.WithFields(log.Fields{
"bucket": p.Bucket,
}).Info("Attempting to upload")
bucket, err := client.Bucket(ctx, p.Bucket)
if err != nil {
log.WithFields(log.Fields{
"error": err,
}).Error("Could not find bucket")
return err
}
matches, err := matches(p.Source, p.Exclude)
if err != nil {
log.WithFields(log.Fields{
"error": err,
}).Error("Could not match files")
return err
}
for _, match := range matches {
stat, err := os.Stat(match)
if err != nil {
continue // should never happen
}
// skip directories
if stat.IsDir() {
continue
}
target := filepath.Join(p.Target, strings.TrimPrefix(match, p.StripPrefix))
if strings.HasPrefix(target, "/") {
target = strings.TrimPrefix(target, "/")
}
// amazon S3 has pretty crappy default content-type headers so this pluign
// attempts to provide a proper content-type.
content := contentType(match)
// log file for debug purposes.
log.WithFields(log.Fields{
"name": match,
"bucket": p.Bucket,
"target": target,
"content-type": content,
}).Info("Uploading file")
// when executing a dry-run we exit because we don't actually want to
// upload the file to S3.
if p.DryRun {
continue
}
b2attrs := &b2.Attrs{
ContentType: content,
}
f, err := os.Open(match)
if err != nil {
log.WithFields(log.Fields{
"error": err,
"file": match,
}).Error("Problem opening file")
return err
}
defer f.Close()
obj := bucket.Object(target)
w := obj.NewWriter(ctx)
w.WithAttrs(b2attrs)
if _, err := io.Copy(w, f); err != nil {
log.WithFields(log.Fields{
"name": match,
"bucket": p.Bucket,
"target": target,
"error": err,
}).Error("Could not upload file")
w.Close()
return err
}
w.Close()
f.Close()
}
return nil
}
// matches is a helper function that returns a list of all files matching the
// included Glob pattern, while excluding all files that matche the exclusion
// Glob pattners.
func matches(include string, exclude []string) ([]string, error) {
matches, err := zglob.Glob(include)
if err != nil {
return nil, err
}
if len(exclude) == 0 {
return matches, nil
}
// find all files that are excluded and load into a map. we can verify
// each file in the list is not a member of the exclusion list.
excludem := map[string]bool{}
for _, pattern := range exclude {
excludes, err := zglob.Glob(pattern)
if err != nil {
return nil, err
}
for _, match := range excludes {
excludem[match] = true
}
}
var included []string
for _, include := range matches {
_, ok := excludem[include]
if ok {
continue
}
included = append(included, include)
}
return included, nil
}
// contentType is a helper function that returns the content type for the file
// based on extension. If the file extension is unknown application/octet-stream
// is returned.
func contentType(path string) string {
ext := filepath.Ext(path)
typ := mime.TypeByExtension(ext)
if typ == "" {
typ = "application/octet-stream"
}
return typ
}