Começando com o Backstage Spotify

Do zero ao "Não to mais tão perdido"

A gente já falou do Backstage, mas como eu faço para começar hoje com ele?

Bora lá!

gatinho digitando no computador

Criando a sua aplicação do Backstage

A primeira coisa que eu acredito que gera muita confusão nas pessoas é como começar no Backstage. É comum as pessoas olharem o repositório e pensar que ela vai usar esse repositório como aplicação, o que não é verdade.

Aqui eu quero que você imagine o Backstage quase como um conjunto de bibliotecas que te permitem fazer algo, e que você precisa “instalar” na sua aplicação. Muito parecido com o que o Create React App faz, nós vamos iniciar um novo app a partir do CLI do Backstage (considerando que você já atende os pré-requisitos), com o nome "angeliski-stage"

imagem do terminal com a criação da nova app

Sem muito segredo: Você roda o comando, diz o nome da sua app, ele cria um monorepo pronto para usar. Vamos dar uma olhada na estrutura das pastas criadas (Não se preocupe, vamos aprofundar isso ao longo do tempo):

├── README.md
├── app-config.local.yaml
├── app-config.production.yaml
├── app-config.yaml
├── backstage.json
├── catalog-info.yaml
├── examples
|  ├── entities.yaml
|  ├── org.yaml
|  └── template
├── lerna.json
├── package.json
├── packages
|  ├── README.md
|  ├── app
|  └── backend
├── plugins
|  └──     README.md
├── tsconfig.json
└── yarn.lock
  • app-config.[ENVIROMENT].yaml - são os arquivos de configuração. Eles podem ser separados por ambiente e tem uma hierarquia sobrescrita na execução. Destaque para o arquivo local, que é apenas para uso local e não é comitado pois está no .gitignore

  • backstage.json - É um arquivo que carrega informação da versão do Backstage que você está usando. Já fica a dica de usar o hellper para atualização.

  • catalog-info.yaml - arquivo de mapeamento do catalogo no Backstage

  • examples - Alguns exemplos, para você já sair testando o catalogo

  • packages/app - A aplicação front-end (React)

  • packages/backend - A aplicação backend (Express)

  • plugins - Essa pasta vai conter possíveis implementações internas de plugins

Nesse momento, você pode rodar um simples yarn dev na raiz do projeto e ver ele rodando (se você tiver clonado ele, rode um yarn antes).

imagem obtida ao acessar a tela com o yarn dev

Entendendo o Catálogo do Backstage

O catálogo é o coração do Backstage. Não só muitas das funcionalidades são dependentes dele, mas boa parte da visualização inicial que você tem acesso vem a partir dele.

Então é importante a gente entender mais dele antes de começar a mexer na nossa aplicação.

O catálogo carrega o conceito de Entidades (Entity), onde elas são ingeridas de alguma fonte externa (Github, Okta, Bitbucket, LDAP) e passa por um ciclo de vida (Ingestão, processamento, validação, erros) até ficarem disponíveis para acesso via UI (ou API)
O catálogo tem um modelo padrão que é onde os plugins normalmente se baseiam:

Imagem do padrão do catalog, contendo as entidades necessárias

Isso significa que essas entidades são parte do fluxo padrão. Além disso, entidades como usuários e grupos ficam disponíveis para vínculos de responsabilidade.

Ele trabalha com o formato YAML tanto para ingestão, quanto na resposta das APIs (que são devolvidas em JSON) e tem uma série de regras de acordo com cada entidade. Normalmente ele acaba sendo parecido com isso:

apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
 name: angeliski-stage
 description: An example of a Backstage application.
 annotations:
   github.com/project-slug: angeliski/angeliski-stage
spec:
 type: service
 owner: angeliski
 lifecycle: experimental

Esse arquivo é comumente chamado de catalog-info.yaml e normalmente aparece na raiz do repositório, mas esse padrão não é obrigatório.

Como nosso repositório está no Github nós vamos fazer a ingestão dos serviços através dele (o Github).

Configurando a ingestão do catálogo via Github

