Merge remote-tracking branch 'origin/master' into blam/move-scaffolder-to-integrations

This commit is contained in:
Johan Haals
2021-01-18 16:53:54 +01:00
290 changed files with 7550 additions and 2206 deletions
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-lighthouse': patch
---
Strip trailing slash from url when creating a new audit. This change prevents duplicate audits from being displayed in the audit list.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-kubernetes-backend': patch
---
Support HTTP 400 Bad Request from Kubernetes API
+25
View File
@@ -0,0 +1,25 @@
---
'@backstage/backend-common': patch
'@backstage/integration': patch
---
Add support for GitHub Apps authentication for backend plugins.
`GithubCredentialsProvider` requests and caches GitHub credentials based on a repository or organization url.
The `GithubCredentialsProvider` class should be considered stateful since tokens will be cached internally.
Consecutive calls to get credentials will return the same token, tokens older than 50 minutes will be considered expired and reissued.
`GithubCredentialsProvider` will default to the configured access token if no GitHub Apps are configured.
More information on how to create and configure a GitHub App to use with backstage can be found in the documentation.
Usage:
```javascript
const credentialsProvider = new GithubCredentialsProvider(config);
const { token, headers } = await credentialsProvider.getCredentials({
url: 'https://github.com/',
});
```
Updates `GithubUrlReader` to use the `GithubCredentialsProvider`.
@@ -0,0 +1,5 @@
---
'@backstage/plugin-cost-insights': patch
---
bug(cost-insights): Remove entity count when none present
-6
View File
@@ -1,6 +0,0 @@
---
'@backstage/plugin-kubernetes': patch
'@backstage/plugin-kubernetes-backend': patch
---
Revamped Kubernetes UI and added error reporting/detection
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/integration': minor
---
Build out the `ScmIntegrations` class, as well as the individual `*Integration` classes
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-jenkins': patch
---
Handle missing ObjectMetadataAction in Jenkins API
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/techdocs-common': patch
---
Fix bug where binary files (`png`, etc.) could not load when using AWS or GCS publisher.
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/plugin-catalog': patch
---
Display the owner, system, and domain as links to the entity pages in the about card.
Only display fields in the about card that are applicable to the entity kind.
+19
View File
@@ -0,0 +1,19 @@
---
'@backstage/create-app': patch
---
fix routing and config for user-settings plugin
To make the corresponding change in your local app, add the following in your App.tsx
```
import { Router as SettingsRouter } from '@backstage/plugin-user-settings';
...
<Route path="/settings" element={<SettingsRouter />} />
```
and the following to your plugins.ts:
```
export { plugin as UserSettings } from '@backstage/plugin-user-settings';
```
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/cli': patch
---
Append `-credentials.yaml` to credentials file generated by `backstage-cli create-github-app` and display warning about sensitive contents.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-backend': patch
---
Refuse to remove the bootstrap location
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-catalog-import': patch
---
Remove dependency to `@backstage/plugin-catalog-backend`.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/core': patch
---
Fix issue where `SidebarItem` with `onClick` and without `to` renders an inaccessible div. It now renders a button.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/cli': patch
---
Added experimental `create-github-app` command.
+12
View File
@@ -0,0 +1,12 @@
---
'@backstage/create-app': patch
---
Add `*-credentials.yaml` to gitignore to prevent accidental commits of sensitive credential information.
To apply this change to an existing installation, add these lines to your `.gitignore`
```gitignore
# Sensitive credentials
*-credentials.yaml
```
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-backend': patch
---
Reduce log noise on locations refresh
+53
View File
@@ -0,0 +1,53 @@
---
'@backstage/create-app': patch
---
use `fromConfig` for all scaffolder helpers, and use the url protocol for app-config location entries.
To apply this change to your local installation, replace the contents of your `packages/backend/src/plugins/scaffolder.ts` with the following contents:
```ts
import {
CookieCutter,
createRouter,
Preparers,
Publishers,
CreateReactAppTemplater,
Templaters,
CatalogEntityClient,
} from '@backstage/plugin-scaffolder-backend';
import { SingleHostDiscovery } from '@backstage/backend-common';
import type { PluginEnvironment } from '../types';
import Docker from 'dockerode';
export default async function createPlugin({
logger,
config,
}: PluginEnvironment) {
const cookiecutterTemplater = new CookieCutter();
const craTemplater = new CreateReactAppTemplater();
const templaters = new Templaters();
templaters.register('cookiecutter', cookiecutterTemplater);
templaters.register('cra', craTemplater);
const preparers = await Preparers.fromConfig(config, { logger });
const publishers = await Publishers.fromConfig(config, { logger });
const dockerClient = new Docker();
const discovery = SingleHostDiscovery.fromConfig(config);
const entityClient = new CatalogEntityClient({ discovery });
return await createRouter({
preparers,
templaters,
publishers,
logger,
config,
dockerClient,
entityClient,
});
}
```
This will ensure that the `scaffolder-backend` package can add handlers for the `url` protocol which is becoming the standard when registering entities in the `catalog`
+9
View File
@@ -0,0 +1,9 @@
---
'@backstage/create-app': patch
---
Remove the `@types/helmet` dev dependency from the app template. This
dependency is now unused as the package `helmet` brings its own types.
To update your existing app, simply remove the `@types/helmet` dependency from
the `package.json` of your backend package.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-backend': patch
---
Update the @azure/msal-node dependency to 1.0.0-beta.3.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-scaffolder-backend': patch
---
Export all preparers and publishers properly
+1
View File
@@ -13,3 +13,4 @@
/plugins/techdocs-backend @backstage/techdocs-core
/packages/techdocs-common @backstage/techdocs-core
/.changeset/cost-insights-* @backstage/silver-lining
/.changeset/techdocs-* @backstage/techdocs-core
+15 -8
View File
@@ -8,6 +8,8 @@ apis
args
asciidoc
async
Autoscaling
autoscaling
Avro
backrub
Balachandran
@@ -68,6 +70,7 @@ Ek
env
Env
eslint
Expedia
facto
failover
Figma
@@ -75,21 +78,21 @@ Firekube
Fiverr
freben
Fredrik
github
Georgoulas
gitbeaker
GitHub
gitlab
GitLab
Grafana
graphql
GraphQL
graphviz
Gustavsson
Hackathons
haproxy
Henneke
heroku
Heroku
horizontalpodautoscalers
Hostname
html
http
https
Iain
@@ -98,8 +101,8 @@ incentivised
inlined
inlinehilite
interop
javascript
Javascript
Ioannis
JavaScript
jq
js
json
@@ -108,6 +111,7 @@ Kaewkasi
Knex
kubectl
kubernetes
Kumar
learnings
lerna
Lerna
@@ -142,10 +146,9 @@ noop
npm
nvarchar
nvm
oauth
OAuth
octokit
oidc
okta
Okta
Oldsberg
onboarding
@@ -184,6 +187,8 @@ rollbar
Rollbar
Rollup
Rosaceae
routable
Routable
rst
rsync
rugvip
@@ -196,6 +201,7 @@ semlas
semver
Serverless
Sinon
Sneha
Snyk
sourcemaps
sparklines
@@ -234,6 +240,7 @@ transpiled
transpilation
Tuite
ui
unmanaged
untracked
upvote
url
+14 -1
View File
@@ -8,6 +8,9 @@ jobs:
build:
runs-on: ubuntu-latest
outputs:
needs_release: ${{ steps.release_check.outputs.needs_release }}
strategy:
matrix:
node-version: [12.x, 14.x]
@@ -47,6 +50,15 @@ jobs:
run: yarn install --frozen-lockfile
# End of yarn setup
- name: Fetch previous commit for release check
run: git fetch origin '${{ github.event.before }}'
- name: Check if release
id: release_check
run: node scripts/check-if-release.js
env:
COMMIT_SHA_BEFORE: '${{ github.event.before }}'
- name: validate config
run: yarn backstage-cli config:check
@@ -82,9 +94,10 @@ jobs:
# We can't re-use the output from the above step, but we'll have a guaranteed node_modules cache and
# only run the build steps that are necessary for publishing
release:
if: contains(github.event.commits.*.author.username, 'github-actions[bot]') && contains(github.event.head_commit.message, 'from backstage/changeset-release/master')
needs: build
if: needs.build.outputs.needs_release == 'true'
runs-on: ubuntu-latest
strategy:
+4 -1
View File
@@ -13,7 +13,7 @@ jobs:
strategy:
matrix:
node-version: [12.x, 14.x]
node-version: [14.x]
env:
CI: true
@@ -27,6 +27,9 @@ jobs:
with:
node-version: ${{ matrix.node-version }}
- name: verify doc links
run: node scripts/verify-links.js
# Skip caching of microsite dependencies, it keeps the global cache size
# smaller, which make Windows builds a lot faster for the rest of the project.
- name: yarn install
@@ -17,7 +17,7 @@ jobs:
strategy:
matrix:
node-version: [12.x]
node-version: [14.x]
env:
CI: true
@@ -55,7 +55,7 @@ jobs:
run: ls microsite/build/backstage && ls microsite/build/backstage/storybook
- name: Deploy both microsite and storybook to gh-pages
uses: JamesIves/github-pages-deploy-action@3.4.2
uses: JamesIves/github-pages-deploy-action@3.7.1
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BRANCH: gh-pages
+6 -4
View File
@@ -9,18 +9,17 @@ on:
pull_request:
types: [opened, reopened, labeled, edited]
env:
MY_GITHUB_TOKEN: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }}
jobs:
assign_issue_or_pr_to_project:
runs-on: ubuntu-latest
name: Triage
if: ${{ env.MY_GITHUB_TOKEN }} != null
env:
MY_GITHUB_TOKEN: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }}
steps:
- name: Assign new issue to Incoming based on its title.
uses: srggrs/assign-one-project-github-action@1.2.0
if: |
env.MY_GITHUB_TOKEN != null &&
contains(github.event.issue.title, 'TechDocs') ||
contains(github.event.issue.title, 'techdocs') ||
contains(github.event.issue.title, 'Techdocs')
@@ -31,6 +30,7 @@ jobs:
- name: Assign new issue to Incoming based on its label.
uses: srggrs/assign-one-project-github-action@1.2.0
if: |
env.MY_GITHUB_TOKEN != null &&
contains(github.event.issue.labels.*.name, 'docs-like-code')
with:
project: 'https://github.com/orgs/backstage/projects/1'
@@ -39,6 +39,7 @@ jobs:
- name: Assign new PR to Incoming based on its title.
uses: srggrs/assign-one-project-github-action@1.2.0
if: |
env.MY_GITHUB_TOKEN != null &&
contains(github.event.pull_request.title, 'TechDocs') ||
contains(github.event.pull_request.title, 'techdocs') ||
contains(github.event.pull_request.title, 'Techdocs')
@@ -49,6 +50,7 @@ jobs:
- name: Assign new PR to Incoming based on its label.
uses: srggrs/assign-one-project-github-action@1.2.0
if: |
env.MY_GITHUB_TOKEN != null &&
contains(github.event.pull_request.labels.*.name, 'docs-like-code')
with:
project: 'https://github.com/orgs/backstage/projects/1'
+3
View File
@@ -130,3 +130,6 @@ site
# Local configuration files
*.local.yaml
# Sensitive credentials
*-credentials.yaml
+21 -19
View File
@@ -1,19 +1,21 @@
| Organization | Contact | Description of Use |
| -------------------------------------------- | ------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
| [Spotify](https://www.spotify.com) | [@leemills83](https://github.com/leemills83) | Main interface towards all of Spotify's infrastructure and technical documentation. |
| [bol.com](https://www.bol.com) | [@RoyJacobs](https://github.com/RoyJacobs) | Initial work being done to unify platform tooling. |
| [DFDS](https://www.dfds.com) | [@carlsendk](https://github.com/carlsendk) | V2 self-service platform. |
| [Roadie](https://roadie.io) | [@dtuite](https://github.com/dtuite) | Hosted, managed Backstage with easy set-up |
| [Roku](https://www.roku.com) | [@timurista](https://github.com/timurista) | Initial work on Cloud engineering service platform. |
| [SDA SE](https://sda.se) | [@Fox32](https://github.com/Fox32) | Central place for developing and sharing services in our insurance ecosystem. |
| [H-E-B](https://www.heb.com) | [@german-j-rodriguez](https://github.com/german-j-rodriguez) | Initial work on Engineering Portal service platform. |
| [American Airlines](https://www.aa.com) | [@paulpach](https://github.com/paulpach) | Central place for developers to develop and maintain applications |
| [Kiwi.com](https://kiwi.com) | [@aexvir](https://github.com/aexvir) | Replacing the frontend of [The Zoo](https://github.com/kiwicom/the-zoo), their service registry. |
| [Voi](https://www.voiscooters.com/) | [@K-Phoen](https://github.com/K-Phoen) | Developer portal, main gateway to our infrastructure, documentation and internal tooling. |
| [Talkdesk](https://www.talkdesk.com) | [@jaime-talkdesk](https://github.com/jaime-talkdesk) | Initial work for Engineering Portal and Self Provisioning to R&D |
| [Wealthsimple](https://www.wealthsimple.com) | [@andrewthauer](https://github.com/andrewthauer) | Developer portal, service catalog, documentation and tooling |
| [Grab](https://www.grab.com) | [@althafh](https://github.com/althafh) | Initial work as a unified interface for all of Grab's internal tooling |
| [Telenor Sweden](https://www.telenor.se) | [@O5ten](https://github.com/O5ten) | Building a developer portal for scaffolding projects towards our unified build environment and microservice stacks |
| [Fiverr](https://www.fiverr.com) | [@nirga](https://github.com/nirga) | Unifying separate tools that developers are using today (i.e. monitoring, dead letter queues management, etc.) into a single platform. |
| [Zalando SE](https://www.zalando.de) | [@leviferreira](https://github.com/leviferreira) | Building V2 of the Internal Development Portal. |
| [LegalZoom](https://legalzoom.com) | [@backjo](https://github.com/backjo) | Developer portal - hub for all engineering projects and metadata. |
| Organization | Contact | Description of Use |
| --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| [Spotify](https://www.spotify.com) | [@leemills83](https://github.com/leemills83) | Main interface towards all of Spotify's infrastructure and technical documentation. |
| [bol.com](https://www.bol.com) | [@RoyJacobs](https://github.com/RoyJacobs) | Initial work being done to unify platform tooling. |
| [DFDS](https://www.dfds.com) | [@carlsendk](https://github.com/carlsendk) | V2 self-service platform. |
| [Roadie](https://roadie.io) | [@dtuite](https://github.com/dtuite) | Hosted, managed Backstage with easy set-up |
| [Roku](https://www.roku.com) | [@timurista](https://github.com/timurista) | Initial work on Cloud engineering service platform. |
| [SDA SE](https://sda.se) | [@Fox32](https://github.com/Fox32) | Central place for developing and sharing services in our insurance ecosystem. |
| [H-E-B](https://www.heb.com) | [@german-j-rodriguez](https://github.com/german-j-rodriguez) | Initial work on Engineering Portal service platform. |
| [American Airlines](https://www.aa.com) | [@paulpach](https://github.com/paulpach) | Central place for developers to develop and maintain applications |
| [Kiwi.com](https://kiwi.com) | [@aexvir](https://github.com/aexvir) | Replacing the frontend of [The Zoo](https://github.com/kiwicom/the-zoo), their service registry. |
| [Voi](https://www.voiscooters.com/) | [@K-Phoen](https://github.com/K-Phoen) | Developer portal, main gateway to our infrastructure, documentation and internal tooling. |
| [Talkdesk](https://www.talkdesk.com) | [@jaime-talkdesk](https://github.com/jaime-talkdesk) | Initial work for Engineering Portal and Self Provisioning to R&D |
| [Wealthsimple](https://www.wealthsimple.com) | [@andrewthauer](https://github.com/andrewthauer) | Developer portal, service catalog, documentation and tooling |
| [Grab](https://www.grab.com) | [@althafh](https://github.com/althafh) | Initial work as a unified interface for all of Grab's internal tooling |
| [Telenor Sweden](https://www.telenor.se) | [@O5ten](https://github.com/O5ten) | Building a developer portal for scaffolding projects towards our unified build environment and microservice stacks |
| [Fiverr](https://www.fiverr.com) | [@nirga](https://github.com/nirga) | Unifying separate tools that developers are using today (i.e. monitoring, dead letter queues management, etc.) into a single platform. |
| [Zalando SE](https://www.zalando.de) | [@leviferreira](https://github.com/leviferreira) | Building V2 of the Internal Development Portal. |
| [LegalZoom](https://legalzoom.com) | [@backjo](https://github.com/backjo) | Developer portal - hub for all engineering projects and metadata. |
| [Expedia Group](https://www.expediagroup.com) | [Mike Turner](mailto:miturner@expediagroup.com), [Sneha Kumar](mailto:snkumar@expediagroup.com), [@guillermomanzo](https://github.com/guillermomanzo) | EG Common Developer Toolkit |
| [Paddle.com](https://paddle.com) | [Ioannis Georgoulas](https://github.com/geototti21) | Developer portal (Tech Docs, Service Catalog, Internal Tooling), we use vanilla Backstage FE and custom BE implementation in Go |
+22 -3
View File
@@ -127,7 +127,16 @@ integrations:
catalog:
rules:
- allow: [Component, API, Group, User, Template, Location]
- allow:
- Component
- API
- Resource
- Group
- User
- Template
- System
- Domain
- Location
processors:
githubOrg:
@@ -178,12 +187,21 @@ catalog:
# Example component for github-actions
- type: url
target: https://github.com/backstage/backstage/blob/master/plugins/github-actions/examples/sample.yaml
# Example component for techdocs
# Example component for TechDocs
- type: url
target: https://github.com/backstage/backstage/blob/master/plugins/techdocs-backend/examples/documented-component/documented-component.yaml
target: https://github.com/backstage/backstage/blob/master/plugins/techdocs-backend/examples/documented-component/catalog-info.yaml
# Backstage example APIs
- type: url
target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml
# Backstage example resources
- type: url
target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml
# Backstage example systems
- type: url
target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-systems.yaml
# Backstage example domains
- type: url
target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-domains.yaml
# Backstage example templates
- type: url
target: https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend/sample-templates/all-templates.yaml
@@ -213,6 +231,7 @@ scaffolder:
$env: BITBUCKET_USERNAME
token:
$env: BITBUCKET_TOKEN
auth:
environment: development
### Providing an auth.session.secret will enable session support in the auth-backend
+1 -1
View File
@@ -6,7 +6,7 @@ metadata:
Backstage is an open-source developer portal that puts the developer experience first.
annotations:
github.com/project-slug: backstage/backstage
backstage.io/techdocs-ref: github:https://github.com/backstage/backstage.git
backstage.io/techdocs-ref: url:https://github.com/backstage/backstage/tree/master
lighthouse.com/website-url: https://backstage.io
spec:
type: library
@@ -8,8 +8,6 @@ FROM nginx:mainline
# This dockerfile requires the app to be built on the host first, as it
# simply copies in the build output into the image.
# The safest way to build this image is to use `yarn docker-build:app`
RUN apt-get update && apt-get -y install jq && rm -rf /var/lib/apt/lists/*
COPY packages/app/dist /usr/share/nginx/html
@@ -14,8 +14,8 @@ import {
HeaderLabel,
SupportButton,
identityApiRef,
useApi,
} from '@backstage/core';
import { useApi } from '@backstage/core-api';
import ExampleFetchComponent from '../ExampleFetchComponent';
const ExampleComponent = () => {
@@ -11,8 +11,8 @@ import {
TableColumn,
Progress,
githubAuthApiRef,
useApi,
} from '@backstage/core';
import { useApi } from '@backstage/core-api';
import { graphql } from '@octokit/graphql';
const query = `{
+541
View File
@@ -0,0 +1,541 @@
<svg host="65bd71144e" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="789px" height="766px" viewBox="-0.5 -0.5 789 766" content="&lt;mxfile host=&quot;bd2205bb-07f8-4b61-b1c1-5174fe4ebe37&quot; modified=&quot;2021-01-14T13:46:42.842Z&quot; agent=&quot;5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Code/1.52.0 Chrome/83.0.4103.122 Electron/9.3.5 Safari/537.36&quot; etag=&quot;jW9IV2PM6529z9FP4d6-&quot; version=&quot;13.10.0&quot; type=&quot;embed&quot;&gt;&lt;diagram id=&quot;AOZgdlUmH_6GT6Gt5u4e&quot; name=&quot;Page-1&quot;&gt;7V1bc6M4Fv41rpp5CMVNAh4TJ5ntre7qbGdqdvqRYMWmG4MXcGLvrx8JSYAuNtgG59JJV3VASALOd3R0zqcjMnGmy80febhafMlmKJnY5mwzca4ntu3ZPv6fFGxpAXQALZjn8YwWWU3Bffx/xApNVrqOZ6gQKpZZlpTxSiyMsjRFUSmUhXmePYvVHrNEvOsqnCOl4D4KE7X0v/GsXNBSH5hN+b9QPF/wO1smu/IQRj/nebZO2f0mtvNY/dDLy5D3xeoXi3CWPbeKnJuJM82zrKRHy80UJUS0XGy03e2Oq/Vz5ygt+zSALm3xFCZrxB+5erByy4XxvIhLdL8KI3L+jPGeOFeLcpngMwsfhsWKQvAYbxDu9uoxTpJplmR51dyxral5CXB5UebZT9S6YpoQAp+0yNKyVQ6m5B8uZ8+G8hJtdr6gVYsNayPKlqjMt7gKawACpnlME90goOfPDa6Wy9BYtDHlhSHTpXnddyNPfMBEukO8zjsXrwteUryepUgTzfBAZqdZXi6yeZaGyU1TelWNTiLJaxOfNXU+Z9mKyfwHKssts0rhusxERNAmLv8mzQ0PsNPvrUvXG9Z1dbJlJ0UZ5uUlMU24IM1SxMtuY/J6tEE64zWiJCyKOKKFrIp1AshELPshxlLM1nnEanlMcfETzhGr5us1IUdJWMZPYvc6VFnTuyzGN641CHKjuuVzBxC7oI/AWjW6gSUVblvVVqRCsfs+ninfx5RUjfbYKF79jr100Q1+saFucaDOMtTXf20vp9HzZuluzOW/s23w14/igrsZ+2QuDvcuBJJ4nuLjBD2WI8hfM+gUSHrL37FcRf62P5b8gf3O1Vt2FF6DegONzGFCNHMWP+HDeVm9Oy16yOUSfFOh3nsdGTVU3AnxXUOHnqVBLxgNPJ1n/QGeatbEadlRcbP5yBRGnTcAcO7pRm2PzFpSZg7fLCwWFWbWyLZMHA9QI1Oos2TOADLtMSXjoHdFDqNtEmMlzu1uDX6g6v75oS6oA+2v6xJ3gzge1Gu3wGvTcwCB4TsiLhpggG84rgqND05Hxla1/XK1wgV3WJYVIXI5IU4xDJdE+pUV+patS1S9OntA+L91xsxTgcI8WrSLTJSgJRORdyX2ZBhG1fttXUwNm3etLZZ1Bku5FFUiRxjr8KGqQGwgCdQ4+i2bF+HnQRjPKwJVHIXJJbuwjGezKiBkkQO+DbiagGv90BV1hhWKRnhE5XEk30TVG8fWDGh7gPFsaygMt+K5cPiKlca+XSXreZxeUHW4IFdwzPoBoOS8i9OcZWpsss4/GQRC1Y/8Wi6wTPHQr7ArVDfktz9RtLjOInwJ929OwzJMsjluXp3elNHvwyHM3JK3jK/igro+YYcUF1Q37Q4x69rwEG+TCGJC2HMui8aE13R1y6oXnJ/nZVb7cg9Hlt7v1/RvZc3AqqELTrSD3/K8WotO0g6NU7bHhn/YbhFBS0LQ1oeXurE9hPnWhpeuo6B0RuqbHxPe2zQ4C65nvrHg823diJzQVpQ8J+dNu+qsocwHG65taht4tKxNbQPmcks0+alk98EcdSAGCI4XtBWls74V2JJitThttbVMKEJLsTZUcAr53tmX7XpiRztY/COIdj3borLv3MW54g4pc3bwwTmdm3dhAmX31Q56cs+j2T/HUgC/Z5OXeZPOCSGAcb5JwgILneJ6nyU5OwrDe3qEysj4AFsC2wuMoP3jCth7fJ5rY++cFXs1JFWx/4SRmOfYWGdkxH8Ot8QYfOC8Z1C7nmto3JpgJGi5ypzZg+HeSO1vMB8GdrsjrRX8Zn2+vYgvZAcILpLXw0e6Q3mMpUj0akz/hw/ftv/jjeP/KG6CvIYJLM+wRlrwd2XSjDlLu1wYuT5gQunnTMlvZpuB0c+XGsoFAi87niTv/qDRtCsfRhtt2PuH0lhRg6sZNc4oo+ZQRfel+NSC3l5Fl+sDb08mjNpa5jMss5eaqyGDPN4OsAVHjJnv0AXbNfzx9Ox9uUfbfJr/jC4cHW0mDaIO7ul1kU0StB7oO6PLKB4zo+tFfHpe1KsSMZQlrMhXlyQyxGq1Vrz8Zu9FvLIG6xZKtAoMgTzfDidj3Zr2MUw65bqxAEzSnbOTIX/D+NUZ5Rw/rQnS5lGNNUSALgmbSr5Yhakga44VkdAFDfzIorhn2KsN/l3JyCRh4wULBcnVatlBgflvfCVdLx8qEip7JLfhQSqOMtdJWbTwpw+yA/+3F6beVj8DWQTTFzTK185p5lhRqqfAcdYU8/4Z5k1k2zjf3wXf+6S49lyZ544m85xmxJzgV/feUABeFO1D4pwW3Ca0xeArCLwO0HcQDgftUWA1mg0Kp4RfQ6gJTec7+6KNJZgnHhUOta9AQ3Z2zl7MHSFzUz5/+K3KBzQp4S0d/15PatKMFxw8302z5QprRNp/XnvT+eJ1Oh3HHUBlVoJQ46fCAczUi05KRy3+SnSrPRTb+nq2TDkahgiaeh0a1yI50lYGx7fb2tVZ34L+oCbMU+OnO4xVnfGZPhTkF1/DkROJ/kT5krx9tYaLgcUTliZhjV4uFyhVr31D5TonpvIbd7w1o+dz+ICSiTavqNuPlh1y1a9OSPdXddZyS+9YnHiEtWLbn9l9J7XD29ZUait2mjHTsHzGF9brQPTsRKr/AgqdAkOm9rLHxwKdyurxQTdQaF4vI17efeobnfNsdiz0MElQks3zcElUoOVdCddabldXYls18903gd2rmgEt2czwPWLtuEyXlgwHiMtc3d6ol+VNTttdedw2mCFCXA++pDchBj3Hxjz16tO54x2iEuwdgzM6Gy5UnY1XEf4Alpgz2Lbq3YnNH+HPSxp/VzT+buDrkmLHioDAyxI14pcfajt1EC/XZbLeEhsDNGwMOBdpt4ffZz5gJ79vmWS8K95gtECzdUII/HfoCkoc/UnWAJq+IacFOLoNMCwSFeyBO4QOdMYBnToQ6FTgj5BlEV9n0XopWfdTFu2Gt+6D4ukEEp4Wl3B7r6qlggmGALNHVsZb3UN8WpDgBYYlBuuOG2jXVw1fs4sYKDlpR8Hjv5V44U1NobptKXAIj15x2YGt32YyQtYVf6tT5mZLZ5ix4LEMkM4yN7zfV2K5v6AynIVl+EsYbuBLnAw25Bq3XLdVwx+ASoC6bKQBpuGKjjNv0tmK6mTvvabvCVs3UCdlzQckRpqUoY5wGwDa/6xR9ZJ3eRahoojTeV8w1bSYIfl6KaWmxfNYmq+3DIozcA0QiEBrxvBY+zJgZ7bbcUB/wvP/5gPoAz85NtamOqhLyz0J5cNWWt6HNgz6gaAeX6odSxu8weftw7ThfUwC5/6w7lgzgNdJqB0Ebu2u8YIvYVp9dcJkRqBG+kFpUnt40xxV7j4h7r+hZfZUH68S4s4JcQDLFcDdx1idRnMOxaWfV8UUqAQO4PstzxAG8HjyvAzBcClDvRf1mgUBu3+i7uQofsA7lU4/anUPmKIW7f1qstoaQm3rLqKh8zGg3NFweyu9Tib5IONHlgXjBNU26WaDIvpJPT7nPeYZSW/al+X0K1kuV2IwgK1xgzQxrn94jItPm7+0QFWl+WsWzs0/&lt;/diagram&gt;&lt;/mxfile&gt;" style="background-color: rgb(255, 255, 255);">
<defs/>
<g>
<rect x="560" y="484" width="140" height="140" fill="#21c0a5" stroke="#006658" pointer-events="all"/>
<rect x="420" y="484" width="140" height="140" fill="#21c0a5" stroke="#006658" pointer-events="all"/>
<path d="M 664.5 595 L 664.5 705 L 595.54 705" fill="none" stroke="#006658" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 590.29 705 L 597.29 701.5 L 595.54 705 L 597.29 708.5 Z" fill="#006658" stroke="#006658" stroke-miterlimit="10" pointer-events="all"/>
<rect x="420" y="110" width="140" height="140" fill="#21c0a5" stroke="#006658" pointer-events="all"/>
<rect x="420" y="299" width="280" height="140" fill="#21c0a5" stroke="#006658" pointer-events="all"/>
<rect x="560" y="110" width="140" height="140" fill="#21c0a5" stroke="#006658" pointer-events="all"/>
<rect x="90" y="469.25" width="110" height="90" fill="#21c0a5" stroke="#006658" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 108px; height: 1px; padding-top: 514px; margin-left: 92px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #5C5C5C; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
<div>
<br/>
</div>
</div>
</div>
</div>
</foreignObject>
<text x="92" y="518" fill="#5C5C5C" font-family="Helvetica" font-size="12px">
&#xa;
</text>
</switch>
</g>
<rect x="5" y="20" width="295" height="170" fill="#21c0a5" stroke="#006658" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 293px; height: 1px; padding-top: 105px; margin-left: 7px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #5C5C5C; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
<div>
<br/>
</div>
</div>
</div>
</div>
</foreignObject>
<text x="7" y="109" fill="#5C5C5C" font-family="Helvetica" font-size="12px">
&#xa;
</text>
</switch>
</g>
<rect x="20" y="50" width="260" height="130" fill="none" stroke="#006658" stroke-dasharray="3 3" pointer-events="all"/>
<path d="M 530.83 665 C 530.83 656.72 543.89 650 560 650 C 567.74 650 575.16 651.58 580.63 654.39 C 586.1 657.21 589.17 661.02 589.17 665 L 589.17 720 C 589.17 723.98 586.1 727.79 580.63 730.61 C 575.16 733.42 567.74 735 560 735 C 543.89 735 530.83 728.28 530.83 720 Z" fill="#21c0a5" stroke="#006658" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 589.17 665 C 589.17 668.98 586.1 672.79 580.63 675.61 C 575.16 678.42 567.74 680 560 680 C 543.89 680 530.83 673.28 530.83 665" fill="none" stroke="#006658" stroke-miterlimit="10" pointer-events="all"/>
<rect x="0" y="0" width="320" height="20" fill="none" stroke="none" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 10px; margin-left: 160px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #5C5C5C; line-height: 1.2; pointer-events: all; white-space: nowrap; ">
App Package: &lt;Route path="/search" element={&lt;... /&gt;} /&gt;
</div>
</div>
</div>
</foreignObject>
<text x="160" y="14" fill="#5C5C5C" font-family="Helvetica" font-size="12px" text-anchor="middle">
App Package: &lt;Route path="/search" element={&lt;... /&gt;}...
</text>
</switch>
</g>
<rect x="415" y="90" width="210" height="20" fill="none" stroke="none" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 100px; margin-left: 520px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #5C5C5C; line-height: 1.2; pointer-events: all; white-space: nowrap; ">
@backstage/plugin-search-backend
</div>
</div>
</div>
</foreignObject>
<text x="520" y="104" fill="#5C5C5C" font-family="Helvetica" font-size="12px" text-anchor="middle">
@backstage/plugin-search-backend
</text>
</switch>
</g>
<rect x="90" y="433.75" width="160" height="30" fill="none" stroke="none" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 1px; height: 1px; padding-top: 449px; margin-left: 92px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #5C5C5C; line-height: 1.2; pointer-events: all; white-space: nowrap; ">
Other Plugins
<br/>
(TechDocs, Catalogue, Etc)
</div>
</div>
</div>
</foreignObject>
<text x="92" y="452" fill="#5C5C5C" font-family="Helvetica" font-size="12px">
Other Plugins...
</text>
</switch>
</g>
<rect x="90" y="229.25" width="210" height="177.75" fill="#21c0a5" stroke="#006658" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 208px; height: 1px; padding-top: 318px; margin-left: 92px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #5C5C5C; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
<div>
<font color="#ffffff" size="1">
<br/>
</font>
</div>
</div>
</div>
</div>
</foreignObject>
<text x="92" y="322" fill="#5C5C5C" font-family="Helvetica" font-size="12px"></text>
</switch>
</g>
<rect x="80" y="209.25" width="160" height="20" fill="none" stroke="none" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 219px; margin-left: 160px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #5C5C5C; line-height: 1.2; pointer-events: all; white-space: nowrap; ">
@backstage/plugin-search
</div>
</div>
</div>
</foreignObject>
<text x="160" y="223" fill="#5C5C5C" font-family="Helvetica" font-size="12px" text-anchor="middle">
@backstage/plugin-search
</text>
</switch>
</g>
<path d="M 630 364.25 L 758 364.3 L 758 177.3 L 661.87 177.25" fill="none" stroke="#006658" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 656.62 177.25 L 663.62 173.75 L 661.87 177.25 L 663.62 180.75 Z" fill="#006658" stroke="#006658" stroke-miterlimit="10" pointer-events="all"/>
<rect x="415" y="279" width="280" height="20" fill="none" stroke="none" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 289px; margin-left: 555px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #5C5C5C; line-height: 1.2; pointer-events: all; white-space: nowrap; ">
Other Backend Plugin (TechDocs, Catalogue, Etc)
</div>
</div>
</div>
</foreignObject>
<text x="555" y="293" fill="#5C5C5C" font-family="Helvetica" font-size="12px" text-anchor="middle">
Other Backend Plugin (TechDocs, Catalogue, Etc)
</text>
</switch>
</g>
<rect x="445" y="745" width="230" height="20" fill="none" stroke="none" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 755px; margin-left: 560px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #5C5C5C; line-height: 1.2; pointer-events: all; white-space: nowrap; ">
Search Engine (Elastic, Solr, SaaS, etc.)
</div>
</div>
</div>
</foreignObject>
<text x="560" y="759" fill="#5C5C5C" font-family="Helvetica" font-size="12px" text-anchor="middle">
Search Engine (Elastic, Solr, SaaS, et...
</text>
</switch>
</g>
<rect x="415" y="459.5" width="190" height="20" fill="none" stroke="none" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 470px; margin-left: 510px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #5C5C5C; line-height: 1.2; pointer-events: all; white-space: nowrap; ">
Search Engine Integration Layer
</div>
</div>
</div>
</foreignObject>
<text x="510" y="473" fill="#5C5C5C" font-family="Helvetica" font-size="12px" text-anchor="middle">
Search Engine Integration Layer
</text>
</switch>
</g>
<path d="M 458.13 178.25 L 400 178.3 L 400 559.8 L 443.63 559.77" fill="none" stroke="#006658" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 463.38 178.25 L 456.38 181.76 L 458.13 178.25 L 456.38 174.76 Z" fill="#006658" stroke="#006658" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 448.88 559.77 L 441.88 563.27 L 443.63 559.77 L 441.88 556.27 Z" fill="#006658" stroke="#006658" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 655.5 151.75 L 780 151.8 L 780 555 L 690.87 555" fill="none" stroke="#006658" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 685.62 555 L 692.62 551.5 L 690.87 555 L 692.62 558.5 Z" fill="#006658" stroke="#006658" stroke-miterlimit="10" pointer-events="all"/>
<rect x="80" y="60.5" width="190" height="10" fill="#21c0a5" stroke="#006658" pointer-events="all"/>
<rect x="30" y="60" width="40" height="70" fill="#21c0a5" stroke="#006658" pointer-events="all"/>
<rect x="80" y="90" width="190" height="65.5" fill="#21c0a5" stroke="#006658" pointer-events="all"/>
<rect x="229" y="160.5" width="40" height="10" fill="#21c0a5" stroke="#006658" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 38px; height: 1px; padding-top: 166px; margin-left: 230px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #5C5C5C; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
<font color="#ffffff">
1 2 3
</font>
</div>
</div>
</div>
</foreignObject>
<text x="249" y="169" fill="#5C5C5C" font-family="Helvetica" font-size="12px" text-anchor="middle">
1 2 3
</text>
</switch>
</g>
<rect x="73" y="70.5" width="100" height="20" fill="none" stroke="none" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 81px; margin-left: 123px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #FFFFFF; line-height: 1.2; pointer-events: all; white-space: nowrap; ">
<span style="font-size: 7.2px ; text-align: left">
X number of search results
</span>
</div>
</div>
</div>
</foreignObject>
<text x="123" y="84" fill="#FFFFFF" font-family="Helvetica" font-size="12px" text-anchor="middle">
X number of sear...
</text>
</switch>
</g>
<path d="M 220 313.37 L 220 330.63" fill="none" stroke="#006658" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 220 308.12 L 223.5 315.12 L 220 313.37 L 216.5 315.12 Z" fill="#006658" stroke="#006658" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 220 335.88 L 216.5 328.88 L 220 330.63 L 223.5 328.88 Z" fill="#006658" stroke="#006658" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 187 257.5 L 36.1 257.5 L 36.12 179.61" fill="none" stroke="#006658" stroke-miterlimit="10" pointer-events="stroke"/>
<rect x="187" y="241" width="66" height="66" fill="#21c0a5" stroke="#006658" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 64px; height: 1px; padding-top: 274px; margin-left: 188px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #5C5C5C; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
<span style="color: rgb(255 , 255 , 255) ; font-size: 9px ; text-align: left">
Components
</span>
</div>
</div>
</div>
</foreignObject>
<text x="220" y="278" fill="#5C5C5C" font-family="Helvetica" font-size="12px" text-anchor="middle">
Components
</text>
</switch>
</g>
<path d="M 276.37 367 L 360 367 L 360 152.8 L 458.13 152.75" fill="none" stroke="#006658" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 271.12 367 L 278.12 363.5 L 276.37 367 L 278.12 370.5 Z" fill="#006658" stroke="#006658" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 463.38 152.75 L 456.38 156.25 L 458.13 152.75 L 456.38 149.25 Z" fill="#006658" stroke="#006658" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 220px; margin-left: 351px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #5C5C5C; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">
Pass Search
<br/>
Term and Filters
<br/>
and then
<br/>
Return Results
</div>
</div>
</div>
</foreignObject>
<text x="351" y="223" fill="#5C5C5C" font-family="Helvetica" font-size="11px" text-anchor="middle">
Pass Search...
</text>
</switch>
</g>
<path d="M 160 397 L 180 337 L 280 337 L 260 397 Z" fill="#21c0a5" stroke="#006658" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 367px; margin-left: 161px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #5C5C5C; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
<font color="#ffffff">
Search API
</font>
</div>
</div>
</div>
</foreignObject>
<text x="220" y="371" fill="#5C5C5C" font-family="Helvetica" font-size="12px" text-anchor="middle">
Search API
</text>
</switch>
</g>
<rect x="5" y="20" width="295" height="20" fill="#21c0a5" stroke="#006658" pointer-events="all"/>
<path d="M 110 516.25 L 36.1 516.3 L 36.12 180" fill="none" stroke="#006658" stroke-miterlimit="10" pointer-events="stroke"/>
<rect x="110" y="483.25" width="66" height="66" fill="#21c0a5" stroke="#006658" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 64px; height: 1px; padding-top: 516px; margin-left: 111px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #5C5C5C; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
<span style="color: rgb(255 , 255 , 255) ; font-size: 9px ; text-align: left">
Components
</span>
</div>
</div>
</div>
</foreignObject>
<text x="143" y="520" fill="#5C5C5C" font-family="Helvetica" font-size="12px" text-anchor="middle">
Components
</text>
</switch>
</g>
<path d="M 635 198.75 L 630 198.8 L 630 190" fill="none" stroke="#006658" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 573.5 239.75 L 593.5 198.75 L 655.5 198.75 L 635.5 239.75 Z" fill="#21c0a5" stroke="#006658" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 80px; height: 1px; padding-top: 219px; margin-left: 575px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #FFFFFF; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
<font style="font-size: 10px">
Scheduler
</font>
</div>
</div>
</div>
</foreignObject>
<text x="615" y="223" fill="#FFFFFF" font-family="Helvetica" font-size="12px" text-anchor="middle">
Scheduler
</text>
</switch>
</g>
<rect x="604.5" y="139" width="51" height="51" fill="#21c0a5" stroke="#006658" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 49px; height: 1px; padding-top: 165px; margin-left: 606px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #FFFFFF; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
<font style="font-size: 9px">
Gather Documents
</font>
</div>
</div>
</div>
</foreignObject>
<text x="630" y="168" fill="#FFFFFF" font-family="Helvetica" font-size="12px" text-anchor="middle">
Gather D...
</text>
</switch>
</g>
<path d="M 444.17 349.5 C 444.17 341.22 453.31 334.5 464.59 334.5 C 470.01 334.5 475.2 336.08 479.03 338.89 C 482.86 341.71 485.01 345.52 485.01 349.5 L 485.01 379 C 485.01 387.28 475.87 394 464.59 394 C 453.31 394 444.17 387.28 444.17 379 Z" fill="#21c0a5" stroke="#006658" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 485.01 349.5 C 485.01 357.78 475.87 364.5 464.59 364.5 C 453.31 364.5 444.17 357.78 444.17 349.5" fill="none" stroke="#006658" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 550 364.25 L 517.5 364.3 L 485.01 364.3" fill="none" stroke="#006658" stroke-miterlimit="10" pointer-events="stroke"/>
<rect x="550" y="324.25" width="80" height="80" fill="#21c0a5" stroke="#006658" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 78px; height: 1px; padding-top: 364px; margin-left: 551px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #FFFFFF; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
<font style="font-size: 11px">
Collate Documents
<br/>
Or Metadata
</font>
</div>
</div>
</div>
</foreignObject>
<text x="590" y="368" fill="#FFFFFF" font-family="Helvetica" font-size="12px" text-anchor="middle">
Collate Docum...
</text>
</switch>
</g>
<rect x="464.5" y="140" width="51" height="51" fill="#21c0a5" stroke="#006658" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 49px; height: 1px; padding-top: 166px; margin-left: 466px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #FFFFFF; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
<font style="font-size: 9px">
API Endpoint
<br/>
</font>
</div>
</div>
</div>
</foreignObject>
<text x="490" y="169" fill="#FFFFFF" font-family="Helvetica" font-size="12px" text-anchor="middle">
API Endp...
</text>
</switch>
</g>
<rect x="419.59" y="110" width="90" height="20" fill="none" stroke="none" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 120px; margin-left: 465px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #FFFFFF; line-height: 1.2; pointer-events: all; white-space: nowrap; ">
<font style="font-size: 9px">
Query Processing
</font>
</div>
</div>
</div>
</foreignObject>
<text x="465" y="123" fill="#FFFFFF" font-family="Helvetica" font-size="11px" text-anchor="middle">
Query Processing
</text>
</switch>
</g>
<rect x="560" y="110" width="80" height="20" fill="none" stroke="none" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 120px; margin-left: 600px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #FFFFFF; line-height: 1.2; pointer-events: all; white-space: nowrap; ">
<font style="font-size: 9px">
Index Processing
</font>
</div>
</div>
</div>
</foreignObject>
<text x="600" y="123" fill="#FFFFFF" font-family="Helvetica" font-size="11px" text-anchor="middle">
Index Processi...
</text>
</switch>
</g>
<rect x="560" y="484" width="80" height="20" fill="none" stroke="none" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 494px; margin-left: 600px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #5C5C5C; line-height: 1.2; pointer-events: all; white-space: nowrap; ">
<font style="font-size: 9px" color="#ffffff">
Index Processing
</font>
</div>
</div>
</div>
</foreignObject>
<text x="600" y="497" fill="#5C5C5C" font-family="Helvetica" font-size="11px" text-anchor="middle">
Index Processi...
</text>
</switch>
</g>
<rect x="420" y="484" width="90" height="20" fill="none" stroke="none" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 494px; margin-left: 465px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #5C5C5C; line-height: 1.2; pointer-events: all; white-space: nowrap; ">
<font style="font-size: 9px" color="#ffffff">
Query Processing
</font>
</div>
</div>
</div>
</foreignObject>
<text x="465" y="497" fill="#5C5C5C" font-family="Helvetica" font-size="11px" text-anchor="middle">
Query Processing
</text>
</switch>
</g>
<rect x="604.5" y="515" width="80" height="80" fill="#21c0a5" stroke="#006658" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 78px; height: 1px; padding-top: 555px; margin-left: 606px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 9px; font-family: Helvetica; color: #5C5C5C; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
<font color="#ffffff">
<b>
Manage Index
</b>
<br/>
Create, Remove, Replace Documents and Indices
</font>
</div>
</div>
</div>
</foreignObject>
<text x="645" y="558" fill="#5C5C5C" font-family="Helvetica" font-size="9px" text-anchor="middle">
Manage Index...
</text>
</switch>
</g>
<path d="M 470.25 601.37 L 470.3 705 L 524.63 705" fill="none" stroke="#006658" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 470.25 596.12 L 473.75 603.12 L 470.25 601.37 L 466.75 603.12 Z" fill="#006658" stroke="#006658" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 529.88 705 L 522.88 708.5 L 524.63 705 L 522.88 701.5 Z" fill="#006658" stroke="#006658" stroke-miterlimit="10" pointer-events="all"/>
<rect x="450" y="514" width="81" height="81" fill="#21c0a5" stroke="#006658" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 79px; height: 1px; padding-top: 555px; margin-left: 451px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 9px; font-family: Helvetica; color: #5C5C5C; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
<font color="#ffffff">
Compile and Execute Query from Term and Filters
</font>
</div>
</div>
</div>
</foreignObject>
<text x="491" y="557" fill="#5C5C5C" font-family="Helvetica" font-size="9px" text-anchor="middle">
Compile and Execut...
</text>
</switch>
</g>
</g>
<switch>
<g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/>
<a transform="translate(0,-5)" xlink:href="https://desk.draw.io/support/solutions/articles/16000042487" target="_blank">
<text text-anchor="middle" font-size="10px" x="50%" y="100%">
Viewer does not support full SVG 1.1
</text>
</a>
</switch>
</svg>

After

Width:  |  Height:  |  Size: 44 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 15 KiB

+2 -3
View File
@@ -1,9 +1,8 @@
---
id: oauth
title: OAuth and OpenID Connect
description: This section describes how Backstage allows plugins to request
OAuth Access Tokens and OpenID Connect ID Tokens on behalf of the user, to be
used for auth to various third party APIs
# prettier-ignore
description: This section describes how Backstage allows plugins to request OAuth Access Tokens and OpenID Connect ID Tokens on behalf of the user, to be used for auth to various third party APIs
---
This section describes how Backstage allows plugins to request OAuth Access
+2 -2
View File
@@ -1,8 +1,8 @@
---
id: figma
title: Figma
description: Documentation on using Figma to build your own plugins for
Backstage
# prettier-ignore
description: Documentation on using Figma to build your own plugins for Backstage
---
We have a [Figma component library](https://www.figma.com/@backstage) that you
@@ -1,11 +1,10 @@
---
id: kubernetes-in-backstage
title: Kubernetes in Backstage
id: overview
title: Kubernetes
sidebar_label: Overview
description: Monitoring Kubernetes based services with the service catalog
---
# Kubernetes in Backstage
Kubernetes in Backstage is a way to monitor your service's current status when
it is deployed on Kubernetes.
@@ -21,8 +20,9 @@ kubernetes:
clusters:
- url: http://127.0.0.1:9999
name: minikube
serviceAccountToken: TOKEN
authProvider: 'serviceAccount'
serviceAccountToken:
$env: K8S_MINIKUBE_TOKEN
- url: http://127.0.0.2:9999
name: gke-cluster-1
authProvider: 'google'
@@ -32,22 +32,19 @@ kubernetes:
This configures how to determine which clusters a component is running in.
Currently, the only valid serviceLocatorMethod is:
Currently, the only valid value is:
### multiTenant
This configuration assumes that all components run on all the provided clusters.
- `multiTenant` - This configuration assumes that all components run on all the
provided clusters.
### clusterLocatorMethods
This is used to determine where to retrieve cluster configuration from.
This is an array used to determine where to retrieve cluster configuration from.
Currently, the only valid cluster locator method is:
`"config"`
This cluster locator method will read cluster information from your app-config
(see below).
- `config` - This cluster locator method will read cluster information from your
app-config (see below).
### clusters
@@ -56,7 +53,7 @@ Used by the `config` cluster locator method to construct Kubernetes clients.
### clusters.\*.url
The base URL to the Kubernetes control plane. Can be found by using the
`Kubernetes master` result from running the `kubectl cluster-info` command.
"Kubernetes master" result from running the `kubectl cluster-info` command.
### clusters.\*.name
@@ -78,7 +75,7 @@ cluster. Valid values are:
The service account token to be used when using the `serviceAccount` auth
provider.
## RBAC
## Role Based Access Control
The current RBAC permissions required are read-only cluster wide, for the
following objects:
@@ -93,7 +90,7 @@ following objects:
## Surfacing your Kubernetes components as part of an entity
There are two ways to surface your kubernetes components as part of an entity.
There are two ways to surface your Kubernetes components as part of an entity.
The label selector takes precedence over the annotation/service id.
### Common `backstage.io/kubernetes-id` label
@@ -101,7 +98,7 @@ The label selector takes precedence over the annotation/service id.
#### Adding the entity annotation
In order for Backstage to detect that an entity has Kubernetes components, the
following annotation should be added to the entity.
following annotation should be added to the entity's `catalog-info.yaml`:
```yaml
annotations:
@@ -111,20 +108,18 @@ annotations:
#### Labeling Kubernetes components
In order for Kubernetes components to show up in the service catalog as a part
of an entity, Kubernetes components must be labeled with the following label:
of an entity, Kubernetes components themselves can have the following label:
```yaml
'backstage.io/kubernetes-id': <ENTITY_NAME>
'backstage.io/kubernetes-id': <BACKSTAGE_ENTITY_NAME>
```
### label selector query annotation
### Label selector query annotation
#### Adding a label selector query annotation
You can write your own custom label selector query that backstage will use to
You can write your own custom label selector query that Backstage will use to
lookup the objects (similar to `kubectl --selector="your query here"`). Review
the documentation
[here](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/)
the
[labels and selectors Kubernetes documentation](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/)
for more info.
```yaml
+100
View File
@@ -0,0 +1,100 @@
---
id: search-overview
title: Search Documentation
sidebar_label: Overview
# prettier-ignore
description: Backstage Search lets you find the right information you are looking for in the Backstage ecosystem.
---
# Backstage Search
## What is it?
Backstage Search lets you find the right information you are looking for in the
Backstage ecosystem.
## Features
- A federated, faceted search, searching across all entities registered in your
Backstage instance.
- A search that lets you plug in your own search engine of choice.
- A standardized search API where you can choose to index other plugins data.
## Project roadmap
| Version | Description |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Backstage Search V.0 ✅ | Search Frontend letting you search through the entities of the software catalog. [See V.0 Use Cases.](#backstage-search-v0) |
| Backstage Search V.1 ⌛ | Basic “out-of-the-box” in-memory indexing process of entities, and their metadata, registered to the Software Catalog. [See V.1 Use Cases.](#backstage-search-v1) |
| Backstage Search V.2 ⌛ | Search Backend responsible for the indexing process of entities, and their metadata, registered to the Software Catalog. [See V.2 Use Cases.](#backstage-search-v2) |
| Backstage Search V.3 ⌛ | Standardized Search API lets you index other plugins data to the search engine of choice. [See V.3 Use Cases.](#backstage-search-v3) |
## Use Cases
#### Backstage Search V.0
- As a software engineer I should be able to navigate to a search page and
search for entities registered in the Software Catalog.
- As a software engineer I should be able to use the search input field in the
sidebar to search for entities registered in the Software Catalog.
- As a software engineer I should be able to see the number of results my search
returned.
- As a software engineer I should be able to filter on metadata (kind,
lifecycle) when Ive performed a search.
- As a software engineer I should be able to hide the filters if I dont need to
use them.
#### Backstage Search V.1
- As a software engineer I should be able to get a match of a search on all
entity metadata (e.g. owner, name, description, kind).
- As an integrator I should not have to plug in any search engine, instead I can
use the out of the box in-memory indexing process to index entities and their
metadata registered in the Software Catalog.
#### Backstage Search V.2
- As an integrator I should be able to spin up an instance of ElasticSearch.
- As an integrator I should be able to define a ElasticSearch cluster in my
app_config.yaml where my data gets indexed to.
more to come...
#### Backstage Search V.3
- As a contributor I should be able to integrate plugin data to the indexing
process of Backstage Search by using the standardized API.
- As a software engineer I should be able to search for all content (for
example, entities, metadata, documentation) in backstage search.
more to come...
## Search Engines Supported
See [Backstage Search Architecture](architecture.md) to get an overview of how
the search engines are used.
| Search Engine | Support Status |
| ------------- | -------------- |
| ElasticSearch | Not yet ❌ |
[Reach out to us](#feedback) if you want to chat about support for more search
engines.
## Tech Stack
| Stack | Location |
| --------------- | ------------------------ |
| Frontend Plugin | @backstage/plugin-search |
| Backend Plugin | ⌛ |
## Feedback
For any questions of feedback, reach out to us in the `#search` channel of our
[Discord chatroom](https://github.com/backstage/backstage#community).
We are still looking for feedback to improve the architecture to fit your
use-case, see
[this open issue](https://github.com/backstage/backstage/issues/4078).
+39
View File
@@ -0,0 +1,39 @@
---
id: architecture
title: Search Architecture
description: Documentation on Search Architecture
---
# Search Architecture
> _This is a proposed architecture which has not been implemented yet. We are
> still looking for feedback to improve the architecture to fit your use-case,
> see [this open issue](https://github.com/backstage/backstage/issues/4078)._
Below you can explore the Search Architecture. Our aim with this architecture is
to support a wide variety of search engines, while providing a simple developer
experience for plugin developers, and a good out-of-the-box experience for
Backstage end-users.
<img data-zoomable src="../../assets/search/architecture.drawio.svg" alt="Search Architecture" />
At a base-level, we want to support the following:
- We aim to enable the capability to search across the entire Backstage
ecosystem by decoupling search from content management.
- We aim to enable the capability to deploy Backstage using any search engine,
by providing an integration and translation layer between the core search
plugin and search engine specific logic that can be extended for different
search engines. We may also introduce the ability to replace the backend API
endpoint with a custom endpoint for simpler customization.
More advanced use-cases we hope to support with this architecture include:
- It should be easy for any plugin to expose new content to search. (e.g. entity
metadata, documentation from TechDocs)
- It should be easy for any plugin to append relevant metadata to existing
content in search. (e.g. location (path) for TechDocs page)
- It should be easy to refine search queries (e.g. ranking, scoring, etc.)
- It should be easy to customize the search UI
- It should be easy to add search functionality to any Backstage plugin or
deployment
@@ -60,7 +60,7 @@ data from. Each entry is a structure with up to four elements:
and raw. If it is not supplied, anonymous access will be used.
- `apiBaseUrl` (optional): If you want to communicate using the APIv3 method
with this provider, specify the base URL for its endpoint here, with no
trailing slash. Specifically when the target is github, you can leave it out
trailing slash. Specifically when the target is GitHub, you can leave it out
to be inferred automatically. For a GitHub Enterprise installation, it is
commonly at `https://api.<host>` or `https://<host>/api/v3`.
- `rawBaseUrl` (optional): If you want to communicate using the raw HTTP method
@@ -60,7 +60,7 @@ software catalog API.
},
"spec": {
"lifecycle": "production",
"owner": "artist-relations@example.com",
"owner": "artist-relations-team",
"type": "website"
}
}
@@ -84,7 +84,7 @@ metadata:
spec:
type: website
lifecycle: production
owner: artist-relations@example.com
owner: artist-relations-team
```
The root fields `apiVersion`, `kind`, `metadata`, and `spec` are part of the
@@ -268,7 +268,7 @@ identical in use to
Their purpose is mainly, but not limited, to reference into external systems.
This could for example be a reference to the git ref the entity was ingested
from, to monitoring and logging systems, to pagerduty schedules, etc. Users may
from, to monitoring and logging systems, to PagerDuty schedules, etc. Users may
add these to descriptor YAML files, but in addition to this automated systems
may also add annotations, either during ingestion into the catalog, or at a
later time.
@@ -381,7 +381,8 @@ metadata:
spec:
type: website
lifecycle: production
owner: artist-relations@example.com
owner: artist-relations-team
system: artist-engagement-portal
providesApis:
- artist-api
```
@@ -427,8 +428,8 @@ The current set of well-known and common values for this field is:
### `spec.owner` [required]
The owner of the component, e.g. `artist-relations@example.com`. This field is
required.
An [entity reference](#string-references) to the owner of the component, e.g.
`artist-relations-team`. This field is required.
In Backstage, the owner of a component is the singular entity (commonly a team)
that bears ultimate responsibility for the component, and has the authority and
@@ -440,25 +441,45 @@ not to be used by automated processes to for example assign authorization in
runtime systems. There may be others that also develop or otherwise touch the
component, but there will always be one ultimate owner.
Apart from being a string, the software catalog leaves the format of this field
open to implementers to choose. Most commonly, it is set to the ID or email of a
group of people in an organizational structure.
| [`kind`](#apiversion-and-kind-required) | Default [`namespace`](#namespace-optional) | Generated [relation](well-known-relations.md) type |
| ------------------------------------------------------ | ------------------------------------------ | ------------------------------------------------------------------------------- |
| [`Group`](#kind-group) (default), [`User`](#kind-user) | Same as this entity, typically `default` | [`ownerOf`, and reverse `ownedBy`](well-known-relations.md#ownedby-and-ownerof) |
### `spec.system` [optional]
An [entity reference](#string-references) to the system that the component
belongs to, e.g. `artist-engagement-portal`. This field is optional.
| [`kind`](#apiversion-and-kind-required) | Default [`namespace`](#namespace-optional) | Generated [relation](well-known-relations.md) type |
| --------------------------------------- | ------------------------------------------ | ----------------------------------------------------------------------------- |
| [`System`](#kind-system) (default) | Same as this entity, typically `default` | [`partOf`, and reverse `hasPart`](well-known-relations.md#partof-and-haspart) |
### `spec.subcomponentOf` [optional]
An [entity reference](#string-references) to another component of which the
component is a part, e.g. `spotify-ios-app`. This field is optional.
| [`kind`](#apiversion-and-kind-required) | Default [`namespace`](#namespace-optional) | Generated [relation](well-known-relations.md) type |
| ---------------------------------------- | ------------------------------------------ | ----------------------------------------------------------------------------- |
| [`Component`](#kind-component) (default) | Same as this entity, typically `default` | [`partOf`, and reverse `hasPart`](well-known-relations.md#partof-and-haspart) |
### `spec.providesApis` [optional]
Links APIs that are provided by the component, e.g. `artist-api`. This field is
optional.
An array of [entity references](#string-references) to the APIs that are
provided by the component, e.g. `artist-api`. This field is optional.
The software catalog expects a list of one or more strings that references the
names of other entities of the `kind` `API`.
| [`kind`](#apiversion-and-kind-required) | Default [`namespace`](#namespace-optional) | Generated [relation](well-known-relations.md) type |
| --------------------------------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------------- |
| [`API`](#kind-api) (default) | Same as this entity, typically `default` | [`providesApi`, and reverse `apiProvidedBy`](well-known-relations.md#providesapi-and-apiprovidedby) |
### `spec.consumesApis` [optional]
Links APIs that are consumed by the component, e.g. `artist-api`. This field is
optional.
An array of [entity references](#string-references) to the APIs that are
consumed by the component, e.g. `artist-api`. This field is optional.
The software catalog expects a list of one or more strings that references the
names of other entities of the `kind` `API`.
| [`kind`](#apiversion-and-kind-required) | Default [`namespace`](#namespace-optional) | Generated [relation](well-known-relations.md) type |
| --------------------------------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------------- |
| [`API`](#kind-api) (default) | Same as this entity, typically `default` | [`consumesApi`, and reverse `apiConsumedBy`](well-known-relations.md#consumesapi-and-apiconsumedby) |
## Kind: Template
@@ -597,7 +618,8 @@ metadata:
spec:
type: openapi
lifecycle: production
owner: artist-relations@example.com
owner: artist-relations-team
system: artist-engagement-portal
definition: |
openapi: "3.0.0"
info:
@@ -663,8 +685,8 @@ The current set of well-known and common values for this field is:
### `spec.owner` [required]
The owner of the API, e.g. `artist-relations@example.com`. This field is
required.
An [entity reference](#string-references) to the owner of the component, e.g.
`artist-relations-team`. This field is required.
In Backstage, the owner of an API is the singular entity (commonly a team) that
bears ultimate responsibility for the API, and has the authority and capability
@@ -676,9 +698,18 @@ processes to for example assign authorization in runtime systems. There may be
others that also develop or otherwise touch the API, but there will always be
one ultimate owner.
Apart from being a string, the software catalog leaves the format of this field
open to implementers to choose. Most commonly, it is set to the ID or email of a
group of people in an organizational structure.
| [`kind`](#apiversion-and-kind-required) | Default [`namespace`](#namespace-optional) | Generated [relation](well-known-relations.md) type |
| ------------------------------------------------------ | ------------------------------------------ | ------------------------------------------------------------------------------- |
| [`Group`](#kind-group) (default), [`User`](#kind-user) | Same as this entity, typically `default` | [`ownerOf`, and reverse `ownedBy`](well-known-relations.md#ownedby-and-ownerof) |
### `spec.system` [optional]
An [entity reference](#string-references) to the system that the API belongs to,
e.g. `artist-engagement-portal`. This field is optional.
| [`kind`](#apiversion-and-kind-required) | Default [`namespace`](#namespace-optional) | Generated [relation](well-known-relations.md) type |
| --------------------------------------- | ------------------------------------------ | ----------------------------------------------------------------------------- |
| [`System`](#kind-system) (default) | Same as this entity, typically `default` | [`partOf`, and reverse `hasPart`](well-known-relations.md#partof-and-haspart) |
### `spec.definition` [required]
@@ -751,11 +782,11 @@ parent; the catalog supports multi-root hierarchies. Groups may however not have
more than one parent.
This field is an
[entity reference](https://backstage.io/docs/features/software-catalog/references),
with the default kind `Group` and the default namespace equal to the same
namespace as the user. Only `Group` entities may be referenced. Most commonly,
this field points to a group in the same namespace, so in those cases it is
sufficient to enter only the `metadata.name` field of that group.
[entity reference](https://backstage.io/docs/features/software-catalog/references).
| [`kind`](#apiversion-and-kind-required) | Default [`namespace`](#namespace-optional) | Generated [relation](well-known-relations.md) type |
| --------------------------------------- | ------------------------------------------ | --------------------------------------------------------------------------------- |
| [`Group`](#kind-group) (default) | Same as this entity, typically `default` | [`childOf`, and reverse `parentOf`](well-known-relations.md#parentof-and-childof) |
### `spec.children` [required]
@@ -765,11 +796,11 @@ no child groups. The items are not guaranteed to be ordered in any particular
way.
The entries of this array are
[entity references](https://backstage.io/docs/features/software-catalog/references),
with the default kind `Group` and the default namespace equal to the same
namespace as the user. Only `Group` entities may be referenced. Most commonly,
these entries point to groups in the same namespace, so in those cases it is
sufficient to enter only the `metadata.name` field of those groups.
[entity references](https://backstage.io/docs/features/software-catalog/references).
| [`kind`](#apiversion-and-kind-required) | Default [`namespace`](#namespace-optional) | Generated [relation](well-known-relations.md) type |
| --------------------------------------- | ------------------------------------------ | ------------------------------------------------------------------------------------- |
| [`Group`](#kind-group) (default) | Same as this entity, typically `default` | [`hasMember`, and reverse `memberOf`](well-known-relations.md#memberof-and-hasmember) |
## Kind: User
@@ -825,23 +856,202 @@ user is not member of any groups. The items are not guaranteed to be ordered in
any particular way.
The entries of this array are
[entity references](https://backstage.io/docs/features/software-catalog/references),
with the default kind `Group` and the default namespace equal to the same
namespace as the user. Only `Group` entities may be referenced. Most commonly,
these entries point to groups in the same namespace, so in those cases it is
sufficient to enter only the `metadata.name` field of those groups.
[entity references](https://backstage.io/docs/features/software-catalog/references).
| [`kind`](#apiversion-and-kind-required) | Default [`namespace`](#namespace-optional) | Generated [relation](well-known-relations.md) type |
| --------------------------------------- | ------------------------------------------ | ------------------------------------------------------------------------------------- |
| [`Group`](#kind-group) (default) | Same as this entity, typically `default` | [`memberOf`, and reverse `hasMember`](well-known-relations.md#memberof-and-hasmember) |
## Kind: Resource
This kind is not yet defined, but is reserved [for future use](system-model.md).
Describes the following entity kind:
| Field | Value |
| ------------ | ----------------------- |
| `apiVersion` | `backstage.io/v1alpha1` |
| `kind` | `Resource` |
A resource describes the infrastructure a system needs to operate, like BigTable
databases, Pub/Sub topics, S3 buckets or CDNs. Modelling them together with
components and systems allows to visualize resource footprint, and create
tooling around them.
Descriptor files for this kind may look as follows.
```yaml
apiVersion: backstage.io/v1alpha1
kind: Resource
metadata:
name: artists-db
description: Stores artist details
spec:
type: database
owner: artist-relations-team
system: artist-engagement-portal
```
In addition to the [common envelope metadata](#common-to-all-kinds-the-metadata)
shape, this kind has the following structure.
### `apiVersion` and `kind` [required]
Exactly equal to `backstage.io/v1alpha1` and `Resource`, respectively.
### `spec.owner` [required]
An [entity reference](#string-references) to the owner of the resource, e.g.
`artist-relations-team`. This field is required.
In Backstage, the owner of a resource is the singular entity (commonly a team)
that bears ultimate responsibility for the resource, and has the authority and
capability to develop and maintain it. They will be the point of contact if
something goes wrong, or if features are to be requested. The main purpose of
this field is for display purposes in Backstage, so that people looking at
catalog items can get an understanding of to whom this resource belongs. It is
not to be used by automated processes to for example assign authorization in
runtime systems. There may be others that also manage or otherwise touch the
resource, but there will always be one ultimate owner.
| [`kind`](#apiversion-and-kind-required) | Default [`namespace`](#namespace-optional) | Generated [relation](well-known-relations.md) type |
| ------------------------------------------------------ | ------------------------------------------ | ------------------------------------------------------------------------------- |
| [`Group`](#kind-group) (default), [`User`](#kind-user) | Same as this entity, typically `default` | [`ownerOf`, and reverse `ownedBy`](well-known-relations.md#ownedby-and-ownerof) |
### `spec.type` [required]
The type of resource as a string, e.g. `database`. This field is required. There
is currently no enforced set of values for this field, so it is left up to the
adopting organization to choose a nomenclature that matches the resources used
in their tech stack.
Some common values for this field could be:
- `database`
- `s3-bucket`
- `cluster`
### `spec.system` [optional]
An [entity reference](#string-references) to the system that the resource
belongs to, e.g. `artist-engagement-portal`. This field is optional.
| [`kind`](#apiversion-and-kind-required) | Default [`namespace`](#namespace-optional) | Generated [relation](well-known-relations.md) type |
| --------------------------------------- | ------------------------------------------ | ----------------------------------------------------------------------------- |
| [`System`](#kind-system) (default) | Same as this entity, typically `default` | [`partOf`, and reverse `hasPart`](well-known-relations.md#partof-and-haspart) |
## Kind: System
This kind is not yet defined, but is reserved [for future use](system-model.md).
Describes the following entity kind:
| Field | Value |
| ------------ | ----------------------- |
| `apiVersion` | `backstage.io/v1alpha1` |
| `kind` | `System` |
A system is a collection of resources and components. The system may expose or
consume one or several APIs. It is viewed as abstraction level that provides
potential consumers insights into exposed features without needing a too
detailed view into the details of all components. This also gives the owning
team the possibility to decide about published artifacts and APIs.
Descriptor files for this kind may look as follows.
```yaml
apiVersion: backstage.io/v1alpha1
kind: System
metadata:
name: artist-engagement-portal
description: Handy tools to keep artists in the loop
spec:
owner: artist-relations-team
domain: artists
providesApis:
- artist-api
```
In addition to the [common envelope metadata](#common-to-all-kinds-the-metadata)
shape, this kind has the following structure.
### `apiVersion` and `kind` [required]
Exactly equal to `backstage.io/v1alpha1` and `System`, respectively.
### `spec.owner` [required]
An [entity reference](#string-references) to the owner of the system, e.g.
`artist-relations-team`. This field is required.
In Backstage, the owner of a system is the singular entity (commonly a team)
that bears ultimate responsibility for the system, and has the authority and
capability to develop and maintain it. They will be the point of contact if
something goes wrong, or if features are to be requested. The main purpose of
this field is for display purposes in Backstage, so that people looking at
catalog items can get an understanding of to whom this system belongs. It is not
to be used by automated processes to for example assign authorization in runtime
systems. There may be others that also develop or otherwise touch the system,
but there will always be one ultimate owner.
| [`kind`](#apiversion-and-kind-required) | Default [`namespace`](#namespace-optional) | Generated [relation](well-known-relations.md) type |
| ------------------------------------------------------ | ------------------------------------------ | ------------------------------------------------------------------------------- |
| [`Group`](#kind-group) (default), [`User`](#kind-user) | Same as this entity, typically `default` | [`ownerOf`, and reverse `ownedBy`](well-known-relations.md#ownedby-and-ownerof) |
### `spec.domain` [optional]
An [entity reference](#string-references) to the domain that the system belongs
to, e.g. `artists`. This field is optional.
| [`kind`](#apiversion-and-kind-required) | Default [`namespace`](#namespace-optional) | Generated [relation](well-known-relations.md) type |
| --------------------------------------- | ------------------------------------------ | ----------------------------------------------------------------------------- |
| [`Domain`](#kind-domain) (default) | Same as this entity, typically `default` | [`partOf`, and reverse `hasPart`](well-known-relations.md#partof-and-haspart) |
## Kind: Domain
This kind is not yet defined, but is reserved [for future use](system-model.md).
Describes the following entity kind:
| Field | Value |
| ------------ | ----------------------- |
| `apiVersion` | `backstage.io/v1alpha1` |
| `kind` | `Domain` |
A Domain groups a collection of systems that share terminology, domain models,
business purpose, or documentation, i.e. form a bounded context.
Descriptor files for this kind may look as follows.
```yaml
apiVersion: backstage.io/v1alpha1
kind: Domain
metadata:
name: artists
description: Everything about artists
spec:
owner: artist-relations-team
```
In addition to the [common envelope metadata](#common-to-all-kinds-the-metadata)
shape, this kind has the following structure.
### `apiVersion` and `kind` [required]
Exactly equal to `backstage.io/v1alpha1` and `Domain`, respectively.
### `spec.owner` [required]
An [entity reference](#string-references) to the owner of the domain, e.g.
`artist-relations-team`. This field is required.
In Backstage, the owner of a domain is the singular entity (commonly a team)
that bears ultimate responsibility for the domain, and has the authority and
capability to develop and maintain it. They will be the point of contact if
something goes wrong, or if features are to be requested. The main purpose of
this field is for display purposes in Backstage, so that people looking at
catalog items can get an understanding of to whom this domain belongs. It is not
to be used by automated processes to for example assign authorization in runtime
systems. There may be others that also develop or otherwise touch the domain,
but there will always be one ultimate owner.
| [`kind`](#apiversion-and-kind-required) | Default [`namespace`](#namespace-optional) | Generated [relation](well-known-relations.md) type |
| ------------------------------------------------------ | ------------------------------------------ | ------------------------------------------------------------------------------- |
| [`Group`](#kind-group) (default), [`User`](#kind-user) | Same as this entity, typically `default` | [`ownerOf`, and reverse `ownedBy`](well-known-relations.md#ownedby-and-ownerof) |
## Kind: Location
@@ -23,7 +23,7 @@ We model software in the Backstage catalogue using these three core entities
- **Resources** are physical or virtual infrastructure needed to operate a
component
![](../../assets/software-catalog/software-model-core-entities.png)
![](../../assets/software-catalog/software-model-core-entities.drawio.svg)
### Component
@@ -73,6 +73,8 @@ these entities using the following (optional) concepts:
function
- **Domains** relate entities and systems to part of the business
![](../../assets/software-catalog/software-model-entities.drawio.svg)
### System
With increasing complexity in software, systems form an important abstraction
@@ -107,10 +109,6 @@ product or use-case, share the same entity types in their APIs, and integrate
well with each other. Other domains could be “Content Ingestion”, “Ads” or
“Search”.
## Current status
Backstage currently supports Components and APIs.
## Links
- [Original RFC](https://github.com/backstage/backstage/issues/390)
@@ -48,11 +48,10 @@ where present.
### `providesApi` and `apiProvidedBy`
A relation with an [API](descriptor-format.md#kind-api) entity, typically from a
[Component](descriptor-format.md#kind-component) or
[System](descriptor-format.md#kind-system).
[Component](descriptor-format.md#kind-component).
These relations express that a component or system exposes an API - meaning that
it hosts callable endpoints from which you can consume that API.
These relations express that a component exposes an API - meaning that it hosts
callable endpoints from which you can consume that API.
This relation is commonly generated based on `spec.providesApis` of the
component or system in question.
@@ -60,11 +59,10 @@ component or system in question.
### `consumesApi` and `apiConsumedBy`
A relation with an [API](descriptor-format.md#kind-api) entity, typically from a
[Component](descriptor-format.md#kind-component) or
[System](descriptor-format.md#kind-system).
[Component](descriptor-format.md#kind-component).
These relations express that a component or system consumes an API - meaning
that it depends on endpoints of the API.
These relations express that a component consumes an API - meaning that it
depends on endpoints of the API.
This relation is commonly generated based on `spec.consumesApis` of the
component or system in question.
@@ -91,3 +89,18 @@ A membership relation, typically for [Users](descriptor-format.md#kind-user) in
[Groups](descriptor-format.md#kind-group).
This relation is commonly based on `spec.memberOf`.
### `partOf` and `hasPart`
A relation with a [Domain](descriptor-format.md#kind-domain),
[System](descriptor-format.md#kind-system) or
[Component](descriptor-format.md#kind-component) entity, typically from a
[Component](descriptor-format.md#kind-component),
[API](descriptor-format.md#kind-api), or
[System](descriptor-format.md#kind-system).
These relations express that a component belongs to a larger component; a
component, API or resource belongs to a system; or that a system is grouped
under a domain.
This relation is commonly based on `spec.system` or `spec.domain`.
+2 -2
View File
@@ -2,8 +2,8 @@
id: software-templates-index
title: Backstage Software Templates
sidebar_label: Overview
description: The Software Templates part of Backstage is a tool that can help
you create Components inside Backstage
# prettier-ignore
description: The Software Templates part of Backstage is a tool that can help you create Components inside Backstage
---
The Software Templates part of Backstage is a tool that can help you create
+2 -2
View File
@@ -2,8 +2,8 @@
id: techdocs-overview
title: TechDocs Documentation
sidebar_label: Overview
description: TechDocs is Spotifys homegrown docs-like-code solution built
directly into Backstage
# prettier-ignore
description: TechDocs is Spotifys homegrown docs-like-code solution built directly into Backstage
---
## What is it?
+2 -2
View File
@@ -1,8 +1,8 @@
---
id: concepts
title: Concepts
description: Documentation on concepts that are introduced with
Spotify's docs-like-code solution in Backstage
# prettier-ignore
description: Documentation on concepts that are introduced with Spotify's docs-like-code solution in Backstage
---
This page describes concepts that are introduced with Spotify's docs-like-code
+20 -14
View File
@@ -1,8 +1,8 @@
---
id: configuration
title: TechDocs Configuration Options
description:
Reference documentation for configuring TechDocs using app-config.yaml
# prettier-ignore
description: Reference documentation for configuring TechDocs using app-config.yaml
---
Using the `app-config.yaml` in the Backstage app, you can configure TechDocs
@@ -54,28 +54,34 @@ techdocs:
# Required when techdocs.publisher.type is set to 'googleGcs'. Skip otherwise.
googleGcs:
# An API key is required to write to a storage bucket.
# (Required) Cloud Storage Bucket Name
bucketName: 'techdocs-storage'
# (Optional) An API key is required to write to a storage bucket.
# If missing, GOOGLE_APPLICATION_CREDENTIALS environment variable will be used.
# https://cloud.google.com/docs/authentication/production
credentials:
$file: '/path/to/google_application_credentials.json'
# Your GCP Project ID where the Cloud Storage Bucket is hosted.
projectId: 'gcp-project-id'
# Cloud Storage Bucket Name
bucketName: 'techdocs-storage'
# Required when techdocs.publisher.type is set to 'awsS3'. Skip otherwise.
awsS3:
# An API key is required to write to a storage bucket.
# (Required) AWS S3 Bucket Name
bucketName: 'techdocs-storage'
# (Optional) An API key is required to write to a storage bucket.
# If not set, environment variables or aws config file will be used to authenticate.
# https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/loading-node-credentials-environment.html
# https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/loading-node-credentials-shared.html
credentials:
accessKeyId:
$env: TECHDOCS_AWSS3_ACCESS_KEY_ID_CREDENTIAL
secretAccessKey:
$env: TECHDOCS_AWSS3_SECRET_ACCESS_KEY_CREDENTIAL
region:
$env: AWSS3_REGION
# AWS S3 Bucket Name
bucketName: 'techdocs-storage'
# (Optional) AWS Region of the bucket.
# If not set, AWS_REGION environment variable or aws config file will be used.
# https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/setting-region.html
region:
$env: AWS_REGION
```
+54
View File
@@ -0,0 +1,54 @@
---
id: how-to-guides
title: TechDocs "HOW TO" guides
sidebar_label: "HOW TO" guides
description: TechDocs "HOW TO" guides related to TechDocs
---
## How to use URL Reader in TechDocs Prepare step?
If TechDocs is configured to generate docs, it will first download the
repository associated with the `backstage.io/techdocs-ref` annotation defined in
the Entity's `catalog-info.yaml` file. This is also called the
[Prepare](./concepts.md#techdocs-preparer) step.
There are two kinds of preparers or two ways of downloading these source files
- Preparer 1: Doing a `git clone` of the repository (also known as Common Git
Preparer)
- Preparer 2: Downloading an archive.zip or equivalent of the repository (also
known as URL Reader)
If `backstage.io/techdocs-ref` is equal to any of these -
1. `github:https://githubhost.com/org/repo`
2. `gitlab:https://gitlabhost.com/org/repo`
3. `bitbucket:https://bitbuckethost.com/project/repo`
4. `azure/api:https://azurehost.com/org/project`
Then Common Git Preparer will be used i.e. a `git clone`. But the URL Reader is
a much faster way to do this step. Convert the `backstage.io/techdocs-ref`
values to the following -
1. `url:https://githubhost.com/org/repo/tree/<branch_name>`
2. `url:https://gitlabhost.com/org/repo/tree/<branch_name>`
3. `url:https://bitbuckethost.com/project/repo/src/<branch_name>`
4. `url:https://azurehost.com/organization/project/_git/repository`
Note that you can also provide a path to a non-root directory inside the
repository which contains the `docs/` directory.
e.g.
`url:https://github.com/backstage/backstage/tree/master/plugins/techdocs-backend/examples/documented-component`
### Why is URL Reader faster than a git clone?
URL Reader uses the source code hosting provider to download a zip or tarball of
the repository. The archive does not have any git history attached to it. Also
it is a compressed file. Hence the file size is significantly smaller than how
much data git clone has to transfer.
Caveat: Currently TechDocs sites built using URL Reader will be cached for 30
minutes which means they will not be re-built if new changes are made within 30
minutes. This cache invalidation will be replaced by commit timestamp based
implementation very soon.
+50 -3
View File
@@ -5,6 +5,53 @@ sidebar_label: Troubleshooting
description: Troubleshooting for TechDocs
---
- TechDocs will fail to clone your docs if you have a git config which overrides
the `https` protocol with `ssh` or something else. Make sure to remove your
git config locally when you try TechDocs.
## Failure to clone
TechDocs will fail to clone your docs if you have a git config which overrides
the `https` protocol with `ssh` or something else. Make sure to remove your git
config locally when you try TechDocs.
## MkDocs Build Errors
Using the [TechDocs CLI](https://github.com/backstage/techdocs-cli), you can
troubleshoot MkDocs build issues locally. Note this requires you have Docker
available to launch images. First, `git clone` the target repository locally,
then in the root of the repository, run:
```
npx @techdocs/cli serve
```
For example, if you have forgotten to put an MkDocs configuration file in your
repo, the resulting error will be:
```
npx: installed 278 in 9.089s
[techdocs-preview-bundle] Running local version of Backstage at http://localhost:3000
INFO - Building documentation...
Config file '/content/mkdocs.yml' does not exist.
```
When it works, a local copy of both Backstage and your site will be launched
locally:
```
npx: installed 278 in 9.682s
[techdocs-preview-bundle] Running local version of Backstage at http://localhost:3000
INFO - Building documentation...
WARNING - Config value: 'dev_addr'. Warning: The use of the IP address '0.0.0.0'
suggests a production environment or the use of a proxy to connect to the MkDocs
server. However, the MkDocs' server is intended for local development purposes only.
Please use a third party production-ready server instead.
INFO - Cleaning site directory
DEBUG - Successfully imported extension module "plantuml_markdown".
DEBUG - Successfully loaded extension "plantuml_markdown.PlantUMLMarkdownExtension".
INFO - Documentation built in 0.23 seconds
[I 210115 19:00:45 server:335] Serving on http://0.0.0.0:8000
INFO - Serving on http://0.0.0.0:8000
[I 210115 19:00:45 handlers:62] Start watching changes
INFO - Start watching changes
[I 210115 19:00:45 handlers:64] Start detecting changes
INFO - Start detecting changes
```
+76 -146
View File
@@ -30,20 +30,35 @@ techdocs:
type: 'googleGcs'
```
**2. GCP (Google Cloud Platform) Project**
**2. Create a GCS Bucket**
Create or choose a dedicated GCP project. Set
`techdocs.publisher.googleGcs.projectId` to the project ID.
Create a dedicated Google Cloud Storage bucket for TechDocs sites.
techdocs-backend will publish documentation to this bucket. TechDocs will fetch
files from here to serve documentation in Backstage. Note that the bucket names
are globally unique.
Set the config `techdocs.publisher.googleGcs.bucketName` in your
`app-config.yaml` to the name of the bucket you just created.
```yaml
techdocs:
publisher:
type: 'googleGcs'
googleGcs:
projectId: 'gcp-project-id'
googleGcs:
bucketName: 'name-of-techdocs-storage-bucket'
```
**3. Service account API key**
**3a. (Recommended) Authentication using environment variable**
The GCS Node.js client will automatically use the environment variable
`GOOGLE_APPLICATION_CREDENTIALS` to authenticate with Google Cloud. It might
already be set in Compute Engine, Google Kubernetes Engine, etc. Read
https://cloud.google.com/docs/authentication/production for more details.
**3b. Authentication using app-config.yaml**
If you do not prefer (3a) and optionally like to use a service account, you can
follow these steps.
Create a new Service Account and a key associated with it. In roles of the
service account, use "Storage Admin".
@@ -65,41 +80,32 @@ techdocs:
publisher:
type: 'googleGcs'
googleGcs:
projectId: 'gcp-project-id'
bucketName: 'name-of-techdocs-storage-bucket'
credentials:
$file: '/path/to/google_application_credentials.json'
```
**4. GCS Bucket**
Create a dedicated bucket for TechDocs sites. techdocs-backend will publish
documentation to this bucket. TechDocs will fetch files from here to serve
documentation in Backstage.
Set the name of the bucket to `techdocs.publisher.googleGcs.bucketName`.
Note: If you are finding it difficult to make the file
`google_application_credentials.json` available on a server, you could use the
file's content and set as an environment variable. And then use
```yaml
techdocs:
publisher:
type: 'googleGcs'
googleGcs:
projectId: 'gcp-project-id'
credentials:
$file: '/path/to/google_application_credentials.json'
bucketName: 'name-of-techdocs-storage-bucket'
credentials:
$env: GOOGLE_APPLICATION_CREDENTIALS
```
**5. That's it!**
**4. That's it!**
Your Backstage app is now ready to use Google Cloud Storage for TechDocs, to
store the static generated documentation files.
store and read the static generated documentation files.
## Configuring AWS S3 Bucket with TechDocs
Follow the
[official AWS S3 documentation](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html)
for the latest instructions on the following steps involving AWS S3.
**1. Set `techdocs.publisher.type` config in your `app-config.yaml`**
Set `techdocs.publisher.type` to `'awsS3'`.
@@ -110,43 +116,17 @@ techdocs:
type: 'awsS3'
```
**2. AWS Policies**
**2. Create an S3 Bucket**
AWS Policies lets you **control access** to Amazon Web Services (AWS) products
and resources.
Here we will use a user policy **and** a bucket policy to show you the different
possibilities you have but you can use only one.
Create a dedicated AWS S3 bucket for the storage of TechDocs sites.
[Refer to the official documentation](https://docs.aws.amazon.com/AmazonS3/latest/user-guide/create-bucket.html).
<img data-zoomable src="../../assets/techdocs/aws-s3.drawio.svg" alt="AWS S3" width="500" />
TechDocs will publish documentation to this bucket and will fetch files from
here to serve documentation in Backstage. Note that the bucket names are
globally unique.
This is an example of how you can manage your policies:
a. Admin user creates a **bucket policy** granting a set of permissions to our
TechDocs user.
b. Admin user attaches a **user policy** to the TechDocs user granting
additional permissions.
c. TechDocs User then tries permissions granted via both the **bucket** policy
and the **user** policy.
**2.1 Creation**
**2.1.1 Create an Admin user** (if you don't have one yet)
Create an **administrator user** account `ADMIN_USER` and grant it administrator
privileges by attaching a user policy giving the account **full access**.
Note down the Admin User credentials and IAM User Sign-In URL as you will need
to use this information in the next step.
**2.1.2 Create an AWS S3 Bucket**
Using the credentials of your Admin User `ADMIN_USER`, and the special IAM user
sign-in URL, create a dedicated **bucket** for TechDocs sites. techdocs-backend
will publish documentation to this bucket. TechDocs will fetch files from here
to serve documentation in Backstage.
Set the name of the bucket to `techdocs.publisher.awsS3.bucketName`.
Set the config `techdocs.publisher.awsS3.bucketName` in your `app-config.yaml`
to the name of the bucket you just created.
```yaml
techdocs:
@@ -156,112 +136,62 @@ techdocs:
bucketName: 'name-of-techdocs-storage-bucket'
```
**2.1.3 Create the `TechDocs` user**
**3a. (Recommended) Setup authentication the AWS way, using environment
variables**
This user will be used to interact with your bucket, it will only have
permissions to **get - put** objects.
You should follow the
[AWS security best practices guide for authentication](https://docs.aws.amazon.com/general/latest/gr/aws-access-keys-best-practices.html).
In the IAM console, do the following:
If the environment variables
- Create a new user, `TechDocs`
- Note down the TechDocs User credentials
- Note down the Amazon Resource Name (ARN) for the TechDocs user. In the IAM
console, select the TechDocs user, and you can find the user ARN in the
Summary tab.
- `AWS_ACCESS_KEY_ID`
- `AWS_SECRET_ACCESS_KEY`
- `AWS_REGION`
**2.2 Attach policies**
are set and can be used to access the bucket you created in step 2, they will be
used by the AWS SDK v3 Node.js client for authentication.
[Refer to the official documentation.](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/loading-node-credentials-environment.html)
Remember that you can use Bucket policy **or** User policy.
Just make sure that you grant all the permissions to the TechDocs user:
`3:PutObject`, `s3:GetObject`, `s3:ListBucket` and `s3:GetBucketLocation`.
If the environment variables are missing, the AWS SDK tries to read the
`~/.aws/credentials` file for credentials.
[Refer to the official documentation.](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/loading-node-credentials-shared.html)
**2.2.1 Create the bucket policy**
Note that the region of the bucket has to be set for the AWS SDK to work.
[See this](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/setting-region.html).
You now have to attach the following policy to your bucket in the Permission
section:
**3b. Authentication using app-config.yaml**
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "statement1",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::YOUR_ACCOUNT_ID:user/TechDocs"
},
"Action": ["s3:GetBucketLocation", "s3:ListBucket"],
"Resource": ["arn:aws:s3:::name-of-techdocs-storage-bucket"]
},
{
"Sid": "statement2",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::YOUR_ACCOUNT_ID:user/TechDocs"
},
"Action": ["s3:GetObject"],
"Resource": ["arn:aws:s3:::name-of-techdocs-storage-bucket/*"]
}
]
}
```
- The first statement grants **TechDocs User** the bucket operation permissions
`s3:GetBucketLocation` and `s3:ListBucket` which are permissions required by
the console.
- The second statement grants the `s3:GetObject` permission.
(**NOTE :** if you do not use the user policy defined below you must also add
the `s3:PutObject` permission to allow the TechDocs user to add objects.)
**2.2.2 Create the user policy**
Create an inline policy for the TechDocs user by using the following policy:
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PermissionForObjectOperations",
"Effect": "Allow",
"Action": ["s3:PutObject"],
"Resource": ["arn:aws:s3:::name-of-techdocs-storage-bucket/*"]
}
]
}
```
See more details in the section
[Working with Inline Policies](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_manage.html).
Now you need to fill in the environment variables with the `TechDocs` User
credentials. You can also specify a region if you want to accesses the resources
in a specific region. Otherwise no region will be selected by default.
```properties
TECHDOCS_AWSS3_ACCESS_KEY_ID_CREDENTIAL="TECHDOCS_ACCESS_KEY_ID"
TECHDOCS_AWSS3_SECRET_ACCESS_KEY_CREDENTIAL="TECHDOCS_SECRET_ACCESS_KEY"
AWSS3_REGION="" // Optional
```
Make it available in your Backstage server and/or your local development server
and set it in the app config techdocs.publisher.awsS3.
AWS credentials and region can be provided to the AWS SDK via `app-config.yaml`.
If the configs below are present, they will be used over existing `AWS_*`
environment variables and the `~/.aws/credentials` config file.
```yaml
techdocs:
publisher:
type: 'awsS3'
awsS3:
bucketName: 'name-of-techdocs-storage-bucket'
region:
$env: AWS_REGION
credentials:
accessKeyId:
$env: TECHDOCS_AWSS3_ACCESS_KEY_ID_CREDENTIAL
$env: AWS_ACCESS_KEY_ID
secretAccessKey:
$env: TECHDOCS_AWSS3_SECRET_ACCESS_KEY_CREDENTIAL
region:
$env: AWSS3_REGION
$env: AWS_SECRET_ACCESS_KEY
```
**3. That's it!**
Refer to the
[official AWS documentation for obtaining the credentials](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/getting-your-credentials.html).
Your Backstage app is now ready to use AWS S3 for TechDocs, to store the static
generated documentation files.
Note: If you are using Amazon EC2 instance to deploy Backstage, you do not need
to obtain the access keys separately. They can be made available in the
environment automatically by defining appropriate IAM role with access to the
bucket. Read more
[here](https://docs.aws.amazon.com/general/latest/gr/aws-access-keys-best-practices.html#use-roles).
**4. That's it!**
Your Backstage app is now ready to use AWS S3 for TechDocs, to store and read
the static generated documentation files. When you start the backend of the app,
you should be able to see
`techdocs info Successfully connected to the AWS S3 bucket` in the logs.
+16
View File
@@ -74,6 +74,22 @@ those plugins in your backend. This is because the transformation of backend
module tree stops whenever a non-local package is encountered, and from that
point node will `require` packages directly for that entire module subtree.
Type checking can also have issues when linking in external packages, since the
linked in packages will use the types in the external project and dependency
version mismatches between the two projects may cause errors. To fix any of
those errors you need to sync versions of the dependencies in the two projects.
A simple way to do this can be to copy over `yarn.lock` from the external
project and run `yarn install`, although this is quite intrusive and can cause
other issues in existing projects, so use this method with care. It can often be
best to simply ignore the type errors, as app serving will work just fine
anyway.
Another issue with type checking is that the incremental type cache doesn't
invalidate correctly for the linked in packages, causing type checking to not
reflect changes made to types. You can work around this by either setting
`compilerOptions.incremental = false` in `tsconfig.json`, or by deleting the
types cache folder `dist-types` before running `yarn tsc`.
### Troubleshooting
The create app command doesn't always work as expected, this is a collection of
+1 -1
View File
@@ -16,7 +16,7 @@ $ yarn docker-build
$ docker run --rm -it -p 7000:7000 -e APP_ENV=production -e NODE_ENV=development example-backend:latest
```
Then open http://localhost/ on your browser.
Then open http://localhost:7000 on your browser.
## Heroku
@@ -1,8 +1,8 @@
---
id: development-environment
title: Development Environment
description: Documentation on how to get set up for doing development on
the Backstage repository
# prettier-ignore
description: Documentation on how to get set up for doing development on the Backstage repository
---
This section describes how to get set up for doing development on the Backstage
+21
View File
@@ -0,0 +1,21 @@
---
id: glossary
title: Backstage Glossary
# prettier-ignore
description: List of all the terms, abbreviations, and phrases used in Backstage, together with their explanations.
---
The Backstage Glossary lists all the terms, abbreviations, and phrases used in
Backstage, together with their explanations. We encourage you to use the
terminology below for clarity and consistency when discussing Backstage.
### Backstage User Profiles
There are three main user profiles for Backstage: the integrator, the
contributor, and the software engineer.
| Term | Explanation |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Integrator | The **integrator** hosts the Backstage app and configures which plugins are available to use in the app. |
| Contributor | The **contributor** adds functionality to the app by writing plugins. |
| Software Engineer | The **software engineer** uses the app's functionality and interacts with its plugins. In practice, this profile covers the various roles that help deliver software, from the Software Engineer themselves, to Designers, Data Scientists, Product Owners, Engineering Managers, etc. |
+2 -2
View File
@@ -1,8 +1,8 @@
---
id: adopting
title: Strategies for adopting
description: Documentation on some general best practices that have been key
to Backstage's success inside Spotify
# prettier-ignore
description: Documentation on some general best practices that have been key to Backstage's success inside Spotify
---
This document outlines some general best practices that have been key to
-11
View File
@@ -185,17 +185,6 @@ separate Docker images.
![Boxes around the architecture to indicate how it is containerised](../assets/architecture-overview/containerised.png)
The frontend container can be built with a provided command.
```bash
yarn install
yarn tsc
yarn run docker-build:app
```
Running this will simply generate a Docker container containing the contents of
the UIs `dist` directory.
The backend container can be built by running the following command:
```bash
+2 -2
View File
@@ -1,8 +1,8 @@
---
id: background
title: The Spotify Story
description: Backstage was born out of necessity at Spotify. We found that as we grew, our
infrastructure was becoming more fragmented, our engineers less productive.
# prettier-ignore
description: Backstage was born out of necessity at Spotify. We found that as we grew, our infrastructure was becoming more fragmented, our engineers less productive.
---
Backstage was born out of necessity at Spotify. We found that as we grew, our
+3 -3
View File
@@ -8,9 +8,9 @@ description: Roadmap of Backstage Project
> Backstage is currently under rapid development. This means that you can expect
> APIs and features to evolve. It is also recommended that teams who adopt
> Backstage today upgrade their installation as new
> [releases](https://github.com/backstage/backstage/releases) become available,
> as Backwards compatibility is not yet guaranteed.
> Backstage today [upgrade their installation](../cli/commands.md#versionsbump)
> as new [releases](https://github.com/backstage/backstage/releases) become
> available, as Backwards compatibility is not yet guaranteed.
## Phases
+3 -4
View File
@@ -1,9 +1,8 @@
---
id: stability-index
title: Stability Index
description:
An overview of the commitment to stability for different parts of the
Backstage codebase.
# prettier-ignore
description: An overview of the commitment to stability for different parts of the Backstage codebase.
---
## Overview
@@ -291,7 +290,7 @@ Stability: `1`. There are plans to rework parts of the Processor interface.
### `catalog-graphql` [GitHub](https://github.com/backstage/backstage/tree/master/plugins/catalog-graphql/)
Provides the catalog schema and resolvers for the graphql backend.
Provides the catalog schema and resolvers for the GraphQL backend.
Stability: `0`. Under heavy development and subject to change.
+2 -2
View File
@@ -1,8 +1,8 @@
---
id: vision
title: Vision
description: Goal is to provide engineers with the best developer experience in
the world
# prettier-ignore
description: Goal is to provide engineers with the best developer experience in the world
---
Our goal is to provide engineers with the best developer experience in the
+2 -2
View File
@@ -1,8 +1,8 @@
---
id: what-is-backstage
title: What is Backstage?
description: Backstage is an open platform for building developer portals.
Powered by a centralized service catalog, Backstage restores order to your microservices and infrastructure
# prettier-ignore
description: Backstage is an open platform for building developer portals. Powered by a centralized service catalog, Backstage restores order to your microservices and infrastructure
---
![service-catalog](https://backstage.io/blog/assets/6/header.png)
+2 -2
View File
@@ -1,8 +1,8 @@
---
id: call-existing-api
title: Call Existing API
description: Describes the various options that Backstage frontend plugins have,
in communicating with service APIs that already exist
# prettier-ignore
description: Describes the various options that Backstage frontend plugins have, in communicating with service APIs that already exist
---
This article describes the various options that Backstage frontend plugins have,
+585
View File
@@ -0,0 +1,585 @@
---
id: composability
title: Composability System Migration
# prettier-ignore
description: Documentation and migration instructions for new composability APIs.
---
## Summary
This page describes the new composability system that was recently introduced in
Backstage, and it does so from the perspective of the existing patterns and
APIs. As the new system is solidified and existing code is ported, this page
will be removed and replaced with a more direct description of the composability
system. For now, the primary purpose of this documentation is to aid in the
migration of existing plugins, but it does cover the migration of apps as well.
The core principle of the new composability system is that plugins should have
clear boundaries and connections. It should isolate crashes within a plugin, but
allow navigation between them. It should allow for plugins to be loaded only
when needed, and enable plugins to provide extension points for other plugins to
build upon. The composability system is also built with an app-first mindset,
prioritizing simplicity and clarity in the app over that in the plugins and core
APIs.
The new composability system isn't a single new API surface. It is a collection
of patterns, primitives, new APIs, and old APIs used in new ways. At the core is
the new concept of extensions, which are exported by plugins for use in the app.
There is also a new primitive called component data, which assists in the
conversion to a more declarative app. The `RouteRef`s now have a clear purpose
as well, and can be used route to pages in a flexible way.
## New Concepts
This section is a brief look into all the new and updated concepts that were put
in place to support the new composability system.
### Component Data
Component data is a new composability primitive that is introduced as a way to
provide a new data dimension for React components. Data is attached to React
components using a key, and is then readable from any JSX elements created with
those components, using the same key, as illustrated by the following example:
```tsx
const MyComponent = () => <h1>This is my component</h1>;
attachComponentData(MyComponent, 'my.data', 5);
const element = <MyComponent />;
const myData = getComponentData(element, 'my.data');
// myData === 5
```
The purpose of component data is to provide a method for embedding data that can
be inspected before rendering elements. Element inspection is a pattern that is
quite common among React libraries, and used for example by `react-router` and
`material-ui` to discover properties of the child elements before rendering.
Although in those libraries only the element type and props are typically
inspected, while our component data adds more structured access and simplifies
evolution by allowing for multiple different versions of a piece of data to be
used and interpreted at once.
The initial use-case for component data is to support route and plugin discovery
through elements in the app. Through this we allow for the React element tree in
the app to be the source of truth, both for which plugins are used, as well as
all top-level plugin routes in the app. The use of component data is not limited
to these use-cases though, as it can be used as a primitive to create new
abstractions as well.
### Extensions
Extensions are what plugins export for use in an app. Most typically they are
React components, but in practice they can be any kind of JavaScript value. They
are created using `create*Extension` functions, and wrapped with
`plugin.provide()` in order to create the actual exported extension.
The extension type is a simple one:
```ts
export type Extension<T> = {
expose(plugin: BackstagePlugin<any, any>): T;
};
```
The power of extensions comes from the ability of various actors to hook into
their usage. The creation and plugin wrapping is controlled by whoever owns the
creation function, the Backstage core is able to hook into the process of
exposing the extension outside the plugin, and in the end the app controls the
usage of the extension.
The Backstage core API currently provides two different types of extension
creators, `createComponentExtension`, and `createRoutableExtension`. Component
extensions are plain React component with no particular requirements, for
example a card for an entity overview page. The component will be exported more
or less as is, but is wrapped to provide things like an error boundary, lazy
loading, and a plugin context.
Routable extensions build on top of component extensions and are used for any
component that should be rendered at a specific route path, such as top-level
pages or entity page tab content. When creating a routable extension you need to
supply a `RouteRef` as `mountPoint`. The mount point will be the handle of the
component for the outside world, and is used by other components and plugins
that wish to link to the routable component.
As of now there are only two extension creation functions, but it is possible to
add more of them in the future, both in the core library and in plugins that
wish to provide an extension point for other plugins to build upon. Extensions
are also not tied to React, and can both be used to model generic JavaScript
concepts, as well as potentially bridge to rendering libraries and web
frameworks other than React.
### Extensions from a Plugin's Point of View
Extensions are one of the primary methods to traverse the plugin boundary, and
the way that plugins provide concrete content for use within an app. They
replace existing component export concepts such as `Router` or `*Card`s for
display on entity overview pages.
It is recommended to create the exported extensions either in the top-level
`plugin.ts` file, or in a dedicated `extensions.ts` (or `.tsx`) file. That file
should not contain the bulk of the implementation though, and in fact, if the
extension is a React component it is recommended to lazy-load the actual
component. Component extensions support lazy loading out of the box using the
`lazy` component declaration, for example:
```ts
export const EntityFooCard = plugin.provide(
createComponentExtension({
component: {
lazy: () => import('./components/FooCard').then(m => m.FooCard),
},
}),
);
```
Routable extensions even enforce lazy loading, as it is the only way to provide
a component:
```ts
export const FooPage = plugin.provide(
createRoutableExtension({
component: () => import('./components/FooPage').then(m => m.FooPage),
mountPoint: fooPageRouteRef,
}),
);
```
### Using Extensions in an App
Right now all extensions are modelled as React components. The usage of these
extension is like regular usage of any React components, with one important
difference. Extensions must all be part of a single React element tree spanning
from the root `AppProvider`.
For example, the following app code does **NOT** work:
```tsx
const AppRoutes = () => (
<Routes>
<Route path="/foo" element={<FooPage />} />
<Route path="/bar" element={<BarPage />} />
</Routes>
);
const App = () => (
<AppProvider>
<AppRouter>
<Root>
<AppRoutes />
</Root>
</AppRouter>
</AppProvider>
);
```
But in this case it is simple to fix! Simply be sure to not create any
intermediate components in the app, for example like this:
```tsx
const appRoutes = (
<Routes>
<Route path="/foo" element={<FooPage />} />
<Route path="/bar" element={<BarPage />} />
</Routes>
);
const App = () => (
<AppProvider>
<AppRouter>
<Root>{appRoutes}</Root>
</AppRouter>
</AppProvider>
);
```
### New Routing System
A big piece of what is enabled by moving over to this new composability system
is to make `RouteRef`s useful. The `RouteRef`s no longer have their own path, in
fact the only required parameter is currently a `title`. Instead of assigning a
path to each `RouteRef` and possibly overriding these paths in the app, the
concrete `path` for each `RouteRef` is discovered based on the element tree in
the app. Let's consider the following example:
```tsx
const appRoutes = (
<Routes>
<Route path="/foo" element={<FooPage />} />
<Route path="/bar" element={<BarPage />} />
</Routes>
);
```
We'll assume that `FooPage` and `BarPage` are routable extensions, exported by
the `fooPlugin` and `barPlugin` respectively. Since the `FooPage` is a routable
extension it has a `RouteRef` assigned as its mount point, which we'll refer to
as `fooPageRouteRef`.
Given the above example, the `fooPageRouteRef` will be associated with the
`'/foo'` route. The path is no longer accessible via the `path` property of the
`RouteRef` though, as the routing structure is tied to the app's react tree. We
instead use the new `useRouteRef` hook if we want to create a concrete link to
the page. The `useRouteRef` hook takes a single `RouteRef` as its only
parameter, and returns a function that is called to create the URL. For example
like this:
```tsx
const MyComponent = () => {
const fooRoute = useRouteRef(fooPageRouteRef);
return <a href={fooRoute()}>Link to Foo</a>;
};
```
Now let's assume that we want to link from the `BarPage` to the `FooPage`.
Before the introduction of the new composability system, we would do this by
importing the `fooPageRouteRef` exported by the `fooPlugin`. This created an
unnecessary dependency on the plugin, and also provided little flexibility in
allowing the app to tie plugins together, with the links instead being dictated
by the plugins themselves. To solve this, we introduce `ExternalRouteRef`s. Much
like regular route references, they can be passed to `useRouteRef` to create
concrete URLs, but they can not be used as mount points in routable component
and instead have to be associated with a target route using route bindings in
the app.
We create a new `ExternalRouteRef` inside the `barPlugin`, using a neutral name
that describes its role in the plugin rather than a specific plugin page that it
might be linking to, allowing the app to decide the final target. If the
`BarPage` for example wants to link to an external page in the header, it might
declare an `ExternalRouteRef` similar to this:
```ts
const headerLinkRouteRef = createExternalRouteRef();
```
### Binding External Routes in the App
The association of external routes is controlled by the app. Each
`ExternalRouteRef` of a plugin should be bound to an actual `RouteRef`, usually
from another plugin. The binding process happens once at app startup, and is
then used through the lifetime of the app to help resolve concrete route paths.
Using the above example of the `BarPage` linking to the `FooPage`, we might do
something like this in the app:
```ts
createApp({
bindRoutes({ bind }) {
bind(barPlugin.externalRoutes, {
headerLink: fooPlugin.routes.root,
});
},
});
```
Given the above binding, using `useRouteRef(headerLinkRouteRef)` within the
`barPlugin` will let us create a link to whatever path the `FooPage` is mounted
at.
Note that we are not importing and using the `RouteRef`s directly in the app,
and instead rely on the plugin instance to access routes of the plugins. This is
a new convention that was introduced to provide better namespacing and
discoverability of routes, as well as reduce the number of separate exports from
each plugin package. The route references would be supplied to `createPlugin`
like this:
```ts
// In foo-plugin
export const fooPlugin = createPlugin({
routes: {
root: fooPageRouteRef,
},
...
})
// In bar-plugin
export const barPlugin = createPlugin({
externalRoutes: {
headerLink: headerLinkRouteRef,
},
...
})
```
Also note that you almost always want to create the route references themselves
in a different file than the one that creates the plugin instance, for example a
top-level `routes.ts`. This is to avoid circular imports when you use the route
references from other parts of the same plugin.
### Parameterized Routes
A new addition to `RouteRef`s is the possibility of adding named and typed
parameters. Parameters are declared at creation, and will enforce presence of
the parameters in the path in the app, and require them as a parameter when
using `useRouteRef`.
The following is an example of creation and usage of a parameterized route:
```tsx
// Creation of a parameterized route
const myRouteRef = createRouteRef({
title: 'My Named Route',
params: ['name']
})
// In the app, where MyPage is a routable extension with myRouteRef set as mountPoint
<Route path='/my-page/:name' element={<MyPage />}/>
// Usage within a component
const myRoute = useRouteRef(myRouteRef)
return (
<div>
<a href={myRoute({name: 'a'})}>A</a>
<a href={myRoute({name: 'b'})}>B</a>
</div>
)
```
It is currently not possible to have parameterized `ExternalRouteRef`s, or to
bind an external route to a parameterized route, although this may be added in
the future if needed.
### New Catalog Components
The established pattern for selecting what plugins should be available on each
catalog page is to use custom components in the app, with logic embedded in the
render function. Typically this takes form as a component that either receives
the entity via props or uses the `useEntity` hook to retrieve the selected
entity. A `switch` or `if` / `else if` chain is then used to select what
children should be rendered based on information in the entity.
This pattern will no longer work with the new composability system, and in
general is very difficult to build any form of declarative model around, as it
depends on runtime execution. To help replace existing code, a new
`EntitySwitch` component has been added to the `@backstage/catalog` plugin,
which grabs the selected entity from a context, and selects at most one element
to render using a list of `EntitySwitch.Case` children.
For example, if you want all entities of kind `"Template"` to be rendered with a
`MyTemplate` component, and all other entities to be rendered with a `MyOther`
component, you would do the following:
```tsx
<EntitySwitch>
<EntitySwitch.Case if={isKind('template')}>
<MyTemplate />
</EntitySwitch.Case>
<EntitySwitch.Case>
<MyTemplate />
</EntitySwitch.Case>
</EntitySwitch>
// Shorter form if desired:
<EntitySwitch>
<EntitySwitch.Case if={isKind('template')} children={<MyTemplate />}/>
<EntitySwitch.Case children={<MyTemplate />}/>
</EntitySwitch>
```
The `EntitySwitch` component will render the children of the first
`EntitySwitch.Case` that returns `true` when the selected entity is passed to
the function of the `if` prop. If none of the cases match, no children will be
rendered, and if a case doesn't specify an `if` filter function, it will always
match. The `if` property is simply a function of the type
`(entity: Entity) => boolean`, for example, `isKind` can be implemented like
this:
```ts
function isKind(kind: string) {
return (entity: Entity) => entity.kind.toLowerCase() === kind.toLowerCase();
}
```
The `@backstage/catalog` plugin provides a couple of built-in conditions,
`isKind`, `isComponentType`, and `isNamespace`.
In addition to the `EntitySwitch` component, the catalog plugin also exports a
new `EntityLayout` component. It is a tweaked version and replacement for the
`EntityPageLayout` component, and is introduced more in depth in the app
migration section below.
## Porting Existing Plugins
There are a couple of high-level steps to porting an existing plugin to the new
composability system:
- Remove usage of `router.addRoute` or `router.registerRoute` within
`createPlugin`, and export the page components as routable extensions instead.
- Switch any `Router` export to instead be a routable extension.
- Change any plain component exports, such as catalog overview cards, to be
component extensions.
- Stop exporting `RouteRef`s and instead pass them to `createPlugin`.
- Stop accepting `RouteRef`s as props or importing them from other plugins,
instead create an `ExternalRouteRef` as a replacement, and pass it to
`createPlugin.`
- Rename any other exported symbols according to the naming pattern table below.
Note that removing the existing exports and configuration is a breaking change
in any plugin. If backwards compatibility is needed the existing code be
deprecated while making the new additions, to then be removed at a later point.
### Naming Patterns
Many export naming patterns have been changed to avoid import aliases and to
clarify intent. Refer to the following table to formulate the new name:
| Description | Existing Pattern | New Pattern | Examples |
| -------------------- | -------------------------- | --------------- | ---------------------------------------------- |
| Top-level Pages | Router | \*Page | CatalogIndexPage, SettingsPage, LighthousePage |
| Entity Tab Content | Router | Entity\*Content | EntityJenkinsContent, EntityKubernetesContent |
| Entity Overview Card | \*Card | Entity\*Card | EntitySentryCard, EntityPagerDutyCard |
| Entity Conditional | isPluginApplicableToEntity | is\*Available | isPagerDutyAvailable, isJenkinsAvailable |
| Plugin Instance | plugin | \*Plugin | jenkinsPlugin, catalogPlugin |
## Porting Existing Apps
The first step of porting any app is to replace the root `Routes` component with
`FlatRoutes` from `@backstage/core`. As opposed to the `Routes` component,
`FlatRoutes` only considers the first level of `Route` components in its
children, and provides any additional children to the outlet of the route. It
also removes the need to append `"/*"` to paths, as it is added automatically.
```diff
const AppRoutes = () => (
- <Routes>
+ <FlatRoutes>
...
- <Route path="/docs/*" element={<DocsRouter />} />
+ <Route path="/docs" element={<DocsRouter />} />
...
- </Routes>
+ </FlatRoutes>
);
```
The next step should be to switch from using `EntityPageLayout` to
`EntityLayout`, as this can also be done without waiting for plugins to be
ported. You should also replace the top-level `Router` from the catalog plugin
with the separate `CatalogIndexPage` and `CatalogEntityPage` extensions that
have been added to the catalog:
```diff
-<Route
- path={`${catalogRouteRef.path}/*`}
- element={<CatalogRouter EntityPage={EntityPage} />}
-/>
+<Route path="/catalog" element={<CatalogIndexPage />} />
+<Route
+ path="/catalog/:namespace/:kind/:name"
+ element={<CatalogEntityPage />}
+>
+ <EntityPage />
+</Route>
```
At that point you should flatten out the element tree as much as possible in the
app, removing any intermediate components. At the top level this should usually
be straightforward, but when reaching the catalog entity pages you may need to
wait for some plugins to be migrated. This is because it is no longer possible
to pass in the selected entity through component props, and it should be picked
up from context inside the plugin instead. See the sections below for how to
carry out migrations of some common entity page patterns.
Once the app element tree doesn't contain any intermediate components, and all
plugin imports have been switched to extensions rather than plain components,
the app has been fully ported.
### Switching from EntityPageLayout to EntityLayout
The existing `EntityPageLayout` is replaced by the new `EntityLayout` component,
which has a slightly different pattern for expressing the contents and paths.
Porting from the old to the new API is just a matter of moving some things
around. For example, given the following existing code:
```tsx
<EntityPageLayout>
<EntityPageLayout.Content
path="/"
title="Overview"
element={<ComponentOverviewContent entity={entity} />}
/>
<EntityPageLayout.Content
path="/sentry"
title="Sentry"
element={<SentryRouter entity={entity} />}
/>
<EntityPageLayout.Content
path="/kubernetes/*"
title="Kubernetes"
element={<KubernetesRouter entity={entity} />}
/>
</EntityPageLayout>
```
It would be ported to this:
```tsx
<EntityLayout>
<EntityLayout.Route path="/" title="Overview">
<ComponentOverviewContent entity={entity} />
</EntityLayout.Route>
<EntityLayout.Route path="/sentry" title="Sentry">
<SentryRouter entity={entity} />
</EntityLayout.Route>
<EntityLayout.Route path="/kubernetes" title="Kubernetes">
<KubernetesRouter entity={entity} />
</EntityLayout.Route>
</EntityLayout>
```
In addition to the renaming, the `element` prop has been moved to `children`.
Also note that the `/*` suffix has been removed from the `"/kubernetes"` path,
as it's now added automatically.
Usage of the `EntityLayout` component is required to be able to properly
discover routes, and so it is required to apply this change before you can start
using routable entity content extensions from plugins.
### Porting Entity Pages
The established pattern in the app is to use custom components in order to
select what plugin components to render for a given entity. The new
`EntitySwitch` component introduced above is what is intended to replace this
pattern, now that the entire app needs to be rendered as a single element tree.
For example, given the following existing code:
```tsx
export const EntityPage = () => {
const { entity } = useEntity();
switch (entity?.kind?.toLowerCase()) {
case 'component':
return <ComponentEntityPage entity={entity} />;
case 'api':
return <ApiEntityPage entity={entity} />;
case 'group':
return <GroupEntityPage entity={entity} />;
case 'user':
return <UserEntityPage entity={entity} />;
default:
return <DefaultEntityPage entity={entity} />;
}
};
```
It would be migrated to this:
```tsx
export const entityPage = (
<EntitySwitch>
<EntitySwitch.Case if={isKind('component')} children={componentPage} />
<EntitySwitch.Case if={isKind('api')} children={apiPage} />
<EntitySwitch.Case if={isKind('group')} children={groupPage} />
<EntitySwitch.Case if={isKind('user')} children={userPage} />
<EntitySwitch.Case children={defaultPage} />
</EntitySwitch>
);
```
Note that for example `<ComponentEntityPage ... />` has been changed to simply
`componentPage`, that is because just like the `EntityPage` component, the
`ComponentEntityPage` also needs to be ported to be an element rather a
component in a similar way.
+82
View File
@@ -0,0 +1,82 @@
# Using GitHub Apps for Backend Authentication
Backstage can be configured to use GitHub Apps for backend authentication. This
comes with advantages such as higher rate limits and that Backstage can act as
an application instead of a user or bot account.
It also provides a much clearer and better authorization model as a opposed to
the OAuth apps and their respective scopes.
## Caveats
- It's not possible to have multiple Backstage GitHub Apps installed in the same
GitHub organization, to be handled by Backstage. We currently don't check
through all the registered GitHub Apps to see which ones are installed for a
particular repository. We only respect global Organization installs right now.
- App permissions is not managed by Backstage. They're created with some simple
default permissions which you are free to change as you need, but you will
need to update them in the GitHub web console, not in Backstage right now. The
permissions that are defaulted are `metadata:read` and `contents:read`.
- The created GitHub App is private by default, this is most likely what you
want for github.com but it's recommended to make your application public for
GitHub Enterprise in order to share application across your GHE organizations.
A GitHub app created with `backstage-cli create-github-app` will have read
access by default. You have to manually update the GitHub App settings in GitHub
to grant the app more permissions if needed.
### Using the CLI (public GitHub only)
You can use the `backstage-cli` to create GitHub App' using a manifest file that
we provide. This gives us a way to automate some of the work required to create
a GitHub app.
You can read more about the `backstage-cli create-github-app` method
[here](../cli/commands.md#create-github-app)
Once you've gone through the CLI command, it should produce a `yaml` file in the
root of the project which you can then use as an `include` in your
`app-config.yaml`. You can go ahead and skip to
[here](#including-in-integrations-config) if you've got to this part.
### GitHub Enterprise
You have to create the GitHub Application manually using these
[instructions](https://docs.github.com/en/free-pro-team@latest/developers/apps/creating-a-github-app)
as GitHub Enterprise does not support creation of apps from manifests.
Once the application is created you have to generate a private key for the
application it in a `yaml` file.
The yaml file must include the following information. Please note that the
indentation for the `privateKey` is required.
```yaml
appId: 1
clientId: client id
clientSecret: client secret
webhookSecret: webhook secret
privateKey: |
-----BEGIN RSA PRIVATE KEY-----
...Key content...
-----END RSA PRIVATE KEY-----
```
### Including in Integrations Config
Once the credentials are stored in a yaml file generated by `create-github-app`
or manually by following the [GitHub Enterprise](#gitHub-enterprise)
instructions, they can be included in the `app-config.yaml` under the
`integrations` section.
Please note that the credentials file is highly sensitive and should NOT be
checked into any kind of version control. Instead use your preferred secure
method of distributing secrets.
```yaml
integrations:
github:
- host: github.com
apps:
- $include: example-backstage-app-credentials.yaml
```
+1 -1
View File
@@ -7,7 +7,7 @@ description: Documentation on Publishing npm packages
## npm
npm packages are published through CI/CD in the
[.github/workflows/master.yml](https://github.com/backstage/backstage/blob/master/.github/workflows/master.yml)
[`.github/workflows/master.yml`](https://github.com/backstage/backstage/blob/master/.github/workflows/master.yml)
workflow. Every commit that is merged to master will be checked for new versions
of all public packages, and any new versions will automatically be published to
npm.
+1 -1
View File
@@ -29,7 +29,7 @@ These types are part of the API declaration, but may not be unique to this API.
### Error
Mirrors the javascript Error class, for the purpose of providing documentation
Mirrors the JavaScript Error class, for the purpose of providing documentation
and optional fields.
<pre>
+2 -6
View File
@@ -1,8 +1,8 @@
---
id: project-structure
title: Backstage Project Structure
description:
Introduction to files and folders in the Backstage Project repository
# prettier-ignore
description: Introduction to files and folders in the Backstage Project repository
---
Backstage is a complex project, and the GitHub repository contains many
@@ -32,10 +32,6 @@ the code.
better control over our `yarn.lock` file and hopefully avoid problems due to
yarn versioning differences.
- [`docker/`](https://github.com/backstage/backstage/tree/master/docker) - Files
related to our root Dockerfile. We are planning to refactor this, so expect
this folder to be moved in the future.
- [`contrib/`](https://github.com/backstage/backstage/tree/master/contrib) -
Collection of examples or resources provided by the community. We really
appreciate contributions in here and encourage them being kept up to date.
+250 -28
View File
@@ -3,20 +3,18 @@ id: quickstart-app-auth
title: Monorepo App Setup With Authentication
---
###### September 15th 2020 - @backstage/create-app - v0.1.1-alpha.21
###### January 8th 2021 - @backstage/create-app - v0.4.5
<br />
> This document takes you through setting up a Backstage app that runs in your
> own environment. It starts with a skeleton install and verifying of the
> monorepo's functionality. Next, GitHub authentication is added and tested.
> monorepo's functionality. Next, authentication is added and tested.
>
> This document assumes you have Node.js 12 active along with Yarn and Python.
> Please note, that at the time of this writing, the current version is
> 0.1.1-alpha.21. This guide can still be used with future versions, just,
> verify as you go. If you run into issues, you can compare your setup with mine
> here >
> [simple-backstage-app](https://github.com/johnson-jesse/simple-backstage-app).
> This document assumes you have Node.js 12 or 14 active along with Yarn and
> Python. Please note, that at the time of this writing, the current version is
> v0.4.5. This guide can still be used with future versions, just, verify as you
> go.
# The Skeleton Application
@@ -55,7 +53,17 @@ guest. Let's fix that now and add auth.
# The Auth Configuration
1. Open `app-config.yaml` and change it as follows
A default Backstage installation includes multiple authentication providers out
of the box. The steps to enable new authentication providers in Backstage are
very similar to each other, the biggest difference is usually configuring the
external authentication provider. Please see a subset of possible providers and
instructions to integrate them below. Steps 1 & 2 are described separately for
each provider and steps beyond that are common for all.
<details><summary>GitHub</summary>
<p>
### 1. Open `app-config.yaml` and change it as follows
_from:_
@@ -75,23 +83,229 @@ auth:
$env: AUTH_GITHUB_CLIENT_ID
clientSecret:
$env: AUTH_GITHUB_CLIENT_SECRET
## uncomment the following three lines if using enterprise
## uncomment the following two lines if using enterprise
# enterpriseInstanceUrl:
# $env: AUTH_GITHUB_ENTERPRISE_INSTANCE_URL
```
2. Set environment variables in whatever fashion is easiest for you. I chose to
add mine to my `.zshrc` profile.
### 2. Generate a GitHub client ID and secret
- Log into http://github.com
- Navigate to (Settings > Developer Settings > OAuth Apps > New OAuth
App)[https://github.com/settings/applications/new]
- Set Homepage URL = `http://localhost:3000`
- Set Callback URL = `http://localhost:7000/api/auth/github`
- Click [Register application]
- On the next page, copy and paste your new Client ID and Client Secret to
environment variables defined in the `app-config.yaml` file,
`AUTH_GITHUB_CLIENT_ID` & `AUTH_GITHUB_CLIENT_SECRET`
</p>
</details>
<details><summary>GitLab</summary>
<p>
### 1. Open `app-config.yaml` and change it as follows
_from:_
```yaml
auth:
providers: {}
```
_to:_
```yaml
auth:
providers:
gitlab:
development:
clientId:
$env: AUTH_GITLAB_CLIENT_ID
clientSecret:
$env: AUTH_GITLAB_CLIENT_SECRET
audience: https://gitlab.com # Or your self-hosted GitLab instance URL
```
### 2. Generate a GitLab Application client ID and secret
- Log into GitLab
- Navigate to (Profile > Settings >
Applications)[https://gitlab.com/-/profile/applications]
- Name your application
- Set Callback URL = `http://localhost:7000/api/auth/gitlab/handler/frame`
- Select the following values:
- `read_user` (Read the authenticated user's personal information)
- `read_repository` (Allows read-only access to the repository)
- `write_repository` (Allows read-write access to the repository)
- `openid` (Authenticate using OpenID Connect)
- `profile` (Allows read-only access to the user's personal information using
OpenID Connect)
- `email` (Allows read-only access to the user's primary email address using
OpenID Connect)
- Click [Save application]
- On the next page, copy and paste your new Application ID and Secret to
environment variables defined in the `app-config.yaml` file,
`AUTH_GITLAB_CLIENT_ID` & `AUTH_GITLAB_CLIENT_SECRET`
</p>
</details>
<details><summary>Google</summary>
<p>
### 1. Open `app-config.yaml` and change it as follows
_from:_
```yaml
auth:
providers: {}
```
_to:_
```yaml
auth:
providers:
google:
development:
clientId:
$env: AUTH_GOOGLE_CLIENT_ID
clientSecret:
$env: AUTH_GOOGLE_CLIENT_SECRET
```
### 2. Generate Google Credentials in Google Cloud console
- Log into https://console.cloud.google.com
- Select or create a new project from the dropdown on the top bar
- Navigate to (APIs & Services >
Credentials)[https://console.cloud.google.com/apis/credentials]
- Click Create Credentials and select [OAuth client ID]
- Select Web Application as the application type
- Add new Authorised JavaScript origin = `http://localhost:3000`
- Add new Authorised redirect URI =
`http://localhost:7000/api/auth/google/handler/frame`
- Click [Save application]
- Google should display a modal with your Client ID and Secret. Copy and paste
those to environment variables defined in the `app-config.yaml` file,
`AUTH_GOOGLE_CLIENT_ID` & `AUTH_GOOGLE_CLIENT_SECRET`
</p>
</details>
<details><summary>Microsoft</summary>
<p>
### 1. Open `app-config.yaml` and change it as follows
_from:_
```yaml
auth:
providers: {}
```
_to:_
```yaml
auth:
providers:
microsoft:
development:
clientId:
$env: AUTH_MICROSOFT_CLIENT_ID
clientSecret:
$env: AUTH_MICROSOFT_CLIENT_SECRET
tenantId:
$env: AUTH_MICROSOFT_TENANT_ID
```
### 2. Create a Microsoft App Registration in Microsoft Portal
- Log into https://portal.azure.com
- Navigate to (Azure Active Directory > App
Registrations)[https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/RegisteredApps]
- Create a New Registration
- Add new Redirect URI = `http://localhost:3000`
- Add new Authorised redirect URI =
`http://localhost:7000/api/auth/microsoft/handler/frame`
- Click [Save application]
- Set environment variable `AUTH_MICROSOFT_CLIENT_ID` from
`Application (client) Id` displayed on the directory page
- Set environment variable `AUTH_MICROSOFT_TENANT_ID` from
`Directory (tenant) ID` displayed on the directory page
- Navigate to Certificates & Secrets section and click [Create a new secret]
- Set environment variable `AUTH_MICROSOFT_CLIENT_SECRET` from the `value` field
created.
</p>
</details>
<details><summary>Auth0</summary>
<p>
### 1. Open `app-config.yaml` and change it as follows
_from:_
```yaml
auth:
providers: {}
```
_to:_
```yaml
auth:
providers:
auth0:
development:
clientId:
$env: AUTH_AUTH0_CLIENT_ID
clientSecret:
$env: AUTH_AUTH0_CLIENT_SECRET
domain:
$env: AUTH_AUTH0_DOMAIN_ID
```
### 2. Create an Auth0 application in the Auth0 management console
- Log into https://manage.auth0.com/dashboard/
- Navigate to Applications
- Create a New Application
- Select Single Page Web Application
- Go to Settings tab
- Add new line to Allowed Callback URLs =
`http://localhost:7000/api/auth/auth0/handler/frame`
- Click [Save Changes]
- Set environment variables displayed on the Basic Information page
- `AUTH_AUTH0_CLIENT_ID` from `Client ID` displayed on Auth0 application page
- `AUTH_AUTH0_CLIENT_SECRET` from `Client Secret` displayed on Auth0
application page
- `AUTH_AUTH0_DOMAIN_ID` from `Domain` displayed on Auth0 application page
</p>
</details>
### 3. Set environment variables in whatever fashion is easiest for you. I chose to
add mine to my `.zshrc` profile.
```zsh
# For macOS Catalina & Z Shell
# ------ simple-backstage-app GitHub
#
# (Change the name of the environment variables based on your auth setup above)
export AUTH_GITHUB_CLIENT_ID=xxx
export AUTH_GITHUB_CLIENT_SECRET=xxx
# export AUTH_GITHUB_ENTERPRISE_INSTANCE_URL=https://github.{MY_BIZ}.com
```
3. And of course I need to source that file.
### 4. And of course I need to source that file.
```zsh
# Loading the new variables
@@ -107,26 +321,28 @@ export AUTH_GITHUB_CLIENT_SECRET=xxx
> ...
```
4. The values to replace `xxx` above come from your oauth app setup.
### 5. Open and change _root > packages > app > src >_ `App.tsx` to use correct
```
> Log into http://github.com
> Navigate to (Settings > Developer Settings > OAuth Apps > New OAuth App)[https://github.com/settings/applications/new]
> Set Homepage URL = http://localhost:3000
> Set Callback URL = http://localhost:7000/api/auth/github
> Click [Register application]
> On the next page, copy and paste your new Client ID and Client Secret to the environment variables above, `AUTH_GITHUB_CLIENT_ID` & `AUTH_GITHUB_CLIENT_SECRET`
> Don't forget to `source` that profile file again if necessary.
```
5. Open and change _root > packages > app > src >_`App.tsx` as follows
authentication provider reference
```tsx
// Add the following imports to the existing list from core
import { githubAuthApiRef, SignInPage } from '@backstage/core';
```
6. In the same file, change the createApp function as follows
Modify the imported reference based on the authentication method you selected
above:
| Auth Provider | Import Name |
| ------------- | ------------------- |
| GitHub | githubAuthApiRef |
| GitLab | gitlabAuthApiRef |
| Google | googleAuthApiRef |
| Microsoft | microsoftAuthApiRef |
| Auth0 | auth0AuthApiRef |
### 6. In the same file, modify createApp
Remember to modify the provider information based on the table above.
```tsx
const app = createApp({
@@ -153,12 +369,18 @@ const app = createApp({
});
```
7. Start the backend and frontend as before
After finishing setting up one (or multiple) authentication providers defined
above you can start the backend and frontend as before
When the browser loads, you should be presented with a login page for GitHub.
Login as usual with your GitHub account. If this is your first time, you will be
asked to authorize and then are redirected to the catalog page if all is well.
For more information you can clone
[the backstage-auth-example repository](https://github.com/RoadieHQ/backstage-auth-example).
Each authentication setting is set up there on a branch named after the
authentication provider.
# Where to go from here
> You're probably eager to write your first custom plugin. Follow this next
+3 -4
View File
@@ -59,7 +59,7 @@ import GitHubIcon from '@material-ui/icons/GitHub';
```
Simple! The App will reload with your changes automatically. You should now see
a github icon displayed in the sidebar. Clicking that will link to our new
a GitHub icon displayed in the sidebar. Clicking that will link to our new
plugin. And now, the API fun begins.
# The Identity
@@ -72,8 +72,7 @@ Our first modification will be to extract information from the Identity API.
```tsx
// Add identityApiRef to the list of imported from core
import { identityApiRef } from '@backstage/core';
import { useApi } from '@backstage/core-api';
import { identityApiRef, useApi } from '@backstage/core';
```
3. Adjust the ExampleComponent from inline to block
@@ -143,8 +142,8 @@ import {
TableColumn,
Progress,
githubAuthApiRef,
useApi,
} from '@backstage/core';
import { useApi } from '@backstage/core-api';
import { graphql } from '@octokit/graphql';
const ExampleFetchComponent = () => {
@@ -6,7 +6,7 @@ authorURL: https://github.com/garyniemen
Since we [open sourced Backstage](https://backstage.io/blog/2020/03/16/announcing-backstage), one of the most requested features has been for a technical documentation plugin. Well, good news. The first open source version of TechDocs is here. Now lets start collaborating and making it better, together.
<iframe width="780" height="440" src="https://www.youtube.com/embed/mOLCgdPw1iA" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen>
<iframe width="780" height="440" src="https://www.youtube.com/embed/mOLCgdPw1iA" frameBorder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowFullScreen>
</iframe>
<!--truncate-->
@@ -6,7 +6,7 @@ authorURL: https://twitter.com/janisa_a
How did Spotify save millions on cloud costs within a matter of months?? We made cost optimization just another part of the daily development process. Our newly open sourced [Cost Insights plugin](https://github.com/backstage/backstage/tree/master/plugins/cost-insights) makes a teams cloud costs visible — and actionable — right inside Backstage. So engineers can see the impact of their cloud usage (down to a product and resource level) and make optimizations wherever and whenever it makes sense. By managing cloud costs from the ground up, you can make smarter decisions that let you continue to build and scale quickly, without wasting resources.
<iframe width="780" height="440" src="https://www.youtube.com/embed/YLAd5hdXR_Q" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="780" height="440" src="https://www.youtube.com/embed/YLAd5hdXR_Q" frameBorder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowFullScreen></iframe>
Are we turning engineers into accountants? Nope, were just letting engineers do what they do best, in the place that feels natural to them: inside Backstage.
@@ -0,0 +1,72 @@
---
title: New Backstage feature: Kubernetes for Service owners
author: Matthew Clarke, Spotify
authorURL: https://github.com/mclarke47
---
![Animation of Kubernetes and cloud provider icons becoming the Backstage logo](assets/21-01-12/backstage-k8s-1-hero.gif)
TLDR; Were rethinking the Kubernetes developer experience with a new feature: a Kubernetes monitoring tool thats designed around the needs of service owners, not cluster admins. Now developers can easily check the health of their services no matter how or where those services are deployed — whether its on a local host for testing or in production on dozens of clusters around the world.
And since Backstage uses the native Kubernetes API, the feature works with whichever cloud provider (AWS, Azure, GCP, etc.) or managed service (OpenShift, IBM Cloud, GKE, etc.) you already use.
<!--truncate-->
## The missing link between K8s and your service
A core feature of Backstage is its service catalog, which aggregates information about software systems together inside a single tool, with a consistent, familiar UI.
By navigating to a services overview page in Backstage, you can see everything you need to know about the service: what it does, its APIs and technical documentation, CI/CD progress — and now detailed information about its presence on Kubernetes clusters.
## No more context switching
Kubernetes in Backstage can be configured to search multiple clusters for your services. It will then aggregate them together into a single view. So if you deploy to multiple clusters you will no longer need to switch kubectl contexts to understand the current state of your service.
![List of deployments in Backstage Kubernetes plugin](assets/21-01-12/backstage-k8s-2-deployments.png)
## Automatic error reporting
Instead of trying different kubectl commands to figure out where an error occurred, Backstage will automatically find and highlight errors in Kubernetes resources that are affecting your service. So you can spend time fixing errors, not hunting for them.
![Error reporting screen in Backstage Kubernetes plugin](assets/21-01-12/backstage-k8s-3-error-reporting.png)
## Autoscaling limits at a glance
Backstage also shows you how close your service is to its autoscaling limit. Coming up to a period of high load? Now you will be able to see how your horizontal autoscaling is dealing with it across multiple clusters.
![Autoscaling limits screen in Backstage Kubernetes plugin](assets/21-01-12/backstage-k8s-4-autoscaling-limits.png)
![Autoscaling limits screen in Backstage Kubernetes plugin](assets/21-01-12/backstage-k8s-5-autoscaling-limits.png)
## Pick a cloud, any Cloud
Since Backstage communicates directly with the Kubernetes API, its cloud agnostic — it doesnt matter how or where youre running Kubernetes. Youll always get the same familiar view of your deployments, whether youre:
- Deploying to clusters on AWS, Azure, GCP, or another cloud provider
- Using an unmanaged or managed Kubernetes service (like OpenShift, etc.)
- Migrating from one cloud provider or service to another
- Testing on a single local machine or deploying to a dozen clusters in production
In short: local or global, single or multi-cloud, managed or unmanaged — Backstage always provides a seamless Kubernetes experience for your service owners day-to-day development needs.
## Rethinking the developer experience
The philosophy behind Backstage is simple: improve developer experience by reducing infrastructure complexity. As popular and widespread as Kubernetes has become, all of the tools to date have been geared toward the needs of cluster admins. These tools add unnecessary complexity to the workflows of the typical developer building, testing, and deploying services.
We believe Backstage Kubernetes gives developers back control of their services by providing a more focused and consistent experience. Backstage provides a single standard for developers to monitor their Kubernetes deployments, regardless of the underlying cloud infrastructure.
## Future iterations
The current focus of Kubernetes in Backstage is Deployments/ReplicaSets/Pods — but we know that not everyone utilizes these.
As we continue to grow and develop Kubernetes in Backstage with the community, we hope to offer support for Kubernetes resources beyond Deployments and Custom Resource Definitions. You can browse or add open issues for the plugin [here].
## Getting started
We made the Kubernetes plugin a core feature of Backstage. Like Software Templates (scaffolder) and TechDocs, the k8s-plugin is installed with the core app. When you update the app to the latest version and go to the Kubernetes tab of any service, you will be asked to provide your cloud provider credentials. To learn more, including details on configuration and surfacing your Kubernetes components as part of an entity, [read the docs].
To contribute or get more information on Kubernetes in Backstage, [join the discussion on Discord]!
[here]: https://github.com/backstage/backstage/issues?q=is%3Aissue+is%3Aopen+kubernetes+label%3Ak8s-plugin
[read the docs]: https://backstage.io/docs/features/kubernetes/overview
[join the discussion on discord]: https://discord.gg/MUpMjP2
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 359 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

+1 -1
View File
@@ -15,7 +15,7 @@
},
"devDependencies": {
"@spotify/prettier-config": "^9.0.0",
"docusaurus": "^2.0.0-alpha.378053ac5",
"docusaurus": "^2.0.0-alpha.70",
"js-yaml": "^4.0.0",
"prettier": "^2.2.1"
},
+4 -4
View File
@@ -100,9 +100,9 @@ const Background = props => {
width="560"
height="315"
src="https://www.youtube.com/embed/YLAd5hdXR_Q"
frameborder="0"
frameBorder="0"
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
allowFullScreen
></iframe>
</Block.MediaFrame>
</Block.Container>
@@ -135,9 +135,9 @@ const Background = props => {
width="560"
height="315"
src="https://www.youtube.com/embed/mOLCgdPw1iA"
frameborder="0"
frameBorder="0"
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
allowFullScreen
></iframe>
</Block.MediaFrame>
</Block.Container>
@@ -0,0 +1,15 @@
const React = require('react');
const Redirect = require('../../../../../core/Redirect.js');
const siteConfig = require(process.cwd() + '/siteConfig.js');
function Docs() {
return (
<Redirect
redirect="/docs/features/software-catalog/software-catalog-overview"
config={siteConfig}
/>
);
}
module.exports = Docs;
@@ -0,0 +1,15 @@
const React = require('react');
const Redirect = require('../../../../../core/Redirect.js');
const siteConfig = require(process.cwd() + '/siteConfig.js');
function Docs() {
return (
<Redirect
redirect="/docs/features/software-templates/software-templates-index"
config={siteConfig}
/>
);
}
module.exports = Docs;
@@ -0,0 +1,15 @@
const React = require('react');
const Redirect = require('../../../../../core/Redirect.js');
const siteConfig = require(process.cwd() + '/siteConfig.js');
function Docs() {
return (
<Redirect
redirect="/docs/features/techdocs/techdocs-overview"
config={siteConfig}
/>
);
}
module.exports = Docs;
+66
View File
@@ -379,6 +379,72 @@ class Index extends React.Component {
</ActionBlock.Link>
</ActionBlock>
<Block className="stripe-top bg-teal-bottom" wrapped>
<Block.Container wrapped>
<Block.TextBox wide>
<img
className="Block__GIF"
src={`${baseUrl}animations/backstage-kubernetes-icon-1.gif`}
/>
<Block.Subtitle>Backstage Kubernetes</Block.Subtitle>
<Block.Title small>
Manage your services, not clusters
</Block.Title>
</Block.TextBox>
<Block.TextBox small>
<BulletLine />
<Block.SmallTitle small>
Kubernetes made just for service owners
</Block.SmallTitle>
<Block.Paragraph>
Backstage features the first Kubernetes monitoring tool designed
around the needs of service owners, not cluster admins
</Block.Paragraph>
</Block.TextBox>
<Block.TextBox small>
<BulletLine />
<Block.SmallTitle small>
Your service at a glance
</Block.SmallTitle>
<Block.Paragraph>
Get all your service's deployments in one, aggregated view no
more digging through cluster logs in a CLI, no more combing
through lists of services you don't own
</Block.Paragraph>
</Block.TextBox>
<Block.TextBox small>
<BulletLine />
<Block.SmallTitle small>Pick a cloud, any cloud</Block.SmallTitle>
<Block.Paragraph>
Since Backstage uses the Kubernetes API, it's cloud agnostic
so it works no matter which cloud provide or managed Kubernetes
service you use, and even works in multi-cloud orgs
</Block.Paragraph>
</Block.TextBox>
<Block.TextBox small>
<BulletLine />
<Block.SmallTitle small>Any K8s, one UI</Block.SmallTitle>
<Block.Paragraph>
Now you don't have to switch dashboards when you move from local
testing to production, or from one cloud provider to another
</Block.Paragraph>
</Block.TextBox>
</Block.Container>
</Block>
<ActionBlock className="stripe bg-teal">
<ActionBlock.Title>Learn more about the K8s plugin</ActionBlock.Title>
<ActionBlock.Link
href={`https://backstage.io/blog/2021/01/12/new-backstage-feature-kubernetes-for-service-owners`}
>
Read
</ActionBlock.Link>
</ActionBlock>
<Block className="stripe-top bg-teal-bottom" wrapped>
<Block.Container wrapped>
<Block.TextBox wide>
+16
View File
@@ -36,6 +36,11 @@
],
"CLI": ["cli/index", "cli/commands"],
"Core Features": [
{
"type": "subcategory",
"label": "Kubernetes",
"ids": ["features/kubernetes/overview"]
},
{
"type": "subcategory",
"label": "Software Catalog",
@@ -67,6 +72,14 @@
"features/software-templates/extending/extending-preparer"
]
},
{
"type": "subcategory",
"label": "Backstage Search",
"ids": [
"features/search/search-overview",
"features/search/architecture"
]
},
{
"type": "subcategory",
"label": "TechDocs",
@@ -78,6 +91,7 @@
"features/techdocs/creating-and-publishing",
"features/techdocs/configuration",
"features/techdocs/using-cloud-storage",
"features/techdocs/how-to-guides",
"features/techdocs/troubleshooting",
"features/techdocs/faqs"
]
@@ -90,6 +104,7 @@
"plugins/plugin-development",
"plugins/structure-of-a-plugin",
"plugins/integrating-plugin-into-service-catalog",
"plugins/composability",
{
"type": "subcategory",
"label": "Backends and APIs",
@@ -171,6 +186,7 @@
],
"Contribute": ["../CONTRIBUTING"],
"Support": ["support/support", "support/project-structure"],
"Glossary": ["glossary"],
"FAQ": ["FAQ"]
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

+5
View File
@@ -48,6 +48,11 @@ h6 {
color: $textColor;
}
summary {
color: $textColor;
cursor: pointer;
}
h2:hover .hash-link {
opacity: 1;
}
+486 -567
View File
File diff suppressed because it is too large Load Diff
+8
View File
@@ -48,6 +48,9 @@ nav:
- Create your own Templater: 'features/software-templates/extending/create-your-own-templater.md'
- Create your own Publisher: 'features/software-templates/extending/create-your-own-publisher.md'
- Create your own Preparer: 'features/software-templates/extending/create-your-own-preparer.md'
- Backstage Search:
- Overview: 'features/search/README.md'
- Architecture: 'features/search/architecture.md'
- TechDocs:
- Overview: 'features/techdocs/README.md'
- Getting Started: 'features/techdocs/getting-started.md'
@@ -56,14 +59,18 @@ nav:
- Creating and Publishing Documentation: 'features/techdocs/creating-and-publishing.md'
- Configuration: 'features/techdocs/configuration.md'
- Using Cloud Storage: 'features/techdocs/using-cloud-storage.md'
- HOW TO guides: 'features/techdocs/how-to-guides.md'
- Troubleshooting: 'features/techdocs/troubleshooting.md'
- FAQ: 'features/techdocs/FAQ.md'
- Kubernetes:
- Overview: 'features/kubernetes/index.md'
- Plugins:
- Overview: 'plugins/index.md'
- Existing plugins: 'plugins/existing-plugins.md'
- Creating a new plugin: 'plugins/create-a-plugin.md'
- Developing a plugin: 'plugins/plugin-development.md'
- Structure of a plugin: 'plugins/structure-of-a-plugin.md'
- Composability System Migration: 'plugins/composability.md'
- Backends and APIs:
- Proxying: 'plugins/proxying.md'
- Backstage backend plugin: 'plugins/backend-plugin.md'
@@ -115,4 +122,5 @@ nav:
- Support:
- 'support/support.md'
- 'support/project-structure.md'
- Glossary: glossary.md
- FAQ: FAQ.md
+1 -2
View File
@@ -20,7 +20,6 @@
"lint:all": "lerna run lint --",
"lint:type-deps": "node scripts/check-type-dependencies.js",
"docgen": "lerna run docgen",
"docker-build:app": "yarn workspace example-app build && docker build . -t spotify/backstage",
"docker-build": "yarn tsc && yarn workspace example-backend build-image",
"create-plugin": "backstage-cli create-plugin --scope backstage --no-private",
"remove-plugin": "backstage-cli remove-plugin",
@@ -43,7 +42,7 @@
"version": "1.0.0",
"devDependencies": {
"@changesets/cli": "^2.11.0",
"@octokit/openapi-types": "^2.0.0",
"@octokit/openapi-types": "^2.2.0",
"@spotify/eslint-config-oss": "^1.0.1",
"@spotify/prettier-config": "^9.0.0",
"command-exists": "^1.2.9",
+1 -1
View File
@@ -36,7 +36,7 @@
"@backstage/theme": "^0.2.2",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@octokit/rest": "^18.0.0",
"@octokit/rest": "^18.0.12",
"@roadiehq/backstage-plugin-buildkite": "^0.1.3",
"@roadiehq/backstage-plugin-github-insights": "^0.2.16",
"@roadiehq/backstage-plugin-github-pull-requests": "^0.6.3",
+7
View File
@@ -1,5 +1,12 @@
# @backstage/backend-common
## 0.4.3
### Patch Changes
- Updated dependencies [466354aaa]
- @backstage/integration@0.2.0
## 0.4.2
### Patch Changes
+3 -3
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/backend-common",
"description": "Common functionality library for Backstage backends",
"version": "0.4.2",
"version": "0.4.3",
"main": "src/index.ts",
"types": "src/index.ts",
"private": false,
@@ -32,7 +32,7 @@
"@backstage/cli-common": "^0.1.1",
"@backstage/config": "^0.1.2",
"@backstage/config-loader": "^0.4.1",
"@backstage/integration": "^0.1.5",
"@backstage/integration": "^0.2.0",
"@types/cors": "^2.8.6",
"@types/express": "^4.17.6",
"archiver": "^5.0.2",
@@ -66,7 +66,7 @@
}
},
"devDependencies": {
"@backstage/cli": "^0.4.5",
"@backstage/cli": "^0.4.6",
"@backstage/test-utils": "^0.1.5",
"@types/archiver": "^5.1.0",
"@types/compression": "^1.7.0",
@@ -23,7 +23,7 @@ import {
readBitbucketIntegrationConfigs,
} from '@backstage/integration';
import fetch from 'cross-fetch';
import parseGitUri from 'git-url-parse';
import parseGitUrl from 'git-url-parse';
import { Readable } from 'stream';
import { NotFoundError } from '../errors';
import { ReadTreeResponseFactory } from './tree';
@@ -101,8 +101,9 @@ export class BitbucketUrlReader implements UrlReader {
url: string,
options?: ReadTreeOptions,
): Promise<ReadTreeResponse> {
const gitUrl: parseGitUri.GitUrl = parseGitUri(url);
const { name: repoName, owner: project, resource, filepath } = gitUrl;
const { name: repoName, owner: project, resource, filepath } = parseGitUrl(
url,
);
const isHosted = resource === 'bitbucket.org';
@@ -142,7 +143,7 @@ export class BitbucketUrlReader implements UrlReader {
}
private async getLastCommitShortHash(url: string): Promise<String> {
const { name: repoName, owner: project, ref } = parseGitUri(url);
const { name: repoName, owner: project, ref } = parseGitUrl(url);
let branch = ref;
if (!branch) {
@@ -15,6 +15,7 @@
*/
import { ConfigReader } from '@backstage/config';
import { GithubCredentialsProvider } from '@backstage/integration';
import { msw } from '@backstage/test-utils';
import fs from 'fs';
import { rest } from 'msw';
@@ -28,6 +29,18 @@ const treeResponseFactory = ReadTreeResponseFactory.create({
});
describe('GithubUrlReader', () => {
const mockCredentialsProvider = ({
getCredentials: jest.fn().mockResolvedValue({ headers: {} }),
} as unknown) as GithubCredentialsProvider;
const worker = setupServer();
msw.setupDefaultHandlers(worker);
beforeEach(() => {
jest.clearAllMocks();
});
describe('implementation', () => {
it('rejects unknown targets', async () => {
const processor = new GithubUrlReader(
@@ -35,7 +48,7 @@ describe('GithubUrlReader', () => {
host: 'github.com',
apiBaseUrl: 'https://api.github.com',
},
{ treeResponseFactory },
{ treeResponseFactory, credentialsProvider: mockCredentialsProvider },
);
await expect(
processor.read('https://not.github.com/apa'),
@@ -45,11 +58,52 @@ describe('GithubUrlReader', () => {
});
});
describe('read', () => {
it('should use the headers from the credentials provider to the fetch request when doing read', async () => {
expect.assertions(2);
const mockHeaders = {
Authorization: 'bearer blah',
otherheader: 'something',
};
(mockCredentialsProvider.getCredentials as jest.Mock).mockResolvedValue({
headers: mockHeaders,
});
worker.use(
rest.get(
'https://api.github.com/repos/backstage/mock/tree/contents/?ref=repo',
(req, res, ctx) => {
expect(req.headers.get('authorization')).toBe(
mockHeaders.Authorization,
);
expect(req.headers.get('otherheader')).toBe(
mockHeaders.otherheader,
);
return res(
ctx.status(200),
ctx.set('Content-Type', 'application/x-gzip'),
ctx.body('foo'),
);
},
),
);
const processor = new GithubUrlReader(
{
host: 'ghe.github.com',
apiBaseUrl: 'https://api.github.com',
},
{ treeResponseFactory, credentialsProvider: mockCredentialsProvider },
);
await processor.read(
'https://ghe.github.com/backstage/mock/tree/blob/repo',
);
});
});
describe('readTree', () => {
const worker = setupServer();
msw.setupDefaultHandlers(worker);
const repoBuffer = fs.readFileSync(
path.resolve('src', 'reading', '__fixtures__', 'repo.tar.gz'),
);
@@ -74,7 +128,7 @@ describe('GithubUrlReader', () => {
host: 'github.com',
apiBaseUrl: 'https://api.github.com',
},
{ treeResponseFactory },
{ treeResponseFactory, credentialsProvider: mockCredentialsProvider },
);
const response = await processor.readTree(
@@ -110,7 +164,7 @@ describe('GithubUrlReader', () => {
host: 'ghe.github.com',
apiBaseUrl: 'https://api.github.com',
},
{ treeResponseFactory },
{ treeResponseFactory, credentialsProvider: mockCredentialsProvider },
);
const response = await processor.readTree(
@@ -125,13 +179,57 @@ describe('GithubUrlReader', () => {
expect(indexMarkdownFile.toString()).toBe('# Test\n');
});
it('should use the headers from the credentials provider to the fetch request', async () => {
expect.assertions(2);
const mockHeaders = {
Authorization: 'bearer blah',
otherheader: 'something',
};
(mockCredentialsProvider.getCredentials as jest.Mock).mockResolvedValue({
headers: mockHeaders,
});
worker.use(
rest.get(
'https://ghe.github.com/backstage/mock/archive/repo.tar.gz',
(req, res, ctx) => {
expect(req.headers.get('authorization')).toBe(
mockHeaders.Authorization,
);
expect(req.headers.get('otherheader')).toBe(
mockHeaders.otherheader,
);
return res(
ctx.status(200),
ctx.set('Content-Type', 'application/x-gzip'),
ctx.body(repoBuffer),
);
},
),
);
const processor = new GithubUrlReader(
{
host: 'ghe.github.com',
apiBaseUrl: 'https://api.github.com',
},
{ treeResponseFactory, credentialsProvider: mockCredentialsProvider },
);
await processor.readTree(
'https://ghe.github.com/backstage/mock/tree/repo/docs',
);
});
it('must specify a branch', async () => {
const processor = new GithubUrlReader(
{
host: 'github.com',
apiBaseUrl: 'https://api.github.com',
},
{ treeResponseFactory },
{ treeResponseFactory, credentialsProvider: mockCredentialsProvider },
);
await expect(
@@ -147,7 +245,7 @@ describe('GithubUrlReader', () => {
host: 'github.com',
apiBaseUrl: 'https://api.github.com',
},
{ treeResponseFactory },
{ treeResponseFactory, credentialsProvider: mockCredentialsProvider },
);
const response = await processor.readTree(

Some files were not shown because too many files have changed in this diff Show More