All Notes

BTP Setup Automation mit Terraform

BTPTerraformIaCAutomationDevOps

BTP Setup Automation mit Terraform

Die manuelle Konfiguration von SAP BTP-Accounts ist zeitaufwendig, fehleranfällig und nicht reproduzierbar. Infrastructure as Code (IaC) löst diese Probleme durch deklarative Konfiguration.

Das Problem

Typische manuelle BTP-Setups erfordern:

  • 📋 Subaccounts erstellen
  • 🔐 Entitlements zuweisen
  • 👥 Rollen-Collections konfigurieren
  • 🔗 Service Instances & Bindings anlegen
  • 🌐 Destinations definieren
  • ✅ Subscriptions aktivieren

Dies manuell zu tun bedeutet:

  • ⏱️ Stunden bis Tage Aufwand
  • 🐛 Inkonsistenzen zwischen Environments
  • 📝 Keine Nachvollziehbarkeit (Audit Trail)
  • 🔄 Schwierige Reproduzierbarkeit

Die Lösung: BTP Terraform Provider

SAP bietet einen offiziellen Terraform Provider für BTP, der die gesamte Platform-Konfiguration als Code ermöglicht.

Installation

# versions.tf
terraform {
  required_providers {
    btp = {
      source  = "SAP/btp"
      version = "~> 1.0"
    }
  }
}
 
provider "btp" {
  globalaccount = var.globalaccount
  username      = var.username
  password      = var.password
}

Grundlegendes Setup

Subaccount erstellen

# subaccount.tf
resource "btp_subaccount" "dev" {
  name        = "Development Environment"
  region      = "eu10"
  subdomain   = "my-dev-subaccount"
  description = "Development environment for CAP applications"
 
  labels = {
    environment = ["dev"]
    team        = ["backend"]
  }
}

Entitlements zuweisen

# entitlements.tf
resource "btp_subaccount_entitlement" "cap_tools" {
  subaccount_id = btp_subaccount.dev.id
  service_name  = "sapappstudio"
  plan_name     = "standard-edition"
  amount        = 1
}
 
resource "btp_subaccount_entitlement" "hana_cloud" {
  subaccount_id = btp_subaccount.dev.id
  service_name  = "hana-cloud"
  plan_name     = "hana"
  amount        = 1
}
 
resource "btp_subaccount_entitlement" "xsuaa" {
  subaccount_id = btp_subaccount.dev.id
  service_name  = "xsuaa"
  plan_name     = "application"
}

Service Instances

# services.tf
resource "btp_subaccount_service_instance" "xsuaa_instance" {
  subaccount_id  = btp_subaccount.dev.id
  name           = "my-xsuaa"
  service_plan   = "application"
  service_name   = "xsuaa"
 
  parameters = jsonencode({
    xsappname = "my-cap-app"
    tenant-mode = "dedicated"
    scopes = [
      {
        name = "$XSAPPNAME.Admin"
        description = "Admin scope"
      }
    ]
    role-templates = [
      {
        name = "Admin"
        description = "Administrator"
        scope-references = ["$XSAPPNAME.Admin"]
      }
    ]
  })
}
 
resource "btp_subaccount_service_binding" "xsuaa_binding" {
  subaccount_id = btp_subaccount.dev.id
  service_instance_id = btp_subaccount_service_instance.xsuaa_instance.id
  name = "xsuaa-binding"
}

Fortgeschrittene Patterns

Environment-spezifische Konfiguration

# environments/dev/terraform.tfvars
globalaccount = "my-global-account-id"
region        = "eu10"
environment   = "dev"
cap_users     = ["developer1@company.com", "developer2@company.com"]
 
# environments/prod/terraform.tfvars
globalaccount = "my-global-account-id"
region        = "eu10"
environment   = "prod"
cap_users     = ["admin@company.com"]

Modulare Struktur

# modules/cap-setup/main.tf
variable "subaccount_id" {
  type = string
}
 
