Skip to content

Commit 2b126cf

Browse files
authored
image-mapper: helm chart support (#256)
When migrating a Helm chart to Chainguard it's a bit of a chore to figure out: 1. Which values you need to override 2. Which images you should replace the values with 3. Whether there are any tags you need to adjust. This change adds subcommands that streamline the process. This involves quite a lot of changes to the existing mapper: 1. Make it possible to match inactive tags by fetching the full list of tags from data.chaingurd.dev. Helm charts are tightly coupled to specific versions and therefore it isn't reasonable to expect users to bump a given version of a chart to active tags. Therefore, we need to aim to match the closest version possible. 2. Make it possible to map to a custom repository prefix. Chances are, users are using an internal mirror or proxy when they deploy images via Helm. 3. Make it possible for the mapper to ignore certain tags. We typically don't want/need to use -dev tags in Helm charts as our non-dev images *should* drop in nicely. 4. Nest all the commands under a `map` command. This has nicer semantics. 5. Overhaul the docs to account for multiple commands. I've also had to tweak and fix some tests and other bits that I found while I was testing the Helm feature.
1 parent 8fe7be4 commit 2b126cf

File tree

29 files changed

+3250
-196
lines changed

29 files changed

+3250
-196
lines changed

image-mapper/README.md

Lines changed: 58 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -2,108 +2,96 @@
22

33
A tool for matching non-Chainguard images to their Chainguard equivalents.
44

5-
## Usage
5+
## Build
66

77
Build the tool.
88

99
```
1010
$ go build -o image-mapper .
1111
```
1212

13-
Then, provide the images to map on the command line.
13+
You can also build and run the tool with Docker.
1414

1515
```
16-
$ ./image-mapper ghcr.io/stakater/reloader:v1.4.1 registry.k8s.io/sig-storage/livenessprobe:v2.13.1
17-
ghcr.io/stakater/reloader:v1.4.1 -> cgr.dev/chainguard/stakater-reloader-fips:v1.4.12
18-
ghcr.io/stakater/reloader:v1.4.1 -> cgr.dev/chainguard/stakater-reloader:v1.4.12
19-
registry.k8s.io/sig-storage/livenessprobe:v2.13.1 -> cgr.dev/chainguard/kubernetes-csi-livenessprobe:v2.17.0
20-
```
21-
22-
You'll notice that the mapper increments the tag to the closest version
23-
supported by Chainguard. To benefit from continued CVE remediation, it's
24-
important, where possible, to use tags that are being actively maintained.
16+
# Build the image
17+
docker build -t image-mapper .
2518
26-
You can also provide a list of images (one image per line) via stdin when the first
27-
argument is `-`.
19+
# Run for an individual image
20+
docker run -it --rm image-mapper map ghcr.io/stakater/reloader:v1.4.1
2821
29-
```
30-
$ cat ./images.txt | ./image-mapper -
22+
# Or, pass a list of images from a text file
23+
docker run -i --rm image-mapper -- map - < images.txt
3124
```
3225

33-
## Options
26+
## Basic Usage
3427

35-
### Output
28+
### Map
3629

37-
Configure the output format with the `-o` flag. Supported formats are: `csv`,
38-
`json` and `text`.
30+
The `map` command maps images provided on the command line.
3931

4032
```
41-
$ ./image-mapper ghcr.io/stakater/reloader:v1.4.1 registry.k8s.io/sig-storage/livenessprobe:v2.13.1 -o json | jq -r .
42-
[
43-
{
44-
"image": "ghcr.io/stakater/reloader:v1.4.1",
45-
"results": [
46-
"cgr.dev/chainguard/stakater-reloader-fips:v1.4.12",
47-
"cgr.dev/chainguard/stakater-reloader:v1.4.12"
48-
]
49-
},
50-
{
51-
"image": "registry.k8s.io/sig-storage/livenessprobe:v2.13.1",
52-
"results": [
53-
"cgr.dev/chainguard/kubernetes-csi-livenessprobe:v2.17.0"
54-
]
55-
}
56-
]
33+
$ ./image-mapper map ghcr.io/stakater/reloader:v1.4.1 registry.k8s.io/sig-storage/livenessprobe:v2.13.1
34+
ghcr.io/stakater/reloader:v1.4.1 -> cgr.dev/chainguard/stakater-reloader-fips:v1.4.12
35+
ghcr.io/stakater/reloader:v1.4.1 -> cgr.dev/chainguard/stakater-reloader:v1.4.12
36+
registry.k8s.io/sig-storage/livenessprobe:v2.13.1 -> cgr.dev/chainguard/kubernetes-csi-livenessprobe:v2.17.0
5737
```
5838

39+
You can also provide a list of images (one image per line) via stdin when the first
40+
argument is `-`.
41+
5942
```
60-
$ ./image-mapper ghcr.io/stakater/reloader:v1.4.1 registry.k8s.io/sig-storage/livenessprobe:v2.13.1 -o csv
61-
ghcr.io/stakater/reloader:v1.4.1,[cgr.dev/chainguard/stakater-reloader-fips:v1.4.12 cgr.dev/chainguard/stakater-reloader:v1.4.12]
62-
registry.k8s.io/sig-storage/livenessprobe:v2.13.1,[cgr.dev/chainguard/kubernetes-csi-livenessprobe:v2.17.0]
43+
$ cat ./images.txt | ./image-mapper map -
44+
ghcr.io/stakater/reloader:v1.4.1 -> cgr.dev/chainguard/stakater-reloader-fips:v1.4.12
45+
ghcr.io/stakater/reloader:v1.4.1 -> cgr.dev/chainguard/stakater-reloader:v1.4.12
46+
registry.k8s.io/sig-storage/livenessprobe:v2.13.1 -> cgr.dev/chainguard/kubernetes-csi-livenessprobe:v2.17.0
6347
```
6448

65-
### Ignore Tiers (i.e FIPS)
66-
67-
The output will map both FIPS and non-FIPS variants. You can exclude FIPS with
68-
the `--ignore-tiers` flag.
49+
You'll notice that the mapper increments the tag to the closest version
50+
supported by Chainguard. To benefit from continued CVE remediation, it's
51+
important, where possible, to use tags that are being actively maintained.
6952

70-
```
71-
$ ./image-mapper prom/prometheus
72-
prom/prometheus -> cgr.dev/chainguard/prometheus-fips:latest
73-
prom/prometheus -> cgr.dev/chainguard/prometheus-iamguarded-fips:latest
74-
prom/prometheus -> cgr.dev/chainguard/prometheus-iamguarded:latest
75-
prom/prometheus -> cgr.dev/chainguard/prometheus:latest
76-
77-
$ ./image-mapper prom/prometheus --ignore-tiers=FIPS
78-
prom/prometheus -> cgr.dev/chainguard/prometheus-iamguarded:latest
79-
prom/prometheus -> cgr.dev/chainguard/prometheus:latest
80-
```
53+
Refer to [this page](./docs/map.md) for more details.
8154

82-
### Ignore Iamguarded
55+
### Helm
8356

84-
The mapper will also return matches for our `-iamguarded` images. These images
85-
are designed specifically to work with Chainguard's Helm charts. If you aren't
86-
interested in using our charts, you can exclude those matches with
87-
`--ignore-iamguarded`.
57+
The `helm-chart` and `helm-values` subcommands extract image related values and
58+
map them to Chainguard.
8859

8960
```
90-
$ ./image-mapper prom/prometheus --ignore-iamguarded
91-
prom/prometheus -> cgr.dev/chainguard/prometheus-fips:latest
92-
prom/prometheus -> cgr.dev/chainguard/prometheus:latest
61+
$ ./image-mapper map helm-chart argocd/argo-cd
62+
redis-ha:
63+
image:
64+
repository: cgr.dev/chainguard/redis # Original: ecr-public.aws.com/docker/library/redis
65+
tag: 8.2.2 # Original: 8.2.2-alpine
66+
configmapTest:
67+
image:
68+
repository: cgr.dev/chainguard/shellcheck # Original: koalaman/shellcheck
69+
tag: v0.11.0-dev # Original: v0.10.0
70+
haproxy:
71+
image:
72+
repository: cgr.dev/chainguard/haproxy # Original: ecr-public.aws.com/docker/library/haproxy
73+
exporter:
74+
image: cgr.dev/chainguard/prometheus-redis-exporter # Original: ghcr.io/oliver006/redis_exporter
75+
global:
76+
image:
77+
repository: cgr.dev/chainguard/argocd # Original: quay.io/argoproj/argocd
78+
...
9379
```
9480

95-
## Docker
96-
9781
```
98-
# Build the image
99-
docker build -t image-mapper .
82+
$ helm show values argocd/argo-cd | ./image-mapper map helm-values -
83+
global:
84+
image:
85+
repository: cgr.dev/chainguard/argocd # Original: quay.io/argoproj/argocd
86+
dex:
87+
image:
88+
repository: cgr.dev/chainguard/dex # Original: ghcr.io/dexidp/dex
89+
...
90+
```
10091

101-
# Run for an individual image
102-
docker run -it --rm image-mapper ghcr.io/stakater/reloader:v1.4.1
92+
These commands provide values overrides that you can pass to `helm install`.
10393

104-
# Or, pass a list of images from a text file
105-
docker run -i --rm image-mapper -- - < images.txt
106-
```
94+
Refer to [this page](./docs/map_helm.md) for more details.
10795

10896
## Development
10997

image-mapper/cmd/map.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
"github.com/chainguard-dev/customer-success/scripts/image-mapper/internal/mapper"
8+
"github.com/spf13/cobra"
9+
)
10+
11+
func init() {
12+
rootCmd.AddCommand(
13+
MapCommand(),
14+
)
15+
}
16+
17+
func MapCommand() *cobra.Command {
18+
opts := struct {
19+
OutputFormat string
20+
IgnoreTiers []string
21+
IgnoreIamguarded bool
22+
Repo string
23+
}{}
24+
cmd := &cobra.Command{
25+
Use: "map",
26+
Short: "Map upstream image references to Chainguard images.",
27+
Args: cobra.MinimumNArgs(1),
28+
RunE: func(cmd *cobra.Command, args []string) error {
29+
output, err := mapper.NewOutput(opts.OutputFormat)
30+
if err != nil {
31+
return fmt.Errorf("constructing output: %w", err)
32+
}
33+
34+
var ignoreFns []mapper.IgnoreFn
35+
if len(opts.IgnoreTiers) > 0 {
36+
ignoreFns = append(ignoreFns, mapper.IgnoreTiers(opts.IgnoreTiers))
37+
}
38+
if opts.IgnoreIamguarded {
39+
ignoreFns = append(ignoreFns, mapper.IgnoreIamguarded())
40+
}
41+
m, err := mapper.NewMapper(cmd.Context(), mapper.WithRepository(opts.Repo), mapper.WithIgnoreFns(ignoreFns...))
42+
if err != nil {
43+
return fmt.Errorf("creating mapper: %w", err)
44+
}
45+
46+
it := mapper.NewArgsIterator(args)
47+
if args[0] == "-" {
48+
it = mapper.NewReaderIterator(os.Stdin)
49+
}
50+
51+
mappings, err := m.MapAll(it)
52+
if err != nil {
53+
return fmt.Errorf("mapping images: %w", err)
54+
}
55+
56+
return output(os.Stdout, mappings)
57+
},
58+
}
59+
60+
rootCmd.Flags().StringVarP(&opts.OutputFormat, "output", "o", "text", "Output format (csv, json, text, customer-yaml)")
61+
rootCmd.Flags().StringSliceVar(&opts.IgnoreTiers, "ignore-tiers", []string{}, "Ignore Chainguard repos of specific tiers (PREMIUM, APPLICATION, BASE, FIPS, AI)")
62+
rootCmd.Flags().BoolVar(&opts.IgnoreIamguarded, "ignore-iamguarded", false, "Ignore iamguarded images")
63+
rootCmd.Flags().StringVar(&opts.Repo, "repository", "cgr.dev/chainguard", "Modifies the repository URI in the mappings. For instance, registry.internal.dev/chainguard would result in registry.internal.dev/chainguard/<image> in the output.")
64+
65+
cmd.AddCommand(
66+
MapHelmChartCommand(),
67+
MapHelmValuesCommand(),
68+
)
69+
70+
return cmd
71+
}

image-mapper/cmd/map_helm.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"os"
7+
8+
"github.com/chainguard-dev/customer-success/scripts/image-mapper/internal/helm"
9+
"github.com/chainguard-dev/customer-success/scripts/image-mapper/internal/mapper"
10+
"github.com/spf13/cobra"
11+
)
12+
13+
func MapHelmChartCommand() *cobra.Command {
14+
opts := struct {
15+
Repo string
16+
ChartRepo string
17+
ChartVersion string
18+
}{}
19+
cmd := &cobra.Command{
20+
Use: "helm-chart",
21+
Short: "Extract image related values from a Helm chart and map them to Chainguard.",
22+
Example: `
23+
# Map a Helm chart. This requires that the Chart repo has been added with 'helm repo add' beforehand.
24+
image-mapper map helm-chart argocd/argo-cd
25+
26+
# Override the repository in the mappings with your own mirror or proxy. For instance, cgr.dev/chainguard/<image> would become registry.internal/cgr/<image> in the output.
27+
image-mapper map helm-chart argocd/argo-cd --repository=registry.internal/cgr
28+
29+
# Map a specific version of a Helm chart.
30+
image-mapper map helm-chart argocd/argo-cd --chart-version=9.0.0
31+
32+
# Specify a remote Chart repostory. This means the repository doesn't need to be added with 'helm repo add'.
33+
image-mapper map helm-chart argo-cd --chart-repo=https://argoproj.github.io/argo-helm
34+
35+
# Specify a specific version of a remote Chart.
36+
image-mapper map helm-chart argo-cd --chart-repo=https://argoproj.github.io/argo-helm --chart-version=9.0.0
37+
`,
38+
Args: cobra.MinimumNArgs(1),
39+
RunE: func(cmd *cobra.Command, args []string) error {
40+
chart := helm.ChartDescriptor{
41+
Name: args[0],
42+
Repository: opts.ChartRepo,
43+
Version: opts.ChartVersion,
44+
}
45+
output, err := helm.MapChart(cmd.Context(), chart, mapper.WithRepository(opts.Repo))
46+
if err != nil {
47+
return fmt.Errorf("mapping values: %w", err)
48+
}
49+
50+
if _, err := os.Stdout.Write(output); err != nil {
51+
return fmt.Errorf("writing output: %w", err)
52+
}
53+
54+
return nil
55+
},
56+
}
57+
58+
cmd.Flags().StringVar(&opts.Repo, "repository", "cgr.dev/chainguard", "Modifies the repository URI in the mappings. For instance, registry.internal.dev/chainguard would result in registry.internal.dev/chainguard/<image> in the output.")
59+
cmd.Flags().StringVar(&opts.ChartRepo, "chart-repo", "", "The chart repository url to locate the requested chart.")
60+
cmd.Flags().StringVar(&opts.ChartVersion, "chart-version", "", "A version constraint for the chart version.")
61+
62+
return cmd
63+
}
64+
65+
func MapHelmValuesCommand() *cobra.Command {
66+
opts := struct {
67+
Repo string
68+
}{}
69+
cmd := &cobra.Command{
70+
Use: "helm-values",
71+
Short: "Extract image related values from a Helm values file and map them to Chainguard.",
72+
Example: `
73+
# Map images in the values returned by 'helm show values'
74+
helm show values argocd/argo-cd | image-mapper map helm-values -
75+
76+
# Map images in a values file on disk.
77+
helm show values argocd/argo-cd > values.yaml
78+
image-mapper map helm-values values.yaml
79+
80+
# Override the repository in the mappings with your own mirror or proxy. For instance, cgr.dev/chainguard/<image> would become registry.internal/cgr/<image> in the output.
81+
image-mapper map helm-values values.yaml --repository=registry.internal/cgr
82+
`,
83+
Args: cobra.MinimumNArgs(1),
84+
RunE: func(cmd *cobra.Command, args []string) error {
85+
var (
86+
input []byte
87+
err error
88+
)
89+
switch args[0] {
90+
case "-":
91+
input, err = io.ReadAll(os.Stdin)
92+
if err != nil {
93+
return fmt.Errorf("reading stdin: %w", err)
94+
}
95+
default:
96+
input, err = os.ReadFile(args[0])
97+
if err != nil {
98+
return fmt.Errorf("reading file: %s: %w", args[0], err)
99+
}
100+
}
101+
102+
output, err := helm.MapValues(cmd.Context(), input, mapper.WithRepository(opts.Repo))
103+
if err != nil {
104+
return fmt.Errorf("mapping values: %w", err)
105+
}
106+
107+
if _, err := os.Stdout.Write(output); err != nil {
108+
return fmt.Errorf("writing output: %w", err)
109+
}
110+
111+
return nil
112+
},
113+
}
114+
115+
cmd.Flags().StringVar(&opts.Repo, "repository", "cgr.dev/chainguard", "Modifies the repository URI in the mappings. For instance, registry.internal.dev/chainguard would result in registry.internal.dev/chainguard/<image> in the output.")
116+
117+
return cmd
118+
}

0 commit comments

Comments
 (0)