awsacmpca: thread ctx through factory + registry — fix CI contextcheck

Follow-up to 6119f26 (awsacmpca: replace stub client with AWS SDK v2
implementation). CI's golangci-lint contextcheck rule flagged six
violations in awsacmpca_test.go where mustNew/awsacmpca.New were
called from test functions that had ctx in scope but didn't thread it
through New(). The previous commit used context.Background() inside
New() with the rationale that "the audit allows either threading or
documenting the limitation"; CI made that choice for us.

Threading ctx is the right shape per the audit's stated preference.
The fix cascades from awsacmpca.New through issuerfactory.NewFromConfig
and IssuerRegistry.Rebuild because the contextcheck rule propagates
upward through every caller that has ctx in scope.

This commit:
- Changes awsacmpca.New(config, logger) to
  awsacmpca.New(ctx, config, logger). The ctx is passed to
  buildSDKClient → awsconfig.LoadDefaultConfig so SDK credential chain
  resolution honors caller deadlines (LoadDefaultConfig may probe IMDS
  or remote credential sources). The doc-comment on New explains that
  callers without a useful deadline should pass context.Background()
  and that the SDK has internal credential-resolution timeouts.
- Adds ctx as the first parameter of issuerfactory.NewFromConfig.
  Currently only the AWSACMPCA branch uses ctx (it's threaded into
  awsacmpca.New); the other 11 branches accept ctx without using it.
  This is a contractual change that lets callers thread ctx through
  without contextcheck warnings, even though most issuer constructors
  do no ctx-aware work today.
- Adds ctx as the first parameter of IssuerRegistry.Rebuild. Rebuild
  iterates over configs and calls NewFromConfig per issuer; the same
  ctx flows through every connector instantiation.
- Updates the two production call sites in internal/service:
  - issuer.go:279 (TestIssuer connection test) now passes its
    method-scoped ctx
  - issuer.go:303 (BuildRegistry) now passes its method-scoped ctx
    to Rebuild
- Updates 13 test sites in internal/connector/issuerfactory/factory_test.go
  via a new testCtx() helper that returns context.Background(). Helper
  is dedicated to this file so contextcheck's "you have a ctx in scope,
  pass it" rule doesn't fire on test functions that don't otherwise
  need ctx.
- Updates 6 test sites in internal/service/issuer_registry_test.go
  to pass context.Background() to Rebuild.
- Removes the now-stale "// NewFromConfig has no ctx parameter
  (preserved across all 12 connectors); pass context.Background() ..."
  comment from the awsacmpca branch in factory.go — that workaround
  is no longer the design.

Verified locally:
- gofmt -l . clean
- go vet ./... clean
- staticcheck ./... clean
- golangci-lint run --timeout 5m ./... clean (was failing with 6
  contextcheck issues before the cascade; now 0 issues)
- go test -short -count=1 across all changed packages green

Sandbox couldn't run the existing CI's full make verify due to
disk pressure on /sessions and a virtiofs concurrent-open-file
ceiling on go mod tidy; operator should run `make verify` on
the workstation to confirm.

