diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml new file mode 100644 index 0000000..e14cce4 --- /dev/null +++ b/.github/workflows/build_and_test.yml @@ -0,0 +1,16 @@ +on: [push, pull_request] +name: Build & Test + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.20' + - name: Build + run: go build -v ./... + - name: Test + run: go test -v ./... diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..31eb2c4 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,31 @@ +on: + push: + tags: + - 'v*' + +name: Release + +permissions: + contents: write + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.20' + - name: Run tests + run: go test -v ./... + - name: Build binaries + run: | + GOOS=linux GOARCH=arm64 go build -o nm-configurator-arm64 main.go + GOOS=linux GOARCH=amd64 go build -o nm-configurator-amd64 main.go + - name: Create a release + uses: softprops/action-gh-release@v1 + with: + files: | + nm-configurator-arm64 + nm-configurator-amd64 diff --git a/README.md b/README.md index 3ea116e..66cfa67 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,111 @@ # nm-configurator -NetworkManager configuration tool + +A tool capable of identifying & storing the relevant NetworkManager settings +for a given host out of a pool of predefined desired configurations. + +Typically used with [Combustion](https://documentation.suse.com/sle-micro/5.4/single-html/SLE-Micro-deployment/#cha-images-combustion) +in order to bootstrap multiple nodes using the same provisioning artefact instead of depending on different custom images per machine. + +## What are the prerequisites? + +### Desired network configurations per host + +`nm-configurator` depends on having the desired network state for all known nodes beforehand. + +[NetworkManager](https://documentation.suse.com/sle-micro/5.4/html/SLE-Micro-all/cha-nm-configuration.html) +is using connection profiles defined as files stored under `/etc/NetworkManager/system-connections`. +These config files (*.nmconnection) can be generated using [nmstate](https://nmstate.io/features/gen_conf.html). + +Each file contains the desired state for a single network interface (e.g. `eth0`). +Configurations for all interfaces for all known hosts must be generated using `nmstate`. + +### Network interface mapping + +Network interface mapping is required in order for `nm-configurator` +to identify the proper configurations for each host it is running on. + +This additional config must be provided in a YAML format mapping the logical name of the interface to its MAC address: + +```yaml +host_config: + - hostname: node1.example.com + interfaces: + - logical_name: eth0 + mac_address: 00:10:20:30:40:50 + - logical_name: eth1 + mac_address: 10:20:30:40:50:60 + - hostname: node2.example.com + interfaces: + - logical_name: eth0 + mac_address: 00:11:22:33:44:55 +``` + +**NOTE:** Interface names during the installation of nodes might differ from the preconfigured logical ones. +This is expected and `nm-configurator` will rely on the MAC addresses and use the actual names for the +NetworkManager configurations instead e.g. settings for interface with a predefined logical name `eth0` but +actually named `eth0.101` will automatically be adjusted and stored to `/etc/NetworkManager/eth0.101.nmconnection`. + +## How to install it? + +### Standard method: + +Each release is published with `nm-configurator` already built for `amd64` and `arm64` Linux systems: + +For AMD64 / x86_64 based systems: +```shell +$ curl -o nm-configurator -L https://github.com/suse-edge/nm-configurator/releases/latest/download/nm-configurator-amd64 +$ chmod +x nm-configurator +``` + +For ARM64 based systems: +```shell +$ curl -o nm-configurator -L https://github.com/suse-edge/nm-configurator/releases/latest/download/nm-configurator-arm64 +$ chmod +x nm-configurator +``` + +### Manual method: + +```shell +$ git clone https://github.com/suse-edge/nm-configurator.git +$ cd nm-configurator +$ go build . # optionally specify GOOS and GOARCH flags if cross compiling +``` + +## How to run it? + +Using an example configuration of three known nodes (with hostnames `node1.example.com`, `node2.example.com` +and `node3.example.com` and their respective NetworkManager settings) and interface mapping defined in `host_config.yaml`: + +```text +config +├── node1.example.com +│ ├── eth0.nmconnection +│ └── eth1.nmconnection +├── node2.example.com +│ └── eth0.nmconnection +├── node3.example.com +│ ├── bond0.nmconnection +│ └── eth1.nmconnection +└── host_config.yaml +``` + +```shell +$ ./nm-configurator -config-dir=config -hosts-config-file=host_config.yaml +INFO[2023-08-17T17:32:23+03:00] starting network manager configurator... +INFO[2023-08-17T17:32:23+03:00] successfully identified host: node1.example.com +INFO[2023-08-17T17:32:23+03:00] storing file /etc/NetworkManager/system-connections/eth0.nmconnection... +INFO[2023-08-17T17:32:23+03:00] storing file /etc/NetworkManager/system-connections/eth1.nmconnection... +INFO[2023-08-17T17:32:23+03:00] successfully configured network manager +``` + +*Note:* The default values for `-config-dir` and `-hosts-config-file` flags are `config` and `host_config.yaml` +respectively so providing them is not necessary with the file structure in the example: + +```shell +$ ./nm-configurator +INFO[2023-08-17T17:45:41+03:00] starting network manager configurator... +INFO[2023-08-17T17:45:41+03:00] successfully identified host: node1.example.com +INFO[2023-08-17T17:45:41+03:00] storing file /etc/NetworkManager/system-connections/eth0.nmconnection... +INFO[2023-08-17T17:45:41+03:00] storing file /etc/NetworkManager/system-connections/eth1.nmconnection... +INFO[2023-08-17T17:45:41+03:00] successfully configured network manager +``` diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..6f5765c --- /dev/null +++ b/go.mod @@ -0,0 +1,16 @@ +module github.com/suse-edge/nm-configurator + +go 1.20 + +require ( + github.com/sirupsen/logrus v1.9.3 + github.com/stretchr/testify v1.7.0 + gopkg.in/ini.v1 v1.67.0 + gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..c8704c1 --- /dev/null +++ b/go.sum @@ -0,0 +1,18 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..78e4144 --- /dev/null +++ b/main.go @@ -0,0 +1,63 @@ +package main + +import ( + "flag" + "os" + + log "github.com/sirupsen/logrus" + "github.com/suse-edge/nm-configurator/pkg/config" + "github.com/suse-edge/nm-configurator/pkg/configurator" +) + +const systemConnectionsDir = "/etc/NetworkManager/system-connections" + +func init() { + log.SetFormatter(&log.TextFormatter{ + FullTimestamp: true, + QuoteEmptyFields: true, + }) + log.SetOutput(os.Stdout) +} + +func main() { + var ( + configDir string + hostsConfigFile string + verbose bool + ) + + flag.StringVar(&configDir, "config-dir", "config", "directory storing host mapping ('host_config.yaml') and *.nmconnection files per host") + flag.StringVar(&hostsConfigFile, "hosts-config-file", "host_config.yaml", "name of the hosts config file mapping interfaces to the respective MAC addresses") + flag.BoolVar(&verbose, "verbose", false, "enables DEBUG log level") + flag.Parse() + + if verbose { + log.SetLevel(log.DebugLevel) + } + + log.Info("starting network manager configurator...") + + if err := os.MkdirAll(systemConnectionsDir, 0755); err != nil { + log.Fatalf("failed to create \"system-connections\" dir: %s", err) + } + + conf, err := config.Load(configDir, hostsConfigFile, systemConnectionsDir) + if err != nil { + log.Fatalf("failed to load static host configuration: %s", err) + } + + log.Debugf("loaded static configuration: %+v", conf) + + networkInterfaces, err := configurator.GetNetworkInterfaces() + if err != nil { + log.Fatalf("failed to list system network interfaces: %s", err) + } + + log.Debugf("fetched system network interfaces: %+v", networkInterfaces) + + c := configurator.New(conf, networkInterfaces) + if err = c.Run(); err != nil { + log.Fatalf("failed to configure network manager: %s", err) + } + log.Info("successfully configured network manager") +} diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..6c38b4d --- /dev/null +++ b/main_test.go @@ -0,0 +1,69 @@ +package main + +import ( + "os" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/suse-edge/nm-configurator/pkg/config" + "github.com/suse-edge/nm-configurator/pkg/configurator" +) + +const ( + sourceDir = "testdata" + configFile = "host_config.yaml" + destDir = "testdata/out" +) + +func setupDestDir(t *testing.T) func(t *testing.T) { + require.NoError(t, os.MkdirAll(destDir, 0755)) + + return func(t *testing.T) { + assert.NoError(t, os.RemoveAll(destDir)) + } +} + +func TestConfigurator(t *testing.T) { + teardown := setupDestDir(t) + defer teardown(t) + + conf, err := config.Load(sourceDir, configFile, destDir) + require.Nil(t, err) + + networkInterfaces := map[string]string{ + "00:11:22:33:44:55": "eth0", + "00:11:22:33:44:56": "eth0.202", // Defined as "eth0.101" in eth0.101.nmconnection + "00:11:22:33:44:57": "eth1", + //"00:11:22:33:44:58": "bond0", Excluded on purpose, "bond0.nmconnection" should still be copied + } + + c := configurator.New(conf, networkInterfaces) + require.NoError(t, c.Run()) + + // Verify the content of the copied files. + hostDir := filepath.Join(sourceDir, "node1.example.com") + entries, err := os.ReadDir(hostDir) + require.Nil(t, err) + + assert.Len(t, entries, 4) + + for _, entry := range entries { + filename := entry.Name() + input, err := os.ReadFile(filepath.Join(hostDir, filename)) + require.Nil(t, err) + + // Adjust the name and content for the "eth0.101"->"eth0.202" edge case. + if filename == "eth0.101.nmconnection" { + filename = "eth0.202.nmconnection" + input = []byte(strings.ReplaceAll(string(input), "eth0.101", "eth0.202")) + } + + output, err := os.ReadFile(filepath.Join(destDir, filename)) + require.Nil(t, err) + + assert.Equal(t, string(input), string(output)) + } +} diff --git a/pkg/config/config.go b/pkg/config/config.go new file mode 100644 index 0000000..6f8c8cf --- /dev/null +++ b/pkg/config/config.go @@ -0,0 +1,62 @@ +package config + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "gopkg.in/yaml.v3" +) + +type Config struct { + // Configuration directory storing the preconfigured *.nmconnection files per host. + SourceDir string + // Destination directory to store the final *.nmconnection files for NetworkManager. + // Default "/etc/NetworkManager/system-connections". + DestinationDir string + Hosts []*Host `yaml:"host_config"` +} + +type Host struct { + Name string `yaml:"hostname"` + Interfaces []*Interface `yaml:"interfaces"` +} + +func (h *Host) String() string { + return fmt.Sprintf("{Name: %s Interfaces: %+v}", h.Name, h.Interfaces) +} + +type Interface struct { + LogicalName string `yaml:"logical_name"` + MACAddress string `yaml:"mac_address"` +} + +func (i *Interface) String() string { + return fmt.Sprintf("{LogicalName: %s MACAddress: %s}", i.LogicalName, i.MACAddress) +} + +func Load(sourceDir, configFilename, destinationDir string) (*Config, error) { + configFile := filepath.Join(sourceDir, configFilename) + file, err := os.ReadFile(configFile) + if err != nil { + return nil, err + } + + var c Config + if err = yaml.Unmarshal(file, &c); err != nil { + return nil, err + } + + // Ensure lower case formatting. + for _, host := range c.Hosts { + for _, i := range host.Interfaces { + i.MACAddress = strings.ToLower(i.MACAddress) + } + } + + c.SourceDir = sourceDir + c.DestinationDir = destinationDir + + return &c, nil +} diff --git a/pkg/configurator/configurator.go b/pkg/configurator/configurator.go new file mode 100644 index 0000000..84f83b4 --- /dev/null +++ b/pkg/configurator/configurator.go @@ -0,0 +1,130 @@ +package configurator + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "strings" + + log "github.com/sirupsen/logrus" + "github.com/suse-edge/nm-configurator/pkg/config" + "gopkg.in/ini.v1" +) + +const connectionFileExt = ".nmconnection" + +type Configurator struct { + config *config.Config + networkInterfaces NetworkInterfaces +} + +func New(config *config.Config, interfaces NetworkInterfaces) *Configurator { + return &Configurator{ + config: config, + networkInterfaces: interfaces, + } +} + +func (c *Configurator) Run() error { + host, err := c.identifyHost() + if err != nil { + return fmt.Errorf("identifying host: %w", err) + } + log.Infof("successfully identified host: %s", host.Name) + + if err = c.copyConnectionFiles(host); err != nil { + return fmt.Errorf("copying files: %w", err) + } + + return nil +} + +// Identify the preconfigured static host by matching the MAC address of at least one of the local network interfaces. +func (c *Configurator) identifyHost() (*config.Host, error) { + for _, host := range c.config.Hosts { + for _, i := range host.Interfaces { + if _, ok := c.networkInterfaces[i.MACAddress]; ok { + return host, nil + } + } + } + + return nil, fmt.Errorf("none of the preconfigured hosts match local NICs") +} + +// Copy all *.nmconnection files from the preconfigured host dir to the +// appropriate NetworkManager dir (default "/etc/NetworkManager/system-connections"). +func (c *Configurator) copyConnectionFiles(host *config.Host) error { + hostConfigDir := filepath.Join(c.config.SourceDir, host.Name) + dirEntries, err := os.ReadDir(hostConfigDir) + if err != nil { + return err + } + + var errs []error + + for _, entry := range dirEntries { + name := entry.Name() + if entry.IsDir() { + log.Warnf("ignoring unexpected directory: %s", name) + continue + } + + if filepath.Ext(name) != connectionFileExt { + log.Warnf("ignoring unexpected file: %s", name) + continue + } + + source := filepath.Join(hostConfigDir, name) + file, err := ini.Load(source) + if err != nil { + errs = append(errs, fmt.Errorf("loading file %s: %w", source, err)) + continue + } + + destination := filepath.Join(c.config.DestinationDir, name) + filename := strings.TrimSuffix(name, connectionFileExt) + + // Update the name and all references of the host NIC in the settings file if there is a difference from the static config. + for _, i := range host.Interfaces { + if i.LogicalName != filename { + continue + } + + interfaceName, ok := c.networkInterfaces[i.MACAddress] + if ok && interfaceName != i.LogicalName { + log.Debugf("using name '%s' for interface with MAC address '%s' instead of the preconfigured '%s'", + interfaceName, i.MACAddress, i.LogicalName) + + for _, section := range file.Sections() { + if !section.HasValue(i.LogicalName) { + continue + } + + for _, key := range section.Keys() { + if key.Value() == i.LogicalName { + key.SetValue(interfaceName) + } + } + } + + destination = fmt.Sprintf("%s/%s%s", c.config.DestinationDir, interfaceName, connectionFileExt) + } + break + } + + log.Infof("storing file %s...", destination) + if err = file.SaveTo(destination); err != nil { + errs = append(errs, fmt.Errorf("storing file %s: %w", destination, err)) + continue + } + + // Set the necessary permissions required by NetworkManager. + if err = os.Chmod(destination, 0600); err != nil { + errs = append(errs, fmt.Errorf("updating permissions for file %s: %w", destination, err)) + } + } + + return errors.Join(errs...) +} diff --git a/pkg/configurator/configurator_test.go b/pkg/configurator/configurator_test.go new file mode 100644 index 0000000..26806d8 --- /dev/null +++ b/pkg/configurator/configurator_test.go @@ -0,0 +1,156 @@ +package configurator + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/suse-edge/nm-configurator/pkg/config" +) + +func TestConfigurator_Run(t *testing.T) { + const destDir = "testdata/out" + require.Nil(t, os.MkdirAll(destDir, 0755)) + + defer func() { + assert.Nil(t, os.RemoveAll(destDir)) + }() + + tests := []struct { + name string + conf *config.Config + localInterfaces NetworkInterfaces + expectedErr string + }{ + { + name: "configurator fails due to none of the preconfigured hosts matching local interfaces", + conf: &config.Config{ + Hosts: []*config.Host{ + { + Name: "host1", + Interfaces: []*config.Interface{ + { + LogicalName: "eth0", + MACAddress: "00:11:22:33:44:55", + }, + }, + }, + { + Name: "host2", + Interfaces: []*config.Interface{ + { + LogicalName: "eth0", + MACAddress: "00:11:22:33:44:56", + }, + }, + }, + }, + }, + localInterfaces: map[string]string{ + "00:10:20:30:40:50": "eth0", + }, + expectedErr: "identifying host: none of the preconfigured hosts match local NICs", + }, + { + name: "configurator fails due to reading from non-existing config dir", + conf: &config.Config{ + SourceDir: "some-non-existing-dir-123", + Hosts: []*config.Host{ + { + Name: "host1", + Interfaces: []*config.Interface{ + { + LogicalName: "eth0", + MACAddress: "00:11:22:33:44:55", + }, + }, + }, + }, + }, + localInterfaces: map[string]string{ + "00:11:22:33:44:55": "eth0", + }, + expectedErr: "copying files: open some-non-existing-dir-123/host1: no such file or directory", + }, + { + name: "configurator fails due to parsing invalid config file", + conf: &config.Config{ + SourceDir: "testdata", + Hosts: []*config.Host{ + { + Name: "host2", + Interfaces: []*config.Interface{ + { + LogicalName: "eth0", + MACAddress: "00:11:22:33:44:57", + }, + }, + }, + }, + }, + localInterfaces: map[string]string{ + "00:11:22:33:44:57": "eth0", + }, + expectedErr: "copying files: loading file testdata/host2/invalid.nmconnection: key-value delimiter not found: -[connection]\n", + }, + { + name: "configurator fails due to storing to non-existing destination dir", + conf: &config.Config{ + SourceDir: "testdata", + DestinationDir: "some-non-existing-dir-123", + Hosts: []*config.Host{ + { + Name: "host1", + Interfaces: []*config.Interface{ + { + LogicalName: "eth0", + MACAddress: "00:11:22:33:44:57", + }, + }, + }, + }, + }, + localInterfaces: map[string]string{ + "00:11:22:33:44:57": "eth0", + }, + expectedErr: "copying files: storing file some-non-existing-dir-123/eth0.nmconnection: open some-non-existing-dir-123/eth0.nmconnection: no such file or directory", + }, + { + name: "configurator executed successfully", + conf: &config.Config{ + SourceDir: "testdata", + DestinationDir: destDir, + Hosts: []*config.Host{ + { + Name: "host1", + Interfaces: []*config.Interface{ + { + LogicalName: "eth0", + MACAddress: "00:11:22:33:44:55", + }, + }, + }, + }, + }, + localInterfaces: map[string]string{ + "00:11:22:33:44:55": "eth1", + }, + expectedErr: "", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + configurator := New(test.conf, test.localInterfaces) + + err := configurator.Run() + + if test.expectedErr == "" { + assert.Nil(t, err) + } else { + assert.EqualError(t, err, test.expectedErr) + } + }) + } +} diff --git a/pkg/configurator/network.go b/pkg/configurator/network.go new file mode 100644 index 0000000..82f9f2b --- /dev/null +++ b/pkg/configurator/network.go @@ -0,0 +1,33 @@ +package configurator + +import ( + "net" + "strings" +) + +// NetworkInterfaces maps system network interfaces. +// +// Key is MAC Address. +// Value is Name. +type NetworkInterfaces map[string]string + +func GetNetworkInterfaces() (NetworkInterfaces, error) { + interfaces, err := net.Interfaces() + if err != nil { + return nil, err + } + + interfaceAddresses := map[string]string{} + + for _, i := range interfaces { + if i.HardwareAddr == nil { + // omit loopback / virtual interfaces + continue + } + + address := strings.ToLower(i.HardwareAddr.String()) + interfaceAddresses[address] = i.Name + } + + return interfaceAddresses, nil +} diff --git a/pkg/configurator/testdata/host1/eth0.nmconnection b/pkg/configurator/testdata/host1/eth0.nmconnection new file mode 100644 index 0000000..71c6723 --- /dev/null +++ b/pkg/configurator/testdata/host1/eth0.nmconnection @@ -0,0 +1,23 @@ +[connection] +id=eth0 +uuid=4fd00f34-9191-481c-b931-caa24dae871a +type=ethernet +interface-name=eth0 + +[ethernet] + +[ipv4] +address1=192.168.123.1/24 +dns=192.168.123.100 +dns-priority=40 +method=manual +route1=0.0.0.0/0,192.168.123.1 +route1_options=table=254 + +[ipv6] +addr-gen-mode=eui64 +dhcp-duid=ll +dhcp-iaid=mac +method=disabled + +[proxy] diff --git a/pkg/configurator/testdata/host2/invalid.nmconnection b/pkg/configurator/testdata/host2/invalid.nmconnection new file mode 100644 index 0000000..4bcd520 --- /dev/null +++ b/pkg/configurator/testdata/host2/invalid.nmconnection @@ -0,0 +1,6 @@ +-[connection] +id=eth0 +uuid=d4c69e0e-f9fc-441a-b586-3cfdfffdd70a +type=ethernet +interface-name=eth0 + diff --git a/testdata/host_config.yaml b/testdata/host_config.yaml new file mode 100644 index 0000000..8873969 --- /dev/null +++ b/testdata/host_config.yaml @@ -0,0 +1,11 @@ +host_config: + - hostname: node1.example.com + interfaces: + - logical_name: eth0 + mac_address: 00:11:22:33:44:55 + - logical_name: eth0.101 + mac_address: 00:11:22:33:44:56 + - logical_name: eth1 + mac_address: 00:11:22:33:44:57 + - logical_name: bond0 + mac_address: 00:11:22:33:44:58 diff --git a/testdata/node1.example.com/bond0.nmconnection b/testdata/node1.example.com/bond0.nmconnection new file mode 100644 index 0000000..7065dba --- /dev/null +++ b/testdata/node1.example.com/bond0.nmconnection @@ -0,0 +1,18 @@ +[connection] +autoconnect = true +autoconnect-slaves = 1 +id = bond0 +interface-name = bond0 +type = bond +uuid = 925b4a95-2de0-5b2d-bcf5-8b684a7e9cb4 + +[bond] +miimon = 140 +mode = balance-rr + +[ipv4] +address0 = 192.0.2.0/24 +method = manual + +[ipv6] +method = disabled diff --git a/testdata/node1.example.com/eth0.101.nmconnection b/testdata/node1.example.com/eth0.101.nmconnection new file mode 100644 index 0000000..cf6042d --- /dev/null +++ b/testdata/node1.example.com/eth0.101.nmconnection @@ -0,0 +1,23 @@ +[connection] +id = eth0.101 +uuid = ad451df9-e022-4ce4-9ba1-4bc691c9abc1 +type = ethernet +interface-name = eth0.101 + +[ethernet] + +[ipv4] +address1 = 192.168.123.3/24 +dns = 192.168.123.100 +dns-priority = 40 +method = manual +route1 = 0.0.0.0/0,192.168.123.3 +route1_options = table=254 + +[ipv6] +addr-gen-mode = eui64 +dhcp-duid = ll +dhcp-iaid = mac +method = disabled + +[proxy] diff --git a/testdata/node1.example.com/eth0.nmconnection b/testdata/node1.example.com/eth0.nmconnection new file mode 100644 index 0000000..3a7a871 --- /dev/null +++ b/testdata/node1.example.com/eth0.nmconnection @@ -0,0 +1,23 @@ +[connection] +id = eth0 +uuid = 4fd00f34-9191-481c-b931-caa24dae871a +type = ethernet +interface-name = eth0 + +[ethernet] + +[ipv4] +address1 = 192.168.123.1/24 +dns = 192.168.123.100 +dns-priority = 40 +method = manual +route1 = 0.0.0.0/0,192.168.123.1 +route1_options = table=254 + +[ipv6] +addr-gen-mode = eui64 +dhcp-duid = ll +dhcp-iaid = mac +method = disabled + +[proxy] diff --git a/testdata/node1.example.com/eth1.nmconnection b/testdata/node1.example.com/eth1.nmconnection new file mode 100644 index 0000000..9669f87 --- /dev/null +++ b/testdata/node1.example.com/eth1.nmconnection @@ -0,0 +1,23 @@ +[connection] +id = eth1 +uuid = 4fd00f34-9191-481c-b931-caa24dae871a +type = ethernet +interface-name = eth1 + +[ethernet] + +[ipv4] +address1 = 192.168.123.2/24 +dns = 192.168.123.100 +dns-priority = 40 +method = manual +route1 = 0.0.0.0/0,192.168.123.2 +route1_options = table=254 + +[ipv6] +addr-gen-mode = eui64 +dhcp-duid = ll +dhcp-iaid = mac +method = disabled + +[proxy] diff --git a/testdata/node2.example.com/eth0.nmconnection b/testdata/node2.example.com/eth0.nmconnection new file mode 100644 index 0000000..f1de39a --- /dev/null +++ b/testdata/node2.example.com/eth0.nmconnection @@ -0,0 +1,23 @@ +[connection] +id = eth0 +uuid = 3cfe4645-8617-4d7f-95b4-c60decc02a4d +type = ethernet +interface-name = eth0 + +[ethernet] + +[ipv4] +address1 = 192.168.123.1/24 +dns = 192.168.123.100 +dns-priority = 40 +method = manual +route1 = 0.0.0.0/0,192.168.123.1 +route1_options = table=254 + +[ipv6] +addr-gen-mode = eui64 +dhcp-duid = ll +dhcp-iaid = mac +method = disabled + +[proxy]