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

feat: allow deb, config, and container generation #10

Merged
merged 3 commits into from
Oct 2, 2024
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
*.profraw
.idea
.vscode
.DS_Store
2 changes: 1 addition & 1 deletion cargo-prosa/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "cargo-prosa"
version = "0.1.1"
version = "0.1.2"
authors.workspace = true
description = "ProSA utility to package and deliver a builded ProSA"
homepage.workspace = true
Expand Down
34 changes: 32 additions & 2 deletions cargo-prosa/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ cargo prosa new my-prosa
cargo prosa init
```

cargo-prosa is meant to evolve in the future.
_cargo-prosa_ is meant to evolve in the future.
So maybe new things will be introduced.
To update your model, you can update the generated file with `cargo prosa update`.

Expand All @@ -47,8 +47,9 @@ Your project uses a _build.rs_/_main.rs_ to create a binary that you can use.
## Configuration

Keep in mind that you also need to have a settings file.
A `target/config.yml` and `target/config.toml` will be generated when building.

You can initiate a default one with:
But you can initiate a default one with:
```bash
cargo run -- -c default_config.yaml --dry_run
```
Expand All @@ -69,3 +70,32 @@ cargo run -- -n "MyBuiltProSA" -c default_config.yaml
# or with binary
target/debug/my-prosa -n "MyBuiltProSA" -c default_config.yaml
```

## Deploy

This builder offer you several possibilities to deploy your ProSA.
The goal is to use the easiest method of a plateform to run your application.

### Container

Containerization will allow you to build and load ProSA in an image:
```bash
# Generate a Containerfile
cargo prosa container
# Generate a Dockerfile
cargo prosa container --docker
```

For your own needs, you can:
- Select from which image the container need to be build `--image debian:stable-slim`
- Along that you may have to specify the package manager use to install mandatory packages `--package_manager apt`
- If you want to compile ProSA through a builder, you can specify it with `--builder rust:latest`. A multi stage container file will be created.

### Deb package

