docs: New README, two options to choose from right now (#59)
All checks were successful
Run Check Script / check (push) Successful in 1m52s
All checks were successful
Run Check Script / check (push) Successful in 1m52s
Reviewed-on: https://git.nationtech.io/NationTech/harmony/pulls/59 Co-authored-by: Jean-Gabriel Gill-Couture <jg@nationtech.io> Co-committed-by: Jean-Gabriel Gill-Couture <jg@nationtech.io>
This commit is contained in:
parent
b94dd1e595
commit
86c681be70
278
README.md
278
README.md
@ -1,171 +1,141 @@
|
|||||||
# Harmony : Open Infrastructure Orchestration
|
# Harmony
|
||||||
|
|
||||||
## Quick demo
|
[](https://git.nationtech.io/nationtech/harmony)
|
||||||
|
[](LICENSE)
|
||||||
|
|
||||||
`cargo run -p example-tui`
|
**Open-source infrastructure orchestration that treats your platform like first-class code.**
|
||||||
|
|
||||||
This will launch Harmony's minimalist terminal ui which embeds a few demo scores.
|
Harmony unifies project scaffolding, infrastructure provisioning, application deployment, and day-2 operations in **one strongly-typed Rust codebase**. From a developer laptop to a global production cluster, a single source of truth drives the full software lifecycle.
|
||||||
|
|
||||||
Usage instructions will be displayed at the bottom of the TUI.
|
---
|
||||||
|
|
||||||
`cargo run --bin example-cli -- --help`
|
## 1 · The Harmony Philosophy
|
||||||
|
|
||||||
This is the harmony CLI, a minimal implementation
|
Infrastructure is essential, but it shouldn’t be your core business. Harmony is built on three guiding principles that make modern platforms reliable, repeatable, and easy to reason about.
|
||||||
|
|
||||||
The current help text:
|
| Principle | What it means for you |
|
||||||
|
|-----------|-----------------------|
|
||||||
|
| **Infrastructure as Resilient Code** | Replace sprawling YAML and bash scripts with type-safe Rust. Test, refactor, and version your platform just like application code. |
|
||||||
|
| **Prove It Works — Before You Deploy** | Harmony uses the compiler to verify that your application’s needs match the target environment’s capabilities at **compile-time**, eliminating an entire class of runtime outages. |
|
||||||
|
| **One Unified Model** | Software and infrastructure are a single system. Harmony models them together, enabling deep automation—from bare-metal servers to Kubernetes workloads—with zero context switching. |
|
||||||
|
|
||||||
````
|
These principles surface as simple, ergonomic Rust APIs that let teams focus on their product while trusting the platform underneath.
|
||||||
Usage: example-cli [OPTIONS]
|
|
||||||
|
|
||||||
Options:
|
---
|
||||||
-y, --yes Run score(s) or not
|
|
||||||
-f, --filter <FILTER> Filter query
|
|
||||||
-i, --interactive Run interactive TUI or not
|
|
||||||
-a, --all Run all or nth, defaults to all
|
|
||||||
-n, --number <NUMBER> Run nth matching, zero indexed [default: 0]
|
|
||||||
-l, --list list scores, will also be affected by run filter
|
|
||||||
-h, --help Print help
|
|
||||||
-V, --version Print version```
|
|
||||||
|
|
||||||
## Core architecture
|
## 2 · Quick Start
|
||||||
|
|
||||||

|
The snippet below spins up a complete **production-grade LAMP stack** with monitoring. Swap it for your own scores to deploy anything from microservices to machine-learning pipelines.
|
||||||
````
|
|
||||||
## Supporting a new field in OPNSense `config.xml`
|
|
||||||
|
|
||||||
Two steps:
|
```rust
|
||||||
- Supporting the field in `opnsense-config-xml`
|
use harmony::{
|
||||||
- Enabling Harmony to control the field
|
data::Version,
|
||||||
|
inventory::Inventory,
|
||||||
We'll use the `filename` field in the `dhcpcd` section of the file as an example.
|
maestro::Maestro,
|
||||||
|
modules::{
|
||||||
### Supporting the field
|
lamp::{LAMPConfig, LAMPScore},
|
||||||
|
monitoring::monitoring_alerting::MonitoringAlertingStackScore,
|
||||||
As type checking if enforced, every field from `config.xml` must be known by the code. Each subsection of `config.xml` has its `.rs` file. For the `dhcpcd` section, we'll modify `opnsense-config-xml/src/data/dhcpd.rs`.
|
},
|
||||||
|
topology::{K8sAnywhereTopology, Url},
|
||||||
When a new field appears in the xml file, an error like this will be thrown and Harmony will panic :
|
|
||||||
```
|
|
||||||
Running `/home/stremblay/nt/dir/harmony/target/debug/example-nanodc`
|
|
||||||
Found unauthorized element filename
|
|
||||||
thread 'main' panicked at opnsense-config-xml/src/data/opnsense.rs:54:14:
|
|
||||||
OPNSense received invalid string, should be full XML: ()
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
Define the missing field (`filename`) in the `DhcpInterface` struct of `opnsense-config-xml/src/data/dhcpd.rs`:
|
|
||||||
```
|
|
||||||
pub struct DhcpInterface {
|
|
||||||
...
|
|
||||||
pub filename: Option<String>,
|
|
||||||
```
|
|
||||||
|
|
||||||
Harmony should now be fixed, build and run.
|
|
||||||
|
|
||||||
### Controlling the field
|
|
||||||
|
|
||||||
Define the `xml field setter` in `opnsense-config/src/modules/dhcpd.rs`.
|
|
||||||
```
|
|
||||||
impl<'a> DhcpConfig<'a> {
|
|
||||||
...
|
|
||||||
pub fn set_filename(&mut self, filename: &str) {
|
|
||||||
self.enable_netboot();
|
|
||||||
self.get_lan_dhcpd().filename = Some(filename.to_string());
|
|
||||||
}
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
Define the `value setter` in the `DhcpServer trait` in `domain/topology/network.rs`
|
|
||||||
```
|
|
||||||
#[async_trait]
|
|
||||||
pub trait DhcpServer: Send + Sync {
|
|
||||||
...
|
|
||||||
async fn set_filename(&self, filename: &str) -> Result<(), ExecutorError>;
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
Implement the `value setter` in each `DhcpServer` implementation.
|
|
||||||
`infra/opnsense/dhcp.rs`:
|
|
||||||
```
|
|
||||||
#[async_trait]
|
|
||||||
impl DhcpServer for OPNSenseFirewall {
|
|
||||||
...
|
|
||||||
async fn set_filename(&self, filename: &str) -> Result<(), ExecutorError> {
|
|
||||||
{
|
|
||||||
let mut writable_opnsense = self.opnsense_config.write().await;
|
|
||||||
writable_opnsense.dhcp().set_filename(filename);
|
|
||||||
debug!("OPNsense dhcp server set filename {filename}");
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
`domain/topology/ha_cluster.rs`
|
|
||||||
```
|
|
||||||
#[async_trait]
|
|
||||||
impl DhcpServer for DummyInfra {
|
|
||||||
...
|
|
||||||
async fn set_filename(&self, _filename: &str) -> Result<(), ExecutorError> {
|
|
||||||
unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA)
|
|
||||||
}
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
Add the new field to the DhcpScore in `modules/dhcp.rs`
|
|
||||||
```
|
|
||||||
pub struct DhcpScore {
|
|
||||||
...
|
|
||||||
pub filename: Option<String>,
|
|
||||||
```
|
|
||||||
|
|
||||||
Define it in its implementation in `modules/okd/dhcp.rs`
|
|
||||||
```
|
|
||||||
impl OKDDhcpScore {
|
|
||||||
...
|
|
||||||
Self {
|
|
||||||
dhcp_score: DhcpScore {
|
|
||||||
...
|
|
||||||
filename: Some("undionly.kpxe".to_string()),
|
|
||||||
```
|
|
||||||
|
|
||||||
Define it in its implementation in `modules/okd/bootstrap_dhcp.rs`
|
|
||||||
```
|
|
||||||
impl OKDDhcpScore {
|
|
||||||
...
|
|
||||||
Self {
|
|
||||||
dhcp_score: DhcpScore::new(
|
|
||||||
...
|
|
||||||
Some("undionly.kpxe".to_string()),
|
|
||||||
```
|
|
||||||
|
|
||||||
Update the interpret (function called by the `execute` fn of the interpret) so it now updates the `filename` field value in `modules/dhcp.rs`
|
|
||||||
```
|
|
||||||
impl DhcpInterpret {
|
|
||||||
...
|
|
||||||
let filename_outcome = match &self.score.filename {
|
|
||||||
Some(filename) => {
|
|
||||||
let dhcp_server = Arc::new(topology.dhcp_server.clone());
|
|
||||||
dhcp_server.set_filename(&filename).await?;
|
|
||||||
Outcome::new(
|
|
||||||
InterpretStatus::SUCCESS,
|
|
||||||
format!("Dhcp Interpret Set filename to {filename}"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
None => Outcome::noop(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if next_server_outcome.status == InterpretStatus::NOOP
|
#[tokio::main]
|
||||||
&& boot_filename_outcome.status == InterpretStatus::NOOP
|
async fn main() {
|
||||||
&& filename_outcome.status == InterpretStatus::NOOP
|
// 1. Describe what you want
|
||||||
|
let lamp_stack = LAMPScore {
|
||||||
|
name: "harmony-lamp-demo".into(),
|
||||||
|
domain: Url::Url(url::Url::parse("https://lampdemo.example.com").unwrap()),
|
||||||
|
php_version: Version::from("8.3.0").unwrap(),
|
||||||
|
config: LAMPConfig {
|
||||||
|
project_root: "./php".into(),
|
||||||
|
database_size: "4Gi".into(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
...
|
// 2. Pick where it should run
|
||||||
|
let mut maestro = Maestro::<K8sAnywhereTopology>::initialize(
|
||||||
Ok(Outcome::new(
|
Inventory::autoload(), // auto-detect hardware / kube-config
|
||||||
InterpretStatus::SUCCESS,
|
K8sAnywhereTopology::from_env(), // local k3d, CI, staging, prod…
|
||||||
format!(
|
|
||||||
"Dhcp Interpret Set next boot to [{:?}], boot_filename to [{:?}], filename to [{:?}]",
|
|
||||||
self.score.boot_filename, self.score.boot_filename, self.score.filename
|
|
||||||
)
|
)
|
||||||
...
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// 3. Enhance with extra scores (monitoring, CI/CD, …)
|
||||||
|
let mut monitoring = MonitoringAlertingStackScore::new();
|
||||||
|
monitoring.namespace = Some(lamp_stack.config.namespace.clone());
|
||||||
|
|
||||||
|
maestro.register_all(vec![Box::new(lamp_stack), Box::new(monitoring)]);
|
||||||
|
|
||||||
|
// 4. Launch an interactive CLI / TUI
|
||||||
|
harmony_cli::init(maestro, None).await.unwrap();
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Run it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo run
|
||||||
|
```
|
||||||
|
|
||||||
|
Harmony analyses the code, shows an execution plan in a TUI, and applies it once you confirm. Same code, same binary—every environment.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3 · Core Concepts
|
||||||
|
|
||||||
|
| Term | One-liner |
|
||||||
|
|------|-----------|
|
||||||
|
| **Score<T>** | Declarative description of the desired state (e.g., `LAMPScore`). |
|
||||||
|
| **Interpret<T>** | Imperative logic that realises a `Score` on a specific environment. |
|
||||||
|
| **Topology** | An environment (local k3d, AWS, bare-metal) exposing verified *Capabilities* (Kubernetes, DNS, …). |
|
||||||
|
| **Maestro** | Orchestrator that compiles Scores + Topology, ensuring all capabilities line up **at compile-time**. |
|
||||||
|
| **Inventory** | Optional catalogue of physical assets for bare-metal and edge deployments. |
|
||||||
|
|
||||||
|
A visual overview is in the diagram below.
|
||||||
|
|
||||||
|
[Harmony Core Architecture](docs/diagrams/Harmony_Core_Architecture.drawio.svg)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4 · Install
|
||||||
|
|
||||||
|
Prerequisites:
|
||||||
|
|
||||||
|
* Rust
|
||||||
|
* Docker (if you deploy locally)
|
||||||
|
* `kubectl` / `helm` for Kubernetes-based topologies
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://git.nationtech.io/nationtech/harmony
|
||||||
|
cd harmony
|
||||||
|
cargo build --release # builds the CLI, TUI and libraries
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5 · Learning More
|
||||||
|
|
||||||
|
* **Architectural Decision Records** – dive into the rationale
|
||||||
|
- [ADR-001 · Why Rust](adr/001-rust.md)
|
||||||
|
- [ADR-003 · Infrastructure Abstractions](adr/003-infrastructure-abstractions.md)
|
||||||
|
- [ADR-006 · Secret Management](adr/006-secret-management.md)
|
||||||
|
- [ADR-011 · Multi-Tenant Cluster](adr/011-multi-tenant-cluster.md)
|
||||||
|
|
||||||
|
* **Extending Harmony** – write new Scores / Interprets, add hardware like OPNsense firewalls, or embed Harmony in your own tooling (`/docs`).
|
||||||
|
|
||||||
|
* **Community** – discussions and roadmap live in [GitLab issues](https://git.nationtech.io/nationtech/harmony/-/issues). PRs, ideas, and feedback are welcome!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6 · License
|
||||||
|
|
||||||
|
Harmony is released under the **GNU AGPL v3**.
|
||||||
|
|
||||||
|
> We choose a strong copyleft license to ensure the project—and every improvement to it—remains open and benefits the entire community. Fork it, enhance it, even out-innovate us; just keep it open.
|
||||||
|
|
||||||
|
See [LICENSE](LICENSE) for the full text.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Made with ❤️ & 🦀 by the NationTech and the Harmony community*
|
||||||
|
1
docs/README.md
Normal file
1
docs/README.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
Not much here yet, see the `adr` folder for now. More to come in time!
|
13
docs/cyborg-metaphor.md
Normal file
13
docs/cyborg-metaphor.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
## Conceptual metaphor : The Cyborg and the Central Nervous System
|
||||||
|
|
||||||
|
At the heart of Harmony lies a core belief: in modern, decentralized systems, **software and infrastructure are not separate entities.** They are a single, symbiotic organism—a cyborg.
|
||||||
|
|
||||||
|
The software is the electronics, the "mind"; the infrastructure is the biological host, the "body". They live or die, thrive or sink together.
|
||||||
|
|
||||||
|
Traditional approaches attempt to manage this complex organism with fragmented tools: static YAML for configuration, brittle scripts for automation, and separate Infrastructure as Code (IaC) for provisioning. This creates a disjointed system that struggles to scale or heal itself, making it inadequate for the demands of fully automated, enterprise-grade clusters.
|
||||||
|
|
||||||
|
Harmony's goal is to provide the **central nervous system for this cyborg**. We aim to achieve the full automation of complex, decentralized clouds by managing this integrated entity holistically.
|
||||||
|
|
||||||
|
To achieve this, a tool must be both robust and powerful. It must manage the entire lifecycle—deployment, upgrades, failure recovery, and decommissioning—with precision. This requires full control over application packaging and a deep, intrinsic integration between the software and the infrastructure it inhabits.
|
||||||
|
|
||||||
|
This is why Harmony uses a powerful, living language like Rust. It replaces static, lifeless configuration files with a dynamic, breathing codebase. It allows us to express the complex relationships and behaviors of a modern distributed system, enabling the creation of truly automated, resilient, and powerful platforms that can thrive.
|
27
harmony_cli/README.md
Normal file
27
harmony_cli/README.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
## Quick demo
|
||||||
|
|
||||||
|
`cargo run -p example-tui`
|
||||||
|
|
||||||
|
This will launch Harmony's minimalist terminal ui which embeds a few demo scores.
|
||||||
|
|
||||||
|
Usage instructions will be displayed at the bottom of the TUI.
|
||||||
|
|
||||||
|
`cargo run --bin example-cli -- --help`
|
||||||
|
|
||||||
|
This is the harmony CLI, a minimal implementation
|
||||||
|
|
||||||
|
The current help text:
|
||||||
|
|
||||||
|
```
|
||||||
|
Usage: example-cli [OPTIONS]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-y, --yes Run score(s) or not
|
||||||
|
-f, --filter <FILTER> Filter query
|
||||||
|
-i, --interactive Run interactive TUI or not
|
||||||
|
-a, --all Run all or nth, defaults to all
|
||||||
|
-n, --number <NUMBER> Run nth matching, zero indexed [default: 0]
|
||||||
|
-l, --list list scores, will also be affected by run filter
|
||||||
|
-h, --help Print help
|
||||||
|
-V, --version Print version```
|
||||||
|
|
146
opnsense-config/README.md
Normal file
146
opnsense-config/README.md
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
## Supporting a new field in OPNSense `config.xml`
|
||||||
|
|
||||||
|
Two steps:
|
||||||
|
- Supporting the field in `opnsense-config-xml`
|
||||||
|
- Enabling Harmony to control the field
|
||||||
|
|
||||||
|
We'll use the `filename` field in the `dhcpcd` section of the file as an example.
|
||||||
|
|
||||||
|
### Supporting the field
|
||||||
|
|
||||||
|
As type checking if enforced, every field from `config.xml` must be known by the code. Each subsection of `config.xml` has its `.rs` file. For the `dhcpcd` section, we'll modify `opnsense-config-xml/src/data/dhcpd.rs`.
|
||||||
|
|
||||||
|
When a new field appears in the xml file, an error like this will be thrown and Harmony will panic :
|
||||||
|
```
|
||||||
|
Running `/home/stremblay/nt/dir/harmony/target/debug/example-nanodc`
|
||||||
|
Found unauthorized element filename
|
||||||
|
thread 'main' panicked at opnsense-config-xml/src/data/opnsense.rs:54:14:
|
||||||
|
OPNSense received invalid string, should be full XML: ()
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Define the missing field (`filename`) in the `DhcpInterface` struct of `opnsense-config-xml/src/data/dhcpd.rs`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct DhcpInterface {
|
||||||
|
...
|
||||||
|
pub filename: Option<String>,
|
||||||
|
```
|
||||||
|
|
||||||
|
Harmony should now be fixed, build and run.
|
||||||
|
|
||||||
|
### Controlling the field
|
||||||
|
|
||||||
|
Define the `xml field setter` in `opnsense-config/src/modules/dhcpd.rs`.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
impl<'a> DhcpConfig<'a> {
|
||||||
|
...
|
||||||
|
pub fn set_filename(&mut self, filename: &str) {
|
||||||
|
self.enable_netboot();
|
||||||
|
self.get_lan_dhcpd().filename = Some(filename.to_string());
|
||||||
|
}
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
Define the `value setter` in the `DhcpServer trait` in `domain/topology/network.rs`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[async_trait]
|
||||||
|
pub trait DhcpServer: Send + Sync {
|
||||||
|
...
|
||||||
|
async fn set_filename(&self, filename: &str) -> Result<(), ExecutorError>;
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
Implement the `value setter` in each `DhcpServer` implementation.
|
||||||
|
`infra/opnsense/dhcp.rs`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[async_trait]
|
||||||
|
impl DhcpServer for OPNSenseFirewall {
|
||||||
|
...
|
||||||
|
async fn set_filename(&self, filename: &str) -> Result<(), ExecutorError> {
|
||||||
|
{
|
||||||
|
let mut writable_opnsense = self.opnsense_config.write().await;
|
||||||
|
writable_opnsense.dhcp().set_filename(filename);
|
||||||
|
debug!("OPNsense dhcp server set filename {filename}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
`domain/topology/ha_cluster.rs`
|
||||||
|
```rust
|
||||||
|
#[async_trait]
|
||||||
|
impl DhcpServer for DummyInfra {
|
||||||
|
...
|
||||||
|
async fn set_filename(&self, _filename: &str) -> Result<(), ExecutorError> {
|
||||||
|
unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA)
|
||||||
|
}
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
Add the new field to the DhcpScore in `modules/dhcp.rs`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct DhcpScore {
|
||||||
|
...
|
||||||
|
pub filename: Option<String>,
|
||||||
|
```
|
||||||
|
|
||||||
|
Define it in its implementation in `modules/okd/dhcp.rs`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
impl OKDDhcpScore {
|
||||||
|
...
|
||||||
|
Self {
|
||||||
|
dhcp_score: DhcpScore {
|
||||||
|
...
|
||||||
|
filename: Some("undionly.kpxe".to_string()),
|
||||||
|
```
|
||||||
|
|
||||||
|
Define it in its implementation in `modules/okd/bootstrap_dhcp.rs`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
impl OKDDhcpScore {
|
||||||
|
...
|
||||||
|
Self {
|
||||||
|
dhcp_score: DhcpScore::new(
|
||||||
|
...
|
||||||
|
Some("undionly.kpxe".to_string()),
|
||||||
|
```
|
||||||
|
|
||||||
|
Update the interpret (function called by the `execute` fn of the interpret) so it now updates the `filename` field value in `modules/dhcp.rs`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
impl DhcpInterpret {
|
||||||
|
...
|
||||||
|
let filename_outcome = match &self.score.filename {
|
||||||
|
Some(filename) => {
|
||||||
|
let dhcp_server = Arc::new(topology.dhcp_server.clone());
|
||||||
|
dhcp_server.set_filename(&filename).await?;
|
||||||
|
Outcome::new(
|
||||||
|
InterpretStatus::SUCCESS,
|
||||||
|
format!("Dhcp Interpret Set filename to {filename}"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
None => Outcome::noop(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if next_server_outcome.status == InterpretStatus::NOOP
|
||||||
|
&& boot_filename_outcome.status == InterpretStatus::NOOP
|
||||||
|
&& filename_outcome.status == InterpretStatus::NOOP
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
Ok(Outcome::new(
|
||||||
|
InterpretStatus::SUCCESS,
|
||||||
|
format!(
|
||||||
|
"Dhcp Interpret Set next boot to [{:?}], boot_filename to [{:?}], filename to [{:?}]",
|
||||||
|
self.score.boot_filename, self.score.boot_filename, self.score.filename
|
||||||
|
)
|
||||||
|
...
|
||||||
|
```
|
Loading…
Reference in New Issue
Block a user