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
65 changes: 65 additions & 0 deletions artifact/src/installinator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

use semver::Version;
use serde::{Deserialize, Serialize};

use crate::ArtifactHash;

/// Artifact-specific information used by installinator.
///
/// This document contains information used by installinator to learn about
/// which artifacts to fetch. Unlike
/// [`ArtifactsDocument`](crate::ArtifactsDocument):
///
/// * This document is treated as an opaque blob by Wicketd and Nexus, since
/// we'd like previous versions of those services to be able to process newer
/// versions of this document.
/// * There are no backwards compatibility constraints for this document. The
/// version of installinator that processes this document is the same as the
/// version of tufaceous that creates it.
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct InstallinatorDocument {
pub system_version: Version,
pub artifacts: Vec<InstallinatorArtifact>,
}

impl InstallinatorDocument {
/// Creates an installinator document with the provided system version and
/// an empty list of artifacts.
pub fn empty(system_version: Version) -> Self {
Self { system_version, artifacts: Vec::new() }
}

pub fn file_name(&self) -> String {
format!("installinator_document-{}.json", self.system_version)
}
}

/// Describes an artifact available to installinator.
///
/// The fields here match [`Artifact`](crate::Artifact).
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct InstallinatorArtifact {
pub name: String,
/// The kind of artifact.
///
/// This is an [`InstallinatorArtifactKind`] rather than an
/// [`ArtifactKind`](crate::ArtifactKind) because there aren't any backwards
/// compatibility constraints with `InstallinatorArtifact`.
pub kind: InstallinatorArtifactKind,
pub hash: ArtifactHash,
}