Audit reference: cowork/issuer-coverage-audit-2026-05-01/RESULTS.md
Top-10 fix #1 (CI follow-up; behavior unchanged from 6119f26).
This commit is contained in:
shankar0123
2026-05-01 23:27:25 +00:00
parent 6119f26b1b
commit 62d03d9134
7 changed files with 68 additions and 45 deletions
@@ -72,11 +72,14 @@ func (m *mockACMPCAClient) GetCACertificate(ctx context.Context, input *awsacmpc
// mustNew is a test helper that calls awsacmpca.New and fails the test if
// New returns an error. Use this for the ValidateConfig-only test sites
// where config is nil; New(nil, ...) skips SDK loading and never errors,
// so this helper is just to keep the call sites terse.
func mustNew(t *testing.T, config *awsacmpca.Config, logger *slog.Logger) *awsacmpca.Connector {
// where config is nil; New(ctx, nil, ...) skips SDK loading and never
// errors, so this helper is just to keep the call sites terse. The ctx
// parameter exists for contextcheck-lint cleanliness — when callers have
// ctx in scope, they should pass it through to New, which threads it
// into the SDK config load.
func mustNew(t *testing.T, ctx context.Context, config *awsacmpca.Config, logger *slog.Logger) *awsacmpca.Connector {
t.Helper()
c, err := awsacmpca.New(config, logger)
c, err := awsacmpca.New(ctx, config, logger)
if err != nil {
t.Fatalf("awsacmpca.New: %v", err)
}
@@ -155,7 +158,7 @@ func TestAWSACMPCAConnector(t *testing.T) {
ValidityDays: 365,
}
connector := mustNew(t, nil, logger)
connector := mustNew(t, ctx, nil, logger)
rawConfig, _ := json.Marshal(config)
err := connector.ValidateConfig(ctx, rawConfig)
if err != nil {
@@ -172,7 +175,7 @@ func TestAWSACMPCAConnector(t *testing.T) {
TemplateArn: "arn:aws:acm-pca:eu-west-1:123456789012:template/WebServer",
}
connector := mustNew(t, nil, logger)
connector := mustNew(t, ctx, nil, logger)
rawConfig, _ := json.Marshal(config)
err := connector.ValidateConfig(ctx, rawConfig)
if err != nil {
@@ -181,7 +184,7 @@ func TestAWSACMPCAConnector(t *testing.T) {
})
t.Run("ValidateConfig_InvalidJSON", func(t *testing.T) {
connector := mustNew(t, nil, logger)
connector := mustNew(t, ctx, nil, logger)
err := connector.ValidateConfig(ctx, []byte(`{invalid json}`))
if err == nil {
t.Fatal("Expected error for invalid JSON")
@@ -196,7 +199,7 @@ func TestAWSACMPCAConnector(t *testing.T) {
CAArn: "arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/12345678-1234-1234-1234-123456789012",
}
connector := mustNew(t, nil, logger)
connector := mustNew(t, ctx, nil, logger)
rawConfig, _ := json.Marshal(config)
err := connector.ValidateConfig(ctx, rawConfig)
if err == nil {
@@ -212,7 +215,7 @@ func TestAWSACMPCAConnector(t *testing.T) {
Region: "us-east-1",
}
connector := mustNew(t, nil, logger)
connector := mustNew(t, ctx, nil, logger)
rawConfig, _ := json.Marshal(config)
err := connector.ValidateConfig(ctx, rawConfig)
if err == nil {
@@ -229,7 +232,7 @@ func TestAWSACMPCAConnector(t *testing.T) {
CAArn: "not-an-arn",
}
connector := mustNew(t, nil, logger)
connector := mustNew(t, ctx, nil, logger)
rawConfig, _ := json.Marshal(config)
err := connector.ValidateConfig(ctx, rawConfig)
if err == nil {
@@ -247,7 +250,7 @@ func TestAWSACMPCAConnector(t *testing.T) {
SigningAlgorithm: "INVALID_ALGO",
}
connector := mustNew(t, nil, logger)
connector := mustNew(t, ctx, nil, logger)
rawConfig, _ := json.Marshal(config)
err := connector.ValidateConfig(ctx, rawConfig)
if err == nil {
@@ -265,7 +268,7 @@ func TestAWSACMPCAConnector(t *testing.T) {
ValidityDays: -1,
}
connector := mustNew(t, nil, logger)
connector := mustNew(t, ctx, nil, logger)
rawConfig, _ := json.Marshal(config)
err := connector.ValidateConfig(ctx, rawConfig)
if err == nil {
@@ -595,7 +598,7 @@ func TestAWSACMPCAConnector(t *testing.T) {
// SigningAlgorithm and ValidityDays not set
}
connector := mustNew(t, nil, logger)
connector := mustNew(t, ctx, nil, logger)
rawConfig, _ := json.Marshal(config)
err := connector.ValidateConfig(ctx, rawConfig)
if err != nil {
@@ -668,7 +671,7 @@ func TestNew_ProductionPath(t *testing.T) {
Region: "us-east-1",
CAArn: "arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/12345678-1234-1234-1234-123456789012",
}
c, err := awsacmpca.New(cfg, logger)
c, err := awsacmpca.New(ctx, cfg, logger)
if err != nil {
t.Fatalf("New with valid config returned error: %v", err)
}
@@ -702,7 +705,7 @@ func TestNew_ProductionPath(t *testing.T) {
// the connector is constructed with no client and ValidateConfig
// must be called before any operation. This documents the lazy
// initialization contract.
c, err := awsacmpca.New(nil, logger)
c, err := awsacmpca.New(ctx, nil, logger)
if err != nil {
t.Fatalf("New(nil, logger) returned error: %v", err)
}
@@ -728,7 +731,7 @@ func TestNew_ProductionPath(t *testing.T) {
// New(nil, ...) leaves client nil; ValidateConfig with a valid
// config should build it. After ValidateConfig succeeds, client-
// using methods should work end-to-end (modulo network errors).
c, err := awsacmpca.New(nil, logger)
c, err := awsacmpca.New(ctx, nil, logger)
if err != nil {
t.Fatalf("New(nil, logger): %v", err)
}
@@ -760,7 +763,7 @@ func TestNew_ProductionPath(t *testing.T) {
t.Run("RevokeBeforeInitFailsFast", func(t *testing.T) {
// The audit also flagged RevokeCertificate as part of the stub
// blocker. Verify the nil-client guard fires for revoke too.
c, err := awsacmpca.New(nil, logger)
c, err := awsacmpca.New(ctx, nil, logger)
if err != nil {
t.Fatalf("New(nil, logger): %v", err)
}
@@ -776,7 +779,7 @@ func TestNew_ProductionPath(t *testing.T) {
})
t.Run("GetCAPEMBeforeInitFailsFast", func(t *testing.T) {
c, err := awsacmpca.New(nil, logger)
c, err := awsacmpca.New(ctx, nil, logger)
if err != nil {
t.Fatalf("New(nil, logger): %v", err)
}