Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provider assets #159

Merged
merged 10 commits into from
May 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/nixpacks/app.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use anyhow::anyhow;
use std::collections::HashMap;
use std::path::Path;
use std::{env, fs, path::PathBuf};

Expand All @@ -8,6 +9,9 @@ use regex::Regex;
use serde::de::DeserializeOwned;
use walkdir::WalkDir;

pub static ASSETS_DIR: &str = "/assets/";
pub type StaticAssets = HashMap<String, String>;

#[derive(Debug, Clone)]
pub struct App {
pub source: PathBuf,
Expand Down Expand Up @@ -136,6 +140,11 @@ impl App {
// Convert path to PathBuf
Ok(stripped.to_owned())
}

/// Get the path in the container to an asset defined in `static_assets`.
pub fn asset_path(&self, name: &str) -> String {
format!("{ASSETS_DIR}{name}")
}
}

#[cfg(test)]
Expand Down Expand Up @@ -251,4 +260,11 @@ mod tests {
);
Ok(())
}

#[test]
fn test_static_asset_path() -> Result<()> {
let app = App::new("./examples/npm")?;
assert_eq!(&app.asset_path("hi.txt"), "/assets/hi.txt");
Ok(())
}
}
80 changes: 74 additions & 6 deletions src/nixpacks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub mod plan;
use crate::providers::Provider;

