278 lines
7.9 KiB
Go
278 lines
7.9 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package command
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"regexp"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/hashicorp/nomad/api"
|
|
"github.com/hashicorp/nomad/ci"
|
|
"github.com/mitchellh/cli"
|
|
"github.com/posener/complete"
|
|
"github.com/shoenig/test/must"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestVarPutCommand_Implements(t *testing.T) {
|
|
ci.Parallel(t)
|
|
var _ cli.Command = &VarPutCommand{}
|
|
}
|
|
func TestVarPutCommand_Fails(t *testing.T) {
|
|
ci.Parallel(t)
|
|
t.Run("bad_args", func(t *testing.T) {
|
|
ci.Parallel(t)
|
|
ui := cli.NewMockUi()
|
|
cmd := &VarPutCommand{Meta: Meta{Ui: ui}}
|
|
code := cmd.Run([]string{"-bad-flag"})
|
|
out := ui.ErrorWriter.String()
|
|
require.Equal(t, 1, code, "expected exit code 1, got: %d")
|
|
require.Contains(t, out, commandErrorText(cmd), "expected help output, got: %s", out)
|
|
})
|
|
t.Run("bad_address", func(t *testing.T) {
|
|
ci.Parallel(t)
|
|
ui := cli.NewMockUi()
|
|
cmd := &VarPutCommand{Meta: Meta{Ui: ui}}
|
|
code := cmd.Run([]string{"-address=nope", "foo", "-"})
|
|
out := ui.ErrorWriter.String()
|
|
require.Equal(t, 1, code, "expected exit code 1, got: %d")
|
|
require.Contains(t, out, "Error creating variable", "expected error creating variable, got: %s", out)
|
|
})
|
|
t.Run("missing_template", func(t *testing.T) {
|
|
ci.Parallel(t)
|
|
ui := cli.NewMockUi()
|
|
cmd := &VarPutCommand{Meta: Meta{Ui: ui}}
|
|
code := cmd.Run([]string{`-out=go-template`, "foo", "-"})
|
|
out := strings.TrimSpace(ui.ErrorWriter.String())
|
|
require.Equal(t, 1, code, "expected exit code 1, got: %d", code)
|
|
require.Equal(t, errMissingTemplate+"\n"+commandErrorText(cmd), out)
|
|
})
|
|
t.Run("unexpected_template", func(t *testing.T) {
|
|
ci.Parallel(t)
|
|
ui := cli.NewMockUi()
|
|
cmd := &VarPutCommand{Meta: Meta{Ui: ui}}
|
|
code := cmd.Run([]string{`-out=json`, `-template="bad"`, "foo", "-"})
|
|
out := strings.TrimSpace(ui.ErrorWriter.String())
|
|
require.Equal(t, 1, code, "expected exit code 1, got: %d", code)
|
|
require.Equal(t, errUnexpectedTemplate+"\n"+commandErrorText(cmd), out)
|
|
})
|
|
t.Run("bad_in", func(t *testing.T) {
|
|
ci.Parallel(t)
|
|
ui := cli.NewMockUi()
|
|
cmd := &VarPutCommand{Meta: Meta{Ui: ui}}
|
|
code := cmd.Run([]string{`-in=bad`, "foo", "-"})
|
|
out := strings.TrimSpace(ui.ErrorWriter.String())
|
|
require.Equal(t, 1, code, "expected exit code 1, got: %d", code)
|
|
require.Equal(t, errInvalidInFormat+"\n"+commandErrorText(cmd), out)
|
|
})
|
|
t.Run("wildcard_namespace", func(t *testing.T) {
|
|
ci.Parallel(t)
|
|
ui := cli.NewMockUi()
|
|
cmd := &VarPutCommand{Meta: Meta{Ui: ui}}
|
|
code := cmd.Run([]string{`-namespace=*`, "foo", "-"})
|
|
out := strings.TrimSpace(ui.ErrorWriter.String())
|
|
require.Equal(t, 1, code, "expected exit code 1, got: %d", code)
|
|
require.Equal(t, errWildcardNamespaceNotAllowed, out)
|
|
})
|
|
}
|
|
|
|
func TestVarPutCommand_GoodJson(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
// Create a server
|
|
srv, client, url := testServer(t, true, nil)
|
|
defer srv.Shutdown()
|
|
|
|
ui := cli.NewMockUi()
|
|
cmd := &VarPutCommand{Meta: Meta{Ui: ui}}
|
|
|
|
// Get the variable
|
|
code := cmd.Run([]string{"-address=" + url, "-out=json", "test/var", "k1=v1", "k2=v2"})
|
|
require.Equal(t, 0, code, "expected exit 0, got: %d; %v", code, ui.ErrorWriter.String())
|
|
|
|
t.Cleanup(func() {
|
|
_, _ = client.Variables().Delete("test/var", nil)
|
|
})
|
|
|
|
var outVar api.Variable
|
|
b := ui.OutputWriter.Bytes()
|
|
err := json.Unmarshal(b, &outVar)
|
|
require.NoError(t, err, "error unmarshaling json: %v\nb: %s", err, b)
|
|
require.Equal(t, "default", outVar.Namespace)
|
|
require.Equal(t, "test/var", outVar.Path)
|
|
require.Equal(t, api.VariableItems{"k1": "v1", "k2": "v2"}, outVar.Items)
|
|
}
|
|
|
|
func TestVarPutCommand_FlagsWithSpec(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
// Create a server
|
|
srv, _, url := testServer(t, true, nil)
|
|
defer srv.Shutdown()
|
|
|
|
ui := cli.NewMockUi()
|
|
cmd := &VarPutCommand{Meta: Meta{Ui: ui}}
|
|
|
|
// Create a temporary file and ensure the proper cleanup is run once the
|
|
// test ends.
|
|
osFile, err := os.CreateTemp("", "nomad-cli-var-put-test-*.hcl")
|
|
must.NoError(t, err)
|
|
|
|
t.Cleanup(func() {
|
|
_ = osFile.Close()
|
|
_ = os.Remove(osFile.Name())
|
|
})
|
|
|
|
// Write out a partial spec that includes the namespace and variable path.
|
|
_, err = osFile.Write([]byte("path = \"path/to/variable\"\nnamespace = \"default\""))
|
|
must.NoError(t, err)
|
|
|
|
// Create the variables, ensure we clean it up, and check the command
|
|
// response.
|
|
code := cmd.Run([]string{"-address=" + url, "@" + osFile.Name(), "k1=v1", "k2=v2"})
|
|
must.Zero(t, code)
|
|
|
|
must.StrContains(t, ui.OutputWriter.String(), "path/to/variable")
|
|
must.StrContains(t, ui.OutputWriter.String(), "\"k1\": \"v1\"")
|
|
must.StrContains(t, ui.OutputWriter.String(), "\"k2\": \"v2\"")
|
|
}
|
|
|
|
func TestVarPutCommand_AutocompleteArgs(t *testing.T) {
|
|
ci.Parallel(t)
|
|
srv, client, url := testServer(t, true, nil)
|
|
defer srv.Shutdown()
|
|
|
|
ui := cli.NewMockUi()
|
|
cmd := &VarPutCommand{Meta: Meta{Ui: ui, flagAddress: url}}
|
|
|
|
// Create a var
|
|
sv := testVariable()
|
|
_, _, err := client.Variables().Create(sv, nil)
|
|
require.NoError(t, err)
|
|
|
|
args := complete.Args{Last: "t"}
|
|
predictor := cmd.AutocompleteArgs()
|
|
|
|
res := predictor.Predict(args)
|
|
require.Equal(t, 1, len(res))
|
|
require.Equal(t, sv.Path, res[0])
|
|
}
|
|
|
|
func TestVarPutCommand_KeyWarning(t *testing.T) {
|
|
// Extract invalid characters from warning message.
|
|
r := regexp.MustCompile(`contains characters \[(.*)\]`)
|
|
|
|
tcs := []struct {
|
|
name string
|
|
goodKeys []string
|
|
badKeys []string
|
|
badChars []string
|
|
}{
|
|
{
|
|
name: "simple",
|
|
goodKeys: []string{"simple"},
|
|
},
|
|
{
|
|
name: "hasDot",
|
|
badKeys: []string{"has.Dot"},
|
|
badChars: []string{`"."`},
|
|
},
|
|
{
|
|
name: "unicode_letters",
|
|
goodKeys: []string{"世界"},
|
|
},
|
|
{
|
|
name: "unicode_numbers",
|
|
goodKeys: []string{"٣٢١"},
|
|
},
|
|
{
|
|
name: "two_good",
|
|
goodKeys: []string{"aardvark", "beagle"},
|
|
},
|
|
{
|
|
name: "one_good_one_bad",
|
|
goodKeys: []string{"aardvark"},
|
|
badKeys: []string{"bad.key"},
|
|
badChars: []string{`"."`},
|
|
},
|
|
{
|
|
name: "one_good_two_bad",
|
|
goodKeys: []string{"aardvark"},
|
|
badKeys: []string{"bad.key", "also-bad"},
|
|
badChars: []string{`"."`, `"-"`},
|
|
},
|
|
{
|
|
name: "repeated_bad_char",
|
|
goodKeys: []string{"aardvark"},
|
|
badKeys: []string{"bad.key", "also.bad"},
|
|
badChars: []string{`"."`, `"."`},
|
|
},
|
|
{
|
|
name: "repeated_bad_char_same_key",
|
|
goodKeys: []string{"aardvark"},
|
|
badKeys: []string{"bad.key."},
|
|
badChars: []string{`"."`},
|
|
},
|
|
{
|
|
name: "dont_escape",
|
|
goodKeys: []string{"aardvark"},
|
|
badKeys: []string{"bad\\key"},
|
|
badChars: []string{`"\"`},
|
|
},
|
|
}
|
|
|
|
ci.Parallel(t)
|
|
_, _, url := testServer(t, false, nil)
|
|
|
|
for _, tc := range tcs {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
tc := tc // capture test case
|
|
ci.Parallel(t) // make the subtests parallel
|
|
|
|
keys := append(tc.goodKeys, tc.badKeys...) // combine keys into a single slice
|
|
for i, k := range keys {
|
|
keys[i] = k + "=value" // Make each key into a k=v pair; value is not part of test
|
|
}
|
|
|
|
ui := cli.NewMockUi()
|
|
cmd := &VarPutCommand{Meta: Meta{Ui: ui}}
|
|
args := append([]string{"-address=" + url, "-force", "-out=json", "test/var"}, keys...)
|
|
code := cmd.Run(args)
|
|
errOut := ui.ErrorWriter.String()
|
|
|
|
must.Eq(t, 0, code) // the command should always succeed
|
|
|
|
badKeysLen := len(tc.badKeys)
|
|
switch badKeysLen {
|
|
case 0:
|
|
must.Eq(t, "", errOut) // cases with no bad keys shouldn't put anything to stderr
|
|
return
|
|
case 1:
|
|
must.StrContains(t, errOut, "1 warning:") // header should be singular
|
|
default:
|
|
must.StrContains(t, errOut, fmt.Sprintf("%d warnings:", badKeysLen)) // header should be plural
|
|
}
|
|
|
|
for _, k := range tc.badKeys {
|
|
must.StrContains(t, errOut, k) // every bad key should appear in the warning output
|
|
}
|
|
|
|
if len(tc.badChars) > 0 {
|
|
invalid := r.FindAllStringSubmatch(errOut, -1)
|
|
for i, k := range tc.badChars {
|
|
must.Eq(t, invalid[i][1], k) // every bad char should appear in the warning output
|
|
}
|
|
}
|
|
|
|
for _, k := range tc.goodKeys {
|
|
must.StrNotContains(t, errOut, k) // good keys should not be emitted
|
|
}
|
|
})
|
|
}
|
|
}
|