mirror of
https://github.com/opentffoundation/opentf.git
synced 2025-12-21 18:56:57 -05:00
Due to some past confusion about the purpose of this package, it has grown to include a confusing mix of currently-viable code and legacy support code from the move to HCL 2. This has in turn caused confusion about which parts of this package _should_ be used for new code. To help clarify that distinction we'll move the legacy support code into a package under the "legacy" directory, which is also where most of its callers live. There are unfortunately still some callers to these outside of the legacy tree, but the vast majority are either old tests written before HCL 2 adoption or helper code used only by those tests. The one dubious exception is the use in ResourceInstanceObjectSrc.Decode, which makes a best effort to shim flatmap as a concession to the fact that not all state-loading codepaths are able to run the provider state upgrade function that would normally be responsible for the flatmap-to-JSON conversion, which is explained in a new comment inline. Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
356 lines
11 KiB
Go
356 lines
11 KiB
Go
// Copyright (c) The OpenTofu Authors
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
// Copyright (c) 2023 HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package oss
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"strings"
|
|
|
|
"github.com/aliyun/aliyun-oss-go-sdk/oss"
|
|
"github.com/aliyun/aliyun-tablestore-go-sdk/tablestore"
|
|
"github.com/opentofu/opentofu/internal/backend"
|
|
"github.com/opentofu/opentofu/internal/encryption"
|
|
"github.com/opentofu/opentofu/internal/legacy/hcl2shim"
|
|
)
|
|
|
|
// verify that we are doing ACC tests or the OSS tests specifically
|
|
func testACC(t *testing.T) {
|
|
t.Helper()
|
|
skip := os.Getenv("TF_ACC") == "" && os.Getenv("TF_OSS_TEST") == ""
|
|
if skip {
|
|
t.Log("oss backend tests require setting TF_ACC or TF_OSS_TEST")
|
|
t.Skip()
|
|
}
|
|
if skip {
|
|
t.Fatal("oss backend tests require setting ALICLOUD_ACCESS_KEY or ALICLOUD_ACCESS_KEY_ID")
|
|
}
|
|
t.Setenv("ALICLOUD_REGION", "cn-beijing")
|
|
}
|
|
|
|
func TestBackend_impl(t *testing.T) {
|
|
var _ backend.Backend = new(Backend)
|
|
}
|
|
|
|
func TestBackendConfig(t *testing.T) {
|
|
testACC(t)
|
|
config := map[string]interface{}{
|
|
"region": "cn-beijing",
|
|
"bucket": "terraform-backend-oss-test",
|
|
"prefix": "mystate",
|
|
"key": "first.tfstate",
|
|
"tablestore_endpoint": "https://terraformstate.cn-beijing.ots.aliyuncs.com",
|
|
"tablestore_table": "TableStore",
|
|
}
|
|
|
|
b := backend.TestBackendConfig(t, New(encryption.StateEncryptionDisabled()), backend.TestWrapConfig(config)).(*Backend)
|
|
|
|
if !strings.HasPrefix(b.ossClient.Config.Endpoint, "https://oss-cn-beijing") {
|
|
t.Fatalf("Incorrect region was provided")
|
|
}
|
|
if b.bucketName != "terraform-backend-oss-test" {
|
|
t.Fatalf("Incorrect bucketName was provided")
|
|
}
|
|
if b.statePrefix != "mystate" {
|
|
t.Fatalf("Incorrect state file path was provided")
|
|
}
|
|
if b.stateKey != "first.tfstate" {
|
|
t.Fatalf("Incorrect keyName was provided")
|
|
}
|
|
|
|
if b.ossClient.Config.AccessKeyID == "" {
|
|
t.Fatalf("No Access Key Id was provided")
|
|
}
|
|
if b.ossClient.Config.AccessKeySecret == "" {
|
|
t.Fatalf("No Secret Access Key was provided")
|
|
}
|
|
}
|
|
|
|
func TestBackendConfigWorkSpace(t *testing.T) {
|
|
testACC(t)
|
|
bucketName := fmt.Sprintf("terraform-backend-oss-test-%d", rand.Intn(1000))
|
|
config := map[string]interface{}{
|
|
"region": "cn-beijing",
|
|
"bucket": bucketName,
|
|
"prefix": "mystate",
|
|
"key": "first.tfstate",
|
|
"tablestore_endpoint": "https://terraformstate.cn-beijing.ots.aliyuncs.com",
|
|
"tablestore_table": "TableStore",
|
|
}
|
|
|
|
b := backend.TestBackendConfig(t, New(encryption.StateEncryptionDisabled()), backend.TestWrapConfig(config)).(*Backend)
|
|
createOSSBucket(t, b.ossClient, bucketName)
|
|
defer deleteOSSBucket(t, b.ossClient, bucketName)
|
|
if _, err := b.Workspaces(t.Context()); err != nil {
|
|
t.Fatal(err.Error())
|
|
}
|
|
if !strings.HasPrefix(b.ossClient.Config.Endpoint, "https://oss-cn-beijing") {
|
|
t.Fatalf("Incorrect region was provided")
|
|
}
|
|
if b.bucketName != bucketName {
|
|
t.Fatalf("Incorrect bucketName was provided")
|
|
}
|
|
if b.statePrefix != "mystate" {
|
|
t.Fatalf("Incorrect state file path was provided")
|
|
}
|
|
if b.stateKey != "first.tfstate" {
|
|
t.Fatalf("Incorrect keyName was provided")
|
|
}
|
|
|
|
if b.ossClient.Config.AccessKeyID == "" {
|
|
t.Fatalf("No Access Key Id was provided")
|
|
}
|
|
if b.ossClient.Config.AccessKeySecret == "" {
|
|
t.Fatalf("No Secret Access Key was provided")
|
|
}
|
|
}
|
|
|
|
func TestBackendConfigProfile(t *testing.T) {
|
|
testACC(t)
|
|
config := map[string]interface{}{
|
|
"region": "cn-beijing",
|
|
"bucket": "terraform-backend-oss-test",
|
|
"prefix": "mystate",
|
|
"key": "first.tfstate",
|
|
"tablestore_endpoint": "https://terraformstate.cn-beijing.ots.aliyuncs.com",
|
|
"tablestore_table": "TableStore",
|
|
"profile": "default",
|
|
}
|
|
|
|
b := backend.TestBackendConfig(t, New(encryption.StateEncryptionDisabled()), backend.TestWrapConfig(config)).(*Backend)
|
|
|
|
if !strings.HasPrefix(b.ossClient.Config.Endpoint, "https://oss-cn-beijing") {
|
|
t.Fatalf("Incorrect region was provided")
|
|
}
|
|
if b.bucketName != "terraform-backend-oss-test" {
|
|
t.Fatalf("Incorrect bucketName was provided")
|
|
}
|
|
if b.statePrefix != "mystate" {
|
|
t.Fatalf("Incorrect state file path was provided")
|
|
}
|
|
if b.stateKey != "first.tfstate" {
|
|
t.Fatalf("Incorrect keyName was provided")
|
|
}
|
|
|
|
if b.ossClient.Config.AccessKeyID == "" {
|
|
t.Fatalf("No Access Key Id was provided")
|
|
}
|
|
if b.ossClient.Config.AccessKeySecret == "" {
|
|
t.Fatalf("No Secret Access Key was provided")
|
|
}
|
|
}
|
|
|
|
func TestBackendConfig_invalidKey(t *testing.T) {
|
|
testACC(t)
|
|
cfg := hcl2shim.HCL2ValueFromConfigValue(map[string]interface{}{
|
|
"region": "cn-beijing",
|
|
"bucket": "terraform-backend-oss-test",
|
|
"prefix": "/leading-slash",
|
|
"name": "/test.tfstate",
|
|
"tablestore_endpoint": "https://terraformstate.cn-beijing.ots.aliyuncs.com",
|
|
"tablestore_table": "TableStore",
|
|
})
|
|
|
|
_, results := New(encryption.StateEncryptionDisabled()).PrepareConfig(cfg)
|
|
if !results.HasErrors() {
|
|
t.Fatal("expected config validation error")
|
|
}
|
|
}
|
|
|
|
func TestBackend(t *testing.T) {
|
|
testACC(t)
|
|
|
|
bucketName := fmt.Sprintf("terraform-remote-oss-test-%x", time.Now().Unix())
|
|
statePrefix := "multi/level/path/"
|
|
|
|
b1 := backend.TestBackendConfig(t, New(encryption.StateEncryptionDisabled()), backend.TestWrapConfig(map[string]interface{}{
|
|
"bucket": bucketName,
|
|
"prefix": statePrefix,
|
|
})).(*Backend)
|
|
|
|
b2 := backend.TestBackendConfig(t, New(encryption.StateEncryptionDisabled()), backend.TestWrapConfig(map[string]interface{}{
|
|
"bucket": bucketName,
|
|
"prefix": statePrefix,
|
|
})).(*Backend)
|
|
|
|
createOSSBucket(t, b1.ossClient, bucketName)
|
|
defer deleteOSSBucket(t, b1.ossClient, bucketName)
|
|
|
|
backend.TestBackendStates(t, b1)
|
|
backend.TestBackendStateLocks(t, b1, b2)
|
|
backend.TestBackendStateForceUnlock(t, b1, b2)
|
|
}
|
|
|
|
func createOSSBucket(t *testing.T, ossClient *oss.Client, bucketName string) {
|
|
// Be clear about what we're doing in case the user needs to clean this up later.
|
|
if err := ossClient.CreateBucket(bucketName); err != nil {
|
|
t.Fatal("failed to create test OSS bucket:", err)
|
|
}
|
|
}
|
|
|
|
func deleteOSSBucket(t *testing.T, ossClient *oss.Client, bucketName string) {
|
|
warning := "WARNING: Failed to delete the test OSS bucket. It may have been left in your Alibaba Cloud account and may incur storage charges. (error was %s)"
|
|
|
|
// first we have to get rid of the env objects, or we can't delete the bucket
|
|
bucket, err := ossClient.Bucket(bucketName)
|
|
if err != nil {
|
|
t.Fatal("Error getting bucket:", err)
|
|
return
|
|
}
|
|
objects, err := bucket.ListObjects()
|
|
if err != nil {
|
|
t.Logf(warning, err)
|
|
return
|
|
}
|
|
for _, obj := range objects.Objects {
|
|
if err := bucket.DeleteObject(obj.Key); err != nil {
|
|
// this will need cleanup no matter what, so just warn and exit
|
|
t.Logf(warning, err)
|
|
return
|
|
}
|
|
}
|
|
|
|
if err := ossClient.DeleteBucket(bucketName); err != nil {
|
|
t.Logf(warning, err)
|
|
}
|
|
}
|
|
|
|
// create the tablestore table, and wait until we can query it.
|
|
func createTablestoreTable(t *testing.T, otsClient *tablestore.TableStoreClient, tableName string) {
|
|
tableMeta := new(tablestore.TableMeta)
|
|
tableMeta.TableName = tableName
|
|
tableMeta.AddPrimaryKeyColumn(pkName, tablestore.PrimaryKeyType_STRING)
|
|
|
|
tableOption := new(tablestore.TableOption)
|
|
tableOption.TimeToAlive = -1
|
|
tableOption.MaxVersion = 1
|
|
|
|
reservedThroughput := new(tablestore.ReservedThroughput)
|
|
|
|
_, err := otsClient.CreateTable(&tablestore.CreateTableRequest{
|
|
TableMeta: tableMeta,
|
|
TableOption: tableOption,
|
|
ReservedThroughput: reservedThroughput,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func deleteTablestoreTable(t *testing.T, otsClient *tablestore.TableStoreClient, tableName string) {
|
|
params := &tablestore.DeleteTableRequest{
|
|
TableName: tableName,
|
|
}
|
|
_, err := otsClient.DeleteTable(params)
|
|
if err != nil {
|
|
t.Logf("WARNING: Failed to delete the test TableStore table %q. It has been left in your Alibaba Cloud account and may incur charges. (error was %s)", tableName, err)
|
|
}
|
|
}
|
|
|
|
func TestGetHttpProxyUrl(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
rawUrl string
|
|
httpProxy string
|
|
httpsProxy string
|
|
noProxy string
|
|
expectedProxyURL string
|
|
}{
|
|
{
|
|
name: "should set proxy using http_proxy environment variable",
|
|
rawUrl: "http://example.com",
|
|
httpProxy: "http://foo.bar:3128",
|
|
httpsProxy: "https://secure.example.com",
|
|
noProxy: "",
|
|
expectedProxyURL: "http://foo.bar:3128",
|
|
},
|
|
{
|
|
name: "should set proxy using http_proxy environment variable",
|
|
rawUrl: "http://example.com",
|
|
httpProxy: "http://foo.barr",
|
|
httpsProxy: "https://secure.example.com",
|
|
noProxy: "",
|
|
expectedProxyURL: "http://foo.barr",
|
|
},
|
|
{
|
|
name: "should set proxy using https_proxy environment variable",
|
|
rawUrl: "https://secure.example.com",
|
|
httpProxy: "http://foo.bar",
|
|
httpsProxy: "https://foo.bar.com:3128",
|
|
noProxy: "",
|
|
expectedProxyURL: "https://foo.bar.com:3128",
|
|
},
|
|
{
|
|
name: "should set proxy using https_proxy environment variable",
|
|
rawUrl: "https://secure.example.com",
|
|
httpProxy: "",
|
|
httpsProxy: "http://foo.baz",
|
|
noProxy: "",
|
|
expectedProxyURL: "http://foo.baz",
|
|
},
|
|
{
|
|
name: "should not set http proxy if NO_PROXY contains the host",
|
|
rawUrl: "http://example.internal",
|
|
httpProxy: "http://foo.bar:3128",
|
|
httpsProxy: "",
|
|
noProxy: "example.internal",
|
|
expectedProxyURL: "",
|
|
},
|
|
{
|
|
name: "should not set HTTP proxy when NO_PROXY matches the domain with suffix",
|
|
rawUrl: "http://qqu.example.internal",
|
|
httpProxy: "http://foo.bar:3128",
|
|
httpsProxy: "",
|
|
noProxy: ".example.internal",
|
|
expectedProxyURL: "",
|
|
},
|
|
{
|
|
name: "should not set https proxy if NO_PROXY contains the host",
|
|
rawUrl: "https://secure.internal",
|
|
httpProxy: "",
|
|
httpsProxy: "https://foo.baz:3128",
|
|
noProxy: "secure.internal",
|
|
expectedProxyURL: "",
|
|
},
|
|
{
|
|
name: "should not set https proxy if NO_PROXY matches the domain with suffix",
|
|
rawUrl: "https://ss.qcsc.secure.internal",
|
|
httpProxy: "",
|
|
httpsProxy: "https://foo.baz:3128",
|
|
noProxy: ".secure.internal",
|
|
expectedProxyURL: "",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Set environment variables
|
|
t.Setenv("HTTP_PROXY", tt.httpProxy)
|
|
t.Setenv("HTTPS_PROXY", tt.httpsProxy)
|
|
t.Setenv("NO_PROXY", tt.noProxy)
|
|
|
|
proxyUrl, err := getHttpProxyUrl(tt.rawUrl)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
|
|
if tt.expectedProxyURL == "" {
|
|
if proxyUrl != nil {
|
|
t.Fatalf("unexpected proxy URL, want nil, got: %s", proxyUrl)
|
|
}
|
|
} else {
|
|
if tt.expectedProxyURL != proxyUrl.String() {
|
|
t.Fatalf("unexpected proxy URL, want: %s, got: %s", tt.expectedProxyURL, proxyUrl.String())
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|