Skip to content

Commit a70d858

Browse files
committed
kcc-cache: add -h/--help/help and usage message
kcc-cache is a pass-through wrapper that execs os.Args[1] on a cache miss. Running 'kcc-cache -h' tried to exec a binary literally named '-h', failing with a confusing PATH error. Handle -h/--help/help (and the no-args case) explicitly with a usage message before treating the first arg as a command. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V2pTHkL3AmyFqgyQhgrohw
1 parent 41c461f commit a70d858

1 file changed

Lines changed: 36 additions & 4 deletions

File tree

cmd/kcc-cache/main.go

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,22 @@ type Backend interface {
4949
}
5050

5151
func main() {
52+
// help
53+
//
54+
// kcc-cache wraps another command (os.Args[1:]) and execs it on a cache
55+
// miss. Without this guard, `kcc-cache -h` would try to exec a binary
56+
// literally named "-h" and fail with a confusing PATH error, so handle the
57+
// usual help flags explicitly before treating the first arg as a command.
58+
if len(os.Args) < 2 {
59+
usage(os.Stderr)
60+
os.Exit(1)
61+
}
62+
switch os.Args[1] {
63+
case "-h", "--help", "help":
64+
usage(os.Stdout)
65+
os.Exit(0)
66+
}
67+
5268
// configuration
5369
var (
5470
refreshMargin = time.Second * 30
@@ -119,10 +135,7 @@ func main() {
119135
fatal("cache read failed: %s", err)
120136
}
121137
if !ok || time.Until(cache.Status.ExpirationTimestamp) < refreshMargin {
122-
// refresh
123-
if len(os.Args) < 2 {
124-
fatal("not enough command at args")
125-
}
138+
// refresh (os.Args[1] is guaranteed present; checked at startup)
126139
cmd := exec.Command(os.Args[1], os.Args[2:]...)
127140
cmd.Stderr = os.Stderr
128141
bytes, err := cmd.Output()
@@ -289,6 +302,25 @@ func resolveCacheFilepath() string {
289302
return path.Join(cacheDir, "kube-credential-cache", "cache.json")
290303
}
291304

305+
// usage prints how to invoke kcc-cache. It is a transparent caching wrapper:
306+
// the arguments after the program name are the credential command it runs and
307+
// caches, so there are no flags of its own (configuration is via environment
308+
// variables, documented in the README).
309+
func usage(w *os.File) {
310+
name := path.Base(os.Args[0])
311+
fmt.Fprintf(w, "Usage: %s <command> [args...]\n", name)
312+
fmt.Fprintf(w, "\n")
313+
fmt.Fprintf(w, "Caching proxy for Kubernetes client-go credential plugins (ExecCredential).\n")
314+
fmt.Fprintf(w, "Runs <command> [args...], caches its ExecCredential output, and serves it\n")
315+
fmt.Fprintf(w, "from cache until the credential is near expiry.\n")
316+
fmt.Fprintf(w, "\n")
317+
fmt.Fprintf(w, "Example:\n")
318+
fmt.Fprintf(w, " %s aws --region <region> eks get-token --cluster-name <cluster>\n", name)
319+
fmt.Fprintf(w, "\n")
320+
fmt.Fprintf(w, "Configuration is via environment variables; see\n")
321+
fmt.Fprintf(w, "https://github.com/ryodocx/kube-credential-cache#kcc-cache\n")
322+
}
323+
292324
func fatal(format string, v ...any) {
293325
log(format, v...)
294326

0 commit comments

Comments
 (0)