Compare commits

..

No commits in common. "main" and "feat/with-htmx" have entirely different histories.

51 changed files with 2824 additions and 4835 deletions

View File

@ -1,2 +1,170 @@
kind: template
load: cuddle-rust-lib-plan.yaml
kind: pipeline
name: default
type: docker
steps:
- name: build ci
image: rustlang/rust:nightly
volumes:
- name: ci
path: /mnt/ci
environment:
PKG_CONFIG_SYSROOT_DIR: "/"
CI_PREFIX: "/mnt/ci"
commands:
- set -e
- apt update
- apt install musl-tools pkg-config libssl-dev openssl build-essential musl-dev -y
- rustup target add x86_64-unknown-linux-musl
- cargo build --target=x86_64-unknown-linux-musl -p ci
#- cargo build -p ci
- mv target/x86_64-unknown-linux-musl/debug/ci "$CI_PREFIX/ci"
#- mv target/debug/ci $CI_PREFIX/ci
- name: load_secret
image: debian:buster-slim
volumes:
- name: ssh
path: /root/.ssh/
environment:
SSH_KEY:
from_secret: gitea_id_ed25519
commands:
- mkdir -p $HOME/.ssh/
- echo "$SSH_KEY" | base64 -d > $HOME/.ssh/id_ed25519
- name: build pr
image: kasperhermansen/cuddle:latest
pull: always
volumes:
- name: ssh
path: /root/.ssh/
- name: dockersock
path: /var/run
- name: ci
path: /mnt/ci
commands:
- eval `ssh-agent`
- chmod -R 600 ~/.ssh
- ssh-add
- echo "$DOCKER_PASSWORD" | docker login --password-stdin --username="$DOCKER_USERNAME" docker.io
- ldd $CI_PREFIX
- apk add git
- cuddle x ci:pr
environment:
DOCKER_BUILDKIT: 1
DOCKER_PASSWORD:
from_secret: docker_password
DOCKER_USERNAME:
from_secret: docker_username
CUDDLE_SECRETS_PROVIDER: 1password
CUDDLE_ONE_PASSWORD_DOT_ENV: ".env.ci"
CUDDLE_SSH_AGENT: "true"
CI_PREFIX: "/mnt/ci/ci"
CUDDLE_PLEASE_TOKEN:
from_secret: cuddle_please_token
OP_SERVICE_ACCOUNT_TOKEN:
from_secret: op_service_account_token
when:
event:
- pull_request
exclude:
- main
- master
depends_on:
- "load_secret"
- "build ci"
- name: build main
image: kasperhermansen/cuddle:latest
pull: always
volumes:
- name: ssh
path: /root/.ssh/
- name: dockersock
path: /var/run
- name: ci
path: /mnt/ci
commands:
- eval `ssh-agent`
- chmod -R 600 ~/.ssh
- ssh-add
- echo "$DOCKER_PASSWORD" | docker login --password-stdin --username="$DOCKER_USERNAME" docker.io
- ldd $CI_PREFIX
- apk add git
- cuddle x ci:main
environment:
DOCKER_BUILDKIT: 1
DOCKER_PASSWORD:
from_secret: docker_password
DOCKER_USERNAME:
from_secret: docker_username
CUDDLE_SECRETS_PROVIDER: 1password
CUDDLE_ONE_PASSWORD_DOT_ENV: ".env.ci"
CUDDLE_SSH_AGENT: "true"
CI_PREFIX: "/mnt/ci/ci"
CUDDLE_PLEASE_TOKEN:
from_secret: cuddle_please_token
OP_SERVICE_ACCOUNT_TOKEN:
from_secret: op_service_account_token
when:
event:
- push
branch:
- main
- master
exclude:
- pull_request
depends_on:
- "load_secret"
- "build ci"
- name: deploy release
image: kasperhermansen/cuddle:latest
pull: always
volumes:
- name: ssh
path: /root/.ssh/
- name: dockersock
path: /var/run
commands:
- eval `ssh-agent`
- chmod -R 600 ~/.ssh
- ssh-add
- cuddle x build:release:all
- cuddle x deploy:docs:preview
environment:
DOCKER_BUILDKIT: 1
CUDDLE_SECRETS_PROVIDER: 1password
CUDDLE_ONE_PASSWORD_DOT_ENV: ".env.ci"
CUDDLE_SSH_AGENT: "true"
CUDDLE_CI: "true"
CUDDLE_PLEASE_TOKEN:
from_secret: cuddle_please_token
OP_SERVICE_ACCOUNT_TOKEN:
from_secret: op_service_account_token
when:
event:
- tag
ref:
include:
- refs/tags/v*
depends_on:
- "load_secret"
- "build ci"
services:
- name: docker
image: docker:dind
privileged: true
volumes:
- name: dockersock
path: /var/run
volumes:
- name: ssh
temp: {}
- name: dockersock
temp: {}
- name: ci
temp: {}

View File

@ -6,240 +6,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.3.0] - 2024-11-16
### Added
- with lib drone
- with rust something
- fix errors
- update dagger
- update
- without extra packages
- wrong exclude
- also exclude tests
- update dagger 0.11.10
- update dagger 0.11.7
- add empty cuddle please for now
- update
- update a lot
- update components on prs to also build release
- update version
- update image
- move to user local bin
- with alpine instead
- add permissions
- use version as well
- install curl
- trying again
- add dagger bin actually6 compiles
- add dagger bin
- add file
- match values first
- pretty print
- with debug
- return with sqlx
- add debug logs
- with single migrations
- handle for cuddle_file
- with cuddle release clone
- use stuff
- with as ref as well
- implement clone
- now with to owned as well
- use generics
- use cuddle please
- with rust workspace members
- update dagger
- fix cuddle_releaser
- make cloneable
- remove extra fluff
- add trace
- add image tag
- make sure to add values property
- update image
- add system time
- update drone templater
- include pipeline
- include sync
- use self.client
- with aborting
- fix errors
- fix errors
- create drone templater action
- make cuddle_releaser great again
- upgrade services to bookworm
- upgrade to bookworm
- with apt
- with logging
- with logging
- update leptos service
- rerun blabla
- with export
- with fix
- cargo update
- with ignore sub source as well
- add helm to kubectl
- add main
- add timestamp
- use tag instead
- use tag instead
- revert
- from cuddle file up
- remember to split output string
- with cuddle x render args
- with context
- with kubeslice
- with kubeslice
- add kubectl command
- trying spawn
- trying std::process instead
- stderr pipes
- with nonzero exit code
- with more logs
- update releaser
- disable ci for now
- add cuddle_cli
- add cuddle_cli
- with docker
- with apt before package
- all the logs
- all the logs
- all the logs
- all the logs
- all the logs
- all the logs
- with new image
- with new image
- with new image
- set rust log error
- add time str
- new image
- without logs
- try again
- with updated releaser
- with trace
- with output
- use proper releaser
- add auth sock
- with cuddle x render
- add deployment take 2
- add deployment
- add releaser
- conditionally disable deployment
- without home
- run before base
- set user
- with empty string
- with ingored host key checking
- with migrations as well
- with opinionated ssh auth sock fetch
- set env variable as well
- with ssh agent
- extract cuddle_please
- update image
- with cuddle please
- without new async
- with rust_lib
- with main as well
- with test with leptos
- add initial leptos
- add postgresql-dev
- without nodemodules
- with entry point
- with actual pr
- with pub fn new
- add node service
- trying again with opts
- without opts
- set registry
- move to after package
- with docker cache
- update assets
- with assets
- with package as well
- without deps
- with ca certificates
- with working ssh
- update with ssh
- with ssh sock dep
- can use ssh sock
- with username
- with git name
- with sync
- with update deployment
- with registry
- with before test
- with impl into
- with arc
- with &mut service
- with mutex
- with rust service impl
- with src
- with sqlx
- forgot async_trait
- with cargo clean
- with extensions
- extract arch
- with full support for rust services
- with middleware
- with logs
- add cuddle ci draft
- with offline mode
- with cargo clean
- without export
- with output
- with nested mold
- fix name
- with mold
- with mold
- with htmx
- add leptos
- ignore cache
- update lock
- with updated dagger-sdk
- *(rust-publish)* with rust publish
### Fixed
- *(deps)* update rust crate chrono to 0.4.38
- typo
- *(deps)* update all dependencies
- *(deps)* update rust crate chrono to 0.4.35
- *(deps)* update rust crate chrono to 0.4.34
- *(deps)* update all dependencies
- *(deps)* update rust crate futures to 0.3.30
- as isize
- actually build the builder
- build errors on ssh agent
- *(deps)* update rust crate async-scoped to 0.8.0
- *(deps)* update rust crate futures to 0.3.29
- *(git)* make sure we actually fail when running an invalid git command
### Other
- *(deps)* update rust crate serde to v1.0.215
- *(deps)* update rust crate serde to v1.0.214
- *(deps)* update rust crate serde to v1.0.213
- *(deps)* update rust crate serde to v1.0.210
- *(deps)* update rust crate serde to v1.0.209
- *(deps)* update rust crate serde to v1.0.208
- *(deps)* update rust crate serde to v1.0.203
- *(deps)* update rust crate async-trait to 0.1.80
- split module
- *(deps)* update rust crate async-trait to 0.1.79
- *(deps)* update rust crate async-trait to 0.1.78
- *(deps)* update rust crate tokio to 1.36.0
- *(deps)* update rust crate eyre to 0.6.12
- rename with_socket to with_ssh_agent
- fmt
- fmt
- *(deps)* update rust crate async-scoped to 0.8.0
- *(deps)* update rust crate futures to 0.3.29
- *(deps)* update rust crate eyre to 0.6.9
- *(deps)* update rust crate tokio to 1.34.0
- *(deps)* update all dependencies
- with version 0.2.0
- publish
- add noop release script
## [0.2.0] - 2023-08-12
### Added

1360
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,9 @@
[workspace]
members = ["crates/*", "examples/*"]
members = ["crates/*", "examples/*", "ci"]
resolver = "2"
[workspace.package]
version = "0.3.0"
version = "0.2.0"
edition = "2021"
license = "MIT"
authors = ["kjuulh <contact@kjuulh.io>"]
@ -15,16 +15,11 @@ cuddle-components = { path = "crates/cuddle-components" }
dagger-components = { path = "crates/dagger-components" }
dagger-cuddle-please = { path = "crates/dagger-cuddle-please" }
dagger-rust = { path = "crates/dagger-rust" }
ci = { path = "ci" }
dagger-sdk = "0.13.7"
eyre = "0.6"
tokio = "1"
#dagger-sdk = "0.3.2"
dagger-sdk = {git = "https://github.com/kjuulh/dagger.git", branch = "feat/with-send-sync"}
eyre = "0.6.9"
tokio = "1.34.0"
dotenv = "0.15.0"
async-trait = "0.1"
color-eyre = "*"
clap = { version = "4", features = ["derive"] }
futures = "0.3"
async-scoped = { version = "0.9.0", features = ["tokio", "use-tokio"] }
serde_json = { version = "1" }
serde_yaml = { version = "0.9" }
serde = { version = "1", features = ["derive"] }
async-trait = "0.1.74"

1861
ci/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

19
ci/Cargo.toml Normal file
View File

