Merge branch 'master' into mob/feature-flag-component
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const {
|
||||
default: defaultChangelogFunctions,
|
||||
} = require('@changesets/cli/changelog');
|
||||
|
||||
// Custom CHANGELOG generation for changesets, stolen from here with one minor change:
|
||||
// https://github.com/atlassian/changesets/blob/main/packages/cli/src/changelog/index.ts
|
||||
async function getDependencyReleaseLine(changesets, dependenciesUpdated) {
|
||||
if (dependenciesUpdated.length === 0) return '';
|
||||
|
||||
const updatedDepenenciesList = dependenciesUpdated.map(
|
||||
dependency => ` - ${dependency.name}@${dependency.newVersion}`,
|
||||
);
|
||||
|
||||
// Return one `Updated dependencies` bullet instead of repeating for each changeset; this
|
||||
// sacrifices the commit shas for brevity.
|
||||
return ['- Updated dependencies', ...updatedDepenenciesList].join('\n');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getReleaseLine: defaultChangelogFunctions.getReleaseLine,
|
||||
getDependencyReleaseLine,
|
||||
};
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-catalog': patch
|
||||
---
|
||||
|
||||
Exports `CatalogLayout` and `CreateComponentButton` for catalog customization.
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
'@backstage/create-app': patch
|
||||
---
|
||||
|
||||
Adding .DS_Store pattern to .gitignore in Scaffolded Backstage App. To migrate an existing app that pattern should be added manually.
|
||||
|
||||
```diff
|
||||
+# macOS
|
||||
+.DS_Store
|
||||
```
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://unpkg.com/@changesets/config@1.3.0/schema.json",
|
||||
"changelog": "@changesets/cli/changelog",
|
||||
"changelog": "./backstage-changelog.js",
|
||||
"commit": false,
|
||||
"linked": [["*"]],
|
||||
"access": "public",
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend': patch
|
||||
'@backstage/create-app': patch
|
||||
---
|
||||
|
||||
This release enables the new catalog processing engine which is a major milestone for the catalog!
|
||||
|
||||
This update makes processing more scalable across multiple instances, adds support for deletions and ui flagging of entities that are no longer referenced by a location.
|
||||
|
||||
**Changes Required** to `catalog.ts`
|
||||
|
||||
```diff
|
||||
-import { useHotCleanup } from '@backstage/backend-common';
|
||||
import {
|
||||
CatalogBuilder,
|
||||
- createRouter,
|
||||
- runPeriodically
|
||||
+ createRouter
|
||||
} from '@backstage/plugin-catalog-backend';
|
||||
import { Router } from 'express';
|
||||
import { PluginEnvironment } from '../types';
|
||||
|
||||
export default async function createPlugin(env: PluginEnvironment): Promise<Router> {
|
||||
- const builder = new CatalogBuilder(env);
|
||||
+ const builder = await CatalogBuilder.create(env);
|
||||
const {
|
||||
entitiesCatalog,
|
||||
locationsCatalog,
|
||||
- higherOrderOperation,
|
||||
+ locationService,
|
||||
+ processingEngine,
|
||||
locationAnalyzer,
|
||||
} = await builder.build();
|
||||
|
||||
- useHotCleanup(
|
||||
- module,
|
||||
- runPeriodically(() => higherOrderOperation.refreshAllLocations(), 100000),
|
||||
- );
|
||||
+ await processingEngine.start();
|
||||
|
||||
return await createRouter({
|
||||
entitiesCatalog,
|
||||
locationsCatalog,
|
||||
- higherOrderOperation,
|
||||
+ locationService,
|
||||
locationAnalyzer,
|
||||
logger: env.logger,
|
||||
config: env.config,
|
||||
```
|
||||
|
||||
As this is a major internal change we have taken some precaution by still allowing the old catalog to be enabled by keeping your `catalog.ts` in it's current state.
|
||||
If you encounter any issues and have to revert to the previous catalog engine make sure to raise an issue immediately as the old catalog engine is deprecated and will be removed in a future release.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/codemods': patch
|
||||
---
|
||||
|
||||
Fix execution of `jscodeshift` on windows.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend': patch
|
||||
---
|
||||
|
||||
Switches the default catalog processing engine to use a batched streaming task execution strategy for higher parallelism.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend': patch
|
||||
---
|
||||
|
||||
Rely on `SELECT ... FOR UPDATE SKIP LOCKED` where available in order to speed up processing item acquisition and reduce work duplication.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend': patch
|
||||
---
|
||||
|
||||
Make refresh interval configurable for the `NextCatalogBuilder` using `.setRefreshIntervalSeconds()`.
|
||||
|
||||
Change `DefaultProcessingDatabase` constructor to accept an options object instead of individual arguments.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend': patch
|
||||
---
|
||||
|
||||
Adds support to enable LFS for hosted Bitbucket
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-user-settings': patch
|
||||
---
|
||||
|
||||
Fix a bug that prevented changing themes on the user settings page when the theme `id` didn't match exactly the theme `variant`.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/config-loader': patch
|
||||
---
|
||||
|
||||
Removed workaround for breaking change in typescript 4.3 and bump `typescript-json-schema` instead. This should again allow the usage of `@items.visibility <value>` to set the visibility of array items.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/core-components': patch
|
||||
---
|
||||
|
||||
Add title prop in SupportButton component
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-techdocs': patch
|
||||
---
|
||||
|
||||
Adding support for user owned document filter for TechDocs custom Homepage
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/catalog-model': patch
|
||||
---
|
||||
|
||||
Removed unused `typescript-json-schema` dependency.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend': patch
|
||||
---
|
||||
|
||||
Use the correct parameter to create a public repository in Bitbucket Server.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-proxy-backend': patch
|
||||
---
|
||||
|
||||
Bump http-proxy-middleware from 0.19.2 to 2.0.0
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
# The last matching pattern takes precedence.
|
||||
# https://help.github.com/articles/about-codeowners/
|
||||
|
||||
* @backstage/maintainers
|
||||
* @backstage/reviewers
|
||||
/docs/features/techdocs @backstage/techdocs-core
|
||||
/docs/features/search @backstage/techdocs-core
|
||||
/docs/assets/search @backstage/techdocs-core
|
||||
|
||||
@@ -30,3 +30,4 @@
|
||||
| [Netflix](https://www.netflix.com/) | [bleathem](https://github.com/bleathem) | Our Backstage implementation will be the front door to a unified experience connecting our internal platform products across important workflows with integrated knowledge and support. |
|
||||
| [b.well](https://www.icanbwell.com/) | [Jacob Rosales](https://github.com/jrosales) | Foundation for our engineering portal and cloud insights. |
|
||||
| [PagerDuty](https://www.pagerduty.com/) | [Mark Shaw](https://github.com/markshawtoronto) | Developer portal, initially focused on software templates and tech-docs. |
|
||||
| [MoonShiner](https://moonshiner.at) | [Fabian Hippmann](https://github.com/FabianHippmann) | Developer portal - helps us keep track of our customer projects, onboard new developers & improve our development process 🌕🚀🧑🚀 |
|
||||
|
||||
+25
-1
@@ -36,9 +36,33 @@ To become a maintainer you need to demonstrate the following:
|
||||
|
||||
If a maintainer is no longer interested or cannot perform the maintainer duties listed above, they should volunteer to be moved to emeritus status. In extreme cases this can also occur by a vote of the sponsors and maintainers per the voting process below.
|
||||
|
||||
# Reviewers
|
||||
|
||||
The project also contains a team called [@backstage/reviewers](https://github.com/orgs/backstage/teams/reviewers). This is the team of people who are the fallback in [`CODEOWNERS`](./.github/CODEOWNERS). This team will typically contain the maintainers, and a small number of additional people who are permitted to approve and merge pull requests. The purpose of this group is to offload some of the review work from the maintainers, simplifying and speeding up the review process for contributors.
|
||||
|
||||
This responsibility is distinct from the maintainer role. A reviewer must not approve and merge changes that have a level of impact that a maintainer should oversee; see below for clarification. For that class of changes, a reviewer can still review the pull request thoroughly without approving it (e.g. with a comment on the pull request), and is expected to notify `@backstage/maintainers` for final approval. Note that it is best to not use the GitHub review approve functionality for this, since that would let Hall of Fame members self-merge the pull request before maintainers get the chance to look at it.
|
||||
|
||||
The following is a non-exhaustive list of types of change, for which a reviewer should defer final decision and merge to a maintainer:
|
||||
|
||||
- A larger refactoring that significantly affects the structure of/between packages
|
||||
- Changes that settle or alter the trajectory of contested ongoing topics in issues or elsewhere
|
||||
- Changes that affect the [Architecture Decision Records](./docs/architecture-decisions)
|
||||
- Changes to APIs that have large customer impact, such as the core APIs in `@backstage/core-*` packages, or significant `@backstage/cli` changes.
|
||||
- Pull requests whose build checks are not passing fully
|
||||
- Additions and removals of entire packages
|
||||
- Releases (e.g. pull requests titled `Version Packages`)
|
||||
|
||||
A maintainer may suggest an addition to the reviewers team by opening a pull request that modifies [`OWNERS.md`](./OWNERS.md) accordingly. Prospective reviewers are not expected to do this themselves, but should rather ask a maintainer to sponsor their addition. All of the maintainers and sponsors are called to vote on the addition (see the section below about voting). If the vote passes, the pull request can be approved and merged, and the corresponding addition to the GitHub team can be made.
|
||||
|
||||
A reviewer can elect to remove themselves from the reviewers group by opening, or asking a maintainer to open, a pull request that modifies [`OWNERS.md`](./OWNERS.md) accordingly. A maintainer will approve and merge the pull request, and the corresponding removal from the GitHub team can be made.
|
||||
|
||||
A maintainer can call on the other maintainers and sponsors for a vote to remove a reviewer (see the section below about conflict resolution and voting). If the vote passes, a maintainer creates a pull request that modifies [`OWNERS.md`](./OWNERS.md) accordingly. After approval by another maintainer, the pull request can be merged, and the corresponding removal from the GitHub team can be made.
|
||||
|
||||
# Conflict resolution and voting
|
||||
|
||||
In general, we prefer that technical issues and maintainer membership are amicably worked out between the persons involved. If a dispute cannot be decided independently, the sponsors and maintainers can be called in to decide an issue. If the sponsors and maintainers themselves cannot decide an issue, the issue will be resolved by voting. The voting process is a simple majority in which each sponsor receives two votes and each maintainer receives one vote.
|
||||
In general, we prefer that technical issues and membership are amicably worked out between the persons involved. If a dispute cannot be decided independently, the sponsors and maintainers can be called in to decide an issue. If the sponsors and maintainers themselves cannot decide an issue, the issue will be resolved by voting.
|
||||
|
||||
In all cases in this document where voting is mentioned, the voting process is a simple majority in which each sponsor receives two votes and each maintainer receives one vote. If such a majority is reached, the vote is said to have _passed_.
|
||||
|
||||
# Adding new projects to the Backstage GitHub organization
|
||||
|
||||
|
||||
@@ -16,6 +16,18 @@ This page lists all active sponsors and maintainers.
|
||||
- Ben Lambert ([benjdlambert](https://github.com/benjdlambert)) (Discord: @blam)
|
||||
- Johan Haals ([jhaals](https://github.com/jhaals)) (Discord: @jhaals)
|
||||
|
||||
# Reviewers
|
||||
|
||||
See [`GOVERNANCE.md`](./GOVERNANCE.md) for details about how the reviewers team
|
||||
works.
|
||||
|
||||
- Patrik Oldsberg ([rugvip](https://github.com/rugvip)) (Discord: @Rugvip)
|
||||
- Fredrik Adelöw ([freben](https://github.com/freben)) (Discord: @freben)
|
||||
- Ben Lambert ([benjdlambert](https://github.com/benjdlambert)) (Discord: @blam)
|
||||
- Johan Haals ([jhaals](https://github.com/jhaals)) (Discord: @jhaals)
|
||||
- Himanshu Mishra ([OrkoHunter](https://github.com/OrkoHunter)) (Discord: @OrkoHunter)
|
||||
- Tim Hansen ([timbonicus](https://github.com/timbonicus)) (Discord: @timbonicus)
|
||||
|
||||
# Emeritus maintainers
|
||||
|
||||
- Stefan Ålund ([stefanalund](https://github.com/stefanalund)) (Discord: @stalund)
|
||||
|
||||
@@ -93,6 +93,21 @@ declare the visibility of a leaf node of `type: "string"`.
|
||||
| `backend` | (Default) Only in backend |
|
||||
| `secret` | Only in backend and may be excluded from logs for security reasons |
|
||||
|
||||
You can set visibility with an `@visibility` comment in the `Config` Typescript
|
||||
interface.
|
||||
|
||||
```ts
|
||||
export interface Config {
|
||||
app: {
|
||||
/**
|
||||
* Frontend root URL
|
||||
* @visibility frontend
|
||||
*/
|
||||
baseUrl: string;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Validation
|
||||
|
||||
Schemas can be validated using the `backstage-cli config:check` command. If you
|
||||
|
||||
@@ -370,7 +370,7 @@ techdocs:
|
||||
openStackSwift:
|
||||
containerName: 'name-of-techdocs-storage-bucket'
|
||||
credentials:
|
||||
userName: ${OPENSTACK_SWIFT_STORAGE_USERNAME}
|
||||
username: ${OPENSTACK_SWIFT_STORAGE_USERNAME}
|
||||
password: ${OPENSTACK_SWIFT_STORAGE_PASSWORD}
|
||||
authUrl: ${OPENSTACK_SWIFT_STORAGE_AUTH_URL}
|
||||
keystoneAuthVersion: ${OPENSTACK_SWIFT_STORAGE_AUTH_VERSION}
|
||||
|
||||
@@ -99,7 +99,7 @@ import carmen from './plugins/carmen';
|
||||
async function main() {
|
||||
// ...
|
||||
const carmenEnv = useHotMemoize(module, () => createEnv('carmen'));
|
||||
apiRouter.use('/carmen', await carmen(badgesEnv));
|
||||
apiRouter.use('/carmen', await carmen(carmenEnv));
|
||||
```
|
||||
|
||||
After you start the backend (e.g. using `yarn start-backend` from the repo
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
---
|
||||
id: configuring-plugin-databases
|
||||
title: Configuring Plugin Databases
|
||||
# prettier-ignore
|
||||
description: Guide on how to configure Backstage databases.
|
||||
---
|
||||
|
||||
This guide covers a variety of production persistence use cases which are
|
||||
supported out of the box by Backstage. The database manager allows the developer
|
||||
to set the client and database connection details on a per plugin basis in
|
||||
addition to the base client and connection configuration. This means that you
|
||||
can use a SQLite 3 in-memory database for a specific plugin whilst using
|
||||
PostgreSQL for everything else and so on.
|
||||
|
||||
By default, Backstage uses automatically created databases for each plugin whose
|
||||
names follow the `backstage_plugin_<pluginId>` pattern, e.g.
|
||||
`backstage_plugin_auth`. You can configure a different database name prefix for
|
||||
use cases where you have multiple deployments running on a shared database
|
||||
instance or cluster.
|
||||
|
||||
With infrastructure defined as code or data (Terraform, AWS CloudFormation,
|
||||
etc.), you may have database credentials which lack permissions to create new
|
||||
databases or you do not have control over the database names. In these
|
||||
instances, you can set the database name and connection information on a per
|
||||
plugin basis as mentioned earlier.
|
||||
|
||||
Backstage supports all of these use cases with the `DatabaseManager` provided by
|
||||
`@backstage/backend-common`. We will now cover how to use and configure
|
||||
Backstage's databases.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Dependencies
|
||||
|
||||
Please ensure the appropriate database drivers are installed in your `backend`
|
||||
package. If you intend to use both `postgres` and `sqlite3`, you can install
|
||||
both of them.
|
||||
|
||||
```shell
|
||||
cd packages/backend
|
||||
|
||||
# install pg if you need postgres
|
||||
yarn add pg
|
||||
|
||||
# install sqlite3 if you intend to set it as the client
|
||||
yarn add sqlite3
|
||||
```
|
||||
|
||||
From an operational perspective, you only need to install drivers for clients
|
||||
that are actively used.
|
||||
|
||||
### Database Manager
|
||||
|
||||
Existing Backstage instances should be updated to use `DatabaseManager` from
|
||||
`@backstage/backend-common` in your `packages/backend/src/index.ts` file, the
|
||||
`SingleConnectionDatabaseManager` has been deprecated. Import the manager and
|
||||
update the references as shown below if this is not the case:
|
||||
|
||||
```diff
|
||||
import {
|
||||
- SingleConnectionDatabaseManager,
|
||||
+ DatabaseManager,
|
||||
} from '@backstage/backend-common';
|
||||
|
||||
// ...
|
||||
|
||||
function makeCreateEnv(config: Config) {
|
||||
// ...
|
||||
- const databaseManager = SingleConnectionDatabaseManager.fromConfig(config);
|
||||
+ const databaseManager = DatabaseManager.fromConfig(config);
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
You should set the base database client and connection information in your
|
||||
`app-config.yaml` (or equivalent) file. The base client and configuration is
|
||||
used as the default which is extended for each plugin with the same or unset
|
||||
client type. If a client type is specified for a specific plugin which does not
|
||||
match the base client, the configuration set for the plugin will be used as is
|
||||
without extending the base configuration.
|
||||
|
||||
Client type and configuration for plugins need to be defined under
|
||||
**`backend.database.plugin.<pluginId>`**. As an example, `catalog` is the
|
||||
`pluginId` for the catalog plugin and any configuration defined under that block
|
||||
is specific to that plugin. We will now explore more detailed example
|
||||
configurations below.
|
||||
|
||||
### Minimal In-Memory Configuration
|
||||
|
||||
In the example below, we are using `sqlite3` in-memory databases for all
|
||||
plugins. You may want to use this configuration for testing or other non-durable
|
||||
use cases.
|
||||
|
||||
```yaml
|
||||
backend:
|
||||
database:
|
||||
client: sqlite3
|
||||
connection: ':memory:'
|
||||
```
|
||||
|
||||
### PostgreSQL
|
||||
|
||||
The example below uses PostgreSQL (`pg`) as the database client for all plugins.
|
||||
The `auth` plugin uses a user defined database name instead of the automatically
|
||||
generated one which would have been `backstage_plugin_auth`.
|
||||
|
||||
```yaml
|
||||
backend:
|
||||
database:
|
||||
client: pg
|
||||
connection:
|
||||
host: some.example-pg-instance.tld
|
||||
user: postgres
|
||||
password: password
|
||||
port: 5432
|
||||
plugin:
|
||||
auth:
|
||||
connection:
|
||||
database: pg_auth_set_by_user
|
||||
```
|
||||
|
||||
### Custom Database Name Prefix
|
||||
|
||||
The configuration below uses `example_prefix_` as the database name prefix
|
||||
instead of `backstage_plugin_`. Plugins such as `auth` and `catalog` will use
|
||||
databases named `example_prefix_auth` and `example_prefix_catalog` respectively.
|
||||
|
||||
```yaml
|
||||
backend:
|
||||
database:
|
||||
client: pg
|
||||
connection:
|
||||
host: some.example-pg-instance.tld
|
||||
user: postgres
|
||||
password: password
|
||||
port: 5432
|
||||
prefix: 'example_prefix_'
|
||||
```
|
||||
|
||||
### Connection Configuration Per Plugin
|
||||
|
||||
Both `auth` and `catalog` use connection configuration with different
|
||||
credentials and database names. This type of configuration can be useful for
|
||||
environments with infrastructure as code or data which may provide randomly
|
||||
generated credentials and/or database names.
|
||||
|
||||
```yaml
|
||||
backend:
|
||||
database:
|
||||
client: pg
|
||||
connection: 'postgresql://some.example-pg-instance.tld:5432'
|
||||
plugin:
|
||||
auth:
|
||||
connection: 'postgresql://fort:knox@some.example-pg-instance.tld:5432/unwitting_fox_jumps'
|
||||
catalog:
|
||||
connection: 'postgresql://bank:reserve@some.example-pg-instance.tld:5432/shuffle_ransack_playback'
|
||||
```
|
||||
|
||||
### PostgreSQL and SQLite 3
|
||||
|
||||
The example below uses PostgreSQL (`pg`) as the database client for all plugins
|
||||
except the `auth` plugin which uses `sqlite3`. As the `auth` plugin's client
|
||||
type is different from the base client type, the connection configuration for
|
||||
`auth` is used verbatim without extending the base configuration for PostgreSQL.
|
||||
|
||||
```yaml
|
||||
backend:
|
||||
database:
|
||||
client: pg
|
||||
connection: 'postgresql://foo:bar@some.example-pg-instance.tld:5432'
|
||||
plugin:
|
||||
auth:
|
||||
client: sqlite3
|
||||
connection: ':memory:'
|
||||
```
|
||||
|
||||
## Check Your Databases
|
||||
|
||||
The `DatabaseManager` will attempt to create the databases if they do not exist.
|
||||
If you have set credentials per plugin because the credentials in the base
|
||||
configuration do not have permissions to create databases, you must ensure they
|
||||
exist before starting the service. The service will not be able to create them,
|
||||
it can only use them.
|
||||
|
||||
Good luck!
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: GKE Usage
|
||||
author: BESTSELLER
|
||||
authorUrl: bestsellerit.com
|
||||
category: Discovery
|
||||
description: This plugin will show you the cost and resource usage of your application within GKE.
|
||||
documentation: https://github.com/BESTSELLER/backstage-plugin-gkeusage/blob/master/README.md
|
||||
iconUrl: https://bestsellerit.com/img/google-container-engine_avatar.svg
|
||||
npmPackageName: '@bestsellerit/backstage-plugin-gkeusage'
|
||||
tags:
|
||||
- gke
|
||||
- cost
|
||||
- google
|
||||
- usage
|
||||
- metering
|
||||
@@ -1,5 +1,23 @@
|
||||
# example-app
|
||||
|
||||
## 0.2.33
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/plugin-catalog-react@0.2.3
|
||||
- @backstage/plugin-catalog@0.6.3
|
||||
- @backstage/cli@0.7.1
|
||||
- @backstage/plugin-api-docs@0.5.0
|
||||
- @backstage/plugin-jenkins@0.4.5
|
||||
- @backstage/plugin-techdocs@0.9.6
|
||||
- @backstage/plugin-circleci@0.2.16
|
||||
- @backstage/plugin-catalog-import@0.5.10
|
||||
- @backstage/plugin-sentry@0.3.12
|
||||
- @backstage/plugin-user-settings@0.2.11
|
||||
- @backstage/catalog-model@0.8.3
|
||||
- @backstage/core@0.7.13
|
||||
|
||||
## 0.2.32
|
||||
|
||||
### Patch Changes
|
||||
|
||||
+13
-13
@@ -1,21 +1,21 @@
|
||||
{
|
||||
"name": "example-app",
|
||||
"version": "0.2.32",
|
||||
"version": "0.2.33",
|
||||
"private": true,
|
||||
"bundled": true,
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.8.2",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/core": "^0.7.12",
|
||||
"@backstage/catalog-model": "^0.8.3",
|
||||
"@backstage/cli": "^0.7.1",
|
||||
"@backstage/core": "^0.7.13",
|
||||
"@backstage/integration-react": "^0.1.3",
|
||||
"@backstage/core-app-api": "^0.1.2",
|
||||
"@backstage/core-components": "^0.1.1",
|
||||
"@backstage/plugin-api-docs": "^0.4.15",
|
||||
"@backstage/plugin-api-docs": "^0.5.0",
|
||||
"@backstage/plugin-badges": "^0.2.2",
|
||||
"@backstage/plugin-catalog": "^0.6.2",
|
||||
"@backstage/plugin-catalog-import": "^0.5.9",
|
||||
"@backstage/plugin-catalog-react": "^0.2.2",
|
||||
"@backstage/plugin-circleci": "^0.2.15",
|
||||
"@backstage/plugin-catalog": "^0.6.3",
|
||||
"@backstage/plugin-catalog-import": "^0.5.10",
|
||||
"@backstage/plugin-catalog-react": "^0.2.3",
|
||||
"@backstage/plugin-circleci": "^0.2.16",
|
||||
"@backstage/plugin-cloudbuild": "^0.2.16",
|
||||
"@backstage/plugin-code-coverage": "^0.1.4",
|
||||
"@backstage/plugin-cost-insights": "^0.10.2",
|
||||
@@ -23,7 +23,7 @@
|
||||
"@backstage/plugin-gcp-projects": "^0.2.6",
|
||||
"@backstage/plugin-github-actions": "^0.4.9",
|
||||
"@backstage/plugin-graphiql": "^0.2.11",
|
||||
"@backstage/plugin-jenkins": "^0.4.4",
|
||||
"@backstage/plugin-jenkins": "^0.4.5",
|
||||
"@backstage/plugin-kafka": "^0.2.8",
|
||||
"@backstage/plugin-kubernetes": "^0.4.5",
|
||||
"@backstage/plugin-lighthouse": "^0.2.17",
|
||||
@@ -33,12 +33,12 @@
|
||||
"@backstage/plugin-rollbar": "^0.3.6",
|
||||
"@backstage/plugin-scaffolder": "^0.9.8",
|
||||
"@backstage/plugin-search": "^0.4.0",
|
||||
"@backstage/plugin-sentry": "^0.3.11",
|
||||
"@backstage/plugin-sentry": "^0.3.12",
|
||||
"@backstage/plugin-shortcuts": "^0.1.2",
|
||||
"@backstage/plugin-tech-radar": "^0.4.0",
|
||||
"@backstage/plugin-techdocs": "^0.9.5",
|
||||
"@backstage/plugin-techdocs": "^0.9.6",
|
||||
"@backstage/plugin-todo": "^0.1.2",
|
||||
"@backstage/plugin-user-settings": "^0.2.10",
|
||||
"@backstage/plugin-user-settings": "^0.2.11",
|
||||
"@backstage/theme": "^0.2.8",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
|
||||
@@ -1,5 +1,59 @@
|
||||
# @backstage/backend-common
|
||||
|
||||
## 0.8.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- e5cdf0560: Provide a more clear error message when database connection fails.
|
||||
- 772dbdb51: Deprecates `SingleConnectionDatabaseManager` and provides an API compatible database
|
||||
connection manager, `DatabaseManager`, which allows developers to configure database
|
||||
connections on a per plugin basis.
|
||||
|
||||
The `backend.database` config path allows you to set `prefix` to use an
|
||||
alternate prefix for automatically generated database names, the default is
|
||||
`backstage_plugin_`. Use `backend.database.plugin.<pluginId>` to set plugin
|
||||
specific database connection configuration, e.g.
|
||||
|
||||
```yaml
|
||||
backend:
|
||||
database:
|
||||
client: 'pg',
|
||||
prefix: 'custom_prefix_'
|
||||
connection:
|
||||
host: 'localhost'
|
||||
user: 'foo'
|
||||
password: 'bar'
|
||||
plugin:
|
||||
catalog:
|
||||
connection:
|
||||
database: 'database_name_overriden'
|
||||
scaffolder:
|
||||
client: 'sqlite3'
|
||||
connection: ':memory:'
|
||||
```
|
||||
|
||||
Migrate existing backstage installations by swapping out the database manager in the
|
||||
`packages/backend/src/index.ts` file as shown below:
|
||||
|
||||
```diff
|
||||
import {
|
||||
- SingleConnectionDatabaseManager,
|
||||
+ DatabaseManager,
|
||||
} from '@backstage/backend-common';
|
||||
|
||||
// ...
|
||||
|
||||
function makeCreateEnv(config: Config) {
|
||||
// ...
|
||||
- const databaseManager = SingleConnectionDatabaseManager.fromConfig(config);
|
||||
+ const databaseManager = DatabaseManager.fromConfig(config);
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/config-loader@0.6.4
|
||||
|
||||
## 0.8.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -100,6 +100,12 @@ export function createServiceBuilder(_module: NodeModule): ServiceBuilderImpl;
|
||||
// @public (undocumented)
|
||||
export function createStatusCheckRouter(options: StatusCheckRouterOptions): Promise<express.Router>;
|
||||
|
||||
// @public (undocumented)
|
||||
export class DatabaseManager {
|
||||
forPlugin(pluginId: string): PluginDatabaseManager;
|
||||
static fromConfig(config: Config): DatabaseManager;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export class DockerContainerRunner implements ContainerRunner {
|
||||
constructor({ dockerClient }: {
|
||||
@@ -326,11 +332,8 @@ export type ServiceBuilder = {
|
||||
// @public (undocumented)
|
||||
export function setRootLogger(newLogger: winston.Logger): void;
|
||||
|
||||
// @public
|
||||
export class SingleConnectionDatabaseManager {
|
||||
forPlugin(pluginId: string): PluginDatabaseManager;
|
||||
static fromConfig(config: Config): SingleConnectionDatabaseManager;
|
||||
}
|
||||
// @public @deprecated
|
||||
export const SingleConnectionDatabaseManager: typeof DatabaseManager;
|
||||
|
||||
// @public
|
||||
export class SingleHostDiscovery implements PluginEndpointDiscovery {
|
||||
|
||||
Vendored
+20
-10
@@ -53,20 +53,30 @@ export interface Config {
|
||||
};
|
||||
};
|
||||
|
||||
/** Database connection configuration, select database type using the `client` field */
|
||||
database:
|
||||
| {
|
||||
client: 'sqlite3';
|
||||
connection: ':memory:' | string | { filename: string };
|
||||
}
|
||||
| {
|
||||
client: 'pg';
|
||||
/** Database connection configuration, select base database type using the `client` field */
|
||||
database: {
|
||||
/** Default database client to use */
|
||||
client: 'sqlite3' | 'pg';
|
||||
/**
|
||||
* Base database connection string or Knex object
|
||||
* @secret
|
||||
*/
|
||||
connection: string | object;
|
||||
/** Database name prefix override */
|
||||
prefix?: string;
|
||||
/** Plugin specific database configuration and client override */
|
||||
plugin?: {
|
||||
[pluginId: string]: {
|
||||
/** Database client override */
|
||||
client?: 'sqlite3' | 'pg';
|
||||
/**
|
||||
* PostgreSQL connection string or knex configuration object.
|
||||
* Database connection string or Knex object override
|
||||
* @secret
|
||||
*/
|
||||
connection: string | object;
|
||||
connection?: string | object;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
/** Cache connection configuration, select cache type using the `store` field */
|
||||
cache?:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/backend-common",
|
||||
"description": "Common functionality library for Backstage backends",
|
||||
"version": "0.8.2",
|
||||
"version": "0.8.3",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"private": false,
|
||||
@@ -31,7 +31,7 @@
|
||||
"dependencies": {
|
||||
"@backstage/cli-common": "^0.1.1",
|
||||
"@backstage/config": "^0.1.5",
|
||||
"@backstage/config-loader": "^0.6.2",
|
||||
"@backstage/config-loader": "^0.6.4",
|
||||
"@backstage/errors": "^0.1.1",
|
||||
"@backstage/integration": "^0.5.6",
|
||||
"@google-cloud/storage": "^5.8.0",
|
||||
@@ -76,7 +76,7 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/cli": "^0.7.1",
|
||||
"@backstage/test-utils": "^0.1.12",
|
||||
"@types/archiver": "^5.1.0",
|
||||
"@types/compression": "^1.7.0",
|
||||
|
||||
@@ -0,0 +1,318 @@
|
||||
/*
|
||||
* 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 { ConfigReader } from '@backstage/config';
|
||||
import { omit } from 'lodash';
|
||||
import { createDatabaseClient, ensureDatabaseExists } from './connection';
|
||||
import { DatabaseManager } from './DatabaseManager';
|
||||
|
||||
jest.mock('./connection', () => ({
|
||||
...jest.requireActual('./connection'),
|
||||
createDatabaseClient: jest.fn(),
|
||||
ensureDatabaseExists: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('DatabaseManager', () => {
|
||||
// This is similar to the ts-jest `mocked` helper.
|
||||
const mocked = (f: Function) => f as jest.Mock;
|
||||
|
||||
afterEach(() => jest.resetAllMocks());
|
||||
|
||||
describe('DatabaseManager.fromConfig', () => {
|
||||
it('accesses the backend.database key', () => {
|
||||
const config = new ConfigReader({
|
||||
backend: {
|
||||
database: {
|
||||
client: 'pg',
|
||||
connection: {
|
||||
host: 'localhost',
|
||||
user: 'foo',
|
||||
password: 'bar',
|
||||
database: 'foodb',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const getConfigSpy = jest.spyOn(config, 'getConfig');
|
||||
DatabaseManager.fromConfig(config);
|
||||
|
||||
expect(getConfigSpy).toHaveBeenCalledWith('backend.database');
|
||||
});
|
||||
});
|
||||
|
||||
describe('DatabaseManager.forPlugin', () => {
|
||||
const config = {
|
||||
backend: {
|
||||
database: {
|
||||
client: 'pg',
|
||||
prefix: 'test_prefix_',
|
||||
connection: {
|
||||
host: 'localhost',
|
||||
user: 'foo',
|
||||
password: 'bar',
|
||||
database: 'foodb',
|
||||
},
|
||||
plugin: {
|
||||
testdbname: {
|
||||
connection: {
|
||||
database: 'database_name_overriden',
|
||||
},
|
||||
},
|
||||
differentclient: {
|
||||
client: 'sqlite3',
|
||||
connection: {
|
||||
filename: 'plugin_with_different_client',
|
||||
},
|
||||
},
|
||||
differentclientconnstring: {
|
||||
client: 'sqlite3',
|
||||
connection: ':memory:',
|
||||
},
|
||||
stringoverride: {
|
||||
connection: 'postgresql://testuser:testpass@acme:5432/userdbname',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const manager = DatabaseManager.fromConfig(new ConfigReader(config));
|
||||
|
||||
it('connects to a plugin database using default config', async () => {
|
||||
const pluginId = 'pluginwithoutconfig';
|
||||
|
||||
await manager.forPlugin(pluginId).getClient();
|
||||
expect(mocked(createDatabaseClient)).toHaveBeenCalledTimes(1);
|
||||
|
||||
const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1);
|
||||
const [baseConfig, overrides] = mockCalls[0];
|
||||
|
||||
// default config should be passed through to underlying connector
|
||||
expect(baseConfig.get()).toMatchObject({
|
||||
client: 'pg',
|
||||
connection: omit(config.backend.database.connection, ['database']),
|
||||
});
|
||||
|
||||
// override using database name generated from pluginId and prefix
|
||||
expect(overrides).toMatchObject({
|
||||
connection: {
|
||||
database: `${config.backend.database.prefix}${pluginId}`,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('provides a plugin db which uses components from top level connection string', async () => {
|
||||
const testManager = DatabaseManager.fromConfig(
|
||||
new ConfigReader({
|
||||
backend: {
|
||||
database: {
|
||||
client: 'pg',
|
||||
connection: 'postgresql://foo:bar@acme:5432/foodb',
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
await testManager.forPlugin('pluginwithoutconfig').getClient();
|
||||
const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1);
|
||||
const [baseConfig, overrides] = mockCalls[0];
|
||||
|
||||
// parsed connection string **without** db name should be passed through
|
||||
expect(baseConfig.get()).toMatchObject({
|
||||
connection: {
|
||||
host: 'acme',
|
||||
user: 'foo',
|
||||
password: 'bar',
|
||||
port: '5432',
|
||||
},
|
||||
});
|
||||
|
||||
// we expect a pg database name override with ${prefix} followed by pluginId
|
||||
expect(overrides).toHaveProperty(
|
||||
'connection.database',
|
||||
expect.stringContaining('pluginwithoutconfig'),
|
||||
);
|
||||
});
|
||||
|
||||
it('uses top level sqlite database filename if plugin config is not present', async () => {
|
||||
const testManager = DatabaseManager.fromConfig(
|
||||
new ConfigReader({
|
||||
backend: {
|
||||
database: {
|
||||
client: 'sqlite3',
|
||||
connection: 'some-file-path',
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
await testManager.forPlugin('pluginwithoutconfig').getClient();
|
||||
const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1);
|
||||
const [_, overrides] = mockCalls[0];
|
||||
|
||||
expect(overrides).toHaveProperty(
|
||||
'connection.filename',
|
||||
expect.stringContaining('some-file-path'),
|
||||
);
|
||||
});
|
||||
|
||||
it('provides an inmemory sqlite database if top level is also inmemory and plugin config is not present', async () => {
|
||||
const testManager = DatabaseManager.fromConfig(
|
||||
new ConfigReader({
|
||||
backend: {
|
||||
database: {
|
||||
client: 'sqlite3',
|
||||
connection: ':memory:',
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
await testManager.forPlugin('pluginwithoutconfig').getClient();
|
||||
const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1);
|
||||
const [_, overrides] = mockCalls[0];
|
||||
|
||||
expect(overrides).toHaveProperty(
|
||||
'connection.filename',
|
||||
expect.stringContaining(':memory:'),
|
||||
);
|
||||
});
|
||||
|
||||
it('connects to a plugin database using a specific database name', async () => {
|
||||
// testdbname.connection.database is set in config
|
||||
await manager.forPlugin('testdbname').getClient();
|
||||
|
||||
const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1);
|
||||
const [_baseConfig, overrides] = mockCalls[0];
|
||||
|
||||
// simple case where only database name is overriden
|
||||
expect(overrides).toMatchObject({
|
||||
connection: {
|
||||
database: 'database_name_overriden',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('ensure plugin specific database is created', async () => {
|
||||
const pluginId = 'testdbname';
|
||||
// testdbname.connection.database is set in config
|
||||
await manager.forPlugin(pluginId).getClient();
|
||||
|
||||
const mockCalls = mocked(ensureDatabaseExists).mock.calls.splice(-1);
|
||||
const [_, dbname] = mockCalls[0];
|
||||
|
||||
expect(dbname).toEqual(
|
||||
config.backend.database.plugin[pluginId].connection.database,
|
||||
);
|
||||
});
|
||||
|
||||
it('provides different plugins with their own databases', async () => {
|
||||
await manager.forPlugin('plugin1').getClient();
|
||||
await manager.forPlugin('plugin2').getClient();
|
||||
|
||||
expect(mocked(createDatabaseClient)).toHaveBeenCalledTimes(2);
|
||||
|
||||
const mockCalls = mocked(createDatabaseClient).mock.calls;
|
||||
const [plugin1CallArgs, plugin2CallArgs] = mockCalls;
|
||||
|
||||
// database name overrides should be different
|
||||
expect(plugin1CallArgs[1].connection.database).not.toEqual(
|
||||
plugin2CallArgs[1].connection.database,
|
||||
);
|
||||
});
|
||||
|
||||
it('uses plugin connection as base if default client is different from plugin client', async () => {
|
||||
const pluginId = 'differentclient';
|
||||
await manager.forPlugin(pluginId).getClient();
|
||||
|
||||
const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1);
|
||||
const [baseConfig, _overrides] = mockCalls[0];
|
||||
|
||||
// plugin connection should be used as base config, client is different
|
||||
expect(baseConfig.get()).toMatchObject({
|
||||
client: 'sqlite3',
|
||||
connection: config.backend.database.plugin[pluginId].connection,
|
||||
});
|
||||
});
|
||||
|
||||
it('provides database client specific base and override when client set under plugin', async () => {
|
||||
const pluginId = 'differentclient';
|
||||
await manager.forPlugin(pluginId).getClient();
|
||||
|
||||
const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1);
|
||||
const [baseConfig, overrides] = mockCalls[0];
|
||||
|
||||
// plugin client should be sqlite3
|
||||
expect(baseConfig.get().client).toEqual('sqlite3');
|
||||
|
||||
// sqlite3 uses 'filename' instead of 'database'
|
||||
expect(overrides).toHaveProperty('connection.filename');
|
||||
});
|
||||
|
||||
it('provides database client specific base from plugin connection string when client set under plugin', async () => {
|
||||
const pluginId = 'differentclientconnstring';
|
||||
await manager.forPlugin(pluginId).getClient();
|
||||
|
||||
const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1);
|
||||
const [baseConfig, overrides] = mockCalls[0];
|
||||
|
||||
expect(baseConfig.get().client).toEqual('sqlite3');
|
||||
|
||||
expect(overrides).toHaveProperty('connection.filename', ':memory:');
|
||||
});
|
||||
|
||||
it('generates a database name override when prefix is not explicitly set', async () => {
|
||||
const testManager = DatabaseManager.fromConfig(
|
||||
new ConfigReader({
|
||||
backend: {
|
||||
database: {
|
||||
client: 'pg',
|
||||
connection: {
|
||||
host: 'localhost',
|
||||
user: 'foo',
|
||||
password: 'bar',
|
||||
database: 'foodb',
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
await testManager.forPlugin('testplugin').getClient();
|
||||
const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1);
|
||||
const [_baseConfig, overrides] = mockCalls[0];
|
||||
|
||||
expect(overrides).toHaveProperty(
|
||||
'connection.database',
|
||||
expect.stringContaining('backstage_plugin_'),
|
||||
);
|
||||
});
|
||||
|
||||
it('uses values from plugin connection string if top level client should be used', async () => {
|
||||
const pluginId = 'stringoverride';
|
||||
await manager.forPlugin(pluginId).getClient();
|
||||
|
||||
const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1);
|
||||
const [baseConfig, overrides] = mockCalls[0];
|
||||
|
||||
// plugin client should be pg
|
||||
expect(baseConfig.get().client).toEqual('pg');
|
||||
|
||||
expect(overrides).toHaveProperty(
|
||||
'connection.database',
|
||||
expect.stringContaining('userdbname'),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,220 @@
|
||||
/*
|
||||
* 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 { Knex } from 'knex';
|
||||
import { omit } from 'lodash';
|
||||
import { Config, ConfigReader, JsonObject } from '@backstage/config';
|
||||
import {
|
||||
createDatabaseClient,
|
||||
ensureDatabaseExists,
|
||||
createNameOverride,
|
||||
normalizeConnection,
|
||||
} from './connection';
|
||||
import { PluginDatabaseManager } from './types';
|
||||
|
||||
/**
|
||||
* Provides a config lookup path for a plugin's config block.
|
||||
*/
|
||||
function pluginPath(pluginId: string): string {
|
||||
return `plugin.${pluginId}`;
|
||||
}
|
||||
|
||||
export class DatabaseManager {
|
||||
/**
|
||||
* Creates a DatabaseManager from `backend.database` config.
|
||||
*
|
||||
* The database manager allows the user to set connection and client settings on a per pluginId
|
||||
* basis by defining a database config block under `plugin.<pluginId>` in addition to top level
|
||||
* defaults. Optionally, a user may set `prefix` which is used to prefix generated database
|
||||
* names if config is not provided.
|
||||
*
|
||||
* @param config The loaded application configuration.
|
||||
*/
|
||||
static fromConfig(config: Config): DatabaseManager {
|
||||
const databaseConfig = config.getConfig('backend.database');
|
||||
|
||||
return new DatabaseManager(
|
||||
databaseConfig,
|
||||
databaseConfig.getOptionalString('prefix'),
|
||||
);
|
||||
}
|
||||
|
||||
private constructor(
|
||||
private readonly config: Config,
|
||||
private readonly prefix: string = 'backstage_plugin_',
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Generates a PluginDatabaseManager for consumption by plugins.
|
||||
*
|
||||
* @param pluginId The plugin that the database manager should be created for. Plugin names
|
||||
* should be unique as they are used to look up database config overrides under
|
||||
* `backend.database.plugin`.
|
||||
*/
|
||||
forPlugin(pluginId: string): PluginDatabaseManager {
|
||||
const _this = this;
|
||||
|
||||
return {
|
||||
getClient(): Promise<Knex> {
|
||||
return _this.getDatabase(pluginId);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the canonical database name for a given plugin.
|
||||
*
|
||||
* This method provides the effective database name which is determined using global
|
||||
* and plugin specific database config. If no explicit database name is configured,
|
||||
* this method will provide a generated name which is the pluginId prefixed with
|
||||
* 'backstage_plugin_'.
|
||||
*
|
||||
* @param pluginId Lookup the database name for given plugin
|
||||
* @returns String representing the plugin's database name
|
||||
*/
|
||||
private getDatabaseName(pluginId: string): string {
|
||||
const connection = this.getConnectionConfig(pluginId);
|
||||
|
||||
if (this.getClientType(pluginId).client === 'sqlite3') {
|
||||
// sqlite database name should fallback to ':memory:' as a special case
|
||||
return (
|
||||
(connection as Knex.Sqlite3ConnectionConfig)?.filename ?? ':memory:'
|
||||
);
|
||||
}
|
||||
// all other supported databases should fallback to an auto-prefixed name
|
||||
return (
|
||||
(connection as Knex.ConnectionConfig)?.database ??
|
||||
`${this.prefix}${pluginId}`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the client type which should be used for a given plugin.
|
||||
*
|
||||
* The client type is determined by plugin specific config if present. Otherwise the base
|
||||
* client is used as the fallback.
|
||||
*
|
||||
* @param pluginId Plugin to get the client type for
|
||||
* @returns Object with client type returned as `client` and boolean representing whether
|
||||
* or not the client was overridden as `overridden`
|
||||
*/
|
||||
private getClientType(
|
||||
pluginId: string,
|
||||
): {
|
||||
client: string;
|
||||
overridden: boolean;
|
||||
} {
|
||||
const pluginClient = this.config.getOptionalString(
|
||||
`${pluginPath(pluginId)}.client`,
|
||||
);
|
||||
|
||||
const baseClient = this.config.getString('client');
|
||||
const client = pluginClient ?? baseClient;
|
||||
return {
|
||||
client,
|
||||
overridden: client !== baseClient,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a Knex connection plugin config by combining base and plugin config.
|
||||
*
|
||||
* This method provides a baseConfig for a plugin database connector. If the client type
|
||||
* has not been overridden, the global connection config will be included with plugin
|
||||
* specific config as the base. Values from the plugin connection take precedence over the
|
||||
* base. Base database name is omitted for all supported databases excluding SQLite.
|
||||
*/
|
||||
private getConnectionConfig(
|
||||
pluginId: string,
|
||||
): Partial<Knex.StaticConnectionConfig> {
|
||||
const { client, overridden } = this.getClientType(pluginId);
|
||||
|
||||
let baseConnection = normalizeConnection(
|
||||
this.config.get('connection'),
|
||||
this.config.getString('client'),
|
||||
);
|
||||
// As databases cannot be shared, the `database` property from the base connection
|
||||
// is omitted. SQLite3's `filename` property is an exception as this is used as a
|
||||
// directory elsewhere so we preserve `filename`.
|
||||
baseConnection = omit(baseConnection, 'database');
|
||||
|
||||
// get and normalize optional plugin specific database connection
|
||||
const connection = normalizeConnection(
|
||||
this.config.getOptional(`${pluginPath(pluginId)}.connection`),
|
||||
client,
|
||||
);
|
||||
|
||||
return {
|
||||
// include base connection if client type has not been overriden
|
||||
...(overridden ? {} : baseConnection),
|
||||
...connection,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a Knex database config for a given plugin.
|
||||
*
|
||||
* This method provides a Knex configuration object along with the plugin's client type.
|
||||
*
|
||||
* @param pluginId The plugin that the database config should correspond with
|
||||
*/
|
||||
private getConfigForPlugin(pluginId: string): Knex.Config {
|
||||
const { client } = this.getClientType(pluginId);
|
||||
|
||||
return {
|
||||
client,
|
||||
connection: this.getConnectionConfig(pluginId),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a partial Knex.Config database name override for a given plugin.
|
||||
*
|
||||
* @param pluginId Target plugin to get database name override
|
||||
* @returns Partial Knex.Config with database name override
|
||||
*/
|
||||
private getDatabaseOverrides(pluginId: string): Knex.Config {
|
||||
return createNameOverride(
|
||||
this.getClientType(pluginId).client,
|
||||
this.getDatabaseName(pluginId),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a scoped Knex client for a plugin as per application config.
|
||||
*
|
||||
* @param pluginId Plugin to get a Knex client for
|
||||
* @returns Promise which resolves to a scoped Knex database client for a plugin
|
||||
*/
|
||||
private async getDatabase(pluginId: string): Promise<Knex> {
|
||||
const pluginConfig = new ConfigReader(
|
||||
this.getConfigForPlugin(pluginId) as JsonObject,
|
||||
);
|
||||
|
||||
const databaseName = this.getDatabaseName(pluginId);
|
||||
try {
|
||||
await ensureDatabaseExists(pluginConfig, databaseName);
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Failed to connect to the database to make sure that '${databaseName}' exists, ${error}`,
|
||||
);
|
||||
}
|
||||
|
||||
return createDatabaseClient(
|
||||
pluginConfig,
|
||||
this.getDatabaseOverrides(pluginId),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -15,10 +15,14 @@
|
||||
*/
|
||||
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { createDatabaseClient } from './connection';
|
||||
import { createDatabaseClient, ensureDatabaseExists } from './connection';
|
||||
import { SingleConnectionDatabaseManager } from './SingleConnection';
|
||||
|
||||
jest.mock('./connection');
|
||||
jest.mock('./connection', () => ({
|
||||
...jest.requireActual('./connection'),
|
||||
createDatabaseClient: jest.fn(),
|
||||
ensureDatabaseExists: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('SingleConnectionDatabaseManager', () => {
|
||||
const defaultConfigOptions = {
|
||||
@@ -43,9 +47,8 @@ describe('SingleConnectionDatabaseManager', () => {
|
||||
|
||||
describe('SingleConnectionDatabaseManager.fromConfig', () => {
|
||||
it('accesses the backend.database key', () => {
|
||||
const getConfig = jest.fn();
|
||||
const config = defaultConfig();
|
||||
config.getConfig = getConfig;
|
||||
const getConfig = jest.spyOn(config, 'getConfig');
|
||||
|
||||
SingleConnectionDatabaseManager.fromConfig(config);
|
||||
|
||||
@@ -64,7 +67,6 @@ describe('SingleConnectionDatabaseManager', () => {
|
||||
|
||||
const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1);
|
||||
const callArgs = mockCalls[0];
|
||||
expect(callArgs[0].get()).toEqual(defaultConfigOptions.backend.database);
|
||||
expect(callArgs[1].connection.database).toEqual(
|
||||
`backstage_plugin_${pluginId}`,
|
||||
);
|
||||
@@ -85,5 +87,13 @@ describe('SingleConnectionDatabaseManager', () => {
|
||||
plugin2CallArgs[1].connection.database,
|
||||
);
|
||||
});
|
||||
|
||||
it('ensure plugin database is created', async () => {
|
||||
await manager.forPlugin('test').getClient();
|
||||
const mockCalls = mocked(ensureDatabaseExists).mock.calls.splice(-1);
|
||||
const [_, database] = mockCalls[0];
|
||||
|
||||
expect(database).toEqual('backstage_plugin_test');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,69 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Knex } from 'knex';
|
||||
import { Config } from '@backstage/config';
|
||||
import { createDatabaseClient, ensureDatabaseExists } from './connection';
|
||||
import { PluginDatabaseManager } from './types';
|
||||
import { DatabaseManager } from './DatabaseManager';
|
||||
|
||||
/**
|
||||
* Implements a Database Manager which will automatically create new databases
|
||||
* for plugins when requested. All requested databases are created with the
|
||||
* credentials provided; if the database already exists no attempt to create
|
||||
* the database will be made.
|
||||
*
|
||||
* @deprecated Use `DatabaseManager` from `@backend-common` instead.
|
||||
*/
|
||||
export class SingleConnectionDatabaseManager {
|
||||
/**
|
||||
* Creates a new SingleConnectionDatabaseManager instance by reading from the `backend`
|
||||
* config section, specifically the `.database` key for discovering the management
|
||||
* database configuration.
|
||||
*
|
||||
* @param config The loaded application configuration.
|
||||
*/
|
||||
static fromConfig(config: Config): SingleConnectionDatabaseManager {
|
||||
return new SingleConnectionDatabaseManager(
|
||||
config.getConfig('backend.database'),
|
||||
);
|
||||
}
|
||||
|
||||
private constructor(private readonly config: Config) {}
|
||||
|
||||
/**
|
||||
* Generates a PluginDatabaseManager for consumption by plugins.
|
||||
*
|
||||
* @param pluginId The plugin that the database manager should be created for. Plugin names should be unique.
|
||||
*/
|
||||
forPlugin(pluginId: string): PluginDatabaseManager {
|
||||
const _this = this;
|
||||
|
||||
return {
|
||||
getClient(): Promise<Knex> {
|
||||
return _this.getDatabase(pluginId);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private async getDatabase(pluginId: string): Promise<Knex> {
|
||||
const config = this.config;
|
||||
const overrides = SingleConnectionDatabaseManager.getDatabaseOverrides(
|
||||
pluginId,
|
||||
);
|
||||
const overrideConfig = overrides.connection as Knex.ConnectionConfig;
|
||||
await this.ensureDatabase(overrideConfig.database);
|
||||
|
||||
return createDatabaseClient(config, overrides);
|
||||
}
|
||||
|
||||
private static getDatabaseOverrides(pluginId: string): Knex.Config {
|
||||
return {
|
||||
connection: {
|
||||
database: `backstage_plugin_${pluginId}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private async ensureDatabase(database: string) {
|
||||
const config = this.config;
|
||||
await ensureDatabaseExists(config, database);
|
||||
}
|
||||
}
|
||||
export const SingleConnectionDatabaseManager = DatabaseManager;
|
||||
|
||||
@@ -15,7 +15,11 @@
|
||||
*/
|
||||
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { createDatabaseClient } from './connection';
|
||||
import {
|
||||
createDatabaseClient,
|
||||
createNameOverride,
|
||||
parseConnectionString,
|
||||
} from './connection';
|
||||
|
||||
describe('database connection', () => {
|
||||
describe('createDatabaseClient', () => {
|
||||
@@ -103,4 +107,49 @@ describe('database connection', () => {
|
||||
).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('createNameOverride', () => {
|
||||
it('returns Knex config for postgres', () => {
|
||||
expect(createNameOverride('pg', 'testpg')).toHaveProperty(
|
||||
'connection.database',
|
||||
'testpg',
|
||||
);
|
||||
});
|
||||
|
||||
it('returns Knex config for sqlite', () => {
|
||||
expect(createNameOverride('sqlite3', 'testsqlite')).toHaveProperty(
|
||||
'connection.filename',
|
||||
'testsqlite',
|
||||
);
|
||||
});
|
||||
|
||||
it('returns Knex config for mysql', () => {
|
||||
expect(createNameOverride('mysql', 'testmysql')).toHaveProperty(
|
||||
'connection.database',
|
||||
'testmysql',
|
||||
);
|
||||
});
|
||||
|
||||
it('throws an error for unknown connection', () => {
|
||||
expect(() => createNameOverride('unknown', 'testname')).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseConnectionString', () => {
|
||||
it('returns parsed Knex.StaticConnectionConfig for postgres', () => {
|
||||
expect(
|
||||
parseConnectionString('postgresql://foo:bar@acme:5432/foodb', 'pg'),
|
||||
).toHaveProperty('database', 'foodb');
|
||||
});
|
||||
|
||||
it('returns parsed Knex.StaticConnectionConfig for mysql2', () => {
|
||||
expect(
|
||||
parseConnectionString('mysql://foo:bar@acme:3306/foodb', 'mysql2'),
|
||||
).toHaveProperty('database', 'foodb');
|
||||
});
|
||||
|
||||
it('throws an error if client hint is not provided', () => {
|
||||
expect(() => parseConnectionString('sqlite://')).toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,14 +14,28 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Config } from '@backstage/config';
|
||||
import { Config, JsonObject } from '@backstage/config';
|
||||
import { InputError } from '@backstage/errors';
|
||||
import knexFactory, { Knex } from 'knex';
|
||||
import { mergeDatabaseConfig } from './config';
|
||||
import { createMysqlDatabaseClient, ensureMysqlDatabaseExists } from './mysql';
|
||||
import { createPgDatabaseClient, ensurePgDatabaseExists } from './postgres';
|
||||
import { createSqliteDatabaseClient } from './sqlite3';
|
||||
import { DatabaseConnector } from './types';
|
||||
|
||||
type DatabaseClient = 'pg' | 'sqlite3' | string;
|
||||
import { mysqlConnector, pgConnector, sqlite3Connector } from './connectors';
|
||||
|
||||
type DatabaseClient = 'pg' | 'sqlite3' | 'mysql' | 'mysql2' | string;
|
||||
|
||||
/**
|
||||
* Mapping of client type to supported database connectors
|
||||
*
|
||||
* Database connectors can be aliased here, for example mysql2 uses
|
||||
* the same connector as mysql.
|
||||
*/
|
||||
const ConnectorMapping: Record<DatabaseClient, DatabaseConnector> = {
|
||||
pg: pgConnector,
|
||||
sqlite3: sqlite3Connector,
|
||||
mysql: mysqlConnector,
|
||||
mysql2: mysqlConnector,
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a knex database connection
|
||||
@@ -35,15 +49,10 @@ export function createDatabaseClient(
|
||||
) {
|
||||
const client: DatabaseClient = dbConfig.getString('client');
|
||||
|
||||
if (client === 'pg') {
|
||||
return createPgDatabaseClient(dbConfig, overrides);
|
||||
} else if (client === 'mysql' || client === 'mysql2') {
|
||||
return createMysqlDatabaseClient(dbConfig, overrides);
|
||||
} else if (client === 'sqlite3') {
|
||||
return createSqliteDatabaseClient(dbConfig, overrides);
|
||||
}
|
||||
|
||||
return knexFactory(mergeDatabaseConfig(dbConfig.get(), overrides));
|
||||
return (
|
||||
ConnectorMapping[client]?.createClient(dbConfig, overrides) ??
|
||||
knexFactory(mergeDatabaseConfig(dbConfig.get(), overrides))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,14 +67,66 @@ export const createDatabase = createDatabaseClient;
|
||||
export async function ensureDatabaseExists(
|
||||
dbConfig: Config,
|
||||
...databases: Array<string>
|
||||
) {
|
||||
): Promise<void> {
|
||||
const client: DatabaseClient = dbConfig.getString('client');
|
||||
|
||||
if (client === 'pg') {
|
||||
return ensurePgDatabaseExists(dbConfig, ...databases);
|
||||
} else if (client === 'mysql' || client === 'mysql2') {
|
||||
return ensureMysqlDatabaseExists(dbConfig, ...databases);
|
||||
return ConnectorMapping[client]?.ensureDatabaseExists?.(
|
||||
dbConfig,
|
||||
...databases,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a Knex.Config object with the provided database name for a given client.
|
||||
*/
|
||||
export function createNameOverride(
|
||||
client: string,
|
||||
name: string,
|
||||
): Partial<Knex.Config> {
|
||||
try {
|
||||
return ConnectorMapping[client].createNameOverride(name);
|
||||
} catch (e) {
|
||||
throw new InputError(
|
||||
`Unable to create database name override for '${client}' connector`,
|
||||
e,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a connection string for a given client and provides a connection config.
|
||||
*/
|
||||
export function parseConnectionString(
|
||||
connectionString: string,
|
||||
client?: string,
|
||||
): Knex.StaticConnectionConfig {
|
||||
if (typeof client === 'undefined' || client === null) {
|
||||
throw new InputError(
|
||||
'Database connection string client type auto-detection is not yet supported.',
|
||||
);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
try {
|
||||
return ConnectorMapping[client].parseConnectionString(connectionString);
|
||||
} catch (e) {
|
||||
throw new InputError(
|
||||
`Unable to parse connection string for '${client}' connector`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a connection config or string into an object which can be passed to Knex.
|
||||
*/
|
||||
export function normalizeConnection(
|
||||
connection: Knex.StaticConnectionConfig | JsonObject | string | undefined,
|
||||
client: string,
|
||||
): Partial<Knex.StaticConnectionConfig> {
|
||||
if (typeof connection === 'undefined' || connection === null) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return typeof connection === 'string' || connection instanceof String
|
||||
? parseConnectionString(connection as string, client)
|
||||
: connection;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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 defaultNameOverride from './defaultNameOverride';
|
||||
|
||||
describe('defaultNameOverride()', () => {
|
||||
it('returns a partial knex static connection config with database name', () => {
|
||||
const testDatabaseName = 'testdatabase';
|
||||
expect(defaultNameOverride(testDatabaseName)).toHaveProperty(
|
||||
'connection.database',
|
||||
testDatabaseName,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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 { Knex } from 'knex';
|
||||
|
||||
/**
|
||||
* Provides a partial knex config with database name override.
|
||||
*
|
||||
* Default override for knex database drivers which accept ConnectionConfig
|
||||
* with `connection.database` as the database name field.
|
||||
*
|
||||
* @param name database name to get config override for
|
||||
*/
|
||||
export default function defaultNameOverride(
|
||||
name: string,
|
||||
): Partial<Knex.Config> {
|
||||
return {
|
||||
connection: {
|
||||
database: name,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
export * from './mysql';
|
||||
export * from './postgres';
|
||||
export * from './sqlite3';
|
||||
+18
-3
@@ -14,11 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import knexFactory, { Knex } from 'knex';
|
||||
import yn from 'yn';
|
||||
|
||||
import { Config } from '@backstage/config';
|
||||
import { InputError } from '@backstage/errors';
|
||||
import knexFactory, { Knex } from 'knex';
|
||||
import { mergeDatabaseConfig } from './config';
|
||||
import yn from 'yn';
|
||||
import { mergeDatabaseConfig } from '../config';
|
||||
import { DatabaseConnector } from '../types';
|
||||
import defaultNameOverride from './defaultNameOverride';
|
||||
|
||||
/**
|
||||
* Creates a knex mysql database connection
|
||||
@@ -159,3 +162,15 @@ export async function ensureMysqlDatabaseExists(
|
||||
await admin.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* MySQL database connector.
|
||||
*
|
||||
* Exposes database connector functionality via an immutable object.
|
||||
*/
|
||||
export const mysqlConnector: DatabaseConnector = Object.freeze({
|
||||
createClient: createMysqlDatabaseClient,
|
||||
ensureDatabaseExists: ensureMysqlDatabaseExists,
|
||||
createNameOverride: defaultNameOverride,
|
||||
parseConnectionString: parseMysqlConnectionString,
|
||||
});
|
||||
+16
-1
@@ -15,8 +15,11 @@
|
||||
*/
|
||||
|
||||
import knexFactory, { Knex } from 'knex';
|
||||
|
||||
import { Config } from '@backstage/config';
|
||||
import { mergeDatabaseConfig } from './config';
|
||||
import { mergeDatabaseConfig } from '../config';
|
||||
import { DatabaseConnector } from '../types';
|
||||
import defaultNameOverride from './defaultNameOverride';
|
||||
|
||||
/**
|
||||
* Creates a knex postgres database connection
|
||||
@@ -131,3 +134,15 @@ export async function ensurePgDatabaseExists(
|
||||
await admin.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PostgreSQL database connector.
|
||||
*
|
||||
* Exposes database connector functionality via an immutable object.
|
||||
*/
|
||||
export const pgConnector: DatabaseConnector = Object.freeze({
|
||||
createClient: createPgDatabaseClient,
|
||||
ensureDatabaseExists: ensurePgDatabaseExists,
|
||||
createNameOverride: defaultNameOverride,
|
||||
parseConnectionString: parsePgConnectionString,
|
||||
});
|
||||
+38
-5
@@ -13,15 +13,17 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import path from 'path';
|
||||
|
||||
import { Config } from '@backstage/config';
|
||||
import { ensureDirSync } from 'fs-extra';
|
||||
import knexFactory, { Knex } from 'knex';
|
||||
import path from 'path';
|
||||
import { mergeDatabaseConfig } from './config';
|
||||
|
||||
import { Config } from '@backstage/config';
|
||||
import { mergeDatabaseConfig } from '../config';
|
||||
import { DatabaseConnector } from '../types';
|
||||
|
||||
/**
|
||||
* Creates a knex sqlite3 database connection
|
||||
* Creates a knex SQLite3 database connection
|
||||
*
|
||||
* @param dbConfig The database config
|
||||
* @param overrides Additional options to merge with the config
|
||||
@@ -54,7 +56,7 @@ export function createSqliteDatabaseClient(
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a knex sqlite3 connection config
|
||||
* Builds a knex SQLite3 connection config
|
||||
*
|
||||
* @param dbConfig The database config
|
||||
* @param overrides Additional options to merge with the config
|
||||
@@ -99,3 +101,34 @@ export function buildSqliteDatabaseConfig(
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a partial knex SQLite3 config to override database name.
|
||||
*/
|
||||
export function createSqliteNameOverride(name: string): Partial<Knex.Config> {
|
||||
return {
|
||||
connection: parseSqliteConnectionString(name),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces a partial knex SQLite3 connection config with database name.
|
||||
*/
|
||||
export function parseSqliteConnectionString(
|
||||
name: string,
|
||||
): Knex.Sqlite3ConnectionConfig {
|
||||
return {
|
||||
filename: name,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* SQLite3 database connector.
|
||||
*
|
||||
* Exposes database connector functionality via an immutable object.
|
||||
*/
|
||||
export const sqlite3Connector: DatabaseConnector = Object.freeze({
|
||||
createClient: createSqliteDatabaseClient,
|
||||
createNameOverride: createSqliteNameOverride,
|
||||
parseConnectionString: parseSqliteConnectionString,
|
||||
});
|
||||
@@ -14,6 +14,17 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export * from './connection';
|
||||
export * from './types';
|
||||
export * from './SingleConnection';
|
||||
export * from './DatabaseManager';
|
||||
|
||||
/*
|
||||
* Undocumented API surface from connection is being reduced for future deprecation.
|
||||
* Avoid exporting additional symbols.
|
||||
*/
|
||||
export {
|
||||
createDatabaseClient,
|
||||
createDatabase,
|
||||
ensureDatabaseExists,
|
||||
} from './connection';
|
||||
|
||||
export type { PluginDatabaseManager } from './types';
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Config } from '@backstage/config';
|
||||
import { Knex } from 'knex';
|
||||
|
||||
/**
|
||||
@@ -28,3 +29,37 @@ export interface PluginDatabaseManager {
|
||||
*/
|
||||
getClient(): Promise<Knex>;
|
||||
}
|
||||
|
||||
/**
|
||||
* DatabaseConnector manages an underlying Knex database driver.
|
||||
*/
|
||||
export interface DatabaseConnector {
|
||||
/**
|
||||
* createClient provides an instance of a knex database connector.
|
||||
*/
|
||||
createClient(dbConfig: Config, overrides?: Partial<Knex.Config>): Knex;
|
||||
/**
|
||||
* createNameOverride provides a partial knex config sufficient to override a
|
||||
* database name.
|
||||
*/
|
||||
createNameOverride(name: string): Partial<Knex.Config>;
|
||||
/**
|
||||
* parseConnectionString produces a knex connection config object representing
|
||||
* a database connection string.
|
||||
*/
|
||||
parseConnectionString(
|
||||
connectionString: string,
|
||||
client?: string,
|
||||
): Knex.StaticConnectionConfig;
|
||||
/**
|
||||
* ensureDatabaseExists performs a side-effect to ensure database names passed in are
|
||||
* present.
|
||||
*
|
||||
* Calling this function on databases which already exist should do nothing.
|
||||
* Missing databases should be created if needed.
|
||||
*/
|
||||
ensureDatabaseExists?(
|
||||
dbConfig: Config,
|
||||
...databases: Array<string>
|
||||
): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,59 @@
|
||||
# @backstage/backend-test-utils
|
||||
|
||||
## 0.1.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 772dbdb51: Deprecates `SingleConnectionDatabaseManager` and provides an API compatible database
|
||||
connection manager, `DatabaseManager`, which allows developers to configure database
|
||||
connections on a per plugin basis.
|
||||
|
||||
The `backend.database` config path allows you to set `prefix` to use an
|
||||
alternate prefix for automatically generated database names, the default is
|
||||
`backstage_plugin_`. Use `backend.database.plugin.<pluginId>` to set plugin
|
||||
specific database connection configuration, e.g.
|
||||
|
||||
```yaml
|
||||
backend:
|
||||
database:
|
||||
client: 'pg',
|
||||
prefix: 'custom_prefix_'
|
||||
connection:
|
||||
host: 'localhost'
|
||||
user: 'foo'
|
||||
password: 'bar'
|
||||
plugin:
|
||||
catalog:
|
||||
connection:
|
||||
database: 'database_name_overriden'
|
||||
scaffolder:
|
||||
client: 'sqlite3'
|
||||
connection: ':memory:'
|
||||
```
|
||||
|
||||
Migrate existing backstage installations by swapping out the database manager in the
|
||||
`packages/backend/src/index.ts` file as shown below:
|
||||
|
||||
```diff
|
||||
import {
|
||||
- SingleConnectionDatabaseManager,
|
||||
+ DatabaseManager,
|
||||
} from '@backstage/backend-common';
|
||||
|
||||
// ...
|
||||
|
||||
function makeCreateEnv(config: Config) {
|
||||
// ...
|
||||
- const databaseManager = SingleConnectionDatabaseManager.fromConfig(config);
|
||||
+ const databaseManager = DatabaseManager.fromConfig(config);
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/backend-common@0.8.3
|
||||
- @backstage/cli@0.7.1
|
||||
|
||||
## 0.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/backend-test-utils",
|
||||
"description": "Test helpers library for Backstage backends",
|
||||
"version": "0.1.2",
|
||||
"version": "0.1.3",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"private": false,
|
||||
@@ -30,8 +30,8 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.8.2",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/backend-common": "^0.8.3",
|
||||
"@backstage/cli": "^0.7.1",
|
||||
"@backstage/config": "^0.1.5",
|
||||
"knex": "^0.95.1",
|
||||
"mysql2": "^2.2.5",
|
||||
@@ -41,7 +41,7 @@
|
||||
"uuid": "^8.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/cli": "^0.7.1",
|
||||
"jest": "^26.0.1"
|
||||
},
|
||||
"files": [
|
||||
|
||||
@@ -71,7 +71,7 @@ describe('TestDatabases', () => {
|
||||
await input.insert({ x: 'y' }).into('a');
|
||||
|
||||
// Look for the mark
|
||||
const database = 'backstage_plugin_0';
|
||||
const database = 'backstage_plugin_db0';
|
||||
const output = knexFactory({
|
||||
client: 'pg',
|
||||
connection: { host, port, user, password, database },
|
||||
@@ -105,7 +105,7 @@ describe('TestDatabases', () => {
|
||||
await input.insert({ x: 'y' }).into('a');
|
||||
|
||||
// Look for the mark
|
||||
const database = 'backstage_plugin_0';
|
||||
const database = 'backstage_plugin_db0';
|
||||
const output = knexFactory({
|
||||
client: 'pg',
|
||||
connection: { host, port, user, password, database },
|
||||
@@ -139,7 +139,7 @@ describe('TestDatabases', () => {
|
||||
await input.insert({ x: 'y' }).into('a');
|
||||
|
||||
// Look for the mark
|
||||
const database = 'backstage_plugin_0';
|
||||
const database = 'backstage_plugin_db0';
|
||||
const output = knexFactory({
|
||||
client: 'mysql2',
|
||||
connection: { host, port, user, password, database },
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { SingleConnectionDatabaseManager } from '@backstage/backend-common';
|
||||
import { DatabaseManager } from '@backstage/backend-common';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { Knex } from 'knex';
|
||||
import { isDockerDisabledForTests } from '../util/isDockerDisabledForTests';
|
||||
@@ -142,7 +142,7 @@ export class TestDatabases {
|
||||
|
||||
// Ensure that a unique logical database is created in the instance
|
||||
const connection = await instance.databaseManager
|
||||
.forPlugin(String(this.lastDatabaseIndex++))
|
||||
.forPlugin(String(`db${this.lastDatabaseIndex++}`))
|
||||
.getClient();
|
||||
|
||||
instance.connections.push(connection);
|
||||
@@ -157,7 +157,7 @@ export class TestDatabases {
|
||||
if (envVarName) {
|
||||
const connectionString = process.env[envVarName];
|
||||
if (connectionString) {
|
||||
const databaseManager = SingleConnectionDatabaseManager.fromConfig(
|
||||
const databaseManager = DatabaseManager.fromConfig(
|
||||
new ConfigReader({
|
||||
backend: {
|
||||
database: {
|
||||
@@ -195,7 +195,7 @@ export class TestDatabases {
|
||||
properties.dockerImageName!,
|
||||
);
|
||||
|
||||
const databaseManager = SingleConnectionDatabaseManager.fromConfig(
|
||||
const databaseManager = DatabaseManager.fromConfig(
|
||||
new ConfigReader({
|
||||
backend: {
|
||||
database: {
|
||||
@@ -220,7 +220,7 @@ export class TestDatabases {
|
||||
properties.dockerImageName!,
|
||||
);
|
||||
|
||||
const databaseManager = SingleConnectionDatabaseManager.fromConfig(
|
||||
const databaseManager = DatabaseManager.fromConfig(
|
||||
new ConfigReader({
|
||||
backend: {
|
||||
database: {
|
||||
@@ -241,7 +241,7 @@ export class TestDatabases {
|
||||
private async initSqlite(
|
||||
_properties: TestDatabaseProperties,
|
||||
): Promise<Instance> {
|
||||
const databaseManager = SingleConnectionDatabaseManager.fromConfig(
|
||||
const databaseManager = DatabaseManager.fromConfig(
|
||||
new ConfigReader({
|
||||
backend: {
|
||||
database: {
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { SingleConnectionDatabaseManager } from '@backstage/backend-common';
|
||||
import { DatabaseManager } from '@backstage/backend-common';
|
||||
import { Knex } from 'knex';
|
||||
|
||||
/**
|
||||
@@ -35,10 +35,9 @@ export type TestDatabaseProperties = {
|
||||
|
||||
export type Instance = {
|
||||
stopContainer?: () => Promise<void>;
|
||||
databaseManager: SingleConnectionDatabaseManager;
|
||||
databaseManager: DatabaseManager;
|
||||
connections: Array<Knex>;
|
||||
};
|
||||
|
||||
export const allDatabases: Record<
|
||||
TestDatabaseId,
|
||||
TestDatabaseProperties
|
||||
|
||||
@@ -29,7 +29,7 @@ import {
|
||||
getRootLogger,
|
||||
loadBackendConfig,
|
||||
notFoundHandler,
|
||||
SingleConnectionDatabaseManager,
|
||||
DatabaseManager,
|
||||
SingleHostDiscovery,
|
||||
UrlReaders,
|
||||
useHotMemoize,
|
||||
@@ -59,7 +59,7 @@ function makeCreateEnv(config: Config) {
|
||||
|
||||
root.info(`Created UrlReader ${reader}`);
|
||||
|
||||
const databaseManager = SingleConnectionDatabaseManager.fromConfig(config);
|
||||
const databaseManager = DatabaseManager.fromConfig(config);
|
||||
const cacheManager = CacheManager.fromConfig(config);
|
||||
|
||||
return (plugin: string): PluginEnvironment => {
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @backstage/catalog-model
|
||||
|
||||
## 0.8.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 1d2ed7844: Removed unused `typescript-json-schema` dependency.
|
||||
|
||||
## 0.8.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/catalog-model",
|
||||
"version": "0.8.2",
|
||||
"version": "0.8.3",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -40,7 +40,7 @@
|
||||
"yup": "^0.29.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/cli": "^0.7.1",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/jest": "^26.0.7",
|
||||
"@types/lodash": "^4.14.151",
|
||||
|
||||
@@ -1,5 +1,33 @@
|
||||
# @backstage/cli
|
||||
|
||||
## 0.7.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 3108ff7bf: Make `yarn dev` in newly created backend plugins respect the `PLUGIN_PORT` environment variable.
|
||||
|
||||
You can achieve the same in your created backend plugins by making sure to properly call the port and CORS methods on your service builder. Typically in a file named `src/service/standaloneServer.ts` inside your backend plugin package, replace the following:
|
||||
|
||||
```ts
|
||||
const service = createServiceBuilder(module)
|
||||
.enableCors({ origin: 'http://localhost:3000' })
|
||||
.addRouter('/my-plugin', router);
|
||||
```
|
||||
|
||||
With something like the following:
|
||||
|
||||
```ts
|
||||
let service = createServiceBuilder(module)
|
||||
.setPort(options.port)
|
||||
.addRouter('/my-plugin', router);
|
||||
if (options.enableCors) {
|
||||
service = service.enableCors({ origin: 'http://localhost:3000' });
|
||||
}
|
||||
```
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/config-loader@0.6.4
|
||||
|
||||
## 0.7.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/cli",
|
||||
"description": "CLI for developing Backstage plugins and apps",
|
||||
"version": "0.7.0",
|
||||
"version": "0.7.1",
|
||||
"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.5",
|
||||
"@backstage/config-loader": "^0.6.3",
|
||||
"@backstage/config-loader": "^0.6.4",
|
||||
"@hot-loader/react-dom": "^16.13.0",
|
||||
"@lerna/package-graph": "^4.0.0",
|
||||
"@lerna/project": "^4.0.0",
|
||||
@@ -54,7 +54,7 @@
|
||||
"@types/webpack-env": "^1.15.2",
|
||||
"@types/webpack-node-externals": "^2.5.0",
|
||||
"@typescript-eslint/eslint-plugin": "^v4.26.0",
|
||||
"@typescript-eslint/parser": "^v4.14.0",
|
||||
"@typescript-eslint/parser": "^v4.27.0",
|
||||
"@yarnpkg/lockfile": "^1.1.0",
|
||||
"babel-plugin-dynamic-import-node": "^2.3.3",
|
||||
"bfj": "^7.0.2",
|
||||
@@ -94,8 +94,8 @@
|
||||
"react-hot-loader": "^4.12.21",
|
||||
"recursive-readdir": "^2.2.2",
|
||||
"replace-in-file": "^6.0.0",
|
||||
"rollup": "2.33.x",
|
||||
"rollup-plugin-dts": "^2.0.1",
|
||||
"rollup": "2.44.x",
|
||||
"rollup-plugin-dts": "^3.0.1",
|
||||
"rollup-plugin-esbuild": "2.6.x",
|
||||
"rollup-plugin-peer-deps-external": "^2.2.2",
|
||||
"rollup-plugin-postcss": "^4.0.0",
|
||||
@@ -118,9 +118,9 @@
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/backend-common": "^0.8.2",
|
||||
"@backstage/backend-common": "^0.8.3",
|
||||
"@backstage/config": "^0.1.5",
|
||||
"@backstage/core": "^0.7.12",
|
||||
"@backstage/core": "^0.7.13",
|
||||
"@backstage/dev-utils": "^0.1.17",
|
||||
"@backstage/test-utils": "^0.1.13",
|
||||
"@backstage/theme": "^0.2.8",
|
||||
|
||||
@@ -34,9 +34,12 @@ export async function startStandaloneServer(
|
||||
logger,
|
||||
});
|
||||
|
||||
const service = createServiceBuilder(module)
|
||||
.enableCors({ origin: 'http://localhost:3000' })
|
||||
let service = createServiceBuilder(module)
|
||||
.setPort(options.port)
|
||||
.addRouter('/{{id}}', router);
|
||||
if (options.enableCors) {
|
||||
service = service.enableCors({ origin: 'http://localhost:3000' });
|
||||
}
|
||||
|
||||
return await service.start().catch(err => {
|
||||
logger.error(err);
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @backstage/codemods
|
||||
|
||||
## 0.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 59752e103: Fix execution of `jscodeshift` on windows.
|
||||
- Updated dependencies
|
||||
- @backstage/core-components@0.1.3
|
||||
|
||||
## 0.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/codemods",
|
||||
"description": "A collection of codemods for Backstage projects",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.2",
|
||||
"private": true,
|
||||
"homepage": "https://backstage.io",
|
||||
"repository": {
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @backstage/config-loader
|
||||
|
||||
## 0.6.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- f00493739: Removed workaround for breaking change in typescript 4.3 and bump `typescript-json-schema` instead. This should again allow the usage of `@items.visibility <value>` to set the visibility of array items.
|
||||
|
||||
## 0.6.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/config-loader",
|
||||
"description": "Config loading functionality used by Backstage backend, and CLI",
|
||||
"version": "0.6.3",
|
||||
"version": "0.6.4",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @backstage/core-components
|
||||
|
||||
## 0.1.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- d2c31b132: Add title prop in SupportButton component
|
||||
- d4644f592: Use the Backstage `Link` component in the `Button`
|
||||
|
||||
## 0.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/core-components",
|
||||
"description": "Core components used by Backstage plugins and apps",
|
||||
"version": "0.1.2",
|
||||
"version": "0.1.3",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
@@ -71,7 +71,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/core-app-api": "^0.1.2",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/cli": "^0.7.1",
|
||||
"@backstage/test-utils": "^0.1.13",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^11.2.5",
|
||||
|
||||
@@ -14,17 +14,19 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { ComponentProps } from 'react';
|
||||
import { Button as MaterialButton } from '@material-ui/core';
|
||||
import { Link as RouterLink } from 'react-router-dom';
|
||||
import {
|
||||
Button as MaterialButton,
|
||||
ButtonProps as MaterialButtonProps,
|
||||
} from '@material-ui/core';
|
||||
import React from 'react';
|
||||
import { Link, LinkProps } from '../Link';
|
||||
|
||||
type Props = ComponentProps<typeof MaterialButton> &
|
||||
ComponentProps<typeof RouterLink>;
|
||||
type Props = MaterialButtonProps & Omit<LinkProps, 'variant' | 'color'>;
|
||||
|
||||
/**
|
||||
* Thin wrapper on top of material-ui's Button component
|
||||
* Makes the Button to utilise react-router
|
||||
*/
|
||||
export const Button = React.forwardRef<any, Props>((props, ref) => (
|
||||
<MaterialButton ref={ref} component={RouterLink} {...props} />
|
||||
<MaterialButton ref={ref} component={Link} {...props} />
|
||||
));
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @backstage/core
|
||||
|
||||
## 0.7.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- d4644f592: Use the Backstage `Link` component in the `Button`
|
||||
|
||||
## 0.7.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/core",
|
||||
"description": "Core API used by Backstage plugins and apps",
|
||||
"version": "0.7.12",
|
||||
"version": "0.7.13",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
@@ -71,7 +71,7 @@
|
||||
"zen-observable": "^0.8.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/cli": "^0.7.1",
|
||||
"@backstage/test-utils": "^0.1.13",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^11.2.5",
|
||||
|
||||
@@ -14,17 +14,19 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { ComponentProps } from 'react';
|
||||
import { Button as MaterialButton } from '@material-ui/core';
|
||||
import { Link as RouterLink } from 'react-router-dom';
|
||||
import {
|
||||
Button as MaterialButton,
|
||||
ButtonProps as MaterialButtonProps,
|
||||
} from '@material-ui/core';
|
||||
import React from 'react';
|
||||
import { Link, LinkProps } from '../Link';
|
||||
|
||||
type Props = ComponentProps<typeof MaterialButton> &
|
||||
ComponentProps<typeof RouterLink>;
|
||||
type Props = MaterialButtonProps & Omit<LinkProps, 'variant' | 'color'>;
|
||||
|
||||
/**
|
||||
* Thin wrapper on top of material-ui's Button component
|
||||
* Makes the Button to utilise react-router
|
||||
*/
|
||||
export const Button = React.forwardRef<any, Props>((props, ref) => (
|
||||
<MaterialButton ref={ref} component={RouterLink} {...props} />
|
||||
<MaterialButton ref={ref} component={Link} {...props} />
|
||||
));
|
||||
|
||||
@@ -1,5 +1,130 @@
|
||||
# @backstage/create-app
|
||||
|
||||
## 0.3.26
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 5db7445b4: Adding .DS_Store pattern to .gitignore in Scaffolded Backstage App. To migrate an existing app that pattern should be added manually.
|
||||
|
||||
```diff
|
||||
+# macOS
|
||||
+.DS_Store
|
||||
```
|
||||
|
||||
- b45e29410: This release enables the new catalog processing engine which is a major milestone for the catalog!
|
||||
|
||||
This update makes processing more scalable across multiple instances, adds support for deletions and ui flagging of entities that are no longer referenced by a location.
|
||||
|
||||
**Changes Required** to `catalog.ts`
|
||||
|
||||
```diff
|
||||
-import { useHotCleanup } from '@backstage/backend-common';
|
||||
import {
|
||||
CatalogBuilder,
|
||||
- createRouter,
|
||||
- runPeriodically
|
||||
+ createRouter
|
||||
} from '@backstage/plugin-catalog-backend';
|
||||
import { Router } from 'express';
|
||||
import { PluginEnvironment } from '../types';
|
||||
|
||||
export default async function createPlugin(env: PluginEnvironment): Promise<Router> {
|
||||
- const builder = new CatalogBuilder(env);
|
||||
+ const builder = await CatalogBuilder.create(env);
|
||||
const {
|
||||
entitiesCatalog,
|
||||
locationsCatalog,
|
||||
- higherOrderOperation,
|
||||
+ locationService,
|
||||
+ processingEngine,
|
||||
locationAnalyzer,
|
||||
} = await builder.build();
|
||||
|
||||
- useHotCleanup(
|
||||
- module,
|
||||
- runPeriodically(() => higherOrderOperation.refreshAllLocations(), 100000),
|
||||
- );
|
||||
+ await processingEngine.start();
|
||||
|
||||
return await createRouter({
|
||||
entitiesCatalog,
|
||||
locationsCatalog,
|
||||
- higherOrderOperation,
|
||||
+ locationService,
|
||||
locationAnalyzer,
|
||||
logger: env.logger,
|
||||
config: env.config,
|
||||
```
|
||||
|
||||
As this is a major internal change we have taken some precaution by still allowing the old catalog to be enabled by keeping your `catalog.ts` in it's current state.
|
||||
If you encounter any issues and have to revert to the previous catalog engine make sure to raise an issue immediately as the old catalog engine is deprecated and will be removed in a future release.
|
||||
|
||||
- 772dbdb51: Deprecates `SingleConnectionDatabaseManager` and provides an API compatible database
|
||||
connection manager, `DatabaseManager`, which allows developers to configure database
|
||||
connections on a per plugin basis.
|
||||
|
||||
The `backend.database` config path allows you to set `prefix` to use an
|
||||
alternate prefix for automatically generated database names, the default is
|
||||
`backstage_plugin_`. Use `backend.database.plugin.<pluginId>` to set plugin
|
||||
specific database connection configuration, e.g.
|
||||
|
||||
```yaml
|
||||
backend:
|
||||
database:
|
||||
client: 'pg',
|
||||
prefix: 'custom_prefix_'
|
||||
connection:
|
||||
host: 'localhost'
|
||||
user: 'foo'
|
||||
password: 'bar'
|
||||
plugin:
|
||||
catalog:
|
||||
connection:
|
||||
database: 'database_name_overriden'
|
||||
scaffolder:
|
||||
client: 'sqlite3'
|
||||
connection: ':memory:'
|
||||
```
|
||||
|
||||
Migrate existing backstage installations by swapping out the database manager in the
|
||||
`packages/backend/src/index.ts` file as shown below:
|
||||
|
||||
```diff
|
||||
import {
|
||||
- SingleConnectionDatabaseManager,
|
||||
+ DatabaseManager,
|
||||
} from '@backstage/backend-common';
|
||||
|
||||
// ...
|
||||
|
||||
function makeCreateEnv(config: Config) {
|
||||
// ...
|
||||
- const databaseManager = SingleConnectionDatabaseManager.fromConfig(config);
|
||||
+ const databaseManager = DatabaseManager.fromConfig(config);
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/plugin-catalog@0.6.3
|
||||
- @backstage/plugin-search-backend-node@0.2.1
|
||||
- @backstage/plugin-catalog-backend@0.10.3
|
||||
- @backstage/backend-common@0.8.3
|
||||
- @backstage/cli@0.7.1
|
||||
- @backstage/plugin-api-docs@0.5.0
|
||||
- @backstage/plugin-scaffolder-backend@0.12.1
|
||||
- @backstage/plugin-techdocs@0.9.6
|
||||
- @backstage/plugin-techdocs-backend@0.8.3
|
||||
- @backstage/plugin-catalog-import@0.5.10
|
||||
- @backstage/plugin-app-backend@0.3.14
|
||||
- @backstage/plugin-proxy-backend@0.2.10
|
||||
- @backstage/plugin-rollbar-backend@0.1.12
|
||||
- @backstage/plugin-search-backend@0.2.1
|
||||
- @backstage/plugin-user-settings@0.2.11
|
||||
- @backstage/catalog-model@0.8.3
|
||||
- @backstage/plugin-auth-backend@0.3.13
|
||||
- @backstage/core@0.7.13
|
||||
|
||||
## 0.3.25
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/create-app",
|
||||
"description": "Create app package for Backstage",
|
||||
"version": "0.3.25",
|
||||
"version": "0.3.26",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
useHotMemoize,
|
||||
notFoundHandler,
|
||||
CacheManager,
|
||||
SingleConnectionDatabaseManager,
|
||||
DatabaseManager,
|
||||
SingleHostDiscovery,
|
||||
UrlReaders,
|
||||
} from '@backstage/backend-common';
|
||||
@@ -35,8 +35,8 @@ function makeCreateEnv(config: Config) {
|
||||
|
||||
root.info(`Created UrlReader ${reader}`);
|
||||
|
||||
const databaseManager = SingleConnectionDatabaseManager.fromConfig(config);
|
||||
const cacheManager = CacheManager.fromConfig(config);
|
||||
const databaseManager = DatabaseManager.fromConfig(config);
|
||||
|
||||
return (plugin: string): PluginEnvironment => {
|
||||
const logger = root.child({ type: 'plugin', plugin });
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
# @backstage/plugin-api-docs
|
||||
|
||||
## 0.5.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 2ebc430c4: Rework `ApiExplorerPage` to utilize `EntityListProvider` to provide a consistent UI with the `CatalogIndexPage` which now exposes support for starring entities, pagination, and customizing columns.
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 14ce64b4f: Add pagination to ApiExplorerTable
|
||||
- Updated dependencies
|
||||
- @backstage/plugin-catalog-react@0.2.3
|
||||
- @backstage/plugin-catalog@0.6.3
|
||||
- @backstage/catalog-model@0.8.3
|
||||
- @backstage/core@0.7.13
|
||||
|
||||
## 0.4.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-api-docs",
|
||||
"version": "0.4.15",
|
||||
"version": "0.5.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -30,9 +30,10 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@asyncapi/react-component": "^0.23.0",
|
||||
"@backstage/catalog-model": "^0.8.2",
|
||||
"@backstage/core": "^0.7.11",
|
||||
"@backstage/plugin-catalog-react": "^0.2.2",
|
||||
"@backstage/catalog-model": "^0.8.3",
|
||||
"@backstage/core": "^0.7.13",
|
||||
"@backstage/plugin-catalog": "^0.6.3",
|
||||
"@backstage/plugin-catalog-react": "^0.2.3",
|
||||
"@backstage/theme": "^0.2.8",
|
||||
"@material-icons/font": "^1.0.2",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
@@ -49,7 +50,7 @@
|
||||
"swagger-ui-react": "^3.37.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/cli": "^0.7.1",
|
||||
"@backstage/dev-utils": "^0.1.17",
|
||||
"@backstage/test-utils": "^0.1.13",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { Entity, RELATION_MEMBER_OF } from '@backstage/catalog-model';
|
||||
import {
|
||||
ApiProvider,
|
||||
ApiRegistry,
|
||||
@@ -55,6 +55,19 @@ describe('ApiCatalogPage', () => {
|
||||
}),
|
||||
getLocationByEntity: () =>
|
||||
Promise.resolve({ id: 'id', type: 'github', target: 'url' }),
|
||||
getEntityByName: async entityName => {
|
||||
return {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'User',
|
||||
metadata: { name: entityName.name },
|
||||
relations: [
|
||||
{
|
||||
type: RELATION_MEMBER_OF,
|
||||
target: { namespace: 'default', kind: 'Group', name: 'tools' },
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const configApi: ConfigApi = new ConfigReader({
|
||||
|
||||
@@ -18,36 +18,46 @@ import {
|
||||
Content,
|
||||
ContentHeader,
|
||||
SupportButton,
|
||||
useApi,
|
||||
TableColumn,
|
||||
useRouteRef,
|
||||
} from '@backstage/core';
|
||||
import { catalogApiRef } from '@backstage/plugin-catalog-react';
|
||||
import { Button } from '@material-ui/core';
|
||||
import {
|
||||
EntityKindPicker,
|
||||
EntityLifecyclePicker,
|
||||
EntityListProvider,
|
||||
EntityOwnerPicker,
|
||||
EntityTagPicker,
|
||||
EntityTypePicker,
|
||||
UserListFilterKind,
|
||||
UserListPicker,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { CatalogTable, CatalogTableRow } from '@backstage/plugin-catalog';
|
||||
import { Button, makeStyles } from '@material-ui/core';
|
||||
import React from 'react';
|
||||
import { Link as RouterLink } from 'react-router-dom';
|
||||
import { useAsync } from 'react-use';
|
||||
import { createComponentRouteRef } from '../../routes';
|
||||
import { ApiExplorerTable } from '../ApiExplorerTable';
|
||||
import { ApiExplorerLayout } from './ApiExplorerLayout';
|
||||
|
||||
export const ApiExplorerPage = () => {
|
||||
const useStyles = makeStyles(theme => ({
|
||||
contentWrapper: {
|
||||
display: 'grid',
|
||||
gridTemplateAreas: "'filters' 'table'",
|
||||
gridTemplateColumns: '250px 1fr',
|
||||
gridColumnGap: theme.spacing(2),
|
||||
},
|
||||
}));
|
||||
|
||||
export type ApiExplorerPageProps = {
|
||||
initiallySelectedFilter?: UserListFilterKind;
|
||||
columns?: TableColumn<CatalogTableRow>[];
|
||||
};
|
||||
|
||||
export const ApiExplorerPage = ({
|
||||
initiallySelectedFilter = 'all',
|
||||
columns,
|
||||
}: ApiExplorerPageProps) => {
|
||||
const styles = useStyles();
|
||||
const createComponentLink = useRouteRef(createComponentRouteRef);
|
||||
const catalogApi = useApi(catalogApiRef);
|
||||
const { loading, error, value: catalogResponse } = useAsync(() => {
|
||||
return catalogApi.getEntities({
|
||||
filter: { kind: 'API' },
|
||||
fields: [
|
||||
'apiVersion',
|
||||
'kind',
|
||||
'metadata',
|
||||
'relations',
|
||||
'spec.lifecycle',
|
||||
'spec.owner',
|
||||
'spec.type',
|
||||
'spec.system',
|
||||
],
|
||||
});
|
||||
}, [catalogApi]);
|
||||
|
||||
return (
|
||||
<ApiExplorerLayout>
|
||||
@@ -65,11 +75,19 @@ export const ApiExplorerPage = () => {
|
||||
)}
|
||||
<SupportButton>All your APIs</SupportButton>
|
||||
</ContentHeader>
|
||||
<ApiExplorerTable
|
||||
entities={catalogResponse?.items ?? []}
|
||||
loading={loading}
|
||||
error={error}
|
||||
/>
|
||||
<div className={styles.contentWrapper}>
|
||||
<EntityListProvider>
|
||||
<div>
|
||||
<EntityKindPicker initialFilter="api" hidden />
|
||||
<UserListPicker initialFilter={initiallySelectedFilter} />
|
||||
<EntityTypePicker />
|
||||
<EntityOwnerPicker />
|
||||
<EntityLifecyclePicker />
|
||||
<EntityTagPicker />
|
||||
</div>
|
||||
<CatalogTable columns={columns} />
|
||||
</EntityListProvider>
|
||||
</div>
|
||||
</Content>
|
||||
</ApiExplorerLayout>
|
||||
);
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
/*
|
||||
* 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 { Entity } from '@backstage/catalog-model';
|
||||
import { ApiProvider, ApiRegistry } from '@backstage/core';
|
||||
import { wrapInTestApp } from '@backstage/test-utils';
|
||||
import { render } from '@testing-library/react';
|
||||
import * as React from 'react';
|
||||
import { apiDocsConfigRef } from '../../config';
|
||||
import { ApiExplorerTable } from './ApiExplorerTable';
|
||||
|
||||
const entities: Entity[] = [
|
||||
{
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'API',
|
||||
metadata: { name: 'api1' },
|
||||
spec: { type: 'openapi' },
|
||||
},
|
||||
{
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'API',
|
||||
metadata: { name: 'api2' },
|
||||
spec: { type: 'openapi' },
|
||||
},
|
||||
{
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'API',
|
||||
metadata: { name: 'api3' },
|
||||
spec: { type: 'grpc' },
|
||||
},
|
||||
];
|
||||
|
||||
const apiRegistry = ApiRegistry.with(apiDocsConfigRef, {
|
||||
getApiDefinitionWidget: () => undefined,
|
||||
});
|
||||
|
||||
describe('ApiCatalogTable component', () => {
|
||||
it('should render error message when error is passed in props', async () => {
|
||||
const rendered = render(
|
||||
wrapInTestApp(
|
||||
<ApiProvider apis={apiRegistry}>
|
||||
<ApiExplorerTable
|
||||
entities={[]}
|
||||
loading={false}
|
||||
error={{ code: 'error' }}
|
||||
/>
|
||||
</ApiProvider>,
|
||||
),
|
||||
);
|
||||
const errorMessage = await rendered.findByText(
|
||||
/Could not fetch catalog entities./,
|
||||
);
|
||||
expect(errorMessage).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should display entity names when loading has finished and no error occurred', async () => {
|
||||
const rendered = render(
|
||||
wrapInTestApp(
|
||||
<ApiProvider apis={apiRegistry}>
|
||||
<ApiExplorerTable entities={entities} loading={false} />
|
||||
</ApiProvider>,
|
||||
),
|
||||
);
|
||||
expect(rendered.getByText(/api1/)).toBeInTheDocument();
|
||||
expect(rendered.getByText(/api2/)).toBeInTheDocument();
|
||||
expect(rendered.getByText(/api3/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -1,214 +0,0 @@
|
||||
/*
|
||||
* 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 {
|
||||
ApiEntityV1alpha1,
|
||||
Entity,
|
||||
EntityName,
|
||||
RELATION_OWNED_BY,
|
||||
RELATION_PART_OF,
|
||||
} from '@backstage/catalog-model';
|
||||
import {
|
||||
CodeSnippet,
|
||||
OverflowTooltip,
|
||||
Table,
|
||||
TableColumn,
|
||||
TableFilter,
|
||||
TableState,
|
||||
useQueryParamState,
|
||||
WarningPanel,
|
||||
} from '@backstage/core';
|
||||
import {
|
||||
EntityRefLink,
|
||||
EntityRefLinks,
|
||||
formatEntityRefTitle,
|
||||
getEntityRelations,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { Chip } from '@material-ui/core';
|
||||
import React from 'react';
|
||||
import { ApiTypeTitle } from '../ApiDefinitionCard';
|
||||
|
||||
type EntityRow = {
|
||||
entity: ApiEntityV1alpha1;
|
||||
resolved: {
|
||||
name: string;
|
||||
partOfSystemRelationTitle?: string;
|
||||
partOfSystemRelations: EntityName[];
|
||||
ownedByRelationsTitle?: string;
|
||||
ownedByRelations: EntityName[];
|
||||
};
|
||||
};
|
||||
|
||||
const columns: TableColumn<EntityRow>[] = [
|
||||
{
|
||||
title: 'Name',
|
||||
field: 'resolved.name',
|
||||
highlight: true,
|
||||
render: ({ entity }) => (
|
||||
<EntityRefLink entityRef={entity} defaultKind="API" />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'System',
|
||||
field: 'resolved.partOfSystemRelationTitle',
|
||||
render: ({ resolved }) => (
|
||||
<EntityRefLinks
|
||||
entityRefs={resolved.partOfSystemRelations}
|
||||
defaultKind="system"
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Owner',
|
||||
field: 'resolved.ownedByRelationsTitle',
|
||||
render: ({ resolved }) => (
|
||||
<EntityRefLinks
|
||||
entityRefs={resolved.ownedByRelations}
|
||||
defaultKind="group"
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Lifecycle',
|
||||
field: 'entity.spec.lifecycle',
|
||||
},
|
||||
{
|
||||
title: 'Type',
|
||||
field: 'entity.spec.type',
|
||||
render: ({ entity }) => <ApiTypeTitle apiEntity={entity} />,
|
||||
},
|
||||
{
|
||||
title: 'Description',
|
||||
field: 'entity.metadata.description',
|
||||
render: ({ entity }) => (
|
||||
<OverflowTooltip
|
||||
text={entity.metadata.description}
|
||||
placement="bottom-start"
|
||||
/>
|
||||
),
|
||||
width: 'auto',
|
||||
},
|
||||
{
|
||||
title: 'Tags',
|
||||
field: 'entity.metadata.tags',
|
||||
cellStyle: {
|
||||
padding: '0px 16px 0px 20px',
|
||||
},
|
||||
render: ({ entity }) => (
|
||||
<>
|
||||
{entity.metadata.tags &&
|
||||
entity.metadata.tags.map(t => (
|
||||
<Chip
|
||||
key={t}
|
||||
label={t}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
style={{ marginBottom: '0px' }}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
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[];
|
||||
loading: boolean;
|
||||
error?: any;
|
||||
};
|
||||
|
||||
export const ApiExplorerTable = ({
|
||||
entities,
|
||||
loading,
|
||||
error,
|
||||
}: ExplorerTableProps) => {
|
||||
const [queryParamState, setQueryParamState] = useQueryParamState<TableState>(
|
||||
'apiTable',
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<WarningPanel severity="error" title="Could not fetch catalog entities.">
|
||||
<CodeSnippet language="text" text={error.toString()} />
|
||||
</WarningPanel>
|
||||
);
|
||||
}
|
||||
|
||||
const rows = entities.map(entity => {
|
||||
const partOfSystemRelations = getEntityRelations(entity, RELATION_PART_OF, {
|
||||
kind: 'system',
|
||||
});
|
||||
const ownedByRelations = getEntityRelations(entity, RELATION_OWNED_BY);
|
||||
|
||||
return {
|
||||
entity: entity as ApiEntityV1alpha1,
|
||||
resolved: {
|
||||
name: formatEntityRefTitle(entity, {
|
||||
defaultKind: 'API',
|
||||
}),
|
||||
ownedByRelationsTitle: ownedByRelations
|
||||
.map(r => formatEntityRefTitle(r, { defaultKind: 'group' }))
|
||||
.join(', '),
|
||||
ownedByRelations,
|
||||
partOfSystemRelationTitle: partOfSystemRelations
|
||||
.map(r =>
|
||||
formatEntityRefTitle(r, {
|
||||
defaultKind: 'system',
|
||||
}),
|
||||
)
|
||||
.join(', '),
|
||||
partOfSystemRelations,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<Table<EntityRow>
|
||||
isLoading={loading}
|
||||
columns={columns}
|
||||
options={{
|
||||
paging: false,
|
||||
actionsColumnIndex: -1,
|
||||
loadingType: 'linear',
|
||||
padding: 'dense',
|
||||
showEmptyDataSourceMessage: !loading,
|
||||
}}
|
||||
data={rows}
|
||||
filters={filters}
|
||||
initialState={queryParamState}
|
||||
onStateChange={setQueryParamState}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1,5 +1,14 @@
|
||||
# @backstage/plugin-app-backend
|
||||
|
||||
## 0.3.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 3108ff7bf: Make `yarn dev` respect the `PLUGIN_PORT` environment variable.
|
||||
- Updated dependencies
|
||||
- @backstage/backend-common@0.8.3
|
||||
- @backstage/config-loader@0.6.4
|
||||
|
||||
## 0.3.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-app-backend",
|
||||
"version": "0.3.13",
|
||||
"version": "0.3.14",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -29,8 +29,8 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.8.2",
|
||||
"@backstage/config-loader": "^0.6.1",
|
||||
"@backstage/backend-common": "^0.8.3",
|
||||
"@backstage/config-loader": "^0.6.4",
|
||||
"@backstage/config": "^0.1.5",
|
||||
"@types/express": "^4.17.6",
|
||||
"express": "^4.17.1",
|
||||
@@ -40,7 +40,7 @@
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/cli": "^0.7.1",
|
||||
"@types/supertest": "^2.0.8",
|
||||
"msw": "^0.20.5",
|
||||
"supertest": "^6.1.3"
|
||||
|
||||
@@ -38,7 +38,9 @@ export async function startStandaloneServer(
|
||||
appPackageName: 'example-app',
|
||||
});
|
||||
|
||||
const service = createServiceBuilder(module).addRouter('', router);
|
||||
const service = createServiceBuilder(module)
|
||||
.setPort(options.port)
|
||||
.addRouter('', router);
|
||||
|
||||
return await service.start().catch(err => {
|
||||
logger.error(err);
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
# @backstage/plugin-auth-backend
|
||||
|
||||
## 0.3.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 1aa31f0af: Add support for refreshing GitLab auth sessions.
|
||||
- Updated dependencies
|
||||
- @backstage/backend-common@0.8.3
|
||||
- @backstage/catalog-model@0.8.3
|
||||
|
||||
## 0.3.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-auth-backend",
|
||||
"version": "0.3.12",
|
||||
"version": "0.3.13",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -29,9 +29,9 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.8.2",
|
||||
"@backstage/backend-common": "^0.8.3",
|
||||
"@backstage/catalog-client": "^0.3.13",
|
||||
"@backstage/catalog-model": "^0.8.2",
|
||||
"@backstage/catalog-model": "^0.8.3",
|
||||
"@backstage/config": "^0.1.5",
|
||||
"@backstage/errors": "^0.1.1",
|
||||
"@backstage/test-utils": "^0.1.12",
|
||||
@@ -68,7 +68,7 @@
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/cli": "^0.7.1",
|
||||
"@types/body-parser": "^1.19.0",
|
||||
"@types/cookie-parser": "^1.4.2",
|
||||
"@types/express-session": "^1.17.2",
|
||||
|
||||
@@ -27,24 +27,29 @@ describe('GitlabAuthProvider', () => {
|
||||
it('should transform to type OAuthResponse', async () => {
|
||||
const tests = [
|
||||
{
|
||||
result: {
|
||||
accessToken: '19xasczxcm9n7gacn9jdgm19me',
|
||||
fullProfile: {
|
||||
id: 'uid-123',
|
||||
username: 'jimmymarkum',
|
||||
provider: 'gitlab',
|
||||
displayName: 'Jimmy Markum',
|
||||
emails: [
|
||||
{
|
||||
value: 'jimmymarkum@gmail.com',
|
||||
},
|
||||
],
|
||||
avatarUrl:
|
||||
'https://a1cf74336522e87f135f-2f21ace9a6cf0052456644b80fa06d4f.ssl.cf2.rackcdn.com/images/characters_opt/p-mystic-river-sean-penn.jpg',
|
||||
input: {
|
||||
result: {
|
||||
accessToken: '19xasczxcm9n7gacn9jdgm19me',
|
||||
fullProfile: {
|
||||
id: 'uid-123',
|
||||
username: 'jimmymarkum',
|
||||
provider: 'gitlab',
|
||||
displayName: 'Jimmy Markum',
|
||||
emails: [
|
||||
{
|
||||
value: 'jimmymarkum@gmail.com',
|
||||
},
|
||||
],
|
||||
avatarUrl:
|
||||
'https://a1cf74336522e87f135f-2f21ace9a6cf0052456644b80fa06d4f.ssl.cf2.rackcdn.com/images/characters_opt/p-mystic-river-sean-penn.jpg',
|
||||
},
|
||||
params: {
|
||||
scope: 'user_read write_repository',
|
||||
expires_in: 100,
|
||||
},
|
||||
},
|
||||
params: {
|
||||
scope: 'user_read write_repository',
|
||||
expires_in: 100,
|
||||
privateInfo: {
|
||||
refreshToken: 'gacn9jdgm19me19xasczxcm9n7',
|
||||
},
|
||||
},
|
||||
expect: {
|
||||
@@ -65,23 +70,28 @@ describe('GitlabAuthProvider', () => {
|
||||
},
|
||||
},
|
||||
{
|
||||
result: {
|
||||
accessToken:
|
||||
'ajakljsdoiahoawxbrouawucmbawe.awkxjemaneasdxwe.sodijxqeqwexeqwxe',
|
||||
fullProfile: {
|
||||
id: 'ipd12039',
|
||||
username: 'daveboyle',
|
||||
provider: 'gitlab',
|
||||
displayName: 'Dave Boyle',
|
||||
emails: [
|
||||
{
|
||||
value: 'daveboyle@gitlab.org',
|
||||
},
|
||||
],
|
||||
input: {
|
||||
result: {
|
||||
accessToken:
|
||||
'ajakljsdoiahoawxbrouawucmbawe.awkxjemaneasdxwe.sodijxqeqwexeqwxe',
|
||||
fullProfile: {
|
||||
id: 'ipd12039',
|
||||
username: 'daveboyle',
|
||||
provider: 'gitlab',
|
||||
displayName: 'Dave Boyle',
|
||||
emails: [
|
||||
{
|
||||
value: 'daveboyle@gitlab.org',
|
||||
},
|
||||
],
|
||||
},
|
||||
params: {
|
||||
scope: 'read_repository',
|
||||
expires_in: 200,
|
||||
},
|
||||
},
|
||||
params: {
|
||||
scope: 'read_repository',
|
||||
expires_in: 200,
|
||||
privateInfo: {
|
||||
refreshToken: 'gacn96f3y6y5jdgm19mec348nqrty719xasczf356yxcm9n7',
|
||||
},
|
||||
},
|
||||
expect: {
|
||||
@@ -109,7 +119,7 @@ describe('GitlabAuthProvider', () => {
|
||||
baseUrl: 'mock',
|
||||
});
|
||||
for (const test of tests) {
|
||||
mockFrameHandler.mockResolvedValueOnce({ result: test.result });
|
||||
mockFrameHandler.mockResolvedValueOnce(test.input);
|
||||
const { response } = await provider.handler({} as any);
|
||||
expect(response).toEqual(test.expect);
|
||||
}
|
||||
|
||||
@@ -17,8 +17,10 @@
|
||||
import express from 'express';
|
||||
import { Strategy as GitlabStrategy } from 'passport-gitlab2';
|
||||
import {
|
||||
executeFrameHandlerStrategy,
|
||||
executeRedirectStrategy,
|
||||
executeFrameHandlerStrategy,
|
||||
executeRefreshTokenStrategy,
|
||||
executeFetchUserProfileStrategy,
|
||||
makeProfileInfo,
|
||||
PassportDoneCallback,
|
||||
} from '../../lib/passport';
|
||||
@@ -30,14 +32,40 @@ import {
|
||||
OAuthResponse,
|
||||
OAuthEnvironmentHandler,
|
||||
OAuthStartRequest,
|
||||
OAuthRefreshRequest,
|
||||
encodeState,
|
||||
OAuthResult,
|
||||
} from '../../lib/oauth';
|
||||
|
||||
type FullProfile = OAuthResult['fullProfile'] & {
|
||||
avatarUrl?: string;
|
||||
};
|
||||
|
||||
type PrivateInfo = {
|
||||
refreshToken: string;
|
||||
};
|
||||
|
||||
export type GitlabAuthProviderOptions = OAuthProviderOptions & {
|
||||
baseUrl: string;
|
||||
};
|
||||
|
||||
function transformProfile(fullProfile: FullProfile) {
|
||||
const profile = makeProfileInfo({
|
||||
...fullProfile,
|
||||
photos: [
|
||||
...(fullProfile.photos ?? []),
|
||||
...(fullProfile.avatarUrl ? [{ value: fullProfile.avatarUrl }] : []),
|
||||
],
|
||||
});
|
||||
|
||||
let id = fullProfile.id;
|
||||
if (profile.email) {
|
||||
id = profile.email.split('@')[0];
|
||||
}
|
||||
|
||||
return { id, profile };
|
||||
}
|
||||
|
||||
export class GitlabAuthProvider implements OAuthHandlers {
|
||||
private readonly _strategy: GitlabStrategy;
|
||||
|
||||
@@ -51,12 +79,18 @@ export class GitlabAuthProvider implements OAuthHandlers {
|
||||
},
|
||||
(
|
||||
accessToken: any,
|
||||
_refreshToken: any,
|
||||
refreshToken: any,
|
||||
params: any,
|
||||
fullProfile: any,
|
||||
done: PassportDoneCallback<OAuthResult>,
|
||||
done: PassportDoneCallback<OAuthResult, PrivateInfo>,
|
||||
) => {
|
||||
done(undefined, { fullProfile, params, accessToken });
|
||||
done(
|
||||
undefined,
|
||||
{ fullProfile, params, accessToken },
|
||||
{
|
||||
refreshToken,
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -68,33 +102,16 @@ export class GitlabAuthProvider implements OAuthHandlers {
|
||||
});
|
||||
}
|
||||
|
||||
async handler(req: express.Request): Promise<{ response: OAuthResponse }> {
|
||||
const { result } = await executeFrameHandlerStrategy<OAuthResult>(
|
||||
req,
|
||||
this._strategy,
|
||||
);
|
||||
async handler(
|
||||
req: express.Request,
|
||||
): Promise<{ response: OAuthResponse; refreshToken: string }> {
|
||||
const { result, privateInfo } = await executeFrameHandlerStrategy<
|
||||
OAuthResult,
|
||||
PrivateInfo
|
||||
>(req, this._strategy);
|
||||
const { accessToken, params } = result;
|
||||
const fullProfile = result.fullProfile as OAuthResult['fullProfile'] & {
|
||||
avatarUrl?: string;
|
||||
};
|
||||
|
||||
const profile = makeProfileInfo(
|
||||
{
|
||||
...fullProfile,
|
||||
photos: [
|
||||
...(fullProfile.photos ?? []),
|
||||
...(fullProfile.avatarUrl ? [{ value: fullProfile.avatarUrl }] : []),
|
||||
],
|
||||
},
|
||||
params.id_token,
|
||||
);
|
||||
|
||||
// gitlab provides an id numeric value (123)
|
||||
// as a fallback
|
||||
let id = fullProfile.id;
|
||||
if (profile.email) {
|
||||
id = profile.email.split('@')[0];
|
||||
}
|
||||
const { id, profile } = transformProfile(result.fullProfile);
|
||||
|
||||
return {
|
||||
response: {
|
||||
@@ -109,6 +126,39 @@ export class GitlabAuthProvider implements OAuthHandlers {
|
||||
id,
|
||||
},
|
||||
},
|
||||
refreshToken: privateInfo.refreshToken,
|
||||
};
|
||||
}
|
||||
|
||||
async refresh(req: OAuthRefreshRequest): Promise<OAuthResponse> {
|
||||
const {
|
||||
accessToken,
|
||||
refreshToken: newRefreshToken,
|
||||
params,
|
||||
} = await executeRefreshTokenStrategy(
|
||||
this._strategy,
|
||||
req.refreshToken,
|
||||
req.scope,
|
||||
);
|
||||
|
||||
const fullProfile = await executeFetchUserProfileStrategy(
|
||||
this._strategy,
|
||||
accessToken,
|
||||
);
|
||||
const { id, profile } = transformProfile(fullProfile);
|
||||
|
||||
return {
|
||||
profile,
|
||||
providerInfo: {
|
||||
accessToken,
|
||||
refreshToken: newRefreshToken, // GitLab expires the old refresh token when used
|
||||
idToken: params.id_token,
|
||||
expiresInSeconds: params.expires_in,
|
||||
scope: params.scope,
|
||||
},
|
||||
backstageIdentity: {
|
||||
id,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -134,7 +184,7 @@ export const createGitlabProvider = (
|
||||
});
|
||||
|
||||
return OAuthAdapter.fromConfig(globalConfig, provider, {
|
||||
disableRefresh: true,
|
||||
disableRefresh: false,
|
||||
providerId,
|
||||
tokenIssuer,
|
||||
});
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
# @backstage/plugin-badges-backend
|
||||
|
||||
## 0.1.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 3108ff7bf: Make `yarn dev` respect the `PLUGIN_PORT` environment variable.
|
||||
- Updated dependencies
|
||||
- @backstage/backend-common@0.8.3
|
||||
- @backstage/catalog-model@0.8.3
|
||||
|
||||
## 0.1.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-badges-backend",
|
||||
"version": "0.1.6",
|
||||
"version": "0.1.7",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -30,9 +30,9 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.8.2",
|
||||
"@backstage/backend-common": "^0.8.3",
|
||||
"@backstage/catalog-client": "^0.3.13",
|
||||
"@backstage/catalog-model": "^0.8.2",
|
||||
"@backstage/catalog-model": "^0.8.3",
|
||||
"@backstage/config": "^0.1.5",
|
||||
"@backstage/errors": "^0.1.1",
|
||||
"@types/express": "^4.17.6",
|
||||
@@ -45,7 +45,7 @@
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/cli": "^0.7.1",
|
||||
"@types/supertest": "^2.0.8",
|
||||
"supertest": "^6.1.3"
|
||||
},
|
||||
|
||||
@@ -40,9 +40,12 @@ export async function startStandaloneServer(
|
||||
|
||||
const router = await createRouter({ config, discovery });
|
||||
|
||||
const service = createServiceBuilder(module)
|
||||
.enableCors({ origin: 'http://localhost:3000' })
|
||||
let service = createServiceBuilder(module)
|
||||
.setPort(options.port)
|
||||
.addRouter('/badges', router);
|
||||
if (options.enableCors) {
|
||||
service = service.enableCors({ origin: 'http://localhost:3000' });
|
||||
}
|
||||
|
||||
return await service.start().catch(err => {
|
||||
logger.error(err);
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/cli": "^0.7.1",
|
||||
"@backstage/dev-utils": "^0.1.17",
|
||||
"@backstage/test-utils": "^0.1.13",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
"recharts": "^1.8.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/cli": "^0.7.1",
|
||||
"@backstage/dev-utils": "^0.1.17",
|
||||
"@backstage/test-utils": "^0.1.13",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -1,5 +1,70 @@
|
||||
# @backstage/plugin-catalog-backend
|
||||
|
||||
## 0.10.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- b45e29410: This release enables the new catalog processing engine which is a major milestone for the catalog!
|
||||
|
||||
This update makes processing more scalable across multiple instances, adds support for deletions and ui flagging of entities that are no longer referenced by a location.
|
||||
|
||||
**Changes Required** to `catalog.ts`
|
||||
|
||||
```diff
|
||||
-import { useHotCleanup } from '@backstage/backend-common';
|
||||
import {
|
||||
CatalogBuilder,
|
||||
- createRouter,
|
||||
- runPeriodically
|
||||
+ createRouter
|
||||
} from '@backstage/plugin-catalog-backend';
|
||||
import { Router } from 'express';
|
||||
import { PluginEnvironment } from '../types';
|
||||
|
||||
export default async function createPlugin(env: PluginEnvironment): Promise<Router> {
|
||||
- const builder = new CatalogBuilder(env);
|
||||
+ const builder = await CatalogBuilder.create(env);
|
||||
const {
|
||||
entitiesCatalog,
|
||||
locationsCatalog,
|
||||
- higherOrderOperation,
|
||||
+ locationService,
|
||||
+ processingEngine,
|
||||
locationAnalyzer,
|
||||
} = await builder.build();
|
||||
|
||||
- useHotCleanup(
|
||||
- module,
|
||||
- runPeriodically(() => higherOrderOperation.refreshAllLocations(), 100000),
|
||||
- );
|
||||
+ await processingEngine.start();
|
||||
|
||||
return await createRouter({
|
||||
entitiesCatalog,
|
||||
locationsCatalog,
|
||||
- higherOrderOperation,
|
||||
+ locationService,
|
||||
locationAnalyzer,
|
||||
logger: env.logger,
|
||||
config: env.config,
|
||||
```
|
||||
|
||||
As this is a major internal change we have taken some precaution by still allowing the old catalog to be enabled by keeping your `catalog.ts` in it's current state.
|
||||
If you encounter any issues and have to revert to the previous catalog engine make sure to raise an issue immediately as the old catalog engine is deprecated and will be removed in a future release.
|
||||
|
||||
- 72fbf4372: Switches the default catalog processing engine to use a batched streaming task execution strategy for higher parallelism.
|
||||
- 18ab535c8: Rely on `SELECT ... FOR UPDATE SKIP LOCKED` where available in order to speed up processing item acquisition and reduce work duplication.
|
||||
- db17fd734: Make refresh interval configurable for the `NextCatalogBuilder` using `.setRefreshIntervalSeconds()`.
|
||||
|
||||
Change `DefaultProcessingDatabase` constructor to accept an options object instead of individual arguments.
|
||||
|
||||
- cb09e445e: Implement `NextCatalogBuilder.addEntityProvider`
|
||||
- 3108ff7bf: Make `yarn dev` respect the `PLUGIN_PORT` environment variable.
|
||||
- Updated dependencies
|
||||
- @backstage/plugin-search-backend-node@0.2.1
|
||||
- @backstage/backend-common@0.8.3
|
||||
- @backstage/catalog-model@0.8.3
|
||||
|
||||
## 0.10.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-catalog-backend",
|
||||
"version": "0.10.2",
|
||||
"version": "0.10.3",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -30,13 +30,13 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@azure/msal-node": "^1.0.0-beta.3",
|
||||
"@backstage/backend-common": "^0.8.2",
|
||||
"@backstage/backend-common": "^0.8.3",
|
||||
"@backstage/catalog-client": "^0.3.13",
|
||||
"@backstage/catalog-model": "^0.8.2",
|
||||
"@backstage/catalog-model": "^0.8.3",
|
||||
"@backstage/config": "^0.1.5",
|
||||
"@backstage/errors": "^0.1.1",
|
||||
"@backstage/integration": "^0.5.6",
|
||||
"@backstage/plugin-search-backend-node": "^0.2.0",
|
||||
"@backstage/plugin-search-backend-node": "^0.2.1",
|
||||
"@backstage/search-common": "^0.1.2",
|
||||
"@microsoft/microsoft-graph-types": "^1.25.0",
|
||||
"@octokit/graphql": "^4.5.8",
|
||||
@@ -65,8 +65,8 @@
|
||||
"yup": "^0.29.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/backend-test-utils": "^0.1.2",
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/backend-test-utils": "^0.1.3",
|
||||
"@backstage/cli": "^0.7.1",
|
||||
"@backstage/test-utils": "^0.1.13",
|
||||
"@types/core-js": "^2.5.4",
|
||||
"@types/git-url-parse": "^9.0.0",
|
||||
|
||||
@@ -63,7 +63,11 @@ import {
|
||||
} from '../ingestion/processors/PlaceholderProcessor';
|
||||
import { defaultEntityDataParser } from '../ingestion/processors/util/parse';
|
||||
import { LocationAnalyzer } from '../ingestion/types';
|
||||
import { CatalogProcessingEngine, LocationService } from '../next/types';
|
||||
import {
|
||||
CatalogProcessingEngine,
|
||||
EntityProvider,
|
||||
LocationService,
|
||||
} from '../next/types';
|
||||
import { ConfigLocationEntityProvider } from './ConfigLocationEntityProvider';
|
||||
import { DefaultProcessingDatabase } from './database/DefaultProcessingDatabase';
|
||||
import { DefaultCatalogProcessingEngine } from './DefaultCatalogProcessingEngine';
|
||||
@@ -105,6 +109,7 @@ export class NextCatalogBuilder {
|
||||
private entityPoliciesReplace: boolean;
|
||||
private placeholderResolvers: Record<string, PlaceholderResolver>;
|
||||
private fieldFormatValidators: Partial<Validators>;
|
||||
private entityProviders: EntityProvider[];
|
||||
private processors: CatalogProcessor[];
|
||||
private processorsReplace: boolean;
|
||||
private parser: CatalogProcessorParser | undefined;
|
||||
@@ -116,6 +121,7 @@ export class NextCatalogBuilder {
|
||||
this.entityPoliciesReplace = false;
|
||||
this.placeholderResolvers = {};
|
||||
this.fieldFormatValidators = {};
|
||||
this.entityProviders = [];
|
||||
this.processors = [];
|
||||
this.processorsReplace = false;
|
||||
this.parser = undefined;
|
||||
@@ -198,6 +204,20 @@ export class NextCatalogBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds or replaces entity providers. These are responsible for bootstrapping
|
||||
* the list of entities out of original data sources. For example, there is
|
||||
* one entity source for the config locations, and one for the database
|
||||
* stored locations. If you ingest entities out of a third party system, you
|
||||
* may want to implement that in terms of an entity provider as well.
|
||||
*
|
||||
* @param providers One or more entity providers
|
||||
*/
|
||||
addEntityProvider(...providers: EntityProvider[]): NextCatalogBuilder {
|
||||
this.entityProviders.push(...providers);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds entity processors. These are responsible for reading, parsing, and
|
||||
* processing entities before they are persisted in the catalog.
|
||||
@@ -277,13 +297,18 @@ export class NextCatalogBuilder {
|
||||
policy,
|
||||
});
|
||||
const entitiesCatalog = new NextEntitiesCatalog(dbClient);
|
||||
const stitcher = new Stitcher(dbClient, logger);
|
||||
|
||||
const locationStore = new DefaultLocationStore(dbClient);
|
||||
const stitcher = new Stitcher(dbClient, logger);
|
||||
const configLocationProvider = new ConfigLocationEntityProvider(config);
|
||||
const entityProviders = lodash.uniqBy(
|
||||
[...this.entityProviders, locationStore, configLocationProvider],
|
||||
provider => provider.getProviderName(),
|
||||
);
|
||||
|
||||
const processingEngine = new DefaultCatalogProcessingEngine(
|
||||
logger,
|
||||
[locationStore, configLocationProvider],
|
||||
entityProviders,
|
||||
processingDatabase,
|
||||
orchestrator,
|
||||
stitcher,
|
||||
@@ -295,6 +320,7 @@ export class NextCatalogBuilder {
|
||||
locationStore,
|
||||
orchestrator,
|
||||
);
|
||||
|
||||
return {
|
||||
entitiesCatalog,
|
||||
locationsCatalog,
|
||||
|
||||
@@ -63,9 +63,12 @@ export async function startStandaloneServer(
|
||||
logger,
|
||||
config,
|
||||
});
|
||||
const service = createServiceBuilder(module)
|
||||
.enableCors({ origin: 'http://localhost:3000' })
|
||||
let service = createServiceBuilder(module)
|
||||
.setPort(options.port)
|
||||
.addRouter('/catalog', router);
|
||||
if (options.enableCors) {
|
||||
service = service.enableCors({ origin: 'http://localhost:3000' });
|
||||
}
|
||||
return await service.start().catch(err => {
|
||||
logger.error(err);
|
||||
process.exit(1);
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# @backstage/plugin-catalog-import
|
||||
|
||||
## 0.5.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 873116e5d: Fix a react warning in `<EntityListComponent>`.
|
||||
- Updated dependencies
|
||||
- @backstage/plugin-catalog-react@0.2.3
|
||||
- @backstage/catalog-model@0.8.3
|
||||
- @backstage/core@0.7.13
|
||||
|
||||
## 0.5.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-catalog-import",
|
||||
"version": "0.5.9",
|
||||
"version": "0.5.10",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -30,12 +30,12 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.8.2",
|
||||
"@backstage/catalog-model": "^0.8.3",
|
||||
"@backstage/catalog-client": "^0.3.13",
|
||||
"@backstage/core": "^0.7.11",
|
||||
"@backstage/core": "^0.7.13",
|
||||
"@backstage/integration": "^0.5.6",
|
||||
"@backstage/integration-react": "^0.1.3",
|
||||
"@backstage/plugin-catalog-react": "^0.2.2",
|
||||
"@backstage/plugin-catalog-react": "^0.2.3",
|
||||
"@backstage/theme": "^0.2.8",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
@@ -53,7 +53,7 @@
|
||||
"yaml": "^1.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/cli": "^0.7.1",
|
||||
"@backstage/dev-utils": "^0.1.17",
|
||||
"@backstage/test-utils": "^0.1.13",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -146,11 +146,15 @@ export const EntityListComponent = ({
|
||||
<List component="div" disablePadding dense>
|
||||
{sortEntities(r.entities).map(entity => (
|
||||
<ListItem
|
||||
component={withLinks ? EntityRefLink : 'div'}
|
||||
entityRef={withLinks ? entity : undefined}
|
||||
button={withLinks as any}
|
||||
key={formatEntityRefTitle(entity)}
|
||||
className={classes.nested}
|
||||
{...(withLinks
|
||||
? {
|
||||
component: EntityRefLink,
|
||||
entityRef: entity,
|
||||
button: withLinks as any,
|
||||
}
|
||||
: {})}
|
||||
>
|
||||
<ListItemIcon>{getEntityIcon(entity)}</ListItemIcon>
|
||||
<ListItemText primary={formatEntityRefTitle(entity)} />
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
# @backstage/plugin-catalog-react
|
||||
|
||||
## 0.2.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 172c97324: Add `EntityLifecyclePicker` and `EntityOwnerPicker` UI components to allow filtering by `spec.lifecycle` and `spec.owner` on catalog-related pages.
|
||||
- Updated dependencies
|
||||
- @backstage/catalog-model@0.8.3
|
||||
- @backstage/core@0.7.13
|
||||
|
||||
## 0.2.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-catalog-react",
|
||||
"version": "0.2.2",
|
||||
"version": "0.2.3",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -29,8 +29,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-client": "^0.3.13",
|
||||
"@backstage/catalog-model": "^0.8.2",
|
||||
"@backstage/core": "^0.7.12",
|
||||
"@backstage/catalog-model": "^0.8.3",
|
||||
"@backstage/core": "^0.7.13",
|
||||
"@backstage/core-plugin-api": "^0.1.2",
|
||||
"@backstage/integration": "^0.5.6",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
@@ -44,8 +44,8 @@
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.7.0",
|
||||
"@backstage/core": "^0.7.12",
|
||||
"@backstage/cli": "^0.7.1",
|
||||
"@backstage/core": "^0.7.13",
|
||||
"@backstage/dev-utils": "^0.1.17",
|
||||
"@backstage/test-utils": "^0.1.13",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user