diff --git a/internal/backend/remote-state/s3/client.go b/internal/backend/remote-state/s3/client.go index e56703bef908..5014d9236170 100644 --- a/internal/backend/remote-state/s3/client.go +++ b/internal/backend/remote-state/s3/client.go @@ -346,18 +346,21 @@ func (c *RemoteClient) Lock(info *statemgr.LockInfo) (string, error) { // exist, the operation will succeed, acquiring the lock. If the lock file already exists, the operation // will fail due to a conditional write, indicating that the lock is already held by another Terraform client. func (c *RemoteClient) lockWithFile(ctx context.Context, info *statemgr.LockInfo, log hclog.Logger) error { - lockFileJson, err := json.Marshal(info) + data, err := json.Marshal(info) if err != nil { return err } input := &s3.PutObjectInput{ ContentType: aws.String("application/json"), - Body: bytes.NewReader(lockFileJson), + Body: bytes.NewReader(data), Bucket: aws.String(c.bucketName), Key: aws.String(c.lockFilePath), IfNoneMatch: aws.String("*"), } + if !c.skipS3Checksum { + input.ChecksumAlgorithm = s3types.ChecksumAlgorithmSha256 + } if c.serverSideEncryption { if c.kmsKeyID != "" { @@ -378,7 +381,8 @@ func (c *RemoteClient) lockWithFile(ctx context.Context, info *statemgr.LockInfo log.Debug("Uploading lock file") - _, err = c.s3Client.PutObject(ctx, input) + uploader := manager.NewUploader(c.s3Client, func(u *manager.Uploader) {}) + _, err = uploader.Upload(ctx, input) if err != nil { // Attempt to retrieve lock info from the file, and merge errors if it fails. lockInfo, infoErr := c.getLockInfoWithFile(ctx) diff --git a/internal/backend/remote-state/s3/client_test.go b/internal/backend/remote-state/s3/client_test.go index a1c7722ea1cf..c08729c72fcc 100644 --- a/internal/backend/remote-state/s3/client_test.go +++ b/internal/backend/remote-state/s3/client_test.go @@ -382,6 +382,46 @@ func TestRemoteClientPutLargeUploadWithObjectLock(t *testing.T) { } } +func TestRemoteClientObjectLockAndLockFile(t *testing.T) { + testACC(t) + objectLockPreCheck(t) + + ctx := context.TODO() + + bucketName := fmt.Sprintf("terraform-remote-s3-test-%x", time.Now().Unix()) + keyName := "testState" + + b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{ + "bucket": bucketName, + "key": keyName, + "use_lockfile": true, + })).(*Backend) + + createS3Bucket(ctx, t, b.s3Client, bucketName, b.awsConfig.Region, + s3BucketWithVersioning, + s3BucketWithObjectLock(s3types.ObjectLockRetentionModeCompliance), + ) + defer deleteS3Bucket(ctx, t, b.s3Client, bucketName, b.awsConfig.Region) + + s1, err := b.StateMgr(backend.DefaultStateName) + if err != nil { + t.Fatal(err) + } + client := s1.(*remote.State).Client + + var state bytes.Buffer + dataW := io.LimitReader(neverEnding('x'), manager.DefaultUploadPartSize) + _, err = state.ReadFrom(dataW) + if err != nil { + t.Fatalf("writing dummy data: %s", err) + } + + err = client.Put(state.Bytes()) + if err != nil { + t.Fatalf("putting data: %s", err) + } +} + type neverEnding byte func (b neverEnding) Read(p []byte) (n int, err error) {