Repository navigation
feat: Management Access utilities #1290
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
36 commits
Select commit
Hold shift + click to select a range
e8a291d
add agent label utility
labrenbe 6ca45a0
add TlsClientCredential enum as platform access utility
labrenbe 7cb677e
add changelog entry
labrenbe 7a0d956
fix cargo deny by upgrading yoke-derive
labrenbe f3d2da7
use hard-coded volume and mount names
labrenbe 3b91d73
add support for anonymous platform access
labrenbe e364acd
make crd fragments versioned
labrenbe 7687938
add ManagementPolicy CRD fragment
labrenbe 754e444
add Ready & Synced conditions for resources reconciled by agents
labrenbe 92e1a36
rename tls client cert dir
labrenbe 88e3184
Merge remote-tracking branch 'origin/main' into feat/platform-access
labrenbe e9c595e
harmonize CRD fragments with accepted decisions
labrenbe a575fc3
move managed resource conditions to status and support Unknown and ob…
labrenbe d67261e
Merge remote-tracking branch 'origin/main' into feat/platform-access
labrenbe 22536cd
avoid clashes with role names in component label
labrenbe 0f8cbeb
return an error when the TLS client certificate volume collides
labrenbe 1ddd9fc
use with_secret
labrenbe a2a7d0c
make the description of anonymous idependent from agent
labrenbe 079f741
add RbacResourceNames trait and AgentResourceNames type
labrenbe 1247d14
add the stackable-agent crate with DeletionPolicy and managed resourc…
labrenbe 8eae8f0
Merge branch 'feat/stackable-agent' into feat/platform-access
labrenbe b815f20
add ResourceOperation to stackable-agent
labrenbe ccfff58
add all_agents_selector util
labrenbe a46d940
change doc comment on ManagedCondition reason
labrenbe 63760c6
add autoTLS domain components to agent tls cert
labrenbe b61f619
remove autoTLS domain components to agent tls cert again
labrenbe d769774
Merge remote-tracking branch 'origin/main' into feat/platform-access
sbernauer 8adb4da
add CRD preview
labrenbe 46a18a9
derive the condition status strings
labrenbe 02db679
Merge remote-tracking branch 'origin/main' into feat/platform-access
labrenbe e77a2b1
fix changelogs
labrenbe 915f8c8
Merge remote-tracking branch 'origin/main' into feat/platform-access
labrenbe 2be5aec
fix prek
labrenbe 1d2bf3f
fix: Temporarily allow redundant_field_names lint
Techassi fe34baf
chore: Fix various Clippy errors
Techassi bc7e95f
chore: Add link to darling fix PR
Techassi File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| # Changelog | ||
|
|
||
| All notable changes to this project will be documented in this file. | ||
|
|
||
| ## [Unreleased] | ||
|
|
||
| ### Added | ||
|
|
||
| - `DeletionPolicy`, whether an agent deletes a resource in the product ([#1290]). | ||
| - `ResourceOperation` with `reconciliationPaused`, which stops an agent from changing a resource | ||
| in the product ([#1290]). | ||
| - `Ready`/`Synced` conditions of resources managed by agents (`condition`), as `metav1.Condition` | ||
| with `True`/`False`/`Unknown` and `observedGeneration` ([#1290]). | ||
|
|
||
| ## [0.0.1] | ||
|
|
||
| ### Added | ||
|
|
||
| - Add the `stackable-agent` crate as a workspace member ([#1290]). | ||
|
|
||
| [#1290]: https://github.com/stackabletech/operator-rs/pull/1290 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| [package] | ||
| name = "stackable-agent" | ||
| description = "Building blocks for agents managing resources inside products of the Stackable Data Platform" | ||
| version = "0.0.1" | ||
| authors.workspace = true | ||
| license.workspace = true | ||
| edition.workspace = true | ||
| repository.workspace = true | ||
|
|
||
| [dependencies] | ||
| stackable-versioned = { path = "../stackable-versioned" } | ||
|
|
||
| k8s-openapi.workspace = true | ||
| schemars.workspace = true | ||
| serde.workspace = true | ||
| strum.workspace = true | ||
|
|
||
| [dev-dependencies] | ||
| rstest.workspace = true | ||
| serde_json.workspace = true | ||
|
|
||
| [lints] | ||
| workspace = true |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,186 @@ | ||
| //! Conditions of resources an agent manages inside a product, as `metav1.Condition`. | ||
|
|
||
| use k8s_openapi::apimachinery::pkg::apis::meta::v1::{Condition, Time}; | ||
| use strum::Display; | ||
|
|
||
| /// Condition types of resources an agent manages inside a product. | ||
| #[derive(Clone, Copy, Debug, Display, Eq, PartialEq)] | ||
| pub enum ManagedConditionType { | ||
| /// The resource exists in the product and is usable. | ||
| Ready, | ||
|
|
||
| /// The last reconciliation applied the spec to the resource in the product. | ||
| Synced, | ||
| } | ||
|
|
||
| /// The status of a condition. | ||
| #[derive(Clone, Copy, Debug, Display, Eq, PartialEq)] | ||
| pub enum ConditionStatus { | ||
| True, | ||
| False, | ||
| Unknown, | ||
| } | ||
|
|
||
| /// The desired state of one condition, without the bookkeeping fields. | ||
| #[derive(Clone, Debug, Eq, PartialEq)] | ||
| pub struct ManagedCondition { | ||
| pub type_: ManagedConditionType, | ||
|
|
||
| /// `Unknown` if the agent could not determine the state, e.g. because the product is not | ||
| /// reachable. | ||
| pub status: ConditionStatus, | ||
|
|
||
| /// A CamelCase reason like `Available` or `UnsupportedChange`. | ||
| pub reason: String, | ||
|
|
||
| pub message: String, | ||
| } | ||
|
|
||
| /// Merges the given conditions into the current ones. | ||
| /// | ||
| /// `lastTransitionTime` only changes if the status of a condition changes. Conditions of other | ||
| /// types are kept. `observed_generation` is the `metadata.generation` of the object the conditions | ||
| /// were determined for. | ||
| pub fn update_conditions( | ||
| current: &[Condition], | ||
| updates: Vec<ManagedCondition>, | ||
| now: &Time, | ||
| observed_generation: Option<i64>, | ||
| ) -> Vec<Condition> { | ||
| let mut conditions = current.to_vec(); | ||
|
|
||
| for update in updates { | ||
| let type_ = update.type_.to_string(); | ||
| let status = update.status.to_string(); | ||
| let existing = conditions | ||
| .iter() | ||
| .position(|condition| condition.type_ == type_); | ||
|
|
||
| let last_transition_time = match existing { | ||
| Some(index) if conditions[index].status == status => { | ||
| conditions[index].last_transition_time.clone() | ||
| } | ||
| _ => now.clone(), | ||
| }; | ||
|
|
||
| let condition = Condition { | ||
| last_transition_time, | ||
| message: update.message, | ||
| observed_generation, | ||
| reason: update.reason, | ||
| status, | ||
| type_, | ||
| }; | ||
|
|
||
| match existing { | ||
| Some(index) => conditions[index] = condition, | ||
| None => conditions.push(condition), | ||
| } | ||
| } | ||
|
|
||
| conditions | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use k8s_openapi::jiff::Timestamp; | ||
|
|
||
| use super::*; | ||
|
|
||
| fn time(seconds: i64) -> Time { | ||
| Time(Timestamp::from_second(seconds).expect("the timestamp should be valid")) | ||
| } | ||
|
|
||
| fn ready(status: ConditionStatus, reason: &str) -> ManagedCondition { | ||
| ManagedCondition { | ||
| type_: ManagedConditionType::Ready, | ||
| status, | ||
| reason: reason.to_owned(), | ||
| message: String::new(), | ||
| } | ||
| } | ||
|
|
||
| #[test] | ||
| fn add_missing_condition() { | ||
| let conditions = update_conditions( | ||
| &[], | ||
| vec![ready(ConditionStatus::True, "Created")], | ||
| &time(10), | ||
| Some(3), | ||
| ); | ||
|
|
||
| assert_eq!(1, conditions.len()); | ||
| assert_eq!("Ready", conditions[0].type_); | ||
| assert_eq!("True", conditions[0].status); | ||
| assert_eq!("Created", conditions[0].reason); | ||
| assert_eq!(time(10), conditions[0].last_transition_time); | ||
| assert_eq!(Some(3), conditions[0].observed_generation); | ||
| } | ||
|
|
||
| #[test] | ||
| fn keep_transition_time_if_status_is_unchanged() { | ||
| let current = update_conditions( | ||
| &[], | ||
| vec![ready(ConditionStatus::True, "Created")], | ||
| &time(10), | ||
| Some(1), | ||
| ); | ||
|
|
||
| let conditions = update_conditions( | ||
| ¤t, | ||
| vec![ready(ConditionStatus::True, "Updated")], | ||
| &time(20), | ||
| Some(2), | ||
| ); | ||
|
|
||
| assert_eq!("Updated", conditions[0].reason); | ||
| assert_eq!(time(10), conditions[0].last_transition_time); | ||
| assert_eq!(Some(2), conditions[0].observed_generation); | ||
| } | ||
|
|
||
| #[test] | ||
| fn update_transition_time_if_status_changes() { | ||
| let current = update_conditions( | ||
| &[], | ||
| vec![ready(ConditionStatus::True, "Created")], | ||
| &time(10), | ||
| Some(1), | ||
| ); | ||
|
|
||
| let conditions = update_conditions( | ||
| ¤t, | ||
| vec![ready(ConditionStatus::Unknown, "ConnectionFailed")], | ||
| &time(20), | ||
| Some(1), | ||
| ); | ||
|
|
||
| assert_eq!("Unknown", conditions[0].status); | ||
| assert_eq!(time(20), conditions[0].last_transition_time); | ||
| } | ||
|
|
||
| #[test] | ||
| fn keep_conditions_of_other_types() { | ||
| let other = Condition { | ||
| last_transition_time: time(5), | ||
| message: String::new(), | ||
| observed_generation: None, | ||
| reason: "Other".to_owned(), | ||
| status: "True".to_owned(), | ||
| type_: "Other".to_owned(), | ||
| }; | ||
|
|
||
| let conditions = update_conditions( | ||
| std::slice::from_ref(&other), | ||
| vec![ready(ConditionStatus::True, "Created")], | ||
| &time(10), | ||
| Some(1), | ||
| ); | ||
|
|
||
| let types: Vec<_> = conditions | ||
| .iter() | ||
| .map(|condition| condition.type_.as_str()) | ||
| .collect(); | ||
| assert_eq!(vec!["Other", "Ready"], types); | ||
| assert_eq!(None, conditions[0].observed_generation); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| use schemars::JsonSchema; | ||
| use serde::{Deserialize, Serialize}; | ||
| use stackable_versioned::versioned; | ||
|
|
||
| #[versioned(version(name = "v1alpha1"))] | ||
| pub mod versioned { | ||
| /// What happens to a resource in the product when the object managing it is deleted. | ||
| #[derive(Clone, Copy, Debug, Default, Deserialize, Eq, JsonSchema, PartialEq, Serialize)] | ||
| pub enum DeletionPolicy { | ||
| /// Keep the resource in the product. | ||
| #[default] | ||
| Retain, | ||
|
|
||
| /// Delete the resource in the product. | ||
| Delete, | ||
|
labrenbe marked this conversation as resolved.
Resolved
|
||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use rstest::rstest; | ||
|
|
||
| use super::v1alpha1::DeletionPolicy; | ||
|
|
||
| #[rstest] | ||
| #[case::retain(r#""Retain""#, DeletionPolicy::Retain)] | ||
| #[case::delete(r#""Delete""#, DeletionPolicy::Delete)] | ||
| fn deserialize_policy(#[case] input: &str, #[case] expected: DeletionPolicy) { | ||
| let policy: DeletionPolicy = | ||
| serde_json::from_str(input).expect("the policy should be valid"); | ||
|
|
||
| assert_eq!(expected, policy); | ||
| } | ||
|
|
||
| #[test] | ||
| fn retain_by_default() { | ||
| assert_eq!(DeletionPolicy::Retain, DeletionPolicy::default()); | ||
| } | ||
|
|
||
| #[test] | ||
| fn reject_unknown_policy() { | ||
| assert!(serde_json::from_str::<DeletionPolicy>(r#""Orphan""#).is_err()); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| //! Building blocks for agents managing resources inside products, like topics in Apache Kafka. | ||
|
|
||
| pub mod condition; | ||
| pub mod deletion; | ||
| pub mod operation; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| use schemars::JsonSchema; | ||
| use serde::{Deserialize, Serialize}; | ||
| use stackable_versioned::versioned; | ||
|
|
||
| #[versioned(version(name = "v1alpha1"))] | ||
| pub mod versioned { | ||
| /// Operations on this resource in the product, independent of the `clusterOperation` of the | ||
| /// product cluster. | ||
| #[derive(Clone, Debug, Default, Deserialize, Eq, JsonSchema, PartialEq, Serialize)] | ||
| #[serde(rename_all = "camelCase")] | ||
| pub struct ResourceOperation { | ||
| /// Stops the agent from changing the resource in the product. The status is still updated. | ||
| #[serde(default)] | ||
| pub reconciliation_paused: bool, | ||
| } | ||
| } | ||
|
maltesander marked this conversation as resolved.
Resolved
|
||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::v1alpha1::ResourceOperation; | ||
|
|
||
| #[test] | ||
| fn not_paused_by_default() { | ||
| let operation: ResourceOperation = | ||
| serde_json::from_str("{}").expect("the operation should be valid"); | ||
|
|
||
| assert!(!operation.reconciliation_paused); | ||
| } | ||
|
|
||
| #[test] | ||
| fn deserialize_paused() { | ||
| let operation: ResourceOperation = | ||
| serde_json::from_str(r#"{"reconciliationPaused": true}"#) | ||
| .expect("the operation should be valid"); | ||
|
|
||
| assert!(operation.reconciliation_paused); | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.