summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2021-03-04 09:47:35 +0000
committerLars Wirzenius <liw@liw.fi>2021-03-04 09:47:35 +0000
commit7f063f5a05bd9ac21a5679901c2f5bc5851a3cfb (patch)
tree9ad0731c2142d8fa49dda598d5a929556d221f3f
parent9ec059250bacd1b17547e40b95709fac8d4f164a (diff)
parent458305fa48338de178739af2e3e86b85c0036a54 (diff)
downloadvmadm-7f063f5a05bd9ac21a5679901c2f5bc5851a3cfb.tar.gz
Merge branch 'defaults' into 'main'
feat: use configured defaults to unspecified machine attributes See merge request larswirzenius/vmadm!9
-rw-r--r--src/config.rs4
-rw-r--r--src/spec.rs148
-rw-r--r--subplot/vmadm.py9
-rw-r--r--subplot/vmadm.yaml2
-rw-r--r--vmadm.md27
5 files changed, 138 insertions, 52 deletions
diff --git a/src/config.rs b/src/config.rs
index dd6c2d7..a13c989 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -7,7 +7,11 @@ use std::path::{Path, PathBuf};
#[derive(Default, Debug, Deserialize)]
pub struct Configuration {
pub default_base_image: Option<PathBuf>,
+ pub default_image_gib: Option<u64>,
+ pub default_memory_mib: Option<u64>,
+ pub default_cpus: Option<u64>,
pub image_directory: Option<PathBuf>,
+ pub authorized_keys: Option<Vec<PathBuf>>,
}
#[derive(Debug, thiserror::Error)]
diff --git a/src/spec.rs b/src/spec.rs
index c99da6b..d01abcf 100644
--- a/src/spec.rs
+++ b/src/spec.rs
@@ -10,7 +10,7 @@ use std::path::{Path, PathBuf};
#[serde(deny_unknown_fields)]
struct OneVmInputSpecification {
#[serde(default)]
- pub ssh_key_files: Vec<PathBuf>,
+ pub ssh_key_files: Option<Vec<PathBuf>>,
pub rsa_host_key: Option<String>,
pub rsa_host_cert: Option<String>,
@@ -23,9 +23,94 @@ struct OneVmInputSpecification {
pub base: Option<PathBuf>,
pub image: Option<PathBuf>,
- pub image_size_gib: u64,
- pub memory_mib: u64,
- pub cpus: u64,
+ pub image_size_gib: Option<u64>,
+ pub memory_mib: Option<u64>,
+ pub cpus: Option<u64>,
+}
+
+impl OneVmInputSpecification {
+ fn ssh_key_files(
+ &self,
+ config: &Configuration,
+ name: &str,
+ ) -> Result<Vec<PathBuf>, SpecificationError> {
+ get(
+ &self.ssh_key_files,
+ &config.authorized_keys,
+ SpecificationError::NoAuthorizedKeys(name.to_string()),
+ )
+ }
+
+ fn base_image(
+ &self,
+ config: &Configuration,
+ name: &str,
+ ) -> Result<PathBuf, SpecificationError> {
+ get(
+ &self.base,
+ &config.default_base_image,
+ SpecificationError::NoBaseImage(name.to_string()),
+ )
+ }
+
+ fn image(&self, config: &Configuration, name: &str) -> Result<PathBuf, SpecificationError> {
+ let default_image = if let Some(dirname) = &config.image_directory {
+ Some(dirname.join(format!("{}.qcow2", name)))
+ } else {
+ None
+ };
+
+ get(
+ &self.image,
+ &default_image,
+ SpecificationError::NoBaseImage(name.to_string()),
+ )
+ }
+
+ fn image_size_gib(
+ &self,
+ config: &Configuration,
+ name: &str,
+ ) -> Result<u64, SpecificationError> {
+ get(
+ &self.image_size_gib,
+ &config.default_image_gib,
+ SpecificationError::NoBaseImage(name.to_string()),
+ )
+ }
+
+ fn memory_mib(&self, config: &Configuration, name: &str) -> Result<u64, SpecificationError> {
+ get(
+ &self.memory_mib,
+ &config.default_memory_mib,
+ SpecificationError::NoBaseImage(name.to_string()),
+ )
+ }
+
+ fn cpus(&self, config: &Configuration, name: &str) -> Result<u64, SpecificationError> {
+ get(
+ &self.cpus,
+ &config.default_cpus,
+ SpecificationError::NoBaseImage(name.to_string()),
+ )
+ }
+}
+
+fn get<'a, T>(
+ input: &'a Option<T>,
+ default: &'a Option<T>,
+ error: SpecificationError,
+) -> Result<T, SpecificationError>
+where
+ T: Clone,
+{
+ if let Some(input) = input {
+ Ok((*input).clone())
+ } else if let Some(default) = default {
+ Ok((*default).clone())
+ } else {
+ Err(error)
+ }
}
#[derive(Debug)]
@@ -50,11 +135,23 @@ pub struct Specification {
#[derive(Debug, thiserror::Error)]
pub enum SpecificationError {
- #[error("No base image or default base image specified in {0} for {1}")]
- NoBaseImage(PathBuf, String),
+ #[error("No base image or default base image specified for {0}")]
+ NoBaseImage(String),
+
+ #[error("No image filename specified for {0} and no image_directory in configuration")]
+ NoImage(String),
- #[error("No image filename and no image directory specified in configuration")]
- NoImage,
+ #[error("No image size specified for {0} and no default configured")]
+ NoImageSize(String),
+
+ #[error("No memory size specified for {0} and no default configured")]
+ NoMemorySize(String),
+
+ #[error("No CPU count specified for {0} and no default configured")]
+ NoCpuCount(String),
+
+ #[error("No SSH authorized keys specified for {0} and no default configured")]
+ NoAuthorizedKeys(String),
#[error("Failed to read SSH public key file {0}")]
SshKeyRead(PathBuf, #[source] std::io::Error),
@@ -81,7 +178,7 @@ impl Specification {
let mut machines = vec![];
for (name, machine) in input.iter() {
- let spec = Specification::one_machine(config, &filename, &name, &machine)?;
+ let spec = Specification::one_machine(config, &name, &machine)?;
debug!("machine with defaults applied: {:#?}", spec);
machines.push(spec);
}
@@ -91,32 +188,15 @@ impl Specification {
fn one_machine(
config: &Configuration,
- filename: &Path,
name: &str,
input: &OneVmInputSpecification,
) -> Result<Specification, SpecificationError> {
- let base = if let Some(base) = &input.base {
- base.to_path_buf()
- } else if let Some(ref base) = config.default_base_image {
- base.to_path_buf()
- } else {
- return Err(SpecificationError::NoBaseImage(
- filename.to_path_buf(),
- name.to_string(),
- ));
- };
-
- let image = if let Some(image) = &input.image {
- image.to_path_buf()
- } else if let Some(dirname) = &config.image_directory {
- dirname.join(format!("{}.qcow2", name))
- } else {
- return Err(SpecificationError::NoImage.into());
- };
+ let key_filenames = input.ssh_key_files(config, name)?;
+ let ssh_keys = ssh_keys(&key_filenames)?;
let spec = Specification {
name: name.to_string(),
- ssh_keys: ssh_keys(&input.ssh_key_files)?,
+ ssh_keys: ssh_keys,
rsa_host_key: input.rsa_host_key.clone(),
rsa_host_cert: input.rsa_host_cert.clone(),
dsa_host_key: input.dsa_host_key.clone(),
@@ -125,11 +205,11 @@ impl Specification {
ecdsa_host_cert: input.ecdsa_host_cert.clone(),
ed25519_host_key: input.ed25519_host_key.clone(),
ed25519_host_cert: input.ed25519_host_cert.clone(),
- base,
- image,
- image_size_gib: input.image_size_gib,
- memory_mib: input.memory_mib,
- cpus: input.cpus,
+ base: input.base_image(config, name)?,
+ image: input.image(config, name)?,
+ image_size_gib: input.image_size_gib(config, name)?,
+ memory_mib: input.memory_mib(config, name)?,
+ cpus: input.cpus(config, name)?,
};
debug!("specification as with defaults applied: {:#?}", spec);
diff --git a/subplot/vmadm.py b/subplot/vmadm.py
index ae3a3a3..6997c5b 100644
--- a/subplot/vmadm.py
+++ b/subplot/vmadm.py
@@ -63,20 +63,21 @@ def directories_match(ctx, actual=None, expected=None):
assert_eq(edata, adata)
-def create_vm(ctx, filename=None):
+def create_vm(ctx, config=None, filename=None):
runcmd_run = globals()["runcmd_run"]
runcmd_exit_code_is_zero = globals()["runcmd_exit_code_is_zero"]
+ ctx["config"] = config
ctx["spec"] = filename
- runcmd_run(ctx, ["vmadm", "new", filename])
+ runcmd_run(ctx, ["vmadm", "new", "--config", config, filename])
runcmd_exit_code_is_zero(ctx)
-def delete_vm(ctx, filename=None):
+def delete_vm(ctx, config=None, filename=None):
runcmd_run = globals()["runcmd_run"]
runcmd_exit_code_is_zero = globals()["runcmd_exit_code_is_zero"]
- runcmd_run(ctx, ["vmadm", "delete", ctx["spec"]])
+ runcmd_run(ctx, ["vmadm", "delete", "--config", ctx["config"], ctx["spec"]])
runcmd_exit_code_is_zero(ctx)
diff --git a/subplot/vmadm.yaml b/subplot/vmadm.yaml
index ec7d424..21662b6 100644
--- a/subplot/vmadm.yaml
+++ b/subplot/vmadm.yaml
@@ -7,7 +7,7 @@
- when: "I invoke vmadm cloud-init --config {config} {filename} {dirname}"
function: invoke_cloud_init
-- when: "I invoke vmadm new {filename}"
+- when: "I invoke vmadm new --config {config} {filename}"
function: create_vm
cleanup: delete_vm
diff --git a/vmadm.md b/vmadm.md
index d14fc3d..73b4a52 100644
--- a/vmadm.md
+++ b/vmadm.md
@@ -3,14 +3,7 @@
This section has some data files used by scenarios.
~~~{#smoke.yaml .file .yaml}
-smoke:
- ssh_key_files:
- - .ssh/id_rsa.pub
- base: base.qcow2
- image: smoke.qcow2
- image_size_gib: 5
- memory_mib: 2048
- cpus: 1
+smoke: {}
~~~
~~~{#ssh_key .file}
@@ -43,6 +36,16 @@ V4cecTlFJGBtUOUAAAAMbGl3QGV4b2xvYmUxAQIDBAUGBw==
-----END OPENSSH PRIVATE KEY-----
~~~
+~~~{#config.yaml .file .yaml}
+image_directory: images
+default_base_image: base.qcow2
+default_image_gib: 5
+default_memory_mib: 2048
+default_cpus: 1
+authorized_keys:
+ - .ssh/id_rsa.pub
+~~~
+
~~~{#ssh_key_pub .file}
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQChZ6mVuGLBpW7SarFU/Tu6TemquNxatbMUZuTk8RqVtbkvTKeWFZ5h5tntWPHgST8ykYFaIrr8eYuKQkKdBxHW7H8kejTNwRu/rDbRYX5wxTn4jw4RVopGTpxMlGrWeu5CkWPoLAhQtIzzUAnrDGp9sqG6P1G4ohI61wZMFQta9R2uNxXnnes+e2r4Y78GxmlQH/o0ouI8fBnsxRK0IoSfFs2LutO6wjyzR59FdC9TT7wufd5kXMRzxsmPGeXzNcaqvHGxBvRucGFclCkqSRwk3GNEpXZQhlCIoTIoRu0IPAp/430tlx9zJMhhwDlZsOOXRrFYpdWVMSTAAKECLSYx liw@exolobe1
~~~
@@ -63,6 +66,7 @@ correctly.
~~~scenario
given an installed vmadm
given file init.yaml
+given file config.yaml
given file .ssh/id_rsa.pub from init_ssh_key_pub
given file expected/init-test/meta-data from init-metadata
given file expected/init-test/user-data from init-userdata
@@ -70,10 +74,6 @@ when I invoke vmadm cloud-init --config config.yaml init.yaml actual
then directories actual/init-test and expected/init-test are identical
~~~
-~~~{#config.yaml .file. yaml}
-image_directory: images
----
-
~~~{#init.yaml .file .yaml}
init-test:
ssh_key_files:
@@ -128,10 +128,11 @@ can be addressed by name.
given an installed vmadm
given a Debian 10 OpenStack cloud image
given file smoke.yaml
+given file config.yaml
given file .ssh/id_rsa from ssh_key
given file .ssh/id_rsa.pub from ssh_key_pub
given file .ssh/config from ssh_config
-when I invoke vmadm new smoke.yaml
+when I invoke vmadm new --config config.yaml smoke.yaml
when I invoke ssh -F .ssh/config debian@smoke hostname
then stdout contains "smoke"
when I invoke ssh -F .ssh/config debian@smoke df -h /