Merge remote-tracking branch 'origin/master' into erikengervall/plugin-release-manager-as-a-service
This commit is contained in:
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-sonarqube': patch
|
||||
---
|
||||
|
||||
Export isSonarQubeAvailable.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend': patch
|
||||
---
|
||||
|
||||
Add built-in publish action for creating GitHub pull requests.
|
||||
@@ -1,30 +0,0 @@
|
||||
---
|
||||
'@backstage/create-app': patch
|
||||
---
|
||||
|
||||
Fixing dependency resolution for problematic library `graphql-language-service-interface`.
|
||||
|
||||
This change might not have to be applied to your local installation, however if you run into this error:
|
||||
|
||||
```
|
||||
Error: Failed to compile.
|
||||
/tmp/backstage-e2e-uMeycm/test-app/node_modules/graphql-language-service-interface/esm/GraphQLLanguageService.js 100:23
|
||||
Module parse failed: Unexpected token (100:23)
|
||||
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
|
||||
| }
|
||||
| let customRules = null;
|
||||
> if (extensions?.customValidationRules &&
|
||||
| typeof extensions.customValidationRules === 'function') {
|
||||
| customRules = extensions.customValidationRules(this._graphQLConfig);
|
||||
```
|
||||
|
||||
You can fix it by adding the following to the root `package.json`.
|
||||
|
||||
```json
|
||||
...
|
||||
"resolutions": {
|
||||
"graphql-language-service-interface": "2.8.2",
|
||||
"graphql-language-service-parser": "1.9.0"
|
||||
},
|
||||
...
|
||||
```
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend': patch
|
||||
---
|
||||
|
||||
Fix for the `file://` protocol check in the `FilePreparer` being too strict, breaking Windows.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend': patch
|
||||
---
|
||||
|
||||
Add support for configure an LDAP query filter on multiple lines.
|
||||
@@ -1,31 +0,0 @@
|
||||
---
|
||||
'@backstage/create-app': patch
|
||||
'@backstage/plugin-scaffolder': minor
|
||||
---
|
||||
|
||||
Expose the catalog-import route as an external route from the scaffolder.
|
||||
|
||||
This will make it possible to hide the "Register Existing Component" button
|
||||
when you for example are running backstage with `catalog.readonly=true`.
|
||||
|
||||
As a consequence of this change you need add a new binding to your createApp call to
|
||||
keep the button visible. However, if you instead want to hide the button you can safely
|
||||
ignore the following example.
|
||||
|
||||
To bind the external route from the catalog-import plugin to the scaffolder template
|
||||
index page, make sure you have the appropriate imports and add the following
|
||||
to the createApp call:
|
||||
|
||||
```typescript
|
||||
import { catalogImportPlugin } from '@backstage/plugin-catalog-import';
|
||||
|
||||
const app = createApp({
|
||||
// ...
|
||||
bindRoutes({ bind }) {
|
||||
// ...
|
||||
bind(scaffolderPlugin.externalRoutes, {
|
||||
registerComponent: catalogImportPlugin.routes.importPage,
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/core': patch
|
||||
---
|
||||
|
||||
Exported SignInProviderConfig to strongly type SignInPage providers
|
||||
@@ -1,14 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend': patch
|
||||
---
|
||||
|
||||
Bitbucket server needs username to be set as well as the token or appPassword for the publishing process to work.
|
||||
|
||||
```yaml
|
||||
integrations:
|
||||
bitbucket:
|
||||
- host: bitbucket.mycompany.com
|
||||
apiBaseUrl: https://bitbucket.mycompany.com/rest/api/1.0
|
||||
token: token
|
||||
username: username
|
||||
```
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
'@backstage/catalog-model': patch
|
||||
'@backstage/plugin-catalog-backend': patch
|
||||
'@backstage/plugin-catalog': patch
|
||||
---
|
||||
|
||||
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.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-techdocs-backend': patch
|
||||
---
|
||||
|
||||
Change the response status of metadata endpoints in case a documentation is not
|
||||
available to `404 NOT FOUND`. This also introduces the JSON based error messages
|
||||
used by other backends.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend': patch
|
||||
---
|
||||
|
||||
Adds a collaborator field to GitHub publish action for multiple users and access levels
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-techdocs': patch
|
||||
---
|
||||
|
||||
Add borders to TechDocs tables and increase font size. Fixes #5264 and #5276.
|
||||
@@ -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,6 +0,0 @@
|
||||
---
|
||||
'@backstage/core': patch
|
||||
'@backstage/plugin-api-docs': patch
|
||||
---
|
||||
|
||||
add debounce time attribute for apis-docs for search, giving more time to the users when they are typing.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/core': patch
|
||||
---
|
||||
|
||||
exposes undocumented `PageTheme`
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/core': patch
|
||||
---
|
||||
|
||||
Don't use a drag & drop cursor when clicking on disabled `IconLinkVertical`.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend': patch
|
||||
---
|
||||
|
||||
Fix bug in pull request sample template.
|
||||
@@ -114,6 +114,7 @@ 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
|
||||
|
||||
+56
-112
@@ -40,47 +40,40 @@ proxy:
|
||||
'/circleci/api':
|
||||
target: https://circleci.com/api/v1.1
|
||||
headers:
|
||||
Circle-Token:
|
||||
$env: CIRCLECI_AUTH_TOKEN
|
||||
Circle-Token: ${CIRCLECI_AUTH_TOKEN}
|
||||
|
||||
'/jenkins/api':
|
||||
target: http://localhost:8080
|
||||
headers:
|
||||
Authorization:
|
||||
$env: JENKINS_BASIC_AUTH_HEADER
|
||||
Authorization: ${JENKINS_BASIC_AUTH_HEADER}
|
||||
|
||||
'/travisci/api':
|
||||
target: https://api.travis-ci.com
|
||||
changeOrigin: true
|
||||
headers:
|
||||
Authorization:
|
||||
$env: TRAVISCI_AUTH_TOKEN
|
||||
Authorization: ${TRAVISCI_AUTH_TOKEN}
|
||||
travis-api-version: '3'
|
||||
|
||||
'/newrelic/apm/api':
|
||||
target: https://api.newrelic.com/v2
|
||||
headers:
|
||||
X-Api-Key:
|
||||
$env: NEW_RELIC_REST_API_KEY
|
||||
X-Api-Key: ${NEW_RELIC_REST_API_KEY}
|
||||
|
||||
'/pagerduty':
|
||||
target: https://api.pagerduty.com
|
||||
headers:
|
||||
Authorization:
|
||||
$env: PAGERDUTY_TOKEN
|
||||
Authorization: ${PAGERDUTY_TOKEN}
|
||||
|
||||
'/buildkite/api':
|
||||
target: https://api.buildkite.com/v2/
|
||||
headers:
|
||||
Authorization:
|
||||
$env: BUILDKITE_TOKEN
|
||||
Authorization: ${BUILDKITE_TOKEN}
|
||||
|
||||
'/sentry/api':
|
||||
target: https://sentry.io/api/
|
||||
allowedMethods: ['GET']
|
||||
headers:
|
||||
Authorization:
|
||||
$env: SENTRY_TOKEN
|
||||
Authorization: ${SENTRY_TOKEN}
|
||||
|
||||
organization:
|
||||
name: My Company
|
||||
@@ -124,36 +117,28 @@ kafka:
|
||||
integrations:
|
||||
github:
|
||||
- host: github.com
|
||||
token:
|
||||
$env: GITHUB_TOKEN
|
||||
token: ${GITHUB_TOKEN}
|
||||
### Example for how to add your GitHub Enterprise instance using the API:
|
||||
# - host: ghe.example.net
|
||||
# apiBaseUrl: https://ghe.example.net/api/v3
|
||||
# token:
|
||||
# $env: GHE_TOKEN
|
||||
# token: ${GHE_TOKEN}
|
||||
### Example for how to add your GitHub Enterprise instance using raw HTTP fetches (token is optional):
|
||||
# - host: ghe.example.net
|
||||
# rawBaseUrl: https://ghe.example.net/raw
|
||||
# token:
|
||||
# $env: GHE_TOKEN
|
||||
# token: ${GHE_TOKEN}
|
||||
gitlab:
|
||||
- host: gitlab.com
|
||||
token:
|
||||
$env: GITLAB_TOKEN
|
||||
token: ${GITLAB_TOKEN}
|
||||
bitbucket:
|
||||
- host: bitbucket.org
|
||||
username:
|
||||
$env: BITBUCKET_USERNAME
|
||||
appPassword:
|
||||
$env: BITBUCKET_APP_PASSWORD
|
||||
username: ${BITBUCKET_USERNAME}
|
||||
appPassword: ${BITBUCKET_APP_PASSWORD}
|
||||
azure:
|
||||
- host: dev.azure.com
|
||||
token:
|
||||
$env: AZURE_TOKEN
|
||||
token: ${AZURE_TOKEN}
|
||||
# googleGcs:
|
||||
# clientEmail: 'example@example.com'
|
||||
# privateKey:
|
||||
# $env: GCS_PRIVATE_KEY
|
||||
# privateKey: ${GCS_PRIVATE_KEY}
|
||||
|
||||
catalog:
|
||||
rules:
|
||||
@@ -172,21 +157,18 @@ catalog:
|
||||
githubOrg:
|
||||
providers:
|
||||
- target: https://github.com
|
||||
token:
|
||||
$env: GITHUB_TOKEN
|
||||
token: ${GITHUB_TOKEN}
|
||||
#### Example for how to add your GitHub Enterprise instance using the API:
|
||||
# - target: https://ghe.example.net
|
||||
# apiBaseUrl: https://ghe.example.net/api
|
||||
# token:
|
||||
# $env: GHE_TOKEN
|
||||
# token: ${GHE_TOKEN}
|
||||
ldapOrg:
|
||||
### Example for how to add your enterprise LDAP server
|
||||
# providers:
|
||||
# - target: ldaps://ds.example.net
|
||||
# bind:
|
||||
# dn: uid=ldap-reader-user,ou=people,ou=example,dc=example,dc=net
|
||||
# secret:
|
||||
# $env: LDAP_SECRET
|
||||
# secret: ${LDAP_SECRET}
|
||||
# users:
|
||||
# dn: ou=people,ou=example,dc=example,dc=net
|
||||
# options:
|
||||
@@ -202,12 +184,9 @@ catalog:
|
||||
#providers:
|
||||
# - target: https://graph.microsoft.com/v1.0
|
||||
# authority: https://login.microsoftonline.com
|
||||
# tenantId:
|
||||
# $env: MICROSOFT_GRAPH_TENANT_ID
|
||||
# clientId:
|
||||
# $env: MICROSOFT_GRAPH_CLIENT_ID
|
||||
# clientSecret:
|
||||
# $env: MICROSOFT_GRAPH_CLIENT_SECRET_TOKEN
|
||||
# tenantId: ${MICROSOFT_GRAPH_TENANT_ID}
|
||||
# clientId: ${MICROSOFT_GRAPH_CLIENT_ID}
|
||||
# clientSecret: ${MICROSOFT_GRAPH_CLIENT_SECRET_TOKEN}
|
||||
# userFilter: accountEnabled eq true and userType eq 'member'
|
||||
# groupFilter: securityEnabled eq false and mailEnabled eq true and groupTypes/any(c:c+eq+'Unified')
|
||||
|
||||
@@ -255,27 +234,22 @@ catalog:
|
||||
|
||||
scaffolder:
|
||||
github:
|
||||
token:
|
||||
$env: GITHUB_TOKEN
|
||||
token: ${GITHUB_TOKEN}
|
||||
visibility: public # or 'internal' or 'private'
|
||||
gitlab:
|
||||
api:
|
||||
baseUrl: https://gitlab.com
|
||||
token:
|
||||
$env: GITLAB_TOKEN
|
||||
token: ${GITLAB_TOKEN}
|
||||
visibility: public # or 'internal' or 'private'
|
||||
azure:
|
||||
baseUrl: https://dev.azure.com/{your-organization}
|
||||
api:
|
||||
token:
|
||||
$env: AZURE_TOKEN
|
||||
token: ${AZURE_TOKEN}
|
||||
bitbucket:
|
||||
api:
|
||||
host: https://bitbucket.org
|
||||
username:
|
||||
$env: BITBUCKET_USERNAME
|
||||
token:
|
||||
$env: BITBUCKET_TOKEN
|
||||
username: ${BITBUCKET_USERNAME}
|
||||
token: ${BITBUCKET_TOKEN}
|
||||
visibility: public # or or 'private'
|
||||
|
||||
auth:
|
||||
@@ -286,89 +260,59 @@ auth:
|
||||
providers:
|
||||
google:
|
||||
development:
|
||||
clientId:
|
||||
$env: AUTH_GOOGLE_CLIENT_ID
|
||||
clientSecret:
|
||||
$env: AUTH_GOOGLE_CLIENT_SECRET
|
||||
clientId: ${AUTH_GOOGLE_CLIENT_ID}
|
||||
clientSecret: ${AUTH_GOOGLE_CLIENT_SECRET}
|
||||
github:
|
||||
development:
|
||||
clientId:
|
||||
$env: AUTH_GITHUB_CLIENT_ID
|
||||
clientSecret:
|
||||
$env: AUTH_GITHUB_CLIENT_SECRET
|
||||
enterpriseInstanceUrl:
|
||||
$env: AUTH_GITHUB_ENTERPRISE_INSTANCE_URL
|
||||
clientId: ${AUTH_GITHUB_CLIENT_ID}
|
||||
clientSecret: ${AUTH_GITHUB_CLIENT_SECRET}
|
||||
enterpriseInstanceUrl: ${AUTH_GITHUB_ENTERPRISE_INSTANCE_URL}
|
||||
gitlab:
|
||||
development:
|
||||
clientId:
|
||||
$env: AUTH_GITLAB_CLIENT_ID
|
||||
clientSecret:
|
||||
$env: AUTH_GITLAB_CLIENT_SECRET
|
||||
audience:
|
||||
$env: GITLAB_BASE_URL
|
||||
clientId: ${AUTH_GITLAB_CLIENT_ID}
|
||||
clientSecret: ${AUTH_GITLAB_CLIENT_SECRET}
|
||||
audience: ${GITLAB_BASE_URL}
|
||||
saml:
|
||||
entryPoint: 'http://localhost:7001/'
|
||||
issuer: 'passport-saml'
|
||||
okta:
|
||||
development:
|
||||
clientId:
|
||||
$env: AUTH_OKTA_CLIENT_ID
|
||||
clientSecret:
|
||||
$env: AUTH_OKTA_CLIENT_SECRET
|
||||
audience:
|
||||
$env: AUTH_OKTA_AUDIENCE
|
||||
clientId: ${AUTH_OKTA_CLIENT_ID}
|
||||
clientSecret: ${AUTH_OKTA_CLIENT_SECRET}
|
||||
audience: ${AUTH_OKTA_AUDIENCE}
|
||||
oauth2:
|
||||
development:
|
||||
clientId:
|
||||
$env: AUTH_OAUTH2_CLIENT_ID
|
||||
clientSecret:
|
||||
$env: AUTH_OAUTH2_CLIENT_SECRET
|
||||
authorizationUrl:
|
||||
$env: AUTH_OAUTH2_AUTH_URL
|
||||
tokenUrl:
|
||||
$env: AUTH_OAUTH2_TOKEN_URL
|
||||
clientId: ${AUTH_OAUTH2_CLIENT_ID}
|
||||
clientSecret: ${AUTH_OAUTH2_CLIENT_SECRET}
|
||||
authorizationUrl: ${AUTH_OAUTH2_AUTH_URL}
|
||||
tokenUrl: ${AUTH_OAUTH2_TOKEN_URL}
|
||||
###
|
||||
# provide a list of scopes as needed for your OAuth2 Server:
|
||||
#
|
||||
# scope: saml-login-selector openid profile email
|
||||
oidc:
|
||||
development:
|
||||
metadataUrl:
|
||||
$env: AUTH_OIDC_METADATA_URL
|
||||
clientId:
|
||||
$env: AUTH_OIDC_CLIENT_ID
|
||||
clientSecret:
|
||||
$env: AUTH_OIDC_CLIENT_SECRET
|
||||
authorizationUrl:
|
||||
$env: AUTH_OIDC_AUTH_URL
|
||||
tokenUrl:
|
||||
$env: AUTH_OIDC_TOKEN_URL
|
||||
tokenSignedResponseAlg:
|
||||
$env: AUTH_OIDC_TOKEN_SIGNED_RESPONSE_ALG
|
||||
metadataUrl: ${AUTH_OIDC_METADATA_URL}
|
||||
clientId: ${AUTH_OIDC_CLIENT_ID}
|
||||
clientSecret: ${AUTH_OIDC_CLIENT_SECRET}
|
||||
authorizationUrl: ${AUTH_OIDC_AUTH_URL}
|
||||
tokenUrl: ${AUTH_OIDC_TOKEN_URL}
|
||||
tokenSignedResponseAlg: ${AUTH_OIDC_TOKEN_SIGNED_RESPONSE_ALG}
|
||||
auth0:
|
||||
development:
|
||||
clientId:
|
||||
$env: AUTH_AUTH0_CLIENT_ID
|
||||
clientSecret:
|
||||
$env: AUTH_AUTH0_CLIENT_SECRET
|
||||
domain:
|
||||
$env: AUTH_AUTH0_DOMAIN
|
||||
clientId: ${AUTH_AUTH0_CLIENT_ID}
|
||||
clientSecret: ${AUTH_AUTH0_CLIENT_SECRET}
|
||||
domain: ${AUTH_AUTH0_DOMAIN}
|
||||
microsoft:
|
||||
development:
|
||||
clientId:
|
||||
$env: AUTH_MICROSOFT_CLIENT_ID
|
||||
clientSecret:
|
||||
$env: AUTH_MICROSOFT_CLIENT_SECRET
|
||||
tenantId:
|
||||
$env: AUTH_MICROSOFT_TENANT_ID
|
||||
clientId: ${AUTH_MICROSOFT_CLIENT_ID}
|
||||
clientSecret: ${AUTH_MICROSOFT_CLIENT_SECRET}
|
||||
tenantId: ${AUTH_MICROSOFT_TENANT_ID}
|
||||
onelogin:
|
||||
development:
|
||||
clientId:
|
||||
$env: AUTH_ONELOGIN_CLIENT_ID
|
||||
clientSecret:
|
||||
$env: AUTH_ONELOGIN_CLIENT_SECRET
|
||||
issuer:
|
||||
$env: AUTH_ONELOGIN_ISSUER
|
||||
clientId: ${AUTH_ONELOGIN_CLIENT_ID}
|
||||
clientSecret: ${AUTH_ONELOGIN_CLIENT_SECRET}
|
||||
issuer: ${AUTH_ONELOGIN_ISSUER}
|
||||
costInsights:
|
||||
engineerCost: 200000
|
||||
products:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
+3
-3
@@ -58,7 +58,7 @@ the local `auth.environment` setting will be selected.
|
||||
After configuring an authentication provider, the `app` frontend package needs a
|
||||
small update to show this provider as a login option. The `SignInPage` component
|
||||
handles this, and takes either a `provider` or `providers` (array) prop of
|
||||
`SignInConfig` definitions.
|
||||
`SignInProviderConfig` definitions.
|
||||
|
||||
These reference the [ApiRef](../reference/utility-apis/README.md) exported by
|
||||
the provider. Again, an example using GitHub that can be adapted to any of the
|
||||
@@ -66,9 +66,9 @@ built-in providers:
|
||||
|
||||
```diff
|
||||
# packages/app/src/App.tsx
|
||||
+ import { githubAuthApiRef, SignInConfig, SignInPage } from '@backstage/core';
|
||||
+ import { githubAuthApiRef, SignInProviderConfig, SignInPage } from '@backstage/core';
|
||||
|
||||
+ const githubProvider: SignInConfig = {
|
||||
+ const githubProvider: SignInProviderConfig = {
|
||||
+ id: 'github-auth-provider',
|
||||
+ title: 'GitHub',
|
||||
+ message: 'Sign in using GitHub',
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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!**
|
||||
|
||||
@@ -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,8 +67,7 @@ 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
|
||||
|
||||
@@ -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!
|
||||
@@ -24,10 +24,8 @@ Explicit credentials can be set in the following format:
|
||||
```yaml
|
||||
integrations:
|
||||
googleGcs:
|
||||
clientEmail:
|
||||
$env: GCS_CLIENT_EMAIL
|
||||
privateKey:
|
||||
$env: GCS_PRIVATE_KEY
|
||||
clientEmail: ${GCS_CLIENT_EMAIL}
|
||||
privateKey: ${GCS_PRIVATE_KEY}
|
||||
```
|
||||
|
||||
Then make sure the environment variables `GCS_CLIENT_EMAIL` and
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
+6
-10
@@ -22,16 +22,6 @@
|
||||
"getting-started/app-custom-theme"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "subcategory",
|
||||
"label": "Deployment",
|
||||
"ids": [
|
||||
"getting-started/deployment-docker",
|
||||
"getting-started/deployment-k8s",
|
||||
"getting-started/deployment-helm",
|
||||
"getting-started/deployment-other"
|
||||
]
|
||||
},
|
||||
"getting-started/keeping-backstage-updated",
|
||||
"getting-started/concepts",
|
||||
"getting-started/contributors"
|
||||
@@ -211,6 +201,12 @@
|
||||
"auth/auth-backend-classes",
|
||||
"auth/glossary"
|
||||
],
|
||||
"Deployment": [
|
||||
"deployment/index",
|
||||
"deployment/k8s",
|
||||
"deployment/helm",
|
||||
"deployment/heroku"
|
||||
],
|
||||
"Designing for Backstage": [
|
||||
"dls/design",
|
||||
"dls/contributing-to-storybook",
|
||||
|
||||
+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"
|
||||
|
||||
|
||||
@@ -1,5 +1,30 @@
|
||||
# example-app
|
||||
|
||||
## 0.2.24
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [a360f9478]
|
||||
- Updated dependencies [bb5055aee]
|
||||
- Updated dependencies [d0d1c2f7b]
|
||||
- Updated dependencies [5d0740563]
|
||||
- Updated dependencies [12390778e]
|
||||
- Updated dependencies [fef852ecd]
|
||||
- Updated dependencies [18f7345a6]
|
||||
- Updated dependencies [5cafcf452]
|
||||
- Updated dependencies [86a95ba67]
|
||||
- Updated dependencies [e27cb6c45]
|
||||
- Updated dependencies [184b02bef]
|
||||
- Updated dependencies [60ce64aa2]
|
||||
- @backstage/plugin-scaffolder@0.9.0
|
||||
- @backstage/catalog-model@0.7.7
|
||||
- @backstage/core@0.7.5
|
||||
- @backstage/plugin-catalog@0.5.4
|
||||
- @backstage/plugin-api-docs@0.4.11
|
||||
- @backstage/plugin-techdocs@0.7.2
|
||||
- @backstage/plugin-tech-radar@0.3.9
|
||||
- @backstage/cli@0.6.8
|
||||
|
||||
## 0.2.23
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"name": "example-app",
|
||||
"version": "0.2.23",
|
||||
"version": "0.2.24",
|
||||
"private": true,
|
||||
"bundled": true,
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.7.6",
|
||||
"@backstage/cli": "^0.6.7",
|
||||
"@backstage/core": "^0.7.4",
|
||||
"@backstage/catalog-model": "^0.7.7",
|
||||
"@backstage/cli": "^0.6.8",
|
||||
"@backstage/core": "^0.7.5",
|
||||
"@backstage/integration-react": "^0.1.1",
|
||||
"@backstage/plugin-api-docs": "^0.4.9",
|
||||
"@backstage/plugin-api-docs": "^0.4.11",
|
||||
"@backstage/plugin-badges": "^0.2.0",
|
||||
"@backstage/plugin-catalog": "^0.5.3",
|
||||
"@backstage/plugin-catalog": "^0.5.4",
|
||||
"@backstage/plugin-catalog-import": "^0.5.2",
|
||||
"@backstage/plugin-catalog-react": "^0.1.3",
|
||||
"@backstage/plugin-circleci": "^0.2.12",
|
||||
@@ -32,11 +32,11 @@
|
||||
"@backstage/plugin-register-component": "^0.2.12",
|
||||
"@backstage/plugin-github-release-manager": "^0.1.1",
|
||||
"@backstage/plugin-rollbar": "^0.3.3",
|
||||
"@backstage/plugin-scaffolder": "^0.8.2",
|
||||
"@backstage/plugin-scaffolder": "^0.9.0",
|
||||
"@backstage/plugin-search": "^0.3.4",
|
||||
"@backstage/plugin-sentry": "^0.3.8",
|
||||
"@backstage/plugin-tech-radar": "^0.3.8",
|
||||
"@backstage/plugin-techdocs": "^0.7.0",
|
||||
"@backstage/plugin-tech-radar": "^0.3.9",
|
||||
"@backstage/plugin-techdocs": "^0.7.2",
|
||||
"@backstage/plugin-todo": "^0.1.0",
|
||||
"@backstage/plugin-user-settings": "^0.2.8",
|
||||
"@backstage/theme": "^0.2.5",
|
||||
|
||||
@@ -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,18 @@
|
||||
# @backstage/catalog-model
|
||||
|
||||
## 0.7.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- bb5055aee: Add getEntitySourceLocation helper
|
||||
- 5d0740563: Implemented missing support for the dependsOn/dependencyOf relationships
|
||||
between `Component` and `Resource` catalog model objects.
|
||||
|
||||
Added support for generating the relevant relationships to the
|
||||
`BuiltinKindsEntityProcessor`, and added simple support for fetching
|
||||
relationships between `Components` and `Resources` for rendering in the
|
||||
system diagram. All catalog-model changes backwards compatible.
|
||||
|
||||
## 0.7.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/catalog-model",
|
||||
"version": "0.7.6",
|
||||
"version": "0.7.7",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -39,7 +39,7 @@
|
||||
"yup": "^0.29.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.7",
|
||||
"@backstage/cli": "^0.6.8",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/jest": "^26.0.7",
|
||||
"@types/lodash": "^4.14.151",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @backstage/cli
|
||||
|
||||
## 0.6.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 60ce64aa2: Disable hot reloading in CI environments.
|
||||
|
||||
## 0.6.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/cli",
|
||||
"description": "CLI for developing Backstage plugins and apps",
|
||||
"version": "0.6.7",
|
||||
"version": "0.6.8",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
@@ -118,7 +118,7 @@
|
||||
"devDependencies": {
|
||||
"@backstage/backend-common": "^0.6.2",
|
||||
"@backstage/config": "^0.1.4",
|
||||
"@backstage/core": "^0.7.4",
|
||||
"@backstage/core": "^0.7.5",
|
||||
"@backstage/dev-utils": "^0.1.13",
|
||||
"@backstage/test-utils": "^0.1.10",
|
||||
"@backstage/theme": "^0.2.5",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
# @backstage/core
|
||||
|
||||
## 0.7.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- d0d1c2f7b: Pass `inverse` prop to Gauge from GaugeCard
|
||||
- 5cafcf452: add debounce time attribute for apis-docs for search, giving more time to the users when they are typing.
|
||||
- 86a95ba67: exposes undocumented `PageTheme`
|
||||
- e27cb6c45: Don't use a drag & drop cursor when clicking on disabled `IconLinkVertical`.
|
||||
|
||||
## 0.7.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/core",
|
||||
"description": "Core API used by Backstage plugins and apps",
|
||||
"version": "0.7.4",
|
||||
"version": "0.7.5",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
@@ -69,7 +69,7 @@
|
||||
"zen-observable": "^0.8.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.7",
|
||||
"@backstage/cli": "^0.6.8",
|
||||
"@backstage/test-utils": "^0.1.10",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^11.2.5",
|
||||
|
||||
@@ -45,6 +45,9 @@ export const Default = () => (
|
||||
<Grid item>
|
||||
<GaugeCard title="Progress" progress={0.89} />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<GaugeCard title="Progress" inverse progress={0.2} />
|
||||
</Grid>
|
||||
</Wrapper>
|
||||
);
|
||||
|
||||
@@ -67,6 +70,14 @@ export const Subhead = () => (
|
||||
progress={0.89}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<GaugeCard
|
||||
title="Progress"
|
||||
subheader="With a subheader"
|
||||
inverse
|
||||
progress={0.2}
|
||||
/>
|
||||
</Grid>
|
||||
</Wrapper>
|
||||
);
|
||||
|
||||
@@ -81,5 +92,8 @@ export const LinkInFooter = () => (
|
||||
<Grid item>
|
||||
<GaugeCard title="Progress" deepLink={linkInfo} progress={0.89} />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<GaugeCard title="Progress" deepLink={linkInfo} inverse progress={0.2} />
|
||||
</Grid>
|
||||
</Wrapper>
|
||||
);
|
||||
|
||||
@@ -26,6 +26,7 @@ type Props = {
|
||||
variant?: InfoCardVariants;
|
||||
/** Progress in % specified as decimal, e.g. "0.23" */
|
||||
progress: number;
|
||||
inverse?: boolean;
|
||||
deepLink?: BottomLinkProps;
|
||||
};
|
||||
|
||||
@@ -38,7 +39,7 @@ const useStyles = makeStyles({
|
||||
|
||||
export const GaugeCard = (props: Props) => {
|
||||
const classes = useStyles(props);
|
||||
const { title, subheader, progress, deepLink, variant } = props;
|
||||
const { title, subheader, progress, inverse, deepLink, variant } = props;
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
@@ -48,7 +49,7 @@ export const GaugeCard = (props: Props) => {
|
||||
deepLink={deepLink}
|
||||
variant={variant}
|
||||
>
|
||||
<Gauge value={progress} />
|
||||
<Gauge value={progress} inverse={inverse} />
|
||||
</InfoCard>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -53,38 +53,6 @@ export const Apis = () => (
|
||||
</Page>
|
||||
);
|
||||
|
||||
export const Grpc = () => (
|
||||
<Page themeId="grpc">
|
||||
<Header title="Grpc catalogue" type="tool">
|
||||
{labels}
|
||||
</Header>
|
||||
</Page>
|
||||
);
|
||||
|
||||
export const AsyncApi = () => (
|
||||
<Page themeId="asyncapi">
|
||||
<Header title="Async API catalogue" type="tool">
|
||||
{labels}
|
||||
</Header>
|
||||
</Page>
|
||||
);
|
||||
|
||||
export const Graphql = () => (
|
||||
<Page themeId="graphql">
|
||||
<Header title="GraphQL API catalogue" type="tool">
|
||||
{labels}
|
||||
</Header>
|
||||
</Page>
|
||||
);
|
||||
|
||||
export const OpenApi = () => (
|
||||
<Page themeId="openapi">
|
||||
<Header title="OpenAPI catalogue" type="tool">
|
||||
{labels}
|
||||
</Header>
|
||||
</Page>
|
||||
);
|
||||
|
||||
export const Tool = () => (
|
||||
<Page themeId="tool">
|
||||
<Header title="Stand-alone tool" type="tool">
|
||||
|
||||
@@ -22,7 +22,7 @@ import { ContentHeader } from '../ContentHeader/ContentHeader';
|
||||
import { Grid, Button, Typography } from '@material-ui/core';
|
||||
import { SignInPageProps, useApi, configApiRef } from '@backstage/core-api';
|
||||
import { useSignInProviders, getSignInProviders } from './providers';
|
||||
import { IdentityProviders, SignInConfig } from './types';
|
||||
import { IdentityProviders, SignInProviderConfig } from './types';
|
||||
import { Progress } from '../../components/Progress';
|
||||
import { GridItem, useStyles } from './styles';
|
||||
import { InfoCard } from '../InfoCard';
|
||||
@@ -34,7 +34,7 @@ type MultiSignInPageProps = SignInPageProps & {
|
||||
};
|
||||
|
||||
type SingleSignInPageProps = SignInPageProps & {
|
||||
provider: SignInConfig;
|
||||
provider: SignInProviderConfig;
|
||||
auto?: boolean;
|
||||
};
|
||||
|
||||
|
||||
@@ -21,13 +21,13 @@ import {
|
||||
ProviderComponent,
|
||||
ProviderLoader,
|
||||
SignInProvider,
|
||||
SignInConfig,
|
||||
SignInProviderConfig,
|
||||
} from './types';
|
||||
import { useApi, errorApiRef } from '@backstage/core-api';
|
||||
import { GridItem } from './styles';
|
||||
|
||||
const Component: ProviderComponent = ({ config, onResult }) => {
|
||||
const { apiRef, title, message } = config as SignInConfig;
|
||||
const { apiRef, title, message } = config as SignInProviderConfig;
|
||||
const authApi = useApi(apiRef);
|
||||
const errorApi = useApi(errorApiRef);
|
||||
|
||||
|
||||
@@ -14,4 +14,5 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export type { SignInProviderConfig } from './types';
|
||||
export { SignInPage } from './SignInPage';
|
||||
|
||||
@@ -22,7 +22,11 @@ import {
|
||||
useApiHolder,
|
||||
errorApiRef,
|
||||
} from '@backstage/core-api';
|
||||
import { SignInConfig, IdentityProviders, SignInProvider } from './types';
|
||||
import {
|
||||
IdentityProviders,
|
||||
SignInProvider,
|
||||
SignInProviderConfig,
|
||||
} from './types';
|
||||
import { commonProvider } from './commonProvider';
|
||||
import { guestProvider } from './guestProvider';
|
||||
import { customProvider } from './customProvider';
|
||||
@@ -33,7 +37,7 @@ export type SignInProviderType = {
|
||||
[key: string]: {
|
||||
components: SignInProvider;
|
||||
id: string;
|
||||
config?: SignInConfig;
|
||||
config?: SignInProviderConfig;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -62,7 +66,7 @@ export function getSignInProviders(
|
||||
return acc;
|
||||
}
|
||||
|
||||
const { id } = config as SignInConfig;
|
||||
const { id } = config as SignInProviderConfig;
|
||||
validateIDs(id, acc);
|
||||
|
||||
acc[id] = { components: signInProviders.common, id, config };
|
||||
|
||||
@@ -25,17 +25,17 @@ import {
|
||||
SessionApi,
|
||||
} from '@backstage/core-api';
|
||||
|
||||
export type SignInConfig = {
|
||||
export type SignInProviderConfig = {
|
||||
id: string;
|
||||
title: string;
|
||||
message: string;
|
||||
apiRef: ApiRef<ProfileInfoApi & BackstageIdentityApi & SessionApi>;
|
||||
};
|
||||
|
||||
export type IdentityProviders = ('guest' | 'custom' | SignInConfig)[];
|
||||
export type IdentityProviders = ('guest' | 'custom' | SignInProviderConfig)[];
|
||||
|
||||
export type ProviderComponent = ComponentType<
|
||||
SignInPageProps & { config: SignInConfig }
|
||||
SignInPageProps & { config: SignInProviderConfig }
|
||||
>;
|
||||
|
||||
export type ProviderLoader = (
|
||||
|
||||
@@ -1,5 +1,128 @@
|
||||
# @backstage/create-app
|
||||
|
||||
## 0.3.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- b49a525ab: Fixing dependency resolution for problematic library `graphql-language-service-interface`.
|
||||
|
||||
This change might not have to be applied to your local installation, however if you run into this error:
|
||||
|
||||
```
|
||||
Error: Failed to compile.
|
||||
/tmp/backstage-e2e-uMeycm/test-app/node_modules/graphql-language-service-interface/esm/GraphQLLanguageService.js 100:23
|
||||
Module parse failed: Unexpected token (100:23)
|
||||
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
|
||||
| }
|
||||
| let customRules = null;
|
||||
> if (extensions?.customValidationRules &&
|
||||
| typeof extensions.customValidationRules === 'function') {
|
||||
| customRules = extensions.customValidationRules(this._graphQLConfig);
|
||||
```
|
||||
|
||||
You can fix it by adding the following to the root `package.json`.
|
||||
|
||||
```json
|
||||
...
|
||||
"resolutions": {
|
||||
"graphql-language-service-interface": "2.8.2",
|
||||
"graphql-language-service-parser": "1.9.0"
|
||||
},
|
||||
...
|
||||
```
|
||||
|
||||
- a360f9478: Expose the catalog-import route as an external route from the scaffolder.
|
||||
|
||||
This will make it possible to hide the "Register Existing Component" button
|
||||
when you for example are running backstage with `catalog.readonly=true`.
|
||||
|
||||
As a consequence of this change you need add a new binding to your createApp call to
|
||||
keep the button visible. However, if you instead want to hide the button you can safely
|
||||
ignore the following example.
|
||||
|
||||
To bind the external route from the catalog-import plugin to the scaffolder template
|
||||
index page, make sure you have the appropriate imports and add the following
|
||||
to the createApp call:
|
||||
|
||||
```typescript
|
||||
import { catalogImportPlugin } from '@backstage/plugin-catalog-import';
|
||||
|
||||
const app = createApp({
|
||||
// ...
|
||||
bindRoutes({ bind }) {
|
||||
// ...
|
||||
bind(scaffolderPlugin.externalRoutes, {
|
||||
registerComponent: catalogImportPlugin.routes.importPage,
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
- f1952337c: Due to a change in the techdocs publishers, they don't check if they are able to reach e.g. the configured S3 bucket anymore.
|
||||
This can be added again by the following change. Note that the backend process will no longer exit when it is not reachable but will only emit an error log message.
|
||||
You should include the check when your backend to get early feedback about a potential misconfiguration:
|
||||
|
||||
```diff
|
||||
// packages/backend/src/plugins/techdocs.ts
|
||||
|
||||
export default async function createPlugin({
|
||||
logger,
|
||||
config,
|
||||
discovery,
|
||||
reader,
|
||||
}: PluginEnvironment): Promise<Router> {
|
||||
// ...
|
||||
|
||||
const publisher = await Publisher.fromConfig(config, {
|
||||
logger,
|
||||
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();
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
- Updated dependencies [d8ffec739]
|
||||
- Updated dependencies [7abec4dbc]
|
||||
- Updated dependencies [017192ee8]
|
||||
- Updated dependencies [a360f9478]
|
||||
- Updated dependencies [bb5055aee]
|
||||
- Updated dependencies [d840d30bc]
|
||||
- Updated dependencies [d0d1c2f7b]
|
||||
- Updated dependencies [5d0740563]
|
||||
- Updated dependencies [b25846562]
|
||||
- Updated dependencies [12390778e]
|
||||
- Updated dependencies [cba5944fc]
|
||||
- Updated dependencies [a376e3ee8]
|
||||
- Updated dependencies [fef852ecd]
|
||||
- Updated dependencies [18f7345a6]
|
||||
- Updated dependencies [5cafcf452]
|
||||
- Updated dependencies [423a514c3]
|
||||
- Updated dependencies [86a95ba67]
|
||||
- Updated dependencies [442f34b87]
|
||||
- Updated dependencies [e27cb6c45]
|
||||
- Updated dependencies [184b02bef]
|
||||
- Updated dependencies [0b7fd7a9d]
|
||||
- Updated dependencies [60ce64aa2]
|
||||
- @backstage/plugin-scaffolder-backend@0.9.6
|
||||
- @backstage/plugin-catalog-backend@0.7.1
|
||||
- @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-backend@0.7.1
|
||||
- @backstage/plugin-techdocs@0.7.2
|
||||
- @backstage/catalog-client@0.3.10
|
||||
- @backstage/plugin-tech-radar@0.3.9
|
||||
- @backstage/cli@0.6.8
|
||||
|
||||
## 0.3.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/create-app",
|
||||
"description": "Create app package for Backstage",
|
||||
"version": "0.3.17",
|
||||
"version": "0.3.18",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -25,14 +25,10 @@ backend:
|
||||
database:
|
||||
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
|
||||
@@ -43,13 +39,11 @@ backend:
|
||||
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}
|
||||
|
||||
proxy:
|
||||
'/test':
|
||||
@@ -73,8 +67,7 @@ auth:
|
||||
|
||||
scaffolder:
|
||||
github:
|
||||
token:
|
||||
$env: GITHUB_TOKEN
|
||||
token: ${GITHUB_TOKEN}
|
||||
visibility: public # or 'internal' or 'private'
|
||||
|
||||
catalog:
|
||||
|
||||
@@ -34,6 +34,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,59 @@
|
||||
# @backstage/techdocs-common
|
||||
|
||||
## 0.5.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- bc9d62f4f: Move the sanity checks of the publisher configurations to a dedicated `PublisherBase#getReadiness()` method instead of throwing an error when doing `Publisher.fromConfig(...)`.
|
||||
You should include the check when your backend to get early feedback about a potential misconfiguration:
|
||||
|
||||
```diff
|
||||
// packages/backend/src/plugins/techdocs.ts
|
||||
|
||||
export default async function createPlugin({
|
||||
logger,
|
||||
config,
|
||||
discovery,
|
||||
reader,
|
||||
}: PluginEnvironment): Promise<Router> {
|
||||
// ...
|
||||
|
||||
const publisher = await Publisher.fromConfig(config, {
|
||||
logger,
|
||||
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();
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
If you want to crash your application on invalid configurations, you can throw an `Error` to preserve the old behavior.
|
||||
Please be aware that this is not the recommended for the use in a Backstage backend but might be helpful in CLI tools such as the `techdocs-cli`.
|
||||
|
||||
```ts
|
||||
const publisher = await Publisher.fromConfig(config, {
|
||||
logger,
|
||||
discovery,
|
||||
});
|
||||
|
||||
const ready = await publisher.getReadiness();
|
||||
if (!ready.isAvailable) {
|
||||
throw new Error('Invalid TechDocs publisher configuration');
|
||||
}
|
||||
```
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [bb5055aee]
|
||||
- Updated dependencies [5d0740563]
|
||||
- @backstage/catalog-model@0.7.7
|
||||
|
||||
## 0.4.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -81,10 +81,12 @@ class Bucket {
|
||||
this.bucketName = bucketName;
|
||||
}
|
||||
|
||||
getMetadata() {
|
||||
return new Promise(resolve => {
|
||||
resolve('');
|
||||
});
|
||||
async getMetadata() {
|
||||
if (this.bucketName === 'errorBucket') {
|
||||
throw Error('Bucket does not exist');
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
upload(source: string, { destination }) {
|
||||
|
||||
@@ -80,10 +80,16 @@ export class S3 {
|
||||
};
|
||||
}
|
||||
|
||||
headBucket() {
|
||||
return new Promise(resolve => {
|
||||
resolve('');
|
||||
});
|
||||
headBucket({ Bucket }) {
|
||||
return {
|
||||
promise: async () => {
|
||||
if (Bucket === 'errorBucket') {
|
||||
throw new Error('Bucket does not exist');
|
||||
}
|
||||
|
||||
return {};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
upload({ Key }: { Key: string }) {
|
||||
|
||||
@@ -13,10 +13,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { EventEmitter } from 'events';
|
||||
import fs from 'fs-extra';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import { EventEmitter } from 'events';
|
||||
import { ClientError } from 'pkgcloud';
|
||||
|
||||
const rootDir = os.platform() === 'win32' ? 'C:\\rootDir' : '/rootDir';
|
||||
|
||||
@@ -37,12 +38,11 @@ class PkgCloudStorageClient {
|
||||
getFile(
|
||||
containerName: string,
|
||||
file: string,
|
||||
callback: (err: any, file: string) => any,
|
||||
callback: (err: any, file: any) => any,
|
||||
) {
|
||||
checkFileExists(file).then(res => {
|
||||
if (!res) {
|
||||
callback('File does not exist', file);
|
||||
throw new Error('File does not exist');
|
||||
callback('File does not exist', undefined);
|
||||
} else {
|
||||
callback(undefined, 'success');
|
||||
}
|
||||
@@ -51,13 +51,12 @@ class PkgCloudStorageClient {
|
||||
|
||||
getContainer(
|
||||
containerName: string,
|
||||
callback: (err: string, container: string) => any,
|
||||
callback: (err: ClientError, container: any) => any,
|
||||
) {
|
||||
if (containerName !== 'mock') {
|
||||
callback('Container does not exist', containerName);
|
||||
throw new Error('Container does not exist');
|
||||
callback(new Error('Container does not exist'), undefined);
|
||||
} else {
|
||||
callback('Container does not exist', 'success');
|
||||
callback(undefined, 'success');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/techdocs-common",
|
||||
"description": "Common functionalities for TechDocs, to be shared between techdocs-backend plugin and techdocs-cli",
|
||||
"version": "0.4.5",
|
||||
"version": "0.5.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"private": false,
|
||||
@@ -39,7 +39,7 @@
|
||||
"@azure/identity": "^1.2.2",
|
||||
"@azure/storage-blob": "^12.4.0",
|
||||
"@backstage/backend-common": "^0.6.0",
|
||||
"@backstage/catalog-model": "^0.7.4",
|
||||
"@backstage/catalog-model": "^0.7.7",
|
||||
"@backstage/config": "^0.1.4",
|
||||
"@backstage/errors": "^0.1.1",
|
||||
"@backstage/integration": "^0.5.1",
|
||||
@@ -62,7 +62,7 @@
|
||||
"winston": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.5",
|
||||
"@backstage/cli": "^0.6.8",
|
||||
"@types/fs-extra": "^9.0.5",
|
||||
"@types/git-url-parse": "^9.0.0",
|
||||
"@types/js-yaml": "^4.0.0",
|
||||
|
||||
@@ -13,16 +13,16 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import {
|
||||
Entity,
|
||||
EntityName,
|
||||
ENTITY_DEFAULT_NAMESPACE,
|
||||
EntityName,
|
||||
} from '@backstage/catalog-model';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import mockFs from 'mock-fs';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import * as winston from 'winston';
|
||||
import { AwsS3Publish } from './awsS3';
|
||||
import { PublisherBase, TechDocsMetadata } from './types';
|
||||
|
||||
@@ -59,9 +59,7 @@ const getEntityRootDir = (entity: Entity) => {
|
||||
return path.join(rootDir, namespace || ENTITY_DEFAULT_NAMESPACE, kind, name);
|
||||
};
|
||||
|
||||
const logger = winston.createLogger();
|
||||
jest.spyOn(logger, 'info').mockReturnValue(logger);
|
||||
jest.spyOn(logger, 'error').mockReturnValue(logger);
|
||||
const logger = getVoidLogger();
|
||||
|
||||
let publisher: PublisherBase;
|
||||
|
||||
@@ -87,6 +85,39 @@ beforeEach(() => {
|
||||
});
|
||||
|
||||
describe('AwsS3Publish', () => {
|
||||
describe('getReadiness', () => {
|
||||
it('should validate correct config', async () => {
|
||||
expect(await publisher.getReadiness()).toEqual({
|
||||
isAvailable: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should reject incorrect config', async () => {
|
||||
const mockConfig = new ConfigReader({
|
||||
techdocs: {
|
||||
requestUrl: 'http://localhost:7000',
|
||||
publisher: {
|
||||
type: 'awsS3',
|
||||
awsS3: {
|
||||
credentials: {
|
||||
accessKeyId: 'accessKeyId',
|
||||
secretAccessKey: 'secretAccessKey',
|
||||
},
|
||||
// this bucket name will throw an error
|
||||
bucketName: 'errorBucket',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const errorPublisher = AwsS3Publish.fromConfig(mockConfig, logger);
|
||||
|
||||
expect(await errorPublisher.getReadiness()).toEqual({
|
||||
isAvailable: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('publish', () => {
|
||||
beforeEach(() => {
|
||||
const entity = createMockEntity();
|
||||
|
||||
@@ -17,16 +17,21 @@ import { Entity, EntityName } from '@backstage/catalog-model';
|
||||
import { Config } from '@backstage/config';
|
||||
import aws, { Credentials } from 'aws-sdk';
|
||||
import { ManagedUpload } from 'aws-sdk/clients/s3';
|
||||
import { CredentialsOptions } from 'aws-sdk/lib/credentials';
|
||||
import express from 'express';
|
||||
import fs from 'fs-extra';
|
||||
import JSON5 from 'json5';
|
||||
import createLimiter from 'p-limit';
|
||||
import { CredentialsOptions } from 'aws-sdk/lib/credentials';
|
||||
import path from 'path';
|
||||
import { Readable } from 'stream';
|
||||
import { Logger } from 'winston';
|
||||
import { getFileTreeRecursively, getHeadersForFileExtension } from './helpers';
|
||||
import { PublisherBase, PublishRequest, TechDocsMetadata } from './types';
|
||||
import {
|
||||
PublisherBase,
|
||||
PublishRequest,
|
||||
ReadinessResponse,
|
||||
TechDocsMetadata,
|
||||
} from './types';
|
||||
|
||||
const streamToBuffer = (stream: Readable): Promise<Buffer> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -81,30 +86,6 @@ export class AwsS3Publish implements PublisherBase {
|
||||
...(endpoint && { endpoint }),
|
||||
});
|
||||
|
||||
// Check if the defined bucket exists. Being able to connect means the configuration is good
|
||||
// and the storage client will work.
|
||||
storageClient.headBucket(
|
||||
{
|
||||
Bucket: bucketName,
|
||||
},
|
||||
err => {
|
||||
if (err) {
|
||||
logger.error(
|
||||
`Could not retrieve metadata about the AWS S3 bucket ${bucketName}. ` +
|
||||
'Make sure the bucket exists. Also make sure that authentication is setup either by ' +
|
||||
'explicitly defining credentials and region in techdocs.publisher.awsS3 in app config or ' +
|
||||
'by using environment variables. Refer to https://backstage.io/docs/features/techdocs/using-cloud-storage',
|
||||
);
|
||||
logger.error(`from AWS client library: ${err.message}`);
|
||||
throw new Error();
|
||||
} else {
|
||||
logger.info(
|
||||
`Successfully connected to the AWS S3 bucket ${bucketName}.`,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
return new AwsS3Publish(storageClient, bucketName, logger);
|
||||
}
|
||||
|
||||
@@ -149,6 +130,35 @@ export class AwsS3Publish implements PublisherBase {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the defined bucket exists. Being able to connect means the configuration is good
|
||||
* and the storage client will work.
|
||||
*/
|
||||
async getReadiness(): Promise<ReadinessResponse> {
|
||||
try {
|
||||
await this.storageClient
|
||||
.headBucket({ Bucket: this.bucketName })
|
||||
.promise();
|
||||
|
||||
this.logger.info(
|
||||
`Successfully connected to the AWS S3 bucket ${this.bucketName}.`,
|
||||
);
|
||||
|
||||
return { isAvailable: true };
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Could not retrieve metadata about the AWS S3 bucket ${this.bucketName}. ` +
|
||||
'Make sure the bucket exists. Also make sure that authentication is setup either by ' +
|
||||
'explicitly defining credentials and region in techdocs.publisher.awsS3 in app config or ' +
|
||||
'by using environment variables. Refer to https://backstage.io/docs/features/techdocs/using-cloud-storage',
|
||||
);
|
||||
this.logger.error(`from AWS client library`, error);
|
||||
return {
|
||||
isAvailable: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload all the files from the generated `directory` to the S3 bucket.
|
||||
* Directory structure used in the bucket is - entityNamespace/entityKind/entityName/index.html
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import {
|
||||
Entity,
|
||||
EntityName,
|
||||
ENTITY_DEFAULT_NAMESPACE,
|
||||
EntityName,
|
||||
} from '@backstage/catalog-model';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import mockFs from 'mock-fs';
|
||||
@@ -59,12 +59,9 @@ const getEntityRootDir = (entity: Entity) => {
|
||||
return path.join(rootDir, namespace || ENTITY_DEFAULT_NAMESPACE, kind, name);
|
||||
};
|
||||
|
||||
function createLogger() {
|
||||
const logger = getVoidLogger();
|
||||
jest.spyOn(logger, 'info').mockReturnValue(logger);
|
||||
jest.spyOn(logger, 'error').mockReturnValue(logger);
|
||||
return logger;
|
||||
}
|
||||
const logger = getVoidLogger();
|
||||
jest.spyOn(logger, 'info').mockReturnValue(logger);
|
||||
jest.spyOn(logger, 'error').mockReturnValue(logger);
|
||||
|
||||
let publisher: PublisherBase;
|
||||
beforeEach(async () => {
|
||||
@@ -85,13 +82,51 @@ beforeEach(async () => {
|
||||
},
|
||||
});
|
||||
|
||||
publisher = await AzureBlobStoragePublish.fromConfig(
|
||||
mockConfig,
|
||||
createLogger(),
|
||||
);
|
||||
publisher = AzureBlobStoragePublish.fromConfig(mockConfig, logger);
|
||||
});
|
||||
|
||||
describe('publishing with valid credentials', () => {
|
||||
describe('getReadiness', () => {
|
||||
it('should validate correct config', async () => {
|
||||
expect(await publisher.getReadiness()).toEqual({
|
||||
isAvailable: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should reject incorrect config', async () => {
|
||||
const mockConfig = new ConfigReader({
|
||||
techdocs: {
|
||||
requestUrl: 'http://localhost:7000',
|
||||
publisher: {
|
||||
type: 'azureBlobStorage',
|
||||
azureBlobStorage: {
|
||||
credentials: {
|
||||
accountName: 'accountName',
|
||||
accountKey: 'accountKey',
|
||||
},
|
||||
containerName: 'bad_container',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const errorPublisher = await AzureBlobStoragePublish.fromConfig(
|
||||
mockConfig,
|
||||
logger,
|
||||
);
|
||||
|
||||
expect(await errorPublisher.getReadiness()).toEqual({
|
||||
isAvailable: false,
|
||||
});
|
||||
|
||||
expect(logger.error).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
`Could not retrieve metadata about the Azure Blob Storage container bad_container.`,
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('publish', () => {
|
||||
beforeEach(() => {
|
||||
const entity = createMockEntity();
|
||||
@@ -151,6 +186,60 @@ describe('publishing with valid credentials', () => {
|
||||
});
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
it('reports an error when bad account credentials', async () => {
|
||||
const mockConfig = new ConfigReader({
|
||||
techdocs: {
|
||||
requestUrl: 'http://localhost:7000',
|
||||
publisher: {
|
||||
type: 'azureBlobStorage',
|
||||
azureBlobStorage: {
|
||||
credentials: {
|
||||
accountName: 'failupload',
|
||||
accountKey: 'accountKey',
|
||||
},
|
||||
containerName: 'containerName',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
publisher = await AzureBlobStoragePublish.fromConfig(mockConfig, logger);
|
||||
|
||||
const entity = createMockEntity();
|
||||
const entityRootDir = getEntityRootDir(entity);
|
||||
|
||||
mockFs({
|
||||
[entityRootDir]: {
|
||||
'index.html': '',
|
||||
},
|
||||
});
|
||||
|
||||
let error;
|
||||
try {
|
||||
await publisher.publish({
|
||||
entity,
|
||||
directory: entityRootDir,
|
||||
});
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error.message).toContain(
|
||||
`Unable to upload file(s) to Azure Blob Storage.`,
|
||||
);
|
||||
|
||||
expect(logger.error).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
`Unable to upload file(s) to Azure Blob Storage. Error: Upload failed for ${path.join(
|
||||
entityRootDir,
|
||||
'index.html',
|
||||
)} with status code 500`,
|
||||
),
|
||||
);
|
||||
|
||||
mockFs.restore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasDocsBeenGenerated', () => {
|
||||
@@ -243,156 +332,3 @@ describe('publishing with valid credentials', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('error reporting', () => {
|
||||
it('reports an error when unable to read container properties', async () => {
|
||||
const mockConfig = new ConfigReader({
|
||||
techdocs: {
|
||||
requestUrl: 'http://localhost:7000',
|
||||
publisher: {
|
||||
type: 'azureBlobStorage',
|
||||
azureBlobStorage: {
|
||||
credentials: {
|
||||
accountName: 'accountName',
|
||||
},
|
||||
containerName: 'bad_container',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const logger = createLogger();
|
||||
|
||||
let error;
|
||||
try {
|
||||
publisher = await AzureBlobStoragePublish.fromConfig(mockConfig, logger);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
|
||||
expect(logger.error).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
`Could not retrieve metadata about the Azure Blob Storage container bad_container.`,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('reports an error when bad account credentials', async () => {
|
||||
const mockConfig = new ConfigReader({
|
||||
techdocs: {
|
||||
requestUrl: 'http://localhost:7000',
|
||||
publisher: {
|
||||
type: 'azureBlobStorage',
|
||||
azureBlobStorage: {
|
||||
credentials: {
|
||||
accountName: 'failupload',
|
||||
accountKey: 'accountKey',
|
||||
},
|
||||
containerName: 'containerName',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const logger = createLogger();
|
||||
|
||||
publisher = await AzureBlobStoragePublish.fromConfig(mockConfig, logger);
|
||||
|
||||
const entity = createMockEntity();
|
||||
const entityRootDir = getEntityRootDir(entity);
|
||||
|
||||
mockFs({
|
||||
[entityRootDir]: {
|
||||
'index.html': '',
|
||||
},
|
||||
});
|
||||
|
||||
let error;
|
||||
try {
|
||||
await publisher.publish({
|
||||
entity,
|
||||
directory: entityRootDir,
|
||||
});
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error.message).toContain(
|
||||
`Unable to upload file(s) to Azure Blob Storage.`,
|
||||
);
|
||||
|
||||
expect(logger.error).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
`Unable to upload file(s) to Azure Blob Storage. Error: Upload failed for ${path.join(
|
||||
entityRootDir,
|
||||
'index.html',
|
||||
)} with status code 500`,
|
||||
),
|
||||
);
|
||||
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
describe('fetchTechDocsMetadata', () => {
|
||||
it('should return tech docs metadata', async () => {
|
||||
const entityNameMock = createMockEntityName();
|
||||
const entity = createMockEntity();
|
||||
const entityRootDir = getEntityRootDir(entity);
|
||||
|
||||
mockFs({
|
||||
[entityRootDir]: {
|
||||
'techdocs_metadata.json':
|
||||
'{"site_name": "backstage", "site_description": "site_content", "etag": "etag"}',
|
||||
},
|
||||
});
|
||||
const expectedMetadata: TechDocsMetadata = {
|
||||
site_name: 'backstage',
|
||||
site_description: 'site_content',
|
||||
etag: 'etag',
|
||||
};
|
||||
expect(
|
||||
await publisher.fetchTechDocsMetadata(entityNameMock),
|
||||
).toStrictEqual(expectedMetadata);
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
it('should return tech docs metadata when json encoded with single quotes', async () => {
|
||||
const entityNameMock = createMockEntityName();
|
||||
const entity = createMockEntity();
|
||||
const entityRootDir = getEntityRootDir(entity);
|
||||
|
||||
mockFs({
|
||||
[entityRootDir]: {
|
||||
'techdocs_metadata.json': `{'site_name': 'backstage', 'site_description': 'site_content', 'etag': 'etag'}`,
|
||||
},
|
||||
});
|
||||
|
||||
const expectedMetadata: TechDocsMetadata = {
|
||||
site_name: 'backstage',
|
||||
site_description: 'site_content',
|
||||
etag: 'etag',
|
||||
};
|
||||
expect(
|
||||
await publisher.fetchTechDocsMetadata(entityNameMock),
|
||||
).toStrictEqual(expectedMetadata);
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
it('should return an error if the techdocs_metadata.json file is not present', async () => {
|
||||
const entityNameMock = createMockEntityName();
|
||||
|
||||
let error;
|
||||
try {
|
||||
await publisher.fetchTechDocsMetadata(entityNameMock);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error.message).toEqual(
|
||||
expect.stringContaining('TechDocs metadata fetch'),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -26,16 +26,18 @@ import limiterFactory from 'p-limit';
|
||||
import { default as path, default as platformPath } from 'path';
|
||||
import { Logger } from 'winston';
|
||||
import { getFileTreeRecursively, getHeadersForFileExtension } from './helpers';
|
||||
import { PublisherBase, PublishRequest, TechDocsMetadata } from './types';
|
||||
import {
|
||||
PublisherBase,
|
||||
PublishRequest,
|
||||
ReadinessResponse,
|
||||
TechDocsMetadata,
|
||||
} from './types';
|
||||
|
||||
// The number of batches that may be ongoing at the same time.
|
||||
const BATCH_CONCURRENCY = 3;
|
||||
|
||||
export class AzureBlobStoragePublish implements PublisherBase {
|
||||
static async fromConfig(
|
||||
config: Config,
|
||||
logger: Logger,
|
||||
): Promise<PublisherBase> {
|
||||
static fromConfig(config: Config, logger: Logger): PublisherBase {
|
||||
let containerName = '';
|
||||
try {
|
||||
containerName = config.getString(
|
||||
@@ -78,26 +80,6 @@ export class AzureBlobStoragePublish implements PublisherBase {
|
||||
credential,
|
||||
);
|
||||
|
||||
try {
|
||||
const response = await storageClient
|
||||
.getContainerClient(containerName)
|
||||
.getProperties();
|
||||
|
||||
if (response._response.status >= 400) {
|
||||
throw new Error(
|
||||
`Failed to retrieve metadata from ${response._response.request.url} with status code ${response._response.status}.`,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
`Could not retrieve metadata about the Azure Blob Storage container ${containerName}. ` +
|
||||
'Make sure that the Azure project and container exist and the access key is setup correctly ' +
|
||||
'techdocs.publisher.azureBlobStorage.credentials defined in app config has correct permissions. ' +
|
||||
'Refer to https://backstage.io/docs/features/techdocs/using-cloud-storage',
|
||||
);
|
||||
throw new Error(`from Azure Blob Storage client library: ${e.message}`);
|
||||
}
|
||||
|
||||
return new AzureBlobStoragePublish(storageClient, containerName, logger);
|
||||
}
|
||||
|
||||
@@ -111,6 +93,37 @@ export class AzureBlobStoragePublish implements PublisherBase {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
async getReadiness(): Promise<ReadinessResponse> {
|
||||
try {
|
||||
const response = await this.storageClient
|
||||
.getContainerClient(this.containerName)
|
||||
.getProperties();
|
||||
|
||||
if (response._response.status === 200) {
|
||||
return {
|
||||
isAvailable: true,
|
||||
};
|
||||
}
|
||||
|
||||
if (response._response.status >= 400) {
|
||||
this.logger.error(
|
||||
`Failed to retrieve metadata from ${response._response.request.url} with status code ${response._response.status}.`,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
this.logger.error(`from Azure Blob Storage client library: ${e.message}`);
|
||||
}
|
||||
|
||||
this.logger.error(
|
||||
`Could not retrieve metadata about the Azure Blob Storage container ${this.containerName}. ` +
|
||||
'Make sure that the Azure project and container exist and the access key is setup correctly ' +
|
||||
'techdocs.publisher.azureBlobStorage.credentials defined in app config has correct permissions. ' +
|
||||
'Refer to https://backstage.io/docs/features/techdocs/using-cloud-storage',
|
||||
);
|
||||
|
||||
return { isAvailable: false };
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload all the files from the generated `directory` to the Azure Blob Storage container.
|
||||
* Directory structure used in the container is - entityNamespace/entityKind/entityName/index.html
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import {
|
||||
Entity,
|
||||
EntityName,
|
||||
ENTITY_DEFAULT_NAMESPACE,
|
||||
EntityName,
|
||||
} from '@backstage/catalog-model';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import mockFs from 'mock-fs';
|
||||
@@ -83,6 +83,35 @@ beforeEach(async () => {
|
||||
});
|
||||
|
||||
describe('GoogleGCSPublish', () => {
|
||||
describe('getReadiness', () => {
|
||||
it('should validate correct config', async () => {
|
||||
expect(await publisher.getReadiness()).toEqual({
|
||||
isAvailable: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should reject incorrect config', async () => {
|
||||
const mockConfig = new ConfigReader({
|
||||
techdocs: {
|
||||
requestUrl: 'http://localhost:7000',
|
||||
publisher: {
|
||||
type: 'googleGcs',
|
||||
googleGcs: {
|
||||
credentials: '{}',
|
||||
bucketName: 'errorBucket',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const errorPublisher = GoogleGCSPublish.fromConfig(mockConfig, logger);
|
||||
|
||||
expect(await errorPublisher.getReadiness()).toEqual({
|
||||
isAvailable: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('publish', () => {
|
||||
beforeEach(() => {
|
||||
const entity = createMockEntity();
|
||||
|
||||
@@ -26,13 +26,15 @@ import createLimiter from 'p-limit';
|
||||
import path from 'path';
|
||||
import { Logger } from 'winston';
|
||||
import { getFileTreeRecursively, getHeadersForFileExtension } from './helpers';
|
||||
import { PublisherBase, PublishRequest, TechDocsMetadata } from './types';
|
||||
import {
|
||||
PublisherBase,
|
||||
PublishRequest,
|
||||
ReadinessResponse,
|
||||
TechDocsMetadata,
|
||||
} from './types';
|
||||
|
||||
export class GoogleGCSPublish implements PublisherBase {
|
||||
static async fromConfig(
|
||||
config: Config,
|
||||
logger: Logger,
|
||||
): Promise<PublisherBase> {
|
||||
static fromConfig(config: Config, logger: Logger): PublisherBase {
|
||||
let bucketName = '';
|
||||
try {
|
||||
bucketName = config.getString('techdocs.publisher.googleGcs.bucketName');
|
||||
@@ -65,21 +67,6 @@ export class GoogleGCSPublish implements PublisherBase {
|
||||
}),
|
||||
});
|
||||
|
||||
// Check if the defined bucket exists. Being able to connect means the configuration is good
|
||||
// and the storage client will work.
|
||||
try {
|
||||
await storageClient.bucket(bucketName).getMetadata();
|
||||
logger.info(`Successfully connected to the GCS bucket ${bucketName}.`);
|
||||
} catch (err) {
|
||||
logger.error(
|
||||
`Could not retrieve metadata about the GCS bucket ${bucketName}. ` +
|
||||
'Make sure the bucket exists. Also make sure that authentication is setup either by explicitly defining ' +
|
||||
'techdocs.publisher.googleGcs.credentials in app config or by using environment variables. ' +
|
||||
'Refer to https://backstage.io/docs/features/techdocs/using-cloud-storage',
|
||||
);
|
||||
throw new Error(err.message);
|
||||
}
|
||||
|
||||
return new GoogleGCSPublish(storageClient, bucketName, logger);
|
||||
}
|
||||
|
||||
@@ -93,6 +80,33 @@ export class GoogleGCSPublish implements PublisherBase {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the defined bucket exists. Being able to connect means the configuration is good
|
||||
* and the storage client will work.
|
||||
*/
|
||||
async getReadiness(): Promise<ReadinessResponse> {
|
||||
try {
|
||||
await this.storageClient.bucket(this.bucketName).getMetadata();
|
||||
this.logger.info(
|
||||
`Successfully connected to the GCS bucket ${this.bucketName}.`,
|
||||
);
|
||||
|
||||
return {
|
||||
isAvailable: true,
|
||||
};
|
||||
} catch (err) {
|
||||
this.logger.error(
|
||||
`Could not retrieve metadata about the GCS bucket ${this.bucketName}. ` +
|
||||
'Make sure the bucket exists. Also make sure that authentication is setup either by explicitly defining ' +
|
||||
'techdocs.publisher.googleGcs.credentials in app config or by using environment variables. ' +
|
||||
'Refer to https://backstage.io/docs/features/techdocs/using-cloud-storage',
|
||||
);
|
||||
this.logger.error(`from GCS client library: ${err.message}`);
|
||||
|
||||
return { isAvailable: false };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload all the files from the generated `directory` to the GCS bucket.
|
||||
* Directory structure used in the bucket is - entityNamespace/entityKind/entityName/index.html
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
PublisherBase,
|
||||
PublishRequest,
|
||||
PublishResponse,
|
||||
ReadinessResponse,
|
||||
TechDocsMetadata,
|
||||
} from './types';
|
||||
|
||||
@@ -65,6 +66,12 @@ export class LocalPublish implements PublisherBase {
|
||||
this.discovery = discovery;
|
||||
}
|
||||
|
||||
async getReadiness(): Promise<ReadinessResponse> {
|
||||
return {
|
||||
isAvailable: true,
|
||||
};
|
||||
}
|
||||
|
||||
publish({ entity, directory }: PublishRequest): Promise<PublishResponse> {
|
||||
const entityNamespace = entity.metadata.namespace ?? 'default';
|
||||
|
||||
|
||||
@@ -13,16 +13,16 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import {
|
||||
Entity,
|
||||
EntityName,
|
||||
ENTITY_DEFAULT_NAMESPACE,
|
||||
EntityName,
|
||||
} from '@backstage/catalog-model';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import mockFs from 'mock-fs';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import * as winston from 'winston';
|
||||
import { OpenStackSwiftPublish } from './openStackSwift';
|
||||
import { PublisherBase, TechDocsMetadata } from './types';
|
||||
|
||||
@@ -59,9 +59,7 @@ const getEntityRootDir = (entity: Entity) => {
|
||||
return path.join(rootDir, namespace || ENTITY_DEFAULT_NAMESPACE, kind, name);
|
||||
};
|
||||
|
||||
const logger = winston.createLogger();
|
||||
jest.spyOn(logger, 'info').mockReturnValue(logger);
|
||||
jest.spyOn(logger, 'error').mockReturnValue(logger);
|
||||
const logger = getVoidLogger();
|
||||
|
||||
let publisher: PublisherBase;
|
||||
|
||||
@@ -89,6 +87,43 @@ beforeEach(() => {
|
||||
});
|
||||
|
||||
describe('OpenStackSwiftPublish', () => {
|
||||
describe('getReadiness', () => {
|
||||
it('should validate correct config', async () => {
|
||||
expect(await publisher.getReadiness()).toEqual({
|
||||
isAvailable: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should reject incorrect config', async () => {
|
||||
const mockConfig = new ConfigReader({
|
||||
techdocs: {
|
||||
requestUrl: 'http://localhost:7000',
|
||||
publisher: {
|
||||
type: 'openStackSwift',
|
||||
openStackSwift: {
|
||||
credentials: {
|
||||
username: 'mockuser',
|
||||
password: 'verystrongpass',
|
||||
},
|
||||
authUrl: 'mockauthurl',
|
||||
region: 'mockregion',
|
||||
containerName: 'errorBucket',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const errorPublisher = OpenStackSwiftPublish.fromConfig(
|
||||
mockConfig,
|
||||
logger,
|
||||
);
|
||||
|
||||
expect(await errorPublisher.getReadiness()).toEqual({
|
||||
isAvailable: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('publish', () => {
|
||||
beforeEach(() => {
|
||||
const entity = createMockEntity();
|
||||
|
||||
@@ -15,16 +15,21 @@
|
||||
*/
|
||||
import { Entity, EntityName } from '@backstage/catalog-model';
|
||||
import { Config } from '@backstage/config';
|
||||
import { storage } from 'pkgcloud';
|
||||
import express from 'express';
|
||||
import fs from 'fs-extra';
|
||||
import JSON5 from 'json5';
|
||||
import createLimiter from 'p-limit';
|
||||
import path from 'path';
|
||||
import { storage } from 'pkgcloud';
|
||||
import { Readable } from 'stream';
|
||||
import { Logger } from 'winston';
|
||||
import { getFileTreeRecursively, getHeadersForFileExtension } from './helpers';
|
||||
import { PublisherBase, PublishRequest, TechDocsMetadata } from './types';
|
||||
import {
|
||||
PublisherBase,
|
||||
PublishRequest,
|
||||
ReadinessResponse,
|
||||
TechDocsMetadata,
|
||||
} from './types';
|
||||
|
||||
const streamToBuffer = (stream: Readable): Promise<Buffer> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -70,25 +75,6 @@ export class OpenStackSwiftPublish implements PublisherBase {
|
||||
region: openStackSwiftConfig.getString('region'),
|
||||
});
|
||||
|
||||
// Check if the defined container exists. Being able to connect means the configuration is good
|
||||
// and the storage client will work.
|
||||
storageClient.getContainer(containerName, (err, container) => {
|
||||
if (container) {
|
||||
logger.info(
|
||||
`Successfully connected to the OpenStack Swift container ${containerName}.`,
|
||||
);
|
||||
} else {
|
||||
logger.error(
|
||||
`Could not retrieve metadata about the OpenStack Swift container ${containerName}. ` +
|
||||
'Make sure the container exists. Also make sure that authentication is setup either by ' +
|
||||
'explicitly defining credentials and region in techdocs.publisher.openStackSwift in app config or ' +
|
||||
'by using environment variables. Refer to https://backstage.io/docs/features/techdocs/using-cloud-storage',
|
||||
);
|
||||
|
||||
logger.error(`from OpenStack client library: ${err.message}`);
|
||||
}
|
||||
});
|
||||
|
||||
return new OpenStackSwiftPublish(storageClient, containerName, logger);
|
||||
}
|
||||
|
||||
@@ -102,6 +88,37 @@ export class OpenStackSwiftPublish implements PublisherBase {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if the defined container exists. Being able to connect means the configuration is good
|
||||
* and the storage client will work.
|
||||
*/
|
||||
getReadiness(): Promise<ReadinessResponse> {
|
||||
return new Promise(resolve => {
|
||||
this.storageClient.getContainer(this.containerName, (err, container) => {
|
||||
if (container) {
|
||||
this.logger.info(
|
||||
`Successfully connected to the OpenStack Swift container ${this.containerName}.`,
|
||||
);
|
||||
resolve({
|
||||
isAvailable: true,
|
||||
});
|
||||
} else {
|
||||
this.logger.error(
|
||||
`Could not retrieve metadata about the OpenStack Swift container ${this.containerName}. ` +
|
||||
'Make sure the container exists. Also make sure that authentication is setup either by ' +
|
||||
'explicitly defining credentials and region in techdocs.publisher.openStackSwift in app config or ' +
|
||||
'by using environment variables. Refer to https://backstage.io/docs/features/techdocs/using-cloud-storage',
|
||||
);
|
||||
|
||||
this.logger.error(`from OpenStack client library: ${err.message}`);
|
||||
resolve({
|
||||
isAvailable: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload all the files from the generated `directory` to the OpenStack Swift container.
|
||||
* Directory structure used in the bucket is - entityNamespace/entityKind/entityName/index.html
|
||||
|
||||
@@ -13,16 +13,16 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Logger } from 'winston';
|
||||
import { Config } from '@backstage/config';
|
||||
import { PluginEndpointDiscovery } from '@backstage/backend-common';
|
||||
|
||||
import { PublisherType, PublisherBase } from './types';
|
||||
import { LocalPublish } from './local';
|
||||
import { GoogleGCSPublish } from './googleStorage';
|
||||
import { PluginEndpointDiscovery } from '@backstage/backend-common';
|
||||
import { Config } from '@backstage/config';
|
||||
import { Logger } from 'winston';
|
||||
import { AwsS3Publish } from './awsS3';
|
||||
import { AzureBlobStoragePublish } from './azureBlobStorage';
|
||||
import { GoogleGCSPublish } from './googleStorage';
|
||||
import { LocalPublish } from './local';
|
||||
import { OpenStackSwiftPublish } from './openStackSwift';
|
||||
import { PublisherBase, PublisherType } from './types';
|
||||
|
||||
type factoryOptions = {
|
||||
logger: Logger;
|
||||
@@ -45,7 +45,7 @@ export class Publisher {
|
||||
switch (publisherType) {
|
||||
case 'googleGcs':
|
||||
logger.info('Creating Google Storage Bucket publisher for TechDocs');
|
||||
return await GoogleGCSPublish.fromConfig(config, logger);
|
||||
return GoogleGCSPublish.fromConfig(config, logger);
|
||||
case 'awsS3':
|
||||
logger.info('Creating AWS S3 Bucket publisher for TechDocs');
|
||||
return AwsS3Publish.fromConfig(config, logger);
|
||||
|
||||
@@ -37,6 +37,14 @@ export type PublishResponse = {
|
||||
remoteUrl?: string;
|
||||
} | void;
|
||||
|
||||
/**
|
||||
* Result for the validation check.
|
||||
*/
|
||||
export type ReadinessResponse = {
|
||||
/** If true, the publisher is able to interact with the backing storage. */
|
||||
isAvailable: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Type to hold metadata found in techdocs_metadata.json and associated with each site
|
||||
* @param etag ETag of the resource used to generate the site. Usually the latest commit sha of the source repository.
|
||||
@@ -53,6 +61,14 @@ export type TechDocsMetadata = {
|
||||
* It also provides APIs to communicate with the storage service.
|
||||
*/
|
||||
export interface PublisherBase {
|
||||
/**
|
||||
* Check if the publisher is ready. This check tries to perform certain checks to see if the
|
||||
* publisher is configured correctly and can be used to publish or read documentations.
|
||||
* The different implementations might e.g. use the provided service credentials to access the
|
||||
* target or check if a folder/bucket is available.
|
||||
*/
|
||||
getReadiness(): Promise<ReadinessResponse>;
|
||||
|
||||
/**
|
||||
* Store the generated static files onto a storage service (either local filesystem or external service).
|
||||
*
|
||||
|
||||
@@ -63,5 +63,5 @@ export const pageTheme: Record<string, PageTheme> = {
|
||||
library: genPageTheme(colorVariants.rubyRed, shapes.wave),
|
||||
other: genPageTheme(colorVariants.darkGrey, shapes.wave),
|
||||
app: genPageTheme(colorVariants.toastyOrange, shapes.wave),
|
||||
apis: genPageTheme(colorVariants.eveningSea, shapes.wave2),
|
||||
apis: genPageTheme(colorVariants.teal, shapes.wave2),
|
||||
};
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
# @backstage/plugin-api-docs
|
||||
|
||||
## 0.4.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 12390778e: chore(deps): bump @asyncapi/react-component from 0.19.2 to 0.22.3
|
||||
- 5cafcf452: add debounce time attribute for apis-docs for search, giving more time to the users when they are typing.
|
||||
- Updated dependencies [bb5055aee]
|
||||
- Updated dependencies [d0d1c2f7b]
|
||||
- Updated dependencies [5d0740563]
|
||||
- Updated dependencies [5cafcf452]
|
||||
- Updated dependencies [86a95ba67]
|
||||
- Updated dependencies [e27cb6c45]
|
||||
- @backstage/catalog-model@0.7.7
|
||||
- @backstage/core@0.7.5
|
||||
|
||||
## 0.4.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-api-docs",
|
||||
"version": "0.4.10",
|
||||
"version": "0.4.11",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -29,9 +29,9 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@asyncapi/react-component": "^0.19.2",
|
||||
"@backstage/catalog-model": "^0.7.5",
|
||||
"@backstage/core": "^0.7.4",
|
||||
"@asyncapi/react-component": "^0.22.3",
|
||||
"@backstage/catalog-model": "^0.7.7",
|
||||
"@backstage/core": "^0.7.5",
|
||||
"@backstage/plugin-catalog-react": "^0.1.4",
|
||||
"@backstage/theme": "^0.2.5",
|
||||
"@material-icons/font": "^1.0.2",
|
||||
@@ -49,7 +49,7 @@
|
||||
"swagger-ui-react": "^3.37.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.7",
|
||||
"@backstage/cli": "^0.6.8",
|
||||
"@backstage/dev-utils": "^0.1.13",
|
||||
"@backstage/test-utils": "^0.1.10",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.7.6",
|
||||
"@backstage/core": "^0.7.4",
|
||||
"@backstage/core": "^0.7.5",
|
||||
"@backstage/errors": "^0.1.1",
|
||||
"@backstage/plugin-catalog-react": "^0.1.3",
|
||||
"@backstage/theme": "^0.2.5",
|
||||
@@ -34,7 +34,7 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.7",
|
||||
"@backstage/cli": "^0.6.8",
|
||||
"@backstage/dev-utils": "^0.1.13",
|
||||
"@backstage/test-utils": "^0.1.10",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -55,8 +55,7 @@ proxy:
|
||||
target: 'https://api.bitrise.io/v0.1'
|
||||
allowedMethods: ['GET']
|
||||
headers:
|
||||
Authorization:
|
||||
$env: BITRISE_AUTH_TOKEN
|
||||
Authorization: ${BITRISE_AUTH_TOKEN}
|
||||
```
|
||||
|
||||
Learn on https://devcenter.bitrise.io/api/authentication how to create a new Bitrise token.
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.7.2",
|
||||
"@backstage/core": "^0.7.4",
|
||||
"@backstage/core": "^0.7.5",
|
||||
"@backstage/plugin-catalog-react": "^0.1.2",
|
||||
"@backstage/theme": "^0.2.5",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
@@ -37,7 +37,7 @@
|
||||
"recharts": "^1.8.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.7",
|
||||
"@backstage/cli": "^0.6.8",
|
||||
"@backstage/dev-utils": "^0.1.13",
|
||||
"@backstage/test-utils": "^0.1.10",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
# @backstage/plugin-catalog-backend
|
||||
|
||||
## 0.7.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 017192ee8: Add support for configure an LDAP query filter on multiple lines.
|
||||
- 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.
|
||||
|
||||
- Updated dependencies [bb5055aee]
|
||||
- Updated dependencies [5d0740563]
|
||||
- @backstage/catalog-model@0.7.7
|
||||
|
||||
## 0.7.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-catalog-backend",
|
||||
"version": "0.7.0",
|
||||
"version": "0.7.1",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -31,7 +31,7 @@
|
||||
"dependencies": {
|
||||
"@azure/msal-node": "^1.0.0-beta.3",
|
||||
"@backstage/backend-common": "^0.6.1",
|
||||
"@backstage/catalog-model": "^0.7.5",
|
||||
"@backstage/catalog-model": "^0.7.7",
|
||||
"@backstage/config": "^0.1.4",
|
||||
"@backstage/errors": "^0.1.1",
|
||||
"@backstage/integration": "^0.5.1",
|
||||
@@ -62,7 +62,7 @@
|
||||
"yup": "^0.29.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.6",
|
||||
"@backstage/cli": "^0.6.8",
|
||||
"@backstage/test-utils": "^0.1.9",
|
||||
"@types/core-js": "^2.5.4",
|
||||
"@types/git-url-parse": "^9.0.0",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.7.6",
|
||||
"@backstage/catalog-client": "^0.3.9",
|
||||
"@backstage/core": "^0.7.4",
|
||||
"@backstage/core": "^0.7.5",
|
||||
"@backstage/integration": "^0.5.0",
|
||||
"@backstage/integration-react": "^0.1.1",
|
||||
"@backstage/plugin-catalog-react": "^0.1.4",
|
||||
@@ -53,7 +53,7 @@
|
||||
"yaml": "^1.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.7",
|
||||
"@backstage/cli": "^0.6.8",
|
||||
"@backstage/dev-utils": "^0.1.13",
|
||||
"@backstage/test-utils": "^0.1.10",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -1,5 +1,28 @@
|
||||
# @backstage/plugin-catalog
|
||||
|
||||
## 0.5.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 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.
|
||||
|
||||
- Updated dependencies [bb5055aee]
|
||||
- Updated dependencies [d0d1c2f7b]
|
||||
- Updated dependencies [5d0740563]
|
||||
- Updated dependencies [5cafcf452]
|
||||
- Updated dependencies [86a95ba67]
|
||||
- Updated dependencies [442f34b87]
|
||||
- Updated dependencies [e27cb6c45]
|
||||
- @backstage/catalog-model@0.7.7
|
||||
- @backstage/core@0.7.5
|
||||
- @backstage/catalog-client@0.3.10
|
||||
|
||||
## 0.5.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user