DEV Community

Cover image for Enable Developers on SAP BTP with Terraform, GitHub Actions and Backstage
Christian Lechner
Christian Lechner

Posted on • Updated on

Enable Developers on SAP BTP with Terraform, GitHub Actions and Backstage

Motivation

One important topic in the software industry is the Developer Experience (DevEx). Citing the blog post published on GitHub Blogs DevEx can be defined as follows:

“DevEx refers to the systems, technology, process, and culture that influence the effectiveness of software development. It looks at all components of a developer’s ecosystem—from environment to workflows to tools—and asks how they are contributing to developer productivity, satisfaction, and operational impact.”
(source: https://github.blog/2023-06-08-developer-experience-what-is-it-and-why-should-you-care/ ; visited 18.03.2024).

The main goal of DevEx is to enable developers in their work. In the year 2024 of course, this can be connected to (generative) AI and tools like GitHub CoPilot improve DevEx a lot.

However, this blog post has no signs of AI in it. I want to focus on another aspect of DevEx, namely giving the developers the best possible starting point for their work which is building business applications. In addition, I will cover a special ecosystem namely development on the SAP Business Technology Platform (SAP BTP).

Starting Point

Before diving into the technical ingredients of such a setup, I want to elaborate a bit more about the starting point. Let us a assume that a development team wants to start a new development project. The requirements that need to be covered are clear and the technical work can start. What’s next? The worst thing that could happen now is that the team must do the technical project setup from scratch. This comprises:

  • Setting up their development environment (tooling etc.)
  • Investigating on the best practices, rules, and guidelines that they need to adhere in the company and that are continuously evolving.
  • Setup the project structure from a coding perspective
  • Setup their source code repository
  • Setup CI/CD
  • Provide the infrastructure/resources in the cloud.

While these tasks are important and necessary, they basically prevent the team from getting their job done. “Reinventing the wheel” with every development project also means that the basic technical setup will not be aligned and look different for every project.
This also causes issues on the long run when the projects need to be maintained and the teams working on the projects change as they need to be onboarded into a “new” project although technically the basic setup should be the same.

Wouldn’t it be great to start with some development templates that already represent a basic project outline that comprises:

  • The structure of a source code repository according to the best practices
  • Some (basic) source code as blueprint following the best practices of the company.
  • The infrastructure definition via Infrastructure as Code
  • The CI/CD setup following the best practices and helping with staying compliant.

I am a big fan of devcontainers, so for me a sample setup should also contain a devcontainer definition.

Wouldn’t it be greater to have the ability to search for such curated templates in a developer portal and then (now fasten your seatbelt) to kick off the creation of the setup for the development project as a self-service?

What if I told you that this is already possible today? Curious? Then let us look how we can achieve this and get things cooking.

Ingredients

What do we need as ingredients for this setup? Let us start with the developer portal. There are several options available. The cloud-native ecosystem is in favor of Backstage which is a CNCF project (https://backstage.io/), so let us use this as ingredient “developer portal” in our recipe.

We also need to define the Infrastructure as Code. As we are looking at SAP BTP, we will use the Terraform provider for SAP BTP (https://registry.terraform.io/providers/SAP/btp/latest) for this task.

To deal with repositories and CI/CD we leverage the GitHub ecosystem, namely organizations on GitHub, repositories as well as GitHub Actions.

Note: you can of course also use other tools for that like e.g., GitLab as Backstage provides many integration options.

That’s already it. Now let’s mix these things together and see what we will get.

Recipe

Let us reflect on what we want to achieve: we want to have one (or multiple templates) that a development team can chose from in our Backstage developer portal. When selecting such a template we want to query the user some basic information about the project setup and then:

  • Trigger the creation of a new GitHub repository that uses a predefined layout and provides some basic content.
  • Trigger the creation of needed resources on SAP BTP. The configuration should also be part of the source code repository to keep it self-contained.
  • Register the newly created project in the developer portal with a link to the source code repository.

Let's do that!

Laying the foundation in GitHub

We use an organization on GitHub. Within this organization we create a repository that will serve as a blueprint for the project setup. This could look like this:

GitHub repository - blueprint for Backstage

The repository provides a layout of a sample project, the Terraform configuration, GitHub Actions for CI/CD and governance (like a dependabot configuration) and the definition of a devcontainer.

The concrete layout and content are tailored based on the requirements of and the best practices established by the company.

The sample code, the used ecosystem like Node.js, the used libraries and frameworks like CAP and the structure and tools (e.g., for linting) are all company specific topics and there is no silver bullet how to best structure that.

Nevertheless, I am convinced that every company can standardize this to some extent. There is an initial effort to achieve this, but the result helps developers a lot. Especially when thinking about governance topics: why not deliver a compliant pipeline out of the box that ensures requirements around security etc.?

The Backstage Part

Note: This will not be a comprehensive guide on how to setup Backstage. We will focus on the setup and integration of the template.

Once we have the sample GitHub repository in place let us do the setup on Backstage.

First you need to setup Backstage per se. I did a local setup of Backstage. The installation is described in the official documentation. I also added a local PostgreSQL and connected it to Backstage as described in the documentation.

There are some pitfalls that I stumbled across that I want to share:

  • When initially creating the Backstage app via yarn, make sure that the build environment is set up in accordance with the Backstage requirements see the release notes of Backstage on “Scaffolder build requirements” as well as the information in this GitHub repository of isolated-vm. Otherwise, the yarn installation and the build will fail.
  • When you first start Backstage locally, and you are using Node.js 20 make sure that you have set the environment variable NODE_OPTIONS='--no-node-snapshot'. Otherwise, the template execution will fail later.

Once Backstage is up and running, we want to configure a software template. The template consists of several parts:

  • The metadata of the template
  • The parameters that we want from our user to be able to execute the template
  • The scaffolding actions that are executed to instantiate the template

The configuration is done in YAML format. I created a dedicated folder named btp-sample-remote to store the configuration.

Let us look at the layout of the template. The first part is the metadata:

apiVersion: scaffolder.backstage.io/v1beta3
# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-template
kind: Template
metadata:
  name: sample-btpsubaccount-remote-template
  title: Remote Template for SAP BTP Subaccount Setup
  description: A remote template that creates a basic SAP BTP Subaccount setup
  tags:
    - sap
    - btp
    - basic
    - javascript
spec:
  owner: user:guest
  type: service
Enter fullscreen mode Exit fullscreen mode

This represents what you will see in the Backstage developer portal as part of the template tile.

The next part is the parameters section that describe what input a user must provide and how the input flow will be structured:

parameters:
    - title: Project Data
      required:
        - projectName
        - region
      properties:
        projectName:
          title: Project Name
          description: Unique name of the project
          type: string
          maxLength: 50
          pattern: '^[a-zA-Z0-9_\\-]{1,200}'
          ui:autofocus: true
        region:
          title: Region of BTP Subaccount
          type: array
          items:
            type: string
            enum: 
              - 'us10'
              - 'eu10'
              - 'ap10'
          uniqueItems: true
          ui:widget: checkboxes    
    - title: Organizational Data
      required:
        - costCenter
        - stage
        - orgUnit
      properties:
        costCenter:
          title: Cost Center
          description: Cost center for the project
          type: string
          pattern: '^[0-9]{10}'
        stage:
          title: Development stage
          description: Development stage of the project
          type: array
          items:
            type: string
            enum:
              - 'DEV'
              - 'TST'
              - 'SBX'
            enumNames:
              - Development
              - Testing
              - Sandbox  
          uniqueItems: true
          ui:widget: checkboxes  
        orgUnit:
          title: Organizational Unit
          description: Organizational unit for the project
          type: array
          items:
            type: string
            enum:
              - B2B
              - B2C
              - ECOMMERCE
          uniqueItems: true
          ui:widget: checkboxes 
Enter fullscreen mode Exit fullscreen mode

As you can see the visual representation of the input can be well structured and basic layout option as well as validations can be configured.

The last part of the template is the scaffolding action that will be executed when the user finished the input:

steps:
    # This step creates a new repository in the org's GitHub account.
    - id: fetchBase
      name: Fetch Base
      action: fetch:template
      input:
        url: https://github.com/btp-automation-scenarios/backstage-base-btp-template
        copyWithoutTemplating:
          - .devcontainer/*.json
          - .devcontainer/withenvfile/*.json
          - .github/workflows/*.yml
          - .github/**/*.yml
          - infra/*.tf
          - src/*.*

        values:
          name: ${{ parameters.projectName }}

    # This step publishes the contents of the working directory to GitHub.
    - id: createRepo
      name: Publish
      action: publish:github
      input:
        repoUrl: 'github.com?repo=${{ parameters.projectName }}&owner=btp-automation-scenarios'
        description: 'Project Repository for ${{ parameters.projectName }} by Backstage.io'
        repoVisibility: 'public' 
        defaultBranch: 'main'

    # This step triggers the execution of the Terraform setup via GitHub Actions.
    - id: setupViaGHAction
      name: Execute Terraform Setup via GitHub Actions
      action: github:actions:dispatch
      input:
        repoUrl: 'github.com?repo=${{ parameters.projectName }}&owner=btp-automation-scenarios'
        workflowId: 'create_base_project.yml'
        branchOrTagName: 'main'
        workflowInputs: 
          PROJECT_NAME: '${{ parameters.projectName }}'
          REGION: '${{ parameters.region[0] }}'
          COST_CENTER: '${{ parameters.costCenter }}'
          STAGE: '${{ parameters.stage[0] }}'
          ORGANIZATION: '${{ parameters.orgUnit[0] }}'


    # The final step is to register our new component in the catalog.
    - id: register
      name: Register
      action: catalog:register
      input:
        repoContentsUrl: ${{ steps['createRepo'].output.repoContentsUrl }}
        catalogInfoPath: '/catalog-info.yaml'

  # Outputs are displayed to the user after a successful execution of the template.
  output:
    links:
      - title: Repository
        url: ${{ steps['publish'].output.remoteUrl }}
      - title: Open in catalog
        icon: catalog
        entityRef: ${{ steps['register'].output.entityRef }}
Enter fullscreen mode Exit fullscreen mode

Without going into the details, you can see that we use several actions that are available out-of-the-box in Backstage:

  • The first action fetches the template from the GitHub repository and copies the content to the working directory. In this step it also executes some templating i.e., replacing placeholders with input data. That is why you see the exclusion of several files.

  • The second action publishes the content to a new repository on GitHub.

  • The third action triggers the execution of a GitHub Action workflow that is part of the newly created repository.

  • The fourth and last action registers the new component in the Backstage catalogue so that we disclose the project to the developers.

The output section provides links to the newly created repository and the registered component in the Backstage portal.

To make this template visible in the portal we add the template to the app-config.yaml file of our Backstage application. The file contains the overall Backstage configuration. The template is added to the catalog section:

catalog:
  import:
    entityFilename: catalog-info.yaml
    pullRequestBranchName: backstage-integration
  rules:
    - allow: [Component, System, API, Resource, Location]
  locations:
    # Example of a template for SAP BTP subaccount setup 
    - type: file
      target: ../../btp-sample-remote/sap-btp-subaccount-remote.yaml
      rules:
        - allow: [Template]       
Enter fullscreen mode Exit fullscreen mode

That's it from a Backstage app perspective.

To make the template repository a "real" template we add a catalog-info.yaml file to our GitHub repository. It contains some metatdata and will be filled with the user input (where applicable) during the templating step in Backstage:

apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: ${{ values.name | dump }}
  region: ${{ values.region | dump }}
  stage: ${{ values.stage | dump }}
  orgUnit: ${{ values.orgUnit | dump }}
  costCenter: ${{ values.costCenter | dump }}
spec:
  type: service
  owner: user:guest
  lifecycle: experimental
Enter fullscreen mode Exit fullscreen mode

Last thing to do is to finalize the setup of the GitHub Action workflow that is triggered by the scaffolding action in Backstage.

The GitHub Action/Terraform part

The workflow is part of the template repository and is responsible for the Terraform setup. The workflow is triggered by the dispatch event once the new repository is created. It is defined as follows:

name: Basic Subaccount via Terraform

on:
  workflow_dispatch:
    inputs:
      PROJECT_NAME:
        description: "Name of the project"
        required: true
      REGION:
        description: "Region for the sub account"
        required: true
      COST_CENTER:
        description: "Cost center for the project"
        required: true
      STAGE:
        description: "Stage for the project"
        required: true
      ORGANIZATION:
        description: "Organization for the project"
        required: true

env:
  PATH_TO_TFSCRIPT: 'infra'

jobs:
  execute_base_setuup:
    name: BTP Subaccount Setup
    runs-on: ubuntu-latest
    steps:
    - name: Check out Git repository
      id: checkout_repo
      uses: actions/checkout@v4

    - name: Setup Terraform
      id : setup_terraform
      uses: hashicorp/setup-terraform@v3
      with:
        terraform_wrapper: false
        terraform_version: latest

    - name: Terraform Init
      id: terraform_init
      shell: bash
      run: |
        terraform -chdir=${{ env.PATH_TO_TFSCRIPT }} init -no-color

    - name: Terraform Apply 
      id: terraform_apply
      shell: bash
      # We do not store the state - in a real setup we would reference a remote backend to store the state
      run: |
        export BTP_USERNAME=${{ secrets.BTP_USERNAME }}
        export BTP_PASSWORD=${{ secrets.BTP_PASSWORD }}
        terraform -chdir=${{ env.PATH_TO_TFSCRIPT }} apply -var globalaccount=${{ secrets.GLOBALACCOUNT }} -var region=${{ github.event.inputs.REGION }} -var project_name=${{ github.event.inputs.PROJECT_NAME }} -var stage=${{ github.event.inputs.STAGE }} -var costcenter=${{ github.event.inputs.COST_CENTER }} -var org_name=${{ github.event.inputs.ORGANIZATION }} -auto-approve -no-color           
Enter fullscreen mode Exit fullscreen mode

As you can see the workflow receives the information needed for the setup via input variables.

The Terraform configuration is stored in the infra folder of the repository. The Terraform configuration is not part of this blog post but you can find a sample in the GitHub repository.

I stored the secrets needed for the setup in the GitHub organization settings, to make them available for all repositories in the organization.

And there is nothing left to do than ... to try it out.

Let's start cooking

We start the Backstage app and navigate to the developer portal. Next, we create a new project by selecting the template:

Backstage developer portal - template selection

As defined in our template we are asked for some input that we happily provide:

Backstage developer portal - data input step 1

Backstage developer portal - data input step 2

After that we can review our input and kick off the setup:

Backstage developer portal - review of input

The actions triggered by the scaffolding action are executed:

Backstage developer portal - scaffolding activities

After successful execution we can navigate to the new project in the developer portal:

Backstage developer portal - new component

From there we can jump to the newly created repository:

New GitHub repository based on template

And finally see the GitHub Action running for the setup:

GitHub Action for subaccount setup via Terraform

Once the GitHub action has finished, we can check the provided subaccount in the SAP BTP cockpit:

Created subaccount via Terraform from Backstage template

So, mission accomplished!

You want to see this in action? Here you go:

.

Conclusion

With very few ingredients, we were able to provide a self-service for the setup of a new development project on SAP BTP. The setup is standardized self-contained and can be executed with a few clicks and enables the development team to start their development work immediately.

Of course, the setup presented here has some rough edges and further refinement is needed, but you see what is already possible today in SAP BTP development projects when combining the right ingredients.

It is worth to mention that there is a learning curve when it comes to Backstage. I clearly see the power of Backstage and the uncountable options that come with Backstage. But this is accompanied by a certain degree of complexity. Unfortunately, I do not think that the documentation is leveling this out. Nevertheless, the power of Backstage is from my perspective worth the effort.

What do you think?

I am curious to hear your thoughts on this setup. Do you see potential in this? Do you already have experience with comparable setups especially when it comes to development on SAP BTP?

I am looking forward to your comments and feedback.

Top comments (0)