Deb package can be created with the [cargo-deb](https://crates.io/crates/cargo-deb) crate.

To enable this feature, _create_, _init_ or _update_ your ProSA with the option `--deb`.
It'll add every needed properties to generate a deb package.

The deb package will include the released binary, a default configuration file, and a systemd service file.
86 changes: 82 additions & 4 deletions cargo-prosa/assets/build.rs.j2
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
use std::collections::HashMap;
use std::io::Write;
use std::{{ '{' }}env, io{{ '}' }};
use std::{{ '{' }}env, io, path{{ '}' }};
use std::ffi::OsString;
use std::fs;
use std::path::Path;
use std::{{ '{' }}fs, path::Path{{ '}' }};

use cargo_prosa::builder::Desc;
use cargo_prosa::cargo::{{ '{' }}CargoMetadata, Metadata{{ '}' }};
use cargo_prosa::CONFIGURATION_FILENAME;
{%- if deb_pkg %}
use cargo_prosa::package::deb::DebPkg;
{% endif %}

fn write_settings_rs(out_dir: &OsString, desc: &Desc, metadata: &HashMap<String, Metadata>) -> io::Result<()> {{ '{' }}
let mut f = fs::File::create(Path::new(&out_dir).join("settings.rs"))?;
Expand Down Expand Up @@ -114,7 +116,7 @@ fn write_run_rs(out_dir: &OsString, desc: &Desc, metadata: &HashMap<String, Meta
proc_id += 1;
writeln!(f, "debug!(\"Start processor {{ '{}' }}\");", processor.get_name())?;
let proc_metadata = metadata.get(&processor.proc_name).unwrap_or_else(|| panic!("Can't get the processor {{ '{}' }} metadata ({{ '{:?}' }})", processor.proc, processor.name));
if let Some(_) = &proc_metadata.settings {{ '{' }}
if proc_metadata.settings.is_some() {{ '{' }}
writeln!(f, "let proc = {{ '{}' }}::<{{ '{}' }}>::create({{ '{}' }}, bus.clone(), settings.{{ '{}' }}.clone());", processor.proc, desc.prosa.tvf, proc_id, processor.get_name().replace('-', "_"))?;
{{ '}' }} else {{ '{' }}
writeln!(f, "let proc = {{ '{}' }}::<{{ '{}' }}>::create_raw({{ '{}' }}, bus.clone());", processor.proc, desc.prosa.tvf, proc_id)?;
Expand Down Expand Up @@ -176,8 +178,73 @@ fn write_run_rs(out_dir: &OsString, desc: &Desc, metadata: &HashMap<String, Meta
writeln!(f, "{{ '}}' }}")
{{ '}' }}

fn write_target_config(out_dir: &OsString, target_dir: &Path) -> io::Result<()> {{ '{' }}
// Create temporary project to generate config file
let prosa_config_path = Path::new(&out_dir).join("prosa_config");
fs::create_dir_all(prosa_config_path.join("src"))?;
fs::copy(Path::new(&out_dir).join("settings.rs"), prosa_config_path.join("src").join("settings.rs"))?;

// Correct relative path in the Cargo.toml by the `out_dir` one
let cargo_content = fs::read_to_string("Cargo.toml")?.replace("\"../", format!("\"{}/../", env::current_dir()?.display()).as_str());
let mut cargo_dst = fs::File::create(&prosa_config_path.join("Cargo.toml"))?;
cargo_dst.write(cargo_content.as_bytes())?;

let mut f = fs::File::create(prosa_config_path.join("src").join("main.rs"))?;
writeln!(f, "use prosa::core::settings::Settings;\n")?;
writeln!(f, "use serde::{{ '{{' }}Deserialize, Serialize{{ '}}' }};\n")?;
writeln!(f, "include!(\"settings.rs\");\n")?;
writeln!(f, "fn main() -> std::io::Result<()> {{ '{{' }}")?;
writeln!(f, " let args: Vec<String> = std::env::args().collect();")?;
writeln!(f, " RunSettings::default().write_config(args.last().expect(\"Missing config path\"))")?;
writeln!(f, "{{ '}}' }}")?;

let config_build_yml = std::process::Command::new("cargo")
.args(["run", "--", target_dir.join("config.yml").to_str().unwrap()])
.current_dir(&prosa_config_path)
.output()
.expect("Failed to generate config.yml");
if !config_build_yml.status.success() {{ '{' }}
return Err(io::Error::new(
io::ErrorKind::Other,
std::str::from_utf8(config_build_yml.stderr.as_slice()).unwrap_or(
format!(
"Can't build config.yml config file {:?}",
config_build_yml.status.code()
)
.as_str(),
),
));
{{ '}' }}

let config_build_toml = std::process::Command::new("cargo")
.args([
"run",
"--",
target_dir.join("config.toml").to_str().unwrap(),
])
.current_dir(&prosa_config_path)
.output()
.expect("Failed to generate config.toml");
if !config_build_toml.status.success() {{ '{' }}
return Err(io::Error::new(
io::ErrorKind::Other,
std::str::from_utf8(config_build_toml.stderr.as_slice()).unwrap_or(
format!(
"Can't build config.toml config file {{ '{' }}:?{{ '}' }}",
config_build_toml.status.code()
)
.as_str(),
),
));
{{ '}' }}

Ok(())
{{ '}' }}

fn main() {{ '{' }}
// Generate Rust code for ProSA
let out_dir = env::var_os("OUT_DIR").unwrap();
let target_path = path::absolute("target").unwrap();
let cargo_metadata = CargoMetadata::load_metadata().unwrap();
let prosa_proc_metadata = cargo_metadata.prosa_proc_metadata();
let prosa_desc = toml::from_str::<Desc>(fs::read_to_string(CONFIGURATION_FILENAME).unwrap().as_str()).unwrap();
Expand All @@ -186,6 +253,17 @@ fn main() {{ '{' }}
write_config_rs(&out_dir, &prosa_desc, &cargo_metadata).unwrap();
write_run_rs(&out_dir, &prosa_desc, &prosa_proc_metadata).unwrap();

write_target_config(&out_dir, &target_path).unwrap();

println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=ProSA.toml");
{%- if deb_pkg %}
println!("cargo:rerun-if-changed=Cargo.toml");

// Generate files for ProSA packages
{% endif -%}
{%- if deb_pkg %}
let deb_pkg = DebPkg::new(target_path.to_path_buf()).unwrap();
deb_pkg.write_package_data().unwrap();
{% endif -%}
{{ '}' }}
50 changes: 50 additions & 0 deletions cargo-prosa/assets/container.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{% if builder_image is defined -%}
FROM {{ builder_image }} AS builder
WORKDIR /opt
COPY . .

RUN mkdir -p ~/.ssh \
&& touch ~/.ssh/config \
&& echo "Host *\n StrictHostKeyChecking=accept-new" >> ~/.ssh/config \
&& chmod 644 ~/.ssh/config

{% if package_manager == "apt" -%}
RUN export DEBIAN_FRONTEND=noninteractive \
&& apt-get -y update && apt-get -y install \
libssl-dev
{% endif -%}

{% if docker -%}
RUN --mount=type=ssh export CARGO_NET_GIT_FETCH_WITH_CLI=true \
&& cargo build -r
{% else %}
RUN cargo build -r
{% endif %}
{% endif -%}
FROM {{ image }}

LABEL org.opencontainers.image.title="{{ name }}"
LABEL org.opencontainers.image.version="{{ version }}"
{% if license is defined %}LABEL org.opencontainers.image.licenses="{{ license }}"{% endif %}
{% if authors is defined %}LABEL org.opencontainers.image.authors="{{ authors | join(sep=", ") }}"{% endif %}
{% if description is defined %}LABEL org.opencontainers.image.description="{{ description }}"{% endif %}
{% if documentation is defined %}LABEL org.opencontainers.image.documentation="{{ documentation }}"{% endif %}

{% if builder_image is defined -%}
COPY --from=builder --chmod=755 /opt/target/release/{{ name }} /usr/local/bin/{{ name }}
{% else %}
COPY --chmod=755 target/release/{{ name }} /usr/local/bin/{{ name }}
{% endif -%}

{% if package_manager == "apt" %}
RUN export DEBIAN_FRONTEND=noninteractive \
&& apt-get -y update && apt-get -y install \
libssl3 \
&& apt-get -y clean && apt-get -y autoclean \
&& rm -rf /tmp/* \
&& /usr/local/bin/{{ name }} -c /etc/{{ name }}.yml --dry_run
{% else %}
RUN /usr/local/bin/{{ name }} -c /etc/{{ name }}.yml --dry_run
{% endif %}

ENTRYPOINT ["/usr/local/bin/{{ name }}", "-c", "/etc/{{ name }}.yml"]
13 changes: 13 additions & 0 deletions cargo-prosa/assets/systemd.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[Unit]
{% if description is defined %}Description={{ description }}{% endif %}
{% if documentation is defined %}Documentation={{ documentation }}{% endif %}
After=network-online.target
ConditionFileNotEmpty={{ config }}

[Service]
ExecStart={{ bin }} -c {{ config }}
Restart=on-failure

[Install]
WantedBy=multi-user.target
Alias={{ name }}.service
Loading