Eu já disse antes que o catálogo é uma parte fundamental do Backstage, mas para ele funcionar bem você precisa colocar a informação ali. Na app criada já existem alguns arquivos de demonstração, mas no mundo real a gente quer consumir esses dados de algum outro lugar.

Para isso, nós podemos usar duas opções: Entity Providers ou Catalog Processors.

Neste momento não vou me aprofundar nas diferenças, só dizer que para a maioria dos casos você vai preferir usar um Entity Provider.

No nosso exemplo, nós vamos fazer uma integração com o GIthub, utilizando o provider dele.

  1. adicionar o plugin responsável por esse processo:
yarn add --cwd packages/backend @backstage/plugin-catalog-backend-module-github
  1. modificar o arquivo onde nós configuramos nosso catálogo:

     // No packages/backend/src/plugins/catalog.ts
    
     //importar o provider
      import { GithubEntityProvider } from '@backstage/plugin-catalog-backend-module-github';
    
     // no corpo da função
         builder.addEntityProvider(
             GithubEntityProvider.fromConfig(env.config, {
               logger: env.logger,
               schedule: env.scheduler.createScheduledTaskRunner({
                 frequency: { minutes: 30 },
                 timeout: { minutes: 3 },
               }),
             }),
           );
    
  2. Adicionar no arquivo app-config.yaml a configuração necessária para achar os arquivos do catálogo:

providers:
   github:
     providerId:
       organization: 'angeliski'
       catalogPath: '/catalog-info.yaml'
       filters:
         branch: 'main'
         repository: 'angeliski-stage'
  1. Configurar a autenticação com o Github. Nessa etapa a gente só precisa gerar um token do Github e configurar ele na variável de ambiente GITHUB_TOKEN. Em outro momento podemos fazer a configuração com uma Github App

Ao colocar o nosso projeto em execução, vamos encontrar um catálogo com o seguinte resultado (O repositório pode demorar alguns segundos para aparecer, então pode ser necessário atualizar a página):

imagem do catalogo com o repositório aparecendo

No nosso exemplo eu apenas fiz a ingestão de um repositório, mas você pode configurar essa ingestão para ser feita através da sua organização inteira. Além disso, você consegue fazer a ingestão de usuários e grupos da sua organização, de modo a ter um catalogo com a informação correta, indicando os donos de cada serviço (para isso que serve aquele campo owner no arquivo)

Commit do Github com as alterações: aqui

Criando o primeiro template

Agora nós vamos começar a adicionar um template na nossa aplicação. A ideia aqui é criar uma simples app express, muito mais para mostrar como o Software Template funciona do que definir como serão seus templates. Você inclusive pode ver alguns exemplos no Github.

Antes de começar vamos entender quais são os elementos que fazem parte do nosso template:

  • Inputs - Esses são os parâmetros de entrada para criar seu template. Basicamente são as informações que você vai informar para o Backstage conseguir criar sua aplicação a partir do template. Nós vamos ver como declarar isso no nosso arquivo do template mais para frente.

  • Actions - É aqui onde a mágica acontece, as actions são responsáveis por executar operações que vão gerar seu template final. Aqui temos desde download do template até publicação do seu repositório no Github.

  • Outputs - Aqui nós estamos falando da saída para o usuário final, a nível de interface. Podemos por exemplo exibir um link para o repositório do Github.

Vamos dar uma olhada em como vai ficar o arquivo de template para o nosso exemplo:

apiVersion: scaffolder.backstage.io/v1beta3
# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-template
kind: Template
metadata:
  name: express-app
  title: Example Express Template
  description: An example template for the scaffolder that creates a simple express app
