Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Techassi committed Oct 30, 2023
1 parent d0e6786 commit 4c89e5b
Show file tree
Hide file tree
Showing 8 changed files with 316 additions and 2 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

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

6 changes: 5 additions & 1 deletion rust/stackable-cockpit/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ repository.workspace = true
publish = false

[features]
full = ["openapi"]
full = ["openapi", "specs_v2"]
openapi = ["dep:utoipa"]
specs_v2 = []
specs_v3 = []

[dependencies]
helm-sys = { path = "../helm-sys" }
Expand All @@ -19,7 +21,9 @@ bcrypt.workspace = true
indexmap.workspace = true
k8s-openapi.workspace = true
kube.workspace = true
lazy_static.workspace = true
rand.workspace = true
regex.workspace = true
reqwest.workspace = true
serde_json.workspace = true
serde_yaml.workspace = true
Expand Down
136 changes: 136 additions & 0 deletions rust/stackable-cockpit/src/platform/label/key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
use std::{fmt::Display, str::FromStr};

use lazy_static::lazy_static;
use regex::Regex;
use snafu::{ensure, Snafu};

const LABEL_KEY_PREFIX_MAX_LEN: usize = 253;
const LABEL_KEY_NAME_MAX_LEN: usize = 63;

lazy_static! {
static ref LABEL_KEY_PREFIX_REGEX: Regex =
Regex::new(r"^[a-zA-Z](\.?[a-zA-Z0-9-])*\.[a-zA-Z]{2,}\.?$").unwrap();
static ref LABEL_KEY_NAME_REGEX: Regex =
Regex::new(r"^[a-z0-9A-Z]([a-z0-9A-Z-_.]*[a-z0-9A-Z]+)?$").unwrap();
}

#[derive(Debug, Snafu)]
pub enum LabelKeyError {}

pub struct LabelKey {
prefix: Option<LabelKeyPrefix>,
name: LabelKeyName,
}

