From b38d4598e5d8258c3cccd1aabfa396d107040614 Mon Sep 17 00:00:00 2001 From: clux Date: Sat, 9 Mar 2024 11:16:13 +0000 Subject: [PATCH 1/3] Failing tests for `PodMonitor` struct with duplicate names of different case Signed-off-by: clux --- justfile | 8 + src/analyzer.rs | 53 +++ src/main.rs | 10 +- tests/podmon-crd.yaml | 741 ++++++++++++++++++++++++++++++++++++++++++ tests/podmon.yaml | 15 + 5 files changed, 826 insertions(+), 1 deletion(-) create mode 100644 tests/podmon-crd.yaml create mode 100644 tests/podmon.yaml diff --git a/justfile b/justfile index 237c3c6..5b21142 100644 --- a/justfile +++ b/justfile @@ -85,5 +85,13 @@ test-istio-destrule: # NB: this currently fails because of an empty status object with preserve-unknown-fields cargo test --test runner -- --nocapture +test-podmon: + curl -sSL https://github.com/prometheus-operator/prometheus-operator/raw/main/example/prometheus-operator-crd/monitoring.coreos.com_podmonitors.yaml > tests/podmon-crd.yaml + kubectl apply --server-side -f tests/podmon-crd.yaml + cargo run --bin kopium -- podmonitors.monitoring.coreos.com > tests/gen.rs + echo "pub type CR = PodMonitor;" >> tests/gen.rs + kubectl apply -f tests/podmon.yaml + cargo test --test runner -- --nocapture + release: cargo release minor --execute diff --git a/src/analyzer.rs b/src/analyzer.rs index c1315fa..7aa4fb1 100644 --- a/src/analyzer.rs +++ b/src/analyzer.rs @@ -12,6 +12,7 @@ const IGNORED_KEYS: [&str; 3] = ["metadata", "apiVersion", "kind"]; #[derive(Default)] pub struct Config { pub no_condition: bool, + pub no_rename: Vec, } /// Scan a schema for structs and members, and recurse to find all structs @@ -724,6 +725,58 @@ type: object assert_eq!(&op.members[3].type_, ""); } + #[test] + fn avoid_rename_on_duplicated_value_containers() { + init(); + let schema_str = r#" + properties: + relabelings: + items: + properties: + action: + default: replace + enum: + - replace + - Replace + - keep + - Keep + - drop + - Drop + - labelkeep + - LabelKeep + type: string + modulus: + format: int64 + type: integer + type: object + type: array + type: object + "#; + let cfg = Cfg { + no_rename: vec!["MetricsRelabelingsAction".into()], + ..Cfg::default() + }; + let schema: JSONSchemaProps = serde_yaml::from_str(schema_str).unwrap(); + let structs = analyze(schema, "MetricRelabelings", cfg).unwrap().0; + println!("got {:?}", structs); + let root = &structs[0]; + assert_eq!(root.name, "MetricRelabelings"); + assert_eq!(root.level, 0); + assert_eq!(&root.members[0].name, "action"); + assert_eq!(&root.members[0].type_, "MetricRelabelingsAction"); + + // operator member + let op = &structs[1]; + assert!(op.is_enum); + assert_eq!(op.name, "MetricRelabelingsAction"); + + // should have enum members that avoids the clashes + assert_eq!(&op.members[0].name, "replace"); + assert_eq!(&op.members[0].type_, ""); + assert_eq!(&op.members[0].name, "Replace"); + assert_eq!(&op.members[0].type_, ""); + } + #[test] fn enum_string_within_container() { init(); diff --git a/src/main.rs b/src/main.rs index 7d872e6..104b64f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ use std::path::PathBuf; -#[macro_use] extern crate log; +#[macro_use] +extern crate log; use anyhow::{anyhow, Context, Result}; use clap::{CommandFactory, Parser, Subcommand}; use k8s_openapi::apiextensions_apiserver::pkg::apis::apiextensions::v1::{ @@ -90,6 +91,12 @@ struct Kopium { #[arg(long, short = 'e')] elide: Vec, + /// Avoid renaming structs matching struct names + /// + /// This avoids awkward cases where certain CRDs contain duplicate names with the different case + #[arg(long)] + no_rename: Vec, + /// Enable generation of custom Condition APIs. /// /// If false, it detects if a particular path is an array of Condition objects and uses a standard @@ -192,6 +199,7 @@ impl Kopium { log::debug!("schema: {}", serde_json::to_string_pretty(&schema)?); let cfg = Config { no_condition: self.no_condition, + no_rename: self.no_rename, }; let structs = analyze(schema, kind, cfg)? .rename() diff --git a/tests/podmon-crd.yaml b/tests/podmon-crd.yaml new file mode 100644 index 0000000..03479c9 --- /dev/null +++ b/tests/podmon-crd.yaml @@ -0,0 +1,741 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.13.0 + operator.prometheus.io/version: 0.72.0 + name: podmonitors.monitoring.coreos.com +spec: + group: monitoring.coreos.com + names: + categories: + - prometheus-operator + kind: PodMonitor + listKind: PodMonitorList + plural: podmonitors + shortNames: + - pmon + singular: podmonitor + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: PodMonitor defines monitoring for a set of pods. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Specification of desired Pod selection for target discovery + by Prometheus. + properties: + attachMetadata: + description: "`attachMetadata` defines additional metadata which is + added to the discovered targets. \n It requires Prometheus >= v2.37.0." + properties: + node: + description: When set to true, Prometheus must have the `get` + permission on the `Nodes` objects. + type: boolean + type: object + jobLabel: + description: "The label to use to retrieve the job name from. `jobLabel` + selects the label from the associated Kubernetes `Pod` object which + will be used as the `job` label for all metrics. \n For example + if `jobLabel` is set to `foo` and the Kubernetes `Pod` object is + labeled with `foo: bar`, then Prometheus adds the `job=\"bar\"` + label to all ingested metrics. \n If the value of this field is + empty, the `job` label of the metrics defaults to the namespace + and name of the PodMonitor object (e.g. `/`)." + type: string + keepDroppedTargets: + description: "Per-scrape limit on the number of targets dropped by + relabeling that will be kept in memory. 0 means no limit. \n It + requires Prometheus >= v2.47.0." + format: int64 + type: integer + labelLimit: + description: "Per-scrape limit on number of labels that will be accepted + for a sample. \n It requires Prometheus >= v2.27.0." + format: int64 + type: integer + labelNameLengthLimit: + description: "Per-scrape limit on length of labels name that will + be accepted for a sample. \n It requires Prometheus >= v2.27.0." + format: int64 + type: integer + labelValueLengthLimit: + description: "Per-scrape limit on length of labels value that will + be accepted for a sample. \n It requires Prometheus >= v2.27.0." + format: int64 + type: integer + namespaceSelector: + description: Selector to select which namespaces the Kubernetes `Pods` + objects are discovered from. + properties: + any: + description: Boolean describing whether all namespaces are selected + in contrast to a list restricting them. + type: boolean + matchNames: + description: List of namespace names to select from. + items: + type: string + type: array + type: object + podMetricsEndpoints: + description: List of endpoints part of this PodMonitor. + items: + description: PodMetricsEndpoint defines an endpoint serving Prometheus + metrics to be scraped by Prometheus. + properties: + authorization: + description: "`authorization` configures the Authorization header + credentials to use when scraping the target. \n Cannot be + set at the same time as `basicAuth`, or `oauth2`." + properties: + credentials: + description: Selects a key of a Secret in the namespace + that contains the credentials for authentication. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: + description: "Defines the authentication type. The value + is case-insensitive. \n \"Basic\" is not a supported value. + \n Default: \"Bearer\"" + type: string + type: object + basicAuth: + description: "`basicAuth` configures the Basic Authentication + credentials to use when scraping the target. \n Cannot be + set at the same time as `authorization`, or `oauth2`." + properties: + password: + description: '`password` specifies a key of a Secret containing + the password for authentication.' + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + username: + description: '`username` specifies a key of a Secret containing + the username for authentication.' + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + bearerTokenSecret: + description: "`bearerTokenSecret` specifies a key of a Secret + containing the bearer token for scraping targets. The secret + needs to be in the same namespace as the PodMonitor object + and readable by the Prometheus Operator. \n Deprecated: use + `authorization` instead." + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + enableHttp2: + description: '`enableHttp2` can be used to disable HTTP2 when + scraping the target.' + type: boolean + filterRunning: + description: "When true, the pods which are not running (e.g. + either in Failed or Succeeded state) are dropped during the + target discovery. \n If unset, the filtering is enabled. \n + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-phase" + type: boolean + followRedirects: + description: '`followRedirects` defines whether the scrape requests + should follow HTTP 3xx redirects.' + type: boolean + honorLabels: + description: When true, `honorLabels` preserves the metric's + labels when they collide with the target's labels. + type: boolean + honorTimestamps: + description: '`honorTimestamps` controls whether Prometheus + preserves the timestamps when exposed by the target.' + type: boolean + interval: + description: "Interval at which Prometheus scrapes the metrics + from the target. \n If empty, Prometheus uses the global scrape + interval." + pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ + type: string + metricRelabelings: + description: '`metricRelabelings` configures the relabeling + rules to apply to the samples before ingestion.' + items: + description: "RelabelConfig allows dynamic rewriting of the + label set for targets, alerts, scraped samples and remote + write samples. \n More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config" + properties: + action: + default: replace + description: "Action to perform based on the regex matching. + \n `Uppercase` and `Lowercase` actions require Prometheus + >= v2.36.0. `DropEqual` and `KeepEqual` actions require + Prometheus >= v2.41.0. \n Default: \"Replace\"" + enum: + - replace + - Replace + - keep + - Keep + - drop + - Drop + - hashmod + - HashMod + - labelmap + - LabelMap + - labeldrop + - LabelDrop + - labelkeep + - LabelKeep + - lowercase + - Lowercase + - uppercase + - Uppercase + - keepequal + - KeepEqual + - dropequal + - DropEqual + type: string + modulus: + description: "Modulus to take of the hash of the source + label values. \n Only applicable when the action is + `HashMod`." + format: int64 + type: integer + regex: + description: Regular expression against which the extracted + value is matched. + type: string + replacement: + description: "Replacement value against which a Replace + action is performed if the regular expression matches. + \n Regex capture groups are available." + type: string + separator: + description: Separator is the string between concatenated + SourceLabels. + type: string + sourceLabels: + description: The source labels select values from existing + labels. Their content is concatenated using the configured + Separator and matched against the configured regular + expression. + items: + description: LabelName is a valid Prometheus label name + which may only contain ASCII letters, numbers, as + well as underscores. + pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$ + type: string + type: array + targetLabel: + description: "Label to which the resulting string is written + in a replacement. \n It is mandatory for `Replace`, + `HashMod`, `Lowercase`, `Uppercase`, `KeepEqual` and + `DropEqual` actions. \n Regex capture groups are available." + type: string + type: object + type: array + oauth2: + description: "`oauth2` configures the OAuth2 settings to use + when scraping the target. \n It requires Prometheus >= 2.27.0. + \n Cannot be set at the same time as `authorization`, or `basicAuth`." + properties: + clientId: + description: '`clientId` specifies a key of a Secret or + ConfigMap containing the OAuth2 client''s ID.' + properties: + configMap: + description: ConfigMap containing data to use for the + targets. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap or its + key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + secret: + description: Secret containing data to use for the targets. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + clientSecret: + description: '`clientSecret` specifies a key of a Secret + containing the OAuth2 client''s secret.' + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + endpointParams: + additionalProperties: + type: string + description: '`endpointParams` configures the HTTP parameters + to append to the token URL.' + type: object + scopes: + description: '`scopes` defines the OAuth2 scopes used for + the token request.' + items: + type: string + type: array + tokenUrl: + description: '`tokenURL` configures the URL to fetch the + token from.' + minLength: 1 + type: string + required: + - clientId + - clientSecret + - tokenUrl + type: object + params: + additionalProperties: + items: + type: string + type: array + description: '`params` define optional HTTP URL parameters.' + type: object + path: + description: "HTTP path from which to scrape for metrics. \n + If empty, Prometheus uses the default value (e.g. `/metrics`)." + type: string + port: + description: "Name of the Pod port which this endpoint refers + to. \n It takes precedence over `targetPort`." + type: string + proxyUrl: + description: '`proxyURL` configures the HTTP Proxy URL (e.g. + "http://proxyserver:2195") to go through when scraping the + target.' + type: string + relabelings: + description: "`relabelings` configures the relabeling rules + to apply the target's metadata labels. \n The Operator automatically + adds relabelings for a few standard Kubernetes fields. \n + The original scrape job's name is available via the `__tmp_prometheus_job_name` + label. \n More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config" + items: + description: "RelabelConfig allows dynamic rewriting of the + label set for targets, alerts, scraped samples and remote + write samples. \n More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config" + properties: + action: + default: replace + description: "Action to perform based on the regex matching. + \n `Uppercase` and `Lowercase` actions require Prometheus + >= v2.36.0. `DropEqual` and `KeepEqual` actions require + Prometheus >= v2.41.0. \n Default: \"Replace\"" + enum: + - replace + - Replace + - keep + - Keep + - drop + - Drop + - hashmod + - HashMod + - labelmap + - LabelMap + - labeldrop + - LabelDrop + - labelkeep + - LabelKeep + - lowercase + - Lowercase + - uppercase + - Uppercase + - keepequal + - KeepEqual + - dropequal + - DropEqual + type: string + modulus: + description: "Modulus to take of the hash of the source + label values. \n Only applicable when the action is + `HashMod`." + format: int64 + type: integer + regex: + description: Regular expression against which the extracted + value is matched. + type: string + replacement: + description: "Replacement value against which a Replace + action is performed if the regular expression matches. + \n Regex capture groups are available." + type: string + separator: + description: Separator is the string between concatenated + SourceLabels. + type: string + sourceLabels: + description: The source labels select values from existing + labels. Their content is concatenated using the configured + Separator and matched against the configured regular + expression. + items: + description: LabelName is a valid Prometheus label name + which may only contain ASCII letters, numbers, as + well as underscores. + pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$ + type: string + type: array + targetLabel: + description: "Label to which the resulting string is written + in a replacement. \n It is mandatory for `Replace`, + `HashMod`, `Lowercase`, `Uppercase`, `KeepEqual` and + `DropEqual` actions. \n Regex capture groups are available." + type: string + type: object + type: array + scheme: + description: "HTTP scheme to use for scraping. \n `http` and + `https` are the expected values unless you rewrite the `__scheme__` + label via relabeling. \n If empty, Prometheus uses the default + value `http`." + enum: + - http + - https + type: string + scrapeTimeout: + description: "Timeout after which Prometheus considers the scrape + to be failed. \n If empty, Prometheus uses the global scrape + timeout unless it is less than the target's scrape interval + value in which the latter is used." + pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ + type: string + targetPort: + anyOf: + - type: integer + - type: string + description: "Name or number of the target port of the `Pod` + object behind the Service, the port must be specified with + container port property. \n Deprecated: use 'port' instead." + x-kubernetes-int-or-string: true + tlsConfig: + description: TLS configuration to use when scraping the target. + properties: + ca: + description: Certificate authority used when verifying server + certificates. + properties: + configMap: + description: ConfigMap containing data to use for the + targets. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap or its + key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + secret: + description: Secret containing data to use for the targets. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + cert: + description: Client certificate to present when doing client-authentication. + properties: + configMap: + description: ConfigMap containing data to use for the + targets. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap or its + key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + secret: + description: Secret containing data to use for the targets. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + insecureSkipVerify: + description: Disable target certificate validation. + type: boolean + keySecret: + description: Secret containing the client key file for the + targets. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + serverName: + description: Used to verify the hostname for the targets. + type: string + type: object + trackTimestampsStaleness: + description: "`trackTimestampsStaleness` defines whether Prometheus + tracks staleness of the metrics that have an explicit timestamp + present in scraped data. Has no effect if `honorTimestamps` + is false. \n It requires Prometheus >= v2.48.0." + type: boolean + type: object + type: array + podTargetLabels: + description: '`podTargetLabels` defines the labels which are transferred + from the associated Kubernetes `Pod` object onto the ingested metrics.' + items: + type: string + type: array + sampleLimit: + description: '`sampleLimit` defines a per-scrape limit on the number + of scraped samples that will be accepted.' + format: int64 + type: integer + scrapeClass: + description: The scrape class to apply. + minLength: 1 + type: string + scrapeProtocols: + description: "`scrapeProtocols` defines the protocols to negotiate + during a scrape. It tells clients the protocols supported by Prometheus + in order of preference (from most to least preferred). \n If unset, + Prometheus uses its default value. \n It requires Prometheus >= + v2.49.0." + items: + description: 'ScrapeProtocol represents a protocol used by Prometheus + for scraping metrics. Supported values are: * `OpenMetricsText0.0.1` + * `OpenMetricsText1.0.0` * `PrometheusProto` * `PrometheusText0.0.4`' + enum: + - PrometheusProto + - OpenMetricsText0.0.1 + - OpenMetricsText1.0.0 + - PrometheusText0.0.4 + type: string + type: array + x-kubernetes-list-type: set + selector: + description: Label selector to select the Kubernetes `Pod` objects. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: A label selector requirement is a selector that + contains values, a key, and an operator that relates the key + and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: operator represents a key's relationship to + a set of values. Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of string values. If the + operator is In or NotIn, the values array must be non-empty. + If the operator is Exists or DoesNotExist, the values + array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single + {key,value} in the matchLabels map is equivalent to an element + of matchExpressions, whose key field is "key", the operator + is "In", and the values array contains only "value". The requirements + are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + targetLimit: + description: '`targetLimit` defines a limit on the number of scraped + targets that will be accepted.' + format: int64 + type: integer + required: + - selector + type: object + required: + - spec + type: object + served: true + storage: true diff --git a/tests/podmon.yaml b/tests/podmon.yaml new file mode 100644 index 0000000..7034ed9 --- /dev/null +++ b/tests/podmon.yaml @@ -0,0 +1,15 @@ +apiVersion: monitoring.coreos.com/v1 +kind: PodMonitor +metadata: + name: gen +spec: + jobLabel: app + namespaceSelector: + matchNames: + - gen + selector: + matchLabels: + app: gen + podMetricsEndpoints: + - port: http + path: /metrics From bebc405ddf5d0b542efc16ee56da6c80c32db0ec Mon Sep 17 00:00:00 2001 From: clux Date: Sat, 9 Mar 2024 11:56:36 +0000 Subject: [PATCH 2/3] implement and add tests for relabelings name override Signed-off-by: clux --- justfile | 4 ++-- src/analyzer.rs | 40 ++++++++++++++++++++++------------------ src/main.rs | 2 +- src/output.rs | 47 ++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 71 insertions(+), 22 deletions(-) diff --git a/justfile b/justfile index 5b21142..4841f03 100644 --- a/justfile +++ b/justfile @@ -86,9 +86,9 @@ test-istio-destrule: cargo test --test runner -- --nocapture test-podmon: - curl -sSL https://github.com/prometheus-operator/prometheus-operator/raw/main/example/prometheus-operator-crd/monitoring.coreos.com_podmonitors.yaml > tests/podmon-crd.yaml kubectl apply --server-side -f tests/podmon-crd.yaml - cargo run --bin kopium -- podmonitors.monitoring.coreos.com > tests/gen.rs + #curl -sSL https://github.com/prometheus-operator/prometheus-operator/raw/main/example/prometheus-operator-crd/monitoring.coreos.com_podmonitors.yaml > tests/podmon-crd.yaml + cargo run --bin kopium -- --no-rename RelabelingsAction podmonitors.monitoring.coreos.com > tests/gen.rs echo "pub type CR = PodMonitor;" >> tests/gen.rs kubectl apply -f tests/podmon.yaml cargo test --test runner -- --nocapture diff --git a/src/analyzer.rs b/src/analyzer.rs index 7aa4fb1..d28086d 100644 --- a/src/analyzer.rs +++ b/src/analyzer.rs @@ -173,7 +173,7 @@ fn find_containers( // plain enums do not need to recurse, can collect it here // ....although this makes it impossible for us to handle enums at the top level // TODO: move this to the top level - let new_result = analyze_enum_properties(en, &next_stack, level, schema)?; + let new_result = analyze_enum_properties(en, &next_stack, level, schema, cfg)?; results.push(new_result); } else { debug!("..not recursing into {} ('{}' is not a container)", key, x) @@ -190,6 +190,7 @@ fn analyze_enum_properties( stack: &str, level: u8, schema: &JSONSchemaProps, + cfg: &Config, ) -> Result { let mut members = vec![]; debug!("analyzing enum {}", serde_json::to_string(&schema).unwrap()); @@ -224,6 +225,7 @@ fn analyze_enum_properties( level, docs: schema.description.clone(), is_enum: true, + no_rename: cfg.no_rename.iter().any(|x| stack.contains(x)), }) } @@ -328,6 +330,7 @@ fn extract_container( level, docs: schema.description.clone(), is_enum: false, + no_rename: cfg.no_rename.iter().any(|x| stack.contains(x)), }) } @@ -740,8 +743,6 @@ type: object - Replace - keep - Keep - - drop - - Drop - labelkeep - LabelKeep type: string @@ -753,28 +754,31 @@ type: object type: object "#; let cfg = Cfg { - no_rename: vec!["MetricsRelabelingsAction".into()], + no_rename: vec!["RelabelingsAction".into()], ..Cfg::default() }; let schema: JSONSchemaProps = serde_yaml::from_str(schema_str).unwrap(); - let structs = analyze(schema, "MetricRelabelings", cfg).unwrap().0; + let structs = analyze(schema, "Endpoint", cfg).unwrap().0; println!("got {:?}", structs); let root = &structs[0]; - assert_eq!(root.name, "MetricRelabelings"); - assert_eq!(root.level, 0); - assert_eq!(&root.members[0].name, "action"); - assert_eq!(&root.members[0].type_, "MetricRelabelingsAction"); + assert_eq!(root.name, "Endpoint"); + assert_eq!(&root.members[0].type_, "Option>"); + assert!(!root.no_rename); // no-rename NOT set on EP (string not similar) - // operator member - let op = &structs[1]; - assert!(op.is_enum); - assert_eq!(op.name, "MetricRelabelingsAction"); + let rel = &structs[1]; + assert_eq!(rel.name, "EndpointRelabelings"); + assert_eq!(rel.is_enum, false); + assert_eq!(&rel.members[0].name, "action"); + assert_eq!(&rel.members[0].type_, "Option"); + assert!(!rel.no_rename); // no-rename NOT set EPR (action not in string) - // should have enum members that avoids the clashes - assert_eq!(&op.members[0].name, "replace"); - assert_eq!(&op.members[0].type_, ""); - assert_eq!(&op.members[0].name, "Replace"); - assert_eq!(&op.members[0].type_, ""); + // action enum member + let act = &structs[2]; + assert_eq!(act.name, "EndpointRelabelingsAction"); + assert_eq!(act.is_enum, true); + // no-rename SET! contains partial struct name + assert!(act.no_rename); + // NB: we verify that this causes no renames in output.rs } #[test] diff --git a/src/main.rs b/src/main.rs index 104b64f..f2b3e89 100644 --- a/src/main.rs +++ b/src/main.rs @@ -199,7 +199,7 @@ impl Kopium { log::debug!("schema: {}", serde_json::to_string_pretty(&schema)?); let cfg = Config { no_condition: self.no_condition, - no_rename: self.no_rename, + no_rename: self.no_rename.clone(), }; let structs = analyze(schema, kind, cfg)? .rename() diff --git a/src/output.rs b/src/output.rs index 5724f2a..a528be1 100644 --- a/src/output.rs +++ b/src/output.rs @@ -16,6 +16,8 @@ pub struct Container { pub docs: Option, /// Whether this container is an enum pub is_enum: bool, + /// Elide rename for this container (when passing no-rename) + pub no_rename: bool, } /// Output member belonging to an Container @@ -111,7 +113,7 @@ impl Container { .unwrap_or_else(|| panic!("invalid field name '{}' could not be escaped", m.name)) }; - if new_name != m.name { + if new_name != m.name && !self.no_rename { m.serde_annot.push(format!("rename = \"{}\"", m.name)); m.name = new_name; } @@ -177,3 +179,46 @@ impl Output { self } } + +// unit tests +#[cfg(test)] +mod test { + use super::{Container, Member}; + fn name_only_enum_member(name: &str) -> Member { + Member { + name: name.to_string(), + type_: "".to_string(), + serde_annot: vec![], + extra_annot: vec![], + docs: None, + } + } + + #[test] + fn no_rename_is_respected() { + let mut c = Container { + name: "EndpointRelabelingsAction".to_string(), + level: 1, + members: vec![ + name_only_enum_member("replace"), + name_only_enum_member("Replace"), + name_only_enum_member("keep"), + name_only_enum_member("Keep"), + name_only_enum_member("labelkeep"), + name_only_enum_member("LabelKeep"), + ], + docs: None, + is_enum: true, + no_rename: true, + }; + + c.rename(); + // should have enum members with ORIGINAL names AFTER rename + assert_eq!(&c.members[0].name, "replace"); + assert_eq!(&c.members[1].name, "Replace"); + assert_eq!(&c.members[2].name, "keep"); + assert_eq!(&c.members[3].name, "Keep"); + assert_eq!(&c.members[4].name, "labelkeep"); + assert_eq!(&c.members[5].name, "LabelKeep"); + } +} From 773ccad3cf7fc77ecc7d5c6cc3d040c5b02842b6 Mon Sep 17 00:00:00 2001 From: clux Date: Sat, 9 Mar 2024 11:58:00 +0000 Subject: [PATCH 3/3] fmt Signed-off-by: clux --- src/main.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index f2b3e89..27239f1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,5 @@ use std::path::PathBuf; -#[macro_use] -extern crate log; +#[macro_use] extern crate log; use anyhow::{anyhow, Context, Result}; use clap::{CommandFactory, Parser, Subcommand}; use k8s_openapi::apiextensions_apiserver::pkg::apis::apiextensions::v1::{