Replace dataroot module with BinDir and DataDir on instance, adapt modules
This commit is contained in:
@@ -55,7 +55,7 @@ func (d *Downloader) updateIndex(ctx context.Context) error {
|
||||
break
|
||||
}
|
||||
|
||||
log.Warningf("updates: failed to update index from %q: %s", url, err)
|
||||
log.Warningf("updates/%s: failed to update index from %q: %s", d.u.cfg.Name, url, err)
|
||||
err = fmt.Errorf("update index file from %q: %s", url, err)
|
||||
}
|
||||
if err != nil {
|
||||
@@ -111,7 +111,7 @@ func (d *Downloader) gatherExistingFiles(dir string) error {
|
||||
// Read full file.
|
||||
fileData, err := os.ReadFile(fullpath)
|
||||
if err != nil {
|
||||
log.Debugf("updates: failed to read file %q while searching for existing files: %w", fullpath, err)
|
||||
log.Debugf("updates/%s: failed to read file %q while searching for existing files: %w", d.u.cfg.Name, fullpath, err)
|
||||
return fmt.Errorf("failed to read file %s: %w", fullpath, err)
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ artifacts:
|
||||
if err == nil {
|
||||
continue artifacts
|
||||
}
|
||||
log.Debugf("updates: failed to copy existing file %s: %w", artifact.Filename, err)
|
||||
log.Debugf("updates/%s: failed to copy existing file %s: %w", d.u.cfg.Name, artifact.Filename, err)
|
||||
}
|
||||
|
||||
// Try to download the artifact from one of the URLs.
|
||||
@@ -182,7 +182,7 @@ artifacts:
|
||||
return fmt.Errorf("rename %s after write: %w", artifact.Filename, err)
|
||||
}
|
||||
|
||||
log.Infof("updates: downloaded and verified %s", artifact.Filename)
|
||||
log.Infof("updates/%s: downloaded and verified %s", d.u.cfg.Name, artifact.Filename)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -35,7 +35,8 @@ type Artifact struct {
|
||||
Unpack string `json:"Unpack,omitempty"`
|
||||
Version string `json:"Version,omitempty"`
|
||||
|
||||
localFile string
|
||||
localFile string
|
||||
versionNum *semver.Version
|
||||
}
|
||||
|
||||
// GetFileMode returns the required filesystem permission for the artifact.
|
||||
@@ -52,6 +53,67 @@ func (a *Artifact) GetFileMode() os.FileMode {
|
||||
return defaultFileMode
|
||||
}
|
||||
|
||||
// Path returns the absolute path to the local file.
|
||||
func (a *Artifact) Path() string {
|
||||
return a.localFile
|
||||
}
|
||||
|
||||
// SemVer returns the version of the artifact.
|
||||
func (a *Artifact) SemVer() *semver.Version {
|
||||
return a.versionNum
|
||||
}
|
||||
|
||||
// IsNewerThan returns whether the artifact is newer than the given artifact.
|
||||
// Returns true if the given artifact is nil.
|
||||
// The second return value "ok" is false when version could not be compared.
|
||||
// In this case, it is up to the caller to decide how to proceed.
|
||||
func (a *Artifact) IsNewerThan(b *Artifact) (newer, ok bool) {
|
||||
switch {
|
||||
case a == nil:
|
||||
return false, false
|
||||
case b == nil:
|
||||
return true, true
|
||||
case a.versionNum == nil:
|
||||
return false, false
|
||||
case b.versionNum == nil:
|
||||
return false, false
|
||||
case a.versionNum.GreaterThan(b.versionNum):
|
||||
return true, true
|
||||
default:
|
||||
return false, true
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Artifact) export(dir string, indexVersion *semver.Version) *Artifact {
|
||||
copy := &Artifact{
|
||||
Filename: a.Filename,
|
||||
SHA256: a.SHA256,
|
||||
URLs: a.URLs,
|
||||
Platform: a.Platform,
|
||||
Unpack: a.Unpack,
|
||||
Version: a.Version,
|
||||
localFile: filepath.Join(dir, a.Filename),
|
||||
versionNum: a.versionNum,
|
||||
}
|
||||
|
||||
// Make sure we have a version number.
|
||||
switch {
|
||||
case copy.versionNum != nil:
|
||||
// Version already parsed.
|
||||
case copy.Version != "":
|
||||
// Need to parse version.
|
||||
v, err := semver.NewVersion(copy.Version)
|
||||
if err == nil {
|
||||
copy.versionNum = v
|
||||
}
|
||||
default:
|
||||
// No version defined, inherit index version.
|
||||
copy.versionNum = indexVersion
|
||||
}
|
||||
|
||||
return copy
|
||||
}
|
||||
|
||||
// Index represents a collection of artifacts with metadata.
|
||||
type Index struct {
|
||||
Name string `json:"Name"`
|
||||
@@ -90,16 +152,26 @@ func ParseIndex(jsonContent []byte, trustStore jess.TrustStore) (*Index, error)
|
||||
return nil, fmt.Errorf("parse index: %w", err)
|
||||
}
|
||||
|
||||
// Initialize data.
|
||||
err = index.init()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &index, nil
|
||||
}
|
||||
|
||||
func (index *Index) init() error {
|
||||
// Parse version number, if set.
|
||||
if index.Version != "" {
|
||||
versionNum, err := semver.NewVersion(index.Version)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid index version %q: %w", index.Version, err)
|
||||
return fmt.Errorf("invalid index version %q: %w", index.Version, err)
|
||||
}
|
||||
index.versionNum = versionNum
|
||||
}
|
||||
|
||||
// Filter artifacts by currnet platform.
|
||||
// Filter artifacts by current platform.
|
||||
filtered := make([]Artifact, 0)
|
||||
for _, a := range index.Artifacts {
|
||||
if a.Platform == "" || a.Platform == currentPlatform {
|
||||
@@ -108,7 +180,19 @@ func ParseIndex(jsonContent []byte, trustStore jess.TrustStore) (*Index, error)
|
||||
}
|
||||
index.Artifacts = filtered
|
||||
|
||||
return &index, nil
|
||||
// Parse artifact version numbers.
|
||||
for _, a := range index.Artifacts {
|
||||
if a.Version != "" {
|
||||
v, err := semver.NewVersion(a.Version)
|
||||
if err == nil {
|
||||
a.versionNum = v
|
||||
}
|
||||
} else {
|
||||
a.versionNum = index.versionNum
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CanDoUpgrades returns whether the index is able to follow a secure upgrade path.
|
||||
|
||||
@@ -45,6 +45,8 @@ var (
|
||||
|
||||
// Config holds the configuration for the updates module.
|
||||
type Config struct {
|
||||
// Name of the updater.
|
||||
Name string
|
||||
// Directory is the main directory where the currently to-be-used artifacts live.
|
||||
Directory string
|
||||
// DownloadDirectory is the directory where new artifacts are downloaded to and prepared for upgrading.
|
||||
@@ -80,6 +82,8 @@ type Config struct {
|
||||
func (cfg *Config) Check() error {
|
||||
// Check if required fields are set.
|
||||
switch {
|
||||
case cfg.Name == "":
|
||||
return errors.New("name must be set")
|
||||
case cfg.Directory == "":
|
||||
return errors.New("directory must be set")
|
||||
case cfg.DownloadDirectory == "":
|
||||
@@ -157,19 +161,22 @@ func New(instance instance, name string, cfg Config) (*Updater, error) {
|
||||
|
||||
// Load index.
|
||||
index, err := LoadIndex(filepath.Join(cfg.Directory, cfg.IndexFile), cfg.Verify)
|
||||
if err != nil {
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
log.Errorf("updates: invalid index file, falling back to dir scan: %w", err)
|
||||
}
|
||||
|
||||
// Fall back to scanning the directory.
|
||||
index, err = GenerateIndexFromDir(cfg.Directory, IndexScanConfig{Version: "0.0.0"})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("updates index load and dir scan failed: %w", err)
|
||||
}
|
||||
if err == nil {
|
||||
module.index = index
|
||||
return module, nil
|
||||
}
|
||||
module.index = index
|
||||
|
||||
// Fall back to scanning the directory.
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
log.Errorf("updates/%s: invalid index file, falling back to dir scan: %w", cfg.Name, err)
|
||||
}
|
||||
index, err = GenerateIndexFromDir(cfg.Directory, IndexScanConfig{Version: "0.0.0"})
|
||||
if err == nil && index.init() == nil {
|
||||
module.index = index
|
||||
return module, nil
|
||||
}
|
||||
|
||||
// Fall back to empty index.
|
||||
return module, nil
|
||||
}
|
||||
|
||||
@@ -207,7 +214,7 @@ func (u *Updater) updateAndUpgrade(w *mgr.WorkerCtx, indexURLs []string, ignoreV
|
||||
u.indexLock.Unlock()
|
||||
// Check with local pointer to index.
|
||||
if err := index.ShouldUpgradeTo(downloader.index); err != nil {
|
||||
log.Infof("updates: no new or eligible update: %s", err)
|
||||
log.Infof("updates/%s: no new or eligible update: %s", u.cfg.Name, err)
|
||||
if u.cfg.Notify && u.instance.Notifications() != nil {
|
||||
u.instance.Notifications().NotifyInfo(
|
||||
noNewUpdateNotificationID,
|
||||
@@ -247,12 +254,12 @@ func (u *Updater) updateAndUpgrade(w *mgr.WorkerCtx, indexURLs []string, ignoreV
|
||||
|
||||
// Download any remaining needed files.
|
||||
// If everything is already found in the download directory, then this is a no-op.
|
||||
log.Infof("updates: downloading new version: %s %s", downloader.index.Name, downloader.index.Version)
|
||||
log.Infof("updates/%s: downloading new version: %s %s", u.cfg.Name, downloader.index.Name, downloader.index.Version)
|
||||
err = downloader.downloadArtifacts(w.Ctx())
|
||||
if err != nil {
|
||||
log.Errorf("updates: failed to download update: %s", err)
|
||||
log.Errorf("updates/%s: failed to download update: %s", u.cfg.Name, err)
|
||||
if err := u.deleteUnfinishedFiles(u.cfg.DownloadDirectory); err != nil {
|
||||
log.Debugf("updates: failed to delete unfinished files in download directory %s", u.cfg.DownloadDirectory)
|
||||
log.Debugf("updates/%s: failed to delete unfinished files in download directory %s", u.cfg.Name, u.cfg.DownloadDirectory)
|
||||
}
|
||||
return fmt.Errorf("downloading failed: %w", err)
|
||||
}
|
||||
@@ -282,7 +289,7 @@ func (u *Updater) updateAndUpgrade(w *mgr.WorkerCtx, indexURLs []string, ignoreV
|
||||
err = u.upgrade(downloader, ignoreVersion)
|
||||
if err != nil {
|
||||
if err := u.deleteUnfinishedFiles(u.cfg.PurgeDirectory); err != nil {
|
||||
log.Debugf("updates: failed to delete unfinished files in purge directory %s", u.cfg.PurgeDirectory)
|
||||
log.Debugf("updates/%s: failed to delete unfinished files in purge directory %s", u.cfg.Name, u.cfg.PurgeDirectory)
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -334,6 +341,14 @@ func (u *Updater) upgradeWorker(w *mgr.WorkerCtx) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ForceUpdate executes a forced update and upgrade directly and synchronously
|
||||
// and is intended to be used only within a tool, not a service.
|
||||
func (u *Updater) ForceUpdate() error {
|
||||
return u.m.Do("update and upgrade", func(w *mgr.WorkerCtx) error {
|
||||
return u.updateAndUpgrade(w, u.cfg.IndexURLs, true, true)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateFromURL installs an update from the provided url.
|
||||
func (u *Updater) UpdateFromURL(url string) error {
|
||||
u.m.Go("custom update from url", func(w *mgr.WorkerCtx) error {
|
||||
@@ -383,10 +398,15 @@ func (u *Updater) GetMainDir() string {
|
||||
}
|
||||
|
||||
// GetFile returns the path of a file given the name. Returns ErrNotFound if file is not found.
|
||||
func (u *Updater) GetFile(name string) (string, error) {
|
||||
func (u *Updater) GetFile(name string) (*Artifact, error) {
|
||||
u.indexLock.Lock()
|
||||
defer u.indexLock.Unlock()
|
||||
|
||||
// Check if any index is active.
|
||||
if u.index == nil {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
|
||||
for _, artifact := range u.index.Artifacts {
|
||||
switch {
|
||||
case artifact.Filename != name:
|
||||
@@ -396,11 +416,11 @@ func (u *Updater) GetFile(name string) (string, error) {
|
||||
// Platforms are usually pre-filtered, but just to be sure.
|
||||
default:
|
||||
// Artifact matches!
|
||||
return filepath.Join(u.cfg.Directory, artifact.Filename), nil
|
||||
return artifact.export(u.cfg.Directory, u.index.versionNum), nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", ErrNotFound
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
|
||||
// Stop stops the module.
|
||||
|
||||
@@ -43,7 +43,7 @@ func (u *Updater) upgrade(downloader *Downloader, ignoreVersion bool) error {
|
||||
}
|
||||
|
||||
// Recovery failed too.
|
||||
return fmt.Errorf("upgrade (including recovery) failed: %s", upgradeError)
|
||||
return fmt.Errorf("upgrade (including recovery) failed: %s", u.cfg.Name, upgradeError)
|
||||
}
|
||||
|
||||
func (u *Updater) upgradeMoveFiles(downloader *Downloader, ignoreVersion bool) error {
|
||||
@@ -60,7 +60,9 @@ func (u *Updater) upgradeMoveFiles(downloader *Downloader, ignoreVersion bool) e
|
||||
}
|
||||
|
||||
// Move current version files into purge folder.
|
||||
log.Debugf("updates: removing the old version (v%s from %s)", u.index.Version, u.index.Published)
|
||||
if u.index != nil {
|
||||
log.Debugf("updates/%s: removing the old version (v%s from %s)", u.cfg.Name, u.index.Version, u.index.Published)
|
||||
}
|
||||
files, err := os.ReadDir(u.cfg.Directory)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read current directory: %w", err)
|
||||
@@ -74,17 +76,17 @@ func (u *Updater) upgradeMoveFiles(downloader *Downloader, ignoreVersion bool) e
|
||||
// Otherwise, move file to purge dir.
|
||||
src := filepath.Join(u.cfg.Directory, file.Name())
|
||||
dst := filepath.Join(u.cfg.PurgeDirectory, file.Name())
|
||||
err := moveFile(src, dst, "", file.Type().Perm())
|
||||
err := u.moveFile(src, dst, "", file.Type().Perm())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to move current file %s to purge dir: %w", file.Name(), err)
|
||||
}
|
||||
}
|
||||
|
||||
// Move the new index file into main directory.
|
||||
log.Debugf("updates: installing the new version (v%s from %s)", u.index.Version, u.index.Published)
|
||||
log.Debugf("updates/%s: installing the new version (v%s from %s)", u.cfg.Name, downloader.index.Version, downloader.index.Published)
|
||||
src := filepath.Join(u.cfg.DownloadDirectory, u.cfg.IndexFile)
|
||||
dst := filepath.Join(u.cfg.Directory, u.cfg.IndexFile)
|
||||
err = moveFile(src, dst, "", defaultFileMode)
|
||||
err = u.moveFile(src, dst, "", defaultFileMode)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to move index file to %s: %w", dst, err)
|
||||
}
|
||||
@@ -93,30 +95,30 @@ func (u *Updater) upgradeMoveFiles(downloader *Downloader, ignoreVersion bool) e
|
||||
for _, artifact := range downloader.index.Artifacts {
|
||||
src = filepath.Join(u.cfg.DownloadDirectory, artifact.Filename)
|
||||
dst = filepath.Join(u.cfg.Directory, artifact.Filename)
|
||||
err = moveFile(src, dst, artifact.SHA256, artifact.GetFileMode())
|
||||
err = u.moveFile(src, dst, artifact.SHA256, artifact.GetFileMode())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to move file %s: %w", artifact.Filename, err)
|
||||
} else {
|
||||
log.Debugf("updates: %s moved", artifact.Filename)
|
||||
log.Debugf("updates/%s: %s moved", u.cfg.Name, artifact.Filename)
|
||||
}
|
||||
}
|
||||
|
||||
// Set new index on module.
|
||||
u.index = downloader.index
|
||||
log.Infof("updates: update complete (v%s from %s)", u.index.Version, u.index.Published)
|
||||
log.Infof("updates/%s: update complete (v%s from %s)", u.cfg.Name, u.index.Version, u.index.Published)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// moveFile moves a file and falls back to copying if it fails.
|
||||
func moveFile(currentPath, newPath string, sha256sum string, fileMode fs.FileMode) error {
|
||||
func (u *Updater) moveFile(currentPath, newPath string, sha256sum string, fileMode fs.FileMode) error {
|
||||
// Try to simply move file.
|
||||
err := os.Rename(currentPath, newPath)
|
||||
if err == nil {
|
||||
// Moving was successful, return.
|
||||
return nil
|
||||
}
|
||||
log.Tracef("updates: failed to move to %q, falling back to copy+delete: %w", newPath, err)
|
||||
log.Tracef("updates/%s: failed to move to %q, falling back to copy+delete: %w", u.cfg.Name, newPath, err)
|
||||
|
||||
// Copy and check the checksum while we are at it.
|
||||
err = copyAndCheckSHA256Sum(currentPath, newPath, sha256sum, fileMode)
|
||||
@@ -139,10 +141,10 @@ func (u *Updater) recoverFromFailedUpgrade() error {
|
||||
for _, file := range files {
|
||||
purgedFile := filepath.Join(u.cfg.PurgeDirectory, file.Name())
|
||||
activeFile := filepath.Join(u.cfg.Directory, file.Name())
|
||||
err := moveFile(purgedFile, activeFile, "", file.Type().Perm())
|
||||
err := u.moveFile(purgedFile, activeFile, "", file.Type().Perm())
|
||||
if err != nil {
|
||||
// Only warn and continue to recover as many files as possible.
|
||||
log.Warningf("updates: failed to roll back file %s: %w", file.Name(), err)
|
||||
log.Warningf("updates/%s: failed to roll back file %s: %w", u.cfg.Name, file.Name(), err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,18 +178,18 @@ func (u *Updater) deleteUnfinishedFiles(dir string) error {
|
||||
|
||||
case strings.HasSuffix(e.Name(), ".download"):
|
||||
path := filepath.Join(dir, e.Name())
|
||||
log.Warningf("updates: deleting unfinished download file: %s\n", path)
|
||||
log.Warningf("updates/%s: deleting unfinished download file: %s", u.cfg.Name, path)
|
||||
err := os.Remove(path)
|
||||
if err != nil {
|
||||
log.Errorf("updates: failed to delete unfinished download file %s: %s", path, err)
|
||||
log.Errorf("updates/%s: failed to delete unfinished download file %s: %s", u.cfg.Name, path, err)
|
||||
}
|
||||
|
||||
case strings.HasSuffix(e.Name(), ".copy"):
|
||||
path := filepath.Join(dir, e.Name())
|
||||
log.Warningf("updates: deleting unfinished copied file: %s\n", path)
|
||||
log.Warningf("updates/%s: deleting unfinished copied file: %s", u.cfg.Name, path)
|
||||
err := os.Remove(path)
|
||||
if err != nil {
|
||||
log.Errorf("updates: failed to delete unfinished copied file %s: %s", path, err)
|
||||
log.Errorf("updates/%s: failed to delete unfinished copied file %s: %s", u.cfg.Name, path, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user