impl FromStr for LabelKey {
type Err = LabelKeyError;

fn from_str(input: &str) -> Result<Self, Self::Err> {

Check failure on line 28 in rust/stackable-cockpit/src/platform/label/key.rs

View workflow job for this annotation

GitHub Actions / clippy

[clippy] rust/stackable-cockpit/src/platform/label/key.rs#L28

error: unused variable: `input` --> rust/stackable-cockpit/src/platform/label/key.rs:28:17 | 28 | fn from_str(input: &str) -> Result<Self, Self::Err> { | ^^^^^ help: if this is intentional, prefix it with an underscore: `_input` | = note: `-D unused-variables` implied by `-D warnings`
Raw output
rust/stackable-cockpit/src/platform/label/key.rs:28:17:e:error: unused variable: `input`
  --> rust/stackable-cockpit/src/platform/label/key.rs:28:17
   |
28 |     fn from_str(input: &str) -> Result<Self, Self::Err> {
   |                 ^^^^^ help: if this is intentional, prefix it with an underscore: `_input`
   |
   = note: `-D unused-variables` implied by `-D warnings`


__END__

Check failure on line 28 in rust/stackable-cockpit/src/platform/label/key.rs

View workflow job for this annotation

GitHub Actions / clippy

[clippy] rust/stackable-cockpit/src/platform/label/key.rs#L28

error: unused variable: `input` --> rust/stackable-cockpit/src/platform/label/key.rs:28:17 | 28 | fn from_str(input: &str) -> Result<Self, Self::Err> { | ^^^^^ help: if this is intentional, prefix it with an underscore: `_input` | = note: `-D unused-variables` implied by `-D warnings`
Raw output
rust/stackable-cockpit/src/platform/label/key.rs:28:17:e:error: unused variable: `input`
  --> rust/stackable-cockpit/src/platform/label/key.rs:28:17
   |
28 |     fn from_str(input: &str) -> Result<Self, Self::Err> {
   |                 ^^^^^ help: if this is intentional, prefix it with an underscore: `_input`
   |
   = note: `-D unused-variables` implied by `-D warnings`


__END__
todo!()
}
}

impl Display for LabelKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.prefix {
Some(prefix) => write!(f, "{}/{}", prefix, self.name),
None => write!(f, "{}", self.name),
}
}
}

#[derive(Debug, Snafu)]
pub enum LabelKeyPrefixError {
#[snafu(display("prefix segment of key cannot be empty"))]
PrefixEmpty,

#[snafu(display("prefix segment of key exceeds the maximum length - expected 253 characters or less, got {length}"))]
PrefixTooLong { length: usize },

#[snafu(display("prefix segment of key contains non-ascii characters"))]
PrefixNotAscii,

#[snafu(display("prefix segment of key violates kubernetes format"))]
PrefixInvalid,
}

pub struct LabelKeyPrefix(String);

impl Display for LabelKeyPrefix {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}

impl FromStr for LabelKeyPrefix {
type Err = LabelKeyPrefixError;

fn from_str(input: &str) -> Result<Self, Self::Err> {
// The prefix cannot be empty when one is provided
ensure!(!input.is_empty(), PrefixEmptySnafu);

// The length of the prefix cannot exceed 253 characters
ensure!(
input.len() <= LABEL_KEY_PREFIX_MAX_LEN,
PrefixTooLongSnafu {
length: input.len()
}
);

// The prefix cannot contain non-ascii characters
ensure!(input.is_ascii(), PrefixNotAsciiSnafu);

// The prefix must use the format specified by Kubernetes
ensure!(LABEL_KEY_PREFIX_REGEX.is_match(input), PrefixInvalidSnafu);

Ok(Self(input.to_string()))
}
}

#[derive(Debug, Snafu)]
pub enum LabelKeyNameError {
#[snafu(display("name segment of key cannot be empty"))]
NameEmpty,

#[snafu(display("name segment of key exceeds the maximum length - expected 63 characters or less, got {length}"))]
NameTooLong { length: usize },

#[snafu(display("name segment of key contains non-ascii characters"))]
NameNotAscii,

#[snafu(display("name segment of key violates kubernetes format"))]
NameInvalid,
}

pub struct LabelKeyName(String);

impl Display for LabelKeyName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}

impl FromStr for LabelKeyName {
type Err = LabelKeyNameError;

fn from_str(input: &str) -> Result<Self, Self::Err> {
// The name cannot be empty
ensure!(!input.is_empty(), NameEmptySnafu);

// The length of the name cannot exceed 63 characters
ensure!(
input.len() <= LABEL_KEY_NAME_MAX_LEN,
NameTooLongSnafu {
length: input.len()
}
);

// The name cannot contain non-ascii characters
ensure!(input.is_ascii(), NameNotAsciiSnafu);

// The name must use the format specified by Kubernetes
ensure!(LABEL_KEY_NAME_REGEX.is_match(input), NameInvalidSnafu);

Ok(Self(input.to_string()))
}
}
92 changes: 92 additions & 0 deletions rust/stackable-cockpit/src/platform/label/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
use std::{fmt::Display, str::FromStr};

use snafu::{ensure, ResultExt, Snafu};

mod key;
mod value;

pub use key::*;
pub use value::*;

#[derive(Debug, Snafu)]

Check failure on line 11 in rust/stackable-cockpit/src/platform/label/mod.rs

View workflow job for this annotation

GitHub Actions / clippy

[clippy] rust/stackable-cockpit/src/platform/label/mod.rs#L11

error: unreachable expression --> rust/stackable-cockpit/src/platform/label/mod.rs:11:17 | 11 | #[derive(Debug, Snafu)] | ^^^^^ | | | unreachable expression | any code following this expression is unreachable | note: this expression has type `platform::label::key::LabelKeyError`, which is uninhabited --> rust/stackable-cockpit/src/platform/label/mod.rs:11:17 | 11 | #[derive(Debug, Snafu)] | ^^^^^ = note: `-D unreachable-code` implied by `-D warnings` = note: this error originates in the derive macro `Snafu` (in Nightly builds, run with -Z macro-backtrace for more info)
Raw output
rust/stackable-cockpit/src/platform/label/mod.rs:11:17:e:error: unreachable expression
  --> rust/stackable-cockpit/src/platform/label/mod.rs:11:17
   |
11 | #[derive(Debug, Snafu)]
   |                 ^^^^^
   |                 |
   |                 unreachable expression
   |                 any code following this expression is unreachable
   |
note: this expression has type `platform::label::key::LabelKeyError`, which is uninhabited
  --> rust/stackable-cockpit/src/platform/label/mod.rs:11:17
   |
11 | #[derive(Debug, Snafu)]
   |                 ^^^^^
   = note: `-D unreachable-code` implied by `-D warnings`
   = note: this error originates in the derive macro `Snafu` (in Nightly builds, run with -Z macro-backtrace for more info)


__END__

Check failure on line 11 in rust/stackable-cockpit/src/platform/label/mod.rs

View workflow job for this annotation

GitHub Actions / clippy

[clippy] rust/stackable-cockpit/src/platform/label/mod.rs#L11

error: unreachable expression --> rust/stackable-cockpit/src/platform/label/mod.rs:11:17 | 11 | #[derive(Debug, Snafu)] | ^^^^^ | | | unreachable expression | any code following this expression is unreachable | note: this expression has type `platform::label::key::LabelKeyError`, which is uninhabited --> rust/stackable-cockpit/src/platform/label/mod.rs:11:17 | 11 | #[derive(Debug, Snafu)] | ^^^^^ = note: `-D unreachable-code` implied by `-D warnings` = note: this error originates in the derive macro `Snafu` (in Nightly builds, run with -Z macro-backtrace for more info)
Raw output
rust/stackable-cockpit/src/platform/label/mod.rs:11:17:e:error: unreachable expression
  --> rust/stackable-cockpit/src/platform/label/mod.rs:11:17
   |
11 | #[derive(Debug, Snafu)]
   |                 ^^^^^
   |                 |
   |                 unreachable expression
   |                 any code following this expression is unreachable
   |
note: this expression has type `platform::label::key::LabelKeyError`, which is uninhabited
  --> rust/stackable-cockpit/src/platform/label/mod.rs:11:17
   |
11 | #[derive(Debug, Snafu)]
   |                 ^^^^^
   = note: `-D unreachable-code` implied by `-D warnings`
   = note: this error originates in the derive macro `Snafu` (in Nightly builds, run with -Z macro-backtrace for more info)


__END__
pub enum LabelParseError {
#[snafu(display("label input cannot be empty"))]
EmptyInput,

#[snafu(display("invalid number of equal signs - expected exactly 1, got {signs}"))]
InvalidEqualSignCount { signs: usize },

#[snafu(display("failed to parse label key"))]
LabelKeyError { source: LabelKeyError },

#[snafu(display("failed to parse label value"))]
LabelValueError { source: LabelValueError },
}

// NOTE (Techassi): This should probably be upstreamed into operator-rs
pub struct Label {
key: LabelKey,
value: LabelValue,
}

impl FromStr for Label {
type Err = LabelParseError;

fn from_str(input: &str) -> Result<Self, Self::Err> {
let input = input.trim();

// Ensure the input is not empty
ensure!(!input.is_empty(), EmptyInputSnafu);

// Then split up the key and value, which is separated by an equal
// sign
let parts: Vec<_> = input.split('=').collect();

// Ensure there are only two parts
ensure!(
parts.len() == 2,
InvalidEqualSignCountSnafu { signs: parts.len() }
);

// Parse key and value parts
let key = LabelKey::from_str(parts[0]).context(LabelKeySnafu)?;
let value = LabelValue::new(parts[1]).context(LabelValueSnafu)?;

Ok(Self { key, value })
}
}

impl Display for Label {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}={}", self.key, self.value)
}
}

