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 ] +}