diff --git a/pkgmgr/package.go b/pkgmgr/package.go index 79ac451..4ca6ab3 100644 --- a/pkgmgr/package.go +++ b/pkgmgr/package.go @@ -15,6 +15,7 @@ package pkgmgr import ( + "errors" "fmt" "io" "io/fs" @@ -283,6 +284,69 @@ func (p Package) uninstall(cfg Config, context string, keepData bool) error { return nil } +func (p Package) activate(cfg Config, context string) error { + pkgName := fmt.Sprintf("%s-%s-%s", p.Name, p.Version, context) + for _, installStep := range p.InstallSteps { + // Evaluate condition if defined + if installStep.Condition != "" { + if ok, err := cfg.Template.EvaluateCondition(installStep.Condition, nil); err != nil { + return NewInstallStepConditionError(installStep.Condition, err) + } else if !ok { + cfg.Logger.Debug( + fmt.Sprintf( + "skipping install step due to condition: %s", + installStep.Condition, + ), + ) + continue + } + } + if installStep.Docker != nil { + if err := installStep.Docker.activate(cfg, pkgName); err != nil { + return err + } + } else if installStep.File != nil { + if err := installStep.File.activate(cfg, pkgName); err != nil { + return err + } + } else { + return ErrNoInstallMethods + } + } + return nil +} + +func (p Package) deactivate(cfg Config, context string) error { + pkgName := fmt.Sprintf("%s-%s-%s", p.Name, p.Version, context) + for _, installStep := range p.InstallSteps { + // Evaluate condition if defined + if installStep.Condition != "" { + if ok, err := cfg.Template.EvaluateCondition(installStep.Condition, nil); err != nil { + return NewInstallStepConditionError(installStep.Condition, err) + } else if !ok { + cfg.Logger.Debug( + fmt.Sprintf( + "skipping install step due to condition: %s", + installStep.Condition, + ), + ) + continue + } + } + if installStep.Docker != nil { + if err := installStep.Docker.deactivate(cfg, pkgName); err != nil { + return err + } + } else if installStep.File != nil { + if err := installStep.File.deactivate(cfg, pkgName); err != nil { + return err + } + } else { + return ErrNoInstallMethods + } + } + return nil +} func (p Package) startService(cfg Config, context string) error { pkgName := fmt.Sprintf("%s-%s-%s", p.Name, p.Version, context) @@ -523,6 +587,16 @@ func (p *PackageInstallStepDocker) uninstall(cfg Config, pkgName string, keepDat return nil } +func (p *PackageInstallStepDocker) activate(cfg Config, pkgName string) error { + // Nothing to do + return nil +} + +func (p *PackageInstallStepDocker) deactivate(cfg Config, pkgName string) error { + // Nothing to do + return nil +} + type PackageInstallStepFile struct { Binary bool `yaml:"binary"` Filename string `yaml:"filename"` @@ -556,7 +630,35 @@ func (p *PackageInstallStepFile) install(cfg Config, pkgName string) error { return err } cfg.Logger.Debug(fmt.Sprintf("wrote file %s", filePath)) + return nil +} + +func (p *PackageInstallStepFile) uninstall(cfg Config, pkgName string) error { + filePath := filepath.Join( + cfg.DataDir, + pkgName, + p.Filename, + ) + cfg.Logger.Debug(fmt.Sprintf("deleting file %s", filePath)) + if err := os.Remove(filePath); err != nil { + if !errors.Is(err, fs.ErrNotExist) { + cfg.Logger.Warn(fmt.Sprintf("failed to remove file %s", filePath)) + } + } + return nil +} + +func (p *PackageInstallStepFile) activate(cfg Config, pkgName string) error { if p.Binary { + tmpFilePath, err := cfg.Template.Render(p.Filename, nil) + if err != nil { + return err + } + filePath := filepath.Join( + cfg.DataDir, + pkgName, + p.Filename, + ) binPath := filepath.Join( cfg.BinDir, tmpFilePath, @@ -565,6 +667,26 @@ func (p *PackageInstallStepFile) install(cfg Config, pkgName string) error { if err := os.MkdirAll(parentDir, fs.ModePerm); err != nil { return err } + // Check for existing file at symlink location + if stat, err := os.Lstat(binPath); err != nil { + if !errors.Is(err, fs.ErrNotExist) { + return err + } + } else { + if (stat.Mode() & fs.ModeSymlink) > 0 { + // Remove existing symlink + if err := os.Remove(binPath); err != nil { + if !errors.Is(err, fs.ErrNotExist) { + return err + } + } + cfg.Logger.Debug( + fmt.Sprintf("removed existing symlink %q", binPath), + ) + } else { + return fmt.Errorf("will not overwrite existing file %q with symlink", binPath) + } + } if err := os.Symlink(filePath, binPath); err != nil { return err } @@ -573,24 +695,26 @@ func (p *PackageInstallStepFile) install(cfg Config, pkgName string) error { return nil } -func (p *PackageInstallStepFile) uninstall(cfg Config, pkgName string) error { - filePath := filepath.Join( - cfg.DataDir, - pkgName, - p.Filename, - ) - cfg.Logger.Debug(fmt.Sprintf("deleting file %s", filePath)) - if err := os.Remove(filePath); err != nil { - cfg.Logger.Warn(fmt.Sprintf("failed to remove file %s", filePath)) - } +func (p *PackageInstallStepFile) deactivate(cfg Config, pkgName string) error { if p.Binary { + tmpFilePath, err := cfg.Template.Render(p.Filename, nil) + if err != nil { + return err + } binPath := filepath.Join( cfg.BinDir, - p.Filename, + tmpFilePath, ) + parentDir := filepath.Dir(binPath) + if err := os.MkdirAll(parentDir, fs.ModePerm); err != nil { + return err + } if err := os.Remove(binPath); err != nil { - cfg.Logger.Warn(fmt.Sprintf("failed to remove symlink %s", binPath)) + if !errors.Is(err, fs.ErrNotExist) { + return err + } } + cfg.Logger.Debug(fmt.Sprintf("removed symlink %s", binPath)) } return nil } diff --git a/pkgmgr/pkgmgr.go b/pkgmgr/pkgmgr.go index d5c2e29..62dfaaf 100644 --- a/pkgmgr/pkgmgr.go +++ b/pkgmgr/pkgmgr.go @@ -187,6 +187,12 @@ func (p *PackageManager) Install(pkgs ...string) error { notes, ) } + // Activate package + if err := installPkg.Install.activate(p.config, activeContextName); err != nil { + p.config.Logger.Warn( + fmt.Sprintf("failed to activate package: %s", err), + ) + } } // Display post-install notes if notesOutput != "" { @@ -230,6 +236,12 @@ func (p *PackageManager) Upgrade(pkgs ...string) error { ) // Capture options from existing package pkgOpts := upgradePkg.Installed.Options + // Deactivate old package + if err := upgradePkg.Installed.Package.deactivate(p.config, activeContextName); err != nil { + p.config.Logger.Warn( + fmt.Sprintf("failed to deactivate package: %s", err), + ) + } // Uninstall old version if err := p.uninstallPackage(upgradePkg.Installed, true); err != nil { return err @@ -256,6 +268,12 @@ func (p *PackageManager) Upgrade(pkgs ...string) error { if err := p.state.Save(); err != nil { return err } + // Activate new package + if err := upgradePkg.Upgrade.activate(p.config, activeContextName); err != nil { + p.config.Logger.Warn( + fmt.Sprintf("failed to activate package: %s", err), + ) + } } // Display post-install notes if notesOutput != "" { @@ -306,6 +324,12 @@ func (p *PackageManager) Uninstall(keepData bool, pkgs ...string) error { return err } for _, uninstallPkg := range uninstallPkgs { + // Deactivate package + if err := uninstallPkg.Package.deactivate(p.config, activeContextName); err != nil { + p.config.Logger.Warn( + fmt.Sprintf("failed to deactivate package: %s", err), + ) + } if err := p.uninstallPackage(uninstallPkg, keepData); err != nil { return err } @@ -482,12 +506,29 @@ func (p *PackageManager) SetActiveContext(name string) error { if _, ok := p.state.Contexts[name]; !ok { return ErrContextNotExist } + // Deactivate packages in current context + activeContextName, _ := p.ActiveContext() + for _, pkg := range p.InstalledPackages() { + if err := pkg.Package.deactivate(p.config, activeContextName); err != nil { + p.config.Logger.Warn( + fmt.Sprintf("failed to deactivate package: %s", err), + ) + } + } p.state.ActiveContext = name if err := p.state.Save(); err != nil { return err } // Update templating values p.initTemplate() + // Activate packages in new context + for _, pkg := range p.InstalledPackages() { + if err := pkg.Package.activate(p.config, name); err != nil { + p.config.Logger.Warn( + fmt.Sprintf("failed to activate package: %s", err), + ) + } + } return nil }