← Back to Index

redhat developer hub

Customer want to integrate Azure DevOps with Red Hat Developer Hub, so that they can see the code, and the pipeline status in the Developer Hub. We will try to do that.

The whole senario includes:

  1. rhdh
  2. dev workspace
  3. ocp gitops

To complish the target, we will use several components:

  1. rhsso, for azure ad integration.
  2. backstage plugin
    1. azure devops (included in rhdh)
    2. azure devops auto discovery (working)
    3. msgraph(does not work right now)

There is a great demo in demo.redhat.com, we will try this lab based on that demo env.

deploy using demo.redhat.com

try with demo.redhat.com instance:

The document of this demo is here.

The github repo used for integrated gitlab:

rhdh azure devops plugin

After the demo env startup, we need to customize the backstage plugin, to enable the azure devops plugin.

Azure DevOps Env

Before we can setup the plugin in rhdh, we need to setup the azure devops env.

create azure devops service in :

create a personal access token:

Try to create the personal access token in all organization, this will elimate the permission issue.

Install Code Search Feature for Azure devops

create a repo under your organization, and project. The content of the repo does matter for this lab, because it contains catalog for demo system, components, and api.

Find your tenant id, client id, and client secret

Create an application, then you can get the client id

Set correct permission

Set searchable branch.

rhdh app config

In this lab, RHDP is gitopts installed, so find in gitlab, and change the content, both plugins and integrations.

Apply below content to the backstage-values.yaml, do not delete existed, just add. Not all config is correct, I am still working on it. Replace the secure key with your own ones.

[!TIP] get checksum with npm info

