mirror of https://github.com/hashicorp/boundary
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2479 lines
61 KiB
2479 lines
61 KiB
// 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)
|
|
}
|
|
})
|
|
}
|
|
}
|