forked from NationTech/harmony
Compare commits
9 Commits
feat/learn
...
feat/clust
| Author | SHA1 | Date | |
|---|---|---|---|
| 759a9287d3 | |||
| 24922321b1 | |||
| 4ff57062ae | |||
| 50ce54ea66 | |||
|
|
827a49e56b | ||
| cf84f2cce8 | |||
| a12d12aa4f | |||
| cefb65933a | |||
| 95cfc03518 |
55
Cargo.lock
generated
55
Cargo.lock
generated
@@ -1719,24 +1719,6 @@ dependencies = [
|
|||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "example-ha-cluster"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"brocade",
|
|
||||||
"cidr",
|
|
||||||
"env_logger",
|
|
||||||
"harmony",
|
|
||||||
"harmony_macros",
|
|
||||||
"harmony_secret",
|
|
||||||
"harmony_tui",
|
|
||||||
"harmony_types",
|
|
||||||
"log",
|
|
||||||
"serde",
|
|
||||||
"tokio",
|
|
||||||
"url",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "example-kube-rs"
|
name = "example-kube-rs"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -1822,6 +1804,25 @@ dependencies = [
|
|||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "example-okd-cluster-alerts"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"brocade",
|
||||||
|
"cidr",
|
||||||
|
"env_logger",
|
||||||
|
"harmony",
|
||||||
|
"harmony_cli",
|
||||||
|
"harmony_macros",
|
||||||
|
"harmony_secret",
|
||||||
|
"harmony_secret_derive",
|
||||||
|
"harmony_types",
|
||||||
|
"log",
|
||||||
|
"serde",
|
||||||
|
"tokio",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "example-okd-install"
|
name = "example-okd-install"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -1856,24 +1857,6 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "example-opnsense"
|
name = "example-opnsense"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
|
||||||
"brocade",
|
|
||||||
"cidr",
|
|
||||||
"env_logger",
|
|
||||||
"harmony",
|
|
||||||
"harmony_cli",
|
|
||||||
"harmony_macros",
|
|
||||||
"harmony_secret",
|
|
||||||
"harmony_types",
|
|
||||||
"log",
|
|
||||||
"serde",
|
|
||||||
"tokio",
|
|
||||||
"url",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "example-opnsense-2"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"brocade",
|
"brocade",
|
||||||
"cidr",
|
"cidr",
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
## OPNSense demo
|
|
||||||
|
|
||||||
Download the virtualbox snapshot from {{TODO URL}}
|
|
||||||
|
|
||||||
Start the virtualbox image
|
|
||||||
|
|
||||||
This virtualbox image is configured to use a bridge on the host's physical interface, make sure the bridge is up and the virtual machine can reach internet.
|
|
||||||
|
|
||||||
Credentials are opnsense default (root/opnsense)
|
|
||||||
|
|
||||||
Run the project with the correct ip address on the command line :
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo run -p example-opnsense -- 192.168.5.229
|
|
||||||
```
|
|
||||||
@@ -1,141 +0,0 @@
|
|||||||
use std::{
|
|
||||||
net::{IpAddr, Ipv4Addr},
|
|
||||||
sync::Arc,
|
|
||||||
};
|
|
||||||
|
|
||||||
use brocade::BrocadeOptions;
|
|
||||||
use cidr::Ipv4Cidr;
|
|
||||||
use harmony::{
|
|
||||||
hardware::{HostCategory, Location, PhysicalHost, SwitchGroup},
|
|
||||||
infra::{brocade::BrocadeSwitchClient, opnsense::OPNSenseManagementInterface},
|
|
||||||
inventory::Inventory,
|
|
||||||
modules::{
|
|
||||||
dummy::{ErrorScore, PanicScore, SuccessScore},
|
|
||||||
http::StaticFilesHttpScore,
|
|
||||||
okd::{dhcp::OKDDhcpScore, dns::OKDDnsScore, load_balancer::OKDLoadBalancerScore},
|
|
||||||
opnsense::OPNsenseShellCommandScore,
|
|
||||||
tftp::TftpScore,
|
|
||||||
},
|
|
||||||
topology::{LogicalHost, UnmanagedRouter},
|
|
||||||
};
|
|
||||||
use harmony_macros::{ip, mac_address};
|
|
||||||
use harmony_secret::{Secret, SecretManager};
|
|
||||||
use harmony_types::net::Url;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() {
|
|
||||||
let firewall = harmony::topology::LogicalHost {
|
|
||||||
ip: ip!("192.168.5.229"),
|
|
||||||
name: String::from("opnsense-1"),
|
|
||||||
};
|
|
||||||
|
|
||||||
let switch_auth = SecretManager::get_or_prompt::<BrocadeSwitchAuth>()
|
|
||||||
.await
|
|
||||||
.expect("Failed to get credentials");
|
|
||||||
|
|
||||||
let switches: Vec<IpAddr> = vec![ip!("192.168.5.101")]; // TODO: Adjust me
|
|
||||||
let brocade_options = Some(BrocadeOptions {
|
|
||||||
dry_run: *harmony::config::DRY_RUN,
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
let switch_client = BrocadeSwitchClient::init(
|
|
||||||
&switches,
|
|
||||||
&switch_auth.username,
|
|
||||||
&switch_auth.password,
|
|
||||||
brocade_options,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.expect("Failed to connect to switch");
|
|
||||||
|
|
||||||
let switch_client = Arc::new(switch_client);
|
|
||||||
|
|
||||||
let opnsense = Arc::new(
|
|
||||||
harmony::infra::opnsense::OPNSenseFirewall::new(firewall, None, "root", "opnsense").await,
|
|
||||||
);
|
|
||||||
let lan_subnet = Ipv4Addr::new(10, 100, 8, 0);
|
|
||||||
let gateway_ipv4 = Ipv4Addr::new(10, 100, 8, 1);
|
|
||||||
let gateway_ip = IpAddr::V4(gateway_ipv4);
|
|
||||||
let topology = harmony::topology::HAClusterTopology {
|
|
||||||
kubeconfig: None,
|
|
||||||
domain_name: "demo.harmony.mcd".to_string(),
|
|
||||||
router: Arc::new(UnmanagedRouter::new(
|
|
||||||
gateway_ip,
|
|
||||||
Ipv4Cidr::new(lan_subnet, 24).unwrap(),
|
|
||||||
)),
|
|
||||||
load_balancer: opnsense.clone(),
|
|
||||||
firewall: opnsense.clone(),
|
|
||||||
tftp_server: opnsense.clone(),
|
|
||||||
http_server: opnsense.clone(),
|
|
||||||
dhcp_server: opnsense.clone(),
|
|
||||||
dns_server: opnsense.clone(),
|
|
||||||
control_plane: vec![LogicalHost {
|
|
||||||
ip: ip!("10.100.8.20"),
|
|
||||||
name: "cp0".to_string(),
|
|
||||||
}],
|
|
||||||
bootstrap_host: LogicalHost {
|
|
||||||
ip: ip!("10.100.8.20"),
|
|
||||||
name: "cp0".to_string(),
|
|
||||||
},
|
|
||||||
workers: vec![],
|
|
||||||
switch_client: switch_client.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let inventory = Inventory {
|
|
||||||
location: Location::new(
|
|
||||||
"232 des Éperviers, Wendake, Qc, G0A 4V0".to_string(),
|
|
||||||
"wk".to_string(),
|
|
||||||
),
|
|
||||||
switch: SwitchGroup::from([]),
|
|
||||||
firewall_mgmt: Box::new(OPNSenseManagementInterface::new()),
|
|
||||||
storage_host: vec![],
|
|
||||||
worker_host: vec![],
|
|
||||||
control_plane_host: vec![
|
|
||||||
PhysicalHost::empty(HostCategory::Server)
|
|
||||||
.mac_address(mac_address!("08:00:27:62:EC:C3")),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO regroup smaller scores in a larger one such as this
|
|
||||||
// let okd_boostrap_preparation();
|
|
||||||
|
|
||||||
let dhcp_score = OKDDhcpScore::new(&topology, &inventory);
|
|
||||||
let dns_score = OKDDnsScore::new(&topology);
|
|
||||||
let load_balancer_score = OKDLoadBalancerScore::new(&topology);
|
|
||||||
|
|
||||||
let tftp_score = TftpScore::new(Url::LocalFolder("./data/watchguard/tftpboot".to_string()));
|
|
||||||
let http_score = StaticFilesHttpScore {
|
|
||||||
folder_to_serve: Some(Url::LocalFolder(
|
|
||||||
"./data/watchguard/pxe-http-files".to_string(),
|
|
||||||
)),
|
|
||||||
files: vec![],
|
|
||||||
remote_path: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
harmony_tui::run(
|
|
||||||
inventory,
|
|
||||||
topology,
|
|
||||||
vec![
|
|
||||||
Box::new(dns_score),
|
|
||||||
Box::new(dhcp_score),
|
|
||||||
Box::new(load_balancer_score),
|
|
||||||
Box::new(tftp_score),
|
|
||||||
Box::new(http_score),
|
|
||||||
Box::new(OPNsenseShellCommandScore {
|
|
||||||
opnsense: opnsense.get_opnsense_config(),
|
|
||||||
command: "touch /tmp/helloharmonytouching".to_string(),
|
|
||||||
}),
|
|
||||||
Box::new(SuccessScore {}),
|
|
||||||
Box::new(ErrorScore {}),
|
|
||||||
Box::new(PanicScore {}),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Secret, Serialize, Deserialize, Debug)]
|
|
||||||
pub struct BrocadeSwitchAuth {
|
|
||||||
pub username: String,
|
|
||||||
pub password: String,
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "example-ha-cluster"
|
name = "example-okd-cluster-alerts"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
version.workspace = true
|
version.workspace = true
|
||||||
readme.workspace = true
|
readme.workspace = true
|
||||||
@@ -8,14 +8,15 @@ publish = false
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
harmony = { path = "../../harmony" }
|
harmony = { path = "../../harmony" }
|
||||||
harmony_tui = { path = "../../harmony_tui" }
|
harmony_cli = { path = "../../harmony_cli" }
|
||||||
harmony_types = { path = "../../harmony_types" }
|
harmony_types = { path = "../../harmony_types" }
|
||||||
|
harmony_secret = { path = "../../harmony_secret" }
|
||||||
|
harmony_secret_derive = { path = "../../harmony_secret_derive" }
|
||||||
cidr = { workspace = true }
|
cidr = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
harmony_macros = { path = "../../harmony_macros" }
|
harmony_macros = { path = "../../harmony_macros" }
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
env_logger = { workspace = true }
|
env_logger = { workspace = true }
|
||||||
url = { workspace = true }
|
url = { workspace = true }
|
||||||
harmony_secret = { path = "../../harmony_secret" }
|
serde.workspace = true
|
||||||
brocade = { path = "../../brocade" }
|
brocade = { path = "../../brocade" }
|
||||||
serde = { workspace = true }
|
|
||||||
26
examples/okd_cluster_alerts/src/main.rs
Normal file
26
examples/okd_cluster_alerts/src/main.rs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
use harmony::{
|
||||||
|
inventory::Inventory,
|
||||||
|
modules::monitoring::{
|
||||||
|
alert_channel::discord_alert_channel::DiscordWebhook,
|
||||||
|
okd::cluster_monitoring::OpenshiftClusterAlertScore,
|
||||||
|
},
|
||||||
|
topology::K8sAnywhereTopology,
|
||||||
|
};
|
||||||
|
use harmony_macros::hurl;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
harmony_cli::run(
|
||||||
|
Inventory::autoload(),
|
||||||
|
K8sAnywhereTopology::from_env(),
|
||||||
|
vec![Box::new(OpenshiftClusterAlertScore {
|
||||||
|
receivers: vec![Box::new(DiscordWebhook {
|
||||||
|
name: "discord-webhook-example".to_string(),
|
||||||
|
url: hurl!("http://something.o"),
|
||||||
|
})],
|
||||||
|
})],
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ publish = false
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
harmony = { path = "../../harmony" }
|
harmony = { path = "../../harmony" }
|
||||||
harmony_cli = { path = "../../harmony_cli" }
|
harmony_tui = { path = "../../harmony_tui" }
|
||||||
harmony_types = { path = "../../harmony_types" }
|
harmony_types = { path = "../../harmony_types" }
|
||||||
cidr = { workspace = true }
|
cidr = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
|
|||||||
@@ -1,23 +1,15 @@
|
|||||||
# OPNSense Demo
|
## OPNSense demo
|
||||||
|
|
||||||
This example demonstrate how to manage an Opnsense server with harmony.
|
Download the virtualbox snapshot from {{TODO URL}}
|
||||||
|
|
||||||
todo: add more info
|
Start the virtualbox image
|
||||||
|
|
||||||
## Demo instructions
|
This virtualbox image is configured to use a bridge on the host's physical interface, make sure the bridge is up and the virtual machine can reach internet.
|
||||||
|
|
||||||
todo: add detailed instructions
|
Credentials are opnsense default (root/opnsense)
|
||||||
|
|
||||||
- setup the example execution environment
|
Run the project with the correct ip address on the command line :
|
||||||
- setup your system configuration
|
|
||||||
- topology
|
|
||||||
- scores
|
|
||||||
- secrets
|
|
||||||
- build
|
|
||||||
- execute
|
|
||||||
- verify/inspect
|
|
||||||
|
|
||||||
## Example execution
|
|
||||||
|
|
||||||
See [learning tool documentation](./scripts/README.md)
|
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo run -p example-opnsense -- 192.168.5.229
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
export HARMONY_SECRET_NAMESPACE=example-opnsense
|
|
||||||
export HARMONY_SECRET_STORE=file
|
|
||||||
export HARMONY_DATABASE_URL=sqlite://harmony_vms.sqlite RUST_LOG=info
|
|
||||||
export RUST_LOG=info
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
## réseau
|
|
||||||
|
|
||||||
- fonctionne uniquement avec connection filaire, pas wifi
|
|
||||||
- on crée des bridges pour le réseau WAN et LAN pour qu'ils soient facilement accessible sur le poste de travail
|
|
||||||
|
|
||||||
```
|
|
||||||
# paramètres
|
|
||||||
PRIMARY_PROFIL=enpXsY
|
|
||||||
NETWORK_LABEL=harmony
|
|
||||||
|
|
||||||
WAN_BRIDGE=$NETWORL_LABEL-wan-brd # max 15 char
|
|
||||||
LAN_NIC=$NETWORK_LABEL-lan-nic
|
|
||||||
LAN_BRIDGE=$NETWORK_LABEL-lan-brd
|
|
||||||
|
|
||||||
|
|
||||||
# Setup WAN bridge
|
|
||||||
nmcli c down $PRIMARY
|
|
||||||
nmcli c add type bridge ifname $WAN_BRIDGE con-name $WAN_BRIDGE
|
|
||||||
nmcli c add type bridge-slave ifname $PRIMARY_PROFILE master $WAN_BRIDGE ipv4.method auto
|
|
||||||
# nmcli c up $WAN_BRIDGE
|
|
||||||
|
|
||||||
|
|
||||||
# Setup LAN nic
|
|
||||||
sudo modprobe dummy
|
|
||||||
sudo ip link add $LAN_NIC type dummy
|
|
||||||
ip tuntap add dev $LAN_NIC mode tap user root # todo: why user root?
|
|
||||||
|
|
||||||
# Setup LAN bridge
|
|
||||||
nmcli c add type bridge ifname $LAN_BRIDGE con-name $LAN_BRIDGE
|
|
||||||
nmcli c add type bridge-slave ifname $LAN_NIC master $LAN_BRIDGE ipv4.method auto
|
|
||||||
nmcli c up $LAN_BRIDGE
|
|
||||||
```
|
|
||||||
|
|
||||||
LAN bridge do not have an IP address and appear down. But it is successfully used by opnsense and can be accessed from the host network.
|
|
||||||
|
|
||||||
## config opnsense minimale
|
|
||||||
|
|
||||||
- ssh enabled
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
# Virtualized Execution Environment for Harmony
|
|
||||||
|
|
||||||
Scripts included in this directory have 3 purposes:
|
|
||||||
|
|
||||||
- automate initial setup of localhost or VM (nested virtualization) for this example
|
|
||||||
- prototype a solution for an 'OpensenseLocalhostTopology'
|
|
||||||
- prototype
|
|
||||||
|
|
||||||
This exprimentation aim to find an approach suitable for using harmony on virtualised execution environment such that:
|
|
||||||
|
|
||||||
- it straights forward for a user with minimal knowledge to start testing harmony
|
|
||||||
- installation and execution have **minimal impact on the user desktop**
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### Installation
|
|
||||||
|
|
||||||
1. download this directory
|
|
||||||
2. add this directory in your PATH (example `. setup`)
|
|
||||||
|
|
||||||
|
|
||||||
```
|
|
||||||
# show help page
|
|
||||||
harmony-vee
|
|
||||||
|
|
||||||
# show active configurations
|
|
||||||
harmony-vee config
|
|
||||||
|
|
||||||
# show what will be modified at installation
|
|
||||||
harmony-vee install --dry-run
|
|
||||||
|
|
||||||
# install
|
|
||||||
harmony-vee install
|
|
||||||
|
|
||||||
# show what will be modified at unistallation
|
|
||||||
harmony-vee uninstall --dry-run
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
### Create and start a new Virtual Execution Environment
|
|
||||||
|
|
||||||
|
|
||||||
```
|
|
||||||
# Create a HVEE to test opnsense_score
|
|
||||||
harmony-vee init opnsense_score
|
|
||||||
|
|
||||||
# List existing HVEE
|
|
||||||
harmony-vee list
|
|
||||||
|
|
||||||
# Show HVEE information including devices ip and vault type/location
|
|
||||||
harmony-vee show opnsense_score
|
|
||||||
|
|
||||||
# Start/Stop a HVEE
|
|
||||||
harmony-vee stop opnsense_score
|
|
||||||
|
|
||||||
|
|
||||||
# Destroy a HVEE instance
|
|
||||||
harmony-vee destroy opnsense_score
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### Variable d'environnement
|
|
||||||
|
|
||||||
```
|
|
||||||
## directory containing harmony-ve data
|
|
||||||
# HVE_ROOT=~/.harmony-ve
|
|
||||||
|
|
||||||
## OPNSENSE SRC
|
|
||||||
# main mirror
|
|
||||||
# HVE_OPNSENSE_URL=https://pkg.opnsense.org/releases
|
|
||||||
# first alternative mirror
|
|
||||||
# HVE_OPENSENSE_URL_ALT1=https://mirror.vraphim.com/opnsense/releases
|
|
||||||
# HVE_OPNSENSE_URL_ALT2=https://mirror.winsub.kr/opnsense/releases
|
|
||||||
|
|
||||||
|
|
||||||
## Network
|
|
||||||
# HVE_NETWORK_LABEL=harmony
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Remarks
|
|
||||||
|
|
||||||
- A nested VM setup could be safer
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
### Learning environment directly on host
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### Learning environment nested in a "workspace vm"
|
|
||||||
|
|
||||||

|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
#! /bin/bash
|
|
||||||
|
|
||||||
_warn(){ >&2 echo "WARNING: $*" ; }
|
|
||||||
|
|
||||||
_fatal(){
|
|
||||||
>&2 echo "FATAL ERROR: $*"
|
|
||||||
>&2 echo stopping...
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
pushd () {
|
|
||||||
command pushd "$@" > /dev/null
|
|
||||||
}
|
|
||||||
|
|
||||||
popd () {
|
|
||||||
command popd "$@" > /dev/null
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
# Conventions:
|
|
||||||
# - Namespaced with HVE, short for Harmony Virtualised Execution Environment
|
|
||||||
# - Prefixed values used internally
|
|
||||||
# - Not prefixed may be supercharged by the user
|
|
||||||
|
|
||||||
# Root of harmony data
|
|
||||||
_HVE_ROOT=${HVE_ROOT:-$HOME/harmony-ve}
|
|
||||||
[ -d "$_HVE_ROOT" ] || mkdir -p "${_HVE_ROOT}"
|
|
||||||
|
|
||||||
|
|
||||||
_HVE_SRC_IMG=${_HVE_ROOT}/src/images
|
|
||||||
[ -d "$_HVE_SRC_IMG" ] || mkdir -p "${_HVE_SRC_IMG}"
|
|
||||||
_HVE_IMG=${_HVE_ROOT}/images
|
|
||||||
[ -d "$_HVE_IMG" ] || mkdir -p "$_HVE_IMG"
|
|
||||||
|
|
||||||
# Opnsense
|
|
||||||
_HVE_OPNSENSE_URL=${HVE_OPNSENSE_URL:-https://pkg.opnsense.org/releases}
|
|
||||||
# first alternative mirror
|
|
||||||
_HVE_OPNSENSE_URL_ALT1=${HVE_OPNSENSE_URL_ALT1:-https://mirror.vraphim.com/opnsense/releases}
|
|
||||||
_HVE_OPNSENSE_URL_ALT2=${HVE_OPNSENSE_URL_ALT2:-https://mirror.winsub.kr/opnsense/releases}
|
|
||||||
|
|
||||||
_HVE_OPNSENSE_SRC_IMG=${_HVE_SRC_IMG}/opnsense
|
|
||||||
[ -d "$_HVE_OPNSENSE_SRC_IMG" ] || mkdir -p "${_HVE_OPNSENSE_SRC_IMG}"
|
|
||||||
_HVE_OPNSENSE_IMG=${_HVE_IMG}/opnsense
|
|
||||||
[ -d "$_HVE_OPNSENSE_IMG" ] || mkdir -p "${_HVE_OPNSENSE_IMG}"
|
|
||||||
|
|
||||||
# Network
|
|
||||||
_HVE_NETWORK=${HVE_NETWORK:-harmony}
|
|
||||||
_HVE_WAN_BRIDGE=${HVE_WAN_BRIDGE:-${_HVE_NETWORK}-wan-brd}
|
|
||||||
_HVE_LAN_BRIDGE=${HVE_LAN_BRIDGE:-${_HVE_NETWORK}-lann-brd}
|
|
||||||
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
#! /bin/bash
|
|
||||||
is_string_empty(){
|
|
||||||
if [ "${*:-}" != "" ]; then
|
|
||||||
return 0
|
|
||||||
else
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
is_debian_family()(
|
|
||||||
is_string_empty "$(apt --version 2> /dev/null )"
|
|
||||||
)
|
|
||||||
|
|
||||||
has_ip(){
|
|
||||||
is_string_empty "$(ip -V 2> /dev/null)"
|
|
||||||
}
|
|
||||||
|
|
||||||
has_virsh(){
|
|
||||||
is_string_empty "$(virsh --version 2> /dev/null)"
|
|
||||||
}
|
|
||||||
|
|
||||||
has_virt_customize(){
|
|
||||||
is_string_empty "$(virt-customize --version 2> /dev/null)"
|
|
||||||
}
|
|
||||||
|
|
||||||
has_curl(){
|
|
||||||
is_string_empty "$(curl --version 2> /dev/null)"
|
|
||||||
}
|
|
||||||
has_wget(){
|
|
||||||
is_string_empty "$(wget --version 2> /dev/null)"
|
|
||||||
}
|
|
||||||
|
|
||||||
install_kvm(){
|
|
||||||
sudo apt install -y --no-install-recommends qemu-system libvirt-clients libvirt-daemon-system
|
|
||||||
sudo usermod -aG libvirt "$USER"
|
|
||||||
# todo: finf how to fix image access out of /var/lib/libvirt/images
|
|
||||||
sudo setfacl -Rm u:libvirt-qemu:rx $_HVE_IMG
|
|
||||||
sudo systemctl restart libvirtd
|
|
||||||
}
|
|
||||||
|
|
||||||
install_virt_customize(){
|
|
||||||
sudo apt install -y libguestfs-tools
|
|
||||||
}
|
|
||||||
|
|
||||||
install_wget(){
|
|
||||||
sudo apt install -y wget
|
|
||||||
}
|
|
||||||
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 106 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 138 KiB |
@@ -1,321 +0,0 @@
|
|||||||
<mxfile host="app.diagrams.net" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36" version="28.2.9" pages="2">
|
|
||||||
<diagram name="localhost" id="lK0WmoXmZXwFmV5PC-RW">
|
|
||||||
<mxGraphModel dx="1111" dy="487" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
|
||||||
<root>
|
|
||||||
<mxCell id="0" />
|
|
||||||
<mxCell id="1" parent="0" />
|
|
||||||
<mxCell id="1Ax8jaXdU0J25Zkiwu96-1" value="" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="300" y="230" width="700" height="550" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="VVCo9gNhF-9Hs0fZa6i8-1" value="<b>localhost</b>" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="380" y="240" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="X0RF5wBsf5JYURxROWwA-1" value="" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="350" y="283" width="230" height="67" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="X0RF5wBsf5JYURxROWwA-2" value="" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="590" y="393" width="310" height="270" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="X0RF5wBsf5JYURxROWwA-3" value="<b>opnsense vm</b>" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="620" y="403" width="90" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="X0RF5wBsf5JYURxROWwA-4" value="" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="620" y="250" width="280" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="X0RF5wBsf5JYURxROWwA-5" value="<b>localhost network</b>" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="610" y="250" width="150" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="X0RF5wBsf5JYURxROWwA-6" value="<b>Src repo or</b><div style=""><b>Harmony learning tool</b></div>" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="370" y="294" width="152" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="X0RF5wBsf5JYURxROWwA-7" value="" style="html=1;rounded=0;direction=south;rotation=90;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="760" y="290" width="30" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="X0RF5wBsf5JYURxROWwA-8" value="" style="endArrow=none;html=1;rounded=0;align=center;verticalAlign=top;endFill=0;labelBackgroundColor=none;endSize=2;" parent="1" source="X0RF5wBsf5JYURxROWwA-7" target="X0RF5wBsf5JYURxROWwA-9" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="X0RF5wBsf5JYURxROWwA-9" value="" style="ellipse;html=1;fontSize=11;align=center;fillColor=none;points=[];aspect=fixed;resizable=0;verticalAlign=bottom;labelPosition=center;verticalLabelPosition=top;flipH=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="771" y="342" width="8" height="8" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="X0RF5wBsf5JYURxROWwA-13" value="" style="html=1;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="810" y="290" width="30" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="X0RF5wBsf5JYURxROWwA-14" value="" style="endArrow=none;html=1;rounded=0;align=center;verticalAlign=top;endFill=0;labelBackgroundColor=none;endSize=2;" parent="1" source="X0RF5wBsf5JYURxROWwA-13" target="X0RF5wBsf5JYURxROWwA-15" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="X0RF5wBsf5JYURxROWwA-15" value="" style="ellipse;html=1;fontSize=11;align=center;fillColor=none;points=[];aspect=fixed;resizable=0;verticalAlign=bottom;labelPosition=center;verticalLabelPosition=top;flipH=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="820" y="342" width="8" height="8" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="X0RF5wBsf5JYURxROWwA-16" value="" style="html=1;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="760" y="383" width="30" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="X0RF5wBsf5JYURxROWwA-17" value="" style="endArrow=none;html=1;rounded=0;align=center;verticalAlign=top;endFill=0;labelBackgroundColor=none;endSize=2;" parent="1" source="X0RF5wBsf5JYURxROWwA-16" target="X0RF5wBsf5JYURxROWwA-18" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="X0RF5wBsf5JYURxROWwA-18" value="" style="shape=requiredInterface;html=1;fontSize=11;align=center;fillColor=none;points=[];aspect=fixed;resizable=0;verticalAlign=bottom;labelPosition=center;verticalLabelPosition=top;flipH=1;rotation=-90;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="772.5" y="353" width="5" height="10" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="X0RF5wBsf5JYURxROWwA-19" value="" style="html=1;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="809" y="383" width="30" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="X0RF5wBsf5JYURxROWwA-20" value="" style="endArrow=none;html=1;rounded=0;align=center;verticalAlign=top;endFill=0;labelBackgroundColor=none;endSize=2;" parent="1" source="X0RF5wBsf5JYURxROWwA-19" target="X0RF5wBsf5JYURxROWwA-21" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="X0RF5wBsf5JYURxROWwA-21" value="" style="shape=requiredInterface;html=1;fontSize=11;align=center;fillColor=none;points=[];aspect=fixed;resizable=0;verticalAlign=bottom;labelPosition=center;verticalLabelPosition=top;flipH=1;rotation=-90;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="821.5" y="353" width="5" height="10" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="X0RF5wBsf5JYURxROWwA-22" value="" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="350" y="440" width="230" height="130" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="X0RF5wBsf5JYURxROWwA-23" value="<b>Example dependencies</b>" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="360" y="450" width="152" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="X0RF5wBsf5JYURxROWwA-24" value="<ul><li>kvm</li><li>virt-customize</li><li>...</li></ul>" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="360" y="510" width="150" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="X0RF5wBsf5JYURxROWwA-25" value="" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="350" y="590" width="230" height="130" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="X0RF5wBsf5JYURxROWwA-26" value="<b>Example resources</b>" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="370" y="600" width="152" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="X0RF5wBsf5JYURxROWwA-27" value="<ul><li>src opnsense images</li><li>modified opnsense images</li></ul>" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="361" y="650" width="208" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="X0RF5wBsf5JYURxROWwA-28" value="" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="350" y="353" width="230" height="77" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="X0RF5wBsf5JYURxROWwA-29" value="<b>Local workspace</b>" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="370" y="361.5" width="152" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="X0RF5wBsf5JYURxROWwA-30" value="<ul><li>provisioned using the learning tool</li><li>managed using harmony</li></ul>" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="620" y="450" width="250" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="X0RF5wBsf5JYURxROWwA-31" value="<i><font style="color: rgb(51, 51, 51);">Minimun required to learn Harmony</font></i>" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="370" y="323" width="200" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="X0RF5wBsf5JYURxROWwA-33" value="<font color="#333333"><i>A place to store configs and</i></font><div><font color="#333333"><i>runtime info.</i></font></div>" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="370" y="395" width="200" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="X0RF5wBsf5JYURxROWwA-34" value="<i><font style="color: rgb(51, 51, 51);">Modifications of localhost</font></i>" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="360" y="470" width="200" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="X0RF5wBsf5JYURxROWwA-35" value="<i><font style="color: rgb(51, 51, 51);">Modifications of localhost</font></i>" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="626.5" y="264" width="200" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="X0RF5wBsf5JYURxROWwA-36" value="<i><font style="color: rgb(51, 51, 51);">Data store (image registry, etc.)</font></i>" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="369" y="620" width="200" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="X0RF5wBsf5JYURxROWwA-37" value="<font color="#333333"><i>Execution environment</i></font>" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="626.5" y="420" width="200" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
</root>
|
|
||||||
</mxGraphModel>
|
|
||||||
</diagram>
|
|
||||||
<diagram id="oOuscOXp9aWETXQepMaW" name="nested-virtualization">
|
|
||||||
<mxGraphModel dx="1111" dy="487" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
|
||||||
<root>
|
|
||||||
<mxCell id="0" />
|
|
||||||
<mxCell id="1" parent="0" />
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-1" value="" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="190" y="60" width="1240" height="660" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-37" value="" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="540" y="210" width="830" height="480" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-2" value="<b>localhost</b>" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="260" y="84" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-3" value="" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="240" y="223" width="230" height="67" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-4" value="" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="855" y="403.75" width="310" height="266.25" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-5" value="<b>opnsense vm</b>" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="885" y="413.75" width="90" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-6" value="" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="810" y="230" width="530" height="120" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-7" value="<b>workspace VM network</b>" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="825" y="234" width="150" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-8" value="<b>Src repo or</b><div style=""><b>Harmony learning tool</b></div>" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="260" y="234" width="152" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-10" value="" style="endArrow=none;html=1;rounded=0;align=center;verticalAlign=top;endFill=0;labelBackgroundColor=none;endSize=2;" parent="1" source="vj3pG7w7rTpS1tnBMssI-9" target="vj3pG7w7rTpS1tnBMssI-11" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-13" value="" style="endArrow=none;html=1;rounded=0;align=center;verticalAlign=top;endFill=0;labelBackgroundColor=none;endSize=2;" parent="1" source="vj3pG7w7rTpS1tnBMssI-12" target="vj3pG7w7rTpS1tnBMssI-14" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-15" value="" style="html=1;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="1025" y="393.75" width="30" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-16" value="" style="endArrow=none;html=1;rounded=0;align=center;verticalAlign=top;endFill=0;labelBackgroundColor=none;endSize=2;" parent="1" source="vj3pG7w7rTpS1tnBMssI-15" target="vj3pG7w7rTpS1tnBMssI-17" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-17" value="" style="shape=requiredInterface;html=1;fontSize=11;align=center;fillColor=none;points=[];aspect=fixed;resizable=0;verticalAlign=bottom;labelPosition=center;verticalLabelPosition=top;flipH=1;rotation=-90;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="1037.5" y="363.75" width="5" height="10" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-18" value="" style="html=1;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="1074" y="393.75" width="30" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-19" value="" style="endArrow=none;html=1;rounded=0;align=center;verticalAlign=top;endFill=0;labelBackgroundColor=none;endSize=2;" parent="1" source="vj3pG7w7rTpS1tnBMssI-18" target="vj3pG7w7rTpS1tnBMssI-20" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-20" value="" style="shape=requiredInterface;html=1;fontSize=11;align=center;fillColor=none;points=[];aspect=fixed;resizable=0;verticalAlign=bottom;labelPosition=center;verticalLabelPosition=top;flipH=1;rotation=-90;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="1086.5" y="363.75" width="5" height="10" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-21" value="" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="240" y="380" width="230" height="120" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-22" value="<b>Learning dependencies</b>" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="250" y="391" width="152" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-23" value="<ul><li>harmony</li><li>kvm</li><li>virt-customize</li><li>...</li></ul>" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="250" y="450" width="150" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-24" value="" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="240" y="540" width="230" height="130" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-25" value="<b>Example resources</b>" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="260" y="550" width="152" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-26" value="<ul><li>Can be mounted locally<br>for persistence</li></ul>" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="251" y="600" width="189" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-27" value="" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="240" y="293" width="230" height="67" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-28" value="<b>Local workspace</b>" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="260" y="301.5" width="152" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-29" value="<ul><li>provisioned using the learning tool</li><li>managed using harmony</li></ul>" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="885" y="460.75" width="250" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-30" value="<i><font style="color: rgb(51, 51, 51);">Minimun required to learn Harmony</font></i>" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="260" y="263" width="200" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-31" value="<font color="#333333"><i>A place to store configs</i></font>" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="260" y="321" width="200" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-32" value="<i><font style="color: rgb(51, 51, 51);">Modifications of localhost</font></i>" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="250" y="410" width="200" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-34" value="<i><font style="color: rgb(51, 51, 51);">Data store (image registry, etc.)</font></i>" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="259" y="570" width="200" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-35" value="<font color="#333333"><i>Execution environment</i></font>" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="891.5" y="430.75" width="200" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-38" value="<b>workspace VM</b>" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="580" y="234" width="130" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-39" value="" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="570" y="305" width="230" height="215" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-40" value="<b>workspace VM dependencies</b>" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="580" y="316" width="200" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-41" value="<ul><li>kvm</li><li>virt-customize</li><li>...</li></ul>" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="580" y="375" width="150" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-44" value="" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="570" y="540" width="230" height="130" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-48" value="" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="830" y="277" width="235" height="45" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-45" value="<b>Example resources</b>" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="590" y="550" width="152" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-46" value="<ul><li>src opnsense images</li><li>modified opnsense images</li></ul>" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="581" y="600" width="208" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-47" value="<i><font style="color: rgb(51, 51, 51);">Data store (image registry, etc.)</font></i>" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="589" y="570" width="200" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-9" value="" style="html=1;rounded=0;direction=south;rotation=90;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="1025" y="300.75" width="30" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-11" value="" style="ellipse;html=1;fontSize=11;align=center;fillColor=none;points=[];aspect=fixed;resizable=0;verticalAlign=bottom;labelPosition=center;verticalLabelPosition=top;flipH=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="1036" y="352.75" width="8" height="8" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-49" value="" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="1069" y="275" width="251" height="45" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-12" value="" style="html=1;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="1075" y="300.75" width="30" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-14" value="" style="ellipse;html=1;fontSize=11;align=center;fillColor=none;points=[];aspect=fixed;resizable=0;verticalAlign=bottom;labelPosition=center;verticalLabelPosition=top;flipH=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="1085" y="352.75" width="8" height="8" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-50" value="<b>wan</b>" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="821.5" y="277" width="70" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-51" value="<b>lan</b>" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="1055" y="275" width="70" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-52" value="" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="1181" y="403.75" width="159" height="116.25" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-53" value="" style="endArrow=none;html=1;rounded=0;align=center;verticalAlign=top;endFill=0;labelBackgroundColor=none;endSize=2;" parent="1" source="vj3pG7w7rTpS1tnBMssI-54" target="vj3pG7w7rTpS1tnBMssI-55" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-54" value="" style="html=1;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="1211" y="305" width="30" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-55" value="" style="ellipse;html=1;fontSize=11;align=center;fillColor=none;points=[];aspect=fixed;resizable=0;verticalAlign=bottom;labelPosition=center;verticalLabelPosition=top;flipH=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="1221" y="357" width="8" height="8" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-58" value="" style="html=1;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="1210" y="393.75" width="30" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-59" value="" style="endArrow=none;html=1;rounded=0;align=center;verticalAlign=top;endFill=0;labelBackgroundColor=none;endSize=2;" parent="1" source="vj3pG7w7rTpS1tnBMssI-58" target="vj3pG7w7rTpS1tnBMssI-60" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-60" value="" style="shape=requiredInterface;html=1;fontSize=11;align=center;fillColor=none;points=[];aspect=fixed;resizable=0;verticalAlign=bottom;labelPosition=center;verticalLabelPosition=top;flipH=1;rotation=-90;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="1222.5" y="363.75" width="5" height="10" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-61" value="<b>other vm</b>" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="1181" y="430" width="90" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="vj3pG7w7rTpS1tnBMssI-63" value="" style="endArrow=classic;startArrow=classic;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="vj3pG7w7rTpS1tnBMssI-24" target="vj3pG7w7rTpS1tnBMssI-44" edge="1">
|
|
||||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
|
||||||
<mxPoint x="600" y="770" as="sourcePoint" />
|
|
||||||
<mxPoint x="360" y="885" as="targetPoint" />
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Za91R7Nqk7Jj5VTRes6Y-1" value="" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="799" y="84" width="280" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Za91R7Nqk7Jj5VTRes6Y-2" value="<b>localhost network</b>" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="789" y="84" width="150" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Za91R7Nqk7Jj5VTRes6Y-6" value="" style="html=1;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="989" y="124" width="30" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Za91R7Nqk7Jj5VTRes6Y-7" value="" style="endArrow=none;html=1;rounded=0;align=center;verticalAlign=top;endFill=0;labelBackgroundColor=none;endSize=2;" parent="1" source="Za91R7Nqk7Jj5VTRes6Y-6" target="Za91R7Nqk7Jj5VTRes6Y-8" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Za91R7Nqk7Jj5VTRes6Y-8" value="" style="ellipse;html=1;fontSize=11;align=center;fillColor=none;points=[];aspect=fixed;resizable=0;verticalAlign=bottom;labelPosition=center;verticalLabelPosition=top;flipH=1;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="999" y="176" width="8" height="8" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Za91R7Nqk7Jj5VTRes6Y-12" value="" style="html=1;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="988" y="217" width="30" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Za91R7Nqk7Jj5VTRes6Y-13" value="" style="endArrow=none;html=1;rounded=0;align=center;verticalAlign=top;endFill=0;labelBackgroundColor=none;endSize=2;" parent="1" source="Za91R7Nqk7Jj5VTRes6Y-12" target="Za91R7Nqk7Jj5VTRes6Y-14" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Za91R7Nqk7Jj5VTRes6Y-14" value="" style="shape=requiredInterface;html=1;fontSize=11;align=center;fillColor=none;points=[];aspect=fixed;resizable=0;verticalAlign=bottom;labelPosition=center;verticalLabelPosition=top;flipH=1;rotation=-90;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="1000.5" y="187" width="5" height="10" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Za91R7Nqk7Jj5VTRes6Y-15" value="<font color="#333333"><i>No modification required</i></font>" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;rounded=0;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="805.5" y="98" width="200" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
</root>
|
|
||||||
</mxGraphModel>
|
|
||||||
</diagram>
|
|
||||||
</mxfile>
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
## directory containing harmony-ve data
|
|
||||||
# HVE_ROOT=~/.harmony-ve
|
|
||||||
|
|
||||||
## OPNSENSE SRC
|
|
||||||
# main mirror
|
|
||||||
# HVE_OPNSENSE_URL=https://pkg.opnsense.org/releases
|
|
||||||
# first alternative mirror
|
|
||||||
# HVE_OPENSENSE_URL_ALT1=https://mirror.vraphim.com/opnsense/releases
|
|
||||||
# HVE_OPNSENSE_URL_ALT2=https://mirror.winsub.kr/opnsense/releases
|
|
||||||
|
|
||||||
|
|
||||||
## Network
|
|
||||||
# HVE_NETWORK_LABEL=harmony
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
|
|
||||||
#! /bin/bash
|
|
||||||
|
|
||||||
harmony-ve()(
|
|
||||||
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
[ "${1:-}" != "-d" ] || { set -x ; shift ; }
|
|
||||||
trap '[ "$?" = "0" ] || >&2 echo ABNORMAL TERMINATION' EXIT
|
|
||||||
|
|
||||||
SCRIPTS_DIR=$(readlink -f "$(dirname "${BASH_SOURCE}")")
|
|
||||||
. "${SCRIPTS_DIR}/common"
|
|
||||||
|
|
||||||
_short_help(){
|
|
||||||
|
|
||||||
cat <<-EOM
|
|
||||||
|
|
||||||
NAME
|
|
||||||
|
|
||||||
harmony-ve
|
|
||||||
|
|
||||||
DESCRIPTION
|
|
||||||
|
|
||||||
CLI management toolkit for Harmony Virtualized Execution Environment
|
|
||||||
|
|
||||||
SYNOPSYS
|
|
||||||
|
|
||||||
harmony-ve [GLOBAL_OPTIONS] COMMAND [OPTIONS]
|
|
||||||
|
|
||||||
harmony-ve dependencies # manage localhost depend
|
|
||||||
harmony-ve network # manage localhost netork
|
|
||||||
|
|
||||||
harmony-ve opnsense-img-src # manage opnsense OS source images
|
|
||||||
harmony-ve opnsense-img # manage opnsense OS images
|
|
||||||
|
|
||||||
harmony-ve vm # manage vm
|
|
||||||
|
|
||||||
EOM
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
_extra_help(){
|
|
||||||
|
|
||||||
cat <<-EOM
|
|
||||||
|
|
||||||
DESCRIPTION
|
|
||||||
|
|
||||||
Automation CLI to easily provision and manage a Virtualized Execution Environment for testing and learning Harmony.
|
|
||||||
|
|
||||||
This tool allows:
|
|
||||||
- new harmony users to start testing within 15 minutes on their development desktop
|
|
||||||
- automate virtualized test in pipeline
|
|
||||||
|
|
||||||
GLOBAL_OPTIONS
|
|
||||||
|
|
||||||
-d Debug mode.
|
|
||||||
|
|
||||||
WARNINGS
|
|
||||||
|
|
||||||
This script is experimetal. Use with caution.
|
|
||||||
EOM
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# Implement functions
|
|
||||||
|
|
||||||
case "${1:-}" in
|
|
||||||
|
|
||||||
"")
|
|
||||||
_short_help
|
|
||||||
;;
|
|
||||||
-h|--help)
|
|
||||||
_short_help
|
|
||||||
_extra_help
|
|
||||||
;;
|
|
||||||
# Commands entrypoints
|
|
||||||
deps|dependencies)
|
|
||||||
harmony-ve-dependencies "${@:2}"
|
|
||||||
;;
|
|
||||||
net|network)
|
|
||||||
harmony-ve-network"${@:2}"
|
|
||||||
;;
|
|
||||||
img-src|opnsense-img-src)
|
|
||||||
harmony-ve-opnsense-img-src "${@:2}"
|
|
||||||
;;
|
|
||||||
img|opnsense-img)
|
|
||||||
harmony-ve-opnsense-img "${@:2}"
|
|
||||||
;;
|
|
||||||
vm)
|
|
||||||
harmony-ve-vm "${@:2}"
|
|
||||||
;;
|
|
||||||
|
|
||||||
*)
|
|
||||||
_warn "Unknown COMMAND '$1'"
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
[ "$0" != "${BASH_SOURCE}" ] || harmony-ve "${@}"
|
|
||||||
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
#! /bin/bash
|
|
||||||
#
|
|
||||||
# virt-install <= virtinst
|
|
||||||
# quemu-img
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
harmony-ve-dependencies()(
|
|
||||||
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
[ "${1:-}" != "-d" ] || { set -x ; shift ; }
|
|
||||||
trap '[ "$?" = "0" ] || >&2 echo ABNORMAL TERMINATION' EXIT
|
|
||||||
|
|
||||||
SCRIPTS_DIR=$(readlink -f "$(dirname "${BASH_SOURCE}")")
|
|
||||||
. "${SCRIPTS_DIR}/common"
|
|
||||||
_short_help(){
|
|
||||||
|
|
||||||
cat <<-EOM
|
|
||||||
|
|
||||||
NAME
|
|
||||||
|
|
||||||
harmony-ve-dependencies
|
|
||||||
|
|
||||||
DESCRIPTION
|
|
||||||
|
|
||||||
Manage localhost dependencies needed for Harmony Virtual Execution Environment
|
|
||||||
|
|
||||||
SYNOPSYS
|
|
||||||
|
|
||||||
devops-dependencies [GLOBAL_OPTIONS] COMMAND [OPTIONS]
|
|
||||||
|
|
||||||
devops check # Check that dependencies are installed
|
|
||||||
devops install # Install missing dependencies
|
|
||||||
|
|
||||||
EOM
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
_extra_help(){
|
|
||||||
|
|
||||||
cat <<-EOM
|
|
||||||
|
|
||||||
GLOBAL_OPTIONS
|
|
||||||
|
|
||||||
-d Debug mode.
|
|
||||||
|
|
||||||
WARNINGS
|
|
||||||
|
|
||||||
This script is experimetal. Use with caution.
|
|
||||||
EOM
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
_check_dependencies(){
|
|
||||||
. "${SCRIPTS_DIR}/dependencies-management"
|
|
||||||
missing=0
|
|
||||||
NEED_IP=false
|
|
||||||
NEED_KVM=false
|
|
||||||
NEED_VIRT_CUSTOMIZE=false
|
|
||||||
NEED_WGET=false
|
|
||||||
is_debian_family || _fatal only debian based version is supported
|
|
||||||
has_ip || {
|
|
||||||
missing=$(( missing + 1));
|
|
||||||
_warn "ip command is missing";
|
|
||||||
NEED_IP=true
|
|
||||||
}
|
|
||||||
has_virsh ||{
|
|
||||||
missing=$(( missing + 1));
|
|
||||||
_warn "virsh command is missing";
|
|
||||||
NEED_KVM=true
|
|
||||||
}
|
|
||||||
has_virt_customize || {
|
|
||||||
missing=$(( missing + 1));
|
|
||||||
_warn "virt-customize command is missing";
|
|
||||||
NEED_VIRT_CUSTOMIZE=true
|
|
||||||
}
|
|
||||||
has_wget || has_curl || {
|
|
||||||
missing=$(( missing + 1));
|
|
||||||
_warn "wget and curl commands are missing"; NEED_WGET=true
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
_install_dependencies(){
|
|
||||||
|
|
||||||
[ "$NEED_KVM" != "true" ] || install_kvm
|
|
||||||
[ "$NEED_VIRT_CUSTOMIZE" != "true" ] || install_virt_customize
|
|
||||||
[ "$NEED_WGET" != "true" ] || install_wget
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
case "${1:-}" in
|
|
||||||
|
|
||||||
"")
|
|
||||||
_short_help
|
|
||||||
;;
|
|
||||||
-h|--help)
|
|
||||||
_short_help
|
|
||||||
_extra_help
|
|
||||||
;;
|
|
||||||
cdeps|check-dependencies)
|
|
||||||
_check_dependencies
|
|
||||||
if [ "$missing" -gt 0 ]; then
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
_warn No missing dependencies
|
|
||||||
;;
|
|
||||||
ideps|install-dependencies)
|
|
||||||
_check_dependencies
|
|
||||||
_install_dependencies
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
_warn "Unknown COMMAND '$1'"
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
[ "$0" != "${BASH_SOURCE}" ] || harmony-ve-dependencies "${@}"
|
|
||||||
|
|
||||||
@@ -1,232 +0,0 @@
|
|||||||
|
|
||||||
#! /bin/bash
|
|
||||||
# todo: allow wan to switch from ethernet to wifi
|
|
||||||
|
|
||||||
|
|
||||||
harmony-ve-network()(
|
|
||||||
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
[ "${1:-}" != "-d" ] || { set -x ; shift ; }
|
|
||||||
trap '[ "$?" = "0" ] || >&2 echo ABNORMAL TERMINATION' EXIT
|
|
||||||
|
|
||||||
SCRIPTS_DIR=$(readlink -f "$(dirname "${BASH_SOURCE}")")
|
|
||||||
. "${SCRIPTS_DIR}/common"
|
|
||||||
. "${SCRIPTS_DIR}/default-env-var"
|
|
||||||
|
|
||||||
_short_help(){
|
|
||||||
|
|
||||||
cat <<-EOM
|
|
||||||
|
|
||||||
NAME
|
|
||||||
|
|
||||||
harmony-ve-network
|
|
||||||
|
|
||||||
DESCRIPTION
|
|
||||||
|
|
||||||
Modify localhost network for Harmony Virtual Execution Environment
|
|
||||||
SYNOPSYS
|
|
||||||
|
|
||||||
harmony-ve-network [GLOBAL_OPTIONS] COMMAND [OPTIONS]
|
|
||||||
|
|
||||||
harmony-ve-network check
|
|
||||||
harmony-ve-network setup
|
|
||||||
harmony-ve-network cleanup
|
|
||||||
|
|
||||||
EOM
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
_extra_help(){
|
|
||||||
|
|
||||||
cat <<-EOM
|
|
||||||
|
|
||||||
GLOBAL_OPTIONS
|
|
||||||
|
|
||||||
-d Debug mode.
|
|
||||||
|
|
||||||
WARNINGS
|
|
||||||
|
|
||||||
This script is experimetal. Use with caution.
|
|
||||||
|
|
||||||
|
|
||||||
IMPLEMENTATION NOTES
|
|
||||||
|
|
||||||
- use the network manager present to add 2 bridges (wan + lan)
|
|
||||||
|
|
||||||
|
|
||||||
EOM
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
# dependency management
|
|
||||||
|
|
||||||
_is_service_used(){
|
|
||||||
service=$1
|
|
||||||
sudo systemctl list-unit-files $service || return 1
|
|
||||||
sudo systemctl status --no-pager $service || return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Implement functions
|
|
||||||
|
|
||||||
_list_bridges(){
|
|
||||||
|
|
||||||
ip -o link show type bridge | awk '{print $2}' | sed 's/://g'
|
|
||||||
}
|
|
||||||
|
|
||||||
_is_a_bridge(){
|
|
||||||
bridge=$1
|
|
||||||
matched=$(_list_bridges | grep "$bridge")
|
|
||||||
[ "$matched" = "$bridge" ] || return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
_bridge_is_up(){
|
|
||||||
_fatal Not implemented
|
|
||||||
}
|
|
||||||
|
|
||||||
_rename_nmcli_profile(){
|
|
||||||
device=$1
|
|
||||||
profile=$(nmcli -t -f DEVICE,UUID c show --active | grep "^$device:" | cut -d':' -f2)
|
|
||||||
[ "$profile" != "" ] || _fatal Failed to find nmcli profile
|
|
||||||
|
|
||||||
sudo nmcli con mod "$profile" con-name "$device"
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
_create_a_bridge_using_networkmanager(){
|
|
||||||
bridge=$1
|
|
||||||
|
|
||||||
profile=$(nmcli -t -f DEVICE,UUID c show --active | grep "^$PRIMARY_INTERFACE:" | cut -d':' -f2)
|
|
||||||
|
|
||||||
nmcli conn delete "$profile"
|
|
||||||
|
|
||||||
nmcli conn add type bridge ifname $bridge con-name $bridge || _fatal Fail to create a bridge using nmcli
|
|
||||||
|
|
||||||
|
|
||||||
nmcli con add type bridge-slave ifname $PRIMARY_INTERFACE master $bridge || _fatal Fail to create a slave-interface using nmcli
|
|
||||||
|
|
||||||
nmcli con up $bridge || _fatal Fail to set interface up using nmcli
|
|
||||||
|
|
||||||
sudo systemctl restart NetworkManager.service
|
|
||||||
# todo: use a check loop until connection with a timeout
|
|
||||||
#sleep 10
|
|
||||||
#ping nationtech.io | _fatal Internet connection lost
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
_delete_a_bridge_using_networkmanager(){
|
|
||||||
device=$1
|
|
||||||
|
|
||||||
nmcli conn delete bridge-slave-$PRIMARY_INTERFACE
|
|
||||||
nmcli conn delete $device
|
|
||||||
|
|
||||||
nmcli con add type ethernet ifname $PRIMARY_INTERFACE con-name $PRIMARY_INTERFACE autoconnect yes ipv4.method auto ipv6.method ignore
|
|
||||||
|
|
||||||
nmcli conn up "$PRIMARY_INTERFACE"
|
|
||||||
sudo systemctl restart NetworkManager.service
|
|
||||||
|
|
||||||
# todo: use a check loop until connection with a timeout
|
|
||||||
#sleep 10
|
|
||||||
#ping nationtech.io | _fatal Internet connection lost
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
_create_a_bridge(){
|
|
||||||
|
|
||||||
bridge=$1
|
|
||||||
|
|
||||||
[ $USE_NETWORKMANAGER = 0 ] | _fatal "Only NetworkManager is implemented"
|
|
||||||
|
|
||||||
_create_a_bridge_using_networkmanager $bridge
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
_setup_a_bridge(){
|
|
||||||
|
|
||||||
$bridge
|
|
||||||
|
|
||||||
bridge_exist=1
|
|
||||||
bridge_is_up=1
|
|
||||||
bridge_has_ip=1
|
|
||||||
bridge_has_route=1
|
|
||||||
bridge_is_working=1
|
|
||||||
|
|
||||||
_is_a_bridge $bridge && bridge_exists=0 || _create_a_bridge $bridge || _fatal Fail to create a bridge
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
_get_networkmanager_profile_from_device(){
|
|
||||||
|
|
||||||
device=$1
|
|
||||||
|
|
||||||
profile=$(nmcli -t -f DEVICE,NAME c show --active | grep "^$device:" | cut -d':' -f2)
|
|
||||||
|
|
||||||
[ "$profile" != "" ] || _fatal Fail to retreive nmcli profile
|
|
||||||
|
|
||||||
echo "$profile"
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
_find_primary_interface(){
|
|
||||||
PRIMARY_INTERFACE=$(ip route | grep '^default' | sed 's/ dev /!/g' | cut -d'!' -f 2 | awk '{ print $1 }' )
|
|
||||||
[ "$PRIMARY_INTERFACE" != "" ] || _fatal Fail to find the primary interface
|
|
||||||
}
|
|
||||||
|
|
||||||
_find_used_network_manager(){
|
|
||||||
|
|
||||||
_is_service_used NetworkManager.service && USE_NETWORKMANAGER=0 || USE_NETWORKMANAGER=1
|
|
||||||
_is_service_used systemd-networkd.service && USE_SYSTEMD_NETWORKD=0 || USE_SYSTEMD_NETWORKD=1
|
|
||||||
_is_service_used dhcpd.service && USE_DHCPD=0 || USE_DHCPD=1
|
|
||||||
|
|
||||||
USE_MANUAL=0 && [ $USE_NETWORKMANAGER = 0 ] || [ $USE_SYSTEMD_NETWORKD = 0 ] || [ $USE_DHCPD = 0 ] || USE_MANUAL=0
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
_connect(){
|
|
||||||
|
|
||||||
_find_used_network_manager
|
|
||||||
_find_primary_interface
|
|
||||||
|
|
||||||
_setup_a_bridge $_HVE_WAN_BRIDGE
|
|
||||||
_setup_a_bridge $_HVE_LAN_BRIDGE
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
case "${1:-}" in
|
|
||||||
|
|
||||||
"")
|
|
||||||
_short_help
|
|
||||||
;;
|
|
||||||
-h|--help)
|
|
||||||
_short_help
|
|
||||||
_extra_help
|
|
||||||
;;
|
|
||||||
connect)
|
|
||||||
_connect "${@:2}"
|
|
||||||
;;
|
|
||||||
disconnect)
|
|
||||||
_disconnect "${@:2}"
|
|
||||||
;;
|
|
||||||
|
|
||||||
dev)
|
|
||||||
|
|
||||||
"${@:2}"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
_warn "Unknown COMMAND '$1'"
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
[ "$0" != "${BASH_SOURCE}" ] || harmony-ve-network "${@}"
|
|
||||||
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
|
|
||||||
#! /bin/bash
|
|
||||||
|
|
||||||
harmony-ve-opnsense-img()(
|
|
||||||
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
[ "${1:-}" != "-d" ] || { set -x ; shift ; }
|
|
||||||
trap '[ "$?" = "0" ] || >&2 echo ABNORMAL TERMINATION' EXIT
|
|
||||||
|
|
||||||
SCRIPTS_DIR=$(readlink -f "$(dirname "${BASH_SOURCE}")")
|
|
||||||
. "${SCRIPTS_DIR}/common"
|
|
||||||
. "${SCRIPTS_DIR}/default-env-var"
|
|
||||||
|
|
||||||
export PATH=$SCRIPTS_DIR:$PATH
|
|
||||||
|
|
||||||
_short_help(){
|
|
||||||
|
|
||||||
cat <<-EOM
|
|
||||||
|
|
||||||
NAME
|
|
||||||
|
|
||||||
harmony-ve-opnsense-img
|
|
||||||
|
|
||||||
DESCRIPTION
|
|
||||||
|
|
||||||
Manage opnsense images needed by Harmony Virtual Execution Environment
|
|
||||||
SYNOPSYS
|
|
||||||
|
|
||||||
harmony-vee-opnsense-img [GLOBAL_OPTIONS] COMMAND [OPTIONS]
|
|
||||||
|
|
||||||
harmony-ve-opnsense-img list
|
|
||||||
harmony-ve-opnsense-img init NAME VERSION
|
|
||||||
harmony-ve-opnsense-img start NAME
|
|
||||||
harmony-ve-opnsense-img update NAME
|
|
||||||
harmony-ve-opnsense-img delete [NAME]
|
|
||||||
|
|
||||||
|
|
||||||
EOM
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
_extra_help(){
|
|
||||||
|
|
||||||
cat <<-EOM
|
|
||||||
|
|
||||||
GLOBAL_OPTIONS
|
|
||||||
|
|
||||||
-d Debug mode.
|
|
||||||
|
|
||||||
WARNINGS
|
|
||||||
|
|
||||||
This script is experimetal. Use with caution.
|
|
||||||
EOM
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# assertions
|
|
||||||
|
|
||||||
_assert_image_do_not_exists(){
|
|
||||||
name=$1
|
|
||||||
[ ! -d "$_HVE_OPNSENSE_IMG/$name" ] || _fatal "An image '$name' already exists"
|
|
||||||
}
|
|
||||||
_assert_image_exists(){
|
|
||||||
name=$1
|
|
||||||
[ -d "$_HVE_OPNSENSE_IMG/$name" ] || _fatal "Image '$name' do not exists"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# Implement functions
|
|
||||||
_init(){
|
|
||||||
name=$1
|
|
||||||
version=${2}
|
|
||||||
|
|
||||||
_assert_image_do_not_exists $name
|
|
||||||
mkdir -p "${_HVE_OPNSENSE_IMG}/$name"
|
|
||||||
|
|
||||||
harmony-ve opnsense-img-src download $version
|
|
||||||
|
|
||||||
sudo qemu-img convert -f raw -O qcow2 "$_HVE_OPNSENSE_SRC_IMG/OPNsense-${version}-nano-amd64.img" "/var/lib/libvirt/images/opnsense-$name.qcow2"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
cat <<-EOM > "$_HVE_OPNSENSE_IMG/$name/$name.sh"
|
|
||||||
|
|
||||||
virt-install \
|
|
||||||
--name $name \
|
|
||||||
--os-variant freebsd14.0 \
|
|
||||||
--vcpus=2,sockets=1,cores=2,threads=1 \
|
|
||||||
--memory 4096 \
|
|
||||||
--disk path="/var/lib/libvirt/images/opnsense-$name.qcow2" \
|
|
||||||
--network bridge=${_HVE_WAN_BRIDGE},model=virtio \
|
|
||||||
--network bridge=${_HVE_LAN_BRIDGE},model=virtio \
|
|
||||||
--graphics none \
|
|
||||||
--console pty,target_type=serial \
|
|
||||||
--import \
|
|
||||||
--autostart
|
|
||||||
EOM
|
|
||||||
|
|
||||||
chmod +x "$_HVE_OPNSENSE_IMG/$name/$name.sh"
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
_start(){
|
|
||||||
name=$1
|
|
||||||
_assert_image_exists $name
|
|
||||||
|
|
||||||
"$_HVE_OPNSENSE_IMG/$name/$name.sh"
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
case "${1:-}" in
|
|
||||||
|
|
||||||
"")
|
|
||||||
_short_help
|
|
||||||
;;
|
|
||||||
-h|--help)
|
|
||||||
_short_help
|
|
||||||
_extra_help
|
|
||||||
;;
|
|
||||||
# Commands entrypoints
|
|
||||||
init)
|
|
||||||
_init "${@:2}"
|
|
||||||
;;
|
|
||||||
start)
|
|
||||||
_start "${@:2}"
|
|
||||||
;;
|
|
||||||
delete)
|
|
||||||
rm -r ${_HVE_OPNSENSE_IMG}/"$2"
|
|
||||||
;;
|
|
||||||
ls|list)
|
|
||||||
ls ${_HVE_OPNSENSE_IMG} | cat
|
|
||||||
;;
|
|
||||||
show)
|
|
||||||
ls ${_HVE_OPNSENSE_IMG}/"$2" | cat
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
_warn "Unknown COMMAND '$1'"
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
[ "$0" != "${BASH_SOURCE}" ] || harmony-ve-opnsense-img "${@}"
|
|
||||||
|
|
||||||
@@ -1,310 +0,0 @@
|
|||||||
|
|
||||||
#! /bin/bash
|
|
||||||
|
|
||||||
harmony-ve-opnsense-img-src()(
|
|
||||||
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
[ "${1:-}" != "-d" ] || { set -x ; shift ; }
|
|
||||||
trap '[ "$?" = "0" ] || >&2 echo ABNORMAL TERMINATION' EXIT
|
|
||||||
|
|
||||||
SCRIPTS_DIR=$(readlink -f "$(dirname "${BASH_SOURCE}")")
|
|
||||||
. "${SCRIPTS_DIR}/common"
|
|
||||||
. "${SCRIPTS_DIR}/default-env-var"
|
|
||||||
|
|
||||||
|
|
||||||
_short_help(){
|
|
||||||
|
|
||||||
cat <<-EOM
|
|
||||||
|
|
||||||
NAME
|
|
||||||
|
|
||||||
harmony-ve-opnsense-img-src
|
|
||||||
|
|
||||||
DESCRIPTION
|
|
||||||
|
|
||||||
Manage opnsense source images needed by Harmony Virtual Execution Environment
|
|
||||||
|
|
||||||
SYNOPSYS
|
|
||||||
|
|
||||||
harmony-vee-opnsense-img-src [GLOBAL_OPTIONS] COMMAND [OPTIONS]
|
|
||||||
|
|
||||||
harmony-vee-opnsense-img-src list [--remote]
|
|
||||||
harmony-vee-opnsense-img-src download [VERSION]
|
|
||||||
harmony-vee-opnsense-img-src check [VERSION]
|
|
||||||
harmony-vee-opnsense-img-src delete [VERSION]
|
|
||||||
|
|
||||||
EOM
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
_extra_help(){
|
|
||||||
|
|
||||||
cat <<-EOM
|
|
||||||
|
|
||||||
GLOBAL_OPTIONS
|
|
||||||
|
|
||||||
-d Debug mode.
|
|
||||||
|
|
||||||
WARNINGS
|
|
||||||
|
|
||||||
This script is experimetal. Use with caution.
|
|
||||||
|
|
||||||
DETAILS
|
|
||||||
|
|
||||||
- for 'list', show local images available
|
|
||||||
- for 'list --remote', show available upstream images
|
|
||||||
- for 'download', when no VERSION is specified, use the latest
|
|
||||||
- for 'delete', when no VERSION is specified, delete all image
|
|
||||||
- use the 'nano' flavor
|
|
||||||
EOM
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# Implement functions
|
|
||||||
_parse_version_from_image_file_string(){
|
|
||||||
#https://pkg.opnsense.org/releases/25.7/OPNsense-25.7-nano-amd64.img.bz2
|
|
||||||
echo "$1" | cut -d '/' -f 5
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
_list_local_images(){
|
|
||||||
ls "${_HVE_OPNSENSE_SRC_IMG}" | grep "OPNsense-" | grep "\-nano\-amd64\.img" | cut -d'-' -f 2 | sort -u -r
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
_list_remote_images(){
|
|
||||||
curl -L -s "${_HVE_OPNSENSE_URL}" | sed 's/</\n</g' | grep href | grep 2 | cut -d'>' -f 2 | cut -d '/' -f 1 | sort -r
|
|
||||||
}
|
|
||||||
|
|
||||||
_latest_version(){
|
|
||||||
_list_remote_images | head -n 1
|
|
||||||
}
|
|
||||||
|
|
||||||
_is_downloaded(){
|
|
||||||
version=$1
|
|
||||||
name="${_HVE_OPNSENSE_SRC_IMG}/OPNsense-${version}-nano-amd64.img"
|
|
||||||
[ -f "$name" ] && return 0 || return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
_is_valid_version(){
|
|
||||||
version=${1}
|
|
||||||
matched_version=$(_list_remote_images | grep "$version")
|
|
||||||
[ "$matched_version" != "" ] && return 0 || return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
_download_img(){
|
|
||||||
version=$1
|
|
||||||
|
|
||||||
_download_crypto_files $version
|
|
||||||
|
|
||||||
|
|
||||||
name="OPNsense-${version}-nano-amd64.img"
|
|
||||||
compressed_name=$name.bz2
|
|
||||||
|
|
||||||
_is_downloaded $version && {
|
|
||||||
_warn "Image '$name' is already downloaded"
|
|
||||||
} || {
|
|
||||||
url=$_HVE_OPNSENSE_URL/$version/$compressed_name
|
|
||||||
>&2 echo DOWNLOAD $url
|
|
||||||
wget -q -c "${url}"
|
|
||||||
|
|
||||||
_verify_image_checksum $version
|
|
||||||
|
|
||||||
>&2 echo DECOMPRESS $url
|
|
||||||
bzip2 -d $compressed_name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
_compare_files_checksum(){
|
|
||||||
|
|
||||||
file1=$1
|
|
||||||
file2=$2
|
|
||||||
|
|
||||||
sha256_1=$(openssl sha256 $file1 | cut -d" " -f2)
|
|
||||||
sha256_2=$(openssl sha256 $file2 | cut -d" " -f2)
|
|
||||||
|
|
||||||
[ "$sha256_1" = "$sha256_2" ] || return 1
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
_download_crypto_files(){
|
|
||||||
# see: https://docs.opnsense.org/manual/install.html#download-and-verification
|
|
||||||
|
|
||||||
version=$1
|
|
||||||
|
|
||||||
# download multiple pubkeys from different server
|
|
||||||
pubkey="OPNsense-${version}.pub"
|
|
||||||
|
|
||||||
rm -f $pubkey $pubkey.sig $pubkey.alt1 $pubkey.alt2
|
|
||||||
|
|
||||||
url=$_HVE_OPNSENSE_URL/$version/$pubkey
|
|
||||||
wget -q -c "${url}"
|
|
||||||
|
|
||||||
# failing:
|
|
||||||
wget -q -c "${url}.sig"
|
|
||||||
rm -f /tmp/file.sig
|
|
||||||
openssl base64 -d -in $pubkey.sig -out /tmp/file.sig
|
|
||||||
openssl dgst -sha256 -verify $pubkey -signature /tmp/file.sig $pubkey || _fatal "Can't verify the signature of the public key"
|
|
||||||
|
|
||||||
url_alt1=$_HVE_OPNSENSE_URL_ALT1/$version/$pubkey
|
|
||||||
wget -q -c -O "$pubkey.alt1" "${url_alt1}"
|
|
||||||
|
|
||||||
url_alt2=$_HVE_OPNSENSE_URL_ALT2/$version/$pubkey
|
|
||||||
wget -q -c -O "$pubkey.alt2" "${url_alt2}"
|
|
||||||
|
|
||||||
_compare_files_checksum $pubkey $pubkey.alt1 || _fatal "Fail to compare pubkeys" ;
|
|
||||||
|
|
||||||
_compare_files_checksum $pubkey $pubkey.alt2 || _fatal "Fail to compare pubkeys" ;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
img_sig="OPNsense-${version}-nano-amd64.img.sig"
|
|
||||||
|
|
||||||
sha256_name="OPNsense-${version}-checksums-amd64.sha256"
|
|
||||||
sha256_sig=$sha256_name.sig
|
|
||||||
|
|
||||||
[ ! -f "$img_sig" ] || rm $img_sig
|
|
||||||
[ ! -f "$sha256_name" ] || rm $sha256_name
|
|
||||||
[ ! -f "$sha256_sig" ] || rm $sha256_sig
|
|
||||||
|
|
||||||
|
|
||||||
for file in $img_sig $sha256_name $sha256_sig;
|
|
||||||
do
|
|
||||||
url=$_HVE_OPNSENSE_URL/$version/$file
|
|
||||||
wget -q -c "${url}"
|
|
||||||
done
|
|
||||||
|
|
||||||
|
|
||||||
rm -f /tmp/file.sig
|
|
||||||
openssl base64 -d -in $sha256_sig -out /tmp/file.sig
|
|
||||||
openssl dgst -sha256 -verify $pubkey -signature /tmp/file.sig $sha256_name || _fatal "Can't verify the signature of the checksum file"
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
_download(){
|
|
||||||
|
|
||||||
version=${1:-}
|
|
||||||
[ "${version:-}" != "" ] || _fatal "Must pass a VERSION for downloading"
|
|
||||||
|
|
||||||
_is_valid_version $version || _fatal "'$version' is not a valid version number"
|
|
||||||
|
|
||||||
|
|
||||||
_download_img ${version}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
_verify_image_checksum(){
|
|
||||||
|
|
||||||
version=$1
|
|
||||||
name="OPNsense-${version}-nano-amd64.img.bz2"
|
|
||||||
sha256_file="OPNsense-${version}-checksums-amd64.sha256"
|
|
||||||
|
|
||||||
sha256=$(cat $sha256_file | grep "$name" | cut -d'=' -f 2 | tr -s [:space:])
|
|
||||||
|
|
||||||
echo "$sha256 $name" | sha256sum -c || _fatal "Checksum failed for '$name'"
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
_verify_image_signature(){
|
|
||||||
version=$1
|
|
||||||
|
|
||||||
# download multiple pubkeys from different server
|
|
||||||
pubkey="OPNsense-${version}.pub"
|
|
||||||
img_name="OPNsense-${version}-nano-amd64.img"
|
|
||||||
img_sig="${img_name}.sig"
|
|
||||||
|
|
||||||
|
|
||||||
rm -f /tmp/file.sig
|
|
||||||
openssl base64 -d -in $img_sig -out /tmp/file.sig
|
|
||||||
openssl dgst -sha256 -verify $pubkey -signature /tmp/file.sig $img_name || _fatal "Can't verify image signature"
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
_check(){
|
|
||||||
version=${1:-}
|
|
||||||
if [ "${version:-}" = "" ] ; then
|
|
||||||
for version in $(_list_local_images);
|
|
||||||
do
|
|
||||||
>&2 echo check $version
|
|
||||||
_download_crypto_files $version
|
|
||||||
_verify_image_signature $version
|
|
||||||
done
|
|
||||||
else
|
|
||||||
_download_crypto_files $version
|
|
||||||
_verify_image_signature $version
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
_delete(){
|
|
||||||
version=${1:-}
|
|
||||||
if [ -z "${version:-1}" ]; then
|
|
||||||
_clear
|
|
||||||
rm -f *.img
|
|
||||||
else
|
|
||||||
rm -f *$version*.img
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
_clear(){
|
|
||||||
rm -f *.pub *.sig *.bz2 *.alt1 *.alt2 *.sha256
|
|
||||||
}
|
|
||||||
|
|
||||||
case "${1:-}" in
|
|
||||||
|
|
||||||
"")
|
|
||||||
_short_help
|
|
||||||
;;
|
|
||||||
-h|--help)
|
|
||||||
_short_help
|
|
||||||
_extra_help
|
|
||||||
;;
|
|
||||||
ls|list)
|
|
||||||
|
|
||||||
if [ "${2:-}" == "" ]; then
|
|
||||||
_list_local_images
|
|
||||||
elif [ "${2:-}" == "--remote" ]; then
|
|
||||||
_list_remote_images
|
|
||||||
else
|
|
||||||
_warn "Unknown option '$2'"
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
download)
|
|
||||||
pushd "${_HVE_OPNSENSE_SRC_IMG}"
|
|
||||||
_download "${2:-"$(_latest_version)"}"
|
|
||||||
popd
|
|
||||||
;;
|
|
||||||
delete)
|
|
||||||
pushd "${_HVE_OPNSENSE_SRC_IMG}"
|
|
||||||
_delete "${@:2}"
|
|
||||||
popd
|
|
||||||
;;
|
|
||||||
check)
|
|
||||||
pushd "${_HVE_OPNSENSE_SRC_IMG}"
|
|
||||||
_check "${@:2}"
|
|
||||||
popd
|
|
||||||
;;
|
|
||||||
show)
|
|
||||||
ls $_HVE_OPNSENSE_SRC_IMG | cat
|
|
||||||
;;
|
|
||||||
clear)
|
|
||||||
pushd "${_HVE_OPNSENSE_SRC_IMG}"
|
|
||||||
_clear "${@:2}"
|
|
||||||
popd
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
_warn "Unknown COMMAND '$1'"
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
[ "$0" != "${BASH_SOURCE}" ] || harmony-ve-opnsense-img-src "${@}"
|
|
||||||
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
|
|
||||||
#! /bin/bash
|
|
||||||
|
|
||||||
harmony-ve-vm()(
|
|
||||||
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
[ "${1:-}" != "-d" ] || { set -x ; shift ; }
|
|
||||||
trap '[ "$?" = "0" ] || >&2 echo ABNORMAL TERMINATION' EXIT
|
|
||||||
|
|
||||||
BASE_DIR=$(readlink -f "$(dirname "${BASH_SOURCE}")/..")
|
|
||||||
|
|
||||||
_short_help(){
|
|
||||||
|
|
||||||
cat <<-EOM
|
|
||||||
|
|
||||||
NAME
|
|
||||||
|
|
||||||
harmony-ve-mv
|
|
||||||
|
|
||||||
DESCRIPTION
|
|
||||||
|
|
||||||
Manage virtalized hosts (VM) dependencies by Harmony Virtual Execution Environment
|
|
||||||
|
|
||||||
SYNOPSYS
|
|
||||||
|
|
||||||
harmony-ve-vm [GLOBAL_OPTIONS] COMMAND [OPTIONS]
|
|
||||||
|
|
||||||
harmony-ve-vm list
|
|
||||||
harmony-ve-vm create
|
|
||||||
harmony-ve-vm start
|
|
||||||
harmony-ve-vm stop
|
|
||||||
harmony-ve-vm login
|
|
||||||
|
|
||||||
EOM
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
_extra_help(){
|
|
||||||
|
|
||||||
cat <<-EOM
|
|
||||||
|
|
||||||
GLOBAL_OPTIONS
|
|
||||||
|
|
||||||
-d Debug mode.
|
|
||||||
|
|
||||||
WARNINGS
|
|
||||||
|
|
||||||
This script is experimetal. Use with caution.
|
|
||||||
EOM
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# Implement functions
|
|
||||||
|
|
||||||
case "${1:-}" in
|
|
||||||
|
|
||||||
"")
|
|
||||||
_short_help
|
|
||||||
;;
|
|
||||||
-h|--help)
|
|
||||||
_short_help
|
|
||||||
_extra_help
|
|
||||||
;;
|
|
||||||
# Commands entrypoints
|
|
||||||
*)
|
|
||||||
_warn "Unknown COMMAND '$1'"
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
[ "$0" != "${BASH_SOURCE}" ] || harmony-ve-vm "${@}"
|
|
||||||
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
#! /bin/bash
|
|
||||||
|
|
||||||
learn-harmony()(
|
|
||||||
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
[ "${1:-}" != "-d" ] || { set -x ; shift ; }
|
|
||||||
trap '[ "$?" = "0" ] || >&2 echo ABNORMAL TERMINATION' EXIT
|
|
||||||
|
|
||||||
BASE_DIR=$(readlink -f "$(dirname "${BASH_SOURCE}")/..")
|
|
||||||
SCRIPTS_DIR=$(readlink -f "$(dirname "${BASH_SOURCE}")")
|
|
||||||
. "${SCRIPTS_DIR}/common"
|
|
||||||
export PATH=$SCRIPTS_DIR:$PATH
|
|
||||||
|
|
||||||
_short_help(){
|
|
||||||
|
|
||||||
cat <<-EOM
|
|
||||||
|
|
||||||
NAME
|
|
||||||
learn-harmony -- Harmony Learning Tool prototype
|
|
||||||
($(basename ${BASE_DIR}) example)
|
|
||||||
|
|
||||||
SYNOPSYS
|
|
||||||
|
|
||||||
learn-harmony [GLOBAL_OPTIONS] COMMAND [OPTIONS]
|
|
||||||
|
|
||||||
learn-harmony list # List learning steps
|
|
||||||
learn-harmony show STEP # Show instruction of step STEP
|
|
||||||
learn-harmony check [STEP] # Verify that your ready to begin step STEP+1
|
|
||||||
|
|
||||||
EOM
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
_extra_help(){
|
|
||||||
|
|
||||||
cat <<-EOM
|
|
||||||
|
|
||||||
GLOBAL_OPTIONS
|
|
||||||
|
|
||||||
-d Debug mode.
|
|
||||||
|
|
||||||
WARNINGS
|
|
||||||
|
|
||||||
This script is experimetal. Use with caution.
|
|
||||||
EOM
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
case "${1:-}" in
|
|
||||||
|
|
||||||
-h|--help|"")
|
|
||||||
_short_help
|
|
||||||
_extra_help
|
|
||||||
;;
|
|
||||||
ls|list)
|
|
||||||
echo "not implemented"
|
|
||||||
;;
|
|
||||||
show)
|
|
||||||
echo "not implemented"
|
|
||||||
;;
|
|
||||||
verify)
|
|
||||||
echo "not implemented"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
_warn "Unknown COMMAND '$1'"
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
[ "$0" != "${BASH_SOURCE}" ] || learn-harmony "${@}"
|
|
||||||
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
#! /bin/bash
|
|
||||||
|
|
||||||
|
|
||||||
export PATH=$(readlink -f "$(dirname "${BASH_SOURCE}")"):"${PATH}"
|
|
||||||
@@ -1,70 +1,134 @@
|
|||||||
use harmony::{
|
use std::{
|
||||||
config::secret::OPNSenseFirewallCredentials,
|
net::{IpAddr, Ipv4Addr},
|
||||||
infra::opnsense::OPNSenseFirewall,
|
sync::Arc,
|
||||||
inventory::Inventory,
|
|
||||||
modules::{dhcp::DhcpScore, opnsense::OPNsenseShellCommandScore},
|
|
||||||
topology::LogicalHost,
|
|
||||||
};
|
};
|
||||||
use harmony_macros::{ip, ipv4};
|
|
||||||
|
use brocade::BrocadeOptions;
|
||||||
|
use cidr::Ipv4Cidr;
|
||||||
|
use harmony::{
|
||||||
|
hardware::{HostCategory, Location, PhysicalHost, SwitchGroup},
|
||||||
|
infra::{brocade::BrocadeSwitchClient, opnsense::OPNSenseManagementInterface},
|
||||||
|
inventory::Inventory,
|
||||||
|
modules::{
|
||||||
|
dummy::{ErrorScore, PanicScore, SuccessScore},
|
||||||
|
http::StaticFilesHttpScore,
|
||||||
|
okd::{dhcp::OKDDhcpScore, dns::OKDDnsScore, load_balancer::OKDLoadBalancerScore},
|
||||||
|
opnsense::OPNsenseShellCommandScore,
|
||||||
|
tftp::TftpScore,
|
||||||
|
},
|
||||||
|
topology::{LogicalHost, UnmanagedRouter},
|
||||||
|
};
|
||||||
|
use harmony_macros::{ip, mac_address};
|
||||||
use harmony_secret::{Secret, SecretManager};
|
use harmony_secret::{Secret, SecretManager};
|
||||||
|
use harmony_types::net::Url;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let firewall = LogicalHost {
|
let firewall = harmony::topology::LogicalHost {
|
||||||
ip: ip!("192.168.1.1"),
|
ip: ip!("192.168.5.229"),
|
||||||
name: String::from("opnsense-1"),
|
name: String::from("opnsense-1"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let opnsense_auth = SecretManager::get_or_prompt::<OPNSenseFirewallCredentials>()
|
let switch_auth = SecretManager::get_or_prompt::<BrocadeSwitchAuth>()
|
||||||
.await
|
.await
|
||||||
.expect("Failed to get credentials");
|
.expect("Failed to get credentials");
|
||||||
|
|
||||||
let opnsense = OPNSenseFirewall::new(
|
let switches: Vec<IpAddr> = vec![ip!("192.168.5.101")]; // TODO: Adjust me
|
||||||
firewall,
|
let brocade_options = Some(BrocadeOptions {
|
||||||
None,
|
dry_run: *harmony::config::DRY_RUN,
|
||||||
&opnsense_auth.username,
|
..Default::default()
|
||||||
&opnsense_auth.password,
|
});
|
||||||
|
let switch_client = BrocadeSwitchClient::init(
|
||||||
|
&switches,
|
||||||
|
&switch_auth.username,
|
||||||
|
&switch_auth.password,
|
||||||
|
brocade_options,
|
||||||
)
|
)
|
||||||
.await;
|
.await
|
||||||
|
.expect("Failed to connect to switch");
|
||||||
|
|
||||||
let dhcp_score = DhcpScore {
|
let switch_client = Arc::new(switch_client);
|
||||||
dhcp_range: (
|
|
||||||
ipv4!("192.168.1.100").into(),
|
let opnsense = Arc::new(
|
||||||
ipv4!("192.168.1.150").into(),
|
harmony::infra::opnsense::OPNSenseFirewall::new(firewall, None, "root", "opnsense").await,
|
||||||
),
|
);
|
||||||
host_binding: vec![],
|
let lan_subnet = Ipv4Addr::new(10, 100, 8, 0);
|
||||||
next_server: None,
|
let gateway_ipv4 = Ipv4Addr::new(10, 100, 8, 1);
|
||||||
boot_filename: None,
|
let gateway_ip = IpAddr::V4(gateway_ipv4);
|
||||||
filename: None,
|
let topology = harmony::topology::HAClusterTopology {
|
||||||
filename64: None,
|
kubeconfig: None,
|
||||||
filenameipxe: Some("filename.ipxe".to_string()),
|
domain_name: "demo.harmony.mcd".to_string(),
|
||||||
domain: None,
|
router: Arc::new(UnmanagedRouter::new(
|
||||||
|
gateway_ip,
|
||||||
|
Ipv4Cidr::new(lan_subnet, 24).unwrap(),
|
||||||
|
)),
|
||||||
|
load_balancer: opnsense.clone(),
|
||||||
|
firewall: opnsense.clone(),
|
||||||
|
tftp_server: opnsense.clone(),
|
||||||
|
http_server: opnsense.clone(),
|
||||||
|
dhcp_server: opnsense.clone(),
|
||||||
|
dns_server: opnsense.clone(),
|
||||||
|
control_plane: vec![LogicalHost {
|
||||||
|
ip: ip!("10.100.8.20"),
|
||||||
|
name: "cp0".to_string(),
|
||||||
|
}],
|
||||||
|
bootstrap_host: LogicalHost {
|
||||||
|
ip: ip!("10.100.8.20"),
|
||||||
|
name: "cp0".to_string(),
|
||||||
|
},
|
||||||
|
workers: vec![],
|
||||||
|
switch_client: switch_client.clone(),
|
||||||
};
|
};
|
||||||
// let dns_score = OKDDnsScore::new(&topology);
|
|
||||||
// let load_balancer_score = OKDLoadBalancerScore::new(&topology);
|
|
||||||
//
|
|
||||||
// let tftp_score = TftpScore::new(Url::LocalFolder("./data/watchguard/tftpboot".to_string()));
|
|
||||||
// let http_score = StaticFilesHttpScore {
|
|
||||||
// folder_to_serve: Some(Url::LocalFolder(
|
|
||||||
// "./data/watchguard/pxe-http-files".to_string(),
|
|
||||||
// )),
|
|
||||||
// files: vec![],
|
|
||||||
// remote_path: None,
|
|
||||||
// };
|
|
||||||
let opnsense_config = opnsense.get_opnsense_config();
|
|
||||||
|
|
||||||
harmony_cli::run(
|
let inventory = Inventory {
|
||||||
Inventory::autoload(),
|
location: Location::new(
|
||||||
opnsense,
|
"232 des Éperviers, Wendake, Qc, G0A 4V0".to_string(),
|
||||||
vec![
|
"wk".to_string(),
|
||||||
Box::new(dhcp_score),
|
),
|
||||||
Box::new(OPNsenseShellCommandScore {
|
switch: SwitchGroup::from([]),
|
||||||
opnsense: opnsense_config,
|
firewall_mgmt: Box::new(OPNSenseManagementInterface::new()),
|
||||||
command: "touch /tmp/helloharmonytouching_2".to_string(),
|
storage_host: vec![],
|
||||||
}),
|
worker_host: vec![],
|
||||||
|
control_plane_host: vec![
|
||||||
|
PhysicalHost::empty(HostCategory::Server)
|
||||||
|
.mac_address(mac_address!("08:00:27:62:EC:C3")),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO regroup smaller scores in a larger one such as this
|
||||||
|
// let okd_boostrap_preparation();
|
||||||
|
|
||||||
|
let dhcp_score = OKDDhcpScore::new(&topology, &inventory);
|
||||||
|
let dns_score = OKDDnsScore::new(&topology);
|
||||||
|
let load_balancer_score = OKDLoadBalancerScore::new(&topology);
|
||||||
|
|
||||||
|
let tftp_score = TftpScore::new(Url::LocalFolder("./data/watchguard/tftpboot".to_string()));
|
||||||
|
let http_score = StaticFilesHttpScore {
|
||||||
|
folder_to_serve: Some(Url::LocalFolder(
|
||||||
|
"./data/watchguard/pxe-http-files".to_string(),
|
||||||
|
)),
|
||||||
|
files: vec![],
|
||||||
|
remote_path: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
harmony_tui::run(
|
||||||
|
inventory,
|
||||||
|
topology,
|
||||||
|
vec![
|
||||||
|
Box::new(dns_score),
|
||||||
|
Box::new(dhcp_score),
|
||||||
|
Box::new(load_balancer_score),
|
||||||
|
Box::new(tftp_score),
|
||||||
|
Box::new(http_score),
|
||||||
|
Box::new(OPNsenseShellCommandScore {
|
||||||
|
opnsense: opnsense.get_opnsense_config(),
|
||||||
|
command: "touch /tmp/helloharmonytouching".to_string(),
|
||||||
|
}),
|
||||||
|
Box::new(SuccessScore {}),
|
||||||
|
Box::new(ErrorScore {}),
|
||||||
|
Box::new(PanicScore {}),
|
||||||
],
|
],
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|||||||
@@ -67,16 +67,16 @@ impl<T: Topology> Maestro<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_topology_initialized(&self) -> bool {
|
|
||||||
self.topology_state.status == TopologyStatus::Success
|
|
||||||
|| self.topology_state.status == TopologyStatus::Noop
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn register_all(&mut self, mut scores: ScoreVec<T>) {
|
pub fn register_all(&mut self, mut scores: ScoreVec<T>) {
|
||||||
let mut score_mut = self.scores.write().expect("Should acquire lock");
|
let mut score_mut = self.scores.write().expect("Should acquire lock");
|
||||||
score_mut.append(&mut scores);
|
score_mut.append(&mut scores);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_topology_initialized(&self) -> bool {
|
||||||
|
self.topology_state.status == TopologyStatus::Success
|
||||||
|
|| self.topology_state.status == TopologyStatus::Noop
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn interpret(&self, score: Box<dyn Score<T>>) -> Result<Outcome, InterpretError> {
|
pub async fn interpret(&self, score: Box<dyn Score<T>>) -> Result<Outcome, InterpretError> {
|
||||||
if !self.is_topology_initialized() {
|
if !self.is_topology_initialized() {
|
||||||
warn!(
|
warn!(
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use kube::{
|
|||||||
Client, Config, Discovery, Error, Resource,
|
Client, Config, Discovery, Error, Resource,
|
||||||
api::{Api, AttachParams, DeleteParams, ListParams, Patch, PatchParams, ResourceExt},
|
api::{Api, AttachParams, DeleteParams, ListParams, Patch, PatchParams, ResourceExt},
|
||||||
config::{KubeConfigOptions, Kubeconfig},
|
config::{KubeConfigOptions, Kubeconfig},
|
||||||
core::ErrorResponse,
|
core::{DynamicResourceScope, ErrorResponse},
|
||||||
discovery::{ApiCapabilities, Scope},
|
discovery::{ApiCapabilities, Scope},
|
||||||
error::DiscoveryError,
|
error::DiscoveryError,
|
||||||
runtime::reflector::Lookup,
|
runtime::reflector::Lookup,
|
||||||
@@ -94,6 +94,23 @@ impl K8sClient {
|
|||||||
resource.get(name).await
|
resource.get(name).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_secret_json_value(
|
||||||
|
&self,
|
||||||
|
name: &str,
|
||||||
|
namespace: Option<&str>,
|
||||||
|
) -> Result<DynamicObject, Error> {
|
||||||
|
self.get_resource_json_value(
|
||||||
|
name,
|
||||||
|
namespace,
|
||||||
|
&GroupVersionKind {
|
||||||
|
group: "".to_string(),
|
||||||
|
version: "v1".to_string(),
|
||||||
|
kind: "Secret".to_string(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_deployment(
|
pub async fn get_deployment(
|
||||||
&self,
|
&self,
|
||||||
name: &str,
|
name: &str,
|
||||||
@@ -337,6 +354,169 @@ impl K8sClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_api_for_dynamic_object(
|
||||||
|
&self,
|
||||||
|
object: &DynamicObject,
|
||||||
|
ns: Option<&str>,
|
||||||
|
) -> Result<Api<DynamicObject>, Error> {
|
||||||
|
let api_resource = object
|
||||||
|
.types
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|t| {
|
||||||
|
let parts: Vec<&str> = t.api_version.split('/').collect();
|
||||||
|
match parts.as_slice() {
|
||||||
|
[version] => Some(ApiResource::from_gvk(&GroupVersionKind::gvk(
|
||||||
|
"", version, &t.kind,
|
||||||
|
))),
|
||||||
|
[group, version] => Some(ApiResource::from_gvk(&GroupVersionKind::gvk(
|
||||||
|
group, version, &t.kind,
|
||||||
|
))),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.ok_or_else(|| {
|
||||||
|
Error::BuildRequest(kube::core::request::Error::Validation(
|
||||||
|
"Invalid apiVersion in DynamicObject {object:#?}".to_string(),
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
match ns {
|
||||||
|
Some(ns) => Ok(Api::namespaced_with(self.client.clone(), ns, &api_resource)),
|
||||||
|
None => Ok(Api::default_namespaced_with(
|
||||||
|
self.client.clone(),
|
||||||
|
&api_resource,
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn apply_dynamic_many(
|
||||||
|
&self,
|
||||||
|
resource: &[DynamicObject],
|
||||||
|
namespace: Option<&str>,
|
||||||
|
force_conflicts: bool,
|
||||||
|
) -> Result<Vec<DynamicObject>, Error> {
|
||||||
|
let mut result = Vec::new();
|
||||||
|
for r in resource.iter() {
|
||||||
|
result.push(self.apply_dynamic(r, namespace, force_conflicts).await?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply DynamicObject resource to the cluster
|
||||||
|
pub async fn apply_dynamic(
|
||||||
|
&self,
|
||||||
|
resource: &DynamicObject,
|
||||||
|
namespace: Option<&str>,
|
||||||
|
force_conflicts: bool,
|
||||||
|
) -> Result<DynamicObject, Error> {
|
||||||
|
// Build API for this dynamic object
|
||||||
|
let api = self.get_api_for_dynamic_object(resource, namespace)?;
|
||||||
|
let name = resource
|
||||||
|
.metadata
|
||||||
|
.name
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| {
|
||||||
|
Error::BuildRequest(kube::core::request::Error::Validation(
|
||||||
|
"DynamicObject must have metadata.name".to_string(),
|
||||||
|
))
|
||||||
|
})?
|
||||||
|
.as_str();
|
||||||
|
|
||||||
|
debug!(
|
||||||
|
"Applying dynamic resource kind={:?} apiVersion={:?} name='{}' ns={:?}",
|
||||||
|
resource.types.as_ref().map(|t| &t.kind),
|
||||||
|
resource.types.as_ref().map(|t| &t.api_version),
|
||||||
|
name,
|
||||||
|
namespace
|
||||||
|
);
|
||||||
|
trace!(
|
||||||
|
"Dynamic resource payload:\n{:#}",
|
||||||
|
serde_json::to_value(resource).unwrap_or(serde_json::Value::Null)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Using same field manager as in apply()
|
||||||
|
let mut patch_params = PatchParams::apply("harmony");
|
||||||
|
patch_params.force = force_conflicts;
|
||||||
|
|
||||||
|
if *crate::config::DRY_RUN {
|
||||||
|
// Dry-run path: fetch current, show diff, and return appropriate object
|
||||||
|
match api.get(name).await {
|
||||||
|
Ok(current) => {
|
||||||
|
trace!("Received current dynamic value {current:#?}");
|
||||||
|
|
||||||
|
println!("\nPerforming dry-run for resource: '{}'", name);
|
||||||
|
|
||||||
|
// Serialize current and new, and strip status from current if present
|
||||||
|
let mut current_yaml =
|
||||||
|
serde_yaml::to_value(¤t).unwrap_or_else(|_| serde_yaml::Value::Null);
|
||||||
|
if let Some(map) = current_yaml.as_mapping_mut() {
|
||||||
|
if map.contains_key(&serde_yaml::Value::String("status".to_string())) {
|
||||||
|
let removed =
|
||||||
|
map.remove(&serde_yaml::Value::String("status".to_string()));
|
||||||
|
trace!("Removed status from current dynamic object: {:?}", removed);
|
||||||
|
} else {
|
||||||
|
trace!(
|
||||||
|
"Did not find status entry for current dynamic object {}/{}",
|
||||||
|
current.metadata.namespace.as_deref().unwrap_or(""),
|
||||||
|
current.metadata.name.as_deref().unwrap_or("")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let current_yaml = serde_yaml::to_string(¤t_yaml)
|
||||||
|
.unwrap_or_else(|_| "Failed to serialize current resource".to_string());
|
||||||
|
let new_yaml = serde_yaml::to_string(resource)
|
||||||
|
.unwrap_or_else(|_| "Failed to serialize new resource".to_string());
|
||||||
|
|
||||||
|
if current_yaml == new_yaml {
|
||||||
|
println!("No changes detected.");
|
||||||
|
return Ok(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Changes detected:");
|
||||||
|
let diff = TextDiff::from_lines(¤t_yaml, &new_yaml);
|
||||||
|
for change in diff.iter_all_changes() {
|
||||||
|
let sign = match change.tag() {
|
||||||
|
similar::ChangeTag::Delete => "-",
|
||||||
|
similar::ChangeTag::Insert => "+",
|
||||||
|
similar::ChangeTag::Equal => " ",
|
||||||
|
};
|
||||||
|
print!("{}{}", sign, change);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the incoming resource as the would-be applied state
|
||||||
|
Ok(resource.clone())
|
||||||
|
}
|
||||||
|
Err(Error::Api(ErrorResponse { code: 404, .. })) => {
|
||||||
|
println!("\nPerforming dry-run for new resource: '{}'", name);
|
||||||
|
println!(
|
||||||
|
"Resource does not exist. It would be created with the following content:"
|
||||||
|
);
|
||||||
|
let new_yaml = serde_yaml::to_string(resource)
|
||||||
|
.unwrap_or_else(|_| "Failed to serialize new resource".to_string());
|
||||||
|
for line in new_yaml.lines() {
|
||||||
|
println!("+{}", line);
|
||||||
|
}
|
||||||
|
Ok(resource.clone())
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to get dynamic resource '{}': {}", name, e);
|
||||||
|
Err(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Real apply via server-side apply
|
||||||
|
debug!("Patching (server-side apply) dynamic resource '{}'", name);
|
||||||
|
api.patch(name, &patch_params, &Patch::Apply(resource))
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("Failed to apply dynamic resource '{}': {}", name, e);
|
||||||
|
e
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Apply a resource in namespace
|
/// Apply a resource in namespace
|
||||||
///
|
///
|
||||||
/// See `kubectl apply` for more information on the expected behavior of this function
|
/// See `kubectl apply` for more information on the expected behavior of this function
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
mod ha_cluster;
|
mod ha_cluster;
|
||||||
pub mod ingress;
|
pub mod ingress;
|
||||||
pub mod opnsense;
|
|
||||||
use harmony_types::net::IpAddress;
|
use harmony_types::net::IpAddress;
|
||||||
mod host_binding;
|
mod host_binding;
|
||||||
mod http;
|
mod http;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use kube::api::DynamicObject;
|
||||||
use log::debug;
|
use log::debug;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -76,6 +77,14 @@ pub trait AlertReceiver<S: AlertSender>: std::fmt::Debug + Send + Sync {
|
|||||||
fn name(&self) -> String;
|
fn name(&self) -> String;
|
||||||
fn clone_box(&self) -> Box<dyn AlertReceiver<S>>;
|
fn clone_box(&self) -> Box<dyn AlertReceiver<S>>;
|
||||||
fn as_any(&self) -> &dyn Any;
|
fn as_any(&self) -> &dyn Any;
|
||||||
|
fn as_alertmanager_receiver(&self) -> Result<AlertManagerReceiver, String>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct AlertManagerReceiver {
|
||||||
|
pub receiver_config: serde_json::Value,
|
||||||
|
// FIXME we should not leak k8s here. DynamicObject is k8s specific
|
||||||
|
pub additional_ressources: Vec<DynamicObject>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
use async_trait::async_trait;
|
|
||||||
use log::info;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
infra::opnsense::OPNSenseFirewall,
|
|
||||||
topology::{PreparationError, PreparationOutcome, Topology},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl Topology for OPNSenseFirewall {
|
|
||||||
async fn ensure_ready(&self) -> Result<PreparationOutcome, PreparationError> {
|
|
||||||
// FIXME we should be initializing the opnsense config here instead of
|
|
||||||
// OPNSenseFirewall::new as this causes the config to be loaded too early in
|
|
||||||
// harmony initialization process
|
|
||||||
let details = "OPNSenseFirewall topology is ready".to_string();
|
|
||||||
info!("{}", details);
|
|
||||||
Ok(PreparationOutcome::Success { details })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"OPNSenseFirewall"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -14,7 +14,7 @@ use k8s_openapi::{
|
|||||||
},
|
},
|
||||||
apimachinery::pkg::util::intstr::IntOrString,
|
apimachinery::pkg::util::intstr::IntOrString,
|
||||||
};
|
};
|
||||||
use kube::Resource;
|
use kube::{api::DynamicObject, Resource};
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|||||||
182
harmony/src/infra/kube.rs
Normal file
182
harmony/src/infra/kube.rs
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
use k8s_openapi::Resource as K8sResource;
|
||||||
|
use kube::api::{ApiResource, DynamicObject, GroupVersionKind};
|
||||||
|
use kube::core::TypeMeta;
|
||||||
|
use serde::Serialize;
|
||||||
|
use serde::de::DeserializeOwned;
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
/// Convert a typed Kubernetes resource `K` into a `DynamicObject`.
|
||||||
|
///
|
||||||
|
/// Requirements:
|
||||||
|
/// - `K` must be a k8s_openapi resource (provides static GVK via `Resource`).
|
||||||
|
/// - `K` must have standard Kubernetes shape (metadata + payload fields).
|
||||||
|
///
|
||||||
|
/// Notes:
|
||||||
|
/// - We set `types` (apiVersion/kind) and copy `metadata`.
|
||||||
|
/// - We place the remaining top-level fields into `obj.data` as JSON.
|
||||||
|
/// - Scope is not encoded on the object itself; you still need the corresponding
|
||||||
|
/// `DynamicResource` (derived from K::group/version/kind) when constructing an Api.
|
||||||
|
///
|
||||||
|
/// Example usage:
|
||||||
|
/// let dyn_obj = kube_resource_to_dynamic(secret)?;
|
||||||
|
/// let api: Api<DynamicObject> = Api::namespaced_with(client, "ns", &dr);
|
||||||
|
/// api.patch(&dyn_obj.name_any(), &PatchParams::apply("mgr"), &Patch::Apply(dyn_obj)).await?;
|
||||||
|
pub fn kube_resource_to_dynamic<K>(res: &K) -> Result<DynamicObject, String>
|
||||||
|
where
|
||||||
|
K: K8sResource + Serialize + DeserializeOwned,
|
||||||
|
{
|
||||||
|
// Serialize the typed resource to JSON so we can split metadata and payload
|
||||||
|
let mut v = serde_json::to_value(res).map_err(|e| format!("Failed to serialize : {e}"))?;
|
||||||
|
let obj = v
|
||||||
|
.as_object_mut()
|
||||||
|
.ok_or_else(|| "expected object JSON".to_string())?;
|
||||||
|
|
||||||
|
// Extract and parse metadata into kube::core::ObjectMeta
|
||||||
|
let metadata_value = obj
|
||||||
|
.remove("metadata")
|
||||||
|
.ok_or_else(|| "missing metadata".to_string())?;
|
||||||
|
let metadata: kube::core::ObjectMeta = serde_json::from_value(metadata_value)
|
||||||
|
.map_err(|e| format!("Failed to deserialize : {e}"))?;
|
||||||
|
|
||||||
|
// Name is required for DynamicObject::new; prefer metadata.name
|
||||||
|
let name = metadata
|
||||||
|
.name
|
||||||
|
.clone()
|
||||||
|
.ok_or_else(|| "metadata.name is required".to_string())?;
|
||||||
|
|
||||||
|
// Remaining fields (spec/status/data/etc.) become the dynamic payload
|
||||||
|
let payload = Value::Object(obj.clone());
|
||||||
|
|
||||||
|
// Construct the DynamicObject
|
||||||
|
let mut dyn_obj = DynamicObject::new(
|
||||||
|
&name,
|
||||||
|
&ApiResource::from_gvk(&GroupVersionKind::gvk(K::GROUP, K::VERSION, K::KIND)),
|
||||||
|
);
|
||||||
|
dyn_obj.types = Some(TypeMeta {
|
||||||
|
api_version: api_version_for::<K>(),
|
||||||
|
kind: K::KIND.into(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Preserve namespace/labels/annotations/etc.
|
||||||
|
dyn_obj.metadata = metadata;
|
||||||
|
|
||||||
|
// Attach payload
|
||||||
|
dyn_obj.data = payload;
|
||||||
|
|
||||||
|
Ok(dyn_obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper: compute apiVersion string ("group/version" or "v1" for core).
|
||||||
|
fn api_version_for<K>() -> String
|
||||||
|
where
|
||||||
|
K: K8sResource,
|
||||||
|
{
|
||||||
|
let group = K::GROUP;
|
||||||
|
let version = K::VERSION;
|
||||||
|
if group.is_empty() {
|
||||||
|
version.to_string() // core/v1 => "v1"
|
||||||
|
} else {
|
||||||
|
format!("{}/{}", group, version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use k8s_openapi::api::{
|
||||||
|
apps::v1::{Deployment, DeploymentSpec},
|
||||||
|
core::v1::{PodTemplateSpec, Secret},
|
||||||
|
};
|
||||||
|
use kube::api::ObjectMeta;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn secret_to_dynamic_roundtrip() {
|
||||||
|
// Create a sample Secret resource
|
||||||
|
let mut secret = Secret {
|
||||||
|
metadata: ObjectMeta {
|
||||||
|
name: Some("my-secret".to_string()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
type_: Some("kubernetes.io/service-account-token".to_string()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Convert to DynamicResource
|
||||||
|
let dynamic: DynamicObject =
|
||||||
|
kube_resource_to_dynamic(&secret).expect("Failed to convert Secret to DynamicResource");
|
||||||
|
|
||||||
|
// Serialize both the original and dynamic resources to Value
|
||||||
|
let original_value = serde_json::to_value(&secret).expect("Failed to serialize Secret");
|
||||||
|
let dynamic_value =
|
||||||
|
serde_json::to_value(&dynamic).expect("Failed to serialize DynamicResource");
|
||||||
|
|
||||||
|
// Assert that they are identical
|
||||||
|
assert_eq!(original_value, dynamic_value);
|
||||||
|
|
||||||
|
secret.metadata.namespace = Some("false".to_string());
|
||||||
|
let modified_value = serde_json::to_value(&secret).expect("Failed to serialize Secret");
|
||||||
|
assert_ne!(modified_value, dynamic_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deployment_to_dynamic_roundtrip() {
|
||||||
|
// Create a sample Deployment with nested structures
|
||||||
|
let mut deployment = Deployment {
|
||||||
|
metadata: ObjectMeta {
|
||||||
|
name: Some("my-deployment".to_string()),
|
||||||
|
labels: Some({
|
||||||
|
let mut map = std::collections::BTreeMap::new();
|
||||||
|
map.insert("app".to_string(), "nginx".to_string());
|
||||||
|
map
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
spec: Some(DeploymentSpec {
|
||||||
|
replicas: Some(3),
|
||||||
|
selector: Default::default(),
|
||||||
|
template: PodTemplateSpec {
|
||||||
|
metadata: Some(ObjectMeta {
|
||||||
|
labels: Some({
|
||||||
|
let mut map = std::collections::BTreeMap::new();
|
||||||
|
map.insert("app".to_string(), "nginx".to_string());
|
||||||
|
map
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
spec: Some(Default::default()), // PodSpec with empty containers for simplicity
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let dynamic = kube_resource_to_dynamic(&deployment).expect("Failed to convert Deployment");
|
||||||
|
|
||||||
|
let original_value = serde_json::to_value(&deployment).unwrap();
|
||||||
|
let dynamic_value = serde_json::to_value(&dynamic).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(original_value, dynamic_value);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
dynamic.data.get("spec").unwrap().get("replicas").unwrap(),
|
||||||
|
3
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
dynamic
|
||||||
|
.data
|
||||||
|
.get("spec")
|
||||||
|
.unwrap()
|
||||||
|
.get("template")
|
||||||
|
.unwrap()
|
||||||
|
.get("metadata")
|
||||||
|
.unwrap()
|
||||||
|
.get("labels")
|
||||||
|
.unwrap()
|
||||||
|
.get("app")
|
||||||
|
.unwrap()
|
||||||
|
.as_str()
|
||||||
|
.unwrap(),
|
||||||
|
"nginx".to_string()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,5 +3,6 @@ pub mod executors;
|
|||||||
pub mod hp_ilo;
|
pub mod hp_ilo;
|
||||||
pub mod intel_amt;
|
pub mod intel_amt;
|
||||||
pub mod inventory;
|
pub mod inventory;
|
||||||
|
pub mod kube;
|
||||||
pub mod opnsense;
|
pub mod opnsense;
|
||||||
mod sqlx;
|
mod sqlx;
|
||||||
|
|||||||
@@ -25,8 +25,6 @@ impl OPNSenseFirewall {
|
|||||||
self.host.ip
|
self.host.ip
|
||||||
}
|
}
|
||||||
|
|
||||||
/// panics : if the opnsense config file cannot be loaded by the underlying opnsense_config
|
|
||||||
/// crate
|
|
||||||
pub async fn new(host: LogicalHost, port: Option<u16>, username: &str, password: &str) -> Self {
|
pub async fn new(host: LogicalHost, port: Option<u16>, username: &str, password: &str) -> Self {
|
||||||
Self {
|
Self {
|
||||||
opnsense_config: Arc::new(RwLock::new(
|
opnsense_config: Arc::new(RwLock::new(
|
||||||
|
|||||||
@@ -3,16 +3,20 @@ use std::collections::BTreeMap;
|
|||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use k8s_openapi::api::core::v1::Secret;
|
use k8s_openapi::api::core::v1::Secret;
|
||||||
use kube::api::ObjectMeta;
|
use kube::Resource;
|
||||||
|
use kube::api::{DynamicObject, ObjectMeta};
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use serde_yaml::{Mapping, Value};
|
use serde_yaml::{Mapping, Value};
|
||||||
|
|
||||||
|
use crate::infra::kube::kube_resource_to_dynamic;
|
||||||
use crate::modules::monitoring::kube_prometheus::crd::crd_alertmanager_config::{
|
use crate::modules::monitoring::kube_prometheus::crd::crd_alertmanager_config::{
|
||||||
AlertmanagerConfig, AlertmanagerConfigSpec, CRDPrometheus,
|
AlertmanagerConfig, AlertmanagerConfigSpec, CRDPrometheus,
|
||||||
};
|
};
|
||||||
use crate::modules::monitoring::kube_prometheus::crd::rhob_alertmanager_config::RHOBObservability;
|
use crate::modules::monitoring::kube_prometheus::crd::rhob_alertmanager_config::RHOBObservability;
|
||||||
|
use crate::modules::monitoring::okd::OpenshiftClusterAlertSender;
|
||||||
|
use crate::topology::oberservability::monitoring::AlertManagerReceiver;
|
||||||
use crate::{
|
use crate::{
|
||||||
interpret::{InterpretError, Outcome},
|
interpret::{InterpretError, Outcome},
|
||||||
modules::monitoring::{
|
modules::monitoring::{
|
||||||
@@ -28,14 +32,25 @@ use harmony_types::net::Url;
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Serialize)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
pub struct DiscordWebhook {
|
pub struct DiscordWebhook {
|
||||||
|
// FIXME use a stricter type as this is used as a k8s resource name. It could also be converted
|
||||||
|
// to remove whitespace and other invalid characters, but this is a potential bug that is not
|
||||||
|
// very easy to figure out for beginners.
|
||||||
|
//
|
||||||
|
// It gives out error messages like this :
|
||||||
|
//
|
||||||
|
// [2025-10-30 15:10:49 ERROR harmony::domain::topology::k8s] Failed to get dynamic resource 'Webhook example-secret': Failed to build request: failed to build request: invalid uri character
|
||||||
|
// [2025-10-30 15:10:49 ERROR harmony_cli::cli_logger] ⚠️ InterpretError : Failed to build request: failed to build request: invalid uri character
|
||||||
|
// [2025-10-30 15:10:49 DEBUG harmony::domain::maestro] Got result Err(InterpretError { msg: "InterpretError : Failed to build request: failed to build request: invalid uri character" })
|
||||||
|
// [2025-10-30 15:10:49 INFO harmony_cli::cli_logger] 🎼 Harmony completed
|
||||||
|
//
|
||||||
|
// thread 'main' panicked at examples/okd_cluster_alerts/src/main.rs:25:6:
|
||||||
|
// called `Result::unwrap()` on an `Err` value: InterpretError { msg: "InterpretError : Failed to build request: failed to build request: invalid uri character" }
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub url: Url,
|
pub url: Url,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
impl DiscordWebhook {
|
||||||
impl AlertReceiver<RHOBObservability> for DiscordWebhook {
|
fn get_receiver_config(&self) -> Result<AlertManagerReceiver, String> {
|
||||||
async fn install(&self, sender: &RHOBObservability) -> Result<Outcome, InterpretError> {
|
|
||||||
let ns = sender.namespace.clone();
|
|
||||||
let secret_name = format!("{}-secret", self.name.clone());
|
let secret_name = format!("{}-secret", self.name.clone());
|
||||||
let webhook_key = format!("{}", self.url.clone());
|
let webhook_key = format!("{}", self.url.clone());
|
||||||
|
|
||||||
@@ -52,26 +67,74 @@ impl AlertReceiver<RHOBObservability> for DiscordWebhook {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let _ = sender.client.apply(&secret, Some(&ns)).await;
|
Ok(AlertManagerReceiver {
|
||||||
|
additional_ressources: vec![kube_resource_to_dynamic(&secret)?],
|
||||||
|
|
||||||
|
receiver_config: json!({
|
||||||
|
"name": self.name,
|
||||||
|
"discordConfigs": [
|
||||||
|
{
|
||||||
|
"apiURL": {
|
||||||
|
"name": secret_name,
|
||||||
|
"key": "webhook-url",
|
||||||
|
},
|
||||||
|
"title": "{{ template \"discord.default.title\" . }}",
|
||||||
|
"message": "{{ template \"discord.default.message\" . }}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl AlertReceiver<OpenshiftClusterAlertSender> for DiscordWebhook {
|
||||||
|
async fn install(
|
||||||
|
&self,
|
||||||
|
sender: &OpenshiftClusterAlertSender,
|
||||||
|
) -> Result<Outcome, InterpretError> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> String {
|
||||||
|
self.name.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clone_box(&self) -> Box<dyn AlertReceiver<OpenshiftClusterAlertSender>> {
|
||||||
|
Box::new(self.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_any(&self) -> &dyn Any {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_alertmanager_receiver(&self) -> Result<AlertManagerReceiver, String> {
|
||||||
|
self.get_receiver_config()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl AlertReceiver<RHOBObservability> for DiscordWebhook {
|
||||||
|
fn as_alertmanager_receiver(&self) -> Result<AlertManagerReceiver, String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn install(&self, sender: &RHOBObservability) -> Result<Outcome, InterpretError> {
|
||||||
|
let ns = sender.namespace.clone();
|
||||||
|
|
||||||
|
let config = self.get_receiver_config()?;
|
||||||
|
for resource in config.additional_ressources.iter() {
|
||||||
|
todo!("can I apply a dynamicresource");
|
||||||
|
// sender.client.apply(resource, Some(&ns)).await;
|
||||||
|
}
|
||||||
|
|
||||||
let spec = crate::modules::monitoring::kube_prometheus::crd::rhob_alertmanager_config::AlertmanagerConfigSpec {
|
let spec = crate::modules::monitoring::kube_prometheus::crd::rhob_alertmanager_config::AlertmanagerConfigSpec {
|
||||||
data: json!({
|
data: json!({
|
||||||
"route": {
|
"route": {
|
||||||
"receiver": self.name,
|
"receiver": self.name,
|
||||||
},
|
},
|
||||||
"receivers": [
|
"receivers": [
|
||||||
{
|
config.receiver_config
|
||||||
"name": self.name,
|
|
||||||
"discordConfigs": [
|
|
||||||
{
|
|
||||||
"apiURL": {
|
|
||||||
"name": secret_name,
|
|
||||||
"key": "webhook-url",
|
|
||||||
},
|
|
||||||
"title": "{{ template \"discord.default.title\" . }}",
|
|
||||||
"message": "{{ template \"discord.default.message\" . }}"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
@@ -122,6 +185,9 @@ impl AlertReceiver<RHOBObservability> for DiscordWebhook {
|
|||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl AlertReceiver<CRDPrometheus> for DiscordWebhook {
|
impl AlertReceiver<CRDPrometheus> for DiscordWebhook {
|
||||||
|
fn as_alertmanager_receiver(&self) -> Result<AlertManagerReceiver, String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
async fn install(&self, sender: &CRDPrometheus) -> Result<Outcome, InterpretError> {
|
async fn install(&self, sender: &CRDPrometheus) -> Result<Outcome, InterpretError> {
|
||||||
let ns = sender.namespace.clone();
|
let ns = sender.namespace.clone();
|
||||||
let secret_name = format!("{}-secret", self.name.clone());
|
let secret_name = format!("{}-secret", self.name.clone());
|
||||||
@@ -200,6 +266,9 @@ impl AlertReceiver<CRDPrometheus> for DiscordWebhook {
|
|||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl AlertReceiver<Prometheus> for DiscordWebhook {
|
impl AlertReceiver<Prometheus> for DiscordWebhook {
|
||||||
|
fn as_alertmanager_receiver(&self) -> Result<AlertManagerReceiver, String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
async fn install(&self, sender: &Prometheus) -> Result<Outcome, InterpretError> {
|
async fn install(&self, sender: &Prometheus) -> Result<Outcome, InterpretError> {
|
||||||
sender.install_receiver(self).await
|
sender.install_receiver(self).await
|
||||||
}
|
}
|
||||||
@@ -226,6 +295,9 @@ impl PrometheusReceiver for DiscordWebhook {
|
|||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl AlertReceiver<KubePrometheus> for DiscordWebhook {
|
impl AlertReceiver<KubePrometheus> for DiscordWebhook {
|
||||||
|
fn as_alertmanager_receiver(&self) -> Result<AlertManagerReceiver, String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
async fn install(&self, sender: &KubePrometheus) -> Result<Outcome, InterpretError> {
|
async fn install(&self, sender: &KubePrometheus) -> Result<Outcome, InterpretError> {
|
||||||
sender.install_receiver(self).await
|
sender.install_receiver(self).await
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ use crate::{
|
|||||||
},
|
},
|
||||||
prometheus::prometheus::{Prometheus, PrometheusReceiver},
|
prometheus::prometheus::{Prometheus, PrometheusReceiver},
|
||||||
},
|
},
|
||||||
topology::oberservability::monitoring::AlertReceiver,
|
topology::oberservability::monitoring::{AlertManagerReceiver, AlertReceiver},
|
||||||
};
|
};
|
||||||
use harmony_types::net::Url;
|
use harmony_types::net::Url;
|
||||||
|
|
||||||
@@ -31,6 +31,9 @@ pub struct WebhookReceiver {
|
|||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl AlertReceiver<RHOBObservability> for WebhookReceiver {
|
impl AlertReceiver<RHOBObservability> for WebhookReceiver {
|
||||||
|
fn as_alertmanager_receiver(&self) -> Result<AlertManagerReceiver, String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
async fn install(&self, sender: &RHOBObservability) -> Result<Outcome, InterpretError> {
|
async fn install(&self, sender: &RHOBObservability) -> Result<Outcome, InterpretError> {
|
||||||
let spec = crate::modules::monitoring::kube_prometheus::crd::rhob_alertmanager_config::AlertmanagerConfigSpec {
|
let spec = crate::modules::monitoring::kube_prometheus::crd::rhob_alertmanager_config::AlertmanagerConfigSpec {
|
||||||
data: json!({
|
data: json!({
|
||||||
@@ -97,6 +100,9 @@ impl AlertReceiver<RHOBObservability> for WebhookReceiver {
|
|||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl AlertReceiver<CRDPrometheus> for WebhookReceiver {
|
impl AlertReceiver<CRDPrometheus> for WebhookReceiver {
|
||||||
|
fn as_alertmanager_receiver(&self) -> Result<AlertManagerReceiver, String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
async fn install(&self, sender: &CRDPrometheus) -> Result<Outcome, InterpretError> {
|
async fn install(&self, sender: &CRDPrometheus) -> Result<Outcome, InterpretError> {
|
||||||
let spec = crate::modules::monitoring::kube_prometheus::crd::crd_alertmanager_config::AlertmanagerConfigSpec {
|
let spec = crate::modules::monitoring::kube_prometheus::crd::crd_alertmanager_config::AlertmanagerConfigSpec {
|
||||||
data: json!({
|
data: json!({
|
||||||
@@ -158,6 +164,9 @@ impl AlertReceiver<CRDPrometheus> for WebhookReceiver {
|
|||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl AlertReceiver<Prometheus> for WebhookReceiver {
|
impl AlertReceiver<Prometheus> for WebhookReceiver {
|
||||||
|
fn as_alertmanager_receiver(&self) -> Result<AlertManagerReceiver, String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
async fn install(&self, sender: &Prometheus) -> Result<Outcome, InterpretError> {
|
async fn install(&self, sender: &Prometheus) -> Result<Outcome, InterpretError> {
|
||||||
sender.install_receiver(self).await
|
sender.install_receiver(self).await
|
||||||
}
|
}
|
||||||
@@ -184,6 +193,9 @@ impl PrometheusReceiver for WebhookReceiver {
|
|||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl AlertReceiver<KubePrometheus> for WebhookReceiver {
|
impl AlertReceiver<KubePrometheus> for WebhookReceiver {
|
||||||
|
fn as_alertmanager_receiver(&self) -> Result<AlertManagerReceiver, String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
async fn install(&self, sender: &KubePrometheus) -> Result<Outcome, InterpretError> {
|
async fn install(&self, sender: &KubePrometheus) -> Result<Outcome, InterpretError> {
|
||||||
sender.install_receiver(self).await
|
sender.install_receiver(self).await
|
||||||
}
|
}
|
||||||
|
|||||||
214
harmony/src/modules/monitoring/okd/cluster_monitoring.rs
Normal file
214
harmony/src/modules/monitoring/okd/cluster_monitoring.rs
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
use base64::prelude::*;
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use harmony_types::id::Id;
|
||||||
|
use kube::api::DynamicObject;
|
||||||
|
use log::{debug, info, trace};
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
data::Version,
|
||||||
|
interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome},
|
||||||
|
inventory::Inventory,
|
||||||
|
modules::monitoring::okd::OpenshiftClusterAlertSender,
|
||||||
|
score::Score,
|
||||||
|
topology::{K8sclient, Topology, oberservability::monitoring::AlertReceiver},
|
||||||
|
};
|
||||||
|
|
||||||
|
impl Clone for Box<dyn AlertReceiver<OpenshiftClusterAlertSender>> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
self.clone_box()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for Box<dyn AlertReceiver<OpenshiftClusterAlertSender>> {
|
||||||
|
fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
pub struct OpenshiftClusterAlertScore {
|
||||||
|
pub receivers: Vec<Box<dyn AlertReceiver<OpenshiftClusterAlertSender>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Topology + K8sclient> Score<T> for OpenshiftClusterAlertScore {
|
||||||
|
fn name(&self) -> String {
|
||||||
|
"ClusterAlertScore".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
|
||||||
|
Box::new(OpenshiftClusterAlertInterpret {
|
||||||
|
receivers: self.receivers.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct OpenshiftClusterAlertInterpret {
|
||||||
|
receivers: Vec<Box<dyn AlertReceiver<OpenshiftClusterAlertSender>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<T: Topology + K8sclient> Interpret<T> for OpenshiftClusterAlertInterpret {
|
||||||
|
async fn execute(
|
||||||
|
&self,
|
||||||
|
_inventory: &Inventory,
|
||||||
|
topology: &T,
|
||||||
|
) -> Result<Outcome, InterpretError> {
|
||||||
|
let client = topology.k8s_client().await?;
|
||||||
|
let openshift_monitoring_namespace = "openshift-monitoring";
|
||||||
|
|
||||||
|
let mut alertmanager_main_secret: DynamicObject = client
|
||||||
|
.get_secret_json_value("alertmanager-main", Some(openshift_monitoring_namespace))
|
||||||
|
.await?;
|
||||||
|
trace!("Got secret {alertmanager_main_secret:#?}");
|
||||||
|
|
||||||
|
let data: &mut serde_json::Value = &mut alertmanager_main_secret.data;
|
||||||
|
trace!("Alertmanager-main secret data {data:#?}");
|
||||||
|
let data_obj = data
|
||||||
|
.get_mut("data")
|
||||||
|
.ok_or(InterpretError::new(
|
||||||
|
"Missing 'data' field in alertmanager-main secret.".to_string(),
|
||||||
|
))?
|
||||||
|
.as_object_mut()
|
||||||
|
.ok_or(InterpretError::new(
|
||||||
|
"'data' field in alertmanager-main secret is expected to be an object ."
|
||||||
|
.to_string(),
|
||||||
|
))?;
|
||||||
|
|
||||||
|
let config_b64 = data_obj
|
||||||
|
.get("alertmanager.yaml")
|
||||||
|
.ok_or(InterpretError::new(
|
||||||
|
"Missing 'alertmanager.yaml' in alertmanager-main secret data".to_string(),
|
||||||
|
))?
|
||||||
|
.as_str()
|
||||||
|
.unwrap_or("");
|
||||||
|
trace!("Config base64 {config_b64}");
|
||||||
|
|
||||||
|
let config_bytes = BASE64_STANDARD.decode(config_b64).unwrap_or_default();
|
||||||
|
|
||||||
|
let mut am_config: serde_yaml::Value =
|
||||||
|
serde_yaml::from_str(&String::from_utf8(config_bytes).unwrap_or_default())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
debug!("Current alertmanager config {am_config:#?}");
|
||||||
|
|
||||||
|
let existing_receivers_sequence = if let Some(receivers) = am_config.get_mut("receivers") {
|
||||||
|
match receivers.as_sequence_mut() {
|
||||||
|
Some(seq) => seq,
|
||||||
|
None => {
|
||||||
|
return Err(InterpretError::new(format!(
|
||||||
|
"Expected alertmanager config receivers to be a sequence, got {:?}",
|
||||||
|
receivers
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
&mut serde_yaml::Sequence::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut additional_resources = vec![];
|
||||||
|
|
||||||
|
for custom_receiver in &self.receivers {
|
||||||
|
let name = custom_receiver.name();
|
||||||
|
let alertmanager_receiver = custom_receiver.as_alertmanager_receiver()?;
|
||||||
|
|
||||||
|
let json_value = alertmanager_receiver.receiver_config;
|
||||||
|
|
||||||
|
let yaml_string = serde_json::to_string(&json_value).map_err(|e| {
|
||||||
|
InterpretError::new(format!("Failed to serialize receiver config: {}", e))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let yaml_value: serde_yaml::Value =
|
||||||
|
serde_yaml::from_str(&yaml_string).map_err(|e| {
|
||||||
|
InterpretError::new(format!("Failed to parse receiver config as YAML: {}", e))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if let Some(idx) = existing_receivers_sequence.iter().position(|r| {
|
||||||
|
r.get("name")
|
||||||
|
.and_then(|n| n.as_str())
|
||||||
|
.map_or(false, |n| n == name)
|
||||||
|
}) {
|
||||||
|
info!("Replacing existing AlertManager receiver: {}", name);
|
||||||
|
existing_receivers_sequence[idx] = yaml_value;
|
||||||
|
} else {
|
||||||
|
debug!("Adding new AlertManager receiver: {}", name);
|
||||||
|
existing_receivers_sequence.push(yaml_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
additional_resources.push(alertmanager_receiver.additional_ressources);
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!("Current alertmanager config {am_config:#?}");
|
||||||
|
// TODO
|
||||||
|
// - save new version of alertmanager config
|
||||||
|
// - write additional ressources to the cluster
|
||||||
|
let am_config = serde_yaml::to_string(&am_config).map_err(|e| {
|
||||||
|
InterpretError::new(format!(
|
||||||
|
"Failed to serialize new alertmanager config to string : {e}"
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut am_config_b64 = String::new();
|
||||||
|
BASE64_STANDARD.encode_string(am_config, &mut am_config_b64);
|
||||||
|
|
||||||
|
// TODO put update configmap value and save new value
|
||||||
|
data_obj.insert(
|
||||||
|
"alertmanager.yaml".to_string(),
|
||||||
|
serde_json::Value::String(am_config_b64),
|
||||||
|
);
|
||||||
|
|
||||||
|
// https://kubernetes.io/docs/reference/using-api/server-side-apply/#field-management
|
||||||
|
alertmanager_main_secret.metadata.managed_fields = None;
|
||||||
|
|
||||||
|
trace!("Applying new alertmanager_main_secret {alertmanager_main_secret:#?}");
|
||||||
|
client
|
||||||
|
.apply_dynamic(
|
||||||
|
&alertmanager_main_secret,
|
||||||
|
Some(openshift_monitoring_namespace),
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let additional_resources = additional_resources.concat();
|
||||||
|
trace!("Applying additional ressources for alert receivers {additional_resources:#?}");
|
||||||
|
client
|
||||||
|
.apply_dynamic_many(
|
||||||
|
&additional_resources,
|
||||||
|
Some(openshift_monitoring_namespace),
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(Outcome::success(format!(
|
||||||
|
"Successfully configured {} cluster alert receivers: {}",
|
||||||
|
self.receivers.len(),
|
||||||
|
self.receivers
|
||||||
|
.iter()
|
||||||
|
.map(|r| r.name())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ")
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_name(&self) -> InterpretName {
|
||||||
|
InterpretName::Custom("OpenshiftClusterAlertInterpret")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_version(&self) -> Version {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_status(&self) -> InterpretStatus {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_children(&self) -> Vec<Id> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
90
harmony/src/modules/monitoring/okd/config.rs
Normal file
90
harmony/src/modules/monitoring/okd/config.rs
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
use std::{collections::BTreeMap, sync::Arc};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
interpret::{InterpretError, Outcome},
|
||||||
|
topology::k8s::K8sClient,
|
||||||
|
};
|
||||||
|
use k8s_openapi::api::core::v1::ConfigMap;
|
||||||
|
use kube::api::ObjectMeta;
|
||||||
|
|
||||||
|
pub(crate) struct Config;
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub async fn create_cluster_monitoring_config_cm(
|
||||||
|
client: &Arc<K8sClient>,
|
||||||
|
) -> Result<Outcome, InterpretError> {
|
||||||
|
let mut data = BTreeMap::new();
|
||||||
|
data.insert(
|
||||||
|
"config.yaml".to_string(),
|
||||||
|
r#"
|
||||||
|
enableUserWorkload: true
|
||||||
|
alertmanagerMain:
|
||||||
|
enableUserAlertmanagerConfig: true
|
||||||
|
"#
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let cm = ConfigMap {
|
||||||
|
metadata: ObjectMeta {
|
||||||
|
name: Some("cluster-monitoring-config".to_string()),
|
||||||
|
namespace: Some("openshift-monitoring".to_string()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
data: Some(data),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
client.apply(&cm, Some("openshift-monitoring")).await?;
|
||||||
|
|
||||||
|
Ok(Outcome::success(
|
||||||
|
"updated cluster-monitoring-config-map".to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn create_user_workload_monitoring_config_cm(
|
||||||
|
client: &Arc<K8sClient>,
|
||||||
|
) -> Result<Outcome, InterpretError> {
|
||||||
|
let mut data = BTreeMap::new();
|
||||||
|
data.insert(
|
||||||
|
"config.yaml".to_string(),
|
||||||
|
r#"
|
||||||
|
alertmanager:
|
||||||
|
enabled: true
|
||||||
|
enableAlertmanagerConfig: true
|
||||||
|
"#
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
let cm = ConfigMap {
|
||||||
|
metadata: ObjectMeta {
|
||||||
|
name: Some("user-workload-monitoring-config".to_string()),
|
||||||
|
namespace: Some("openshift-user-workload-monitoring".to_string()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
data: Some(data),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
client
|
||||||
|
.apply(&cm, Some("openshift-user-workload-monitoring"))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(Outcome::success(
|
||||||
|
"updated openshift-user-monitoring-config-map".to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn verify_user_workload(client: &Arc<K8sClient>) -> Result<Outcome, InterpretError> {
|
||||||
|
let namespace = "openshift-user-workload-monitoring";
|
||||||
|
let alertmanager_name = "alertmanager-user-workload-0";
|
||||||
|
let prometheus_name = "prometheus-user-workload-0";
|
||||||
|
client
|
||||||
|
.wait_for_pod_ready(alertmanager_name, Some(namespace))
|
||||||
|
.await?;
|
||||||
|
client
|
||||||
|
.wait_for_pod_ready(prometheus_name, Some(namespace))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(Outcome::success(format!(
|
||||||
|
"pods: {}, {} ready in ns: {}",
|
||||||
|
alertmanager_name, prometheus_name, namespace
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,13 @@
|
|||||||
use std::{collections::BTreeMap, sync::Arc};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
data::Version,
|
data::Version,
|
||||||
interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome},
|
interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome},
|
||||||
inventory::Inventory,
|
inventory::Inventory,
|
||||||
|
modules::monitoring::okd::config::Config,
|
||||||
score::Score,
|
score::Score,
|
||||||
topology::{K8sclient, Topology, k8s::K8sClient},
|
topology::{K8sclient, Topology},
|
||||||
};
|
};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use harmony_types::id::Id;
|
use harmony_types::id::Id;
|
||||||
use k8s_openapi::api::core::v1::ConfigMap;
|
|
||||||
use kube::api::ObjectMeta;
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
@@ -37,10 +34,9 @@ impl<T: Topology + K8sclient> Interpret<T> for OpenshiftUserWorkloadMonitoringIn
|
|||||||
topology: &T,
|
topology: &T,
|
||||||
) -> Result<Outcome, InterpretError> {
|
) -> Result<Outcome, InterpretError> {
|
||||||
let client = topology.k8s_client().await.unwrap();
|
let client = topology.k8s_client().await.unwrap();
|
||||||
self.update_cluster_monitoring_config_cm(&client).await?;
|
Config::create_cluster_monitoring_config_cm(&client).await?;
|
||||||
self.update_user_workload_monitoring_config_cm(&client)
|
Config::create_user_workload_monitoring_config_cm(&client).await?;
|
||||||
.await?;
|
Config::verify_user_workload(&client).await?;
|
||||||
self.verify_user_workload(&client).await?;
|
|
||||||
Ok(Outcome::success(
|
Ok(Outcome::success(
|
||||||
"successfully enabled user-workload-monitoring".to_string(),
|
"successfully enabled user-workload-monitoring".to_string(),
|
||||||
))
|
))
|
||||||
@@ -62,88 +58,3 @@ impl<T: Topology + K8sclient> Interpret<T> for OpenshiftUserWorkloadMonitoringIn
|
|||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OpenshiftUserWorkloadMonitoringInterpret {
|
|
||||||
pub async fn update_cluster_monitoring_config_cm(
|
|
||||||
&self,
|
|
||||||
client: &Arc<K8sClient>,
|
|
||||||
) -> Result<Outcome, InterpretError> {
|
|
||||||
let mut data = BTreeMap::new();
|
|
||||||
data.insert(
|
|
||||||
"config.yaml".to_string(),
|
|
||||||
r#"
|
|
||||||
enableUserWorkload: true
|
|
||||||
alertmanagerMain:
|
|
||||||
enableUserAlertmanagerConfig: true
|
|
||||||
"#
|
|
||||||
.to_string(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let cm = ConfigMap {
|
|
||||||
metadata: ObjectMeta {
|
|
||||||
name: Some("cluster-monitoring-config".to_string()),
|
|
||||||
namespace: Some("openshift-monitoring".to_string()),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
data: Some(data),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
client.apply(&cm, Some("openshift-monitoring")).await?;
|
|
||||||
|
|
||||||
Ok(Outcome::success(
|
|
||||||
"updated cluster-monitoring-config-map".to_string(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn update_user_workload_monitoring_config_cm(
|
|
||||||
&self,
|
|
||||||
client: &Arc<K8sClient>,
|
|
||||||
) -> Result<Outcome, InterpretError> {
|
|
||||||
let mut data = BTreeMap::new();
|
|
||||||
data.insert(
|
|
||||||
"config.yaml".to_string(),
|
|
||||||
r#"
|
|
||||||
alertmanager:
|
|
||||||
enabled: true
|
|
||||||
enableAlertmanagerConfig: true
|
|
||||||
"#
|
|
||||||
.to_string(),
|
|
||||||
);
|
|
||||||
let cm = ConfigMap {
|
|
||||||
metadata: ObjectMeta {
|
|
||||||
name: Some("user-workload-monitoring-config".to_string()),
|
|
||||||
namespace: Some("openshift-user-workload-monitoring".to_string()),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
data: Some(data),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
client
|
|
||||||
.apply(&cm, Some("openshift-user-workload-monitoring"))
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(Outcome::success(
|
|
||||||
"updated openshift-user-monitoring-config-map".to_string(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn verify_user_workload(
|
|
||||||
&self,
|
|
||||||
client: &Arc<K8sClient>,
|
|
||||||
) -> Result<Outcome, InterpretError> {
|
|
||||||
let namespace = "openshift-user-workload-monitoring";
|
|
||||||
let alertmanager_name = "alertmanager-user-workload-0";
|
|
||||||
let prometheus_name = "prometheus-user-workload-0";
|
|
||||||
client
|
|
||||||
.wait_for_pod_ready(alertmanager_name, Some(namespace))
|
|
||||||
.await?;
|
|
||||||
client
|
|
||||||
.wait_for_pod_ready(prometheus_name, Some(namespace))
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(Outcome::success(format!(
|
|
||||||
"pods: {}, {} ready in ns: {}",
|
|
||||||
alertmanager_name, prometheus_name, namespace
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1 +1,14 @@
|
|||||||
|
use crate::topology::oberservability::monitoring::AlertSender;
|
||||||
|
|
||||||
|
pub mod cluster_monitoring;
|
||||||
|
pub(crate) mod config;
|
||||||
pub mod enable_user_workload;
|
pub mod enable_user_workload;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct OpenshiftClusterAlertSender;
|
||||||
|
|
||||||
|
impl AlertSender for OpenshiftClusterAlertSender {
|
||||||
|
fn name(&self) -> String {
|
||||||
|
"OpenshiftClusterAlertSender".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ pub struct Interface {
|
|||||||
pub physical_interface_name: String,
|
pub physical_interface_name: String,
|
||||||
pub descr: Option<MaybeString>,
|
pub descr: Option<MaybeString>,
|
||||||
pub mtu: Option<MaybeString>,
|
pub mtu: Option<MaybeString>,
|
||||||
pub enable: MaybeString,
|
pub enable: Option<MaybeString>,
|
||||||
pub lock: Option<MaybeString>,
|
pub lock: Option<MaybeString>,
|
||||||
#[yaserde(rename = "spoofmac")]
|
#[yaserde(rename = "spoofmac")]
|
||||||
pub spoof_mac: Option<MaybeString>,
|
pub spoof_mac: Option<MaybeString>,
|
||||||
@@ -134,19 +134,15 @@ mod test {
|
|||||||
<interfaces>
|
<interfaces>
|
||||||
<paul>
|
<paul>
|
||||||
<if></if>
|
<if></if>
|
||||||
<enable/>
|
|
||||||
</paul>
|
</paul>
|
||||||
<anotherpaul>
|
<anotherpaul>
|
||||||
<if></if>
|
<if></if>
|
||||||
<enable/>
|
|
||||||
</anotherpaul>
|
</anotherpaul>
|
||||||
<thirdone>
|
<thirdone>
|
||||||
<if></if>
|
<if></if>
|
||||||
<enable/>
|
|
||||||
</thirdone>
|
</thirdone>
|
||||||
<andgofor4>
|
<andgofor4>
|
||||||
<if></if>
|
<if></if>
|
||||||
<enable/>
|
|
||||||
</andgofor4>
|
</andgofor4>
|
||||||
</interfaces>
|
</interfaces>
|
||||||
<bar>foo</bar>
|
<bar>foo</bar>
|
||||||
|
|||||||
@@ -216,7 +216,7 @@ pub struct System {
|
|||||||
pub maximumfrags: Option<MaybeString>,
|
pub maximumfrags: Option<MaybeString>,
|
||||||
pub aliasesresolveinterval: Option<MaybeString>,
|
pub aliasesresolveinterval: Option<MaybeString>,
|
||||||
pub maximumtableentries: Option<MaybeString>,
|
pub maximumtableentries: Option<MaybeString>,
|
||||||
pub language: Option<MaybeString>,
|
pub language: String,
|
||||||
pub dnsserver: Option<MaybeString>,
|
pub dnsserver: Option<MaybeString>,
|
||||||
pub dns1gw: Option<String>,
|
pub dns1gw: Option<String>,
|
||||||
pub dns2gw: Option<String>,
|
pub dns2gw: Option<String>,
|
||||||
|
|||||||
Reference in New Issue
Block a user