~bigbes/ci-cacher

ref: v0.1.0 ci-cacher/cmd/doctor.go -rw-r--r-- 2.2 KiB
176b6cb9 — Eugene Blikh Bump VERSION to 0.1.0 for tag 2 days ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
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]
}