impl Label {
pub fn new(key: LabelKey, value: LabelValue) -> Self {
Self { key, value }
}
}

macro_rules! label {

Check failure on line 71 in rust/stackable-cockpit/src/platform/label/mod.rs

View workflow job for this annotation

GitHub Actions / clippy

[clippy] rust/stackable-cockpit/src/platform/label/mod.rs#L71

error: unused macro definition: `label` --> rust/stackable-cockpit/src/platform/label/mod.rs:71:14 | 71 | macro_rules! label { | ^^^^^ | = note: `-D unused-macros` implied by `-D warnings`
Raw output
rust/stackable-cockpit/src/platform/label/mod.rs:71:14:e:error: unused macro definition: `label`
  --> rust/stackable-cockpit/src/platform/label/mod.rs:71:14
   |
71 | macro_rules! label {
   |              ^^^^^
   |
   = note: `-D unused-macros` implied by `-D warnings`


__END__

Check failure on line 71 in rust/stackable-cockpit/src/platform/label/mod.rs

View workflow job for this annotation

GitHub Actions / clippy

[clippy] rust/stackable-cockpit/src/platform/label/mod.rs#L71

error: unused macro definition: `label` --> rust/stackable-cockpit/src/platform/label/mod.rs:71:14 | 71 | macro_rules! label { | ^^^^^ | = note: `-D unused-macros` implied by `-D warnings`
Raw output
rust/stackable-cockpit/src/platform/label/mod.rs:71:14:e:error: unused macro definition: `label`
  --> rust/stackable-cockpit/src/platform/label/mod.rs:71:14
   |
71 | macro_rules! label {
   |              ^^^^^
   |
   = note: `-D unused-macros` implied by `-D warnings`


__END__
($Input:expr) => {
$crate::platform::label::Label::from_str($Input)
};
}

#[cfg(test)]
mod test {
use super::*;

Check failure on line 79 in rust/stackable-cockpit/src/platform/label/mod.rs

View workflow job for this annotation

GitHub Actions / clippy

[clippy] rust/stackable-cockpit/src/platform/label/mod.rs#L79

error: unused import: `super::*` --> rust/stackable-cockpit/src/platform/label/mod.rs:79:9 | 79 | use super::*; | ^^^^^^^^ | = note: `-D unused-imports` implied by `-D warnings`
Raw output
rust/stackable-cockpit/src/platform/label/mod.rs:79:9:e:error: unused import: `super::*`
  --> rust/stackable-cockpit/src/platform/label/mod.rs:79:9
   |
79 |     use super::*;
   |         ^^^^^^^^
   |
   = note: `-D unused-imports` implied by `-D warnings`


__END__

#[test]
fn test_basic() {
// let l = label!("stackable.tech/vendor=Stackable").unwrap();

// let key_prefix = LabelKeyPrefix::new("stackable.tech").unwrap();
// let key = LabelKey::new(Some(key_prefix), "vendor").unwrap();
// let value = LabelValue::new("Stackable").unwrap();

// let label = Label::new(key, value);
// assert_eq!(label.to_string(), "stackable.tech/vendor=Stackable")
}
}
57 changes: 57 additions & 0 deletions rust/stackable-cockpit/src/platform/label/value.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use std::fmt::Display;

use lazy_static::lazy_static;
use regex::Regex;
use snafu::{ensure, Snafu};

const LABEL_VALUE_MAX_LEN: usize = 63;

lazy_static! {
static ref LABEL_VALUE_REGEX: Regex =
Regex::new(r"^[a-z0-9A-Z]([a-z0-9A-Z-_.]*[a-z0-9A-Z]+)?$").unwrap();
}

#[derive(Debug, Snafu)]
pub enum LabelValueError {
#[snafu(display(
"value exceeds the maximum length - expected 63 characters or less, got {length}"
))]
ValueTooLong { length: usize },

#[snafu(display("value contains non-ascii characters"))]
ValueNotAscii,

#[snafu(display("value violates kubernetes format"))]
ValueInvalid,
}

pub struct LabelValue(String);

impl Display for LabelValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}

