Files
nomad/command/node_pool_apply_test.go
James Rasell c80c60965f node pool: Allow specifying node identity ttl in HCL or JSON spec. (#26825)
The node identity TTL defaults to 24hr but can be altered by
setting the node identity TTL parameter. In order to allow setting
and viewing the value, the field is now plumbed through the CLI
and HTTP API.

In order to parse the HCL, a new helper package has been created
which contains generic parsing and decoding functionality for
dealing with HCL that contains time durations. hclsimple can be
used when this functionality is not needed. In order to parse the
JSON, custom marshal and unmarshal functions have been created as
used in many other places.

The node pool init command has been updated to include this new
parameter, although commented out, so reference. The info command
now includes the TTL in its output too.
2025-09-24 14:20:34 +01:00

211 lines
5.0 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package command
import (
"fmt"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/hashicorp/cli"
"github.com/hashicorp/nomad/ci"
"github.com/shoenig/test"
"github.com/shoenig/test/must"
)
func TestNodePoolApplyCommand_Implements(t *testing.T) {
ci.Parallel(t)
var _ cli.Command = &NodePoolApplyCommand{}
}
func TestNodePoolApplyCommand_Run(t *testing.T) {
ci.Parallel(t)
// Start test server.
srv, client, url := testServer(t, true, nil)
defer srv.Shutdown()
waitForNodes(t, client)
// Initialize UI and command.
ui := cli.NewMockUi()
cmd := &NodePoolApplyCommand{Meta: Meta{Ui: ui}}
// Create node pool with HCL file.
hclTestFile := `
node_pool "dev" {
description = "dev node pool"
}`
file, err := os.CreateTemp(t.TempDir(), "node-pool-test-*.hcl")
must.NoError(t, err)
_, err = file.WriteString(hclTestFile)
must.NoError(t, err)
// Run command.
args := []string{"-address", url, file.Name()}
code := cmd.Run(args)
must.Eq(t, 0, code)
// Verify node pool was created.
got, err := srv.Agent.Server().State().NodePoolByName(nil, "dev")
must.NoError(t, err)
must.NotNil(t, got)
// Update node pool.
file.Truncate(0)
file.Seek(0, 0)
hclTestFile = `
node_pool "dev" {
description = "dev node pool"
node_identity_ttl = "720h"
meta {
test = "true"
}
}`
_, err = file.WriteString(hclTestFile)
must.NoError(t, err)
// Run command.
code = cmd.Run(args)
must.Eq(t, 0, code)
// Verify node pool was updated.
got, err = srv.Agent.Server().State().NodePoolByName(nil, "dev")
must.NoError(t, err)
must.NotNil(t, got)
must.NotNil(t, got.Meta)
must.Eq(t, "true", got.Meta["test"])
must.Eq(t, 720*time.Hour, got.NodeIdentityTTL)
// Create node pool with JSON file.
jsonTestFile := `
{
"Name": "prod",
"Description": "prod node pool",
"NodeIdentityTTL": "720h"
}`
file, err = os.CreateTemp(t.TempDir(), "node-pool-test-*.json")
must.NoError(t, err)
_, err = file.WriteString(jsonTestFile)
must.NoError(t, err)
// Run command.
args = []string{"-address", url, "-json", file.Name()}
code = cmd.Run(args)
must.Eq(t, 0, code)
// Verify node pool was created.
got, err = srv.Agent.Server().State().NodePoolByName(nil, "prod")
must.NoError(t, err)
must.NotNil(t, got)
}
func TestNodePoolApplyCommand_Run_fail(t *testing.T) {
ci.Parallel(t)
// Start test server.
srv, client, url := testServer(t, true, nil)
defer srv.Shutdown()
waitForNodes(t, client)
testCases := []struct {
name string
args []string
input string
expectedOutput string
expectedCode int
}{
{
name: "missing file",
args: []string{},
expectedOutput: "This command takes one argument",
expectedCode: 1,
},
{
name: "file doesn't exist",
args: []string{"doesn-exist.hcl"},
expectedOutput: "no such file",
expectedCode: 1,
},
{
name: "invalid json",
args: []string{"-json", "invalid.json"},
input: "not json",
expectedOutput: "Failed to parse input",
expectedCode: 1,
},
{
name: "invalid hcl",
args: []string{"invalid.hcl"},
input: "not HCL",
expectedOutput: "Failed to parse input",
expectedCode: 1,
},
{
name: "valid json without json flag",
args: []string{"valid.json"},
input: `{"Name": "dev"}`,
expectedOutput: "Failed to parse input",
expectedCode: 1,
},
{
name: "valid hcl with json flag",
args: []string{"-json", "valid.hcl"},
input: `node_pool "dev" {}`,
expectedOutput: "Failed to parse input",
expectedCode: 1,
},
{
name: "invalid node pool hcl",
args: []string{"invalid.hcl"},
input: `not_a_node_pool "dev" {}`,
expectedOutput: "Failed to parse input",
expectedCode: 1,
},
{
name: "invalid node pool",
args: []string{"-address", url, "invalid_node_pool.hcl"},
input: `node_pool "invalid name" {}`,
expectedOutput: "Error applying node pool",
expectedCode: 1,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Initialize UI and command.
ui := cli.NewMockUi()
cmd := &NodePoolApplyCommand{Meta: Meta{Ui: ui}}
// Write input to file.
if tc.input != "" {
// Split the filename and extension from the last argument to
// add a "*" between them so os.CreateTemp retains the file
// extension.
filename := tc.args[len(tc.args)-1]
ext := filepath.Ext(filename)
name, _ := strings.CutSuffix(filename, ext)
file, err := os.CreateTemp(t.TempDir(), fmt.Sprintf("%s-*%s", name, ext))
must.NoError(t, err)
_, err = file.WriteString(tc.input)
must.NoError(t, err)
// Update last arg with full test file path.
tc.args[len(tc.args)-1] = file.Name()
}
got := cmd.Run(tc.args)
test.Eq(t, tc.expectedCode, got)
test.StrContains(t, ui.ErrorWriter.String(), tc.expectedOutput)
})
}
}