Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/v1.15/ENHANCEMENTS-20260226-102105.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: ENHANCEMENTS
body: 'backend/s3: Improved performance when using workspaces by replacing workspace listing with a direct state file check'
time: 2026-02-26T10:21:05.578904Z
custom:
Issue: "33137"
16 changes: 4 additions & 12 deletions internal/backend/remote-state/s3/backend_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,18 +193,10 @@ func (b *Backend) StateMgr(name string) (statemgr.Full, tfdiags.Diagnostics) {
// If we need to force-unlock, but for some reason the state no longer
// exists, the user will have to use aws tools to manually fix the
// situation.
existing, wDiags := b.Workspaces()
diags = diags.Append(wDiags)
if wDiags.HasErrors() {
return nil, diags
}

exists := false
for _, s := range existing {
if s == name {
exists = true
break
}
ctx := context.TODO()
exists, err := client.Exists(ctx)
if err != nil {
return nil, diags.Append(err)
}

// We need to create the object so it's listed by States.
Expand Down
25 changes: 25 additions & 0 deletions internal/backend/remote-state/s3/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,31 @@ func (c *RemoteClient) get(ctx context.Context) (*remote.Payload, error) {
return payload, nil
}

func (c *RemoteClient) Exists(ctx context.Context) (bool, error) {
headInput := &s3.HeadObjectInput{
Bucket: aws.String(c.bucketName),
Key: aws.String(c.path),
}
if c.serverSideEncryption && c.customerEncryptionKey != nil {
headInput.SSECustomerKey = aws.String(base64.StdEncoding.EncodeToString(c.customerEncryptionKey))
headInput.SSECustomerAlgorithm = aws.String(s3EncryptionAlgorithm)
headInput.SSECustomerKeyMD5 = aws.String(c.getSSECustomerKeyMD5())
}

_, err := c.s3Client.HeadObject(ctx, headInput)
if err != nil {
switch {
case IsA[*s3types.NoSuchBucket](err):
return false, fmt.Errorf(errS3NoSuchBucket, c.bucketName, err)
case IsA[*s3types.NotFound](err):
return false, nil
}
return false, fmt.Errorf("Unable to access object %q in S3 bucket %q: %w", c.path, c.bucketName, err)
}

return true, nil
}

func (c *RemoteClient) Put(data []byte) tfdiags.Diagnostics {
var diags tfdiags.Diagnostics
return diags.Append(c.put(data))
Expand Down