Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
3b68d9a
chore(lint): clear all golangci-lint findings to green
cristim Jul 13, 2026
c8fbc92
chore(lint): clear all golangci-lint findings and restore gosec/SQL d…
cristim Jul 13, 2026
1516f4b
fix(ci): exclude _test.go from standalone gocyclo to match .golangci.yml
cristim Jul 13, 2026
2401c58
chore(lint): eliminate hugeParam/fieldalignment/unusedwrite nolints v…
cristim Jul 13, 2026
7466f7f
chore(lint): fix paramTypeCombine and importShadow gocritic findings
cristim Jul 13, 2026
08b12a4
chore(lint): fix rangeValCopy gocritic findings in 32 files
cristim Jul 13, 2026
3e52d26
chore(lint): fix builtinShadow gocritic finding in payloadDepth
cristim Jul 13, 2026
3c6f23e
fix(lint): address CodeRabbit CR findings on lint-debt-clear PR
cristim Jul 13, 2026
e08228f
fix(lint): suppress QF1011 on intentional typed compile-time assertion
cristim Jul 13, 2026
b67e12d
fix(lint): address second batch of CR findings on lint-debt-clear
cristim Jul 14, 2026
768b19f
fix(lint): address third batch of CR findings on lint-debt-clear
cristim Jul 14, 2026
7460d69
fix(lint): restore fieldalignment global disable to fix CI
cristim Jul 14, 2026
850ee18
fix(lint): address fourth batch of CR findings on lint-debt-clear
cristim Jul 14, 2026
ee82691
fix(sec): add #nosec G101 form to dummyPasswordHash sentinel constant
cristim Jul 16, 2026
6a0422d
fix(ri-exchange): revert "canceled" -> "cancelled" spelling in reject…
cristim Jul 16, 2026
9a014a0
fix(sec): address Major CR security findings on lint-debt-clear
cristim Jul 16, 2026
c61d679
test(ri-exchange): align mock spelling with DB canonical "cancelled"
cristim Jul 16, 2026
7a23e6e
fix(lint): clear pre-existing golangci-lint debt in handler_ladder.go
cristim Jul 16, 2026
d1bfb1f
fix(lint): clear findings introduced by rebase onto current main
cristim Jul 16, 2026
2903532
fix(review): address fifth batch of CR findings on lint-debt-clear
cristim Jul 16, 2026
90786f9
fix(lint): US spelling in handler_inventory.go comments
cristim Jul 16, 2026
23ed924
fix(lint): rename flag param shadowing the flag package in sanity CLI
cristim Jul 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,10 @@ jobs:
- name: Check cyclomatic complexity
run: |
echo "Checking for functions with cyclomatic complexity over 10..."
COMPLEXITY_ISSUES=$(gocyclo -over 10 . 2>&1 || true)
# Exclude _test.go files to stay consistent with .golangci.yml, which
# excludes gocyclo on test files (test helpers/table-driven tests are
# allowed higher complexity). Production code is still gated at >10.
COMPLEXITY_ISSUES=$(gocyclo -over 10 -ignore "_test\.go" . 2>&1 || true)
if [ -n "$COMPLEXITY_ISSUES" ]; then
echo "❌ Found functions with cyclomatic complexity over 10:"
echo "$COMPLEXITY_ISSUES"
Expand Down Expand Up @@ -319,10 +322,9 @@ jobs:

