Skip to content

Commit 9e0cd42

Browse files
authored
Merge pull request #356 from clux/allow-avoiding-schemagen
make schema a default compile feature of kube-derive - for #355
2 parents 5b7a7f5 + d1b0df5 commit 9e0cd42

File tree

7 files changed

+126
-4
lines changed

7 files changed

+126
-4
lines changed

.circleci/config.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ jobs:
102102
- run: cargo test --lib --all -j4
103103
- run: cargo test --doc --all -j4
104104
- run: cargo test -j4 -p examples
105+
- run: cd examples && cargo test --example crd_derive_no_schema --no-default-features --features=native-tls
105106
- run: cd kube && cargo test --lib --no-default-features --features=rustls-tls
106107
- save_cache:
107108
paths:

examples/Cargo.toml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,18 @@ publish = false
99
edition = "2018"
1010

1111
[features]
12-
default = ["native-tls"]
12+
default = ["native-tls", "schema", "kubederive"]
13+
kubederive = ["kube/derive"] # by default import kube-derive with its default features
14+
schema = ["kube-derive/schema"] # crd_derive_no_schema shows how to opt out
1315
native-tls = ["reqwest/native-tls", "kube/native-tls", "kube-runtime/native-tls"]
1416
rustls-tls = ["reqwest/rustls-tls", "kube/rustls-tls", "kube-runtime/rustls-tls"]
1517

1618
[dev-dependencies]
1719
anyhow = "1.0.32"
1820
env_logger = "0.7.1"
1921
futures = "0.3.5"
20-
kube = { path = "../kube", version = "^0.44.0", default-features = false, features = ["derive"] }
22+
kube = { path = "../kube", version = "^0.44.0", default-features = false }
23+
kube-derive = { path = "../kube-derive", version = "^0.44.0", default-features = false } # only needed to opt out of schema
2124
kube-runtime = { path = "../kube-runtime", version = "^0.44.0", default-features = false }
2225
k8s-openapi = { version = "0.10.0", features = ["v1_19"], default-features = false }
2326
log = "0.4.11"
@@ -32,6 +35,7 @@ either = "1.6.0"
3235
# Some configuration tweaking require reqwest atm
3336
reqwest = { version = "0.10.8", default-features = false, features = ["json", "gzip", "stream"] }
3437
schemars = "0.8.0"
38+
static_assertions = "1.1.0"
3539

3640
[[example]]
3741
name = "configmapgen_controller"
@@ -61,6 +65,10 @@ path = "crd_derive.rs"
6165
name = "crd_derive_schema"
6266
path = "crd_derive_schema.rs"
6367

68+
[[example]] # run this without --no-default-features --features="native-tls"
69+
name = "crd_derive_no_schema"
70+
path = "crd_derive_no_schema.rs"
71+
6472
[[example]]
6573
name = "crd_reflector"
6674
path = "crd_reflector.rs"

