diff --git a/.gitignore b/.gitignore index 0dbd3e85..946fbbb0 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ *.log secret.auto.tfvars terraform.tfvars +*.out # Any kind of invironment variables diff --git a/released/usecases/trial_integration_suite/01_base_setup_directory/main.tf b/released/usecases/trial_integration_suite/01_base_setup_directory/main.tf new file mode 100644 index 00000000..81c86a0d --- /dev/null +++ b/released/usecases/trial_integration_suite/01_base_setup_directory/main.tf @@ -0,0 +1,39 @@ +locals { + flattened_role_collection_assignments = flatten([ + for index, role_collection_assignment in var.role_collection_assignments : [ + for index, user in role_collection_assignment.users : { + role_collection_name = role_collection_assignment.role_collection_name + user = user + } + ] + ]) +} + +resource "btp_directory" "self" { + name = var.directory_name + description = var.directory_description + features = toset(var.features) + labels = { + "managed_by" = ["terraform"] + "scope" = ["integration"] + "costcenter" = [var.project_costcenter] + } +} + +resource "btp_directory_entitlement" "dir_entitlement_assignment" { + for_each = { for e in var.entitlement_assignments : e.name => e } + directory_id = btp_directory.self.id + service_name = each.value.name + plan_name = each.value.plan + amount = each.value.amount != 0 ? each.value.amount : null + distribute = each.value.distribute + auto_assign = each.value.auto_assign +} + + +resource "btp_directory_role_collection_assignment" "dir_role_collection_assignment" { + for_each = { for index, role_collection_assignment in local.flattened_role_collection_assignments : index => role_collection_assignment } + directory_id = btp_directory.self.id + role_collection_name = each.value.role_collection_name + user_name = each.value.user +} diff --git a/released/usecases/trial_integration_suite/01_base_setup_directory/outputs.tf b/released/usecases/trial_integration_suite/01_base_setup_directory/outputs.tf new file mode 100644 index 00000000..92e9e7e7 --- /dev/null +++ b/released/usecases/trial_integration_suite/01_base_setup_directory/outputs.tf @@ -0,0 +1,4 @@ +output "directory_id" { + description = "The ID of the directory" + value = btp_directory.self.id +} diff --git a/released/usecases/trial_integration_suite/01_base_setup_directory/provider.tf b/released/usecases/trial_integration_suite/01_base_setup_directory/provider.tf new file mode 100644 index 00000000..2fb8aadb --- /dev/null +++ b/released/usecases/trial_integration_suite/01_base_setup_directory/provider.tf @@ -0,0 +1,12 @@ +terraform { + required_providers { + btp = { + source = "SAP/btp" + version = "~> 1.10.0" + } + } +} + +provider "btp" { + globalaccount = var.globalaccount +} diff --git a/released/usecases/trial_integration_suite/01_base_setup_directory/terraform.tfvars.sample b/released/usecases/trial_integration_suite/01_base_setup_directory/terraform.tfvars.sample new file mode 100644 index 00000000..2cff7486 --- /dev/null +++ b/released/usecases/trial_integration_suite/01_base_setup_directory/terraform.tfvars.sample @@ -0,0 +1,31 @@ +globalaccount = "" +features = ["DEFAULT", "ENTITLEMENTS", "AUTHORIZATIONS"] +project_costcenter = "54321" +entitlement_assignments = [ + { + name = "integrationsuite-trial" + plan = "trial" + amount = 1 + distribute = false + auto_assign = false + }, + // can be added only after fix of https://github.com/SAP/terraform-provider-btp/issues/930 + /* { + name = "APPLICATION_RUNTIME" + plan = "MEMORY" + amount = 2 + distribute = false + auto_assign = false + },*/ +] +// The user executing the script gets automatically added to the directory +role_collection_assignments = [ + { + role_collection_name = "Directory Administrator" + users = ["john.doe@sap.com"] + }, + { + role_collection_name = "Directory Viewer" + users = ["john.doe@sap.com"] + } +] diff --git a/released/usecases/trial_integration_suite/01_base_setup_directory/variables.tf b/released/usecases/trial_integration_suite/01_base_setup_directory/variables.tf new file mode 100644 index 00000000..ef48c1bd --- /dev/null +++ b/released/usecases/trial_integration_suite/01_base_setup_directory/variables.tf @@ -0,0 +1,56 @@ +variable "globalaccount" { + description = "Subdomain of the global account" + type = string +} + +variable "directory_name" { + description = "Name of the directory" + type = string + default = "Integration Directory" +} + +variable "directory_description" { + description = "Description of the directory" + type = string + default = "Directory for all integration subaccounts" +} + +variable "features" { + description = "Directory features to be activated" + type = list(string) + default = ["DEFAULT"] + validation { + condition = alltrue([for feature in var.features : contains(["DEFAULT", "ENTITLEMENTS", "AUTHORIZATIONS"], feature)]) + error_message = "The only supported features are DEFAULT, ENTITLEMENTS and AUTHORIZATIONS" + } +} + +variable "project_costcenter" { + description = "Cost center of the project" + type = string + validation { + condition = can(regex("^[0-9]{5}$", var.project_costcenter)) + error_message = "Cost center must be a 5 digit number" + } +} + +variable "entitlement_assignments" { + description = "list of entitlements to be assigned ot the directory" + type = list(object({ + name = string + plan = string + amount = number + distribute = bool + auto_assign = bool + })) + default = [] +} + +variable "role_collection_assignments" { + description = "List of role collections to assign to a user" + type = list(object({ + role_collection_name = string + users = set(string) + })) + default = [] +} diff --git a/released/usecases/trial_integration_suite/02_base_setup_subaccount/main.tf b/released/usecases/trial_integration_suite/02_base_setup_subaccount/main.tf new file mode 100644 index 00000000..57de87a0 --- /dev/null +++ b/released/usecases/trial_integration_suite/02_base_setup_subaccount/main.tf @@ -0,0 +1,106 @@ +resource "random_uuid" "uuid" {} + +locals { + subaccount_name = "${var.subaccount_stage} ${var.project_name}" + subaccount_description = "Subaccount for Project ${var.project_name} (stage ${var.subaccount_stage})" + subaccount_subdomain = join("-", [lower(replace("${var.subaccount_stage}-${var.project_name}", " ", "-")), random_uuid.uuid.result]) + service_name_prefix = lower(replace("${var.subaccount_stage}-${var.project_name}", " ", "-")) + subaccount_cf_org = local.subaccount_subdomain + cf_space_name = lower(replace("${var.subaccount_stage}-${var.project_name}", " ", "-")) + beta_enabled = var.subaccount_stage == "DEV" ? true : false + usage = var.subaccount_stage == "PROD" ? "USED_FOR_PRODUCTION" : "NOT_USED_FOR_PRODUCTION" +} + +resource "btp_subaccount" "project_subaccount" { + parent_id = var.parent_id + name = local.subaccount_name + subdomain = local.subaccount_subdomain + description = var.project_name + region = var.subaccount_region + beta_enabled = local.beta_enabled + usage = local.usage + labels = { + "stage" = [var.subaccount_stage] + "costcenter" = [var.project_costcenter] + "managed_by" = ["terraform"] + "scope" = ["integration"] + } +} + +resource "btp_subaccount_role_collection_assignment" "emergency_admins" { + for_each = toset(var.emergency_admins) + subaccount_id = btp_subaccount.project_subaccount.id + role_collection_name = "Subaccount Administrator" + user_name = each.value +} + + +resource "btp_subaccount_entitlement" "integrationsuite_app_trial" { + subaccount_id = btp_subaccount.project_subaccount.id + service_name = "integrationsuite-trial" + plan_name = "trial" + amount = 1 +} + +resource "btp_subaccount_entitlement" "cf_memory" { + subaccount_id = btp_subaccount.project_subaccount.id + service_name = "APPLICATION_RUNTIME" + plan_name = "MEMORY" + amount = 1 +} + +resource "btp_subaccount_environment_instance" "cloudfoundry" { + subaccount_id = btp_subaccount.project_subaccount.id + name = local.subaccount_cf_org + environment_type = "cloudfoundry" + service_name = "cloudfoundry" + plan_name = "trial" + landscape_label = "cf-${var.cf_landscape_label}" + parameters = jsonencode({ + instance_name = local.subaccount_cf_org + }) + depends_on = [btp_subaccount_entitlement.cf_memory] +} + +locals { + cf_org_id = jsondecode(btp_subaccount_environment_instance.cloudfoundry.labels)["Org ID"] +} + +resource "cloudfoundry_org_role" "org_manager" { + for_each = toset(var.emergency_admins) + username = each.value + type = "organization_user" + org = local.cf_org_id +} + +resource "cloudfoundry_space" "project_space" { + name = local.cf_space_name + org = local.cf_org_id +} + +resource "cloudfoundry_space_role" "emergency_space_manager" { + for_each = toset(var.emergency_admins) + username = each.value + type = "space_manager" + space = cloudfoundry_space.project_space.id + origin = "sap.ids" + depends_on = [cloudfoundry_org_role.org_manager] +} + +resource "cloudfoundry_space_role" "space_manager" { + for_each = toset(var.space_managers) + username = each.value + type = "space_manager" + space = cloudfoundry_space.project_space.id + origin = "sap.ids" + depends_on = [cloudfoundry_org_role.org_manager] +} + +resource "cloudfoundry_space_role" "space_developer" { + for_each = toset(var.space_managers) + username = each.value + type = "space_developer" + space = cloudfoundry_space.project_space.id + origin = "sap.ids" + depends_on = [cloudfoundry_org_role.org_manager] +} diff --git a/released/usecases/trial_integration_suite/02_base_setup_subaccount/outputs.tf b/released/usecases/trial_integration_suite/02_base_setup_subaccount/outputs.tf new file mode 100644 index 00000000..b67b3985 --- /dev/null +++ b/released/usecases/trial_integration_suite/02_base_setup_subaccount/outputs.tf @@ -0,0 +1,9 @@ +output "subaccount_id" { + description = "The ID of the subaccount" + value = btp_subaccount.project_subaccount.id +} + +output "cf_space_id" { + description = "The ID of the Cloud Foundry space" + value = cloudfoundry_space.project_space.id +} diff --git a/released/usecases/trial_integration_suite/02_base_setup_subaccount/provider.tf b/released/usecases/trial_integration_suite/02_base_setup_subaccount/provider.tf new file mode 100644 index 00000000..9e690704 --- /dev/null +++ b/released/usecases/trial_integration_suite/02_base_setup_subaccount/provider.tf @@ -0,0 +1,21 @@ +terraform { + required_providers { + btp = { + source = "SAP/btp" + version = "~> 1.10.0" + } + cloudfoundry = { + source = "cloudfoundry/cloudfoundry" + version = "~> 1.3.0" + } + } +} + +provider "btp" { + globalaccount = var.globalaccount +} + +// Interpolation of the API endpoint only works on trial accounts +provider "cloudfoundry" { + api_url = "https://api.cf.${var.cf_landscape_label}.hana.ondemand.com" +} diff --git a/released/usecases/trial_integration_suite/02_base_setup_subaccount/terraform.tfvars.sample b/released/usecases/trial_integration_suite/02_base_setup_subaccount/terraform.tfvars.sample new file mode 100644 index 00000000..161d5c49 --- /dev/null +++ b/released/usecases/trial_integration_suite/02_base_setup_subaccount/terraform.tfvars.sample @@ -0,0 +1,6 @@ +globalaccount = "" +parent_id = "outout directory_id of step 1" +project_costcenter = "54321" +emergency_admins = ["john.doe@sap.com"] +space_managers = ["john.doe@sap.com"] +space_developers = ["john.doe@sap.com"] diff --git a/released/usecases/trial_integration_suite/02_base_setup_subaccount/variables.tf b/released/usecases/trial_integration_suite/02_base_setup_subaccount/variables.tf new file mode 100644 index 00000000..3cf31af8 --- /dev/null +++ b/released/usecases/trial_integration_suite/02_base_setup_subaccount/variables.tf @@ -0,0 +1,73 @@ +variable "globalaccount" { + description = "Subdomain of the global account" + type = string +} + +variable "parent_id" { + description = "The parent ID for the subaccount" + type = string + default = "" +} + +variable "project_name" { + description = "Name of the project" + type = string + default = "Integration Account" +} + +variable "subaccount_stage" { + description = "Stage of the subaccount" + type = string + default = "DEV" + validation { + condition = contains(["DEV", "TEST", "PROD"], var.subaccount_stage) + error_message = "Stage must be one of DEV, TEST or PROD" + } +} + +variable "subaccount_region" { + description = "Region of the subaccount" + type = string + default = "us10" + validation { + condition = contains(["us10", "ap21"], var.subaccount_region) + error_message = "Region must be one of us10 or ap21" + } +} + +variable "cf_landscape_label" { + description = "Label of the Cloud Foundry landscape" + type = string + default = "us10-001" + validation { + condition = contains(["us10-001", "ap21"], var.cf_landscape_label) + error_message = "Trial landscape must be one of us10-001 or ap21" + } +} + +variable "project_costcenter" { + description = "Cost center of the project" + type = string + validation { + condition = can(regex("^[0-9]{5}$", var.project_costcenter)) + error_message = "Cost center must be a 5 digit number" + } +} + +variable "emergency_admins" { + description = "List of emergency admins" + type = list(string) + default = [] +} + +variable "space_managers" { + description = "List of space managers" + type = list(string) + default = [] +} + +variable "space_developers" { + description = "List of space developers" + type = list(string) + default = [] +} diff --git a/released/usecases/trial_integration_suite/03_base_setup_integration_app/main.tf b/released/usecases/trial_integration_suite/03_base_setup_integration_app/main.tf new file mode 100644 index 00000000..253968da --- /dev/null +++ b/released/usecases/trial_integration_suite/03_base_setup_integration_app/main.tf @@ -0,0 +1,29 @@ +// The app name must be determined dynamically as for integration suite this is not the service name of the entitlement +data "btp_subaccount_subscriptions" "all" { + subaccount_id = var.subaccount_id +} + +locals { + integrationsuite_trial_subscription = try( + { + for subscription in data.btp_subaccount_subscriptions.all.values : subscription.commercial_app_name => subscription + if subscription.commercial_app_name == "integrationsuite-trial" + }, + {} + ) +} + +resource "btp_subaccount_subscription" "integrationsuite_app_trial" { + for_each = local.integrationsuite_trial_subscription + subaccount_id = var.subaccount_id + app_name = each.value.app_name + plan_name = "trial" +} + +resource "btp_subaccount_role_collection_assignment" "integration_provisioners" { + for_each = toset(var.integration_provisioners) + subaccount_id = var.subaccount_id + role_collection_name = "Integration_Provisioner" + user_name = each.value + depends_on = [btp_subaccount_subscription.integrationsuite_app_trial] +} diff --git a/released/usecases/trial_integration_suite/03_base_setup_integration_app/provider.tf b/released/usecases/trial_integration_suite/03_base_setup_integration_app/provider.tf new file mode 100644 index 00000000..b49f07d6 --- /dev/null +++ b/released/usecases/trial_integration_suite/03_base_setup_integration_app/provider.tf @@ -0,0 +1,13 @@ +terraform { + required_providers { + btp = { + source = "SAP/btp" + version = "~> 1.10.0" + } + } + +} + +provider "btp" { + globalaccount = var.globalaccount +} diff --git a/released/usecases/trial_integration_suite/03_base_setup_integration_app/terraform.tfvars.sample b/released/usecases/trial_integration_suite/03_base_setup_integration_app/terraform.tfvars.sample new file mode 100644 index 00000000..f9cd3720 --- /dev/null +++ b/released/usecases/trial_integration_suite/03_base_setup_integration_app/terraform.tfvars.sample @@ -0,0 +1,3 @@ +globalaccount = "" +subaccount_id = "Output subaccount_id of step 2" +integration_provisioners = ["john.doe@sap.com"] diff --git a/released/usecases/trial_integration_suite/03_base_setup_integration_app/variables.tf b/released/usecases/trial_integration_suite/03_base_setup_integration_app/variables.tf new file mode 100644 index 00000000..962a2b17 --- /dev/null +++ b/released/usecases/trial_integration_suite/03_base_setup_integration_app/variables.tf @@ -0,0 +1,15 @@ +variable "globalaccount" { + description = "Subdomain of the global account" + type = string +} + +variable "subaccount_id" { + description = "The ID of the subaccount" + type = string +} + +variable "integration_provisioners" { + description = "List of user that should be assigned as integration provisioners" + type = list(string) + default = [] +} diff --git a/released/usecases/trial_integration_suite/04_setup_oauth_clients/main.tf b/released/usecases/trial_integration_suite/04_setup_oauth_clients/main.tf new file mode 100644 index 00000000..aee249a5 --- /dev/null +++ b/released/usecases/trial_integration_suite/04_setup_oauth_clients/main.tf @@ -0,0 +1,156 @@ +/* +After enablement of the capabilites, we have +- additional entitlements to assigne to the directory and the subaccount +- additional role collections to assign to the users +- Creation of service instances for OAuth Clients +- Creation of service bindings for the OAuth Clients +*/ + +locals { + it_if_plan = "integration-flow" + it_api_plan = "api" + it_service = "it-rt" + it_if_binding_name = "integration-flow-oauthclient-binding" + it_api_binding_name = "api-oauthclient-binding" +} + +resource "btp_subaccount_role_collection_assignment" "pi_admins" { + for_each = toset(var.pi_admins) + subaccount_id = var.subaccount_id + role_collection_name = "PI_Administrator" + user_name = each.value +} + +resource "btp_subaccount_role_collection_assignment" "pi_business_experts" { + for_each = toset(var.pi_business_experts) + subaccount_id = var.subaccount_id + role_collection_name = "PI_Business_Expert" + user_name = each.value +} + +resource "btp_subaccount_role_collection_assignment" "pi_int_dev" { + for_each = toset(var.pi_integration_developers) + subaccount_id = var.subaccount_id + role_collection_name = "PI_Integration_Developer" + user_name = each.value +} + +resource "btp_subaccount_role_collection_assignment" "pi_readonly" { + for_each = toset(var.pi_readonly) + subaccount_id = var.subaccount_id + role_collection_name = "PI_Read_Only" + user_name = each.value +} + +resource "btp_directory_entitlement" "dir_pi_if_entitlement" { + directory_id = var.directory_id + service_name = local.it_service + plan_name = local.it_if_plan + distribute = false + auto_assign = false +} + +resource "btp_directory_entitlement" "dir_pi_api_entitlement" { + directory_id = var.directory_id + service_name = local.it_service + plan_name = local.it_api_plan + distribute = false + auto_assign = false +} + +resource "btp_subaccount_entitlement" "subaccount_pi_if_entitlement" { + subaccount_id = var.subaccount_id + service_name = local.it_service + plan_name = local.it_if_plan + depends_on = [btp_directory_entitlement.dir_pi_if_entitlement] +} + +resource "btp_subaccount_entitlement" "subaccount_pi_api_entitlement" { + subaccount_id = var.subaccount_id + service_name = local.it_service + plan_name = local.it_api_plan + depends_on = [btp_directory_entitlement.dir_pi_api_entitlement] +} + +//We need to wait until the entitlements are propagated to the CF marketplace to create service instances +resource "time_sleep" "wait_30_seconds" { + depends_on = [btp_subaccount_entitlement.subaccount_pi_api_entitlement, btp_subaccount_entitlement.subaccount_pi_if_entitlement] + + create_duration = "30s" +} + +data "cloudfoundry_service_plans" "integration_flow" { + name = local.it_if_plan + service_offering_name = local.it_service + depends_on = [time_sleep.wait_30_seconds] +} + +resource "cloudfoundry_service_instance" "integration_flow" { + name = "integration-flow-oauthclient" + type = "managed" + space = var.space_id + service_plan = data.cloudfoundry_service_plans.integration_flow.service_plans[0].id + parameters = < [!IMPORTANT] +> If you want to doa deployment on a productive account, you must exchange all trial specific settings and variable validations. + + +## Steps before the activation of the capabilities + +### Setup Directory + +In `01_base_setup_directory` we provide the basic setup for a directory that contains the integration-specific subaccounts. The directory can be managed or unmanaged. + +> [!NOTE] +> This step can be omitted if you do not want to have a directory that contains the subaccounts for the integration suite. + +### Setup Subaccount + +In `02_base_setup_subaccount` we do a basic subaccount setup for the integration suite in a specific stage like DEV or PROD. This comprises: + +- Creation of the subaccount +- Assigning of the entitlement for the integration Suite application +- Creation of Cloud Foundry environment +- Creation of a Cloud Foundry space +- Several role collection assignments on subaccount, Cloud Foundry organization and Cloud Foundry space level + +In this step the app gets published on the marketplace. In this case the application name is **not** the same as the service offering name that was entitled. Consequently, it must be defined dynamically. As Terraform cannot calculate a plan for this it cannot be subscribed to in the same step. + +> [!NOTE] +> If you omitted the creation of the directory in the previous step, you must leave the `parent_id` empty in the `variables.tf` file. + +### Subscription to Integration Suite Application + +in `03_base_setup_integration_app` we determine the application name dynamically and subscribe to the application. This subscription triggers the assignment of the role collection `Integration_Provisioner` to the subaccount. Consequently, this step also comprises the assignment of the role collection `Integration_Provisioner` to the users. + +## Manual activation of capabilities + +The activation of capabilities must be done via the subscribed application. After executing the activation several further role collections as well as service plans get created on global account as well as on subaccount level. + +## Steps after the activation of the capabilities + +### Setup of OAuth Clients + +After the manual activation of the capabilities, we want to create the OAuth clients with the service bindings. To achieve this several steps must be executed via Terraform as defined in `04_setup_oauth_clients`: + +- Assignment of relevant service plans to the managed directory and to the subaccount +- Assignment of Integration Suite specific role collections to the users in the subaccount +- Creation of service instances for the OAuth clients in the Cloud Foundry space +- Creation of the service bindings for the OAuth clients in the Cloud Foundry space + +Due to a setting of the services in the Cloud Foundry space, the service binding data cannot be fetched from the platform. To get the relevant data the user must navigate to the cockpit namely the Cloud Foundry space and fetch the data from the service bindings in the UI. + +## Variables + +Each step requires some variables to be set as defined in the `variables.tf` file. To give you an idea about the content of the variables we provide a `terraform.tfvars.sample` file in each step.