mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-31 04:23:40 +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
|
set -e
|
||||||
shopt -s expand_aliases
|
shopt -s expand_aliases
|
||||||
|
|
||||||
if [ "$0" != "./build-sdk.sh" ]; then
|
if [ "$0" != "./install-sdk.sh" ]; then
|
||||||
>&2 echo "Must be run from backend directory"
|
>&2 echo "Must be run from backend directory"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
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::borrow::Cow;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
use std::ffi::{OsStr, OsString};
|
use std::ffi::{OsStr, OsString};
|
||||||
use std::net::Ipv4Addr;
|
use std::net::Ipv4Addr;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
@@ -23,6 +23,17 @@ use crate::{Error, ResultExt, HOST_IP};
|
|||||||
|
|
||||||
pub const NET_TLD: &str = "embassy";
|
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)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub struct DockerAction {
|
pub struct DockerAction {
|
||||||
@@ -44,6 +55,32 @@ pub struct DockerAction {
|
|||||||
pub sigterm_timeout: Option<SerdeDuration>,
|
pub sigterm_timeout: Option<SerdeDuration>,
|
||||||
}
|
}
|
||||||
impl DockerAction {
|
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))]
|
#[instrument(skip(ctx, input))]
|
||||||
pub async fn execute<I: Serialize, O: for<'de> Deserialize<'de>>(
|
pub async fn execute<I: Serialize, O: for<'de> Deserialize<'de>>(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@@ -14,7 +14,7 @@ use tracing::instrument;
|
|||||||
use self::docker::DockerAction;
|
use self::docker::DockerAction;
|
||||||
use crate::config::{Config, ConfigSpec};
|
use crate::config::{Config, ConfigSpec};
|
||||||
use crate::context::RpcContext;
|
use crate::context::RpcContext;
|
||||||
use crate::id::{Id, InvalidId};
|
use crate::id::{Id, ImageId, InvalidId};
|
||||||
use crate::s9pk::manifest::PackageId;
|
use crate::s9pk::manifest::PackageId;
|
||||||
use crate::util::serde::{display_serializable, parse_stdin_deserializable, IoFormat};
|
use crate::util::serde::{display_serializable, parse_stdin_deserializable, IoFormat};
|
||||||
use crate::util::Version;
|
use crate::util::Version;
|
||||||
@@ -109,6 +109,18 @@ pub struct Action {
|
|||||||
pub input_spec: ConfigSpec,
|
pub input_spec: ConfigSpec,
|
||||||
}
|
}
|
||||||
impl Action {
|
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))]
|
#[instrument(skip(ctx))]
|
||||||
pub async fn execute(
|
pub async fn execute(
|
||||||
&self,
|
&self,
|
||||||
@@ -147,6 +159,20 @@ pub enum ActionImplementation {
|
|||||||
Docker(DockerAction),
|
Docker(DockerAction),
|
||||||
}
|
}
|
||||||
impl ActionImplementation {
|
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))]
|
#[instrument(skip(ctx, input))]
|
||||||
pub async fn execute<I: Serialize, O: for<'de> Deserialize<'de>>(
|
pub async fn execute<I: Serialize, O: for<'de> Deserialize<'de>>(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
@@ -15,6 +15,7 @@ use self::target::PackageBackupInfo;
|
|||||||
use crate::action::{ActionImplementation, NoOutput};
|
use crate::action::{ActionImplementation, NoOutput};
|
||||||
use crate::context::RpcContext;
|
use crate::context::RpcContext;
|
||||||
use crate::dependencies::reconfigure_dependents_with_live_pointers;
|
use crate::dependencies::reconfigure_dependents_with_live_pointers;
|
||||||
|
use crate::id::ImageId;
|
||||||
use crate::install::PKG_ARCHIVE_DIR;
|
use crate::install::PKG_ARCHIVE_DIR;
|
||||||
use crate::net::interface::{InterfaceId, Interfaces};
|
use crate::net::interface::{InterfaceId, Interfaces};
|
||||||
use crate::s9pk::manifest::PackageId;
|
use crate::s9pk::manifest::PackageId;
|
||||||
@@ -67,6 +68,16 @@ pub struct BackupActions {
|
|||||||
pub restore: ActionImplementation,
|
pub restore: ActionImplementation,
|
||||||
}
|
}
|
||||||
impl BackupActions {
|
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))]
|
#[instrument(skip(ctx))]
|
||||||
pub async fn create(
|
pub async fn create(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
@@ -10,11 +10,12 @@ use super::{Config, ConfigSpec};
|
|||||||
use crate::action::ActionImplementation;
|
use crate::action::ActionImplementation;
|
||||||
use crate::context::RpcContext;
|
use crate::context::RpcContext;
|
||||||
use crate::dependencies::Dependencies;
|
use crate::dependencies::Dependencies;
|
||||||
|
use crate::id::ImageId;
|
||||||
use crate::s9pk::manifest::PackageId;
|
use crate::s9pk::manifest::PackageId;
|
||||||
use crate::status::health_check::HealthCheckId;
|
use crate::status::health_check::HealthCheckId;
|
||||||
use crate::util::Version;
|
use crate::util::Version;
|
||||||
use crate::volume::Volumes;
|
use crate::volume::Volumes;
|
||||||
use crate::Error;
|
use crate::{Error, ResultExt};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
@@ -29,6 +30,16 @@ pub struct ConfigActions {
|
|||||||
pub set: ActionImplementation,
|
pub set: ActionImplementation,
|
||||||
}
|
}
|
||||||
impl ConfigActions {
|
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))]
|
#[instrument(skip(ctx))]
|
||||||
pub async fn get(
|
pub async fn get(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use std::borrow::{Borrow, Cow};
|
use std::borrow::{Borrow, Cow};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
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>
|
impl<'de, S> Deserialize<'de> for ImageId<S>
|
||||||
where
|
where
|
||||||
S: AsRef<str>,
|
S: AsRef<str>,
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
use emver::VersionRange;
|
use emver::VersionRange;
|
||||||
use futures::{Future, FutureExt};
|
use futures::{Future, FutureExt};
|
||||||
@@ -8,10 +10,11 @@ use tracing::instrument;
|
|||||||
|
|
||||||
use crate::action::ActionImplementation;
|
use crate::action::ActionImplementation;
|
||||||
use crate::context::RpcContext;
|
use crate::context::RpcContext;
|
||||||
|
use crate::id::ImageId;
|
||||||
use crate::s9pk::manifest::PackageId;
|
use crate::s9pk::manifest::PackageId;
|
||||||
use crate::util::Version;
|
use crate::util::Version;
|
||||||
use crate::volume::Volumes;
|
use crate::volume::Volumes;
|
||||||
use crate::Error;
|
use crate::{Error, ResultExt};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, HasModel)]
|
#[derive(Clone, Debug, Default, Deserialize, Serialize, HasModel)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
@@ -20,6 +23,27 @@ pub struct Migrations {
|
|||||||
pub to: IndexMap<VersionRange, ActionImplementation>,
|
pub to: IndexMap<VersionRange, ActionImplementation>,
|
||||||
}
|
}
|
||||||
impl Migrations {
|
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))]
|
#[instrument(skip(ctx))]
|
||||||
pub fn from<'a>(
|
pub fn from<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
|
|||||||
@@ -14,12 +14,24 @@ use crate::db::model::{InterfaceAddressMap, InterfaceAddresses};
|
|||||||
use crate::id::Id;
|
use crate::id::Id;
|
||||||
use crate::s9pk::manifest::PackageId;
|
use crate::s9pk::manifest::PackageId;
|
||||||
use crate::util::serde::Port;
|
use crate::util::serde::Port;
|
||||||
use crate::Error;
|
use crate::{Error, ResultExt};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub struct Interfaces(pub BTreeMap<InterfaceId, Interface>); // TODO
|
pub struct Interfaces(pub BTreeMap<InterfaceId, Interface>); // TODO
|
||||||
impl Interfaces {
|
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))]
|
#[instrument(skip(secrets))]
|
||||||
pub async fn install<Ex>(
|
pub async fn install<Ex>(
|
||||||
&self,
|
&self,
|
||||||
@@ -152,6 +164,21 @@ pub struct Interface {
|
|||||||
pub ui: bool,
|
pub ui: bool,
|
||||||
pub protocols: IndexSet<String>,
|
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)]
|
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
|||||||
@@ -1,19 +1,25 @@
|
|||||||
|
use std::collections::BTreeSet;
|
||||||
use std::io::SeekFrom;
|
use std::io::SeekFrom;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
use std::str::FromStr;
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
|
use color_eyre::eyre::eyre;
|
||||||
use digest::Output;
|
use digest::Output;
|
||||||
use ed25519_dalek::PublicKey;
|
use ed25519_dalek::PublicKey;
|
||||||
|
use futures::TryStreamExt;
|
||||||
use sha2::{Digest, Sha512};
|
use sha2::{Digest, Sha512};
|
||||||
use tokio::fs::File;
|
use tokio::fs::File;
|
||||||
use tokio::io::{AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt, ReadBuf, Take};
|
use tokio::io::{AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt, ReadBuf, Take};
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use super::header::{FileSection, Header, TableOfContents};
|
use super::header::{FileSection, Header, TableOfContents};
|
||||||
use super::manifest::Manifest;
|
use super::manifest::{Manifest, PackageId};
|
||||||
use super::SIG_CONTEXT;
|
use super::SIG_CONTEXT;
|
||||||
|
use crate::id::ImageId;
|
||||||
use crate::install::progress::InstallProgressTracker;
|
use crate::install::progress::InstallProgressTracker;
|
||||||
|
use crate::util::Version;
|
||||||
use crate::{Error, ResultExt};
|
use crate::{Error, ResultExt};
|
||||||
|
|
||||||
#[pin_project::pin_project]
|
#[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> {
|
pub struct S9pkReader<R: AsyncRead + AsyncSeek + Unpin = File> {
|
||||||
hash: Option<Output<Sha512>>,
|
hash: Option<Output<Sha512>>,
|
||||||
hash_string: Option<String>,
|
hash_string: Option<String>,
|
||||||
@@ -71,8 +137,66 @@ impl<R: AsyncRead + AsyncSeek + Unpin> S9pkReader<InstallProgressTracker<R>> {
|
|||||||
impl<R: AsyncRead + AsyncSeek + Unpin> S9pkReader<R> {
|
impl<R: AsyncRead + AsyncSeek + Unpin> S9pkReader<R> {
|
||||||
#[instrument(skip(self))]
|
#[instrument(skip(self))]
|
||||||
pub async fn validate(&mut self) -> Result<(), Error> {
|
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(())
|
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))]
|
#[instrument(skip(rdr))]
|
||||||
pub async fn from_reader(mut rdr: R, check_sig: bool) -> Result<Self, Error> {
|
pub async fn from_reader(mut rdr: R, check_sig: bool) -> Result<Self, Error> {
|
||||||
let header = Header::deserialize(&mut rdr).await?;
|
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 std::path::Path;
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
@@ -7,12 +7,12 @@ use tracing::instrument;
|
|||||||
|
|
||||||
use crate::action::{ActionImplementation, NoOutput};
|
use crate::action::{ActionImplementation, NoOutput};
|
||||||
use crate::context::RpcContext;
|
use crate::context::RpcContext;
|
||||||
use crate::id::Id;
|
use crate::id::{Id, ImageId};
|
||||||
use crate::s9pk::manifest::PackageId;
|
use crate::s9pk::manifest::PackageId;
|
||||||
use crate::util::serde::Duration;
|
use crate::util::serde::Duration;
|
||||||
use crate::util::Version;
|
use crate::util::Version;
|
||||||
use crate::volume::Volumes;
|
use crate::volume::Volumes;
|
||||||
use crate::Error;
|
use crate::{Error, ResultExt};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
|
||||||
pub struct HealthCheckId<S: AsRef<str> = String>(Id<S>);
|
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)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
pub struct HealthChecks(pub BTreeMap<HealthCheckId, HealthCheck>);
|
pub struct HealthChecks(pub BTreeMap<HealthCheckId, HealthCheck>);
|
||||||
impl HealthChecks {
|
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(
|
pub async fn check_all(
|
||||||
&self,
|
&self,
|
||||||
ctx: &RpcContext,
|
ctx: &RpcContext,
|
||||||
|
|||||||
@@ -3,15 +3,17 @@ use std::collections::BTreeMap;
|
|||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use color_eyre::eyre::eyre;
|
||||||
use patch_db::{HasModel, Map, MapModel};
|
use patch_db::{HasModel, Map, MapModel};
|
||||||
use serde::{Deserialize, Deserializer, Serialize};
|
use serde::{Deserialize, Deserializer, Serialize};
|
||||||
|
use tracing::instrument;
|
||||||
|
|
||||||
use crate::context::RpcContext;
|
use crate::context::RpcContext;
|
||||||
use crate::id::{Id, IdUnchecked};
|
use crate::id::{Id, IdUnchecked};
|
||||||
use crate::net::interface::InterfaceId;
|
use crate::net::interface::{InterfaceId, Interfaces};
|
||||||
use crate::s9pk::manifest::PackageId;
|
use crate::s9pk::manifest::PackageId;
|
||||||
use crate::util::Version;
|
use crate::util::Version;
|
||||||
use crate::Error;
|
use crate::{Error, ResultExt};
|
||||||
|
|
||||||
pub const PKG_VOLUME_DIR: &'static str = "package-data/volumes";
|
pub const PKG_VOLUME_DIR: &'static str = "package-data/volumes";
|
||||||
pub const BACKUP_DIR: &'static str = "/media/embassy-os/backups";
|
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)]
|
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||||
pub struct Volumes(BTreeMap<VolumeId, Volume>);
|
pub struct Volumes(BTreeMap<VolumeId, Volume>);
|
||||||
impl Volumes {
|
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(
|
pub async fn install(
|
||||||
&self,
|
&self,
|
||||||
ctx: &RpcContext,
|
ctx: &RpcContext,
|
||||||
@@ -180,6 +192,18 @@ pub enum Volume {
|
|||||||
Backup { readonly: bool },
|
Backup { readonly: bool },
|
||||||
}
|
}
|
||||||
impl Volume {
|
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(
|
pub async fn install(
|
||||||
&self,
|
&self,
|
||||||
ctx: &RpcContext,
|
ctx: &RpcContext,
|
||||||
|
|||||||
Reference in New Issue
Block a user