github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/state/backups/metadata.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package backups 5 6 import ( 7 "bytes" 8 "crypto/sha1" 9 "encoding/base64" 10 "encoding/json" 11 "io" 12 "os" 13 "time" 14 15 "github.com/juju/errors" 16 "github.com/juju/utils/filestorage" 17 "github.com/juju/version" 18 19 jujuversion "github.com/juju/juju/version" 20 ) 21 22 // checksumFormat identifies how to interpret the checksum for a backup 23 // generated with this version of juju. 24 const checksumFormat = "SHA-1, base64 encoded" 25 26 // Origin identifies where a backup archive came from. While it is 27 // more about where and Metadata about what and when, that distinction 28 // does not merit special consideration. Instead, Origin exists 29 // separately from Metadata due to its use as an argument when 30 // requesting the creation of a new backup. 31 type Origin struct { 32 Model string 33 Machine string 34 Hostname string 35 Version version.Number 36 Series string 37 } 38 39 // UnknownString is a marker value for string fields with unknown values. 40 const UnknownString = "<unknown>" 41 42 // UnknownVersion is a marker value for version fields with unknown values. 43 var UnknownVersion = version.MustParse("9999.9999.9999") 44 45 // UnknownOrigin returns a new backups origin with unknown values. 46 func UnknownOrigin() Origin { 47 return Origin{ 48 Model: UnknownString, 49 Machine: UnknownString, 50 Hostname: UnknownString, 51 Version: UnknownVersion, 52 } 53 } 54 55 // Metadata contains the metadata for a single state backup archive. 56 type Metadata struct { 57 *filestorage.FileMetadata 58 59 // Started records when the backup was started. 60 Started time.Time 61 62 // Finished records when the backup was complete. 63 Finished *time.Time 64 65 // Origin identifies where the backup was created. 66 Origin Origin 67 68 // Notes is an optional user-supplied annotation. 69 Notes string 70 71 // TODO(wallyworld) - remove these ASAP 72 // These are only used by the restore CLI when re-bootstrapping. 73 // We will use a better solution but the way restore currently 74 // works, we need them and they are no longer available via 75 // bootstrap config. We will need to ifx how re-bootstrap deals 76 // with these keys to address the issue. 77 78 // CACert is the controller CA certificate. 79 CACert string 80 81 // CAPrivateKey is the controller CA private key. 82 CAPrivateKey string 83 } 84 85 // NewMetadata returns a new Metadata for a state backup archive. Only 86 // the start time and the version are set. 87 func NewMetadata() *Metadata { 88 return &Metadata{ 89 FileMetadata: filestorage.NewMetadata(), 90 // TODO(fwereade): 2016-03-17 lp:1558657 91 Started: time.Now().UTC(), 92 Origin: Origin{ 93 Version: jujuversion.Current, 94 }, 95 } 96 } 97 98 // NewMetadataState composes a new backup metadata with its origin 99 // values set. The model UUID comes from state. The hostname is 100 // retrieved from the OS. 101 func NewMetadataState(db DB, machine, series string) (*Metadata, error) { 102 // hostname could be derived from the model... 103 hostname, err := os.Hostname() 104 if err != nil { 105 // If os.Hostname() is not working, something is woefully wrong. 106 // Run for the hills. 107 return nil, errors.Annotate(err, "could not get hostname (system unstable?)") 108 } 109 110 meta := NewMetadata() 111 meta.Origin.Model = db.ModelTag().Id() 112 meta.Origin.Machine = machine 113 meta.Origin.Hostname = hostname 114 meta.Origin.Series = series 115 116 si, err := db.StateServingInfo() 117 if err != nil { 118 return nil, errors.Annotate(err, "could not get server secrets") 119 } 120 controllerCfg, err := db.ControllerConfig() 121 if err != nil { 122 return nil, errors.Annotate(err, "could not get controller config") 123 } 124 meta.CACert, _ = controllerCfg.CACert() 125 meta.CAPrivateKey = si.CAPrivateKey 126 return meta, nil 127 } 128 129 // MarkComplete populates the remaining metadata values. The default 130 // checksum format is used. 131 func (m *Metadata) MarkComplete(size int64, checksum string) error { 132 if size == 0 { 133 return errors.New("missing size") 134 } 135 if checksum == "" { 136 return errors.New("missing checksum") 137 } 138 format := checksumFormat 139 // TODO(fwereade): 2016-03-17 lp:1558657 140 finished := time.Now().UTC() 141 142 if err := m.SetFileInfo(size, checksum, format); err != nil { 143 return errors.Annotate(err, "unexpected failure") 144 } 145 m.Finished = &finished 146 147 return nil 148 } 149 150 type flatMetadata struct { 151 ID string 152 153 // file storage 154 155 Checksum string 156 ChecksumFormat string 157 Size int64 158 Stored time.Time 159 160 // backup 161 162 Started time.Time 163 Finished time.Time 164 Notes string 165 Environment string 166 Machine string 167 Hostname string 168 Version version.Number 169 Series string 170 171 CACert string 172 CAPrivateKey string 173 } 174 175 // TODO(ericsnow) Move AsJSONBuffer to filestorage.Metadata. 176 177 // AsJSONBuffer returns a bytes.Buffer containing the JSON-ified metadata. 178 func (m *Metadata) AsJSONBuffer() (io.Reader, error) { 179 flat := flatMetadata{ 180 ID: m.ID(), 181 182 Checksum: m.Checksum(), 183 ChecksumFormat: m.ChecksumFormat(), 184 Size: m.Size(), 185 186 Started: m.Started, 187 Notes: m.Notes, 188 Environment: m.Origin.Model, 189 Machine: m.Origin.Machine, 190 Hostname: m.Origin.Hostname, 191 Version: m.Origin.Version, 192 Series: m.Origin.Series, 193 CACert: m.CACert, 194 CAPrivateKey: m.CAPrivateKey, 195 } 196 197 stored := m.Stored() 198 if stored != nil { 199 flat.Stored = *stored 200 } 201 202 if m.Finished != nil { 203 flat.Finished = *m.Finished 204 } 205 206 var outfile bytes.Buffer 207 if err := json.NewEncoder(&outfile).Encode(flat); err != nil { 208 return nil, errors.Trace(err) 209 } 210 return &outfile, nil 211 } 212 213 // NewMetadataJSONReader extracts a new metadata from the JSON file. 214 func NewMetadataJSONReader(in io.Reader) (*Metadata, error) { 215 var flat flatMetadata 216 if err := json.NewDecoder(in).Decode(&flat); err != nil { 217 return nil, errors.Trace(err) 218 } 219 220 meta := NewMetadata() 221 meta.SetID(flat.ID) 222 223 err := meta.SetFileInfo(flat.Size, flat.Checksum, flat.ChecksumFormat) 224 if err != nil { 225 return nil, errors.Trace(err) 226 } 227 228 if !flat.Stored.IsZero() { 229 meta.SetStored(&flat.Stored) 230 } 231 232 meta.Started = flat.Started 233 if !flat.Finished.IsZero() { 234 meta.Finished = &flat.Finished 235 } 236 meta.Notes = flat.Notes 237 meta.Origin = Origin{ 238 Model: flat.Environment, 239 Machine: flat.Machine, 240 Hostname: flat.Hostname, 241 Version: flat.Version, 242 Series: flat.Series, 243 } 244 245 // TODO(wallyworld) - put these in a separate file. 246 meta.CACert = flat.CACert 247 meta.CAPrivateKey = flat.CAPrivateKey 248 249 return meta, nil 250 } 251 252 func fileTimestamp(fi os.FileInfo) time.Time { 253 timestamp := creationTime(fi) 254 if !timestamp.IsZero() { 255 return timestamp 256 } 257 // Fall back to modification time. 258 return fi.ModTime() 259 } 260 261 // BuildMetadata generates the metadata for a backup archive file. 262 func BuildMetadata(file *os.File) (*Metadata, error) { 263 264 // Extract the file size. 265 fi, err := file.Stat() 266 if err != nil { 267 return nil, errors.Trace(err) 268 } 269 size := fi.Size() 270 271 // Extract the timestamp. 272 timestamp := fileTimestamp(fi) 273 274 // Get the checksum. 275 hasher := sha1.New() 276 _, err = io.Copy(hasher, file) 277 if err != nil { 278 return nil, errors.Trace(err) 279 } 280 rawsum := hasher.Sum(nil) 281 checksum := base64.StdEncoding.EncodeToString(rawsum) 282 283 // Build the metadata. 284 meta := NewMetadata() 285 meta.Started = time.Time{} 286 meta.Origin = UnknownOrigin() 287 err = meta.MarkComplete(size, checksum) 288 if err != nil { 289 return nil, errors.Trace(err) 290 } 291 meta.Finished = ×tamp 292 return meta, nil 293 }