In #2076 the refactor changed the position of a parenthesis, causing the dev key to truncate the base64 representation instead of specifying which bytes to base64 encode. We don't need this slicing anymore anyways since we now do individual key generation calls so just remove it.
2479 lines
61 KiB
Go
2479 lines
61 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package config
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hashicorp/boundary/internal/event"
|
|
configutil "github.com/hashicorp/go-secure-stdlib/configutil/v2"
|
|
"github.com/hashicorp/go-secure-stdlib/listenerutil"
|
|
"github.com/hashicorp/go-secure-stdlib/parseutil"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestDevController(t *testing.T) {
|
|
actual, err := DevController()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
truePointer := new(bool)
|
|
*truePointer = true
|
|
|
|
apiHeaders := map[int]http.Header{
|
|
0: {
|
|
"Content-Security-Policy": {"default-src 'none'"},
|
|
"X-Content-Type-Options": {"nosniff"},
|
|
"Strict-Transport-Security": {"max-age=31536000; includeSubDomains"},
|
|
"Cache-Control": {"no-store"},
|
|
},
|
|
}
|
|
uiHeaders := map[int]http.Header{
|
|
0: {
|
|
"Content-Security-Policy": {defaultCsp},
|
|
"X-Content-Type-Options": {"nosniff"},
|
|
"Strict-Transport-Security": {"max-age=31536000; includeSubDomains"},
|
|
"Cache-Control": {"no-store"},
|
|
},
|
|
}
|
|
|
|
exp := &Config{
|
|
Eventing: event.DefaultEventerConfig(),
|
|
SharedConfig: &configutil.SharedConfig{
|
|
DisableMlock: true,
|
|
Listeners: []*listenerutil.ListenerConfig{
|
|
{
|
|
Type: "tcp",
|
|
Purpose: []string{"api"},
|
|
TLSDisable: true,
|
|
CorsEnabled: truePointer,
|
|
CorsAllowedOrigins: []string{"*"},
|
|
CustomApiResponseHeaders: apiHeaders,
|
|
CustomUiResponseHeaders: uiHeaders,
|
|
},
|
|
{
|
|
Type: "tcp",
|
|
Purpose: []string{"cluster"},
|
|
CustomApiResponseHeaders: apiHeaders,
|
|
CustomUiResponseHeaders: uiHeaders,
|
|
},
|
|
{
|
|
Type: "tcp",
|
|
Purpose: []string{"ops"},
|
|
TLSDisable: true,
|
|
CustomApiResponseHeaders: apiHeaders,
|
|
CustomUiResponseHeaders: uiHeaders,
|
|
},
|
|
},
|
|
Seals: []*configutil.KMS{
|
|
{
|
|
Type: "aead",
|
|
Purpose: []string{"root"},
|
|
Config: map[string]string{
|
|
"aead_type": "aes-gcm",
|
|
"key_id": "global_root",
|
|
},
|
|
},
|
|
{
|
|
Type: "aead",
|
|
Purpose: []string{"worker-auth"},
|
|
Config: map[string]string{
|
|
"aead_type": "aes-gcm",
|
|
"key_id": "global_worker-auth",
|
|
},
|
|
},
|
|
{
|
|
Type: "aead",
|
|
Purpose: []string{"bsr"},
|
|
Config: map[string]string{
|
|
"aead_type": "aes-gcm",
|
|
"key_id": "global_bsr",
|
|
},
|
|
},
|
|
{
|
|
Type: "aead",
|
|
Purpose: []string{"recovery"},
|
|
Config: map[string]string{
|
|
"aead_type": "aes-gcm",
|
|
"key_id": "global_recovery",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Controller: &Controller{
|
|
Name: "dev-controller",
|
|
Description: "A default controller created in dev mode",
|
|
},
|
|
DevController: true,
|
|
}
|
|
exp.Eventing.ErrorEventsDisabled = true
|
|
exp.Eventing.SysEventsEnabled = false
|
|
exp.Eventing.ObservationsEnabled = false
|
|
|
|
exp.Listeners[0].RawConfig = actual.Listeners[0].RawConfig
|
|
exp.Listeners[1].RawConfig = actual.Listeners[1].RawConfig
|
|
exp.Listeners[2].RawConfig = actual.Listeners[2].RawConfig
|
|
exp.Seals[0].Config["key"] = actual.Seals[0].Config["key"]
|
|
exp.Seals[1].Config["key"] = actual.Seals[1].Config["key"]
|
|
exp.Seals[2].Config["key"] = actual.Seals[2].Config["key"]
|
|
exp.Seals[3].Config["key"] = actual.Seals[3].Config["key"]
|
|
exp.DevControllerKey = actual.Seals[0].Config["key"]
|
|
exp.DevWorkerAuthKey = actual.Seals[1].Config["key"]
|
|
exp.DevBsrKey = actual.Seals[2].Config["key"]
|
|
exp.DevRecoveryKey = actual.Seals[3].Config["key"]
|
|
|
|
assert.Equal(t, exp, actual)
|
|
|
|
// Do some CORS-specific testing
|
|
{
|
|
// CORS disabled
|
|
conf := `
|
|
listener "tcp" {
|
|
purpose = "api"
|
|
cors_enabled = false
|
|
}
|
|
`
|
|
actual, err = Parse(conf)
|
|
assert.NoError(t, err)
|
|
l0 := actual.Listeners[0]
|
|
assert.False(t, *l0.CorsEnabled)
|
|
assert.Empty(t, l0.CorsAllowedHeaders)
|
|
|
|
// Enabled with a wildcard
|
|
conf = `
|
|
listener "tcp" {
|
|
purpose = "api"
|
|
cors_enabled = true
|
|
cors_allowed_origins = ["*"]
|
|
}
|
|
`
|
|
actual, err = Parse(conf)
|
|
assert.NoError(t, err)
|
|
l0 = actual.Listeners[0]
|
|
assert.True(t, *l0.CorsEnabled)
|
|
assert.Equal(t, []string{"*"}, l0.CorsAllowedOrigins)
|
|
assert.Nil(t, l0.CorsDisableDefaultAllowedOriginValues)
|
|
|
|
// Disabled, default behavior
|
|
conf = `
|
|
listener "tcp" {
|
|
purpose = "api"
|
|
}
|
|
`
|
|
actual, err = Parse(conf)
|
|
assert.NoError(t, err)
|
|
l0 = actual.Listeners[0]
|
|
assert.True(t, *l0.CorsEnabled)
|
|
assert.Equal(t, []string{"*"}, l0.CorsAllowedOrigins)
|
|
assert.Nil(t, l0.CorsDisableDefaultAllowedOriginValues)
|
|
|
|
// Disabled, default behavior
|
|
conf = `
|
|
listener "tcp" {
|
|
purpose = "api"
|
|
cors_disable_default_allowed_origin_values = true
|
|
}
|
|
`
|
|
actual, err = Parse(conf)
|
|
assert.NoError(t, err)
|
|
l0 = actual.Listeners[0]
|
|
assert.Nil(t, l0.CorsEnabled)
|
|
assert.Empty(t, l0.CorsAllowedOrigins)
|
|
assert.True(t, *l0.CorsDisableDefaultAllowedOriginValues)
|
|
}
|
|
|
|
// Test plugins block
|
|
{
|
|
// CORS disabled
|
|
conf := `
|
|
plugins {
|
|
execution_dir = "/tmp/foobar"
|
|
}
|
|
`
|
|
actual, err = Parse(conf)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, actual.Plugins.ExecutionDir, "/tmp/foobar")
|
|
}
|
|
}
|
|
|
|
func TestDevWorker(t *testing.T) {
|
|
actual, err := DevWorker(WithSysEventsEnabled(true), WithObservationsEnabled(true), TestWithErrorEventsEnabled(t, true))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
exp := &Config{
|
|
Eventing: event.DefaultEventerConfig(),
|
|
SharedConfig: &configutil.SharedConfig{
|
|
DisableMlock: true,
|
|
Listeners: []*listenerutil.ListenerConfig{
|
|
{
|
|
Type: "tcp",
|
|
Purpose: []string{"proxy"},
|
|
CustomApiResponseHeaders: map[int]http.Header{
|
|
0: {
|
|
"Content-Security-Policy": {"default-src 'none'"},
|
|
"X-Content-Type-Options": {"nosniff"},
|
|
"Strict-Transport-Security": {"max-age=31536000; includeSubDomains"},
|
|
"Cache-Control": {"no-store"},
|
|
},
|
|
},
|
|
CustomUiResponseHeaders: map[int]http.Header{
|
|
0: {
|
|
"Content-Security-Policy": {defaultCsp},
|
|
"X-Content-Type-Options": {"nosniff"},
|
|
"Strict-Transport-Security": {"max-age=31536000; includeSubDomains"},
|
|
"Cache-Control": {"no-store"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Seals: []*configutil.KMS{
|
|
{
|
|
Type: "aead",
|
|
Purpose: []string{"worker-auth-storage"},
|
|
Config: map[string]string{
|
|
"aead_type": "aes-gcm",
|
|
"key_id": "worker-auth-storage",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Worker: &Worker{
|
|
Name: "w_1234567890",
|
|
Description: "A default worker created in dev mode",
|
|
InitialUpstreams: []string{"127.0.0.1"},
|
|
InitialUpstreamsRaw: []any{"127.0.0.1"},
|
|
Tags: map[string][]string{
|
|
"type": {"dev", "local"},
|
|
},
|
|
},
|
|
}
|
|
|
|
exp.Listeners[0].RawConfig = actual.Listeners[0].RawConfig
|
|
exp.Seals[0].Config["key"] = actual.Seals[0].Config["key"]
|
|
exp.Worker.TagsRaw = actual.Worker.TagsRaw
|
|
assert.Equal(t, exp, actual)
|
|
|
|
// Redo it with key=value syntax for tags
|
|
devWorkerKeyValueConfig := `
|
|
listener "tcp" {
|
|
purpose = "proxy"
|
|
}
|
|
|
|
worker {
|
|
name = "w_1234567890"
|
|
description = "A default worker created in dev mode"
|
|
initial_upstreams = ["127.0.0.1"]
|
|
tags = ["type=dev", "type=local"]
|
|
}
|
|
`
|
|
|
|
actual, err = Parse(devConfig + devWorkerKeyValueConfig)
|
|
assert.NoError(t, err)
|
|
exp.Listeners[0].RawConfig = actual.Listeners[0].RawConfig
|
|
exp.Seals = nil
|
|
exp.Worker.TagsRaw = actual.Worker.TagsRaw
|
|
assert.Equal(t, exp, actual)
|
|
|
|
// Handle when there is a singular value not indicated as a slice
|
|
devWorkerKeyValueConfig = `
|
|
listener "tcp" {
|
|
purpose = "proxy"
|
|
}
|
|
|
|
worker {
|
|
name = "w_1234567890"
|
|
description = "A default worker created in dev mode"
|
|
initial_upstreams = ["127.0.0.1"]
|
|
tags {
|
|
type = "local"
|
|
}
|
|
}
|
|
`
|
|
|
|
actual, err = Parse(devConfig + devWorkerKeyValueConfig)
|
|
assert.NoError(t, err)
|
|
exp.Listeners[0].RawConfig = actual.Listeners[0].RawConfig
|
|
exp.Worker.TagsRaw = actual.Worker.TagsRaw
|
|
prevTags := exp.Worker.Tags
|
|
exp.Worker.Tags = map[string][]string{"type": {"local"}}
|
|
assert.Equal(t, exp, actual)
|
|
exp.Worker.Tags = prevTags
|
|
|
|
// Redo it with non-lower-cased keys
|
|
devWorkerKeyValueConfig = `
|
|
listener "tcp" {
|
|
purpose = "proxy"
|
|
}
|
|
|
|
worker {
|
|
name = "w_1234567890"
|
|
description = "A default worker created in dev mode"
|
|
initial_upstreams = ["127.0.0.1"]
|
|
tags = ["tyPe=dev", "type=local"]
|
|
}
|
|
`
|
|
|
|
_, err = Parse(devConfig + devWorkerKeyValueConfig)
|
|
assert.Error(t, err)
|
|
|
|
// Redo it with non-lower-cased values
|
|
devWorkerKeyValueConfig = `
|
|
listener "tcp" {
|
|
purpose = "proxy"
|
|
}
|
|
|
|
worker {
|
|
name = "w_1234567890"
|
|
description = "A default worker created in dev mode"
|
|
initial_upstreams = ["127.0.0.1"]
|
|
tags = ["type=dev", "type=loCal"]
|
|
}
|
|
`
|
|
|
|
_, err = Parse(devConfig + devWorkerKeyValueConfig)
|
|
assert.Error(t, err)
|
|
|
|
// Redo with non-printable characters to validate the strutil function
|
|
devWorkerKeyValueConfig = `
|
|
listener "tcp" {
|
|
purpose = "proxy"
|
|
}
|
|
|
|
worker {
|
|
name = "dev-work\u0000er"
|
|
description = "A default worker created in dev mode"
|
|
initial_upstreams = ["127.0.0.1"]
|
|
tags = ["type=dev", "type=local"]
|
|
}
|
|
`
|
|
|
|
_, err = Parse(devConfig + devWorkerKeyValueConfig)
|
|
assert.Error(t, err)
|
|
|
|
// Check activation token parsing
|
|
devWorkerActivationTokenConfig := `
|
|
listener "tcp" {
|
|
purpose = "proxy"
|
|
}
|
|
|
|
worker {
|
|
name = "dev-worker"
|
|
description = "A default worker created in dev mode"
|
|
initial_upstreams = ["127.0.0.1"]
|
|
controller_generated_activation_token = "foobar"
|
|
}
|
|
`
|
|
|
|
actual, err = Parse(devConfig + devWorkerActivationTokenConfig)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "foobar", actual.Worker.ControllerGeneratedActivationToken)
|
|
}
|
|
|
|
func TestDevCombined(t *testing.T) {
|
|
actual, err := DevCombined()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
truePointer := new(bool)
|
|
*truePointer = true
|
|
|
|
apiHeaders := map[int]http.Header{
|
|
0: {
|
|
"Content-Security-Policy": {"default-src 'none'"},
|
|
"X-Content-Type-Options": {"nosniff"},
|
|
"Strict-Transport-Security": {"max-age=31536000; includeSubDomains"},
|
|
"Cache-Control": {"no-store"},
|
|
},
|
|
}
|
|
uiHeaders := map[int]http.Header{
|
|
0: {
|
|
"Content-Security-Policy": {defaultCsp},
|
|
"X-Content-Type-Options": {"nosniff"},
|
|
"Strict-Transport-Security": {"max-age=31536000; includeSubDomains"},
|
|
"Cache-Control": {"no-store"},
|
|
},
|
|
}
|
|
|
|
exp := &Config{
|
|
Eventing: event.DefaultEventerConfig(),
|
|
SharedConfig: &configutil.SharedConfig{
|
|
DisableMlock: true,
|
|
Listeners: []*listenerutil.ListenerConfig{
|
|
{
|
|
Type: "tcp",
|
|
Purpose: []string{"api"},
|
|
TLSDisable: true,
|
|
CorsEnabled: truePointer,
|
|
CorsAllowedOrigins: []string{"*"},
|
|
CustomApiResponseHeaders: apiHeaders,
|
|
CustomUiResponseHeaders: uiHeaders,
|
|
},
|
|
{
|
|
Type: "tcp",
|
|
Purpose: []string{"cluster"},
|
|
CustomApiResponseHeaders: apiHeaders,
|
|
CustomUiResponseHeaders: uiHeaders,
|
|
},
|
|
{
|
|
Type: "tcp",
|
|
Purpose: []string{"ops"},
|
|
TLSDisable: true,
|
|
CustomApiResponseHeaders: apiHeaders,
|
|
CustomUiResponseHeaders: uiHeaders,
|
|
},
|
|
{
|
|
Type: "tcp",
|
|
Purpose: []string{"proxy"},
|
|
CustomApiResponseHeaders: apiHeaders,
|
|
CustomUiResponseHeaders: uiHeaders,
|
|
},
|
|
},
|
|
Seals: []*configutil.KMS{
|
|
{
|
|
Type: "aead",
|
|
Purpose: []string{"root"},
|
|
Config: map[string]string{
|
|
"aead_type": "aes-gcm",
|
|
"key_id": "global_root",
|
|
},
|
|
},
|
|
{
|
|
Type: "aead",
|
|
Purpose: []string{"worker-auth"},
|
|
Config: map[string]string{
|
|
"aead_type": "aes-gcm",
|
|
"key_id": "global_worker-auth",
|
|
},
|
|
},
|
|
{
|
|
Type: "aead",
|
|
Purpose: []string{"bsr"},
|
|
Config: map[string]string{
|
|
"aead_type": "aes-gcm",
|
|
"key_id": "global_bsr",
|
|
},
|
|
},
|
|
{
|
|
Type: "aead",
|
|
Purpose: []string{"recovery"},
|
|
Config: map[string]string{
|
|
"aead_type": "aes-gcm",
|
|
"key_id": "global_recovery",
|
|
},
|
|
},
|
|
{
|
|
Type: "aead",
|
|
Purpose: []string{"worker-auth-storage"},
|
|
Config: map[string]string{
|
|
"aead_type": "aes-gcm",
|
|
"key_id": "worker-auth-storage",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Controller: &Controller{
|
|
Name: "dev-controller",
|
|
Description: "A default controller created in dev mode",
|
|
},
|
|
DevController: true,
|
|
Worker: &Worker{
|
|
Name: "w_1234567890",
|
|
Description: "A default worker created in dev mode",
|
|
InitialUpstreams: []string{"127.0.0.1"},
|
|
InitialUpstreamsRaw: []any{"127.0.0.1"},
|
|
Tags: map[string][]string{
|
|
"type": {"dev", "local"},
|
|
},
|
|
},
|
|
}
|
|
|
|
exp.Listeners[0].RawConfig = actual.Listeners[0].RawConfig
|
|
exp.Listeners[1].RawConfig = actual.Listeners[1].RawConfig
|
|
exp.Listeners[2].RawConfig = actual.Listeners[2].RawConfig
|
|
exp.Listeners[3].RawConfig = actual.Listeners[3].RawConfig
|
|
exp.Seals[0].Config["key"] = actual.Seals[0].Config["key"]
|
|
exp.Seals[1].Config["key"] = actual.Seals[1].Config["key"]
|
|
exp.Seals[2].Config["key"] = actual.Seals[2].Config["key"]
|
|
exp.Seals[3].Config["key"] = actual.Seals[3].Config["key"]
|
|
exp.Seals[4].Config["key"] = actual.Seals[4].Config["key"]
|
|
exp.DevControllerKey = actual.Seals[0].Config["key"]
|
|
exp.DevWorkerAuthKey = actual.Seals[1].Config["key"]
|
|
exp.DevBsrKey = actual.Seals[2].Config["key"]
|
|
exp.DevRecoveryKey = actual.Seals[3].Config["key"]
|
|
exp.DevWorkerAuthStorageKey = actual.Seals[4].Config["key"]
|
|
exp.Worker.TagsRaw = actual.Worker.TagsRaw
|
|
assert.Equal(t, exp, actual)
|
|
}
|
|
|
|
func TestDevWorkerCredentialStoragePath(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
name string
|
|
devWorkerProvidedConfiguration string
|
|
storagePath string
|
|
}{
|
|
{
|
|
name: "Relative Storage Directory",
|
|
devWorkerProvidedConfiguration: `
|
|
listener "tcp" {
|
|
purpose = "proxy"
|
|
}
|
|
|
|
worker {
|
|
name = "w_1234567890"
|
|
description = "A default worker created in dev mode"
|
|
initial_upstreams = ["127.0.0.1"]
|
|
tags {
|
|
type = ["dev", "local"]
|
|
}
|
|
auth_storage_path = ".."
|
|
}
|
|
`,
|
|
storagePath: "..",
|
|
},
|
|
{
|
|
name: "Nonexistent Storage Directory",
|
|
devWorkerProvidedConfiguration: `
|
|
listener "tcp" {
|
|
purpose = "proxy"
|
|
}
|
|
|
|
worker {
|
|
name = "w_1234567890"
|
|
description = "A default worker created in dev mode"
|
|
initial_upstreams = ["127.0.0.1"]
|
|
tags {
|
|
type = ["dev", "local"]
|
|
}
|
|
auth_storage_path = "nonexistent_dir/here"
|
|
}
|
|
`,
|
|
storagePath: "nonexistent_dir/here",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
parsed, err := Parse(devConfig + tt.devWorkerProvidedConfiguration)
|
|
require.NoError(t, err)
|
|
require.Equal(t, tt.storagePath, parsed.Worker.AuthStoragePath)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDevWorkerRecordingStoragePath(t *testing.T) {
|
|
t.Parallel()
|
|
td := t.TempDir()
|
|
tests := []struct {
|
|
name string
|
|
devWorkerProvidedConfiguration string
|
|
storagePath string
|
|
}{
|
|
{
|
|
name: "Relative Storage Directory",
|
|
devWorkerProvidedConfiguration: `
|
|
listener "tcp" {
|
|
purpose = "proxy"
|
|
}
|
|
|
|
worker {
|
|
name = "w_1234567890"
|
|
description = "A default worker created in dev mode"
|
|
initial_upstreams = ["127.0.0.1"]
|
|
tags {
|
|
type = ["dev", "local"]
|
|
}
|
|
recording_storage_path = ".."
|
|
}
|
|
`,
|
|
storagePath: "..",
|
|
},
|
|
{
|
|
name: "temp dir",
|
|
devWorkerProvidedConfiguration: fmt.Sprintf(`
|
|
listener "tcp" {
|
|
purpose = "proxy"
|
|
}
|
|
|
|
worker {
|
|
name = "w_1234567890"
|
|
description = "A default worker created in dev mode"
|
|
initial_upstreams = ["127.0.0.1"]
|
|
tags {
|
|
type = ["dev", "local"]
|
|
}
|
|
recording_storage_path = "%v"
|
|
}
|
|
`, td),
|
|
storagePath: td,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
parsed, err := Parse(devConfig + tt.devWorkerProvidedConfiguration)
|
|
require.NoError(t, err)
|
|
require.Equal(t, tt.storagePath, parsed.Worker.RecordingStoragePath)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDevKeyGeneration(t *testing.T) {
|
|
t.Parallel()
|
|
dk := DevKeyGeneration()
|
|
buf, err := base64.StdEncoding.DecodeString(dk)
|
|
require.NoError(t, err)
|
|
require.Len(t, buf, 32)
|
|
require.NotEqual(t, dk, DevKeyGeneration())
|
|
}
|
|
|
|
func TestParsingName(t *testing.T) {
|
|
t.Parallel()
|
|
config := `
|
|
controller {
|
|
name = "%s"
|
|
}
|
|
worker {
|
|
name = "%s"
|
|
}
|
|
`
|
|
controllerEnv := "FOOENV"
|
|
workerEnv := "BARENV"
|
|
cases := []struct {
|
|
name string
|
|
templateController string
|
|
templateWorker string
|
|
envController string
|
|
envWorker string
|
|
expectedController string
|
|
expectedWorker string
|
|
}{
|
|
{
|
|
name: "no env",
|
|
templateController: "foobar",
|
|
templateWorker: "test_worker_barfoo",
|
|
expectedController: "foobar",
|
|
expectedWorker: "test_worker_barfoo",
|
|
},
|
|
{
|
|
name: "env",
|
|
templateController: fmt.Sprintf("env://%s", controllerEnv),
|
|
templateWorker: fmt.Sprintf("env://%s", workerEnv),
|
|
envController: "foobar2",
|
|
envWorker: "env_name_barfoo2",
|
|
expectedController: "foobar2",
|
|
expectedWorker: "env_name_barfoo2",
|
|
},
|
|
}
|
|
for _, tt := range cases {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if tt.envController != "" {
|
|
os.Setenv(controllerEnv, tt.envController)
|
|
}
|
|
if tt.envWorker != "" {
|
|
os.Setenv(workerEnv, tt.envWorker)
|
|
}
|
|
out, err := Parse(fmt.Sprintf(config, tt.templateController, tt.templateWorker))
|
|
require.NoError(t, err)
|
|
assert.Equal(t, tt.expectedController, out.Controller.Name)
|
|
assert.Equal(t, tt.expectedWorker, out.Worker.Name)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParsingSchedulerIntervals(t *testing.T) {
|
|
t.Parallel()
|
|
cases := []struct {
|
|
name string
|
|
config string
|
|
wantErr bool
|
|
wantMonitorInterval time.Duration
|
|
wantRunJobInterval time.Duration
|
|
}{
|
|
{
|
|
name: "invalid-run-interval",
|
|
config: `
|
|
controller {
|
|
scheduler {
|
|
job_run_interval = "hello"
|
|
}
|
|
}
|
|
`,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "invalid-monitor-interval",
|
|
config: `
|
|
controller {
|
|
scheduler {
|
|
monitor_interval = "hello"
|
|
}
|
|
}
|
|
`,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "valid-undefined",
|
|
config: `controller { scheduler {} }`,
|
|
wantMonitorInterval: 0,
|
|
wantRunJobInterval: 0,
|
|
},
|
|
{
|
|
name: "run-job-interval",
|
|
config: `
|
|
controller {
|
|
scheduler {
|
|
job_run_interval = "10m"
|
|
}
|
|
}
|
|
`,
|
|
wantMonitorInterval: 0,
|
|
wantRunJobInterval: 10 * time.Minute,
|
|
},
|
|
{
|
|
name: "monitor-interval",
|
|
config: `
|
|
controller {
|
|
scheduler {
|
|
monitor_interval = "6h"
|
|
}
|
|
}
|
|
`,
|
|
wantMonitorInterval: 6 * time.Hour,
|
|
wantRunJobInterval: 0,
|
|
},
|
|
{
|
|
name: "both",
|
|
config: `
|
|
controller {
|
|
scheduler {
|
|
monitor_interval = "7d"
|
|
job_run_interval = "20s"
|
|
}
|
|
}
|
|
`,
|
|
wantMonitorInterval: 7 * 24 * time.Hour,
|
|
wantRunJobInterval: 20 * time.Second,
|
|
},
|
|
}
|
|
for _, tt := range cases {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
out, err := Parse(tt.config)
|
|
if tt.wantErr {
|
|
require.Error(t, err)
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
assert.Equal(t, tt.wantMonitorInterval, out.Controller.Scheduler.MonitorIntervalDuration)
|
|
assert.Equal(t, tt.wantRunJobInterval, out.Controller.Scheduler.JobRunIntervalDuration)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWorkerTags(t *testing.T) {
|
|
defaultStateFn := func(t *testing.T, tags string) {
|
|
t.Setenv("BOUNDARY_WORKER_TAGS", tags)
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
in string
|
|
stateFn func(t *testing.T, tags string)
|
|
actualTags string
|
|
expWorkerTags map[string][]string
|
|
expErr bool
|
|
expErrStr string
|
|
}{
|
|
{
|
|
name: "tags in HCL",
|
|
in: `
|
|
worker {
|
|
tags {
|
|
type = ["dev", "local"]
|
|
typetwo = "devtwo"
|
|
}
|
|
}`,
|
|
expWorkerTags: map[string][]string{
|
|
"type": {"dev", "local"},
|
|
"typetwo": {"devtwo"},
|
|
},
|
|
expErr: false,
|
|
},
|
|
{
|
|
name: "tags in HCL key=value",
|
|
in: `
|
|
worker {
|
|
tags = ["type=dev", "type=local", "typetwo=devtwo"]
|
|
}
|
|
`,
|
|
expWorkerTags: map[string][]string{
|
|
"type": {"dev", "local"},
|
|
"typetwo": {"devtwo"},
|
|
},
|
|
expErr: false,
|
|
},
|
|
{
|
|
name: "no tags",
|
|
in: `
|
|
worker {
|
|
name = "w_1234567890"
|
|
}
|
|
`,
|
|
expWorkerTags: nil,
|
|
expErr: false,
|
|
},
|
|
{
|
|
name: "empty tags",
|
|
in: `
|
|
worker {
|
|
name = "w_1234567890"
|
|
tags = {}
|
|
}
|
|
`,
|
|
expWorkerTags: map[string][]string{},
|
|
expErr: false,
|
|
},
|
|
{
|
|
name: "empty tags 2",
|
|
in: `
|
|
worker {
|
|
name = "w_1234567890"
|
|
tags = []
|
|
}
|
|
`,
|
|
expWorkerTags: map[string][]string{},
|
|
expErr: false,
|
|
},
|
|
{
|
|
name: "empty str",
|
|
in: `
|
|
worker {
|
|
tags = ""
|
|
}`,
|
|
expWorkerTags: map[string][]string{},
|
|
expErr: false,
|
|
},
|
|
{
|
|
name: "empty env var",
|
|
in: `
|
|
worker {
|
|
tags = "env://BOUNDARY_WORKER_TAGS"
|
|
}`,
|
|
expWorkerTags: map[string][]string{},
|
|
expErr: false,
|
|
},
|
|
{
|
|
name: "not a url - entire tags block",
|
|
in: `
|
|
worker {
|
|
tags = "\x00"
|
|
}`,
|
|
expWorkerTags: map[string][]string{},
|
|
expErr: true,
|
|
expErrStr: `Error parsing worker tags: error parsing url ("parse \"\\x00\": net/url: invalid control character in URL"): not a url`,
|
|
},
|
|
{
|
|
name: "not a url - key's value set to string",
|
|
in: `
|
|
worker {
|
|
tags {
|
|
type = "\x00"
|
|
}
|
|
}
|
|
`,
|
|
expWorkerTags: map[string][]string{
|
|
"type": {"\x00"},
|
|
},
|
|
expErr: false,
|
|
},
|
|
{
|
|
name: "one tag key",
|
|
in: `
|
|
worker {
|
|
tags = "env://BOUNDARY_WORKER_TAGS"
|
|
}`,
|
|
stateFn: defaultStateFn,
|
|
actualTags: `type = ["dev", "local"]`,
|
|
expWorkerTags: map[string][]string{
|
|
"type": {"dev", "local"},
|
|
},
|
|
expErr: false,
|
|
},
|
|
{
|
|
name: "multiple tag keys",
|
|
in: `
|
|
worker {
|
|
tags = "env://BOUNDARY_WORKER_TAGS"
|
|
}`,
|
|
stateFn: defaultStateFn,
|
|
actualTags: `
|
|
type = ["dev", "local"]
|
|
typetwo = ["devtwo", "localtwo"]
|
|
`,
|
|
expWorkerTags: map[string][]string{
|
|
"type": {"dev", "local"},
|
|
"typetwo": {"devtwo", "localtwo"},
|
|
},
|
|
expErr: false,
|
|
},
|
|
{
|
|
name: "comma in tag key string",
|
|
in: `
|
|
worker {
|
|
tags {
|
|
"key,"= ["value"],
|
|
}
|
|
}`,
|
|
expErr: true,
|
|
expErrStr: `Tag key "key," cannot contain commas`,
|
|
},
|
|
{
|
|
name: "comma in tag value string",
|
|
in: `
|
|
worker {
|
|
tags {
|
|
"key"= ["va,lue","value2"],
|
|
}
|
|
}`,
|
|
expErr: true,
|
|
expErrStr: `Tag value "va,lue" for tag key "key" cannot contain commas`,
|
|
},
|
|
{
|
|
name: "json tags - entire tags block",
|
|
in: `
|
|
worker {
|
|
tags = "env://BOUNDARY_WORKER_TAGS"
|
|
}`,
|
|
stateFn: defaultStateFn,
|
|
actualTags: `
|
|
{
|
|
"type": ["dev", "local"],
|
|
"typetwo": ["devtwo", "localtwo"]
|
|
}
|
|
`,
|
|
expWorkerTags: map[string][]string{
|
|
"type": {"dev", "local"},
|
|
"typetwo": {"devtwo", "localtwo"},
|
|
},
|
|
expErr: false,
|
|
},
|
|
{
|
|
name: "json tags - keys specified in the HCL file, values point to env/file",
|
|
in: `
|
|
worker {
|
|
tags = {
|
|
type = "env://BOUNDARY_WORKER_TAGS"
|
|
typetwo = "env://BOUNDARY_WORKER_TAGS_TWO"
|
|
}
|
|
}`,
|
|
stateFn: func(t *testing.T, tags string) {
|
|
defaultStateFn(t, tags)
|
|
t.Setenv("BOUNDARY_WORKER_TAGS_TWO", `["devtwo", "localtwo"]`)
|
|
},
|
|
actualTags: `["dev","local"]`,
|
|
expWorkerTags: map[string][]string{
|
|
"type": {"dev", "local"},
|
|
"typetwo": {"devtwo", "localtwo"},
|
|
},
|
|
expErr: false,
|
|
},
|
|
{
|
|
name: "json tags - mix n' match",
|
|
in: `
|
|
worker {
|
|
name = "web-prod-us-east-1"
|
|
tags {
|
|
type = "env://BOUNDARY_WORKER_TYPE_TAGS"
|
|
typetwo = "file://type_two_tags.json"
|
|
typethree = ["devthree", "localthree"]
|
|
}
|
|
}
|
|
`,
|
|
stateFn: func(t *testing.T, tags string) {
|
|
workerTypeTags := `["dev", "local"]`
|
|
t.Setenv("BOUNDARY_WORKER_TYPE_TAGS", workerTypeTags)
|
|
|
|
filepath := "./type_two_tags.json"
|
|
err := os.WriteFile(filepath, []byte(`["devtwo", "localtwo"]`), 0o666)
|
|
require.NoError(t, err)
|
|
|
|
t.Cleanup(func() {
|
|
err := os.Remove(filepath)
|
|
require.NoError(t, err)
|
|
})
|
|
},
|
|
expWorkerTags: map[string][]string{
|
|
"type": {"dev", "local"},
|
|
"typetwo": {"devtwo", "localtwo"},
|
|
"typethree": {"devthree", "localthree"},
|
|
},
|
|
expErr: false,
|
|
},
|
|
{
|
|
name: "bad json tags",
|
|
in: `
|
|
worker {
|
|
tags = {
|
|
type = "env://BOUNDARY_WORKER_TAGS"
|
|
typetwo = "env://BOUNDARY_WORKER_TAGS"
|
|
}
|
|
}`,
|
|
stateFn: defaultStateFn,
|
|
actualTags: `
|
|
{
|
|
"type": ["dev", "local"],
|
|
"typetwo": ["devtwo", "localtwo"]
|
|
}
|
|
`,
|
|
expWorkerTags: nil,
|
|
expErr: true,
|
|
expErrStr: "Error unmarshaling env var/file contents: json: cannot unmarshal object into Go value of type []string",
|
|
},
|
|
{
|
|
name: "no clean mapping to internal structures",
|
|
in: `
|
|
worker {
|
|
tags = "env://BOUNDARY_WORKER_TAGS"
|
|
}`,
|
|
stateFn: defaultStateFn,
|
|
actualTags: `
|
|
worker {
|
|
tags {
|
|
type = "indeed"
|
|
}
|
|
}
|
|
`,
|
|
expErr: true,
|
|
expErrStr: "Error decoding the worker's tags: 1 error(s) decoding:\n\n* '[0][worker][0]' expected type 'string', got unconvertible type 'map[string]interface {}', value: 'map[tags:[map[type:indeed]]]'",
|
|
},
|
|
{
|
|
name: "not HCL",
|
|
in: `worker {
|
|
tags = "env://BOUNDARY_WORKER_TAGS"
|
|
}`,
|
|
stateFn: defaultStateFn,
|
|
actualTags: `not_hcl`,
|
|
expErr: true,
|
|
expErrStr: "Error decoding raw worker tags: At 1:9: key 'not_hcl' expected start of object ('{') or assignment ('=')",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if tt.stateFn != nil {
|
|
tt.stateFn(t, tt.actualTags)
|
|
}
|
|
|
|
c, err := Parse(tt.in)
|
|
if tt.expErr {
|
|
require.EqualError(t, err, tt.expErrStr)
|
|
require.Nil(t, c)
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, c)
|
|
require.NotNil(t, c.Worker)
|
|
require.Equal(t, tt.expWorkerTags, c.Worker.Tags)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestController_EventingConfig(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
config []string
|
|
wantEventerConfig *event.EventerConfig
|
|
wantErr string
|
|
}{
|
|
{
|
|
name: "default",
|
|
wantEventerConfig: event.DefaultEventerConfig(),
|
|
},
|
|
{
|
|
name: "audit-enabled",
|
|
config: []string{`
|
|
events {
|
|
audit_enabled = true
|
|
}
|
|
`},
|
|
wantEventerConfig: &event.EventerConfig{
|
|
AuditEnabled: true,
|
|
ObservationsEnabled: false,
|
|
Sinks: []*event.SinkConfig{
|
|
event.DefaultSink(),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "observations-enabled",
|
|
config: []string{`
|
|
events {
|
|
observations_enabled = true
|
|
}
|
|
`},
|
|
wantEventerConfig: &event.EventerConfig{
|
|
AuditEnabled: false,
|
|
ObservationsEnabled: true,
|
|
Sinks: []*event.SinkConfig{
|
|
event.DefaultSink(),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "no-sink-type-determined",
|
|
config: []string{
|
|
`events {
|
|
audit_enabled = false
|
|
observations_enabled = true
|
|
sink {
|
|
format = "cloudevents-json"
|
|
name = "configured-sink"
|
|
event_types = [ "audit", "observation" ]
|
|
}
|
|
}`,
|
|
},
|
|
wantErr: `error parsing "events": sink type could not be determined`,
|
|
},
|
|
{
|
|
name: "sinks-configured",
|
|
config: []string{
|
|
`events {
|
|
audit_enabled = false
|
|
observations_enabled = true
|
|
sink {
|
|
type = "file"
|
|
format = "cloudevents-json"
|
|
name = "configured-sink"
|
|
event_types = [ "audit", "observation" ]
|
|
file {
|
|
file_name = "file-name"
|
|
rotate_duration = "2m"
|
|
}
|
|
}
|
|
sink {
|
|
type = "stderr"
|
|
format = "hclog-text"
|
|
name = "stderr-sink"
|
|
event_types = [ "error" ]
|
|
}
|
|
}`,
|
|
`events {
|
|
audit_enabled = false
|
|
observations_enabled = true
|
|
sink "file" {
|
|
format = "cloudevents-json"
|
|
name = "configured-sink"
|
|
event_types = [ "audit", "observation" ]
|
|
file {
|
|
file_name = "file-name"
|
|
rotate_duration = "2m"
|
|
}
|
|
}
|
|
sink "stderr" {
|
|
format = "hclog-text"
|
|
name = "stderr-sink"
|
|
event_types = [ "error" ]
|
|
}
|
|
}`,
|
|
`events {
|
|
audit_enabled = false
|
|
observations_enabled = true
|
|
sink {
|
|
format = "cloudevents-json"
|
|
name = "configured-sink"
|
|
event_types = [ "audit", "observation" ]
|
|
file {
|
|
file_name = "file-name"
|
|
rotate_duration = "2m"
|
|
}
|
|
}
|
|
sink {
|
|
format = "hclog-text"
|
|
name = "stderr-sink"
|
|
event_types = [ "error" ]
|
|
stderr = {}
|
|
}
|
|
}`,
|
|
`{
|
|
"events": {
|
|
"audit_enabled": false,
|
|
"observations_enabled": true,
|
|
"sink": [
|
|
{
|
|
"type": "file",
|
|
"format": "cloudevents-json",
|
|
"name": "configured-sink",
|
|
"event_types": ["audit", "observation"],
|
|
"file": {
|
|
"file_name": "file-name",
|
|
"rotate_duration": "2m"
|
|
}
|
|
},
|
|
{
|
|
"format": "hclog-text",
|
|
"name": "stderr-sink",
|
|
"event_types": ["error"],
|
|
"stderr": {}
|
|
}
|
|
]
|
|
}
|
|
}`,
|
|
},
|
|
wantEventerConfig: &event.EventerConfig{
|
|
AuditEnabled: false,
|
|
ObservationsEnabled: true,
|
|
Sinks: []*event.SinkConfig{
|
|
{
|
|
Type: "file",
|
|
Name: "configured-sink",
|
|
Format: "cloudevents-json",
|
|
EventTypes: []event.Type{"audit", "observation"},
|
|
FileConfig: &event.FileSinkTypeConfig{
|
|
FileName: "file-name",
|
|
RotateDurationHCL: "2m",
|
|
RotateDuration: 2 * time.Minute,
|
|
},
|
|
},
|
|
{
|
|
Type: "stderr",
|
|
Name: "stderr-sink",
|
|
Format: "hclog-text",
|
|
EventTypes: []event.Type{"error"},
|
|
StderrConfig: &event.StderrSinkTypeConfig{},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "audit_config",
|
|
config: []string{
|
|
`events {
|
|
audit_enabled = true
|
|
sink {
|
|
name = "audit-sink"
|
|
format = "cloudevents-json"
|
|
event_types = ["audit"]
|
|
file {
|
|
file_name = "audit.log"
|
|
}
|
|
audit_config {
|
|
audit_filter_overrides {
|
|
sensitive = ""
|
|
secret = "hmac-sha256"
|
|
}
|
|
}
|
|
}
|
|
}`,
|
|
},
|
|
wantEventerConfig: &event.EventerConfig{
|
|
AuditEnabled: true,
|
|
Sinks: []*event.SinkConfig{
|
|
{
|
|
Type: "file",
|
|
Name: "audit-sink",
|
|
Format: "cloudevents-json",
|
|
EventTypes: []event.Type{"audit"},
|
|
FileConfig: &event.FileSinkTypeConfig{
|
|
FileName: "audit.log",
|
|
},
|
|
AuditConfig: &event.AuditConfig{
|
|
FilterOverridesHCL: map[string]string{
|
|
"sensitive": "",
|
|
"secret": "hmac-sha256",
|
|
},
|
|
FilterOverrides: event.AuditFilterOperations{
|
|
event.SensitiveClassification: event.NoOperation,
|
|
event.SecretClassification: event.HmacSha256Operation,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
for i, conf := range tt.config {
|
|
c, err := Parse(conf)
|
|
if tt.wantErr != "" {
|
|
require.Error(err)
|
|
assert.Empty(c)
|
|
assert.Equal(tt.wantErr, err.Error(), "config %d want %q and got %q", i, tt.wantErr, err.Error())
|
|
return
|
|
}
|
|
require.NoError(err)
|
|
assert.NotEmpty(c)
|
|
assert.Equal(tt.wantEventerConfig, c.Eventing)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWorkerUpstreams(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in string
|
|
stateFn func(t *testing.T)
|
|
expWorkerUpstreams []string
|
|
expErr bool
|
|
expErrIs error
|
|
expErrStr string
|
|
}{
|
|
{
|
|
name: "No Upstreams",
|
|
in: `
|
|
worker {
|
|
name = "test"
|
|
}
|
|
`,
|
|
expWorkerUpstreams: nil,
|
|
expErr: false,
|
|
},
|
|
{
|
|
name: "One Upstream",
|
|
in: `
|
|
worker {
|
|
name = "test"
|
|
initial_upstreams = ["127.0.0.1"]
|
|
}
|
|
`,
|
|
expWorkerUpstreams: []string{"127.0.0.1"},
|
|
expErr: false,
|
|
},
|
|
{
|
|
name: "Multiple Upstreams",
|
|
in: `
|
|
worker {
|
|
name = "test"
|
|
initial_upstreams = ["127.0.0.1", "127.0.0.2", "127.0.0.3"]
|
|
}
|
|
`,
|
|
expWorkerUpstreams: []string{"127.0.0.1", "127.0.0.2", "127.0.0.3"},
|
|
expErr: false,
|
|
},
|
|
{
|
|
name: "Using env var",
|
|
in: `
|
|
worker {
|
|
name = "test"
|
|
initial_upstreams = "env://BOUNDARY_WORKER_UPSTREAMS"
|
|
}
|
|
`,
|
|
stateFn: func(t *testing.T) { t.Setenv("BOUNDARY_WORKER_UPSTREAMS", `["127.0.0.1", "127.0.0.2", "127.0.0.3"]`) },
|
|
expWorkerUpstreams: []string{"127.0.0.1", "127.0.0.2", "127.0.0.3"},
|
|
expErr: false,
|
|
},
|
|
{
|
|
name: "Using env var - invalid input 1",
|
|
in: `
|
|
worker {
|
|
name = "test"
|
|
initial_upstreams = "env://BOUNDARY_WORKER_UPSTREAMS"
|
|
}
|
|
`,
|
|
stateFn: func(t *testing.T) {
|
|
upstreams := `
|
|
worker {
|
|
initial_upstreams = ["127.0.0.1"]
|
|
}
|
|
`
|
|
t.Setenv("BOUNDARY_WORKER_UPSTREAMS", upstreams)
|
|
},
|
|
expWorkerUpstreams: nil,
|
|
expErr: true,
|
|
expErrStr: "Failed to parse worker upstreams: failed to unmarshal env/file contents: invalid character 'w' looking for beginning of value",
|
|
},
|
|
{
|
|
name: "Using env var - invalid input 2",
|
|
in: `
|
|
worker {
|
|
name = "test"
|
|
initial_upstreams = "env://BOUNDARY_WORKER_UPSTREAMS"
|
|
}
|
|
`,
|
|
stateFn: func(t *testing.T) { t.Setenv("BOUNDARY_WORKER_UPSTREAMS", `initial_upstreams = ["127.0.0.1"]`) },
|
|
expWorkerUpstreams: nil,
|
|
expErr: true,
|
|
expErrStr: "Failed to parse worker upstreams: failed to unmarshal env/file contents: invalid character 'i' looking for beginning of value",
|
|
},
|
|
{
|
|
name: "Unsupported object",
|
|
in: `
|
|
worker {
|
|
name = "test"
|
|
initial_upstreams = {
|
|
ip = "127.0.0.1"
|
|
ip = "127.0.0.2"
|
|
}
|
|
}
|
|
`,
|
|
expWorkerUpstreams: nil,
|
|
expErr: true,
|
|
expErrStr: "Failed to parse worker upstreams: unexpected type \"[]map[string]interface {}\"",
|
|
},
|
|
{
|
|
name: "Worker initial_upstreams set to invalid url",
|
|
in: `
|
|
worker {
|
|
name = "test"
|
|
initial_upstreams = "env://\x00"
|
|
}`,
|
|
expWorkerUpstreams: nil,
|
|
expErr: true,
|
|
expErrIs: parseutil.ErrNotAUrl,
|
|
},
|
|
{
|
|
name: "Worker using deprecated controllers field",
|
|
in: `
|
|
worker {
|
|
name = "test"
|
|
controllers = ["127.0.0.1", "127.0.0.2", "127.0.0.3"]
|
|
}`,
|
|
expWorkerUpstreams: []string{"127.0.0.1", "127.0.0.2", "127.0.0.3"},
|
|
expErr: false,
|
|
},
|
|
{
|
|
name: "Different values in controllers and initial_upstreams field",
|
|
in: `
|
|
worker {
|
|
name = "test"
|
|
controllers = ["127.0.0.1", "127.0.0.2", "127.0.0.3"]
|
|
initial_upstreams = ["127.0.0.1"]
|
|
}`,
|
|
expWorkerUpstreams: nil,
|
|
expErr: true,
|
|
expErrStr: "Failed to parse worker upstreams: both initial_upstreams and controllers fields are populated",
|
|
},
|
|
{
|
|
name: "Identical values in controllers and initial_upstreams field",
|
|
in: `
|
|
worker {
|
|
name = "test"
|
|
controllers = ["127.0.0.1"]
|
|
initial_upstreams = ["127.0.0.1"]
|
|
}`,
|
|
expWorkerUpstreams: nil,
|
|
expErr: true,
|
|
expErrStr: "Failed to parse worker upstreams: both initial_upstreams and controllers fields are populated",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if tt.stateFn != nil {
|
|
tt.stateFn(t)
|
|
}
|
|
|
|
c, err := Parse(tt.in)
|
|
if tt.expErr {
|
|
if tt.expErrIs != nil {
|
|
require.ErrorIs(t, err, tt.expErrIs)
|
|
} else {
|
|
require.EqualError(t, err, tt.expErrStr)
|
|
}
|
|
require.Nil(t, c)
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, c)
|
|
require.NotNil(t, c.Worker)
|
|
require.EqualValues(t, tt.expWorkerUpstreams, c.Worker.InitialUpstreams)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestControllerDescription(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in string
|
|
envDescription string
|
|
expDescription string
|
|
expErr bool
|
|
expErrStr string
|
|
}{
|
|
{
|
|
name: "Valid controller description from env var",
|
|
in: `
|
|
controller {
|
|
description = "env://CONTROLLER_DESCRIPTION"
|
|
}`,
|
|
envDescription: "Test controller description",
|
|
expDescription: "Test controller description",
|
|
expErr: false,
|
|
}, {
|
|
name: "Invalid controller description from env var",
|
|
in: `
|
|
controller {
|
|
description = "\uTest controller description"
|
|
}`,
|
|
expErr: true,
|
|
expErrStr: "At 3:22: illegal char escape",
|
|
}, {
|
|
name: "Not a URL, non-printable description",
|
|
in: `
|
|
controller {
|
|
description = "\x00"
|
|
}`,
|
|
expErr: true,
|
|
expErrStr: "Controller description contains non-printable characters",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Setenv("CONTROLLER_DESCRIPTION", tt.envDescription)
|
|
c, err := Parse(tt.in)
|
|
if tt.expErr {
|
|
require.EqualError(t, err, tt.expErrStr)
|
|
require.Nil(t, c)
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, c)
|
|
require.NotNil(t, c.Controller)
|
|
require.Equal(t, tt.expDescription, c.Controller.Description)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWorkerDescription(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in string
|
|
envDescription string
|
|
expDescription string
|
|
expErr bool
|
|
expErrStr string
|
|
}{
|
|
{
|
|
name: "Valid worker description from env var",
|
|
in: `
|
|
worker {
|
|
description = "env://WORKER_DESCRIPTION"
|
|
}`,
|
|
envDescription: "Test worker description",
|
|
expDescription: "Test worker description",
|
|
expErr: false,
|
|
}, {
|
|
name: "Invalid worker description",
|
|
in: `
|
|
worker {
|
|
description = "\uTest worker description"
|
|
}`,
|
|
expErr: true,
|
|
expErrStr: "At 3:22: illegal char escape",
|
|
}, {
|
|
name: "Not a URL, non-printable description",
|
|
in: `
|
|
worker {
|
|
description = "\x00"
|
|
}`,
|
|
expErr: true,
|
|
expErrStr: "Worker description contains non-printable characters",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Setenv("WORKER_DESCRIPTION", tt.envDescription)
|
|
c, err := Parse(tt.in)
|
|
if tt.expErr {
|
|
require.EqualError(t, err, tt.expErrStr)
|
|
require.Nil(t, c)
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, c)
|
|
require.NotNil(t, c.Worker)
|
|
require.Equal(t, tt.expDescription, c.Worker.Description)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPluginExecutionDir(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in string
|
|
envPluginExecutionDir string
|
|
expPluginExecutionDir string
|
|
expErr bool
|
|
expErrStr string
|
|
}{
|
|
{
|
|
name: "Valid plugin execution dir from env var",
|
|
in: `
|
|
plugins {
|
|
execution_dir = "env://PLUGIN_EXEC_DIR"
|
|
}`,
|
|
envPluginExecutionDir: `/var/run/boundary/plugin-exec`,
|
|
expPluginExecutionDir: `/var/run/boundary/plugin-exec`,
|
|
expErr: false,
|
|
}, {
|
|
name: "Invalid plugin execution dir from env var",
|
|
in: `
|
|
plugins {
|
|
execution_dir ="\ubad plugin directory"
|
|
}`,
|
|
expErr: true,
|
|
expErrStr: "At 3:28: illegal char escape",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Setenv("PLUGIN_EXEC_DIR", tt.envPluginExecutionDir)
|
|
p, err := Parse(tt.in)
|
|
if tt.expErr {
|
|
require.EqualError(t, err, tt.expErrStr)
|
|
require.Nil(t, p)
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, p)
|
|
require.NotNil(t, p.Plugins)
|
|
require.Equal(t, tt.expPluginExecutionDir, p.Plugins.ExecutionDir)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDatabaseMaxConnections(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in string
|
|
envMaxOpenConnections string
|
|
expMaxOpenConnections int
|
|
expErr bool
|
|
expErrStr string
|
|
}{
|
|
{
|
|
name: "Valid integer value",
|
|
in: `
|
|
controller {
|
|
name = "example-controller"
|
|
database {
|
|
max_open_connections = 5
|
|
}
|
|
}`,
|
|
expMaxOpenConnections: 5,
|
|
expErr: false,
|
|
},
|
|
{
|
|
name: "Valid string value",
|
|
in: `
|
|
controller {
|
|
name = "example-controller"
|
|
database {
|
|
max_open_connections = "5"
|
|
}
|
|
}`,
|
|
expMaxOpenConnections: 5,
|
|
expErr: false,
|
|
},
|
|
{
|
|
name: "Invalid value string",
|
|
in: `
|
|
controller {
|
|
name = "example-controller"
|
|
database {
|
|
max_open_connections = "string bad"
|
|
}
|
|
}`,
|
|
expErr: true,
|
|
expErrStr: "Database max open connections value is not an int: " +
|
|
"strconv.Atoi: parsing \"string bad\": invalid syntax",
|
|
},
|
|
{
|
|
name: "Invalid value type",
|
|
in: `
|
|
controller {
|
|
name = "example-controller"
|
|
database {
|
|
max_open_connections = false
|
|
}
|
|
}`,
|
|
expErr: true,
|
|
expErrStr: "Database max open connections: unsupported type \"bool\"",
|
|
},
|
|
{
|
|
name: "Valid env var",
|
|
in: `
|
|
controller {
|
|
name = "example-controller"
|
|
database {
|
|
max_open_connections = "env://ENV_MAX_CONN"
|
|
}
|
|
}`,
|
|
expMaxOpenConnections: 8,
|
|
envMaxOpenConnections: "8",
|
|
expErr: false,
|
|
},
|
|
{
|
|
name: "Invalid env var",
|
|
in: `
|
|
controller {
|
|
name = "example-controller"
|
|
database {
|
|
max_open_connections = "env://ENV_MAX_CONN"
|
|
}
|
|
}`,
|
|
envMaxOpenConnections: "bogus value",
|
|
expErr: true,
|
|
expErrStr: "Database max open connections value is not an int: " +
|
|
"strconv.Atoi: parsing \"bogus value\": invalid syntax",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Setenv("ENV_MAX_CONN", tt.envMaxOpenConnections)
|
|
c, err := Parse(tt.in)
|
|
if tt.expErr {
|
|
require.EqualError(t, err, tt.expErrStr)
|
|
require.Nil(t, c)
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, c)
|
|
require.NotNil(t, c.Controller)
|
|
require.NotNil(t, c.Controller.Database)
|
|
require.Equal(t, tt.expMaxOpenConnections, c.Controller.Database.MaxOpenConnections)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDatabaseMaxIdleConnections(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in string
|
|
envMaxIdleConnections string
|
|
expMaxIdleConnections int
|
|
expErr bool
|
|
expErrStr string
|
|
}{
|
|
{
|
|
name: "Valid integer value",
|
|
in: `
|
|
controller {
|
|
name = "example-controller"
|
|
database {
|
|
max_idle_connections = 5
|
|
}
|
|
}`,
|
|
expMaxIdleConnections: 5,
|
|
expErr: false,
|
|
},
|
|
{
|
|
name: "Valid integer string",
|
|
in: `
|
|
controller {
|
|
name = "example-controller"
|
|
database {
|
|
max_idle_connections = "5"
|
|
}
|
|
}`,
|
|
expMaxIdleConnections: 5,
|
|
expErr: false,
|
|
},
|
|
{
|
|
name: "Invalid value string",
|
|
in: `
|
|
controller {
|
|
name = "example-controller"
|
|
database {
|
|
max_idle_connections = "string bad"
|
|
}
|
|
}`,
|
|
expErr: true,
|
|
expErrStr: "Database max idle connections value is not a uint: " +
|
|
"strconv.Atoi: parsing \"string bad\": invalid syntax",
|
|
},
|
|
{
|
|
name: "Invalid value type",
|
|
in: `
|
|
controller {
|
|
name = "example-controller"
|
|
database {
|
|
max_idle_connections = false
|
|
}
|
|
}`,
|
|
expErr: true,
|
|
expErrStr: "Database max idle connections: unsupported type \"bool\"",
|
|
},
|
|
{
|
|
name: "Valid env var",
|
|
in: `
|
|
controller {
|
|
name = "example-controller"
|
|
database {
|
|
max_idle_connections = "env://ENV_MAX_IDLE_CONN"
|
|
}
|
|
}`,
|
|
expMaxIdleConnections: 8,
|
|
envMaxIdleConnections: "8",
|
|
expErr: false,
|
|
},
|
|
{
|
|
name: "Invalid env var",
|
|
in: `
|
|
controller {
|
|
name = "example-controller"
|
|
database {
|
|
max_idle_connections = "env://ENV_MAX_IDLE_CONN"
|
|
}
|
|
}`,
|
|
envMaxIdleConnections: "bogus value",
|
|
expErr: true,
|
|
expErrStr: "Database max idle connections value is not a uint: " +
|
|
"strconv.Atoi: parsing \"bogus value\": invalid syntax",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Setenv("ENV_MAX_IDLE_CONN", tt.envMaxIdleConnections)
|
|
c, err := Parse(tt.in)
|
|
if tt.expErr {
|
|
require.EqualError(t, err, tt.expErrStr)
|
|
require.Nil(t, c)
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, c)
|
|
require.NotNil(t, c.Controller)
|
|
require.NotNil(t, c.Controller.Database)
|
|
require.Equal(t, tt.expMaxIdleConnections, *c.Controller.Database.MaxIdleConnections)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDatabaseConnMaxIdleTimeDuration(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in string
|
|
envConnMaxIdleTimeDuration string
|
|
expConnMaxIdleTimeDuration time.Duration
|
|
expErr bool
|
|
expErrStr string
|
|
}{
|
|
{
|
|
name: "Valid duration value",
|
|
in: `
|
|
controller {
|
|
name = "example-controller"
|
|
database {
|
|
max_idle_time = "5m"
|
|
}
|
|
}`,
|
|
expConnMaxIdleTimeDuration: time.Minute * 5,
|
|
expErr: false,
|
|
},
|
|
{
|
|
name: "Valid env var value",
|
|
envConnMaxIdleTimeDuration: "5m",
|
|
in: `
|
|
controller {
|
|
name = "example-controller"
|
|
database {
|
|
max_idle_time = "env://ENV_CONN_MAX_IDLE_TIME"
|
|
}
|
|
}`,
|
|
expConnMaxIdleTimeDuration: time.Minute * 5,
|
|
expErr: false,
|
|
},
|
|
{
|
|
name: "Invalid value string",
|
|
in: `
|
|
controller {
|
|
name = "example-controller"
|
|
database {
|
|
max_idle_time = "string bad"
|
|
}
|
|
}`,
|
|
expErr: true,
|
|
expErrStr: "Connection max idle time is not a duration: " +
|
|
"strconv.ParseInt: parsing \"string ba\": invalid syntax",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Setenv("ENV_CONN_MAX_IDLE_TIME", tt.envConnMaxIdleTimeDuration)
|
|
c, err := Parse(tt.in)
|
|
if tt.expErr {
|
|
require.EqualError(t, err, tt.expErrStr)
|
|
require.Nil(t, c)
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, c)
|
|
require.NotNil(t, c.Controller)
|
|
require.NotNil(t, c.Controller.Database)
|
|
require.Equal(t, tt.expConnMaxIdleTimeDuration, *c.Controller.Database.ConnMaxIdleTimeDuration)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDatabaseSkipSharedLockAcquisition(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in string
|
|
expSkipSharedLockAcquisition bool
|
|
}{
|
|
{
|
|
name: "not set",
|
|
in: `
|
|
controller {
|
|
name = "example-controller"
|
|
database {
|
|
}
|
|
}`,
|
|
expSkipSharedLockAcquisition: false,
|
|
},
|
|
{
|
|
name: "set",
|
|
in: `
|
|
controller {
|
|
name = "example-controller"
|
|
database {
|
|
skip_shared_lock_acquisition = true
|
|
}
|
|
}`,
|
|
expSkipSharedLockAcquisition: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
c, err := Parse(tt.in)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, c)
|
|
require.NotNil(t, c.Controller)
|
|
require.NotNil(t, c.Controller.Database)
|
|
require.Equal(t, tt.expSkipSharedLockAcquisition, c.Controller.Database.SkipSharedLockAcquisition)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSetupControllerPublicClusterAddress(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
inputConfig *Config
|
|
inputFlagValue string
|
|
stateFn func(t *testing.T)
|
|
expErr bool
|
|
expErrStr string
|
|
expPublicClusterAddress string
|
|
}{
|
|
{
|
|
name: "nil controller",
|
|
inputConfig: &Config{
|
|
SharedConfig: &configutil.SharedConfig{
|
|
Listeners: []*listenerutil.ListenerConfig{},
|
|
},
|
|
Controller: nil,
|
|
},
|
|
inputFlagValue: "",
|
|
expErr: false,
|
|
expErrStr: "",
|
|
expPublicClusterAddress: ":9201",
|
|
},
|
|
{
|
|
name: "setting public cluster address directly with ip",
|
|
inputConfig: &Config{
|
|
SharedConfig: &configutil.SharedConfig{
|
|
Listeners: []*listenerutil.ListenerConfig{},
|
|
},
|
|
Controller: &Controller{
|
|
PublicClusterAddr: "127.0.0.1",
|
|
},
|
|
},
|
|
inputFlagValue: "",
|
|
expErr: false,
|
|
expErrStr: "",
|
|
expPublicClusterAddress: "127.0.0.1:9201",
|
|
},
|
|
{
|
|
name: "setting public cluster address directly with ip:port",
|
|
inputConfig: &Config{
|
|
SharedConfig: &configutil.SharedConfig{
|
|
Listeners: []*listenerutil.ListenerConfig{},
|
|
},
|
|
Controller: &Controller{
|
|
PublicClusterAddr: "127.0.0.1:8080",
|
|
},
|
|
},
|
|
inputFlagValue: "",
|
|
expErr: false,
|
|
expErrStr: "",
|
|
expPublicClusterAddress: "127.0.0.1:8080",
|
|
},
|
|
{
|
|
name: "setting public cluster address to env var",
|
|
inputConfig: &Config{
|
|
SharedConfig: &configutil.SharedConfig{
|
|
Listeners: []*listenerutil.ListenerConfig{},
|
|
},
|
|
Controller: &Controller{
|
|
PublicClusterAddr: "env://TEST_ENV_VAR_FOR_CONTROLLER_ADDR",
|
|
},
|
|
},
|
|
inputFlagValue: "",
|
|
stateFn: func(t *testing.T) {
|
|
t.Setenv("TEST_ENV_VAR_FOR_CONTROLLER_ADDR", "127.0.0.1:8080")
|
|
},
|
|
expErr: false,
|
|
expErrStr: "",
|
|
expPublicClusterAddress: "127.0.0.1:8080",
|
|
},
|
|
{
|
|
name: "setting public cluster address to env var that points to template",
|
|
inputConfig: &Config{
|
|
SharedConfig: &configutil.SharedConfig{
|
|
Listeners: []*listenerutil.ListenerConfig{},
|
|
},
|
|
Controller: &Controller{
|
|
PublicClusterAddr: "env://TEST_ENV_VAR_FOR_CONTROLLER_ADDR",
|
|
},
|
|
},
|
|
inputFlagValue: "",
|
|
stateFn: func(t *testing.T) {
|
|
t.Setenv("TEST_ENV_VAR_FOR_CONTROLLER_ADDR", `{{ GetAllInterfaces | include "flags" "loopback" | include "type" "IPV4" | join "address" " " }}`)
|
|
},
|
|
expErr: false,
|
|
expErrStr: "",
|
|
expPublicClusterAddress: "127.0.0.1:9201",
|
|
},
|
|
{
|
|
name: "setting public cluster address to ip template",
|
|
inputConfig: &Config{
|
|
SharedConfig: &configutil.SharedConfig{
|
|
Listeners: []*listenerutil.ListenerConfig{},
|
|
},
|
|
Controller: &Controller{
|
|
PublicClusterAddr: `{{ GetAllInterfaces | include "flags" "loopback" | include "type" "IPV4" | join "address" " " }}`,
|
|
},
|
|
},
|
|
inputFlagValue: "",
|
|
expErr: false,
|
|
expErrStr: "",
|
|
expPublicClusterAddress: "127.0.0.1:9201",
|
|
},
|
|
{
|
|
name: "setting public cluster address to multiline ip template",
|
|
inputConfig: &Config{
|
|
SharedConfig: &configutil.SharedConfig{
|
|
Listeners: []*listenerutil.ListenerConfig{},
|
|
},
|
|
Controller: &Controller{
|
|
PublicClusterAddr: `{{ with $local := GetAllInterfaces | include "flags" "loopback" | include "type" "IPV4" -}}
|
|
{{- $local | join "address" " " -}}
|
|
{{- end }}`,
|
|
},
|
|
},
|
|
inputFlagValue: "",
|
|
expErr: false,
|
|
expErrStr: "",
|
|
expPublicClusterAddress: "127.0.0.1:9201",
|
|
},
|
|
{
|
|
name: "using flag value with ip only",
|
|
inputConfig: &Config{
|
|
SharedConfig: &configutil.SharedConfig{
|
|
Listeners: []*listenerutil.ListenerConfig{},
|
|
},
|
|
Controller: &Controller{},
|
|
},
|
|
inputFlagValue: "127.0.0.1",
|
|
expErr: false,
|
|
expErrStr: "",
|
|
expPublicClusterAddress: "127.0.0.1:9201",
|
|
},
|
|
{
|
|
name: "using flag value with ip:port",
|
|
inputConfig: &Config{
|
|
SharedConfig: &configutil.SharedConfig{
|
|
Listeners: []*listenerutil.ListenerConfig{},
|
|
},
|
|
Controller: &Controller{},
|
|
},
|
|
inputFlagValue: "127.0.0.1:8080",
|
|
expErr: false,
|
|
expErrStr: "",
|
|
expPublicClusterAddress: "127.0.0.1:8080",
|
|
},
|
|
{
|
|
name: "using flag value with ip template",
|
|
inputConfig: &Config{
|
|
SharedConfig: &configutil.SharedConfig{
|
|
Listeners: []*listenerutil.ListenerConfig{},
|
|
},
|
|
Controller: &Controller{},
|
|
},
|
|
inputFlagValue: `{{ GetAllInterfaces | include "flags" "loopback" | include "type" "IPV4" | join "address" " " }}`,
|
|
expErr: false,
|
|
expErrStr: "",
|
|
expPublicClusterAddress: "127.0.0.1:9201",
|
|
},
|
|
{
|
|
name: "using flag value with multiline ip template",
|
|
inputConfig: &Config{
|
|
SharedConfig: &configutil.SharedConfig{
|
|
Listeners: []*listenerutil.ListenerConfig{},
|
|
},
|
|
Controller: &Controller{},
|
|
},
|
|
inputFlagValue: `{{ with $local := GetAllInterfaces | include "flags" "loopback" | include "type" "IPV4" -}}
|
|
{{- $local | join "address" " " -}}
|
|
{{- end }}`,
|
|
expErr: false,
|
|
expErrStr: "",
|
|
expPublicClusterAddress: "127.0.0.1:9201",
|
|
},
|
|
{
|
|
name: "using flag value to point to env var with ip only",
|
|
inputConfig: &Config{
|
|
SharedConfig: &configutil.SharedConfig{
|
|
Listeners: []*listenerutil.ListenerConfig{},
|
|
},
|
|
Controller: &Controller{},
|
|
},
|
|
inputFlagValue: "env://TEST_ENV_VAR_FOR_CONTROLLER_ADDR",
|
|
stateFn: func(t *testing.T) {
|
|
t.Setenv("TEST_ENV_VAR_FOR_CONTROLLER_ADDR", "127.0.0.1")
|
|
},
|
|
expErr: false,
|
|
expErrStr: "",
|
|
expPublicClusterAddress: "127.0.0.1:9201",
|
|
},
|
|
{
|
|
name: "using flag value to point to env var with ip:port",
|
|
inputConfig: &Config{
|
|
SharedConfig: &configutil.SharedConfig{
|
|
Listeners: []*listenerutil.ListenerConfig{},
|
|
},
|
|
Controller: &Controller{},
|
|
},
|
|
inputFlagValue: "env://TEST_ENV_VAR_FOR_CONTROLLER_ADDR",
|
|
stateFn: func(t *testing.T) {
|
|
t.Setenv("TEST_ENV_VAR_FOR_CONTROLLER_ADDR", "127.0.0.1:8080")
|
|
},
|
|
expErr: false,
|
|
expErrStr: "",
|
|
expPublicClusterAddress: "127.0.0.1:8080",
|
|
},
|
|
{
|
|
name: "read address from listeners ip only",
|
|
inputConfig: &Config{
|
|
SharedConfig: &configutil.SharedConfig{
|
|
Listeners: []*listenerutil.ListenerConfig{
|
|
{Purpose: []string{"cluster"}, Address: "127.0.0.1"},
|
|
},
|
|
},
|
|
Controller: &Controller{},
|
|
},
|
|
expErr: false,
|
|
expErrStr: "",
|
|
expPublicClusterAddress: "127.0.0.1:9201",
|
|
},
|
|
{
|
|
name: "read address from listeners ip:port",
|
|
inputConfig: &Config{
|
|
SharedConfig: &configutil.SharedConfig{
|
|
Listeners: []*listenerutil.ListenerConfig{
|
|
{Purpose: []string{"cluster"}, Address: "127.0.0.1:8080"},
|
|
},
|
|
},
|
|
Controller: &Controller{},
|
|
},
|
|
expErr: false,
|
|
expErrStr: "",
|
|
expPublicClusterAddress: "127.0.0.1:8080",
|
|
},
|
|
{
|
|
name: "read address from listeners is ignored on different purpose",
|
|
inputConfig: &Config{
|
|
SharedConfig: &configutil.SharedConfig{
|
|
Listeners: []*listenerutil.ListenerConfig{
|
|
{Purpose: []string{"somethingelse"}, Address: "127.0.0.1:8080"},
|
|
},
|
|
},
|
|
Controller: &Controller{},
|
|
},
|
|
expErr: false,
|
|
expErrStr: "",
|
|
expPublicClusterAddress: ":9201",
|
|
},
|
|
{
|
|
name: "using flag value to point to nonexistent file",
|
|
inputConfig: &Config{
|
|
SharedConfig: &configutil.SharedConfig{
|
|
Listeners: []*listenerutil.ListenerConfig{},
|
|
},
|
|
Controller: &Controller{},
|
|
},
|
|
inputFlagValue: "file://this_doesnt_exist_for_sure",
|
|
expErr: true,
|
|
expErrStr: "Error parsing public cluster addr: error reading file at file://this_doesnt_exist_for_sure: open this_doesnt_exist_for_sure: no such file or directory",
|
|
expPublicClusterAddress: "",
|
|
},
|
|
{
|
|
name: "using flag value to provoke error in SplitHostPort",
|
|
inputConfig: &Config{
|
|
SharedConfig: &configutil.SharedConfig{
|
|
Listeners: []*listenerutil.ListenerConfig{},
|
|
},
|
|
Controller: &Controller{},
|
|
},
|
|
inputFlagValue: "abc::123",
|
|
expErr: true,
|
|
expErrStr: "Error splitting public cluster adddress host/port: address abc::123: too many colons in address",
|
|
expPublicClusterAddress: "",
|
|
},
|
|
{
|
|
name: "bad ip template",
|
|
inputConfig: &Config{
|
|
SharedConfig: &configutil.SharedConfig{
|
|
Listeners: []*listenerutil.ListenerConfig{},
|
|
},
|
|
Controller: &Controller{},
|
|
},
|
|
inputFlagValue: "{{ somethingthatdoesntexist }}",
|
|
expErr: true,
|
|
expErrStr: "Error parsing IP template on controller public cluster addr: unable to parse address template \"{{ somethingthatdoesntexist }}\": unable to parse template \"{{ somethingthatdoesntexist }}\": template: sockaddr.Parse:1: function \"somethingthatdoesntexist\" not defined",
|
|
expPublicClusterAddress: "",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if tt.stateFn != nil {
|
|
tt.stateFn(t)
|
|
}
|
|
err := tt.inputConfig.SetupControllerPublicClusterAddress(tt.inputFlagValue)
|
|
if tt.expErr {
|
|
require.EqualError(t, err, tt.expErrStr)
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, tt.inputConfig.Controller)
|
|
require.Equal(t, tt.expPublicClusterAddress, tt.inputConfig.Controller.PublicClusterAddr)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSetupWorkerInitialUpstreams(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
name string
|
|
inputConfig *Config
|
|
stateFn func(t *testing.T)
|
|
expErr bool
|
|
expErrStr string
|
|
expInitialUpstreams []string
|
|
}{
|
|
{
|
|
name: "NilController",
|
|
inputConfig: &Config{
|
|
SharedConfig: &configutil.SharedConfig{
|
|
Listeners: []*listenerutil.ListenerConfig{},
|
|
},
|
|
Controller: nil,
|
|
Worker: &Worker{
|
|
InitialUpstreams: []string{"192.168.0.2:9201"},
|
|
},
|
|
},
|
|
expErr: false,
|
|
expErrStr: "",
|
|
expInitialUpstreams: []string{"192.168.0.2:9201"},
|
|
},
|
|
{
|
|
name: "NilWorker",
|
|
inputConfig: &Config{
|
|
SharedConfig: &configutil.SharedConfig{
|
|
Listeners: []*listenerutil.ListenerConfig{},
|
|
},
|
|
Controller: &Controller{
|
|
PublicClusterAddr: "192.168.0.3:9201",
|
|
},
|
|
Worker: nil,
|
|
},
|
|
expErr: false,
|
|
expErrStr: "",
|
|
expInitialUpstreams: nil,
|
|
},
|
|
{
|
|
name: "PublicClusterAddr",
|
|
inputConfig: &Config{
|
|
SharedConfig: &configutil.SharedConfig{
|
|
Listeners: []*listenerutil.ListenerConfig{},
|
|
},
|
|
Controller: &Controller{
|
|
PublicClusterAddr: "192.168.0.4:9201",
|
|
},
|
|
Worker: &Worker{},
|
|
},
|
|
expErr: false,
|
|
expErrStr: "",
|
|
expInitialUpstreams: []string{"192.168.0.4:9201"},
|
|
},
|
|
{
|
|
name: "ListenerNoAddr",
|
|
inputConfig: &Config{
|
|
SharedConfig: &configutil.SharedConfig{
|
|
Listeners: []*listenerutil.ListenerConfig{
|
|
{
|
|
Purpose: []string{"cluster"},
|
|
},
|
|
},
|
|
},
|
|
Controller: &Controller{},
|
|
Worker: &Worker{},
|
|
},
|
|
expErr: false,
|
|
expErrStr: "",
|
|
expInitialUpstreams: []string{"127.0.0.1:9201"},
|
|
},
|
|
{
|
|
name: "ListenerAddr",
|
|
inputConfig: &Config{
|
|
SharedConfig: &configutil.SharedConfig{
|
|
Listeners: []*listenerutil.ListenerConfig{
|
|
{
|
|
Purpose: []string{"cluster"},
|
|
Address: "192.168.0.5:9201",
|
|
},
|
|
},
|
|
},
|
|
Controller: &Controller{},
|
|
Worker: &Worker{},
|
|
},
|
|
expErr: false,
|
|
expErrStr: "",
|
|
expInitialUpstreams: []string{"192.168.0.5:9201"},
|
|
},
|
|
{
|
|
name: "ListenerAddrDomain",
|
|
inputConfig: &Config{
|
|
SharedConfig: &configutil.SharedConfig{
|
|
Listeners: []*listenerutil.ListenerConfig{
|
|
{
|
|
Purpose: []string{"cluster"},
|
|
Address: "foo.test",
|
|
},
|
|
},
|
|
},
|
|
Controller: &Controller{},
|
|
Worker: &Worker{},
|
|
},
|
|
expErr: false,
|
|
expErrStr: "",
|
|
expInitialUpstreams: []string{"foo.test"},
|
|
},
|
|
{
|
|
name: "ListenerAddrMultiplePurpose",
|
|
inputConfig: &Config{
|
|
SharedConfig: &configutil.SharedConfig{
|
|
Listeners: []*listenerutil.ListenerConfig{
|
|
{
|
|
Purpose: []string{"cluster", "api"},
|
|
},
|
|
},
|
|
},
|
|
Controller: &Controller{},
|
|
Worker: &Worker{},
|
|
},
|
|
expErr: true,
|
|
expErrStr: "Specifying a listener with more than one purpose is not supported",
|
|
expInitialUpstreams: nil,
|
|
},
|
|
{
|
|
name: "ListenerAddrNoPurposes",
|
|
inputConfig: &Config{
|
|
SharedConfig: &configutil.SharedConfig{
|
|
Listeners: []*listenerutil.ListenerConfig{
|
|
{
|
|
Purpose: []string{},
|
|
},
|
|
},
|
|
},
|
|
Controller: &Controller{},
|
|
Worker: &Worker{},
|
|
},
|
|
expErr: true,
|
|
expErrStr: "Listener specified without a purpose",
|
|
expInitialUpstreams: nil,
|
|
},
|
|
{
|
|
name: "ListenerAddrMismatchAddress",
|
|
inputConfig: &Config{
|
|
SharedConfig: &configutil.SharedConfig{
|
|
Listeners: []*listenerutil.ListenerConfig{
|
|
{
|
|
Purpose: []string{"cluster"},
|
|
Address: "192.168.0.5:9201",
|
|
},
|
|
},
|
|
},
|
|
Controller: &Controller{},
|
|
Worker: &Worker{
|
|
InitialUpstreams: []string{"192.168.0.2:9201"},
|
|
},
|
|
},
|
|
expErr: true,
|
|
expErrStr: `When running a combined controller and worker, it's invalid to specify a "initial_upstreams" or "controllers" key in the worker block with any values other than the controller cluster or upstream worker address/port when using IPs rather than DNS names`,
|
|
expInitialUpstreams: nil,
|
|
},
|
|
{
|
|
name: "ClusterAddrMismatchAddress",
|
|
inputConfig: &Config{
|
|
SharedConfig: &configutil.SharedConfig{
|
|
Listeners: []*listenerutil.ListenerConfig{},
|
|
},
|
|
Controller: &Controller{
|
|
PublicClusterAddr: "192.168.0.3:9201",
|
|
},
|
|
Worker: &Worker{
|
|
InitialUpstreams: []string{"192.168.0.2:9201"},
|
|
},
|
|
},
|
|
expErr: true,
|
|
expErrStr: `When running a combined controller and worker, it's invalid to specify a "initial_upstreams" or "controllers" key in the worker block with any values other than the controller cluster or upstream worker address/port when using IPs rather than DNS names`,
|
|
expInitialUpstreams: nil,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if tt.stateFn != nil {
|
|
tt.stateFn(t)
|
|
}
|
|
err := tt.inputConfig.SetupWorkerInitialUpstreams()
|
|
if tt.expErr {
|
|
require.EqualError(t, err, tt.expErrStr)
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
if tt.inputConfig.Worker == nil {
|
|
require.Empty(t, tt.expInitialUpstreams)
|
|
} else {
|
|
require.ElementsMatch(t, tt.expInitialUpstreams, tt.inputConfig.Worker.InitialUpstreams)
|
|
}
|
|
})
|
|
}
|
|
}
|