Skip to content

Commit

Permalink
Merge pull request #536 from sighupio/feat/get-ugprade-paths
Browse files Browse the repository at this point in the history
feat: get ugprade-paths command
  • Loading branch information
ralgozino authored Sep 4, 2024
2 parents 22f269f + 9d459e4 commit ca87805
Show file tree
Hide file tree
Showing 5 changed files with 341 additions and 12 deletions.
26 changes: 19 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

> 💡 Learn more about the Kubernetes Fury Distribution in the [official site](https://kubernetesfury.com).
If you're looking for the old documentation, you can find it [here](https://github.com/sighupio/furyctl/blob/release-v0.11/README.md).
If you're looking for the old documentation for furyctl legacy, you can find it [here](https://github.com/sighupio/furyctl/blob/release-v0.11/README.md).

### Available providers

Expand Down Expand Up @@ -139,8 +139,8 @@ See all the available commands and their usage by running `furyctl help`.

> 💡 **TIP**
>
> You can follow the Kubernetes Fury Distribution quick start guides in KFD's official documentation site:
> <https://docs.kubernetesfury.com/docs/quickstart/quickstart>
> You can follow the Kubernetes Fury Distribution quick start guides for cloud and on-premises installations in KFD's official documentation site:
> https://docs.kubernetesfury.com/docs/quickstart/quickstart
<!-- line left blank as spacer -->

Expand All @@ -167,7 +167,7 @@ Additionally, the schema of the file is versioned with the `apiVersion` field, s
To scaffold a configuration file to use as a starter, you use the following command:

```console
furyctl create config --version v1.29.1 --kind "EKSCluster"
furyctl create config --version v1.29.3 --kind "EKSCluster"
```

> 💡 **TIP**
Expand Down Expand Up @@ -246,23 +246,35 @@ furyctl create cluster --config /path/to/your/furyctl.yaml

#### 3. Upgrade a cluster

> [!NOTE]
> This is a quick overview of the process. For a more complete documentation please see [the universal upgrade guide](./docs/upgrades/kfd/README.md).
Upgrading a cluster is a process that can be divided into two steps: upgrading the fury version and running the migrations (if present).

The first step consists in bringing the cluster up to date with the latest version of the Kubernetes Fury Distribution. This is done by running the following command:
The first step consists in bringing the cluster up to date with the latest version of the Kubernetes Fury Distribution. This is done by:

1. Identifying the target version to which upgrade to with:

```bash
furyctl get upgrade-paths
```

2. Bumping the version in the configuration file to the desired one.
3. Upgrading the cluster:

```console
furyctl apply --upgrade --config /path/to/your/furyctl.yaml
```

Once that is done, if you were also planning to move to a different provider (e.g.: `opensearch` to `loki`), you can run the following command to run the migrations:
Once that is done, if you were also planning to move to a different configuration (e.g.: changing from logging type `opensearch` to `loki`), you can run the following command to run the migrations as usual:

```console
furyctl apply --config /path/to/your/furyctl.yaml
```

> ❗️ **WARNING**
>
> You must first upgrade the cluster using the old provider(e.g.: `opensearch`), update the configuration file to use the new provider(e.g.: `loki`) and then run the command above.
> You must first upgrade the cluster using the old configuration (e.g.: logging type `opensearch`), update the configuration file to use the new type (e.g.: `loki`) and then run the command above.
#### 3.1. Advanced upgrade options (OnPremises provider only)

Expand Down
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
}
295 changes: 295 additions & 0 deletions cmd/get/upgrade-paths.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,295 @@
// Copyright (c) 2017-present SIGHUP s.r.l All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

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 the "--from" or "--kind" parameters are specified, the command will give the upgrade path for those instead.
Examples:
- furyctl get upgrade-paths will show the available upgrade paths for the kind and 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
}
Loading

0 comments on commit ca87805

Please sign in to comment.