spec:
  owner: user:guest
  type: service

  # These parameters are used to generate the input form in the frontend, and are
  # used to gather input data for the execution of the template.
  parameters:
    - title: Fill in some steps
      required:
        - name
      properties:
        name:
          title: Name
          type: string
          description: Unique name of the app
          ui:autofocus: true
          ui:options:
            rows: 5
    - title: Choose a location
      required:
        - repoUrl
      properties:
        repoUrl:
          title: Repository Location
          type: string
          ui:field: RepoUrlPicker
          ui:options:
            allowedHosts:
              - github.com

  # These steps are executed in the scaffolder backend, using data that we gathered
  # via the parameters above.
  steps:
    # Each step executes an action, in this case one templates files into the working directory.
    - id: fetch-base
      name: Fetch Base
      action: fetch:template
      input:
        url: ./content
        values:
          name: ${{ parameters.name }}

    # This step publishes the contents of the working directory to GitHub.
    - id: publish
      name: Publish
      action: publish:github
      input:
        allowedHosts: ['github.com']
        description: My express ${{ parameters.name }}
        repoUrl: ${{ parameters.repoUrl }}

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

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

O template também é uma Entity do seu catálogo, então ela segue a mesma ideia de owner e metadados. Além disso, você pode reparar que no começo desse template nós temos a definição dessa API, que permite você saber quais são os valores e informações aceitáveis no seu template.

O field parameters é onde nós declaramos nossos inputs. Essa declaração é o que o Backstage vai utilizar para montar a interface, ela segue um uso forte do react-jsonschema-form, o que torna nosso front-end muito versátil e dinâmico (inclusive permitindo que você crie componentes customizados, mas isso é papo para outra hora)

Aqui vale ressaltar que você está construindo um Wizard, então ele pode ter várias etapas onde você vai organizar a informação para gerar uma melhor experiência para seu time de engenharia . Nosso exemplo tem duas paradas, uma onde solicitamos o nome da aplicação e outra onde vamos solicitar as informações do Github.

A próxima seção importante é a de steps, onde nós vamos criar nossa receita para gerar a aplicação. Cada item dessa seção vai ser uma action que vai ser responsável por realizar alguma operação para gerar nosso template final. A usa instância do Backstage permite exibir uma listagem das actions disponíveis, com informações relevantes ( no futuro a gente pode construir uma action e explorar mais isso), essa listagem está disponível em

http://localhost:3000/create/actions ou pode ser acessível pela tela em Installed Actions

No nosso exemplo nós temos três actions:

  • Fetch Base - Essa action é responsável por obter o template, ele busca a base do template que nós vamos aplicar. Essa action em específico ainda realiza um render alterando as variáveis que nós temos no nosso template, utilizando a sintaxe do Nunjucks (a template engine utilizada pelo Backstage)

  • Publish - Essa action vai publicar o seu código no Github, inclusive realizando a criação do repositório para isso

  • Register - Essa action registra a aplicação recém criada no próprio Backstage (para isso precisa existir um arquivo de catálogo, repare que nosso template final já tem essa informação)

O último pedaço faz referência aos nossos links de saída, o que é muito útil para permitir um rápido acesso a informação recém criada. No nosso exemplo nós temos acesso ao serviço no catálogo e ao repositório no Github.

Você pode ver o resultado final dessa app aqui, bem como o template utilizado aqui.

Commit do Github com as alterações: aqui

Autenticando com o Github

Uma das coisas legais do nosso catálogo é permitir que ao acessar o Backstage ele consiga te dizer quais são os serviços sob sua responsabilidade, considerando os seus times e as declarações de owner presentes no catálogo.

Mas para que ele consiga fazer isso, ele precisa saber quem é você. Para isso ele conta com o conceito de Identity (Identidade), que basicamente significa você se identificar no Backstage, normalmente considerando alguma fonte externa (no nosso exemplo nós vamos usar o Github).

Aqui vale um disclaimer muito importante e que costuma gerar confusão:

Esse mecanismo não foi criado com o intuito de bloquear acessos (não é um mecanismo de autorização), então é muito recomendado que você tenha algum mecanismo que permita você bloquear usuários externos de acessar sua aplicação principal.

A documentação oficial tem diversos providers que você pode utilizar, ou se não estiver disponível você ainda pode adicionar um novo provider seguindo as interfaces necessárias.

Vamos agora ver como podemos fazer para que nossa aplicação permita o usuário se identificar através do Github.

Para isso, você precisa criar um GitHub App ou OAuth App que vão ser responsáveis por controlar o fluxo do OAuth2. Vamos criar um Github App no nosso caso.

