mirror of
https://github.com/argoproj/argo-cd
synced 2026-05-24 01:38:43 +00:00
* feat: CLI: Allow setting Helm values literal (#3601) While you could already set values files using the `--values` flag with external data using the CLI with `argocd app create` and `argocd app set`, this was not yet possible for managing the literal `.spec.source.helm.values` value in an Application without resorting to a complicated `argocd app patch` escaped parameter or by generating the entire application YAML manifest by yourself. Therefore, this PR adds a `--values-literal-file` flag to the `argocd app create` and `argocd app set` commands, which accepts a local file name or URL to a values file, which will be read and included as a multiline string in the application manifest. This is different from the `--helm-set-file` flag which expects the file in the chart itself. The `argocd app unset` command is expanded with a `--values-literal` flag, so we can also unset this field again. I hope I chose nice enough names for the flags, I wanted to make clear it expects a file name, but also distinguish it enough from the existing `--values` flag which actually points to values files. Because the current `setHelmOpt()` functionality would not work for unsetting things to an empty value and it was difficult to do these changes independently, this PR also contains the fix for issue #3644. A separate PR has still been created for that one because I think it should end up as a separate issue in the release notes. * feat: CLI: Allow setting Helm values literal, add tests
This commit is contained in:
parent
2277af2f32
commit
36da074344
2 changed files with 96 additions and 6 deletions
|
|
@ -6,6 +6,7 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
|
|
@ -492,6 +493,18 @@ func setAppSpecOptions(flags *pflag.FlagSet, spec *argoappv1.ApplicationSpec, ap
|
|||
spec.RevisionHistoryLimit = &i
|
||||
case "values":
|
||||
setHelmOpt(&spec.Source, helmOpts{valueFiles: appOpts.valuesFiles})
|
||||
case "values-literal-file":
|
||||
var data []byte
|
||||
|
||||
// read uri
|
||||
parsedURL, err := url.ParseRequestURI(appOpts.values)
|
||||
if err != nil || !(parsedURL.Scheme == "http" || parsedURL.Scheme == "https") {
|
||||
data, err = ioutil.ReadFile(appOpts.values)
|
||||
} else {
|
||||
data, err = config.ReadRemoteFile(appOpts.values)
|
||||
}
|
||||
errors.CheckError(err)
|
||||
setHelmOpt(&spec.Source, helmOpts{values: string(data)})
|
||||
case "release-name":
|
||||
setHelmOpt(&spec.Source, helmOpts{releaseName: appOpts.releaseName})
|
||||
case "helm-set":
|
||||
|
|
@ -613,6 +626,7 @@ func setKustomizeOpt(src *argoappv1.ApplicationSource, opts kustomizeOpts) {
|
|||
|
||||
type helmOpts struct {
|
||||
valueFiles []string
|
||||
values string
|
||||
releaseName string
|
||||
helmSets []string
|
||||
helmSetStrings []string
|
||||
|
|
@ -626,6 +640,9 @@ func setHelmOpt(src *argoappv1.ApplicationSource, opts helmOpts) {
|
|||
if len(opts.valueFiles) > 0 {
|
||||
src.Helm.ValueFiles = opts.valueFiles
|
||||
}
|
||||
if len(opts.values) > 0 {
|
||||
src.Helm.Values = opts.values
|
||||
}
|
||||
if opts.releaseName != "" {
|
||||
src.Helm.ReleaseName = opts.releaseName
|
||||
}
|
||||
|
|
@ -684,6 +701,7 @@ type appOptions struct {
|
|||
destNamespace string
|
||||
parameters []string
|
||||
valuesFiles []string
|
||||
values string
|
||||
releaseName string
|
||||
helmSets []string
|
||||
helmSetStrings []string
|
||||
|
|
@ -716,6 +734,7 @@ func addAppFlags(command *cobra.Command, opts *appOptions) {
|
|||
command.Flags().StringVar(&opts.destNamespace, "dest-namespace", "", "K8s target namespace (overrides the namespace specified in the ksonnet app.yaml)")
|
||||
command.Flags().StringArrayVarP(&opts.parameters, "parameter", "p", []string{}, "set a parameter override (e.g. -p guestbook=image=example/guestbook:latest)")
|
||||
command.Flags().StringArrayVar(&opts.valuesFiles, "values", []string{}, "Helm values file(s) to use")
|
||||
command.Flags().StringVar(&opts.values, "values-literal-file", "", "Filename or URL to import as a literal Helm values block")
|
||||
command.Flags().StringVar(&opts.releaseName, "release-name", "", "Helm release-name")
|
||||
command.Flags().StringArrayVar(&opts.helmSets, "helm-set", []string{}, "Helm set values on the command line (can be repeated to set several values: --helm-set key1=val1 --helm-set key2=val2)")
|
||||
command.Flags().StringArrayVar(&opts.helmSetStrings, "helm-set-string", []string{}, "Helm set STRING values on the command line (can be repeated to set several values: --helm-set-string key1=val1 --helm-set-string key2=val2)")
|
||||
|
|
@ -741,6 +760,7 @@ func addAppFlags(command *cobra.Command, opts *appOptions) {
|
|||
func NewApplicationUnsetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
var (
|
||||
parameters []string
|
||||
valuesLiteral bool
|
||||
valuesFiles []string
|
||||
nameSuffix bool
|
||||
namePrefix bool
|
||||
|
|
@ -822,7 +842,7 @@ func NewApplicationUnsetCommand(clientOpts *argocdclient.ClientOptions) *cobra.C
|
|||
}
|
||||
}
|
||||
if app.Spec.Source.Helm != nil {
|
||||
if len(parameters) == 0 && len(valuesFiles) == 0 {
|
||||
if len(parameters) == 0 && len(valuesFiles) == 0 && !valuesLiteral {
|
||||
c.HelpFunc()(c, args)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
|
@ -836,17 +856,20 @@ func NewApplicationUnsetCommand(clientOpts *argocdclient.ClientOptions) *cobra.C
|
|||
}
|
||||
}
|
||||
}
|
||||
specValueFiles := app.Spec.Source.Helm.ValueFiles
|
||||
if valuesLiteral {
|
||||
app.Spec.Source.Helm.Values = ""
|
||||
updated = true
|
||||
}
|
||||
for _, valuesFile := range valuesFiles {
|
||||
specValueFiles := app.Spec.Source.Helm.ValueFiles
|
||||
for i, vf := range specValueFiles {
|
||||
if vf == valuesFile {
|
||||
specValueFiles = append(specValueFiles[0:i], specValueFiles[i+1:]...)
|
||||
app.Spec.Source.Helm.ValueFiles = append(specValueFiles[0:i], specValueFiles[i+1:]...)
|
||||
updated = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
setHelmOpt(&app.Spec.Source, helmOpts{valueFiles: specValueFiles})
|
||||
if !updated {
|
||||
return
|
||||
}
|
||||
|
|
@ -859,8 +882,9 @@ func NewApplicationUnsetCommand(clientOpts *argocdclient.ClientOptions) *cobra.C
|
|||
errors.CheckError(err)
|
||||
},
|
||||
}
|
||||
command.Flags().StringArrayVarP(¶meters, "parameter", "p", []string{}, "unset a parameter override (e.g. -p guestbook=image)")
|
||||
command.Flags().StringArrayVar(&valuesFiles, "values", []string{}, "unset one or more helm values files")
|
||||
command.Flags().StringArrayVarP(¶meters, "parameter", "p", []string{}, "Unset a parameter override (e.g. -p guestbook=image)")
|
||||
command.Flags().StringArrayVar(&valuesFiles, "values", []string{}, "Unset one or more Helm values files")
|
||||
command.Flags().BoolVar(&valuesLiteral, "values-literal", false, "Unset literal Helm values block")
|
||||
command.Flags().BoolVar(&nameSuffix, "namesuffix", false, "Kustomize namesuffix")
|
||||
command.Flags().BoolVar(&namePrefix, "nameprefix", false, "Kustomize nameprefix")
|
||||
command.Flags().BoolVar(&kustomizeVersion, "kustomize-version", false, "Kustomize version")
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ package e2e
|
|||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
|
|
@ -121,6 +123,70 @@ func TestHelmValues(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestHelmValuesLiteralFileLocal(t *testing.T) {
|
||||
Given(t).
|
||||
Path("helm").
|
||||
When().
|
||||
Create().
|
||||
AppSet("--values-literal-file", "testdata/helm/baz.yaml").
|
||||
Then().
|
||||
And(func(app *Application) {
|
||||
data, err := ioutil.ReadFile("testdata/helm/baz.yaml")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
assert.Equal(t, string(data), app.Spec.Source.Helm.Values)
|
||||
}).
|
||||
When().
|
||||
AppUnSet("--values-literal").
|
||||
Then().
|
||||
And(func(app *Application) {
|
||||
assert.Nil(t, app.Spec.Source.Helm)
|
||||
})
|
||||
}
|
||||
|
||||
func TestHelmValuesLiteralFileRemote(t *testing.T) {
|
||||
sentinel := "a: b"
|
||||
serve := func(c chan<- string) {
|
||||
// listen on first available dynamic (unprivileged) port
|
||||
listener, err := net.Listen("tcp", ":0")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// send back the address so that it can be used
|
||||
c <- listener.Addr().String()
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
// return the sentinel text at root URL
|
||||
fmt.Fprint(w, sentinel)
|
||||
})
|
||||
|
||||
panic(http.Serve(listener, nil))
|
||||
}
|
||||
c := make(chan string, 1)
|
||||
|
||||
// run a local webserver to test data retrieval
|
||||
go serve(c)
|
||||
address := <-c
|
||||
t.Logf("Listening at address: %s", address)
|
||||
|
||||
Given(t).
|
||||
Path("helm").
|
||||
When().
|
||||
Create().
|
||||
AppSet("--values-literal-file", "http://"+address).
|
||||
Then().
|
||||
And(func(app *Application) {
|
||||
assert.Equal(t, "a: b", app.Spec.Source.Helm.Values)
|
||||
}).
|
||||
When().
|
||||
AppUnSet("--values-literal").
|
||||
Then().
|
||||
And(func(app *Application) {
|
||||
assert.Nil(t, app.Spec.Source.Helm)
|
||||
})
|
||||
}
|
||||
|
||||
func TestHelmCrdHook(t *testing.T) {
|
||||
Given(t).
|
||||
Path("helm-crd").
|
||||
|
|
|
|||
Loading…
Reference in a new issue