use anyhow::{Context, Result};
use aws_sdk_eks::types::Cluster;
use serde::{Deserialize, Serialize};
use crate::{
clients::{AwsClients, K8sClients},
eks,
finding::Findings,
k8s, version,
};
#[derive(Debug, Serialize, Deserialize)]
pub struct Results {
pub cluster: eks::ClusterFindings,
pub subnets: eks::SubnetFindings,
pub data_plane: eks::DataPlaneFindings,
pub addons: eks::AddonFindings,
pub kubernetes: k8s::KubernetesFindings,
#[serde(rename = "suppressed")]
pub kubernetes_suppressed: k8s::KubernetesSuppressed,
pub service_limits: eks::ServiceLimitFindings,
pub insights: eks::InsightsFindings,
}
impl Results {
pub fn filter_recommended(&mut self) {
self
.cluster
.cluster_health
.retain(|f| !f.finding.remediation.is_recommended());
self
.subnets
.control_plane_ips
.retain(|f| !f.finding.remediation.is_recommended());
self.subnets.pod_ips.retain(|f| !f.finding.remediation.is_recommended());
self.addons.health.retain(|f| !f.finding.remediation.is_recommended());
self
.addons
.version_compatibility
.retain(|f| !f.finding.remediation.is_recommended());
self
.data_plane
.eks_managed_nodegroup_health
.retain(|f| !f.finding.remediation.is_recommended());
self
.data_plane
.eks_managed_nodegroup_update
.retain(|f| !f.finding.remediation.is_recommended());
self
.data_plane
.self_managed_nodegroup_update
.retain(|f| !f.finding.remediation.is_recommended());
self
.data_plane
.al2_ami_deprecation
.retain(|f| !f.finding.remediation.is_recommended());
self
.data_plane
.node_ips
.retain(|f| !f.finding.remediation.is_recommended());
self
.kubernetes
.version_skew
.retain(|f| !f.finding.remediation.is_recommended());
self
.kubernetes
.min_replicas
.retain(|f| !f.finding.remediation.is_recommended());
self
.kubernetes
.min_ready_seconds
.retain(|f| !f.finding.remediation.is_recommended());
self
.kubernetes
.pod_topology_distribution
.retain(|f| !f.finding.remediation.is_recommended());
self
.kubernetes
.readiness_probe
.retain(|f| !f.finding.remediation.is_recommended());
self
.kubernetes
.termination_grace_period
.retain(|f| !f.finding.remediation.is_recommended());
self
.kubernetes
.docker_socket
.retain(|f| !f.finding.remediation.is_recommended());
self
.kubernetes
.kube_proxy_version_skew
.retain(|f| !f.finding.remediation.is_recommended());
self
.kubernetes
.kube_proxy_ipvs_mode
.retain(|f| !f.finding.remediation.is_recommended());
self
.kubernetes
.ingress_nginx_retirement
.retain(|f| !f.finding.remediation.is_recommended());
self
.kubernetes
.pod_disruption_budgets
.retain(|f| !f.finding.remediation.is_recommended());
self
.service_limits
.ec2_limits
.retain(|f| !f.finding.remediation.is_recommended());
self
.service_limits
.ebs_gp2_limits
.retain(|f| !f.finding.remediation.is_recommended());
self
.service_limits
.ebs_gp3_limits
.retain(|f| !f.finding.remediation.is_recommended());
self
.insights
.upgrade_readiness
.retain(|f| !f.finding.remediation.is_recommended());
self
.insights
.misconfiguration
.retain(|f| !f.finding.remediation.is_recommended());
}
pub fn to_stdout_table(&self, show_suppressed: bool) -> Result<String> {
use std::fmt::Write;
let mut output = String::new();
output.push_str(&self.subnets.pod_ips.to_stdout_table()?);
output.push_str(&self.subnets.control_plane_ips.to_stdout_table()?);
output.push_str(&self.cluster.cluster_health.to_stdout_table()?);
output.push_str(&self.data_plane.eks_managed_nodegroup_health.to_stdout_table()?);
output.push_str(&self.addons.health.to_stdout_table()?);
output.push_str(&self.addons.version_compatibility.to_stdout_table()?);
output.push_str(&self.data_plane.eks_managed_nodegroup_update.to_stdout_table()?);
output.push_str(&self.data_plane.self_managed_nodegroup_update.to_stdout_table()?);
output.push_str(&self.data_plane.al2_ami_deprecation.to_stdout_table()?);
output.push_str(&self.data_plane.node_ips.to_stdout_table()?);
output.push_str(&self.kubernetes.version_skew.to_stdout_table()?);
output.push_str(&self.kubernetes.min_replicas.to_stdout_table()?);
output.push_str(&self.kubernetes.min_ready_seconds.to_stdout_table()?);
output.push_str(&self.kubernetes.pod_topology_distribution.to_stdout_table()?);
output.push_str(&self.kubernetes.readiness_probe.to_stdout_table()?);
output.push_str(&self.kubernetes.termination_grace_period.to_stdout_table()?);
output.push_str(&self.kubernetes.docker_socket.to_stdout_table()?);
output.push_str(&self.kubernetes.kube_proxy_version_skew.to_stdout_table()?);
output.push_str(&self.kubernetes.kube_proxy_ipvs_mode.to_stdout_table()?);
output.push_str(&self.kubernetes.ingress_nginx_retirement.to_stdout_table()?);
output.push_str(&self.kubernetes.pod_disruption_budgets.to_stdout_table()?);
output.push_str(&self.service_limits.ec2_limits.to_stdout_table()?);
output.push_str(&self.service_limits.ebs_gp2_limits.to_stdout_table()?);
output.push_str(&self.service_limits.ebs_gp3_limits.to_stdout_table()?);
output.push_str(&self.insights.upgrade_readiness.to_stdout_table()?);
output.push_str(&self.insights.misconfiguration.to_stdout_table()?);
let suppressed_total = self.kubernetes_suppressed.total();
if suppressed_total > 0 {
if show_suppressed {
writeln!(output, "\nSuppressed by .eksup.yaml ({suppressed_total}):")?;
output.push_str(&self.kubernetes_suppressed.min_replicas.to_stdout_table()?);
output.push_str(&self.kubernetes_suppressed.min_ready_seconds.to_stdout_table()?);
output.push_str(&self.kubernetes_suppressed.readiness_probe.to_stdout_table()?);
output.push_str(&self.kubernetes_suppressed.pod_topology_distribution.to_stdout_table()?);
output.push_str(&self.kubernetes_suppressed.termination_grace_period.to_stdout_table()?);
output.push_str(&self.kubernetes_suppressed.docker_socket.to_stdout_table()?);
output.push_str(&self.kubernetes_suppressed.ingress_nginx_retirement.to_stdout_table()?);
output.push_str(&self.kubernetes_suppressed.pod_disruption_budgets.to_stdout_table()?);
} else {
writeln!(
output,
"\n{suppressed_total} findings suppressed by .eksup.yaml (use --show-suppressed to view)"
)?;
}
}
Ok(output)
}
}
pub async fn analyze(
aws: &impl AwsClients,
k8s: &impl K8sClients,
cluster: &Cluster,
target_minor: i32,
config: &crate::config::Config,
) -> Result<Results> {
let cluster_name = cluster.name().context("Cluster name missing from API response")?;
let cluster_version = cluster.version().context("Cluster version missing from API response")?;
let control_plane_minor = version::parse_minor(cluster_version)?;
let cluster_findings = eks::get_cluster_findings(cluster)?;
let (
subnet_findings,
addon_findings,
dataplane_findings,
kubernetes_result,
service_limit_findings,
insights_findings,
) = tokio::try_join!(
eks::get_subnet_findings(aws, k8s, cluster),
eks::get_addon_findings(aws, cluster_name, cluster_version, target_minor),
eks::get_data_plane_findings(aws, cluster, target_minor),
k8s::get_kubernetes_findings(k8s, control_plane_minor, target_minor, &config.checks),
eks::get_service_limit_findings(aws),
eks::get_insights_findings(aws, cluster_name),
)?;
let (kubernetes_findings, kubernetes_suppressed) = kubernetes_result;
Ok(Results {
cluster: cluster_findings,
subnets: subnet_findings,
addons: addon_findings,
data_plane: dataplane_findings,
kubernetes: kubernetes_findings,
kubernetes_suppressed,
service_limits: service_limit_findings,
insights: insights_findings,
})
}