Merge branch 'master' into anderoo/cuatom-columns-on-deployments-plugin
This commit is contained in:
@@ -1,15 +0,0 @@
|
||||
---
|
||||
'@backstage/config-loader': minor
|
||||
---
|
||||
|
||||
Fix bug where `$${...}` was not being escaped to `${...}`
|
||||
|
||||
Add support for environment variable substitution in `$include`, `$file` and
|
||||
`$env` transform values.
|
||||
|
||||
- This change allows for including dynamic paths, such as environment specific
|
||||
secrets by using the same environment variable substitution (`${..}`) already
|
||||
supported outside of the various include transforms.
|
||||
- If you are currently using the syntax `${...}` in your include transform values,
|
||||
you will need to escape the substitution by using `$${...}` instead to maintain
|
||||
the same behavior.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-auth-backend': patch
|
||||
---
|
||||
|
||||
When using OAuth2 authentication the name is now taken from the name property of the JWT instead of the email property
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
'@backstage/core-api': patch
|
||||
'@backstage/core': patch
|
||||
---
|
||||
|
||||
Improved error messaging for routable extension errors, making it easier to identify the component and mount point that caused the error.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/catalog-model': patch
|
||||
---
|
||||
|
||||
Added `stringifyEntityRef`, which always creates a string representation of an entity reference. Also deprecated `serializeEntityRef`, as `stringifyEntityRef` should be used instead.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/core': patch
|
||||
---
|
||||
|
||||
Exported SignInProviderConfig to strongly type SignInPage providers
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
'@backstage/core-api': patch
|
||||
'@backstage/core': patch
|
||||
---
|
||||
|
||||
Fixed a bug with `useRouteRef` where navigating from routes beneath a mount point would often fail.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/test-utils': patch
|
||||
---
|
||||
|
||||
Remove unnecessary wrapping of elements rendered by `wrapInTestApp` and `renderInTestApp`, which was breaking mount discovery.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-org': patch
|
||||
---
|
||||
|
||||
Optimize data fetched for the `OwnershipCard`.
|
||||
@@ -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
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-github-deployments': patch
|
||||
---
|
||||
|
||||
Add a button to reload the GitHub Deployments card
|
||||
@@ -9,9 +9,11 @@ Blackbox
|
||||
Chai
|
||||
Changesets
|
||||
Chanwit
|
||||
Cloudformation
|
||||
Codecov
|
||||
Codehilite
|
||||
Config
|
||||
Debounce
|
||||
Discoverability
|
||||
Dockerfile
|
||||
Dockerize
|
||||
@@ -110,7 +112,9 @@ cookiecutter
|
||||
css
|
||||
dataflow
|
||||
deadnaming
|
||||
debounce
|
||||
declaratively
|
||||
deps
|
||||
destructured
|
||||
dev
|
||||
devops
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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. |
|
||||
|
||||
+58
-110
@@ -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,32 +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: ${GCS_PRIVATE_KEY}
|
||||
|
||||
catalog:
|
||||
rules:
|
||||
@@ -168,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:
|
||||
@@ -198,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')
|
||||
|
||||
@@ -251,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:
|
||||
@@ -282,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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 <div />;
|
||||
} catch (err) {
|
||||
return <div>{err.message}</div>;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
- 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<AuthResponse<any>> => {
|
||||
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.
|
||||
|
||||
<!-- links -->
|
||||
|
||||
[monorepo-app-setup-with-auth-ms]: https://backstage.io/docs/tutorials/quickstart-app-auth#the-auth-configuration
|
||||
@@ -0,0 +1,195 @@
|
||||
# Deploying Backstage on AWS using ECR and EKS
|
||||
|
||||
Backstage documentation shows how to build a [Docker
|
||||
image](https://backstage.io/docs/getting-started/deployment-docker); this
|
||||
tutorial shows how to deploy that Docker image to AWS using Elastic Container
|
||||
Registry (ECR) and Elastic Kubernetes Service (EKS). Amazon also supports
|
||||
deployments with Helm, covered in the [Helm
|
||||
Kubernetes](../kubernetes/basic_kubernetes_example_with_helm) example.
|
||||
|
||||
The basic workflow for this method is to build a Backstage Docker image, upload
|
||||
the new version to a container registry, and update a Kubernetes deployment with
|
||||
the new image.
|
||||
|
||||
## Create a container registry
|
||||
|
||||
To create an Elastic Container Registry on AWS, go to the [AWS ECR
|
||||
console](https://console.aws.amazon.com/ecr/repositories).
|
||||
|
||||
Click `Create repository` and give the repository a name, like `backstage`.
|
||||
|
||||
## Add an ECR IAM user
|
||||
|
||||
To push to this new ECR repository, there must be an IAM user with
|
||||
`AmazonEC2ContainerRegistry` permission.
|
||||
|
||||
Go to [AWS IAM console](https://console.aws.amazon.com/iam/home) and select
|
||||
`Users`.
|
||||
|
||||
1. Click `Add User`
|
||||
2. Set the username to something like `ecr-publisher` and select `Programmatic access` only.
|
||||
3. For `Permissions`, you can either create a new group or attach policies
|
||||
directly to the user with `AmazonEC2ContainerRegistryFullAccess`.
|
||||
4. Finish creating the user; an access key ID and secret access key will be
|
||||
generated.
|
||||
|
||||
## Publish a Backstage build
|
||||
|
||||
Follow the [Docker
|
||||
image](https://backstage.io/docs/getting-started/deployment-docker)
|
||||
documentation to build a new Backstage Docker image:
|
||||
|
||||
```shell
|
||||
$ yarn build
|
||||
$ docker image build . -f packages/backend/Dockerfile --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:
|
||||
|
||||
```shell
|
||||
$ aws configure
|
||||
AWS Access Key ID: <access key ID>
|
||||
AWS Secret Access Key: <secret access key>
|
||||
Default region name: <region for your ECR repository>
|
||||
```
|
||||
|
||||
Now you can use the AWS CLI to push the built image to the ECR repository. It's
|
||||
a good practice to use a specific version tag as well as `latest` when pushing;
|
||||
more about [Semver tagging
|
||||
here](https://medium.com/@mccode/using-semantic-versioning-for-docker-image-tags-dfde8be06699).
|
||||
|
||||
Go to the [AWS ECR console](https://console.aws.amazon.com/ecr/repositories) and
|
||||
click on your repository, then `View push commands`. This will show the
|
||||
repository URL to push and some sample commands.
|
||||
|
||||
```shell
|
||||
# Copy from push command window
|
||||
$ aws ecr get-login-password --region <region> | docker login --username AWS --password-stdin <repo_url>
|
||||
Login Succeeded
|
||||
|
||||
$ docker tag backstage:latest <repo_url>/backstage:latest
|
||||
$ docker push <repo_url>/backstage:latest
|
||||
|
||||
$ docker tag backstage:latest <repo_url>/backstage:1.0.0
|
||||
$ docker push <repo_url>/backstage:1.0.0
|
||||
```
|
||||
|
||||
These will take a few minutes to upload the Docker images, then you can see
|
||||
the image with `latest, 1.0.0` tags in the ECR repository. Now you can create a
|
||||
Kubernetes deployment based on this image.
|
||||
|
||||
## Deploy to an EKS cluster
|
||||
|
||||
Creating an Elastic Kubernetes Service (EKS) cluster is beyond the scope of this
|
||||
document, but it can be as easy as `eksctl create cluster` documented in the
|
||||
[AWS EKS getting started
|
||||
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`:
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: backstage-backend
|
||||
labels:
|
||||
app: backstage-backend
|
||||
namespace: default
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: backstage-backend
|
||||
strategy:
|
||||
rollingUpdate:
|
||||
maxSurge: 25%
|
||||
maxUnavailable: 25%
|
||||
type: RollingUpdate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: backstage-backend
|
||||
spec:
|
||||
containers:
|
||||
- image: <repo_url>/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:
|
||||
|
||||
```shell
|
||||
$ kubectl apply -f deployment.yaml
|
||||
$ kubectl apply -f service.yaml
|
||||
```
|
||||
|
||||
Now you can see your Backstage workload running from the [EKS
|
||||
console](https://console.aws.amazon.com/eks/home).
|
||||
|
||||
## Further steps
|
||||
|
||||
### 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
|
||||
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
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
namespace: default
|
||||
name: backstage-ingress
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: alb
|
||||
alb.ingress.kubernetes.io/scheme: internet-facing
|
||||
alb.ingress.kubernetes.io/target-type: ip
|
||||
spec:
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: backstage-backend
|
||||
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`.
|
||||
@@ -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
|
||||
|
||||
@@ -46,29 +46,5 @@ The Auth0 provider is a structure with three configuration keys:
|
||||
## Adding the provider to the Backstage frontend
|
||||
|
||||
To add the provider to the frontend, add the `auth0AuthApi` reference and
|
||||
`SignInPage` component to `createApp` in `packages/app/src/App.tsx`:
|
||||
|
||||
```diff
|
||||
+ import { auth0AuthApiRef, SignInConfig, SignInPage } from '@backstage/core';
|
||||
|
||||
+ const auth0Provider: SignInConfig = {
|
||||
+ id: 'auth0-auth-provider',
|
||||
+ title: 'Auth0',
|
||||
+ message: 'Sign in using Auth0',
|
||||
+ apiRef: auth0AuthApiRef,
|
||||
+};
|
||||
+
|
||||
const app = createApp({
|
||||
apis,
|
||||
plugins: Object.values(plugins),
|
||||
+ components: {
|
||||
+ SignInPage: props => (
|
||||
+ <SignInPage
|
||||
+ {...props}
|
||||
+ auto
|
||||
+ provider={auth0Provider}
|
||||
+ />
|
||||
+ ),
|
||||
+ },
|
||||
bindRoutes({ bind }) {
|
||||
```
|
||||
`SignInPage` component as shown in
|
||||
[Adding the provider to the sign-in page](../index.md#adding-the-provider-to-the-sign-in-page).
|
||||
|
||||
@@ -49,29 +49,5 @@ The GitHub provider is a structure with three configuration keys:
|
||||
## Adding the provider to the Backstage frontend
|
||||
|
||||
To add the provider to the frontend, add the `githubAuthApi` reference and
|
||||
`SignInPage` component to `createApp` in `packages/app/src/App.tsx`:
|
||||
|
||||
```diff
|
||||
+ import { githubAuthApiRef, SignInConfig, SignInPage } from '@backstage/core';
|
||||
|
||||
+ const githubProvider: SignInConfig = {
|
||||
+ id: 'github-auth-provider',
|
||||
+ title: 'GitHub',
|
||||
+ message: 'Sign in using GitHub',
|
||||
+ apiRef: githubAuthApiRef,
|
||||
+};
|
||||
+
|
||||
const app = createApp({
|
||||
apis,
|
||||
plugins: Object.values(plugins),
|
||||
+ components: {
|
||||
+ SignInPage: props => (
|
||||
+ <SignInPage
|
||||
+ {...props}
|
||||
+ auto
|
||||
+ provider={githubProvider}
|
||||
+ />
|
||||
+ ),
|
||||
+ },
|
||||
bindRoutes({ bind }) {
|
||||
```
|
||||
`SignInPage` component as shown in
|
||||
[Adding the provider to the sign-in page](../index.md#adding-the-provider-to-the-sign-in-page).
|
||||
|
||||
@@ -48,29 +48,5 @@ The GitLab provider is a structure with three configuration keys:
|
||||
## Adding the provider to the Backstage frontend
|
||||
|
||||
To add the provider to the frontend, add the `gitlabAuthApi` reference and
|
||||
`SignInPage` component to `createApp` in `packages/app/src/App.tsx`:
|
||||
|
||||
```diff
|
||||
+ import { gitlabAuthApiRef, SignInConfig, SignInPage } from '@backstage/core';
|
||||
|
||||
+ const gitlabProvider: SignInConfig = {
|
||||
+ id: 'gitlab-auth-provider',
|
||||
+ title: 'GitLab',
|
||||
+ message: 'Sign in using GitLab',
|
||||
+ apiRef: gitlabAuthApiRef,
|
||||
+};
|
||||
+
|
||||
const app = createApp({
|
||||
apis,
|
||||
plugins: Object.values(plugins),
|
||||
+ components: {
|
||||
+ SignInPage: props => (
|
||||
+ <SignInPage
|
||||
+ {...props}
|
||||
+ auto
|
||||
+ provider={gitlabProvider}
|
||||
+ />
|
||||
+ ),
|
||||
+ },
|
||||
bindRoutes({ bind }) {
|
||||
```
|
||||
`SignInPage` component as shown in
|
||||
[Adding the provider to the sign-in page](../index.md#adding-the-provider-to-the-sign-in-page).
|
||||
|
||||
@@ -53,29 +53,5 @@ The Google provider is a structure with two configuration keys:
|
||||
## Adding the provider to the Backstage frontend
|
||||
|
||||
To add the provider to the frontend, add the `googleAuthApi` reference and
|
||||
`SignInPage` component to `createApp` in `packages/app/src/App.tsx`:
|
||||
|
||||
```diff
|
||||
+ import { googleAuthApiRef, SignInConfig, SignInPage } from '@backstage/core';
|
||||
|
||||
+ const googleProvider: SignInConfig = {
|
||||
+ id: 'google-auth-provider',
|
||||
+ title: 'Google',
|
||||
+ message: 'Sign in using Google',
|
||||
+ apiRef: googleAuthApiRef,
|
||||
+};
|
||||
+
|
||||
const app = createApp({
|
||||
apis,
|
||||
plugins: Object.values(plugins),
|
||||
+ components: {
|
||||
+ SignInPage: props => (
|
||||
+ <SignInPage
|
||||
+ {...props}
|
||||
+ auto
|
||||
+ provider={googleProvider}
|
||||
+ />
|
||||
+ ),
|
||||
+ },
|
||||
bindRoutes({ bind }) {
|
||||
```
|
||||
`SignInPage` component as shown in
|
||||
[Adding the provider to the sign-in page](../index.md#adding-the-provider-to-the-sign-in-page).
|
||||
|
||||
+96
-75
@@ -1,99 +1,120 @@
|
||||
---
|
||||
id: index
|
||||
title: User Authentication and Authorization in Backstage
|
||||
description: Documentation on User Authentication and Authorization in Backstage
|
||||
title: Adding Authentication
|
||||
description: How to add authentication to a Backstage application
|
||||
---
|
||||
|
||||
## Summary
|
||||
Authentication in Backstage identifies the user, and provides a way for plugins
|
||||
to make requests on behalf of a user to third-party services. Backstage can have
|
||||
zero (guest access), one, or many authentication providers. The default
|
||||
`@backstage/create-app` template uses guest access for easy startup.
|
||||
|
||||
The purpose of the Auth APIs in Backstage is to identify the user, and to
|
||||
provide a way for plugins to request access to 3rd party services on behalf of
|
||||
the user (OAuth). This documentation focuses on the implementation of that
|
||||
solution and how to extend it. For documentation on how to consume the Auth APIs
|
||||
in a plugin, see [TODO](#TODO).
|
||||
See [Using authentication and identity](using-auth.md) for tips on using
|
||||
Backstage identity information in your app or plugins.
|
||||
|
||||
### Accessing Third Party Services
|
||||
## Adding an authentication provider
|
||||
|
||||
The main pattern for talking to third party services in Backstage is
|
||||
user-to-server requests, where short-lived OAuth Access Tokens are requested by
|
||||
plugins to authenticate calls to external services. These calls can be made
|
||||
either directly to the services or through a backend plugin or service.
|
||||
Backstage comes with many common authentication providers in the core library:
|
||||
|
||||
By relying on user-to-server calls we keep the coupling between the frontend and
|
||||
backend low, and provide a much lower barrier for plugins to make use of third
|
||||
party services. This is in comparison to for example a session-based system,
|
||||
where access tokens are stored server-side. Such a solution would require a much
|
||||
deeper coupling between the auth backend plugin, its session storage, and other
|
||||
backend plugins or separate services. A goal of Backstage is to make it as easy
|
||||
as possible to create new plugins, and an auth solution based on user-to-server
|
||||
OAuth helps in that regard.
|
||||
- [Auth0](auth0/provider.md)
|
||||
- [Azure](microsoft/provider.md)
|
||||
- [GitHub](github/provider.md)
|
||||
- [GitLab](gitlab/provider.md)
|
||||
- [Google](google/provider.md)
|
||||
- [Okta](okta/provider.md)
|
||||
- [OneLogin](onelogin/provider.md)
|
||||
|
||||
The method with which frontend plugins request access to third party services is
|
||||
through [Utility APIs](../api/utility-apis.md) for each service provider. For a
|
||||
full list of providers, see the
|
||||
[Utility API References](../reference/utility-apis/README.md).
|
||||
These built-in providers handle the authentication flow for a particular service
|
||||
including required scopes, callbacks, etc. These providers are each added to a
|
||||
Backstage app in a similar way.
|
||||
|
||||
### Identity - WIP
|
||||
### Adding provider configuration
|
||||
|
||||
> NOTE: Identity management and the `SignInPage` in Backstage is NOT a method
|
||||
> for blocking access for unauthorized users, that either requires additional
|
||||
> backend implementation or a separate service like Google's Identity-Aware
|
||||
> Proxy. The identity system only serves to provide a personalized experience
|
||||
> and access to a Backstage Identity Token, which can be passed to backend
|
||||
> plugins.
|
||||
Each built-in provider has a configuration block under the `auth` section of
|
||||
`app-config.yaml`. For example, the GitHub provider:
|
||||
|
||||
Identity management is still work in progress, but there are already a couple of
|
||||
pieces in place that can be used.
|
||||
```yaml
|
||||
auth:
|
||||
environment: development
|
||||
providers:
|
||||
github:
|
||||
development:
|
||||
clientId: ${AUTH_GITHUB_CLIENT_ID}
|
||||
clientSecret: ${AUTH_GITHUB_CLIENT_SECRET}
|
||||
```
|
||||
|
||||
#### Identity for Plugin Developers
|
||||
See the documentation for a particular provider to see what configuration is
|
||||
needed.
|
||||
|
||||
As a plugin developer, there are two main touchpoints for identities: the
|
||||
`IdentityApi` exported by `@backstage/core` via the `identityApiRef`, and a not
|
||||
yet existing middleware exported by `@backstage/backend-common`.
|
||||
The `providers` key may have several authentication providers, if multiple
|
||||
authentication methods are supported. Each provider may also have configuration
|
||||
for different authentication environments (development, production, etc). This
|
||||
allows a single auth backend to serve multiple environments, such as running a
|
||||
local frontend against a deployed backend. The provider configuration matching
|
||||
the local `auth.environment` setting will be selected.
|
||||
|
||||
The `IdentityApi` gives access to the signed-in user's identity in the frontend.
|
||||
It provides access to the user's ID, lightweight profile information, and an ID
|
||||
token used to make authenticated calls within Backstage.
|
||||
### Adding the provider to the sign-in page
|
||||
|
||||
The middleware that will be provided by `@backstage/backend-common` allows
|
||||
verification of Backstage ID tokens, and optionally loading additional
|
||||
information about the user. The progress is tracked in
|
||||
https://github.com/backstage/backstage/issues/1435.
|
||||
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
|
||||
`SignInProviderConfig` definitions.
|
||||
|
||||
#### Identity for App Developers
|
||||
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
|
||||
built-in providers:
|
||||
|
||||
If you're setting up your own Backstage app, or want to add a new identity
|
||||
provider, there are three touchpoints: the frontend auth APIs in
|
||||
`@backstage/core-api`, the backend auth providers in `auth-backend`, and the
|
||||
`SignInPage` component configured in the Backstage app via `createApp`.
|
||||
```diff
|
||||
# packages/app/src/App.tsx
|
||||
+ import { githubAuthApiRef, SignInProviderConfig, SignInPage } from '@backstage/core';
|
||||
|
||||
The frontend APIs and backend providers are tightly coupled together for each
|
||||
auth provider, and together they implement an e2e auth flow. Only some auth
|
||||
providers also act as identity providers though. For example, at the moment of
|
||||
writing, the Google Auth provider is able to act as a Backstage identity
|
||||
provider, but the GitHub one can not. For an auth provider to also act as an
|
||||
identity provider, it needs to implement the `BackstageIdentityApi` in the
|
||||
frontend, and in the backend it needs to return a `BackstageIdentity` structure.
|
||||
+ const githubProvider: SignInProviderConfig = {
|
||||
+ id: 'github-auth-provider',
|
||||
+ title: 'GitHub',
|
||||
+ message: 'Sign in using GitHub',
|
||||
+ apiRef: githubAuthApiRef,
|
||||
+};
|
||||
+
|
||||
const app = createApp({
|
||||
apis,
|
||||
plugins: Object.values(plugins),
|
||||
+ components: {
|
||||
+ SignInPage: props => (
|
||||
+ <SignInPage
|
||||
+ {...props}
|
||||
+ auto
|
||||
+ provider={githubProvider}
|
||||
+ />
|
||||
+ ),
|
||||
+ },
|
||||
bindRoutes({ bind }) {
|
||||
```
|
||||
|
||||
It is up to each provider to implement the mapping between a provider identity
|
||||
and the corresponding Backstage identity. That is currently still work in
|
||||
progress, and as a stop-gap for example the Google provider returns the local
|
||||
part of the user's email as the user ID.
|
||||
To also allow unauthenticated guest access, use the `providers` prop for
|
||||
`SignInPage`:
|
||||
|
||||
The final piece of the puzzle is the `SignInPage` component that can be
|
||||
configured as part of the app. Without a sign-in page, Backstage will fall back
|
||||
to a `guest` identity for all users, without any ID token. To enable sign-in, a
|
||||
`SignInPage` needs to be configured, which in turn has to supply a user to the
|
||||
app. The `@backstage/core` package provides a basic sign-in page that allows
|
||||
both the user and the app developer to choose between a couple of different
|
||||
sign-in methods, or to designate a single provider that may also be logged in to
|
||||
automatically.
|
||||
```diff
|
||||
const app = createApp({
|
||||
apis,
|
||||
plugins: Object.values(plugins),
|
||||
+ components: {
|
||||
+ SignInPage: props => (
|
||||
+ <SignInPage
|
||||
+ {...props}
|
||||
+ providers={['guest', githubProvider]}
|
||||
+ />
|
||||
+ ),
|
||||
+ },
|
||||
bindRoutes({ bind }) {
|
||||
```
|
||||
|
||||
## Further Reading
|
||||
## Adding a custom authentication provider
|
||||
|
||||
More details are provided in dedicated sections of the documentation.
|
||||
There are generic authentication providers for OAuth2 and SAML. These can reduce
|
||||
the amount of code needed to implement a custom authentication provider that
|
||||
adheres to these standards.
|
||||
|
||||
- [OAuth](./oauth.md): Description of the generic OAuth flow implemented by the
|
||||
[auth-backend](https://github.com/backstage/backstage/tree/master/plugins/auth-backend).
|
||||
- [Glossary](./glossary.md): Glossary of some common terms related to the auth
|
||||
flows.
|
||||
Backstage uses [Passport](http://www.passportjs.org/) under the hood, which has
|
||||
a wide library of authentication strategies for different providers. See
|
||||
[Add authentication provider](add-auth-provider.md) for details on adding a new
|
||||
Passport-supported authentication method.
|
||||
|
||||
@@ -49,29 +49,5 @@ The Microsoft provider is a structure with three configuration keys:
|
||||
## Adding the provider to the Backstage frontend
|
||||
|
||||
To add the provider to the frontend, add the `microsoftAuthApi` reference and
|
||||
`SignInPage` component to `createApp` in `packages/app/src/App.tsx`:
|
||||
|
||||
```diff
|
||||
+ import { microsoftAuthApiRef, SignInConfig, SignInPage } from '@backstage/core';
|
||||
|
||||
+ const microsoftProvider: SignInConfig = {
|
||||
+ id: 'microsoft-auth-provider',
|
||||
+ title: 'Microsoft Azure',
|
||||
+ message: 'Sign in using Azure',
|
||||
+ apiRef: microsoftAuthApiRef,
|
||||
+};
|
||||
+
|
||||
const app = createApp({
|
||||
apis,
|
||||
plugins: Object.values(plugins),
|
||||
+ components: {
|
||||
+ SignInPage: props => (
|
||||
+ <SignInPage
|
||||
+ {...props}
|
||||
+ auto
|
||||
+ provider={microsoftProvider}
|
||||
+ />
|
||||
+ ),
|
||||
+ },
|
||||
bindRoutes({ bind }) {
|
||||
```
|
||||
`SignInPage` component as shown in
|
||||
[Adding the provider to the sign-in page](../index.md#adding-the-provider-to-the-sign-in-page).
|
||||
|
||||
@@ -62,29 +62,5 @@ The values referenced are found on the Application page on your Okta site.
|
||||
## Adding the provider to the Backstage frontend
|
||||
|
||||
To add the provider to the frontend, add the `oktaAuthApi` reference and
|
||||
`SignInPage` component to `createApp` in `packages/app/src/App.tsx`:
|
||||
|
||||
```diff
|
||||
+ import { oktaAuthApiRef, SignInConfig, SignInPage } from '@backstage/core';
|
||||
|
||||
+ const oktaProvider: SignInConfig = {
|
||||
+ id: 'okta-auth-provider',
|
||||
+ title: 'Okta',
|
||||
+ message: 'Sign in using Okta',
|
||||
+ apiRef: oktaAuthApiRef,
|
||||
+};
|
||||
+
|
||||
const app = createApp({
|
||||
apis,
|
||||
plugins: Object.values(plugins),
|
||||
+ components: {
|
||||
+ SignInPage: props => (
|
||||
+ <SignInPage
|
||||
+ {...props}
|
||||
+ auto
|
||||
+ provider={oktaProvider}
|
||||
+ />
|
||||
+ ),
|
||||
+ },
|
||||
bindRoutes({ bind }) {
|
||||
```
|
||||
`SignInPage` component as shown in
|
||||
[Adding the provider to the sign-in page](../index.md#adding-the-provider-to-the-sign-in-page).
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
---
|
||||
id: provider
|
||||
title: OneLogin Authentication Provider
|
||||
sidebar_label: OneLogin
|
||||
description: Adding OneLogin OIDC as an authentication provider in Backstage
|
||||
---
|
||||
|
||||
The Backstage `core-api` package comes with a OneLogin authentication provider
|
||||
that can authenticate users using OpenID Connect.
|
||||
|
||||
## Create an Application on OneLogin
|
||||
|
||||
To support OneLogin authentication, you must create an Application:
|
||||
|
||||
1. From the OneLogin Admin portal, choose Applications
|
||||
2. Click `Add App` and select `OpenID Connect`
|
||||
- Display Name: Backstage (or your custom app name)
|
||||
3. Click Save
|
||||
4. Go to the Configuration tab for the Application and set:
|
||||
- `Login Url`: `http://localhost:3000`
|
||||
- `Redirect URIs`: `http://localhost:7000/api/auth/onelogin/handler/frame`
|
||||
5. Click Save
|
||||
6. Go to the SSO tab for the Application and set:
|
||||
- `Token Endpoint` > `Authentication Method`: `POST`
|
||||
7. Click Save
|
||||
|
||||
## Configuration
|
||||
|
||||
The provider configuration can then be added to your `app-config.yaml` under the
|
||||
root `auth` configuration:
|
||||
|
||||
```yaml
|
||||
auth:
|
||||
environment: development
|
||||
providers:
|
||||
onelogin:
|
||||
development:
|
||||
clientId: ${AUTH_ONELOGIN_CLIENT_ID}
|
||||
clientSecret: ${AUTH_ONELOGIN_CLIENT_SECRET}
|
||||
issuer: https://<company>.onelogin.com/oidc/2
|
||||
```
|
||||
|
||||
The OneLogin provider is a structure with three configuration keys; **these are
|
||||
found on the SSO tab** for the OneLogin Application:
|
||||
|
||||
- `clientId`: The client ID
|
||||
- `clientSecret`: The client secret
|
||||
- `issuer`: The issuer URL
|
||||
|
||||
## Adding the provider to the Backstage frontend
|
||||
|
||||
To add the provider to the frontend, add the `oneloginAuthApi` reference and
|
||||
`SignInPage` component as shown in
|
||||
[Adding the provider to the sign-in page](../index.md#adding-the-provider-to-the-sign-in-page).
|
||||
@@ -0,0 +1,96 @@
|
||||
---
|
||||
id: using-auth
|
||||
title: Using authentication and identity
|
||||
description: How to use authentication and identity in Backstage
|
||||
---
|
||||
|
||||
The Auth APIs in Backstage identify the user, and provide a way for plugins to
|
||||
request access to 3rd party services on behalf of the user (OAuth). This
|
||||
documentation focuses on the implementation of that solution and how to extend
|
||||
it.
|
||||
|
||||
### Accessing Third Party Services
|
||||
|
||||
The main pattern for talking to third party services in Backstage is
|
||||
user-to-server requests, where short-lived OAuth Access Tokens are requested by
|
||||
plugins to authenticate calls to external services. These calls can be made
|
||||
either directly to the services or through a backend plugin or service.
|
||||
|
||||
By relying on user-to-server calls we keep the coupling between the frontend and
|
||||
backend low, and provide a much lower barrier for plugins to make use of third
|
||||
party services. This is in comparison to for example a session-based system,
|
||||
where access tokens are stored server-side. Such a solution would require a much
|
||||
deeper coupling between the auth backend plugin, its session storage, and other
|
||||
backend plugins or separate services. A goal of Backstage is to make it as easy
|
||||
as possible to create new plugins, and an auth solution based on user-to-server
|
||||
OAuth helps in that regard.
|
||||
|
||||
The method with which frontend plugins request access to third party services is
|
||||
through [Utility APIs](../api/utility-apis.md) for each service provider. For a
|
||||
full list of providers, see the
|
||||
[Utility API References](../reference/utility-apis/README.md).
|
||||
|
||||
### Identity - WIP
|
||||
|
||||
> NOTE: Identity management and the `SignInPage` in Backstage is NOT a method
|
||||
> for blocking access for unauthorized users, that either requires additional
|
||||
> backend implementation or a separate service like Google's Identity-Aware
|
||||
> Proxy. The identity system only serves to provide a personalized experience
|
||||
> and access to a Backstage Identity Token, which can be passed to backend
|
||||
> plugins.
|
||||
|
||||
Identity management is still work in progress, but there are already a couple of
|
||||
pieces in place that can be used.
|
||||
|
||||
#### Identity for Plugin Developers
|
||||
|
||||
As a plugin developer, there are two main touchpoints for identities: the
|
||||
`IdentityApi` exported by `@backstage/core` via the `identityApiRef`, and a not
|
||||
yet existing middleware exported by `@backstage/backend-common`.
|
||||
|
||||
The `IdentityApi` gives access to the signed-in user's identity in the frontend.
|
||||
It provides access to the user's ID, lightweight profile information, and an ID
|
||||
token used to make authenticated calls within Backstage.
|
||||
|
||||
The middleware that will be provided by `@backstage/backend-common` allows
|
||||
verification of Backstage ID tokens, and optionally loading additional
|
||||
information about the user. The progress is tracked in
|
||||
https://github.com/backstage/backstage/issues/1435.
|
||||
|
||||
#### Identity for App Developers
|
||||
|
||||
If you're setting up your own Backstage app, or want to add a new identity
|
||||
provider, there are three touchpoints: the frontend auth APIs in
|
||||
`@backstage/core-api`, the backend auth providers in `auth-backend`, and the
|
||||
`SignInPage` component configured in the Backstage app via `createApp`.
|
||||
|
||||
The frontend APIs and backend providers are tightly coupled together for each
|
||||
auth provider, and together they implement an e2e auth flow. Only some auth
|
||||
providers also act as identity providers though. For example, at the moment of
|
||||
writing, the Google Auth provider is able to act as a Backstage identity
|
||||
provider, but the GitHub one can not. For an auth provider to also act as an
|
||||
identity provider, it needs to implement the `BackstageIdentityApi` in the
|
||||
frontend, and in the backend it needs to return a `BackstageIdentity` structure.
|
||||
|
||||
It is up to each provider to implement the mapping between a provider identity
|
||||
and the corresponding Backstage identity. That is currently still work in
|
||||
progress, and as a stop-gap for example the Google provider returns the local
|
||||
part of the user's email as the user ID.
|
||||
|
||||
The final piece of the puzzle is the `SignInPage` component that can be
|
||||
configured as part of the app. Without a sign-in page, Backstage will fall back
|
||||
to a `guest` identity for all users, without any ID token. To enable sign-in, a
|
||||
`SignInPage` needs to be configured, which in turn has to supply a user to the
|
||||
app. The `@backstage/core` package provides a basic sign-in page that allows
|
||||
both the user and the app developer to choose between a couple of different
|
||||
sign-in methods, or to designate a single provider that may also be logged in to
|
||||
automatically.
|
||||
|
||||
## Further Reading
|
||||
|
||||
More details are provided in dedicated sections of the documentation.
|
||||
|
||||
- [OAuth](./oauth.md): Description of the generic OAuth flow implemented by the
|
||||
[auth-backend](https://github.com/backstage/backstage/tree/master/plugins/auth-backend).
|
||||
- [Glossary](./glossary.md): Glossary of some common terms related to the auth
|
||||
flows.
|
||||
+22
-2
@@ -54,6 +54,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
|
||||
```
|
||||
|
||||
@@ -190,10 +192,13 @@ output of `backstage-cli backend:bundle` into an image:
|
||||
FROM node:14-buster-slim
|
||||
WORKDIR /app
|
||||
|
||||
ADD yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./
|
||||
COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./
|
||||
RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz
|
||||
|
||||
RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)"
|
||||
|
||||
ADD packages/backend/dist/bundle.tar.gz app-config.yaml ./
|
||||
COPY packages/backend/dist/bundle.tar.gz app-config.yaml ./
|
||||
RUN tar xzf bundle.tar.gz && rm bundle.tar.gz
|
||||
|
||||
CMD ["node", "packages/backend"]
|
||||
```
|
||||
@@ -606,3 +611,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>
|
||||
```
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 <your-app>
|
||||
```
|
||||
|
||||
## 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 <your-app>
|
||||
$ heroku container:release web -a <your-app>
|
||||
```
|
||||
|
||||
Now you should have Backstage up and running! 🎉
|
||||
@@ -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.
|
||||
@@ -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 <none> 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, <pod> -c <container>
|
||||
$ 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.
|
||||
@@ -25,8 +25,7 @@ kubernetes:
|
||||
- url: http://127.0.0.1:9999
|
||||
name: minikube
|
||||
authProvider: 'serviceAccount'
|
||||
serviceAccountToken:
|
||||
$env: K8S_MINIKUBE_TOKEN
|
||||
serviceAccountToken: ${K8S_MINIKUBE_TOKEN}
|
||||
- url: http://127.0.0.2:9999
|
||||
name: aws-cluster-1
|
||||
authProvider: 'aws'
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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<Router> {
|
||||
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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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=<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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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!**
|
||||
|
||||
@@ -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),
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
@@ -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 <EntityPageLayout>
|
||||
<EntityPageLayout.Content
|
||||
path="/ci-cd/*"
|
||||
title="CI/CD"
|
||||
element={<CircleCIRouter />}
|
||||
/>;
|
||||
...
|
||||
const cicdContent = (
|
||||
<EntitySwitch>
|
||||
...
|
||||
+ <EntitySwitch.Case if={isCircleCIAvailable}>
|
||||
+ <EntityCircleCIContent />
|
||||
+ </EntitySwitch.Case>;
|
||||
</EntitySwitch>
|
||||
);
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
@@ -106,6 +106,12 @@ The install process may also fail if no Python installation is available. Python
|
||||
is commonly available in most systems already, but if it isn't you can head for
|
||||
example [here](https://www.python.org/downloads/) to install it.
|
||||
|
||||
#### Could not execute command yarn install
|
||||
|
||||
Install yarn on your system with `npm install --global yarn` or for more details
|
||||
refer to the
|
||||
[prerequisites](https://backstage.io/docs/getting-started/running-backstage-locally#prerequisites)
|
||||
|
||||
## Run the app
|
||||
|
||||
When the installation is complete you can open the app folder and start the app.
|
||||
|
||||
@@ -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.
|
||||
@@ -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 <your-app>
|
||||
```
|
||||
|
||||
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 <your-app>
|
||||
$ heroku container:release web -a <your-app>
|
||||
```
|
||||
|
||||
With that, you should have Backstage up and running!
|
||||
@@ -2,8 +2,8 @@
|
||||
id: locations
|
||||
title: Azure DevOps Locations
|
||||
sidebar_label: Locations
|
||||
description:
|
||||
Integrating source code stored in Azure DevOps into the Backstage catalog
|
||||
# prettier-ignore
|
||||
description: Integrating source code stored in Azure DevOps into the Backstage catalog
|
||||
---
|
||||
|
||||
The Azure integration supports loading catalog entities from Azure DevOps.
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
id: discovery
|
||||
title: Bitbucket Discovery
|
||||
sidebar_label: Discovery
|
||||
description:
|
||||
Automatically discovering catalog entities from repositories in Bitbucket
|
||||
# prettier-ignore
|
||||
description: Automatically discovering catalog entities from repositories in Bitbucket
|
||||
---
|
||||
|
||||
The Bitbucket integration has a special discovery processor for discovering
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
id: locations
|
||||
title: Bitbucket Locations
|
||||
sidebar_label: Locations
|
||||
description:
|
||||
Integrating source code stored in Bitbucket into the Backstage catalog
|
||||
# prettier-ignore
|
||||
description: Integrating source code stored in Bitbucket into the Backstage catalog
|
||||
---
|
||||
|
||||
The Bitbucket integration supports loading catalog entities from bitbucket.org
|
||||
@@ -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://<host>/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.
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
id: discovery
|
||||
title: GitHub Discovery
|
||||
sidebar_label: Discovery
|
||||
description:
|
||||
Automatically discovering catalog entities from repositories in a GitHub
|
||||
organization
|
||||
# prettier-ignore
|
||||
description: Automatically discovering catalog entities from repositories in a GitHub organization
|
||||
---
|
||||
|
||||
The GitHub integration has a special discovery processor for discovering catalog
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
---
|
||||
id: locations
|
||||
sidebar_label: Locations
|
||||
title: Google Cloud Storage Locations
|
||||
# prettier-ignore
|
||||
description: Setting up an integration with Google Cloud Storage
|
||||
---
|
||||
|
||||
The Backstage catalog can import entities from a yaml file stored in a GCS
|
||||
(Google Cloud Storage) bucket. To enable the ingestion of said entities the
|
||||
`GoogleGcs` integration must be enabled first.
|
||||
|
||||
## Configuration
|
||||
|
||||
To configure the integration add the appropriate credentials to the Backstage
|
||||
backend. There are two main ways to do this: by explicitly setting a
|
||||
`clientEmail` and a `privateKey` or by letting the Google Storage SDK discover
|
||||
the credentials automatically.
|
||||
|
||||
### Explicit credentials
|
||||
|
||||
Explicit credentials can be set in the following format:
|
||||
|
||||
```yaml
|
||||
integrations:
|
||||
googleGcs:
|
||||
clientEmail: ${GCS_CLIENT_EMAIL}
|
||||
privateKey: ${GCS_PRIVATE_KEY}
|
||||
```
|
||||
|
||||
Then make sure the environment variables `GCS_CLIENT_EMAIL` and
|
||||
`GCS_PRIVATE_KEY` are set when you run Backstage.
|
||||
|
||||
### Automatic discovery of Google credentials
|
||||
|
||||
Since this integration uses the Google Storage SDK, you can also choose to not
|
||||
provide any explicit credentials and let the SDK discover them automatically.
|
||||
|
||||
One of these discovery methods is to provide an environment variable called
|
||||
`GOOGLE_APPLICATION_CREDENTIALS` and set it to the file path of your JSON
|
||||
service account key.
|
||||
|
||||
For more details and methods to provide credentials to the Google Storage SDK
|
||||
you can check [this documentation page][google gcs docs].
|
||||
|
||||
## Usage
|
||||
|
||||
To use this integration to import entities from a GCS bucket go to the Google
|
||||
console and browse the file you would like to import. Then copy the
|
||||
`Authenticated URL` and paste it into the text box in the `register component`
|
||||
form. This url should look like
|
||||
`https://storage.cloud.google.com/<bucket>/<path>/catalog-info.yaml`.
|
||||
|
||||
[google gcs docs]:
|
||||
https://cloud.google.com/docs/authentication/production#auth-cloud-implicit-nodejs
|
||||
@@ -2,9 +2,8 @@
|
||||
id: index
|
||||
title: Integrations
|
||||
sidebar_label: Overview
|
||||
description:
|
||||
Configuring Backstage to read or publish data with external providers using
|
||||
integrations
|
||||
# prettier-ignore
|
||||
description: Configuring Backstage to read or publish data with external providers using integrations
|
||||
---
|
||||
|
||||
Integrations allow Backstage to read or publish data using external providers
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
---
|
||||
id: switching-sqlite-postgres
|
||||
title: Switching Backstage from SQLite to PostgreSQL
|
||||
description:
|
||||
How to get ready for deploying Backstage to production with PostgreSQL
|
||||
# prettier-ignore
|
||||
description: How to get ready for deploying Backstage to production with PostgreSQL
|
||||
---
|
||||
|
||||
The default `@backstage/create-app` database is SQLite, an in-memory database
|
||||
@@ -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.
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -292,7 +292,7 @@ class Index extends React.Component {
|
||||
Build your own software templates
|
||||
</ActionBlock.Title>
|
||||
<ActionBlock.Link
|
||||
href={`https://github.com/backstage/backstage/blob/master/docs/features/software-templates/extending/index.md`}
|
||||
href={`https://github.com/backstage/backstage/blob/master/docs/features/software-templates/index.md`}
|
||||
>
|
||||
Contribute
|
||||
</ActionBlock.Link>
|
||||
|
||||
+17
-14
@@ -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"
|
||||
@@ -142,6 +132,11 @@
|
||||
"type": "subcategory",
|
||||
"label": "LDAP",
|
||||
"ids": ["integrations/ldap/org"]
|
||||
},
|
||||
{
|
||||
"type": "subcategory",
|
||||
"label": "Google GCS",
|
||||
"ids": ["integrations/google-cloud-storage/locations"]
|
||||
}
|
||||
],
|
||||
"Plugins": [
|
||||
@@ -186,7 +181,6 @@
|
||||
],
|
||||
"Auth and identity": [
|
||||
"auth/index",
|
||||
"auth/add-auth-provider",
|
||||
{
|
||||
"type": "subcategory",
|
||||
"label": "Included providers",
|
||||
@@ -196,13 +190,22 @@
|
||||
"auth/github/provider",
|
||||
"auth/gitlab/provider",
|
||||
"auth/google/provider",
|
||||
"auth/okta/provider"
|
||||
"auth/okta/provider",
|
||||
"auth/onelogin/provider"
|
||||
]
|
||||
},
|
||||
"auth/add-auth-provider",
|
||||
"auth/using-auth",
|
||||
"auth/auth-backend",
|
||||
"auth/oauth",
|
||||
"auth/glossary",
|
||||
"auth/auth-backend-classes"
|
||||
"auth/auth-backend-classes",
|
||||
"auth/glossary"
|
||||
],
|
||||
"Deployment": [
|
||||
"deployment/index",
|
||||
"deployment/k8s",
|
||||
"deployment/helm",
|
||||
"deployment/heroku"
|
||||
],
|
||||
"Designing for Backstage": [
|
||||
"dls/design",
|
||||
|
||||
+8
-8
@@ -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"
|
||||
|
||||
|
||||
+3
-1
@@ -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": {
|
||||
|
||||
@@ -1,5 +1,54 @@
|
||||
# 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
|
||||
|
||||
- Updated dependencies [d0b4ebf22]
|
||||
- Updated dependencies [1279a3325]
|
||||
- Updated dependencies [4a4681b1b]
|
||||
- Updated dependencies [97b60de98]
|
||||
- Updated dependencies [3f96a9d5a]
|
||||
- Updated dependencies [b051e770c]
|
||||
- Updated dependencies [f9c75f7a9]
|
||||
- Updated dependencies [98dd5da71]
|
||||
- Updated dependencies [97d53f686]
|
||||
- Updated dependencies [64d2ce700]
|
||||
- @backstage/plugin-badges@0.2.0
|
||||
- @backstage/core@0.7.4
|
||||
- @backstage/catalog-model@0.7.6
|
||||
- @backstage/plugin-scaffolder@0.8.2
|
||||
- @backstage/plugin-catalog-import@0.5.2
|
||||
- @backstage/plugin-catalog@0.5.3
|
||||
- @backstage/plugin-org@0.3.12
|
||||
- @backstage/plugin-github-deployments@0.1.2
|
||||
- @backstage/cli@0.6.7
|
||||
|
||||
## 0.2.21
|
||||
|
||||
### Patch Changes
|
||||
|
||||
+18
-18
@@ -1,17 +1,17 @@
|
||||
{
|
||||
"name": "example-app",
|
||||
"version": "0.2.21",
|
||||
"version": "0.2.24",
|
||||
"private": true,
|
||||
"bundled": true,
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.7.4",
|
||||
"@backstage/cli": "^0.6.6",
|
||||
"@backstage/core": "^0.7.3",
|
||||
"@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-badges": "^0.1.2",
|
||||
"@backstage/plugin-catalog": "^0.5.1",
|
||||
"@backstage/plugin-catalog-import": "^0.5.0",
|
||||
"@backstage/plugin-api-docs": "^0.4.11",
|
||||
"@backstage/plugin-badges": "^0.2.0",
|
||||
"@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",
|
||||
"@backstage/plugin-cloudbuild": "^0.2.13",
|
||||
@@ -19,7 +19,7 @@
|
||||
"@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.1",
|
||||
"@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",
|
||||
@@ -27,25 +27,25 @@
|
||||
"@backstage/plugin-kubernetes": "^0.4.2",
|
||||
"@backstage/plugin-lighthouse": "^0.2.14",
|
||||
"@backstage/plugin-newrelic": "^0.2.6",
|
||||
"@backstage/plugin-org": "^0.3.10",
|
||||
"@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.0",
|
||||
"@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.1.3",
|
||||
"@roadiehq/backstage-plugin-github-insights": "^0.3.2",
|
||||
"@roadiehq/backstage-plugin-github-pull-requests": "^0.7.6",
|
||||
"@roadiehq/backstage-plugin-travis-ci": "^0.4.5",
|
||||
"@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",
|
||||
@@ -57,7 +57,7 @@
|
||||
"zen-observable": "^0.8.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/test-utils": "^0.1.9",
|
||||
"@backstage/test-utils": "^0.1.10",
|
||||
"@testing-library/cypress": "^7.0.1",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^11.2.5",
|
||||
|
||||
@@ -27,7 +27,11 @@ import {
|
||||
CatalogIndexPage,
|
||||
catalogPlugin,
|
||||
} from '@backstage/plugin-catalog';
|
||||
import { CatalogImportPage } from '@backstage/plugin-catalog-import';
|
||||
|
||||
import {
|
||||
CatalogImportPage,
|
||||
catalogImportPlugin,
|
||||
} from '@backstage/plugin-catalog-import';
|
||||
import {
|
||||
CostInsightsLabelDataflowInstructionsPage,
|
||||
CostInsightsPage,
|
||||
@@ -48,8 +52,8 @@ import React from 'react';
|
||||
import { hot } from 'react-hot-loader/root';
|
||||
import { Navigate, Route } from 'react-router';
|
||||
import { apis } from './apis';
|
||||
import { EntityPage } from './components/catalog/EntityPage';
|
||||
import { Root } from './components/Root';
|
||||
import { entityPage } from './components/catalog/EntityPage';
|
||||
import { providers } from './identityProviders';
|
||||
import * as plugins from './plugins';
|
||||
|
||||
@@ -82,6 +86,9 @@ const app = createApp({
|
||||
bind(explorePlugin.externalRoutes, {
|
||||
catalogEntity: catalogPlugin.routes.catalogEntity,
|
||||
});
|
||||
bind(scaffolderPlugin.externalRoutes, {
|
||||
registerComponent: catalogImportPlugin.routes.importPage,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -96,7 +103,7 @@ const routes = (
|
||||
path="/catalog/:namespace/:kind/:name"
|
||||
element={<CatalogEntityPage />}
|
||||
>
|
||||
<EntityPage />
|
||||
{entityPage}
|
||||
</Route>
|
||||
<Route path="/catalog-import" element={<CatalogImportPage />} />
|
||||
<Route path="/docs" element={<TechdocsPage />} />
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { CICDSwitcher } from './EntityPage';
|
||||
import { UrlPatternDiscovery, ApiProvider, ApiRegistry } from '@backstage/core';
|
||||
import {
|
||||
buildKiteApiRef,
|
||||
BuildkiteApi,
|
||||
} from '@roadiehq/backstage-plugin-buildkite';
|
||||
import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils';
|
||||
import { ApiProvider, ApiRegistry } from '@backstage/core';
|
||||
import { EntityLayout } from '@backstage/plugin-catalog';
|
||||
import { EntityProvider } from '@backstage/plugin-catalog-react';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
import { cicdContent } from './EntityPage';
|
||||
import { githubActionsApiRef } from '@backstage/plugin-github-actions';
|
||||
|
||||
describe('EntityPage Test', () => {
|
||||
const entity = {
|
||||
@@ -29,7 +29,7 @@ describe('EntityPage Test', () => {
|
||||
metadata: {
|
||||
name: 'ExampleComponent',
|
||||
annotations: {
|
||||
'buildkite.com/project-slug': 'exampleProject/examplePipeline',
|
||||
'github.com/project-slug': 'example/project',
|
||||
},
|
||||
},
|
||||
spec: {
|
||||
@@ -39,24 +39,36 @@ describe('EntityPage Test', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const discoveryApi = UrlPatternDiscovery.compile('http://exampleapi.com');
|
||||
const mockedApi = {
|
||||
listWorkflowRuns: jest.fn().mockResolvedValue([]),
|
||||
getWorkflow: jest.fn(),
|
||||
getWorkflowRun: jest.fn(),
|
||||
reRunWorkflow: jest.fn(),
|
||||
downloadJobLogsForWorkflowRun: jest.fn(),
|
||||
} as jest.Mocked<typeof githubActionsApiRef.T>;
|
||||
|
||||
const apis = ApiRegistry.from([
|
||||
[buildKiteApiRef, new BuildkiteApi({ discoveryApi })],
|
||||
]);
|
||||
const apis = ApiRegistry.with(githubActionsApiRef, mockedApi);
|
||||
|
||||
describe('CICDSwitcher Test', () => {
|
||||
it('Should render Buildkite View', async () => {
|
||||
const renderedComponent = await renderWithEffects(
|
||||
wrapInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<CICDSwitcher entity={entity} />
|
||||
</ApiProvider>,
|
||||
),
|
||||
describe('cicdContent', () => {
|
||||
it('Should render GitHub Actions View', async () => {
|
||||
const rendered = await renderInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<EntityProvider entity={entity}>
|
||||
<EntityLayout>
|
||||
<EntityLayout.Route path="/ci-cd" title="CI-CD">
|
||||
{cicdContent}
|
||||
</EntityLayout.Route>
|
||||
</EntityLayout>
|
||||
</EntityProvider>
|
||||
</ApiProvider>,
|
||||
);
|
||||
expect(
|
||||
renderedComponent.getByText(/exampleProject\/examplePipeline/),
|
||||
).toBeInTheDocument();
|
||||
|
||||
expect(rendered.getByText('ExampleComponent')).toBeInTheDocument();
|
||||
|
||||
await expect(
|
||||
rendered.findByText('No Workflow Data'),
|
||||
).resolves.toBeInTheDocument();
|
||||
expect(rendered.getByText('Create new Workflow')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,175 +14,96 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
ApiEntity,
|
||||
DomainEntity,
|
||||
Entity,
|
||||
GroupEntity,
|
||||
SystemEntity,
|
||||
UserEntity,
|
||||
} from '@backstage/catalog-model';
|
||||
import React, { ReactNode, useMemo, useState } from 'react';
|
||||
import BadgeIcon from '@material-ui/icons/CallToAction';
|
||||
import { EmptyState } from '@backstage/core';
|
||||
import {
|
||||
ApiDefinitionCard,
|
||||
ConsumedApisCard,
|
||||
ConsumingComponentsCard,
|
||||
EntityApiDefinitionCard,
|
||||
EntityConsumingComponentsCard,
|
||||
EntityHasApisCard,
|
||||
ProvidedApisCard,
|
||||
ProvidingComponentsCard,
|
||||
EntityProvidingComponentsCard,
|
||||
EntityProvidedApisCard,
|
||||
EntityConsumedApisCard,
|
||||
} from '@backstage/plugin-api-docs';
|
||||
import { EntityBadgesDialog } from '@backstage/plugin-badges';
|
||||
import {
|
||||
AboutCard,
|
||||
EntityAboutCard,
|
||||
EntityHasComponentsCard,
|
||||
EntityHasSubcomponentsCard,
|
||||
EntityHasSystemsCard,
|
||||
EntityLayout,
|
||||
EntityLinksCard,
|
||||
EntityPageLayout,
|
||||
EntitySystemDiagramCard,
|
||||
EntitySwitch,
|
||||
isComponentType,
|
||||
isKind,
|
||||
} from '@backstage/plugin-catalog';
|
||||
import { EntityProvider, useEntity } from '@backstage/plugin-catalog-react';
|
||||
import {
|
||||
isPluginApplicableToEntity as isCircleCIAvailable,
|
||||
Router as CircleCIRouter,
|
||||
EntityCircleCIContent,
|
||||
isCircleCIAvailable,
|
||||
} from '@backstage/plugin-circleci';
|
||||
import {
|
||||
isPluginApplicableToEntity as isCloudbuildAvailable,
|
||||
Router as CloudbuildRouter,
|
||||
EntityCloudbuildContent,
|
||||
isCloudbuildAvailable,
|
||||
} from '@backstage/plugin-cloudbuild';
|
||||
import {
|
||||
isPluginApplicableToEntity as isGitHubActionsAvailable,
|
||||
RecentWorkflowRunsCard,
|
||||
Router as GitHubActionsRouter,
|
||||
EntityGithubActionsContent,
|
||||
EntityRecentGithubActionsRunsCard,
|
||||
isGithubActionsAvailable,
|
||||
} from '@backstage/plugin-github-actions';
|
||||
import {
|
||||
isPluginApplicableToEntity as isJenkinsAvailable,
|
||||
LatestRunCard as JenkinsLatestRunCard,
|
||||
Router as JenkinsRouter,
|
||||
EntityJenkinsContent,
|
||||
EntityLatestJenkinsRunCard,
|
||||
isJenkinsAvailable,
|
||||
} from '@backstage/plugin-jenkins';
|
||||
import { Router as KafkaRouter } from '@backstage/plugin-kafka';
|
||||
import { Router as KubernetesRouter } from '@backstage/plugin-kubernetes';
|
||||
import { EntityKafkaContent } from '@backstage/plugin-kafka';
|
||||
import { EntityKubernetesContent } from '@backstage/plugin-kubernetes';
|
||||
import {
|
||||
EmbeddedRouter as LighthouseRouter,
|
||||
isPluginApplicableToEntity as isLighthouseAvailable,
|
||||
LastLighthouseAuditCard,
|
||||
EntityLastLighthouseAuditCard,
|
||||
EntityLighthouseContent,
|
||||
isLighthouseAvailable,
|
||||
} from '@backstage/plugin-lighthouse';
|
||||
import {
|
||||
GroupProfileCard,
|
||||
MembersListCard,
|
||||
OwnershipCard,
|
||||
UserProfileCard,
|
||||
EntityGroupProfileCard,
|
||||
EntityMembersListCard,
|
||||
EntityOwnershipCard,
|
||||
EntityUserProfileCard,
|
||||
} from '@backstage/plugin-org';
|
||||
import {
|
||||
isPluginApplicableToEntity as isPagerDutyAvailable,
|
||||
PagerDutyCard,
|
||||
EntityPagerDutyCard,
|
||||
isPagerDutyAvailable,
|
||||
} from '@backstage/plugin-pagerduty';
|
||||
import {
|
||||
EntityRollbarContent,
|
||||
isRollbarAvailable,
|
||||
Router as RollbarRouter,
|
||||
} from '@backstage/plugin-rollbar';
|
||||
import { Router as SentryRouter } from '@backstage/plugin-sentry';
|
||||
import { EmbeddedDocsRouter as DocsRouter } from '@backstage/plugin-techdocs';
|
||||
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 {
|
||||
isPluginApplicableToEntity as isBuildkiteAvailable,
|
||||
Router as BuildkiteRouter,
|
||||
EntityBuildkiteContent,
|
||||
isBuildkiteAvailable,
|
||||
} from '@roadiehq/backstage-plugin-buildkite';
|
||||
import {
|
||||
isPluginApplicableToEntity as isGitHubAvailable,
|
||||
LanguagesCard,
|
||||
ReadMeCard,
|
||||
ReleasesCard,
|
||||
Router as GitHubInsightsRouter,
|
||||
EntityGithubInsightsContent,
|
||||
EntityGithubInsightsLanguagesCard,
|
||||
EntityGithubInsightsReadmeCard,
|
||||
EntityGithubInsightsReleasesCard,
|
||||
isGithubInsightsAvailable,
|
||||
} from '@roadiehq/backstage-plugin-github-insights';
|
||||
import {
|
||||
isPluginApplicableToEntity as isPullRequestsAvailable,
|
||||
PullRequestsStatsCard,
|
||||
Router as PullRequestsRouter,
|
||||
EntityGithubPullRequestsContent,
|
||||
EntityGithubPullRequestsOverviewCard,
|
||||
isGithubPullRequestsAvailable,
|
||||
} from '@roadiehq/backstage-plugin-github-pull-requests';
|
||||
import {
|
||||
isPluginApplicableToEntity as isTravisCIAvailable,
|
||||
RecentTravisCIBuildsWidget,
|
||||
Router as TravisCIRouter,
|
||||
EntityTravisCIContent,
|
||||
EntityTravisCIOverviewCard,
|
||||
isTravisciAvailable,
|
||||
} from '@roadiehq/backstage-plugin-travis-ci';
|
||||
import React, { ReactNode, useMemo, useState } from 'react';
|
||||
import BadgeIcon from '@material-ui/icons/CallToAction';
|
||||
import { EntityGithubDeploymentsCard } from '@backstage/plugin-github-deployments';
|
||||
|
||||
export const CICDSwitcher = ({ entity }: { entity: Entity }) => {
|
||||
// This component is just an example of how you can implement your company's logic in entity page.
|
||||
// You can for example enforce that all components of type 'service' should use GitHubActions
|
||||
switch (true) {
|
||||
case isJenkinsAvailable(entity):
|
||||
return <JenkinsRouter entity={entity} />;
|
||||
case isBuildkiteAvailable(entity):
|
||||
return <BuildkiteRouter entity={entity} />;
|
||||
case isCircleCIAvailable(entity):
|
||||
return <CircleCIRouter entity={entity} />;
|
||||
case isCloudbuildAvailable(entity):
|
||||
return <CloudbuildRouter entity={entity} />;
|
||||
case isTravisCIAvailable(entity):
|
||||
return <TravisCIRouter entity={entity} />;
|
||||
case isGitHubActionsAvailable(entity):
|
||||
return <GitHubActionsRouter entity={entity} />;
|
||||
default:
|
||||
return (
|
||||
<EmptyState
|
||||
title="No CI/CD available for this entity"
|
||||
missing="info"
|
||||
description="You need to add an annotation to your component if you want to enable CI/CD for it. You can read more about annotations in Backstage by clicking the button below."
|
||||
action={
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
href="https://backstage.io/docs/features/software-catalog/well-known-annotations"
|
||||
>
|
||||
Read more
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const RecentCICDRunsSwitcher = ({ entity }: { entity: Entity }) => {
|
||||
let content: ReactNode;
|
||||
switch (true) {
|
||||
case isJenkinsAvailable(entity):
|
||||
content = <JenkinsLatestRunCard branch="master" variant="gridItem" />;
|
||||
break;
|
||||
case isTravisCIAvailable(entity):
|
||||
content = <RecentTravisCIBuildsWidget entity={entity} />;
|
||||
break;
|
||||
case isGitHubActionsAvailable(entity):
|
||||
content = (
|
||||
<RecentWorkflowRunsCard entity={entity} limit={4} variant="gridItem" />
|
||||
);
|
||||
break;
|
||||
default:
|
||||
content = null;
|
||||
}
|
||||
if (!content) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Grid item sm={6}>
|
||||
{content}
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export const ErrorsSwitcher = ({ entity }: { entity: Entity }) => {
|
||||
switch (true) {
|
||||
case isRollbarAvailable(entity):
|
||||
return <RollbarRouter entity={entity} />;
|
||||
default:
|
||||
return <SentryRouter entity={entity} />;
|
||||
}
|
||||
};
|
||||
|
||||
const EntityPageLayoutWrapper = (props: { children?: React.ReactNode }) => {
|
||||
const EntityLayoutWrapper = (props: { children?: ReactNode }) => {
|
||||
const [badgesDialogOpen, setBadgesDialogOpen] = useState(false);
|
||||
|
||||
const extraMenuItems = useMemo(() => {
|
||||
@@ -197,9 +118,9 @@ const EntityPageLayoutWrapper = (props: { children?: React.ReactNode }) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<EntityPageLayout UNSTABLE_extraContextMenuItems={extraMenuItems}>
|
||||
<EntityLayout UNSTABLE_extraContextMenuItems={extraMenuItems}>
|
||||
{props.children}
|
||||
</EntityPageLayout>
|
||||
</EntityLayout>
|
||||
<EntityBadgesDialog
|
||||
open={badgesDialogOpen}
|
||||
onClose={() => setBadgesDialogOpen(false)}
|
||||
@@ -208,353 +129,367 @@ const EntityPageLayoutWrapper = (props: { children?: React.ReactNode }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const ComponentOverviewContent = ({ entity }: { entity: Entity }) => (
|
||||
export const cicdContent = (
|
||||
<EntitySwitch>
|
||||
<EntitySwitch.Case if={isJenkinsAvailable}>
|
||||
<EntityJenkinsContent />
|
||||
</EntitySwitch.Case>
|
||||
|
||||
<EntitySwitch.Case if={isBuildkiteAvailable}>
|
||||
<EntityBuildkiteContent />
|
||||
</EntitySwitch.Case>
|
||||
|
||||
<EntitySwitch.Case if={isCircleCIAvailable}>
|
||||
<EntityCircleCIContent />
|
||||
</EntitySwitch.Case>
|
||||
|
||||
<EntitySwitch.Case if={isCloudbuildAvailable}>
|
||||
<EntityCloudbuildContent />
|
||||
</EntitySwitch.Case>
|
||||
|
||||
<EntitySwitch.Case if={isTravisciAvailable}>
|
||||
<EntityTravisCIContent />
|
||||
</EntitySwitch.Case>
|
||||
|
||||
<EntitySwitch.Case if={isGithubActionsAvailable}>
|
||||
<EntityGithubActionsContent />
|
||||
</EntitySwitch.Case>
|
||||
|
||||
<EntitySwitch.Case>
|
||||
<EmptyState
|
||||
title="No CI/CD available for this entity"
|
||||
missing="info"
|
||||
description="You need to add an annotation to your component if you want to enable CI/CD for it. You can read more about annotations in Backstage by clicking the button below."
|
||||
action={
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
href="https://backstage.io/docs/features/software-catalog/well-known-annotations"
|
||||
>
|
||||
Read more
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</EntitySwitch.Case>
|
||||
</EntitySwitch>
|
||||
);
|
||||
|
||||
const cicdCard = (
|
||||
<EntitySwitch>
|
||||
<EntitySwitch.Case if={isJenkinsAvailable}>
|
||||
<Grid item sm={6}>
|
||||
<EntityLatestJenkinsRunCard branch="master" variant="gridItem" />
|
||||
</Grid>
|
||||
</EntitySwitch.Case>
|
||||
|
||||
<EntitySwitch.Case if={isTravisciAvailable}>
|
||||
<Grid item sm={6}>
|
||||
<EntityTravisCIOverviewCard />
|
||||
</Grid>
|
||||
</EntitySwitch.Case>
|
||||
|
||||
<EntitySwitch.Case if={isGithubActionsAvailable}>
|
||||
<Grid item sm={6}>
|
||||
<EntityRecentGithubActionsRunsCard limit={4} variant="gridItem" />
|
||||
</Grid>
|
||||
</EntitySwitch.Case>
|
||||
</EntitySwitch>
|
||||
);
|
||||
|
||||
const errorsContent = (
|
||||
<EntitySwitch>
|
||||
<EntitySwitch.Case if={isRollbarAvailable}>
|
||||
<EntityRollbarContent />
|
||||
</EntitySwitch.Case>
|
||||
|
||||
<EntitySwitch.Case>
|
||||
<EntitySentryContent />
|
||||
</EntitySwitch.Case>
|
||||
</EntitySwitch>
|
||||
);
|
||||
|
||||
const overviewContent = (
|
||||
<Grid container spacing={3} alignItems="stretch">
|
||||
<Grid item xs={12} md={6}>
|
||||
<AboutCard entity={entity} variant="gridItem" />
|
||||
<Grid item md={6}>
|
||||
<EntityAboutCard variant="gridItem" />
|
||||
</Grid>
|
||||
{isPagerDutyAvailable(entity) && (
|
||||
<Grid item xs={12} md={6}>
|
||||
<EntityProvider entity={entity}>
|
||||
<PagerDutyCard />
|
||||
</EntityProvider>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={12} md={6}>
|
||||
<EntityLinksCard entity={entity} />
|
||||
|
||||
<EntitySwitch>
|
||||
<EntitySwitch.Case if={isPagerDutyAvailable}>
|
||||
<Grid item md={6}>
|
||||
<EntityPagerDutyCard />
|
||||
</Grid>
|
||||
</EntitySwitch.Case>
|
||||
</EntitySwitch>
|
||||
|
||||
<Grid item md={4} sm={6}>
|
||||
<EntityLinksCard />
|
||||
</Grid>
|
||||
<RecentCICDRunsSwitcher entity={entity} />
|
||||
{isGitHubAvailable(entity) && (
|
||||
<>
|
||||
<Grid item xs={12} md={6}>
|
||||
<LanguagesCard entity={entity} />
|
||||
<ReleasesCard entity={entity} />
|
||||
|
||||
{cicdCard}
|
||||
|
||||
<EntitySwitch>
|
||||
<EntitySwitch.Case if={e => Boolean(isGithubInsightsAvailable(e))}>
|
||||
<Grid item md={6}>
|
||||
<EntityGithubInsightsLanguagesCard />
|
||||
<EntityGithubInsightsReleasesCard />
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<ReadMeCard entity={entity} maxHeight={350} />
|
||||
<Grid item md={6}>
|
||||
<EntityGithubInsightsReadmeCard maxHeight={350} />
|
||||
</Grid>
|
||||
</>
|
||||
)}
|
||||
{isLighthouseAvailable(entity) && (
|
||||
<Grid item xs={12} sm={4}>
|
||||
<LastLighthouseAuditCard variant="gridItem" />
|
||||
</Grid>
|
||||
)}
|
||||
{isPullRequestsAvailable(entity) && (
|
||||
<Grid item xs={12} sm={4}>
|
||||
<PullRequestsStatsCard entity={entity} />
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={12} md={6}>
|
||||
</EntitySwitch.Case>
|
||||
</EntitySwitch>
|
||||
|
||||
<EntitySwitch>
|
||||
<EntitySwitch.Case if={isLighthouseAvailable}>
|
||||
<Grid item sm={4}>
|
||||
<EntityLastLighthouseAuditCard variant="gridItem" />
|
||||
</Grid>
|
||||
</EntitySwitch.Case>
|
||||
</EntitySwitch>
|
||||
|
||||
<EntitySwitch>
|
||||
<EntitySwitch.Case if={e => Boolean(isGithubPullRequestsAvailable(e))}>
|
||||
<Grid item sm={4}>
|
||||
<EntityGithubPullRequestsOverviewCard />
|
||||
</Grid>
|
||||
</EntitySwitch.Case>
|
||||
</EntitySwitch>
|
||||
|
||||
<Grid item md={6}>
|
||||
<EntityHasSubcomponentsCard variant="gridItem" />
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<EntityGithubDeploymentsCard />
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
|
||||
const ComponentApisContent = ({ entity }: { entity: Entity }) => (
|
||||
<Grid container spacing={3} alignItems="stretch">
|
||||
<Grid item xs={12} md={6}>
|
||||
<ProvidedApisCard entity={entity} />
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<ConsumedApisCard entity={entity} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
const serviceEntityPage = (
|
||||
<EntityLayoutWrapper>
|
||||
<EntityLayout.Route path="/" title="Overview">
|
||||
{overviewContent}
|
||||
</EntityLayout.Route>
|
||||
|
||||
const ServiceEntityPage = ({ entity }: { entity: Entity }) => (
|
||||
<EntityPageLayoutWrapper>
|
||||
<EntityPageLayout.Content
|
||||
path="/"
|
||||
title="Overview"
|
||||
element={<ComponentOverviewContent entity={entity} />}
|
||||
/>
|
||||
<EntityPageLayout.Content
|
||||
path="/ci-cd/*"
|
||||
title="CI/CD"
|
||||
element={<CICDSwitcher entity={entity} />}
|
||||
/>
|
||||
<EntityPageLayout.Content
|
||||
path="/errors/*"
|
||||
title="Errors"
|
||||
element={<ErrorsSwitcher entity={entity} />}
|
||||
/>
|
||||
<EntityPageLayout.Content
|
||||
path="/api/*"
|
||||
title="API"
|
||||
element={<ComponentApisContent entity={entity} />}
|
||||
/>
|
||||
<EntityPageLayout.Content
|
||||
path="/docs/*"
|
||||
title="Docs"
|
||||
element={<DocsRouter entity={entity} />}
|
||||
/>
|
||||
<EntityPageLayout.Content
|
||||
path="/kubernetes/*"
|
||||
title="Kubernetes"
|
||||
element={<KubernetesRouter entity={entity} />}
|
||||
/>
|
||||
<EntityPageLayout.Content
|
||||
path="/pull-requests"
|
||||
title="Pull Requests"
|
||||
element={<PullRequestsRouter entity={entity} />}
|
||||
/>
|
||||
<EntityPageLayout.Content
|
||||
path="/code-insights"
|
||||
title="Code Insights"
|
||||
element={<GitHubInsightsRouter entity={entity} />}
|
||||
/>
|
||||
<EntityPageLayout.Content
|
||||
path="/kafka/*"
|
||||
title="Kafka"
|
||||
element={<KafkaRouter entity={entity} />}
|
||||
/>
|
||||
<EntityPageLayout.Content
|
||||
path="/todos"
|
||||
title="TODOs"
|
||||
element={<EntityTodoContent />}
|
||||
/>
|
||||
</EntityPageLayoutWrapper>
|
||||
);
|
||||
<EntityLayout.Route path="/ci-cd" title="CI/CD">
|
||||
{cicdContent}
|
||||
</EntityLayout.Route>
|
||||
|
||||
const WebsiteEntityPage = ({ entity }: { entity: Entity }) => (
|
||||
<EntityPageLayoutWrapper>
|
||||
<EntityPageLayout.Content
|
||||
path="/"
|
||||
title="Overview"
|
||||
element={<ComponentOverviewContent entity={entity} />}
|
||||
/>
|
||||
<EntityPageLayout.Content
|
||||
path="/ci-cd/*"
|
||||
title="CI/CD"
|
||||
element={<CICDSwitcher entity={entity} />}
|
||||
/>
|
||||
<EntityPageLayout.Content
|
||||
path="/lighthouse/*"
|
||||
title="Lighthouse"
|
||||
element={<LighthouseRouter entity={entity} />}
|
||||
/>
|
||||
<EntityPageLayout.Content
|
||||
path="/errors/*"
|
||||
title="Errors"
|
||||
element={<ErrorsSwitcher entity={entity} />}
|
||||
/>
|
||||
<EntityPageLayout.Content
|
||||
path="/docs/*"
|
||||
title="Docs"
|
||||
element={<DocsRouter entity={entity} />}
|
||||
/>
|
||||
<EntityPageLayout.Content
|
||||
path="/kubernetes/*"
|
||||
title="Kubernetes"
|
||||
element={<KubernetesRouter entity={entity} />}
|
||||
/>
|
||||
<EntityPageLayout.Content
|
||||
path="/pull-requests"
|
||||
title="Pull Requests"
|
||||
element={<PullRequestsRouter entity={entity} />}
|
||||
/>
|
||||
<EntityPageLayout.Content
|
||||
path="/code-insights"
|
||||
title="Code Insights"
|
||||
element={<GitHubInsightsRouter entity={entity} />}
|
||||
/>
|
||||
<EntityPageLayout.Content
|
||||
path="/todos"
|
||||
title="TODOs"
|
||||
element={<EntityTodoContent />}
|
||||
/>
|
||||
</EntityPageLayoutWrapper>
|
||||
);
|
||||
<EntityLayout.Route path="/errors" title="Errors">
|
||||
{errorsContent}
|
||||
</EntityLayout.Route>
|
||||
|
||||
const DefaultEntityPage = ({ entity }: { entity: Entity }) => (
|
||||
<EntityPageLayoutWrapper>
|
||||
<EntityPageLayout.Content
|
||||
path="/*"
|
||||
title="Overview"
|
||||
element={<ComponentOverviewContent entity={entity} />}
|
||||
/>
|
||||
<EntityPageLayout.Content
|
||||
path="/docs/*"
|
||||
title="Docs"
|
||||
element={<DocsRouter entity={entity} />}
|
||||
/>
|
||||
<EntityPageLayout.Content
|
||||
path="/todos"
|
||||
title="TODOs"
|
||||
element={<EntityTodoContent />}
|
||||
/>
|
||||
</EntityPageLayoutWrapper>
|
||||
);
|
||||
|
||||
export const ComponentEntityPage = ({ entity }: { entity: Entity }) => {
|
||||
switch (entity?.spec?.type) {
|
||||
case 'service':
|
||||
return <ServiceEntityPage entity={entity} />;
|
||||
case 'website':
|
||||
return <WebsiteEntityPage entity={entity} />;
|
||||
default:
|
||||
return <DefaultEntityPage entity={entity} />;
|
||||
}
|
||||
};
|
||||
|
||||
const ApiOverviewContent = ({ entity }: { entity: Entity }) => (
|
||||
<Grid container spacing={3}>
|
||||
<Grid item md={6}>
|
||||
<AboutCard entity={entity} />
|
||||
</Grid>
|
||||
<Grid container item md={12}>
|
||||
<Grid item md={6}>
|
||||
<ProvidingComponentsCard entity={entity} />
|
||||
<EntityLayout.Route path="/api" title="API">
|
||||
<Grid container spacing={3} alignItems="stretch">
|
||||
<Grid item md={6}>
|
||||
<EntityProvidedApisCard />
|
||||
</Grid>
|
||||
<Grid item md={6}>
|
||||
<EntityConsumedApisCard />
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item md={6}>
|
||||
<ConsumingComponentsCard entity={entity} />
|
||||
</EntityLayout.Route>
|
||||
|
||||
<EntityLayout.Route path="/docs" title="Docs">
|
||||
<EntityTechdocsContent />
|
||||
</EntityLayout.Route>
|
||||
|
||||
<EntityLayout.Route path="/kubernetes" title="Kubernetes">
|
||||
<EntityKubernetesContent />
|
||||
</EntityLayout.Route>
|
||||
|
||||
<EntityLayout.Route path="/pull-requests" title="Pull Requests">
|
||||
<EntityGithubPullRequestsContent />
|
||||
</EntityLayout.Route>
|
||||
|
||||
<EntityLayout.Route path="/code-insights" title="Code Insights">
|
||||
<EntityGithubInsightsContent />
|
||||
</EntityLayout.Route>
|
||||
|
||||
<EntityLayout.Route path="/kafka" title="Kafka">
|
||||
<EntityKafkaContent />
|
||||
</EntityLayout.Route>
|
||||
|
||||
<EntityLayout.Route path="/todos" title="TODOs">
|
||||
<EntityTodoContent />
|
||||
</EntityLayout.Route>
|
||||
</EntityLayoutWrapper>
|
||||
);
|
||||
|
||||
const websiteEntityPage = (
|
||||
<EntityLayoutWrapper>
|
||||
<EntityLayout.Route path="/" title="Overview">
|
||||
{overviewContent}
|
||||
</EntityLayout.Route>
|
||||
|
||||
<EntityLayout.Route path="/ci-cd" title="CI/CD">
|
||||
{cicdContent}
|
||||
</EntityLayout.Route>
|
||||
|
||||
<EntityLayout.Route path="/lighthouse" title="Lighthouse">
|
||||
<EntityLighthouseContent />
|
||||
</EntityLayout.Route>
|
||||
|
||||
<EntityLayout.Route path="/errors" title="Errors">
|
||||
{errorsContent}
|
||||
</EntityLayout.Route>
|
||||
|
||||
<EntityLayout.Route path="/docs" title="Docs">
|
||||
<EntityTechdocsContent />
|
||||
</EntityLayout.Route>
|
||||
|
||||
<EntityLayout.Route path="/kubernetes" title="Kubernetes">
|
||||
<EntityKubernetesContent />
|
||||
</EntityLayout.Route>
|
||||
|
||||
<EntityLayout.Route path="/pull-requests" title="Pull Requests">
|
||||
<EntityGithubPullRequestsContent />
|
||||
</EntityLayout.Route>
|
||||
|
||||
<EntityLayout.Route path="/code-insights" title="Code Insights">
|
||||
<EntityGithubInsightsContent />
|
||||
</EntityLayout.Route>
|
||||
|
||||
<EntityLayout.Route path="/todos" title="TODOs">
|
||||
<EntityTodoContent />
|
||||
</EntityLayout.Route>
|
||||
</EntityLayoutWrapper>
|
||||
);
|
||||
|
||||
const defaultEntityPage = (
|
||||
<EntityLayoutWrapper>
|
||||
<EntityLayout.Route path="/" title="Overview">
|
||||
{overviewContent}
|
||||
</EntityLayout.Route>
|
||||
|
||||
<EntityLayout.Route path="/docs" title="Docs">
|
||||
<EntityTechdocsContent />
|
||||
</EntityLayout.Route>
|
||||
|
||||
<EntityLayout.Route path="/todos" title="TODOs">
|
||||
<EntityTodoContent />
|
||||
</EntityLayout.Route>
|
||||
</EntityLayoutWrapper>
|
||||
);
|
||||
|
||||
const componentPage = (
|
||||
<EntitySwitch>
|
||||
<EntitySwitch.Case if={isComponentType('service')}>
|
||||
{serviceEntityPage}
|
||||
</EntitySwitch.Case>
|
||||
|
||||
<EntitySwitch.Case if={isComponentType('website')}>
|
||||
{websiteEntityPage}
|
||||
</EntitySwitch.Case>
|
||||
|
||||
<EntitySwitch.Case>{defaultEntityPage}</EntitySwitch.Case>
|
||||
</EntitySwitch>
|
||||
);
|
||||
|
||||
const apiPage = (
|
||||
<EntityLayoutWrapper>
|
||||
<EntityLayout.Route path="/" title="Overview">
|
||||
<Grid container spacing={3}>
|
||||
<Grid item md={6}>
|
||||
<EntityAboutCard />
|
||||
</Grid>
|
||||
<Grid container item md={12}>
|
||||
<Grid item md={6}>
|
||||
<EntityProvidingComponentsCard />
|
||||
</Grid>
|
||||
<Grid item md={6}>
|
||||
<EntityConsumingComponentsCard />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</EntityLayout.Route>
|
||||
|
||||
<EntityLayout.Route path="/definition" title="Definition">
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
<EntityApiDefinitionCard />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</EntityLayout.Route>
|
||||
</EntityLayoutWrapper>
|
||||
);
|
||||
|
||||
const ApiDefinitionContent = ({ entity }: { entity: ApiEntity }) => (
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
<ApiDefinitionCard apiEntity={entity} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
const userPage = (
|
||||
<EntityLayoutWrapper>
|
||||
<EntityLayout.Route path="/" title="Overview">
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<EntityUserProfileCard variant="gridItem" />
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<EntityOwnershipCard variant="gridItem" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</EntityLayout.Route>
|
||||
</EntityLayoutWrapper>
|
||||
);
|
||||
|
||||
const ApiEntityPage = ({ entity }: { entity: Entity }) => (
|
||||
<EntityPageLayoutWrapper>
|
||||
<EntityPageLayout.Content
|
||||
path="/*"
|
||||
title="Overview"
|
||||
element={<ApiOverviewContent entity={entity} />}
|
||||
/>
|
||||
<EntityPageLayout.Content
|
||||
path="/definition/*"
|
||||
title="Definition"
|
||||
element={<ApiDefinitionContent entity={entity as ApiEntity} />}
|
||||
/>
|
||||
</EntityPageLayoutWrapper>
|
||||
const groupPage = (
|
||||
<EntityLayoutWrapper>
|
||||
<EntityLayout.Route path="/" title="Overview">
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<EntityGroupProfileCard variant="gridItem" />
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<EntityOwnershipCard variant="gridItem" />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<EntityMembersListCard />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</EntityLayout.Route>
|
||||
|
||||
<EntityLayout.Route path="/diagram" title="Diagram">
|
||||
<EntitySystemDiagramCard />
|
||||
</EntityLayout.Route>
|
||||
</EntityLayoutWrapper>
|
||||
);
|
||||
|
||||
const UserOverviewContent = ({ entity }: { entity: UserEntity }) => (
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<UserProfileCard entity={entity} variant="gridItem" />
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<OwnershipCard entity={entity} variant="gridItem" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
const systemPage = (
|
||||
<EntityLayoutWrapper>
|
||||
<EntityLayout.Route path="/" title="Overview">
|
||||
<Grid container spacing={3} alignItems="stretch">
|
||||
<Grid item md={6}>
|
||||
<EntityAboutCard variant="gridItem" />
|
||||
</Grid>
|
||||
<Grid item md={6}>
|
||||
<EntityHasComponentsCard variant="gridItem" />
|
||||
</Grid>
|
||||
<Grid item md={6}>
|
||||
<EntityHasApisCard variant="gridItem" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</EntityLayout.Route>
|
||||
</EntityLayoutWrapper>
|
||||
);
|
||||
|
||||
const UserEntityPage = ({ entity }: { entity: Entity }) => (
|
||||
<EntityPageLayoutWrapper>
|
||||
<EntityPageLayout.Content
|
||||
path="/*"
|
||||
title="Overview"
|
||||
element={<UserOverviewContent entity={entity as UserEntity} />}
|
||||
/>
|
||||
</EntityPageLayoutWrapper>
|
||||
const domainPage = (
|
||||
<EntityLayoutWrapper>
|
||||
<EntityLayout.Route path="/" title="Overview">
|
||||
<Grid container spacing={3} alignItems="stretch">
|
||||
<Grid item md={6}>
|
||||
<EntityAboutCard variant="gridItem" />
|
||||
</Grid>
|
||||
<Grid item md={6}>
|
||||
<EntityHasSystemsCard variant="gridItem" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</EntityLayout.Route>
|
||||
</EntityLayoutWrapper>
|
||||
);
|
||||
|
||||
const GroupOverviewContent = ({ entity }: { entity: GroupEntity }) => (
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<GroupProfileCard entity={entity} variant="gridItem" />
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<OwnershipCard entity={entity} variant="gridItem" />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<MembersListCard entity={entity} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
export const entityPage = (
|
||||
<EntitySwitch>
|
||||
<EntitySwitch.Case if={isKind('component')} children={componentPage} />
|
||||
<EntitySwitch.Case if={isKind('api')} children={apiPage} />
|
||||
<EntitySwitch.Case if={isKind('group')} children={groupPage} />
|
||||
<EntitySwitch.Case if={isKind('user')} children={userPage} />
|
||||
<EntitySwitch.Case if={isKind('system')} children={systemPage} />
|
||||
<EntitySwitch.Case if={isKind('domain')} children={domainPage} />
|
||||
|
||||
<EntitySwitch.Case>{defaultEntityPage}</EntitySwitch.Case>
|
||||
</EntitySwitch>
|
||||
);
|
||||
|
||||
const GroupEntityPage = ({ entity }: { entity: Entity }) => (
|
||||
<EntityPageLayoutWrapper>
|
||||
<EntityPageLayout.Content
|
||||
path="/*"
|
||||
title="Overview"
|
||||
element={<GroupOverviewContent entity={entity as GroupEntity} />}
|
||||
/>
|
||||
</EntityPageLayoutWrapper>
|
||||
);
|
||||
|
||||
const SystemOverviewContent = ({ entity }: { entity: SystemEntity }) => (
|
||||
<Grid container spacing={3} alignItems="stretch">
|
||||
<Grid item md={6}>
|
||||
<AboutCard entity={entity} variant="gridItem" />
|
||||
</Grid>
|
||||
<Grid item md={6}>
|
||||
<EntityHasComponentsCard variant="gridItem" />
|
||||
</Grid>
|
||||
<Grid item md={6}>
|
||||
<EntityHasApisCard variant="gridItem" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
|
||||
const SystemEntityPage = ({ entity }: { entity: Entity }) => (
|
||||
<EntityPageLayoutWrapper>
|
||||
<EntityPageLayout.Content
|
||||
path="/*"
|
||||
title="Overview"
|
||||
element={<SystemOverviewContent entity={entity as SystemEntity} />}
|
||||
/>
|
||||
<EntityPageLayout.Content
|
||||
path="/diagram/*"
|
||||
title="Diagram"
|
||||
element={<EntitySystemDiagramCard />}
|
||||
/>
|
||||
</EntityPageLayoutWrapper>
|
||||
);
|
||||
|
||||
const DomainOverviewContent = ({ entity }: { entity: DomainEntity }) => (
|
||||
<Grid container spacing={3} alignItems="stretch">
|
||||
<Grid item md={6}>
|
||||
<AboutCard entity={entity} variant="gridItem" />
|
||||
</Grid>
|
||||
<Grid item md={6}>
|
||||
<EntityHasSystemsCard variant="gridItem" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
|
||||
const DomainEntityPage = ({ entity }: { entity: Entity }) => (
|
||||
<EntityPageLayoutWrapper>
|
||||
<EntityPageLayout.Content
|
||||
path="/*"
|
||||
title="Overview"
|
||||
element={<DomainOverviewContent entity={entity as DomainEntity} />}
|
||||
/>
|
||||
</EntityPageLayoutWrapper>
|
||||
);
|
||||
|
||||
export const EntityPage = () => {
|
||||
const { entity } = useEntity();
|
||||
|
||||
switch (entity?.kind?.toLocaleLowerCase('en-US')) {
|
||||
case 'component':
|
||||
return <ComponentEntityPage entity={entity} />;
|
||||
case 'api':
|
||||
return <ApiEntityPage entity={entity} />;
|
||||
case 'group':
|
||||
return <GroupEntityPage entity={entity} />;
|
||||
case 'user':
|
||||
return <UserEntityPage entity={entity} />;
|
||||
case 'system':
|
||||
return <SystemEntityPage entity={entity} />;
|
||||
case 'domain':
|
||||
return <DomainEntityPage entity={entity} />;
|
||||
case 'location':
|
||||
case 'resource':
|
||||
case 'template':
|
||||
default:
|
||||
return <DefaultEntityPage entity={entity} />;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -27,19 +27,19 @@ 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 { travisciPlugin } 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 { githubPullRequestsPlugin } 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 { githubInsightsPlugin } 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 { buildkitePlugin } 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';
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @backstage/backend-common
|
||||
|
||||
## 0.6.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- b779b5fee: Add UrlReader for Google Cloud Storage
|
||||
- Updated dependencies [82c66b8cd]
|
||||
- @backstage/config-loader@0.6.0
|
||||
|
||||
## 0.6.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/backend-common",
|
||||
"description": "Common functionality library for Backstage backends",
|
||||
"version": "0.6.1",
|
||||
"version": "0.6.2",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"private": false,
|
||||
@@ -31,9 +31,10 @@
|
||||
"dependencies": {
|
||||
"@backstage/cli-common": "^0.1.1",
|
||||
"@backstage/config": "^0.1.4",
|
||||
"@backstage/config-loader": "^0.5.1",
|
||||
"@backstage/config-loader": "^0.6.0",
|
||||
"@backstage/errors": "^0.1.1",
|
||||
"@backstage/integration": "^0.5.1",
|
||||
"@google-cloud/storage": "^5.8.0",
|
||||
"@octokit/rest": "^18.0.12",
|
||||
"@types/cors": "^2.8.6",
|
||||
"@types/dockerode": "^3.2.1",
|
||||
@@ -56,6 +57,7 @@
|
||||
"minimatch": "^3.0.4",
|
||||
"minimist": "^1.2.5",
|
||||
"morgan": "^1.10.0",
|
||||
"raw-body": "^2.4.1",
|
||||
"selfsigned": "^1.10.7",
|
||||
"stoppable": "^1.1.0",
|
||||
"tar": "^6.0.5",
|
||||
@@ -71,8 +73,8 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.5",
|
||||
"@backstage/test-utils": "^0.1.9",
|
||||
"@backstage/cli": "^0.6.7",
|
||||
"@backstage/test-utils": "^0.1.10",
|
||||
"@types/archiver": "^5.1.0",
|
||||
"@types/compression": "^1.7.0",
|
||||
"@types/concat-stream": "^1.6.0",
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2020 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 { ConfigReader, JsonObject } from '@backstage/config';
|
||||
import { getVoidLogger } from '../logging';
|
||||
import { ReadTreeResponseFactory } from './tree';
|
||||
import { GoogleGcsUrlReader } from './GoogleGcsUrlReader';
|
||||
import { UrlReaderPredicateTuple } from './types';
|
||||
|
||||
describe('GcsUrlReader', () => {
|
||||
const createReader = (config: JsonObject): UrlReaderPredicateTuple[] => {
|
||||
return GoogleGcsUrlReader.factory({
|
||||
config: new ConfigReader(config),
|
||||
logger: getVoidLogger(),
|
||||
treeResponseFactory: ReadTreeResponseFactory.create({
|
||||
config: new ConfigReader({}),
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
it('does not create a reader without the googleGcs field', () => {
|
||||
const entries = createReader({
|
||||
integrations: {},
|
||||
});
|
||||
expect(entries).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('creates a reader with credentials correctly configured', () => {
|
||||
const entries = createReader({
|
||||
integrations: {
|
||||
googleGcs: {
|
||||
privateKey: '--- BEGIN KEY ---- fakekey --- END KEY ---',
|
||||
clientEmail: 'someone@example.com',
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(entries).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('creates a reader with default credentials provider', () => {
|
||||
const entries = createReader({
|
||||
integrations: {
|
||||
googleGcs: {},
|
||||
},
|
||||
});
|
||||
expect(entries).toHaveLength(1);
|
||||
});
|
||||
|
||||
describe('predicates', () => {
|
||||
const readers = createReader({
|
||||
integrations: {
|
||||
googleGcs: {},
|
||||
},
|
||||
});
|
||||
const predicate = readers[0].predicate;
|
||||
|
||||
it('returns true for the correct google cloud storage host', () => {
|
||||
expect(predicate(new URL('https://storage.cloud.google.com'))).toBe(true);
|
||||
});
|
||||
it('returns true for a url with the full path and the correct host', () => {
|
||||
expect(
|
||||
predicate(
|
||||
new URL(
|
||||
'https://storage.cloud.google.com/team1/service1/catalog-info.yaml',
|
||||
),
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
it('returns false for the wrong hostname under cloud.google.com', () => {
|
||||
expect(predicate(new URL('https://storage2.cloud.google.com'))).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
it('returns false for a partially correct host', () => {
|
||||
expect(predicate(new URL('https://cloud.google.com'))).toBe(false);
|
||||
});
|
||||
it('returns false for a completely different host', () => {
|
||||
expect(predicate(new URL('https://a.example.com/test'))).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright 2020 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 { Storage } from '@google-cloud/storage';
|
||||
import {
|
||||
ReaderFactory,
|
||||
ReadTreeResponse,
|
||||
SearchResponse,
|
||||
UrlReader,
|
||||
} from './types';
|
||||
import getRawBody from 'raw-body';
|
||||
import {
|
||||
GoogleGcsIntegrationConfig,
|
||||
readGoogleGcsIntegrationConfig,
|
||||
} from '@backstage/integration';
|
||||
|
||||
const GOOGLE_GCS_HOST = 'storage.cloud.google.com';
|
||||
|
||||
const parseURL = (
|
||||
url: string,
|
||||
): { host: string; bucket: string; key: string } => {
|
||||
const { host, pathname } = new URL(url);
|
||||
|
||||
if (host !== GOOGLE_GCS_HOST) {
|
||||
throw new Error(`not a valid GCS URL: ${url}`);
|
||||
}
|
||||
|
||||
const [, bucket, ...key] = pathname.split('/');
|
||||
return {
|
||||
host: host,
|
||||
bucket,
|
||||
key: key.join('/'),
|
||||
};
|
||||
};
|
||||
|
||||
export class GoogleGcsUrlReader implements UrlReader {
|
||||
static factory: ReaderFactory = ({ config, logger }) => {
|
||||
if (!config.has('integrations.googleGcs')) {
|
||||
return [];
|
||||
}
|
||||
const gcsConfig = readGoogleGcsIntegrationConfig(
|
||||
config.getConfig('integrations.googleGcs'),
|
||||
);
|
||||
let storage: Storage;
|
||||
if (!gcsConfig.clientEmail || !gcsConfig.privateKey) {
|
||||
logger.info(
|
||||
'googleGcs credentials not found in config. Using default credentials provider.',
|
||||
);
|
||||
storage = new Storage();
|
||||
} else {
|
||||
storage = new Storage({
|
||||
credentials: {
|
||||
client_email: gcsConfig.clientEmail || undefined,
|
||||
private_key: gcsConfig.privateKey || undefined,
|
||||
},
|
||||
});
|
||||
}
|
||||
const reader = new GoogleGcsUrlReader(gcsConfig, storage);
|
||||
const predicate = (url: URL) => url.host === GOOGLE_GCS_HOST;
|
||||
return [{ reader, predicate }];
|
||||
};
|
||||
|
||||
constructor(
|
||||
private readonly integration: GoogleGcsIntegrationConfig,
|
||||
private readonly storage: Storage,
|
||||
) {}
|
||||
|
||||
async read(url: string): Promise<Buffer> {
|
||||
try {
|
||||
const { bucket, key } = parseURL(url);
|
||||
|
||||
return await getRawBody(
|
||||
this.storage.bucket(bucket).file(key).createReadStream(),
|
||||
);
|
||||
} catch (error) {
|
||||
throw new Error(`unable to read gcs file from ${url}, ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
async readTree(): Promise<ReadTreeResponse> {
|
||||
throw new Error('GcsUrlReader does not implement readTree');
|
||||
}
|
||||
|
||||
async search(): Promise<SearchResponse> {
|
||||
throw new Error('GcsUrlReader does not implement search');
|
||||
}
|
||||
|
||||
toString() {
|
||||
const key = this.integration.privateKey;
|
||||
return `googleGcs{host=${GOOGLE_GCS_HOST},authed=${Boolean(key)}}`;
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import { GithubUrlReader } from './GithubUrlReader';
|
||||
import { GitlabUrlReader } from './GitlabUrlReader';
|
||||
import { ReadTreeResponseFactory } from './tree';
|
||||
import { FetchUrlReader } from './FetchUrlReader';
|
||||
import { GoogleGcsUrlReader } from './GoogleGcsUrlReader';
|
||||
|
||||
type CreateOptions = {
|
||||
/** Root config object */
|
||||
@@ -70,6 +71,7 @@ export class UrlReaders {
|
||||
BitbucketUrlReader.factory,
|
||||
GithubUrlReader.factory,
|
||||
GitlabUrlReader.factory,
|
||||
GoogleGcsUrlReader.factory,
|
||||
FetchUrlReader.factory,
|
||||
]),
|
||||
});
|
||||
|
||||
@@ -16,11 +16,13 @@ WORKDIR /app
|
||||
# Copy repo skeleton first, to avoid unnecessary docker cache invalidation.
|
||||
# The skeleton contains the package.json of each package in the monorepo,
|
||||
# and along with yarn.lock and the root package.json, that's enough to run yarn install.
|
||||
ADD yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./
|
||||
COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./
|
||||
RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz
|
||||
|
||||
RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)"
|
||||
|
||||
# Then copy the rest of the backend bundle, along with any other files we might want.
|
||||
ADD packages/backend/dist/bundle.tar.gz app-config.yaml ./
|
||||
RUN tar xzf bundle.tar.gz && rm bundle.tar.gz
|
||||
|
||||
CMD ["node", "packages/backend", "--config", "app-config.yaml"]
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -42,10 +42,14 @@ export class CatalogClient implements CatalogApi {
|
||||
}
|
||||
|
||||
async getLocationById(
|
||||
id: String,
|
||||
id: string,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<Location | undefined> {
|
||||
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<void> {
|
||||
await this.requestIgnored('DELETE', `/locations/${id}`, options);
|
||||
await this.requestIgnored(
|
||||
'DELETE',
|
||||
`/locations/${encodeURIComponent(id)}`,
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
async removeEntityByUid(
|
||||
uid: string,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<void> {
|
||||
await this.requestIgnored('DELETE', `/entities/by-uid/${uid}`, options);
|
||||
await this.requestIgnored(
|
||||
'DELETE',
|
||||
`/entities/by-uid/${encodeURIComponent(uid)}`,
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -46,7 +46,7 @@ export interface CatalogApi {
|
||||
|
||||
// Locations
|
||||
getLocationById(
|
||||
id: String,
|
||||
id: string,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<Location | undefined>;
|
||||
getOriginLocationByEntity(
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
# @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
|
||||
|
||||
- 97b60de98: Added `stringifyEntityRef`, which always creates a string representation of an entity reference. Also deprecated `serializeEntityRef`, as `stringifyEntityRef` should be used instead.
|
||||
- 98dd5da71: Add support for multiple links to post-scaffold task summary page
|
||||
|
||||
## 0.7.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -14,4 +14,4 @@ spec:
|
||||
lifecycle: production
|
||||
owner: team-a
|
||||
definition:
|
||||
$text: https://github.com/APIs-guru/openapi-directory/blob/master/APIs/spotify.com/v1/swagger.yaml
|
||||
$text: https://github.com/APIs-guru/openapi-directory/blob/dab6854d4d599aafb0eb36e6c7ae1fe0c37509b7/APIs/spotify.com/2021.4.2/openapi.yaml
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/catalog-model",
|
||||
"version": "0.7.5",
|
||||
"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.4",
|
||||
"@backstage/cli": "^0.6.8",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/jest": "^26.0.7",
|
||||
"@types/lodash": "^4.14.151",
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -34,6 +34,7 @@ export interface ComponentEntityV1alpha1 extends Entity {
|
||||
subcomponentOf?: string;
|
||||
providesApis?: string[];
|
||||
consumesApis?: string[];
|
||||
dependsOn?: string[];
|
||||
system?: string;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -30,6 +30,7 @@ export interface ResourceEntityV1alpha1 extends Entity {
|
||||
spec: {
|
||||
type: string;
|
||||
owner: string;
|
||||
dependsOn?: string[];
|
||||
system?: string;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -135,6 +135,36 @@
|
||||
"output": {
|
||||
"type": "object",
|
||||
"description": "A templated object describing the outputs of the scaffolding task.",
|
||||
"properties": {
|
||||
"links": {
|
||||
"type": "array",
|
||||
"description": "A list of external hyperlinks, typically pointing to resources created or updated by the template",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["url"],
|
||||
"properties": {
|
||||
"url": {
|
||||
"type": "string",
|
||||
"description": "A url in a standard uri format.",
|
||||
"examples": ["https://github.com/my-org/my-new-repo"],
|
||||
"minLength": 1
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "A user friendly display name for the link.",
|
||||
"examples": ["View new repo"],
|
||||
"minLength": 1
|
||||
},
|
||||
"icon": {
|
||||
"type": "string",
|
||||
"description": "A key representing a visual icon to be displayed in the UI.",
|
||||
"examples": ["dashboard"],
|
||||
"minLength": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
# @backstage/cli
|
||||
|
||||
## 0.6.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 60ce64aa2: Disable hot reloading in CI environments.
|
||||
|
||||
## 0.6.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [82c66b8cd]
|
||||
- @backstage/config-loader@0.6.0
|
||||
|
||||
## 0.6.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"incremental": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "react",
|
||||
"lib": ["DOM", "DOM.Iterable", "ScriptHost", "ES2020", "ESNext.Promise"],
|
||||
"lib": ["DOM", "DOM.Iterable", "ScriptHost", "ES2020"],
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"noEmit": false,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/cli",
|
||||
"description": "CLI for developing Backstage plugins and apps",
|
||||
"version": "0.6.6",
|
||||
"version": "0.6.8",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
@@ -32,7 +32,7 @@
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.4.4",
|
||||
"@backstage/cli-common": "^0.1.1",
|
||||
"@backstage/config": "^0.1.4",
|
||||
"@backstage/config-loader": "^0.5.1",
|
||||
"@backstage/config-loader": "^0.6.0",
|
||||
"@hot-loader/react-dom": "^16.13.0",
|
||||
"@lerna/package-graph": "^4.0.0",
|
||||
"@lerna/project": "^4.0.0",
|
||||
@@ -116,11 +116,11 @@
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/backend-common": "^0.6.0",
|
||||
"@backstage/backend-common": "^0.6.2",
|
||||
"@backstage/config": "^0.1.4",
|
||||
"@backstage/core": "^0.7.3",
|
||||
"@backstage/core": "^0.7.5",
|
||||
"@backstage/dev-utils": "^0.1.13",
|
||||
"@backstage/test-utils": "^0.1.9",
|
||||
"@backstage/test-utils": "^0.1.10",
|
||||
"@backstage/theme": "^0.2.5",
|
||||
"@types/diff": "^5.0.0",
|
||||
"@types/express": "^4.17.6",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# @backstage/config-loader
|
||||
|
||||
## 0.6.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 82c66b8cd: Fix bug where `${...}` was not being escaped to `${...}`
|
||||
|
||||
Add support for environment variable substitution in `$include`, `$file` and
|
||||
`$env` transform values.
|
||||
|
||||
- This change allows for including dynamic paths, such as environment specific
|
||||
secrets by using the same environment variable substitution (`${..}`) already
|
||||
supported outside of the various include transforms.
|
||||
- If you are currently using the syntax `${...}` in your include transform values,
|
||||
you will need to escape the substitution by using `${...}` instead to maintain
|
||||
the same behavior.
|
||||
|
||||
## 0.5.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/config-loader",
|
||||
"description": "Config loading functionality used by Backstage backend, and CLI",
|
||||
"version": "0.5.1",
|
||||
"version": "0.6.0",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
# @backstage/core-api
|
||||
|
||||
## 0.2.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 1279a3325: Introduce a `load-chunk` step in the `BootErrorPage` to show make chunk loading
|
||||
errors visible to the user.
|
||||
- 4a4681b1b: Improved error messaging for routable extension errors, making it easier to identify the component and mount point that caused the error.
|
||||
- b051e770c: Fixed a bug with `useRouteRef` where navigating from routes beneath a mount point would often fail.
|
||||
|
||||
## 0.2.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/core-api",
|
||||
"description": "Internal Core API used by Backstage plugins and apps",
|
||||
"version": "0.2.15",
|
||||
"version": "0.2.16",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
@@ -42,8 +42,8 @@
|
||||
"zen-observable": "^0.8.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.6",
|
||||
"@backstage/test-utils": "^0.1.9",
|
||||
"@backstage/cli": "^0.6.7",
|
||||
"@backstage/test-utils": "^0.1.10",
|
||||
"@backstage/test-utils-core": "^0.1.1",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^11.2.5",
|
||||
|
||||
@@ -24,7 +24,7 @@ import { AppConfig } from '@backstage/config';
|
||||
import { SubRouteRef } from '../routing/types';
|
||||
|
||||
export type BootErrorPageProps = {
|
||||
step: 'load-config';
|
||||
step: 'load-config' | 'load-chunk';
|
||||
error: Error;
|
||||
};
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user