Skip to content

CNTRLPLANE-3167: support STS/IRSA credentials and standalone Velero#247

Merged
openshift-merge-bot[bot] merged 5 commits into
openshift:mainfrom
jparrill:CNTRLPLANE-3167
Jun 4, 2026
Merged

CNTRLPLANE-3167: support STS/IRSA credentials and standalone Velero#247
openshift-merge-bot[bot] merged 5 commits into
openshift:mainfrom
jparrill:CNTRLPLANE-3167

Conversation

@jparrill

@jparrill jparrill commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add AWS STS/IRSA credential auto-detection and pre-signing support for etcd snapshot restore (pure stdlib STS client, no AWS SDK dependency)
  • Detect OADP+DPA vs standalone Velero at plugin initialization via DPA CRD check
  • Fall back to well-known cloud-credentials secret (key: cloud) when BSL has no .spec.credential reference (ARO with Workload Identity, future ROSA without DPA)
  • Preserve original secret key alongside credentials in copied secret so the HCPEtcdBackup controller can auto-detect credential type

Description

ROSA HCP and ARO HCP use short-lived federated credentials instead of long-lived static keys. This PR enables the OADP plugin to handle these environments:

  • AWS STS/IRSA: ParseAWSCredentialData auto-detects 3 credential formats (bare ARN, INI with role_arn, static keys). When IRSA credentials are detected during restore, the plugin assumes the role via AssumeRoleWithWebIdentity before pre-signing the S3 URL
  • Standalone Velero detection: Plugin init checks for the DPA CRD (dataprotectionapplications.oadp.openshift.io). When absent (ARO, future ROSA without DPA), falls back to the well-known cloud-credentials secret in the Velero namespace
  • Azure Workload Identity: No pre-signing needed — the Azure Blob URL passes through directly, and the HCPEtcdBackup controller accesses the blob via projected SA token
  • Secret key preservation: copyCredentialSecret now copies the credential data with both credentials (for S3 controller) and the original key (e.g., cloud for Azure WI detection)

Scenarios covered

Scenario Backup (copy creds to HO NS) Restore (inject URL)
ROSA + OADP/DPA BSL .spec.credential (existing flow) Pre-sign S3 with BSL creds (existing flow)
ROSA + Velero (future) Fallback cloud-credentials in velero NS Pre-sign S3 with fallback + STS
ARO + Velero Fallback cloud-credentials in velero NS No pre-sign — Azure Blob URL passed through, controller accesses via WI

Related PRs

  • Controller side: openshift/hypershift#8368 — HCPEtcdBackup controller auto-detect credential mode (STS/IRSA for AWS, Workload Identity for Azure)

Jira

Test plan

  • go build ./... passes
  • All unit tests pass (pkg/s3presign, pkg/core, pkg/etcdbackup)
  • Existing tests with BSL .spec.credential present pass (no regression)
  • New tests: BSL without credential ref — fallback success + fallback with missing secret error
  • New tests: ParseAWSCredentialData — bare ARN, INI STS, static keys, invalid input
  • New tests: STSClient.AssumeRoleWithWebIdentity — success, error, XML parsing
  • E2E: ROSA STS cluster — trigger restore, verify plugin assumes role via STS and pre-signs URL
  • E2E: ARO cluster — trigger backup with standalone Velero, verify fallback credential copy works

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Azure Blob presigning with AAD user-delegation SAS and account-key SAS
    • AWS credential type detection including web-identity/role-based flows
    • Automatic fallback to a default credential secret when none is specified
    • Detection of paired vs standalone deployments for credential handling
  • Improvements

    • Skip etcd StatefulSet restores when appropriate
    • Generate Kubernetes-safe resource names within label length limits
    • More robust cross-cloud credential resolution and signing
  • Tests

    • Expanded unit coverage for Azure, AWS STS, token providers, and restore flows

@openshift-ci openshift-ci Bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Apr 30, 2026
@openshift-ci-robot openshift-ci-robot added the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label Apr 30, 2026
@openshift-ci

openshift-ci Bot commented Apr 30, 2026

Copy link
Copy Markdown

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

@openshift-ci-robot

openshift-ci-robot commented Apr 30, 2026

Copy link
Copy Markdown

@jparrill: This pull request references CNTRLPLANE-3167 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "5.0.0" version, but no target version was set.

Details

In response to this:

Summary

  • Add AWS STS/IRSA credential auto-detection and pre-signing support for etcd snapshot restore (no AWS SDK dependency — pure stdlib STS client)
  • Detect OADP+DPA vs standalone Velero at plugin initialization via DPA CRD check
  • Fall back to well-known cloud-credentials secret (key: cloud) when BSL has no .spec.credential reference (ARO with Workload Identity, future ROSA without DPA)
  • Preserve original secret key alongside credentials in copied secret so the HCPEtcdBackup controller can auto-detect credential type
  • Refactor copyCredentialSecret to accept a resolved SecretKeySelector

Related: openshift/hypershift#8368

Scenarios covered

Scenario Backup (copy creds to HO NS) Restore (inject URL)
ROSA + OADP/DPA BSL .spec.credential (existing flow) Pre-sign S3 with BSL creds (existing flow)
ROSA + Velero (future) Fallback cloud-credentials in velero NS Pre-sign S3 with fallback + STS
ARO + Velero Fallback cloud-credentials in velero NS No pre-sign — Azure Blob URL passed through, controller accesses via WI

Test plan

  • go build ./... passes
  • All unit tests pass (pkg/s3presign, pkg/core, pkg/etcdbackup)
  • Existing tests with BSL .spec.credential present pass (no regression)
  • New tests: BSL without credential ref — fallback success + fallback with missing secret error
  • E2E: ROSA STS cluster — trigger restore, verify plugin assumes role via STS and pre-signs URL
  • E2E: ARO cluster — trigger backup with standalone Velero, verify fallback credential copy works

🤖 Generated with Claude Code

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@coderabbitai

coderabbitai Bot commented Apr 30, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds default credential constants and resolver; classifies/parses AWS credentials and adds an STS client; implements Azure Blob account-key SAS and user-delegation SAS with AAD token provider; integrates presigning into restore and etcd backup flows; adds safe resource naming and expands tests and go.mod deps.

Changes

Common credential defaults & helper

Layer / File(s) Summary
Default constants
pkg/common/types.go
Adds exported constants: DefaultCredentialSecretName, DefaultCredentialSecretKey, and DPACRDName.
Credential resolver
pkg/common/credentials.go
Adds ResolveCredentialRef(bsl *velerov1api.BackupStorageLocation) *corev1.SecretKeySelector returning explicit BSL credential when set or a default secret selector otherwise.

Plugin wiring & restore entrypoints

Layer / File(s) Summary
Plugin DPA detection & wiring
pkg/core/backup.go, pkg/core/restore.go
Adds hasDPA bool to plugins and checks for DPA CRD at construction; RestorePlugin gets injectable newTokenProvider and newSTSClient.
Snapshot presign routing & StatefulSet handling
pkg/core/restore.go
Execute routes etcd snapshot URLs through signSnapshotURL (S3 or Azure) passing resource name; adds skip-restore for StatefulSet named etcd when method is etcdSnapshot.
Presign orchestration & credential fetch
pkg/core/restore.go
Adds fetchBSLCredentials to resolve BSL creds (uses ResolveCredentialRef), signSnapshotURL orchestration, presignS3URL (typed AWS creds, accepts hcName), and presignAzBlobUrl (AAD vs account-key paths).

S3 presign: credential parsing + STS

Layer / File(s) Summary
Credential parsing types & API
pkg/s3presign/presign.go
Adds typed credential detection: CredentialType, STSRoleCredentials, ParsedAWSCredentials, constants, and ParseAWSCredentialData (detects bare role ARN, INI profiles, or static creds).
STS client & interface
pkg/s3presign/sts.go
Adds STSAssumeRoler interface and STSClient with AssumeRoleWithWebIdentity that reads web-identity token files, POSTs to STS, validates 200 OK, and parses STS XML to return temp creds.
S3 presign tests & parsing tests
pkg/s3presign/sts_test.go, pkg/s3presign/presign_test.go
Unit tests covering STS request/response paths, error cases, and credential parsing (bare ARN, INI profile, token-file fallback, negative cases).

etcd backup orchestrator & credential copying

Layer / File(s) Summary
Credential fallback & safe names
pkg/etcdbackup/orchestrator.go
CreateEtcdBackup uses ResolveCredentialRef and falls back to default secret when BSL lacks credentials; replaces random-suffix name with safeResourceName to enforce 63-byte label-value limit.
Credential copy refactor
pkg/etcdbackup/orchestrator.go
copyCredentialSecret signature now accepts *corev1.SecretKeySelector, fetches source by name/key, remaps to credentials in destination, and preserves original key when different.
Orchestrator tests
pkg/etcdbackup/orchestrator_test.go
Adds TestSafeResourceName, refactors TestCopyCredentialSecret to use SecretKeySelector, and extends TestCreateEtcdBackup to cover fallback and name-length cases.