Depois de criada você vai obter um clientId e um clientSecret, eles são necessários para realizar o fluxo de OAuth2. Agora vamos colocar a nossa nova configuração no app-config.yaml

auth:
  environment: development
  providers:
    github:
      development:
        clientId: ${AUTH_GITHUB_CLIENT_ID}
        clientSecret: ${AUTH_GITHUB_CLIENT_SECRET}

No nosso backend vamos precisar setar os valores da envs antes de subir o projeto. Agora vamos configurar nossa interface para dar a opção ao nosso usuário de realizar esse processo pelo Github.

No arquivo packages/app/src/App.tsx nós vamos realizar algumas modificações:

+ import { githubAuthApiRef } from '@backstage/core-plugin-api';
+ import { SignInPage } from '@backstage/core-components';

 const app = createApp({
   apis,
+  components: {
+    SignInPage: props => (
+      <SignInPage
+        {...props}
+        auto
+        provider={{
+          id: 'github-auth-provider',
+          title: 'GitHub',
+          message: 'Sign in using GitHub',
+          apiRef: githubAuthApiRef,
+        }}
+      />
+    ),
+  },
   bindRoutes({ bind }) {

E temos o seguinte resultado:

Isso é suficiente para habilitar o login através do Github. Note que eu removi a opção de acessar usando guest user (basicamente um usuário anônimo).

Outra coisa importante é a relação entre o catálogo e a autenticação. Em versões mais antigas o usuário que estava se autenticado não precisava ter uma correspondência no catálogo, mas isso mudou (por padrão). Você ainda pode realizar essa operação, mas vai precisar customizar um pouco, basta olhar a documentação. Isso foi alterado pois é uma expectativa que o usuário que se identificou tenha uma representação no catálogo além de suas relações com os serviços.

Commit do Github com as alterações: aqui

Mas e para colocar em produção?

Com isso nós colocamos um conjunto de funcionalidades na nossa aplicação que vai nos permitir tirar valor do Backstage. Em outro texto podemos falar mais de estratégias de adoção e aprofundar em caminhos para mapear nosso catálogo e como usar outras fontes além do Github.

Mas e agora, como mandar para produção?

Existem diversas formas de fazer o deploy da sua aplicação, a mais comum é fazer isso através de containers, então vamos seguir essa abordagem.

A primeira coisa a se notar é que no nosso projeto já foi criado por padrão um Dockerfile que vai permitir a gente criar essa imagem e fazer o deploy dela.

Basicamente nós precisamos fazer o seguinte processo ante de criar a imagem:

yarn install --frozen-lockfile
yarn tsc
yarn build:backend

Com isso o projeto vai estar pronto para construção da imagem:

docker image build . -f packages/backend/Dockerfile --tag backstage

E para executar localmente:

docker run -it -p 7007:7007 backstage

Para nosso exemplo, eu vou adicionar um Github Action que faz esse processo de construção aqui. Não vou emburacar no processo de deploy pois cada ecossistema/empresa tem uma maneira diferente de fazer deploy (quem sabe no futuro a gente vem falar só disso?).

Importante se atentar ao build da imagem que precisa usar o buildkit.

Finalizando

O movimento inicial é normalmente o mais complicado. Muita coisa nova, muita coisa confusa e difícil de compreender como encaixa tudo junto. Esse artigo tem como objetivo dar um pontapé e muitas coisas dele estão descritas na documentação oficial.

“Nossa Rogerio, então porque escrever ele?”

Dois motivos: Nem todo mundo sabe inglês e para organizar uma ideia.

O inglês eu nem preciso explicar, sai da sua bolha.

A organização da ideia vai te ajudar para ir do ponto ZERO até uma imagem funcional do Backstage, o que já é um SUPER adianto para quem não tem nem ideia por onde começar. Eu explorei detalhes que me deram dor de cabeça quando eu comecei (e as vezes passam despercebidos) e deixei um commit de refêrencia que fica fácil de você comparar com o seu resultado.

Fica a vontade para me contar suas dúvidas e dificuldades, mais para frente eu trago outros artigos abordando alguns detalhes do que eu falei por aqui hoje.