Skip to content

Kots extensions#1

Open
perfectayush wants to merge 12 commits into
mainfrom
ayush/kots-extensions
Open

Kots extensions#1
perfectayush wants to merge 12 commits into
mainfrom
ayush/kots-extensions

Conversation

@perfectayush

@perfectayush perfectayush commented Apr 1, 2026

Copy link
Copy Markdown

Summary by CodeRabbit

  • New Features
    • Added three new KOTS query tables: installed applications, version history, and configuration, with namespace auto-discovery of running KOTS admin consoles.
  • Documentation
    • Added table docs, RBAC guidance, and SQL examples; documented inline kubeconfig support for config_path.
  • Bug Fixes
    • Improved kubeconfig handling for inline kubeconfig inputs and caching behavior.
  • Chores
    • Bumped Go toolchain and refreshed dependency versions.
  • Tests
    • Added unit tests covering inline kubeconfig detection/parsing.
  • Changelog
    • Added a v1.6.0 entry for the KOTS enhancements and other related improvements (including kubernetes_cronjob.time_zone).

perfectayush and others added 2 commits April 1, 2026 11:01
Add two new tables that query the KOTS admin console API to expose
application version history and configuration. The tables auto-discover
all namespaces where kotsadm is installed and query each one, with an
optional namespace filter for targeted queries. Communication with
kotsadm uses port-forwarding via SPDY and auth via the kotsadm-authstring
secret, matching the kots CLI behavior.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…bles

Add a new kubernetes_kots_app table that lists KOTS applications with
their runtime state by querying both the /api/v1/apps and per-app
/api/v1/app/{slug}/status endpoints. The table auto-discovers all
namespaces where kotsadm is installed.

Fix context_name returning null in kubernetes_kots_version and
kubernetes_kots_config by populating ContextName directly in the row
structs via getKubectlContext, replacing the broken hydrate approach.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Apr 1, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 46757242-f5a5-4ae8-906d-6c4a3d38bdaa

📥 Commits

Reviewing files that changed from the base of the PR and between 1d86012 and c30d51a.

📒 Files selected for processing (11)
  • CHANGELOG.md
  • docs/index.md
  • docs/tables/kubernetes_cronjob.md
  • k8s-test/tests/kubernetes_cronjob/cronjob.yaml
  • k8s-test/tests/kubernetes_cronjob/test-get-expected.json
  • k8s-test/tests/kubernetes_cronjob/test-get-query.sql
  • k8s-test/tests/kubernetes_cronjob/test-list-expected.json
  • k8s-test/tests/kubernetes_cronjob/test-list-query.sql
  • kubernetes/table_kubernetes_cronjob.go
  • kubernetes/utils.go
  • kubernetes/utils_test.go
✅ Files skipped from review due to trivial changes (5)
  • k8s-test/tests/kubernetes_cronjob/test-get-query.sql
  • k8s-test/tests/kubernetes_cronjob/test-list-query.sql
  • k8s-test/tests/kubernetes_cronjob/test-get-expected.json
  • CHANGELOG.md
  • docs/index.md

Walkthrough

The pull request updates Go module versions, adds inline kubeconfig parsing support with tests and docs, and adds a time_zone field to the kubernetes_cronjob table with matching documentation and fixtures. It also adds KOTS-related documentation and a changelog entry covering the new KOTS tables and related configuration behavior.


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title is vague and does not clearly describe the main KOTS tables, APIs, and kubeconfig changes in this PR. Rename it to something specific like "Add KOTS app, config, and version tables".
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 SQLFluff (4.2.2)
k8s-test/tests/kubernetes_cronjob/test-get-query.sql
k8s-test/tests/kubernetes_cronjob/test-list-query.sql
🔧 markdownlint-cli2 (0.22.1)
CHANGELOG.md

