diff --git a/.github/governance.yml b/.github/governance.yml
index e45e8f6..e78e916 100644
--- a/.github/governance.yml
+++ b/.github/governance.yml
@@ -39,4 +39,4 @@ issue:
- release-process
# kargo-render component areas
multiple: true
- needs: true
+ needs: false
diff --git a/README.md b/README.md
index eca5e96..2a50b73 100644
--- a/README.md
+++ b/README.md
@@ -4,8 +4,10 @@
[](https://codecov.io/gh/akuity/kargo-render)
[](https://app.netlify.com/sites/docs-kargo-render-akuity-io/deploys)
[](CODE_OF_CONDUCT.md)
+[](http://akuity.community)
-
+
Placeholder
diff --git a/argocd-schema.json b/argocd-schema.json
new file mode 100644
index 0000000..e038da4
--- /dev/null
+++ b/argocd-schema.json
@@ -0,0 +1,234 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "$id": "argocd-schema.json",
+ "definitions": {
+ "helm": {
+ "description": "Helm holds helm specific options",
+ "properties": {
+ "fileParameters": {
+ "description": "FileParameters are file parameters to the helm template",
+ "items": {
+ "description": "HelmFileParameter is a file parameter that's passed to helm template during manifest generation",
+ "properties": {
+ "name": {
+ "description": "Name is the name of the Helm parameter",
+ "type": "string"
+ },
+ "path": {
+ "description": "Path is the path to the file containing the values for the Helm parameter",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "type": "array"
+ },
+ "ignoreMissingValueFiles": {
+ "description": "IgnoreMissingValueFiles prevents helm template from failing when valueFiles do not exist locally by not appending them to helm template --values",
+ "type": "boolean"
+ },
+ "parameters": {
+ "description": "Parameters is a list of Helm parameters which are passed to the helm template command upon manifest generation",
+ "items": {
+ "description": "HelmParameter is a parameter that's passed to helm template during manifest generation",
+ "properties": {
+ "forceString": {
+ "description": "ForceString determines whether to tell Helm to interpret booleans and numbers as strings",
+ "type": "boolean"
+ },
+ "name": {
+ "description": "Name is the name of the Helm parameter",
+ "type": "string"
+ },
+ "value": {
+ "description": "Value is the value for the Helm parameter",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "type": "array"
+ },
+ "passCredentials": {
+ "description": "PassCredentials pass credentials to all domains (Helm's --pass-credentials)",
+ "type": "boolean"
+ },
+ "releaseName": {
+ "description": "ReleaseName is the Helm release name to use. If omitted it will use the application name",
+ "type": "string"
+ },
+ "skipCrds": {
+ "description": "SkipCrds skips custom resource definition installation step (Helm's --skip-crds)",
+ "type": "boolean"
+ },
+ "valueFiles": {
+ "description": "ValuesFiles is a list of Helm value files to use when generating a template",
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ "values": {
+ "description": "Values specifies Helm values to be passed to helm template, typically defined as a block. ValuesObject takes precedence over Values, so use one or the other.",
+ "type": "string"
+ },
+ "valuesObject": {
+ "description": "ValuesObject specifies Helm values to be passed to helm template, defined as a map. This takes precedence over Values.",
+ "type": "object",
+ "x-kubernetes-preserve-unknown-fields": true
+ },
+ "version": {
+ "description": "Version is the Helm version to use for templating (\"3\")",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "kustomize": {
+ "description": "Kustomize holds kustomize specific options",
+ "properties": {
+ "commonAnnotations": {
+ "additionalProperties": {
+ "type": "string"
+ },
+ "description": "CommonAnnotations is a list of additional annotations to add to rendered manifests",
+ "type": "object"
+ },
+ "commonAnnotationsEnvsubst": {
+ "description": "CommonAnnotationsEnvsubst specifies whether to apply env variables substitution for annotation values",
+ "type": "boolean"
+ },
+ "commonLabels": {
+ "additionalProperties": {
+ "type": "string"
+ },
+ "description": "CommonLabels is a list of additional labels to add to rendered manifests",
+ "type": "object"
+ },
+ "forceCommonAnnotations": {
+ "description": "ForceCommonAnnotations specifies whether to force applying common annotations to resources for Kustomize apps",
+ "type": "boolean"
+ },
+ "forceCommonLabels": {
+ "description": "ForceCommonLabels specifies whether to force applying common labels to resources for Kustomize apps",
+ "type": "boolean"
+ },
+ "images": {
+ "description": "Images is a list of Kustomize image override specifications",
+ "items": {
+ "description": "KustomizeImage represents a Kustomize image definition in the format [old_image_name=]:",
+ "type": "string"
+ },
+ "type": "array"
+ },
+ "namePrefix": {
+ "description": "NamePrefix is a prefix appended to resources for Kustomize apps",
+ "type": "string"
+ },
+ "nameSuffix": {
+ "description": "NameSuffix is a suffix appended to resources for Kustomize apps",
+ "type": "string"
+ },
+ "namespace": {
+ "description": "Namespace sets the namespace that Kustomize adds to all resources",
+ "type": "string"
+ },
+ "replicas": {
+ "description": "Replicas is a list of Kustomize Replicas override specifications",
+ "items": {
+ "properties": {
+ "count": {
+ "anyOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "type": "string"
+ }
+ ],
+ "description": "Number of replicas",
+ "x-kubernetes-int-or-string": true
+ },
+ "name": {
+ "description": "Name of Deployment or StatefulSet",
+ "type": "string"
+ }
+ },
+ "required": [
+ "count",
+ "name"
+ ],
+ "type": "object"
+ },
+ "type": "array"
+ },
+ "version": {
+ "description": "Version controls which version of Kustomize to use for rendering manifests",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "plugin": {
+ "description": "Plugin holds config management plugin specific options",
+ "properties": {
+ "env": {
+ "description": "Env is a list of environment variable entries",
+ "items": {
+ "description": "EnvEntry represents an entry in the application's environment",
+ "properties": {
+ "name": {
+ "description": "Name is the name of the variable, usually expressed in uppercase",
+ "type": "string"
+ },
+ "value": {
+ "description": "Value is the value of the variable",
+ "type": "string"
+ }
+ },
+ "required": [
+ "name",
+ "value"
+ ],
+ "type": "object"
+ },
+ "type": "array"
+ },
+ "name": {
+ "type": "string"
+ },
+ "parameters": {
+ "items": {
+ "properties": {
+ "array": {
+ "description": "Array is the value of an array type parameter.",
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ "map": {
+ "additionalProperties": {
+ "type": "string"
+ },
+ "description": "Map is the value of a map type parameter.",
+ "type": "object"
+ },
+ "name": {
+ "description": "Name is the name identifying a parameter.",
+ "type": "string"
+ },
+ "string": {
+ "description": "String_ is the value of a string type parameter.",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "type": "array"
+ }
+ },
+ "type": "object"
+ }
+ }
+}
diff --git a/branches.go b/branches.go
index c4708be..0fe4435 100644
--- a/branches.go
+++ b/branches.go
@@ -4,6 +4,7 @@ import (
"fmt"
"os"
"path/filepath"
+ "strings"
"github.com/ghodss/yaml"
"github.com/pkg/errors"
@@ -144,19 +145,90 @@ func switchToCommitBranch(rc requestContext) (string, error) {
}
}
- // Clean existing output paths
- for appName, appConfig := range rc.target.branchConfig.AppConfigs {
- var outputDir string
- if appConfig.OutputPath != "" {
- outputDir = filepath.Join(rc.repo.WorkingDir(), appConfig.OutputPath)
- } else {
- outputDir = filepath.Join(rc.repo.WorkingDir(), appName)
- }
- if err := os.RemoveAll(outputDir); err != nil {
- return "", errors.Wrapf(err, "error deleting %q", outputDir)
- }
+ // Clean the branch so we can replace its contents wholesale
+ if err := cleanCommitBranch(
+ rc.repo.WorkingDir(),
+ rc.target.branchConfig.PreservedPaths,
+ ); err != nil {
+ return "", errors.Wrap(err, "error cleaning commit branch")
}
logger.Debug("cleaned commit branch")
return commitBranch, nil
}
+
+// cleanCommitBranch deletes the entire contents of the specified directory
+// EXCEPT for the paths specified by preservedPaths.
+func cleanCommitBranch(dir string, preservedPaths []string) error {
+ _, err := cleanDir(
+ dir,
+ normalizePreservedPaths(
+ dir,
+ append(preservedPaths, ".git", ".kargo-render"),
+ ),
+ )
+ return err
+}
+
+// normalizePreservedPaths converts the relative paths in the preservedPaths
+// argument to absolute paths relative to the workingDir argument. It also
+// removes any trailing path separators from the paths.
+func normalizePreservedPaths(
+ workingDir string,
+ preservedPaths []string,
+) []string {
+ normalizedPreservedPaths := make([]string, len(preservedPaths))
+ for i, preservedPath := range preservedPaths {
+ if strings.HasSuffix(preservedPath, string(os.PathSeparator)) {
+ preservedPath = preservedPath[:len(preservedPath)-1]
+ }
+ normalizedPreservedPaths[i] = filepath.Join(workingDir, preservedPath)
+ }
+ return normalizedPreservedPaths
+}
+
+// cleanDir recursively deletes the entire contents of the directory specified
+// by the absolute path dir EXCEPT for any paths specified by the preservedPaths
+// argument. The function returns true if dir is left empty afterwards and false
+// otherwise.
+func cleanDir(dir string, preservedPaths []string) (bool, error) {
+ items, err := os.ReadDir(dir)
+ if err != nil {
+ return false, err
+ }
+ for _, item := range items {
+ path := filepath.Join(dir, item.Name())
+ if isPathPreserved(path, preservedPaths) {
+ continue
+ }
+ if item.IsDir() {
+ var isEmpty bool
+ if isEmpty, err = cleanDir(path, preservedPaths); err != nil {
+ return false, err
+ }
+ if isEmpty {
+ if err = os.Remove(path); err != nil {
+ return false, err
+ }
+ }
+ } else if err = os.Remove(path); err != nil {
+ return false, err
+ }
+ }
+ if items, err = os.ReadDir(dir); err != nil {
+ return false, err
+ }
+ return len(items) == 0, nil
+}
+
+// isPathPreserved returns true if the specified path is among those specified
+// by the preservedPaths argument. Both path and preservedPaths MUST be absolute
+// paths. Paths to directories MUST NOT end with a trailing path separator.
+func isPathPreserved(path string, preservedPaths []string) bool {
+ for _, preservedPath := range preservedPaths {
+ if path == preservedPath {
+ return true
+ }
+ }
+ return false
+}
diff --git a/branches_test.go b/branches_test.go
index 626d701..9750eae 100644
--- a/branches_test.go
+++ b/branches_test.go
@@ -1,6 +1,7 @@
package render
import (
+ "fmt"
"os"
"path/filepath"
"testing"
@@ -93,3 +94,166 @@ func TestWriteBranchMetadata(t *testing.T) {
require.NoError(t, err)
require.True(t, exists)
}
+
+func TestCleanCommitBranch(t *testing.T) {
+ const subdirCount = 50
+ const fileCount = 50
+ // Create dummy repo dir
+ dir, err := createDummyCommitBranchDir(subdirCount, fileCount)
+ defer os.RemoveAll(dir)
+ require.NoError(t, err)
+ // Double-check the setup
+ dirEntries, err := os.ReadDir(dir)
+ require.NoError(t, err)
+ require.Len(t, dirEntries, subdirCount+fileCount+2)
+ // Delete
+ err = cleanCommitBranch(dir, []string{})
+ require.NoError(t, err)
+ // .git should not have been deleted
+ _, err = os.Stat(filepath.Join(dir, ".git"))
+ require.NoError(t, err)
+ // .kargo-render should not have been deleted
+ _, err = os.Stat(filepath.Join(dir, ".kargo-render"))
+ require.NoError(t, err)
+ // Everything else should be deleted
+ dirEntries, err = os.ReadDir(dir)
+ require.NoError(t, err)
+ require.Len(t, dirEntries, 2)
+}
+
+func TestNormalizePreservedPaths(t *testing.T) {
+ preservedPaths := []string{
+ "foo/bar",
+ "bat/baz/",
+ }
+ normalizedPreservedPaths :=
+ normalizePreservedPaths("fake-work-dir", preservedPaths)
+ require.Equal(
+ t,
+ []string{
+ filepath.Join("fake-work-dir", "foo", "bar"),
+ filepath.Join("fake-work-dir", "bat", "baz"),
+ },
+ normalizedPreservedPaths,
+ )
+}
+
+func TestCleanDir(t *testing.T) {
+ dir, err := os.MkdirTemp("", "")
+ defer os.RemoveAll(dir)
+ require.NoError(t, err)
+
+ // This is what the test directory structure will look like:
+ // .
+ // ├── foo preserved directly
+ // │ └── foo.txt preserved because foo is
+ // ├── bar preserved because bar/bar.txt is
+ // │ └── bar.txt preserved directly
+ // ├── baz deleted because empty
+ // │ └── baz.txt deleted
+ // └── keep.txt preserved directly
+
+ // Create the test directory structure
+ fooDir := filepath.Join(dir, "foo")
+ err = os.Mkdir(fooDir, 0755)
+ require.NoError(t, err)
+ fooFile := filepath.Join(fooDir, "foo.txt")
+ err = os.WriteFile(fooFile, []byte("foo"), 0600)
+ require.NoError(t, err)
+
+ barDir := filepath.Join(dir, "bar")
+ err = os.Mkdir(barDir, 0755)
+ require.NoError(t, err)
+ barFile := filepath.Join(barDir, "bar.txt")
+ err = os.WriteFile(barFile, []byte("bar"), 0600)
+ require.NoError(t, err)
+
+ bazDir := filepath.Join(dir, "baz")
+ err = os.Mkdir(bazDir, 0755)
+ require.NoError(t, err)
+ bazFile := filepath.Join(bazDir, "baz.txt")
+ err = os.WriteFile(bazFile, []byte("baz"), 0600)
+ require.NoError(t, err)
+
+ keepFile := filepath.Join(dir, "keep.txt")
+ err = os.WriteFile(keepFile, []byte("keep"), 0600)
+ require.NoError(t, err)
+
+ preservedPaths := []string{
+ fooDir,
+ barFile,
+ keepFile,
+ }
+
+ isEmpty, err := cleanDir(dir, preservedPaths)
+ require.NoError(t, err)
+ require.False(t, isEmpty)
+
+ // Validate what was deleted and what wasn't
+
+ // All of foo/ remains
+ _, err = os.Stat(fooDir)
+ require.NoError(t, err)
+ _, err = os.Stat(fooFile)
+ require.NoError(t, err)
+
+ // All of bar/ remains
+ _, err = os.Stat(barDir)
+ require.NoError(t, err)
+ _, err = os.Stat(barFile)
+ require.NoError(t, err)
+
+ // All of baz/ is gone
+ _, err = os.Stat(bazDir)
+ require.True(t, os.IsNotExist(err))
+
+ // keep.txt remains
+ _, err = os.Stat(keepFile)
+ require.NoError(t, err)
+}
+
+func TestIsPathPreserved(t *testing.T) {
+ preservedPaths := []string{
+ "/foo/bar",
+ "/foo/bat",
+ }
+ require.True(t, isPathPreserved("/foo/bar", preservedPaths))
+ require.True(t, isPathPreserved("/foo/bat", preservedPaths))
+ require.False(t, isPathPreserved("/foo/baz", preservedPaths))
+}
+
+func createDummyCommitBranchDir(dirCount, fileCount int) (string, error) {
+ // Create a directory
+ dir, err := os.MkdirTemp("", "")
+ if err != nil {
+ return dir, err
+ }
+ // Add a dummy .git/ subdir
+ if err = os.Mkdir(filepath.Join(dir, ".git"), 0755); err != nil {
+ return dir, err
+ }
+ // Add a dummy .kargo-render/ subdir
+ if err = os.Mkdir(filepath.Join(dir, ".kargo-render"), 0755); err != nil {
+ return dir, err
+ }
+ // Add some other dummy dirs
+ for i := 0; i < dirCount; i++ {
+ if err = os.Mkdir(
+ filepath.Join(dir, fmt.Sprintf("dir-%d", i)),
+ 0755,
+ ); err != nil {
+ return dir, err
+ }
+ }
+ // Add some dummy files
+ for i := 0; i < fileCount; i++ {
+ file, err := os.Create(filepath.Join(dir, fmt.Sprintf("file-%d", i)))
+ if err != nil {
+ return dir, err
+ }
+ if err = file.Close(); err != nil {
+ return dir, err
+ }
+ }
+ return dir, nil
+}
diff --git a/cmd/cli/cli.go b/cmd/cli/cli.go
index e87f12a..335d8e8 100644
--- a/cmd/cli/cli.go
+++ b/cmd/cli/cli.go
@@ -3,9 +3,21 @@ package cli
import (
"fmt"
"os"
+
+ log "github.com/sirupsen/logrus"
)
func Run() {
+ // These two lines are required to suppress undesired log output from the Argo
+ // CD repo server, which Kargo Render uses as a library. This does NOT
+ // interfere with using the Kargo Render CLI's own --debug flag.
+ os.Setenv("ARGOCD_LOG_LEVEL", "PANIC")
+ log.SetLevel(log.PanicLevel)
+ // This line makes all log output go to stderr, leaving stdout for actual
+ // program output only. This is important for cases where machine readable
+ // output (e.g. JSON) is requested.
+ log.SetOutput(os.Stderr)
+
cmd, err := newRootCommand()
if err != nil {
fmt.Println(err)
diff --git a/config.go b/config.go
index c521a79..b37fe6b 100644
--- a/config.go
+++ b/config.go
@@ -12,20 +12,30 @@ import (
"github.com/pkg/errors"
"github.com/xeipuuv/gojsonschema"
+ "github.com/akuity/kargo-render/internal/argocd"
"github.com/akuity/kargo-render/internal/file"
- "github.com/akuity/kargo-render/internal/helm"
- "github.com/akuity/kargo-render/internal/kustomize"
- "github.com/akuity/kargo-render/internal/ytt"
_ "embed"
)
//go:embed schema.json
var configSchemaBytes []byte
-var configSchemaJSONLoader gojsonschema.JSONLoader
+
+//go:embed argocd-schema.json
+var argocdConfigSchemaBytes []byte
+
+var configSchema *gojsonschema.Schema
func init() {
- configSchemaJSONLoader = gojsonschema.NewBytesLoader(configSchemaBytes)
+ sl := gojsonschema.NewSchemaLoader()
+ if err := sl.AddSchema("argocd-schema.json", gojsonschema.NewBytesLoader(argocdConfigSchemaBytes)); err != nil {
+ panic(fmt.Sprintf("error adding Argo CD schema: %s", err))
+ }
+
+ var err error
+ if configSchema, err = sl.Compile(gojsonschema.NewBytesLoader(configSchemaBytes)); err != nil {
+ panic(fmt.Sprintf("error compiling schema: %s", err))
+ }
}
// repoConfig encapsulates all Kargo Render configuration options for a
@@ -48,7 +58,7 @@ func (r *repoConfig) GetBranchConfig(name string) (branchConfig, error) {
}
submatches := regex.FindStringSubmatch(name)
if len(submatches) > 0 {
- return cfg.expand(submatches), nil
+ return cfg.expand(submatches)
}
}
}
@@ -69,22 +79,42 @@ type branchConfig struct {
// PRs encapsulates details about how to manage any pull requests associated
// with this branch.
PRs pullRequestConfig `json:"prs,omitempty"`
+ // PreservedPaths specifies paths relative to the root of the repository that
+ // should be exempted from pre-render cleaning (deletion) of
+ // environment-specific branch contents. This is useful for preserving any
+ // environment-specific files that are manually maintained. Typically there
+ // are very few such files, if any at all, with an environment-specific
+ // CODEOWNERS file at the root of the repository being the most emblematic
+ // exception. Paths may be to files or directories. Any path to a directory
+ // will cause that directory's entire contents to be preserved.
+ PreservedPaths []string `json:"preservedPaths,omitempty"`
}
-func (b branchConfig) expand(values []string) branchConfig {
+func (b branchConfig) expand(values []string) (branchConfig, error) {
cfg := b
cfg.AppConfigs = map[string]appConfig{}
for appName, appConfig := range b.AppConfigs {
- cfg.AppConfigs[appName] = appConfig.expand(values)
+ var err error
+ if cfg.AppConfigs[appName], err = appConfig.expand(values); err != nil {
+ return cfg, errors.Wrapf(
+ err,
+ "error expanding app config for app %q",
+ appName,
+ )
+ }
}
- return cfg
+
+ for i, path := range b.PreservedPaths {
+ b.PreservedPaths[i] = file.ExpandPath(path, values)
+ }
+ return cfg, nil
}
// appConfig encapsulates application-specific Kargo Render configuration.
type appConfig struct {
// ConfigManagement encapsulates configuration management options to be
// used with this branch and app.
- ConfigManagement configManagementConfig `json:"configManagement,omitempty"`
+ ConfigManagement argocd.ConfigManagementConfig `json:"configManagement"`
// OutputPath specifies a path relative to the root of the repository where
// rendered manifests for this app will be stored in this branch.
OutputPath string `json:"outputPath,omitempty"`
@@ -93,40 +123,14 @@ type appConfig struct {
CombineManifests bool `json:"combineManifests,omitempty"`
}
-func (a appConfig) expand(values []string) appConfig {
+func (a appConfig) expand(values []string) (appConfig, error) {
cfg := a
- cfg.ConfigManagement = a.ConfigManagement.expand(values)
- cfg.OutputPath = file.ExpandPath(a.OutputPath, values)
- return cfg
-}
-
-// configManagementConfig is a wrapper around more specific configuration for
-// one of three supported configuration management tools: helm, kustomize, or
-// ytt. Only one of its fields may be non-nil.
-type configManagementConfig struct { // nolint: revive
- // Helm encapsulates optional Helm configuration options.
- Helm *helm.Config `json:"helm,omitempty"`
- // Kustomize encapsulates optional Kustomize configuration options.
- Kustomize *kustomize.Config `json:"kustomize,omitempty"`
- // Ytt encapsulates optional ytt configuration options.
- Ytt *ytt.Config `json:"ytt,omitempty"`
-}
-
-func (c configManagementConfig) expand(values []string) configManagementConfig {
- cfg := c
- if c.Helm != nil {
- helmCfg := c.Helm.Expand(values)
- cfg.Helm = &helmCfg
- }
- if c.Kustomize != nil {
- kustomizeCfg := c.Kustomize.Expand(values)
- cfg.Kustomize = &kustomizeCfg
- }
- if c.Ytt != nil {
- yttCfg := c.Ytt.Expand(values)
- cfg.Ytt = &yttCfg
+ var err error
+ if cfg.ConfigManagement, err = a.ConfigManagement.Expand(values); err != nil {
+ return cfg, errors.Wrap(err, "error expanding config management config")
}
- return cfg
+ cfg.OutputPath = file.ExpandPath(a.OutputPath, values)
+ return cfg, nil
}
// pullRequestConfig encapsulates details related to PR management for a branch.
@@ -198,10 +202,8 @@ func normalizeAndValidate(configBytes []byte) ([]byte, error) {
return nil,
errors.Wrap(err, "error normalizing Kargo Render configuration")
}
- validationResult, err := gojsonschema.Validate(
- configSchemaJSONLoader,
- gojsonschema.NewBytesLoader(configBytes),
- )
+
+ validationResult, err := configSchema.Validate(gojsonschema.NewBytesLoader(configBytes))
if err != nil {
return nil, errors.Wrap(err, "error validating Kargo Render configuration")
}
diff --git a/config_test.go b/config_test.go
index 56a78bb..5252b29 100644
--- a/config_test.go
+++ b/config_test.go
@@ -7,10 +7,6 @@ import (
"testing"
"github.com/stretchr/testify/require"
-
- "github.com/akuity/kargo-render/internal/helm"
- "github.com/akuity/kargo-render/internal/kustomize"
- "github.com/akuity/kargo-render/internal/ytt"
)
func TestLoadRepoConfig(t *testing.T) {
@@ -140,6 +136,72 @@ func TestNormalizeAndValidate(t *testing.T) {
require.NoError(t, err)
},
},
+ {
+ name: "valid kustomize",
+ assertions: func(err error) {
+ require.NoError(t, err)
+ },
+ config: []byte(`configVersion: v1alpha1
+branchConfigs:
+ - name: env/prod
+ appConfigs:
+ my-proj:
+ configManagement:
+ path: env/prod/my-proj
+ kustomize:
+ buildOptions: "--load-restrictor LoadRestrictionsNone"
+ outputPath: prod/my-proj
+ combineManifests: true`),
+ },
+ {
+ name: "valid helm",
+ assertions: func(err error) {
+ require.NoError(t, err)
+ },
+ config: []byte(`configVersion: v1alpha1
+branchConfigs:
+ - name: env/prod
+ appConfigs:
+ my-proj:
+ configManagement:
+ path: env/prod/my-proj
+ helm:
+ namespace: my-namespace
+ outputPath: prod/my-proj
+ combineManifests: true`),
+ },
+ {
+ name: "valid no config management tool",
+ assertions: func(err error) {
+ require.NoError(t, err)
+ },
+ config: []byte(`configVersion: v1alpha1
+branchConfigs:
+ - name: env/prod
+ appConfigs:
+ my-proj:
+ configManagement:
+ path: env/prod/my-proj
+ outputPath: prod/my-proj
+ combineManifests: true`),
+ },
+ {
+ name: "invalid property",
+ assertions: func(err error) {
+ require.Error(t, err)
+ },
+ config: []byte(`configVersion: v1alpha1
+branchConfigs:
+ - name: env/prod
+ appConfigs:
+ my-proj:
+ configManagement:
+ path: env/prod/my-proj
+ unknown:
+ hello: world
+ outputPath: prod/my-proj
+ combineManifests: true`),
+ },
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
@@ -155,94 +217,3 @@ func TestNormalizeAndValidate(t *testing.T) {
})
}
}
-
-func TestExpandBranchConfig(t *testing.T) {
- const val = "foo"
- testCfg := branchConfig{
- AppConfigs: map[string]appConfig{
- "my-kustomize-app": {
- ConfigManagement: configManagementConfig{
- Kustomize: &kustomize.Config{
- Path: "${0}",
- },
- },
- OutputPath: "${0}",
- },
- "my-helm-app": {
- ConfigManagement: configManagementConfig{
- Helm: &helm.Config{
- ChartPath: "${0}",
- ValuesPaths: []string{"${0}", "${0}"},
- },
- },
- OutputPath: "${0}",
- },
- "my-ytt-app": {
- ConfigManagement: configManagementConfig{
- Ytt: &ytt.Config{
- Paths: []string{"${0}", "${0}"},
- },
- },
- OutputPath: "${0}",
- },
- },
- }
- cfg := testCfg.expand([]string{val})
- require.Equal(
- t,
- val,
- cfg.AppConfigs["my-kustomize-app"].ConfigManagement.Kustomize.Path,
- )
- require.Equal(
- t,
- val,
- cfg.AppConfigs["my-kustomize-app"].OutputPath,
- )
- require.Equal(
- t,
- val,
- cfg.AppConfigs["my-helm-app"].ConfigManagement.Helm.ChartPath,
- )
- require.Equal(
- t,
- []string{val, val},
- cfg.AppConfigs["my-helm-app"].ConfigManagement.Helm.ValuesPaths,
- )
- require.Equal(
- t,
- val,
- cfg.AppConfigs["my-helm-app"].OutputPath,
- )
- require.Equal(
- t,
- []string{val, val},
- cfg.AppConfigs["my-ytt-app"].ConfigManagement.Ytt.Paths,
- )
- require.Equal(
- t,
- val,
- cfg.AppConfigs["my-ytt-app"].OutputPath,
- )
- // Check that the original testCfg.AppConfigs haven't been touched.
- // References to maps are pointers, hence the extra care.
- require.Equal(
- t,
- "${0}",
- testCfg.AppConfigs["my-kustomize-app"].ConfigManagement.Kustomize.Path,
- )
- require.Equal(
- t,
- "${0}",
- testCfg.AppConfigs["my-helm-app"].ConfigManagement.Helm.ChartPath,
- )
- require.Equal(
- t,
- []string{"${0}", "${0}"},
- testCfg.AppConfigs["my-helm-app"].ConfigManagement.Helm.ValuesPaths,
- )
- require.Equal(
- t,
- []string{"${0}", "${0}"},
- testCfg.AppConfigs["my-ytt-app"].ConfigManagement.Ytt.Paths,
- )
-}
diff --git a/docs/docs/10-overview.md b/docs/docs/10-overview.md
index e0118ab..67e057e 100644
--- a/docs/docs/10-overview.md
+++ b/docs/docs/10-overview.md
@@ -31,8 +31,21 @@ which you wish to render and store manifests. Kargo Render does the rest and you
can point applicable configuration of your preferred GitOps-enabled CD platform
at the environment branch.
+## Kargo Render & Argo CD
+
+Kargo Render is compatible with Argo CD manifest rendering. Behind the scenes, Kargo Render uses
+Argo CD repo server to generate final manifests. That includes the same config management settings as well as features
+like automatic tool detection and
+[git parameter overrides](https://argo-cd.readthedocs.io/en/stable/user-guide/parameters/#store-overrides-in-git).
+This ensures painless back-and-forth switching between native Argo CD manifest generation and Kargo Render.
+
## Getting started
+[Kargo](https://kargo.akuity.io/), the application lifecycle platform
+for Kubernetes, leverages Kargo Render to orchestrate manifests changes promotion.
+You can also use Kargo Render as a standalone tool:
+
+
1. Kargo Render can be integrated into your GitOps practice in a variety of
ways. Regardless of your entrypoint into its functionality, it relies on a
common bit of configuration -- `kargo-render.yaml`. Read more about that
diff --git a/docs/docs/20-environment-branches.md b/docs/docs/20-environment-branches.md
index 8a31c75..1f67e66 100644
--- a/docs/docs/20-environment-branches.md
+++ b/docs/docs/20-environment-branches.md
@@ -13,8 +13,8 @@ begins with understanding some common difficulties encountered by
To keep Kubernetes manifests concise and manageable, most GitOps practitioners
incorporate some manner of configuration management tooling into their
-deployments. [Kustomize](https://kustomize.io/), [ytt](https://carvel.dev/ytt/),
-and [Helm](https://helm.sh/) are three popular examples of such tools. Although
+deployments. [Kustomize](https://kustomize.io/),
+and [Helm](https://helm.sh/) are two popular examples of such tools. Although
they may employ widely varied approaches, tools in this class all enable the
same fundamental capability -- maintaining a common set of "base" configuration
that can be amended or patched in some way to suit each of the environments to
@@ -22,7 +22,7 @@ which you might deploy your application.
Continuous delivery platforms, like [Argo CD](https://argoproj.github.io/cd/) or
[Flux](https://fluxcd.io/), commonly integrate with tools such as these. Argo
-CD, for instance, can easily detect the use of Kustomize or Helm (but not ytt)
+CD, for instance, can easily detect the use of Kustomize or Helm
and utilize embedded versions of those tools to render such configuration into
plain manifests that are appropriate for a given environment. While, at a
glance, this may seem convenient, relying on these integrations to perform
diff --git a/docs/docs/30-how-to-guides/10-configuration.mdx b/docs/docs/30-how-to-guides/10-configuration.mdx
index 148b46e..3463f8e 100644
--- a/docs/docs/30-how-to-guides/10-configuration.mdx
+++ b/docs/docs/30-how-to-guides/10-configuration.mdx
@@ -62,6 +62,12 @@ examples that follow will involve _two_ applications -- `foo` and `bar`. When we
render manifests for an environment branch, we wish to render manifests for
_both_ apps.
+## Config management tool configuration
+
+Behind the scene Kargo Render uses Argo CD Repo Server to render manifests. More
+information about supported settings is available in Argo CD
+[documentation](https://argo-cd.readthedocs.io/en/stable/user-guide/application_sources/).
+
@@ -78,37 +84,31 @@ branchConfigs:
appConfigs:
foo:
configManagement:
- kustomize:
- path: env/test/foo # test overlay for app "foo"
+ path: env/test/foo # test overlay for app "foo"
outputPath: foo
bar:
configManagement:
- kustomize:
- path: env/test/bar # test overlay for app "bar"
+ path: env/test/bar # test overlay for app "bar"
outputPath: bar
- name: env/stage
appConfigs:
foo:
configManagement:
- kustomize:
- path: env/stage/foo # stage overlay for app "foo"
+ path: env/stage/foo # stage overlay for app "foo"
outputPath: foo
bar:
configManagement:
- kustomize:
- path: env/stage/bar # stage overlay for app "bar"
+ path: env/stage/bar # stage overlay for app "bar"
outputPath: bar
- name: env/prod
appConfigs:
foo:
configManagement:
- kustomize:
- path: env/prod/foo # prod overlay for app "foo"
+ path: env/prod/foo # prod overlay for app "foo"
outputPath: foo
bar:
configManagement:
- kustomize:
- path: env/prod/bar # prod overlay for app "bar"
+ path: env/prod/bar # prod overlay for app "bar"
outputPath: bar
```
@@ -118,71 +118,6 @@ for more information.
-
-
-For each environment branch, the configuration below specifies the multiple
-paths that should be used as arguments to the `ytt` command for each
-application. It also specifies where within each environment branch to store the
-rendered manifests for each application.
-
-```yaml
-configVersion: v1alpha1
-branchConfigs:
-- name: env/test
- appConfigs:
- foo:
- configManagement:
- ytt:
- paths: # use all of these paths as args to the ytt command
- - base/foo
- - env/test/foo
- outputPath: foo
- bar:
- configManagement:
- ytt:
- paths: # use all of these paths as args to the ytt command
- - base/bar
- - env/test/bar
- outputPath: bar
-- name: env/stage
- appConfigs:
- foo:
- configManagement:
- ytt:
- paths: # use all of these paths as args to the ytt command
- - base/foo
- - env/stage/foo
- outputPath: foo
- bar:
- configManagement:
- ytt:
- paths: # use all of these paths as args to the ytt command
- - base/bar
- - env/stage/bar
- outputPath: bar
-- name: env/prod
- appConfigs:
- foo:
- configManagement:
- ytt:
- paths: # use all of these paths as args to the ytt command
- - base/foo
- - env/prod/foo
- outputPath: foo
- bar:
- configManagement:
- ytt:
- paths: # use all of these paths as args to the ytt command
- - base/bar
- - env/prod/bar
- outputPath: bar
-```
-
-Refer directly to [ytt's documentation](https://carvel.dev/ytt/) for more
-information.
-
-
-
For each environment branch, the configuration below specifies a release name,
@@ -197,60 +132,60 @@ branchConfigs:
appConfigs:
foo:
configManagement:
+ path: charts/foo
helm:
releaseName: foo
namespace: foo #optional
- chartPath: charts/foo
- valuesPaths:
+ valueFiles:
- env/test/foo/values.yaml
outputPath: foo
bar:
configManagement:
+ path: charts/bar
helm:
releaseName: bar
namespace: bar #optional
- chartPath: charts/bar
- valuesPaths:
+ valuesFiles:
- env/test/bar/values.yaml
outputPath: bar
- name: env/stage
appConfigs:
foo:
configManagement:
+ path: charts/foo
helm:
releaseName: foo
namespace: foo #optional
- chartPath: charts/foo
- valuesPaths:
+ valuesFiles:
- env/stage/foo/values.yaml
outputPath: foo
bar:
configManagement:
+ path: charts/bar
helm:
releaseName: bar
namespace: bar #optional
- chartPath: charts/bar
- valuesPaths:
+ valuesFiles:
- env/stage/bar/values.yaml
outputPath: bar
- name: env/prod
appConfigs:
foo:
configManagement:
+ path: charts/foo
helm:
releaseName: foo
namespace: foo #optional
- chartPath: charts/foo
- valuesPaths:
+ valuesFiles:
- env/prod/foo/values.yaml
outputPath: foo
bar:
configManagement:
+ path: charts/bar
helm:
releaseName: bar
namespace: bar #optional
- chartPath: charts/bar
- valuesPaths:
+ valuesFiles:
- env/prod/bar/values.yaml
outputPath: bar
```
@@ -290,38 +225,11 @@ branchConfigs:
appConfigs:
foo:
configManagement:
- kustomize:
- path: env/${1}/foo
- outputPath: foo
- bar:
- configManagement:
- kustomize:
- path: env/${1}/bar
- outputPath: bar
-```
-
-
-
-
-
-```yaml
-configVersion: v1alpha1
-branchConfigs:
-- pattern: env/(\w+)
- appConfigs:
- foo:
- configManagement:
- ytt:
- paths:
- - base/foo
- - env/${1}/foo
+ path: env/${1}/foo
outputPath: foo
bar:
configManagement:
- ytt:
- paths:
- - base/bar
- - env/${1}/bar
+ path: env/${1}/bar
outputPath: bar
```
@@ -336,20 +244,20 @@ branchConfigs:
appConfigs:
foo:
configManagement:
+ path: charts/foo
helm:
releaseName: foo
namespace: foo #optional
- chartPath: charts/foo
- valuesPaths:
+ valuesFiles:
- env/${1}/foo/values.yaml
outputPath: foo
bar:
configManagement:
+ path: charts/bar
helm:
releaseName: bar
namespace: bar #optional
- chartPath: charts/bar
- valuesPaths:
+ valuesFiles:
- env/${1}/bar/values.yaml
outputPath: bar
```
diff --git a/docs/docs/30-how-to-guides/20-github-actions.md b/docs/docs/30-how-to-guides/20-github-actions.md
index 00aba17..06edf31 100644
--- a/docs/docs/30-how-to-guides/20-github-actions.md
+++ b/docs/docs/30-how-to-guides/20-github-actions.md
@@ -11,7 +11,7 @@ GitHub Actions, Kargo Render can be run as an action.
:::info
The Kargo Render action utilizes the official Kargo Render Docker image and
therefore has guaranteed access to compatible versions of
-Git, Helm, Kustomize, and ytt, which are included on that image.
+Git, Helm, and Kustomize, which are included on that image.
:::
Example usage:
diff --git a/docs/docs/30-how-to-guides/30-docker-image.md b/docs/docs/30-how-to-guides/30-docker-image.md
index 8e8b2ff..f6368ea 100644
--- a/docs/docs/30-how-to-guides/30-docker-image.md
+++ b/docs/docs/30-how-to-guides/30-docker-image.md
@@ -36,7 +36,7 @@ not limited to, popular choices such as [CircleCI](https://circleci.com/) or
:::caution
The `kargo-render` CLI is not designed to be run anywhere except within a
container based on the official Kargo Render image. The official Kargo Render
-image provides compatible versions of Kustomize, ytt, and Helm that cannot be
+image provides compatible versions of Kustomize and Helm that cannot be
guaranteed to exist on other systems.
:::
@@ -44,5 +44,5 @@ guaranteed to exist on other systems.
If you're using Kargo Render's [Go module](./go-module) to interact
programmatically with Kargo Render, you might _also_ consider utilizing the
Kargo Render Docker image as a base image for your own software since it will
-guarantee the existence of compatible versions of Kustomize, ytt, and Helm.
+guarantee the existence of compatible versions of Kustomize, and Helm.
:::
\ No newline at end of file
diff --git a/docs/docs/40-contributor-guide/10-hacking-on-kargo-render.md b/docs/docs/40-contributor-guide/10-hacking-on-kargo-render.md
index 02eb901..c0bc0ef 100644
--- a/docs/docs/40-contributor-guide/10-hacking-on-kargo-render.md
+++ b/docs/docs/40-contributor-guide/10-hacking-on-kargo-render.md
@@ -66,7 +66,7 @@ make hack-build
```
:::note
-Because Kargo Render is dependent on compatible versions of Git, Kustomize, ytt,
+Because Kargo Render is dependent on compatible versions of Git, Kustomize,
and Helm binaries, there is seldom, if ever, a reason to build or execute the
Kargo Render binaries outside the context of a container that provides those
dependencies.
diff --git a/docs/docs/50-roadmap.md b/docs/docs/50-roadmap.md
index 2953f45..0d1f558 100644
--- a/docs/docs/50-roadmap.md
+++ b/docs/docs/50-roadmap.md
@@ -9,4 +9,8 @@ Kargo Render is highly experimental at this time and breaking changes should be
anticipated between pre-GA minor releases.
:::
-Placeholder
+## Argo CD Config Management Plugins
+
+An ability to configure and use Argo CD
+[config management plugins](https://argo-cd.readthedocs.io/en/stable/operator-manual/config-management-plugins/)
+for manifest generation.
diff --git a/hack/fetch-argocd-schema.sh b/hack/fetch-argocd-schema.sh
new file mode 100755
index 0000000..072e294
--- /dev/null
+++ b/hack/fetch-argocd-schema.sh
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+
+SCRIPT_PATH=$( cd "$(dirname "$0")" && pwd )
+ARGOCD_VERSION=$(cat "$SCRIPT_PATH"/../go.mod | grep github.com/argoproj/argo-cd | cut -d' ' -f2-)
+curl https://raw.githubusercontent.com/argoproj/argo-cd/$ARGOCD_VERSION/manifests/crds/application-crd.yaml | yq -o=json | \
+ jq '{"$schema": "http://json-schema.org/draft-07/schema#", "$id": "argocd-schema.json", "definitions": {helm: .spec.versions[0].schema.openAPIV3Schema.properties.spec.properties.source.properties.helm, kustomize: .spec.versions[0].schema.openAPIV3Schema.properties.spec.properties.source.properties.kustomize, plugin: .spec.versions[0].schema.openAPIV3Schema.properties.spec.properties.source.properties.plugin}} ' > \
+ "$SCRIPT_PATH"/../argocd-schema.json
\ No newline at end of file
diff --git a/internal/argocd/render.go b/internal/argocd/render.go
new file mode 100644
index 0000000..67cb349
--- /dev/null
+++ b/internal/argocd/render.go
@@ -0,0 +1,151 @@
+package argocd
+
+import (
+ "context"
+ "encoding/json"
+
+ argoappv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
+ "github.com/argoproj/argo-cd/v2/reposerver/apiclient"
+ "github.com/argoproj/argo-cd/v2/reposerver/repository"
+ "github.com/argoproj/argo-cd/v2/util/git"
+ "github.com/pkg/errors"
+ "k8s.io/apimachinery/pkg/api/resource"
+
+ "github.com/akuity/kargo-render/internal/file"
+ "github.com/akuity/kargo-render/internal/manifests"
+)
+
+// ConfigManagementConfig is a wrapper around more specific configuration for
+// the configuration management tools. Only one of its fields may be non-nil.
+type ConfigManagementConfig struct {
+ Path string `json:"path,omitempty"`
+ Helm *ApplicationSourceHelm `json:"helm,omitempty"`
+ Kustomize *ApplicationSourceKustomize `json:"kustomize,omitempty"`
+ Directory *argoappv1.ApplicationSourceDirectory `json:"directory,omitempty"`
+ Plugin *argoappv1.ApplicationSourcePlugin `json:"plugin,omitempty"`
+}
+
+// ApplicationSourceHelm holds configuration for Helm-based applications.
+type ApplicationSourceHelm struct {
+ argoappv1.ApplicationSourceHelm
+
+ Namespace string `json:"namespace,omitempty"`
+ K8SVersion string `json:"k8sVersion,omitempty"`
+ APIVersions []string `json:"apiVersions,omitempty"`
+
+ RepoURL string `json:"repoURL,omitempty"`
+ Chart string `json:"chart,omitempty"`
+}
+
+// ApplicationSourceKustomize holds configuration for Kustomize-based
+// applications.
+type ApplicationSourceKustomize struct {
+ argoappv1.ApplicationSourceKustomize
+ BuildOptions string `json:"buildOptions,omitempty"`
+}
+
+func expand(item map[string]interface{}, values []string) {
+ for k, v := range item {
+ switch value := v.(type) {
+ case string:
+ item[k] = file.ExpandPath(value, values)
+ case map[string]interface{}:
+ expand(value, values)
+ case []interface{}:
+ for i, v := range value {
+ switch v := v.(type) {
+ case string:
+ value[i] = file.ExpandPath(v, values)
+ case map[string]interface{}:
+ expand(v, values)
+ }
+ }
+ }
+ }
+}
+
+func (c ConfigManagementConfig) Expand(
+ values []string,
+) (ConfigManagementConfig, error) {
+ data, err := json.Marshal(c)
+ if err != nil {
+ return c, err
+ }
+ var cfgMap map[string]interface{}
+ if err = json.Unmarshal(data, &cfgMap); err != nil {
+ return c, err
+ }
+ expand(cfgMap, values)
+ data, err = json.Marshal(cfgMap)
+ if err != nil {
+ return c, err
+ }
+ var cfg ConfigManagementConfig
+ if err := json.Unmarshal(data, &cfg); err != nil {
+ return c, err
+ }
+ return cfg, nil
+}
+
+func Render(
+ ctx context.Context, path string,
+ cfg ConfigManagementConfig,
+) ([]byte, error) {
+ src := argoappv1.ApplicationSource{
+ Plugin: cfg.Plugin,
+ }
+ var apiVersions []string
+ var namespace string
+ var k8sVersion string
+ if cfg.Helm != nil {
+ src.Helm = &cfg.Helm.ApplicationSourceHelm
+ apiVersions = cfg.Helm.APIVersions
+ namespace = cfg.Helm.Namespace
+ k8sVersion = cfg.Helm.K8SVersion
+ }
+ var kustomizeOptions *argoappv1.KustomizeOptions
+ if cfg.Kustomize != nil {
+ src.Kustomize = &cfg.Kustomize.ApplicationSourceKustomize
+ kustomizeOptions = &argoappv1.KustomizeOptions{
+ BuildOptions: cfg.Kustomize.BuildOptions,
+ }
+ }
+
+ res, err := repository.GenerateManifests(
+ ctx,
+ path,
+ // Seems ok for these next two arguments to be empty strings. If this is
+ // last mile rendering, we might be doing this in a directory outside of any
+ // repo. And event for regular rendering, we have already checked the
+ // revision we want.
+ "", // Repo root
+ "", // Revision
+ &apiclient.ManifestRequest{
+ // Both of these fields need to be non-nil
+ Repo: &argoappv1.Repository{},
+ ApplicationSource: &src,
+ KustomizeOptions: kustomizeOptions,
+ ApiVersions: apiVersions,
+ Namespace: namespace,
+ KubeVersion: k8sVersion,
+ },
+ true,
+ &git.NoopCredsStore{}, // No need for this
+ // Allow any quantity of generated manifests
+ resource.MustParse("0"),
+ nil,
+ )
+ if err != nil {
+ return nil,
+ errors.Wrap(err, "error generating manifests using Argo CD repo server")
+ }
+
+ // res.Manifests contains JSON manifests. We want YAML.
+ yamlManifests, err := manifests.JSONStringsToYAMLBytes(res.Manifests)
+ if err != nil {
+ return nil, err
+ }
+
+ // Glue the manifests together
+ return manifests.CombineYAML(yamlManifests), nil
+}
diff --git a/internal/argocd/render_test.go b/internal/argocd/render_test.go
new file mode 100644
index 0000000..c38cd62
--- /dev/null
+++ b/internal/argocd/render_test.go
@@ -0,0 +1,30 @@
+package argocd
+
+import (
+ "testing"
+
+ argoappv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
+ "github.com/stretchr/testify/require"
+)
+
+func TestExpand(t *testing.T) {
+ cfg := ConfigManagementConfig{
+ Path: "charts/foo",
+ Helm: &ApplicationSourceHelm{
+ Namespace: "foo",
+ ApplicationSourceHelm: argoappv1.ApplicationSourceHelm{
+ ReleaseName: "foo",
+ ValueFiles: []string{"env/${1}/foo/values.yaml"},
+ Parameters: []argoappv1.HelmParameter{{
+ Name: "env",
+ Value: "${1}",
+ }},
+ },
+ },
+ }
+ expandedCfg, err := cfg.Expand([]string{"foo", "bar"})
+ require.NoError(t, err)
+
+ require.Equal(t, "env/bar/foo/values.yaml", expandedCfg.Helm.ValueFiles[0])
+ require.Equal(t, "bar", expandedCfg.Helm.Parameters[0].Value)
+}
diff --git a/internal/helm/config.go b/internal/helm/config.go
deleted file mode 100644
index 65fad9e..0000000
--- a/internal/helm/config.go
+++ /dev/null
@@ -1,38 +0,0 @@
-package helm
-
-import "github.com/akuity/kargo-render/internal/file"
-
-// Config encapsulates optional Helm configuration options.
-type Config struct {
- // ReleaseName specifies the release name that will be used when executing the
- // `helm template` command.
- ReleaseName string `json:"releaseName,omitempty"`
- // ChartPath is a path to a directory, relative to the root of the repository,
- // where a Helm chart can be located. This is used as an argument in the
- // `helm template` command. By convention, if left unspecified, the value
- // `base/` is assumed.
- ChartPath string `json:"chartPath,omitempty"`
- // Values are paths to Helm values files (e.g. values.yaml), relative to the
- // root of the repository. Each of these will be used as a value for the
- // `--values` flag in the `helm template` command. By convention, if left
- // unspecified, one path will be assumed: /values.yaml.
- ValuesPaths []string `json:"valuesPaths,omitempty"`
- // Namespace is the Kubernetes namespace in which the Helm chart will be
- // rendered. This is used as an argument in the `helm template` command. By
- // convention, if left unspecified, the value `default` is assumed.
- Namespace string `json:"namespace,omitempty"`
-}
-
-// Expand expands all file/directory paths referenced by this configuration
-// object, replacing placeholders of the form ${n} where n is a non-negative
-// integer, with corresponding values from the provided string array. The
-// modified object is returned.
-func (c Config) Expand(values []string) Config {
- cfg := c
- cfg.ChartPath = file.ExpandPath(c.ChartPath, values)
- cfg.ValuesPaths = make([]string, len(c.ValuesPaths))
- for i, pathTemplate := range c.ValuesPaths {
- cfg.ValuesPaths[i] = file.ExpandPath(pathTemplate, values)
- }
- return cfg
-}
diff --git a/internal/helm/config_test.go b/internal/helm/config_test.go
deleted file mode 100644
index f1dce44..0000000
--- a/internal/helm/config_test.go
+++ /dev/null
@@ -1,21 +0,0 @@
-package helm
-
-import (
- "testing"
-
- "github.com/stretchr/testify/require"
-)
-
-func TestConfigExpand(t *testing.T) {
- const val = "foo"
- testCfg := Config{
- ChartPath: "${0}",
- ValuesPaths: []string{"${0}", "${0}"},
- }
- cfg := testCfg.Expand([]string{val})
- require.Equal(t, cfg.ChartPath, val)
- require.Equal(t, cfg.ValuesPaths, []string{val, val})
- // Check that original testCfg.ValuesPaths are untouched.
- // Slice references are pointers, hence the extra care.
- require.Equal(t, []string{"${0}", "${0}"}, testCfg.ValuesPaths)
-}
diff --git a/internal/helm/helm.go b/internal/helm/helm.go
deleted file mode 100644
index 4ea03c3..0000000
--- a/internal/helm/helm.go
+++ /dev/null
@@ -1,65 +0,0 @@
-package helm
-
-import (
- "context"
-
- argoappv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
- "github.com/argoproj/argo-cd/v2/reposerver/apiclient"
- "github.com/argoproj/argo-cd/v2/reposerver/repository"
- "github.com/argoproj/argo-cd/v2/util/git"
- "github.com/pkg/errors"
- "k8s.io/apimachinery/pkg/api/resource"
-
- "github.com/akuity/kargo-render/internal/manifests"
-)
-
-// Render delegates, in-process to the Argo CD repo server to render plain YAML
-// manifests from a Helm chart.
-func Render(
- ctx context.Context,
- releaseName string,
- namespace string,
- chartPath string,
- valuesPaths []string,
-) ([]byte, error) {
- res, err := repository.GenerateManifests(
- ctx,
- chartPath,
- // Seems ok for these next two arguments to be empty strings. If this is
- // last mile rendering, we might be doing this in a directory outside of any
- // repo. And event for regular rendering, we have already checked the
- // revision we want.
- "", // Repo root
- "", // Revision
- &apiclient.ManifestRequest{
- Namespace: namespace,
- // Both of these fields need to be non-nil
- Repo: &argoappv1.Repository{},
- ApplicationSource: &argoappv1.ApplicationSource{
- Helm: &argoappv1.ApplicationSourceHelm{
- ReleaseName: releaseName,
- ValueFiles: valuesPaths,
- },
- },
- },
- true,
- &git.NoopCredsStore{}, // No need for this
- // TODO: Don't completely understand this next arg, but @alexmt says this is
- // right. Something to do with caching?
- resource.MustParse("0"),
- nil,
- )
- if err != nil {
- return nil,
- errors.Wrap(err, "error generating manifests using Argo CD repo server")
- }
-
- // res.Manifests contains JSON manifests. We want YAML.
- yamlManifests, err := manifests.JSONStringsToYAMLBytes(res.Manifests)
- if err != nil {
- return nil, err
- }
-
- // Glue the manifests together
- return manifests.CombineYAML(yamlManifests), nil
-}
diff --git a/internal/kustomize/config.go b/internal/kustomize/config.go
deleted file mode 100644
index 335b9f2..0000000
--- a/internal/kustomize/config.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package kustomize
-
-import "github.com/akuity/kargo-render/internal/file"
-
-// Config encapsulates optional Kustomize configuration options.
-type Config struct {
- // Path is a path to a directory, relative to the root of the repository,
- // where environment-specific Kustomize configuration for this branch can be
- // located. This will be the directory from which `kustomize build` is
- // executed. By convention, if left unspecified, the path is assumed to be
- // identical to the name of the branch.
- Path string `json:"path,omitempty"`
- // EnableHelm specifies whether Kustomize's Helm Chart Inflator should be
- // enabled. If left unspecified, it defaults to false -- not enabled.
- EnableHelm bool `json:"enableHelm,omitempty"`
- // LoadRestrictor specifies whether the Kustomization may load files from
- // outside its root. If set to 'LoadRestrictionsNone', the Kustomization
- // may load files from outside its root. If left unspecified, it defaults
- // to `LoadRestrictionsRootOnly` - which restricts the Kustomization
- // to only loading files from inside its root.
- LoadRestrictor string `json:"loadRestrictor,omitempty"`
-}
-
-// Expand expands all file/directory paths referenced by this configuration
-// object, replacing placeholders of the form ${n} where n is a non-negative
-// integer, with corresponding values from the provided string array. The
-// modified object is returned.
-func (c Config) Expand(values []string) Config {
- cfg := c
- cfg.Path = file.ExpandPath(c.Path, values)
- return cfg
-}
diff --git a/internal/kustomize/config_test.go b/internal/kustomize/config_test.go
deleted file mode 100644
index a4518ad..0000000
--- a/internal/kustomize/config_test.go
+++ /dev/null
@@ -1,16 +0,0 @@
-package kustomize
-
-import (
- "testing"
-
- "github.com/stretchr/testify/require"
-)
-
-func TestConfigExpand(t *testing.T) {
- const val = "foo"
- testCfg := Config{
- Path: "${0}",
- }
- cfg := testCfg.Expand([]string{val})
- require.Equal(t, cfg.Path, val)
-}
diff --git a/internal/kustomize/kustomize.go b/internal/kustomize/kustomize.go
index 9d38e29..bce781e 100644
--- a/internal/kustomize/kustomize.go
+++ b/internal/kustomize/kustomize.go
@@ -25,7 +25,6 @@ func Render(
ctx context.Context,
path string,
images []string,
- cfg Config,
) ([]byte, error) {
kustomizeImages := make(argoappv1.KustomizeImages, len(images))
for i, image := range images {
@@ -51,7 +50,6 @@ func Render(
Images: kustomizeImages,
},
},
- KustomizeOptions: buildKustomizeOptions(cfg),
},
true,
&git.NoopCredsStore{}, // No need for this
@@ -74,21 +72,3 @@ func Render(
// Glue the manifests together
return manifests.CombineYAML(yamlManifests), nil
}
-
-func buildKustomizeOptions(cfg Config) *argoappv1.KustomizeOptions {
- buildOptions := ""
-
- if cfg.EnableHelm {
- buildOptions += "--enable-helm "
- }
-
- if cfg.LoadRestrictor != "" {
- buildOptions += fmt.Sprintf("--load-restrictor %s", cfg.LoadRestrictor)
- } else {
- buildOptions += "--load-restrictor LoadRestrictionsRootOnly"
- }
-
- return &argoappv1.KustomizeOptions{
- BuildOptions: buildOptions,
- }
-}
diff --git a/internal/kustomize/kustomize_test.go b/internal/kustomize/kustomize_test.go
deleted file mode 100644
index a22bbbd..0000000
--- a/internal/kustomize/kustomize_test.go
+++ /dev/null
@@ -1,50 +0,0 @@
-package kustomize
-
-import (
- "testing"
-
- argoappv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
- "github.com/stretchr/testify/require"
-)
-
-func TestBuildKustomizeOptions(t *testing.T) {
- testCases := []struct {
- name string
- cfg Config
- expected *argoappv1.KustomizeOptions
- }{
- {
- name: "Enable helm, no load restrictor",
- cfg: Config{EnableHelm: true, LoadRestrictor: ""},
- expected: &argoappv1.KustomizeOptions{
- BuildOptions: "--enable-helm --load-restrictor LoadRestrictionsRootOnly", // nolint:all
- },
- },
- {
- name: "Disable helm, provide load restrictor",
- cfg: Config{EnableHelm: false, LoadRestrictor: "LoadRestrictionsNone"},
- expected: &argoappv1.KustomizeOptions{
- BuildOptions: "--load-restrictor LoadRestrictionsNone",
- },
- },
- {
- name: "Disable helm, no load restrictor",
- cfg: Config{EnableHelm: false, LoadRestrictor: ""},
- expected: &argoappv1.KustomizeOptions{
- BuildOptions: "--load-restrictor LoadRestrictionsRootOnly",
- },
- },
- {
- name: "Enable helm, provide load restrictor",
- cfg: Config{EnableHelm: true, LoadRestrictor: "LoadRestrictionsNone"},
- expected: &argoappv1.KustomizeOptions{
- BuildOptions: "--enable-helm --load-restrictor LoadRestrictionsNone",
- },
- },
- }
-
- for _, tc := range testCases {
- actual := buildKustomizeOptions(tc.cfg)
- require.Equal(t, tc.expected.BuildOptions, actual.BuildOptions)
- }
-}
diff --git a/internal/ytt/config.go b/internal/ytt/config.go
deleted file mode 100644
index 92d5463..0000000
--- a/internal/ytt/config.go
+++ /dev/null
@@ -1,26 +0,0 @@
-package ytt
-
-import "github.com/akuity/kargo-render/internal/file"
-
-// Config encapsulates optional ytt configuration options.
-type Config struct {
- // Paths are paths to directories or files, relative to the root of the
- // repository, containing YTT templates or data. Each of these will be used as
- // a value for the `--file` flag in the `ytt` command. By convention, if left
- // unspecified, two paths are assumed: base/ and a path identical to the name
- // of the branch.
- Paths []string `json:"paths,omitempty"`
-}
-
-// Expand expands all file/directory paths referenced by this configuration
-// object, replacing placeholders of the form ${n} where n is a non-negative
-// integer, with corresponding values from the provided string array. The
-// modified object is returned.
-func (c Config) Expand(values []string) Config {
- cfg := c
- cfg.Paths = make([]string, len(c.Paths))
- for i, pathTemplate := range c.Paths {
- cfg.Paths[i] = file.ExpandPath(pathTemplate, values)
- }
- return cfg
-}
diff --git a/internal/ytt/config_test.go b/internal/ytt/config_test.go
deleted file mode 100644
index 13b2995..0000000
--- a/internal/ytt/config_test.go
+++ /dev/null
@@ -1,19 +0,0 @@
-package ytt
-
-import (
- "testing"
-
- "github.com/stretchr/testify/require"
-)
-
-func TestConfigExpand(t *testing.T) {
- const val = "foo"
- testCfg := Config{
- Paths: []string{"${0}", "${0}"},
- }
- cfg := testCfg.Expand([]string{val})
- require.Equal(t, cfg.Paths, []string{val, val})
- // Check that original testCfg.Paths are untouched.
- // Slice references are pointers, hence the extra care.
- require.Equal(t, []string{"${0}", "${0}"}, testCfg.Paths)
-}
diff --git a/internal/ytt/ytt.go b/internal/ytt/ytt.go
deleted file mode 100644
index dea5b8b..0000000
--- a/internal/ytt/ytt.go
+++ /dev/null
@@ -1,21 +0,0 @@
-package ytt
-
-import (
- "context"
- "os/exec"
-
- libExec "github.com/akuity/kargo-render/internal/exec"
-)
-
-// Render shells out to the ytt binary to render the provided paths into plain
-// YAML manifests. Unlike in the case of Kustomize and Helm, this is not done
-// with the help of the Argo CD repo server, since that does not yet support
-// ytt.
-func Render(_ context.Context, paths []string) ([]byte, error) {
- cmdArgs := make([]string, len(paths)*2)
- for i, path := range paths {
- cmdArgs[i*2] = "--file"
- cmdArgs[i*2+1] = path
- }
- return libExec.Exec(exec.Command("ytt", cmdArgs...))
-}
diff --git a/kargo-logo.png b/kargo-logo.png
new file mode 100644
index 0000000..9913044
Binary files /dev/null and b/kargo-logo.png differ
diff --git a/logo.png b/logo.png
deleted file mode 100644
index a610496..0000000
Binary files a/logo.png and /dev/null differ
diff --git a/netlify.toml b/netlify.toml
new file mode 100644
index 0000000..08c8870
--- /dev/null
+++ b/netlify.toml
@@ -0,0 +1,7 @@
+[build]
+ base = "docs/"
+ command = "yarn build"
+ publish = "build/"
+
+[context.deploy-preview]
+ ignore = "git diff --quiet main -- docs"
diff --git a/rendering.go b/rendering.go
index dcc3b3c..82624b4 100644
--- a/rendering.go
+++ b/rendering.go
@@ -7,7 +7,6 @@ import (
"path/filepath"
"github.com/pkg/errors"
- log "github.com/sirupsen/logrus"
"github.com/akuity/kargo-render/internal/kustomize"
"github.com/akuity/kargo-render/internal/strings"
@@ -32,66 +31,11 @@ func (s *service) preRender(
var err error
for appName, appConfig := range rc.target.branchConfig.AppConfigs {
appLogger := logger.WithField("app", appName)
- if appConfig.ConfigManagement.Helm != nil {
- chartPath := appConfig.ConfigManagement.Helm.ChartPath
- if chartPath == "" {
- chartPath = "base"
- }
- valuesPaths := appConfig.ConfigManagement.Helm.ValuesPaths
- if len(valuesPaths) == 0 {
- valuesPaths =
- []string{filepath.Join(rc.request.TargetBranch, "values.yaml")}
- }
- appLogger = appLogger.WithFields(log.Fields{
- "configManagement": "helm",
- "releaseName": appConfig.ConfigManagement.Helm.ReleaseName,
- "namespace": appConfig.ConfigManagement.Helm.Namespace,
- "chartPath": chartPath,
- "valuesPaths": valuesPaths,
- })
- chartPath = filepath.Join(repoDir, chartPath)
- absValuesPaths := make([]string, len(valuesPaths))
- for i, valuesPath := range valuesPaths {
- absValuesPaths[i] = filepath.Join(repoDir, valuesPath)
- }
- manifests[appName], err = s.helmRenderFn(
- ctx,
- appConfig.ConfigManagement.Helm.ReleaseName,
- appConfig.ConfigManagement.Helm.Namespace,
- chartPath,
- absValuesPaths,
- )
- } else if appConfig.ConfigManagement.Ytt != nil {
- paths := appConfig.ConfigManagement.Ytt.Paths
- if len(paths) == 0 {
- paths = []string{"base", rc.request.TargetBranch}
- }
- appLogger = appLogger.WithFields(log.Fields{
- "configManagement": "ytt",
- "paths": paths,
- })
- absPaths := make([]string, len(paths))
- for i, path := range paths {
- absPaths[i] = filepath.Join(repoDir, path)
- }
- manifests[appName], err = s.yttRenderFn(ctx, absPaths)
- } else {
- path := appConfig.ConfigManagement.Kustomize.Path
- if path == "" {
- path = rc.request.TargetBranch
- }
- appLogger = appLogger.WithFields(log.Fields{
- "configManagement": "kustomize",
- "path": path,
- })
- path = filepath.Join(repoDir, path)
- manifests[appName], err = s.kustomizeRenderFn(
- ctx,
- path,
- nil,
- *appConfig.ConfigManagement.Kustomize,
- )
- }
+ manifests[appName], err = s.renderFn(
+ ctx,
+ filepath.Join(repoDir, appConfig.ConfigManagement.Path),
+ appConfig.ConfigManagement,
+ )
if err != nil {
return nil, err
}
@@ -196,7 +140,7 @@ func renderLastMile(
)
}
if manifests[appName], err =
- kustomize.Render(ctx, appDir, images, kustomize.Config{}); err != nil {
+ kustomize.Render(ctx, appDir, images); err != nil {
return nil, nil, errors.Wrapf(
err,
"error rendering manifests from %q",
diff --git a/rendering_test.go b/rendering_test.go
deleted file mode 100644
index 736ee96..0000000
--- a/rendering_test.go
+++ /dev/null
@@ -1,246 +0,0 @@
-package render
-
-import (
- "context"
- "testing"
-
- "github.com/pkg/errors"
- "github.com/sirupsen/logrus"
- "github.com/stretchr/testify/require"
-
- "github.com/akuity/kargo-render/internal/helm"
- "github.com/akuity/kargo-render/internal/kustomize"
- "github.com/akuity/kargo-render/internal/ytt"
-)
-
-func TestPreRender(t *testing.T) {
- const testAppName = "test-app"
- fakeManifest := []byte("fake-manifest")
- testCases := []struct {
- name string
- rc requestContext
- service *service
- assertions func(manifests map[string][]byte, err error)
- }{
- {
- name: "error pre-rendering with helm",
- rc: requestContext{
- target: targetContext{
- branchConfig: branchConfig{
- AppConfigs: map[string]appConfig{
- testAppName: {
- ConfigManagement: configManagementConfig{
- Helm: &helm.Config{},
- },
- },
- },
- },
- },
- },
- service: &service{
- helmRenderFn: func(
- context.Context,
- string,
- string,
- string,
- []string,
- ) ([]byte, error) {
- return nil, errors.New("something went wrong")
- },
- },
- assertions: func(_ map[string][]byte, err error) {
- require.Error(t, err)
- require.Equal(t, "something went wrong", err.Error())
- },
- },
- {
- name: "success pre-rendering with helm",
- rc: requestContext{
- target: targetContext{
- branchConfig: branchConfig{
- AppConfigs: map[string]appConfig{
- testAppName: {
- ConfigManagement: configManagementConfig{
- Helm: &helm.Config{},
- },
- },
- },
- },
- },
- },
- service: &service{
- helmRenderFn: func(
- context.Context,
- string,
- string,
- string,
- []string,
- ) ([]byte, error) {
- return fakeManifest, nil
- },
- },
- assertions: func(manifests map[string][]byte, err error) {
- require.NoError(t, err)
- require.Equal(t, fakeManifest, manifests[testAppName])
- },
- },
- {
- name: "error pre-rendering with ytt",
- rc: requestContext{
- target: targetContext{
- branchConfig: branchConfig{
- AppConfigs: map[string]appConfig{
- "test-app": {
- ConfigManagement: configManagementConfig{
- Ytt: &ytt.Config{},
- },
- },
- },
- },
- },
- },
- service: &service{
- yttRenderFn: func(context.Context, []string) ([]byte, error) {
- return nil, errors.New("something went wrong")
- },
- },
- assertions: func(_ map[string][]byte, err error) {
- require.Error(t, err)
- require.Equal(t, "something went wrong", err.Error())
- },
- },
- {
- name: "success pre-rendering with ytt",
- rc: requestContext{
- target: targetContext{
- branchConfig: branchConfig{
- AppConfigs: map[string]appConfig{
- "test-app": {
- ConfigManagement: configManagementConfig{
- Ytt: &ytt.Config{},
- },
- },
- },
- },
- },
- },
- service: &service{
- yttRenderFn: func(context.Context, []string) ([]byte, error) {
- return fakeManifest, nil
- },
- },
- assertions: func(manifests map[string][]byte, err error) {
- require.NoError(t, err)
- require.Equal(t, fakeManifest, manifests[testAppName])
- },
- },
- {
- name: "error pre-rendering with kustomize",
- rc: requestContext{
- target: targetContext{
- branchConfig: branchConfig{
- AppConfigs: map[string]appConfig{
- "test-app": {
- ConfigManagement: configManagementConfig{
- Kustomize: &kustomize.Config{},
- },
- },
- },
- },
- },
- },
- service: &service{
- kustomizeRenderFn: func(
- context.Context,
- string,
- []string,
- kustomize.Config,
- ) ([]byte, error) {
- return nil, errors.New("something went wrong")
- },
- },
- assertions: func(manifests map[string][]byte, err error) {
- require.Error(t, err)
- require.Equal(t, "something went wrong", err.Error())
- },
- },
- {
- name: "success pre-rendering with kustomize",
- rc: requestContext{
- target: targetContext{
- branchConfig: branchConfig{
- AppConfigs: map[string]appConfig{
- "test-app": {
- ConfigManagement: configManagementConfig{
- Kustomize: &kustomize.Config{},
- },
- },
- },
- },
- },
- },
- service: &service{
- kustomizeRenderFn: func(
- context.Context,
- string,
- []string,
- kustomize.Config,
- ) ([]byte, error) {
- return fakeManifest, nil
- },
- },
- assertions: func(manifests map[string][]byte, err error) {
- require.NoError(t, err)
- require.Equal(t, fakeManifest, manifests[testAppName])
- },
- },
- {
- name: "safeguards against empty manifests",
- rc: requestContext{
- target: targetContext{
- branchConfig: branchConfig{
- AppConfigs: map[string]appConfig{
- "test-app": {
- ConfigManagement: configManagementConfig{
- Kustomize: &kustomize.Config{},
- },
- },
- },
- },
- },
- },
- service: &service{
- kustomizeRenderFn: func(
- context.Context,
- string,
- []string,
- kustomize.Config,
- ) ([]byte, error) {
- return []byte{}, nil // This is probably a mistake!
- },
- },
- assertions: func(manifests map[string][]byte, err error) {
- require.Error(t, err)
- require.Contains(
- t,
- err.Error(),
- "contain 0 bytes; this looks like a mistake",
- )
- },
- },
- }
- for _, testCase := range testCases {
- t.Run(testCase.name, func(t *testing.T) {
- testCase.rc.logger = &logrus.Entry{
- Logger: logrus.New(),
- }
- testCase.assertions(
- testCase.service.preRender(
- context.Background(),
- testCase.rc,
- "fake/repo/path",
- ),
- )
- })
- }
-}
diff --git a/schema.json b/schema.json
index 6db64b9..b023a15 100644
--- a/schema.json
+++ b/schema.json
@@ -40,6 +40,12 @@
},
"prs": {
"$ref": "#/definitions/pullRequestConfig"
+ },
+ "preservedPaths": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/relativePath"
+ }
}
}
},
@@ -62,90 +68,68 @@
"configManagementConfig": {
"type": "object",
- "oneOf": [
- {
- "required": ["helm"],
- "additionalProperties": false,
- "properties": {
- "helm": {
- "$ref": "#/definitions/helmConfig"
- }
- }
- },
- {
- "required": ["kustomize"],
- "additionalProperties": false,
- "properties": {
- "kustomize": {
- "$ref": "#/definitions/kustomizeConfig"
- }
- }
- },
- {
- "required": ["ytt"],
- "additionalProperties": false,
- "properties": {
- "ytt": {
- "$ref": "#/definitions/yttConfig"
- }
- }
- }
- ]
- },
-
- "helmConfig": {
- "type": "object",
- "required": ["releaseName"],
- "additionalProperties": false,
+ "required": ["path"],
"properties": {
- "releaseName": {
- "type": "string",
- "pattern": "^\\w[\\w-]*\\w$"
- },
- "namespace": {
- "type": "string",
- "pattern": "^\\w[\\w-]*\\w$"
- },
- "chartPath": {
+ "path": {
"$ref": "#/definitions/relativePath"
- },
- "valuesPaths": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/relativePath"
+ }
+ },
+ "unevaluatedProperties": false,
+ "oneOf": [{
+ "required": ["helm"],
+ "properties": {
+ "helm": {
+ "properties": {
+ "namespace": {
+ "type": "string"
+ },
+ "k8sVersion": {
+ "type": "string"
+ },
+ "apiVersions": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ },
+ "allOf": [{
+ "#ref": "argocd-schema.json#/definitions/helm"
+ }]
}
}
- }
- },
-
- "kustomizeConfig": {
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "path": {
- "$ref": "#/definitions/relativePath"
- },
- "enableHelm": {
- "type": "boolean"
- },
- "loadRestrictor": {
- "type": "string",
- "enum": ["LoadRestrictionsRootOnly", "LoadRestrictionsNone"]
+ }, {
+ "required": ["kustomize"],
+ "properties": {
+ "kustomize": {
+ "properties": {
+ "buildOptions": {
+ "type": "string"
+ }
+ },
+ "allOf": [{
+ "#ref": "argocd-schema.json#/definitions/kustomize"
+ }]
+ }
}
- }
- },
-
- "yttConfig": {
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "paths": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/relativePath"
+ }, {
+ "required": ["plugin"],
+ "properties": {
+ "plugin": {
+ "$ref": "argocd-schema.json#/definitions/plugin"
}
}
- }
+ }, {
+ "additionalProperties": false,
+ "properties": {
+ "path": {
+ "$ref": "#/definitions/relativePath"
+ },
+ "helm": false,
+ "kustomize": false,
+ "plugin": false
+ }
+ }]
},
"pullRequestConfig": {
diff --git a/service.go b/service.go
index d12e4ba..c188c8f 100644
--- a/service.go
+++ b/service.go
@@ -10,10 +10,8 @@ import (
uuid "github.com/satori/go.uuid"
log "github.com/sirupsen/logrus"
- "github.com/akuity/kargo-render/internal/helm"
- "github.com/akuity/kargo-render/internal/kustomize"
+ "github.com/akuity/kargo-render/internal/argocd"
"github.com/akuity/kargo-render/internal/manifests"
- "github.com/akuity/kargo-render/internal/ytt"
"github.com/akuity/kargo-render/pkg/git"
)
@@ -29,24 +27,11 @@ type Service interface {
}
type service struct {
- logger *log.Logger
-
- // These behaviors are overridable for testing purposes
- helmRenderFn func(
- ctx context.Context,
- releaseName string,
- namespace string,
- chartPath string,
- valuesPaths []string,
- ) ([]byte, error)
-
- yttRenderFn func(ctx context.Context, paths []string) ([]byte, error)
-
- kustomizeRenderFn func(
+ logger *log.Logger
+ renderFn func(
ctx context.Context,
path string,
- images []string,
- cfg kustomize.Config,
+ cfg argocd.ConfigManagementConfig,
) ([]byte, error)
}
@@ -62,10 +47,8 @@ func NewService(opts *ServiceOptions) Service {
logger := log.New()
logger.SetLevel(log.Level(opts.LogLevel))
return &service{
- logger: logger,
- helmRenderFn: helm.Render,
- yttRenderFn: ytt.Render,
- kustomizeRenderFn: kustomize.Render,
+ logger: logger,
+ renderFn: argocd.Render,
}
}
@@ -159,10 +142,8 @@ func (s *service) RenderManifests(
if len(rc.target.branchConfig.AppConfigs) == 0 {
rc.target.branchConfig.AppConfigs = map[string]appConfig{
"app": {
- ConfigManagement: configManagementConfig{
- Kustomize: &kustomize.Config{
- Path: rc.request.TargetBranch,
- },
+ ConfigManagement: argocd.ConfigManagementConfig{
+ Path: rc.request.TargetBranch,
},
},
}
diff --git a/service_test.go b/service_test.go
index 8ef1b23..77314f7 100644
--- a/service_test.go
+++ b/service_test.go
@@ -16,9 +16,7 @@ func TestNewService(t *testing.T) {
svc, ok := s.(*service)
require.True(t, ok)
require.NotNil(t, svc.logger)
- require.NotNil(t, svc.helmRenderFn)
- require.NotNil(t, svc.yttRenderFn)
- require.NotNil(t, svc.kustomizeRenderFn)
+ require.NotNil(t, svc.renderFn)
}
func TestWriteAppManifests(t *testing.T) {