data:
          dynamic-plugins.yaml: |
            includes:
            - dynamic-plugins.default.yaml
            plugins:
              - disabled: false
                package: ./dynamic-plugins/dist/backstage-plugin-azure-devops
              - disabled: false
                package: ./dynamic-plugins/dist/backstage-plugin-azure-devops-backend-dynamic
              # - disabled: false
              #   package: ./dynamic-plugins/dist/backstage-plugin-scaffolder-backend-module-azure-dynamic
              # - disabled: false
              #   integrity: >-
              #       sha512-WxRXsTppHKxzMHpUvEiQR3rYPypSHDHABAqegjareHYEXgA5uVBsRW2zES6GpOeei45KnxGL+NcuoKQezg1D7A==
              #   package: '@backstage/plugin-azure-devops@0.4.4'
              # - disabled: false
              #   integrity: >-
              #       sha512-wHZC7riqyakSzPrxM1+edu1Et99Q0gAd0WXxrnclUo7lT45+xvqYxzbdVR9Kr7OHr/6AugMghJZV1BzCxl2+PQ==
              #   package: '@backstage/plugin-azure-devops-backend@0.6.5'
              - disabled: false
                integrity: >-
                    sha512-H3d4UThnU+EUCFfH3lBPvm0mYXdAQ/GG4blg71Oe8nfjm9eN9yATxq8r74430Xyi1xn+2HVbVbLyvWpgpIp/ig==
                package: '@backstage/plugin-catalog-backend-module-azure@0.1.38'
              - disabled: false
                integrity: >-
                    sha512-C7qhlHOQeXMNMPekgEoTdTiVq2hHdZkHvUHpb4EyCOE8MzGFx1LTl7r7ch4jiFkr15YQuqOImYUc/JhGNnes8A==
                package: '@backstage/plugin-catalog-backend-module-msgraph@0.5.26'
              # - disabled: false
              #   integrity: >-
              #       sha512-eBfl2rPN3HrgECEeHS9uw9Y4xaAQgzNu7qn/kYarqTRi3Rnn5V8zMm5jU4gcqfcxdBbdpUb9HpRvOqk9V96VSA==
              #   package: '@backstage/plugin-azure-devops-common@0.4.2'
        
        upstream:
          backstage:
        
            extraEnvVars:
              - name: AZURE_CLIENT_ID
                value: <change me to secret value>
              - name: AZURE_CLIENT_SECRET
                value: <change me to secret value>
              - name: AZURE_TENANT_ID
                value: <change me to secret value>
              - name: AZURE_TOKEN
                value: <change me to secret value>
              - name: AZURE_ORG
                value: wangzheng422
              - name: KEYCLOAK_BASE_URL
                value: https://keycloak-backstage.apps.cluster-wzjhs.sandbox2376.opentlc.com/auth
              - name: KEYCLOAK_LOGIN_REALM
                value: backstage
              - name: KEYCLOAK_REALM
                value: backstage
              - name: KEYCLOAK_CLIENT_ID
                valueFrom:
                  secretKeyRef:
                    key: CLIENT_ID
                    name: keycloak-client-secret-backstage
              - name: KEYCLOAK_CLIENT_SECRET
                valueFrom:
                  secretKeyRef:
                    key: CLIENT_SECRET
                    name: keycloak-client-secret-backstage
        
            appConfig:
              integrations:
                azure:
                  - host: dev.azure.com
                    credentials:
                      - organizations:
                          - ${AZURE_ORG}
                        personalAccessToken: ${AZURE_TOKEN}
                        
              catalog:
                locations:
                  - target: https://dev.azure.com/wangzheng422/demo/_git/service-demo?path=%2Forg.yaml&version=GBmain&_a=contents
                    # can be git url from azure devops repo
                    # target: https://dev.azure.com/wangzheng422/demo/_git/service-demo?path=%2Forg.yaml&version=GBmain&_a=contents
                    # https://github.com/wangzheng422/backstage-customize/blob/data/org.yaml
                    type: url
                    rules:
                      - allow: [Group, User]
                  - target: https://github.com/redhat-developer/red-hat-developer-hub-software-templates/blob/main/templates/azure/dotnet-frontend/template.yaml
                    type: url
                    rules:
                      - allow: [Template]
        
                providers:
                  azureDevOps:
                    yourProviderId: # identifies your dataset / provider independent of config changes
                      organization: wangzheng422
                      project: '*'
                      repository: '*' # this will match all repos starting with service-*
                      path: /catalog-info.yaml
                      schedule: # optional; same options as in TaskScheduleDefinition
                        # supports cron, ISO duration, "human duration" as used in code
                        frequency: { minutes: 30 }
                        # supports ISO duration, "human duration" as used in code
                        timeout: { minutes: 3 }
                  microsoftGraphOrg:
                    default:
                      tenantId: ${AZURE_TENANT_ID}
                      clientId: ${AZURE_CLIENT_ID}
                      clientSecret: ${AZURE_CLIENT_SECRET}
                      user:
                        filter: accountEnabled eq true and userType eq 'member'
                        select: [id,mail,displayName,givenName,mobilePhone]
                      group:
                        filter: >
                          securityEnabled eq false
                          and mailEnabled eq true
                          and groupTypes/any(c:c+eq+'Unified')
                      schedule:
                        frequency: PT1H
                        timeout: PT50M
                  keycloakOrg:
                    default:
                      baseUrl: ${KEYCLOAK_BASE_URL}
                      loginRealm: ${KEYCLOAK_LOGIN_REALM}
                      realm: ${KEYCLOAK_REALM}
                      clientId: ${KEYCLOAK_CLIENT_ID}
                      clientSecret: ${KEYCLOAK_CLIENT_SECRET}
                      schedule: # optional; same options as in TaskScheduleDefinition
                        # supports cron, ISO duration, "human duration" as used in code
                        frequency: { minutes: 1 }
                        # supports ISO duration, "human duration" as used in code
                        timeout: { minutes: 1 }
                        initialDelay: { seconds: 15 }
        
        
              enabled:
                azure: true
                azureDevOps: true
                microsoftGraphOrg: false
                keycloakOrg: true
                microsoft: false

In gitlab webconsole, change the content and commit

Then, from gitops operator view, update backstage.app.valueFile to the commited raw file url.

Wait sometime, you can see the plugin is enable in RHDP

see the azure devops result

Import the resources, with reference to azure devops, with the auto discovery you can see the components, system, and api.

For API

from the detail view, you can see the dependency relationship.

and you can see the azure devops repo’s pull request

and the pipeline status

and the source code.

For components

For system

Because the msgraph does not work right now, you can see new group created, without user and the desire groups.

For users, we can only see the user from git source code.

Here is the catalog-info.yaml, for your referece

