diff --git a/.DS_Store b/.DS_Store index e2cb9d04..5868e780 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/src/agent/persistence.rs b/src/agent/persistence.rs index 531e038a..33572596 100644 --- a/src/agent/persistence.rs +++ b/src/agent/persistence.rs @@ -251,10 +251,11 @@ impl SessionSelector { let sessions = self.list_sessions(); // Try to parse as numeric index first - if let Ok(index) = identifier.parse::() { - if index > 0 && index <= sessions.len() { - return sessions.into_iter().nth(index - 1); - } + if let Ok(index) = identifier.parse::() + && index > 0 + && index <= sessions.len() + { + return sessions.into_iter().nth(index - 1); } // Try to find by UUID or partial UUID diff --git a/src/agent/tools/fetch.rs b/src/agent/tools/fetch.rs index 30b5b4d3..b4f63eb6 100644 --- a/src/agent/tools/fetch.rs +++ b/src/agent/tools/fetch.rs @@ -69,31 +69,30 @@ impl WebFetchTool { let robots_url = format!("{}://{}/robots.txt", url.scheme(), url.authority()); // Try to fetch robots.txt (ignore errors - many sites don't have one) - if let Ok(response) = self.client().get(&robots_url).send().await { - if response.status().is_success() { - if let Ok(robots_content) = response.text().await { - let path = url.path(); - for line in robots_content.lines() { - if let Some(disallowed) = line.strip_prefix("Disallow: ") { - let disallowed = disallowed.trim(); - if !disallowed.is_empty() { - let disallowed = if !disallowed.starts_with('/') { - format!("/{}", disallowed) - } else { - disallowed.to_string() - }; - let check_path = if !path.starts_with('/') { - format!("/{}", path) - } else { - path.to_string() - }; - if check_path.starts_with(&disallowed) { - return Err(WebFetchError(format!( - "URL {} cannot be fetched due to robots.txt restrictions", - url - ))); - } - } + if let Ok(response) = self.client().get(&robots_url).send().await + && response.status().is_success() + && let Ok(robots_content) = response.text().await + { + let path = url.path(); + for line in robots_content.lines() { + if let Some(disallowed) = line.strip_prefix("Disallow: ") { + let disallowed = disallowed.trim(); + if !disallowed.is_empty() { + let disallowed = if !disallowed.starts_with('/') { + format!("/{}", disallowed) + } else { + disallowed.to_string() + }; + let check_path = if !path.starts_with('/') { + format!("/{}", path) + } else { + path.to_string() + }; + if check_path.starts_with(&disallowed) { + return Err(WebFetchError(format!( + "URL {} cannot be fetched due to robots.txt restrictions", + url + ))); } } } diff --git a/src/agent/ui/hooks.rs b/src/agent/ui/hooks.rs index 8618c441..89240407 100644 --- a/src/agent/ui/hooks.rs +++ b/src/agent/ui/hooks.rs @@ -1193,47 +1193,47 @@ fn format_kubelint_result( let mut lines = Vec::new(); // Check for parse errors first - if let Some(errors) = parse_errors { - if !errors.is_empty() { - lines.push(format!( - "{}☸ {} parse error{} (files could not be fully analyzed){}", - ansi::HIGH, - errors.len(), - if errors.len() == 1 { "" } else { "s" }, - ansi::RESET - )); - for (i, err) in errors.iter().take(3).enumerate() { - if let Some(err_str) = err.as_str() { - let truncated = if err_str.len() > 70 { - format!("{}...", &err_str[..67]) - } else { - err_str.to_string() - }; - lines.push(format!( - "{} {} {}{}", - ansi::HIGH, - if i == errors.len().min(3) - 1 { - "└" - } else { - "│" - }, - truncated, - ansi::RESET - )); - } - } - if errors.len() > 3 { + if let Some(errors) = parse_errors + && !errors.is_empty() + { + lines.push(format!( + "{}☸ {} parse error{} (files could not be fully analyzed){}", + ansi::HIGH, + errors.len(), + if errors.len() == 1 { "" } else { "s" }, + ansi::RESET + )); + for (i, err) in errors.iter().take(3).enumerate() { + if let Some(err_str) = err.as_str() { + let truncated = if err_str.len() > 70 { + format!("{}...", &err_str[..67]) + } else { + err_str.to_string() + }; lines.push(format!( - "{} +{} more errors{}", - ansi::GRAY, - errors.len() - 3, + "{} {} {}{}", + ansi::HIGH, + if i == errors.len().min(3) - 1 { + "└" + } else { + "│" + }, + truncated, ansi::RESET )); } - // If we only have parse errors and no lint issues, return early - if total == 0 { - return (false, lines); - } + } + if errors.len() > 3 { + lines.push(format!( + "{} +{} more errors{}", + ansi::GRAY, + errors.len() - 3, + ansi::RESET + )); + } + // If we only have parse errors and no lint issues, return early + if total == 0 { + return (false, lines); } } @@ -1326,33 +1326,32 @@ fn format_kubelint_result( } // Then high priority - if shown < MAX_PREVIEW { - if let Some(high_issues) = action_plan + if shown < MAX_PREVIEW + && let Some(high_issues) = action_plan .and_then(|a| a.get("high")) .and_then(|h| h.as_array()) - { - for issue in high_issues.iter().take(MAX_PREVIEW - shown) { - lines.push(format_kubelint_issue(issue, "🟠", ansi::HIGH)); - shown += 1; - } + { + for issue in high_issues.iter().take(MAX_PREVIEW - shown) { + lines.push(format_kubelint_issue(issue, "🟠", ansi::HIGH)); + shown += 1; } } // Show quick fix hint - if let Some(quick_fixes) = v.get("quick_fixes").and_then(|q| q.as_array()) { - if let Some(first_fix) = quick_fixes.first().and_then(|f| f.as_str()) { - let truncated = if first_fix.len() > 70 { - format!("{}...", &first_fix[..67]) - } else { - first_fix.to_string() - }; - lines.push(format!( - "{} → Fix: {}{}", - ansi::INFO_BLUE, - truncated, - ansi::RESET - )); - } + if let Some(quick_fixes) = v.get("quick_fixes").and_then(|q| q.as_array()) + && let Some(first_fix) = quick_fixes.first().and_then(|f| f.as_str()) + { + let truncated = if first_fix.len() > 70 { + format!("{}...", &first_fix[..67]) + } else { + first_fix.to_string() + }; + lines.push(format!( + "{} → Fix: {}{}", + ansi::INFO_BLUE, + truncated, + ansi::RESET + )); } // Note about remaining issues @@ -1429,47 +1428,47 @@ fn format_helmlint_result( let mut lines = Vec::new(); // Check for parse errors first - if let Some(errors) = parse_errors { - if !errors.is_empty() { - lines.push(format!( - "{}⎈ {} parse error{} (chart could not be fully analyzed){}", - ansi::HIGH, - errors.len(), - if errors.len() == 1 { "" } else { "s" }, - ansi::RESET - )); - for (i, err) in errors.iter().take(3).enumerate() { - if let Some(err_str) = err.as_str() { - let truncated = if err_str.len() > 70 { - format!("{}...", &err_str[..67]) - } else { - err_str.to_string() - }; - lines.push(format!( - "{} {} {}{}", - ansi::HIGH, - if i == errors.len().min(3) - 1 { - "└" - } else { - "│" - }, - truncated, - ansi::RESET - )); - } - } - if errors.len() > 3 { + if let Some(errors) = parse_errors + && !errors.is_empty() + { + lines.push(format!( + "{}⎈ {} parse error{} (chart could not be fully analyzed){}", + ansi::HIGH, + errors.len(), + if errors.len() == 1 { "" } else { "s" }, + ansi::RESET + )); + for (i, err) in errors.iter().take(3).enumerate() { + if let Some(err_str) = err.as_str() { + let truncated = if err_str.len() > 70 { + format!("{}...", &err_str[..67]) + } else { + err_str.to_string() + }; lines.push(format!( - "{} +{} more errors{}", - ansi::GRAY, - errors.len() - 3, + "{} {} {}{}", + ansi::HIGH, + if i == errors.len().min(3) - 1 { + "└" + } else { + "│" + }, + truncated, ansi::RESET )); } - // If we only have parse errors and no lint issues, return early - if total == 0 { - return (false, lines); - } + } + if errors.len() > 3 { + lines.push(format!( + "{} +{} more errors{}", + ansi::GRAY, + errors.len() - 3, + ansi::RESET + )); + } + // If we only have parse errors and no lint issues, return early + if total == 0 { + return (false, lines); } } @@ -1562,33 +1561,32 @@ fn format_helmlint_result( } // Then high priority - if shown < MAX_PREVIEW { - if let Some(high_issues) = action_plan + if shown < MAX_PREVIEW + && let Some(high_issues) = action_plan .and_then(|a| a.get("high")) .and_then(|h| h.as_array()) - { - for issue in high_issues.iter().take(MAX_PREVIEW - shown) { - lines.push(format_helmlint_issue(issue, "🟠", ansi::HIGH)); - shown += 1; - } + { + for issue in high_issues.iter().take(MAX_PREVIEW - shown) { + lines.push(format_helmlint_issue(issue, "🟠", ansi::HIGH)); + shown += 1; } } // Show quick fix hint - if let Some(quick_fixes) = v.get("quick_fixes").and_then(|q| q.as_array()) { - if let Some(first_fix) = quick_fixes.first().and_then(|f| f.as_str()) { - let truncated = if first_fix.len() > 70 { - format!("{}...", &first_fix[..67]) - } else { - first_fix.to_string() - }; - lines.push(format!( - "{} → Fix: {}{}", - ansi::INFO_BLUE, - truncated, - ansi::RESET - )); - } + if let Some(quick_fixes) = v.get("quick_fixes").and_then(|q| q.as_array()) + && let Some(first_fix) = quick_fixes.first().and_then(|f| f.as_str()) + { + let truncated = if first_fix.len() > 70 { + format!("{}...", &first_fix[..67]) + } else { + first_fix.to_string() + }; + lines.push(format!( + "{} → Fix: {}{}", + ansi::INFO_BLUE, + truncated, + ansi::RESET + )); } // Note about remaining issues diff --git a/src/analyzer/helmlint/lint.rs b/src/analyzer/helmlint/lint.rs index ca0cee98..5fb9094c 100644 --- a/src/analyzer/helmlint/lint.rs +++ b/src/analyzer/helmlint/lint.rs @@ -315,10 +315,10 @@ fn collect_chart_files(path: &Path) -> HashSet { .into_iter() .filter_map(|e| e.ok()) { - if entry.path().is_file() { - if let Ok(relative) = entry.path().strip_prefix(path) { - files.insert(relative.display().to_string()); - } + if entry.path().is_file() + && let Ok(relative) = entry.path().strip_prefix(path) + { + files.insert(relative.display().to_string()); } } diff --git a/src/analyzer/helmlint/pragma.rs b/src/analyzer/helmlint/pragma.rs index b4cd151d..53a203b2 100644 --- a/src/analyzer/helmlint/pragma.rs +++ b/src/analyzer/helmlint/pragma.rs @@ -38,19 +38,18 @@ impl PragmaState { } // Check if the rule is ignored for this specific line - if let Some(ignores) = self.line_ignores.get(&line) { - if ignores.contains(code.as_str()) { - return true; - } + if let Some(ignores) = self.line_ignores.get(&line) + && ignores.contains(code.as_str()) + { + return true; } // Check if previous line has an ignore pragma for this line - if line > 1 { - if let Some(ignores) = self.line_ignores.get(&(line - 1)) { - if ignores.contains(code.as_str()) { - return true; - } - } + if line > 1 + && let Some(ignores) = self.line_ignores.get(&(line - 1)) + && ignores.contains(code.as_str()) + { + return true; } false diff --git a/src/analyzer/helmlint/rules/hl1xxx.rs b/src/analyzer/helmlint/rules/hl1xxx.rs index 7ad31a4f..3af285da 100644 --- a/src/analyzer/helmlint/rules/hl1xxx.rs +++ b/src/analyzer/helmlint/rules/hl1xxx.rs @@ -86,21 +86,21 @@ impl Rule for HL1002 { } fn check(&self, ctx: &LintContext) -> Vec { - if let Some(chart) = ctx.chart_metadata { - if !chart.has_valid_api_version() { - let version = match &chart.api_version { - ApiVersion::Unknown(v) => v.clone(), - _ => "unknown".to_string(), - }; - return vec![CheckFailure::new( - "HL1002", - Severity::Error, - format!("Invalid apiVersion '{}'. Must be v1 or v2", version), - "Chart.yaml", - 1, - RuleCategory::Structure, - )]; - } + if let Some(chart) = ctx.chart_metadata + && !chart.has_valid_api_version() + { + let version = match &chart.api_version { + ApiVersion::Unknown(v) => v.clone(), + _ => "unknown".to_string(), + }; + return vec![CheckFailure::new( + "HL1002", + Severity::Error, + format!("Invalid apiVersion '{}'. Must be v1 or v2", version), + "Chart.yaml", + 1, + RuleCategory::Structure, + )]; } vec![] } @@ -127,17 +127,17 @@ impl Rule for HL1003 { } fn check(&self, ctx: &LintContext) -> Vec { - if let Some(chart) = ctx.chart_metadata { - if chart.name.is_empty() { - return vec![CheckFailure::new( - "HL1003", - Severity::Error, - "Missing required field 'name' in Chart.yaml", - "Chart.yaml", - 1, - RuleCategory::Structure, - )]; - } + if let Some(chart) = ctx.chart_metadata + && chart.name.is_empty() + { + return vec![CheckFailure::new( + "HL1003", + Severity::Error, + "Missing required field 'name' in Chart.yaml", + "Chart.yaml", + 1, + RuleCategory::Structure, + )]; } vec![] } @@ -164,17 +164,17 @@ impl Rule for HL1004 { } fn check(&self, ctx: &LintContext) -> Vec { - if let Some(chart) = ctx.chart_metadata { - if chart.version.is_empty() { - return vec![CheckFailure::new( - "HL1004", - Severity::Error, - "Missing required field 'version' in Chart.yaml", - "Chart.yaml", - 1, - RuleCategory::Structure, - )]; - } + if let Some(chart) = ctx.chart_metadata + && chart.version.is_empty() + { + return vec![CheckFailure::new( + "HL1004", + Severity::Error, + "Missing required field 'version' in Chart.yaml", + "Chart.yaml", + 1, + RuleCategory::Structure, + )]; } vec![] } @@ -201,20 +201,21 @@ impl Rule for HL1005 { } fn check(&self, ctx: &LintContext) -> Vec { - if let Some(chart) = ctx.chart_metadata { - if !chart.version.is_empty() && !is_valid_semver(&chart.version) { - return vec![CheckFailure::new( - "HL1005", - Severity::Warning, - format!( - "Version '{}' is not valid SemVer (expected X.Y.Z format)", - chart.version - ), - "Chart.yaml", - 1, - RuleCategory::Structure, - )]; - } + if let Some(chart) = ctx.chart_metadata + && !chart.version.is_empty() + && !is_valid_semver(&chart.version) + { + return vec![CheckFailure::new( + "HL1005", + Severity::Warning, + format!( + "Version '{}' is not valid SemVer (expected X.Y.Z format)", + chart.version + ), + "Chart.yaml", + 1, + RuleCategory::Structure, + )]; } vec![] } @@ -241,23 +242,22 @@ impl Rule for HL1006 { } fn check(&self, ctx: &LintContext) -> Vec { - if let Some(chart) = ctx.chart_metadata { - if chart.description.is_none() + if let Some(chart) = ctx.chart_metadata + && (chart.description.is_none() || chart .description .as_ref() .map(|d| d.is_empty()) - .unwrap_or(true) - { - return vec![CheckFailure::new( - "HL1006", - Severity::Info, - "Chart.yaml is missing a description", - "Chart.yaml", - 1, - RuleCategory::Structure, - )]; - } + .unwrap_or(true)) + { + return vec![CheckFailure::new( + "HL1006", + Severity::Info, + "Chart.yaml is missing a description", + "Chart.yaml", + 1, + RuleCategory::Structure, + )]; } vec![] } @@ -284,17 +284,17 @@ impl Rule for HL1007 { } fn check(&self, ctx: &LintContext) -> Vec { - if let Some(chart) = ctx.chart_metadata { - if chart.maintainers.is_empty() { - return vec![CheckFailure::new( - "HL1007", - Severity::Info, - "Chart.yaml has no maintainers listed", - "Chart.yaml", - 1, - RuleCategory::Structure, - )]; - } + if let Some(chart) = ctx.chart_metadata + && chart.maintainers.is_empty() + { + return vec![CheckFailure::new( + "HL1007", + Severity::Info, + "Chart.yaml has no maintainers listed", + "Chart.yaml", + 1, + RuleCategory::Structure, + )]; } vec![] } @@ -321,17 +321,17 @@ impl Rule for HL1008 { } fn check(&self, ctx: &LintContext) -> Vec { - if let Some(chart) = ctx.chart_metadata { - if chart.is_deprecated() { - return vec![CheckFailure::new( - "HL1008", - Severity::Warning, - "Chart is marked as deprecated", - "Chart.yaml", - 1, - RuleCategory::Structure, - )]; - } + if let Some(chart) = ctx.chart_metadata + && chart.is_deprecated() + { + return vec![CheckFailure::new( + "HL1008", + Severity::Warning, + "Chart is marked as deprecated", + "Chart.yaml", + 1, + RuleCategory::Structure, + )]; } vec![] } @@ -359,10 +359,10 @@ impl Rule for HL1009 { fn check(&self, ctx: &LintContext) -> Vec { // Skip for library charts - if let Some(chart) = ctx.chart_metadata { - if chart.is_library() { - return vec![]; - } + if let Some(chart) = ctx.chart_metadata + && chart.is_library() + { + return vec![]; } let has_templates = ctx @@ -466,20 +466,20 @@ impl Rule for HL1012 { } fn check(&self, ctx: &LintContext) -> Vec { - if let Some(chart) = ctx.chart_metadata { - if !is_valid_chart_name(&chart.name) { - return vec![CheckFailure::new( - "HL1012", - Severity::Error, - format!( - "Chart name '{}' contains invalid characters. Use only lowercase letters, numbers, and hyphens", - chart.name - ), - "Chart.yaml", - 1, - RuleCategory::Structure, - )]; - } + if let Some(chart) = ctx.chart_metadata + && !is_valid_chart_name(&chart.name) + { + return vec![CheckFailure::new( + "HL1012", + Severity::Error, + format!( + "Chart name '{}' contains invalid characters. Use only lowercase letters, numbers, and hyphens", + chart.name + ), + "Chart.yaml", + 1, + RuleCategory::Structure, + )]; } vec![] } @@ -506,19 +506,18 @@ impl Rule for HL1013 { } fn check(&self, ctx: &LintContext) -> Vec { - if let Some(chart) = ctx.chart_metadata { - if let Some(icon) = &chart.icon { - if icon.starts_with("http://") { - return vec![CheckFailure::new( - "HL1013", - Severity::Warning, - "Icon URL should use HTTPS instead of HTTP", - "Chart.yaml", - 1, - RuleCategory::Structure, - )]; - } - } + if let Some(chart) = ctx.chart_metadata + && let Some(icon) = &chart.icon + && icon.starts_with("http://") + { + return vec![CheckFailure::new( + "HL1013", + Severity::Warning, + "Icon URL should use HTTPS instead of HTTP", + "Chart.yaml", + 1, + RuleCategory::Structure, + )]; } vec![] } @@ -545,19 +544,18 @@ impl Rule for HL1014 { } fn check(&self, ctx: &LintContext) -> Vec { - if let Some(chart) = ctx.chart_metadata { - if let Some(home) = &chart.home { - if home.starts_with("http://") { - return vec![CheckFailure::new( - "HL1014", - Severity::Warning, - "Home URL should use HTTPS instead of HTTP", - "Chart.yaml", - 1, - RuleCategory::Structure, - )]; - } - } + if let Some(chart) = ctx.chart_metadata + && let Some(home) = &chart.home + && home.starts_with("http://") + { + return vec![CheckFailure::new( + "HL1014", + Severity::Warning, + "Home URL should use HTTPS instead of HTTP", + "Chart.yaml", + 1, + RuleCategory::Structure, + )]; } vec![] } diff --git a/src/analyzer/helmlint/rules/hl2xxx.rs b/src/analyzer/helmlint/rules/hl2xxx.rs index 9db1ae56..7cdcbe36 100644 --- a/src/analyzer/helmlint/rules/hl2xxx.rs +++ b/src/analyzer/helmlint/rules/hl2xxx.rs @@ -239,25 +239,23 @@ impl Rule for HL2005 { let lower_path = path.to_lowercase(); let is_port_field = port_patterns.iter().any(|p| lower_path.ends_with(p)); - if is_port_field { - if let Some(value) = values.get(path) { - if let Some(port) = extract_port_number(value) { - if !(1..=65535).contains(&port) { - let line = values.line_for_path(path).unwrap_or(1); - failures.push(CheckFailure::new( - "HL2005", - Severity::Error, - format!( - "Invalid port number {} at '{}'. Must be between 1 and 65535", - port, path - ), - "values.yaml", - line, - RuleCategory::Values, - )); - } - } - } + if is_port_field + && let Some(value) = values.get(path) + && let Some(port) = extract_port_number(value) + && !(1..=65535).contains(&port) + { + let line = values.line_for_path(path).unwrap_or(1); + failures.push(CheckFailure::new( + "HL2005", + Severity::Error, + format!( + "Invalid port number {} at '{}'. Must be between 1 and 65535", + port, path + ), + "values.yaml", + line, + RuleCategory::Values, + )); } } @@ -296,11 +294,12 @@ impl Rule for HL2007 { // Look for image.tag or similar patterns for path in &values.defined_paths { let lower_path = path.to_lowercase(); - if lower_path.ends_with(".tag") || lower_path.ends_with("imagetag") { - if let Some(serde_yaml::Value::String(tag)) = values.get(path) { - if tag == "latest" { - let line = values.line_for_path(path).unwrap_or(1); - failures.push(CheckFailure::new( + if (lower_path.ends_with(".tag") || lower_path.ends_with("imagetag")) + && let Some(serde_yaml::Value::String(tag)) = values.get(path) + && tag == "latest" + { + let line = values.line_for_path(path).unwrap_or(1); + failures.push(CheckFailure::new( "HL2007", Severity::Warning, format!( @@ -311,8 +310,6 @@ impl Rule for HL2007 { line, RuleCategory::Values, )); - } - } } } @@ -350,25 +347,23 @@ impl Rule for HL2008 { for path in &values.defined_paths { let lower_path = path.to_lowercase(); - if lower_path.ends_with("replicacount") || lower_path.ends_with("replicas") { - if let Some(value) = values.get(path) { - if let Some(count) = extract_number(value) { - if count == 0 { - let line = values.line_for_path(path).unwrap_or(1); - failures.push(CheckFailure::new( - "HL2008", - Severity::Warning, - format!( - "Replica count at '{}' is 0. No pods will be created by default", - path - ), - "values.yaml", - line, - RuleCategory::Values, - )); - } - } - } + if (lower_path.ends_with("replicacount") || lower_path.ends_with("replicas")) + && let Some(value) = values.get(path) + && let Some(count) = extract_number(value) + && count == 0 + { + let line = values.line_for_path(path).unwrap_or(1); + failures.push(CheckFailure::new( + "HL2008", + Severity::Warning, + format!( + "Replica count at '{}' is 0. No pods will be created by default", + path + ), + "values.yaml", + line, + RuleCategory::Values, + )); } } diff --git a/src/analyzer/helmlint/rules/hl3xxx.rs b/src/analyzer/helmlint/rules/hl3xxx.rs index b53b8d0c..139fb66e 100644 --- a/src/analyzer/helmlint/rules/hl3xxx.rs +++ b/src/analyzer/helmlint/rules/hl3xxx.rs @@ -304,10 +304,10 @@ impl Rule for HL3008 { fn check(&self, ctx: &LintContext) -> Vec { // Skip for library charts - if let Some(chart) = ctx.chart_metadata { - if chart.is_library() { - return vec![]; - } + if let Some(chart) = ctx.chart_metadata + && chart.is_library() + { + return vec![]; } let has_notes = ctx.files.iter().any(|f| f.ends_with("NOTES.txt")); diff --git a/src/analyzer/helmlint/rules/hl4xxx.rs b/src/analyzer/helmlint/rules/hl4xxx.rs index 03d32030..db002009 100644 --- a/src/analyzer/helmlint/rules/hl4xxx.rs +++ b/src/analyzer/helmlint/rules/hl4xxx.rs @@ -123,20 +123,19 @@ impl Rule for HL4002 { // Check values.yaml if let Some(values) = ctx.values { for path in &values.defined_paths { - if path.to_lowercase().contains("privileged") { - if let Some(value) = values.get(path) { - if is_truthy(value) { - let line = values.line_for_path(path).unwrap_or(1); - failures.push(CheckFailure::new( - "HL4002", - Severity::Error, - format!("Privileged mode enabled at '{}'", path), - "values.yaml", - line, - RuleCategory::Security, - )); - } - } + if path.to_lowercase().contains("privileged") + && let Some(value) = values.get(path) + && is_truthy(value) + { + let line = values.line_for_path(path).unwrap_or(1); + failures.push(CheckFailure::new( + "HL4002", + Severity::Error, + format!("Privileged mode enabled at '{}'", path), + "values.yaml", + line, + RuleCategory::Security, + )); } } } @@ -144,17 +143,17 @@ impl Rule for HL4002 { // Check templates for hardcoded privileged: true for template in ctx.templates { for token in &template.tokens { - if let TemplateToken::Text { content, line } = token { - if content.contains("privileged: true") { - failures.push(CheckFailure::new( - "HL4002", - Severity::Error, - "Container is configured with privileged: true", - &template.path, - *line, - RuleCategory::Security, - )); - } + if let TemplateToken::Text { content, line } = token + && content.contains("privileged: true") + { + failures.push(CheckFailure::new( + "HL4002", + Severity::Error, + "Container is configured with privileged: true", + &template.path, + *line, + RuleCategory::Security, + )); } } } @@ -188,9 +187,10 @@ impl Rule for HL4003 { for template in ctx.templates { for token in &template.tokens { - if let TemplateToken::Text { content, line } = token { - if content.contains("hostPath:") { - failures.push(CheckFailure::new( + if let TemplateToken::Text { content, line } = token + && content.contains("hostPath:") + { + failures.push(CheckFailure::new( "HL4003", Severity::Warning, "Using hostPath volume mount. This can expose the host filesystem to the container", @@ -198,7 +198,6 @@ impl Rule for HL4003 { *line, RuleCategory::Security, )); - } } } } @@ -233,20 +232,19 @@ impl Rule for HL4004 { // Check values.yaml if let Some(values) = ctx.values { for path in &values.defined_paths { - if path.to_lowercase().contains("hostnetwork") { - if let Some(value) = values.get(path) { - if is_truthy(value) { - let line = values.line_for_path(path).unwrap_or(1); - failures.push(CheckFailure::new( - "HL4004", - Severity::Warning, - format!("Host network enabled at '{}'", path), - "values.yaml", - line, - RuleCategory::Security, - )); - } - } + if path.to_lowercase().contains("hostnetwork") + && let Some(value) = values.get(path) + && is_truthy(value) + { + let line = values.line_for_path(path).unwrap_or(1); + failures.push(CheckFailure::new( + "HL4004", + Severity::Warning, + format!("Host network enabled at '{}'", path), + "values.yaml", + line, + RuleCategory::Security, + )); } } } @@ -254,17 +252,17 @@ impl Rule for HL4004 { // Check templates for template in ctx.templates { for token in &template.tokens { - if let TemplateToken::Text { content, line } = token { - if content.contains("hostNetwork: true") { - failures.push(CheckFailure::new( - "HL4004", - Severity::Warning, - "Pod uses host network. This bypasses network policies", - &template.path, - *line, - RuleCategory::Security, - )); - } + if let TemplateToken::Text { content, line } = token + && content.contains("hostNetwork: true") + { + failures.push(CheckFailure::new( + "HL4004", + Severity::Warning, + "Pod uses host network. This bypasses network policies", + &template.path, + *line, + RuleCategory::Security, + )); } } } @@ -298,17 +296,17 @@ impl Rule for HL4005 { for template in ctx.templates { for token in &template.tokens { - if let TemplateToken::Text { content, line } = token { - if content.contains("hostPID: true") { - failures.push(CheckFailure::new( - "HL4005", - Severity::Warning, - "Pod uses host PID namespace. This can expose host processes", - &template.path, - *line, - RuleCategory::Security, - )); - } + if let TemplateToken::Text { content, line } = token + && content.contains("hostPID: true") + { + failures.push(CheckFailure::new( + "HL4005", + Severity::Warning, + "Pod uses host PID namespace. This can expose host processes", + &template.path, + *line, + RuleCategory::Security, + )); } } } diff --git a/src/analyzer/kubelint/config.rs b/src/analyzer/kubelint/config.rs index a22ad294..f24e14ae 100644 --- a/src/analyzer/kubelint/config.rs +++ b/src/analyzer/kubelint/config.rs @@ -156,10 +156,10 @@ impl KubelintConfig { let path_str = path.to_string_lossy(); for pattern in &self.ignore_paths { - if let Ok(glob) = glob::Pattern::new(pattern) { - if glob.matches(&path_str) { - return true; - } + if let Ok(glob) = glob::Pattern::new(pattern) + && glob.matches(&path_str) + { + return true; } // Also check simple prefix/suffix matches if path_str.contains(pattern) { @@ -186,10 +186,10 @@ impl KubelintConfig { pub fn load_from_default() -> Option { for filename in &[".kube-linter.yaml", ".kube-linter.yml"] { let path = Path::new(filename); - if path.exists() { - if let Ok(config) = Self::load_from_file(path) { - return Some(config); - } + if path.exists() + && let Ok(config) = Self::load_from_file(path) + { + return Some(config); } } None diff --git a/src/analyzer/kubelint/templates/hostnetwork.rs b/src/analyzer/kubelint/templates/hostnetwork.rs index 945e0c4b..abfc3671 100644 --- a/src/analyzer/kubelint/templates/hostnetwork.rs +++ b/src/analyzer/kubelint/templates/hostnetwork.rs @@ -43,17 +43,17 @@ impl CheckFunc for HostNetworkCheck { fn check(&self, object: &Object) -> Vec { let mut diagnostics = Vec::new(); - if let Some(pod_spec) = extract::pod_spec::extract_pod_spec(&object.k8s_object) { - if pod_spec.host_network == Some(true) { - diagnostics.push(Diagnostic { - message: "Pod is configured to use the host's network namespace".to_string(), - remediation: Some( - "Remove hostNetwork: true unless absolutely necessary. \ + if let Some(pod_spec) = extract::pod_spec::extract_pod_spec(&object.k8s_object) + && pod_spec.host_network == Some(true) + { + diagnostics.push(Diagnostic { + message: "Pod is configured to use the host's network namespace".to_string(), + remediation: Some( + "Remove hostNetwork: true unless absolutely necessary. \ Using host network grants access to all network interfaces on the host." - .to_string(), - ), - }); - } + .to_string(), + ), + }); } diagnostics @@ -98,18 +98,18 @@ impl CheckFunc for HostPIDCheck { fn check(&self, object: &Object) -> Vec { let mut diagnostics = Vec::new(); - if let Some(pod_spec) = extract::pod_spec::extract_pod_spec(&object.k8s_object) { - if pod_spec.host_pid == Some(true) { - diagnostics.push(Diagnostic { - message: "Pod is configured to use the host's PID namespace".to_string(), - remediation: Some( - "Remove hostPID: true unless absolutely necessary. \ + if let Some(pod_spec) = extract::pod_spec::extract_pod_spec(&object.k8s_object) + && pod_spec.host_pid == Some(true) + { + diagnostics.push(Diagnostic { + message: "Pod is configured to use the host's PID namespace".to_string(), + remediation: Some( + "Remove hostPID: true unless absolutely necessary. \ Using host PID allows processes in the container to see and signal all \ processes on the host." - .to_string(), - ), - }); - } + .to_string(), + ), + }); } diagnostics @@ -154,9 +154,10 @@ impl CheckFunc for HostIPCCheck { fn check(&self, object: &Object) -> Vec { let mut diagnostics = Vec::new(); - if let Some(pod_spec) = extract::pod_spec::extract_pod_spec(&object.k8s_object) { - if pod_spec.host_ipc == Some(true) { - diagnostics.push(Diagnostic { + if let Some(pod_spec) = extract::pod_spec::extract_pod_spec(&object.k8s_object) + && pod_spec.host_ipc == Some(true) + { + diagnostics.push(Diagnostic { message: "Pod is configured to use the host's IPC namespace".to_string(), remediation: Some( "Remove hostIPC: true unless absolutely necessary. \ @@ -164,7 +165,6 @@ impl CheckFunc for HostIPCCheck { .to_string(), ), }); - } } diagnostics diff --git a/src/analyzer/kubelint/templates/misc.rs b/src/analyzer/kubelint/templates/misc.rs index 607b6173..1d2a0845 100644 --- a/src/analyzer/kubelint/templates/misc.rs +++ b/src/analyzer/kubelint/templates/misc.rs @@ -52,25 +52,22 @@ impl CheckFunc for SysctlsCheck { "net.", ]; - if let Some(pod_spec) = extract::pod_spec::extract_pod_spec(&object.k8s_object) { - if let Some(sc) = &pod_spec.security_context { - for sysctl in &sc.sysctls { - let is_unsafe = unsafe_sysctls - .iter() - .any(|prefix| sysctl.name.starts_with(prefix)); - if is_unsafe { - diagnostics.push(Diagnostic { - message: format!( - "Pod uses potentially unsafe sysctl '{}'", - sysctl.name - ), - remediation: Some( - "Ensure this sysctl is allowed by the cluster's PodSecurityPolicy \ + if let Some(pod_spec) = extract::pod_spec::extract_pod_spec(&object.k8s_object) + && let Some(sc) = &pod_spec.security_context + { + for sysctl in &sc.sysctls { + let is_unsafe = unsafe_sysctls + .iter() + .any(|prefix| sysctl.name.starts_with(prefix)); + if is_unsafe { + diagnostics.push(Diagnostic { + message: format!("Pod uses potentially unsafe sysctl '{}'", sysctl.name), + remediation: Some( + "Ensure this sysctl is allowed by the cluster's PodSecurityPolicy \ or PodSecurityStandard and is necessary for your workload." - .to_string(), - ), - }); - } + .to_string(), + ), + }); } } } @@ -117,30 +114,27 @@ impl CheckFunc for DnsConfigOptionsCheck { fn check(&self, object: &Object) -> Vec { let mut diagnostics = Vec::new(); - if let Some(pod_spec) = extract::pod_spec::extract_pod_spec(&object.k8s_object) { - if let Some(dns_config) = &pod_spec.dns_config { - // Check for ndots setting that could cause performance issues - for option in &dns_config.options { - if let Some(name) = &option.name { - if name == "ndots" { - if let Some(value) = &option.value { - if let Ok(ndots) = value.parse::() { - if ndots > 5 { - diagnostics.push(Diagnostic { - message: format!( - "DNS ndots is set to {}, which may cause DNS lookup performance issues", - ndots - ), - remediation: Some( - "Consider lowering ndots to 2 or less for better DNS performance." - .to_string(), - ), - }); - } - } - } - } - } + if let Some(pod_spec) = extract::pod_spec::extract_pod_spec(&object.k8s_object) + && let Some(dns_config) = &pod_spec.dns_config + { + // Check for ndots setting that could cause performance issues + for option in &dns_config.options { + if let Some(name) = &option.name + && name == "ndots" + && let Some(value) = &option.value + && let Ok(ndots) = value.parse::() + && ndots > 5 + { + diagnostics.push(Diagnostic { + message: format!( + "DNS ndots is set to {}, which may cause DNS lookup performance issues", + ndots + ), + remediation: Some( + "Consider lowering ndots to 2 or less for better DNS performance." + .to_string(), + ), + }); } } } diff --git a/src/analyzer/kubelint/templates/pdb.rs b/src/analyzer/kubelint/templates/pdb.rs index b9076a3a..3a0d4939 100644 --- a/src/analyzer/kubelint/templates/pdb.rs +++ b/src/analyzer/kubelint/templates/pdb.rs @@ -43,21 +43,21 @@ impl CheckFunc for PdbMaxUnavailableCheck { fn check(&self, object: &Object) -> Vec { let mut diagnostics = Vec::new(); - if let K8sObject::PodDisruptionBudget(pdb) = &object.k8s_object { - if let Some(max_unavailable) = &pdb.max_unavailable { - // Check if it's set to 0 or 0% - if max_unavailable == "0" || max_unavailable == "0%" { - diagnostics.push(Diagnostic { - message: - "PDB maxUnavailable is set to 0, which blocks all voluntary disruptions" - .to_string(), - remediation: Some( - "Set maxUnavailable to at least 1 or a non-zero percentage to allow \ + if let K8sObject::PodDisruptionBudget(pdb) = &object.k8s_object + && let Some(max_unavailable) = &pdb.max_unavailable + { + // Check if it's set to 0 or 0% + if max_unavailable == "0" || max_unavailable == "0%" { + diagnostics.push(Diagnostic { + message: + "PDB maxUnavailable is set to 0, which blocks all voluntary disruptions" + .to_string(), + remediation: Some( + "Set maxUnavailable to at least 1 or a non-zero percentage to allow \ voluntary disruptions during cluster maintenance." - .to_string(), - ), - }); - } + .to_string(), + ), + }); } } @@ -103,19 +103,21 @@ impl CheckFunc for PdbMinAvailableCheck { fn check(&self, object: &Object) -> Vec { let mut diagnostics = Vec::new(); - if let K8sObject::PodDisruptionBudget(pdb) = &object.k8s_object { - if let Some(min_available) = &pdb.min_available { - // Check if it's set to 100% - if min_available == "100%" { - diagnostics.push(Diagnostic { - message: "PDB minAvailable is set to 100%, which blocks all voluntary disruptions".to_string(), - remediation: Some( - "Set minAvailable to less than 100% to allow voluntary disruptions \ + if let K8sObject::PodDisruptionBudget(pdb) = &object.k8s_object + && let Some(min_available) = &pdb.min_available + { + // Check if it's set to 100% + if min_available == "100%" { + diagnostics.push(Diagnostic { + message: + "PDB minAvailable is set to 100%, which blocks all voluntary disruptions" + .to_string(), + remediation: Some( + "Set minAvailable to less than 100% to allow voluntary disruptions \ during cluster maintenance." - .to_string(), - ), - }); - } + .to_string(), + ), + }); } } diff --git a/src/analyzer/kubelint/templates/privileged.rs b/src/analyzer/kubelint/templates/privileged.rs index 9f6f45fd..3057bb83 100644 --- a/src/analyzer/kubelint/templates/privileged.rs +++ b/src/analyzer/kubelint/templates/privileged.rs @@ -45,9 +45,10 @@ impl CheckFunc for PrivilegedCheck { if let Some(pod_spec) = extract::pod_spec::extract_pod_spec(&object.k8s_object) { for container in extract::container::all_containers(pod_spec) { - if let Some(sc) = &container.security_context { - if sc.privileged == Some(true) { - diagnostics.push(Diagnostic { + if let Some(sc) = &container.security_context + && sc.privileged == Some(true) + { + diagnostics.push(Diagnostic { message: format!( "Container '{}' is running in privileged mode", container.name @@ -57,7 +58,6 @@ impl CheckFunc for PrivilegedCheck { Set securityContext.privileged to false.".to_string() ), }); - } } } } diff --git a/src/analyzer/kubelint/templates/rbac.rs b/src/analyzer/kubelint/templates/rbac.rs index 846ad541..1d1318d4 100644 --- a/src/analyzer/kubelint/templates/rbac.rs +++ b/src/analyzer/kubelint/templates/rbac.rs @@ -49,17 +49,18 @@ impl CheckFunc for ClusterAdminRoleBindingCheck { _ => None, }; - if let Some(role_ref) = role_ref { - if role_ref.kind == "ClusterRole" && role_ref.name == "cluster-admin" { - diagnostics.push(Diagnostic { - message: "Binding grants cluster-admin privileges".to_string(), - remediation: Some( - "Avoid binding to cluster-admin. Create a more restrictive ClusterRole \ + if let Some(role_ref) = role_ref + && role_ref.kind == "ClusterRole" + && role_ref.name == "cluster-admin" + { + diagnostics.push(Diagnostic { + message: "Binding grants cluster-admin privileges".to_string(), + remediation: Some( + "Avoid binding to cluster-admin. Create a more restrictive ClusterRole \ with only the required permissions." - .to_string(), - ), - }); - } + .to_string(), + ), + }); } diagnostics diff --git a/src/analyzer/kubelint/templates/unsafeprocmount.rs b/src/analyzer/kubelint/templates/unsafeprocmount.rs index 7d8330a9..cade100b 100644 --- a/src/analyzer/kubelint/templates/unsafeprocmount.rs +++ b/src/analyzer/kubelint/templates/unsafeprocmount.rs @@ -45,10 +45,11 @@ impl CheckFunc for UnsafeProcMountCheck { if let Some(pod_spec) = extract::pod_spec::extract_pod_spec(&object.k8s_object) { for container in extract::container::all_containers(pod_spec) { - if let Some(sc) = &container.security_context { - if let Some(proc_mount) = &sc.proc_mount { - if proc_mount == "Unmasked" { - diagnostics.push(Diagnostic { + if let Some(sc) = &container.security_context + && let Some(proc_mount) = &sc.proc_mount + && proc_mount == "Unmasked" + { + diagnostics.push(Diagnostic { message: format!( "Container '{}' has unsafe /proc mount (procMount: Unmasked)", container.name @@ -59,8 +60,6 @@ impl CheckFunc for UnsafeProcMountCheck { .to_string(), ), }); - } - } } } } diff --git a/src/analyzer/kubelint/templates/validation.rs b/src/analyzer/kubelint/templates/validation.rs index 157d2136..0ad14a54 100644 --- a/src/analyzer/kubelint/templates/validation.rs +++ b/src/analyzer/kubelint/templates/validation.rs @@ -99,16 +99,17 @@ impl CheckFunc for RestartPolicyCheck { fn check(&self, object: &Object) -> Vec { let mut diagnostics = Vec::new(); - if let Some(pod_spec) = extract::pod_spec::extract_pod_spec(&object.k8s_object) { - if let Some(policy) = &pod_spec.restart_policy { - // For Deployments, StatefulSets, DaemonSets - must be Always - match &object.k8s_object { - K8sObject::Deployment(_) - | K8sObject::StatefulSet(_) - | K8sObject::DaemonSet(_) - | K8sObject::ReplicaSet(_) => { - if policy != "Always" { - diagnostics.push(Diagnostic { + if let Some(pod_spec) = extract::pod_spec::extract_pod_spec(&object.k8s_object) + && let Some(policy) = &pod_spec.restart_policy + { + // For Deployments, StatefulSets, DaemonSets - must be Always + match &object.k8s_object { + K8sObject::Deployment(_) + | K8sObject::StatefulSet(_) + | K8sObject::DaemonSet(_) + | K8sObject::ReplicaSet(_) => { + if policy != "Always" { + diagnostics.push(Diagnostic { message: format!( "Restart policy is '{}' but should be 'Always' for this workload type", policy @@ -119,10 +120,9 @@ impl CheckFunc for RestartPolicyCheck { .to_string(), ), }); - } } - _ => {} } + _ => {} } } @@ -566,16 +566,16 @@ impl CheckFunc for JobTtlSecondsAfterFinishedCheck { fn check(&self, object: &Object) -> Vec { let mut diagnostics = Vec::new(); - if let K8sObject::Job(job) = &object.k8s_object { - if job.ttl_seconds_after_finished.is_none() { - diagnostics.push(Diagnostic { - message: "Job does not have ttlSecondsAfterFinished set".to_string(), - remediation: Some( - "Set ttlSecondsAfterFinished to automatically clean up finished Jobs." - .to_string(), - ), - }); - } + if let K8sObject::Job(job) = &object.k8s_object + && job.ttl_seconds_after_finished.is_none() + { + diagnostics.push(Diagnostic { + message: "Job does not have ttlSecondsAfterFinished set".to_string(), + remediation: Some( + "Set ttlSecondsAfterFinished to automatically clean up finished Jobs." + .to_string(), + ), + }); } diagnostics @@ -620,15 +620,15 @@ impl CheckFunc for PriorityClassNameCheck { fn check(&self, object: &Object) -> Vec { let mut diagnostics = Vec::new(); - if let Some(pod_spec) = extract::pod_spec::extract_pod_spec(&object.k8s_object) { - if pod_spec.priority_class_name.is_none() { - diagnostics.push(Diagnostic { - message: "Pod does not have priorityClassName set".to_string(), - remediation: Some( - "Set priorityClassName to control pod scheduling priority.".to_string(), - ), - }); - } + if let Some(pod_spec) = extract::pod_spec::extract_pod_spec(&object.k8s_object) + && pod_spec.priority_class_name.is_none() + { + diagnostics.push(Diagnostic { + message: "Pod does not have priorityClassName set".to_string(), + remediation: Some( + "Set priorityClassName to control pod scheduling priority.".to_string(), + ), + }); } diagnostics @@ -690,18 +690,14 @@ impl CheckFunc for ServiceTypeCheck { fn check(&self, object: &Object) -> Vec { let mut diagnostics = Vec::new(); - if let K8sObject::Service(svc) = &object.k8s_object { - if let Some(svc_type) = &svc.type_ { - if self.disallowed.contains(svc_type) { - diagnostics.push(Diagnostic { - message: format!("Service uses disallowed type '{}'", svc_type), - remediation: Some(format!( - "Consider using ClusterIP instead of {}.", - svc_type - )), - }); - } - } + if let K8sObject::Service(svc) = &object.k8s_object + && let Some(svc_type) = &svc.type_ + && self.disallowed.contains(svc_type) + { + diagnostics.push(Diagnostic { + message: format!("Service uses disallowed type '{}'", svc_type), + remediation: Some(format!("Consider using ClusterIP instead of {}.", svc_type)), + }); } diagnostics @@ -755,21 +751,20 @@ impl CheckFunc for HpaMinReplicasCheck { fn check(&self, object: &Object) -> Vec { let mut diagnostics = Vec::new(); - if let K8sObject::HorizontalPodAutoscaler(hpa) = &object.k8s_object { - if let Some(min) = hpa.min_replicas { - if min < self.min_replicas { - diagnostics.push(Diagnostic { - message: format!( - "HPA minReplicas is {} but should be at least {}", - min, self.min_replicas - ), - remediation: Some(format!( - "Set minReplicas to at least {} for better availability.", - self.min_replicas - )), - }); - } - } + if let K8sObject::HorizontalPodAutoscaler(hpa) = &object.k8s_object + && let Some(min) = hpa.min_replicas + && min < self.min_replicas + { + diagnostics.push(Diagnostic { + message: format!( + "HPA minReplicas is {} but should be at least {}", + min, self.min_replicas + ), + remediation: Some(format!( + "Set minReplicas to at least {} for better availability.", + self.min_replicas + )), + }); } diagnostics diff --git a/src/auth/credentials.rs b/src/auth/credentials.rs index 646add04..c089c46f 100644 --- a/src/auth/credentials.rs +++ b/src/auth/credentials.rs @@ -40,10 +40,7 @@ pub fn get_access_token() -> Option { // Check expiry if let Some(expires_at) = config.syncable_auth.expires_at { - let now = SystemTime::now() - .duration_since(UNIX_EPOCH) - .ok()? - .as_secs(); + let now = SystemTime::now().duration_since(UNIX_EPOCH).ok()?.as_secs(); if now > expires_at { return None; // Token expired } diff --git a/src/auth/device_flow.rs b/src/auth/device_flow.rs index 0d69aaf6..5bed012f 100644 --- a/src/auth/device_flow.rs +++ b/src/auth/device_flow.rs @@ -3,7 +3,7 @@ //! Implements the OAuth 2.0 device flow to authenticate CLI users via the Syncable web interface. use super::credentials; -use anyhow::{anyhow, Result}; +use anyhow::{Result, anyhow}; use reqwest::Client; use serde::Deserialize; use std::time::{Duration, Instant}; @@ -100,7 +100,7 @@ pub async fn login(no_browser: bool) -> Result<()> { .verification_uri_complete .as_ref() .unwrap_or(&device_code.verification_uri); - + if let Err(e) = open::that(url) { println!("⚠️ Could not open browser automatically: {}", e); println!(" Please open the URL above manually."); diff --git a/src/lib.rs b/src/lib.rs index a9d0a642..38b15d6d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -118,6 +118,18 @@ pub async fn run_command(command: Commands) -> Result<()> { use cli::ChatProvider; use config::load_agent_config; + // Check if user is authenticated with Syncable + if !auth::credentials::is_authenticated() { + println!("\n\x1b[1;33m📢 Sign in to use Syncable Agent\x1b[0m"); + println!(" It's free and costs you nothing!\n"); + println!(" Run: \x1b[1;36msync-ctl auth login\x1b[0m\n"); + return Err(error::IaCGeneratorError::Config( + error::ConfigError::MissingConfig( + "Syncable authentication required".to_string(), + ), + )); + } + let project_path = path.canonicalize().unwrap_or(path); // Handle --resume flag @@ -215,19 +227,23 @@ pub async fn run_command(command: Commands) -> Result<()> { } } Commands::Auth { command } => { - use cli::AuthCommand; use auth::credentials; use auth::device_flow; - + use cli::AuthCommand; + match command { AuthCommand::Login { no_browser } => { device_flow::login(no_browser).await.map_err(|e| { - error::IaCGeneratorError::Config(error::ConfigError::ParsingFailed(e.to_string())) + error::IaCGeneratorError::Config(error::ConfigError::ParsingFailed( + e.to_string(), + )) }) } AuthCommand::Logout => { credentials::clear_credentials().map_err(|e| { - error::IaCGeneratorError::Config(error::ConfigError::ParsingFailed(e.to_string())) + error::IaCGeneratorError::Config(error::ConfigError::ParsingFailed( + e.to_string(), + )) })?; println!("✅ Logged out successfully. Credentials cleared."); Ok(()) @@ -263,22 +279,20 @@ pub async fn run_command(command: Commands) -> Result<()> { } Ok(()) } - AuthCommand::Token { raw } => { - match credentials::get_access_token() { - Some(token) => { - if raw { - print!("{}", token); - } else { - println!("Access Token: {}", token); - } - Ok(()) - } - None => { - eprintln!("Not authenticated. Run: sync-ctl auth login"); - std::process::exit(1); + AuthCommand::Token { raw } => match credentials::get_access_token() { + Some(token) => { + if raw { + print!("{}", token); + } else { + println!("Access Token: {}", token); } + Ok(()) } - } + None => { + eprintln!("Not authenticated. Run: sync-ctl auth login"); + std::process::exit(1); + } + }, } } }