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]
|
[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 = [
|
dependencies = [
|
||||||
"android-tzdata",
|
"android-tzdata",
|
||||||
"iana-time-zone",
|
"iana-time-zone",
|
||||||
|
"js-sys",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
|
"wasm-bindgen",
|
||||||
"windows-targets",
|
"windows-targets",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -788,6 +790,7 @@ version = "0.2.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ exclude = [".gitlab-ci.yml", ".helix/", ".git/", ".gitignore"]
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.75"
|
anyhow = "1.0.75"
|
||||||
cfg-if = "1.0.0"
|
cfg-if = "1.0.0"
|
||||||
|
chrono = "0.4.31"
|
||||||
clap = { version = "4.4.4", features = ["derive"] }
|
clap = { version = "4.4.4", features = ["derive"] }
|
||||||
serde = { version = "1.0.188", features = ["derive"] }
|
serde = { version = "1.0.188", features = ["derive"] }
|
||||||
serde_json = "1.0.107"
|
serde_json = "1.0.107"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use std::path::PathBuf;
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
|
|
@ -17,7 +17,7 @@ enum CommandArgs {
|
||||||
Template {
|
Template {
|
||||||
/// The recipe file to create a template from
|
/// The recipe file to create a template from
|
||||||
#[arg()]
|
#[arg()]
|
||||||
recipe: String,
|
recipe: PathBuf,
|
||||||
|
|
||||||
/// Optional Containerfile to use as a template
|
/// Optional Containerfile to use as a template
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
|
|
@ -39,8 +39,30 @@ enum CommandArgs {
|
||||||
/// Build an image from a Containerfile
|
/// Build an image from a Containerfile
|
||||||
#[cfg(feature = "build")]
|
#[cfg(feature = "build")]
|
||||||
Build {
|
Build {
|
||||||
|
/// The recipe file to create a template from
|
||||||
#[arg()]
|
#[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,
|
containerfile,
|
||||||
output,
|
output,
|
||||||
} => {
|
} => {
|
||||||
let (tera, context) = ublue_rs::setup_tera(recipe, containerfile)?;
|
ublue_rs::template_file(&recipe, containerfile.as_ref(), output.as_ref())?;
|
||||||
let output_str = tera.render("Containerfile", &context)?;
|
|
||||||
|
|
||||||
if let Some(output) = output {
|
|
||||||
std::fs::write(output, output_str)?;
|
|
||||||
} else {
|
|
||||||
println!("{output_str}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#[cfg(feature = "init")]
|
#[cfg(feature = "init")]
|
||||||
CommandArgs::Init { dir } => {
|
CommandArgs::Init { dir } => {
|
||||||
|
|
@ -72,8 +87,25 @@ fn main() -> Result<()> {
|
||||||
ublue_rs::init::initialize_directory(base_dir);
|
ublue_rs::init::initialize_directory(base_dir);
|
||||||
}
|
}
|
||||||
#[cfg(feature = "build")]
|
#[cfg(feature = "build")]
|
||||||
CommandArgs::Build { containerfile: _ } => {
|
CommandArgs::Build {
|
||||||
println!("Not yet implemented!");
|
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!();
|
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")]
|
#[cfg(feature = "init")]
|
||||||
pub mod init;
|
pub mod init;
|
||||||
|
|
||||||
|
#[cfg(feature = "build")]
|
||||||
|
pub mod build;
|
||||||
|
|
||||||
pub mod module_recipe;
|
pub mod module_recipe;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
fs::{self, read_to_string},
|
fs::{self, read_to_string},
|
||||||
path::PathBuf,
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
@ -25,9 +28,8 @@ use tera::{Context, Tera};
|
||||||
|
|
||||||
pub const DEFAULT_CONTAINERFILE: &str = include_str!("../templates/Containerfile.tera");
|
pub const DEFAULT_CONTAINERFILE: &str = include_str!("../templates/Containerfile.tera");
|
||||||
|
|
||||||
pub fn setup_tera(recipe: String, containerfile: Option<PathBuf>) -> Result<(Tera, Context)> {
|
fn setup_tera(recipe: &Path, containerfile: Option<&PathBuf>) -> Result<(Tera, Context)> {
|
||||||
let recipe_de =
|
let recipe_de = serde_yaml::from_str::<Recipe>(fs::read_to_string(&recipe)?.as_str())?;
|
||||||
serde_yaml::from_str::<Recipe>(fs::read_to_string(PathBuf::from(&recipe))?.as_str())?;
|
|
||||||
|
|
||||||
let mut context = Context::from_serialize(recipe_de)?;
|
let mut context = Context::from_serialize(recipe_de)?;
|
||||||
context.insert("recipe", &recipe);
|
context.insert("recipe", &recipe);
|
||||||
|
|
@ -94,3 +96,19 @@ pub fn setup_tera(recipe: String, containerfile: Option<PathBuf>) -> Result<(Ter
|
||||||
|
|
||||||
Ok((tera, context))
|
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 struct Recipe {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|
||||||
|
pub description: String,
|
||||||
|
|
||||||
#[serde(alias = "base-image")]
|
#[serde(alias = "base-image")]
|
||||||
pub base_image: String,
|
pub base_image: String,
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue