diff --git a/docs/index.md b/docs/index.md index 276da809..51aee8c5 100644 --- a/docs/index.md +++ b/docs/index.md @@ -151,7 +151,6 @@ Note: AWS specific checks must be skipped as they do not work on STACKIT. For de ### Optional -- `argus_custom_endpoint` (String, Deprecated) Custom endpoint for the Argus service - `authorization_custom_endpoint` (String) Custom endpoint for the Membership service - `cdn_custom_endpoint` (String) Custom endpoint for the CDN service - `credentials_path` (String) Path of JSON from where the credentials are read. Takes precedence over the env var `STACKIT_CREDENTIALS_PATH`. Default value is `~/.stackit/credentials.json`. diff --git a/stackit/internal/core/core.go b/stackit/internal/core/core.go index 9235da2a..293afa32 100644 --- a/stackit/internal/core/core.go +++ b/stackit/internal/core/core.go @@ -21,7 +21,6 @@ type ProviderData struct { // Deprecated: Use DefaultRegion instead Region string DefaultRegion string - ArgusCustomEndpoint string AuthorizationCustomEndpoint string CdnCustomEndpoint string DnsCustomEndpoint string diff --git a/stackit/internal/testutil/testutil.go b/stackit/internal/testutil/testutil.go index 220a79b8..849487d5 100644 --- a/stackit/internal/testutil/testutil.go +++ b/stackit/internal/testutil/testutil.go @@ -50,7 +50,6 @@ var ( // TestImageLocalFilePath is the local path to an image file used for image acceptance tests TestImageLocalFilePath = getenv("TF_ACC_TEST_IMAGE_LOCAL_FILE_PATH", "default") - ArgusCustomEndpoint = os.Getenv("TF_ACC_ARGUS_CUSTOM_ENDPOINT") CdnCustomEndpoint = os.Getenv("TF_ACC_CDN_CUSTOM_ENDPOINT") DnsCustomEndpoint = os.Getenv("TF_ACC_DNS_CUSTOM_ENDPOINT") GitCustomEndpoint = os.Getenv("TF_ACC_GIT_CUSTOM_ENDPOINT") @@ -78,22 +77,6 @@ var ( // Provider config helper functions -func ArgusProviderConfig() string { - if ArgusCustomEndpoint == "" { - return `provider "stackit" { - default_region = "eu01" - }` - } - return fmt.Sprintf(` - provider "stackit" { - argus_custom_endpoint = "%s" - }`, - ArgusCustomEndpoint, - ) -} - -// Provider config helper functions - func ObservabilityProviderConfig() string { if ObservabilityCustomEndpoint == "" { return `provider "stackit" { @@ -299,7 +282,7 @@ func RedisProviderConfig() string { } func ResourceManagerProviderConfig() string { - token := getTestProjectServiceAccountToken("") + token := GetTestProjectServiceAccountToken("") if ResourceManagerCustomEndpoint == "" || AuthorizationCustomEndpoint == "" { return fmt.Sprintf(` provider "stackit" { @@ -458,7 +441,7 @@ func ResourceNameWithDateTime(name string) string { return fmt.Sprintf("tf-acc-%s-%s", name, dateTimeTrimmed) } -func getTestProjectServiceAccountToken(path string) string { +func GetTestProjectServiceAccountToken(path string) string { var err error token, tokenSet := os.LookupEnv("TF_ACC_TEST_PROJECT_SERVICE_ACCOUNT_TOKEN") if !tokenSet || token == "" { diff --git a/stackit/provider.go b/stackit/provider.go index eae4987e..a3392bdf 100644 --- a/stackit/provider.go +++ b/stackit/provider.go @@ -115,7 +115,6 @@ type providerModel struct { // Deprecated: Use DefaultRegion instead Region types.String `tfsdk:"region"` DefaultRegion types.String `tfsdk:"default_region"` - ArgusCustomEndpoint types.String `tfsdk:"argus_custom_endpoint"` CdnCustomEndpoint types.String `tfsdk:"cdn_custom_endpoint"` DNSCustomEndpoint types.String `tfsdk:"dns_custom_endpoint"` GitCustomEndpoint types.String `tfsdk:"git_custom_endpoint"` @@ -157,7 +156,6 @@ func (p *Provider) Schema(_ context.Context, _ provider.SchemaRequest, resp *pro "service_account_email": "Service account email. It can also be set using the environment variable STACKIT_SERVICE_ACCOUNT_EMAIL. It is required if you want to use the resource manager project resource.", "region": "Region will be used as the default location for regional services. Not all services require a region, some are global", "default_region": "Region will be used as the default location for regional services. Not all services require a region, some are global", - "argus_custom_endpoint": "Custom endpoint for the Argus service", "cdn_custom_endpoint": "Custom endpoint for the CDN service", "dns_custom_endpoint": "Custom endpoint for the DNS service", "git_custom_endpoint": "Custom endpoint for the Git service", @@ -233,11 +231,6 @@ func (p *Provider) Schema(_ context.Context, _ provider.SchemaRequest, resp *pro stringvalidator.ConflictsWith(path.MatchRoot("region")), }, }, - "argus_custom_endpoint": schema.StringAttribute{ - Optional: true, - Description: descriptions["argus_custom_endpoint"], - DeprecationMessage: "Argus service has been deprecated and integration will be removed after February 26th 2025. Please use `observability_custom_endpoint` and `observability` resources instead, which offer the exact same functionality.", - }, "cdn_custom_endpoint": schema.StringAttribute{ Optional: true, Description: descriptions["cdn_custom_endpoint"], diff --git a/stackit/provider_acc_test.go b/stackit/provider_acc_test.go new file mode 100644 index 00000000..24eb81f8 --- /dev/null +++ b/stackit/provider_acc_test.go @@ -0,0 +1,259 @@ +package stackit_test + +import ( + _ "embed" + "fmt" + "os" + "path" + "regexp" + "runtime" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/testutil" +) + +//go:embed testdata/provider-credentials.tf +var providerCredentialConfig string + +//go:embed testdata/provider-invalid-attribute.tf +var providerInvalidAttribute string + +//go:embed testdata/provider-all-attributes.tf +var providerValidAttributes string + +var testConfigProviderCredentials = config.Variables{ + "project_id": config.StringVariable(testutil.ProjectId), + "name": config.StringVariable(fmt.Sprintf("tf-acc-prov%s", acctest.RandStringFromCharSet(3, acctest.CharSetAlphaNum))), +} + +// Helper function to obtain the home directory on different systems. +// Based on os.UserHomeDir(). +func getHomeEnvVariableName() string { + env := "HOME" + switch runtime.GOOS { + case "windows": + env = "USERPROFILE" + case "plan9": + env = "home" + } + return env +} + +// create temporary home and initialize the credentials file as well +func createTemporaryHome(createValidCredentialsFile bool, t *testing.T) string { + // create a temporary file + tempHome, err := os.MkdirTemp("", "tempHome") + if err != nil { + t.Fatalf("Failed to create temporary home directory: %v", err) + } + + // create credentials file in temp directory + stackitFolder := path.Join(tempHome, ".stackit") + if err := os.Mkdir(stackitFolder, 0o750); err != nil { + t.Fatalf("Failed to create stackit folder: %v", err) + } + + filePath := path.Join(stackitFolder, "credentials.json") + file, err := os.Create(filePath) + if err != nil { + t.Fatalf("Failed to create credentials file: %v", err) + } + defer func() { + if err := file.Close(); err != nil { + t.Fatalf("Error while closing the file: %v", err) + } + }() + + // Define content, default = invalid token + token := "foo_token" + if createValidCredentialsFile { + token = testutil.GetTestProjectServiceAccountToken("") + } + content := fmt.Sprintf(` + { + "STACKIT_SERVICE_ACCOUNT_TOKEN": "%s" + }`, token) + + if _, err = file.WriteString(content); err != nil { + t.Fatalf("Error writing to file: %v", err) + } + + return tempHome +} + +// Function to overwrite the home folder +func setTemporaryHome(tempHomePath string) { + env := getHomeEnvVariableName() + if err := os.Setenv(env, tempHomePath); err != nil { + fmt.Printf("Error setting temporary home directory %v", err) + } +} + +// cleanup the temporary home and reset the environment variable +func cleanupTemporaryHome(tempHomePath string, t *testing.T) { + if err := os.RemoveAll(tempHomePath); err != nil { + t.Fatalf("Error cleaning up temporary folder: %v", err) + } + originalHomeDir, err := os.UserHomeDir() + if err != nil { + t.Fatalf("Failed to restore home directory back to normal: %v", err) + } + // revert back to original home folder + env := getHomeEnvVariableName() + if err := os.Setenv(env, originalHomeDir); err != nil { + fmt.Printf("Error resetting temporary home directory %v", err) + } +} + +func getServiceAccountToken() (string, error) { + token, set := os.LookupEnv("TF_ACC_TEST_PROJECT_SERVICE_ACCOUNT_TOKEN") + if !set || token == "" { + return "", fmt.Errorf("Token not set, please set TF_ACC_TEST_PROJECT_SERVICE_ACCOUNT_TOKEN to a valid token to perform tests") + } + return token, nil +} + +func TestAccEnvVarTokenValid(t *testing.T) { + // Check if acceptance tests should be run + if v := os.Getenv(resource.EnvTfAcc); v == "" { + t.Skipf( + "Acceptance tests skipped unless env '%s' set", + resource.EnvTfAcc) + return + } + + token, err := getServiceAccountToken() + if err != nil { + t.Fatalf("Can't get token: %v", err) + } + + t.Setenv("STACKIT_CREDENTIALS_PATH", "") + t.Setenv("STACKIT_SERVICE_ACCOUNT_TOKEN", token) + tempHomeFolder := createTemporaryHome(false, t) + defer cleanupTemporaryHome(tempHomeFolder, t) + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + PreConfig: func() { setTemporaryHome(tempHomeFolder) }, + ConfigVariables: testConfigProviderCredentials, + Config: providerCredentialConfig, + }, + }, + }) +} + +func TestAccEnvVarTokenInvalid(t *testing.T) { + t.Setenv("STACKIT_CREDENTIALS_PATH", "") + t.Setenv("STACKIT_SERVICE_ACCOUNT_TOKEN", "foo") + tempHomeFolder := createTemporaryHome(false, t) + defer cleanupTemporaryHome(tempHomeFolder, t) + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + PreConfig: func() { setTemporaryHome(tempHomeFolder) }, + ConfigVariables: testConfigProviderCredentials, + Config: providerCredentialConfig, + ExpectError: regexp.MustCompile(`undefined response type, status code 401`), + }, + }, + }) +} + +func TestAccCredentialsFileValid(t *testing.T) { + t.Setenv("STACKIT_CREDENTIALS_PATH", "") + t.Setenv("STACKIT_SERVICE_ACCOUNT_TOKEN", "") + tempHomeFolder := createTemporaryHome(true, t) + defer cleanupTemporaryHome(tempHomeFolder, t) + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + PreConfig: func() { setTemporaryHome(tempHomeFolder) }, + ConfigVariables: testConfigProviderCredentials, + Config: providerCredentialConfig, + }, + }, + }) +} + +func TestAccCredentialsFileInvalid(t *testing.T) { + t.Setenv("STACKIT_CREDENTIALS_PATH", "") + t.Setenv("STACKIT_SERVICE_ACCOUNT_TOKEN", "") + tempHomeFolder := createTemporaryHome(false, t) + defer cleanupTemporaryHome(tempHomeFolder, t) + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + PreConfig: func() { setTemporaryHome(tempHomeFolder) }, + ConfigVariables: testConfigProviderCredentials, + Config: providerCredentialConfig, + ExpectError: regexp.MustCompile(`Jwt is not in(\r\n|\r|\n)the form of Header.Payload.Signature`), + }, + }, + }) +} + +func TestAccProviderConfigureValidValues(t *testing.T) { + // Check if acceptance tests should be run + if v := os.Getenv(resource.EnvTfAcc); v == "" { + t.Skipf( + "Acceptance tests skipped unless env '%s' set", + resource.EnvTfAcc) + return + } + // use service account token for these tests + token, err := getServiceAccountToken() + if err != nil { + t.Fatalf("Can't get token: %v", err) + } + + t.Setenv("STACKIT_CREDENTIALS_PATH", "") + t.Setenv("STACKIT_SERVICE_ACCOUNT_TOKEN", token) + tempHomeFolder := createTemporaryHome(true, t) + defer cleanupTemporaryHome(tempHomeFolder, t) + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { // valid provider attributes + ConfigVariables: testConfigProviderCredentials, + Config: providerValidAttributes, + }, + }, + }) +} + +func TestAccProviderConfigureAnInvalidValue(t *testing.T) { + // Check if acceptance tests should be run + if v := os.Getenv(resource.EnvTfAcc); v == "" { + t.Skipf( + "Acceptance tests skipped unless env '%s' set", + resource.EnvTfAcc) + return + } + // use service account token for these tests + token, err := getServiceAccountToken() + if err != nil { + t.Fatalf("Can't get token: %v", err) + } + + t.Setenv("STACKIT_CREDENTIALS_PATH", "") + t.Setenv("STACKIT_SERVICE_ACCOUNT_TOKEN", token) + tempHomeFolder := createTemporaryHome(true, t) + defer cleanupTemporaryHome(tempHomeFolder, t) + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { // invalid test attribute should throw an error + ConfigVariables: testConfigProviderCredentials, + Config: providerInvalidAttribute, + ExpectError: regexp.MustCompile(`An argument named "test" is not expected here\.`), + }, + }, + }) +} diff --git a/stackit/testdata/provider-all-attributes.tf b/stackit/testdata/provider-all-attributes.tf new file mode 100644 index 00000000..895ea245 --- /dev/null +++ b/stackit/testdata/provider-all-attributes.tf @@ -0,0 +1,41 @@ +variable "project_id" {} +variable "name" {} + +provider "stackit" { + default_region = "eu01" + credentials_path = "~/.stackit/credentials.json" + service_account_token = "" + service_account_key_path = "" + service_account_key = "" + private_key_path = "" + private_key = "" + service_account_email = "abc@abc.de" + cdn_custom_endpoint = "https://cdn.api.eu01.stackit.cloud" + dns_custom_endpoint = "https://dns.api.stackit.cloud" + git_custom_endpoint = "https://git.api.stackit.cloud" + iaas_custom_endpoint = "https://iaas.api.stackit.cloud" + mongodbflex_custom_endpoint = "https://mongodbflex.api.stackit.cloud" + modelserving_custom_endpoint = "https://modelserving.api.stackit.cloud" + loadbalancer_custom_endpoint = "https://load-balancer.api.stackit.cloud" + mariadb_custom_endpoint = "https://mariadb.api.stackit.cloud" + authorization_custom_endpoint = "https://authorization.api.stackit.cloud" + objectstorage_custom_endpoint = "https://objectstorage.api.stackit.cloud" + observability_custom_endpoint = "https://observability.api.stackit.cloud" + opensearch_custom_endpoint = "https://opensearch.api.stackit.cloud" + postgresflex_custom_endpoint = "https://postgresflex.api.stackit.cloud" + redis_custom_endpoint = "https://redis.api.stackit.cloud" + server_backup_custom_endpoint = "https://server-backup.api.stackit.cloud" + server_update_custom_endpoint = "https://server-update.api.stackit.cloud" + service_account_custom_endpoint = "https://service-account.api.stackit.cloud" + resourcemanager_custom_endpoint = "https://resourcemanager.api.stackit.cloud" + sqlserverflex_custom_endpoint = "https://sqlserverflex.api.stackit.cloud" + ske_custom_endpoint = "https://ske.api.stackit.cloud" + service_enablement_custom_endpoint = "https://service-enablement.api.stackit.cloud" + token_custom_endpoint = "https://token.api.stackit.cloud" + enable_beta_resources = "true" +} + +resource "stackit_network" "network" { + name = var.name + project_id = var.project_id +} diff --git a/stackit/testdata/provider-credentials.tf b/stackit/testdata/provider-credentials.tf new file mode 100644 index 00000000..32c1d863 --- /dev/null +++ b/stackit/testdata/provider-credentials.tf @@ -0,0 +1,10 @@ +variable "project_id" {} +variable "name" {} + +provider "stackit" { +} + +resource "stackit_network" "network" { + name = var.name + project_id = var.project_id +} \ No newline at end of file diff --git a/stackit/testdata/provider-invalid-attribute.tf b/stackit/testdata/provider-invalid-attribute.tf new file mode 100644 index 00000000..d5a11a2c --- /dev/null +++ b/stackit/testdata/provider-invalid-attribute.tf @@ -0,0 +1,11 @@ +variable "project_id" {} +variable "name" {} + +provider "stackit" { + test = "test" +} + +resource "stackit_network" "network" { + name = var.name + project_id = var.project_id +} \ No newline at end of file