Skip to content

Commit d59cd72

Browse files
authored
feat: [close Vanilla-OS#165] Differ API support
* Implement differ WIP * Fix BaseImagePackageDiff * Overlay package diff * Unify package diff functions return types * Show package diff on upgrade check * Add upgrade check for packages * Update vendored packages * Package diff improvements
1 parent 891f94a commit d59cd72

File tree

1,304 files changed

+355436
-48436
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

1,304 files changed

+355436
-48436
lines changed

cmd/upgrade.go

+83-10
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,14 @@ package cmd
1414
*/
1515

1616
import (
17+
"fmt"
1718
"os"
19+
"strings"
1820

1921
"github.com/spf13/cobra"
2022

2123
"github.com/vanilla-os/abroot/core"
24+
"github.com/vanilla-os/differ/diff"
2225
"github.com/vanilla-os/orchid/cmdr"
2326
)
2427

@@ -50,11 +53,6 @@ func NewUpgradeCommand() *cmdr.Command {
5053
}
5154

5255
func upgrade(cmd *cobra.Command, args []string) error {
53-
if !core.RootCheck(false) {
54-
cmdr.Error.Println(abroot.Trans("upgrade.rootRequired"))
55-
return nil
56-
}
57-
5856
checkOnly, err := cmd.Flags().GetBool("check-only")
5957
if err != nil {
6058
cmdr.Error.Println(err)
@@ -68,14 +66,49 @@ func upgrade(cmd *cobra.Command, args []string) error {
6866
}
6967

7068
if checkOnly {
71-
_, res := aBsys.CheckUpdate()
69+
cmdr.Info.Println(abroot.Trans("upgrade.checkingSystemUpdate"))
70+
71+
// Check for image updates
72+
newDigest, res := aBsys.CheckUpdate()
7273
if res {
73-
cmdr.Info.Println(abroot.Trans("upgrade.updateAvailable"))
74-
os.Exit(0)
74+
cmdr.Info.Println(abroot.Trans("upgrade.systemUpdateAvailable"))
75+
76+
added, upgraded, downgraded, removed, err := core.BaseImagePackageDiff(aBsys.CurImage.Digest, newDigest)
77+
if err != nil {
78+
return err
79+
}
80+
err = renderPackageDiff(added, upgraded, downgraded, removed)
81+
if err != nil {
82+
return err
83+
}
7584
} else {
7685
cmdr.Info.Println(abroot.Trans("upgrade.noUpdateAvailable"))
77-
os.Exit(1)
7886
}
87+
88+
// Check for package updates
89+
cmdr.Info.Println(abroot.Trans("upgrade.checkingPackageUpdate"))
90+
added, upgraded, downgraded, removed, err := core.OverlayPackageDiff()
91+
if err != nil {
92+
return err
93+
}
94+
95+
sumChanges := len(added) + len(upgraded) + len(downgraded) + len(removed)
96+
if sumChanges == 0 {
97+
cmdr.Info.Println(abroot.Trans("upgrade.noUpdateAvailable"))
98+
} else {
99+
cmdr.Info.Sprintf(abroot.Trans("upgrade.packageUpdateAvailable"), sumChanges)
100+
101+
err = renderPackageDiff(added, upgraded, downgraded, removed)
102+
if err != nil {
103+
return err
104+
}
105+
}
106+
107+
return nil
108+
}
109+
110+
if !core.RootCheck(false) {
111+
cmdr.Error.Println(abroot.Trans("upgrade.rootRequired"))
79112
return nil
80113
}
81114

@@ -94,7 +127,7 @@ func upgrade(cmd *cobra.Command, args []string) error {
94127

95128
err = aBsys.RunOperation(operation)
96129
if err != nil {
97-
if err == core.NoUpdateError {
130+
if err == core.ErrNoUpdate {
98131
cmdr.Info.Println(abroot.Trans("upgrade.noUpdateAvailable"))
99132
return err
100133
}
@@ -106,3 +139,43 @@ func upgrade(cmd *cobra.Command, args []string) error {
106139
os.Exit(0)
107140
return nil
108141
}
142+
143+
func renderPackageDiff(added, upgraded, downgraded, removed []diff.PackageDiff) error {
144+
pkgFmt := "%s '%s' -> '%s'"
145+
146+
// Calculate largest string for proper alignment
147+
largestPkgName := 0
148+
for _, pkgSet := range [][]diff.PackageDiff{added, upgraded, downgraded, removed} {
149+
for _, pkg := range pkgSet {
150+
if len(pkg.Name) > largestPkgName {
151+
largestPkgName = len(pkg.Name)
152+
}
153+
}
154+
}
155+
156+
for _, pkgSet := range []struct {
157+
Set []diff.PackageDiff
158+
Header string
159+
Color cmdr.Color
160+
}{
161+
{added, abroot.Trans("upgrade.added"), cmdr.FgGreen},
162+
{upgraded, abroot.Trans("upgrade.upgraded"), cmdr.FgBlue},
163+
{downgraded, abroot.Trans("upgrade.downgraded"), cmdr.FgYellow},
164+
{removed, abroot.Trans("upgrade.removed"), cmdr.FgRed},
165+
} {
166+
cmdr.NewStyle(cmdr.Bold, pkgSet.Color).Println(pkgSet.Header + ":")
167+
bulletItems := []cmdr.BulletListItem{}
168+
for _, pkg := range pkgSet.Set {
169+
bulletItems = append(bulletItems, cmdr.BulletListItem{
170+
Level: 1,
171+
Text: fmt.Sprintf(pkgFmt, pkg.Name+strings.Repeat(" ", largestPkgName-len(pkg.Name)), pkg.PreviousVersion, pkg.NewVersion),
172+
})
173+
}
174+
err := cmdr.BulletList.WithItems(bulletItems).Render()
175+
if err != nil {
176+
return err
177+
}
178+
}
179+
180+
return nil
181+
}

config/abroot.json

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
"iPkgMngRm": "apt remove -y",
1515
"iPkgMngApi": "https://packages.vanillaos.org/api/pkg/{packageName}",
1616

17+
"differURL": "https://differ.vanillaos.org",
18+
1719
"partLabelVar": "vos-var",
1820
"partLabelA": "vos-a",
1921
"partLabelB": "vos-b",

core/package-diff.go

+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package core
2+
3+
/* License: GPLv3
4+
Authors:
5+
Mirko Brombin <[email protected]>
6+
Vanilla OS Contributors <https://github.com/vanilla-os/>
7+
Copyright: 2023
8+
Description:
9+
ABRoot is utility which provides full immutability and
10+
atomicity to a Linux system, by transacting between
11+
two root filesystems. Updates are performed using OCI
12+
images, to ensure that the system is always in a
13+
consistent state.
14+
*/
15+
16+
import (
17+
"encoding/json"
18+
"fmt"
19+
"io"
20+
"net/http"
21+
"strings"
22+
23+
"github.com/vanilla-os/abroot/extras/dpkg"
24+
"github.com/vanilla-os/abroot/settings"
25+
"github.com/vanilla-os/differ/diff"
26+
)
27+
28+
// BaseImagePackageDiff retrieves the added, removed, upgraded and downgraded
29+
// base packages (the ones bundled with the image).
30+
func BaseImagePackageDiff(currentDigest, newDigest string) (
31+
added, upgraded, downgraded, removed []diff.PackageDiff,
32+
err error,
33+
) {
34+
PrintVerbose("PackageDiff.BaseImagePackageDiff: running...")
35+
36+
imageComponents := strings.Split(settings.Cnf.Name, "/")
37+
imageName := imageComponents[len(imageComponents)-1]
38+
reqUrl := fmt.Sprintf("%s/images/%s/diff", settings.Cnf.DifferURL, imageName)
39+
body := fmt.Sprintf("{\"old_digest\": \"%s\", \"new_digest\": \"%s\"}", currentDigest, newDigest)
40+
41+
PrintVerbose("PackageDiff.BaseImagePackageDiff: Requesting base image diff to %s with body:\n%s", reqUrl, body)
42+
43+
request, err := http.NewRequest(http.MethodGet, reqUrl, strings.NewReader(body))
44+
if err != nil {
45+
PrintVerbose("PackageDiff.BaseImagePackageDiff:err: %s", err)
46+
return
47+
}
48+
defer request.Body.Close()
49+
50+
resp, err := http.DefaultClient.Do(request)
51+
if err != nil {
52+
PrintVerbose("PackageDiff.BaseImagePackageDiff(1):err: %s", err)
53+
return
54+
}
55+
56+
contents, err := io.ReadAll(resp.Body)
57+
if err != nil {
58+
PrintVerbose("PackageDiff.BaseImagePackageDiff(2):err: %s", err)
59+
return
60+
}
61+
62+
pkgDiff := struct {
63+
Added, Upgraded, Downgraded, Removed []diff.PackageDiff
64+
}{}
65+
err = json.Unmarshal(contents, &pkgDiff)
66+
if err != nil {
67+
PrintVerbose("PackageDiff.BaseImagePackageDiff(3):err: %s", err)
68+
return
69+
}
70+
71+
added = pkgDiff.Added
72+
upgraded = pkgDiff.Upgraded
73+
downgraded = pkgDiff.Downgraded
74+
removed = pkgDiff.Removed
75+
76+
return
77+
}
78+
79+
// OverlayPackageDiff retrieves the added, removed, upgraded and downgraded
80+
// overlay packages (the ones added manually via `abroot pkg add`).
81+
func OverlayPackageDiff() (
82+
added, upgraded, downgraded, removed []diff.PackageDiff,
83+
err error,
84+
) {
85+
PrintVerbose("OverlayPackageDiff: running...")
86+
87+
pkgM := NewPackageManager(false)
88+
addedPkgs, err := pkgM.GetAddPackages()
89+
if err != nil {
90+
PrintVerbose("PackageDiff.OverlayPackageDiff:err: %s", err)
91+
return
92+
}
93+
94+
localAddedVersions := dpkg.DpkgBatchGetPackageVersion(addedPkgs)
95+
localAdded := map[string]string{}
96+
for i := 0; i < len(addedPkgs); i++ {
97+
if localAddedVersions[i] != "" {
98+
localAdded[addedPkgs[i]] = localAddedVersions[i]
99+
}
100+
}
101+
102+
remoteAdded := map[string]string{}
103+
var pkgInfo map[string]any
104+
for pkgName := range localAdded {
105+
pkgInfo, err = GetRepoContentsForPkg(pkgName)
106+
if err != nil {
107+
PrintVerbose("PackageDiff.OverlayPackageDiff(1):err: %s", err)
108+
return
109+
}
110+
version, ok := pkgInfo["version"].(string)
111+
if !ok {
112+
err = fmt.Errorf("PackageDiff.OverlayPackageDiff(2):err: Unexpected value when retrieving upstream version of '%s'", pkgName)
113+
return
114+
}
115+
remoteAdded[pkgName] = version
116+
}
117+
118+
added, upgraded, downgraded, removed = diff.DiffPackages(localAdded, remoteAdded)
119+
return
120+
}

core/packages.go

+70-9
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,12 @@ package core
1414
*/
1515

1616
import (
17+
"encoding/json"
18+
"errors"
1719
"fmt"
1820
"io"
1921
"net/http"
22+
"net/url"
2023
"os"
2124
"path/filepath"
2225
"strings"
@@ -294,7 +297,7 @@ func (p *PackageManager) getPackages(file string) ([]string, error) {
294297
return pkgs, err
295298
}
296299

297-
pkgs = strings.Split(string(b), "\n")
300+
pkgs = strings.Split(strings.TrimSpace(string(b)), "\n")
298301

299302
PrintVerbose("PackageManager.getPackages: returning packages")
300303
return pkgs, nil
@@ -374,9 +377,10 @@ func (p *PackageManager) processApplyPackages() (string, string) {
374377

375378
var addPkgs, removePkgs []string
376379
for _, pkg := range unstaged {
377-
if pkg.Status == ADD {
380+
switch pkg.Status {
381+
case ADD:
378382
addPkgs = append(addPkgs, pkg.Name)
379-
} else if pkg.Status == REMOVE {
383+
case REMOVE:
380384
removePkgs = append(removePkgs, pkg.Name)
381385
}
382386
}
@@ -462,16 +466,36 @@ func (p *PackageManager) GetFinalCmd(operation ABSystemOperation) string {
462466
return cmd
463467
}
464468

465-
func (p *PackageManager) ExistsInRepo(pkg string) error {
466-
PrintVerbose("PackageManager.ExistsInRepo: running...")
467-
469+
// assertPkgMngApiSetUp checks whether the repo API is properly configured.
470+
// If a configuration exists but is malformed, returns an error.
471+
func assertPkgMngApiSetUp() (bool, error) {
468472
if settings.Cnf.IPkgMngApi == "" {
469-
PrintVerbose("PackageManager.ExistsInRepo: no API url set, will not check if package exists. This could lead to errors")
470-
return nil
473+
PrintVerbose("PackageManager.assertPkgMngApiSetUp: no API url set, will not check if package exists. This could lead to errors")
474+
return false, nil
475+
}
476+
477+
_, err := url.ParseRequestURI(settings.Cnf.IPkgMngApi)
478+
if err != nil {
479+
return false, fmt.Errorf("PackageManager.assertPkgMngApiSetUp: Value set as API url (%s) is not a valid URL", settings.Cnf.IPkgMngApi)
471480
}
472481

473482
if !strings.Contains(settings.Cnf.IPkgMngApi, "{packageName}") {
474-
return fmt.Errorf("PackageManager.ExistsInRepo: API url does not contain {packageName} placeholder. ABRoot is probably misconfigured, please report the issue to the maintainers of the distribution")
483+
return false, fmt.Errorf("PackageManager.assertPkgMngApiSetUp: API url does not contain {packageName} placeholder. ABRoot is probably misconfigured, please report the issue to the maintainers of the distribution")
484+
}
485+
486+
PrintVerbose("PackageManager.assertPkgMngApiSetUp: Repo is set up properly")
487+
return true, nil
488+
}
489+
490+
func (p *PackageManager) ExistsInRepo(pkg string) error {
491+
PrintVerbose("PackageManager.ExistsInRepo: running...")
492+
493+
ok, err := assertPkgMngApiSetUp()
494+
if err != nil {
495+
return err
496+
}
497+
if !ok {
498+
return nil
475499
}
476500

477501
url := strings.Replace(settings.Cnf.IPkgMngApi, "{packageName}", pkg, 1)
@@ -491,3 +515,40 @@ func (p *PackageManager) ExistsInRepo(pkg string) error {
491515
PrintVerbose("PackageManager.ExistsInRepo: package exists in repo")
492516
return nil
493517
}
518+
519+
// GetRepoContentsForPkg retrieves package information from the repository API
520+
func GetRepoContentsForPkg(pkg string) (map[string]any, error) {
521+
PrintVerbose("PackageManager.GetRepoContentsForPkg: running...")
522+
523+
ok, err := assertPkgMngApiSetUp()
524+
if err != nil {
525+
return map[string]any{}, err
526+
}
527+
if !ok {
528+
return map[string]any{}, errors.New("PackageManager.GetRepoContentsForPkg: no API url set, cannot query package information")
529+
}
530+
531+
url := strings.Replace(settings.Cnf.IPkgMngApi, "{packageName}", pkg, 1)
532+
PrintVerbose("PackageManager.GetRepoContentsForPkg: fetching package information in: " + url)
533+
534+
resp, err := http.Get(url)
535+
if err != nil {
536+
PrintVerbose("PackageManager.GetRepoContentsForPkg:err: " + err.Error())
537+
return map[string]any{}, err
538+
}
539+
540+
contents, err := io.ReadAll(resp.Body)
541+
if err != nil {
542+
PrintVerbose("PackageManager.GetRepoContentsForPkg(2):err: %s", err)
543+
return map[string]any{}, err
544+
}
545+
546+
pkgInfo := map[string]any{}
547+
err = json.Unmarshal(contents, &pkgInfo)
548+
if err != nil {
549+
PrintVerbose("PackageManager.GetRepoContentsForPkg(3):err: %s", err)
550+
return map[string]any{}, err
551+
}
552+
553+
return pkgInfo, nil
554+
}

0 commit comments

Comments
 (0)