feat: with init command
Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
parent
91ee9d4387
commit
6c5fed87b1
1898
Cargo.lock
generated
1898
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,2 +1,3 @@
|
||||
[workspace]
|
||||
members = ["cuddle_cli", "examples/base"]
|
||||
members = ["cuddle_cli", "examples/base", "ci"]
|
||||
resolver = "2"
|
||||
|
11
ci/Cargo.toml
Normal file
11
ci/Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "ci"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
dagger-sdk = "0.2.22"
|
||||
eyre = "0.6.8"
|
||||
tokio = { version = "1.28.2", features = ["full"] }
|
112
ci/src/main.rs
Normal file
112
ci/src/main.rs
Normal file
@ -0,0 +1,112 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use dagger_sdk::{
|
||||
Container, ContainerPublishOptsBuilder, Directory, HostDirectoryOptsBuilder, Query,
|
||||
QueryContainerOptsBuilder,
|
||||
};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> eyre::Result<()> {
|
||||
let client = dagger_sdk::connect().await?;
|
||||
|
||||
let src = client.host().directory_opts(
|
||||
".",
|
||||
HostDirectoryOptsBuilder::default()
|
||||
.include(vec![
|
||||
"ci/",
|
||||
"cuddle_cli/",
|
||||
"examples",
|
||||
"Cargo.lock",
|
||||
"Cargo.toml",
|
||||
])
|
||||
.build()?,
|
||||
);
|
||||
|
||||
client
|
||||
.container()
|
||||
.publish_opts(
|
||||
"kasperhermansen/cuddle:dev",
|
||||
ContainerPublishOptsBuilder::default()
|
||||
.platform_variants(vec![
|
||||
dind_image(client.clone(), src.clone(), "x86_64", "linux/amd64")
|
||||
.await?
|
||||
.id()
|
||||
.await?,
|
||||
dind_image(client.clone(), src.clone(), "aarch64", "linux/arm64/v8")
|
||||
.await?
|
||||
.id()
|
||||
.await?,
|
||||
])
|
||||
.build()?,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn dind_image(
|
||||
client: Arc<Query>,
|
||||
src: Directory,
|
||||
architecture: &str,
|
||||
platform: &str,
|
||||
) -> eyre::Result<Container> {
|
||||
let rust_bin = client
|
||||
.container_opts(
|
||||
QueryContainerOptsBuilder::default()
|
||||
.platform(platform)
|
||||
.build()?,
|
||||
)
|
||||
.from("rust:1.70.0-slim-bullseye")
|
||||
.with_exec(vec![
|
||||
"rustup",
|
||||
"target",
|
||||
"add",
|
||||
&format!("{architecture}-unknown-linux-musl"),
|
||||
])
|
||||
.with_exec(vec!["update-ca-certificates"])
|
||||
.with_exec(vec!["apt-get", "update"])
|
||||
.with_exec(vec!["apt-get", "upgrade", "-y"])
|
||||
.with_exec(vec![
|
||||
"apt-get",
|
||||
"install",
|
||||
"-y",
|
||||
"-q",
|
||||
"build-essential",
|
||||
"curl",
|
||||
"git",
|
||||
"musl-tools",
|
||||
"musl-dev",
|
||||
"libz-dev",
|
||||
])
|
||||
.with_workdir("/app/cuddle/")
|
||||
.with_directory(".", src.id().await?)
|
||||
.with_exec(vec![
|
||||
"cargo",
|
||||
"install",
|
||||
"--target",
|
||||
&format!("{architecture}-unknown-linux-musl"),
|
||||
"--path",
|
||||
"cuddle_cli",
|
||||
"--profile=release",
|
||||
]);
|
||||
|
||||
let final_image = client
|
||||
.container_opts(
|
||||
QueryContainerOptsBuilder::default()
|
||||
.platform(platform)
|
||||
.build()?,
|
||||
)
|
||||
.from("docker:dind")
|
||||
.with_directory(
|
||||
"/usr/local/cargo/bin/",
|
||||
rust_bin.directory("/usr/local/cargo/bin/").id().await?,
|
||||
);
|
||||
|
||||
let path_env = final_image.env_variable("PATH").await?;
|
||||
|
||||
let final_image = final_image
|
||||
.with_env_variable("PATH", format!("{path_env}:/usr/local/cargo/bin"))
|
||||
.with_exec(vec![""]);
|
||||
|
||||
Ok(final_image)
|
||||
}
|
@ -15,12 +15,22 @@ anyhow = "1.0.60"
|
||||
serde = { version = "1.0.143", features = ["derive"] }
|
||||
serde_yaml = "0.9.4"
|
||||
walkdir = "2.3.2"
|
||||
git2 = { version = "0.15.0", features = ["ssh"] }
|
||||
clap = "3.2.16"
|
||||
git2 = { version = "0.17.2", default-features = false, features = [
|
||||
"vendored-libgit2",
|
||||
"vendored-openssl",
|
||||
] }
|
||||
clap = { version = "4.3.4", features = ["env", "string"] }
|
||||
envconfig = "0.10.0"
|
||||
dirs = "4.0.0"
|
||||
dirs = "5.0.1"
|
||||
tracing = "0.1.36"
|
||||
tracing-subscriber = { version = "0.3.15", features = ["json"] }
|
||||
log = { version = "0.4.17", features = ["std", "kv_unstable"] }
|
||||
openssl = { version = "0.10", features = ["vendored"] }
|
||||
tera = "1.17.0"
|
||||
openssl = { version = "0.10.54", features = ["vendored"] }
|
||||
libz-sys = { version = "1.1.9", default-features = false, features = [
|
||||
"libc",
|
||||
"static",
|
||||
] }
|
||||
inquire = { version = "0.6.2", features = ["console"] }
|
||||
tempfile = { version = "3.6.0" }
|
||||
serde_json = "1.0.97"
|
||||
|
@ -18,20 +18,20 @@ use crate::{
|
||||
use self::subcommands::render_template::RenderTemplateCommand;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CuddleCli<'a> {
|
||||
pub struct CuddleCli {
|
||||
scripts: Vec<CuddleAction>,
|
||||
variables: Vec<CuddleVariable>,
|
||||
context: Arc<Mutex<Vec<CuddleContext>>>,
|
||||
command: Option<Command<'a>>,
|
||||
command: Option<Command>,
|
||||
tmp_dir: Option<PathBuf>,
|
||||
config: CuddleConfig,
|
||||
}
|
||||
|
||||
impl<'a> CuddleCli<'a> {
|
||||
impl CuddleCli {
|
||||
pub fn new(
|
||||
context: Arc<Mutex<Vec<CuddleContext>>>,
|
||||
config: CuddleConfig,
|
||||
) -> anyhow::Result<CuddleCli<'a>> {
|
||||
) -> anyhow::Result<CuddleCli> {
|
||||
let mut cli = CuddleCli {
|
||||
scripts: vec![],
|
||||
variables: vec![],
|
||||
@ -159,6 +159,7 @@ impl<'a> CuddleCli<'a> {
|
||||
|
||||
root_cmd = subcommands::x::build_command(root_cmd, self.clone());
|
||||
root_cmd = subcommands::render_template::build_command(root_cmd);
|
||||
root_cmd = subcommands::init::build_command(root_cmd, self.clone());
|
||||
|
||||
self.command = Some(root_cmd);
|
||||
|
||||
@ -176,13 +177,15 @@ impl<'a> CuddleCli<'a> {
|
||||
.and_then(|cmd| cmd.execute())?;
|
||||
Ok(())
|
||||
}
|
||||
Some(("init", sub_matches)) => {
|
||||
subcommands::init::execute_init(sub_matches, self.clone())
|
||||
}
|
||||
_ => Err(anyhow::anyhow!("could not find a match")),
|
||||
};
|
||||
|
||||
match res {
|
||||
Ok(()) => {}
|
||||
Err(e) => {
|
||||
let _ = cli.print_long_help();
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
|
147
cuddle_cli/src/cli/subcommands/init.rs
Normal file
147
cuddle_cli/src/cli/subcommands/init.rs
Normal file
@ -0,0 +1,147 @@
|
||||
use std::fs::{create_dir_all, read, read_dir};
|
||||
use std::io::Write;
|
||||
|
||||
use clap::{ArgMatches, Command};
|
||||
|
||||
use crate::cli::CuddleCli;
|
||||
|
||||
pub fn build_command(root_cmd: Command, cli: CuddleCli) -> Command {
|
||||
let mut repo_url = clap::Arg::new("repo").long("repo").short('r');
|
||||
|
||||
if let Ok(cuddle_template_url) = std::env::var("CUDDLE_TEMPLATE_URL") {
|
||||
repo_url = repo_url.default_value(cuddle_template_url);
|
||||
} else {
|
||||
repo_url = repo_url.required(true);
|
||||
}
|
||||
|
||||
let mut execute_cmd = Command::new("init")
|
||||
.about("init bootstraps a repository from a template")
|
||||
.arg(repo_url)
|
||||
.arg(clap::Arg::new("name"))
|
||||
.arg(clap::Arg::new("path"));
|
||||
|
||||
root_cmd.subcommand(execute_cmd)
|
||||
}
|
||||
|
||||
pub fn execute_init(exe_submatch: &ArgMatches, cli: CuddleCli) -> anyhow::Result<()> {
|
||||
let repo = exe_submatch.get_one::<String>("repo").unwrap();
|
||||
let name = exe_submatch.get_one::<String>("name");
|
||||
let path = exe_submatch.get_one::<String>("path");
|
||||
|
||||
tracing::info!("Downloading: {}", repo);
|
||||
|
||||
create_dir_all(std::env::temp_dir())?;
|
||||
|
||||
let tmpdir = tempfile::tempdir()?;
|
||||
|
||||
let tmpdir_path = tmpdir.path().canonicalize()?;
|
||||
|
||||
let output = std::process::Command::new("git")
|
||||
.args(&["clone", repo, "."])
|
||||
.current_dir(tmpdir_path)
|
||||
.output()?;
|
||||
|
||||
std::io::stdout().write_all(&output.stdout)?;
|
||||
std::io::stderr().write_all(&output.stderr)?;
|
||||
|
||||
let templates_path = tmpdir.path().join("cuddle-templates.json");
|
||||
let template_path = tmpdir.path().join("cuddle-template.json");
|
||||
let templates = if templates_path.exists() {
|
||||
let templates = read(templates_path)?;
|
||||
let templates: CuddleTemplates = serde_json::from_slice(&templates)?;
|
||||
|
||||
let mut single_templates = Vec::new();
|
||||
|
||||
for template_name in templates.templates.iter() {
|
||||
let template = read(
|
||||
tmpdir
|
||||
.path()
|
||||
.join(template_name)
|
||||
.join("cuddle-template.json"),
|
||||
)?;
|
||||
let template = serde_json::from_slice::<CuddleTemplate>(&template)?;
|
||||
|
||||
single_templates.push((template_name, template))
|
||||
}
|
||||
|
||||
single_templates
|
||||
.into_iter()
|
||||
.map(|(name, template)| (name.clone(), tmpdir.path().join(name), template))
|
||||
.collect::<Vec<_>>()
|
||||
} else if template_path.exists() {
|
||||
let template = read(template_path)?;
|
||||
let template = serde_json::from_slice::<CuddleTemplate>(&template)?;
|
||||
vec![(template.clone().name, tmpdir.path().to_path_buf(), template)]
|
||||
} else {
|
||||
anyhow::bail!("No cuddle-template.json or cuddle-templates.json found");
|
||||
};
|
||||
|
||||
let template = match name {
|
||||
Some(name) => {
|
||||
let template = read(tmpdir.path().join(name).join("cuddle-template.json"))?;
|
||||
let template = serde_json::from_slice::<CuddleTemplate>(&template)?;
|
||||
Ok((name.clone(), tmpdir.path().join(name), template))
|
||||
}
|
||||
None => {
|
||||
if templates.len() > 1 {
|
||||
let name = inquire::Select::new(
|
||||
"template",
|
||||
templates.iter().map(|t| t.0.clone()).collect(),
|
||||
)
|
||||
.with_help_message("name of which template to use")
|
||||
.prompt()?;
|
||||
|
||||
let found_template = templates
|
||||
.iter()
|
||||
.find(|item| item.0 == name)
|
||||
.ok_or(anyhow::anyhow!("could not find an item with that name"))?;
|
||||
|
||||
Ok(found_template.clone())
|
||||
} else if templates.len() == 1 {
|
||||
Ok(templates[0].clone())
|
||||
} else {
|
||||
Err(anyhow::anyhow!("No templates found, with any valid names"))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let (name, template_dir, template) = template?;
|
||||
|
||||
let path = match path {
|
||||
Some(path) => path.clone(),
|
||||
None => inquire::Text::new("path")
|
||||
.with_help_message("to where it should be placed")
|
||||
.with_default(".")
|
||||
.prompt()?,
|
||||
};
|
||||
|
||||
create_dir_all(&path)?;
|
||||
let dir = std::fs::read_dir(&path)?;
|
||||
if dir.count() != 0 {
|
||||
anyhow::bail!("Directory {} is not empty", &path);
|
||||
}
|
||||
|
||||
for entry in read_dir(template_dir)? {
|
||||
let entry = entry?;
|
||||
let entry_path = entry.path();
|
||||
let name = entry.file_name();
|
||||
|
||||
if name == "cuddle-template.json" || name == "cuddle-templates.json" {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::fs::rename(entry_path, std::path::PathBuf::from(&path).join(name))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
struct CuddleTemplates {
|
||||
pub templates: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
struct CuddleTemplate {
|
||||
pub name: String,
|
||||
}
|
@ -1,2 +1,3 @@
|
||||
pub mod init;
|
||||
pub mod render_template;
|
||||
pub mod x;
|
||||
|
@ -4,7 +4,7 @@ use clap::{Arg, ArgMatches, Command};
|
||||
|
||||
use crate::{cli::CuddleCli, model::CuddleVariable};
|
||||
|
||||
pub fn build_command<'a>(root_cmd: Command<'a>) -> Command<'a> {
|
||||
pub fn build_command(root_cmd: Command) -> Command {
|
||||
root_cmd.subcommand(
|
||||
Command::new("render_template")
|
||||
.about("renders a jinja compatible template")
|
||||
|
@ -2,14 +2,15 @@ use clap::{ArgMatches, Command};
|
||||
|
||||
use crate::cli::CuddleCli;
|
||||
|
||||
pub fn build_command<'a>(root_cmd: Command<'a>, cli: CuddleCli<'a>) -> Command<'a> {
|
||||
pub fn build_command(root_cmd: Command, cli: CuddleCli) -> Command {
|
||||
if cli.scripts.len() > 0 {
|
||||
let mut execute_cmd = Command::new("x").about("x is your entry into your domains scripts, scripts inherited from parents will also be present here").subcommand_required(true);
|
||||
let execute_cmd_about = "x is your entry into your domains scripts, scripts inherited from parents will also be present here";
|
||||
let mut execute_cmd = Command::new("x")
|
||||
.about(execute_cmd_about)
|
||||
.subcommand_required(true);
|
||||
|
||||
for script in cli.scripts.iter() {
|
||||
let action_cmd = Command::new(script.name.clone());
|
||||
|
||||
// TODO: Some way to add an about for clap, requires conversion from String -> &str
|
||||
let action_cmd = Command::new(&script.name).about(&script.name);
|
||||
|
||||
execute_cmd = execute_cmd.subcommand(action_cmd);
|
||||
}
|
||||
|
@ -20,10 +20,7 @@ fn main() -> anyhow::Result<()> {
|
||||
}
|
||||
|
||||
fn init_logging() -> anyhow::Result<()> {
|
||||
tracing_subscriber::fmt()
|
||||
.pretty()
|
||||
.with_max_level(Level::INFO)
|
||||
.init();
|
||||
tracing_subscriber::fmt().with_max_level(Level::INFO).init();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM rust:1.62.1-slim-bullseye as base
|
||||
FROM rust:1.70-slim-bullseye as base
|
||||
|
||||
RUN rustup target add x86_64-unknown-linux-musl
|
||||
|
||||
|
@ -1,2 +1,3 @@
|
||||
.cuddle/
|
||||
.git/
|
||||
target/
|
||||
|
Loading…
Reference in New Issue
Block a user