@ -0,0 +1,19 @@
[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-cuddle-please.workspace = true
dagger-rust.workspace = true
dagger-sdk.workspace = true
eyre = "*"
color-eyre = "*"
tokio = "1"
clap = {version = "4", features = ["derive"]}
futures = "0.3.29"
async-scoped = { version = "0.8.0", features = ["tokio", "use-tokio"] }
dotenv.workspace = true

157
ci/src/main.rs Normal file
View File

@ -0,0 +1,157 @@
use std::path::PathBuf;
use std::sync::Arc;
use clap::Args;
use clap::Parser;
use clap::Subcommand;
use clap::ValueEnum;
use crate::please_release::run_release_please;
#[derive(Parser, Clone)]
#[command(author, version, about, long_about = None, subcommand_required = true)]
pub struct Command {
#[command(subcommand)]
commands: Commands,
#[command(flatten)]
global: GlobalArgs,
}
#[derive(Subcommand, Clone)]
pub enum Commands {
#[command(subcommand_required = true)]
Local {
#[command(subcommand)]
command: LocalCommands,
},
PullRequest {},
Main {},
Release,
}
#[derive(Subcommand, Clone)]
pub enum LocalCommands {
Test,
PleaseRelease,
}
#[derive(Debug, Clone, ValueEnum)]
pub enum BuildProfile {
Debug,
Release,
}
#[derive(Debug, Clone, Args)]
pub struct GlobalArgs {
#[arg(long, global = true, help_heading = "Global")]
dry_run: bool,
#[arg(long, global = true, help_heading = "Global")]
rust_builder_image: Option<String>,
#[arg(long, global = true, help_heading = "Global")]
cuddle_please_image: Option<String>,
#[arg(long, global = true, help_heading = "Global")]
source: Option<PathBuf>,
}
#[tokio::main]
async fn main() -> eyre::Result<()> {
let _ = dotenv::dotenv();
let _ = color_eyre::install();
let client = dagger_sdk::connect().await?;
let cli = Command::parse();
match &cli.commands {
Commands::Local { command } => match command {
LocalCommands::Test => {
test::execute(client, &cli.global).await?;
}
LocalCommands::PleaseRelease => todo!(),
},
Commands::PullRequest {} => {
async fn test(client: dagger_sdk::Query, cli: &Command) {
let args = &cli.global;
test::execute(client.clone(), args).await.unwrap();
}
tokio::join!(test(client.clone(), &cli),);
}
Commands::Main {} => {
async fn test(client: dagger_sdk::Query, cli: &Command) {
let args = &cli.global;
test::execute(client.clone(), args).await.unwrap();
}
async fn cuddle_please(client: dagger_sdk::Query, cli: &Command) {
run_release_please(client.clone(), &cli.global)
.await
.unwrap();
}
tokio::join!(
test(client.clone(), &cli),
cuddle_please(client.clone(), &cli)
);
}
Commands::Release => {}
}
Ok(())
}
mod please_release {
use std::sync::Arc;
use dagger_cuddle_please::{models::CuddlePleaseSrcArgs, DaggerCuddlePleaseAction};
use crate::GlobalArgs;
pub async fn run_release_please(
client: dagger_sdk::Query,
args: &GlobalArgs,
) -> eyre::Result<()> {
DaggerCuddlePleaseAction::dagger(client)
.execute_src(&CuddlePleaseSrcArgs {
cuddle_image: args
.cuddle_please_image
.clone()
.unwrap_or("kasperhermansen/cuddle-please:latest".into()),
server: dagger_cuddle_please::models::SrcServer::Gitea {
token: std::env::var("CUDDLE_PLEASE_TOKEN")
.expect("CUDDLE_PLEASE_TOKEN to be present"),
},
log_level: Some(dagger_cuddle_please::models::LogLevel::Debug),
})
.await?;
Ok(())
}
}
mod test {
use std::{path::PathBuf, sync::Arc};
use dagger_rust::build::RustVersion;
use crate::GlobalArgs;
pub async fn execute(client: dagger_sdk::Query, _args: &GlobalArgs) -> eyre::Result<()> {
dagger_rust::test::RustTest::new(client)
.test(
None::<PathBuf>,
RustVersion::Nightly,
&["crates/*", "examples/*", "ci"],
&[],
)
.await?;
Ok(())
}
}

View File

@ -1,36 +0,0 @@
[package]
name = "cuddle-ci"
version.workspace = true
edition.workspace = true
license.workspace = true
authors.workspace = true
readme.workspace = true
repository.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
dagger-rust.workspace = true
dagger-cuddle-please.workspace = true
dagger-sdk.workspace = true
eyre.workspace = true
clap.workspace = true
async-trait.workspace = true
futures.workspace = true
tokio.workspace = true
serde_json.workspace = true
serde_yaml.workspace = true
serde.workspace = true
tracing = {version = "0.1.40", features = ["log"]}
chrono = {version = "0.4.38"}
toml = "0.8.12"
[dev-dependencies]
pretty_assertions = "1.4.0"
tokio.workspace = true
[features]
default = []
dagger = []
integration = []

View File

@ -1,202 +0,0 @@
use std::{
collections::BTreeMap,
ops::{Deref, DerefMut},
};
use async_trait::async_trait;
pub struct CuddleCI {
pr_action: Vec<Box<dyn PullRequestAction + Send + Sync>>,
main_action: Vec<Box<dyn MainAction + Send + Sync>>,
release_action: Vec<Box<dyn ReleaseAction + Send + Sync>>,
}
#[derive(Default, Debug)]
pub struct Context {
store: BTreeMap<String, String>,
}
impl Deref for Context {
type Target = BTreeMap<String, String>;
fn deref(&self) -> &Self::Target {
&self.store
}
}
impl DerefMut for Context {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.store
}
}
impl CuddleCI {
pub fn new(
pr: Box<dyn PullRequestAction + Send + Sync>,
main: Box<dyn MainAction + Send + Sync>,
release: Box<dyn ReleaseAction + Send + Sync>,
) -> Self {
Self {
pr_action: vec![pr],
main_action: vec![main],
release_action: vec![release],
}
}
pub fn with_pull_request<T>(&mut self, pr: &T) -> &mut Self
where
T: PullRequestAction + ToOwned + Send + Sync + 'static,
T: ToOwned<Owned = T>,
{
self.pr_action.push(Box::new(pr.to_owned()));
self
}
pub fn with_main<T>(&mut self, main: &T) -> &mut Self
where
T: MainAction + Send + Sync + 'static,
T: ToOwned<Owned = T>,
{
self.main_action.push(Box::new(main.to_owned()));
self
}
pub fn with_release<T>(&mut self, release: &T) -> &mut Self
where
T: ReleaseAction + Send + Sync + 'static,
T: ToOwned<Owned = T>,
{
self.release_action.push(Box::new(release.to_owned()));
self
}
pub async fn execute(
&mut self,
args: impl IntoIterator<Item = impl Into<String>>,
) -> eyre::Result<()> {
let matches = clap::Command::new("cuddle-ci")
.about("is a wrapper around common CI actions")
.subcommand(clap::Command::new("pr"))
.subcommand(clap::Command::new("main"))
.subcommand(clap::Command::new("release"))
.subcommand_required(true)
.try_get_matches_from(args.into_iter().map(|a| a.into()).collect::<Vec<String>>())?;
let mut context = Context::default();
match matches.subcommand() {
Some((name, args)) => match (name, args) {
("pr", _args) => {
eprintln!("starting pr validate");
for pr_action in self.pr_action.iter() {
pr_action.execute_pull_request(&mut context).await?;
}
eprintln!("finished pr validate");
}
("main", _args) => {
eprintln!("starting main validate");
for main_action in self.main_action.iter() {
main_action.execute_main(&mut context).await?;
}
eprintln!("finished main validate");
}
("release", _args) => {
eprintln!("starting release validate");
for release_action in self.release_action.iter() {
release_action.execute_release(&mut context).await?;
}
eprintln!("finished release validate");
}
(command_name, _) => {
eyre::bail!("command is not recognized: {}", command_name)
}
},
None => eyre::bail!("command required a subcommand [pr, main, release] etc."),
}
Ok(())
}
}
impl Default for CuddleCI {
fn default() -> Self {
Self::new(
Box::new(DefaultPullRequestAction {}),
Box::new(DefaultMainAction {}),
Box::new(DefaultReleaseAction {}),
)
}
}
#[async_trait]
pub trait PullRequestAction {
async fn execute_pull_request(&self, _ctx: &mut Context) -> eyre::Result<()> {
eprintln!("validate pull request: noop");
Ok(())
}
}
pub struct DefaultPullRequestAction {}
#[async_trait]
impl PullRequestAction for DefaultPullRequestAction {}
#[async_trait]
pub trait MainAction {
async fn execute_main(&self, _ctx: &mut Context) -> eyre::Result<()>;
}
pub struct DefaultMainAction {}
#[async_trait]
impl MainAction for DefaultMainAction {
async fn execute_main(&self, _ctx: &mut Context) -> eyre::Result<()> {
Ok(())
}
}
#[async_trait]
pub trait ReleaseAction {
async fn execute_release(&self, _ctx: &mut Context) -> eyre::Result<()> {
eprintln!("validate release: noop");
Ok(())
}
}
pub struct DefaultReleaseAction {}
#[async_trait]
impl ReleaseAction for DefaultReleaseAction {}
#[cfg(test)]
mod test {
use super::*;
#[tokio::test]
async fn test_can_call_default() -> eyre::Result<()> {
CuddleCI::default().execute(["cuddle-ci", "pr"]).await?;
Ok(())
}
#[tokio::test]
async fn test_fails_on_no_command() -> eyre::Result<()> {
let res = CuddleCI::default().execute(["cuddle-ci"]).await;
assert!(res.is_err());
Ok(())
}
#[tokio::test]
async fn test_fails_on_wrong_command() -> eyre::Result<()> {
let res = CuddleCI::default()
.execute(["cuddle-ci", "something"])
.await;
assert!(res.is_err());
Ok(())
}
}

View File

