Skip to content

Commit f94d64d

Browse files
bryantbiggsclaude
andauthored
feat: glob ignores for workload checks (#160)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 9c213c4 commit f94d64d

24 files changed

Lines changed: 1052 additions & 214 deletions

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

completions/_eksup

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

completions/_eksup.ps1

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

completions/eksup.bash

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

completions/eksup.elv

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

completions/eksup.fish

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/info/usage.md

Lines changed: 75 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ Options:
5959
--config <CONFIG>
6060
Path to an eksup configuration file (default: .eksup.yaml in cwd)
6161
62+
--show-suppressed
63+
Include findings suppressed by .eksup.yaml ignore rules
64+
6265
-h, --help
6366
Print help (see a summary with '-h')
6467
@@ -127,6 +130,8 @@ Options:
127130
Exclude recommendations from the output
128131
--config <CONFIG>
129132
Path to an eksup configuration file (default: .eksup.yaml in cwd)
133+
--show-suppressed
134+
Include findings suppressed by .eksup.yaml ignore rules
130135
-h, --help
131136
Print help
132137
-V, --version
@@ -150,25 +155,80 @@ eksup analyze --cluster my-cluster --config /path/to/config.yaml
150155
#### Configuration Format
151156

152157
```yaml
158+
# .eksup.yaml — full example
153159
checks:
160+
all: # applies to every workload check
161+
ignore:
162+
- name: "*"
163+
namespace: "*-dev*" # skip all dev namespaces
164+
154165
K8S002:
155-
min_replicas: 3 # Global minimum replica threshold (default: 2)
156-
ignore: # Resources to skip entirely
157-
- name: metrics-server
158-
namespace: kube-system
159-
overrides: # Per-resource threshold overrides
160-
- name: critical-app
161-
namespace: production
166+
min_replicas: 3 # default minimum
167+
ignore: # globs supported
168+
- name: "preview-*"
169+
namespace: "preview"
170+
overrides: # globs supported here too
171+
- name: "stateful-*"
172+
namespace: "prod"
162173
min_replicas: 5
163-
K8S004:
164-
ignore: # Resources to skip PDB check
165-
- name: singleton-worker
166-
namespace: batch
174+
175+
K8S003: { ignore: [{ name: "*-batch", namespace: "*" }] }
176+
K8S004: { ignore: [{ name: "singleton-*", namespace: "default" }] }
177+
K8S005: { ignore: [{ name: "*-logger*", namespace: "*monitoring*" }] }
178+
# ... K8S006, K8S007, K8S008, K8S013 all use the same `ignore:` shape
167179
```
168180

169181
- `K8S002.min_replicas`: Global minimum replica threshold (default: 2). Must be >= 1.
170-
- `K8S002.ignore`: List of resources (by name + namespace) to exclude from the minimum replicas check.
171-
- `K8S002.overrides`: Per-resource minimum replica threshold. Overrides the global default.
172-
- `K8S004.ignore`: List of resources (by name + namespace) to exclude from the PodDisruptionBudget check.
173-
- Ignore takes precedence over overrides when both match the same resource.
182+
- `K8S002.overrides`: Per-resource minimum replica threshold (globs supported). Overrides the global default.
183+
- `ignore` takes precedence over `overrides` when both match the same resource.
174184
- Unknown fields in the configuration file are rejected with an error.
185+
186+
#### The `checks.all` block
187+
188+
`checks.all.ignore` applies across **every** workload check. Selectors listed here are merged with each check's own `ignore` list, so you can suppress noisy namespaces or naming patterns in one place instead of repeating the same entry under every code.
189+
190+
#### Glob syntax
191+
192+
`name` and `namespace` selectors support standard glob syntax:
193+
194+
- `*` — matches any sequence of characters (including empty)
195+
- `?` — matches exactly one character
196+
- `[abc]` / `[a-z]` — character classes
197+
- `{a,b,c}` — brace expansion (alternation)
198+
199+
Literal strings still work and continue to behave as exact matches — existing configs without globs remain valid.
200+
201+
#### Workload checks that support `ignore`
202+
203+
The following workload checks accept the `ignore:` shape shown above (each rule is matched against the resource's `name` + `namespace`):
204+
205+
- `K8S002` — minimum replicas
206+
- `K8S003` — minimum ready seconds
207+
- `K8S004` — PodDisruptionBudget present
208+
- `K8S005` — pod topology spread / anti-affinity
209+
- `K8S006` — readiness probe configured
210+
- `K8S007` — termination grace period
211+
- `K8S008` — Docker socket mount
212+
- `K8S013``ingress-nginx` controller retirement
213+
214+
#### Cluster-level checks (no `ignore` support)
215+
216+
Cluster-level checks do not support `ignore` because their findings have no resource `name` + `namespace` to match against:
217+
218+
- `AWS001``AWS005` — AWS-side checks
219+
- `EKS001``EKS010` — EKS-side checks
220+
- `K8S001` — Kubernetes version skew
221+
- `K8S009` — Pod Security Policies
222+
- `K8S010` — EBS CSI driver
223+
- `K8S011` — kube-proxy / kubelet version skew
224+
- `K8S012` — kube-proxy IPVS mode (singleton DaemonSet; finding has no per-resource fields)
225+
226+
The right control for a cluster-level check is "disable the check entirely" — a separate, future feature.
227+
228+
#### `--show-suppressed` flag
229+
230+
Both `eksup analyze` and `eksup create playbook` accept `--show-suppressed`. By default, suppressed findings are hidden and a single-line summary (e.g. `3 findings suppressed by .eksup.yaml (use --show-suppressed to view)`) is printed. With the flag set, suppressed findings are rendered inline under a `Suppressed by .eksup.yaml (N):` section so you can audit exactly what your config is filtering out.
231+
232+
#### JSON output: `suppressed` key
233+
234+
The JSON output (`--format json`) always includes a top-level `suppressed:` key alongside the regular findings, regardless of `--show-suppressed`. Programmatic consumers can inspect every suppressed finding without re-running the analysis. The `--show-suppressed` flag only affects human-readable stdout/playbook rendering.

eksup/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ clap = { version = "4.6", default-features = false, features = ["std", "derive",
3535
clap-verbosity-flag = { version = "3.0", default-features = false, features = ["tracing"] }
3636
clap_complete = { version = "4.5", default-features = false }
3737
clap_mangen = { version = "0.2", default-features = false }
38+
globset = { version = "0.4", default-features = false }
3839
indicatif = { version = "0.18", default-features = false, features = ["unicode-width"] }
3940
handlebars = { version = "6.4", default-features = false, features = ["rust-embed"] }
4041
# https://kube.rs/kubernetes-version/

eksup/src/analysis.rs

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ pub struct Results {
1717
pub data_plane: eks::DataPlaneFindings,
1818
pub addons: eks::AddonFindings,
1919
pub kubernetes: k8s::KubernetesFindings,
20+
#[serde(rename = "suppressed")]
21+
pub kubernetes_suppressed: k8s::KubernetesSuppressed,
2022
pub service_limits: eks::ServiceLimitFindings,
2123
pub insights: eks::InsightsFindings,
2224
}
@@ -125,7 +127,9 @@ impl Results {
125127
}
126128

127129
/// Renders all findings as a formatted stdout table string
128-
pub fn to_stdout_table(&self) -> Result<String> {
130+
pub fn to_stdout_table(&self, show_suppressed: bool) -> Result<String> {
131+
use std::fmt::Write;
132+
129133
let mut output = String::new();
130134

131135
output.push_str(&self.subnets.pod_ips.to_stdout_table()?);
@@ -155,6 +159,26 @@ impl Results {
155159
output.push_str(&self.insights.upgrade_readiness.to_stdout_table()?);
156160
output.push_str(&self.insights.misconfiguration.to_stdout_table()?);
157161

162+
let suppressed_total = self.kubernetes_suppressed.total();
163+
if suppressed_total > 0 {
164+
if show_suppressed {
165+
writeln!(output, "\nSuppressed by .eksup.yaml ({suppressed_total}):")?;
166+
output.push_str(&self.kubernetes_suppressed.min_replicas.to_stdout_table()?);
167+
output.push_str(&self.kubernetes_suppressed.min_ready_seconds.to_stdout_table()?);
168+
output.push_str(&self.kubernetes_suppressed.readiness_probe.to_stdout_table()?);
169+
output.push_str(&self.kubernetes_suppressed.pod_topology_distribution.to_stdout_table()?);
170+
output.push_str(&self.kubernetes_suppressed.termination_grace_period.to_stdout_table()?);
171+
output.push_str(&self.kubernetes_suppressed.docker_socket.to_stdout_table()?);
172+
output.push_str(&self.kubernetes_suppressed.ingress_nginx_retirement.to_stdout_table()?);
173+
output.push_str(&self.kubernetes_suppressed.pod_disruption_budgets.to_stdout_table()?);
174+
} else {
175+
writeln!(
176+
output,
177+
"\n{suppressed_total} findings suppressed by .eksup.yaml (use --show-suppressed to view)"
178+
)?;
179+
}
180+
}
181+
158182
Ok(output)
159183
}
160184
}
@@ -177,30 +201,26 @@ pub async fn analyze(
177201
subnet_findings,
178202
addon_findings,
179203
dataplane_findings,
180-
kubernetes_findings,
204+
kubernetes_result,
181205
service_limit_findings,
182206
insights_findings,
183207
) = tokio::try_join!(
184208
eks::get_subnet_findings(aws, k8s, cluster),
185209
eks::get_addon_findings(aws, cluster_name, cluster_version, target_minor),
186210
eks::get_data_plane_findings(aws, cluster, target_minor),
187-
k8s::get_kubernetes_findings(
188-
k8s,
189-
control_plane_minor,
190-
target_minor,
191-
&config.checks.k8s002,
192-
&config.checks.k8s004
193-
),
211+
k8s::get_kubernetes_findings(k8s, control_plane_minor, target_minor, &config.checks),
194212
eks::get_service_limit_findings(aws),
195213
eks::get_insights_findings(aws, cluster_name),
196214
)?;
215+
let (kubernetes_findings, kubernetes_suppressed) = kubernetes_result;
197216

198217
Ok(Results {
199218
cluster: cluster_findings,
200219
subnets: subnet_findings,
201220
addons: addon_findings,
202221
data_plane: dataplane_findings,
203222
kubernetes: kubernetes_findings,
223+
kubernetes_suppressed,
204224
service_limits: service_limit_findings,
205225
insights: insights_findings,
206226
})

0 commit comments

Comments
 (0)