Lock object writing is configured the same with the state object writing (#2606)

Signed-off-by: yottta <andrei.ciobanu@opentofu.org>
This commit is contained in:
Andrei Ciobanu
2025-03-18 12:34:27 +02:00
committed by GitHub
parent 1b103f3ac4
commit 44d07cea4b
2 changed files with 80 additions and 0 deletions

View File

@@ -351,6 +351,23 @@ func (c *RemoteClient) s3Lock(info *statemgr.LockInfo) error {
IfNoneMatch: aws.String("*"),
}
if !c.skipS3Checksum {
putParams.ChecksumAlgorithm = types.ChecksumAlgorithmSha256
// There is a conflict in the aws-go-sdk-v2 that prevents it from working with many s3 compatible services
// Since we can pre-compute the hash here, we can work around it.
// ref: https://github.com/aws/aws-sdk-go-v2/issues/1689
algo := sha256.New()
algo.Write(lInfo)
sum64str := base64.StdEncoding.EncodeToString(algo.Sum(nil))
putParams.ChecksumSHA256 = &sum64str
}
if c.acl != "" {
putParams.ACL = types.ObjectCannedACL(c.acl)
}
log.Printf("[DEBUG] Uploading s3 locking object: %#v", putParams)
ctx := context.TODO()
ctx, _ = attachLoggerToContext(ctx)
_, err := c.s3Client.PutObject(ctx, putParams, s3optDisableDefaultChecksum(c.skipS3Checksum))

View File

@@ -12,6 +12,7 @@ import (
"fmt"
"io"
"net/http"
"slices"
"strings"
"testing"
"time"
@@ -900,6 +901,68 @@ func TestS3ChecksumsHeaders(t *testing.T) {
}
}
// TestS3LockingWritingHeaders is double-checking that the configuration for writing the lock object is the same
// with the state writing configuration
func TestS3LockingWritingHeaders(t *testing.T) {
ignoredValues := []string{"Amz-Sdk-Invocation-Id", "Authorization", "X-Amz-Checksum-Sha256"}
// Configured the aws config the same way it is done for the backend to ensure a similar setup as the actual main logic.
_, awsCfg, _ := awsbase.GetAwsConfig(context.Background(), &awsbase.Config{Region: "us-east-1", AccessKey: "test", SecretKey: "key"})
httpCl := &mockHttpClient{resp: &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(""))}}
s3Cl := s3.NewFromConfig(awsCfg, func(options *s3.Options) {
options.HTTPClient = httpCl
})
rc := RemoteClient{
s3Client: s3Cl,
bucketName: "test-bucket",
path: "state-file",
useLockfile: true,
}
var (
stateWritingReq, lockWritingReq *http.Request
)
// get the request from state writing
{
err := rc.Put([]byte("test"))
if err != nil {
t.Fatalf("expected to have no error writing the state object but got one: %s", err)
}
if httpCl.receivedReq == nil {
t.Fatal("request didn't reach the mock http client")
}
stateWritingReq = httpCl.receivedReq
}
// get the request from lock object writing
{
err := rc.s3Lock(&statemgr.LockInfo{Info: "test"})
if err != nil {
t.Fatalf("expected to have no error writing the lock object but got one: %s", err)
}
if httpCl.receivedReq == nil {
t.Fatal("request didn't reach the mock http client")
}
lockWritingReq = httpCl.receivedReq
}
// compare headers
for k, v := range stateWritingReq.Header {
got := lockWritingReq.Header.Values(k)
if len(got) == 0 {
t.Errorf("found a header that is missing from the request for locking: %s", k)
continue
}
// do not compare header values that are meant to be different since are generated but ensure that there are values in those
if slices.Contains(ignoredValues, k) {
if len(got[0]) == 0 {
t.Errorf("header %q from lock request is having an empty value: %#v", k, got)
}
continue
}
if !slices.Equal(got, v) {
t.Errorf("found a header %q in lock request that is having a different value from the state one\nin-state-req: %#v\nin-lock-req: %#v", k, v, got)
}
}
}
// mockHttpClient is used to test the interaction of the s3 backend with the aws-sdk.
// This is meant to be configured with a response that will be returned to the aws-sdk.
// The receivedReq is going to contain the last request received by it.