feat(scf): Add STACKIT Cloud Foundry (#991)
* onboard STACKIT Cloud Foundry resources/datasource
This commit is contained in:
parent
fcc7a99488
commit
a8e874699f
32 changed files with 3700 additions and 0 deletions
43
docs/data-sources/scf_organization.md
Normal file
43
docs/data-sources/scf_organization.md
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
---
|
||||
# generated by https://github.com/hashicorp/terraform-plugin-docs
|
||||
page_title: "stackit_scf_organization Data Source - stackit"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
STACKIT Cloud Foundry organization datasource schema. Must have a region specified in the provider configuration.
|
||||
---
|
||||
|
||||
# stackit_scf_organization (Data Source)
|
||||
|
||||
STACKIT Cloud Foundry organization datasource schema. Must have a `region` specified in the provider configuration.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```terraform
|
||||
data "stackit_scf_organization" "example" {
|
||||
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
org_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
||||
### Required
|
||||
|
||||
- `org_id` (String) The ID of the Cloud Foundry Organization
|
||||
- `project_id` (String) The ID of the project associated with the organization
|
||||
|
||||
### Optional
|
||||
|
||||
- `region` (String) The resource region. If not defined, the provider region is used
|
||||
|
||||
### Read-Only
|
||||
|
||||
- `created_at` (String) The time when the organization was created
|
||||
- `id` (String) Terraform's internal resource ID, structured as "`project_id`,`region`,`org_id`".
|
||||
- `name` (String) The name of the organization
|
||||
- `platform_id` (String) The ID of the platform associated with the organization
|
||||
- `quota_id` (String) The ID of the quota associated with the organization
|
||||
- `status` (String) The status of the organization (e.g., deleting, delete_failed)
|
||||
- `suspended` (Boolean) A boolean indicating whether the organization is suspended
|
||||
- `updated_at` (String) The time when the organization was last updated
|
||||
41
docs/data-sources/scf_organization_manager.md
Normal file
41
docs/data-sources/scf_organization_manager.md
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
---
|
||||
# generated by https://github.com/hashicorp/terraform-plugin-docs
|
||||
page_title: "stackit_scf_organization_manager Data Source - stackit"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
STACKIT Cloud Foundry organization manager datasource schema.
|
||||
---
|
||||
|
||||
# stackit_scf_organization_manager (Data Source)
|
||||
|
||||
STACKIT Cloud Foundry organization manager datasource schema.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```terraform
|
||||
data "stackit_scf_organization_manager" "example" {
|
||||
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
org_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
||||
### Required
|
||||
|
||||
- `org_id` (String) The ID of the Cloud Foundry Organization
|
||||
- `project_id` (String) The ID of the project associated with the organization of the organization manager
|
||||
|
||||
### Optional
|
||||
|
||||
- `region` (String) The region where the organization of the organization manager is located. If not defined, the provider region is used
|
||||
|
||||
### Read-Only
|
||||
|
||||
- `created_at` (String) The time when the organization manager was created
|
||||
- `id` (String) Terraform's internal resource ID, structured as "`project_id`,`region`,`org_id`,`user_id`".
|
||||
- `platform_id` (String) The ID of the platform associated with the organization of the organization manager
|
||||
- `updated_at` (String) The time when the organization manager was last updated
|
||||
- `user_id` (String) The ID of the organization manager user
|
||||
- `username` (String) An auto-generated organization manager user name
|
||||
40
docs/data-sources/scf_platform.md
Normal file
40
docs/data-sources/scf_platform.md
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
---
|
||||
# generated by https://github.com/hashicorp/terraform-plugin-docs
|
||||
page_title: "stackit_scf_platform Data Source - stackit"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
STACKIT Cloud Foundry Platform datasource schema.
|
||||
---
|
||||
|
||||
# stackit_scf_platform (Data Source)
|
||||
|
||||
STACKIT Cloud Foundry Platform datasource schema.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```terraform
|
||||
data "stackit_scf_platform" "example" {
|
||||
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
platform_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
||||
### Required
|
||||
|
||||
- `platform_id` (String) The unique id of the platform
|
||||
- `project_id` (String) The ID of the project associated with the platform
|
||||
|
||||
### Optional
|
||||
|
||||
- `region` (String) The region where the platform is located. If not defined, the provider region is used
|
||||
|
||||
### Read-Only
|
||||
|
||||
- `api_url` (String) The CF API Url of the platform
|
||||
- `console_url` (String) The Stratos URL of the platform
|
||||
- `display_name` (String) The name of the platform
|
||||
- `id` (String) Terraform's internal resource ID, structured as "`project_id`,`region`,`platform_id`".
|
||||
- `system_id` (String) The ID of the platform System
|
||||
248
docs/guides/scf_cloudfoundry.md
Normal file
248
docs/guides/scf_cloudfoundry.md
Normal file
|
|
@ -0,0 +1,248 @@
|
|||
# How to Provisioning Cloud Foundry using Terrform
|
||||
|
||||
## Objective
|
||||
|
||||
This tutorial demonstrates how to provision Cloud Foundry resources by
|
||||
integrating the STACKIT Terraform provider with the Cloud Foundry Terraform
|
||||
provider. The STACKIT Terraform provider will create a managed Cloud Foundry
|
||||
organization and set up a technical "org manager" user with
|
||||
`organization_manager` permissions. These credentials, along with the Cloud
|
||||
Foundry API URL (retrieved dynamically from a platform data resource), are
|
||||
passed to the Cloud Foundry Terraform provider to manage resources within the
|
||||
new organization.
|
||||
|
||||
### Output
|
||||
|
||||
This configuration creates a Cloud Foundry organization, mirroring the structure
|
||||
created via the portal. It sets up three distinct spaces: `dev`, `qa`, and
|
||||
`prod`. The configuration assigns, a specified user the `organization_manager`
|
||||
and `organization_user` roles at the organization level, and the
|
||||
`space_developer` role in each space.
|
||||
|
||||
### Scope
|
||||
|
||||
This tutorial covers the interaction between the STACKIT Terraform provider and
|
||||
the Cloud Foundry Terraform provider. It assumes you are familiar with:
|
||||
|
||||
- Setting up a STACKIT project and configuring the STACKIT Terraform provider
|
||||
with a service account (see the general STACKIT documentation for details).
|
||||
- Basic Terraform concepts, such as variables and locals.
|
||||
|
||||
This document does not cover foundational topics or every feature of the Cloud
|
||||
Foundry Terraform provider.
|
||||
|
||||
### Example configuration
|
||||
|
||||
The following Terraform configuration provisions a Cloud Foundry organization
|
||||
and related resources using the STACKIT Terraform provider and the Cloud Foundry
|
||||
Terraform provider:
|
||||
|
||||
```
|
||||
terraform {
|
||||
required_providers {
|
||||
stackit = {
|
||||
source = "stackitcloud/stackit"
|
||||
}
|
||||
cloudfoundry = {
|
||||
source = "cloudfoundry/cloudfoundry"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variable "project_id" {
|
||||
type = string
|
||||
description = "Id of the Project"
|
||||
}
|
||||
|
||||
variable "org_name" {
|
||||
type = string
|
||||
description = "Name of the Organization"
|
||||
}
|
||||
|
||||
variable "admin_email" {
|
||||
type = string
|
||||
description = "Users who are granted permissions"
|
||||
}
|
||||
|
||||
provider "stackit" {
|
||||
default_region = "eu01"
|
||||
}
|
||||
|
||||
resource "stackit_scf_organization" "scf_org" {
|
||||
name = var.org_name
|
||||
project_id = var.project_id
|
||||
}
|
||||
|
||||
data "stackit_scf_platform" "scf_platform" {
|
||||
project_id = var.project_id
|
||||
platform_id = stackit_scf_organization.scf_org.platform_id
|
||||
}
|
||||
|
||||
resource "stackit_scf_organization_manager" "scf_manager" {
|
||||
project_id = var.project_id
|
||||
org_id = stackit_scf_organization.scf_org.org_id
|
||||
}
|
||||
|
||||
provider "cloudfoundry" {
|
||||
api_url = data.stackit_scf_platform.scf_platform.api_url
|
||||
user = stackit_scf_organization_manager.scf_manager.username
|
||||
password = stackit_scf_organization_manager.scf_manager.password
|
||||
}
|
||||
|
||||
locals {
|
||||
spaces = ["dev", "qa", "prod"]
|
||||
}
|
||||
|
||||
resource "cloudfoundry_org_role" "org_user" {
|
||||
username = var.admin_email
|
||||
type = "organization_user"
|
||||
org = stackit_scf_organization.scf_org.org_id
|
||||
}
|
||||
|
||||
resource "cloudfoundry_org_role" "org_manager" {
|
||||
username = var.admin_email
|
||||
type = "organization_manager"
|
||||
org = stackit_scf_organization.scf_org.org_id
|
||||
}
|
||||
|
||||
resource "cloudfoundry_space" "spaces" {
|
||||
for_each = toset(local.spaces)
|
||||
name = each.key
|
||||
org = stackit_scf_organization.scf_org.org_id
|
||||
}
|
||||
|
||||
resource "cloudfoundry_space_role" "space_developer" {
|
||||
for_each = toset(local.spaces)
|
||||
username = var.admin_email
|
||||
type = "space_developer"
|
||||
depends_on = [ cloudfoundry_org_role.org_user ]
|
||||
space = cloudfoundry_space.spaces[each.key].id
|
||||
}
|
||||
```
|
||||
|
||||
## Explanation of configuration
|
||||
|
||||
### STACKIT provider configuration
|
||||
|
||||
```
|
||||
provider "stackit" {
|
||||
default_region = "eu01"
|
||||
}
|
||||
```
|
||||
|
||||
The STACKIT Cloud Foundry Application Programming Interface (SCF API) is
|
||||
regionalized. Each region operates independently. Set `default_region` in the
|
||||
provider configuration, to specify the region for all resources, unless you
|
||||
override it for individual resources. You must also provide access data for the
|
||||
relevant STACKIT project for the provider to function.
|
||||
|
||||
For more details, see
|
||||
the:[STACKIT Terraform Provider documentation.](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs)
|
||||
|
||||
### stackit_scf_organization.scf_org resource
|
||||
|
||||
```
|
||||
resource "stackit_scf_organization" "scf_org" {
|
||||
name = var.org_name
|
||||
project_id = var.project_id
|
||||
}
|
||||
```
|
||||
|
||||
This resource provisions a Cloud Foundry organization, which acts as the
|
||||
foundational container in the Cloud Foundry environment. Each Cloud Foundry
|
||||
provider configuration is scoped to a specific organization. The organization’s
|
||||
name, defined by a variable, must be unique across the platform. The
|
||||
organization is created within a designated STACKIT project, which requires the
|
||||
STACKIT provider to be configured with the necessary permissions for that
|
||||
project.
|
||||
|
||||
### stackit_scf_organization_manager.scf_manager resource
|
||||
|
||||
```
|
||||
resource "stackit_scf_organization_manager" "scf_manager" {
|
||||
project_id = var.project_id
|
||||
org_id = stackit_scf_organization.scf_org.org_id
|
||||
}
|
||||
```
|
||||
|
||||
This resource creates a technical user in the Cloud Foundry organization with
|
||||
the organization_manager permission. The user is linked to the organization and
|
||||
is automatically deleted when the organization is removed.
|
||||
|
||||
### stackit_scf_platform.scf_platform data source
|
||||
|
||||
```
|
||||
data "stackit_scf_platform" "scf_platform" {
|
||||
project_id = var.project_id
|
||||
platform_id = stackit_scf_organization.scf_org.platform_id
|
||||
}
|
||||
```
|
||||
|
||||
This data source retrieves properties of the Cloud Foundry platform where the
|
||||
organization is provisioned. It does not create resources, but provides
|
||||
information about the existing platform.
|
||||
|
||||
### Cloud Foundry provider configuration
|
||||
|
||||
```
|
||||
provider "cloudfoundry" {
|
||||
api_url = data.stackit_scf_platform.scf_platform.api_url
|
||||
user = stackit_scf_organization_manager.scf_manager.username
|
||||
password = stackit_scf_organization_manager.scf_manager.password
|
||||
}
|
||||
```
|
||||
|
||||
The Cloud Foundry provider is configured to manage resources in the new
|
||||
organization. The provider uses the API URL from the `stackit_scf_platform` data
|
||||
source and authenticates using the credentials of the technical user created by
|
||||
the `stackit_scf_organization_manager` resource.
|
||||
|
||||
For more information, see the:
|
||||
[Cloud Foundry Terraform Provider documentation.](https://registry.terraform.io/providers/cloudfoundry/cloudfoundry/latest/docs)
|
||||
|
||||
## Deploy resources
|
||||
|
||||
Follow these steps to initialize your environment and provision Cloud Foundry
|
||||
resources using Terraform.
|
||||
|
||||
### Initialize Terraform
|
||||
|
||||
Run the following command to initialize the working directory and download the
|
||||
required provider plugins:
|
||||
|
||||
```
|
||||
terraform init
|
||||
```
|
||||
|
||||
### Create the organization manager user
|
||||
|
||||
Run this command to provision the organization and technical user needed to
|
||||
initialize the Cloud Foundry Terraform provider. This step is required only
|
||||
during the initial setup. For later changes, you do not need the -target flag.
|
||||
|
||||
```
|
||||
terraform apply -target stackit_scf_organization_manager.scf_manager
|
||||
```
|
||||
|
||||
### Apply the full configuration
|
||||
|
||||
Run this command to provision all resources defined in your Terraform
|
||||
configuration within the Cloud Foundry organization:
|
||||
|
||||
```
|
||||
terraform apply
|
||||
```
|
||||
|
||||
## Verify the deployment
|
||||
|
||||
Verify that your Cloud Foundry resources are provisioned correctly. Use the
|
||||
following Cloud Foundry CLI commands to check applications, services, and
|
||||
routes:
|
||||
|
||||
- `cf apps`
|
||||
- `cf services`
|
||||
- `cf routes`
|
||||
|
||||
For more information, see the
|
||||
[Cloud Foundry documentation](https://docs.cloudfoundry.org/) and the
|
||||
[Cloud Foundry CLI Reference Guide](https://cli.cloudfoundry.org/).
|
||||
|
|
@ -177,6 +177,7 @@ Note: AWS specific checks must be skipped as they do not work on STACKIT. For de
|
|||
- `redis_custom_endpoint` (String) Custom endpoint for the Redis service
|
||||
- `region` (String, Deprecated) Region will be used as the default location for regional services. Not all services require a region, some are global
|
||||
- `resourcemanager_custom_endpoint` (String) Custom endpoint for the Resource Manager service
|
||||
- `scf_custom_endpoint` (String) Custom endpoint for the Cloud Foundry (SCF) service
|
||||
- `secretsmanager_custom_endpoint` (String) Custom endpoint for the Secrets Manager service
|
||||
- `server_backup_custom_endpoint` (String) Custom endpoint for the Server Backup service
|
||||
- `server_update_custom_endpoint` (String) Custom endpoint for the Server Update service
|
||||
|
|
|
|||
57
docs/resources/scf_organization.md
Normal file
57
docs/resources/scf_organization.md
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
---
|
||||
# generated by https://github.com/hashicorp/terraform-plugin-docs
|
||||
page_title: "stackit_scf_organization Resource - stackit"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
STACKIT Cloud Foundry organization resource schema. Must have a region specified in the provider configuration.
|
||||
---
|
||||
|
||||
# stackit_scf_organization (Resource)
|
||||
|
||||
STACKIT Cloud Foundry organization resource schema. Must have a `region` specified in the provider configuration.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```terraform
|
||||
resource "stackit_scf_organization" "example" {
|
||||
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
name = "example"
|
||||
}
|
||||
|
||||
resource "stackit_scf_organization" "example" {
|
||||
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
name = "example"
|
||||
platform_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
quota_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
suspended = false
|
||||
}
|
||||
|
||||
# Only use the import statement, if you want to import an existing scf organization
|
||||
import {
|
||||
to = stackit_scf_organization.import-example
|
||||
id = "${var.project_id},${var.region},${var.org_id}"
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
||||
### Required
|
||||
|
||||
- `name` (String) The name of the organization
|
||||
- `project_id` (String) The ID of the project associated with the organization
|
||||
|
||||
### Optional
|
||||
|
||||
- `platform_id` (String) The ID of the platform associated with the organization
|
||||
- `quota_id` (String) The ID of the quota associated with the organization
|
||||
- `region` (String) The resource region. If not defined, the provider region is used
|
||||
- `suspended` (Boolean) A boolean indicating whether the organization is suspended
|
||||
|
||||
### Read-Only
|
||||
|
||||
- `created_at` (String) The time when the organization was created
|
||||
- `id` (String) Terraform's internal resource ID, structured as "`project_id`,`region`,`org_id`".
|
||||
- `org_id` (String) The ID of the Cloud Foundry Organization
|
||||
- `status` (String) The status of the organization (e.g., deleting, delete_failed)
|
||||
- `updated_at` (String) The time when the organization was last updated
|
||||
49
docs/resources/scf_organization_manager.md
Normal file
49
docs/resources/scf_organization_manager.md
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
---
|
||||
# generated by https://github.com/hashicorp/terraform-plugin-docs
|
||||
page_title: "stackit_scf_organization_manager Resource - stackit"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
STACKIT Cloud Foundry organization manager resource schema.
|
||||
---
|
||||
|
||||
# stackit_scf_organization_manager (Resource)
|
||||
|
||||
STACKIT Cloud Foundry organization manager resource schema.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```terraform
|
||||
resource "stackit_scf_organization_manager" "example" {
|
||||
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
org_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
}
|
||||
|
||||
# Only use the import statement, if you want to import an existing scf org user
|
||||
# The password field is still null after import and must be entered manually in the state.
|
||||
import {
|
||||
to = stackit_scf_organization_manager.import-example
|
||||
id = "${var.project_id},${var.region},${var.org_id},${var.user_id}"
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
||||
### Required
|
||||
|
||||
- `org_id` (String) The ID of the Cloud Foundry Organization
|
||||
- `project_id` (String) The ID of the project associated with the organization of the organization manager
|
||||
|
||||
### Optional
|
||||
|
||||
- `region` (String) The region where the organization of the organization manager is located. If not defined, the provider region is used
|
||||
|
||||
### Read-Only
|
||||
|
||||
- `created_at` (String) The time when the organization manager was created
|
||||
- `id` (String) Terraform's internal resource ID, structured as "`project_id`,`region`,`org_id`,`user_id`".
|
||||
- `password` (String, Sensitive) An auto-generated password
|
||||
- `platform_id` (String) The ID of the platform associated with the organization of the organization manager
|
||||
- `updated_at` (String) The time when the organization manager was last updated
|
||||
- `user_id` (String) The ID of the organization manager user
|
||||
- `username` (String) An auto-generated organization manager user name
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
data "stackit_scf_organization" "example" {
|
||||
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
org_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
data "stackit_scf_organization_manager" "example" {
|
||||
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
org_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
data "stackit_scf_platform" "example" {
|
||||
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
platform_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
}
|
||||
18
examples/resources/stackit_scf_organization/resource.tf
Normal file
18
examples/resources/stackit_scf_organization/resource.tf
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
resource "stackit_scf_organization" "example" {
|
||||
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
name = "example"
|
||||
}
|
||||
|
||||
resource "stackit_scf_organization" "example" {
|
||||
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
name = "example"
|
||||
platform_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
quota_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
suspended = false
|
||||
}
|
||||
|
||||
# Only use the import statement, if you want to import an existing scf organization
|
||||
import {
|
||||
to = stackit_scf_organization.import-example
|
||||
id = "${var.project_id},${var.region},${var.org_id}"
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
resource "stackit_scf_organization_manager" "example" {
|
||||
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
org_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
}
|
||||
|
||||
# Only use the import statement, if you want to import an existing scf org user
|
||||
# The password field is still null after import and must be entered manually in the state.
|
||||
import {
|
||||
to = stackit_scf_organization_manager.import-example
|
||||
id = "${var.project_id},${var.region},${var.org_id},${var.user_id}"
|
||||
}
|
||||
1
go.mod
1
go.mod
|
|
@ -29,6 +29,7 @@ require (
|
|||
github.com/stackitcloud/stackit-sdk-go/services/rabbitmq v0.25.1
|
||||
github.com/stackitcloud/stackit-sdk-go/services/redis v0.25.1
|
||||
github.com/stackitcloud/stackit-sdk-go/services/resourcemanager v0.17.1
|
||||
github.com/stackitcloud/stackit-sdk-go/services/scf v0.2.1
|
||||
github.com/stackitcloud/stackit-sdk-go/services/secretsmanager v0.13.1
|
||||
github.com/stackitcloud/stackit-sdk-go/services/serverbackup v1.3.2
|
||||
github.com/stackitcloud/stackit-sdk-go/services/serverupdate v1.2.1
|
||||
|
|
|
|||
2
go.sum
2
go.sum
|
|
@ -190,6 +190,8 @@ github.com/stackitcloud/stackit-sdk-go/services/redis v0.25.1 h1:8uPt82Ez34OYMOi
|
|||
github.com/stackitcloud/stackit-sdk-go/services/redis v0.25.1/go.mod h1:1Y2GEICmZDt+kr8aGnBx/sjYVAIYHmtfC8xYi9oxNEE=
|
||||
github.com/stackitcloud/stackit-sdk-go/services/resourcemanager v0.17.1 h1:r7oaINTwLmIG31AaqKTuQHHFF8YNuYGzi+46DOuSjw4=
|
||||
github.com/stackitcloud/stackit-sdk-go/services/resourcemanager v0.17.1/go.mod h1:ipcrPRbwfQXHH18dJVfY7K5ujHF5dTT6isoXgmA7YwQ=
|
||||
github.com/stackitcloud/stackit-sdk-go/services/scf v0.2.1 h1:OdofRB6uj6lwN/TXLVHVrEOwNMG34MlFNwkiHD+eOts=
|
||||
github.com/stackitcloud/stackit-sdk-go/services/scf v0.2.1/go.mod h1:5p7Xi8jadpJNDYr0t+07DXS104/RJLfhhA1r6P7PlGs=
|
||||
github.com/stackitcloud/stackit-sdk-go/services/secretsmanager v0.13.1 h1:WKFzlHllql3JsVcAq+Y1m5pSMkvwp1qH3Vf2N7i8CPg=
|
||||
github.com/stackitcloud/stackit-sdk-go/services/secretsmanager v0.13.1/go.mod h1:WGMFtGugBmUxI+nibI7eUZIQk4AGlDvwqX+m17W1y5w=
|
||||
github.com/stackitcloud/stackit-sdk-go/services/serverbackup v1.3.2 h1:tfKC4Z6Uah9AQZrtCn/ytqOgc//ChQRfJ6ozxovgads=
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ type ProviderData struct {
|
|||
RabbitMQCustomEndpoint string
|
||||
RedisCustomEndpoint string
|
||||
ResourceManagerCustomEndpoint string
|
||||
ScfCustomEndpoint string
|
||||
SecretsManagerCustomEndpoint string
|
||||
SQLServerFlexCustomEndpoint string
|
||||
ServerBackupCustomEndpoint string
|
||||
|
|
|
|||
176
stackit/internal/services/scf/organization/datasource.go
Normal file
176
stackit/internal/services/scf/organization/datasource.go
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
package organization
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
"github.com/stackitcloud/stackit-sdk-go/services/scf"
|
||||
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
|
||||
scfUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/scf/utils"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate"
|
||||
)
|
||||
|
||||
// Ensure the implementation satisfies the expected interfaces.
|
||||
var (
|
||||
_ datasource.DataSource = &scfOrganizationDataSource{}
|
||||
_ datasource.DataSourceWithConfigure = &scfOrganizationDataSource{}
|
||||
)
|
||||
|
||||
// NewScfOrganizationDataSource creates a new instance of the scfOrganizationDataSource.
|
||||
func NewScfOrganizationDataSource() datasource.DataSource {
|
||||
return &scfOrganizationDataSource{}
|
||||
}
|
||||
|
||||
// scfOrganizationDataSource is the datasource implementation.
|
||||
type scfOrganizationDataSource struct {
|
||||
client *scf.APIClient
|
||||
providerData core.ProviderData
|
||||
}
|
||||
|
||||
func (s *scfOrganizationDataSource) Configure(ctx context.Context, request datasource.ConfigureRequest, response *datasource.ConfigureResponse) {
|
||||
var ok bool
|
||||
s.providerData, ok = conversion.ParseProviderData(ctx, request.ProviderData, &response.Diagnostics)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
apiClient := scfUtils.ConfigureClient(ctx, &s.providerData, &response.Diagnostics)
|
||||
if response.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
s.client = apiClient
|
||||
tflog.Info(ctx, "scf client configured")
|
||||
}
|
||||
|
||||
func (s *scfOrganizationDataSource) Metadata(_ context.Context, request datasource.MetadataRequest, response *datasource.MetadataResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
response.TypeName = request.ProviderTypeName + "_scf_organization"
|
||||
}
|
||||
|
||||
func (s *scfOrganizationDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, response *datasource.SchemaResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
response.Schema = schema.Schema{
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{
|
||||
Description: descriptions["id"],
|
||||
Computed: true,
|
||||
},
|
||||
"created_at": schema.StringAttribute{
|
||||
Description: descriptions["created_at"],
|
||||
Computed: true,
|
||||
},
|
||||
"name": schema.StringAttribute{
|
||||
Description: descriptions["name"],
|
||||
Computed: true,
|
||||
Validators: []validator.String{
|
||||
stringvalidator.LengthBetween(1, 255),
|
||||
},
|
||||
},
|
||||
"platform_id": schema.StringAttribute{
|
||||
Description: descriptions["platform_id"],
|
||||
Computed: true,
|
||||
Validators: []validator.String{
|
||||
validate.UUID(),
|
||||
validate.NoSeparator(),
|
||||
},
|
||||
},
|
||||
"project_id": schema.StringAttribute{
|
||||
Description: descriptions["project_id"],
|
||||
Required: true,
|
||||
Validators: []validator.String{
|
||||
validate.UUID(),
|
||||
validate.NoSeparator(),
|
||||
},
|
||||
},
|
||||
"org_id": schema.StringAttribute{
|
||||
Description: descriptions["org_id"],
|
||||
Required: true,
|
||||
Validators: []validator.String{
|
||||
validate.UUID(),
|
||||
validate.NoSeparator(),
|
||||
},
|
||||
},
|
||||
"quota_id": schema.StringAttribute{
|
||||
Description: descriptions["quota_id"],
|
||||
Computed: true,
|
||||
Validators: []validator.String{
|
||||
validate.UUID(),
|
||||
validate.NoSeparator(),
|
||||
},
|
||||
},
|
||||
"region": schema.StringAttribute{
|
||||
Description: descriptions["region"],
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
"status": schema.StringAttribute{
|
||||
Description: descriptions["status"],
|
||||
Computed: true,
|
||||
},
|
||||
"suspended": schema.BoolAttribute{
|
||||
Description: descriptions["suspended"],
|
||||
Computed: true,
|
||||
},
|
||||
"updated_at": schema.StringAttribute{
|
||||
Description: descriptions["updated_at"],
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
Description: "STACKIT Cloud Foundry organization datasource schema. Must have a `region` specified in the provider configuration.",
|
||||
}
|
||||
}
|
||||
|
||||
func (s *scfOrganizationDataSource) Read(ctx context.Context, request datasource.ReadRequest, response *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
// Retrieve the current state of the resource.
|
||||
var model Model
|
||||
diags := request.Config.Get(ctx, &model)
|
||||
response.Diagnostics.Append(diags...)
|
||||
if response.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// Extract the project ID and instance id of the model
|
||||
projectId := model.ProjectId.ValueString()
|
||||
orgId := model.OrgId.ValueString()
|
||||
|
||||
// Extract the region
|
||||
region := s.providerData.GetRegionWithOverride(model.Region)
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "org_id", orgId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
|
||||
// Read the current scf organization via orgId
|
||||
scfOrgResponse, err := s.client.GetOrganization(ctx, projectId, region, orgId).Execute()
|
||||
if err != nil {
|
||||
utils.LogError(
|
||||
ctx,
|
||||
&response.Diagnostics,
|
||||
err,
|
||||
"Reading scf organization",
|
||||
fmt.Sprintf("Organization with ID %q does not exist in project %q.", orgId, projectId),
|
||||
map[int]string{
|
||||
http.StatusForbidden: fmt.Sprintf("Organization with ID %q not found or forbidden access", orgId),
|
||||
},
|
||||
)
|
||||
response.State.RemoveResource(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
err = mapFields(scfOrgResponse, &model)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &response.Diagnostics, "Error reading scf organization", fmt.Sprintf("Processing API response: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Set the updated state.
|
||||
diags = response.State.Set(ctx, &model)
|
||||
response.Diagnostics.Append(diags...)
|
||||
tflog.Info(ctx, fmt.Sprintf("read scf organization %s", orgId))
|
||||
}
|
||||
540
stackit/internal/services/scf/organization/resource.go
Normal file
540
stackit/internal/services/scf/organization/resource.go
Normal file
|
|
@ -0,0 +1,540 @@
|
|||
package organization
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
|
||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
|
||||
"github.com/stackitcloud/stackit-sdk-go/services/scf"
|
||||
"github.com/stackitcloud/stackit-sdk-go/services/scf/wait"
|
||||
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
|
||||
scfUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/scf/utils"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate"
|
||||
)
|
||||
|
||||
// Ensure the implementation satisfies the expected interfaces.
|
||||
var (
|
||||
_ resource.Resource = &scfOrganizationResource{}
|
||||
_ resource.ResourceWithConfigure = &scfOrganizationResource{}
|
||||
_ resource.ResourceWithImportState = &scfOrganizationResource{}
|
||||
_ resource.ResourceWithModifyPlan = &scfOrganizationResource{}
|
||||
)
|
||||
|
||||
type Model struct {
|
||||
Id types.String `tfsdk:"id"` // Required by Terraform
|
||||
CreateAt types.String `tfsdk:"created_at"`
|
||||
Name types.String `tfsdk:"name"`
|
||||
PlatformId types.String `tfsdk:"platform_id"`
|
||||
ProjectId types.String `tfsdk:"project_id"`
|
||||
QuotaId types.String `tfsdk:"quota_id"`
|
||||
OrgId types.String `tfsdk:"org_id"`
|
||||
Region types.String `tfsdk:"region"`
|
||||
Status types.String `tfsdk:"status"`
|
||||
Suspended types.Bool `tfsdk:"suspended"`
|
||||
UpdatedAt types.String `tfsdk:"updated_at"`
|
||||
}
|
||||
|
||||
// NewScfOrganizationResource is a helper function to create a new scf organization resource.
|
||||
func NewScfOrganizationResource() resource.Resource {
|
||||
return &scfOrganizationResource{}
|
||||
}
|
||||
|
||||
// scfOrganizationResource implements the resource interface for scf organization.
|
||||
type scfOrganizationResource struct {
|
||||
client *scf.APIClient
|
||||
providerData core.ProviderData
|
||||
}
|
||||
|
||||
// descriptions for the attributes in the Schema
|
||||
var descriptions = map[string]string{
|
||||
"id": "Terraform's internal resource ID, structured as \"`project_id`,`region`,`org_id`\".",
|
||||
"created_at": "The time when the organization was created",
|
||||
"name": "The name of the organization",
|
||||
"platform_id": "The ID of the platform associated with the organization",
|
||||
"project_id": "The ID of the project associated with the organization",
|
||||
"quota_id": "The ID of the quota associated with the organization",
|
||||
"region": "The resource region. If not defined, the provider region is used",
|
||||
"status": "The status of the organization (e.g., deleting, delete_failed)",
|
||||
"suspended": "A boolean indicating whether the organization is suspended",
|
||||
"org_id": "The ID of the Cloud Foundry Organization",
|
||||
"updated_at": "The time when the organization was last updated",
|
||||
}
|
||||
|
||||
func (s *scfOrganizationResource) Configure(ctx context.Context, request resource.ConfigureRequest, response *resource.ConfigureResponse) {
|
||||
var ok bool
|
||||
s.providerData, ok = conversion.ParseProviderData(ctx, request.ProviderData, &response.Diagnostics)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
apiClient := scfUtils.ConfigureClient(ctx, &s.providerData, &response.Diagnostics)
|
||||
if response.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
s.client = apiClient
|
||||
tflog.Info(ctx, "scf client configured")
|
||||
}
|
||||
|
||||
func (s *scfOrganizationResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) {
|
||||
response.TypeName = request.ProviderTypeName + "_scf_organization"
|
||||
}
|
||||
|
||||
// ModifyPlan implements resource.ResourceWithModifyPlan.
|
||||
// Use the modifier to set the effective region in the current plan.
|
||||
func (r *scfOrganizationResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
var configModel Model
|
||||
// skip initial empty configuration to avoid follow-up errors
|
||||
if req.Config.Raw.IsNull() {
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.Append(req.Config.Get(ctx, &configModel)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
var planModel Model
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &planModel)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
utils.AdaptRegion(ctx, configModel.Region, &planModel.Region, r.providerData.GetRegion(), resp)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.Plan.Set(ctx, planModel)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (s *scfOrganizationResource) Schema(_ context.Context, _ resource.SchemaRequest, response *resource.SchemaResponse) {
|
||||
response.Schema = schema.Schema{
|
||||
Description: "STACKIT Cloud Foundry organization resource schema. Must have a `region` specified in the provider configuration.",
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{
|
||||
Description: descriptions["id"],
|
||||
Computed: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
},
|
||||
},
|
||||
"created_at": schema.StringAttribute{
|
||||
Description: descriptions["created_at"],
|
||||
Computed: true,
|
||||
},
|
||||
"name": schema.StringAttribute{
|
||||
Description: descriptions["name"],
|
||||
Required: true,
|
||||
Validators: []validator.String{
|
||||
stringvalidator.LengthBetween(1, 255),
|
||||
},
|
||||
},
|
||||
"platform_id": schema.StringAttribute{
|
||||
Description: descriptions["platform_id"],
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Validators: []validator.String{
|
||||
validate.UUID(),
|
||||
validate.NoSeparator(),
|
||||
},
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
},
|
||||
"project_id": schema.StringAttribute{
|
||||
Description: descriptions["project_id"],
|
||||
Required: true,
|
||||
Validators: []validator.String{
|
||||
validate.UUID(),
|
||||
validate.NoSeparator(),
|
||||
},
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
},
|
||||
"org_id": schema.StringAttribute{
|
||||
Description: descriptions["org_id"],
|
||||
Computed: true,
|
||||
Validators: []validator.String{
|
||||
validate.UUID(),
|
||||
validate.NoSeparator(),
|
||||
},
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
},
|
||||
},
|
||||
"quota_id": schema.StringAttribute{
|
||||
Description: descriptions["quota_id"],
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Validators: []validator.String{
|
||||
validate.UUID(),
|
||||
validate.NoSeparator(),
|
||||
},
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
},
|
||||
},
|
||||
"region": schema.StringAttribute{
|
||||
Description: descriptions["region"],
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
},
|
||||
"status": schema.StringAttribute{
|
||||
Description: descriptions["status"],
|
||||
Computed: true,
|
||||
},
|
||||
"suspended": schema.BoolAttribute{
|
||||
Description: descriptions["suspended"],
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
PlanModifiers: []planmodifier.Bool{
|
||||
boolplanmodifier.UseStateForUnknown(),
|
||||
},
|
||||
},
|
||||
"updated_at": schema.StringAttribute{
|
||||
Description: descriptions["updated_at"],
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *scfOrganizationResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
// Retrieve the planned values for the resource.
|
||||
var model Model
|
||||
diags := request.Plan.Get(ctx, &model)
|
||||
response.Diagnostics.Append(diags...)
|
||||
if response.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// Set logging context with the project ID and instance ID.
|
||||
region := model.Region.ValueString()
|
||||
projectId := model.ProjectId.ValueString()
|
||||
orgName := model.Name.ValueString()
|
||||
quotaId := model.QuotaId.ValueString()
|
||||
suspended := model.Suspended.ValueBool()
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "org_name", orgName)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
payload, err := toCreatePayload(&model)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &response.Diagnostics, "Error creating scf organization", fmt.Sprintf("Creating API payload: %v\n", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Create the new scf organization via the API client.
|
||||
scfOrgCreateResponse, err := s.client.CreateOrganization(ctx, projectId, region).
|
||||
CreateOrganizationPayload(payload).
|
||||
Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &response.Diagnostics, "Error creating scf organization", fmt.Sprintf("Calling API to create org: %v", err))
|
||||
return
|
||||
}
|
||||
orgId := *scfOrgCreateResponse.Guid
|
||||
|
||||
// Apply the org quota if provided
|
||||
if quotaId != "" {
|
||||
applyOrgQuota, err := s.client.ApplyOrganizationQuota(ctx, projectId, region, orgId).ApplyOrganizationQuotaPayload(
|
||||
scf.ApplyOrganizationQuotaPayload{
|
||||
QuotaId: "aId,
|
||||
}).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &response.Diagnostics, "Error creating scf organization", fmt.Sprintf("Calling API to apply quota: %v", err))
|
||||
return
|
||||
}
|
||||
model.QuotaId = types.StringPointerValue(applyOrgQuota.QuotaId)
|
||||
}
|
||||
|
||||
if suspended {
|
||||
_, err := s.client.UpdateOrganization(ctx, projectId, region, orgId).UpdateOrganizationPayload(
|
||||
|
||||
scf.UpdateOrganizationPayload{
|
||||
Suspended: &suspended,
|
||||
}).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &response.Diagnostics, "Error creating scf organization", fmt.Sprintf("Calling API to update suspended: %v", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Load the newly created scf organization
|
||||
scfOrgResponse, err := s.client.GetOrganization(ctx, projectId, region, orgId).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &response.Diagnostics, "Error creating scf organization", fmt.Sprintf("Calling API to load created org: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
err = mapFields(scfOrgResponse, &model)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &response.Diagnostics, "Error creating scf organization", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Set the state with fully populated data.
|
||||
diags = response.State.Set(ctx, model)
|
||||
response.Diagnostics.Append(diags...)
|
||||
if response.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
tflog.Info(ctx, "Scf organization created")
|
||||
}
|
||||
|
||||
// Read refreshes the Terraform state with the latest scf organization data.
|
||||
func (s *scfOrganizationResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
// Retrieve the current state of the resource.
|
||||
var model Model
|
||||
diags := request.State.Get(ctx, &model)
|
||||
response.Diagnostics.Append(diags...)
|
||||
if response.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// Extract the project ID and instance id of the model
|
||||
projectId := model.ProjectId.ValueString()
|
||||
orgId := model.OrgId.ValueString()
|
||||
// Extract the region
|
||||
region := s.providerData.GetRegionWithOverride(model.Region)
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "org_id", orgId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
// Read the current scf organization via guid
|
||||
scfOrgResponse, err := s.client.GetOrganization(ctx, projectId, region, orgId).Execute()
|
||||
if err != nil {
|
||||
var oapiErr *oapierror.GenericOpenAPIError
|
||||
ok := errors.As(err, &oapiErr)
|
||||
if ok && oapiErr.StatusCode == http.StatusNotFound {
|
||||
response.State.RemoveResource(ctx)
|
||||
return
|
||||
}
|
||||
core.LogAndAddError(ctx, &response.Diagnostics, "Error reading scf organization", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
err = mapFields(scfOrgResponse, &model)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &response.Diagnostics, "Error reading scf organization", fmt.Sprintf("Processing API response: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Set the updated state.
|
||||
diags = response.State.Set(ctx, &model)
|
||||
response.Diagnostics.Append(diags...)
|
||||
tflog.Info(ctx, fmt.Sprintf("read scf organization %s", orgId))
|
||||
}
|
||||
|
||||
// Update attempts to update the resource.
|
||||
func (s *scfOrganizationResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
// Retrieve values from plan
|
||||
var model Model
|
||||
diags := request.Plan.Get(ctx, &model)
|
||||
response.Diagnostics.Append(diags...)
|
||||
if response.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
region := model.Region.ValueString()
|
||||
projectId := model.ProjectId.ValueString()
|
||||
orgId := model.OrgId.ValueString()
|
||||
name := model.Name.ValueString()
|
||||
quotaId := model.QuotaId.ValueString()
|
||||
suspended := model.Suspended.ValueBool()
|
||||
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "org_id", orgId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
|
||||
org, err := s.client.GetOrganization(ctx, projectId, region, orgId).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &response.Diagnostics, "Error retrieving organization state", fmt.Sprintf("Getting organization state: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// handle a change of the organization name or the suspended flag
|
||||
if name != org.GetName() || suspended != org.GetSuspended() {
|
||||
updatedOrg, err := s.client.UpdateOrganization(ctx, projectId, region, orgId).UpdateOrganizationPayload(
|
||||
scf.UpdateOrganizationPayload{
|
||||
Name: &name,
|
||||
Suspended: &suspended,
|
||||
}).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &response.Diagnostics, "Error updating organization", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
}
|
||||
org = updatedOrg
|
||||
}
|
||||
|
||||
// handle a quota change of the org
|
||||
if quotaId != org.GetQuotaId() {
|
||||
applyOrgQuota, err := s.client.ApplyOrganizationQuota(ctx, projectId, region, orgId).ApplyOrganizationQuotaPayload(
|
||||
scf.ApplyOrganizationQuotaPayload{
|
||||
QuotaId: "aId,
|
||||
}).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &response.Diagnostics, "Error applying organization quota", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
}
|
||||
org.QuotaId = applyOrgQuota.QuotaId
|
||||
}
|
||||
|
||||
err = mapFields(org, &model)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &response.Diagnostics, "Error updating organization", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
diags = response.State.Set(ctx, model)
|
||||
response.Diagnostics.Append(diags...)
|
||||
if response.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
tflog.Info(ctx, "organization updated")
|
||||
}
|
||||
|
||||
// Delete deletes the git instance and removes it from the Terraform state on success.
|
||||
func (s *scfOrganizationResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
// Retrieve current state of the resource.
|
||||
var model Model
|
||||
diags := request.State.Get(ctx, &model)
|
||||
response.Diagnostics.Append(diags...)
|
||||
if response.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
orgId := model.OrgId.ValueString()
|
||||
|
||||
// Extract the region
|
||||
region := model.Region.ValueString()
|
||||
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "org_id", orgId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
|
||||
// Call API to delete the existing scf organization.
|
||||
_, err := s.client.DeleteOrganization(ctx, projectId, region, orgId).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &response.Diagnostics, "Error deleting scf organization", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
_, err = wait.DeleteOrganizationWaitHandler(ctx, s.client, projectId, model.Region.ValueString(), orgId).WaitWithContext(ctx)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &response.Diagnostics, "Error waiting for scf org deletion", fmt.Sprintf("SCFOrganization deleting waiting: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
tflog.Info(ctx, "Scf organization deleted")
|
||||
}
|
||||
|
||||
func (s *scfOrganizationResource) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) {
|
||||
// Split the import identifier to extract project ID and email.
|
||||
idParts := strings.Split(request.ID, core.Separator)
|
||||
|
||||
// Ensure the import identifier format is correct.
|
||||
if len(idParts) != 3 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" {
|
||||
core.LogAndAddError(ctx, &response.Diagnostics,
|
||||
"Error importing scf organization",
|
||||
fmt.Sprintf("Expected import identifier with format: [project_id],[region],[org_id] Got: %q", request.ID),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
projectId := idParts[0]
|
||||
region := idParts[1]
|
||||
orgId := idParts[2]
|
||||
// Set the project id and organization id in the state
|
||||
response.Diagnostics.Append(response.State.SetAttribute(ctx, path.Root("project_id"), projectId)...)
|
||||
response.Diagnostics.Append(response.State.SetAttribute(ctx, path.Root("region"), region)...)
|
||||
response.Diagnostics.Append(response.State.SetAttribute(ctx, path.Root("org_id"), orgId)...)
|
||||
tflog.Info(ctx, "Scf organization state imported")
|
||||
}
|
||||
|
||||
// mapFields maps a SCF Organization response to the model.
|
||||
func mapFields(response *scf.Organization, model *Model) error {
|
||||
if response == nil {
|
||||
return fmt.Errorf("response input is nil")
|
||||
}
|
||||
if model == nil {
|
||||
return fmt.Errorf("model input is nil")
|
||||
}
|
||||
|
||||
var orgId string
|
||||
if response.Guid != nil {
|
||||
orgId = *response.Guid
|
||||
} else if model.OrgId.ValueString() != "" {
|
||||
orgId = model.OrgId.ValueString()
|
||||
} else {
|
||||
return fmt.Errorf("org id is not present")
|
||||
}
|
||||
|
||||
var projectId string
|
||||
if response.ProjectId != nil {
|
||||
projectId = *response.ProjectId
|
||||
} else if model.ProjectId.ValueString() != "" {
|
||||
projectId = model.ProjectId.ValueString()
|
||||
} else {
|
||||
return fmt.Errorf("project id is not present")
|
||||
}
|
||||
|
||||
var region string
|
||||
if response.Region != nil {
|
||||
region = *response.Region
|
||||
} else if model.Region.ValueString() != "" {
|
||||
region = model.Region.ValueString()
|
||||
} else {
|
||||
return fmt.Errorf("region is not present")
|
||||
}
|
||||
|
||||
// Build the ID by combining the project ID and organization id and assign the model's fields.
|
||||
model.Id = utils.BuildInternalTerraformId(projectId, region, orgId)
|
||||
model.ProjectId = types.StringValue(projectId)
|
||||
model.Region = types.StringValue(region)
|
||||
model.PlatformId = types.StringPointerValue(response.PlatformId)
|
||||
model.OrgId = types.StringValue(orgId)
|
||||
model.Name = types.StringPointerValue(response.Name)
|
||||
model.Status = types.StringPointerValue(response.Status)
|
||||
model.Suspended = types.BoolPointerValue(response.Suspended)
|
||||
model.QuotaId = types.StringPointerValue(response.QuotaId)
|
||||
model.CreateAt = types.StringValue(response.CreatedAt.String())
|
||||
model.UpdatedAt = types.StringValue(response.UpdatedAt.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
// toCreatePayload creates the payload to create a scf organization instance
|
||||
func toCreatePayload(model *Model) (scf.CreateOrganizationPayload, error) {
|
||||
if model == nil {
|
||||
return scf.CreateOrganizationPayload{}, fmt.Errorf("nil model")
|
||||
}
|
||||
|
||||
payload := scf.CreateOrganizationPayload{
|
||||
Name: model.Name.ValueStringPointer(),
|
||||
}
|
||||
if !model.PlatformId.IsNull() && !model.PlatformId.IsUnknown() {
|
||||
payload.PlatformId = model.PlatformId.ValueStringPointer()
|
||||
}
|
||||
return payload, nil
|
||||
}
|
||||
177
stackit/internal/services/scf/organization/resource_test.go
Normal file
177
stackit/internal/services/scf/organization/resource_test.go
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
package organization
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/uuid"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/stackitcloud/stackit-sdk-go/core/utils"
|
||||
"github.com/stackitcloud/stackit-sdk-go/services/scf"
|
||||
)
|
||||
|
||||
var (
|
||||
testOrgId = uuid.New().String()
|
||||
testProjectId = uuid.New().String()
|
||||
testPlatformId = uuid.New().String()
|
||||
testQuotaId = uuid.New().String()
|
||||
testRegion = "eu01"
|
||||
)
|
||||
|
||||
func TestMapFields(t *testing.T) {
|
||||
createdTime, err := time.Parse("2006-01-02 15:04:05 -0700 MST", "2025-01-01 00:00:00 +0000 UTC")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse test time: %v", err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
description string
|
||||
input *scf.Organization
|
||||
expected *Model
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
description: "minimal_input",
|
||||
input: &scf.Organization{
|
||||
Guid: utils.Ptr(testOrgId),
|
||||
Name: utils.Ptr("scf-org-min-instance"),
|
||||
Region: utils.Ptr(testRegion),
|
||||
CreatedAt: &createdTime,
|
||||
UpdatedAt: &createdTime,
|
||||
ProjectId: utils.Ptr(testProjectId),
|
||||
},
|
||||
expected: &Model{
|
||||
Id: types.StringValue(fmt.Sprintf("%s,%s,%s", testProjectId, testRegion, testOrgId)),
|
||||
ProjectId: types.StringValue(testProjectId),
|
||||
Region: types.StringValue(testRegion),
|
||||
Name: types.StringValue("scf-org-min-instance"),
|
||||
PlatformId: types.StringNull(),
|
||||
OrgId: types.StringValue(testOrgId),
|
||||
QuotaId: types.StringNull(),
|
||||
Status: types.StringNull(),
|
||||
Suspended: types.BoolNull(),
|
||||
CreateAt: types.StringValue("2025-01-01 00:00:00 +0000 UTC"),
|
||||
UpdatedAt: types.StringValue("2025-01-01 00:00:00 +0000 UTC"),
|
||||
},
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
description: "max_input",
|
||||
input: &scf.Organization{
|
||||
CreatedAt: &createdTime,
|
||||
Guid: utils.Ptr(testOrgId),
|
||||
Name: utils.Ptr("scf-full-org"),
|
||||
PlatformId: utils.Ptr(testPlatformId),
|
||||
ProjectId: utils.Ptr(testProjectId),
|
||||
QuotaId: utils.Ptr(testQuotaId),
|
||||
Region: utils.Ptr(testRegion),
|
||||
Status: nil,
|
||||
Suspended: utils.Ptr(true),
|
||||
UpdatedAt: &createdTime,
|
||||
},
|
||||
expected: &Model{
|
||||
Id: types.StringValue(fmt.Sprintf("%s,%s,%s", testProjectId, testRegion, testOrgId)),
|
||||
ProjectId: types.StringValue(testProjectId),
|
||||
OrgId: types.StringValue(testOrgId),
|
||||
Name: types.StringValue("scf-full-org"),
|
||||
Region: types.StringValue(testRegion),
|
||||
PlatformId: types.StringValue(testPlatformId),
|
||||
CreateAt: types.StringValue("2025-01-01 00:00:00 +0000 UTC"),
|
||||
UpdatedAt: types.StringValue("2025-01-01 00:00:00 +0000 UTC"),
|
||||
QuotaId: types.StringValue(testQuotaId),
|
||||
Status: types.StringNull(),
|
||||
Suspended: types.BoolValue(true),
|
||||
},
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
description: "nil_org",
|
||||
input: nil,
|
||||
expected: nil,
|
||||
isValid: false,
|
||||
},
|
||||
{
|
||||
description: "empty_org",
|
||||
input: &scf.Organization{},
|
||||
expected: nil,
|
||||
isValid: false,
|
||||
},
|
||||
{
|
||||
description: "missing_id",
|
||||
input: &scf.Organization{
|
||||
Name: utils.Ptr("scf-missing-id"),
|
||||
},
|
||||
expected: nil,
|
||||
isValid: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
state := &Model{}
|
||||
if tt.expected != nil {
|
||||
state.ProjectId = tt.expected.ProjectId
|
||||
}
|
||||
err := mapFields(tt.input, state)
|
||||
|
||||
if tt.isValid && err != nil {
|
||||
t.Fatalf("expected success, got error: %v", err)
|
||||
}
|
||||
if !tt.isValid && err == nil {
|
||||
t.Fatalf("expected error, got nil")
|
||||
}
|
||||
if tt.isValid {
|
||||
if diff := cmp.Diff(tt.expected, state); diff != "" {
|
||||
t.Errorf("unexpected diff (-want +got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestToCreatePayload(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
input *Model
|
||||
expected scf.CreateOrganizationPayload
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
description: "default values",
|
||||
input: &Model{
|
||||
Name: types.StringValue("example-org"),
|
||||
PlatformId: types.StringValue(testPlatformId),
|
||||
},
|
||||
expected: scf.CreateOrganizationPayload{
|
||||
Name: utils.Ptr("example-org"),
|
||||
PlatformId: utils.Ptr(testPlatformId),
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
description: "nil input model",
|
||||
input: nil,
|
||||
expected: scf.CreateOrganizationPayload{},
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
output, err := toCreatePayload(tt.input)
|
||||
|
||||
if tt.expectError && err == nil {
|
||||
t.Fatalf("expected diagnostics error but got none")
|
||||
}
|
||||
|
||||
if !tt.expectError && err != nil {
|
||||
t.Fatalf("unexpected diagnostics error: %v", err)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(tt.expected, output); diff != "" {
|
||||
t.Fatalf("unexpected payload (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
238
stackit/internal/services/scf/organizationmanager/datasource.go
Normal file
238
stackit/internal/services/scf/organizationmanager/datasource.go
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
package organizationmanager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
"github.com/stackitcloud/stackit-sdk-go/services/scf"
|
||||
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
|
||||
scfUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/scf/utils"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate"
|
||||
)
|
||||
|
||||
// Ensure the implementation satisfies the expected interfaces.
|
||||
var (
|
||||
_ datasource.DataSource = &scfOrganizationManagerDataSource{}
|
||||
_ datasource.DataSourceWithConfigure = &scfOrganizationManagerDataSource{}
|
||||
)
|
||||
|
||||
type DataSourceModel struct {
|
||||
Id types.String `tfsdk:"id"` // Required by Terraform
|
||||
Region types.String `tfsdk:"region"`
|
||||
PlatformId types.String `tfsdk:"platform_id"`
|
||||
ProjectId types.String `tfsdk:"project_id"`
|
||||
OrgId types.String `tfsdk:"org_id"`
|
||||
UserId types.String `tfsdk:"user_id"`
|
||||
UserName types.String `tfsdk:"username"`
|
||||
CreateAt types.String `tfsdk:"created_at"`
|
||||
UpdatedAt types.String `tfsdk:"updated_at"`
|
||||
}
|
||||
|
||||
// NewScfOrganizationManagerDataSource creates a new instance of the scfOrganizationDataSource.
|
||||
func NewScfOrganizationManagerDataSource() datasource.DataSource {
|
||||
return &scfOrganizationManagerDataSource{}
|
||||
}
|
||||
|
||||
// scfOrganizationManagerDataSource is the datasource implementation.
|
||||
type scfOrganizationManagerDataSource struct {
|
||||
client *scf.APIClient
|
||||
providerData core.ProviderData
|
||||
}
|
||||
|
||||
func (s *scfOrganizationManagerDataSource) Configure(ctx context.Context, request datasource.ConfigureRequest, response *datasource.ConfigureResponse) {
|
||||
var ok bool
|
||||
s.providerData, ok = conversion.ParseProviderData(ctx, request.ProviderData, &response.Diagnostics)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
apiClient := scfUtils.ConfigureClient(ctx, &s.providerData, &response.Diagnostics)
|
||||
if response.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
s.client = apiClient
|
||||
tflog.Info(ctx, "scf client configured for scfOrganizationManagerDataSource")
|
||||
}
|
||||
|
||||
func (s *scfOrganizationManagerDataSource) Metadata(_ context.Context, request datasource.MetadataRequest, response *datasource.MetadataResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
response.TypeName = request.ProviderTypeName + "_scf_organization_manager"
|
||||
}
|
||||
|
||||
func (s *scfOrganizationManagerDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, response *datasource.SchemaResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
response.Schema = schema.Schema{
|
||||
Description: "STACKIT Cloud Foundry organization manager datasource schema.",
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{
|
||||
Description: descriptions["id"],
|
||||
Computed: true,
|
||||
},
|
||||
"region": schema.StringAttribute{
|
||||
Description: descriptions["region"],
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
"platform_id": schema.StringAttribute{
|
||||
Description: descriptions["platform_id"],
|
||||
Computed: true,
|
||||
Validators: []validator.String{
|
||||
validate.UUID(),
|
||||
validate.NoSeparator(),
|
||||
},
|
||||
},
|
||||
"project_id": schema.StringAttribute{
|
||||
Description: descriptions["project_id"],
|
||||
Required: true,
|
||||
Validators: []validator.String{
|
||||
validate.UUID(),
|
||||
validate.NoSeparator(),
|
||||
},
|
||||
},
|
||||
"org_id": schema.StringAttribute{
|
||||
Description: descriptions["org_id"],
|
||||
Required: true,
|
||||
Validators: []validator.String{
|
||||
validate.UUID(),
|
||||
validate.NoSeparator(),
|
||||
},
|
||||
},
|
||||
"user_id": schema.StringAttribute{
|
||||
Description: descriptions["user_id"],
|
||||
Computed: true,
|
||||
Validators: []validator.String{
|
||||
validate.UUID(),
|
||||
validate.NoSeparator(),
|
||||
},
|
||||
},
|
||||
"username": schema.StringAttribute{
|
||||
Description: descriptions["username"],
|
||||
Computed: true,
|
||||
Validators: []validator.String{
|
||||
stringvalidator.LengthBetween(1, 255),
|
||||
},
|
||||
},
|
||||
"created_at": schema.StringAttribute{
|
||||
Description: descriptions["created_at"],
|
||||
Computed: true,
|
||||
},
|
||||
"updated_at": schema.StringAttribute{
|
||||
Description: descriptions["updated_at"],
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *scfOrganizationManagerDataSource) Read(ctx context.Context, request datasource.ReadRequest, response *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
// Retrieve the current state of the resource.
|
||||
var model DataSourceModel
|
||||
diags := request.Config.Get(ctx, &model)
|
||||
response.Diagnostics.Append(diags...)
|
||||
if response.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// Extract the project ID and instance id of the model
|
||||
projectId := model.ProjectId.ValueString()
|
||||
orgId := model.OrgId.ValueString()
|
||||
|
||||
region := s.providerData.GetRegionWithOverride(model.Region)
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "org_id", orgId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
// Read the current scf organization manager via orgId
|
||||
ScfOrgManager, err := s.client.GetOrgManagerExecute(ctx, projectId, region, orgId)
|
||||
if err != nil {
|
||||
utils.LogError(
|
||||
ctx,
|
||||
&response.Diagnostics,
|
||||
err,
|
||||
"Reading scf organization manager",
|
||||
fmt.Sprintf("Organization with ID %q does not exist in project %q.", orgId, projectId),
|
||||
map[int]string{
|
||||
http.StatusForbidden: fmt.Sprintf("Organization with ID %q not found or forbidden access", orgId),
|
||||
},
|
||||
)
|
||||
response.State.RemoveResource(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
err = mapFieldsDataSource(ScfOrgManager, &model)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &response.Diagnostics, "Error reading scf organization manager", fmt.Sprintf("Processing API response: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Set the updated state.
|
||||
diags = response.State.Set(ctx, &model)
|
||||
response.Diagnostics.Append(diags...)
|
||||
tflog.Info(ctx, fmt.Sprintf("read scf organization manager %s", orgId))
|
||||
}
|
||||
|
||||
func mapFieldsDataSource(response *scf.OrgManager, model *DataSourceModel) error {
|
||||
if response == nil {
|
||||
return fmt.Errorf("response input is nil")
|
||||
}
|
||||
if model == nil {
|
||||
return fmt.Errorf("model input is nil")
|
||||
}
|
||||
|
||||
var projectId string
|
||||
if response.ProjectId != nil {
|
||||
projectId = *response.ProjectId
|
||||
} else if model.ProjectId.ValueString() != "" {
|
||||
projectId = model.ProjectId.ValueString()
|
||||
} else {
|
||||
return fmt.Errorf("project id is not present")
|
||||
}
|
||||
|
||||
var region string
|
||||
if response.Region != nil {
|
||||
region = *response.Region
|
||||
} else if model.Region.ValueString() != "" {
|
||||
region = model.Region.ValueString()
|
||||
} else {
|
||||
return fmt.Errorf("region is not present")
|
||||
}
|
||||
|
||||
var orgId string
|
||||
if response.OrgId != nil {
|
||||
orgId = *response.OrgId
|
||||
} else if model.OrgId.ValueString() != "" {
|
||||
orgId = model.OrgId.ValueString()
|
||||
} else {
|
||||
return fmt.Errorf("org id is not present")
|
||||
}
|
||||
|
||||
var userId string
|
||||
if response.Guid != nil {
|
||||
userId = *response.Guid
|
||||
if model.UserId.ValueString() != "" && userId != model.UserId.ValueString() {
|
||||
return fmt.Errorf("user id mismatch in response and model")
|
||||
}
|
||||
} else if model.UserId.ValueString() != "" {
|
||||
userId = model.UserId.ValueString()
|
||||
} else {
|
||||
return fmt.Errorf("user id is not present")
|
||||
}
|
||||
|
||||
model.Id = utils.BuildInternalTerraformId(projectId, region, orgId, userId)
|
||||
model.Region = types.StringValue(region)
|
||||
model.PlatformId = types.StringPointerValue(response.PlatformId)
|
||||
model.ProjectId = types.StringValue(projectId)
|
||||
model.OrgId = types.StringValue(orgId)
|
||||
model.UserId = types.StringValue(userId)
|
||||
model.UserName = types.StringPointerValue(response.Username)
|
||||
model.CreateAt = types.StringValue(response.CreatedAt.String())
|
||||
model.UpdatedAt = types.StringValue(response.UpdatedAt.String())
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
package organizationmanager
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/stackitcloud/stackit-sdk-go/core/utils"
|
||||
"github.com/stackitcloud/stackit-sdk-go/services/scf"
|
||||
)
|
||||
|
||||
func TestMapFieldsDataSource(t *testing.T) {
|
||||
createdTime, err := time.Parse("2006-01-02 15:04:05 -0700 MST", "2025-01-01 00:00:00 +0000 UTC")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse test time: %v", err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
description string
|
||||
input *scf.OrgManager
|
||||
expected *DataSourceModel
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
description: "minimal_input",
|
||||
input: &scf.OrgManager{
|
||||
Guid: utils.Ptr(testUserId),
|
||||
OrgId: utils.Ptr(testOrgId),
|
||||
ProjectId: utils.Ptr(testProjectId),
|
||||
Region: utils.Ptr(testRegion),
|
||||
CreatedAt: &createdTime,
|
||||
UpdatedAt: &createdTime,
|
||||
},
|
||||
expected: &DataSourceModel{
|
||||
Id: types.StringValue(fmt.Sprintf("%s,%s,%s,%s", testProjectId, testRegion, testOrgId, testUserId)),
|
||||
UserId: types.StringValue(testUserId),
|
||||
OrgId: types.StringValue(testOrgId),
|
||||
ProjectId: types.StringValue(testProjectId),
|
||||
Region: types.StringValue(testRegion),
|
||||
UserName: types.StringNull(),
|
||||
PlatformId: types.StringNull(),
|
||||
CreateAt: types.StringValue("2025-01-01 00:00:00 +0000 UTC"),
|
||||
UpdatedAt: types.StringValue("2025-01-01 00:00:00 +0000 UTC"),
|
||||
},
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
description: "max_input",
|
||||
input: &scf.OrgManager{
|
||||
Guid: utils.Ptr(testUserId),
|
||||
OrgId: utils.Ptr(testOrgId),
|
||||
ProjectId: utils.Ptr(testProjectId),
|
||||
PlatformId: utils.Ptr(testPlatformId),
|
||||
Region: utils.Ptr(testRegion),
|
||||
CreatedAt: &createdTime,
|
||||
UpdatedAt: &createdTime,
|
||||
Username: utils.Ptr("test-user"),
|
||||
},
|
||||
expected: &DataSourceModel{
|
||||
Id: types.StringValue(fmt.Sprintf("%s,%s,%s,%s", testProjectId, testRegion, testOrgId, testUserId)),
|
||||
UserId: types.StringValue(testUserId),
|
||||
OrgId: types.StringValue(testOrgId),
|
||||
ProjectId: types.StringValue(testProjectId),
|
||||
PlatformId: types.StringValue(testPlatformId),
|
||||
Region: types.StringValue(testRegion),
|
||||
UserName: types.StringValue("test-user"),
|
||||
CreateAt: types.StringValue("2025-01-01 00:00:00 +0000 UTC"),
|
||||
UpdatedAt: types.StringValue("2025-01-01 00:00:00 +0000 UTC"),
|
||||
},
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
description: "nil_org",
|
||||
input: nil,
|
||||
expected: nil,
|
||||
isValid: false,
|
||||
},
|
||||
{
|
||||
description: "empty_org",
|
||||
input: &scf.OrgManager{},
|
||||
expected: nil,
|
||||
isValid: false,
|
||||
},
|
||||
{
|
||||
description: "missing_id",
|
||||
input: &scf.OrgManager{
|
||||
Username: utils.Ptr("scf-missing-id"),
|
||||
},
|
||||
expected: nil,
|
||||
isValid: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
state := &DataSourceModel{}
|
||||
if tt.expected != nil {
|
||||
state.ProjectId = tt.expected.ProjectId
|
||||
}
|
||||
err := mapFieldsDataSource(tt.input, state)
|
||||
|
||||
if tt.isValid && err != nil {
|
||||
t.Fatalf("expected success, got error: %v", err)
|
||||
}
|
||||
if !tt.isValid && err == nil {
|
||||
t.Fatalf("expected error, got nil")
|
||||
}
|
||||
if tt.isValid {
|
||||
if diff := cmp.Diff(tt.expected, state); diff != "" {
|
||||
t.Errorf("unexpected diff (-want +got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
471
stackit/internal/services/scf/organizationmanager/resource.go
Normal file
471
stackit/internal/services/scf/organizationmanager/resource.go
Normal file
|
|
@ -0,0 +1,471 @@
|
|||
package organizationmanager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
|
||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
|
||||
"github.com/stackitcloud/stackit-sdk-go/services/scf"
|
||||
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
|
||||
scfUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/scf/utils"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate"
|
||||
)
|
||||
|
||||
// Ensure the implementation satisfies the expected interfaces.
|
||||
var (
|
||||
_ resource.Resource = &scfOrganizationManagerResource{}
|
||||
_ resource.ResourceWithConfigure = &scfOrganizationManagerResource{}
|
||||
_ resource.ResourceWithImportState = &scfOrganizationManagerResource{}
|
||||
_ resource.ResourceWithModifyPlan = &scfOrganizationManagerResource{}
|
||||
)
|
||||
|
||||
type Model struct {
|
||||
Id types.String `tfsdk:"id"` // Required by Terraform
|
||||
Region types.String `tfsdk:"region"`
|
||||
PlatformId types.String `tfsdk:"platform_id"`
|
||||
ProjectId types.String `tfsdk:"project_id"`
|
||||
OrgId types.String `tfsdk:"org_id"`
|
||||
UserId types.String `tfsdk:"user_id"`
|
||||
UserName types.String `tfsdk:"username"`
|
||||
Password types.String `tfsdk:"password"`
|
||||
CreateAt types.String `tfsdk:"created_at"`
|
||||
UpdatedAt types.String `tfsdk:"updated_at"`
|
||||
}
|
||||
|
||||
// NewScfOrganizationManagerResource is a helper function to create a new scf organization manager resource.
|
||||
func NewScfOrganizationManagerResource() resource.Resource {
|
||||
return &scfOrganizationManagerResource{}
|
||||
}
|
||||
|
||||
// scfOrganizationManagerResource implements the resource interface for scf organization manager.
|
||||
type scfOrganizationManagerResource struct {
|
||||
client *scf.APIClient
|
||||
providerData core.ProviderData
|
||||
}
|
||||
|
||||
// descriptions for the attributes in the Schema
|
||||
var descriptions = map[string]string{
|
||||
"id": "Terraform's internal resource ID, structured as \"`project_id`,`region`,`org_id`,`user_id`\".",
|
||||
"region": "The region where the organization of the organization manager is located. If not defined, the provider region is used",
|
||||
"platform_id": "The ID of the platform associated with the organization of the organization manager",
|
||||
"project_id": "The ID of the project associated with the organization of the organization manager",
|
||||
"org_id": "The ID of the Cloud Foundry Organization",
|
||||
"user_id": "The ID of the organization manager user",
|
||||
"username": "An auto-generated organization manager user name",
|
||||
"password": "An auto-generated password",
|
||||
"created_at": "The time when the organization manager was created",
|
||||
"updated_at": "The time when the organization manager was last updated",
|
||||
}
|
||||
|
||||
func (s *scfOrganizationManagerResource) Configure(ctx context.Context, request resource.ConfigureRequest, response *resource.ConfigureResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
var ok bool
|
||||
s.providerData, ok = conversion.ParseProviderData(ctx, request.ProviderData, &response.Diagnostics)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
apiClient := scfUtils.ConfigureClient(ctx, &s.providerData, &response.Diagnostics)
|
||||
if response.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
s.client = apiClient
|
||||
tflog.Info(ctx, "scf client configured")
|
||||
}
|
||||
|
||||
func (s *scfOrganizationManagerResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
response.TypeName = request.ProviderTypeName + "_scf_organization_manager"
|
||||
}
|
||||
|
||||
// ModifyPlan implements resource.ResourceWithModifyPlan.
|
||||
// Use the modifier to set the effective region in the current plan.
|
||||
func (r *scfOrganizationManagerResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
var configModel Model
|
||||
// skip initial empty configuration to avoid follow-up errors
|
||||
if req.Config.Raw.IsNull() {
|
||||
return
|
||||
}
|
||||
resp.Diagnostics.Append(req.Config.Get(ctx, &configModel)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
var planModel Model
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &planModel)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
utils.AdaptRegion(ctx, configModel.Region, &planModel.Region, r.providerData.GetRegion(), resp)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.Plan.Set(ctx, planModel)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (s *scfOrganizationManagerResource) Schema(_ context.Context, _ resource.SchemaRequest, response *resource.SchemaResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
response.Schema = schema.Schema{
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{
|
||||
Description: descriptions["id"],
|
||||
Computed: true,
|
||||
},
|
||||
"region": schema.StringAttribute{
|
||||
Description: descriptions["region"],
|
||||
Computed: true,
|
||||
Optional: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
},
|
||||
"platform_id": schema.StringAttribute{
|
||||
Description: descriptions["platform_id"],
|
||||
Computed: true,
|
||||
Validators: []validator.String{
|
||||
validate.UUID(),
|
||||
validate.NoSeparator(),
|
||||
},
|
||||
},
|
||||
"project_id": schema.StringAttribute{
|
||||
Description: descriptions["project_id"],
|
||||
Required: true,
|
||||
Validators: []validator.String{
|
||||
validate.UUID(),
|
||||
validate.NoSeparator(),
|
||||
},
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
},
|
||||
"org_id": schema.StringAttribute{
|
||||
Description: descriptions["org_id"],
|
||||
Required: true,
|
||||
Validators: []validator.String{
|
||||
validate.UUID(),
|
||||
validate.NoSeparator(),
|
||||
},
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
},
|
||||
"user_id": schema.StringAttribute{
|
||||
Description: descriptions["user_id"],
|
||||
Computed: true,
|
||||
Validators: []validator.String{
|
||||
validate.UUID(),
|
||||
validate.NoSeparator(),
|
||||
},
|
||||
},
|
||||
"username": schema.StringAttribute{
|
||||
Description: descriptions["username"],
|
||||
Computed: true,
|
||||
Validators: []validator.String{
|
||||
stringvalidator.LengthBetween(1, 255),
|
||||
},
|
||||
},
|
||||
"password": schema.StringAttribute{
|
||||
Description: descriptions["password"],
|
||||
Computed: true,
|
||||
Sensitive: true,
|
||||
Validators: []validator.String{
|
||||
stringvalidator.LengthBetween(1, 255),
|
||||
},
|
||||
},
|
||||
"created_at": schema.StringAttribute{
|
||||
Description: descriptions["created_at"],
|
||||
Computed: true,
|
||||
},
|
||||
"updated_at": schema.StringAttribute{
|
||||
Description: descriptions["updated_at"],
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
Description: "STACKIT Cloud Foundry organization manager resource schema.",
|
||||
}
|
||||
}
|
||||
|
||||
func (s *scfOrganizationManagerResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
// Retrieve the planned values for the resource.
|
||||
var model Model
|
||||
diags := request.Plan.Get(ctx, &model)
|
||||
response.Diagnostics.Append(diags...)
|
||||
if response.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// Set logging context with the project ID and username.
|
||||
projectId := model.ProjectId.ValueString()
|
||||
orgId := model.OrgId.ValueString()
|
||||
userName := model.UserName.ValueString()
|
||||
region := model.Region.ValueString()
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "username", userName)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
|
||||
response.Diagnostics.Append(diags...)
|
||||
if response.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// Create the new scf organization manager via the API client.
|
||||
scfOrgManagerCreateResponse, err := s.client.CreateOrgManagerExecute(ctx, projectId, region, orgId)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &response.Diagnostics, "Error creating scf organization manager", fmt.Sprintf("Calling API to create org manager: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
err = mapFieldsCreate(scfOrgManagerCreateResponse, &model)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &response.Diagnostics, "Error creating scf organization manager", fmt.Sprintf("Mapping fields: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Set the state with fully populated data.
|
||||
diags = response.State.Set(ctx, model)
|
||||
response.Diagnostics.Append(diags...)
|
||||
if response.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
tflog.Info(ctx, "Scf organization manager created")
|
||||
}
|
||||
|
||||
func (s *scfOrganizationManagerResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
// Retrieve the current state of the resource.
|
||||
var model Model
|
||||
diags := request.State.Get(ctx, &model)
|
||||
response.Diagnostics.Append(diags...)
|
||||
if response.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// Extract the project ID, region and org id of the model
|
||||
projectId := model.ProjectId.ValueString()
|
||||
orgId := model.OrgId.ValueString()
|
||||
region := s.providerData.GetRegionWithOverride(model.Region)
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "org_id", orgId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
|
||||
// Read the current scf organization manager via orgId
|
||||
scfOrgManager, err := s.client.GetOrgManagerExecute(ctx, projectId, region, orgId)
|
||||
if err != nil {
|
||||
var oapiErr *oapierror.GenericOpenAPIError
|
||||
ok := errors.As(err, &oapiErr)
|
||||
if ok && oapiErr.StatusCode == http.StatusNotFound {
|
||||
core.LogAndAddWarning(ctx, &response.Diagnostics, "SCF Organization manager not found", "SCF Organization manager not found, remove from state")
|
||||
response.State.RemoveResource(ctx)
|
||||
return
|
||||
}
|
||||
core.LogAndAddError(ctx, &response.Diagnostics, "Error reading scf organization manager", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
err = mapFieldsRead(scfOrgManager, &model)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &response.Diagnostics, "Error reading scf organization manager", fmt.Sprintf("Processing API response: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Set the updated state.
|
||||
diags = response.State.Set(ctx, &model)
|
||||
response.Diagnostics.Append(diags...)
|
||||
tflog.Info(ctx, fmt.Sprintf("read scf organization manager %s", orgId))
|
||||
}
|
||||
|
||||
func (s *scfOrganizationManagerResource) Update(ctx context.Context, _ resource.UpdateRequest, response *resource.UpdateResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
// organization manager cannot be updated, so we log an error.
|
||||
core.LogAndAddError(ctx, &response.Diagnostics, "Error updating organization manager", "Organization Manager can't be updated")
|
||||
}
|
||||
|
||||
func (s *scfOrganizationManagerResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
// Retrieve current state of the resource.
|
||||
var model Model
|
||||
diags := request.State.Get(ctx, &model)
|
||||
response.Diagnostics.Append(diags...)
|
||||
if response.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
orgId := model.OrgId.ValueString()
|
||||
region := model.Region.ValueString()
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "org_id", orgId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
|
||||
// Call API to delete the existing scf organization manager.
|
||||
_, err := s.client.DeleteOrgManagerExecute(ctx, projectId, region, orgId)
|
||||
if err != nil {
|
||||
var oapiErr *oapierror.GenericOpenAPIError
|
||||
ok := errors.As(err, &oapiErr)
|
||||
if ok && oapiErr.StatusCode == http.StatusGone {
|
||||
tflog.Info(ctx, "Scf organization manager was already deleted")
|
||||
return
|
||||
}
|
||||
core.LogAndAddError(ctx, &response.Diagnostics, "Error deleting scf organization manager", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
}
|
||||
tflog.Info(ctx, "Scf organization manager deleted")
|
||||
}
|
||||
|
||||
func (s *scfOrganizationManagerResource) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
// Split the import identifier to extract project ID, region org ID and user ID.
|
||||
idParts := strings.Split(request.ID, core.Separator)
|
||||
|
||||
// Ensure the import identifier format is correct.
|
||||
if len(idParts) != 4 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" || idParts[3] == "" {
|
||||
core.LogAndAddError(ctx, &response.Diagnostics,
|
||||
"Error importing scf organization manager",
|
||||
fmt.Sprintf("Expected import identifier with format: [project_id],[region],[org_id],[user_id] Got: %q", request.ID),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
projectId := idParts[0]
|
||||
region := idParts[1]
|
||||
orgId := idParts[2]
|
||||
userId := idParts[3]
|
||||
// Set the project id, region organization id and user id in the state
|
||||
response.Diagnostics.Append(response.State.SetAttribute(ctx, path.Root("project_id"), projectId)...)
|
||||
response.Diagnostics.Append(response.State.SetAttribute(ctx, path.Root("region"), region)...)
|
||||
response.Diagnostics.Append(response.State.SetAttribute(ctx, path.Root("org_id"), orgId)...)
|
||||
response.Diagnostics.Append(response.State.SetAttribute(ctx, path.Root("user_id"), userId)...)
|
||||
tflog.Info(ctx, "Scf organization manager state imported")
|
||||
}
|
||||
|
||||
func mapFieldsCreate(response *scf.OrgManagerResponse, model *Model) error {
|
||||
if response == nil {
|
||||
return fmt.Errorf("response input is nil")
|
||||
}
|
||||
if model == nil {
|
||||
return fmt.Errorf("model input is nil")
|
||||
}
|
||||
|
||||
var projectId string
|
||||
if response.ProjectId != nil {
|
||||
projectId = *response.ProjectId
|
||||
} else if model.ProjectId.ValueString() != "" {
|
||||
projectId = model.ProjectId.ValueString()
|
||||
} else {
|
||||
return fmt.Errorf("project id is not present")
|
||||
}
|
||||
|
||||
var region string
|
||||
if response.Region != nil {
|
||||
region = *response.Region
|
||||
} else if model.Region.ValueString() != "" {
|
||||
region = model.Region.ValueString()
|
||||
} else {
|
||||
return fmt.Errorf("region is not present")
|
||||
}
|
||||
|
||||
var orgId string
|
||||
if response.OrgId != nil {
|
||||
orgId = *response.OrgId
|
||||
} else if model.OrgId.ValueString() != "" {
|
||||
orgId = model.OrgId.ValueString()
|
||||
} else {
|
||||
return fmt.Errorf("org id is not present")
|
||||
}
|
||||
|
||||
var userId string
|
||||
if response.Guid != nil {
|
||||
userId = *response.Guid
|
||||
} else if model.UserId.ValueString() != "" {
|
||||
userId = model.UserId.ValueString()
|
||||
} else {
|
||||
return fmt.Errorf("user id is not present")
|
||||
}
|
||||
|
||||
model.Id = utils.BuildInternalTerraformId(projectId, region, orgId, userId)
|
||||
model.Region = types.StringValue(region)
|
||||
model.PlatformId = types.StringPointerValue(response.PlatformId)
|
||||
model.ProjectId = types.StringValue(projectId)
|
||||
model.OrgId = types.StringValue(orgId)
|
||||
model.UserId = types.StringValue(userId)
|
||||
model.UserName = types.StringPointerValue(response.Username)
|
||||
model.Password = types.StringPointerValue(response.Password)
|
||||
model.CreateAt = types.StringValue(response.CreatedAt.String())
|
||||
model.UpdatedAt = types.StringValue(response.UpdatedAt.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
func mapFieldsRead(response *scf.OrgManager, model *Model) error {
|
||||
if response == nil {
|
||||
return fmt.Errorf("response input is nil")
|
||||
}
|
||||
if model == nil {
|
||||
return fmt.Errorf("model input is nil")
|
||||
}
|
||||
|
||||
var projectId string
|
||||
if response.ProjectId != nil {
|
||||
projectId = *response.ProjectId
|
||||
} else if model.ProjectId.ValueString() != "" {
|
||||
projectId = model.ProjectId.ValueString()
|
||||
} else {
|
||||
return fmt.Errorf("project id is not present")
|
||||
}
|
||||
|
||||
var region string
|
||||
if response.Region != nil {
|
||||
region = *response.Region
|
||||
} else if model.Region.ValueString() != "" {
|
||||
region = model.Region.ValueString()
|
||||
} else {
|
||||
return fmt.Errorf("region is not present")
|
||||
}
|
||||
|
||||
var orgId string
|
||||
if response.OrgId != nil {
|
||||
orgId = *response.OrgId
|
||||
} else if model.OrgId.ValueString() != "" {
|
||||
orgId = model.OrgId.ValueString()
|
||||
} else {
|
||||
return fmt.Errorf("org id is not present")
|
||||
}
|
||||
|
||||
var userId string
|
||||
if response.Guid != nil {
|
||||
userId = *response.Guid
|
||||
if model.UserId.ValueString() != "" && userId != model.UserId.ValueString() {
|
||||
return fmt.Errorf("user id mismatch in response and model")
|
||||
}
|
||||
} else if model.UserId.ValueString() != "" {
|
||||
userId = model.UserId.ValueString()
|
||||
} else {
|
||||
return fmt.Errorf("user id is not present")
|
||||
}
|
||||
|
||||
model.Id = utils.BuildInternalTerraformId(projectId, region, orgId, userId)
|
||||
model.Region = types.StringValue(region)
|
||||
model.PlatformId = types.StringPointerValue(response.PlatformId)
|
||||
model.ProjectId = types.StringValue(projectId)
|
||||
model.OrgId = types.StringValue(orgId)
|
||||
model.UserId = types.StringValue(userId)
|
||||
model.UserName = types.StringPointerValue(response.Username)
|
||||
model.CreateAt = types.StringValue(response.CreatedAt.String())
|
||||
model.UpdatedAt = types.StringValue(response.UpdatedAt.String())
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,233 @@
|
|||
package organizationmanager
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/uuid"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/stackitcloud/stackit-sdk-go/core/utils"
|
||||
"github.com/stackitcloud/stackit-sdk-go/services/scf"
|
||||
)
|
||||
|
||||
var (
|
||||
testOrgId = uuid.New().String()
|
||||
testProjectId = uuid.New().String()
|
||||
testPlatformId = uuid.New().String()
|
||||
testUserId = uuid.New().String()
|
||||
testRegion = "eu01"
|
||||
)
|
||||
|
||||
func TestMapFields(t *testing.T) {
|
||||
createdTime, err := time.Parse("2006-01-02 15:04:05 -0700 MST", "2025-01-01 00:00:00 +0000 UTC")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse test time: %v", err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
description string
|
||||
input *scf.OrgManager
|
||||
expected *Model
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
description: "minimal_input",
|
||||
input: &scf.OrgManager{
|
||||
Guid: utils.Ptr(testUserId),
|
||||
OrgId: utils.Ptr(testOrgId),
|
||||
ProjectId: utils.Ptr(testProjectId),
|
||||
Region: utils.Ptr(testRegion),
|
||||
CreatedAt: &createdTime,
|
||||
UpdatedAt: &createdTime,
|
||||
},
|
||||
expected: &Model{
|
||||
Id: types.StringValue(fmt.Sprintf("%s,%s,%s,%s", testProjectId, testRegion, testOrgId, testUserId)),
|
||||
UserId: types.StringValue(testUserId),
|
||||
OrgId: types.StringValue(testOrgId),
|
||||
ProjectId: types.StringValue(testProjectId),
|
||||
Region: types.StringValue(testRegion),
|
||||
UserName: types.StringNull(),
|
||||
PlatformId: types.StringNull(),
|
||||
CreateAt: types.StringValue("2025-01-01 00:00:00 +0000 UTC"),
|
||||
UpdatedAt: types.StringValue("2025-01-01 00:00:00 +0000 UTC"),
|
||||
},
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
description: "max_input",
|
||||
input: &scf.OrgManager{
|
||||
Guid: utils.Ptr(testUserId),
|
||||
OrgId: utils.Ptr(testOrgId),
|
||||
ProjectId: utils.Ptr(testProjectId),
|
||||
PlatformId: utils.Ptr(testPlatformId),
|
||||
Region: utils.Ptr(testRegion),
|
||||
CreatedAt: &createdTime,
|
||||
UpdatedAt: &createdTime,
|
||||
Username: utils.Ptr("test-user"),
|
||||
},
|
||||
expected: &Model{
|
||||
Id: types.StringValue(fmt.Sprintf("%s,%s,%s,%s", testProjectId, testRegion, testOrgId, testUserId)),
|
||||
UserId: types.StringValue(testUserId),
|
||||
OrgId: types.StringValue(testOrgId),
|
||||
ProjectId: types.StringValue(testProjectId),
|
||||
PlatformId: types.StringValue(testPlatformId),
|
||||
Region: types.StringValue(testRegion),
|
||||
Password: types.StringNull(),
|
||||
UserName: types.StringValue("test-user"),
|
||||
CreateAt: types.StringValue("2025-01-01 00:00:00 +0000 UTC"),
|
||||
UpdatedAt: types.StringValue("2025-01-01 00:00:00 +0000 UTC"),
|
||||
},
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
description: "nil_org",
|
||||
input: nil,
|
||||
expected: nil,
|
||||
isValid: false,
|
||||
},
|
||||
{
|
||||
description: "empty_org",
|
||||
input: &scf.OrgManager{},
|
||||
expected: nil,
|
||||
isValid: false,
|
||||
},
|
||||
{
|
||||
description: "missing_id",
|
||||
input: &scf.OrgManager{
|
||||
Username: utils.Ptr("scf-missing-id"),
|
||||
},
|
||||
expected: nil,
|
||||
isValid: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
state := &Model{}
|
||||
if tt.expected != nil {
|
||||
state.ProjectId = tt.expected.ProjectId
|
||||
}
|
||||
err := mapFieldsRead(tt.input, state)
|
||||
|
||||
if tt.isValid && err != nil {
|
||||
t.Fatalf("expected success, got error: %v", err)
|
||||
}
|
||||
if !tt.isValid && err == nil {
|
||||
t.Fatalf("expected error, got nil")
|
||||
}
|
||||
if tt.isValid {
|
||||
if diff := cmp.Diff(tt.expected, state); diff != "" {
|
||||
t.Errorf("unexpected diff (-want +got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapFieldsCreate(t *testing.T) {
|
||||
createdTime, err := time.Parse("2006-01-02 15:04:05 -0700 MST", "2025-01-01 00:00:00 +0000 UTC")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse test time: %v", err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
description string
|
||||
input *scf.OrgManagerResponse
|
||||
expected *Model
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
description: "minimal_input",
|
||||
input: &scf.OrgManagerResponse{
|
||||
Guid: utils.Ptr(testUserId),
|
||||
OrgId: utils.Ptr(testOrgId),
|
||||
ProjectId: utils.Ptr(testProjectId),
|
||||
Region: utils.Ptr(testRegion),
|
||||
CreatedAt: &createdTime,
|
||||
UpdatedAt: &createdTime,
|
||||
},
|
||||
expected: &Model{
|
||||
Id: types.StringValue(fmt.Sprintf("%s,%s,%s,%s", testProjectId, testRegion, testOrgId, testUserId)),
|
||||
UserId: types.StringValue(testUserId),
|
||||
OrgId: types.StringValue(testOrgId),
|
||||
ProjectId: types.StringValue(testProjectId),
|
||||
Region: types.StringValue(testRegion),
|
||||
UserName: types.StringNull(),
|
||||
PlatformId: types.StringNull(),
|
||||
Password: types.StringNull(),
|
||||
CreateAt: types.StringValue("2025-01-01 00:00:00 +0000 UTC"),
|
||||
UpdatedAt: types.StringValue("2025-01-01 00:00:00 +0000 UTC"),
|
||||
},
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
description: "max_input",
|
||||
input: &scf.OrgManagerResponse{
|
||||
Guid: utils.Ptr(testUserId),
|
||||
OrgId: utils.Ptr(testOrgId),
|
||||
ProjectId: utils.Ptr(testProjectId),
|
||||
PlatformId: utils.Ptr(testPlatformId),
|
||||
Region: utils.Ptr(testRegion),
|
||||
CreatedAt: &createdTime,
|
||||
UpdatedAt: &createdTime,
|
||||
Username: utils.Ptr("test-user"),
|
||||
Password: utils.Ptr("test-password"),
|
||||
},
|
||||
expected: &Model{
|
||||
Id: types.StringValue(fmt.Sprintf("%s,%s,%s,%s", testProjectId, testRegion, testOrgId, testUserId)),
|
||||
UserId: types.StringValue(testUserId),
|
||||
OrgId: types.StringValue(testOrgId),
|
||||
ProjectId: types.StringValue(testProjectId),
|
||||
PlatformId: types.StringValue(testPlatformId),
|
||||
Region: types.StringValue(testRegion),
|
||||
UserName: types.StringValue("test-user"),
|
||||
Password: types.StringValue("test-password"),
|
||||
CreateAt: types.StringValue("2025-01-01 00:00:00 +0000 UTC"),
|
||||
UpdatedAt: types.StringValue("2025-01-01 00:00:00 +0000 UTC"),
|
||||
},
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
description: "nil_org",
|
||||
input: nil,
|
||||
expected: nil,
|
||||
isValid: false,
|
||||
},
|
||||
{
|
||||
description: "empty_org",
|
||||
input: &scf.OrgManagerResponse{},
|
||||
expected: nil,
|
||||
isValid: false,
|
||||
},
|
||||
{
|
||||
description: "missing_id",
|
||||
input: &scf.OrgManagerResponse{
|
||||
Username: utils.Ptr("scf-missing-id"),
|
||||
},
|
||||
expected: nil,
|
||||
isValid: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
state := &Model{}
|
||||
if tt.expected != nil {
|
||||
state.ProjectId = tt.expected.ProjectId
|
||||
}
|
||||
err := mapFieldsCreate(tt.input, state)
|
||||
|
||||
if tt.isValid && err != nil {
|
||||
t.Fatalf("expected success, got error: %v", err)
|
||||
}
|
||||
if !tt.isValid && err == nil {
|
||||
t.Fatalf("expected error, got nil")
|
||||
}
|
||||
if tt.isValid {
|
||||
if diff := cmp.Diff(tt.expected, state); diff != "" {
|
||||
t.Errorf("unexpected diff (-want +got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
219
stackit/internal/services/scf/platform/datasource.go
Normal file
219
stackit/internal/services/scf/platform/datasource.go
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
package platform
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
"github.com/stackitcloud/stackit-sdk-go/services/scf"
|
||||
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
|
||||
scfUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/scf/utils"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate"
|
||||
)
|
||||
|
||||
// Ensure the implementation satisfies the expected interfaces.
|
||||
var (
|
||||
_ datasource.DataSource = &scfPlatformDataSource{}
|
||||
_ datasource.DataSourceWithConfigure = &scfPlatformDataSource{}
|
||||
)
|
||||
|
||||
// NewScfPlatformDataSource creates a new instance of the ScfPlatformDataSource.
|
||||
func NewScfPlatformDataSource() datasource.DataSource {
|
||||
return &scfPlatformDataSource{}
|
||||
}
|
||||
|
||||
// scfPlatformDataSource is the datasource implementation.
|
||||
type scfPlatformDataSource struct {
|
||||
client *scf.APIClient
|
||||
providerData core.ProviderData
|
||||
}
|
||||
|
||||
func (s *scfPlatformDataSource) Configure(ctx context.Context, request datasource.ConfigureRequest, response *datasource.ConfigureResponse) {
|
||||
var ok bool
|
||||
s.providerData, ok = conversion.ParseProviderData(ctx, request.ProviderData, &response.Diagnostics)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
apiClient := scfUtils.ConfigureClient(ctx, &s.providerData, &response.Diagnostics)
|
||||
if response.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
s.client = apiClient
|
||||
tflog.Info(ctx, "scf client configured for platform")
|
||||
}
|
||||
|
||||
func (s *scfPlatformDataSource) Metadata(_ context.Context, request datasource.MetadataRequest, response *datasource.MetadataResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
response.TypeName = request.ProviderTypeName + "_scf_platform"
|
||||
}
|
||||
|
||||
type Model struct {
|
||||
Id types.String `tfsdk:"id"` // Required by Terraform
|
||||
PlatformId types.String `tfsdk:"platform_id"`
|
||||
ProjectId types.String `tfsdk:"project_id"`
|
||||
SystemId types.String `tfsdk:"system_id"`
|
||||
DisplayName types.String `tfsdk:"display_name"`
|
||||
Region types.String `tfsdk:"region"`
|
||||
ApiUrl types.String `tfsdk:"api_url"`
|
||||
ConsoleUrl types.String `tfsdk:"console_url"`
|
||||
}
|
||||
|
||||
// descriptions for the attributes in the Schema
|
||||
var descriptions = map[string]string{
|
||||
"id": "Terraform's internal resource ID, structured as \"`project_id`,`region`,`platform_id`\".",
|
||||
"platform_id": "The unique id of the platform",
|
||||
"project_id": "The ID of the project associated with the platform",
|
||||
"system_id": "The ID of the platform System",
|
||||
"display_name": "The name of the platform",
|
||||
"region": "The region where the platform is located. If not defined, the provider region is used",
|
||||
"api_url": "The CF API Url of the platform",
|
||||
"console_url": "The Stratos URL of the platform",
|
||||
}
|
||||
|
||||
func (s *scfPlatformDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, response *datasource.SchemaResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
response.Schema = schema.Schema{
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{
|
||||
Description: descriptions["id"],
|
||||
Computed: true,
|
||||
},
|
||||
"platform_id": schema.StringAttribute{
|
||||
Description: descriptions["platform_id"],
|
||||
Required: true,
|
||||
Validators: []validator.String{
|
||||
validate.UUID(),
|
||||
validate.NoSeparator(),
|
||||
},
|
||||
},
|
||||
"project_id": schema.StringAttribute{
|
||||
Description: descriptions["project_id"],
|
||||
Required: true,
|
||||
Validators: []validator.String{
|
||||
validate.UUID(),
|
||||
validate.NoSeparator(),
|
||||
},
|
||||
},
|
||||
"system_id": schema.StringAttribute{
|
||||
Description: descriptions["system_id"],
|
||||
Computed: true,
|
||||
},
|
||||
"display_name": schema.StringAttribute{
|
||||
Description: descriptions["display_name"],
|
||||
Computed: true,
|
||||
},
|
||||
"region": schema.StringAttribute{
|
||||
Description: descriptions["region"],
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
"api_url": schema.StringAttribute{
|
||||
Description: descriptions["api_url"],
|
||||
Computed: true,
|
||||
},
|
||||
"console_url": schema.StringAttribute{
|
||||
Description: descriptions["console_url"],
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
Description: "STACKIT Cloud Foundry Platform datasource schema.",
|
||||
}
|
||||
}
|
||||
|
||||
func (s *scfPlatformDataSource) Read(ctx context.Context, request datasource.ReadRequest, response *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
// Retrieve the current state of the resource.
|
||||
var model Model
|
||||
diags := request.Config.Get(ctx, &model)
|
||||
response.Diagnostics.Append(diags...)
|
||||
if response.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// Extract the project ID region and platform id of the model
|
||||
projectId := model.ProjectId.ValueString()
|
||||
platformId := model.PlatformId.ValueString()
|
||||
region := s.providerData.GetRegionWithOverride(model.Region)
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "platform_id", platformId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
|
||||
// Read the scf platform
|
||||
scfPlatformResponse, err := s.client.GetPlatformExecute(ctx, projectId, region, platformId)
|
||||
if err != nil {
|
||||
utils.LogError(
|
||||
ctx,
|
||||
&response.Diagnostics,
|
||||
err,
|
||||
"Reading scf platform",
|
||||
fmt.Sprintf("Platform with ID %q does not exist in project %q.", platformId, projectId),
|
||||
map[int]string{
|
||||
http.StatusForbidden: fmt.Sprintf("Platform with ID %q not found or forbidden access", platformId),
|
||||
},
|
||||
)
|
||||
response.State.RemoveResource(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
err = mapFields(scfPlatformResponse, &model)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &response.Diagnostics, "Error reading scf platform", fmt.Sprintf("Processing API response: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Set the updated state.
|
||||
diags = response.State.Set(ctx, &model)
|
||||
response.Diagnostics.Append(diags...)
|
||||
tflog.Info(ctx, fmt.Sprintf("read scf Platform %s", platformId))
|
||||
}
|
||||
|
||||
// mapFields maps a SCF Platform response to the model.
|
||||
func mapFields(response *scf.Platforms, model *Model) error {
|
||||
if response == nil {
|
||||
return fmt.Errorf("response input is nil")
|
||||
}
|
||||
if model == nil {
|
||||
return fmt.Errorf("model input is nil")
|
||||
}
|
||||
|
||||
var projectId string
|
||||
if model.ProjectId.ValueString() == "" {
|
||||
return fmt.Errorf("project id is not present")
|
||||
}
|
||||
projectId = model.ProjectId.ValueString()
|
||||
|
||||
var region string
|
||||
if response.Region != nil {
|
||||
region = *response.Region
|
||||
} else if model.Region.ValueString() != "" {
|
||||
region = model.Region.ValueString()
|
||||
} else {
|
||||
return fmt.Errorf("region is not present")
|
||||
}
|
||||
|
||||
var platformId string
|
||||
if response.Guid != nil {
|
||||
platformId = *response.Guid
|
||||
} else if model.PlatformId.ValueString() != "" {
|
||||
platformId = model.PlatformId.ValueString()
|
||||
} else {
|
||||
return fmt.Errorf("platform id is not present")
|
||||
}
|
||||
|
||||
// Build the ID
|
||||
model.Id = utils.BuildInternalTerraformId(projectId, region, platformId)
|
||||
model.PlatformId = types.StringValue(platformId)
|
||||
model.ProjectId = types.StringValue(projectId)
|
||||
model.SystemId = types.StringPointerValue(response.SystemId)
|
||||
model.DisplayName = types.StringPointerValue(response.DisplayName)
|
||||
model.Region = types.StringValue(region)
|
||||
model.ApiUrl = types.StringPointerValue(response.ApiUrl)
|
||||
model.ConsoleUrl = types.StringPointerValue(response.ConsoleUrl)
|
||||
return nil
|
||||
}
|
||||
109
stackit/internal/services/scf/platform/datasource_test.go
Normal file
109
stackit/internal/services/scf/platform/datasource_test.go
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
package platform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/uuid"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/stackitcloud/stackit-sdk-go/core/utils"
|
||||
"github.com/stackitcloud/stackit-sdk-go/services/scf"
|
||||
)
|
||||
|
||||
var (
|
||||
testProjectId = uuid.New().String()
|
||||
testPlatformId = uuid.New().String()
|
||||
testRegion = "eu01"
|
||||
)
|
||||
|
||||
func TestMapFields(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
input *scf.Platforms
|
||||
expected *Model
|
||||
isValid bool
|
||||
}{
|
||||
{
|
||||
description: "minimal_input",
|
||||
input: &scf.Platforms{
|
||||
Guid: utils.Ptr(testPlatformId),
|
||||
Region: utils.Ptr(testRegion),
|
||||
},
|
||||
expected: &Model{
|
||||
Id: types.StringValue(fmt.Sprintf("%s,%s,%s", testProjectId, testRegion, testPlatformId)),
|
||||
PlatformId: types.StringValue(testPlatformId),
|
||||
ProjectId: types.StringValue(testProjectId),
|
||||
Region: types.StringValue(testRegion),
|
||||
SystemId: types.StringNull(),
|
||||
DisplayName: types.StringNull(),
|
||||
ApiUrl: types.StringNull(),
|
||||
ConsoleUrl: types.StringNull(),
|
||||
},
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
description: "max_input",
|
||||
input: &scf.Platforms{
|
||||
Guid: utils.Ptr(testPlatformId),
|
||||
SystemId: utils.Ptr("eu01.01"),
|
||||
DisplayName: utils.Ptr("scf-full-org"),
|
||||
Region: utils.Ptr(testRegion),
|
||||
ApiUrl: utils.Ptr("https://example.scf.stackit.cloud"),
|
||||
ConsoleUrl: utils.Ptr("https://example.console.scf.stackit.cloud"),
|
||||
},
|
||||
expected: &Model{
|
||||
Id: types.StringValue(fmt.Sprintf("%s,%s,%s", testProjectId, testRegion, testPlatformId)),
|
||||
ProjectId: types.StringValue(testProjectId),
|
||||
PlatformId: types.StringValue(testPlatformId),
|
||||
Region: types.StringValue(testRegion),
|
||||
SystemId: types.StringValue("eu01.01"),
|
||||
DisplayName: types.StringValue("scf-full-org"),
|
||||
ApiUrl: types.StringValue("https://example.scf.stackit.cloud"),
|
||||
ConsoleUrl: types.StringValue("https://example.console.scf.stackit.cloud"),
|
||||
},
|
||||
isValid: true,
|
||||
},
|
||||
{
|
||||
description: "nil_org",
|
||||
input: nil,
|
||||
expected: nil,
|
||||
isValid: false,
|
||||
},
|
||||
{
|
||||
description: "empty_org",
|
||||
input: &scf.Platforms{},
|
||||
expected: nil,
|
||||
isValid: false,
|
||||
},
|
||||
{
|
||||
description: "missing_id",
|
||||
input: &scf.Platforms{
|
||||
DisplayName: utils.Ptr("scf-missing-id"),
|
||||
},
|
||||
expected: nil,
|
||||
isValid: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
state := &Model{}
|
||||
if tt.expected != nil {
|
||||
state.ProjectId = tt.expected.ProjectId
|
||||
}
|
||||
err := mapFields(tt.input, state)
|
||||
|
||||
if tt.isValid && err != nil {
|
||||
t.Fatalf("expected success, got error: %v", err)
|
||||
}
|
||||
if !tt.isValid && err == nil {
|
||||
t.Fatalf("expected error, got nil")
|
||||
}
|
||||
if tt.isValid {
|
||||
if diff := cmp.Diff(tt.expected, state); diff != "" {
|
||||
t.Errorf("unexpected diff (-want +got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
456
stackit/internal/services/scf/scf_acc_test.go
Normal file
456
stackit/internal/services/scf/scf_acc_test.go
Normal file
|
|
@ -0,0 +1,456 @@
|
|||
package scf
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"maps"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stackitcloud/stackit-sdk-go/services/scf"
|
||||
|
||||
"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/hashicorp/terraform-plugin-testing/terraform"
|
||||
stackitSdkConfig "github.com/stackitcloud/stackit-sdk-go/core/config"
|
||||
"github.com/stackitcloud/stackit-sdk-go/core/utils"
|
||||
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/testutil"
|
||||
)
|
||||
|
||||
//go:embed testdata/resource-min.tf
|
||||
var resourceMin string
|
||||
|
||||
//go:embed testdata/resource-max.tf
|
||||
var resourceMax string
|
||||
|
||||
var randName = acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)
|
||||
var nameMin = fmt.Sprintf("scf-min-%s-org", randName)
|
||||
var nameMinUpdated = fmt.Sprintf("scf-min-%s-upd-org", randName)
|
||||
var nameMax = fmt.Sprintf("scf-max-%s-org", randName)
|
||||
var nameMaxUpdated = fmt.Sprintf("scf-max-%s-upd-org", randName)
|
||||
|
||||
const (
|
||||
platformName = "Shared Cloud Foundry (public)"
|
||||
platformSystemId = "01.cf.eu01"
|
||||
platformIdMax = "0a3d1188-353a-4004-832c-53039c0e3868"
|
||||
platformApiUrl = "https://api.system.01.cf.eu01.stackit.cloud"
|
||||
platformConsoleUrl = "https://console.apps.01.cf.eu01.stackit.cloud"
|
||||
quotaIdMax = "e22cfe1a-0318-473f-88db-61d62dc629c0" // small
|
||||
quotaIdMaxUpdated = "5ea6b9ab-4048-4bd9-8a8a-5dd7fc40745d" // medium
|
||||
suspendedMax = true
|
||||
region = "eu01"
|
||||
)
|
||||
|
||||
var testConfigVarsMin = config.Variables{
|
||||
"project_id": config.StringVariable(testutil.ProjectId),
|
||||
"name": config.StringVariable(nameMin),
|
||||
}
|
||||
|
||||
var testConfigVarsMax = config.Variables{
|
||||
"project_id": config.StringVariable(testutil.ProjectId),
|
||||
"name": config.StringVariable(nameMax),
|
||||
"platform_id": config.StringVariable(platformIdMax),
|
||||
"quota_id": config.StringVariable(quotaIdMax),
|
||||
"suspended": config.BoolVariable(suspendedMax),
|
||||
"region": config.StringVariable(region),
|
||||
}
|
||||
|
||||
func testScfOrgConfigVarsMinUpdated() config.Variables {
|
||||
tempConfig := make(config.Variables, len(testConfigVarsMin))
|
||||
maps.Copy(tempConfig, testConfigVarsMin)
|
||||
// update scf organization to a new name
|
||||
tempConfig["name"] = config.StringVariable(nameMinUpdated)
|
||||
return tempConfig
|
||||
}
|
||||
|
||||
func testScfOrgConfigVarsMaxUpdated() config.Variables {
|
||||
tempConfig := make(config.Variables, len(testConfigVarsMax))
|
||||
maps.Copy(tempConfig, testConfigVarsMax)
|
||||
// update scf organization to a new name, unsuspend it and assign a new quota
|
||||
tempConfig["name"] = config.StringVariable(nameMaxUpdated)
|
||||
tempConfig["quota_id"] = config.StringVariable(quotaIdMaxUpdated)
|
||||
tempConfig["suspended"] = config.BoolVariable(!suspendedMax)
|
||||
return tempConfig
|
||||
}
|
||||
|
||||
func TestAccScfOrganizationMin(t *testing.T) {
|
||||
resource.Test(t, resource.TestCase{
|
||||
ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories,
|
||||
CheckDestroy: testAccCheckScfOrganizationDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
// Creation
|
||||
{
|
||||
ConfigVariables: testConfigVarsMin,
|
||||
Config: testutil.ScfProviderConfig() + resourceMin,
|
||||
Check: resource.ComposeAggregateTestCheckFunc(
|
||||
resource.TestCheckResourceAttr("stackit_scf_organization.org", "project_id", testutil.ConvertConfigVariable(testConfigVarsMin["project_id"])),
|
||||
resource.TestCheckResourceAttr("stackit_scf_organization.org", "name", testutil.ConvertConfigVariable(testConfigVarsMin["name"])),
|
||||
resource.TestCheckResourceAttrSet("stackit_scf_organization.org", "created_at"),
|
||||
resource.TestCheckResourceAttrSet("stackit_scf_organization.org", "platform_id"),
|
||||
resource.TestCheckResourceAttrSet("stackit_scf_organization.org", "org_id"),
|
||||
resource.TestCheckResourceAttrSet("stackit_scf_organization.org", "quota_id"),
|
||||
resource.TestCheckResourceAttrSet("stackit_scf_organization.org", "region"),
|
||||
resource.TestCheckResourceAttrSet("stackit_scf_organization.org", "status"),
|
||||
resource.TestCheckResourceAttrSet("stackit_scf_organization.org", "suspended"),
|
||||
resource.TestCheckResourceAttrSet("stackit_scf_organization.org", "updated_at"),
|
||||
resource.TestCheckResourceAttrSet("stackit_scf_organization_manager.orgmanager", "id"),
|
||||
resource.TestCheckResourceAttrSet("stackit_scf_organization_manager.orgmanager", "org_id"),
|
||||
resource.TestCheckResourceAttr("stackit_scf_organization_manager.orgmanager", "platform_id", testutil.ConvertConfigVariable(testConfigVarsMax["platform_id"])),
|
||||
resource.TestCheckResourceAttr("stackit_scf_organization_manager.orgmanager", "project_id", testutil.ConvertConfigVariable(testConfigVarsMax["project_id"])),
|
||||
resource.TestCheckResourceAttrSet("stackit_scf_organization_manager.orgmanager", "user_id"),
|
||||
resource.TestCheckResourceAttrSet("stackit_scf_organization_manager.orgmanager", "username"),
|
||||
resource.TestCheckResourceAttrSet("stackit_scf_organization_manager.orgmanager", "password"),
|
||||
resource.TestCheckResourceAttrSet("stackit_scf_organization_manager.orgmanager", "created_at"),
|
||||
resource.TestCheckResourceAttrSet("stackit_scf_organization_manager.orgmanager", "updated_at"),
|
||||
),
|
||||
},
|
||||
// Data source
|
||||
{
|
||||
ConfigVariables: testConfigVarsMin,
|
||||
Config: fmt.Sprintf(`
|
||||
%s
|
||||
data "stackit_scf_organization" "org" {
|
||||
project_id = stackit_scf_organization.org.project_id
|
||||
org_id = stackit_scf_organization.org.org_id
|
||||
}
|
||||
data "stackit_scf_organization_manager" "orgmanager" {
|
||||
org_id = stackit_scf_organization.org.org_id
|
||||
project_id = stackit_scf_organization.org.project_id
|
||||
}
|
||||
`, testutil.ScfProviderConfig()+resourceMin,
|
||||
),
|
||||
Check: resource.ComposeAggregateTestCheckFunc(
|
||||
// Instance
|
||||
resource.TestCheckResourceAttr("data.stackit_scf_organization.org", "project_id", testutil.ConvertConfigVariable(testConfigVarsMin["project_id"])),
|
||||
resource.TestCheckResourceAttrPair(
|
||||
"stackit_scf_organization.org", "project_id",
|
||||
"data.stackit_scf_organization.org", "project_id",
|
||||
),
|
||||
resource.TestCheckResourceAttrPair(
|
||||
"stackit_scf_organization.org", "created_at",
|
||||
"data.stackit_scf_organization.org", "created_at",
|
||||
),
|
||||
resource.TestCheckResourceAttrPair(
|
||||
"stackit_scf_organization.org", "name",
|
||||
"data.stackit_scf_organization.org", "name",
|
||||
),
|
||||
resource.TestCheckResourceAttrPair(
|
||||
"stackit_scf_organization.org", "platform_id",
|
||||
"data.stackit_scf_organization.org", "platform_id",
|
||||
),
|
||||
resource.TestCheckResourceAttrPair(
|
||||
"stackit_scf_organization.org", "org_id",
|
||||
"data.stackit_scf_organization.org", "org_id",
|
||||
),
|
||||
resource.TestCheckResourceAttrPair(
|
||||
"stackit_scf_organization.org", "quota_id",
|
||||
"data.stackit_scf_organization.org", "quota_id",
|
||||
),
|
||||
resource.TestCheckResourceAttrPair(
|
||||
"stackit_scf_organization.org", "region",
|
||||
"data.stackit_scf_organization.org", "region",
|
||||
),
|
||||
resource.TestCheckResourceAttrPair(
|
||||
"stackit_scf_organization.org", "status",
|
||||
"data.stackit_scf_organization.org", "status",
|
||||
),
|
||||
resource.TestCheckResourceAttrPair(
|
||||
"stackit_scf_organization.org", "suspended",
|
||||
"data.stackit_scf_organization.org", "suspended",
|
||||
),
|
||||
resource.TestCheckResourceAttrPair(
|
||||
"stackit_scf_organization.org", "updated_at",
|
||||
"data.stackit_scf_organization.org", "updated_at",
|
||||
),
|
||||
resource.TestCheckResourceAttrPair(
|
||||
"stackit_scf_organization.org", "region",
|
||||
"data.stackit_scf_organization_manager.orgmanager", "region",
|
||||
),
|
||||
resource.TestCheckResourceAttrPair(
|
||||
"stackit_scf_organization.org", "platform_id",
|
||||
"data.stackit_scf_organization_manager.orgmanager", "platform_id",
|
||||
),
|
||||
resource.TestCheckResourceAttrPair(
|
||||
"stackit_scf_organization.org", "project_id",
|
||||
"data.stackit_scf_organization_manager.orgmanager", "project_id",
|
||||
),
|
||||
resource.TestCheckResourceAttrPair(
|
||||
"stackit_scf_organization.org", "org_id",
|
||||
"data.stackit_scf_organization_manager.orgmanager", "org_id",
|
||||
),
|
||||
resource.TestCheckResourceAttrSet("data.stackit_scf_organization_manager.orgmanager", "user_id"),
|
||||
resource.TestCheckResourceAttrSet("data.stackit_scf_organization_manager.orgmanager", "username"),
|
||||
resource.TestCheckResourceAttrSet("data.stackit_scf_organization_manager.orgmanager", "created_at"),
|
||||
resource.TestCheckResourceAttrSet("data.stackit_scf_organization_manager.orgmanager", "updated_at"),
|
||||
),
|
||||
},
|
||||
// Import
|
||||
{
|
||||
ConfigVariables: testConfigVarsMin,
|
||||
ResourceName: "stackit_scf_organization.org",
|
||||
ImportStateIdFunc: func(s *terraform.State) (string, error) {
|
||||
r, ok := s.RootModule().Resources["stackit_scf_organization.org"]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("couldn't find resource stackit_scf_organization.org")
|
||||
}
|
||||
orgId, ok := r.Primary.Attributes["org_id"]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("couldn't find attribute org_id")
|
||||
}
|
||||
regionInAttributes, ok := r.Primary.Attributes["region"]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("couldn't find attribute region")
|
||||
}
|
||||
return fmt.Sprintf("%s,%s,%s", testutil.ProjectId, regionInAttributes, orgId), nil
|
||||
},
|
||||
ImportState: true,
|
||||
ImportStateVerify: true,
|
||||
},
|
||||
// Update
|
||||
{
|
||||
ConfigVariables: testScfOrgConfigVarsMinUpdated(),
|
||||
Config: testutil.ScfProviderConfig() + resourceMin,
|
||||
Check: resource.ComposeAggregateTestCheckFunc(
|
||||
resource.TestCheckResourceAttr("stackit_scf_organization.org", "project_id", testutil.ConvertConfigVariable(testScfOrgConfigVarsMinUpdated()["project_id"])),
|
||||
resource.TestCheckResourceAttr("stackit_scf_organization.org", "name", testutil.ConvertConfigVariable(testScfOrgConfigVarsMinUpdated()["name"])),
|
||||
resource.TestCheckResourceAttrSet("stackit_scf_organization.org", "created_at"),
|
||||
resource.TestCheckResourceAttrSet("stackit_scf_organization.org", "platform_id"),
|
||||
resource.TestCheckResourceAttrSet("stackit_scf_organization.org", "org_id"),
|
||||
resource.TestCheckResourceAttrSet("stackit_scf_organization.org", "quota_id"),
|
||||
resource.TestCheckResourceAttrSet("stackit_scf_organization.org", "region"),
|
||||
resource.TestCheckResourceAttrSet("stackit_scf_organization.org", "suspended"),
|
||||
resource.TestCheckResourceAttrSet("stackit_scf_organization.org", "updated_at"),
|
||||
),
|
||||
},
|
||||
// Deletion is done by the framework implicitly
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccScfOrgMax(t *testing.T) {
|
||||
resource.Test(t, resource.TestCase{
|
||||
ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories,
|
||||
CheckDestroy: testAccCheckScfOrganizationDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
// Creation
|
||||
{
|
||||
ConfigVariables: testConfigVarsMax,
|
||||
Config: testutil.ScfProviderConfig() + resourceMax,
|
||||
Check: resource.ComposeAggregateTestCheckFunc(
|
||||
resource.TestCheckResourceAttr("stackit_scf_organization.org", "project_id", testutil.ConvertConfigVariable(testConfigVarsMax["project_id"])),
|
||||
resource.TestCheckResourceAttr("stackit_scf_organization.org", "name", testutil.ConvertConfigVariable(testConfigVarsMax["name"])),
|
||||
resource.TestCheckResourceAttr("stackit_scf_organization.org", "platform_id", testutil.ConvertConfigVariable(testConfigVarsMax["platform_id"])),
|
||||
resource.TestCheckResourceAttr("stackit_scf_organization.org", "quota_id", testutil.ConvertConfigVariable(testConfigVarsMax["quota_id"])),
|
||||
resource.TestCheckResourceAttr("stackit_scf_organization.org", "suspended", testutil.ConvertConfigVariable(testConfigVarsMax["suspended"])),
|
||||
resource.TestCheckResourceAttrSet("stackit_scf_organization.org", "created_at"),
|
||||
resource.TestCheckResourceAttrSet("stackit_scf_organization.org", "org_id"),
|
||||
resource.TestCheckResourceAttrSet("stackit_scf_organization.org", "region"),
|
||||
resource.TestCheckResourceAttrSet("stackit_scf_organization.org", "updated_at"),
|
||||
resource.TestCheckResourceAttr("data.stackit_scf_platform.scf_platform", "platform_id", testutil.ConvertConfigVariable(testConfigVarsMax["platform_id"])),
|
||||
resource.TestCheckResourceAttr("data.stackit_scf_platform.scf_platform", "project_id", testutil.ConvertConfigVariable(testConfigVarsMax["project_id"])),
|
||||
resource.TestCheckResourceAttr("data.stackit_scf_platform.scf_platform", "display_name", platformName),
|
||||
resource.TestCheckResourceAttr("data.stackit_scf_platform.scf_platform", "system_id", platformSystemId),
|
||||
resource.TestCheckResourceAttr("data.stackit_scf_platform.scf_platform", "api_url", platformApiUrl),
|
||||
resource.TestCheckResourceAttr("data.stackit_scf_platform.scf_platform", "console_url", platformConsoleUrl),
|
||||
resource.TestCheckResourceAttrSet("stackit_scf_organization_manager.orgmanager", "id"),
|
||||
resource.TestCheckResourceAttrSet("stackit_scf_organization_manager.orgmanager", "org_id"),
|
||||
resource.TestCheckResourceAttr("stackit_scf_organization_manager.orgmanager", "platform_id", testutil.ConvertConfigVariable(testConfigVarsMax["platform_id"])),
|
||||
resource.TestCheckResourceAttr("stackit_scf_organization_manager.orgmanager", "project_id", testutil.ConvertConfigVariable(testConfigVarsMax["project_id"])),
|
||||
resource.TestCheckResourceAttrSet("stackit_scf_organization_manager.orgmanager", "user_id"),
|
||||
resource.TestCheckResourceAttrSet("stackit_scf_organization_manager.orgmanager", "username"),
|
||||
resource.TestCheckResourceAttrSet("stackit_scf_organization_manager.orgmanager", "password"),
|
||||
resource.TestCheckResourceAttrSet("stackit_scf_organization_manager.orgmanager", "created_at"),
|
||||
resource.TestCheckResourceAttrSet("stackit_scf_organization_manager.orgmanager", "updated_at"),
|
||||
),
|
||||
},
|
||||
// Data source
|
||||
{
|
||||
ConfigVariables: testConfigVarsMax,
|
||||
Config: fmt.Sprintf(`
|
||||
%s
|
||||
data "stackit_scf_organization" "org" {
|
||||
project_id = stackit_scf_organization.org.project_id
|
||||
org_id = stackit_scf_organization.org.org_id
|
||||
region = var.region
|
||||
}
|
||||
data "stackit_scf_organization_manager" "orgmanager" {
|
||||
org_id = stackit_scf_organization.org.org_id
|
||||
project_id = stackit_scf_organization.org.project_id
|
||||
}
|
||||
data "stackit_scf_platform" "platform" {
|
||||
platform_id = stackit_scf_organization.org.platform_id
|
||||
project_id = stackit_scf_organization.org.project_id
|
||||
}
|
||||
`, testutil.ScfProviderConfig()+resourceMax,
|
||||
),
|
||||
Check: resource.ComposeAggregateTestCheckFunc(
|
||||
// Instance
|
||||
resource.TestCheckResourceAttr("data.stackit_scf_organization.org", "project_id", testutil.ConvertConfigVariable(testConfigVarsMax["project_id"])),
|
||||
resource.TestCheckResourceAttrPair(
|
||||
"stackit_scf_organization.org", "project_id",
|
||||
"data.stackit_scf_organization.org", "project_id",
|
||||
),
|
||||
resource.TestCheckResourceAttrPair(
|
||||
"stackit_scf_organization.org", "created_at",
|
||||
"data.stackit_scf_organization.org", "created_at",
|
||||
),
|
||||
resource.TestCheckResourceAttrPair(
|
||||
"stackit_scf_organization.org", "name",
|
||||
"data.stackit_scf_organization.org", "name",
|
||||
),
|
||||
resource.TestCheckResourceAttrPair(
|
||||
"stackit_scf_organization.org", "platform_id",
|
||||
"data.stackit_scf_organization.org", "platform_id",
|
||||
),
|
||||
resource.TestCheckResourceAttrPair(
|
||||
"stackit_scf_organization.org", "org_id",
|
||||
"data.stackit_scf_organization.org", "org_id",
|
||||
),
|
||||
resource.TestCheckResourceAttrPair(
|
||||
"stackit_scf_organization.org", "quota_id",
|
||||
"data.stackit_scf_organization.org", "quota_id",
|
||||
),
|
||||
resource.TestCheckResourceAttrPair(
|
||||
"stackit_scf_organization.org", "region",
|
||||
"data.stackit_scf_organization.org", "region",
|
||||
),
|
||||
resource.TestCheckResourceAttrPair(
|
||||
"stackit_scf_organization.org", "status",
|
||||
"data.stackit_scf_organization.org", "status",
|
||||
),
|
||||
resource.TestCheckResourceAttrPair(
|
||||
"stackit_scf_organization.org", "suspended",
|
||||
"data.stackit_scf_organization.org", "suspended",
|
||||
),
|
||||
resource.TestCheckResourceAttrPair(
|
||||
"stackit_scf_organization.org", "updated_at",
|
||||
"data.stackit_scf_organization.org", "updated_at",
|
||||
),
|
||||
resource.TestCheckResourceAttrPair(
|
||||
"stackit_scf_organization.org", "platform_id",
|
||||
"data.stackit_scf_platform.platform", "platform_id",
|
||||
),
|
||||
resource.TestCheckResourceAttrPair(
|
||||
"stackit_scf_organization.org", "project_id",
|
||||
"data.stackit_scf_platform.platform", "project_id",
|
||||
),
|
||||
resource.TestCheckResourceAttr("data.stackit_scf_platform.platform", "display_name", platformName),
|
||||
resource.TestCheckResourceAttr("data.stackit_scf_platform.platform", "system_id", platformSystemId),
|
||||
resource.TestCheckResourceAttr("data.stackit_scf_platform.platform", "display_name", platformName),
|
||||
resource.TestCheckResourceAttr("data.stackit_scf_platform.platform", "region", region),
|
||||
resource.TestCheckResourceAttr("data.stackit_scf_platform.platform", "api_url", platformApiUrl),
|
||||
resource.TestCheckResourceAttr("data.stackit_scf_platform.platform", "console_url", platformConsoleUrl),
|
||||
resource.TestCheckResourceAttrPair(
|
||||
"stackit_scf_organization.org", "region",
|
||||
"data.stackit_scf_organization_manager.orgmanager", "region",
|
||||
),
|
||||
resource.TestCheckResourceAttrPair(
|
||||
"stackit_scf_organization.org", "platform_id",
|
||||
"data.stackit_scf_organization_manager.orgmanager", "platform_id",
|
||||
),
|
||||
resource.TestCheckResourceAttrPair(
|
||||
"stackit_scf_organization.org", "project_id",
|
||||
"data.stackit_scf_organization_manager.orgmanager", "project_id",
|
||||
),
|
||||
resource.TestCheckResourceAttrPair(
|
||||
"stackit_scf_organization.org", "org_id",
|
||||
"data.stackit_scf_organization_manager.orgmanager", "org_id",
|
||||
),
|
||||
resource.TestCheckResourceAttrSet("data.stackit_scf_organization_manager.orgmanager", "user_id"),
|
||||
resource.TestCheckResourceAttrSet("data.stackit_scf_organization_manager.orgmanager", "username"),
|
||||
resource.TestCheckResourceAttrSet("data.stackit_scf_organization_manager.orgmanager", "created_at"),
|
||||
resource.TestCheckResourceAttrSet("data.stackit_scf_organization_manager.orgmanager", "updated_at"),
|
||||
),
|
||||
},
|
||||
// Import
|
||||
{
|
||||
ConfigVariables: testConfigVarsMax,
|
||||
ResourceName: "stackit_scf_organization.org",
|
||||
ImportStateIdFunc: func(s *terraform.State) (string, error) {
|
||||
r, ok := s.RootModule().Resources["stackit_scf_organization.org"]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("couldn't find resource stackit_scf_organization.org")
|
||||
}
|
||||
orgId, ok := r.Primary.Attributes["org_id"]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("couldn't find attribute org_id")
|
||||
}
|
||||
regionInAttributes, ok := r.Primary.Attributes["region"]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("couldn't find attribute region")
|
||||
}
|
||||
return fmt.Sprintf("%s,%s,%s", testutil.ProjectId, regionInAttributes, orgId), nil
|
||||
},
|
||||
ImportState: true,
|
||||
ImportStateVerify: true,
|
||||
},
|
||||
// Update
|
||||
{
|
||||
ConfigVariables: testScfOrgConfigVarsMaxUpdated(),
|
||||
Config: testutil.ScfProviderConfig() + resourceMax,
|
||||
Check: resource.ComposeAggregateTestCheckFunc(
|
||||
resource.TestCheckResourceAttr("stackit_scf_organization.org", "project_id", testutil.ConvertConfigVariable(testConfigVarsMax["project_id"])),
|
||||
resource.TestCheckResourceAttr("stackit_scf_organization.org", "name", testutil.ConvertConfigVariable(testScfOrgConfigVarsMaxUpdated()["name"])),
|
||||
resource.TestCheckResourceAttr("stackit_scf_organization.org", "platform_id", testutil.ConvertConfigVariable(testConfigVarsMax["platform_id"])),
|
||||
resource.TestCheckResourceAttr("stackit_scf_organization.org", "quota_id", testutil.ConvertConfigVariable(testScfOrgConfigVarsMaxUpdated()["quota_id"])),
|
||||
resource.TestCheckResourceAttr("stackit_scf_organization.org", "suspended", testutil.ConvertConfigVariable(testScfOrgConfigVarsMaxUpdated()["suspended"])),
|
||||
resource.TestCheckResourceAttrSet("stackit_scf_organization.org", "created_at"),
|
||||
resource.TestCheckResourceAttrSet("stackit_scf_organization.org", "org_id"),
|
||||
resource.TestCheckResourceAttrSet("stackit_scf_organization.org", "region"),
|
||||
resource.TestCheckResourceAttrSet("stackit_scf_organization.org", "updated_at"),
|
||||
),
|
||||
},
|
||||
// Deletion is done by the framework implicitly
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckScfOrganizationDestroy(s *terraform.State) error {
|
||||
ctx := context.Background()
|
||||
var client *scf.APIClient
|
||||
var err error
|
||||
|
||||
if testutil.ScfCustomEndpoint == "" {
|
||||
client, err = scf.NewAPIClient()
|
||||
} else {
|
||||
client, err = scf.NewAPIClient(
|
||||
stackitSdkConfig.WithEndpoint(testutil.ScfCustomEndpoint),
|
||||
)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating client: %w", err)
|
||||
}
|
||||
|
||||
var orgsToDestroy []string
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
if rs.Type != "stackit_scf_organization" {
|
||||
continue
|
||||
}
|
||||
orgId := strings.Split(rs.Primary.ID, core.Separator)[1]
|
||||
orgsToDestroy = append(orgsToDestroy, orgId)
|
||||
}
|
||||
|
||||
organizationsList, err := client.ListOrganizations(ctx, testutil.ProjectId, testutil.Region).Execute()
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting scf organizations: %w", err)
|
||||
}
|
||||
|
||||
scfOrgs := organizationsList.GetResources()
|
||||
for i := range scfOrgs {
|
||||
if scfOrgs[i].Guid == nil {
|
||||
continue
|
||||
}
|
||||
if utils.Contains(orgsToDestroy, *scfOrgs[i].Guid) {
|
||||
_, err := client.DeleteOrganizationExecute(ctx, testutil.ProjectId, testutil.Region, *scfOrgs[i].Guid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("destroying scf organization %s during CheckDestroy: %w", *scfOrgs[i].Guid, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
23
stackit/internal/services/scf/testdata/resource-max.tf
vendored
Normal file
23
stackit/internal/services/scf/testdata/resource-max.tf
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
|
||||
variable "project_id" {}
|
||||
variable "name" {}
|
||||
variable "quota_id" {}
|
||||
variable "suspended" {}
|
||||
variable "region" {}
|
||||
|
||||
resource "stackit_scf_organization" "org" {
|
||||
project_id = var.project_id
|
||||
name = var.name
|
||||
suspended = var.suspended
|
||||
quota_id = var.quota_id
|
||||
region = var.region
|
||||
}
|
||||
|
||||
resource "stackit_scf_organization_manager" "orgmanager" {
|
||||
project_id = var.project_id
|
||||
org_id = stackit_scf_organization.org.org_id
|
||||
}
|
||||
data "stackit_scf_platform" "scf_platform" {
|
||||
project_id = var.project_id
|
||||
platform_id = stackit_scf_organization.org.platform_id
|
||||
}
|
||||
13
stackit/internal/services/scf/testdata/resource-min.tf
vendored
Normal file
13
stackit/internal/services/scf/testdata/resource-min.tf
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
|
||||
variable "project_id" {}
|
||||
variable "name" {}
|
||||
|
||||
resource "stackit_scf_organization" "org" {
|
||||
project_id = var.project_id
|
||||
name = var.name
|
||||
}
|
||||
|
||||
resource "stackit_scf_organization_manager" "orgmanager" {
|
||||
project_id = var.project_id
|
||||
org_id = stackit_scf_organization.org.org_id
|
||||
}
|
||||
30
stackit/internal/services/scf/utils/utils.go
Normal file
30
stackit/internal/services/scf/utils/utils.go
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/diag"
|
||||
"github.com/stackitcloud/stackit-sdk-go/core/config"
|
||||
"github.com/stackitcloud/stackit-sdk-go/services/scf"
|
||||
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
|
||||
)
|
||||
|
||||
func ConfigureClient(ctx context.Context, providerData *core.ProviderData, diags *diag.Diagnostics) *scf.APIClient {
|
||||
apiClientConfigOptions := []config.ConfigurationOption{
|
||||
config.WithCustomAuth(providerData.RoundTripper),
|
||||
utils.UserAgentConfigOption(providerData.Version),
|
||||
}
|
||||
if providerData.ScfCustomEndpoint != "" {
|
||||
apiClientConfigOptions = append(apiClientConfigOptions, config.WithEndpoint(providerData.ScfCustomEndpoint))
|
||||
}
|
||||
apiClient, err := scf.NewAPIClient(apiClientConfigOptions...)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, diags, "Error configuring API client", fmt.Sprintf("Configuring client: %v. This is an error related to the provider configuration, not to the resource configuration", err))
|
||||
return nil
|
||||
}
|
||||
|
||||
return apiClient
|
||||
}
|
||||
94
stackit/internal/services/scf/utils/utils_test.go
Normal file
94
stackit/internal/services/scf/utils/utils_test.go
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/diag"
|
||||
sdkClients "github.com/stackitcloud/stackit-sdk-go/core/clients"
|
||||
"github.com/stackitcloud/stackit-sdk-go/core/config"
|
||||
"github.com/stackitcloud/stackit-sdk-go/services/scf"
|
||||
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
testVersion = "0.8.15"
|
||||
testCustomEndpoint = "https://scf-custom-endpoint.api.stackit.cloud"
|
||||
)
|
||||
|
||||
func TestConfigureClient(t *testing.T) {
|
||||
/* mock authentication by setting service account token env variable */
|
||||
os.Clearenv()
|
||||
err := os.Setenv(sdkClients.ServiceAccountToken, "mock-val")
|
||||
if err != nil {
|
||||
t.Errorf("error setting env variable: %v", err)
|
||||
}
|
||||
|
||||
type args struct {
|
||||
providerData *core.ProviderData
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
expected *scf.APIClient
|
||||
}{
|
||||
{
|
||||
name: "default endpoint",
|
||||
args: args{
|
||||
providerData: &core.ProviderData{
|
||||
Version: testVersion,
|
||||
},
|
||||
},
|
||||
expected: func() *scf.APIClient {
|
||||
apiClient, err := scf.NewAPIClient(
|
||||
utils.UserAgentConfigOption(testVersion),
|
||||
)
|
||||
if err != nil {
|
||||
t.Errorf("error configuring client: %v", err)
|
||||
}
|
||||
return apiClient
|
||||
}(),
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "custom endpoint",
|
||||
args: args{
|
||||
providerData: &core.ProviderData{
|
||||
Version: testVersion,
|
||||
ScfCustomEndpoint: testCustomEndpoint,
|
||||
},
|
||||
},
|
||||
expected: func() *scf.APIClient {
|
||||
apiClient, err := scf.NewAPIClient(
|
||||
utils.UserAgentConfigOption(testVersion),
|
||||
config.WithEndpoint(testCustomEndpoint),
|
||||
)
|
||||
if err != nil {
|
||||
t.Errorf("error configuring client: %v", err)
|
||||
}
|
||||
return apiClient
|
||||
}(),
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
diags := diag.Diagnostics{}
|
||||
|
||||
actual := ConfigureClient(ctx, tt.args.providerData, &diags)
|
||||
if diags.HasError() != tt.wantErr {
|
||||
t.Errorf("ConfigureClient() error = %v, want %v", diags.HasError(), tt.wantErr)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(actual, tt.expected) {
|
||||
t.Errorf("ConfigureClient() = %v, want %v", actual, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/hashicorp/terraform-plugin-framework/providerserver"
|
||||
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
|
||||
"github.com/hashicorp/terraform-plugin-testing/config"
|
||||
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit"
|
||||
)
|
||||
|
||||
|
|
@ -67,6 +68,7 @@ var (
|
|||
RabbitMQCustomEndpoint = os.Getenv("TF_ACC_RABBITMQ_CUSTOM_ENDPOINT")
|
||||
RedisCustomEndpoint = os.Getenv("TF_ACC_REDIS_CUSTOM_ENDPOINT")
|
||||
ResourceManagerCustomEndpoint = os.Getenv("TF_ACC_RESOURCEMANAGER_CUSTOM_ENDPOINT")
|
||||
ScfCustomEndpoint = os.Getenv("TF_ACC_SCF_CUSTOM_ENDPOINT")
|
||||
SecretsManagerCustomEndpoint = os.Getenv("TF_ACC_SECRETSMANAGER_CUSTOM_ENDPOINT")
|
||||
SQLServerFlexCustomEndpoint = os.Getenv("TF_ACC_SQLSERVERFLEX_CUSTOM_ENDPOINT")
|
||||
ServerBackupCustomEndpoint = os.Getenv("TF_ACC_SERVER_BACKUP_CUSTOM_ENDPOINT")
|
||||
|
|
@ -495,6 +497,22 @@ func GitProviderConfig() string {
|
|||
)
|
||||
}
|
||||
|
||||
func ScfProviderConfig() string {
|
||||
if ScfCustomEndpoint == "" {
|
||||
return `
|
||||
provider "stackit" {
|
||||
default_region = "eu01"
|
||||
}`
|
||||
}
|
||||
return fmt.Sprintf(`
|
||||
provider "stackit" {
|
||||
default_region = "eu01"
|
||||
scf_custom_endpoint = "%s"
|
||||
}`,
|
||||
ScfCustomEndpoint,
|
||||
)
|
||||
}
|
||||
|
||||
func ResourceNameWithDateTime(name string) string {
|
||||
dateTime := time.Now().Format(time.RFC3339)
|
||||
// Remove timezone to have a smaller datetime
|
||||
|
|
|
|||
|
|
@ -76,6 +76,9 @@ import (
|
|||
redisInstance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/redis/instance"
|
||||
resourceManagerFolder "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/resourcemanager/folder"
|
||||
resourceManagerProject "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/resourcemanager/project"
|
||||
scfOrganization "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/scf/organization"
|
||||
scfOrganizationmanager "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/scf/organizationmanager"
|
||||
scfPlatform "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/scf/platform"
|
||||
secretsManagerInstance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/secretsmanager/instance"
|
||||
secretsManagerUser "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/secretsmanager/user"
|
||||
serverBackupSchedule "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/serverbackup/schedule"
|
||||
|
|
@ -147,6 +150,7 @@ type providerModel struct {
|
|||
ServerUpdateCustomEndpoint types.String `tfsdk:"server_update_custom_endpoint"`
|
||||
ServiceAccountCustomEndpoint types.String `tfsdk:"service_account_custom_endpoint"`
|
||||
ResourceManagerCustomEndpoint types.String `tfsdk:"resourcemanager_custom_endpoint"`
|
||||
ScfCustomEndpoint types.String `tfsdk:"scf_custom_endpoint"`
|
||||
TokenCustomEndpoint types.String `tfsdk:"token_custom_endpoint"`
|
||||
EnableBetaResources types.Bool `tfsdk:"enable_beta_resources"`
|
||||
ServiceEnablementCustomEndpoint types.String `tfsdk:"service_enablement_custom_endpoint"`
|
||||
|
|
@ -185,6 +189,7 @@ func (p *Provider) Schema(_ context.Context, _ provider.SchemaRequest, resp *pro
|
|||
"server_update_custom_endpoint": "Custom endpoint for the Server Update service",
|
||||
"service_account_custom_endpoint": "Custom endpoint for the Service Account service",
|
||||
"resourcemanager_custom_endpoint": "Custom endpoint for the Resource Manager service",
|
||||
"scf_custom_endpoint": "Custom endpoint for the Cloud Foundry (SCF) service",
|
||||
"secretsmanager_custom_endpoint": "Custom endpoint for the Secrets Manager service",
|
||||
"sqlserverflex_custom_endpoint": "Custom endpoint for the SQL Server Flex service",
|
||||
"ske_custom_endpoint": "Custom endpoint for the Kubernetes Engine (SKE) service",
|
||||
|
|
@ -307,6 +312,10 @@ func (p *Provider) Schema(_ context.Context, _ provider.SchemaRequest, resp *pro
|
|||
Optional: true,
|
||||
Description: descriptions["redis_custom_endpoint"],
|
||||
},
|
||||
"scf_custom_endpoint": schema.StringAttribute{
|
||||
Optional: true,
|
||||
Description: descriptions["scf_custom_endpoint"],
|
||||
},
|
||||
"resourcemanager_custom_endpoint": schema.StringAttribute{
|
||||
Optional: true,
|
||||
Description: descriptions["resourcemanager_custom_endpoint"],
|
||||
|
|
@ -417,6 +426,7 @@ func (p *Provider) Configure(ctx context.Context, req provider.ConfigureRequest,
|
|||
setStringField(providerConfig.OpenSearchCustomEndpoint, func(v string) { providerData.OpenSearchCustomEndpoint = v })
|
||||
setStringField(providerConfig.RedisCustomEndpoint, func(v string) { providerData.RedisCustomEndpoint = v })
|
||||
setStringField(providerConfig.ResourceManagerCustomEndpoint, func(v string) { providerData.ResourceManagerCustomEndpoint = v })
|
||||
setStringField(providerConfig.ScfCustomEndpoint, func(v string) { providerData.ScfCustomEndpoint = v })
|
||||
setStringField(providerConfig.SecretsManagerCustomEndpoint, func(v string) { providerData.SecretsManagerCustomEndpoint = v })
|
||||
setStringField(providerConfig.SQLServerFlexCustomEndpoint, func(v string) { providerData.SQLServerFlexCustomEndpoint = v })
|
||||
setStringField(providerConfig.ServiceAccountCustomEndpoint, func(v string) { providerData.ServiceAccountCustomEndpoint = v })
|
||||
|
|
@ -500,6 +510,9 @@ func (p *Provider) DataSources(_ context.Context) []func() datasource.DataSource
|
|||
redisInstance.NewInstanceDataSource,
|
||||
redisCredential.NewCredentialDataSource,
|
||||
resourceManagerProject.NewProjectDataSource,
|
||||
scfOrganization.NewScfOrganizationDataSource,
|
||||
scfOrganizationmanager.NewScfOrganizationManagerDataSource,
|
||||
scfPlatform.NewScfPlatformDataSource,
|
||||
resourceManagerFolder.NewFolderDataSource,
|
||||
secretsManagerInstance.NewInstanceDataSource,
|
||||
secretsManagerUser.NewUserDataSource,
|
||||
|
|
@ -567,6 +580,8 @@ func (p *Provider) Resources(_ context.Context) []func() resource.Resource {
|
|||
redisInstance.NewInstanceResource,
|
||||
redisCredential.NewCredentialResource,
|
||||
resourceManagerProject.NewProjectResource,
|
||||
scfOrganization.NewScfOrganizationResource,
|
||||
scfOrganizationmanager.NewScfOrganizationManagerResource,
|
||||
resourceManagerFolder.NewFolderResource,
|
||||
secretsManagerInstance.NewInstanceResource,
|
||||
secretsManagerUser.NewUserResource,
|
||||
|
|
|
|||
248
templates/guides/scf_cloudfoundry.md.tmpl
Normal file
248
templates/guides/scf_cloudfoundry.md.tmpl
Normal file
|
|
@ -0,0 +1,248 @@
|
|||
# How to Provisioning Cloud Foundry using Terrform
|
||||
|
||||
## Objective
|
||||
|
||||
This tutorial demonstrates how to provision Cloud Foundry resources by
|
||||
integrating the STACKIT Terraform provider with the Cloud Foundry Terraform
|
||||
provider. The STACKIT Terraform provider will create a managed Cloud Foundry
|
||||
organization and set up a technical "org manager" user with
|
||||
`organization_manager` permissions. These credentials, along with the Cloud
|
||||
Foundry API URL (retrieved dynamically from a platform data resource), are
|
||||
passed to the Cloud Foundry Terraform provider to manage resources within the
|
||||
new organization.
|
||||
|
||||
### Output
|
||||
|
||||
This configuration creates a Cloud Foundry organization, mirroring the structure
|
||||
created via the portal. It sets up three distinct spaces: `dev`, `qa`, and
|
||||
`prod`. The configuration assigns, a specified user the `organization_manager`
|
||||
and `organization_user` roles at the organization level, and the
|
||||
`space_developer` role in each space.
|
||||
|
||||
### Scope
|
||||
|
||||
This tutorial covers the interaction between the STACKIT Terraform provider and
|
||||
the Cloud Foundry Terraform provider. It assumes you are familiar with:
|
||||
|
||||
- Setting up a STACKIT project and configuring the STACKIT Terraform provider
|
||||
with a service account (see the general STACKIT documentation for details).
|
||||
- Basic Terraform concepts, such as variables and locals.
|
||||
|
||||
This document does not cover foundational topics or every feature of the Cloud
|
||||
Foundry Terraform provider.
|
||||
|
||||
### Example configuration
|
||||
|
||||
The following Terraform configuration provisions a Cloud Foundry organization
|
||||
and related resources using the STACKIT Terraform provider and the Cloud Foundry
|
||||
Terraform provider:
|
||||
|
||||
```
|
||||
terraform {
|
||||
required_providers {
|
||||
stackit = {
|
||||
source = "stackitcloud/stackit"
|
||||
}
|
||||
cloudfoundry = {
|
||||
source = "cloudfoundry/cloudfoundry"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variable "project_id" {
|
||||
type = string
|
||||
description = "Id of the Project"
|
||||
}
|
||||
|
||||
variable "org_name" {
|
||||
type = string
|
||||
description = "Name of the Organization"
|
||||
}
|
||||
|
||||
variable "admin_email" {
|
||||
type = string
|
||||
description = "Users who are granted permissions"
|
||||
}
|
||||
|
||||
provider "stackit" {
|
||||
default_region = "eu01"
|
||||
}
|
||||
|
||||
resource "stackit_scf_organization" "scf_org" {
|
||||
name = var.org_name
|
||||
project_id = var.project_id
|
||||
}
|
||||
|
||||
data "stackit_scf_platform" "scf_platform" {
|
||||
project_id = var.project_id
|
||||
platform_id = stackit_scf_organization.scf_org.platform_id
|
||||
}
|
||||
|
||||
resource "stackit_scf_organization_manager" "scf_manager" {
|
||||
project_id = var.project_id
|
||||
org_id = stackit_scf_organization.scf_org.org_id
|
||||
}
|
||||
|
||||
provider "cloudfoundry" {
|
||||
api_url = data.stackit_scf_platform.scf_platform.api_url
|
||||
user = stackit_scf_organization_manager.scf_manager.username
|
||||
password = stackit_scf_organization_manager.scf_manager.password
|
||||
}
|
||||
|
||||
locals {
|
||||
spaces = ["dev", "qa", "prod"]
|
||||
}
|
||||
|
||||
resource "cloudfoundry_org_role" "org_user" {
|
||||
username = var.admin_email
|
||||
type = "organization_user"
|
||||
org = stackit_scf_organization.scf_org.org_id
|
||||
}
|
||||
|
||||
resource "cloudfoundry_org_role" "org_manager" {
|
||||
username = var.admin_email
|
||||
type = "organization_manager"
|
||||
org = stackit_scf_organization.scf_org.org_id
|
||||
}
|
||||
|
||||
resource "cloudfoundry_space" "spaces" {
|
||||
for_each = toset(local.spaces)
|
||||
name = each.key
|
||||
org = stackit_scf_organization.scf_org.org_id
|
||||
}
|
||||
|
||||
resource "cloudfoundry_space_role" "space_developer" {
|
||||
for_each = toset(local.spaces)
|
||||
username = var.admin_email
|
||||
type = "space_developer"
|
||||
depends_on = [ cloudfoundry_org_role.org_user ]
|
||||
space = cloudfoundry_space.spaces[each.key].id
|
||||
}
|
||||
```
|
||||
|
||||
## Explanation of configuration
|
||||
|
||||
### STACKIT provider configuration
|
||||
|
||||
```
|
||||
provider "stackit" {
|
||||
default_region = "eu01"
|
||||
}
|
||||
```
|
||||
|
||||
The STACKIT Cloud Foundry Application Programming Interface (SCF API) is
|
||||
regionalized. Each region operates independently. Set `default_region` in the
|
||||
provider configuration, to specify the region for all resources, unless you
|
||||
override it for individual resources. You must also provide access data for the
|
||||
relevant STACKIT project for the provider to function.
|
||||
|
||||
For more details, see
|
||||
the:[STACKIT Terraform Provider documentation.](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs)
|
||||
|
||||
### stackit_scf_organization.scf_org resource
|
||||
|
||||
```
|
||||
resource "stackit_scf_organization" "scf_org" {
|
||||
name = var.org_name
|
||||
project_id = var.project_id
|
||||
}
|
||||
```
|
||||
|
||||
This resource provisions a Cloud Foundry organization, which acts as the
|
||||
foundational container in the Cloud Foundry environment. Each Cloud Foundry
|
||||
provider configuration is scoped to a specific organization. The organization’s
|
||||
name, defined by a variable, must be unique across the platform. The
|
||||
organization is created within a designated STACKIT project, which requires the
|
||||
STACKIT provider to be configured with the necessary permissions for that
|
||||
project.
|
||||
|
||||
### stackit_scf_organization_manager.scf_manager resource
|
||||
|
||||
```
|
||||
resource "stackit_scf_organization_manager" "scf_manager" {
|
||||
project_id = var.project_id
|
||||
org_id = stackit_scf_organization.scf_org.org_id
|
||||
}
|
||||
```
|
||||
|
||||
This resource creates a technical user in the Cloud Foundry organization with
|
||||
the organization_manager permission. The user is linked to the organization and
|
||||
is automatically deleted when the organization is removed.
|
||||
|
||||
### stackit_scf_platform.scf_platform data source
|
||||
|
||||
```
|
||||
data "stackit_scf_platform" "scf_platform" {
|
||||
project_id = var.project_id
|
||||
platform_id = stackit_scf_organization.scf_org.platform_id
|
||||
}
|
||||
```
|
||||
|
||||
This data source retrieves properties of the Cloud Foundry platform where the
|
||||
organization is provisioned. It does not create resources, but provides
|
||||
information about the existing platform.
|
||||
|
||||
### Cloud Foundry provider configuration
|
||||
|
||||
```
|
||||
provider "cloudfoundry" {
|
||||
api_url = data.stackit_scf_platform.scf_platform.api_url
|
||||
user = stackit_scf_organization_manager.scf_manager.username
|
||||
password = stackit_scf_organization_manager.scf_manager.password
|
||||
}
|
||||
```
|
||||
|
||||
The Cloud Foundry provider is configured to manage resources in the new
|
||||
organization. The provider uses the API URL from the `stackit_scf_platform` data
|
||||
source and authenticates using the credentials of the technical user created by
|
||||
the `stackit_scf_organization_manager` resource.
|
||||
|
||||
For more information, see the:
|
||||
[Cloud Foundry Terraform Provider documentation.](https://registry.terraform.io/providers/cloudfoundry/cloudfoundry/latest/docs)
|
||||
|
||||
## Deploy resources
|
||||
|
||||
Follow these steps to initialize your environment and provision Cloud Foundry
|
||||
resources using Terraform.
|
||||
|
||||
### Initialize Terraform
|
||||
|
||||
Run the following command to initialize the working directory and download the
|
||||
required provider plugins:
|
||||
|
||||
```
|
||||
terraform init
|
||||
```
|
||||
|
||||
### Create the organization manager user
|
||||
|
||||
Run this command to provision the organization and technical user needed to
|
||||
initialize the Cloud Foundry Terraform provider. This step is required only
|
||||
during the initial setup. For later changes, you do not need the -target flag.
|
||||
|
||||
```
|
||||
terraform apply -target stackit_scf_organization_manager.scf_manager
|
||||
```
|
||||
|
||||
### Apply the full configuration
|
||||
|
||||
Run this command to provision all resources defined in your Terraform
|
||||
configuration within the Cloud Foundry organization:
|
||||
|
||||
```
|
||||
terraform apply
|
||||
```
|
||||
|
||||
## Verify the deployment
|
||||
|
||||
Verify that your Cloud Foundry resources are provisioned correctly. Use the
|
||||
following Cloud Foundry CLI commands to check applications, services, and
|
||||
routes:
|
||||
|
||||
- `cf apps`
|
||||
- `cf services`
|
||||
- `cf routes`
|
||||
|
||||
For more information, see the
|
||||
[Cloud Foundry documentation](https://docs.cloudfoundry.org/) and the
|
||||
[Cloud Foundry CLI Reference Guide](https://cli.cloudfoundry.org/).
|
||||
Loading…
Add table
Add a link
Reference in a new issue