Merge branch 'master' into cost-insights-comparable-metrics
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'example-app': patch
|
||||
---
|
||||
|
||||
Add Pull Request tab to components view.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'e2e-test': minor
|
||||
---
|
||||
|
||||
Converted into a CLI, use `yarn e2e-test run` to run
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/backend-common': minor
|
||||
'@backstage/cli': minor
|
||||
---
|
||||
|
||||
Use APP_ENV before NODE_ENV for determining what config to load
|
||||
@@ -6,6 +6,7 @@ coverage:
|
||||
default:
|
||||
threshold: 0% # Ref: https://docs.codecov.io/docs/codecovyml-reference#coveragestatus
|
||||
target: auto
|
||||
informational: true # Don't block PRs
|
||||
|
||||
# Since Backstage is a mono repo, flags here help in getting the code coverage of individual packages.
|
||||
# Documentation: https://docs.codecov.io/docs/flags
|
||||
|
||||
@@ -205,3 +205,5 @@ cloudbuild
|
||||
Grafana
|
||||
Iain
|
||||
Snyk
|
||||
www
|
||||
WWW
|
||||
|
||||
@@ -97,7 +97,7 @@ jobs:
|
||||
if: ${{ steps.yarn-lock.outcome == 'failure' }}
|
||||
run: |
|
||||
yarn lerna -- run test -- --coverage
|
||||
bash <(curl -s https://codecov.io/bash)
|
||||
bash <(curl -s https://codecov.io/bash) -N $(git rev-parse FETCH_HEAD)
|
||||
|
||||
- name: verify plugin template
|
||||
run: yarn lerna -- run diff -- --check
|
||||
|
||||
@@ -40,4 +40,4 @@ jobs:
|
||||
- name: yarn build
|
||||
run: yarn build --ignore example-app --ignore example-backend --ignore @techdocs/cli
|
||||
- name: run E2E test
|
||||
run: yarn workspace e2e-test start
|
||||
run: yarn e2e-test run
|
||||
|
||||
@@ -70,7 +70,7 @@ jobs:
|
||||
- name: run E2E test
|
||||
run: |
|
||||
sudo sysctl fs.inotify.max_user_watches=524288
|
||||
yarn workspace e2e-test start
|
||||
yarn e2e-test run
|
||||
env:
|
||||
POSTGRES_HOST: localhost
|
||||
POSTGRES_PORT: ${{ job.services.postgres.ports[5432] }}
|
||||
|
||||
@@ -8,6 +8,10 @@ If you encounter issues while upgrading to a newer version, don't hesitate to re
|
||||
|
||||
> Collect changes for the next release below
|
||||
|
||||
### @backstage/cli
|
||||
|
||||
- The recommended way to set the configuration environment is now to use `APP_ENV` instead of `NODE_ENV`.
|
||||
|
||||
### Backend (example-backend, or backends created with @backstage/create-app)
|
||||
|
||||
- A plugin database manager has been created, and plugins can now accept that interface as an argument during initialisation. Notably, the `auth` plugin has a [`createRouter` signature change](./plugins/auth-backend/src/service/router.ts). See [packages/backend/src/index.ts](./packages/backend/src/index.ts) on how to set it up. [#2697](https://github.com/spotify/backstage/pull/2697)
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
# [Backstage](https://backstage.io)
|
||||
|
||||
[](https://opensource.org/licenses/Apache-2.0)
|
||||

|
||||
[](https://www.cncf.io/projects)
|
||||
[](https://github.com/spotify/backstage/actions?query=workflow%3A%22Main+Master+Build%22)
|
||||
[](https://discord.gg/EBHEGzX)
|
||||

|
||||
[](https://codecov.io/gh/spotify/backstage)
|
||||
@@ -24,9 +25,7 @@ Out of the box, Backstage includes:
|
||||
- [Backstage TechDocs](https://backstage.io/docs/features/techdocs/techdocs-overview) for making it easy to create, maintain, find, and use technical documentation, using a "docs like code" approach
|
||||
- Plus, a growing ecosystem of [open source plugins](https://github.com/spotify/backstage/tree/master/plugins) that further expand Backstage’s customizability and functionality
|
||||
|
||||
For more information go to [backstage.io](https://backstage.io) or join our [Discord chatroom](https://discord.gg/EBHEGzX).
|
||||
|
||||
🎉 Backstage is a CNCF Sandbox project. Read the announcement [here](https://backstage.io/blog/2020/09/23/backstage-cncf-sandbox).
|
||||
Backstage was created by Spotify but is now hosted by the [Cloud Native Computing Foundation (CNCF)](https://www.cncf.io) as a Sandbox level project. Read the announcement [here](https://backstage.io/blog/2020/09/23/backstage-cncf-sandbox).
|
||||
|
||||
## Project roadmap
|
||||
|
||||
@@ -58,6 +57,6 @@ Check out [the documentation](https://backstage.io/docs/getting-started) on how
|
||||
|
||||
## License
|
||||
|
||||
Copyright 2020 Spotify AB.
|
||||
Copyright 2020 © Backstage Project Authors. All rights reserved. The Linux Foundation has registered trademarks and uses trademarks. For a list of trademarks of The Linux Foundation, please see our Trademark Usage page: https://www.linuxfoundation.org/trademark-usage
|
||||
|
||||
Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
dependencies:
|
||||
- name: postgresql
|
||||
repository: https://charts.bitnami.com/bitnami
|
||||
version: 9.4.1
|
||||
digest: sha256:f949ec0fe7d146610ce2ee78fbfb4e52d235883a797968b0a1e61aa88ada2786
|
||||
generated: "2020-09-25T08:59:54.255582519+02:00"
|
||||
@@ -0,0 +1,30 @@
|
||||
apiVersion: v2
|
||||
name: backstage
|
||||
description: A Helm chart for Spotify Backstage
|
||||
type: application
|
||||
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
version: 0.1.0
|
||||
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application.
|
||||
appVersion: v0.1.1-alpha.23
|
||||
|
||||
sources:
|
||||
- https://github.com/spotify/backstage
|
||||
- https://github.com/spotify/lighthouse-audit-service
|
||||
|
||||
dependencies:
|
||||
- name: postgresql
|
||||
condition: postgresql.enabled
|
||||
version: 9.4.1
|
||||
repository: https://charts.bitnami.com/bitnami
|
||||
|
||||
maintainers:
|
||||
- name: Martina Iglesias Fernández
|
||||
email: martina@roadie.io
|
||||
url: https://roadie.io
|
||||
- name: David Tuite
|
||||
email: david@roadie.io
|
||||
url: https://roadie.io
|
||||
@@ -0,0 +1,218 @@
|
||||
# Backstage demo helm charts
|
||||
|
||||
This folder contains Helm charts that can easily create a Kubernetes deployment of a demo Backstage app.
|
||||
|
||||
### Pre-requisites
|
||||
|
||||
These charts depend on the `nginx-ingress` controller being present in the cluster. If it's not already installed you
|
||||
can run:
|
||||
|
||||
```
|
||||
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
|
||||
helm install nginx-ingress ingress-nginx/ingress-nginx
|
||||
```
|
||||
|
||||
### Installing the charts
|
||||
|
||||
After choosing a DNS name where backstage will be hosted create a yaml file for your custom configuration.
|
||||
|
||||
```
|
||||
appConfig:
|
||||
app:
|
||||
baseUrl: https://backstage.mydomain.com
|
||||
title: Backstage
|
||||
backend:
|
||||
baseUrl: https://backstage.mydomain.com
|
||||
cors:
|
||||
origin: https://backstage.mydomain.com
|
||||
lighthouse:
|
||||
baseUrl: https://backstage.mydomain.com/lighthouse-api
|
||||
techdocs:
|
||||
storageUrl: https://backstage.mydomain.com/api/techdocs/static/docs
|
||||
requestUrl: https://backstage.mydomain.com/api/techdocs
|
||||
|
||||
```
|
||||
|
||||
Then use it to run:
|
||||
|
||||
```
|
||||
git clone https://github.com/spotify/backstage.git
|
||||
cd contrib/chart/backstage
|
||||
helm dependency update
|
||||
helm install -f backstage-mydomain.yaml backstage .
|
||||
```
|
||||
|
||||
This command will deploy the following pieces:
|
||||
|
||||
- Backstage frontend
|
||||
- Backstage backend with scaffolder and auth plugins
|
||||
- (optional) a PostgreSQL instance
|
||||
- lighthouse plugin
|
||||
- ingress
|
||||
|
||||
After a few minutes Backstage should be up and running in your cluster under the DNS specified earlier.
|
||||
|
||||
Make sure to create the appropriate DNS entry in your infrastructure. To find the public IP address run:
|
||||
|
||||
```bash
|
||||
$ kubectl get ingress
|
||||
NAME HOSTS ADDRESS PORTS AGE
|
||||
backstage-ingress * 123.1.2.3 80 17m
|
||||
```
|
||||
|
||||
> **NOTE**: this is not a production ready deployment.
|
||||
|
||||
## Caveats
|
||||
|
||||
The current implementation does not generate certificates for the ingress which means the browser will alert that the
|
||||
site is insecure and using self-signed certificates.
|
||||
|
||||
## Customization
|
||||
|
||||
### Custom PostgreSQL instance
|
||||
|
||||
Configuring a connection to an existing PostgreSQL instance is possible through the chart's values.
|
||||
|
||||
First create a yaml file with the configuration you want to override, for example `backstage-prod.yaml`:
|
||||
|
||||
```bash
|
||||
postgresql:
|
||||
enabled: false
|
||||
|
||||
appConfig:
|
||||
app:
|
||||
baseUrl: https://backstage-demo.mydomain.com
|
||||
title: Backstage
|
||||
backend:
|
||||
baseUrl: https://backstage-demo.mydomain.com
|
||||
cors:
|
||||
origin: https://backstage-demo.mydomain.com
|
||||
database:
|
||||
client: pg
|
||||
connection:
|
||||
database: backstage_plugin_catalog
|
||||
host: <host>
|
||||
user: <pg user>
|
||||
password: <password>
|
||||
lighthouse:
|
||||
baseUrl: https://backstage-demo.mydomain.com/lighthouse-api
|
||||
|
||||
lighthouse:
|
||||
database:
|
||||
client: pg
|
||||
connection:
|
||||
host: <host>
|
||||
user: <pg user>
|
||||
password: <password>
|
||||
database: lighthouse_audit_service
|
||||
|
||||
```
|
||||
|
||||
For the CA, create a `configMap` named `<helm_release_name>-postgres-ca` with a file called `ca.crt`:
|
||||
|
||||
```
|
||||
kubectl create configmap my-backstage --from-file=ca.crt"
|
||||
```
|
||||
|
||||
Now install the helm chart:
|
||||
|
||||
```
|
||||
cd contrib/chart/backstage
|
||||
helm install -f backstage-prod.yaml my-backstage .
|
||||
```
|
||||
|
||||
### Use your own docker images
|
||||
|
||||
The docker images used for the deployment can be configured through the charts values:
|
||||
|
||||
```
|
||||
frontend:
|
||||
image:
|
||||
repository: <image-name>
|
||||
tag: <image-tag>
|
||||
|
||||
backend:
|
||||
image:
|
||||
repository: <image-name>
|
||||
tag: <image-tag>
|
||||
|
||||
lighthouse:
|
||||
image:
|
||||
repository: <image-name
|
||||
tag: <image-tag>
|
||||
```
|
||||
|
||||
### Different namespace
|
||||
|
||||
To install the charts a specific namespace use `--namespace <ns>`:
|
||||
|
||||
```
|
||||
helm install -f my_values.yaml --namespace demos backstage .
|
||||
```
|
||||
|
||||
### Disable loading of demo data
|
||||
|
||||
To deploy backstage with the pre-loaded demo data disable `backend.demoData`:
|
||||
|
||||
```
|
||||
helm install -f my_values.yaml --set backend.demoData=false backstage .
|
||||
```
|
||||
|
||||
### Other options
|
||||
|
||||
For more customization options take a look at the [values.yaml](/contrib/chart/backstage/values.yaml) file.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
Some resources created by these charts are meant to survive after upgrades and even after uninstalls. When
|
||||
troubleshooting these charts it can be useful to delete these resources between re-installs.
|
||||
|
||||
Secrets:
|
||||
|
||||
```
|
||||
<release-name>-postgresql-certs -- contains the certificates used by the deployed PostgreSQL
|
||||
```
|
||||
|
||||
Persistent volumes:
|
||||
|
||||
```
|
||||
data-<release-name>-postgresql-0 -- this is the data volume used by PostgreSQL to store data and configuration
|
||||
```
|
||||
|
||||
> **NOTE**: this volume also stores the configuration for PostgreSQL which includes things like the password for the
|
||||
> `postgres` user. This means that uninstalling and re-installing the charts with `postgres.enabled` set to `true` and
|
||||
> auto generated passwords will fail. The solution is to delete this volume with
|
||||
> `kubectl delete pvc data-<release-name>-postgresql-0`
|
||||
|
||||
ConfigMaps:
|
||||
|
||||
```
|
||||
<release-name>-postgres-ca -- contains the generated CA certificate for PostgreSQL when `postgres` is enabled
|
||||
```
|
||||
|
||||
#### Unable to verify signature
|
||||
|
||||
```
|
||||
Backend failed to start up Error: unable to verify the first certificate
|
||||
at TLSSocket.onConnectSecure (_tls_wrap.js:1501:34)
|
||||
at TLSSocket.emit (events.js:315:20)
|
||||
at TLSSocket._finishInit (_tls_wrap.js:936:8)
|
||||
at TLSWrap.ssl.onhandshakedone (_tls_wrap.js:710:12) {
|
||||
code: 'UNABLE_TO_VERIFY_LEAF_SIGNATURE'
|
||||
```
|
||||
|
||||
This error happens in the backend when it tries to connect to the configured PostgreSQL database and the specified CA is not correct. The solution is to make sure that the contents of the `configMap` that holds the certificate match the CA for the PostgreSQL instance. A workaround is to set `appConfig.backend.database.connection.ssl.rejectUnauthorized` to `false` in the chart's values.
|
||||
|
||||
<!-- TODO Add example command when we know the final name of the charts -->
|
||||
|
||||
## Uninstalling Backstage
|
||||
|
||||
To uninstall Backstage simply run:
|
||||
|
||||
```
|
||||
RELEASE_NAME=<release-name> # use `helm list` to find out the name
|
||||
helm uninstall ${RELEASE_NAME}
|
||||
kubectl delete pvc data-${RELEASE_NAME}-postgresql-0
|
||||
kubectl delete secret ${RELEASE_NAME}-postgresql-certs
|
||||
kubectl delete configMap ${RELEASE_NAME}-postgres-ca
|
||||
```
|
||||
@@ -0,0 +1,51 @@
|
||||
backend:
|
||||
lighthouseHostname: {{ include "lighthouse.serviceName" . | quote }}
|
||||
listen:
|
||||
port: {{ .Values.appConfig.backend.listen.port | default 7000 }}
|
||||
database:
|
||||
client: {{ .Values.appConfig.backend.database.client | quote }}
|
||||
connection:
|
||||
host: {{ include "backend.postgresql.host" . | quote }}
|
||||
port: {{ include "backend.postgresql.port" . | quote }}
|
||||
user: {{ include "backend.postgresql.user" . | quote }}
|
||||
database: {{ .Values.appConfig.backend.database.connection.database | quote }}
|
||||
ssl:
|
||||
rejectUnauthorized: {{ .Values.appConfig.backend.database.connection.ssl.rejectUnauthorized | quote }}
|
||||
ca: {{ include "backstage.backend.postgresCaFilename" . | quote }}
|
||||
|
||||
catalog:
|
||||
{{- if .Values.backend.demoData }}
|
||||
locations:
|
||||
# Backstage example components
|
||||
- type: github
|
||||
target: https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/all-components.yaml
|
||||
# Example component for github-actions
|
||||
- type: github
|
||||
target: https://github.com/spotify/backstage/blob/master/plugins/github-actions/examples/sample.yaml
|
||||
# Example component for techdocs
|
||||
- type: github
|
||||
target: https://github.com/spotify/backstage/blob/master/plugins/techdocs-backend/examples/documented-component/documented-component.yaml
|
||||
# Backstage example APIs
|
||||
- type: github
|
||||
target: https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml
|
||||
# Backstage example templates
|
||||
- type: github
|
||||
target: https://github.com/spotify/backstage/blob/master/plugins/scaffolder-backend/sample-templates/all-templates.yaml
|
||||
{{- else }}
|
||||
locations: []
|
||||
{{- end }}
|
||||
|
||||
auth:
|
||||
providers:
|
||||
microsoft: null
|
||||
|
||||
scaffolder:
|
||||
azure: null
|
||||
|
||||
|
||||
sentry:
|
||||
organization: {{ .Values.appConfig.sentry.organization | quote }}
|
||||
|
||||
techdocs:
|
||||
generators:
|
||||
techdocs: 'local'
|
||||
@@ -0,0 +1,13 @@
|
||||
{{ $backendDb := .Values.appConfig.backend.database.connection.database }}
|
||||
{{ $lighthouseDb := .Values.lighthouse.database.connection.database }}
|
||||
{{ $user := .Values.global.postgresql.postgresqlUsername }}
|
||||
|
||||
grant all privileges on database {{ $backendDb }} to {{ $user }};
|
||||
|
||||
create database backstage_plugin_auth;
|
||||
grant all privileges on database backstage_plugin_auth to {{ $user }};
|
||||
|
||||
{{ if not (eq $backendDb $lighthouseDb) }}
|
||||
create database {{ $lighthouseDb }};
|
||||
grant all privileges on database {{ $lighthouseDb }} to {{ $user }};
|
||||
{{ end }}
|
||||
@@ -0,0 +1,286 @@
|
||||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "backstage.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Create a default fully qualified app name.
|
||||
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||
If release name contains chart name it will be used as a full name.
|
||||
*/}}
|
||||
{{- define "backstage.fullname" -}}
|
||||
{{- if .Values.fullnameOverride -}}
|
||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
|
||||
{{- else -}}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride -}}
|
||||
{{- if contains $name .Release.Name -}}
|
||||
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
|
||||
{{- else -}}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Create chart name and version as used by the chart label.
|
||||
*/}}
|
||||
{{- define "backstage.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Common App labels
|
||||
*/}}
|
||||
{{- define "backstage.app.labels" -}}
|
||||
app.kubernetes.io/name: {{ include "backstage.name" . }}-app
|
||||
helm.sh/chart: {{ include "backstage.chart" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- if .Chart.AppVersion }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
{{- end }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Common Backend labels
|
||||
*/}}
|
||||
{{- define "backstage.backend.labels" -}}
|
||||
app.kubernetes.io/name: {{ include "backstage.name" . }}-backend
|
||||
helm.sh/chart: {{ include "backstage.chart" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- if .Chart.AppVersion }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
{{- end }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Name for postgresql dependency
|
||||
See https://github.com/helm/helm/issues/3920#issuecomment-686913512
|
||||
*/}}
|
||||
{{- define "backstage.postgresql.fullname" -}}
|
||||
{{ printf "%s-%s" .Release.Name .Values.postgresql.nameOverride }}
|
||||
{{- end -}}
|
||||
|
||||
|
||||
{{/*
|
||||
Create the name of the service account to use for the app
|
||||
*/}}
|
||||
{{- define "backstage.app.serviceAccountName" -}}
|
||||
{{- if .Values.app.serviceAccount.create -}}
|
||||
{{ default "default" .Values.app.serviceAccount.name }}
|
||||
{{- else -}}
|
||||
{{ default "default" .Values.app.serviceAccount.name }}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Create the name of the service account to use for the backend
|
||||
*/}}
|
||||
{{- define "backstage.backend.serviceAccountName" -}}
|
||||
{{- if .Values.backend.serviceAccount.create -}}
|
||||
{{ default default .Values.backend.serviceAccount.name }}
|
||||
{{- else -}}
|
||||
{{ default "default" .Values.backend.serviceAccount.name }}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Path to the CA certificate file in the backend
|
||||
*/}}
|
||||
{{- define "backstage.backend.postgresCaFilename" -}}
|
||||
{{ include "backstage.backend.postgresCaDir" . }}/{{- required "The name for the CA certificate file for postgresql is required" .Values.global.postgresql.caFilename }}
|
||||
{{- end -}}
|
||||
{{/*
|
||||
|
||||
{{/*
|
||||
Directory path to the CA certificate file in the backend
|
||||
*/}}
|
||||
{{- define "backstage.backend.postgresCaDir" -}}
|
||||
{{- if .Values.appConfig.backend.database.connection.ssl.ca -}}
|
||||
{{ .Values.appConfig.backend.database.connection.ssl.ca }}
|
||||
{{- else -}}
|
||||
/etc/postgresql
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{/*
|
||||
|
||||
Path to the CA certificate file in lighthouse
|
||||
*/}}
|
||||
{{- define "backstage.lighthouse.postgresCaFilename" -}}
|
||||
{{ include "backstage.lighthouse.postgresCaDir" . }}/{{- required "The name for the CA certificate file for postgresql is required" .Values.global.postgresql.caFilename }}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Directory path to the CA certificate file in lighthouse
|
||||
*/}}
|
||||
{{- define "backstage.lighthouse.postgresCaDir" -}}
|
||||
{{- if .Values.lighthouse.database.pathToDatabaseCa -}}
|
||||
{{ .Values.lighthouse.database.pathToDatabaseCa }}
|
||||
{{- else -}}
|
||||
/etc/postgresql
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{/*
|
||||
|
||||
{{/*
|
||||
Generate ca for postgresql
|
||||
*/}}
|
||||
{{- define "backstage.postgresql.generateCA" -}}
|
||||
{{- $ca := .ca | default (genCA (include "backstage.postgresql.fullname" .) 365) -}}
|
||||
{{- $_ := set . "ca" $ca -}}
|
||||
{{- $ca.Cert -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Generate certificates for postgresql
|
||||
*/}}
|
||||
{{- define "generateCerts" -}}
|
||||
{{- $postgresName := (include "backstage.postgresql.fullname" .) }}
|
||||
{{- $altNames := list $postgresName ( printf "%s.%s" $postgresName .Release.Namespace ) ( printf "%s.%s.svc" ( $postgresName ) .Release.Namespace ) -}}
|
||||
{{- $ca := .ca | default (genCA (include "backstage.postgresql.fullname" .) 365) -}}
|
||||
{{- $_ := set . "ca" $ca -}}
|
||||
{{- $cert := genSignedCert ( $postgresName ) nil $altNames 365 $ca -}}
|
||||
tls.crt: {{ $cert.Cert | b64enc }}
|
||||
tls.key: {{ $cert.Key | b64enc }}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Generate a password for the postgres user used for the connections from the backend and lighthouse
|
||||
*/}}
|
||||
{{- define "postgresql.generateUserPassword" -}}
|
||||
{{- $pgPassword := .pgPassword | default ( randAlphaNum 12 ) -}}
|
||||
{{- $_ := set . "pgPassword" $pgPassword -}}
|
||||
{{ $pgPassword}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Name of the backend service
|
||||
*/}}
|
||||
{{- define "backend.serviceName" -}}
|
||||
{{ include "backstage.fullname" . }}-backend
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Name of the frontend service
|
||||
*/}}
|
||||
{{- define "frontend.serviceName" -}}
|
||||
{{ include "backstage.fullname" . }}-frontend
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Name of the lighthouse backend service
|
||||
*/}}
|
||||
{{- define "lighthouse.serviceName" -}}
|
||||
{{ include "backstage.fullname" . }}-lighthouse
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Name of the postgresql service
|
||||
*/}}
|
||||
{{- define "postgresql.serviceName" -}}
|
||||
{{- include "backstage.postgresql.fullname" . }}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Postgres host for lighthouse
|
||||
*/}}
|
||||
{{- define "lighthouse.postgresql.host" -}}
|
||||
{{- if .Values.postgresql.enabled }}
|
||||
{{- include "postgresql.serviceName" . }}
|
||||
{{- else -}}
|
||||
{{- required "A valid .Values.lighthouse.database.connection.host is required when postgresql is not enabled" .Values.lighthouse.database.connection.host -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Postgres host for the backend
|
||||
*/}}
|
||||
{{- define "backend.postgresql.host" -}}
|
||||
{{- if .Values.postgresql.enabled }}
|
||||
{{- include "postgresql.serviceName" . }}
|
||||
{{- else -}}
|
||||
{{- required "A valid .Values.appConfig.backend.database.connection.host is required when postgresql is not enabled" .Values.appConfig.backend.database.connection.host -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Postgres port for the backend
|
||||
*/}}
|
||||
{{- define "backend.postgresql.port" -}}
|
||||
{{- if .Values.postgresql.enabled }}
|
||||
{{- .Values.postgresql.service.port }}
|
||||
{{- else if .Values.appConfig.backend.database.connection.port -}}
|
||||
{{- .Values.appConfig.backend.database.connection.port }}
|
||||
{{ else }}
|
||||
5432
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Postgres port for lighthouse
|
||||
*/}}
|
||||
{{- define "lighthouse.postgresql.port" -}}
|
||||
{{- if .Values.postgresql.enabled }}
|
||||
{{- .Values.postgresql.service.port }}
|
||||
{{- else if .Values.lighthouse.database.connection.port -}}
|
||||
{{- .Values.lighthouse.database.connection.port }}
|
||||
{{- else -}}
|
||||
5432
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Postgres user for backend
|
||||
*/}}
|
||||
{{- define "backend.postgresql.user" -}}
|
||||
{{- if .Values.postgresql.enabled }}
|
||||
{{- .Values.global.postgresql.postgresqlUsername }}
|
||||
{{- else -}}
|
||||
{{- required "A valid .Values.appConfig.backend.database.connection.user is required when postgresql is not enabled" .Values.appConfig.backend.database.connection.user -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Postgres user for lighthouse
|
||||
*/}}
|
||||
{{- define "lighthouse.postgresql.user" -}}
|
||||
{{- if .Values.postgresql.enabled }}
|
||||
{{- .Values.global.postgresql.postgresqlUsername }}
|
||||
{{- else -}}
|
||||
{{- required "A valid .Values.lighthouse.database.connection.user is required when postgresql is not enabled" .Values.lighthouse.database.connection.user -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Postgres password secret for backend
|
||||
*/}}
|
||||
{{- define "backend.postgresql.passwordSecret" -}}
|
||||
{{- if .Values.postgresql.enabled }}
|
||||
{{- template "backstage.postgresql.fullname" . }}
|
||||
{{- else -}}
|
||||
{{ $secretName := (printf "%s-backend-postgres" (include "backstage.fullname" . )) }}
|
||||
{{- required "A valid .Values.appConfig.backend.database.connection.password is required when postgresql is not enabled" $secretName -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Postgres password for lighthouse
|
||||
*/}}
|
||||
{{- define "lighthouse.postgresql.passwordSecret" -}}
|
||||
{{- if .Values.postgresql.enabled }}
|
||||
{{- template "backstage.postgresql.fullname" . }}
|
||||
{{- else -}}
|
||||
{{ $secretName := (printf "%s-lighthouse-postgres" (include "backstage.fullname" . )) }}
|
||||
{{- required "A valid .Values.lighthouse.database.connection.password is required when postgresql is not enabled" $secretName -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
app-config file name
|
||||
*/}}
|
||||
{{- define "backstage.appConfigFilename" -}}
|
||||
{{- "app-config.development.yaml" -}}
|
||||
{{- end -}}
|
||||
@@ -0,0 +1,79 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "backstage.fullname" . }}-backend
|
||||
|
||||
spec:
|
||||
replicas: {{ .Values.backend.replicaCount }}
|
||||
|
||||
selector:
|
||||
matchLabels:
|
||||
app: backstage
|
||||
component: backend
|
||||
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
ad.datadoghq.com/backstage.logs: '[{"source":"backstage","service":"backend"}]'
|
||||
labels:
|
||||
app: backstage
|
||||
component: backend
|
||||
|
||||
spec:
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}-backend
|
||||
image: {{ .Values.backend.image.repository }}:{{ .Values.backend.image.tag }}
|
||||
imagePullPolicy: {{ .Values.backend.image.pullPolicy }}
|
||||
ports:
|
||||
- containerPort: {{ .Values.backend.containerPort }}
|
||||
resources:
|
||||
{{- toYaml .Values.backend.resources | nindent 12 }}
|
||||
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: {{ include "backstage.fullname" . }}-backend
|
||||
- configMapRef:
|
||||
name: {{ include "backstage.fullname" . }}-app-env
|
||||
- configMapRef:
|
||||
name: {{ include "backstage.fullname" . }}-auth
|
||||
env:
|
||||
- name: NODE_ENV
|
||||
value: {{ .Values.backend.nodeEnv | default "development" }}
|
||||
- name: APP_CONFIG_backend_database_connection_password
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "backend.postgresql.passwordSecret" .}}
|
||||
key: postgresql-password
|
||||
volumeMounts:
|
||||
- name: postgres-ca
|
||||
mountPath: {{ include "backstage.backend.postgresCaDir" . }}
|
||||
- name: app-config
|
||||
mountPath: {{ printf "/usr/src/app/%s" (include "backstage.appConfigFilename" .) }}
|
||||
subPath: {{ include "backstage.appConfigFilename" . }}
|
||||
|
||||
volumes:
|
||||
- name: postgres-ca
|
||||
configMap:
|
||||
name: {{ include "backstage.fullname" . }}-postgres-ca
|
||||
- name: app-config
|
||||
configMap:
|
||||
name: {{ include "backstage.fullname" . }}-app-config
|
||||
|
||||
{{- if .Values.backend.enabled }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "backend.serviceName" . }}
|
||||
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: {{ .Values.backend.containerPort }}
|
||||
|
||||
selector:
|
||||
app: backstage
|
||||
component: backend
|
||||
|
||||
type: ClusterIP
|
||||
{{- end }}
|
||||
@@ -0,0 +1,21 @@
|
||||
{{- if .Values.backend.enabled }}
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: {{ include "backstage.fullname" . }}-backend
|
||||
type: Opaque
|
||||
stringData:
|
||||
AUTH_GOOGLE_CLIENT_SECRET: {{ .Values.auth.google.clientSecret }}
|
||||
AUTH_GITHUB_CLIENT_SECRET: {{ .Values.auth.github.clientSecret }}
|
||||
AUTH_GITLAB_CLIENT_SECRET: {{ .Values.auth.gitlab.clientSecret }}
|
||||
AUTH_OKTA_CLIENT_SECRET: {{ .Values.auth.okta.clientSecret }}
|
||||
AUTH_OAUTH2_CLIENT_SECRET: {{ .Values.auth.oauth2.clientSecret }}
|
||||
AUTH_AUTH0_CLIENT_SECRET: {{ .Values.auth.auth0.clientSecret }}
|
||||
AUTH_MICROSOFT_CLIENT_SECRET: {{ .Values.auth.microsoft.clientSecret }}
|
||||
SENTRY_TOKEN: {{ .Values.auth.sentryToken }}
|
||||
ROLLBAR_ACCOUNT_TOKEN: {{ .Values.auth.rollbarAccountToken }}
|
||||
CIRCLECI_AUTH_TOKEN: {{ .Values.auth.circleciAuthToken }}
|
||||
GITHUB_TOKEN: {{ .Values.auth.githubToken }}
|
||||
GITLAB_TOKEN: {{ .Values.auth.gitlabToken }}
|
||||
AZURE_TOKEN: {{ .Values.auth.azure.api.token }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,27 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ include "backstage.fullname" . }}-app-config
|
||||
data:
|
||||
{{ include "backstage.appConfigFilename" . | indent 2 }}: |
|
||||
{{ tpl (.Files.Get "files/app-config.development.yaml.tpl") . | indent 4 }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ include "backstage.fullname" . }}-app-env
|
||||
data:
|
||||
APP_CONFIG_app_baseUrl: {{ .Values.appConfig.app.baseUrl | quote | quote }}
|
||||
APP_CONFIG_app_title: {{ .Values.appConfig.app.title | quote | quote }}
|
||||
APP_CONFIG_backend_baseUrl: {{ .Values.appConfig.backend.baseUrl | quote | quote }}
|
||||
APP_CONFIG_backend_cors_origin: {{ .Values.appConfig.backend.cors.origin | quote | quote }}
|
||||
APP_CONFIG_techdocs_storageUrl: {{ .Values.appConfig.techdocs.storageUrl | quote | quote }}
|
||||
APP_CONFIG_techdocs_requestUrl: {{ .Values.appConfig.techdocs.requestUrl | quote | quote }}
|
||||
APP_CONFIG_lighthouse_baseUrl: {{ .Values.appConfig.lighthouse.baseUrl | quote | quote }}
|
||||
APP_CONFIG_backend_database_connection_ssl_rejectUnauthorized: "false"
|
||||
APP_CONFIG_auth_providers_github_development_appOrigin: {{ .Values.appConfig.auth.providers.github.development.appOrigin | quote | quote }}
|
||||
APP_CONFIG_auth_providers_google_development_appOrigin: {{ .Values.appConfig.auth.providers.google.development.appOrigin | quote | quote }}
|
||||
APP_CONFIG_auth_providers_gitlab_development_appOrigin: {{ .Values.appConfig.auth.providers.gitlab.development.appOrigin | quote | quote }}
|
||||
APP_CONFIG_auth_providers_okta_development_appOrigin: {{ .Values.appConfig.auth.providers.okta.development.appOrigin | quote | quote }}
|
||||
APP_CONFIG_auth_providers_oauth2_development_appOrigin: {{ .Values.appConfig.auth.providers.oauth2.development.appOrigin | quote | quote }}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ include "backstage.fullname" . }}-auth
|
||||
data:
|
||||
AUTH_GOOGLE_CLIENT_ID: {{ .Values.auth.google.clientId }}
|
||||
AUTH_GITHUB_CLIENT_ID: {{ .Values.auth.github.clientId }}
|
||||
AUTH_GITLAB_CLIENT_ID: {{ .Values.auth.gitlab.clientId }}
|
||||
# This should not be prefixed with AUTH_. This could be a typo in the Backstage app config.
|
||||
# Regardless, it is not decided by me.
|
||||
GITLAB_BASE_URL: {{ .Values.auth.gitlab.baseUrl }}
|
||||
AUTH_OKTA_CLIENT_ID: {{ .Values.auth.okta.clientId }}
|
||||
AUTH_OKTA_AUDIENCE: {{ .Values.auth.okta.audience }}
|
||||
AUTH_OAUTH2_CLIENT_ID: {{ .Values.auth.oauth2.clientId }}
|
||||
AUTH_OAUTH2_AUTH_URL: {{ .Values.auth.oauth2.authUrl }}
|
||||
AUTH_OAUTH2_TOKEN_URL: {{ .Values.auth.oauth2.tokenUrl }}
|
||||
AUTH_AUTH0_CLIENT_ID: {{ .Values.auth.auth0.clientId }}
|
||||
AUTH_AUTH0_DOMAIN: {{ .Values.auth.auth0.domain }}
|
||||
AUTH_MICROSOFT_CLIENT_ID: {{ .Values.auth.microsoft.clientId }}
|
||||
AUTH_MICROSOFT_TENANT_ID: {{ .Values.auth.microsoft.tenantId }}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "backstage.fullname" . }}-frontend
|
||||
|
||||
spec:
|
||||
replicas: {{ .Values.frontend.replicaCount }}
|
||||
|
||||
selector:
|
||||
matchLabels:
|
||||
app: backstage
|
||||
component: frontend
|
||||
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
ad.datadoghq.com/backstage.logs: '[{"source":"backstage","service":"frontend"}]'
|
||||
labels:
|
||||
app: backstage
|
||||
component: frontend
|
||||
|
||||
spec:
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}-frontend
|
||||
image: {{ .Values.frontend.image.repository }}:{{ .Values.frontend.image.tag }}
|
||||
imagePullPolicy: {{ .Values.frontend.image.pullPolicy }}
|
||||
ports:
|
||||
- containerPort: {{ .Values.frontend.containerPort }}
|
||||
resources:
|
||||
{{- toYaml .Values.backend.resources | nindent 12 }}
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: {{ include "backstage.fullname" . }}-app-env
|
||||
volumeMounts:
|
||||
- name: app-config
|
||||
mountPath: {{ printf "/usr/share/nginx/html/static/%s" (include "backstage.appConfigFilename" .) }}
|
||||
subPath: {{ include "backstage.appConfigFilename" . }}
|
||||
volumes:
|
||||
- name: app-config
|
||||
configMap:
|
||||
name: {{ include "backstage.fullname" . }}-app-config
|
||||
{{- if .Values.frontend.enabled }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "frontend.serviceName" . }}
|
||||
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: {{ .Values.frontend.containerPort }}
|
||||
|
||||
selector:
|
||||
app: backstage
|
||||
component: frontend
|
||||
|
||||
type: ClusterIP
|
||||
{{- end }}
|
||||
@@ -0,0 +1,78 @@
|
||||
{{- $frontendUrl := urlParse .Values.appConfig.app.baseUrl}}
|
||||
{{- $backendUrl := urlParse .Values.appConfig.backend.baseUrl}}
|
||||
{{- $lighthouseUrl := urlParse .Values.appConfig.lighthouse.baseUrl}}
|
||||
apiVersion: networking.k8s.io/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ include "backstage.fullname" . }}-ingress
|
||||
{{- with .Values.ingress.annotations }}
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/ssl-redirect: "false"
|
||||
nginx.ingress.kubernetes.io/configuration-snippet: |
|
||||
if ($scheme = https) {
|
||||
add_header Strict-Transport-Security "max-age=0;";
|
||||
}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
rules:
|
||||
- host: {{ $frontendUrl.host }}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
backend:
|
||||
serviceName: {{ include "frontend.serviceName" . }}
|
||||
servicePort: 80
|
||||
{{/* Route the backend inside the same hostname as the frontend when they are the same */}}
|
||||
{{- if eq $frontendUrl.host $backendUrl.host}}
|
||||
- path: /api
|
||||
backend:
|
||||
serviceName: {{ include "backend.serviceName" . }}
|
||||
servicePort: 80
|
||||
{{/* Route the backend through a different host */}}
|
||||
{{- else -}}
|
||||
- host: {{ $backendUrl.host }}
|
||||
http:
|
||||
paths:
|
||||
- path: {{ $backendUrl.path | default "/" }}
|
||||
backend:
|
||||
serviceName: {{ include "backend.serviceName" . }}
|
||||
servicePort: 80
|
||||
{{- end }}
|
||||
|
||||
{{/* Route lighthouse through a different host */}}
|
||||
{{- if not ( eq $frontendUrl.host $lighthouseUrl.host ) }}
|
||||
- host: {{ $lighthouseUrl.host }}
|
||||
http:
|
||||
paths:
|
||||
- path: {{ $lighthouseUrl.path | default "/" }}
|
||||
backend:
|
||||
serviceName: {{ include "lighthouse.serviceName" . }}
|
||||
servicePort: 80
|
||||
{{- else }}
|
||||
{{/* Route lighthouse by path with re-write rules when it is hosted under the same hostname */}}
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ include "backstage.fullname" . }}-ingress-lighthouse
|
||||
{{- with .Values.ingress.annotations }}
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/rewrite-target: /$2
|
||||
nginx.ingress.kubernetes.io/ssl-redirect: "false"
|
||||
nginx.ingress.kubernetes.io/configuration-snippet: |
|
||||
if ($scheme = https) {
|
||||
add_header Strict-Transport-Security "max-age=0;";
|
||||
}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
rules:
|
||||
- host: {{ $frontendUrl.host }}
|
||||
http:
|
||||
paths:
|
||||
- path: {{$lighthouseUrl.path}}(/|$)(.*)
|
||||
backend:
|
||||
serviceName: {{ include "lighthouse.serviceName" . }}
|
||||
servicePort: 80
|
||||
{{- end }}
|
||||
@@ -0,0 +1,10 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ include "backstage.fullname" . -}}-lighthouse
|
||||
data:
|
||||
PGDATABASE: {{ .Values.lighthouse.database.connection.database | quote }}
|
||||
PGUSER: {{ include "lighthouse.postgresql.user" . | quote }}
|
||||
PGPORT: {{ include "lighthouse.postgresql.port" . | quote }}
|
||||
PGHOST: {{ include "lighthouse.postgresql.host" . | quote }}
|
||||
PGPATH_TO_CA: {{ include "backstage.lighthouse.postgresCaFilename" . | quote }}
|
||||
@@ -0,0 +1,74 @@
|
||||
{{- if .Values.lighthouse.enabled }}
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "backstage.fullname" . }}-lighthouse
|
||||
|
||||
spec:
|
||||
replicas: {{ .Values.lighthouse.replicaCount }}
|
||||
|
||||
selector:
|
||||
matchLabels:
|
||||
app: backstage
|
||||
component: lighthouse-audit-service
|
||||
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
ad.datadoghq.com/backstage.logs: '[{"source":"backstage","service":"lighthouse"}]'
|
||||
labels:
|
||||
app: backstage
|
||||
component: lighthouse-audit-service
|
||||
|
||||
spec:
|
||||
containers:
|
||||
- name: lighthouse-audit-service
|
||||
image: {{ .Values.lighthouse.image.repository }}:{{ .Values.lighthouse.image.tag }}
|
||||
imagePullPolicy: {{ .Values.lighthouse.image.pullPolicy }}
|
||||
ports:
|
||||
- containerPort: {{ .Values.lighthouse.containerPort }}
|
||||
resources:
|
||||
{{- toYaml .Values.lighthouse.resources | nindent 12 }}
|
||||
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: {{ include "backstage.fullname" . -}}-lighthouse
|
||||
- configMapRef:
|
||||
name: {{ include "backstage.fullname" . }}-app-env
|
||||
|
||||
env:
|
||||
- name: LAS_PORT
|
||||
value: {{ .Values.lighthouse.containerPort | quote }}
|
||||
- name: LAS_CORS
|
||||
value: "true"
|
||||
- name: PGPASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "lighthouse.postgresql.passwordSecret" . }}
|
||||
key: postgresql-password
|
||||
|
||||
volumeMounts:
|
||||
- name: postgres-ca
|
||||
mountPath: {{ include "backstage.lighthouse.postgresCaDir" . }}
|
||||
|
||||
volumes:
|
||||
- name: postgres-ca
|
||||
configMap:
|
||||
name: {{ include "backstage.fullname" . }}-postgres-ca
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "lighthouse.serviceName" . }}
|
||||
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: {{ .Values.lighthouse.containerPort }}
|
||||
|
||||
selector:
|
||||
app: backstage
|
||||
component: lighthouse-audit-service
|
||||
|
||||
type: ClusterIP
|
||||
{{- end }}
|
||||
@@ -0,0 +1,21 @@
|
||||
{{- if .Values.postgresql.enabled }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ include "backstage.fullname" . }}-postgres-ca
|
||||
labels:
|
||||
app: {{ include "backstage.postgresql.fullname" . }}
|
||||
release: {{ .Release.Name }}
|
||||
annotations:
|
||||
"helm.sh/hook": "pre-install"
|
||||
"helm.sh/hook-delete-policy": "before-hook-creation"
|
||||
data:
|
||||
{{ .Values.global.postgresql.caFilename }}: |
|
||||
{{ include "backstage.postgresql.generateCA" . | indent 4}}
|
||||
{{- else }}
|
||||
{{- $caConfig := printf "%s-postgres-ca" (include "backstage.fullname" .) }}
|
||||
{{- if not ( lookup "v1" "ConfigMap" .Release.Namespace $caConfig ) }}
|
||||
{{- fail (printf "\n\nPlease create the '%s' configmap with the CA certificate for your existing postgresql: kubectl create configmap %s --from-file=ca.crt" $caConfig $caConfig) }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,16 @@
|
||||
{{- if .Values.postgresql.enabled }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
type: kubernetes.io/tls
|
||||
metadata:
|
||||
name: {{ required ".Values.postgresql.tls.certificatesSecret is required" .Values.postgresql.tls.certificatesSecret }}
|
||||
labels:
|
||||
app: {{ include "backstage.postgresql.fullname" . }}
|
||||
release: {{ .Release.Name }}
|
||||
annotations:
|
||||
"helm.sh/hook": "pre-install"
|
||||
"helm.sh/hook-delete-policy": "before-hook-creation"
|
||||
data:
|
||||
{{ include "generateCerts" . | indent 2 }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,14 @@
|
||||
{{- if .Values.postgresql.enabled }}
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: {{ required ".Values.postgresql.initdbScriptsSecret is required" .Values.postgresql.initdbScriptsSecret }}
|
||||
annotations:
|
||||
"helm.sh/hook": "pre-install"
|
||||
"helm.sh/hook-delete-policy": "before-hook-creation"
|
||||
type: Opaque
|
||||
data:
|
||||
create-backend-dbs.sql: |
|
||||
{{ tpl (.Files.Get "files/create-backend-dbs.sql") . | b64enc }}
|
||||
{{- end }}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
{{- if not .Values.postgresql.enabled }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
type: Opaque
|
||||
metadata:
|
||||
name: {{ include "backend.postgresql.passwordSecret" . }}
|
||||
labels:
|
||||
release: {{ .Release.Name }}
|
||||
annotations:
|
||||
"helm.sh/hook": "pre-install"
|
||||
"helm.sh/hook-delete-policy": "before-hook-creation"
|
||||
data:
|
||||
postgresql-password: {{ .Values.appConfig.backend.database.connection.password | b64enc }}
|
||||
{{- end }}
|
||||
{{- if not .Values.postgresql.enabled }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
type: Opaque
|
||||
metadata:
|
||||
name: {{ include "lighthouse.postgresql.passwordSecret" . }}
|
||||
labels:
|
||||
release: {{ .Release.Name }}
|
||||
annotations:
|
||||
"helm.sh/hook": "pre-install"
|
||||
"helm.sh/hook-delete-policy": "before-hook-creation"
|
||||
data:
|
||||
postgresql-password: {{ .Values.lighthouse.database.connection.password | b64enc }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: {{ include "backstage.fullname" . -}}-test-app-connection
|
||||
annotations:
|
||||
"helm.sh/hook": test
|
||||
spec:
|
||||
containers:
|
||||
- name: test-app
|
||||
image: busybox
|
||||
command:
|
||||
- /bin/sh
|
||||
- -ecx
|
||||
- |
|
||||
echo -e "===== Testing the connection with the frontend...\n"
|
||||
wget -q -O - {{ printf "%s.%s" (include "frontend.serviceName" .) .Release.Namespace | quote }}
|
||||
echo -e "\n\n===== Testing the connection with the backend...\n"
|
||||
wget -q -O - {{ printf "http://%s.%s/catalog/entities" (include "backend.serviceName" .) .Release.Namespace | quote }}
|
||||
echo -e "\n\n===== Testing the connection with the lighthouse plugin...\n"
|
||||
wget -q -O - {{ printf "%s.%s/v1/audits" (include "lighthouse.serviceName" .) .Release.Namespace | quote }}
|
||||
echo -e "\n"
|
||||
restartPolicy: Never
|
||||
@@ -0,0 +1,33 @@
|
||||
{{- if .Values.postgresql.enabled}}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: {{ include "backstage.name" . -}}-test-postgres
|
||||
annotations:
|
||||
"helm.sh/hook": test
|
||||
spec:
|
||||
containers:
|
||||
- name: postgresql-client
|
||||
image: bitnami/postgresql
|
||||
env:
|
||||
- name: PG_HOST
|
||||
value: {{ include "postgresql.serviceName" . | quote }}
|
||||
- name: PG_PORT
|
||||
value: {{ .Values.postgresql.service.port | quote }}
|
||||
- name: PG_USER
|
||||
value: {{ .Values.global.postgresql.postgresqlUsername | quote }}
|
||||
- name: PGPASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "backend.postgresql.passwordSecret" .}}
|
||||
key: postgresql-password
|
||||
- name: PG_DBNAME
|
||||
value: {{ .Values.appConfig.backend.database.connection.database }}
|
||||
command:
|
||||
- /bin/bash
|
||||
- -ecx
|
||||
- |
|
||||
psql --host=$PG_HOST --port=$PG_PORT --username=$PG_USER --dbname=$PG_DBNAME --no-password
|
||||
restartPolicy: Never
|
||||
{{- end }}
|
||||
@@ -0,0 +1,245 @@
|
||||
# Default values for backstage.
|
||||
# This is a YAML-formatted file.
|
||||
# Declare variables to be passed into your templates.
|
||||
|
||||
frontend:
|
||||
enabled: true
|
||||
replicaCount: 1
|
||||
image:
|
||||
repository: martinaif/backstage-k8s-demo-frontend
|
||||
tag: test1
|
||||
pullPolicy: IfNotPresent
|
||||
containerPort: 80
|
||||
resources:
|
||||
requests:
|
||||
memory: 128Mi
|
||||
limits:
|
||||
memory: 256Mi
|
||||
|
||||
backend:
|
||||
enabled: true
|
||||
nodeEnv: development
|
||||
demoData: true
|
||||
replicaCount: 1
|
||||
image:
|
||||
repository: martinaif/backstage-k8s-demo-backend
|
||||
tag: test1
|
||||
pullPolicy: IfNotPresent
|
||||
containerPort: 7000
|
||||
resources:
|
||||
requests:
|
||||
memory: 512Mi
|
||||
limits:
|
||||
memory: 1024Mi
|
||||
|
||||
lighthouse:
|
||||
enabled: true
|
||||
replicaCount: 1
|
||||
image:
|
||||
repository: roadiehq/lighthouse-audit-service
|
||||
tag: latest
|
||||
pullPolicy: IfNotPresent
|
||||
containerPort: 3003
|
||||
resources:
|
||||
requests:
|
||||
memory: 128Mi
|
||||
limits:
|
||||
memory: 256Mi
|
||||
database:
|
||||
connection:
|
||||
port:
|
||||
host:
|
||||
user:
|
||||
password:
|
||||
database: lighthouse_audit_service
|
||||
pathToDatabaseCa:
|
||||
|
||||
nameOverride: ''
|
||||
fullnameOverride: ''
|
||||
|
||||
ingress:
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: nginx
|
||||
|
||||
global:
|
||||
postgresql:
|
||||
postgresqlUsername: backend-user
|
||||
caFilename: ca.crt
|
||||
|
||||
postgresql:
|
||||
enabled: true
|
||||
nameOverride: postgresql
|
||||
tls:
|
||||
enabled: true
|
||||
certificatesSecret: backstage-postgresql-certs
|
||||
certFilename: tls.crt
|
||||
certKeyFilename: tls.key
|
||||
volumePermissions:
|
||||
enabled: true
|
||||
initdbScriptsSecret: backstage-postgresql-initdb
|
||||
|
||||
appConfig:
|
||||
app:
|
||||
baseUrl: https://demo.example.com
|
||||
title: Backstage
|
||||
backend:
|
||||
baseUrl: https://demo.example.com
|
||||
listen:
|
||||
port: 7000
|
||||
cors:
|
||||
origin: https://demo.example.com
|
||||
database:
|
||||
client: pg
|
||||
connection:
|
||||
database: backstage_plugin_catalog
|
||||
host:
|
||||
user:
|
||||
port:
|
||||
password:
|
||||
ssl:
|
||||
rejectUnauthorized: false
|
||||
ca:
|
||||
sentry:
|
||||
organization: spotify
|
||||
techdocs:
|
||||
storageUrl: https://demo.example.com/api/techdocs/static/docs
|
||||
requestUrl: https://demo.example.com/api/techdocs
|
||||
lighthouse:
|
||||
baseUrl: https://demo.example.com/lighthouse-api
|
||||
rollbar:
|
||||
organization: roadie
|
||||
|
||||
# Auth config has recently moved into the app config file in upstream Backstage. However,
|
||||
# most of this config simply mandates that items like the client id and client secret should
|
||||
# be picked up from the environment variables named below. Those environment variables are
|
||||
# set in this helm controlled environment by the 'auth' configuration below this section.
|
||||
# Thus, the only key in this config which directly controls an app config is the
|
||||
# auth.providers.github.development.appOrigin property.
|
||||
auth:
|
||||
providers:
|
||||
google:
|
||||
development:
|
||||
appOrigin: 'http://localhost:3000/'
|
||||
secure: false
|
||||
clientId:
|
||||
$secret:
|
||||
env: AUTH_GOOGLE_CLIENT_ID
|
||||
clientSecret:
|
||||
$secret:
|
||||
env: AUTH_GOOGLE_CLIENT_SECRET
|
||||
github:
|
||||
development:
|
||||
appOrigin: 'http://localhost:3000/'
|
||||
secure: false
|
||||
clientId:
|
||||
$secret:
|
||||
env: AUTH_GITHUB_CLIENT_ID
|
||||
clientSecret:
|
||||
$secret:
|
||||
env: AUTH_GITHUB_CLIENT_SECRET
|
||||
enterpriseInstanceUrl:
|
||||
$secret:
|
||||
env: AUTH_GITHUB_ENTERPRISE_INSTANCE_URL
|
||||
gitlab:
|
||||
development:
|
||||
appOrigin: 'http://localhost:3000/'
|
||||
secure: false
|
||||
clientId:
|
||||
$secret:
|
||||
env: AUTH_GITLAB_CLIENT_ID
|
||||
clientSecret:
|
||||
$secret:
|
||||
env: AUTH_GITLAB_CLIENT_SECRET
|
||||
audience:
|
||||
$secret:
|
||||
env: GITLAB_BASE_URL
|
||||
okta:
|
||||
development:
|
||||
appOrigin: 'http://localhost:3000/'
|
||||
secure: false
|
||||
clientId:
|
||||
$secret:
|
||||
env: AUTH_OKTA_CLIENT_ID
|
||||
clientSecret:
|
||||
$secret:
|
||||
env: AUTH_OKTA_CLIENT_SECRET
|
||||
audience:
|
||||
$secret:
|
||||
env: AUTH_OKTA_AUDIENCE
|
||||
oauth2:
|
||||
development:
|
||||
appOrigin: 'http://localhost:3000/'
|
||||
secure: false
|
||||
clientId:
|
||||
$secret:
|
||||
env: AUTH_OAUTH2_CLIENT_ID
|
||||
clientSecret:
|
||||
$secret:
|
||||
env: AUTH_OAUTH2_CLIENT_SECRET
|
||||
authorizationURL:
|
||||
$secret:
|
||||
env: AUTH_OAUTH2_AUTH_URL
|
||||
tokenURL:
|
||||
$secret:
|
||||
env: AUTH_OAUTH2_TOKEN_URL
|
||||
auth0:
|
||||
development:
|
||||
clientId:
|
||||
$secret:
|
||||
env: AUTH_AUTH0_CLIENT_ID
|
||||
clientSecret:
|
||||
$secret:
|
||||
env: AUTH_AUTH0_CLIENT_SECRET
|
||||
domain:
|
||||
$secret:
|
||||
env: AUTH_AUTH0_DOMAIN
|
||||
microsoft:
|
||||
development:
|
||||
clientId:
|
||||
$secret:
|
||||
env: AUTH_MICROSOFT_CLIENT_ID
|
||||
clientSecret:
|
||||
$secret:
|
||||
env: AUTH_MICROSOFT_CLIENT_SECRET
|
||||
tenantId:
|
||||
$secret:
|
||||
env: AUTH_MICROSOFT_TENANT_ID
|
||||
|
||||
auth:
|
||||
google:
|
||||
clientId: a
|
||||
clientSecret: a
|
||||
github:
|
||||
clientId: c
|
||||
clientSecret: c
|
||||
gitlab:
|
||||
clientId: b
|
||||
clientSecret: b
|
||||
baseUrl: b
|
||||
okta:
|
||||
clientId: b
|
||||
clientSecret: b
|
||||
audience: b
|
||||
oauth2:
|
||||
clientId: b
|
||||
clientSecret: b
|
||||
authUrl: b
|
||||
tokenUrl: b
|
||||
auth0:
|
||||
clientId: b
|
||||
clientSecret: b
|
||||
domain: b
|
||||
microsoft:
|
||||
clientId: f
|
||||
clientSecret: f
|
||||
tenantId: f
|
||||
azure:
|
||||
api:
|
||||
token: h
|
||||
sentryToken: e
|
||||
rollbarAccountToken: f
|
||||
# This is a 'Personal Access Token'
|
||||
circleciAuthToken: r
|
||||
# Used by the scaffolder to create GitHub repos. Must have 'repo' scope.
|
||||
githubToken: g
|
||||
gitlabToken: g
|
||||
@@ -65,9 +65,10 @@ All `app-config.yaml` files inside the monorepo root and package root are
|
||||
considered, as are files with additional `local` and environment affixes such as
|
||||
`development`, for example `app-config.local.yaml`,
|
||||
`app-config.production.yaml`, and `app-config.development.local.yaml`. Which
|
||||
environment config files are loaded is determined by the `NODE_ENV` environment
|
||||
variable. Local configuration files are always loaded, but are meant for local
|
||||
development overrides and should typically be `.gitignore`'d.
|
||||
environment config files are loaded is determined by the `APP_ENV` environment
|
||||
variable, or `NODE_ENV` if it is not set. Local configuration files are always
|
||||
loaded, but are meant for local development overrides and should typically be
|
||||
`.gitignore`'d.
|
||||
|
||||
All loaded configuration files are merged together using the following rules:
|
||||
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
---
|
||||
id: deployment-helm
|
||||
title: Helm
|
||||
description: Documentation on Kubernetes and Helm Deployment
|
||||
---
|
||||
|
||||
# Helm charts
|
||||
|
||||
An example Backstage app can be deployed in Kubernetes using the [Backstage Helm
|
||||
charts][backstage-helm-charts]
|
||||
|
||||
First, choose a DNS name where backstage will be hosted create a yaml file for
|
||||
your custom configuration.
|
||||
|
||||
```
|
||||
appConfig:
|
||||
app:
|
||||
baseUrl: https://backstage.mydomain.com
|
||||
title: Backstage
|
||||
backend:
|
||||
baseUrl: https://backstage.mydomain.com
|
||||
cors:
|
||||
origin: https://backstage.mydomain.com
|
||||
lighthouse:
|
||||
baseUrl: https://backstage.mydomain.com/lighthouse-api
|
||||
techdocs:
|
||||
storageUrl: https://backstage.mydomain.com/api/techdocs/static/docs
|
||||
requestUrl: https://backstage.mydomain.com/api/techdocs
|
||||
|
||||
```
|
||||
|
||||
Then use it to run:
|
||||
|
||||
```
|
||||
git clone https://github.com/spotify/backstage.git
|
||||
cd contrib/chart/backstage
|
||||
helm dependency update
|
||||
helm install -f backstage-mydomain.yaml backstage .
|
||||
```
|
||||
|
||||
This command will deploy the following pieces:
|
||||
|
||||
- Backstage frontend
|
||||
- Backstage backend with scaffolder and auth plugins
|
||||
- (optional) a PostgreSQL instance
|
||||
- lighthouse plugin
|
||||
- ingress
|
||||
|
||||
After a few minutes Backstage should be up and running in your cluster under the
|
||||
DNS specified earlier.
|
||||
|
||||
Make sure to create the appropriate DNS entry in your infrastructure. To find
|
||||
the public IP address run:
|
||||
|
||||
```bash
|
||||
$ kubectl get ingress
|
||||
NAME HOSTS ADDRESS PORTS AGE
|
||||
backstage-ingress * 123.1.2.3 80 17m
|
||||
```
|
||||
|
||||
> **NOTE**: this is not a production ready deployment.
|
||||
|
||||
For more information on how to customize the deployment check the
|
||||
[README][charts-readme].
|
||||
|
||||
[backstage-helm-charts]:
|
||||
(https://github.com/spotify/backstage/tree/master/contrib/chart/backstage)
|
||||
[charts-readme]:
|
||||
(https://github.com/spotify/backstage/tree/master/contrib/chart/backstage/README.md)
|
||||
@@ -13,7 +13,7 @@ Run the following commands if you have Docker environment
|
||||
```bash
|
||||
$ yarn install
|
||||
$ yarn docker-build
|
||||
$ docker run --rm -it -p 7000:7000 -e NODE_ENV=development example-backend:latest
|
||||
$ docker run --rm -it -p 7000:7000 -e APP_ENV=production -e NODE_ENV=development example-backend:latest
|
||||
```
|
||||
|
||||
Then open http://localhost/ on your browser.
|
||||
|
||||
@@ -66,12 +66,6 @@ guidelines to get started.
|
||||
The platform APIs and features are stable and can be depended on for
|
||||
production use. After this plugins will require little to no maintenance.
|
||||
|
||||
- **Backstage Design System** - By providing design guidelines for common plugin
|
||||
layouts together, rich set of reusable UI components
|
||||
([Storybook](https://backstage.io/storybook)) and Figma design resources. The
|
||||
Design System will make it easy to design and build plugins that are
|
||||
consistent across the platform -- supporting both developers and designers.
|
||||
|
||||
- Further improvements to platform documentation
|
||||
|
||||
### Plugins
|
||||
@@ -115,6 +109,7 @@ Chances are that someone will jump in and help build it.
|
||||
|
||||
### Completed milestones ✅
|
||||
|
||||
- [Backstage Design System 💅](https://backstage.io/blog/2020/09/30/backstage-design-system)
|
||||
- [Cost Insights plugin 💸](https://engineering.atspotify.com/2020/09/29/managing-clouds-from-the-ground-up-cost-engineering-at-spotify/)
|
||||
- [Donate Backstage to the CNCF 🎉](https://backstage.io/blog/2020/09/23/backstage-cncf-sandbox)
|
||||
- [TechDocs v1](https://backstage.io/blog/2020/09/08/announcing-tech-docs)
|
||||
|
||||
+1
-1
@@ -2,5 +2,5 @@
|
||||
"packages": ["packages/*", "plugins/*"],
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"version": "0.1.1-alpha.24"
|
||||
"version": "0.1.1-alpha.25"
|
||||
}
|
||||
|
||||
+13
-13
@@ -1,8 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) 2017-present, Facebook, Inc.
|
||||
/*
|
||||
* Copyright 2020 Backstage Project Authors.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const React = require('react');
|
||||
@@ -82,15 +91,6 @@ class Footer extends React.Component {
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<a
|
||||
href={this.props.config.fossWebsite}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
className="spotifyOpenSource"
|
||||
>
|
||||
Made with <span>❤</span> at {this.props.config.organizationName}
|
||||
</a>
|
||||
<p className="copyright">{this.props.config.copyright}</p>
|
||||
</footer>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: GitHub Insights
|
||||
author: roadie.io
|
||||
authorUrl: https://roadie.io
|
||||
category: Monitoring
|
||||
description: View GitHub Insights for your components in Backstage.
|
||||
documentation: https://roadie.io/backstage/plugins/github-insights
|
||||
iconUrl: https://roadie.io/static/2ad5123c425908efde0c922d707e737b/06c84/code-icon.png
|
||||
npmPackageName: '@roadiehq/backstage-plugin-github-insights'
|
||||
@@ -78,7 +78,7 @@ const siteConfig = {
|
||||
},
|
||||
|
||||
// This copyright info is used in /core/Footer.js and blog RSS/Atom feeds.
|
||||
copyright: `Copyright © ${new Date().getFullYear()} Spotify AB`,
|
||||
copyright: `Copyright © ${new Date().getFullYear()} Backstage Project Authors. All rights reserved. The Linux Foundation has registered trademarks and uses trademarks. For a list of trademarks of The Linux Foundation, please see our Trademark Usage page: https://www.linuxfoundation.org/trademark-usage`,
|
||||
|
||||
highlight: {
|
||||
// Highlight.js theme to use for syntax highlighting in code blocks.
|
||||
|
||||
@@ -1084,3 +1084,10 @@ code {
|
||||
margin-bottom: 40px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.nav-footer .copyright {
|
||||
width: 600px;
|
||||
color: #fff;
|
||||
margin: auto;
|
||||
font-size: 10pt;
|
||||
}
|
||||
|
||||
+30
-29
@@ -1,40 +1,41 @@
|
||||
{
|
||||
"name": "example-app",
|
||||
"version": "0.1.1-alpha.24",
|
||||
"version": "0.1.1-alpha.25",
|
||||
"private": true,
|
||||
"bundled": true,
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.1.1-alpha.24",
|
||||
"@backstage/cli": "^0.1.1-alpha.24",
|
||||
"@backstage/core": "^0.1.1-alpha.24",
|
||||
"@backstage/plugin-api-docs": "^0.1.1-alpha.24",
|
||||
"@backstage/plugin-catalog": "^0.1.1-alpha.24",
|
||||
"@backstage/plugin-circleci": "^0.1.1-alpha.24",
|
||||
"@backstage/plugin-cloudbuild": "^0.1.1-alpha.24",
|
||||
"@backstage/plugin-cost-insights": "^0.1.1-alpha.24",
|
||||
"@backstage/plugin-explore": "^0.1.1-alpha.24",
|
||||
"@backstage/plugin-gcp-projects": "^0.1.1-alpha.24",
|
||||
"@backstage/plugin-github-actions": "^0.1.1-alpha.24",
|
||||
"@backstage/plugin-gitops-profiles": "^0.1.1-alpha.24",
|
||||
"@backstage/plugin-graphiql": "^0.1.1-alpha.24",
|
||||
"@backstage/plugin-jenkins": "^0.1.1-alpha.24",
|
||||
"@backstage/plugin-kubernetes": "^0.1.1-alpha.24",
|
||||
"@backstage/plugin-lighthouse": "^0.1.1-alpha.24",
|
||||
"@backstage/plugin-newrelic": "^0.1.1-alpha.24",
|
||||
"@backstage/plugin-register-component": "^0.1.1-alpha.24",
|
||||
"@backstage/plugin-rollbar": "^0.1.1-alpha.24",
|
||||
"@backstage/plugin-scaffolder": "^0.1.1-alpha.24",
|
||||
"@backstage/plugin-sentry": "^0.1.1-alpha.24",
|
||||
"@backstage/plugin-tech-radar": "^0.1.1-alpha.24",
|
||||
"@backstage/plugin-techdocs": "^0.1.1-alpha.24",
|
||||
"@backstage/plugin-user-settings": "^0.1.1-alpha.24",
|
||||
"@backstage/plugin-welcome": "^0.1.1-alpha.24",
|
||||
"@backstage/test-utils": "^0.1.1-alpha.24",
|
||||
"@backstage/theme": "^0.1.1-alpha.24",
|
||||
"@backstage/catalog-model": "^0.1.1-alpha.25",
|
||||
"@backstage/cli": "^0.1.1-alpha.25",
|
||||
"@backstage/core": "^0.1.1-alpha.25",
|
||||
"@backstage/plugin-api-docs": "^0.1.1-alpha.25",
|
||||
"@backstage/plugin-catalog": "^0.1.1-alpha.25",
|
||||
"@backstage/plugin-circleci": "^0.1.1-alpha.25",
|
||||
"@backstage/plugin-cloudbuild": "^0.1.1-alpha.25",
|
||||
"@backstage/plugin-cost-insights": "^0.1.1-alpha.25",
|
||||
"@backstage/plugin-explore": "^0.1.1-alpha.25",
|
||||
"@backstage/plugin-gcp-projects": "^0.1.1-alpha.25",
|
||||
"@backstage/plugin-github-actions": "^0.1.1-alpha.25",
|
||||
"@backstage/plugin-gitops-profiles": "^0.1.1-alpha.25",
|
||||
"@backstage/plugin-graphiql": "^0.1.1-alpha.25",
|
||||
"@backstage/plugin-jenkins": "^0.1.1-alpha.25",
|
||||
"@backstage/plugin-kubernetes": "^0.1.1-alpha.25",
|
||||
"@backstage/plugin-lighthouse": "^0.1.1-alpha.25",
|
||||
"@backstage/plugin-newrelic": "^0.1.1-alpha.25",
|
||||
"@backstage/plugin-register-component": "^0.1.1-alpha.25",
|
||||
"@backstage/plugin-rollbar": "^0.1.1-alpha.25",
|
||||
"@backstage/plugin-scaffolder": "^0.1.1-alpha.25",
|
||||
"@backstage/plugin-sentry": "^0.1.1-alpha.25",
|
||||
"@backstage/plugin-tech-radar": "^0.1.1-alpha.25",
|
||||
"@backstage/plugin-techdocs": "^0.1.1-alpha.25",
|
||||
"@backstage/plugin-user-settings": "^0.1.1-alpha.25",
|
||||
"@backstage/plugin-welcome": "^0.1.1-alpha.25",
|
||||
"@backstage/test-utils": "^0.1.1-alpha.25",
|
||||
"@backstage/theme": "^0.1.1-alpha.25",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@octokit/rest": "^18.0.0",
|
||||
"@roadiehq/backstage-plugin-github-pull-requests": "^0.4.3",
|
||||
"@roadiehq/backstage-plugin-github-pull-requests": "^0.5.1",
|
||||
"@roadiehq/backstage-plugin-github-insights": "^0.2.2",
|
||||
"@roadiehq/backstage-plugin-travis-ci": "^0.2.3",
|
||||
"dayjs": "^1.9.1",
|
||||
"history": "^5.0.0",
|
||||
|
||||
@@ -40,6 +40,13 @@ import { Router as ApiDocsRouter } from '@backstage/plugin-api-docs';
|
||||
import { Router as SentryRouter } from '@backstage/plugin-sentry';
|
||||
import { EmbeddedDocsRouter as DocsRouter } from '@backstage/plugin-techdocs';
|
||||
import { Router as KubernetesRouter } from '@backstage/plugin-kubernetes';
|
||||
import {
|
||||
Router as GitHubInsightsRouter,
|
||||
isPluginApplicableToEntity as isGitHubAvailable,
|
||||
ReadMeCard,
|
||||
LanguagesCard,
|
||||
ReleasesCard,
|
||||
} from '@roadiehq/backstage-plugin-github-insights';
|
||||
import React, { ReactNode } from 'react';
|
||||
import {
|
||||
AboutCard,
|
||||
@@ -54,6 +61,11 @@ import {
|
||||
LastLighthouseAuditCard,
|
||||
isPluginApplicableToEntity as isLighthouseAvailable,
|
||||
} from '@backstage/plugin-lighthouse/';
|
||||
import {
|
||||
Router as PullRequestsRouter,
|
||||
isPluginApplicableToEntity as isPullRequestsAvailable,
|
||||
PullRequestsStatsCard,
|
||||
} from '@roadiehq/backstage-plugin-github-pull-requests';
|
||||
|
||||
const CICDSwitcher = ({ entity }: { entity: Entity }) => {
|
||||
// This component is just an example of how you can implement your company's logic in entity page.
|
||||
@@ -120,11 +132,27 @@ const OverviewContent = ({ entity }: { entity: Entity }) => (
|
||||
<AboutCard entity={entity} />
|
||||
</Grid>
|
||||
<RecentCICDRunsSwitcher entity={entity} />
|
||||
{isGitHubAvailable(entity) && (
|
||||
<>
|
||||
<Grid item md={6}>
|
||||
<LanguagesCard entity={entity} />
|
||||
<ReleasesCard entity={entity} />
|
||||
</Grid>
|
||||
<Grid item md={6}>
|
||||
<ReadMeCard entity={entity} maxHeight={350} />
|
||||
</Grid>
|
||||
</>
|
||||
)}
|
||||
{isLighthouseAvailable(entity) && (
|
||||
<Grid item sm={4}>
|
||||
<LastLighthouseAuditCard />
|
||||
</Grid>
|
||||
)}
|
||||
{isPullRequestsAvailable(entity) && (
|
||||
<Grid item sm={4}>
|
||||
<PullRequestsStatsCard entity={entity} />
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
);
|
||||
|
||||
@@ -160,6 +188,16 @@ const ServiceEntityPage = ({ entity }: { entity: Entity }) => (
|
||||
title="Kubernetes"
|
||||
element={<KubernetesRouter entity={entity} />}
|
||||
/>
|
||||
<EntityPageLayout.Content
|
||||
path="/pull-requests"
|
||||
title="Pull Requests"
|
||||
element={<PullRequestsRouter entity={entity} />}
|
||||
/>
|
||||
<EntityPageLayout.Content
|
||||
path="/code-insights"
|
||||
title="Code Insights"
|
||||
element={<GitHubInsightsRouter entity={entity} />}
|
||||
/>
|
||||
</EntityPageLayout>
|
||||
);
|
||||
|
||||
@@ -195,6 +233,16 @@ const WebsiteEntityPage = ({ entity }: { entity: Entity }) => (
|
||||
title="Kubernetes"
|
||||
element={<KubernetesRouter entity={entity} />}
|
||||
/>
|
||||
<EntityPageLayout.Content
|
||||
path="/pull-requests"
|
||||
title="Pull Requests"
|
||||
element={<PullRequestsRouter entity={entity} />}
|
||||
/>
|
||||
<EntityPageLayout.Content
|
||||
path="/code-insights"
|
||||
title="Code Insights"
|
||||
element={<GitHubInsightsRouter entity={entity} />}
|
||||
/>
|
||||
</EntityPageLayout>
|
||||
);
|
||||
const DefaultEntityPage = ({ entity }: { entity: Entity }) => (
|
||||
|
||||
@@ -36,4 +36,5 @@ export { plugin as GcpProjects } from '@backstage/plugin-gcp-projects';
|
||||
export { plugin as Kubernetes } from '@backstage/plugin-kubernetes';
|
||||
export { plugin as Cloudbuild } from '@backstage/plugin-cloudbuild';
|
||||
export { plugin as CostInsights } from '@backstage/plugin-cost-insights';
|
||||
export { plugin as GitHubInsights } from '@roadiehq/backstage-plugin-github-insights';
|
||||
export { plugin as UserSettings } from '@backstage/plugin-user-settings';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/backend-common",
|
||||
"description": "Common functionality library for Backstage backends",
|
||||
"version": "0.1.1-alpha.24",
|
||||
"version": "0.1.1-alpha.25",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"private": false,
|
||||
@@ -29,9 +29,9 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/cli-common": "^0.1.1-alpha.24",
|
||||
"@backstage/config": "^0.1.1-alpha.24",
|
||||
"@backstage/config-loader": "^0.1.1-alpha.24",
|
||||
"@backstage/cli-common": "^0.1.1-alpha.25",
|
||||
"@backstage/config": "^0.1.1-alpha.25",
|
||||
"@backstage/config-loader": "^0.1.1-alpha.25",
|
||||
"@types/cors": "^2.8.6",
|
||||
"@types/express": "^4.17.6",
|
||||
"compression": "^1.7.4",
|
||||
@@ -60,7 +60,7 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.24",
|
||||
"@backstage/cli": "^0.1.1-alpha.25",
|
||||
"@types/compression": "^1.7.0",
|
||||
"@types/http-errors": "^1.6.3",
|
||||
"@types/morgan": "^1.9.0",
|
||||
|
||||
@@ -24,7 +24,7 @@ export async function loadBackendConfig() {
|
||||
/* eslint-disable-next-line no-restricted-syntax */
|
||||
const paths = findPaths(__dirname);
|
||||
const configs = await loadConfig({
|
||||
env: process.env.NODE_ENV ?? 'development',
|
||||
env: process.env.APP_ENV ?? process.env.NODE_ENV ?? 'development',
|
||||
rootPaths: [paths.targetRoot, paths.targetDir],
|
||||
shouldReadSecrets: true,
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "example-backend",
|
||||
"version": "0.1.1-alpha.24",
|
||||
"version": "0.1.1-alpha.25",
|
||||
"main": "dist/index.cjs.js",
|
||||
"types": "src/index.ts",
|
||||
"private": true,
|
||||
@@ -18,24 +18,24 @@
|
||||
"migrate:create": "knex migrate:make -x ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.1.1-alpha.24",
|
||||
"@backstage/catalog-model": "^0.1.1-alpha.24",
|
||||
"@backstage/config": "^0.1.1-alpha.24",
|
||||
"@backstage/plugin-app-backend": "^0.1.1-alpha.24",
|
||||
"@backstage/plugin-auth-backend": "^0.1.1-alpha.24",
|
||||
"@backstage/plugin-catalog-backend": "^0.1.1-alpha.24",
|
||||
"@backstage/plugin-graphql-backend": "^0.1.1-alpha.24",
|
||||
"@backstage/plugin-kubernetes-backend": "^0.1.1-alpha.24",
|
||||
"@backstage/plugin-proxy-backend": "^0.1.1-alpha.24",
|
||||
"@backstage/plugin-rollbar-backend": "^0.1.1-alpha.24",
|
||||
"@backstage/plugin-scaffolder-backend": "^0.1.1-alpha.24",
|
||||
"@backstage/plugin-sentry-backend": "^0.1.1-alpha.24",
|
||||
"@backstage/plugin-techdocs-backend": "^0.1.1-alpha.24",
|
||||
"@backstage/backend-common": "^0.1.1-alpha.25",
|
||||
"@backstage/catalog-model": "^0.1.1-alpha.25",
|
||||
"@backstage/config": "^0.1.1-alpha.25",
|
||||
"@backstage/plugin-app-backend": "^0.1.1-alpha.25",
|
||||
"@backstage/plugin-auth-backend": "^0.1.1-alpha.25",
|
||||
"@backstage/plugin-catalog-backend": "^0.1.1-alpha.25",
|
||||
"@backstage/plugin-graphql-backend": "^0.1.1-alpha.25",
|
||||
"@backstage/plugin-kubernetes-backend": "^0.1.1-alpha.25",
|
||||
"@backstage/plugin-proxy-backend": "^0.1.1-alpha.25",
|
||||
"@backstage/plugin-rollbar-backend": "^0.1.1-alpha.25",
|
||||
"@backstage/plugin-scaffolder-backend": "^0.1.1-alpha.25",
|
||||
"@backstage/plugin-sentry-backend": "^0.1.1-alpha.25",
|
||||
"@backstage/plugin-techdocs-backend": "^0.1.1-alpha.25",
|
||||
"@gitbeaker/node": "^23.5.0",
|
||||
"@octokit/rest": "^18.0.0",
|
||||
"azure-devops-node-api": "^10.1.1",
|
||||
"dockerode": "^3.2.0",
|
||||
"example-app": "^0.1.1-alpha.24",
|
||||
"example-app": "^0.1.1-alpha.25",
|
||||
"express": "^4.17.1",
|
||||
"express-promise-router": "^3.0.3",
|
||||
"knex": "^0.21.1",
|
||||
@@ -45,7 +45,7 @@
|
||||
"winston": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.24",
|
||||
"@backstage/cli": "^0.1.1-alpha.25",
|
||||
"@types/dockerode": "^2.5.32",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/express-serve-static-core": "^4.17.5",
|
||||
|
||||
@@ -21,9 +21,7 @@ import {
|
||||
Generators,
|
||||
LocalPublish,
|
||||
TechdocsGenerator,
|
||||
GithubPreparer,
|
||||
GitlabPreparer,
|
||||
AzurePreparer,
|
||||
CommonGitPreparer,
|
||||
} from '@backstage/plugin-techdocs-backend';
|
||||
import { PluginEnvironment } from '../types';
|
||||
import Docker from 'dockerode';
|
||||
@@ -38,14 +36,13 @@ export default async function createPlugin({
|
||||
generators.register('techdocs', techdocsGenerator);
|
||||
|
||||
const preparers = new Preparers();
|
||||
const githubPreparer = new GithubPreparer(logger);
|
||||
const gitlabPreparer = new GitlabPreparer(logger);
|
||||
const azurePreparer = new AzurePreparer(logger);
|
||||
const commonGitPreparer = new CommonGitPreparer(logger);
|
||||
|
||||
const directoryPreparer = new DirectoryPreparer(logger);
|
||||
preparers.register('dir', directoryPreparer);
|
||||
preparers.register('github', githubPreparer);
|
||||
preparers.register('gitlab', gitlabPreparer);
|
||||
preparers.register('azure/api', azurePreparer);
|
||||
preparers.register('github', commonGitPreparer);
|
||||
preparers.register('gitlab', commonGitPreparer);
|
||||
preparers.register('azure/api', commonGitPreparer);
|
||||
|
||||
const publisher = new LocalPublish(logger);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/catalog-model",
|
||||
"version": "0.1.1-alpha.24",
|
||||
"version": "0.1.1-alpha.25",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -20,7 +20,7 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/config": "^0.1.1-alpha.24",
|
||||
"@backstage/config": "^0.1.1-alpha.25",
|
||||
"@types/json-schema": "^7.0.5",
|
||||
"@types/yup": "^0.28.2",
|
||||
"json-schema": "^0.2.5",
|
||||
@@ -29,7 +29,7 @@
|
||||
"yup": "^0.29.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.24",
|
||||
"@backstage/cli": "^0.1.1-alpha.25",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/jest": "^26.0.7",
|
||||
"@types/lodash": "^4.14.151",
|
||||
|
||||
@@ -40,10 +40,13 @@ class AllEntityPolicies implements EntityPolicy {
|
||||
async enforce(entity: Entity): Promise<Entity> {
|
||||
let result = entity;
|
||||
for (const policy of this.policies) {
|
||||
const output = await policy.enforce(entity);
|
||||
if (output) {
|
||||
result = output;
|
||||
const output = await policy.enforce(result);
|
||||
if (!output) {
|
||||
throw new Error(
|
||||
`Policy ${policy.constructor.name} did not return a result`,
|
||||
);
|
||||
}
|
||||
result = output;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -54,10 +57,10 @@ class AllEntityPolicies implements EntityPolicy {
|
||||
class AnyEntityPolicy implements EntityPolicy {
|
||||
constructor(private readonly policies: EntityPolicy[]) {}
|
||||
|
||||
async enforce(entity: Entity): Promise<Entity | undefined> {
|
||||
async enforce(entity: Entity): Promise<Entity> {
|
||||
for (const policy of this.policies) {
|
||||
const output = await policy.enforce(entity);
|
||||
if (output !== null) {
|
||||
if (output) {
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/cli-common",
|
||||
"description": "Common functionality used by cli, backend, and create-app",
|
||||
"version": "0.1.1-alpha.24",
|
||||
"version": "0.1.1-alpha.25",
|
||||
"private": false,
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/cli",
|
||||
"description": "CLI for developing Backstage plugins and apps",
|
||||
"version": "0.1.1-alpha.24",
|
||||
"version": "0.1.1-alpha.25",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
@@ -28,9 +28,9 @@
|
||||
"backstage-cli": "bin/backstage-cli"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/cli-common": "^0.1.1-alpha.24",
|
||||
"@backstage/config": "^0.1.1-alpha.24",
|
||||
"@backstage/config-loader": "^0.1.1-alpha.24",
|
||||
"@backstage/cli-common": "^0.1.1-alpha.25",
|
||||
"@backstage/config": "^0.1.1-alpha.25",
|
||||
"@backstage/config-loader": "^0.1.1-alpha.25",
|
||||
"@hot-loader/react-dom": "^16.13.0",
|
||||
"@lerna/package-graph": "^3.18.5",
|
||||
"@lerna/project": "^3.18.0",
|
||||
|
||||
@@ -22,7 +22,7 @@ import { buildBundle } from '../../lib/bundler';
|
||||
|
||||
export default async (cmd: Command) => {
|
||||
const appConfigs = await loadConfig({
|
||||
env: process.env.NODE_ENV ?? 'production',
|
||||
env: process.env.APP_ENV ?? process.env.NODE_ENV ?? 'production',
|
||||
rootPaths: [paths.targetRoot, paths.targetDir],
|
||||
});
|
||||
await buildBundle({
|
||||
|
||||
@@ -22,7 +22,7 @@ import { serveBundle } from '../../lib/bundler';
|
||||
|
||||
export default async (cmd: Command) => {
|
||||
const appConfigs = await loadConfig({
|
||||
env: process.env.NODE_ENV ?? 'development',
|
||||
env: process.env.APP_ENV ?? process.env.NODE_ENV ?? 'development',
|
||||
rootPaths: [paths.targetRoot, paths.targetDir],
|
||||
});
|
||||
const waitForExit = await serveBundle({
|
||||
|
||||
@@ -22,7 +22,7 @@ import { serveBackend } from '../../lib/bundler/backend';
|
||||
|
||||
export default async (cmd: Command) => {
|
||||
const appConfigs = await loadConfig({
|
||||
env: process.env.NODE_ENV ?? 'development',
|
||||
env: process.env.APP_ENV ?? process.env.NODE_ENV ?? 'development',
|
||||
rootPaths: [paths.targetRoot, paths.targetDir],
|
||||
});
|
||||
|
||||
|
||||
@@ -22,7 +22,8 @@ import { stringify as stringifyYaml } from 'yaml';
|
||||
|
||||
export default async (cmd: Command) => {
|
||||
const appConfigs = await loadConfig({
|
||||
env: cmd.env ?? process.env.NODE_ENV ?? 'development',
|
||||
env:
|
||||
cmd.env ?? process.env.APP_ENV ?? process.env.NODE_ENV ?? 'development',
|
||||
shouldReadSecrets: cmd.withSecrets ?? false,
|
||||
rootPaths: [paths.targetRoot, paths.targetDir],
|
||||
});
|
||||
|
||||
@@ -224,9 +224,11 @@ export default async (cmd: Command) => {
|
||||
}
|
||||
|
||||
const answers: Answers = await inquirer.prompt(questions);
|
||||
const pluginId = cmd.backend ? `${answers.id}-backend` : answers.id;
|
||||
|
||||
const name = cmd.scope
|
||||
? `@${cmd.scope.replace(/^@/, '')}/plugin-${answers.id}`
|
||||
: `plugin-${answers.id}`;
|
||||
? `@${cmd.scope.replace(/^@/, '')}/plugin-${pluginId}`
|
||||
: `plugin-${pluginId}`;
|
||||
const npmRegistry = cmd.npmRegistry && cmd.scope ? cmd.npmRegistry : '';
|
||||
const privatePackage = cmd.private === false ? false : true;
|
||||
const isMonoRepo = await fs.pathExists(paths.resolveTargetRoot('lerna.json'));
|
||||
@@ -236,10 +238,10 @@ export default async (cmd: Command) => {
|
||||
? 'templates/default-backend-plugin'
|
||||
: 'templates/default-plugin',
|
||||
);
|
||||
const tempDir = resolvePath(os.tmpdir(), answers.id);
|
||||
const tempDir = resolvePath(os.tmpdir(), pluginId);
|
||||
const pluginDir = isMonoRepo
|
||||
? paths.resolveTargetRoot('plugins', answers.id)
|
||||
: paths.resolveTargetRoot(answers.id);
|
||||
? paths.resolveTargetRoot('plugins', pluginId)
|
||||
: paths.resolveTargetRoot(pluginId);
|
||||
const ownerIds = parseOwnerIds(answers.owner);
|
||||
const { version } = isMonoRepo
|
||||
? await fs.readJson(paths.resolveTargetRoot('lerna.json'))
|
||||
@@ -267,7 +269,7 @@ export default async (cmd: Command) => {
|
||||
});
|
||||
|
||||
Task.section('Moving to final location');
|
||||
await movePlugin(tempDir, pluginDir, answers.id);
|
||||
await movePlugin(tempDir, pluginDir, pluginId);
|
||||
|
||||
Task.section('Building the plugin');
|
||||
await buildPlugin(pluginDir);
|
||||
@@ -277,13 +279,13 @@ export default async (cmd: Command) => {
|
||||
await addPluginDependencyToApp(paths.targetRoot, name, version);
|
||||
|
||||
Task.section('Import plugin in app');
|
||||
await addPluginToApp(paths.targetRoot, answers.id, name);
|
||||
await addPluginToApp(paths.targetRoot, pluginId, name);
|
||||
}
|
||||
|
||||
if (ownerIds && ownerIds.length) {
|
||||
await addCodeownersEntry(
|
||||
codeownersPath!,
|
||||
`/plugins/${answers.id}`,
|
||||
`/plugins/${pluginId}`,
|
||||
ownerIds,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ export function registerCommands(program: CommanderStatic) {
|
||||
.option('--with-secrets', 'Include secrets in the printed configuration')
|
||||
.option(
|
||||
'--env <env>',
|
||||
'The environment to print configuration for [NODE_ENV or development]',
|
||||
'The environment to print configuration for [APP_ENV or NODE_ENV or development]',
|
||||
)
|
||||
.option(
|
||||
'--format <format>',
|
||||
|
||||
@@ -22,7 +22,7 @@ import { buildBundle } from '../../lib/bundler';
|
||||
|
||||
export default async (cmd: Command) => {
|
||||
const appConfigs = await loadConfig({
|
||||
env: process.env.NODE_ENV ?? 'production',
|
||||
env: process.env.APP_ENV ?? process.env.NODE_ENV ?? 'production',
|
||||
rootPaths: [paths.targetRoot, paths.targetDir],
|
||||
});
|
||||
await buildBundle({
|
||||
|
||||
@@ -22,7 +22,7 @@ import { serveBundle } from '../../lib/bundler';
|
||||
|
||||
export default async (cmd: Command) => {
|
||||
const appConfigs = await loadConfig({
|
||||
env: process.env.NODE_ENV ?? 'development',
|
||||
env: process.env.APP_ENV ?? process.env.NODE_ENV ?? 'development',
|
||||
rootPaths: [paths.targetRoot, paths.targetDir],
|
||||
});
|
||||
const waitForExit = await serveBundle({
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^{{version}}",
|
||||
"@backstage/config": "^{{version}}",
|
||||
"@backstage/backend-common": "^{{backstageVersion}}",
|
||||
"@backstage/config": "^{{backstageVersion}}",
|
||||
"@types/express": "^4.17.6",
|
||||
"express": "^4.17.1",
|
||||
"express-promise-router": "^3.0.3",
|
||||
@@ -33,7 +33,7 @@
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^{{version}}",
|
||||
"@backstage/cli": "^{{backstageVersion}}",
|
||||
"@types/supertest": "^2.0.8",
|
||||
"supertest": "^4.0.2",
|
||||
"msw": "^0.20.5"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/config-loader",
|
||||
"description": "Config loading functionality used by Backstage backend, and CLI",
|
||||
"version": "0.1.1-alpha.24",
|
||||
"version": "0.1.1-alpha.25",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
@@ -30,7 +30,7 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/config": "^0.1.1-alpha.24",
|
||||
"@backstage/config": "^0.1.1-alpha.25",
|
||||
"fs-extra": "^9.0.0",
|
||||
"yaml": "^1.9.2",
|
||||
"yup": "^0.29.1"
|
||||
|
||||
@@ -28,7 +28,7 @@ type ResolveOptions = {
|
||||
* Resolves all configuration files that should be loaded in the given environment.
|
||||
*
|
||||
* For each root directory, search for the default app-config.yaml, along with suffixed
|
||||
* NODE_ENV and local variants, e.g. app-config.production.yaml or app-config.development.local.yaml
|
||||
* APP_ENV and local variants, e.g. app-config.production.yaml or app-config.development.local.yaml
|
||||
*
|
||||
* The priority order of config loaded through suffixes is `env > local > none`, meaning that
|
||||
* for example app-config.development.yaml has higher priority than `app-config.local.yaml`.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/config",
|
||||
"description": "Config API used by Backstage core, backend, and CLI",
|
||||
"version": "0.1.1-alpha.24",
|
||||
"version": "0.1.1-alpha.25",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/core-api",
|
||||
"description": "Internal Core API used by Backstage plugins and apps",
|
||||
"version": "0.1.1-alpha.24",
|
||||
"version": "0.1.1-alpha.25",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
@@ -29,8 +29,8 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/config": "^0.1.1-alpha.24",
|
||||
"@backstage/theme": "^0.1.1-alpha.24",
|
||||
"@backstage/config": "^0.1.1-alpha.25",
|
||||
"@backstage/theme": "^0.1.1-alpha.25",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@types/react": "^16.9",
|
||||
@@ -41,8 +41,8 @@
|
||||
"zen-observable": "^0.8.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.24",
|
||||
"@backstage/test-utils-core": "^0.1.1-alpha.24",
|
||||
"@backstage/cli": "^0.1.1-alpha.25",
|
||||
"@backstage/test-utils-core": "^0.1.1-alpha.25",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
"@testing-library/user-event": "^12.0.7",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/core",
|
||||
"description": "Core API used by Backstage plugins and apps",
|
||||
"version": "0.1.1-alpha.24",
|
||||
"version": "0.1.1-alpha.25",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
@@ -29,9 +29,9 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/config": "^0.1.1-alpha.24",
|
||||
"@backstage/core-api": "^0.1.1-alpha.24",
|
||||
"@backstage/theme": "^0.1.1-alpha.24",
|
||||
"@backstage/config": "^0.1.1-alpha.25",
|
||||
"@backstage/core-api": "^0.1.1-alpha.25",
|
||||
"@backstage/theme": "^0.1.1-alpha.25",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.45",
|
||||
@@ -55,8 +55,8 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.24",
|
||||
"@backstage/test-utils": "^0.1.1-alpha.24",
|
||||
"@backstage/cli": "^0.1.1-alpha.25",
|
||||
"@backstage/test-utils": "^0.1.1-alpha.25",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
"@testing-library/user-event": "^12.0.7",
|
||||
|
||||
@@ -14,25 +14,23 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
Checkbox,
|
||||
Chip,
|
||||
ClickAwayListener,
|
||||
FormControl,
|
||||
InputBase,
|
||||
MenuItem,
|
||||
Select,
|
||||
Typography,
|
||||
} from '@material-ui/core';
|
||||
import {
|
||||
createStyles,
|
||||
makeStyles,
|
||||
withStyles,
|
||||
Theme,
|
||||
withStyles,
|
||||
} from '@material-ui/core/styles';
|
||||
|
||||
import {
|
||||
FormControl,
|
||||
Select,
|
||||
MenuItem,
|
||||
InputBase,
|
||||
Chip,
|
||||
Typography,
|
||||
Checkbox,
|
||||
ClickAwayListener,
|
||||
} from '@material-ui/core';
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import ClosedDropdown from './static/ClosedDropdown';
|
||||
import OpenedDropdown from './static/OpenedDropdown';
|
||||
|
||||
@@ -111,7 +109,7 @@ export const SelectComponent = (props: SelectProps) => {
|
||||
const [value, setValue] = useState<any[] | string | number>(
|
||||
multiple ? [] : '',
|
||||
);
|
||||
const [canOpen, setCanOpen] = React.useState(false);
|
||||
const [isOpen, setOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setValue(multiple ? [] : '');
|
||||
@@ -122,19 +120,17 @@ export const SelectComponent = (props: SelectProps) => {
|
||||
onChange(event.target.value);
|
||||
};
|
||||
|
||||
const selectHandleOnOpen = () => {
|
||||
setCanOpen(previous => {
|
||||
if (multiple) {
|
||||
const handleClick = (event: React.ChangeEvent<any>) => {
|
||||
setOpen(previous => {
|
||||
if (multiple && !(event.target instanceof HTMLElement)) {
|
||||
return true;
|
||||
}
|
||||
return !previous;
|
||||
});
|
||||
};
|
||||
|
||||
const handleClickAway = (event: React.ChangeEvent<any>) => {
|
||||
if (event.target.id !== 'menu-item') {
|
||||
setCanOpen(false);
|
||||
}
|
||||
const handleClickAway = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const handleDelete = (selectedValue: string | number) => () => {
|
||||
@@ -154,8 +150,8 @@ export const SelectComponent = (props: SelectProps) => {
|
||||
displayEmpty
|
||||
multiple={multiple}
|
||||
onChange={handleChange}
|
||||
onClick={selectHandleOnOpen}
|
||||
open={canOpen}
|
||||
onClick={handleClick}
|
||||
open={isOpen}
|
||||
input={<BootstrapInput />}
|
||||
renderValue={selected =>
|
||||
multiple && (value as any[]).length !== 0 ? (
|
||||
@@ -181,7 +177,7 @@ export const SelectComponent = (props: SelectProps) => {
|
||||
)
|
||||
}
|
||||
IconComponent={() =>
|
||||
!canOpen ? <ClosedDropdown /> : <OpenedDropdown />
|
||||
!isOpen ? <ClosedDropdown /> : <OpenedDropdown />
|
||||
}
|
||||
MenuProps={{
|
||||
anchorOrigin: {
|
||||
@@ -200,7 +196,7 @@ export const SelectComponent = (props: SelectProps) => {
|
||||
)}
|
||||
{items &&
|
||||
items.map(item => (
|
||||
<MenuItem id="menu-item" key={item.value} value={item.value}>
|
||||
<MenuItem key={item.value} value={item.value}>
|
||||
{multiple && (
|
||||
<Checkbox
|
||||
color="primary"
|
||||
|
||||
@@ -50,7 +50,7 @@ const useSubvalueCellStyles = makeStyles<BackstageTheme>(theme => ({
|
||||
},
|
||||
}));
|
||||
|
||||
type Without<T, K> = Pick<T, Exclude<keyof T, K>>;
|
||||
export type Without<T, K> = Pick<T, Exclude<keyof T, K>>;
|
||||
|
||||
export type Filter = {
|
||||
type: 'select' | 'checkbox-tree' | 'multiple-select';
|
||||
|
||||
@@ -45,7 +45,9 @@ import MTable, {
|
||||
Options,
|
||||
} from 'material-table';
|
||||
import React, { forwardRef, useCallback, useEffect, useState } from 'react';
|
||||
import { Filters, SelectedFilters } from './Filters';
|
||||
import { CheckboxTreeProps } from '../CheckboxTree/CheckboxTree';
|
||||
import { SelectProps } from '../Select/Select';
|
||||
import { Filter, Filters, SelectedFilters, Without } from './Filters';
|
||||
|
||||
const tableIcons = {
|
||||
Add: forwardRef((props, ref: React.Ref<SVGSVGElement>) => (
|
||||
@@ -101,6 +103,23 @@ const tableIcons = {
|
||||
)),
|
||||
};
|
||||
|
||||
// TODO: Material table might already have such a function internally that we can use?
|
||||
function extractValueByField(data: any, field: string): any | undefined {
|
||||
const path = field.split('.');
|
||||
let value = data[path[0]];
|
||||
|
||||
for (let i = 1; i < path.length; ++i) {
|
||||
if (value === undefined) {
|
||||
return value;
|
||||
}
|
||||
|
||||
const f = path[i];
|
||||
value = value[f];
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
const useHeaderStyles = makeStyles<BackstageTheme>(theme => ({
|
||||
header: {
|
||||
padding: theme.spacing(1, 2, 1, 2.5),
|
||||
@@ -234,11 +253,21 @@ export function Table<T extends object = {}>({
|
||||
el =>
|
||||
!!Object.entries(selectedFilters)
|
||||
.filter(([, value]) => !!value.length)
|
||||
.every(([key, value]) => {
|
||||
if (Array.isArray(value)) {
|
||||
return value.includes(el[getFieldByTitle(key)]);
|
||||
.every(([key, filterValue]) => {
|
||||
const fieldValue = extractValueByField(
|
||||
el,
|
||||
getFieldByTitle(key) as string,
|
||||
);
|
||||
|
||||
if (Array.isArray(fieldValue) && Array.isArray(filterValue)) {
|
||||
return fieldValue.some(v => filterValue.includes(v));
|
||||
} else if (Array.isArray(fieldValue)) {
|
||||
return fieldValue.includes(filterValue);
|
||||
} else if (Array.isArray(filterValue)) {
|
||||
return filterValue.includes(fieldValue);
|
||||
}
|
||||
return el[getFieldByTitle(key)] === value;
|
||||
|
||||
return fieldValue === filterValue;
|
||||
}),
|
||||
);
|
||||
setTableData(newData);
|
||||
@@ -248,29 +277,61 @@ export function Table<T extends object = {}>({
|
||||
setSelectedFiltersLength(selectedFiltersArray.flat().length);
|
||||
}, [data, selectedFilters, getFieldByTitle]);
|
||||
|
||||
const constructFilters = (filterConfig: TableFilter[], dataValue: any[]) => {
|
||||
const extractColumnData = (column: string | keyof T) =>
|
||||
dataValue.map(el => ({ label: el[column], options: [] }));
|
||||
const constructFilters = (
|
||||
filterConfig: TableFilter[],
|
||||
dataValue: any[],
|
||||
): Filter[] => {
|
||||
const extractDistinctValues = (field: string | keyof T): Set<any> => {
|
||||
const distinctValues = new Set<any>();
|
||||
const addValue = (value: any) => {
|
||||
if (value !== undefined && value !== null) {
|
||||
distinctValues.add(value);
|
||||
}
|
||||
};
|
||||
|
||||
dataValue.forEach(el => {
|
||||
const value = extractValueByField(el, getFieldByTitle(field) as string);
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
(value as []).forEach(addValue);
|
||||
} else {
|
||||
addValue(value);
|
||||
}
|
||||
});
|
||||
|
||||
return distinctValues;
|
||||
};
|
||||
|
||||
const constructCheckboxTree = (
|
||||
filter: TableFilter,
|
||||
): Without<CheckboxTreeProps, 'onChange'> => ({
|
||||
label: filter.column,
|
||||
subCategories: [...extractDistinctValues(filter.column)].map(v => ({
|
||||
label: v,
|
||||
options: [],
|
||||
})),
|
||||
});
|
||||
|
||||
const constructSelect = (
|
||||
filter: TableFilter,
|
||||
): Without<SelectProps, 'onChange'> => {
|
||||
return {
|
||||
placeholder: 'All results',
|
||||
label: filter.column,
|
||||
multiple: filter.type === 'multiple-select',
|
||||
items: [...extractDistinctValues(filter.column)].map(value => ({
|
||||
label: value,
|
||||
value,
|
||||
})),
|
||||
};
|
||||
};
|
||||
|
||||
return filterConfig.map(filter => ({
|
||||
type: filter.type,
|
||||
element:
|
||||
filter.type === 'checkbox-tree'
|
||||
? {
|
||||
label: filter.column,
|
||||
subCategories: extractColumnData(
|
||||
getFieldByTitle(filter.column) || '',
|
||||
),
|
||||
}
|
||||
: {
|
||||
placeholder: 'All results',
|
||||
label: filter.column,
|
||||
multiple: filter.type === 'multiple-select',
|
||||
items: dataValue.map(el => ({
|
||||
label: el[getFieldByTitle(filter.column) || ''],
|
||||
value: el[getFieldByTitle(filter.column) || ''],
|
||||
})),
|
||||
},
|
||||
? constructCheckboxTree(filter)
|
||||
: constructSelect(filter),
|
||||
}));
|
||||
};
|
||||
|
||||
|
||||
@@ -14,6 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { Table } from './Table';
|
||||
export type { TableColumn, TableProps } from './Table';
|
||||
export { SubvalueCell } from './SubvalueCell';
|
||||
export { Table } from './Table';
|
||||
export type { TableColumn, TableFilter, TableProps } from './Table';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/create-app",
|
||||
"description": "Create app package for Backstage",
|
||||
"version": "0.1.1-alpha.24",
|
||||
"version": "0.1.1-alpha.25",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
@@ -27,7 +27,7 @@
|
||||
"start": "nodemon --"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/cli-common": "^0.1.1-alpha.24",
|
||||
"@backstage/cli-common": "^0.1.1-alpha.25",
|
||||
"chalk": "^4.0.0",
|
||||
"commander": "^6.1.0",
|
||||
"fs-extra": "^9.0.0",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
createRouter,
|
||||
DirectoryPreparer,
|
||||
GithubPreparer,
|
||||
CommonGitPreparer,
|
||||
Preparers,
|
||||
Generators,
|
||||
LocalPublish,
|
||||
@@ -22,10 +22,11 @@ export default async function createPlugin({
|
||||
|
||||
const preparers = new Preparers();
|
||||
const directoryPreparer = new DirectoryPreparer(logger);
|
||||
const githubPreparer = new GithubPreparer(logger);
|
||||
const commonGitPreparer = new CommonGitPreparer(logger);
|
||||
|
||||
preparers.register('dir', directoryPreparer);
|
||||
preparers.register('github', githubPreparer);
|
||||
preparers.register('github', commonGitPreparer);
|
||||
preparers.register('gitlab', commonGitPreparer);
|
||||
|
||||
const publisher = new LocalPublish(logger);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/dev-utils",
|
||||
"description": "Utilities for developing Backstage plugins.",
|
||||
"version": "0.1.1-alpha.24",
|
||||
"version": "0.1.1-alpha.25",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
@@ -29,10 +29,10 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.24",
|
||||
"@backstage/core": "^0.1.1-alpha.24",
|
||||
"@backstage/test-utils": "^0.1.1-alpha.24",
|
||||
"@backstage/theme": "^0.1.1-alpha.24",
|
||||
"@backstage/cli": "^0.1.1-alpha.25",
|
||||
"@backstage/core": "^0.1.1-alpha.25",
|
||||
"@backstage/test-utils": "^0.1.1-alpha.25",
|
||||
"@backstage/theme": "^0.1.1-alpha.25",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "docgen",
|
||||
"description": "Tool for generating API Documentation for itself",
|
||||
"version": "0.1.1-alpha.24",
|
||||
"version": "0.1.1-alpha.25",
|
||||
"private": true,
|
||||
"homepage": "https://backstage.io",
|
||||
"repository": {
|
||||
|
||||
@@ -14,7 +14,7 @@ yarn tsc
|
||||
yarn build
|
||||
```
|
||||
|
||||
Once those tasks have completed, you can now run the test using `yarn start` inside this package.
|
||||
Once those tasks have completed, you can now run the test using `yarn e2e-test run`.
|
||||
|
||||
If you make changes to other packages you will need to rerun `yarn tsc && yarn build`. Changes to this package do not require a rebuild.
|
||||
|
||||
|
||||
Regular → Executable
+5
-2
@@ -1,3 +1,4 @@
|
||||
#!/usr/bin/env node
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
@@ -14,13 +15,15 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
|
||||
require('ts-node').register({
|
||||
transpileOnly: true,
|
||||
/* eslint-disable-next-line no-restricted-syntax */
|
||||
project: require('path').resolve(__dirname, '../../../tsconfig.json'),
|
||||
project: path.resolve(__dirname, '../../../tsconfig.json'),
|
||||
compilerOptions: {
|
||||
module: 'CommonJS',
|
||||
},
|
||||
});
|
||||
|
||||
require('./e2e-test');
|
||||
require('../src');
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "e2e-test",
|
||||
"description": "E2E test for verifying Backstage packages",
|
||||
"version": "0.1.1-alpha.24",
|
||||
"version": "0.1.1-alpha.25",
|
||||
"private": true,
|
||||
"homepage": "https://backstage.io",
|
||||
"repository": {
|
||||
@@ -13,17 +13,22 @@
|
||||
"backstage"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"main": "src/index.js",
|
||||
"main": "src/index.ts",
|
||||
"scripts": {
|
||||
"start": "node .",
|
||||
"lint": "backstage-cli lint",
|
||||
"test": "backstage-cli test",
|
||||
"test:e2e": "yarn start"
|
||||
},
|
||||
"bin": {
|
||||
"e2e-test": "bin/e2e-test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli-common": "^0.1.1-alpha.24",
|
||||
"@backstage/cli-common": "^0.1.1-alpha.25",
|
||||
"@types/fs-extra": "^9.0.1",
|
||||
"@types/node": "^13.7.2",
|
||||
"chalk": "^4.0.0",
|
||||
"commander": "^6.1.0",
|
||||
"fs-extra": "^9.0.0",
|
||||
"handlebars": "^4.7.3",
|
||||
"node-fetch": "^2.6.0",
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { CommanderStatic } from 'commander';
|
||||
import { run } from './run';
|
||||
|
||||
export function registerCommands(program: CommanderStatic) {
|
||||
program.command('run').description('Run e2e tests').action(run);
|
||||
}
|
||||
@@ -24,16 +24,15 @@ import Browser from 'zombie';
|
||||
import {
|
||||
spawnPiped,
|
||||
runPlain,
|
||||
handleError,
|
||||
waitForPageWithText,
|
||||
waitFor,
|
||||
waitForExit,
|
||||
print,
|
||||
} from './helpers';
|
||||
} from '../lib/helpers';
|
||||
import pgtools from 'pgtools';
|
||||
import { findPaths } from '@backstage/cli-common';
|
||||
|
||||
/* eslint-disable-next-line no-restricted-syntax */
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
const paths = findPaths(__dirname);
|
||||
|
||||
const templatePackagePaths = [
|
||||
@@ -42,7 +41,7 @@ const templatePackagePaths = [
|
||||
'packages/create-app/templates/default-app/packages/backend/package.json.hbs',
|
||||
];
|
||||
|
||||
async function main() {
|
||||
export async function run() {
|
||||
const rootDir = await fs.mkdtemp(resolvePath(os.tmpdir(), 'backstage-e2e-'));
|
||||
print(`CLI E2E test root: ${rootDir}\n`);
|
||||
|
||||
@@ -57,7 +56,7 @@ async function main() {
|
||||
const pluginName = await createPlugin('test-plugin', appDir);
|
||||
|
||||
print('Creating a Backstage Backend Plugin');
|
||||
await createPlugin('test-backend-plugin', appDir, ['--backend']);
|
||||
await createPlugin('test-plugin', appDir, ['--backend']);
|
||||
|
||||
print('Starting the app');
|
||||
await testAppServe(pluginName, appDir);
|
||||
@@ -266,13 +265,18 @@ async function createPlugin(
|
||||
print('Waiting for plugin create script to be done');
|
||||
await waitForExit(child);
|
||||
|
||||
const pluginDir = resolvePath(appDir, 'plugins', pluginName);
|
||||
const canonicalName = options.includes('--backend')
|
||||
? `${pluginName}-backend`
|
||||
: pluginName;
|
||||
|
||||
const pluginDir = resolvePath(appDir, 'plugins', canonicalName);
|
||||
|
||||
for (const cmd of [['tsc'], ['lint'], ['test', '--no-watch']]) {
|
||||
print(`Running 'yarn ${cmd.join(' ')}' in newly created plugin`);
|
||||
await runPlain(['yarn', ...cmd], { cwd: pluginDir });
|
||||
}
|
||||
|
||||
return pluginName;
|
||||
return canonicalName;
|
||||
} finally {
|
||||
child.kill();
|
||||
}
|
||||
@@ -413,16 +417,3 @@ async function testBackendStart(appDir: string, isPostgres: boolean) {
|
||||
print('Backend startup test finished successfully');
|
||||
}
|
||||
}
|
||||
|
||||
process.on('unhandledRejection', (error: Error) => {
|
||||
// Try to avoid exiting if the unhandled error is coming from jsdom, i.e. zombie.
|
||||
// Those are typically errors on the page that should be benign, at least in the
|
||||
// context of this test. We have other ways of asserting that the page is being
|
||||
// rendered correctly.
|
||||
if (error?.stack?.includes('node_modules/jsdom/lib')) {
|
||||
console.log(`Ignored error inside jsdom, ${error}`);
|
||||
} else {
|
||||
handleError(error);
|
||||
}
|
||||
});
|
||||
main().catch(handleError);
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import program from 'commander';
|
||||
import chalk from 'chalk';
|
||||
import { registerCommands } from './commands';
|
||||
import { version } from '../package.json';
|
||||
import { exitWithError } from './lib/helpers';
|
||||
|
||||
async function main(argv: string[]) {
|
||||
program.name('e2e-test').version(version);
|
||||
|
||||
registerCommands(program);
|
||||
|
||||
program.on('command:*', () => {
|
||||
console.log();
|
||||
console.log(chalk.red(`Invalid command: ${program.args.join(' ')}`));
|
||||
console.log();
|
||||
program.outputHelp();
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
program.parse(argv);
|
||||
}
|
||||
|
||||
process.on('unhandledRejection', (rejection: unknown) => {
|
||||
// Try to avoid exiting if the unhandled error is coming from jsdom, i.e. zombie.
|
||||
// Those are typically errors on the page that should be benign, at least in the
|
||||
// context of this test. We have other ways of asserting that the page is being
|
||||
// rendered correctly.
|
||||
if (
|
||||
rejection instanceof Error &&
|
||||
rejection?.stack?.includes('node_modules/jsdom/lib')
|
||||
) {
|
||||
console.log(`Ignored error inside jsdom, ${rejection?.stack ?? rejection}`);
|
||||
} else {
|
||||
if (rejection instanceof Error) {
|
||||
exitWithError(rejection);
|
||||
} else {
|
||||
exitWithError(new Error(`Unknown rejection: '${rejection}'`));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
main(process.argv).catch(exitWithError);
|
||||
@@ -42,7 +42,7 @@ export function spawnPiped(cmd: string[], options?: SpawnOptions) {
|
||||
shell: true,
|
||||
...options,
|
||||
});
|
||||
child.on('error', handleError);
|
||||
child.on('error', exitWithError);
|
||||
|
||||
const logPrefix = cmd.map(s => s.replace(/.+\//, '')).join(' ');
|
||||
child.stdout?.on(
|
||||
@@ -75,7 +75,7 @@ export async function runPlain(cmd: string[], options?: SpawnOptions) {
|
||||
}
|
||||
}
|
||||
|
||||
export function handleError(err: Error & { code?: unknown }) {
|
||||
export function exitWithError(err: Error & { code?: unknown }) {
|
||||
process.stdout.write(`${err.name}: ${err.stack || err.message}\n`);
|
||||
|
||||
if (typeof err.code === 'number') {
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "storybook",
|
||||
"version": "0.1.1-alpha.24",
|
||||
"version": "0.1.1-alpha.25",
|
||||
"description": "Storybook build for core package",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
@@ -14,7 +14,7 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/theme": "^0.1.1-alpha.24"
|
||||
"@backstage/theme": "^0.1.1-alpha.25"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@storybook/addon-actions": "^6.0.21",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@techdocs/cli",
|
||||
"description": "CLI for running TechDocs locally.",
|
||||
"version": "0.1.1-alpha.24",
|
||||
"version": "0.1.1-alpha.25",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
@@ -44,7 +44,7 @@
|
||||
"ext": "ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.24",
|
||||
"@backstage/cli": "^0.1.1-alpha.25",
|
||||
"commander": "^6.1.0",
|
||||
"fs-extra": "^9.0.1",
|
||||
"http-proxy": "^1.18.1",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/test-utils-core",
|
||||
"description": "Utilities to test Backstage core",
|
||||
"version": "0.1.1-alpha.24",
|
||||
"version": "0.1.1-alpha.25",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/test-utils",
|
||||
"description": "Utilities to test Backstage plugins and apps.",
|
||||
"version": "0.1.1-alpha.24",
|
||||
"version": "0.1.1-alpha.25",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
@@ -29,10 +29,10 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.24",
|
||||
"@backstage/core-api": "^0.1.1-alpha.24",
|
||||
"@backstage/test-utils-core": "^0.1.1-alpha.24",
|
||||
"@backstage/theme": "^0.1.1-alpha.24",
|
||||
"@backstage/cli": "^0.1.1-alpha.25",
|
||||
"@backstage/core-api": "^0.1.1-alpha.25",
|
||||
"@backstage/test-utils-core": "^0.1.1-alpha.25",
|
||||
"@backstage/theme": "^0.1.1-alpha.25",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/theme",
|
||||
"description": "material-ui theme for use with Backstage.",
|
||||
"version": "0.1.1-alpha.24",
|
||||
"version": "0.1.1-alpha.25",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
@@ -31,7 +31,7 @@
|
||||
"@material-ui/core": "^4.11.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.24"
|
||||
"@backstage/cli": "^0.1.1-alpha.25"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-api-docs",
|
||||
"version": "0.1.1-alpha.24",
|
||||
"version": "0.1.1-alpha.25",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -20,10 +20,10 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.1.1-alpha.24",
|
||||
"@backstage/core": "^0.1.1-alpha.24",
|
||||
"@backstage/plugin-catalog": "^0.1.1-alpha.24",
|
||||
"@backstage/theme": "^0.1.1-alpha.24",
|
||||
"@backstage/catalog-model": "^0.1.1-alpha.25",
|
||||
"@backstage/core": "^0.1.1-alpha.25",
|
||||
"@backstage/plugin-catalog": "^0.1.1-alpha.25",
|
||||
"@backstage/theme": "^0.1.1-alpha.25",
|
||||
"@kyma-project/asyncapi-react": "^0.13.1",
|
||||
"@material-icons/font": "^1.0.2",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
@@ -39,9 +39,9 @@
|
||||
"swagger-ui-react": "^3.31.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.24",
|
||||
"@backstage/dev-utils": "^0.1.1-alpha.24",
|
||||
"@backstage/test-utils": "^0.1.1-alpha.24",
|
||||
"@backstage/cli": "^0.1.1-alpha.25",
|
||||
"@backstage/dev-utils": "^0.1.1-alpha.25",
|
||||
"@backstage/test-utils": "^0.1.1-alpha.25",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
"@testing-library/user-event": "^12.0.7",
|
||||
|
||||
@@ -72,6 +72,6 @@ describe('ApiCatalogPage', () => {
|
||||
// https://github.com/mbrn/material-table/issues/1293
|
||||
it('should render', async () => {
|
||||
const { findByText } = renderWrapped(<ApiExplorerPage />);
|
||||
expect(await findByText(/APIs \(2\)/)).toBeInTheDocument();
|
||||
expect(await findByText(/Backstage API Explorer/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -44,7 +44,6 @@ export const ApiExplorerPage = () => {
|
||||
<SupportButton>All your APIs</SupportButton>
|
||||
</ContentHeader>
|
||||
<ApiExplorerTable
|
||||
titlePreamble="APIs"
|
||||
entities={matchingEntities!}
|
||||
loading={loading}
|
||||
error={error}
|
||||
|
||||
@@ -53,7 +53,6 @@ describe('ApiCatalogTable component', () => {
|
||||
wrapInTestApp(
|
||||
<ApiProvider apis={apiRegistry}>
|
||||
<ApiExplorerTable
|
||||
titlePreamble="APIs"
|
||||
entities={[]}
|
||||
loading={false}
|
||||
error={{ code: 'error' }}
|
||||
@@ -71,15 +70,10 @@ describe('ApiCatalogTable component', () => {
|
||||
const rendered = render(
|
||||
wrapInTestApp(
|
||||
<ApiProvider apis={apiRegistry}>
|
||||
<ApiExplorerTable
|
||||
titlePreamble="APIs"
|
||||
entities={entites}
|
||||
loading={false}
|
||||
/>
|
||||
<ApiExplorerTable entities={entites} loading={false} />
|
||||
</ApiProvider>,
|
||||
),
|
||||
);
|
||||
expect(rendered.getByText(/APIs \(3\)/)).toBeInTheDocument();
|
||||
expect(rendered.getByText(/api1/)).toBeInTheDocument();
|
||||
expect(rendered.getByText(/api2/)).toBeInTheDocument();
|
||||
expect(rendered.getByText(/api3/)).toBeInTheDocument();
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import { ApiEntityV1alpha1, Entity } from '@backstage/catalog-model';
|
||||
import { Table, TableColumn, useApi } from '@backstage/core';
|
||||
import { Table, TableFilter, TableColumn, useApi } from '@backstage/core';
|
||||
import { Chip, Link } from '@material-ui/core';
|
||||
import { Alert } from '@material-ui/lab';
|
||||
import React from 'react';
|
||||
@@ -90,9 +90,27 @@ const columns: TableColumn<Entity>[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const filters: TableFilter[] = [
|
||||
{
|
||||
column: 'Owner',
|
||||
type: 'select',
|
||||
},
|
||||
{
|
||||
column: 'Type',
|
||||
type: 'multiple-select',
|
||||
},
|
||||
{
|
||||
column: 'Lifecycle',
|
||||
type: 'multiple-select',
|
||||
},
|
||||
{
|
||||
column: 'Tags',
|
||||
type: 'checkbox-tree',
|
||||
},
|
||||
];
|
||||
|
||||
type ExplorerTableProps = {
|
||||
entities: Entity[];
|
||||
titlePreamble: string;
|
||||
loading: boolean;
|
||||
error?: any;
|
||||
};
|
||||
@@ -101,7 +119,6 @@ export const ApiExplorerTable = ({
|
||||
entities,
|
||||
loading,
|
||||
error,
|
||||
titlePreamble,
|
||||
}: ExplorerTableProps) => {
|
||||
if (error) {
|
||||
return (
|
||||
@@ -114,7 +131,7 @@ export const ApiExplorerTable = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<Table<Entity>
|
||||
<Table
|
||||
isLoading={loading}
|
||||
columns={columns}
|
||||
options={{
|
||||
@@ -123,8 +140,8 @@ export const ApiExplorerTable = ({
|
||||
loadingType: 'linear',
|
||||
showEmptyDataSourceMessage: !loading,
|
||||
}}
|
||||
title={`${titlePreamble} (${(entities && entities.length) || 0})`}
|
||||
data={entities}
|
||||
filters={filters}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-app-backend",
|
||||
"version": "0.1.1-alpha.24",
|
||||
"version": "0.1.1-alpha.25",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -20,8 +20,8 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.1.1-alpha.24",
|
||||
"@backstage/config-loader": "^0.1.1-alpha.24",
|
||||
"@backstage/backend-common": "^0.1.1-alpha.25",
|
||||
"@backstage/config-loader": "^0.1.1-alpha.25",
|
||||
"@types/express": "^4.17.6",
|
||||
"express": "^4.17.1",
|
||||
"express-promise-router": "^3.0.3",
|
||||
@@ -30,7 +30,7 @@
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.24",
|
||||
"@backstage/cli": "^0.1.1-alpha.25",
|
||||
"@types/supertest": "^2.0.8",
|
||||
"msw": "^0.20.5",
|
||||
"supertest": "^4.0.2"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-auth-backend",
|
||||
"version": "0.1.1-alpha.24",
|
||||
"version": "0.1.1-alpha.25",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -20,9 +20,9 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.1.1-alpha.24",
|
||||
"@backstage/catalog-model": "^0.1.1-alpha.24",
|
||||
"@backstage/config": "^0.1.1-alpha.24",
|
||||
"@backstage/backend-common": "^0.1.1-alpha.25",
|
||||
"@backstage/catalog-model": "^0.1.1-alpha.25",
|
||||
"@backstage/config": "^0.1.1-alpha.25",
|
||||
"@types/express": "^4.17.6",
|
||||
"compression": "^1.7.4",
|
||||
"cookie-parser": "^1.4.5",
|
||||
@@ -51,7 +51,7 @@
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.24",
|
||||
"@backstage/cli": "^0.1.1-alpha.25",
|
||||
"@types/body-parser": "^1.19.0",
|
||||
"@types/cookie-parser": "^1.4.2",
|
||||
"@types/jwt-decode": "2.2.1",
|
||||
|
||||
@@ -41,8 +41,10 @@ type SamlInfo = {
|
||||
export class SamlAuthProvider implements AuthProviderRouteHandlers {
|
||||
private readonly strategy: SamlStrategy;
|
||||
private readonly tokenIssuer: TokenIssuer;
|
||||
private readonly appUrl: string;
|
||||
|
||||
constructor(options: SAMLProviderOptions) {
|
||||
this.appUrl = options.appUrl;
|
||||
this.tokenIssuer = options.tokenIssuer;
|
||||
this.strategy = new SamlStrategy({ ...options }, ((
|
||||
profile: SamlProfile,
|
||||
@@ -82,7 +84,7 @@ export class SamlAuthProvider implements AuthProviderRouteHandlers {
|
||||
claims: { sub: id },
|
||||
});
|
||||
|
||||
return postMessageResponse(res, 'http://localhost:3000', {
|
||||
return postMessageResponse(res, this.appUrl, {
|
||||
type: 'authorization_response',
|
||||
response: {
|
||||
providerInfo: {},
|
||||
@@ -91,7 +93,7 @@ export class SamlAuthProvider implements AuthProviderRouteHandlers {
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
return postMessageResponse(res, 'http://localhost:3000', {
|
||||
return postMessageResponse(res, this.appUrl, {
|
||||
type: 'authorization_response',
|
||||
error: {
|
||||
name: error.name,
|
||||
@@ -115,19 +117,24 @@ type SAMLProviderOptions = {
|
||||
issuer: string;
|
||||
path: string;
|
||||
tokenIssuer: TokenIssuer;
|
||||
appUrl: string;
|
||||
};
|
||||
|
||||
export const createSamlProvider: AuthProviderFactory = ({
|
||||
globalConfig,
|
||||
config,
|
||||
tokenIssuer,
|
||||
}) => {
|
||||
const url = new URL(globalConfig.baseUrl);
|
||||
const providerId = 'saml';
|
||||
const entryPoint = config.getString('entryPoint');
|
||||
const issuer = config.getString('issuer');
|
||||
const opts = {
|
||||
entryPoint,
|
||||
issuer,
|
||||
path: '/auth/saml/handler/frame',
|
||||
path: `${url.pathname}/${providerId}/handler/frame`,
|
||||
tokenIssuer,
|
||||
appUrl: globalConfig.appUrl,
|
||||
};
|
||||
|
||||
return new SamlAuthProvider(opts);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-catalog-backend",
|
||||
"version": "0.1.1-alpha.24",
|
||||
"version": "0.1.1-alpha.25",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -20,9 +20,9 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.1.1-alpha.24",
|
||||
"@backstage/catalog-model": "^0.1.1-alpha.24",
|
||||
"@backstage/config": "^0.1.1-alpha.24",
|
||||
"@backstage/backend-common": "^0.1.1-alpha.25",
|
||||
"@backstage/catalog-model": "^0.1.1-alpha.25",
|
||||
"@backstage/config": "^0.1.1-alpha.25",
|
||||
"@octokit/graphql": "^4.5.6",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/node-fetch": "^2.5.7",
|
||||
@@ -46,7 +46,7 @@
|
||||
"yup": "^0.29.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.24",
|
||||
"@backstage/cli": "^0.1.1-alpha.25",
|
||||
"@types/core-js": "^2.5.4",
|
||||
"@types/git-url-parse": "^9.0.0",
|
||||
"@types/ldapjs": "^1.0.9",
|
||||
|
||||
@@ -14,51 +14,36 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ENTITY_DEFAULT_NAMESPACE, Entity } from '@backstage/catalog-model';
|
||||
import { buildEntitySearch, visitEntityPart } from './search';
|
||||
import type { DbEntitiesSearchRow } from './types';
|
||||
import { Entity, ENTITY_DEFAULT_NAMESPACE } from '@backstage/catalog-model';
|
||||
import { buildEntitySearch, mapToRows, traverse } from './search';
|
||||
|
||||
describe('search', () => {
|
||||
describe('visitEntityPart', () => {
|
||||
describe('traverse', () => {
|
||||
it('expands lists of strings to several rows', () => {
|
||||
const input = { a: ['b', 'c', 'd'] };
|
||||
const output: DbEntitiesSearchRow[] = [];
|
||||
visitEntityPart('eid', '', input, output);
|
||||
const output = traverse(input);
|
||||
expect(output).toEqual([
|
||||
{ entity_id: 'eid', key: 'a', value: 'b' },
|
||||
{ entity_id: 'eid', key: 'a', value: 'c' },
|
||||
{ entity_id: 'eid', key: 'a', value: 'd' },
|
||||
{ key: 'a', value: 'b' },
|
||||
{ key: 'a', value: 'c' },
|
||||
{ key: 'a', value: 'd' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('expands objects', () => {
|
||||
const input = { a: { b: { c: 'd' }, e: 'f' } };
|
||||
const output: DbEntitiesSearchRow[] = [];
|
||||
visitEntityPart('eid', '', input, output);
|
||||
const output = traverse(input);
|
||||
expect(output).toEqual([
|
||||
{ entity_id: 'eid', key: 'a.b.c', value: 'd' },
|
||||
{ entity_id: 'eid', key: 'a.e', value: 'f' },
|
||||
{ key: 'a.b.c', value: 'd' },
|
||||
{ key: 'a.e', value: 'f' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('converts base types to strings or null', () => {
|
||||
const input = {
|
||||
a: true,
|
||||
b: false,
|
||||
c: 7,
|
||||
d: 'string',
|
||||
e: null,
|
||||
f: undefined,
|
||||
};
|
||||
const output: DbEntitiesSearchRow[] = [];
|
||||
visitEntityPart('eid', '', input, output);
|
||||
it('expands list of objects', () => {
|
||||
const input = { root: { list: [{ a: 1 }, { a: 2 }] } };
|
||||
const output = traverse(input);
|
||||
expect(output).toEqual([
|
||||
{ entity_id: 'eid', key: 'a', value: 'true' },
|
||||
{ entity_id: 'eid', key: 'b', value: 'false' },
|
||||
{ entity_id: 'eid', key: 'c', value: '7' },
|
||||
{ entity_id: 'eid', key: 'd', value: 'string' },
|
||||
{ entity_id: 'eid', key: 'e', value: null },
|
||||
{ entity_id: 'eid', key: 'f', value: null },
|
||||
{ key: 'root.list.a', value: 1 },
|
||||
{ key: 'root.list.a', value: 2 },
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -76,34 +61,47 @@ describe('search', () => {
|
||||
},
|
||||
d: 'd',
|
||||
};
|
||||
const output: DbEntitiesSearchRow[] = [];
|
||||
visitEntityPart('eid', '', input, output);
|
||||
const output = traverse(input);
|
||||
expect(output).toEqual([
|
||||
{ entity_id: 'eid', key: 'a', value: 'a' },
|
||||
{ entity_id: 'eid', key: 'metadata.b', value: 'b' },
|
||||
{ entity_id: 'eid', key: 'metadata.c', value: 'c' },
|
||||
{ entity_id: 'eid', key: 'd', value: 'd' },
|
||||
{ key: 'a', value: 'a' },
|
||||
{ key: 'metadata.b', value: 'b' },
|
||||
{ key: 'metadata.c', value: 'c' },
|
||||
{ key: 'd', value: 'd' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
it('expands list of objects', () => {
|
||||
const input = { root: { list: [{ a: 1 }, { a: 2 }] } };
|
||||
const output: DbEntitiesSearchRow[] = [];
|
||||
visitEntityPart('eid', '', input, output);
|
||||
describe('mapToRows', () => {
|
||||
it('converts base types to strings or null', () => {
|
||||
const input = [
|
||||
{ key: 'a', value: true },
|
||||
{ key: 'b', value: false },
|
||||
{ key: 'c', value: 7 },
|
||||
{ key: 'd', value: 'string' },
|
||||
{ key: 'e', value: null },
|
||||
{ key: 'f', value: undefined },
|
||||
];
|
||||
const output = mapToRows(input, 'eid');
|
||||
expect(output).toEqual([
|
||||
{ entity_id: 'eid', key: 'root.list.a', value: '1' },
|
||||
{ entity_id: 'eid', key: 'root.list.a', value: '2' },
|
||||
{ entity_id: 'eid', key: 'a', value: 'true' },
|
||||
{ entity_id: 'eid', key: 'b', value: 'false' },
|
||||
{ entity_id: 'eid', key: 'c', value: '7' },
|
||||
{ entity_id: 'eid', key: 'd', value: 'string' },
|
||||
{ entity_id: 'eid', key: 'e', value: null },
|
||||
{ entity_id: 'eid', key: 'f', value: null },
|
||||
]);
|
||||
});
|
||||
|
||||
it('emits lowercase version of keys and values', () => {
|
||||
const input = { theRoot: { listItems: [{ a: 'One' }, { a: 2 }] } };
|
||||
const output: DbEntitiesSearchRow[] = [];
|
||||
visitEntityPart('eid', '', input, output);
|
||||
expect(output).toEqual([
|
||||
{ entity_id: 'eid', key: 'theroot.listitems.a', value: 'one' },
|
||||
{ entity_id: 'eid', key: 'theroot.listitems.a', value: '2' },
|
||||
]);
|
||||
const input = [{ key: 'fOo', value: 'BaR' }];
|
||||
const output = mapToRows(input, 'eid');
|
||||
expect(output).toEqual([{ entity_id: 'eid', key: 'foo', value: 'bar' }]);
|
||||
});
|
||||
|
||||
it('skips very large values', () => {
|
||||
const input = [{ key: 'foo', value: 'a'.repeat(10000) }];
|
||||
const output = mapToRows(input, 'eid');
|
||||
expect(output).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -115,6 +113,8 @@ describe('search', () => {
|
||||
metadata: { name: 'n' },
|
||||
};
|
||||
expect(buildEntitySearch('eid', input)).toEqual([
|
||||
{ entity_id: 'eid', key: 'apiversion', value: 'a' },
|
||||
{ entity_id: 'eid', key: 'kind', value: 'b' },
|
||||
{ entity_id: 'eid', key: 'metadata.name', value: 'n' },
|
||||
{ entity_id: 'eid', key: 'metadata.namespace', value: null },
|
||||
{ entity_id: 'eid', key: 'metadata.uid', value: null },
|
||||
@@ -123,8 +123,6 @@ describe('search', () => {
|
||||
key: 'metadata.namespace',
|
||||
value: ENTITY_DEFAULT_NAMESPACE,
|
||||
},
|
||||
{ entity_id: 'eid', key: 'apiversion', value: 'a' },
|
||||
{ entity_id: 'eid', key: 'kind', value: 'b' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -28,16 +28,18 @@ const SPECIAL_KEYS = [
|
||||
'metadata.generation',
|
||||
];
|
||||
|
||||
function toValue(current: any): string | null {
|
||||
if (current === undefined || current === null) {
|
||||
return null;
|
||||
}
|
||||
// The maximum length allowed for search values. These columns are indexed, and
|
||||
// database engines do not like to index on massive values. For example,
|
||||
// postgres will balk after 8191 byte line sizes.
|
||||
const MAX_VALUE_LENGTH = 200;
|
||||
|
||||
return String(current).toLowerCase();
|
||||
}
|
||||
type Kv = {
|
||||
key: string;
|
||||
value: any;
|
||||
};
|
||||
|
||||
// Helper for iterating through a nested structure and outputting a list of
|
||||
// path->value entries.
|
||||
// Helper for traversing through a nested structure and outputting a list of
|
||||
// path->value entries of the leaves.
|
||||
//
|
||||
// For example, this yaml structure
|
||||
//
|
||||
@@ -53,57 +55,77 @@ function toValue(current: any): string | null {
|
||||
//
|
||||
// will result in
|
||||
//
|
||||
// "a", "1"
|
||||
// "a", 1
|
||||
// "b.c", null
|
||||
// "b.e": "f"
|
||||
// "b.e": "g"
|
||||
// "h.i": "1"
|
||||
// "h.i": 1
|
||||
// "h.j": "k"
|
||||
// "h.i": "2"
|
||||
// "h.i": 2
|
||||
// "h.j": "l"
|
||||
export function visitEntityPart(
|
||||
entityId: string,
|
||||
path: string,
|
||||
current: any,
|
||||
output: DbEntitiesSearchRow[],
|
||||
) {
|
||||
// ignored
|
||||
if (SPECIAL_KEYS.includes(path)) {
|
||||
return;
|
||||
}
|
||||
export function traverse(root: any): Kv[] {
|
||||
const output: Kv[] = [];
|
||||
|
||||
// empty or scalar
|
||||
if (
|
||||
current === undefined ||
|
||||
current === null ||
|
||||
['string', 'number', 'boolean'].includes(typeof current)
|
||||
) {
|
||||
output.push({ entity_id: entityId, key: path, value: toValue(current) });
|
||||
return;
|
||||
}
|
||||
|
||||
// unknown
|
||||
if (typeof current !== 'object') {
|
||||
return;
|
||||
}
|
||||
|
||||
// array
|
||||
if (Array.isArray(current)) {
|
||||
for (const item of current) {
|
||||
visitEntityPart(entityId, path, item, output);
|
||||
function visit(path: string, current: any) {
|
||||
if (SPECIAL_KEYS.includes(path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// empty or scalar
|
||||
if (
|
||||
current === undefined ||
|
||||
current === null ||
|
||||
['string', 'number', 'boolean'].includes(typeof current)
|
||||
) {
|
||||
output.push({ key: path, value: current });
|
||||
return;
|
||||
}
|
||||
|
||||
// unknown
|
||||
if (typeof current !== 'object') {
|
||||
return;
|
||||
}
|
||||
|
||||
// array
|
||||
if (Array.isArray(current)) {
|
||||
for (const item of current) {
|
||||
// keep the same path as currently
|
||||
visit(path, item);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// object
|
||||
for (const [key, value] of Object.entries(current)) {
|
||||
visit(path ? `${path}.${key}` : key, value);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// object
|
||||
for (const [key, value] of Object.entries(current)) {
|
||||
visitEntityPart(
|
||||
entityId,
|
||||
(path ? `${path}.${key}` : key).toLowerCase(),
|
||||
value,
|
||||
output,
|
||||
);
|
||||
visit('', root);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
// Translates a number of raw data rows to search table rows
|
||||
export function mapToRows(
|
||||
input: Kv[],
|
||||
entityId: string,
|
||||
): DbEntitiesSearchRow[] {
|
||||
const result: DbEntitiesSearchRow[] = [];
|
||||
|
||||
for (let { key, value } of input) {
|
||||
key = key.toLowerCase();
|
||||
if (value === undefined || value === null) {
|
||||
result.push({ entity_id: entityId, key, value: null });
|
||||
} else {
|
||||
value = String(value).toLowerCase();
|
||||
if (value.length <= MAX_VALUE_LENGTH) {
|
||||
result.push({ entity_id: entityId, key, value });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -117,38 +139,20 @@ export function buildEntitySearch(
|
||||
entityId: string,
|
||||
entity: Entity,
|
||||
): DbEntitiesSearchRow[] {
|
||||
// Visit the entire structure recursively
|
||||
const raw = traverse(entity);
|
||||
|
||||
// Start with some special keys that are always present because you want to
|
||||
// be able to easily search for null specifically
|
||||
const result: DbEntitiesSearchRow[] = [
|
||||
{
|
||||
entity_id: entityId,
|
||||
key: 'metadata.name',
|
||||
value: toValue(entity.metadata.name),
|
||||
},
|
||||
{
|
||||
entity_id: entityId,
|
||||
key: 'metadata.namespace',
|
||||
value: toValue(entity.metadata.namespace),
|
||||
},
|
||||
{
|
||||
entity_id: entityId,
|
||||
key: 'metadata.uid',
|
||||
value: toValue(entity.metadata.uid),
|
||||
},
|
||||
];
|
||||
raw.push({ key: 'metadata.name', value: entity.metadata.name });
|
||||
raw.push({ key: 'metadata.namespace', value: entity.metadata.namespace });
|
||||
raw.push({ key: 'metadata.uid', value: entity.metadata.uid });
|
||||
|
||||
// Namespace not specified has the default value "default", so we want to
|
||||
// match on that as well
|
||||
if (!entity.metadata.namespace) {
|
||||
result.push({
|
||||
entity_id: entityId,
|
||||
key: 'metadata.namespace',
|
||||
value: toValue(ENTITY_DEFAULT_NAMESPACE),
|
||||
});
|
||||
raw.push({ key: 'metadata.namespace', value: ENTITY_DEFAULT_NAMESPACE });
|
||||
}
|
||||
|
||||
// Visit the entire structure recursively
|
||||
visitEntityPart(entityId, '', entity, result);
|
||||
|
||||
return result;
|
||||
return mapToRows(raw, entityId);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-catalog-graphql",
|
||||
"version": "0.1.1-alpha.24",
|
||||
"version": "0.1.1-alpha.25",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -20,9 +20,9 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.1.1-alpha.24",
|
||||
"@backstage/catalog-model": "^0.1.1-alpha.24",
|
||||
"@backstage/config": "^0.1.1-alpha.24",
|
||||
"@backstage/backend-common": "^0.1.1-alpha.25",
|
||||
"@backstage/catalog-model": "^0.1.1-alpha.25",
|
||||
"@backstage/config": "^0.1.1-alpha.25",
|
||||
"@graphql-modules/core": "^0.7.17",
|
||||
"apollo-server": "^2.16.1",
|
||||
"graphql": "^15.3.0",
|
||||
@@ -32,7 +32,7 @@
|
||||
"winston": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.24",
|
||||
"@backstage/cli": "^0.1.1-alpha.25",
|
||||
"@graphql-codegen/cli": "^1.17.7",
|
||||
"@graphql-codegen/typescript": "^1.17.7",
|
||||
"@graphql-codegen/typescript-resolvers": "^1.17.7",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-catalog",
|
||||
"version": "0.1.1-alpha.24",
|
||||
"version": "0.1.1-alpha.25",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -21,11 +21,11 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.1.1-alpha.24",
|
||||
"@backstage/core": "^0.1.1-alpha.24",
|
||||
"@backstage/plugin-scaffolder": "^0.1.1-alpha.24",
|
||||
"@backstage/plugin-techdocs": "^0.1.1-alpha.24",
|
||||
"@backstage/theme": "^0.1.1-alpha.24",
|
||||
"@backstage/catalog-model": "^0.1.1-alpha.25",
|
||||
"@backstage/core": "^0.1.1-alpha.25",
|
||||
"@backstage/plugin-scaffolder": "^0.1.1-alpha.25",
|
||||
"@backstage/plugin-techdocs": "^0.1.1-alpha.25",
|
||||
"@backstage/theme": "^0.1.1-alpha.25",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.45",
|
||||
@@ -41,9 +41,9 @@
|
||||
"swr": "^0.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.24",
|
||||
"@backstage/dev-utils": "^0.1.1-alpha.24",
|
||||
"@backstage/test-utils": "^0.1.1-alpha.24",
|
||||
"@backstage/cli": "^0.1.1-alpha.25",
|
||||
"@backstage/dev-utils": "^0.1.1-alpha.25",
|
||||
"@backstage/test-utils": "^0.1.1-alpha.25",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
"@testing-library/react-hooks": "^3.3.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-circleci",
|
||||
"version": "0.1.1-alpha.24",
|
||||
"version": "0.1.1-alpha.25",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -21,10 +21,10 @@
|
||||
"postpack": "backstage-cli postpack"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.1.1-alpha.24",
|
||||
"@backstage/core": "^0.1.1-alpha.24",
|
||||
"@backstage/plugin-catalog": "^0.1.1-alpha.24",
|
||||
"@backstage/theme": "^0.1.1-alpha.24",
|
||||
"@backstage/catalog-model": "^0.1.1-alpha.25",
|
||||
"@backstage/core": "^0.1.1-alpha.25",
|
||||
"@backstage/plugin-catalog": "^0.1.1-alpha.25",
|
||||
"@backstage/theme": "^0.1.1-alpha.25",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.45",
|
||||
@@ -38,8 +38,8 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.24",
|
||||
"@backstage/dev-utils": "^0.1.1-alpha.24",
|
||||
"@backstage/cli": "^0.1.1-alpha.25",
|
||||
"@backstage/dev-utils": "^0.1.1-alpha.25",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
"@testing-library/user-event": "^12.0.7",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user