Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,8 @@ extraPromptPaths: [] # Additional prompt template paths

# Debug and trace settings
tracePath: "/tmp/kubectl-ai-trace.txt" # Path to trace file
logFilePath: "/tmp/kubectl-ai.log" # Path to log file (empty or '/dev/null' to disable)
stderrThreshold: "ERROR" # Minimum log level to output to stderr (INFO, WARNING, ERROR, FATAL)
```

</details>
Expand Down Expand Up @@ -295,14 +297,15 @@ A custom tool definition for `helm` could look like the following example:
Use `helm --help` or `helm <subcommand> --help` to see full syntax, available flags, and examples for each command.
```

## Docker Quick Start
## Docker Quick Start

This project provides a Docker image that gives you a standalone environment for running kubectl-ai, including against a GKE cluster.

### Running the container against GKE

#### Step 1: Build the Image

Clone the repository and build the image with the following command
Clone the repository and build the image with the following command

```bash
git clone https://github.com/GoogleCloudPlatform/kubectl-ai.git
Expand All @@ -311,15 +314,18 @@ docker build -t kubectl-ai:latest -f images/kubectl-ai/Dockerfile .
```

#### Step 2: Connect to Your GKE Cluster

Set up application default credentials and connect to your GKE cluster.

```bash
gcloud auth application-default login # If in a gcloud shell this is not necessary
gcloud container clusters get-credentials <cluster-name> --zone <zone>
```

#### Step 3: Run the kubectl-ai container
Below is a sample command that can be used to launch the container with a locally hosted web-ui. Be sure to replace the placeholder values with your specific Google Cloud project ID and location. Note you
do not need to mount the gcloud config directory if you're on a cloudshell machine.

Below is a sample command that can be used to launch the container with a locally hosted web-ui. Be sure to replace the placeholder values with your specific Google Cloud project ID and location. Note you
do not need to mount the gcloud config directory if you're on a cloudshell machine.

```bash
docker run --rm -it -p 8080:8080 -v ~/.kube:/root/.kube -v ~/.config/gcloud:/root/.config/gcloud -e GOOGLE_CLOUD_LOCATION=us-central1 -e GOOGLE_CLOUD_PROJECT=my-gcp-project kubectl-ai:latest --llm-provider vertexai --ui-listen-address 0.0.0.0:8080 --ui-type web
Expand Down
50 changes: 35 additions & 15 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,17 @@ var (
date = "unknown"
)

func BuildRootCommand(opt *Options) (*cobra.Command, error) {
func BuildRootCommand(opt *Options, klogFlags *flag.FlagSet) (*cobra.Command, error) {
rootCmd := &cobra.Command{
Use: "kubectl-ai",
Short: "A CLI tool to interact with Kubernetes using natural language",
Long: "kubectl-ai is a command-line tool that allows you to interact with your Kubernetes cluster using natural language queries. It leverages large language models to understand your intent and translate it into kubectl",
Args: cobra.MaximumNArgs(1), // Only one positional arg is allowed.
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
// Configure logging after flags are parsed
configureLogging(klogFlags, opt.LogFilePath, opt.StderrThreshold)
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
return RunRootCommand(cmd.Context(), *opt, args)
},
Expand Down Expand Up @@ -105,6 +110,8 @@ type Options struct {
PromptTemplateFilePath string `json:"promptTemplateFilePath,omitempty"`
ExtraPromptPaths []string `json:"extraPromptPaths,omitempty"`
TracePath string `json:"tracePath,omitempty"`
LogFilePath string `json:"logFilePath,omitempty"`
StderrThreshold string `json:"stderrThreshold,omitempty"`
RemoveWorkDir bool `json:"removeWorkDir,omitempty"`
ToolConfigPaths []string `json:"toolConfigPaths,omitempty"`

Expand Down Expand Up @@ -146,6 +153,8 @@ func (o *Options) InitDefaults() {
o.PromptTemplateFilePath = ""
o.ExtraPromptPaths = []string{}
o.TracePath = filepath.Join(os.TempDir(), "kubectl-ai-trace.txt")
o.LogFilePath = filepath.Join(os.TempDir(), "kubectl-ai.log")
o.StderrThreshold = "ERROR"
o.RemoveWorkDir = false
o.ToolConfigPaths = defaultToolConfigPaths
// Default to terminal UI
Expand Down Expand Up @@ -235,27 +244,22 @@ func main() {
}

func run(ctx context.Context) error {
// klog setup must happen before Cobra parses any flags

// add commandline flags for logging
klogFlags := flag.NewFlagSet("klog", flag.ExitOnError)
klog.InitFlags(klogFlags)

klogFlags.Set("logtostderr", "false")
klogFlags.Set("log_file", filepath.Join(os.TempDir(), "kubectl-ai.log"))

defer klog.Flush()

var opt Options

opt.InitDefaults()

// load YAML config values
// load YAML config values early to get log configuration
if err := opt.LoadConfigurationFile(); err != nil {
return fmt.Errorf("failed to load config file: %w", err)
}

rootCmd, err := BuildRootCommand(&opt)
// klog setup must happen before Cobra parses any flags
// add commandline flags for logging
klogFlags := flag.NewFlagSet("klog", flag.ExitOnError)
klog.InitFlags(klogFlags)

defer klog.Flush()

rootCmd, err := BuildRootCommand(&opt, klogFlags)
if err != nil {
return err
}
Expand All @@ -281,6 +285,8 @@ func (opt *Options) bindCLIFlags(f *pflag.FlagSet) error {
f.StringVar(&opt.PromptTemplateFilePath, "prompt-template-file-path", opt.PromptTemplateFilePath, "path to custom prompt template file")
f.StringArrayVar(&opt.ExtraPromptPaths, "extra-prompt-paths", opt.ExtraPromptPaths, "extra prompt template paths")
f.StringVar(&opt.TracePath, "trace-path", opt.TracePath, "path to the trace file")
f.StringVar(&opt.LogFilePath, "log-file-path", opt.LogFilePath, "path to the log file (empty or '/dev/null' to disable logging)")
f.StringVar(&opt.StderrThreshold, "stderr-threshold", opt.StderrThreshold, "minimum log level to output to stderr (INFO, WARNING, ERROR, FATAL)")
f.BoolVar(&opt.RemoveWorkDir, "remove-workdir", opt.RemoveWorkDir, "remove the temporary working directory after execution")

f.StringVar(&opt.ProviderID, "llm-provider", opt.ProviderID, "language model provider")
Expand Down Expand Up @@ -592,3 +598,17 @@ func startMCPServer(ctx context.Context, opt Options) error {
}
return mcpServer.Serve(ctx)
}

func configureLogging(klogFlags *flag.FlagSet, logFilePath string, stderrThreshold string) {
if logFilePath == "" || logFilePath == "/dev/null" {
// Disable file logging - only log to stderr if needed
klogFlags.Set("logtostderr", "true")
klogFlags.Set("stderrthreshold", "FATAL") // Only log fatal errors
Copy link

Copilot AI Jul 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting stderrthreshold to "FATAL" when logtostderr is "true" means that only fatal errors will be logged to stderr, effectively silencing most log output. This could hide important warnings and errors that users need to see.

Suggested change
klogFlags.Set("stderrthreshold", "FATAL") // Only log fatal errors
klogFlags.Set("stderrthreshold", "ERROR") // Log errors and above

Copilot uses AI. Check for mistakes.
} else {
klogFlags.Set("logtostderr", "false")
klogFlags.Set("log_file", logFilePath)
if stderrThreshold != "" {
klogFlags.Set("stderrthreshold", stderrThreshold)
}
}
}
Loading