mirror of
https://github.com/kemko/nomad.git
synced 2026-01-01 16:05:42 +03:00
The server startup could "hang" to the view of an operator if it had a key that could not be decrypted or replicated loaded from the FSM at startup. In order to prevent this happening, the server startup function will now use a timeout to wait for the encrypter to be ready. If the timeout is reached, the error is sent back to the caller which fails the CLI command. This bubbling of error message will also flush to logs which will provide addition operator feedback. The server only cares about keys loaded from the FSM snapshot and trailing logs before the encrypter should be classed as ready. So that the encrypter ready function does not get blocked by keys added outside of the initial Raft load, we take a snapshot of the decryption tasks as we enter the blocking call, and class these as our barrier.
1847 lines
49 KiB
Go
1847 lines
49 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package agent
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"runtime"
|
|
"testing"
|
|
"time"
|
|
|
|
sockaddr "github.com/hashicorp/go-sockaddr"
|
|
"github.com/hashicorp/nomad/ci"
|
|
client "github.com/hashicorp/nomad/client/config"
|
|
"github.com/hashicorp/nomad/client/testutil"
|
|
"github.com/hashicorp/nomad/helper/pointer"
|
|
"github.com/hashicorp/nomad/nomad/structs"
|
|
"github.com/hashicorp/nomad/nomad/structs/config"
|
|
"github.com/shoenig/test/must"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
var (
|
|
// trueValue/falseValue are used to get a pointer to a boolean
|
|
trueValue = true
|
|
falseValue = false
|
|
)
|
|
|
|
func TestConfig_Merge(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
c0 := &Config{}
|
|
|
|
c1 := &Config{
|
|
Telemetry: &Telemetry{},
|
|
Client: &ClientConfig{},
|
|
Server: &ServerConfig{},
|
|
ACL: &ACLConfig{},
|
|
Audit: &config.AuditConfig{},
|
|
Ports: &Ports{},
|
|
Addresses: &Addresses{},
|
|
AdvertiseAddrs: &AdvertiseAddrs{},
|
|
Sentinel: &config.SentinelConfig{},
|
|
Autopilot: &config.AutopilotConfig{},
|
|
}
|
|
|
|
c2 := &Config{
|
|
Region: "global",
|
|
Datacenter: "dc1",
|
|
NodeName: "node1",
|
|
DataDir: "/tmp/dir1",
|
|
PluginDir: "/tmp/pluginDir1",
|
|
LogLevel: "INFO",
|
|
LogIncludeLocation: false,
|
|
LogJson: false,
|
|
EnableDebug: false,
|
|
LeaveOnInt: false,
|
|
LeaveOnTerm: false,
|
|
EnableSyslog: false,
|
|
SyslogFacility: "local0.info",
|
|
DisableUpdateCheck: pointer.Of(false),
|
|
DisableAnonymousSignature: false,
|
|
BindAddr: "127.0.0.1",
|
|
Telemetry: &Telemetry{
|
|
StatsiteAddr: "127.0.0.1:8125",
|
|
StatsdAddr: "127.0.0.1:8125",
|
|
DataDogAddr: "127.0.0.1:8125",
|
|
DataDogTags: []string{"cat1:tag1", "cat2:tag2"},
|
|
PrometheusMetrics: true,
|
|
DisableHostname: false,
|
|
DisableAllocationHookMetrics: pointer.Of(false),
|
|
CirconusAPIToken: "0",
|
|
CirconusAPIApp: "nomadic",
|
|
CirconusAPIURL: "http://api.circonus.com/v2",
|
|
CirconusSubmissionInterval: "60s",
|
|
CirconusCheckSubmissionURL: "https://someplace.com/metrics",
|
|
CirconusCheckID: "0",
|
|
CirconusCheckForceMetricActivation: "true",
|
|
CirconusCheckInstanceID: "node1:nomadic",
|
|
CirconusCheckSearchTag: "service:nomadic",
|
|
CirconusCheckDisplayName: "node1:nomadic",
|
|
CirconusCheckTags: "cat1:tag1,cat2:tag2",
|
|
CirconusBrokerID: "0",
|
|
CirconusBrokerSelectTag: "dc:dc1",
|
|
PrefixFilter: []string{"filter1", "filter2"},
|
|
},
|
|
Audit: &config.AuditConfig{
|
|
Enabled: pointer.Of(true),
|
|
Sinks: []*config.AuditSink{
|
|
{
|
|
DeliveryGuarantee: "enforced",
|
|
Name: "file",
|
|
Type: "file",
|
|
Format: "json",
|
|
Path: "/opt/nomad/audit.log",
|
|
RotateDuration: 24 * time.Hour,
|
|
RotateDurationHCL: "24h",
|
|
RotateBytes: 100,
|
|
RotateMaxFiles: 10,
|
|
},
|
|
},
|
|
},
|
|
Client: &ClientConfig{
|
|
Enabled: false,
|
|
StateDir: "/tmp/state1",
|
|
AllocDir: "/tmp/alloc1",
|
|
NodeClass: "class1",
|
|
NodePool: "dev",
|
|
Options: map[string]string{
|
|
"foo": "bar",
|
|
},
|
|
NetworkSpeed: 100,
|
|
CpuCompute: 100,
|
|
MinDynamicPort: 10001,
|
|
MaxDynamicPort: 10002,
|
|
MemoryMB: 100,
|
|
MaxKillTimeout: "20s",
|
|
ClientMaxPort: 19996,
|
|
DisableRemoteExec: false,
|
|
TemplateConfig: &client.ClientTemplateConfig{
|
|
FunctionDenylist: client.DefaultTemplateFunctionDenylist,
|
|
DisableSandbox: false,
|
|
},
|
|
Reserved: &Resources{
|
|
CPU: 10,
|
|
MemoryMB: 10,
|
|
DiskMB: 10,
|
|
ReservedPorts: "1,10-30,55",
|
|
},
|
|
NomadServiceDiscovery: pointer.Of(false),
|
|
},
|
|
Server: &ServerConfig{
|
|
Enabled: false,
|
|
AuthoritativeRegion: "global",
|
|
BootstrapExpect: 1,
|
|
DataDir: "/tmp/data1",
|
|
ProtocolVersion: 1,
|
|
RaftProtocol: 1,
|
|
RaftMultiplier: pointer.Of(5),
|
|
RaftSnapshotThreshold: pointer.Of(100),
|
|
RaftSnapshotInterval: pointer.Of("30m"),
|
|
RaftTrailingLogs: pointer.Of(200),
|
|
NumSchedulers: pointer.Of(1),
|
|
NodeGCThreshold: "1h",
|
|
BatchEvalGCThreshold: "4h",
|
|
HeartbeatGrace: 30 * time.Second,
|
|
MinHeartbeatTTL: 30 * time.Second,
|
|
MaxHeartbeatsPerSecond: 30.0,
|
|
RedundancyZone: "foo",
|
|
UpgradeVersion: "foo",
|
|
EnableEventBroker: pointer.Of(false),
|
|
EventBufferSize: pointer.Of(0),
|
|
PlanRejectionTracker: &PlanRejectionTracker{
|
|
Enabled: pointer.Of(true),
|
|
NodeThreshold: 100,
|
|
NodeWindow: 11 * time.Minute,
|
|
},
|
|
OIDCIssuer: "https://oidc.test.nomadproject.io",
|
|
StartTimeout: "45s",
|
|
},
|
|
ACL: &ACLConfig{
|
|
Enabled: true,
|
|
TokenTTL: 60 * time.Second,
|
|
PolicyTTL: 60 * time.Second,
|
|
RoleTTL: 60 * time.Second,
|
|
TokenMinExpirationTTL: 60 * time.Second,
|
|
TokenMaxExpirationTTL: 60 * time.Second,
|
|
ReplicationToken: "foo",
|
|
},
|
|
Ports: &Ports{
|
|
HTTP: 4646,
|
|
RPC: 4647,
|
|
Serf: 4648,
|
|
},
|
|
Addresses: &Addresses{
|
|
HTTP: "127.0.0.1",
|
|
RPC: "127.0.0.1",
|
|
Serf: "127.0.0.1",
|
|
},
|
|
AdvertiseAddrs: &AdvertiseAddrs{
|
|
RPC: "127.0.0.1",
|
|
Serf: "127.0.0.1",
|
|
},
|
|
HTTPAPIResponseHeaders: map[string]string{
|
|
"Access-Control-Allow-Origin": "*",
|
|
},
|
|
Vaults: []*config.VaultConfig{{
|
|
Name: structs.VaultDefaultCluster,
|
|
Addr: "1",
|
|
TLSCaFile: "1",
|
|
TLSCaPath: "1",
|
|
TLSCertFile: "1",
|
|
TLSKeyFile: "1",
|
|
TLSSkipVerify: &falseValue,
|
|
TLSServerName: "1",
|
|
}},
|
|
Consuls: []*config.ConsulConfig{{
|
|
ServerServiceName: "1",
|
|
ClientServiceName: "1",
|
|
AutoAdvertise: &falseValue,
|
|
Addr: "1",
|
|
Timeout: 1 * time.Second,
|
|
Token: "1",
|
|
Auth: "1",
|
|
EnableSSL: &falseValue,
|
|
VerifySSL: &falseValue,
|
|
CAFile: "1",
|
|
CertFile: "1",
|
|
KeyFile: "1",
|
|
ServerAutoJoin: &falseValue,
|
|
ClientAutoJoin: &falseValue,
|
|
ChecksUseAdvertise: &falseValue,
|
|
}},
|
|
Autopilot: &config.AutopilotConfig{
|
|
CleanupDeadServers: &falseValue,
|
|
ServerStabilizationTime: 1 * time.Second,
|
|
LastContactThreshold: 1 * time.Second,
|
|
MaxTrailingLogs: 1,
|
|
MinQuorum: 1,
|
|
EnableRedundancyZones: &falseValue,
|
|
DisableUpgradeMigration: &falseValue,
|
|
EnableCustomUpgrades: &falseValue,
|
|
},
|
|
Plugins: []*config.PluginConfig{
|
|
{
|
|
Name: "docker",
|
|
Args: []string{"foo"},
|
|
Config: map[string]interface{}{
|
|
"bar": 1,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
c3 := &Config{
|
|
Region: "global",
|
|
Datacenter: "dc2",
|
|
NodeName: "node2",
|
|
DataDir: "/tmp/dir2",
|
|
PluginDir: "/tmp/pluginDir2",
|
|
LogLevel: "DEBUG",
|
|
LogIncludeLocation: true,
|
|
LogJson: true,
|
|
EnableDebug: true,
|
|
LeaveOnInt: true,
|
|
LeaveOnTerm: true,
|
|
EnableSyslog: true,
|
|
SyslogFacility: "local0.debug",
|
|
DisableUpdateCheck: pointer.Of(true),
|
|
DisableAnonymousSignature: true,
|
|
BindAddr: "127.0.0.2",
|
|
Audit: &config.AuditConfig{
|
|
Enabled: pointer.Of(true),
|
|
Sinks: []*config.AuditSink{
|
|
{
|
|
DeliveryGuarantee: "enforced",
|
|
Name: "file",
|
|
Type: "file",
|
|
Format: "json",
|
|
Path: "/opt/nomad/audit.log",
|
|
RotateDuration: 24 * time.Hour,
|
|
RotateDurationHCL: "24h",
|
|
RotateBytes: 100,
|
|
RotateMaxFiles: 10,
|
|
},
|
|
},
|
|
},
|
|
Telemetry: &Telemetry{
|
|
StatsiteAddr: "127.0.0.2:8125",
|
|
StatsdAddr: "127.0.0.2:8125",
|
|
DataDogAddr: "127.0.0.1:8125",
|
|
DataDogTags: []string{"cat1:tag1", "cat2:tag2"},
|
|
PrometheusMetrics: true,
|
|
DisableHostname: true,
|
|
DisableAllocationHookMetrics: pointer.Of(true),
|
|
PublishNodeMetrics: true,
|
|
PublishAllocationMetrics: true,
|
|
CirconusAPIToken: "1",
|
|
CirconusAPIApp: "nomad",
|
|
CirconusAPIURL: "https://api.circonus.com/v2",
|
|
CirconusSubmissionInterval: "10s",
|
|
CirconusCheckSubmissionURL: "https://example.com/metrics",
|
|
CirconusCheckID: "1",
|
|
CirconusCheckForceMetricActivation: "false",
|
|
CirconusCheckInstanceID: "node2:nomad",
|
|
CirconusCheckSearchTag: "service:nomad",
|
|
CirconusCheckDisplayName: "node2:nomad",
|
|
CirconusCheckTags: "cat1:tag1,cat2:tag2",
|
|
CirconusBrokerID: "1",
|
|
CirconusBrokerSelectTag: "dc:dc2",
|
|
PrefixFilter: []string{"prefix1", "prefix2"},
|
|
DisableDispatchedJobSummaryMetrics: true,
|
|
DisableQuotaUtilizationMetrics: false,
|
|
DisableRPCRateMetricsLabels: true,
|
|
FilterDefault: pointer.Of(false),
|
|
},
|
|
Client: &ClientConfig{
|
|
Enabled: true,
|
|
StateDir: "/tmp/state2",
|
|
AllocDir: "/tmp/alloc2",
|
|
NodeClass: "class2",
|
|
NodePool: "dev",
|
|
Servers: []string{"server2"},
|
|
Meta: map[string]string{
|
|
"baz": "zip",
|
|
},
|
|
Options: map[string]string{
|
|
"foo": "bar",
|
|
"baz": "zip",
|
|
},
|
|
ChrootEnv: map[string]string{},
|
|
ClientMaxPort: 20000,
|
|
ClientMinPort: 22000,
|
|
NetworkSpeed: 105,
|
|
CpuCompute: 105,
|
|
MinDynamicPort: 10002,
|
|
MaxDynamicPort: 10003,
|
|
MemoryMB: 105,
|
|
MaxKillTimeout: "50s",
|
|
DisableRemoteExec: false,
|
|
TemplateConfig: &client.ClientTemplateConfig{
|
|
FunctionDenylist: client.DefaultTemplateFunctionDenylist,
|
|
DisableSandbox: false,
|
|
BlockQueryWaitTime: pointer.Of(5 * time.Minute),
|
|
MaxStale: pointer.Of(client.DefaultTemplateMaxStale),
|
|
Wait: &client.WaitConfig{
|
|
Min: pointer.Of(5 * time.Second),
|
|
Max: pointer.Of(4 * time.Minute),
|
|
},
|
|
ConsulRetry: &client.RetryConfig{Attempts: pointer.Of(0)},
|
|
VaultRetry: &client.RetryConfig{Attempts: pointer.Of(0)},
|
|
NomadRetry: &client.RetryConfig{Attempts: pointer.Of(0)},
|
|
},
|
|
Reserved: &Resources{
|
|
CPU: 15,
|
|
MemoryMB: 15,
|
|
DiskMB: 15,
|
|
ReservedPorts: "2,10-30,55",
|
|
},
|
|
GCInterval: 6 * time.Second,
|
|
GCParallelDestroys: 6,
|
|
GCDiskUsageThreshold: 71,
|
|
GCInodeUsageThreshold: 86,
|
|
NomadServiceDiscovery: pointer.Of(false),
|
|
},
|
|
Server: &ServerConfig{
|
|
Enabled: true,
|
|
AuthoritativeRegion: "global2",
|
|
BootstrapExpect: 2,
|
|
DataDir: "/tmp/data2",
|
|
ProtocolVersion: 2,
|
|
RaftProtocol: 2,
|
|
RaftMultiplier: pointer.Of(6),
|
|
RaftSnapshotThreshold: pointer.Of(100),
|
|
RaftSnapshotInterval: pointer.Of("30m"),
|
|
RaftTrailingLogs: pointer.Of(200),
|
|
NumSchedulers: pointer.Of(2),
|
|
EnabledSchedulers: []string{structs.JobTypeBatch},
|
|
NodeGCThreshold: "12h",
|
|
BatchEvalGCThreshold: "4h",
|
|
HeartbeatGrace: 2 * time.Minute,
|
|
MinHeartbeatTTL: 2 * time.Minute,
|
|
MaxHeartbeatsPerSecond: 200.0,
|
|
RejoinAfterLeave: true,
|
|
StartJoin: []string{"1.1.1.1"},
|
|
RetryJoin: []string{"1.1.1.1"},
|
|
RetryInterval: time.Second * 10,
|
|
NonVotingServer: true,
|
|
RedundancyZone: "bar",
|
|
UpgradeVersion: "bar",
|
|
EnableEventBroker: pointer.Of(true),
|
|
EventBufferSize: pointer.Of(100),
|
|
PlanRejectionTracker: &PlanRejectionTracker{
|
|
Enabled: pointer.Of(true),
|
|
NodeThreshold: 100,
|
|
NodeWindow: 11 * time.Minute,
|
|
},
|
|
JobMaxPriority: pointer.Of(200),
|
|
JobDefaultPriority: pointer.Of(100),
|
|
OIDCIssuer: "https://oidc.test.nomadproject.io",
|
|
StartTimeout: "1m",
|
|
},
|
|
ACL: &ACLConfig{
|
|
Enabled: true,
|
|
TokenTTL: 20 * time.Second,
|
|
PolicyTTL: 20 * time.Second,
|
|
RoleTTL: 20 * time.Second,
|
|
TokenMinExpirationTTL: 20 * time.Second,
|
|
TokenMaxExpirationTTL: 20 * time.Second,
|
|
ReplicationToken: "foobar",
|
|
},
|
|
Ports: &Ports{
|
|
HTTP: 20000,
|
|
RPC: 21000,
|
|
Serf: 22000,
|
|
},
|
|
Addresses: &Addresses{
|
|
HTTP: "127.0.0.2",
|
|
RPC: "127.0.0.2",
|
|
Serf: "127.0.0.2",
|
|
},
|
|
AdvertiseAddrs: &AdvertiseAddrs{
|
|
RPC: "127.0.0.2",
|
|
Serf: "127.0.0.2",
|
|
},
|
|
HTTPAPIResponseHeaders: map[string]string{
|
|
"Access-Control-Allow-Origin": "*",
|
|
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
},
|
|
Vaults: []*config.VaultConfig{{
|
|
Name: structs.VaultDefaultCluster,
|
|
Addr: "2",
|
|
TLSCaFile: "2",
|
|
TLSCaPath: "2",
|
|
TLSCertFile: "2",
|
|
TLSKeyFile: "2",
|
|
TLSSkipVerify: &trueValue,
|
|
TLSServerName: "2",
|
|
ConnectionRetryIntv: time.Duration(30000000000),
|
|
JWTAuthBackendPath: "jwt",
|
|
}},
|
|
Consuls: []*config.ConsulConfig{{
|
|
Name: "default",
|
|
ServerServiceName: "2",
|
|
ClientServiceName: "2",
|
|
AutoAdvertise: &trueValue,
|
|
Addr: "2",
|
|
Timeout: 2 * time.Second,
|
|
Token: "2",
|
|
Auth: "2",
|
|
EnableSSL: &trueValue,
|
|
VerifySSL: &trueValue,
|
|
CAFile: "2",
|
|
CertFile: "2",
|
|
KeyFile: "2",
|
|
ServerAutoJoin: &trueValue,
|
|
ClientAutoJoin: &trueValue,
|
|
ChecksUseAdvertise: &trueValue,
|
|
ServerHTTPCheckName: "Nomad Server HTTP Check",
|
|
ServerSerfCheckName: "Nomad Server Serf Check",
|
|
ServerRPCCheckName: "Nomad Server RPC Check",
|
|
ClientHTTPCheckName: "Nomad Client HTTP Check",
|
|
ServiceIdentityAuthMethod: structs.ConsulWorkloadsDefaultAuthMethodName,
|
|
TaskIdentityAuthMethod: structs.ConsulWorkloadsDefaultAuthMethodName,
|
|
}},
|
|
Sentinel: &config.SentinelConfig{
|
|
Imports: []*config.SentinelImport{
|
|
{
|
|
Name: "foo",
|
|
Path: "foo",
|
|
Args: []string{"a", "b", "c"},
|
|
},
|
|
},
|
|
},
|
|
Autopilot: &config.AutopilotConfig{
|
|
CleanupDeadServers: &trueValue,
|
|
ServerStabilizationTime: 2 * time.Second,
|
|
LastContactThreshold: 2 * time.Second,
|
|
MaxTrailingLogs: 2,
|
|
MinQuorum: 2,
|
|
EnableRedundancyZones: &trueValue,
|
|
DisableUpgradeMigration: &trueValue,
|
|
EnableCustomUpgrades: &trueValue,
|
|
},
|
|
Plugins: []*config.PluginConfig{
|
|
{
|
|
Name: "docker",
|
|
Args: []string{"bam"},
|
|
Config: map[string]interface{}{
|
|
"baz": 2,
|
|
},
|
|
},
|
|
{
|
|
Name: "exec",
|
|
Args: []string{"arg"},
|
|
Config: map[string]interface{}{
|
|
"config": true,
|
|
},
|
|
},
|
|
},
|
|
Reporting: &config.ReportingConfig{
|
|
License: &config.LicenseReportingConfig{
|
|
Enabled: pointer.Of(true),
|
|
},
|
|
},
|
|
}
|
|
|
|
result := c0.Merge(c1)
|
|
result = result.Merge(c2)
|
|
result = result.Merge(c3)
|
|
expected := c3.Copy()
|
|
|
|
must.Eq(t, expected, result)
|
|
}
|
|
|
|
func TestConfig_ParseConfigFile(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
// Fails if the file doesn't exist
|
|
if _, err := ParseConfigFile("/unicorns/leprechauns"); err == nil {
|
|
t.Fatalf("expected error, got nothing")
|
|
}
|
|
|
|
fh, err := os.CreateTemp("", "nomad")
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
defer os.RemoveAll(fh.Name())
|
|
|
|
// Invalid content returns error
|
|
if _, err := fh.WriteString("nope;!!!"); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
if _, err := ParseConfigFile(fh.Name()); err == nil {
|
|
t.Fatalf("expected load error, got nothing")
|
|
}
|
|
|
|
// Valid content parses successfully
|
|
if err := fh.Truncate(0); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
if _, err := fh.Seek(0, 0); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
if _, err := fh.WriteString(`{"region":"west"}`); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
config, err := ParseConfigFile(fh.Name())
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
if config.Region != "west" {
|
|
t.Fatalf("bad region: %q", config.Region)
|
|
}
|
|
}
|
|
|
|
func TestConfig_LoadConfigDir(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
// Fails if the dir doesn't exist.
|
|
if _, err := LoadConfigDir("/unicorns/leprechauns"); err == nil {
|
|
t.Fatalf("expected error, got nothing")
|
|
}
|
|
|
|
dir := t.TempDir()
|
|
|
|
// Returns empty config on empty dir
|
|
config, err := LoadConfig(dir)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
if config == nil {
|
|
t.Fatalf("should not be nil")
|
|
}
|
|
|
|
file1 := filepath.Join(dir, "conf1.hcl")
|
|
err = os.WriteFile(file1, []byte(`{"region":"west"}`), 0600)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
file2 := filepath.Join(dir, "conf2.hcl")
|
|
err = os.WriteFile(file2, []byte(`{"datacenter":"sfo"}`), 0600)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
file3 := filepath.Join(dir, "conf3.hcl")
|
|
err = os.WriteFile(file3, []byte(`nope;!!!`), 0600)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Fails if we have a bad config file
|
|
if _, err := LoadConfigDir(dir); err == nil {
|
|
t.Fatalf("expected load error, got nothing")
|
|
}
|
|
|
|
if err := os.Remove(file3); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Works if configs are valid
|
|
config, err = LoadConfigDir(dir)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
if config.Region != "west" || config.Datacenter != "sfo" {
|
|
t.Fatalf("bad: %#v", config)
|
|
}
|
|
}
|
|
|
|
func TestConfig_LoadConfig(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
// Fails if the target doesn't exist
|
|
if _, err := LoadConfig("/unicorns/leprechauns"); err == nil {
|
|
t.Fatalf("expected error, got nothing")
|
|
}
|
|
|
|
fh, err := os.CreateTemp("", "nomad")
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
defer os.Remove(fh.Name())
|
|
|
|
if _, err := fh.WriteString(`{"region":"west"}`); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Works on a config file
|
|
config, err := LoadConfig(fh.Name())
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
if config.Region != "west" {
|
|
t.Fatalf("bad: %#v", config)
|
|
}
|
|
|
|
expectedConfigFiles := []string{fh.Name()}
|
|
if !reflect.DeepEqual(config.Files, expectedConfigFiles) {
|
|
t.Errorf("Loaded configs don't match\nExpected\n%+vGot\n%+v\n",
|
|
expectedConfigFiles, config.Files)
|
|
}
|
|
|
|
dir := t.TempDir()
|
|
|
|
file1 := filepath.Join(dir, "config1.hcl")
|
|
err = os.WriteFile(file1, []byte(`{"datacenter":"sfo"}`), 0600)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Works on config dir
|
|
config, err = LoadConfig(dir)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
if config.Datacenter != "sfo" {
|
|
t.Fatalf("bad: %#v", config)
|
|
}
|
|
|
|
expectedConfigFiles = []string{file1}
|
|
if !reflect.DeepEqual(config.Files, expectedConfigFiles) {
|
|
t.Errorf("Loaded configs don't match\nExpected\n%+vGot\n%+v\n",
|
|
expectedConfigFiles, config.Files)
|
|
}
|
|
}
|
|
|
|
func TestConfig_LoadConfigsFileOrder(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
config1, err := LoadConfigDir("test-resources/etcnomad")
|
|
if err != nil {
|
|
t.Fatalf("Failed to load config: %s", err)
|
|
}
|
|
|
|
config2, err := LoadConfig("test-resources/myconf")
|
|
if err != nil {
|
|
t.Fatalf("Failed to load config: %s", err)
|
|
}
|
|
|
|
expected := []string{
|
|
// filepath.FromSlash changes these to backslash \ on Windows
|
|
filepath.FromSlash("test-resources/etcnomad/common.hcl"),
|
|
filepath.FromSlash("test-resources/etcnomad/server.json"),
|
|
filepath.FromSlash("test-resources/myconf"),
|
|
}
|
|
|
|
config := config1.Merge(config2)
|
|
|
|
if !reflect.DeepEqual(config.Files, expected) {
|
|
t.Errorf("Loaded configs don't match\nwant: %+v\n got: %+v\n",
|
|
expected, config.Files)
|
|
}
|
|
}
|
|
|
|
func TestConfig_Listener(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
config := DefaultConfig()
|
|
|
|
// Fails on invalid input
|
|
if ln, err := config.Listener("tcp", "nope", 8080); err == nil {
|
|
ln.Close()
|
|
t.Fatalf("expected addr error")
|
|
}
|
|
if ln, err := config.Listener("nope", "127.0.0.1", 8080); err == nil {
|
|
ln.Close()
|
|
t.Fatalf("expected protocol err")
|
|
}
|
|
if ln, err := config.Listener("tcp", "127.0.0.1", -1); err == nil {
|
|
ln.Close()
|
|
t.Fatalf("expected port error")
|
|
}
|
|
|
|
// Works with valid inputs
|
|
ports := ci.PortAllocator.Grab(2)
|
|
|
|
ln, err := config.Listener("tcp", "127.0.0.1", ports[0])
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
ln.Close()
|
|
|
|
if net := ln.Addr().Network(); net != "tcp" {
|
|
t.Fatalf("expected tcp, got: %q", net)
|
|
}
|
|
want := fmt.Sprintf("127.0.0.1:%d", ports[0])
|
|
if addr := ln.Addr().String(); addr != want {
|
|
t.Fatalf("expected %q, got: %q", want, addr)
|
|
}
|
|
|
|
// Falls back to default bind address if non provided
|
|
config.BindAddr = "0.0.0.0"
|
|
ln, err = config.Listener("tcp4", "", ports[1])
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
ln.Close()
|
|
|
|
want = fmt.Sprintf("0.0.0.0:%d", ports[1])
|
|
if addr := ln.Addr().String(); addr != want {
|
|
t.Fatalf("expected %q, got: %q", want, addr)
|
|
}
|
|
}
|
|
|
|
func TestConfig_DevMode_validate(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
cases := []struct {
|
|
devConfig *devModeConfig
|
|
expectedErr string
|
|
}{}
|
|
if runtime.GOOS != "linux" {
|
|
cases = []struct {
|
|
devConfig *devModeConfig
|
|
expectedErr string
|
|
}{
|
|
{
|
|
devConfig: &devModeConfig{
|
|
connectMode: true,
|
|
},
|
|
expectedErr: "-dev-connect is only supported on linux",
|
|
},
|
|
{
|
|
devConfig: &devModeConfig{
|
|
defaultMode: true,
|
|
connectMode: true,
|
|
},
|
|
expectedErr: "-dev-connect is only supported on linux",
|
|
},
|
|
}
|
|
}
|
|
if runtime.GOOS == "linux" {
|
|
testutil.RequireRoot(t)
|
|
cases = []struct {
|
|
devConfig *devModeConfig
|
|
expectedErr string
|
|
}{
|
|
{
|
|
devConfig: &devModeConfig{
|
|
connectMode: true,
|
|
},
|
|
expectedErr: "",
|
|
},
|
|
{
|
|
devConfig: &devModeConfig{
|
|
defaultMode: true,
|
|
connectMode: true,
|
|
},
|
|
expectedErr: "",
|
|
},
|
|
}
|
|
}
|
|
for _, c := range cases {
|
|
t.Run("", func(t *testing.T) {
|
|
err := c.devConfig.validate()
|
|
if c.expectedErr != "" {
|
|
must.Error(t, err)
|
|
} else {
|
|
must.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestConfig_normalizeAddrs_DevMode asserts that normalizeAddrs allows
|
|
// advertising localhost in dev mode.
|
|
func TestConfig_normalizeAddrs_DevMode(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
// allow to advertise 127.0.0.1 if dev-mode is enabled
|
|
c := &Config{
|
|
BindAddr: "127.0.0.1",
|
|
Ports: &Ports{
|
|
HTTP: 4646,
|
|
RPC: 4647,
|
|
Serf: 4648,
|
|
},
|
|
Addresses: &Addresses{},
|
|
AdvertiseAddrs: &AdvertiseAddrs{},
|
|
DevMode: true,
|
|
}
|
|
|
|
if err := c.normalizeAddrs(); err != nil {
|
|
t.Fatalf("unable to normalize addresses: %s", err)
|
|
}
|
|
|
|
if c.BindAddr != "127.0.0.1" {
|
|
t.Fatalf("expected BindAddr 127.0.0.1, got %s", c.BindAddr)
|
|
}
|
|
|
|
if c.normalizedAddrs.HTTP[0] != "127.0.0.1:4646" {
|
|
t.Fatalf("expected HTTP address 127.0.0.1:4646, got %s", c.normalizedAddrs.HTTP)
|
|
}
|
|
|
|
if c.normalizedAddrs.RPC != "127.0.0.1:4647" {
|
|
t.Fatalf("expected RPC address 127.0.0.1:4647, got %s", c.normalizedAddrs.RPC)
|
|
}
|
|
|
|
if c.normalizedAddrs.Serf != "127.0.0.1:4648" {
|
|
t.Fatalf("expected Serf address 127.0.0.1:4648, got %s", c.normalizedAddrs.Serf)
|
|
}
|
|
|
|
if c.AdvertiseAddrs.HTTP != "127.0.0.1:4646" {
|
|
t.Fatalf("expected HTTP advertise address 127.0.0.1:4646, got %s", c.AdvertiseAddrs.HTTP)
|
|
}
|
|
|
|
if c.AdvertiseAddrs.RPC != "127.0.0.1:4647" {
|
|
t.Fatalf("expected RPC advertise address 127.0.0.1:4647, got %s", c.AdvertiseAddrs.RPC)
|
|
}
|
|
|
|
// Client mode, no Serf address defined
|
|
if c.AdvertiseAddrs.Serf != "" {
|
|
t.Fatalf("expected unset Serf advertise address, got %s", c.AdvertiseAddrs.Serf)
|
|
}
|
|
}
|
|
|
|
// TestConfig_normalizeAddrs_NoAdvertise asserts that normalizeAddrs will
|
|
// fail if no valid advertise address available in non-dev mode.
|
|
func TestConfig_normalizeAddrs_NoAdvertise(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
c := &Config{
|
|
BindAddr: "127.0.0.1",
|
|
Ports: &Ports{
|
|
HTTP: 4646,
|
|
RPC: 4647,
|
|
Serf: 4648,
|
|
},
|
|
Addresses: &Addresses{},
|
|
AdvertiseAddrs: &AdvertiseAddrs{},
|
|
DevMode: false,
|
|
}
|
|
|
|
if err := c.normalizeAddrs(); err == nil {
|
|
t.Fatalf("expected an error when no valid advertise address is available")
|
|
}
|
|
|
|
if c.AdvertiseAddrs.HTTP == "127.0.0.1:4646" {
|
|
t.Fatalf("expected non-localhost HTTP advertise address, got %s", c.AdvertiseAddrs.HTTP)
|
|
}
|
|
|
|
if c.AdvertiseAddrs.RPC == "127.0.0.1:4647" {
|
|
t.Fatalf("expected non-localhost RPC advertise address, got %s", c.AdvertiseAddrs.RPC)
|
|
}
|
|
|
|
if c.AdvertiseAddrs.Serf == "127.0.0.1:4648" {
|
|
t.Fatalf("expected non-localhost Serf advertise address, got %s", c.AdvertiseAddrs.Serf)
|
|
}
|
|
}
|
|
|
|
// TestConfig_normalizeAddrs_AdvertiseLocalhost asserts localhost can be
|
|
// advertised if it's explicitly set in the config.
|
|
func TestConfig_normalizeAddrs_AdvertiseLocalhost(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
c := &Config{
|
|
BindAddr: "127.0.0.1",
|
|
Ports: &Ports{
|
|
HTTP: 4646,
|
|
RPC: 4647,
|
|
Serf: 4648,
|
|
},
|
|
Addresses: &Addresses{},
|
|
AdvertiseAddrs: &AdvertiseAddrs{
|
|
HTTP: "127.0.0.1",
|
|
RPC: "127.0.0.1",
|
|
Serf: "127.0.0.1",
|
|
},
|
|
DevMode: false,
|
|
Server: &ServerConfig{Enabled: true},
|
|
}
|
|
|
|
if err := c.normalizeAddrs(); err != nil {
|
|
t.Fatalf("unexpected error when manually setting bind mode: %v", err)
|
|
}
|
|
|
|
if c.AdvertiseAddrs.HTTP != "127.0.0.1:4646" {
|
|
t.Errorf("expected localhost HTTP advertise address, got %s", c.AdvertiseAddrs.HTTP)
|
|
}
|
|
|
|
if c.AdvertiseAddrs.RPC != "127.0.0.1:4647" {
|
|
t.Errorf("expected localhost RPC advertise address, got %s", c.AdvertiseAddrs.RPC)
|
|
}
|
|
|
|
if c.AdvertiseAddrs.Serf != "127.0.0.1:4648" {
|
|
t.Errorf("expected localhost Serf advertise address, got %s", c.AdvertiseAddrs.Serf)
|
|
}
|
|
}
|
|
|
|
// TestConfig_normalizeAddrs_IPv6Loopback asserts that an IPv6 loopback address
|
|
// is normalized properly. See #2739
|
|
func TestConfig_normalizeAddrs_IPv6Loopback(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
c := &Config{
|
|
BindAddr: "::1",
|
|
Ports: &Ports{
|
|
HTTP: 4646,
|
|
RPC: 4647,
|
|
},
|
|
Addresses: &Addresses{},
|
|
AdvertiseAddrs: &AdvertiseAddrs{
|
|
HTTP: "::1",
|
|
RPC: "::1",
|
|
},
|
|
DevMode: false,
|
|
}
|
|
|
|
if err := c.normalizeAddrs(); err != nil {
|
|
t.Fatalf("unexpected error when manually setting bind mode: %v", err)
|
|
}
|
|
|
|
if c.Addresses.HTTP != "::1" {
|
|
t.Errorf("expected ::1 HTTP address, got %s", c.Addresses.HTTP)
|
|
}
|
|
|
|
if c.Addresses.RPC != "::1" {
|
|
t.Errorf("expected ::1 RPC address, got %s", c.Addresses.RPC)
|
|
}
|
|
|
|
if c.AdvertiseAddrs.HTTP != "[::1]:4646" {
|
|
t.Errorf("expected [::1] HTTP advertise address, got %s", c.AdvertiseAddrs.HTTP)
|
|
}
|
|
|
|
if c.AdvertiseAddrs.RPC != "[::1]:4647" {
|
|
t.Errorf("expected [::1] RPC advertise address, got %s", c.AdvertiseAddrs.RPC)
|
|
}
|
|
}
|
|
|
|
// TestConfig_normalizeAddrs_MultipleInterface asserts that normalizeAddrs will
|
|
// handle normalizing multiple interfaces in a single protocol.
|
|
func TestConfig_normalizeAddrs_MultipleInterfaces(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
testCases := []struct {
|
|
name string
|
|
addressConfig *Addresses
|
|
expectedNormalizedAddrs *NormalizedAddrs
|
|
expectErr bool
|
|
}{
|
|
{
|
|
name: "multiple http addresses",
|
|
addressConfig: &Addresses{
|
|
HTTP: "127.0.0.1 127.0.0.2",
|
|
},
|
|
expectedNormalizedAddrs: &NormalizedAddrs{
|
|
HTTP: []string{"127.0.0.1:4646", "127.0.0.2:4646"},
|
|
RPC: "127.0.0.1:4647",
|
|
Serf: "127.0.0.1:4648",
|
|
},
|
|
expectErr: false,
|
|
},
|
|
}
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
c := &Config{
|
|
BindAddr: "127.0.0.1",
|
|
Ports: &Ports{
|
|
HTTP: 4646,
|
|
RPC: 4647,
|
|
Serf: 4648,
|
|
},
|
|
Addresses: tc.addressConfig,
|
|
AdvertiseAddrs: &AdvertiseAddrs{
|
|
HTTP: "127.0.0.1",
|
|
RPC: "127.0.0.1",
|
|
Serf: "127.0.0.1",
|
|
},
|
|
}
|
|
err := c.normalizeAddrs()
|
|
if tc.expectErr {
|
|
require.Error(t, err)
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
require.Equal(t, tc.expectedNormalizedAddrs, c.normalizedAddrs)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestConfig_normalizeAddrs(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
c := &Config{
|
|
BindAddr: "169.254.1.5",
|
|
Ports: &Ports{
|
|
HTTP: 4646,
|
|
RPC: 4647,
|
|
Serf: 4648,
|
|
},
|
|
Addresses: &Addresses{
|
|
HTTP: "169.254.1.10",
|
|
},
|
|
AdvertiseAddrs: &AdvertiseAddrs{
|
|
RPC: "169.254.1.40",
|
|
},
|
|
Server: &ServerConfig{
|
|
Enabled: true,
|
|
},
|
|
}
|
|
|
|
if err := c.normalizeAddrs(); err != nil {
|
|
t.Fatalf("unable to normalize addresses: %s", err)
|
|
}
|
|
|
|
if c.BindAddr != "169.254.1.5" {
|
|
t.Fatalf("expected BindAddr 169.254.1.5, got %s", c.BindAddr)
|
|
}
|
|
|
|
if c.AdvertiseAddrs.HTTP != "169.254.1.10:4646" {
|
|
t.Fatalf("expected HTTP advertise address 169.254.1.10:4646, got %s", c.AdvertiseAddrs.HTTP)
|
|
}
|
|
|
|
if c.AdvertiseAddrs.RPC != "169.254.1.40:4647" {
|
|
t.Fatalf("expected RPC advertise address 169.254.1.40:4647, got %s", c.AdvertiseAddrs.RPC)
|
|
}
|
|
|
|
if c.AdvertiseAddrs.Serf != "169.254.1.5:4648" {
|
|
t.Fatalf("expected Serf advertise address 169.254.1.5:4648, got %s", c.AdvertiseAddrs.Serf)
|
|
}
|
|
|
|
c = &Config{
|
|
BindAddr: "{{ GetPrivateIP }}",
|
|
Ports: &Ports{
|
|
HTTP: 4646,
|
|
RPC: 4647,
|
|
Serf: 4648,
|
|
},
|
|
Addresses: &Addresses{},
|
|
AdvertiseAddrs: &AdvertiseAddrs{
|
|
RPC: "{{ GetPrivateIP }}",
|
|
},
|
|
Server: &ServerConfig{
|
|
Enabled: true,
|
|
},
|
|
}
|
|
|
|
if err := c.normalizeAddrs(); err != nil {
|
|
t.Fatalf("unable to normalize addresses: %s", err)
|
|
}
|
|
|
|
exp := net.JoinHostPort(c.BindAddr, "4646")
|
|
if c.AdvertiseAddrs.HTTP != exp {
|
|
t.Fatalf("expected HTTP advertise address %s, got %s", exp, c.AdvertiseAddrs.HTTP)
|
|
}
|
|
|
|
exp = net.JoinHostPort(c.BindAddr, "4647")
|
|
if c.AdvertiseAddrs.RPC != exp {
|
|
t.Fatalf("expected RPC advertise address %s, got %s", exp, c.AdvertiseAddrs.RPC)
|
|
}
|
|
|
|
exp = net.JoinHostPort(c.BindAddr, "4648")
|
|
if c.AdvertiseAddrs.Serf != exp {
|
|
t.Fatalf("expected Serf advertise address %s, got %s", exp, c.AdvertiseAddrs.Serf)
|
|
}
|
|
|
|
// allow to advertise 127.0.0.1 in non-dev mode, if explicitly configured to do so
|
|
c = &Config{
|
|
BindAddr: "127.0.0.1",
|
|
Ports: &Ports{
|
|
HTTP: 4646,
|
|
RPC: 4647,
|
|
Serf: 4648,
|
|
},
|
|
Addresses: &Addresses{},
|
|
AdvertiseAddrs: &AdvertiseAddrs{
|
|
HTTP: "127.0.0.1:4646",
|
|
RPC: "127.0.0.1:4647",
|
|
Serf: "127.0.0.1:4648",
|
|
},
|
|
DevMode: false,
|
|
Server: &ServerConfig{
|
|
Enabled: true,
|
|
},
|
|
}
|
|
|
|
if err := c.normalizeAddrs(); err != nil {
|
|
t.Fatalf("unable to normalize addresses: %s", err)
|
|
}
|
|
|
|
if c.AdvertiseAddrs.HTTP != "127.0.0.1:4646" {
|
|
t.Fatalf("expected HTTP advertise address 127.0.0.1:4646, got %s", c.AdvertiseAddrs.HTTP)
|
|
}
|
|
|
|
if c.AdvertiseAddrs.RPC != "127.0.0.1:4647" {
|
|
t.Fatalf("expected RPC advertise address 127.0.0.1:4647, got %s", c.AdvertiseAddrs.RPC)
|
|
}
|
|
|
|
if c.AdvertiseAddrs.RPC != "127.0.0.1:4647" {
|
|
t.Fatalf("expected RPC advertise address 127.0.0.1:4647, got %s", c.AdvertiseAddrs.RPC)
|
|
}
|
|
}
|
|
|
|
func TestConfig_templateNetworkInterface(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
// find the first interface
|
|
ifaces, err := sockaddr.GetAllInterfaces()
|
|
if err != nil {
|
|
t.Fatalf("failed to get interfaces: %v", err)
|
|
}
|
|
iface := ifaces[0]
|
|
testCases := []struct {
|
|
name string
|
|
clientConfig *ClientConfig
|
|
expectedInterface string
|
|
expectErr bool
|
|
}{
|
|
{
|
|
name: "empty string",
|
|
clientConfig: &ClientConfig{
|
|
Enabled: true,
|
|
NetworkInterface: "",
|
|
},
|
|
expectedInterface: "",
|
|
expectErr: false,
|
|
},
|
|
{
|
|
name: "simple string",
|
|
clientConfig: &ClientConfig{
|
|
Enabled: true,
|
|
NetworkInterface: iface.Name,
|
|
},
|
|
expectedInterface: iface.Name,
|
|
expectErr: false,
|
|
},
|
|
{
|
|
name: "valid interface",
|
|
clientConfig: &ClientConfig{
|
|
Enabled: true,
|
|
NetworkInterface: `{{ GetAllInterfaces | attr "name" }}`,
|
|
},
|
|
expectedInterface: iface.Name,
|
|
expectErr: false,
|
|
},
|
|
{
|
|
name: "invalid interface",
|
|
clientConfig: &ClientConfig{
|
|
Enabled: true,
|
|
NetworkInterface: `no such interface`,
|
|
},
|
|
expectedInterface: iface.Name,
|
|
expectErr: true,
|
|
},
|
|
{
|
|
name: "insignificant whitespace",
|
|
clientConfig: &ClientConfig{
|
|
Enabled: true,
|
|
NetworkInterface: ` {{GetAllInterfaces | attr "name" }}`,
|
|
},
|
|
expectedInterface: iface.Name,
|
|
expectErr: false,
|
|
},
|
|
{
|
|
name: "empty template return",
|
|
clientConfig: &ClientConfig{
|
|
Enabled: true,
|
|
NetworkInterface: `{{ printf "" }}`,
|
|
},
|
|
expectedInterface: iface.Name,
|
|
expectErr: true,
|
|
},
|
|
}
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
c := &Config{
|
|
BindAddr: "127.0.0.1",
|
|
Ports: &Ports{
|
|
HTTP: 4646,
|
|
RPC: 4647,
|
|
Serf: 4648,
|
|
},
|
|
Addresses: &Addresses{},
|
|
AdvertiseAddrs: &AdvertiseAddrs{
|
|
HTTP: "127.0.0.1:4646",
|
|
RPC: "127.0.0.1:4647",
|
|
Serf: "127.0.0.1:4648",
|
|
},
|
|
DevMode: false,
|
|
Client: tc.clientConfig,
|
|
}
|
|
err := c.normalizeAddrs()
|
|
if tc.expectErr {
|
|
require.Error(t, err)
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
require.Equal(t, c.Client.NetworkInterface, tc.expectedInterface)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIsMissingPort(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
_, _, err := net.SplitHostPort("localhost")
|
|
if missing := isMissingPort(err); !missing {
|
|
t.Errorf("expected missing port error, but got %v", err)
|
|
}
|
|
_, _, err = net.SplitHostPort("localhost:9000")
|
|
if missing := isMissingPort(err); missing {
|
|
t.Errorf("expected no error, but got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestMergeServerJoin(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
require := require.New(t)
|
|
|
|
{
|
|
retryJoin := []string{"127.0.0.1", "127.0.0.2"}
|
|
startJoin := []string{"127.0.0.1", "127.0.0.2"}
|
|
retryMaxAttempts := 1
|
|
retryInterval := time.Duration(0)
|
|
|
|
a := &ServerJoin{
|
|
RetryJoin: retryJoin,
|
|
StartJoin: startJoin,
|
|
RetryMaxAttempts: retryMaxAttempts,
|
|
RetryInterval: time.Duration(retryInterval),
|
|
}
|
|
b := &ServerJoin{}
|
|
|
|
result := a.Merge(b)
|
|
require.Equal(result.RetryJoin, retryJoin)
|
|
require.Equal(result.StartJoin, startJoin)
|
|
require.Equal(result.RetryMaxAttempts, retryMaxAttempts)
|
|
require.Equal(result.RetryInterval, retryInterval)
|
|
}
|
|
{
|
|
retryJoin := []string{"127.0.0.1", "127.0.0.2"}
|
|
startJoin := []string{"127.0.0.1", "127.0.0.2"}
|
|
retryMaxAttempts := 1
|
|
retryInterval := time.Duration(0)
|
|
|
|
a := &ServerJoin{}
|
|
b := &ServerJoin{
|
|
RetryJoin: retryJoin,
|
|
StartJoin: startJoin,
|
|
RetryMaxAttempts: retryMaxAttempts,
|
|
RetryInterval: time.Duration(retryInterval),
|
|
}
|
|
|
|
result := a.Merge(b)
|
|
require.Equal(result.RetryJoin, retryJoin)
|
|
require.Equal(result.StartJoin, startJoin)
|
|
require.Equal(result.RetryMaxAttempts, retryMaxAttempts)
|
|
require.Equal(result.RetryInterval, retryInterval)
|
|
}
|
|
{
|
|
retryJoin := []string{"127.0.0.1", "127.0.0.2"}
|
|
startJoin := []string{"127.0.0.1", "127.0.0.2"}
|
|
retryMaxAttempts := 1
|
|
retryInterval := time.Duration(0)
|
|
|
|
var a *ServerJoin
|
|
b := &ServerJoin{
|
|
RetryJoin: retryJoin,
|
|
StartJoin: startJoin,
|
|
RetryMaxAttempts: retryMaxAttempts,
|
|
RetryInterval: time.Duration(retryInterval),
|
|
}
|
|
|
|
result := a.Merge(b)
|
|
require.Equal(result.RetryJoin, retryJoin)
|
|
require.Equal(result.StartJoin, startJoin)
|
|
require.Equal(result.RetryMaxAttempts, retryMaxAttempts)
|
|
require.Equal(result.RetryInterval, retryInterval)
|
|
}
|
|
{
|
|
retryJoin := []string{"127.0.0.1", "127.0.0.2"}
|
|
startJoin := []string{"127.0.0.1", "127.0.0.2"}
|
|
retryMaxAttempts := 1
|
|
retryInterval := time.Duration(0)
|
|
|
|
a := &ServerJoin{
|
|
RetryJoin: retryJoin,
|
|
StartJoin: startJoin,
|
|
RetryMaxAttempts: retryMaxAttempts,
|
|
RetryInterval: time.Duration(retryInterval),
|
|
}
|
|
var b *ServerJoin
|
|
|
|
result := a.Merge(b)
|
|
require.Equal(result.RetryJoin, retryJoin)
|
|
require.Equal(result.StartJoin, startJoin)
|
|
require.Equal(result.RetryMaxAttempts, retryMaxAttempts)
|
|
require.Equal(result.RetryInterval, retryInterval)
|
|
}
|
|
{
|
|
retryJoin := []string{"127.0.0.1", "127.0.0.2"}
|
|
startJoin := []string{"127.0.0.1", "127.0.0.2"}
|
|
retryMaxAttempts := 1
|
|
retryInterval := time.Duration(0)
|
|
|
|
a := &ServerJoin{
|
|
RetryJoin: retryJoin,
|
|
StartJoin: startJoin,
|
|
}
|
|
b := &ServerJoin{
|
|
RetryMaxAttempts: retryMaxAttempts,
|
|
RetryInterval: time.Duration(retryInterval),
|
|
}
|
|
|
|
result := a.Merge(b)
|
|
require.Equal(result.RetryJoin, retryJoin)
|
|
require.Equal(result.StartJoin, startJoin)
|
|
require.Equal(result.RetryMaxAttempts, retryMaxAttempts)
|
|
require.Equal(result.RetryInterval, retryInterval)
|
|
}
|
|
}
|
|
|
|
func TestTelemetry_PrefixFilters(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
cases := []struct {
|
|
in []string
|
|
expAllow []string
|
|
expBlock []string
|
|
expErr bool
|
|
}{
|
|
{
|
|
in: []string{"+foo"},
|
|
expAllow: []string{"foo"},
|
|
},
|
|
{
|
|
in: []string{"-foo"},
|
|
expBlock: []string{"foo"},
|
|
},
|
|
{
|
|
in: []string{"+a.b.c", "-x.y.z"},
|
|
expAllow: []string{"a.b.c"},
|
|
expBlock: []string{"x.y.z"},
|
|
},
|
|
{
|
|
in: []string{"+foo", "bad", "-bar"},
|
|
expErr: true,
|
|
},
|
|
}
|
|
|
|
for i, c := range cases {
|
|
t.Run(fmt.Sprintf("PrefixCase%d", i), func(t *testing.T) {
|
|
require := require.New(t)
|
|
tel := &Telemetry{
|
|
PrefixFilter: c.in,
|
|
}
|
|
|
|
allow, block, err := tel.PrefixFilters()
|
|
require.Exactly(c.expAllow, allow)
|
|
require.Exactly(c.expBlock, block)
|
|
require.Equal(c.expErr, err != nil)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTelemetry_Validate(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
testCases := []struct {
|
|
name string
|
|
inputTelemetry *Telemetry
|
|
expectedError error
|
|
}{
|
|
{
|
|
name: "nil",
|
|
inputTelemetry: nil,
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "invalid",
|
|
inputTelemetry: &Telemetry{
|
|
inMemoryCollectionInterval: 10 * time.Second,
|
|
inMemoryRetentionPeriod: 1 * time.Second,
|
|
},
|
|
expectedError: errors.New("telemetry in-memory collection interval cannot be greater than retention period"),
|
|
},
|
|
{
|
|
name: "valid",
|
|
inputTelemetry: &Telemetry{
|
|
inMemoryCollectionInterval: 1 * time.Second,
|
|
inMemoryRetentionPeriod: 10 * time.Second,
|
|
},
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "missing in-memory interval",
|
|
inputTelemetry: &Telemetry{
|
|
inMemoryRetentionPeriod: 10 * time.Second,
|
|
},
|
|
expectedError: errors.New("telemetry in-memory collection interval must be greater than zero"),
|
|
},
|
|
{
|
|
name: "missing in-memory collection",
|
|
inputTelemetry: &Telemetry{
|
|
inMemoryCollectionInterval: 10 * time.Second,
|
|
},
|
|
expectedError: errors.New("telemetry in-memory retention period must be greater than zero"),
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
actualError := tc.inputTelemetry.Validate()
|
|
if tc.expectedError != nil {
|
|
must.EqError(t, actualError, tc.expectedError.Error())
|
|
} else {
|
|
must.NoError(t, actualError)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTelemetry_Parse(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
dir := t.TempDir()
|
|
|
|
file1 := filepath.Join(dir, "config1.hcl")
|
|
err := os.WriteFile(file1, []byte(`telemetry{
|
|
prefix_filter = ["+nomad.raft"]
|
|
filter_default = false
|
|
disable_dispatched_job_summary_metrics = true
|
|
disable_quota_utilization_metrics = true
|
|
disable_rpc_rate_metrics_labels = true
|
|
}`), 0600)
|
|
must.NoError(t, err)
|
|
|
|
// Works on config dir
|
|
config, err := LoadConfig(dir)
|
|
must.NoError(t, err)
|
|
|
|
must.False(t, *config.Telemetry.FilterDefault)
|
|
must.Eq(t, []string{"+nomad.raft"}, config.Telemetry.PrefixFilter)
|
|
must.True(t, config.Telemetry.DisableDispatchedJobSummaryMetrics)
|
|
must.True(t, config.Telemetry.DisableQuotaUtilizationMetrics)
|
|
must.True(t, config.Telemetry.DisableRPCRateMetricsLabels)
|
|
}
|
|
|
|
func TestEventBroker_Parse(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
require := require.New(t)
|
|
{
|
|
a := &ServerConfig{
|
|
EnableEventBroker: pointer.Of(false),
|
|
EventBufferSize: pointer.Of(0),
|
|
}
|
|
b := DefaultConfig().Server
|
|
b.EnableEventBroker = nil
|
|
b.EventBufferSize = nil
|
|
|
|
result := a.Merge(b)
|
|
require.Equal(false, *result.EnableEventBroker)
|
|
require.Equal(0, *result.EventBufferSize)
|
|
}
|
|
|
|
{
|
|
a := &ServerConfig{
|
|
EnableEventBroker: pointer.Of(true),
|
|
EventBufferSize: pointer.Of(5000),
|
|
}
|
|
b := DefaultConfig().Server
|
|
b.EnableEventBroker = nil
|
|
b.EventBufferSize = nil
|
|
|
|
result := a.Merge(b)
|
|
require.Equal(true, *result.EnableEventBroker)
|
|
require.Equal(5000, *result.EventBufferSize)
|
|
}
|
|
|
|
{
|
|
a := &ServerConfig{
|
|
EnableEventBroker: pointer.Of(false),
|
|
EventBufferSize: pointer.Of(0),
|
|
}
|
|
b := DefaultConfig().Server
|
|
b.EnableEventBroker = pointer.Of(true)
|
|
b.EventBufferSize = pointer.Of(20000)
|
|
|
|
result := a.Merge(b)
|
|
require.Equal(true, *result.EnableEventBroker)
|
|
require.Equal(20000, *result.EventBufferSize)
|
|
}
|
|
}
|
|
|
|
func TestConfig_LoadConsulTemplateConfig(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
t.Run("minimal client expect defaults", func(t *testing.T) {
|
|
defaultConfig := DefaultConfig()
|
|
agentConfig, err := LoadConfig("test-resources/minimal_client.hcl")
|
|
must.NoError(t, err)
|
|
agentConfig = defaultConfig.Merge(agentConfig)
|
|
must.Eq(t, defaultConfig.Client.TemplateConfig, agentConfig.Client.TemplateConfig)
|
|
})
|
|
|
|
t.Run("client config with nil function denylist", func(t *testing.T) {
|
|
defaultConfig := DefaultConfig()
|
|
agentConfig, err := LoadConfig("test-resources/client_with_function_denylist_nil.hcl")
|
|
must.NoError(t, err)
|
|
agentConfig = defaultConfig.Merge(agentConfig)
|
|
|
|
templateConfig := agentConfig.Client.TemplateConfig
|
|
must.Len(t, 3, templateConfig.FunctionDenylist)
|
|
})
|
|
|
|
t.Run("client config with basic template", func(t *testing.T) {
|
|
defaultConfig := DefaultConfig()
|
|
agentConfig, err := LoadConfig("test-resources/client_with_basic_template.hcl")
|
|
must.NoError(t, err)
|
|
agentConfig = defaultConfig.Merge(agentConfig)
|
|
|
|
templateConfig := agentConfig.Client.TemplateConfig
|
|
|
|
// check explicit overrides
|
|
must.Eq(t, true, templateConfig.DisableSandbox)
|
|
must.Len(t, 0, templateConfig.FunctionDenylist)
|
|
|
|
// check all the complex defaults
|
|
must.Eq(t, 87600*time.Hour, *templateConfig.MaxStale)
|
|
must.Eq(t, 5*time.Minute, *templateConfig.BlockQueryWaitTime)
|
|
|
|
// Wait
|
|
must.NotNil(t, templateConfig.Wait)
|
|
must.Eq(t, 5*time.Second, *templateConfig.Wait.Min)
|
|
must.Eq(t, 4*time.Minute, *templateConfig.Wait.Max)
|
|
|
|
// WaitBounds
|
|
must.Nil(t, templateConfig.WaitBounds)
|
|
|
|
// Consul Retry
|
|
must.NotNil(t, templateConfig.ConsulRetry)
|
|
must.Eq(t, 12, *templateConfig.ConsulRetry.Attempts)
|
|
must.Eq(t, time.Millisecond*250, *templateConfig.ConsulRetry.Backoff)
|
|
must.Eq(t, time.Minute, *templateConfig.ConsulRetry.MaxBackoff)
|
|
|
|
// Vault Retry
|
|
must.NotNil(t, templateConfig.VaultRetry)
|
|
must.Eq(t, 12, *templateConfig.VaultRetry.Attempts)
|
|
must.Eq(t, time.Millisecond*250, *templateConfig.VaultRetry.Backoff)
|
|
must.Eq(t, time.Minute, *templateConfig.VaultRetry.MaxBackoff)
|
|
|
|
// Nomad Retry
|
|
must.NotNil(t, templateConfig.NomadRetry)
|
|
must.Eq(t, 12, *templateConfig.NomadRetry.Attempts)
|
|
must.Eq(t, time.Millisecond*250, *templateConfig.NomadRetry.Backoff)
|
|
must.Eq(t, time.Minute, *templateConfig.NomadRetry.MaxBackoff)
|
|
})
|
|
|
|
t.Run("client config with full template block", func(t *testing.T) {
|
|
defaultConfig := DefaultConfig()
|
|
|
|
agentConfig, err := LoadConfig("test-resources/client_with_template.hcl")
|
|
must.NoError(t, err)
|
|
|
|
agentConfig = defaultConfig.Merge(agentConfig)
|
|
|
|
clientAgent := Agent{config: agentConfig}
|
|
clientConfig, err := clientAgent.clientConfig()
|
|
must.NoError(t, err)
|
|
|
|
templateConfig := clientConfig.TemplateConfig
|
|
|
|
// Make sure all fields to test are set
|
|
must.NotNil(t, templateConfig.BlockQueryWaitTime)
|
|
must.NotNil(t, templateConfig.MaxStale)
|
|
must.NotNil(t, templateConfig.Wait)
|
|
must.NotNil(t, templateConfig.WaitBounds)
|
|
must.NotNil(t, templateConfig.ConsulRetry)
|
|
must.NotNil(t, templateConfig.VaultRetry)
|
|
must.NotNil(t, templateConfig.NomadRetry)
|
|
|
|
// Direct properties
|
|
must.Eq(t, 300*time.Second, *templateConfig.MaxStale)
|
|
must.Eq(t, 90*time.Second, *templateConfig.BlockQueryWaitTime)
|
|
|
|
// Wait
|
|
must.Eq(t, 2*time.Second, *templateConfig.Wait.Min)
|
|
must.Eq(t, 60*time.Second, *templateConfig.Wait.Max)
|
|
|
|
// WaitBounds
|
|
must.Eq(t, 2*time.Second, *templateConfig.WaitBounds.Min)
|
|
must.Eq(t, 60*time.Second, *templateConfig.WaitBounds.Max)
|
|
|
|
// Consul Retry
|
|
must.NotNil(t, templateConfig.ConsulRetry)
|
|
must.Eq(t, 5, *templateConfig.ConsulRetry.Attempts)
|
|
must.Eq(t, 5*time.Second, *templateConfig.ConsulRetry.Backoff)
|
|
must.Eq(t, 10*time.Second, *templateConfig.ConsulRetry.MaxBackoff)
|
|
|
|
// Vault Retry
|
|
must.NotNil(t, templateConfig.VaultRetry)
|
|
must.Eq(t, 0, *templateConfig.VaultRetry.Attempts)
|
|
must.Eq(t, 15*time.Second, *templateConfig.VaultRetry.Backoff)
|
|
must.Eq(t, 20*time.Second, *templateConfig.VaultRetry.MaxBackoff)
|
|
|
|
// Nomad Retry
|
|
must.NotNil(t, templateConfig.NomadRetry)
|
|
must.Eq(t, 12, *templateConfig.NomadRetry.Attempts)
|
|
must.Eq(t, 20*time.Second, *templateConfig.NomadRetry.Backoff)
|
|
must.Eq(t, 25*time.Second, *templateConfig.NomadRetry.MaxBackoff)
|
|
})
|
|
|
|
}
|
|
|
|
func TestConfig_LoadConsulTemplate_FunctionDenylist(t *testing.T) {
|
|
cases := []struct {
|
|
File string
|
|
Expected *client.ClientTemplateConfig
|
|
}{
|
|
{
|
|
"test-resources/minimal_client.hcl",
|
|
nil,
|
|
},
|
|
{
|
|
"test-resources/client_with_basic_template.json",
|
|
&client.ClientTemplateConfig{
|
|
DisableSandbox: true,
|
|
FunctionDenylist: []string{},
|
|
},
|
|
},
|
|
{
|
|
"test-resources/client_with_basic_template.hcl",
|
|
&client.ClientTemplateConfig{
|
|
DisableSandbox: true,
|
|
FunctionDenylist: []string{},
|
|
},
|
|
},
|
|
{
|
|
"test-resources/client_with_function_denylist.hcl",
|
|
&client.ClientTemplateConfig{
|
|
DisableSandbox: false,
|
|
FunctionDenylist: []string{"foo"},
|
|
},
|
|
},
|
|
{
|
|
"test-resources/client_with_function_denylist_empty.hcl",
|
|
&client.ClientTemplateConfig{
|
|
DisableSandbox: false,
|
|
FunctionDenylist: []string{},
|
|
},
|
|
},
|
|
{
|
|
"test-resources/client_with_function_denylist_empty_string.hcl",
|
|
&client.ClientTemplateConfig{
|
|
DisableSandbox: true,
|
|
FunctionDenylist: []string{""},
|
|
},
|
|
},
|
|
{
|
|
"test-resources/client_with_function_denylist_empty_string.json",
|
|
&client.ClientTemplateConfig{
|
|
DisableSandbox: true,
|
|
FunctionDenylist: []string{""},
|
|
},
|
|
},
|
|
{
|
|
"test-resources/client_with_function_denylist_nil.hcl",
|
|
&client.ClientTemplateConfig{
|
|
DisableSandbox: true,
|
|
},
|
|
},
|
|
{
|
|
"test-resources/client_with_empty_template.hcl",
|
|
nil,
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.File, func(t *testing.T) {
|
|
agentConfig, err := LoadConfig(tc.File)
|
|
|
|
require.NoError(t, err)
|
|
|
|
templateConfig := agentConfig.Client.TemplateConfig
|
|
require.Equal(t, tc.Expected, templateConfig)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParseMultipleIPTemplates(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
testCases := []struct {
|
|
name string
|
|
tmpl string
|
|
expectedOut []string
|
|
expectErr bool
|
|
}{
|
|
{
|
|
name: "deduplicates same ip and preserves order",
|
|
tmpl: "127.0.0.1 10.0.0.1 127.0.0.1",
|
|
expectedOut: []string{"127.0.0.1", "10.0.0.1"},
|
|
expectErr: false,
|
|
},
|
|
{
|
|
name: "includes sockaddr expression",
|
|
tmpl: "10.0.0.1 {{ GetAllInterfaces | include \"flags\" \"loopback\" | limit 1 | attr \"address\" }} 10.0.0.2",
|
|
expectedOut: []string{"10.0.0.1", "127.0.0.1", "10.0.0.2"},
|
|
expectErr: false,
|
|
},
|
|
}
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
out, err := parseMultipleIPTemplate(tc.tmpl)
|
|
require.NoError(t, err)
|
|
require.Equal(t, tc.expectedOut, out)
|
|
})
|
|
}
|
|
}
|
|
|
|
// this test makes sure Consul configs with and without WI merging happens
|
|
// correctly; here to assure we don't introduce regressions
|
|
func Test_mergeConsulConfigs(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
c0 := &Config{
|
|
Consuls: []*config.ConsulConfig{
|
|
{
|
|
Token: "foo",
|
|
},
|
|
},
|
|
}
|
|
|
|
c1 := &Config{
|
|
Consuls: []*config.ConsulConfig{
|
|
{
|
|
ServiceIdentity: &config.WorkloadIdentityConfig{
|
|
Audience: []string{"consul.io"},
|
|
TTL: pointer.Of(time.Hour),
|
|
},
|
|
TaskIdentity: &config.WorkloadIdentityConfig{
|
|
Audience: []string{"consul.io"},
|
|
TTL: pointer.Of(time.Hour),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
result := c0.Merge(c1)
|
|
|
|
must.Eq(t, c1.Consuls[0].ServiceIdentity, result.Consuls[0].ServiceIdentity)
|
|
must.Eq(t, c1.Consuls[0].TaskIdentity, result.Consuls[0].TaskIdentity)
|
|
must.Eq(t, c0.Consuls[0].Token, result.Consuls[0].Token)
|
|
}
|
|
|
|
func Test_mergeKEKProviderConfigs(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
left := []*structs.KEKProviderConfig{
|
|
{
|
|
// incomplete config with name
|
|
Provider: "awskms",
|
|
Name: "foo",
|
|
Active: true,
|
|
Config: map[string]string{
|
|
"region": "us-east-1",
|
|
"access_key": "AKIAIOSFODNN7EXAMPLE",
|
|
},
|
|
},
|
|
{
|
|
// empty config
|
|
Provider: "aead",
|
|
},
|
|
}
|
|
right := []*structs.KEKProviderConfig{
|
|
{
|
|
// same awskms.foo provider with fields to merge
|
|
Provider: "awskms",
|
|
Name: "foo",
|
|
Active: false,
|
|
Config: map[string]string{
|
|
"access_key": "AKIAIOSXABCD7EXAMPLE",
|
|
"secret_key": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
|
|
"kms_key_id": "19ec80b0-dfdd-4d97-8164-c6examplekey",
|
|
},
|
|
},
|
|
{
|
|
// same awskms provider, different name
|
|
Provider: "awskms",
|
|
Name: "bar",
|
|
Active: false,
|
|
Config: map[string]string{
|
|
"region": "us-east-1",
|
|
"access_key": "AKIAIOSFODNN7EXAMPLE",
|
|
},
|
|
},
|
|
}
|
|
|
|
result := mergeKEKProviderConfigs(left, right)
|
|
must.Eq(t, []*structs.KEKProviderConfig{
|
|
{
|
|
Provider: "aead",
|
|
},
|
|
{
|
|
Provider: "awskms",
|
|
Name: "bar",
|
|
Active: false,
|
|
Config: map[string]string{
|
|
"region": "us-east-1",
|
|
"access_key": "AKIAIOSFODNN7EXAMPLE",
|
|
},
|
|
},
|
|
{
|
|
Provider: "awskms",
|
|
Name: "foo",
|
|
Active: false, // should be flipped
|
|
Config: map[string]string{
|
|
"region": "us-east-1",
|
|
"access_key": "AKIAIOSXABCD7EXAMPLE", // override
|
|
"secret_key": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", // added
|
|
"kms_key_id": "19ec80b0-dfdd-4d97-8164-c6examplekey", // added
|
|
},
|
|
},
|
|
}, result)
|
|
}
|