From 96b0aa75c6c83fd0b4d94604b8f169b954db3e94 Mon Sep 17 00:00:00 2001 From: leone Date: Sat, 1 Jun 2024 18:24:55 +0200 Subject: [PATCH 1/9] feature/nanocld: better sql schema with indexes (#964) * feature/nanocld: better sql schema with indexes --- .vscode/settings.json | 5 ++++ Cargo.lock | 6 +++++ bin/nanocl/src/models/process.rs | 2 +- bin/nanocld/.env | 4 +++- bin/nanocld/Cargo.toml | 2 ++ .../up.sql | 8 +++++++ .../2022-05-20-134629_namespaces/up.sql | 7 +++++- .../migrations/2022-05-24-185444_specs/up.sql | 12 ++++++++-- .../2022-06-17-122356_cargos/up.sql | 7 ++++++ .../migrations/2022-08-04-214925_nodes/up.sql | 14 +++++++++-- .../2023-01-15-121652_resources/up.sql | 9 +++++++ .../2023-02-17-193350_metrics/up.sql | 14 ++++++++--- .../migrations/2023-03-10-234850_vms/up.sql | 24 ++++++++++++++++--- .../2023-10-04-045333_secrets/up.sql | 12 ++++++++-- .../migrations/2023-11-06-175428_jobs/up.sql | 11 +++++++-- .../2023-11-19-101132_processes/up.sql | 13 ++++++++-- .../down.sql | 2 -- .../2023-12-30-161201_metrics_add_note/up.sql | 2 -- .../2024-01-02-132813_events/up.sql | 19 ++++++++++++--- .../down.sql | 2 -- .../up.sql | 2 -- bin/nanocld/specs/swagger.yaml | 12 +++++++--- bin/nanocld/src/models/namespace.rs | 5 ++++ bin/nanocld/src/models/node.rs | 10 ++++++-- bin/nanocld/src/models/process.rs | 6 ++--- bin/nanocld/src/models/vm.rs | 12 +++++----- bin/nanocld/src/models/vm_image.rs | 2 ++ bin/nanocld/src/repositories/node.rs | 21 +++++++++++++--- bin/nanocld/src/repositories/process.rs | 4 ++-- bin/nanocld/src/schema.rs | 17 +++++++++---- bin/nanocld/src/services/event.rs | 5 +++- bin/nanocld/src/services/namespace.rs | 1 + bin/nanocld/src/services/vm_image.rs | 2 +- bin/nanocld/src/system/init.rs | 2 ++ bin/nanocld/src/utils/container.rs | 2 +- bin/nanocld/src/utils/mod.rs | 1 + bin/nanocld/src/utils/system.rs | 9 ++++--- bin/nanocld/src/utils/vm_image.rs | 7 ++++-- bin/ncproxy/src/subsystem/metric.rs | 3 ++- crates/nanocl_stubs/src/namespace.rs | 12 ++++++++++ crates/nanocl_stubs/src/process.rs | 8 +++---- crates/nanocld_client/src/namespace.rs | 5 +++- scripts/prepare_test_ci.sh | 1 + 43 files changed, 254 insertions(+), 70 deletions(-) delete mode 100644 bin/nanocld/migrations/2023-12-30-161201_metrics_add_note/down.sql delete mode 100644 bin/nanocld/migrations/2023-12-30-161201_metrics_add_note/up.sql delete mode 100644 bin/nanocld/migrations/2024-01-02-135246_metrics_rename_expire/down.sql delete mode 100644 bin/nanocld/migrations/2024-01-02-135246_metrics_rename_expire/up.sql diff --git a/.vscode/settings.json b/.vscode/settings.json index e1de0b6db..5873a2899 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -23,6 +23,7 @@ "Cgroupns", "chrono", "Clippy", + "cloudimg", "cockroachdb", "codecov", "consts", @@ -38,12 +39,16 @@ "freeifaddrs", "gethostname", "getifaddrs", + "rowid", + "Inet", + "Timestamptz", "Healthcheck", "iface", "ifaddrs", "indicatif", "Insertable", "ipam", + "ipnet", "jsonschema", "keygen", "letsencrypt", diff --git a/Cargo.lock b/Cargo.lock index e52d06a6e..0ee540f75 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -831,7 +831,9 @@ dependencies = [ "byteorder", "chrono", "diesel_derives", + "ipnet", "itoa", + "libc", "pq-sys", "r2d2", "serde_json", @@ -1683,6 +1685,9 @@ name = "ipnet" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +dependencies = [ + "serde", +] [[package]] name = "is_terminal_polyfill" @@ -2125,6 +2130,7 @@ dependencies = [ "env_logger", "futures", "futures-util", + "ipnet", "jsonschema", "libc", "log", diff --git a/bin/nanocl/src/models/process.rs b/bin/nanocl/src/models/process.rs index 92596fedf..cc6bf2488 100644 --- a/bin/nanocl/src/models/process.rs +++ b/bin/nanocl/src/models/process.rs @@ -133,7 +133,7 @@ impl From for ProcessRow { key: process.key, name: name.to_owned(), image: config.image.unwrap_or_default(), - node: process.node_key, + node: process.node_name, status, ip: ip_addr, created_at, diff --git a/bin/nanocld/.env b/bin/nanocld/.env index 8da786af0..4519b5ee5 100644 --- a/bin/nanocld/.env +++ b/bin/nanocld/.env @@ -1 +1,3 @@ -DATABASE_URL=postgres://root:root@store.nanocl.internal:26258/defaultdb?sslmode=verify-full&sslcert=$HOME/.nanocl_dev/state/store/certs/client.root.crt&sslkey=$HOME/.nanocl_dev/state/store/certs/client.root.key&sslrootcert=$HOME/.nanocl_dev/state/store/certs/ca.crt +DATABASE_URL=postgres://root:root@store.nanocl.internal:26258/defaultdb? + +#sslmode=verify-full&sslcert=$HOME/.nanocl_dev/state/store/certs/client.root.crt&sslkey=$HOME/.nanocl_dev/state/store/certs/client.root.key&sslrootcert=$HOME/.nanocl_dev/state/store/certs/ca.crt diff --git a/bin/nanocld/Cargo.toml b/bin/nanocld/Cargo.toml index eb4122d7b..8e776c629 100644 --- a/bin/nanocld/Cargo.toml +++ b/bin/nanocld/Cargo.toml @@ -66,6 +66,7 @@ diesel = { version = "2.1", features = [ "chrono", "uuid", "serde_json", + "ipnet-address", "i-implement-a-third-party-backend-and-opt-into-breaking-changes", ] } tokio = { version = "1.36", features = ["fs", "process", "io-std"] } @@ -87,3 +88,4 @@ notify = "6.1" ntex-cors = "2" rand = "0.8" openssl = { version = "0.10" } +ipnet = { version = "2.9.0", features = ["serde"] } diff --git a/bin/nanocld/migrations/2022-01-10-150631_object_process_statuses/up.sql b/bin/nanocld/migrations/2022-01-10-150631_object_process_statuses/up.sql index 9c99329f0..f863ef972 100644 --- a/bin/nanocld/migrations/2022-01-10-150631_object_process_statuses/up.sql +++ b/bin/nanocld/migrations/2022-01-10-150631_object_process_statuses/up.sql @@ -8,3 +8,11 @@ CREATE TABLE IF NOT EXISTS "object_process_statuses" ( "actual" VARCHAR NOT NULL, "prev_actual" VARCHAR NOT NULL ); + +CREATE INDEX "object_process_statuses_key_idx" ON "object_process_statuses" ("key"); +CREATE INDEX "object_process_statuses_created_at_idx" ON "object_process_statuses" ("created_at"); +CREATE INDEX "object_process_statuses_updated_at_idx" ON "object_process_statuses" ("updated_at"); +CREATE INDEX "object_process_statuses_wanted_idx" ON "object_process_statuses" ("wanted"); +CREATE INDEX "object_process_statuses_prev_wanted_idx" ON "object_process_statuses" ("prev_wanted"); +CREATE INDEX "object_process_statuses_actual_idx" ON "object_process_statuses" ("actual"); +CREATE INDEX "object_process_statuses_prev_actual_idx" ON "object_process_statuses" ("prev_actual"); diff --git a/bin/nanocld/migrations/2022-05-20-134629_namespaces/up.sql b/bin/nanocld/migrations/2022-05-20-134629_namespaces/up.sql index b3f759279..39cb414fa 100644 --- a/bin/nanocld/migrations/2022-05-20-134629_namespaces/up.sql +++ b/bin/nanocld/migrations/2022-05-20-134629_namespaces/up.sql @@ -1,5 +1,10 @@ -- Your SQL goes here CREATE TABLE IF NOT EXISTS "namespaces" ( "name" VARCHAR NOT NULL UNIQUE PRIMARY KEY, - "created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW() + "created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(), + "metadata" JSONB ); + +CREATE INDEX "namespaces_name_idx" ON "namespaces" ("name"); +CREATE INDEX "namespaces_created_at_idx" ON "namespaces" ("created_at"); +CREATE INDEX "namespaces_metadata_idx" ON "namespaces" USING GIN ("metadata"); diff --git a/bin/nanocld/migrations/2022-05-24-185444_specs/up.sql b/bin/nanocld/migrations/2022-05-24-185444_specs/up.sql index 6f01dedc1..d66d22c5e 100644 --- a/bin/nanocld/migrations/2022-05-24-185444_specs/up.sql +++ b/bin/nanocld/migrations/2022-05-24-185444_specs/up.sql @@ -5,6 +5,14 @@ CREATE TABLE IF NOT EXISTS "specs" ( "kind_name" VARCHAR NOT NULL, "kind_key" VARCHAR NOT NULL, "version" VARCHAR NOT NULL, - "data" JSON NOT NULL, - "metadata" JSON + "data" JSONB NOT NULL, + "metadata" JSONB ); + +CREATE INDEX "specs_key_idx" ON "specs" ("key"); +CREATE INDEX "specs_created_at_idx" ON "specs" ("created_at"); +CREATE INDEX "specs_kind_name_idx" ON "specs" ("kind_name"); +CREATE INDEX "specs_kind_key_idx" ON "specs" ("kind_key"); +CREATE INDEX "specs_version_idx" ON "specs" ("version"); +CREATE INDEX "specs_data_idx" ON "specs" USING GIN ("data"); +CREATE INDEX "specs_metadata_idx" ON "specs" USING GIN ("metadata"); diff --git a/bin/nanocld/migrations/2022-06-17-122356_cargos/up.sql b/bin/nanocld/migrations/2022-06-17-122356_cargos/up.sql index 3ece57281..fdfe7013b 100644 --- a/bin/nanocld/migrations/2022-06-17-122356_cargos/up.sql +++ b/bin/nanocld/migrations/2022-06-17-122356_cargos/up.sql @@ -6,3 +6,10 @@ CREATE TABLE IF NOT EXISTS "cargoes" ( "status_key" VARCHAR NOT NULL REFERENCES object_process_statuses("key"), "namespace_name" VARCHAR NOT NULL REFERENCES namespaces("name") ); + +CREATE INDEX "cargoes_key_idx" ON "cargoes" ("key"); +CREATE INDEX "cargoes_created_at_idx" ON "cargoes" ("created_at"); +CREATE INDEX "cargoes_name_idx" ON "cargoes" ("name"); +CREATE INDEX "cargoes_spec_key_idx" ON "cargoes" ("spec_key"); +CREATE INDEX "cargoes_status_key_idx" ON "cargoes" ("status_key"); +CREATE INDEX "cargoes_namespace_name_idx" ON "cargoes" ("namespace_name"); diff --git a/bin/nanocld/migrations/2022-08-04-214925_nodes/up.sql b/bin/nanocld/migrations/2022-08-04-214925_nodes/up.sql index 7a690e04a..b642b5bfd 100644 --- a/bin/nanocld/migrations/2022-08-04-214925_nodes/up.sql +++ b/bin/nanocld/migrations/2022-08-04-214925_nodes/up.sql @@ -1,8 +1,11 @@ -- Your SQL goes here CREATE TABLE IF NOT EXISTS "nodes" ( "name" VARCHAR NOT NULL UNIQUE PRIMARY KEY, - "ip_address" VARCHAR NOT NULL UNIQUE, - "created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW() + "created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(), + "ip_address" INET NOT NULL UNIQUE, + "endpoint" VARCHAR NOT NULL, + "version" VARCHAR NOT NULL, + "metadata" JSONB ); CREATE TABLE IF NOT EXISTS "node_groups" ( @@ -13,3 +16,10 @@ CREATE TABLE IF NOT EXISTS "node_group_links" ( "node_name" VARCHAR NOT NULL REFERENCES "nodes" ("name"), "node_group_name" VARCHAR NOT NULL REFERENCES "node_groups" ("name") ); + +CREATE INDEX "nodes_name_idx" ON "nodes" ("name"); +CREATE INDEX "nodes_created_at_idx" ON "nodes" ("created_at"); +CREATE INDEX "nodes_ip_address_idx" ON "nodes" ("ip_address"); +CREATE INDEX "nodes_endpoint_idx" ON "nodes" ("endpoint"); +CREATE INDEX "nodes_version_idx" ON "nodes" ("version"); +CREATE INDEX "nodes_metadata_idx" ON "nodes" USING GIN ("metadata"); diff --git a/bin/nanocld/migrations/2023-01-15-121652_resources/up.sql b/bin/nanocld/migrations/2023-01-15-121652_resources/up.sql index 2251d112a..6eccceb93 100644 --- a/bin/nanocld/migrations/2023-01-15-121652_resources/up.sql +++ b/bin/nanocld/migrations/2023-01-15-121652_resources/up.sql @@ -5,9 +5,18 @@ CREATE TABLE IF NOT EXISTS "resource_kinds" ( "spec_key" UUID NOT NULL REFERENCES specs("key") ); +CREATE INDEX "resource_kinds_name_idx" ON "resource_kinds" ("name"); +CREATE INDEX "resource_kinds_created_at_idx" ON "resource_kinds" ("created_at"); +CREATE INDEX "resource_kinds_spec_key_idx" ON "resource_kinds" ("spec_key"); + CREATE TABLE IF NOT EXISTS "resources" ( "key" VARCHAR NOT NULL UNIQUE PRIMARY KEY, "created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(), "kind" VARCHAR NOT NULL, "spec_key" UUID NOT NULL REFERENCES specs("key") ); + +CREATE INDEX "resources_key_idx" ON "resources" ("key"); +CREATE INDEX "resources_created_at_idx" ON "resources" ("created_at"); +CREATE INDEX "resources_kind_idx" ON "resources" ("kind"); +CREATE INDEX "resources_spec_key_idx" ON "resources" ("spec_key"); diff --git a/bin/nanocld/migrations/2023-02-17-193350_metrics/up.sql b/bin/nanocld/migrations/2023-02-17-193350_metrics/up.sql index 896700145..20cec38ba 100644 --- a/bin/nanocld/migrations/2023-02-17-193350_metrics/up.sql +++ b/bin/nanocld/migrations/2023-02-17-193350_metrics/up.sql @@ -2,8 +2,16 @@ CREATE TABLE IF NOT EXISTS "metrics" ( "key" UUID PRIMARY KEY DEFAULT uuid_generate_v4(), "created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(), - "expire_at" TIMESTAMPTZ NOT NULL DEFAULT NOW() + INTERVAL '4 month', + "expires_at" TIMESTAMPTZ NOT NULL DEFAULT NOW() + INTERVAL '4 month', "node_name" VARCHAR NOT NULL, "kind" VARCHAR NOT NULL, - "data" JSON NOT NULL -) WITH (ttl_expiration_expression = 'expire_at'); + "data" JSONB NOT NULL, + "note" VARCHAR +) WITH (ttl_expiration_expression = 'expires_at'); + +CREATE INDEX "metrics_key_idx" ON "metrics" ("key"); +CREATE INDEX "metrics_created_at_idx" ON "metrics" ("created_at"); +CREATE INDEX "metrics_expires_at_idx" ON "metrics" ("expires_at"); +CREATE INDEX "metrics_node_name_idx" ON "metrics" ("node_name"); +CREATE INDEX "metrics_kind_idx" ON "metrics" ("kind"); +CREATE INDEX "metrics_data_idx" ON "metrics" USING GIN ("data"); diff --git a/bin/nanocld/migrations/2023-03-10-234850_vms/up.sql b/bin/nanocld/migrations/2023-03-10-234850_vms/up.sql index d870b5cf8..1176235b7 100644 --- a/bin/nanocld/migrations/2023-03-10-234850_vms/up.sql +++ b/bin/nanocld/migrations/2023-03-10-234850_vms/up.sql @@ -1,6 +1,7 @@ -- Your SQL goes here CREATE TABLE IF NOT EXISTS "vm_images" ( "name" VARCHAR NOT NULL PRIMARY KEY, + "node_name" VARCHAR NOT NULL REFERENCES nodes("name"), "created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(), "kind" VARCHAR NOT NULL, "path" VARCHAR NOT NULL, @@ -10,11 +11,28 @@ CREATE TABLE IF NOT EXISTS "vm_images" ( "parent" VARCHAR REFERENCES vm_images("name") ); +CREATE INDEX "vm_images_name_idx" ON "vm_images" ("name"); +CREATE INDEX "vm_images_node_name_idx" ON "vm_images" ("node_name"); +CREATE INDEX "vm_images_created_at_idx" ON "vm_images" ("created_at"); +CREATE INDEX "vm_images_kind_idx" ON "vm_images" ("kind"); +CREATE INDEX "vm_images_path_idx" ON "vm_images" ("path"); +CREATE INDEX "vm_images_format_idx" ON "vm_images" ("format"); +CREATE INDEX "vm_images_size_actual_idx" ON "vm_images" ("size_actual"); +CREATE INDEX "vm_images_size_virtual_idx" ON "vm_images" ("size_virtual"); +CREATE INDEX "vm_images_parent_idx" ON "vm_images" ("parent"); + CREATE TABLE IF NOT EXISTS "vms" ( "key" VARCHAR NOT NULL UNIQUE PRIMARY KEY, - "created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(), "name" VARCHAR NOT NULL, - "spec_key" UUID NOT NULL REFERENCES specs("key"), + "created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(), + "namespace_name" VARCHAR NOT NULL REFERENCES namespaces("name"), "status_key" VARCHAR NOT NULL REFERENCES object_process_statuses("key"), - "namespace_name" VARCHAR NOT NULL REFERENCES namespaces("name") + "spec_key" UUID NOT NULL REFERENCES specs("key") ); + +CREATE INDEX "vms_key_idx" ON "vms" ("key"); +CREATE INDEX "vms_name_idx" ON "vms" ("name"); +CREATE INDEX "vms_created_at_idx" ON "vms" ("created_at"); +CREATE INDEX "vms_namespace_name_idx" ON "vms" ("namespace_name"); +CREATE INDEX "vms_status_key_idx" ON "vms" ("status_key"); +CREATE INDEX "vms_spec_key_idx" ON "vms" ("spec_key"); diff --git a/bin/nanocld/migrations/2023-10-04-045333_secrets/up.sql b/bin/nanocld/migrations/2023-10-04-045333_secrets/up.sql index eb71c3284..edb8b8101 100644 --- a/bin/nanocld/migrations/2023-10-04-045333_secrets/up.sql +++ b/bin/nanocld/migrations/2023-10-04-045333_secrets/up.sql @@ -5,6 +5,14 @@ CREATE TABLE IF NOT EXISTS "secrets" ( "updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(), "kind" VARCHAR NOT NULL, "immutable" BOOLEAN NOT NULL DEFAULT FALSE, - "data" JSON NOT NULL, - "metadata" JSON + "data" JSONB NOT NULL, + "metadata" JSONB ); + +CREATE INDEX "secrets_key_idx" ON "secrets" ("key"); +CREATE INDEX "secrets_created_at_idx" ON "secrets" ("created_at"); +CREATE INDEX "secrets_updated_at_idx" ON "secrets" ("updated_at"); +CREATE INDEX "secrets_kind_idx" ON "secrets" ("kind"); +CREATE INDEX "secrets_immutable_idx" ON "secrets" ("immutable"); +CREATE INDEX "secrets_data_idx" ON "secrets" USING GIN ("data"); +CREATE INDEX "secrets_metadata_idx" ON "secrets" USING GIN ("metadata"); diff --git a/bin/nanocld/migrations/2023-11-06-175428_jobs/up.sql b/bin/nanocld/migrations/2023-11-06-175428_jobs/up.sql index 4541fac0f..4205ce532 100644 --- a/bin/nanocld/migrations/2023-11-06-175428_jobs/up.sql +++ b/bin/nanocld/migrations/2023-11-06-175428_jobs/up.sql @@ -4,6 +4,13 @@ CREATE TABLE IF NOT EXISTS "jobs" ( "created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(), "updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(), "status_key" VARCHAR NOT NULL REFERENCES object_process_statuses("key"), - "data" JSON NOT NULL, - "metadata" JSON + "data" JSONB NOT NULL, + "metadata" JSONB ); + +CREATE INDEX "jobs_key_idx" ON "jobs" ("key"); +CREATE INDEX "jobs_created_at_idx" ON "jobs" ("created_at"); +CREATE INDEX "jobs_updated_at_idx" ON "jobs" ("updated_at"); +CREATE INDEX "jobs_status_key_idx" ON "jobs" ("status_key"); +CREATE INDEX "jobs_data_idx" ON "jobs" USING GIN ("data"); +CREATE INDEX "jobs_metadata_idx" ON "jobs" USING GIN ("metadata"); diff --git a/bin/nanocld/migrations/2023-11-19-101132_processes/up.sql b/bin/nanocld/migrations/2023-11-19-101132_processes/up.sql index f6a1687b4..26c46bb0a 100644 --- a/bin/nanocld/migrations/2023-11-19-101132_processes/up.sql +++ b/bin/nanocld/migrations/2023-11-19-101132_processes/up.sql @@ -5,7 +5,16 @@ CREATE TABLE IF NOT EXISTS "processes" ( "updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(), "name" VARCHAR NOT NULL, "kind" VARCHAR NOT NULL, - "data" JSON NOT NULL, - "node_key" VARCHAR NOT NULL, + "data" JSONB NOT NULL, + "node_name" VARCHAR NOT NULL REFERENCES nodes("name"), "kind_key" VARCHAR NOT NULL ); + +CREATE INDEX "processes_key_idx" ON "processes" ("key"); +CREATE INDEX "processes_created_at_idx" ON "processes" ("created_at"); +CREATE INDEX "processes_updated_at_idx" ON "processes" ("updated_at"); +CREATE INDEX "processes_name_idx" ON "processes" ("name"); +CREATE INDEX "processes_kind_idx" ON "processes" ("kind"); +CREATE INDEX "processes_data_idx" ON "processes" USING GIN ("data"); +CREATE INDEX "processes_node_name_idx" ON "processes" ("node_name"); +CREATE INDEX "processes_kind_key_idx" ON "processes" ("kind_key"); diff --git a/bin/nanocld/migrations/2023-12-30-161201_metrics_add_note/down.sql b/bin/nanocld/migrations/2023-12-30-161201_metrics_add_note/down.sql deleted file mode 100644 index 0458c7dae..000000000 --- a/bin/nanocld/migrations/2023-12-30-161201_metrics_add_note/down.sql +++ /dev/null @@ -1,2 +0,0 @@ --- This file should undo anything in `up.sql` -ALTER TABLE IF EXISTS "metrics" DROP COLUMN "note"; diff --git a/bin/nanocld/migrations/2023-12-30-161201_metrics_add_note/up.sql b/bin/nanocld/migrations/2023-12-30-161201_metrics_add_note/up.sql deleted file mode 100644 index ad1e33138..000000000 --- a/bin/nanocld/migrations/2023-12-30-161201_metrics_add_note/up.sql +++ /dev/null @@ -1,2 +0,0 @@ --- Your SQL goes here -ALTER TABLE IF EXISTS "metrics" ADD COLUMN "note" VARCHAR; diff --git a/bin/nanocld/migrations/2024-01-02-132813_events/up.sql b/bin/nanocld/migrations/2024-01-02-132813_events/up.sql index 3ba705201..6373238f4 100644 --- a/bin/nanocld/migrations/2024-01-02-132813_events/up.sql +++ b/bin/nanocld/migrations/2024-01-02-132813_events/up.sql @@ -9,7 +9,20 @@ CREATE TABLE IF NOT EXISTS "events" ( "action" VARCHAR NOT NULL, "reason" VARCHAR NOT NULL, "note" VARCHAR, - "actor" JSON, - "related" JSON, - "metadata" JSON + "actor" JSONB, + "related" JSONB, + "metadata" JSONB ) WITH (ttl_expiration_expression = 'expires_at'); + +CREATE INDEX "events_key_idx" ON "events" ("key"); +CREATE INDEX "events_created_at_idx" ON "events" ("created_at"); +CREATE INDEX "events_expires_at_idx" ON "events" ("expires_at"); +CREATE INDEX "events_reporting_node_idx" ON "events" ("reporting_node"); +CREATE INDEX "events_reporting_controller_idx" ON "events" ("reporting_controller"); +CREATE INDEX "events_kind_idx" ON "events" ("kind"); +CREATE INDEX "events_action_idx" ON "events" ("action"); +CREATE INDEX "events_reason_idx" ON "events" ("reason"); +CREATE INDEX "events_note_idx" ON "events" ("note"); +CREATE INDEX "events_actor_idx" ON "events" USING GIN ("actor"); +CREATE INDEX "events_related_idx" ON "events" USING GIN ("related"); +CREATE INDEX "events_metadata_idx" ON "events" USING GIN ("metadata"); diff --git a/bin/nanocld/migrations/2024-01-02-135246_metrics_rename_expire/down.sql b/bin/nanocld/migrations/2024-01-02-135246_metrics_rename_expire/down.sql deleted file mode 100644 index 7357b0954..000000000 --- a/bin/nanocld/migrations/2024-01-02-135246_metrics_rename_expire/down.sql +++ /dev/null @@ -1,2 +0,0 @@ --- This file should undo anything in `up.sql` -ALTER TABLE IF EXISTS "metrics" RENAME COLUMN "expires_at" TO "expire_at"; diff --git a/bin/nanocld/migrations/2024-01-02-135246_metrics_rename_expire/up.sql b/bin/nanocld/migrations/2024-01-02-135246_metrics_rename_expire/up.sql deleted file mode 100644 index 6d7bcdb88..000000000 --- a/bin/nanocld/migrations/2024-01-02-135246_metrics_rename_expire/up.sql +++ /dev/null @@ -1,2 +0,0 @@ --- Your SQL goes here -ALTER TABLE IF EXISTS "metrics" RENAME COLUMN "expire_at" TO "expires_at"; diff --git a/bin/nanocld/specs/swagger.yaml b/bin/nanocld/specs/swagger.yaml index ee3f5a9fa..0d680ee85 100644 --- a/bin/nanocld/specs/swagger.yaml +++ b/bin/nanocld/specs/swagger.yaml @@ -5098,6 +5098,9 @@ components: type: string format: date-time description: When the namespace was created + Metadata: + description: User defined metadata + nullable: true NamespaceInspect: type: object description: |- @@ -5129,6 +5132,9 @@ components: Name: type: string description: Name of the namespace + Metadata: + description: User defined metadata + nullable: true additionalProperties: false NamespaceSummary: type: object @@ -5599,7 +5605,7 @@ components: - UpdatedAt - Name - Kind - - NodeKey + - NodeName - KindKey - Data properties: @@ -5619,9 +5625,9 @@ components: description: Name of the process Kind: $ref: '#/components/schemas/ProcessKind' - NodeKey: + NodeName: type: string - description: Key of the node where the container is running + description: Name of the node where the container is running KindKey: type: string description: Key of the related kind diff --git a/bin/nanocld/src/models/namespace.rs b/bin/nanocld/src/models/namespace.rs index 5cf7b08ee..aceb8d53a 100644 --- a/bin/nanocld/src/models/namespace.rs +++ b/bin/nanocld/src/models/namespace.rs @@ -19,6 +19,8 @@ pub struct NamespaceDb { pub name: String, /// When the namespace was created pub created_at: chrono::NaiveDateTime, + /// User defined metadata + pub metadata: Option, } impl NamespaceDb { @@ -27,6 +29,7 @@ impl NamespaceDb { Self { name: name.to_owned(), created_at: chrono::Utc::now().naive_utc(), + metadata: None, } } } @@ -36,6 +39,7 @@ impl From<&NamespacePartial> for NamespaceDb { Self { name: p.name.clone(), created_at: chrono::Utc::now().naive_utc(), + metadata: p.metadata.clone(), } } } @@ -45,6 +49,7 @@ impl From for Namespace { Self { name: namespace.name, created_at: namespace.created_at, + metadata: namespace.metadata, } } } diff --git a/bin/nanocld/src/models/node.rs b/bin/nanocld/src/models/node.rs index f47fa8d2d..d3d020879 100644 --- a/bin/nanocld/src/models/node.rs +++ b/bin/nanocld/src/models/node.rs @@ -14,8 +14,14 @@ use crate::schema::nodes; pub struct NodeDb { /// The name of the node pub name: String, - /// The ip address of the node - pub ip_address: String, /// The created at date pub created_at: chrono::NaiveDateTime, + /// The ip address of the node + pub ip_address: ipnet::IpNet, + /// Endpoint to connect to the node + pub endpoint: String, + /// Version of the node + pub version: String, + /// User defined metadata + pub metadata: Option, } diff --git a/bin/nanocld/src/models/process.rs b/bin/nanocld/src/models/process.rs index 98afb4e76..33d1e0b63 100644 --- a/bin/nanocld/src/models/process.rs +++ b/bin/nanocld/src/models/process.rs @@ -24,7 +24,7 @@ pub struct ProcessDb { /// The data of the process a ContainerInspect pub data: serde_json::Value, /// Id of the node where the container is running - pub node_key: String, + pub node_name: String, /// Id of the related kind pub kind_key: String, } @@ -54,7 +54,7 @@ impl TryFrom for Process { kind: ProcessKind::try_from(model.kind)?, data: serde_json::from_value(model.data) .map_err(|err| err.map_err_context(|| "Process"))?, - node_key: model.node_key, + node_name: model.node_name, kind_key: model.kind_key, }) } @@ -67,7 +67,7 @@ impl From<&ProcessPartial> for ProcessDb { name: model.name.clone(), kind: model.kind.to_string(), data: model.data.clone(), - node_key: model.node_key.clone(), + node_name: model.node_name.clone(), kind_key: model.kind_key.clone(), created_at: model .created_at diff --git a/bin/nanocld/src/models/vm.rs b/bin/nanocld/src/models/vm.rs index b0b440cf9..faca5dc9b 100644 --- a/bin/nanocld/src/models/vm.rs +++ b/bin/nanocld/src/models/vm.rs @@ -17,16 +17,16 @@ use super::NamespaceDb; pub struct VmDb { /// The key of the vm pub key: String, - /// The created at date - pub created_at: chrono::NaiveDateTime, /// The name of the vm pub name: String, - /// The spec key reference - pub spec_key: uuid::Uuid, - /// The status key - pub status_key: String, + /// The created at date + pub created_at: chrono::NaiveDateTime, /// The namespace name reference pub namespace_name: String, + /// The status key + pub status_key: String, + /// The spec key reference + pub spec_key: uuid::Uuid, } /// This structure is used to update a vm in the database. diff --git a/bin/nanocld/src/models/vm_image.rs b/bin/nanocld/src/models/vm_image.rs index ad0fef5bf..fc3569844 100644 --- a/bin/nanocld/src/models/vm_image.rs +++ b/bin/nanocld/src/models/vm_image.rs @@ -22,6 +22,8 @@ use crate::schema::vm_images; pub struct VmImageDb { /// The name of the virtual machine image pub name: String, + /// The node where the image is stored + pub node_name: String, /// The created at date pub created_at: chrono::NaiveDateTime, /// The kind of the virtual machine image (Base, Snapshot) diff --git a/bin/nanocld/src/repositories/node.rs b/bin/nanocld/src/repositories/node.rs index 9b1b10d3b..7c13d700c 100644 --- a/bin/nanocld/src/repositories/node.rs +++ b/bin/nanocld/src/repositories/node.rs @@ -1,8 +1,8 @@ -use std::collections::HashMap; +use std::{net::IpAddr, collections::HashMap}; use diesel::prelude::*; -use nanocl_error::io::IoResult; +use nanocl_error::io::{IoError, IoResult}; use nanocl_stubs::generic::GenericFilter; @@ -10,6 +10,7 @@ use crate::{ gen_sql_multiple, gen_sql_order_by, gen_sql_query, models::{ColumnType, NodeDb, Pool, SystemState}, schema::nodes, + vars, }; use super::generic::*; @@ -80,10 +81,24 @@ impl NodeDb { } pub async fn register(state: &SystemState) -> IoResult<()> { + println!("DEBUG: Registering node {}", state.inner.config.gateway); + let ip_address = + state + .inner + .config + .gateway + .parse::() + .map_err(|err| { + IoError::invalid_data("Invalid gateway", err.to_string().as_str()) + })?; + let ip_address = ipnet::IpNet::from(ip_address); let node = NodeDb { name: state.inner.config.hostname.clone(), - ip_address: state.inner.config.gateway.clone(), + ip_address, + endpoint: state.inner.config.advertise_addr.clone(), created_at: chrono::Utc::now().naive_utc(), + version: vars::VERSION.to_owned(), + metadata: None, }; NodeDb::create_if_not_exists(&node, &state.inner.pool).await?; Ok(()) diff --git a/bin/nanocld/src/repositories/process.rs b/bin/nanocld/src/repositories/process.rs index fb6f53b59..df65ad662 100644 --- a/bin/nanocld/src/repositories/process.rs +++ b/bin/nanocld/src/repositories/process.rs @@ -11,8 +11,8 @@ use nanocl_stubs::{ use crate::{ gen_sql_multiple, gen_sql_order_by, gen_sql_query, - models::{ColumnType, Pool, ProcessDb, ProcessUpdateDb}, schema::processes, + models::{ColumnType, Pool, ProcessDb, ProcessUpdateDb}, }; use super::generic::*; @@ -23,7 +23,7 @@ impl RepositoryBase for ProcessDb { ("key", (ColumnType::Text, "processes.key")), ("name", (ColumnType::Text, "processes.name")), ("kind", (ColumnType::Text, "processes.kind")), - ("node_key", (ColumnType::Text, "processes.node_key")), + ("node_name", (ColumnType::Text, "processes.node_name")), ("kind_key", (ColumnType::Text, "processes.kind_key")), ("data", (ColumnType::Json, "processes.data")), ( diff --git a/bin/nanocld/src/schema.rs b/bin/nanocld/src/schema.rs index ae198e89f..aa2a34ce4 100644 --- a/bin/nanocld/src/schema.rs +++ b/bin/nanocld/src/schema.rs @@ -55,6 +55,7 @@ diesel::table! { namespaces (name) { name -> Varchar, created_at -> Timestamptz, + metadata -> Nullable, } } @@ -75,8 +76,11 @@ diesel::table! { diesel::table! { nodes (name) { name -> Varchar, - ip_address -> Varchar, created_at -> Timestamptz, + ip_address -> Inet, + endpoint -> Varchar, + version -> Varchar, + metadata -> Nullable, } } @@ -100,7 +104,7 @@ diesel::table! { name -> Varchar, kind -> Varchar, data -> Jsonb, - node_key -> Varchar, + node_name -> Varchar, kind_key -> Varchar, } } @@ -149,6 +153,7 @@ diesel::table! { diesel::table! { vm_images (name) { name -> Varchar, + node_name -> Varchar, created_at -> Timestamptz, kind -> Varchar, path -> Varchar, @@ -162,11 +167,11 @@ diesel::table! { diesel::table! { vms (key) { key -> Varchar, - created_at -> Timestamptz, name -> Varchar, - spec_key -> Uuid, - status_key -> Varchar, + created_at -> Timestamptz, namespace_name -> Varchar, + status_key -> Varchar, + spec_key -> Uuid, } } @@ -176,8 +181,10 @@ diesel::joinable!(cargoes -> specs (spec_key)); diesel::joinable!(jobs -> object_process_statuses (status_key)); diesel::joinable!(node_group_links -> node_groups (node_group_name)); diesel::joinable!(node_group_links -> nodes (node_name)); +diesel::joinable!(processes -> nodes (node_name)); diesel::joinable!(resource_kinds -> specs (spec_key)); diesel::joinable!(resources -> specs (spec_key)); +diesel::joinable!(vm_images -> nodes (node_name)); diesel::joinable!(vms -> namespaces (namespace_name)); diesel::joinable!(vms -> object_process_statuses (status_key)); diesel::joinable!(vms -> specs (spec_key)); diff --git a/bin/nanocld/src/services/event.rs b/bin/nanocld/src/services/event.rs index f20561730..201eb9d63 100644 --- a/bin/nanocld/src/services/event.rs +++ b/bin/nanocld/src/services/event.rs @@ -193,6 +193,9 @@ mod tests { None::, ) .await; - assert!(wait_task.await.is_ok()) + assert!(wait_task.await.is_ok()); + let _ = client + .send_delete(&format!("/cargoes/{CARGO_NAME}"), None::) + .await; } } diff --git a/bin/nanocld/src/services/namespace.rs b/bin/nanocld/src/services/namespace.rs index 104db6639..70151eb5f 100644 --- a/bin/nanocld/src/services/namespace.rs +++ b/bin/nanocld/src/services/namespace.rs @@ -149,6 +149,7 @@ mod test_namespace { async fn create(client: &TestClient) { let new_namespace = NamespacePartial { name: String::from("controller-default"), + metadata: None, }; let res = client .send_post(ENDPOINT, Some(new_namespace), None::) diff --git a/bin/nanocld/src/services/vm_image.rs b/bin/nanocld/src/services/vm_image.rs index aad8cab31..2eca017e3 100644 --- a/bin/nanocld/src/services/vm_image.rs +++ b/bin/nanocld/src/services/vm_image.rs @@ -90,7 +90,7 @@ pub async fn import_vm_image( )) })?; } - utils::vm_image::create(&name, &filepath, &state.inner.pool).await?; + utils::vm_image::create(&name, &filepath, &state).await?; Ok(web::HttpResponse::Ok().into()) } diff --git a/bin/nanocld/src/system/init.rs b/bin/nanocld/src/system/init.rs index 4b9cd76d1..3aa103e39 100644 --- a/bin/nanocld/src/system/init.rs +++ b/bin/nanocld/src/system/init.rs @@ -165,6 +165,8 @@ mod tests { "postgresql://root:root@store.nanocl.internal:26258/defaultdb" .to_owned(), ), + hostname: Some("init-test.nanocl.io".to_owned()), + gateway: Some("127.0.0.1".to_owned()), conf_dir: String::from("/etc/nanocl"), nodes: Vec::default(), ..Default::default() diff --git a/bin/nanocld/src/utils/container.rs b/bin/nanocld/src/utils/container.rs index 8e12923f1..d93b66793 100644 --- a/bin/nanocld/src/utils/container.rs +++ b/bin/nanocld/src/utils/container.rs @@ -222,7 +222,7 @@ pub async fn create_instance( kind: kind.clone(), data: serde_json::to_value(&inspect) .map_err(|err| err.map_err_context(|| "CreateProcess"))?, - node_key: state.inner.config.hostname.clone(), + node_name: state.inner.config.hostname.clone(), kind_key: kind_key.to_owned(), created_at: Some( chrono::NaiveDateTime::parse_from_str( diff --git a/bin/nanocld/src/utils/mod.rs b/bin/nanocld/src/utils/mod.rs index 3dcef33ef..de728625b 100644 --- a/bin/nanocld/src/utils/mod.rs +++ b/bin/nanocld/src/utils/mod.rs @@ -50,6 +50,7 @@ pub mod tests { let config = DaemonConfig { state_dir: format!("{home}/.nanocl_dev/state"), docker_host, + hostname: "nanocl.internal".to_owned(), store_addr: Some( "postgresql://root:root@store.nanocl.internal:26258/defaultdb" .to_owned(), diff --git a/bin/nanocld/src/utils/system.rs b/bin/nanocld/src/utils/system.rs index 668bc01cd..fd9669251 100644 --- a/bin/nanocld/src/utils/system.rs +++ b/bin/nanocld/src/utils/system.rs @@ -68,7 +68,7 @@ pub async fn sync_process( name: name.clone(), kind: kind.to_owned().try_into()?, data: container_instance_data.clone(), - node_key: state.inner.config.hostname.clone(), + node_name: state.inner.config.hostname.clone(), kind_key: key.to_owned(), created_at: Some( chrono::NaiveDateTime::parse_from_str( @@ -105,6 +105,7 @@ pub async fn register_namespace( } let new_nsp = NamespacePartial { name: name.to_owned(), + metadata: None, }; if create_network { NamespaceDb::create_obj(&new_nsp, state).await?; @@ -246,7 +247,7 @@ pub async fn sync_processes(state: &SystemState) -> IoResult<()> { GenericClause::NotIn(ids.iter().map(|id| id.to_owned()).collect()), ) .r#where( - "node_key", + "node_name", GenericClause::Eq(state.inner.config.hostname.clone()), ); ProcessDb::del_by(&filter, &state.inner.pool).await?; @@ -278,9 +279,7 @@ pub async fn sync_vm_images(state: &SystemState) -> IoResult<()> { { continue; } - if let Err(error) = - utils::vm_image::create(&name, path, &state.inner.pool).await - { + if let Err(error) = utils::vm_image::create(&name, path, state).await { log::warn!("system::sync_vm_images: {error}") } } diff --git a/bin/nanocld/src/utils/vm_image.rs b/bin/nanocld/src/utils/vm_image.rs index ed285bd82..503d0ba59 100644 --- a/bin/nanocld/src/utils/vm_image.rs +++ b/bin/nanocld/src/utils/vm_image.rs @@ -113,6 +113,7 @@ pub async fn create_snap( let img_info = get_info(&snapshot_path).await?; let snap_image = VmImageDb { name: name.to_owned(), + node_name: state.inner.config.hostname.clone(), created_at: chrono::Utc::now().naive_utc(), kind: "Snapshot".into(), path: snapshot_path.clone(), @@ -232,6 +233,7 @@ pub async fn clone( }; let new_base_image = VmImageDb { name: name.to_owned(), + node_name: daemon_conf.hostname.clone(), created_at: chrono::Utc::now().naive_utc(), kind: "Base".into(), path: base_path.clone(), @@ -312,7 +314,7 @@ pub async fn resize_by_name( pub async fn create( name: &str, filepath: &str, - pool: &Pool, + state: &SystemState, ) -> HttpResult { // Get image info let img_info = match utils::vm_image::get_info(filepath).await { @@ -325,6 +327,7 @@ pub async fn create( }; let vm_image = VmImageDb { name: name.to_owned(), + node_name: state.inner.config.hostname.clone(), created_at: chrono::Utc::now().naive_utc(), kind: "Base".into(), format: img_info.format, @@ -333,6 +336,6 @@ pub async fn create( path: filepath.to_owned(), parent: None, }; - let image = VmImageDb::create_from(vm_image, pool).await?; + let image = VmImageDb::create_from(vm_image, &state.inner.pool).await?; Ok(image) } diff --git a/bin/ncproxy/src/subsystem/metric.rs b/bin/ncproxy/src/subsystem/metric.rs index 7a9224e02..874a6c5f4 100644 --- a/bin/ncproxy/src/subsystem/metric.rs +++ b/bin/ncproxy/src/subsystem/metric.rs @@ -101,7 +101,8 @@ async fn watch(state: &SystemStateRef) -> IoResult<()> { let path = format!("{}/log", state.store.dir); let path = Path::new(&path); if !path.exists() { - return Err(IoError::not_found("Metric", &format!("{}", path.display()))); + std::fs::create_dir_all(path) + .map_err(|e| e.map_err_context(|| "metric"))?; } let (tx, rx) = std::sync::mpsc::channel(); // Automatically select the best implementation for your platform. diff --git a/crates/nanocl_stubs/src/namespace.rs b/crates/nanocl_stubs/src/namespace.rs index 48e3480b9..bb15f5da1 100644 --- a/crates/nanocl_stubs/src/namespace.rs +++ b/crates/nanocl_stubs/src/namespace.rs @@ -19,6 +19,12 @@ pub struct Namespace { pub name: String, /// When the namespace was created pub created_at: chrono::NaiveDateTime, + /// User defined metadata + #[cfg_attr( + feature = "serde", + serde(skip_serializing_if = "Option::is_none") + )] + pub metadata: Option, } /// A Namespace partial is a payload used to create a new namespace @@ -33,6 +39,12 @@ pub struct Namespace { pub struct NamespacePartial { /// Name of the namespace pub name: String, + /// User defined metadata + #[cfg_attr( + feature = "serde", + serde(skip_serializing_if = "Option::is_none") + )] + pub metadata: Option, } /// A Namespace Summary is a summary of a namespace diff --git a/crates/nanocl_stubs/src/process.rs b/crates/nanocl_stubs/src/process.rs index e094fa8a6..0e87d882f 100644 --- a/crates/nanocl_stubs/src/process.rs +++ b/crates/nanocl_stubs/src/process.rs @@ -85,8 +85,8 @@ pub struct ProcessPartial { pub kind: ProcessKind, /// The data of the process a ContainerInspect pub data: serde_json::Value, - /// Key of the node where the container is running - pub node_key: String, + /// Name of the node where the container is running + pub node_name: String, /// Key of the related kind pub kind_key: String, /// The created at date @@ -110,8 +110,8 @@ pub struct Process { pub name: String, /// Kind of the process (Job, Vm, Cargo) pub kind: ProcessKind, - /// Key of the node where the container is running - pub node_key: String, + /// Name of the node where the container is running + pub node_name: String, /// Key of the related kind pub kind_key: String, /// The data of the process a ContainerInspect diff --git a/crates/nanocld_client/src/namespace.rs b/crates/nanocld_client/src/namespace.rs index 5f776d81a..b35199864 100644 --- a/crates/nanocld_client/src/namespace.rs +++ b/crates/nanocld_client/src/namespace.rs @@ -37,7 +37,10 @@ impl NanocldClient { &self, name: &str, ) -> HttpClientResult { - let new_item = NamespacePartial { name: name.into() }; + let new_item = NamespacePartial { + name: name.to_owned(), + metadata: None, + }; let res = self .send_post(Self::NAMESPACE_PATH, Some(new_item), None::) .await?; diff --git a/scripts/prepare_test_ci.sh b/scripts/prepare_test_ci.sh index c8e1ef8c4..dfcfb82ce 100755 --- a/scripts/prepare_test_ci.sh +++ b/scripts/prepare_test_ci.sh @@ -20,6 +20,7 @@ docker run -d --rm \ -v /run/nanocl:/run/nanocl \ -e HOME=$HOME \ -w /project \ + --hostname nanocl.internal \ --network host \ ghcr.io/next-hat/nanocl-dev:dev \ run --bin nanocld --no-default-features --features "test" -- --store-addr postgresql://root:root@store.nanocl.internal:26258/defaultdb\ From ff9aae66bc4a169a8c83496f0f335866e1bea8b6 Mon Sep 17 00:00:00 2001 From: leone Date: Sun, 2 Jun 2024 16:29:23 +0200 Subject: [PATCH 2/9] feature/nanocld: handle filter by dates (#965) --- .vscode/settings.json | 7 +- Cargo.lock | 68 ++++------- bin/nanocl/src/models/node.rs | 15 ++- bin/nanocld/specs/swagger.yaml | 21 +++- bin/nanocld/src/models/mod.rs | 172 ++++++++++++++++++++++++++- bin/nanocld/src/models/node.rs | 1 + bin/nanocld/src/repositories/node.rs | 1 - crates/nanocl_stubs/Cargo.toml | 1 + crates/nanocl_stubs/src/generic.rs | 2 + crates/nanocl_stubs/src/node.rs | 13 +- 10 files changed, 248 insertions(+), 53 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 5873a2899..8c036c4c0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -30,6 +30,7 @@ "cpus", "crond", "curr", + "datetime", "dialoguer", "dnsmasq", "Domainname", @@ -39,13 +40,11 @@ "freeifaddrs", "gethostname", "getifaddrs", - "rowid", - "Inet", - "Timestamptz", "Healthcheck", "iface", "ifaddrs", "indicatif", + "Inet", "Insertable", "ipam", "ipnet", @@ -77,11 +76,13 @@ "precpu", "proto", "qcow", + "rowid", "rustc", "schemars", "sockaddr", "statefile", "Statefiles", + "Timestamptz", "Tmpfs", "Ulimits", "unexpose", diff --git a/Cargo.lock b/Cargo.lock index 0ee540f75..9a2608fdf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -143,7 +143,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "258b52a1aa741b9f09783b2d86cf0aeeb617bbf847f6933340a39644227acbdb" dependencies = [ "event-listener 5.3.1", - "event-listener-strategy 0.5.2", + "event-listener-strategy", "futures-core", "pin-project-lite", ] @@ -166,7 +166,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" dependencies = [ "concurrent-queue 2.5.0", - "event-listener-strategy 0.5.2", + "event-listener-strategy", "futures-core", "pin-project-lite", ] @@ -192,8 +192,8 @@ checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" dependencies = [ "async-channel 2.3.1", "async-executor", - "async-io 2.3.2", - "async-lock 3.3.0", + "async-io 2.3.3", + "async-lock 3.4.0", "blocking", "futures-lite 2.3.0", "once_cell", @@ -221,17 +221,17 @@ dependencies = [ [[package]] name = "async-io" -version = "2.3.2" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcccb0f599cfa2f8ace422d3555572f47424da5648a4382a9dd0310ff8210884" +checksum = "0d6baa8f0178795da0e71bc42c9e5d13261aac7ee549853162e66a241ba17964" dependencies = [ - "async-lock 3.3.0", + "async-lock 3.4.0", "cfg-if", "concurrent-queue 2.5.0", "futures-io", "futures-lite 2.3.0", "parking", - "polling 3.7.0", + "polling 3.7.1", "rustix 0.38.34", "slab", "tracing", @@ -249,12 +249,12 @@ dependencies = [ [[package]] name = "async-lock" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" dependencies = [ - "event-listener 4.0.3", - "event-listener-strategy 0.4.0", + "event-listener 5.3.1", + "event-listener-strategy", "pin-project-lite", ] @@ -288,12 +288,12 @@ dependencies = [ [[package]] name = "async-signal" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afe66191c335039c7bb78f99dc7520b0cbb166b3a1cb33a03f53d8a1c6f2afda" +checksum = "329972aa325176e89114919f2a80fdae4f4c040f66a370b1a1159c6c0f94e7aa" dependencies = [ - "async-io 2.3.2", - "async-lock 3.3.0", + "async-io 2.3.3", + "async-lock 3.4.0", "atomic-waker", "cfg-if", "futures-core", @@ -1009,17 +1009,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "event-listener" -version = "4.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" -dependencies = [ - "concurrent-queue 2.5.0", - "parking", - "pin-project-lite", -] - [[package]] name = "event-listener" version = "5.3.1" @@ -1031,16 +1020,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "event-listener-strategy" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" -dependencies = [ - "event-listener 4.0.3", - "pin-project-lite", -] - [[package]] name = "event-listener-strategy" version = "0.5.2" @@ -2092,6 +2071,7 @@ dependencies = [ "bollard-next", "chrono", "clap", + "ipnet", "schemars", "serde", "serde_json", @@ -2529,7 +2509,7 @@ dependencies = [ "ntex-service", "ntex-util", "oneshot", - "polling 3.7.0", + "polling 3.7.1", "signal-hook", "socket2 0.5.7", ] @@ -2930,9 +2910,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "piper" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "464db0c665917b13ebb5d453ccdec4add5658ee1adc7affc7677615356a8afaf" +checksum = "ae1d5c74c9876f070d3e8fd503d748c7d974c3e48da8f41350fa5222ef9b4391" dependencies = [ "atomic-waker", "fastrand 2.1.0", @@ -2963,9 +2943,9 @@ dependencies = [ [[package]] name = "polling" -version = "3.7.0" +version = "3.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645493cf344456ef24219d02a768cf1fb92ddf8c92161679ae3d91b91a637be3" +checksum = "5e6a007746f34ed64099e88783b0ae369eaa3da6392868ba262e2af9b8fbaea1" dependencies = [ "cfg-if", "concurrent-queue 2.5.0", @@ -3038,9 +3018,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.84" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6" +checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" dependencies = [ "unicode-ident", ] diff --git a/bin/nanocl/src/models/node.rs b/bin/nanocl/src/models/node.rs index a3035803d..f5698c1f1 100644 --- a/bin/nanocl/src/models/node.rs +++ b/bin/nanocl/src/models/node.rs @@ -22,17 +22,30 @@ pub enum NodeCommand { /// A row of the node table #[derive(Tabled)] +#[tabled(rename_all = "UPPERCASE")] pub struct NodeRow { + /// Name of the node pub name: String, + /// IP address of the node pub ip_address: String, + /// Endpoint of the node + pub endpoint: String, + /// Version of the node + pub version: String, + #[tabled(rename = "CREATED AT")] + created_at: String, } /// Convert a Node to a NodeRow impl From for NodeRow { fn from(node: Node) -> Self { + let created_at = node.created_at.format("%Y-%m-%d %H:%M:%S").to_string(); Self { name: node.name, - ip_address: node.ip_address, + ip_address: node.ip_address.to_string(), + endpoint: node.endpoint, + version: node.version, + created_at, } } } diff --git a/bin/nanocld/specs/swagger.yaml b/bin/nanocld/specs/swagger.yaml index 0d680ee85..c94eff0a1 100644 --- a/bin/nanocld/specs/swagger.yaml +++ b/bin/nanocld/specs/swagger.yaml @@ -3619,7 +3619,9 @@ components: - contains properties: contains: - description: JSON contains + description: |- + TODO: Add Between + JSON contains - type: object required: - has-key @@ -5433,12 +5435,29 @@ components: type: object required: - Name + - CreatedAt - IpAddress + - Endpoint + - Version properties: Name: type: string + description: The name of the node + CreatedAt: + type: string + format: date-time + description: The created at date IpAddress: + $ref: '#/components/schemas/ipnet.IpNet' + Endpoint: type: string + description: Endpoint to connect to the node + Version: + type: string + description: Version of the node + Metadata: + description: User defined metadata + nullable: true ObjPsStatus: type: object required: diff --git a/bin/nanocld/src/models/mod.rs b/bin/nanocld/src/models/mod.rs index 6be7f4f58..293f63c49 100644 --- a/bin/nanocld/src/models/mod.rs +++ b/bin/nanocld/src/models/mod.rs @@ -2,6 +2,7 @@ use diesel::PgConnection; use diesel::r2d2::{Pool as R2D2Pool, PooledConnection, ConnectionManager}; mod ws; +use nanocl_error::io::{IoError, IoResult}; pub use ws::*; mod node; @@ -62,6 +63,8 @@ pub enum ColumnType { Text, Json, Uuid, + // TODO: Implement Inet type + // Inet, Timestamptz, } @@ -185,6 +188,162 @@ macro_rules! gen_sql_where4string { }; } +pub fn parse_date_string( + date_str: &str, +) -> IoResult> { + // Define possible date and datetime formats + let formats = [ + "%Y-%m-%d %H:%M:%S%.f %:z", // 2023-06-01 12:34:56.789 +02:00 + "%Y-%m-%d %H:%M:%S %:z", // 2023-06-01 12:34:56 +02:00 + "%Y-%m-%d %H:%M:%S%.f", // 2023-06-01 12:34:56.789 + "%Y-%m-%d %H:%M:%S", // 2023-06-01 12:34:56 + "%Y-%m-%d", // 2023-06-01 + ]; + + // Attempt to parse with each format + for &format in &formats { + if let Ok(datetime) = chrono::DateTime::parse_from_str(date_str, format) { + // Convert to Utc if parsed as FixedOffset + return Ok(datetime.with_timezone(&chrono::Utc)); + } + } + + // Try parsing as RFC 3339 if other formats fail + if let Ok(datetime) = chrono::DateTime::parse_from_rfc3339(date_str) { + return Ok(datetime.with_timezone(&chrono::Utc)); + } + + // As a last resort, parse as NaiveDate (date without time) and assume UTC + if let Ok(naive_date) = + chrono::NaiveDate::parse_from_str(date_str, "%Y-%m-%d") + { + let naive_datetime = chrono::NaiveDateTime::new( + naive_date, + chrono::NaiveTime::from_hms_opt(0, 0, 0).unwrap(), + ); + return Ok(chrono::DateTime::::from_naive_utc_and_offset( + naive_datetime, + chrono::Utc, + )); + } + + // If all formats fail, return error + Err(IoError::invalid_data( + "Invalid date format", + &format!("Invalid date format: {}", date_str), + )) +} + +#[macro_export] +macro_rules! gen_sql_where4timestamptz { + ($query: expr, $column: expr, $value: expr) => { + match $value { + nanocl_stubs::generic::GenericClause::Eq(val) => { + let val = $crate::models::parse_date_string(&val).unwrap(); + $query = $query.filter($column.eq(val.clone())); + } + nanocl_stubs::generic::GenericClause::Ne(val) => { + let val = $crate::models::parse_date_string(&val).unwrap(); + $query = $query.filter($column.ne(val.clone())); + } + nanocl_stubs::generic::GenericClause::Gt(val) => { + let val = $crate::models::parse_date_string(&val).unwrap(); + $query = $query.filter($column.gt(val.clone())); + } + nanocl_stubs::generic::GenericClause::Lt(val) => { + let val = $crate::models::parse_date_string(&val).unwrap(); + $query = $query.filter($column.lt(val.clone())); + } + nanocl_stubs::generic::GenericClause::Ge(val) => { + let val = $crate::models::parse_date_string(&val).unwrap(); + $query = $query.filter($column.ge(val.clone())); + } + nanocl_stubs::generic::GenericClause::Le(val) => { + let val = $crate::models::parse_date_string(&val).unwrap(); + $query = $query.filter($column.le(val.clone())); + } + nanocl_stubs::generic::GenericClause::IsNull => { + $query = $query.filter($column.is_null()); + } + nanocl_stubs::generic::GenericClause::IsNotNull => { + $query = $query.filter($column.is_not_null()); + } + nanocl_stubs::generic::GenericClause::In(items) => { + let items: Vec> = items + .iter() + .map(|item| $crate::models::parse_date_string(item).unwrap()) + .collect(); + $query = $query.filter($column.eq_any(items)); + } + nanocl_stubs::generic::GenericClause::NotIn(items) => { + let items: Vec> = items + .iter() + .map(|item| $crate::models::parse_date_string(item).unwrap()) + .collect(); + $query = $query.filter($column.ne_all(items)); + } + _ => { + // Ignore unsupported clause + } + } + }; +} + +#[macro_export] +macro_rules! gen_sql_and4timestamptz { + ($query: expr, $column: expr, $value: expr) => { + match $value { + nanocl_stubs::generic::GenericClause::Eq(val) => { + let val = $crate::models::parse_date_string(&val).unwrap(); + Box::new($query.and($column.eq(val.clone()))) + } + nanocl_stubs::generic::GenericClause::Ne(val) => { + let val = $crate::models::parse_date_string(&val).unwrap(); + Box::new($query.and($column.ne(val.clone()))) + } + nanocl_stubs::generic::GenericClause::Gt(val) => { + let val = $crate::models::parse_date_string(&val).unwrap(); + Box::new($query.and($column.gt(val.clone()))) + } + nanocl_stubs::generic::GenericClause::Lt(val) => { + let val = $crate::models::parse_date_string(&val).unwrap(); + Box::new($query.and($column.lt(val.clone()))) + } + nanocl_stubs::generic::GenericClause::Ge(val) => { + let val = $crate::models::parse_date_string(&val).unwrap(); + Box::new($query.and($column.ge(val.clone()))) + } + nanocl_stubs::generic::GenericClause::Le(val) => { + let val = $crate::models::parse_date_string(&val).unwrap(); + Box::new($query.and($column.le(val.clone()))) + } + nanocl_stubs::generic::GenericClause::IsNull => { + Box::new($query.and($column.is_null())) + } + nanocl_stubs::generic::GenericClause::IsNotNull => { + Box::new($query.and($column.is_not_null())) + } + nanocl_stubs::generic::GenericClause::In(items) => { + let items: Vec> = items + .iter() + .map(|item| $crate::models::parse_date_string(item).unwrap()) + .collect(); + Box::new($query.and($column.eq_any(items))) + } + nanocl_stubs::generic::GenericClause::NotIn(items) => { + let items: Vec> = items + .iter() + .map(|item| $crate::models::parse_date_string(item).unwrap()) + .collect(); + Box::new($query.and($column.ne_all(items))) + } + _ => { + panic!("Unsupported clause"); + } + } + }; +} + /// Generate a where clause for a json column #[macro_export] macro_rules! gen_sql_where4json { @@ -283,7 +442,11 @@ macro_rules! gen_sql_query { diesel::dsl::sql::(s_column.1); $crate::gen_sql_where4string!($query, column, value); } - _ => {} + ColumnType::Timestamptz => { + let column = + diesel::dsl::sql::(s_column.1); + $crate::gen_sql_where4timestamptz!($query, column, value); + } } } } @@ -315,7 +478,12 @@ macro_rules! gen_sql_query { or_condition = $crate::gen_sql_and4json!(or_condition, column, value); } - _ => {} + ColumnType::Timestamptz => { + let column = + diesel::dsl::sql::(s_column.1); + or_condition = + $crate::gen_sql_and4timestamptz!(or_condition, column, value); + } } } } diff --git a/bin/nanocld/src/models/node.rs b/bin/nanocld/src/models/node.rs index d3d020879..8a4cc2743 100644 --- a/bin/nanocld/src/models/node.rs +++ b/bin/nanocld/src/models/node.rs @@ -23,5 +23,6 @@ pub struct NodeDb { /// Version of the node pub version: String, /// User defined metadata + #[serde(skip_serializing_if = "Option::is_none")] pub metadata: Option, } diff --git a/bin/nanocld/src/repositories/node.rs b/bin/nanocld/src/repositories/node.rs index 7c13d700c..85643984c 100644 --- a/bin/nanocld/src/repositories/node.rs +++ b/bin/nanocld/src/repositories/node.rs @@ -81,7 +81,6 @@ impl NodeDb { } pub async fn register(state: &SystemState) -> IoResult<()> { - println!("DEBUG: Registering node {}", state.inner.config.gateway); let ip_address = state .inner diff --git a/crates/nanocl_stubs/Cargo.toml b/crates/nanocl_stubs/Cargo.toml index 8b94d1121..4c6d4ac59 100644 --- a/crates/nanocl_stubs/Cargo.toml +++ b/crates/nanocl_stubs/Cargo.toml @@ -32,3 +32,4 @@ serde = { version = "1.0", features = ["derive"], optional = true } utoipa = { version = "4", features = ["uuid", "chrono"], optional = true } schemars = { version = "0.8", features = ["uuid1", "chrono"], optional = true } clap = { version = "4.5", features = ["derive", "cargo"], optional = true } +ipnet = { version = "2.9.0", features = ["serde"] } diff --git a/crates/nanocl_stubs/src/generic.rs b/crates/nanocl_stubs/src/generic.rs index b3f8977d4..f02cc48df 100644 --- a/crates/nanocl_stubs/src/generic.rs +++ b/crates/nanocl_stubs/src/generic.rs @@ -62,6 +62,8 @@ pub enum GenericClause { IsNull, /// Is not null IsNotNull, + /// TODO: Add Between + // Between(String, String), /// JSON contains Contains(serde_json::Value), /// JSON Has key diff --git a/crates/nanocl_stubs/src/node.rs b/crates/nanocl_stubs/src/node.rs index a8f4bbfbb..2ec55cae5 100644 --- a/crates/nanocl_stubs/src/node.rs +++ b/crates/nanocl_stubs/src/node.rs @@ -7,6 +7,17 @@ use serde::{Serialize, Deserialize}; #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", serde(rename_all = "PascalCase"))] pub struct Node { + /// The name of the node pub name: String, - pub ip_address: String, + /// The created at date + pub created_at: chrono::NaiveDateTime, + /// The ip address of the node + pub ip_address: ipnet::IpNet, + /// Endpoint to connect to the node + pub endpoint: String, + /// Version of the node + pub version: String, + /// User defined metadata + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option, } From 0ab81dff6db00cd2dc925b1a7de192b56615d892 Mon Sep 17 00:00:00 2001 From: leone Date: Sun, 2 Jun 2024 16:58:34 +0200 Subject: [PATCH 3/9] fix/nanocld: create network if inspect fail (#966) --- bin/nanocld/src/objects/namespace.rs | 10 ++--- bin/nanocld/src/repositories/namespace.rs | 10 +---- bin/nanocld/src/repositories/node.rs | 5 +-- bin/nanocld/src/utils/mod.rs | 1 + bin/nanocld/src/utils/network.rs | 49 +++++++++++++++++++++++ 5 files changed, 58 insertions(+), 17 deletions(-) create mode 100644 bin/nanocld/src/utils/network.rs diff --git a/bin/nanocld/src/objects/namespace.rs b/bin/nanocld/src/objects/namespace.rs index d2ab32ba3..e4a69829f 100644 --- a/bin/nanocld/src/objects/namespace.rs +++ b/bin/nanocld/src/objects/namespace.rs @@ -4,8 +4,9 @@ use nanocl_error::http::{HttpResult, HttpError}; use nanocl_stubs::namespace::{NamespacePartial, Namespace, NamespaceInspect}; use crate::{ + utils, repositories::generic::*, - models::{NamespaceDb, SystemState, CargoDb}, + models::{CargoDb, NamespaceDb, SystemState}, }; use super::generic::*; @@ -60,17 +61,14 @@ impl ObjInspectByPk for NamespaceDb { let namespace = NamespaceDb::read_by_pk(pk, &state.inner.pool).await?; let models = CargoDb::read_by_namespace(&namespace.name, &state.inner.pool).await?; + // TODO: Refactor this it doesn't scale at all let mut cargoes = Vec::new(); for cargo in models { let cargo = CargoDb::inspect_obj_by_pk(&cargo.spec.cargo_key, state).await?; cargoes.push(cargo); } - let network = state - .inner - .docker_api - .inspect_network(pk, None::>) - .await?; + let network = utils::network::inspect_network(pk, state).await?; Ok(NamespaceInspect { name: namespace.name, cargoes, diff --git a/bin/nanocld/src/repositories/namespace.rs b/bin/nanocld/src/repositories/namespace.rs index b4f57fc4a..22db0f4df 100644 --- a/bin/nanocld/src/repositories/namespace.rs +++ b/bin/nanocld/src/repositories/namespace.rs @@ -2,13 +2,11 @@ use std::collections::HashMap; use diesel::prelude::*; -use bollard_next::network::InspectNetworkOptions; - use nanocl_error::http::{HttpError, HttpResult}; use nanocl_stubs::{generic::GenericFilter, namespace::NamespaceSummary}; use crate::{ - gen_sql_multiple, gen_sql_order_by, gen_sql_query, + gen_sql_multiple, gen_sql_order_by, gen_sql_query, utils, schema::namespaces, models::{CargoDb, ColumnType, NamespaceDb, ProcessDb, SystemState}, }; @@ -84,11 +82,7 @@ impl NamespaceDb { CargoDb::count_by_namespace(&item.name, &state.inner.pool).await?; let processes = ProcessDb::list_by_namespace(&item.name, &state.inner.pool).await?; - let network = state - .inner - .docker_api - .inspect_network(&item.name, None::>) - .await?; + let network = utils::network::inspect_network(&item.name, state).await?; let ipam = network.ipam.unwrap_or_default(); let ipam_config = ipam.config.unwrap_or_default(); let gateway = ipam_config diff --git a/bin/nanocld/src/repositories/node.rs b/bin/nanocld/src/repositories/node.rs index 85643984c..37dfec176 100644 --- a/bin/nanocld/src/repositories/node.rs +++ b/bin/nanocld/src/repositories/node.rs @@ -7,10 +7,9 @@ use nanocl_error::io::{IoError, IoResult}; use nanocl_stubs::generic::GenericFilter; use crate::{ - gen_sql_multiple, gen_sql_order_by, gen_sql_query, - models::{ColumnType, NodeDb, Pool, SystemState}, + gen_sql_multiple, gen_sql_order_by, gen_sql_query, vars, schema::nodes, - vars, + models::{ColumnType, NodeDb, Pool, SystemState}, }; use super::generic::*; diff --git a/bin/nanocld/src/utils/mod.rs b/bin/nanocld/src/utils/mod.rs index de728625b..b705a9c05 100644 --- a/bin/nanocld/src/utils/mod.rs +++ b/bin/nanocld/src/utils/mod.rs @@ -11,6 +11,7 @@ pub mod ctrl_client; pub mod server; pub mod container; pub mod query_string; +pub mod network; #[cfg(test)] pub mod tests { diff --git a/bin/nanocld/src/utils/network.rs b/bin/nanocld/src/utils/network.rs new file mode 100644 index 000000000..a33deb571 --- /dev/null +++ b/bin/nanocld/src/utils/network.rs @@ -0,0 +1,49 @@ +use bollard_next::{ + secret::Network, + network::{CreateNetworkOptions, InspectNetworkOptions}, +}; + +use nanocl_error::http::HttpResult; + +use crate::models::SystemState; + +pub async fn inspect_network( + pk: &str, + state: &SystemState, +) -> HttpResult { + match state + .inner + .docker_api + .inspect_network(pk, None::>) + .await + { + Err(err) => match err { + bollard_next::errors::Error::DockerResponseServerError { + status_code, + message: _, + } => { + if status_code != 404 { + return Err(err.into()); + } + state + .inner + .docker_api + .create_network(CreateNetworkOptions { + name: pk.to_owned(), + driver: String::from("bridge"), + ..Default::default() + }) + .await?; + Ok( + state + .inner + .docker_api + .inspect_network(pk, None::>) + .await?, + ) + } + _ => Err(err.into()), + }, + Ok(network) => Ok(network), + } +} From 3a5ca627fa67d210b7fe3d1b09afadfc743b2ebd Mon Sep 17 00:00:00 2001 From: leone Date: Sun, 2 Jun 2024 18:59:50 +0200 Subject: [PATCH 4/9] feature/nanocld: updated related cargo if secret change (#967) --- bin/nanocld/src/system/event.rs | 64 ++++++++++++++++++++++++++------- bin/nanocld/src/tasks/cargo.rs | 1 + examples/deploy_example.yml | 1 + examples/secret_env.yml | 3 +- 4 files changed, 56 insertions(+), 13 deletions(-) diff --git a/bin/nanocld/src/system/event.rs b/bin/nanocld/src/system/event.rs index f97d97cb7..967edf69f 100644 --- a/bin/nanocld/src/system/event.rs +++ b/bin/nanocld/src/system/event.rs @@ -3,9 +3,12 @@ use std::str::FromStr; use ntex::rt; use nanocl_error::io::IoResult; -use nanocl_stubs::system::{ - Event, EventActor, EventActorKind, EventKind, NativeEventAction, - ObjPsStatusKind, +use nanocl_stubs::{ + generic::{GenericClause, GenericFilter}, + system::{ + Event, EventActor, EventActorKind, EventKind, NativeEventAction, + ObjPsStatusKind, + }, }; use crate::{ @@ -74,7 +77,7 @@ async fn job_ttl(actor: &EventActor, state: &SystemState) -> IoResult<()> { Ok(()) } -fn start( +fn starting( key: &str, actor: &EventActor, state: &SystemState, @@ -99,7 +102,7 @@ fn start( /// Handle delete event for living objects (job, cargo, vm) /// by checking if the event is `NativeEventAction::Deleting` /// and pushing into the task manager the task to delete the object -fn delete( +fn destroying( key: &str, actor: &EventActor, state: &SystemState, @@ -122,7 +125,7 @@ fn delete( } } -fn update( +fn updating( key: &str, actor: &EventActor, state: &SystemState, @@ -140,7 +143,44 @@ fn update( } } -fn stop( +async fn update( + key: &str, + actor: &EventActor, + state: &SystemState, +) -> Option { + match actor.kind { + // If a secret is updated we check for the cargoes using it and fire an update for them + EventActorKind::Secret => { + log::debug!("handling update event for secret {key}"); + let filter = GenericFilter::new().r#where( + "data", + GenericClause::Contains(serde_json::json!({ + "Secrets": [ + key + ] + })), + ); + let cargoes = CargoDb::transform_read_by(&filter, &state.inner.pool) + .await + .unwrap(); + log::debug!("found {} cargoes using secret {key}", cargoes.len()); + for cargo in &cargoes { + ObjPsStatusDb::update_actual_status( + &cargo.spec.cargo_key, + &ObjPsStatusKind::Updating, + &state.inner.pool, + ) + .await + .ok(); + state.emit_normal_native_action(cargo, NativeEventAction::Updating); + } + None + } + _ => None, + } +} + +fn stopping( key: &str, actor: &EventActor, state: &SystemState, @@ -166,7 +206,6 @@ fn stop( /// 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<()> { match e.kind { EventKind::Error | EventKind::Warning => return Ok(()), @@ -199,10 +238,11 @@ async fn _exec_event(e: &Event, state: &SystemState) -> IoResult<()> { } let action = NativeEventAction::from_str(e.action.as_str())?; let task: Option = match action { - NativeEventAction::Starting => start(&key, actor, state), - NativeEventAction::Stopping => stop(&key, actor, state), - NativeEventAction::Updating => update(&key, actor, state), - NativeEventAction::Destroying => delete(&key, actor, state), + NativeEventAction::Starting => starting(&key, actor, state), + NativeEventAction::Stopping => stopping(&key, actor, state), + NativeEventAction::Updating => updating(&key, actor, state), + NativeEventAction::Update => update(&key, actor, state).await, + NativeEventAction::Destroying => destroying(&key, actor, state), NativeEventAction::Die => { job_ttl(actor, state).await?; None diff --git a/bin/nanocld/src/tasks/cargo.rs b/bin/nanocld/src/tasks/cargo.rs index 62b775817..62d5a2ca6 100644 --- a/bin/nanocld/src/tasks/cargo.rs +++ b/bin/nanocld/src/tasks/cargo.rs @@ -187,6 +187,7 @@ impl ObjTaskUpdate for CargoDb { }); } Ok(_) => { + log::debug!("cargo instance {} started", cargo.spec.cargo_key); // Delete old containers let state_ptr_ptr = state.clone(); let _ = utils::container::delete_instances( diff --git a/examples/deploy_example.yml b/examples/deploy_example.yml index d3f9a45db..b8f0d3a4c 100644 --- a/examples/deploy_example.yml +++ b/examples/deploy_example.yml @@ -72,6 +72,7 @@ Cargoes: App: deploy-example Secrets: - env.deploy-example.com + - env.deploy-secret Container: Image: ghcr.io/next-hat/nanocl-get-started:latest Env: diff --git a/examples/secret_env.yml b/examples/secret_env.yml index 3adf49643..f739c947d 100644 --- a/examples/secret_env.yml +++ b/examples/secret_env.yml @@ -1,8 +1,9 @@ ApiVersion: v0.14 Secrets: -- Name: deploy-secret +- Name: env.deploy-secret Kind: nanocl.io/env Data: - TOTO=TATA + - TEST=GG - GG=WP From 2c53c0ecfa51cae91dde79b294da31c96eb56b7d Mon Sep 17 00:00:00 2001 From: leone Date: Mon, 3 Jun 2024 17:53:00 +0200 Subject: [PATCH 5/9] fix/nanocl: state diff trigger and prettier loaders (#968) * fix/nanocl: state diff trigger and prettier loaders --- .vscode/settings.json | 4 ++ Cargo.lock | 11 ++++ bin/nanocl/Cargo.toml | 1 + bin/nanocl/src/commands/generic.rs | 2 +- bin/nanocl/src/commands/install.rs | 8 ++- bin/nanocl/src/commands/state.rs | 70 +++++++++++++++++--------- bin/nanocl/src/commands/uninstall.rs | 8 ++- bin/nanocl/src/models/secret.rs | 2 +- bin/nanocl/src/utils/progress.rs | 27 ++++++---- bin/nanocld/specs/swagger.yaml | 1 - bin/nanocld/src/models/secret.rs | 2 +- bin/nanocld/src/services/secret.rs | 2 +- bin/nanocld/src/system/docker_event.rs | 6 ++- crates/nanocl_stubs/src/proxy.rs | 8 +++ crates/nanocl_stubs/src/secret.rs | 15 +++--- crates/nanocld_client/src/secret.rs | 2 +- examples/cargo_example.yml | 2 + examples/deploy_example.yml | 3 -- 18 files changed, 114 insertions(+), 60 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 8c036c4c0..64d00a96a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -41,6 +41,7 @@ "gethostname", "getifaddrs", "Healthcheck", + "identitytoken", "iface", "ifaddrs", "indicatif", @@ -76,9 +77,12 @@ "precpu", "proto", "qcow", + "recurr", + "registrytoken", "rowid", "rustc", "schemars", + "serveraddress", "sockaddr", "statefile", "Statefiles", diff --git a/Cargo.lock b/Cargo.lock index 9a2608fdf..afeae1815 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -602,6 +602,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" +[[package]] +name = "colored" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" +dependencies = [ + "lazy_static", + "windows-sys 0.48.0", +] + [[package]] name = "concurrent-queue" version = "1.2.4" @@ -2026,6 +2036,7 @@ dependencies = [ "chrono", "clap", "clap_mangen", + "colored", "ctrlc", "dialoguer", "dotenvy", diff --git a/bin/nanocl/Cargo.toml b/bin/nanocl/Cargo.toml index fed59574d..991dc894e 100644 --- a/bin/nanocl/Cargo.toml +++ b/bin/nanocl/Cargo.toml @@ -75,3 +75,4 @@ dotenvy = "0.15" openssl = "0.10" async-recursion = "1.1" url = "2.5" +colored = "2.1.0" diff --git a/bin/nanocl/src/commands/generic.rs b/bin/nanocl/src/commands/generic.rs index e8a6b395e..78ae55323 100644 --- a/bin/nanocl/src/commands/generic.rs +++ b/bin/nanocl/src/commands/generic.rs @@ -128,9 +128,9 @@ where )) .map_err(|err| err.map_err_context(|| "Delete"))?; } - let pg_style = utils::progress::create_spinner_style("red"); for name in &opts.keys { let token = format!("{object_name}/{}", name); + let pg_style = utils::progress::create_spinner_style(&token, "red"); let pg = utils::progress::create_progress(&token, &pg_style); let (key, waiter_kind) = match object_name { "vms" => ( diff --git a/bin/nanocl/src/commands/install.rs b/bin/nanocl/src/commands/install.rs index b1f0bee6d..51ae09450 100644 --- a/bin/nanocl/src/commands/install.rs +++ b/bin/nanocl/src/commands/install.rs @@ -131,7 +131,6 @@ pub async fn exec_install(args: &InstallOpts) -> IoResult<()> { .await .map_err(|err| err.map_err_context(|| "Nanocl system network"))?; } - let pg_style = utils::progress::create_spinner_style("green"); for cargo in &cargoes { let image = cargo.container.image.clone().ok_or(IoError::invalid_data( format!("Cargo {} image", cargo.name), @@ -147,10 +146,9 @@ pub async fn exec_install(args: &InstallOpts) -> IoResult<()> { if docker.inspect_image(&image).await.is_err() || args.force_pull { utils::docker::install_image(image_name, image_tag, &docker).await?; } - let pg = utils::progress::create_progress( - &format!("cargo/{}", &cargo.name), - &pg_style, - ); + let token = format!("cargo/{}", &cargo.name); + let pg_style = utils::progress::create_spinner_style(&token, "red"); + let pg = utils::progress::create_progress("submitting", &pg_style); let container = utils::docker::create_cargo_container( cargo, &deployment.namespace.clone().unwrap_or("system".into()), diff --git a/bin/nanocl/src/commands/state.rs b/bin/nanocl/src/commands/state.rs index 6d4678a66..d4a878e52 100644 --- a/bin/nanocl/src/commands/state.rs +++ b/bin/nanocl/src/commands/state.rs @@ -563,6 +563,8 @@ async fn parse_state_file_recurr( .into_iter() .collect::, _>>()?; let mut states = vec![state_file.clone()]; + // TODO: check if we need to reverse the order of parsed_sub_states + // parsed_sub_states.reverse(); states.append(&mut parsed_sub_states.into_iter().flatten().collect()); states.reverse(); Ok(states) @@ -575,31 +577,38 @@ async fn state_apply( ) -> IoResult<()> { let client = &cli_conf.client; let namespace = state_file.data.namespace.clone().unwrap_or("global".into()); - let pg_style = utils::progress::create_spinner_style("green"); if let Some(secrets) = &state_file.data.secrets { for secret in secrets { let token = format!("secret/{}", secret.name); - let pg = utils::progress::create_progress(&token, &pg_style); + let pg_style = utils::progress::create_spinner_style(&token, "green"); + let pg = utils::progress::create_progress("(submitting)", &pg_style); match client.inspect_secret(&secret.name).await { Err(_) => { client.create_secret(secret).await?; + pg.set_message("(created)"); } Ok(inspect) => { let cmp: SecretPartial = inspect.into(); if cmp != *secret { let update: SecretUpdate = secret.clone().into(); client.patch_secret(&secret.name, &update).await?; + pg.set_message("(updated)"); + } else { + pg.finish_with_message("(unchanged)"); + continue; } } } - pg.finish(); + pg.finish_with_message("(done)"); } } if let Some(jobs) = &state_file.data.jobs { for job in jobs { let token = format!("job/{}", job.name); - let pg = utils::progress::create_progress(&token, &pg_style); + let pg_style = utils::progress::create_spinner_style(&token, "green"); + let pg = utils::progress::create_progress("(submitting)", &pg_style); if client.inspect_job(&job.name).await.is_ok() { + pg.set_message("(clearing)"); let waiter = utils::process::wait_process_state( &job.name, EventActorKind::Job, @@ -609,8 +618,10 @@ async fn state_apply( .await?; client.delete_job(&job.name).await?; waiter.await??; + pg.finish_with_message("(cleared)"); } client.create_job(job).await?; + pg.finish_with_message("(created)"); let waiter = utils::process::wait_process_state( &job.name, EventActorKind::Job, @@ -618,32 +629,36 @@ async fn state_apply( client, ) .await?; + pg.set_message("(starting)"); client.start_process("job", &job.name, None).await?; waiter.await??; - pg.finish(); + pg.finish_with_message("(running)"); } } if let Some(cargoes) = &state_file.data.cargoes { for cargo in cargoes { let token = format!("cargo/{}", cargo.name); - let pg = utils::progress::create_progress(&token, &pg_style); + let pg_style = utils::progress::create_spinner_style(&token, "green"); + let pg = utils::progress::create_progress("(submitting)", &pg_style); match client.inspect_cargo(&cargo.name, Some(&namespace)).await { Err(_) => { client.create_cargo(cargo, Some(&namespace)).await?; + pg.set_message("(created)"); } Ok(inspect) => { - if inspect.status.actual == ObjPsStatusKind::Start && !opts.reload { - pg.finish(); - continue; - } let cmp: CargoSpecPartial = inspect.spec.into(); - if cmp != *cargo || opts.reload { + if (cmp != *cargo) || opts.reload { client .put_cargo(&cargo.name, cargo, Some(&namespace)) .await?; + pg.set_message("(updated)"); + } else if inspect.status.actual == ObjPsStatusKind::Start { + pg.finish_with_message("(unchanged)"); + continue; } } } + pg.finish_with_message("(starting)"); let waiter = utils::process::wait_process_state( &format!("{}.{namespace}", cargo.name), EventActorKind::Cargo, @@ -655,29 +670,32 @@ async fn state_apply( .start_process("cargo", &cargo.name, Some(&namespace)) .await?; waiter.await??; - pg.finish(); + pg.finish_with_message("(running)"); } } if let Some(vms) = &state_file.data.virtual_machines { for vm in vms { let token = format!("vm/{}", vm.name); - let pg = utils::progress::create_progress(&token, &pg_style); + let pg_style = utils::progress::create_spinner_style(&token, "green"); + let pg = utils::progress::create_progress("(submitting)", &pg_style); match client.inspect_vm(&vm.name, Some(&namespace)).await { Err(_) => { client.create_vm(vm, Some(&namespace)).await?; + pg.set_message("(created)"); } Ok(inspect) => { - if inspect.status.actual == ObjPsStatusKind::Start && !opts.reload { - pg.finish(); - continue; - } let cmp: VmSpecPartial = inspect.spec.into(); - if cmp != *vm { + if (cmp != *vm) || opts.reload { let update: VmSpecUpdate = vm.clone().into(); client.patch_vm(&vm.name, &update, Some(&namespace)).await?; + pg.set_message("(updated)"); + } else if inspect.status.actual == ObjPsStatusKind::Start { + pg.finish_with_message("(unchanged)"); + continue; } } } + pg.set_message("(starting)"); let waiter = utils::process::wait_process_state( &format!("{}.{namespace}", vm.name), EventActorKind::Vm, @@ -689,26 +707,32 @@ async fn state_apply( .start_process("vm", &vm.name, Some(&namespace)) .await?; waiter.await??; - pg.finish(); + pg.finish_with_message("(running)"); } } if let Some(resources) = &state_file.data.resources { for resource in resources { let token = format!("resource/{}", resource.name); - let pg = utils::progress::create_progress(&token, &pg_style); + let pg_style = utils::progress::create_spinner_style(&token, "green"); + let pg = utils::progress::create_progress("(submitting)", &pg_style); match client.inspect_resource(&resource.name).await { Err(_) => { client.create_resource(resource).await?; + pg.set_message("(created)"); } Ok(inspect) => { let cmp: ResourcePartial = inspect.into(); - if cmp != *resource { + if (cmp != *resource) || opts.reload { let update: ResourceUpdate = resource.clone().into(); client.put_resource(&resource.name, &update).await?; + pg.set_message("(updated)"); + } else { + pg.finish_with_message("(unchanged)"); + continue; } } } - pg.finish(); + pg.finish_with_message("(done)"); } } Ok(()) @@ -716,7 +740,7 @@ async fn state_apply( fn print_states(states: &[StateRef]) { let raw = states.iter().fold(String::new(), |init, state| { - format!("{init}{}\n", state.raw) + format!("{init}{}\n", state.raw.trim()) }); println!("{raw}"); } diff --git a/bin/nanocl/src/commands/uninstall.rs b/bin/nanocl/src/commands/uninstall.rs index 150454d7a..59ba8bb92 100644 --- a/bin/nanocl/src/commands/uninstall.rs +++ b/bin/nanocl/src/commands/uninstall.rs @@ -37,12 +37,10 @@ pub async fn exec_uninstall(args: &UninstallOpts) -> IoResult<()> { let installer = serde_yaml::from_str::(&installer) .map_err(|err| err.map_err_context(|| "Unable to parse installer"))?; let cargoes = installer.cargoes.unwrap_or_default(); - let pg_style = utils::progress::create_spinner_style("red"); for cargo in cargoes { - let pg = utils::progress::create_progress( - &format!("cargo/{}", cargo.name), - &pg_style, - ); + let token = format!("cargo/{}", cargo.name); + let pg_style = utils::progress::create_spinner_style(&token, "red"); + let pg = utils::progress::create_progress("submitting", &pg_style); let key = format!("{}.system.c", &cargo.name); if docker .inspect_container(&key, None::) diff --git a/bin/nanocl/src/models/secret.rs b/bin/nanocl/src/models/secret.rs index 2b4933001..785edcb96 100644 --- a/bin/nanocl/src/models/secret.rs +++ b/bin/nanocl/src/models/secret.rs @@ -91,7 +91,7 @@ impl TryFrom for SecretPartial { Ok(Self { name: opts.name, kind: kind.to_string(), - immutable: None, + immutable: false, data, metadata: None, }) diff --git a/bin/nanocl/src/utils/progress.rs b/bin/nanocl/src/utils/progress.rs index a22886287..98ee85624 100644 --- a/bin/nanocl/src/utils/progress.rs +++ b/bin/nanocl/src/utils/progress.rs @@ -1,18 +1,27 @@ +use colored::Colorize; use indicatif::{ProgressBar, ProgressStyle}; -pub(crate) fn create_progress( - token: &str, - style: &ProgressStyle, -) -> ProgressBar { +pub(crate) fn create_progress(msg: &str, style: &ProgressStyle) -> ProgressBar { let pg = ProgressBar::new(1); pg.enable_steady_tick(std::time::Duration::from_millis(50)); pg.set_style(style.clone()); - pg.set_message(token.to_owned()); + pg.set_message(msg.to_owned()); pg } -pub(crate) fn create_spinner_style(color: &str) -> ProgressStyle { - ProgressStyle::with_template(&format!("{{spinner:.{color}}} {{msg}}")) - .unwrap() - .tick_strings(&["▹▹▹▹▹", "▸▹▹▹▹", "▹▸▹▹▹", "▹▹▸▹▹", "▹▹▹▸▹", "▹▹▹▹▸", ">"]) +pub(crate) fn create_spinner_style(key: &str, color: &str) -> ProgressStyle { + ProgressStyle::with_template(&format!( + "{{spinner:.{color}.bold}} {} {{msg}}", + key.bold() + )) + .unwrap() + .tick_strings(&[ + "▹▹▹▹▹", + "▸▹▹▹▹", + "▹▸▹▹▹", + "▹▹▸▹▹", + "▹▹▹▸▹", + "▹▹▹▹▸", + ">", + ]) } diff --git a/bin/nanocld/specs/swagger.yaml b/bin/nanocld/specs/swagger.yaml index c94eff0a1..c41daa630 100644 --- a/bin/nanocld/specs/swagger.yaml +++ b/bin/nanocld/specs/swagger.yaml @@ -6346,7 +6346,6 @@ components: Immutable: type: boolean description: The secret cannot be updated - nullable: true Metadata: type: object description: The metadata of the resource (user defined) diff --git a/bin/nanocld/src/models/secret.rs b/bin/nanocld/src/models/secret.rs index 4e8f371df..30ce33d35 100644 --- a/bin/nanocld/src/models/secret.rs +++ b/bin/nanocld/src/models/secret.rs @@ -41,7 +41,7 @@ impl From<&SecretPartial> for SecretDb { created_at: chrono::Utc::now().naive_utc(), updated_at: chrono::Utc::now().naive_utc(), kind: secret.kind.clone(), - immutable: secret.immutable.unwrap_or(false), + immutable: secret.immutable, data: secret.data.clone(), metadata: secret.metadata.clone(), } diff --git a/bin/nanocld/src/services/secret.rs b/bin/nanocld/src/services/secret.rs index e170e66c9..626979de3 100644 --- a/bin/nanocld/src/services/secret.rs +++ b/bin/nanocld/src/services/secret.rs @@ -198,7 +198,7 @@ mod test_secret { let new_secret = SecretPartial { name: String::from("test-secret"), kind: String::from("test-create.io/test"), - immutable: None, + immutable: false, data: json!({ "Tls": { "cert": "MY CERT", "key": "MY KEY" }, }), diff --git a/bin/nanocld/src/system/docker_event.rs b/bin/nanocld/src/system/docker_event.rs index ac9eac3d7..74670d162 100644 --- a/bin/nanocld/src/system/docker_event.rs +++ b/bin/nanocld/src/system/docker_event.rs @@ -111,7 +111,8 @@ async fn exec_docker( ObjPsStatusDb::read_by_pk(&kind_key, &state.inner.pool).await?; match (&kind, &actual_status.wanted) { (EventActorKind::Cargo, status) - if status != &ObjPsStatusKind::Stop.to_string() => + if status != &ObjPsStatusKind::Stop.to_string() + || status != &ObjPsStatusKind::Start.to_string() => { ObjPsStatusDb::update_actual_status( &kind_key, @@ -128,7 +129,8 @@ async fn exec_docker( ); } (EventActorKind::Vm, status) - if status != &ObjPsStatusKind::Stop.to_string() => + if status != &ObjPsStatusKind::Stop.to_string() + || status != &ObjPsStatusKind::Start.to_string() => { ObjPsStatusDb::update_actual_status( &kind_key, diff --git a/crates/nanocl_stubs/src/proxy.rs b/crates/nanocl_stubs/src/proxy.rs index 83a0e1bfd..7cbd0d811 100644 --- a/crates/nanocl_stubs/src/proxy.rs +++ b/crates/nanocl_stubs/src/proxy.rs @@ -282,8 +282,16 @@ pub struct ProxyHttpLocation { /// The target cargo pub target: LocationTarget, /// Setup limit request for this location + #[cfg_attr( + feature = "serde", + serde(skip_serializing_if = "Option::is_none") + )] pub limit_req: Option, /// Allowed ip addr + #[cfg_attr( + feature = "serde", + serde(skip_serializing_if = "Option::is_none") + )] pub allowed_ips: Option>, /// Extras header to add #[cfg_attr( diff --git a/crates/nanocl_stubs/src/secret.rs b/crates/nanocl_stubs/src/secret.rs index da029c6fa..10136c9cd 100644 --- a/crates/nanocl_stubs/src/secret.rs +++ b/crates/nanocl_stubs/src/secret.rs @@ -20,7 +20,8 @@ pub struct SecretPartial { /// The kind of secret pub kind: String, /// The secret cannot be updated - pub immutable: Option, + #[cfg_attr(feature = "serde", serde(default))] + pub immutable: bool, /// The metadata of the resource (user defined) #[cfg_attr( feature = "serde", @@ -66,13 +67,13 @@ pub struct Secret { } impl From for SecretPartial { - fn from(db: Secret) -> Self { + fn from(secret: Secret) -> Self { SecretPartial { - name: db.name, - kind: db.kind, - immutable: Some(db.immutable), - data: db.data, - metadata: db.metadata, + name: secret.name, + kind: secret.kind, + immutable: secret.immutable, + data: secret.data, + metadata: secret.metadata, } } } diff --git a/crates/nanocld_client/src/secret.rs b/crates/nanocld_client/src/secret.rs index d53c633ac..6bc229019 100644 --- a/crates/nanocld_client/src/secret.rs +++ b/crates/nanocld_client/src/secret.rs @@ -113,7 +113,7 @@ mod tests { kind: "gen.io/generic".to_owned(), data: serde_json::json!({"key": "value"}), metadata: None, - immutable: None, + immutable: false, }; let secret = client.create_secret(&secret).await.unwrap(); assert_eq!(secret.name, SECRET_NAME); diff --git a/examples/cargo_example.yml b/examples/cargo_example.yml index cddada741..460ac0380 100644 --- a/examples/cargo_example.yml +++ b/examples/cargo_example.yml @@ -6,5 +6,7 @@ Namespace: cargo-example # https://docs.next-hat.com/references/nanocl/objects/cargo Cargoes: - Name: cargo-example + Metadata: + Certbot: managed Container: Image: ghcr.io/next-hat/nanocl-get-started:latest diff --git a/examples/deploy_example.yml b/examples/deploy_example.yml index b8f0d3a4c..c59a5789f 100644 --- a/examples/deploy_example.yml +++ b/examples/deploy_example.yml @@ -75,9 +75,6 @@ Cargoes: - env.deploy-secret Container: Image: ghcr.io/next-hat/nanocl-get-started:latest - Env: - - APP=GET_STARTED - - NEW=GG - Name: deploy-example2 Secrets: From f999aa59fe83926ef4aabc9435568f22076fc4f3 Mon Sep 17 00:00:00 2001 From: leone Date: Mon, 3 Jun 2024 19:19:33 +0200 Subject: [PATCH 6/9] feature/nanocl: backup command to save the current state (#970) --- .gitignore | 1 + bin/nanocl/src/commands/backup.rs | 164 ++++++++++++++++++++++++++++++ bin/nanocl/src/commands/mod.rs | 2 + bin/nanocl/src/main.rs | 1 + bin/nanocl/src/models/backup.rs | 12 +++ bin/nanocl/src/models/mod.rs | 4 + tests/backup/.gitkeep | 0 7 files changed, 184 insertions(+) create mode 100644 bin/nanocl/src/commands/backup.rs create mode 100644 bin/nanocl/src/models/backup.rs create mode 100644 tests/backup/.gitkeep diff --git a/.gitignore b/.gitignore index da57e3a3f..7ee9fc086 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ test_local/ .vagrant/ tests/ubuntu-22.04-minimal-cloudimg-amd64.img lcov.info +tests/backup/*.yml diff --git a/bin/nanocl/src/commands/backup.rs b/bin/nanocl/src/commands/backup.rs new file mode 100644 index 000000000..a28408fe8 --- /dev/null +++ b/bin/nanocl/src/commands/backup.rs @@ -0,0 +1,164 @@ +use nanocl_error::io::{IoError, IoResult}; +use nanocld_client::stubs::{ + cargo_spec::CargoSpecPartial, generic::GenericFilterNsp, job::JobPartial, + resource::ResourcePartial, secret::SecretPartial, statefile::Statefile, + vm_spec::VmSpecPartial, +}; + +use crate::{utils, config::CliConfig, models::BackupOpts}; + +pub async fn exec_backup( + cli_conf: &CliConfig, + opts: &BackupOpts, +) -> IoResult<()> { + let dir_path = opts + .output_dir + .clone() + .unwrap_or(std::env::current_dir()?.to_string_lossy().to_string()); + std::fs::create_dir_all(&dir_path)?; + // Backup namespaces + let namespaces = cli_conf.client.list_namespace(None).await?; + for namespace in namespaces { + let file_path = format!("{}/{}.yml", dir_path, namespace.name); + let token = format!("namespace/{}", namespace.name); + let pg_style = utils::progress::create_spinner_style(&token, "green"); + let pg = utils::progress::create_progress("(processing)", &pg_style); + pg.set_message("(processing: cargoes)"); + let cargoes = cli_conf + .client + .list_cargo(Some(&GenericFilterNsp { + namespace: Some(namespace.name.clone()), + ..Default::default() + })) + .await? + .iter() + .map(|cargo| cargo.spec.clone().into()) + .collect::>(); + pg.set_message("(processing: virtual machines)"); + let vms = cli_conf + .client + .list_vm(Some(&GenericFilterNsp { + namespace: Some(namespace.name.clone()), + ..Default::default() + })) + .await? + .iter() + .map(|vm| vm.spec.clone().into()) + .collect::>(); + pg.set_message(format!("(writing statefile: {}.yml)", namespace.name)); + let state_file = Statefile { + api_version: cli_conf.client.version.clone(), + sub_states: None, + args: None, + group: None, + namespace: Some(namespace.name.clone()), + secrets: None, + resources: None, + cargoes: Some(cargoes), + virtual_machines: Some(vms), + jobs: None, + }; + let data = serde_yaml::to_string(&state_file).map_err(|err| { + IoError::interrupted("Backup state", err.to_string().as_str()) + })?; + pg.finish_with_message(format!("(backup: {}.yml)", namespace.name)); + if std::path::Path::new(&file_path).exists() && !opts.skip_confirm { + utils::dialog::confirm("File already exist override ?")?; + } + std::fs::write(&file_path, data)?; + } + // Backup jobs + let file_path = format!("{}/jobs.yml", dir_path); + let pg_style = utils::progress::create_spinner_style("jobs", "green"); + let pg = utils::progress::create_progress("(processing)", &pg_style); + let jobs = cli_conf + .client + .list_job(None) + .await? + .iter() + .map(|job| job.spec.clone().into()) + .collect::>(); + if std::path::Path::new(&file_path).exists() && !opts.skip_confirm { + utils::dialog::confirm("File already exist override ?")?; + } + let state_file = Statefile { + api_version: cli_conf.client.version.clone(), + sub_states: None, + args: None, + group: None, + namespace: None, + secrets: None, + resources: None, + cargoes: None, + virtual_machines: None, + jobs: Some(jobs), + }; + let data = serde_yaml::to_string(&state_file).map_err(|err| { + IoError::interrupted("Backup state", err.to_string().as_str()) + })?; + std::fs::write(&file_path, data)?; + pg.finish_with_message("(backup: jobs.yml)"); + // Backup secrets + let file_path = format!("{}/secrets.yml", dir_path); + let pg_style = utils::progress::create_spinner_style("secrets", "green"); + let pg = utils::progress::create_progress("(processing)", &pg_style); + let secrets = cli_conf + .client + .list_secret(None) + .await? + .into_iter() + .map(|secret| secret.into()) + .collect::>(); + if std::path::Path::new(&file_path).exists() && !opts.skip_confirm { + utils::dialog::confirm("File already exist override ?")?; + } + let state_file = Statefile { + api_version: cli_conf.client.version.clone(), + sub_states: None, + args: None, + group: None, + namespace: None, + secrets: Some(secrets), + resources: None, + cargoes: None, + virtual_machines: None, + jobs: None, + }; + let data = serde_yaml::to_string(&state_file).map_err(|err| { + IoError::interrupted("Backup state", err.to_string().as_str()) + })?; + std::fs::write(&file_path, data)?; + pg.finish_with_message("(backup: secrets.yml)"); + // Backup resources + let file_path = format!("{}/resources.yml", dir_path); + let pg_style = utils::progress::create_spinner_style("resources", "green"); + let pg = utils::progress::create_progress("(processing)", &pg_style); + let resources = cli_conf + .client + .list_resource(None) + .await? + .iter() + .map(|resource| resource.clone().into()) + .collect::>(); + if std::path::Path::new(&file_path).exists() && !opts.skip_confirm { + utils::dialog::confirm("File already exist override ?")?; + } + let state_file = Statefile { + api_version: cli_conf.client.version.clone(), + sub_states: None, + args: None, + group: None, + namespace: None, + secrets: None, + resources: Some(resources), + cargoes: None, + virtual_machines: None, + jobs: None, + }; + let data = serde_yaml::to_string(&state_file).map_err(|err| { + IoError::interrupted("Backup state", err.to_string().as_str()) + })?; + std::fs::write(&file_path, data)?; + pg.finish_with_message("(backup: resources.yml)"); + Ok(()) +} diff --git a/bin/nanocl/src/commands/mod.rs b/bin/nanocl/src/commands/mod.rs index 2fda18fcb..18b439a55 100644 --- a/bin/nanocl/src/commands/mod.rs +++ b/bin/nanocl/src/commands/mod.rs @@ -16,6 +16,7 @@ mod job; mod generic; mod metric; mod event; +mod backup; pub use generic::*; @@ -35,3 +36,4 @@ pub use install::exec_install; pub use uninstall::exec_uninstall; pub use secret::exec_secret; pub use metric::exec_metric; +pub use backup::exec_backup; diff --git a/bin/nanocl/src/main.rs b/bin/nanocl/src/main.rs index 078b5ec71..6f07d1fcc 100644 --- a/bin/nanocl/src/main.rs +++ b/bin/nanocl/src/main.rs @@ -96,6 +96,7 @@ async fn execute_arg(cli_args: &Cli) -> IoResult<()> { Command::Context(args) => commands::exec_context(&cli_conf, args).await, Command::Info => commands::exec_info(&cli_conf).await, Command::Metric(args) => commands::exec_metric(&cli_conf, args).await, + Command::Backup(opts) => commands::exec_backup(&cli_conf, opts).await, } } diff --git a/bin/nanocl/src/models/backup.rs b/bin/nanocl/src/models/backup.rs new file mode 100644 index 000000000..8e3758248 --- /dev/null +++ b/bin/nanocl/src/models/backup.rs @@ -0,0 +1,12 @@ +use clap::Parser; + +#[derive(Clone, Parser)] +#[clap(name = "nanocl backup")] +pub struct BackupOpts { + /// Directory where to write the backup default to the current directory + #[clap(short, long)] + pub output_dir: Option, + /// Skip confirmation + #[clap(short = 'y', long = "yes")] + pub skip_confirm: bool, +} diff --git a/bin/nanocl/src/models/mod.rs b/bin/nanocl/src/models/mod.rs index 55ee799d5..819e7b5f8 100644 --- a/bin/nanocl/src/models/mod.rs +++ b/bin/nanocl/src/models/mod.rs @@ -18,6 +18,7 @@ mod job; mod generic; mod metric; mod event; +mod backup; pub use event::*; pub use generic::*; @@ -35,6 +36,7 @@ pub use install::*; pub use uninstall::*; pub use node::*; pub use job::*; +pub use backup::*; /// Cli available options and commands #[derive(Parser)] @@ -83,6 +85,8 @@ pub enum Command { Install(InstallOpts), /// Uninstall components Uninstall(UninstallOpts), + /// Backup the current state + Backup(BackupOpts), // TODO: shell completion // Completion { // /// Shell to generate completion for diff --git a/tests/backup/.gitkeep b/tests/backup/.gitkeep new file mode 100644 index 000000000..e69de29bb From facf63cbf23ced575ef611c55edf970b4a5099e7 Mon Sep 17 00:00:00 2001 From: leone Date: Mon, 3 Jun 2024 19:23:07 +0200 Subject: [PATCH 7/9] fix/nanocl: loader when removing a state (#971) --- bin/nanocl/src/commands/generic.rs | 14 +++++++------- bin/nanocl/src/commands/state.rs | 6 +++--- examples/cargo_example.yml | 3 ++- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/bin/nanocl/src/commands/generic.rs b/bin/nanocl/src/commands/generic.rs index 78ae55323..cb343a86f 100644 --- a/bin/nanocl/src/commands/generic.rs +++ b/bin/nanocl/src/commands/generic.rs @@ -15,12 +15,12 @@ use nanocl_error::{ use serde::{de::DeserializeOwned, Serialize}; use crate::{ + utils, config::CliConfig, models::{ GenericInspectOpts, GenericListOpts, GenericRemoveOpts, GenericStartOpts, GenericStopOpts, }, - utils, }; pub trait GenericCommand { @@ -129,9 +129,9 @@ where .map_err(|err| err.map_err_context(|| "Delete"))?; } for name in &opts.keys { - let token = format!("{object_name}/{}", name); + let token = format!("{object_name}/{name}"); let pg_style = utils::progress::create_spinner_style(&token, "red"); - let pg = utils::progress::create_progress(&token, &pg_style); + let pg = utils::progress::create_progress("(deleting)", &pg_style); let (key, waiter_kind) = match object_name { "vms" => ( format!("{name}.{}", namespace.clone().unwrap_or_default()), @@ -166,18 +166,18 @@ where { if let HttpClientError::HttpError(err) = &err { if err.status == StatusCode::NOT_FOUND { - pg.finish(); + pg.finish_with_message("(unchanged)"); continue; } } - pg.finish_and_clear(); - eprintln!("{err} {name}"); + pg.finish(); + eprintln!("{name}: {err}"); continue; } if let Some(waiter) = waiter { waiter.await??; } - pg.finish(); + pg.finish_with_message("(deleted)"); } Ok(()) } diff --git a/bin/nanocl/src/commands/state.rs b/bin/nanocl/src/commands/state.rs index d4a878e52..f5f679c21 100644 --- a/bin/nanocl/src/commands/state.rs +++ b/bin/nanocl/src/commands/state.rs @@ -618,10 +618,10 @@ async fn state_apply( .await?; client.delete_job(&job.name).await?; waiter.await??; - pg.finish_with_message("(cleared)"); + pg.set_message("(cleared)"); } client.create_job(job).await?; - pg.finish_with_message("(created)"); + pg.set_message("(created)"); let waiter = utils::process::wait_process_state( &job.name, EventActorKind::Job, @@ -658,7 +658,7 @@ async fn state_apply( } } } - pg.finish_with_message("(starting)"); + pg.set_message("(starting)"); let waiter = utils::process::wait_process_state( &format!("{}.{namespace}", cargo.name), EventActorKind::Cargo, diff --git a/examples/cargo_example.yml b/examples/cargo_example.yml index 460ac0380..95c11bd9c 100644 --- a/examples/cargo_example.yml +++ b/examples/cargo_example.yml @@ -6,7 +6,8 @@ Namespace: cargo-example # https://docs.next-hat.com/references/nanocl/objects/cargo Cargoes: - Name: cargo-example + ImagePullPolicy: Always Metadata: Certbot: managed Container: - Image: ghcr.io/next-hat/nanocl-get-started:latest + Image: nginx:latest From c369f31e527921c7059a9f7f868c4e5ef230a926 Mon Sep 17 00:00:00 2001 From: leon3s Date: Mon, 3 Jun 2024 19:28:13 +0200 Subject: [PATCH 8/9] chore/nanocld: update changelod.md --- bin/nanocld/changelog.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/bin/nanocld/changelog.md b/bin/nanocld/changelog.md index 53dcb359d..7350a1945 100644 --- a/bin/nanocld/changelog.md +++ b/bin/nanocld/changelog.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.15.0] +### Chore + +- Update ntex to version 2 + ### Added - `--store-addr` options from the command line to specify the store address @@ -15,11 +19,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Generic RepositoryCountBy interface - Endpoint `GET /metrics/count` to get the number of metrics - status field for VmSummary, CargoSummary +- Better generic filter to sql filter +- Indexes for all sql schema +- Filter query by dates +- Updating a secret now trigger an update for related cargoes + ### Fixed - Removing a job with a running process will now stop the process before removing the job - Patch a restarting cargo was not working and returning an error +- Create network namespace if not existing when inspecting a listing namespaces +- Patching cargo when restarting was not working ### Changed From c961296c3a344c96a78dcbf333f4e1b5f7b15d92 Mon Sep 17 00:00:00 2001 From: leon3s Date: Mon, 3 Jun 2024 19:33:16 +0200 Subject: [PATCH 9/9] chore/nanocl: update readme.md --- bin/nanocl/changelog.md | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/bin/nanocl/changelog.md b/bin/nanocl/changelog.md index 88ae58f84..be99b693d 100644 --- a/bin/nanocl/changelog.md +++ b/bin/nanocl/changelog.md @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.15.0] +### Added + +- Status information in the table of cargo ls and vm ls command and job ls +- `nanocl metric inspect` command to get details about a metric +- `nanocl event inspect` command to get details about an event +- `nanocl backup` command to backup the current state into multiple Statefiles +- `HOST` env variable to override the default host +- `CERT` and `CERT_KEY` env variable to pass certificate and certificate key to the client + ### Fixed - `nanocl cargo run` now correctly wait the cargo to be running before returning @@ -17,17 +26,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `nanocl job start` now correctly wait the job to be running before returning - `nanocl vm start` now correctly wait the vm to be running before returning - `nanocl vm stop` now correctly wait the vm to be stopped before returning +- Diff trigger when applying a Statefile now correctly compare the current state with the new state ### Changed - `inspect` `rm` `stop` `start` have been refactored to have a single interface matching all object - removed the namespace in the table of cargo ls and vm ls command - -### Added - -- Status information in the table of cargo ls and vm ls command and job ls -- `nanocl metric inspect` command to get details about a metric -- `nanocl event inspect` command to get details about an event +- Cleaner Loader when apply and removing Statefile ## [0.14.0] - 2024-05-08