@ -1,273 +0,0 @@
use std::{collections::BTreeMap, path::PathBuf};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct CuddleFile {
pub vars: CuddleVars,
pub deployment: Option<CuddleDeployment>,
pub components: Option<CuddleComponents>,
pub please: Option<CuddlePlease>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct CuddleComponents {
pub database: Option<CuddleDatabase>,
pub assets: Option<CuddleAssets>,
pub packages: Option<Packages>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct CuddleAssets {
pub volumes: Option<Vec<CuddleAssetInclude>>,
pub clean: Option<bool>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct CuddlePlease {}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct Packages {
pub debian: DebianPackages,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct DebianPackages {
pub dev: Vec<String>,
pub release: Vec<String>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct CuddleAssetInclude {
pub from: PathBuf,
pub to: PathBuf,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[serde(untagged)]
pub enum CuddleDatabase {
Enabled(bool),
Values { migrations: PathBuf },
Default {},
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct CuddleVars {
pub service: String,
pub registry: String,
pub clusters: Option<CuddleClusters>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct CuddleDeployment {
pub registry: String,
pub env: CuddleDeploymentEnv,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct CuddleDeploymentEnv(pub BTreeMap<String, CuddleDeploymentCluster>);
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct CuddleDeploymentCluster {
pub clusters: Vec<String>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct CuddleClusters(pub BTreeMap<String, CuddleCluster>);
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct CuddleCluster {
pub namespace: String,
}
impl CuddleFile {
pub async fn from_cuddle_file() -> eyre::Result<Self> {
let cuddle_file_content = tokio::fs::read_to_string("cuddle.yaml").await?;
Self::parse_cuddle_file(&cuddle_file_content)
}
pub fn parse_cuddle_file(content: &str) -> eyre::Result<Self> {
let cuddle_file: CuddleFile = serde_yaml::from_str(content)?;
Ok(cuddle_file)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn parse_file() {
let cuddle_file = r#"
base: "git@git.front.kjuulh.io:kjuulh/cuddle-base.git"
vars:
service: "infrastructure-example"
registry: kasperhermansen
clusters:
clank_prod:
replicas: "3"
namespace: clank_prod
deployment:
registry: git@git.front.kjuulh.io:kjuulh/clank-clusters
env:
prod:
clusters:
- clank_prod
components:
database: true
assets:
volumes:
- from: somewhere
to: somewhere-else
packages:
debian:
dev:
- "capnp"
release:
- "capnp"
scripts:
render:
type: shell
args:
cluster:
name: cluster
type: flag
image_tag:
name: image_tag
type: flag"#;
let res = CuddleFile::parse_cuddle_file(cuddle_file).expect("to parse file");
let mut clusters = BTreeMap::new();
clusters.insert(
"clank_prod".into(),
CuddleCluster {
namespace: "clank_prod".into(),
},
);
let mut deployment = BTreeMap::new();
deployment.insert(
"prod".into(),
CuddleDeploymentCluster {
clusters: vec!["clank_prod".into()],
},
);
let expected = CuddleFile {
vars: CuddleVars {
service: "infrastructure-example".into(),
registry: "kasperhermansen".into(),
clusters: Some(CuddleClusters(clusters)),
},
deployment: Some(crate::cuddle_file::CuddleDeployment {
registry: "git@git.front.kjuulh.io:kjuulh/clank-clusters".into(),
env: CuddleDeploymentEnv(deployment),
}),
components: Some(CuddleComponents {
database: Some(CuddleDatabase::Enabled(true)),
assets: Some(CuddleAssets {
volumes: Some(vec![CuddleAssetInclude {
from: "somewhere".into(),
to: "somewhere-else".into(),
}]),
clean: None,
}),
packages: Some(Packages {
debian: DebianPackages {
dev: vec!["capnp".into()],
release: vec!["capnp".into()],
},
}),
}),
please: None,
};
pretty_assertions::assert_eq!(expected, res)
}
#[test]
fn cuddle_database_default() {
let cuddle_file = r#"
base: "git@git.front.kjuulh.io:kjuulh/cuddle-base.git"
vars:
service: "infrastructure-example"
registry: kasperhermansen
components:
database: {}
"#;
let res = CuddleFile::parse_cuddle_file(cuddle_file).expect("to parse file");
let expected = CuddleFile {
vars: CuddleVars {
service: "infrastructure-example".into(),
registry: "kasperhermansen".into(),
clusters: None,
},
deployment: None,
components: Some(CuddleComponents {
database: Some(CuddleDatabase::Default {}),
assets: None,
packages: None,
}),
please: None,
};
pretty_assertions::assert_eq!(expected, res)
}
#[test]
fn cuddle_packages() {
let cuddle_file = r#"
base: "git@git.front.kjuulh.io:kjuulh/cuddle-base.git"
vars:
service: "infrastructure-example"
registry: kasperhermansen
components:
packages:
debian:
dev:
- "capnp"
release:
- "capnp"
"#;
let res = CuddleFile::parse_cuddle_file(cuddle_file).expect("to parse file");
let expected = CuddleFile {
vars: CuddleVars {
service: "infrastructure-example".into(),
registry: "kasperhermansen".into(),
clusters: None,
},
deployment: None,
components: Some(CuddleComponents {
database: None,
assets: None,
packages: Some(Packages {
debian: DebianPackages {
dev: vec!["capnp".into()],
release: vec!["capnp".into()],
},
}),
}),
please: None,
};
pretty_assertions::assert_eq!(expected, res)
}
}

View File

@ -1,37 +0,0 @@
use async_trait::async_trait;
use dagger_cuddle_please::{models::CuddlePleaseSrcArgs, DaggerCuddlePlease};
use crate::{Context, MainAction};
#[derive(Clone)]
pub struct CuddlePlease {
client: dagger_sdk::Query,
}
impl CuddlePlease {
pub fn new(client: dagger_sdk::Query) -> Self {
Self { client }
}
}
#[async_trait]
impl MainAction for CuddlePlease {
async fn execute_main(&self, _ctx: &mut Context) -> eyre::Result<()> {
let client = self.client.clone();
let action = DaggerCuddlePlease::new(client);
action
.cuddle_please_src(&CuddlePleaseSrcArgs {
cuddle_image: "kasperhermansen/cuddle-please:main-1712698022".into(),
server: dagger_cuddle_please::models::SrcServer::Gitea {
token: std::env::var("CUDDLE_PLEASE_TOKEN")
.expect("CUDDLE_PLEASE_TOKEN to be present"),
},
log_level: Some(dagger_cuddle_please::models::LogLevel::Debug),
})
.await?;
Ok(())
}
}

View File

@ -1,152 +0,0 @@
use std::fmt::Display;
use async_trait::async_trait;
use eyre::Context;
use crate::{cli, cuddle_file::CuddleFile, MainAction};
#[derive(Clone)]
pub struct CuddleReleaser {
client: dagger_sdk::Query,
env: Option<String>,
cuddle_file: CuddleFile,
folder: String,
}
pub struct CuddleReleaserOptions {
upstream: String,
cluster: String,
namespace: String,
app: String,
}
pub enum CuddleEnv {
Prod,
Dev,
}
impl Display for CuddleEnv {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CuddleEnv::Prod => f.write_str("prod"),
CuddleEnv::Dev => f.write_str("dev"),
}
}
}
impl TryInto<CuddleEnv> for String {
type Error = eyre::Error;
fn try_into(self) -> Result<CuddleEnv, Self::Error> {
let env = match self.as_str() {
"prod" => CuddleEnv::Prod,
"dev" => CuddleEnv::Dev,
_ => eyre::bail!("was not a valid env: {}", self),
};
Ok(env)
}
}
impl CuddleReleaser {
pub async fn new(client: dagger_sdk::Query) -> eyre::Result<Self> {
let cuddle_file = CuddleFile::from_cuddle_file().await?;
let env = std::env::var("CUDDLE_ENV").ok();
Ok(Self {
client,
cuddle_file,
env,
folder: ".cuddle/tmp".into(),
})
}
pub async fn releaser(&self, env: CuddleEnv) -> eyre::Result<()> {
let client = self.client.clone();
if self.cuddle_file.deployment.is_none() {
return Ok(());
}
let chosen_cluster = match self
.cuddle_file
.deployment
.as_ref()
.unwrap()
.env
.0
.get(&self.env.as_ref().unwrap_or(&env.to_string()).to_string())
{
Some(c) => match c.clusters.first().take() {
Some(c) => c,
None => return Ok(()),
},
None => return Ok(()),
};
if let Some(clusters) = &self.cuddle_file.vars.clusters {
let cluster = match clusters.0.get(chosen_cluster) {
Some(c) => c,
None => eyre::bail!("no cluster found for: {}", chosen_cluster),
};
let options = CuddleReleaserOptions {
cluster: chosen_cluster.clone(),
namespace: cluster.namespace.clone(),
app: self.cuddle_file.vars.service.clone(),
upstream: self
.cuddle_file
.deployment
.as_ref()
.unwrap()
.registry
.clone(),
};
let cuddle_releaser_image = "docker.io/kasperhermansen/cuddle-releaser:main-1706726858";
let folder = client.host().directory(&self.folder);
let ssh_sock = std::env::var("SSH_AUTH_SOCK").context("SSH_AUTH_SOCK not set")?;
let cuddle_releaser = client
.container()
.from(cuddle_releaser_image)
.with_env_variable("RUST_LOG", "trace")
.with_directory("/mnt/templates", folder)
.with_unix_socket(
ssh_sock.clone(),
client.host().unix_socket(ssh_sock.clone()),
);
let time = chrono::Local::now();
cuddle_releaser
.with_exec(vec!["echo", &time.to_rfc3339()])
.with_exec(vec![
"cuddle-releaser",
"release",
&format!("--upstream={}", options.upstream),
&format!("--folder={}", "/mnt/templates/k8s"),
&format!("--cluster={}", options.cluster),
&format!("--namespace={}", options.namespace),
&format!("--app={}", options.app),
])
.sync()
.await?;
}
Ok(())
}
}
#[async_trait]
impl MainAction for CuddleReleaser {
async fn execute_main(&self, _ctx: &mut cli::Context) -> eyre::Result<()> {
self.releaser(CuddleEnv::Prod).await?;
Ok(())
}
}

View File

@ -1,68 +0,0 @@
pub struct CuddleX {
command: String,
args: Vec<String>,
}
impl CuddleX {
pub fn command(command: impl Into<String>) -> Self {
Self {
command: command.into(),
args: Vec::new(),
}
}
pub fn arg(&mut self, arg: impl Into<String>) -> &mut Self {
self.args.push(arg.into());
self
}
pub async fn run(&mut self) -> eyre::Result<(String, String, i32)> {
let mut cmd = tokio::process::Command::new("cuddle");
let cmd = cmd.arg("x").arg(&self.command).args(&self.args);
let process = cmd.spawn()?;
let output = process.wait_with_output().await?;
Ok((
std::str::from_utf8(&output.stdout)?.to_string(),
std::str::from_utf8(&output.stderr)?.to_string(),
output.status.code().unwrap_or(0),
))
}
}
pub mod well_known {
use super::CuddleX;
pub async fn render(args: impl IntoIterator<Item = impl Into<String>>) -> eyre::Result<()> {
tracing::info!("running render");
let mut cmd = CuddleX::command("render");
for arg in args.into_iter() {
let arg = arg.into();
cmd.arg(arg);
}
let (stdout, stderr, status) = cmd.run().await?;
for line in stdout.lines() {
tracing::trace!("render: {}", line);
}
for line in stderr.lines() {
tracing::trace!("render: {}", line);
}
if status != 0 {
tracing::warn!("finished render with non-zero exit code: {}", status);
}
tracing::info!("finished running render");
Ok(())
}
}

View File

@ -1,54 +0,0 @@
use async_trait::async_trait;
use dagger_sdk::Container;
use std::{future::Future, pin::Pin, sync::Arc};
pub type DynMiddleware = Arc<dyn DaggerMiddleware + Send + Sync>;
#[async_trait]
pub trait DaggerMiddleware {
async fn handle(
&self,
container: dagger_sdk::Container,
) -> eyre::Result<dagger_sdk::Container> {
Ok(container)
}
}
pub struct DaggerMiddlewareFn<F>
where
F: Fn(Container) -> Pin<Box<dyn Future<Output = eyre::Result<Container>> + Send>>,
{
pub func: F,
}
pub fn middleware<F>(func: F) -> Box<DaggerMiddlewareFn<F>>
where
F: Fn(Container) -> Pin<Box<dyn Future<Output = eyre::Result<Container>> + Send>>,
{
Box::new(DaggerMiddlewareFn { func })
}
#[async_trait]
impl<F> DaggerMiddleware for DaggerMiddlewareFn<F>
where
F: Fn(Container) -> Pin<Box<dyn Future<Output = eyre::Result<Container>> + Send>> + Send + Sync,
{
async fn handle(&self, container: Container) -> eyre::Result<Container> {
// Call the closure stored in the struct
(self.func)(container).await
}
}
#[cfg(test)]
mod test {
use futures::FutureExt;
use super::*;
#[tokio::test]
async fn can_add_middleware() -> eyre::Result<()> {
middleware(|c| async move { Ok(c) }.boxed());
Ok(())
}
}

View File

@ -1,87 +0,0 @@
use std::{collections::BTreeMap, path::PathBuf, time::UNIX_EPOCH};
const DRONE_TEMPLATER_IMAGE: &str = "kasperhermansen/drone-templater:main-1711807810";
use async_trait::async_trait;
use eyre::{Context, OptionExt};
use crate::{rust_service::RustServiceContext, MainAction};
#[derive(Clone)]
pub struct DroneTemplater {
client: dagger_sdk::Query,
template: PathBuf,
variables: BTreeMap<String, String>,
}
impl DroneTemplater {
pub fn new(client: dagger_sdk::Query, template: impl Into<PathBuf>) -> Self {
Self {
client,
template: template.into(),
variables: BTreeMap::default(),
}
}
pub fn with_variable(
&mut self,
name: impl Into<String>,
value: impl Into<String>,
) -> &mut Self {
self.variables.insert(name.into(), value.into());
self
}
}
#[async_trait]
impl MainAction for DroneTemplater {
async fn execute_main(&self, ctx: &mut crate::Context) -> eyre::Result<()> {
let image_tag = ctx
.get_image_tag()?
.ok_or_eyre(eyre::eyre!("failed to find image tag"))?;
let src = self.client.host().directory(".cuddle/tmp/");
let drone_host = std::env::var("DRONE_HOST").context("DRONE_HOST is missing")?;
let drone_user = std::env::var("DRONE_USER").context("DRONE_USER is missing")?;
let drone_token = std::env::var("DRONE_TOKEN").context("DRONE_TOKEN is missing")?;
let drone_token_secret = self.client.set_secret("DRONE_TOKEN", drone_token);
let now = std::time::SystemTime::now()
.duration_since(UNIX_EPOCH)
.context("failed to get system time")?;
let template_name = self.template.display().to_string();
let cmd = vec!["drone-templater", "upload", "--template", &template_name]
.into_iter()
.map(|v| v.to_string())
.chain(
self.variables
.iter()
.map(|(name, value)| format!(r#"--variable={name}={value}"#)),
)
.chain(vec![format!("--variable=image_tag={}", image_tag)])
.collect::<Vec<_>>();
self.client
.container()
.from(DRONE_TEMPLATER_IMAGE)
.with_env_variable("RUST_LOG", "drone_templater=trace")
.with_exec(vec!["echo", &format!("{}", now.as_secs())])
.with_directory("/src/templates", src)
.with_workdir("/src")
.with_env_variable("DRONE_HOST", drone_host)
.with_env_variable("DRONE_USER", drone_user)
.with_secret_variable("DRONE_TOKEN", drone_token_secret)
.with_exec(cmd)
.sync()
.await
.context("failed to upload drone templates with error")?;
Ok(())
}
}

View File

@ -1,423 +0,0 @@
use std::{path::PathBuf, sync::Arc};
use async_trait::async_trait;
use dagger_rust::source::RustSource;
use dagger_sdk::Container;
use futures::{stream, StreamExt};
use crate::{
dagger_middleware::DaggerMiddleware,
rust_service::{
architecture::{Architecture, Os},
extensions::CargoBInstallExt,
RustServiceContext, RustServiceStage,
},
Context, MainAction, PullRequestAction,
};
#[derive(Clone)]
pub struct LeptosService {
pub(crate) client: dagger_sdk::Query,
base_image: Option<dagger_sdk::Container>,
final_image: Option<dagger_sdk::Container>,
stages: Vec<RustServiceStage>,
source: Option<PathBuf>,
crates: Vec<String>,
bin_name: String,
arch: Option<Architecture>,
os: Option<Os>,
deploy_target_name: Option<String>,
deploy: bool,
}
impl LeptosService {
pub fn new(client: dagger_sdk::Query, bin_name: impl Into<String>) -> Self {
Self {
client,
base_image: None,
final_image: None,
stages: Vec::new(),
source: None,
crates: Vec::new(),
bin_name: bin_name.into(),
arch: None,
os: None,
deploy_target_name: None,
deploy: true,
}
}
pub fn with_base_image(&mut self, base: dagger_sdk::Container) -> &mut Self {
self.base_image = Some(base);
self
}
pub fn with_stage(&mut self, stage: RustServiceStage) -> &mut Self {
self.stages.push(stage);
self
}
pub fn with_source(&mut self, path: impl Into<PathBuf>) -> &mut Self {
self.source = Some(path.into());
self
}
pub fn with_deploy_target(&mut self, deploy_target: impl Into<String>) -> &mut Self {
self.deploy_target_name = Some(deploy_target.into());
self
}
pub fn with_crates(
&mut self,
crates: impl IntoIterator<Item = impl Into<String>>,
) -> &mut Self {
self.crates = crates.into_iter().map(|c| c.into()).collect();
self
}
pub fn with_arch(&mut self, arch: Architecture) -> &mut Self {
self.arch = Some(arch);
self
}
pub fn with_os(&mut self, os: Os) -> &mut Self {
self.os = Some(os);
self
}
pub fn with_deploy(&mut self, deploy: bool) -> &mut Self {
self.deploy = deploy;
self
}
fn get_src(&self) -> PathBuf {
self.source
.clone()
.unwrap_or(std::env::current_dir().unwrap())
}
pub fn get_arch(&self) -> Architecture {
self.arch
.clone()
.unwrap_or_else(|| match std::env::consts::ARCH {
"x86" | "x86_64" | "amd64" => Architecture::Amd64,
"arm" => Architecture::Arm64,
arch => panic!("unsupported architecture: {arch}"),
})
}
pub fn get_os(&self) -> Os {
self.os
.clone()
.unwrap_or_else(|| match std::env::consts::OS {
"linux" => Os::Linux,
"macos" => Os::MacOS,
os => panic!("unsupported os: {os}"),
})
}
async fn run_stage(
&self,
stages: impl IntoIterator<Item = &Arc<dyn DaggerMiddleware + Send + Sync>>,
container: Container,
) -> eyre::Result<Container> {
let before_deps_stream = stream::iter(stages.into_iter().map(Ok));
let res = StreamExt::fold(before_deps_stream, Ok(container), |base, m| async move {
match (base, m) {
(Ok(base), Ok(m)) => m.handle(base).await,
(_, Err(e)) | (Err(e), _) => eyre::bail!("failed with {e}"),
}
})
.await?;
Ok(res)
}
pub async fn build_base(&self) -> eyre::Result<Container> {
let rust_src = RustSource::new(self.client.clone());
let (src, dep_src) = rust_src
.get_rust_src(Some(&self.get_src()), self.crates.clone())
.await?;
let base_image = self
.base_image
.clone()
.unwrap_or(self.client.container().from("rustlang/rust:nightly"));
let before_deps = self
.stages
.iter()
.filter_map(|s| match s {
RustServiceStage::BeforeDeps(middleware) => Some(middleware),
_ => None,
})
.collect::<Vec<_>>();
let image = self.run_stage(before_deps, base_image).await?;
let image = image.with_exec(vec!["rustup", "target", "add", "wasm32-unknown-unknown"]);
let after_deps = self
.stages
.iter()
.filter_map(|s| match s {
RustServiceStage::AfterDeps(m) => Some(m),
_ => None,
})
.collect::<Vec<_>>();
let image = self.run_stage(after_deps, image).await?;
let before_base = self
.stages
.iter()
.filter_map(|s| match s {
RustServiceStage::BeforeBase(m) => Some(m),
_ => None,
})
.collect::<Vec<_>>();
let image = self.run_stage(before_base, image).await?;
let cache = self.client.cache_volume("rust_target_cache");
let rust_prebuild = image
.with_workdir("/mnt/src")
.with_directory("/mnt/src", dep_src)
.with_exec(vec![
"cargo",
"leptos",
"build",
"--release",
"-vv",
"--project",
&self.bin_name,
])
.with_mounted_cache("/mnt/src/target/", cache);
let incremental_dir = rust_src
.get_rust_target_src(&self.get_src(), rust_prebuild.clone(), self.crates.clone())
.await?;
let rust_with_src = image
.with_workdir("/mnt/src")
.with_directory(
"/usr/local/cargo",
rust_prebuild.directory("/usr/local/cargo"),
)
.with_directory("/mnt/src/target", incremental_dir)
.with_directory("/mnt/src/", src);
let after_base = self
.stages
.iter()
.filter_map(|s| match s {
RustServiceStage::AfterBase(m) => Some(m),
_ => None,
})
.collect::<Vec<_>>();
let image = self.run_stage(after_base, rust_with_src).await?;
Ok(image)
}
pub async fn build_release(&self) -> eyre::Result<Container> {
let base = self.build_base().await?;
let before_build = self
.stages
.iter()
.filter_map(|s| match s {
RustServiceStage::BeforeBuild(m) => Some(m),
_ => None,
})
.collect::<Vec<_>>();
let base = self.run_stage(before_build, base).await?;
let binary_build = base.with_exec(vec![
"cargo",
"leptos",
"build",
"--release",
"--project",
&self.bin_name,
]);
let after_build = self
.stages
.iter()
.filter_map(|s| match s {
RustServiceStage::AfterBuild(m) => Some(m),
_ => None,
})
.collect::<Vec<_>>();
let binary_build = self.run_stage(after_build, binary_build).await?;
let dest = self
.final_image
.clone()
.unwrap_or(self.client.container().from("debian:bookworm"));
let before_package = self
.stages
.iter()
.filter_map(|s| match s {
RustServiceStage::BeforePackage(m) => Some(m),
_ => None,
})
.collect::<Vec<_>>();
let dest = self.run_stage(before_package, dest).await?;
let final_image = dest
.with_workdir("/mnt/app")
.with_file(
format!("/usr/local/bin/{}", self.bin_name),
binary_build.file(format!("/mnt/src/target/release/{}", self.bin_name)),
)
.with_directory(
"/mnt/app/target/site",
binary_build.directory("/mnt/src/target/site"),
)
.with_file(
"/mnt/app/Cargo.toml",
binary_build.file(format!("/mnt/src/crates/{}/Cargo.toml", self.bin_name)),
)
.with_env_variable("APP_ENVIRONMENT", "production")
.with_env_variable("LEPTOS_OUTPUT_NAME", &self.bin_name)
.with_env_variable("LEPTOS_SITE_ADDR", "0.0.0.0:3000")
.with_env_variable("LEPTOS_SITE_PKG_DIR", "pkg")
.with_exposed_port(3000);
let after_package = self
.stages
.iter()
.filter_map(|s| match s {
RustServiceStage::AfterPackage(m) => Some(m),
_ => None,
})
.collect::<Vec<_>>();
let final_image = self.run_stage(after_package, final_image).await?;
Ok(final_image)
}
pub async fn build_test(&self) -> eyre::Result<()> {
let base = self.build_base().await?;
let before_build = self
.stages
.iter()
.filter_map(|s| match s {
RustServiceStage::BeforeBuild(m) => Some(m),
_ => None,
})
.collect::<Vec<_>>();
let base = self.run_stage(before_build, base).await?;
base.with_exec(vec!["cargo", "leptos", "test", "--release"])
.sync()
.await?;
Ok(())
}
fn get_deploy_target(&self) -> String {
self.deploy_target_name
.clone()
.unwrap_or(self.bin_name.clone())
}
}
#[async_trait]
impl PullRequestAction for LeptosService {
async fn execute_pull_request(&self, _ctx: &mut Context) -> eyre::Result<()> {
let mut s = self.clone();
s.with_cargo_binstall("latest", ["cargo-leptos"])
.build_test()
.await?;
Ok(())
}
}
#[async_trait]
impl MainAction for LeptosService {
async fn execute_main(&self, ctx: &mut Context) -> eyre::Result<()> {
let mut s = self.clone();
let container = s
.with_cargo_binstall("latest", ["cargo-leptos"])
.build_release()
.await?;
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
let tag = format!(
"docker.io/kasperhermansen/{}:main-{}",
self.bin_name, timestamp,
);
container.publish(tag.clone()).await?;
tracing::info!("published: {}", tag);
ctx.set_image_tag(format!("main-{}", &timestamp.to_string()))?;
if self.deploy {
let update_deployments_docker_image =
"docker.io/kasperhermansen/update-deployment:1701123940";
let dep = self
.client
.container()
.from(update_deployments_docker_image);
let dep = if let Ok(sock) = std::env::var("SSH_AUTH_SOCK") {
dep.with_unix_socket("/tmp/ssh_sock", self.client.host().unix_socket(sock))
.with_env_variable("SSH_AUTH_SOCK", "/tmp/ssh_sock")
.with_exec(vec![
"update-deployment",
"--repo",
&format!(
"git@git.front.kjuulh.io:kjuulh/{}-deployment.git",
self.get_deploy_target()
),
"--service",
&self.bin_name,
"--image",
&format!("kasperhermansen/{}:main-{}", self.bin_name, timestamp),
])
} else {
dep.with_env_variable("GIT_USERNAME", "kjuulh")
.with_env_variable(
"GIT_PASSWORD",
std::env::var("GIT_PASSWORD").expect("GIT_PASSWORD to be set"),
)
.with_exec(vec![
"update-deployment",
"--repo",
&format!(
"https://git.front.kjuulh.io/kjuulh/{}-deployment.git",
self.get_deploy_target()
),
"--service",
&self.bin_name,
"--image",
&format!("kasperhermansen/{}:main-{}", self.bin_name, timestamp),
])
};
dep.sync().await?;
}
Ok(())
}
}

View File

@ -1,15 +0,0 @@
pub mod cli;
pub use cli::*;
pub mod leptos_service;
pub mod node_service;
pub mod rust_lib;
pub mod rust_service;
pub mod cuddle_file;
pub mod cuddle_please;
pub mod cuddle_releaser;
pub mod cuddle_x;
pub mod dagger_middleware;
pub mod drone_templater;
pub mod rust_workspace;

View File

@ -1,290 +0,0 @@
use std::path::PathBuf;
use async_trait::async_trait;
use dagger_sdk::{Container, ContainerWithDirectoryOptsBuilder, HostDirectoryOptsBuilder};
use crate::{
dagger_middleware::DynMiddleware,
rust_service::architecture::{Architecture, Os},
Context, MainAction, PullRequestAction,
};
#[derive(Clone)]
pub enum NodeServiceStage {
BeforeDeps(DynMiddleware),
AfterDeps(DynMiddleware),
BeforeBase(DynMiddleware),
AfterBase(DynMiddleware),
BeforeBuild(DynMiddleware),
AfterBuild(DynMiddleware),
BeforePackage(DynMiddleware),
AfterPackage(DynMiddleware),
BeforeRelease(DynMiddleware),
AfterRelease(DynMiddleware),
}
#[derive(Clone)]
pub struct NodeService {
client: dagger_sdk::Query,
service: String,
base_image: Option<Container>,
final_image: Option<Container>,
stages: Vec<NodeServiceStage>,
source: Option<PathBuf>,
arch: Option<Architecture>,
os: Option<Os>,
}
impl NodeService {
pub fn new(value: dagger_sdk::Query, service: impl Into<String>) -> Self {
Self {
client: value,
service: service.into(),
base_image: None,
final_image: None,
stages: Vec::new(),
source: None,
arch: None,
os: None,
}
}
pub fn with_base_image(&mut self, base: dagger_sdk::Container) -> &mut Self {
self.base_image = Some(base);
self
}
pub fn with_service(&mut self, service: impl Into<String>) -> &mut Self {
self.service = service.into();
self
}
pub fn with_stage(&mut self, stage: NodeServiceStage) -> &mut Self {
self.stages.push(stage);
self
}
pub fn with_source(&mut self, path: impl Into<PathBuf>) -> &mut Self {
self.source = Some(path.into());
self
}
pub fn with_arch(&mut self, arch: Architecture) -> &mut Self {
self.arch = Some(arch);
self
}
pub fn with_os(&mut self, os: Os) -> &mut Self {
self.os = Some(os);
self
}
fn get_src(&self) -> PathBuf {
self.source
.clone()
.unwrap_or(std::env::current_dir().unwrap())
}
fn get_arch(&self) -> Architecture {
self.arch
.clone()
.unwrap_or_else(|| match std::env::consts::ARCH {
"x86" | "x86_64" | "amd64" => Architecture::Amd64,
"arm" => Architecture::Arm64,
arch => panic!("unsupported architecture: {arch}"),
})
}
fn get_os(&self) -> Os {
self.os
.clone()
.unwrap_or_else(|| match std::env::consts::OS {
"linux" => Os::Linux,
"macos" => Os::MacOS,
os => panic!("unsupported os: {os}"),
})
}
pub async fn build_base(&self) -> eyre::Result<Container> {
let src = self.client.host().directory_opts(
self.get_src().to_string_lossy(),
HostDirectoryOptsBuilder::default()
.exclude(vec!["node_modules/", ".git/", ".cuddle/"])
.build()?,
);
let pkg_files = self.client.host().directory_opts(
self.get_src().to_string_lossy(),
HostDirectoryOptsBuilder::default()
.include(vec!["package.json", "yarn.lock"])
.build()?,
);
let deps =
"apk add --no-cache build-base gcc autoconf automake zlib-dev libpng-dev vips-dev git postgresql-dev"
.split_whitespace();
let base_image = match self.base_image.clone() {
Some(image) => image,
None => self
.client
.container()
.from("node:20-alpine")
.with_exec(vec!["apk", "update"])
.with_exec(deps.collect()),
}
.with_env_variable("NODE_ENV", "production");
let base_yarn_image = base_image
.with_workdir("/opt/")
.with_directory(".", pkg_files)
.with_exec(vec!["yarn", "global", "add", "node-gyp"])
.with_exec(vec![
"yarn",
"config",
"set",
"network-timeout",
"600000",
"-g",
])
.with_exec(vec!["yarn", "install", "--production"]);
let base_build = base_yarn_image
.with_env_variable(
"PATH",
format!(
"/opt/node_modules/.bin:{}",
base_yarn_image.env_variable("PATH").await?
),
)
.with_workdir("/opt/app")
.with_directory(".", src)
.with_exec(vec!["yarn", "build"]);
Ok(base_build)
}
pub async fn build_release(&self) -> eyre::Result<Container> {
let base = self.build_base().await?;
let final_build_image = match self.final_image.clone() {
Some(c) => c,
None => self
.client
.container()
.from("node:20-alpine")
.with_exec(vec![
"apk",
"add",
"--no-cache",
"vips-dev",
"postgresql-dev",
]),
}
.with_env_variable("NODE_ENV", "production");
let final_image = final_build_image
.with_workdir("/opt/")
.with_directory("/opt/node_modules", base.directory("/opt/node_modules"))
.with_workdir("/opt/app")
.with_directory_opts(
"/opt/app",
base.directory("/opt/app"),
ContainerWithDirectoryOptsBuilder::default()
.owner("node:node")
.build()?,
)
.with_env_variable(
"PATH",
format!(
"/opt/node_modules/.bin:{}",
final_build_image.env_variable("PATH").await?
),
)
.with_user("node")
.with_exposed_port(1337)
.with_entrypoint(vec!["yarn", "start"]);
Ok(final_image)
}
}
#[async_trait]
impl PullRequestAction for NodeService {
async fn execute_pull_request(&self, _ctx: &mut Context) -> eyre::Result<()> {
let release = self.build_release().await?;
release.sync().await?;
Ok(())
}
}
#[async_trait]
impl MainAction for NodeService {
async fn execute_main(&self, _ctx: &mut Context) -> eyre::Result<()> {
let container = self.build_release().await?;
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
container
.publish(format!(
"docker.io/kasperhermansen/{}:main-{}",
self.service, timestamp,
))
.await?;
let update_deployments_docker_image =
"docker.io/kasperhermansen/update-deployment:1701123940";
let dep = self
.client
.container()
.from(update_deployments_docker_image);
let dep = match std::env::var("SSH_AUTH_SOCK").ok() {
Some(sock) => dep
.with_unix_socket("/tmp/ssh_sock", self.client.host().unix_socket(sock))
.with_env_variable("SSH_AUTH_SOCK", "/tmp/ssh_sock")
.with_exec(vec![
"update-deployment",
"--repo",
&format!(
"git@git.front.kjuulh.io:kjuulh/{}-deployment.git",
self.service
),
"--service",
&self.service,
"--image",
&format!("kasperhermansen/{}:main-{}", self.service, timestamp),
]),
_ => dep
.with_env_variable("GIT_USERNAME", "kjuulh")
.with_env_variable(
"GIT_PASSWORD",
std::env::var("GIT_PASSWORD").expect("GIT_PASSWORD to be set"),
)
.with_exec(vec![
"update-deployment",
"--repo",
&format!(
"https://git.front.kjuulh.io/kjuulh/{}-deployment.git",
self.service
),
"--service",
&self.service,
"--image",
&format!("kasperhermansen/{}:main-{}", self.service, timestamp),
]),
};
dep.sync().await?;
Ok(())
}
}

View File

@ -1,147 +0,0 @@
use std::path::PathBuf;
use async_trait::async_trait;
use dagger_rust::source::RustSource;
use dagger_sdk::Container;
use crate::{
cli,
rust_service::architecture::{Architecture, Os},
rust_workspace, MainAction, PullRequestAction,
};
#[derive(Clone)]
pub struct RustLib {
client: dagger_sdk::Query,
base_image: Option<dagger_sdk::Container>,
source: Option<PathBuf>,
crates: Vec<String>,
arch: Option<Architecture>,
os: Option<Os>,
}
impl RustLib {
pub fn new(value: dagger_sdk::Query) -> Self {
Self {
client: value,
source: None,
crates: Vec::new(),
arch: None,
os: None,
base_image: None,
}
}
pub fn with_source(&mut self, path: impl Into<PathBuf>) -> &mut Self {
self.source = Some(path.into());
self
}
pub fn with_base_image(&mut self, base: dagger_sdk::Container) -> &mut Self {
self.base_image = Some(base);
self
}
pub fn with_crates(
&mut self,
crates: impl IntoIterator<Item = impl Into<String>>,
) -> &mut Self {
self.crates = crates.into_iter().map(|c| c.into()).collect();
self
}
pub async fn with_workspace_crates(&mut self) -> &mut Self {
if let Ok(Some(file)) = rust_workspace::File::read_file().await {
if let Some(members) = file.get_workspace_members() {
return self.with_crates(members);
}
}
self
}
pub fn with_arch(&mut self, arch: Architecture) -> &mut Self {
self.arch = Some(arch);
self
}
pub fn with_os(&mut self, os: Os) -> &mut Self {
self.os = Some(os);
self
}
fn get_src(&self) -> PathBuf {
self.source
.clone()
.unwrap_or(std::env::current_dir().unwrap())
}
pub async fn build_base(&self) -> eyre::Result<Container> {
let rust_src = RustSource::new(self.client.clone());
let (src, dep_src) = rust_src
.get_rust_src(Some(&self.get_src()), self.crates.clone())
.await?;
let base_image = self
.base_image
.clone()
.unwrap_or(self.client.container().from("rustlang/rust:nightly"));
let cache = self.client.cache_volume("rust_target_cache");
let rust_prebuild = base_image
.with_workdir("/mnt/src")
.with_directory("/mnt/src", dep_src)
.with_exec(vec!["cargo", "build", "--tests", "--workspace"])
.with_mounted_cache("/mnt/src/target/", cache);
let incremental_dir = rust_src
.get_rust_target_src(&self.get_src(), rust_prebuild.clone(), self.crates.clone())
.await?;
let rust_with_src = base_image
.with_workdir("/mnt/src")
.with_directory(
"/usr/local/cargo",
rust_prebuild.directory("/usr/local/cargo"),
)
.with_directory("/mnt/src/target", incremental_dir)
.with_directory("/mnt/src/", src);
Ok(rust_with_src)
}
pub async fn build_test(&self) -> eyre::Result<()> {
let base = self.build_base().await?;
base.with_exec(vec!["cargo", "test", "--tests", "--workspace"])
.sync()
.await?;
Ok(())
}
}
#[async_trait]
impl PullRequestAction for RustLib {
async fn execute_pull_request(&self, _ctx: &mut cli::Context) -> eyre::Result<()> {
self.build_test().await?;
Ok(())
}
}
#[async_trait]
impl MainAction for RustLib {
async fn execute_main(&self, _ctx: &mut cli::Context) -> eyre::Result<()> {
self.build_test().await?;
Ok(())
}
}

View File

@ -1,527 +0,0 @@
use std::{path::PathBuf, sync::Arc};
use async_trait::async_trait;
use dagger_rust::source::RustSource;
use dagger_sdk::Container;
use futures::{stream, StreamExt};
use crate::{
dagger_middleware::{DaggerMiddleware, DynMiddleware},
Context, MainAction, PullRequestAction,
};
use self::architecture::{Architecture, Os};
#[derive(Clone)]
pub enum RustServiceStage {
BeforeDeps(DynMiddleware),
AfterDeps(DynMiddleware),
BeforeBase(DynMiddleware),
AfterBase(DynMiddleware),
BeforeBuild(DynMiddleware),
AfterBuild(DynMiddleware),
BeforePackage(DynMiddleware),
AfterPackage(DynMiddleware),
BeforeRelease(DynMiddleware),
AfterRelease(DynMiddleware),
}
#[derive(Clone)]
pub struct RustService {
client: dagger_sdk::Query,
base_image: Option<dagger_sdk::Container>,
final_image: Option<dagger_sdk::Container>,
stages: Vec<RustServiceStage>,
source: Option<PathBuf>,
crates: Vec<String>,
bin_name: String,
arch: Option<Architecture>,
os: Option<Os>,
deployment: bool,
}
impl From<dagger_sdk::Query> for RustService {
fn from(value: dagger_sdk::Query) -> Self {
Self {
client: value,
base_image: None,
final_image: None,
stages: Vec::new(),
source: None,
crates: Vec::new(),
bin_name: String::new(),
arch: None,
os: None,
deployment: true,
}
}
}
impl RustService {
pub async fn new(client: dagger_sdk::Query) -> eyre::Result<Self> {
Ok(Self {
client,
base_image: None,
final_image: None,
stages: Vec::new(),
source: None,
crates: Vec::new(),
bin_name: String::new(),
arch: None,
os: None,
deployment: true,
})
}
pub fn with_base_image(&mut self, base: dagger_sdk::Container) -> &mut Self {
self.base_image = Some(base);
self
}
pub fn with_stage(&mut self, stage: RustServiceStage) -> &mut Self {
self.stages.push(stage);
self
}
pub fn with_source(&mut self, path: impl Into<PathBuf>) -> &mut Self {
self.source = Some(path.into());
self
}
pub fn with_bin_name(&mut self, bin_name: impl Into<String>) -> &mut Self {
self.bin_name = bin_name.into();
self
}
pub fn with_crates(
&mut self,
crates: impl IntoIterator<Item = impl Into<String>>,
) -> &mut Self {
self.crates = crates.into_iter().map(|c| c.into()).collect();
self
}
pub fn with_arch(&mut self, arch: Architecture) -> &mut Self {
self.arch = Some(arch);
self
}
pub fn with_os(&mut self, os: Os) -> &mut Self {
self.os = Some(os);
self
}
pub fn with_deployment(&mut self, deployment: bool) -> &mut Self {
self.deployment = deployment;
self
}
fn get_src(&self) -> PathBuf {
self.source
.clone()
.unwrap_or(std::env::current_dir().unwrap())
}
fn get_arch(&self) -> Architecture {
self.arch
.clone()
.unwrap_or_else(|| match std::env::consts::ARCH {
"x86" | "x86_64" | "amd64" => Architecture::Amd64,
"arm" => Architecture::Arm64,
arch => panic!("unsupported architecture: {arch}"),
})
}
fn get_os(&self) -> Os {
self.os
.clone()
.unwrap_or_else(|| match std::env::consts::OS {
"linux" => Os::Linux,
"macos" => Os::MacOS,
os => panic!("unsupported os: {os}"),
})
}
async fn run_stage(
&self,
stages: impl IntoIterator<Item = &Arc<dyn DaggerMiddleware + Send + Sync>>,
container: Container,
) -> eyre::Result<Container> {
let before_deps_stream = stream::iter(stages.into_iter().map(Ok));
let res = StreamExt::fold(before_deps_stream, Ok(container), |base, m| async move {
match (base, m) {
(Ok(base), Ok(m)) => m.handle(base).await,
(_, Err(e)) | (Err(e), _) => eyre::bail!("failed with {e}"),
}
})
.await?;
Ok(res)
}
pub async fn build_base(&self) -> eyre::Result<Container> {
let client = self.client.clone();
let rust_src = RustSource::new(client.clone());
let (src, dep_src) = rust_src
.get_rust_src(Some(&self.get_src()), self.crates.clone())
.await?;
let base_image = self
.base_image
.clone()
.unwrap_or(client.container().from("rustlang/rust:nightly"));
let before_deps = self
.stages
.iter()
.filter_map(|s| match s {
RustServiceStage::BeforeDeps(middleware) => Some(middleware),
_ => None,
})
.collect::<Vec<_>>();
let image = self.run_stage(before_deps, base_image).await?;
let after_deps = self
.stages
.iter()
.filter_map(|s| match s {
RustServiceStage::AfterDeps(m) => Some(m),
_ => None,
})
.collect::<Vec<_>>();
let image = self.run_stage(after_deps, image).await?;
let before_base = self
.stages
.iter()
.filter_map(|s| match s {
RustServiceStage::BeforeBase(m) => Some(m),
_ => None,
})
.collect::<Vec<_>>();
let image = self.run_stage(before_base, image).await?;
let cache = client.cache_volume("rust_target_cache");
let rust_prebuild = image
.with_workdir("/mnt/src")
.with_directory("/mnt/src", dep_src)
.with_exec(vec!["cargo", "build", "--release", "--bin", &self.bin_name])
.with_mounted_cache("/mnt/src/target/", cache);
let incremental_dir = rust_src
.get_rust_target_src(&self.get_src(), rust_prebuild.clone(), self.crates.clone())
.await?;
let rust_with_src = image
.with_workdir("/mnt/src")
.with_directory(
"/usr/local/cargo",
rust_prebuild.directory("/usr/local/cargo"),
)
.with_directory("/mnt/src/target", incremental_dir)
.with_directory("/mnt/src/", src);
let after_base = self
.stages
.iter()
.filter_map(|s| match s {
RustServiceStage::AfterBase(m) => Some(m),
_ => None,
})
.collect::<Vec<_>>();
let image = self.run_stage(after_base, rust_with_src).await?;
Ok(image)
}
pub async fn build_release(&self) -> eyre::Result<Container> {
let base = self.build_base().await?;
let client = self.client.clone();
let before_build = self
.stages
.iter()
.filter_map(|s| match s {
RustServiceStage::BeforeBuild(m) => Some(m),
_ => None,
})
.collect::<Vec<_>>();
let base = self.run_stage(before_build, base).await?;
let binary_build =
base.with_exec(vec!["cargo", "build", "--release", "--bin", &self.bin_name]);
let after_build = self
.stages
.iter()
.filter_map(|s| match s {
RustServiceStage::AfterBuild(m) => Some(m),
_ => None,
})
.collect::<Vec<_>>();
let binary_build = self.run_stage(after_build, binary_build).await?;
let dest = self
.final_image
.clone()
.unwrap_or(client.container().from("debian:bookworm"));
let before_package = self
.stages
.iter()
.filter_map(|s| match s {
RustServiceStage::BeforePackage(m) => Some(m),
_ => None,
})
.collect::<Vec<_>>();
let dest = self.run_stage(before_package, dest).await?;
let final_image = dest.with_workdir("/mnt/app").with_file(
format!("/usr/local/bin/{}", self.bin_name),
binary_build.file(format!("/mnt/src/target/release/{}", self.bin_name)),
);
let after_package = self
.stages
.iter()
.filter_map(|s| match s {
RustServiceStage::AfterPackage(m) => Some(m),
_ => None,
})
.collect::<Vec<_>>();
let final_image = self.run_stage(after_package, final_image).await?;
Ok(final_image)
}
pub async fn build_test(&self) -> eyre::Result<()> {
let base = self.build_base().await?;
let before_build = self
.stages
.iter()
.filter_map(|s| match s {
RustServiceStage::BeforeBuild(m) => Some(m),
_ => None,
})
.collect::<Vec<_>>();
let base = self.run_stage(before_build, base).await?;
base.with_exec(vec!["cargo", "test", "--release"])
.sync()
.await?;
Ok(())
}
}
#[async_trait]
impl PullRequestAction for RustService {
async fn execute_pull_request(&self, ctx: &mut Context) -> eyre::Result<()> {
self.build_test().await?;
let container = self.build_release().await?;
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
let tag = format!(
"docker.io/kasperhermansen/{}:dev-{}",
self.bin_name, timestamp,
);
container.publish(&tag).await?;
ctx.set_image_tag(format!("dev-{}", &timestamp.to_string()))?;
Ok(())
}
}
const IMAGE_TAG: &str = "RUST_SERVICE_IMAGE_TAG";
pub trait RustServiceContext {
fn set_image_tag(&mut self, tag: impl Into<String>) -> eyre::Result<()>;
fn get_image_tag(&self) -> eyre::Result<Option<String>>;
}
impl RustServiceContext for Context {
fn get_image_tag(&self) -> eyre::Result<Option<String>> {
Ok(self.get(IMAGE_TAG).cloned())
}
fn set_image_tag(&mut self, tag: impl Into<String>) -> eyre::Result<()> {
let tag = tag.into();
self.insert(IMAGE_TAG.to_string(), tag);
Ok(())
}
}
#[async_trait]
impl MainAction for RustService {
async fn execute_main(&self, ctx: &mut Context) -> eyre::Result<()> {
let container = self.build_release().await?;
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
let tag = format!(
"docker.io/kasperhermansen/{}:main-{}",
self.bin_name, timestamp,
);
container.publish(&tag).await?;
ctx.set_image_tag(format!("main-{}", &timestamp.to_string()))?;
if self.deployment {
let update_deployments_docker_image =
"docker.io/kasperhermansen/update-deployment:1701123940";
let dep = self
.client
.container()
.from(update_deployments_docker_image);
let dep = if let Ok(sock) = std::env::var("SSH_AUTH_SOCK") {
dep.with_unix_socket("/tmp/ssh_sock", self.client.host().unix_socket(sock))
.with_env_variable("SSH_AUTH_SOCK", "/tmp/ssh_sock")
.with_exec(vec![
"update-deployment",
"--repo",
&format!(
"git@git.front.kjuulh.io:kjuulh/{}-deployment.git",
self.bin_name
),
"--service",
&self.bin_name,
"--image",
&format!("kasperhermansen/{}:main-{}", self.bin_name, timestamp),
])
} else {
dep.with_env_variable("GIT_USERNAME", "kjuulh")
.with_env_variable(
"GIT_PASSWORD",
std::env::var("GIT_PASSWORD").expect("GIT_PASSWORD to be set"),
)
.with_exec(vec![
"update-deployment",
"--repo",
&format!(
"https://git.front.kjuulh.io/kjuulh/{}-deployment.git",
self.bin_name
),
"--service",
&self.bin_name,
"--image",
&format!("kasperhermansen/{}:main-{}", self.bin_name, timestamp),
])
};
dep.sync().await?;
}
Ok(())
}
}
pub mod architecture {
#[derive(Debug, Clone)]
pub enum Architecture {
Amd64,
Arm64,
}
#[derive(Debug, Clone)]
pub enum Os {
Linux,
MacOS,
}
}
mod apt;
mod apt_ca_certificates;
mod assets;
mod cargo_binstall;
mod cargo_clean;
mod clap_sanity_test;
mod cuddle_cli;
mod cuddle_file;
mod dagger_bin;
mod docker_cache;
mod docker_cli;
mod kubectl;
mod mold;
mod rust_workspace;
mod sqlx;
mod ssh_agent;
pub mod extensions {
pub use super::apt::*;
pub use super::apt_ca_certificates::*;
pub use super::assets::*;
pub use super::cargo_binstall::*;
pub use super::cargo_clean::*;
pub use super::clap_sanity_test::*;
pub use super::cuddle_cli::*;
pub use super::cuddle_file::*;
pub use super::dagger_bin::*;
pub use super::docker_cache::*;
pub use super::docker_cli::*;
pub use super::kubectl::*;
pub use super::mold::*;
pub use super::rust_workspace::*;
pub use super::sqlx::*;
pub use super::ssh_agent::*;
}
#[cfg(test)]
mod test {
#[tokio::test]
#[cfg(any(feature = "dagger", feature = "integration"))]
async fn test_can_build_rust() -> eyre::Result<()> {
dagger_sdk::connect(|client| async move {
let root_dir = std::path::PathBuf::from("../../").canonicalize()?;
let container = RustService::from(client.clone())
.with_arch(Architecture::Amd64)
.with_os(Os::Linux)
.with_source(root_dir)
.with_bin_name("ci")
.with_crates(["crates/*", "examples/*", "ci"])
.with_apt(&["git"])
.with_cargo_binstall("latest", ["sqlx-cli"])
.with_mold("2.3.3")
// .with_stage(RustServiceStage::BeforeDeps(middleware(|c| {
// async move {
// // Noop
// Ok(c)
// }
// .boxed()
// })))
.with_clap_sanity_test()
.build_release()
.await?;
container.sync().await?;
Ok(())
})
.await?;
Ok(())
}
}

View File

@ -1,86 +0,0 @@
use std::sync::Arc;
use async_trait::async_trait;
use dagger_sdk::Container;
use crate::{dagger_middleware::DaggerMiddleware, leptos_service::LeptosService};
use super::RustService;
pub struct Apt {
deps: Vec<String>,
}
impl Default for Apt {
fn default() -> Self {
Self::new()
}
}
impl Apt {
pub fn new() -> Self {
Self { deps: Vec::new() }
}
pub fn add(mut self, dep_name: impl Into<String>) -> Self {
self.deps.push(dep_name.into());
self
}
pub fn extend(mut self, deps: &[&str]) -> Self {
self.deps.extend(deps.iter().map(|s| s.to_string()));
self
}
}
#[async_trait]
impl DaggerMiddleware for Apt {
async fn handle(&self, container: Container) -> eyre::Result<Container> {
let mut deps = vec!["apt", "install", "-y"];
deps.extend(self.deps.iter().map(|s| s.as_str()));
let c = container.with_exec(vec!["apt", "update"]).with_exec(deps);
Ok(c)
}
}
pub trait AptExt {
fn with_apt(&mut self, deps: &[&str]) -> &mut Self {
self
}
fn with_apt_release(&mut self, deps: &[&str]) -> &mut Self {
self
}
}
impl AptExt for RustService {
fn with_apt(&mut self, deps: &[&str]) -> &mut Self {
self.with_stage(super::RustServiceStage::BeforeDeps(Arc::new(
Apt::new().extend(deps),
)))
}
fn with_apt_release(&mut self, deps: &[&str]) -> &mut Self {
self.with_stage(super::RustServiceStage::BeforePackage(Arc::new(
Apt::new().extend(deps),
)))
}
}
impl AptExt for LeptosService {
fn with_apt(&mut self, deps: &[&str]) -> &mut Self {
self.with_stage(super::RustServiceStage::BeforeDeps(Arc::new(
Apt::new().extend(deps),
)))
}
fn with_apt_release(&mut self, deps: &[&str]) -> &mut Self {
self.with_stage(super::RustServiceStage::BeforePackage(Arc::new(
Apt::new().extend(deps),
)))
}
}

View File

@ -1,62 +0,0 @@
use std::sync::Arc;
use async_trait::async_trait;
use dagger_sdk::Container;
use crate::{dagger_middleware::DaggerMiddleware, leptos_service::LeptosService};
use super::RustService;
pub struct AptCaCertificates {}
impl Default for AptCaCertificates {
fn default() -> Self {
Self::new()
}
}
impl AptCaCertificates {
pub fn new() -> Self {
Self {}
}
}
#[async_trait]
impl DaggerMiddleware for AptCaCertificates {
async fn handle(&self, container: Container) -> eyre::Result<Container> {
let c = container
.with_exec(vec!["apt", "update"])
.with_exec(vec!["apt", "install", "-y", "ca-certificates"])
.with_exec(vec!["update-ca-certificates"]);
Ok(c)
}
}
pub trait AptCaCertificatesExt {
fn with_apt_ca_certificates(&mut self) -> &mut Self {
self
}
}
impl AptCaCertificatesExt for RustService {
fn with_apt_ca_certificates(&mut self) -> &mut Self {
self.with_stage(super::RustServiceStage::BeforeDeps(Arc::new(
AptCaCertificates::new(),
)))
.with_stage(super::RustServiceStage::BeforePackage(Arc::new(
AptCaCertificates::new(),
)))
}
}
impl AptCaCertificatesExt for LeptosService {
fn with_apt_ca_certificates(&mut self) -> &mut Self {
self.with_stage(super::RustServiceStage::BeforeDeps(Arc::new(
AptCaCertificates::new(),
)))
.with_stage(super::RustServiceStage::BeforePackage(Arc::new(
AptCaCertificates::new(),
)))
}
}

View File

@ -1,68 +0,0 @@
use std::{path::PathBuf, sync::Arc};
use async_trait::async_trait;
use dagger_sdk::Container;
use crate::{dagger_middleware::DaggerMiddleware, leptos_service::LeptosService};
use super::RustService;
pub struct Assets {
client: dagger_sdk::Query,
assets: Vec<(PathBuf, PathBuf)>,
}
impl Assets {
pub fn new(client: dagger_sdk::Query) -> Self {
Self {
client,
assets: Vec::default(),
}
}
fn with_folders(mut self, folders: impl IntoIterator<Item = (PathBuf, PathBuf)>) -> Self {
let mut folders = folders.into_iter().collect::<Vec<(PathBuf, PathBuf)>>();
self.assets.append(&mut folders);
self
}
}
#[async_trait]
impl DaggerMiddleware for Assets {
async fn handle(&self, container: Container) -> eyre::Result<Container> {
let container =
self.assets
.iter()
.fold(container, |container, (src_asset_path, dest_asset_path)| {
let src_path = src_asset_path.display().to_string();
let dest_path = dest_asset_path.display().to_string();
let path = self.client.host().directory(src_path);
container.with_directory(dest_path, path)
});
Ok(container)
}
}
pub trait AssetsExt {
fn with_assets(&mut self, folders: impl IntoIterator<Item = (PathBuf, PathBuf)>) -> &mut Self {
self
}
}
impl AssetsExt for RustService {
fn with_assets(&mut self, folders: impl IntoIterator<Item = (PathBuf, PathBuf)>) -> &mut Self {
self.with_stage(super::RustServiceStage::AfterPackage(Arc::new(
Assets::new(self.client.clone()).with_folders(folders),
)))
}
}
impl AssetsExt for LeptosService {
fn with_assets(&mut self, folders: impl IntoIterator<Item = (PathBuf, PathBuf)>) -> &mut Self {
self.with_stage(super::RustServiceStage::AfterPackage(Arc::new(
Assets::new(self.client.clone()).with_folders(folders),
)))
}
}

View File

@ -1,120 +0,0 @@
use std::sync::Arc;
use async_trait::async_trait;
use dagger_sdk::Container;
use crate::{dagger_middleware::DaggerMiddleware, leptos_service::LeptosService};
use super::{
architecture::{Architecture, Os},
RustService,
};
pub struct CargoBInstall {
arch: Architecture,
os: Os,
version: String,
crates: Vec<String>,
}
impl CargoBInstall {
pub fn new(
arch: Architecture,
os: Os,
version: impl Into<String>,
crates: impl Into<Vec<String>>,
) -> Self {
Self {
arch,
os,
version: version.into(),
crates: crates.into(),
}
}
fn get_arch(&self) -> String {
match self.arch {
Architecture::Amd64 => "x86_64",
Architecture::Arm64 => "armv7",
}
.into()
}
fn get_os(&self) -> String {
match self.os {
Os::Linux => "linux",
Os::MacOS => "darwin",
}
.into()
}
pub fn get_download_url(&self) -> String {
format!("https://github.com/cargo-bins/cargo-binstall/releases/{}/download/cargo-binstall-{}-unknown-{}-musl.tgz", self.version, self.get_arch(), self.get_os())
}
pub fn get_archive(&self) -> String {
format!(
"cargo-binstall-{}-unknown-{}-musl.tgz",
self.get_arch(),
self.get_os()
)
}
}
#[async_trait]
impl DaggerMiddleware for CargoBInstall {
async fn handle(&self, container: Container) -> eyre::Result<Container> {
let c = container
.with_exec(vec!["wget", &self.get_download_url()])
.with_exec(vec!["tar", "-xvf", &self.get_archive()])
.with_exec(
"mv cargo-binstall /usr/local/cargo/bin"
.split_whitespace()
.collect(),
);
let c = self.crates.iter().cloned().fold(c, |acc, item| {
acc.with_exec(vec!["cargo", "binstall", &item, "-y"])
});
Ok(c)
}
}
pub trait CargoBInstallExt {
fn with_cargo_binstall(
&mut self,
version: impl Into<String>,
crates: impl IntoIterator<Item = impl Into<String>>,
) -> &mut Self {
self
}
}
impl CargoBInstallExt for RustService {
fn with_cargo_binstall(
&mut self,
version: impl Into<String>,
crates: impl IntoIterator<Item = impl Into<String>>,
) -> &mut Self {
let crates: Vec<String> = crates.into_iter().map(|s| s.into()).collect();
self.with_stage(super::RustServiceStage::BeforeDeps(Arc::new(
CargoBInstall::new(self.get_arch(), self.get_os(), version, crates),
)))
}
}
impl CargoBInstallExt for LeptosService {
fn with_cargo_binstall(
&mut self,
version: impl Into<String>,
crates: impl IntoIterator<Item = impl Into<String>>,
) -> &mut Self {
let crates: Vec<String> = crates.into_iter().map(|s| s.into()).collect();
self.with_stage(super::RustServiceStage::BeforeDeps(Arc::new(
CargoBInstall::new(self.get_arch(), self.get_os(), version, crates),
)))
}
}

View File

@ -1,45 +0,0 @@
use std::sync::Arc;
use async_trait::async_trait;
use dagger_sdk::Container;
use crate::dagger_middleware::DaggerMiddleware;
use super::RustService;
pub struct CargoClean;
impl Default for CargoClean {
fn default() -> Self {
Self::new()
}
}
impl CargoClean {
pub fn new() -> Self {
Self {}
}
}
#[async_trait]
impl DaggerMiddleware for CargoClean {
async fn handle(&self, container: Container) -> eyre::Result<Container> {
Ok(container.with_exec(vec!["cargo", "clean"]))
}
}
pub trait CargoCleanExt {
fn with_cargo_clean(&mut self) -> &mut Self {
self
}
}
impl CargoCleanExt for RustService {
fn with_cargo_clean(&mut self) -> &mut Self {
self.with_stage(super::RustServiceStage::BeforeBuild(Arc::new(
CargoClean::new(),
)));
self
}
}

View File

@ -1,43 +0,0 @@
use std::sync::Arc;
use async_trait::async_trait;
use dagger_sdk::Container;
use crate::dagger_middleware::DaggerMiddleware;
use super::RustService;
pub struct ClapSanityTest {
bin_name: String,
}
impl ClapSanityTest {
pub fn new(bin_name: impl Into<String>) -> Self {
Self {
bin_name: bin_name.into(),
}
}
}
#[async_trait]
impl DaggerMiddleware for ClapSanityTest {
async fn handle(&self, container: Container) -> eyre::Result<Container> {
Ok(container.with_exec(vec![&self.bin_name, "--help"]))
}
}
pub trait ClapSanityTestExt {
fn with_clap_sanity_test(&mut self) -> &mut Self {
self
}
}
impl ClapSanityTestExt for RustService {
fn with_clap_sanity_test(&mut self) -> &mut Self {
self.with_stage(super::RustServiceStage::AfterPackage(Arc::new(
ClapSanityTest::new(&self.bin_name),
)));
self
}
}

View File

@ -1,49 +0,0 @@
use std::sync::Arc;
use async_trait::async_trait;
use dagger_sdk::Container;
use crate::dagger_middleware::DaggerMiddleware;
use super::RustService;
pub struct CuddleCli {
client: dagger_sdk::Query,
}
impl CuddleCli {
pub fn new(client: dagger_sdk::Query) -> Self {
Self { client }
}
}
#[async_trait]
impl DaggerMiddleware for CuddleCli {
async fn handle(&self, container: Container) -> eyre::Result<Container> {
let cuddle = self
.client
.container()
.from("kasperhermansen/cuddle:latest");
Ok(container.with_file(
"/usr/local/bin/cuddle",
cuddle.file("/usr/local/cargo/bin/cuddle"),
))
}
}
pub trait CuddleCliExt {
fn with_cuddle_cli(&mut self) -> &mut Self {
self
}
}
impl CuddleCliExt for RustService {
fn with_cuddle_cli(&mut self) -> &mut Self {
self.with_stage(super::RustServiceStage::BeforePackage(Arc::new(
CuddleCli::new(self.client.clone()),
)));
self
}
}

View File

@ -1,86 +0,0 @@
use std::path::PathBuf;
use crate::{
cuddle_file::{CuddleDatabase, CuddleFile},
rust_service::extensions::AptExt,
};
use super::{
extensions::{AssetsExt, CargoCleanExt, SqlxExt},
RustService,
};
#[derive(Default)]
pub struct CuddleFileAction {}
impl CuddleFileAction {
pub fn new() -> Self {
Self {}
}
}
pub trait CuddleFileExt {
fn with_cuddle_file(&mut self, cuddle_file: &CuddleFile) -> &mut Self;
}
impl CuddleFileExt for RustService {
fn with_cuddle_file(&mut self, cuddle_file: &CuddleFile) -> &mut Self {
let mut s = self
.with_bin_name(&cuddle_file.vars.service)
.with_deployment(false);
tracing::trace!("with cuddle file: {:+?}", &cuddle_file);
if let Some(components) = &cuddle_file.components {
s = if let Some(database) = &components.database {
match database {
CuddleDatabase::Enabled(true) => s.with_sqlx_migrations(
PathBuf::from("crates")
.join(&cuddle_file.vars.service)
.join("migrations/crdb"),
),
CuddleDatabase::Values { migrations } => s.with_sqlx_migrations(migrations),
CuddleDatabase::Enabled(false) | CuddleDatabase::Default {} => s,
}
} else {
s
};
if let Some(assets) = &components.assets {
if let Some(true) = assets.clean {
s = s.with_cargo_clean()
}
if let Some(volumes) = &assets.volumes {
let mappings = volumes.iter().cloned().map(|val| (val.from, val.to));
s = s.with_assets(mappings);
}
}
if let Some(packages) = &components.packages {
s = s
.with_apt(
packages
.debian
.dev
.iter()
.map(|r| r.as_str())
.collect::<Vec<_>>()
.as_slice(),
)
.with_apt_release(
packages
.debian
.release
.iter()
.map(|r| r.as_str())
.collect::<Vec<_>>()
.as_slice(),
);
}
}
s
}
}

View File

@ -1,72 +0,0 @@
use std::sync::Arc;
use async_trait::async_trait;
use dagger_sdk::Container;
use crate::dagger_middleware::DaggerMiddleware;
use super::RustService;
pub struct DaggerBin {
client: dagger_sdk::Query,
version: String,
}
impl DaggerBin {
pub fn new(client: dagger_sdk::Query, version: impl Into<String>) -> Self {
Self {
client,
version: version.into(),
}
}
}
#[async_trait]
impl DaggerMiddleware for DaggerBin {
async fn handle(&self, container: Container) -> eyre::Result<Container> {
let install_script = self.client.http("https://dl.dagger.io/dagger/install.sh");
let dagger_bin = self
.client
.container()
.from("alpine")
.with_file_opts(
"/mnt/install.sh",
install_script,
dagger_sdk::ContainerWithFileOpts {
owner: None,
permissions: Some(0o755),
expand: None,
},
)
.with_env_variable("DAGGER_VERSION", &self.version)
.with_exec(vec!["/mnt/install.sh"])
.file("/bin/dagger");
Ok(container
.with_file_opts(
"/usr/local/bin/dagger",
dagger_bin,
dagger_sdk::ContainerWithFileOpts {
owner: None,
permissions: Some(0o755),
expand: None,
},
)
.with_exec(vec!["/usr/local/bin/dagger", "version"]))
}
}
pub trait DaggerBinExt {
fn with_dagger_bin(&mut self, dagger_version: impl Into<String>) -> &mut Self;
}
impl DaggerBinExt for RustService {
fn with_dagger_bin(&mut self, dagger_version: impl Into<String>) -> &mut Self {
self.with_stage(super::RustServiceStage::AfterPackage(Arc::new(
DaggerBin::new(self.client.clone(), dagger_version.into()),
)));
self
}
}

View File

@ -1,70 +0,0 @@
use std::sync::Arc;
use async_trait::async_trait;
use dagger_sdk::{Container, ImageMediaTypes};
use crate::dagger_middleware::DaggerMiddleware;
use super::RustService;
pub struct DockerCache {
client: dagger_sdk::Query,
image_name: String,
}
impl DockerCache {
pub fn new(client: dagger_sdk::Query, image_name: impl Into<String>) -> Self {
Self {
client,
image_name: image_name.into(),
}
}
}
#[async_trait]
impl DaggerMiddleware for DockerCache {
async fn handle(&self, container: Container) -> eyre::Result<Container> {
match (
std::env::var("REGISTRY_CACHE_USERNAME"),
std::env::var("REGISTRY_CACHE_PASSWORD"),
) {
(Ok(username), Ok(password)) => {
let url = format!("harbor.front.kjuulh.io/cache/{}:cache", self.image_name);
let secret = self.client.set_secret("REGISTRY_CACHE_PASSWORD", password);
container
.with_registry_auth(&url, &username, secret)
.publish_opts(
&url,
dagger_sdk::ContainerPublishOpts {
forced_compression: Some(dagger_sdk::ImageLayerCompression::Zstd),
media_types: Some(ImageMediaTypes::OciMediaTypes),
platform_variants: None,
},
)
.await?;
}
_ => {
eprintln!("failed to find REGISTRY_CACHE_USERNAME or REGISTRY_CACHE_PASSWORD");
}
}
Ok(container)
}
}
pub trait DockerCacheExt {
fn with_docker_cache(&mut self) -> &mut Self {
self
}
}
impl DockerCacheExt for RustService {
fn with_docker_cache(&mut self) -> &mut Self {
self.with_stage(super::RustServiceStage::AfterPackage(Arc::new(
DockerCache::new(self.client.clone(), self.bin_name.clone()),
)));
self
}
}

View File

@ -1,48 +0,0 @@
use std::sync::Arc;
use async_trait::async_trait;
use dagger_sdk::{Container};
use crate::dagger_middleware::DaggerMiddleware;
use super::RustService;
pub struct DockerCli {
client: dagger_sdk::Query,
}
impl DockerCli {
pub fn new(client: dagger_sdk::Query) -> Self {
Self { client }
}
}
#[async_trait]
impl DaggerMiddleware for DockerCli {
async fn handle(&self, container: Container) -> eyre::Result<Container> {
let docker = self.client.container().from("docker:cli");
Ok(container
.with_file(
"/usr/local/bin/docker",
docker.file("/usr/local/bin/docker"),
)
.with_directory("/certs", docker.directory("/certs")))
}
}
pub trait DockerCliExt {
fn with_docker_cli(&mut self) -> &mut Self {
self
}
}
impl DockerCliExt for RustService {
fn with_docker_cli(&mut self) -> &mut Self {
self.with_stage(super::RustServiceStage::BeforePackage(Arc::new(
DockerCli::new(self.client.clone()),
)));
self
}
}

View File

@ -1,75 +0,0 @@
use std::sync::Arc;
use async_trait::async_trait;
use dagger_sdk::Container;
use crate::dagger_middleware::DaggerMiddleware;
use super::RustService;
pub struct Kubectl {
client: dagger_sdk::Query,
}
impl Kubectl {
pub fn new(client: dagger_sdk::Query) -> Self {
Self { client }
}
}
const KUBESLICEDOWNLOAD: &str = r#"slice_VERSION=v1.2.7 && \
wget -O kubectl-slice_linux_x86_64.tar.gz "https://github.com/patrickdappollonio/kubectl-slice/releases/download/$slice_VERSION/kubectl-slice_linux_x86_64.tar.gz" && \
tar -xf kubectl-slice_linux_x86_64.tar.gz && \
chmod +x ./kubectl-slice && \
mv ./kubectl-slice /usr/local/bin/kubectl-slice && \
rm kubectl-slice_linux_x86_64.tar.gz"#;
#[async_trait]
impl DaggerMiddleware for Kubectl {
async fn handle(&self, container: Container) -> eyre::Result<Container> {
let kubectl = self
.client
.container()
.from("line/kubectl-kustomize:1.29.1-5.3.0");
let kubeslice = self
.client
.container()
.from("alpine:3.19")
.with_exec(vec!["apk", "add", "tar", "wget"])
.with_exec(vec!["sh", "-c", KUBESLICEDOWNLOAD]);
let helm = self.client.container().from("alpine/helm:3.11.1");
Ok(container
.with_file(
"/usr/local/bin/kubectl",
kubectl.file("/usr/local/bin/kubectl"),
)
.with_file(
"/usr/local/bin/kustomize",
kubectl.file("/usr/local/bin/kustomize"),
)
.with_file(
"/usr/local/bin/kubectl-slice",
kubeslice.file("/usr/local/bin/kubectl-slice"),
)
.with_file("/usr/local/bin/helm", helm.file("/usr/bin/helm")))
}
}
pub trait KubectlExt {
fn with_kubectl(&mut self) -> &mut Self {
self
}
}
impl KubectlExt for RustService {
fn with_kubectl(&mut self) -> &mut Self {
self.with_stage(super::RustServiceStage::BeforePackage(Arc::new(
Kubectl::new(self.client.clone()),
)));
self
}
}

View File

@ -1,104 +0,0 @@
use std::sync::Arc;
use async_trait::async_trait;
use crate::dagger_middleware::DaggerMiddleware;
use super::{
architecture::{Architecture, Os},
RustService,
};
pub struct MoldInstall {
arch: Architecture,
os: Os,
version: String,
}
impl MoldInstall {
pub fn new(arch: Architecture, os: Os, version: impl Into<String>) -> Self {
Self {
arch,
os,
version: version.into(),
}
}
fn get_arch(&self) -> String {
match self.arch {
Architecture::Amd64 => "x86_64",
Architecture::Arm64 => "arm",
}
.into()
}
fn get_os(&self) -> String {
match &self.os {
Os::Linux => "linux",
o => todo!("os not implemented for mold: {:?}", o),
}
.into()
}
pub fn get_download_url(&self) -> String {
format!(
"https://github.com/rui314/mold/releases/download/v{}/mold-{}-{}-{}.tar.gz",
self.version,
self.version,
self.get_arch(),
self.get_os()
)
}
pub fn get_folder(&self) -> String {
format!(
"mold-{}-{}-{}",
self.version,
self.get_arch(),
self.get_os()
)
}
pub fn get_archive_name(&self) -> String {
format!(
"mold-{}-{}-{}.tar.gz",
self.version,
self.get_arch(),
self.get_os()
)
}
}
#[async_trait]
impl DaggerMiddleware for MoldInstall {
async fn handle(
&self,
container: dagger_sdk::Container,
) -> eyre::Result<dagger_sdk::Container> {
println!("installing mold");
let c = container
.with_exec(vec!["wget", &self.get_download_url()])
.with_exec(vec!["tar", "-xvf", &self.get_archive_name()])
.with_exec(vec![
"mv",
&format!("{}/bin/mold", self.get_folder()),
"/usr/bin/mold",
]);
Ok(c)
}
}
pub trait MoldActionExt {
fn with_mold(&mut self, version: impl Into<String>) -> &mut Self {
self
}
}
impl MoldActionExt for RustService {
fn with_mold(&mut self, version: impl Into<String>) -> &mut Self {
self.with_stage(super::RustServiceStage::AfterDeps(Arc::new(
MoldInstall::new(self.get_arch(), self.get_os(), version),
)))
}
}

View File

@ -1,66 +0,0 @@
use std::sync::Arc;
use async_trait::async_trait;
use dagger_sdk::Container;
use crate::{dagger_middleware::DaggerMiddleware, leptos_service::LeptosService, rust_workspace};
use super::RustService;
pub struct RustWorkspace {}
impl Default for RustWorkspace {
fn default() -> Self {
Self::new()
}
}
impl RustWorkspace {
pub fn new() -> Self {
Self {}
}
}
#[async_trait]
impl DaggerMiddleware for RustWorkspace {
async fn handle(&self, container: Container) -> eyre::Result<Container> {
Ok(container)
}
}
#[async_trait]
pub trait RustWorkspaceExt {
async fn with_workspace_crates(&mut self) -> &mut Self {
self
}
}
#[async_trait]
impl RustWorkspaceExt for RustService {
async fn with_workspace_crates(&mut self) -> &mut Self {
self.with_crates(get_members().await)
.with_stage(super::RustServiceStage::BeforeDeps(Arc::new(
RustWorkspace::new(),
)))
}
}
#[async_trait]
impl RustWorkspaceExt for LeptosService {
async fn with_workspace_crates(&mut self) -> &mut Self {
self.with_crates(get_members().await)
.with_stage(super::RustServiceStage::BeforeDeps(Arc::new(
RustWorkspace::new(),
)))
}
}
async fn get_members() -> Vec<String> {
if let Ok(Some(file)) = rust_workspace::File::read_file().await {
if let Some(members) = file.get_workspace_members() {
return members;
}
}
Vec::new()
}

View File

@ -1,81 +0,0 @@
use std::{path::PathBuf, sync::Arc};
use async_trait::async_trait;
use dagger_sdk::Container;
use crate::dagger_middleware::DaggerMiddleware;
use super::RustService;
pub struct Sqlx {
client: dagger_sdk::Query,
migration_path: Option<PathBuf>,
}
impl Sqlx {
pub fn new(client: dagger_sdk::Query) -> Self {
Self {
client,
migration_path: None,
}
}
pub fn with_migration_path(mut self, migration_path: impl Into<PathBuf>) -> Self {
self.migration_path = Some(migration_path.into());
self
}
}
#[async_trait]
impl DaggerMiddleware for Sqlx {
async fn handle(&self, container: Container) -> eyre::Result<Container> {
let container = if std::path::PathBuf::from(".sqlx/").exists() {
tracing::debug!("found .sqlx folder enabling offline mode");
let src = self.client.host().directory(".sqlx/");
container
.with_directory(".sqlx", src)
.with_env_variable("SQLX_OFFLINE", "true")
} else {
tracing::debug!("did not find a .sqlx folder, requires a running database");
container
};
let container = if let Some(migration_path) = &self.migration_path {
container
.with_directory(
"/mnt/sqlx/migrations",
self.client
.host()
.directory(migration_path.display().to_string()),
)
.with_env_variable("NEFARIOUS_DB_MIGRATION_PATH", "/mnt/sqlx/migrations")
} else {
container
};
Ok(container)
}
}
pub trait SqlxExt {
fn with_sqlx(&mut self) -> &mut Self;
fn with_sqlx_migrations(&mut self, path: impl Into<PathBuf>) -> &mut Self;
}
impl SqlxExt for RustService {
fn with_sqlx(&mut self) -> &mut Self {
self.with_stage(super::RustServiceStage::BeforeBuild(Arc::new(Sqlx::new(
self.client.clone(),
))))
}
fn with_sqlx_migrations(&mut self, path: impl Into<PathBuf>) -> &mut Self {
self.with_stage(super::RustServiceStage::BeforeBuild(Arc::new(
Sqlx::new(self.client.clone()).with_migration_path(path),
)))
}
}

View File

@ -1,79 +0,0 @@
use std::sync::Arc;
use async_trait::async_trait;
use dagger_sdk::{Container, ContainerWithNewFileOptsBuilder};
use eyre::Context;
use crate::{dagger_middleware::DaggerMiddleware, leptos_service::LeptosService};
use super::RustService;
pub struct SshAgent {
client: dagger_sdk::Query,
}
impl SshAgent {
pub fn new(client: dagger_sdk::Query) -> Self {
Self { client }
}
}
#[async_trait]
impl DaggerMiddleware for SshAgent {
async fn handle(&self, container: Container) -> eyre::Result<Container> {
let sock_var =
std::env::var("SSH_AUTH_SOCK").context("failed to find variable SSH_AUTH_SOCK")?;
let socket = self.client.host().unix_socket(&sock_var);
let c = container
.with_new_file_opts(
".ssh/config".to_string(),
r#"
Host *
UserKnownHostsFile=/dev/null
StrictHostKeyChecking no
"#,
ContainerWithNewFileOptsBuilder::default()
.permissions(0o700_isize)
.build()?,
)
.with_unix_socket(&sock_var, socket)
.with_env_variable("SSH_AUTH_SOCK", &sock_var);
Ok(c)
}
}
pub trait SshAgentExt {
fn with_ssh_agent(&mut self) -> &mut Self {
self
}
}
impl SshAgentExt for RustService {
fn with_ssh_agent(&mut self) -> &mut Self {
let client = self.client.clone();
self.with_stage(super::RustServiceStage::BeforeDeps(Arc::new(
SshAgent::new(client.clone()),
)))
.with_stage(super::RustServiceStage::BeforeBase(Arc::new(
SshAgent::new(client),
)))
}
}
impl SshAgentExt for LeptosService {
fn with_ssh_agent(&mut self) -> &mut Self {
let client = self.client.clone();
self.with_stage(super::RustServiceStage::BeforeDeps(Arc::new(
SshAgent::new(client.clone()),
)))
.with_stage(super::RustServiceStage::AfterBase(Arc::new(SshAgent::new(
client,
))))
}
}

View File

@ -1,31 +0,0 @@
use serde::Deserialize;
#[derive(Deserialize, Clone, Debug)]
pub struct Workspace {
pub members: Vec<String>,
}
#[derive(Deserialize, Clone, Debug)]
pub struct File {
pub workspace: Option<Workspace>,
}
impl File {
pub async fn read_file() -> eyre::Result<Option<Self>> {
let file = match tokio::fs::read_to_string("Cargo.toml").await {
Ok(file) => file,
Err(e) => {
tracing::warn!("Cargo.toml was not found: {}", e);
return Ok(None);
}
};
let workspace_file: File = toml::from_str(&file)?;
Ok(Some(workspace_file))
}
pub fn get_workspace_members(&self) -> Option<Vec<String>> {
self.workspace.as_ref().map(|w| w.members.clone())
}
}

View File

@ -1,4 +1,4 @@
use std::sync::Arc;
use std::sync::{Arc, Mutex};
use models::{CuddlePleaseArgs, CuddlePleaseSrcArgs};
use traits::CuddlePlease;
@ -103,7 +103,7 @@ impl DaggerCuddlePleaseAction {
}
#[derive(Clone)]
pub struct DaggerCuddlePlease {
struct DaggerCuddlePlease {
client: dagger_sdk::Query,
}
@ -189,12 +189,13 @@ impl DaggerCuddlePlease {
dagger_sdk::QueryGitOpts {
experimental_service_host: None,
keep_git_dir: Some(true),
ssh_auth_socket: Some(socket.id().await?),
ssh_known_hosts: None,
},
)
.branch("main")
.tree()
.tree_opts(dagger_sdk::GitRefTreeOpts {
ssh_auth_socket: Some(socket.id().await?),
ssh_known_hosts: None,
})
} else {
self.client
.git_opts(
@ -202,8 +203,6 @@ impl DaggerCuddlePlease {
dagger_sdk::QueryGitOpts {
experimental_service_host: None,
keep_git_dir: Some(true),
ssh_auth_socket: None,
ssh_known_hosts: None,
},
)
.branch("main")
@ -285,16 +284,17 @@ impl DaggerCuddlePlease {
.with_env_variable("SSH_AUTH_SOCK", "/tmp/ssh.sock")
.with_new_file_opts(
"/root/.ssh/config",
"
dagger_sdk::ContainerWithNewFileOpts {
contents: Some(
"
Host *
User git
StrictHostKeyChecking no
UserKnownHostsFile /dev/null
",
dagger_sdk::ContainerWithNewFileOpts {
),
owner: Some("root"),
permissions: Some(700),
expand: None,
},
);

View File

@ -44,9 +44,6 @@ impl RustBuild {
.from(rust_version.to_string())
.with_exec(vec!["rustup", "target", "add", &target.to_string()])
.with_exec(vec!["apt", "update"])
.with_exec(vec!["wget", "https://github.com/rui314/mold/releases/latest/download/mold-2.3.3-x86_64-linux.tar.gz"])
.with_exec("tar -xvf mold-2.3.3-x86_64-linux.tar.gz".split_whitespace().collect())
.with_exec("mv mold-2.3.3-x86_64-linux/bin/mold /usr/bin/mold".split_whitespace().collect())
.with_exec(deps);
let target_cache = self.client.cache_volume(format!(
@ -113,8 +110,6 @@ impl RustBuild {
.await?;
let bin = build_container
.with_env_variable("SQLX_OFFLINE", "true")
.with_exec(vec!["cargo", "clean"])
.with_exec(vec![
"cargo",
"build",
@ -203,6 +198,7 @@ impl RustBuild {
let base_debian = self
.client
.container_opts(dagger_sdk::QueryContainerOpts {
id: None,
platform: Some(target.into_platform()),
})
.from(image);
@ -233,6 +229,7 @@ impl RustBuild {
let base_debian = self
.client
.container_opts(dagger_sdk::QueryContainerOpts {
id: None,
platform: Some(target.into_platform()),
})
.from(image);

View File

@ -110,7 +110,7 @@ impl HtmxBuild {
let container =
match &container_image {
SlimImage::Debian { image, deps, .. } => {
let _target = BuildTarget::from_target(&container_image);
let target = BuildTarget::from_target(&container_image);
let build_container = self
.build(
@ -200,6 +200,7 @@ impl HtmxBuild {
let base_debian = self
.client
.container_opts(dagger_sdk::QueryContainerOpts {
id: None,
platform: Some(target.into_platform()),
})
.from(image);
@ -218,7 +219,7 @@ impl HtmxBuild {
)
.with_directory(
"/mnt/app/target/site",
builder_image.directory("/mnt/src/target/site".to_string()),
builder_image.directory(format!("/mnt/src/target/site")),
)
.with_file(
"/mnt/app/Cargo.toml",
@ -249,6 +250,7 @@ impl HtmxBuild {
let base_debian = self
.client
.container_opts(dagger_sdk::QueryContainerOpts {
id: None,
platform: Some(target.into_platform()),
})
.from(image);

View File

@ -55,7 +55,7 @@ impl LeptosBuild {
.client
.cache_volume(format!("rust_leptos_{}", profile.to_string()));
let build_options = vec!["cargo", "leptos", "build", "--release", "-vv"];
let mut build_options = vec!["cargo", "leptos", "build", "--release", "-vv"];
let rust_prebuild = rust_build_image
.with_workdir("/mnt/src")
@ -95,7 +95,7 @@ impl LeptosBuild {
for container_image in images {
let container = match &container_image {
SlimImage::Debian { image, deps, .. } => {
let _target = BuildTarget::from_target(&container_image);
let target = BuildTarget::from_target(&container_image);
let build_container = self
.build(
@ -107,13 +107,9 @@ impl LeptosBuild {
)
.await?;
let binary_build = build_container.with_exec(vec![
"cargo",
"leptos",
"build",
"--release",
"-vv",
]);
let binary_build =
build_container
.with_exec(vec!["cargo", "leptos", "build", "--release", "-vv"]);
self.build_debian_image(
binary_build,
@ -150,11 +146,7 @@ impl LeptosBuild {
"-p",
bin_name,
])
.file(format!(
"target/{}/release/{}",
target.to_string(),
bin_name
));
.file(format!("target/{}/release/{}", target.to_string(), bin_name));
self.build_alpine_image(
bin,
@ -187,6 +179,7 @@ impl LeptosBuild {
let base_debian = self
.client
.container_opts(dagger_sdk::QueryContainerOpts {
id: None,
platform: Some(target.into_platform()),
})
.from(image);
@ -205,7 +198,7 @@ impl LeptosBuild {
)
.with_directory(
"/mnt/app/target/site",
builder_image.directory("/mnt/src/target/site".to_string()),
builder_image.directory(format!("/mnt/src/target/site")),
)
.with_file(
"/mnt/app/Cargo.toml",
@ -236,6 +229,7 @@ impl LeptosBuild {
let base_debian = self
.client
.container_opts(dagger_sdk::QueryContainerOpts {
id: None,
platform: Some(target.into_platform()),
})
.from(image);

View File

@ -1,4 +1,7 @@
use std::path::{Path, PathBuf};
use std::{
path::{Path, PathBuf},
sync::Arc,
};
use eyre::Context;
@ -89,7 +92,6 @@ impl RustSource {
let mut excludes = self.exclude.clone();
excludes.push("**/src".to_string());
excludes.push("**/tests".to_string());
let directory = self.client.host().directory_opts(
source.display().to_string(),
@ -117,10 +119,7 @@ impl RustSource {
.map(|c| format!("**/*{}*", c.replace('-', "_")))
.collect::<Vec<_>>();
let mut original_crates = crates.clone();
original_crates.extend(exclude);
let exclude = original_crates.iter().map(|c| c.as_str()).collect();
let exclude = exclude.iter().map(|c| c.as_str()).collect();
let incremental_dir = self.client.directory().with_directory_opts(
".",
@ -131,7 +130,7 @@ impl RustSource {
},
);
Ok(incremental_dir)
return Ok(incremental_dir);
}
pub async fn get_rust_skeleton_files(
@ -192,7 +191,7 @@ impl RustSource {
}
directory = create_skeleton_files(
directory,
rust_crate.strip_prefix(source_path).unwrap_or(rust_crate),
rust_crate.strip_prefix(source_path).unwrap_or(&rust_crate),
)?;
}

View File

@ -1,4 +1,4 @@
use std::{path::PathBuf};
use std::{path::PathBuf, sync::Arc};
use crate::{build::RustVersion, source::RustSource};
@ -40,7 +40,7 @@ impl RustTest {
.with_exec(vec!["apt", "update"])
.with_exec(deps);
let target_cache = self.client.cache_volume("rust_target_test".to_string());
let target_cache = self.client.cache_volume(format!("rust_target_test",));
let build_options = vec!["cargo", "build", "--workspace"];
let rust_prebuild = rust_build_image

View File

@ -15,8 +15,6 @@ please:
branch: main
settings:
api_url: https://git.front.kjuulh.io
actions:
rust:
scripts:
"ci:main":

View File

@ -2,21 +2,18 @@ use dagger_cuddle_please::{models::CuddlePleaseSrcArgs, DaggerCuddlePleaseAction
#[tokio::main]
pub async fn main() -> eyre::Result<()> {
dagger_sdk::connect(|client| async move {
DaggerCuddlePleaseAction::dagger(client.clone())
.execute_src(&CuddlePleaseSrcArgs {
cuddle_image: "kasperhermansen/cuddle-please:main-1691504183".into(),
server: dagger_cuddle_please::models::SrcServer::Gitea {
token: std::env::var("CUDDLE_PLEASE_TOKEN")
.expect("CUDDLE_PLEASE_TOKEN to be present"),
},
log_level: Some(dagger_cuddle_please::models::LogLevel::Debug),
})
.await?;
let client = dagger_sdk::connect().await?;
Ok(())
})
.await?;
DaggerCuddlePleaseAction::dagger(client.clone())
.execute_src(&CuddlePleaseSrcArgs {
cuddle_image: "kasperhermansen/cuddle-please:main-1691504183".into(),
server: dagger_cuddle_please::models::SrcServer::Gitea {
token: std::env::var("CUDDLE_PLEASE_TOKEN")
.expect("CUDDLE_PLEASE_TOKEN to be present"),
},
log_level: Some(dagger_cuddle_please::models::LogLevel::Debug),
})
.await?;
Ok(())
}

View File

@ -2,27 +2,25 @@ use dagger_cuddle_please::{models::CuddlePleaseArgs, DaggerCuddlePleaseAction};
#[tokio::main]
pub async fn main() -> eyre::Result<()> {
dagger_sdk::connect(|client| async move {
DaggerCuddlePleaseAction::dagger(client)
.execute(&CuddlePleaseArgs {
repository: "dagger-components".into(),
owner: "kjuulh".into(),
branch: "main".into(),
cuddle_image: "kasperhermansen/cuddle-please:latest".into(),
server: dagger_cuddle_please::models::Server::Gitea {
url: "https://git.front.kjuulh.io".into(),
user: "git".into(),
token: std::env::var("CUDDLE_PLEASE_TOKEN")
.expect("CUDDLE_PLEASE_TOKEN to be present"),
insecure: None,
},
log_level: None,
use_ssh_socket: false,
})
.await?;
let client = dagger_sdk::connect().await?;
DaggerCuddlePleaseAction::dagger(client)
.execute(&CuddlePleaseArgs {
repository: "dagger-components".into(),
owner: "kjuulh".into(),
branch: "main".into(),
cuddle_image: "kasperhermansen/cuddle-please:latest".into(),
server: dagger_cuddle_please::models::Server::Gitea {
url: "https://git.front.kjuulh.io".into(),
user: "git".into(),
token: std::env::var("CUDDLE_PLEASE_TOKEN")
.expect("CUDDLE_PLEASE_TOKEN to be present"),
insecure: None,
},
log_level: None,
use_ssh_socket: false,
})
.await?;
Ok(())
})
.await?;
Ok(())
}

View File

@ -2,17 +2,15 @@ use std::path::PathBuf;
#[tokio::main]
pub async fn main() -> eyre::Result<()> {
dagger_sdk::connect(|client| async move {
let crates = ["some-crate"];
let dag = dagger_rust::source::RustSource::new(client.clone());
let (_src, _rust_src) = dag.get_rust_src(None::<PathBuf>, crates).await?;
let client = dagger_sdk::connect().await?;
let _full_src = dag
.get_rust_target_src(&PathBuf::from("."), client.container(), crates)
.await?;
let crates = ["some-crate"];
let dag = dagger_rust::source::RustSource::new(client.clone());
let (_src, _rust_src) = dag.get_rust_src(None::<PathBuf>, crates).await?;
let _full_src = dag
.get_rust_target_src(&PathBuf::from("."), client.container(), crates)
.await?;
Ok(())
})
.await?;
Ok(())
}

View File

@ -1,64 +1,62 @@
use dagger_rust::build::{RustVersion, SlimImage};
use dagger_rust::build::{BuildProfile, RustVersion, SlimImage};
#[tokio::main]
pub async fn main() -> eyre::Result<()> {
dagger_sdk::connect(|client| async move {
let rust_build = dagger_rust::leptos::LeptosBuild::new(client.clone());
let client = dagger_sdk::connect().await?;
let containers = rust_build
.build_release(
Some("testdata"),
RustVersion::Nightly,
&["crates/*"],
&[
"openssl",
"libssl-dev",
"pkg-config",
"musl-tools",
"ca-certificates",
let rust_build = dagger_rust::leptos::LeptosBuild::new(client.clone());
let containers = rust_build
.build_release(
Some("testdata"),
RustVersion::Nightly,
&["crates/*"],
&[
"openssl",
"libssl-dev",
"pkg-config",
"musl-tools",
"ca-certificates",
],
vec![SlimImage::Debian {
image: "debian:bullseye".into(),
deps: vec![
"openssl".into(),
"libssl-dev".into(),
"pkg-config".into(),
"musl-tools".into(),
"ca-certificates".into(),
],
vec![SlimImage::Debian {
image: "debian:bullseye".into(),
deps: vec![
"openssl".into(),
"libssl-dev".into(),
"pkg-config".into(),
"musl-tools".into(),
"ca-certificates".into(),
],
architecture: dagger_rust::build::BuildArchitecture::Amd64,
}],
"hackernews_axum",
)
.await?;
architecture: dagger_rust::build::BuildArchitecture::Amd64,
}],
"hackernews_axum",
)
.await?;
let container = containers.first().unwrap();
let container = containers.first().unwrap();
container.directory("/mnt/app").export("output").await?;
container.directory("/mnt/app").export("output").await?;
let tunnel = client.host().tunnel(
container
.with_env_variable("LEPTOS_SITE_ADDR", "0.0.0.0:8080")
.with_exec(vec!["/mnt/app/hackernews_axum"])
.as_service(),
);
let tunnel = client.host().tunnel(
container
.with_env_variable("LEPTOS_SITE_ADDR", "0.0.0.0:8080")
.with_exec(vec!["/mnt/app/hackernews_axum"])
.as_service(),
);
tunnel.start().await?;
tunnel.start().await?;
let endpoint = tunnel
.endpoint_opts(
dagger_sdk::ServiceEndpointOptsBuilder::default()
.scheme("http")
.build()?,
)
.await?;
let endpoint = tunnel
.endpoint_opts(
dagger_sdk::ServiceEndpointOptsBuilder::default()
.scheme("http")
.build()?,
)
.await?;
println!("running on: {endpoint}, press enter to stop");
println!("running on: {endpoint}, press enter to stop");
std::io::stdin().read_line(&mut String::new()).unwrap();
std::io::stdin().read_line(&mut String::new()).unwrap();
Ok(())
})
.await?;
Ok(())
}

View File

@ -2,30 +2,28 @@ use dagger_rust::build::{RustVersion, SlimImage};
#[tokio::main]
pub async fn main() -> eyre::Result<()> {
dagger_sdk::connect(|client| async move {
let rust_build = dagger_rust::build::RustBuild::new(client.clone());
let client = dagger_sdk::connect().await?;
let containers = rust_build
.build_release(
Some("testdata"),
RustVersion::Nightly,
&["crates/*"],
&["openssl"],
vec![SlimImage::Debian {
image: "debian:bookworm".into(),
deps: vec!["openssl".into()],
architecture: dagger_rust::build::BuildArchitecture::Amd64,
}],
"example_bin",
)
.await?;
let rust_build = dagger_rust::build::RustBuild::new(client.clone());
for container in containers {
container.sync().await?;
}
let containers = rust_build
.build_release(
Some("testdata"),
RustVersion::Nightly,
&["crates/*"],
&["openssl"],
vec![SlimImage::Debian {
image: "debian:bookworm".into(),
deps: vec!["openssl".into()],
architecture: dagger_rust::build::BuildArchitecture::Amd64,
}],
"example_bin",
)
.await?;
for container in containers {
container.sync().await?;
}
Ok(())
})
.await?;
Ok(())
}

View File

@ -2,17 +2,15 @@ use std::path::PathBuf;
#[tokio::main]
pub async fn main() -> eyre::Result<()> {
dagger_sdk::connect(|client| async move {
let crates = ["some-crate"];
let dag = dagger_rust::source::RustSource::new(client.clone());
let (_src, _rust_src) = dag.get_rust_src(None::<PathBuf>, crates).await?;
let client = dagger_sdk::connect().await?;
let _full_src = dag
.get_rust_target_src(&PathBuf::from("."), client.container(), crates)
.await?;
let crates = ["some-crate"];
let dag = dagger_rust::source::RustSource::new(client.clone());
let (_src, _rust_src) = dag.get_rust_src(None::<PathBuf>, crates).await?;
let _full_src = dag
.get_rust_target_src(&PathBuf::from("."), client.container(), crates)
.await?;
Ok(())
})
.await?;
Ok(())
}

View File

@ -2,19 +2,15 @@ use dagger_rust::{build::RustVersion, test::RustTest};
#[tokio::main]
pub async fn main() -> eyre::Result<()> {
dagger_sdk::connect(|client| async move {
RustTest::new(client.clone())
.test(
Some("testdata"),
RustVersion::Nightly,
&["crates/*"],
&["openssl"],
)
.await?;
Ok(())
})
.await?;
let client = dagger_sdk::connect().await?;
RustTest::new(client.clone())
.test(
Some("testdata"),
RustVersion::Nightly,
&["crates/*"],
&["openssl"],
)
.await?;
Ok(())
}