Merge pull request #239 from helmfile/local-oci-integration-test
Add integration test for #238 with local docker registry as a OCI-based helm chart repo
This commit is contained in:
commit
93b1ac2b19
|
|
@ -0,0 +1,10 @@
|
|||
This directory contains a set of Go test source and testdata
|
||||
to test the helmfile template's rendering result by calling `helmfile build` or `helmfile template` on test input
|
||||
and comparing the output against the snapshot.
|
||||
|
||||
The `testdata` directory is composed of:
|
||||
|
||||
- `charts`: The Helm charts used from within test helmfile configs (`snapshpt/*/input.yaml`) as local charts and remote charts
|
||||
- `snapshot/$NAME/input.yaml`: The input helmfile config for the test case of `$NAME`
|
||||
- `snapshot/$NAME/output.yaml`: The expected output of the helmfile command
|
||||
- `snapshot/$NAME/config.yaml`: The snapshot test configuration file. See the `Config` struct defined in `snapshot_test.go` for more information
|
||||
|
|
@ -2,21 +2,34 @@ package helmfile
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func TestHelmfileTemplateWithBuildCommand(t *testing.T) {
|
||||
type Config struct {
|
||||
LocalDockerRegistry struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
Port int `yaml:"port"`
|
||||
} `yaml:"localDockerRegistry"`
|
||||
ChartifyTempDir string `yaml:"chartifyTempDir"`
|
||||
HelmfileArgs []string `yaml:"helmfileArgs"`
|
||||
}
|
||||
|
||||
_, filename, _, _ := runtime.Caller(0)
|
||||
projectRoot := filepath.Join(filepath.Dir(filename), "..", "..", "..", "..")
|
||||
helmfileBin := filepath.Join(projectRoot, "helmfile")
|
||||
testdataDir := "testdata/snapshot"
|
||||
chartsDir := "testdata/charts"
|
||||
|
||||
entries, err := os.ReadDir(testdataDir)
|
||||
require.NoError(t, err)
|
||||
|
|
@ -28,23 +41,152 @@ func TestHelmfileTemplateWithBuildCommand(t *testing.T) {
|
|||
|
||||
name := e.Name()
|
||||
|
||||
t.Run(name, func(t *testing.T) {
|
||||
inputFile := filepath.Join(testdataDir, name, "input.yaml")
|
||||
|
||||
want, err := os.ReadFile(filepath.Join(testdataDir, name, "output.yaml"))
|
||||
wd, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
|
||||
// We read the config from `testdata/snapshot/$CASE_NAME/config.yaml`.
|
||||
// It's optional so the test won't fail even if the config file does not exist.
|
||||
|
||||
var config Config
|
||||
|
||||
configFile := filepath.Join(testdataDir, name, "config.yaml")
|
||||
if configData, err := os.ReadFile(configFile); err == nil {
|
||||
if err := yaml.Unmarshal(configData, &config); err != nil {
|
||||
t.Fatalf("Unable to load %s: %v", configFile, err)
|
||||
}
|
||||
}
|
||||
|
||||
// We run `helmfile build` by default.
|
||||
// If you want to test `helmfile template`, set the following in the config.yaml:
|
||||
//
|
||||
// helmfileArgs:
|
||||
// - template
|
||||
helmfileArgs := config.HelmfileArgs
|
||||
if len(helmfileArgs) == 0 {
|
||||
helmfileArgs = append(helmfileArgs, "build")
|
||||
}
|
||||
|
||||
t.Run(name, func(t *testing.T) {
|
||||
// Use the specific chartify tempdir for easy debugging and the test reproducibility.
|
||||
// We do snapshot testing in this test. The default chartify tempdir is a random directory created within the os temp dir.
|
||||
// Without making it a static path, it's unnecessarily hard to snapshot test it, as the dir path embedded in the output changes
|
||||
// on each test run.
|
||||
chartifyTempDir := config.ChartifyTempDir
|
||||
if chartifyTempDir == "" {
|
||||
chartifyTempDir = "chartify_temp"
|
||||
}
|
||||
|
||||
// We set the envvar probided by chartify, CHARTIFY_TEMPDIR, to make the tempdir static.
|
||||
chartifyTempDir = filepath.Join(wd, chartifyTempDir)
|
||||
t.Setenv("CHARTIFY_TEMPDIR", chartifyTempDir)
|
||||
// Ensure there's no dangling and remaining tempdir from the previous run
|
||||
if err := os.RemoveAll(chartifyTempDir); err != nil {
|
||||
t.Fatalf("unable to remove chartify temp dir %q: %v", chartifyTempDir, err)
|
||||
}
|
||||
// Ensure it's removed on test completion
|
||||
t.Cleanup(func() {
|
||||
if err := os.RemoveAll(chartifyTempDir); err != nil {
|
||||
t.Fatalf("unable to remove chartify temp dir %q: %v", chartifyTempDir, err)
|
||||
}
|
||||
})
|
||||
|
||||
// If localDockerRegistry.enabled is set to `true`,
|
||||
// run the docker registry v2 and push the test charts to the registry
|
||||
// so that it can be accessed by helm and helmfile as a oci registry based chart repository.
|
||||
if config.LocalDockerRegistry.Enabled {
|
||||
containerName := "helmfile_docker_registry"
|
||||
|
||||
hostPort := config.LocalDockerRegistry.Port
|
||||
if hostPort < 0 {
|
||||
hostPort = 5000
|
||||
}
|
||||
|
||||
execDocker(t, "run", "-d", "-p", fmt.Sprintf("%d:5000", hostPort), "--restart=always", "--name", containerName, "registry:2")
|
||||
t.Cleanup(func() {
|
||||
execDocker(t, "stop", containerName)
|
||||
execDocker(t, "rm", containerName)
|
||||
})
|
||||
|
||||
// We helm-package and helm-push every test chart saved in the ./testdata/charts directory
|
||||
// to the local registry, so that they can be accessed by helmfile and helm invoked while testing.
|
||||
charts, err := os.ReadDir(chartsDir)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, c := range charts {
|
||||
chartPath := filepath.Join(chartsDir, c.Name())
|
||||
if !c.IsDir() {
|
||||
t.Fatalf("%s is not a directory", c)
|
||||
}
|
||||
tgzFile := execHelmPackage(t, chartPath)
|
||||
_ = execHelm(t, "push", tgzFile, fmt.Sprintf("oci://localhost:%d/myrepo", hostPort))
|
||||
}
|
||||
}
|
||||
|
||||
inputFile := filepath.Join(testdataDir, name, "input.yaml")
|
||||
outputFile := filepath.Join(testdataDir, name, "output.yaml")
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
cmd := exec.CommandContext(ctx, helmfileBin, "-f", inputFile, "build")
|
||||
args := []string{"-f", inputFile}
|
||||
args = append(args, helmfileArgs...)
|
||||
cmd := exec.CommandContext(ctx, helmfileBin, args...)
|
||||
got, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Logf("%s", string(got))
|
||||
t.Logf("Output from %v: %s", args, string(got))
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, string(want), string(got))
|
||||
gotStr := string(got)
|
||||
gotStr = strings.ReplaceAll(gotStr, fmt.Sprintf("chart=%s", wd), "chart=$WD")
|
||||
|
||||
require.NoError(t, err, "Unable to run helmfile with args %v", args)
|
||||
|
||||
if stat, _ := os.Stat(outputFile); stat != nil {
|
||||
want, err := os.ReadFile(outputFile)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, string(want), gotStr)
|
||||
} else {
|
||||
// To update the test golden image(output.yaml), just remove it and rerun this test.
|
||||
// We automatically capture the output to `output.yaml` in the test case directory
|
||||
// when the output.yaml doesn't exist.
|
||||
require.NoError(t, os.WriteFile(outputFile, []byte(gotStr), 0664))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func execDocker(t *testing.T, args ...string) {
|
||||
t.Helper()
|
||||
|
||||
docker := exec.Command("docker", args...)
|
||||
out, err := docker.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Logf("Docker output: %s", string(out))
|
||||
t.Fatalf("Unable to run docker: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func execHelmPackage(t *testing.T, localChart string) string {
|
||||
t.Helper()
|
||||
|
||||
out := execHelm(t, "package", localChart)
|
||||
msg := strings.Split(out, " ")
|
||||
tgzAbsPath := msg[len(msg)-1]
|
||||
return strings.TrimSpace(tgzAbsPath)
|
||||
}
|
||||
|
||||
func execHelm(t *testing.T, args ...string) string {
|
||||
t.Helper()
|
||||
|
||||
cmd := []string{"helm"}
|
||||
cmd = append(cmd, args...)
|
||||
c := strings.Join(cmd, " ")
|
||||
docker := exec.Command("helm", args...)
|
||||
out, err := docker.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Logf("%s: %s", c, string(out))
|
||||
t.Fatalf("Unable to run %s: %v", c, err)
|
||||
}
|
||||
|
||||
return string(out)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
*.tgz
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
# Patterns to ignore when building packages.
|
||||
# This supports shell glob matching, relative path matching, and
|
||||
# negation (prefixed with !). Only one pattern per line.
|
||||
.DS_Store
|
||||
# Common VCS dirs
|
||||
.git/
|
||||
.gitignore
|
||||
.bzr/
|
||||
.bzrignore
|
||||
.hg/
|
||||
.hgignore
|
||||
.svn/
|
||||
# Common backup files
|
||||
*.swp
|
||||
*.bak
|
||||
*.tmp
|
||||
*.orig
|
||||
*~
|
||||
# Various IDEs
|
||||
.project
|
||||
.idea/
|
||||
*.tmproj
|
||||
.vscode/
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
apiVersion: v2
|
||||
name: raw
|
||||
description: A Helm chart for Kubernetes
|
||||
type: application
|
||||
version: 0.1.0
|
||||
appVersion: "1.16.0"
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
You should be able to test pushing this chart to a local registry with:
|
||||
|
||||
```
|
||||
$ helm package .
|
||||
Successfully packaged chart and saved it to: /home/mumoshu/p/helmfile/test/e2e/template/helmfile/testdata/charts/raw/raw-0.1.0.tgz
|
||||
|
||||
$ helm push raw-0.1.0.tgz oci://localhost:5000/myrepo/raw
|
||||
Pushed: localhost:5000/myrepo/raw/raw:0.1.0
|
||||
Digest: sha256:9b7c9633b519b024fdbec1db795bc2dd8b0009149135908a3aafc55280146ad9
|
||||
```
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{{- range $i, $r := $.Values.templates }}
|
||||
{{- if gt $i 0 }}
|
||||
---
|
||||
{{- end }}
|
||||
{{- (tpl $r $) }}
|
||||
{{- end }}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
templates: []
|
||||
|
||||
##
|
||||
## Example: Uncomment the below and run `helm template ./`:
|
||||
##
|
||||
#
|
||||
# templates:
|
||||
# - |
|
||||
# apiVersion: v1
|
||||
# kind: ConfigMap
|
||||
# metadata:
|
||||
# name: {{ .Release.Name }}-1
|
||||
# namespace: {{ .Release.Namespace }}
|
||||
# data:
|
||||
# foo: {{ .Values.foo }}
|
||||
# - |
|
||||
# apiVersion: v1
|
||||
# kind: ConfigMap
|
||||
# metadata:
|
||||
# name: {{ .Release.Name }}-2
|
||||
# namespace: {{ .Release.Namespace }}
|
||||
# data:
|
||||
# foo: {{ .Values.foo }}
|
||||
# values:
|
||||
# foo: FOO
|
||||
#
|
||||
##
|
||||
## Expected Output:
|
||||
##
|
||||
#
|
||||
# ---
|
||||
# # Source: raw/templates/resources.yaml
|
||||
# apiVersion: v1
|
||||
# kind: ConfigMap
|
||||
# metadata:
|
||||
# name: release-name-1
|
||||
# namespace: default
|
||||
# data:
|
||||
# foo:
|
||||
# ---
|
||||
# # Source: raw/templates/resources.yaml
|
||||
# apiVersion: v1
|
||||
# kind: ConfigMap
|
||||
# metadata:
|
||||
# name: release-name-2
|
||||
# namespace: default
|
||||
# data:
|
||||
# foo:
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
localDockerRegistry:
|
||||
enabled: true
|
||||
port: 5000
|
||||
chartifyTempDir: temp1
|
||||
helmfileArgs:
|
||||
- template
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
releases:
|
||||
- name: foo
|
||||
chart: ../../charts/raw
|
||||
values:
|
||||
- templates:
|
||||
- |
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{`{{ .Release.Name }}`}}-1
|
||||
namespace: {{`{{ .Release.Namespace }}`}}
|
||||
data:
|
||||
foo: FOO
|
||||
dep:
|
||||
templates:
|
||||
- |
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{`{{ .Release.Name }}`}}-2
|
||||
namespace: {{`{{ .Release.Namespace }}`}}
|
||||
data:
|
||||
bar: BAR
|
||||
dependencies:
|
||||
- alias: dep
|
||||
chart: oci://localhost:5000/myrepo/raw
|
||||
version: 0.1.0
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
Building dependency release=foo, chart=$WD/temp1/foo
|
||||
Templating release=foo, chart=$WD/temp1/foo
|
||||
---
|
||||
# Source: raw/templates/charts/dep/templates/resources.yaml
|
||||
# Source: raw/charts/dep/templates/resources.yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: foo-2
|
||||
namespace: default
|
||||
data:
|
||||
bar: BAR
|
||||
---
|
||||
# Source: raw/templates/resources.yaml
|
||||
# Source: raw/templates/resources.yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: foo-1
|
||||
namespace: default
|
||||
data:
|
||||
foo: FOO
|
||||
|
||||
Loading…
Reference in New Issue