From 5e81db68c6f91867eeaec22580b3645531c4a0ab Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Thu, 10 Apr 2025 15:19:54 +0200 Subject: [PATCH 01/24] Support custom Python processors via git-sync --- CHANGELOG.md | 2 + Cargo.lock | 109 +---- Cargo.nix | 355 +++----------- Cargo.toml | 3 + crate-hashes.json | 14 +- deploy/helm/nifi-operator/crds/crds.yaml | 64 ++- rust/operator-binary/src/config/mod.rs | 42 ++ rust/operator-binary/src/controller.rs | 57 ++- .../operator-binary/src/crd/authentication.rs | 62 +-- rust/operator-binary/src/crd/mod.rs | 10 +- rust/operator-binary/src/main.rs | 5 +- .../src/security/authentication.rs | 24 +- rust/operator-binary/src/security/oidc.rs | 20 +- .../00-patch-ns.yaml.j2 | 9 + .../10-assert.yaml.j2 | 10 + ...tor-aggregator-discovery-configmap.yaml.j2 | 9 + .../custom-processors-git-sync/20-assert.yaml | 12 + .../20-install-zk.yaml.j2 | 28 ++ .../custom-processors-git-sync/30-assert.yaml | 16 + .../30-install-nifi.yaml.j2 | 458 ++++++++++++++++++ .../custom-processors-git-sync/40-assert.yaml | 11 + .../40-test-nifi-greeting.yaml | 18 + .../processors-0/greet_processor.py | 16 + .../processors-1/shout_processor.py | 18 + tests/test-definition.yaml | 7 +- 25 files changed, 931 insertions(+), 448 deletions(-) create mode 100644 tests/templates/kuttl/custom-processors-git-sync/00-patch-ns.yaml.j2 create mode 100644 tests/templates/kuttl/custom-processors-git-sync/10-assert.yaml.j2 create mode 100644 tests/templates/kuttl/custom-processors-git-sync/10-install-vector-aggregator-discovery-configmap.yaml.j2 create mode 100644 tests/templates/kuttl/custom-processors-git-sync/20-assert.yaml create mode 100644 tests/templates/kuttl/custom-processors-git-sync/20-install-zk.yaml.j2 create mode 100644 tests/templates/kuttl/custom-processors-git-sync/30-assert.yaml create mode 100644 tests/templates/kuttl/custom-processors-git-sync/30-install-nifi.yaml.j2 create mode 100644 tests/templates/kuttl/custom-processors-git-sync/40-assert.yaml create mode 100644 tests/templates/kuttl/custom-processors-git-sync/40-test-nifi-greeting.yaml create mode 100644 tests/templates/kuttl/custom-processors-git-sync/processors-0/greet_processor.py create mode 100644 tests/templates/kuttl/custom-processors-git-sync/processors-1/shout_processor.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 0497f782..56d92c40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ All notable changes to this project will be documented in this file. - Use `--file-log-max-files` (or `FILE_LOG_MAX_FILES`) to limit the number of log files kept. - Use `--file-log-rotation-period` (or `FILE_LOG_ROTATION_PERIOD`) to configure the frequency of rotation. - Use `--console-log-format` (or `CONSOLE_LOG_FORMAT`) to set the format to `plain` (default) or `json`. +- Add support for custom Python processors via git-sync ([#793]). ### Changed @@ -41,6 +42,7 @@ All notable changes to this project will be documented in this file. [#782]: https://github.com/stackabletech/nifi-operator/pull/782 [#787]: https://github.com/stackabletech/nifi-operator/pull/787 [#789]: https://github.com/stackabletech/nifi-operator/pull/789 +[#793]: https://github.com/stackabletech/nifi-operator/pull/793 ## [25.3.0] - 2025-03-21 diff --git a/Cargo.lock b/Cargo.lock index 92e49bf7..b6ded60c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -173,40 +173,13 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" -[[package]] -name = "axum" -version = "0.7.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" -dependencies = [ - "async-trait", - "axum-core 0.4.5", - "bytes", - "futures-util", - "http", - "http-body", - "http-body-util", - "itoa", - "matchit 0.7.3", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "rustversion", - "serde", - "sync_wrapper", - "tower 0.5.2", - "tower-layer", - "tower-service", -] - [[package]] name = "axum" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de45108900e1f9b9242f7f2e254aa3e2c029c921c258fe9e6b4217eeebd54288" dependencies = [ - "axum-core 0.5.2", + "axum-core", "bytes", "form_urlencoded", "futures-util", @@ -216,7 +189,7 @@ dependencies = [ "hyper", "hyper-util", "itoa", - "matchit 0.8.4", + "matchit", "memchr", "mime", "percent-encoding", @@ -234,26 +207,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "axum-core" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" -dependencies = [ - "async-trait", - "bytes", - "futures-util", - "http", - "http-body", - "http-body-util", - "mime", - "pin-project-lite", - "rustversion", - "sync_wrapper", - "tower-layer", - "tower-service", -] - [[package]] name = "axum-core" version = "0.5.2" @@ -1482,7 +1435,7 @@ dependencies = [ [[package]] name = "k8s-version" version = "0.1.2" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.92.0#5fdc47a10de685e4eea49fd0a3f6c3a15a4966c1" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#5d7a0076b1e51b6105375e46e44e79198d6e233a" dependencies = [ "darling", "regex", @@ -1666,12 +1619,6 @@ dependencies = [ "regex-automata 0.1.10", ] -[[package]] -name = "matchit" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" - [[package]] name = "matchit" version = "0.8.4" @@ -1758,9 +1705,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "opentelemetry" -version = "0.28.0" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "236e667b670a5cdf90c258f5a55794ec5ac5027e960c224bff8367a59e1e6426" +checksum = "9e87237e2775f74896f9ad219d26a2081751187eb7c9f5c58dde20a23b95d16c" dependencies = [ "futures-core", "futures-sink", @@ -1772,9 +1719,9 @@ dependencies = [ [[package]] name = "opentelemetry-appender-tracing" -version = "0.28.1" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c513c7af3bec30113f3d4620134ff923295f1e9c580fda2b8abe0831f925ddc0" +checksum = "e716f864eb23007bdd9dc4aec381e188a1cee28eecf22066772b5fd822b9727d" dependencies = [ "opentelemetry", "tracing", @@ -1784,9 +1731,9 @@ dependencies = [ [[package]] name = "opentelemetry-http" -version = "0.28.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8863faf2910030d139fb48715ad5ff2f35029fc5f244f6d5f689ddcf4d26253" +checksum = "46d7ab32b827b5b495bd90fa95a6cb65ccc293555dcc3199ae2937d2d237c8ed" dependencies = [ "async-trait", "bytes", @@ -1798,11 +1745,10 @@ dependencies = [ [[package]] name = "opentelemetry-otlp" -version = "0.28.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bef114c6d41bea83d6dc60eb41720eedd0261a67af57b66dd2b84ac46c01d91" +checksum = "d899720fe06916ccba71c01d04ecd77312734e2de3467fd30d9d580c8ce85656" dependencies = [ - "async-trait", "futures-core", "http", "opentelemetry", @@ -1819,9 +1765,9 @@ dependencies = [ [[package]] name = "opentelemetry-proto" -version = "0.28.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f8870d3024727e99212eb3bb1762ec16e255e3e6f58eeb3dc8db1aa226746d" +checksum = "8c40da242381435e18570d5b9d50aca2a4f4f4d8e146231adb4e7768023309b3" dependencies = [ "opentelemetry", "opentelemetry_sdk", @@ -1831,18 +1777,17 @@ dependencies = [ [[package]] name = "opentelemetry_sdk" -version = "0.28.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84dfad6042089c7fc1f6118b7040dc2eb4ab520abbf410b79dc481032af39570" +checksum = "afdefb21d1d47394abc1ba6c57363ab141be19e27cc70d0e422b7f303e4d290b" dependencies = [ - "async-trait", "futures-channel", "futures-executor", "futures-util", "glob", "opentelemetry", "percent-encoding", - "rand 0.8.5", + "rand 0.9.0", "serde_json", "thiserror 2.0.12", "tokio", @@ -2701,7 +2646,7 @@ dependencies = [ [[package]] name = "stackable-operator" version = "0.92.0" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.92.0#5fdc47a10de685e4eea49fd0a3f6c3a15a4966c1" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#5d7a0076b1e51b6105375e46e44e79198d6e233a" dependencies = [ "chrono", "clap", @@ -2738,7 +2683,7 @@ dependencies = [ [[package]] name = "stackable-operator-derive" version = "0.3.1" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.92.0#5fdc47a10de685e4eea49fd0a3f6c3a15a4966c1" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#5d7a0076b1e51b6105375e46e44e79198d6e233a" dependencies = [ "darling", "proc-macro2", @@ -2749,7 +2694,7 @@ dependencies = [ [[package]] name = "stackable-shared" version = "0.0.1" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.92.0#5fdc47a10de685e4eea49fd0a3f6c3a15a4966c1" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#5d7a0076b1e51b6105375e46e44e79198d6e233a" dependencies = [ "kube", "semver", @@ -2761,9 +2706,9 @@ dependencies = [ [[package]] name = "stackable-telemetry" version = "0.6.0" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.92.0#5fdc47a10de685e4eea49fd0a3f6c3a15a4966c1" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#5d7a0076b1e51b6105375e46e44e79198d6e233a" dependencies = [ - "axum 0.8.3", + "axum", "clap", "futures-util", "opentelemetry", @@ -2784,7 +2729,7 @@ dependencies = [ [[package]] name = "stackable-versioned" version = "0.7.1" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.92.0#5fdc47a10de685e4eea49fd0a3f6c3a15a4966c1" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#5d7a0076b1e51b6105375e46e44e79198d6e233a" dependencies = [ "stackable-versioned-macros", ] @@ -2792,7 +2737,7 @@ dependencies = [ [[package]] name = "stackable-versioned-macros" version = "0.7.1" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.92.0#5fdc47a10de685e4eea49fd0a3f6c3a15a4966c1" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#5d7a0076b1e51b6105375e46e44e79198d6e233a" dependencies = [ "convert_case", "darling", @@ -3059,13 +3004,10 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" dependencies = [ - "async-stream", "async-trait", - "axum 0.7.9", "base64 0.22.1", "bytes", "flate2", - "h2", "http", "http-body", "http-body-util", @@ -3075,7 +3017,6 @@ dependencies = [ "percent-encoding", "pin-project", "prost", - "socket2", "tokio", "tokio-stream", "tower 0.4.13", @@ -3209,9 +3150,9 @@ dependencies = [ [[package]] name = "tracing-opentelemetry" -version = "0.29.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "721f2d2569dce9f3dfbbddee5906941e953bfcdf736a62da3377f5751650cc36" +checksum = "fd8e764bd6f5813fd8bebc3117875190c5b0415be8f7f8059bffb6ecd979c444" dependencies = [ "js-sys", "once_cell", diff --git a/Cargo.nix b/Cargo.nix index 934bf0b7..f7067c94 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -516,123 +516,7 @@ rec { ]; }; - "axum 0.7.9" = rec { - crateName = "axum"; - version = "0.7.9"; - edition = "2021"; - sha256 = "07z7wqczi9i8xb4460rvn39p4wjqwr32hx907crd1vwb2fy8ijpd"; - dependencies = [ - { - name = "async-trait"; - packageId = "async-trait"; - } - { - name = "axum-core"; - packageId = "axum-core 0.4.5"; - } - { - name = "bytes"; - packageId = "bytes"; - } - { - name = "futures-util"; - packageId = "futures-util"; - usesDefaultFeatures = false; - features = [ "alloc" ]; - } - { - name = "http"; - packageId = "http"; - } - { - name = "http-body"; - packageId = "http-body"; - } - { - name = "http-body-util"; - packageId = "http-body-util"; - } - { - name = "itoa"; - packageId = "itoa"; - } - { - name = "matchit"; - packageId = "matchit 0.7.3"; - } - { - name = "memchr"; - packageId = "memchr"; - } - { - name = "mime"; - packageId = "mime"; - } - { - name = "percent-encoding"; - packageId = "percent-encoding"; - } - { - name = "pin-project-lite"; - packageId = "pin-project-lite"; - } - { - name = "rustversion"; - packageId = "rustversion"; - } - { - name = "serde"; - packageId = "serde"; - } - { - name = "sync_wrapper"; - packageId = "sync_wrapper"; - } - { - name = "tower"; - packageId = "tower 0.5.2"; - usesDefaultFeatures = false; - features = [ "util" ]; - } - { - name = "tower-layer"; - packageId = "tower-layer"; - } - { - name = "tower-service"; - packageId = "tower-service"; - } - ]; - devDependencies = [ - { - name = "serde"; - packageId = "serde"; - features = [ "derive" ]; - } - { - name = "tower"; - packageId = "tower 0.5.2"; - rename = "tower"; - features = [ "util" "timeout" "limit" "load-shed" "steer" "filter" ]; - } - ]; - features = { - "__private_docs" = [ "axum-core/__private_docs" "tower/full" "dep:tower-http" ]; - "default" = [ "form" "http1" "json" "matched-path" "original-uri" "query" "tokio" "tower-log" "tracing" ]; - "form" = [ "dep:serde_urlencoded" ]; - "http1" = [ "dep:hyper" "hyper?/http1" "hyper-util?/http1" ]; - "http2" = [ "dep:hyper" "hyper?/http2" "hyper-util?/http2" ]; - "json" = [ "dep:serde_json" "dep:serde_path_to_error" ]; - "macros" = [ "dep:axum-macros" ]; - "multipart" = [ "dep:multer" ]; - "query" = [ "dep:serde_urlencoded" ]; - "tokio" = [ "dep:hyper-util" "dep:tokio" "tokio/net" "tokio/rt" "tower/make" "tokio/macros" ]; - "tower-log" = [ "tower/log" ]; - "tracing" = [ "dep:tracing" "axum-core/tracing" ]; - "ws" = [ "dep:hyper" "tokio" "dep:tokio-tungstenite" "dep:sha1" "dep:base64" ]; - }; - }; - "axum 0.8.3" = rec { + "axum" = rec { crateName = "axum"; version = "0.8.3"; edition = "2021"; @@ -640,7 +524,7 @@ rec { dependencies = [ { name = "axum-core"; - packageId = "axum-core 0.5.2"; + packageId = "axum-core"; } { name = "bytes"; @@ -686,7 +570,7 @@ rec { } { name = "matchit"; - packageId = "matchit 0.8.4"; + packageId = "matchit"; } { name = "memchr"; @@ -811,78 +695,7 @@ rec { }; resolvedDefaultFeatures = [ "default" "form" "http1" "json" "matched-path" "original-uri" "query" "tokio" "tower-log" "tracing" ]; }; - "axum-core 0.4.5" = rec { - crateName = "axum-core"; - version = "0.4.5"; - edition = "2021"; - sha256 = "16b1496c4gm387q20hkv5ic3k5bd6xmnvk50kwsy6ymr8rhvvwh9"; - libName = "axum_core"; - dependencies = [ - { - name = "async-trait"; - packageId = "async-trait"; - } - { - name = "bytes"; - packageId = "bytes"; - } - { - name = "futures-util"; - packageId = "futures-util"; - usesDefaultFeatures = false; - features = [ "alloc" ]; - } - { - name = "http"; - packageId = "http"; - } - { - name = "http-body"; - packageId = "http-body"; - } - { - name = "http-body-util"; - packageId = "http-body-util"; - } - { - name = "mime"; - packageId = "mime"; - } - { - name = "pin-project-lite"; - packageId = "pin-project-lite"; - } - { - name = "rustversion"; - packageId = "rustversion"; - } - { - name = "sync_wrapper"; - packageId = "sync_wrapper"; - } - { - name = "tower-layer"; - packageId = "tower-layer"; - } - { - name = "tower-service"; - packageId = "tower-service"; - } - ]; - devDependencies = [ - { - name = "futures-util"; - packageId = "futures-util"; - usesDefaultFeatures = false; - features = [ "alloc" ]; - } - ]; - features = { - "__private_docs" = [ "dep:tower-http" ]; - "tracing" = [ "dep:tracing" ]; - }; - }; - "axum-core 0.5.2" = rec { + "axum-core" = rec { crateName = "axum-core"; version = "0.5.2"; edition = "2021"; @@ -3760,7 +3573,7 @@ rec { "tokio" = [ "dep:tokio" "tokio/net" "tokio/rt" "tokio/time" ]; "tracing" = [ "dep:tracing" ]; }; - resolvedDefaultFeatures = [ "client" "client-legacy" "default" "http1" "http2" "server" "server-auto" "service" "tokio" ]; + resolvedDefaultFeatures = [ "client" "client-legacy" "default" "http1" "server" "service" "tokio" ]; }; "iana-time-zone" = rec { crateName = "iana-time-zone"; @@ -4670,9 +4483,9 @@ rec { edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "5fdc47a10de685e4eea49fd0a3f6c3a15a4966c1"; - sha256 = "0li9smdrz7danqz17lfkl0j9zl2i84csgc7d01lxs5qi8jcs9fzw"; + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "5d7a0076b1e51b6105375e46e44e79198d6e233a"; + sha256 = "1aziyw6b3ldvp0r5hbqgg0q4qph4xk45y5rfnvfn7k8fi2niqrli"; }; libName = "k8s_version"; authors = [ @@ -5460,19 +5273,7 @@ rec { ]; }; - "matchit 0.7.3" = rec { - crateName = "matchit"; - version = "0.7.3"; - edition = "2021"; - sha256 = "156bgdmmlv4crib31qhgg49nsjk88dxkdqp80ha2pk2rk6n6ax0f"; - authors = [ - "Ibraheem Ahmed " - ]; - features = { - }; - resolvedDefaultFeatures = [ "default" ]; - }; - "matchit 0.8.4" = rec { + "matchit" = rec { crateName = "matchit"; version = "0.8.4"; edition = "2021"; @@ -5715,9 +5516,9 @@ rec { }; "opentelemetry" = rec { crateName = "opentelemetry"; - version = "0.28.0"; + version = "0.29.1"; edition = "2021"; - sha256 = "09k43sgaarw3zx5j434ngq1canpcjibsbxaqqa8dyp0acxxncvi3"; + sha256 = "0v6ijlxs486yip2zbjdpgqc525q8l8k9s8ddz6b4ixvm4xz271wy"; dependencies = [ { name = "futures-core"; @@ -5753,7 +5554,8 @@ rec { } ]; features = { - "default" = [ "trace" "metrics" "logs" "internal-logs" ]; + "default" = [ "trace" "metrics" "logs" "internal-logs" "futures" ]; + "futures" = [ "futures-core" "futures-sink" "pin-project-lite" ]; "futures-core" = [ "dep:futures-core" ]; "futures-sink" = [ "dep:futures-sink" ]; "internal-logs" = [ "tracing" ]; @@ -5761,16 +5563,16 @@ rec { "spec_unstable_logs_enabled" = [ "logs" ]; "testing" = [ "trace" ]; "thiserror" = [ "dep:thiserror" ]; - "trace" = [ "pin-project-lite" "futures-sink" "futures-core" "thiserror" ]; + "trace" = [ "futures" "thiserror" ]; "tracing" = [ "dep:tracing" ]; }; - resolvedDefaultFeatures = [ "default" "futures-core" "futures-sink" "internal-logs" "logs" "metrics" "pin-project-lite" "spec_unstable_logs_enabled" "thiserror" "trace" "tracing" ]; + resolvedDefaultFeatures = [ "default" "futures" "futures-core" "futures-sink" "internal-logs" "logs" "metrics" "pin-project-lite" "spec_unstable_logs_enabled" "thiserror" "trace" "tracing" ]; }; "opentelemetry-appender-tracing" = rec { crateName = "opentelemetry-appender-tracing"; - version = "0.28.1"; + version = "0.29.1"; edition = "2021"; - sha256 = "1h6x4pwk225yi8mxl3sqkhg5ya93z57i68267lzi2c7c7fpwf4y5"; + sha256 = "0zbjp4idhprbfxk21wpcivicx8c8w60w7bn4kpfpn013xdjgh5p7"; libName = "opentelemetry_appender_tracing"; dependencies = [ { @@ -5797,11 +5599,17 @@ rec { } ]; devDependencies = [ + { + name = "tracing"; + packageId = "tracing"; + usesDefaultFeatures = false; + features = [ "std" ]; + } { name = "tracing-subscriber"; packageId = "tracing-subscriber"; usesDefaultFeatures = false; - features = [ "registry" "std" "env-filter" ]; + features = [ "env-filter" "registry" "std" "fmt" ]; } ]; features = { @@ -5815,9 +5623,9 @@ rec { }; "opentelemetry-http" = rec { crateName = "opentelemetry-http"; - version = "0.28.0"; + version = "0.29.0"; edition = "2021"; - sha256 = "0lv2sbsdr7b8bxnly92zzhlm1wzjbynib1xlkw9hs0qh56pkz1m8"; + sha256 = "1vf86z9d4dr9msck3k2xan9w5k35rfk9bylhpnav9d97p0rapms6"; libName = "opentelemetry_http"; dependencies = [ { @@ -5866,15 +5674,11 @@ rec { }; "opentelemetry-otlp" = rec { crateName = "opentelemetry-otlp"; - version = "0.28.0"; + version = "0.29.0"; edition = "2021"; - sha256 = "148xq13ar11bvmk7pxbslrhh5pgf40bv83n6dlysigj1dm613vsv"; + sha256 = "0mjnx260qn4x1p9pyip35m7764kkszn087f0f6xcq5k9w07p56fq"; libName = "opentelemetry_otlp"; dependencies = [ - { - name = "async-trait"; - packageId = "async-trait"; - } { name = "futures-core"; packageId = "futures-core"; @@ -5949,6 +5753,12 @@ rec { usesDefaultFeatures = false; features = [ "macros" "rt-multi-thread" ]; } + { + name = "tonic"; + packageId = "tonic"; + usesDefaultFeatures = false; + features = [ "server" ]; + } ]; features = { "default" = [ "http-proto" "reqwest-blocking-client" "trace" "metrics" "logs" "internal-logs" ]; @@ -5985,9 +5795,9 @@ rec { }; "opentelemetry-proto" = rec { crateName = "opentelemetry-proto"; - version = "0.28.0"; + version = "0.29.0"; edition = "2021"; - sha256 = "0vbl4si1mny87pmqxxg6wday45pcc8bvpcrf46cpwwi4606qgy2n"; + sha256 = "1cq96c16hxsfvcd26ip1v3sg9952mi89snqdawc5whw14cjdlh4c"; libName = "opentelemetry_proto"; dependencies = [ { @@ -6017,7 +5827,7 @@ rec { "base64" = [ "dep:base64" ]; "default" = [ "full" ]; "full" = [ "gen-tonic" "trace" "logs" "metrics" "zpages" "with-serde" "internal-logs" ]; - "gen-tonic" = [ "gen-tonic-messages" "tonic/transport" ]; + "gen-tonic" = [ "gen-tonic-messages" "tonic/channel" ]; "gen-tonic-messages" = [ "tonic" "prost" ]; "hex" = [ "dep:hex" ]; "internal-logs" = [ "tracing" ]; @@ -6038,15 +5848,10 @@ rec { }; "opentelemetry_sdk" = rec { crateName = "opentelemetry_sdk"; - version = "0.28.0"; + version = "0.29.0"; edition = "2021"; - sha256 = "0w4mycm070f4knvi1x5v199apd1fvi0712qiyv0pz70889havpw4"; + sha256 = "02r99lz30zrb8870vivww8cvwhdi78v5fv5sq6mr8wyls4hzppmg"; dependencies = [ - { - name = "async-trait"; - packageId = "async-trait"; - optional = true; - } { name = "futures-channel"; packageId = "futures-channel"; @@ -6077,10 +5882,10 @@ rec { } { name = "rand"; - packageId = "rand 0.8.5"; + packageId = "rand 0.9.0"; optional = true; usesDefaultFeatures = false; - features = [ "std" "std_rng" "small_rng" ]; + features = [ "std" "std_rng" "small_rng" "os_rng" "thread_rng" ]; } { name = "serde_json"; @@ -6112,10 +5917,9 @@ rec { } ]; features = { - "async-std" = [ "dep:async-std" ]; - "async-trait" = [ "dep:async-trait" ]; "default" = [ "trace" "metrics" "logs" "internal-logs" ]; "experimental_logs_batch_log_processor_with_async_runtime" = [ "logs" ]; + "experimental_logs_concurrent_log_processor" = [ "logs" ]; "experimental_metrics_disable_name_validation" = [ "metrics" ]; "experimental_metrics_periodicreader_with_async_runtime" = [ "metrics" ]; "experimental_trace_batch_span_processor_with_async_runtime" = [ "trace" ]; @@ -6124,25 +5928,24 @@ rec { "internal-logs" = [ "tracing" ]; "jaeger_remote_sampler" = [ "trace" "opentelemetry-http" "http" "serde" "serde_json" "url" ]; "logs" = [ "opentelemetry/logs" "serde_json" ]; - "metrics" = [ "opentelemetry/metrics" "glob" "async-trait" ]; + "metrics" = [ "opentelemetry/metrics" "glob" ]; "opentelemetry-http" = [ "dep:opentelemetry-http" ]; "percent-encoding" = [ "dep:percent-encoding" ]; "rand" = [ "dep:rand" ]; - "rt-async-std" = [ "async-std" "experimental_async_runtime" ]; "rt-tokio" = [ "tokio" "tokio-stream" "experimental_async_runtime" ]; "rt-tokio-current-thread" = [ "tokio" "tokio-stream" "experimental_async_runtime" ]; "serde" = [ "dep:serde" ]; "serde_json" = [ "dep:serde_json" ]; "spec_unstable_logs_enabled" = [ "logs" "opentelemetry/spec_unstable_logs_enabled" ]; "spec_unstable_metrics_views" = [ "metrics" ]; - "testing" = [ "opentelemetry/testing" "trace" "metrics" "logs" "rt-async-std" "rt-tokio" "rt-tokio-current-thread" "tokio/macros" "tokio/rt-multi-thread" ]; + "testing" = [ "opentelemetry/testing" "trace" "metrics" "logs" "rt-tokio" "rt-tokio-current-thread" "tokio/macros" "tokio/rt-multi-thread" ]; "tokio" = [ "dep:tokio" ]; "tokio-stream" = [ "dep:tokio-stream" ]; "trace" = [ "opentelemetry/trace" "rand" "percent-encoding" ]; "tracing" = [ "dep:tracing" ]; "url" = [ "dep:url" ]; }; - resolvedDefaultFeatures = [ "async-trait" "default" "experimental_async_runtime" "glob" "internal-logs" "logs" "metrics" "percent-encoding" "rand" "rt-tokio" "serde_json" "spec_unstable_logs_enabled" "tokio" "tokio-stream" "trace" "tracing" ]; + resolvedDefaultFeatures = [ "default" "experimental_async_runtime" "glob" "internal-logs" "logs" "metrics" "percent-encoding" "rand" "rt-tokio" "serde_json" "spec_unstable_logs_enabled" "tokio" "tokio-stream" "trace" "tracing" ]; }; "ordered-float" = rec { crateName = "ordered-float"; @@ -8800,9 +8603,9 @@ rec { edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "5fdc47a10de685e4eea49fd0a3f6c3a15a4966c1"; - sha256 = "0li9smdrz7danqz17lfkl0j9zl2i84csgc7d01lxs5qi8jcs9fzw"; + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "5d7a0076b1e51b6105375e46e44e79198d6e233a"; + sha256 = "1aziyw6b3ldvp0r5hbqgg0q4qph4xk45y5rfnvfn7k8fi2niqrli"; }; libName = "stackable_operator"; authors = [ @@ -8959,9 +8762,9 @@ rec { edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "5fdc47a10de685e4eea49fd0a3f6c3a15a4966c1"; - sha256 = "0li9smdrz7danqz17lfkl0j9zl2i84csgc7d01lxs5qi8jcs9fzw"; + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "5d7a0076b1e51b6105375e46e44e79198d6e233a"; + sha256 = "1aziyw6b3ldvp0r5hbqgg0q4qph4xk45y5rfnvfn7k8fi2niqrli"; }; procMacro = true; libName = "stackable_operator_derive"; @@ -8994,9 +8797,9 @@ rec { edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "5fdc47a10de685e4eea49fd0a3f6c3a15a4966c1"; - sha256 = "0li9smdrz7danqz17lfkl0j9zl2i84csgc7d01lxs5qi8jcs9fzw"; + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "5d7a0076b1e51b6105375e46e44e79198d6e233a"; + sha256 = "1aziyw6b3ldvp0r5hbqgg0q4qph4xk45y5rfnvfn7k8fi2niqrli"; }; libName = "stackable_shared"; authors = [ @@ -9035,9 +8838,9 @@ rec { edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "5fdc47a10de685e4eea49fd0a3f6c3a15a4966c1"; - sha256 = "0li9smdrz7danqz17lfkl0j9zl2i84csgc7d01lxs5qi8jcs9fzw"; + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "5d7a0076b1e51b6105375e46e44e79198d6e233a"; + sha256 = "1aziyw6b3ldvp0r5hbqgg0q4qph4xk45y5rfnvfn7k8fi2niqrli"; }; libName = "stackable_telemetry"; authors = [ @@ -9046,7 +8849,7 @@ rec { dependencies = [ { name = "axum"; - packageId = "axum 0.8.3"; + packageId = "axum"; } { name = "clap"; @@ -9140,9 +8943,9 @@ rec { edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "5fdc47a10de685e4eea49fd0a3f6c3a15a4966c1"; - sha256 = "0li9smdrz7danqz17lfkl0j9zl2i84csgc7d01lxs5qi8jcs9fzw"; + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "5d7a0076b1e51b6105375e46e44e79198d6e233a"; + sha256 = "1aziyw6b3ldvp0r5hbqgg0q4qph4xk45y5rfnvfn7k8fi2niqrli"; }; libName = "stackable_versioned"; authors = [ @@ -9166,9 +8969,9 @@ rec { edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "5fdc47a10de685e4eea49fd0a3f6c3a15a4966c1"; - sha256 = "0li9smdrz7danqz17lfkl0j9zl2i84csgc7d01lxs5qi8jcs9fzw"; + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "5d7a0076b1e51b6105375e46e44e79198d6e233a"; + sha256 = "1aziyw6b3ldvp0r5hbqgg0q4qph4xk45y5rfnvfn7k8fi2niqrli"; }; procMacro = true; libName = "stackable_versioned_macros"; @@ -9899,7 +9702,7 @@ rec { "time" = [ "tokio/time" ]; "tokio-util" = [ "dep:tokio-util" ]; }; - resolvedDefaultFeatures = [ "default" "net" "time" ]; + resolvedDefaultFeatures = [ "default" "time" ]; }; "tokio-util" = rec { crateName = "tokio-util"; @@ -10015,22 +9818,11 @@ rec { "Lucio Franco " ]; dependencies = [ - { - name = "async-stream"; - packageId = "async-stream"; - optional = true; - } { name = "async-trait"; packageId = "async-trait"; optional = true; } - { - name = "axum"; - packageId = "axum 0.7.9"; - optional = true; - usesDefaultFeatures = false; - } { name = "base64"; packageId = "base64 0.22.1"; @@ -10044,11 +9836,6 @@ rec { packageId = "flate2"; optional = true; } - { - name = "h2"; - packageId = "h2"; - optional = true; - } { name = "http"; packageId = "http"; @@ -10093,12 +9880,6 @@ rec { usesDefaultFeatures = false; features = [ "std" ]; } - { - name = "socket2"; - packageId = "socket2"; - optional = true; - features = [ "all" ]; - } { name = "tokio"; packageId = "tokio"; @@ -10156,7 +9937,7 @@ rec { "transport" = [ "server" "channel" ]; "zstd" = [ "dep:zstd" ]; }; - resolvedDefaultFeatures = [ "channel" "codegen" "gzip" "prost" "router" "server" "transport" ]; + resolvedDefaultFeatures = [ "channel" "codegen" "gzip" "prost" ]; }; "tower 0.4.13" = rec { crateName = "tower"; @@ -10696,9 +10477,9 @@ rec { }; "tracing-opentelemetry" = rec { crateName = "tracing-opentelemetry"; - version = "0.29.0"; + version = "0.30.0"; edition = "2021"; - sha256 = "0dnca0b7bxbp6gd64skkvzy3p58yjh35kvnxpggz7sfwd4jjs7vj"; + sha256 = "0i64g7cyrdpzkc2zixz8bd0v1icha63ifcdwpvc3z0gmsr5pd3px"; libName = "tracing_opentelemetry"; dependencies = [ { diff --git a/Cargo.toml b/Cargo.toml index 61407770..017ad61d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,3 +37,6 @@ xml-rs = "0.8" # [patch."https://github.com/stackabletech/operator-rs.git"] # stackable-operator = { git = "https://github.com/stackabletech//operator-rs.git", branch = "main" } # stackable-operator = { path = "../operator-rs/crates/stackable-operator" } + +[patch."https://github.com/stackabletech/operator-rs.git"] +stackable-operator = { git = "https://github.com/stackabletech//operator-rs.git", branch = "feat/git-sync" } diff --git a/crate-hashes.json b/crate-hashes.json index 9dcf61ef..fb21465b 100644 --- a/crate-hashes.json +++ b/crate-hashes.json @@ -1,10 +1,10 @@ { - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.92.0#k8s-version@0.1.2": "0li9smdrz7danqz17lfkl0j9zl2i84csgc7d01lxs5qi8jcs9fzw", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.92.0#stackable-operator-derive@0.3.1": "0li9smdrz7danqz17lfkl0j9zl2i84csgc7d01lxs5qi8jcs9fzw", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.92.0#stackable-operator@0.92.0": "0li9smdrz7danqz17lfkl0j9zl2i84csgc7d01lxs5qi8jcs9fzw", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.92.0#stackable-shared@0.0.1": "0li9smdrz7danqz17lfkl0j9zl2i84csgc7d01lxs5qi8jcs9fzw", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.92.0#stackable-telemetry@0.6.0": "0li9smdrz7danqz17lfkl0j9zl2i84csgc7d01lxs5qi8jcs9fzw", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.92.0#stackable-versioned-macros@0.7.1": "0li9smdrz7danqz17lfkl0j9zl2i84csgc7d01lxs5qi8jcs9fzw", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.92.0#stackable-versioned@0.7.1": "0li9smdrz7danqz17lfkl0j9zl2i84csgc7d01lxs5qi8jcs9fzw", + "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#k8s-version@0.1.2": "1aziyw6b3ldvp0r5hbqgg0q4qph4xk45y5rfnvfn7k8fi2niqrli", + "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#stackable-operator-derive@0.3.1": "1aziyw6b3ldvp0r5hbqgg0q4qph4xk45y5rfnvfn7k8fi2niqrli", + "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#stackable-operator@0.92.0": "1aziyw6b3ldvp0r5hbqgg0q4qph4xk45y5rfnvfn7k8fi2niqrli", + "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#stackable-shared@0.0.1": "1aziyw6b3ldvp0r5hbqgg0q4qph4xk45y5rfnvfn7k8fi2niqrli", + "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#stackable-telemetry@0.6.0": "1aziyw6b3ldvp0r5hbqgg0q4qph4xk45y5rfnvfn7k8fi2niqrli", + "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#stackable-versioned-macros@0.7.1": "1aziyw6b3ldvp0r5hbqgg0q4qph4xk45y5rfnvfn7k8fi2niqrli", + "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#stackable-versioned@0.7.1": "1aziyw6b3ldvp0r5hbqgg0q4qph4xk45y5rfnvfn7k8fi2niqrli", "git+https://github.com/stackabletech/product-config.git?tag=0.7.0#product-config@0.7.0": "0gjsm80g6r75pm3824dcyiz4ysq1ka4c1if6k1mjm9cnd5ym0gny" } \ No newline at end of file diff --git a/deploy/helm/nifi-operator/crds/crds.yaml b/deploy/helm/nifi-operator/crds/crds.yaml index 7511f55a..1c6d7bf9 100644 --- a/deploy/helm/nifi-operator/crds/crds.yaml +++ b/deploy/helm/nifi-operator/crds/crds.yaml @@ -33,10 +33,16 @@ spec: items: properties: authenticationClass: - description: Name of the [AuthenticationClass](https://docs.stackable.tech/home/nightly/concepts/authentication) used to authenticate users. + description: |- + Name of the [AuthenticationClass](https://docs.stackable.tech/home/nightly/concepts/authentication) used to authenticate users. + + To get the concrete [`AuthenticationClass`], we must resolve it. This resolution can be achieved by using [`ClientAuthenticationDetails::resolve_class`]. type: string oidc: - description: This field contains OIDC-specific configuration. It is only required in case OIDC is used. + description: |- + This field contains OIDC-specific configuration. It is only required in case OIDC is used. + + Use [`ClientAuthenticationDetails::oidc_or_error`] to get the value or report an error to the user. nullable: true properties: clientCredentialsSecret: @@ -44,7 +50,10 @@ spec: type: string extraScopes: default: [] - description: An optional list of extra scopes which get merged with the scopes defined in the [`AuthenticationClass`]. + description: |- + An optional list of extra scopes which get merged with the scopes defined in the [`AuthenticationClass`][1]. + + [1]: crate::crd::authentication::core::v1alpha1::AuthenticationClass items: type: string type: array @@ -71,6 +80,55 @@ spec: type: object x-kubernetes-preserve-unknown-fields: true type: object + customProcessorsGitSync: + default: [] + description: The `gitSync` settings allow configuring Python processors to mount via `git-sync`. Learn more in the [mounting DAGs documentation](https://docs.stackable.tech/home/nightly/nifi/usage-guide/mounting-processors#_via_git_sync). + items: + properties: + branch: + default: main + description: |- + The branch to clone; defaults to `main`. + + Since git-sync v4.x.x this field is mapped to the flag `--ref`. + type: string + credentialsSecret: + description: 'The name of the Secret used to access the repository if it is not public. This should include two fields: `user` and `password`. The `password` field can either be an actual password (not recommended) or a GitHub token, as described [here](https://github.com/kubernetes/git-sync/tree/v4.2.4?tab=readme-ov-file#manual).' + nullable: true + type: string + depth: + default: 1 + description: The depth of syncing, i.e. the number of commits to clone; defaults to 1. + format: uint32 + minimum: 0.0 + type: integer + gitFolder: + default: / + description: |- + The location of the DAG folder, relative to the synced repository root. + + It can optionally start with `/`, however, no trailing slash is recommended. An empty string (``) or slash (`/`) corresponds to the root folder in Git. + type: string + gitSyncConf: + additionalProperties: + type: string + default: {} + description: A map of optional configuration settings that are listed in the [git-sync documentation](https://github.com/kubernetes/git-sync/tree/v4.2.4?tab=readme-ov-file#manual). Read the [git sync example](https://docs.stackable.tech/home/nightly/airflow/usage-guide/mounting-dags#_example). + type: object + repo: + description: 'The git repository URL that will be cloned, for example: `https://github.com/stackabletech/airflow-operator`.' + type: string + wait: + default: 20s + description: |- + The synchronization interval, e.g. `20s` or `5m`; defaults to `20s`. + + Since git-sync v4.x.x this field is mapped to the flag `--period`. + type: string + required: + - repo + type: object + type: array extraVolumes: description: Extra volumes similar to `.spec.volumes` on a Pod to mount into every container, this can be useful to for example make client certificates, keytabs or similar things available to processors. These volumes will be mounted into all pods at `/stackable/userdata/{volumename}`. See also the [external files usage guide](https://docs.stackable.tech/home/nightly/nifi/usage_guide/extra-volumes). items: diff --git a/rust/operator-binary/src/config/mod.rs b/rust/operator-binary/src/config/mod.rs index b075c495..1e0f71ea 100644 --- a/rust/operator-binary/src/config/mod.rs +++ b/rust/operator-binary/src/config/mod.rs @@ -8,6 +8,7 @@ use product_config::{ProductConfigManager, types::PropertyNameKind}; use snafu::{ResultExt, Snafu}; use stackable_operator::{ commons::resources::Resources, + crd::git_sync, memory::MemoryQuantity, product_config_utils::{ ValidatedRoleConfigByPropertyKind, transform_all_roles_to_config, @@ -34,6 +35,7 @@ use crate::{ pub mod jvm; pub const NIFI_CONFIG_DIRECTORY: &str = "/stackable/nifi/conf"; +pub const NIFI_PYTHON_WORKING_DIRECTORY: &str = "/nifi-python-working-directory"; pub const NIFI_BOOTSTRAP_CONF: &str = "bootstrap.conf"; pub const NIFI_PROPERTIES: &str = "nifi.properties"; @@ -142,6 +144,7 @@ pub fn build_nifi_properties( auth_config: &NifiAuthenticationConfig, overrides: BTreeMap, product_version: &str, + git_sync_resources: &git_sync::v1alpha1::GitSyncResources, ) -> Result { let mut properties = BTreeMap::new(); // Core Properties @@ -572,6 +575,45 @@ pub fn build_nifi_properties( "${env:ZOOKEEPER_CHROOT}".to_string(), ); + //########################## + // Python-based processors # + //########################## + if git_sync_resources.is_git_sync_enabled() { + // The command used to launch Python. + // This property must be set to enable Python-based processors. + properties.insert("nifi.python.command".to_string(), "python3".to_string()); + + // The directory that contains the Python framework for communicating between the Python and + // Java processes. + properties.insert( + "nifi.python.framework.source.directory".to_string(), + "/stackable/nifi/python/framework/".to_string(), + ); + + // The working directory where NiFi should store artifacts; + // This property defaults to ./work/python but if you want to mount an + // emptyDir for the working directory then another directory has to be + // set to avoid ownership conflicts with ./work/nar. + properties.insert( + "nifi.python.working.directory".to_string(), + NIFI_PYTHON_WORKING_DIRECTORY.to_string(), + ); + + for (i, git_folder) in git_sync_resources + .git_content_folders_as_string() + .into_iter() + .enumerate() + { + // The directory that NiFi should look in to find custom Python-based + // Processors. + properties.insert( + format!("nifi.python.extensions.source.directory.{i}"), + git_folder, + ); + } + } + //########################## + // override with config overrides properties.extend(overrides); diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index cca71af6..54c5b8a9 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -26,11 +26,9 @@ use stackable_operator::{ }, client::Client, cluster_resources::{ClusterResourceApplyStrategy, ClusterResources}, - commons::{ - authentication::oidc::AuthenticationProvider, - product_image_selection::ResolvedProductImage, rbac::build_rbac_resources, - }, + commons::{product_image_selection::ResolvedProductImage, rbac::build_rbac_resources}, config::fragment, + crd::{authentication::oidc, git_sync}, k8s_openapi::{ DeepMerge, api::{ @@ -52,6 +50,7 @@ use stackable_operator::{ kvp::{Label, Labels, ObjectLabels}, logging::controller::ReconcilerError, memory::{BinaryMultiple, MemoryQuantity}, + product_config_utils::env_vars_from_rolegroup_config, product_logging::{ self, framework::{ @@ -77,8 +76,9 @@ use crate::{ OPERATOR_NAME, config::{ self, JVM_SECURITY_PROPERTIES_FILE, NIFI_BOOTSTRAP_CONF, NIFI_CONFIG_DIRECTORY, - NIFI_PROPERTIES, NIFI_STATE_MANAGEMENT_XML, NifiRepository, build_bootstrap_conf, - build_nifi_properties, build_state_management_xml, validated_product_config, + NIFI_PROPERTIES, NIFI_PYTHON_WORKING_DIRECTORY, NIFI_STATE_MANAGEMENT_XML, NifiRepository, + build_bootstrap_conf, build_nifi_properties, build_state_management_xml, + validated_product_config, }, crd::{ APP_NAME, BALANCE_PORT, BALANCE_PORT_NAME, Container, CurrentlySupportedListenerClasses, @@ -258,6 +258,9 @@ pub enum Error { #[snafu(display("failed to resolve and merge config for role and role group"))] FailedToResolveConfig { source: crate::crd::Error }, + #[snafu(display("invalid git-sync specification"))] + InvalidGitSyncSpec { source: git_sync::v1alpha1::Error }, + #[snafu(display("vector agent is enabled but vector aggregator ConfigMap is missing"))] VectorAggregatorConfigMapMissing, @@ -501,6 +504,14 @@ pub async fn reconcile_nifi( .merged_config(&NifiRole::Node, rolegroup_name) .context(FailedToResolveConfigSnafu)?; + let git_sync_resources = git_sync::v1alpha1::GitSyncResources::new( + &nifi.spec.cluster_config.custom_processors_git_sync, + &resolved_product_image, + &env_vars_from_rolegroup_config(rolegroup_config), + &[], + ) + .context(InvalidGitSyncSpecSnafu)?; + let rg_service = build_node_rolegroup_service(nifi, &resolved_product_image, &rolegroup)?; @@ -522,6 +533,7 @@ pub async fn reconcile_nifi( rolegroup_config, &merged_config, &proxy_hosts, + &git_sync_resources, ) .await?; @@ -545,6 +557,7 @@ pub async fn reconcile_nifi( rolling_upgrade_supported, replicas, &rbac_sa.name_any(), + &git_sync_resources, ) .await?; @@ -705,6 +718,7 @@ async fn build_node_rolegroup_config_map( rolegroup_config: &HashMap>, merged_config: &NifiConfig, proxy_hosts: &str, + git_sync_resources: &git_sync::v1alpha1::GitSyncResources, ) -> Result { tracing::debug!("building rolegroup configmaps"); @@ -769,6 +783,7 @@ async fn build_node_rolegroup_config_map( })? .clone(), resolved_product_image.product_version.as_ref(), + git_sync_resources, ) .with_context(|_| BuildProductConfigSnafu { rolegroup: rolegroup.clone(), @@ -874,6 +889,7 @@ async fn build_node_rolegroup_statefulset( rolling_update_supported: bool, replicas: Option, sa_name: &str, + git_sync_resources: &git_sync::v1alpha1::GitSyncResources, ) -> Result { tracing::debug!("Building statefulset"); let role_group = role.role_groups.get(&rolegroup_ref.role_group); @@ -923,9 +939,11 @@ async fn build_node_rolegroup_statefulset( )); if let NifiAuthenticationConfig::Oidc { oidc, .. } = nifi_auth_config { - env_vars.extend(AuthenticationProvider::client_credentials_env_var_mounts( - oidc.client_credentials_secret_ref.clone(), - )); + env_vars.extend( + oidc::v1alpha1::AuthenticationProvider::client_credentials_env_var_mounts( + oidc.client_credentials_secret_ref.clone(), + ), + ); } let node_address = format!( @@ -1163,9 +1181,30 @@ async fn build_node_rolegroup_statefulset( .context(AddVolumeMountSnafu)?; } + if git_sync_resources.is_git_sync_enabled() { + let volume_name = "nifi-python-working-directory".to_string(); + + pod_builder + .add_empty_dir_volume(&volume_name, None) + .context(AddVolumeSnafu)?; + container_nifi + .add_volume_mount(&volume_name, NIFI_PYTHON_WORKING_DIRECTORY) + .context(AddVolumeMountSnafu)?; + } + // We want to add nifi container first for easier defaulting into this container pod_builder.add_container(container_nifi.build()); + for container in git_sync_resources.git_sync_containers.iter().cloned() { + pod_builder.add_container(container); + } + pod_builder + .add_volumes(git_sync_resources.git_content_volumes.to_owned()) + .context(AddVolumeSnafu)?; + container_nifi + .add_volume_mounts(git_sync_resources.git_content_volume_mounts.to_owned()) + .context(AddVolumeMountSnafu)?; + if let Some(ContainerLogConfig { choice: Some(ContainerLogConfigChoice::Custom(CustomContainerLogConfig { diff --git a/rust/operator-binary/src/crd/authentication.rs b/rust/operator-binary/src/crd/authentication.rs index f3e0b32e..987e9a7f 100644 --- a/rust/operator-binary/src/crd/authentication.rs +++ b/rust/operator-binary/src/crd/authentication.rs @@ -3,10 +3,7 @@ use std::future::Future; use snafu::{ResultExt, Snafu}; use stackable_operator::{ client::Client, - commons::authentication::{ - AuthenticationClass, AuthenticationClassProvider, ClientAuthenticationDetails, ldap, oidc, - static_, - }, + crd::authentication::{core as auth_core, ldap, oidc, r#static}, kube::{ResourceExt, runtime::reflector::ObjectRef}, }; @@ -34,20 +31,18 @@ pub enum Error { ))] AuthenticationClassProviderNotSupported { authentication_class_provider: String, - authentication_class: ObjectRef, + authentication_class: ObjectRef, }, #[snafu(display( "Nifi doesn't support skipping the LDAP TLS verification of the AuthenticationClass {authentication_class}" ))] NoLdapTlsVerificationNotSupported { - authentication_class: ObjectRef, + authentication_class: ObjectRef, }, #[snafu(display("invalid OIDC configuration"))] - OidcConfigurationInvalid { - source: stackable_operator::commons::authentication::Error, - }, + OidcConfigurationInvalid { source: auth_core::v1alpha1::Error }, } type Result = std::result::Result; @@ -55,14 +50,14 @@ type Result = std::result::Result; #[derive(Clone)] pub enum AuthenticationClassResolved { Static { - provider: static_::AuthenticationProvider, + provider: r#static::v1alpha1::AuthenticationProvider, }, Ldap { - provider: ldap::AuthenticationProvider, + provider: ldap::v1alpha1::AuthenticationProvider, }, Oidc { - provider: oidc::AuthenticationProvider, - oidc: oidc::ClientAuthenticationOptions<()>, + provider: oidc::v1alpha1::AuthenticationProvider, + oidc: oidc::v1alpha1::ClientAuthenticationOptions<()>, nifi: v1alpha1::NifiCluster, }, } @@ -72,19 +67,25 @@ impl AuthenticationClassResolved { nifi: &v1alpha1::NifiCluster, client: &Client, ) -> Result> { - let resolve_auth_class = |auth_details: ClientAuthenticationDetails| async move { - auth_details.resolve_class(client).await - }; + let resolve_auth_class = + |auth_details: auth_core::v1alpha1::ClientAuthenticationDetails| async move { + auth_details.resolve_class(client).await + }; AuthenticationClassResolved::resolve(nifi, resolve_auth_class).await } /// Retrieve all provided `AuthenticationClass` references. pub async fn resolve( nifi: &v1alpha1::NifiCluster, - resolve_auth_class: impl Fn(ClientAuthenticationDetails) -> R, + resolve_auth_class: impl Fn(auth_core::v1alpha1::ClientAuthenticationDetails) -> R, ) -> Result> where - R: Future>, + R: Future< + Output = Result< + auth_core::v1alpha1::AuthenticationClass, + stackable_operator::client::Error, + >, + >, { let mut resolved_auth_classes = vec![]; let auth_details = &nifi.spec.cluster_config.authentication; @@ -103,16 +104,18 @@ impl AuthenticationClassResolved { let auth_class_name = auth_class.name_any(); match &auth_class.spec.provider { - AuthenticationClassProvider::Static(provider) => { + auth_core::v1alpha1::AuthenticationClassProvider::Static(provider) => { resolved_auth_classes.push(AuthenticationClassResolved::Static { provider: provider.to_owned(), }) } - AuthenticationClassProvider::Ldap(provider) => { + auth_core::v1alpha1::AuthenticationClassProvider::Ldap(provider) => { if provider.tls.uses_tls() && !provider.tls.uses_tls_verification() { NoLdapTlsVerificationNotSupportedSnafu { - authentication_class: ObjectRef::::new( - &auth_class_name, + authentication_class: ObjectRef::< + auth_core::v1alpha1::AuthenticationClass, + >::new( + &auth_class_name ), } .fail()? @@ -121,7 +124,7 @@ impl AuthenticationClassResolved { provider: provider.to_owned(), }) } - AuthenticationClassProvider::Oidc(provider) => { + auth_core::v1alpha1::AuthenticationClassProvider::Oidc(provider) => { resolved_auth_classes.push(Ok(AuthenticationClassResolved::Oidc { provider: provider.to_owned(), oidc: entry @@ -131,11 +134,16 @@ impl AuthenticationClassResolved { nifi: nifi.clone(), })?) } - _ => AuthenticationClassProviderNotSupportedSnafu { - authentication_class_provider: auth_class.spec.provider.to_string(), - authentication_class: ObjectRef::::new(&auth_class_name), + _ => { + AuthenticationClassProviderNotSupportedSnafu { + authentication_class_provider: auth_class.spec.provider.to_string(), + authentication_class: + ObjectRef::::new( + &auth_class_name, + ), + } + .fail()? } - .fail()?, }; } diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index c3227e77..91859fb5 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -10,7 +10,6 @@ use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::{ commons::{ affinity::StackableAffinity, - authentication::ClientAuthenticationDetails, cluster_operation::ClusterOperation, product_image_selection::ProductImage, resources::{ @@ -22,6 +21,7 @@ use stackable_operator::{ fragment::{self, Fragment, ValidationError}, merge::Merge, }, + crd::{authentication::core as auth_core, git_sync}, k8s_openapi::{ api::core::v1::{PodTemplateSpec, Volume}, apimachinery::pkg::api::resource::Quantity, @@ -117,7 +117,7 @@ pub mod versioned { /// Authentication options for NiFi (required). /// Read more about authentication in the [security documentation](DOCS_BASE_URL_PLACEHOLDER/nifi/usage_guide/security). // We don't add `#[serde(default)]` here, as we require authentication - pub authentication: Vec, + pub authentication: Vec, /// Configuration of allowed proxies e.g. load balancers or Kubernetes Ingress. Using a proxy that is not allowed by NiFi results /// in a failed host header check. @@ -144,6 +144,12 @@ pub mod versioned { /// to deploy a ZooKeeper cluster, this will simply be the name of your ZookeeperCluster resource. pub zookeeper_config_map_name: String, + /// The `gitSync` settings allow configuring Python processors to mount via `git-sync`. + /// Learn more in the + /// [mounting DAGs documentation](DOCS_BASE_URL_PLACEHOLDER/nifi/usage-guide/mounting-processors#_via_git_sync). + #[serde(default)] + pub custom_processors_git_sync: Vec, + /// Extra volumes similar to `.spec.volumes` on a Pod to mount into every container, this can be useful to for /// example make client certificates, keytabs or similar things available to processors. These volumes will be /// mounted into all pods at `/stackable/userdata/{volumename}`. diff --git a/rust/operator-binary/src/main.rs b/rust/operator-binary/src/main.rs index e6810d8b..c10144bf 100644 --- a/rust/operator-binary/src/main.rs +++ b/rust/operator-binary/src/main.rs @@ -5,7 +5,7 @@ use futures::stream::StreamExt; use stackable_operator::{ YamlSchema, cli::{Command, ProductOperatorRun}, - commons::authentication::AuthenticationClass, + crd::authentication::core as auth_core, k8s_openapi::api::{ apps::v1::StatefulSet, core::v1::{ConfigMap, Service}, @@ -119,7 +119,8 @@ async fn main() -> anyhow::Result<()> { ) .shutdown_on_signal() .watches( - client.get_api::>(&()), + client + .get_api::>(&()), watcher::Config::default(), move |_| { authentication_class_store diff --git a/rust/operator-binary/src/security/authentication.rs b/rust/operator-binary/src/security/authentication.rs index 4c2f2e15..74c67b60 100644 --- a/rust/operator-binary/src/security/authentication.rs +++ b/rust/operator-binary/src/security/authentication.rs @@ -5,11 +5,7 @@ use stackable_operator::{ self, pod::{PodBuilder, container::ContainerBuilder}, }, - commons::authentication::{ - ldap, - oidc::{self, ClientAuthenticationOptions}, - static_, - }, + crd::authentication::{ldap, oidc, r#static}, k8s_openapi::api::core::v1::{KeyToPath, SecretVolumeSource, Volume}, }; @@ -41,9 +37,7 @@ pub enum Error { }, #[snafu(display("Failed to add LDAP volumes and volumeMounts to the Pod and containers"))] - AddLdapVolumes { - source: stackable_operator::commons::authentication::ldap::Error, - }, + AddLdapVolumes { source: ldap::v1alpha1::Error }, #[snafu(display("Failed to add OIDC volumes and volumeMounts to the Pod and containers"))] AddOidcVolumes { @@ -67,14 +61,14 @@ pub enum Error { #[allow(clippy::large_enum_variant)] pub enum NifiAuthenticationConfig { SingleUser { - provider: static_::AuthenticationProvider, + provider: r#static::v1alpha1::AuthenticationProvider, }, Ldap { - provider: ldap::AuthenticationProvider, + provider: ldap::v1alpha1::AuthenticationProvider, }, Oidc { - provider: oidc::AuthenticationProvider, - oidc: ClientAuthenticationOptions, + provider: oidc::v1alpha1::AuthenticationProvider, + oidc: oidc::v1alpha1::ClientAuthenticationOptions, nifi: v1alpha1::NifiCluster, }, } @@ -279,7 +273,9 @@ impl NifiAuthenticationConfig { } } -fn get_ldap_login_identity_provider(ldap: &ldap::AuthenticationProvider) -> Result { +fn get_ldap_login_identity_provider( + ldap: &ldap::v1alpha1::AuthenticationProvider, +) -> Result { let mut search_filter = ldap.search_filter.clone(); // If no search_filter is specified we will set a default filter that just searches for the user logging in using the specified uid field name @@ -344,7 +340,7 @@ fn get_ldap_login_identity_provider(ldap: &ldap::AuthenticationProvider) -> Resu }) } -fn get_ldap_authorizer(ldap: &ldap::AuthenticationProvider) -> Result { +fn get_ldap_authorizer(ldap: &ldap::v1alpha1::AuthenticationProvider) -> Result { let (username_file, _) = ldap .bind_credentials_mount_paths() .context(LdapAuthenticationClassMissingBindCredentialsSnafu)?; diff --git a/rust/operator-binary/src/security/oidc.rs b/rust/operator-binary/src/security/oidc.rs index e8232be6..b7897d43 100644 --- a/rust/operator-binary/src/security/oidc.rs +++ b/rust/operator-binary/src/security/oidc.rs @@ -5,10 +5,8 @@ use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::{ builder::meta::ObjectMetaBuilder, client::Client, - commons::{ - authentication::oidc::{self, AuthenticationProvider, ClientAuthenticationOptions}, - tls_verification::{CaCert, TlsServerVerification, TlsVerification}, - }, + commons::tls_verification::{CaCert, TlsServerVerification, TlsVerification}, + crd::authentication::oidc, k8s_openapi::api::core::v1::Secret, kube::{ResourceExt, runtime::reflector::ObjectRef}, }; @@ -33,9 +31,7 @@ pub enum Error { MissingAdminPasswordKey { secret: ObjectRef }, #[snafu(display("invalid well-known OIDC configuration URL"))] - InvalidWellKnownConfigUrl { - source: stackable_operator::commons::authentication::oidc::Error, - }, + InvalidWellKnownConfigUrl { source: oidc::v1alpha1::Error }, #[snafu(display("Nifi doesn't support skipping the OIDC TLS verification"))] SkippingTlsVerificationNotSupported {}, @@ -105,8 +101,8 @@ pub fn build_oidc_admin_password_secret_name(nifi: &v1alpha1::NifiCluster) -> St /// Adds all the required configuration properties to enable OIDC authentication. pub fn add_oidc_config_to_properties( - provider: &oidc::AuthenticationProvider, - client_auth_options: &ClientAuthenticationOptions, + provider: &oidc::v1alpha1::AuthenticationProvider, + client_auth_options: &oidc::v1alpha1::ClientAuthenticationOptions, properties: &mut BTreeMap, ) -> Result<(), Error> { let well_known_url = provider @@ -118,7 +114,7 @@ pub fn add_oidc_config_to_properties( well_known_url.to_string(), ); let (oidc_client_id_env, oidc_client_secret_env) = - AuthenticationProvider::client_credentials_env_names( + oidc::v1alpha1::AuthenticationProvider::client_credentials_env_names( &client_auth_options.client_credentials_secret_ref, ); properties.insert( @@ -171,7 +167,7 @@ mod tests { #[case("/realms/sdp/////")] fn test_add_oidc_config(#[case] root_path: String) { let mut properties = BTreeMap::new(); - let provider = oidc::AuthenticationProvider::new( + let provider = oidc::v1alpha1::AuthenticationProvider::new( "keycloak.mycorp.org".to_owned().try_into().unwrap(), Some(443), root_path, @@ -186,7 +182,7 @@ mod tests { vec!["openid".to_owned()], None, ); - let oidc = ClientAuthenticationOptions { + let oidc = oidc::v1alpha1::ClientAuthenticationOptions { client_credentials_secret_ref: "nifi-keycloak-client".to_owned(), extra_scopes: vec![], product_specific_fields: (), diff --git a/tests/templates/kuttl/custom-processors-git-sync/00-patch-ns.yaml.j2 b/tests/templates/kuttl/custom-processors-git-sync/00-patch-ns.yaml.j2 new file mode 100644 index 00000000..67185acf --- /dev/null +++ b/tests/templates/kuttl/custom-processors-git-sync/00-patch-ns.yaml.j2 @@ -0,0 +1,9 @@ +{% if test_scenario['values']['openshift'] == 'true' %} +# see https://github.com/stackabletech/issues/issues/566 +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - script: kubectl patch namespace $NAMESPACE -p '{"metadata":{"labels":{"pod-security.kubernetes.io/enforce":"privileged"}}}' + timeout: 120 +{% endif %} diff --git a/tests/templates/kuttl/custom-processors-git-sync/10-assert.yaml.j2 b/tests/templates/kuttl/custom-processors-git-sync/10-assert.yaml.j2 new file mode 100644 index 00000000..50b1d4c3 --- /dev/null +++ b/tests/templates/kuttl/custom-processors-git-sync/10-assert.yaml.j2 @@ -0,0 +1,10 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +{% if lookup('env', 'VECTOR_AGGREGATOR') %} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: vector-aggregator-discovery +{% endif %} diff --git a/tests/templates/kuttl/custom-processors-git-sync/10-install-vector-aggregator-discovery-configmap.yaml.j2 b/tests/templates/kuttl/custom-processors-git-sync/10-install-vector-aggregator-discovery-configmap.yaml.j2 new file mode 100644 index 00000000..2d6a0df5 --- /dev/null +++ b/tests/templates/kuttl/custom-processors-git-sync/10-install-vector-aggregator-discovery-configmap.yaml.j2 @@ -0,0 +1,9 @@ +{% if lookup('env', 'VECTOR_AGGREGATOR') %} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: vector-aggregator-discovery +data: + ADDRESS: {{ lookup('env', 'VECTOR_AGGREGATOR') }} +{% endif %} diff --git a/tests/templates/kuttl/custom-processors-git-sync/20-assert.yaml b/tests/templates/kuttl/custom-processors-git-sync/20-assert.yaml new file mode 100644 index 00000000..e0766c49 --- /dev/null +++ b/tests/templates/kuttl/custom-processors-git-sync/20-assert.yaml @@ -0,0 +1,12 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +timeout: 600 +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: test-zk-server-default +status: + readyReplicas: 1 + replicas: 1 diff --git a/tests/templates/kuttl/custom-processors-git-sync/20-install-zk.yaml.j2 b/tests/templates/kuttl/custom-processors-git-sync/20-install-zk.yaml.j2 new file mode 100644 index 00000000..ab2a9536 --- /dev/null +++ b/tests/templates/kuttl/custom-processors-git-sync/20-install-zk.yaml.j2 @@ -0,0 +1,28 @@ +--- +apiVersion: zookeeper.stackable.tech/v1alpha1 +kind: ZookeeperCluster +metadata: + name: test-zk +spec: + image: + productVersion: "{{ test_scenario['values']['zookeeper-latest'] }}" + pullPolicy: IfNotPresent + clusterConfig: +{% if lookup('env', 'VECTOR_AGGREGATOR') %} + vectorAggregatorConfigMapName: vector-aggregator-discovery +{% endif %} + servers: + config: + logging: + enableVectorAgent: {{ lookup('env', 'VECTOR_AGGREGATOR') | length > 0 }} + roleGroups: + default: + replicas: 1 +--- +apiVersion: zookeeper.stackable.tech/v1alpha1 +kind: ZookeeperZnode +metadata: + name: test-nifi-znode +spec: + clusterRef: + name: test-zk diff --git a/tests/templates/kuttl/custom-processors-git-sync/30-assert.yaml b/tests/templates/kuttl/custom-processors-git-sync/30-assert.yaml new file mode 100644 index 00000000..5735208f --- /dev/null +++ b/tests/templates/kuttl/custom-processors-git-sync/30-assert.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +timeout: 1200 +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: test-nifi-node-default +spec: + template: + spec: + terminationGracePeriodSeconds: 300 +status: + readyReplicas: 2 + replicas: 2 diff --git a/tests/templates/kuttl/custom-processors-git-sync/30-install-nifi.yaml.j2 b/tests/templates/kuttl/custom-processors-git-sync/30-install-nifi.yaml.j2 new file mode 100644 index 00000000..67250dee --- /dev/null +++ b/tests/templates/kuttl/custom-processors-git-sync/30-install-nifi.yaml.j2 @@ -0,0 +1,458 @@ +--- +apiVersion: authentication.stackable.tech/v1alpha1 +kind: AuthenticationClass +metadata: + name: nifi-users +spec: + provider: + static: + userCredentialsSecret: + name: nifi-user-credentials +--- +apiVersion: v1 +kind: Secret +metadata: + name: nifi-user-credentials +stringData: + admin: admin +--- +apiVersion: nifi.stackable.tech/v1alpha1 +kind: NifiCluster +metadata: + name: test-nifi +spec: + image: +{% if test_scenario['values']['nifi-latest'].find(",") > 0 %} + custom: "{{ test_scenario['values']['nifi-latest'].split(',')[1] }}" + productVersion: "{{ test_scenario['values']['nifi-latest'].split(',')[0] }}" +{% else %} + productVersion: "{{ test_scenario['values']['nifi-latest'] }}" +{% endif %} + pullPolicy: IfNotPresent + clusterConfig: + listenerClass: external-unstable + zookeeperConfigMapName: test-nifi-znode + authentication: + - authenticationClass: nifi-users + sensitiveProperties: + keySecret: nifi-sensitive-property-key + autoGenerate: true + customProcessorsGitSync: + - repo: https://github.com/stackabletech/nifi-operator + branch: feat/custom-processors # TODO Change to commit + gitFolder: tests/templates/kuttl/custom-processors-git-sync/processors-0 + - repo: https://github.com/stackabletech/nifi-operator + branch: feat/custom-processors # TODO Change to commit + gitFolder: tests/templates/kuttl/custom-processors-git-sync/processors-1 +{% if lookup('env', 'VECTOR_AGGREGATOR') %} + vectorAggregatorConfigMapName: vector-aggregator-discovery +{% endif %} + nodes: + config: + logging: + enableVectorAgent: {{ lookup('env', 'VECTOR_AGGREGATOR') | length > 0 }} + podOverrides: + spec: + initContainers: + - name: init-flow + image: oci.stackable.tech/sdp/testing-tools:0.2.0-stackable0.0.0-dev + command: + - /bin/bash + - -c + args: + - gzip --stdout /stackable/nifi/flow/flow.json > /stackable/data/database/flow.json.gz + volumeMounts: + - name: nifi-flow + mountPath: /stackable/nifi/flow + - name: database-repository + mountPath: /stackable/data/database + containers: + - name: nifi + ports: + - name: greeting + containerPort: 8090 + protocol: TCP + volumes: + - name: nifi-flow + configMap: + name: nifi-flow + roleGroups: + default: + replicas: 2 +--- +apiVersion: v1 +kind: Service +metadata: + name: nifi-greeting +spec: + selector: + app.kubernetes.io/component: node + app.kubernetes.io/instance: test-nifi + app.kubernetes.io/name: nifi + app.kubernetes.io/role-group: default + ports: + - name: greeting + port: 80 + protocol: TCP + targetPort: greeting +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: nifi-flow +data: + flow.json: | + { + "encodingVersion": { + "majorVersion": 2, + "minorVersion": 0 + }, + "maxTimerDrivenThreadCount": 10, + "registries": [], + "parameterContexts": [], + "parameterProviders": [], + "controllerServices": [], + "reportingTasks": [], + "flowAnalysisRules": [], + "rootGroup": { + "identifier": "a0c74b17-4386-3e84-8de4-6cefe4328565", + "instanceIdentifier": "243351c2-0196-1000-9730-38306d34ae6d", + "name": "NiFi Flow", + "comments": "", + "position": { + "x": 0.0, + "y": 0.0 + }, + "processGroups": [], + "remoteProcessGroups": [], + "processors": [ + { + "identifier": "a36b7e9b-c90a-3fad-9a53-53450ff2efd7", + "instanceIdentifier": "25736232-0196-1000-0000-00003edfe17e", + "name": "HandleHttpResponse", + "comments": "", + "position": { + "x": -248.0, + "y": -24.0 + }, + "type": "org.apache.nifi.processors.standard.HandleHttpResponse", + "bundle": { + "group": "org.apache.nifi", + "artifact": "nifi-standard-nar", + "version": "2.2.0" + }, + "properties": { + "HTTP Context Map": "2572dfc2-0196-1000-ffff-ffffb3493dd1", + "HTTP Status Code": "200" + }, + "propertyDescriptors": {}, + "style": {}, + "schedulingPeriod": "0 sec", + "schedulingStrategy": "TIMER_DRIVEN", + "executionNode": "ALL", + "penaltyDuration": "30 sec", + "yieldDuration": "1 sec", + "bulletinLevel": "WARN", + "runDurationMillis": 0, + "concurrentlySchedulableTaskCount": 1, + "autoTerminatedRelationships": [ + "success", + "failure" + ], + "scheduledState": "RUNNING", + "retryCount": 10, + "retriedRelationships": [], + "backoffMechanism": "PENALIZE_FLOWFILE", + "maxBackoffPeriod": "10 mins", + "componentType": "PROCESSOR", + "groupIdentifier": "a0c74b17-4386-3e84-8de4-6cefe4328565" + }, + { + "identifier": "c331dc17-5ded-398b-bce0-2a96fa6d8005", + "instanceIdentifier": "257286eb-0196-1000-ffff-ffffcfa02e60", + "name": "HandleHttpRequest", + "comments": "", + "position": { + "x": -248.0, + "y": -664.0 + }, + "type": "org.apache.nifi.processors.standard.HandleHttpRequest", + "bundle": { + "group": "org.apache.nifi", + "artifact": "nifi-standard-nar", + "version": "2.2.0" + }, + "properties": { + "multipart-request-max-size": "1 MB", + "Allow POST": "false", + "Default URL Character Set": "UTF-8", + "Allow DELETE": "false", + "Request Header Maximum Size": "8 KB", + "Maximum Threads": "200", + "HTTP Protocols": "HTTP_1_1", + "container-queue-size": "50", + "HTTP Context Map": "2572dfc2-0196-1000-ffff-ffffb3493dd1", + "multipart-read-buffer-size": "512 KB", + "Allow OPTIONS": "false", + "Allowed Paths": "/greeting", + "Allow GET": "true", + "Allow HEAD": "false", + "Listening Port": "8090", + "Client Authentication": "No Authentication", + "Allow PUT": "false" + }, + "propertyDescriptors": {}, + "style": {}, + "schedulingPeriod": "0 sec", + "schedulingStrategy": "TIMER_DRIVEN", + "executionNode": "ALL", + "penaltyDuration": "30 sec", + "yieldDuration": "1 sec", + "bulletinLevel": "WARN", + "runDurationMillis": 0, + "concurrentlySchedulableTaskCount": 1, + "autoTerminatedRelationships": [], + "scheduledState": "RUNNING", + "retryCount": 10, + "retriedRelationships": [], + "backoffMechanism": "PENALIZE_FLOWFILE", + "maxBackoffPeriod": "10 mins", + "componentType": "PROCESSOR", + "groupIdentifier": "a0c74b17-4386-3e84-8de4-6cefe4328565" + }, + { + "identifier": "aa2534ff-9199-3e7e-9e38-9a974864599f", + "instanceIdentifier": "25731de4-0196-1000-ffff-ffffdac9784b", + "name": "Shout", + "comments": "", + "position": { + "x": -248.0, + "y": -240.0 + }, + "type": "Shout", + "bundle": { + "group": "org.apache.nifi", + "artifact": "python-extensions", + "version": "1.0.0" + }, + "properties": {}, + "propertyDescriptors": {}, + "style": {}, + "schedulingPeriod": "0 sec", + "schedulingStrategy": "TIMER_DRIVEN", + "executionNode": "ALL", + "penaltyDuration": "30 sec", + "yieldDuration": "1 sec", + "bulletinLevel": "WARN", + "runDurationMillis": 25, + "concurrentlySchedulableTaskCount": 1, + "autoTerminatedRelationships": [ + "original", + "failure" + ], + "scheduledState": "RUNNING", + "retryCount": 10, + "retriedRelationships": [], + "backoffMechanism": "PENALIZE_FLOWFILE", + "maxBackoffPeriod": "10 mins", + "componentType": "PROCESSOR", + "groupIdentifier": "a0c74b17-4386-3e84-8de4-6cefe4328565" + }, + { + "identifier": "d897a9e1-fb76-3cfe-93af-9cb7fa07a307", + "instanceIdentifier": "25723295-0196-1000-0000-0000467c4e68", + "name": "Greet", + "comments": "", + "position": { + "x": -248.0, + "y": -448.0 + }, + "type": "Greet", + "bundle": { + "group": "org.apache.nifi", + "artifact": "python-extensions", + "version": "1.0.0" + }, + "properties": {}, + "propertyDescriptors": {}, + "style": {}, + "schedulingPeriod": "0 sec", + "schedulingStrategy": "TIMER_DRIVEN", + "executionNode": "ALL", + "penaltyDuration": "30 sec", + "yieldDuration": "1 sec", + "bulletinLevel": "WARN", + "runDurationMillis": 25, + "concurrentlySchedulableTaskCount": 1, + "autoTerminatedRelationships": [ + "original", + "failure" + ], + "scheduledState": "RUNNING", + "retryCount": 10, + "retriedRelationships": [], + "backoffMechanism": "PENALIZE_FLOWFILE", + "maxBackoffPeriod": "10 mins", + "componentType": "PROCESSOR", + "groupIdentifier": "a0c74b17-4386-3e84-8de4-6cefe4328565" + } + ], + "inputPorts": [], + "outputPorts": [], + "connections": [ + { + "identifier": "0cc1ce67-1998-3338-8777-7b2b4dbc37f8", + "instanceIdentifier": "25729ba3-0196-1000-0000-0000198b702d", + "name": "", + "source": { + "id": "c331dc17-5ded-398b-bce0-2a96fa6d8005", + "type": "PROCESSOR", + "groupId": "a0c74b17-4386-3e84-8de4-6cefe4328565", + "name": "HandleHttpRequest", + "comments": "", + "instanceIdentifier": "257286eb-0196-1000-ffff-ffffcfa02e60" + }, + "destination": { + "id": "d897a9e1-fb76-3cfe-93af-9cb7fa07a307", + "type": "PROCESSOR", + "groupId": "a0c74b17-4386-3e84-8de4-6cefe4328565", + "name": "Greet", + "comments": "", + "instanceIdentifier": "25723295-0196-1000-0000-0000467c4e68" + }, + "labelIndex": 0, + "zIndex": 0, + "selectedRelationships": [ + "success" + ], + "backPressureObjectThreshold": 10000, + "backPressureDataSizeThreshold": "1 GB", + "flowFileExpiration": "0 sec", + "prioritizers": [], + "bends": [], + "loadBalanceStrategy": "DO_NOT_LOAD_BALANCE", + "partitioningAttribute": "", + "loadBalanceCompression": "DO_NOT_COMPRESS", + "componentType": "CONNECTION", + "groupIdentifier": "a0c74b17-4386-3e84-8de4-6cefe4328565" + }, + { + "identifier": "daccc7eb-0ed3-3bc8-89a9-942a6f21608e", + "instanceIdentifier": "2573957c-0196-1000-ffff-ffffaad0c8c0", + "name": "", + "source": { + "id": "aa2534ff-9199-3e7e-9e38-9a974864599f", + "type": "PROCESSOR", + "groupId": "a0c74b17-4386-3e84-8de4-6cefe4328565", + "name": "Shout", + "comments": "", + "instanceIdentifier": "25731de4-0196-1000-ffff-ffffdac9784b" + }, + "destination": { + "id": "a36b7e9b-c90a-3fad-9a53-53450ff2efd7", + "type": "PROCESSOR", + "groupId": "a0c74b17-4386-3e84-8de4-6cefe4328565", + "name": "HandleHttpResponse", + "comments": "", + "instanceIdentifier": "25736232-0196-1000-0000-00003edfe17e" + }, + "labelIndex": 0, + "zIndex": 0, + "selectedRelationships": [ + "success" + ], + "backPressureObjectThreshold": 10000, + "backPressureDataSizeThreshold": "1 GB", + "flowFileExpiration": "0 sec", + "prioritizers": [], + "bends": [], + "loadBalanceStrategy": "DO_NOT_LOAD_BALANCE", + "partitioningAttribute": "", + "loadBalanceCompression": "DO_NOT_COMPRESS", + "componentType": "CONNECTION", + "groupIdentifier": "a0c74b17-4386-3e84-8de4-6cefe4328565" + }, + { + "identifier": "3f4602ba-25b1-37d3-b377-d734750e49ca", + "instanceIdentifier": "25733c86-0196-1000-0000-0000142bd2e3", + "name": "", + "source": { + "id": "d897a9e1-fb76-3cfe-93af-9cb7fa07a307", + "type": "PROCESSOR", + "groupId": "a0c74b17-4386-3e84-8de4-6cefe4328565", + "name": "Greet", + "comments": "", + "instanceIdentifier": "25723295-0196-1000-0000-0000467c4e68" + }, + "destination": { + "id": "aa2534ff-9199-3e7e-9e38-9a974864599f", + "type": "PROCESSOR", + "groupId": "a0c74b17-4386-3e84-8de4-6cefe4328565", + "name": "Shout", + "comments": "", + "instanceIdentifier": "25731de4-0196-1000-ffff-ffffdac9784b" + }, + "labelIndex": 0, + "zIndex": 0, + "selectedRelationships": [ + "success" + ], + "backPressureObjectThreshold": 10000, + "backPressureDataSizeThreshold": "1 GB", + "flowFileExpiration": "0 sec", + "prioritizers": [], + "bends": [], + "loadBalanceStrategy": "DO_NOT_LOAD_BALANCE", + "partitioningAttribute": "", + "loadBalanceCompression": "DO_NOT_COMPRESS", + "componentType": "CONNECTION", + "groupIdentifier": "a0c74b17-4386-3e84-8de4-6cefe4328565" + } + ], + "labels": [], + "funnels": [], + "controllerServices": [ + { + "identifier": "1989ed4e-5edf-3df1-a1a1-6ed4a5c49d7f", + "instanceIdentifier": "2572dfc2-0196-1000-ffff-ffffb3493dd1", + "name": "StandardHttpContextMap", + "comments": "", + "type": "org.apache.nifi.http.StandardHttpContextMap", + "bundle": { + "group": "org.apache.nifi", + "artifact": "nifi-http-context-map-nar", + "version": "2.2.0" + }, + "properties": { + "Request Expiration": "1 min", + "Maximum Outstanding Requests": "5000" + }, + "propertyDescriptors": {}, + "controllerServiceApis": [ + { + "type": "org.apache.nifi.http.HttpContextMap", + "bundle": { + "group": "org.apache.nifi", + "artifact": "nifi-standard-services-api-nar", + "version": "2.2.0" + } + } + ], + "scheduledState": "ENABLED", + "bulletinLevel": "WARN", + "componentType": "CONTROLLER_SERVICE", + "groupIdentifier": "a0c74b17-4386-3e84-8de4-6cefe4328565" + } + ], + "defaultFlowFileExpiration": "0 sec", + "defaultBackPressureObjectThreshold": 10000, + "defaultBackPressureDataSizeThreshold": "1 GB", + "scheduledState": "ENABLED", + "executionEngine": "INHERITED", + "maxConcurrentTasks": 1, + "statelessFlowTimeout": "1 min", + "flowFileConcurrency": "UNBOUNDED", + "flowFileOutboundPolicy": "STREAM_WHEN_AVAILABLE", + "componentType": "PROCESS_GROUP" + } + } diff --git a/tests/templates/kuttl/custom-processors-git-sync/40-assert.yaml b/tests/templates/kuttl/custom-processors-git-sync/40-assert.yaml new file mode 100644 index 00000000..2c407438 --- /dev/null +++ b/tests/templates/kuttl/custom-processors-git-sync/40-assert.yaml @@ -0,0 +1,11 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +timeout: 300 +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: test-nifi-greeting +status: + succeeded: 1 diff --git a/tests/templates/kuttl/custom-processors-git-sync/40-test-nifi-greeting.yaml b/tests/templates/kuttl/custom-processors-git-sync/40-test-nifi-greeting.yaml new file mode 100644 index 00000000..300390d1 --- /dev/null +++ b/tests/templates/kuttl/custom-processors-git-sync/40-test-nifi-greeting.yaml @@ -0,0 +1,18 @@ +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: test-nifi-greeting +spec: + template: + spec: + containers: + - name: test + image: oci.stackable.tech/sdp/testing-tools:0.2.0-stackable0.0.0-dev + command: + - /bin/bash + - -c + args: + - test "$(curl nifi-greeting/greeting)" = HELLO! + restartPolicy: OnFailure + terminationGracePeriodSeconds: 1 diff --git a/tests/templates/kuttl/custom-processors-git-sync/processors-0/greet_processor.py b/tests/templates/kuttl/custom-processors-git-sync/processors-0/greet_processor.py new file mode 100644 index 00000000..e646e659 --- /dev/null +++ b/tests/templates/kuttl/custom-processors-git-sync/processors-0/greet_processor.py @@ -0,0 +1,16 @@ +from nifiapi.flowfiletransform import FlowFileTransform, FlowFileTransformResult + + +class Greet(FlowFileTransform): + class Java: + implements = ["org.apache.nifi.python.processor.FlowFileTransform"] + + class ProcessorDetails: + version = "1.0.0" + description = "A Python processor that greets politely." + + def __init__(self, **kwargs): + pass + + def transform(self, context, flowfile): + return FlowFileTransformResult(relationship="success", contents="Hello!") diff --git a/tests/templates/kuttl/custom-processors-git-sync/processors-1/shout_processor.py b/tests/templates/kuttl/custom-processors-git-sync/processors-1/shout_processor.py new file mode 100644 index 00000000..af132768 --- /dev/null +++ b/tests/templates/kuttl/custom-processors-git-sync/processors-1/shout_processor.py @@ -0,0 +1,18 @@ +from nifiapi.flowfiletransform import FlowFileTransform, FlowFileTransformResult + + +class Shout(FlowFileTransform): + class Java: + implements = ["org.apache.nifi.python.processor.FlowFileTransform"] + + class ProcessorDetails: + version = "1.0.0" + description = "A Python processor that shouts the received." + + def __init__(self, **kwargs): + pass + + def transform(self, context, flowfile): + input = flowfile.getContentsAsBytes().decode("utf-8") + output = input.upper() + return FlowFileTransformResult(relationship="success", contents=output) diff --git a/tests/test-definition.yaml b/tests/test-definition.yaml index d83e0751..93cf578c 100644 --- a/tests/test-definition.yaml +++ b/tests/test-definition.yaml @@ -19,7 +19,7 @@ dimensions: # - 2.2.0,oci.stackable.tech/sandbox/nifi:2.2.0-stackable0.0.0-dev - name: nifi-latest values: - - 2.2.0 + - 2.2.0,oci.stackable.tech/sandbox/git-sync/nifi:2.2.0-stackable0.0.0-dev # Alternatively, if you want to use a custom image, append a comma and the full image name to the product version # as in the example below. # - 2.2.0,oci.stackable.tech/sandbox/nifi:2.2.0-stackable0.0.0-dev @@ -90,6 +90,11 @@ tests: - zookeeper-latest - oidc-use-tls - openshift + - name: custom-processors-git-sync + dimensions: + - nifi-latest + - zookeeper-latest + - openshift suites: - name: nightly patch: From d50432e0fcaefe00db1fdc7a580fb554b7070821 Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Mon, 19 May 2025 12:40:18 +0200 Subject: [PATCH 02/24] Upgrade stackable-operator --- Cargo.lock | 61 ++++++++++------ Cargo.nix | 174 ++++++++++++++++++++++++++++++++++------------ crate-hashes.json | 14 ++-- 3 files changed, 177 insertions(+), 72 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b6ded60c..b5b0d095 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -555,6 +555,26 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + [[package]] name = "digest" version = "0.10.7" @@ -1420,22 +1440,21 @@ dependencies = [ [[package]] name = "k8s-openapi" -version = "0.24.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c75b990324f09bef15e791606b7b7a296d02fc88a344f6eba9390970a870ad5" +checksum = "aa60a41b57ae1a0a071af77dbcf89fc9819cfe66edaf2beeb204c34459dcf0b2" dependencies = [ "base64 0.22.1", "chrono", "schemars", "serde", - "serde-value", "serde_json", ] [[package]] name = "k8s-version" version = "0.1.2" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#5d7a0076b1e51b6105375e46e44e79198d6e233a" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#b41837a04b1dfa61ab6c79a41c418fc6e864d4f9" dependencies = [ "darling", "regex", @@ -1444,9 +1463,9 @@ dependencies = [ [[package]] name = "kube" -version = "0.99.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a4eb20010536b48abe97fec37d23d43069bcbe9686adcf9932202327bc5ca6e" +checksum = "1b49c39074089233c2bb7b1791d1b6c06c84dbab26757491fad9d233db0d432f" dependencies = [ "k8s-openapi", "kube-client", @@ -1457,9 +1476,9 @@ dependencies = [ [[package]] name = "kube-client" -version = "0.99.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fc2ed952042df20d15ac2fe9614d0ec14b6118eab89633985d4b36e688dccf1" +checksum = "e199797b1b08865041c9c698f0d11a91de0a8143e808b71e250cd4a1d7ce2b9f" dependencies = [ "base64 0.22.1", "bytes", @@ -1494,11 +1513,12 @@ dependencies = [ [[package]] name = "kube-core" -version = "0.99.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff0d0793db58e70ca6d689489183816cb3aa481673e7433dc618cf7e8007c675" +checksum = "1bdefbba89dea2d99ea822a1d7cd6945535efbfb10b790056ee9284bf9e698e7" dependencies = [ "chrono", + "derive_more", "form_urlencoded", "http", "json-patch", @@ -1512,9 +1532,9 @@ dependencies = [ [[package]] name = "kube-derive" -version = "0.99.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c562f58dc9f7ca5feac8a6ee5850ca221edd6f04ce0dd2ee873202a88cd494c9" +checksum = "8e609a3633689a50869352a3c16e01d863b6137863c80eeb038383d5ab9f83bf" dependencies = [ "darling", "proc-macro2", @@ -1526,14 +1546,13 @@ dependencies = [ [[package]] name = "kube-runtime" -version = "0.99.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88f34cfab9b4bd8633062e0e85edb81df23cb09f159f2e31c60b069ae826ffdc" +checksum = "1d4bd8a4554786f8f9a87bfa977fb7dbaa1d7f102a30477338b044b65de29d8e" dependencies = [ "ahash", "async-broadcast", "async-stream", - "async-trait", "backon", "educe", "futures 0.3.31", @@ -2646,7 +2665,7 @@ dependencies = [ [[package]] name = "stackable-operator" version = "0.92.0" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#5d7a0076b1e51b6105375e46e44e79198d6e233a" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#b41837a04b1dfa61ab6c79a41c418fc6e864d4f9" dependencies = [ "chrono", "clap", @@ -2683,7 +2702,7 @@ dependencies = [ [[package]] name = "stackable-operator-derive" version = "0.3.1" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#5d7a0076b1e51b6105375e46e44e79198d6e233a" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#b41837a04b1dfa61ab6c79a41c418fc6e864d4f9" dependencies = [ "darling", "proc-macro2", @@ -2694,7 +2713,7 @@ dependencies = [ [[package]] name = "stackable-shared" version = "0.0.1" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#5d7a0076b1e51b6105375e46e44e79198d6e233a" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#b41837a04b1dfa61ab6c79a41c418fc6e864d4f9" dependencies = [ "kube", "semver", @@ -2706,7 +2725,7 @@ dependencies = [ [[package]] name = "stackable-telemetry" version = "0.6.0" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#5d7a0076b1e51b6105375e46e44e79198d6e233a" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#b41837a04b1dfa61ab6c79a41c418fc6e864d4f9" dependencies = [ "axum", "clap", @@ -2729,7 +2748,7 @@ dependencies = [ [[package]] name = "stackable-versioned" version = "0.7.1" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#5d7a0076b1e51b6105375e46e44e79198d6e233a" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#b41837a04b1dfa61ab6c79a41c418fc6e864d4f9" dependencies = [ "stackable-versioned-macros", ] @@ -2737,7 +2756,7 @@ dependencies = [ [[package]] name = "stackable-versioned-macros" version = "0.7.1" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#5d7a0076b1e51b6105375e46e44e79198d6e233a" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#b41837a04b1dfa61ab6c79a41c418fc6e864d4f9" dependencies = [ "convert_case", "darling", diff --git a/Cargo.nix b/Cargo.nix index f7067c94..bafa90eb 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -1714,6 +1714,94 @@ rec { }; resolvedDefaultFeatures = [ "alloc" "powerfmt" "std" ]; }; + "derive_more" = rec { + crateName = "derive_more"; + version = "2.0.1"; + edition = "2021"; + sha256 = "0y3n97cc7rsvgnj211p92y1ppzh6jzvq5kvk6340ghkhfp7l4ch9"; + authors = [ + "Jelte Fennema " + ]; + dependencies = [ + { + name = "derive_more-impl"; + packageId = "derive_more-impl"; + } + ]; + features = { + "add" = [ "derive_more-impl/add" ]; + "add_assign" = [ "derive_more-impl/add_assign" ]; + "as_ref" = [ "derive_more-impl/as_ref" ]; + "constructor" = [ "derive_more-impl/constructor" ]; + "debug" = [ "derive_more-impl/debug" ]; + "default" = [ "std" ]; + "deref" = [ "derive_more-impl/deref" ]; + "deref_mut" = [ "derive_more-impl/deref_mut" ]; + "display" = [ "derive_more-impl/display" ]; + "error" = [ "derive_more-impl/error" ]; + "from" = [ "derive_more-impl/from" ]; + "from_str" = [ "derive_more-impl/from_str" ]; + "full" = [ "add" "add_assign" "as_ref" "constructor" "debug" "deref" "deref_mut" "display" "error" "from" "from_str" "index" "index_mut" "into" "into_iterator" "is_variant" "mul" "mul_assign" "not" "sum" "try_from" "try_into" "try_unwrap" "unwrap" ]; + "index" = [ "derive_more-impl/index" ]; + "index_mut" = [ "derive_more-impl/index_mut" ]; + "into" = [ "derive_more-impl/into" ]; + "into_iterator" = [ "derive_more-impl/into_iterator" ]; + "is_variant" = [ "derive_more-impl/is_variant" ]; + "mul" = [ "derive_more-impl/mul" ]; + "mul_assign" = [ "derive_more-impl/mul_assign" ]; + "not" = [ "derive_more-impl/not" ]; + "sum" = [ "derive_more-impl/sum" ]; + "testing-helpers" = [ "derive_more-impl/testing-helpers" "dep:rustc_version" ]; + "try_from" = [ "derive_more-impl/try_from" ]; + "try_into" = [ "derive_more-impl/try_into" ]; + "try_unwrap" = [ "derive_more-impl/try_unwrap" ]; + "unwrap" = [ "derive_more-impl/unwrap" ]; + }; + resolvedDefaultFeatures = [ "default" "from" "std" ]; + }; + "derive_more-impl" = rec { + crateName = "derive_more-impl"; + version = "2.0.1"; + edition = "2021"; + sha256 = "1wqxcb7d5lzvpplz9szp4rwy1r23f5wmixz0zd2vcjscqknji9mx"; + procMacro = true; + libName = "derive_more_impl"; + authors = [ + "Jelte Fennema " + ]; + dependencies = [ + { + name = "proc-macro2"; + packageId = "proc-macro2"; + } + { + name = "quote"; + packageId = "quote"; + } + { + name = "syn"; + packageId = "syn 2.0.100"; + } + ]; + features = { + "as_ref" = [ "syn/extra-traits" "syn/visit" ]; + "debug" = [ "syn/extra-traits" "dep:unicode-xid" ]; + "display" = [ "syn/extra-traits" "dep:unicode-xid" ]; + "error" = [ "syn/extra-traits" ]; + "from" = [ "syn/extra-traits" ]; + "full" = [ "add" "add_assign" "as_ref" "constructor" "debug" "deref" "deref_mut" "display" "error" "from" "from_str" "index" "index_mut" "into" "into_iterator" "is_variant" "mul" "mul_assign" "not" "sum" "try_from" "try_into" "try_unwrap" "unwrap" ]; + "into" = [ "syn/extra-traits" ]; + "is_variant" = [ "dep:convert_case" ]; + "mul" = [ "syn/extra-traits" ]; + "mul_assign" = [ "syn/extra-traits" ]; + "not" = [ "syn/extra-traits" ]; + "testing-helpers" = [ "dep:rustc_version" ]; + "try_into" = [ "syn/extra-traits" ]; + "try_unwrap" = [ "dep:convert_case" ]; + "unwrap" = [ "dep:convert_case" ]; + }; + resolvedDefaultFeatures = [ "default" "from" ]; + }; "digest" = rec { crateName = "digest"; version = "0.10.7"; @@ -3573,7 +3661,7 @@ rec { "tokio" = [ "dep:tokio" "tokio/net" "tokio/rt" "tokio/time" ]; "tracing" = [ "dep:tracing" ]; }; - resolvedDefaultFeatures = [ "client" "client-legacy" "default" "http1" "server" "service" "tokio" ]; + resolvedDefaultFeatures = [ "client" "client-legacy" "default" "http1" "server" "service" "tokio" "tracing" ]; }; "iana-time-zone" = rec { crateName = "iana-time-zone"; @@ -4426,10 +4514,10 @@ rec { }; "k8s-openapi" = rec { crateName = "k8s-openapi"; - version = "0.24.0"; + version = "0.25.0"; edition = "2021"; - links = "k8s-openapi-0.24.0"; - sha256 = "1m8ahw59g44kp9p4yd4ar0px15m2nyvhc5krbvqvw2ag6a8bjx9c"; + links = "k8s-openapi-0.25.0"; + sha256 = "1cphvicl9hq4nbp2pbzdcvz9r0f9kzwbqzgp383hl6mfawds8q5a"; libName = "k8s_openapi"; authors = [ "Arnav Singh " @@ -4458,11 +4546,6 @@ rec { packageId = "serde"; usesDefaultFeatures = false; } - { - name = "serde-value"; - packageId = "serde-value"; - usesDefaultFeatures = false; - } { name = "serde_json"; packageId = "serde_json"; @@ -4471,11 +4554,12 @@ rec { } ]; features = { - "earliest" = [ "v1_28" ]; - "latest" = [ "v1_32" ]; + "default" = [ "std" ]; + "earliest" = [ "v1_30" ]; + "latest" = [ "v1_33" ]; "schemars" = [ "dep:schemars" ]; }; - resolvedDefaultFeatures = [ "schemars" "v1_32" ]; + resolvedDefaultFeatures = [ "schemars" "v1_33" ]; }; "k8s-version" = rec { crateName = "k8s-version"; @@ -4484,8 +4568,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "5d7a0076b1e51b6105375e46e44e79198d6e233a"; - sha256 = "1aziyw6b3ldvp0r5hbqgg0q4qph4xk45y5rfnvfn7k8fi2niqrli"; + rev = "b41837a04b1dfa61ab6c79a41c418fc6e864d4f9"; + sha256 = "06hybs97rxp9m4r6k09af5x5swi951i6x229jqr78xxmyqxca0kj"; }; libName = "k8s_version"; authors = [ @@ -4508,14 +4592,15 @@ rec { ]; features = { "darling" = [ "dep:darling" ]; + "serde" = [ "dep:serde" ]; }; resolvedDefaultFeatures = [ "darling" ]; }; "kube" = rec { crateName = "kube"; - version = "0.99.0"; + version = "1.0.0"; edition = "2021"; - sha256 = "0vnaqmxk40i2jgwxqsk8x75rn1j37p93gv3zx6mlhssk200b4kls"; + sha256 = "0bs31pdk7lnrza8p8x96mgdq8v60nv8r25vvpg1374h8fj8c6j8v"; authors = [ "clux " "Natalie Klestrup Röijezon " @@ -4586,9 +4671,9 @@ rec { }; "kube-client" = rec { crateName = "kube-client"; - version = "0.99.0"; + version = "1.0.0"; edition = "2021"; - sha256 = "1wfciml6xcylhlwn72dbiq8vc57cs0a9dzn2bb8j1ps242ayvhkz"; + sha256 = "17rbrvbs3m0c4lgbf2788f0hmpli3b8z1666r50m11h83dxpk6g1"; libName = "kube_client"; authors = [ "clux " @@ -4671,7 +4756,7 @@ rec { name = "hyper-util"; packageId = "hyper-util"; optional = true; - features = [ "client" "client-legacy" "http1" "tokio" ]; + features = [ "client" "client-legacy" "http1" "tokio" "tracing" ]; } { name = "jsonpath-rust"; @@ -4827,9 +4912,9 @@ rec { }; "kube-core" = rec { crateName = "kube-core"; - version = "0.99.0"; + version = "1.0.0"; edition = "2021"; - sha256 = "0xf60y07xkqqqqyl7rvk2r4amcvch61r2j49ssk0rrsqvf9hf3gz"; + sha256 = "1rwqwvwlna79dq2r1dqhzgxmwls5d76xg892m2gdk8nyi6xgpphv"; libName = "kube_core"; authors = [ "clux " @@ -4843,6 +4928,11 @@ rec { usesDefaultFeatures = false; features = [ "now" ]; } + { + name = "derive_more"; + packageId = "derive_more"; + features = [ "from" ]; + } { name = "form_urlencoded"; packageId = "form_urlencoded"; @@ -4904,9 +4994,9 @@ rec { }; "kube-derive" = rec { crateName = "kube-derive"; - version = "0.99.0"; + version = "1.0.0"; edition = "2021"; - sha256 = "1jclsj6ah0ijhzpd43ff0ipxs7i2r985ivm6r3m5zjppr66zaqn5"; + sha256 = "1gw3kymxb0w30gmhxj33g09vcqyq05pc38sjjf3516k86cv9lq4f"; procMacro = true; libName = "kube_derive"; authors = [ @@ -4953,9 +5043,9 @@ rec { }; "kube-runtime" = rec { crateName = "kube-runtime"; - version = "0.99.0"; + version = "1.0.0"; edition = "2021"; - sha256 = "1p7z4vl9l1hbqqqjx7qmkyq3rwhxp3nqa3if0qrqdgdlp7x4rww8"; + sha256 = "13lxw9fvci5h71rlfc1a21zivanvnxzrgykvm3wzi1j7anjdhjqx"; libName = "kube_runtime"; authors = [ "clux " @@ -4975,10 +5065,6 @@ rec { name = "async-stream"; packageId = "async-stream"; } - { - name = "async-trait"; - packageId = "async-trait"; - } { name = "backon"; packageId = "backon"; @@ -8604,8 +8690,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "5d7a0076b1e51b6105375e46e44e79198d6e233a"; - sha256 = "1aziyw6b3ldvp0r5hbqgg0q4qph4xk45y5rfnvfn7k8fi2niqrli"; + rev = "b41837a04b1dfa61ab6c79a41c418fc6e864d4f9"; + sha256 = "06hybs97rxp9m4r6k09af5x5swi951i6x229jqr78xxmyqxca0kj"; }; libName = "stackable_operator"; authors = [ @@ -8660,7 +8746,7 @@ rec { name = "k8s-openapi"; packageId = "k8s-openapi"; usesDefaultFeatures = false; - features = [ "schemars" "v1_32" ]; + features = [ "schemars" "v1_33" ]; } { name = "kube"; @@ -8763,8 +8849,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "5d7a0076b1e51b6105375e46e44e79198d6e233a"; - sha256 = "1aziyw6b3ldvp0r5hbqgg0q4qph4xk45y5rfnvfn7k8fi2niqrli"; + rev = "b41837a04b1dfa61ab6c79a41c418fc6e864d4f9"; + sha256 = "06hybs97rxp9m4r6k09af5x5swi951i6x229jqr78xxmyqxca0kj"; }; procMacro = true; libName = "stackable_operator_derive"; @@ -8798,8 +8884,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "5d7a0076b1e51b6105375e46e44e79198d6e233a"; - sha256 = "1aziyw6b3ldvp0r5hbqgg0q4qph4xk45y5rfnvfn7k8fi2niqrli"; + rev = "b41837a04b1dfa61ab6c79a41c418fc6e864d4f9"; + sha256 = "06hybs97rxp9m4r6k09af5x5swi951i6x229jqr78xxmyqxca0kj"; }; libName = "stackable_shared"; authors = [ @@ -8839,8 +8925,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "5d7a0076b1e51b6105375e46e44e79198d6e233a"; - sha256 = "1aziyw6b3ldvp0r5hbqgg0q4qph4xk45y5rfnvfn7k8fi2niqrli"; + rev = "b41837a04b1dfa61ab6c79a41c418fc6e864d4f9"; + sha256 = "06hybs97rxp9m4r6k09af5x5swi951i6x229jqr78xxmyqxca0kj"; }; libName = "stackable_telemetry"; authors = [ @@ -8944,8 +9030,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "5d7a0076b1e51b6105375e46e44e79198d6e233a"; - sha256 = "1aziyw6b3ldvp0r5hbqgg0q4qph4xk45y5rfnvfn7k8fi2niqrli"; + rev = "b41837a04b1dfa61ab6c79a41c418fc6e864d4f9"; + sha256 = "06hybs97rxp9m4r6k09af5x5swi951i6x229jqr78xxmyqxca0kj"; }; libName = "stackable_versioned"; authors = [ @@ -8970,8 +9056,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "5d7a0076b1e51b6105375e46e44e79198d6e233a"; - sha256 = "1aziyw6b3ldvp0r5hbqgg0q4qph4xk45y5rfnvfn7k8fi2niqrli"; + rev = "b41837a04b1dfa61ab6c79a41c418fc6e864d4f9"; + sha256 = "06hybs97rxp9m4r6k09af5x5swi951i6x229jqr78xxmyqxca0kj"; }; procMacro = true; libName = "stackable_versioned_macros"; @@ -8996,7 +9082,7 @@ rec { packageId = "k8s-openapi"; optional = true; usesDefaultFeatures = false; - features = [ "schemars" "v1_32" ]; + features = [ "schemars" "v1_33" ]; } { name = "k8s-version"; @@ -9028,7 +9114,7 @@ rec { name = "k8s-openapi"; packageId = "k8s-openapi"; usesDefaultFeatures = false; - features = [ "schemars" "v1_32" ]; + features = [ "schemars" "v1_33" ]; } ]; features = { diff --git a/crate-hashes.json b/crate-hashes.json index fb21465b..5b6b43df 100644 --- a/crate-hashes.json +++ b/crate-hashes.json @@ -1,10 +1,10 @@ { - "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#k8s-version@0.1.2": "1aziyw6b3ldvp0r5hbqgg0q4qph4xk45y5rfnvfn7k8fi2niqrli", - "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#stackable-operator-derive@0.3.1": "1aziyw6b3ldvp0r5hbqgg0q4qph4xk45y5rfnvfn7k8fi2niqrli", - "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#stackable-operator@0.92.0": "1aziyw6b3ldvp0r5hbqgg0q4qph4xk45y5rfnvfn7k8fi2niqrli", - "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#stackable-shared@0.0.1": "1aziyw6b3ldvp0r5hbqgg0q4qph4xk45y5rfnvfn7k8fi2niqrli", - "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#stackable-telemetry@0.6.0": "1aziyw6b3ldvp0r5hbqgg0q4qph4xk45y5rfnvfn7k8fi2niqrli", - "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#stackable-versioned-macros@0.7.1": "1aziyw6b3ldvp0r5hbqgg0q4qph4xk45y5rfnvfn7k8fi2niqrli", - "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#stackable-versioned@0.7.1": "1aziyw6b3ldvp0r5hbqgg0q4qph4xk45y5rfnvfn7k8fi2niqrli", + "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#k8s-version@0.1.2": "06hybs97rxp9m4r6k09af5x5swi951i6x229jqr78xxmyqxca0kj", + "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#stackable-operator-derive@0.3.1": "06hybs97rxp9m4r6k09af5x5swi951i6x229jqr78xxmyqxca0kj", + "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#stackable-operator@0.92.0": "06hybs97rxp9m4r6k09af5x5swi951i6x229jqr78xxmyqxca0kj", + "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#stackable-shared@0.0.1": "06hybs97rxp9m4r6k09af5x5swi951i6x229jqr78xxmyqxca0kj", + "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#stackable-telemetry@0.6.0": "06hybs97rxp9m4r6k09af5x5swi951i6x229jqr78xxmyqxca0kj", + "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#stackable-versioned-macros@0.7.1": "06hybs97rxp9m4r6k09af5x5swi951i6x229jqr78xxmyqxca0kj", + "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#stackable-versioned@0.7.1": "06hybs97rxp9m4r6k09af5x5swi951i6x229jqr78xxmyqxca0kj", "git+https://github.com/stackabletech/product-config.git?tag=0.7.0#product-config@0.7.0": "0gjsm80g6r75pm3824dcyiz4ysq1ka4c1if6k1mjm9cnd5ym0gny" } \ No newline at end of file From d226b7ef07fa5a219931a4c3ed3ca54abb78ffe8 Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Mon, 19 May 2025 13:25:33 +0200 Subject: [PATCH 03/24] Add logging support to git-sync containers --- rust/operator-binary/src/controller.rs | 13 ++++++++----- rust/operator-binary/src/crd/mod.rs | 1 + .../templates/kuttl/logging/04-install-nifi.yaml.j2 | 12 ++++++++++++ .../logging/nifi-vector-aggregator-values.yaml.j2 | 6 ++++++ tests/test-definition.yaml | 2 +- 5 files changed, 28 insertions(+), 6 deletions(-) diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index 54c5b8a9..234c303b 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -109,6 +109,7 @@ pub const NIFI_FULL_CONTROLLER_NAME: &str = concatcp!(NIFI_CONTROLLER_NAME, '.', pub const NIFI_UID: i64 = 1000; const DOCKER_IMAGE_BASE_NAME: &str = "nifi"; +const LOG_VOLUME_NAME: &str = "log"; pub struct Ctx { pub client: Client, @@ -509,6 +510,8 @@ pub async fn reconcile_nifi( &resolved_product_image, &env_vars_from_rolegroup_config(rolegroup_config), &[], + LOG_VOLUME_NAME, + &merged_config.logging.for_container(&Container::GitSync), ) .context(InvalidGitSyncSpecSnafu)?; @@ -1052,7 +1055,7 @@ async fn build_node_rolegroup_statefulset( .context(AddVolumeMountSnafu)? .add_volume_mount("sensitiveproperty", "/stackable/sensitiveproperty") .context(AddVolumeMountSnafu)? - .add_volume_mount("log", STACKABLE_LOG_DIR) + .add_volume_mount(LOG_VOLUME_NAME, STACKABLE_LOG_DIR) .context(AddVolumeMountSnafu)? .add_volume_mount(TRUSTSTORE_VOLUME_NAME, STACKABLE_SERVER_TLS_DIR) .context(AddVolumeMountSnafu)? @@ -1128,7 +1131,7 @@ async fn build_node_rolegroup_statefulset( .context(AddVolumeMountSnafu)? .add_volume_mount("log-config", STACKABLE_LOG_CONFIG_DIR) .context(AddVolumeMountSnafu)? - .add_volume_mount("log", STACKABLE_LOG_DIR) + .add_volume_mount(LOG_VOLUME_NAME, STACKABLE_LOG_DIR) .context(AddVolumeMountSnafu)? .add_volume_mount(TRUSTSTORE_VOLUME_NAME, STACKABLE_SERVER_TLS_DIR) .context(AddVolumeMountSnafu)? @@ -1242,7 +1245,7 @@ async fn build_node_rolegroup_statefulset( product_logging::framework::vector_container( resolved_product_image, "config", - "log", + LOG_VOLUME_NAME, merged_config.logging.containers.get(&Container::Vector), ResourceRequirementsBuilder::new() .with_cpu_request("250m") @@ -1308,7 +1311,7 @@ async fn build_node_rolegroup_statefulset( }) .context(AddVolumeSnafu)? .add_empty_dir_volume( - "log", + LOG_VOLUME_NAME, // Set volume size to higher than theoretically necessary to avoid running out of disk space as log rotation triggers are only checked by Logback every 5s. Some( MemoryQuantity { @@ -1409,7 +1412,7 @@ async fn build_node_rolegroup_statefulset( ), ..LabelSelector::default() }, - service_name: rolegroup_ref.object_name(), + service_name: Some(rolegroup_ref.object_name()), template: pod_template, update_strategy: Some(StatefulSetUpdateStrategy { type_: if rolling_update_supported { diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index 91859fb5..11a7deb6 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -473,6 +473,7 @@ pub enum Container { Prepare, Vector, Nifi, + GitSync, } #[derive(Clone, Debug, Default, Fragment, JsonSchema, PartialEq)] diff --git a/tests/templates/kuttl/logging/04-install-nifi.yaml.j2 b/tests/templates/kuttl/logging/04-install-nifi.yaml.j2 index c3926671..41b2a423 100644 --- a/tests/templates/kuttl/logging/04-install-nifi.yaml.j2 +++ b/tests/templates/kuttl/logging/04-install-nifi.yaml.j2 @@ -96,6 +96,10 @@ spec: - authenticationClass: simple-nifi-users sensitiveProperties: keySecret: nifi-sensitive-property-key + customProcessorsGitSync: + - repo: https://github.com/stackabletech/nifi-operator + branch: feat/custom-processors # TODO Change to commit + gitFolder: tests/templates/kuttl/custom-processors-git-sync/processors-0 vectorAggregatorConfigMapName: nifi-vector-aggregator-discovery zookeeperConfigMapName: test-nifi-znode nodes: @@ -116,6 +120,14 @@ spec: loggers: ROOT: level: INFO + git-sync: + console: + level: INFO + file: + level: INFO + loggers: + ROOT: + level: INFO vector: console: level: INFO diff --git a/tests/templates/kuttl/logging/nifi-vector-aggregator-values.yaml.j2 b/tests/templates/kuttl/logging/nifi-vector-aggregator-values.yaml.j2 index 3a16c399..c05f58cf 100644 --- a/tests/templates/kuttl/logging/nifi-vector-aggregator-values.yaml.j2 +++ b/tests/templates/kuttl/logging/nifi-vector-aggregator-values.yaml.j2 @@ -30,6 +30,12 @@ customConfig: condition: >- .pod == "test-nifi-node-automatic-log-config-0" && .container == "nifi" + filteredAutomaticLogConfigNodeGitSync: + type: filter + inputs: [validEvents] + condition: >- + .pod == "test-nifi-node-automatic-log-config-0" && + .container == "git-sync-0" filteredAutomaticLogConfigNodeVector: type: filter inputs: [validEvents] diff --git a/tests/test-definition.yaml b/tests/test-definition.yaml index 93cf578c..3745a2d6 100644 --- a/tests/test-definition.yaml +++ b/tests/test-definition.yaml @@ -4,7 +4,7 @@ dimensions: values: - 1.27.0 - 1.28.1 - - 2.2.0 + - 2.2.0,oci.stackable.tech/sandbox/git-sync/nifi:2.2.0-stackable0.0.0-dev # Alternatively, if you want to use a custom image, append a comma and the full image name to the product version # as in the example below. # - 2.2.0,oci.stackable.tech/sandbox/nifi:2.2.0-stackable0.0.0-dev From 116ab1db21fa7fb640a0d60c5520db2e52cd4d55 Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Mon, 19 May 2025 13:34:03 +0200 Subject: [PATCH 04/24] Regenerate charts --- deploy/helm/nifi-operator/crds/crds.yaml | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/deploy/helm/nifi-operator/crds/crds.yaml b/deploy/helm/nifi-operator/crds/crds.yaml index 1c6d7bf9..cf983ff0 100644 --- a/deploy/helm/nifi-operator/crds/crds.yaml +++ b/deploy/helm/nifi-operator/crds/crds.yaml @@ -93,7 +93,12 @@ spec: Since git-sync v4.x.x this field is mapped to the flag `--ref`. type: string credentialsSecret: - description: 'The name of the Secret used to access the repository if it is not public. This should include two fields: `user` and `password`. The `password` field can either be an actual password (not recommended) or a GitHub token, as described [here](https://github.com/kubernetes/git-sync/tree/v4.2.4?tab=readme-ov-file#manual).' + description: |- + The name of the Secret used to access the repository if it is not public. + + The referenced Secret must include two fields: `user` and `password`. The `password` field can either be an actual password (not recommended) or a GitHub token, as described in the git-sync [documentation]. + + [documentation]: https://github.com/kubernetes/git-sync/tree/v4.2.4?tab=readme-ov-file#manual nullable: true type: string depth: @@ -105,7 +110,7 @@ spec: gitFolder: default: / description: |- - The location of the DAG folder, relative to the synced repository root. + Location in the Git repository containing the resource; defaults to the root folder. It can optionally start with `/`, however, no trailing slash is recommended. An empty string (``) or slash (`/`) corresponds to the root folder in Git. type: string @@ -113,10 +118,16 @@ spec: additionalProperties: type: string default: {} - description: A map of optional configuration settings that are listed in the [git-sync documentation](https://github.com/kubernetes/git-sync/tree/v4.2.4?tab=readme-ov-file#manual). Read the [git sync example](https://docs.stackable.tech/home/nightly/airflow/usage-guide/mounting-dags#_example). + description: |- + A map of optional configuration settings that are listed in the git-sync [documentation]. + + Also read the git-sync [example] in our documentation. + + [documentation]: https://github.com/kubernetes/git-sync/tree/v4.2.4?tab=readme-ov-file#manual [example]: https://docs.stackable.tech/home/nightly/airflow/usage-guide/mounting-dags#_example type: object repo: description: 'The git repository URL that will be cloned, for example: `https://github.com/stackabletech/airflow-operator`.' + format: uri type: string wait: default: 20s From 70e110e0a4cbc6b264de6aaaedaea368fafc6bc2 Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Tue, 20 May 2025 16:29:24 +0200 Subject: [PATCH 05/24] Upgrade stackable-operator --- Cargo.lock | 18 +++++----- Cargo.nix | 46 ++++++++++++------------ Cargo.toml | 5 +-- crate-hashes.json | 14 ++++---- deploy/helm/nifi-operator/crds/crds.yaml | 17 +++------ 5 files changed, 44 insertions(+), 56 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b5b0d095..6f4131fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1453,8 +1453,8 @@ dependencies = [ [[package]] name = "k8s-version" -version = "0.1.2" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#b41837a04b1dfa61ab6c79a41c418fc6e864d4f9" +version = "0.1.3" +source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.93.1#d990019c690ed85aea29b095a03661c015676caa" dependencies = [ "darling", "regex", @@ -2664,8 +2664,8 @@ dependencies = [ [[package]] name = "stackable-operator" -version = "0.92.0" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#b41837a04b1dfa61ab6c79a41c418fc6e864d4f9" +version = "0.93.1" +source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.93.1#d990019c690ed85aea29b095a03661c015676caa" dependencies = [ "chrono", "clap", @@ -2702,7 +2702,7 @@ dependencies = [ [[package]] name = "stackable-operator-derive" version = "0.3.1" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#b41837a04b1dfa61ab6c79a41c418fc6e864d4f9" +source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.93.1#d990019c690ed85aea29b095a03661c015676caa" dependencies = [ "darling", "proc-macro2", @@ -2713,7 +2713,7 @@ dependencies = [ [[package]] name = "stackable-shared" version = "0.0.1" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#b41837a04b1dfa61ab6c79a41c418fc6e864d4f9" +source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.93.1#d990019c690ed85aea29b095a03661c015676caa" dependencies = [ "kube", "semver", @@ -2725,7 +2725,7 @@ dependencies = [ [[package]] name = "stackable-telemetry" version = "0.6.0" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#b41837a04b1dfa61ab6c79a41c418fc6e864d4f9" +source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.93.1#d990019c690ed85aea29b095a03661c015676caa" dependencies = [ "axum", "clap", @@ -2748,7 +2748,7 @@ dependencies = [ [[package]] name = "stackable-versioned" version = "0.7.1" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#b41837a04b1dfa61ab6c79a41c418fc6e864d4f9" +source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.93.1#d990019c690ed85aea29b095a03661c015676caa" dependencies = [ "stackable-versioned-macros", ] @@ -2756,7 +2756,7 @@ dependencies = [ [[package]] name = "stackable-versioned-macros" version = "0.7.1" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#b41837a04b1dfa61ab6c79a41c418fc6e864d4f9" +source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.93.1#d990019c690ed85aea29b095a03661c015676caa" dependencies = [ "convert_case", "darling", diff --git a/Cargo.nix b/Cargo.nix index bafa90eb..d48c1ef0 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -4563,13 +4563,13 @@ rec { }; "k8s-version" = rec { crateName = "k8s-version"; - version = "0.1.2"; + version = "0.1.3"; edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech//operator-rs.git"; - rev = "b41837a04b1dfa61ab6c79a41c418fc6e864d4f9"; - sha256 = "06hybs97rxp9m4r6k09af5x5swi951i6x229jqr78xxmyqxca0kj"; + url = "https://github.com/stackabletech/operator-rs.git"; + rev = "d990019c690ed85aea29b095a03661c015676caa"; + sha256 = "16faz0f3dsv095hk94kmb7mk3pr6lban1v3k0g6yawak6gk5xln6"; }; libName = "k8s_version"; authors = [ @@ -8685,13 +8685,13 @@ rec { }; "stackable-operator" = rec { crateName = "stackable-operator"; - version = "0.92.0"; + version = "0.93.1"; edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech//operator-rs.git"; - rev = "b41837a04b1dfa61ab6c79a41c418fc6e864d4f9"; - sha256 = "06hybs97rxp9m4r6k09af5x5swi951i6x229jqr78xxmyqxca0kj"; + url = "https://github.com/stackabletech/operator-rs.git"; + rev = "d990019c690ed85aea29b095a03661c015676caa"; + sha256 = "16faz0f3dsv095hk94kmb7mk3pr6lban1v3k0g6yawak6gk5xln6"; }; libName = "stackable_operator"; authors = [ @@ -8848,9 +8848,9 @@ rec { edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech//operator-rs.git"; - rev = "b41837a04b1dfa61ab6c79a41c418fc6e864d4f9"; - sha256 = "06hybs97rxp9m4r6k09af5x5swi951i6x229jqr78xxmyqxca0kj"; + url = "https://github.com/stackabletech/operator-rs.git"; + rev = "d990019c690ed85aea29b095a03661c015676caa"; + sha256 = "16faz0f3dsv095hk94kmb7mk3pr6lban1v3k0g6yawak6gk5xln6"; }; procMacro = true; libName = "stackable_operator_derive"; @@ -8883,9 +8883,9 @@ rec { edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech//operator-rs.git"; - rev = "b41837a04b1dfa61ab6c79a41c418fc6e864d4f9"; - sha256 = "06hybs97rxp9m4r6k09af5x5swi951i6x229jqr78xxmyqxca0kj"; + url = "https://github.com/stackabletech/operator-rs.git"; + rev = "d990019c690ed85aea29b095a03661c015676caa"; + sha256 = "16faz0f3dsv095hk94kmb7mk3pr6lban1v3k0g6yawak6gk5xln6"; }; libName = "stackable_shared"; authors = [ @@ -8924,9 +8924,9 @@ rec { edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech//operator-rs.git"; - rev = "b41837a04b1dfa61ab6c79a41c418fc6e864d4f9"; - sha256 = "06hybs97rxp9m4r6k09af5x5swi951i6x229jqr78xxmyqxca0kj"; + url = "https://github.com/stackabletech/operator-rs.git"; + rev = "d990019c690ed85aea29b095a03661c015676caa"; + sha256 = "16faz0f3dsv095hk94kmb7mk3pr6lban1v3k0g6yawak6gk5xln6"; }; libName = "stackable_telemetry"; authors = [ @@ -9029,9 +9029,9 @@ rec { edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech//operator-rs.git"; - rev = "b41837a04b1dfa61ab6c79a41c418fc6e864d4f9"; - sha256 = "06hybs97rxp9m4r6k09af5x5swi951i6x229jqr78xxmyqxca0kj"; + url = "https://github.com/stackabletech/operator-rs.git"; + rev = "d990019c690ed85aea29b095a03661c015676caa"; + sha256 = "16faz0f3dsv095hk94kmb7mk3pr6lban1v3k0g6yawak6gk5xln6"; }; libName = "stackable_versioned"; authors = [ @@ -9055,9 +9055,9 @@ rec { edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech//operator-rs.git"; - rev = "b41837a04b1dfa61ab6c79a41c418fc6e864d4f9"; - sha256 = "06hybs97rxp9m4r6k09af5x5swi951i6x229jqr78xxmyqxca0kj"; + url = "https://github.com/stackabletech/operator-rs.git"; + rev = "d990019c690ed85aea29b095a03661c015676caa"; + sha256 = "16faz0f3dsv095hk94kmb7mk3pr6lban1v3k0g6yawak6gk5xln6"; }; procMacro = true; libName = "stackable_versioned_macros"; diff --git a/Cargo.toml b/Cargo.toml index 017ad61d..8369da44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ repository = "https://github.com/stackabletech/nifi-operator" [workspace.dependencies] product-config = { git = "https://github.com/stackabletech/product-config.git", tag = "0.7.0" } -stackable-operator = { git = "https://github.com/stackabletech/operator-rs.git", features = ["telemetry", "versioned"], tag = "stackable-operator-0.92.0" } +stackable-operator = { git = "https://github.com/stackabletech/operator-rs.git", features = ["telemetry", "versioned"], tag = "stackable-operator-0.93.1" } anyhow = "1.0" built = { version = "0.7", features = ["chrono", "git2"] } @@ -37,6 +37,3 @@ xml-rs = "0.8" # [patch."https://github.com/stackabletech/operator-rs.git"] # stackable-operator = { git = "https://github.com/stackabletech//operator-rs.git", branch = "main" } # stackable-operator = { path = "../operator-rs/crates/stackable-operator" } - -[patch."https://github.com/stackabletech/operator-rs.git"] -stackable-operator = { git = "https://github.com/stackabletech//operator-rs.git", branch = "feat/git-sync" } diff --git a/crate-hashes.json b/crate-hashes.json index 5b6b43df..81f487ff 100644 --- a/crate-hashes.json +++ b/crate-hashes.json @@ -1,10 +1,10 @@ { - "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#k8s-version@0.1.2": "06hybs97rxp9m4r6k09af5x5swi951i6x229jqr78xxmyqxca0kj", - "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#stackable-operator-derive@0.3.1": "06hybs97rxp9m4r6k09af5x5swi951i6x229jqr78xxmyqxca0kj", - "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#stackable-operator@0.92.0": "06hybs97rxp9m4r6k09af5x5swi951i6x229jqr78xxmyqxca0kj", - "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#stackable-shared@0.0.1": "06hybs97rxp9m4r6k09af5x5swi951i6x229jqr78xxmyqxca0kj", - "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#stackable-telemetry@0.6.0": "06hybs97rxp9m4r6k09af5x5swi951i6x229jqr78xxmyqxca0kj", - "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#stackable-versioned-macros@0.7.1": "06hybs97rxp9m4r6k09af5x5swi951i6x229jqr78xxmyqxca0kj", - "git+https://github.com/stackabletech//operator-rs.git?branch=feat%2Fgit-sync#stackable-versioned@0.7.1": "06hybs97rxp9m4r6k09af5x5swi951i6x229jqr78xxmyqxca0kj", + "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.93.1#k8s-version@0.1.3": "16faz0f3dsv095hk94kmb7mk3pr6lban1v3k0g6yawak6gk5xln6", + "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.93.1#stackable-operator-derive@0.3.1": "16faz0f3dsv095hk94kmb7mk3pr6lban1v3k0g6yawak6gk5xln6", + "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.93.1#stackable-operator@0.93.1": "16faz0f3dsv095hk94kmb7mk3pr6lban1v3k0g6yawak6gk5xln6", + "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.93.1#stackable-shared@0.0.1": "16faz0f3dsv095hk94kmb7mk3pr6lban1v3k0g6yawak6gk5xln6", + "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.93.1#stackable-telemetry@0.6.0": "16faz0f3dsv095hk94kmb7mk3pr6lban1v3k0g6yawak6gk5xln6", + "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.93.1#stackable-versioned-macros@0.7.1": "16faz0f3dsv095hk94kmb7mk3pr6lban1v3k0g6yawak6gk5xln6", + "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.93.1#stackable-versioned@0.7.1": "16faz0f3dsv095hk94kmb7mk3pr6lban1v3k0g6yawak6gk5xln6", "git+https://github.com/stackabletech/product-config.git?tag=0.7.0#product-config@0.7.0": "0gjsm80g6r75pm3824dcyiz4ysq1ka4c1if6k1mjm9cnd5ym0gny" } \ No newline at end of file diff --git a/deploy/helm/nifi-operator/crds/crds.yaml b/deploy/helm/nifi-operator/crds/crds.yaml index cf983ff0..e03a7b54 100644 --- a/deploy/helm/nifi-operator/crds/crds.yaml +++ b/deploy/helm/nifi-operator/crds/crds.yaml @@ -33,16 +33,10 @@ spec: items: properties: authenticationClass: - description: |- - Name of the [AuthenticationClass](https://docs.stackable.tech/home/nightly/concepts/authentication) used to authenticate users. - - To get the concrete [`AuthenticationClass`], we must resolve it. This resolution can be achieved by using [`ClientAuthenticationDetails::resolve_class`]. + description: Name of the [AuthenticationClass](https://docs.stackable.tech/home/nightly/concepts/authentication) used to authenticate users type: string oidc: - description: |- - This field contains OIDC-specific configuration. It is only required in case OIDC is used. - - Use [`ClientAuthenticationDetails::oidc_or_error`] to get the value or report an error to the user. + description: This field contains OIDC-specific configuration. It is only required in case OIDC is used. nullable: true properties: clientCredentialsSecret: @@ -50,10 +44,7 @@ spec: type: string extraScopes: default: [] - description: |- - An optional list of extra scopes which get merged with the scopes defined in the [`AuthenticationClass`][1]. - - [1]: crate::crd::authentication::core::v1alpha1::AuthenticationClass + description: An optional list of extra scopes which get merged with the scopes defined in the `AuthenticationClass`. items: type: string type: array @@ -121,7 +112,7 @@ spec: description: |- A map of optional configuration settings that are listed in the git-sync [documentation]. - Also read the git-sync [example] in our documentation. + Also read the git-sync [example] in our documentation. These settings are not verified. [documentation]: https://github.com/kubernetes/git-sync/tree/v4.2.4?tab=readme-ov-file#manual [example]: https://docs.stackable.tech/home/nightly/airflow/usage-guide/mounting-dags#_example type: object From a758ea6827196cfbfa32ed8d49924741439045db Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Thu, 22 May 2025 11:23:25 +0200 Subject: [PATCH 06/24] test: Reimplement the ShoutProcessor in Java --- .../30-install-nifi.yaml.j2 | 30 +++++++++--------- .../java-processors/nifi-sample-nar-1.0.0.nar | Bin 0 -> 125767 bytes .../java-processors/sample-processor.tar.gz | Bin 0 -> 4087 bytes .../processors-1/shout_processor.py | 18 ----------- .../greet_processor.py | 0 5 files changed, 15 insertions(+), 33 deletions(-) create mode 100644 tests/templates/kuttl/custom-processors-git-sync/java-processors/nifi-sample-nar-1.0.0.nar create mode 100644 tests/templates/kuttl/custom-processors-git-sync/java-processors/sample-processor.tar.gz delete mode 100644 tests/templates/kuttl/custom-processors-git-sync/processors-1/shout_processor.py rename tests/templates/kuttl/custom-processors-git-sync/{processors-0 => python-processors}/greet_processor.py (100%) diff --git a/tests/templates/kuttl/custom-processors-git-sync/30-install-nifi.yaml.j2 b/tests/templates/kuttl/custom-processors-git-sync/30-install-nifi.yaml.j2 index 67250dee..e81a39c7 100644 --- a/tests/templates/kuttl/custom-processors-git-sync/30-install-nifi.yaml.j2 +++ b/tests/templates/kuttl/custom-processors-git-sync/30-install-nifi.yaml.j2 @@ -40,10 +40,10 @@ spec: customProcessorsGitSync: - repo: https://github.com/stackabletech/nifi-operator branch: feat/custom-processors # TODO Change to commit - gitFolder: tests/templates/kuttl/custom-processors-git-sync/processors-0 + gitFolder: tests/templates/kuttl/custom-processors-git-sync/java-processors - repo: https://github.com/stackabletech/nifi-operator branch: feat/custom-processors # TODO Change to commit - gitFolder: tests/templates/kuttl/custom-processors-git-sync/processors-1 + gitFolder: tests/templates/kuttl/custom-processors-git-sync/python-processors {% if lookup('env', 'VECTOR_AGGREGATOR') %} vectorAggregatorConfigMapName: vector-aggregator-discovery {% endif %} @@ -53,19 +53,19 @@ spec: enableVectorAgent: {{ lookup('env', 'VECTOR_AGGREGATOR') | length > 0 }} podOverrides: spec: - initContainers: - - name: init-flow - image: oci.stackable.tech/sdp/testing-tools:0.2.0-stackable0.0.0-dev - command: - - /bin/bash - - -c - args: - - gzip --stdout /stackable/nifi/flow/flow.json > /stackable/data/database/flow.json.gz - volumeMounts: - - name: nifi-flow - mountPath: /stackable/nifi/flow - - name: database-repository - mountPath: /stackable/data/database + # initContainers: + # - name: init-flow + # image: oci.stackable.tech/sdp/testing-tools:0.2.0-stackable0.0.0-dev + # command: + # - /bin/bash + # - -c + # args: + # - gzip --stdout /stackable/nifi/flow/flow.json > /stackable/data/database/flow.json.gz + # volumeMounts: + # - name: nifi-flow + # mountPath: /stackable/nifi/flow + # - name: database-repository + # mountPath: /stackable/data/database containers: - name: nifi ports: diff --git a/tests/templates/kuttl/custom-processors-git-sync/java-processors/nifi-sample-nar-1.0.0.nar b/tests/templates/kuttl/custom-processors-git-sync/java-processors/nifi-sample-nar-1.0.0.nar new file mode 100644 index 0000000000000000000000000000000000000000..d9e7f9037ce310b7b38794cc78ad64a7b374773c GIT binary patch literal 125767 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 z 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 literal 0 HcmV?d00001 diff --git a/tests/templates/kuttl/custom-processors-git-sync/java-processors/sample-processor.tar.gz b/tests/templates/kuttl/custom-processors-git-sync/java-processors/sample-processor.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..168e18721ba048f9cd8a3ac103449882167171ce GIT binary patch literal 4087 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 z zEv4}Pa)E=-f4^=pQ-w?CI-u+P)0;|VbDl&j;{Y~}-p%&`BjM@cbE$^O0tX?Tl__yF z;xA|aFnsRJP~j_n>yTmpKWokqFKkL+87KIKtH9R$e{l9<_Ft_kYT^Io0*AK$Lp-+` zj_OWoB)}19WhcN}#hqiW9a%-~co^5w%*E7Ap3ynK6>4bfA24w%~&TtzJ*Gt6I6OhH+^AxZ1BEX_|c^m-3u?RP+)RaylW;Z z+s2_-$p55}vHmahgY5h~*cSh(Rh!B9PeU!<|H}mqz5f5I(@WxDku`CpEf&#)+PU`~ z83Do$nX{mXB0IO(P#gy`=D$28j7%-t!3St#+x)LKlJVcBT5S~bKM(jzkeq-#aT|#5 p_~}W7&agSDoW$P*E3Qx~pnw7jD4>7>3izYL{{WI+;phOM003 Date: Thu, 22 May 2025 15:58:30 +0200 Subject: [PATCH 07/24] test: Use the Java processor in the NiFi flow --- rust/operator-binary/src/config/mod.rs | 14 +- rust/operator-binary/src/controller.rs | 10 +- .../30-install-nifi.yaml.j2 | 247 +++++++++--------- .../custom-processors-git-sync/README.md | 29 ++ .../custom-processors-git-sync/canvas.png | Bin 0 -> 159875 bytes 5 files changed, 167 insertions(+), 133 deletions(-) create mode 100644 tests/templates/kuttl/custom-processors-git-sync/README.md create mode 100644 tests/templates/kuttl/custom-processors-git-sync/canvas.png diff --git a/rust/operator-binary/src/config/mod.rs b/rust/operator-binary/src/config/mod.rs index 1e0f71ea..a599852d 100644 --- a/rust/operator-binary/src/config/mod.rs +++ b/rust/operator-binary/src/config/mod.rs @@ -575,9 +575,9 @@ pub fn build_nifi_properties( "${env:ZOOKEEPER_CHROOT}".to_string(), ); - //########################## - // Python-based processors # - //########################## + //#################### + // Custom components # + //#################### if git_sync_resources.is_git_sync_enabled() { // The command used to launch Python. // This property must be set to enable Python-based processors. @@ -605,11 +605,15 @@ pub fn build_nifi_properties( .enumerate() { // The directory that NiFi should look in to find custom Python-based - // Processors. + // components. properties.insert( format!("nifi.python.extensions.source.directory.{i}"), - git_folder, + git_folder.clone(), ); + + // The directory that NiFi should look in to find custom Java-based + // components. + properties.insert(format!("nifi.nar.library.directory.{i}"), git_folder); } } //########################## diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index 234c303b..e3943303 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -1195,18 +1195,22 @@ async fn build_node_rolegroup_statefulset( .context(AddVolumeMountSnafu)?; } + container_nifi + .add_volume_mounts(git_sync_resources.git_content_volume_mounts.to_owned()) + .context(AddVolumeMountSnafu)?; + // We want to add nifi container first for easier defaulting into this container pod_builder.add_container(container_nifi.build()); for container in git_sync_resources.git_sync_containers.iter().cloned() { pod_builder.add_container(container); } + for container in git_sync_resources.git_sync_init_containers.iter().cloned() { + pod_builder.add_init_container(container); + } pod_builder .add_volumes(git_sync_resources.git_content_volumes.to_owned()) .context(AddVolumeSnafu)?; - container_nifi - .add_volume_mounts(git_sync_resources.git_content_volume_mounts.to_owned()) - .context(AddVolumeMountSnafu)?; if let Some(ContainerLogConfig { choice: diff --git a/tests/templates/kuttl/custom-processors-git-sync/30-install-nifi.yaml.j2 b/tests/templates/kuttl/custom-processors-git-sync/30-install-nifi.yaml.j2 index e81a39c7..02f9e78b 100644 --- a/tests/templates/kuttl/custom-processors-git-sync/30-install-nifi.yaml.j2 +++ b/tests/templates/kuttl/custom-processors-git-sync/30-install-nifi.yaml.j2 @@ -53,19 +53,19 @@ spec: enableVectorAgent: {{ lookup('env', 'VECTOR_AGGREGATOR') | length > 0 }} podOverrides: spec: - # initContainers: - # - name: init-flow - # image: oci.stackable.tech/sdp/testing-tools:0.2.0-stackable0.0.0-dev - # command: - # - /bin/bash - # - -c - # args: - # - gzip --stdout /stackable/nifi/flow/flow.json > /stackable/data/database/flow.json.gz - # volumeMounts: - # - name: nifi-flow - # mountPath: /stackable/nifi/flow - # - name: database-repository - # mountPath: /stackable/data/database + initContainers: + - name: init-flow + image: oci.stackable.tech/sdp/testing-tools:0.2.0-stackable0.0.0-dev + command: + - /bin/bash + - -c + args: + - gzip --stdout /stackable/nifi/flow/flow.json > /stackable/data/database/flow.json.gz + volumeMounts: + - name: nifi-flow + mountPath: /stackable/nifi/flow + - name: database-repository + mountPath: /stackable/data/database containers: - name: nifi ports: @@ -115,8 +115,8 @@ data: "reportingTasks": [], "flowAnalysisRules": [], "rootGroup": { - "identifier": "a0c74b17-4386-3e84-8de4-6cefe4328565", - "instanceIdentifier": "243351c2-0196-1000-9730-38306d34ae6d", + "identifier": "a0cca604-51c7-32b2-9529-72ec3566e93c", + "instanceIdentifier": "f7cda0ce-0196-1000-30b6-0f5577f07b63", "name": "NiFi Flow", "comments": "", "position": { @@ -127,24 +127,21 @@ data: "remoteProcessGroups": [], "processors": [ { - "identifier": "a36b7e9b-c90a-3fad-9a53-53450ff2efd7", - "instanceIdentifier": "25736232-0196-1000-0000-00003edfe17e", - "name": "HandleHttpResponse", + "identifier": "238c6f6f-9e6b-3faf-bcdc-7058c9473c76", + "instanceIdentifier": "f80dee83-0196-1000-0000-000051a29aa3", + "name": "ShoutProcessor", "comments": "", "position": { - "x": -248.0, - "y": -24.0 + "x": -112.0, + "y": 96.0 }, - "type": "org.apache.nifi.processors.standard.HandleHttpResponse", + "type": "tech.stackable.nifi.processors.sample.ShoutProcessor", "bundle": { - "group": "org.apache.nifi", - "artifact": "nifi-standard-nar", - "version": "2.2.0" - }, - "properties": { - "HTTP Context Map": "2572dfc2-0196-1000-ffff-ffffb3493dd1", - "HTTP Status Code": "200" + "group": "tech.stackable.nifi", + "artifact": "nifi-sample-nar", + "version": "1.0.0" }, + "properties": {}, "propertyDescriptors": {}, "style": {}, "schedulingPeriod": "0 sec", @@ -155,52 +152,31 @@ data: "bulletinLevel": "WARN", "runDurationMillis": 0, "concurrentlySchedulableTaskCount": 1, - "autoTerminatedRelationships": [ - "success", - "failure" - ], + "autoTerminatedRelationships": [], "scheduledState": "RUNNING", "retryCount": 10, "retriedRelationships": [], "backoffMechanism": "PENALIZE_FLOWFILE", "maxBackoffPeriod": "10 mins", "componentType": "PROCESSOR", - "groupIdentifier": "a0c74b17-4386-3e84-8de4-6cefe4328565" + "groupIdentifier": "a0cca604-51c7-32b2-9529-72ec3566e93c" }, { - "identifier": "c331dc17-5ded-398b-bce0-2a96fa6d8005", - "instanceIdentifier": "257286eb-0196-1000-ffff-ffffcfa02e60", - "name": "HandleHttpRequest", + "identifier": "1975618d-36bc-32b4-9d14-06a14d4dcba0", + "instanceIdentifier": "f80d8c53-0196-1000-ffff-ffffb40a54f8", + "name": "Greet", "comments": "", "position": { - "x": -248.0, - "y": -664.0 + "x": -112.0, + "y": -128.0 }, - "type": "org.apache.nifi.processors.standard.HandleHttpRequest", + "type": "Greet", "bundle": { "group": "org.apache.nifi", - "artifact": "nifi-standard-nar", - "version": "2.2.0" - }, - "properties": { - "multipart-request-max-size": "1 MB", - "Allow POST": "false", - "Default URL Character Set": "UTF-8", - "Allow DELETE": "false", - "Request Header Maximum Size": "8 KB", - "Maximum Threads": "200", - "HTTP Protocols": "HTTP_1_1", - "container-queue-size": "50", - "HTTP Context Map": "2572dfc2-0196-1000-ffff-ffffb3493dd1", - "multipart-read-buffer-size": "512 KB", - "Allow OPTIONS": "false", - "Allowed Paths": "/greeting", - "Allow GET": "true", - "Allow HEAD": "false", - "Listening Port": "8090", - "Client Authentication": "No Authentication", - "Allow PUT": "false" + "artifact": "python-extensions", + "version": "1.0.0" }, + "properties": {}, "propertyDescriptors": {}, "style": {}, "schedulingPeriod": "0 sec", @@ -209,33 +185,39 @@ data: "penaltyDuration": "30 sec", "yieldDuration": "1 sec", "bulletinLevel": "WARN", - "runDurationMillis": 0, + "runDurationMillis": 25, "concurrentlySchedulableTaskCount": 1, - "autoTerminatedRelationships": [], + "autoTerminatedRelationships": [ + "original", + "failure" + ], "scheduledState": "RUNNING", "retryCount": 10, "retriedRelationships": [], "backoffMechanism": "PENALIZE_FLOWFILE", "maxBackoffPeriod": "10 mins", "componentType": "PROCESSOR", - "groupIdentifier": "a0c74b17-4386-3e84-8de4-6cefe4328565" + "groupIdentifier": "a0cca604-51c7-32b2-9529-72ec3566e93c" }, { - "identifier": "aa2534ff-9199-3e7e-9e38-9a974864599f", - "instanceIdentifier": "25731de4-0196-1000-ffff-ffffdac9784b", - "name": "Shout", + "identifier": "0b7338da-d770-365b-bff5-bba3f0236229", + "instanceIdentifier": "f80e3b3f-0196-1000-0000-00004590be6a", + "name": "HandleHttpResponse", "comments": "", "position": { - "x": -248.0, - "y": -240.0 + "x": -112.0, + "y": 320.0 }, - "type": "Shout", + "type": "org.apache.nifi.processors.standard.HandleHttpResponse", "bundle": { "group": "org.apache.nifi", - "artifact": "python-extensions", - "version": "1.0.0" + "artifact": "nifi-standard-nar", + "version": "2.2.0" + }, + "properties": { + "HTTP Context Map": "f80ef95f-0196-1000-ffff-ffffabf564cb", + "HTTP Status Code": "200" }, - "properties": {}, "propertyDescriptors": {}, "style": {}, "schedulingPeriod": "0 sec", @@ -244,10 +226,10 @@ data: "penaltyDuration": "30 sec", "yieldDuration": "1 sec", "bulletinLevel": "WARN", - "runDurationMillis": 25, + "runDurationMillis": 0, "concurrentlySchedulableTaskCount": 1, "autoTerminatedRelationships": [ - "original", + "success", "failure" ], "scheduledState": "RUNNING", @@ -256,24 +238,42 @@ data: "backoffMechanism": "PENALIZE_FLOWFILE", "maxBackoffPeriod": "10 mins", "componentType": "PROCESSOR", - "groupIdentifier": "a0c74b17-4386-3e84-8de4-6cefe4328565" + "groupIdentifier": "a0cca604-51c7-32b2-9529-72ec3566e93c" }, { - "identifier": "d897a9e1-fb76-3cfe-93af-9cb7fa07a307", - "instanceIdentifier": "25723295-0196-1000-0000-0000467c4e68", - "name": "Greet", + "identifier": "522d46b7-8c43-35b7-ab91-2e5150e44e5b", + "instanceIdentifier": "f809987c-0196-1000-ffff-fffff71b58df", + "name": "HandleHttpRequest", "comments": "", "position": { - "x": -248.0, - "y": -448.0 + "x": -112.0, + "y": -352.0 }, - "type": "Greet", + "type": "org.apache.nifi.processors.standard.HandleHttpRequest", "bundle": { "group": "org.apache.nifi", - "artifact": "python-extensions", - "version": "1.0.0" + "artifact": "nifi-standard-nar", + "version": "2.2.0" + }, + "properties": { + "multipart-request-max-size": "1 MB", + "Allow POST": "false", + "Default URL Character Set": "UTF-8", + "Allow DELETE": "false", + "Request Header Maximum Size": "8 KB", + "Maximum Threads": "200", + "HTTP Protocols": "HTTP_1_1", + "container-queue-size": "50", + "HTTP Context Map": "f80ef95f-0196-1000-ffff-ffffabf564cb", + "multipart-read-buffer-size": "512 KB", + "Allow OPTIONS": "false", + "Allowed Paths": "/greeting", + "Allow GET": "true", + "Allow HEAD": "false", + "Listening Port": "8090", + "Client Authentication": "No Authentication", + "Allow PUT": "false" }, - "properties": {}, "propertyDescriptors": {}, "style": {}, "schedulingPeriod": "0 sec", @@ -282,43 +282,40 @@ data: "penaltyDuration": "30 sec", "yieldDuration": "1 sec", "bulletinLevel": "WARN", - "runDurationMillis": 25, + "runDurationMillis": 0, "concurrentlySchedulableTaskCount": 1, - "autoTerminatedRelationships": [ - "original", - "failure" - ], + "autoTerminatedRelationships": [], "scheduledState": "RUNNING", "retryCount": 10, "retriedRelationships": [], "backoffMechanism": "PENALIZE_FLOWFILE", "maxBackoffPeriod": "10 mins", "componentType": "PROCESSOR", - "groupIdentifier": "a0c74b17-4386-3e84-8de4-6cefe4328565" + "groupIdentifier": "a0cca604-51c7-32b2-9529-72ec3566e93c" } ], "inputPorts": [], "outputPorts": [], "connections": [ { - "identifier": "0cc1ce67-1998-3338-8777-7b2b4dbc37f8", - "instanceIdentifier": "25729ba3-0196-1000-0000-0000198b702d", + "identifier": "5f7beeba-4cdc-3099-aef6-96ced3560ecc", + "instanceIdentifier": "f80e0681-0196-1000-ffff-ffffc59964fa", "name": "", "source": { - "id": "c331dc17-5ded-398b-bce0-2a96fa6d8005", + "id": "1975618d-36bc-32b4-9d14-06a14d4dcba0", "type": "PROCESSOR", - "groupId": "a0c74b17-4386-3e84-8de4-6cefe4328565", - "name": "HandleHttpRequest", + "groupId": "a0cca604-51c7-32b2-9529-72ec3566e93c", + "name": "Greet", "comments": "", - "instanceIdentifier": "257286eb-0196-1000-ffff-ffffcfa02e60" + "instanceIdentifier": "f80d8c53-0196-1000-ffff-ffffb40a54f8" }, "destination": { - "id": "d897a9e1-fb76-3cfe-93af-9cb7fa07a307", + "id": "238c6f6f-9e6b-3faf-bcdc-7058c9473c76", "type": "PROCESSOR", - "groupId": "a0c74b17-4386-3e84-8de4-6cefe4328565", - "name": "Greet", + "groupId": "a0cca604-51c7-32b2-9529-72ec3566e93c", + "name": "ShoutProcessor", "comments": "", - "instanceIdentifier": "25723295-0196-1000-0000-0000467c4e68" + "instanceIdentifier": "f80dee83-0196-1000-0000-000051a29aa3" }, "labelIndex": 0, "zIndex": 0, @@ -334,27 +331,27 @@ data: "partitioningAttribute": "", "loadBalanceCompression": "DO_NOT_COMPRESS", "componentType": "CONNECTION", - "groupIdentifier": "a0c74b17-4386-3e84-8de4-6cefe4328565" + "groupIdentifier": "a0cca604-51c7-32b2-9529-72ec3566e93c" }, { - "identifier": "daccc7eb-0ed3-3bc8-89a9-942a6f21608e", - "instanceIdentifier": "2573957c-0196-1000-ffff-ffffaad0c8c0", + "identifier": "5165254a-e18c-3ccb-b8cb-25c55d7151a7", + "instanceIdentifier": "f80e54a1-0196-1000-ffff-ffffa1c1bed7", "name": "", "source": { - "id": "aa2534ff-9199-3e7e-9e38-9a974864599f", + "id": "238c6f6f-9e6b-3faf-bcdc-7058c9473c76", "type": "PROCESSOR", - "groupId": "a0c74b17-4386-3e84-8de4-6cefe4328565", - "name": "Shout", + "groupId": "a0cca604-51c7-32b2-9529-72ec3566e93c", + "name": "ShoutProcessor", "comments": "", - "instanceIdentifier": "25731de4-0196-1000-ffff-ffffdac9784b" + "instanceIdentifier": "f80dee83-0196-1000-0000-000051a29aa3" }, "destination": { - "id": "a36b7e9b-c90a-3fad-9a53-53450ff2efd7", + "id": "0b7338da-d770-365b-bff5-bba3f0236229", "type": "PROCESSOR", - "groupId": "a0c74b17-4386-3e84-8de4-6cefe4328565", + "groupId": "a0cca604-51c7-32b2-9529-72ec3566e93c", "name": "HandleHttpResponse", "comments": "", - "instanceIdentifier": "25736232-0196-1000-0000-00003edfe17e" + "instanceIdentifier": "f80e3b3f-0196-1000-0000-00004590be6a" }, "labelIndex": 0, "zIndex": 0, @@ -370,27 +367,27 @@ data: "partitioningAttribute": "", "loadBalanceCompression": "DO_NOT_COMPRESS", "componentType": "CONNECTION", - "groupIdentifier": "a0c74b17-4386-3e84-8de4-6cefe4328565" + "groupIdentifier": "a0cca604-51c7-32b2-9529-72ec3566e93c" }, { - "identifier": "3f4602ba-25b1-37d3-b377-d734750e49ca", - "instanceIdentifier": "25733c86-0196-1000-0000-0000142bd2e3", + "identifier": "ad7fc77b-6b4b-3750-8e22-fcbb228a6fb4", + "instanceIdentifier": "f80dbacc-0196-1000-0000-0000274b2561", "name": "", "source": { - "id": "d897a9e1-fb76-3cfe-93af-9cb7fa07a307", + "id": "522d46b7-8c43-35b7-ab91-2e5150e44e5b", "type": "PROCESSOR", - "groupId": "a0c74b17-4386-3e84-8de4-6cefe4328565", - "name": "Greet", + "groupId": "a0cca604-51c7-32b2-9529-72ec3566e93c", + "name": "HandleHttpRequest", "comments": "", - "instanceIdentifier": "25723295-0196-1000-0000-0000467c4e68" + "instanceIdentifier": "f809987c-0196-1000-ffff-fffff71b58df" }, "destination": { - "id": "aa2534ff-9199-3e7e-9e38-9a974864599f", + "id": "1975618d-36bc-32b4-9d14-06a14d4dcba0", "type": "PROCESSOR", - "groupId": "a0c74b17-4386-3e84-8de4-6cefe4328565", - "name": "Shout", + "groupId": "a0cca604-51c7-32b2-9529-72ec3566e93c", + "name": "Greet", "comments": "", - "instanceIdentifier": "25731de4-0196-1000-ffff-ffffdac9784b" + "instanceIdentifier": "f80d8c53-0196-1000-ffff-ffffb40a54f8" }, "labelIndex": 0, "zIndex": 0, @@ -406,15 +403,15 @@ data: "partitioningAttribute": "", "loadBalanceCompression": "DO_NOT_COMPRESS", "componentType": "CONNECTION", - "groupIdentifier": "a0c74b17-4386-3e84-8de4-6cefe4328565" + "groupIdentifier": "a0cca604-51c7-32b2-9529-72ec3566e93c" } ], "labels": [], "funnels": [], "controllerServices": [ { - "identifier": "1989ed4e-5edf-3df1-a1a1-6ed4a5c49d7f", - "instanceIdentifier": "2572dfc2-0196-1000-ffff-ffffb3493dd1", + "identifier": "68712a48-5a26-3bee-8bfc-dafafda62660", + "instanceIdentifier": "f80ef95f-0196-1000-ffff-ffffabf564cb", "name": "StandardHttpContextMap", "comments": "", "type": "org.apache.nifi.http.StandardHttpContextMap", @@ -441,7 +438,7 @@ data: "scheduledState": "ENABLED", "bulletinLevel": "WARN", "componentType": "CONTROLLER_SERVICE", - "groupIdentifier": "a0c74b17-4386-3e84-8de4-6cefe4328565" + "groupIdentifier": "a0cca604-51c7-32b2-9529-72ec3566e93c" } ], "defaultFlowFileExpiration": "0 sec", diff --git a/tests/templates/kuttl/custom-processors-git-sync/README.md b/tests/templates/kuttl/custom-processors-git-sync/README.md new file mode 100644 index 00000000..31d24886 --- /dev/null +++ b/tests/templates/kuttl/custom-processors-git-sync/README.md @@ -0,0 +1,29 @@ +# Overview + +This test case configures the Nifi cluster to fetch Python and Java +processors via git-sync. A flow is deployed that include these +processors. The test job triggers the flow and checks the result. + +# Custom processors + +## Python processor "Greet" + +The Greet processor is implemented in Python and answers a request with +the content "Hello!". + +## Java processor "Shout" + +The Shout processor is implemented in Java and transforms the received +content to uppercase. + +# Flow + +![NiFi canvas](./canvas.png) + +1. The HandleHttpRequest processor listens for HTTP requests to + `GET /greeting`. The request is forwarded to the Greet processor. +2. The Greet processor writes the content "Hello!" to the flow file that + is then forwarded to the Shout processor. +3. The Shout processor transforms the content to uppercase. The flow + file is forwarded to the HandleHttpResponse processor. +4. The HandleHttpResponse answers the HTTP request with this content. diff --git a/tests/templates/kuttl/custom-processors-git-sync/canvas.png b/tests/templates/kuttl/custom-processors-git-sync/canvas.png new file mode 100644 index 0000000000000000000000000000000000000000..e6872596401bf2eb056dc6d05406bbb2926c1db4 GIT binary patch literal 159875 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 Cw-DX{ literal 0 HcmV?d00001 From 1123388eb2ab6740ac177b9c3121758892c451ee Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Thu, 22 May 2025 16:39:15 +0200 Subject: [PATCH 08/24] Rename "custom processors" to "custom components" --- CHANGELOG.md | 2 +- rust/operator-binary/src/controller.rs | 2 +- rust/operator-binary/src/crd/mod.rs | 6 +++--- .../00-patch-ns.yaml.j2 | 0 .../10-assert.yaml.j2 | 0 ...ll-vector-aggregator-discovery-configmap.yaml.j2 | 0 .../20-assert.yaml | 0 .../20-install-zk.yaml.j2 | 0 .../30-assert.yaml | 0 .../30-install-nifi.yaml.j2 | 0 .../40-assert.yaml | 0 .../40-test-nifi-greeting.yaml | 0 .../README.md | 0 .../canvas.png | Bin .../java-processors/nifi-sample-nar-1.0.0.nar | Bin .../java-processors/sample-processor.tar.gz | Bin .../python-processors/greet_processor.py | 0 tests/test-definition.yaml | 2 +- 18 files changed, 6 insertions(+), 6 deletions(-) rename tests/templates/kuttl/{custom-processors-git-sync => custom-components-git-sync}/00-patch-ns.yaml.j2 (100%) rename tests/templates/kuttl/{custom-processors-git-sync => custom-components-git-sync}/10-assert.yaml.j2 (100%) rename tests/templates/kuttl/{custom-processors-git-sync => custom-components-git-sync}/10-install-vector-aggregator-discovery-configmap.yaml.j2 (100%) rename tests/templates/kuttl/{custom-processors-git-sync => custom-components-git-sync}/20-assert.yaml (100%) rename tests/templates/kuttl/{custom-processors-git-sync => custom-components-git-sync}/20-install-zk.yaml.j2 (100%) rename tests/templates/kuttl/{custom-processors-git-sync => custom-components-git-sync}/30-assert.yaml (100%) rename tests/templates/kuttl/{custom-processors-git-sync => custom-components-git-sync}/30-install-nifi.yaml.j2 (100%) rename tests/templates/kuttl/{custom-processors-git-sync => custom-components-git-sync}/40-assert.yaml (100%) rename tests/templates/kuttl/{custom-processors-git-sync => custom-components-git-sync}/40-test-nifi-greeting.yaml (100%) rename tests/templates/kuttl/{custom-processors-git-sync => custom-components-git-sync}/README.md (100%) rename tests/templates/kuttl/{custom-processors-git-sync => custom-components-git-sync}/canvas.png (100%) rename tests/templates/kuttl/{custom-processors-git-sync => custom-components-git-sync}/java-processors/nifi-sample-nar-1.0.0.nar (100%) rename tests/templates/kuttl/{custom-processors-git-sync => custom-components-git-sync}/java-processors/sample-processor.tar.gz (100%) rename tests/templates/kuttl/{custom-processors-git-sync => custom-components-git-sync}/python-processors/greet_processor.py (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 56d92c40..5ca99b25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ All notable changes to this project will be documented in this file. - Use `--file-log-max-files` (or `FILE_LOG_MAX_FILES`) to limit the number of log files kept. - Use `--file-log-rotation-period` (or `FILE_LOG_ROTATION_PERIOD`) to configure the frequency of rotation. - Use `--console-log-format` (or `CONSOLE_LOG_FORMAT`) to set the format to `plain` (default) or `json`. -- Add support for custom Python processors via git-sync ([#793]). +- Add support for custom components via git-sync ([#793]). ### Changed diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index e3943303..1352eaeb 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -506,7 +506,7 @@ pub async fn reconcile_nifi( .context(FailedToResolveConfigSnafu)?; let git_sync_resources = git_sync::v1alpha1::GitSyncResources::new( - &nifi.spec.cluster_config.custom_processors_git_sync, + &nifi.spec.cluster_config.custom_components_git_sync, &resolved_product_image, &env_vars_from_rolegroup_config(rolegroup_config), &[], diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index 11a7deb6..2f6b6d8c 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -144,11 +144,11 @@ pub mod versioned { /// to deploy a ZooKeeper cluster, this will simply be the name of your ZookeeperCluster resource. pub zookeeper_config_map_name: String, - /// The `gitSync` settings allow configuring Python processors to mount via `git-sync`. + /// The `customComponentsGitSync` setting allows configuring custom components to mount via `git-sync`. /// Learn more in the - /// [mounting DAGs documentation](DOCS_BASE_URL_PLACEHOLDER/nifi/usage-guide/mounting-processors#_via_git_sync). + /// [Custom Python processors documentation](DOCS_BASE_URL_PLACEHOLDER/nifi/usage_guide/custom-components/custom-python-processors/#git-sync). #[serde(default)] - pub custom_processors_git_sync: Vec, + pub custom_components_git_sync: Vec, /// Extra volumes similar to `.spec.volumes` on a Pod to mount into every container, this can be useful to for /// example make client certificates, keytabs or similar things available to processors. These volumes will be diff --git a/tests/templates/kuttl/custom-processors-git-sync/00-patch-ns.yaml.j2 b/tests/templates/kuttl/custom-components-git-sync/00-patch-ns.yaml.j2 similarity index 100% rename from tests/templates/kuttl/custom-processors-git-sync/00-patch-ns.yaml.j2 rename to tests/templates/kuttl/custom-components-git-sync/00-patch-ns.yaml.j2 diff --git a/tests/templates/kuttl/custom-processors-git-sync/10-assert.yaml.j2 b/tests/templates/kuttl/custom-components-git-sync/10-assert.yaml.j2 similarity index 100% rename from tests/templates/kuttl/custom-processors-git-sync/10-assert.yaml.j2 rename to tests/templates/kuttl/custom-components-git-sync/10-assert.yaml.j2 diff --git a/tests/templates/kuttl/custom-processors-git-sync/10-install-vector-aggregator-discovery-configmap.yaml.j2 b/tests/templates/kuttl/custom-components-git-sync/10-install-vector-aggregator-discovery-configmap.yaml.j2 similarity index 100% rename from tests/templates/kuttl/custom-processors-git-sync/10-install-vector-aggregator-discovery-configmap.yaml.j2 rename to tests/templates/kuttl/custom-components-git-sync/10-install-vector-aggregator-discovery-configmap.yaml.j2 diff --git a/tests/templates/kuttl/custom-processors-git-sync/20-assert.yaml b/tests/templates/kuttl/custom-components-git-sync/20-assert.yaml similarity index 100% rename from tests/templates/kuttl/custom-processors-git-sync/20-assert.yaml rename to tests/templates/kuttl/custom-components-git-sync/20-assert.yaml diff --git a/tests/templates/kuttl/custom-processors-git-sync/20-install-zk.yaml.j2 b/tests/templates/kuttl/custom-components-git-sync/20-install-zk.yaml.j2 similarity index 100% rename from tests/templates/kuttl/custom-processors-git-sync/20-install-zk.yaml.j2 rename to tests/templates/kuttl/custom-components-git-sync/20-install-zk.yaml.j2 diff --git a/tests/templates/kuttl/custom-processors-git-sync/30-assert.yaml b/tests/templates/kuttl/custom-components-git-sync/30-assert.yaml similarity index 100% rename from tests/templates/kuttl/custom-processors-git-sync/30-assert.yaml rename to tests/templates/kuttl/custom-components-git-sync/30-assert.yaml diff --git a/tests/templates/kuttl/custom-processors-git-sync/30-install-nifi.yaml.j2 b/tests/templates/kuttl/custom-components-git-sync/30-install-nifi.yaml.j2 similarity index 100% rename from tests/templates/kuttl/custom-processors-git-sync/30-install-nifi.yaml.j2 rename to tests/templates/kuttl/custom-components-git-sync/30-install-nifi.yaml.j2 diff --git a/tests/templates/kuttl/custom-processors-git-sync/40-assert.yaml b/tests/templates/kuttl/custom-components-git-sync/40-assert.yaml similarity index 100% rename from tests/templates/kuttl/custom-processors-git-sync/40-assert.yaml rename to tests/templates/kuttl/custom-components-git-sync/40-assert.yaml diff --git a/tests/templates/kuttl/custom-processors-git-sync/40-test-nifi-greeting.yaml b/tests/templates/kuttl/custom-components-git-sync/40-test-nifi-greeting.yaml similarity index 100% rename from tests/templates/kuttl/custom-processors-git-sync/40-test-nifi-greeting.yaml rename to tests/templates/kuttl/custom-components-git-sync/40-test-nifi-greeting.yaml diff --git a/tests/templates/kuttl/custom-processors-git-sync/README.md b/tests/templates/kuttl/custom-components-git-sync/README.md similarity index 100% rename from tests/templates/kuttl/custom-processors-git-sync/README.md rename to tests/templates/kuttl/custom-components-git-sync/README.md diff --git a/tests/templates/kuttl/custom-processors-git-sync/canvas.png b/tests/templates/kuttl/custom-components-git-sync/canvas.png similarity index 100% rename from tests/templates/kuttl/custom-processors-git-sync/canvas.png rename to tests/templates/kuttl/custom-components-git-sync/canvas.png diff --git a/tests/templates/kuttl/custom-processors-git-sync/java-processors/nifi-sample-nar-1.0.0.nar b/tests/templates/kuttl/custom-components-git-sync/java-processors/nifi-sample-nar-1.0.0.nar similarity index 100% rename from tests/templates/kuttl/custom-processors-git-sync/java-processors/nifi-sample-nar-1.0.0.nar rename to tests/templates/kuttl/custom-components-git-sync/java-processors/nifi-sample-nar-1.0.0.nar diff --git a/tests/templates/kuttl/custom-processors-git-sync/java-processors/sample-processor.tar.gz b/tests/templates/kuttl/custom-components-git-sync/java-processors/sample-processor.tar.gz similarity index 100% rename from tests/templates/kuttl/custom-processors-git-sync/java-processors/sample-processor.tar.gz rename to tests/templates/kuttl/custom-components-git-sync/java-processors/sample-processor.tar.gz diff --git a/tests/templates/kuttl/custom-processors-git-sync/python-processors/greet_processor.py b/tests/templates/kuttl/custom-components-git-sync/python-processors/greet_processor.py similarity index 100% rename from tests/templates/kuttl/custom-processors-git-sync/python-processors/greet_processor.py rename to tests/templates/kuttl/custom-components-git-sync/python-processors/greet_processor.py diff --git a/tests/test-definition.yaml b/tests/test-definition.yaml index e1dea755..7c9c2bdc 100644 --- a/tests/test-definition.yaml +++ b/tests/test-definition.yaml @@ -90,7 +90,7 @@ tests: - zookeeper-latest - oidc-use-tls - openshift - - name: custom-processors-git-sync + - name: custom-components-git-sync dimensions: - nifi-latest - zookeeper-latest From a41b4497916ac5681281e1ef157d88de68f5f8ef Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Fri, 23 May 2025 13:41:35 +0200 Subject: [PATCH 09/24] Use fully qualified types for external error sources --- rust/operator-binary/src/crd/authentication.rs | 4 +++- rust/operator-binary/src/security/authentication.rs | 4 +++- rust/operator-binary/src/security/oidc.rs | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/rust/operator-binary/src/crd/authentication.rs b/rust/operator-binary/src/crd/authentication.rs index 987e9a7f..561e5cfc 100644 --- a/rust/operator-binary/src/crd/authentication.rs +++ b/rust/operator-binary/src/crd/authentication.rs @@ -42,7 +42,9 @@ pub enum Error { }, #[snafu(display("invalid OIDC configuration"))] - OidcConfigurationInvalid { source: auth_core::v1alpha1::Error }, + OidcConfigurationInvalid { + source: stackable_operator::crd::authentication::core::v1alpha1::Error, + }, } type Result = std::result::Result; diff --git a/rust/operator-binary/src/security/authentication.rs b/rust/operator-binary/src/security/authentication.rs index abbbd4f5..4f6633c0 100644 --- a/rust/operator-binary/src/security/authentication.rs +++ b/rust/operator-binary/src/security/authentication.rs @@ -37,7 +37,9 @@ pub enum Error { }, #[snafu(display("Failed to add LDAP volumes and volumeMounts to the Pod and containers"))] - AddLdapVolumes { source: ldap::v1alpha1::Error }, + AddLdapVolumes { + source: stackable_operator::crd::authentication::ldap::v1alpha1::Error, + }, #[snafu(display("Failed to add OIDC volumes and volumeMounts to the Pod and containers"))] AddOidcVolumes { diff --git a/rust/operator-binary/src/security/oidc.rs b/rust/operator-binary/src/security/oidc.rs index b7897d43..038fb28b 100644 --- a/rust/operator-binary/src/security/oidc.rs +++ b/rust/operator-binary/src/security/oidc.rs @@ -31,7 +31,9 @@ pub enum Error { MissingAdminPasswordKey { secret: ObjectRef }, #[snafu(display("invalid well-known OIDC configuration URL"))] - InvalidWellKnownConfigUrl { source: oidc::v1alpha1::Error }, + InvalidWellKnownConfigUrl { + source: stackable_operator::crd::authentication::oidc::v1alpha1::Error, + }, #[snafu(display("Nifi doesn't support skipping the OIDC TLS verification"))] SkippingTlsVerificationNotSupported {}, From a72505392112d026b2748108d6511ba40033b6a5 Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Fri, 23 May 2025 14:54:51 +0200 Subject: [PATCH 10/24] test: Recompile the Java processor with Java 11 --- .../java-processors/nifi-sample-nar-1.0.0.nar | Bin 125767 -> 125771 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/templates/kuttl/custom-components-git-sync/java-processors/nifi-sample-nar-1.0.0.nar b/tests/templates/kuttl/custom-components-git-sync/java-processors/nifi-sample-nar-1.0.0.nar index d9e7f9037ce310b7b38794cc78ad64a7b374773c..bd203bf8cc3cf39ca4a4ea2d601cf84a0416ed69 100644 GIT binary patch delta 7758 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 delta 7755 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 From 9166242ce49d2ce7386d7f290dafac862499d43b Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Fri, 23 May 2025 15:41:25 +0200 Subject: [PATCH 11/24] test: Test customComponentsGitSync also for NiFi 1.x --- .../30-install-nifi.yaml.j2 | 408 +++++++++++++++++- .../custom-components-git-sync/README.md | 9 +- .../java-processors/sample-processor.tar.gz | Bin 4087 -> 2200 bytes tests/test-definition.yaml | 2 +- 4 files changed, 408 insertions(+), 11 deletions(-) diff --git a/tests/templates/kuttl/custom-components-git-sync/30-install-nifi.yaml.j2 b/tests/templates/kuttl/custom-components-git-sync/30-install-nifi.yaml.j2 index 02f9e78b..fafa01c1 100644 --- a/tests/templates/kuttl/custom-components-git-sync/30-install-nifi.yaml.j2 +++ b/tests/templates/kuttl/custom-components-git-sync/30-install-nifi.yaml.j2 @@ -22,11 +22,11 @@ metadata: name: test-nifi spec: image: -{% if test_scenario['values']['nifi-latest'].find(",") > 0 %} - custom: "{{ test_scenario['values']['nifi-latest'].split(',')[1] }}" - productVersion: "{{ test_scenario['values']['nifi-latest'].split(',')[0] }}" +{% if test_scenario['values']['nifi'].find(",") > 0 %} + custom: "{{ test_scenario['values']['nifi'].split(',')[1] }}" + productVersion: "{{ test_scenario['values']['nifi'].split(',')[0] }}" {% else %} - productVersion: "{{ test_scenario['values']['nifi-latest'] }}" + productVersion: "{{ test_scenario['values']['nifi'] }}" {% endif %} pullPolicy: IfNotPresent clusterConfig: @@ -37,13 +37,13 @@ spec: sensitiveProperties: keySecret: nifi-sensitive-property-key autoGenerate: true - customProcessorsGitSync: + customComponentsGitSync: - repo: https://github.com/stackabletech/nifi-operator branch: feat/custom-processors # TODO Change to commit - gitFolder: tests/templates/kuttl/custom-processors-git-sync/java-processors + gitFolder: tests/templates/kuttl/custom-components-git-sync/java-processors - repo: https://github.com/stackabletech/nifi-operator branch: feat/custom-processors # TODO Change to commit - gitFolder: tests/templates/kuttl/custom-processors-git-sync/python-processors + gitFolder: tests/templates/kuttl/custom-components-git-sync/python-processors {% if lookup('env', 'VECTOR_AGGREGATOR') %} vectorAggregatorConfigMapName: vector-aggregator-discovery {% endif %} @@ -75,7 +75,11 @@ spec: volumes: - name: nifi-flow configMap: - name: nifi-flow +{% if test_scenario['values']['nifi'].startswith("1.") %} + name: nifi1-flow +{% else %} + name: nifi2-flow +{% endif %} roleGroups: default: replicas: 2 @@ -99,7 +103,393 @@ spec: apiVersion: v1 kind: ConfigMap metadata: - name: nifi-flow + name: nifi1-flow +data: + flow.json: | + { + "encodingVersion": { + "majorVersion": 2, + "minorVersion": 0 + }, + "maxTimerDrivenThreadCount": 10, + "maxEventDrivenThreadCount": 1, + "registries": [], + "parameterContexts": [], + "parameterProviders": [], + "controllerServices": [], + "reportingTasks": [ + { + "identifier": "fd3cf892-0196-1000-0000-000074a555a2", + "instanceIdentifier": "fd3cf892-0196-1000-0000-000074a555a2", + "name": "StackablePrometheusReportingTask", + "type": "org.apache.nifi.reporting.prometheus.PrometheusReportingTask", + "bundle": { + "group": "org.apache.nifi", + "artifact": "nifi-prometheus-nar", + "version": "1.28.1" + }, + "properties": { + "prometheus-reporting-task-metrics-endpoint-port": "8081", + "prometheus-reporting-task-metrics-strategy": "All Components", + "prometheus-reporting-task-instance-id": "${hostname(true)}", + "prometheus-reporting-task-client-auth": "No Authentication", + "prometheus-reporting-task-metrics-send-jvm": "true" + }, + "propertyDescriptors": {}, + "scheduledState": "RUNNING", + "schedulingPeriod": "60 sec", + "schedulingStrategy": "TIMER_DRIVEN", + "componentType": "REPORTING_TASK" + } + ], + "templates": [], + "rootGroup": { + "identifier": "b4fe1571-02fd-3351-a2c0-e92f420b3ce9", + "instanceIdentifier": "fd37b89e-0196-1000-9750-8b45eabee468", + "name": "NiFi Flow", + "comments": "", + "position": { + "x": 0.0, + "y": 0.0 + }, + "processGroups": [], + "remoteProcessGroups": [], + "processors": [ + { + "identifier": "b34e789e-1a70-3275-b5c5-f5856b628166", + "instanceIdentifier": "fd432952-0196-1000-0000-000048f10556", + "name": "Greet", + "comments": "", + "position": { + "x": 800.0, + "y": 304.0 + }, + "type": "org.apache.nifi.processors.standard.ReplaceText", + "bundle": { + "group": "org.apache.nifi", + "artifact": "nifi-standard-nar", + "version": "1.28.1" + }, + "properties": { + "Regular Expression": "(?s)(^.*$)", + "Replacement Value": "Hello!", + "Evaluation Mode": "Entire text", + "Line-by-Line Evaluation Mode": "All", + "Character Set": "UTF-8", + "Maximum Buffer Size": "1 MB", + "Replacement Strategy": "Always Replace" + }, + "propertyDescriptors": {}, + "style": {}, + "schedulingPeriod": "0 sec", + "schedulingStrategy": "TIMER_DRIVEN", + "executionNode": "ALL", + "penaltyDuration": "30 sec", + "yieldDuration": "1 sec", + "bulletinLevel": "WARN", + "runDurationMillis": 25, + "concurrentlySchedulableTaskCount": 1, + "autoTerminatedRelationships": [ + "failure" + ], + "scheduledState": "RUNNING", + "retryCount": 10, + "retriedRelationships": [], + "backoffMechanism": "PENALIZE_FLOWFILE", + "maxBackoffPeriod": "10 mins", + "componentType": "PROCESSOR", + "groupIdentifier": "b4fe1571-02fd-3351-a2c0-e92f420b3ce9" + }, + { + "identifier": "d15c871b-e9c5-38bd-903b-6bf3a9c55765", + "instanceIdentifier": "fd45c24b-0196-1000-0000-00001a549d74", + "name": "HandleHttpResponse", + "comments": "", + "position": { + "x": 800.0, + "y": 752.0 + }, + "type": "org.apache.nifi.processors.standard.HandleHttpResponse", + "bundle": { + "group": "org.apache.nifi", + "artifact": "nifi-standard-nar", + "version": "1.28.1" + }, + "properties": { + "HTTP Context Map": "fd3b17c4-0196-1000-0000-000076ed786c", + "HTTP Status Code": "200" + }, + "propertyDescriptors": {}, + "style": {}, + "schedulingPeriod": "0 sec", + "schedulingStrategy": "TIMER_DRIVEN", + "executionNode": "ALL", + "penaltyDuration": "30 sec", + "yieldDuration": "1 sec", + "bulletinLevel": "WARN", + "runDurationMillis": 0, + "concurrentlySchedulableTaskCount": 1, + "autoTerminatedRelationships": [ + "success", + "failure" + ], + "scheduledState": "RUNNING", + "retryCount": 10, + "retriedRelationships": [], + "backoffMechanism": "PENALIZE_FLOWFILE", + "maxBackoffPeriod": "10 mins", + "componentType": "PROCESSOR", + "groupIdentifier": "b4fe1571-02fd-3351-a2c0-e92f420b3ce9" + }, + { + "identifier": "35d73904-95e1-3dae-81a7-56d0f907ad5c", + "instanceIdentifier": "fd4557bd-0196-1000-ffff-ffffb0a46ce1", + "name": "ShoutProcessor", + "comments": "", + "position": { + "x": 800.0, + "y": 528.0 + }, + "type": "tech.stackable.nifi.processors.sample.ShoutProcessor", + "bundle": { + "group": "tech.stackable.nifi", + "artifact": "nifi-sample-nar", + "version": "1.0.0" + }, + "properties": {}, + "propertyDescriptors": {}, + "style": {}, + "schedulingPeriod": "0 sec", + "schedulingStrategy": "TIMER_DRIVEN", + "executionNode": "ALL", + "penaltyDuration": "30 sec", + "yieldDuration": "1 sec", + "bulletinLevel": "WARN", + "runDurationMillis": 0, + "concurrentlySchedulableTaskCount": 1, + "autoTerminatedRelationships": [], + "scheduledState": "RUNNING", + "retryCount": 10, + "retriedRelationships": [], + "backoffMechanism": "PENALIZE_FLOWFILE", + "maxBackoffPeriod": "10 mins", + "componentType": "PROCESSOR", + "groupIdentifier": "b4fe1571-02fd-3351-a2c0-e92f420b3ce9" + }, + { + "identifier": "c5fad835-91fd-31dc-afbf-c37209e03598", + "instanceIdentifier": "fd3a912f-0196-1000-ffff-ffffacaca66e", + "name": "HandleHttpRequest", + "comments": "", + "position": { + "x": 800.0, + "y": 80.0 + }, + "type": "org.apache.nifi.processors.standard.HandleHttpRequest", + "bundle": { + "group": "org.apache.nifi", + "artifact": "nifi-standard-nar", + "version": "1.28.1" + }, + "properties": { + "multipart-request-max-size": "1 MB", + "Allow POST": "false", + "Default URL Character Set": "UTF-8", + "Allow DELETE": "false", + "Maximum Threads": "200", + "HTTP Protocols": "HTTP_1_1", + "container-queue-size": "50", + "HTTP Context Map": "fd3b17c4-0196-1000-0000-000076ed786c", + "multipart-read-buffer-size": "512 KB", + "Allow OPTIONS": "false", + "Allowed Paths": "/greeting", + "Allow GET": "true", + "Allow HEAD": "false", + "Listening Port": "8090", + "Client Authentication": "No Authentication", + "Allow PUT": "false" + }, + "propertyDescriptors": {}, + "style": {}, + "schedulingPeriod": "0 sec", + "schedulingStrategy": "TIMER_DRIVEN", + "executionNode": "ALL", + "penaltyDuration": "30 sec", + "yieldDuration": "1 sec", + "bulletinLevel": "WARN", + "runDurationMillis": 0, + "concurrentlySchedulableTaskCount": 1, + "autoTerminatedRelationships": [], + "scheduledState": "RUNNING", + "retryCount": 10, + "retriedRelationships": [], + "backoffMechanism": "PENALIZE_FLOWFILE", + "maxBackoffPeriod": "10 mins", + "componentType": "PROCESSOR", + "groupIdentifier": "b4fe1571-02fd-3351-a2c0-e92f420b3ce9" + } + ], + "inputPorts": [], + "outputPorts": [], + "connections": [ + { + "identifier": "3cb76499-9b4a-3199-9697-e05fdb86b38e", + "instanceIdentifier": "fd4532b6-0196-1000-ffff-ffffcb96239f", + "name": "", + "source": { + "id": "c5fad835-91fd-31dc-afbf-c37209e03598", + "type": "PROCESSOR", + "groupId": "b4fe1571-02fd-3351-a2c0-e92f420b3ce9", + "name": "HandleHttpRequest", + "comments": "", + "instanceIdentifier": "fd3a912f-0196-1000-ffff-ffffacaca66e" + }, + "destination": { + "id": "b34e789e-1a70-3275-b5c5-f5856b628166", + "type": "PROCESSOR", + "groupId": "b4fe1571-02fd-3351-a2c0-e92f420b3ce9", + "name": "Greet", + "comments": "", + "instanceIdentifier": "fd432952-0196-1000-0000-000048f10556" + }, + "labelIndex": 1, + "zIndex": 0, + "selectedRelationships": [ + "success" + ], + "backPressureObjectThreshold": 10000, + "backPressureDataSizeThreshold": "1 GB", + "flowFileExpiration": "0 sec", + "prioritizers": [], + "bends": [], + "loadBalanceStrategy": "DO_NOT_LOAD_BALANCE", + "partitioningAttribute": "", + "loadBalanceCompression": "DO_NOT_COMPRESS", + "componentType": "CONNECTION", + "groupIdentifier": "b4fe1571-02fd-3351-a2c0-e92f420b3ce9" + }, + { + "identifier": "895aecef-a30b-3ad5-993a-6df611e64e0d", + "instanceIdentifier": "fd459382-0196-1000-0000-00000ad9142c", + "name": "", + "source": { + "id": "b34e789e-1a70-3275-b5c5-f5856b628166", + "type": "PROCESSOR", + "groupId": "b4fe1571-02fd-3351-a2c0-e92f420b3ce9", + "name": "Greet", + "comments": "", + "instanceIdentifier": "fd432952-0196-1000-0000-000048f10556" + }, + "destination": { + "id": "35d73904-95e1-3dae-81a7-56d0f907ad5c", + "type": "PROCESSOR", + "groupId": "b4fe1571-02fd-3351-a2c0-e92f420b3ce9", + "name": "ShoutProcessor", + "comments": "", + "instanceIdentifier": "fd4557bd-0196-1000-ffff-ffffb0a46ce1" + }, + "labelIndex": 1, + "zIndex": 0, + "selectedRelationships": [ + "success" + ], + "backPressureObjectThreshold": 10000, + "backPressureDataSizeThreshold": "1 GB", + "flowFileExpiration": "0 sec", + "prioritizers": [], + "bends": [], + "loadBalanceStrategy": "DO_NOT_LOAD_BALANCE", + "partitioningAttribute": "", + "loadBalanceCompression": "DO_NOT_COMPRESS", + "componentType": "CONNECTION", + "groupIdentifier": "b4fe1571-02fd-3351-a2c0-e92f420b3ce9" + }, + { + "identifier": "47c43ae4-44e5-3e94-9185-bfae5fd6776e", + "instanceIdentifier": "fd45f133-0196-1000-ffff-fffff094098e", + "name": "", + "source": { + "id": "35d73904-95e1-3dae-81a7-56d0f907ad5c", + "type": "PROCESSOR", + "groupId": "b4fe1571-02fd-3351-a2c0-e92f420b3ce9", + "name": "ShoutProcessor", + "comments": "", + "instanceIdentifier": "fd4557bd-0196-1000-ffff-ffffb0a46ce1" + }, + "destination": { + "id": "d15c871b-e9c5-38bd-903b-6bf3a9c55765", + "type": "PROCESSOR", + "groupId": "b4fe1571-02fd-3351-a2c0-e92f420b3ce9", + "name": "HandleHttpResponse", + "comments": "", + "instanceIdentifier": "fd45c24b-0196-1000-0000-00001a549d74" + }, + "labelIndex": 1, + "zIndex": 0, + "selectedRelationships": [ + "success" + ], + "backPressureObjectThreshold": 10000, + "backPressureDataSizeThreshold": "1 GB", + "flowFileExpiration": "0 sec", + "prioritizers": [], + "bends": [], + "loadBalanceStrategy": "DO_NOT_LOAD_BALANCE", + "partitioningAttribute": "", + "loadBalanceCompression": "DO_NOT_COMPRESS", + "componentType": "CONNECTION", + "groupIdentifier": "b4fe1571-02fd-3351-a2c0-e92f420b3ce9" + } + ], + "labels": [], + "funnels": [], + "controllerServices": [ + { + "identifier": "0e58c44e-c294-3b7f-b323-bb94ca5074fb", + "instanceIdentifier": "fd3b17c4-0196-1000-0000-000076ed786c", + "name": "StandardHttpContextMap", + "comments": "", + "type": "org.apache.nifi.http.StandardHttpContextMap", + "bundle": { + "group": "org.apache.nifi", + "artifact": "nifi-http-context-map-nar", + "version": "1.28.1" + }, + "properties": { + "Request Expiration": "1 min", + "Maximum Outstanding Requests": "5000" + }, + "propertyDescriptors": {}, + "controllerServiceApis": [ + { + "type": "org.apache.nifi.http.HttpContextMap", + "bundle": { + "group": "org.apache.nifi", + "artifact": "nifi-standard-services-api-nar", + "version": "1.28.1" + } + } + ], + "scheduledState": "ENABLED", + "bulletinLevel": "WARN", + "componentType": "CONTROLLER_SERVICE", + "groupIdentifier": "b4fe1571-02fd-3351-a2c0-e92f420b3ce9" + } + ], + "variables": {}, + "defaultFlowFileExpiration": "0 sec", + "defaultBackPressureObjectThreshold": 10000, + "defaultBackPressureDataSizeThreshold": "1 GB", + "flowFileConcurrency": "UNBOUNDED", + "flowFileOutboundPolicy": "STREAM_WHEN_AVAILABLE", + "componentType": "PROCESS_GROUP" + } + } +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: nifi2-flow data: flow.json: | { diff --git a/tests/templates/kuttl/custom-components-git-sync/README.md b/tests/templates/kuttl/custom-components-git-sync/README.md index 31d24886..67829d52 100644 --- a/tests/templates/kuttl/custom-components-git-sync/README.md +++ b/tests/templates/kuttl/custom-components-git-sync/README.md @@ -1,6 +1,6 @@ # Overview -This test case configures the Nifi cluster to fetch Python and Java +This test case configures the NiFi cluster to fetch Python and Java processors via git-sync. A flow is deployed that include these processors. The test job triggers the flow and checks the result. @@ -11,11 +11,18 @@ processors. The test job triggers the flow and checks the result. The Greet processor is implemented in Python and answers a request with the content "Hello!". +Only NiFi 2 supports Python components. In the flow of NiFi 1, the +Python processor is replaced with the internal ReplaceText processor. +NiFi 1 ignores the Python configurations. + ## Java processor "Shout" The Shout processor is implemented in Java and transforms the received content to uppercase. +The processor is compiled with the dependencies for NiFi 2, but also +works in NiFi 1 if compiled with JDK 11. + # Flow ![NiFi canvas](./canvas.png) diff --git a/tests/templates/kuttl/custom-components-git-sync/java-processors/sample-processor.tar.gz b/tests/templates/kuttl/custom-components-git-sync/java-processors/sample-processor.tar.gz index 168e18721ba048f9cd8a3ac103449882167171ce..f599ac0e5ad07974c939dbbfab9a0b7e57d9767a 100644 GIT binary patch literal 2200 zcmV;J2xs>niwFP!000001MOUGbKAHP_GkSHM4#x6MTw+dZW39SoX?)7%h zFL;^%D4d#4)8LS8NP_zb-|P6dde&gUe}B-kEa1P}>kdv(=a5Ql!v8t{!=Hg{G=sj8 zke)X!v(rSFdZAC~!ooOu z?~kDVv?)r)WY|tKmZB~bWbAqZ5~WBBKM71_<*kR9`T^#{c1~njZb8h*HoH=-^fpnb zoodu5s3WlVXo5Yx&+bF8R;9c;We2dTNT!69*rupNB-@$5Xc>X$kSe9Ojm)|+^e@+^ zBqgy!xpl5gFSEDGQtD##li?fM+z;!YPC?Sm9JV26EB=bB*4sMIKVE)Bg-1L zH)N%j&sE=0w{muj8sB<4SH_C}Ur)=6mIalb|6K?C|Df;m_4B_D?8`rA-m}NRzWh55 zJoasP{@b0dZEODPKo$O{E}_pE1l9SsoUWe#wV*QppWK;SaS&AJ-?rg3Q1f2{p2xpj zV(vc(s`KyImiGU(;063=uNb>VLUsOo7Aylb|25zs{L3<120?ZH`<;Q_|EmRu}4EE=L0Iz-n;NR-?^!%>{X4`DPy$Eq{2vs@od0)^VJv=2lTBIjTX$EZggW@o&Hr`& z-vPg0^8b$A@9OV=bs+g(gVCCgM&^=HGhy^iqd}%o$OI~V@-kgYL!rLXnSSWSQ%o0k z7a;XO0wTWrin+%~BvapPs%9L7^D%+;ui(cOku7eJ%ycvVw$w_FO_PM?FY$Pc8TN0n z>tpt2Q}*wSh&9>oX%ve)!LU2sP{B}H+UB=&_6&DWtKqV(G`fxNyp(gMhttH%&>1ZPa9GE5D*V}fy+56 zI%cFkM*1ARd*Fh(UM}aq$=9?+VJss> zypRnBCjr>eI9-0;1(?*GVx!4pPqx-9W%~K9UOuVuhZeHXvt{k&f)BDWg}$UREI-) z&&Xr~#1(p7>g5?KagO!^ENgq|T7;HRiHDix6s>7yAbQy+BTJZy_;_`epKi*&c@zZHe z_X1=?O2KkNiQiBSt-2Ef)Zg6HX*w^LNUn~d^xCAFCHwNx%88k`g5V>ui%u2<*J zvbwL0uRq@R__|Sf{l_p5V+P4b-T!y``u)Eaynz1?m-p{l@BewVb0AdbzuU7l|Fz%{ z{DWm?WQ#|rvj5k!`*Q!^fo#ox4fsCx|E$&hKTGfb)q#V~|IIJy*$dOR?yp7>b@0Dq z{|_>IMgQ;E1G)dN_y3NMz4rag;)aoatNQDp%KR6Yi<;s;yYl(3*MG-HW&Z1l|8#Wx zcRhF>|HqI2?DjPOb>Id3A2t4SpyU5*!9n;xeEg?n>-gVVa5(-C8vp6o+W*&rqwxRC z_)q!&|J|-F<3IbB-v6%!-^c!MaV)6)->%;OtpmrL|HWOSdnm1AK)*NC!GCW4uRZ_8 z>6QJz<5)et|62zR6#uEh73B=SjVaBAL++5{1;I}-CEz0BNW~Yq5ix(kU*!|V)|{-hacem-9;1~de;F0xbSoT) zwpL|$kgqp{lIC6*;Suy_MttlK+sXY>tM)pzj;5rvCdu9F|9&I+1KZeqB;a=UJg-ea9*stt(P%UpD^!~=Ok7?#wvH*aoyx1j zLlvOaXz<@kt5Fr-m3m$L_TiP%s4BHuOKmB&SMW)#RbQdT5qMbxmom*k=oKYXvKbFM zG!KUI1)7xq!k){IbMugGZ~-?xd8f%AfW2fw0jM z`bKQ)TvFt!gs`RC2C=5+(x>se@(ZK3Hi zEZfesax=K9)a5FtA~4@PQW9c6JUqw`HG!vs*en0I>W`q}Tq_d`q+sb-Iza(I#Wz$u z?c2J>1b16oMI3{sp*&KB zt>CN2P$l5#B*qTI`b>uL)bF)BGnJ(%J4pku&Pd3F;*Cw5i3M+G3fgi3mV>htd_4(A z*M}Lc z0vvs&ITSN_#5Bv$9HZlXr*TY|5mV*|oRO}s9*i)9&i$M^h9#+uJ_05RM*^{##t`L` zj>2b*9IRuq#D*-Lo-Euuljuk{HA+$3Fe7moX;9R@qs-Ct$dmtihj@Nv%4Mpf39&R2 z4Y4UCOlM?)hTZ<{=u@ZD9gWaAvhYLfF$_2{VH``RtK(e&+0y2?BvDuAtx1W8MzpKl zN1=bn$AzA)s3$Al@y1H0Kl-dYWT`QXU?|U`JYB?SP#^o}!4f+TF|c3PhH1gDftEHg z5U~g=d7TeOs0U?4Gs%Ahsg7+i7)+#lzfWQO0=8nU!59HQvOOt91`asph+k4U@dU8m zi795$0$Pf<$y~<*RUlMO^mbcIqQkZ|cF1%Jf{XP!(aYB;_H+dQp&dlM-%);oNsT7K zhw}hUhK`i{5#T^3s1#y-2P);7W|S0%py~skfg$#5R3g@E1PXqN$`^?$9GLTjN=vZ$ zj+orOzQ#>Pi5IQe0YNx9pXE>Eceg*BMtF|_Z%8nhY{;`4qM>zjQUdm~n>r2VWfFh{ zv8Ko*7EWj9VaCU^RN@%Gk2eA`{{`+pE&=6?v5pZDEc(OFDap+Kv{+!LqfuP)jF~(f z48~c5@@1_wUh*YTFAy{sCyP$FI)<`dpFb;dUrxRh2>{z*Z~xE1)OH;xK5G2G)-3k_ zxxfqLf7Kng%f0J&TNPn{`PUn2A^%+95b}qSS&|JFVW0S)$p2KEHF#XeKL_|W;(y9| z{7)(3e|f+`=YRGlJ=`#T?fw=JhdkuJCH@Bw8*%$zQ(M&{|34SlDefo714hNJ>dyoF z%zuU{%PIe}&e#9K|2sbHE&qJ-KWjz)cRuhu`5!<3v)(A=p9j1^{zuLKY!&(cxxhi> zfB5`QMJ@7wbAiLj|DgGwHMOw+xxi86|APF_YK`yzm1eb=|GB`o5&wt=e8oj1!rF z(G;mwZFC-?N@WW_tiF~K3Gsr>MA{$6UTcq&536M~EMx0;7rVG5eZZz^zeW$XV;bK{ zK_X-V+i|T@#v@<~J8bMHTXk&LVoA;;X;-h263M*c>28tiz6jMRWS-@ky{hyeJKe$c^>IdePoN8p!z{GD{>8)F$;FvswrxQ>*F zS-R)#cN^D#OFPJYuzSS+{QKXUs#3&%bAhe$Kfryk+x)+3GXEE_l*0eZ1q%P~&+Y$- zn^K>9qpNjLuW+|v+SHj%&g@Qu_R@v_8J-vQU*anVS+d+^Nf*mn7dE`1g|Zy&-tH!|F^0X@Bici8S;OyoqGz(jt_Ac{O0vvQ5&^dLjFx2 z5-QgJ9AMw|Kg6tyP@K!gU%tS#Q<_t7*!8)DPe zj4>E52`x3#h2l}S-|dWhzjh^55#%c&{nXYvGtHXfKB4SNv$QF8g2qfJLI$2_uF1|N z=D5%XG0r9Ru3DQkwY#!W#Y(x3F)shjz}2z=s$OmBn%>eHQj9p25VXoKv^bZfz{;NK z;u+E=u%Wxr*z8>T{$@D%2+ZGJ_J-eQdC`(rU_WQXv&GpFaF-RZi(x0QU z_QLm(j>X%7&dOiJ-d4>xtMGY}Ha|xn2EPH3c7O0boScceG5im`_aFN3e|*~+T)*r0 zI^$7T&zpT)s$CDRZ`)$(-*!HzPy}@-@r}zGPFx=IU zyeCZFXt0LePWO6z`@RQl4F==eoA&ra*i?MjWjXP0M;>lG6Wg&(ey_;HmM+e|`&Z|( zJ#PPtANWZcXogNShK(Nha*xs*q@H~F_rEW8q}wxm6Zm*@eKR0!g&3{z=ZC-d-`0N^ z46S0>&yn|)BiyF|G{m>_Oj^;f1XL+gB zcIfB=gHMfVCwP(^!+ZQRw!LdIVEnvuRO#a<9?rXZicdfMh;>+{9Ke%?t|h&WBBxB( zeeq+5y0O}q_|BaM^?}p?cE)X;ktJbIoYDioj%1Dp76`*(yoMMgzrvKT@&hrZn3WeM zENe5H-@U`JCRk$?9ULBe9x+!zy?dLP#44L$Q(O>S*OHgPcAbJdM->Rg`Z##LC0w2-et^O=eNu| zNS`b{VnxdeH@ZA}@MWhd0fQPi42WV=T=H|CDXKu-gOSi8s+-Un3GvOCPf9|3cV|0K z*8}vOzJM5e(V{J^&DjtnswT~(A2Bl7!0)<}xKkIdxLi|}&JH8MXie4!CZi)`u%@2di zWFs+E+GC1%+MNF!;dew@>^9WtuUZ#Z>FylQ;1z_CFkIF8XH#H=%%)k z*1v7u;b?(Pvopi`J$AxPAp)O~kvYC3Iv+mGfo;aQRF%*>;a_{7JHlCAL%H+!Ia-b3$ugoezR-23nPt}pxD``w#~AAtW0kb){7l6)BVnhKb(QJ%M#Wf+1eG5gbe#X zCsaS!p8?-u|En#vm9YPfCcpoou>Z%0L)-uEBi{gO<_NaU;DyGPfiUEnwqaSLBvt_b z+`#~BVc;bCvXAyXw3MA!S=<%hk@ z zEv4}Pa)E=-f4^=pQ-w?CI-u+P)0;|VbDl&j;{Y~}-p%&`BjM@cbE$^O0tX?Tl__yF z;xA|aFnsRJP~j_n>yTmpKWokqFKkL+87KIKtH9R$e{l9<_Ft_kYT^Io0*AK$Lp-+` zj_OWoB)}19WhcN}#hqiW9a%-~co^5w%*E7Ap3ynK6>4bfA24w%~&TtzJ*Gt6I6OhH+^AxZ1BEX_|c^m-3u?RP+)RaylW;Z z+s2_-$p55}vHmahgY5h~*cSh(Rh!B9PeU!<|H}mqz5f5I(@WxDku`CpEf&#)+PU`~ z83Do$nX{mXB0IO(P#gy`=D$28j7%-t!3St#+x)LKlJVcBT5S~bKM(jzkeq-#aT|#5 p_~}W7&agSDoW$P*E3Qx~pnw7jD4>7>3izYL{{WI+;phOM003 Date: Fri, 23 May 2025 15:41:57 +0200 Subject: [PATCH 12/24] Add comment --- rust/operator-binary/src/config/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rust/operator-binary/src/config/mod.rs b/rust/operator-binary/src/config/mod.rs index a599852d..10573504 100644 --- a/rust/operator-binary/src/config/mod.rs +++ b/rust/operator-binary/src/config/mod.rs @@ -579,6 +579,8 @@ pub fn build_nifi_properties( // Custom components # //#################### if git_sync_resources.is_git_sync_enabled() { + // NiFi 1.x does not support Python components. The Python configuration is just ignored. + // The command used to launch Python. // This property must be set to enable Python-based processors. properties.insert("nifi.python.command".to_string(), "python3".to_string()); From 0a278ae50299bcb0c3e00692a917554996e583f0 Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Mon, 26 May 2025 13:47:33 +0200 Subject: [PATCH 13/24] docs: Document loading of custom components via git-sync --- .../pages/usage_guide/custom-components.adoc | 87 +++++++++++++++++++ docs/modules/nifi/partials/nav.adoc | 4 +- 2 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 docs/modules/nifi/pages/usage_guide/custom-components.adoc diff --git a/docs/modules/nifi/pages/usage_guide/custom-components.adoc b/docs/modules/nifi/pages/usage_guide/custom-components.adoc new file mode 100644 index 00000000..54c9fe62 --- /dev/null +++ b/docs/modules/nifi/pages/usage_guide/custom-components.adoc @@ -0,0 +1,87 @@ += Loading custom components +:description: Load custom NiFi components for enhanced functionality. +:nifi-docs-developers-guide: https://nifi.apache.org/docs/nifi-docs/html/developer-guide.html +:nifi-docs-python-developers-guide: https://nifi.apache.org/nifi-docs/python-developer-guide.html +:git-sync-docs: https://github.com/kubernetes/git-sync/tree/v4.2.4#manual + +You can develop or use custom components for Apache NiFi, typically custom processors, to extend its functionality. + +There are currently two types of custom components: + +1. Custom NiFi Archives (NARs) + + The {nifi-docs-developers-guide}[NiFi Developer’s Guide] provides further information on how to develop custom NARs. +2. Starting with NiFi 2.0, also custom Python extensions can be used. + + In {nifi-docs-python-developers-guide}, the development of custom components using Python is described. + +The Stackable image contains the required tooling for both types. +For instance, a supported Python version is included. +NARs are only loaded once, but for Python scripts, hot-reloading is supported. + +TIP: You might need to refresh your browser window to see the new or changed Python components. + +Several options are described below, to add custom components to NiFi. + +== git-sync + +Custom NiFi components can be synchronized from a Git repository directly into the NiFi pods with git-sync. +The {crd-docs}/nifi.stackable.tech/nificluster/v1alpha1[NifiCluster CRD] allows the specification of one or multiple Git repositories: + +[source,yaml] +---- +--- +apiVersion: nifi.stackable.tech/v1alpha1 +kind: NifiCluster +spec: + clusterConfig: + customComponentsGitSync: # <1> + - repo: https://example.com/git/custom-nifi-components # <2> + branch: main # <3> + gitFolder: path/to/the/components # <4> + depth: 10 # <5> + wait: 10s # <6> + credentialsSecret: git-credentials # <7> + gitSyncConf: # <8> + --git-config: http.sslCAInfo:/tmp/ca-cert/ca.crt + - repo: https://example.com/git/other-nifi-components # <9> + nodes: + config: + logging: + enableVectorAgent: true + containers: + git-sync: # <10> + console: + level: INFO + file: + level: INFO + loggers: + ROOT: + level: INFO +--- +apiVersion: v1 +kind: Secret +metadata: + name: git-credentials +type: Opaque +data: + user: ... + password: ... +---- +<1> If the optional field `customComponentsGitSync` is defined, then containers running git-sync are deployed to synchronize the specified repositories. +<2> The git repository URL that will be cloned +<3> The branch to clone; defaults to `main` +<4> Location in the Git repository containing the NiFi components; defaults to the root folder +<5> The depth of synchronizing, i.e. the number of commits to clone; defaults to 1 +<6> The synchronization interval, e.g. `20s` or `5m`; defaults to `20s` +<7> The name of the Secret used to access the repository if it is not public. + + The referenced Secret must include two fields: `user` and `password`. + The `password` field can either be an actual password (not recommended) or a GitHub token, as described in the {git-sync-docs}[git-sync documentation]. +<8> A map of optional configuration settings that are listed in the {git-sync-docs}[git-sync documentation]. + + These settings are not verified. +<9> Multiple repositories can be defined. Only the `repo` field is mandatory. +<10> Logging can be configured as described in xref:concepts:logging.adoc[]. + As git-sync is a command-line tool, just its output is logged and no fine-grained log configuration is possible. + All git-sync containers are configured via the one `git-sync` field. + +It cannot be specified, if a repository contains NiFi Archives or Python components. +The operator just configures each repository for both types. +In particular, the parameters `nifi.nar.library.directory` and `nifi.python.extensions.source.directory` are set. diff --git a/docs/modules/nifi/partials/nav.adoc b/docs/modules/nifi/partials/nav.adoc index de4119e2..a9a3bc78 100644 --- a/docs/modules/nifi/partials/nav.adoc +++ b/docs/modules/nifi/partials/nav.adoc @@ -16,9 +16,7 @@ ** xref:nifi:usage_guide/exposing-processors/index.adoc[] *** xref:nifi:usage_guide/exposing-processors/http.adoc[] *** xref:nifi:usage_guide/exposing-processors/tcp.adoc[] -** xref:nifi:usage_guide/custom-components/index.adoc[] -*** xref:nifi:usage_guide/custom-components/custom-nars.adoc[] -*** xref:nifi:usage_guide/custom-components/custom-python-processors.adoc[] +** xref:nifi:usage_guide/custom-components.adoc[] ** xref:nifi:usage_guide/operations/index.adoc[] *** xref:nifi:usage_guide/operations/cluster-operations.adoc[] *** xref:nifi:usage_guide/operations/pod-placement.adoc[] From e8d68640db1ba829c0b8565ce170d045b25635d9 Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Tue, 27 May 2025 11:19:40 +0200 Subject: [PATCH 14/24] docs: Document all options of loading custom components --- .../pages/usage_guide/custom-components.adoc | 217 +++++++++++++++++- .../custom-components/custom-nars.adoc | 129 ----------- .../custom-python-processors.adoc | 165 ------------- .../usage_guide/custom-components/index.adoc | 9 - 4 files changed, 212 insertions(+), 308 deletions(-) delete mode 100644 docs/modules/nifi/pages/usage_guide/custom-components/custom-nars.adoc delete mode 100644 docs/modules/nifi/pages/usage_guide/custom-components/custom-python-processors.adoc delete mode 100644 docs/modules/nifi/pages/usage_guide/custom-components/index.adoc diff --git a/docs/modules/nifi/pages/usage_guide/custom-components.adoc b/docs/modules/nifi/pages/usage_guide/custom-components.adoc index 54c9fe62..757ae6a3 100644 --- a/docs/modules/nifi/pages/usage_guide/custom-components.adoc +++ b/docs/modules/nifi/pages/usage_guide/custom-components.adoc @@ -2,6 +2,7 @@ :description: Load custom NiFi components for enhanced functionality. :nifi-docs-developers-guide: https://nifi.apache.org/docs/nifi-docs/html/developer-guide.html :nifi-docs-python-developers-guide: https://nifi.apache.org/nifi-docs/python-developer-guide.html +:nifi-docs-flowfile-source: https://nifi.apache.org/nifi-docs/python-developer-guide.html#flowfile-source :git-sync-docs: https://github.com/kubernetes/git-sync/tree/v4.2.4#manual You can develop or use custom components for Apache NiFi, typically custom processors, to extend its functionality. @@ -11,7 +12,7 @@ There are currently two types of custom components: 1. Custom NiFi Archives (NARs) + The {nifi-docs-developers-guide}[NiFi Developer’s Guide] provides further information on how to develop custom NARs. 2. Starting with NiFi 2.0, also custom Python extensions can be used. + - In {nifi-docs-python-developers-guide}, the development of custom components using Python is described. + In the {nifi-docs-python-developers-guide}[NiFi Python Developer’s Guide], the development of custom components using Python is described. The Stackable image contains the required tooling for both types. For instance, a supported Python version is included. @@ -21,14 +22,28 @@ TIP: You might need to refresh your browser window to see the new or changed Pyt Several options are described below, to add custom components to NiFi. -== git-sync +[TIP] +==== +Check the logs of a NiFi node to ensure that the custom components are actually loaded, e.g.: + +[source,console] +---- +$ kubectl logs nifi-node-default-0 +… +… INFO [main] o.a.n.n.StandardExtensionDiscoveringManager Loaded extensions for tech.stackable.nifi:nifi-sample-nar:1.0.0 in 6 millis +… +… INFO [main] o.a.n.n.StandardExtensionDiscoveringManager Discovered Python Processor Greet +… +---- +==== + +== Synchronize with git-sync Custom NiFi components can be synchronized from a Git repository directly into the NiFi pods with git-sync. The {crd-docs}/nifi.stackable.tech/nificluster/v1alpha1[NifiCluster CRD] allows the specification of one or multiple Git repositories: [source,yaml] ---- ---- apiVersion: nifi.stackable.tech/v1alpha1 kind: NifiCluster spec: @@ -68,7 +83,7 @@ data: ---- <1> If the optional field `customComponentsGitSync` is defined, then containers running git-sync are deployed to synchronize the specified repositories. <2> The git repository URL that will be cloned -<3> The branch to clone; defaults to `main` +<3> The git revision (branch, tag, or hash) to check out; defaults to the `main` branch <4> Location in the Git repository containing the NiFi components; defaults to the root folder <5> The depth of synchronizing, i.e. the number of commits to clone; defaults to 1 <6> The synchronization interval, e.g. `20s` or `5m`; defaults to `20s` @@ -84,4 +99,196 @@ data: It cannot be specified, if a repository contains NiFi Archives or Python components. The operator just configures each repository for both types. -In particular, the parameters `nifi.nar.library.directory` and `nifi.python.extensions.source.directory` are set. +In particular, the parameters `nifi.nar.library.directory.+*+` and `nifi.python.extensions.source.directory.+*+` are set. + +== Mount as a ConfigMap + +Custom components can also be stored in ConfigMaps. +This makes especially sense for Python components, but NAR files work as well. +This way, the components are stored and versioned alongside your NifiCluster itself. + +// Technically it's yaml, but most of the content is Python +[source,python] +---- +apiVersion: v1 +kind: ConfigMap +metadata: + name: nifi-processors +data: + CreateFlowFileProcessor.py: | + from nifiapi.flowfilesource import FlowFileSource, FlowFileSourceResult + + class CreateFlowFile(FlowFileSource): + class Java: + implements = ['org.apache.nifi.python.processor.FlowFileSource'] + + class ProcessorDetails: + version = '0.0.1-SNAPSHOT' + description = '''A Python processor that creates FlowFiles.''' + + def __init__(self, **kwargs): + pass + + def create(self, context): + return FlowFileSourceResult( + relationship = 'success', + attributes = {'greeting': 'hello'}, + contents = 'Hello World!' + ) +binaryData: + custom-nifi-processor-1.0.0.nar: ... +---- + +The Python script is taken from {nifi-docs-flowfile-source}[the offical NiFi Python developer guide]. + +Afterwards, we need to mount the ConfigMap as described in xref:nifi:usage_guide/extra-volumes.adoc[] and extend the `nifi.properties` file: + +[source,yaml] +---- +apiVersion: nifi.stackable.tech/v1alpha1 +kind: NifiCluster +metadata: + name: simple-nifi +spec: + image: + productVersion: 2.2.0 + clusterConfig: + authentication: + - authenticationClass: simple-nifi-admin-user + extraVolumes: # <1> + - name: nifi-processors + configMap: + name: nifi-processors + listenerClass: external-unstable + sensitiveProperties: + keySecret: nifi-sensitive-property-key + autoGenerate: true + zookeeperConfigMapName: simple-nifi-znode + nodes: + configOverrides: + nifi.properties: + nifi.nar.library.directory.myCustomLibs: /stackable/userdata/nifi-processors/ # <2> + nifi.python.extensions.source.directory.myCustomLibs: /stackable/userdata/nifi-processors/ + roleGroups: + default: + replicas: 1 +---- +<1> Specify your ConfigMaps here. +<2> The directory name after `userdata` has to match the name of the volume, while `myCustomLibs` is a name you can freely change. + +== Custom Docker image + +You can extend the official Stackable NiFi image by copying the required NAR files into NiFi's classpath or the Python files into the default extension directory. +The benefit of this method is that there is no need for any config overrides or extra mounts, you can use any of the NiFi examples, swap the image and your components will be available. +But this means, you will need to have access to a registry to push your custom image to. + +The basic Dockerfile below shows how to achieve this: + +[source,Dockerfile] +---- +FROM oci.stackable.tech/sdp/nifi:2.2.0-stackable25.3.0 +COPY /path/to/your/nar.file /stackable/nifi/lib/ +COPY /path/to/your/Python.file /stackable/nifi/python/extensions/ +---- + +You then need to make this image available to your Kubernetes cluster and specify it in your NiFi resource as described in xref:concepts:product_image_selection.adoc[]. + +[source,yaml] +---- +spec: + image: + productVersion: 2.2.0 + custom: oci-registry.company.org/nifi:2.2.0-customprocessors +---- + +Also read the xref:guides:custom-images.adoc[Using customized product images] guide for additional information. + +== Using the official image + +If you do not want to create a custom image or do not have access to an image registry, you can use the extra volume mount functionality to mount a volume containing your custom components and configure NiFi to read these from the mounted volumes. + +For this to work you'll need to prepare a PersistentVolumeClaim (PVC) containing your components. +Usually the best way to do this is to mount the PVC into a temporary container and then `kubectl cp` the NAR or Python files into that volume. + +The following listing shows an example of creating the PVC and the Pod: + +[source, yaml] +---- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: nifi-processors +spec: + accessModes: + - ReadWriteOnce # <1> + resources: + requests: + storage: 1Gi +--- +apiVersion: v1 +kind: Pod +metadata: + name: processorcopy +spec: + volumes: + - name: nifi-processors + persistentVolumeClaim: + claimName: nifi-processors + containers: + - name: copycontainer + image: alpine + command: + - tail + - -f + - /dev/null + volumeMounts: + - mountPath: /volume + name: nifi-processors +---- +<1> Please note that this access mode means that you can only use this volume with a single NiFi Pod. + For a distributed scenario, an additional list value of `ReadOnlyMany` could be specified here if this is supported. + +The commands to then copy the NAR bundle and Python files into the PVC is: + +[source,bash] +---- +kubectl cp /path/to/component.nar processorcopy:/volume/ +kubectl cp /path/to/Python.files processorcopy:/volume/ +---- + +Now you can mount the extra volume into your NiFi instance as described in xref:nifi:usage_guide/extra-volumes.adoc[]. + +After this is done, your components are available within the NiFi Pods and NiFi can be configured to load components from the volume: + +[source,yaml] +---- +apiVersion: nifi.stackable.tech/v1alpha1 +kind: NifiCluster +metadata: + name: simple-nifi +spec: + image: + productVersion: 2.2.0 + clusterConfig: + authentication: + - authenticationClass: simple-nifi-admin-user + extraVolumes: # <1> + - name: nifi-processors + persistentVolumeClaim: + claimName: nifi-processors + listenerClass: external-unstable + sensitiveProperties: + keySecret: nifi-sensitive-property-key + autoGenerate: true + zookeeperConfigMapName: simple-nifi-znode + nodes: + configOverrides: + nifi.properties: + nifi.nar.library.directory.myCustomLibs: /stackable/userdata/nifi-processors/ # <2> + nifi.python.extensions.source.directory.myCustomLibs: /stackable/userdata/nifi-processors/ + roleGroups: + default: + replicas: 1 +---- +<1> Specify your prepared PVC here. +<2> The directory name after `userdata` has to match the name of the volume, while `myCustomLibs` is a name you can freely change. diff --git a/docs/modules/nifi/pages/usage_guide/custom-components/custom-nars.adoc b/docs/modules/nifi/pages/usage_guide/custom-components/custom-nars.adoc deleted file mode 100644 index 78d5da2a..00000000 --- a/docs/modules/nifi/pages/usage_guide/custom-components/custom-nars.adoc +++ /dev/null @@ -1,129 +0,0 @@ -= Custom `.nar` files - -:description: Load custom NiFi components by using custom Docker images or mounting external volumes with nar files for enhanced functionality. -:nifi-docs-custom-components: https://nifi.apache.org/docs/nifi-docs/html/developer-guide.html#introduction - -You can develop {nifi-docs-custom-components}[custom components] for Apache NiFi, typically custom processors, to extend its functionality. -For these to be available in the NiFi UI, they need to be present in the classpath at startup, so the nar bundles have to be injected into your NiFi instance. - -The Stackable Data Platform supports two ways to achive this goal, both of which are described below. - -Please note that building your own Docker image is the recommended solution, as giving multiple pods access to a shared PVC can often be tricky (see comment on access mode in the second section). - -== Custom Docker image - -You can extend the official Stackable NiFi image by copying the required _nar_ files into NiFi's classpath. -The benefit of this method is that there is no need for any config overrides or extra mounts, you can use any of the NiFi examples, swap the image and your components will be available. -But this means you will need to have access to a registry to push your custom image to. - -The basic Dockerfile below shows how to achieve this: - -[source,Dockerfile] ----- -FROM oci.stackable.tech/sdp/nifi:1.27.0-stackable0.0.0-dev -COPY /path/to/your/nar.file /stackable/nifi/lib/ ----- - -You then need to make this image available to your Kubernetes cluster and specify it in your NiFi resource as described in xref:concepts:product_image_selection.adoc[]. - -[source,yaml] ----- -spec: - image: - productVersion: 1.27.0 - custom: "docker.company.org/nifi:1.27.0-customprocessor" ----- - -Also read the xref:guides:custom-images.adoc[Using customized product images] guide for additional information. - -== Using the official image - -If you don't want to create a custom image or don't have access to an image registry, you can use the extra volume mount functionality to mount a volume containing your custom components and configure NiFi to read these from the mounted volumes. - -For this to work you'll need to prepare a PersistentVolumeClaim (PVC) containing your components. -Usually the best way to do this is to mount the PVC into a temporary container and then `kubectl cp` the _nar_ files into that volume. - -The following listing shows an example of creating the PVC and the Pod: - -[source, yaml] ----- -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: nifi-processor -spec: - accessModes: - - ReadWriteOnce # <1> - resources: - requests: - storage: 1Gi ---- -apiVersion: v1 -kind: Pod -metadata: - name: processorcopy -spec: - volumes: - - name: nifi-processor - persistentVolumeClaim: - claimName: nifi-processor - containers: - - name: copycontainer - image: alpine - command: - - tail - - "-f" - - "/dev/null" - volumeMounts: - - mountPath: "/volume" - name: nifi-processor ----- - -<1> Please note that this access mode means that you can only use this volume with a single NiFi Pod. - For a distributed scenario, an additional list value of `ReadOnlyMany` could be specified here if this is supported. - -The command to then copy the nar bundle into the PVC is: - -[source,bash] ----- -kubectl cp /path/to/component.nar processorcopy:/volume/ ----- - -Now you can mount the extra volume into your NiFi instance as described in xref:nifi:usage_guide/extra-volumes.adoc[]. - -After this is done, your components are available within the NiFi Pods and NiFi can be configured to load these at startup by adding an extra directory to the classpath: - - -[source,yaml] ----- -apiVersion: nifi.stackable.tech/v1alpha1 -kind: NifiCluster -metadata: - name: simple-nifi -spec: - image: - productVersion: 1.27.0 - clusterConfig: - authentication: - - authenticationClass: simple-nifi-admin-user - extraVolumes: # <1> - - name: nifi-processor - persistentVolumeClaim: - claimName: nifi-processor - listenerClass: external-unstable - sensitiveProperties: - keySecret: nifi-sensitive-property-key - autoGenerate: true - zookeeperConfigMapName: simple-nifi-znode - nodes: - config: - configOverrides: - nifi.properties: - nifi.nar.library.directory.myCustomLibs: "../userdata/nifi-processor/" # <2> - roleGroups: - default: - replicas: 1 ----- - -<1> Specify your prepared PVC here. -<2> The directory name after `userdata` has to match the name of the volume, while `myCustomLibs` is a name you can freely change. diff --git a/docs/modules/nifi/pages/usage_guide/custom-components/custom-python-processors.adoc b/docs/modules/nifi/pages/usage_guide/custom-components/custom-python-processors.adoc deleted file mode 100644 index 99cffcc6..00000000 --- a/docs/modules/nifi/pages/usage_guide/custom-components/custom-python-processors.adoc +++ /dev/null @@ -1,165 +0,0 @@ -= Custom Python processors - -In NiFi 2.0, support for custom processors written in Python was added. -The Stackable images already contain the required tooling, such as - obviously - a supported Python version. - -== General configuration - -[source,yaml] ----- -spec: - nodes: - configOverrides: - nifi.properties: - # The command used to launch Python. - # This property must be set to enable Python-based processors. - nifi.python.command: python3 - # The directory that NiFi should look in to find custom Python-based - # Processors. - nifi.python.extensions.source.directory.custom: /nifi-python-extensions - # The directory that contains the Python framework for communicating - # between the Python and Java processes. - nifi.python.framework.source.directory: /stackable/nifi/python/framework/ - # The working directory where NiFi should store artifacts - # This property defaults to ./work/python but if you want to mount an - # emptyDir for the working directory then another directory has to be - # set to avoid ownership conflicts with ./work/nar. - nifi.python.working.directory: /nifi-python-working-directory ----- - -== Getting Python scripts into NiFi - -TIP: NiFi should hot-reload the Python scripts. You might need to refresh your browser window to see the new processor. - -[#configmap] -=== 1. Mount as ConfigMap - -The easiest way is defining a ConfigMap and mounting it as follows. -This way, the Python processors are stored and versioned alongside your NifiCluster itself. - -// Technically it's yaml, but the most content is Python -[source,python] ----- -apiVersion: v1 -kind: ConfigMap -metadata: - name: nifi-python-extensions -data: - CreateFlowFileProcessor.py: | - from nifiapi.flowfilesource import FlowFileSource, FlowFileSourceResult - - class CreateFlowFile(FlowFileSource): - class Java: - implements = ['org.apache.nifi.python.processor.FlowFileSource'] - - class ProcessorDetails: - version = '0.0.1-SNAPSHOT' - description = '''A Python processor that creates FlowFiles.''' - - def __init__(self, **kwargs): - pass - - def create(self, context): - return FlowFileSourceResult( - relationship = 'success', - attributes = {'greeting': 'hello'}, - contents = 'Hello World!' - ) ----- - -The Python script is taken from https://nifi.apache.org/nifi-docs/python-developer-guide.html#flowfile-source[the offical NiFi Python developer guide]. - -You can add multiple Python scripts in the ConfigMap. -Afterwards we need to mount the Python scripts into `/nifi-python-extensions`: - -[source,yaml] ----- -spec: - nodes: - podOverrides: - spec: - containers: - - name: nifi - volumeMounts: - - name: nifi-python-extensions - mountPath: /nifi-python-extensions - - name: nifi-python-working-directory - mountPath: /nifi-python-working-directory - volumes: - - name: nifi-python-extensions - configMap: - name: nifi-python-extensions - - name: nifi-python-working-directory - emptyDir: {} ----- - -[#git-sync] -=== 2. Use git-sync - -As an alternative you can use `git-sync` to keep your Python processors up to date. -You need to add a sidecar using podOverrides that syncs into a shared volume between the `nifi` and `git-sync` container. - -The following snippet can serve as a starting point (the Git repo has the folder `processors` with the Python scripts inside). - -[source,yaml] ----- -spec: - nodes: - podOverrides: - spec: - containers: - - name: nifi - volumeMounts: - - name: nifi-python-extensions - mountPath: /nifi-python-extensions - - name: nifi-python-working-directory - mountPath: /nifi-python-working-directory - - name: git-sync - image: registry.k8s.io/git-sync/git-sync:v4.2.3 - args: - - --repo=https://github.com/stackabletech/nifi-talk - - --root=/nifi-python-extensions - - --period=10s - volumeMounts: - - name: nifi-python-extensions - mountPath: /nifi-python-extensions - volumes: - - name: nifi-python-extensions - emptyDir: {} - - name: nifi-python-working-directory - emptyDir: {} ----- - -Afterwards you need to update your source directory (the one you added previously) accordingly, to point into the Git subfolder you have. - -[source,yaml] ----- -spec: - nodes: - configOverrides: - nifi.properties: - # Replace the property from the previous step - # Format is /nifi-python-extensions/// - nifi.python.extensions.source.directory.custom: > - /nifi-python-extensions/nifi-talk/processors/ ----- - -=== 3. Use PersistentVolume - -You can also mount a PVC below `/nifi-python-extensions` using podOverrides and shell into the NiFi Pod to make changes. -However, the <> or <> approach is recommended. - -== Check processors have been loaded - -NiFi logs every Python processor it found. -You can use that to check if the processors have been loaded. - -[source,console] ----- -$ kubectl logs nifi-2-0-0-node-default-0 -c nifi \ - | grep 'Discovered.*Python Processor' -… INFO [main] … Discovered Python Processor PythonZgrepProcessor -… INFO [main] … Discovered Python Processor TransformOpenskyStates -… INFO [main] … Discovered Python Processor UpdateAttributeFileLookup -… INFO [main] … Discovered or updated 3 Python Processors in 64 millis ----- diff --git a/docs/modules/nifi/pages/usage_guide/custom-components/index.adoc b/docs/modules/nifi/pages/usage_guide/custom-components/index.adoc deleted file mode 100644 index 710b8812..00000000 --- a/docs/modules/nifi/pages/usage_guide/custom-components/index.adoc +++ /dev/null @@ -1,9 +0,0 @@ -= Loading custom components -:description: Load custom NiFi components for enhanced functionality. - -You can develop or use custom components for Apache NiFi, typically custom processors, to extend its functionality. - -There are currently two types of custom components: - -1. xref:nifi:usage_guide/custom-components/custom-nars.adoc[] -2. Starting with NiFi 2.0 you can also use xref:nifi:usage_guide/custom-components/custom-python-processors.adoc[] From 5c8add4da51f87dfbffe3f4cf7d33a8f18d72e9a Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Tue, 27 May 2025 11:21:10 +0200 Subject: [PATCH 15/24] Configure Python regardless of git-sync --- rust/operator-binary/src/config/mod.rs | 84 ++++++++++++++------------ rust/operator-binary/src/controller.rs | 17 +++--- 2 files changed, 54 insertions(+), 47 deletions(-) diff --git a/rust/operator-binary/src/config/mod.rs b/rust/operator-binary/src/config/mod.rs index 10573504..08b24254 100644 --- a/rust/operator-binary/src/config/mod.rs +++ b/rust/operator-binary/src/config/mod.rs @@ -578,45 +578,49 @@ pub fn build_nifi_properties( //#################### // Custom components # //#################### - if git_sync_resources.is_git_sync_enabled() { - // NiFi 1.x does not support Python components. The Python configuration is just ignored. + // NiFi 1.x does not support Python components and the Python configuration below is just + // ignored. - // The command used to launch Python. - // This property must be set to enable Python-based processors. - properties.insert("nifi.python.command".to_string(), "python3".to_string()); + // The command used to launch Python. + // This property must be set to enable Python-based processors. + properties.insert("nifi.python.command".to_string(), "python3".to_string()); - // The directory that contains the Python framework for communicating between the Python and - // Java processes. - properties.insert( - "nifi.python.framework.source.directory".to_string(), - "/stackable/nifi/python/framework/".to_string(), - ); + // The directory that contains the Python framework for communicating between the Python and + // Java processes. + properties.insert( + "nifi.python.framework.source.directory".to_string(), + "/stackable/nifi/python/framework/".to_string(), + ); + + // The working directory where NiFi should store artifacts; + // This property defaults to ./work/python but if you want to mount an emptyDir for the working + // directory then another directory has to be set to avoid ownership conflicts with ./work/nar. + properties.insert( + "nifi.python.working.directory".to_string(), + NIFI_PYTHON_WORKING_DIRECTORY.to_string(), + ); - // The working directory where NiFi should store artifacts; - // This property defaults to ./work/python but if you want to mount an - // emptyDir for the working directory then another directory has to be - // set to avoid ownership conflicts with ./work/nar. + // The default directory that NiFi should look in to find custom Python-based components. + // This directory is mentioned in the documentation + // (docs/modules/nifi/pages/usage_guide/custom-components.adoc), so do not change it! + properties.insert( + "nifi.python.extensions.source.directory.default".to_string(), + "/stackable/nifi/python/extensions/".to_string(), + ); + + for (i, git_folder) in git_sync_resources + .git_content_folders_as_string() + .into_iter() + .enumerate() + { + // The directory that NiFi should look in to find custom Python-based components. properties.insert( - "nifi.python.working.directory".to_string(), - NIFI_PYTHON_WORKING_DIRECTORY.to_string(), + format!("nifi.python.extensions.source.directory.{i}"), + git_folder.clone(), ); - for (i, git_folder) in git_sync_resources - .git_content_folders_as_string() - .into_iter() - .enumerate() - { - // The directory that NiFi should look in to find custom Python-based - // components. - properties.insert( - format!("nifi.python.extensions.source.directory.{i}"), - git_folder.clone(), - ); - - // The directory that NiFi should look in to find custom Java-based - // components. - properties.insert(format!("nifi.nar.library.directory.{i}"), git_folder); - } + // The directory that NiFi should look in to find custom Java-based components. + properties.insert(format!("nifi.nar.library.directory.{i}"), git_folder); } //########################## @@ -744,7 +748,9 @@ mod tests { "#; let bootstrap_conf = construct_bootstrap_conf(input); - assert_eq!(bootstrap_conf, indoc! {" + assert_eq!( + bootstrap_conf, + indoc! {" conf.dir=./conf graceful.shutdown.seconds=300 java=java @@ -763,7 +769,8 @@ mod tests { lib.dir=./lib preserve.environment=false run.as= - "}); + "} + ); } #[test] @@ -809,7 +816,9 @@ mod tests { "#; let bootstrap_conf = construct_bootstrap_conf(input); - assert_eq!(bootstrap_conf, indoc! {" + assert_eq!( + bootstrap_conf, + indoc! {" conf.dir=./conf graceful.shutdown.seconds=300 java=java @@ -830,7 +839,8 @@ mod tests { lib.dir=./lib preserve.environment=false run.as= - "}); + "} + ); } fn construct_bootstrap_conf(nifi_cluster: &str) -> String { diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index 0cd30255..9123be1f 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -1207,16 +1207,13 @@ async fn build_node_rolegroup_statefulset( .context(AddVolumeMountSnafu)?; } - if git_sync_resources.is_git_sync_enabled() { - let volume_name = "nifi-python-working-directory".to_string(); - - pod_builder - .add_empty_dir_volume(&volume_name, None) - .context(AddVolumeSnafu)?; - container_nifi - .add_volume_mount(&volume_name, NIFI_PYTHON_WORKING_DIRECTORY) - .context(AddVolumeMountSnafu)?; - } + let volume_name = "nifi-python-working-directory".to_string(); + pod_builder + .add_empty_dir_volume(&volume_name, None) + .context(AddVolumeSnafu)?; + container_nifi + .add_volume_mount(&volume_name, NIFI_PYTHON_WORKING_DIRECTORY) + .context(AddVolumeMountSnafu)?; container_nifi .add_volume_mounts(git_sync_resources.git_content_volume_mounts.to_owned()) From 3792dbe43cd13a4feaf69678dbdae98136c7ce2a Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Tue, 27 May 2025 11:25:02 +0200 Subject: [PATCH 16/24] Format Rust code --- rust/operator-binary/src/controller.rs | 8 +-- rust/operator-binary/src/crd/affinity.rs | 65 +++++++++++++----------- rust/operator-binary/src/main.rs | 11 ++-- 3 files changed, 45 insertions(+), 39 deletions(-) diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index 9123be1f..1ea112f9 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -1289,10 +1289,10 @@ async fn build_node_rolegroup_statefulset( } authentication_config - .add_volumes_and_mounts(&mut pod_builder, vec![ - &mut container_prepare, - container_nifi, - ]) + .add_volumes_and_mounts( + &mut pod_builder, + vec![&mut container_prepare, container_nifi], + ) .context(AddAuthVolumesSnafu)?; let metadata = ObjectMetaBuilder::new() diff --git a/rust/operator-binary/src/crd/affinity.rs b/rust/operator-binary/src/crd/affinity.rs index bb1ed6f9..ac1e570e 100644 --- a/rust/operator-binary/src/crd/affinity.rs +++ b/rust/operator-binary/src/crd/affinity.rs @@ -61,36 +61,39 @@ mod tests { serde_yaml::with::singleton_map_recursive::deserialize(deserializer).unwrap(); let merged_config = nifi.merged_config(&NifiRole::Node, "default").unwrap(); - assert_eq!(merged_config.affinity, StackableAffinity { - pod_affinity: None, - pod_anti_affinity: Some(PodAntiAffinity { - preferred_during_scheduling_ignored_during_execution: Some(vec![ - WeightedPodAffinityTerm { - pod_affinity_term: PodAffinityTerm { - label_selector: Some(LabelSelector { - match_expressions: None, - match_labels: Some(BTreeMap::from([ - ("app.kubernetes.io/name".to_string(), "nifi".to_string(),), - ( - "app.kubernetes.io/instance".to_string(), - "simple-nifi".to_string(), - ), - ( - "app.kubernetes.io/component".to_string(), - "node".to_string(), - ) - ])) - }), - topology_key: "kubernetes.io/hostname".to_string(), - ..Default::default() - }, - weight: 70 - } - ]), - required_during_scheduling_ignored_during_execution: None, - }), - node_affinity: None, - node_selector: None, - }); + assert_eq!( + merged_config.affinity, + StackableAffinity { + pod_affinity: None, + pod_anti_affinity: Some(PodAntiAffinity { + preferred_during_scheduling_ignored_during_execution: Some(vec![ + WeightedPodAffinityTerm { + pod_affinity_term: PodAffinityTerm { + label_selector: Some(LabelSelector { + match_expressions: None, + match_labels: Some(BTreeMap::from([ + ("app.kubernetes.io/name".to_string(), "nifi".to_string(),), + ( + "app.kubernetes.io/instance".to_string(), + "simple-nifi".to_string(), + ), + ( + "app.kubernetes.io/component".to_string(), + "node".to_string(), + ) + ])) + }), + topology_key: "kubernetes.io/hostname".to_string(), + ..Default::default() + }, + weight: 70 + } + ]), + required_during_scheduling_ignored_during_execution: None, + }), + node_affinity: None, + node_selector: None, + } + ); } } diff --git a/rust/operator-binary/src/main.rs b/rust/operator-binary/src/main.rs index e445abbc..bf38fe52 100644 --- a/rust/operator-binary/src/main.rs +++ b/rust/operator-binary/src/main.rs @@ -91,10 +91,13 @@ async fn main() -> anyhow::Result<()> { ) .await?; - let event_recorder = Arc::new(Recorder::new(client.as_kube_client(), Reporter { - controller: NIFI_FULL_CONTROLLER_NAME.to_string(), - instance: None, - })); + let event_recorder = Arc::new(Recorder::new( + client.as_kube_client(), + Reporter { + controller: NIFI_FULL_CONTROLLER_NAME.to_string(), + instance: None, + }, + )); let nifi_controller = Controller::new( watch_namespace.get_api::>(&client), From bada2d7948cf5976f7519298a616bc6cfb4f65f9 Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Tue, 27 May 2025 11:57:57 +0200 Subject: [PATCH 17/24] Re-format Rust code --- rust/operator-binary/src/config/mod.rs | 14 ++--- rust/operator-binary/src/controller.rs | 8 +-- rust/operator-binary/src/crd/affinity.rs | 65 +++++++++++------------- rust/operator-binary/src/main.rs | 11 ++-- 4 files changed, 43 insertions(+), 55 deletions(-) diff --git a/rust/operator-binary/src/config/mod.rs b/rust/operator-binary/src/config/mod.rs index 08b24254..5a2d9680 100644 --- a/rust/operator-binary/src/config/mod.rs +++ b/rust/operator-binary/src/config/mod.rs @@ -748,9 +748,7 @@ mod tests { "#; let bootstrap_conf = construct_bootstrap_conf(input); - assert_eq!( - bootstrap_conf, - indoc! {" + assert_eq!(bootstrap_conf, indoc! {" conf.dir=./conf graceful.shutdown.seconds=300 java=java @@ -769,8 +767,7 @@ mod tests { lib.dir=./lib preserve.environment=false run.as= - "} - ); + "}); } #[test] @@ -816,9 +813,7 @@ mod tests { "#; let bootstrap_conf = construct_bootstrap_conf(input); - assert_eq!( - bootstrap_conf, - indoc! {" + assert_eq!(bootstrap_conf, indoc! {" conf.dir=./conf graceful.shutdown.seconds=300 java=java @@ -839,8 +834,7 @@ mod tests { lib.dir=./lib preserve.environment=false run.as= - "} - ); + "}); } fn construct_bootstrap_conf(nifi_cluster: &str) -> String { diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index 1ea112f9..9123be1f 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -1289,10 +1289,10 @@ async fn build_node_rolegroup_statefulset( } authentication_config - .add_volumes_and_mounts( - &mut pod_builder, - vec![&mut container_prepare, container_nifi], - ) + .add_volumes_and_mounts(&mut pod_builder, vec![ + &mut container_prepare, + container_nifi, + ]) .context(AddAuthVolumesSnafu)?; let metadata = ObjectMetaBuilder::new() diff --git a/rust/operator-binary/src/crd/affinity.rs b/rust/operator-binary/src/crd/affinity.rs index ac1e570e..bb1ed6f9 100644 --- a/rust/operator-binary/src/crd/affinity.rs +++ b/rust/operator-binary/src/crd/affinity.rs @@ -61,39 +61,36 @@ mod tests { serde_yaml::with::singleton_map_recursive::deserialize(deserializer).unwrap(); let merged_config = nifi.merged_config(&NifiRole::Node, "default").unwrap(); - assert_eq!( - merged_config.affinity, - StackableAffinity { - pod_affinity: None, - pod_anti_affinity: Some(PodAntiAffinity { - preferred_during_scheduling_ignored_during_execution: Some(vec![ - WeightedPodAffinityTerm { - pod_affinity_term: PodAffinityTerm { - label_selector: Some(LabelSelector { - match_expressions: None, - match_labels: Some(BTreeMap::from([ - ("app.kubernetes.io/name".to_string(), "nifi".to_string(),), - ( - "app.kubernetes.io/instance".to_string(), - "simple-nifi".to_string(), - ), - ( - "app.kubernetes.io/component".to_string(), - "node".to_string(), - ) - ])) - }), - topology_key: "kubernetes.io/hostname".to_string(), - ..Default::default() - }, - weight: 70 - } - ]), - required_during_scheduling_ignored_during_execution: None, - }), - node_affinity: None, - node_selector: None, - } - ); + assert_eq!(merged_config.affinity, StackableAffinity { + pod_affinity: None, + pod_anti_affinity: Some(PodAntiAffinity { + preferred_during_scheduling_ignored_during_execution: Some(vec![ + WeightedPodAffinityTerm { + pod_affinity_term: PodAffinityTerm { + label_selector: Some(LabelSelector { + match_expressions: None, + match_labels: Some(BTreeMap::from([ + ("app.kubernetes.io/name".to_string(), "nifi".to_string(),), + ( + "app.kubernetes.io/instance".to_string(), + "simple-nifi".to_string(), + ), + ( + "app.kubernetes.io/component".to_string(), + "node".to_string(), + ) + ])) + }), + topology_key: "kubernetes.io/hostname".to_string(), + ..Default::default() + }, + weight: 70 + } + ]), + required_during_scheduling_ignored_during_execution: None, + }), + node_affinity: None, + node_selector: None, + }); } } diff --git a/rust/operator-binary/src/main.rs b/rust/operator-binary/src/main.rs index bf38fe52..e445abbc 100644 --- a/rust/operator-binary/src/main.rs +++ b/rust/operator-binary/src/main.rs @@ -91,13 +91,10 @@ async fn main() -> anyhow::Result<()> { ) .await?; - let event_recorder = Arc::new(Recorder::new( - client.as_kube_client(), - Reporter { - controller: NIFI_FULL_CONTROLLER_NAME.to_string(), - instance: None, - }, - )); + let event_recorder = Arc::new(Recorder::new(client.as_kube_client(), Reporter { + controller: NIFI_FULL_CONTROLLER_NAME.to_string(), + instance: None, + })); let nifi_controller = Controller::new( watch_namespace.get_api::>(&client), From ba2f47b0b4e463f4927a904cadd1d0b3a8a8382d Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Tue, 27 May 2025 14:28:55 +0200 Subject: [PATCH 18/24] Upgrade stackable-operator --- Cargo.lock | 22 ++++++++----- Cargo.nix | 81 +++++++++++++++++++++++++++++++---------------- Cargo.toml | 2 +- crate-hashes.json | 14 ++++---- 4 files changed, 76 insertions(+), 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6f4131fa..06883a8a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1454,10 +1454,11 @@ dependencies = [ [[package]] name = "k8s-version" version = "0.1.3" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.93.1#d990019c690ed85aea29b095a03661c015676caa" +source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.93.2#e0309b9f85413846984ff16f6e522d0f720dee43" dependencies = [ "darling", "regex", + "serde", "snafu 0.8.5", ] @@ -2664,8 +2665,8 @@ dependencies = [ [[package]] name = "stackable-operator" -version = "0.93.1" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.93.1#d990019c690ed85aea29b095a03661c015676caa" +version = "0.93.2" +source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.93.2#e0309b9f85413846984ff16f6e522d0f720dee43" dependencies = [ "chrono", "clap", @@ -2702,7 +2703,7 @@ dependencies = [ [[package]] name = "stackable-operator-derive" version = "0.3.1" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.93.1#d990019c690ed85aea29b095a03661c015676caa" +source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.93.2#e0309b9f85413846984ff16f6e522d0f720dee43" dependencies = [ "darling", "proc-macro2", @@ -2713,7 +2714,7 @@ dependencies = [ [[package]] name = "stackable-shared" version = "0.0.1" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.93.1#d990019c690ed85aea29b095a03661c015676caa" +source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.93.2#e0309b9f85413846984ff16f6e522d0f720dee43" dependencies = [ "kube", "semver", @@ -2725,7 +2726,7 @@ dependencies = [ [[package]] name = "stackable-telemetry" version = "0.6.0" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.93.1#d990019c690ed85aea29b095a03661c015676caa" +source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.93.2#e0309b9f85413846984ff16f6e522d0f720dee43" dependencies = [ "axum", "clap", @@ -2748,15 +2749,20 @@ dependencies = [ [[package]] name = "stackable-versioned" version = "0.7.1" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.93.1#d990019c690ed85aea29b095a03661c015676caa" +source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.93.2#e0309b9f85413846984ff16f6e522d0f720dee43" dependencies = [ + "k8s-version", + "schemars", + "serde", + "serde_json", + "serde_yaml", "stackable-versioned-macros", ] [[package]] name = "stackable-versioned-macros" version = "0.7.1" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.93.1#d990019c690ed85aea29b095a03661c015676caa" +source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.93.2#e0309b9f85413846984ff16f6e522d0f720dee43" dependencies = [ "convert_case", "darling", diff --git a/Cargo.nix b/Cargo.nix index d48c1ef0..d8ead010 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -693,7 +693,7 @@ rec { "tracing" = [ "dep:tracing" "axum-core/tracing" ]; "ws" = [ "dep:hyper" "tokio" "dep:tokio-tungstenite" "dep:sha1" "dep:base64" ]; }; - resolvedDefaultFeatures = [ "default" "form" "http1" "json" "matched-path" "original-uri" "query" "tokio" "tower-log" "tracing" ]; + resolvedDefaultFeatures = [ "default" "form" "http1" "http2" "json" "matched-path" "original-uri" "query" "tokio" "tower-log" "tracing" ]; }; "axum-core" = rec { crateName = "axum-core"; @@ -3661,7 +3661,7 @@ rec { "tokio" = [ "dep:tokio" "tokio/net" "tokio/rt" "tokio/time" ]; "tracing" = [ "dep:tracing" ]; }; - resolvedDefaultFeatures = [ "client" "client-legacy" "default" "http1" "server" "service" "tokio" "tracing" ]; + resolvedDefaultFeatures = [ "client" "client-legacy" "default" "http1" "http2" "server" "service" "tokio" "tracing" ]; }; "iana-time-zone" = rec { crateName = "iana-time-zone"; @@ -4568,8 +4568,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech/operator-rs.git"; - rev = "d990019c690ed85aea29b095a03661c015676caa"; - sha256 = "16faz0f3dsv095hk94kmb7mk3pr6lban1v3k0g6yawak6gk5xln6"; + rev = "e0309b9f85413846984ff16f6e522d0f720dee43"; + sha256 = "0604b0vvlzhh7a6pmcnm6k72pnkcv3f171n9981vj06wsbj9j98r"; }; libName = "k8s_version"; authors = [ @@ -4585,6 +4585,12 @@ rec { name = "regex"; packageId = "regex"; } + { + name = "serde"; + packageId = "serde"; + optional = true; + features = [ "derive" ]; + } { name = "snafu"; packageId = "snafu 0.8.5"; @@ -4594,7 +4600,7 @@ rec { "darling" = [ "dep:darling" ]; "serde" = [ "dep:serde" ]; }; - resolvedDefaultFeatures = [ "darling" ]; + resolvedDefaultFeatures = [ "darling" "serde" ]; }; "kube" = rec { crateName = "kube"; @@ -8685,13 +8691,13 @@ rec { }; "stackable-operator" = rec { crateName = "stackable-operator"; - version = "0.93.1"; + version = "0.93.2"; edition = "2024"; workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech/operator-rs.git"; - rev = "d990019c690ed85aea29b095a03661c015676caa"; - sha256 = "16faz0f3dsv095hk94kmb7mk3pr6lban1v3k0g6yawak6gk5xln6"; + rev = "e0309b9f85413846984ff16f6e522d0f720dee43"; + sha256 = "0604b0vvlzhh7a6pmcnm6k72pnkcv3f171n9981vj06wsbj9j98r"; }; libName = "stackable_operator"; authors = [ @@ -8849,8 +8855,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech/operator-rs.git"; - rev = "d990019c690ed85aea29b095a03661c015676caa"; - sha256 = "16faz0f3dsv095hk94kmb7mk3pr6lban1v3k0g6yawak6gk5xln6"; + rev = "e0309b9f85413846984ff16f6e522d0f720dee43"; + sha256 = "0604b0vvlzhh7a6pmcnm6k72pnkcv3f171n9981vj06wsbj9j98r"; }; procMacro = true; libName = "stackable_operator_derive"; @@ -8884,8 +8890,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech/operator-rs.git"; - rev = "d990019c690ed85aea29b095a03661c015676caa"; - sha256 = "16faz0f3dsv095hk94kmb7mk3pr6lban1v3k0g6yawak6gk5xln6"; + rev = "e0309b9f85413846984ff16f6e522d0f720dee43"; + sha256 = "0604b0vvlzhh7a6pmcnm6k72pnkcv3f171n9981vj06wsbj9j98r"; }; libName = "stackable_shared"; authors = [ @@ -8925,8 +8931,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech/operator-rs.git"; - rev = "d990019c690ed85aea29b095a03661c015676caa"; - sha256 = "16faz0f3dsv095hk94kmb7mk3pr6lban1v3k0g6yawak6gk5xln6"; + rev = "e0309b9f85413846984ff16f6e522d0f720dee43"; + sha256 = "0604b0vvlzhh7a6pmcnm6k72pnkcv3f171n9981vj06wsbj9j98r"; }; libName = "stackable_telemetry"; authors = [ @@ -8936,6 +8942,7 @@ rec { { name = "axum"; packageId = "axum"; + features = [ "http2" ]; } { name = "clap"; @@ -9030,14 +9037,42 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech/operator-rs.git"; - rev = "d990019c690ed85aea29b095a03661c015676caa"; - sha256 = "16faz0f3dsv095hk94kmb7mk3pr6lban1v3k0g6yawak6gk5xln6"; + rev = "e0309b9f85413846984ff16f6e522d0f720dee43"; + sha256 = "0604b0vvlzhh7a6pmcnm6k72pnkcv3f171n9981vj06wsbj9j98r"; }; libName = "stackable_versioned"; authors = [ "Stackable GmbH " ]; dependencies = [ + { + name = "k8s-version"; + packageId = "k8s-version"; + optional = true; + features = [ "serde" ]; + } + { + name = "schemars"; + packageId = "schemars"; + optional = true; + features = [ "url" ]; + } + { + name = "serde"; + packageId = "serde"; + optional = true; + features = [ "derive" ]; + } + { + name = "serde_json"; + packageId = "serde_json"; + optional = true; + } + { + name = "serde_yaml"; + packageId = "serde_yaml"; + optional = true; + } { name = "stackable-versioned-macros"; packageId = "stackable-versioned-macros"; @@ -9045,7 +9080,7 @@ rec { ]; features = { "full" = [ "k8s" ]; - "k8s" = [ "stackable-versioned-macros/k8s" ]; + "k8s" = [ "stackable-versioned-macros/k8s" "dep:k8s-version" "dep:serde_json" "dep:serde_yaml" "dep:schemars" "dep:serde" ]; }; resolvedDefaultFeatures = [ "k8s" ]; }; @@ -9056,8 +9091,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech/operator-rs.git"; - rev = "d990019c690ed85aea29b095a03661c015676caa"; - sha256 = "16faz0f3dsv095hk94kmb7mk3pr6lban1v3k0g6yawak6gk5xln6"; + rev = "e0309b9f85413846984ff16f6e522d0f720dee43"; + sha256 = "0604b0vvlzhh7a6pmcnm6k72pnkcv3f171n9981vj06wsbj9j98r"; }; procMacro = true; libName = "stackable_versioned_macros"; @@ -9109,14 +9144,6 @@ rec { packageId = "syn 2.0.100"; } ]; - devDependencies = [ - { - name = "k8s-openapi"; - packageId = "k8s-openapi"; - usesDefaultFeatures = false; - features = [ "schemars" "v1_33" ]; - } - ]; features = { "full" = [ "k8s" ]; "k8s" = [ "dep:kube" "dep:k8s-openapi" ]; diff --git a/Cargo.toml b/Cargo.toml index 8369da44..64adb8ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ repository = "https://github.com/stackabletech/nifi-operator" [workspace.dependencies] product-config = { git = "https://github.com/stackabletech/product-config.git", tag = "0.7.0" } -stackable-operator = { git = "https://github.com/stackabletech/operator-rs.git", features = ["telemetry", "versioned"], tag = "stackable-operator-0.93.1" } +stackable-operator = { git = "https://github.com/stackabletech/operator-rs.git", features = ["telemetry", "versioned"], tag = "stackable-operator-0.93.2" } anyhow = "1.0" built = { version = "0.7", features = ["chrono", "git2"] } diff --git a/crate-hashes.json b/crate-hashes.json index 81f487ff..3b97911f 100644 --- a/crate-hashes.json +++ b/crate-hashes.json @@ -1,10 +1,10 @@ { - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.93.1#k8s-version@0.1.3": "16faz0f3dsv095hk94kmb7mk3pr6lban1v3k0g6yawak6gk5xln6", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.93.1#stackable-operator-derive@0.3.1": "16faz0f3dsv095hk94kmb7mk3pr6lban1v3k0g6yawak6gk5xln6", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.93.1#stackable-operator@0.93.1": "16faz0f3dsv095hk94kmb7mk3pr6lban1v3k0g6yawak6gk5xln6", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.93.1#stackable-shared@0.0.1": "16faz0f3dsv095hk94kmb7mk3pr6lban1v3k0g6yawak6gk5xln6", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.93.1#stackable-telemetry@0.6.0": "16faz0f3dsv095hk94kmb7mk3pr6lban1v3k0g6yawak6gk5xln6", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.93.1#stackable-versioned-macros@0.7.1": "16faz0f3dsv095hk94kmb7mk3pr6lban1v3k0g6yawak6gk5xln6", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.93.1#stackable-versioned@0.7.1": "16faz0f3dsv095hk94kmb7mk3pr6lban1v3k0g6yawak6gk5xln6", + "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.93.2#k8s-version@0.1.3": "0604b0vvlzhh7a6pmcnm6k72pnkcv3f171n9981vj06wsbj9j98r", + "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.93.2#stackable-operator-derive@0.3.1": "0604b0vvlzhh7a6pmcnm6k72pnkcv3f171n9981vj06wsbj9j98r", + "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.93.2#stackable-operator@0.93.2": "0604b0vvlzhh7a6pmcnm6k72pnkcv3f171n9981vj06wsbj9j98r", + "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.93.2#stackable-shared@0.0.1": "0604b0vvlzhh7a6pmcnm6k72pnkcv3f171n9981vj06wsbj9j98r", + "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.93.2#stackable-telemetry@0.6.0": "0604b0vvlzhh7a6pmcnm6k72pnkcv3f171n9981vj06wsbj9j98r", + "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.93.2#stackable-versioned-macros@0.7.1": "0604b0vvlzhh7a6pmcnm6k72pnkcv3f171n9981vj06wsbj9j98r", + "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.93.2#stackable-versioned@0.7.1": "0604b0vvlzhh7a6pmcnm6k72pnkcv3f171n9981vj06wsbj9j98r", "git+https://github.com/stackabletech/product-config.git?tag=0.7.0#product-config@0.7.0": "0gjsm80g6r75pm3824dcyiz4ysq1ka4c1if6k1mjm9cnd5ym0gny" } \ No newline at end of file From 648e075e58d23bfb92ee26764142cbeb29785fb0 Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Tue, 27 May 2025 14:30:15 +0200 Subject: [PATCH 19/24] Improve the documentation --- docs/modules/nifi/pages/usage_guide/custom-components.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/nifi/pages/usage_guide/custom-components.adoc b/docs/modules/nifi/pages/usage_guide/custom-components.adoc index 757ae6a3..1d1c097a 100644 --- a/docs/modules/nifi/pages/usage_guide/custom-components.adoc +++ b/docs/modules/nifi/pages/usage_guide/custom-components.adoc @@ -207,7 +207,7 @@ Also read the xref:guides:custom-images.adoc[Using customized product images] gu If you do not want to create a custom image or do not have access to an image registry, you can use the extra volume mount functionality to mount a volume containing your custom components and configure NiFi to read these from the mounted volumes. -For this to work you'll need to prepare a PersistentVolumeClaim (PVC) containing your components. +For this to work, you will need to prepare a PersistentVolumeClaim (PVC) containing your components. Usually the best way to do this is to mount the PVC into a temporary container and then `kubectl cp` the NAR or Python files into that volume. The following listing shows an example of creating the PVC and the Pod: From 7e027a0dd8d7ff44946a2b17363e74b8a8380ee4 Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Tue, 27 May 2025 14:46:08 +0200 Subject: [PATCH 20/24] Fix documentation URL --- docs/modules/nifi/pages/usage_guide/custom-components.adoc | 5 +++++ rust/operator-binary/src/crd/mod.rs | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/modules/nifi/pages/usage_guide/custom-components.adoc b/docs/modules/nifi/pages/usage_guide/custom-components.adoc index 1d1c097a..210aa925 100644 --- a/docs/modules/nifi/pages/usage_guide/custom-components.adoc +++ b/docs/modules/nifi/pages/usage_guide/custom-components.adoc @@ -1,3 +1,4 @@ +[#custom-components] = Loading custom components :description: Load custom NiFi components for enhanced functionality. :nifi-docs-developers-guide: https://nifi.apache.org/docs/nifi-docs/html/developer-guide.html @@ -37,6 +38,7 @@ $ kubectl logs nifi-node-default-0 ---- ==== +[#git-sync] == Synchronize with git-sync Custom NiFi components can be synchronized from a Git repository directly into the NiFi pods with git-sync. @@ -101,6 +103,7 @@ It cannot be specified, if a repository contains NiFi Archives or Python compone The operator just configures each repository for both types. In particular, the parameters `nifi.nar.library.directory.+*+` and `nifi.python.extensions.source.directory.+*+` are set. +[#config-map] == Mount as a ConfigMap Custom components can also be stored in ConfigMaps. @@ -176,6 +179,7 @@ spec: <1> Specify your ConfigMaps here. <2> The directory name after `userdata` has to match the name of the volume, while `myCustomLibs` is a name you can freely change. +[#custom-image] == Custom Docker image You can extend the official Stackable NiFi image by copying the required NAR files into NiFi's classpath or the Python files into the default extension directory. @@ -203,6 +207,7 @@ spec: Also read the xref:guides:custom-images.adoc[Using customized product images] guide for additional information. +[#pvc] == Using the official image If you do not want to create a custom image or do not have access to an image registry, you can use the extra volume mount functionality to mount a volume containing your custom components and configure NiFi to read these from the mounted volumes. diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index 35d2ba21..569ea457 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -153,7 +153,7 @@ pub mod versioned { /// The `customComponentsGitSync` setting allows configuring custom components to mount via `git-sync`. /// Learn more in the - /// [Custom Python processors documentation](DOCS_BASE_URL_PLACEHOLDER/nifi/usage_guide/custom-components/custom-python-processors/#git-sync). + /// [Custom Python processors documentation](DOCS_BASE_URL_PLACEHOLDER/nifi/usage_guide/custom-components.html#git_sync). #[serde(default)] pub custom_components_git_sync: Vec, From ef61c87311ad2f57484c33245c9ed50908a1c785 Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Tue, 27 May 2025 14:54:55 +0200 Subject: [PATCH 21/24] Fix documentation URL --- rust/operator-binary/src/crd/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index 569ea457..ceb7afa7 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -152,8 +152,8 @@ pub mod versioned { pub zookeeper_config_map_name: String, /// The `customComponentsGitSync` setting allows configuring custom components to mount via `git-sync`. - /// Learn more in the - /// [Custom Python processors documentation](DOCS_BASE_URL_PLACEHOLDER/nifi/usage_guide/custom-components.html#git_sync). + /// Learn more in the documentation for + /// [Loading custom components](DOCS_BASE_URL_PLACEHOLDER/nifi/usage_guide/custom-components.html#git_sync). #[serde(default)] pub custom_components_git_sync: Vec, From 78dc3c0586a0bd3703c178a5b5824b33d73ac73e Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Tue, 27 May 2025 15:21:59 +0200 Subject: [PATCH 22/24] test: Use fixed Git commits for the custom processors --- .../kuttl/custom-components-git-sync/30-install-nifi.yaml.j2 | 4 ++-- tests/templates/kuttl/logging/04-install-nifi.yaml.j2 | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/templates/kuttl/custom-components-git-sync/30-install-nifi.yaml.j2 b/tests/templates/kuttl/custom-components-git-sync/30-install-nifi.yaml.j2 index fafa01c1..a6153bd6 100644 --- a/tests/templates/kuttl/custom-components-git-sync/30-install-nifi.yaml.j2 +++ b/tests/templates/kuttl/custom-components-git-sync/30-install-nifi.yaml.j2 @@ -39,10 +39,10 @@ spec: autoGenerate: true customComponentsGitSync: - repo: https://github.com/stackabletech/nifi-operator - branch: feat/custom-processors # TODO Change to commit + branch: ef61c87311ad2f57484c33245c9ed50908a1c785 gitFolder: tests/templates/kuttl/custom-components-git-sync/java-processors - repo: https://github.com/stackabletech/nifi-operator - branch: feat/custom-processors # TODO Change to commit + branch: ef61c87311ad2f57484c33245c9ed50908a1c785 gitFolder: tests/templates/kuttl/custom-components-git-sync/python-processors {% if lookup('env', 'VECTOR_AGGREGATOR') %} vectorAggregatorConfigMapName: vector-aggregator-discovery diff --git a/tests/templates/kuttl/logging/04-install-nifi.yaml.j2 b/tests/templates/kuttl/logging/04-install-nifi.yaml.j2 index 41b2a423..e1f93839 100644 --- a/tests/templates/kuttl/logging/04-install-nifi.yaml.j2 +++ b/tests/templates/kuttl/logging/04-install-nifi.yaml.j2 @@ -98,7 +98,7 @@ spec: keySecret: nifi-sensitive-property-key customProcessorsGitSync: - repo: https://github.com/stackabletech/nifi-operator - branch: feat/custom-processors # TODO Change to commit + branch: ef61c87311ad2f57484c33245c9ed50908a1c785 gitFolder: tests/templates/kuttl/custom-processors-git-sync/processors-0 vectorAggregatorConfigMapName: nifi-vector-aggregator-discovery zookeeperConfigMapName: test-nifi-znode From 3832bbe064874ba41a48942c4b84939b627185c1 Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Tue, 27 May 2025 16:30:42 +0200 Subject: [PATCH 23/24] Regenerate charts --- deploy/helm/nifi-operator/crds/crds.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/helm/nifi-operator/crds/crds.yaml b/deploy/helm/nifi-operator/crds/crds.yaml index a1994d22..ad87b5a2 100644 --- a/deploy/helm/nifi-operator/crds/crds.yaml +++ b/deploy/helm/nifi-operator/crds/crds.yaml @@ -109,7 +109,7 @@ spec: type: object customComponentsGitSync: default: [] - description: The `customComponentsGitSync` setting allows configuring custom components to mount via `git-sync`. Learn more in the [Custom Python processors documentation](https://docs.stackable.tech/home/nightly/nifi/usage_guide/custom-components/custom-python-processors/#git-sync). + description: The `customComponentsGitSync` setting allows configuring custom components to mount via `git-sync`. Learn more in the documentation for [Loading custom components](https://docs.stackable.tech/home/nightly/nifi/usage_guide/custom-components.html#git_sync). items: properties: branch: From 88a0a5a3fd1cd86e280407739128ff09f5c01cae Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Wed, 28 May 2025 10:18:43 +0200 Subject: [PATCH 24/24] test: Fix the logging test --- tests/templates/kuttl/logging/04-install-nifi.yaml.j2 | 2 +- .../kuttl/logging/nifi-vector-aggregator-values.yaml.j2 | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/templates/kuttl/logging/04-install-nifi.yaml.j2 b/tests/templates/kuttl/logging/04-install-nifi.yaml.j2 index e1f93839..a04ef2a4 100644 --- a/tests/templates/kuttl/logging/04-install-nifi.yaml.j2 +++ b/tests/templates/kuttl/logging/04-install-nifi.yaml.j2 @@ -96,7 +96,7 @@ spec: - authenticationClass: simple-nifi-users sensitiveProperties: keySecret: nifi-sensitive-property-key - customProcessorsGitSync: + customComponentsGitSync: - repo: https://github.com/stackabletech/nifi-operator branch: ef61c87311ad2f57484c33245c9ed50908a1c785 gitFolder: tests/templates/kuttl/custom-processors-git-sync/processors-0 diff --git a/tests/templates/kuttl/logging/nifi-vector-aggregator-values.yaml.j2 b/tests/templates/kuttl/logging/nifi-vector-aggregator-values.yaml.j2 index c05f58cf..1c4d5f31 100644 --- a/tests/templates/kuttl/logging/nifi-vector-aggregator-values.yaml.j2 +++ b/tests/templates/kuttl/logging/nifi-vector-aggregator-values.yaml.j2 @@ -36,6 +36,12 @@ customConfig: condition: >- .pod == "test-nifi-node-automatic-log-config-0" && .container == "git-sync-0" + filteredAutomaticLogConfigNodeGitSyncInit: + type: filter + inputs: [validEvents] + condition: >- + .pod == "test-nifi-node-automatic-log-config-0" && + .container == "git-sync-0-init" filteredAutomaticLogConfigNodeVector: type: filter inputs: [validEvents]