Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
471 changes: 459 additions & 12 deletions Cargo.lock

Large diffs are not rendered by default.

1,796 changes: 1,715 additions & 81 deletions Cargo.nix

Large diffs are not rendered by default.

8 changes: 5 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,21 @@ edition = "2021"
repository = "https://github.com/stackabletech/commons-operator"

[workspace.dependencies]
stackable-operator = { git = "https://github.com/stackabletech/operator-rs.git", features = ["telemetry"], tag = "stackable-operator-0.100.1" }
stackable-operator = { git = "https://github.com/stackabletech/operator-rs.git", features = ["telemetry", "webhook"], tag = "stackable-operator-0.100.1" }

anyhow = "1.0"
built = { version = "0.8", features = ["chrono", "git2"] }
clap = "4.5"
futures = { version = "0.3", features = ["compat"] }
http = "1.3"
json-patch = "4.1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
snafu = "0.8"
strum = { version = "0.27", features = ["derive"] }
tokio = { version = "1.40", features = ["full"] }
tracing = "0.1"

# [patch."https://github.com/stackabletech/operator-rs.git"]
# stackable-operator = { git = "https://github.com/stackabletech//operator-rs.git", branch = "main" }
[patch."https://github.com/stackabletech/operator-rs.git"]
# stackable-operator = { path = "../operator-rs/crates/stackable-operator" }
stackable-operator = { git = "https://github.com/stackabletech//operator-rs.git", branch = "feat/mutating-webhook" }
63 changes: 63 additions & 0 deletions _TEST.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
---
apiVersion: v1
kind: ConfigMap
metadata:
name: web-config
data:
foo: bar
---
apiVersion: v1
kind: ConfigMap
metadata:
name: web-config-2
data:
foo: bar
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
labels:
restarter.stackable.tech/enabled: "true"
spec:
selector:
matchLabels:
app: nginx
serviceName: nginx
replicas: 3
template:
metadata:
labels:
app: nginx
spec:
terminationGracePeriodSeconds: 10
containers:
- name: nginx

Check failure on line 49 in _TEST.yaml

View workflow job for this annotation

GitHub Actions / pre-commit

49:9 [indentation] wrong indentation: expected 6 but found 8
image: registry.k8s.io/nginx-slim:0.24
ports:
- containerPort: 80

Check failure on line 52 in _TEST.yaml

View workflow job for this annotation

GitHub Actions / pre-commit

52:13 [indentation] wrong indentation: expected 10 but found 12
name: web
volumeMounts:
- name: config

Check failure on line 55 in _TEST.yaml

View workflow job for this annotation

GitHub Actions / pre-commit

55:13 [indentation] wrong indentation: expected 10 but found 12
mountPath: "/config"
envFrom:
- configMapRef:

Check failure on line 58 in _TEST.yaml

View workflow job for this annotation

GitHub Actions / pre-commit

58:13 [indentation] wrong indentation: expected 10 but found 12
name: web-config-2
volumes:
- name: config

Check failure on line 61 in _TEST.yaml

View workflow job for this annotation

GitHub Actions / pre-commit

61:9 [indentation] wrong indentation: expected 6 but found 8
configMap:
name: web-config
16 changes: 9 additions & 7 deletions crate-hashes.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions deploy/helm/commons-operator/templates/roles.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,10 @@ rules:
- pods/eviction
verbs:
- create
# Required to maintain MutatingWebhookConfigurations. The operator needs to do this, as it needs
# to enter e.g. it's generated certificate in the webhooks.
- apiGroups: [admissionregistration.k8s.io]
resources: [mutatingwebhookconfigurations]
verbs:
- create
- patch
1 change: 1 addition & 0 deletions rust/operator-binary/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ anyhow.workspace = true
clap.workspace = true
http.workspace = true
futures.workspace = true
json-patch.workspace = true
serde.workspace = true
serde_json.workspace = true
snafu.workspace = true
Expand Down
73 changes: 60 additions & 13 deletions rust/operator-binary/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
// This will need changes in our and upstream error types.
#![allow(clippy::large_enum_variant)]

mod restart_controller;

