Start work on build command
This commit is contained in:
parent
bcd7e710a2
commit
71d93977b9
7 changed files with 226 additions and 18 deletions
|
|
@ -1,2 +1,2 @@
|
|||
[language-server.rust-analyzer.config]
|
||||
cargo.features = ["init"]
|
||||
cargo.features = ["build"]
|
||||
|
|
|
|||
3
Cargo.lock
generated
3
Cargo.lock
generated
|
|
@ -140,7 +140,9 @@ checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
|
|||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"wasm-bindgen",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
|
|
@ -788,6 +790,7 @@ version = "0.2.2"
|
|||
dependencies = [
|
||||
"anyhow",
|
||||
"cfg-if",
|
||||
"chrono",
|
||||
"clap",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ exclude = [".gitlab-ci.yml", ".helix/", ".git/", ".gitignore"]
|
|||
[dependencies]
|
||||
anyhow = "1.0.75"
|
||||
cfg-if = "1.0.0"
|
||||
chrono = "0.4.31"
|
||||
clap = { version = "4.4.4", features = ["derive"] }
|
||||
serde = { version = "1.0.188", features = ["derive"] }
|
||||
serde_json = "1.0.107"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{Parser, Subcommand};
|
||||
|
|
@ -17,7 +17,7 @@ enum CommandArgs {
|
|||
Template {
|
||||
/// The recipe file to create a template from
|
||||
#[arg()]
|
||||
recipe: String,
|
||||
recipe: PathBuf,
|
||||
|
||||
/// Optional Containerfile to use as a template
|
||||
#[arg(short, long)]
|
||||
|
|
@ -39,8 +39,30 @@ enum CommandArgs {
|
|||
/// Build an image from a Containerfile
|
||||
#[cfg(feature = "build")]
|
||||
Build {
|
||||
/// The recipe file to create a template from
|
||||
#[arg()]
|
||||
containerfile: String,
|
||||
recipe: PathBuf,
|
||||
|
||||
#[arg(short, long)]
|
||||
containerfile: Option<PathBuf>,
|
||||
|
||||
#[arg(short, long, default_value = "Containerfile")]
|
||||
output: PathBuf,
|
||||
|
||||
#[arg(short, long)]
|
||||
push: bool,
|
||||
|
||||
#[arg(long)]
|
||||
registry: Option<String>,
|
||||
|
||||
#[arg(long)]
|
||||
registry_path: Option<String>,
|
||||
|
||||
#[arg(long)]
|
||||
username: Option<String>,
|
||||
|
||||
#[arg(long)]
|
||||
password: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -53,14 +75,7 @@ fn main() -> Result<()> {
|
|||
containerfile,
|
||||
output,
|
||||
} => {
|
||||
let (tera, context) = ublue_rs::setup_tera(recipe, containerfile)?;
|
||||
let output_str = tera.render("Containerfile", &context)?;
|
||||
|
||||
if let Some(output) = output {
|
||||
std::fs::write(output, output_str)?;
|
||||
} else {
|
||||
println!("{output_str}");
|
||||
}
|
||||
ublue_rs::template_file(&recipe, containerfile.as_ref(), output.as_ref())?;
|
||||
}
|
||||
#[cfg(feature = "init")]
|
||||
CommandArgs::Init { dir } => {
|
||||
|
|
@ -72,8 +87,25 @@ fn main() -> Result<()> {
|
|||
ublue_rs::init::initialize_directory(base_dir);
|
||||
}
|
||||
#[cfg(feature = "build")]
|
||||
CommandArgs::Build { containerfile: _ } => {
|
||||
println!("Not yet implemented!");
|
||||
CommandArgs::Build {
|
||||
recipe,
|
||||
containerfile,
|
||||
output,
|
||||
push,
|
||||
registry,
|
||||
registry_path,
|
||||
username,
|
||||
password,
|
||||
} => {
|
||||
ublue_rs::template_file(&recipe, containerfile.as_ref(), Some(&output))?;
|
||||
ublue_rs::build::build_image(
|
||||
&recipe,
|
||||
registry.as_ref(),
|
||||
registry_path.as_ref(),
|
||||
username.as_ref(),
|
||||
password.as_ref(),
|
||||
push,
|
||||
)?;
|
||||
todo!();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
152
src/build.rs
Normal file
152
src/build.rs
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
use std::{env, fs, path::Path, process::Command};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use chrono::{Datelike, Local};
|
||||
|
||||
use crate::module_recipe::Recipe;
|
||||
|
||||
fn check_command_exists(command: &str) -> Result<()> {
|
||||
match Command::new("command")
|
||||
.arg("-v")
|
||||
.arg(command)
|
||||
.status()?
|
||||
.success()
|
||||
{
|
||||
true => Ok(()),
|
||||
false => Err(anyhow!(
|
||||
"Command {command} doesn't exist and is required to build the image"
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_tags(recipe: &Recipe) -> Vec<String> {
|
||||
let mut tags: Vec<String> = Vec::new();
|
||||
let image_version = recipe.image_version;
|
||||
let timestamp = Local::now().format("%Y%m%d").to_string();
|
||||
|
||||
if let Ok(_) = env::var("CI") {
|
||||
if let (Ok(mr_iid), Ok(pipeline_source)) = (
|
||||
env::var("CI_MERGE_REQUEST_IID"),
|
||||
env::var("CI_PIPELINE_SOURCE"),
|
||||
) {
|
||||
if pipeline_source == "merge_request_event" {
|
||||
tags.push(format!("{mr_iid}-{image_version}"));
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(commit_sha) = env::var("CI_COMMIT_SHORT_SHA") {
|
||||
tags.push(format!("{commit_sha}-{image_version}"));
|
||||
}
|
||||
|
||||
if let (Ok(commit_branch), Ok(default_branch)) =
|
||||
(env::var("CI_COMMIT_BRANCH"), env::var("CI_DEFAULT_BRANCH"))
|
||||
{
|
||||
if default_branch != commit_branch {
|
||||
tags.push(format!("br-{commit_branch}-{image_version}"));
|
||||
} else {
|
||||
tags.push(format!("{image_version}"));
|
||||
tags.push(format!("{image_version}-{timestamp}"));
|
||||
tags.push(format!("{timestamp}"));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tags.push(format!("{image_version}-local"));
|
||||
}
|
||||
tags
|
||||
}
|
||||
|
||||
fn login(
|
||||
registry: Option<&String>,
|
||||
username: Option<&String>,
|
||||
password: Option<&String>,
|
||||
) -> Result<()> {
|
||||
let registry = match registry {
|
||||
Some(registry) => registry.to_owned(),
|
||||
None => env::var("CI_REGISTRY")?,
|
||||
};
|
||||
|
||||
let username = match username {
|
||||
Some(username) => username.to_owned(),
|
||||
None => env::var("CI_REGISTRY_USER")?,
|
||||
};
|
||||
|
||||
let password = match password {
|
||||
Some(password) => password.to_owned(),
|
||||
None => env::var("CI_REGISTRY_PASSWORD")?,
|
||||
};
|
||||
|
||||
match Command::new("buildah")
|
||||
.arg("login")
|
||||
.arg("-u")
|
||||
.arg(&username)
|
||||
.arg("-p")
|
||||
.arg(&password)
|
||||
.arg(®istry)
|
||||
.status()?
|
||||
.success()
|
||||
{
|
||||
true => eprintln!("Buildah login success!"),
|
||||
false => return Err(anyhow!("Failed to login for buildah!")),
|
||||
}
|
||||
|
||||
match Command::new("cosign")
|
||||
.arg("login")
|
||||
.arg("-u")
|
||||
.arg(&username)
|
||||
.arg("-p")
|
||||
.arg(&password)
|
||||
.arg(®istry)
|
||||
.status()?
|
||||
.success()
|
||||
{
|
||||
true => eprintln!("Cosign login success!"),
|
||||
false => return Err(anyhow!("Failed to login for cosign!")),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate_full_image_name(
|
||||
recipe: &Recipe,
|
||||
registry: Option<&String>,
|
||||
registry_path: Option<&String>,
|
||||
) -> Result<String> {
|
||||
let image_name = recipe.name.as_str();
|
||||
|
||||
if let Ok(_) = env::var("CI") {
|
||||
// if let (Ok())
|
||||
todo!()
|
||||
} else {
|
||||
Ok(image_name.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
fn build(recipe: &Recipe, image_name: &str, tags: &[String]) -> Result<()> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn build_image(
|
||||
recipe: &Path,
|
||||
registry: Option<&String>,
|
||||
registry_path: Option<&String>,
|
||||
username: Option<&String>,
|
||||
password: Option<&String>,
|
||||
push: bool,
|
||||
) -> Result<()> {
|
||||
check_command_exists("buildah")?;
|
||||
if push {
|
||||
check_command_exists("cosign")?;
|
||||
check_command_exists("skopeo")?;
|
||||
login(registry.clone(), username.clone(), password.clone())?;
|
||||
}
|
||||
|
||||
let recipe: Recipe = serde_yaml::from_str(fs::read_to_string(recipe)?.as_str())?;
|
||||
|
||||
let tags = generate_tags(&recipe);
|
||||
|
||||
let image_name = generate_full_image_name(&recipe, registry.clone(), registry_path.clone())?;
|
||||
|
||||
build(&recipe, &image_name, &tags)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
26
src/lib.rs
26
src/lib.rs
|
|
@ -11,12 +11,15 @@
|
|||
#[cfg(feature = "init")]
|
||||
pub mod init;
|
||||
|
||||
#[cfg(feature = "build")]
|
||||
pub mod build;
|
||||
|
||||
pub mod module_recipe;
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs::{self, read_to_string},
|
||||
path::PathBuf,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
|
|
@ -25,9 +28,8 @@ use tera::{Context, Tera};
|
|||
|
||||
pub const DEFAULT_CONTAINERFILE: &str = include_str!("../templates/Containerfile.tera");
|
||||
|
||||
pub fn setup_tera(recipe: String, containerfile: Option<PathBuf>) -> Result<(Tera, Context)> {
|
||||
let recipe_de =
|
||||
serde_yaml::from_str::<Recipe>(fs::read_to_string(PathBuf::from(&recipe))?.as_str())?;
|
||||
fn setup_tera(recipe: &Path, containerfile: Option<&PathBuf>) -> Result<(Tera, Context)> {
|
||||
let recipe_de = serde_yaml::from_str::<Recipe>(fs::read_to_string(&recipe)?.as_str())?;
|
||||
|
||||
let mut context = Context::from_serialize(recipe_de)?;
|
||||
context.insert("recipe", &recipe);
|
||||
|
|
@ -94,3 +96,19 @@ pub fn setup_tera(recipe: String, containerfile: Option<PathBuf>) -> Result<(Ter
|
|||
|
||||
Ok((tera, context))
|
||||
}
|
||||
|
||||
pub fn template_file(
|
||||
recipe: &Path,
|
||||
containerfile: Option<&PathBuf>,
|
||||
output: Option<&PathBuf>,
|
||||
) -> Result<()> {
|
||||
let (tera, context) = setup_tera(recipe, containerfile)?;
|
||||
let output_str = tera.render("Containerfile", &context)?;
|
||||
|
||||
if let Some(output) = output {
|
||||
std::fs::write(output, output_str)?;
|
||||
} else {
|
||||
println!("{output_str}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ use serde_yaml::Value;
|
|||
pub struct Recipe {
|
||||
pub name: String,
|
||||
|
||||
pub description: String,
|
||||
|
||||
#[serde(alias = "base-image")]
|
||||
pub base_image: String,
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue