Skip to content
Open
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
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,17 @@ Example manual run of the generated project:
CF_CLI_CONFIG=/tmp/cf-demo/config/quickstart.yml cargo run --manifest-path /tmp/cf-demo/.cyberfabric/quickstart/Cargo.toml
```

### Deployment bundle generation

- `deploy --template docker` generates a Docker build bundle under `.cyberfabric/deploy/<name>/` using deploy assets
from `cf-template-rust`
- the generated bundle includes a Dockerfile, `config.yml`, the generated `.cyberfabric/<name>`
server project, and the workspace members needed by local path dependencies
- existing bundle directories under `.cyberfabric/deploy/` are replaced automatically, while existing custom
`--output-dir` paths require `--force`
- copied workspace paths skip common local-only entries such as `.git`, `.vscode`, `target`, `.env*`, and swap files;
symlinked entries are rejected

### Source inspection

- `docs` resolves Rust source for crates, modules, and items from the workspace, local cache, or `crates.io`
Expand Down
58 changes: 56 additions & 2 deletions SKILLS.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ cyberfabric
│ ├── add
│ ├── edit
│ └── rm
├── deploy
├── docs
├── lint
├── test
Expand All @@ -69,7 +70,10 @@ cyberfabric
there is no default. For `build` and `run`, the CLI forwards this path to the generated server through the
`CF_CLI_CONFIG` environment variable.
- **[`--name <NAME>`]** For `build` and `run`, overrides the generated server project and binary name that would
otherwise default to the config filename stem.
otherwise default to the config filename stem. `deploy` uses the same name resolution for the generated executable and
output bundle.
- **[`--template <KIND>`]** For `deploy`, selects which deployment asset set to render. Only `docker` is currently
implemented.
- **[`-v, --verbose`]** Usually enables more logging or richer output.
- **[name validation]** Config-managed names for modules, DB servers, and generated server names only allow letters,
numbers, `-`, and `_`.
Expand All @@ -82,6 +86,7 @@ From the current implementation, the CLI is mainly for:
- **[config management]** Enable modules and patch YAML config sections
- **[server generation]** Generate a runnable Cargo project under `.cyberfabric/<name>/`
- **[build/run]** Build or run that generated server
- **[deploy bundle generation]** Generate a Docker build bundle under `.cyberfabric/deploy/<name>/`
- **[source inspection]** Resolve Rust source for crates/items through workspace metadata or crates.io
- **[tool bootstrap]** Install or upgrade `rustup`, `cargofmt`, and `clippy`

Expand Down Expand Up @@ -652,8 +657,55 @@ cyberfabric build -p /tmp/cf-demo -c /tmp/cf-demo/config/quickstart.yml --releas
cyberfabric build -p /tmp/cf-demo -c /tmp/cf-demo/config/quickstart.yml --otel --clean
```

### `deploy`

Generate a Docker deployment bundle under `.cyberfabric/deploy/<name>/`.

Synopsis:

```bash
cyberfabric deploy --template docker -c <CONFIG> [-p <PATH>] [--name <NAME>] [--output-dir <PATH>] [--force] [--image-name <NAME>] [--image-tag <TAG>] [--local-path <PATH>] [--git <URL>] [--subfolder <NAME>] [--branch <NAME>]
```

Arguments:

- **[`--template docker`]** Render the Docker deployment bundle template
- **[`-c, --config <CONFIG>`]** Required config file path
- **[`-p, --path <PATH>`]** Workspace root, defaults to `.`
- **[`--name <NAME>`]** Override the generated server project and executable name; defaults to the config filename stem
- **[`--output-dir <PATH>`]** Override the deploy bundle output directory; defaults to `.cyberfabric/deploy/<name>/`
- **[`--force`]** Allow replacing an existing custom output directory outside `.cyberfabric/deploy/`
- **[`--image-name <NAME>`]** Optional image name used in the generated helper command
- **[`--image-tag <TAG>`]** Optional image tag used in the generated helper command; defaults to `latest`
- **[`--local-path <PATH>`]** Use a local deploy template repository instead of Git
- **[`--git <URL>`]** Deploy template repo URL, defaults to `https://github.com/cyberfabric/cf-template-rust`
- **[`--subfolder <NAME>`]** Template repo subfolder root, defaults to `Deploy`
- **[`--branch <NAME>`]** Template repo branch, defaults to `main`

Behavior:

- **[reuses name resolution]** Uses the config filename stem by default, so `config/quickstart.yml` generates under `.cyberfabric/deploy/quickstart/`; `--name` overrides that default
- **[recreates generated server structure]** Writes a generated server project under `.cyberfabric/deploy/<name>/.cyberfabric/<name>/` configured to load `/srv/config.yml` at runtime
- **[copies workspace inputs]** Copies the workspace manifest, optional `Cargo.lock`, local workspace members, and dependency paths needed for Docker compilation
- **[filters common junk entries]** Skips common local-only files and folders such as `.git`, `.vscode`, `target`, `.env*`, swap files, and Finder metadata when copying workspace paths into the bundle
- **[rejects symlinked bundle inputs]** Fails fast if a copied workspace path contains a symlinked entry
- **[copies config as `config.yml`]** Places the selected config file in the deploy bundle root as `config.yml`
- **[renders from deploy templates]** Loads `Deploy/docker` templates from `cf-template-rust` and renders the `Dockerfile`
- **[protects custom output directories]** Replaces existing bundle directories under `.cyberfabric/deploy/` automatically, but requires `--force` before deleting an existing custom `--output-dir`
- **[generate-only]** Does not run `docker build`, push to a registry, or deploy to a cluster

Examples:

```bash
cyberfabric deploy --template docker -p /tmp/cf-demo -c /tmp/cf-demo/config/quickstart.yml
```

```bash
cyberfabric deploy --template docker -p /tmp/cf-demo -c /tmp/cf-demo/config/quickstart.yml --name demo-server
```

```bash
cyberfabric build -p /tmp/cf-demo -c /tmp/cf-demo/config/quickstart.yml --name demo-server
cyberfabric deploy --template docker -p /tmp/cf-demo -c /tmp/cf-demo/config/quickstart.yml --local-path ~/dev/cf-template-rust
```

### `lint`
Expand Down Expand Up @@ -741,6 +793,8 @@ cyberfabric docs --verbose tokio::sync
cyberfabric init <path>
cyberfabric mod add <background-worker|api-db-handler|rest-gateway> [-p <workspace>]

cyberfabric deploy --template docker [-p <workspace>] -c <config>

cyberfabric config mod list [-p <workspace>] -c <config>
cyberfabric config mod add <module> [-p <workspace>] -c <config>
cyberfabric config mod rm <module> [-p <workspace>] -c <config>
Expand Down
49 changes: 40 additions & 9 deletions crates/cli/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,17 @@ impl Display for Registry {
}
}

pub fn resolve_workspace_config_and_name(
path_config: &PathConfigArgs,
override_name: Option<&str>,
) -> anyhow::Result<(PathBuf, PathBuf, String)> {
let path = workspace_root()?;
let config_path = path_config.resolve_config()?;
let project_name = resolve_generated_project_name(&config_path, override_name)?;

Ok((path, config_path, project_name))
}

impl BuildRunArgs {
pub fn resolve_config_and_name(&self) -> anyhow::Result<(PathBuf, String)> {
let config_path = self.path_config.resolve_config()?;
Expand Down Expand Up @@ -241,6 +252,15 @@ fn create_required_deps() -> anyhow::Result<CargoTomlDependencies> {
pub fn generate_server_structure(
project_name: &str,
current_dependencies: &CargoTomlDependencies,
) -> anyhow::Result<()> {
let output_root = workspace_root()?;
generate_server_structure_at(&output_root, project_name, current_dependencies)
}

pub fn generate_server_structure_at(
output_root: &Path,
project_name: &str,
current_dependencies: &CargoTomlDependencies,
) -> anyhow::Result<()> {
let mut dependencies = current_dependencies.clone();
dependencies.extend(create_required_deps()?);
Expand All @@ -257,9 +277,14 @@ pub fn generate_server_structure(
toml::to_string(&cargo_toml).context("something went wrong when transforming to toml")?;
let main_rs = prepare_cargo_server_main(current_dependencies);

create_file_structure(project_name, "Cargo.toml", &cargo_toml_str)?;
create_file_structure(project_name, ".cargo/config.toml", CARGO_CONFIG_TOML)?;
create_file_structure(project_name, "src/main.rs", &main_rs)?;
create_file_structure_at(output_root, project_name, "Cargo.toml", &cargo_toml_str)?;
create_file_structure_at(
output_root,
project_name,
".cargo/config.toml",
CARGO_CONFIG_TOML,
)?;
create_file_structure_at(output_root, project_name, "src/main.rs", &main_rs)?;

Ok(())
}
Expand All @@ -268,13 +293,17 @@ pub fn generated_project_dir(project_name: &str) -> anyhow::Result<PathBuf> {
Ok(workspace_root()?.join(BASE_PATH).join(project_name))
}

fn create_file_structure(
fn create_file_structure_at(
output_root: &Path,
project_name: &str,
relative_path: &str,
contents: &str,
) -> anyhow::Result<()> {
use std::io::Write;
let path = generated_project_dir(project_name)?.join(relative_path);
let path = output_root
.join(BASE_PATH)
.join(project_name)
.join(relative_path);
fs::create_dir_all(
path.parent().context(
"this should be unreachable, the parent for the file structure always exists",
Expand Down Expand Up @@ -337,7 +366,9 @@ fn prepare_cargo_server_main(dependencies: &CargoTomlDependencies) -> String {
#[cfg(test)]
mod tests {
use super::{merge_module_metadata, prepare_cargo_server_main, resolve_generated_project_name};
use module_parser::{Capability, CargoTomlDependencies, ConfigModuleMetadata};
use module_parser::{
Capability, CargoTomlDependencies, CargoTomlDependency, ConfigModuleMetadata,
};
use std::path::Path;

#[test]
Expand Down Expand Up @@ -390,9 +421,9 @@ mod tests {
#[test]
fn generated_server_main_reads_config_from_env_and_includes_dependencies() {
let dependencies = CargoTomlDependencies::from([
("module_a".to_owned(), Default::default()),
("module_b".to_owned(), Default::default()),
("api-db-handler".to_owned(), Default::default()),
("module_a".to_owned(), CargoTomlDependency::default()),
("module_b".to_owned(), CargoTomlDependency::default()),
("api-db-handler".to_owned(), CargoTomlDependency::default()),
]);

let main_rs = prepare_cargo_server_main(&dependencies);
Expand Down
Loading