From 03d981779b85efb3380e7954308dc2be7a70fb29 Mon Sep 17 00:00:00 2001 From: Julian Sommer <115516918+juliansommer@users.noreply.github.com> Date: Wed, 15 Jul 2026 15:45:25 +1000 Subject: [PATCH] chore: enable errorlint --- .golangci.yml | 1 + cmd/ecs-agent/credendpoint.go | 3 ++- cmd/installer/install/install.go | 2 +- spinifex/admin/admin.go | 2 +- spinifex/admin/images_remove.go | 4 ++-- spinifex/gateway/bedrock/credentials.go | 3 ++- spinifex/handlers/ec2/image/service_impl.go | 4 ++-- spinifex/handlers/ec2/snapshot/service_impl.go | 2 +- spinifex/handlers/eks/nats_bootstrap.go | 6 +++--- spinifex/objectstore/objectstore.go | 9 ++++++--- spinifex/utils/images.go | 10 +++++----- spinifex/utils/nats.go | 2 +- spinifex/utils/utils.go | 10 +++++----- spinifex/vpcd/vpcd.go | 9 ++++++--- 14 files changed, 38 insertions(+), 29 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index d3f0595a1..acc680629 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -20,6 +20,7 @@ linters: - errcheck # checks for unchecked errors in type assertions and I/O - errchkjson # checks types passed to json encoding functions, flags omittable error checks - errname # checks that sentinel errors are prefixed with Err, error types suffixed with Error + - errorlint # catches ==/!= on errors, type assertions on errors, and %v instead of %w — all break on wrapped errors - exptostd # detects golang.org/x/exp/ functions replaceable by stdlib - fatcontext # detects nested contexts in loops - forcetypeassert # flags unchecked type assertions that can panic diff --git a/cmd/ecs-agent/credendpoint.go b/cmd/ecs-agent/credendpoint.go index 526c92661..1e7340205 100644 --- a/cmd/ecs-agent/credendpoint.go +++ b/cmd/ecs-agent/credendpoint.go @@ -3,6 +3,7 @@ package main import ( "context" "encoding/json" + "errors" "fmt" "log/slog" "net" @@ -143,7 +144,7 @@ func (c *credEndpoint) ServeHTTP(w http.ResponseWriter, r *http.Request) { } creds, roleARN, err := c.fetch(r.Context(), credID) if err != nil { - if err == errUnknownCredID { + if errors.Is(err, errUnknownCredID) { http.Error(w, "not found", http.StatusNotFound) return } diff --git a/cmd/installer/install/install.go b/cmd/installer/install/install.go index b840e7fce..b34b5512a 100644 --- a/cmd/installer/install/install.go +++ b/cmd/installer/install/install.go @@ -539,7 +539,7 @@ func installBootloader(disk string) error { ); biosErr != nil { if efiErr != nil { // Both targets failed — the system will not boot. - return fmt.Errorf("both bootloader targets failed (EFI: %v; BIOS: %w)", efiErr, biosErr) + return fmt.Errorf("both bootloader targets failed (EFI: %w; BIOS: %w)", efiErr, biosErr) } return biosErr } diff --git a/spinifex/admin/admin.go b/spinifex/admin/admin.go index d190649d6..48f1d79f7 100644 --- a/spinifex/admin/admin.go +++ b/spinifex/admin/admin.go @@ -143,7 +143,7 @@ type ConfigFile struct { func GenerateConfigFiles(configs []ConfigFile, configSettings ConfigSettings) error { for _, cfg := range configs { if err := GenerateConfigFile(cfg.Path, cfg.Template, configSettings); err != nil { - return fmt.Errorf("error creating %s: %v", cfg.Name, err) + return fmt.Errorf("error creating %s: %w", cfg.Name, err) } fmt.Printf("✅ Created: %s\n", cfg.Name) } diff --git a/spinifex/admin/images_remove.go b/spinifex/admin/images_remove.go index b900102f5..e772ad6be 100644 --- a/spinifex/admin/images_remove.go +++ b/spinifex/admin/images_remove.go @@ -320,7 +320,7 @@ func readAMIConfig(store objectstore.ObjectStore, bucket, imageID string) (viper var state viperblock.VBState if err := json.Unmarshal(viperblock.StateBody(body), &state); err != nil { - return viperblock.AMIMetadata{}, fmt.Errorf("%w: %s: %v", handlers_ec2_image.ErrCorruptAMIConfig, key, err) + return viperblock.AMIMetadata{}, fmt.Errorf("%w: %s: %w", handlers_ec2_image.ErrCorruptAMIConfig, key, err) } return state.VolumeConfig.AMIMetadata, nil } @@ -352,7 +352,7 @@ func readVolumeConfig(store objectstore.ObjectStore, bucket, volumeID string) (* var w volumeConfigWrapper if err := json.Unmarshal(viperblock.StateBody(body), &w); err != nil { - return nil, fmt.Errorf("%w: %s: %v", errCorruptVolumeConfig, key, err) + return nil, fmt.Errorf("%w: %s: %w", errCorruptVolumeConfig, key, err) } return &w.VolumeConfig, nil } diff --git a/spinifex/gateway/bedrock/credentials.go b/spinifex/gateway/bedrock/credentials.go index 9c82319cb..bd965b1ed 100644 --- a/spinifex/gateway/bedrock/credentials.go +++ b/spinifex/gateway/bedrock/credentials.go @@ -2,6 +2,7 @@ package gateway_bedrock import ( "context" + "errors" "fmt" "sync" @@ -88,7 +89,7 @@ func (s *CredentialStore) Resolve(_ context.Context, accountID, vendor string) ( return "", false, fmt.Errorf("decrypt credential for %s/%s: %w", accountID, vendor, err) } return plaintext, true, nil - case err != nats.ErrKeyNotFound: + case !errors.Is(err, nats.ErrKeyNotFound): return "", false, fmt.Errorf("kv get credential for %s/%s: %w", accountID, vendor, err) } } diff --git a/spinifex/handlers/ec2/image/service_impl.go b/spinifex/handlers/ec2/image/service_impl.go index 929d6bb1d..b9ed083e3 100644 --- a/spinifex/handlers/ec2/image/service_impl.go +++ b/spinifex/handlers/ec2/image/service_impl.go @@ -709,11 +709,11 @@ func (s *ImageServiceImpl) GetAMIConfig(ctx context.Context, imageID string) (vi body, err := io.ReadAll(result.Body) if err != nil { - return viperblock.AMIMetadata{}, fmt.Errorf("%w: %s: %v", ErrCorruptAMIConfig, configKey, err) + return viperblock.AMIMetadata{}, fmt.Errorf("%w: %s: %w", ErrCorruptAMIConfig, configKey, err) } var vbState viperblock.VBState if err := json.Unmarshal(viperblock.StateBody(body), &vbState); err != nil { - return viperblock.AMIMetadata{}, fmt.Errorf("%w: %s: %v", ErrCorruptAMIConfig, configKey, err) + return viperblock.AMIMetadata{}, fmt.Errorf("%w: %s: %w", ErrCorruptAMIConfig, configKey, err) } return vbState.VolumeConfig.AMIMetadata, nil } diff --git a/spinifex/handlers/ec2/snapshot/service_impl.go b/spinifex/handlers/ec2/snapshot/service_impl.go index 08d97d601..30cd85fb7 100644 --- a/spinifex/handlers/ec2/snapshot/service_impl.go +++ b/spinifex/handlers/ec2/snapshot/service_impl.go @@ -134,7 +134,7 @@ func ReadSnapshotConfig(store objectstore.ObjectStore, bucket, snapshotID string var cfg SnapshotConfig if err := json.NewDecoder(result.Body).Decode(&cfg); err != nil { - return nil, fmt.Errorf("%w: %v", ErrCorruptSnapshotMetadata, err) + return nil, fmt.Errorf("%w: %w", ErrCorruptSnapshotMetadata, err) } return &cfg, nil } diff --git a/spinifex/handlers/eks/nats_bootstrap.go b/spinifex/handlers/eks/nats_bootstrap.go index b756684e9..31c8185b2 100644 --- a/spinifex/handlers/eks/nats_bootstrap.go +++ b/spinifex/handlers/eks/nats_bootstrap.go @@ -302,7 +302,7 @@ func (b *NATSBootstrap) persistCA(data []byte) error { return fmt.Errorf("%w: CA envelope empty", errBootstrapPermanent) } if _, err := base64.StdEncoding.DecodeString(env.CertificateAuthority); err != nil { - return fmt.Errorf("%w: CA not base64: %v", errBootstrapPermanent, err) + return fmt.Errorf("%w: CA not base64: %w", errBootstrapPermanent, err) } return SetClusterCertificateAuthority(b.kv, b.clusterName, env.CertificateAuthority) } @@ -310,7 +310,7 @@ func (b *NATSBootstrap) persistCA(data []byte) error { func unmarshalBootstrapEnvelope(data []byte) (BootstrapEnvelope, error) { var env BootstrapEnvelope if err := json.Unmarshal(data, &env); err != nil { - return env, fmt.Errorf("%w: unmarshal envelope: %v", errBootstrapPermanent, err) + return env, fmt.Errorf("%w: unmarshal envelope: %w", errBootstrapPermanent, err) } return env, nil } @@ -323,7 +323,7 @@ func assertJWKSMatch(existing, incoming []byte) error { return fmt.Errorf("unmarshal existing JWKS: %w", err) } if err := json.Unmarshal(incoming, &in); err != nil { - return fmt.Errorf("%w: unmarshal incoming JWKS: %v", errBootstrapPermanent, err) + return fmt.Errorf("%w: unmarshal incoming JWKS: %w", errBootstrapPermanent, err) } if len(in.Keys) == 0 { return fmt.Errorf("%w: incoming JWKS has no keys", errBootstrapPermanent) diff --git a/spinifex/objectstore/objectstore.go b/spinifex/objectstore/objectstore.go index f2f99e810..6dc38ec9c 100644 --- a/spinifex/objectstore/objectstore.go +++ b/spinifex/objectstore/objectstore.go @@ -92,7 +92,8 @@ func NewS3ObjectStore(client *s3.S3) *S3ObjectStore { func (s *S3ObjectStore) GetObject(ctx context.Context, input *s3.GetObjectInput) (*s3.GetObjectOutput, error) { out, err := s.client.GetObjectWithContext(ctx, input) if err != nil { - if aerr, ok := err.(awserr.Error); ok && + var aerr awserr.Error + if errors.As(err, &aerr) && (aerr.Code() == s3.ErrCodeNoSuchKey || aerr.Code() == "NotFound") { return nil, &NoSuchKeyError{Key: aws.StringValue(input.Key)} } @@ -104,7 +105,8 @@ func (s *S3ObjectStore) GetObject(ctx context.Context, input *s3.GetObjectInput) func (s *S3ObjectStore) HeadObject(ctx context.Context, input *s3.HeadObjectInput) (*s3.HeadObjectOutput, error) { out, err := s.client.HeadObjectWithContext(ctx, input) if err != nil { - if aerr, ok := err.(awserr.Error); ok && + var aerr awserr.Error + if errors.As(err, &aerr) && (aerr.Code() == s3.ErrCodeNoSuchKey || aerr.Code() == "NotFound") { return nil, &NoSuchKeyError{Key: aws.StringValue(input.Key)} } @@ -134,7 +136,8 @@ func (s *S3ObjectStore) EnsureBucket(ctx context.Context, bucket string) error { } _, err := s.client.CreateBucketWithContext(ctx, &s3.CreateBucketInput{Bucket: aws.String(bucket)}) if err != nil { - if aerr, ok := err.(awserr.Error); ok { + var aerr awserr.Error + if errors.As(err, &aerr) { switch aerr.Code() { case s3.ErrCodeBucketAlreadyOwnedByYou, s3.ErrCodeBucketAlreadyExists: return nil diff --git a/spinifex/utils/images.go b/spinifex/utils/images.go index 45a225e47..6722abcf0 100644 --- a/spinifex/utils/images.go +++ b/spinifex/utils/images.go @@ -99,7 +99,7 @@ func newHasher(checksumType string) (hash.Hash, error) { func fetchExpectedDigest(checksumURL, filename string) (string, error) { parsed, err := url.Parse(checksumURL) if err != nil { - return "", fmt.Errorf("%w: parse url: %v", ErrChecksumFetchFailed, err) + return "", fmt.Errorf("%w: parse url: %w", ErrChecksumFetchFailed, err) } if parsed.Scheme != "https" { return "", fmt.Errorf("%w: non-https checksum url scheme %q", ErrChecksumFetchFailed, parsed.Scheme) @@ -138,12 +138,12 @@ func fetchExpectedDigest(checksumURL, filename string) (string, error) { req, err := http.NewRequestWithContext(ctx, http.MethodGet, checksumURL, nil) if err != nil { - return "", fmt.Errorf("%w: build request: %v", ErrChecksumFetchFailed, err) + return "", fmt.Errorf("%w: build request: %w", ErrChecksumFetchFailed, err) } resp, err := client.Do(req) if err != nil { - return "", fmt.Errorf("%w: %v", ErrChecksumFetchFailed, err) + return "", fmt.Errorf("%w: %w", ErrChecksumFetchFailed, err) } defer resp.Body.Close() @@ -153,7 +153,7 @@ func fetchExpectedDigest(checksumURL, filename string) (string, error) { body, err := io.ReadAll(io.LimitReader(resp.Body, sumsFileMaxSize+1)) if err != nil { - return "", fmt.Errorf("%w: read body: %v", ErrChecksumFetchFailed, err) + return "", fmt.Errorf("%w: read body: %w", ErrChecksumFetchFailed, err) } if len(body) > sumsFileMaxSize { return "", fmt.Errorf("%w: sums file exceeds %d byte limit", ErrChecksumFetchFailed, sumsFileMaxSize) @@ -203,7 +203,7 @@ func parseSumsFile(body []byte, filename string) (string, error) { } } if err := scanner.Err(); err != nil { - return "", fmt.Errorf("%w: scan sums file: %v", ErrChecksumFetchFailed, err) + return "", fmt.Errorf("%w: scan sums file: %w", ErrChecksumFetchFailed, err) } // Alpine single-file fallback: accept only if exactly one bare-digest line in the file. diff --git a/spinifex/utils/nats.go b/spinifex/utils/nats.go index 2ae123d91..d6a35ac35 100644 --- a/spinifex/utils/nats.go +++ b/spinifex/utils/nats.go @@ -61,7 +61,7 @@ func ConnectNATS(host, token, caCertPath string, opts ...RetryOption) (*nats.Con if caCertPath != "" { caCert, err := os.ReadFile(caCertPath) if err != nil { - return nil, fmt.Errorf("%w %s: %v", ErrCACertRead, caCertPath, err) + return nil, fmt.Errorf("%w %s: %w", ErrCACertRead, caCertPath, err) } pool := x509.NewCertPool() if !pool.AppendCertsFromPEM(caCert) { diff --git a/spinifex/utils/utils.go b/spinifex/utils/utils.go index 9f72f359c..1e567e4ea 100644 --- a/spinifex/utils/utils.go +++ b/spinifex/utils/utils.go @@ -182,12 +182,12 @@ func DownloadFileWithProgress(url string, name string, filename string, timeout req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { - return fmt.Errorf("request error: %v", err) + return fmt.Errorf("request error: %w", err) } resp, err := http.DefaultClient.Do(req) if err != nil { - return fmt.Errorf("http error: %v", err) + return fmt.Errorf("http error: %w", err) } defer resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode > 299 { @@ -196,7 +196,7 @@ func DownloadFileWithProgress(url string, name string, filename string, timeout f, err := os.Create(filename) if err != nil { - return fmt.Errorf("file create error: %v", err) + return fmt.Errorf("file create error: %w", err) } defer f.Close() @@ -215,7 +215,7 @@ func DownloadFileWithProgress(url string, name string, filename string, timeout _, err = io.Copy(f, reader) _, _ = bar.Stop() if err != nil { - return fmt.Errorf("copy error: %v", err) + return fmt.Errorf("copy error: %w", err) } return err } else { @@ -231,7 +231,7 @@ func DownloadFileWithProgress(url string, name string, filename string, timeout _ = spin.Stop() if err != nil { - return fmt.Errorf("copy error: %v", err) + return fmt.Errorf("copy error: %w", err) } } diff --git a/spinifex/vpcd/vpcd.go b/spinifex/vpcd/vpcd.go index 2018de813..70e41d29d 100644 --- a/spinifex/vpcd/vpcd.go +++ b/spinifex/vpcd/vpcd.go @@ -212,7 +212,8 @@ var localSystemID = func() (string, error) { out, err := sudoCommand("ovs-vsctl", "get", "open_vswitch", ".", "external-ids:system-id").Output() if err != nil { var stderr string - if exitErr, ok := err.(*exec.ExitError); ok { + var exitErr *exec.ExitError + if errors.As(err, &exitErr) { stderr = strings.TrimSpace(string(exitErr.Stderr)) } return "", fmt.Errorf("ovs-vsctl get system-id: %s: %w", stderr, err) @@ -239,7 +240,8 @@ var discoverChassis = func(sbAddr string) ([]string, error) { out, err := sudoCommand("ovn-sbctl", args...).Output() if err != nil { var stderr string - if exitErr, ok := err.(*exec.ExitError); ok { + var exitErr *exec.ExitError + if errors.As(err, &exitErr) { stderr = strings.TrimSpace(string(exitErr.Stderr)) } return nil, fmt.Errorf("ovn-sbctl list Chassis: %s: %w", stderr, err) @@ -843,7 +845,8 @@ var portToBr = func(port string) (string, error) { out, err := sudoCommand("ovs-vsctl", "port-to-br", port).Output() if err != nil { var stderr string - if exitErr, ok := err.(*exec.ExitError); ok { + var exitErr *exec.ExitError + if errors.As(err, &exitErr) { stderr = strings.TrimSpace(string(exitErr.Stderr)) } return "", fmt.Errorf("ovs-vsctl port-to-br %s: %s: %w", port, stderr, err)