Skip to content

Commit bf0df56

Browse files
aryan9600clux
andcommitted
add support for standard Condition APIs
Detect the presence of a Conditions object in a schema and use `k8s_openapi::apimachinery::pkg::apis::meta::v1::Condition` if present. Introduce a new flag `--no-condition` which allows revering back to the old behavior of generating a custom Condition API for each instance. Furthermore, introduce `analyzer::Config` which allows for customizing the behavior of the `analyze` function. Signed-off-by: Sanskar Jaiswal <[email protected]> Co-authored-by: clux <[email protected]>
1 parent bb163bb commit bf0df56

File tree

4 files changed

+109
-29
lines changed

4 files changed

+109
-29
lines changed

src/analyzer.rs

+86-26
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,17 @@ use std::collections::{BTreeMap, HashMap};
99

1010
const IGNORED_KEYS: [&str; 3] = ["metadata", "apiVersion", "kind"];
1111

12+
#[derive(Default)]
13+
pub struct Config {
14+
pub no_condition: bool,
15+
}
16+
1217
/// Scan a schema for structs and members, and recurse to find all structs
1318
///
1419
/// All found output structs will have its names prefixed by the kind it is for
15-
pub fn analyze(schema: JSONSchemaProps, kind: &str) -> Result<Output> {
20+
pub fn analyze(schema: JSONSchemaProps, kind: &str, cfg: Config) -> Result<Output> {
1621
let mut res = vec![];
17-
analyze_(&schema, "", kind, 0, &mut res)?;
22+
analyze_(&schema, "", kind, 0, &mut res, &cfg)?;
1823
Ok(Output(res))
1924
}
2025

@@ -31,6 +36,7 @@ fn analyze_(
3136
stack: &str,
3237
level: u8,
3338
results: &mut Vec<Container>,
39+
cfg: &Config,
3440
) -> Result<()> {
3541
let props = schema.properties.clone().unwrap_or_default();
3642
let mut array_recurse_level: HashMap<String, u8> = Default::default();
@@ -46,7 +52,7 @@ fn analyze_(
4652
if let Some(extra_props) = &s.properties {
4753
// map values is an object with properties
4854
debug!("Generating map struct for {} (under {})", current, stack);
49-
let c = extract_container(extra_props, stack, &mut array_recurse_level, level, schema)?;
55+
let c = extract_container(extra_props, stack, &mut array_recurse_level, level, schema, cfg)?;
5056
results.push(c);
5157
} else if !dict_type.is_empty() {
5258
warn!("not generating type {} - using {} map", current, dict_type);
@@ -60,7 +66,7 @@ fn analyze_(
6066
warn!("not generating type {} - using BTreeMap", current);
6167
return Ok(());
6268
}
63-
let c = extract_container(&props, stack, &mut array_recurse_level, level, schema)?;
69+
let c = extract_container(&props, stack, &mut array_recurse_level, level, schema, cfg)?;
6470
results.push(c);
6571
}
6672
}
@@ -74,10 +80,10 @@ fn analyze_(
7480
// again; additionalProperties XOR properties
7581
let extras = if let Some(JSONSchemaPropsOrBool::Schema(s)) = schema.additional_properties.as_ref() {
7682
let extra_props = s.properties.clone().unwrap_or_default();
77-
find_containers(&extra_props, stack, &mut array_recurse_level, level, schema)?
83+
find_containers(&extra_props, stack, &mut array_recurse_level, level, schema, cfg)?
7884
} else {
7985
// regular properties only
80-
find_containers(&props, stack, &mut array_recurse_level, level, schema)?
86+
find_containers(&props, stack, &mut array_recurse_level, level, schema, cfg)?
8187
};
8288
results.extend(extras);
8389

@@ -95,6 +101,7 @@ fn find_containers(
95101
array_recurse_level: &mut HashMap<String, u8>,
96102
level: u8,
97103
schema: &JSONSchemaProps,
104+
cfg: &Config,
98105
) -> Result<Vec<Container>> {
99106
//trace!("finding containers in: {}", serde_yaml::to_string(&props)?);
100107
let mut results = vec![];
@@ -116,7 +123,7 @@ fn find_containers(
116123
// unpack the inner object from the array wrap
117124
if let Some(JSONSchemaPropsOrArray::Schema(items)) = &s.as_ref().items {
118125
debug!("..recursing into object member {}", key);
119-
analyze_(items, &next_key, &next_stack, level + 1, &mut results)?;
126+
analyze_(items, &next_key, &next_stack, level + 1, &mut results, cfg)?;
120127
handled_inner = true;
121128
}
122129
}
@@ -130,7 +137,7 @@ fn find_containers(
130137
}
131138
if !handled_inner {
132139
// normal object recurse
133-
analyze_(value, &next_key, &next_stack, level + 1, &mut results)?;
140+
analyze_(value, &next_key, &next_stack, level + 1, &mut results, cfg)?;
134141
}
135142
}
136143
"array" => {
@@ -150,7 +157,7 @@ fn find_containers(
150157
bail!("could not recurse into vec");
151158
}
152159
}
153-
analyze_(&inner, &next_key, &next_stack, level + 1, &mut results)?;
160+
analyze_(&inner, &next_key, &next_stack, level + 1, &mut results, cfg)?;
154161
}
155162
}
156163
"" => {
@@ -226,6 +233,7 @@ fn extract_container(
226233
array_recurse_level: &mut HashMap<String, u8>,
227234
level: u8,
228235
schema: &JSONSchemaProps,
236+
cfg: &Config,
229237
) -> Result<Container, anyhow::Error> {
230238
let mut members = vec![];
231239
//debug!("analyzing object {}", serde_json::to_string(&schema).unwrap());
@@ -262,9 +270,13 @@ fn extract_container(
262270
"integer" => extract_integer_type(value)?,
263271
"array" => {
264272
// recurse through repeated arrays until we find a concrete type (keep track of how deep we went)
265-
let (array_type, recurse_level) = array_recurse_for_type(value, stack, key, 1)?;
273+
let (mut array_type, recurse_level) = array_recurse_for_type(value, stack, key, 1)?;
266274
trace!("got array {} for {} in level {}", array_type, key, recurse_level);
267-
array_recurse_level.insert(key.clone(), recurse_level);
275+
if !cfg.no_condition && key == "conditions" && is_conditions(value) {
276+
array_type = "Vec<Condition>".into();
277+
} else {
278+
array_recurse_level.insert(key.clone(), recurse_level);
279+
}
268280
array_type
269281
}
270282
"" => {
@@ -439,6 +451,21 @@ fn array_recurse_for_type(
439451

440452
// ----------------------------------------------------------------------------
441453
// helpers
454+
fn is_conditions(value: &JSONSchemaProps) -> bool {
455+
if let Some(JSONSchemaPropsOrArray::Schema(props)) = &value.items {
456+
if let Some(p) = &props.properties {
457+
let type_ = p.get("type");
458+
let status = p.get("status");
459+
let reason = p.get("reason");
460+
let message = p.get("message");
461+
let ltt = p.get("lastTransitionTime");
462+
if type_.is_some() && status.is_some() && reason.is_some() && message.is_some() && ltt.is_some() {
463+
return true;
464+
}
465+
}
466+
}
467+
false
468+
}
442469

443470
fn extract_date_type(value: &JSONSchemaProps) -> Result<String> {
444471
Ok(if let Some(f) = &value.format {
@@ -499,7 +526,7 @@ fn extract_integer_type(value: &JSONSchemaProps) -> Result<String> {
499526
// unit tests particular schema patterns
500527
#[cfg(test)]
501528
mod test {
502-
use crate::analyze;
529+
use super::{analyze, Config as Cfg};
503530
use k8s_openapi::apiextensions_apiserver::pkg::apis::apiextensions::v1::JSONSchemaProps;
504531

505532
use std::sync::Once;
@@ -545,7 +572,7 @@ mod test {
545572
let schema: JSONSchemaProps = serde_yaml::from_str(schema_str).unwrap();
546573
//println!("schema: {}", serde_json::to_string_pretty(&schema).unwrap());
547574

548-
let structs = analyze(schema, "Agent").unwrap().0;
575+
let structs = analyze(schema, "Agent", Cfg::default()).unwrap().0;
549576
//println!("{:?}", structs);
550577
let root = &structs[0];
551578
assert_eq!(root.name, "Agent");
@@ -589,7 +616,7 @@ type: object
589616
"#;
590617
let schema: JSONSchemaProps = serde_yaml::from_str(schema_str).unwrap();
591618
//println!("schema: {}", serde_json::to_string_pretty(&schema).unwrap());
592-
let structs = analyze(schema, "Server").unwrap().0;
619+
let structs = analyze(schema, "Server", Cfg::default()).unwrap().0;
593620
//println!("{:#?}", structs);
594621

595622
let root = &structs[0];
@@ -620,7 +647,7 @@ type: object
620647
"#;
621648
let schema: JSONSchemaProps = serde_yaml::from_str(schema_str).unwrap();
622649

623-
let structs = analyze(schema, "Server").unwrap().0;
650+
let structs = analyze(schema, "Server", Cfg::default()).unwrap().0;
624651
let root = &structs[0];
625652
assert_eq!(root.name, "Server");
626653
// should have an IntOrString member:
@@ -646,7 +673,7 @@ type: object
646673
type: object
647674
"#;
648675
let schema: JSONSchemaProps = serde_yaml::from_str(schema_str).unwrap();
649-
let structs = analyze(schema, "Options").unwrap().0;
676+
let structs = analyze(schema, "Options", Cfg::default()).unwrap().0;
650677
println!("got {:?}", structs);
651678
let root = &structs[0];
652679
assert_eq!(root.name, "Options");
@@ -673,7 +700,7 @@ type: object
673700
"#;
674701

675702
let schema: JSONSchemaProps = serde_yaml::from_str(schema_str).unwrap();
676-
let structs = analyze(schema, "MatchExpressions").unwrap().0;
703+
let structs = analyze(schema, "MatchExpressions", Cfg::default()).unwrap().0;
677704
println!("got {:?}", structs);
678705
let root = &structs[0];
679706
assert_eq!(root.name, "MatchExpressions");
@@ -726,7 +753,7 @@ type: object
726753
"#;
727754

728755
let schema: JSONSchemaProps = serde_yaml::from_str(schema_str).unwrap();
729-
let structs = analyze(schema, "Endpoint").unwrap().0;
756+
let structs = analyze(schema, "Endpoint", Cfg::default()).unwrap().0;
730757
println!("got {:?}", structs);
731758
let root = &structs[0];
732759
assert_eq!(root.name, "Endpoint");
@@ -810,7 +837,7 @@ type: object
810837
type: object"#;
811838

812839
let schema: JSONSchemaProps = serde_yaml::from_str(schema_str).unwrap();
813-
let structs = analyze(schema, "ServerSpec").unwrap().0;
840+
let structs = analyze(schema, "ServerSpec", Cfg::default()).unwrap().0;
814841
println!("got {:?}", structs);
815842
let root = &structs[0];
816843
assert_eq!(root.name, "ServerSpec");
@@ -881,7 +908,7 @@ type: object
881908
type: object
882909
"#;
883910
let schema: JSONSchemaProps = serde_yaml::from_str(schema_str).unwrap();
884-
let structs = analyze(schema, "ServiceMonitor").unwrap().0;
911+
let structs = analyze(schema, "ServiceMonitor", Cfg::default()).unwrap().0;
885912
println!("got {:?}", structs);
886913
let root = &structs[0];
887914
assert_eq!(root.name, "ServiceMonitor");
@@ -944,7 +971,7 @@ type: object
944971
let schema: JSONSchemaProps = serde_yaml::from_str(schema_str).unwrap();
945972

946973
//println!("schema: {}", serde_json::to_string_pretty(&schema).unwrap());
947-
let structs = analyze(schema, "DestinationRule").unwrap().0;
974+
let structs = analyze(schema, "DestinationRule", Cfg::default()).unwrap().0;
948975
//println!("{:#?}", structs);
949976

950977
// this should produce the root struct struct
@@ -979,7 +1006,7 @@ type: object
9791006
"#;
9801007
let schema: JSONSchemaProps = serde_yaml::from_str(schema_str).unwrap();
9811008
println!("got schema {}", serde_yaml::to_string(&schema).unwrap());
982-
let structs = analyze(schema, "StatusCode").unwrap().0;
1009+
let structs = analyze(schema, "StatusCode", Cfg::default()).unwrap().0;
9831010
println!("got {:?}", structs);
9841011
let root = &structs[0];
9851012
assert_eq!(root.name, "StatusCode");
@@ -1005,7 +1032,7 @@ type: object
10051032
"#;
10061033

10071034
let schema: JSONSchemaProps = serde_yaml::from_str(schema_str).unwrap();
1008-
let structs = analyze(schema, "KustomizationSpec").unwrap().0;
1035+
let structs = analyze(schema, "KustomizationSpec", Cfg::default()).unwrap().0;
10091036
println!("got {:?}", structs);
10101037
let root = &structs[0];
10111038
assert_eq!(root.name, "KustomizationSpec");
@@ -1047,7 +1074,7 @@ type: object
10471074
type: object
10481075
type: object"#;
10491076
let schema: JSONSchemaProps = serde_yaml::from_str(schema_str).unwrap();
1050-
let structs = analyze(schema, "AppProjectStatus").unwrap().0;
1077+
let structs = analyze(schema, "AppProjectStatus", Cfg::default()).unwrap().0;
10511078
println!("got {:?}", structs);
10521079
let root = &structs[0];
10531080
assert_eq!(root.name, "AppProjectStatus");
@@ -1081,7 +1108,7 @@ type: object
10811108
"#;
10821109
let schema: JSONSchemaProps = serde_yaml::from_str(schema_str).unwrap();
10831110

1084-
let structs = analyze(schema, "Agent").unwrap().0;
1111+
let structs = analyze(schema, "Agent", Cfg::default()).unwrap().0;
10851112

10861113
let root = &structs[0];
10871114
assert_eq!(root.name, "Agent");
@@ -1111,7 +1138,7 @@ type: object
11111138
"#;
11121139
let schema: JSONSchemaProps = serde_yaml::from_str(schema_str).unwrap();
11131140

1114-
let structs = analyze(schema, "Geoip").unwrap().0;
1141+
let structs = analyze(schema, "Geoip", Cfg::default()).unwrap().0;
11151142

11161143
assert_eq!(structs.len(), 1);
11171144
assert_eq!(structs[0].members.len(), 1);
@@ -1121,4 +1148,37 @@ type: object
11211148
"Option<Vec<BTreeMap<String, String>>>"
11221149
);
11231150
}
1151+
1152+
#[test]
1153+
fn uses_k8s_openapi_conditions() {
1154+
init();
1155+
let schema_str = r#"
1156+
properties:
1157+
conditions:
1158+
items:
1159+
properties:
1160+
lastTransitionTime:
1161+
type: string
1162+
message:
1163+
type: string
1164+
observedGeneration:
1165+
type: integer
1166+
reason:
1167+
type: string
1168+
status:
1169+
type: string
1170+
type:
1171+
type: string
1172+
type: object
1173+
type: array
1174+
type: object
1175+
"#;
1176+
1177+
let schema: JSONSchemaProps = serde_yaml::from_str(schema_str).unwrap();
1178+
1179+
let structs = analyze(schema, "Gateway", Cfg::default()).unwrap().0;
1180+
assert_eq!(structs.len(), 1);
1181+
assert_eq!(structs[0].members.len(), 1);
1182+
assert_eq!(structs[0].members[0].type_, "Option<Vec<Condition>>");
1183+
}
11241184
}

src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#[macro_use] extern crate log;
22

33
mod analyzer;
4-
pub use analyzer::analyze;
4+
pub use analyzer::{analyze, Config};
55
mod output;
66
pub use output::{Container, Member, Output};

src/main.rs

+18-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use clap::{CommandFactory, Parser, Subcommand};
55
use k8s_openapi::apiextensions_apiserver::pkg::apis::apiextensions::v1::{
66
CustomResourceDefinition, CustomResourceDefinitionVersion, CustomResourceSubresources,
77
};
8-
use kopium::{analyze, Container};
8+
use kopium::{analyze, Config, Container};
99
use kube::{api, core::Version, Api, Client, ResourceExt};
1010
use quote::format_ident;
1111

@@ -89,6 +89,13 @@ struct Kopium {
8989
/// the output first.
9090
#[arg(long, short = 'e')]
9191
elide: Vec<String>,
92+
93+
/// Enable generation of custom Condition APIs.
94+
///
95+
/// If false, it detects if a particular path is an array of Condition objects and uses a standard
96+
/// Condition API instead of generating a custom definition.
97+
#[arg(long)]
98+
no_condition: bool,
9299
}
93100

94101
#[derive(Clone, Copy, Debug, Subcommand)]
@@ -183,7 +190,13 @@ impl Kopium {
183190

184191
if let Some(schema) = data {
185192
log::debug!("schema: {}", serde_json::to_string_pretty(&schema)?);
186-
let structs = analyze(schema, kind)?.rename().builder_fields(self.builders).0;
193+
let cfg = Config {
194+
no_condition: self.no_condition,
195+
};
196+
let structs = analyze(schema, kind, cfg)?
197+
.rename()
198+
.builder_fields(self.builders)
199+
.0;
187200

188201
if !self.hide_prelude {
189202
self.print_prelude(&structs);
@@ -340,6 +353,9 @@ impl Kopium {
340353
if results.iter().any(|o| o.uses_int_or_string()) {
341354
println!("use k8s_openapi::apimachinery::pkg::util::intstr::IntOrString;");
342355
}
356+
if results.iter().any(|o| o.contains_conditions()) && !self.no_condition {
357+
println!("use k8s_openapi::apimachinery::pkg::apis::meta::v1::Condition;");
358+
}
343359
println!();
344360
}
345361

src/output.rs

+4
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ impl Container {
7272
pub fn is_main_container(&self) -> bool {
7373
self.level == 1 && self.name.ends_with("Spec")
7474
}
75+
76+
pub fn contains_conditions(&self) -> bool {
77+
self.members.iter().any(|m| m.type_.contains("Vec<Condition>"))
78+
}
7579
}
7680

7781
impl Container {

0 commit comments

Comments
 (0)