variable "app_name" {
  type = string
}
 
resource "btp_subaccount_service_instance" "cap_services" {
  for_each = toset(["xsuaa", "destination", "connectivity"])
 
  subaccount_id = var.subaccount_id
  name          = "${var.app_name}-${each.key}"
  service_name  = each.key
  service_plan  = each.key == "xsuaa" ? "application" : "lite"
}
 
output "service_keys" {
  value = {
    for k, v in btp_subaccount_service_instance.cap_services :
    k => v.id
  }
}

Verwendung:

# main.tf
module "my_cap_app" {
  source = "./modules/cap-setup"
 
  subaccount_id = btp_subaccount.dev.id
  app_name      = "timetracking"
}

Destinations mit Terraform

resource "btp_subaccount_destination" "s4hana_backend" {
  subaccount_id = btp_subaccount.dev.id
  name          = "S4HANA_BACKEND"
 
  type          = "HTTP"
  url           = "https://my-s4hana.s4hana.ondemand.com"
  authentication = "OAuth2SAMLBearerAssertion"
 
  properties = {
    "audience"        = "https://my-s4hana.s4hana.ondemand.com"
    "authnContextClassRef" = "urn:oasis:names:tc:SAML:2.0:ac:classes:PreviousSession"
    "clientKey"       = var.s4hana_client_id
    "tokenServiceURL" = "https://my-s4hana.authentication.eu10.hana.ondemand.com/oauth/token"
    "ProxyType"       = "OnPremise"
  }
}

Role Collections & User Assignment

# roles.tf
resource "btp_subaccount_role_collection" "admin" {
  subaccount_id = btp_subaccount.dev.id
  name          = "CAP_Admin"
  description   = "CAP Application Administrator"
 
  roles = [
    {
      name              = "Admin"
      role_template_app = btp_subaccount_service_instance.xsuaa_instance.name
      role_template     = "Admin"
    }
  ]
}
 
resource "btp_subaccount_role_collection_assignment" "admin_users" {
  for_each = toset(var.admin_users)
 
  subaccount_id        = btp_subaccount.dev.id
  role_collection_name = btp_subaccount_role_collection.admin.name
  user_name            = each.value
}

State Management

Remote State mit Cloud Storage

# backend.tf
terraform {
  backend "s3" {
    bucket = "my-terraform-state"
    key    = "btp/dev/terraform.tfstate"
    region = "eu-central-1"
  }
}

Oder mit BTP Object Store:

terraform {
  backend "http" {
    address = "https://objectstore.cf.eu10.hana.ondemand.com/terraform-state/dev.tfstate"
    lock_address = "https://objectstore.cf.eu10.hana.ondemand.com/terraform-state/dev.tfstate.lock"
    unlock_address = "https://objectstore.cf.eu10.hana.ondemand.com/terraform-state/dev.tfstate.lock"
  }
}

CI/CD Integration

GitHub Actions Workflow

# .github/workflows/btp-deploy.yml
name: BTP Infrastructure
 
on:
  push:
    branches: [main]
    paths:
      - 'terraform/**'
 
jobs:
  terraform:
    runs-on: ubuntu-latest
 
    steps:
      - uses: actions/checkout@v3
 
      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v2
        with:
          terraform_version: 1.6.0
 
      - name: Terraform Init
        working-directory: ./terraform
        run: terraform init
        env:
          BTP_USERNAME: ${{ secrets.BTP_USERNAME }}
          BTP_PASSWORD: ${{ secrets.BTP_PASSWORD }}
 
      - name: Terraform Plan
        working-directory: ./terraform
        run: terraform plan -out=tfplan
 
      - name: Terraform Apply
        if: github.ref == 'refs/heads/main'
        working-directory: ./terraform
        run: terraform apply -auto-approve tfplan

BTP CLI Integration

Für Aufgaben, die Terraform (noch) nicht unterstützt:

#!/bin/bash
# scripts/setup-additional.sh
 
