Skip to content

Commit 41f8649

Browse files
committed
docs: Add/improve various (doc) comments
1 parent 1c44459 commit 41f8649

File tree

3 files changed

+107
-87
lines changed

3 files changed

+107
-87
lines changed

crates/stackable-operator/src/crd/maintainer.rs

Lines changed: 67 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,33 @@ pub struct CustomResourceDefinitionMaintainer {
5050
impl CustomResourceDefinitionMaintainer {
5151
/// Creates and returns a new [`CustomResourceDefinitionMaintainer`] which manages one or more
5252
/// custom resource definitions.
53+
///
54+
/// ## Parameters
55+
///
56+
/// This function expects four parameters:
57+
///
58+
/// - `client`: A [`Client`] to interact with the Kubernetes API server. It continuously patches
59+
/// the CRDs when the TLS certificate is rotated.
60+
/// - `certificate_rx`: A [`mpsc::Receiver`] to receive newly generated TLS certificates. The
61+
/// certificate data sent through the channel is used to set the caBundle in the conversion
62+
/// section of the CRD.
63+
/// - `definitions`: An iterator of [`CustomResourceDefinition`]s which should be maintained
64+
/// by this maintainer. If the iterator is empty, the maintainer returns early without doing
65+
/// any work. As such, a polling mechanism which waits for all futures should be used to
66+
/// prevent premature termination of the operator.
67+
/// - `options`: Provides [`CustomResourceDefinitionMaintainerOptions`] to customize various
68+
/// parts of the maintainer. In the future, this will be converted to a builder, to enable a
69+
/// cleaner API interface.
70+
///
71+
/// ## Return Values
72+
///
73+
/// This function returns a 2-tuple (pair) of values:
74+
///
75+
/// - The [`CustomResourceDefinitionMaintainer`] itself. This is used to run the maintainer.
76+
/// See [`CustomResourceDefinitionMaintainer::run`] for more details.
77+
/// - The [`oneshot::Receiver`] which will be used to send out a message once the initial
78+
/// CRD reconciliation ran. This signal can be used to trigger the deployment of custom
79+
/// resources defined by the maintained CRDs.
5380
pub fn new(
5481
client: Client,
5582
certificate_rx: mpsc::Receiver<Certificate>,
@@ -70,26 +97,36 @@ impl CustomResourceDefinitionMaintainer {
7097
(maintainer, initial_reconcile_rx)
7198
}
7299

100+
/// Runs the [`CustomResourceDefinitionMaintainer`] asynchronously.
101+
///
102+
/// This needs to be polled in parallel with other parts of an operator, like controllers or
103+
/// webhook servers. If it is disabled, the returned future immediately resolves to
104+
/// [`std::task::Poll::Ready`] and thus doesn't consume any resources.
73105
pub async fn run(mut self) -> Result<(), Error> {
74106
let CustomResourceDefinitionMaintainerOptions {
75107
operator_service_name,
76108
operator_namespace,
77109
field_manager,
78-
https_port,
110+
webhook_https_port: https_port,
79111
disabled,
80112
} = self.options;
81113

82-
// If the maintainer is disabled, immediately return without doing any work.
83-
if disabled {
114+
// If the maintainer is disabled or there are no custom resource definitions, immediately
115+
// return without doing any work.
116+
if disabled || self.definitions.is_empty() {
84117
return Ok(());
85118
}
86119

120+
// This get's polled by the async runtime on a regular basis (or when woken up). Once we
121+
// receive a message containing the newly generated TLS certificate for the conversion
122+
// webhook, we need to update the caBundle in the CRD.
87123
while let Some(certificate) = self.certificate_rx.recv().await {
88124
tracing::info!(
89125
k8s.crd.names = ?self.definitions.iter().map(CustomResourceDefinition::name_any).collect::<Vec<_>>(),
90126
"reconciling custom resource definitions"
91127
);
92128

129+
// The caBundle needs to be provided as a base64-encoded PEM envelope.
93130
let ca_bundle = certificate
94131
.to_pem(LineEnding::LF)
95132
.context(EncodeCertificateAuthorityAsPemSnafu)?;
@@ -109,9 +146,12 @@ impl CustomResourceDefinitionMaintainer {
109146
crd.spec.conversion = Some(CustomResourceConversion {
110147
strategy: "Webhook".to_owned(),
111148
webhook: Some(WebhookConversion {
112-
// conversionReviewVersions indicates what ConversionReview versions are understood/preferred by the webhook.
113-
// The first version in the list understood by the API server is sent to the webhook.
114-
// The webhook must respond with a ConversionReview object in the same version it received.
149+
// conversionReviewVersions indicates what ConversionReview versions are
150+
// supported by the webhook. The first version in the list understood by the
151+
// API server is sent to the webhook. The webhook must respond with a
152+
// ConversionReview object in the same version it received. We only support
153+
// the stable v1 ConversionReview to keep the implementation as simple as
154+
// possible.
115155
conversion_review_versions: vec!["v1".to_owned()],
116156
client_config: Some(WebhookClientConfig {
117157
service: Some(ServiceReference {
@@ -120,12 +160,15 @@ impl CustomResourceDefinitionMaintainer {
120160
path: Some(format!("/convert/{crd_name}")),
121161
port: Some(https_port.into()),
122162
}),
163+
// Here, ByteString takes care of encoding the provided content as
164+
// base64.
123165
ca_bundle: Some(ByteString(ca_bundle.as_bytes().to_vec())),
124166
url: None,
125167
}),
126168
}),
127169
});
128170

171+
// Deploy the updated CRDs using a server-side apply.
129172
let patch = Patch::Apply(&crd);
130173
let patch_params = PatchParams::apply(&field_manager);
131174
crd_api
@@ -134,12 +177,15 @@ impl CustomResourceDefinitionMaintainer {
134177
.with_context(|_| PatchCrdSnafu { crd_name })?;
135178
}
136179

137-
// Once all CRDs are reconciled, send a heartbeat for consumers to be notified that
138-
// custom resources of these kinds can bow be deployed.
180+
// After the reconciliation of the CRDs, the initial reconcile heartbeat is sent out
181+
// via the oneshot channel. This channel can only be used exactly once. The sender's
182+
// send method consumes self, and as such, the sender is wrapped in an Option to be
183+
// able to call take to consume the inner value.
139184
if let Some(initial_reconcile_tx) = self.initial_reconcile_tx.take() {
140-
initial_reconcile_tx
141-
.send(())
142-
.ignore_context(SendInitialReconcileHeartbeatSnafu)?
185+
match initial_reconcile_tx.send(()) {
186+
Ok(_) => {}
187+
Err(_) => return SendInitialReconcileHeartbeatSnafu.fail(),
188+
}
143189
}
144190
}
145191

@@ -148,30 +194,20 @@ impl CustomResourceDefinitionMaintainer {
148194
}
149195

150196
// TODO (@Techassi): Make this a builder instead
197+
/// This contains required options to customize a [`CustomResourceDefinitionMaintainer`].
151198
pub struct CustomResourceDefinitionMaintainerOptions {
199+
/// The service name used by the operator/conversion webhook.
152200
operator_service_name: String,
201+
202+
/// The namespace the operator/conversion webhook runs in.
153203
operator_namespace: String,
204+
205+
/// The name of the field manager used for the server-side apply.
154206
field_manager: String,
155-
https_port: u16,
156-
disabled: bool,
157-
}
158207

159-
trait ResultContextExt<T> {
160-
fn ignore_context<C, E2>(self, context: C) -> Result<T, E2>
161-
where
162-
C: snafu::IntoError<E2, Source = snafu::NoneError>,
163-
E2: std::error::Error + snafu::ErrorCompat;
164-
}
208+
/// The HTTPS port the conversion webhook listens on.
209+
webhook_https_port: u16,
165210

166-
impl<T, E> ResultContextExt<T> for Result<T, E> {
167-
fn ignore_context<C, E2>(self, context: C) -> Result<T, E2>
168-
where
169-
C: snafu::IntoError<E2, Source = snafu::NoneError>,
170-
E2: std::error::Error + snafu::ErrorCompat,
171-
{
172-
match self {
173-
Ok(v) => Ok(v),
174-
Err(_) => Err(context.into_error(snafu::NoneError)),
175-
}
176-
}
211+
/// Indicates if the maintainer should be disabled.
212+
disabled: bool,
177213
}

crates/stackable-webhook/src/options.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
//! Contains available options to configure the [WebhookServer][crate::WebhookServer].
1+
//! Contains available options to configure the [WebhookServer].
2+
23
use std::{
34
net::{IpAddr, SocketAddr},
45
path::PathBuf,

crates/stackable-webhook/src/servers/conversion.rs

Lines changed: 38 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -74,76 +74,59 @@ impl ConversionWebhookServer {
7474
/// [`WebhookServer::DEFAULT_SOCKET_ADDRESS`].
7575
pub const DEFAULT_SOCKET_ADDRESS: SocketAddr = WebhookServer::DEFAULT_SOCKET_ADDRESS;
7676

77-
/// Creates a new conversion webhook server, which expects POST requests being made to the
78-
/// `/convert/{CRD_NAME}` endpoint.
77+
/// Creates and returns a new [`ConversionWebhookServer`], which expects POST requests being
78+
/// made to the `/convert/{CRD_NAME}` endpoint.
7979
///
80-
/// You need to provide a few things for every CRD passed in via the `crds_and_handlers` argument:
80+
/// ## Parameters
8181
///
82-
/// 1. The CRD
83-
/// 2. A conversion function to convert between CRD versions. Typically you would use the
84-
/// the auto-generated `try_convert` function on CRD spec definition structs for this.
85-
/// 3. A [`kube::Client`] used to create/update the CRDs.
82+
/// This function expects the following parameters:
8683
///
87-
/// The [`ConversionWebhookServer`] takes care of reconciling the CRDs into the Kubernetes
88-
/// cluster and takes care of adding itself as conversion webhook. This includes TLS
89-
/// certificates and CA bundles.
84+
/// - `crds_and_handlers`: An iterator over a 2-tuple (pair) mapping a [`CustomResourceDefinition`]
85+
/// to a handler function. In most cases, the generated `CustomResource::try_merge` function
86+
/// should be used. It provides the expected `fn(ConversionReview) -> ConversionReview`
87+
/// signature.
88+
/// - `options`: Provides [`ConversionWebhookOptions`] to customize various parts of the
89+
/// webhook server, eg. the socket address used to listen on.
9090
///
91-
/// # Example
91+
/// ## Return Values
9292
///
93-
/// ```no_run
94-
/// use clap::Parser;
95-
/// use stackable_webhook::{
96-
/// servers::{ConversionWebhookServer, ConversionWebhookOptions},
97-
/// constants::CONVERSION_WEBHOOK_HTTPS_PORT,
98-
/// WebhookOptions
99-
/// };
100-
/// use stackable_operator::{
101-
/// kube::Client,
102-
/// crd::s3::{S3Connection, S3ConnectionVersion},
103-
/// cli::{RunArguments, MaintenanceOptions},
104-
/// };
93+
/// This function returns a [`Result`] which contains a 2-tuple (pair) of values for the [`Ok`]
94+
/// variant:
95+
///
96+
/// - The [`ConversionWebhookServer`] itself. This is used to run the server. See
97+
/// [`ConversionWebhookServer::run`] for more details.
98+
/// - The [`mpsc::Receiver`] which will be used to send out messages containing the newly
99+
/// generated TLS certificate. This channel is used by the CRD maintainer to trigger a
100+
/// reconcile of the CRDs it maintains.
105101
///
106-
/// # async fn test() {
107-
/// // Things that should already be in you operator:
108-
/// const OPERATOR_NAME: &str = "product-operator";
109-
/// let client = Client::try_default().await.expect("failed to create Kubernetes client");
110-
/// let RunArguments {
111-
/// operator_environment,
112-
/// maintenance: MaintenanceOptions {
113-
/// disable_crd_maintenance,
114-
/// ..
115-
/// },
116-
/// ..
117-
/// } = RunArguments::parse();
102+
/// ## Example
103+
///
104+
/// ```
105+
/// use stackable_webhook::{ConversionWebhookServer, ConversionWebhookOptions};
106+
/// use stackable_operator::crd::s3::{S3Connection, S3ConnectionVersion};
118107
///
119-
/// let crds_and_handlers = [
108+
/// # #[tokio::test]
109+
/// # async fn main() {
110+
/// let crds_and_handlers = vec![
120111
/// (
121112
/// S3Connection::merged_crd(S3ConnectionVersion::V1Alpha1)
122-
/// .expect("failed to merge S3Connection CRD"),
123-
/// S3Connection::try_convert as fn(_) -> _,
124-
/// ),
113+
/// .expect("the S3Connection CRD must be merged"),
114+
/// S3Connection::try_convert,
115+
/// )
125116
/// ];
126117
///
127118
/// let options = ConversionWebhookOptions {
128-
/// socket_addr: format!("0.0.0.0:{CONVERSION_WEBHOOK_HTTPS_PORT}")
129-
/// .parse()
130-
/// .expect("static address is always valid"),
131-
/// namespace: operator_environment.operator_namespace,
132-
/// service_name: operator_environment.operator_service_name,
133-
/// maintain_crds: !disable_crd_maintenance,
134-
/// field_manager: OPERATOR_NAME.to_owned(),
119+
/// socket_addr: ConversionWebhookServer::DEFAULT_SOCKET_ADDRESS,
120+
/// namespace: "stackable-operators".to_owned(),
121+
/// service_name: "product-operator".to_owned(),
135122
/// };
136123
///
137-
/// // Construct the conversion webhook server
138-
/// let conversion_webhook = ConversionWebhookServer::new(
139-
/// crds_and_handlers,
140-
/// options,
141-
/// client,
142-
/// )
143-
/// .await
144-
/// .expect("failed to create ConversionWebhookServer");
124+
/// let (conversion_webhook_server, _certificate_rx) =
125+
/// ConversionWebhookServer::new(crds_and_handlers, options)
126+
/// .await
127+
/// .unwrap();
145128
///
146-
/// conversion_webhook.run().await.expect("failed to run ConversionWebhookServer");
129+
/// conversion_webhook_server.run().await.unwrap();
147130
/// # }
148131
/// ```
149132
#[instrument(name = "create_conversion_webhook_server", skip(crds_and_handlers))]

0 commit comments

Comments
 (0)