impl LabelValue {
pub fn new(value: impl Into<String>) -> Result<Self, LabelValueError> {
let value: String = value.into();

// The length of the value cannot exceed 63 characters, but can be
// empty
ensure!(
value.len() <= LABEL_VALUE_MAX_LEN,
ValueTooLongSnafu {
length: value.len()
}
);

// The value cannot contain non-ascii characters
ensure!(value.is_ascii(), ValueNotAsciiSnafu);

// The value must use the format specified by Kubernetes
ensure!(LABEL_VALUE_REGEX.is_match(&value), ValueInvalidSnafu);

Ok(Self(value))
}
}
1 change: 1 addition & 0 deletions rust/stackable-cockpit/src/platform/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pub mod cluster;
pub mod credentials;
pub mod demo;
pub mod label;
pub mod namespace;
pub mod operator;
pub mod product;
Expand Down
24 changes: 23 additions & 1 deletion rust/stackable-cockpit/src/platform/stack/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ use serde::{Deserialize, Serialize};
mod spec;
pub use spec::*;

use crate::common::{List, SpecIter};
use crate::{
common::{List, SpecIter},
platform::release::ReleaseList,
xfer::FileTransferClient,
};

/// This struct describes a complete demos v2 file
#[derive(Debug, Deserialize, Serialize)]
Expand All @@ -21,3 +25,21 @@ impl SpecIter<StackSpecV2> for StacksV2 {
}

pub type StackList = List<StacksV2, StackSpecV2>;

pub trait StackSpecExt {
type Error: std::error::Error;

fn check_prerequisites(&self, product_namespace: &str) -> Result<(), Self::Error>;
fn install_release(
&self,
release_list: ReleaseList,
operator_namespace: &str,
product_namespace: &str,
) -> Result<(), Self::Error>;
fn install_stack_manifests(
&self,
parameters: &[String],
product_namespace: &str,
transfer_client: &FileTransferClient,
) -> Result<(), Self::Error>;
}
Empty file.

0 comments on commit 4c89e5b

Please sign in to comment.