diff --git a/.changeset/chatty-ghosts-lay.md b/.changeset/chatty-ghosts-lay.md
deleted file mode 100644
index 7d8ef3edbd..0000000000
--- a/.changeset/chatty-ghosts-lay.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-sonarqube': patch
----
-
-Export isSonarQubeAvailable.
diff --git a/.changeset/clever-walls-bow.md b/.changeset/clever-walls-bow.md
deleted file mode 100644
index 36e56f6e37..0000000000
--- a/.changeset/clever-walls-bow.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-scaffolder-backend': patch
----
-
-Add built-in publish action for creating GitHub pull requests.
diff --git a/.changeset/dry-elephants-doubt.md b/.changeset/dry-elephants-doubt.md
new file mode 100644
index 0000000000..7a9ccc9892
--- /dev/null
+++ b/.changeset/dry-elephants-doubt.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-github-deployments': patch
+---
+
+Adds extraColumns field to GitHub Deployments card
diff --git a/.changeset/fair-carrots-tell.md b/.changeset/fair-carrots-tell.md
new file mode 100644
index 0000000000..62dce5c3ce
--- /dev/null
+++ b/.changeset/fair-carrots-tell.md
@@ -0,0 +1,5 @@
+---
+'@backstage/cli': patch
+---
+
+Add `config:docs` command that opens up reference documentation for the local configuration schema in a browser.
diff --git a/.changeset/fluffy-suns-repair.md b/.changeset/fluffy-suns-repair.md
new file mode 100644
index 0000000000..6f45905219
--- /dev/null
+++ b/.changeset/fluffy-suns-repair.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-scaffolder': patch
+---
+
+Respect top-level UI schema keys in scaffolder forms. Allows more advanced RJSF features such as explicit field ordering.
diff --git a/.changeset/fresh-cheetahs-rush.md b/.changeset/fresh-cheetahs-rush.md
new file mode 100644
index 0000000000..fe8a9caafb
--- /dev/null
+++ b/.changeset/fresh-cheetahs-rush.md
@@ -0,0 +1,5 @@
+---
+'@backstage/cli': patch
+---
+
+No longer add newly created plugins to `plugins.ts` in the app, as it is no longer needed.
diff --git a/.changeset/good-glasses-build.md b/.changeset/good-glasses-build.md
new file mode 100644
index 0000000000..3d506d3e43
--- /dev/null
+++ b/.changeset/good-glasses-build.md
@@ -0,0 +1,6 @@
+---
+'@backstage/core-api': patch
+'@backstage/core': patch
+---
+
+Add support for discovering plugins through the app element tree, removing the need to register them explicitly.
diff --git a/.changeset/loud-apricots-fix.md b/.changeset/loud-apricots-fix.md
deleted file mode 100644
index 3763ebf0e3..0000000000
--- a/.changeset/loud-apricots-fix.md
+++ /dev/null
@@ -1,31 +0,0 @@
----
-'@backstage/create-app': patch
-'@backstage/plugin-scaffolder': minor
----
-
-Expose the catalog-import route as an external route from the scaffolder.
-
-This will make it possible to hide the "Register Existing Component" button
-when you for example are running backstage with `catalog.readonly=true`.
-
-As a consequence of this change you need add a new binding to your createApp call to
-keep the button visible. However, if you instead want to hide the button you can safely
-ignore the following example.
-
-To bind the external route from the catalog-import plugin to the scaffolder template
-index page, make sure you have the appropriate imports and add the following
-to the createApp call:
-
-```typescript
-import { catalogImportPlugin } from '@backstage/plugin-catalog-import';
-
-const app = createApp({
- // ...
- bindRoutes({ bind }) {
- // ...
- bind(scaffolderPlugin.externalRoutes, {
- registerComponent: catalogImportPlugin.routes.importPage,
- });
- },
-});
-```
diff --git a/.changeset/moody-pigs-repeat.md b/.changeset/moody-pigs-repeat.md
new file mode 100644
index 0000000000..396fa80281
--- /dev/null
+++ b/.changeset/moody-pigs-repeat.md
@@ -0,0 +1,5 @@
+---
+'@backstage/core': patch
+---
+
+Exported SignInProviderConfig to strongly type SignInPage providers
diff --git a/.changeset/quiet-badgers-cheer.md b/.changeset/quiet-badgers-cheer.md
new file mode 100644
index 0000000000..e22bf9492c
--- /dev/null
+++ b/.changeset/quiet-badgers-cheer.md
@@ -0,0 +1,11 @@
+---
+'@backstage/create-app': patch
+---
+
+Removed `plugins.ts` from the app, as plugins are now discovered through the react tree.
+
+To apply this change to an existing app, simply delete `packages/app/src/plugins.ts` along with the import and usage in `packages/app/src/App.tsx`.
+
+Note that there are a few plugins that require explicit registration, in which case you would need to keep them in `plugins.ts`. The set of plugins that need explicit registration is any plugin that doesn't have a component extension that gets rendered as part of the app element tree. An example of such a plugin in the main Backstage repo is `@backstage/plugin-badges`. In the case of the badges plugin this is because there is not yet a component-based API for adding context menu items to the entity layout.
+
+If you have plugins that still rely on route registration through the `register` method of `createPlugin`, these need to be kept in `plugins.ts` as well. However, it is recommended to migrate these to export an extensions component instead.
diff --git a/.changeset/real-apples-visit.md b/.changeset/real-apples-visit.md
new file mode 100644
index 0000000000..b5165f633b
--- /dev/null
+++ b/.changeset/real-apples-visit.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-backend': patch
+---
+
+GithubDiscoveryProcessor now excludes archived repositories so they won't be added to Backstage.
diff --git a/.changeset/rude-items-bow.md b/.changeset/rude-items-bow.md
new file mode 100644
index 0000000000..69b76d821e
--- /dev/null
+++ b/.changeset/rude-items-bow.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-kubernetes-backend': patch
+---
+
+Kubernetes client TLS verification is now configurable and defaults to true
diff --git a/.changeset/six-turtles-sip.md b/.changeset/six-turtles-sip.md
new file mode 100644
index 0000000000..e71b04e64d
--- /dev/null
+++ b/.changeset/six-turtles-sip.md
@@ -0,0 +1,6 @@
+---
+'@backstage/backend-common': patch
+---
+
+Support configuration of file storage for SQLite databases. Every plugin has its
+own database file at the specified path.
diff --git a/.changeset/sour-plums-enjoy.md b/.changeset/sour-plums-enjoy.md
new file mode 100644
index 0000000000..ef68166255
--- /dev/null
+++ b/.changeset/sour-plums-enjoy.md
@@ -0,0 +1,7 @@
+---
+'@backstage/create-app': patch
+---
+
+Fix system diagram card to be on the system page
+
+To apply the same fix to an existing application, in `EntityPage.tsx` simply move the `` for the `/diagram` path from the `groupPage` down into the `systemPage` element.
diff --git a/.changeset/stale-carpets-poke.md b/.changeset/stale-carpets-poke.md
new file mode 100644
index 0000000000..4f39e57b71
--- /dev/null
+++ b/.changeset/stale-carpets-poke.md
@@ -0,0 +1,30 @@
+---
+'@backstage/plugin-catalog-backend': patch
+---
+
+Externalize repository processing for BitbucketDiscoveryProcessor.
+
+Add an extension point where you can customize how a matched Bitbucket repository should
+be processed. This can for example be used if you want to generate the catalog-info.yaml
+automatically based on other files in a repository, while taking advantage of the
+build-in repository crawling functionality.
+
+`BitbucketDiscoveryProcessor.fromConfig` now takes an optional parameter `options.parser` where
+you can customize the logic for each repository found. The default parser has the same
+behaviour as before, where it emits an optional location for the matched repository
+and lets the other processors take care of further processing.
+
+```typescript
+const customRepositoryParser: BitbucketRepositoryParser = async function* customRepositoryParser({
+ client,
+ repository,
+}) {
+ // Custom logic for interpret the matching repository.
+ // See defaultRepositoryParser for an example
+};
+
+const processor = BitbucketDiscoveryProcessor.fromConfig(env.config, {
+ parser: customRepositoryParser,
+ logger: env.logger,
+});
+```
diff --git a/.changeset/stale-chefs-retire.md b/.changeset/stale-chefs-retire.md
new file mode 100644
index 0000000000..e43c20062a
--- /dev/null
+++ b/.changeset/stale-chefs-retire.md
@@ -0,0 +1,5 @@
+---
+'@backstage/core': patch
+---
+
+Adding close button on support menu
diff --git a/.changeset/techdocs-swift-mugs-invent.md b/.changeset/techdocs-swift-mugs-invent.md
new file mode 100644
index 0000000000..912089fb3c
--- /dev/null
+++ b/.changeset/techdocs-swift-mugs-invent.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-techdocs': patch
+---
+
+Add a test id to the shadow root element of the Reader to access it easily in e2e tests
diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt
index b0bcf9c9f7..226af9bf0a 100644
--- a/.github/styles/vocab.txt
+++ b/.github/styles/vocab.txt
@@ -13,6 +13,7 @@ Cloudformation
Codecov
Codehilite
Config
+Debounce
Discoverability
Dockerfile
Dockerize
@@ -111,7 +112,9 @@ cookiecutter
css
dataflow
deadnaming
+debounce
declaratively
+deps
destructured
dev
devops
diff --git a/.tugboat/tugboat.app-config.production.yaml b/.tugboat/tugboat.app-config.production.yaml
index f606574d89..b264d53f3f 100644
--- a/.tugboat/tugboat.app-config.production.yaml
+++ b/.tugboat/tugboat.app-config.production.yaml
@@ -1,13 +1,10 @@
app:
title: Backstage Tugboat Preview
- baseUrl:
- $env: TUGBOAT_DEFAULT_SERVICE_URL
+ baseUrl: ${TUGBOAT_DEFAULT_SERVICE_URL}
backend:
- baseUrl:
- $env: TUGBOAT_DEFAULT_SERVICE_URL
+ baseUrl: ${TUGBOAT_DEFAULT_SERVICE_URL}
cors:
- origin:
- $env: TUGBOAT_DEFAULT_SERVICE_URL
+ origin: ${TUGBOAT_DEFAULT_SERVICE_URL}
methods: [GET, POST, PUT, DELETE]
credentials: true
diff --git a/ADOPTERS.md b/ADOPTERS.md
index 218a4edeba..45842b1833 100644
--- a/ADOPTERS.md
+++ b/ADOPTERS.md
@@ -24,3 +24,4 @@
| [Trendyol](https://trendyol.com) | [Erdogan Oksuz](https://github.com/erdoganoksuz) | The Developer Portal has been called `Pandora`. Provides an overview of Trendyol tech ecosystem. TechDocs, Catalog, Custom Plugins and Theme. |
| [Peloton](https://www.onepeloton.com/) | [Jim Haughwout](https://github.com/JimHaughwout) | Creating our first developer portal and tech-docs. Exploring Service Catalog, Tech Insights and Cost Insights as well. |
| [TELUS](https://telus.com) | [Seb Barre](https://github.com/sbarre) | The Go-to place to find answers about development and delivery at TELUS. |
+| [Brex](https://www.brex.com/) | [Vamsi Chitters](https://github.com/vamsikc) | A centralized UI to understand how a service fits in the whole Brex architecture and manage a teamโs engineering dependencies. |
diff --git a/app-config.yaml b/app-config.yaml
index 309fa28234..afbda40852 100644
--- a/app-config.yaml
+++ b/app-config.yaml
@@ -40,47 +40,40 @@ proxy:
'/circleci/api':
target: https://circleci.com/api/v1.1
headers:
- Circle-Token:
- $env: CIRCLECI_AUTH_TOKEN
+ Circle-Token: ${CIRCLECI_AUTH_TOKEN}
'/jenkins/api':
target: http://localhost:8080
headers:
- Authorization:
- $env: JENKINS_BASIC_AUTH_HEADER
+ Authorization: ${JENKINS_BASIC_AUTH_HEADER}
'/travisci/api':
target: https://api.travis-ci.com
changeOrigin: true
headers:
- Authorization:
- $env: TRAVISCI_AUTH_TOKEN
+ Authorization: ${TRAVISCI_AUTH_TOKEN}
travis-api-version: '3'
'/newrelic/apm/api':
target: https://api.newrelic.com/v2
headers:
- X-Api-Key:
- $env: NEW_RELIC_REST_API_KEY
+ X-Api-Key: ${NEW_RELIC_REST_API_KEY}
'/pagerduty':
target: https://api.pagerduty.com
headers:
- Authorization:
- $env: PAGERDUTY_TOKEN
+ Authorization: ${PAGERDUTY_TOKEN}
'/buildkite/api':
target: https://api.buildkite.com/v2/
headers:
- Authorization:
- $env: BUILDKITE_TOKEN
+ Authorization: ${BUILDKITE_TOKEN}
'/sentry/api':
target: https://sentry.io/api/
allowedMethods: ['GET']
headers:
- Authorization:
- $env: SENTRY_TOKEN
+ Authorization: ${SENTRY_TOKEN}
organization:
name: My Company
@@ -124,36 +117,28 @@ kafka:
integrations:
github:
- host: github.com
- token:
- $env: GITHUB_TOKEN
+ token: ${GITHUB_TOKEN}
### Example for how to add your GitHub Enterprise instance using the API:
# - host: ghe.example.net
# apiBaseUrl: https://ghe.example.net/api/v3
- # token:
- # $env: GHE_TOKEN
+ # token: ${GHE_TOKEN}
### Example for how to add your GitHub Enterprise instance using raw HTTP fetches (token is optional):
# - host: ghe.example.net
# rawBaseUrl: https://ghe.example.net/raw
- # token:
- # $env: GHE_TOKEN
+ # token: ${GHE_TOKEN}
gitlab:
- host: gitlab.com
- token:
- $env: GITLAB_TOKEN
+ token: ${GITLAB_TOKEN}
bitbucket:
- host: bitbucket.org
- username:
- $env: BITBUCKET_USERNAME
- appPassword:
- $env: BITBUCKET_APP_PASSWORD
+ username: ${BITBUCKET_USERNAME}
+ appPassword: ${BITBUCKET_APP_PASSWORD}
azure:
- host: dev.azure.com
- token:
- $env: AZURE_TOKEN
+ token: ${AZURE_TOKEN}
# googleGcs:
# clientEmail: 'example@example.com'
-# privateKey:
-# $env: GCS_PRIVATE_KEY
+# privateKey: ${GCS_PRIVATE_KEY}
catalog:
rules:
@@ -172,21 +157,18 @@ catalog:
githubOrg:
providers:
- target: https://github.com
- token:
- $env: GITHUB_TOKEN
+ token: ${GITHUB_TOKEN}
#### Example for how to add your GitHub Enterprise instance using the API:
# - target: https://ghe.example.net
# apiBaseUrl: https://ghe.example.net/api
- # token:
- # $env: GHE_TOKEN
+ # token: ${GHE_TOKEN}
ldapOrg:
### Example for how to add your enterprise LDAP server
# providers:
# - target: ldaps://ds.example.net
# bind:
# dn: uid=ldap-reader-user,ou=people,ou=example,dc=example,dc=net
- # secret:
- # $env: LDAP_SECRET
+ # secret: ${LDAP_SECRET}
# users:
# dn: ou=people,ou=example,dc=example,dc=net
# options:
@@ -202,12 +184,9 @@ catalog:
#providers:
# - target: https://graph.microsoft.com/v1.0
# authority: https://login.microsoftonline.com
- # tenantId:
- # $env: MICROSOFT_GRAPH_TENANT_ID
- # clientId:
- # $env: MICROSOFT_GRAPH_CLIENT_ID
- # clientSecret:
- # $env: MICROSOFT_GRAPH_CLIENT_SECRET_TOKEN
+ # tenantId: ${MICROSOFT_GRAPH_TENANT_ID}
+ # clientId: ${MICROSOFT_GRAPH_CLIENT_ID}
+ # clientSecret: ${MICROSOFT_GRAPH_CLIENT_SECRET_TOKEN}
# userFilter: accountEnabled eq true and userType eq 'member'
# groupFilter: securityEnabled eq false and mailEnabled eq true and groupTypes/any(c:c+eq+'Unified')
@@ -255,27 +234,22 @@ catalog:
scaffolder:
github:
- token:
- $env: GITHUB_TOKEN
+ token: ${GITHUB_TOKEN}
visibility: public # or 'internal' or 'private'
gitlab:
api:
baseUrl: https://gitlab.com
- token:
- $env: GITLAB_TOKEN
+ token: ${GITLAB_TOKEN}
visibility: public # or 'internal' or 'private'
azure:
baseUrl: https://dev.azure.com/{your-organization}
api:
- token:
- $env: AZURE_TOKEN
+ token: ${AZURE_TOKEN}
bitbucket:
api:
host: https://bitbucket.org
- username:
- $env: BITBUCKET_USERNAME
- token:
- $env: BITBUCKET_TOKEN
+ username: ${BITBUCKET_USERNAME}
+ token: ${BITBUCKET_TOKEN}
visibility: public # or or 'private'
auth:
@@ -286,89 +260,59 @@ auth:
providers:
google:
development:
- clientId:
- $env: AUTH_GOOGLE_CLIENT_ID
- clientSecret:
- $env: AUTH_GOOGLE_CLIENT_SECRET
+ clientId: ${AUTH_GOOGLE_CLIENT_ID}
+ clientSecret: ${AUTH_GOOGLE_CLIENT_SECRET}
github:
development:
- clientId:
- $env: AUTH_GITHUB_CLIENT_ID
- clientSecret:
- $env: AUTH_GITHUB_CLIENT_SECRET
- enterpriseInstanceUrl:
- $env: AUTH_GITHUB_ENTERPRISE_INSTANCE_URL
+ clientId: ${AUTH_GITHUB_CLIENT_ID}
+ clientSecret: ${AUTH_GITHUB_CLIENT_SECRET}
+ enterpriseInstanceUrl: ${AUTH_GITHUB_ENTERPRISE_INSTANCE_URL}
gitlab:
development:
- clientId:
- $env: AUTH_GITLAB_CLIENT_ID
- clientSecret:
- $env: AUTH_GITLAB_CLIENT_SECRET
- audience:
- $env: GITLAB_BASE_URL
+ clientId: ${AUTH_GITLAB_CLIENT_ID}
+ clientSecret: ${AUTH_GITLAB_CLIENT_SECRET}
+ audience: ${GITLAB_BASE_URL}
saml:
entryPoint: 'http://localhost:7001/'
issuer: 'passport-saml'
okta:
development:
- clientId:
- $env: AUTH_OKTA_CLIENT_ID
- clientSecret:
- $env: AUTH_OKTA_CLIENT_SECRET
- audience:
- $env: AUTH_OKTA_AUDIENCE
+ clientId: ${AUTH_OKTA_CLIENT_ID}
+ clientSecret: ${AUTH_OKTA_CLIENT_SECRET}
+ audience: ${AUTH_OKTA_AUDIENCE}
oauth2:
development:
- clientId:
- $env: AUTH_OAUTH2_CLIENT_ID
- clientSecret:
- $env: AUTH_OAUTH2_CLIENT_SECRET
- authorizationUrl:
- $env: AUTH_OAUTH2_AUTH_URL
- tokenUrl:
- $env: AUTH_OAUTH2_TOKEN_URL
+ clientId: ${AUTH_OAUTH2_CLIENT_ID}
+ clientSecret: ${AUTH_OAUTH2_CLIENT_SECRET}
+ authorizationUrl: ${AUTH_OAUTH2_AUTH_URL}
+ tokenUrl: ${AUTH_OAUTH2_TOKEN_URL}
###
# provide a list of scopes as needed for your OAuth2 Server:
#
# scope: saml-login-selector openid profile email
oidc:
development:
- metadataUrl:
- $env: AUTH_OIDC_METADATA_URL
- clientId:
- $env: AUTH_OIDC_CLIENT_ID
- clientSecret:
- $env: AUTH_OIDC_CLIENT_SECRET
- authorizationUrl:
- $env: AUTH_OIDC_AUTH_URL
- tokenUrl:
- $env: AUTH_OIDC_TOKEN_URL
- tokenSignedResponseAlg:
- $env: AUTH_OIDC_TOKEN_SIGNED_RESPONSE_ALG
+ metadataUrl: ${AUTH_OIDC_METADATA_URL}
+ clientId: ${AUTH_OIDC_CLIENT_ID}
+ clientSecret: ${AUTH_OIDC_CLIENT_SECRET}
+ authorizationUrl: ${AUTH_OIDC_AUTH_URL}
+ tokenUrl: ${AUTH_OIDC_TOKEN_URL}
+ tokenSignedResponseAlg: ${AUTH_OIDC_TOKEN_SIGNED_RESPONSE_ALG}
auth0:
development:
- clientId:
- $env: AUTH_AUTH0_CLIENT_ID
- clientSecret:
- $env: AUTH_AUTH0_CLIENT_SECRET
- domain:
- $env: AUTH_AUTH0_DOMAIN
+ clientId: ${AUTH_AUTH0_CLIENT_ID}
+ clientSecret: ${AUTH_AUTH0_CLIENT_SECRET}
+ domain: ${AUTH_AUTH0_DOMAIN}
microsoft:
development:
- clientId:
- $env: AUTH_MICROSOFT_CLIENT_ID
- clientSecret:
- $env: AUTH_MICROSOFT_CLIENT_SECRET
- tenantId:
- $env: AUTH_MICROSOFT_TENANT_ID
+ clientId: ${AUTH_MICROSOFT_CLIENT_ID}
+ clientSecret: ${AUTH_MICROSOFT_CLIENT_SECRET}
+ tenantId: ${AUTH_MICROSOFT_TENANT_ID}
onelogin:
development:
- clientId:
- $env: AUTH_ONELOGIN_CLIENT_ID
- clientSecret:
- $env: AUTH_ONELOGIN_CLIENT_SECRET
- issuer:
- $env: AUTH_ONELOGIN_ISSUER
+ clientId: ${AUTH_ONELOGIN_CLIENT_ID}
+ clientSecret: ${AUTH_ONELOGIN_CLIENT_SECRET}
+ issuer: ${AUTH_ONELOGIN_ISSUER}
costInsights:
engineerCost: 200000
products:
diff --git a/contrib/chart/backstage/values.yaml b/contrib/chart/backstage/values.yaml
index 7b26d2bccd..bd80bc22b6 100644
--- a/contrib/chart/backstage/values.yaml
+++ b/contrib/chart/backstage/values.yaml
@@ -127,68 +127,47 @@ appConfig:
development:
appOrigin: 'http://localhost:3000/'
secure: false
- clientId:
- $env: AUTH_GOOGLE_CLIENT_ID
- clientSecret:
- $env: AUTH_GOOGLE_CLIENT_SECRET
+ clientId: ${AUTH_GOOGLE_CLIENT_ID}
+ clientSecret: ${AUTH_GOOGLE_CLIENT_SECRET}
github:
development:
appOrigin: 'http://localhost:3000/'
secure: false
- clientId:
- $env: AUTH_GITHUB_CLIENT_ID
- clientSecret:
- $env: AUTH_GITHUB_CLIENT_SECRET
- enterpriseInstanceUrl:
- $env: AUTH_GITHUB_ENTERPRISE_INSTANCE_URL
+ clientId: ${AUTH_GITHUB_CLIENT_ID}
+ clientSecret: ${AUTH_GITHUB_CLIENT_SECRET}
+ enterpriseInstanceUrl: ${AUTH_GITHUB_ENTERPRISE_INSTANCE_URL}
gitlab:
development:
appOrigin: 'http://localhost:3000/'
secure: false
- clientId:
- $env: AUTH_GITLAB_CLIENT_ID
- clientSecret:
- $env: AUTH_GITLAB_CLIENT_SECRET
- audience:
- $env: GITLAB_BASE_URL
+ clientId: ${AUTH_GITLAB_CLIENT_ID}
+ clientSecret: ${AUTH_GITLAB_CLIENT_SECRET}
+ audience: ${GITLAB_BASE_URL}
okta:
development:
appOrigin: 'http://localhost:3000/'
secure: false
- clientId:
- $env: AUTH_OKTA_CLIENT_ID
- clientSecret:
- $env: AUTH_OKTA_CLIENT_SECRET
- audience:
- $env: AUTH_OKTA_AUDIENCE
+ clientId: ${AUTH_OKTA_CLIENT_ID}
+ clientSecret: ${AUTH_OKTA_CLIENT_SECRET}
+ audience: ${AUTH_OKTA_AUDIENCE}
oauth2:
development:
appOrigin: 'http://localhost:3000/'
secure: false
- clientId:
- $env: AUTH_OAUTH2_CLIENT_ID
- clientSecret:
- $env: AUTH_OAUTH2_CLIENT_SECRET
- authorizationURL:
- $env: AUTH_OAUTH2_AUTH_URL
- tokenURL:
- $env: AUTH_OAUTH2_TOKEN_URL
+ clientId: ${AUTH_OAUTH2_CLIENT_ID}
+ clientSecret: ${AUTH_OAUTH2_CLIENT_SECRET}
+ authorizationURL: ${AUTH_OAUTH2_AUTH_URL}
+ tokenURL: ${AUTH_OAUTH2_TOKEN_URL}
auth0:
development:
- clientId:
- $env: AUTH_AUTH0_CLIENT_ID
- clientSecret:
- $env: AUTH_AUTH0_CLIENT_SECRET
- domain:
- $env: AUTH_AUTH0_DOMAIN
+ clientId: ${AUTH_AUTH0_CLIENT_ID}
+ clientSecret: ${AUTH_AUTH0_CLIENT_SECRET}
+ domain: ${AUTH_AUTH0_DOMAIN}
microsoft:
development:
- clientId:
- $env: AUTH_MICROSOFT_CLIENT_ID
- clientSecret:
- $env: AUTH_MICROSOFT_CLIENT_SECRET
- tenantId:
- $env: AUTH_MICROSOFT_TENANT_ID
+ clientId: ${AUTH_MICROSOFT_CLIENT_ID}
+ clientSecret: ${AUTH_MICROSOFT_CLIENT_SECRET}
+ tenantId: ${AUTH_MICROSOFT_TENANT_ID}
auth:
google:
diff --git a/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md b/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md
new file mode 100644
index 0000000000..ba0aef89ea
--- /dev/null
+++ b/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md
@@ -0,0 +1,170 @@
+# Using AWS Application Load Balancer with Azure Active Directory to authenticate requests
+
+Backstage allows offloading the responsibility of authenticating users to an AWS Application Load Balancer (**ALB**), leveraging the authentication support on ALB.
+This tutorial shows how to use authentication on an ALB sitting in front of Backstage.
+Azure Active Directory (**AAD**) is used as identity provider but any identity provider supporting OpenID Connect (OIDC) can be used.
+
+It is assumed an ALB is already serving traffic in front of a Backstage instance configured to serve the frontend app from the backend.
+
+## Infrastructure setup
+
+### AAD App
+
+The AAD App is used to execute the authentication flow, serve and refresh the identity token.
+
+Create the AAD App following the steps outlined in `Create a Microsoft App Registration in Microsoft Portal` section from the tutorial [Monorepo App Setup With Authentication][monorepo-app-setup-with-auth].
+
+Instead of `localhost` addresses, use the following values.
+
+- Identifier URI: `https://backstage.yourdomain.com`
+- Redirect URI: `https://backstage.yourdomain.com/oauth2/idpresponse`
+
+`Application (client) Id`, `Directory (tenant) ID` and `client secret`values will be used while configuring the ALB.
+
+### ALB
+
+In the AWS console, configure ALB Authentication:
+
+- Edit the ALB rule used to forward the traffic to Backstage and add a new `Authenticate` action. The action will have higher priority compared to the existing `Forward to`.
+- Select `OIDC` under `Authenticate`
+- Set `Issuer` to `https://login.microsoftonline.com/{TENANT_ID}/v2.0`, replacing `{TENANT_ID}` with the value of `Directory (tenant) ID` of the AAD App.
+- Set `Authorization endpoint` to `https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/authorize`, replacing `{TENANT_ID}` with the value of `Directory (tenant) ID` of the AAD App.
+- Set `Token endpoint` to `https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/authorize`, replacing `{TENANT_ID}` with the value of `Directory (tenant) ID` of the AAD App.
+- Set `User info endpoint` to `https://graph.microsoft.com/oidc/userinfo`
+- Set `Client ID` to the AAD App `Application (client) Id`
+- Set `Client secret` to the AAD APP `client secret`
+
+Use the following advanced settings:
+
+- `Session cookie name` = `AWSELBAuthSessionCookie`
+- `Session timeout` = `604800` seconds
+- `Scope` = `openid profile offline_access`
+- `Action on unauthenticated request` = `Autenticate (client reattempt)`
+
+Once you've saved the action, you should see an authentication flow be triggered against AAD when visiting Backstage address at `https://backstage.yourdomain.com`. The flow will not complete successfully as the Backstage app isn't yet configured properly.
+
+## Backstage changes
+
+### Frontend
+
+The Backstage App needs a SignInPage when authentication is required.
+When using ALB authentication Backstage will only be loaded once the user has successfully authenticated; we won't need to display a SignIn page, however we will need to create a dummy SignIn component that can refresh the token.
+
+- edit `packages/app/src/App.tsx`
+- import the following two additional definitions from `@backstage/core`: `useApi`, `configApiRef`; these will be used to check whether Backstage is running locally or behind an ALB
+- add the following definition just before the app is created (`const app = createApp`):
+
+```ts
+const DummySignInComponent: any = (props: any) => {
+ try {
+ const config = useApi(configApiRef);
+ const shouldAuth = !!config.getOptionalConfig('auth.providers.awsalb');
+ if (shouldAuth) {
+ fetch(`${window.location.origin}/api/auth/awsalb/refresh`)
+ .then(data => data.json())
+ .then(data => {
+ props.onResult({
+ userId: data.backstageIdentity.id,
+ profile: data.profile,
+ });
+ });
+ } else {
+ props.onResult({
+ userId: 'guest',
+ profile: {
+ email: 'guest@example.com',
+ displayName: 'Guest',
+ picture: '',
+ },
+ });
+ }
+ return ;
+ } catch (err) {
+ return
{err.message}
;
+ }
+};
+```
+
+- add `DummySignInComponent` as `SignInPage`:
+
+```ts
+const app = createApp({
+ ...
+ components: {
+ SignInPage: DummySignInComponent,
+ ...
+ },
+ ...
+});
+```
+
+### Backend
+
+When using ALB auth it is not possible to leverage the built-in auth config discovery mechanism implemented in the app created by default; bespoke logic needs to be implemented.
+
+- replace the content of `packages/backend/plugin/auth.ts` with the below
+
+```ts
+import {
+ createRouter,
+ AuthResponse,
+ AuthProviderFactoryOptions,
+ defaultAuthProviderFactories,
+} from '@backstage/plugin-auth-backend';
+import { PluginEnvironment } from '../types';
+
+export default async function createPlugin({
+ logger,
+ database,
+ config,
+ discovery,
+}: PluginEnvironment) {
+ const identityResolver = (payload: any): Promise> => {
+ return Promise.resolve({
+ providerInfo: {},
+ profile: {
+ email: payload.email,
+ displayName: payload.name,
+ picture: payload.picture,
+ },
+ backstageIdentity: {
+ id: payload.email,
+ },
+ });
+ };
+ const providerFactories = {
+ awsalb: (options: AuthProviderFactoryOptions) =>
+ defaultAuthProviderFactories.awsalb({ ...options, identityResolver }),
+ };
+ return await createRouter({
+ logger,
+ config,
+ database,
+ discovery,
+ providerFactories,
+ });
+}
+```
+
+### Configuration
+
+Use the following `auth` configuration when running Backstage on AWS:
+
+```yaml
+auth:
+ providers:
+ awsalb:
+ issuer:
+ issuer: https://login.microsoftonline.com/{TENANT_ID}/v2.0
+ region: { AWS_REGION }
+```
+
+Replace `{TENANT_ID}` with the value of `Directory (tenant) ID` of the AAD App and `{AWS_REGION}` with the AWS region identifier where the ALB is deployed (for example: `eu-central-1`).
+
+## Conclusion
+
+Once it's deployed, after going through the AAD authentication flow, Backstage should display the AAD user details.
+
+
+
+[monorepo-app-setup-with-auth-ms]: https://backstage.io/docs/tutorials/quickstart-app-auth#the-auth-configuration
diff --git a/contrib/docs/tutorials/aws-deployment.md b/contrib/docs/tutorials/aws-deployment.md
index 5f94672b82..47170658a3 100644
--- a/contrib/docs/tutorials/aws-deployment.md
+++ b/contrib/docs/tutorials/aws-deployment.md
@@ -41,12 +41,9 @@ documentation to build a new Backstage Docker image:
```shell
$ yarn build
-$ docker image build . -f packages/backend/Dockerfile --tag backstage
+$ yarn build-image --tag backstage
```
-This command builds a backend-only image, but you can similarly build a frontend
-or combined Docker image.
-
Next, configure the [AWS CLI](https://aws.amazon.com/cli/) to use the
`ecr-publisher` user you created:
@@ -90,65 +87,37 @@ document, but it can be as easy as `eksctl create cluster` documented in the
guide](https://docs.aws.amazon.com/eks/latest/userguide/getting-started-eksctl.html),
which uses a Cloudformation template to create the necessary resources.
-To deploy the Docker image to EKS, create a `kubernetes` folder in your
-Backstage source folder and add a Kubernetes `deployment.yaml`:
+To deploy the Docker image to EKS, follow the [Kubernetes
+guide](https://backstage.io/docs/deployment/k8s#creating-the-backstage-instance)
+but set the Backstage deployment `image` to the ECR repository URL:
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
- name: backstage-backend
- labels:
- app: backstage-backend
- namespace: default
+ name: backstage
+ namespace: backstage
spec:
- replicas: 1
- selector:
- matchLabels:
- app: backstage-backend
- strategy:
- rollingUpdate:
- maxSurge: 25%
- maxUnavailable: 25%
- type: RollingUpdate
+ ...
template:
metadata:
labels:
- app: backstage-backend
+ app: backstage
spec:
containers:
- image: /backstage:1.0.0
imagePullPolicy: Always
- name: backstage-backend
- ports:
- - containerPort: 7000
- protocol: TCP
+ ...
```
-Note the `image` key in the container spec referencing the ECR repository.
-
-Now create a simple `service.yaml` to map the container ports:
-
-```yaml
-apiVersion: v1
-kind: Service
-metadata:
- name: backstage-backend
-spec:
- selector:
- app: backstage-backend
- ports:
- - protocol: TCP
- port: 80
- targetPort: 7000
-```
-
-Apply these Kubernetes definitions to the EKS cluster to complete the Backstage
-deployment:
+Create the [Service
+descriptor](https://backstage.io/docs/deployment/k8s#creating-a-backstage-service)
+as well, and apply these Kubernetes definitions to the EKS cluster to complete
+the Backstage deployment:
```shell
-$ kubectl apply -f deployment.yaml
-$ kubectl apply -f service.yaml
+$ kubectl apply -f kubernetes/backstage.yaml
+$ kubectl apply -f kubernetes/backstage-service.yaml
```
Now you can see your Backstage workload running from the [EKS
@@ -158,14 +127,15 @@ console](https://console.aws.amazon.com/eks/home).
### Exposing Backstage with a load balancer
-Backstage users need to query the backend, which means we need to expose
-the workload with a load balancer. Follow the [Application load balancing on
+To make the service useful, we need to expose the workload with a load balancer.
+Follow the [Application load balancing on
EKS](https://docs.aws.amazon.com/eks/latest/userguide/alb-ingress.html) guide to
set up a Load Balancer controller and Kubernetes ingress to your application.
This is ultimately a `kubectl apply` with an ingress definition:
```yaml
+# kubernetes/backstage-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
@@ -187,9 +157,3 @@ spec:
port:
number: 80
```
-
-### Updating the deployment
-
-To update the Kubernetes deployment to a newly published version of your
-Backstage Docker image, update the image tag reference in `deployment.yaml` and
-then apply the changes to EKS with `kubectl apply -f deployment.yaml`.
diff --git a/contrib/kubernetes/plain_single_backend_deployment/README.md b/contrib/kubernetes/plain_single_backend_deployment/README.md
deleted file mode 100644
index e4709615ce..0000000000
--- a/contrib/kubernetes/plain_single_backend_deployment/README.md
+++ /dev/null
@@ -1,44 +0,0 @@
-# Plain Kubernetes Deployment
-
-This directory contains an example of a simple Kubernetes deployment of Backstage. It is not intended to serve as a complete production deployment, but as a starting point for setting one up.
-
-## Usage
-
-You can try the deployment out as is. The easiest way is to use [Docker Desktop](https://www.docker.com/products/docker-desktop) with [Kubernetes](https://docs.docker.com/get-started/kube-deploy/).
-
-You can now follow the documentation here to build the Backend Container [Docker Build](https://backstage.io/docs/getting-started/deployment-docker)
-
-From a fresh clone of this repo, run the following in the root:
-
-```bash
-yarn install
-
-yarn docker-build
-
-kubectl apply -f contrib/kubernetes/plain_single_backend_deployment/deployment.yaml
-```
-
-You can use the following commands to monitor the deployment:
-
-```bash
-# List all resources in the backstage namespace
-kubectl -n backstage get all
-
-# Inspect the status of the deployment resource
-kubectl -n backstage describe deployment backstage-backend
-
-# Inspect the status of the pod running the backstage backend
-kubectl -n backstage describe pod -l app=backstage,component=backend
-```
-
-Once the deployment is up and running, you can use the following to set up a proxy to reach the backend locally:
-
-```bash
-kubectl proxy
-```
-
-With the proxy up and running, you should be able to navigate to [http://localhost:8001/api/v1/namespaces/backstage/services/backstage-backend:http/proxy](http://localhost:8001/api/v1/namespaces/backstage/services/backstage-backend:http/proxy) and see Backstage. Note that you'll end up on a 404 page, but hitting the home icon in the sidebar should take you to the catalog page where you can see a few example services.
-
-## Caveats
-
-This deployment is for demonstration purposes only, for a production deployment you will need to set up at least a persistent database and some form of ingress. If your organization doesn't already have established patterns for these, you could look at options of managed PostgreSQL instances from cloud providers, or something like Zalando's [postgres-operator](https://github.com/zalando/postgres-operator). For ingress there are also [plenty of options](https://ramitsurana.gitbook.io/awesome-kubernetes/docs/projects/projects#load-balancing), where `nginx` is a popular choice to get started.
diff --git a/contrib/kubernetes/plain_single_backend_deployment/deployment.yaml b/contrib/kubernetes/plain_single_backend_deployment/deployment.yaml
deleted file mode 100644
index 822f5e42dc..0000000000
--- a/contrib/kubernetes/plain_single_backend_deployment/deployment.yaml
+++ /dev/null
@@ -1,107 +0,0 @@
----
-apiVersion: v1
-kind: Namespace
-metadata:
- name: backstage
----
-apiVersion: apps/v1
-kind: Deployment
-metadata:
- name: backstage-backend
- namespace: backstage
-spec:
- replicas: 1
- selector:
- matchLabels:
- app: backstage
- component: backend
- template:
- metadata:
- labels:
- app: backstage
- component: backend
- spec:
- containers:
- - name: backend
- # This image is built with `yarn docker-build` in the repo root.
- # Replace this with your own image to deploy your own Backstage app.
- image: example-backend:latest
- imagePullPolicy: Never
-
- command: [node, packages/backend]
- args: [--config, app-config.yaml, --config, k8s-config.yaml]
-
- env:
- # We set this to development to make the backend start with incomplete configuration. In a production
- # deployment you will want to make sure that you have a full configuration, and remove any plugins that
- # you are not using.
- - name: NODE_ENV
- value: development
-
- # This makes it possible for the app to reach the backend when serving through `kubectl proxy`
- # If you expose the service using for example an ingress controller, you should
- # switch this out or remove it.
- #
- # Note that we're not setting app.baseUrl here, as setting the base path is not working at the moment.
- # Further work is needed around the routing in the frontend or react-router before we can support that.
- - name: APP_CONFIG_backend_baseUrl
- value: http://localhost:8001/api/v1/namespaces/backstage/services/backstage-backend:http/proxy
-
- ports:
- - name: http
- containerPort: 7000
-
- volumeMounts:
- - name: config-volume
- mountPath: /app/k8s-config.yaml
- subPath: k8s-config.yaml
-
- resources:
- limits:
- cpu: 1
- memory: 0.5Gi
-
- readinessProbe:
- httpGet:
- port: 7000
- path: /healthcheck
- livenessProbe:
- httpGet:
- port: 7000
- path: /healthcheck
-
- volumes:
- - name: config-volume
- configMap:
- name: backstage-config
- items:
- - key: app-config
- path: k8s-config.yaml
----
-apiVersion: v1
-kind: ConfigMap
-metadata:
- name: backstage-config
- namespace: backstage
-data:
- # Note that the config here is only applied to the backend. The frontend config is applied at build time.
- # To override frontend config in this deployment, use `APP_CONFIG_` env vars.
- app-config: |
- app:
- baseUrl: http://localhost:8001/api/v1/namespaces/backstage/services/backstage-backend:http/proxy
- backend:
- baseUrl: http://localhost:8001/api/v1/namespaces/backstage/services/backstage-backend:http/proxy
----
-apiVersion: v1
-kind: Service
-metadata:
- name: backstage-backend
- namespace: backstage
-spec:
- selector:
- app: backstage
- component: backend
- ports:
- - name: http
- port: 80
- targetPort: http
diff --git a/docs/auth/auth-backend-classes.md b/docs/auth/auth-backend-classes.md
index a99ca119f0..5b5950f6c3 100644
--- a/docs/auth/auth-backend-classes.md
+++ b/docs/auth/auth-backend-classes.md
@@ -95,40 +95,27 @@ auth:
providers:
google:
development:
- clientId:
- $env: AUTH_GOOGLE_CLIENT_ID
- clientSecret:
- $env: AUTH_GOOGLE_CLIENT_SECRET
+ clientId: ${AUTH_GOOGLE_CLIENT_ID}
+ clientSecret: ${AUTH_GOOGLE_CLIENT_SECRET}
github:
development:
- clientId:
- $env: AUTH_GITHUB_CLIENT_ID
- clientSecret:
- $env: AUTH_GITHUB_CLIENT_SECRET
- enterpriseInstanceUrl:
- $env: AUTH_GITHUB_ENTERPRISE_INSTANCE_URL
+ clientId: ${AUTH_GITHUB_CLIENT_ID}
+ clientSecret: ${AUTH_GITHUB_CLIENT_SECRET}
+ enterpriseInstanceUrl: ${AUTH_GITHUB_ENTERPRISE_INSTANCE_URL}
gitlab:
development:
- clientId:
- $env:
+ clientId: ${AUTH_GITLAB_CLIENT_ID}
oauth2:
development:
- clientId:
- $env: AUTH_OAUTH2_CLIENT_ID
- clientSecret:
- $env: AUTH_OAUTH2_CLIENT_SECRET
- authorizationUrl:
- $env: AUTH_OAUTH2_AUTH_URL
- tokenUrl:
- $env: AUTH_OAUTH2_TOKEN_URL
- scope:
- $env: AUTH_OAUTH2_SCOPE
+ clientId: ${AUTH_OAUTH2_CLIENT_ID}
+ clientSecret: ${AUTH_OAUTH2_CLIENT_SECRET}
+ authorizationUrl: ${AUTH_OAUTH2_AUTH_URL}
+ tokenUrl: ${AUTH_OAUTH2_TOKEN_URL}
+ scope: ${AUTH_OAUTH2_SCOPE}
saml:
- entryPoint:
- $env: AUTH_SAML_ENTRY_POINT
- issuer:
- $env: AUTH_SAML_ISSUER
- ...
+ entryPoint: ${AUTH_SAML_ENTRY_POINT}
+ issuer: ${AUTH_SAML_ISSUER}
+ ...
```
## Implementing Your Own Auth Wrapper
diff --git a/docs/auth/index.md b/docs/auth/index.md
index fc6ce33e2b..f3783c79f3 100644
--- a/docs/auth/index.md
+++ b/docs/auth/index.md
@@ -58,7 +58,7 @@ the local `auth.environment` setting will be selected.
After configuring an authentication provider, the `app` frontend package needs a
small update to show this provider as a login option. The `SignInPage` component
handles this, and takes either a `provider` or `providers` (array) prop of
-`SignInConfig` definitions.
+`SignInProviderConfig` definitions.
These reference the [ApiRef](../reference/utility-apis/README.md) exported by
the provider. Again, an example using GitHub that can be adapted to any of the
@@ -66,9 +66,9 @@ built-in providers:
```diff
# packages/app/src/App.tsx
-+ import { githubAuthApiRef, SignInConfig, SignInPage } from '@backstage/core';
++ import { githubAuthApiRef, SignInProviderConfig, SignInPage } from '@backstage/core';
-+ const githubProvider: SignInConfig = {
++ const githubProvider: SignInProviderConfig = {
+ id: 'github-auth-provider',
+ title: 'GitHub',
+ message: 'Sign in using GitHub',
diff --git a/docs/cli/commands.md b/docs/cli/commands.md
index f5e63824a7..324d17463c 100644
--- a/docs/cli/commands.md
+++ b/docs/cli/commands.md
@@ -44,6 +44,7 @@ clean Delete cache directories
create-plugin Creates a new plugin in the current repository
remove-plugin Removes plugin in the current repository
+config:docs Browse the configuration reference documentation
config:print Print the app configuration for the current package
config:check Validate that the given configuration loads and matches schema
config:schema Dump the app configuration schema
@@ -54,6 +55,8 @@ versions:check Check Backstage package versioning
prepack Prepares a package for packaging before publishing
postpack Restores the changes made by the prepack command
+create-github-app Create new GitHub App in your organization (experimental)
+
help [command] display help for command
```
@@ -445,6 +448,25 @@ Options:
--backstage-cli-help display help for command
```
+## config:docs
+
+Scope: `root`
+
+This commands opens up the reference documentation of your apps local
+configuration schema in the browser. This is useful to get an overview of what
+configuration values are available to use, a description of what they do and
+their format, and where they get sent.
+
+```text
+Usage: backstage-cli config:docs [options]
+
+Browse the configuration reference documentation
+
+Options:
+ --package Only include the schema that applies to the given package
+ -h, --help display help for command
+```
+
## config:print
Scope: `root`
@@ -609,3 +631,18 @@ the resulting archive in the target `workspace-dir`.
```text
Usage: backstage-cli build-workspace [options] <workspace-dir>
```
+
+## create-github-app
+
+Scope: `root`
+
+Creates a GitHub App in your GitHub organization. This is an alternative to
+token-based [GitHub integration](../integrations/github/locations.md). See
+[GitHub Apps for Backstage Authentication](../plugins/github-apps.md).
+
+Launches a browser to create the App through GitHub and saves the result as a
+YAML file that can be referenced in the GitHub integration configuration.
+
+```text
+Usage: backstage-cli create-github-app <github-org>
+```
diff --git a/docs/conf/writing.md b/docs/conf/writing.md
index ac8509db0a..c7b3bfe296 100644
--- a/docs/conf/writing.md
+++ b/docs/conf/writing.md
@@ -129,6 +129,9 @@ variable.
$env: MY_SECRET
```
+Note however, that it's often more convenient to use
+[environment variable substitution](#environment-variable-substitution) instead.
+
### File Includes
This reads a string value from the entire contents of a text file. The file path
diff --git a/docs/getting-started/deployment-docker.md b/docs/deployment/docker.md
similarity index 95%
rename from docs/getting-started/deployment-docker.md
rename to docs/deployment/docker.md
index 70963df3d3..ef178b2977 100644
--- a/docs/getting-started/deployment-docker.md
+++ b/docs/deployment/docker.md
@@ -1,14 +1,15 @@
---
-id: deployment-docker
-title: Docker
-description: Documentation on how to deploy Backstage as a Docker image
+id: docker
+title: Building a Docker image
+sidebar_label: Docker
+description: How to build a Backstage Docker image for deployment
---
This section describes how to build a Backstage App into a deployable Docker
image. It is split into three sections, first covering the host build approach,
which is recommended due its speed and more efficient and often simpler caching.
The second section covers a full multi-stage Docker build, and the last section
-covers how to split frontend content into a separate image.
+covers how to deploy the frontend and backend as separate images.
Something that goes for all of these docker deployment strategies is that they
are stateless, so for a production deployment you will want to set up and
@@ -20,10 +21,10 @@ bundled and served from the backend. This is done using the
`@backstage/plugin-app-backend` plugin, which also injects the frontend
configuration into the app. This means you that you only need to build and
deploy a single container in a minimal setup of Backstage. If you wish to
-separate the serving of the frontend out from the backend, see
-[the section on that topic below](#separate-frontend).
+separate the serving of the frontend out from the backend, see the
+[separate frontend](#separate-frontend) topic below.
-### Host Build
+## Host Build
This section describes how to build a Docker image from a Backstage repo with
most of the build happening outside of Docker. This is almost always the faster
@@ -109,7 +110,7 @@ docker run -it -p 7000:7000 backstage
You should then start to get logs in your terminal, and then you can open your
browser at `http://localhost:7000`
-### Multistage Build
+## Multi-stage Build
This section describes how to set up a multi-stage Docker build that builds the
entire project within Docker. This is typically slower than a host build, but is
@@ -207,7 +208,7 @@ docker run -it -p 7000:7000 backstage
You should then start to get logs in your terminal, and then you can open your
browser at `http://localhost:7000`
-### Separate Frontend
+## Separate Frontend
It is sometimes desirable to serve the frontend separately from the backend,
either from a separate image or for example a static file serving provider. The
diff --git a/docs/getting-started/deployment-helm.md b/docs/deployment/helm.md
similarity index 92%
rename from docs/getting-started/deployment-helm.md
rename to docs/deployment/helm.md
index d1a52a5b30..fd36aeb81f 100644
--- a/docs/getting-started/deployment-helm.md
+++ b/docs/deployment/helm.md
@@ -1,8 +1,8 @@
---
-id: deployment-helm
-title: Helm
-description: Documentation on Kubernetes and Helm Deployment
-sidebar_label: Kubernetes and Helm
+id: helm
+title: Deploying Backstage with Helm
+description: How to deploy Backstage with Helm and Kubernetes
+sidebar_label: Helm
---
# Helm charts
diff --git a/docs/deployment/heroku.md b/docs/deployment/heroku.md
new file mode 100644
index 0000000000..42ab817b30
--- /dev/null
+++ b/docs/deployment/heroku.md
@@ -0,0 +1,44 @@
+---
+id: heroku
+title: Deploying with Heroku
+sidebar_label: Heroku
+description: How to deploy Backstage to Heroku
+---
+
+Heroku is a Platform as a Service (PaaS) designed to handle application
+deployment in a hands-off way. Heroku supports container deployment of Docker
+images, a natural fit for Backstage.
+
+## Configuring the CLI
+
+First, install the
+[heroku-cli](https://devcenter.heroku.com/articles/heroku-cli) and login:
+
+```shell
+$ heroku login
+```
+
+Heroku runs a container registry on `registry.heroku.com`. To push Backstage
+Docker images, log in to the container registry also:
+
+```shell
+$ heroku container:login
+```
+
+You _might_ also need to set your Heroku app's stack to `container`:
+
+```bash
+$ heroku stack:set container -a
+```
+
+## Push and deploy a Docker image
+
+Now we can push a Backstage [Docker image](docker.md) to Heroku's container
+registry and release it to the `web` worker:
+
+```bash
+$ heroku container:push web -a
+$ heroku container:release web -a
+```
+
+Now you should have Backstage up and running! ๐
diff --git a/docs/deployment/index.md b/docs/deployment/index.md
new file mode 100644
index 0000000000..db4ceca6f1
--- /dev/null
+++ b/docs/deployment/index.md
@@ -0,0 +1,40 @@
+---
+id: index
+title: Deploying Backstage
+sidebar_title: Overview
+description: Packaging Backstage and deploying to production
+---
+
+Backstage provides tooling to build Docker images, but can be deployed with or
+without Docker on many different infrastructures. The _best_ way to deploy
+Backstage is in _the same way_ you deploy other software at your organization.
+
+This documentation shows common examples that may be useful when deploying
+Backstage for the first time, or for those without established deployment
+practices.
+
+> Note: The _easiest_ way to explore Backstage is to visit the
+> [live demo site](https://demo.backstage.io).
+
+At Spotify, we deploy software generally by:
+
+1. Building a Docker image
+2. Storing the Docker image on a container registry
+3. Referencing the image in a Kubernetes Deployment YAML
+4. Applying that Deployment to a Kubernetes cluster
+
+This method is covered in [Building a Docker image](docker.md) and
+[Deploying with Kubernetes](k8s.md).
+
+There is also an example of deploying on [Heroku](heroku.md), which only
+requires the first two steps.
+
+An example of deploying Backstage with a [Helm chart](helm.md), a common pattern
+in AWS, is also available.
+
+Please consider contributing other deployment guides if you get Backstage set up
+on common infrastructure, it would be a great benefit to the community.
+
+If you need to run Backstage behind a corporate proxy, this
+[contributed guide](https://github.com/backstage/backstage/blob/master/contrib/docs/tutorials/help-im-behind-a-corporate-proxy.md)
+may help.
diff --git a/docs/deployment/k8s.md b/docs/deployment/k8s.md
new file mode 100644
index 0000000000..9dbf447740
--- /dev/null
+++ b/docs/deployment/k8s.md
@@ -0,0 +1,519 @@
+---
+id: k8s
+title: Deploying with Kubernetes
+sidebar_label: Kubernetes
+description: How to deploy Backstage to a Kubernetes cluster
+---
+
+[Kubernetes](https://kubernetes.io/) is a system for deploying, scaling and
+managing containerized applications. Backstage is designed to fit this model and
+run as a stateless application with an external PostgreSQL database.
+
+There are many different tools and patterns for Kubernetes clusters, so the best
+way to deploy to an existing Kubernetes setup is _the same way_ you deploy
+everything else.
+
+This guide covers basic Kubernetes definitions needed to get Backstage up and
+running in a typical cluster. The object definitions might look familiar, since
+the Backstage software catalog
+[also uses](../features/software-catalog/descriptor-format.md) the Kubernetes
+object format for its entity definition files!
+
+## Testing locally
+
+To test out these concepts locally before deploying to a production Kubernetes
+cluster, first install [kubectl](https://kubernetes.io/docs/tasks/tools/), the
+Kubernetes command-line tool.
+
+Next, install [minikube](https://minikube.sigs.k8s.io/docs/start/). This creates
+a single-node Kubernetes cluster on your local machine:
+
+```shell
+# Assumes Mac + Homebrew; see the minikube site for other installations
+$ brew install minikube
+$ minikube start
+
+...
+Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default.
+```
+
+Now you can run `kubectl` commands and have changes applied to the minikube
+cluster. You should be able to see the `kube-system` Kubernetes pods running:
+
+```shell
+$ kubectl get pods -A
+```
+
+When you're done with the tutorial, use `minikube stop` to halt the cluster and
+free up resources.
+
+## Creating a namespace
+
+Deployments in Kubernetes are commonly assigned to their own
+[namespace](https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/)
+to isolate services in a multi-tenant environment.
+
+This can be done through `kubectl` directly:
+
+```shell
+$ kubectl create namespace backstage
+namespace/backstage created
+```
+
+Alternatively, create and apply a Namespace definition:
+
+```yaml
+# kubernetes/namespace.yaml
+apiVersion: v1
+kind: Namespace
+metadata:
+ name: backstage
+```
+
+```shell
+$ kubectl apply -f kubernetes/namespace.yaml
+namespace/backstage created
+```
+
+## Creating the PostgreSQL database
+
+Backstage in production uses PostgreSQL as a database. To isolate the database
+from Backstage app deployments, we can create a separate Kubernetes deployment
+for PostgreSQL.
+
+### Creating a PostgreSQL secret
+
+First, create a Kubernetes Secret for the PostgreSQL username and password. This
+will be used by both the PostgreSQL database and Backstage deployments:
+
+```yaml
+# kubernetes/postgres-secrets.yaml
+apiVersion: v1
+kind: Secret
+metadata:
+ name: postgres-secrets
+ namespace: backstage
+type: Opaque
+data:
+ POSTGRES_USER: YmFja3N0YWdl
+ POSTGRES_PASSWORD: aHVudGVyMg==
+```
+
+The data in Kubernetes secrets are base64-encoded. The values can be generated
+on the command line:
+
+```shell
+$ echo -n "backstage" | base64
+YmFja3N0YWdl
+```
+
+> Note: Secrets are base64-encoded, but not encrypted. Be sure to enable
+> [Encryption at Rest](https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/)
+> for the cluster. For storing secrets in Git, consider
+> [SealedSecrets or other solutions](https://learnk8s.io/kubernetes-secrets-in-git).
+
+The secrets can now be applied to the Kubernetes cluster:
+
+```shell
+$ kubectl apply -f kubernetes/postgres-secrets.yaml
+secret/postgres-secrets created
+```
+
+### Creating a PostgreSQL persistent volume
+
+PostgreSQL needs a persistent volume to store data; we'll create one along with
+a `PersistentVolumeClaim`. In this case, we're claiming the whole volume - but
+claims can ask for only part of a volume as well.
+
+```yaml
+# kubernetes/postgres-storage.yaml
+apiVersion: v1
+kind: PersistentVolume
+metadata:
+ name: postgres-storage
+ namespace: backstage
+ labels:
+ type: local
+spec:
+ storageClassName: manual
+ capacity:
+ storage: 2G
+ accessModes:
+ - ReadWriteOnce
+ persistentVolumeReclaimPolicy: Retain
+ hostPath:
+ path: '/mnt/data'
+---
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+ name: postgres-storage-claim
+ namespace: backstage
+spec:
+ storageClassName: manual
+ accessModes:
+ - ReadWriteOnce
+ resources:
+ requests:
+ storage: 2G
+```
+
+This file contains definitions for two different kinds, separated by a line with
+a triple dash. This syntax is helpful if you want to consolidate related
+Kubernetes definitions in a single file and apply them at the same time.
+
+Note the volume `type: local`; this creates a volume using local disk on
+Kubernetes nodes. More likely in a production scenario, you'd want to use a more
+highly available
+[type of PersistentVolume](https://kubernetes.io/docs/concepts/storage/persistent-volumes/#types-of-persistent-volumes).
+
+Apply the storage volume and claim to the Kubernetes cluster:
+
+```shell
+$ kubectl apply -f kubernetes/postgres-storage.yaml
+persistentvolume/postgres-storage created
+persistentvolumeclaim/postgres-storage-claim created
+```
+
+### Creating a PostgreSQL deployment
+
+Now we can create a Kubernetes Deployment descriptor for the PostgreSQL database
+deployment itself:
+
+```yaml
+# kubernetes/postgres.yaml
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: postgres
+ namespace: backstage
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: postgres
+ template:
+ metadata:
+ labels:
+ app: postgres
+ spec:
+ containers:
+ - name: postgres
+ image: postgres:13.2-alpine
+ imagePullPolicy: 'IfNotPresent'
+ ports:
+ - containerPort: 5432
+ envFrom:
+ - secretRef:
+ name: postgres-secrets
+ volumeMounts:
+ - mountPath: /var/lib/postgresql/data
+ name: postgresdb
+ volumes:
+ - name: postgresdb
+ persistentVolumeClaim:
+ claimName: postgres-storage-claim
+```
+
+If you're not used to Kubernetes, this is a lot to take in. We're describing a
+Deployment (one or more instances of an application) that we'd like Kubernetes
+to know about in the `metadata` block.
+
+The `spec` block describes the desired state. Here we've requested Kubernetes
+create 1 replica (running instance of PostgreSQL), and to create the replica
+with the given pod `template`, which again contains Kubernetes metadata and a
+desired state. The template `spec` shows one container, created from the
+[published](https://hub.docker.com/_/postgres) `postgres:13.2-alpine` Docker
+image.
+
+Note the `envFrom` and `secretRef` - this tells Kubernetes to fill environment
+variables in the container with values from the Secret we created. We've also
+referenced the volume created for the deployment, and given it the mount path
+expected by PostgreSQL.
+
+Apply the PostgreSQL deployment to the Kubernetes cluster:
+
+```shell
+$ kubectl apply -f kubernetes/postgres.yaml
+deployment.apps/postgres created
+
+$ kubectl get pods --namespace=backstage
+NAME READY STATUS RESTARTS AGE
+postgres-56c86b8bbc-66pt2 1/1 Running 0 21s
+```
+
+Verify the deployment by connecting to the pod:
+
+```shell
+$ kubectl exec -it --namespace=backstage postgres-56c86b8bbc-66pt2 -- /bin/bash
+bash-5.1# psql -U $POSTGRES_USER
+psql (13.2)
+backstage=# \q
+bash-5.1# exit
+```
+
+### Creating a PostgreSQL service
+
+The database pod is running, but how does another pod connect to it?
+
+Kubernetes pods are transient - they can be killed, restarted, or created
+dynamically. Therefore we don't want to try to connect to pods directly, but
+rather create a Kubernetes Service. Services keep track of pods and direct
+traffic to the right place.
+
+The final step for our database is to create the service descriptor:
+
+```yaml
+# kubernetes/postgres-service.yaml
+apiVersion: v1
+kind: Service
+metadata:
+ name: postgres
+ namespace: backstage
+spec:
+ selector:
+ app: postgres
+ ports:
+ - port: 5432
+```
+
+Apply the service to the Kubernetes cluster:
+
+```shell
+$ kubectl apply -f kubernetes/postgres-service.yaml
+service/postgres created
+
+$ kubectl get services --namespace=backstage
+NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
+postgres ClusterIP 10.96.5.103 5432/TCP 29s
+```
+
+## Creating the Backstage instance
+
+Now that we have PostgreSQL up and ready to store data, we can create the
+Backstage instance. This follows similar steps as the PostgreSQL deployment.
+
+### Creating a Backstage secret
+
+For any Backstage configuration secrets, such as authorization tokens, we can
+create a similar Kubernetes Secret as we did
+[for PostgreSQL](#creating-a-postgresql-secret), remembering to base64 encode
+the values:
+
+```yaml
+# kubernetes/backstage-secrets.yaml
+apiVersion: v1
+kind: Secret
+metadata:
+ name: backstage-secrets
+ namespace: backstage
+type: Opaque
+data:
+ GITHUB_TOKEN: VG9rZW5Ub2tlblRva2VuVG9rZW5NYWxrb3ZpY2hUb2tlbg==
+```
+
+Apply the secret to the Kubernetes cluster:
+
+```shell
+$ kubectl apply -f kubernetes/backstage-secrets.yaml
+secret/backstage-secrets created
+```
+
+### Creating a Backstage deployment
+
+To create the Backstage deployment, first create a [Docker image](docker.md).
+We'll use this image to create a Kubernetes deployment. For this example, we'll
+use the standard host build with the frontend bundled and served from the
+backend.
+
+First, create a Kubernetes Deployment descriptor:
+
+```yaml
+# kubernetes/backstage.yaml
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: backstage
+ namespace: backstage
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: backstage
+ template:
+ metadata:
+ labels:
+ app: backstage
+ spec:
+ containers:
+ - name: backstage
+ image: backstage:1.0.0
+ imagePullPolicy: IfNotPresent
+ ports:
+ - name: http
+ containerPort: 7000
+ envFrom:
+ - secretRef:
+ name: postgres-secrets
+ - secretRef:
+ name: backstage-secrets
+```
+
+For production deployments, the `image` reference will usually be a full URL to
+a repository on a container registry (for example, ECR on AWS).
+
+For testing locally with `minikube`, you can point the local Docker daemon to
+the `minikube` internal Docker registry and then rebuild the image to install
+it:
+
+```shell
+$ eval $(minikube docker-env)
+$ yarn build-image --tag backstage:1.0.0
+```
+
+There is no special wiring needed to access the PostgreSQL service. Since it's
+running on the same cluster, Kubernetes will inject `POSTGRES_SERVICE_HOST` and
+`POSTGRES_SERVICE_PORT` environment variables into our Backstage container.
+These can be used in the Backstage `app-config.yaml` along with the secrets:
+
+```yaml
+backend:
+ database:
+ client: pg
+ connection:
+ host: ${POSTGRES_SERVICE_HOST}
+ port: ${POSTGRES_SERVICE_PORT}
+ user: ${POSTGRES_USER}
+ password: ${POSTGRES_PASSWORD}
+```
+
+Make sure to rebuild the Docker image after applying `app-config.yaml` changes.
+
+Apply this Deployment to the Kubernetes cluster:
+
+```shell
+$ kubectl apply -f kubernetes/backstage.yaml
+deployment.apps/backstage created
+
+$ kubectl get deployments --namespace=backstage
+NAME READY UP-TO-DATE AVAILABLE AGE
+backstage 1/1 1 1 1m
+postgres 1/1 1 1 10m
+
+$ kubectl get pods --namespace=backstage
+NAME READY STATUS RESTARTS AGE
+backstage-54bfcd6476-n2jkm 1/1 Running 0 58s
+postgres-56c86b8bbc-66pt2 1/1 Running 0 9m
+```
+
+Beautiful! ๐ The deployment and pod are running in the cluster. If you run into
+any trouble, check the container logs from the pod:
+
+```shell
+# -f to tail, -c
+$ kubectl logs --namespace=backstage -f backstage-54bfcd6476-n2jkm -c backstage
+```
+
+### Creating a Backstage service
+
+Like the [PostgreSQL service](#creating-a-postgresql-service) above, we need to
+create a Kubernetes Service for Backstage to handle connecting requests to the
+correct pods.
+
+Create the Kubernetes Service descriptor:
+
+```yaml
+# kubernetes/backstage-service.yaml
+apiVersion: v1
+kind: Service
+metadata:
+ name: backstage
+ namespace: backstage
+spec:
+ selector:
+ app: backstage
+ ports:
+ - name: http
+ port: 80
+ targetPort: http
+```
+
+The `selector` here is telling the Service which pods to target, and the port
+mapping translates normal HTTP port 80 to the backend http port (7000) on the
+pod.
+
+Apply this Service to the Kubernetes cluster:
+
+```shell
+$ kubectl apply -f kubernetes/backstage-service.yaml
+service/backstage created
+```
+
+Now we have a fully operational Backstage deployment! ๐ For a _**grand
+reveal**_, you can forward a local port to the service:
+
+```shell
+$ sudo kubectl port-forward --namespace=backstage svc/backstage 80:80
+Forwarding from 127.0.0.1:80 -> 7000
+```
+
+This shows port 7000 since `port-forward` doesn't _really_ support services, so
+it cheats by looking up the first pod for a service and connecting to the mapped
+pod port.
+
+Note that `app.baseUrl` and `backend.baseUrl` in your `app-config.yaml` should
+match what we're forwarding here (port omitted in this example since we're using
+the default HTTP port 80):
+
+```yaml
+# app-config.yaml
+app:
+ baseUrl: http://localhost
+
+organization:
+ name: Spotify
+
+backend:
+ baseUrl: http://localhost
+ listen:
+ port: 7000
+ cors:
+ origin: http://localhost
+```
+
+If you're using an [auth provider](../auth/index.md), it should also have this
+address configured for the authentication pop-up to work properly.
+
+Now you can open a browser on your machine to [localhost](http://localhost) and
+browse your Kubernetes-deployed Backstage instance. ๐ข๐ข๐ข
+
+## Further steps
+
+This is most of the way to a full production deployment of Backstage on
+Kubernetes. There's a few additional steps to that will likely be needed beyond
+the scope of this document.
+
+### Set up a more reliable volume
+
+The `PersistentVolume` configured above uses `local` Kubernetes node storage.
+This should be replaced with a cloud volume, network attached storage, or
+something more persistent beyond a Kubernetes node.
+
+### Expose the Backstage service
+
+The Kubernetes Service is not exposed for external connections from outside the
+cluster. This is generally done with a Kubernetes
+[ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) or
+an
+[external load balancer](https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/).
+
+### Update the Deployment image
+
+To update the Kubernetes deployment to a newly published version of your
+Backstage Docker image, update the image tag reference in `backstage.yaml` and
+then apply the changes with `kubectl apply -f kubernetes/backstage.yaml`.
+
+For production purposes, this image tag will generally be a full-fledged URL
+pointing to a container registry where built Docker images are hosted. This can
+be hosted internally in your infrastructure, or a managed one offered by a cloud
+provider.
diff --git a/docs/features/kubernetes/configuration.md b/docs/features/kubernetes/configuration.md
index c21ac7e18a..8222fbb400 100644
--- a/docs/features/kubernetes/configuration.md
+++ b/docs/features/kubernetes/configuration.md
@@ -25,8 +25,8 @@ kubernetes:
- url: http://127.0.0.1:9999
name: minikube
authProvider: 'serviceAccount'
- serviceAccountToken:
- $env: K8S_MINIKUBE_TOKEN
+ skipTLSVerify: false
+ serviceAccountToken: ${K8S_MINIKUBE_TOKEN}
- url: http://127.0.0.2:9999
name: aws-cluster-1
authProvider: 'aws'
@@ -79,6 +79,11 @@ cluster. Valid values are:
| `serviceAccount` | This will use a Kubernetes [service account](https://kubernetes.io/docs/reference/access-authn-authz/service-accounts-admin/) to access the Kubernetes API. When this is used the `serviceAccountToken` field should also be set. |
| `google` | This will use a user's Google auth token from the [Google auth plugin](https://backstage.io/docs/auth/) to access the Kubernetes API. |
+##### `clusters.\*.skipTLSVerify`
+
+This determines whether or not the Kubernetes client verifies the TLS
+certificate presented by the API server. Defaults to `false`.
+
##### `clusters.\*.serviceAccountToken` (optional)
The service account token to be used when using the `serviceAccount` auth
diff --git a/docs/features/software-catalog/descriptor-format.md b/docs/features/software-catalog/descriptor-format.md
index 7397ce7d23..f4e1f86dd8 100644
--- a/docs/features/software-catalog/descriptor-format.md
+++ b/docs/features/software-catalog/descriptor-format.md
@@ -519,6 +519,17 @@ consumed by the component, e.g. `artist-api`. This field is optional.
| --------------------------------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------------- |
| [`API`](#kind-api) (default) | Same as this entity, typically `default` | [`consumesApi`, and reverse `apiConsumedBy`](well-known-relations.md#consumesapi-and-apiconsumedby) |
+### `spec.dependsOn` [optional]
+
+An array of [entity references](#string-references) to the components and
+resources that the component depends on, e.g. `artists-db`. This field is
+optional.
+
+| [`kind`](#apiversion-and-kind-required) | Default [`namespace`](#namespace-optional) | Generated [relation](well-known-relations.md) type |
+| --------------------------------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------- |
+| [`Component`](#kind-component) | Same as this entity, typically `default` | [`dependsOn`, and reverse `dependencyOf`](well-known-relations.md#dependson-and-dependencyof) |
+| [`Resource`](#kind-resource) | Same as this entity, typically `default` | [`dependsOn`, and reverse `dependencyOf`](well-known-relations.md#dependson-and-dependencyof) |
+
## Kind: Template
The following describes the following entity kind:
@@ -1014,6 +1025,17 @@ belongs to, e.g. `artist-engagement-portal`. This field is optional.
| --------------------------------------- | ------------------------------------------ | ----------------------------------------------------------------------------- |
| [`System`](#kind-system) (default) | Same as this entity, typically `default` | [`partOf`, and reverse `hasPart`](well-known-relations.md#partof-and-haspart) |
+### `spec.dependsOn` [optional]
+
+An array of [entity references](#string-references) to the components and
+resources that the resource depends on, e.g. `artist-lookup`. This field is
+optional.
+
+| [`kind`](#apiversion-and-kind-required) | Default [`namespace`](#namespace-optional) | Generated [relation](well-known-relations.md) type |
+| --------------------------------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------- |
+| [`Component`](#kind-component) | Same as this entity, typically `default` | [`dependsOn`, and reverse `dependencyOf`](well-known-relations.md#dependson-and-dependencyof) |
+| [`Resource`](#kind-resource) | Same as this entity, typically `default` | [`dependsOn`, and reverse `dependencyOf`](well-known-relations.md#dependson-and-dependencyof) |
+
## Kind: System
Describes the following entity kind:
diff --git a/docs/features/software-catalog/external-integrations.md b/docs/features/software-catalog/external-integrations.md
index e278bc7a1a..247324a46c 100644
--- a/docs/features/software-catalog/external-integrations.md
+++ b/docs/features/software-catalog/external-integrations.md
@@ -156,8 +156,18 @@ The key points to note are:
- Call `emit` any number of times with the results of that process
- Finally return `true`
-You should now be able to instantiate this class in your backend, and add it to
-the `CatalogBuilder` using the `addProcessors` method.
+You should now be able to add this class to your backend in
+`packages/backend/src/plugins/catalog.ts`:
+
+```diff
++ import { SystemXReaderProcessor } from '../path/to/class';
+
+export default async function createPlugin(
+ env: PluginEnvironment,
+): Promise {
+ const builder = new CatalogBuilder(env);
++ builder.addProcessor(new SystemXReaderProcessor(env.reader));
+```
Start up the backend - it should now start reading from the previously
registered location and you'll see your entities start to appear in Backstage.
diff --git a/docs/features/software-catalog/well-known-annotations.md b/docs/features/software-catalog/well-known-annotations.md
index 8ec4813799..0053c14b79 100644
--- a/docs/features/software-catalog/well-known-annotations.md
+++ b/docs/features/software-catalog/well-known-annotations.md
@@ -92,12 +92,14 @@ view and edit links need changing.
# Example:
metadata:
annotations:
- backstage.io/source-location: github:https://github.com/my-org/my-service
+ backstage.io/source-location: url:https://github.com/my-org/my-service/
```
A `Location` reference that points to the source code of the entity (typically a
`Component`). Useful when catalog files do not get ingested from the source code
-repository itself.
+repository itself. If the URL points to a folder, it is important that it is
+suffixed with a `'/'` in order for relative path resolution to work
+consistently.
### jenkins.io/github-folder
diff --git a/docs/features/software-templates/installation.md b/docs/features/software-templates/installation.md
index e145567666..105b7e5f96 100644
--- a/docs/features/software-templates/installation.md
+++ b/docs/features/software-templates/installation.md
@@ -189,8 +189,7 @@ public within the enterprise.
integrations:
github:
- host: github.com
- token:
- $env: GITHUB_TOKEN
+ token: ${GITHUB_TOKEN}
scaffolder:
github:
@@ -207,8 +206,7 @@ instance:
integrations:
gitlab:
- host: gitlab.com
- token:
- $env: GITLAB_TOKEN
+ token: ${GITLAB_TOKEN}
```
#### Bitbucket
@@ -221,8 +219,7 @@ following:
integrations:
bitbucket:
- host: bitbucket.org
- token:
- $env: BITBUCKET_TOKEN
+ token: ${BITBUCKET_TOKEN}
```
or
@@ -231,10 +228,8 @@ or
integrations:
bitbucket:
- host: bitbucket.org
- appPassword:
- $env: BITBUCKET_APP_PASSWORD
- username:
- $env: BITBUCKET_USERNAME
+ appPassword: ${BITBUCKET_APP_PASSWORD}
+ username: ${BITBUCKET_USERNAME}
```
#### Azure DevOps
@@ -249,8 +244,7 @@ verified.
integrations:
azure:
- host: dev.azure.com
- token:
- $env: AZURE_TOKEN
+ token: ${AZURE_TOKEN}
```
### Running the Backend
@@ -265,3 +259,15 @@ GITHUB_TOKEN= yarn start
If you've also set up the frontend plugin, so you should be ready to go browse
the templates at [localhost:3000/create](http://localhost:3000/create) now!
+
+### Disabling Docker in Docker situation (Optional)
+
+Software Templates use
+[Cookiecutter](https://github.com/cookiecutter/cookiecutter) as templating
+library. By default it will use the
+[spotify/backstage-cookiecutter](<[spotify/backstage-cookiecutter](https://github.com/backstage/backstage/blob/37e35b910afc7d1270855aed0ec4718aba366c91/plugins/scaffolder-backend/scripts/Cookiecutter.dockerfile)>)
+docker image.
+
+If you are running backstage from a Docker container and you want to avoid
+calling a container inside a container, you can set up Cookiecutter in your own
+image, this will use the local installation instead.
diff --git a/docs/features/techdocs/configuration.md b/docs/features/techdocs/configuration.md
index 9bf2c25926..c7876f3874 100644
--- a/docs/features/techdocs/configuration.md
+++ b/docs/features/techdocs/configuration.md
@@ -65,22 +65,18 @@ techdocs:
# 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
+ accessKeyId: ${TECHDOCS_AWSS3_ACCESS_KEY_ID_CREDENTIAL}
+ secretAccessKey: ${TECHDOCS_AWSS3_SECRET_ACCESS_KEY_CREDENTIAL}
# (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
+ region: ${AWS_REGION}
# (Optional) Endpoint URI to send requests to.
# If not set, the default endpoint is built from the configured region.
# https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#constructor-property
- endpoint:
- $env: AWS_ENDPOINT
+ endpoint: ${AWS_ENDPOINT}
# Required when techdocs.publisher.type is set to 'azureBlobStorage'. Skip otherwise.
@@ -91,13 +87,11 @@ techdocs:
# (Required) An account name is required to write to a storage blob container.
# https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key
credentials:
- accountName:
- $env: TECHDOCS_AZURE_BLOB_STORAGE_ACCOUNT_NAME
+ accountName: ${TECHDOCS_AZURE_BLOB_STORAGE_ACCOUNT_NAME}
# (Optional) An account key is required to write to a storage container.
# If missing,AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET environment variable will be used.
# https://docs.microsoft.com/en-us/azure/storage/common/storage-auth?toc=/azure/storage/blobs/toc.json
- accountKey:
- $env: TECHDOCS_AZURE_BLOB_STORAGE_ACCOUNT_KEY
+ accountKey: ${TECHDOCS_AZURE_BLOB_STORAGE_ACCOUNT_KEY}
# (Optional and Legacy) TechDocs makes API calls to techdocs-backend using this URL. e.g. get docs of an entity, get metadata, etc.
# You don't have to specify this anymore.
diff --git a/docs/features/techdocs/using-cloud-storage.md b/docs/features/techdocs/using-cloud-storage.md
index fd8f320859..d5a3a68425 100644
--- a/docs/features/techdocs/using-cloud-storage.md
+++ b/docs/features/techdocs/using-cloud-storage.md
@@ -95,8 +95,7 @@ techdocs:
type: 'googleGcs'
googleGcs:
bucketName: 'name-of-techdocs-storage-bucket'
- credentials:
- $env: GOOGLE_APPLICATION_CREDENTIALS
+ credentials: ${GOOGLE_APPLICATION_CREDENTIALS}
```
**4. That's it!**
@@ -179,13 +178,10 @@ techdocs:
type: 'awsS3'
awsS3:
bucketName: 'name-of-techdocs-storage-bucket'
- region:
- $env: AWS_REGION
+ region: ${AWS_REGION}
credentials:
- accessKeyId:
- $env: AWS_ACCESS_KEY_ID
- secretAccessKey:
- $env: AWS_SECRET_ACCESS_KEY
+ accessKeyId: ${AWS_ACCESS_KEY_ID}
+ secretAccessKey: ${AWS_SECRET_ACCESS_KEY}
```
Refer to the
@@ -202,8 +198,7 @@ techdocs:
type: 'awsS3'
awsS3:
bucketName: 'name-of-techdocs-storage-bucket'
- region:
- $env: AWS_REGION
+ region: ${AWS_REGION}
credentials:
roleArn: arn:aws:iam::123456789012:role/my-backstage-role
```
@@ -276,8 +271,7 @@ techdocs:
azureBlobStorage:
containerName: 'name-of-techdocs-storage-bucket'
credentials:
- accountName:
- $env: TECHDOCS_AZURE_BLOB_STORAGE_ACCOUNT_NAME
+ accountName: ${TECHDOCS_AZURE_BLOB_STORAGE_ACCOUNT_NAME}
```
**3b. Authentication using app-config.yaml**
@@ -297,10 +291,8 @@ techdocs:
azureBlobStorage:
containerName: 'name-of-techdocs-storage-bucket'
credentials:
- accountName:
- $env: TECHDOCS_AZURE_BLOB_STORAGE_ACCOUNT_NAME
- accountKey:
- $env: TECHDOCS_AZURE_BLOB_STORAGE_ACCOUNT_KEY
+ accountName: ${TECHDOCS_AZURE_BLOB_STORAGE_ACCOUNT_NAME}
+ accountKey: ${TECHDOCS_AZURE_BLOB_STORAGE_ACCOUNT_KEY}
```
**4. That's it!**
@@ -361,20 +353,13 @@ techdocs:
openStackSwift:
containerName: 'name-of-techdocs-storage-bucket'
credentials:
- userName:
- $env: OPENSTACK_SWIFT_STORAGE_USERNAME
- password:
- $env: OPENSTACK_SWIFT_STORAGE_PASSWORD
- authUrl:
- $env: OPENSTACK_SWIFT_STORAGE_AUTH_URL
- keystoneAuthVersion:
- $env: OPENSTACK_SWIFT_STORAGE_AUTH_VERSION
- domainId:
- $env: OPENSTACK_SWIFT_STORAGE_DOMAIN_ID
- domainName:
- $env: OPENSTACK_SWIFT_STORAGE_DOMAIN_NAME
- region:
- $env: OPENSTACK_SWIFT_STORAGE_REGION
+ userName: ${OPENSTACK_SWIFT_STORAGE_USERNAME}
+ password: ${OPENSTACK_SWIFT_STORAGE_PASSWORD}
+ authUrl: ${OPENSTACK_SWIFT_STORAGE_AUTH_URL}
+ keystoneAuthVersion: ${OPENSTACK_SWIFT_STORAGE_AUTH_VERSION}
+ domainId: ${OPENSTACK_SWIFT_STORAGE_DOMAIN_ID}
+ domainName: ${OPENSTACK_SWIFT_STORAGE_DOMAIN_NAME}
+ region: ${OPENSTACK_SWIFT_STORAGE_REGION}
```
**4. That's it!**
diff --git a/docs/getting-started/app-custom-theme.md b/docs/getting-started/app-custom-theme.md
index 345b7ad969..181f541929 100644
--- a/docs/getting-started/app-custom-theme.md
+++ b/docs/getting-started/app-custom-theme.md
@@ -74,3 +74,64 @@ Note that your list of custom themes overrides the default themes. If you still
want to use the default themes, they are exported as `lightTheme` and
`darkTheme` from
[@backstage/theme](https://www.npmjs.com/package/@backstage/theme).
+
+## Example of a custom theme
+
+```ts
+const themeOptions = createThemeOptions({
+ palette: {
+ ...lightTheme.palette,
+ primary: {
+ main: '#123456',
+ },
+ secondary: {
+ main: '#123456',
+ },
+ error: {
+ main: '#123456'
+ },
+ warning: {
+ main: '#123456',
+ },
+ info: {
+ main: '#123456',
+ },
+ success: {
+ main: '#123456',
+ },
+ background: {
+ default: '#123456',
+ paper: '#123456',
+ },
+ banner: {
+ info: '#123456',
+ error: '#123456'
+ text: '#123456'
+ link: '#123456',
+ },
+ errorBackground: '#123456'
+ warningBackground: '#123456'
+ infoBackground: '#123456'
+ navigation: {
+ background: '#123456',
+ indicator: '#123456'
+ color: '#123456'
+ selectedColor: '#123456',
+ },
+ },
+ defaultPageTheme: 'home',
+ fontFamily: 'Comic Sans',
+ /* below drives the header colors */
+ pageTheme: {
+ home: genPageTheme(['#123456','#123456'], shapes.wave),
+ documentation: genPageTheme(['#123456','#123456'], shapes.wave2),
+ tool: genPageTheme(['#123456','#123456'], shapes.round),
+ service: genPageTheme(['#123456','#123456'], shapes.wave),
+ website: genPageTheme(['#123456','#123456'], shapes.wave),
+ library: genPageTheme(['#123456','#123456'] shapes.wave),
+ other: genPageTheme(['#123456','#123456'], shapes.wave),
+ app: genPageTheme(['#123456','#123456'], shapes.wave),
+ apis: genPageTheme(['#123456','#123456'], shapes.wave),
+ },
+});
+```
diff --git a/docs/getting-started/configure-app-with-plugins.md b/docs/getting-started/configure-app-with-plugins.md
index 964987c4a7..b2f3a37d90 100644
--- a/docs/getting-started/configure-app-with-plugins.md
+++ b/docs/getting-started/configure-app-with-plugins.md
@@ -31,26 +31,33 @@ CircleCI is a frontend UI plugin, it goes in `app` rather than `backend`.
```js
// packages/app/src/plugins.ts
-export { plugin as Circleci } from '@backstage/plugin-circleci';
+export { plugin as CircleCi } from '@backstage/plugin-circleci';
```
-3. Register the plugin router:
+3. Register the plugin in the entity pages:
-```jsx
-// packages/app/src/components/catalog/EntityPage.tsx
+```diff
+ // packages/app/src/components/catalog/EntityPage.tsx
++import {
++ EntityCircleCIContent,
++ isCircleCIAvailable,
++} from '@backstage/plugin-circleci';
-import { Router as CircleCIRouter } from '@backstage/plugin-circleci';
-
-// Then somewhere inside
-}
-/>;
+...
+ const cicdContent = (
+
+ ...
++
++
++ ;
+
+ );
```
-Note that stand-alone plugins that are not "attached" to the Software Catalog
-would be added outside the `EntityPage`.
+This is just one example, but each Backstage instance may integrate content or
+cards to suit their needs on different pages, tabs, etc. Note that stand-alone
+plugins that are not "attached" to the Software Catalog would be added outside
+the `EntityPage`.
4. [Optional] Add proxy config:
@@ -60,17 +67,16 @@ proxy:
'/circleci/api':
target: https://circleci.com/api/v1.1
headers:
- Circle-Token:
- $env: CIRCLECI_AUTH_TOKEN
+ Circle-Token: ${CIRCLECI_AUTH_TOKEN}
```
### Adding a plugin page to the Sidebar
In a standard Backstage app created with
[@backstage/create-app](./create-an-app.md), the sidebar is managed inside
-`packages/app/src/components/Root.tsx`. The file exports the entire `Sidebar`
-element of your app, which you can extend with additional entries by adding new
-`SidebarItem` elements.
+`packages/app/src/components/Root/Root.tsx`. The file exports the entire
+`Sidebar` element of your app, which you can extend with additional entries by
+adding new `SidebarItem` elements.
For example, if you install the `api-docs` plugin, a matching `SidebarItem`
could be something like this:
diff --git a/docs/getting-started/deployment-k8s.md b/docs/getting-started/deployment-k8s.md
deleted file mode 100644
index bc24894ed1..0000000000
--- a/docs/getting-started/deployment-k8s.md
+++ /dev/null
@@ -1,13 +0,0 @@
----
-id: deployment-k8s
-title: Kubernetes
-description: Documentation on Kubernetes and K8s Deployment
----
-
-Backstage itself provides tooling up to the point of building Docker images.
-Beyond that point we do not have an opinionated way to deploy Backstage within
-Kubernetes, as each cluster has its own unique set of tooling and patterns.
-
-We do provide examples to help you get started though. Check out
-[this example](https://github.com/backstage/backstage/tree/master/contrib/kubernetes/plain_single_backend_deployment/)
-for a basic single-deployment setup.
diff --git a/docs/getting-started/deployment-other.md b/docs/getting-started/deployment-other.md
deleted file mode 100644
index cc622d00f5..0000000000
--- a/docs/getting-started/deployment-other.md
+++ /dev/null
@@ -1,35 +0,0 @@
----
-id: deployment-other
-title: Other
-description: Documentation on different ways of Deployment
----
-
-## Heroku
-
-Deploying to Heroku is relatively easy following these steps.
-
-First, make sure you have the
-[Heroku CLI installed](https://devcenter.heroku.com/articles/heroku-cli) and log
-into it as well as login into Heroku's
-[container registry](https://devcenter.heroku.com/articles/container-registry-and-runtime).
-
-```bash
-$ heroku login
-$ heroku container:login
-```
-
-You _might_ also need to set your Heroku app's stack to `container`.
-
-```bash
-$ heroku stack:set container -a
-```
-
-We can now build/push the Docker image to Heroku's container registry and
-release it to the `web` worker.
-
-```bash
-$ heroku container:push web -a
-$ heroku container:release web -a
-```
-
-With that, you should have Backstage up and running!
diff --git a/docs/integrations/bitbucket/discovery.md b/docs/integrations/bitbucket/discovery.md
index b24734abde..fadb7c9f06 100644
--- a/docs/integrations/bitbucket/discovery.md
+++ b/docs/integrations/bitbucket/discovery.md
@@ -39,3 +39,29 @@ The target is composed of four parts:
- The path within each repository to find the catalog YAML file. This will
usually be `/catalog-info.yaml` or a similar variation for catalog files
stored in the root directory of each repository.
+
+## Custom repository processing
+
+The Bitbucket Discovery Processor will by default emit a location for each
+matching repository for further processing by other processors. However, it is
+possible to override this functionality and take full control of how each
+matching repository is processed.
+
+`BitbucketDiscoveryProcessor.fromConfig` takes an optional parameter
+`options.parser` where you can set your own parser to be used for each matched
+repository.
+
+```typescript
+const customRepositoryParser: BitbucketRepositoryParser = async function* customRepositoryParser({
+ client,
+ repository,
+}) {
+ // Custom logic for interpret the matching repository.
+ // See defaultRepositoryParser for an example
+};
+
+const processor = BitbucketDiscoveryProcessor.fromConfig(env.config, {
+ parser: customRepositoryParser,
+ logger: env.logger,
+});
+```
diff --git a/docs/integrations/bitbucket/locations.md b/docs/integrations/bitbucket/locations.md
index 992a240290..d318eef874 100644
--- a/docs/integrations/bitbucket/locations.md
+++ b/docs/integrations/bitbucket/locations.md
@@ -38,3 +38,6 @@ a structure with up to four elements:
- `apiBaseUrl` (optional): The URL of the Bitbucket API. For self-hosted
installations, it is commonly at `https:///rest/api/1.0`. For
bitbucket.org, this configuration is not needed as it can be inferred.
+
+> Note: If you are using Bitbucket server you MUST set the username as well as
+> the token or appPassword.
diff --git a/docs/integrations/google-cloud-storage/locations.md b/docs/integrations/google-cloud-storage/locations.md
index e3f6b72847..bcdf612b9a 100644
--- a/docs/integrations/google-cloud-storage/locations.md
+++ b/docs/integrations/google-cloud-storage/locations.md
@@ -24,10 +24,8 @@ Explicit credentials can be set in the following format:
```yaml
integrations:
googleGcs:
- clientEmail:
- $env: GCS_CLIENT_EMAIL
- privateKey:
- $env: GCS_PRIVATE_KEY
+ clientEmail: ${GCS_CLIENT_EMAIL}
+ privateKey: ${GCS_PRIVATE_KEY}
```
Then make sure the environment variables `GCS_CLIENT_EMAIL` and
diff --git a/docs/plugins/proxying.md b/docs/plugins/proxying.md
index f2d7d3c12a..50c56af7a6 100644
--- a/docs/plugins/proxying.md
+++ b/docs/plugins/proxying.md
@@ -40,8 +40,9 @@ proxy:
'/larger-example/v1':
target: http://larger.example.com:8080/svc.v1
headers:
- Authorization:
- $env: EXAMPLE_AUTH_HEADER
+ Authorization: ${EXAMPLE_AUTH_HEADER}
+ # ...or interpolating a value into part of a string,
+ # Authorization: Bearer ${EXAMPLE_AUTH_TOKEN}
```
Each key under the proxy configuration entry is a route to match, below the
diff --git a/docs/tutorials/quickstart-app-auth.md b/docs/tutorials/quickstart-app-auth.md
index d322e2ec3c..510b697221 100644
--- a/docs/tutorials/quickstart-app-auth.md
+++ b/docs/tutorials/quickstart-app-auth.md
@@ -79,13 +79,10 @@ auth:
providers:
github:
development:
- clientId:
- $env: AUTH_GITHUB_CLIENT_ID
- clientSecret:
- $env: AUTH_GITHUB_CLIENT_SECRET
- ## uncomment the following two lines if using enterprise
- # enterpriseInstanceUrl:
- # $env: AUTH_GITHUB_ENTERPRISE_INSTANCE_URL
+ clientId: ${AUTH_GITHUB_CLIENT_ID}
+ clientSecret: ${AUTH_GITHUB_CLIENT_SECRET}
+ ## uncomment the following line if using enterprise
+ # enterpriseInstanceUrl: ${AUTH_GITHUB_ENTERPRISE_INSTANCE_URL}
```
### 2. Generate a GitHub client ID and secret
@@ -122,10 +119,8 @@ auth:
providers:
gitlab:
development:
- clientId:
- $env: AUTH_GITLAB_CLIENT_ID
- clientSecret:
- $env: AUTH_GITLAB_CLIENT_SECRET
+ clientId: ${AUTH_GITLAB_CLIENT_ID}
+ clientSecret: ${AUTH_GITLAB_CLIENT_SECRET}
audience: https://gitlab.com # Or your self-hosted GitLab instance URL
```
@@ -172,10 +167,8 @@ auth:
providers:
google:
development:
- clientId:
- $env: AUTH_GOOGLE_CLIENT_ID
- clientSecret:
- $env: AUTH_GOOGLE_CLIENT_SECRET
+ clientId: ${AUTH_GOOGLE_CLIENT_ID}
+ clientSecret: ${AUTH_GOOGLE_CLIENT_SECRET}
```
### 2. Generate Google Credentials in Google Cloud console
@@ -216,12 +209,9 @@ auth:
providers:
microsoft:
development:
- clientId:
- $env: AUTH_MICROSOFT_CLIENT_ID
- clientSecret:
- $env: AUTH_MICROSOFT_CLIENT_SECRET
- tenantId:
- $env: AUTH_MICROSOFT_TENANT_ID
+ clientId: ${AUTH_MICROSOFT_CLIENT_ID}
+ clientSecret: ${AUTH_MICROSOFT_CLIENT_SECRET}
+ tenantId: ${AUTH_MICROSOFT_TENANT_ID}
```
### 2. Create a Microsoft App Registration in Microsoft Portal
@@ -264,12 +254,9 @@ auth:
providers:
auth0:
development:
- clientId:
- $env: AUTH_AUTH0_CLIENT_ID
- clientSecret:
- $env: AUTH_AUTH0_CLIENT_SECRET
- domain:
- $env: AUTH_AUTH0_DOMAIN_ID
+ clientId: ${AUTH_AUTH0_CLIENT_ID}
+ clientSecret: ${AUTH_AUTH0_CLIENT_SECRET}
+ domain: ${AUTH_AUTH0_DOMAIN_ID}
```
### 2. Create an Auth0 application in the Auth0 management console
diff --git a/docs/tutorials/switching-sqlite-postgres.md b/docs/tutorials/switching-sqlite-postgres.md
index 72e5589baf..a759b7dc49 100644
--- a/docs/tutorials/switching-sqlite-postgres.md
+++ b/docs/tutorials/switching-sqlite-postgres.md
@@ -38,14 +38,10 @@ backend:
+ # config options: https://node-postgres.com/api/client
+ client: pg
+ connection:
-+ host:
-+ $env: POSTGRES_HOST
-+ port:
-+ $env: POSTGRES_PORT
-+ user:
-+ $env: POSTGRES_USER
-+ password:
-+ $env: POSTGRES_PASSWORD
++ host: ${POSTGRES_HOST}
++ port: ${POSTGRES_PORT}
++ user: ${POSTGRES_USER}
++ password: ${POSTGRES_PASSWORD}
+ # https://node-postgres.com/features/ssl
+ #ssl: require # see https://www.postgresql.org/docs/current/libpq-ssl.html Table 33.1. SSL Mode Descriptions (e.g. require)
+ #ca: # if you have a CA file and want to verify it you can uncomment this section
@@ -53,9 +49,9 @@ backend:
```
-If you have a `app-config.local.yaml` for local development, a similar update
+If you have an `app-config.local.yaml` for local development, a similar update
should be made there. You can set the `POSTGRES_` environment variables prior to
-launching Backstage, or remove the $env keys and simply set values directly for
-development.
+launching Backstage, or remove the `${...}` values and simply set actual values
+directly for development.
The Backstage App is now ready to start up with a PostgreSQL backing database.
diff --git a/microsite/package.json b/microsite/package.json
index ea16fd8a32..07899fa703 100644
--- a/microsite/package.json
+++ b/microsite/package.json
@@ -15,9 +15,9 @@
"verify:sidebars": "node ./scripts/verify-sidebars"
},
"devDependencies": {
- "@spotify/prettier-config": "^9.0.0",
+ "@spotify/prettier-config": "^10.0.0",
"docusaurus": "^2.0.0-alpha.70",
- "js-yaml": "^4.0.0",
+ "js-yaml": "^4.1.0",
"prettier": "^2.2.1"
},
"prettier": "@spotify/prettier-config"
diff --git a/microsite/pages/en/index.js b/microsite/pages/en/index.js
index 47f9568d9b..a761d6b6e2 100644
--- a/microsite/pages/en/index.js
+++ b/microsite/pages/en/index.js
@@ -292,7 +292,7 @@ class Index extends React.Component {
Build your own software templates
Contribute
diff --git a/microsite/sidebars.json b/microsite/sidebars.json
index 64ff108a78..86e94d9fbd 100644
--- a/microsite/sidebars.json
+++ b/microsite/sidebars.json
@@ -22,16 +22,6 @@
"getting-started/app-custom-theme"
]
},
- {
- "type": "subcategory",
- "label": "Deployment",
- "ids": [
- "getting-started/deployment-docker",
- "getting-started/deployment-k8s",
- "getting-started/deployment-helm",
- "getting-started/deployment-other"
- ]
- },
"getting-started/keeping-backstage-updated",
"getting-started/concepts",
"getting-started/contributors"
@@ -211,6 +201,12 @@
"auth/auth-backend-classes",
"auth/glossary"
],
+ "Deployment": [
+ "deployment/index",
+ "deployment/k8s",
+ "deployment/helm",
+ "deployment/heroku"
+ ],
"Designing for Backstage": [
"dls/design",
"dls/contributing-to-storybook",
diff --git a/microsite/yarn.lock b/microsite/yarn.lock
index 898fd69ad2..bed7897abc 100644
--- a/microsite/yarn.lock
+++ b/microsite/yarn.lock
@@ -909,10 +909,10 @@
resolved "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd"
integrity sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==
-"@spotify/prettier-config@^9.0.0":
- version "9.0.0"
- resolved "https://registry.yarnpkg.com/@spotify/prettier-config/-/prettier-config-9.0.0.tgz#7b562d56573c6fc0094446fbc92b22bc318945dc"
- integrity sha512-In1q0tIiqTYKAGe3KOHDcFDdZRFISyQeSeipeTHGfki23ebHRZcjxvqj5SSdBkw65D4VpSREMi0s9i5iJiMcTw==
+"@spotify/prettier-config@^10.0.0":
+ version "10.0.0"
+ resolved "https://registry.yarnpkg.com/@spotify/prettier-config/-/prettier-config-10.0.0.tgz#fa076d98d2e7e6c53dd3d86a696307a7010bd056"
+ integrity sha512-VYOdo8P7lIScAkl02nB9KpUAuOYMManryBIBuKJkAw5D3aVtLobfmdIKvdV6MqEmGMEQPbn7w/UpnjJYhUH+IA==
"@types/cheerio@^0.22.8":
version "0.22.23"
@@ -3862,10 +3862,10 @@ js-yaml@^3.13.1, js-yaml@^3.8.1:
argparse "^1.0.7"
esprima "^4.0.0"
-js-yaml@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.0.0.tgz#f426bc0ff4b4051926cd588c71113183409a121f"
- integrity sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==
+js-yaml@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
+ integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
dependencies:
argparse "^2.0.1"
diff --git a/package.json b/package.json
index fcabe5b3e5..d87b98fbdb 100644
--- a/package.json
+++ b/package.json
@@ -38,7 +38,9 @@
"resolutions": {
"**/@roadiehq/**/@backstage/core": "*",
"**/@roadiehq/**/@backstage/plugin-catalog": "*",
- "**/@roadiehq/**/@backstage/catalog-model": "*"
+ "**/@roadiehq/**/@backstage/catalog-model": "*",
+ "graphql-language-service-interface": "2.8.2",
+ "graphql-language-service-parser": "1.9.0"
},
"version": "1.0.0",
"devDependencies": {
diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md
index 5b96841e65..81a7f66f4c 100644
--- a/packages/app/CHANGELOG.md
+++ b/packages/app/CHANGELOG.md
@@ -1,5 +1,30 @@
# example-app
+## 0.2.24
+
+### Patch Changes
+
+- Updated dependencies [a360f9478]
+- Updated dependencies [bb5055aee]
+- Updated dependencies [d0d1c2f7b]
+- Updated dependencies [5d0740563]
+- Updated dependencies [12390778e]
+- Updated dependencies [fef852ecd]
+- Updated dependencies [18f7345a6]
+- Updated dependencies [5cafcf452]
+- Updated dependencies [86a95ba67]
+- Updated dependencies [e27cb6c45]
+- Updated dependencies [184b02bef]
+- Updated dependencies [60ce64aa2]
+ - @backstage/plugin-scaffolder@0.9.0
+ - @backstage/catalog-model@0.7.7
+ - @backstage/core@0.7.5
+ - @backstage/plugin-catalog@0.5.4
+ - @backstage/plugin-api-docs@0.4.11
+ - @backstage/plugin-techdocs@0.7.2
+ - @backstage/plugin-tech-radar@0.3.9
+ - @backstage/cli@0.6.8
+
## 0.2.23
### Patch Changes
diff --git a/packages/app/package.json b/packages/app/package.json
index 2bac2e6e09..4d2dbe660a 100644
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -1,16 +1,16 @@
{
"name": "example-app",
- "version": "0.2.23",
+ "version": "0.2.24",
"private": true,
"bundled": true,
"dependencies": {
- "@backstage/catalog-model": "^0.7.6",
- "@backstage/cli": "^0.6.7",
- "@backstage/core": "^0.7.4",
+ "@backstage/catalog-model": "^0.7.7",
+ "@backstage/cli": "^0.6.8",
+ "@backstage/core": "^0.7.5",
"@backstage/integration-react": "^0.1.1",
- "@backstage/plugin-api-docs": "^0.4.9",
+ "@backstage/plugin-api-docs": "^0.4.11",
"@backstage/plugin-badges": "^0.2.0",
- "@backstage/plugin-catalog": "^0.5.3",
+ "@backstage/plugin-catalog": "^0.5.4",
"@backstage/plugin-catalog-import": "^0.5.2",
"@backstage/plugin-catalog-react": "^0.1.3",
"@backstage/plugin-circleci": "^0.2.12",
@@ -19,8 +19,6 @@
"@backstage/plugin-explore": "^0.3.2",
"@backstage/plugin-gcp-projects": "^0.2.5",
"@backstage/plugin-github-actions": "^0.4.2",
- "@backstage/plugin-github-deployments": "^0.1.2",
- "@backstage/plugin-gitops-profiles": "^0.2.6",
"@backstage/plugin-graphiql": "^0.2.9",
"@backstage/plugin-jenkins": "^0.4.1",
"@backstage/plugin-kafka": "^0.2.6",
@@ -29,23 +27,22 @@
"@backstage/plugin-newrelic": "^0.2.6",
"@backstage/plugin-org": "^0.3.12",
"@backstage/plugin-pagerduty": "0.3.2",
- "@backstage/plugin-register-component": "^0.2.12",
"@backstage/plugin-rollbar": "^0.3.3",
- "@backstage/plugin-scaffolder": "^0.8.2",
+ "@backstage/plugin-scaffolder": "^0.9.0",
"@backstage/plugin-search": "^0.3.4",
"@backstage/plugin-sentry": "^0.3.8",
- "@backstage/plugin-tech-radar": "^0.3.8",
- "@backstage/plugin-techdocs": "^0.7.0",
+ "@backstage/plugin-tech-radar": "^0.3.9",
+ "@backstage/plugin-techdocs": "^0.7.2",
"@backstage/plugin-todo": "^0.1.0",
"@backstage/plugin-user-settings": "^0.2.8",
"@backstage/theme": "^0.2.5",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@octokit/rest": "^18.0.12",
- "@roadiehq/backstage-plugin-buildkite": "^0.3.0",
- "@roadiehq/backstage-plugin-github-insights": "^0.3.3",
- "@roadiehq/backstage-plugin-github-pull-requests": "^0.7.9",
- "@roadiehq/backstage-plugin-travis-ci": "^0.4.7",
+ "@roadiehq/backstage-plugin-buildkite": "^1.0.0",
+ "@roadiehq/backstage-plugin-github-insights": "^1.0.0",
+ "@roadiehq/backstage-plugin-github-pull-requests": "^1.0.0",
+ "@roadiehq/backstage-plugin-travis-ci": "^1.0.0",
"history": "^5.0.0",
"prop-types": "^15.7.2",
"react": "^16.12.0",
diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx
index d8ee62a12c..d4db2b0e63 100644
--- a/packages/app/src/components/catalog/EntityPage.tsx
+++ b/packages/app/src/components/catalog/EntityPage.tsx
@@ -81,27 +81,27 @@ import { EntitySentryContent } from '@backstage/plugin-sentry';
import { EntityTechdocsContent } from '@backstage/plugin-techdocs';
import { EntityTodoContent } from '@backstage/plugin-todo';
import { Button, Grid } from '@material-ui/core';
-// import {
-// EntityBuildkiteContent,
-// isBuildkiteAvailable,
-// } from '@roadiehq/backstage-plugin-buildkite';
-// import {
-// EntityGitHubInsightsContent,
-// EntityLanguagesCard,
-// EntityReadMeCard,
-// EntityReleasesCard,
-// isGithubInsightsAvailable,
-// } from '@roadiehq/backstage-plugin-github-insights';
-// import {
-// EntityGithubPullRequestsContent,
-// EntityGithubPullRequestsOverviewCard,
-// isGithubPullRequestsAvailable,
-// } from '@roadiehq/backstage-plugin-github-pull-requests';
-// import {
-// EntityTravisCIContent,
-// EntityTravisCIOverviewCard,
-// isTravisciAvailable,
-// } from '@roadiehq/backstage-plugin-travis-ci';
+import {
+ EntityBuildkiteContent,
+ isBuildkiteAvailable,
+} from '@roadiehq/backstage-plugin-buildkite';
+import {
+ EntityGithubInsightsContent,
+ EntityGithubInsightsLanguagesCard,
+ EntityGithubInsightsReadmeCard,
+ EntityGithubInsightsReleasesCard,
+ isGithubInsightsAvailable,
+} from '@roadiehq/backstage-plugin-github-insights';
+import {
+ EntityGithubPullRequestsContent,
+ EntityGithubPullRequestsOverviewCard,
+ isGithubPullRequestsAvailable,
+} from '@roadiehq/backstage-plugin-github-pull-requests';
+import {
+ EntityTravisCIContent,
+ EntityTravisCIOverviewCard,
+ isTravisciAvailable,
+} from '@roadiehq/backstage-plugin-travis-ci';
const EntityLayoutWrapper = (props: { children?: ReactNode }) => {
const [badgesDialogOpen, setBadgesDialogOpen] = useState(false);
@@ -135,9 +135,9 @@ export const cicdContent = (
- {/*
+
- */}
+
@@ -147,9 +147,9 @@ export const cicdContent = (
- {/*
+
- */}
+
@@ -182,11 +182,11 @@ const cicdCard = (
- {/*
+
- */}
+
@@ -228,17 +228,17 @@ const overviewContent = (
{cicdCard}
- {/*
+ Boolean(isGithubInsightsAvailable(e))}>
-
-
+
+
-
+
- */}
+
@@ -248,13 +248,13 @@ const overviewContent = (
- {/*
+ Boolean(isGithubPullRequestsAvailable(e))}>
- */}
+
@@ -295,13 +295,13 @@ const serviceEntityPage = (
- {/*
+
- */}
+
- {/*
-
- */}
+
+
+
@@ -339,13 +339,13 @@ const websiteEntityPage = (
- {/*
+
- */}
+
- {/*
+
- */}
+
@@ -441,10 +441,6 @@ const groupPage = (
-
-
-
-
);
@@ -463,6 +459,9 @@ const systemPage = (
+
+
+
);
diff --git a/packages/app/src/plugins.ts b/packages/app/src/plugins.ts
index ccc7e2a45c..7dbea5b995 100644
--- a/packages/app/src/plugins.ts
+++ b/packages/app/src/plugins.ts
@@ -13,36 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-export { plugin as LighthousePlugin } from '@backstage/plugin-lighthouse';
-export { catalogPlugin } from '@backstage/plugin-catalog';
-export { scaffolderPlugin } from '@backstage/plugin-scaffolder';
-export { plugin as TechRadar } from '@backstage/plugin-tech-radar';
-export { explorePlugin } from '@backstage/plugin-explore';
-export { plugin as Circleci } from '@backstage/plugin-circleci';
-export { plugin as RegisterComponent } from '@backstage/plugin-register-component';
-export { plugin as Sentry } from '@backstage/plugin-sentry';
-export { plugin as GitopsProfiles } from '@backstage/plugin-gitops-profiles';
-export { plugin as TechDocs } from '@backstage/plugin-techdocs';
-export { plugin as GraphiQL } from '@backstage/plugin-graphiql';
-export { plugin as GithubActions } from '@backstage/plugin-github-actions';
-export { plugin as Rollbar } from '@backstage/plugin-rollbar';
-export { plugin as Newrelic } from '@backstage/plugin-newrelic';
-export { plugin as TravisCI } from '@roadiehq/backstage-plugin-travis-ci';
-export { plugin as Jenkins } from '@backstage/plugin-jenkins';
-export { plugin as ApiDocs } from '@backstage/plugin-api-docs';
-export { plugin as GithubPullRequests } from '@roadiehq/backstage-plugin-github-pull-requests';
-export { plugin as GcpProjects } from '@backstage/plugin-gcp-projects';
-export { plugin as Kubernetes } from '@backstage/plugin-kubernetes';
-export { plugin as Cloudbuild } from '@backstage/plugin-cloudbuild';
-export { plugin as CostInsights } from '@backstage/plugin-cost-insights';
-export { plugin as GitHubInsights } from '@roadiehq/backstage-plugin-github-insights';
-export { plugin as CatalogImport } from '@backstage/plugin-catalog-import';
-export { plugin as UserSettings } from '@backstage/plugin-user-settings';
-export { plugin as PagerDuty } from '@backstage/plugin-pagerduty';
-export { plugin as Buildkite } from '@roadiehq/backstage-plugin-buildkite';
-export { plugin as Search } from '@backstage/plugin-search';
-export { plugin as Org } from '@backstage/plugin-org';
-export { plugin as Kafka } from '@backstage/plugin-kafka';
-export { todoPlugin } from '@backstage/plugin-todo';
+
+// TODO(Rugvip): This plugin is currently not part of the app element tree,
+// ideally we have an API for the context menu that permits that.
export { badgesPlugin } from '@backstage/plugin-badges';
-export { githubDeploymentsPlugin } from '@backstage/plugin-github-deployments';
diff --git a/packages/backend-common/config.d.ts b/packages/backend-common/config.d.ts
index 74c199e737..41845f952e 100644
--- a/packages/backend-common/config.d.ts
+++ b/packages/backend-common/config.d.ts
@@ -57,7 +57,7 @@ export interface Config {
database:
| {
client: 'sqlite3';
- connection: ':memory:' | string;
+ connection: ':memory:' | string | { filename: string };
}
| {
client: 'pg';
diff --git a/packages/backend-common/src/database/connection.ts b/packages/backend-common/src/database/connection.ts
index 17ef2c461d..3502e21674 100644
--- a/packages/backend-common/src/database/connection.ts
+++ b/packages/backend-common/src/database/connection.ts
@@ -37,7 +37,7 @@ export function createDatabaseClient(
if (client === 'pg') {
return createPgDatabaseClient(dbConfig, overrides);
} else if (client === 'sqlite3') {
- return createSqliteDatabaseClient(dbConfig);
+ return createSqliteDatabaseClient(dbConfig, overrides);
}
return knexFactory(mergeDatabaseConfig(dbConfig.get(), overrides));
diff --git a/packages/backend-common/src/database/sqlite3.test.ts b/packages/backend-common/src/database/sqlite3.test.ts
index a6b8e5d84d..86f3a6968b 100644
--- a/packages/backend-common/src/database/sqlite3.test.ts
+++ b/packages/backend-common/src/database/sqlite3.test.ts
@@ -15,6 +15,7 @@
*/
import { ConfigReader } from '@backstage/config';
+import path from 'path';
import {
buildSqliteDatabaseConfig,
createSqliteDatabaseClient,
@@ -25,25 +26,70 @@ describe('sqlite3', () => {
new ConfigReader({ client: 'sqlite3', connection });
describe('buildSqliteDatabaseConfig', () => {
- it('buidls a string connection', () => {
+ it('builds an in-memory connection', () => {
expect(buildSqliteDatabaseConfig(createConfig(':memory:'))).toEqual({
client: 'sqlite3',
- connection: ':memory:',
+ connection: { filename: ':memory:' },
useNullAsDefault: true,
});
});
- it('builds a filename connection', () => {
+ it('builds an in-memory connection by override with filename', () => {
+ expect(
+ buildSqliteDatabaseConfig(
+ createConfig(path.join('path', 'to', 'foo')),
+ { connection: ':memory:' },
+ ),
+ ).toEqual({
+ client: 'sqlite3',
+ connection: { filename: ':memory:' },
+ useNullAsDefault: true,
+ });
+ });
+
+ it('builds a persistent connection, normalize config with filename', () => {
+ expect(
+ buildSqliteDatabaseConfig(createConfig(path.join('path', 'to', 'foo'))),
+ ).toEqual({
+ client: 'sqlite3',
+ connection: { filename: path.join('path', 'to', 'foo') },
+ useNullAsDefault: true,
+ });
+ });
+
+ it('builds a persistent connection', () => {
expect(
buildSqliteDatabaseConfig(
createConfig({
- filename: '/path/to/foo',
+ filename: path.join('path', 'to', 'foo'),
}),
),
).toEqual({
client: 'sqlite3',
connection: {
- filename: '/path/to/foo',
+ filename: path.join('path', 'to', 'foo'),
+ },
+ useNullAsDefault: true,
+ });
+ });
+
+ it('builds a persistent connection per database', () => {
+ expect(
+ buildSqliteDatabaseConfig(
+ createConfig({
+ filename: path.join('path', 'to', 'foo'),
+ }),
+ {
+ connection: {
+ database: 'my-database',
+ },
+ },
+ ),
+ ).toEqual({
+ client: 'sqlite3',
+ connection: {
+ filename: path.join('path', 'to', 'foo', 'my-database.sqlite'),
+ database: 'my-database',
},
useNullAsDefault: true,
});
@@ -52,12 +98,12 @@ describe('sqlite3', () => {
it('replaces the connection with an override', () => {
expect(
buildSqliteDatabaseConfig(createConfig(':memory:'), {
- connection: { filename: '/path/to/foo' },
+ connection: { filename: path.join('path', 'to', 'foo') },
}),
).toEqual({
client: 'sqlite3',
connection: {
- filename: '/path/to/foo',
+ filename: path.join('path', 'to', 'foo'),
},
useNullAsDefault: true,
});
diff --git a/packages/backend-common/src/database/sqlite3.ts b/packages/backend-common/src/database/sqlite3.ts
index f5742c68a0..d4169e3899 100644
--- a/packages/backend-common/src/database/sqlite3.ts
+++ b/packages/backend-common/src/database/sqlite3.ts
@@ -14,8 +14,10 @@
* limitations under the License.
*/
-import knexFactory, { Knex } from 'knex';
import { Config } from '@backstage/config';
+import { ensureDirSync } from 'fs-extra';
+import knexFactory, { Knex } from 'knex';
+import path from 'path';
import { mergeDatabaseConfig } from './config';
/**
@@ -29,6 +31,19 @@ export function createSqliteDatabaseClient(
overrides?: Knex.Config,
) {
const knexConfig = buildSqliteDatabaseConfig(dbConfig, overrides);
+
+ // If storage on disk is used, ensure that the directory exists
+ if (
+ (knexConfig.connection as Knex.Sqlite3ConnectionConfig).filename &&
+ (knexConfig.connection as Knex.Sqlite3ConnectionConfig).filename !==
+ ':memory:'
+ ) {
+ const { filename } = knexConfig.connection as Knex.Sqlite3ConnectionConfig;
+ const directory = path.dirname(filename);
+
+ ensureDirSync(directory);
+ }
+
const database = knexFactory(knexConfig);
database.client.pool.on('createSuccess', (_eventId: any, resource: any) => {
@@ -47,12 +62,40 @@ export function createSqliteDatabaseClient(
export function buildSqliteDatabaseConfig(
dbConfig: Config,
overrides?: Knex.Config,
-) {
- return mergeDatabaseConfig(
- dbConfig.get(),
+): Knex.Config {
+ const baseConfig = dbConfig.get();
+
+ // Normalize config to always contain a connection object
+ if (typeof baseConfig.connection === 'string') {
+ baseConfig.connection = { filename: baseConfig.connection };
+ }
+ if (overrides && typeof overrides.connection === 'string') {
+ overrides.connection = { filename: overrides.connection };
+ }
+
+ const config: Knex.Config = mergeDatabaseConfig(
+ {
+ connection: {},
+ },
+ baseConfig,
{
useNullAsDefault: true,
},
overrides,
);
+
+ // If we don't create an in-memory database, interpret the connection string
+ // as a directory that contains multiple sqlite files based on the database
+ // name.
+ const database = (config.connection as Knex.ConnectionConfig).database;
+ const sqliteConnection = config.connection as Knex.Sqlite3ConnectionConfig;
+
+ if (database && sqliteConnection.filename !== ':memory:') {
+ sqliteConnection.filename = path.join(
+ sqliteConnection.filename,
+ `${database}.sqlite`,
+ );
+ }
+
+ return config;
}
diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts
index 9883f2afbc..d0387cd15d 100644
--- a/packages/backend/src/plugins/techdocs.ts
+++ b/packages/backend/src/plugins/techdocs.ts
@@ -48,6 +48,9 @@ export default async function createPlugin({
discovery,
});
+ // checks if the publisher is working and logs the result
+ await publisher.getReadiness();
+
// Docker client (conditionally) used by the generators, based on techdocs.generators config.
const dockerClient = new Docker();
diff --git a/packages/catalog-client/CHANGELOG.md b/packages/catalog-client/CHANGELOG.md
index 8364560625..0076202c64 100644
--- a/packages/catalog-client/CHANGELOG.md
+++ b/packages/catalog-client/CHANGELOG.md
@@ -1,5 +1,14 @@
# @backstage/catalog-client
+## 0.3.10
+
+### Patch Changes
+
+- 442f34b87: Make sure the `CatalogClient` escapes URL parameters correctly.
+- Updated dependencies [bb5055aee]
+- Updated dependencies [5d0740563]
+ - @backstage/catalog-model@0.7.7
+
## 0.3.9
### Patch Changes
diff --git a/packages/catalog-client/package.json b/packages/catalog-client/package.json
index ea61f3eded..91845e56ab 100644
--- a/packages/catalog-client/package.json
+++ b/packages/catalog-client/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/catalog-client",
- "version": "0.3.9",
+ "version": "0.3.10",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -29,13 +29,13 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/catalog-model": "^0.7.5",
+ "@backstage/catalog-model": "^0.7.7",
"@backstage/config": "^0.1.4",
"@backstage/errors": "^0.1.1",
"cross-fetch": "^3.0.6"
},
"devDependencies": {
- "@backstage/cli": "^0.6.5",
+ "@backstage/cli": "^0.6.8",
"@types/jest": "^26.0.7",
"msw": "^0.21.2"
},
diff --git a/packages/catalog-client/src/CatalogClient.ts b/packages/catalog-client/src/CatalogClient.ts
index 7426aabaac..3de25e9808 100644
--- a/packages/catalog-client/src/CatalogClient.ts
+++ b/packages/catalog-client/src/CatalogClient.ts
@@ -42,10 +42,14 @@ export class CatalogClient implements CatalogApi {
}
async getLocationById(
- id: String,
+ id: string,
options?: CatalogRequestOptions,
): Promise {
- return await this.requestOptional('GET', `/locations/${id}`, options);
+ return await this.requestOptional(
+ 'GET',
+ `/locations/${encodeURIComponent(id)}`,
+ options,
+ );
}
async getEntities(
@@ -86,7 +90,9 @@ export class CatalogClient implements CatalogApi {
const { kind, namespace = 'default', name } = compoundName;
return this.requestOptional(
'GET',
- `/entities/by-name/${kind}/${namespace}/${name}`,
+ `/entities/by-name/${encodeURIComponent(kind)}/${encodeURIComponent(
+ namespace,
+ )}/${encodeURIComponent(name)}`,
options,
);
}
@@ -171,14 +177,22 @@ export class CatalogClient implements CatalogApi {
id: string,
options?: CatalogRequestOptions,
): Promise {
- await this.requestIgnored('DELETE', `/locations/${id}`, options);
+ await this.requestIgnored(
+ 'DELETE',
+ `/locations/${encodeURIComponent(id)}`,
+ options,
+ );
}
async removeEntityByUid(
uid: string,
options?: CatalogRequestOptions,
): Promise {
- await this.requestIgnored('DELETE', `/entities/by-uid/${uid}`, options);
+ await this.requestIgnored(
+ 'DELETE',
+ `/entities/by-uid/${encodeURIComponent(uid)}`,
+ options,
+ );
}
//
diff --git a/packages/catalog-client/src/types.ts b/packages/catalog-client/src/types.ts
index 04cbb2b689..0d25bf7483 100644
--- a/packages/catalog-client/src/types.ts
+++ b/packages/catalog-client/src/types.ts
@@ -46,7 +46,7 @@ export interface CatalogApi {
// Locations
getLocationById(
- id: String,
+ id: string,
options?: CatalogRequestOptions,
): Promise;
getOriginLocationByEntity(
diff --git a/packages/catalog-model/CHANGELOG.md b/packages/catalog-model/CHANGELOG.md
index 71a0ba6676..445da3afab 100644
--- a/packages/catalog-model/CHANGELOG.md
+++ b/packages/catalog-model/CHANGELOG.md
@@ -1,5 +1,18 @@
# @backstage/catalog-model
+## 0.7.7
+
+### Patch Changes
+
+- bb5055aee: Add getEntitySourceLocation helper
+- 5d0740563: Implemented missing support for the dependsOn/dependencyOf relationships
+ between `Component` and `Resource` catalog model objects.
+
+ Added support for generating the relevant relationships to the
+ `BuiltinKindsEntityProcessor`, and added simple support for fetching
+ relationships between `Components` and `Resources` for rendering in the
+ system diagram. All catalog-model changes backwards compatible.
+
## 0.7.6
### Patch Changes
diff --git a/packages/catalog-model/package.json b/packages/catalog-model/package.json
index 680c73e905..64550b35d1 100644
--- a/packages/catalog-model/package.json
+++ b/packages/catalog-model/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/catalog-model",
- "version": "0.7.6",
+ "version": "0.7.7",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -39,7 +39,7 @@
"yup": "^0.29.3"
},
"devDependencies": {
- "@backstage/cli": "^0.6.7",
+ "@backstage/cli": "^0.6.8",
"@types/express": "^4.17.6",
"@types/jest": "^26.0.7",
"@types/lodash": "^4.14.151",
diff --git a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.test.ts b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.test.ts
index 9284a5d5b1..358e7b6526 100644
--- a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.test.ts
+++ b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.test.ts
@@ -36,6 +36,7 @@ describe('ComponentV1alpha1Validator', () => {
subcomponentOf: 'monolith',
providesApis: ['api-0'],
consumesApis: ['api-0'],
+ dependsOn: ['resource:resource-0', 'component:component-0'],
system: 'system',
},
};
@@ -160,6 +161,26 @@ describe('ComponentV1alpha1Validator', () => {
await expect(validator.check(entity)).resolves.toBe(true);
});
+ it('accepts missing dependsOn', async () => {
+ delete (entity as any).spec.dependsOn;
+ await expect(validator.check(entity)).resolves.toBe(true);
+ });
+
+ it('rejects empty dependsOn', async () => {
+ (entity as any).spec.dependsOn = [''];
+ await expect(validator.check(entity)).rejects.toThrow(/dependsOn/);
+ });
+
+ it('rejects undefined dependsOn', async () => {
+ (entity as any).spec.dependsOn = [undefined];
+ await expect(validator.check(entity)).rejects.toThrow(/dependsOn/);
+ });
+
+ it('accepts no dependsOn', async () => {
+ (entity as any).spec.dependsOn = [];
+ await expect(validator.check(entity)).resolves.toBe(true);
+ });
+
it('accepts missing system', async () => {
delete (entity as any).spec.system;
await expect(validator.check(entity)).resolves.toBe(true);
diff --git a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts
index 3006a4288c..489aa8b1bd 100644
--- a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts
+++ b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts
@@ -34,6 +34,7 @@ export interface ComponentEntityV1alpha1 extends Entity {
subcomponentOf?: string;
providesApis?: string[];
consumesApis?: string[];
+ dependsOn?: string[];
system?: string;
};
}
diff --git a/packages/catalog-model/src/kinds/ResourceEntityV1alpha1.test.ts b/packages/catalog-model/src/kinds/ResourceEntityV1alpha1.test.ts
index ad8ea5cdf3..953ec5889a 100644
--- a/packages/catalog-model/src/kinds/ResourceEntityV1alpha1.test.ts
+++ b/packages/catalog-model/src/kinds/ResourceEntityV1alpha1.test.ts
@@ -32,6 +32,7 @@ describe('ResourceV1alpha1Validator', () => {
spec: {
type: 'database',
owner: 'me',
+ dependsOn: ['component:component-0', 'resource:resource-0'],
system: 'system',
},
};
@@ -86,6 +87,26 @@ describe('ResourceV1alpha1Validator', () => {
await expect(validator.check(entity)).rejects.toThrow(/owner/);
});
+ it('accepts missing dependsOn', async () => {
+ delete (entity as any).spec.dependsOn;
+ await expect(validator.check(entity)).resolves.toBe(true);
+ });
+
+ it('rejects empty dependsOn', async () => {
+ (entity as any).spec.dependsOn = [''];
+ await expect(validator.check(entity)).rejects.toThrow(/dependsOn/);
+ });
+
+ it('rejects undefined dependsOn', async () => {
+ (entity as any).spec.dependsOn = [undefined];
+ await expect(validator.check(entity)).rejects.toThrow(/dependsOn/);
+ });
+
+ it('accepts no dependsOn', async () => {
+ (entity as any).spec.dependsOn = [];
+ await expect(validator.check(entity)).resolves.toBe(true);
+ });
+
it('accepts missing system', async () => {
delete (entity as any).spec.system;
await expect(validator.check(entity)).resolves.toBe(true);
diff --git a/packages/catalog-model/src/kinds/ResourceEntityV1alpha1.ts b/packages/catalog-model/src/kinds/ResourceEntityV1alpha1.ts
index 520a39f02c..fd71500f40 100644
--- a/packages/catalog-model/src/kinds/ResourceEntityV1alpha1.ts
+++ b/packages/catalog-model/src/kinds/ResourceEntityV1alpha1.ts
@@ -30,6 +30,7 @@ export interface ResourceEntityV1alpha1 extends Entity {
spec: {
type: string;
owner: string;
+ dependsOn?: string[];
system?: string;
};
}
diff --git a/packages/catalog-model/src/location/helpers.test.ts b/packages/catalog-model/src/location/helpers.test.ts
index 888c79b328..3b5994b956 100644
--- a/packages/catalog-model/src/location/helpers.test.ts
+++ b/packages/catalog-model/src/location/helpers.test.ts
@@ -14,7 +14,11 @@
* limitations under the License.
*/
-import { parseLocationReference, stringifyLocationReference } from './helpers';
+import {
+ getEntitySourceLocation,
+ parseLocationReference,
+ stringifyLocationReference,
+} from './helpers';
describe('parseLocationReference', () => {
it('works for the simple case', () => {
@@ -68,3 +72,48 @@ describe('stringifyLocationReference', () => {
).toThrow('Unable to stringify location reference, empty target');
});
});
+
+describe('getEntitySourceLocation', () => {
+ it('returns the source-location', () => {
+ expect(
+ getEntitySourceLocation({
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Location',
+ metadata: {
+ name: 'test',
+ namespace: 'default',
+ annotations: {
+ 'backstage.io/source-location': 'url:https://backstage.io/foo.yaml',
+ 'backstage.io/managed-by-location': 'url:https://spotify.com',
+ },
+ },
+ }),
+ ).toEqual({ target: 'https://backstage.io/foo.yaml', type: 'url' });
+ });
+
+ it('returns the managed-by-location', () => {
+ expect(
+ getEntitySourceLocation({
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Location',
+ metadata: {
+ name: 'test',
+ namespace: 'default',
+ annotations: {
+ 'backstage.io/managed-by-location': 'url:https://spotify.com',
+ },
+ },
+ }),
+ ).toEqual({ target: 'https://spotify.com', type: 'url' });
+ });
+
+ it('rejects missing location annotation', () => {
+ expect(() =>
+ getEntitySourceLocation({
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Location',
+ metadata: { name: 'test', namespace: 'default' },
+ }),
+ ).toThrow(`Entity 'location:default/test' is missing location`);
+ });
+});
diff --git a/packages/catalog-model/src/location/helpers.ts b/packages/catalog-model/src/location/helpers.ts
index fb1be5abad..5eff598c87 100644
--- a/packages/catalog-model/src/location/helpers.ts
+++ b/packages/catalog-model/src/location/helpers.ts
@@ -14,6 +14,9 @@
* limitations under the License.
*/
+import { Entity, stringifyEntityRef } from '../entity';
+import { LOCATION_ANNOTATION, SOURCE_LOCATION_ANNOTATION } from './annotation';
+
/**
* Parses a string form location reference.
*
@@ -80,3 +83,26 @@ export function stringifyLocationReference(ref: {
return `${type}:${target}`;
}
+
+/**
+ * Returns the source code location of the Entity, to the extent that one exists.
+ *
+ * If the returned location type is of type 'url', the target should be readable at least
+ * using the UrlReader from @backstage/backend-common. If it is not of type 'url', the caller
+ * needs to have explicit handling of each location type or signal that it is not supported.
+ */
+export function getEntitySourceLocation(
+ entity: Entity,
+): { type: string; target: string } {
+ const locationRef =
+ entity.metadata?.annotations?.[SOURCE_LOCATION_ANNOTATION] ??
+ entity.metadata?.annotations?.[LOCATION_ANNOTATION];
+
+ if (!locationRef) {
+ throw new Error(
+ `Entity '${stringifyEntityRef(entity)}' is missing location`,
+ );
+ }
+
+ return parseLocationReference(locationRef);
+}
diff --git a/packages/catalog-model/src/location/index.ts b/packages/catalog-model/src/location/index.ts
index fddc8bde37..751172c6ba 100644
--- a/packages/catalog-model/src/location/index.ts
+++ b/packages/catalog-model/src/location/index.ts
@@ -19,7 +19,11 @@ export {
ORIGIN_LOCATION_ANNOTATION,
SOURCE_LOCATION_ANNOTATION,
} from './annotation';
-export { parseLocationReference, stringifyLocationReference } from './helpers';
+export {
+ parseLocationReference,
+ stringifyLocationReference,
+ getEntitySourceLocation,
+} from './helpers';
export type { Location, LocationSpec } from './types';
export {
analyzeLocationSchema,
diff --git a/packages/catalog-model/src/schema/kinds/Component.v1alpha1.schema.json b/packages/catalog-model/src/schema/kinds/Component.v1alpha1.schema.json
index 822059a0ea..34fc48d285 100644
--- a/packages/catalog-model/src/schema/kinds/Component.v1alpha1.schema.json
+++ b/packages/catalog-model/src/schema/kinds/Component.v1alpha1.schema.json
@@ -84,6 +84,14 @@
"type": "string",
"minLength": 1
}
+ },
+ "dependsOn": {
+ "type": "array",
+ "description": "An array of references to other entities that the component depends on to function.",
+ "items": {
+ "type": "string",
+ "minLength": 1
+ }
}
}
}
diff --git a/packages/catalog-model/src/schema/kinds/Resource.v1alpha1.schema.json b/packages/catalog-model/src/schema/kinds/Resource.v1alpha1.schema.json
index b426dd7f94..4958afc18c 100644
--- a/packages/catalog-model/src/schema/kinds/Resource.v1alpha1.schema.json
+++ b/packages/catalog-model/src/schema/kinds/Resource.v1alpha1.schema.json
@@ -47,6 +47,14 @@
"examples": ["artist-relations-team", "user:john.johnson"],
"minLength": 1
},
+ "dependsOn": {
+ "type": "array",
+ "description": "An array of references to other entities that the resource depends on to function.",
+ "items": {
+ "type": "string",
+ "minLength": 1
+ }
+ },
"system": {
"type": "string",
"description": "An entity reference to the system that the resource belongs to.",
diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md
index f9dc749009..a1381495ff 100644
--- a/packages/cli/CHANGELOG.md
+++ b/packages/cli/CHANGELOG.md
@@ -1,5 +1,11 @@
# @backstage/cli
+## 0.6.8
+
+### Patch Changes
+
+- 60ce64aa2: Disable hot reloading in CI environments.
+
## 0.6.7
### Patch Changes
diff --git a/packages/cli/package.json b/packages/cli/package.json
index 22b116e87c..548e659e76 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/cli",
"description": "CLI for developing Backstage plugins and apps",
- "version": "0.6.7",
+ "version": "0.6.8",
"private": false,
"publishConfig": {
"access": "public"
@@ -62,7 +62,7 @@
"commander": "^6.1.0",
"css-loader": "^3.5.3",
"dashify": "^2.0.0",
- "diff": "^4.0.2",
+ "diff": "^5.0.0",
"esbuild": "^0.8.56",
"eslint": "^7.1.0",
"eslint-config-prettier": "^6.0.0",
@@ -118,7 +118,7 @@
"devDependencies": {
"@backstage/backend-common": "^0.6.2",
"@backstage/config": "^0.1.4",
- "@backstage/core": "^0.7.4",
+ "@backstage/core": "^0.7.5",
"@backstage/dev-utils": "^0.1.13",
"@backstage/test-utils": "^0.1.10",
"@backstage/theme": "^0.2.5",
diff --git a/packages/cli/src/commands/config/docs.ts b/packages/cli/src/commands/config/docs.ts
new file mode 100644
index 0000000000..e06bc42c27
--- /dev/null
+++ b/packages/cli/src/commands/config/docs.ts
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2021 Spotify AB
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { JsonObject } from '@backstage/config';
+import { mergeConfigSchemas } from '@backstage/config-loader';
+import { Command } from 'commander';
+import { JSONSchema7 as JSONSchema } from 'json-schema';
+import openBrowser from 'react-dev-utils/openBrowser';
+import { loadCliConfig } from '../../lib/config';
+
+const DOCS_URL = 'https://config.backstage.io';
+
+export default async (cmd: Command) => {
+ const { schema: appSchemas } = await loadCliConfig({
+ args: [],
+ fromPackage: cmd.package,
+ mockEnv: true,
+ });
+
+ const schema = mergeConfigSchemas(
+ (appSchemas.serialize().schemas as JsonObject[]).map(
+ _ => _.value as JSONSchema,
+ ),
+ );
+
+ openBrowser(`${DOCS_URL}#schema=${JSON.stringify(schema)}`);
+};
diff --git a/packages/cli/src/commands/create-plugin/createPlugin.ts b/packages/cli/src/commands/create-plugin/createPlugin.ts
index affbf64922..9fc5531dfa 100644
--- a/packages/cli/src/commands/create-plugin/createPlugin.ts
+++ b/packages/cli/src/commands/create-plugin/createPlugin.ts
@@ -106,24 +106,6 @@ export async function addPluginDependencyToApp(
});
}
-export async function addPluginImportToApp(
- rootDir: string,
- pluginVar: string,
- pluginPackage: string,
-) {
- const pluginExport = `export { ${pluginVar} } from '${pluginPackage}';`;
- const pluginsFilePath = 'packages/app/src/plugins.ts';
- const pluginsFile = resolvePath(rootDir, pluginsFilePath);
-
- await Task.forItem('processing', pluginsFilePath, async () => {
- await addExportStatement(pluginsFile, pluginExport).catch(error => {
- throw new Error(
- `Failed to import plugin in app: ${pluginsFile}: ${error.message}`,
- );
- });
- });
-}
-
export async function addPluginExtensionToApp(
pluginId: string,
extensionName: string,
@@ -320,7 +302,6 @@ export default async (cmd: Command) => {
await addPluginDependencyToApp(paths.targetRoot, name, pluginVersion);
Task.section('Import plugin in app');
- await addPluginImportToApp(paths.targetRoot, pluginVar, name);
await addPluginExtensionToApp(pluginId, extensionName, name);
}
diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts
index 46b6deeb21..c770239464 100644
--- a/packages/cli/src/commands/index.ts
+++ b/packages/cli/src/commands/index.ts
@@ -140,6 +140,15 @@ export function registerCommands(program: CommanderStatic) {
.description('Run tests, forwarding args to Jest, defaulting to watch mode')
.action(lazy(() => import('./testCommand').then(m => m.default)));
+ program
+ .command('config:docs')
+ .option(
+ '--package ',
+ 'Only include the schema that applies to the given package',
+ )
+ .description('Browse the configuration reference documentation')
+ .action(lazy(() => import('./config/docs').then(m => m.default)));
+
program
.command('config:print')
.option(
diff --git a/packages/cli/src/lib/bundler/server.ts b/packages/cli/src/lib/bundler/server.ts
index 5fc3a7b5de..24a24d25d2 100644
--- a/packages/cli/src/lib/bundler/server.ts
+++ b/packages/cli/src/lib/bundler/server.ts
@@ -43,7 +43,7 @@ export async function serveBundle(options: ServeOptions) {
const compiler = webpack(config);
const server = new WebpackDevServer(compiler, {
- hot: true,
+ hot: !process.env.CI,
contentBase: paths.targetPublic,
contentBasePublicPath: config.output?.publicPath,
publicPath: config.output?.publicPath,
diff --git a/packages/cli/src/lib/bundler/transforms.ts b/packages/cli/src/lib/bundler/transforms.ts
index 4f097343c2..6107485d78 100644
--- a/packages/cli/src/lib/bundler/transforms.ts
+++ b/packages/cli/src/lib/bundler/transforms.ts
@@ -74,7 +74,7 @@ export const transforms = (options: TransformOptions): Transforms => {
/\.jpe?g$/,
/\.png$/,
/\.frag/,
- { test: /\.svg/, not: [/\.icon\.svg/] },
+ { and: [/\.svg/, { not: [/\.icon\.svg/] }] },
/\.xml/,
],
loader: require.resolve('url-loader'),
diff --git a/packages/core-api/src/app/App.test.tsx b/packages/core-api/src/app/App.test.tsx
index c91eef1fb0..3cade002de 100644
--- a/packages/core-api/src/app/App.test.tsx
+++ b/packages/core-api/src/app/App.test.tsx
@@ -212,6 +212,9 @@ describe('Integration Test', () => {
expect(screen.getByText('extLink2: /foo/a')).toBeInTheDocument();
expect(screen.getByText('extLink3: /sub1')).toBeInTheDocument();
expect(screen.getByText('extLink4: /foo/b')).toBeInTheDocument();
+
+ // Plugins should be discovered through element tree
+ expect(app.getPlugins()).toEqual([plugin1, plugin2]);
});
it('runs happy paths without optional routes', async () => {
diff --git a/packages/core-api/src/app/App.tsx b/packages/core-api/src/app/App.tsx
index d2346e478c..fdbee075fc 100644
--- a/packages/core-api/src/app/App.tsx
+++ b/packages/core-api/src/app/App.tsx
@@ -52,6 +52,7 @@ import {
} from '../extensions/traversal';
import { IconComponent, IconComponentMap, IconKey } from '../icons';
import { BackstagePlugin } from '../plugin';
+import { pluginCollector } from '../plugin/collectors';
import { AnyRoutes } from '../plugin/types';
import { RouteRef, ExternalRouteRef, SubRouteRef } from '../routing';
import {
@@ -189,7 +190,7 @@ export class PrivateAppImpl implements BackstageApp {
private readonly apis: Iterable;
private readonly icons: IconComponentMap;
- private readonly plugins: BackstagePlugin[];
+ private readonly plugins: Set>;
private readonly components: AppComponents;
private readonly themes: AppTheme[];
private readonly configLoader?: AppConfigLoader;
@@ -201,7 +202,7 @@ export class PrivateAppImpl implements BackstageApp {
constructor(options: FullAppOptions) {
this.apis = options.apis;
this.icons = options.icons;
- this.plugins = options.plugins;
+ this.plugins = new Set(options.plugins);
this.components = options.components;
this.themes = options.themes;
this.configLoader = options.configLoader;
@@ -210,7 +211,7 @@ export class PrivateAppImpl implements BackstageApp {
}
getPlugins(): BackstagePlugin[] {
- return this.plugins;
+ return Array.from(this.plugins);
}
getSystemIcon(key: IconKey): IconComponent | undefined {
@@ -276,7 +277,6 @@ export class PrivateAppImpl implements BackstageApp {
getProvider(): ComponentType<{}> {
const appContext = new AppContextImpl(this);
- const apiHolder = this.getApiHolder();
const Provider = ({ children }: PropsWithChildren<{}>) => {
const appThemeApi = useMemo(
@@ -292,11 +292,25 @@ export class PrivateAppImpl implements BackstageApp {
routePaths: routePathCollector,
routeParents: routeParentCollector,
routeObjects: routeObjectCollector,
+ collectedPlugins: pluginCollector,
},
});
validateRoutes(result.routePaths, result.routeParents);
+ // TODO(Rugvip): Restructure the public API so that we can get an immediate view of
+ // the app, rather than having to wait for the provider to render.
+ // For now we need to push the additional plugins we find during
+ // collection and then make sure we initialize things afterwards.
+ result.collectedPlugins.forEach(plugin => this.plugins.add(plugin));
+ this.verifyPlugins(this.plugins);
+
+ // Initialize APIs once all plugins are available
+ if (this.apiHolder) {
+ throw new Error('Plugin holder was initialized too soon');
+ }
+ this.getApiHolder();
+
return result;
}, [children]);
@@ -340,7 +354,7 @@ export class PrivateAppImpl implements BackstageApp {
}
return (
-
+ ) {
const pluginIds = new Set();
- for (const plugin of this.plugins) {
+ for (const plugin of plugins) {
const id = plugin.getId();
if (pluginIds.has(id)) {
throw new Error(`Duplicate plugin found '${id}'`);
diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md
index 519a43788d..7f691a2787 100644
--- a/packages/core/CHANGELOG.md
+++ b/packages/core/CHANGELOG.md
@@ -1,5 +1,14 @@
# @backstage/core
+## 0.7.5
+
+### Patch Changes
+
+- d0d1c2f7b: Pass `inverse` prop to Gauge from GaugeCard
+- 5cafcf452: add debounce time attribute for apis-docs for search, giving more time to the users when they are typing.
+- 86a95ba67: exposes undocumented `PageTheme`
+- e27cb6c45: Don't use a drag & drop cursor when clicking on disabled `IconLinkVertical`.
+
## 0.7.4
### Patch Changes
diff --git a/packages/core/package.json b/packages/core/package.json
index d5f9042d0e..7b1898a19d 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/core",
"description": "Core API used by Backstage plugins and apps",
- "version": "0.7.4",
+ "version": "0.7.5",
"private": false,
"publishConfig": {
"access": "public",
@@ -69,7 +69,7 @@
"zen-observable": "^0.8.15"
},
"devDependencies": {
- "@backstage/cli": "^0.6.7",
+ "@backstage/cli": "^0.6.8",
"@backstage/test-utils": "^0.1.10",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
diff --git a/packages/core/src/components/HeaderIconLinkRow/IconLinkVertical.tsx b/packages/core/src/components/HeaderIconLinkRow/IconLinkVertical.tsx
index b3e665820a..425bed3490 100644
--- a/packages/core/src/components/HeaderIconLinkRow/IconLinkVertical.tsx
+++ b/packages/core/src/components/HeaderIconLinkRow/IconLinkVertical.tsx
@@ -35,12 +35,10 @@ const useIconStyles = makeStyles(theme => ({
justifyItems: 'center',
gridGap: 4,
textAlign: 'center',
- '&:active': {
- cursor: 'grabbing',
- },
},
disabled: {
color: 'gray',
+ cursor: 'default',
},
primary: {
color: theme.palette.primary.main,
diff --git a/packages/core/src/components/ProgressBars/GaugeCard.stories.tsx b/packages/core/src/components/ProgressBars/GaugeCard.stories.tsx
index 982999f8d6..5d54e5eda6 100644
--- a/packages/core/src/components/ProgressBars/GaugeCard.stories.tsx
+++ b/packages/core/src/components/ProgressBars/GaugeCard.stories.tsx
@@ -45,6 +45,9 @@ export const Default = () => (
+
+
+
);
@@ -67,6 +70,14 @@ export const Subhead = () => (
progress={0.89}
/>
+
+
+
);
@@ -81,5 +92,8 @@ export const LinkInFooter = () => (
+
+
+
);
diff --git a/packages/core/src/components/ProgressBars/GaugeCard.tsx b/packages/core/src/components/ProgressBars/GaugeCard.tsx
index 2154619b01..8dcec129c5 100644
--- a/packages/core/src/components/ProgressBars/GaugeCard.tsx
+++ b/packages/core/src/components/ProgressBars/GaugeCard.tsx
@@ -26,6 +26,7 @@ type Props = {
variant?: InfoCardVariants;
/** Progress in % specified as decimal, e.g. "0.23" */
progress: number;
+ inverse?: boolean;
deepLink?: BottomLinkProps;
};
@@ -38,7 +39,7 @@ const useStyles = makeStyles({
export const GaugeCard = (props: Props) => {
const classes = useStyles(props);
- const { title, subheader, progress, deepLink, variant } = props;
+ const { title, subheader, progress, inverse, deepLink, variant } = props;
return (