Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adding env-files to kind and node topology definition #847

Merged
merged 7 commits into from
Apr 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion clab/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,9 +260,17 @@ func (c *CLab) createNodeCfg(nodeName string, nodeDef *types.NodeDefinition, idx
// Extras
Extras: c.Config.Topology.GetNodeExtras(nodeName),
}
var err error

// Load content of the EnvVarFiles
envFileContent, err := utils.LoadEnvVarFiles(c.TopoFile.dir, c.Config.Topology.GetNodeEnvFiles(nodeName))
if err != nil {
return nil, err
}
// Merge EnvVarFiles content and the existing env variable
nodeCfg.Env = utils.MergeStringMaps(envFileContent, nodeCfg.Env)
hellt marked this conversation as resolved.
Show resolved Hide resolved

log.Debugf("node config: %+v", nodeCfg)
var err error
// initialize config
p, err := c.Config.Topology.GetNodeStartupConfig(nodeCfg.ShortName)
if err != nil {
Expand Down
51 changes: 51 additions & 0 deletions clab/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -470,3 +470,54 @@ func TestVerifyRootNetnsInterfaceUniqueness(t *testing.T) {
t.Logf("error: %v", err)

}

func TestEnvFileInit(t *testing.T) {
tests := map[string]struct {
got string
node string
want map[string]string
}{
"env-file_defined_at_node_and_default_1": {
got: "test_data/topo10.yml",
node: "node1",
want: map[string]string{
"env1": "val1",
"env2": "val2",
"ENVFILE1": "SOMEOTHERDATA",
"ENVFILE2": "THISANDTHAT",
},
},
"env-file_defined_at_node_and_default_2": {
got: "test_data/topo10.yml",
node: "node2",
want: map[string]string{
"ENVFILE1": "SOMEENVVARDATA",
"ENVFILE2": "THISANDTHAT",
},
},
}

teardownTestCase := setupTestCase(t)
defer teardownTestCase(t)

for name, tc := range tests {
t.Run(name, func(t *testing.T) {
opts := []ClabOption{
WithTopoFile(tc.got, ""),
}
c, err := NewContainerLab(opts...)
if err != nil {
t.Fatal(err)
}

env := c.Nodes[tc.node].Config().Env
// check all the want key/values are there
for k, v := range tc.want {
//check keys defined in tc.want exist and values are equal
if val, exists := env[k]; !(exists && val == v) {
t.Fatalf("wanted %q to be contained in env, but got %q", tc.want, env)
}
}
})
}
}
1 change: 1 addition & 0 deletions clab/test_data/envfile1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ENVFILE1=SOMEENVVARDATA
2 changes: 2 additions & 0 deletions clab/test_data/envfile2
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ENVFILE1=SOMEOTHERDATA
ENVFILE2=THISANDTHAT
20 changes: 20 additions & 0 deletions clab/test_data/topo10.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: topo10
topology:
defaults:
env-files:
- envfile2
nodes:
node1:
kind: linux
env:
env1: val1
env2: val2
mgmt_ipv4: 172.100.100.11
node2:
kind: linux
mgmt_ipv4: 172.100.100.12
labels:
node-label: value
env-files:
- envfile1

24 changes: 24 additions & 0 deletions docs/manual/nodes.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,30 @@ topology:

You can also specify a magic ENV VAR - `__IMPORT_ENVS: true` - which will import all environment variables defined in your shell to the relevant topology level.

### env-files
To add environment variables defined in a file use the `env-files` property that can be defined at `defaults`, `kind` and `node` levels.

The variable defined in the files are merged across all of them wtit more specific definitions overwriting less specific. Node level is the most specific one.

Files can either be specified with their absolute path or a relative path. The base path for the relative path resolution is the directory that holds the topology definition file.

```yaml
topology:
defaults:
env-files:
- envfiles/defaults
- /home/user/clab/default-env
kinds:
srl:
env-files:
- envfiles/common
- ~/spines
nodes:
node1:
env-files:
- /home/user/somefile
```

### user
To set a user which will be used to run a containerized process use the `user` configuration option. Can be defined at `node`, `kind` and `global` levels.

Expand Down
9 changes: 9 additions & 0 deletions types/node_definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ type NodeDefinition struct {
Publish []string `yaml:"publish,omitempty"`
// environment variables
Env map[string]string `yaml:"env,omitempty"`
// external file containing environment variables
EnvFiles []string `yaml:"env-files,omitempty"`
// linux user used in a container
User string `yaml:"user,omitempty"`
// container labels
Expand Down Expand Up @@ -186,6 +188,13 @@ func (n *NodeDefinition) GetEnv() map[string]string {
return n.Env
}

func (n *NodeDefinition) GetEnvFiles() []string {
if n == nil {
return nil
}
return n.EnvFiles
}

func (n *NodeDefinition) GetUser() string {
if n == nil {
return ""
Expand Down
10 changes: 10 additions & 0 deletions types/topology.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,16 @@ func (t *Topology) GetNodeEnv(name string) map[string]string {
return nil
}

func (t *Topology) GetNodeEnvFiles(name string) []string {
if ndef, ok := t.Nodes[name]; ok {
return utils.MergeStringSlices(
utils.MergeStringSlices(t.GetDefaults().GetEnvFiles(),
t.GetKind(t.GetNodeKind(name)).GetEnvFiles()),
ndef.GetEnvFiles())
}
return nil
}

func (t *Topology) GetNodePublish(name string) []string {
if ndef, ok := t.Nodes[name]; ok {
if len(ndef.GetPublish()) > 0 {
Expand Down
25 changes: 25 additions & 0 deletions utils/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"fmt"
"os"
"reflect"

"github.com/joho/godotenv"
)

// convertEnvs convert env variables passed as a map to a list of them
Expand Down Expand Up @@ -87,6 +89,29 @@ func StringInSlice(slice []string, val string) (int, bool) {
return -1, false
}

// LoadEnvVarFiles load EnvVars from the given files, resolving relative paths
func LoadEnvVarFiles(basefolder string, files []string) (map[string]string, error) {
resolvedPaths := []string{}
// resolve given paths, relative (to topology definition file)
for _, file := range files {
resolved := ResolvePath(file, basefolder)
if !FileExists(resolved) {
return nil, fmt.Errorf("env-file %s not found (path resolved to %s)", file, resolved)
}
resolvedPaths = append(resolvedPaths, resolved)
}

if len(resolvedPaths) == 0 {
return map[string]string{}, nil
}

result, err := godotenv.Read(resolvedPaths...)
if err != nil {
return nil, err
}
return result, nil
}

// MergeStringSlices merges string slices with duplicates removed
func MergeStringSlices(ss ...[]string) []string {
res := make([]string, 0)
Expand Down
21 changes: 21 additions & 0 deletions utils/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package utils

import (
"bufio"
"errors"
"fmt"
"io"
Expand Down Expand Up @@ -147,6 +148,26 @@ func ReadFileContent(file string) ([]byte, error) {
return b, err
}

func ReadFileLines(file string) ([]string, error) {
// check file exists
if !FileExists(file) {
return nil, fmt.Errorf("%w: %s", errFileNotExist, file)
}
content, err := os.Open(file)
if err != nil {
return nil, err
}

scanner := bufio.NewScanner(content)
scanner.Split(bufio.ScanLines)
var result []string

for scanner.Scan() {
result = append(result, scanner.Text())
}
return result, nil
}

// ExpandHome expands `~` char in the path to home path of a current user in provided path p.
func ExpandHome(p string) string {
userPath, _ := os.UserHomeDir()
Expand Down