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  }