diff --git a/.changeset/bright-lions-camp.md b/.changeset/bright-lions-camp.md
deleted file mode 100644
index fee5d5b5fc..0000000000
--- a/.changeset/bright-lions-camp.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-scaffolder-backend': patch
----
-
-Allow custom directory to be specified for GitHub publish action
diff --git a/.changeset/chatty-ghosts-lay.md b/.changeset/chatty-ghosts-lay.md
new file mode 100644
index 0000000000..7d8ef3edbd
--- /dev/null
+++ b/.changeset/chatty-ghosts-lay.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-sonarqube': patch
+---
+
+Export isSonarQubeAvailable.
diff --git a/.changeset/clever-walls-bow.md b/.changeset/clever-walls-bow.md
new file mode 100644
index 0000000000..36e56f6e37
--- /dev/null
+++ b/.changeset/clever-walls-bow.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-scaffolder-backend': patch
+---
+
+Add built-in publish action for creating GitHub pull requests.
diff --git a/.changeset/eight-files-rhyme.md b/.changeset/eight-files-rhyme.md
deleted file mode 100644
index c45556366d..0000000000
--- a/.changeset/eight-files-rhyme.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-'@backstage/plugin-badges': minor
-'@backstage/plugin-badges-backend': patch
----
-
-Support auth in badge plugin
diff --git a/.changeset/fair-geese-yell.md b/.changeset/fair-geese-yell.md
deleted file mode 100644
index 00c5b16854..0000000000
--- a/.changeset/fair-geese-yell.md
+++ /dev/null
@@ -1,15 +0,0 @@
----
-'@backstage/config-loader': minor
----
-
-Fix bug where `$${...}` was not being escaped to `${...}`
-
-Add support for environment variable substitution in `$include`, `$file` and
-`$env` transform values.
-
-- This change allows for including dynamic paths, such as environment specific
- secrets by using the same environment variable substitution (`${..}`) already
- supported outside of the various include transforms.
-- If you are currently using the syntax `${...}` in your include transform values,
- you will need to escape the substitution by using `$${...}` instead to maintain
- the same behavior.
diff --git a/.changeset/fluffy-rice-push.md b/.changeset/fluffy-rice-push.md
new file mode 100644
index 0000000000..032d580c6b
--- /dev/null
+++ b/.changeset/fluffy-rice-push.md
@@ -0,0 +1,30 @@
+---
+'@backstage/create-app': patch
+---
+
+Fixing dependency resolution for problematic library `graphql-language-service-interface`.
+
+This change might not have to be applied to your local installation, however if you run into this error:
+
+```
+Error: Failed to compile.
+/tmp/backstage-e2e-uMeycm/test-app/node_modules/graphql-language-service-interface/esm/GraphQLLanguageService.js 100:23
+Module parse failed: Unexpected token (100:23)
+You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
+| }
+| let customRules = null;
+> if (extensions?.customValidationRules &&
+| typeof extensions.customValidationRules === 'function') {
+| customRules = extensions.customValidationRules(this._graphQLConfig);
+```
+
+You can fix it by adding the following to the root `package.json`.
+
+```json
+...
+"resolutions": {
+ "graphql-language-service-interface": "2.8.2",
+ "graphql-language-service-parser": "1.9.0"
+ },
+...
+```
diff --git a/.changeset/forty-ladybugs-move.md b/.changeset/forty-ladybugs-move.md
deleted file mode 100644
index 254efea584..0000000000
--- a/.changeset/forty-ladybugs-move.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-auth-backend': patch
----
-
-When using OAuth2 authentication the name is now taken from the name property of the JWT instead of the email property
diff --git a/.changeset/forty-paws-drum.md b/.changeset/forty-paws-drum.md
new file mode 100644
index 0000000000..1f3b587b8a
--- /dev/null
+++ b/.changeset/forty-paws-drum.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-scaffolder-backend': patch
+---
+
+Fix for the `file://` protocol check in the `FilePreparer` being too strict, breaking Windows.
diff --git a/.changeset/forty-queens-hear.md b/.changeset/forty-queens-hear.md
deleted file mode 100644
index 5e0b35162a..0000000000
--- a/.changeset/forty-queens-hear.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-'@backstage/core': patch
-'@backstage/core-api': patch
----
-
-Introduce a `load-chunk` step in the `BootErrorPage` to show make chunk loading
-errors visible to the user.
diff --git a/.changeset/funny-donkeys-jam.md b/.changeset/funny-donkeys-jam.md
deleted file mode 100644
index 0c45d4b5ff..0000000000
--- a/.changeset/funny-donkeys-jam.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-'@backstage/core-api': patch
-'@backstage/core': patch
----
-
-Improved error messaging for routable extension errors, making it easier to identify the component and mount point that caused the error.
diff --git a/.changeset/green-otters-grin.md b/.changeset/green-otters-grin.md
new file mode 100644
index 0000000000..abfd332a2b
--- /dev/null
+++ b/.changeset/green-otters-grin.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-backend': patch
+---
+
+Add support for configure an LDAP query filter on multiple lines.
diff --git a/.changeset/little-starfishes-whisper.md b/.changeset/little-starfishes-whisper.md
deleted file mode 100644
index ea00753b4f..0000000000
--- a/.changeset/little-starfishes-whisper.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/catalog-model': patch
----
-
-Added `stringifyEntityRef`, which always creates a string representation of an entity reference. Also deprecated `serializeEntityRef`, as `stringifyEntityRef` should be used instead.
diff --git a/.changeset/loud-apricots-fix.md b/.changeset/loud-apricots-fix.md
new file mode 100644
index 0000000000..3763ebf0e3
--- /dev/null
+++ b/.changeset/loud-apricots-fix.md
@@ -0,0 +1,31 @@
+---
+'@backstage/create-app': patch
+'@backstage/plugin-scaffolder': minor
+---
+
+Expose the catalog-import route as an external route from the scaffolder.
+
+This will make it possible to hide the "Register Existing Component" button
+when you for example are running backstage with `catalog.readonly=true`.
+
+As a consequence of this change you need add a new binding to your createApp call to
+keep the button visible. However, if you instead want to hide the button you can safely
+ignore the following example.
+
+To bind the external route from the catalog-import plugin to the scaffolder template
+index page, make sure you have the appropriate imports and add the following
+to the createApp call:
+
+```typescript
+import { catalogImportPlugin } from '@backstage/plugin-catalog-import';
+
+const app = createApp({
+ // ...
+ bindRoutes({ bind }) {
+ // ...
+ bind(scaffolderPlugin.externalRoutes, {
+ registerComponent: catalogImportPlugin.routes.importPage,
+ });
+ },
+});
+```
diff --git a/.changeset/neat-lions-peel.md b/.changeset/neat-lions-peel.md
deleted file mode 100644
index 1cd50641ec..0000000000
--- a/.changeset/neat-lions-peel.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-'@backstage/core-api': patch
-'@backstage/core': patch
----
-
-Fixed a bug with `useRouteRef` where navigating from routes beneath a mount point would often fail.
diff --git a/.changeset/odd-toys-kiss.md b/.changeset/odd-toys-kiss.md
new file mode 100644
index 0000000000..888ba5f752
--- /dev/null
+++ b/.changeset/odd-toys-kiss.md
@@ -0,0 +1,14 @@
+---
+'@backstage/plugin-scaffolder-backend': patch
+---
+
+Bitbucket server needs username to be set as well as the token or appPassword for the publishing process to work.
+
+```yaml
+integrations:
+ bitbucket:
+ - host: bitbucket.mycompany.com
+ apiBaseUrl: https://bitbucket.mycompany.com/rest/api/1.0
+ token: token
+ username: username
+```
diff --git a/.changeset/old-spies-love.md b/.changeset/old-spies-love.md
deleted file mode 100644
index 722d9d937e..0000000000
--- a/.changeset/old-spies-love.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-catalog-import': patch
----
-
-When importing components you will now have the ability to use non Unicode characters in the entity owner field
diff --git a/.changeset/polite-cameras-sin.md b/.changeset/polite-cameras-sin.md
deleted file mode 100644
index b7989be2c6..0000000000
--- a/.changeset/polite-cameras-sin.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/test-utils': patch
----
-
-Remove unnecessary wrapping of elements rendered by `wrapInTestApp` and `renderInTestApp`, which was breaking mount discovery.
diff --git a/.changeset/red-planets-hammer.md b/.changeset/red-planets-hammer.md
new file mode 100644
index 0000000000..eb72314556
--- /dev/null
+++ b/.changeset/red-planets-hammer.md
@@ -0,0 +1,13 @@
+---
+'@backstage/catalog-model': patch
+'@backstage/plugin-catalog-backend': patch
+'@backstage/plugin-catalog': patch
+---
+
+Implemented missing support for the dependsOn/dependencyOf relationships
+between `Component` and `Resource` catalog model objects.
+
+Added support for generating the relevant relationships to the
+`BuiltinKindsEntityProcessor`, and added simple support for fetching
+relationships between `Components` and `Resources` for rendering in the
+system diagram. All catalog-model changes backwards compatible.
diff --git a/.changeset/sharp-rings-march.md b/.changeset/sharp-rings-march.md
deleted file mode 100644
index 81ea9aa9df..0000000000
--- a/.changeset/sharp-rings-march.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-'@backstage/catalog-model': patch
-'@backstage/core': patch
-'@backstage/plugin-catalog': patch
-'@backstage/plugin-scaffolder': patch
----
-
-Add support for multiple links to post-scaffold task summary page
diff --git a/.changeset/stale-geese-rule.md b/.changeset/stale-geese-rule.md
deleted file mode 100644
index f22ca2983d..0000000000
--- a/.changeset/stale-geese-rule.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-org': patch
----
-
-Optimize data fetched for the `OwnershipCard`.
diff --git a/.changeset/stale-jobs-tease.md b/.changeset/stale-jobs-tease.md
new file mode 100644
index 0000000000..e62f87afe7
--- /dev/null
+++ b/.changeset/stale-jobs-tease.md
@@ -0,0 +1,7 @@
+---
+'@backstage/plugin-techdocs-backend': patch
+---
+
+Change the response status of metadata endpoints in case a documentation is not
+available to `404 NOT FOUND`. This also introduces the JSON based error messages
+used by other backends.
diff --git a/.changeset/tasty-apples-sell.md b/.changeset/tasty-apples-sell.md
new file mode 100644
index 0000000000..d24cd61e37
--- /dev/null
+++ b/.changeset/tasty-apples-sell.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-scaffolder-backend': patch
+---
+
+Adds a collaborator field to GitHub publish action for multiple users and access levels
diff --git a/.changeset/techdocs-dry-starfishes-carry.md b/.changeset/techdocs-dry-starfishes-carry.md
new file mode 100644
index 0000000000..f5841c6994
--- /dev/null
+++ b/.changeset/techdocs-dry-starfishes-carry.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-techdocs': patch
+---
+
+Add borders to TechDocs tables and increase font size. Fixes #5264 and #5276.
diff --git a/.changeset/tender-turtles-smash.md b/.changeset/tender-turtles-smash.md
new file mode 100644
index 0000000000..645ed688db
--- /dev/null
+++ b/.changeset/tender-turtles-smash.md
@@ -0,0 +1,6 @@
+---
+'@backstage/core': patch
+'@backstage/plugin-api-docs': patch
+---
+
+add debounce time attribute for apis-docs for search, giving more time to the users when they are typing.
diff --git a/.changeset/thirty-owls-lay.md b/.changeset/thirty-owls-lay.md
new file mode 100644
index 0000000000..3f3ce64606
--- /dev/null
+++ b/.changeset/thirty-owls-lay.md
@@ -0,0 +1,5 @@
+---
+'@backstage/core': patch
+---
+
+exposes undocumented `PageTheme`
diff --git a/.changeset/thirty-panthers-learn.md b/.changeset/thirty-panthers-learn.md
deleted file mode 100644
index f0e2c05e81..0000000000
--- a/.changeset/thirty-panthers-learn.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-github-deployments': patch
----
-
-Add a button to reload the GitHub Deployments card
diff --git a/.changeset/wicked-mirrors-scream.md b/.changeset/wicked-mirrors-scream.md
new file mode 100644
index 0000000000..204d3b7eac
--- /dev/null
+++ b/.changeset/wicked-mirrors-scream.md
@@ -0,0 +1,5 @@
+---
+'@backstage/core': patch
+---
+
+Don't use a drag & drop cursor when clicking on disabled `IconLinkVertical`.
diff --git a/.changeset/witty-rats-bake.md b/.changeset/witty-rats-bake.md
new file mode 100644
index 0000000000..2189961899
--- /dev/null
+++ b/.changeset/witty-rats-bake.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-scaffolder-backend': patch
+---
+
+Fix bug in pull request sample template.
diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt
index b0bcf9c9f7..8c385e1bbb 100644
--- a/.github/styles/vocab.txt
+++ b/.github/styles/vocab.txt
@@ -13,6 +13,7 @@ Cloudformation
Codecov
Codehilite
Config
+Debounce
Discoverability
Dockerfile
Dockerize
@@ -111,6 +112,7 @@ cookiecutter
css
dataflow
deadnaming
+debounce
declaratively
destructured
dev
diff --git a/ADOPTERS.md b/ADOPTERS.md
index 218a4edeba..45842b1833 100644
--- a/ADOPTERS.md
+++ b/ADOPTERS.md
@@ -24,3 +24,4 @@
| [Trendyol](https://trendyol.com) | [Erdogan Oksuz](https://github.com/erdoganoksuz) | The Developer Portal has been called `Pandora`. Provides an overview of Trendyol tech ecosystem. TechDocs, Catalog, Custom Plugins and Theme. |
| [Peloton](https://www.onepeloton.com/) | [Jim Haughwout](https://github.com/JimHaughwout) | Creating our first developer portal and tech-docs. Exploring Service Catalog, Tech Insights and Cost Insights as well. |
| [TELUS](https://telus.com) | [Seb Barre](https://github.com/sbarre) | The Go-to place to find answers about development and delivery at TELUS. |
+| [Brex](https://www.brex.com/) | [Vamsi Chitters](https://github.com/vamsikc) | A centralized UI to understand how a service fits in the whole Brex architecture and manage a team’s engineering dependencies. |
diff --git a/app-config.yaml b/app-config.yaml
index 465c7b0049..309fa28234 100644
--- a/app-config.yaml
+++ b/app-config.yaml
@@ -150,6 +150,10 @@ integrations:
- host: dev.azure.com
token:
$env: AZURE_TOKEN
+# googleGcs:
+# clientEmail: 'example@example.com'
+# privateKey:
+# $env: GCS_PRIVATE_KEY
catalog:
rules:
diff --git a/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md b/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md
new file mode 100644
index 0000000000..ba0aef89ea
--- /dev/null
+++ b/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md
@@ -0,0 +1,170 @@
+# Using AWS Application Load Balancer with Azure Active Directory to authenticate requests
+
+Backstage allows offloading the responsibility of authenticating users to an AWS Application Load Balancer (**ALB**), leveraging the authentication support on ALB.
+This tutorial shows how to use authentication on an ALB sitting in front of Backstage.
+Azure Active Directory (**AAD**) is used as identity provider but any identity provider supporting OpenID Connect (OIDC) can be used.
+
+It is assumed an ALB is already serving traffic in front of a Backstage instance configured to serve the frontend app from the backend.
+
+## Infrastructure setup
+
+### AAD App
+
+The AAD App is used to execute the authentication flow, serve and refresh the identity token.
+
+Create the AAD App following the steps outlined in `Create a Microsoft App Registration in Microsoft Portal` section from the tutorial [Monorepo App Setup With Authentication][monorepo-app-setup-with-auth].
+
+Instead of `localhost` addresses, use the following values.
+
+- Identifier URI: `https://backstage.yourdomain.com`
+- Redirect URI: `https://backstage.yourdomain.com/oauth2/idpresponse`
+
+`Application (client) Id`, `Directory (tenant) ID` and `client secret`values will be used while configuring the ALB.
+
+### ALB
+
+In the AWS console, configure ALB Authentication:
+
+- Edit the ALB rule used to forward the traffic to Backstage and add a new `Authenticate` action. The action will have higher priority compared to the existing `Forward to`.
+- Select `OIDC` under `Authenticate`
+- Set `Issuer` to `https://login.microsoftonline.com/{TENANT_ID}/v2.0`, replacing `{TENANT_ID}` with the value of `Directory (tenant) ID` of the AAD App.
+- Set `Authorization endpoint` to `https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/authorize`, replacing `{TENANT_ID}` with the value of `Directory (tenant) ID` of the AAD App.
+- Set `Token endpoint` to `https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/authorize`, replacing `{TENANT_ID}` with the value of `Directory (tenant) ID` of the AAD App.
+- Set `User info endpoint` to `https://graph.microsoft.com/oidc/userinfo`
+- Set `Client ID` to the AAD App `Application (client) Id`
+- Set `Client secret` to the AAD APP `client secret`
+
+Use the following advanced settings:
+
+- `Session cookie name` = `AWSELBAuthSessionCookie`
+- `Session timeout` = `604800` seconds
+- `Scope` = `openid profile offline_access`
+- `Action on unauthenticated request` = `Autenticate (client reattempt)`
+
+Once you've saved the action, you should see an authentication flow be triggered against AAD when visiting Backstage address at `https://backstage.yourdomain.com`. The flow will not complete successfully as the Backstage app isn't yet configured properly.
+
+## Backstage changes
+
+### Frontend
+
+The Backstage App needs a SignInPage when authentication is required.
+When using ALB authentication Backstage will only be loaded once the user has successfully authenticated; we won't need to display a SignIn page, however we will need to create a dummy SignIn component that can refresh the token.
+
+- edit `packages/app/src/App.tsx`
+- import the following two additional definitions from `@backstage/core`: `useApi`, `configApiRef`; these will be used to check whether Backstage is running locally or behind an ALB
+- add the following definition just before the app is created (`const app = createApp`):
+
+```ts
+const DummySignInComponent: any = (props: any) => {
+ try {
+ const config = useApi(configApiRef);
+ const shouldAuth = !!config.getOptionalConfig('auth.providers.awsalb');
+ if (shouldAuth) {
+ fetch(`${window.location.origin}/api/auth/awsalb/refresh`)
+ .then(data => data.json())
+ .then(data => {
+ props.onResult({
+ userId: data.backstageIdentity.id,
+ profile: data.profile,
+ });
+ });
+ } else {
+ props.onResult({
+ userId: 'guest',
+ profile: {
+ email: 'guest@example.com',
+ displayName: 'Guest',
+ picture: '',
+ },
+ });
+ }
+ return
;
+ } catch (err) {
+ return
{err.message}
;
+ }
+};
+```
+
+- add `DummySignInComponent` as `SignInPage`:
+
+```ts
+const app = createApp({
+ ...
+ components: {
+ SignInPage: DummySignInComponent,
+ ...
+ },
+ ...
+});
+```
+
+### Backend
+
+When using ALB auth it is not possible to leverage the built-in auth config discovery mechanism implemented in the app created by default; bespoke logic needs to be implemented.
+
+- replace the content of `packages/backend/plugin/auth.ts` with the below
+
+```ts
+import {
+ createRouter,
+ AuthResponse,
+ AuthProviderFactoryOptions,
+ defaultAuthProviderFactories,
+} from '@backstage/plugin-auth-backend';
+import { PluginEnvironment } from '../types';
+
+export default async function createPlugin({
+ logger,
+ database,
+ config,
+ discovery,
+}: PluginEnvironment) {
+ const identityResolver = (payload: any): Promise> => {
+ return Promise.resolve({
+ providerInfo: {},
+ profile: {
+ email: payload.email,
+ displayName: payload.name,
+ picture: payload.picture,
+ },
+ backstageIdentity: {
+ id: payload.email,
+ },
+ });
+ };
+ const providerFactories = {
+ awsalb: (options: AuthProviderFactoryOptions) =>
+ defaultAuthProviderFactories.awsalb({ ...options, identityResolver }),
+ };
+ return await createRouter({
+ logger,
+ config,
+ database,
+ discovery,
+ providerFactories,
+ });
+}
+```
+
+### Configuration
+
+Use the following `auth` configuration when running Backstage on AWS:
+
+```yaml
+auth:
+ providers:
+ awsalb:
+ issuer:
+ issuer: https://login.microsoftonline.com/{TENANT_ID}/v2.0
+ region: { AWS_REGION }
+```
+
+Replace `{TENANT_ID}` with the value of `Directory (tenant) ID` of the AAD App and `{AWS_REGION}` with the AWS region identifier where the ALB is deployed (for example: `eu-central-1`).
+
+## Conclusion
+
+Once it's deployed, after going through the AAD authentication flow, Backstage should display the AAD user details.
+
+
+
+[monorepo-app-setup-with-auth-ms]: https://backstage.io/docs/tutorials/quickstart-app-auth#the-auth-configuration
diff --git a/docs/auth/index.md b/docs/auth/index.md
index 6e3747dd25..fc6ce33e2b 100644
--- a/docs/auth/index.md
+++ b/docs/auth/index.md
@@ -22,7 +22,7 @@ Backstage comes with many common authentication providers in the core library:
- [GitLab](gitlab/provider.md)
- [Google](google/provider.md)
- [Okta](okta/provider.md)
-- OneLogin
+- [OneLogin](onelogin/provider.md)
These built-in providers handle the authentication flow for a particular service
including required scopes, callbacks, etc. These providers are each added to a
diff --git a/docs/auth/onelogin/provider.md b/docs/auth/onelogin/provider.md
new file mode 100644
index 0000000000..62c1657fea
--- /dev/null
+++ b/docs/auth/onelogin/provider.md
@@ -0,0 +1,54 @@
+---
+id: provider
+title: OneLogin Authentication Provider
+sidebar_label: OneLogin
+description: Adding OneLogin OIDC as an authentication provider in Backstage
+---
+
+The Backstage `core-api` package comes with a OneLogin authentication provider
+that can authenticate users using OpenID Connect.
+
+## Create an Application on OneLogin
+
+To support OneLogin authentication, you must create an Application:
+
+1. From the OneLogin Admin portal, choose Applications
+2. Click `Add App` and select `OpenID Connect`
+ - Display Name: Backstage (or your custom app name)
+3. Click Save
+4. Go to the Configuration tab for the Application and set:
+ - `Login Url`: `http://localhost:3000`
+ - `Redirect URIs`: `http://localhost:7000/api/auth/onelogin/handler/frame`
+5. Click Save
+6. Go to the SSO tab for the Application and set:
+ - `Token Endpoint` > `Authentication Method`: `POST`
+7. Click Save
+
+## Configuration
+
+The provider configuration can then be added to your `app-config.yaml` under the
+root `auth` configuration:
+
+```yaml
+auth:
+ environment: development
+ providers:
+ onelogin:
+ development:
+ clientId: ${AUTH_ONELOGIN_CLIENT_ID}
+ clientSecret: ${AUTH_ONELOGIN_CLIENT_SECRET}
+ issuer: https://.onelogin.com/oidc/2
+```
+
+The OneLogin provider is a structure with three configuration keys; **these are
+found on the SSO tab** for the OneLogin Application:
+
+- `clientId`: The client ID
+- `clientSecret`: The client secret
+- `issuer`: The issuer URL
+
+## Adding the provider to the Backstage frontend
+
+To add the provider to the frontend, add the `oneloginAuthApi` reference and
+`SignInPage` component as shown in
+[Adding the provider to the sign-in page](../index.md#adding-the-provider-to-the-sign-in-page).
diff --git a/docs/cli/commands.md b/docs/cli/commands.md
index ca89a43a6a..33693b010f 100644
--- a/docs/cli/commands.md
+++ b/docs/cli/commands.md
@@ -54,6 +54,8 @@ versions:check Check Backstage package versioning
prepack Prepares a package for packaging before publishing
postpack Restores the changes made by the prepack command
+create-github-app Create new GitHub App in your organization (experimental)
+
help [command] display help for command
```
@@ -190,10 +192,13 @@ output of `backstage-cli backend:bundle` into an image:
FROM node:14-buster-slim
WORKDIR /app
-ADD yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./
+COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./
+RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz
+
RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)"
-ADD packages/backend/dist/bundle.tar.gz app-config.yaml ./
+COPY packages/backend/dist/bundle.tar.gz app-config.yaml ./
+RUN tar xzf bundle.tar.gz && rm bundle.tar.gz
CMD ["node", "packages/backend"]
```
@@ -606,3 +611,18 @@ the resulting archive in the target `workspace-dir`.
```text
Usage: backstage-cli build-workspace [options] <workspace-dir>
```
+
+## create-github-app
+
+Scope: `root`
+
+Creates a GitHub App in your GitHub organization. This is an alternative to
+token-based [GitHub integration](../integrations/github/locations.md). See
+[GitHub Apps for Backstage Authentication](../plugins/github-apps.md).
+
+Launches a browser to create the App through GitHub and saves the result as a
+YAML file that can be referenced in the GitHub integration configuration.
+
+```text
+Usage: backstage-cli create-github-app <github-org>
+```
diff --git a/docs/features/software-catalog/descriptor-format.md b/docs/features/software-catalog/descriptor-format.md
index 7397ce7d23..f4e1f86dd8 100644
--- a/docs/features/software-catalog/descriptor-format.md
+++ b/docs/features/software-catalog/descriptor-format.md
@@ -519,6 +519,17 @@ consumed by the component, e.g. `artist-api`. This field is optional.
| --------------------------------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------------- |
| [`API`](#kind-api) (default) | Same as this entity, typically `default` | [`consumesApi`, and reverse `apiConsumedBy`](well-known-relations.md#consumesapi-and-apiconsumedby) |
+### `spec.dependsOn` [optional]
+
+An array of [entity references](#string-references) to the components and
+resources that the component depends on, e.g. `artists-db`. This field is
+optional.
+
+| [`kind`](#apiversion-and-kind-required) | Default [`namespace`](#namespace-optional) | Generated [relation](well-known-relations.md) type |
+| --------------------------------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------- |
+| [`Component`](#kind-component) | Same as this entity, typically `default` | [`dependsOn`, and reverse `dependencyOf`](well-known-relations.md#dependson-and-dependencyof) |
+| [`Resource`](#kind-resource) | Same as this entity, typically `default` | [`dependsOn`, and reverse `dependencyOf`](well-known-relations.md#dependson-and-dependencyof) |
+
## Kind: Template
The following describes the following entity kind:
@@ -1014,6 +1025,17 @@ belongs to, e.g. `artist-engagement-portal`. This field is optional.
| --------------------------------------- | ------------------------------------------ | ----------------------------------------------------------------------------- |
| [`System`](#kind-system) (default) | Same as this entity, typically `default` | [`partOf`, and reverse `hasPart`](well-known-relations.md#partof-and-haspart) |
+### `spec.dependsOn` [optional]
+
+An array of [entity references](#string-references) to the components and
+resources that the resource depends on, e.g. `artist-lookup`. This field is
+optional.
+
+| [`kind`](#apiversion-and-kind-required) | Default [`namespace`](#namespace-optional) | Generated [relation](well-known-relations.md) type |
+| --------------------------------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------- |
+| [`Component`](#kind-component) | Same as this entity, typically `default` | [`dependsOn`, and reverse `dependencyOf`](well-known-relations.md#dependson-and-dependencyof) |
+| [`Resource`](#kind-resource) | Same as this entity, typically `default` | [`dependsOn`, and reverse `dependencyOf`](well-known-relations.md#dependson-and-dependencyof) |
+
## Kind: System
Describes the following entity kind:
diff --git a/docs/features/software-templates/installation.md b/docs/features/software-templates/installation.md
index e145567666..b3c9f878d7 100644
--- a/docs/features/software-templates/installation.md
+++ b/docs/features/software-templates/installation.md
@@ -265,3 +265,15 @@ GITHUB_TOKEN= yarn start
If you've also set up the frontend plugin, so you should be ready to go browse
the templates at [localhost:3000/create](http://localhost:3000/create) now!
+
+### Disabling Docker in Docker situation (Optional)
+
+Software Templates use
+[Cookiecutter](https://github.com/cookiecutter/cookiecutter) as templating
+library. By default it will use the
+[spotify/backstage-cookiecutter](<[spotify/backstage-cookiecutter](https://github.com/backstage/backstage/blob/37e35b910afc7d1270855aed0ec4718aba366c91/plugins/scaffolder-backend/scripts/Cookiecutter.dockerfile)>)
+docker image.
+
+If you are running backstage from a Docker container and you want to avoid
+calling a container inside a container, you can set up Cookiecutter in your own
+image, this will use the local installation instead.
diff --git a/docs/getting-started/app-custom-theme.md b/docs/getting-started/app-custom-theme.md
index 345b7ad969..181f541929 100644
--- a/docs/getting-started/app-custom-theme.md
+++ b/docs/getting-started/app-custom-theme.md
@@ -74,3 +74,64 @@ Note that your list of custom themes overrides the default themes. If you still
want to use the default themes, they are exported as `lightTheme` and
`darkTheme` from
[@backstage/theme](https://www.npmjs.com/package/@backstage/theme).
+
+## Example of a custom theme
+
+```ts
+const themeOptions = createThemeOptions({
+ palette: {
+ ...lightTheme.palette,
+ primary: {
+ main: '#123456',
+ },
+ secondary: {
+ main: '#123456',
+ },
+ error: {
+ main: '#123456'
+ },
+ warning: {
+ main: '#123456',
+ },
+ info: {
+ main: '#123456',
+ },
+ success: {
+ main: '#123456',
+ },
+ background: {
+ default: '#123456',
+ paper: '#123456',
+ },
+ banner: {
+ info: '#123456',
+ error: '#123456'
+ text: '#123456'
+ link: '#123456',
+ },
+ errorBackground: '#123456'
+ warningBackground: '#123456'
+ infoBackground: '#123456'
+ navigation: {
+ background: '#123456',
+ indicator: '#123456'
+ color: '#123456'
+ selectedColor: '#123456',
+ },
+ },
+ defaultPageTheme: 'home',
+ fontFamily: 'Comic Sans',
+ /* below drives the header colors */
+ pageTheme: {
+ home: genPageTheme(['#123456','#123456'], shapes.wave),
+ documentation: genPageTheme(['#123456','#123456'], shapes.wave2),
+ tool: genPageTheme(['#123456','#123456'], shapes.round),
+ service: genPageTheme(['#123456','#123456'], shapes.wave),
+ website: genPageTheme(['#123456','#123456'], shapes.wave),
+ library: genPageTheme(['#123456','#123456'] shapes.wave),
+ other: genPageTheme(['#123456','#123456'], shapes.wave),
+ app: genPageTheme(['#123456','#123456'], shapes.wave),
+ apis: genPageTheme(['#123456','#123456'], shapes.wave),
+ },
+});
+```
diff --git a/docs/getting-started/configure-app-with-plugins.md b/docs/getting-started/configure-app-with-plugins.md
index 964987c4a7..e5b4d08a3f 100644
--- a/docs/getting-started/configure-app-with-plugins.md
+++ b/docs/getting-started/configure-app-with-plugins.md
@@ -68,9 +68,9 @@ proxy:
In a standard Backstage app created with
[@backstage/create-app](./create-an-app.md), the sidebar is managed inside
-`packages/app/src/components/Root.tsx`. The file exports the entire `Sidebar`
-element of your app, which you can extend with additional entries by adding new
-`SidebarItem` elements.
+`packages/app/src/components/Root/Root.tsx`. The file exports the entire
+`Sidebar` element of your app, which you can extend with additional entries by
+adding new `SidebarItem` elements.
For example, if you install the `api-docs` plugin, a matching `SidebarItem`
could be something like this:
diff --git a/docs/getting-started/create-an-app.md b/docs/getting-started/create-an-app.md
index 837a27e592..fefb20b095 100644
--- a/docs/getting-started/create-an-app.md
+++ b/docs/getting-started/create-an-app.md
@@ -106,6 +106,12 @@ The install process may also fail if no Python installation is available. Python
is commonly available in most systems already, but if it isn't you can head for
example [here](https://www.python.org/downloads/) to install it.
+#### Could not execute command yarn install
+
+Install yarn on your system with `npm install --global yarn` or for more details
+refer to the
+[prerequisites](https://backstage.io/docs/getting-started/running-backstage-locally#prerequisites)
+
## Run the app
When the installation is complete you can open the app folder and start the app.
diff --git a/docs/integrations/bitbucket/locations.md b/docs/integrations/bitbucket/locations.md
index 992a240290..d318eef874 100644
--- a/docs/integrations/bitbucket/locations.md
+++ b/docs/integrations/bitbucket/locations.md
@@ -38,3 +38,6 @@ a structure with up to four elements:
- `apiBaseUrl` (optional): The URL of the Bitbucket API. For self-hosted
installations, it is commonly at `https:///rest/api/1.0`. For
bitbucket.org, this configuration is not needed as it can be inferred.
+
+> Note: If you are using Bitbucket server you MUST set the username as well as
+> the token or appPassword.
diff --git a/docs/integrations/google-cloud-storage/locations.md b/docs/integrations/google-cloud-storage/locations.md
new file mode 100644
index 0000000000..e3f6b72847
--- /dev/null
+++ b/docs/integrations/google-cloud-storage/locations.md
@@ -0,0 +1,57 @@
+---
+id: locations
+sidebar_label: Locations
+title: Google Cloud Storage Locations
+# prettier-ignore
+description: Setting up an integration with Google Cloud Storage
+---
+
+The Backstage catalog can import entities from a yaml file stored in a GCS
+(Google Cloud Storage) bucket. To enable the ingestion of said entities the
+`GoogleGcs` integration must be enabled first.
+
+## Configuration
+
+To configure the integration add the appropriate credentials to the Backstage
+backend. There are two main ways to do this: by explicitly setting a
+`clientEmail` and a `privateKey` or by letting the Google Storage SDK discover
+the credentials automatically.
+
+### Explicit credentials
+
+Explicit credentials can be set in the following format:
+
+```yaml
+integrations:
+ googleGcs:
+ clientEmail:
+ $env: GCS_CLIENT_EMAIL
+ privateKey:
+ $env: GCS_PRIVATE_KEY
+```
+
+Then make sure the environment variables `GCS_CLIENT_EMAIL` and
+`GCS_PRIVATE_KEY` are set when you run Backstage.
+
+### Automatic discovery of Google credentials
+
+Since this integration uses the Google Storage SDK, you can also choose to not
+provide any explicit credentials and let the SDK discover them automatically.
+
+One of these discovery methods is to provide an environment variable called
+`GOOGLE_APPLICATION_CREDENTIALS` and set it to the file path of your JSON
+service account key.
+
+For more details and methods to provide credentials to the Google Storage SDK
+you can check [this documentation page][google gcs docs].
+
+## Usage
+
+To use this integration to import entities from a GCS bucket go to the Google
+console and browse the file you would like to import. Then copy the
+`Authenticated URL` and paste it into the text box in the `register component`
+form. This url should look like
+`https://storage.cloud.google.com///catalog-info.yaml`.
+
+[google gcs docs]:
+ https://cloud.google.com/docs/authentication/production#auth-cloud-implicit-nodejs
diff --git a/microsite/pages/en/index.js b/microsite/pages/en/index.js
index 47f9568d9b..a761d6b6e2 100644
--- a/microsite/pages/en/index.js
+++ b/microsite/pages/en/index.js
@@ -292,7 +292,7 @@ class Index extends React.Component {
Build your own software templates
Contribute
diff --git a/microsite/sidebars.json b/microsite/sidebars.json
index 0a438b7653..64ff108a78 100644
--- a/microsite/sidebars.json
+++ b/microsite/sidebars.json
@@ -142,6 +142,11 @@
"type": "subcategory",
"label": "LDAP",
"ids": ["integrations/ldap/org"]
+ },
+ {
+ "type": "subcategory",
+ "label": "Google GCS",
+ "ids": ["integrations/google-cloud-storage/locations"]
}
],
"Plugins": [
@@ -195,7 +200,8 @@
"auth/github/provider",
"auth/gitlab/provider",
"auth/google/provider",
- "auth/okta/provider"
+ "auth/okta/provider",
+ "auth/onelogin/provider"
]
},
"auth/add-auth-provider",
diff --git a/package.json b/package.json
index fcabe5b3e5..d87b98fbdb 100644
--- a/package.json
+++ b/package.json
@@ -38,7 +38,9 @@
"resolutions": {
"**/@roadiehq/**/@backstage/core": "*",
"**/@roadiehq/**/@backstage/plugin-catalog": "*",
- "**/@roadiehq/**/@backstage/catalog-model": "*"
+ "**/@roadiehq/**/@backstage/catalog-model": "*",
+ "graphql-language-service-interface": "2.8.2",
+ "graphql-language-service-parser": "1.9.0"
},
"version": "1.0.0",
"devDependencies": {
diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md
index 00cf81a903..5b96841e65 100644
--- a/packages/app/CHANGELOG.md
+++ b/packages/app/CHANGELOG.md
@@ -1,5 +1,29 @@
# example-app
+## 0.2.23
+
+### Patch Changes
+
+- Updated dependencies [d0b4ebf22]
+- Updated dependencies [1279a3325]
+- Updated dependencies [4a4681b1b]
+- Updated dependencies [97b60de98]
+- Updated dependencies [3f96a9d5a]
+- Updated dependencies [b051e770c]
+- Updated dependencies [f9c75f7a9]
+- Updated dependencies [98dd5da71]
+- Updated dependencies [97d53f686]
+- Updated dependencies [64d2ce700]
+ - @backstage/plugin-badges@0.2.0
+ - @backstage/core@0.7.4
+ - @backstage/catalog-model@0.7.6
+ - @backstage/plugin-scaffolder@0.8.2
+ - @backstage/plugin-catalog-import@0.5.2
+ - @backstage/plugin-catalog@0.5.3
+ - @backstage/plugin-org@0.3.12
+ - @backstage/plugin-github-deployments@0.1.2
+ - @backstage/cli@0.6.7
+
## 0.2.21
### Patch Changes
diff --git a/packages/app/package.json b/packages/app/package.json
index 4c734b85f3..304bd8d8b3 100644
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -1,17 +1,17 @@
{
"name": "example-app",
- "version": "0.2.21",
+ "version": "0.2.23",
"private": true,
"bundled": true,
"dependencies": {
- "@backstage/catalog-model": "^0.7.4",
- "@backstage/cli": "^0.6.6",
- "@backstage/core": "^0.7.3",
+ "@backstage/catalog-model": "^0.7.6",
+ "@backstage/cli": "^0.6.7",
+ "@backstage/core": "^0.7.4",
"@backstage/integration-react": "^0.1.1",
"@backstage/plugin-api-docs": "^0.4.9",
- "@backstage/plugin-badges": "^0.1.2",
- "@backstage/plugin-catalog": "^0.5.1",
- "@backstage/plugin-catalog-import": "^0.5.0",
+ "@backstage/plugin-badges": "^0.2.0",
+ "@backstage/plugin-catalog": "^0.5.3",
+ "@backstage/plugin-catalog-import": "^0.5.2",
"@backstage/plugin-catalog-react": "^0.1.3",
"@backstage/plugin-circleci": "^0.2.12",
"@backstage/plugin-cloudbuild": "^0.2.13",
@@ -19,7 +19,7 @@
"@backstage/plugin-explore": "^0.3.2",
"@backstage/plugin-gcp-projects": "^0.2.5",
"@backstage/plugin-github-actions": "^0.4.2",
- "@backstage/plugin-github-deployments": "^0.1.1",
+ "@backstage/plugin-github-deployments": "^0.1.2",
"@backstage/plugin-gitops-profiles": "^0.2.6",
"@backstage/plugin-graphiql": "^0.2.9",
"@backstage/plugin-jenkins": "^0.4.1",
@@ -27,12 +27,12 @@
"@backstage/plugin-kubernetes": "^0.4.2",
"@backstage/plugin-lighthouse": "^0.2.14",
"@backstage/plugin-newrelic": "^0.2.6",
- "@backstage/plugin-org": "^0.3.10",
+ "@backstage/plugin-org": "^0.3.12",
"@backstage/plugin-pagerduty": "0.3.2",
"@backstage/plugin-register-component": "^0.2.12",
"@backstage/plugin-github-release-manager": "^0.1.1",
"@backstage/plugin-rollbar": "^0.3.3",
- "@backstage/plugin-scaffolder": "^0.8.0",
+ "@backstage/plugin-scaffolder": "^0.8.2",
"@backstage/plugin-search": "^0.3.4",
"@backstage/plugin-sentry": "^0.3.8",
"@backstage/plugin-tech-radar": "^0.3.8",
@@ -43,10 +43,10 @@
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@octokit/rest": "^18.0.12",
- "@roadiehq/backstage-plugin-buildkite": "^0.1.3",
- "@roadiehq/backstage-plugin-github-insights": "^0.3.2",
- "@roadiehq/backstage-plugin-github-pull-requests": "^0.7.6",
- "@roadiehq/backstage-plugin-travis-ci": "^0.4.5",
+ "@roadiehq/backstage-plugin-buildkite": "^1.0.0",
+ "@roadiehq/backstage-plugin-github-insights": "^1.0.0",
+ "@roadiehq/backstage-plugin-github-pull-requests": "^1.0.0",
+ "@roadiehq/backstage-plugin-travis-ci": "^1.0.0",
"history": "^5.0.0",
"prop-types": "^15.7.2",
"react": "^16.12.0",
@@ -58,7 +58,7 @@
"zen-observable": "^0.8.15"
},
"devDependencies": {
- "@backstage/test-utils": "^0.1.9",
+ "@backstage/test-utils": "^0.1.10",
"@testing-library/cypress": "^7.0.1",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx
index defa70dbb7..c5e5033973 100644
--- a/packages/app/src/App.tsx
+++ b/packages/app/src/App.tsx
@@ -27,7 +27,11 @@ import {
CatalogIndexPage,
catalogPlugin,
} from '@backstage/plugin-catalog';
-import { CatalogImportPage } from '@backstage/plugin-catalog-import';
+
+import {
+ CatalogImportPage,
+ catalogImportPlugin,
+} from '@backstage/plugin-catalog-import';
import {
CostInsightsLabelDataflowInstructionsPage,
CostInsightsPage,
@@ -48,8 +52,8 @@ import React from 'react';
import { hot } from 'react-hot-loader/root';
import { Navigate, Route } from 'react-router';
import { apis } from './apis';
-import { EntityPage } from './components/catalog/EntityPage';
import { Root } from './components/Root';
+import { entityPage } from './components/catalog/EntityPage';
import { providers } from './identityProviders';
import * as plugins from './plugins';
@@ -82,6 +86,9 @@ const app = createApp({
bind(explorePlugin.externalRoutes, {
catalogEntity: catalogPlugin.routes.catalogEntity,
});
+ bind(scaffolderPlugin.externalRoutes, {
+ registerComponent: catalogImportPlugin.routes.importPage,
+ });
},
});
@@ -96,7 +103,7 @@ const routes = (
path="/catalog/:namespace/:kind/:name"
element={}
>
-
+ {entityPage}
} />
} />
diff --git a/packages/app/src/components/catalog/EntityPage.test.tsx b/packages/app/src/components/catalog/EntityPage.test.tsx
index 1392f77a6d..9860792ea0 100644
--- a/packages/app/src/components/catalog/EntityPage.test.tsx
+++ b/packages/app/src/components/catalog/EntityPage.test.tsx
@@ -13,14 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
import React from 'react';
-import { CICDSwitcher } from './EntityPage';
-import { UrlPatternDiscovery, ApiProvider, ApiRegistry } from '@backstage/core';
-import {
- buildKiteApiRef,
- BuildkiteApi,
-} from '@roadiehq/backstage-plugin-buildkite';
-import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils';
+import { ApiProvider, ApiRegistry } from '@backstage/core';
+import { EntityLayout } from '@backstage/plugin-catalog';
+import { EntityProvider } from '@backstage/plugin-catalog-react';
+import { renderInTestApp } from '@backstage/test-utils';
+import { cicdContent } from './EntityPage';
+import { githubActionsApiRef } from '@backstage/plugin-github-actions';
describe('EntityPage Test', () => {
const entity = {
@@ -29,7 +29,7 @@ describe('EntityPage Test', () => {
metadata: {
name: 'ExampleComponent',
annotations: {
- 'buildkite.com/project-slug': 'exampleProject/examplePipeline',
+ 'github.com/project-slug': 'example/project',
},
},
spec: {
@@ -39,24 +39,36 @@ describe('EntityPage Test', () => {
},
};
- const discoveryApi = UrlPatternDiscovery.compile('http://exampleapi.com');
+ const mockedApi = {
+ listWorkflowRuns: jest.fn().mockResolvedValue([]),
+ getWorkflow: jest.fn(),
+ getWorkflowRun: jest.fn(),
+ reRunWorkflow: jest.fn(),
+ downloadJobLogsForWorkflowRun: jest.fn(),
+ } as jest.Mocked;
- const apis = ApiRegistry.from([
- [buildKiteApiRef, new BuildkiteApi({ discoveryApi })],
- ]);
+ const apis = ApiRegistry.with(githubActionsApiRef, mockedApi);
- describe('CICDSwitcher Test', () => {
- it('Should render Buildkite View', async () => {
- const renderedComponent = await renderWithEffects(
- wrapInTestApp(
-
-
- ,
- ),
+ describe('cicdContent', () => {
+ it('Should render GitHub Actions View', async () => {
+ const rendered = await renderInTestApp(
+
+
+
+
+ {cicdContent}
+
+
+
+ ,
);
- expect(
- renderedComponent.getByText(/exampleProject\/examplePipeline/),
- ).toBeInTheDocument();
+
+ expect(rendered.getByText('ExampleComponent')).toBeInTheDocument();
+
+ await expect(
+ rendered.findByText('No Workflow Data'),
+ ).resolves.toBeInTheDocument();
+ expect(rendered.getByText('Create new Workflow')).toBeInTheDocument();
});
});
});
diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx
index 5dc4f4c570..d6f91957a9 100644
--- a/packages/app/src/components/catalog/EntityPage.tsx
+++ b/packages/app/src/components/catalog/EntityPage.tsx
@@ -14,174 +14,96 @@
* limitations under the License.
*/
-import {
- ApiEntity,
- DomainEntity,
- Entity,
- GroupEntity,
- SystemEntity,
- UserEntity,
-} from '@backstage/catalog-model';
+import React, { ReactNode, useMemo, useState } from 'react';
+import BadgeIcon from '@material-ui/icons/CallToAction';
import { EmptyState } from '@backstage/core';
import {
- ApiDefinitionCard,
- ConsumedApisCard,
- ConsumingComponentsCard,
+ EntityApiDefinitionCard,
+ EntityConsumingComponentsCard,
EntityHasApisCard,
- ProvidedApisCard,
- ProvidingComponentsCard,
+ EntityProvidingComponentsCard,
+ EntityProvidedApisCard,
+ EntityConsumedApisCard,
} from '@backstage/plugin-api-docs';
import { EntityBadgesDialog } from '@backstage/plugin-badges';
import {
- AboutCard,
+ EntityAboutCard,
EntityHasComponentsCard,
EntityHasSubcomponentsCard,
EntityHasSystemsCard,
+ EntityLayout,
EntityLinksCard,
- EntityPageLayout,
EntitySystemDiagramCard,
+ EntitySwitch,
+ isComponentType,
+ isKind,
} from '@backstage/plugin-catalog';
-import { EntityProvider, useEntity } from '@backstage/plugin-catalog-react';
import {
- isPluginApplicableToEntity as isCircleCIAvailable,
- Router as CircleCIRouter,
+ EntityCircleCIContent,
+ isCircleCIAvailable,
} from '@backstage/plugin-circleci';
import {
- isPluginApplicableToEntity as isCloudbuildAvailable,
- Router as CloudbuildRouter,
+ EntityCloudbuildContent,
+ isCloudbuildAvailable,
} from '@backstage/plugin-cloudbuild';
import {
- isPluginApplicableToEntity as isGitHubActionsAvailable,
- RecentWorkflowRunsCard,
- Router as GitHubActionsRouter,
+ EntityGithubActionsContent,
+ EntityRecentGithubActionsRunsCard,
+ isGithubActionsAvailable,
} from '@backstage/plugin-github-actions';
import {
- isPluginApplicableToEntity as isJenkinsAvailable,
- LatestRunCard as JenkinsLatestRunCard,
- Router as JenkinsRouter,
+ EntityJenkinsContent,
+ EntityLatestJenkinsRunCard,
+ isJenkinsAvailable,
} from '@backstage/plugin-jenkins';
-import { Router as KafkaRouter } from '@backstage/plugin-kafka';
-import { Router as KubernetesRouter } from '@backstage/plugin-kubernetes';
+import { EntityKafkaContent } from '@backstage/plugin-kafka';
+import { EntityKubernetesContent } from '@backstage/plugin-kubernetes';
import {
- EmbeddedRouter as LighthouseRouter,
- isPluginApplicableToEntity as isLighthouseAvailable,
- LastLighthouseAuditCard,
+ EntityLastLighthouseAuditCard,
+ EntityLighthouseContent,
+ isLighthouseAvailable,
} from '@backstage/plugin-lighthouse';
import {
- GroupProfileCard,
- MembersListCard,
- OwnershipCard,
- UserProfileCard,
+ EntityGroupProfileCard,
+ EntityMembersListCard,
+ EntityOwnershipCard,
+ EntityUserProfileCard,
} from '@backstage/plugin-org';
import {
- isPluginApplicableToEntity as isPagerDutyAvailable,
- PagerDutyCard,
+ EntityPagerDutyCard,
+ isPagerDutyAvailable,
} from '@backstage/plugin-pagerduty';
import {
+ EntityRollbarContent,
isRollbarAvailable,
- Router as RollbarRouter,
} from '@backstage/plugin-rollbar';
-import { Router as SentryRouter } from '@backstage/plugin-sentry';
-import { EmbeddedDocsRouter as DocsRouter } from '@backstage/plugin-techdocs';
+import { EntitySentryContent } from '@backstage/plugin-sentry';
+import { EntityTechdocsContent } from '@backstage/plugin-techdocs';
import { EntityTodoContent } from '@backstage/plugin-todo';
import { Button, Grid } from '@material-ui/core';
import {
- isPluginApplicableToEntity as isBuildkiteAvailable,
- Router as BuildkiteRouter,
+ EntityBuildkiteContent,
+ isBuildkiteAvailable,
} from '@roadiehq/backstage-plugin-buildkite';
import {
- isPluginApplicableToEntity as isGitHubAvailable,
- LanguagesCard,
- ReadMeCard,
- ReleasesCard,
- Router as GitHubInsightsRouter,
+ EntityGithubInsightsContent,
+ EntityGithubInsightsLanguagesCard,
+ EntityGithubInsightsReadmeCard,
+ EntityGithubInsightsReleasesCard,
+ isGithubInsightsAvailable,
} from '@roadiehq/backstage-plugin-github-insights';
import {
- isPluginApplicableToEntity as isPullRequestsAvailable,
- PullRequestsStatsCard,
- Router as PullRequestsRouter,
+ EntityGithubPullRequestsContent,
+ EntityGithubPullRequestsOverviewCard,
+ isGithubPullRequestsAvailable,
} from '@roadiehq/backstage-plugin-github-pull-requests';
import {
- isPluginApplicableToEntity as isTravisCIAvailable,
- RecentTravisCIBuildsWidget,
- Router as TravisCIRouter,
+ EntityTravisCIContent,
+ EntityTravisCIOverviewCard,
+ isTravisciAvailable,
} from '@roadiehq/backstage-plugin-travis-ci';
-import React, { ReactNode, useMemo, useState } from 'react';
-import BadgeIcon from '@material-ui/icons/CallToAction';
-export const CICDSwitcher = ({ entity }: { entity: Entity }) => {
- // This component is just an example of how you can implement your company's logic in entity page.
- // You can for example enforce that all components of type 'service' should use GitHubActions
- switch (true) {
- case isJenkinsAvailable(entity):
- return ;
- case isBuildkiteAvailable(entity):
- return ;
- case isCircleCIAvailable(entity):
- return ;
- case isCloudbuildAvailable(entity):
- return ;
- case isTravisCIAvailable(entity):
- return ;
- case isGitHubActionsAvailable(entity):
- return ;
- default:
- return (
-
- Read more
-
- }
- />
- );
- }
-};
-
-const RecentCICDRunsSwitcher = ({ entity }: { entity: Entity }) => {
- let content: ReactNode;
- switch (true) {
- case isJenkinsAvailable(entity):
- content = ;
- break;
- case isTravisCIAvailable(entity):
- content = ;
- break;
- case isGitHubActionsAvailable(entity):
- content = (
-
- );
- break;
- default:
- content = null;
- }
- if (!content) {
- return null;
- }
- return (
-
- {content}
-
- );
-};
-
-export const ErrorsSwitcher = ({ entity }: { entity: Entity }) => {
- switch (true) {
- case isRollbarAvailable(entity):
- return ;
- default:
- return ;
- }
-};
-
-const EntityPageLayoutWrapper = (props: { children?: React.ReactNode }) => {
+const EntityLayoutWrapper = (props: { children?: ReactNode }) => {
const [badgesDialogOpen, setBadgesDialogOpen] = useState(false);
const extraMenuItems = useMemo(() => {
@@ -196,9 +118,9 @@ const EntityPageLayoutWrapper = (props: { children?: React.ReactNode }) => {
return (
<>
-
+
{props.children}
-
+
setBadgesDialogOpen(false)}
@@ -207,350 +129,367 @@ const EntityPageLayoutWrapper = (props: { children?: React.ReactNode }) => {
);
};
-const ComponentOverviewContent = ({ entity }: { entity: Entity }) => (
+export const cicdContent = (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Read more
+
+ }
+ />
+
+
+);
+
+const cicdCard = (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+
+const errorsContent = (
+
+
+
+
+
+
+
+
+
+);
+
+const overviewContent = (
-
-
+
+
- {isPagerDutyAvailable(entity) && (
-
-
-
-
-
- )}
-
-
+
+
+
+
+
+
+
+
+
+
+
-
- {isGitHubAvailable(entity) && (
- <>
-
-
-
+
+ {cicdCard}
+
+
+ Boolean(isGithubInsightsAvailable(e))}>
+
+
+
-
-
+
+
- >
- )}
- {isLighthouseAvailable(entity) && (
-
-
-
- )}
- {isPullRequestsAvailable(entity) && (
-
-
-
- )}
-
+
+
+
+
+
+
+
+
+
+
+
+
+ Boolean(isGithubPullRequestsAvailable(e))}>
+
+
+
+
+
+
+
);
-const ComponentApisContent = ({ entity }: { entity: Entity }) => (
-
-
-
-
-
-
-
-
-);
+const serviceEntityPage = (
+
+
+ {overviewContent}
+
-const ServiceEntityPage = ({ entity }: { entity: Entity }) => (
-
- }
- />
- }
- />
- }
- />
- }
- />
- }
- />
- }
- />
- }
- />
- }
- />
- }
- />
- }
- />
-
-);
+
+ {cicdContent}
+
-const WebsiteEntityPage = ({ entity }: { entity: Entity }) => (
-
- }
- />
- }
- />
- }
- />
- }
- />
- }
- />
- }
- />
- }
- />
- }
- />
- }
- />
-
-);
+
+ {errorsContent}
+
-const DefaultEntityPage = ({ entity }: { entity: Entity }) => (
-
- }
- />
- }
- />
- }
- />
-
-);
-
-export const ComponentEntityPage = ({ entity }: { entity: Entity }) => {
- switch (entity?.spec?.type) {
- case 'service':
- return ;
- case 'website':
- return ;
- default:
- return ;
- }
-};
-
-const ApiOverviewContent = ({ entity }: { entity: Entity }) => (
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+
+const websiteEntityPage = (
+
+
+ {overviewContent}
+
+
+
+ {cicdContent}
+
+
+
+
+
+
+
+ {errorsContent}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+
+const defaultEntityPage = (
+
+
+ {overviewContent}
+
+
+
+
+
+
+
+
+
+
+);
+
+const componentPage = (
+
+
+ {serviceEntityPage}
+
+
+
+ {websiteEntityPage}
+
+
+ {defaultEntityPage}
+
+);
+
+const apiPage = (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
);
-const ApiDefinitionContent = ({ entity }: { entity: ApiEntity }) => (
-
-
-
-
-
+const userPage = (
+
+
+
+
+
+
+
+
+
+
+
+
);
-const ApiEntityPage = ({ entity }: { entity: Entity }) => (
-
- }
- />
- }
- />
-
+const groupPage = (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
);
-const UserOverviewContent = ({ entity }: { entity: UserEntity }) => (
-
-
-
-
-
-
-
-
+const systemPage = (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
);
-const UserEntityPage = ({ entity }: { entity: Entity }) => (
-
- }
- />
-
+const domainPage = (
+
+
+
+
+
+
+
+
+
+
+
+
);
-const GroupOverviewContent = ({ entity }: { entity: GroupEntity }) => (
-
-
-
-
-
-
-
-
-
-
-
+export const entityPage = (
+
+
+
+
+
+
+
+
+ {defaultEntityPage}
+
);
-
-const GroupEntityPage = ({ entity }: { entity: Entity }) => (
-
- }
- />
-
-);
-
-const SystemOverviewContent = ({ entity }: { entity: SystemEntity }) => (
-
-
-
-
-
-
-
-
-
-
-
-);
-
-const SystemEntityPage = ({ entity }: { entity: Entity }) => (
-
- }
- />
- }
- />
-
-);
-
-const DomainOverviewContent = ({ entity }: { entity: DomainEntity }) => (
-
-
-
-
-
-
-
-
-);
-
-const DomainEntityPage = ({ entity }: { entity: Entity }) => (
-
- }
- />
-
-);
-
-export const EntityPage = () => {
- const { entity } = useEntity();
-
- switch (entity?.kind?.toLocaleLowerCase('en-US')) {
- case 'component':
- return ;
- case 'api':
- return ;
- case 'group':
- return ;
- case 'user':
- return ;
- case 'system':
- return ;
- case 'domain':
- return ;
- case 'location':
- case 'resource':
- case 'template':
- default:
- return ;
- }
-};
diff --git a/packages/app/src/plugins.ts b/packages/app/src/plugins.ts
index 4099061428..eff6093314 100644
--- a/packages/app/src/plugins.ts
+++ b/packages/app/src/plugins.ts
@@ -27,19 +27,19 @@ export { plugin as GraphiQL } from '@backstage/plugin-graphiql';
export { plugin as GithubActions } from '@backstage/plugin-github-actions';
export { plugin as Rollbar } from '@backstage/plugin-rollbar';
export { plugin as Newrelic } from '@backstage/plugin-newrelic';
-export { plugin as TravisCI } from '@roadiehq/backstage-plugin-travis-ci';
+export { travisciPlugin } from '@roadiehq/backstage-plugin-travis-ci';
export { plugin as Jenkins } from '@backstage/plugin-jenkins';
export { plugin as ApiDocs } from '@backstage/plugin-api-docs';
-export { plugin as GithubPullRequests } from '@roadiehq/backstage-plugin-github-pull-requests';
+export { githubPullRequestsPlugin } from '@roadiehq/backstage-plugin-github-pull-requests';
export { plugin as GcpProjects } from '@backstage/plugin-gcp-projects';
export { plugin as Kubernetes } from '@backstage/plugin-kubernetes';
export { plugin as Cloudbuild } from '@backstage/plugin-cloudbuild';
export { plugin as CostInsights } from '@backstage/plugin-cost-insights';
-export { plugin as GitHubInsights } from '@roadiehq/backstage-plugin-github-insights';
+export { githubInsightsPlugin } from '@roadiehq/backstage-plugin-github-insights';
export { plugin as CatalogImport } from '@backstage/plugin-catalog-import';
export { plugin as UserSettings } from '@backstage/plugin-user-settings';
export { plugin as PagerDuty } from '@backstage/plugin-pagerduty';
-export { plugin as Buildkite } from '@roadiehq/backstage-plugin-buildkite';
+export { buildkitePlugin } from '@roadiehq/backstage-plugin-buildkite';
export { plugin as Search } from '@backstage/plugin-search';
export { plugin as Org } from '@backstage/plugin-org';
export { plugin as Kafka } from '@backstage/plugin-kafka';
diff --git a/packages/backend-common/CHANGELOG.md b/packages/backend-common/CHANGELOG.md
index dfe057da53..c194f7e666 100644
--- a/packages/backend-common/CHANGELOG.md
+++ b/packages/backend-common/CHANGELOG.md
@@ -1,5 +1,13 @@
# @backstage/backend-common
+## 0.6.2
+
+### Patch Changes
+
+- b779b5fee: Add UrlReader for Google Cloud Storage
+- Updated dependencies [82c66b8cd]
+ - @backstage/config-loader@0.6.0
+
## 0.6.1
### Patch Changes
diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json
index b860336d31..41fa657818 100644
--- a/packages/backend-common/package.json
+++ b/packages/backend-common/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/backend-common",
"description": "Common functionality library for Backstage backends",
- "version": "0.6.1",
+ "version": "0.6.2",
"main": "src/index.ts",
"types": "src/index.ts",
"private": false,
@@ -31,9 +31,10 @@
"dependencies": {
"@backstage/cli-common": "^0.1.1",
"@backstage/config": "^0.1.4",
- "@backstage/config-loader": "^0.5.1",
+ "@backstage/config-loader": "^0.6.0",
"@backstage/errors": "^0.1.1",
"@backstage/integration": "^0.5.1",
+ "@google-cloud/storage": "^5.8.0",
"@octokit/rest": "^18.0.12",
"@types/cors": "^2.8.6",
"@types/dockerode": "^3.2.1",
@@ -56,6 +57,7 @@
"minimatch": "^3.0.4",
"minimist": "^1.2.5",
"morgan": "^1.10.0",
+ "raw-body": "^2.4.1",
"selfsigned": "^1.10.7",
"stoppable": "^1.1.0",
"tar": "^6.0.5",
@@ -71,8 +73,8 @@
}
},
"devDependencies": {
- "@backstage/cli": "^0.6.5",
- "@backstage/test-utils": "^0.1.9",
+ "@backstage/cli": "^0.6.7",
+ "@backstage/test-utils": "^0.1.10",
"@types/archiver": "^5.1.0",
"@types/compression": "^1.7.0",
"@types/concat-stream": "^1.6.0",
diff --git a/packages/backend-common/src/reading/GoogleGcsUrlReader.test.ts b/packages/backend-common/src/reading/GoogleGcsUrlReader.test.ts
new file mode 100644
index 0000000000..e201361a91
--- /dev/null
+++ b/packages/backend-common/src/reading/GoogleGcsUrlReader.test.ts
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { ConfigReader, JsonObject } from '@backstage/config';
+import { getVoidLogger } from '../logging';
+import { ReadTreeResponseFactory } from './tree';
+import { GoogleGcsUrlReader } from './GoogleGcsUrlReader';
+import { UrlReaderPredicateTuple } from './types';
+
+describe('GcsUrlReader', () => {
+ const createReader = (config: JsonObject): UrlReaderPredicateTuple[] => {
+ return GoogleGcsUrlReader.factory({
+ config: new ConfigReader(config),
+ logger: getVoidLogger(),
+ treeResponseFactory: ReadTreeResponseFactory.create({
+ config: new ConfigReader({}),
+ }),
+ });
+ };
+
+ it('does not create a reader without the googleGcs field', () => {
+ const entries = createReader({
+ integrations: {},
+ });
+ expect(entries).toHaveLength(0);
+ });
+
+ it('creates a reader with credentials correctly configured', () => {
+ const entries = createReader({
+ integrations: {
+ googleGcs: {
+ privateKey: '--- BEGIN KEY ---- fakekey --- END KEY ---',
+ clientEmail: 'someone@example.com',
+ },
+ },
+ });
+ expect(entries).toHaveLength(1);
+ });
+
+ it('creates a reader with default credentials provider', () => {
+ const entries = createReader({
+ integrations: {
+ googleGcs: {},
+ },
+ });
+ expect(entries).toHaveLength(1);
+ });
+
+ describe('predicates', () => {
+ const readers = createReader({
+ integrations: {
+ googleGcs: {},
+ },
+ });
+ const predicate = readers[0].predicate;
+
+ it('returns true for the correct google cloud storage host', () => {
+ expect(predicate(new URL('https://storage.cloud.google.com'))).toBe(true);
+ });
+ it('returns true for a url with the full path and the correct host', () => {
+ expect(
+ predicate(
+ new URL(
+ 'https://storage.cloud.google.com/team1/service1/catalog-info.yaml',
+ ),
+ ),
+ ).toBe(true);
+ });
+ it('returns false for the wrong hostname under cloud.google.com', () => {
+ expect(predicate(new URL('https://storage2.cloud.google.com'))).toBe(
+ false,
+ );
+ });
+ it('returns false for a partially correct host', () => {
+ expect(predicate(new URL('https://cloud.google.com'))).toBe(false);
+ });
+ it('returns false for a completely different host', () => {
+ expect(predicate(new URL('https://a.example.com/test'))).toBe(false);
+ });
+ });
+});
diff --git a/packages/backend-common/src/reading/GoogleGcsUrlReader.ts b/packages/backend-common/src/reading/GoogleGcsUrlReader.ts
new file mode 100644
index 0000000000..4ea24fa22f
--- /dev/null
+++ b/packages/backend-common/src/reading/GoogleGcsUrlReader.ts
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Storage } from '@google-cloud/storage';
+import {
+ ReaderFactory,
+ ReadTreeResponse,
+ SearchResponse,
+ UrlReader,
+} from './types';
+import getRawBody from 'raw-body';
+import {
+ GoogleGcsIntegrationConfig,
+ readGoogleGcsIntegrationConfig,
+} from '@backstage/integration';
+
+const GOOGLE_GCS_HOST = 'storage.cloud.google.com';
+
+const parseURL = (
+ url: string,
+): { host: string; bucket: string; key: string } => {
+ const { host, pathname } = new URL(url);
+
+ if (host !== GOOGLE_GCS_HOST) {
+ throw new Error(`not a valid GCS URL: ${url}`);
+ }
+
+ const [, bucket, ...key] = pathname.split('/');
+ return {
+ host: host,
+ bucket,
+ key: key.join('/'),
+ };
+};
+
+export class GoogleGcsUrlReader implements UrlReader {
+ static factory: ReaderFactory = ({ config, logger }) => {
+ if (!config.has('integrations.googleGcs')) {
+ return [];
+ }
+ const gcsConfig = readGoogleGcsIntegrationConfig(
+ config.getConfig('integrations.googleGcs'),
+ );
+ let storage: Storage;
+ if (!gcsConfig.clientEmail || !gcsConfig.privateKey) {
+ logger.info(
+ 'googleGcs credentials not found in config. Using default credentials provider.',
+ );
+ storage = new Storage();
+ } else {
+ storage = new Storage({
+ credentials: {
+ client_email: gcsConfig.clientEmail || undefined,
+ private_key: gcsConfig.privateKey || undefined,
+ },
+ });
+ }
+ const reader = new GoogleGcsUrlReader(gcsConfig, storage);
+ const predicate = (url: URL) => url.host === GOOGLE_GCS_HOST;
+ return [{ reader, predicate }];
+ };
+
+ constructor(
+ private readonly integration: GoogleGcsIntegrationConfig,
+ private readonly storage: Storage,
+ ) {}
+
+ async read(url: string): Promise {
+ try {
+ const { bucket, key } = parseURL(url);
+
+ return await getRawBody(
+ this.storage.bucket(bucket).file(key).createReadStream(),
+ );
+ } catch (error) {
+ throw new Error(`unable to read gcs file from ${url}, ${error}`);
+ }
+ }
+
+ async readTree(): Promise {
+ throw new Error('GcsUrlReader does not implement readTree');
+ }
+
+ async search(): Promise {
+ throw new Error('GcsUrlReader does not implement search');
+ }
+
+ toString() {
+ const key = this.integration.privateKey;
+ return `googleGcs{host=${GOOGLE_GCS_HOST},authed=${Boolean(key)}}`;
+ }
+}
diff --git a/packages/backend-common/src/reading/UrlReaders.ts b/packages/backend-common/src/reading/UrlReaders.ts
index 2f233463bb..06c6fdcdcf 100644
--- a/packages/backend-common/src/reading/UrlReaders.ts
+++ b/packages/backend-common/src/reading/UrlReaders.ts
@@ -24,6 +24,7 @@ import { GithubUrlReader } from './GithubUrlReader';
import { GitlabUrlReader } from './GitlabUrlReader';
import { ReadTreeResponseFactory } from './tree';
import { FetchUrlReader } from './FetchUrlReader';
+import { GoogleGcsUrlReader } from './GoogleGcsUrlReader';
type CreateOptions = {
/** Root config object */
@@ -70,6 +71,7 @@ export class UrlReaders {
BitbucketUrlReader.factory,
GithubUrlReader.factory,
GitlabUrlReader.factory,
+ GoogleGcsUrlReader.factory,
FetchUrlReader.factory,
]),
});
diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile
index acef405c8a..02aab45609 100644
--- a/packages/backend/Dockerfile
+++ b/packages/backend/Dockerfile
@@ -16,11 +16,13 @@ WORKDIR /app
# Copy repo skeleton first, to avoid unnecessary docker cache invalidation.
# The skeleton contains the package.json of each package in the monorepo,
# and along with yarn.lock and the root package.json, that's enough to run yarn install.
-ADD yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./
+COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./
+RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz
RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)"
# Then copy the rest of the backend bundle, along with any other files we might want.
ADD packages/backend/dist/bundle.tar.gz app-config.yaml ./
+RUN tar xzf bundle.tar.gz && rm bundle.tar.gz
CMD ["node", "packages/backend", "--config", "app-config.yaml"]
diff --git a/packages/catalog-model/CHANGELOG.md b/packages/catalog-model/CHANGELOG.md
index 0ad5d6993f..71a0ba6676 100644
--- a/packages/catalog-model/CHANGELOG.md
+++ b/packages/catalog-model/CHANGELOG.md
@@ -1,5 +1,12 @@
# @backstage/catalog-model
+## 0.7.6
+
+### Patch Changes
+
+- 97b60de98: Added `stringifyEntityRef`, which always creates a string representation of an entity reference. Also deprecated `serializeEntityRef`, as `stringifyEntityRef` should be used instead.
+- 98dd5da71: Add support for multiple links to post-scaffold task summary page
+
## 0.7.5
### Patch Changes
diff --git a/packages/catalog-model/package.json b/packages/catalog-model/package.json
index 1e5239cc23..680c73e905 100644
--- a/packages/catalog-model/package.json
+++ b/packages/catalog-model/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/catalog-model",
- "version": "0.7.5",
+ "version": "0.7.6",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -39,7 +39,7 @@
"yup": "^0.29.3"
},
"devDependencies": {
- "@backstage/cli": "^0.6.4",
+ "@backstage/cli": "^0.6.7",
"@types/express": "^4.17.6",
"@types/jest": "^26.0.7",
"@types/lodash": "^4.14.151",
diff --git a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.test.ts b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.test.ts
index 9284a5d5b1..358e7b6526 100644
--- a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.test.ts
+++ b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.test.ts
@@ -36,6 +36,7 @@ describe('ComponentV1alpha1Validator', () => {
subcomponentOf: 'monolith',
providesApis: ['api-0'],
consumesApis: ['api-0'],
+ dependsOn: ['resource:resource-0', 'component:component-0'],
system: 'system',
},
};
@@ -160,6 +161,26 @@ describe('ComponentV1alpha1Validator', () => {
await expect(validator.check(entity)).resolves.toBe(true);
});
+ it('accepts missing dependsOn', async () => {
+ delete (entity as any).spec.dependsOn;
+ await expect(validator.check(entity)).resolves.toBe(true);
+ });
+
+ it('rejects empty dependsOn', async () => {
+ (entity as any).spec.dependsOn = [''];
+ await expect(validator.check(entity)).rejects.toThrow(/dependsOn/);
+ });
+
+ it('rejects undefined dependsOn', async () => {
+ (entity as any).spec.dependsOn = [undefined];
+ await expect(validator.check(entity)).rejects.toThrow(/dependsOn/);
+ });
+
+ it('accepts no dependsOn', async () => {
+ (entity as any).spec.dependsOn = [];
+ await expect(validator.check(entity)).resolves.toBe(true);
+ });
+
it('accepts missing system', async () => {
delete (entity as any).spec.system;
await expect(validator.check(entity)).resolves.toBe(true);
diff --git a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts
index 3006a4288c..489aa8b1bd 100644
--- a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts
+++ b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts
@@ -34,6 +34,7 @@ export interface ComponentEntityV1alpha1 extends Entity {
subcomponentOf?: string;
providesApis?: string[];
consumesApis?: string[];
+ dependsOn?: string[];
system?: string;
};
}
diff --git a/packages/catalog-model/src/kinds/ResourceEntityV1alpha1.test.ts b/packages/catalog-model/src/kinds/ResourceEntityV1alpha1.test.ts
index ad8ea5cdf3..953ec5889a 100644
--- a/packages/catalog-model/src/kinds/ResourceEntityV1alpha1.test.ts
+++ b/packages/catalog-model/src/kinds/ResourceEntityV1alpha1.test.ts
@@ -32,6 +32,7 @@ describe('ResourceV1alpha1Validator', () => {
spec: {
type: 'database',
owner: 'me',
+ dependsOn: ['component:component-0', 'resource:resource-0'],
system: 'system',
},
};
@@ -86,6 +87,26 @@ describe('ResourceV1alpha1Validator', () => {
await expect(validator.check(entity)).rejects.toThrow(/owner/);
});
+ it('accepts missing dependsOn', async () => {
+ delete (entity as any).spec.dependsOn;
+ await expect(validator.check(entity)).resolves.toBe(true);
+ });
+
+ it('rejects empty dependsOn', async () => {
+ (entity as any).spec.dependsOn = [''];
+ await expect(validator.check(entity)).rejects.toThrow(/dependsOn/);
+ });
+
+ it('rejects undefined dependsOn', async () => {
+ (entity as any).spec.dependsOn = [undefined];
+ await expect(validator.check(entity)).rejects.toThrow(/dependsOn/);
+ });
+
+ it('accepts no dependsOn', async () => {
+ (entity as any).spec.dependsOn = [];
+ await expect(validator.check(entity)).resolves.toBe(true);
+ });
+
it('accepts missing system', async () => {
delete (entity as any).spec.system;
await expect(validator.check(entity)).resolves.toBe(true);
diff --git a/packages/catalog-model/src/kinds/ResourceEntityV1alpha1.ts b/packages/catalog-model/src/kinds/ResourceEntityV1alpha1.ts
index 520a39f02c..fd71500f40 100644
--- a/packages/catalog-model/src/kinds/ResourceEntityV1alpha1.ts
+++ b/packages/catalog-model/src/kinds/ResourceEntityV1alpha1.ts
@@ -30,6 +30,7 @@ export interface ResourceEntityV1alpha1 extends Entity {
spec: {
type: string;
owner: string;
+ dependsOn?: string[];
system?: string;
};
}
diff --git a/packages/catalog-model/src/schema/kinds/Component.v1alpha1.schema.json b/packages/catalog-model/src/schema/kinds/Component.v1alpha1.schema.json
index 822059a0ea..34fc48d285 100644
--- a/packages/catalog-model/src/schema/kinds/Component.v1alpha1.schema.json
+++ b/packages/catalog-model/src/schema/kinds/Component.v1alpha1.schema.json
@@ -84,6 +84,14 @@
"type": "string",
"minLength": 1
}
+ },
+ "dependsOn": {
+ "type": "array",
+ "description": "An array of references to other entities that the component depends on to function.",
+ "items": {
+ "type": "string",
+ "minLength": 1
+ }
}
}
}
diff --git a/packages/catalog-model/src/schema/kinds/Resource.v1alpha1.schema.json b/packages/catalog-model/src/schema/kinds/Resource.v1alpha1.schema.json
index b426dd7f94..4958afc18c 100644
--- a/packages/catalog-model/src/schema/kinds/Resource.v1alpha1.schema.json
+++ b/packages/catalog-model/src/schema/kinds/Resource.v1alpha1.schema.json
@@ -47,6 +47,14 @@
"examples": ["artist-relations-team", "user:john.johnson"],
"minLength": 1
},
+ "dependsOn": {
+ "type": "array",
+ "description": "An array of references to other entities that the resource depends on to function.",
+ "items": {
+ "type": "string",
+ "minLength": 1
+ }
+ },
"system": {
"type": "string",
"description": "An entity reference to the system that the resource belongs to.",
diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md
index c29669f31c..f9dc749009 100644
--- a/packages/cli/CHANGELOG.md
+++ b/packages/cli/CHANGELOG.md
@@ -1,5 +1,12 @@
# @backstage/cli
+## 0.6.7
+
+### Patch Changes
+
+- Updated dependencies [82c66b8cd]
+ - @backstage/config-loader@0.6.0
+
## 0.6.6
### Patch Changes
diff --git a/packages/cli/config/tsconfig.json b/packages/cli/config/tsconfig.json
index a55b215f74..f6b4f65edd 100644
--- a/packages/cli/config/tsconfig.json
+++ b/packages/cli/config/tsconfig.json
@@ -11,7 +11,7 @@
"incremental": true,
"isolatedModules": true,
"jsx": "react",
- "lib": ["DOM", "DOM.Iterable", "ScriptHost", "ES2020", "ESNext.Promise"],
+ "lib": ["DOM", "DOM.Iterable", "ScriptHost", "ES2020"],
"module": "ESNext",
"moduleResolution": "node",
"noEmit": false,
diff --git a/packages/cli/package.json b/packages/cli/package.json
index 01306eba2a..22b116e87c 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/cli",
"description": "CLI for developing Backstage plugins and apps",
- "version": "0.6.6",
+ "version": "0.6.7",
"private": false,
"publishConfig": {
"access": "public"
@@ -32,7 +32,7 @@
"@babel/plugin-transform-modules-commonjs": "^7.4.4",
"@backstage/cli-common": "^0.1.1",
"@backstage/config": "^0.1.4",
- "@backstage/config-loader": "^0.5.1",
+ "@backstage/config-loader": "^0.6.0",
"@hot-loader/react-dom": "^16.13.0",
"@lerna/package-graph": "^4.0.0",
"@lerna/project": "^4.0.0",
@@ -116,11 +116,11 @@
"yn": "^4.0.0"
},
"devDependencies": {
- "@backstage/backend-common": "^0.6.0",
+ "@backstage/backend-common": "^0.6.2",
"@backstage/config": "^0.1.4",
- "@backstage/core": "^0.7.3",
+ "@backstage/core": "^0.7.4",
"@backstage/dev-utils": "^0.1.13",
- "@backstage/test-utils": "^0.1.9",
+ "@backstage/test-utils": "^0.1.10",
"@backstage/theme": "^0.2.5",
"@types/diff": "^5.0.0",
"@types/express": "^4.17.6",
diff --git a/packages/cli/src/lib/bundler/transforms.ts b/packages/cli/src/lib/bundler/transforms.ts
index 4f097343c2..6107485d78 100644
--- a/packages/cli/src/lib/bundler/transforms.ts
+++ b/packages/cli/src/lib/bundler/transforms.ts
@@ -74,7 +74,7 @@ export const transforms = (options: TransformOptions): Transforms => {
/\.jpe?g$/,
/\.png$/,
/\.frag/,
- { test: /\.svg/, not: [/\.icon\.svg/] },
+ { and: [/\.svg/, { not: [/\.icon\.svg/] }] },
/\.xml/,
],
loader: require.resolve('url-loader'),
diff --git a/packages/config-loader/CHANGELOG.md b/packages/config-loader/CHANGELOG.md
index cbc6473286..cb8ef123ce 100644
--- a/packages/config-loader/CHANGELOG.md
+++ b/packages/config-loader/CHANGELOG.md
@@ -1,5 +1,21 @@
# @backstage/config-loader
+## 0.6.0
+
+### Minor Changes
+
+- 82c66b8cd: Fix bug where `${...}` was not being escaped to `${...}`
+
+ Add support for environment variable substitution in `$include`, `$file` and
+ `$env` transform values.
+
+ - This change allows for including dynamic paths, such as environment specific
+ secrets by using the same environment variable substitution (`${..}`) already
+ supported outside of the various include transforms.
+ - If you are currently using the syntax `${...}` in your include transform values,
+ you will need to escape the substitution by using `${...}` instead to maintain
+ the same behavior.
+
## 0.5.1
### Patch Changes
diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json
index a54975c6ff..0a0a76c2d6 100644
--- a/packages/config-loader/package.json
+++ b/packages/config-loader/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/config-loader",
"description": "Config loading functionality used by Backstage backend, and CLI",
- "version": "0.5.1",
+ "version": "0.6.0",
"private": false,
"publishConfig": {
"access": "public",
diff --git a/packages/core-api/CHANGELOG.md b/packages/core-api/CHANGELOG.md
index f8f38cbf59..609940b8db 100644
--- a/packages/core-api/CHANGELOG.md
+++ b/packages/core-api/CHANGELOG.md
@@ -1,5 +1,14 @@
# @backstage/core-api
+## 0.2.16
+
+### Patch Changes
+
+- 1279a3325: Introduce a `load-chunk` step in the `BootErrorPage` to show make chunk loading
+ errors visible to the user.
+- 4a4681b1b: Improved error messaging for routable extension errors, making it easier to identify the component and mount point that caused the error.
+- b051e770c: Fixed a bug with `useRouteRef` where navigating from routes beneath a mount point would often fail.
+
## 0.2.15
### Patch Changes
diff --git a/packages/core-api/package.json b/packages/core-api/package.json
index 411fa44c0d..33e5ac146d 100644
--- a/packages/core-api/package.json
+++ b/packages/core-api/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/core-api",
"description": "Internal Core API used by Backstage plugins and apps",
- "version": "0.2.15",
+ "version": "0.2.16",
"private": false,
"publishConfig": {
"access": "public",
@@ -42,8 +42,8 @@
"zen-observable": "^0.8.15"
},
"devDependencies": {
- "@backstage/cli": "^0.6.6",
- "@backstage/test-utils": "^0.1.9",
+ "@backstage/cli": "^0.6.7",
+ "@backstage/test-utils": "^0.1.10",
"@backstage/test-utils-core": "^0.1.1",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md
index 45f26804a5..519a43788d 100644
--- a/packages/core/CHANGELOG.md
+++ b/packages/core/CHANGELOG.md
@@ -1,5 +1,19 @@
# @backstage/core
+## 0.7.4
+
+### Patch Changes
+
+- 1279a3325: Introduce a `load-chunk` step in the `BootErrorPage` to show make chunk loading
+ errors visible to the user.
+- 4a4681b1b: Improved error messaging for routable extension errors, making it easier to identify the component and mount point that caused the error.
+- b051e770c: Fixed a bug with `useRouteRef` where navigating from routes beneath a mount point would often fail.
+- 98dd5da71: Add support for multiple links to post-scaffold task summary page
+- Updated dependencies [1279a3325]
+- Updated dependencies [4a4681b1b]
+- Updated dependencies [b051e770c]
+ - @backstage/core-api@0.2.16
+
## 0.7.3
### Patch Changes
diff --git a/packages/core/package.json b/packages/core/package.json
index 051bacc4c2..d5f9042d0e 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/core",
"description": "Core API used by Backstage plugins and apps",
- "version": "0.7.3",
+ "version": "0.7.4",
"private": false,
"publishConfig": {
"access": "public",
@@ -30,7 +30,7 @@
},
"dependencies": {
"@backstage/config": "^0.1.4",
- "@backstage/core-api": "^0.2.15",
+ "@backstage/core-api": "^0.2.16",
"@backstage/errors": "^0.1.1",
"@backstage/theme": "^0.2.5",
"@material-ui/core": "^4.11.0",
@@ -69,8 +69,8 @@
"zen-observable": "^0.8.15"
},
"devDependencies": {
- "@backstage/cli": "^0.6.6",
- "@backstage/test-utils": "^0.1.9",
+ "@backstage/cli": "^0.6.7",
+ "@backstage/test-utils": "^0.1.10",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^12.0.7",
diff --git a/packages/core/src/components/Dialog/Dialog.stories.tsx b/packages/core/src/components/Dialog/Dialog.stories.tsx
new file mode 100644
index 0000000000..c9388dcbec
--- /dev/null
+++ b/packages/core/src/components/Dialog/Dialog.stories.tsx
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2021 Spotify AB
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {
+ Button,
+ Dialog,
+ DialogActions,
+ DialogContent,
+ DialogTitle,
+ IconButton,
+ Typography,
+} from '@material-ui/core';
+import { makeStyles, createStyles, Theme } from '@material-ui/core/styles';
+import CloseIcon from '@material-ui/icons/Close';
+import React, { useState } from 'react';
+
+const useStyles = makeStyles((theme: Theme) =>
+ createStyles({
+ closeButton: {
+ position: 'absolute',
+ right: theme.spacing(1),
+ top: theme.spacing(1),
+ color: theme.palette.grey[500],
+ },
+ }),
+);
+
+export default {
+ title: 'Layout/Dialog',
+ component: Dialog,
+};
+
+export const Default = () => {
+ const [open, setOpen] = useState(false);
+ const classes = useStyles();
+
+ const openDialog = () => {
+ setOpen(true);
+ };
+
+ const closeDialog = () => {
+ setOpen(false);
+ };
+
+ const dialogContent = () => {
+ return (
+ <>
+
+ This is an example of how to use the Dialog component.
+
+
+ This component is used whenever confirmation of some sort is needed,
+ such as:
+
+
+
+
+ Consent to sensitive matters like GDPR, access, etc;
+
+
+
+
+ Save, submit, cancel after a form is completed;
+
+
+
+ Alert message;
+
+
+ Buttons are optional.
+
+
+
+ The color for the secondary button is the same as the primary.
+
+