markdownlint-cli2 v0.22.1 (markdownlint v0.40.0)
Error: Unable to use configuration file '/coderabbit-0.markdownlint-cli2.jsonc'; ENOENT: no such file or directory, open '/coderabbit-0.markdownlint-cli2.jsonc'
at throwForConfigurationFile (file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2.mjs:48:9)
at readOptionsOrConfig (file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2.mjs:169:5)
at async main (file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2.mjs:927:21)
at async file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2-bin.mjs:14:22 {
[cause]: Error: ENOENT: no such file or directory, open '/coderabbit-0.markdownlint-cli2.jsonc'
at async open (node:internal/fs/promises:640:25)
at async Object.readFile (node:internal/fs/promises:1287:14)
at async readOptionsOrConfig (file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2.mjs:141:17)
at async main (file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2.mjs:927:21)
at async file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2-bin.mjs:14:22 {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/coderabbit-0.markdownlint-cli2.jsonc'
}
}

docs/index.md

markdownlint-cli2 v0.22.1 (markdownlint v0.40.0)
Error: Unable to use configuration file '/coderabbit-0.markdownlint-cli2.jsonc'; ENOENT: no such file or directory, open '/coderabbit-0.markdownlint-cli2.jsonc'
at throwForConfigurationFile (file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2.mjs:48:9)
at readOptionsOrConfig (file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2.mjs:169:5)
at async main (file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2.mjs:927:21)
at async file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2-bin.mjs:14:22 {
[cause]: Error: ENOENT: no such file or directory, open '/coderabbit-0.markdownlint-cli2.jsonc'
at async open (node:internal/fs/promises:640:25)
at async Object.readFile (node:internal/fs/promises:1287:14)
at async readOptionsOrConfig (file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2.mjs:141:17)
at async main (file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2.mjs:927:21)
at async file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2-bin.mjs:14:22 {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/coderabbit-0.markdownlint-cli2.jsonc'
}
}

docs/tables/kubernetes_cronjob.md

markdownlint-cli2 v0.22.1 (markdownlint v0.40.0)
Error: Unable to use configuration file '/coderabbit-0.markdownlint-cli2.jsonc'; ENOENT: no such file or directory, open '/coderabbit-0.markdownlint-cli2.jsonc'
at throwForConfigurationFile (file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2.mjs:48:9)
at readOptionsOrConfig (file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2.mjs:169:5)
at async main (file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2.mjs:927:21)
at async file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2-bin.mjs:14:22 {
[cause]: Error: ENOENT: no such file or directory, open '/coderabbit-0.markdownlint-cli2.jsonc'
at async open (node:internal/fs/promises:640:25)
at async Object.readFile (node:internal/fs/promises:1287:14)
at async readOptionsOrConfig (file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2.mjs:141:17)
at async main (file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2.mjs:927:21)
at async file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2-bin.mjs:14:22 {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/coderabbit-0.markdownlint-cli2.jsonc'
}
}


Comment @coderabbitai help to get the list of available commands.