---
        
        # https://backstage.io/docs/features/software-catalog/descriptor-format#kind-system
        
        apiVersion: backstage.io/v1alpha1
        kind: System
        metadata:
          name: azure-examples
          annotations:
            dev.azure.com/project-repo: demo/service-demo
            dev.azure.com/host-org: dev.azure.com/wangzheng422
        spec:
          owner: azure-guests
        ---
        
        # https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component
        
        apiVersion: backstage.io/v1alpha1
        kind: Component
        metadata:
          name: azure-example-website
          annotations:
            dev.azure.com/project-repo: demo/service-demo
            dev.azure.com/host-org: dev.azure.com/wangzheng422
        spec:
          type: azure-website
          lifecycle: experimental
          owner: azure-guests
          system: azure-examples
          providesApis: [azure-example-grpc-api]
        ---
        
        # https://backstage.io/docs/features/software-catalog/descriptor-format#kind-api
        
        apiVersion: backstage.io/v1alpha1
        kind: API
        metadata:
          name: azure-example-grpc-api
          annotations:
            dev.azure.com/project-repo: demo/service-demo
            dev.azure.com/host-org: dev.azure.com/wangzheng422
        spec:
          type: grpc
          lifecycle: experimental
          owner: azure-guests
          system: azure-examples
          definition: |
            syntax = "proto3";
        
            service Exampler {
              rpc Example (ExampleMessage) returns (ExampleMessage) {};
            }
        
            message ExampleMessage {
              string example = 1;
            };

Here is the org.yaml, for your reference

---
        
        # https://backstage.io/docs/features/software-catalog/descriptor-format#kind-user
        
        apiVersion: backstage.io/v1alpha1
        kind: User
        metadata:
          name: azure-guest
        spec:
          memberOf: [azure-guests]
        ---
        
        # https://backstage.io/docs/features/software-catalog/descriptor-format#kind-group
        
        apiVersion: backstage.io/v1alpha1
        kind: Group
        metadata:
          name: azure-guests
        spec:
          type: team
          children: []

disable auto discovery

It seems, you can disable azure devops auto discovery, and add the resource from github manually. The source code just the same.

We will use a repo host on github right now. The demo repo is here:

And you can see the result

And you can see there is already a merge request existed in the azure devops repo.

Azure sso using rhsso/keycloak

The demo.redhat.com ’s demo lab, using rhsso with rhdp as the identity provider, so we can use the same way to integrate with azure ad. You can use this method, if the org import(plugin msgraph) from azure do not work.

reference:

setup

Register a new app

Here you can see the new client id,

Create secret for the app

Set the expired date.

Set permission to read user info.

Add additional information to token claim, espeically the groups.

Get endpoint information:

Config rhsso

add azure ad sso integration

Give it a name.

Copy the endpoint config from azure

Get group name and group id from azure entra

Add group to keycloak/rhsso manually, we do not know how to add it automatically.

Add group mapper, so the new user will be added to its group by matching the group id.

Add username mapping to oid, the rhdh will not create user entity for email address format

Get the redirect url setting from rhsso.

Add the redirect url to azure

Easy the security setting for your azure org, we are testing env, so the user can login only with password.

try it out

Open rhdh url, it will redirect to rhsso, select Azure

Login with azure account in your org.

After login, you will be redirected to rhdh, your user info is automaticalty set, because we defined a client scope and mapping for you.

Wait some moments, after rhdh sync user and group info from rhsso/keycloak, you can see the new user created, and link to sso user info.

If you login using an azure user without a group, you will see this:

Azure sso with hacked msgraph

The msgraph plugin does not work with azure/microsoft sso right now, the msgraph will import user with azure email as rhdh username, but azure/microsoft sso will use azure user oid as rhdh username, they are not match, so we need to hack it.

The upstream backstage have ways to match the user using azure oid, but we are rhdh, we can not cusomize the backstage plugin, so we need to hack the msgraph plugin.

We hack the msgraph using this repo. The change is simple, just use azure user oid as rhdh username.

