Skip to content
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
45 changes: 35 additions & 10 deletions nexus/src/external_api/params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use omicron_common::api::external::{
use schemars::JsonSchema;
use serde::{
de::{self, Visitor},
Deserialize, Deserializer, Serialize,
Deserialize, Deserializer, Serialize, Serializer,
};
use std::net::IpAddr;
use uuid::Uuid;
Expand Down Expand Up @@ -404,10 +404,15 @@ pub struct InstanceCreate {
/// User data for instance initialization systems (such as cloud-init).
/// Must be a Base64-encoded string, as specified in RFC 4648 § 4 (+ and /
/// characters with padding). Maximum 32 KiB unencoded data.
// TODO: this should emit `"format": "byte"`, but progenitor doesn't
// understand that yet.
#[schemars(default, with = "String")]
#[serde(default, with = "serde_user_data")]
// While serde happily accepts #[serde(with = "<mod>")] as a shorthand for
// specifing `serialize_with` and `deserialize_with`, schemars requires the
// argument to `with` to be a type rather than merely a path prefix (i.e. a
// mod or type). It's admittedly a bit tricky for schemars to address;
// unlike `serialize` or `deserialize`, `JsonSchema` requires several
// functions working together. It's unfortunate that schemars has this
// built-in incompatibility, exacerbated by its glacial rate of progress
// and immunity to offers of help.
#[serde(default, with = "UserData")]
pub user_data: Vec<u8>,

/// The network interfaces to be created for this instance.
Expand All @@ -419,9 +424,8 @@ pub struct InstanceCreate {
pub disks: Vec<InstanceDiskAttachment>,
}

mod serde_user_data {
use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};

struct UserData;
impl UserData {
pub fn serialize<S>(
data: &Vec<u8>,
serializer: S,
Expand All @@ -440,22 +444,43 @@ mod serde_user_data {
Ok(buf) => {
// if you change this, also update the stress test in crate::cidata
if buf.len() > crate::cidata::MAX_USER_DATA_BYTES {
Err(D::Error::invalid_length(
Err(<D::Error as serde::de::Error>::invalid_length(
buf.len(),
&"less than 32 KiB",
))
} else {
Ok(buf)
}
}
Err(_) => Err(D::Error::invalid_value(
Err(_) => Err(<D::Error as serde::de::Error>::invalid_value(
serde::de::Unexpected::Other("invalid base64 string"),
&"a valid base64 string",
)),
}
}
}

impl JsonSchema for UserData {
fn schema_name() -> String {
"String".to_string()
}

fn json_schema(
_: &mut schemars::gen::SchemaGenerator,
) -> schemars::schema::Schema {
schemars::schema::SchemaObject {
instance_type: Some(schemars::schema::InstanceType::String.into()),
format: Some("byte".to_string()),
..Default::default()
}
.into()
}

fn is_referenceable() -> bool {
false
}
}

/// Migration parameters for an [`Instance`](omicron_common::api::external::Instance)
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
pub struct InstanceMigrate {
Expand Down
3 changes: 2 additions & 1 deletion openapi/nexus.json
Original file line number Diff line number Diff line change
Expand Up @@ -7830,7 +7830,8 @@
"user_data": {
"description": "User data for instance initialization systems (such as cloud-init). Must be a Base64-encoded string, as specified in RFC 4648 § 4 (+ and / characters with padding). Maximum 32 KiB unencoded data.",
"default": "",
"type": "string"
"type": "string",
"format": "byte"
}
},
"required": [
Expand Down