- name: Run gosec Security Scanner
run: |
# Install pinned gosec using the job's existing setup-go (GO_VERSION 1.26.5).
# The securego/gosec Docker action bundles its own Go toolchain (1.26.1) which
# cannot satisfy the "go 1.26.5" module requirement in go.mod, causing it to
# load 0 files and exit 1 with a toolchain mismatch rather than real findings.
# Install pinned gosec using the job's existing setup-go.
# The securego/gosec Docker action bundles its own Go toolchain which
# cannot satisfy the module's go directive, causing a toolchain mismatch.
go install github.com/securego/gosec/v2/cmd/gosec@v2.26.1
# Multi-module repo: each ./... only walks the current module so scanning root
# alone silently misses pkg/ and providers/*. Mirror the govulncheck per-module
Expand Down
49 changes: 49 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ linters:
- diagnostic
- performance
- style
settings:
# aws.Config and similar SDK/config structs are designed to be passed
# by value (the AWS SDK v2 copies Config intentionally). 1024 bytes covers
# these idiomatic large-but-value-typed params without suppressing
# genuinely oversized plain domain structs.
hugeParam:
sizeThreshold: 1024
gocyclo:
min-complexity: 15
gosec:
Expand All @@ -45,6 +52,12 @@ linters:
pattern: (?i)passwd|pass|password|pwd|secret|token
govet:
enable-all: true
disable:
# fieldalignment: many production structs have alignment violations that
# pre-date this PR; fixing them is a separate concern. Suppress globally
# rather than per-struct since golangci-lint v2.10.x (CI) doesn't treat
# severity:warning as a non-fatal exit, unlike v2.11+.
- fieldalignment
misspell:
locale: US
revive:
Expand Down Expand Up @@ -77,13 +90,49 @@ linters:
- gocyclo
- gosec
path: _test\.go
# unusedwrite: test fixture struct fields may be set for documentation
# completeness even when only a subset of fields is read by a given subtest.
# Scoped to test files so production unusedwrite bugs remain visible.
# Note: fieldalignment is disabled globally in govet.disable (many
# pre-existing production violations; separate cleanup PR needed).
- linters:
- govet
text: "unusedwrite"
path: _test\.go
- linters:
- errcheck
- gosec
path: internal/testutil/
- linters:
- all
path: .*_gen\.go
# revive var-naming: "api" is an intentional package name for the HTTP API
# layer; it is not a utility package and "avoid meaningless package names"
# is a false positive here. All 88+ files in internal/api use this name.
- linters:
- revive
path: internal/api/
text: "avoid meaningless package names"
# noctx: exec.Command calls in deploy/configure CLI helpers intentionally
# omit context (CommandRunner interface contract; operator-controlled inputs).
# Suppressed per-file rather than per-line so #nosec G204 can lead the comment.
- linters:
- noctx
path: (internal/deploy/docker|cmd/configure_gcp|cmd/configure_azure)\.go
# noctx: http.Get and net.Listen in test helpers appropriately use the
# background context; threading a test context through every helper would
# add noise without improving test correctness.
- linters:
- noctx
path: _test\.go
# misspell: "cancelled" is the canonical DB column name (migration 000035,
# UK spelling throughout; migration #1277 tracks the rename). "initialised"
# appears in established comments in internal/config/types.go matching the
# existing UK convention. Allow both spellings in the files listed below.
- linters:
- misspell
path: (internal/config/(store_postgres|store_postgres_pgxmock_test|store_postgres_ladder|types|interfaces)|pkg/ladder/(store|types_test)|internal/api/(handler_history|handler_ri_exchange(_test)?|router_handlers_test|coverage_extras_test)|internal/mocks/stores|internal/purchase/scheduled_fire_test)\.go
text: "cancelled|initialised"
paths:
- third_party$
- builtin$
Expand Down
5 changes: 4 additions & 1 deletion ci_cd_sanity_tests/cmd/azure_sanity/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ func main() {
flag.Parse()

ctx, cancel := context.WithTimeout(context.Background(), time.Duration(*timeoutSec)*time.Second)
defer cancel()

rep, err := azure.Run(ctx, azure.Options{
SubscriptionID: *subID,
Expand All @@ -30,19 +29,23 @@ func main() {
Timeout: time.Duration(*timeoutSec) * time.Second,
})
if err != nil {
cancel()
fmt.Fprintf(os.Stderr, "azure sanity run failed: %v\n", err)
os.Exit(2)
}

if err := rep.WriteJSON(*outPath); err != nil {
cancel()
fmt.Fprintf(os.Stderr, "write report failed: %v\n", err)
os.Exit(2)
}

if rep.HasFailures() {
cancel()
fmt.Fprintf(os.Stderr, "azure sanity: FAIL (see %s)\n", *outPath)
os.Exit(1)
}

cancel()
fmt.Printf("azure sanity: PASS (see %s)\n", *outPath)
}
64 changes: 36 additions & 28 deletions ci_cd_sanity_tests/cmd/ri-exchange/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/json"
"flag"
"fmt"
"math"
"os"
"strings"
"time"
Expand All @@ -13,26 +14,16 @@ import (
)

type Output struct {
Mode string `json:"mode"` // dry-run | execute
Region string `json:"region"`
AccountChk string `json:"expected_account,omitempty"`

ReservedIDs []string `json:"reserved_instance_ids"`
Quote any `json:"quote"`
Mode string `json:"mode"`
Region string `json:"region"`
AccountChk string `json:"expected_account,omitempty"`
TargetOfferingID string `json:"target_offering_id"`
TargetCount int32 `json:"target_count"`
MaxPaymentDueUSD string `json:"max_payment_due_usd,omitempty"`
ExchangeID string `json:"exchange_id,omitempty"`
Quote any `json:"quote"`
Error string `json:"error,omitempty"`
}

// validateTargetCount exits with an error message when n is outside the int32
// range. Extracted to keep main's cyclomatic complexity within the project limit.
func validateTargetCount(n int) {
if n < 1 || n > (1<<31-1) {
fmt.Fprintln(os.Stderr, "ERROR: --target-count must be between 1 and math.MaxInt32")
os.Exit(2)
}
ReservedIDs []string `json:"reserved_instance_ids"`
TargetCount int32 `json:"target_count"`
}

func parseIDs(s string) []string {
Expand All @@ -46,6 +37,25 @@ func parseIDs(s string) []string {
return out
}

// validateRequiredFlags checks that the required CLI flags are present and
// that --target-count can safely be narrowed to int32. It exits on the first
// failure so callers do not need to handle the error return.
func validateRequiredFlags(riIDsCSV, targetOffering string, ids []string, targetCount int) {
if len(ids) == 0 {
fmt.Fprintln(os.Stderr, "ERROR: --ri-ids is required (comma-separated)")
os.Exit(2)
}
if strings.TrimSpace(targetOffering) == "" {
fmt.Fprintln(os.Stderr, "ERROR: --target-offering-id is required")
os.Exit(2)
}
if targetCount < 1 || targetCount > math.MaxInt32 {
fmt.Fprintf(os.Stderr, "ERROR: --target-count must be between 1 and %d, got %d\n", math.MaxInt32, targetCount)
os.Exit(2)
}
_ = riIDsCSV // used indirectly via ids
}

func main() {
var (
region = flag.String("region", "us-east-1", "AWS region")
Expand All @@ -65,20 +75,10 @@ func main() {
)
flag.Parse()

validateTargetCount(*targetCount)
ids := parseIDs(*riIDsCSV)
validateRequiredFlags(*riIDsCSV, *targetOffering, ids, *targetCount)

ctx, cancel := context.WithTimeout(context.Background(), time.Duration(*timeoutSec)*time.Second)
defer cancel()

ids := parseIDs(*riIDsCSV)
if len(ids) == 0 {
fmt.Fprintln(os.Stderr, "ERROR: --ri-ids is required (comma-separated)")
os.Exit(2)
}
if strings.TrimSpace(*targetOffering) == "" {
fmt.Fprintln(os.Stderr, "ERROR: --target-offering-id is required")
os.Exit(2)
}

o := Output{
Region: *region,
Expand All @@ -102,16 +102,19 @@ func main() {
o.Error = err.Error()
o.Quote = q
writeOrExit(o, *outPath)
cancel()
fmt.Fprintf(os.Stderr, "quote: FAIL (see %s)\n", *outPath)
os.Exit(1)
}
o.Quote = q
writeOrExit(o, *outPath)

if !q.IsValidExchange {
cancel()
fmt.Fprintf(os.Stderr, "quote: INVALID (%s) (see %s)\n", q.ValidationFailureReason, *outPath)
os.Exit(1)
}
cancel()
fmt.Printf("quote: OK (valid=%v, paymentDue=%s %s) (see %s)\n", q.IsValidExchange, q.PaymentDueRaw, q.CurrencyCode, *outPath)
os.Exit(0)
}
Expand All @@ -121,19 +124,22 @@ func main() {
if strings.TrimSpace(*ack) != "YES" {
o.Error = "refusing to execute: pass --ack YES"
writeOrExit(o, *outPath)
cancel()
fmt.Fprintf(os.Stderr, "execute: REFUSED (see %s)\n", *outPath)
os.Exit(2)
}
if strings.TrimSpace(*maxPaymentDue) == "" {
o.Error = "refusing to execute: --max-payment-due-usd is required as a safety cap"
writeOrExit(o, *outPath)
cancel()
fmt.Fprintf(os.Stderr, "execute: REFUSED (see %s)\n", *outPath)
os.Exit(2)
}
maxRat, err := exchange.ParseDecimalRat(*maxPaymentDue)
if err != nil {
o.Error = err.Error()
writeOrExit(o, *outPath)
cancel()
fmt.Fprintf(os.Stderr, "execute: BAD INPUT (see %s)\n", *outPath)
os.Exit(2)
}
Expand All @@ -151,12 +157,14 @@ func main() {
if err != nil {
o.Error = err.Error()
writeOrExit(o, *outPath)
cancel()
fmt.Fprintf(os.Stderr, "execute: FAIL (see %s)\n", *outPath)
os.Exit(1)
}

o.ExchangeID = exID
writeOrExit(o, *outPath)
cancel()
fmt.Printf("execute: OK exchangeId=%s (see %s)\n", exID, *outPath)
}

Expand Down
15 changes: 12 additions & 3 deletions ci_cd_sanity_tests/cmd/sanity/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@ import (
"context"
"flag"
"fmt"
"math"
"os"
"time"

"github.com/LeanerCloud/CUDly/ci_cd_sanity_tests/pkg/sanity/aws"
)

// requireInt32Range exits with an error when n is outside [1, math.MaxInt32].
func requireInt32Range(flag string, n int) {
func requireInt32Range(flagName string, n int) {
if n < 1 || n > (1<<31-1) {
fmt.Fprintf(os.Stderr, "ERROR: %s must be between 1 and math.MaxInt32\n", flag)
fmt.Fprintf(os.Stderr, "ERROR: %s must be between 1 and math.MaxInt32\n", flagName)
os.Exit(2)
}
}
Expand All @@ -28,28 +29,36 @@ func main() {
flag.Parse()
requireInt32Range("--max-list", *maxList)

if *maxList < 1 || *maxList > math.MaxInt32 {
fmt.Fprintf(os.Stderr, "ERROR: --max-list must be between 1 and %d, got %d\n", math.MaxInt32, *maxList)
os.Exit(2)
}

ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
defer cancel()

rep, err := aws.Run(ctx, aws.Options{
Region: *region,
ExpectedAccount: *expectedAccount,
MaxList: int32(*maxList), // #nosec G115 -- range-validated above (1 <= maxList <= math.MaxInt32); int->int32 cannot overflow
})
Comment thread
coderabbitai[bot] marked this conversation as resolved.
if err != nil {
cancel()
fmt.Fprintf(os.Stderr, "sanity run failed: %v\n", err)
os.Exit(2)
}

if err := rep.WriteJSON(*outPath); err != nil {
cancel()
fmt.Fprintf(os.Stderr, "write report failed: %v\n", err)
os.Exit(2)
}

if rep.HasFailures() {
cancel()
fmt.Fprintf(os.Stderr, "sanity checks: FAIL (see %s)\n", *outPath)
os.Exit(1)
}

cancel()
fmt.Printf("sanity checks: PASS (see %s)\n", *outPath)
}
12 changes: 6 additions & 6 deletions ci_cd_sanity_tests/pkg/sanity/aws/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,15 @@ func checkInstances(ctx context.Context, cfg aws.Config, maxList int32) (map[str
}

func checkRDS(ctx context.Context, cfg aws.Config, maxList int32) (map[string]string, error) {
max := maxList
if max < 20 {
max = 20
limit := maxList
if limit < 20 {
limit = 20
}
if max > 100 {
max = 100
if limit > 100 {
limit = 100
}
out, err := rds.NewFromConfig(cfg).DescribeDBInstances(ctx, &rds.DescribeDBInstancesInput{
MaxRecords: aws.Int32(max),
MaxRecords: aws.Int32(limit),
})
if err != nil {
return nil, err
Expand Down
8 changes: 4 additions & 4 deletions ci_cd_sanity_tests/pkg/sanity/azure/azure.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ type azAccountShow struct {
} `json:"user"`
}

func truncate(s string, max int) string {
if len(s) <= max {
func truncate(s string, limit int) string {
if len(s) <= limit {
return s
}
return s[:max] + "...(truncated)"
return s[:limit] + "...(truncated)"
}

// validateAccountExpectations parses "az account show" JSON output and checks
Expand Down Expand Up @@ -103,7 +103,7 @@ func Run(ctx context.Context, opts Options) (*report.Report, error) {

runCmd := func(name string, args ...string) ([]byte, report.CheckResult) {
start := time.Now().UTC()
cmd := exec.CommandContext(rctx, "az", args...) // #nosec G702,G204 -- CI sanity test tooling; binary is hardcoded "az" (Azure CLI), args are Azure CLI subcommands constructed within the test code
cmd := exec.CommandContext(rctx, "az", args...) // #nosec G702,G204 -- CI sanity test tooling; binary is hardcoded "az" (Azure CLI). Args are Azure CLI subcommands constructed in test code plus opts.SubscriptionID from config/CLI, which exec.CommandContext passes as a single argv value (no shell interpretation), so it cannot inject commands
out, err := cmd.CombinedOutput()
end := time.Now().UTC()

Expand Down
Loading
Loading