diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml
new file mode 100644
index 000000000..177241d24
--- /dev/null
+++ b/.github/workflows/e2e.yml
@@ -0,0 +1,62 @@
+name: E2E Tests
+
+on:
+ push:
+ branches: ["nightly"]
+ pull_request:
+ branches: ["nightly"]
+ workflow_dispatch: {}
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: ${{ github.ref != 'refs/heads/nightly' }}
+
+env:
+ CARGO_TERM_COLOR: always
+
+jobs:
+ ubuntu_e2e_test:
+ strategy:
+ fail-fast: true
+ runs-on: ubuntu-latest
+
+ steps:
+ # Clone project
+ - uses: actions/checkout@v3
+ # Install buildx
+ - name: Set up Docker Buildx
+ id: buildx
+ uses: docker/setup-buildx-action@v2
+ # Set buildx cache
+ - name: Cache register
+ uses: actions/cache@v3
+ with:
+ path: ~/buildx-cache
+ key: buildx-cache
+ # Install cargo make
+ - uses: davidB/rust-cargo-make@v1
+ - name: Install bats
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y bats
+ # Build nightly image and install nanocl
+ - name: Prepare CI
+ run: |
+ sh ./scripts/build_images.sh
+ NANOCL_CHANNEL=nightly cargo build --release --bin nanocl
+ sudo cp target/release/nanocl /usr/bin/nanocl
+ sudo chmod +x /usr/bin/nanocl
+ sudo groupadd nanocl
+ sudo usermod -aG nanocl $USER
+ newgrp nanocl
+ nanocl install -t installer.yml
+ sleep 20
+ docker ps -a
+ docker logs ndaemon.system.c
+ sudo chmod -R 777 /var/lib/nanocl
+ sudo chmod -R 777 /run/nanocl
+ nanocl version
+ # Run E2E tests
+ - name: E2E
+ run: |
+ bats ./tests/e2e.bats
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index b32ee3a61..7a0786e26 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -35,6 +35,33 @@ jobs:
key: buildx-cache
# Install cargo make
- uses: davidB/rust-cargo-make@v1
+ # Debug ci with ngrok and ssh
+ # - name: Download ngrok
+ # run: curl -sO https://bin.equinox.io/c/bNyj1mQVY4c/ngrok-v3-stable-linux-amd64.tgz
+ # shell: bash
+ # - name: Unzip ngrok
+ # run: tar -xf ngrok-v3-stable-linux-amd64.tgz
+ # shell: bash
+ # - name: Add ~/.ssh directory
+ # run: mkdir -p ~/.ssh
+ # shell: bash
+ # - name: Add SSH public key to authorized_keys
+ # run: echo "${{ secrets.WORKFLOW_SSH_PUB_KEY }}" >> ~/.ssh/authorized_keys
+ # shell: bash
+ # - name: Fix home directory permissions
+ # run: chmod 755 ~
+ # shell: bash
+ # - run: chmod 600 ~/.ssh/authorized_keys
+ # shell: bash
+ # - name: Set ngrok auth token
+ # run: ./ngrok authtoken ${{ secrets.NGROK_TOKEN }}
+ # shell: bash
+ # - name: Debug message
+ # run: echo "Starting ngrok tunnel..."
+ # shell: bash
+ # - name: Setup ngrok tunnel
+ # run: timeout 1h ./ngrok tcp 22 &
+ # shell: bash
# Cache Rust
- uses: Swatinem/rust-cache@v2
with:
diff --git a/.vscode/settings.json b/.vscode/settings.json
index c9745f81a..541068383 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -66,6 +66,7 @@
"ndns",
"newgrp",
"nexthat",
+ "nhnr",
"nmetrics",
"nographic",
"nproxy",
diff --git a/Cargo.lock b/Cargo.lock
index 875aa55f4..9a1887528 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -98,9 +98,9 @@ dependencies = [
[[package]]
name = "anstyle-query"
-version = "1.0.3"
+version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5"
+checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391"
dependencies = [
"windows-sys 0.52.0",
]
@@ -2767,9 +2767,9 @@ dependencies = [
[[package]]
name = "openssl-src"
-version = "300.3.0+3.3.0"
+version = "300.3.1+3.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eba8804a1c5765b18c4b3f907e6897ebabeedebc9830e1a0046c4a4cf44663e1"
+checksum = "7259953d42a81bf137fbbd73bd30a8e1914d6dce43c2b90ed575783a22608b91"
dependencies = [
"cc",
]
@@ -4021,9 +4021,9 @@ checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
[[package]]
name = "unicode-width"
-version = "0.1.12"
+version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6"
+checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d"
[[package]]
name = "unsafe-libyaml"
@@ -4252,9 +4252,9 @@ dependencies = [
[[package]]
name = "webpki-roots"
-version = "0.26.1"
+version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009"
+checksum = "3c452ad30530b54a4d8e71952716a212b08efd0f3562baa66c29a618b07da7c3"
dependencies = [
"rustls-pki-types",
]
diff --git a/bin/nanocl/src/commands/cargo.rs b/bin/nanocl/src/commands/cargo.rs
index 7e63c3f78..233dc58a5 100644
--- a/bin/nanocl/src/commands/cargo.rs
+++ b/bin/nanocl/src/commands/cargo.rs
@@ -293,9 +293,13 @@ async fn exec_cargo_revert(
opts: &CargoRevertOpts,
) -> IoResult<()> {
let client = &cli_conf.client;
+ let waiter =
+ wait_cargo_state(&opts.name, args, NativeEventAction::Start, client)
+ .await?;
let cargo = client
.revert_cargo(&opts.name, &opts.history_id, args.namespace.as_deref())
.await?;
+ waiter.await??;
utils::print::print_yml(cargo)?;
Ok(())
}
diff --git a/bin/nanocl/src/main.rs b/bin/nanocl/src/main.rs
index 6f07d1fcc..02058c24b 100644
--- a/bin/nanocl/src/main.rs
+++ b/bin/nanocl/src/main.rs
@@ -177,14 +177,14 @@ mod tests {
.list_history_cargo(CARGO_NAME, None)
.await
.unwrap()
- .first()
+ .last()
.unwrap()
.clone();
assert_cli_ok!("cargo", "revert", CARGO_NAME, &history.key.to_string());
// Try to stop a cargo
assert_cli_ok!("cargo", "stop", CARGO_NAME);
// Try to remove cargo
- assert_cli_ok!("cargo", "rm", "-y", CARGO_NAME);
+ assert_cli_ok!("cargo", "rm", "-fy", CARGO_NAME);
}
/// Test state file when then include other state files
diff --git a/bin/nanocld/specs/readme.md b/bin/nanocld/specs/readme.md
index 3a3eba6bf..c612249f0 100644
--- a/bin/nanocld/specs/readme.md
+++ b/bin/nanocld/specs/readme.md
@@ -8,7 +8,7 @@ The notable exception is running `Cargo`, which consists of several `API` calls.
## OpenAPI Specification
This API is documented in **OpenAPI format** using [Utoipa](https://github.com/juhaku/utoipa)
The specification is generated automatically when running in development only.
-When releasing a version, the generated file is transfered to our [Documentation](https://github.com/next-hat/documentation).
+When releasing a version, the generated file is transferred to our [Documentation](https://github.com/next-hat/documentation).
## Cross-Origin Resource Sharing
diff --git a/bin/nanocld/specs/swagger.yaml b/bin/nanocld/specs/swagger.yaml
index c41daa630..d65b5549f 100644
--- a/bin/nanocld/specs/swagger.yaml
+++ b/bin/nanocld/specs/swagger.yaml
@@ -12,7 +12,7 @@ info:
## OpenAPI Specification
This API is documented in **OpenAPI format** using [Utoipa](https://github.com/juhaku/utoipa)
The specification is generated automatically when running in development only.
- When releasing a version, the generated file is transfered to our [Documentation](https://github.com/next-hat/documentation).
+ When releasing a version, the generated file is transferred to our [Documentation](https://github.com/next-hat/documentation).
## Cross-Origin Resource Sharing
@@ -7200,6 +7200,10 @@ components:
type: boolean
description: Disable logging for this target
nullable: true
+ Ssl:
+ allOf:
+ - $ref: '#/components/schemas/ProxySsl'
+ nullable: true
additionalProperties: false
UriTarget:
type: object
diff --git a/bin/nanocld/src/objects/cargo.rs b/bin/nanocld/src/objects/cargo.rs
index d1a8ba011..c0c3de036 100644
--- a/bin/nanocld/src/objects/cargo.rs
+++ b/bin/nanocld/src/objects/cargo.rs
@@ -210,10 +210,16 @@ impl ObjPatchByPk for CargoDb {
cmd,
image,
env: Some(env_vars),
- host_config: Some(HostConfig {
- binds: Some(volumes),
- ..cargo.spec.container.host_config.unwrap_or_default()
- }),
+ host_config: if !volumes.is_empty()
+ || cargo.spec.container.host_config.is_some()
+ {
+ Some(HostConfig {
+ binds: Some(volumes),
+ ..cargo.spec.container.host_config.unwrap_or_default()
+ })
+ } else {
+ None
+ },
..cargo.spec.container
}
} else {
diff --git a/bin/nanocld/src/system/docker_event.rs b/bin/nanocld/src/system/docker_event.rs
index 33640d0f1..7674f569d 100644
--- a/bin/nanocld/src/system/docker_event.rs
+++ b/bin/nanocld/src/system/docker_event.rs
@@ -14,9 +14,11 @@ use nanocl_stubs::system::{
};
use crate::{
- vars,
+ models::{
+ CargoDb, ObjPsStatusDb, ProcessDb, ProcessUpdateDb, SystemState, VmDb,
+ },
repositories::generic::*,
- models::{ObjPsStatusDb, ProcessDb, ProcessUpdateDb, SystemState},
+ vars,
};
/// Take actions when a docker event is received
@@ -104,50 +106,50 @@ async fn exec_docker(
_ => {}
}
}
- // "die" => {
- // let actual_status =
- // ObjPsStatusDb::read_by_pk(&kind_key, &state.inner.pool).await?;
- // match (&kind, &actual_status.wanted) {
- // (EventActorKind::Cargo, status)
- // if status != &ObjPsStatusKind::Stop.to_string()
- // || status != &ObjPsStatusKind::Start.to_string() =>
- // {
- // ObjPsStatusDb::update_actual_status(
- // &kind_key,
- // &ObjPsStatusKind::Fail,
- // &state.inner.pool,
- // )
- // .await?;
- // let cargo =
- // CargoDb::transform_read_by_pk(&kind_key, &state.inner.pool).await?;
- // state.emit_warning_native_action(
- // &cargo,
- // NativeEventAction::Fail,
- // Some(format!("Process {name}")),
- // );
- // }
- // (EventActorKind::Vm, status)
- // if status != &ObjPsStatusKind::Stop.to_string()
- // || status != &ObjPsStatusKind::Start.to_string() =>
- // {
- // ObjPsStatusDb::update_actual_status(
- // &kind_key,
- // &ObjPsStatusKind::Fail,
- // &state.inner.pool,
- // )
- // .await?;
- // let vm =
- // VmDb::transform_read_by_pk(&kind_key, &state.inner.pool).await?;
- // state.emit_warning_native_action(
- // &vm,
- // NativeEventAction::Fail,
- // Some(format!("Process {name}")),
- // );
- // }
- // _ => {}
- // }
- // action.clone_into(&mut event.action);
- // }
+ "die" => {
+ let actual_status =
+ ObjPsStatusDb::read_by_pk(&kind_key, &state.inner.pool).await?;
+ match (&kind, &actual_status.wanted) {
+ (EventActorKind::Cargo, status)
+ if status != &ObjPsStatusKind::Stop.to_string()
+ || status != &ObjPsStatusKind::Start.to_string() =>
+ {
+ ObjPsStatusDb::update_actual_status(
+ &kind_key,
+ &ObjPsStatusKind::Fail,
+ &state.inner.pool,
+ )
+ .await?;
+ let cargo =
+ CargoDb::transform_read_by_pk(&kind_key, &state.inner.pool).await?;
+ state.emit_warning_native_action(
+ &cargo,
+ NativeEventAction::Fail,
+ Some(format!("Process {name}")),
+ );
+ }
+ (EventActorKind::Vm, status)
+ if status != &ObjPsStatusKind::Stop.to_string()
+ || status != &ObjPsStatusKind::Start.to_string() =>
+ {
+ ObjPsStatusDb::update_actual_status(
+ &kind_key,
+ &ObjPsStatusKind::Fail,
+ &state.inner.pool,
+ )
+ .await?;
+ let vm =
+ VmDb::transform_read_by_pk(&kind_key, &state.inner.pool).await?;
+ state.emit_warning_native_action(
+ &vm,
+ NativeEventAction::Fail,
+ Some(format!("Process {name}")),
+ );
+ }
+ _ => {}
+ }
+ action.clone_into(&mut event.action);
+ }
"destroy" => {
state.spawn_emit_event(event);
let _ = ProcessDb::del_by_pk(&id, &state.inner.pool).await;
diff --git a/bin/nanocld/src/system/event.rs b/bin/nanocld/src/system/event.rs
index f3b4f7810..d22331bad 100644
--- a/bin/nanocld/src/system/event.rs
+++ b/bin/nanocld/src/system/event.rs
@@ -212,7 +212,7 @@ fn stopping(
/// and push the action into the task manager
/// The task manager will execute the action in background
/// eg: starting, deleting, updating a living object
-async fn _exec_event(e: &Event, state: &SystemState) -> IoResult<()> {
+pub async fn exec_event(e: &Event, state: &SystemState) -> IoResult<()> {
match e.kind {
EventKind::Error | EventKind::Warning => return Ok(()),
_ => {}
@@ -232,11 +232,12 @@ async fn _exec_event(e: &Event, state: &SystemState) -> IoResult<()> {
// This is to avoid data races conditions when manipulating an object
let task_key = format!("{}@{key}", &actor.kind);
let action = NativeEventAction::from_str(e.action.as_str())?;
- match actor.kind {
- EventActorKind::Cargo | EventActorKind::Vm => {
+ match (&actor.kind, &action) {
+ (EventActorKind::Cargo | EventActorKind::Vm, _) => {
state.inner.task_manager.wait_task(&task_key).await;
}
- EventActorKind::Job if action == NativeEventAction::Destroying => {
+ (EventActorKind::Job, NativeEventAction::Destroying) => {
+ log::debug!("Removing task for job {key}");
state.inner.task_manager.remove_task(&task_key).await;
}
_ => {}
@@ -273,13 +274,3 @@ async fn _exec_event(e: &Event, state: &SystemState) -> IoResult<()> {
.await;
Ok(())
}
-
-pub fn exec_event(e: &Event, state: &SystemState) {
- let e = e.clone();
- let state = state.clone();
- rt::spawn(async move {
- if let Err(err) = _exec_event(&e, &state).await {
- log::error!("exec_event: {err}");
- }
- });
-}
diff --git a/bin/nanocld/src/system/system_state.rs b/bin/nanocld/src/system/system_state.rs
index 7e3df3bc8..92499cca1 100644
--- a/bin/nanocld/src/system/system_state.rs
+++ b/bin/nanocld/src/system/system_state.rs
@@ -58,11 +58,13 @@ impl SystemState {
self.inner.arbiter.clone().exec_fn(move || {
rt::spawn(async move {
while let Some(e) = rx.next().await {
- super::exec_event(&e, &self);
- let self_ptr = self.clone();
+ if let Err(err) = super::exec_event(&e, &self).await {
+ log::error!("system::run: exec_event {err}");
+ }
+ let event_emitter_raw = self.inner.event_emitter_raw.clone();
rt::spawn(async move {
- if let Err(err) = self_ptr.inner.event_emitter_raw.emit(&e) {
- log::error!("system::run: raw emit {err}");
+ if let Err(err) = event_emitter_raw.emit(&e) {
+ log::error!("system::run: emit {err}");
}
});
}
diff --git a/bin/nanocld/src/tasks/task_manager.rs b/bin/nanocld/src/tasks/task_manager.rs
index 706f0a4f9..4d96bd41f 100644
--- a/bin/nanocld/src/tasks/task_manager.rs
+++ b/bin/nanocld/src/tasks/task_manager.rs
@@ -24,6 +24,7 @@ impl ObjTask {
pub async fn wait(&self) {
loop {
if self.fut.is_finished() {
+ log::debug!("Task finished: {}", self.kind);
break;
}
time::sleep(Duration::from_micros(10)).await;
@@ -60,7 +61,7 @@ impl TaskManager {
tasks.lock().await.remove(&key_ptr);
Ok::<_, IoError>(())
});
- self.tasks.lock().await.insert(key, new_task.clone());
+ self.tasks.lock().await.insert(key, new_task);
}
pub async fn remove_task(&self, key: &str) {
@@ -81,6 +82,7 @@ impl TaskManager {
pub async fn wait_task(&self, key: &str) {
if let Some(task) = self.get_task(key).await {
task.wait().await;
+ log::debug!("Task finished: {key} {} removing it", task.kind);
self.remove_task(key).await;
}
}
diff --git a/bin/nanocld/src/utils/container.rs b/bin/nanocld/src/utils/container.rs
index 069083226..ac5be05cf 100644
--- a/bin/nanocld/src/utils/container.rs
+++ b/bin/nanocld/src/utils/container.rs
@@ -20,13 +20,13 @@ use nanocl_error::{
use nanocl_stubs::{
cargo::{Cargo, CargoKillOptions},
generic::{GenericClause, GenericFilter, ImagePullPolicy},
+ vm::Vm,
job::Job,
process::{Process, ProcessKind, ProcessPartial},
system::{
EventActor, EventActorKind, EventKind, EventPartial, NativeEventAction,
ObjPsStatusKind,
},
- vm::Vm,
};
use crate::{
@@ -547,10 +547,6 @@ pub async fn stop_instances(
ProcessDb::read_by_kind_key(kind_pk, &state.inner.pool).await?;
log::debug!("stop_process_by_kind_pk: {kind_pk}");
for process in processes {
- let process_state = process.data.state.unwrap_or_default();
- if !process_state.running.unwrap_or_default() {
- return Ok(());
- }
state
.inner
.docker_api
@@ -581,10 +577,6 @@ pub async fn start_instances(
let processes =
ProcessDb::read_by_kind_key(kind_key, &state.inner.pool).await?;
for process in processes {
- let process_state = process.data.state.unwrap_or_default();
- if process_state.running.unwrap_or_default() {
- continue;
- }
state
.inner
.docker_api
diff --git a/bin/ncdns/specs/readme.md b/bin/ncdns/specs/readme.md
index abbec4cea..0000905fe 100644
--- a/bin/ncdns/specs/readme.md
+++ b/bin/ncdns/specs/readme.md
@@ -4,7 +4,7 @@ It is the `API` the `Nanocl Daemon` uses, to create / update and delete `DnsRule
## OpenAPI Specification
This API is documented in **OpenAPI format** using [Utoipa](https://github.com/juhaku/utoipa)
The specification is generated automatically when running in development only.
-When releasing a version, the generated file is transfered to our [Documentation](https://github.com/next-hat/documentation).
+When releasing a version, the generated file is transferred to our [Documentation](https://github.com/next-hat/documentation).
## Cross-Origin Resource Sharing
diff --git a/bin/ncdns/specs/swagger.yaml b/bin/ncdns/specs/swagger.yaml
index 89285b3da..e0058c729 100644
--- a/bin/ncdns/specs/swagger.yaml
+++ b/bin/ncdns/specs/swagger.yaml
@@ -8,7 +8,7 @@ info:
## OpenAPI Specification
This API is documented in **OpenAPI format** using [Utoipa](https://github.com/juhaku/utoipa)
The specification is generated automatically when running in development only.
- When releasing a version, the generated file is transfered to our [Documentation](https://github.com/next-hat/documentation).
+ When releasing a version, the generated file is transferred to our [Documentation](https://github.com/next-hat/documentation).
## Cross-Origin Resource Sharing
diff --git a/bin/ncproxy/specs/readme.md b/bin/ncproxy/specs/readme.md
index df3870611..7152b0c2c 100644
--- a/bin/ncproxy/specs/readme.md
+++ b/bin/ncproxy/specs/readme.md
@@ -4,7 +4,7 @@ It is the `API` the `Nanocl Daemon` uses, to create / update and delete `ncproxy
## OpenAPI Specification
This API is documented in **OpenAPI format** using [Utoipa](https://github.com/juhaku/utoipa)
The specification is generated automatically when running in development only.
-When releasing a version, the generated file is transfered to our [Documentation](https://github.com/next-hat/documentation).
+When releasing a version, the generated file is transferred to our [Documentation](https://github.com/next-hat/documentation).
## Cross-Origin Resource Sharing
diff --git a/bin/ncproxy/specs/swagger.yaml b/bin/ncproxy/specs/swagger.yaml
index 82ef7ff23..6cd29e3b9 100644
--- a/bin/ncproxy/specs/swagger.yaml
+++ b/bin/ncproxy/specs/swagger.yaml
@@ -8,7 +8,7 @@ info:
## OpenAPI Specification
This API is documented in **OpenAPI format** using [Utoipa](https://github.com/juhaku/utoipa)
The specification is generated automatically when running in development only.
- When releasing a version, the generated file is transfered to our [Documentation](https://github.com/next-hat/documentation).
+ When releasing a version, the generated file is transferred to our [Documentation](https://github.com/next-hat/documentation).
## Cross-Origin Resource Sharing
diff --git a/tests/e2e.bats b/tests/e2e.bats
new file mode 100755
index 000000000..694703bf7
--- /dev/null
+++ b/tests/e2e.bats
@@ -0,0 +1,42 @@
+#!/usr/bin/env bats
+
+@test "nanocl --version" {
+ run nanocl --version
+ [ "$status" -eq 0 ]
+}
+
+@test "nanocl version" {
+ run nanocl version
+ [ "$status" -eq 0 ]
+}
+
+@test "nanocl help" {
+ run nanocl help
+ [ "$status" -eq 0 ]
+}
+
+@test "nanocl cargo run" {
+ run nanocl cargo run test nginx:latest
+ [ "$status" -eq 0 ]
+}
+
+@test "nanocl cargo rm" {
+ run nanocl cargo rm -yf test
+ [ "$status" -eq 0 ]
+}
+
+@test "nanocl state apply -ys ./examples/deploy_example.yml" {
+ run nanocl state apply -ys ./examples/deploy_example.yml
+ [ "$status" -eq 0 ]
+}
+
+@test "curl --header \"Host: deploy-example.com\" 127.0.0.1" {
+ run sleep 1
+ run curl --header "Host: deploy-example.com" 127.0.0.1
+ [ "$status" -eq 0 ]
+}
+
+@test "nanocl state rm -ys ./examples/deploy_example.yml" {
+ run nanocl state rm -ys ./examples/deploy_example.yml
+ [ "$status" -eq 0 ]
+}