feat: Add bootc support (#448)
Adds support for using `bootc` as the preferred method for booting from a locally created image. This new method gets rid of the need to create a tarball and move it to the correct place and instead it will make use of `podman scp` which copies the image to the root `containers-storage` and then has `rpm-ostree` and `bootc` boot from that store. Closes #418 Closes #200
This commit is contained in:
parent
2c525854c9
commit
3a0be4099a
65 changed files with 2991 additions and 1857 deletions
179
process/drivers/types/container.rs
Normal file
179
process/drivers/types/container.rs
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
use std::{
|
||||
borrow::Cow,
|
||||
ops::Deref,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use oci_distribution::Reference;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ContainerId(pub(crate) String);
|
||||
|
||||
impl Deref for ContainerId {
|
||||
type Target = str;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ContainerId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", &self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<std::ffi::OsStr> for ContainerId {
|
||||
fn as_ref(&self) -> &std::ffi::OsStr {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MountId(pub(crate) String);
|
||||
|
||||
impl Deref for MountId {
|
||||
type Target = str;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for MountId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", &self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<std::ffi::OsStr> for MountId {
|
||||
fn as_ref(&self) -> &std::ffi::OsStr {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a MountId> for std::borrow::Cow<'a, str> {
|
||||
fn from(value: &'a MountId) -> Self {
|
||||
Self::Borrowed(&value.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OciDir(String);
|
||||
|
||||
impl std::fmt::Display for OciDir {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", &self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<std::ffi::OsStr> for OciDir {
|
||||
fn as_ref(&self) -> &std::ffi::OsStr {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<std::path::PathBuf> for OciDir {
|
||||
type Error = miette::Report;
|
||||
|
||||
fn try_from(value: std::path::PathBuf) -> Result<Self, Self::Error> {
|
||||
if !value.is_dir() {
|
||||
miette::bail!("OCI directory doesn't exist at {}", value.display());
|
||||
}
|
||||
|
||||
Ok(Self(format!("oci:{}", value.display())))
|
||||
}
|
||||
}
|
||||
|
||||
/// An image ref that could reference
|
||||
/// a remote registry or a local tarball.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ImageRef<'scope> {
|
||||
Remote(Cow<'scope, Reference>),
|
||||
LocalTar(Cow<'scope, Path>),
|
||||
Other(Cow<'scope, str>),
|
||||
}
|
||||
|
||||
impl ImageRef<'_> {
|
||||
#[must_use]
|
||||
pub fn remote_ref(&self) -> Option<&Reference> {
|
||||
match self {
|
||||
Self::Remote(remote) => Some(remote.as_ref()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'scope> From<&'scope Self> for ImageRef<'scope> {
|
||||
fn from(value: &'scope ImageRef) -> Self {
|
||||
match value {
|
||||
Self::Remote(remote) => Self::Remote(Cow::Borrowed(remote.as_ref())),
|
||||
Self::LocalTar(path) => Self::LocalTar(Cow::Borrowed(path.as_ref())),
|
||||
Self::Other(other) => Self::Other(Cow::Borrowed(other.as_ref())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'scope> From<&'scope Reference> for ImageRef<'scope> {
|
||||
fn from(value: &'scope Reference) -> Self {
|
||||
Self::Remote(Cow::Borrowed(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Reference> for ImageRef<'_> {
|
||||
fn from(value: Reference) -> Self {
|
||||
Self::Remote(Cow::Owned(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'scope> From<&'scope Path> for ImageRef<'scope> {
|
||||
fn from(value: &'scope Path) -> Self {
|
||||
Self::LocalTar(Cow::Borrowed(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'scope> From<&'scope PathBuf> for ImageRef<'scope> {
|
||||
fn from(value: &'scope PathBuf) -> Self {
|
||||
Self::from(value.as_path())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PathBuf> for ImageRef<'_> {
|
||||
fn from(value: PathBuf) -> Self {
|
||||
Self::LocalTar(Cow::Owned(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ImageRef<'_>> for String {
|
||||
fn from(value: ImageRef<'_>) -> Self {
|
||||
Self::from(&value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ImageRef<'_>> for String {
|
||||
fn from(value: &ImageRef<'_>) -> Self {
|
||||
format!("{value}")
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ImageRef<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Self::Remote(remote) => remote.whole(),
|
||||
Self::LocalTar(path) => format!("oci-archive:{}", path.display()),
|
||||
Self::Other(other) => other.to_string(),
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<Reference> for ImageRef<'_> {
|
||||
fn eq(&self, other: &Reference) -> bool {
|
||||
match self {
|
||||
Self::Remote(remote) => &**remote == other,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
191
process/drivers/types/drivers.rs
Normal file
191
process/drivers/types/drivers.rs
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
use blue_build_utils::{
|
||||
constants::{GITHUB_ACTIONS, GITLAB_CI},
|
||||
get_env_var,
|
||||
};
|
||||
use clap::ValueEnum;
|
||||
use log::trace;
|
||||
|
||||
use crate::drivers::{
|
||||
DetermineDriver, DriverVersion, buildah_driver::BuildahDriver, docker_driver::DockerDriver,
|
||||
podman_driver::PodmanDriver,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, ValueEnum)]
|
||||
pub enum InspectDriverType {
|
||||
Skopeo,
|
||||
Podman,
|
||||
Docker,
|
||||
}
|
||||
|
||||
impl DetermineDriver<InspectDriverType> for Option<InspectDriverType> {
|
||||
fn determine_driver(&mut self) -> InspectDriverType {
|
||||
*self.get_or_insert(
|
||||
match (
|
||||
blue_build_utils::check_command_exists("skopeo"),
|
||||
blue_build_utils::check_command_exists("docker"),
|
||||
blue_build_utils::check_command_exists("podman"),
|
||||
) {
|
||||
(Ok(_skopeo), _, _) => InspectDriverType::Skopeo,
|
||||
(_, Ok(_docker), _) => InspectDriverType::Docker,
|
||||
(_, _, Ok(_podman)) => InspectDriverType::Podman,
|
||||
_ => panic!(
|
||||
"{}{}",
|
||||
"Could not determine inspection strategy. ",
|
||||
"You need either skopeo, docker, or podman",
|
||||
),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, ValueEnum)]
|
||||
pub enum BuildDriverType {
|
||||
Buildah,
|
||||
Podman,
|
||||
Docker,
|
||||
}
|
||||
|
||||
impl DetermineDriver<BuildDriverType> for Option<BuildDriverType> {
|
||||
fn determine_driver(&mut self) -> BuildDriverType {
|
||||
*self.get_or_insert(
|
||||
match (
|
||||
blue_build_utils::check_command_exists("docker"),
|
||||
blue_build_utils::check_command_exists("podman"),
|
||||
blue_build_utils::check_command_exists("buildah"),
|
||||
) {
|
||||
(Ok(_docker), _, _)
|
||||
if DockerDriver::is_supported_version() && DockerDriver::has_buildx() =>
|
||||
{
|
||||
BuildDriverType::Docker
|
||||
}
|
||||
(_, Ok(_podman), _) if PodmanDriver::is_supported_version() => {
|
||||
BuildDriverType::Podman
|
||||
}
|
||||
(_, _, Ok(_buildah)) if BuildahDriver::is_supported_version() => {
|
||||
BuildDriverType::Buildah
|
||||
}
|
||||
_ => panic!(
|
||||
"{}{}{}{}",
|
||||
"Could not determine strategy, ",
|
||||
format_args!(
|
||||
"need either docker version {} with buildx, ",
|
||||
DockerDriver::VERSION_REQ,
|
||||
),
|
||||
format_args!("podman version {}, ", PodmanDriver::VERSION_REQ,),
|
||||
format_args!(
|
||||
"or buildah version {} to continue",
|
||||
BuildahDriver::VERSION_REQ,
|
||||
),
|
||||
),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, ValueEnum)]
|
||||
pub enum SigningDriverType {
|
||||
Cosign,
|
||||
Sigstore,
|
||||
}
|
||||
|
||||
impl DetermineDriver<SigningDriverType> for Option<SigningDriverType> {
|
||||
fn determine_driver(&mut self) -> SigningDriverType {
|
||||
trace!("SigningDriverType::determine_signing_driver()");
|
||||
|
||||
*self.get_or_insert(
|
||||
blue_build_utils::check_command_exists("cosign")
|
||||
.map_or(SigningDriverType::Sigstore, |()| SigningDriverType::Cosign),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, ValueEnum)]
|
||||
pub enum RunDriverType {
|
||||
Podman,
|
||||
Docker,
|
||||
}
|
||||
|
||||
impl From<RunDriverType> for String {
|
||||
fn from(value: RunDriverType) -> Self {
|
||||
match value {
|
||||
RunDriverType::Podman => "podman".to_string(),
|
||||
RunDriverType::Docker => "docker".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DetermineDriver<RunDriverType> for Option<RunDriverType> {
|
||||
fn determine_driver(&mut self) -> RunDriverType {
|
||||
trace!("RunDriver::determine_driver()");
|
||||
|
||||
*self.get_or_insert(
|
||||
match (
|
||||
blue_build_utils::check_command_exists("docker"),
|
||||
blue_build_utils::check_command_exists("podman"),
|
||||
) {
|
||||
(Ok(_docker), _) if DockerDriver::is_supported_version() => RunDriverType::Docker,
|
||||
(_, Ok(_podman)) if PodmanDriver::is_supported_version() => RunDriverType::Podman,
|
||||
_ => panic!(
|
||||
"{}{}{}{}",
|
||||
"Could not determine strategy, ",
|
||||
format_args!("need either docker version {}, ", DockerDriver::VERSION_REQ),
|
||||
format_args!("podman version {}, ", PodmanDriver::VERSION_REQ),
|
||||
format_args!(
|
||||
"or buildah version {} to continue",
|
||||
BuildahDriver::VERSION_REQ
|
||||
),
|
||||
),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, ValueEnum)]
|
||||
pub enum CiDriverType {
|
||||
Local,
|
||||
Gitlab,
|
||||
Github,
|
||||
}
|
||||
|
||||
impl DetermineDriver<CiDriverType> for Option<CiDriverType> {
|
||||
fn determine_driver(&mut self) -> CiDriverType {
|
||||
trace!("CiDriverType::determine_driver()");
|
||||
|
||||
*self.get_or_insert(
|
||||
match (
|
||||
get_env_var(GITLAB_CI).ok(),
|
||||
get_env_var(GITHUB_ACTIONS).ok(),
|
||||
) {
|
||||
(Some(_gitlab_ci), None) => CiDriverType::Gitlab,
|
||||
(None, Some(_github_actions)) => CiDriverType::Github,
|
||||
_ => CiDriverType::Local,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, ValueEnum)]
|
||||
pub enum BootDriverType {
|
||||
#[cfg(feature = "bootc")]
|
||||
Bootc,
|
||||
RpmOstree,
|
||||
None,
|
||||
}
|
||||
|
||||
impl DetermineDriver<BootDriverType> for Option<BootDriverType> {
|
||||
fn determine_driver(&mut self) -> BootDriverType {
|
||||
trace!("BootDriverType::determine_driver()");
|
||||
|
||||
*self.get_or_insert(
|
||||
match (
|
||||
blue_build_utils::check_command_exists("bootc"),
|
||||
blue_build_utils::check_command_exists("rpm-ostree"),
|
||||
) {
|
||||
#[cfg(feature = "bootc")]
|
||||
(Ok(_bootc), _) => BootDriverType::Bootc,
|
||||
(_, Ok(_rpm_ostree)) => BootDriverType::RpmOstree,
|
||||
_ => BootDriverType::None,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
30
process/drivers/types/metadata.rs
Normal file
30
process/drivers/types/metadata.rs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use blue_build_utils::{constants::IMAGE_VERSION_LABEL, semver::Version};
|
||||
use log::warn;
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
pub struct ImageMetadata {
|
||||
pub labels: HashMap<String, Value>,
|
||||
pub digest: String,
|
||||
}
|
||||
|
||||
impl ImageMetadata {
|
||||
#[must_use]
|
||||
pub fn get_version(&self) -> Option<u64> {
|
||||
Some(
|
||||
self.labels
|
||||
.get(IMAGE_VERSION_LABEL)
|
||||
.map(ToOwned::to_owned)
|
||||
.and_then(|v| {
|
||||
serde_json::from_value::<Version>(v)
|
||||
.inspect_err(|e| warn!("Failed to parse version:\n{e}"))
|
||||
.ok()
|
||||
})?
|
||||
.major,
|
||||
)
|
||||
}
|
||||
}
|
||||
155
process/drivers/types/platform.rs
Normal file
155
process/drivers/types/platform.rs
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
use blue_build_utils::string;
|
||||
use clap::ValueEnum;
|
||||
|
||||
mod private {
|
||||
pub trait Private {}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, ValueEnum, PartialEq, Eq, Hash)]
|
||||
pub enum Platform {
|
||||
#[value(name = "linux/amd64")]
|
||||
LinuxAmd64,
|
||||
|
||||
#[value(name = "linux/amd64/v2")]
|
||||
LinuxAmd64V2,
|
||||
|
||||
#[value(name = "linux/arm64")]
|
||||
LinuxArm64,
|
||||
|
||||
#[value(name = "linux/arm")]
|
||||
LinuxArm,
|
||||
|
||||
#[value(name = "linux/arm/v6")]
|
||||
LinuxArmV6,
|
||||
|
||||
#[value(name = "linux/arm/v7")]
|
||||
LinuxArmV7,
|
||||
|
||||
#[value(name = "linux/386")]
|
||||
Linux386,
|
||||
|
||||
#[value(name = "linux/loong64")]
|
||||
LinuxLoong64,
|
||||
|
||||
#[value(name = "linux/mips")]
|
||||
LinuxMips,
|
||||
|
||||
#[value(name = "linux/mipsle")]
|
||||
LinuxMipsle,
|
||||
|
||||
#[value(name = "linux/mips64")]
|
||||
LinuxMips64,
|
||||
|
||||
#[value(name = "linux/mips64le")]
|
||||
LinuxMips64le,
|
||||
|
||||
#[value(name = "linux/ppc64")]
|
||||
LinuxPpc64,
|
||||
|
||||
#[value(name = "linux/ppc64le")]
|
||||
LinuxPpc64le,
|
||||
|
||||
#[value(name = "linux/riscv64")]
|
||||
LinuxRiscv64,
|
||||
|
||||
#[value(name = "linux/s390x")]
|
||||
LinuxS390x,
|
||||
}
|
||||
|
||||
impl Platform {
|
||||
/// The architecture of the platform.
|
||||
#[must_use]
|
||||
pub const fn arch(&self) -> &str {
|
||||
match *self {
|
||||
Self::LinuxAmd64 | Self::LinuxAmd64V2 => "amd64",
|
||||
Self::LinuxArm64 => "arm64",
|
||||
Self::LinuxArm | Self::LinuxArmV6 | Self::LinuxArmV7 => "arm",
|
||||
Self::Linux386 => "386",
|
||||
Self::LinuxLoong64 => "loong64",
|
||||
Self::LinuxMips => "mips",
|
||||
Self::LinuxMipsle => "mipsle",
|
||||
Self::LinuxMips64 => "mips64",
|
||||
Self::LinuxMips64le => "mips64le",
|
||||
Self::LinuxPpc64 => "ppc64",
|
||||
Self::LinuxPpc64le => "ppc64le",
|
||||
Self::LinuxRiscv64 => "riscv64",
|
||||
Self::LinuxS390x => "s390x",
|
||||
}
|
||||
}
|
||||
|
||||
/// The variant of the platform.
|
||||
#[must_use]
|
||||
pub const fn variant(&self) -> Option<&str> {
|
||||
match *self {
|
||||
Self::LinuxAmd64V2 => Some("v2"),
|
||||
Self::LinuxArmV6 => Some("v6"),
|
||||
Self::LinuxArmV7 => Some("v7"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Platform {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match *self {
|
||||
Self::LinuxAmd64 => "linux/amd64",
|
||||
Self::LinuxAmd64V2 => "linux/amd64/v2",
|
||||
Self::LinuxArm64 => "linux/arm64",
|
||||
Self::LinuxArm => "linux/arm",
|
||||
Self::LinuxArmV6 => "linux/arm/v6",
|
||||
Self::LinuxArmV7 => "linux/arm/v7",
|
||||
Self::Linux386 => "linux/386",
|
||||
Self::LinuxLoong64 => "linux/loong64",
|
||||
Self::LinuxMips => "linux/mips",
|
||||
Self::LinuxMipsle => "linux/mipsle",
|
||||
Self::LinuxMips64 => "linux/mips64",
|
||||
Self::LinuxMips64le => "linux/mips64le",
|
||||
Self::LinuxPpc64 => "linux/ppc64",
|
||||
Self::LinuxPpc64le => "linux/ppc64le",
|
||||
Self::LinuxRiscv64 => "linux/riscv64",
|
||||
Self::LinuxS390x => "linux/s390x",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl private::Private for Option<Platform> {}
|
||||
|
||||
pub trait PlatformInfo: private::Private {
|
||||
/// The string representation of the platform.
|
||||
///
|
||||
/// If `None`, then the native architecture will be used.
|
||||
fn to_string(&self) -> String;
|
||||
|
||||
/// The string representation of the architecture.
|
||||
///
|
||||
/// If `None`, then the native architecture will be used.
|
||||
fn arch(&self) -> &str;
|
||||
}
|
||||
|
||||
impl PlatformInfo for Option<Platform> {
|
||||
fn to_string(&self) -> String {
|
||||
self.map_or_else(
|
||||
|| match std::env::consts::ARCH {
|
||||
"x86_64" => string!("linux/amd64"),
|
||||
"aarch64" => string!("linux/arm64"),
|
||||
arch => unimplemented!("Arch {arch} is unsupported"),
|
||||
},
|
||||
|platform| format!("{platform}"),
|
||||
)
|
||||
}
|
||||
|
||||
fn arch(&self) -> &str {
|
||||
self.as_ref().map_or_else(
|
||||
|| match std::env::consts::ARCH {
|
||||
"x86_64" => "amd64",
|
||||
"aarch64" => "arm64",
|
||||
arch => unimplemented!("Arch {arch} is unsupported"),
|
||||
},
|
||||
Platform::arch,
|
||||
)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue