Skip to content

Commit

Permalink
Support installation to existing namespace (#59)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
jlegrone authored and unguiculus committed Dec 14, 2018
1 parent 09eff48 commit 2286037
Show file tree
Hide file tree
Showing 14 changed files with 125 additions and 84 deletions.
13 changes: 10 additions & 3 deletions app/cmd/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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)
}
2 changes: 1 addition & 1 deletion doc/ct.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
8 changes: 6 additions & 2 deletions doc/ct_install.md
Original file line number Diff line number Diff line change
Expand Up @@ -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")
```
Expand All @@ -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
8 changes: 6 additions & 2 deletions doc/ct_lint-and-install.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand All @@ -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
2 changes: 1 addition & 1 deletion doc/ct_lint.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion doc/ct_version.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
95 changes: 54 additions & 41 deletions pkg/chart/chart.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
//
// Install runs `helm install` 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.
//
// 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)
}

Expand All @@ -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
Expand All @@ -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 {
Expand Down Expand Up @@ -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{},
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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
}
}

Expand All @@ -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, "")
}

defer t.kubectl.DeleteNamespace(namespace)
defer t.helm.DeleteRelease(release)
defer t.PrintPodDetailsAndLogs(namespace)
for _, valuesFile := range valuesFiles {
var namespace, release, releaseSelector string

if err := t.helm.InstallWithValues(chart, valuesFile, namespace, release); err != nil {
result.Error = err
break
}
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)
}
} 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
}
}

Expand Down Expand Up @@ -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
Expand Down
29 changes: 16 additions & 13 deletions pkg/chart/chart_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -123,8 +126,8 @@ func init() {
git: fakeGit{},
chartUtils: fakeChartUtils{},
accountValidator: fakeAccountValidator{},
linter: fakeLinter{},
helm: fakeHelm{},
linter: fakeLinter{},
helm: fakeHelm{},
}
}

Expand Down Expand Up @@ -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) {
Expand All @@ -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},
}
Expand Down
11 changes: 9 additions & 2 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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")
Expand Down
5 changes: 4 additions & 1 deletion pkg/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@
package config

import (
"testing"

"github.com/spf13/cobra"
"github.com/stretchr/testify/require"
"testing"
)

func TestUnmarshalYaml(t *testing.T) {
Expand All @@ -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)
}
Loading

0 comments on commit 2286037

Please sign in to comment.