perfectayush and others added 2 commits April 1, 2026 12:24
Add table documentation for kubernetes_kots_app, kubernetes_kots_version,
and kubernetes_kots_config with usage guides and SQL examples. Update
docs/index.md with a KOTS Applications section listing the new tables
and prerequisites. Add CHANGELOG entry for v1.6.0.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add detailed RBAC permission requirements for the KOTS tables: pods
list/get for discovery, pods/portforward create for the tunnel, and
secrets get for the kotsadm-authstring. Include an example ClusterRole
in the main docs and per-table permission notes with a cross-reference.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@kubernetes/kots_utils.go`:
- Around line 185-191: The loop over pods.Items currently returns the first
PodRunning pod which is too weak; instead check each pod's readiness condition
and only return a pod whose Status.Phase == corev1.PodRunning and which has a
Status.Condition of Type corev1.PodReady with Status == corev1.ConditionTrue
(inspect pod.Status.Conditions). Update the logic around the pods.Items
iteration to prefer a Ready kotsadm pod (return pod.Name when both running and
PodReady==True) and update the final error to indicate no ready kotsadm pod
found in the namespace.
- Around line 265-312: getKotsSession currently returns a cached
kotsPortForwardSession indefinitely; update it to validate cached sessions
before reuse by: when cachedData is found, assert to *kotsPortForwardSession and
check liveness (e.g. verify the pod via findKotsadmPod(ctx, clientset,
namespace) matches or perform a quick TCP dial to localhost:session.LocalPort or
check whether session.StopChan has been closed); if the session is stale, ensure
you stop/close the port-forward (close the StopChan or call the stop function
started by startKotsPortForward), remove the cache entry from
d.ConnectionManager.Cache, and then proceed to recreate a fresh session via
startKotsPortForward/getKotsAuthSlug as the existing code does; keep all changes
inside getKotsSession and reuse the existing helpers (startKotsPortForward,
findKotsadmPod, getKotsAuthSlug) and cache methods
(d.ConnectionManager.Cache.Get/Set).
- Around line 327-353: kotsAPIGet currently uses http.DefaultClient without
context so requests can hang; change its signature to accept ctx context.Context
and create the request with http.NewRequestWithContext(ctx, "GET", url, nil),
then use a bounded http.Client (e.g., http.Client{Timeout: time.Second * X})
instead of http.DefaultClient to execute the request; also propagate ctx into
getKotsVersions, getKotsConfig, getKotsApps, and getKotsAppStatus signatures and
calls so they pass the context into kotsAPIGet. Ensure existing behavior
(headers, status checks, body reading, and error wrapping) remains the same but
uses the context-aware request and bounded client to allow
cancellation/timeouts.

In `@kubernetes/table_kubernetes_kots_app.go`:
- Around line 90-97: The state variable is left blank when getKotsAppStatus
flakes; instead initialize state from the list response (use app.AppState) and
only override it when status.AppStatus.State is present. In the block around
getKotsAppStatus, set state := app.AppState (or assign state = app.AppState
before the status call), then keep the existing error logging for
getKotsAppStatus but do not clear or overwrite state on error—only replace state
when status != nil and status.AppStatus != nil and status.AppStatus.State is
non-empty.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4081b77e-5e24-43df-a682-9847d13d0dc5

📥 Commits

Reviewing files that changed from the base of the PR and between 6a276e6 and 4d53535.

📒 Files selected for processing (5)
  • kubernetes/kots_utils.go
  • kubernetes/plugin.go
  • kubernetes/table_kubernetes_kots_app.go
  • kubernetes/table_kubernetes_kots_config.go
  • kubernetes/table_kubernetes_kots_version.go

Comment thread kubernetes/kots_utils.go
Comment on lines +185 to +191
for _, pod := range pods.Items {
if pod.Status.Phase == corev1.PodRunning {
return pod.Name, nil
}
}

return "", fmt.Errorf("no running kotsadm pod found in namespace %s", namespace)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Prefer a Ready kotsadm pod.

PodRunning is too weak here. A pod can be running while the admin API is still failing readiness, so this can pin the port-forward to a bad replica and skip a namespace even when a healthy one exists.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@kubernetes/kots_utils.go` around lines 185 - 191, The loop over pods.Items
currently returns the first PodRunning pod which is too weak; instead check each
pod's readiness condition and only return a pod whose Status.Phase ==
corev1.PodRunning and which has a Status.Condition of Type corev1.PodReady with
Status == corev1.ConditionTrue (inspect pod.Status.Conditions). Update the logic
around the pods.Items iteration to prefer a Ready kotsadm pod (return pod.Name
when both running and PodReady==True) and update the final error to indicate no
ready kotsadm pod found in the namespace.

