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