use self::{
app::App,
app::{App, StaticAssets},
environment::{Environment, EnvironmentVariables},
logger::Logger,
nix::Pkg,
Expand Down Expand Up @@ -100,6 +100,9 @@ impl<'a> AppBuilder<'a> {
let build_phase = self.get_build_phase().context("Generating build phase")?;
let start_phase = self.get_start_phase().context("Generating start phase")?;
let variables = self.get_variables().context("Getting plan variables")?;
let static_assets = self
.get_static_assets()
.context("Getting provider assets")?;

let plan = BuildPlan {
version: Some(NIX_PACKS_VERSION.to_string()),
Expand All @@ -108,6 +111,7 @@ impl<'a> AppBuilder<'a> {
build: Some(build_phase),
start: Some(start_phase),
variables: Some(variables),
static_assets: Some(static_assets),
};

Ok(plan)
Expand Down Expand Up @@ -340,6 +344,17 @@ impl<'a> AppBuilder<'a> {
Ok(new_variables)
}

fn get_static_assets(&self) -> Result<StaticAssets> {
let static_assets = match self.provider {
Some(provider) => provider
.static_assets(self.app, self.environment)?
.unwrap_or_default(),
None => StaticAssets::new(),
};

Ok(static_assets)
}

fn detect(&mut self, providers: Vec<&'a dyn Provider>) -> Result<()> {
for provider in providers {
let matches = provider.detect(self.app, self.environment)?;
Expand Down Expand Up @@ -381,6 +396,24 @@ impl<'a> AppBuilder<'a> {
File::create(dockerfile_path.clone()).context("Creating Dockerfile file")?;
fs::write(dockerfile_path, dockerfile).context("Writing Dockerfile")?;

if let Some(assets) = &plan.static_assets {
if !assets.is_empty() {
let static_assets_path = PathBuf::from(dest).join(PathBuf::from("assets"));
fs::create_dir_all(&static_assets_path).context("Creating static assets folder")?;

for (name, content) in assets {
let path = Path::new(&static_assets_path).join(name);
let parent = path.parent().unwrap();
fs::create_dir_all(parent)
.context(format!("Creating parent directory for {}", name))?;
let mut file =
File::create(path).context(format!("Creating asset file for {name}"))?;
file.write_all(content.as_bytes())
.context(format!("Writing asset {name}"))?;
}
}
}

Ok(())
}

Expand Down Expand Up @@ -441,12 +474,14 @@ impl<'a> AppBuilder<'a> {

pub fn gen_dockerfile(plan: &BuildPlan) -> Result<String> {
let app_dir = "/app/";
let assets_dir = app::ASSETS_DIR;

let setup_phase = plan.setup.clone().unwrap_or_default();
let install_phase = plan.install.clone().unwrap_or_default();
let build_phase = plan.build.clone().unwrap_or_default();
let start_phase = plan.start.clone().unwrap_or_default();
let variables = plan.variables.clone().unwrap_or_default();
let static_assets = plan.static_assets.clone().unwrap_or_default();

// -- Variables
let args_string = if !variables.is_empty() {
Expand Down Expand Up @@ -476,6 +511,18 @@ impl<'a> AppBuilder<'a> {
}
let setup_copy_cmd = format!("COPY {} {}", setup_files.join(" "), app_dir);

// -- Static Assets
let assets_copy_cmd = if !static_assets.is_empty() {
static_assets
.clone()
.into_keys()
.map(|name| format!("COPY assets/{} {}{}", name, assets_dir, name))
.collect::<Vec<String>>()
.join("\n")
} else {
"".to_string()
};

// -- Install
let install_cmd = install_phase
.cmd
Expand Down Expand Up @@ -533,7 +580,7 @@ impl<'a> AppBuilder<'a> {
",
run_image=run_image,
app_dir=app_dir,
copy_cmd=get_copy_from_command("0", &start_files.unwrap_or_default(), app_dir)
copy_cmd=get_copy_from_command("0", &start_files.unwrap_or_default(), app_dir, !static_assets.is_empty())
}
}
None => get_copy_command(
Expand All @@ -550,6 +597,7 @@ impl<'a> AppBuilder<'a> {

# Setup
{setup_copy_cmd}
{assets_copy_cmd}
RUN nix-env -if environment.nix

# Load environment variables
Expand Down Expand Up @@ -591,19 +639,39 @@ fn get_copy_command(files: &[String], app_dir: &str) -> String {
}
}

fn get_copy_from_command(from: &str, files: &[String], app_dir: &str) -> String {
fn get_copy_from_command(from: &str, files: &[String], app_dir: &str, has_assets: bool) -> String {
if files.is_empty() {
format!("COPY --from=0 {} {}", app_dir, app_dir)
format!(
"
COPY --from=0 {} {}
RUN true
{}
",
app_dir,
app_dir,
if has_assets {
"COPY --from=0 /assets /assets"
} else {
""
}
)
} else {
format!(
"COPY --from={} {} {}",
"COPY --from={} {} {}
RUN true
{}",
from,
files
.iter()
.map(|f| f.replace("./", app_dir))
.collect::<Vec<_>>()
.join(" "),
app_dir
app_dir,
if has_assets {
format!("COPY --from={} /assets /assets", from)
} else {
"".to_string()
}
)
}
}
2 changes: 2 additions & 0 deletions src/nixpacks/plan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize};
use super::{
environment::EnvironmentVariables,
phase::{BuildPhase, InstallPhase, SetupPhase, StartPhase},
StaticAssets,
};

#[serde_with::skip_serializing_none]
Expand All @@ -15,6 +16,7 @@ pub struct BuildPlan {
pub build: Option<BuildPhase>,
pub start: Option<StartPhase>,
pub variables: Option<EnvironmentVariables>,
pub static_assets: Option<StaticAssets>,
}

impl BuildPlan {
Expand Down
5 changes: 4 additions & 1 deletion src/providers/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::nixpacks::{
app::App,
app::{App, StaticAssets},
environment::{Environment, EnvironmentVariables},
phase::{BuildPhase, InstallPhase, SetupPhase, StartPhase},
};
Expand Down Expand Up @@ -28,6 +28,9 @@ pub trait Provider {
fn start(&self, _app: &App, _env: &Environment) -> Result<Option<StartPhase>> {
Ok(None)
}
fn static_assets(&self, _app: &App, _env: &Environment) -> Result<Option<StaticAssets>> {
Ok(None)
}
fn environment_variables(
&self,
_app: &App,
Expand Down