package cmd import ( "bytes" "context" "crypto/rand" "encoding/hex" "fmt" "io" "github.com/spf13/cobra" ) var doctorCmd = &cobra.Command{ Use: "doctor", Short: "Smoke-test S3 credentials and bucket reachability", Long: `Validates that cacher can reach the configured S3 bucket using the resolved credentials. Performs a HEAD on the bucket and then writes, reads, and deletes a 1-byte test object. Reports the access key id's length and first two characters as a diagnostic — mirrors the existing ci_aws_setup shell helper's "key_id_len=… key_id_prefix=…" output — without leaking the secret.`, RunE: func(cmd *cobra.Command, _ []string) error { return runDoctor(cmd) }, } func init() { addS3Flags(doctorCmd) rootCmd.AddCommand(doctorCmd) } func runDoctor(cmd *cobra.Command) error { cli, cfg, err := client() if err != nil { return err } out := cmd.OutOrStdout() keyID, secret, _ := cfg.Credentials() // already validated by client() fmt.Fprintf(out, "endpoint=%s region=%s bucket=%s prefix=%s\n", cfg.Endpoint, cfg.Region, cfg.Bucket, cfg.Prefix) fmt.Fprintf(out, "key_id_len=%d key_id_prefix=%s secret_len=%d\n", len(keyID), safePrefix(keyID, 2), len(secret)) ctx := context.Background() if err := cli.PingBucket(ctx); err != nil { return err } fmt.Fprintln(out, "ping bucket: OK") // Write/read/delete a tiny canary so credential perms are exercised // for all three operations, not just Head. Key is random so concurrent // doctor runs don't fight. r := make([]byte, 8) _, _ = rand.Read(r) key := ".cacher-doctor-" + hex.EncodeToString(r) body := []byte("ok") if err := cli.Put(ctx, key, bytes.NewReader(body)); err != nil { return err } fmt.Fprintln(out, "put canary: OK") rc, err := cli.Get(ctx, key) if err != nil { return err } got, err := io.ReadAll(rc) rc.Close() if err != nil { return err } if !bytes.Equal(got, body) { return fmt.Errorf("canary read mismatch: got %q want %q", got, body) } fmt.Fprintln(out, "get canary: OK") if err := cli.Delete(ctx, key); err != nil { return err } fmt.Fprintln(out, "delete canary: OK") fmt.Fprintln(out, "doctor: all checks passed") return nil } func safePrefix(s string, n int) string { if len(s) < n { return s } return s[:n] }