Comment thread kubernetes/kots_utils.go
Comment on lines +265 to +312
func getKotsSession(ctx context.Context, d *plugin.QueryData, namespace string) (*kotsPortForwardSession, error) {
cacheKey := fmt.Sprintf("kotsSession-%s", namespace)

if cachedData, ok := d.ConnectionManager.Cache.Get(cacheKey); ok {
return cachedData.(*kotsPortForwardSession), nil
}

clientset, err := GetNewClientset(ctx, d)
if err != nil {
return nil, fmt.Errorf("failed to get clientset: %w", err)
}
if clientset == nil {
return nil, fmt.Errorf("kubernetes client not available (manifest-only mode)")
}

// Find kotsadm pod
podName, err := findKotsadmPod(ctx, clientset, namespace)
if err != nil {
return nil, err
}

// Get auth slug
authSlug, err := getKotsAuthSlug(ctx, clientset, namespace)
if err != nil {
return nil, err
}

// Get rest config for port forwarding
restConfig, err := getRestConfig(ctx, d)
if err != nil {
return nil, fmt.Errorf("failed to get rest config: %w", err)
}

// Start port forward
localPort, stopChan, err := startKotsPortForward(ctx, restConfig, namespace, podName)
if err != nil {
return nil, err
}

session := &kotsPortForwardSession{
LocalPort: localPort,
StopChan: stopChan,
AuthSlug: authSlug,
}

d.ConnectionManager.Cache.Set(cacheKey, session)

return session, nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't treat cached port-forwards as immortal.

This cache never revalidates or tears down a session. If the forwarded pod restarts or the SPDY stream drops, later queries keep reusing a dead LocalPort and lose that namespace until the plugin process is restarted. StopChan also never gets closed for stale entries, so the old forwarder sticks around.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@kubernetes/kots_utils.go` around lines 265 - 312, getKotsSession currently
returns a cached kotsPortForwardSession indefinitely; update it to validate
cached sessions before reuse by: when cachedData is found, assert to
*kotsPortForwardSession and check liveness (e.g. verify the pod via
findKotsadmPod(ctx, clientset, namespace) matches or perform a quick TCP dial to
localhost:session.LocalPort or check whether session.StopChan has been closed);
if the session is stale, ensure you stop/close the port-forward (close the
StopChan or call the stop function started by startKotsPortForward), remove the
cache entry from d.ConnectionManager.Cache, and then proceed to recreate a fresh
session via startKotsPortForward/getKotsAuthSlug as the existing code does; keep
all changes inside getKotsSession and reuse the existing helpers
(startKotsPortForward, findKotsadmPod, getKotsAuthSlug) and cache methods
(d.ConnectionManager.Cache.Get/Set).

Comment thread kubernetes/kots_utils.go
Comment on lines +327 to +353
// kotsAPIGet performs a GET request to the kotsadm API
func kotsAPIGet(session *kotsPortForwardSession, path string) ([]byte, error) {
url := fmt.Sprintf("http://localhost:%d%s", session.LocalPort, path)

req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Authorization", session.AuthSlug)

resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to execute request: %w", err)
}
defer resp.Body.Close()

if resp.StatusCode != 200 {
return nil, fmt.Errorf("unexpected status code %d from kotsadm API", resp.StatusCode)
}

body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response body: %w", err)
}

return body, nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, find the file and check if it exists
find . -name "kots_utils.go" -type f

Repository: nonfx/steampipe-plugin-kubernetes

Length of output: 103


🏁 Script executed:

# Check the specific lines mentioned in the review
sed -n '327,353p' kubernetes/kots_utils.go

Repository: nonfx/steampipe-plugin-kubernetes

Length of output: 931


🏁 Script executed:

# Get more context around the kotsAPIGet function
rg "func kotsAPIGet" -A 30

Repository: nonfx/steampipe-plugin-kubernetes

Length of output: 1959


🏁 Script executed:

# Search for all calls to kotsAPIGet to understand usage
rg "kotsAPIGet\(" -B 2 -A 2

Repository: nonfx/steampipe-plugin-kubernetes

Length of output: 1986


🏁 Script executed:

# Check the mentioned functions to see if they call kotsAPIGet
rg "func (getKotsVersions|getKotsConfig|getKotsApps|getKotsAppStatus)" -A 15

Repository: nonfx/steampipe-plugin-kubernetes

Length of output: 3623


🏁 Script executed:

# Verify if context.Context is already used elsewhere in the file
rg "context\.Context" kubernetes/kots_utils.go -c

Repository: nonfx/steampipe-plugin-kubernetes

Length of output: 78


🏁 Script executed:

# Find who calls these 4 functions
rg "(getKotsVersions|getKotsConfig|getKotsApps|getKotsAppStatus)\(" kubernetes/kots_utils.go -B 3 -A 2

Repository: nonfx/steampipe-plugin-kubernetes

Length of output: 1217


🏁 Script executed:

# Check if these functions are exported and used elsewhere
rg "(getKotsVersions|getKotsConfig|getKotsApps|getKotsAppStatus)\(" --type go -B 2 -A 2

Repository: nonfx/steampipe-plugin-kubernetes

Length of output: 3669


🏁 Script executed:

# Look at the broader context of http.DefaultClient usage in the file
rg "http\." kubernetes/kots_utils.go | head -20

Repository: nonfx/steampipe-plugin-kubernetes

Length of output: 267


🏁 Script executed:

# Check the table functions that call the helper functions to see if they have context
rg "func listKotsVersions|func listKotsConfig|func listKotsApps" -A 10 kubernetes/

Repository: nonfx/steampipe-plugin-kubernetes

Length of output: 2496


🏁 Script executed:

# Check if steampipe plugin SDK provides context in the table list functions
rg "context\.Context" kubernetes/table_kubernetes_kots*.go -B 3 -A 3

Repository: nonfx/steampipe-plugin-kubernetes

Length of output: 3471


Thread context.Context through KOTS HTTP calls with bounded timeout.

http.DefaultClient has no timeout. When a port-forward wedges, this request can hang the Steampipe list path indefinitely, and query cancellation gets ignored. Add context.Context to kotsAPIGet and use http.NewRequestWithContext with a bounded client.

Also add ctx to getKotsVersions, getKotsConfig, getKotsApps, and getKotsAppStatus so they can pass it through.

Fix sketch
-func kotsAPIGet(session *kotsPortForwardSession, path string) ([]byte, error) {
+func kotsAPIGet(ctx context.Context, session *kotsPortForwardSession, path string) ([]byte, error) {
 	url := fmt.Sprintf("http://localhost:%d%s", session.LocalPort, path)

-	req, err := http.NewRequest("GET", url, nil)
+	req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
 	if err != nil {
 		return nil, fmt.Errorf("failed to create request: %w", err)
 	}
 	req.Header.Add("Content-Type", "application/json")
 	req.Header.Add("Authorization", session.AuthSlug)

-	resp, err := http.DefaultClient.Do(req)
+	client := &http.Client{Timeout: 30 * time.Second}
+	resp, err := client.Do(req)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// kotsAPIGet performs a GET request to the kotsadm API
func kotsAPIGet(session *kotsPortForwardSession, path string) ([]byte, error) {
url := fmt.Sprintf("http://localhost:%d%s", session.LocalPort, path)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Authorization", session.AuthSlug)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to execute request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("unexpected status code %d from kotsadm API", resp.StatusCode)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response body: %w", err)
}
return body, nil
// kotsAPIGet performs a GET request to the kotsadm API
func kotsAPIGet(ctx context.Context, session *kotsPortForwardSession, path string) ([]byte, error) {
url := fmt.Sprintf("http://localhost:%d%s", session.LocalPort, path)
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Authorization", session.AuthSlug)
client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to execute request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("unexpected status code %d from kotsadm API", resp.StatusCode)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response body: %w", err)
}
return body, nil
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@kubernetes/kots_utils.go` around lines 327 - 353, kotsAPIGet currently uses
http.DefaultClient without context so requests can hang; change its signature to
accept ctx context.Context and create the request with
http.NewRequestWithContext(ctx, "GET", url, nil), then use a bounded http.Client
(e.g., http.Client{Timeout: time.Second * X}) instead of http.DefaultClient to
execute the request; also propagate ctx into getKotsVersions, getKotsConfig,
getKotsApps, and getKotsAppStatus signatures and calls so they pass the context
into kotsAPIGet. Ensure existing behavior (headers, status checks, body reading,
and error wrapping) remains the same but uses the context-aware request and
bounded client to allow cancellation/timeouts.

Comment on lines +90 to +97
// Get the runtime state via the status API
state := ""
status, err := getKotsAppStatus(session, app.Slug)
if err != nil {
logger.Warn("listKotsApps", "namespace", namespace, "app", app.Slug, "status_error", err)
} else if status.AppStatus != nil {
state = status.AppStatus.State
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Keep state populated when /status flakes.

KotsApp already has AppState, but this path starts from "" and only fills from the extra status call. A transient /status error now returns a blank state even though the list response had a usable value.

Small fallback
-			state := ""
+			state := app.AppState
 			status, err := getKotsAppStatus(session, app.Slug)
 			if err != nil {
 				logger.Warn("listKotsApps", "namespace", namespace, "app", app.Slug, "status_error", err)
-			} else if status.AppStatus != nil {
+			} else if status.AppStatus != nil && status.AppStatus.State != "" {
 				state = status.AppStatus.State
 			}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Get the runtime state via the status API
state := ""
status, err := getKotsAppStatus(session, app.Slug)
if err != nil {
logger.Warn("listKotsApps", "namespace", namespace, "app", app.Slug, "status_error", err)
} else if status.AppStatus != nil {
state = status.AppStatus.State
}
// Get the runtime state via the status API
state := app.AppState
status, err := getKotsAppStatus(session, app.Slug)
if err != nil {
logger.Warn("listKotsApps", "namespace", namespace, "app", app.Slug, "status_error", err)
} else if status.AppStatus != nil && status.AppStatus.State != "" {
state = status.AppStatus.State
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@kubernetes/table_kubernetes_kots_app.go` around lines 90 - 97, The state
variable is left blank when getKotsAppStatus flakes; instead initialize state
from the list response (use app.AppState) and only override it when
status.AppStatus.State is present. In the block around getKotsAppStatus, set
state := app.AppState (or assign state = app.AppState before the status call),
then keep the existing error logging for getKotsAppStatus but do not clear or
overwrite state on error—only replace state when status != nil and
status.AppStatus != nil and status.AppStatus.State is non-empty.

Kubernetes RBAC does not support label selectors on rules, so
pods/portforward cannot be restricted to only kotsadm pods by label.
Document the two practical scoping approaches: namespace-scoped Role
(recommended for tightest access) and ClusterRole (for auto-discovery).
Include full Role + RoleBinding YAML example.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
docs/index.md (2)

386-386: Tiny wording polish.

“Impractical” reads tighter than “not practical”.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/index.md` at line 386, Edit the sentence that explains resourceNames and
pod names so it uses "impractical" instead of "not practical"; locate the line
referencing Kubernetes RBAC, the `pods/portforward` rule and the `resourceNames`
field (the sentence that mentions dynamically generated names like
`kotsadm-7d4b8c9f5-abc12`) and replace "not practical" with "impractical" to
tighten the wording.

427-447: Consider adding a ClusterRoleBinding example too.

Option 2 currently shows permissions but not binding. Adding the binding snippet will make this section copy/paste complete.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/index.md` around lines 427 - 447, Add a ClusterRoleBinding example to
pair with the ClusterRole shown (ClusterRole metadata.name:
steampipe-kots-reader) so readers can apply permissions cluster-wide; update
docs/index.md near the "Option 2 — ClusterRole" block to include a
ClusterRoleBinding that binds the ClusterRole "steampipe-kots-reader" to a
subject (e.g., ServiceAccount, User, or Group) and show the required fields
(kind, apiGroup, name) for the subject so the snippet is copy/paste complete.
docs/tables/kubernetes_kots_app.md (1)

108-110: Prefer “air-gapped” for doc clarity.

Small wording tweak for readability/consistency in headings and description.

Suggested edit
-### List airgapped applications
-Find applications deployed in airgap mode.
+### List air-gapped applications
+Find applications deployed in air-gap mode.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/tables/kubernetes_kots_app.md` around lines 108 - 110, Update the
heading "List airgapped applications" and the description "Find applications
deployed in airgap mode." to use the preferred hyphenated form "air-gapped" for
clarity and consistency (i.e., change to "List air-gapped applications" and
"Find applications deployed in air-gapped mode.").
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/index.md`:
- Around line 418-420: The RoleBinding subject example is wrong/ambiguous: split
into two clear examples—one for User and one for ServiceAccount—so consumers can
copy/paste correctly; for the User example keep kind: User and apiGroup:
rbac.authorization.k8s.io with the name field as the username, and for the
ServiceAccount example use kind: ServiceAccount, remove apiGroup entirely, and
include the namespace field (namespace: <namespace>) with name set to the
service account name; update the example block around the existing
kind/name/apiGroup lines to show both variants separately and ensure the
ServiceAccount example omits apiGroup.

---

Nitpick comments:
In `@docs/index.md`:
- Line 386: Edit the sentence that explains resourceNames and pod names so it
uses "impractical" instead of "not practical"; locate the line referencing
Kubernetes RBAC, the `pods/portforward` rule and the `resourceNames` field (the
sentence that mentions dynamically generated names like
`kotsadm-7d4b8c9f5-abc12`) and replace "not practical" with "impractical" to
tighten the wording.
- Around line 427-447: Add a ClusterRoleBinding example to pair with the
ClusterRole shown (ClusterRole metadata.name: steampipe-kots-reader) so readers
can apply permissions cluster-wide; update docs/index.md near the "Option 2 —
ClusterRole" block to include a ClusterRoleBinding that binds the ClusterRole
"steampipe-kots-reader" to a subject (e.g., ServiceAccount, User, or Group) and
show the required fields (kind, apiGroup, name) for the subject so the snippet
is copy/paste complete.

In `@docs/tables/kubernetes_kots_app.md`:
- Around line 108-110: Update the heading "List airgapped applications" and the
description "Find applications deployed in airgap mode." to use the preferred
hyphenated form "air-gapped" for clarity and consistency (i.e., change to "List
air-gapped applications" and "Find applications deployed in air-gapped mode.").
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fbd043b0-03dc-4cec-887c-417fb8f458f3

📥 Commits

Reviewing files that changed from the base of the PR and between 4d53535 and 9762f5e.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • docs/index.md
  • docs/tables/kubernetes_kots_app.md
  • docs/tables/kubernetes_kots_config.md
  • docs/tables/kubernetes_kots_version.md
✅ Files skipped from review due to trivial changes (3)
  • docs/tables/kubernetes_kots_config.md
  • CHANGELOG.md
  • docs/tables/kubernetes_kots_version.md

Comment thread docs/index.md
Comment on lines +418 to +420
- kind: User # or ServiceAccount
name: steampipe
apiGroup: rbac.authorization.k8s.io

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

RoleBinding subject example is ambiguous/incorrect for ServiceAccount.

apiGroup: rbac.authorization.k8s.io applies to User, not ServiceAccount. The current “User # or ServiceAccount” hint can break copy/paste configs.

Suggested fix (split examples clearly)
-subjects:
-  - kind: User  # or ServiceAccount
-    name: steampipe
-    apiGroup: rbac.authorization.k8s.io
+subjects:
+  # Use one of the following:
+  - kind: User
+    name: steampipe
+    apiGroup: rbac.authorization.k8s.io
+  # - kind: ServiceAccount
+  #   name: steampipe
+  #   namespace: my-kots-namespace
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/index.md` around lines 418 - 420, The RoleBinding subject example is
wrong/ambiguous: split into two clear examples—one for User and one for
ServiceAccount—so consumers can copy/paste correctly; for the User example keep
kind: User and apiGroup: rbac.authorization.k8s.io with the name field as the
username, and for the ServiceAccount example use kind: ServiceAccount, remove
apiGroup entirely, and include the namespace field (namespace: <namespace>) with
name set to the service account name; update the example block around the
existing kind/name/apiGroup lines to show both variants separately and ensure
the ServiceAccount example omits apiGroup.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@go.mod`:
- Line 121: The go.mod currently pins the vulnerable module
github.com/moby/spdystream to v0.5.0; update the dependency declaration for
github.com/moby/spdystream to v0.5.1 or later (e.g., change the version token in
go.mod to v0.5.1+) and then run go mod tidy (or go get
github.com/moby/spdystream@v0.5.1) to update go.sum and vendor files so the
project uses the fixed release.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5e244c13-4c80-4b94-a60e-7eeb995d9032

📥 Commits

Reviewing files that changed from the base of the PR and between 9762f5e and 8898fb4.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (1)
  • go.mod

Comment thread go.mod Outdated
@perfectayush perfectayush force-pushed the ayush/kots-extensions branch from 8898fb4 to 1d86012 Compare May 19, 2026 05:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants