1
0
mirror of https://github.com/helm/chart-testing.git synced 2026-02-05 09:45:14 +01:00

Support installation to existing namespace (#59)

This PR adds two flags to the `install` command: `namespace` and `release-label`. If `namespace` is specified, releases will target that namespace and `release-label` will be used to select deployments and pods for readiness and reading log output.

Fixes #34
This commit is contained in:
Jacob LeGrone
2018-12-14 09:30:36 -05:00
committed by Reinhard Nägele
parent 09eff4860c
commit 228603701a
14 changed files with 125 additions and 84 deletions

View File

@@ -16,9 +16,10 @@ package cmd
import (
"fmt"
"github.com/spf13/viper"
"os"
"github.com/spf13/viper"
"github.com/MakeNowJust/heredoc"
"github.com/helm/chart-testing/pkg/chart"
"github.com/helm/chart-testing/pkg/config"
@@ -61,7 +62,13 @@ func addInstallFlags(flags *flag.FlagSet) {
the ID of a pull request. If not specified, the name of the chart is used`))
flags.String("helm-extra-args", "", heredoc.Doc(`
Additional arguments for Helm. Must be passed as a single quoted string
(e. g. "--timeout 500 --tiller-namespace tiller"`))
(e.g. "--timeout 500 --tiller-namespace tiller"`))
flags.String("namespace", "", heredoc.Doc(`
Namespace to install the release(s) into. If not specified, each release will be
installed in its own randomly generated namespace.`))
flags.String("release-label", "app.kubernetes.io/instance", heredoc.Doc(`
The label to be used as a selector when inspecting resources created by charts.
This is only used if namespace is specified.`))
}
func install(cmd *cobra.Command, args []string) {
@@ -89,6 +96,6 @@ func install(cmd *cobra.Command, args []string) {
}
func bindInstallFlags(flagSet *flag.FlagSet, v *viper.Viper) error {
options := []string{"build-id", "helm-extra-args"}
options := []string{"build-id", "helm-extra-args", "namespace", "release-label"}
return bindFlags(options, flagSet, v)
}

View File

@@ -25,4 +25,4 @@ in given chart directories.
* [ct lint-and-install](ct_lint-and-install.md) - Lint, install, and test a chart
* [ct version](ct_version.md) - Print version information
###### Auto generated by spf13/cobra on 6-Nov-2018
###### Auto generated by spf13/cobra on 17-Nov-2018

View File

@@ -43,8 +43,12 @@ ct install [flags]
--excluded-charts strings Charts that should be skipped. May be specified multiple times
or separate values with commas
--helm-extra-args string Additional arguments for Helm. Must be passed as a single quoted string
(e. g. "--timeout 500 --tiller-namespace tiller"
(e.g. "--timeout 500 --tiller-namespace tiller"
-h, --help help for install
--namespace string Namespace to install the release(s) into. If not specified, each release will be
installed in its own randomly generated namespace.
--release-label string The label to be used as a selector when inspecting resources created by charts.
This is only used if namespace is specified. (default "app.kubernetes.io/instance")
--remote string The name of the Git remote used to identify changed charts (default "origin")
--target-branch string The name of the target branch used to identify changed charts (default "master")
```
@@ -53,4 +57,4 @@ ct install [flags]
* [ct](ct.md) - The Helm chart testing tool
###### Auto generated by spf13/cobra on 6-Nov-2018
###### Auto generated by spf13/cobra on 17-Nov-2018

View File

@@ -35,11 +35,15 @@ ct lint-and-install [flags]
--excluded-charts strings Charts that should be skipped. May be specified multiple times
or separate values with commas
--helm-extra-args string Additional arguments for Helm. Must be passed as a single quoted string
(e. g. "--timeout 500 --tiller-namespace tiller"
(e.g. "--timeout 500 --tiller-namespace tiller"
-h, --help help for lint-and-install
--lint-conf string The config file for YAML linting. If not specified, 'lintconf.yaml'
is searched in the current directory, '$HOME/.ct', and '/etc/ct', in
that order
--namespace string Namespace to install the release(s) into. If not specified, each release will be
installed in its own randomly generated namespace.
--release-label string The label to be used as a selector when inspecting resources created by charts.
This is only used if namespace is specified. (default "app.kubernetes.io/instance")
--remote string The name of the Git remote used to identify changed charts (default "origin")
--target-branch string The name of the target branch used to identify changed charts (default "master")
--validate-maintainers Enabled validation of maintainer account names in chart.yml (default: true).
@@ -50,4 +54,4 @@ ct lint-and-install [flags]
* [ct](ct.md) - The Helm chart testing tool
###### Auto generated by spf13/cobra on 6-Nov-2018
###### Auto generated by spf13/cobra on 17-Nov-2018

View File

@@ -58,4 +58,4 @@ ct lint [flags]
* [ct](ct.md) - The Helm chart testing tool
###### Auto generated by spf13/cobra on 6-Nov-2018
###### Auto generated by spf13/cobra on 17-Nov-2018

View File

@@ -20,4 +20,4 @@ ct version [flags]
* [ct](ct.md) - The Helm chart testing tool
###### Auto generated by spf13/cobra on 6-Nov-2018
###### Auto generated by spf13/cobra on 17-Nov-2018

View File

@@ -16,11 +16,12 @@ package chart
import (
"fmt"
"github.com/helm/chart-testing/pkg/exec"
"path"
"path/filepath"
"strings"
"github.com/helm/chart-testing/pkg/exec"
"github.com/helm/chart-testing/pkg/config"
"github.com/helm/chart-testing/pkg/tool"
"github.com/helm/chart-testing/pkg/util"
@@ -54,23 +55,20 @@ type Git interface {
//
// BuildDependencies builds the chart's dependencies
//
// Lint runs `helm lint` for the given chart
// LintWithValues runs `helm lint` for the given chart using the specified values file.
// Pass a zero value for valuesFile in order to run lint without specifying a values file.
//
// LintWithValues runs `helm lint` for the given chart using the specified values file
//
// Install runs `helm install` for the given chart
//
// InstallWithValues runs `helm install` for the given chart using the specified values file
// InstallWithValues runs `helm install` for the given chart using the specified values file.
// Pass a zero value for valuesFile in order to run install without specifying a values file.
//
// DeleteRelease purges the specified Helm release.
type Helm interface {
Init() error
AddRepo(name string, url string) error
BuildDependencies(chart string) error
Lint(chart string) error
LintWithValues(chart string, valuesFile string) error
Install(chart string, namespace string, release string) error
InstallWithValues(chart string, valuesFile string, namespace string, release string) error
Test(release string) error
DeleteRelease(release string)
}
@@ -93,7 +91,7 @@ type Helm interface {
// GetContainers gets all containers of pod
type Kubectl interface {
DeleteNamespace(namespace string)
WaitForDeployments(namespace string) error
WaitForDeployments(namespace string, selector string) error
GetPodsforDeployment(namespace string, deployment string) ([]string, error)
GetPods(args ...string) ([]string, error)
DescribePod(namespace string, pod string) error
@@ -112,7 +110,7 @@ type Linter interface {
Yamale(yamlFile string, schemaFile string) error
}
// DiretoryLister is the interface
// DirectoryLister is the interface
//
// ListChildDirs lists direct child directories of parentDir given they pass the test function
type DirectoryLister interface {
@@ -162,13 +160,12 @@ type TestResult struct {
// NewTesting creates a new Testing struct with the given config.
func NewTesting(config config.Configuration) Testing {
procExec := exec.NewProcessExecutor(config.Debug)
kubectl := tool.NewKubectl(procExec)
extraArgs := strings.Fields(config.HelmExtraArgs)
testing := Testing{
config: config,
helm: tool.NewHelm(procExec, kubectl, extraArgs),
helm: tool.NewHelm(procExec, extraArgs),
git: tool.NewGit(procExec),
kubectl: kubectl,
kubectl: tool.NewKubectl(procExec),
linter: tool.NewLinter(procExec),
accountValidator: tool.AccountValidator{},
directoryLister: util.DirectoryLister{},
@@ -244,7 +241,7 @@ func (t *Testing) InstallCharts() ([]TestResult, error) {
return t.processCharts(t.InstallChart)
}
// LintAndInstallChart first lints and then installs charts (changed, all, specific) depending on the configuration.
// LintAndInstallCharts first lints and then installs charts (changed, all, specific) depending on the configuration.
func (t *Testing) LintAndInstallCharts() ([]TestResult, error) {
return t.processCharts(t.LintAndInstallChart)
}
@@ -303,16 +300,15 @@ func (t *Testing) LintChart(chart string, valuesFiles []string) TestResult {
}
}
if len(valuesFiles) > 0 {
for _, valuesFile := range valuesFiles {
if err := t.helm.LintWithValues(chart, valuesFile); err != nil {
result.Error = err
break
}
}
} else {
if err := t.helm.Lint(chart); err != nil {
// Lint with defaults if no values files are specified.
if len(valuesFiles) == 0 {
valuesFiles = append(valuesFiles, "")
}
for _, valuesFile := range valuesFiles {
if err := t.helm.LintWithValues(chart, valuesFile); err != nil {
result.Error = err
break
}
}
@@ -326,28 +322,37 @@ func (t *Testing) InstallChart(chart string, valuesFiles []string) TestResult {
result := TestResult{Chart: chart}
if len(valuesFiles) > 0 {
for _, valuesFile := range valuesFiles {
release, namespace := util.CreateInstallParams(chart, t.config.BuildId)
// Test with defaults if no values files are specified.
if len(valuesFiles) == 0 {
valuesFiles = append(valuesFiles, "")
}
for _, valuesFile := range valuesFiles {
var namespace, release, releaseSelector string
if t.config.Namespace != "" {
namespace = t.config.Namespace
release, _ = util.CreateInstallParams(chart, t.config.BuildId)
releaseSelector = fmt.Sprintf("%s=%s", t.config.ReleaseLabel, release)
} else {
release, namespace = util.CreateInstallParams(chart, t.config.BuildId)
defer t.kubectl.DeleteNamespace(namespace)
defer t.helm.DeleteRelease(release)
defer t.PrintPodDetailsAndLogs(namespace)
if err := t.helm.InstallWithValues(chart, valuesFile, namespace, release); err != nil {
result.Error = err
break
}
}
} else {
release, namespace := util.CreateInstallParams(chart, t.config.BuildId)
defer t.kubectl.DeleteNamespace(namespace)
defer t.helm.DeleteRelease(release)
defer t.PrintPodDetailsAndLogs(namespace)
defer t.PrintPodDetailsAndLogs(namespace, releaseSelector)
if err := t.helm.Install(chart, namespace, release); err != nil {
if err := t.helm.InstallWithValues(chart, valuesFile, namespace, release); err != nil {
result.Error = err
break
}
if err := t.kubectl.WaitForDeployments(namespace, releaseSelector); err != nil {
result.Error = err
break
}
if err := t.helm.Test(release); err != nil {
result.Error = err
break
}
}
@@ -533,8 +538,16 @@ func (t *Testing) ValidateMaintainers(chart string) error {
return nil
}
func (t *Testing) PrintPodDetailsAndLogs(namespace string) {
pods, err := t.kubectl.GetPods("--no-headers", "--namespace", namespace, "--output", "jsonpath={.items[*].metadata.name}")
func (t *Testing) PrintPodDetailsAndLogs(namespace string, selector string) {
pods, err := t.kubectl.GetPods(
"--no-headers",
"--namespace",
namespace,
"--selector",
selector,
"--output",
"jsonpath={.items[*].metadata.name}",
)
if err != nil {
fmt.Println("Error printing logs:", err)
return

View File

@@ -97,17 +97,20 @@ func (v fakeAccountValidator) Validate(repoDomain string, account string) error
type fakeLinter struct{}
func (l fakeLinter) YamlLint(yamlFile, configFile string) error { return nil }
func (l fakeLinter) Yamale(yamlFile, schemaFile string) error { return nil }
func (l fakeLinter) Yamale(yamlFile, schemaFile string) error { return nil }
type fakeHelm struct{}
func (h fakeHelm) Init() error { return nil }
func (h fakeHelm) AddRepo(name, url string) error { return nil }
func (h fakeHelm) BuildDependencies(chart string) error { return nil }
func (h fakeHelm) Lint(chart string) error { return nil }
func (h fakeHelm) Init() error { return nil }
func (h fakeHelm) AddRepo(name, url string) error { return nil }
func (h fakeHelm) BuildDependencies(chart string) error { return nil }
func (h fakeHelm) LintWithValues(chart string, valuesFile string) error { return nil }
func (h fakeHelm) Install(chart string, namespace string, release string) error { return nil }
func (h fakeHelm) InstallWithValues(chart string, valuesFile string, namespace string, release string) error { return nil }
func (h fakeHelm) InstallWithValues(chart string, valuesFile string, namespace string, release string) error {
return nil
}
func (h fakeHelm) Test(release string) error {
return nil
}
func (h fakeHelm) DeleteRelease(release string) {}
var ct Testing
@@ -123,8 +126,8 @@ func init() {
git: fakeGit{},
chartUtils: fakeChartUtils{},
accountValidator: fakeAccountValidator{},
linter: fakeLinter{},
helm: fakeHelm{},
linter: fakeLinter{},
helm: fakeHelm{},
}
}
@@ -166,9 +169,9 @@ func TestValidateMaintainers(t *testing.T) {
func TestLintChartMaintainerValidation(t *testing.T) {
type testData struct {
name string
chartDir string
expected bool
name string
chartDir string
expected bool
}
runTests := func(validate bool) {
@@ -181,7 +184,7 @@ func TestLintChartMaintainerValidation(t *testing.T) {
suffix = "without-validation"
}
testCases := []testData {
testCases := []testData{
{fmt.Sprintf("maintainers-%s", suffix), "testdata/valid_maintainers", true},
{fmt.Sprintf("no-maintainers-%s", suffix), "testdata/no_maintainers", !validate},
}

View File

@@ -16,11 +16,12 @@ package config
import (
"fmt"
"github.com/mitchellh/go-homedir"
"path"
"reflect"
"strings"
"github.com/mitchellh/go-homedir"
"github.com/helm/chart-testing/pkg/util"
"github.com/pkg/errors"
"github.com/spf13/cobra"
@@ -52,6 +53,8 @@ type Configuration struct {
ExcludedCharts []string `mapstructure:"excluded-charts"`
HelmExtraArgs string `mapstructure:"helm-extra-args"`
Debug bool `mapstructure:"debug"`
Namespace string `mapstructure:"namespace"`
ReleaseLabel string `mapstructure:"release-label"`
}
func LoadConfiguration(cfgFile string, cmd *cobra.Command, bindFlagsFunc ...func(flagSet *flag.FlagSet, viper *viper.Viper) error) (*Configuration, error) {
@@ -90,7 +93,11 @@ func LoadConfiguration(cfgFile string, cmd *cobra.Command, bindFlagsFunc ...func
}
if cfg.ProcessAllCharts && len(cfg.Charts) > 0 {
return nil, errors.New("Specifying both, '--all' and '--charts', is not allowed!")
return nil, errors.New("specifying both, '--all' and '--charts', is not allowed")
}
if cfg.Namespace != "" && cfg.ReleaseLabel == "" {
return nil, errors.New("specifying '--namespace' without '--release-label' is not allowed")
}
isLint := strings.Contains(cmd.Use, "lint")

View File

@@ -15,9 +15,10 @@
package config
import (
"testing"
"github.com/spf13/cobra"
"github.com/stretchr/testify/require"
"testing"
)
func TestUnmarshalYaml(t *testing.T) {
@@ -43,4 +44,6 @@ func loadAndAssertConfigFromFile(t *testing.T, configFile string) {
require.Equal(t, []string{"stable", "incubator"}, cfg.ChartDirs)
require.Equal(t, []string{"common"}, cfg.ExcludedCharts)
require.Equal(t, "--timeout 300", cfg.HelmExtraArgs)
require.Equal(t, "default", cfg.Namespace)
require.Equal(t, "release", cfg.ReleaseLabel)
}

View File

@@ -18,5 +18,7 @@
"excluded-charts": [
"common"
],
"helm-extra-args": "--timeout 300"
"helm-extra-args": "--timeout 300",
"namespace": "default",
"release-label": "release"
}

View File

@@ -15,3 +15,5 @@ chart-dirs:
excluded-charts:
- common
helm-extra-args: --timeout 300
namespace: default
release-label: release

View File

@@ -16,19 +16,18 @@ package tool
import (
"fmt"
"github.com/helm/chart-testing/pkg/exec"
)
type Helm struct {
exec exec.ProcessExecutor
kubectl Kubectl
extraArgs []string
}
func NewHelm(exec exec.ProcessExecutor, kubectl Kubectl, extraArgs []string) Helm {
func NewHelm(exec exec.ProcessExecutor, extraArgs []string) Helm {
return Helm{
exec: exec,
kubectl: kubectl,
extraArgs: extraArgs,
}
}
@@ -45,16 +44,13 @@ func (h Helm) BuildDependencies(chart string) error {
return h.exec.RunProcess("helm", "dependency", "build", chart)
}
func (h Helm) Lint(chart string) error {
return h.exec.RunProcess("helm", "lint", chart)
}
func (h Helm) LintWithValues(chart string, valuesFile string) error {
return h.exec.RunProcess("helm", "lint", chart, "--values", valuesFile)
}
var values []string
if valuesFile != "" {
values = []string{"--values", valuesFile}
}
func (h Helm) Install(chart string, namespace string, release string) error {
return h.InstallWithValues(chart, "", namespace, release)
return h.exec.RunProcess("helm", "lint", chart, values)
}
func (h Helm) InstallWithValues(chart string, valuesFile string, namespace string, release string) error {
@@ -68,10 +64,10 @@ func (h Helm) InstallWithValues(chart string, valuesFile string, namespace strin
return err
}
if err := h.kubectl.WaitForDeployments(namespace); err != nil {
return err
}
return nil
}
func (h Helm) Test(release string) error {
return h.exec.RunProcess("helm", "test", release, h.extraArgs)
}

View File

@@ -51,9 +51,9 @@ func (k Kubectl) DeleteNamespace(namespace string) {
}
}
func (k Kubectl) WaitForDeployments(namespace string) error {
func (k Kubectl) WaitForDeployments(namespace string, selector string) error {
output, err := k.exec.RunProcessAndCaptureOutput(
"kubectl", "get", "deployments", "--namespace", namespace, "--output", "jsonpath={.items[*].metadata.name}")
"kubectl", "get", "deployments", "--namespace", namespace, "--selector", selector, "--output", "jsonpath={.items[*].metadata.name}")
if err != nil {
return err
}