use anyhow::anyhow;
use clap::Parser;
use futures::FutureExt;
use futures::{FutureExt, TryFutureExt};
use restart_controller::statefulset::create_context;
use stackable_operator::{
YamlSchema as _,
cli::{Command, RunArguments},
Expand All @@ -17,16 +17,37 @@ use stackable_operator::{
shared::yaml::SerializeOptions,
telemetry::Tracing,
};
use webhook::create_webhook_server;

mod restart_controller;
mod utils;
mod webhook;

mod built_info {
include!(concat!(env!("OUT_DIR"), "/built.rs"));
}

pub const OPERATOR_NAME: &str = "commons.stackable.tech";
pub const FIELD_MANAGER: &str = "commons-operator";

#[derive(Parser)]
#[clap(about, author)]
struct Opts {
#[clap(subcommand)]
cmd: Command,
cmd: Command<CommonsOperatorRunArguments>,
}

#[derive(Debug, PartialEq, Eq, Parser)]
pub struct CommonsOperatorRunArguments {
#[command(flatten)]
pub common: RunArguments,

/// Don't start the controller mutating webhook and maintain the MutatingWebhookConfiguration.
///
/// The mutating webhook is used to prevent an unneeded restart of the first Pod of freshly
/// created StatefulSets. It can be turned off in case you can accept an unneeded Pod restart.
#[arg(long, env)]
pub disable_restarter_mutating_webhook: bool,
}

#[tokio::main]
Expand All @@ -41,12 +62,16 @@ async fn main() -> anyhow::Result<()> {
S3Bucket::merged_crd(S3BucketVersion::V1Alpha1)?
.print_yaml_schema(built_info::PKG_VERSION, SerializeOptions::default())?;
}
Command::Run(RunArguments {
product_config: _,
watch_namespace,
operator_environment: _,
maintenance,
common,
Command::Run(CommonsOperatorRunArguments {
common:
RunArguments {
product_config: _,
watch_namespace,
operator_environment,
maintenance,
common,
},
disable_restarter_mutating_webhook,
}) => {
// NOTE (@NickLarsenNZ): Before stackable-telemetry was used:
// - The console log level was set by `COMMONS_OPERATOR_LOG`, and is now `CONSOLE_LOG` (when using Tracing::pre_configured).
Expand Down Expand Up @@ -76,12 +101,34 @@ async fn main() -> anyhow::Result<()> {
)
.await?;

let sts_restart_controller =
restart_controller::statefulset::start(&client, &watch_namespace).map(anyhow::Ok);
let (ctx, cm_store_tx, secret_store_tx) = create_context(client.clone());
let sts_restart_controller = restart_controller::statefulset::start(
ctx.clone(),
cm_store_tx,
secret_store_tx,
&watch_namespace,
)
.map(anyhow::Ok);
let pod_restart_controller =
restart_controller::pod::start(&client, &watch_namespace).map(anyhow::Ok);

futures::try_join!(sts_restart_controller, pod_restart_controller, eos_checker)?;
let webhook_server = create_webhook_server(
ctx,
&operator_environment,
disable_restarter_mutating_webhook,
client.as_kube_client(),
)
.await?;
let webhook_server = webhook_server
.run()
.map_err(|err| anyhow!(err).context("failed to run webhook"));

futures::try_join!(
sts_restart_controller,
pod_restart_controller,
webhook_server,
eos_checker,
)?;
}
}

Expand Down
8 changes: 5 additions & 3 deletions rust/operator-binary/src/restart_controller/pod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ enum Error {
#[snafu(display(
"failed to parse expiry timestamp annotation ({annotation:?}: {value:?}) as RFC 3999"
))]
UnparseableExpiryTimestamp {
UnparsableExpiryTimestamp {
source: chrono::ParseError,
annotation: String,
value: String,
Expand All @@ -60,7 +60,7 @@ impl ReconcilerError for Error {
match self {
Error::PodHasNoName => None,
Error::PodHasNoNamespace => None,
Error::UnparseableExpiryTimestamp {
Error::UnparsableExpiryTimestamp {
source: _,
annotation: _,
value: _,
Expand All @@ -73,6 +73,8 @@ impl ReconcilerError for Error {
pub async fn start(client: &Client, watch_namespace: &WatchNamespace) {
let controller = Controller::new(
watch_namespace.get_api::<PartialObjectMeta<Pod>>(client),
// TODO: Can we only watch a subset of Pods with a specify label, e.g.
// vendor=Stackable to reduce the memory footprint?
watcher::Config::default(),
);
let event_recorder = Arc::new(Recorder::new(
Expand Down Expand Up @@ -124,7 +126,7 @@ async fn reconcile(pod: Arc<PartialObjectMeta<Pod>>, ctx: Arc<Ctx>) -> Result<Ac
.flatten()
.filter(|(k, _)| k.starts_with("restarter.stackable.tech/expires-at."))
.map(|(k, v)| {
DateTime::parse_from_rfc3339(v).context(UnparseableExpiryTimestampSnafu {
DateTime::parse_from_rfc3339(v).context(UnparsableExpiryTimestampSnafu {
annotation: k,
value: v,
})
Expand Down
Loading
Loading