Skip to content

Commit

Permalink
Add way to hold/ignore images when upgrading compose/charts
Browse files Browse the repository at this point in the history
When upgrading image versions in a compose file or helm chart
there are cases where certain components like a database version
should be static and not change.

This change introduces a more generic concept than previously
planned of an arkade config file. The ignore section includes
paths to images which should be ignored and filtered out
prior to looking up new images.

Tested with the OpenFaaS pro-builder chart, with the path
buildkit.image, I was able to ignore only that image whilst
having the other images inspected/updated.

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <[email protected]>
  • Loading branch information
alexellis committed Jan 30, 2025
1 parent 889135f commit 8a995e3
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 17 deletions.
11 changes: 5 additions & 6 deletions cmd/chart/chart.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,11 @@ import (
func MakeChart() *cobra.Command {

command := &cobra.Command{
Use: "chart",
Short: "Chart utilities",
Long: `Utilities for Helm charts.`,
Aliases: []string{"c"},
Example: ` arkade chart verify --help
`,
Use: "chart",
Short: "Chart utilities",
Long: `Utilities for Helm charts.`,
Aliases: []string{"c"},
Example: ` arkade chart verify --help`,
SilenceUsage: true,
}

Expand Down
55 changes: 51 additions & 4 deletions cmd/chart/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"sync"

"github.com/Masterminds/semver"
"github.com/alexellis/arkade/pkg/config"
"github.com/alexellis/arkade/pkg/helm"
"github.com/google/go-containerregistry/pkg/crane"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -41,13 +42,32 @@ Container images must be specified at the top level, or one level down in the
Returns exit code zero if all images were found on the remote registry.
Otherwise, it returns a non-zero exit code and the updated values.yaml file.`,
Example: `arkade upgrade -f ./chart/values.yaml
arkade upgrade --verbose -f ./chart/values.yaml`,
Otherwise, it returns a non-zero exit code and the updated values.yaml file.
An arkade.yaml file can be colocated with the values.yaml file or specified via
--ignore-file flag
The contents should be in YAML given as:
ignore:
- image
- component1.image
- component2.image
`,
Example: ` # Upgrade and write the changes
arkade chart upgrade -f ./chart/values.yaml --write
# Dry-run mode, don't write the changes (default)
arkade chart upgrade --verbose -f ./chart/values.yaml
# Use an arkade config file to load an ignore list
arkade chart upgrade --ignore-file ./chart/arkade.yaml -f ./chart/values.yaml`,
SilenceUsage: true,
}

command.Flags().StringP("file", "f", "", "Path to values.yaml file")
command.Flags().StringP("ignore-file", "i", "", "Path to an arkade.yaml config file with a list of image paths to ignore defined")

command.Flags().BoolP("verbose", "v", false, "Verbose output")
command.Flags().BoolP("write", "w", false, "Write the updated values back to the file, or stdout when set to false")
command.Flags().IntP("depth", "d", 3, "how many levels deep into the YAML structure to walk looking for image: tags")
Expand All @@ -67,6 +87,8 @@ Otherwise, it returns a non-zero exit code and the updated values.yaml file.`,
return fmt.Errorf("invalid value for flag --file")
}

ignoreFile, _ := cmd.Flags().GetString("ignore-file")

writeFile, _ := cmd.Flags().GetBool("write")

verbose, _ := cmd.Flags().GetBool("verbose")
Expand All @@ -81,6 +103,31 @@ Otherwise, it returns a non-zero exit code and the updated values.yaml file.`,
return fmt.Errorf("--file must be a YAML file")
}

if len(ignoreFile) > 0 {
if _, err := os.Stat(ignoreFile); os.IsNotExist(err) {
return fmt.Errorf("ignore file %s does not exist", ignoreFile)
}
} else {
basePath := path.Dir(file)
defaultConfig := path.Join(basePath, "arkade.yaml")

if _, err := os.Stat(defaultConfig); err == nil {
ignoreFile = defaultConfig
}
}

var arkadeCfg *config.ArkadeConfig

if len(ignoreFile) > 0 {
var err error
arkadeCfg, err = config.Load(ignoreFile)
if err != nil {
return err
}
} else {
arkadeCfg = &config.ArkadeConfig{}
}

if verbose {
log.Printf("Verifying images in: %s\n", file)
}
Expand All @@ -90,7 +137,7 @@ Otherwise, it returns a non-zero exit code and the updated values.yaml file.`,
return err
}

filtered := helm.FilterImagesUptoDepth(values, depth)
filtered := helm.FilterImagesUptoDepth(values, depth, "", arkadeCfg)
if len(filtered) == 0 {
return fmt.Errorf("no images found in %s", file)
}
Expand Down
5 changes: 3 additions & 2 deletions cmd/chart/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"path"
"text/tabwriter"

"github.com/alexellis/arkade/pkg/config"
"github.com/alexellis/arkade/pkg/helm"
"github.com/spf13/cobra"

Expand Down Expand Up @@ -71,8 +72,8 @@ autoscaler ghcr.io/openfaasltd/autoscaler:0.2.5
if err != nil {
return err
}

filtered := helm.FilterImagesUptoDepth(values, depth)
cfg := &config.ArkadeConfig{}
filtered := helm.FilterImagesUptoDepth(values, depth, "", cfg)
if len(filtered) == 0 {
return fmt.Errorf("no images found in %s", file)
}
Expand Down
27 changes: 27 additions & 0 deletions pkg/config/file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package config

import (
"os"

"gopkg.in/yaml.v3"
)

type ArkadeConfig struct {
Ignore []string `yaml:"ignore"`
}

func Load(file string) (*ArkadeConfig, error) {

data, err := os.ReadFile(file)
if err != nil {
return nil, err
}

cfg := &ArkadeConfig{}

if err := yaml.Unmarshal(data, cfg); err != nil {
return nil, err
}

return cfg, nil
}
28 changes: 23 additions & 5 deletions pkg/helm/io.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"reflect"
"strings"

"github.com/alexellis/arkade/pkg/config"
"gopkg.in/yaml.v3"
)

Expand Down Expand Up @@ -59,18 +60,35 @@ func ReplaceValuesInHelmValuesFile(values map[string]string, yamlPath string) (s

// FilterImagesUptoDepth takes a ValuesMap and returns a map of images that
// were found upto max level
func FilterImagesUptoDepth(values ValuesMap, depth int) map[string]string {
func FilterImagesUptoDepth(values ValuesMap, depth int, path string, cfg *config.ArkadeConfig) map[string]string {
images := map[string]string{}

for k, v := range values {

prefix := ""
if len(path) > 0 {
prefix = path + "."
}

if k == "image" && reflect.TypeOf(v).Kind() == reflect.String {
imageUrl := v.(string)
images[imageUrl] = imageUrl
fullPath := prefix + k

ignoreItem := false
if cfg != nil {
for _, ignore := range cfg.Ignore {
if fullPath == ignore {
ignoreItem = true
}
}
}

if !ignoreItem {
imageUrl := v.(string)
images[imageUrl] = imageUrl
}
}

if c, ok := v.(ValuesMap); ok && depth > 0 {
images = mergeMaps(images, FilterImagesUptoDepth(c, depth-1))
images = mergeMaps(images, FilterImagesUptoDepth(c, depth-1, prefix+k, cfg))
}
}
return images
Expand Down

0 comments on commit 8a995e3

Please sign in to comment.