Azure Blob SAS, user delegation, and AAD token provider

Layer / File(s) Summary
SAS helpers and credential parsing
pkg/azblobsas/sas.go, pkg/azblobsas/sas_test.go
Adds ParseAzBlobURL, IsAzBlobURL, ParseAzureCredentials, ParseAzureAADCredentials, GenerateSASBlobURL (account-key SAS), and comprehensive tests validating parsing, URL structure, expiry defaults, and signature behavior.
User Delegation key & SAS generation
pkg/azblobsas/delegation.go, pkg/azblobsas/delegation_test.go
Adds GetUserDelegationKey (POST to account blob endpoint with bearer token, parse XML) and GenerateUserDelegationSASBlobURL (builds user-delegation SAS string-to-sign, computes HMAC-SHA256 signature) with deterministic tests and failure modes.
AAD token provider & cloud helpers
pkg/azblobsas/token.go, pkg/azblobsas/token_test.go
Adds TokenProvider interface and NewAADTokenProvider selecting client-secret, workload identity, or managed identity flows; adds StorageScopeForCloud and cloud mapping helpers with tests.
Integration usage
pkg/core/restore.go
presignAzBlobUrl uses azblobsas utilities and either account-key SAS generation or user-delegation SAS via GetUserDelegationKey and injected token provider when AAD flows are required.

Restore & integration tests

Layer / File(s) Summary
Restore presign and execution tests
pkg/core/restore_test.go
Updates S3 presign tests to pass hcName, adds TestPresignAzBlobUrlWithAAD, extends snapshot URL injection tests to assert Azure SAS URLs, and adds TestRestoreExecuteEtcdStatefulSet for skip behavior.

Module dependency updates

Layer / File(s) Summary
go.mod
go.mod
Adds/bumps Azure SDK modules and other indirect dependencies used by new Azure/AAD code paths.

Sequence Diagram(s)

sequenceDiagram
    participant RestorePlugin
    participant BSL as BackupStorageLocation
    participant Secret as Credential Secret
    participant Presigner
    participant STSClient
    participant STSEndpoint
    participant AzBlob as Azure Blob Service
    RestorePlugin->>Presigner: presign(snapshotURL, resourceName)
    Presigner->>BSL: check spec.credential
    alt explicit credential ref
        Presigner->>Secret: get(secretName, key)
        Secret-->>Presigner: credential bytes
    else fallback to default secret
        Presigner->>Secret: get(DefaultCredentialSecretName, DefaultCredentialSecretKey)
        Secret-->>Presigner: credential bytes
    end
    alt S3 + STS detected
        Presigner->>STSClient: AssumeRoleWithWebIdentity(roleARN, tokenFile, sessionName)
        STSClient->>STSEndpoint: POST web-identity request
        STSEndpoint-->>STSClient: 200 + STS XML
        STSClient-->>Presigner: temp AWS creds
        Presigner->>Presigner: generate presigned S3 URL with temp creds
        Presigner-->>RestorePlugin: presigned S3 URL
    else S3 + static creds
        Presigner->>Presigner: generate presigned S3 URL with static creds
        Presigner-->>RestorePlugin: presigned S3 URL
    else Azure Blob + AAD (user delegation)
        Presigner->>AzBlob: GetUserDelegationKey(account, bearerToken)
        AzBlob-->>Presigner: UserDelegationKey XML
        Presigner->>Presigner: GenerateUserDelegationSASBlobURL(opts)
        Presigner-->>RestorePlugin: SAS URL
    else Azure Blob + account-key
        Presigner->>Presigner: GenerateSASBlobURL(accountKey)
        Presigner-->>RestorePlugin: SAS URL
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~55 minutes

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@openshift-ci

openshift-ci Bot commented Apr 30, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: jparrill

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-ci openshift-ci Bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Apr 30, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pkg/core/restore.go`:
- Around line 325-330: The STS session name built as sessionName :=
fmt.Sprintf("oadp-restore-%s", hcName) can exceed AWS's 64-char RoleSessionName
limit; update the code that sets sessionName (used in the call to
s3presign.NewSTSClient().AssumeRoleWithWebIdentity) to produce a string capped
at 64 characters while preserving the "oadp-restore-" prefix (e.g., build a
helper like buildSTSSessionName(prefix, name) that concatenates prefix+name and
truncates to 64 bytes/characters before passing it into
AssumeRoleWithWebIdentity). Ensure the truncated sessionName is used in the
existing AssumeRoleWithWebIdentity call so AWS validation errors are avoided.

In `@pkg/s3presign/presign_test.go`:
- Around line 549-553: Replace the os.Unsetenv call with t.Setenv to avoid
unchecked errors: in the test block that currently checks tt.envTokenFile and
sets AWS_WEB_IDENTITY_TOKEN_FILE, always call
t.Setenv("AWS_WEB_IDENTITY_TOKEN_FILE", tt.envTokenFile) (use empty string when
tt.envTokenFile == "") instead of calling os.Unsetenv; update the code around
the tt.envTokenFile branch in presign_test.go (the block referencing
tt.envTokenFile and AWS_WEB_IDENTITY_TOKEN_FILE) so the test harness t.Setenv is
used for both branches.

In `@pkg/s3presign/sts_test.go`:
- Around line 37-45: Tests in pkg/s3presign/sts_test.go are ignoring errors
returned by w.Write in several HTTP test handlers (e.g., the handler that writes
the AssumeRoleWithWebIdentityResponse and similar handlers around the other
occurrences), which trips errcheck; modify each handler to capture the error
returned by w.Write and handle it (fail the test) instead of discarding it —
e.g., assign the result of w.Write(...) to a variable and call t.Fatalf/t.Errorf
or use require.NoError(t, err) when err != nil so the test stops on write/parse
failures; apply this change to every handler that currently calls w.Write
without checking the returned error.

In `@pkg/s3presign/sts.go`:
- Around line 111-119: The STS response handling in the function that builds
AWSCredentials (using resp.Result.Credentials from AssumeRoleWithWebIdentity)
currently only checks AccessKeyID and SecretAccessKey; update that check to also
require a non-empty SessionToken and return an error if it's missing so we never
return AWSCredentials without SessionToken (which would cause presigned S3 URLs
to omit X-Amz-Security-Token and fail). Locate the creds :=
resp.Result.Credentials block and change the conditional to validate
creds.SessionToken as well, returning a descriptive fmt.Errorf when the token is
empty before constructing the AWSCredentials.
- Around line 37-75: Modify AssumeRoleWithWebIdentity to accept a
context.Context as its first parameter and use it for the HTTP call: change the
signature of AssumeRoleWithWebIdentity(roleARN, tokenFile, sessionName string)
to AssumeRoleWithWebIdentity(ctx context.Context, roleARN, tokenFile,
sessionName string), build the url.Values as before but replace
c.HTTPClient.PostForm with creating an http.NewRequestWithContext(ctx, "POST",
endpoint, strings.NewReader(params.Encode())), set the Content-Type header to
"application/x-www-form-urlencoded" and call c.HTTPClient.Do(req); keep the
existing error wrapping and resp.Body.Close() handling so
cancellations/deadlines propagate correctly.
- Around line 26-31: NewSTSClient currently hardcodes Endpoint to
defaultSTSEndpoint which breaks non-commercial ARNs (arn:aws-us-gov,
arn:aws-cn); change NewSTSClient/STSClient so the STS endpoint is derived from
the ARN partition rather than always using defaultSTSEndpoint: detect partition
(aws, aws-cn, aws-us-gov) from the parsed ARN and map to the appropriate STS
endpoint (e.g., aws -> https://sts.amazonaws.com or regional variant, aws-cn ->
https://sts.cn-north-1.amazonaws.com.cn, aws-us-gov ->
https://sts.us-gov-west-1.amazonaws.com), and set STSClient.Endpoint accordingly
(or accept the partition/region as an argument) instead of using
defaultSTSEndpoint.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 0f862226-a971-46ed-88fc-b01b1fd13e40

📥 Commits

Reviewing files that changed from the base of the PR and between fbe3121 and 850b259.

📒 Files selected for processing (10)
  • pkg/common/types.go
  • pkg/core/backup.go
  • pkg/core/restore.go
  • pkg/core/restore_test.go
  • pkg/etcdbackup/orchestrator.go
  • pkg/etcdbackup/orchestrator_test.go
  • pkg/s3presign/presign.go
  • pkg/s3presign/presign_test.go
  • pkg/s3presign/sts.go
  • pkg/s3presign/sts_test.go

Comment thread pkg/core/restore.go Outdated
Comment on lines +325 to +330
stsClient := s3presign.NewSTSClient()
sessionName := fmt.Sprintf("oadp-restore-%s", hcName)
creds, err = stsClient.AssumeRoleWithWebIdentity(
parsed.STSRole.RoleARN,
parsed.STSRole.WebIdentityTokenFile,
sessionName,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n pkg/core/restore.go | sed -n '320,335p'

Repository: openshift/hypershift-oadp-plugin

Length of output: 693


🌐 Web query:

AWS STS RoleSessionName maximum length constraint

💡 Result:

The maximum length of the RoleSessionName parameter in AWS STS AssumeRole (and related operations like AssumeRoleWithSAML, AssumeRoleWithWebIdentity) is 64 characters. It has a minimum length of 2 characters and must match the pattern [\w+=,.@-]* (upper/lowercase alphanumeric, plus _, =, +, ,, ., @, -; no spaces).

Citations:


🏁 Script executed:

# Search for hcName definition and how it's derived
rg "hcName\s*:=" pkg/core/restore.go -B 5 -A 2

Repository: openshift/hypershift-oadp-plugin

Length of output: 409


🏁 Script executed:

# Check if there are any existing length validations in the STS or session code
rg "sessionName|RoleSessionName" --type go -A 3 -B 1

Repository: openshift/hypershift-oadp-plugin

Length of output: 2218


🏁 Script executed:

# Look for test files that might show typical hcName values
fd "test\.go|_test\.go" pkg/core/ -x cat {} \; | head -100

Repository: openshift/hypershift-oadp-plugin

Length of output: 3055


Bound the generated STS session name to 64 characters.

Line 326 builds oadp-restore-<hcName> without truncation. AWS STS RoleSessionName has a 64-character maximum; the prefix consumes 13 characters, leaving only 51 characters for hcName. Since HostedCluster names are Kubernetes DNS subdomains (up to 253 characters), typical cluster names easily exceed this budget, causing AssumeRoleWithWebIdentity to fail with a validation error from AWS.

Suggested direction
-		sessionName := fmt.Sprintf("oadp-restore-%s", hcName)
+		sessionName := buildSTSSessionName("oadp-restore-", hcName)
func buildSTSSessionName(prefix, name string) string {
	session := prefix + name
	if len(session) > 64 {
		session = session[:64]
	}
	return session
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/core/restore.go` around lines 325 - 330, The STS session name built as
sessionName := fmt.Sprintf("oadp-restore-%s", hcName) can exceed AWS's 64-char
RoleSessionName limit; update the code that sets sessionName (used in the call
to s3presign.NewSTSClient().AssumeRoleWithWebIdentity) to produce a string
capped at 64 characters while preserving the "oadp-restore-" prefix (e.g., build
a helper like buildSTSSessionName(prefix, name) that concatenates prefix+name
and truncates to 64 bytes/characters before passing it into
AssumeRoleWithWebIdentity). Ensure the truncated sessionName is used in the
existing AssumeRoleWithWebIdentity call so AWS validation errors are avoided.

