github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/model/move/archiver.go (about) 1 package move 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "io" 8 "os" 9 "path" 10 "strconv" 11 "time" 12 13 "github.com/cozy/cozy-stack/model/instance" 14 "github.com/cozy/cozy-stack/pkg/config/config" 15 "github.com/cozy/cozy-stack/pkg/crypto" 16 multierror "github.com/hashicorp/go-multierror" 17 "github.com/ncw/swift/v2" 18 "github.com/spf13/afero" 19 ) 20 21 var ( 22 archiveMaxAge = 7 * 24 * time.Hour 23 archiveMACConfig = crypto.MACConfig{ 24 Name: "exports", 25 MaxAge: archiveMaxAge, 26 MaxLen: 256, 27 } 28 ) 29 30 // Archiver is an interface describing an abstraction for storing archived 31 // data. 32 type Archiver interface { 33 OpenArchive(inst *instance.Instance, exportDoc *ExportDoc) (io.ReadCloser, error) 34 CreateArchive(exportDoc *ExportDoc) (io.WriteCloser, error) 35 RemoveArchives(exportDocs []*ExportDoc) error 36 } 37 38 // SystemArchiver returns the global system archiver, corresponding to the 39 // user's configuration. 40 func SystemArchiver() Archiver { 41 fsURL := config.FsURL() 42 switch fsURL.Scheme { 43 case config.SchemeFile, config.SchemeMem: 44 fs := afero.NewBasePathFs(afero.NewOsFs(), path.Join(fsURL.Path, "exports")) 45 return newAferoArchiver(fs) 46 case config.SchemeSwift, config.SchemeSwiftSecure: 47 return newSwiftArchiver() 48 default: 49 panic(fmt.Errorf("exports: unknown storage provider %s", fsURL.Scheme)) 50 } 51 } 52 53 func newAferoArchiver(fs afero.Fs) Archiver { 54 return aferoArchiver{fs} 55 } 56 57 type aferoArchiver struct { 58 fs afero.Fs 59 } 60 61 func (a aferoArchiver) fileName(exportDoc *ExportDoc) string { 62 return path.Join(exportDoc.Domain, exportDoc.ID()+"tar.gz") 63 } 64 65 func (a aferoArchiver) OpenArchive(inst *instance.Instance, exportDoc *ExportDoc) (io.ReadCloser, error) { 66 return a.fs.Open(a.fileName(exportDoc)) 67 } 68 69 func (a aferoArchiver) CreateArchive(exportDoc *ExportDoc) (io.WriteCloser, error) { 70 f, err := a.fs.OpenFile(a.fileName(exportDoc), os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600) 71 if os.IsNotExist(err) { 72 if err = a.fs.MkdirAll(path.Join("/", exportDoc.Domain), 0700); err == nil { 73 f, err = a.fs.OpenFile(a.fileName(exportDoc), os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600) 74 } 75 } 76 return f, err 77 } 78 79 func (a aferoArchiver) RemoveArchives(exportDocs []*ExportDoc) error { 80 var errm error 81 for _, e := range exportDocs { 82 if err := a.fs.Remove(a.fileName(e)); err != nil { 83 errm = multierror.Append(errm, err) 84 } 85 } 86 return errm 87 } 88 89 func newSwiftArchiver() Archiver { 90 return &switfArchiver{ 91 c: config.GetSwiftConnection(), 92 container: "exports", 93 ctx: context.Background(), 94 } 95 } 96 97 type switfArchiver struct { 98 c *swift.Connection 99 container string 100 ctx context.Context 101 } 102 103 func (a *switfArchiver) init() error { 104 if _, _, err := a.c.Container(a.ctx, a.container); errors.Is(err, swift.ContainerNotFound) { 105 if err = a.c.ContainerCreate(a.ctx, a.container, nil); err != nil { 106 return err 107 } 108 } 109 return nil 110 } 111 112 func (a *switfArchiver) OpenArchive(inst *instance.Instance, exportDoc *ExportDoc) (io.ReadCloser, error) { 113 if err := a.init(); err != nil { 114 return nil, err 115 } 116 objectName := exportDoc.Domain + "/" + exportDoc.ID() 117 f, _, err := a.c.ObjectOpen(a.ctx, a.container, objectName, false, nil) 118 if err != nil { 119 return nil, err 120 } 121 return f, nil 122 } 123 124 func (a *switfArchiver) CreateArchive(exportDoc *ExportDoc) (io.WriteCloser, error) { 125 if err := a.init(); err != nil { 126 return nil, err 127 } 128 objectName := exportDoc.Domain + "/" + exportDoc.ID() 129 objectMeta := swift.Metadata{ 130 "created-at": exportDoc.CreatedAt.Format(time.RFC3339), 131 } 132 headers := objectMeta.ObjectHeaders() 133 headers["X-Delete-At"] = strconv.FormatInt(exportDoc.ExpiresAt.Unix(), 10) 134 return a.c.ObjectCreate(a.ctx, a.container, objectName, true, "", 135 "application/tar+gzip", headers) 136 } 137 138 func (a *switfArchiver) RemoveArchives(exportDocs []*ExportDoc) error { 139 if err := a.init(); err != nil { 140 return err 141 } 142 var objectNames []string 143 for _, e := range exportDocs { 144 objectNames = append(objectNames, e.Domain+"/"+e.ID()) 145 } 146 if len(objectNames) > 0 { 147 _, err := a.c.BulkDelete(a.ctx, a.container, objectNames) 148 return err 149 } 150 return nil 151 }