/// The artifact kind for an installinator artifact.
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum InstallinatorArtifactKind {
/// The host phase 2 artifact.
///
/// This is extracted from the composite host artifact.
HostPhase2,
/// The composite control plane artifact.
ControlPlane,
}
8 changes: 8 additions & 0 deletions artifact/src/kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,13 @@ pub enum KnownArtifactKind {
GimletRotBootloader,
Host,
Trampoline,
/// Installinator document identifier.
///
/// While the installinator document is a metadata file similar to
/// [`ArtifactsDocument`](crate::ArtifactsDocument), Wicketd and Nexus treat
/// it as an opaque single-unit artifact to avoid backwards compatibility
/// issues.
InstallinatorDocument,
/// Composite artifact of all control plane zones
ControlPlane,
/// Individual control plane zone
Expand Down Expand Up @@ -220,6 +227,7 @@ impl KnownArtifactKind {
| KnownArtifactKind::GimletRotBootloader
| KnownArtifactKind::Host
| KnownArtifactKind::Trampoline
| KnownArtifactKind::InstallinatorDocument
| KnownArtifactKind::ControlPlane
| KnownArtifactKind::Zone
| KnownArtifactKind::PscSp
Expand Down
2 changes: 2 additions & 0 deletions artifact/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

mod artifact;
mod installinator;
mod kind;
mod version;

pub use artifact::*;
pub use installinator::*;
pub use kind::*;
pub use version::*;
72 changes: 63 additions & 9 deletions bin/src/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ use camino::Utf8PathBuf;
use chrono::{DateTime, Utc};
use clap::{CommandFactory, Parser};
use semver::Version;
use tufaceous_artifact::{ArtifactKind, ArtifactVersion, ArtifactsDocument};
use tufaceous_artifact::{
ArtifactKind, ArtifactVersion, ArtifactsDocument, KnownArtifactKind,
};
use tufaceous_lib::assemble::{ArtifactManifest, OmicronRepoAssembler};
use tufaceous_lib::{AddArtifact, ArchiveExtractor, Key, OmicronRepo};

Expand Down Expand Up @@ -42,6 +44,11 @@ impl Args {
};

match self.command {
// TODO-cleanup: we no longer use the init and add commands in
// production. We should get rid of these options and direct users
// towards assemble. (If necessary, we should build tooling for
// making it easy to build up a manifest that can then be
// assembled.)
Command::Init { system_version, no_generate_key } => {
let keys = maybe_generate_keys(self.keys, no_generate_key)?;
let root =
Expand All @@ -55,6 +62,7 @@ impl Args {
keys,
root,
self.expiry,
true,
)
.await?;
slog::info!(
Expand Down Expand Up @@ -121,7 +129,7 @@ impl Args {
editor
.add_artifact(&new_artifact)
.context("error adding artifact")?;
editor.sign_and_finish(self.keys, self.expiry).await?;
editor.sign_and_finish(self.keys, self.expiry, true).await?;
println!(
"added {} {}, version {}",
new_artifact.kind(),
Expand All @@ -144,7 +152,11 @@ impl Args {

Ok(())
}
Command::Extract { archive_file, dest } => {
Command::Extract {
archive_file,
dest,
no_installinator_document,
} => {
let mut extractor = ArchiveExtractor::from_path(&archive_file)?;
extractor.extract(&dest)?;

Expand All @@ -157,13 +169,43 @@ impl Args {
(extracted files are still available)"
)
})?;
repo.read_artifacts().await.with_context(|| {
format!(
"error loading {} from extracted archive \
at `{dest}`",
ArtifactsDocument::FILE_NAME
let artifacts =
repo.read_artifacts().await.with_context(|| {
format!(
"error loading {} from extracted archive \
at `{dest}`",
ArtifactsDocument::FILE_NAME
)
})?;
if !no_installinator_document {
// There should be a reference to an installinator document
// within artifacts_document.
let installinator_doc_artifact = artifacts
.artifacts
.iter()
.find(|artifact| {
artifact.kind.to_known()
== Some(
KnownArtifactKind::InstallinatorDocument,
)
})
.context(
"could not find artifact with kind \
`installinator_document` within artifacts.json",
)?;

repo.read_installinator_document(
&installinator_doc_artifact.target,
)
})?;
.await
.with_context(|| {
format!(
"error loading {} from extracted archive \
at `{dest}`",
installinator_doc_artifact.target,
)
})?;
}

Ok(())
}
Expand All @@ -174,6 +216,7 @@ impl Args {
no_generate_key,
skip_all_present,
allow_non_semver,
no_installinator_document,
} => {
// The filename must end with "zip".
if output_path.extension() != Some("zip") {
Expand All @@ -195,6 +238,7 @@ impl Args {
manifest,
keys,
self.expiry,
!no_installinator_document,
output_path,
);
if let Some(dir) = build_dir {
Expand Down Expand Up @@ -260,6 +304,10 @@ enum Command {

/// The destination to extract the file to.
dest: Utf8PathBuf,

/// Indicate that the file does not contain an installinator document.
#[clap(long)]
no_installinator_document: bool,
},
/// Assembles a repository from a provided manifest.
Assemble {
Expand Down Expand Up @@ -287,6 +335,12 @@ enum Command {
/// allowed to be non-semver by default.
#[clap(long)]
allow_non_semver: bool,

/// Do not include the installinator document.
///
/// Transitional option for v15 -> v16, meant to be used for testing.
#[clap(long)]
no_installinator_document: bool,
},
}

Expand Down
18 changes: 16 additions & 2 deletions bin/tests/integration-tests/command_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,25 @@ async fn test_init_and_add() -> Result<()> {
let artifacts = repo.read_artifacts().await?;
assert_eq!(
artifacts.artifacts.len(),
3,
"repo should contain exactly 3 artifacts: {artifacts:?}"
// 3 artifacts added above + installinator_document.json.
4,
"repo should contain exactly 4 artifacts: {artifacts:?}"
);

let mut artifacts_iter = artifacts.artifacts.into_iter();
let artifact = artifacts_iter.next().unwrap();
assert_eq!(artifact.name, "installinator_document", "artifact name");
assert_eq!(artifact.version, "0.0.0".parse().unwrap(), "artifact version");
assert_eq!(
artifact.kind,
ArtifactKind::from_known(KnownArtifactKind::InstallinatorDocument),
"artifact kind"
);
assert_eq!(
artifact.target, "installinator_document-0.0.0.json",
"artifact target"
);

let artifact = artifacts_iter.next().unwrap();
assert_eq!(artifact.name, "nexus", "artifact name");
assert_eq!(artifact.version, "42.0.0".parse().unwrap(), "artifact version");
Expand Down
Loading
Loading