The patch for demo.redhat.com is here:


        data:
          dynamic-plugins.yaml: |
            plugins:
              - disabled: false
                package: ./dynamic-plugins/dist/backstage-plugin-azure-devops
              - disabled: false
                package: ./dynamic-plugins/dist/backstage-plugin-azure-devops-backend-dynamic
              # - disabled: false
              #   package: ./dynamic-plugins/dist/backstage-plugin-scaffolder-backend-module-azure-dynamic
              # - disabled: false
              #   integrity: >-
              #       sha512-WxRXsTppHKxzMHpUvEiQR3rYPypSHDHABAqegjareHYEXgA5uVBsRW2zES6GpOeei45KnxGL+NcuoKQezg1D7A==
              #   package: '@backstage/plugin-azure-devops@0.4.4'
              # - disabled: false
              #   integrity: >-
              #       sha512-wHZC7riqyakSzPrxM1+edu1Et99Q0gAd0WXxrnclUo7lT45+xvqYxzbdVR9Kr7OHr/6AugMghJZV1BzCxl2+PQ==
              #   package: '@backstage/plugin-azure-devops-backend@0.6.5'
              - disabled: false
                integrity: >-
                    sha512-H3d4UThnU+EUCFfH3lBPvm0mYXdAQ/GG4blg71Oe8nfjm9eN9yATxq8r74430Xyi1xn+2HVbVbLyvWpgpIp/ig==
                package: '@backstage/plugin-catalog-backend-module-azure@0.1.38'
              - disabled: false
                integrity: >-
                    sha512-RTOzk+k0XCu0ivqqHrnis4lzsQqAhEVv++6bb4exqhU8LpWGX0R15RPljVzHtWKqT35CXhNt0JWYv8hSjUPzgg==
                package: '@wangzheng422/plugin-catalog-backend-module-msgraph@0.5.26'
              # - disabled: false
              #   integrity: >-
              #       sha512-C7qhlHOQeXMNMPekgEoTdTiVq2hHdZkHvUHpb4EyCOE8MzGFx1LTl7r7ch4jiFkr15YQuqOImYUc/JhGNnes8A==
              #   package: '@backstage/plugin-catalog-backend-module-msgraph@0.5.26'
              # - disabled: false
              #   integrity: >-
              #       sha512-eBfl2rPN3HrgECEeHS9uw9Y4xaAQgzNu7qn/kYarqTRi3Rnn5V8zMm5jU4gcqfcxdBbdpUb9HpRvOqk9V96VSA==
              #   package: '@backstage/plugin-azure-devops-common@0.4.2'
              # - disabled: false
              #   integrity: >-
              #       sha512-iRxCHis0E2CemuEQ/CQvk9O5vVw3dRA/EOLvo4Ms1scfFDdJqogHH+KiVzEOf5nhf3YUmPpMT0cB+G4kx+th9A==
              #   package: '@backstage/plugin-auth-backend-module-azure-easyauth-provider@0.1.1'
        
        upstream:
          backstage:
            # image:
            #   registry: quay.io
            #   repository: wangzheng422/qimgs
            #   tag: 'rhdh-hub-rhel9-1.1-2024.05.17.v02'
        
            extraEnvVars:
              - name: AZURE_CLIENT_ID
                value: <change me to secret value>
              - name: AZURE_CLIENT_SECRET
                value: <change me to secret value>
              - name: AZURE_TENANT_ID
                value: <change me to secret value>
              - name: AZURE_TOKEN
                value: <change me to secret value>
              - name: AZURE_ORG
                value: wangzheng422
              # - name: KEYCLOAK_BASE_URL
              #   value: https://keycloak-backstage.apps.cluster-qjwdr.sandbox928.opentlc.com/auth
              - name: KEYCLOAK_BASE_URL
                value: $(APP_CONFIG_catalog_providers_keycloakOrg_default_baseUrl)
              - name: KEYCLOAK_LOGIN_REALM
                value: backstage
              - name: KEYCLOAK_REALM
                value: backstage
              - name: KEYCLOAK_CLIENT_ID
                valueFrom:
                  secretKeyRef:
                    key: CLIENT_ID
                    name: keycloak-client-secret-backstage
              - name: KEYCLOAK_CLIENT_SECRET
                valueFrom:
                  secretKeyRef:
                    key: CLIENT_SECRET
                    name: keycloak-client-secret-backstage
        
            appConfig:
              integrations:
                azure:
                  - host: dev.azure.com
                    credentials:
                      - organizations:
                          - ${AZURE_ORG}
                        personalAccessToken: ${AZURE_TOKEN}
                        # clientId: ${AZURE_CLIENT_ID}
                        # clientSecret: ${AZURE_CLIENT_SECRET}
                        # tenantId: ${AZURE_TENANT_ID}
        
        
              auth:
                environment: production
                providers:
                  microsoft:
                    production:
                      clientId: ${AZURE_CLIENT_ID}
                      clientSecret: ${AZURE_CLIENT_SECRET}
                      tenantId: ${AZURE_TENANT_ID}
                      domainHint: ${AZURE_TENANT_ID}
                      additionalScopes:
                        - Mail.Send
                      signIn:
                        resolvers:
                          # typically you would pick one of these
                          - resolver: idMatchingUserEntityAnnotation
                          - resolver: emailMatchingUserEntityProfileEmail
                          - resolver: emailLocalPartMatchingUserEntityName
                          - resolver: emailMatchingUserEntityAnnotation  
        
              signInPage: microsoft
        
        
              catalog:
                locations:
                  # https://dev.azure.com/wangzheng422/demo/_git/service-demo?path=%2Forg.yaml&version=GBmain&_a=contents
                  # https://github.com/wangzheng422/backstage-customize/blob/data/org.yaml
                  - target: https://dev.azure.com/wangzheng422/demo/_git/service-demo?path=%2Forg.yaml&version=GBmain&_a=contents
                    type: url
                    rules:
                      - allow: [Group, User]
                  - target: https://github.com/wangzheng422/red-hat-developer-hub-software-templates/blob/wzh-hack/templates/azure/dotnet-frontend/template.yaml
                    type: url
                    rules:
                      - allow: [Template]
        
                providers:
                  azureDevOps:
                    yourProviderId: # identifies your dataset / provider independent of config changes
                      organization: wangzheng422
                      project: '*'
                      repository: '*' # this will match all repos starting with service-*
                      path: /catalog-info.yaml
                      schedule: # optional; same options as in TaskScheduleDefinition
                        # supports cron, ISO duration, "human duration" as used in code
                        frequency: { minutes: 30 }
                        # supports ISO duration, "human duration" as used in code
                        timeout: { minutes: 3 }
                  microsoftGraphOrg:
                    default:
                      tenantId: ${AZURE_TENANT_ID}
                      clientId: ${AZURE_CLIENT_ID}
                      clientSecret: ${AZURE_CLIENT_SECRET}
                      user:
                        filter: >
                          accountEnabled eq true and userType eq 'member'
                        # select: ['id', 'displayName', 'mail']
                      # userPrincipalName eq 'demo-backstage@wangzheng422outlook.onmicrosoft.com'
                      group:
                        filter: >
                          displayName eq 'demo-group-backstage'
                      schedule:
                        frequency: PT1H
                        timeout: PT50M
                  keycloakOrg:
                    default:
                      baseUrl: ${KEYCLOAK_BASE_URL}
                      loginRealm: ${KEYCLOAK_LOGIN_REALM}
                      realm: ${KEYCLOAK_REALM}
                      clientId: ${KEYCLOAK_CLIENT_ID}
                      clientSecret: ${KEYCLOAK_CLIENT_SECRET}
                      schedule: # optional; same options as in TaskScheduleDefinition
                        # supports cron, ISO duration, "human duration" as used in code
                        frequency: { minutes: 1 }
                        # supports ISO duration, "human duration" as used in code
                        timeout: { minutes: 1 }
                        initialDelay: { seconds: 15 }
        
        
              enabled:
                kubernetes: true
                techdocs: true
                argocd: true
                sonarqube: false
                keycloak: false  # true -> false
                ocm: true
                github: false
                githubOrg: false
                gitlab: true
                jenkins: false
                permission: false
        
                azure: true
                azureDevOps: true
                microsoftGraphOrg: true
                keycloakOrg: false
                microsoft: true
                azureEasyAuth: false
        
          service:
            ports:
              backend: 4180
              targetPort: backend
        

