Skip to content

Commit

Permalink
feat: get ugprade-paths command
Browse files Browse the repository at this point in the history
Add a new command to list all the available upgrade paths known by furyctl for a specific cluster `kind` and `version`.

`kind` and `version` will be read from the furyctl.yaml file and can be overriden with a flag if wanted.

Overriding both flags skips downloading the distribution and all the validation steps and works entirely locally.
  • Loading branch information
ralgozino committed Aug 30, 2024
1 parent 22f269f commit aaf5885
Show file tree
Hide file tree
Showing 2 changed files with 292 additions and 0 deletions.
1 change: 1 addition & 0 deletions cmd/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ func NewGetCmd() *cobra.Command {
}

getCmd.AddCommand(get.NewKubeconfigCmd())
getCmd.AddCommand(get.NewUpgradePathsCmd())

return getCmd
}
291 changes: 291 additions & 0 deletions cmd/get/upgrade-paths.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
package get

import (
"fmt"
"io/fs"
"os"
"path"
"path/filepath"
"strings"

"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/viper"

distroconf "github.com/sighupio/fury-distribution/pkg/apis/config"
"github.com/sighupio/furyctl/configs"
"github.com/sighupio/furyctl/internal/analytics"
"github.com/sighupio/furyctl/internal/app"
"github.com/sighupio/furyctl/internal/config"
"github.com/sighupio/furyctl/internal/git"
"github.com/sighupio/furyctl/internal/semver"
cobrax "github.com/sighupio/furyctl/internal/x/cobra"
execx "github.com/sighupio/furyctl/internal/x/exec"
"github.com/sighupio/furyctl/pkg/dependencies"
dist "github.com/sighupio/furyctl/pkg/distribution"
netx "github.com/sighupio/furyctl/pkg/x/net"
yamlx "github.com/sighupio/furyctl/pkg/x/yaml"
)

