Files
helmfile/pkg/app/config.go
T
yxxhero 9b943adc9e feat: add helmfile doctor command for AI-assisted diff analysis (#2660)
* feat: add `helmfile doctor` command for AI-assisted diff analysis

`helmfile doctor` runs `helmfile diff` and asks an OpenAI-compatible LLM to
summarize the changes and flag risks (data loss, security exposure, breaking
changes, downtime, performance, best-practice issues).

Key design decisions:
- When no LLM is configured, doctor is equivalent to `helmfile diff` with
  one exception: --show-secrets is always forced off (secrets never reach
  stdout, even without an LLM).
- Secrets are ALWAYS redacted via two layers: (1) ShowSecrets() forced to
  false so helm-diff emits <REDACTED> placeholders; (2) a defense-in-depth
  text redactor strips residual secret-looking content (Secret YAML blocks,
  sensitive key/value lines, base64 blobs, JWT tokens) before LLM transmission.
- LLM configuration precedence: env (HELMFILE_LLM_*) < helmfile.yaml (llm:)
  < CLI flags (--llm-*).
- Supports any OpenAI-compatible backend (OpenAI, Azure, One-API, LiteLLM,
  Ollama, etc.) with automatic response_format fallback for backends that
  don't support JSON mode.
- Prompt injection defense: release names and environment values are
  JSON-encoded before insertion into the LLM prompt.
- Exit codes: 0 (success/low-risk), 2 (high-risk gate, bypass with --force),
  1 (other errors). Helm-diff's 'detected changes' exit-2 is swallowed.

New packages:
- pkg/agent/llm: OpenAI-compatible client with JSON response parsing, mock
  client for testing, prompt builder with injection defense.
- pkg/agent/doctor: secret redactor (state machine + regex), report renderer
  (markdown + JSON), config resolver (env < yaml < flag merge).

Testing: 70+ unit tests covering redaction patterns, prompt injection,
response_format fallback, JSON parsing, yaml roundtrip, concurrency safety,
panic recovery, and error propagation. go test -race passes.

Documentation: full doctor section in docs/cli.md, llm: block reference in
docs/configuration.md, updated skills/helmfile for AI agents.

Signed-off-by: yxxhero <aiopsclub@163.com>

* docs: fix doctor equivalence wording per PR review

Per review feedback (PR #2660): the docs claimed doctor is 'equivalent to
helmfile diff — same flags, same output, same exit codes' in the unconfigured
path, but this over-promises because:

  1. doctor --output is the report format (not helm-diff's output format)
  2. helm-diff's --output is exposed as --diff-output in doctor
  3. --show-secrets is silently ignored

Updated all three locations (cli.md, cmd/doctor.go Long + godoc, pkg/app/doctor.go
godoc) to say 'falls back to helmfile diff with --show-secrets forced off' and
explicitly note the --output / --diff-output flag difference.

Signed-off-by: yxxhero <aiopsclub@163.com>

---------

Signed-off-by: yxxhero <aiopsclub@163.com>
2026-06-22 16:52:35 +08:00

386 lines
6.6 KiB
Go

package app
import (
"go.uber.org/zap"
"github.com/helmfile/helmfile/pkg/agent/llm"
)
type ConfigProvider interface {
Args() string
HelmBinary() string
KustomizeBinary() string
EnableLiveOutput() bool
StripArgsValuesOnExitError() bool
DisableForceUpdate() bool
EnforcePluginVerification() bool
HelmOCIPlainHTTP() bool
SkipDeps() bool
SkipRefresh() bool
SequentialHelmfiles() bool
FileOrDir() string
KubeContext() string
Namespace() string
Chart() string
Selectors() []string
StateValuesSet() map[string]any
StateValuesFiles() []string
Kubeconfig() string
Env() string
loggingConfig
}
type DepsConfigProvider interface {
Args() string
SkipRepos() bool
IncludeTransitiveNeeds() bool
concurrencyConfig
}
type ReposConfigProvider interface {
Args() string
IncludeTransitiveNeeds() bool
}
type ApplyConfigProvider interface {
Args() string
PostRenderer() string
PostRendererArgs() []string
SkipSchemaValidation() bool
Cascade() string
HideNotes() bool
TakeOwnership() bool
ServerSide() string
SuppressOutputLineRegex() []string
Values() []string
Set() []string
SkipCRDs() bool
SkipDeps() bool
SkipRefresh() bool
Wait() bool
WaitRetries() int
WaitForJobs() bool
Timeout() int
IncludeTests() bool
Suppress() []string
SuppressSecrets() bool
ShowSecrets() bool
NoHooks() bool
SuppressDiff() bool
DetailedExitcode() bool
StripTrailingCR() bool
Color() bool
NoColor() bool
Context() int
DiffOutput() string
Validate() bool
SkipCleanup() bool
SkipDiffOnInstall() bool
DiffArgs() string
SyncArgs() string
SyncReleaseLabels() bool
DAGConfig
TrackMode() string
TrackTimeout() int
TrackLogs() bool
TrackFailedLogs() bool
HelmStuckGrace() int
TrackFailOnError() bool
Description() string
concurrencyConfig
interactive
loggingConfig
valuesControlMode
}
type SyncConfigProvider interface {
Args() string
PostRenderer() string
SkipSchemaValidation() bool
PostRendererArgs() []string
HideNotes() bool
TakeOwnership() bool
ServerSide() string
Cascade() string
Values() []string
Set() []string
SkipCRDs() bool
SkipDeps() bool
SkipRefresh() bool
Wait() bool
WaitRetries() int
WaitForJobs() bool
Timeout() int
SyncArgs() string
Validate() bool
SkipNeeds() bool
IncludeNeeds() bool
IncludeTransitiveNeeds() bool
SyncReleaseLabels() bool
TrackMode() string
TrackTimeout() int
TrackLogs() bool
TrackFailedLogs() bool
HelmStuckGrace() int
TrackFailOnError() bool
Color() bool
NoColor() bool
Description() string
DAGConfig
concurrencyConfig
interactive
loggingConfig
valuesControlMode
}
type DiffConfigProvider interface {
Args() string
PostRenderer() string
PostRendererArgs() []string
SkipSchemaValidation() bool
SuppressOutputLineRegex() []string
Values() []string
Set() []string
Validate() bool
SkipCRDs() bool
SkipDeps() bool
SkipRefresh() bool
IncludeTests() bool
Suppress() []string
SuppressSecrets() bool
ShowSecrets() bool
NoHooks() bool
SuppressDiff() bool
SkipDiffOnInstall() bool
DiffArgs() string
DAGConfig
DetailedExitcode() bool
StripTrailingCR() bool
Color() bool
NoColor() bool
Context() int
DiffOutput() string
TakeOwnership() bool
ServerSide() string
concurrencyConfig
valuesControlMode
}
// DoctorConfigProvider is the configuration surface required by App.Doctor.
// It embeds DiffConfigProvider because doctor is a strict superset of diff:
// the same helm-diff flags plus a handful of AI-specific knobs.
//
// The LLM-related accessors return the flag-sourced config only; env and
// helmfile.yaml sources are resolved by the doctor package itself
// (see pkg/agent/doctor/config.go).
type DoctorConfigProvider interface {
DiffConfigProvider
// FlagLLMConfig returns the LLM configuration sourced from --llm-* flags.
// Empty fields mean "flag not set" and do not override env/yaml.
FlagLLMConfig() llm.Config
// Force skips the high-risk exit-code-2 gate.
Force() bool
// DoctorOutput returns the report format ("text" or "json"). Named
// DoctorOutput to avoid colliding with DiffConfigProvider.DiffOutput
// which is the helm-diff plugin output format.
DoctorOutput() string
}
type DestroyConfigProvider interface {
Args() string
Cascade() string
SkipDeps() bool
SkipRefresh() bool
SkipCharts() bool
DeleteWait() bool
DeleteTimeout() int
NoColor() bool
interactive
loggingConfig
concurrencyConfig
}
type TestConfigProvider interface {
Args() string
SkipDeps() bool
SkipRefresh() bool
Timeout() int
Cleanup() bool
Logs() bool
concurrencyConfig
}
type LintConfigProvider interface {
Args() string
Values() []string
Set() []string
SkipDeps() bool
SkipRefresh() bool
SkipCleanup() bool
DAGConfig
concurrencyConfig
}
type UnittestConfigProvider interface {
Args() string
Values() []string
Set() []string
FailFast() bool
Color() bool
DebugPlugin() bool
SkipDeps() bool
SkipRefresh() bool
SkipCleanup() bool
DAGConfig
concurrencyConfig
}
type FetchConfigProvider interface {
SkipDeps() bool
SkipRefresh() bool
OutputDir() string
OutputDirTemplate() string
WriteOutput() bool
concurrencyConfig
}
type TemplateConfigProvider interface {
Args() string
PostRenderer() string
PostRendererArgs() []string
SkipSchemaValidation() bool
Values() []string
Set() []string
OutputDirTemplate() string
Validate() bool
SkipDeps() bool
SkipRefresh() bool
SkipCleanup() bool
SkipTests() bool
OutputDir() string
IncludeCRDs() bool
NoHooks() bool
KubeVersion() string
ShowOnly() []string
DAGConfig
concurrencyConfig
}
type DAGConfig interface {
SkipNeeds() bool
IncludeNeeds() bool
IncludeTransitiveNeeds() bool
EnforceNeedsAreInstalled() bool
}
type WriteValuesConfigProvider interface {
Values() []string
Set() []string
OutputFileTemplate() string
SkipDeps() bool
SkipRefresh() bool
SkipCleanup() bool
IncludeTransitiveNeeds() bool
concurrencyConfig
}
type StatusesConfigProvider interface {
Args() string
concurrencyConfig
}
type StateConfigProvider interface {
EmbedValues() bool
}
type DAGConfigProvider any
type concurrencyConfig interface {
Concurrency() int
}
type loggingConfig interface {
Logger() *zap.SugaredLogger
}
type interactive interface {
Interactive() bool
}
type ListConfigProvider interface {
Output() string
SkipCharts() bool
}
type CacheConfigProvider any
type InitConfigProvider interface {
Force() bool
}
type CreateConfigProvider interface {
Name() string
OutputDir() string
Force() bool
loggingConfig
}
type PrintEnvConfigProvider interface {
Output() string
}
// reset/reuse values helm cli flags handling for apply/sync/diff
type valuesControlMode interface {
ReuseValues() bool
ResetValues() bool
}