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 = &timestamp
   292  	return meta, nil
   293  }