func NewUpgradePathsCmd() *cobra.Command {
var cmdEvent analytics.Event

upgradePathsCmd := &cobra.Command{
Use: "upgrade-paths",
Short: "Get available upgrade paths for the kind and version defined in the configuration file or a custom one.",
Long: `Get available upgrade paths for the kind and version defined in the configuration file or a custom one. If a from version or kind are specified the command will give the upgrade path for those instaed.
Examples:
- furyctl get upgrade-paths will show the available upgrade paths for the kind a distribution version defined in the configuration file (furyctl.yaml by default)
- furyctl get upgrade-paths --from vX.Y.Z will show the available upgrade paths for the kind defined in the configuration file but for the version X.Y.Z instead.
- furyctl get upgrade-paths --kind OnPremises will show the available upgrade paths for the version defined in the configuration file but for the OnPremises kind, even if the cluster is an EKSCluster, for example.
- furyctl get upgrade-paths --kind OnPremises --from X.Y.X will show the available upgrade paths for the version X.Y.Z of the OnPremises kind, without reading the configuration file.
`,
PreRun: func(cmd *cobra.Command, _ []string) {
cmdEvent = analytics.NewCommandEvent(cobrax.GetFullname(cmd))

if err := viper.BindPFlags(cmd.Flags()); err != nil {
logrus.Fatalf("error while binding flags: %v", err)
}
},
RunE: func(_ *cobra.Command, _ []string) error {
ctn := app.GetContainerInstance()

tracker := ctn.Tracker()
tracker.Flush()

// Get flags.
debug := viper.GetBool("debug")
binPath := viper.GetString("bin-path")
furyctlPath := viper.GetString("config")
outDir := viper.GetString("outdir")
distroLocation := viper.GetString("distro-location")
gitProtocol := viper.GetString("git-protocol")
skipDepsDownload := viper.GetBool("skip-deps-download")
skipDepsValidation := viper.GetBool("skip-deps-validation")
fromVersion := viper.GetString("from")
kind := viper.GetString("kind")
// Get Current dir.
logrus.Debug("Getting current directory path...")

currentDir, err := os.Getwd()
if err != nil {
cmdEvent.AddErrorMessage(err)
tracker.Track(cmdEvent)

return fmt.Errorf("error while getting current directory: %w", err)
}

// Get home dir.
logrus.Debug("Getting Home directory path...")
homeDir, err := os.UserHomeDir()
if err != nil {
cmdEvent.AddErrorMessage(err)
tracker.Track(cmdEvent)

return fmt.Errorf("error while getting user home directory: %w", err)
}

if binPath == "" {
binPath = path.Join(homeDir, ".furyctl", "bin")
}

parsedGitProtocol := (git.Protocol)(gitProtocol)

if outDir == "" {
outDir = currentDir
}

// Init packages.
execx.Debug = debug

// Check that the version passed by the user is semVer valid.
if fromVersion != "" {
if _, err := semver.NewVersion(fromVersion); err != nil {
cmdEvent.AddErrorMessage(err)
tracker.Track(cmdEvent)

return fmt.Errorf("'%s' is not a valid version string: %w", fromVersion, err)
}
}

// Load the configuration file only if we need to get some of the values from there.
// It's time consuming and requires downloading stuff from Internet.
if kind == "" || fromVersion == "" {
executor := execx.NewStdExecutor()

distrodl := &dist.Downloader{}
depsvl := dependencies.NewValidator(executor, binPath, furyctlPath, false)

// Init first half of collaborators.
client := netx.NewGoGetterClient()

if distroLocation == "" {
distrodl = dist.NewCachingDownloader(client, outDir, parsedGitProtocol, "")
} else {
distrodl = dist.NewDownloader(client, parsedGitProtocol, "")
}

// Validate base requirements.
if err := depsvl.ValidateBaseReqs(); err != nil {
cmdEvent.AddErrorMessage(err)
tracker.Track(cmdEvent)

return fmt.Errorf("error while validating requirements: %w", err)
}

// Download the distribution.
logrus.Info("Downloading distribution...")

res, err := distrodl.Download(distroLocation, furyctlPath)
if err != nil {
cmdEvent.AddErrorMessage(err)
tracker.Track(cmdEvent)

return fmt.Errorf("error while downloading distribution: %w", err)
}

basePath := path.Join(outDir, ".furyctl", res.MinimalConf.Metadata.Name)

// Init second half of collaborators.
depsdl := dependencies.NewCachingDownloader(client, homeDir, basePath, binPath, parsedGitProtocol)

// Validate the furyctl.yaml file.
logrus.Info("Validating configuration file...")
if err := config.Validate(furyctlPath, res.RepoPath); err != nil {
cmdEvent.AddErrorMessage(err)
tracker.Track(cmdEvent)

return fmt.Errorf("error while validating configuration file: %w", err)
}

// Download the dependencies.
if !skipDepsDownload {
logrus.Info("Downloading dependencies...")
if _, err := depsdl.DownloadTools(res.DistroManifest); err != nil {
cmdEvent.AddErrorMessage(ErrDownloadDependenciesFailed)
tracker.Track(cmdEvent)

return fmt.Errorf("%w: %v", ErrDownloadDependenciesFailed, err)
}
}

// Validate the dependencies, unless explicitly told to skip it.
if !skipDepsValidation {
logrus.Info("Validating dependencies...")
if err := depsvl.Validate(res); err != nil {
cmdEvent.AddErrorMessage(err)
tracker.Track(cmdEvent)

return fmt.Errorf("error while validating dependencies: %w", err)
}
}

logrus.Debugf("either kind or fromVersion is not specified, reading them from the configuration file in path %s.", furyctlPath)
furyctlConf, err := yamlx.FromFileV3[distroconf.Furyctl](furyctlPath)
if err != nil {
cmdEvent.AddErrorMessage(err)
tracker.Track(cmdEvent)

return fmt.Errorf("error while reading configuration file in path %s: %w", furyctlPath, err)
}
if kind == "" {
kind = furyctlConf.Kind
logrus.Debugf("got kind %s from the configuration file.", kind)
}
if fromVersion == "" {
fromVersion = furyctlConf.Spec.DistributionVersion
logrus.Debugf("got version %s from the configuration file.", fromVersion)
}
}

// We don't need the starting v in the version. Drop it if the user passes it.
fromVersion, _ = strings.CutPrefix(fromVersion, "v")

globPattern := fmt.Sprintf("%s/%s/%s-*", "upgrades", strings.ToLower(kind), fromVersion)
availablePaths, err := fs.Glob(configs.Tpl, globPattern)
logrus.Debug("found folders: ", availablePaths)
if err != nil {
cmdEvent.AddErrorMessage(err)
tracker.Track(cmdEvent)

return fmt.Errorf("error while getting the upgrade paths for version %s: %w", fromVersion, err)
}

if len(availablePaths) > 0 {
var targetVersions []string
for _, match := range availablePaths {
f, err := fs.Stat(configs.Tpl, match)
if err != nil {
cmdEvent.AddErrorMessage(err)
tracker.Track(cmdEvent)

return fmt.Errorf("error while checking filesystem: %w", err)
}
if f.IsDir() {
fromToVersions := strings.Split(filepath.Base(match), "-")
toVersion := fromToVersions[len(fromToVersions)-1]
targetVersions = append(targetVersions, toVersion)
}
}
logrus.Infof("Available upgrade paths for version %s of kind %s are: %s", fromVersion, kind, strings.Join(targetVersions, ", "))
} else {
logrus.Infof("There are no upgrade paths available for version %s of kind %s", fromVersion, kind)
}
cmdEvent.AddSuccessMessage("upgrade paths successfully retrieved")
tracker.Track(cmdEvent)

return nil
},
}

upgradePathsCmd.Flags().StringP(
"bin-path",
"b",
"",
"Path to the folder where all the dependencies' binaries are installed",
)

upgradePathsCmd.Flags().StringP(
"config",
"c",
"furyctl.yaml",
"Path to the configuration file",
)

upgradePathsCmd.Flags().StringP(
"distro-location",
"",
"",
"Location where to download schemas, defaults and the distribution manifests from. "+
"It can either be a local path (eg: /path/to/fury/distribution) or "+
"a remote URL (eg: git::[email protected]:sighupio/fury-distribution?depth=1&ref=BRANCH_NAME). "+
"Any format supported by hashicorp/go-getter can be used.",
)

upgradePathsCmd.Flags().Bool(
"skip-deps-download",
false,
"Skip downloading the binaries",
)

upgradePathsCmd.Flags().Bool(
"skip-deps-validation",
false,
"Skip validating dependencies",
)

upgradePathsCmd.Flags().String(
"from",
"",
"Show upgrade paths for the version specified (eg. 1.29.2) instead of the distribution version in the configuration file.",
)

upgradePathsCmd.Flags().StringP(
"kind",
"k",
"",
"Show upgrade paths for the kind of cluster specified (eg: EKSCluster, KFDDistribution, OnPremises) instead of the kind defined in the configuration file.",
)

return upgradePathsCmd
}

0 comments on commit aaf5885

Please sign in to comment.