examples/README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,21 @@ cargo run --example dynamic_api
2323
NAMESPACE=dev cargo run --example log_stream -- kafka-manager-7d4f4bd8dc-f6c44
2424
```
2525

26+
## kube-derive focused examples
27+
How deriving `CustomResource` works in practice, and how it interacts with the [schemars](https://github.com/GREsau/schemars/) dependency.
28+
29+
```sh
30+
cargo run --example crd_derive
31+
cargo run --example crd_derive_schema
32+
cargo run --example crd_derive_no_schema --no-default-features --features=native-tls
33+
```
34+
35+
The last one opts out from the default `schema` feature from `kube-derive` (and thus the need for you to derive/impl `JsonSchema`).
36+
37+
**However**: without the `schema` feature, it's left **up to you to fill in a valid openapi v3 schema**, as schemas are **required** for [v1::CustomResourceDefinitions](https://docs.rs/k8s-openapi/0.10.0/k8s_openapi/apiextensions_apiserver/pkg/apis/apiextensions/v1/struct.CustomResourceDefinition.html), and the generated crd will be rejected by the apiserver if it's missing. As the last example shows, you can do this directly without `schemars`.
38+
39+
Note that these examples also contain tests for CI, and are invoked with the same parameters, but using `cargo test` rather than `cargo run`.
40+
2641
## kube-runtime focused examples
2742

2843
### watchers

examples/crd_derive.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,10 +133,13 @@ fn verify_crd() {
133133

134134
#[test]
135135
fn verify_resource() {
136+
use static_assertions::{assert_impl_all, assert_impl_one};
136137
assert_eq!(Foo::KIND, "Foo");
137138
assert_eq!(Foo::GROUP, "clux.dev");
138139
assert_eq!(Foo::VERSION, "v1");
139140
assert_eq!(Foo::API_VERSION, "clux.dev/v1");
141+
assert_impl_all!(Foo: k8s_openapi::Resource, k8s_openapi::Metadata, Default);
142+
assert_impl_one!(MyFoo: JsonSchema);
140143
}
141144

142145
#[test]

examples/crd_derive_no_schema.rs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
use k8s_openapi::apiextensions_apiserver::pkg::apis::apiextensions::v1::{
2+
CustomResourceDefinition, CustomResourceValidation, JSONSchemaProps,
3+
};
4+
use kube_derive::CustomResource;
5+
use serde::{Deserialize, Serialize};
6+
7+
/// CustomResource with manually implemented schema
8+
///
9+
/// NB: Everything here is gated on the example's `schema` feature not being set
10+
///
11+
/// Normally you would do this by deriving JsonSchema or manually implementing it / parts of it.
12+
/// But here, we simply drop in a valid schema from a string and avoid schemars from the dependency tree entirely.
13+
#[cfg(not(feature = "schema"))]
14+
#[derive(CustomResource, Serialize, Deserialize, Debug, Clone)]
15+
#[kube(group = "clux.dev", version = "v1", kind = "Bar", namespaced)]
16+
pub struct MyBar {
17+
bars: u32,
18+
}
19+
20+
const MANUAL_SCHEMA: &'static str = r#"
21+
type: object
22+
properties:
23+
spec:
24+
type: object
25+
properties:
26+
bars:
27+
type: int
28+
required:
29+
- bars
30+
"#;
31+
32+
#[cfg(not(feature = "schema"))]
33+
impl Bar {
34+
fn crd_with_manual_schema() -> CustomResourceDefinition {
35+
let schema: JSONSchemaProps = serde_yaml::from_str(MANUAL_SCHEMA).expect("invalid schema");
36+
37+
let mut crd = Self::crd();
38+
crd.spec.versions.iter_mut().for_each(|v| {
39+
v.schema = Some(CustomResourceValidation {
40+
open_api_v3_schema: Some(schema.clone()),
41+
})
42+
});
43+
crd
44+
}
45+
}
46+
47+
48+
#[cfg(not(feature = "schema"))]
49+
fn main() {
50+
let crd = Bar::crd_with_manual_schema();
51+
println!("{}", serde_yaml::to_string(&crd).unwrap());
52+
}
53+
#[cfg(feature = "schema")]
54+
fn main() {
55+
eprintln!("This example it disabled when using the schema feature");
56+
}
57+
58+
// Verify CustomResource derivable still
59+
#[cfg(not(feature = "schema"))]
60+
#[test]
61+
fn verify_bar_is_a_custom_resource() {
62+
use k8s_openapi::Resource;
63+
use schemars::JsonSchema; // only for ensuring it's not implemented
64+
use static_assertions::{assert_impl_all, assert_not_impl_any};
65+
66+
println!("Kind {}", Bar::KIND);
67+
let bar = Bar::new("five", MyBar { bars: 5 });
68+
println!("Spec: {:?}", bar.spec);
69+
assert_impl_all!(Bar: k8s_openapi::Resource, k8s_openapi::Metadata);
70+
assert_not_impl_any!(MyBar: JsonSchema); // but no schemars schema implemented
71+
72+
let crd = Bar::crd_with_manual_schema();
73+
for v in crd.spec.versions {
74+
assert!(v.schema.unwrap().open_api_v3_schema.is_some());
75+
}
76+
}

kube-derive/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ serde_json = "1.0.53"
1818
[lib]
1919
proc-macro = true
2020

21+
[features]
22+
default = ["schema"]
23+
schema = []
24+
2125
[dev-dependencies]
2226
serde = { version = "1.0.111", features = ["derive"] }
2327
serde_yaml = "0.8.14"

kube-derive/src/custom_resource.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ impl CustomDerive for CustomResource {
264264

265265
// Schema generation is always enabled for v1 because it's mandatory.
266266
// TODO Enable schema generation for v1beta1 if the spec derives `JsonSchema`.
267-
let schema_gen_enabled = apiextensions == "v1";
267+
let schema_gen_enabled = apiextensions == "v1" && cfg!(feature = "schema");
268268
// We exclude fields `apiVersion`, `kind`, and `metadata` from our schema because
269269
// these are validated by the API server implicitly. Also, we can't generate the
270270
// schema for `metadata` (`ObjectMeta`) because it doesn't implement `JsonSchema`.
@@ -372,14 +372,29 @@ impl CustomDerive for CustomResource {
372372
let short_json = serde_json::to_string(&shortnames).unwrap();
373373
let crd_meta_name = format!("{}.{}", plural, group);
374374
let crd_meta = quote! { { "name": #crd_meta_name } };
375-
let jsondata = if apiextensions == "v1" {
375+
376+
let schemagen = if schema_gen_enabled {
376377
quote! {
377378
// Don't use definitions and don't include `$schema` because these are not allowed.
378379
let gen = schemars::gen::SchemaSettings::openapi3().with(|s| {
379380
s.inline_subschemas = true;
380381
s.meta_schema = None;
381382
}).into_generator();
382383
let schema = gen.into_root_schema_for::<Self>();
384+
}
385+
} else {
386+
// we could issue a compile time warning for this, but it would hit EVERY compile, which would be noisy
387+
// eprintln!("warning: kube-derive configured with manual schema generation");
388+
// users must manually set a valid schema in crd.spec.versions[*].schema - see examples: crd_derive_no_schema
389+
quote! {
390+
let schema: Option<k8s_openapi::apiextensions_apiserver::pkg::apis::apiextensions::v1::JSONSchemaProps> = None;
391+
}
392+
};
393+
394+
let jsondata = if apiextensions == "v1" {
395+
quote! {
396+
#schemagen
397+
383398
let jsondata = serde_json::json!({
384399
"metadata": #crd_meta,
385400
"spec": {

0 commit comments

Comments
 (0)