mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
add validation to s9pks
This commit is contained in:
committed by
Aiden McClelland
parent
2c17038b19
commit
08770567d2
@@ -3,9 +3,9 @@
|
||||
set -e
|
||||
shopt -s expand_aliases
|
||||
|
||||
if [ "$0" != "./build-sdk.sh" ]; then
|
||||
if [ "$0" != "./install-sdk.sh" ]; then
|
||||
>&2 echo "Must be run from backend directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cargo install --bin=embassy-sdk --path=. --no-default-features --verbose
|
||||
cargo install --bin=embassy-sdk --path=. --no-default-features
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use std::borrow::Cow;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::net::Ipv4Addr;
|
||||
use std::path::PathBuf;
|
||||
@@ -23,6 +23,17 @@ use crate::{Error, ResultExt, HOST_IP};
|
||||
|
||||
pub const NET_TLD: &str = "embassy";
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref SYSTEM_IMAGES: BTreeSet<ImageId> = {
|
||||
let mut set = BTreeSet::new();
|
||||
|
||||
set.insert("compat".parse().unwrap());
|
||||
set.insert("utils".parse().unwrap());
|
||||
|
||||
set
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct DockerAction {
|
||||
@@ -44,6 +55,32 @@ pub struct DockerAction {
|
||||
pub sigterm_timeout: Option<SerdeDuration>,
|
||||
}
|
||||
impl DockerAction {
|
||||
pub fn validate(
|
||||
&self,
|
||||
volumes: &Volumes,
|
||||
image_ids: &BTreeSet<ImageId>,
|
||||
expected_io: bool,
|
||||
) -> Result<(), color_eyre::eyre::Report> {
|
||||
for (volume, _) in &self.mounts {
|
||||
if !volumes.contains_key(volume) {
|
||||
color_eyre::eyre::bail!("unknown volume: {}", volume);
|
||||
}
|
||||
}
|
||||
if self.system {
|
||||
if !SYSTEM_IMAGES.contains(&self.image) {
|
||||
color_eyre::eyre::bail!("unknown system image: {}", self.image);
|
||||
}
|
||||
} else {
|
||||
if !image_ids.contains(&self.image) {
|
||||
color_eyre::eyre::bail!("image for {} not contained in package", self.image);
|
||||
}
|
||||
}
|
||||
if expected_io && self.io_format.is_none() {
|
||||
color_eyre::eyre::bail!("expected io-format");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip(ctx, input))]
|
||||
pub async fn execute<I: Serialize, O: for<'de> Deserialize<'de>>(
|
||||
&self,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
@@ -14,7 +14,7 @@ use tracing::instrument;
|
||||
use self::docker::DockerAction;
|
||||
use crate::config::{Config, ConfigSpec};
|
||||
use crate::context::RpcContext;
|
||||
use crate::id::{Id, InvalidId};
|
||||
use crate::id::{Id, ImageId, InvalidId};
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::util::serde::{display_serializable, parse_stdin_deserializable, IoFormat};
|
||||
use crate::util::Version;
|
||||
@@ -109,6 +109,18 @@ pub struct Action {
|
||||
pub input_spec: ConfigSpec,
|
||||
}
|
||||
impl Action {
|
||||
#[instrument]
|
||||
pub fn validate(&self, volumes: &Volumes, image_ids: &BTreeSet<ImageId>) -> Result<(), Error> {
|
||||
self.implementation
|
||||
.validate(volumes, image_ids, true)
|
||||
.with_ctx(|_| {
|
||||
(
|
||||
crate::ErrorKind::ValidateS9pk,
|
||||
format!("Action {}", self.name),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument(skip(ctx))]
|
||||
pub async fn execute(
|
||||
&self,
|
||||
@@ -147,6 +159,20 @@ pub enum ActionImplementation {
|
||||
Docker(DockerAction),
|
||||
}
|
||||
impl ActionImplementation {
|
||||
#[instrument]
|
||||
pub fn validate(
|
||||
&self,
|
||||
volumes: &Volumes,
|
||||
image_ids: &BTreeSet<ImageId>,
|
||||
expected_io: bool,
|
||||
) -> Result<(), color_eyre::eyre::Report> {
|
||||
match self {
|
||||
ActionImplementation::Docker(action) => {
|
||||
action.validate(volumes, image_ids, expected_io)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(skip(ctx, input))]
|
||||
pub async fn execute<I: Serialize, O: for<'de> Deserialize<'de>>(
|
||||
&self,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::path::Path;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
@@ -15,6 +15,7 @@ use self::target::PackageBackupInfo;
|
||||
use crate::action::{ActionImplementation, NoOutput};
|
||||
use crate::context::RpcContext;
|
||||
use crate::dependencies::reconfigure_dependents_with_live_pointers;
|
||||
use crate::id::ImageId;
|
||||
use crate::install::PKG_ARCHIVE_DIR;
|
||||
use crate::net::interface::{InterfaceId, Interfaces};
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
@@ -67,6 +68,16 @@ pub struct BackupActions {
|
||||
pub restore: ActionImplementation,
|
||||
}
|
||||
impl BackupActions {
|
||||
pub fn validate(&self, volumes: &Volumes, image_ids: &BTreeSet<ImageId>) -> Result<(), Error> {
|
||||
self.create
|
||||
.validate(volumes, image_ids, false)
|
||||
.with_ctx(|_| (crate::ErrorKind::ValidateS9pk, "Backup Create"))?;
|
||||
self.restore
|
||||
.validate(volumes, image_ids, false)
|
||||
.with_ctx(|_| (crate::ErrorKind::ValidateS9pk, "Backup Restore"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip(ctx))]
|
||||
pub async fn create(
|
||||
&self,
|
||||
|
||||
@@ -10,11 +10,12 @@ use super::{Config, ConfigSpec};
|
||||
use crate::action::ActionImplementation;
|
||||
use crate::context::RpcContext;
|
||||
use crate::dependencies::Dependencies;
|
||||
use crate::id::ImageId;
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::status::health_check::HealthCheckId;
|
||||
use crate::util::Version;
|
||||
use crate::volume::Volumes;
|
||||
use crate::Error;
|
||||
use crate::{Error, ResultExt};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
@@ -29,6 +30,16 @@ pub struct ConfigActions {
|
||||
pub set: ActionImplementation,
|
||||
}
|
||||
impl ConfigActions {
|
||||
#[instrument]
|
||||
pub fn validate(&self, volumes: &Volumes, image_ids: &BTreeSet<ImageId>) -> Result<(), Error> {
|
||||
self.get
|
||||
.validate(volumes, image_ids, true)
|
||||
.with_ctx(|_| (crate::ErrorKind::ValidateS9pk, "Config Get"))?;
|
||||
self.set
|
||||
.validate(volumes, image_ids, true)
|
||||
.with_ctx(|_| (crate::ErrorKind::ValidateS9pk, "Config Set"))?;
|
||||
Ok(())
|
||||
}
|
||||
#[instrument(skip(ctx))]
|
||||
pub async fn get(
|
||||
&self,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::borrow::{Borrow, Cow};
|
||||
use std::fmt::Debug;
|
||||
use std::str::FromStr;
|
||||
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
@@ -158,6 +159,12 @@ impl<S: AsRef<str>> ImageId<S> {
|
||||
)
|
||||
}
|
||||
}
|
||||
impl FromStr for ImageId {
|
||||
type Err = InvalidId;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(ImageId(Id::try_from(s.to_owned())?))
|
||||
}
|
||||
}
|
||||
impl<'de, S> Deserialize<'de> for ImageId<S>
|
||||
where
|
||||
S: AsRef<str>,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use color_eyre::eyre::eyre;
|
||||
use emver::VersionRange;
|
||||
use futures::{Future, FutureExt};
|
||||
@@ -8,10 +10,11 @@ use tracing::instrument;
|
||||
|
||||
use crate::action::ActionImplementation;
|
||||
use crate::context::RpcContext;
|
||||
use crate::id::ImageId;
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::util::Version;
|
||||
use crate::volume::Volumes;
|
||||
use crate::Error;
|
||||
use crate::{Error, ResultExt};
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, HasModel)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
@@ -20,6 +23,27 @@ pub struct Migrations {
|
||||
pub to: IndexMap<VersionRange, ActionImplementation>,
|
||||
}
|
||||
impl Migrations {
|
||||
#[instrument]
|
||||
pub fn validate(&self, volumes: &Volumes, image_ids: &BTreeSet<ImageId>) -> Result<(), Error> {
|
||||
for (version, migration) in &self.from {
|
||||
migration.validate(volumes, image_ids, true).with_ctx(|_| {
|
||||
(
|
||||
crate::ErrorKind::ValidateS9pk,
|
||||
format!("Migration from {}", version),
|
||||
)
|
||||
})?;
|
||||
}
|
||||
for (version, migration) in &self.to {
|
||||
migration.validate(volumes, image_ids, true).with_ctx(|_| {
|
||||
(
|
||||
crate::ErrorKind::ValidateS9pk,
|
||||
format!("Migration to {}", version),
|
||||
)
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip(ctx))]
|
||||
pub fn from<'a>(
|
||||
&'a self,
|
||||
|
||||
@@ -14,12 +14,24 @@ use crate::db::model::{InterfaceAddressMap, InterfaceAddresses};
|
||||
use crate::id::Id;
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::util::serde::Port;
|
||||
use crate::Error;
|
||||
use crate::{Error, ResultExt};
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct Interfaces(pub BTreeMap<InterfaceId, Interface>); // TODO
|
||||
impl Interfaces {
|
||||
#[instrument]
|
||||
pub fn validate(&self) -> Result<(), Error> {
|
||||
for (_, interface) in &self.0 {
|
||||
interface.validate().with_ctx(|_| {
|
||||
(
|
||||
crate::ErrorKind::ValidateS9pk,
|
||||
format!("Interface {}", interface.name),
|
||||
)
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
#[instrument(skip(secrets))]
|
||||
pub async fn install<Ex>(
|
||||
&self,
|
||||
@@ -152,6 +164,21 @@ pub struct Interface {
|
||||
pub ui: bool,
|
||||
pub protocols: IndexSet<String>,
|
||||
}
|
||||
impl Interface {
|
||||
#[instrument]
|
||||
pub fn validate(&self) -> Result<(), color_eyre::eyre::Report> {
|
||||
if self.tor_config.is_some() && !self.protocols.contains("tcp") {
|
||||
color_eyre::eyre::bail!("must support tcp to set up a tor hidden service");
|
||||
}
|
||||
if self.lan_config.is_some() && !self.protocols.contains("http") {
|
||||
color_eyre::eyre::bail!("must support http to set up a lan service");
|
||||
}
|
||||
if self.ui && !(self.protocols.contains("http") || self.protocols.contains("https")) {
|
||||
color_eyre::eyre::bail!("must support http or https to serve a ui");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
|
||||
@@ -1,19 +1,25 @@
|
||||
use std::collections::BTreeSet;
|
||||
use std::io::SeekFrom;
|
||||
use std::path::Path;
|
||||
use std::pin::Pin;
|
||||
use std::str::FromStr;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use color_eyre::eyre::eyre;
|
||||
use digest::Output;
|
||||
use ed25519_dalek::PublicKey;
|
||||
use futures::TryStreamExt;
|
||||
use sha2::{Digest, Sha512};
|
||||
use tokio::fs::File;
|
||||
use tokio::io::{AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt, ReadBuf, Take};
|
||||
use tracing::instrument;
|
||||
|
||||
use super::header::{FileSection, Header, TableOfContents};
|
||||
use super::manifest::Manifest;
|
||||
use super::manifest::{Manifest, PackageId};
|
||||
use super::SIG_CONTEXT;
|
||||
use crate::id::ImageId;
|
||||
use crate::install::progress::InstallProgressTracker;
|
||||
use crate::util::Version;
|
||||
use crate::{Error, ResultExt};
|
||||
|
||||
#[pin_project::pin_project]
|
||||
@@ -45,6 +51,66 @@ impl<'a, R: AsyncRead + AsyncSeek + Unpin> AsyncRead for ReadHandle<'a, R> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ImageTag {
|
||||
pub package_id: PackageId,
|
||||
pub image_id: ImageId,
|
||||
pub version: Version,
|
||||
}
|
||||
impl ImageTag {
|
||||
#[instrument]
|
||||
pub fn validate(&self, id: &PackageId, version: &Version) -> Result<(), Error> {
|
||||
if id != &self.package_id {
|
||||
return Err(Error::new(
|
||||
eyre!(
|
||||
"Contains image for incorrect package: id {}",
|
||||
self.package_id,
|
||||
),
|
||||
crate::ErrorKind::ValidateS9pk,
|
||||
));
|
||||
}
|
||||
if id != &self.package_id {
|
||||
return Err(Error::new(
|
||||
eyre!(
|
||||
"Contains image with incorrect version: expected {} received {}",
|
||||
version,
|
||||
self.version,
|
||||
),
|
||||
crate::ErrorKind::ValidateS9pk,
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl FromStr for ImageTag {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let rest = s.strip_prefix("start9/").ok_or_else(|| {
|
||||
Error::new(
|
||||
eyre!("Invalid image tag prefix: expected start9/"),
|
||||
crate::ErrorKind::ValidateS9pk,
|
||||
)
|
||||
})?;
|
||||
let (package, rest) = rest.split_once("/").ok_or_else(|| {
|
||||
Error::new(
|
||||
eyre!("Image tag missing image id"),
|
||||
crate::ErrorKind::ValidateS9pk,
|
||||
)
|
||||
})?;
|
||||
let (image, version) = rest.split_once(":").ok_or_else(|| {
|
||||
Error::new(
|
||||
eyre!("Image tag missing version"),
|
||||
crate::ErrorKind::ValidateS9pk,
|
||||
)
|
||||
})?;
|
||||
Ok(ImageTag {
|
||||
package_id: package.parse()?,
|
||||
image_id: image.parse()?,
|
||||
version: version.parse()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct S9pkReader<R: AsyncRead + AsyncSeek + Unpin = File> {
|
||||
hash: Option<Output<Sha512>>,
|
||||
hash_string: Option<String>,
|
||||
@@ -71,8 +137,66 @@ impl<R: AsyncRead + AsyncSeek + Unpin> S9pkReader<InstallProgressTracker<R>> {
|
||||
impl<R: AsyncRead + AsyncSeek + Unpin> S9pkReader<R> {
|
||||
#[instrument(skip(self))]
|
||||
pub async fn validate(&mut self) -> Result<(), Error> {
|
||||
let image_tags = self.image_tags().await?;
|
||||
let man = self.manifest().await?;
|
||||
let validated_image_ids = image_tags
|
||||
.into_iter()
|
||||
.map(|i| i.validate(&man.id, &man.version).map(|_| i.image_id))
|
||||
.collect::<Result<BTreeSet<ImageId>, _>>()?;
|
||||
man.actions
|
||||
.0
|
||||
.iter()
|
||||
.map(|(_, action)| action.validate(&man.volumes, &validated_image_ids))
|
||||
.collect::<Result<(), Error>>()?;
|
||||
man.backup.validate(&man.volumes, &validated_image_ids)?;
|
||||
if let Some(cfg) = &man.config {
|
||||
cfg.validate(&man.volumes, &validated_image_ids)?;
|
||||
}
|
||||
man.health_checks
|
||||
.validate(&man.volumes, &validated_image_ids)?;
|
||||
man.interfaces.validate()?;
|
||||
man.main
|
||||
.validate(&man.volumes, &validated_image_ids, false)
|
||||
.with_ctx(|_| (crate::ErrorKind::ValidateS9pk, "Main"))?;
|
||||
man.migrations
|
||||
.validate(&man.volumes, &validated_image_ids)?;
|
||||
if let Some(props) = &man.properties {
|
||||
props
|
||||
.validate(&man.volumes, &validated_image_ids, true)
|
||||
.with_ctx(|_| (crate::ErrorKind::ValidateS9pk, "Properties"))?;
|
||||
}
|
||||
man.volumes.validate(&man.interfaces)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
#[instrument(skip(self))]
|
||||
pub async fn image_tags(&mut self) -> Result<Vec<ImageTag>, Error> {
|
||||
let mut tar = tokio_tar::Archive::new(self.docker_images().await?);
|
||||
let mut entries = tar.entries()?;
|
||||
while let Some(mut entry) = entries.try_next().await? {
|
||||
if &*entry.path()? != Path::new("manifest.json") {
|
||||
continue;
|
||||
}
|
||||
let mut buf = Vec::with_capacity(entry.header().size()? as usize);
|
||||
entry.read_to_end(&mut buf).await?;
|
||||
#[derive(serde::Deserialize)]
|
||||
struct ManEntry {
|
||||
#[serde(rename = "RepoTags")]
|
||||
tags: Vec<String>,
|
||||
}
|
||||
let man_entries = serde_json::from_slice::<Vec<ManEntry>>(&buf)
|
||||
.with_ctx(|_| (crate::ErrorKind::Deserialization, "manifest.json"))?;
|
||||
return man_entries
|
||||
.iter()
|
||||
.flat_map(|e| &e.tags)
|
||||
.map(|t| t.parse())
|
||||
.collect();
|
||||
}
|
||||
Err(Error::new(
|
||||
eyre!("image.tar missing manifest.json"),
|
||||
crate::ErrorKind::ParseS9pk,
|
||||
))
|
||||
}
|
||||
#[instrument(skip(rdr))]
|
||||
pub async fn from_reader(mut rdr: R, check_sig: bool) -> Result<Self, Error> {
|
||||
let header = Header::deserialize(&mut rdr).await?;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::path::Path;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
@@ -7,12 +7,12 @@ use tracing::instrument;
|
||||
|
||||
use crate::action::{ActionImplementation, NoOutput};
|
||||
use crate::context::RpcContext;
|
||||
use crate::id::Id;
|
||||
use crate::id::{Id, ImageId};
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::util::serde::Duration;
|
||||
use crate::util::Version;
|
||||
use crate::volume::Volumes;
|
||||
use crate::Error;
|
||||
use crate::{Error, ResultExt};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
|
||||
pub struct HealthCheckId<S: AsRef<str> = String>(Id<S>);
|
||||
@@ -47,6 +47,21 @@ impl<S: AsRef<str>> AsRef<Path> for HealthCheckId<S> {
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct HealthChecks(pub BTreeMap<HealthCheckId, HealthCheck>);
|
||||
impl HealthChecks {
|
||||
#[instrument]
|
||||
pub fn validate(&self, volumes: &Volumes, image_ids: &BTreeSet<ImageId>) -> Result<(), Error> {
|
||||
for (_, check) in &self.0 {
|
||||
check
|
||||
.implementation
|
||||
.validate(&volumes, image_ids, false)
|
||||
.with_ctx(|_| {
|
||||
(
|
||||
crate::ErrorKind::ValidateS9pk,
|
||||
format!("Health Check {}", check.name),
|
||||
)
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub async fn check_all(
|
||||
&self,
|
||||
ctx: &RpcContext,
|
||||
|
||||
@@ -3,15 +3,17 @@ use std::collections::BTreeMap;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use color_eyre::eyre::eyre;
|
||||
use patch_db::{HasModel, Map, MapModel};
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::context::RpcContext;
|
||||
use crate::id::{Id, IdUnchecked};
|
||||
use crate::net::interface::InterfaceId;
|
||||
use crate::net::interface::{InterfaceId, Interfaces};
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::util::Version;
|
||||
use crate::Error;
|
||||
use crate::{Error, ResultExt};
|
||||
|
||||
pub const PKG_VOLUME_DIR: &'static str = "package-data/volumes";
|
||||
pub const BACKUP_DIR: &'static str = "/media/embassy-os/backups";
|
||||
@@ -75,6 +77,16 @@ impl<S: AsRef<str>> Serialize for VolumeId<S> {
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
pub struct Volumes(BTreeMap<VolumeId, Volume>);
|
||||
impl Volumes {
|
||||
#[instrument]
|
||||
pub fn validate(&self, interfaces: &Interfaces) -> Result<(), Error> {
|
||||
for (id, volume) in &self.0 {
|
||||
volume
|
||||
.validate(interfaces)
|
||||
.with_ctx(|_| (crate::ErrorKind::ValidateS9pk, format!("Volume {}", id)))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
#[instrument(skip(ctx))]
|
||||
pub async fn install(
|
||||
&self,
|
||||
ctx: &RpcContext,
|
||||
@@ -180,6 +192,18 @@ pub enum Volume {
|
||||
Backup { readonly: bool },
|
||||
}
|
||||
impl Volume {
|
||||
#[instrument]
|
||||
pub fn validate(&self, interfaces: &Interfaces) -> Result<(), color_eyre::eyre::Report> {
|
||||
match self {
|
||||
Volume::Certificate { interface_id } => {
|
||||
if !interfaces.0.contains_key(interface_id) {
|
||||
color_eyre::eyre::bail!("unknown interface: {}", interface_id);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub async fn install(
|
||||
&self,
|
||||
ctx: &RpcContext,
|
||||
|
||||
Reference in New Issue
Block a user