# Login
btp login --user "$BTP_USERNAME" --password "$BTP_PASSWORD"
 
# Trust Configuration für Custom IDP
btp create security/trust sap-dev-idp \
  --subaccount "$SUBACCOUNT_ID" \
  --name "Corporate IdP" \
  --origin "corporate-idp" \
  --domain "company.com"
 
# Cloud Foundry Environment Instance
btp create accounts/environment-instance \
  --subaccount "$SUBACCOUNT_ID" \
  --environment cloudfoundry \
  --service cloudfoundry \
  --plan standard \
  --parameters '{
    "instance_name": "cf-eu10"
  }'

Monitoring & Drift Detection

# drift-detection.tf
resource "null_resource" "drift_detection" {
  triggers = {
    always_run = timestamp()
  }
 
  provisioner "local-exec" {
    command = <<-EOT
      terraform plan -detailed-exitcode || \
        (echo "⚠️ Configuration drift detected!" && \
         curl -X POST "$SLACK_WEBHOOK_URL" \
           -H 'Content-Type: application/json' \
           -d '{"text":"BTP Configuration Drift Detected!"}')
    EOT
  }
}

Best Practices

1. Secrets Management

# Nutze Vault oder Cloud Secret Manager
data "vault_generic_secret" "btp_credentials" {
  path = "secret/btp/credentials"
}
 
provider "btp" {
  globalaccount = var.globalaccount
  username      = data.vault_generic_secret.btp_credentials.data["username"]
  password      = data.vault_generic_secret.btp_credentials.data["password"]
}

2. Naming Conventions

locals {
  naming_prefix = "${var.project}-${var.environment}"
 
  resource_names = {
    subaccount = "${local.naming_prefix}-subaccount"
    xsuaa      = "${local.naming_prefix}-xsuaa"
    hana       = "${local.naming_prefix}-hana"
  }
}

3. Tagging Strategy

locals {
  common_labels = {
    managed-by  = ["terraform"]
    project     = [var.project_name]
    environment = [var.environment]
    cost-center = [var.cost_center]
    owner       = [var.team_email]
  }
}
 
resource "btp_subaccount" "main" {
  name   = local.resource_names.subaccount
  labels = local.common_labels
  # ...
}

4. Documentation as Code

# outputs.tf
output "subaccount_url" {
  value       = btp_subaccount.dev.subdomain
  description = "URL to access the BTP subaccount"
}
 
output "service_endpoints" {
  value = {
    for k, v in btp_subaccount_service_instance.cap_services :
    k => {
      name = v.name
      plan = v.service_plan
    }
  }
  description = "Overview of created service instances"
}

Troubleshooting

Common Issues

1. Entitlement nicht verfügbar

# Prüfe verfügbare Entitlements
btp list accounts/entitlement \
  --subaccount "$SUBACCOUNT_ID"

2. Service Plan nicht gefunden

# Liste alle verfügbaren Service Plans
btp list services/plan \
  --subaccount "$SUBACCOUNT_ID" \
  --offering "xsuaa"

3. Terraform State Lock

# Force unlock (nur wenn sicher!)
terraform force-unlock <LOCK_ID>

Migration von manuell zu Terraform

#!/bin/bash
# scripts/import-existing.sh
 
# Import existing subaccount
terraform import btp_subaccount.dev <subaccount-id>
 
# Import existing service instance
terraform import btp_subaccount_service_instance.xsuaa_instance \
  <subaccount-id>/<service-instance-id>

Zusammenfassung

Infrastructure as Code für BTP bietet:

  • Reproduzierbarkeit: Identische Environments on-demand
  • Versionierung: Git-basierte Historie aller Änderungen
  • Collaboration: Review-Prozess über Pull Requests
  • Automation: CI/CD-Integration für automatische Deployments
  • Documentation: Code ist Dokumentation
  • Disaster Recovery: Schnelle Wiederherstellung

Weiterführende Resources


Basierend auf Erfahrungen mit: btp-setup-automator Fork