feat: block overriding (#74)

This PR helps transition users who may not realize that we override
their Containefile.

---------

Co-authored-by: Gerald Pinder <gmpinder@gmail.com>
This commit is contained in:
Hikari 2024-02-21 17:34:28 -06:00 committed by GitHub
parent 84de477635
commit ee2a834b28
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 116 additions and 12 deletions

View file

@ -35,7 +35,9 @@ serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde_yaml = "0.9.30"
signal-hook = { version = "0.3.17", optional = true }
signal-hook-tokio = { version = "0.3.1", features = ["futures-v0_3"], optional = true }
signal-hook-tokio = { version = "0.3.1", features = [
"futures-v0_3",
], optional = true }
shadow-rs = { version = "0.26" }
sigstore = { version = "0.8.0", optional = true }
tokio = { version = "1", features = ["full"], optional = true }
@ -48,7 +50,13 @@ which = "6"
[features]
default = []
nightly = ["builtin-podman"]
builtin-podman = ["podman-api", "tokio", "futures-util", "signal-hook-tokio", "signal-hook"]
builtin-podman = [
"podman-api",
"tokio",
"futures-util",
"signal-hook-tokio",
"signal-hook",
]
tls = ["podman-api/tls", "builtin-podman"]
[dev-dependencies]

View file

@ -9,6 +9,7 @@ use std::{
use anyhow::{anyhow, bail, Result};
use clap::Args;
use colorized::{Color, Colors};
use log::{debug, info, trace, warn};
use typed_builder::TypedBuilder;
use uuid::Uuid;
@ -34,7 +35,12 @@ use tokio::{
sync::oneshot::{self, Sender},
};
use crate::{commands::template::TemplateCommand, constants::*, module_recipe::Recipe, ops};
use crate::{
commands::template::TemplateCommand,
constants::{self, *},
module_recipe::Recipe,
ops,
};
use super::BlueBuildCommand;
@ -61,6 +67,15 @@ pub struct BuildCommand {
#[builder(default)]
push: bool,
/// Allow `bluebuild` to overwrite an existing
/// Containerfile without confirmation.
///
/// This is not needed if the Containerfile is in
/// .gitignore or has already been built by `bluebuild`.
#[arg(short, long)]
#[builder(default)]
force: bool,
/// Archives the built image into a tarfile
/// in the specified directory.
#[arg(short, long)]
@ -146,8 +161,56 @@ impl BlueBuildCommand for BuildCommand {
fn try_run(&mut self) -> Result<()> {
trace!("BuildCommand::try_run()");
let build_id = Uuid::new_v4();
// Check if the Containerfile exists
// - If doesn't => *Build*
// - If it does:
// - check entry in .gitignore
// -> If it is => *Build*
// -> If isn't:
// - check if it has the BlueBuild tag (LABEL)
// -> If it does => *Ask* to add to .gitignore and remove from git
// -> If it doesn't => *Ask* to continue and override the file
let container_file_path = Path::new(constants::CONTAINER_FILE);
if !self.force && container_file_path.exists() {
let gitignore = fs::read_to_string(constants::GITIGNORE_PATH)?;
let is_ignored = gitignore
.lines()
.any(|line: &str| line.contains(constants::CONTAINER_FILE));
if !is_ignored {
let containerfile = fs::read_to_string(container_file_path)?;
let has_label = containerfile.lines().any(|line| {
let label = format!("LABEL {}", constants::BUILD_ID_LABEL);
line.to_string().trim().starts_with(&label)
});
let question = requestty::Question::confirm("build")
.message(
if has_label {
LABELED_ERROR_MESSAGE
} else {
NO_LABEL_ERROR_MESSAGE
}
.color(Colors::BrightYellowFg),
)
.default(true)
.build();
if let Ok(answer) = requestty::prompt_one(question) {
if answer.as_bool().unwrap_or(false) {
ops::append_to_file(
constants::GITIGNORE_PATH,
&format!("/{}", constants::CONTAINER_FILE),
)?;
}
}
}
}
let build_id = Uuid::new_v4();
if self.push && self.archive.is_some() {
bail!("You cannot use '--archive' and '--push' at the same time");
}
@ -223,8 +286,7 @@ impl BuildCommand {
image_name.to_string()
} else {
tags.first()
.map(|t| format!("{image_name}:{t}"))
.unwrap_or_else(|| image_name.to_string())
.map_or_else(|| image_name.to_string(), |t| format!("{image_name}:{t}"))
};
debug!("Full tag is {first_image_name}");
@ -473,8 +535,7 @@ impl BuildCommand {
image_name.to_string()
} else {
tags.first()
.map(|t| format!("{image_name}:{t}"))
.unwrap_or_else(|| image_name.to_string())
.map_or_else(|| image_name.to_string(), |t| format!("{image_name}:{t}"))
};
info!("Building image {full_image}");
@ -575,9 +636,7 @@ fn sign_images(image_name: &str, tag: Option<&str>) -> Result<()> {
env::set_var("COSIGN_YES", "true");
let image_digest = get_image_digest(image_name, tag)?;
let image_name_tag = tag
.map(|t| format!("{image_name}:{t}"))
.unwrap_or_else(|| image_name.to_owned());
let image_name_tag = tag.map_or_else(|| image_name.to_owned(), |t| format!("{image_name}:{t}"));
match (
env::var(CI_DEFAULT_BRANCH),

View file

@ -30,6 +30,15 @@ pub struct LocalCommonArgs {
#[arg(short, long)]
#[builder(default)]
reboot: bool,
/// Allow `bluebuild` to overwrite an existing
/// Containerfile without confirmation.
///
/// This is not needed if the Containerfile is in
/// .gitignore or has already been built by `bluebuild`.
#[arg(short, long)]
#[builder(default)]
force: bool,
}
#[derive(Default, Clone, Debug, TypedBuilder, Args)]
@ -49,6 +58,7 @@ impl BlueBuildCommand for UpgradeCommand {
let mut build = BuildCommand::builder()
.recipe(self.common.recipe.clone())
.archive(LOCAL_BUILD)
.force(self.common.force)
.build();
let image_name = build.generate_full_image_name(&recipe)?;
@ -96,6 +106,7 @@ impl BlueBuildCommand for RebaseCommand {
let mut build = BuildCommand::builder()
.recipe(self.common.recipe.clone())
.archive(LOCAL_BUILD)
.force(self.common.force)
.build();
let image_name = build.generate_full_image_name(&recipe)?;

View file

@ -1,7 +1,9 @@
// Paths
pub const ARCHIVE_SUFFIX: &str = "tar.gz";
pub const COSIGN_PATH: &str = "./cosign.pub";
pub const GITIGNORE_PATH: &str = ".gitignore";
pub const LOCAL_BUILD: &str = "/etc/bluebuild";
pub const CONTAINER_FILE: &str = "Containerfile";
pub const MODULES_PATH: &str = "./config/modules";
pub const RECIPE_PATH: &str = "./config/recipe.yml";
pub const RUN_PODMAN_SOCK: &str = "/run/podman/podman.sock";
@ -54,3 +56,13 @@ pub const UNKNOWN_SHELL: &str = "<unknown shell>";
pub const UNKNOWN_VERSION: &str = "<unknown version>";
pub const UNKNOWN_TERMINAL: &str = "<unknown terminal>";
pub const GITHUB_CHAR_LIMIT: usize = 8100; // Magic number accepted by Github
// Messages
pub const LABELED_ERROR_MESSAGE: &str =
"It looks you have a BlueBuild-generated Containerfile that is not .gitignored. \
Do you want to remove it from git and add it to .gitignore? (This will still continue the build)";
pub const NO_LABEL_ERROR_MESSAGE: &str =
"It looks you have a Containerfile that has not been generated by BlueBuild. \
Running `build` will override your Containerfile and add an entry to the .gitignore. \
Do you want to continue?";

View file

@ -1,7 +1,8 @@
use std::{io::Write, process::Command};
use anyhow::{anyhow, Result};
use format_serde_error::SerdeError;
use log::{debug, trace};
use std::process::Command;
pub fn check_command_exists(command: &str) -> Result<()> {
trace!("check_command_exists({command})");
@ -23,6 +24,19 @@ pub fn check_command_exists(command: &str) -> Result<()> {
}
}
pub fn append_to_file(file_path: &str, content: &str) -> Result<()> {
trace!("append_to_file({file_path}, {content})");
debug!("Appending {content} to {file_path}");
let mut file = std::fs::OpenOptions::new()
.append(true)
.create(true)
.open(file_path)?;
writeln!(file, "\n{content}")?;
Ok(())
}
pub fn serde_yaml_err(contents: &str) -> impl Fn(serde_yaml::Error) -> SerdeError + '_ {
|err: serde_yaml::Error| {
let location = err.location();