github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/backups/testing/file.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package testing 5 6 import ( 7 "archive/tar" 8 "bytes" 9 "compress/gzip" 10 "encoding/json" 11 "io" 12 "path" 13 "strings" 14 "time" 15 16 "github.com/juju/collections/set" 17 "github.com/juju/errors" 18 "github.com/juju/version/v2" 19 20 "github.com/juju/juju/state/backups" 21 ) 22 23 // File represents a file during testing. 24 type File struct { 25 // Name is the path to which the file will be identified in the archive. 26 Name string 27 // Content is the data that will be written to the archive for the file. 28 Content string 29 // IsDir determines if the file is a regular file or a directory. 30 IsDir bool 31 } 32 33 // AddToArchive adds the file to the tar archive. 34 func (f *File) AddToArchive(archive *tar.Writer) error { 35 hdr := &tar.Header{ 36 Name: f.Name, 37 } 38 if f.IsDir { 39 hdr.Typeflag = tar.TypeDir 40 hdr.Mode = 0777 41 } else { 42 hdr.Size = int64(len(f.Content)) 43 hdr.Mode = 0666 44 } 45 46 if err := archive.WriteHeader(hdr); err != nil { 47 return errors.Trace(err) 48 } 49 50 if !f.IsDir { 51 if _, err := archive.Write([]byte(f.Content)); err != nil { 52 return errors.Trace(err) 53 } 54 } 55 56 return nil 57 } 58 59 // NewArchive returns a new archive file containing the files. 60 func NewArchive(meta *backups.Metadata, files, dump []File) (*bytes.Buffer, error) { 61 topFiles, err := internalTopFiles(files, dump) 62 if err != nil { 63 return nil, errors.Trace(err) 64 } 65 66 if meta != nil { 67 metaFile, err := meta.AsJSONBuffer() 68 if err != nil { 69 return nil, errors.Trace(err) 70 } 71 topFiles = append(topFiles, 72 File{ 73 Name: "juju-backup/metadata.json", 74 Content: metaFile.(*bytes.Buffer).String(), 75 }, 76 ) 77 } 78 return internalCompress(topFiles) 79 } 80 81 func internalTopFiles(files, dump []File) ([]File, error) { 82 dirs := set.NewStrings() 83 var sysFiles []File 84 for _, file := range files { 85 var parent string 86 for _, p := range strings.Split(path.Dir(file.Name), "/") { 87 if parent == "" { 88 parent = p 89 } else { 90 parent = path.Join(parent, p) 91 } 92 if !dirs.Contains(parent) { 93 sysFiles = append(sysFiles, File{ 94 Name: parent, 95 IsDir: true, 96 }) 97 dirs.Add(parent) 98 } 99 } 100 if file.IsDir { 101 if !dirs.Contains(file.Name) { 102 sysFiles = append(sysFiles, file) 103 dirs.Add(file.Name) 104 } 105 } else { 106 sysFiles = append(sysFiles, file) 107 } 108 } 109 110 var rootFile bytes.Buffer 111 if err := writeToTar(&rootFile, sysFiles); err != nil { 112 return nil, errors.Trace(err) 113 } 114 115 topFiles := []File{{ 116 Name: "juju-backup", 117 IsDir: true, 118 }} 119 120 topFiles = append(topFiles, File{ 121 Name: "juju-backup/dump", 122 IsDir: true, 123 }) 124 for _, dumpFile := range dump { 125 topFiles = append(topFiles, File{ 126 Name: "juju-backup/dump/" + dumpFile.Name, 127 Content: dumpFile.Content, 128 IsDir: dumpFile.IsDir, 129 }) 130 } 131 132 topFiles = append(topFiles, 133 File{ 134 Name: "juju-backup/root.tar", 135 Content: rootFile.String(), 136 }, 137 ) 138 return topFiles, nil 139 } 140 141 // NewArchiveV0 returns a new archive file containing the files, in v0 format. 142 func NewArchiveV0(meta *backups.Metadata, files, dump []File) (*bytes.Buffer, error) { 143 topFiles, err := internalTopFiles(files, dump) 144 if err != nil { 145 return nil, errors.Trace(err) 146 } 147 if meta != nil { 148 metaFile, err := asJSONBufferV0(meta) 149 if err != nil { 150 return nil, errors.Trace(err) 151 } 152 topFiles = append(topFiles, 153 File{ 154 Name: "juju-backup/metadata.json", 155 Content: metaFile.(*bytes.Buffer).String(), 156 }, 157 ) 158 } 159 return internalCompress(topFiles) 160 } 161 162 func internalCompress(topFiles []File) (*bytes.Buffer, error) { 163 var arFile bytes.Buffer 164 compressed := gzip.NewWriter(&arFile) 165 defer compressed.Close() 166 if err := writeToTar(compressed, topFiles); err != nil { 167 return nil, errors.Trace(err) 168 } 169 return &arFile, nil 170 } 171 172 func asJSONBufferV0(m *backups.Metadata) (io.Reader, error) { 173 var outfile bytes.Buffer 174 if err := json.NewEncoder(&outfile).Encode(flatV0(m)); err != nil { 175 return nil, errors.Trace(err) 176 } 177 return &outfile, nil 178 } 179 180 type flatMetadataV0 struct { 181 ID string 182 183 // file storage 184 185 Checksum string 186 ChecksumFormat string 187 Size int64 188 Stored time.Time 189 190 // backup 191 192 Started time.Time 193 Finished time.Time 194 Notes string 195 Environment string 196 Machine string 197 Hostname string 198 Version version.Number 199 Base string 200 } 201 202 func flatV0(m *backups.Metadata) flatMetadataV0 { 203 flat := flatMetadataV0{ 204 ID: m.ID(), 205 Checksum: m.Checksum(), 206 ChecksumFormat: m.ChecksumFormat(), 207 Size: m.Size(), 208 Started: m.Started, 209 Notes: m.Notes, 210 Environment: m.Origin.Model, 211 Machine: m.Origin.Machine, 212 Hostname: m.Origin.Hostname, 213 Version: m.Origin.Version, 214 Base: m.Origin.Base, 215 } 216 stored := m.Stored() 217 if stored != nil { 218 flat.Stored = *stored 219 } 220 221 if m.Finished != nil { 222 flat.Finished = *m.Finished 223 } 224 return flat 225 } 226 227 // NewArchiveBasic returns a new archive file with a few files provided. 228 func NewArchiveBasic(meta *backups.Metadata) (*bytes.Buffer, error) { 229 files := []File{ 230 { 231 Name: "var/lib/juju/tools/1.21-alpha2.1-trusty-amd64/jujud", 232 Content: "<some binary data goes here>", 233 }, 234 { 235 Name: "var/lib/juju/system-identity", 236 Content: "<an ssh key goes here>", 237 }, 238 } 239 dump := []File{ 240 { 241 Name: "juju/machines.bson", 242 Content: "<BSON data goes here>", 243 }, 244 { 245 Name: "oplog.bson", 246 Content: "<BSON data goes here>", 247 }, 248 } 249 250 arFile, err := NewArchive(meta, files, dump) 251 if err != nil { 252 return nil, errors.Trace(err) 253 } 254 return arFile, nil 255 } 256 257 func writeToTar(archive io.Writer, files []File) error { 258 tarw := tar.NewWriter(archive) 259 defer tarw.Close() 260 261 for _, file := range files { 262 if err := file.AddToArchive(tarw); err != nil { 263 return errors.Trace(err) 264 } 265 } 266 return nil 267 }