Compose selected State Anchor paths over prepared roots
This commit is contained in:
parent
71b0266cf2
commit
4dd2b150d2
1 changed files with 1575 additions and 163 deletions
|
|
@ -23,7 +23,8 @@ use rustix::fs::{
|
|||
use sha2::{Digest, Sha256};
|
||||
|
||||
use crate::layer::{
|
||||
IdMap, LayoutSource, POLICY_NAME, PreparationLimits, apply_layout, remove_children,
|
||||
IdMap, LayoutSource, POLICY_NAME, PreparationLimits, PreparedDestination,
|
||||
PreparedDestinationKind, apply_layout, materialize_destinations, remove_children,
|
||||
verify_layout,
|
||||
};
|
||||
use crate::transport::TransportedImage;
|
||||
|
|
@ -38,6 +39,7 @@ pub struct PrepareRequest {
|
|||
pub source_identity: Vec<u8>,
|
||||
pub selected_manifest_digest: [u8; 32],
|
||||
pub id_map: IdMap,
|
||||
pub destinations: Vec<PreparedDestination>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
|
|
@ -115,6 +117,22 @@ impl PreparedLowerStore {
|
|||
self
|
||||
}
|
||||
|
||||
/// Opens the exact published artifact beneath the retained prepared-lower root.
|
||||
///
|
||||
/// # Errors
|
||||
/// Refuses a non-canonical artifact record, catalog drift, or physical identity drift.
|
||||
pub fn open_artifact_authority(&self, artifact: &PreparedArtifact) -> Result<File> {
|
||||
let catalog_artifact = self
|
||||
.catalog_artifact(artifact.prepared_artifact_id)?
|
||||
.context("prepared artifact is absent from the catalog")?;
|
||||
if catalog_artifact != *artifact {
|
||||
bail!("prepared artifact does not match the catalog");
|
||||
}
|
||||
self.require_artifact_identity(artifact)?;
|
||||
self.open_relative(&artifact_path(artifact.prepared_artifact_id))?
|
||||
.context("prepared artifact path is absent")
|
||||
}
|
||||
|
||||
/// Materializes and atomically publishes one verified immutable prepared lower.
|
||||
///
|
||||
/// # Errors
|
||||
|
|
@ -134,6 +152,7 @@ impl PreparedLowerStore {
|
|||
operation_id: PrepareOperationId,
|
||||
image: &TransportedImage,
|
||||
id_map: IdMap,
|
||||
destinations: Vec<PreparedDestination>,
|
||||
) -> Result<PreparedArtifact> {
|
||||
let layout = image
|
||||
.retained_layout()
|
||||
|
|
@ -144,6 +163,7 @@ impl PreparedLowerStore {
|
|||
source_identity: image.source_identity.clone(),
|
||||
selected_manifest_digest: image.selected_manifest_digest,
|
||||
id_map,
|
||||
destinations,
|
||||
};
|
||||
self.prepare_from_source(&request, &LayoutSource::Retained(layout.root()))
|
||||
}
|
||||
|
|
@ -194,6 +214,7 @@ impl PreparedLowerStore {
|
|||
return Err(error.into());
|
||||
}
|
||||
if let Err(error) = apply_layout(layout, &stage, &verified, &request.id_map, self.limits)
|
||||
.and_then(|()| materialize_destinations(&stage, &request.destinations, &request.id_map))
|
||||
.and_then(|()| sync_tree(&stage))
|
||||
{
|
||||
if self
|
||||
|
|
@ -494,7 +515,13 @@ impl Identities {
|
|||
fn new(request: &PrepareRequest) -> Result<Self> {
|
||||
let source = SourceIdentity::new(request.source_identity.clone());
|
||||
let manifest = SelectedManifestDigest::new(request.selected_manifest_digest);
|
||||
let policy = PolicyId::new(hash_parts(&[b"lazaret-policy-v1", POLICY_NAME.as_bytes()]));
|
||||
let destinations = canonical_destinations(&request.destinations)?;
|
||||
let destination_bytes = destination_policy_bytes(&destinations);
|
||||
let policy = PolicyId::new(hash_parts(&[
|
||||
b"lazaret-policy-v2",
|
||||
POLICY_NAME.as_bytes(),
|
||||
&destination_bytes,
|
||||
]));
|
||||
let id_map_bytes = postcard::to_stdvec(&request.id_map)?;
|
||||
let id_map = IdMapDigest::new(hash_parts(&[b"lazaret-id-map-v1", &id_map_bytes]));
|
||||
let artifact_id = PreparedArtifactId::new(hash_parts(&[
|
||||
|
|
@ -555,6 +582,42 @@ impl Identities {
|
|||
}
|
||||
}
|
||||
|
||||
fn canonical_destinations(
|
||||
destinations: &[PreparedDestination],
|
||||
) -> Result<Vec<PreparedDestination>> {
|
||||
let mut destinations = destinations.to_vec();
|
||||
for destination in &destinations {
|
||||
let mut components = destination.path.components();
|
||||
if components.next() != Some(std::path::Component::RootDir)
|
||||
|| components.any(|component| !matches!(component, std::path::Component::Normal(_)))
|
||||
{
|
||||
bail!("prepared destination is not an absolute normalized path");
|
||||
}
|
||||
}
|
||||
destinations.sort();
|
||||
destinations.dedup();
|
||||
for pair in destinations.windows(2) {
|
||||
if pair[0].path == pair[1].path && pair[0].kind != pair[1].kind {
|
||||
bail!("prepared destination has conflicting types");
|
||||
}
|
||||
}
|
||||
Ok(destinations)
|
||||
}
|
||||
|
||||
fn destination_policy_bytes(destinations: &[PreparedDestination]) -> Vec<u8> {
|
||||
let mut bytes = b"lazaret-prepared-layout-policy-v1\0".to_vec();
|
||||
for destination in destinations {
|
||||
bytes.extend_from_slice(destination.path.as_os_str().as_bytes());
|
||||
bytes.push(0);
|
||||
bytes.push(match destination.kind {
|
||||
PreparedDestinationKind::Directory => b'd',
|
||||
PreparedDestinationKind::RegularFile => b'f',
|
||||
});
|
||||
bytes.push(0);
|
||||
}
|
||||
bytes
|
||||
}
|
||||
|
||||
fn artifact_from_operation(
|
||||
operation: &PrepareOperation,
|
||||
path: &Path,
|
||||
|
|
|
|||
Loading…
Reference in a new issue