Comment on lines +549 to +553
if tt.envTokenFile != "" {
t.Setenv("AWS_WEB_IDENTITY_TOKEN_FILE", tt.envTokenFile)
} else {
os.Unsetenv("AWS_WEB_IDENTITY_TOKEN_FILE")
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, examine the file at the specified lines
cat -n pkg/s3presign/presign_test.go | sed -n '545,560p'

Repository: openshift/hypershift-oadp-plugin

Length of output: 558


🏁 Script executed:

# Check if there's a parser that handles the AWS_WEB_IDENTITY_TOKEN_FILE env var
rg -A 10 -B 5 "AWS_WEB_IDENTITY_TOKEN_FILE" pkg/s3presign/ --type go

Repository: openshift/hypershift-oadp-plugin

Length of output: 5223


🏁 Script executed:

# Look for the test function containing these lines to understand context
rg -B 30 "os.Unsetenv.*AWS_WEB_IDENTITY_TOKEN_FILE" pkg/s3presign/presign_test.go --type go

Repository: openshift/hypershift-oadp-plugin

Length of output: 1007


Use t.Setenv for both branches to avoid the errcheck error.

Line 552's os.Unsetenv error is not being handled, so errcheck will flag this. Since the parser treats an empty AWS_WEB_IDENTITY_TOKEN_FILE value the same as an unset variable (both return empty string from os.Getenv), you can simplify this to use t.Setenv in both branches.

Suggested change
-			if tt.envTokenFile != "" {
-				t.Setenv("AWS_WEB_IDENTITY_TOKEN_FILE", tt.envTokenFile)
-			} else {
-				os.Unsetenv("AWS_WEB_IDENTITY_TOKEN_FILE")
-			}
+			t.Setenv("AWS_WEB_IDENTITY_TOKEN_FILE", tt.envTokenFile)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if tt.envTokenFile != "" {
t.Setenv("AWS_WEB_IDENTITY_TOKEN_FILE", tt.envTokenFile)
} else {
os.Unsetenv("AWS_WEB_IDENTITY_TOKEN_FILE")
}
t.Setenv("AWS_WEB_IDENTITY_TOKEN_FILE", tt.envTokenFile)
🧰 Tools
🪛 golangci-lint (2.11.4)

[error] 552-552: Error return value of os.Unsetenv is not checked

(errcheck)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/s3presign/presign_test.go` around lines 549 - 553, Replace the
os.Unsetenv call with t.Setenv to avoid unchecked errors: in the test block that
currently checks tt.envTokenFile and sets AWS_WEB_IDENTITY_TOKEN_FILE, always
call t.Setenv("AWS_WEB_IDENTITY_TOKEN_FILE", tt.envTokenFile) (use empty string
when tt.envTokenFile == "") instead of calling os.Unsetenv; update the code
around the tt.envTokenFile branch in presign_test.go (the block referencing
tt.envTokenFile and AWS_WEB_IDENTITY_TOKEN_FILE) so the test harness t.Setenv is
used for both branches.

Comment thread pkg/s3presign/sts_test.go
Comment on lines +37 to +45
w.Write([]byte(`<AssumeRoleWithWebIdentityResponse>
<AssumeRoleWithWebIdentityResult>
<Credentials>
<AccessKeyId>ASIATESTACCESSKEY</AccessKeyId>
<SecretAccessKey>testSecretKey123</SecretAccessKey>
<SessionToken>testSessionToken456</SessionToken>
</Credentials>
</AssumeRoleWithWebIdentityResult>
</AssumeRoleWithWebIdentityResponse>`))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle write/parse errors in test handlers to avoid errcheck failures.

Line 37, Line 79, Line 106, and Line 204 currently ignore returned errors (same pattern also appears at Line 130 and Line 207). With errcheck enabled, this can break CI.

✅ Suggested patch
@@
-			w.Write([]byte(`<AssumeRoleWithWebIdentityResponse>
+			if _, err := w.Write([]byte(`<AssumeRoleWithWebIdentityResponse>
   <AssumeRoleWithWebIdentityResult>
     <Credentials>
       <AccessKeyId>ASIATESTACCESSKEY</AccessKeyId>
       <SecretAccessKey>testSecretKey123</SecretAccessKey>
       <SessionToken>testSessionToken456</SessionToken>
     </Credentials>
   </AssumeRoleWithWebIdentityResult>
-</AssumeRoleWithWebIdentityResponse>`))
+</AssumeRoleWithWebIdentityResponse>`)); err != nil {
+				t.Fatalf("failed to write response: %v", err)
+			}
@@
-			w.Write([]byte(`<ErrorResponse><Error><Code>AccessDenied</Code><Message>Not authorized</Message></Error></ErrorResponse>`))
+			if _, err := w.Write([]byte(`<ErrorResponse><Error><Code>AccessDenied</Code><Message>Not authorized</Message></Error></ErrorResponse>`)); err != nil {
+				t.Fatalf("failed to write response: %v", err)
+			}
@@
-			w.Write([]byte(`not xml at all`))
+			if _, err := w.Write([]byte(`not xml at all`)); err != nil {
+				t.Fatalf("failed to write response: %v", err)
+			}
@@
-			w.Write([]byte(`<AssumeRoleWithWebIdentityResponse>
+			if _, err := w.Write([]byte(`<AssumeRoleWithWebIdentityResponse>
   <AssumeRoleWithWebIdentityResult>
     <Credentials>
       <AccessKeyId></AccessKeyId>
       <SecretAccessKey></SecretAccessKey>
       <SessionToken></SessionToken>
     </Credentials>
   </AssumeRoleWithWebIdentityResult>
-</AssumeRoleWithWebIdentityResponse>`))
+</AssumeRoleWithWebIdentityResponse>`)); err != nil {
+				t.Fatalf("failed to write response: %v", err)
+			}
@@
-			r.ParseForm()
+			if err := r.ParseForm(); err != nil {
+				t.Fatalf("failed to parse form: %v", err)
+			}
@@
-			w.Write([]byte(`<AssumeRoleWithWebIdentityResponse>
+			if _, err := w.Write([]byte(`<AssumeRoleWithWebIdentityResponse>
   <AssumeRoleWithWebIdentityResult>
     <Credentials>
       <AccessKeyId>ASIA123</AccessKeyId>
       <SecretAccessKey>secret</SecretAccessKey>
       <SessionToken>token</SessionToken>
     </Credentials>
   </AssumeRoleWithWebIdentityResult>
-</AssumeRoleWithWebIdentityResponse>`))
+</AssumeRoleWithWebIdentityResponse>`)); err != nil {
+				t.Fatalf("failed to write response: %v", err)
+			}

Also applies to: 79-79, 106-106, 130-138, 204-215

🧰 Tools
🪛 golangci-lint (2.11.4)

[error] 37-37: Error return value of w.Write is not checked

(errcheck)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/s3presign/sts_test.go` around lines 37 - 45, Tests in
pkg/s3presign/sts_test.go are ignoring errors returned by w.Write in several
HTTP test handlers (e.g., the handler that writes the
AssumeRoleWithWebIdentityResponse and similar handlers around the other
occurrences), which trips errcheck; modify each handler to capture the error
returned by w.Write and handle it (fail the test) instead of discarding it —
e.g., assign the result of w.Write(...) to a variable and call t.Fatalf/t.Errorf
or use require.NoError(t, err) when err != nil so the test stops on write/parse
failures; apply this change to every handler that currently calls w.Write
without checking the returned error.

Comment thread pkg/s3presign/sts.go
Comment on lines +26 to +31
// NewSTSClient creates a default STSClient pointing at the global STS endpoint.
func NewSTSClient() *STSClient {
return &STSClient{
HTTPClient: &http.Client{Timeout: 30 * time.Second},
Endpoint: defaultSTSEndpoint,
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cd /repo && find . -name "sts.go" -o -name "*presign*test*" | head -20

Repository: openshift/hypershift-oadp-plugin

Length of output: 132


🏁 Script executed:

cd /repo && git ls-files | grep -E "(sts\.go|presign.*test)" | head -20

Repository: openshift/hypershift-oadp-plugin

Length of output: 132


🏁 Script executed:

cd /repo && rg "arn:aws-us-gov|arn:aws-cn" --max-count 20

Repository: openshift/hypershift-oadp-plugin

Length of output: 132


🏁 Script executed:

git ls-files | grep -E "(sts\.go|presign)" | head -30

Repository: openshift/hypershift-oadp-plugin

Length of output: 234


🏁 Script executed:

find . -type f -name "*.go" | xargs grep -l "sts\|STS" | head -20

Repository: openshift/hypershift-oadp-plugin

Length of output: 791


🏁 Script executed:

rg "arn:aws-us-gov|arn:aws-cn" -A 2 -B 2

Repository: openshift/hypershift-oadp-plugin

Length of output: 1579


🏁 Script executed:

cat -n pkg/s3presign/sts.go

Repository: openshift/hypershift-oadp-plugin

Length of output: 4152


🏁 Script executed:

cat -n pkg/s3presign/presign.go | head -100

Repository: openshift/hypershift-oadp-plugin

Length of output: 3406


🏁 Script executed:

cat -n pkg/s3presign/presign.go | grep -A 50 "ParseAWSCredentials\|STSRole"

Repository: openshift/hypershift-oadp-plugin

Length of output: 6349


🏁 Script executed:

rg "NewSTSClient\|STSClient\{" -A 3 -B 1

Repository: openshift/hypershift-oadp-plugin

Length of output: 58


🏁 Script executed:

cat -n pkg/s3presign/presign_test.go | head -150

Repository: openshift/hypershift-oadp-plugin

Length of output: 4471


🏁 Script executed:

cat -n pkg/s3presign/presign_test.go | grep -A 40 "GovCloud ARN\|China region ARN"

Repository: openshift/hypershift-oadp-plugin

Length of output: 4522


🏁 Script executed:

rg "NewSTSClient\|NewSTSClientWithEndpoint\|\.Endpoint\s*=" pkg/s3presign/

Repository: openshift/hypershift-oadp-plugin

Length of output: 58


🏁 Script executed:

cat -n pkg/s3presign/sts_test.go

Repository: openshift/hypershift-oadp-plugin

Length of output: 9610


🏁 Script executed:

rg "NewSTSClient\(\)" -A 5 -B 2

Repository: openshift/hypershift-oadp-plugin

Length of output: 3159


🏁 Script executed:

rg "AssumeRoleWithWebIdentity" -A 3 -B 2 pkg/

Repository: openshift/hypershift-oadp-plugin

Length of output: 7914


🏁 Script executed:

cat -n pkg/s3presign/presign.go | grep -A 30 "func GetPresignedURL\|STSClient"

Repository: openshift/hypershift-oadp-plugin

Length of output: 58


🏁 Script executed:

rg "arn:aws" pkg/s3presign/presign.go -B 3 -A 3 | head -50

Repository: openshift/hypershift-oadp-plugin

Length of output: 730


🏁 Script executed:

rg "partition\|Partition\|aws-us-gov\|aws-cn" pkg/s3presign/ -i

Repository: openshift/hypershift-oadp-plugin

Length of output: 58


The STS endpoint must be derived from the ARN partition, not hardcoded.

NewSTSClient() always targets https://sts.amazonaws.com, but the parser also accepts arn:aws-us-gov and arn:aws-cn ARNs (verified in presign_test.go). When non-commercial ARNs are provided, they parse successfully but the STS call will fail because the endpoint is wrong. For example, GovCloud requires https://sts.us-gov-west-1.amazonaws.com and China requires https://sts.cn-north-1.amazonaws.com.cn.

Extract the partition from the ARN and route to the correct regional STS endpoint accordingly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/s3presign/sts.go` around lines 26 - 31, NewSTSClient currently hardcodes
Endpoint to defaultSTSEndpoint which breaks non-commercial ARNs (arn:aws-us-gov,
arn:aws-cn); change NewSTSClient/STSClient so the STS endpoint is derived from
the ARN partition rather than always using defaultSTSEndpoint: detect partition
(aws, aws-cn, aws-us-gov) from the parsed ARN and map to the appropriate STS
endpoint (e.g., aws -> https://sts.amazonaws.com or regional variant, aws-cn ->
https://sts.cn-north-1.amazonaws.com.cn, aws-us-gov ->
https://sts.us-gov-west-1.amazonaws.com), and set STSClient.Endpoint accordingly
(or accept the partition/region as an argument) instead of using
defaultSTSEndpoint.

Comment thread pkg/s3presign/sts.go Outdated
Comment thread pkg/s3presign/sts.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
pkg/azblobsas/delegation.go (1)

80-80: ⚡ Quick win

Use lowercase for error messages.

Error strings should not be capitalized per Go conventions (ST1005).

♻️ Proposed fix
-		return nil, fmt.Errorf("Get User Delegation Key returned %d: %s", resp.StatusCode, string(respBody))
+		return nil, fmt.Errorf("get user delegation key returned %d: %s", resp.StatusCode, string(respBody))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/azblobsas/delegation.go` at line 80, The error string is capitalized;
change it to follow Go convention of lowercase error messages by updating the
fmt.Errorf call in the GetUserDelegationKey code path to start with a lowercase
letter (e.g., "get user delegation key returned %d: %s") while keeping the same
formatting placeholders and returning the error as before.
go.mod (1)

8-9: ⚡ Quick win

Consider updating azcore to v1.21.1.

Both versions exist and are valid releases. azidentity v1.13.1 is the current latest version, but azcore v1.20.0 has a newer version available (v1.21.1 as of May 2026). If not pinned for stability reasons, consider updating azcore to the latest patch version for potential bug fixes and improvements.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@go.mod` around lines 8 - 9, Update the azcore dependency to the latest patch
release by changing the version for github.com/Azure/azure-sdk-for-go/sdk/azcore
from v1.20.0 to v1.21.1 in go.mod so it aligns with the newer patch while
keeping github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 unchanged;
after updating, run `go mod tidy` to verify and refresh module checksums.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/azblobsas/delegation.go`:
- Line 72: The deferred resp.Body.Close() call in delegation.go currently
ignores its error; wrap the defer in an anonymous function that calls
resp.Body.Close(), captures its returned error, and handle it (return it up the
stack or log it via the existing logger) so cleanup failures aren’t
swallowed—look for the resp variable and the line with defer resp.Body.Close()
and replace it with a deferred closure that checks and handles the Close()
error.

---

Nitpick comments:
In `@go.mod`:
- Around line 8-9: Update the azcore dependency to the latest patch release by
changing the version for github.com/Azure/azure-sdk-for-go/sdk/azcore from
v1.20.0 to v1.21.1 in go.mod so it aligns with the newer patch while keeping
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 unchanged; after
updating, run `go mod tidy` to verify and refresh module checksums.

In `@pkg/azblobsas/delegation.go`:
- Line 80: The error string is capitalized; change it to follow Go convention of
lowercase error messages by updating the fmt.Errorf call in the
GetUserDelegationKey code path to start with a lowercase letter (e.g., "get user
delegation key returned %d: %s") while keeping the same formatting placeholders
and returning the error as before.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 7a52f984-546c-42e3-acc3-a4e5e7ca7e57

📥 Commits

Reviewing files that changed from the base of the PR and between 850b259 and 66c2e62.

⛔ Files ignored due to path filters (216)
  • go.sum is excluded by !**/*.sum
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/CHANGELOG.md is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/LICENSE.txt is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/README.md is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/arm/internal/resource/resource_identifier.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/arm/internal/resource/resource_type.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/arm/policy/policy.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/arm/runtime/pipeline.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/arm/runtime/policy_bearer_token.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/arm/runtime/policy_register_rp.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/arm/runtime/policy_trace_namespace.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/arm/runtime/runtime.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/ci.yml is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud/cloud.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/core.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/errors.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/etag.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/exported/exported.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/exported/pipeline.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/exported/request.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/exported/response_error.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/log/log.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/pollers/async/async.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/pollers/body/body.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/pollers/fake/fake.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/pollers/loc/loc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/pollers/op/op.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/pollers/poller.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/pollers/util.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared/constants.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared/shared.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/log/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/log/log.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/policy/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/policy/policy.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/errors.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/pager.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/pipeline.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_api_version.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_bearer_token.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_body_download.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_http_header.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_http_trace.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_include_response.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_key_credential.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_logging.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_request_id.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_retry.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_sas_credential.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_telemetry.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/poller.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/request.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/response.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/transport_default_dialer_other.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/transport_default_dialer_wasm.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/transport_default_http_client.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/streaming/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/streaming/progress.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/tracing/constants.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/tracing/tracing.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/.gitignore is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/BREAKING_CHANGES.md is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/CHANGELOG.md is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/LICENSE.txt is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/MIGRATION.md is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/README.md is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/TOKEN_CACHING.MD is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/TROUBLESHOOTING.md is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/assets.json is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/authentication_record.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/azidentity.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/azure_cli_credential.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/azure_developer_cli_credential.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/azure_pipelines_credential.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/azure_powershell_credential.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/chained_token_credential.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/ci.yml is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/client_assertion_credential.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/client_certificate_credential.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/client_secret_credential.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/confidential_client.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/default_azure_credential.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/developer_credential_util.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/developer_credential_util_nonwindows.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/developer_credential_util_windows.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/device_code_credential.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/environment_credential.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/errors.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/interactive_browser_credential.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/internal/cache.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/logging.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/managed-identity-matrix.json is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/managed_identity_client.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/managed_identity_credential.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/on_behalf_of_credential.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/public_client.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/test-resources-post.ps1 is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/test-resources-pre.ps1 is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/test-resources.bicep is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/username_password_credential.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/version.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/workload_identity.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/LICENSE.txt is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/diag/diag.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/diag/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/errorinfo/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/errorinfo/errorinfo.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/exported/exported.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/log/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/log/log.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/poller/util.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/temporal/resource.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/uuid/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/uuid/uuid.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/AzureAD/microsoft-authentication-library-for-go/LICENSE is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/cache/cache.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential/confidential.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/errors/error_design.md is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/errors/errors.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/base.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/storage/items.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/storage/partitioned_storage.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/storage/storage.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/exported/exported.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/design.md is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/json.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/mapslice.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/marshal.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/struct.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/types/time/time.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/local/server.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/oauth.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/accesstokens/accesstokens.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/accesstokens/apptype_string.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/accesstokens/tokens.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority/authority.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority/authorizetype_string.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/internal/comm/comm.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/internal/comm/compress.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/internal/grant/grant.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/ops.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust/defs/endpointtype_string.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust/defs/mex_document_definitions.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust/defs/saml_assertion_definitions.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust/defs/version_string.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust/defs/wstrust_endpoint.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust/defs/wstrust_mex_document.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust/wstrust.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/resolvers.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/options/options.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/shared/shared.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/version/version.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/managedidentity/azure_ml.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/managedidentity/cloud_shell.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/managedidentity/managedidentity.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/managedidentity/servicefabric.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/public/public.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/golang-jwt/jwt/v5/.gitignore is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/golang-jwt/jwt/v5/LICENSE is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/golang-jwt/jwt/v5/MIGRATION_GUIDE.md is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/golang-jwt/jwt/v5/README.md is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/golang-jwt/jwt/v5/SECURITY.md is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/golang-jwt/jwt/v5/VERSION_HISTORY.md is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/golang-jwt/jwt/v5/claims.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/golang-jwt/jwt/v5/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/golang-jwt/jwt/v5/ecdsa.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/golang-jwt/jwt/v5/ecdsa_utils.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/golang-jwt/jwt/v5/ed25519.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/golang-jwt/jwt/v5/ed25519_utils.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/golang-jwt/jwt/v5/errors.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/golang-jwt/jwt/v5/hmac.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/golang-jwt/jwt/v5/map_claims.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/golang-jwt/jwt/v5/none.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/golang-jwt/jwt/v5/parser.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/golang-jwt/jwt/v5/parser_option.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/golang-jwt/jwt/v5/registered_claims.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/golang-jwt/jwt/v5/rsa.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/golang-jwt/jwt/v5/rsa_pss.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/golang-jwt/jwt/v5/rsa_utils.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/golang-jwt/jwt/v5/signing_method.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/golang-jwt/jwt/v5/staticcheck.conf is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/golang-jwt/jwt/v5/token.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/golang-jwt/jwt/v5/token_option.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/golang-jwt/jwt/v5/types.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/golang-jwt/jwt/v5/validator.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/kylelemons/godebug/LICENSE is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/kylelemons/godebug/diff/diff.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/kylelemons/godebug/pretty/.gitignore is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/kylelemons/godebug/pretty/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/kylelemons/godebug/pretty/public.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/kylelemons/godebug/pretty/reflect.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/kylelemons/godebug/pretty/structure.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/pkg/browser/LICENSE is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/pkg/browser/README.md is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/pkg/browser/browser.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/pkg/browser/browser_darwin.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/pkg/browser/browser_freebsd.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/pkg/browser/browser_linux.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/pkg/browser/browser_netbsd.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/pkg/browser/browser_openbsd.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/pkg/browser/browser_unsupported.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/pkg/browser/browser_windows.go is excluded by !**/vendor/**, !vendor/**
  • vendor/golang.org/x/crypto/LICENSE is excluded by !**/vendor/**, !vendor/**
  • vendor/golang.org/x/crypto/PATENTS is excluded by !**/vendor/**, !vendor/**
  • vendor/golang.org/x/crypto/pkcs12/bmp-string.go is excluded by !**/vendor/**, !vendor/**
  • vendor/golang.org/x/crypto/pkcs12/crypto.go is excluded by !**/vendor/**, !vendor/**
  • vendor/golang.org/x/crypto/pkcs12/errors.go is excluded by !**/vendor/**, !vendor/**
  • vendor/golang.org/x/crypto/pkcs12/internal/rc2/rc2.go is excluded by !**/vendor/**, !vendor/**
  • vendor/golang.org/x/crypto/pkcs12/mac.go is excluded by !**/vendor/**, !vendor/**
  • vendor/golang.org/x/crypto/pkcs12/pbkdf.go is excluded by !**/vendor/**, !vendor/**
  • vendor/golang.org/x/crypto/pkcs12/pkcs12.go is excluded by !**/vendor/**, !vendor/**
  • vendor/golang.org/x/crypto/pkcs12/safebags.go is excluded by !**/vendor/**, !vendor/**
  • vendor/modules.txt is excluded by !**/vendor/**, !vendor/**
📒 Files selected for processing (11)
  • go.mod
  • pkg/azblobsas/delegation.go
  • pkg/azblobsas/delegation_test.go
  • pkg/azblobsas/sas.go
  • pkg/azblobsas/sas_test.go
  • pkg/azblobsas/token.go
  • pkg/azblobsas/token_test.go
  • pkg/core/restore.go
  • pkg/core/restore_test.go
  • pkg/etcdbackup/orchestrator.go
  • pkg/etcdbackup/orchestrator_test.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • pkg/etcdbackup/orchestrator.go
  • pkg/etcdbackup/orchestrator_test.go
  • pkg/core/restore.go

if err != nil {
return nil, fmt.Errorf("calling Get User Delegation Key: %w", err)
}
defer resp.Body.Close()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Check the error from resp.Body.Close().

The deferred resp.Body.Close() call's error return value is not checked, which could silently hide cleanup failures.

🛡️ Proposed fix
-	defer resp.Body.Close()
+	defer func() {
+		if cerr := resp.Body.Close(); cerr != nil && err == nil {
+			err = fmt.Errorf("closing response body: %w", cerr)
+		}
+	}()

Alternatively, if you prefer to just log the error:

-	defer resp.Body.Close()
+	defer func() {
+		if cerr := resp.Body.Close(); cerr != nil {
+			// Log the error if needed
+		}
+	}()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
defer resp.Body.Close()
defer func() {
if cerr := resp.Body.Close(); cerr != nil && err == nil {
err = fmt.Errorf("closing response body: %w", cerr)
}
}()
🧰 Tools
🪛 golangci-lint (2.12.2)

[error] 72-72: Error return value of resp.Body.Close is not checked

(errcheck)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/azblobsas/delegation.go` at line 72, The deferred resp.Body.Close() call
in delegation.go currently ignores its error; wrap the defer in an anonymous
function that calls resp.Body.Close(), captures its returned error, and handle
it (return it up the stack or log it via the existing logger) so cleanup
failures aren’t swallowed—look for the resp variable and the line with defer
resp.Body.Close() and replace it with a deferred closure that checks and handles
the Close() error.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/common/credentials.go`:
- Around line 11-13: ResolveCredentialRef currently dereferences the
BackupStorageLocation pointer bsl without checking for nil; update the function
ResolveCredentialRef to first guard for a nil bsl and return nil immediately if
bsl is nil, then proceed to check bsl.Spec.Credential as before so callers won't
panic when passing a nil BackupStorageLocation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: a35af491-e8e2-4ff1-ab15-2a97ebbffd5f

📥 Commits

Reviewing files that changed from the base of the PR and between 66c2e62 and d00dc31.

📒 Files selected for processing (6)
  • pkg/common/credentials.go
  • pkg/core/restore.go
  • pkg/core/restore_test.go
  • pkg/etcdbackup/orchestrator.go
  • pkg/s3presign/sts.go
  • pkg/s3presign/sts_test.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • pkg/etcdbackup/orchestrator.go
  • pkg/core/restore.go
  • pkg/core/restore_test.go

Comment thread pkg/common/credentials.go
Comment on lines +11 to +13
func ResolveCredentialRef(bsl *velerov1api.BackupStorageLocation) *corev1.SecretKeySelector {
if bsl.Spec.Credential != nil {
return bsl.Spec.Credential

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard against nil bsl to avoid panic on credential resolution.

Line 12 dereferences bsl unconditionally. If a caller passes nil, this will panic and can fail the reconcile flow.

Proposed fix
 func ResolveCredentialRef(bsl *velerov1api.BackupStorageLocation) *corev1.SecretKeySelector {
+	if bsl == nil || bsl.Spec.Credential == nil {
+		return &corev1.SecretKeySelector{
+			LocalObjectReference: corev1.LocalObjectReference{Name: DefaultCredentialSecretName},
+			Key:                  DefaultCredentialSecretKey,
+		}
+	}
-	if bsl.Spec.Credential != nil {
-		return bsl.Spec.Credential
-	}
-	return &corev1.SecretKeySelector{
-		LocalObjectReference: corev1.LocalObjectReference{Name: DefaultCredentialSecretName},
-		Key:                  DefaultCredentialSecretKey,
-	}
+	return bsl.Spec.Credential
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func ResolveCredentialRef(bsl *velerov1api.BackupStorageLocation) *corev1.SecretKeySelector {
if bsl.Spec.Credential != nil {
return bsl.Spec.Credential
func ResolveCredentialRef(bsl *velerov1api.BackupStorageLocation) *corev1.SecretKeySelector {
if bsl == nil || bsl.Spec.Credential == nil {
return &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{Name: DefaultCredentialSecretName},
Key: DefaultCredentialSecretKey,
}
}
return bsl.Spec.Credential
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/common/credentials.go` around lines 11 - 13, ResolveCredentialRef
currently dereferences the BackupStorageLocation pointer bsl without checking
for nil; update the function ResolveCredentialRef to first guard for a nil bsl
and return nil immediately if bsl is nil, then proceed to check
bsl.Spec.Credential as before so callers won't panic when passing a nil
BackupStorageLocation.

@tony-schndr

tony-schndr commented May 28, 2026

Copy link
Copy Markdown
Contributor

@jparrill I have validated image https://quay.io/repository/jparrill/hypershift-oadp-plugin?tab=tags&tag=[CNTRLPLANE-3167](https://redhat.atlassian.net/browse/CNTRLPLANE-3167)-v3 works in ARO-HCP personal development environments by:

  1. Creating a Hosted Cluster
  2. Creating a test config map on Hosted Cluster
  3. Taking an velero backup using etcdBackupMethod: etcdSnapshot
  4. Deleting the Hosted Cluster
  5. Restoring it from the backup from step 3.
  6. Validating the test config map is still present on the Hosted Cluster

Exact image used (mirrored from quay.io): arohcpsvcdev.azurecr.io/jparrill/hypershift-oadp-plugin@sha256:8c5c1db7cee57d5612db8952bfffd9ddb44408b2ed1ee773b54638e6e90a9d8f

@openshift-ci openshift-ci Bot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label May 28, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/core/restore_test.go`:
- Around line 345-356: The helper writeTempFile currently ignores the error
returned by f.Close(), which can hide write/flush failures; update writeTempFile
to check Close() and fail the test on error (use t.Fatalf or t.Fatalf-style
error reporting) after WriteString, e.g., capture err = f.Close() and call
t.Fatalf("failed to close temp file: %v", err) if non-nil; ensure this change is
applied inside the writeTempFile function so tests surface flush/close errors.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 244d0273-eb89-47d7-8f35-48652c9faa40

📥 Commits

Reviewing files that changed from the base of the PR and between d00dc31 and aea364c.

📒 Files selected for processing (6)
  • pkg/common/credentials.go
  • pkg/core/restore.go
  • pkg/core/restore_test.go
  • pkg/etcdbackup/orchestrator.go
  • pkg/s3presign/sts.go
  • pkg/s3presign/sts_test.go
✅ Files skipped from review due to trivial changes (1)
  • pkg/common/credentials.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • pkg/s3presign/sts_test.go
  • pkg/etcdbackup/orchestrator.go
  • pkg/s3presign/sts.go
  • pkg/core/restore.go

Comment thread pkg/core/restore_test.go
Comment on lines +345 to +356
func writeTempFile(t *testing.T, content string) string {
t.Helper()
f, err := os.CreateTemp(t.TempDir(), "token-*")
if err != nil {
t.Fatalf("failed to create temp file: %v", err)
}
if _, err := f.WriteString(content); err != nil {
t.Fatalf("failed to write temp file: %v", err)
}
f.Close()
return f.Name()
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Handle Close() errors in writeTempFile.

Line 354 ignores the Close() return, which can mask write/flush failures in tests.

✅ Suggested fix
 func writeTempFile(t *testing.T, content string) string {
 	t.Helper()
 	f, err := os.CreateTemp(t.TempDir(), "token-*")
 	if err != nil {
 		t.Fatalf("failed to create temp file: %v", err)
 	}
+	defer func() {
+		if err := f.Close(); err != nil {
+			t.Fatalf("failed to close temp file: %v", err)
+		}
+	}()
 	if _, err := f.WriteString(content); err != nil {
 		t.Fatalf("failed to write temp file: %v", err)
 	}
-	f.Close()
 	return f.Name()
 }

As per coding guidelines, "Go security (prodsec-skills): Never ignore error returns".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/core/restore_test.go` around lines 345 - 356, The helper writeTempFile
currently ignores the error returned by f.Close(), which can hide write/flush
failures; update writeTempFile to check Close() and fail the test on error (use
t.Fatalf or t.Fatalf-style error reporting) after WriteString, e.g., capture err
= f.Close() and call t.Fatalf("failed to close temp file: %v", err) if non-nil;
ensure this change is applied inside the writeTempFile function so tests surface
flush/close errors.

jparrill and others added 5 commits May 29, 2026 11:10
…or etcd backup/restore

Add support for AWS STS/IRSA credential detection and pre-signing, and
handle standalone Velero deployments (no DPA) where the BSL has no
explicit credential reference.

- Add ParseAWSCredentialData to auto-detect credential type (static,
  bare ARN, INI with role_arn) and STSClient for AssumeRoleWithWebIdentity
  without AWS SDK dependency
- Detect OADP+DPA vs standalone Velero at plugin init via DPA CRD check
- Fall back to well-known cloud-credentials secret when BSL has no
  .spec.credential (ARO with WI, future ROSA without DPA)
- Preserve original secret key alongside "credentials" in copied secret
  so the HCPEtcdBackup controller can auto-detect credential type
- Refactor copyCredentialSecret to accept resolved SecretKeySelector

Ref: openshift/hypershift#8368

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Juan Manuel Parrilla Madrid <jparrill@redhat.com>
The HyperShift hcpetcdbackup controller sets the CR name as a label
value on the Job and Pod template. Kubernetes label values are capped
at 63 bytes (RFC 1123). Schedule-triggered backups produce names that
exceed this limit, causing Job creation to fail.

Truncate the backup name portion of the CR name so the total never
exceeds 63 bytes, and trim any trailing hyphens left by truncation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…tore

Adds Service SAS (account key) and User Delegation SAS (AAD) signing
for Azure Blob Storage snapshot URLs during OADP restore, matching the
existing S3 pre-signing capability.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When using the etcdSnapshot backup method, the etcd StatefulSet was
being restored by Velero with a stale RESTORE_URL_ETCD environment
variable baked into its pod template from a previous backup/restore
cycle. The StatefulSet controller would create etcd pods with the wrong
snapshot URL before the HyperShift operator could reconcile the correct
URL from the HCP spec, causing etcd to restore data from the wrong
snapshot.

Skip restoring the etcd StatefulSet when etcdBackupMethod is
etcdSnapshot, matching the existing pattern for etcd Pods and PVCs.
The control plane operator creates the StatefulSet fresh with the
correct RESTORE_URL_ETCD derived from the RestoreSnapshotURL already
injected into the HCP spec by this plugin.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rt to STS

- Add context.Context to STSClient.AssumeRoleWithWebIdentity for cancellation support
- Define STSAssumeRoler interface and inject into RestorePlugin for testability
- Centralize credential fallback logic into common.ResolveCredentialRef()
- Extract signSnapshotURL() dispatcher to eliminate duplicate s3/azblob dispatch
- Extract fetchBSLCredentials() to consolidate BSL+secret fetch pattern
- Add missing test for AAD token acquisition failure path
- Replace custom contains/searchSubstring with strings.Contains in sts_test.go

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Juan Manuel Parrilla Madrid <jparrill@redhat.com>
@openshift-ci openshift-ci Bot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label May 29, 2026
@jparrill jparrill marked this pull request as ready for review June 1, 2026 10:57
@jparrill jparrill requested review from enxebre and sjenning as code owners June 1, 2026 10:57
@openshift-ci openshift-ci Bot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Jun 1, 2026
@openshift-ci openshift-ci Bot requested review from bryan-cox and muraee June 1, 2026 10:58
@openshift-ci

openshift-ci Bot commented Jun 1, 2026

Copy link
Copy Markdown

@jparrill: all tests passed!

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

@jparrill

jparrill commented Jun 2, 2026

Copy link
Copy Markdown
Contributor Author

/verified by @tony-schndr

@openshift-ci-robot openshift-ci-robot added the verified Signifies that the PR passed pre-merge verification criteria label Jun 2, 2026
@openshift-ci-robot

Copy link
Copy Markdown

@jparrill: This PR has been marked as verified by @tony-schndr.

Details

In response to this:

/verified by @tony-schndr

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@jparrill

jparrill commented Jun 3, 2026

Copy link
Copy Markdown
Contributor Author

/verified by @tony-schndr

@openshift-ci-robot

Copy link
Copy Markdown

@jparrill: This PR has been marked as verified by @tony-schndr.

Details

In response to this:

/verified by @tony-schndr

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@mgencur mgencur left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @jparrill This looks good! I have a few question here and there. I'm also posting the PR review by /code-review:pr from ai-helpers. There are some minor recommendations but overall it's OK:

Code Review Report: PR #247

PR Info

  • Title: CNTRLPLANE-3167: support STS/IRSA credentials and standalone Velero
  • Author: jparrill (Juan Manuel Parrilla Madrid)
  • Base branch: main
  • Link: #247

Files Reviewed

Source files (10):

File Status
pkg/azblobsas/delegation.go NEW
pkg/azblobsas/sas.go NEW
pkg/azblobsas/token.go NEW
pkg/common/credentials.go NEW
pkg/common/types.go MODIFIED
pkg/core/backup.go MODIFIED
pkg/core/restore.go MODIFIED
pkg/etcdbackup/orchestrator.go MODIFIED
pkg/s3presign/presign.go MODIFIED
pkg/s3presign/sts.go NEW

Test files (8):

File Status
pkg/azblobsas/delegation_test.go NEW
pkg/azblobsas/sas_test.go NEW
pkg/azblobsas/token_test.go NEW
pkg/core/restore_test.go MODIFIED
pkg/etcdbackup/orchestrator_test.go MODIFIED
pkg/s3presign/presign_test.go MODIFIED
pkg/s3presign/sts_test.go NEW

Config: go.mod, go.sum (dependency updates + new Azure SDK deps)


Build Verification: PASS

Check Result
go build ./... PASS
go vet ./... PASS (clean)
go test (6 packages) PASS (all ok)

Unit Test Coverage

Overall: Strong. Every new source file has a corresponding _test.go. Tests use table-driven patterns with t.Run(), cover success and error paths, and test edge cases.

Coverage gaps (ordered by severity)

Severity Location Gap
Medium pkg/common/credentials.go:11 ResolveCredentialRef has no direct unit test. Two branches (explicit ref vs fallback) used in critical backup/restore paths. Tested indirectly but should have a dedicated credentials_test.go.
Low pkg/core/backup.go:266-271 Etcd PVC exclusion logic (strings.HasPrefix(name, "data-etcd-")) is not tested in backup_test.go.
Low pkg/azblobsas/delegation.go:112 Invalid base64 delegation key error path not tested.
Low pkg/azblobsas/token.go:27 NewAADTokenProvider not directly tested (acceptable — requires real Azure infra; mock injection in restore tests is correct).

Coverage details by file

pkg/azblobsas/delegation.go (NEW) — Tests: delegation_test.go

  • GetUserDelegationKeyTESTED (success, HTTP 403, HTTP 500, malformed XML). Verifies request method, query params, headers, and response parsing.
  • GenerateUserDelegationSASBlobURLTESTED (basic URL structure, deterministic output, custom endpoint, http endpoint, missing fields, nil delegation key, different blobs produce different sigs, different keys produce different sigs, default expiry).
  • Minor gap: no test for invalid endpoint string (url.Parse error path at delegation.go:43).
  • Minor gap: no test for invalid base64 delegation key value (error path at delegation.go:112).

pkg/azblobsas/sas.go (NEW) — Tests: sas_test.go

  • ParseAzBlobURLTESTED (11 cases: valid public/gov/china/http URLs, missing blob, missing container, wrong scheme, non-Azure URL, empty URL, trailing slash).
  • IsAzBlobURLTESTED (11 cases: all cloud variants, wrong schemes, empty string, non-URL input).
  • ParseAzureCredentialsTESTED (8 cases: standard key, custom env var, comments/blank lines, whitespace, missing key, empty data, value with equals, multiple entries).
  • ParseAzureAADCredentialsTESTED (6 cases: workload identity, service principal, comments, missing tenant, missing client, empty data).
  • GenerateSASBlobURLTESTED (10 cases: basic structure, determinism, custom endpoint, http endpoint, default expiry, missing fields, missing key, invalid base64, special chars, sig changes).
  • No gaps identified.

pkg/azblobsas/token.go (NEW) — Tests: token_test.go

  • NewAADTokenProviderNOT DIRECTLY TESTED. Acceptable — creates Azure SDK credential objects requiring real infrastructure. Callers inject a mock TokenProvider.
  • StorageScopeForCloudTESTED (6 cases covering all cloud variants and empty string).
  • cloudConfigForNameTESTED (6 cases matching cloud names to authority hosts).

pkg/common/credentials.go (NEW) — Tests: NONE

  • ResolveCredentialRefNOT TESTED directly. Exercised indirectly through orchestrator and restore tests. A direct unit test is warranted.

pkg/core/restore.go (MODIFIED) — Tests: restore_test.go (MODIFIED)

  • RestorePlugin.ExecuteTESTED via TestRestoreExecuteEtcdStatefulSet, TestRestoreExecuteSnapshotURL, TestRestoreExecuteHCPSnapshotURL (etcd StatefulSet skip/restore, HC with s3/https/Azure annotations, HCP with s3/https/Azure annotations, missing BSL).
  • presignS3URLTESTED (9 cases: valid URL, invalid URL, BSL missing, BSL no cred ref fallback, fallback secret missing, wrong key, https URL, IRSA/STS credentials, STS failure, custom endpoint).
  • presignAzBlobUrlTESTED via TestPresignAzBlobUrlWithAAD (7 cases: AAD with credentials, missing AAD fields, WI env vars, missing env vars, token acquisition failure, account key SAS path).

pkg/etcdbackup/orchestrator.go (MODIFIED) — Tests: orchestrator_test.go (MODIFIED)

  • CreateEtcdBackupTESTED (7 cases: valid AWS BSL, nil HC, BSL not found, unsupported provider, no credential ref fallback, long name truncation, missing fallback secret).
  • copyCredentialSecretTESTED (5 cases: key remapping, already exists, key=credentials no duplication, missing key, missing secret).
  • safeResourceNameTESTED (5 cases: short/exact/truncation/trailing hyphen).

pkg/s3presign/presign.go (MODIFIED) — Tests: presign_test.go (MODIFIED)

  • ParseAWSCredentialData (NEW) — TESTED (13 cases: bare ARN, ARN with whitespace, GovCloud/China ARNs, missing env token, INI with role_arn+token, role_arn only env fallback, non-default profile, source_profile error, credential_source error, static creds, static with session token, empty data, whitespace-only, invalid INI).
  • isBareARN (NEW) — TESTED (9 cases).
  • parseSTSProfile (NEW) — TESTED (5 cases).

pkg/s3presign/sts.go (NEW) — Tests: sts_test.go (NEW)

  • STSClient.AssumeRoleWithWebIdentityTESTED (success, HTTP error, malformed XML, 4 missing-credential-field combinations, missing token file, empty token file, empty roleARN, empty tokenFile path, default session name).
  • NewSTSClientTESTED.

Test quality observations

  • All test files use table-driven tests with t.Run() — consistent with project conventions.
  • t.Helper() is used appropriately in helper functions (writeTempFile, writeTokenFile, newHCPUnstructured).
  • t.Parallel() is not used anywhere in the PR. Tests appear independent and could run in parallel, but this is a style choice, not a correctness issue.
  • Error messages are verified with substring matching, which is robust against message rewording.
  • Mock injection (via newTokenProvider, newSTSClient function fields) is a clean pattern for testing external service calls.

Idiomatic Go Code

Severity Location Finding Recommendation
Medium restore.go:339,381 presignAzBlobUrl / presignAzBlobUrlWithAAD — "URL" should be all-caps per Go naming convention Rename to presignAzBlobURL / presignAzBlobURLWithAAD
Low restore.go:411 Uses time.Now() directly instead of nowFunc — inconsistent with azblobsas package's testable time pattern Use nowFunc or accept time as parameter
Low backup.go:8-9, restore.go:13-15 Redundant import aliases that match package names (common "...common", validation "...validation") Remove explicit aliases
Low backup.go:139, restore.go:147 No-op type conversion context.Context(p.ctx) Pre-existing, but should be p.ctx directly
Info presign.go:94,158 ParseAWSCredentials vs ParseAWSCredentialData naming overlap could confuse callers Consider DetectAWSCredentials for the higher-level function
Info presign.go:167 os.Getenv inside a parsing function makes it impure Consider accepting the env var value as a parameter
Info orchestrator_test.go:50-52 Comment/function name mismatch (TestMapBSLToStorage comment on TestSafeResourceName function) Update the comment

DRY Compliance

Severity Location Finding Recommendation
Medium azblobsas/sas.go:240 + s3presign/presign.go:369 hmacSHA256 function is character-for-character identical in both packages Extract to pkg/common/crypto.go
Medium core/backup.go:82-91 + core/restore.go:90-98 DPA CRD detection block is verbatim duplicated (8 identical lines) Extract to helper like common.DetectDPA(ctx, client, log) bool
Medium s3presign/presign.go:94-149 + presign.go:233-270 INI profile iteration logic (~20 lines) duplicated within the same file between ParseAWSCredentials and parseSTSProfile Extract parseINIProfile(data, profile) map[string]string
Low azblobsas/sas.go + delegation.go SAS URL generation structural overlap (time computation, host/scheme, final URL assembly) Acceptable — keeping variants self-contained improves security auditability
Low azblobsas/sas.go:18 + s3presign/presign.go:19 var nowFunc = time.Now in both packages Acceptable — standard Go testing pattern, each package needs its own

Acceptable non-findings

  • parseDotenv (azblobsas) vs ParseAWSCredentials (s3presign): structurally similar but semantically different (dotenv vs INI-with-sections). Separate implementations justified.
  • uriEncode in both packages: different encoding behavior (PathEscape vs QueryEscape+SigV4 fixups). Intentional per Azure/AWS signing specs.
  • BSL fetch logic in restore.go vs orchestrator.go: different responsibilities and return types. Too trivial to extract.

SOLID Compliance

Principle Verdict Notes
SRP Mostly compliant azblobsas well-decomposed by file. Minor: s3presign/presign.go mixes credential parsing + URL generation in one file.
OCP Compliant signSnapshotURL extends behavior (Azure branch) without modifying S3 path. ParseAWSCredentialData extends credential detection without modifying ParseAWSCredentials.
LSP Fully compliant TokenProvider and STSAssumeRoler implementations (production + mocks) honor contracts.
ISP Fully compliant Both interfaces are single-method and maximally focused.
DIP Mostly compliant newTokenProvider and newSTSClient function fields on RestorePlugin are textbook DIP. Minor: direct os.Getenv calls in presign.go:167 and restore.go:390-397 bypass injection.

SOLID details

SRP findings:

  • pkg/azblobsas/ is well-decomposed: delegation.go (user delegation key), sas.go (Service SAS + credential parsing), token.go (AAD token acquisition).
  • pkg/s3presign/sts.go has one job: STS AssumeRoleWithWebIdentity. Clean separation from presigning.
  • pkg/common/credentials.go does exactly one thing: resolve a BSL credential reference with fallback.
  • Minor: s3presign/presign.go mixes credential parsing/detection with SigV4 URL generation.

DIP findings:

  • RestorePlugin depends on abstractions via function fields (newTokenProvider, newSTSClient) — wired with production implementations in NewRestorePlugin and overridden in tests.
  • var httpDo in delegation.go:16 is a weaker DIP form compared to STSClient.HTTPClient struct field in sts.go:29.
  • os.Getenv calls in presign.go:167 and restore.go:390-397 are hard dependencies on the OS environment.

Overall Verdict: PASS WITH RECOMMENDATIONS

This is a well-structured, well-tested PR that adds significant multi-cloud credential support. The architecture decisions (interfaces for testability, dependency injection via function fields, pure-stdlib STS client) are sound. Build, vet, and all tests pass.

Required Actions

None — no blocking issues.

Recommended Improvements

  1. presignAzBlobUrlpresignAzBlobURL (Go acronym convention) — restore.go:339,381
  2. Add pkg/common/credentials_test.go for ResolveCredentialRef — two branches, used in critical paths
  3. Extract duplicated hmacSHA256 to pkg/common/crypto.go — identical function in two packages
  4. Extract DPA detection helper — 8 verbatim-identical lines in backup.go and restore.go
  5. Extract parseINIProfile in s3presign/presign.go — deduplicate ~20 lines of INI iteration

"time"
)

var httpDo = http.DefaultClient.Do

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: It looks like this httpDo is used only once. Probably makes sense to use http.DefaultClient.Do directly and get rid of this variable.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — it is only called once, but the var is intentional: delegation_test.go replaces httpDo with a fake to avoid real HTTP calls (see TestGetUserDelegationKey). It's the same pattern we use with var nowFunc = time.Now in the SAS package. I can add a short comment above the var if that helps clarify the intent.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No action needed.

expiryTime := now.Add(expiry)

signedStart := now.Format("2006-01-02T15:04:05Z")
signedExpiry := expiryTime.Format("2006-01-02T15:04:05Z")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is strange to see this old date here hardcoded. Does it have any meaning or effect?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Answered above — same Go reference time layout.


body, err := xml.Marshal(keyInfoRequest{
Start: start.UTC().Format("2006-01-02T15:04:05Z"),
Expiry: expiry.UTC().Format("2006-01-02T15:04:05Z"),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is strange to see this old date here hardcoded. Does it have any meaning or effect? Is it necessary?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's Go's reference time — it's how time.Format works in Go. The magic date 2006-01-02T15:04:05Z is the layout string (Mon Jan 2 15:04:05 MST 2006), not an actual hardcoded date. The output at runtime is the real current timestamp formatted as ISO 8601.

Comment thread pkg/core/restore.go
p.log.Debugf("Pod found, skipping restore")
return velero.NewRestoreItemActionExecuteOutput(input.Item).WithoutRestore(), nil

case kind == "StatefulSet":

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For me this doesn't belong to this PR. Is this something that was forgotten in previous PRs? Or, it is related to STS/IRSA. Just curious.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is related to this PR — it was added in commit 0537002. When using the etcdSnapshot restore method, Velero would restore the etcd StatefulSet with the old (expired) snapshot URL baked into its spec. That conflicts with the fresh pre-signed URL we inject into the HostedControlPlane. Skipping the StatefulSet restore lets the HCP controller reconcile it with the correct URL. I can add a comment in the code explaining the why if you think it'd help.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. It's fine.

@mgencur

mgencur commented Jun 4, 2026

Copy link
Copy Markdown

/lgtm

@openshift-ci openshift-ci Bot added the lgtm Indicates that a PR is ready to be merged. label Jun 4, 2026
@openshift-merge-bot openshift-merge-bot Bot merged commit 5ec08a6 into openshift:main Jun 4, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. lgtm Indicates that a PR is ready to be merged. verified Signifies that the PR passed pre-merge verification criteria

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants