-
Notifications
You must be signed in to change notification settings - Fork 69
/
plugin.go
278 lines (238 loc) · 6.66 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
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
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
// The `fs` plugin for SHIELD implements generic backup + restore
// functionality for filesystem based backups. It can be used against
// any server that has files that should be backed up. It's not safe
// to use if those files are held open and constantly written to
// by a service (like a database), since there is no coordination
// made with anything that may have those files open.
//
// PLUGIN FEATURES
//
// This plugin implements functionality suitable for use with the following
// SHIELD Job components:
//
// Target: yes
// Store: yes
//
// PLUGIN CONFIGURATION
//
// The endpoint configuration passed to this plugin is used to identify what
// files should be backed up from the local system. Your endpoint JSON
// should look something like this:
//
// {
// "include":"glob-of-files-to-include", // optional
// "exclude":"glob-of-files-to-exclude", // optional
// "bsdtar":"path-to-bsdtar", // optional
// "base_dir":"base-directory-to-backup"
// }
//
// BACKUP DETAILS
//
// The `fs` plugin uses `bsdtar` to back up all files located in `base_dir`
// which match the `include` pattern, but do not match the `exclude` pattern.
// If no exclude pattern is supplied, no files are filtered out. If no `include`
// pattern is supplied, all files found are included. Following `bsdtar`'s logic,
// excludes take priority over includes.
//
// RESTORE DETAILS
//
// The `fs` plugin restores the data backed up with `bsdtar` on top of `base_directory`.
// It does not clean up the directory first, so any files that exist on the FS, but are
// not in the restored archive will not be removed.
//
// DEPENDENCIES
//
// This plugin relies on the `bsdtar` utility. Please ensure that it is present on the
// system that will be running the backups + restores. If you are using shield-boshrelease,
// this is provided automatically for you as part of the `shield-agent` job template.
//
package main
import (
"fmt"
"io"
"os"
"time"
"github.com/starkandwayne/goutils/ansi"
"github.com/starkandwayne/shield/plugin"
)
var (
DefaultBsdTar = "/var/vcap/packages/bsdtar/bin/bsdtar"
)
func main() {
p := FSPlugin{
Name: "FS Plugin",
Author: "Stark & Wayne",
Version: "1.0.0",
Features: plugin.PluginFeatures{
Target: "yes",
Store: "yes",
},
}
plugin.DEBUG("fs plugin starting up...")
plugin.Run(p)
}
type FSPlugin plugin.PluginInfo
type FSConfig struct {
Include string
Exclude string
BasePath string
BsdTar string
}
func (p FSPlugin) Meta() plugin.PluginInfo {
return plugin.PluginInfo(p)
}
func getFSConfig(endpoint plugin.ShieldEndpoint) (*FSConfig, error) {
include, err := endpoint.StringValueDefault("include", "")
if err != nil {
return nil, err
}
exclude, err := endpoint.StringValueDefault("exclude", "")
if err != nil {
return nil, err
}
bsdtar, err := endpoint.StringValueDefault("bsdtar", DefaultBsdTar)
if err != nil {
return nil, err
}
base_dir, err := endpoint.StringValue("base_dir")
if err != nil {
return nil, err
}
return &FSConfig{
Include: include,
Exclude: exclude,
BasePath: base_dir,
BsdTar: bsdtar,
}, nil
}
func (p FSPlugin) Validate(endpoint plugin.ShieldEndpoint) error {
var (
s string
err error
fail bool
)
s, err = endpoint.StringValue("base_dir")
if err != nil {
ansi.Printf("@R{\u2717 base_dir %s}\n", err)
fail = true
} else {
ansi.Printf("@G{\u2713 base_dir} files in @C{%s} will be backed up\n", s)
}
s, err = endpoint.StringValueDefault("include", "")
if err != nil {
ansi.Printf("@R{\u2717 include %s}\n", err)
fail = true
} else if s == "" {
ansi.Printf("@G{\u2713 include} all files will be included\n")
} else {
ansi.Printf("@G{\u2713 include} only files matching @C{%s} will be backed up\n", s)
}
s, err = endpoint.StringValueDefault("exclude", "")
if err != nil {
ansi.Printf("@R{\u2717 base_dir %s}\n", err)
fail = true
} else if s == "" {
ansi.Printf("@G{\u2713 exclude} no files will be excluded\n")
} else {
ansi.Printf("@G{\u2713 exclude} files matching @C{%s} will be skipped\n", s)
}
s, err = endpoint.StringValueDefault("bsdtar", "")
if err != nil {
ansi.Printf("@R{\u2717 bsdtar %s}\n", err)
fail = true
} else if s == "" {
ansi.Printf("@G{\u2713 bsdtar} using default @C{%s}\n", DefaultBsdTar)
} else {
ansi.Printf("@G{\u2713 bsdtar} @C{%s}\n", s)
}
if fail {
return fmt.Errorf("fs: invalid configuration")
}
return nil
}
func (p FSPlugin) Backup(endpoint plugin.ShieldEndpoint) error {
cfg, err := getFSConfig(endpoint)
if err != nil {
return err
}
//FIXME: drop include and exclude if they were not specified
var flags string
if cfg.Include != "" {
flags = fmt.Sprintf("%s --include '%s'", flags, cfg.Include)
}
if cfg.Exclude != "" {
flags = fmt.Sprintf("%s --exclude '%s'", flags, cfg.Exclude)
}
cmd := fmt.Sprintf("%s -c -C %s -f - %s .", cfg.BsdTar, cfg.BasePath, flags)
plugin.DEBUG("Executing `%s`", cmd)
err = plugin.Exec(cmd, plugin.STDOUT)
if err != nil {
return err
}
return nil
}
func (p FSPlugin) Restore(endpoint plugin.ShieldEndpoint) error {
cfg, err := getFSConfig(endpoint)
if err != nil {
return err
}
os.MkdirAll(cfg.BasePath, 0777)
cmd := fmt.Sprintf("%s -x -C %s -f -", cfg.BsdTar, cfg.BasePath)
plugin.DEBUG("Executing `%s`", cmd)
err = plugin.Exec(cmd, plugin.STDIN)
if err != nil {
return err
}
return nil
}
func (p FSPlugin) Store(endpoint plugin.ShieldEndpoint) (string, error) {
cfg, err := getFSConfig(endpoint)
if err != nil {
return "", err
}
t := time.Now()
year, mon, day := t.Date()
hour, min, sec := t.Clock()
uuid := plugin.GenUUID()
dir := fmt.Sprintf("%04d/%02d/%02d", year, mon, day)
file := fmt.Sprintf("%04d-%02d-%02d-%02d%02d%02d-%s", year, mon, day, hour, min, sec, uuid)
err = os.MkdirAll(fmt.Sprintf("%s/%s", cfg.BasePath, dir), 0777) // umask will lower...
if err != nil {
return "", err
}
f, err := os.Create(fmt.Sprintf("%s/%s/%s", cfg.BasePath, dir, file))
if err != nil {
return "", err
}
defer f.Close()
if _, err = io.Copy(f, os.Stdin); err != nil {
return "", err
}
return fmt.Sprintf("%s/%s", dir, file), nil
}
func (p FSPlugin) Retrieve(endpoint plugin.ShieldEndpoint, file string) error {
cfg, err := getFSConfig(endpoint)
if err != nil {
return err
}
f, err := os.Open(fmt.Sprintf("%s/%s", cfg.BasePath, file))
if err != nil {
return err
}
defer f.Close()
if _, err = io.Copy(os.Stdout, f); err != nil {
return err
}
return nil
}
func (p FSPlugin) Purge(endpoint plugin.ShieldEndpoint, file string) error {
cfg, err := getFSConfig(endpoint)
if err != nil {
return err
}
err = os.Remove(fmt.Sprintf("%s/%s", cfg.BasePath, file))
if err != nil {
return err
}
return nil
}