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
15 changes: 14 additions & 1 deletion chartify.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,11 @@ type ChartifyOpts struct {
// and it my produce output unexpected to you.
KubeVersion string

// SortOptions configures kustomize's sortOptions for resource ordering.
// Use &SortOptions{Order: "fifo"} to preserve resource order and minimize diff noise.
// See https://kubectl.docs.kubernetes.io/references/kustomize/kustomization/sortoptions/
SortOptions *SortOptions
Comment on lines +111 to +114

// ApiVersions is a string of kubernetes APIVersions and passed to helm template via --api-versions
// It is required if your chart contains any template that relies on Capabilities.APIVersion for rendering
// resources depending on the API resources and versions available in a target cluster.
Expand Down Expand Up @@ -163,6 +168,12 @@ func (r *Runner) Chartify(release, dirOrChart string, opts ...ChartifyOption) (s
}
}

if u.SortOptions != nil {
if err := u.SortOptions.validate(); err != nil {
return "", err
}
}

isLocal, _ := r.Exists(dirOrChart)

var isKustomization bool
Expand Down Expand Up @@ -267,6 +278,7 @@ func (r *Runner) Chartify(release, dirOrChart string, opts ...ChartifyOption) (s
EnableAlphaPlugins: u.EnableKustomizeAlphaPlugins,
Namespace: u.Namespace,
HelmBinary: r.helmBin(),
SortOptions: u.SortOptions,
}
kustomizeFile, err := r.KustomizeBuild(dirOrChart, tempDir, kustomizeOpts)
if err != nil {
Expand Down Expand Up @@ -466,6 +478,7 @@ func (r *Runner) Chartify(release, dirOrChart string, opts ...ChartifyOption) (s
StrategicMergePatches: u.StrategicMergePatches,
Transformers: u.Transformers,
EnableAlphaPlugins: u.EnableKustomizeAlphaPlugins,
SortOptions: u.SortOptions,
}
if err := r.Patch(tempDir, generatedManifestFiles, patchOpts); err != nil {
return "", err
Expand Down Expand Up @@ -684,7 +697,7 @@ func createDirForFile(f string) error {
dstFileDir := filepath.Dir(f)
if _, err := os.Lstat(dstFileDir); err == nil {

} else if err != nil && os.IsNotExist(err) {
} else if os.IsNotExist(err) {
if err := os.MkdirAll(dstFileDir, 0755); err != nil {
return fmt.Errorf("creating directory %s: %v", dstFileDir, err)
}
Expand Down
62 changes: 58 additions & 4 deletions kustomize.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,45 @@ import (
"gopkg.in/yaml.v3"
)

var validSortOrders = map[string]bool{
"legacy": true,
"fifo": true,
}

type SortOptions struct {
Order string `yaml:"order"`
}
Comment on lines +18 to +20

func (o *SortOptions) validate() error {
if o.Order == "" {
return fmt.Errorf("sortOptions.order must not be empty")
}
if !validSortOrders[o.Order] {
return fmt.Errorf("sortOptions.order %q is not valid; accepted values are: legacy, fifo", o.Order)
}
return nil
}

func marshalSortOptions(opts *SortOptions) ([]byte, error) {
if opts == nil {
return nil, nil
}
if err := opts.validate(); err != nil {
return nil, err
}
sortOptsBytes, err := yaml.Marshal(map[string]*SortOptions{"sortOptions": opts})
if err != nil {
return nil, fmt.Errorf("marshaling sortOptions: %w", err)
}
return sortOptsBytes, nil
}

type KustomizeOpts struct {
Images []KustomizeImage `yaml:"images"`
NamePrefix string `yaml:"namePrefix"`
NameSuffix string `yaml:"nameSuffix"`
Namespace string `yaml:"namespace"`
Images []KustomizeImage `yaml:"images"`
NamePrefix string `yaml:"namePrefix"`
NameSuffix string `yaml:"nameSuffix"`
Namespace string `yaml:"namespace"`
SortOptions *SortOptions `yaml:"sortOptions,omitempty"`
}

type KustomizeImage struct {
Expand Down Expand Up @@ -45,6 +79,7 @@ type KustomizeBuildOpts struct {
EnableAlphaPlugins bool
Namespace string
HelmBinary string
SortOptions *SortOptions
}

func (o *KustomizeBuildOpts) SetKustomizeBuildOption(opts *KustomizeBuildOpts) error {
Expand Down Expand Up @@ -80,6 +115,10 @@ func (r *Runner) KustomizeBuild(srcDir string, tempDir string, opts ...Kustomize
kustomizeOpts.Namespace = u.Namespace
}

if u.SortOptions != nil {
kustomizeOpts.SortOptions = u.SortOptions
}

if len(u.SetValues) > 0 || len(u.SetFlags) > 0 {
panic("--set is not yet supported for kustomize-based apps! Use -f/--values flag instead.")
}
Expand Down Expand Up @@ -139,6 +178,21 @@ func (r *Runner) KustomizeBuild(srcDir string, tempDir string, opts ...Kustomize
return "", err
}
}
// sortOptions is appended directly to kustomization.yaml because kustomize
// has no `edit set sortoptions` command unlike images, nameprefix, etc.
if kustomizeOpts.SortOptions != nil {
sortOptsBytes, err := marshalSortOptions(kustomizeOpts.SortOptions)
if err != nil {
return "", err
}
f, err := r.ReadFile(kustomizationPath)
if err != nil {
return "", fmt.Errorf("reading kustomization.yaml for sortOptions: %w", err)
}
if err := r.WriteFile(kustomizationPath, append(f, sortOptsBytes...), 0644); err != nil {
return "", err
}
}
Comment on lines +183 to +195
outputFile := filepath.Join(tempDir, "templates", "kustomized.yaml")
kustomizeArgs := []string{"-o", outputFile, "build"}

Expand Down
146 changes: 146 additions & 0 deletions kustomize_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package chartify

import (
"os"
"path/filepath"
"strings"
"testing"

"github.com/stretchr/testify/require"
)

func TestSortOptionsValidate(t *testing.T) {
t.Run("empty order is invalid", func(t *testing.T) {
err := (&SortOptions{Order: ""}).validate()
require.Error(t, err)
require.Contains(t, err.Error(), "must not be empty")
})

t.Run("invalid order is rejected", func(t *testing.T) {
err := (&SortOptions{Order: "unknown"}).validate()
require.Error(t, err)
require.Contains(t, err.Error(), "is not valid")
})

t.Run("fifo is valid", func(t *testing.T) {
require.NoError(t, (&SortOptions{Order: "fifo"}).validate())
})

t.Run("legacy is valid", func(t *testing.T) {
require.NoError(t, (&SortOptions{Order: "legacy"}).validate())
})
}

func TestMarshalSortOptions(t *testing.T) {
t.Run("nil returns empty bytes", func(t *testing.T) {
got, err := marshalSortOptions(nil)
require.NoError(t, err)
require.Nil(t, got)
})

t.Run("fifo order", func(t *testing.T) {
got, err := marshalSortOptions(&SortOptions{Order: "fifo"})
require.NoError(t, err)
result := string(got)
require.Contains(t, result, "sortOptions:")
require.Contains(t, result, "order: fifo")
})

t.Run("invalid order returns error", func(t *testing.T) {
_, err := marshalSortOptions(&SortOptions{Order: "bogus"})
require.Error(t, err)
require.Contains(t, err.Error(), "is not valid")
})
}

func TestPatch_SortOptions(t *testing.T) {
t.Run("Patch writes sortOptions to kustomization.yaml", func(t *testing.T) {
tempDir := t.TempDir()

templatesDir := filepath.Join(tempDir, "templates")
require.NoError(t, os.MkdirAll(templatesDir, 0755))

manifest := filepath.Join(templatesDir, "deployment.yaml")
require.NoError(t, os.WriteFile(manifest, []byte(`apiVersion: apps/v1
kind: Deployment
metadata:
name: test-deployment
spec:
replicas: 1
`), 0644))

patchContent := `apiVersion: apps/v1
kind: Deployment
metadata:
name: test-deployment
spec:
replicas: 3
`
patchFile := filepath.Join(tempDir, "patch.yaml")
require.NoError(t, os.WriteFile(patchFile, []byte(patchContent), 0644))

r := New(HelmBin(helm))

var capturedKustomization string
origWriteFile := r.WriteFile
r.WriteFile = func(filename string, data []byte, perm os.FileMode) error {
if strings.HasSuffix(filename, "kustomization.yaml") {
capturedKustomization = string(data)
}
return origWriteFile(filename, data, perm)
}

patchOpts := &PatchOpts{
StrategicMergePatches: []string{patchFile},
SortOptions: &SortOptions{Order: "fifo"},
}
err := r.Patch(tempDir, []string{manifest}, patchOpts)
require.NoError(t, err)
require.Contains(t, capturedKustomization, "sortOptions:")
require.Contains(t, capturedKustomization, "order: fifo")
})

t.Run("Patch without SortOptions omits sortOptions from kustomization.yaml", func(t *testing.T) {
tempDir := t.TempDir()

templatesDir := filepath.Join(tempDir, "templates")
require.NoError(t, os.MkdirAll(templatesDir, 0755))

manifest := filepath.Join(templatesDir, "deployment.yaml")
require.NoError(t, os.WriteFile(manifest, []byte(`apiVersion: apps/v1
kind: Deployment
metadata:
name: test-deployment
spec:
replicas: 1
`), 0644))

patchContent := `apiVersion: apps/v1
kind: Deployment
metadata:
name: test-deployment
spec:
replicas: 3
`
patchFile := filepath.Join(tempDir, "patch.yaml")
require.NoError(t, os.WriteFile(patchFile, []byte(patchContent), 0644))

r := New(HelmBin(helm))

var capturedKustomization string
origWriteFile := r.WriteFile
r.WriteFile = func(filename string, data []byte, perm os.FileMode) error {
if strings.HasSuffix(filename, "kustomization.yaml") {
capturedKustomization = string(data)
}
return origWriteFile(filename, data, perm)
}

patchOpts := &PatchOpts{
StrategicMergePatches: []string{patchFile},
}
err := r.Patch(tempDir, []string{manifest}, patchOpts)
require.NoError(t, err)
require.NotContains(t, capturedKustomization, "sortOptions:")
})
Comment on lines +83 to +145
}
11 changes: 11 additions & 0 deletions patch.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ type PatchOpts struct {
// Above Kustomize v3, it is `--enable-alpha-plugins`.
// Below Kustomize v3 (including v3), it is `--enable_alpha_plugins`.
EnableAlphaPlugins bool

// SortOptions configures kustomize's sortOptions for resource ordering.
SortOptions *SortOptions
}

func (o *PatchOpts) SetPatchOption(opts *PatchOpts) error {
Expand Down Expand Up @@ -173,6 +176,14 @@ resources:
}
}

if u.SortOptions != nil {
sortOptsBytes, err := marshalSortOptions(u.SortOptions)
if err != nil {
return err
}
kustomizationYamlContent += string(sortOptsBytes)
}
Comment on lines +179 to +185

if err := r.WriteFile(filepath.Join(tempDir, "kustomization.yaml"), []byte(kustomizationYamlContent), 0644); err != nil {
return err
}
Expand Down
8 changes: 4 additions & 4 deletions tempdir_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,29 +35,29 @@ func TestGenerateID(t *testing.T) {
release: "foo",
chart: "incubator/raw",
opts: ChartifyOpts{},
want: "foo-57669d77b",
want: "foo-5586b9d54d",
})

run(testcase{
release: "foo",
chart: "stable/envoy",
opts: ChartifyOpts{},
want: "foo-6c769b499",
want: "foo-748fb9844f",
})

run(testcase{
release: "bar",
chart: "incubator/raw",
opts: ChartifyOpts{},
want: "bar-7d49bf498c",
want: "bar-77ddc8bd65",
})

run(testcase{
release: "foo",
opts: ChartifyOpts{
Namespace: "myns",
},
want: "myns-foo-9c6f7fb79",
want: "myns-foo-5dbbf694b5",
})

for id, n := range ids {
Expand Down