From 1b69fde8cfb7c68906237142c257e581e17393ae Mon Sep 17 00:00:00 2001 From: Jakob Steiner Date: Thu, 14 Mar 2024 11:25:17 +0100 Subject: [PATCH] feat(cli, ui): check dependencies and show newly installed packages at update (#113, #114) --- cmd/glasskube/cmd/update.go | 3 ++ internal/web/server.go | 7 ++++ pkg/update/update.go | 67 +++++++++++++++++++++++++++++++++---- 3 files changed, 70 insertions(+), 7 deletions(-) diff --git a/cmd/glasskube/cmd/update.go b/cmd/glasskube/cmd/update.go index 3a261b1ef..c3b72d06c 100644 --- a/cmd/glasskube/cmd/update.go +++ b/cmd/glasskube/cmd/update.go @@ -65,6 +65,9 @@ func printTransaction(tx update.UpdateTransaction) { item.Package.Name, item.Package.Spec.PackageInfo.Version) } } + for _, req := range tx.Requirements { + fmt.Fprintf(w, "%v:\t-\t-> %v\n", req.Name, req.Version) + } _ = w.Flush() } diff --git a/internal/web/server.go b/internal/web/server.go index d487b0496..6e04db1f4 100644 --- a/internal/web/server.go +++ b/internal/web/server.go @@ -274,6 +274,13 @@ func (s *server) updateModal(w http.ResponseWriter, r *http.Request) { }) } } + for _, req := range ut.Requirements { + updates = append(updates, &map[string]any{ + "Name": req.Name, + "CurrentVersion": "-", + "LatestVersion": req.Version, + }) + } err = pkgUpdateModalTmpl.Execute(w, &map[string]any{ "Updates": updates, diff --git a/pkg/update/update.go b/pkg/update/update.go index 5825e10c0..7585e3adf 100644 --- a/pkg/update/update.go +++ b/pkg/update/update.go @@ -5,17 +5,24 @@ import ( "errors" "fmt" + "github.com/Masterminds/semver/v3" "github.com/glasskube/glasskube/api/v1alpha1" + "github.com/glasskube/glasskube/internal/controller/owners" + "github.com/glasskube/glasskube/internal/dependency" + clientadapter "github.com/glasskube/glasskube/internal/dependency/adapter/goclient" "github.com/glasskube/glasskube/internal/repo" "github.com/glasskube/glasskube/pkg/client" "github.com/glasskube/glasskube/pkg/condition" "github.com/glasskube/glasskube/pkg/statuswriter" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/scheme" ) type UpdateTransaction struct { - Items []updateTransactionItem + Items []updateTransactionItem + ConflictItems []updateTransactionItemConflict + Requirements []dependency.PackageWithVersion } func (tx UpdateTransaction) IsEmpty() bool { @@ -32,6 +39,11 @@ type updateTransactionItem struct { Version string } +type updateTransactionItemConflict struct { + updateTransactionItem + Conflicts dependency.Conflicts +} + func (txi updateTransactionItem) UpdateRequired() bool { return txi.Version != "" } @@ -39,12 +51,17 @@ func (txi updateTransactionItem) UpdateRequired() bool { type updater struct { client client.PackageV1Alpha1Client status statuswriter.StatusWriter + dm *dependency.DependendcyManager } func NewUpdater(client client.PackageV1Alpha1Client) *updater { return &updater{ client: client, status: statuswriter.Noop(), + dm: dependency.NewDependencyManager( + clientadapter.NewGoClientAdapter(client), + owners.NewOwnerManager(scheme.Scheme), + ), } } @@ -82,17 +99,32 @@ func (c *updater) Prepare(ctx context.Context, packageNames []string) (*UpdateTr return nil, fmt.Errorf("failed to fetch index: %v", err) } + requirementsSet := make(map[dependency.PackageWithVersion]struct{}) var tx UpdateTransaction outer: for _, pkg := range packagesToUpdate { for _, indexItem := range index.Packages { if indexItem.Name == pkg.Name { - if pkg.Spec.PackageInfo.Version != indexItem.LatestVersion { - // this package should be updated - tx.Items = append(tx.Items, updateTransactionItem{ - Package: pkg, - Version: indexItem.LatestVersion, - }) + if isUpgradable(pkg.Spec.PackageInfo.Version, indexItem.LatestVersion) { + item := updateTransactionItem{Package: pkg, Version: indexItem.LatestVersion} + var manifest v1alpha1.PackageManifest + if err := repo.FetchPackageManifest("", pkg.Name, indexItem.LatestVersion, &manifest); err != nil { + return nil, err + } + if result, err := c.dm.Validate(ctx, &manifest); err != nil { + return nil, err + } else if cf, err := c.dm.IsUpdateAllowed(ctx, &pkg, indexItem.LatestVersion); err != nil { + return nil, err + } else if len(result.Conflicts) > 0 || len(cf) > 0 { + // This package can't be updated due to conflicts + tx.ConflictItems = append(tx.ConflictItems, updateTransactionItemConflict{item, append(result.Conflicts, cf...)}) + } else { + for _, req := range result.Requirements { + requirementsSet[req] = struct{}{} + } + // this package should be updated + tx.Items = append(tx.Items, item) + } } else if len(packageNames) > 0 { // this package is already up-to-date but an update was requested via argument tx.Items = append(tx.Items, updateTransactionItem{Package: pkg}) @@ -104,9 +136,30 @@ outer: return nil, fmt.Errorf("package %v not found in index", pkg.Name) } + for req := range requirementsSet { + tx.Requirements = append(tx.Requirements, req) + } + return &tx, nil } +// isUpgradable checks if latest is greater than installed, according to semver +// As a fallback if either cannot be parsed as semver, it returns whether they are different. +func isUpgradable(installed, latest string) bool { + var parsedInstalled, parsedLatest *semver.Version + var err error + if parsedInstalled, err = semver.NewVersion(installed); err != nil { + parsedInstalled = nil + } else if parsedLatest, err = semver.NewVersion(latest); err != nil { + parsedLatest = nil + } + if parsedLatest != nil && parsedInstalled != nil { + return parsedLatest.GreaterThan(parsedInstalled) + } else { + return installed != latest + } +} + func (c *updater) Apply(ctx context.Context, tx *UpdateTransaction) error { c.status.Start() defer c.status.Stop()