github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/state/backups/files.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  	"fmt"
     8  	"os"
     9  	"path/filepath"
    10  	"sort"
    11  
    12  	"github.com/juju/errors"
    13  )
    14  
    15  // TODO(ericsnow) lp-1392876
    16  // Pull these from authoritative sources (see
    17  // github.com/juju/juju/juju/paths, etc.):
    18  const (
    19  	dataDir = "/var/lib/juju"
    20  	logsDir = "/var/log/juju"
    21  	sshDir  = "/home/ubuntu/.ssh"
    22  
    23  	agentsDir   = "agents"
    24  	agentsConfs = "machine-*"
    25  	toolsDir    = "tools"
    26  
    27  	sshIdentFile = "system-identity"
    28  	nonceFile    = "nonce.txt"
    29  	machineLog   = "machine-%s.log"
    30  	authKeysFile = "authorized_keys"
    31  
    32  	dbPEM    = "server.pem"
    33  	dbSecret = "shared-secret"
    34  )
    35  
    36  // Paths holds the paths that backups needs.
    37  type Paths struct {
    38  	DataDir string
    39  	LogsDir string
    40  }
    41  
    42  // GetFilesToBackUp returns the paths that should be included in the
    43  // backup archive.
    44  func GetFilesToBackUp(rootDir string, paths *Paths, oldmachine string) ([]string, error) {
    45  	var glob string
    46  
    47  	glob = filepath.Join(rootDir, paths.DataDir, agentsDir, agentsConfs)
    48  	agentConfs, err := filepath.Glob(glob)
    49  	if err != nil {
    50  		return nil, errors.Annotate(err, "failed to fetch agent config files")
    51  	}
    52  
    53  	backupFiles := []string{
    54  		filepath.Join(rootDir, paths.DataDir, toolsDir),
    55  
    56  		filepath.Join(rootDir, paths.DataDir, sshIdentFile),
    57  
    58  		filepath.Join(rootDir, paths.DataDir, dbPEM),
    59  		filepath.Join(rootDir, paths.DataDir, dbSecret),
    60  	}
    61  	backupFiles = append(backupFiles, agentConfs...)
    62  
    63  	// TODO(ericsnow) It might not be machine 0...
    64  	machinelog := filepath.Join(rootDir, paths.LogsDir, fmt.Sprintf(machineLog, oldmachine))
    65  	if _, err := os.Stat(machinelog); err != nil {
    66  		if !os.IsNotExist(err) {
    67  			return nil, errors.Trace(err)
    68  		}
    69  		logger.Errorf("skipping missing file %q", machinelog)
    70  	} else {
    71  		backupFiles = append(backupFiles, machinelog)
    72  	}
    73  
    74  	// Handle nonce.txt (might not exist).
    75  	nonce := filepath.Join(rootDir, paths.DataDir, nonceFile)
    76  	if _, err := os.Stat(nonce); err != nil {
    77  		if !os.IsNotExist(err) {
    78  			return nil, errors.Trace(err)
    79  		}
    80  		logger.Errorf("skipping missing file %q", nonce)
    81  	} else {
    82  		backupFiles = append(backupFiles, nonce)
    83  	}
    84  
    85  	// Handle user SSH files (might not exist).
    86  	SSHDir := filepath.Join(rootDir, sshDir)
    87  	if _, err := os.Stat(SSHDir); err != nil {
    88  		if !os.IsNotExist(err) {
    89  			return nil, errors.Trace(err)
    90  		}
    91  		logger.Errorf("skipping missing dir %q", SSHDir)
    92  	} else {
    93  		backupFiles = append(backupFiles, filepath.Join(SSHDir, authKeysFile))
    94  	}
    95  
    96  	return backupFiles, nil
    97  }
    98  
    99  // replaceableFolders for testing purposes.
   100  var replaceableFolders = replaceableFoldersFunc
   101  
   102  // replaceableFoldersFunc will return a map with the folders that need to
   103  // be replaced so they can be deleted prior to a restore.
   104  func replaceableFoldersFunc() (map[string]os.FileMode, error) {
   105  	replaceables := map[string]os.FileMode{}
   106  
   107  	for _, replaceable := range []string{
   108  		filepath.Join(dataDir, "db"),
   109  		dataDir,
   110  		logsDir,
   111  	} {
   112  		dirStat, err := os.Stat(replaceable)
   113  		if err != nil {
   114  			return map[string]os.FileMode{}, errors.Annotatef(err, "cannot stat %q", replaceable)
   115  		}
   116  		replaceables[replaceable] = dirStat.Mode()
   117  	}
   118  	return replaceables, nil
   119  }
   120  
   121  // TODO (perrito666) make this version sensitive when these files change, it would
   122  // also be a good idea to save these instead of deleting them.
   123  
   124  // PrepareMachineForRestore deletes all files from the re-bootstrapped
   125  // machine that are to be replaced by the backup and recreates those
   126  // directories that are to contain new files; this is to avoid
   127  // possible mixup from new/old files that lead to an inconsistent
   128  // restored state machine.
   129  func PrepareMachineForRestore() error {
   130  	replaceFolders, err := replaceableFolders()
   131  	if err != nil {
   132  		return errors.Annotate(err, "cannot retrieve the list of folders to be cleaned before restore")
   133  	}
   134  	var keys []string
   135  	for k := range replaceFolders {
   136  		keys = append(keys, k)
   137  	}
   138  	// sort to avoid trying to create subfolders before folders.
   139  	sort.Strings(keys)
   140  	for _, toBeRecreated := range keys {
   141  		fmode := replaceFolders[toBeRecreated]
   142  		_, err := os.Stat(toBeRecreated)
   143  		if err != nil && !os.IsNotExist(err) {
   144  			return errors.Trace(err)
   145  		}
   146  		if !fmode.IsDir() {
   147  			continue
   148  		}
   149  		if err := os.RemoveAll(toBeRecreated); err != nil {
   150  			return errors.Trace(err)
   151  		}
   152  		if err := os.MkdirAll(toBeRecreated, fmode); err != nil {
   153  			return errors.Trace(err)
   154  		}
   155  	}
   156  	return nil
   157  }