feat(init): Add the new/init subcommands (#85)
This commit is contained in:
parent
e3b246ef91
commit
918da22952
19 changed files with 765 additions and 152 deletions
64
Cargo.lock
generated
64
Cargo.lock
generated
|
|
@ -74,9 +74,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "allocator-api2"
|
||||
version = "0.2.18"
|
||||
version = "0.2.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
|
||||
checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9"
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
|
|
@ -378,6 +378,7 @@ dependencies = [
|
|||
"reqwest 0.12.9",
|
||||
"rstest",
|
||||
"rusty-hook",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yml",
|
||||
|
|
@ -592,9 +593,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.1.36"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baee610e9452a8f6f0a1b6194ec09ff9e2d85dea54432acdae41aa0761c95d70"
|
||||
checksum = "1aeb932158bd710538c73702db6945cb68a8fb08c519e6e12706b94263b36db8"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
|
@ -806,9 +807,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
|||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.14"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0"
|
||||
checksum = "0ca741a962e1b0bff6d724a1a0958b686406e853bb14061f218562e1896f95e6"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
|
@ -1229,9 +1230,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.1.1"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6"
|
||||
checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4"
|
||||
|
||||
[[package]]
|
||||
name = "ff"
|
||||
|
|
@ -1984,17 +1985,17 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "indicatif"
|
||||
version = "0.17.8"
|
||||
version = "0.17.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3"
|
||||
checksum = "cbf675b85ed934d3c67b5c5469701eec7db22689d0a2139d856e0925fa28b281"
|
||||
dependencies = [
|
||||
"console",
|
||||
"instant",
|
||||
"number_prefix",
|
||||
"portable-atomic",
|
||||
"rayon",
|
||||
"unicode-segmentation",
|
||||
"unicode-width 0.1.14",
|
||||
"unicode-width 0.2.0",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2017,15 +2018,6 @@ dependencies = [
|
|||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.10.1"
|
||||
|
|
@ -2308,9 +2300,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.161"
|
||||
version = "0.2.162"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1"
|
||||
checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398"
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
|
|
@ -3498,9 +3490,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3"
|
||||
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
|
|
@ -3789,9 +3781,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.39"
|
||||
version = "0.38.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "375116bee2be9ed569afe2154ea6a99dfdffd257f533f187498c2a8f5feaf4ee"
|
||||
checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"errno",
|
||||
|
|
@ -3972,9 +3964,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.214"
|
||||
version = "1.0.215"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5"
|
||||
checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
|
@ -3991,9 +3983,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.214"
|
||||
version = "1.0.215"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766"
|
||||
checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
@ -4574,18 +4566,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.68"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02dd99dc800bbb97186339685293e1cc5d9df1f8fae2d0aecd9ff1c77efea892"
|
||||
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.68"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7c61ec9a6f64d2793d8a45faba21efbe3ced62a886d44c36a009b2b519b4c7e"
|
||||
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
@ -5231,7 +5223,7 @@ version = "0.1.9"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||
dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
|
|
@ -22,11 +22,11 @@ oci-distribution = { version = "0.11", default-features = false }
|
|||
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] }
|
||||
miette = "7"
|
||||
rstest = "0.18"
|
||||
semver = "1"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
serde_yaml = { version = "0.0.12", package = "serde_yml" }
|
||||
syntect = { version = "5", default-features = false, features = ["default-fancy"] }
|
||||
# tempdir = "0.3"
|
||||
tempfile = "3"
|
||||
tokio = { version = "1", features = ["rt", "rt-multi-thread"] }
|
||||
users = "0.11"
|
||||
|
|
@ -88,6 +88,7 @@ log.workspace = true
|
|||
miette = { workspace = true, features = ["fancy", "syntect-highlighter"] }
|
||||
oci-distribution.workspace = true
|
||||
reqwest.workspace = true
|
||||
semver.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde_yaml.workspace = true
|
||||
|
|
@ -98,7 +99,9 @@ bon.workspace = true
|
|||
users.workspace = true
|
||||
|
||||
[features]
|
||||
# Top level features
|
||||
default = []
|
||||
init = []
|
||||
stages = ["blue-build-recipe/stages"]
|
||||
copy = ["blue-build-recipe/copy"]
|
||||
multi-recipe = ["dep:rayon", "indicatif/rayon"]
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
ARG BASE_IMAGE="alpine"
|
||||
FROM $BASE_IMAGE
|
||||
|
||||
RUN apk update && apk add buildah podman skopeo fuse-overlayfs gpg tini dumb-init
|
||||
RUN apk update && apk add buildah podman skopeo fuse-overlayfs gpg tini dumb-init git
|
||||
|
||||
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ RUN dnf -y install dnf-plugins-core \
|
|||
podman \
|
||||
skopeo \
|
||||
gpg \
|
||||
dumb-init
|
||||
dumb-init \
|
||||
git
|
||||
|
||||
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
|
||||
|
||||
|
|
|
|||
|
|
@ -91,6 +91,39 @@ validate:
|
|||
RUN --no-cache bluebuild -v validate recipes/recipe-invalid-stage.yml && exit 1 || exit 0
|
||||
RUN --no-cache bluebuild -v validate recipes/recipe-invalid-from-file.yml && exit 1 || exit 0
|
||||
|
||||
init:
|
||||
FROM +test-base
|
||||
|
||||
WORKDIR /tmp
|
||||
RUN --no-cache bluebuild new test-github \
|
||||
--image-name test-github \
|
||||
--org-name test \
|
||||
--description 'This is a description' \
|
||||
--registry 'ghcr.io' \
|
||||
--ci-provider github
|
||||
RUN --no-cache bluebuild new test-gitlab \
|
||||
--image-name test-gitlab \
|
||||
--org-name test \
|
||||
--description 'This is a description' \
|
||||
--registry 'registry.gitlab.com' \
|
||||
--ci-provider gitlab
|
||||
RUN --no-cache bluebuild new test-none \
|
||||
--image-name test-none \
|
||||
--org-name test \
|
||||
--description 'This is a description' \
|
||||
--registry 'docker.io' \
|
||||
--ci-provider none \
|
||||
--no-git
|
||||
|
||||
WORKDIR /tmp/test-init
|
||||
RUN --no-cache bluebuild init \
|
||||
--image-name test-init \
|
||||
--org-name test \
|
||||
--description 'This is a description' \
|
||||
--registry 'docker.io' \
|
||||
--ci-provider none \
|
||||
--no-git
|
||||
|
||||
legacy-base:
|
||||
FROM ../+blue-build-cli-alpine --RELEASE=false
|
||||
RUN apk update --no-cache && apk add bash grep jq sudo coreutils
|
||||
|
|
@ -108,7 +141,10 @@ legacy-base:
|
|||
|
||||
test-base:
|
||||
FROM ../+blue-build-cli-alpine --RELEASE=false
|
||||
RUN apk update --no-cache && apk add bash grep jq sudo coreutils
|
||||
RUN apk update --no-cache && apk add bash grep jq sudo coreutils git && \
|
||||
git config --global user.email "you@example.com" && \
|
||||
git config --global user.name "Your Name"
|
||||
|
||||
ENV BB_TEST_LOCAL_IMAGE=/etc/bluebuild/cli_test.tar.gz
|
||||
ENV CLICOLOR_FORCE=1
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ nix = { version = "0.29", features = ["signal"] }
|
|||
once_cell = "1"
|
||||
os_pipe = { version = "1", features = ["io_safety"] }
|
||||
rand = "0.8"
|
||||
semver = { version = "1", features = ["serde"] }
|
||||
signal-hook = { version = "0.3", features = ["extended-siginfo"] }
|
||||
sigstore = { version = "0.10", features = ["full-rustls-tls", "cached-client", "sigstore-trust-root", "sign"], default-features = false, optional = true }
|
||||
zeroize = { version = "1", features = ["aarch64", "derive", "serde"] }
|
||||
|
|
@ -36,6 +35,7 @@ log.workspace = true
|
|||
miette.workspace = true
|
||||
oci-distribution.workspace = true
|
||||
reqwest.workspace = true
|
||||
semver = { workspace = true, features = ["serde"] }
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
tempfile.workspace = true
|
||||
|
|
|
|||
|
|
@ -23,34 +23,25 @@ use log::{info, trace, warn};
|
|||
use miette::{miette, IntoDiagnostic, Result};
|
||||
use oci_distribution::Reference;
|
||||
use once_cell::sync::Lazy;
|
||||
use opts::{GenerateImageNameOpts, GenerateTagsOpts};
|
||||
#[cfg(feature = "sigstore")]
|
||||
use sigstore_driver::SigstoreDriver;
|
||||
use types::Platform;
|
||||
use opts::{
|
||||
BuildOpts, BuildTagPushOpts, CheckKeyPairOpts, GenerateImageNameOpts, GenerateKeyPairOpts,
|
||||
GenerateTagsOpts, GetMetadataOpts, PushOpts, RunOpts, SignOpts, TagOpts, VerifyOpts,
|
||||
};
|
||||
use types::{
|
||||
BuildDriverType, CiDriverType, DetermineDriver, ImageMetadata, InspectDriverType, Platform,
|
||||
RunDriverType, SigningDriverType,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::logging::Logger;
|
||||
|
||||
use self::{
|
||||
buildah_driver::BuildahDriver,
|
||||
cosign_driver::CosignDriver,
|
||||
docker_driver::DockerDriver,
|
||||
github_driver::GithubDriver,
|
||||
gitlab_driver::GitlabDriver,
|
||||
local_driver::LocalDriver,
|
||||
opts::{
|
||||
BuildOpts, BuildTagPushOpts, CheckKeyPairOpts, GenerateKeyPairOpts, GetMetadataOpts,
|
||||
PushOpts, RunOpts, SignOpts, TagOpts, VerifyOpts,
|
||||
},
|
||||
podman_driver::PodmanDriver,
|
||||
skopeo_driver::SkopeoDriver,
|
||||
types::{
|
||||
BuildDriverType, CiDriverType, DetermineDriver, ImageMetadata, InspectDriverType,
|
||||
RunDriverType, SigningDriverType,
|
||||
},
|
||||
pub use self::{
|
||||
buildah_driver::BuildahDriver, cosign_driver::CosignDriver, docker_driver::DockerDriver,
|
||||
github_driver::GithubDriver, gitlab_driver::GitlabDriver, local_driver::LocalDriver,
|
||||
podman_driver::PodmanDriver, skopeo_driver::SkopeoDriver, traits::*,
|
||||
};
|
||||
|
||||
pub use traits::*;
|
||||
#[cfg(feature = "sigstore")]
|
||||
pub use sigstore_driver::SigstoreDriver;
|
||||
|
||||
mod buildah_driver;
|
||||
mod cosign_driver;
|
||||
|
|
@ -449,4 +440,8 @@ impl CiDriver for Driver {
|
|||
{
|
||||
impl_ci_driver!(generate_image_name(opts))
|
||||
}
|
||||
|
||||
fn default_ci_file_path() -> std::path::PathBuf {
|
||||
impl_ci_driver!(default_ci_file_path())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ use blue_build_utils::{
|
|||
};
|
||||
use cached::proc_macro::cached;
|
||||
use log::{debug, info, trace, warn};
|
||||
use miette::{bail, IntoDiagnostic, Result};
|
||||
use miette::{bail, miette, IntoDiagnostic, Result};
|
||||
use once_cell::sync::Lazy;
|
||||
use semver::Version;
|
||||
use serde::Deserialize;
|
||||
|
|
@ -23,19 +23,18 @@ use tempfile::TempDir;
|
|||
|
||||
use crate::{
|
||||
drivers::{
|
||||
opts::{RunOptsEnv, RunOptsVolume},
|
||||
opts::{
|
||||
BuildOpts, BuildTagPushOpts, GetMetadataOpts, PushOpts, RunOpts, RunOptsEnv,
|
||||
RunOptsVolume, TagOpts,
|
||||
},
|
||||
traits::{BuildDriver, DriverVersion, InspectDriver, RunDriver},
|
||||
types::ImageMetadata,
|
||||
types::Platform,
|
||||
},
|
||||
logging::CommandLogging,
|
||||
signal_handler::{add_cid, remove_cid, ContainerId, ContainerRuntime},
|
||||
};
|
||||
|
||||
use super::{
|
||||
opts::{BuildOpts, BuildTagPushOpts, GetMetadataOpts, PushOpts, RunOpts, TagOpts},
|
||||
types::ImageMetadata,
|
||||
BuildDriver, DriverVersion, InspectDriver, RunDriver,
|
||||
};
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
struct DockerImageMetadata {
|
||||
manifest: DockerImageMetadataManifest,
|
||||
|
|
@ -238,7 +237,14 @@ impl BuildDriver for DockerDriver {
|
|||
trace!("{command:?}");
|
||||
let mut child = command.spawn().into_diagnostic()?;
|
||||
|
||||
write!(child.stdin.as_mut().unwrap(), "{password}").into_diagnostic()?;
|
||||
write!(
|
||||
child
|
||||
.stdin
|
||||
.as_mut()
|
||||
.ok_or_else(|| miette!("Unable to open pipe to stdin"))?,
|
||||
"{password}"
|
||||
)
|
||||
.into_diagnostic()?;
|
||||
|
||||
let output = child.wait_with_output().into_diagnostic()?;
|
||||
|
||||
|
|
@ -399,6 +405,8 @@ fn get_metadata_cache(opts: &GetMetadataOpts) -> Result<ImageMetadata> {
|
|||
|
||||
impl RunDriver for DockerDriver {
|
||||
fn run(opts: &RunOpts) -> std::io::Result<ExitStatus> {
|
||||
trace!("DockerDriver::run({opts:#?})");
|
||||
|
||||
let cid_path = TempDir::new()?;
|
||||
let cid_file = cid_path.path().join("cid");
|
||||
let cid = ContainerId::new(&cid_file, ContainerRuntime::Docker, false);
|
||||
|
|
@ -414,6 +422,8 @@ impl RunDriver for DockerDriver {
|
|||
}
|
||||
|
||||
fn run_output(opts: &RunOpts) -> std::io::Result<std::process::Output> {
|
||||
trace!("DockerDriver::run({opts:#?})");
|
||||
|
||||
let cid_path = TempDir::new()?;
|
||||
let cid_file = cid_path.path().join("cid");
|
||||
let cid = ContainerId::new(&cid_file, ContainerRuntime::Docker, false);
|
||||
|
|
@ -432,7 +442,8 @@ fn docker_run(opts: &RunOpts, cid_file: &Path) -> Command {
|
|||
let command = cmd!(
|
||||
"docker",
|
||||
"run",
|
||||
format!("--cidfile={}", cid_file.display()),
|
||||
"--cidfile",
|
||||
cid_file,
|
||||
if opts.privileged => "--privileged",
|
||||
if opts.remove => "--rm",
|
||||
if opts.pull => "--pull=always",
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use blue_build_utils::{
|
||||
constants::{
|
||||
GITHUB_EVENT_NAME, GITHUB_EVENT_PATH, GITHUB_REF_NAME, GITHUB_SHA, GITHUB_TOKEN_ISSUER_URL,
|
||||
|
|
@ -130,6 +132,10 @@ impl CiDriver for GithubDriver {
|
|||
.trim()
|
||||
.to_lowercase())
|
||||
}
|
||||
|
||||
fn default_ci_file_path() -> PathBuf {
|
||||
PathBuf::from(".github/workflows/build.yml")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use blue_build_utils::{
|
||||
constants::{
|
||||
CI_COMMIT_REF_NAME, CI_COMMIT_SHORT_SHA, CI_DEFAULT_BRANCH, CI_MERGE_REQUEST_IID,
|
||||
|
|
@ -140,6 +142,10 @@ impl CiDriver for GitlabDriver {
|
|||
)
|
||||
.to_lowercase())
|
||||
}
|
||||
|
||||
fn default_ci_file_path() -> PathBuf {
|
||||
PathBuf::from(".gitlab-ci.yml")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use blue_build_utils::{cmd, string_vec};
|
||||
use log::trace;
|
||||
use miette::bail;
|
||||
|
||||
use super::{opts::GenerateTagsOpts, CiDriver, Driver};
|
||||
|
||||
|
|
@ -13,13 +14,11 @@ impl CiDriver for LocalDriver {
|
|||
}
|
||||
|
||||
fn keyless_cert_identity() -> miette::Result<String> {
|
||||
trace!("LocalDriver::keyless_cert_identity()");
|
||||
bail!("Keyless not supported");
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn oidc_provider() -> miette::Result<String> {
|
||||
trace!("LocalDriver::oidc_provider()");
|
||||
bail!("Keyless not supported");
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn generate_tags(opts: &GenerateTagsOpts) -> miette::Result<Vec<String>> {
|
||||
|
|
@ -75,6 +74,10 @@ impl CiDriver for LocalDriver {
|
|||
trace!("LocalDriver::get_registry()");
|
||||
Ok(String::from("localhost"))
|
||||
}
|
||||
|
||||
fn default_ci_file_path() -> PathBuf {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
fn commit_sha() -> Option<String> {
|
||||
|
|
|
|||
|
|
@ -19,19 +19,14 @@ use tempfile::TempDir;
|
|||
|
||||
use crate::{
|
||||
drivers::{
|
||||
opts::{RunOptsEnv, RunOptsVolume},
|
||||
types::ImageMetadata,
|
||||
types::Platform,
|
||||
opts::{BuildOpts, GetMetadataOpts, PushOpts, RunOpts, RunOptsEnv, RunOptsVolume, TagOpts},
|
||||
types::{ImageMetadata, Platform},
|
||||
BuildDriver, DriverVersion, InspectDriver, RunDriver,
|
||||
},
|
||||
logging::{CommandLogging, Logger},
|
||||
signal_handler::{add_cid, remove_cid, ContainerId, ContainerRuntime},
|
||||
};
|
||||
|
||||
use super::{
|
||||
opts::{BuildOpts, GetMetadataOpts, PushOpts, RunOpts, TagOpts},
|
||||
BuildDriver, DriverVersion, InspectDriver, RunDriver,
|
||||
};
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
struct PodmanImageMetadata {
|
||||
|
|
|
|||
|
|
@ -12,17 +12,54 @@ use semver::{Version, VersionReq};
|
|||
|
||||
use crate::drivers::{functions::get_private_key, types::CiDriverType, Driver};
|
||||
|
||||
#[cfg(feature = "sigstore")]
|
||||
use super::sigstore_driver::SigstoreDriver;
|
||||
use super::{
|
||||
buildah_driver::BuildahDriver,
|
||||
cosign_driver::CosignDriver,
|
||||
docker_driver::DockerDriver,
|
||||
github_driver::GithubDriver,
|
||||
gitlab_driver::GitlabDriver,
|
||||
local_driver::LocalDriver,
|
||||
opts::{
|
||||
BuildOpts, BuildTagPushOpts, CheckKeyPairOpts, GenerateImageNameOpts, GenerateKeyPairOpts,
|
||||
GenerateTagsOpts, GetMetadataOpts, PushOpts, RunOpts, SignOpts, SignVerifyOpts, TagOpts,
|
||||
VerifyOpts, VerifyType,
|
||||
},
|
||||
podman_driver::PodmanDriver,
|
||||
skopeo_driver::SkopeoDriver,
|
||||
types::ImageMetadata,
|
||||
};
|
||||
|
||||
trait PrivateDriver {}
|
||||
|
||||
macro_rules! impl_private_driver {
|
||||
($($driver:ty),* $(,)?) => {
|
||||
$(
|
||||
impl PrivateDriver for $driver {}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
impl_private_driver!(
|
||||
Driver,
|
||||
DockerDriver,
|
||||
PodmanDriver,
|
||||
BuildahDriver,
|
||||
GithubDriver,
|
||||
GitlabDriver,
|
||||
LocalDriver,
|
||||
CosignDriver,
|
||||
SkopeoDriver,
|
||||
CiDriverType,
|
||||
);
|
||||
|
||||
#[cfg(feature = "sigstore")]
|
||||
impl_private_driver!(SigstoreDriver);
|
||||
|
||||
/// Trait for retrieving version of a driver.
|
||||
pub trait DriverVersion {
|
||||
#[allow(private_bounds)]
|
||||
pub trait DriverVersion: PrivateDriver {
|
||||
/// The version req string slice that follows
|
||||
/// the semver standard <https://semver.org/>.
|
||||
const VERSION_REQ: &'static str;
|
||||
|
|
@ -43,7 +80,8 @@ pub trait DriverVersion {
|
|||
|
||||
/// Allows agnostic building, tagging
|
||||
/// pushing, and login.
|
||||
pub trait BuildDriver {
|
||||
#[allow(private_bounds)]
|
||||
pub trait BuildDriver: PrivateDriver {
|
||||
/// Runs the build logic for the driver.
|
||||
///
|
||||
/// # Errors
|
||||
|
|
@ -148,7 +186,8 @@ pub trait BuildDriver {
|
|||
}
|
||||
|
||||
/// Allows agnostic inspection of images.
|
||||
pub trait InspectDriver {
|
||||
#[allow(private_bounds)]
|
||||
pub trait InspectDriver: PrivateDriver {
|
||||
/// Gets the metadata on an image tag.
|
||||
///
|
||||
/// # Errors
|
||||
|
|
@ -156,7 +195,9 @@ pub trait InspectDriver {
|
|||
fn get_metadata(opts: &GetMetadataOpts) -> Result<ImageMetadata>;
|
||||
}
|
||||
|
||||
pub trait RunDriver: Sync + Send {
|
||||
/// Allows agnostic running of containers.
|
||||
#[allow(private_bounds)]
|
||||
pub trait RunDriver: PrivateDriver {
|
||||
/// Run a container to perform an action.
|
||||
///
|
||||
/// # Errors
|
||||
|
|
@ -170,7 +211,9 @@ pub trait RunDriver: Sync + Send {
|
|||
fn run_output(opts: &RunOpts) -> std::io::Result<Output>;
|
||||
}
|
||||
|
||||
pub trait SigningDriver {
|
||||
/// Allows agnostic management of signature keys.
|
||||
#[allow(private_bounds)]
|
||||
pub trait SigningDriver: PrivateDriver {
|
||||
/// Generate a new private/public key pair.
|
||||
///
|
||||
/// # Errors
|
||||
|
|
@ -275,7 +318,8 @@ pub trait SigningDriver {
|
|||
}
|
||||
|
||||
/// Allows agnostic retrieval of CI-based information.
|
||||
pub trait CiDriver {
|
||||
#[allow(private_bounds)]
|
||||
pub trait CiDriver: PrivateDriver {
|
||||
/// Determines if we're on the main branch of
|
||||
/// a repository.
|
||||
fn on_default_branch() -> bool;
|
||||
|
|
@ -374,4 +418,6 @@ pub trait CiDriver {
|
|||
/// # Errors
|
||||
/// Will error if the environment variables aren't set.
|
||||
fn get_registry() -> Result<String>;
|
||||
|
||||
fn default_ci_file_path() -> PathBuf;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,12 @@ fn main() {
|
|||
#[cfg(feature = "login")]
|
||||
CommandArgs::Login(mut command) => command.run(),
|
||||
|
||||
#[cfg(feature = "init")]
|
||||
CommandArgs::New(mut command) => command.run(),
|
||||
|
||||
#[cfg(feature = "init")]
|
||||
CommandArgs::Init(mut command) => command.run(),
|
||||
|
||||
#[cfg(feature = "iso")]
|
||||
CommandArgs::GenerateIso(mut command) => command.run(),
|
||||
|
||||
|
|
|
|||
|
|
@ -13,16 +13,16 @@ pub mod completions;
|
|||
pub mod generate;
|
||||
#[cfg(feature = "iso")]
|
||||
pub mod generate_iso;
|
||||
#[cfg(feature = "login")]
|
||||
pub mod login;
|
||||
#[cfg(feature = "validate")]
|
||||
pub mod validate;
|
||||
// #[cfg(feature = "init")]
|
||||
// pub mod init;
|
||||
#[cfg(feature = "init")]
|
||||
pub mod init;
|
||||
#[cfg(not(feature = "switch"))]
|
||||
pub mod local;
|
||||
#[cfg(feature = "login")]
|
||||
pub mod login;
|
||||
#[cfg(feature = "switch")]
|
||||
pub mod switch;
|
||||
#[cfg(feature = "validate")]
|
||||
pub mod validate;
|
||||
|
||||
pub trait BlueBuildCommand {
|
||||
/// Runs the command and returns a result
|
||||
|
|
@ -117,6 +117,14 @@ pub enum CommandArgs {
|
|||
#[cfg(feature = "login")]
|
||||
Login(login::LoginCommand),
|
||||
|
||||
/// Create a new bluebuild project.
|
||||
#[cfg(feature = "init")]
|
||||
New(init::NewCommand),
|
||||
|
||||
/// Create a new bluebuild project.
|
||||
#[cfg(feature = "init")]
|
||||
Init(init::InitCommand),
|
||||
|
||||
/// Validate your recipe file and display
|
||||
/// errors to help fix problems.
|
||||
#[cfg(feature = "validate")]
|
||||
|
|
|
|||
|
|
@ -1,68 +1,142 @@
|
|||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
process,
|
||||
env,
|
||||
fmt::{Display, Write as FmtWrite},
|
||||
fs::{self, OpenOptions},
|
||||
io::{BufWriter, Write as IoWrite},
|
||||
path::PathBuf,
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Args;
|
||||
use log::error;
|
||||
use typed_builder::TypedBuilder;
|
||||
use blue_build_process_management::drivers::{
|
||||
opts::GenerateKeyPairOpts, CiDriver, Driver, DriverArgs, GitlabDriver, SigningDriver,
|
||||
};
|
||||
use blue_build_template::{GitlabCiTemplate, InitReadmeTemplate, Template};
|
||||
use blue_build_utils::{
|
||||
cmd,
|
||||
constants::{COSIGN_PUB_PATH, RECIPE_FILE, RECIPE_PATH, TEMPLATE_REPO_URL},
|
||||
};
|
||||
use bon::Builder;
|
||||
use clap::{crate_version, Args, ValueEnum};
|
||||
use log::{debug, info, trace};
|
||||
use miette::{bail, miette, Context, IntoDiagnostic, Report, Result};
|
||||
use requestty::{questions, Answer, Answers, OnEsc};
|
||||
use semver::Version;
|
||||
|
||||
use super::BlueBuildCommand;
|
||||
use crate::commands::BlueBuildCommand;
|
||||
|
||||
const GITLAB_CI_FILE: &'static str = include_str!("../../templates/init/gitlab-ci.yml.tera");
|
||||
const RECIPE_FILE: &'static str = include_str!("../../templates/init/recipe.yml.tera");
|
||||
const LICENSE_FILE: &'static str = include_str!("../../LICENSE");
|
||||
|
||||
#[derive(Debug, Clone, Default, Args, TypedBuilder)]
|
||||
pub struct NewInitCommon {
|
||||
#[builder(default)]
|
||||
no_git: bool,
|
||||
#[derive(Debug, Default, Clone, Copy, ValueEnum)]
|
||||
pub enum CiProvider {
|
||||
#[default]
|
||||
Github,
|
||||
Gitlab,
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Args, TypedBuilder)]
|
||||
pub struct InitCommand {
|
||||
/// The directory to extract the files into. Defaults to the current directory
|
||||
#[arg()]
|
||||
#[builder(setter(strip_option, into), default)]
|
||||
dir: Option<PathBuf>,
|
||||
impl CiProvider {
|
||||
fn default_ci_file_path(self) -> std::path::PathBuf {
|
||||
match self {
|
||||
Self::Gitlab => GitlabDriver::default_ci_file_path(),
|
||||
Self::None | Self::Github => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn render_file(self) -> Result<String> {
|
||||
match self {
|
||||
Self::Gitlab => GitlabCiTemplate::builder()
|
||||
.version({
|
||||
let version = crate_version!();
|
||||
let version: Version = version.parse().into_diagnostic()?;
|
||||
|
||||
format!("v{}.{}", version.major, version.minor)
|
||||
})
|
||||
.build()
|
||||
.render()
|
||||
.into_diagnostic(),
|
||||
Self::None | Self::Github => unimplemented!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for CiProvider {
|
||||
type Error = Report;
|
||||
|
||||
fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
|
||||
Ok(match value {
|
||||
"Gitlab" => Self::Gitlab,
|
||||
"Github" => Self::Github,
|
||||
"None" => Self::None,
|
||||
_ => bail!("Unable to parse for CiProvider"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&String> for CiProvider {
|
||||
type Error = Report;
|
||||
|
||||
fn try_from(value: &String) -> std::result::Result<Self, Self::Error> {
|
||||
Self::try_from(value.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for CiProvider {
|
||||
type Err = Report;
|
||||
|
||||
fn from_str(s: &str) -> std::prelude::v1::Result<Self, Self::Err> {
|
||||
Self::try_from(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for CiProvider {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match *self {
|
||||
Self::Github => "Github",
|
||||
Self::Gitlab => "Gitlab",
|
||||
Self::None => "None",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Args, Builder)]
|
||||
#[builder(on(String, into))]
|
||||
pub struct NewInitCommon {
|
||||
/// The name of the image for the recipe.
|
||||
#[arg(long)]
|
||||
image_name: Option<String>,
|
||||
|
||||
/// The name of the org where your repo will be located.
|
||||
/// This could end up being your username.
|
||||
#[arg(long)]
|
||||
org_name: Option<String>,
|
||||
|
||||
/// Optional description for the GitHub repository.
|
||||
#[arg(long)]
|
||||
description: Option<String>,
|
||||
|
||||
/// The registry to store the image.
|
||||
#[arg(long)]
|
||||
registry: Option<String>,
|
||||
|
||||
/// The CI provider that will be building the image.
|
||||
///
|
||||
/// GitHub Actions and Gitlab CI are currently the
|
||||
/// officially supported CI providers.
|
||||
#[arg(long, short)]
|
||||
ci_provider: Option<CiProvider>,
|
||||
|
||||
/// Disable setting up git.
|
||||
#[arg(long)]
|
||||
no_git: bool,
|
||||
|
||||
#[clap(flatten)]
|
||||
#[builder(default)]
|
||||
common: NewInitCommon,
|
||||
drivers: DriverArgs,
|
||||
}
|
||||
|
||||
impl BlueBuildCommand for InitCommand {
|
||||
fn try_run(&mut self) -> Result<()> {
|
||||
let base_dir = match self.dir.as_ref() {
|
||||
Some(dir) => dir,
|
||||
None => std::path::Path::new("./"),
|
||||
};
|
||||
|
||||
self.initialize_directory(base_dir);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl InitCommand {
|
||||
fn initialize_directory(&self, base_dir: &Path) {
|
||||
let recipe_path = base_dir.join("recipe.yml");
|
||||
|
||||
let gitlab_ci_path = base_dir.join(".gitlab-ci.yml");
|
||||
|
||||
let readme_path = base_dir.join("README.md");
|
||||
|
||||
let license_path = base_dir.join("LICENSE");
|
||||
|
||||
let scripts_dir = base_dir.join("scripts/");
|
||||
|
||||
let pre_scripts_dir = scripts_dir.join("pre/");
|
||||
|
||||
let post_scripts_dir = scripts_dir.join("post/");
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Args, TypedBuilder)]
|
||||
#[derive(Debug, Clone, Args, Builder)]
|
||||
pub struct NewCommand {
|
||||
#[arg()]
|
||||
dir: PathBuf,
|
||||
|
|
@ -80,3 +154,383 @@ impl BlueBuildCommand for NewCommand {
|
|||
.try_run()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Args, Builder)]
|
||||
pub struct InitCommand {
|
||||
#[clap(skip)]
|
||||
#[builder(into)]
|
||||
dir: Option<PathBuf>,
|
||||
|
||||
#[clap(flatten)]
|
||||
common: NewInitCommon,
|
||||
}
|
||||
|
||||
impl BlueBuildCommand for InitCommand {
|
||||
fn try_run(&mut self) -> Result<()> {
|
||||
Driver::init(self.common.drivers);
|
||||
|
||||
let base_dir = self
|
||||
.dir
|
||||
.get_or_insert(env::current_dir().into_diagnostic()?);
|
||||
|
||||
if base_dir.exists() && fs::read_dir(base_dir).is_ok_and(|dir| dir.count() != 0) {
|
||||
bail!("Must be in an empty directory!");
|
||||
}
|
||||
|
||||
self.start(&self.questions()?)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! when {
|
||||
($check:expr) => {
|
||||
|_answers: &::requestty::Answers| $check
|
||||
};
|
||||
}
|
||||
|
||||
impl InitCommand {
|
||||
const CI_PROVIDER: &str = "ci_provider";
|
||||
const REGISTRY: &str = "registry";
|
||||
const IMAGE_NAME: &str = "image_name";
|
||||
const ORG_NAME: &str = "org_name";
|
||||
const DESCRIPTION: &str = "description";
|
||||
|
||||
fn questions(&self) -> Result<Answers> {
|
||||
let questions = questions![
|
||||
Input {
|
||||
name: Self::IMAGE_NAME,
|
||||
message: "What would you like to name your image?",
|
||||
when: when!(self.common.image_name.is_none()),
|
||||
on_esc: OnEsc::Terminate,
|
||||
},
|
||||
Input {
|
||||
name: Self::REGISTRY,
|
||||
message:
|
||||
"What is the registry for the image? (e.g. ghcr.io or registry.gitlab.com)",
|
||||
when: when!(self.common.registry.is_none()),
|
||||
on_esc: OnEsc::Terminate,
|
||||
},
|
||||
Input {
|
||||
name: Self::ORG_NAME,
|
||||
message: "What is the name of your org/username?",
|
||||
when: when!(self.common.org_name.is_none()),
|
||||
on_esc: OnEsc::Terminate,
|
||||
},
|
||||
Input {
|
||||
name: Self::DESCRIPTION,
|
||||
message: "Write a short description of your image:",
|
||||
when: when!(self.common.description.is_none()),
|
||||
on_esc: OnEsc::Terminate,
|
||||
},
|
||||
Select {
|
||||
name: Self::CI_PROVIDER,
|
||||
message: "Are you building on Github or Gitlab?",
|
||||
when: when!(!self.common.no_git && self.common.ci_provider.is_none()),
|
||||
on_esc: OnEsc::Terminate,
|
||||
choices: vec!["Github", "Gitlab", "None"],
|
||||
}
|
||||
];
|
||||
|
||||
requestty::prompt(questions).into_diagnostic()
|
||||
}
|
||||
|
||||
fn start(&self, answers: &Answers) -> Result<()> {
|
||||
self.clone_repository()?;
|
||||
self.remove_git_directory()?;
|
||||
self.template_readme(answers)?;
|
||||
self.template_ci_file(answers)?;
|
||||
self.update_recipe_file(answers)?;
|
||||
self.generate_signing_files()?;
|
||||
|
||||
if !self.common.no_git {
|
||||
self.initialize_git()?;
|
||||
self.add_files()?;
|
||||
self.initial_commit()?;
|
||||
}
|
||||
|
||||
info!(
|
||||
"Created new BlueBuild project in {}",
|
||||
self.dir.as_ref().unwrap().display()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn clone_repository(&self) -> Result<()> {
|
||||
let dir = self.dir.as_ref().unwrap();
|
||||
trace!("clone_repository()");
|
||||
|
||||
let mut command = cmd!("git", "clone", "-q", TEMPLATE_REPO_URL, dir);
|
||||
trace!("{command:?}");
|
||||
|
||||
let status = command
|
||||
.status()
|
||||
.into_diagnostic()
|
||||
.context("Failed to execute git clone")?;
|
||||
|
||||
if !status.success() {
|
||||
bail!("Failed to clone template repo");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove_git_directory(&self) -> Result<()> {
|
||||
trace!("remove_git_directory()");
|
||||
|
||||
let dir = self.dir.as_ref().unwrap();
|
||||
let git_path = dir.join(".git");
|
||||
|
||||
if git_path.exists() {
|
||||
fs::remove_dir_all(&git_path)
|
||||
.into_diagnostic()
|
||||
.context("Failed to remove .git directory")?;
|
||||
debug!(".git directory removed.");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn initialize_git(&self) -> Result<()> {
|
||||
trace!("initialize_git()");
|
||||
|
||||
let dir = self.dir.as_ref().unwrap();
|
||||
|
||||
let mut command = cmd!("git", "init", "-q", "-b", "main", dir);
|
||||
trace!("{command:?}");
|
||||
|
||||
let status = command
|
||||
.status()
|
||||
.into_diagnostic()
|
||||
.context("Failed to execute git init")?;
|
||||
|
||||
if !status.success() {
|
||||
bail!("Error initializing git");
|
||||
}
|
||||
|
||||
debug!("Initialized git in {}", dir.display());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn initial_commit(&self) -> Result<()> {
|
||||
trace!("initial_commit()");
|
||||
|
||||
let dir = self.dir.as_ref().unwrap();
|
||||
|
||||
let mut command = cmd!(
|
||||
"git",
|
||||
"commit",
|
||||
"-a",
|
||||
"-m",
|
||||
"chore: Initial Commit",
|
||||
current_dir = dir,
|
||||
);
|
||||
trace!("{command:?}");
|
||||
|
||||
let status = command
|
||||
.status()
|
||||
.into_diagnostic()
|
||||
.context("Failed to run git commit")?;
|
||||
|
||||
if !status.success() {
|
||||
bail!("Failed to commit initial changes");
|
||||
}
|
||||
|
||||
debug!("Created initial commit");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_files(&self) -> Result<()> {
|
||||
trace!("add_files()");
|
||||
|
||||
let dir = self.dir.as_ref().unwrap();
|
||||
|
||||
let mut command = cmd!("git", "add", ".", current_dir = dir,);
|
||||
trace!("{command:?}");
|
||||
|
||||
let status = command
|
||||
.status()
|
||||
.into_diagnostic()
|
||||
.context("Failed to run git add")?;
|
||||
|
||||
if !status.success() {
|
||||
bail!("Failed to add files to initial commit");
|
||||
}
|
||||
|
||||
debug!("Added files for initial commit");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn template_readme(&self, answers: &Answers) -> Result<()> {
|
||||
trace!("template_readme()");
|
||||
|
||||
let readme_path = self.dir.as_ref().unwrap().join("README.md");
|
||||
|
||||
let readme = InitReadmeTemplate::builder()
|
||||
.repo_name(
|
||||
self.common
|
||||
.org_name
|
||||
.as_deref()
|
||||
.or_else(|| answers.get(Self::ORG_NAME).and_then(Answer::as_string))
|
||||
.ok_or_else(|| miette!("Failed to get organization name"))?,
|
||||
)
|
||||
.image_name(
|
||||
self.common
|
||||
.image_name
|
||||
.as_deref()
|
||||
.or_else(|| answers.get(Self::IMAGE_NAME).and_then(Answer::as_string))
|
||||
.ok_or_else(|| miette!("Failed to get image name"))?,
|
||||
)
|
||||
.registry(
|
||||
self.common
|
||||
.registry
|
||||
.as_deref()
|
||||
.or_else(|| answers.get(Self::REGISTRY).and_then(Answer::as_string))
|
||||
.ok_or_else(|| miette!("Failed to get registry"))?,
|
||||
)
|
||||
.build();
|
||||
|
||||
debug!("Templating README");
|
||||
let readme = readme.render().into_diagnostic()?;
|
||||
|
||||
debug!("Writing README to {}", readme_path.display());
|
||||
fs::write(readme_path, readme).into_diagnostic()
|
||||
}
|
||||
|
||||
fn template_ci_file(&self, answers: &Answers) -> Result<()> {
|
||||
trace!("template_ci_file()");
|
||||
|
||||
let ci_provider = self
|
||||
.common
|
||||
.ci_provider
|
||||
.ok_or("CLI Arg not set")
|
||||
.or_else(|e| {
|
||||
answers
|
||||
.get(Self::CI_PROVIDER)
|
||||
.and_then(Answer::as_list_item)
|
||||
.map(|li| &li.text)
|
||||
.ok_or_else(|| miette!("Failed to get CI Provider answer:\n{e}"))
|
||||
.and_then(CiProvider::try_from)
|
||||
})?;
|
||||
|
||||
if matches!(ci_provider, CiProvider::Github) {
|
||||
fs::remove_file(self.dir.as_ref().unwrap().join(".github/CODEOWNERS"))
|
||||
.into_diagnostic()?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
fs::remove_dir_all(self.dir.as_ref().unwrap().join(".github")).into_diagnostic()?;
|
||||
|
||||
// Never run for None
|
||||
if matches!(ci_provider, CiProvider::None) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let ci_file_path = self
|
||||
.dir
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.join(ci_provider.default_ci_file_path());
|
||||
let parent_path = ci_file_path
|
||||
.parent()
|
||||
.ok_or_else(|| miette!("Couldn't get parent directory from {ci_file_path:?}"))?;
|
||||
fs::create_dir_all(parent_path)
|
||||
.into_diagnostic()
|
||||
.with_context(|| format!("Couldn't create directory path {parent_path:?}"))?;
|
||||
|
||||
let file = &mut BufWriter::new(
|
||||
OpenOptions::new()
|
||||
.truncate(true)
|
||||
.create(true)
|
||||
.write(true)
|
||||
.open(&ci_file_path)
|
||||
.into_diagnostic()
|
||||
.with_context(|| format!("Failed to open file at {ci_file_path:?}"))?,
|
||||
);
|
||||
|
||||
let template = ci_provider.render_file()?;
|
||||
|
||||
writeln!(file, "{template}")
|
||||
.into_diagnostic()
|
||||
.with_context(|| format!("Failed to write CI file {ci_file_path:?}"))
|
||||
}
|
||||
|
||||
fn update_recipe_file(&self, answers: &Answers) -> Result<()> {
|
||||
trace!("update_recipe_file()");
|
||||
|
||||
let recipe_path = self
|
||||
.dir
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.join(RECIPE_PATH)
|
||||
.join(RECIPE_FILE);
|
||||
|
||||
debug!("Reading {recipe_path:?}");
|
||||
let file = fs::read_to_string(&recipe_path)
|
||||
.into_diagnostic()
|
||||
.with_context(|| format!("Failed to read {recipe_path:?}"))?;
|
||||
|
||||
let description = self
|
||||
.common
|
||||
.description
|
||||
.as_deref()
|
||||
.ok_or("Description arg not set")
|
||||
.or_else(|e| {
|
||||
answers
|
||||
.get(Self::DESCRIPTION)
|
||||
.and_then(Answer::as_string)
|
||||
.ok_or_else(|| miette!("Failed to get description:\n{e}"))
|
||||
})?;
|
||||
let name = self
|
||||
.common
|
||||
.image_name
|
||||
.as_deref()
|
||||
.ok_or("Description arg not set")
|
||||
.or_else(|e| {
|
||||
answers
|
||||
.get(Self::IMAGE_NAME)
|
||||
.and_then(Answer::as_string)
|
||||
.ok_or_else(|| miette!("Failed to get description:\n{e}"))
|
||||
})?;
|
||||
|
||||
let mut new_file_str = String::with_capacity(file.capacity());
|
||||
|
||||
for line in file.lines() {
|
||||
if line.starts_with("description:") {
|
||||
writeln!(&mut new_file_str, "description: {description}").into_diagnostic()?;
|
||||
} else if line.starts_with("name: ") {
|
||||
writeln!(&mut new_file_str, "name: {name}").into_diagnostic()?;
|
||||
} else {
|
||||
writeln!(&mut new_file_str, "{line}").into_diagnostic()?;
|
||||
}
|
||||
}
|
||||
|
||||
let file = &mut BufWriter::new(
|
||||
OpenOptions::new()
|
||||
.truncate(true)
|
||||
.write(true)
|
||||
.open(&recipe_path)
|
||||
.into_diagnostic()
|
||||
.with_context(|| format!("Failed to open {recipe_path:?}"))?,
|
||||
);
|
||||
write!(file, "{new_file_str}")
|
||||
.into_diagnostic()
|
||||
.with_context(|| format!("Failed to write to file {recipe_path:?}"))
|
||||
}
|
||||
|
||||
fn generate_signing_files(&self) -> Result<()> {
|
||||
trace!("generate_signing_files()");
|
||||
|
||||
debug!("Removing old cosign files {COSIGN_PUB_PATH}");
|
||||
fs::remove_file(self.dir.as_ref().unwrap().join(COSIGN_PUB_PATH))
|
||||
.into_diagnostic()
|
||||
.with_context(|| format!("Failed to delete old public file {COSIGN_PUB_PATH}"))?;
|
||||
|
||||
Driver::generate_key_pair(
|
||||
&GenerateKeyPairOpts::builder()
|
||||
.maybe_dir(self.dir.as_ref())
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
4
template/rinja.toml
Normal file
4
template/rinja.toml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
[[syntax]]
|
||||
name = "github-actions"
|
||||
expr_start = "{{{"
|
||||
expr_end = "}}}"
|
||||
|
|
@ -60,6 +60,13 @@ pub struct InitReadmeTemplate<'a> {
|
|||
image_name: Cow<'a, str>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Template, Builder)]
|
||||
#[template(path = "init/gitlab-ci.yml.j2", escape = "none")]
|
||||
#[builder(on(Cow<'_, str>, into))]
|
||||
pub struct GitlabCiTemplate<'a> {
|
||||
version: Cow<'a, str>,
|
||||
}
|
||||
|
||||
fn has_cosign_file() -> bool {
|
||||
trace!("has_cosign_file()");
|
||||
std::env::current_dir()
|
||||
|
|
|
|||
40
template/templates/init/gitlab-ci.yml.j2
Normal file
40
template/templates/init/gitlab-ci.yml.j2
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
workflow:
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS && $CI_PIPELINE_SOURCE == "push"
|
||||
when: never
|
||||
- if: "$CI_COMMIT_TAG"
|
||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||
- if: "$CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS"
|
||||
when: never
|
||||
- if: "$CI_COMMIT_BRANCH"
|
||||
|
||||
stages:
|
||||
- build
|
||||
|
||||
build-image:
|
||||
stage: build
|
||||
image:
|
||||
name: ghcr.io/blue-build/cli:{{ version }}
|
||||
entrypoint: [""]
|
||||
services:
|
||||
- docker:dind
|
||||
parallel:
|
||||
matrix:
|
||||
- RECIPE:
|
||||
# Add your recipe files here
|
||||
- recipe.yml
|
||||
variables:
|
||||
# Setup a secure connection with docker-in-docker service
|
||||
# https://docs.gitlab.com/ee/ci/docker/using_docker_build.html
|
||||
DOCKER_HOST: tcp://docker:2376
|
||||
DOCKER_TLS_CERTDIR: /certs
|
||||
DOCKER_TLS_VERIFY: 1
|
||||
DOCKER_CERT_PATH: $DOCKER_TLS_CERTDIR/client
|
||||
before_script:
|
||||
# Pulls secure files into the build
|
||||
- curl --silent "https://gitlab.com/gitlab-org/incubation-engineering/mobile-devops/download-secure-files/-/raw/main/installer" | bash
|
||||
- export COSIGN_PRIVATE_KEY=$(cat .secure_files/cosign.key)
|
||||
script:
|
||||
- sleep 5 # Wait a bit for the docker-in-docker service to start
|
||||
- bluebuild build --push ./recipes/$RECIPE
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue