diff --git a/pkg/installer/installer.go b/pkg/installer/installer.go index 6f994ac0..f7c8cee2 100644 --- a/pkg/installer/installer.go +++ b/pkg/installer/installer.go @@ -92,6 +92,50 @@ func NewLuetInstaller(concurrency int) Installer { return &LuetInstaller{Concurrency: concurrency} } +func (l *LuetInstaller) Upgrade(s *System) error { + Spinner(32) + defer SpinnerStop() + syncedRepos := Repositories{} + for _, r := range l.PackageRepositories { + repo, err := r.Sync() + if err != nil { + return errors.Wrap(err, "Failed syncing repository: "+r.GetName()) + } + syncedRepos = append(syncedRepos, repo) + } + + // compute what to install and from where + sort.Sort(syncedRepos) + + // First match packages against repositories by priority + // matches := syncedRepos.PackageMatches(p) + + // compute a "big" world + allRepos := pkg.NewInMemoryDatabase(false) + syncedRepos.SyncDatabase(allRepos) + solv := solver.NewSolver(s.Database, allRepos, pkg.NewInMemoryDatabase(false)) + uninstall, solution, err := solv.Upgrade() + if err != nil { + return errors.Wrap(err, "Failed solving solution for upgrade") + } + + for _, u := range uninstall { + err := l.Uninstall(u, s) + if err != nil { + Warning("Failed uninstall for ", u.GetFingerPrint()) + } + } + + toInstall := []pkg.Package{} + for _, assertion := range solution { + if assertion.Value { + toInstall = append(toInstall, assertion.Package) + } + } + + return l.Install(toInstall, s) +} + func (l *LuetInstaller) Install(p []pkg.Package, s *System) error { // First get metas from all repos (and decodes trees) @@ -136,8 +180,10 @@ func (l *LuetInstaller) Install(p []pkg.Package, s *System) error { } if matches[0].Package.Matches(artefact.GetCompileSpec().GetPackage()) { - // TODO: Filter out already installed? - toInstall[assertion.Package.GetFingerPrint()] = ArtifactMatch{Package: assertion.Package, Artifact: artefact, Repository: matches[0].Repo} + // Filter out already installed + if _, err := s.Database.FindPackage(assertion.Package); err != nil { + toInstall[assertion.Package.GetFingerPrint()] = ArtifactMatch{Package: assertion.Package, Artifact: artefact, Repository: matches[0].Repo} + } break A } } diff --git a/pkg/installer/installer_test.go b/pkg/installer/installer_test.go index a33d766f..16a6e8cc 100644 --- a/pkg/installer/installer_test.go +++ b/pkg/installer/installer_test.go @@ -257,4 +257,116 @@ uri: "`+tmpdir+`" }) }) + + Context("Simple upgrades", func() { + It("Installs packages and Upgrades a system with a persistent db", func() { + //repo:=NewLuetRepository() + + tmpdir, err := ioutil.TempDir("", "tree") + Expect(err).ToNot(HaveOccurred()) + defer os.RemoveAll(tmpdir) // clean up + + generalRecipe := tree.NewCompilerRecipe(pkg.NewInMemoryDatabase(false)) + + err = generalRecipe.Load("../../tests/fixtures/upgrade") + Expect(err).ToNot(HaveOccurred()) + + Expect(len(generalRecipe.GetDatabase().GetPackages())).To(Equal(4)) + + c := compiler.NewLuetCompiler(backend.NewSimpleDockerBackend(), generalRecipe.GetDatabase()) + + spec, err := c.FromPackage(&pkg.DefaultPackage{Name: "b", Category: "test", Version: "1.0"}) + Expect(err).ToNot(HaveOccurred()) + spec2, err := c.FromPackage(&pkg.DefaultPackage{Name: "b", Category: "test", Version: "1.1"}) + Expect(err).ToNot(HaveOccurred()) + spec3, err := c.FromPackage(&pkg.DefaultPackage{Name: "c", Category: "test", Version: "1.0"}) + Expect(err).ToNot(HaveOccurred()) + + Expect(spec.GetPackage().GetPath()).ToNot(Equal("")) + + tmpdir, err = ioutil.TempDir("", "tree") + Expect(err).ToNot(HaveOccurred()) + defer os.RemoveAll(tmpdir) // clean up + + spec.SetOutputPath(tmpdir) + spec2.SetOutputPath(tmpdir) + spec3.SetOutputPath(tmpdir) + _, errs := c.CompileParallel(2, false, compiler.NewLuetCompilationspecs(spec, spec2, spec3)) + + Expect(errs).To(BeEmpty()) + + repo, err := GenerateRepository("test", tmpdir, "local", 1, tmpdir, "../../tests/fixtures/upgrade", pkg.NewInMemoryDatabase(false)) + Expect(err).ToNot(HaveOccurred()) + Expect(repo.GetName()).To(Equal("test")) + Expect(helpers.Exists(spec.Rel("repository.yaml"))).ToNot(BeTrue()) + Expect(helpers.Exists(spec.Rel("tree.tar"))).ToNot(BeTrue()) + err = repo.Write(tmpdir) + Expect(err).ToNot(HaveOccurred()) + + Expect(helpers.Exists(spec.Rel("repository.yaml"))).To(BeTrue()) + Expect(helpers.Exists(spec.Rel("tree.tar"))).To(BeTrue()) + Expect(repo.GetUri()).To(Equal(tmpdir)) + Expect(repo.GetType()).To(Equal("local")) + + fakeroot, err := ioutil.TempDir("", "fakeroot") + Expect(err).ToNot(HaveOccurred()) + defer os.RemoveAll(fakeroot) // clean up + + inst := NewLuetInstaller(1) + repo2, err := NewLuetRepositoryFromYaml([]byte(` +name: "test" +type: "local" +uri: "`+tmpdir+`" +`), pkg.NewInMemoryDatabase(false)) + Expect(err).ToNot(HaveOccurred()) + + inst.Repositories(Repositories{repo2}) + Expect(repo.GetUri()).To(Equal(tmpdir)) + Expect(repo.GetType()).To(Equal("local")) + + bolt, err := ioutil.TempDir("", "db") + Expect(err).ToNot(HaveOccurred()) + defer os.RemoveAll(bolt) // clean up + + systemDB := pkg.NewBoltDatabase(filepath.Join(bolt, "db.db")) + system := &System{Database: systemDB, Target: fakeroot} + err = inst.Install([]pkg.Package{&pkg.DefaultPackage{Name: "b", Category: "test", Version: "1.0"}}, system) + Expect(err).ToNot(HaveOccurred()) + + Expect(helpers.Exists(filepath.Join(fakeroot, "test5"))).To(BeTrue()) + Expect(helpers.Exists(filepath.Join(fakeroot, "test6"))).To(BeTrue()) + _, err = systemDB.FindPackage(&pkg.DefaultPackage{Name: "b", Category: "test", Version: "1.0"}) + Expect(err).ToNot(HaveOccurred()) + + Expect(len(system.Database.GetPackages())).To(Equal(1)) + p, err := system.Database.GetPackage(system.Database.GetPackages()[0]) + Expect(err).ToNot(HaveOccurred()) + Expect(p.GetName()).To(Equal("b")) + + files, err := systemDB.GetPackageFiles(&pkg.DefaultPackage{Name: "b", Category: "test", Version: "1.0"}) + Expect(files).To(Equal([]string{"artifact42", "test5", "test6"})) + Expect(err).ToNot(HaveOccurred()) + + err = inst.Upgrade(system) + Expect(err).ToNot(HaveOccurred()) + + // Nothing should be there anymore (files, packagedb entry) + Expect(helpers.Exists(filepath.Join(fakeroot, "test5"))).ToNot(BeTrue()) + Expect(helpers.Exists(filepath.Join(fakeroot, "test6"))).ToNot(BeTrue()) + + // New version - new files + Expect(helpers.Exists(filepath.Join(fakeroot, "newc"))).To(BeTrue()) + _, err = system.Database.GetPackageFiles(&pkg.DefaultPackage{Name: "b", Category: "test", Version: "1.0"}) + Expect(err).To(HaveOccurred()) + _, err = system.Database.FindPackage(&pkg.DefaultPackage{Name: "b", Category: "test", Version: "1.0"}) + Expect(err).To(HaveOccurred()) + + // New package should be there + _, err = system.Database.FindPackage(&pkg.DefaultPackage{Name: "b", Category: "test", Version: "1.1"}) + Expect(err).ToNot(HaveOccurred()) + + }) + + }) + }) diff --git a/pkg/installer/interface.go b/pkg/installer/interface.go index e2a9a5fc..374b23d2 100644 --- a/pkg/installer/interface.go +++ b/pkg/installer/interface.go @@ -25,6 +25,7 @@ import ( type Installer interface { Install([]pkg.Package, *System) error Uninstall(pkg.Package, *System) error + Upgrade(s *System) error Repositories([]Repository) } diff --git a/pkg/solver/solver.go b/pkg/solver/solver.go index f6bf9590..42a0061a 100644 --- a/pkg/solver/solver.go +++ b/pkg/solver/solver.go @@ -32,6 +32,7 @@ type PackageSolver interface { ConflictsWithInstalled(p pkg.Package) (bool, error) ConflictsWith(p pkg.Package, ls []pkg.Package) (bool, error) World() []pkg.Package + Upgrade() ([]pkg.Package, PackagesAssertions, error) } // Solver is the default solver for luet @@ -193,6 +194,55 @@ func (s *Solver) ConflictsWithInstalled(p pkg.Package) (bool, error) { return s.ConflictsWith(p, s.Installed()) } +func (s *Solver) Upgrade() ([]pkg.Package, PackagesAssertions, error) { + + // First get candidates that needs to be upgraded.. + + toUninstall := []pkg.Package{} + toInstall := []pkg.Package{} + + availableCache := map[string][]pkg.Package{} + for _, p := range s.DefinitionDatabase.World() { + // Each one, should be expanded + availableCache[p.GetName()+p.GetCategory()] = append(availableCache[p.GetName()+p.GetCategory()], p) + } + + installedcopy := pkg.NewInMemoryDatabase(false) + + for _, p := range s.InstalledDatabase.World() { + installedcopy.CreatePackage(p) + packages, ok := availableCache[p.GetName()+p.GetCategory()] + if ok && len(packages) != 0 { + best := pkg.Best(packages) + if best.GetVersion() != p.GetVersion() { + toUninstall = append(toUninstall, p) + toInstall = append(toInstall, best) + } + } + } + + s2 := NewSolver(installedcopy, s.DefinitionDatabase, pkg.NewInMemoryDatabase(false)) + // Then try to uninstall the versions in the system, and store that tree + for _, p := range toUninstall { + r, err := s.Uninstall(p) + if err != nil { + return nil, nil, errors.Wrap(err, "Could not compute upgrade - couldn't uninstall selected candidate "+p.GetFingerPrint()) + } + for _, z := range r { + err = installedcopy.RemovePackage(z) + if err != nil { + return nil, nil, errors.Wrap(err, "Could not compute upgrade - couldn't remove copy of package targetted for removal") + } + } + + } + r, e := s2.Install(toInstall) + return toUninstall, r, e + // To that tree, ask to install the versions that should be upgraded, and try to solve + // Return the solution + +} + // Uninstall takes a candidate package and return a list of packages that would be removed // in order to purge the candidate. Returns error if unsat. func (s *Solver) Uninstall(c pkg.Package) ([]pkg.Package, error) { diff --git a/pkg/solver/solver_test.go b/pkg/solver/solver_test.go index 0f5a3644..b18e75ca 100644 --- a/pkg/solver/solver_test.go +++ b/pkg/solver/solver_test.go @@ -678,5 +678,39 @@ var _ = Describe("Solver", func() { Expect(p).To(Equal(a03)) }) }) + Context("Upgrades", func() { + C := pkg.NewPackage("c", "1.5", []*pkg.DefaultPackage{&pkg.DefaultPackage{Name: "a", Version: ">=1.0", Category: "test"}}, []*pkg.DefaultPackage{}) + C.SetCategory("test") + B := pkg.NewPackage("b", "1.0", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + B.SetCategory("test") + A := pkg.NewPackage("a", "1.1", []*pkg.DefaultPackage{&pkg.DefaultPackage{Name: "b", Version: "1.0", Category: "test"}}, []*pkg.DefaultPackage{}) + A.SetCategory("test") + A1 := pkg.NewPackage("a", "1.2", []*pkg.DefaultPackage{&pkg.DefaultPackage{Name: "b", Version: "1.0", Category: "test"}}, []*pkg.DefaultPackage{}) + A1.SetCategory("test") + + It("upgrades correctly", func() { + for _, p := range []pkg.Package{A1, B, C} { + _, err := dbDefinitions.CreatePackage(p) + Expect(err).ToNot(HaveOccurred()) + } + + for _, p := range []pkg.Package{A, B} { + _, err := dbInstalled.CreatePackage(p) + Expect(err).ToNot(HaveOccurred()) + } + uninstall, solution, err := s.Upgrade() + Expect(err).ToNot(HaveOccurred()) + + Expect(len(uninstall)).To(Equal(1)) + Expect(uninstall[0].GetName()).To(Equal("a")) + Expect(uninstall[0].GetVersion()).To(Equal("1.1")) + + Expect(solution).To(ContainElement(PackageAssert{Package: A1, Value: true})) + Expect(solution).To(ContainElement(PackageAssert{Package: B, Value: true})) + Expect(solution).To(ContainElement(PackageAssert{Package: C, Value: false})) + Expect(len(solution)).To(Equal(3)) + + }) + }) }) diff --git a/tests/fixtures/upgrade/c/build.yaml b/tests/fixtures/upgrade/c/build.yaml new file mode 100644 index 00000000..dc3fba0a --- /dev/null +++ b/tests/fixtures/upgrade/c/build.yaml @@ -0,0 +1,10 @@ +prelude: + - echo foo > /test + - echo bar > /test2 +steps: + - echo c > /c + - echo c > /cd +requires: +- category: "test" + name: "a" + version: ">=1.0" diff --git a/tests/fixtures/upgrade/c/definition.yaml b/tests/fixtures/upgrade/c/definition.yaml new file mode 100644 index 00000000..348454f7 --- /dev/null +++ b/tests/fixtures/upgrade/c/definition.yaml @@ -0,0 +1,3 @@ +category: "test" +name: "c" +version: "1.0" diff --git a/tests/fixtures/upgrade/cat/a/a/build.yaml b/tests/fixtures/upgrade/cat/a/a/build.yaml new file mode 100644 index 00000000..b9fe4c1d --- /dev/null +++ b/tests/fixtures/upgrade/cat/a/a/build.yaml @@ -0,0 +1,11 @@ +image: "alpine" +prelude: + - echo foo > /test + - echo bar > /test2 +steps: + - echo artifact3 > /test3 + - echo artifact4 > /test4 +requires: +- category: "test" + name: "b" + version: "1.0" diff --git a/tests/fixtures/upgrade/cat/a/a/definition.yaml b/tests/fixtures/upgrade/cat/a/a/definition.yaml new file mode 100644 index 00000000..a419363d --- /dev/null +++ b/tests/fixtures/upgrade/cat/a/a/definition.yaml @@ -0,0 +1,8 @@ +category: "test" +name: "a" +version: "1.1" +requires: +- category: "test2" + name: "b" + version: "1.0" + diff --git a/tests/fixtures/upgrade/cat/b-1.1/build.yaml b/tests/fixtures/upgrade/cat/b-1.1/build.yaml new file mode 100644 index 00000000..3e3f98a4 --- /dev/null +++ b/tests/fixtures/upgrade/cat/b-1.1/build.yaml @@ -0,0 +1,9 @@ +image: "alpine" +prelude: + - echo foo > /test + - echo bar > /test2 + - chmod +x generate.sh +steps: + - echo artifact5 > /newc + - echo artifact6 > /newnewc + - ./generate.sh diff --git a/tests/fixtures/upgrade/cat/b-1.1/definition.yaml b/tests/fixtures/upgrade/cat/b-1.1/definition.yaml new file mode 100644 index 00000000..e695c6c9 --- /dev/null +++ b/tests/fixtures/upgrade/cat/b-1.1/definition.yaml @@ -0,0 +1,3 @@ +category: "test" +name: "b" +version: "1.1" diff --git a/tests/fixtures/upgrade/cat/b-1.1/generate.sh b/tests/fixtures/upgrade/cat/b-1.1/generate.sh new file mode 100644 index 00000000..3b2dc1b2 --- /dev/null +++ b/tests/fixtures/upgrade/cat/b-1.1/generate.sh @@ -0,0 +1 @@ +echo generated > /sonewc diff --git a/tests/fixtures/upgrade/cat/b/build.yaml b/tests/fixtures/upgrade/cat/b/build.yaml new file mode 100644 index 00000000..05a81578 --- /dev/null +++ b/tests/fixtures/upgrade/cat/b/build.yaml @@ -0,0 +1,9 @@ +image: "alpine" +prelude: + - echo foo > /test + - echo bar > /test2 + - chmod +x generate.sh +steps: + - echo artifact5 > /test5 + - echo artifact6 > /test6 + - ./generate.sh \ No newline at end of file diff --git a/tests/fixtures/upgrade/cat/b/definition.yaml b/tests/fixtures/upgrade/cat/b/definition.yaml new file mode 100644 index 00000000..b02a44e8 --- /dev/null +++ b/tests/fixtures/upgrade/cat/b/definition.yaml @@ -0,0 +1,3 @@ +category: "test" +name: "b" +version: "1.0" diff --git a/tests/fixtures/upgrade/cat/b/generate.sh b/tests/fixtures/upgrade/cat/b/generate.sh new file mode 100644 index 00000000..b67417f6 --- /dev/null +++ b/tests/fixtures/upgrade/cat/b/generate.sh @@ -0,0 +1 @@ +echo generated > /artifact42