Skip to content

Commit

Permalink
providers/vmware: Process guestinfo.metadata netplan configuration
Browse files Browse the repository at this point in the history
The network environment can be dynamic and thus needs to be provided as
VM metadata. Since the format should not depend on whether the VM runs
uses Ignition and Afterburn or Cloud-Init, the idea is to also support
the guestinfo.metadata variable as used by Cloud-Init which contains
Netplan YAML/JSON network configuration.
Add a new command to write out netplan configs to a given directory,
similar as we do with networkd units. While this is currently just used
for VMware, other providers could also construct the netplan data type
to provide netplan configurations if the OS rather wants to use
NetworkManager than systemd-networkd. For backwards compatibility and
to not need netplan it would be nice to keep the systemd-networkd
support as long as its used.

References:
https://cloudinit.readthedocs.io/en/latest/reference/datasources/vmware.html#walkthrough-of-guestinfo-keys-transport
https://cloudinit.readthedocs.io/en/latest/reference/network-config-format-v2.html
https://netplan.io/reference/
https://linux-on-z.blogspot.com/p/using-netplan-on-ibm-z.html
  • Loading branch information
pothos committed Oct 2, 2023
1 parent ebd3186 commit f5a6276
Show file tree
Hide file tree
Showing 11 changed files with 330 additions and 11 deletions.
18 changes: 9 additions & 9 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ jobs:
- name: Cache build artifacts
uses: Swatinem/rust-cache@v2
- name: cargo build
run: cargo build --all-targets
run: cargo build --all-features --all-targets
- name: cargo test
run: cargo test --all-targets
run: cargo test --all-features --all-targets
tests-release-stable:
name: Tests (release), stable toolchain
runs-on: ubuntu-latest
Expand All @@ -50,9 +50,9 @@ jobs:
- name: Cache build artifacts
uses: Swatinem/rust-cache@v2
- name: cargo build (release)
run: cargo build --all-targets --release
run: cargo build --all-features --all-targets --release
- name: cargo test (release)
run: cargo test --all-targets --release
run: cargo test --all-features --all-targets --release
tests-release-msrv:
name: Tests (release), minimum supported toolchain
runs-on: ubuntu-latest
Expand All @@ -72,9 +72,9 @@ jobs:
- name: Cache build artifacts
uses: Swatinem/rust-cache@v2
- name: cargo build (release)
run: cargo build --all-targets --release
run: cargo build --all-features --all-targets --release
- name: cargo test (release)
run: cargo test --all-targets --release
run: cargo test --all-features --all-targets --release
linting:
name: Lints, pinned toolchain
runs-on: ubuntu-latest
Expand All @@ -91,7 +91,7 @@ jobs:
- name: cargo fmt (check)
run: cargo fmt -- --check -l
- name: cargo clippy (warnings)
run: cargo clippy --all-targets -- -D warnings
run: cargo clippy --all-features --all-targets -- -D warnings
tests-other-channels:
name: Tests, unstable toolchain
runs-on: ubuntu-latest
Expand All @@ -109,6 +109,6 @@ jobs:
- name: Cache build artifacts
uses: Swatinem/rust-cache@v2
- name: cargo build
run: cargo build --all-targets
run: cargo build --all-features --all-targets
- name: cargo test
run: cargo test --all-targets
run: cargo test --all-features --all-targets
53 changes: 53 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,15 @@ debug = true

[dependencies]
anyhow = "1.0"
base64 = "0.21"
cfg-if = "1.0"
clap = { version = "4", "default_features" = false, "features" = ["std", "cargo", "derive", "error-context", "help", "suggestions", "usage", "wrap_help"] }
ipnetwork = ">= 0.17, < 0.21"
libflate = "1.3"
libsystemd = ">= 0.2.1, < 0.7.0"
mailparse = ">= 0.13, < 0.15"
maplit = "1.0"
netplan-types = { version = "0.3", optional = true }
nix = { version = ">= 0.19, < 0.28", "default_features" = false, "features" = [ "mount", "user"] }
openssh-keys = ">= 0.5, < 0.7"
openssl = ">= 0.10.46, < 0.11"
Expand All @@ -62,3 +65,7 @@ zbus = ">= 2.3, < 4"

[dev-dependencies]
mockito = "1"

