Skip to content

Commit

Permalink
feat: manage binary symlinks when changing context (#102)
Browse files Browse the repository at this point in the history
Fixes #100
  • Loading branch information
agaffney authored Mar 11, 2024
1 parent 14af542 commit 5eb9f67
Show file tree
Hide file tree
Showing 2 changed files with 177 additions and 12 deletions.
148 changes: 136 additions & 12 deletions pkgmgr/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package pkgmgr

import (
"errors"
"fmt"
"io"
"io/fs"
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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"`
Expand Down Expand Up @@ -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,
Expand All @@ -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
}
Expand All @@ -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
}
41 changes: 41 additions & 0 deletions pkgmgr/pkgmgr.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 != "" {
Expand Down Expand Up @@ -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
Expand All @@ -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 != "" {
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}

Expand Down

0 comments on commit 5eb9f67

Please sign in to comment.