result

It turn out the hack works, the user is created with azure oid as username.

azure sso as openshift idp

golden path template

There is a golden path template for backstage, you can use it to create a new backstage app.

we will use this template for test:

New repo created in azure devops:

Go back to rhdh, we can see new component created.

dev space

Add user to org and project.

Login using user in the org.

Set your personal access token

Copy the token generate

Access the dev space in openshift

There is only 4 provider right now

import and create the workspace

The devfile generated

schemaVersion: 2.1.0
        metadata:
          attributes:
            metadata-name-field: generateName
            metadata-name-original-value: dummy-repo-01
          name: dummy-repo-01
          namespace: user1-devspaces
        attributes:
          che-theia.eclipse.org/sidecar-policy: mergeImage
        projects:
          - attributes: {}
            name: dummy-repo-01
            git:
              remotes:
                origin: https://dev.azure.com/wangzheng422/demo/_git/dummy-repo-01
        components:
          - name: universal-developer-image
            container:
              image: registry.redhat.io/devspaces/udi-rhel8@sha256:022cc606ec53638f7079a638f0810fee3a1717b42426bcfa31c2ba2e78520c54
        commands: []

You can see the code editor webUI now.

make some change to see whether git push works.

git push ok

todo

  1. The dev space link can be integrated into rhdh, we will show how to later.
  2. integrate with azure ad/sso

The offical document is here

For our azure devops env, we can not link it to auzre ad/entra right now, because subscription/license issue. So we can not test the sso integration now.

end