diff --git a/.gitignore b/.gitignore index fd3ad8e1..a070b852 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ website/node_modules *.iml *.test *.iml +/terraform-provider-meshstack website/vendor diff --git a/client/client.go b/client/client.go index 7c505fb1..6d06efa0 100644 --- a/client/client.go +++ b/client/client.go @@ -42,6 +42,7 @@ type endpoints struct { WorkspaceGroupBindings *url.URL `json:"meshworkspacegroupbindings"` Tenants *url.URL `json:"meshtenants"` TagDefinitions *url.URL `json:"meshtagdefinitions"` + LandingZones *url.URL `json:"meshlandingzones"` } type loginResponse struct { @@ -71,6 +72,7 @@ func NewClient(rootUrl *url.URL, apiKey string, apiSecret string) (*MeshStackPro WorkspaceGroupBindings: rootUrl.JoinPath(apiMeshObjectsRoot, "meshworkspacebindings", "groupbindings"), Tenants: rootUrl.JoinPath(apiMeshObjectsRoot, "meshtenants"), TagDefinitions: rootUrl.JoinPath(apiMeshObjectsRoot, "meshtagdefinitions"), + LandingZones: rootUrl.JoinPath(apiMeshObjectsRoot, "meshlandingzones"), } return client, nil diff --git a/client/landingzone.go b/client/landingzone.go new file mode 100644 index 00000000..6f60d0b3 --- /dev/null +++ b/client/landingzone.go @@ -0,0 +1,181 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" +) + +const CONTENT_TYPE_LANDINGZONE = "application/vnd.meshcloud.api.meshlandingzone.v1-preview.hal+json" + +type MeshLandingZone struct { + ApiVersion string `json:"apiVersion" tfsdk:"api_version"` + Kind string `json:"kind" tfsdk:"kind"` + Metadata MeshLandingZoneMetadata `json:"metadata" tfsdk:"metadata"` + Spec MeshLandingZoneSpec `json:"spec" tfsdk:"spec"` + Status MeshLandingZoneStatus `json:"status" tfsdk:"status"` +} + +type MeshLandingZoneMetadata struct { + Name string `json:"name" tfsdk:"name"` + Tags map[string][]string `json:"tags" tfsdk:"tags"` +} + +type MeshLandingZoneSpec struct { + DisplayName string `json:"displayName" tfsdk:"display_name"` + Description string `json:"description" tfsdk:"description"` + AutomateDeletionApproval bool `json:"automateDeletionApproval" tfsdk:"automate_deletion_approval"` + AutomateDeletionReplication bool `json:"automateDeletionReplication" tfsdk:"automate_deletion_replication"` + InfoLink string `json:"infoLink" tfsdk:"info_link"` + PlatformRef PlatformRef `json:"platformRef" tfsdk:"platform_ref"` + PlatformProperties *PlatformProperties `json:"platformProperties,omitempty" tfsdk:"platform_properties"` +} + +type MeshLandingZoneStatus struct { + Disabled bool `json:"disabled" tfsdk:"disabled"` + Restricted bool `json:"restricted" tfsdk:"restricted"` +} + +type PlatformRef struct { + Uuid string `json:"uuid" tfsdk:"uuid"` + Kind string `json:"kind" tfsdk:"kind"` +} + +type PlatformProperties struct { + Type string `json:"type" tfsdk:"type"` + Aws *AwsPlatformProperties `json:"aws" tfsdk:"aws"` + Aks *AksPlatformProperties `json:"aks" tfsdk:"aks"` + Azure *AzurePlatformProperties `json:"azure" tfsdk:"azure"` + AzureRg *AzureRgPlatformProperties `json:"azurerg" tfsdk:"azurerg"` + Gcp *GcpPlatformProperties `json:"gcp" tfsdk:"gcp"` + Kubernetes *KubernetesPlatformProperties `json:"kubernetes" tfsdk:"kubernetes"` + OpenShift *OpenShiftPlatformProperties `json:"openshift" tfsdk:"openshift"` +} + +type MeshLandingZoneCreate struct { + ApiVersion string `json:"apiVersion" tfsdk:"api_version"` + Metadata MeshLandingZoneMetadata `json:"metadata" tfsdk:"metadata"` + Spec MeshLandingZoneSpec `json:"spec" tfsdk:"spec"` +} + +func (c *MeshStackProviderClient) urlForLandingZone(name string) *url.URL { + return c.endpoints.LandingZones.JoinPath(name) +} + +func (c *MeshStackProviderClient) ReadLandingZone(name string) (*MeshLandingZone, error) { + targetUrl := c.urlForLandingZone(name) + req, err := http.NewRequest("GET", targetUrl.String(), nil) + if err != nil { + return nil, err + } + req.Header.Set("Accept", CONTENT_TYPE_LANDINGZONE) + + res, err := c.doAuthenticatedRequest(req) + if err != nil { + return nil, err + } + + defer res.Body.Close() + + if res.StatusCode == http.StatusNotFound { + return nil, nil // Not found is not an error + } + + data, err := io.ReadAll(res.Body) + if err != nil { + return nil, err + } + + if !isSuccessHTTPStatus(res) { + return nil, fmt.Errorf("unexpected status code: %d, %s", res.StatusCode, data) + } + + var landingZone MeshLandingZone + err = json.Unmarshal(data, &landingZone) + if err != nil { + return nil, err + } + return &landingZone, nil +} + +func (c *MeshStackProviderClient) CreateLandingZone(landingZone *MeshLandingZoneCreate) (*MeshLandingZone, error) { + payload, err := json.Marshal(landingZone) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", c.endpoints.LandingZones.String(), bytes.NewBuffer(payload)) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", CONTENT_TYPE_LANDINGZONE) + req.Header.Set("Accept", CONTENT_TYPE_LANDINGZONE) + + res, err := c.doAuthenticatedRequest(req) + if err != nil { + return nil, err + } + defer res.Body.Close() + + data, err := io.ReadAll(res.Body) + if err != nil { + return nil, err + } + + if !isSuccessHTTPStatus(res) { + return nil, fmt.Errorf("unexpected status code: %d, %s", res.StatusCode, data) + } + + var createdLandingZone MeshLandingZone + err = json.Unmarshal(data, &createdLandingZone) + if err != nil { + return nil, err + } + return &createdLandingZone, nil +} + +func (c *MeshStackProviderClient) UpdateLandingZone(name string, landingZone *MeshLandingZoneCreate) (*MeshLandingZone, error) { + targetUrl := c.urlForLandingZone(name) + + payload, err := json.Marshal(landingZone) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("PUT", targetUrl.String(), bytes.NewBuffer(payload)) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", CONTENT_TYPE_LANDINGZONE) + req.Header.Set("Accept", CONTENT_TYPE_LANDINGZONE) + + res, err := c.doAuthenticatedRequest(req) + if err != nil { + return nil, err + } + defer res.Body.Close() + + data, err := io.ReadAll(res.Body) + if err != nil { + return nil, err + } + + if !isSuccessHTTPStatus(res) { + return nil, fmt.Errorf("unexpected status code: %d, %s", res.StatusCode, data) + } + + var updatedLandingZone MeshLandingZone + err = json.Unmarshal(data, &updatedLandingZone) + if err != nil { + return nil, err + } + return &updatedLandingZone, nil +} + +func (c *MeshStackProviderClient) DeleteLandingZone(name string) error { + targetUrl := c.urlForLandingZone(name) + return c.deleteMeshObject(*targetUrl, 204) +} diff --git a/client/platform_properties_aks.go b/client/platform_properties_aks.go new file mode 100644 index 00000000..4599a5fe --- /dev/null +++ b/client/platform_properties_aks.go @@ -0,0 +1,10 @@ +package client + +type AksPlatformProperties struct { + KubernetesRoleMappings []KubernetesRoleMapping `json:"kubernetesRoleMappings" tfsdk:"kubernetes_role_mappings"` +} + +type KubernetesRoleMapping struct { + MeshProjectRoleRef MeshProjectRoleRefV2 `json:"projectRoleRef" tfsdk:"project_role_ref"` + PlatformRoles []string `json:"platformRoles" tfsdk:"platform_roles"` +} diff --git a/client/platform_properties_aws.go b/client/platform_properties_aws.go new file mode 100644 index 00000000..bbd8480d --- /dev/null +++ b/client/platform_properties_aws.go @@ -0,0 +1,14 @@ +package client + +type AwsPlatformProperties struct { + AwsTargetOrgUnitId string `json:"awsTargetOrgUnitId" tfsdk:"aws_target_org_unit_id"` + AwsEnrollAccount bool `json:"awsEnrollAccount" tfsdk:"aws_enroll_account"` + AwsLambdaArn *string `json:"awsLambdaArn" tfsdk:"aws_lambda_arn"` + AwsRoleMappings []AwsRoleMapping `json:"awsRoleMappings" tfsdk:"aws_role_mappings"` +} + +type AwsRoleMapping struct { + MeshProjectRoleRef MeshProjectRoleRefV2 `json:"projectRoleRef" tfsdk:"project_role_ref"` + PlatformRole string `json:"platformRole" tfsdk:"platform_role"` + Policies []string `json:"policies" tfsdk:"policies"` +} diff --git a/client/platform_properties_azure.go b/client/platform_properties_azure.go new file mode 100644 index 00000000..a06c0101 --- /dev/null +++ b/client/platform_properties_azure.go @@ -0,0 +1,17 @@ +package client + +type AzurePlatformProperties struct { + AzureRoleMappings []AzureRoleMapping `json:"azureRoleMappings" tfsdk:"azure_role_mappings"` + AzureManagementGroupId string `json:"azureManagementGroupId" tfsdk:"azure_management_group_id"` +} + +type AzureRoleMapping struct { + MeshProjectRoleRef MeshProjectRoleRefV2 `json:"projectRoleRef" tfsdk:"project_role_ref"` + AzureGroupSuffix string `json:"azureGroupSuffix" tfsdk:"azure_group_suffix"` + AzureRoleDefinitions []AzureRoleDefinition `json:"azureRoleDefinitions" tfsdk:"azure_role_definitions"` +} + +type AzureRoleDefinition struct { + AzureRoleDefinitionId string `json:"azureRoleDefinitionId" tfsdk:"azure_role_definition_id"` + AbacCondition *string `json:"abacCondition" tfsdk:"abac_condition"` +} diff --git a/client/platform_properties_azurerg.go b/client/platform_properties_azurerg.go new file mode 100644 index 00000000..dc97e8fe --- /dev/null +++ b/client/platform_properties_azurerg.go @@ -0,0 +1,18 @@ +package client + +type AzureRgPlatformProperties struct { + AzureRgLocation string `json:"azureRgLocation" tfsdk:"azure_rg_location"` + AzureRgRoleMappings []AzureRgRoleMapping `json:"azureRgRoleMappings" tfsdk:"azure_rg_role_mappings"` + AzureFunction *AzureFunction `json:"azureFunction,omitempty" tfsdk:"azure_function"` +} + +type AzureRgRoleMapping struct { + MeshProjectRoleRef MeshProjectRoleRefV2 `json:"projectRoleRef" tfsdk:"project_role_ref"` + AzureGroupSuffix string `json:"azureGroupSuffix" tfsdk:"azure_group_suffix"` + AzureRoleDefinitionIds []string `json:"azureRoleDefinitionIds" tfsdk:"azure_role_definition_ids"` +} + +type AzureFunction struct { + AzureFunctionUrl string `json:"azureFunctionUrl" tfsdk:"azure_function_url"` + AzureFunctionScope string `json:"azureFunctionScope" tfsdk:"azure_function_scope"` +} diff --git a/client/platform_properties_gcp.go b/client/platform_properties_gcp.go new file mode 100644 index 00000000..c8bb02b0 --- /dev/null +++ b/client/platform_properties_gcp.go @@ -0,0 +1,12 @@ +package client + +type GcpPlatformProperties struct { + GcpCloudFunctionUrl *string `json:"gcpCloudFunctionUrl,omitempty" tfsdk:"gcp_cloud_function_url"` + GcpFolderId *string `json:"gcpFolderId,omitempty" tfsdk:"gcp_folder_id"` + GcpRoleMappings []GcpRoleMapping `json:"gcpRoleMappings" tfsdk:"gcp_role_mappings"` +} + +type GcpRoleMapping struct { + MeshProjectRoleRef MeshProjectRoleRefV2 `json:"projectRoleRef" tfsdk:"project_role_ref"` + PlatformRoles []string `json:"platformRoles" tfsdk:"platform_roles"` +} diff --git a/client/platform_properties_kubernetes.go b/client/platform_properties_kubernetes.go new file mode 100644 index 00000000..b48c338a --- /dev/null +++ b/client/platform_properties_kubernetes.go @@ -0,0 +1,5 @@ +package client + +type KubernetesPlatformProperties struct { + KubernetesRoleMappings []KubernetesRoleMapping `json:"kubernetesRoleMappings" tfsdk:"kubernetes_role_mappings"` +} diff --git a/client/platform_properties_openshift.go b/client/platform_properties_openshift.go new file mode 100644 index 00000000..15d67521 --- /dev/null +++ b/client/platform_properties_openshift.go @@ -0,0 +1,5 @@ +package client + +type OpenShiftPlatformProperties struct { + OpenShiftTemplate *string `json:"openShiftTemplate,omitempty" tfsdk:"openshift_template"` +} diff --git a/client/project_binding.go b/client/project_binding.go index f704efce..9e7138e9 100644 --- a/client/project_binding.go +++ b/client/project_binding.go @@ -22,10 +22,17 @@ type MeshProjectBindingMetadata struct { Name string `json:"name" tfsdk:"name"` } +// Deprecated: Use MeshProjectRoleRefV2 if possible. The convention is to also provide the `kind`, +// so this struct should only be used for meshobjects that violate our API conventions. type MeshProjectRoleRef struct { Name string `json:"name" tfsdk:"name"` } +type MeshProjectRoleRefV2 struct { + Name string `json:"name" tfsdk:"name"` + Kind string `json:"kind" tfsdk:"kind"` +} + type MeshProjectTargetRef struct { Name string `json:"name" tfsdk:"name"` OwnedByWorkspace string `json:"ownedByWorkspace" tfsdk:"owned_by_workspace"` diff --git a/client/workspace.go b/client/workspace.go index 0ade57cd..bdf878f3 100644 --- a/client/workspace.go +++ b/client/workspace.go @@ -81,12 +81,12 @@ func (c *MeshStackProviderClient) ReadWorkspace(name string) (*MeshWorkspace, er } func (c *MeshStackProviderClient) CreateWorkspace(workspace *MeshWorkspaceCreate) (*MeshWorkspace, error) { - paylod, err := json.Marshal(workspace) + payload, err := json.Marshal(workspace) if err != nil { return nil, err } - req, err := http.NewRequest("POST", c.endpoints.Workspaces.String(), bytes.NewBuffer(paylod)) + req, err := http.NewRequest("POST", c.endpoints.Workspaces.String(), bytes.NewBuffer(payload)) if err != nil { return nil, err } @@ -119,12 +119,12 @@ func (c *MeshStackProviderClient) CreateWorkspace(workspace *MeshWorkspaceCreate func (c *MeshStackProviderClient) UpdateWorkspace(name string, workspace *MeshWorkspaceCreate) (*MeshWorkspace, error) { targetUrl := c.urlForWorkspace(name) - paylod, err := json.Marshal(workspace) + payload, err := json.Marshal(workspace) if err != nil { return nil, err } - req, err := http.NewRequest("PUT", targetUrl.String(), bytes.NewBuffer(paylod)) + req, err := http.NewRequest("PUT", targetUrl.String(), bytes.NewBuffer(payload)) if err != nil { return nil, err } diff --git a/docs/data-sources/landingzone.md b/docs/data-sources/landingzone.md new file mode 100644 index 00000000..e474d9ed --- /dev/null +++ b/docs/data-sources/landingzone.md @@ -0,0 +1,300 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "meshstack_landingzone Data Source - terraform-provider-meshstack" +subcategory: "" +description: |- + Read a single landing zone by identifier. +--- + +# meshstack_landingzone (Data Source) + +Read a single landing zone by identifier. + + + + +## Schema + +### Required + +- `metadata` (Attributes) (see [below for nested schema](#nestedatt--metadata)) + +### Read-Only + +- `api_version` (String) Landing Zone API version. +- `kind` (String) meshObject type, always `meshLandingZone`. +- `spec` (Attributes) (see [below for nested schema](#nestedatt--spec)) + + +### Nested Schema for `metadata` + +Required: + +- `name` (String) Landing Zone identifier. + +Read-Only: + +- `tags` (Map of List of String) Tags of the landing zone. + + + +### Nested Schema for `spec` + +Read-Only: + +- `automate_deletion_approval` (Boolean) Whether deletion approval is automated for this landing zone. +- `automate_deletion_replication` (Boolean) Whether deletion replication is automated for this landing zone. +- `description` (String) Description of the landing zone. +- `display_name` (String) Display name of the landing zone. +- `info_link` (String) Link to additional information about the landing zone. +- `platform_properties` (Attributes) Platform-specific configuration options. (see [below for nested schema](#nestedatt--spec--platform_properties)) +- `platform_ref` (Attributes) Reference to the platform this landing zone belongs to. (see [below for nested schema](#nestedatt--spec--platform_ref)) + + +### Nested Schema for `spec.platform_properties` + +Optional: + +- `aks` (Attributes) AKS platform properties. (see [below for nested schema](#nestedatt--spec--platform_properties--aks)) +- `aws` (Attributes) AWS platform properties. (see [below for nested schema](#nestedatt--spec--platform_properties--aws)) +- `azure` (Attributes) Azure platform properties. (see [below for nested schema](#nestedatt--spec--platform_properties--azure)) +- `azurerg` (Attributes) Azure Resource Group platform properties. (see [below for nested schema](#nestedatt--spec--platform_properties--azurerg)) +- `gcp` (Attributes) GCP platform properties. (see [below for nested schema](#nestedatt--spec--platform_properties--gcp)) +- `kubernetes` (Attributes) Kubernetes platform properties. (see [below for nested schema](#nestedatt--spec--platform_properties--kubernetes)) +- `openshift` (Attributes) OpenShift platform properties. (see [below for nested schema](#nestedatt--spec--platform_properties--openshift)) + +Read-Only: + +- `type` (String) Type of the platform. One of `aws`, `aks`, `azure`, `azurerg`, `gcp`, `kubernetes`, `openshift`. + + +### Nested Schema for `spec.platform_properties.aks` + +Required: + +- `kubernetes_role_mappings` (Attributes List) Roles need to be mapped from the meshRole to the Cluster Role. You can use both built in roles like 'editor' or custom roles that you setup in the Kubernetes Cluster before. For more information see [the Landing Zone documentation](https://docs.meshcloud.io/meshstack.kubernetes.landing-zones/). (see [below for nested schema](#nestedatt--spec--platform_properties--aks--kubernetes_role_mappings)) + + +### Nested Schema for `spec.platform_properties.aks.kubernetes_role_mappings` + +Required: + +- `platform_roles` (List of String) List of AKS platform roles to assign to the meshProject role. +- `project_role_ref` (Attributes) the meshProject role (see [below for nested schema](#nestedatt--spec--platform_properties--aks--kubernetes_role_mappings--project_role_ref)) + + +### Nested Schema for `spec.platform_properties.aks.kubernetes_role_mappings.project_role_ref` + +Required: + +- `name` (String) The identifier of the meshProjectRole + +Read-Only: + +- `kind` (String) meshObject type, always `meshProjectRole`. + + + + + +### Nested Schema for `spec.platform_properties.aws` + +Required: + +- `aws_enroll_account` (Boolean) If true, accounts will be enrolled to AWS control tower. In case an enrollment configuration is provided for the AWS platform AND this value is set to true, created AWS accounts will automatically be enrolled with AWS Control Tower. Automatic account enrollment does also require the Target Organizational Unit to already be enrolled with AWS Control Tower and the corresponding meshfed-service role needs to be in the "IAM Principal" list for the Portfolio access of the Account Factory Product ID you defined in platform settings. Click [here](https://docs.meshcloud.io/integrations/aws/how-to-integrate/#7-integrate-aws-control-tower) to learn more about the Control Tower setup. +- `aws_role_mappings` (Attributes List) Roles can be mapped from the meshRole to the AWS Role. The AWS role will be part of the role or group name within AWS. If empty, the default that is configured on platform level will be used. (see [below for nested schema](#nestedatt--spec--platform_properties--aws--aws_role_mappings)) +- `aws_target_org_unit_id` (String) The created AWS account for this Landing Zone will be put under the given Organizational Unit. You can also input a Root ID (starting with 'r-') then the account will be put directly under this root without assigning it to an OU (this is not recommended). + +Optional: + +- `aws_lambda_arn` (String) If provided, it is invoked after each project replication. You can use it to trigger a custom Account Vending Machine to perform several additional provisioning steps. + + +### Nested Schema for `spec.platform_properties.aws.aws_lambda_arn` + +Required: + +- `platform_role` (String) The AWS platform role +- `policies` (List of String) List of policies associated with this role mapping +- `project_role_ref` (Attributes) the meshProject role (see [below for nested schema](#nestedatt--spec--platform_properties--aws--aws_lambda_arn--project_role_ref)) + + +### Nested Schema for `spec.platform_properties.aws.aws_lambda_arn.project_role_ref` + +Required: + +- `name` (String) The identifier of the meshProjectRole + +Read-Only: + +- `kind` (String) meshObject type, always `meshProjectRole`. + + + + + +### Nested Schema for `spec.platform_properties.azure` + +Required: + +- `azure_management_group_id` (String) Azure Management Group ID where projects will be created. +- `azure_role_mappings` (Attributes List) An array of mappings between the meshRole and the Azure specific access role. For more information see [the Landing Zone documentation](https://docs.meshcloud.io/meshstack.azure.landing-zones#meshrole-to-platform-role-mapping). If empty, the default that is configured on platform level will be used. (see [below for nested schema](#nestedatt--spec--platform_properties--azure--azure_role_mappings)) + + +### Nested Schema for `spec.platform_properties.azure.azure_role_mappings` + +Required: + +- `azure_group_suffix` (String) The given role name will be injected into the group name via the group naming pattern configured on the platform instance. +- `azure_role_definitions` (Attributes List) List of Azure role definitions (see [below for nested schema](#nestedatt--spec--platform_properties--azure--azure_role_mappings--azure_role_definitions)) +- `project_role_ref` (Attributes) the meshProject role (see [below for nested schema](#nestedatt--spec--platform_properties--azure--azure_role_mappings--project_role_ref)) + + +### Nested Schema for `spec.platform_properties.azure.azure_role_mappings.azure_role_definitions` + +Required: + +- `azure_role_definition_id` (String) Azure role definition ID + +Optional: + +- `abac_condition` (String) an ABAC condition for the role assignment in form of a string + + + +### Nested Schema for `spec.platform_properties.azure.azure_role_mappings.project_role_ref` + +Required: + +- `name` (String) The identifier of the meshProjectRole + +Read-Only: + +- `kind` (String) meshObject type, always `meshProjectRole`. + + + + + +### Nested Schema for `spec.platform_properties.azurerg` + +Required: + +- `azure_rg_location` (String) The newly created Resource Group for the meshProjects will get assigned to this location. It must be all lower case and without spaces (e.g. `eastus2` for East US 2). In order to list the available locations you can use `az account list-locations --query "[*].name" --out tsv | sort` +- `azure_rg_role_mappings` (Attributes List) An array of mappings between the meshRole and the Azure specific access role. For more information see [the Landing Zone documentation](https://docs.meshcloud.io/meshstack.azure.landing-zones#meshrole-to-platform-role-mapping). If empty, the default that is configured on platform level will be used. (see [below for nested schema](#nestedatt--spec--platform_properties--azurerg--azure_rg_role_mappings)) + +Optional: + +- `azure_function` (Attributes) Assign an Azure function to the Landing Zone configuration to trigger a small piece of code in the cloud. (see [below for nested schema](#nestedatt--spec--platform_properties--azurerg--azure_function)) + + +### Nested Schema for `spec.platform_properties.azurerg.azure_function` + +Required: + +- `azure_group_suffix` (String) The given role name will be injected into the group name via the group naming pattern configured on the platform instance. +- `azure_role_definition_ids` (List of String) Role Definitions with the given IDs will be attached to this Azure Role. +- `project_role_ref` (Attributes) the meshProject role (see [below for nested schema](#nestedatt--spec--platform_properties--azurerg--azure_function--project_role_ref)) + + +### Nested Schema for `spec.platform_properties.azurerg.azure_function.project_role_ref` + +Required: + +- `name` (String) The identifier of the meshProjectRole + +Read-Only: + +- `kind` (String) meshObject type, always `meshProjectRole`. + + + + +### Nested Schema for `spec.platform_properties.azurerg.azure_function` + +Required: + +- `azure_function_scope` (String) The unique ID of the Azure Enterprise Application your function belongs to. More details are described [here](https://docs.meshcloud.io/docs/meshstack.azure.landing-zones.html#azure-function-invocation). +- `azure_function_url` (String) The URL of your Azure Function. This is typically a value like https://my-function-app.azurewebsites.net/myfunc + + + + +### Nested Schema for `spec.platform_properties.gcp` + +Required: + +- `gcp_role_mappings` (Attributes List) You can use both built-in roles like 'roles/editor' or custom roles like 'organizations/123123123123/roles/meshstack.project_developer'. For more information see [the Landing Zone documentation](https://docs.meshcloud.io/meshstack.gcp.landing-zones/#meshrole-to-platform-role-mapping). Multiple GCP Roles can be assigned to one meshRole. If empty, the default that is configured on platform level will be used. (see [below for nested schema](#nestedatt--spec--platform_properties--gcp--gcp_role_mappings)) + +Optional: + +- `gcp_cloud_function_url` (String) If a GCP Cloud Function URL is provided it is getting called at the end of the replication process. +- `gcp_folder_id` (String) Google Cloud Projects will be added to this Google Cloud Folder. This allows applying Organization Policies to all projects managed under this Landing Zone. + + +### Nested Schema for `spec.platform_properties.gcp.gcp_folder_id` + +Required: + +- `platform_roles` (List of String) Can be empty. List of GCP IAM roles to assign to the meshProject role. +- `project_role_ref` (Attributes) the meshProject role (see [below for nested schema](#nestedatt--spec--platform_properties--gcp--gcp_folder_id--project_role_ref)) + + +### Nested Schema for `spec.platform_properties.gcp.gcp_folder_id.project_role_ref` + +Required: + +- `name` (String) The identifier of the meshProjectRole + +Read-Only: + +- `kind` (String) meshObject type, always `meshProjectRole`. + + + + + +### Nested Schema for `spec.platform_properties.kubernetes` + +Required: + +- `kubernetes_role_mappings` (Attributes List) Kubernetes role mappings configuration. (see [below for nested schema](#nestedatt--spec--platform_properties--kubernetes--kubernetes_role_mappings)) + + +### Nested Schema for `spec.platform_properties.kubernetes.kubernetes_role_mappings` + +Required: + +- `platform_roles` (List of String) Roles need to be mapped from the meshRole to the Cluster Role. You can use both built in roles like 'editor' or custom roles that you setup in the Kubernetes Cluster before. For more information see [the Landing Zone documentation](https://docs.meshcloud.io/meshstack.kubernetes.landing-zones/). +- `project_role_ref` (Attributes) the meshProject role (see [below for nested schema](#nestedatt--spec--platform_properties--kubernetes--kubernetes_role_mappings--project_role_ref)) + + +### Nested Schema for `spec.platform_properties.kubernetes.kubernetes_role_mappings.project_role_ref` + +Required: + +- `name` (String) The identifier of the meshProjectRole + +Read-Only: + +- `kind` (String) meshObject type, always `meshProjectRole`. + + + + + +### Nested Schema for `spec.platform_properties.openshift` + +Optional: + +- `openshift_template` (String) OpenShift template to use for this landing zone. + + + + +### Nested Schema for `spec.platform_ref` + +Read-Only: + +- `kind` (String) Must always be set to meshPlatform +- `uuid` (String) UUID of the platform. diff --git a/docs/resources/landingzone.md b/docs/resources/landingzone.md new file mode 100644 index 00000000..17d09f35 --- /dev/null +++ b/docs/resources/landingzone.md @@ -0,0 +1,314 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "meshstack_landingzone Resource - terraform-provider-meshstack" +subcategory: "" +description: |- + Represents a meshStack landing zone. +--- + +# meshstack_landingzone (Resource) + +Represents a meshStack landing zone. + + + + +## Schema + +### Required + +- `metadata` (Attributes) (see [below for nested schema](#nestedatt--metadata)) +- `spec` (Attributes) (see [below for nested schema](#nestedatt--spec)) + +### Read-Only + +- `api_version` (String) Landing zone datatype version +- `kind` (String) meshObject type, always `meshLandingZone`. +- `status` (Attributes) Current Landing Zone status. (see [below for nested schema](#nestedatt--status)) + + +### Nested Schema for `metadata` + +Required: + +- `name` (String) Landing zone identifier. + +Optional: + +- `tags` (Map of List of String) Tags of the landing zone. + + + +### Nested Schema for `spec` + +Required: + +- `automate_deletion_approval` (Boolean) Whether deletion approval is automated for this landing zone. +- `automate_deletion_replication` (Boolean) Whether deletion replication is automated for this landing zone. +- `description` (String) description of the landing zone. +- `display_name` (String) Display name of the landing zone. +- `platform_properties` (Attributes) Platform-specific configuration options. (see [below for nested schema](#nestedatt--spec--platform_properties)) +- `platform_ref` (Attributes) Reference to the platform this landing zone belongs to. (see [below for nested schema](#nestedatt--spec--platform_ref)) + +Optional: + +- `info_link` (String) Link to additional information about the landing zone. + + +### Nested Schema for `spec.platform_properties` + +Optional: + +- `aks` (Attributes) AKS platform properties. (see [below for nested schema](#nestedatt--spec--platform_properties--aks)) +- `aws` (Attributes) AWS platform properties. (see [below for nested schema](#nestedatt--spec--platform_properties--aws)) +- `azure` (Attributes) Azure platform properties. (see [below for nested schema](#nestedatt--spec--platform_properties--azure)) +- `azurerg` (Attributes) Azure Resource Group platform properties. (see [below for nested schema](#nestedatt--spec--platform_properties--azurerg)) +- `gcp` (Attributes) GCP platform properties. (see [below for nested schema](#nestedatt--spec--platform_properties--gcp)) +- `kubernetes` (Attributes) Kubernetes platform properties. (see [below for nested schema](#nestedatt--spec--platform_properties--kubernetes)) +- `openshift` (Attributes) OpenShift platform properties. (see [below for nested schema](#nestedatt--spec--platform_properties--openshift)) + +Read-Only: + +- `type` (String) Type of the platform. This field is automatically inferred from which platform configuration is provided and cannot be set manually. + + +### Nested Schema for `spec.platform_properties.aks` + +Required: + +- `kubernetes_role_mappings` (Attributes List) Roles need to be mapped from the meshRole to the Cluster Role. You can use both built in roles like 'editor' or custom roles that you setup in the Kubernetes Cluster before. For more information see [the Landing Zone documentation](https://docs.meshcloud.io/meshstack.kubernetes.landing-zones/). (see [below for nested schema](#nestedatt--spec--platform_properties--aks--kubernetes_role_mappings)) + + +### Nested Schema for `spec.platform_properties.aks.kubernetes_role_mappings` + +Required: + +- `platform_roles` (List of String) List of AKS platform roles to assign to the meshProject role. +- `project_role_ref` (Attributes) the meshProject role (see [below for nested schema](#nestedatt--spec--platform_properties--aks--kubernetes_role_mappings--project_role_ref)) + + +### Nested Schema for `spec.platform_properties.aks.kubernetes_role_mappings.project_role_ref` + +Required: + +- `name` (String) The identifier of the meshProjectRole + +Read-Only: + +- `kind` (String) meshObject type, always `meshProjectRole`. + + + + + +### Nested Schema for `spec.platform_properties.aws` + +Required: + +- `aws_enroll_account` (Boolean) If true, accounts will be enrolled to AWS control tower. In case an enrollment configuration is provided for the AWS platform AND this value is set to true, created AWS accounts will automatically be enrolled with AWS Control Tower. Automatic account enrollment does also require the Target Organizational Unit to already be enrolled with AWS Control Tower and the corresponding meshfed-service role needs to be in the "IAM Principal" list for the Portfolio access of the Account Factory Product ID you defined in platform settings. Click [here](https://docs.meshcloud.io/integrations/aws/how-to-integrate/#7-integrate-aws-control-tower) to learn more about the Control Tower setup. +- `aws_role_mappings` (Attributes List) Roles can be mapped from the meshRole to the AWS Role. The AWS role will be part of the role or group name within AWS. If empty, the default that is configured on platform level will be used. (see [below for nested schema](#nestedatt--spec--platform_properties--aws--aws_role_mappings)) +- `aws_target_org_unit_id` (String) The created AWS account for this Landing Zone will be put under the given Organizational Unit. You can also input a Root ID (starting with 'r-') then the account will be put directly under this root without assigning it to an OU (this is not recommended). + +Optional: + +- `aws_lambda_arn` (String) If provided, it is invoked after each project replication. You can use it to trigger a custom Account Vending Machine to perform several additional provisioning steps. + + +### Nested Schema for `spec.platform_properties.aws.aws_lambda_arn` + +Required: + +- `platform_role` (String) The AWS platform role +- `policies` (List of String) List of policies associated with this role mapping +- `project_role_ref` (Attributes) the meshProject role (see [below for nested schema](#nestedatt--spec--platform_properties--aws--aws_lambda_arn--project_role_ref)) + + +### Nested Schema for `spec.platform_properties.aws.aws_lambda_arn.project_role_ref` + +Required: + +- `name` (String) The identifier of the meshProjectRole + +Read-Only: + +- `kind` (String) meshObject type, always `meshProjectRole`. + + + + + +### Nested Schema for `spec.platform_properties.azure` + +Required: + +- `azure_management_group_id` (String) Azure Management Group ID where projects will be created. +- `azure_role_mappings` (Attributes List) An array of mappings between the meshRole and the Azure specific access role. For more information see [the Landing Zone documentation](https://docs.meshcloud.io/meshstack.azure.landing-zones#meshrole-to-platform-role-mapping). If empty, the default that is configured on platform level will be used. (see [below for nested schema](#nestedatt--spec--platform_properties--azure--azure_role_mappings)) + + +### Nested Schema for `spec.platform_properties.azure.azure_role_mappings` + +Required: + +- `azure_group_suffix` (String) The given role name will be injected into the group name via the group naming pattern configured on the platform instance. +- `azure_role_definitions` (Attributes List) List of Azure role definitions (see [below for nested schema](#nestedatt--spec--platform_properties--azure--azure_role_mappings--azure_role_definitions)) +- `project_role_ref` (Attributes) the meshProject role (see [below for nested schema](#nestedatt--spec--platform_properties--azure--azure_role_mappings--project_role_ref)) + + +### Nested Schema for `spec.platform_properties.azure.azure_role_mappings.azure_role_definitions` + +Required: + +- `azure_role_definition_id` (String) Azure role definition ID + +Optional: + +- `abac_condition` (String) an ABAC condition for the role assignment in form of a string + + + +### Nested Schema for `spec.platform_properties.azure.azure_role_mappings.project_role_ref` + +Required: + +- `name` (String) The identifier of the meshProjectRole + +Read-Only: + +- `kind` (String) meshObject type, always `meshProjectRole`. + + + + + +### Nested Schema for `spec.platform_properties.azurerg` + +Required: + +- `azure_rg_location` (String) The newly created Resource Group for the meshProjects will get assigned to this location. It must be all lower case and without spaces (e.g. `eastus2` for East US 2). In order to list the available locations you can use `az account list-locations --query "[*].name" --out tsv | sort` +- `azure_rg_role_mappings` (Attributes List) An array of mappings between the meshRole and the Azure specific access role. For more information see [the Landing Zone documentation](https://docs.meshcloud.io/meshstack.azure.landing-zones#meshrole-to-platform-role-mapping). If empty, the default that is configured on platform level will be used. (see [below for nested schema](#nestedatt--spec--platform_properties--azurerg--azure_rg_role_mappings)) + +Optional: + +- `azure_function` (Attributes) Assign an Azure function to the Landing Zone configuration to trigger a small piece of code in the cloud. (see [below for nested schema](#nestedatt--spec--platform_properties--azurerg--azure_function)) + + +### Nested Schema for `spec.platform_properties.azurerg.azure_function` + +Required: + +- `azure_group_suffix` (String) The given role name will be injected into the group name via the group naming pattern configured on the platform instance. +- `azure_role_definition_ids` (List of String) Role Definitions with the given IDs will be attached to this Azure Role. +- `project_role_ref` (Attributes) the meshProject role (see [below for nested schema](#nestedatt--spec--platform_properties--azurerg--azure_function--project_role_ref)) + + +### Nested Schema for `spec.platform_properties.azurerg.azure_function.project_role_ref` + +Required: + +- `name` (String) The identifier of the meshProjectRole + +Read-Only: + +- `kind` (String) meshObject type, always `meshProjectRole`. + + + + +### Nested Schema for `spec.platform_properties.azurerg.azure_function` + +Required: + +- `azure_function_scope` (String) The unique ID of the Azure Enterprise Application your function belongs to. More details are described [here](https://docs.meshcloud.io/docs/meshstack.azure.landing-zones.html#azure-function-invocation). +- `azure_function_url` (String) The URL of your Azure Function. This is typically a value like https://my-function-app.azurewebsites.net/myfunc + + + + +### Nested Schema for `spec.platform_properties.gcp` + +Required: + +- `gcp_role_mappings` (Attributes List) You can use both built-in roles like 'roles/editor' or custom roles like 'organizations/123123123123/roles/meshstack.project_developer'. For more information see [the Landing Zone documentation](https://docs.meshcloud.io/meshstack.gcp.landing-zones/#meshrole-to-platform-role-mapping). Multiple GCP Roles can be assigned to one meshRole. If empty, the default that is configured on platform level will be used. (see [below for nested schema](#nestedatt--spec--platform_properties--gcp--gcp_role_mappings)) + +Optional: + +- `gcp_cloud_function_url` (String) If a GCP Cloud Function URL is provided it is getting called at the end of the replication process. +- `gcp_folder_id` (String) Google Cloud Projects will be added to this Google Cloud Folder. This allows applying Organization Policies to all projects managed under this Landing Zone. + + +### Nested Schema for `spec.platform_properties.gcp.gcp_folder_id` + +Required: + +- `platform_roles` (List of String) Can be empty. List of GCP IAM roles to assign to the meshProject role. +- `project_role_ref` (Attributes) the meshProject role (see [below for nested schema](#nestedatt--spec--platform_properties--gcp--gcp_folder_id--project_role_ref)) + + +### Nested Schema for `spec.platform_properties.gcp.gcp_folder_id.project_role_ref` + +Required: + +- `name` (String) The identifier of the meshProjectRole + +Read-Only: + +- `kind` (String) meshObject type, always `meshProjectRole`. + + + + + +### Nested Schema for `spec.platform_properties.kubernetes` + +Required: + +- `kubernetes_role_mappings` (Attributes List) Kubernetes role mappings configuration. (see [below for nested schema](#nestedatt--spec--platform_properties--kubernetes--kubernetes_role_mappings)) + + +### Nested Schema for `spec.platform_properties.kubernetes.kubernetes_role_mappings` + +Required: + +- `platform_roles` (List of String) Roles need to be mapped from the meshRole to the Cluster Role. You can use both built in roles like 'editor' or custom roles that you setup in the Kubernetes Cluster before. For more information see [the Landing Zone documentation](https://docs.meshcloud.io/meshstack.kubernetes.landing-zones/). +- `project_role_ref` (Attributes) the meshProject role (see [below for nested schema](#nestedatt--spec--platform_properties--kubernetes--kubernetes_role_mappings--project_role_ref)) + + +### Nested Schema for `spec.platform_properties.kubernetes.kubernetes_role_mappings.project_role_ref` + +Required: + +- `name` (String) The identifier of the meshProjectRole + +Read-Only: + +- `kind` (String) meshObject type, always `meshProjectRole`. + + + + + +### Nested Schema for `spec.platform_properties.openshift` + +Optional: + +- `openshift_template` (String) OpenShift template to use for this landing zone. + + + + +### Nested Schema for `spec.platform_ref` + +Required: + +- `kind` (String) Must always be set to meshPlatform +- `uuid` (String) UUID of the platform. + + + + +### Nested Schema for `status` + +Read-Only: + +- `disabled` (Boolean) True if the landing zone is disabled. +- `restricted` (Boolean) If true, users will be unable to select this landing zone in meshPanel. Only Platform teams can create tenants using restricted landing zones with the meshObject API. diff --git a/examples/data-sources/meshstack_landing_zone/data-source.tf b/examples/data-sources/meshstack_landing_zone/data-source.tf new file mode 100644 index 00000000..3a64a68f --- /dev/null +++ b/examples/data-sources/meshstack_landing_zone/data-source.tf @@ -0,0 +1,5 @@ +data "meshstack_landingzone" "example" { + metadata = { + name = "my-landingzone-identifier" + } +} diff --git a/examples/resources/meshstack_landing_zone/import.sh b/examples/resources/meshstack_landing_zone/import.sh new file mode 100644 index 00000000..98ddb603 --- /dev/null +++ b/examples/resources/meshstack_landing_zone/import.sh @@ -0,0 +1,2 @@ +# import via landingzone identifier +terraform import meshstack_landingzone.example my-landingzone-identifier diff --git a/examples/resources/meshstack_landing_zone/resource.tf b/examples/resources/meshstack_landing_zone/resource.tf new file mode 100644 index 00000000..d60c97dd --- /dev/null +++ b/examples/resources/meshstack_landing_zone/resource.tf @@ -0,0 +1,38 @@ +resource "meshstack_landingzone" "example" { + metadata = { + name = "my-landing-zone-identifier" + tags = { + "confidentiality" = ["internal"], + "environment" = ["dev", "qa", "test"], + } + } + spec = { + display_name = "My Landing Zone's Display Name" + description = "My Landing Zone Description" + automate_deletion_approval = false + automate_deletion_replication = false + info_link = "https://example.com/info-about-aws-landing-zone" + platform_ref = { + uuid = "4af5864a-da15-11ed-98c8-0242ac190003" + kind = "meshPlatform" + } + platform_properties = { + aws = { + aws_target_org_unit_id = "ou-lpzq-kmf17bec" + aws_enroll_account = true + aws_lambda_arn = "arn:aws:lambda:us-east-1:123456789012:function:MyLambdaFunction" + aws_role_mappings = [ + { + project_role_ref = { + name = "reader" + } + platform_role = "project-reader" + policies = [ + "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess", + ] + } + ] + } + } + } +} diff --git a/internal/modifiers/platformtypemodifier/set_type_from_platform.go b/internal/modifiers/platformtypemodifier/set_type_from_platform.go new file mode 100644 index 00000000..9974fbf2 --- /dev/null +++ b/internal/modifiers/platformtypemodifier/set_type_from_platform.go @@ -0,0 +1,54 @@ +package platformtypemodifier + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +// SetTypeFromPlatform returns a plan modifier that automatically sets the type field +// based on which platform-specific configuration is present. +func SetTypeFromPlatform() planmodifier.String { + return platformTypeModifier{} +} + +type platformTypeModifier struct{} + +// Description returns a human-readable description of the plan modifier. +func (m platformTypeModifier) Description(_ context.Context) string { + return "Automatically sets type based on which platform configuration is provided" +} + +// MarkdownDescription returns a markdown description of the plan modifier. +func (m platformTypeModifier) MarkdownDescription(_ context.Context) string { + return "Automatically sets `type` based on which platform configuration is provided" +} + +// PlanModifyString implements the plan modification logic. +func (m platformTypeModifier) PlanModifyString(ctx context.Context, req planmodifier.StringRequest, resp *planmodifier.StringResponse) { + // Get the parent object (platform_properties) + platformPropsPath := req.Path.ParentPath() + var platformProps types.Object + resp.Diagnostics.Append(req.Plan.GetAttribute(ctx, platformPropsPath, &platformProps)...) + if resp.Diagnostics.HasError() { + return + } + + if platformProps.IsNull() || platformProps.IsUnknown() { + return + } + + // Check which platform-specific configuration is present + platformTypes := []string{"aws", "aks", "azure", "azurerg", "gcp", "kubernetes", "openshift"} + + for _, platformType := range platformTypes { + attrValue := platformProps.Attributes()[platformType] + if attrValue != nil && !attrValue.IsNull() { + resp.PlanValue = types.StringValue(platformType) + // Early return is okay here: if more than one platform-property is set, the singlePlatformValidator + // will catch this. + return + } + } +} diff --git a/internal/modifiers/platformtypemodifier/validate_single_platform.go b/internal/modifiers/platformtypemodifier/validate_single_platform.go new file mode 100644 index 00000000..6ae08bbc --- /dev/null +++ b/internal/modifiers/platformtypemodifier/validate_single_platform.go @@ -0,0 +1,60 @@ +package platformtypemodifier + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" +) + +// ValidateSinglePlatform returns a plan modifier that validates that only one platform +// configuration is specified at a time. +func ValidateSinglePlatform() planmodifier.Object { + return singlePlatformValidator{} +} + +type singlePlatformValidator struct{} + +// Description returns a human-readable description of the plan modifier. +func (v singlePlatformValidator) Description(_ context.Context) string { + return "Validates that only one platform configuration is specified" +} + +// MarkdownDescription returns a markdown description of the plan modifier. +func (v singlePlatformValidator) MarkdownDescription(_ context.Context) string { + return "Validates that only one platform configuration is specified" +} + +// PlanModifyObject implements the plan modification logic. +func (v singlePlatformValidator) PlanModifyObject(ctx context.Context, req planmodifier.ObjectRequest, resp *planmodifier.ObjectResponse) { + if req.PlanValue.IsNull() || req.PlanValue.IsUnknown() { + return + } + + platformTypes := []string{"aws", "aks", "azure", "azurerg", "gcp", "kubernetes", "openshift"} + + var configuredPlatforms []string + for _, platformType := range platformTypes { + attrValue := req.PlanValue.Attributes()[platformType] + if attrValue != nil && !attrValue.IsNull() { + configuredPlatforms = append(configuredPlatforms, platformType) + } + } + + if len(configuredPlatforms) > 1 { + resp.Diagnostics.AddError( + "Multiple Platform Configurations", + fmt.Sprintf("Only one platform configuration can be specified within "+ + "platform_properties, but found: %v. Please specify only one platform "+ + "configuration.", configuredPlatforms), + ) + } + + if len(configuredPlatforms) == 0 { + resp.Diagnostics.AddError( + "No Platform Configuration", + "At least one platform configuration must be specified within platform_properties. "+ + "Please specify one of: aws, aks, azure, azurerg, gcp, kubernetes, openshift.", + ) + } +} diff --git a/internal/provider/landingzone_data_source.go b/internal/provider/landingzone_data_source.go new file mode 100644 index 00000000..bebbe08b --- /dev/null +++ b/internal/provider/landingzone_data_source.go @@ -0,0 +1,181 @@ +package provider + +import ( + "context" + "fmt" + + "github.com/meshcloud/terraform-provider-meshstack/client" + + "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/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var ( + _ datasource.DataSource = &landingZoneDataSource{} + _ datasource.DataSourceWithConfigure = &landingZoneDataSource{} +) + +func NewLandingZoneDataSource() datasource.DataSource { + return &landingZoneDataSource{} +} + +type landingZoneDataSource struct { + client *client.MeshStackProviderClient +} + +func (d *landingZoneDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_landingzone" +} + +// Schema defines the schema for the data source. +func (d *landingZoneDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Read a single landing zone by identifier.", + + Attributes: map[string]schema.Attribute{ + "api_version": schema.StringAttribute{ + MarkdownDescription: "Landing Zone API version.", + Computed: true, + }, + "kind": schema.StringAttribute{ + MarkdownDescription: "meshObject type, always `meshLandingZone`.", + Computed: true, + Validators: []validator.String{ + stringvalidator.OneOf([]string{"meshLandingZone"}...), + }, + }, + + "metadata": schema.SingleNestedAttribute{ + Required: true, + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + MarkdownDescription: "Landing Zone identifier.", + Required: true, + }, + "tags": schema.MapAttribute{ + MarkdownDescription: "Tags of the landing zone.", + ElementType: types.ListType{ElemType: types.StringType}, + Computed: true, + }, + }, + }, + + "spec": schema.SingleNestedAttribute{ + Computed: true, + Attributes: map[string]schema.Attribute{ + "display_name": schema.StringAttribute{ + MarkdownDescription: "Display name of the landing zone.", + Computed: true, + }, + "description": schema.StringAttribute{ + MarkdownDescription: "Description of the landing zone.", + Computed: true, + }, + "automate_deletion_approval": schema.BoolAttribute{ + MarkdownDescription: "Whether deletion approval is automated for this landing zone.", + Computed: true, + }, + "automate_deletion_replication": schema.BoolAttribute{ + MarkdownDescription: "Whether deletion replication is automated for this landing zone.", + Computed: true, + }, + "info_link": schema.StringAttribute{ + MarkdownDescription: "Link to additional information about the landing zone.", + Computed: true, + }, + "platform_ref": schema.SingleNestedAttribute{ + MarkdownDescription: "Reference to the platform this landing zone belongs to.", + Computed: true, + Attributes: map[string]schema.Attribute{ + "uuid": schema.StringAttribute{ + MarkdownDescription: "UUID of the platform.", + Computed: true, + }, + "kind": schema.StringAttribute{ + MarkdownDescription: "Must always be set to meshPlatform", + Computed: true, + Validators: []validator.String{ + stringvalidator.OneOf("meshPlatform"), + }, + }, + }, + }, + "platform_properties": schema.SingleNestedAttribute{ + MarkdownDescription: "Platform-specific configuration options.", + Computed: true, + Attributes: map[string]schema.Attribute{ + "type": schema.StringAttribute{ + MarkdownDescription: "Type of the platform. One of `aws`, `aks`, `azure`, `azurerg`, `gcp`, `kubernetes`, `openshift`.", + Computed: true, + Validators: []validator.String{ + stringvalidator.OneOf([]string{"aws", "aks", "azure", "azurerg", "gcp", "kubernetes", "openshift"}...), + }, + }, + "aws": awsPlatformConfigSchema(), + "aks": aksPlatformConfigSchema(), + "azure": azurePlatformConfigSchema(), + "azurerg": azureRgPlatformConfigSchema(), + "gcp": gcpPlatformConfigSchema(), + "kubernetes": kubernetesPlatformConfigSchema(), + "openshift": openShiftPlatformConfigSchema(), + }, + }, + }, + }, + }, + } +} + +// Configure adds the provider configured client to the data source. +func (d *landingZoneDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*client.MeshStackProviderClient) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *client.MeshStackProviderClient, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + d.client = client +} + +// Read refreshes the Terraform state with the latest data. +func (d *landingZoneDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var name string + + // Read Terraform configuration data into the model + resp.Diagnostics.Append(req.Config.GetAttribute(ctx, path.Root("metadata").AtName("name"), &name)...) + + if resp.Diagnostics.HasError() { + return + } + + landingZone, err := d.client.ReadLandingZone(name) + if err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("Could not read landing zone '%s'", name), + err.Error(), + ) + return + } + + if landingZone == nil { + resp.Diagnostics.AddError( + "Landing zone not found", + fmt.Sprintf("The requested landingZone '%s' was not found.", name), + ) + return + } + + // client data maps directly to the schema so we just need to set the state + resp.Diagnostics.Append(resp.State.Set(ctx, landingZone)...) +} diff --git a/internal/provider/landingzone_resource.go b/internal/provider/landingzone_resource.go new file mode 100644 index 00000000..aa6965b5 --- /dev/null +++ b/internal/provider/landingzone_resource.go @@ -0,0 +1,565 @@ +package provider + +import ( + "context" + "fmt" + "regexp" + + "github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier" + "github.com/meshcloud/terraform-provider-meshstack/client" + "github.com/meshcloud/terraform-provider-meshstack/internal/modifiers/platformtypemodifier" + + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/attr" + "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/mapdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +// Ensure the implementation satisfies the expected interfaces. +var ( + _ resource.Resource = &landingZoneResource{} + _ resource.ResourceWithConfigure = &landingZoneResource{} + _ resource.ResourceWithImportState = &landingZoneResource{} +) + +// NewLandingZoneResource is a helper function to simplify the provider implementation. +func NewLandingZoneResource() resource.Resource { + return &landingZoneResource{} +} + +// landingZoneResource is the resource implementation. +type landingZoneResource struct { + client *client.MeshStackProviderClient +} + +// Metadata returns the resource type name. +func (r *landingZoneResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_landingzone" +} + +// Configure adds the provider configured client to the resource. +func (r *landingZoneResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*client.MeshStackProviderClient) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *MeshStackProviderClient, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + r.client = client +} + +func (r *landingZoneResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Represents a meshStack landing zone.", + Attributes: map[string]schema.Attribute{ + "api_version": schema.StringAttribute{ + MarkdownDescription: "Landing zone datatype version", + Computed: true, + Default: stringdefault.StaticString("v1-preview"), + PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}, + }, + "kind": schema.StringAttribute{ + MarkdownDescription: "meshObject type, always `meshLandingZone`.", + Computed: true, + Default: stringdefault.StaticString("meshLandingZone"), + Validators: []validator.String{ + stringvalidator.OneOf([]string{"meshLandingZone"}...), + }, + PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}, + }, + + "metadata": schema.SingleNestedAttribute{ + Required: true, + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + MarkdownDescription: "Landing zone identifier.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.RegexMatches( + regexp.MustCompile(`^[a-z0-9]+(-[a-z0-9]+)*$`), + "must be alphanumeric with dashes, must be lowercase, and have no leading, trailing or consecutive dashes", + ), + }, + }, + "tags": schema.MapAttribute{ + MarkdownDescription: "Tags of the landing zone.", + ElementType: types.ListType{ElemType: types.StringType}, + Optional: true, + Computed: true, + Default: mapdefault.StaticValue(types.MapValueMust(types.ListType{ElemType: types.StringType}, map[string]attr.Value{})), + }, + }, + }, + + "spec": schema.SingleNestedAttribute{ + Required: true, + Attributes: map[string]schema.Attribute{ + "display_name": schema.StringAttribute{ + MarkdownDescription: "Display name of the landing zone.", + Required: true, + }, + "description": schema.StringAttribute{ + MarkdownDescription: "description of the landing zone.", + Required: true, + }, + "automate_deletion_approval": schema.BoolAttribute{ + MarkdownDescription: "Whether deletion approval is automated for this landing zone.", + Required: true, + }, + "automate_deletion_replication": schema.BoolAttribute{ + MarkdownDescription: "Whether deletion replication is automated for this landing zone.", + Required: true, + }, + "info_link": schema.StringAttribute{ + MarkdownDescription: "Link to additional information about the landing zone.", + Optional: true, + }, + "platform_ref": schema.SingleNestedAttribute{ + MarkdownDescription: "Reference to the platform this landing zone belongs to.", + Required: true, + Attributes: map[string]schema.Attribute{ + "uuid": schema.StringAttribute{ + PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, + MarkdownDescription: "UUID of the platform.", + Required: true, + }, + "kind": schema.StringAttribute{ + MarkdownDescription: "Must always be set to meshPlatform", + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf("meshPlatform"), + }, + }, + }, + }, + "platform_properties": schema.SingleNestedAttribute{ + MarkdownDescription: "Platform-specific configuration options.", + Required: true, + Sensitive: false, + PlanModifiers: []planmodifier.Object{ + platformtypemodifier.ValidateSinglePlatform(), + }, + Attributes: map[string]schema.Attribute{ + "aws": awsPlatformConfigSchema(), + "aks": aksPlatformConfigSchema(), + "azure": azurePlatformConfigSchema(), + "azurerg": azureRgPlatformConfigSchema(), + "gcp": gcpPlatformConfigSchema(), + "kubernetes": kubernetesPlatformConfigSchema(), + "openshift": openShiftPlatformConfigSchema(), + "type": schema.StringAttribute{ + MarkdownDescription: "Type of the platform. This field is automatically inferred from which platform configuration is provided and cannot be set manually.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + platformtypemodifier.SetTypeFromPlatform(), + }, + }, + }, + }, + }, + }, + + "status": schema.SingleNestedAttribute{ + MarkdownDescription: "Current Landing Zone status.", + Computed: true, + PlanModifiers: []planmodifier.Object{objectplanmodifier.UseStateForUnknown()}, + Attributes: map[string]schema.Attribute{ + "disabled": schema.BoolAttribute{ + MarkdownDescription: "True if the landing zone is disabled.", + Computed: true, + }, + "restricted": schema.BoolAttribute{ + MarkdownDescription: "If true, users will be unable to select this landing zone in meshPanel. " + + "Only Platform teams can create tenants using restricted landing zones with the meshObject API.", + Computed: true, + }, + }, + }, + }, + } +} + +func awsPlatformConfigSchema() schema.Attribute { + return schema.SingleNestedAttribute{ + MarkdownDescription: "AWS platform properties.", + Optional: true, + Attributes: map[string]schema.Attribute{ + "aws_target_org_unit_id": schema.StringAttribute{ + MarkdownDescription: "The created AWS account for this Landing Zone will be put under the given Organizational Unit. You can also input a Root ID (starting with 'r-') then the account will be put directly under this root without assigning it to an OU (this is not recommended).", + Required: true, + }, + "aws_enroll_account": schema.BoolAttribute{ + MarkdownDescription: "If true, accounts will be enrolled to AWS control tower. In case an enrollment configuration is provided for the AWS platform AND this value is set to true, created AWS accounts will automatically be enrolled with AWS Control Tower. Automatic account enrollment does also require the Target Organizational Unit to already be enrolled with AWS Control Tower and the corresponding meshfed-service role needs to be in the \"IAM Principal\" list for the Portfolio access of the Account Factory Product ID you defined in platform settings. Click [here](https://docs.meshcloud.io/integrations/aws/how-to-integrate/#7-integrate-aws-control-tower) to learn more about the Control Tower setup.", + Required: true, + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.RequiresReplace(), + }, + }, + "aws_lambda_arn": schema.StringAttribute{ + MarkdownDescription: "If provided, it is invoked after each project replication. You can use it to trigger a custom Account Vending Machine to perform several additional provisioning steps.", + Optional: true, + }, + "aws_role_mappings": schema.ListNestedAttribute{ + MarkdownDescription: "Roles can be mapped from the meshRole to the AWS Role. The AWS role will be part of the role or group name within AWS. If empty, the default that is configured on platform level will be used.", + Required: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "project_role_ref": meshProjectRoleAttribute(), + "platform_role": schema.StringAttribute{ + MarkdownDescription: "The AWS platform role", + Required: true, + }, + "policies": schema.ListAttribute{ + MarkdownDescription: "List of policies associated with this role mapping", + ElementType: types.StringType, + Required: true, + }, + }, + }, + }, + }, + } +} + +func aksPlatformConfigSchema() schema.Attribute { + return schema.SingleNestedAttribute{ + MarkdownDescription: "AKS platform properties.", + Optional: true, + Attributes: map[string]schema.Attribute{ + "kubernetes_role_mappings": schema.ListNestedAttribute{ + MarkdownDescription: "Roles need to be mapped from the meshRole to the Cluster Role. You can use " + + "both built in roles like 'editor' or custom roles that you setup in the Kubernetes Cluster " + + "before. For more information see [the Landing Zone documentation](https://docs.meshcloud.io/meshstack.kubernetes.landing-zones/).", + Required: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "project_role_ref": meshProjectRoleAttribute(), + "platform_roles": schema.ListAttribute{ + MarkdownDescription: "List of AKS platform roles to assign to the meshProject role.", + ElementType: types.StringType, + Required: true, + }, + }, + }, + }, + }, + } +} + +func azurePlatformConfigSchema() schema.Attribute { + return schema.SingleNestedAttribute{ + MarkdownDescription: "Azure platform properties.", + Optional: true, + Attributes: map[string]schema.Attribute{ + "azure_management_group_id": schema.StringAttribute{ + Required: true, + MarkdownDescription: "Azure Management Group ID where projects will be created.", + }, + "azure_role_mappings": schema.ListNestedAttribute{ + MarkdownDescription: "An array of mappings between the meshRole and the Azure" + + " specific access role. " + + "For more information see [the Landing Zone documentation](https://docs.meshcloud.io/meshstack.azure.landing-zones#meshrole-to-platform-role-mapping). " + + "If empty, the default that is configured on platform level will be used.", + Required: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "project_role_ref": meshProjectRoleAttribute(), + "azure_group_suffix": schema.StringAttribute{ + MarkdownDescription: "The given role name will be injected into the" + + " group name via the group naming pattern configured on the" + + " platform instance.", + Required: true, + }, + "azure_role_definitions": schema.ListNestedAttribute{ + MarkdownDescription: "List of Azure role definitions", + Required: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "azure_role_definition_id": schema.StringAttribute{ + Required: true, + MarkdownDescription: "Azure role definition ID", + }, + "abac_condition": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "an ABAC condition for the role assignment in form of a string", + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func gcpPlatformConfigSchema() schema.Attribute { + return schema.SingleNestedAttribute{ + MarkdownDescription: "GCP platform properties.", + Optional: true, + Attributes: map[string]schema.Attribute{ + "gcp_cloud_function_url": schema.StringAttribute{ + MarkdownDescription: "If a GCP Cloud Function URL is provided it is getting called at the end of the replication process.", + Optional: true, + }, + "gcp_folder_id": schema.StringAttribute{ + MarkdownDescription: "Google Cloud Projects will be added to this Google Cloud Folder. This allows applying Organization Policies to all projects managed under this Landing Zone.", + Optional: true, + }, + "gcp_role_mappings": schema.ListNestedAttribute{ + MarkdownDescription: "You can use both built-in roles like 'roles/editor' or" + + " custom roles like 'organizations/123123123123/roles/meshstack." + + "project_developer'. For more information see " + + "[the Landing Zone documentation](https://docs.meshcloud.io/meshstack.gcp.landing-zones/#meshrole-to-platform-role-mapping). Multiple GCP Roles can be assigned to one meshRole. If empty, " + + "the default that is configured on platform level will be used.", + Required: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "project_role_ref": meshProjectRoleAttribute(), + "platform_roles": schema.ListAttribute{ + MarkdownDescription: "Can be empty. List of GCP IAM roles to assign to the meshProject role.", + ElementType: types.StringType, + Required: true, + }, + }, + }, + }, + }, + } +} + +func azureRgPlatformConfigSchema() schema.Attribute { + return schema.SingleNestedAttribute{ + MarkdownDescription: "Azure Resource Group platform properties.", + Optional: true, + Attributes: map[string]schema.Attribute{ + "azure_rg_location": schema.StringAttribute{ + MarkdownDescription: "The newly created Resource Group for the meshProjects will get assigned to this location. It must be all lower case and without spaces (e.g. `eastus2` for East US 2). In order to list the available locations you can use `az account list-locations --query \"[*].name\" --out tsv | sort`", + Required: true, + PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, + }, + "azure_rg_role_mappings": schema.ListNestedAttribute{ + MarkdownDescription: "An array of mappings between the meshRole and the Azure" + + " specific access role. " + + "For more information see [the Landing Zone documentation](https://docs.meshcloud.io/meshstack.azure.landing-zones#meshrole-to-platform-role-mapping). " + + "If empty, the default that is configured on platform level will be used.", + Required: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "project_role_ref": meshProjectRoleAttribute(), + "azure_group_suffix": schema.StringAttribute{ + MarkdownDescription: "The given role name will be injected into the" + + " group name via the group naming pattern configured on the" + + " platform instance.", + Required: true, + }, + "azure_role_definition_ids": schema.ListAttribute{ + MarkdownDescription: "Role Definitions with the given IDs will be attached to this Azure Role.", + ElementType: types.StringType, + Required: true, + }, + }, + }, + }, + "azure_function": schema.SingleNestedAttribute{ + MarkdownDescription: "Assign an Azure function to the Landing Zone configuration to trigger a small piece of code in the cloud.", + Optional: true, + Attributes: map[string]schema.Attribute{ + "azure_function_url": schema.StringAttribute{ + MarkdownDescription: "The URL of your Azure Function. This is typically a value like https://my-function-app.azurewebsites.net/myfunc", + Required: true, + }, + "azure_function_scope": schema.StringAttribute{ + MarkdownDescription: "The unique ID of the Azure Enterprise Application your function belongs to. More details are described [here](https://docs.meshcloud.io/docs/meshstack.azure.landing-zones.html#azure-function-invocation).", + Required: true, + }, + }, + }, + }, + } +} + +func kubernetesPlatformConfigSchema() schema.Attribute { + return schema.SingleNestedAttribute{ + MarkdownDescription: "Kubernetes platform properties.", + Optional: true, + Attributes: map[string]schema.Attribute{ + "kubernetes_role_mappings": schema.ListNestedAttribute{ + MarkdownDescription: "Kubernetes role mappings configuration.", + Required: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "project_role_ref": meshProjectRoleAttribute(), + "platform_roles": schema.ListAttribute{ + MarkdownDescription: "Roles need to be mapped from the meshRole to" + + " the Cluster Role. You can use both built in roles like 'editor' or custom roles that you setup in the Kubernetes Cluster" + + " before. For more information see [the Landing Zone documentation](https://docs.meshcloud.io/meshstack.kubernetes.landing-zones/).", + ElementType: types.StringType, + Required: true, + }, + }, + }, + }, + }, + } +} + +func openShiftPlatformConfigSchema() schema.Attribute { + return schema.SingleNestedAttribute{ + MarkdownDescription: "OpenShift platform properties.", + Optional: true, + Attributes: map[string]schema.Attribute{ + "openshift_template": schema.StringAttribute{ + MarkdownDescription: "OpenShift template to use for this landing zone.", + Optional: true, + }, + }, + } +} + +func meshProjectRoleAttribute() schema.SingleNestedAttribute { + return schema.SingleNestedAttribute{ + MarkdownDescription: "the meshProject role", + Required: true, + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Required: true, + MarkdownDescription: "The identifier of the meshProjectRole", + }, + "kind": schema.StringAttribute{ + MarkdownDescription: "meshObject type, always `meshProjectRole`.", + Computed: true, + Default: stringdefault.StaticString("meshProjectRole"), + Validators: []validator.String{ + stringvalidator.OneOf([]string{"meshProjectRole"}...), + }, + PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}, + }, + }, + } +} + +func (r *landingZoneResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + landingZone := client.MeshLandingZoneCreate{ + Metadata: client.MeshLandingZoneMetadata{}, + } + + // Retrieve values from plan + resp.Diagnostics.Append(req.Plan.GetAttribute(ctx, path.Root("api_version"), &landingZone.ApiVersion)...) + resp.Diagnostics.Append(req.Plan.GetAttribute(ctx, path.Root("spec"), &landingZone.Spec)...) + resp.Diagnostics.Append(req.Plan.GetAttribute(ctx, path.Root("metadata"), &landingZone.Metadata)...) + + if resp.Diagnostics.HasError() { + return + } + + createdLandingZone, err := r.client.CreateLandingZone(&landingZone) + if err != nil { + resp.Diagnostics.AddError( + "Error Creating Landing Zone", + "Could not create landing zone, unexpected error: "+err.Error(), + ) + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, createdLandingZone)...) +} + +func (r *landingZoneResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var name string + + // Read Terraform state data into the model + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("metadata").AtName("name"), &name)...) + + if resp.Diagnostics.HasError() { + return + } + + landingZone, err := r.client.ReadLandingZone(name) + if err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("Could not read landing zone '%s'", name), + err.Error(), + ) + return + } + + if landingZone == nil { + // The landing zone was deleted outside of Terraform, so we remove it from the state + resp.State.RemoveResource(ctx) + return + } + + // client data maps directly to the schema so we just need to set the state + resp.Diagnostics.Append(resp.State.Set(ctx, landingZone)...) +} + +func (r *landingZoneResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + landingZone := client.MeshLandingZoneCreate{ + Metadata: client.MeshLandingZoneMetadata{}, + } + + // Retrieve values from plan + resp.Diagnostics.Append(req.Plan.GetAttribute(ctx, path.Root("api_version"), &landingZone.ApiVersion)...) + resp.Diagnostics.Append(req.Plan.GetAttribute(ctx, path.Root("spec"), &landingZone.Spec)...) + resp.Diagnostics.Append(req.Plan.GetAttribute(ctx, path.Root("metadata"), &landingZone.Metadata)...) + + if resp.Diagnostics.HasError() { + return + } + + updatedLandingZone, err := r.client.UpdateLandingZone(landingZone.Metadata.Name, &landingZone) + if err != nil { + resp.Diagnostics.AddError( + "Error Updating Landing Zone", + "Could not update landing zone, unexpected error: "+err.Error(), + ) + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, updatedLandingZone)...) +} + +func (r *landingZoneResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var name string + + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("metadata").AtName("name"), &name)...) + + if resp.Diagnostics.HasError() { + return + } + + err := r.client.DeleteLandingZone(name) + if err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("Could not delete landing zone '%s'", name), + err.Error(), + ) + return + } +} + +func (r *landingZoneResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("metadata").AtName("name"), req, resp) +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 4d4fec37..f44ce3ad 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -123,6 +123,7 @@ func (p *MeshStackProvider) Resources(ctx context.Context) []func() resource.Res NewBuildingBlockResource, NewBuildingBlockV2Resource, NewTagDefinitionResource, + NewLandingZoneResource, } } @@ -139,6 +140,7 @@ func (p *MeshStackProvider) DataSources(ctx context.Context) []func() datasource NewTagDefinitionDataSource, NewTagDefinitionsDataSource, NewTenantV4DataSource, + NewLandingZoneDataSource, } }