1
// Copyright 2014 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
15
"github.com/juju/errors"
16
jujuversion "github.com/juju/juju/version"
17
"github.com/juju/utils/filestorage"
18
"github.com/juju/version"
21
// checksumFormat identifies how to interpret the checksum for a backup
22
// generated with this version of juju.
23
const checksumFormat = "SHA-1, base64 encoded"
25
// Origin identifies where a backup archive came from. While it is
26
// more about where and Metadata about what and when, that distinction
27
// does not merit special consideration. Instead, Origin exists
28
// separately from Metadata due to its use as an argument when
29
// requesting the creation of a new backup.
34
Version version.Number
38
// UnknownString is a marker value for string fields with unknown values.
39
const UnknownString = "<unknown>"
41
// UnknownVersion is a marker value for version fields with unknown values.
42
var UnknownVersion = version.MustParse("9999.9999.9999")
44
// UnknownOrigin returns a new backups origin with unknown values.
45
func UnknownOrigin() Origin {
48
Machine: UnknownString,
49
Hostname: UnknownString,
50
Version: UnknownVersion,
54
// Metadata contains the metadata for a single state backup archive.
55
type Metadata struct {
56
*filestorage.FileMetadata
58
// Started records when the backup was started.
61
// Finished records when the backup was complete.
64
// Origin identifies where the backup was created.
67
// Notes is an optional user-supplied annotation.
70
// TODO(wallyworld) - remove these ASAP
71
// These are only used by the restore CLI when re-bootstrapping.
72
// We will use a better solution but the way restore currently
73
// works, we need them and they are no longer available via
74
// bootstrap config. We will need to ifx how re-bootstrap deals
75
// with these keys to address the issue.
77
// CACert is the controller CA certificate.
80
// CAPrivateKey is the controller CA private key.
84
// NewMetadata returns a new Metadata for a state backup archive. Only
85
// the start time and the version are set.
86
func NewMetadata() *Metadata {
88
FileMetadata: filestorage.NewMetadata(),
89
// TODO(fwereade): 2016-03-17 lp:1558657
90
Started: time.Now().UTC(),
92
Version: jujuversion.Current,
97
// NewMetadataState composes a new backup metadata with its origin
98
// values set. The model UUID comes from state. The hostname is
99
// retrieved from the OS.
100
func NewMetadataState(db DB, machine, series string) (*Metadata, error) {
101
// hostname could be derived from the model...
102
hostname, err := os.Hostname()
104
// If os.Hostname() is not working, something is woefully wrong.
105
// Run for the hills.
106
return nil, errors.Annotate(err, "could not get hostname (system unstable?)")
109
meta := NewMetadata()
110
meta.Origin.Model = db.ModelTag().Id()
111
meta.Origin.Machine = machine
112
meta.Origin.Hostname = hostname
113
meta.Origin.Series = series
115
si, err := db.StateServingInfo()
117
return nil, errors.Annotate(err, "could not get server secrets")
119
controllerCfg, err := db.ControllerConfig()
121
return nil, errors.Annotate(err, "could not get controller config")
123
meta.CACert, _ = controllerCfg.CACert()
124
meta.CAPrivateKey = si.CAPrivateKey
128
// MarkComplete populates the remaining metadata values. The default
129
// checksum format is used.
130
func (m *Metadata) MarkComplete(size int64, checksum string) error {
132
return errors.New("missing size")
135
return errors.New("missing checksum")
137
format := checksumFormat
138
// TODO(fwereade): 2016-03-17 lp:1558657
139
finished := time.Now().UTC()
141
if err := m.SetFileInfo(size, checksum, format); err != nil {
142
return errors.Annotate(err, "unexpected failure")
144
m.Finished = &finished
149
type flatMetadata struct {
155
ChecksumFormat string
167
Version version.Number
174
// TODO(ericsnow) Move AsJSONBuffer to filestorage.Metadata.
176
// AsJSONBuffer returns a bytes.Buffer containing the JSON-ified metadata.
177
func (m *Metadata) AsJSONBuffer() (io.Reader, error) {
178
flat := flatMetadata{
181
Checksum: m.Checksum(),
182
ChecksumFormat: m.ChecksumFormat(),
187
Environment: m.Origin.Model,
188
Machine: m.Origin.Machine,
189
Hostname: m.Origin.Hostname,
190
Version: m.Origin.Version,
191
Series: m.Origin.Series,
193
CAPrivateKey: m.CAPrivateKey,
198
flat.Stored = *stored
201
if m.Finished != nil {
202
flat.Finished = *m.Finished
205
var outfile bytes.Buffer
206
if err := json.NewEncoder(&outfile).Encode(flat); err != nil {
207
return nil, errors.Trace(err)
212
// NewMetadataJSONReader extracts a new metadata from the JSON file.
213
func NewMetadataJSONReader(in io.Reader) (*Metadata, error) {
214
var flat flatMetadata
215
if err := json.NewDecoder(in).Decode(&flat); err != nil {
216
return nil, errors.Trace(err)
219
meta := NewMetadata()
222
err := meta.SetFileInfo(flat.Size, flat.Checksum, flat.ChecksumFormat)
224
return nil, errors.Trace(err)
227
if !flat.Stored.IsZero() {
228
meta.SetStored(&flat.Stored)
231
meta.Started = flat.Started
232
if !flat.Finished.IsZero() {
233
meta.Finished = &flat.Finished
235
meta.Notes = flat.Notes
236
meta.Origin = Origin{
237
Model: flat.Environment,
238
Machine: flat.Machine,
239
Hostname: flat.Hostname,
240
Version: flat.Version,
244
// TODO(wallyworld) - put these in a separate file.
245
meta.CACert = flat.CACert
246
meta.CAPrivateKey = flat.CAPrivateKey
251
func fileTimestamp(fi os.FileInfo) time.Time {
252
timestamp := creationTime(fi)
253
if !timestamp.IsZero() {
256
// Fall back to modification time.
260
// BuildMetadata generates the metadata for a backup archive file.
261
func BuildMetadata(file *os.File) (*Metadata, error) {
263
// Extract the file size.
264
fi, err := file.Stat()
266
return nil, errors.Trace(err)
270
// Extract the timestamp.
271
timestamp := fileTimestamp(fi)
275
_, err = io.Copy(hasher, file)
277
return nil, errors.Trace(err)
279
rawsum := hasher.Sum(nil)
280
checksum := base64.StdEncoding.EncodeToString(rawsum)
282
// Build the metadata.
283
meta := NewMetadata()
284
meta.Started = time.Time{}
285
meta.Origin = UnknownOrigin()
286
err = meta.MarkComplete(size, checksum)
288
return nil, errors.Trace(err)
290
meta.Finished = ×tamp