github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/model/app/apps.go (about) 1 package app 2 3 import ( 4 "errors" 5 "fmt" 6 "io" 7 "net/url" 8 "path" 9 "time" 10 11 "github.com/cozy/cozy-stack/model/instance" 12 "github.com/cozy/cozy-stack/model/permission" 13 "github.com/cozy/cozy-stack/model/vfs" 14 "github.com/cozy/cozy-stack/model/vfs/vfsafero" 15 "github.com/cozy/cozy-stack/pkg/appfs" 16 "github.com/cozy/cozy-stack/pkg/config/config" 17 "github.com/cozy/cozy-stack/pkg/consts" 18 "github.com/cozy/cozy-stack/pkg/couchdb" 19 "github.com/cozy/cozy-stack/pkg/prefixer" 20 "github.com/spf13/afero" 21 ) 22 23 const ( 24 // ManifestMaxSize is the manifest maximum size 25 ManifestMaxSize = 2 << (2 * 10) // 2MB 26 // WebappManifestName is the name of the manifest at the root of the 27 // client-side application directory 28 WebappManifestName = "manifest.webapp" 29 // KonnectorManifestName is the name of the manifest at the root of the 30 // konnector application directory 31 KonnectorManifestName = "manifest.konnector" 32 ) 33 34 // State is the state of the application 35 type State string 36 37 const ( 38 // Installing state 39 Installing = "installing" 40 // Upgrading state 41 Upgrading = "upgrading" 42 // Installed state, can be used to state that an application has been 43 // installed but needs a user interaction to be activated and "ready". 44 Installed = "installed" 45 // Ready state 46 Ready = "ready" 47 // Errored state 48 Errored = "errored" 49 ) 50 51 // KonnectorArchiveName is the name of the archive created to store the 52 // konnectors sources. 53 const KonnectorArchiveName = "app.tar" 54 55 var ( 56 ErrInvalidAppType = errors.New("invalid app type") 57 ) 58 59 // SubDomainer is an interface with a single method to build an URL from a slug 60 type SubDomainer interface { 61 SubDomain(s string) *url.URL 62 } 63 64 // Manifest interface is used by installer to encapsulate the manifest metadata 65 // that can represent either a webapp or konnector manifest 66 type Manifest interface { 67 couchdb.Doc 68 Fetch(field string) []string 69 ReadManifest(i io.Reader, slug, sourceURL string) (Manifest, error) 70 71 Create(db prefixer.Prefixer) error 72 Update(db prefixer.Prefixer, extraPerms permission.Set) error 73 Delete(db prefixer.Prefixer) error 74 75 AppType() consts.AppType 76 Permissions() permission.Set 77 Source() string 78 Version() string 79 AvailableVersion() string 80 Checksum() string 81 Slug() string 82 State() State 83 LastUpdate() time.Time 84 Terms() Terms 85 86 Name() string 87 Icon() string 88 Notifications() Notifications 89 90 SetError(err error) 91 Error() error 92 93 SetSlug(slug string) 94 SetSource(src *url.URL) 95 SetState(state State) 96 SetVersion(version string) 97 SetAvailableVersion(version string) 98 SetChecksum(shasum string) 99 } 100 101 // GetBySlug returns an app manifest identified by its slug 102 func GetBySlug(db prefixer.Prefixer, slug string, appType consts.AppType) (Manifest, error) { 103 var man Manifest 104 var err error 105 switch appType { 106 case consts.WebappType: 107 man, err = GetWebappBySlug(db, slug) 108 case consts.KonnectorType: 109 man, err = GetKonnectorBySlug(db, slug) 110 default: 111 return nil, fmt.Errorf("%s, %w", appType, ErrInvalidAppType) 112 } 113 if err != nil { 114 return nil, err 115 } 116 return man, nil 117 } 118 119 func routeMatches(path, ctx []string) bool { 120 for i, part := range ctx { 121 if path[i] != part { 122 return false 123 } 124 } 125 return true 126 } 127 128 // UpgradeInstalledState is used to force the legacy "installed" state to 129 // "ready" for a webapp or a konnector manifest 130 func UpgradeInstalledState(inst *instance.Instance, man Manifest) error { 131 if man.State() == Installed { 132 man.SetState(Ready) 133 return man.Update(inst, nil) 134 } 135 return nil 136 } 137 138 // Copier returns the application copier associated with the specified 139 // application type 140 func Copier(appsType consts.AppType, inst *instance.Instance) appfs.Copier { 141 fsURL := config.FsURL() 142 switch fsURL.Scheme { 143 case config.SchemeFile: 144 var baseDirName string 145 switch appsType { 146 case consts.WebappType: 147 baseDirName = vfs.WebappsDirName 148 case consts.KonnectorType: 149 baseDirName = vfs.KonnectorsDirName 150 } 151 baseFS := afero.NewBasePathFs(afero.NewOsFs(), 152 path.Join(fsURL.Path, inst.DirName(), baseDirName)) 153 return appfs.NewAferoCopier(baseFS) 154 case config.SchemeMem: 155 baseFS := vfsafero.GetMemFS("apps") 156 return appfs.NewAferoCopier(baseFS) 157 case config.SchemeSwift, config.SchemeSwiftSecure: 158 return appfs.NewSwiftCopier(config.GetSwiftConnection(), appsType) 159 default: 160 panic(fmt.Sprintf("instance: unknown storage provider %s", fsURL.Scheme)) 161 } 162 } 163 164 // AppsFileServer returns the web-application file server associated to this 165 // instance. 166 func AppsFileServer(i *instance.Instance) appfs.FileServer { 167 fsURL := config.FsURL() 168 switch fsURL.Scheme { 169 case config.SchemeFile: 170 baseFS := afero.NewBasePathFs(afero.NewOsFs(), 171 path.Join(fsURL.Path, i.DirName(), vfs.WebappsDirName)) 172 return appfs.NewAferoFileServer(baseFS, nil) 173 case config.SchemeMem: 174 baseFS := vfsafero.GetMemFS("apps") 175 return appfs.NewAferoFileServer(baseFS, nil) 176 case config.SchemeSwift, config.SchemeSwiftSecure: 177 return appfs.NewSwiftFileServer(config.GetSwiftConnection(), consts.WebappType) 178 default: 179 panic(fmt.Sprintf("instance: unknown storage provider %s", fsURL.Scheme)) 180 } 181 } 182 183 // KonnectorsFileServer returns the web-application file server associated to this 184 // instance. 185 func KonnectorsFileServer(i *instance.Instance) appfs.FileServer { 186 fsURL := config.FsURL() 187 switch fsURL.Scheme { 188 case config.SchemeFile: 189 baseFS := afero.NewBasePathFs(afero.NewOsFs(), 190 path.Join(fsURL.Path, i.DirName(), vfs.KonnectorsDirName)) 191 return appfs.NewAferoFileServer(baseFS, nil) 192 case config.SchemeMem: 193 baseFS := vfsafero.GetMemFS("apps") 194 return appfs.NewAferoFileServer(baseFS, nil) 195 case config.SchemeSwift, config.SchemeSwiftSecure: 196 return appfs.NewSwiftFileServer(config.GetSwiftConnection(), consts.KonnectorType) 197 default: 198 panic(fmt.Sprintf("instance: unknown storage provider %s", fsURL.Scheme)) 199 } 200 }