Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@ testbin/*

dist/

vendor/
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ Usage:
| -cert-manager-install-crd | Allows the user to install cert-manager CRD as part of the cert-manager subchart.(default "true") | `helmify -cert-manager-install-crd` |
| -preserve-ns | Allows users to use the object's original namespace instead of adding all the resources to a common namespace. (default "false") | `helmify -preserve-ns` |
| -add-webhook-option | Adds an option to enable/disable webhook installation | `helmify -add-webhook-option`|
| -optional-crds | Enable optional CRD installation through values. | `helmify -optional-crds` |
## Status
Supported k8s resources:
- Deployment, DaemonSet, StatefulSet
Expand Down
33 changes: 18 additions & 15 deletions cmd/helmify/flags.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"errors"
"flag"
"fmt"
"os"
Expand Down Expand Up @@ -38,6 +39,9 @@ Flags:

type arrayFlags []string

var osExit = os.Exit
var errMutuallyExclusiveCRDs = errors.New("-crd and -optional-crds cannot be used together")

func (i *arrayFlags) String() string {
if i == nil || len(*i) == 0 {
return ""
Expand All @@ -51,48 +55,47 @@ func (i *arrayFlags) Set(value string) error {
}

// ReadFlags command-line flags into app config.
func ReadFlags() config.Config {
func ReadFlags() (config.Config, error) {
files := arrayFlags{}
result := config.Config{}
var h, help, version, crd, preservens bool
var h, help, version bool
flag.BoolVar(&h, "h", false, "Print help. Example: helmify -h")
flag.BoolVar(&help, "help", false, "Print help. Example: helmify -help")
flag.BoolVar(&version, "version", false, "Print helmify version. Example: helmify -version")
flag.BoolVar(&result.Verbose, "v", false, "Enable verbose output (print WARN & INFO). Example: helmify -v")
flag.BoolVar(&result.VeryVerbose, "vv", false, "Enable very verbose output. Same as verbose but with DEBUG. Example: helmify -vv")
flag.BoolVar(&crd, "crd-dir", false, "Enable crd install into 'crds' directory.\nWarning: CRDs placed in 'crds' directory will not be templated by Helm.\nSee https://helm.sh/docs/chart_best_practices/custom_resource_definitions/#some-caveats-and-explanations\nExample: helmify -crd-dir")
flag.BoolVar(&result.ImagePullSecrets, "image-pull-secrets", false, "Allows the user to use existing secrets as imagePullSecrets in values.yaml")
flag.BoolVar(&result.Crd, "crd-dir", false, "Enable crd install into 'crds' directory. (cannot be used with 'optional-crds').\nWarning: CRDs placed in 'crds' directory will not be templated by Helm.\nSee https://helm.sh/docs/chart_best_practices/custom_resource_definitions/#some-caveats-and-explanations\nExample: helmify -crd-dir")
flag.BoolVar(&result.ImagePullSecrets, "image-pull-secrets", false, "Allows the user to use existing secrets as imagePullSecrets in values.yaml.")
flag.BoolVar(&result.GenerateDefaults, "generate-defaults", false, "Allows the user to add empty placeholders for typical customization options in values.yaml. Currently covers: topology constraints, node selectors, tolerances")
flag.BoolVar(&result.CertManagerAsSubchart, "cert-manager-as-subchart", false, "Allows the user to add cert-manager as a subchart")
flag.StringVar(&result.CertManagerVersion, "cert-manager-version", "v1.12.2", "Allows the user to specify cert-manager subchart version. Only useful with cert-manager-as-subchart.")
flag.BoolVar(&result.CertManagerInstallCRD, "cert-manager-install-crd", true, "Allows the user to install cert-manager CRD. Only useful with cert-manager-as-subchart.")
flag.BoolVar(&result.FilesRecursively, "r", false, "Scan dirs from -f option recursively")
flag.BoolVar(&result.OriginalName, "original-name", false, "Use the object's original name instead of adding the chart's release name as the common prefix.")
flag.Var(&files, "f", "File or directory containing k8s manifests")
flag.BoolVar(&preservens, "preserve-ns", false, "Use the object's original namespace instead of adding all the resources to a common namespace")
flag.BoolVar(&result.AddWebhookOption, "add-webhook-option", false, "Allows the user to add webhook option in values.yaml")
flag.Var(&files, "f", "File or directory containing k8s manifests.")
flag.BoolVar(&result.PreserveNs, "preserve-ns", false, "Use the object's original namespace instead of adding all the resources to a common namespace.")
flag.BoolVar(&result.AddWebhookOption, "add-webhook-option", false, "Allows the user to add webhook option in values.yaml.")
flag.BoolVar(&result.OptionalCRDs, "optional-crds", false, "Enable optional CRD installation through values. (cannot be used with 'crd-dir')")

flag.Parse()
if h || help {
fmt.Print(helpText)
flag.CommandLine.SetOutput(os.Stdout)
flag.PrintDefaults()
os.Exit(0)
osExit(0)
}
if version {
printVersion()
os.Exit(0)
osExit(0)
}
name := flag.Arg(0)
if name != "" {
result.ChartName = filepath.Base(name)
result.ChartDir = filepath.Dir(name)
}
if crd {
result.Crd = crd
}
if preservens {
result.PreserveNs = true
if result.Crd && result.OptionalCRDs {
return config.Config{}, errMutuallyExclusiveCRDs
}
result.Files = files
return result
return result, nil
}
200 changes: 200 additions & 0 deletions cmd/helmify/flags_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
package main

import (
"bytes"
"flag"
"io"
"os"
"strings"
"testing"

"github.com/arttor/helmify/pkg/config"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

var actualExitCode int

func mockExit(code int) {
actualExitCode = code
panic("os.Exit called") // Panicking is necessary to stop execution.
}

func resetFlags(t *testing.T) {
t.Helper()
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
}

func TestReadFlags_MutuallyExclusive(t *testing.T) {
oldArgs := os.Args
oldCommandLine := flag.CommandLine

t.Cleanup(func() {
os.Args = oldArgs
flag.CommandLine = oldCommandLine
})

os.Args = []string{
"helmify",
"-crd-dir",
"-optional-crds",
}

flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ContinueOnError)

_, err := ReadFlags()
require.Error(t, err)
require.ErrorIs(t, err, errMutuallyExclusiveCRDs)
require.Equal(t, errMutuallyExclusiveCRDs.Error(), err.Error())
}

func TestReadFlags_Version(t *testing.T) {
oldArgs := os.Args
oldCommandLine := flag.CommandLine
oldOsExit := osExit
stdout := os.Stdout

t.Cleanup(func() {
os.Args = oldArgs
flag.CommandLine = oldCommandLine
osExit = oldOsExit
os.Stdout = stdout
})

os.Args = []string{"helmify", "--version"}
resetFlags(t)

r, w, err := os.Pipe()
require.NoError(t, err)

osExit = mockExit
os.Stdout = w

var capturedOutput bytes.Buffer
defer func() {
require.NoError(t, w.Close())
_, err = io.Copy(&capturedOutput, r)
require.NoError(t, err)
require.NoError(t, r.Close())
require.NotNil(t, recover())

expectedOutput := `Version: development
Build Time: not set
Git Commit: not set
`
assert.Equal(t, expectedOutput, capturedOutput.String())
assert.Equal(t, 0, actualExitCode)
}()
_, err = ReadFlags()
require.NoError(t, err)
}

func TestReadFlags_Help(t *testing.T) {
oldArgs := os.Args
oldCommandLine := flag.CommandLine
oldOsExit := osExit
stdout := os.Stdout

t.Cleanup(func() {
os.Args = oldArgs
flag.CommandLine = oldCommandLine
osExit = oldOsExit
os.Stdout = stdout
})

os.Args = []string{"helmify", "--help"}
resetFlags(t)

r, w, err := os.Pipe()
require.NoError(t, err)

osExit = mockExit
os.Stdout = w

var capturedOutput bytes.Buffer
defer func() {
require.NoError(t, w.Close())
_, err = io.Copy(&capturedOutput, r)
require.NoError(t, err)
require.NoError(t, r.Close())
require.NotNil(t, recover())

var b strings.Builder
b.WriteString(helpText)
flag.CommandLine.SetOutput(&b)
flag.PrintDefaults()

assert.Equal(t, b.String(), capturedOutput.String())
assert.Equal(t, 0, actualExitCode)
}()
_, err = ReadFlags()
require.NoError(t, err)
}

func TestReadFlags_DefaultValuesMatchFlagDefaults(t *testing.T) {
oldArgs := os.Args
oldCommandLine := flag.CommandLine

t.Cleanup(func() {
os.Args = oldArgs
flag.CommandLine = oldCommandLine
})

os.Args = []string{"helmify"}
resetFlags(t)

cfg, err := ReadFlags()
require.NoError(t, err)

stringTests := []struct {
flagName string
getValue func(cfg config.Config) string
}{
{
flagName: "cert-manager-version",
getValue: func(cfg config.Config) string { return cfg.CertManagerVersion },
},
}

boolToStr := func(b bool) string {
if b {
return "true"
}
return "false"
}

boolTests := []struct {
flagName string
getValue func(cfg config.Config) bool
}{
{"v", func(cfg config.Config) bool { return cfg.Verbose }},
{"vv", func(cfg config.Config) bool { return cfg.VeryVerbose }},
{"r", func(cfg config.Config) bool { return cfg.FilesRecursively }},

{"crd-dir", func(cfg config.Config) bool { return cfg.Crd }},
{"optional-crds", func(cfg config.Config) bool { return cfg.OptionalCRDs }},
{"image-pull-secrets", func(cfg config.Config) bool { return cfg.ImagePullSecrets }},
{"generate-defaults", func(cfg config.Config) bool { return cfg.GenerateDefaults }},
{"cert-manager-as-subchart", func(cfg config.Config) bool { return cfg.CertManagerAsSubchart }},
{"cert-manager-install-crd", func(cfg config.Config) bool { return cfg.CertManagerInstallCRD }},
{"original-name", func(cfg config.Config) bool { return cfg.OriginalName }},
{"preserve-ns", func(cfg config.Config) bool { return cfg.PreserveNs }},
{"add-webhook-option", func(cfg config.Config) bool { return cfg.AddWebhookOption }},
}

for _, tt := range stringTests {
t.Run("default_"+tt.flagName, func(t *testing.T) {
f := flag.Lookup(tt.flagName)
require.NotNil(t, f)
assert.Equal(t, f.DefValue, tt.getValue(cfg))
})
}

for _, tt := range boolTests {
t.Run("default_"+tt.flagName, func(t *testing.T) {
f := flag.Lookup(tt.flagName)
require.NotNil(t, f)
assert.Equal(t, f.DefValue, boolToStr(tt.getValue(cfg)))
})
}
}
9 changes: 8 additions & 1 deletion cmd/helmify/main.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
package main

import (
"flag"
"fmt"
"os"

"github.com/arttor/helmify/pkg/app"
"github.com/sirupsen/logrus"
)

func main() {
conf := ReadFlags()
conf, err := ReadFlags()
if err != nil {
fmt.Println(err)
flag.Usage()
os.Exit(1)
}
stat, err := os.Stdin.Stat()
if err != nil {
logrus.WithError(err).Error("stdin error")
Expand Down
4 changes: 3 additions & 1 deletion pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,10 @@ type Config struct {
OriginalName bool
// PreserveNs retains the namespaces on the Kubernetes manifests
PreserveNs bool
// AddWebhookOption enables the generation of a webhook option in values.yamlß
// AddWebhookOption enables the generation of a webhook option in values.yaml
AddWebhookOption bool
// OptionalCRDs - Enable optional CRD installation through values.
OptionalCRDs bool
}

func (c *Config) Validate() error {
Expand Down
25 changes: 19 additions & 6 deletions pkg/processor/crd/crd.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ package crd
import (
"bytes"
"fmt"
"github.com/sirupsen/logrus"
"io"
"strings"

"github.com/sirupsen/logrus"

v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
Expand Down Expand Up @@ -34,6 +35,8 @@ status:
conditions: []
storedVersions: []`

const optionalCRDsConditional = "crds.enabled"

var crdGVC = schema.GroupVersionKind{
Group: "apiextensions.k8s.io",
Version: "v1",
Expand Down Expand Up @@ -129,23 +132,33 @@ func (c crd) Process(appMeta helmify.AppMetadata, obj *unstructured.Unstructured
res := fmt.Sprintf(crdTeml, obj.GetName(), appMeta.ChartName(), annotations, labels, string(specYaml))
res = strings.ReplaceAll(res, "\n\n", "\n")

values := helmify.Values{}

if appMeta.Config().OptionalCRDs {
res = fmt.Sprintf("{{- if .Values.%s }}\n%s\n{{- end }}", optionalCRDsConditional, res)
_, _ = values.Add(true, strings.Split(optionalCRDsConditional, ".")...)
logrus.WithField("crd", name).WithField("condition", optionalCRDsConditional).Debug("enabling optional CRD installation")
}

return true, &result{
name: name + "-crd.yaml",
data: []byte(res),
name: name + "-crd.yaml",
data: []byte(res),
values: values,
}, nil
}

type result struct {
name string
data []byte
name string
data []byte
values helmify.Values
}

func (r *result) Filename() string {
return r.name
}

func (r *result) Values() helmify.Values {
return helmify.Values{}
return r.values
}

func (r *result) Write(writer io.Writer) error {
Expand Down
Loading
Loading