[features]
default = []
netplan = ["dep:netplan-types"]
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ It comprises several modules which may run at different times during the lifecyc
Depending on the specific platform, the following services may run in the [initramfs](https://github.com/coreos/afterburn/tree/main/dracut/30afterburn) on first boot:
* setting local hostname
* injecting [network command-line arguments](usage/initrd-network-cmdline.md)
* configuring the network with [Netplan guestinfo metadata on VMware](usage/vmware-netplan-guestinfo-metadata.md)

The following features are conditionally available on some platforms as [systemd service units](https://github.com/coreos/afterburn/tree/main/systemd):
* installing public SSH keys for local system users
Expand Down
1 change: 1 addition & 0 deletions docs/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ nav_order: 8
Major changes:

- Add support for Scaleway
- Add Netplan guestinfo support on VMware

Minor changes:

Expand Down
4 changes: 4 additions & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ has_toc: false

See [Initrd first-boot network arguments](usage/initrd-network-cmdline.md).

## VMware Netplan guestinfo metadata

See [VMware Netplan guestinfo metadata](usage/vmware-netplan-guestinfo-metadata.md).

## Metadata attributes

See [Metadata attributes](usage/attributes.md).
36 changes: 36 additions & 0 deletions docs/usage/vmware-netplan-guestinfo-metadata.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
nav_order: 2
parent: Usage
---

# VMware Netplan guestinfo metadata

The network environment can vary between VMware servers and instead of leaking these requirements into userdata snippets, a well-known guestinfo metadata field can be used.
The guestinfo metadata field is OS-independent and supported by cloud-init and afterburn. It requires Netplan to render the Netplan YAML format to either NetworkManager or systemd-networkd configuration files.
By default, systemd-networkd units are used. Since the renderer backend is defined in the Netplan config itself, requiring NetworkManager would rule out support for systems that don't use it (unless they would postprocess the `renderer` field to force it to `networkd`). Since systemd-networkd can work in parallel with NetworkManager, it's expected that the renderer field is left to its default.

The afterburn invocation is as follows, where `FOLDER` could be `/run/netplan/`:

```
afterburn multi --netplan-configs FOLDER --provider vmware
```

Afterwards, `netplan generate` can be used to render the config files. If that is done before `systemd-networkd` runs, this is enough, but if the network already is up, `netplan apply` should be used instead.

## Specifying the guestinfo metadata

The guestinfo keys are named `guestinfo.metadata` for the content and `guestinfo.metadata.encoding` to specify the encoding of the content.
The value of the encoding field can be empty to indicate raw string data, or one of `base64` or `b64` to indicate an base64 encoding, or one of `gzip+base64` or `gz+b64` to indicate base64-encoded gzip data.

An example for raw string data is the following:
```
network:
version: 2
ethernets:
nics:
match:
name: ens*
dhcp4: yes
```

The supported config format with examples can be found in the [Netplan specification](https://netplan.readthedocs.io/en/latest/netplan-yaml/).
13 changes: 13 additions & 0 deletions src/cli/multi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ pub struct CliMulti {
/// The directory into which network units are written
#[arg(long = "network-units", value_name = "path")]
network_units_dir: Option<String>,
/// The directory into which netplan configs are written
#[cfg(feature = "netplan")]
#[arg(long = "netplan-configs", value_name = "path")]
netplan_config_dir: Option<String>,
#[cfg(not (feature = "netplan"))]
netplan_config_dir: Option<String>,
/// Update SSH keys for the given user
#[arg(long = "ssh-keys", value_name = "username")]
ssh_keys_user: Option<String>,
Expand All @@ -41,6 +47,7 @@ impl CliMulti {

if self.attributes_file.is_none()
&& self.network_units_dir.is_none()
&& self.netplan_config_dir.is_none()
&& !self.check_in
&& self.ssh_keys_user.is_none()
&& self.hostname_file.is_none()
Expand Down Expand Up @@ -72,6 +79,12 @@ impl CliMulti {
.map_or(Ok(()), |x| metadata.write_network_units(x))
.context("writing network units")?;

// write netplan configs if configured to do so
#[cfg(feature = "netplan")]
self.netplan_config_dir
.map_or(Ok(()), |x| metadata.write_netplan_configs(x))
.context("writing network units")?;

// perform boot check-in.
if self.check_in {
metadata
Expand Down
23 changes: 23 additions & 0 deletions src/providers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,11 @@ pub trait MetadataProvider {
Ok(vec![])
}

#[cfg(feature = "netplan")]
fn netplan_config(&self) -> Result<Option<netplan_types::NetplanConfig>> {
Ok(None)
}

fn boot_checkin(&self) -> Result<()> {
warn!("boot check-in requested, but not supported on this platform");
Ok(())
Expand Down Expand Up @@ -295,6 +300,24 @@ pub trait MetadataProvider {
}
Ok(())
}

#[cfg(feature = "netplan")]
fn write_netplan_configs(&self, netplan_configs_dir: String) -> Result<()> {
let dir_path = Path::new(&netplan_configs_dir);
fs::create_dir_all(dir_path)
.with_context(|| format!("failed to create directory {dir_path:?}"))?;

// Write a single afterburn `.yaml` netplan config.
if let Some(netplan_config) = &self.netplan_config()? {
let netplan_yaml = serde_yaml::to_string(&netplan_config)?;
let file_path = dir_path.join("afterburn.yaml");
let mut config_file = File::create(&file_path)
.with_context(|| format!("failed to create file {file_path:?}"))?;
write!(&mut config_file, "{netplan_yaml}")
.with_context(|| format!("failed to write netplan config file {config_file:?}"))?;
}
Ok(())
}
}

#[cfg(test)]
Expand Down
Loading

0 comments on commit f5a6276

Please sign in to comment.