feat(config): Add env/file support to Worker.Controllers field

This feature allows the user to set the `controllers` field to a string
that points to either an environment variable or file.
We then dynamically pull out the value and parse it into the config.
pull/1765/head
Hugo Vieira 4 years ago
parent a097b6db8f
commit 8462a65637

@ -5,13 +5,16 @@ Canonical reference for changes, improvements, and bugfixes for Boundary.
## Next
### New and Improved
* config: The `execution_dir` field for plugins now supports being set from environment variables
or a file on disk.([PR](https://github.com/hashicorp/boundary/pull/1772))
* config: Add support for reading worker controllers off of environment
variables as well as files. ([PR](https://github.com/hashicorp/boundary/pull/1765))
* config: The `description` field for controllers now supports being set
from environment variables or a file on disk
([PR](https://github.com/hashicorp/boundary/pull/1766))
* config: Add support for reading worker tags off of environment variables
as well as files. ([PR](https://github.com/hashicorp/boundary/pull/1758))
as well as files. ([PR](https://github.com/hashicorp/boundary/pull/1758))
* config: Add support for go-sockaddr templates to Worker and Controller
addresses. ([PR](https://github.com/hashicorp/boundary/pull/1731))
* host: Plugin-based host catalogs will now schedule updates for all

@ -9,6 +9,7 @@ import (
"fmt"
"io"
"io/ioutil"
"reflect"
"strings"
"time"
@ -151,10 +152,15 @@ func (c *Controller) InitNameIfEmpty() (string, error) {
}
type Worker struct {
Name string `hcl:"name"`
Description string `hcl:"description"`
Controllers []string `hcl:"controllers"`
PublicAddr string `hcl:"public_addr"`
Name string `hcl:"name"`
Description string `hcl:"description"`
PublicAddr string `hcl:"public_addr"`
// We use a raw interface here so that we can take in a string
// value pointing to an env var or file. We then resolve that
// and get the actual controller addresses.
Controllers []string `hcl:"-"`
ControllersRaw interface{} `hcl:"controllers"`
// We use a raw interface for parsing so that people can use JSON-like
// syntax that maps directly to the filter input or possibly more familiar
@ -448,6 +454,11 @@ func Parse(d string) (*Config, error) {
}
}
}
result.Worker.Controllers, err = parseWorkerControllers(result)
if err != nil {
return nil, fmt.Errorf("Failed to parse worker controllers: %w", err)
}
}
sharedConfig, err := configutil.ParseConfig(d)
@ -506,6 +517,42 @@ func Parse(d string) (*Config, error) {
return result, nil
}
func parseWorkerControllers(c *Config) ([]string, error) {
if c == nil || c.Worker == nil {
return nil, fmt.Errorf("config or worker field is nil")
}
if c.Worker.ControllersRaw == nil {
return nil, nil
}
switch t := c.Worker.ControllersRaw.(type) {
case []interface{}: // An array was configured directly in Boundary's HCL Config file.
var controllers []string
err := mapstructure.WeakDecode(c.Worker.ControllersRaw, &controllers)
if err != nil {
return nil, fmt.Errorf("failed to decode worker controllers block into config field: %w", err)
}
return controllers, nil
case string:
controllersStr, err := parseutil.ParsePath(t)
if err != nil {
return nil, fmt.Errorf("bad env var or file pointer: %w", err)
}
var addrs []string
err = json.Unmarshal([]byte(controllersStr), &addrs)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal env/file contents: %w", err)
}
return addrs, nil
default:
typ := reflect.TypeOf(t)
return nil, fmt.Errorf("unexpected type %q", typ.String())
}
}
func parseEventing(eventObj *ast.ObjectItem) (*event.EventerConfig, error) {
// Decode the outside struct
var result event.EventerConfig

@ -9,6 +9,7 @@ import (
"github.com/hashicorp/boundary/internal/observability/event"
"github.com/hashicorp/go-secure-stdlib/configutil"
"github.com/hashicorp/go-secure-stdlib/listenerutil"
"github.com/hashicorp/go-secure-stdlib/parseutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -173,9 +174,10 @@ func TestDevWorker(t *testing.T) {
},
},
Worker: &Worker{
Name: "dev-worker",
Description: "A default worker created in dev mode",
Controllers: []string{"127.0.0.1"},
Name: "dev-worker",
Description: "A default worker created in dev mode",
Controllers: []string{"127.0.0.1"},
ControllersRaw: []interface{}{"127.0.0.1"},
Tags: map[string][]string{
"type": {"dev", "local"},
},
@ -811,6 +813,146 @@ func TestController_EventingConfig(t *testing.T) {
}
}
func TestWorkerControllers(t *testing.T) {
tests := []struct {
name string
in string
stateFn func(t *testing.T)
expWorkerControllers []string
expErr bool
expErrIs error
expErrStr string
}{
{
name: "No Controllers",
in: `
worker {
name = "test"
}
`,
expWorkerControllers: nil,
expErr: false,
},
{
name: "One Controller",
in: `
worker {
name = "test"
controllers = ["127.0.0.1"]
}
`,
expWorkerControllers: []string{"127.0.0.1"},
expErr: false,
},
{
name: "Multiple controllers",
in: `
worker {
name = "test"
controllers = ["127.0.0.1", "127.0.0.2", "127.0.0.3"]
}
`,
expWorkerControllers: []string{"127.0.0.1", "127.0.0.2", "127.0.0.3"},
expErr: false,
},
{
name: "Using env var",
in: `
worker {
name = "test"
controllers = "env://BOUNDARY_WORKER_CONTROLLERS"
}
`,
stateFn: func(t *testing.T) { t.Setenv("BOUNDARY_WORKER_CONTROLLERS", `["127.0.0.1", "127.0.0.2", "127.0.0.3"]`) },
expWorkerControllers: []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"
controllers = "env://BOUNDARY_WORKER_CONTROLLERS"
}
`,
stateFn: func(t *testing.T) {
controllers := `
worker {
controllers = ["127.0.0.1"]
}
`
t.Setenv("BOUNDARY_WORKER_CONTROLLERS", controllers)
},
expWorkerControllers: nil,
expErr: true,
expErrStr: "Failed to parse worker controllers: 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"
controllers = "env://BOUNDARY_WORKER_CONTROLLERS"
}
`,
stateFn: func(t *testing.T) { t.Setenv("BOUNDARY_WORKER_CONTROLLERS", `controllers = ["127.0.0.1"]`) },
expWorkerControllers: nil,
expErr: true,
expErrStr: "Failed to parse worker controllers: failed to unmarshal env/file contents: invalid character 'c' looking for beginning of value",
},
{
name: "Unsupported object",
in: `
worker {
name = "test"
controllers = {
ip = "127.0.0.1"
ip = "127.0.0.2"
}
}
`,
expWorkerControllers: nil,
expErr: true,
expErrStr: "Failed to parse worker controllers: unexpected type \"[]map[string]interface {}\"",
},
{
name: "worker controllers set to invalid url",
in: `
worker {
name = "test"
controllers = "env://\x00"
}`,
expWorkerControllers: nil,
expErr: true,
expErrIs: parseutil.ErrNotAUrl,
},
}
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.expWorkerControllers, c.Worker.Controllers)
})
}
}
func TestControllerDescription(t *testing.T) {
tests := []struct {
name string

@ -34,7 +34,11 @@ worker {
or a [go-sockaddr template](https://godoc.org/github.com/hashicorp/go-sockaddr/template).
- `controllers` - A list of hosts/IP addresses and optionally ports for reaching
controllers. The port will default to :9201 if not specified.
controllers. The port will default to :9201 if not specified. This value can be
a direct access string array with the addresses, or it can refer to a file on
disk (file://) from which the addresses will be read, or an env var (env://) from
which the addresses will be read. When using env or file, their contents
must formatted as a JSON array: `["127.0.0.1", "192.168.0.1", "10.0.0.1"]`
- `tags` - A map of key-value pairs where values are an array of strings. Most
commonly used for [filtering](/docs/concepts/filtering) targets a worker can

Loading…
Cancel
Save