diff --git a/internal/manager/manager.go b/internal/manager/manager.go index f1e6d9f..713a8f7 100644 --- a/internal/manager/manager.go +++ b/internal/manager/manager.go @@ -7,6 +7,7 @@ import ( "github.com/nlewo/comin/internal/deployment" "github.com/nlewo/comin/internal/generation" "github.com/nlewo/comin/internal/nix" + "github.com/nlewo/comin/internal/profile" "github.com/nlewo/comin/internal/prometheus" "github.com/nlewo/comin/internal/repository" "github.com/nlewo/comin/internal/store" @@ -148,7 +149,10 @@ func (m Manager) onDeployment(ctx context.Context, deploymentResult deployment.D } m.isRunning = false m.prometheus.SetDeploymentInfo(m.deployment.Generation.SelectedCommitId, deployment.StatusToString(m.deployment.Status)) - m.storage.DeploymentInsertAndCommit(m.deployment) + getsEvicted, evicted := m.storage.DeploymentInsertAndCommit(m.deployment) + if getsEvicted && evicted.ProfilePath != "" { + profile.RemoveProfilePath(evicted.ProfilePath) + } return m } diff --git a/internal/nix/nix.go b/internal/nix/nix.go index 8276638..e9ca918 100644 --- a/internal/nix/nix.go +++ b/internal/nix/nix.go @@ -9,10 +9,10 @@ import ( "io" "os" "os/exec" - "path" "path/filepath" "strings" + "github.com/nlewo/comin/internal/profile" "github.com/sirupsen/logrus" ) @@ -154,45 +154,6 @@ func Build(ctx context.Context, drvPath string) (err error) { return } -// setSystemProfile creates a link into the directory -// /nix/var/nix/profiles/system-profiles/comin to the built system -// store path. This is used by the switch-to-configuration script to -// install all entries into the bootloader. -// Note also comin uses these links as gcroots -// See https://github.com/nixos/nixpkgs/blob/df98ab81f908bed57c443a58ec5230f7f7de9bd3/pkgs/os-specific/linux/nixos-rebuild/nixos-rebuild.sh#L711 -// and https://github.com/nixos/nixpkgs/blob/df98ab81f908bed57c443a58ec5230f7f7de9bd3/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py#L247 -func setSystemProfile(operation string, outPath string, dryRun bool) (profilePath string, err error) { - systemProfilesDir := "/nix/var/nix/profiles/system-profiles" - profile := systemProfilesDir + "/comin" - if operation == "switch" || operation == "boot" { - err := os.MkdirAll(systemProfilesDir, os.ModeDir) - if err != nil && !os.IsExist(err) { - return profilePath, fmt.Errorf("nix: failed to create the profile directory: %s", systemProfilesDir) - } - cmdStr := fmt.Sprintf("nix-env --profile %s --set %s", profile, outPath) - logrus.Infof("nix: running '%s'", cmdStr) - cmd := exec.Command("nix-env", "--profile", profile, "--set", outPath) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - if dryRun { - logrus.Infof("nix: dry-run enabled: '%s' has not been executed", cmdStr) - } else { - err := cmd.Run() - if err != nil { - return profilePath, fmt.Errorf("nix: command '%s' fails with %s", cmdStr, err) - } - logrus.Infof("nix: command '%s' succeeded", cmdStr) - dst, err := os.Readlink(profile) - if err != nil { - return profilePath, fmt.Errorf("nix: failed to os.Readlink(%s)", profile) - } - profilePath = path.Join(systemProfilesDir, dst) - logrus.Infof("nix: the profile %s has been created", profilePath) - } - } - return -} - func cominUnitFileHash() string { logrus.Infof("nix: generating the comin.service unit file sha256: 'systemctl cat comin.service | sha256sum'") cmd := exec.Command("systemctl", "cat", "comin.service") @@ -231,7 +192,7 @@ func Deploy(ctx context.Context, expectedMachineId, outPath, operation string) ( // This is required to write boot entries // Only do this is operation is switch or boot - if profilePath, err = setSystemProfile(operation, outPath, false); err != nil { + if profilePath, err = profile.SetSystemProfile(operation, outPath, false); err != nil { return } diff --git a/internal/profile/profile.go b/internal/profile/profile.go new file mode 100644 index 0000000..b14243c --- /dev/null +++ b/internal/profile/profile.go @@ -0,0 +1,57 @@ +package profile + +import "os" +import "fmt" +import "github.com/sirupsen/logrus" +import "os/exec" +import "path" + +var systemProfilesDir string = "/nix/var/nix/profiles/system-profiles" +var cominProfileDir string = systemProfilesDir + "/comin" + +// setSystemProfile creates a link into the directory +// /nix/var/nix/profiles/system-profiles/comin to the built system +// store path. This is used by the switch-to-configuration script to +// install all entries into the bootloader. +// Note also comin uses these links as gcroots +// See https://github.com/nixos/nixpkgs/blob/df98ab81f908bed57c443a58ec5230f7f7de9bd3/pkgs/os-specific/linux/nixos-rebuild/nixos-rebuild.sh#L711 +// and https://github.com/nixos/nixpkgs/blob/df98ab81f908bed57c443a58ec5230f7f7de9bd3/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py#L247 +func SetSystemProfile(operation string, outPath string, dryRun bool) (profilePath string, err error) { + if operation == "switch" || operation == "boot" { + err := os.MkdirAll(systemProfilesDir, os.ModeDir) + if err != nil && !os.IsExist(err) { + return profilePath, fmt.Errorf("nix: failed to create the profile directory: %s", systemProfilesDir) + } + cmdStr := fmt.Sprintf("nix-env --profile %s --set %s", cominProfileDir, outPath) + logrus.Infof("nix: running '%s'", cmdStr) + cmd := exec.Command("nix-env", "--profile", cominProfileDir, "--set", outPath) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if dryRun { + logrus.Infof("nix: dry-run enabled: '%s' has not been executed", cmdStr) + } else { + err := cmd.Run() + if err != nil { + return profilePath, fmt.Errorf("nix: command '%s' fails with %s", cmdStr, err) + } + logrus.Infof("nix: command '%s' succeeded", cmdStr) + dst, err := os.Readlink(cominProfileDir) + if err != nil { + return profilePath, fmt.Errorf("nix: failed to os.Readlink(%s)", cominProfileDir) + } + profilePath = path.Join(systemProfilesDir, dst) + logrus.Infof("nix: the profile %s has been created", profilePath) + } + } + return +} + +// RemoveProfilePath removes a profile path. +func RemoveProfilePath(profilePath string) (err error) { + logrus.Infof("Removing profile path %s", profilePath) + err = os.Remove(profilePath) + if err != nil { + logrus.Errorf("Failed to remove profile path %s: %s", profilePath, err) + } + return +} diff --git a/internal/profile/profile_test.go b/internal/profile/profile_test.go new file mode 100644 index 0000000..e6239c5 --- /dev/null +++ b/internal/profile/profile_test.go @@ -0,0 +1,27 @@ +package profile + +import ( + "path" + "testing" + + "os" + + "github.com/stretchr/testify/assert" +) + +func TestRemoveProfilePath(t *testing.T) { + dir := t.TempDir() + file1 := path.Join(dir, "file1") + os.Create(file1) + file2 := path.Join(dir, "file2") + os.Create(file2) + + RemoveProfilePath(file1) + entries, _ := os.ReadDir(dir) + files := make([]string, len(entries)) + for i, e := range entries { + files[i] = e.Name() + } + expected := []string{"file2"} + assert.Equal(t, expected, files) +} diff --git a/internal/store/store.go b/internal/store/store.go index 22ca1a2..ad4c734 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -34,15 +34,17 @@ func New(filename string, capacityMain, capacityTesting int) Store { } -func (s *Store) DeploymentInsertAndCommit(dpl deployment.Deployment) { - ok, evicted := s.DeploymentInsert(dpl) +func (s *Store) DeploymentInsertAndCommit(dpl deployment.Deployment) (ok bool, evicted deployment.Deployment) { + ok, evicted = s.DeploymentInsert(dpl) if ok { logrus.Infof("The deployment %s has been removed from store.json file", evicted.UUID) } if err := s.Commit(); err != nil { logrus.Errorf("Error while commiting the store.json file: %s", err) + return } logrus.Infof("The new deployment %s has been commited to store.json file", dpl.UUID) + return } // DeploymentInsert inserts a deployment and return an evicted