diff --git a/released/discovery_center/mission_3808/README.md b/released/discovery_center/mission_3808/README.md new file mode 100644 index 00000000..8082644d --- /dev/null +++ b/released/discovery_center/mission_3808/README.md @@ -0,0 +1,54 @@ +# SAP Discovery Center mission - Onboarding & First Steps to SAP Cloud ALM + +## Overview + +This terraform script shows how to set up the SAP Discovery Center Mission - [Onboarding & First Steps to SAP Cloud ALM](https://discovery-center.cloud.sap/protected/index.html#/missiondetail/3808/3857) + +## Content of setup + +The setup comprises the following resources: + +- Creation of the SAP BTP subaccount +- Entitlements of services +- Subscriptions to applications +- Role collection assignments to users + +## Deploying the resources + +Make sure that you are familiar with SAP BTP and know both the [Get Started with btp-terraform-samples](https://github.com/SAP-samples/btp-terraform-samples/blob/main/GET_STARTED.md) and the [Get Started with the Terraform Provider for BTP](https://developers.sap.com/tutorials/btp-terraform-get-started.html) + +To deploy the resources you must: + +1. Set your credentials as environment variables + + ```bash + export BTP_USERNAME ='' + export BTP_PASSWORD ='' + export CF_USER ='' + export BTP_PASSWORD ='' + ``` + +2. Change the variables in the `sample.tfvars` file to meet your requirements + + > The minimal set of parameters you should specify (besides user_email and password) is global account (i.e. its subdomain) and the used custom_idp and all user assignments + + > ⚠ NOTE: You should pay attention **specifically** to the users defined in the samples.tfvars whether they already exist in your SAP BTP accounts. Otherwise, you might get error messages like, e.g., `Error: The user could not be found: jane.doe@test.com`. + + +3. Initialize your workspace: + + ```bash + terraform init + ``` + +4. You can check what Terraform plans to apply based on your configuration: + + ```bash + terraform plan -var-file="sample.tfvars" + ``` + +5. Apply your configuration to provision the resources: + + ```bash + terraform apply -var-file="sample.tfvars" + ``` diff --git a/released/discovery_center/mission_3808/step1/main.tf b/released/discovery_center/mission_3808/step1/main.tf new file mode 100644 index 00000000..aa98730c --- /dev/null +++ b/released/discovery_center/mission_3808/step1/main.tf @@ -0,0 +1,154 @@ +# ------------------------------------------------------------------------------------------------------ +# Subaccount setup for DC mission 4104 +# ------------------------------------------------------------------------------------------------------ +# Setup subaccount domain (to ensure uniqueness in BTP global account) +resource "random_uuid" "subaccount_domain_suffix" {} + +locals { + random_uuid = random_uuid.subaccount_domain_suffix.result + subaccount_domain = lower(replace("mission-3808-${local.random_uuid}", "_", "-")) + subaccount_cf_org = substr(replace("${local.subaccount_domain}", "-", ""), 0, 32) +} + +# ------------------------------------------------------------------------------------------------------ +# Creation of subaccount +# ------------------------------------------------------------------------------------------------------ +resource "btp_subaccount" "dc_mission" { + name = var.subaccount_name + subdomain = local.subaccount_domain + region = lower(var.region) +} + +# ------------------------------------------------------------------------------------------------------ +# Assign custom IDP to sub account (if custom_idp is set) +# ------------------------------------------------------------------------------------------------------ +resource "btp_subaccount_trust_configuration" "fully_customized" { + # Only create trust configuration if custom_idp has been set + count = var.custom_idp == "" ? 0 : 1 + subaccount_id = btp_subaccount.dc_mission.id + identity_provider = var.custom_idp +} + + +# ------------------------------------------------------------------------------------------------------ +# CLOUDFOUNDRY PREPARATION +# ------------------------------------------------------------------------------------------------------ +# +# Fetch all available environments for the subaccount +data "btp_subaccount_environments" "all" { + subaccount_id = btp_subaccount.dc_mission.id +} +# ------------------------------------------------------------------------------------------------------ +# Take the landscape label from the first CF environment if no environment label is provided +# (this replaces the previous null_resource) +# ------------------------------------------------------------------------------------------------------ +resource "terraform_data" "replacement" { + input = length(var.cf_landscape_label) > 0 ? var.cf_landscape_label : [for env in data.btp_subaccount_environments.all.values : env if env.service_name == "cloudfoundry" && env.environment_type == "cloudfoundry"][0].landscape_label +} +# ------------------------------------------------------------------------------------------------------ +# Create the Cloud Foundry environment instance +# ------------------------------------------------------------------------------------------------------ +resource "btp_subaccount_environment_instance" "cloudfoundry" { + subaccount_id = btp_subaccount.dc_mission.id + name = local.subaccount_cf_org + environment_type = "cloudfoundry" + service_name = "cloudfoundry" + plan_name = "standard" + landscape_label = terraform_data.replacement.output + + parameters = jsonencode({ + instance_name = local.subaccount_cf_org + }) +} + +# ------------------------------------------------------------------------------------------------------ +# SERVICES +# ------------------------------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------------------------------ +# Setup SAP Cloud Transport Management +# ------------------------------------------------------------------------------------------------------ +# Entitle +resource "btp_subaccount_entitlement" "alm" { + subaccount_id = btp_subaccount.dc_mission.id + service_name = "alm-ts" + plan_name = "standard" +} +# Add subscription +resource "btp_subaccount_subscription" "alm" { + subaccount_id = btp_subaccount.dc_mission.id + app_name = "alm-ts" + plan_name = "standard" + depends_on = [btp_subaccount_entitlement.alm] +} + +# ------------------------------------------------------------------------------------------------------ +# Setup SAP Cloud Integration Automation +# ------------------------------------------------------------------------------------------------------ +# Entitle +resource "btp_subaccount_entitlement" "cias" { + subaccount_id = btp_subaccount.dc_mission.id + service_name = "cias" + plan_name = "standard" +} +# Add subscription +resource "btp_subaccount_subscription" "cias" { + subaccount_id = btp_subaccount.dc_mission.id + app_name = "cias" + plan_name = "standard" + depends_on = [btp_subaccount_entitlement.cias] +} + +# ------------------------------------------------------------------------------------------------------ +# Setup SAP Cloud ALM API +# ------------------------------------------------------------------------------------------------------ +# Entitle +resource "btp_subaccount_entitlement" "sapcloudalmapis" { + subaccount_id = btp_subaccount.dc_mission.id + service_name = "SAPCloudALMAPIs" + plan_name = "standard" + amount = 1 +} + +# ------------------------------------------------------------------------------------------------------ +# USERS AND ROLES +# ------------------------------------------------------------------------------------------------------ +# +# ------------------------------------------------------------------------------------------------------ +# Assign role collection "Subaccount Administrator" +# ------------------------------------------------------------------------------------------------------ +resource "btp_subaccount_role_collection_assignment" "subaccount_admin" { + for_each = toset("${var.subaccount_admins}") + subaccount_id = btp_subaccount.dc_mission.id + role_collection_name = "Subaccount Administrator" + user_name = each.value + depends_on = [btp_subaccount.dc_mission] +} + + +# ------------------------------------------------------------------------------------------------------ +# Create tfvars file for step 2 (if variable `create_tfvars_file_for_step2` is set to true) +# ------------------------------------------------------------------------------------------------------ +resource "local_file" "output_vars_step1" { + count = var.create_tfvars_file_for_step2 ? 1 : 0 + content = <<-EOT + globalaccount = "${var.globalaccount}" + cli_server_url = ${jsonencode(var.cli_server_url)} + + subaccount_id = "${btp_subaccount.dc_mission.id}" + + cf_api_url = "${jsondecode(btp_subaccount_environment_instance.cloudfoundry.labels)["API Endpoint"]}" + + cf_org_id = "${jsondecode(btp_subaccount_environment_instance.cloudfoundry.labels)["Org ID"]}" + cf_org_name = "${jsondecode(btp_subaccount_environment_instance.cloudfoundry.labels)["Org Name"]}" + + origin_key = "${var.origin_key}" + + cf_space_name = "${var.cf_space_name}" + + cf_org_admins = ${jsonencode(var.cf_org_admins)} + cf_space_developers = ${jsonencode(var.cf_space_developers)} + cf_space_managers = ${jsonencode(var.cf_space_managers)} + + EOT + filename = "../step2/terraform.tfvars" +} diff --git a/released/discovery_center/mission_3808/step1/provider.tf b/released/discovery_center/mission_3808/step1/provider.tf new file mode 100644 index 00000000..7ceab97d --- /dev/null +++ b/released/discovery_center/mission_3808/step1/provider.tf @@ -0,0 +1,13 @@ +terraform { + required_providers { + btp = { + source = "SAP/btp" + version = "~> 1.4.0" + } + } +} + +provider "btp" { + globalaccount = var.globalaccount + cli_server_url = var.cli_server_url +} diff --git a/released/discovery_center/mission_3808/step1/sample.tfvars b/released/discovery_center/mission_3808/step1/sample.tfvars new file mode 100644 index 00000000..171f875d --- /dev/null +++ b/released/discovery_center/mission_3808/step1/sample.tfvars @@ -0,0 +1,28 @@ +# ------------------------------------------------------------------------------------------------------ +# Provider configuration +# ------------------------------------------------------------------------------------------------------ +# Your global account subdomain +globalaccount = "xxxxxxxx-xxxxxxx-xxxxxxx-xxxxxxxx-xxxxxx" + +# The CLI server URL (needs to be set to null if you are using the default CLI server) +cli_server_url = null + +# Region for your subaccount +region = "us20" + +# Name of your sub account +subaccount_name = "SAP Discovery Center Mission 3808" + +# custom_idp = "sap.custom" + +# ------------------------------------------------------------------------------------------------------ +# USER ROLES +# ------------------------------------------------------------------------------------------------------ +subaccount_admins = ["another.user@test.com"] +subaccount_service_admins = ["another.user@test.com"] + +cf_org_admins = ["another.user@test.com"] +cf_space_managers = ["another.user@test.com", "you@test.com"] +cf_space_developers = ["another.user@test.com", "you@test.com"] + +launchpad_admins = ["another.user@test.com", "you@test.com"] \ No newline at end of file diff --git a/released/discovery_center/mission_3808/step1/variables.tf b/released/discovery_center/mission_3808/step1/variables.tf new file mode 100644 index 00000000..56e552b7 --- /dev/null +++ b/released/discovery_center/mission_3808/step1/variables.tf @@ -0,0 +1,116 @@ +variable "globalaccount" { + type = string + description = "The globalaccount subdomain where the sub account shall be created." +} + +variable "subaccount_name" { + type = string + description = "The subaccount name." + default = "My SAP DC mission subaccount." +} + +variable "cli_server_url" { + type = string + description = "The BTP CLI server URL." + default = "https://cli.btp.cloud.sap" +} + +variable "region" { + type = string + description = "The region where the subaccount shall be created in." + default = "us20" +} + +variable "subaccount_admins" { + type = list(string) + description = "Defines the colleagues who are added to each subaccount as emergency administrators." + + # add validation to check if admins contains a list of valid email addresses + validation { + condition = length([for email in var.subaccount_admins : can(regex("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", email))]) == length(var.subaccount_admins) + error_message = "Please enter a valid email address for the subaccount admins." + } +} + + +variable "custom_idp" { + type = string + description = "Defines the custom IdP" + default = "" +} + +variable "origin_key" { + type = string + description = "Defines the origin key of the identity provider" + default = "sap.ids" + # The value for the origin_key can be defined + # but are normally set to "sap.ids", "sap.default" or "sap.custom" +} + +variable "cf_landscape_label" { + type = string + description = "In case there are multiple environments available for a subaccount, you can use this label to choose with which one you want to go. If nothing is given, we take by default the first available." + default = "" +} + +variable "cf_org_name" { + type = string + description = "Name of the Cloud Foundry org." + default = "mission-3808" + + validation { + condition = can(regex("^.{1,255}$", var.cf_org_name)) + error_message = "The Cloud Foundry org name must not be emtpy and not exceed 255 characters." + } +} + +variable "cf_org_admins" { + type = list(string) + description = "List of users to set as Cloudfoundry org administrators." + + # add validation to check if admins contains a list of valid email addresses + validation { + condition = length([for email in var.cf_org_admins : can(regex("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", email))]) == length(var.cf_org_admins) + error_message = "Please enter a valid email address for the CF Org admins." + } +} + +variable "cf_space_name" { + type = string + description = "Name of the Cloud Foundry space." + default = "dev" + + validation { + condition = can(regex("^.{1,255}$", var.cf_space_name)) + error_message = "The Cloud Foundry space name must not be emtpy and not exceed 255 characters." + } + +} + +variable "cf_space_managers" { + type = list(string) + description = "Defines the colleagues who are added to a CF space as space manager." + + # add validation to check if admins contains a list of valid email addresses + validation { + condition = length([for email in var.cf_space_managers : can(regex("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", email))]) == length(var.cf_space_managers) + error_message = "Please enter a valid email address for the CF space managers." + } +} + +variable "cf_space_developers" { + type = list(string) + description = "Defines the colleagues who are added to a CF space as space developer." + + # add validation to check if admins contains a list of valid email addresses + validation { + condition = length([for email in var.cf_space_developers : can(regex("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", email))]) == length(var.cf_space_developers) + error_message = "Please enter a valid email address for the CF space developers." + } +} + +variable "create_tfvars_file_for_step2" { + type = bool + description = "Switch to enable the creation of the tfvars file for step 2." + default = false +} \ No newline at end of file diff --git a/released/discovery_center/mission_3808/step2/main.tf b/released/discovery_center/mission_3808/step2/main.tf new file mode 100644 index 00000000..a1c88080 --- /dev/null +++ b/released/discovery_center/mission_3808/step2/main.tf @@ -0,0 +1,69 @@ +# ------------------------------------------------------------------------------------------------------ +# Create the Cloud Foundry space +# ------------------------------------------------------------------------------------------------------ +resource "cloudfoundry_space" "space" { + name = var.cf_space_name + org = var.cf_org_id +} + +# ------------------------------------------------------------------------------------------------------ +# USERS AND ROLES +# ------------------------------------------------------------------------------------------------------ +# +# ------------------------------------------------------------------------------------------------------ +# Assign CF Org roles to the admin users +# ------------------------------------------------------------------------------------------------------ +# Define Org User role +resource "cloudfoundry_org_role" "organization_user" { + for_each = toset("${var.cf_org_admins}") + username = each.value + type = "organization_user" + org = var.cf_org_id + origin = var.origin_key +} +# Define Org Manager role +resource "cloudfoundry_org_role" "organization_manager" { + for_each = toset("${var.cf_org_admins}") + username = each.value + type = "organization_manager" + org = var.cf_org_id + origin = var.origin_key + depends_on = [cloudfoundry_org_role.organization_user] +} + +# ------------------------------------------------------------------------------------------------------ +# Assign CF space roles to the users +# ------------------------------------------------------------------------------------------------------ +# Define Space Manager role +resource "cloudfoundry_space_role" "space_managers" { + for_each = toset(var.cf_space_managers) + username = each.value + type = "space_manager" + space = cloudfoundry_space.space.id + origin = var.origin_key + depends_on = [cloudfoundry_org_role.organization_manager] +} +# Define Space Developer role +resource "cloudfoundry_space_role" "space_developers" { + for_each = toset(var.cf_space_developers) + username = each.value + type = "space_developer" + space = cloudfoundry_space.space.id + origin = var.origin_key + depends_on = [cloudfoundry_org_role.organization_manager] +} + +# ------------------------------------------------------------------------------------------------------ +# Create service instance for taskcenter (one-inbox-service) +# ------------------------------------------------------------------------------------------------------ +data "cloudfoundry_service" "sapcloudalmapis" { + name = "SAPCloudALMAPIs" +} + +resource "cloudfoundry_service_instance" "sapcloudalmapis" { + name = "SAPCloudALMAPIs" + type = "managed" + space = cloudfoundry_space.space.id + service_plan = data.cloudfoundry_service.sapcloudalmapis.service_plans["standard"] + depends_on = [cloudfoundry_space_role.space_managers, cloudfoundry_space_role.space_developers] +} diff --git a/released/discovery_center/mission_3808/step2/provider.tf b/released/discovery_center/mission_3808/step2/provider.tf new file mode 100644 index 00000000..d97e6e97 --- /dev/null +++ b/released/discovery_center/mission_3808/step2/provider.tf @@ -0,0 +1,21 @@ +### +# Define the required providers for this module +### +terraform { + required_providers { + btp = { + source = "sap/btp" + } + cloudfoundry = { + source = "SAP/cloudfoundry" + version = "0.2.1-beta" + } + } +} +provider "btp" { + globalaccount = var.globalaccount + cli_server_url = var.cli_server_url +} +provider "cloudfoundry" { + api_url = var.cf_api_url +} diff --git a/released/discovery_center/mission_3808/step2/sample.tfvars b/released/discovery_center/mission_3808/step2/sample.tfvars new file mode 100644 index 00000000..df90e30a --- /dev/null +++ b/released/discovery_center/mission_3808/step2/sample.tfvars @@ -0,0 +1,16 @@ +# ------------------------------------------------------------------------------------------------------ +# Provider configuration (this file will be either created automatically in step 1 or manually in step 2) +# ------------------------------------------------------------------------------------------------------ +globalaccount = "yourglobalaccount" +cli_server_url = "https://cli.btp.cloud.sap" +subaccount_id = "yoursubacountid" +origin_key = "sap.ids" + +cf_api_url = "https://api.cf.us10.hana.ondemand.com" +cf_org_id = "your_cf_org_id" +cf_org_name = "your_cf_org_name" +cf_space_name = "dev" + +cf_org_admins = ["another.user@test.com"] +cf_space_developers = ["another.user@test.com", "you@test.com"] +cf_space_managers = ["another.user@test.com", "you@test.com"] diff --git a/released/discovery_center/mission_3808/step2/variables.tf b/released/discovery_center/mission_3808/step2/variables.tf new file mode 100644 index 00000000..30ec61b0 --- /dev/null +++ b/released/discovery_center/mission_3808/step2/variables.tf @@ -0,0 +1,95 @@ +###################################################################### +# Customer account setup +###################################################################### +variable "globalaccount" { + type = string + description = "Defines the global account" + default = "yourglobalaccount" +} + +variable "cli_server_url" { + type = string + description = "Defines the CLI server URL" + default = "https://cli.btp.cloud.sap" +} + +variable "cf_api_url" { + type = string + description = "The Cloud Foundry API endpoint from the Cloud Foundry environment instance." +} + +variable "cf_space_name" { + type = string + description = "Name of the Cloud Foundry space." + default = "dev" + + validation { + condition = can(regex("^.{1,255}$", var.cf_space_name)) + error_message = "The Cloud Foundry space name must not be emtpy and not exceed 255 characters." + } +} + +variable "cf_org_name" { + type = string + description = "Name of the Cloud Foundry org." + default = "mission-3808" + + validation { + condition = can(regex("^.{1,255}$", var.cf_org_name)) + error_message = "The Cloud Foundry org name must not be emtpy and not exceed 255 characters." + } +} + +# subaccount +variable "subaccount_id" { + type = string + description = "The subaccount ID." + default = "" +} + + +variable "origin_key" { + type = string + description = "Defines the origin key of the identity provider" + default = "sap.ids" + # The value for the origin_key can be defined + # but are normally set to "sap.ids", "sap.default" or "sap.custom" +} + +variable "cf_org_id" { + type = string + description = "The Cloud Foundry Org ID from the Cloud Foundry environment instance." +} + +variable "cf_org_admins" { + type = list(string) + description = "List of users to set as Cloudfoundry org administrators." + + # add validation to check if admins contains a list of valid email addresses + validation { + condition = length([for email in var.cf_org_admins : can(regex("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", email))]) == length(var.cf_org_admins) + error_message = "Please enter a valid email address for the CF Org admins." + } +} + +variable "cf_space_managers" { + type = list(string) + description = "Defines the colleagues who are added to a CF space as space manager." + + # add validation to check if admins contains a list of valid email addresses + validation { + condition = length([for email in var.cf_space_managers : can(regex("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", email))]) == length(var.cf_space_managers) + error_message = "Please enter a valid email address for the CF space managers." + } +} + +variable "cf_space_developers" { + type = list(string) + description = "Defines the colleagues who are added to a CF space as space developer." + + # add validation to check if admins contains a list of valid email addresses + validation { + condition = length([for email in var.cf_space_developers : can(regex("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", email))]) == length(var.cf_space_developers) + error_message = "Please enter a valid email address for the CF space developers." + } +}