Merge branch 'master' into feature/remove-aws-v2
Signed-off-by: Andrew Ochsner <andrew.ochsner@cognizant.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/create-app': patch
|
||||
---
|
||||
|
||||
Correct command to create new plugins
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/theme': minor
|
||||
---
|
||||
|
||||
Updates light theme's primary foreground and `running` status indicator colours to meet WCAG. Previously #2E77D0 changed to #1F5493.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-search-backend-module-elasticsearch': minor
|
||||
---
|
||||
|
||||
Upgrade to aws-sdk v3 and include OpenSearch Serverless support
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-common': minor
|
||||
'@backstage/plugin-scaffolder-react': minor
|
||||
---
|
||||
|
||||
Add support for Markdown text blob outputs from templates
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend': patch
|
||||
---
|
||||
|
||||
Remove unnecessary join in the entity facets endpoint, exclude nulls
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/core-components': patch
|
||||
'@backstage/theme': patch
|
||||
---
|
||||
|
||||
Fix accessibility issue with Backstage Table's header style
|
||||
+39
-2
@@ -220,52 +220,89 @@
|
||||
"@backstage/plugin-vault": "0.1.11",
|
||||
"@backstage/plugin-vault-backend": "0.3.0",
|
||||
"@backstage/plugin-xcmetrics": "0.2.37",
|
||||
"@backstage/plugin-analytics-module-ga4": "0.0.0"
|
||||
"@backstage/plugin-analytics-module-ga4": "0.0.0",
|
||||
"@backstage/plugin-devtools": "0.0.0",
|
||||
"@backstage/plugin-devtools-backend": "0.0.0",
|
||||
"@backstage/plugin-devtools-common": "0.0.0"
|
||||
},
|
||||
"changesets": [
|
||||
"afraid-shirts-train",
|
||||
"backend-like-a-tattoo",
|
||||
"beige-tigers-matter",
|
||||
"blue-eyes-run",
|
||||
"brown-eyes-yell",
|
||||
"brown-keys-raise",
|
||||
"calm-snails-trade",
|
||||
"calm-wolves-taste",
|
||||
"chilled-goats-jump",
|
||||
"chilled-pants-wonder",
|
||||
"chilled-queens-wonder",
|
||||
"chilly-fireants-type",
|
||||
"chilly-taxis-shop",
|
||||
"clean-lobsters-brake",
|
||||
"cold-drinks-exercise",
|
||||
"cold-laws-turn",
|
||||
"cool-dingos-repair",
|
||||
"create-app-1682437922",
|
||||
"curly-rats-fold",
|
||||
"curvy-months-appear",
|
||||
"dry-boats-sniff",
|
||||
"dull-wombats-stare",
|
||||
"eighty-olives-live",
|
||||
"extragavent-fast-fly",
|
||||
"fair-suits-warn",
|
||||
"fifty-grapes-explode",
|
||||
"fifty-laws-count",
|
||||
"five-tigers-whisper",
|
||||
"fluffy-crabs-reply",
|
||||
"fluffy-mirrors-happen",
|
||||
"fresh-moons-unite",
|
||||
"funny-trains-sniff",
|
||||
"good-lions-approve",
|
||||
"green-cheetahs-cover",
|
||||
"healthy-ladybugs-chew",
|
||||
"heavy-penguins-burn",
|
||||
"honest-countries-deny",
|
||||
"honest-turkeys-learn",
|
||||
"hungry-foxes-divide",
|
||||
"hungry-peas-sing",
|
||||
"khaki-brooms-kiss",
|
||||
"lazy-keys-work",
|
||||
"mighty-bears-glow",
|
||||
"mighty-cows-greet",
|
||||
"modern-pumpkins-join",
|
||||
"moody-lizards-beam",
|
||||
"moody-shrimps-train",
|
||||
"new-roses-fail",
|
||||
"nine-ties-smoke",
|
||||
"ninety-eggs-lie",
|
||||
"ninety-mugs-sin",
|
||||
"olive-turkeys-switch",
|
||||
"polite-dingos-train",
|
||||
"pretty-gifts-greet",
|
||||
"quick-flies-guess",
|
||||
"quiet-bikes-smash",
|
||||
"quiet-stingrays-fly",
|
||||
"rare-elephants-arrive",
|
||||
"red-cheetahs-invite",
|
||||
"rotten-chefs-jog",
|
||||
"serious-hornets-remember",
|
||||
"six-mails-shout",
|
||||
"slimy-turkeys-return",
|
||||
"soft-readers-exist",
|
||||
"stupid-seas-stare",
|
||||
"swift-fishes-run",
|
||||
"tame-games-rule",
|
||||
"thick-parents-pump",
|
||||
"thick-pugs-rush",
|
||||
"thin-ways-exist",
|
||||
"thirty-jobs-sort",
|
||||
"tough-moles-whisper",
|
||||
"twelve-zebras-repair",
|
||||
"two-gorillas-drop",
|
||||
"unlucky-deers-teach",
|
||||
"unlucky-plants-give",
|
||||
"warm-lamps-tease"
|
||||
"warm-lamps-tease",
|
||||
"wet-dolphins-love"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-azure-devops': minor
|
||||
---
|
||||
|
||||
The getBuildRuns function now checks contains multiple comma-separated builds and splits them to send multiple requests for each and concatenates the results.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-auth-backend': patch
|
||||
---
|
||||
|
||||
Added a persistent session store through the database
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend-module-github': minor
|
||||
---
|
||||
|
||||
Implement events support for `GithubMultiOrgEntityProvider`
|
||||
|
||||
**BREAKING:** Passing in a custom `teamTransformer` will now correctly completely override the default transformer behavior
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend-module-github': major
|
||||
'@backstage/plugin-catalog-backend-module-github': patch
|
||||
---
|
||||
|
||||
Updated the `team.edited` event emitted from `GithubOrgEntityProvider` to also include teams description.
|
||||
|
||||
@@ -247,6 +247,7 @@ Olausson
|
||||
Oldsberg
|
||||
onboarding
|
||||
Onboarding
|
||||
OpenSearch
|
||||
OpenShift
|
||||
orgs
|
||||
padding
|
||||
|
||||
+5
-1
@@ -92,10 +92,14 @@ For those contributors who have earned write access to the repository, when a pu
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
This project adheres to the [Spotify FOSS Code of Conduct][code-of-conduct]. By participating, you are expected to honor this code.
|
||||
This project adheres to the [CNCF Code of Conduct][code-of-conduct]. By participating, you are expected to honor this code.
|
||||
|
||||
[code-of-conduct]: https://github.com/backstage/backstage/blob/master/CODE_OF_CONDUCT.md
|
||||
|
||||
## Security Issues?
|
||||
|
||||
See [SECURITY](SECURITY.md).
|
||||
|
||||
## Accessibility
|
||||
|
||||
We encourage you to catch any accessibility issues already in the development phase of new features to Backstage, see our [Accessibility documentation](./docs/accessibility/index.md) for more details.
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
---
|
||||
id: index
|
||||
title: Backstage Accessibility
|
||||
description: Documentation on Backstage Accessibility
|
||||
---
|
||||
|
||||
In an effort to bake accessibility practices further into our process of building features for Backstage, we support automated CI tests for some of our core features in the OSS project: Software Catalog, Software Templates, Search, and TechDocs. As these are just a few of the many plugins out there, we encourage you to consider the accessibility implications of your work on Backstage in order to build a great experience for everyone.
|
||||
|
||||
## How to contribute
|
||||
|
||||
There are multiple ways to contribute to making Backstage more accessible, you'll find below a list of examples to help you get started.
|
||||
|
||||
### Run Lighthouse in CI on your plugin
|
||||
|
||||
If your plugin lives in the [Backstage main repository](https://github.com/backstage/backstage/) you can modify the [urls in the Lighthouse config](https://github.com/backstage/backstage/blob/39ba2284d73885b7ca8290cb38e2b1e4d983c8d6/lighthouserc.js#L19-L34) to run the Lighthouse checks on urls where your plugin exists as well. E.g.
|
||||
|
||||
```diff
|
||||
ci: {
|
||||
collect: {
|
||||
url: [
|
||||
+ /** Your plugin paths */
|
||||
+ 'http://localhost:3000/your-plugin-path,
|
||||
/** Software Catalog */
|
||||
'http://localhost:3000/catalog',
|
||||
'http://localhost:3000/catalog-import',
|
||||
'http://localhost:3000/catalog/default/component/backstage',
|
||||
...
|
||||
],
|
||||
settings: {
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
assert: {
|
||||
...
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
To make sure the [Accessibility Github workflow](https://github.com/backstage/backstage/blob/master/.github/workflows/verify_accessibility.yml) is running when changes are made to your plugin folders, modify the [list of paths](https://github.com/backstage/backstage/blob/10759b6ad2561bd86183ad940256f9a309c7a6b0/.github/workflows/verify_accessibility.yml#L7-L16).
|
||||
|
||||
### Run the Lighthouse CLI locally when developing new features
|
||||
|
||||
If you want to use the Lighthouse CLI and run the checks based on the config you can use the following command:
|
||||
|
||||
```
|
||||
yarn dlx @lhci/cli@0.11.x autorun
|
||||
```
|
||||
|
||||
> Note: running this command will use the [Lighthouse config](https://github.com/backstage/backstage/blob/39ba2284d73885b7ca8290cb38e2b1e4d983c8d6/lighthouserc.js#L19-L34) so make sure to adjust it to your needs if needed.
|
||||
|
||||
### Use Lighthouse Github Action on your own repo
|
||||
|
||||
If your Backstage plugin lives outside of the [Backstage main repository](https://github.com/backstage/backstage/), and you use GitHub Actions for continuous integration, we encourage you to add and modify the [Accessibility GitHub workflow](https://github.com/backstage/backstage/blob/master/.github/workflows/verify_accessibility.yml) to your needs.
|
||||
|
||||
### Report identified issues
|
||||
|
||||
It’s important to remember that automated checks can only catch a small number of accessibility issues, therefore we also encourage you to conduct manual testing of your plugins using Assistive technology (screen readers, alternative navigation, and screen magnifiers are a few examples).
|
||||
|
||||
If you have identified accessibility issues and don’t have time to contribute a fix, please open an issue over at [Backstage Issues](https://github.com/backstage/backstage/issues) to let us know.
|
||||
@@ -510,10 +510,8 @@ take a look at, or you can
|
||||
|
||||
Each individual step can output some variables that can be used in the
|
||||
scaffolder frontend for after the job is finished. This is useful for things
|
||||
like linking to the entity that has been created with the backend, and also
|
||||
linking to the created repository.
|
||||
|
||||
The main two that are used are the following:
|
||||
like linking to the entity that has been created with the backend, linking
|
||||
to the created repository, or showing Markdown text blobs.
|
||||
|
||||
```yaml
|
||||
output:
|
||||
@@ -523,6 +521,10 @@ output:
|
||||
- title: Open in catalog
|
||||
icon: catalog
|
||||
entityRef: ${{ steps['register'].output.entityRef }} # link to the entity that has been ingested to the catalog
|
||||
text:
|
||||
- title: More information
|
||||
content: |
|
||||
**Entity URL:** `${{ steps['publish'].output.remoteUrl }}`
|
||||
```
|
||||
|
||||
## The templating syntax
|
||||
|
||||
@@ -65,6 +65,42 @@ export default async function createPlugin(
|
||||
}
|
||||
```
|
||||
|
||||
Alternatively, if you wish to ingest data from multiple GitHub organizations you can use
|
||||
the `GithubMultiOrgEntityProvider` instead. Note that by default, this provider will namespace
|
||||
groups according to the org they originate from to avoid potential name duplicates:
|
||||
|
||||
```ts title="packages/backend/src/plugins/catalog.ts"
|
||||
/* highlight-add-next-line */
|
||||
import { GithubMultiOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-github';
|
||||
|
||||
export default async function createPlugin(
|
||||
env: PluginEnvironment,
|
||||
): Promise<Router> {
|
||||
const builder = await CatalogBuilder.create(env);
|
||||
|
||||
/* highlight-add-start */
|
||||
// The GitHub URL below needs to match a configured integrations.github entry
|
||||
// specified in your app-config.
|
||||
builder.addEntityProvider(
|
||||
GithubMultiOrgEntityProvider.fromConfig(env.config, {
|
||||
id: 'production',
|
||||
githubUrl: 'https://github.com',
|
||||
// Set the following to list the GitHub orgs you wish to ingest from. You can
|
||||
// also omit this option to ingest all orgs accessible by your GitHub integration
|
||||
orgs: ['org-a', 'org-b'],
|
||||
logger: env.logger,
|
||||
schedule: env.scheduler.createScheduledTaskRunner({
|
||||
frequency: { minutes: 60 },
|
||||
timeout: { minutes: 15 },
|
||||
}),
|
||||
}),
|
||||
);
|
||||
/* highlight-add-end */
|
||||
|
||||
// ..
|
||||
}
|
||||
```
|
||||
|
||||
## Installation with Events Support
|
||||
|
||||
Please follow the installation instructions at
|
||||
@@ -110,6 +146,42 @@ export default async function createPlugin(
|
||||
}
|
||||
```
|
||||
|
||||
Or, alternatively, if using the `GithubMultiOrgEntityProvider`:
|
||||
|
||||
```ts title="packages/backend/src/plugins/catalog.ts"
|
||||
/* highlight-add-next-line */
|
||||
import { GithubMultiOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-github';
|
||||
|
||||
export default async function createPlugin(
|
||||
env: PluginEnvironment,
|
||||
): Promise<Router> {
|
||||
const builder = await CatalogBuilder.create(env);
|
||||
|
||||
/* highlight-add-start */
|
||||
// The GitHub URL below needs to match a configured integrations.github entry
|
||||
// specified in your app-config.
|
||||
builder.addEntityProvider(
|
||||
GithubMultiOrgEntityProvider.fromConfig(env.config, {
|
||||
id: 'production',
|
||||
githubUrl: 'https://github.com',
|
||||
// Set the following to list the GitHub orgs you wish to ingest from. You can
|
||||
// also omit this option to ingest all orgs accessible by your GitHub integration
|
||||
orgs: ['org-a', 'org-b'],
|
||||
logger: env.logger,
|
||||
schedule: env.scheduler.createScheduledTaskRunner({
|
||||
frequency: { minutes: 60 },
|
||||
timeout: { minutes: 15 },
|
||||
}),
|
||||
// Pass in the eventBroker to allow this provider to subscribe to GitHub events
|
||||
eventBroker: env.eventBroker,
|
||||
}),
|
||||
);
|
||||
/* highlight-add-end */
|
||||
|
||||
// ..
|
||||
}
|
||||
```
|
||||
|
||||
You can check the official docs to [configure your webhook](https://docs.github.com/en/developers/webhooks-and-events/webhooks/creating-webhooks) and to [secure your request](https://docs.github.com/en/developers/webhooks-and-events/webhooks/securing-your-webhooks).
|
||||
The webhook will need to be configured to forward `organization`,`team` and `membership` events.
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,10 @@
|
||||
---
|
||||
title: xkcd comic
|
||||
author: Vity
|
||||
authorUrl: https://github.com/Vity01
|
||||
category: Humor
|
||||
description: xkcd comic within Backstage
|
||||
documentation: https://github.com/Vity01/backstage-xkcd/
|
||||
iconUrl: https://raw.githubusercontent.com/Vity01/backstage-xkcd/main/docs/xkcd.svg
|
||||
npmPackageName: 'backstage-plugin-xkcd'
|
||||
addedDate: '2023-05-05'
|
||||
@@ -408,6 +408,7 @@
|
||||
"label": "Core Services",
|
||||
"items": ["backend-system/core-services/index"]
|
||||
}
|
||||
]
|
||||
],
|
||||
"Accessibility": ["accessibility/index"]
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -47,7 +47,7 @@
|
||||
"@types/react": "^17",
|
||||
"@types/react-dom": "^17"
|
||||
},
|
||||
"version": "1.14.0-next.1",
|
||||
"version": "1.14.0-next.2",
|
||||
"dependencies": {
|
||||
"@backstage/errors": "workspace:^",
|
||||
"@manypkg/get-packages": "^1.1.3"
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
# @backstage/app-defaults
|
||||
|
||||
## 1.3.1-next.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/theme@0.3.0-next.0
|
||||
- @backstage/core-components@0.13.1-next.1
|
||||
- @backstage/core-app-api@1.8.0-next.1
|
||||
- @backstage/core-plugin-api@1.5.1
|
||||
- @backstage/plugin-permission-react@0.4.12
|
||||
|
||||
## 1.3.1-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/app-defaults",
|
||||
"description": "Provides the default wiring of a Backstage App",
|
||||
"version": "1.3.1-next.1",
|
||||
"version": "1.3.1-next.2",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "dist/index.esm.js",
|
||||
|
||||
@@ -1,5 +1,73 @@
|
||||
# example-app
|
||||
|
||||
## 0.2.83-next.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/plugin-devtools@0.1.0-next.0
|
||||
- @backstage/plugin-kubernetes@0.9.0-next.2
|
||||
- @backstage/plugin-home@0.5.2-next.2
|
||||
- @backstage/theme@0.3.0-next.0
|
||||
- @backstage/plugin-badges@0.2.42-next.2
|
||||
- @backstage/plugin-tech-radar@0.6.4-next.2
|
||||
- @backstage/plugin-scaffolder-react@1.4.0-next.2
|
||||
- @backstage/plugin-explore@0.4.3-next.2
|
||||
- @backstage/plugin-org@0.6.8-next.2
|
||||
- @backstage/core-components@0.13.1-next.1
|
||||
- @backstage/plugin-search-react@1.6.0-next.2
|
||||
- @backstage/plugin-azure-devops@0.3.0-next.2
|
||||
- @backstage/plugin-stack-overflow@0.1.15-next.2
|
||||
- @backstage/app-defaults@1.3.1-next.2
|
||||
- @backstage/cli@0.22.7-next.0
|
||||
- @backstage/integration-react@1.1.13-next.2
|
||||
- @backstage/plugin-airbrake@0.3.18-next.2
|
||||
- @backstage/plugin-api-docs@0.9.3-next.2
|
||||
- @backstage/plugin-azure-sites@0.1.7-next.2
|
||||
- @backstage/plugin-catalog-graph@0.2.30-next.2
|
||||
- @backstage/plugin-catalog-react@1.6.0-next.2
|
||||
- @backstage/plugin-circleci@0.3.18-next.2
|
||||
- @backstage/plugin-cloudbuild@0.3.18-next.2
|
||||
- @backstage/plugin-code-coverage@0.2.11-next.2
|
||||
- @backstage/plugin-cost-insights@0.12.7-next.2
|
||||
- @backstage/plugin-dynatrace@5.0.0-next.2
|
||||
- @backstage/plugin-entity-feedback@0.2.1-next.2
|
||||
- @backstage/plugin-gcalendar@0.3.14-next.1
|
||||
- @backstage/plugin-gcp-projects@0.3.37-next.1
|
||||
- @backstage/plugin-github-actions@0.5.18-next.2
|
||||
- @backstage/plugin-gocd@0.1.24-next.2
|
||||
- @backstage/plugin-graphiql@0.2.50-next.1
|
||||
- @backstage/plugin-jenkins@0.7.17-next.2
|
||||
- @backstage/plugin-kafka@0.3.18-next.2
|
||||
- @backstage/plugin-lighthouse@0.4.3-next.2
|
||||
- @backstage/plugin-linguist@0.1.3-next.2
|
||||
- @backstage/plugin-microsoft-calendar@0.1.3-next.1
|
||||
- @backstage/plugin-newrelic@0.3.36-next.1
|
||||
- @backstage/plugin-octopus-deploy@0.2.0-next.2
|
||||
- @backstage/plugin-pagerduty@0.5.11-next.2
|
||||
- @backstage/plugin-playlist@0.1.9-next.2
|
||||
- @backstage/plugin-puppetdb@0.1.1-next.2
|
||||
- @backstage/plugin-rollbar@0.4.18-next.2
|
||||
- @backstage/plugin-scaffolder@1.13.1-next.2
|
||||
- @backstage/plugin-search@1.3.0-next.2
|
||||
- @backstage/plugin-sentry@0.5.3-next.2
|
||||
- @backstage/plugin-shortcuts@0.3.10-next.2
|
||||
- @backstage/plugin-stackstorm@0.1.2-next.1
|
||||
- @backstage/plugin-tech-insights@0.3.10-next.2
|
||||
- @backstage/plugin-techdocs@1.6.2-next.2
|
||||
- @backstage/plugin-techdocs-module-addons-contrib@1.0.13-next.2
|
||||
- @backstage/plugin-techdocs-react@1.1.6-next.1
|
||||
- @backstage/plugin-todo@0.2.20-next.2
|
||||
- @backstage/plugin-user-settings@0.7.3-next.2
|
||||
- @backstage/plugin-apache-airflow@0.2.11-next.1
|
||||
- @backstage/plugin-catalog-import@0.9.8-next.2
|
||||
- @backstage/plugin-newrelic-dashboard@0.2.11-next.2
|
||||
- @backstage/config@1.0.7
|
||||
- @backstage/core-app-api@1.8.0-next.1
|
||||
- @backstage/core-plugin-api@1.5.1
|
||||
- @backstage/plugin-permission-react@0.4.12
|
||||
- @internal/plugin-catalog-customized@0.0.10-next.2
|
||||
|
||||
## 0.2.83-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "example-app",
|
||||
"version": "0.2.83-next.1",
|
||||
"version": "0.2.83-next.2",
|
||||
"private": true,
|
||||
"backstage": {
|
||||
"role": "frontend"
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# example-backend-next
|
||||
|
||||
## 0.0.11-next.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/plugin-catalog-backend@1.9.1-next.2
|
||||
- @backstage/plugin-kubernetes-backend@0.11.0-next.2
|
||||
- @backstage/plugin-search-backend@1.3.1-next.2
|
||||
- @backstage/plugin-scaffolder-backend@1.13.2-next.2
|
||||
|
||||
## 0.0.11-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "example-backend-next",
|
||||
"version": "0.0.11-next.1",
|
||||
"version": "0.0.11-next.2",
|
||||
"main": "dist/index.cjs.js",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @backstage/backend-openapi-utils
|
||||
|
||||
## 0.0.2-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- fe16bd39e83: Use permalinks for links including a line number reference
|
||||
- 021cfbb5152: Corrected resolution of parameter nested schema to use central schemas.
|
||||
|
||||
## 0.0.2-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/backend-openapi-utils",
|
||||
"description": "OpenAPI typescript support.",
|
||||
"version": "0.0.2-next.0",
|
||||
"version": "0.0.2-next.1",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
# example-backend
|
||||
|
||||
## 0.2.83-next.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/plugin-devtools-backend@0.1.0-next.0
|
||||
- @backstage/plugin-catalog-backend@1.9.1-next.2
|
||||
- @backstage/plugin-badges-backend@0.2.0-next.2
|
||||
- @backstage/plugin-search-backend-module-elasticsearch@1.3.0-next.2
|
||||
- @backstage/plugin-kubernetes-backend@0.11.0-next.2
|
||||
- @backstage/plugin-auth-backend@0.18.3-next.2
|
||||
- @backstage/plugin-search-backend@1.3.1-next.2
|
||||
- example-app@0.2.83-next.2
|
||||
- @backstage/plugin-scaffolder-backend@1.13.2-next.2
|
||||
- @backstage/plugin-scaffolder-backend-module-rails@0.4.14-next.2
|
||||
- @backstage/config@1.0.7
|
||||
|
||||
## 0.2.83-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "example-backend",
|
||||
"version": "0.2.83-next.1",
|
||||
"version": "0.2.83-next.2",
|
||||
"main": "dist/index.cjs.js",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
# @backstage/core-components
|
||||
|
||||
## 0.13.1-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 83b45f9df50: Fix accessibility issue with Backstage Table's header style
|
||||
- e97769f7c0b: Fix accessibility issue on controlled select input on tab navigation + keyboard enter/space action.
|
||||
- Updated dependencies
|
||||
- @backstage/theme@0.3.0-next.0
|
||||
- @backstage/config@1.0.7
|
||||
- @backstage/core-plugin-api@1.5.1
|
||||
|
||||
## 0.13.1-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/core-components",
|
||||
"description": "Core components used by Backstage plugins and apps",
|
||||
"version": "0.13.1-next.0",
|
||||
"version": "0.13.1-next.1",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "dist/index.esm.js",
|
||||
|
||||
@@ -105,7 +105,6 @@ const StyledMTableHeader = withStyles(
|
||||
borderTop: `1px solid ${theme.palette.grey.A100}`,
|
||||
borderBottom: `1px solid ${theme.palette.grey.A100}`,
|
||||
// withStyles hasn't a generic overload for theme
|
||||
color: (theme as BackstageTheme).palette.textSubtle,
|
||||
fontWeight: theme.typography.fontWeightBold,
|
||||
position: 'static',
|
||||
wordBreak: 'normal',
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @backstage/create-app
|
||||
|
||||
## 0.5.1-next.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 1d5e42655cd: Correct command to create new plugins
|
||||
|
||||
## 0.5.1-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/create-app",
|
||||
"description": "A CLI that helps you create your own Backstage app",
|
||||
"version": "0.5.1-next.1",
|
||||
"version": "0.5.1-next.2",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
|
||||
@@ -4,6 +4,6 @@ This is where your own plugins and their associated modules live, each in a
|
||||
separate folder of its own.
|
||||
|
||||
If you want to create a new plugin here, go to your project root directory, run
|
||||
the command `yarn backstage-cli create`, and follow the on-screen instructions.
|
||||
the command `yarn new`, and follow the on-screen instructions.
|
||||
|
||||
You can also check out existing plugins on [the plugin marketplace](https://backstage.io/plugins)!
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# @backstage/dev-utils
|
||||
|
||||
## 1.0.15-next.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/theme@0.3.0-next.0
|
||||
- @backstage/core-components@0.13.1-next.1
|
||||
- @backstage/app-defaults@1.3.1-next.2
|
||||
- @backstage/integration-react@1.1.13-next.2
|
||||
- @backstage/test-utils@1.3.1-next.2
|
||||
- @backstage/plugin-catalog-react@1.6.0-next.2
|
||||
- @backstage/core-app-api@1.8.0-next.1
|
||||
- @backstage/core-plugin-api@1.5.1
|
||||
|
||||
## 1.0.15-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/dev-utils",
|
||||
"description": "Utilities for developing Backstage plugins.",
|
||||
"version": "1.0.15-next.1",
|
||||
"version": "1.0.15-next.2",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "dist/index.esm.js",
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# e2e-test
|
||||
|
||||
## 0.2.3-next.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/create-app@0.5.1-next.2
|
||||
|
||||
## 0.2.3-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "e2e-test",
|
||||
"description": "E2E test for verifying Backstage packages",
|
||||
"version": "0.2.3-next.1",
|
||||
"version": "0.2.3-next.2",
|
||||
"private": true,
|
||||
"backstage": {
|
||||
"role": "cli"
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# @backstage/integration-react
|
||||
|
||||
## 1.1.13-next.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/theme@0.3.0-next.0
|
||||
- @backstage/core-components@0.13.1-next.1
|
||||
- @backstage/config@1.0.7
|
||||
- @backstage/core-plugin-api@1.5.1
|
||||
|
||||
## 1.1.13-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/integration-react",
|
||||
"description": "Frontend package for managing integrations towards external systems",
|
||||
"version": "1.1.13-next.1",
|
||||
"version": "1.1.13-next.2",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
# techdocs-cli-embedded-app
|
||||
|
||||
## 0.2.82-next.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/theme@0.3.0-next.0
|
||||
- @backstage/core-components@0.13.1-next.1
|
||||
- @backstage/app-defaults@1.3.1-next.2
|
||||
- @backstage/cli@0.22.7-next.0
|
||||
- @backstage/integration-react@1.1.13-next.2
|
||||
- @backstage/test-utils@1.3.1-next.2
|
||||
- @backstage/plugin-catalog@1.11.0-next.2
|
||||
- @backstage/plugin-techdocs@1.6.2-next.2
|
||||
- @backstage/plugin-techdocs-react@1.1.6-next.1
|
||||
- @backstage/config@1.0.7
|
||||
- @backstage/core-app-api@1.8.0-next.1
|
||||
- @backstage/core-plugin-api@1.5.1
|
||||
|
||||
## 0.2.82-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "techdocs-cli-embedded-app",
|
||||
"version": "0.2.82-next.1",
|
||||
"version": "0.2.82-next.2",
|
||||
"private": true,
|
||||
"backstage": {
|
||||
"role": "frontend"
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
# @backstage/test-utils
|
||||
|
||||
## 1.3.1-next.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/theme@0.3.0-next.0
|
||||
- @backstage/config@1.0.7
|
||||
- @backstage/core-app-api@1.8.0-next.1
|
||||
- @backstage/core-plugin-api@1.5.1
|
||||
- @backstage/plugin-permission-react@0.4.12
|
||||
|
||||
## 1.3.1-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/test-utils",
|
||||
"description": "Utilities to test Backstage plugins and apps.",
|
||||
"version": "1.3.1-next.1",
|
||||
"version": "1.3.1-next.2",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# @backstage/theme
|
||||
|
||||
## 0.3.0-next.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 98c0c199b15: Updates light theme's primary foreground and `running` status indicator colours to meet WCAG. Previously #2E77D0 changed to #1F5493.
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 83b45f9df50: Fix accessibility issue with Backstage Table's header style
|
||||
|
||||
## 0.2.19
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/theme",
|
||||
"description": "material-ui theme for use with Backstage.",
|
||||
"version": "0.2.19",
|
||||
"version": "0.3.0-next.0",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "dist/index.esm.js",
|
||||
|
||||
@@ -159,7 +159,6 @@ export function createThemeOverrides(theme: BackstageTheme): Overrides {
|
||||
head: {
|
||||
wordBreak: 'break-word',
|
||||
overflow: 'hidden',
|
||||
color: theme.palette.textSubtle,
|
||||
fontWeight: 'normal',
|
||||
lineHeight: '1',
|
||||
},
|
||||
@@ -198,14 +197,10 @@ export function createThemeOverrides(theme: BackstageTheme): Overrides {
|
||||
'&:hover': {
|
||||
color: 'inherit',
|
||||
},
|
||||
'&:focus': {
|
||||
color: 'inherit',
|
||||
},
|
||||
},
|
||||
// Bold font for highlighting selected column
|
||||
active: {
|
||||
fontWeight: 'bold',
|
||||
color: 'inherit',
|
||||
},
|
||||
},
|
||||
MuiListItemText: {
|
||||
|
||||
@@ -33,7 +33,7 @@ export const lightTheme = createTheme({
|
||||
ok: '#1DB954',
|
||||
warning: '#FF9800',
|
||||
error: '#E22134',
|
||||
running: '#2E77D0',
|
||||
running: '#1F5493',
|
||||
pending: '#FFED51',
|
||||
aborted: '#757575',
|
||||
},
|
||||
@@ -48,7 +48,7 @@ export const lightTheme = createTheme({
|
||||
},
|
||||
},
|
||||
primary: {
|
||||
main: '#2E77D0',
|
||||
main: '#1F5493',
|
||||
},
|
||||
banner: {
|
||||
info: '#2E77D0',
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# @backstage/plugin-adr
|
||||
|
||||
## 0.5.1-next.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/theme@0.3.0-next.0
|
||||
- @backstage/core-components@0.13.1-next.1
|
||||
- @backstage/plugin-search-react@1.6.0-next.2
|
||||
- @backstage/integration-react@1.1.13-next.2
|
||||
- @backstage/plugin-catalog-react@1.6.0-next.2
|
||||
- @backstage/core-plugin-api@1.5.1
|
||||
|
||||
## 0.5.1-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-adr",
|
||||
"version": "0.5.1-next.1",
|
||||
"version": "0.5.1-next.2",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# @backstage/plugin-airbrake
|
||||
|
||||
## 0.3.18-next.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/theme@0.3.0-next.0
|
||||
- @backstage/core-components@0.13.1-next.1
|
||||
- @backstage/dev-utils@1.0.15-next.2
|
||||
- @backstage/test-utils@1.3.1-next.2
|
||||
- @backstage/plugin-catalog-react@1.6.0-next.2
|
||||
- @backstage/core-plugin-api@1.5.1
|
||||
|
||||
## 0.3.18-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-airbrake",
|
||||
"version": "0.3.18-next.1",
|
||||
"version": "0.3.18-next.2",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# @backstage/plugin-allure
|
||||
|
||||
## 0.1.34-next.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/theme@0.3.0-next.0
|
||||
- @backstage/core-components@0.13.1-next.1
|
||||
- @backstage/plugin-catalog-react@1.6.0-next.2
|
||||
- @backstage/core-plugin-api@1.5.1
|
||||
|
||||
## 0.1.34-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/plugin-allure",
|
||||
"description": "A Backstage plugin that integrates with Allure",
|
||||
"version": "0.1.34-next.1",
|
||||
"version": "0.1.34-next.2",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# @backstage/plugin-analytics-module-ga
|
||||
|
||||
## 0.1.29-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/theme@0.3.0-next.0
|
||||
- @backstage/core-components@0.13.1-next.1
|
||||
- @backstage/config@1.0.7
|
||||
- @backstage/core-plugin-api@1.5.1
|
||||
|
||||
## 0.1.29-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-analytics-module-ga",
|
||||
"version": "0.1.29-next.0",
|
||||
"version": "0.1.29-next.1",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# @backstage/plugin-analytics-module-ga4
|
||||
|
||||
## 0.1.0-next.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/theme@0.3.0-next.0
|
||||
- @backstage/core-components@0.13.1-next.1
|
||||
- @backstage/config@1.0.7
|
||||
- @backstage/core-plugin-api@1.5.1
|
||||
|
||||
## 0.1.0-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-analytics-module-ga4",
|
||||
"version": "0.1.0-next.1",
|
||||
"version": "0.1.0-next.2",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @backstage/plugin-apache-airflow
|
||||
|
||||
## 0.2.11-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/core-components@0.13.1-next.1
|
||||
- @backstage/core-plugin-api@1.5.1
|
||||
|
||||
## 0.2.11-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-apache-airflow",
|
||||
"version": "0.2.11-next.0",
|
||||
"version": "0.2.11-next.1",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
# @backstage/plugin-api-docs
|
||||
|
||||
## 0.9.3-next.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/theme@0.3.0-next.0
|
||||
- @backstage/core-components@0.13.1-next.1
|
||||
- @backstage/plugin-catalog@1.11.0-next.2
|
||||
- @backstage/plugin-catalog-react@1.6.0-next.2
|
||||
- @backstage/core-plugin-api@1.5.1
|
||||
|
||||
## 0.9.3-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/plugin-api-docs",
|
||||
"description": "A Backstage plugin that helps represent API entities in the frontend",
|
||||
"version": "0.9.3-next.1",
|
||||
"version": "0.9.3-next.2",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
# @backstage/plugin-apollo-explorer
|
||||
|
||||
## 0.1.11-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/theme@0.3.0-next.0
|
||||
- @backstage/core-components@0.13.1-next.1
|
||||
- @backstage/core-plugin-api@1.5.1
|
||||
|
||||
## 0.1.11-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-apollo-explorer",
|
||||
"version": "0.1.11-next.0",
|
||||
"version": "0.1.11-next.1",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @backstage/plugin-auth-backend
|
||||
|
||||
## 0.18.3-next.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 3ffcdac7d07: Added a persistent session store through the database
|
||||
- Updated dependencies
|
||||
- @backstage/config@1.0.7
|
||||
|
||||
## 0.18.3-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2023 The Backstage Authors
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.up = async function up(knex) {
|
||||
// See https://github.com/gx0r/connect-session-knex
|
||||
// Modeled loosely after https://github.com/gx0r/connect-session-knex/blob/4e0e36a9afbb13c3000a89f5e341f2d2d4339a02/lib/index.js#L114
|
||||
// For simplicity we always make the session a string
|
||||
// Do NOT change around this table or column names; the connect-session-knex library makes assumptions about them
|
||||
await knex.schema.createTable('sessions', table => {
|
||||
table.comment('Session data');
|
||||
table.string('sid').primary().notNullable().comment('ID of the session');
|
||||
table
|
||||
.text('sess', 'longtext')
|
||||
.notNullable()
|
||||
.comment('Session data, JSON serialized');
|
||||
table
|
||||
.timestamp('expired')
|
||||
.notNullable()
|
||||
.comment('The point in time when the session expires');
|
||||
table.index('sid', 'sessions_sid_idx');
|
||||
table.index('expired', 'sessions_expired_idx');
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.down = async function down(knex) {
|
||||
await knex.schema.alterTable('sessions', table => {
|
||||
table.dropIndex([], 'sessions_sid_idx');
|
||||
table.dropIndex([], 'sessions_expired_idx');
|
||||
});
|
||||
await knex.schema.dropTable('sessions');
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/plugin-auth-backend",
|
||||
"description": "A Backstage backend plugin that handles authentication",
|
||||
"version": "0.18.3-next.1",
|
||||
"version": "0.18.3-next.2",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -44,6 +44,7 @@
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/passport": "^1.0.3",
|
||||
"compression": "^1.7.4",
|
||||
"connect-session-knex": "^3.0.1",
|
||||
"cookie-parser": "^1.4.5",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.17.1",
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2023 The Backstage Authors
|
||||
*
|
||||
* 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 {
|
||||
DatabaseManager,
|
||||
PluginDatabaseManager,
|
||||
resolvePackagePath,
|
||||
} from '@backstage/backend-common';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { Knex } from 'knex';
|
||||
|
||||
const migrationsDir = resolvePackagePath(
|
||||
'@backstage/plugin-auth-backend',
|
||||
'migrations',
|
||||
);
|
||||
|
||||
/**
|
||||
* Ensures that a database connection is established exactly once and only when
|
||||
* asked for, and runs migrations.
|
||||
*/
|
||||
export class AuthDatabase {
|
||||
readonly #database: PluginDatabaseManager;
|
||||
#promise: Promise<Knex> | undefined;
|
||||
|
||||
static create(database: PluginDatabaseManager): AuthDatabase {
|
||||
return new AuthDatabase(database);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
static forTesting(): AuthDatabase {
|
||||
const config = new ConfigReader({
|
||||
backend: {
|
||||
database: {
|
||||
client: 'better-sqlite3',
|
||||
connection: ':memory:',
|
||||
useNullAsDefault: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
const database = DatabaseManager.fromConfig(config).forPlugin('auth');
|
||||
return new AuthDatabase(database);
|
||||
}
|
||||
|
||||
static async runMigrations(knex: Knex): Promise<void> {
|
||||
await knex.migrate.latest({
|
||||
directory: migrationsDir,
|
||||
});
|
||||
}
|
||||
|
||||
private constructor(database: PluginDatabaseManager) {
|
||||
this.#database = database;
|
||||
}
|
||||
|
||||
get(): Promise<Knex> {
|
||||
this.#promise ??= this.#database.getClient().then(async client => {
|
||||
if (!this.#database.migrations?.skip) {
|
||||
await AuthDatabase.runMigrations(client);
|
||||
}
|
||||
return client;
|
||||
});
|
||||
|
||||
return this.#promise;
|
||||
}
|
||||
}
|
||||
@@ -14,33 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import Knex, { Knex as KnexType } from 'knex';
|
||||
import { DatabaseKeyStore } from './DatabaseKeyStore';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
function createDatabaseManager(
|
||||
client: KnexType,
|
||||
skipMigrations: boolean = false,
|
||||
) {
|
||||
return {
|
||||
getClient: async () => client,
|
||||
migrations: {
|
||||
skip: skipMigrations,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createDB() {
|
||||
const knex = Knex({
|
||||
client: 'better-sqlite3',
|
||||
connection: ':memory:',
|
||||
useNullAsDefault: true,
|
||||
});
|
||||
knex.client.pool.on('createSuccess', (_eventId: any, resource: any) => {
|
||||
resource.run('PRAGMA foreign_keys = ON', () => {});
|
||||
});
|
||||
return knex;
|
||||
}
|
||||
import { AuthDatabase } from '../database/AuthDatabase';
|
||||
import { DatabaseKeyStore } from './DatabaseKeyStore';
|
||||
import { TestDatabases } from '@backstage/backend-test-utils';
|
||||
|
||||
const keyBase = {
|
||||
use: 'sig',
|
||||
@@ -48,95 +25,107 @@ const keyBase = {
|
||||
alg: 'Base64',
|
||||
} as const;
|
||||
|
||||
jest.setTimeout(60_000);
|
||||
|
||||
describe('DatabaseKeyStore', () => {
|
||||
it('should store a key', async () => {
|
||||
const client = createDB();
|
||||
const store = await DatabaseKeyStore.create({
|
||||
database: createDatabaseManager(client),
|
||||
});
|
||||
|
||||
const key = {
|
||||
kid: '123',
|
||||
...keyBase,
|
||||
};
|
||||
|
||||
await expect(store.listKeys()).resolves.toEqual({ items: [] });
|
||||
await store.addKey(key);
|
||||
|
||||
const { items } = await store.listKeys();
|
||||
expect(items).toEqual([{ createdAt: expect.anything(), key }]);
|
||||
expect(
|
||||
Math.abs(
|
||||
DateTime.fromJSDate(items[0].createdAt).diffNow('seconds').seconds,
|
||||
),
|
||||
).toBeLessThan(10);
|
||||
const databases = TestDatabases.create({
|
||||
ids: ['MYSQL_8', 'POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'],
|
||||
});
|
||||
|
||||
it('should remove stored keys', async () => {
|
||||
const client = createDB();
|
||||
const store = await DatabaseKeyStore.create({
|
||||
database: createDatabaseManager(client),
|
||||
});
|
||||
it.each(databases.eachSupportedId())(
|
||||
'should store a key, %p',
|
||||
async databaseId => {
|
||||
const knex = await databases.init(databaseId);
|
||||
await AuthDatabase.runMigrations(knex);
|
||||
|
||||
const key1 = { kid: '1', ...keyBase };
|
||||
const key2 = { kid: '2', ...keyBase };
|
||||
const key3 = { kid: '3', ...keyBase };
|
||||
const store = new DatabaseKeyStore(knex);
|
||||
|
||||
await store.addKey(key1);
|
||||
await store.addKey(key2);
|
||||
await store.addKey(key3);
|
||||
const key = {
|
||||
kid: '123',
|
||||
...keyBase,
|
||||
};
|
||||
|
||||
await expect(store.listKeys()).resolves.toEqual({
|
||||
items: [
|
||||
{ key: key1, createdAt: expect.anything() },
|
||||
{ key: key2, createdAt: expect.anything() },
|
||||
{ key: key3, createdAt: expect.anything() },
|
||||
],
|
||||
});
|
||||
await expect(store.listKeys()).resolves.toEqual({ items: [] });
|
||||
await store.addKey(key);
|
||||
|
||||
store.removeKeys(['1']);
|
||||
const { items } = await store.listKeys();
|
||||
expect(items).toEqual([{ createdAt: expect.anything(), key }]);
|
||||
expect(
|
||||
Math.abs(
|
||||
DateTime.fromJSDate(items[0].createdAt).diffNow('seconds').seconds,
|
||||
),
|
||||
).toBeLessThan(10);
|
||||
},
|
||||
);
|
||||
|
||||
await expect(store.listKeys()).resolves.toEqual({
|
||||
items: [
|
||||
{ key: key2, createdAt: expect.anything() },
|
||||
{ key: key3, createdAt: expect.anything() },
|
||||
],
|
||||
});
|
||||
it.each(databases.eachSupportedId())(
|
||||
'should remove stored keys, %p',
|
||||
async databaseId => {
|
||||
const knex = await databases.init(databaseId);
|
||||
await AuthDatabase.runMigrations(knex);
|
||||
|
||||
store.removeKeys(['1', '2']);
|
||||
const store = new DatabaseKeyStore(knex);
|
||||
|
||||
await expect(store.listKeys()).resolves.toEqual({
|
||||
items: [{ key: key3, createdAt: expect.anything() }],
|
||||
});
|
||||
const key1 = { kid: '1', ...keyBase };
|
||||
const key2 = { kid: '2', ...keyBase };
|
||||
const key3 = { kid: '3', ...keyBase };
|
||||
|
||||
store.removeKeys([]);
|
||||
await store.addKey(key1);
|
||||
await store.addKey(key2);
|
||||
await store.addKey(key3);
|
||||
|
||||
await expect(store.listKeys()).resolves.toEqual({
|
||||
items: [{ key: key3, createdAt: expect.anything() }],
|
||||
});
|
||||
await expect(store.listKeys()).resolves.toEqual({
|
||||
items: [
|
||||
{ key: key1, createdAt: expect.anything() },
|
||||
{ key: key2, createdAt: expect.anything() },
|
||||
{ key: key3, createdAt: expect.anything() },
|
||||
],
|
||||
});
|
||||
|
||||
store.removeKeys(['3', '4']);
|
||||
await store.removeKeys(['1']);
|
||||
|
||||
await expect(store.listKeys()).resolves.toEqual({
|
||||
items: [],
|
||||
});
|
||||
await expect(store.listKeys()).resolves.toEqual({
|
||||
items: [
|
||||
{ key: key2, createdAt: expect.anything() },
|
||||
{ key: key3, createdAt: expect.anything() },
|
||||
],
|
||||
});
|
||||
|
||||
await store.addKey(key1);
|
||||
await store.addKey(key2);
|
||||
await store.addKey(key3);
|
||||
await store.removeKeys(['1', '2']);
|
||||
|
||||
await expect(store.listKeys()).resolves.toEqual({
|
||||
items: [
|
||||
{ key: key1, createdAt: expect.anything() },
|
||||
{ key: key2, createdAt: expect.anything() },
|
||||
{ key: key3, createdAt: expect.anything() },
|
||||
],
|
||||
});
|
||||
await expect(store.listKeys()).resolves.toEqual({
|
||||
items: [{ key: key3, createdAt: expect.anything() }],
|
||||
});
|
||||
|
||||
store.removeKeys(['1', '2', '3']);
|
||||
await store.removeKeys([]);
|
||||
|
||||
await expect(store.listKeys()).resolves.toEqual({
|
||||
items: [],
|
||||
});
|
||||
});
|
||||
await expect(store.listKeys()).resolves.toEqual({
|
||||
items: [{ key: key3, createdAt: expect.anything() }],
|
||||
});
|
||||
|
||||
await store.removeKeys(['3', '4']);
|
||||
|
||||
await expect(store.listKeys()).resolves.toEqual({
|
||||
items: [],
|
||||
});
|
||||
|
||||
await store.addKey(key1);
|
||||
await store.addKey(key2);
|
||||
await store.addKey(key3);
|
||||
|
||||
await expect(store.listKeys()).resolves.toEqual({
|
||||
items: [
|
||||
{ key: key1, createdAt: expect.anything() },
|
||||
{ key: key2, createdAt: expect.anything() },
|
||||
{ key: key3, createdAt: expect.anything() },
|
||||
],
|
||||
});
|
||||
|
||||
await store.removeKeys(['1', '2', '3']);
|
||||
|
||||
await expect(store.listKeys()).resolves.toEqual({
|
||||
items: [],
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -14,19 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
PluginDatabaseManager,
|
||||
resolvePackagePath,
|
||||
} from '@backstage/backend-common';
|
||||
import { Knex } from 'knex';
|
||||
import { DateTime } from 'luxon';
|
||||
import { AnyJWK, KeyStore, StoredKey } from './types';
|
||||
|
||||
const migrationsDir = resolvePackagePath(
|
||||
'@backstage/plugin-auth-backend',
|
||||
'migrations',
|
||||
);
|
||||
|
||||
const TABLE = 'signing_keys';
|
||||
|
||||
type Row = {
|
||||
@@ -35,10 +26,6 @@ type Row = {
|
||||
key: string;
|
||||
};
|
||||
|
||||
type Options = {
|
||||
database: PluginDatabaseManager;
|
||||
};
|
||||
|
||||
const parseDate = (date: string | Date) => {
|
||||
const parsedDate =
|
||||
typeof date === 'string'
|
||||
@@ -55,24 +42,7 @@ const parseDate = (date: string | Date) => {
|
||||
};
|
||||
|
||||
export class DatabaseKeyStore implements KeyStore {
|
||||
static async create(options: Options): Promise<DatabaseKeyStore> {
|
||||
const { database } = options;
|
||||
const client = await database.getClient();
|
||||
|
||||
if (!database.migrations?.skip) {
|
||||
await client.migrate.latest({
|
||||
directory: migrationsDir,
|
||||
});
|
||||
}
|
||||
|
||||
return new DatabaseKeyStore(client);
|
||||
}
|
||||
|
||||
private readonly client: Knex;
|
||||
|
||||
private constructor(client: Knex) {
|
||||
this.client = client;
|
||||
}
|
||||
constructor(private readonly client: Knex) {}
|
||||
|
||||
async addKey(key: AnyJWK): Promise<void> {
|
||||
await this.client<Row>(TABLE).insert({
|
||||
|
||||
@@ -14,13 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { DatabaseManager } from '@backstage/backend-common';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
|
||||
import { MemoryKeyStore } from './MemoryKeyStore';
|
||||
import { AuthDatabase } from '../database/AuthDatabase';
|
||||
import { DatabaseKeyStore } from './DatabaseKeyStore';
|
||||
import { FirestoreKeyStore } from './FirestoreKeyStore';
|
||||
import { KeyStores } from './KeyStores';
|
||||
import { MemoryKeyStore } from './MemoryKeyStore';
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
|
||||
describe('KeyStores', () => {
|
||||
const defaultConfigOptions = {
|
||||
@@ -34,7 +34,10 @@ describe('KeyStores', () => {
|
||||
|
||||
it('reads auth section from config', async () => {
|
||||
const configSpy = jest.spyOn(defaultConfig, 'getOptionalConfig');
|
||||
const keyStore = await KeyStores.fromConfig(defaultConfig);
|
||||
const keyStore = await KeyStores.fromConfig(defaultConfig, {
|
||||
logger: getVoidLogger(),
|
||||
database: AuthDatabase.forTesting(),
|
||||
});
|
||||
|
||||
expect(keyStore).toBeInstanceOf(MemoryKeyStore);
|
||||
expect(configSpy).toHaveBeenCalledWith('auth.keyStore');
|
||||
@@ -46,18 +49,10 @@ describe('KeyStores', () => {
|
||||
});
|
||||
|
||||
it('can handle without auth config', async () => {
|
||||
const config = new ConfigReader({
|
||||
backend: {
|
||||
database: {
|
||||
client: 'better-sqlite3',
|
||||
connection: ':memory:',
|
||||
},
|
||||
},
|
||||
const keyStore = await KeyStores.fromConfig(new ConfigReader({}), {
|
||||
logger: getVoidLogger(),
|
||||
database: AuthDatabase.forTesting(),
|
||||
});
|
||||
const database =
|
||||
DatabaseManager.fromConfig(config).forPlugin('auth-backend');
|
||||
const keyStore = await KeyStores.fromConfig(config, { database });
|
||||
|
||||
expect(keyStore).toBeInstanceOf(DatabaseKeyStore);
|
||||
});
|
||||
|
||||
@@ -82,7 +77,10 @@ describe('KeyStores', () => {
|
||||
},
|
||||
};
|
||||
const config = new ConfigReader(configOptions);
|
||||
const keyStore = await KeyStores.fromConfig(config);
|
||||
const keyStore = await KeyStores.fromConfig(config, {
|
||||
logger: getVoidLogger(),
|
||||
database: AuthDatabase.forTesting(),
|
||||
});
|
||||
|
||||
expect(keyStore).toBeInstanceOf(FirestoreKeyStore);
|
||||
expect(createSpy).toHaveBeenCalledWith(
|
||||
|
||||
@@ -14,20 +14,20 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Logger } from 'winston';
|
||||
import { pickBy } from 'lodash';
|
||||
import { Logger } from 'winston';
|
||||
|
||||
import { PluginDatabaseManager } from '@backstage/backend-common';
|
||||
import { Config } from '@backstage/config';
|
||||
|
||||
import { AuthDatabase } from '../database/AuthDatabase';
|
||||
import { DatabaseKeyStore } from './DatabaseKeyStore';
|
||||
import { MemoryKeyStore } from './MemoryKeyStore';
|
||||
import { FirestoreKeyStore } from './FirestoreKeyStore';
|
||||
import { MemoryKeyStore } from './MemoryKeyStore';
|
||||
import { KeyStore } from './types';
|
||||
|
||||
type Options = {
|
||||
logger?: Logger;
|
||||
database?: PluginDatabaseManager;
|
||||
logger: Logger;
|
||||
database: AuthDatabase;
|
||||
};
|
||||
|
||||
export class KeyStores {
|
||||
@@ -37,23 +37,16 @@ export class KeyStores {
|
||||
*
|
||||
* @returns a KeyStore store
|
||||
*/
|
||||
static async fromConfig(
|
||||
config: Config,
|
||||
options?: Options,
|
||||
): Promise<KeyStore> {
|
||||
const { logger, database } = options ?? {};
|
||||
static async fromConfig(config: Config, options: Options): Promise<KeyStore> {
|
||||
const { logger, database } = options;
|
||||
|
||||
const ks = config.getOptionalConfig('auth.keyStore');
|
||||
const provider = ks?.getOptionalString('provider') ?? 'database';
|
||||
|
||||
logger?.info(`Configuring "${provider}" as KeyStore provider`);
|
||||
logger.info(`Configuring "${provider}" as KeyStore provider`);
|
||||
|
||||
if (provider === 'database') {
|
||||
if (!database) {
|
||||
throw new Error('This KeyStore provider requires a database');
|
||||
}
|
||||
|
||||
return await DatabaseKeyStore.create({ database });
|
||||
return new DatabaseKeyStore(await database.get());
|
||||
}
|
||||
|
||||
if (provider === 'memory') {
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* 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 { TestDatabases } from '@backstage/backend-test-utils';
|
||||
import fs from 'fs';
|
||||
|
||||
const migrationsDir = `${__dirname}/../migrations`;
|
||||
const migrationsFiles = fs.readdirSync(migrationsDir).sort();
|
||||
|
||||
async function migrateUpOnce(knex: Knex): Promise<void> {
|
||||
await knex.migrate.up({ directory: migrationsDir });
|
||||
}
|
||||
|
||||
async function migrateDownOnce(knex: Knex): Promise<void> {
|
||||
await knex.migrate.down({ directory: migrationsDir });
|
||||
}
|
||||
|
||||
async function migrateUntilBefore(knex: Knex, target: string): Promise<void> {
|
||||
const index = migrationsFiles.indexOf(target);
|
||||
if (index === -1) {
|
||||
throw new Error(`Migration ${target} not found`);
|
||||
}
|
||||
for (let i = 0; i < index; i++) {
|
||||
await migrateUpOnce(knex);
|
||||
}
|
||||
}
|
||||
|
||||
jest.setTimeout(60_000);
|
||||
|
||||
describe('migrations', () => {
|
||||
const databases = TestDatabases.create({
|
||||
ids: ['MYSQL_8', 'POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'],
|
||||
});
|
||||
|
||||
it.each(databases.eachSupportedId())(
|
||||
'20230428155633_sessions.js, %p',
|
||||
async databaseId => {
|
||||
const knex = await databases.init(databaseId);
|
||||
|
||||
await migrateUntilBefore(knex, '20230428155633_sessions.js');
|
||||
await migrateUpOnce(knex);
|
||||
|
||||
// Ensure that large cookies are supported
|
||||
const data = `{"cookie":"${'a'.repeat(100_000)}"}`;
|
||||
await knex
|
||||
.insert({ sid: 'abc', expired: knex.fn.now(), sess: data })
|
||||
.into('sessions');
|
||||
await knex
|
||||
.insert({ sid: 'def', expired: knex.fn.now(), sess: data })
|
||||
.into('sessions');
|
||||
|
||||
await expect(knex('sessions').orderBy('sid', 'asc')).resolves.toEqual([
|
||||
{ sid: 'abc', expired: expect.anything(), sess: data },
|
||||
{ sid: 'def', expired: expect.anything(), sess: data },
|
||||
]);
|
||||
|
||||
await migrateDownOnce(knex);
|
||||
|
||||
await knex.destroy();
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -32,9 +32,11 @@ import { CatalogApi, CatalogClient } from '@backstage/catalog-client';
|
||||
import { Config } from '@backstage/config';
|
||||
import { createOidcRouter, TokenFactory, KeyStores } from '../identity';
|
||||
import session from 'express-session';
|
||||
import connectSessionKnex from 'connect-session-knex';
|
||||
import passport from 'passport';
|
||||
import { Minimatch } from 'minimatch';
|
||||
import { CatalogAuthResolverContext } from '../lib/resolvers';
|
||||
import { AuthDatabase } from '../database/AuthDatabase';
|
||||
|
||||
/** @public */
|
||||
export type ProviderFactories = { [s: string]: AuthProviderFactory };
|
||||
@@ -70,7 +72,11 @@ export async function createRouter(
|
||||
const appUrl = config.getString('app.baseUrl');
|
||||
const authUrl = await discovery.getExternalBaseUrl('auth');
|
||||
|
||||
const keyStore = await KeyStores.fromConfig(config, { logger, database });
|
||||
const authDb = AuthDatabase.create(database);
|
||||
const keyStore = await KeyStores.fromConfig(config, {
|
||||
logger,
|
||||
database: authDb,
|
||||
});
|
||||
const keyDurationSeconds = 3600;
|
||||
|
||||
const tokenIssuer = new TokenFactory({
|
||||
@@ -84,14 +90,18 @@ export async function createRouter(
|
||||
const secret = config.getOptionalString('auth.session.secret');
|
||||
if (secret) {
|
||||
router.use(cookieParser(secret));
|
||||
// TODO: Configure the server-side session storage. The default MemoryStore is not designed for production
|
||||
const enforceCookieSSL = authUrl.startsWith('https');
|
||||
const KnexSessionStore = connectSessionKnex(session);
|
||||
router.use(
|
||||
session({
|
||||
secret,
|
||||
saveUninitialized: false,
|
||||
resave: false,
|
||||
cookie: { secure: enforceCookieSSL ? 'auto' : false },
|
||||
store: new KnexSessionStore({
|
||||
createtable: false,
|
||||
knex: await authDb.get(),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
router.use(passport.initialize());
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# @backstage/plugin-azure-devops
|
||||
|
||||
## 0.3.0-next.2
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 877df261085: The getBuildRuns function now checks contains multiple comma-separated builds and splits them to send multiple requests for each and concatenates the results.
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/theme@0.3.0-next.0
|
||||
- @backstage/core-components@0.13.1-next.1
|
||||
- @backstage/plugin-catalog-react@1.6.0-next.2
|
||||
- @backstage/core-plugin-api@1.5.1
|
||||
|
||||
## 0.2.9-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -70,7 +70,7 @@ dev.azure.com/project: <project-name>
|
||||
dev.azure.com/build-definition: <build-definition-name>
|
||||
```
|
||||
|
||||
In this case `<project-name>` will be the name of your Team Project and `<build-definition-name>` will be the name of the Build Definition you would like to see Builds for. If the Build Definition name has spaces in it make sure to put quotes around it
|
||||
In this case `<project-name>` will be the name of your Team Project and `<build-definition-name>` will be the name of the Build Definition you would like to see Builds for, and it's possible to add more Builds separated by a comma. If the Build Definition name has spaces in it make sure to put quotes around it.
|
||||
|
||||
### Azure Pipelines Component
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-azure-devops",
|
||||
"version": "0.2.9-next.1",
|
||||
"version": "0.3.0-next.2",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -120,7 +120,23 @@ export class AzureDevOpsClient implements AzureDevOpsApi {
|
||||
queryString.append('repoName', repoName);
|
||||
}
|
||||
if (definitionName) {
|
||||
queryString.append('definitionName', definitionName);
|
||||
const definitionNames = definitionName.split(',');
|
||||
if (definitionNames.length > 1) {
|
||||
const buildRuns: BuildRun[] = [];
|
||||
for (const name of definitionNames) {
|
||||
queryString.set('definitionName', name.trim());
|
||||
if (options?.top) {
|
||||
queryString.set('top', options.top.toString());
|
||||
}
|
||||
const urlSegment = `builds/${encodeURIComponent(
|
||||
projectName,
|
||||
)}?${queryString}`;
|
||||
const items = await this.get<BuildRun[]>(urlSegment);
|
||||
buildRuns.push(...items);
|
||||
}
|
||||
return { items: buildRuns };
|
||||
}
|
||||
queryString.append('definitionName', definitionName.trim());
|
||||
}
|
||||
if (options?.top) {
|
||||
queryString.append('top', options.top.toString());
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# @backstage/plugin-azure-sites
|
||||
|
||||
## 0.1.7-next.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/theme@0.3.0-next.0
|
||||
- @backstage/core-components@0.13.1-next.1
|
||||
- @backstage/plugin-catalog-react@1.6.0-next.2
|
||||
- @backstage/core-plugin-api@1.5.1
|
||||
|
||||
## 0.1.7-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-azure-sites",
|
||||
"version": "0.1.7-next.1",
|
||||
"version": "0.1.7-next.2",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
# @backstage/plugin-badges-backend
|
||||
|
||||
## 0.2.0-next.2
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- a0108c49774: Fixing badges-backend plugin to get a token from the TokenManager instead of parsing the request header. Hence, it's now possible to disable the authMiddleware for the badges-backend plugin to expose publicly the badges.
|
||||
|
||||
Implementing an obfuscation feature to protect an open badges endpoint from being enumerated. The feature is disabled by default and the change is compatible with the previous version.
|
||||
|
||||
**BREAKING**: `createRouter` now require that `tokenManager`, `logger`, `identityApi` and `database`, are passed in as options.
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/config@1.0.7
|
||||
|
||||
## 0.1.39-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/plugin-badges-backend",
|
||||
"description": "A Backstage backend plugin that generates README badges for your entities",
|
||||
"version": "0.1.39-next.1",
|
||||
"version": "0.2.0-next.2",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# @backstage/plugin-badges
|
||||
|
||||
## 0.2.42-next.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- a0108c49774: Fixing badges-backend plugin to get a token from the TokenManager instead of parsing the request header. Hence, it's now possible to disable the authMiddleware for the badges-backend plugin to expose publicly the badges.
|
||||
|
||||
Implementing an obfuscation feature to protect an open badges endpoint from being enumerated. The feature is disabled by default and the change is compatible with the previous version.
|
||||
|
||||
**BREAKING**: `createRouter` now require that `tokenManager`, `logger`, `identityApi` and `database`, are passed in as options.
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/theme@0.3.0-next.0
|
||||
- @backstage/core-components@0.13.1-next.1
|
||||
- @backstage/plugin-catalog-react@1.6.0-next.2
|
||||
- @backstage/core-plugin-api@1.5.1
|
||||
|
||||
## 0.2.42-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/plugin-badges",
|
||||
"description": "A Backstage plugin that generates README badges for your entities",
|
||||
"version": "0.2.42-next.1",
|
||||
"version": "0.2.42-next.2",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
# @backstage/plugin-bazaar
|
||||
|
||||
## 0.2.8-next.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 900880ab7c3: Fixed `validateDOMNesting` warnings
|
||||
- Updated dependencies
|
||||
- @backstage/theme@0.3.0-next.0
|
||||
- @backstage/core-components@0.13.1-next.1
|
||||
- @backstage/cli@0.22.7-next.0
|
||||
- @backstage/plugin-catalog@1.11.0-next.2
|
||||
- @backstage/plugin-catalog-react@1.6.0-next.2
|
||||
- @backstage/core-plugin-api@1.5.1
|
||||
|
||||
## 0.2.8-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-bazaar",
|
||||
"version": "0.2.8-next.1",
|
||||
"version": "0.2.8-next.2",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# @backstage/plugin-bitrise
|
||||
|
||||
## 0.1.45-next.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/theme@0.3.0-next.0
|
||||
- @backstage/core-components@0.13.1-next.1
|
||||
- @backstage/plugin-catalog-react@1.6.0-next.2
|
||||
- @backstage/core-plugin-api@1.5.1
|
||||
|
||||
## 0.1.45-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/plugin-bitrise",
|
||||
"description": "A Backstage plugin that integrates towards Bitrise",
|
||||
"version": "0.1.45-next.1",
|
||||
"version": "0.1.45-next.2",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @backstage/plugin-catalog-backend-module-aws
|
||||
|
||||
## 0.1.19-next.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/plugin-kubernetes-common@0.6.3-next.0
|
||||
- @backstage/config@1.0.7
|
||||
|
||||
## 0.1.19-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/plugin-catalog-backend-module-aws",
|
||||
"description": "A Backstage catalog backend module that helps integrate towards AWS",
|
||||
"version": "0.1.19-next.1",
|
||||
"version": "0.1.19-next.2",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
# @backstage/plugin-catalog-backend-module-github
|
||||
|
||||
## 0.3.0-next.2
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 970678adbe2: Implement events support for `GithubMultiOrgEntityProvider`
|
||||
|
||||
**BREAKING:** Passing in a custom `teamTransformer` will now correctly completely override the default transformer behavior
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- bd101cefd37: Updated the `team.edited` event emitted from `GithubOrgEntityProvider` to also include teams description.
|
||||
- Updated dependencies
|
||||
- @backstage/plugin-catalog-backend@1.9.1-next.2
|
||||
- @backstage/config@1.0.7
|
||||
|
||||
## 0.2.8-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -10,6 +10,7 @@ import { Config } from '@backstage/config';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { EntityProvider } from '@backstage/plugin-catalog-node';
|
||||
import { EntityProviderConnection } from '@backstage/plugin-catalog-node';
|
||||
import { EventBroker } from '@backstage/plugin-events-node';
|
||||
import { EventParams } from '@backstage/plugin-events-node';
|
||||
import { EventSubscriber } from '@backstage/plugin-events-node';
|
||||
import { GithubCredentialsProvider } from '@backstage/integration';
|
||||
@@ -158,6 +159,7 @@ export class GithubMultiOrgEntityProvider implements EntityProvider {
|
||||
|
||||
// @public
|
||||
export interface GithubMultiOrgEntityProviderOptions {
|
||||
eventBroker?: EventBroker;
|
||||
githubCredentialsProvider?: GithubCredentialsProvider;
|
||||
githubUrl: string;
|
||||
id: string;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/plugin-catalog-backend-module-github",
|
||||
"description": "A Backstage catalog backend module that helps integrate towards GitHub",
|
||||
"version": "0.2.8-next.1",
|
||||
"version": "0.3.0-next.2",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -21,6 +21,7 @@ import { graphql as graphqlMsw } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
import { TeamTransformer, UserTransformer } from './defaultTransformers';
|
||||
import {
|
||||
getOrganizationsFromUser,
|
||||
getOrganizationTeams,
|
||||
getOrganizationUsers,
|
||||
getTeamMembers,
|
||||
@@ -448,6 +449,37 @@ describe('github', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getOrganizationsFromUser', () => {
|
||||
it('reads orgs from user', async () => {
|
||||
const input: QueryResponse = {
|
||||
user: {
|
||||
organizations: {
|
||||
pageInfo: { hasNextPage: false },
|
||||
nodes: [
|
||||
{
|
||||
login: 'a',
|
||||
},
|
||||
{
|
||||
login: 'b',
|
||||
},
|
||||
{
|
||||
login: 'c',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
server.use(
|
||||
graphqlMsw.query('orgs', (_req, res, ctx) => res(ctx.data(input))),
|
||||
);
|
||||
|
||||
await expect(getOrganizationsFromUser(graphql, 'foo')).resolves.toEqual({
|
||||
orgs: ['a', 'b', 'c'],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getTeamMembers', () => {
|
||||
it('reads team members', async () => {
|
||||
const input: QueryResponse = {
|
||||
|
||||
@@ -33,6 +33,7 @@ import { DeferredEntity } from '@backstage/plugin-catalog-node';
|
||||
export type QueryResponse = {
|
||||
organization?: OrganizationResponse;
|
||||
repositoryOwner?: RepositoryOwnerResponse;
|
||||
user?: UserResponse;
|
||||
};
|
||||
|
||||
type RepositoryOwnerResponse = {
|
||||
@@ -46,11 +47,19 @@ export type OrganizationResponse = {
|
||||
repositories?: Connection<RepositoryResponse>;
|
||||
};
|
||||
|
||||
export type UserResponse = {
|
||||
organizations?: Connection<GithubOrg>;
|
||||
};
|
||||
|
||||
export type PageInfo = {
|
||||
hasNextPage: boolean;
|
||||
endCursor?: string;
|
||||
};
|
||||
|
||||
export type GithubOrg = {
|
||||
login: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Github User
|
||||
*
|
||||
@@ -334,6 +343,34 @@ export async function getOrganizationTeamsFromUsers(
|
||||
return { groups };
|
||||
}
|
||||
|
||||
export async function getOrganizationsFromUser(
|
||||
client: typeof graphql,
|
||||
user: string,
|
||||
): Promise<{
|
||||
orgs: string[];
|
||||
}> {
|
||||
const query = `
|
||||
query orgs($user: String!) {
|
||||
user(login: $user) {
|
||||
organizations(first: 100) {
|
||||
nodes { login }
|
||||
pageInfo { hasNextPage, endCursor }
|
||||
}
|
||||
}
|
||||
}`;
|
||||
|
||||
const orgs = await queryWithPaging(
|
||||
client,
|
||||
query,
|
||||
'',
|
||||
r => r.user?.organizations,
|
||||
async o => o.login,
|
||||
{ user },
|
||||
);
|
||||
|
||||
return { orgs };
|
||||
}
|
||||
|
||||
export async function getOrganizationTeam(
|
||||
client: typeof graphql,
|
||||
org: string,
|
||||
|
||||
+1243
-22
File diff suppressed because it is too large
Load Diff
+540
-3
@@ -21,7 +21,9 @@ import {
|
||||
DEFAULT_NAMESPACE,
|
||||
Entity,
|
||||
GroupEntity,
|
||||
parseEntityRef,
|
||||
stringifyEntityRef,
|
||||
UserEntity,
|
||||
} from '@backstage/catalog-model';
|
||||
import { Config } from '@backstage/config';
|
||||
import {
|
||||
@@ -32,10 +34,24 @@ import {
|
||||
ScmIntegrations,
|
||||
} from '@backstage/integration';
|
||||
import {
|
||||
DeferredEntity,
|
||||
EntityProvider,
|
||||
EntityProviderConnection,
|
||||
} from '@backstage/plugin-catalog-node';
|
||||
import { EventBroker, EventParams } from '@backstage/plugin-events-node';
|
||||
import { graphql } from '@octokit/graphql';
|
||||
import {
|
||||
InstallationCreatedEvent,
|
||||
InstallationEvent,
|
||||
OrganizationEvent,
|
||||
OrganizationMemberAddedEvent,
|
||||
OrganizationMemberRemovedEvent,
|
||||
MembershipEvent,
|
||||
TeamCreatedEvent,
|
||||
TeamDeletedEvent,
|
||||
TeamEditedEvent,
|
||||
TeamEvent,
|
||||
} from '@octokit/webhooks-types';
|
||||
import { merge } from 'lodash';
|
||||
import * as uuid from 'uuid';
|
||||
import { Logger } from 'winston';
|
||||
@@ -44,6 +60,7 @@ import {
|
||||
assignGroupsToUsers,
|
||||
buildOrgHierarchy,
|
||||
defaultOrganizationTeamTransformer,
|
||||
defaultUserTransformer,
|
||||
getOrganizationTeams,
|
||||
getOrganizationUsers,
|
||||
GithubTeam,
|
||||
@@ -55,6 +72,11 @@ import {
|
||||
ANNOTATION_GITHUB_TEAM_SLUG,
|
||||
ANNOTATION_GITHUB_USER_LOGIN,
|
||||
} from '../lib/annotation';
|
||||
import {
|
||||
getOrganizationsFromUser,
|
||||
getOrganizationTeam,
|
||||
getOrganizationTeamsFromUsers,
|
||||
} from '../lib/github';
|
||||
import { splitTeamSlug } from '../lib/util';
|
||||
|
||||
/**
|
||||
@@ -118,8 +140,18 @@ export interface GithubMultiOrgEntityProviderOptions {
|
||||
* By default groups will be namespaced according to their GitHub org.
|
||||
*/
|
||||
teamTransformer?: TeamTransformer;
|
||||
|
||||
/**
|
||||
* An EventBroker to subscribe this provider to GitHub events to trigger delta mutations
|
||||
*/
|
||||
eventBroker?: EventBroker;
|
||||
}
|
||||
|
||||
type CreateDeltaOperation = (entities: Entity[]) => {
|
||||
added: DeferredEntity[];
|
||||
removed: DeferredEntity[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Ingests org data (users and groups) from GitHub.
|
||||
*
|
||||
@@ -161,6 +193,13 @@ export class GithubMultiOrgEntityProvider implements EntityProvider {
|
||||
|
||||
provider.schedule(options.schedule);
|
||||
|
||||
if (options.eventBroker) {
|
||||
options.eventBroker.subscribe({
|
||||
supportsEventTopics: provider.supportsEventTopics.bind(provider),
|
||||
onEvent: provider.onEvent.bind(provider),
|
||||
});
|
||||
}
|
||||
|
||||
return provider;
|
||||
}
|
||||
|
||||
@@ -271,6 +310,476 @@ export class GithubMultiOrgEntityProvider implements EntityProvider {
|
||||
markCommitComplete();
|
||||
}
|
||||
|
||||
private supportsEventTopics(): string[] {
|
||||
return [
|
||||
'github.installation',
|
||||
'github.organization',
|
||||
'github.team',
|
||||
'github.membership',
|
||||
];
|
||||
}
|
||||
|
||||
private async onEvent(params: EventParams): Promise<void> {
|
||||
const { logger } = this.options;
|
||||
logger.debug(`Received event from ${params.topic}`);
|
||||
|
||||
const orgs = this.options.orgs?.length
|
||||
? this.options.orgs
|
||||
: await this.getAllOrgs(this.options.gitHubConfig);
|
||||
|
||||
const eventPayload = params.eventPayload as
|
||||
| InstallationEvent
|
||||
| OrganizationEvent
|
||||
| MembershipEvent
|
||||
| TeamEvent;
|
||||
|
||||
if (
|
||||
!orgs.includes(
|
||||
(eventPayload as InstallationEvent).installation?.account?.login,
|
||||
) &&
|
||||
!orgs.includes(
|
||||
(eventPayload as OrganizationEvent | MembershipEvent | TeamEvent)
|
||||
.organization?.login,
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// https://docs.github.com/webhooks-and-events/webhooks/webhook-events-and-payloads#installation
|
||||
if (
|
||||
params.topic.includes('installation') &&
|
||||
eventPayload.action === 'created'
|
||||
) {
|
||||
// We can only respond to installation.created events to add new users/groups since a
|
||||
// installation.deleted event won't provide us info on what user/groups we should remove and
|
||||
// we can't query the uninstalled org since we will no longer have access. This will need to be
|
||||
// eventually resolved via occasional full mutation runs by calling read()
|
||||
await this.onInstallationChange(
|
||||
eventPayload as InstallationCreatedEvent,
|
||||
orgs,
|
||||
);
|
||||
}
|
||||
|
||||
// https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#organization
|
||||
if (
|
||||
params.topic.includes('organization') &&
|
||||
(eventPayload.action === 'member_added' ||
|
||||
eventPayload.action === 'member_removed')
|
||||
) {
|
||||
await this.onMemberChangeInOrganization(eventPayload, orgs);
|
||||
}
|
||||
|
||||
// https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#team
|
||||
if (params.topic.includes('team')) {
|
||||
if (
|
||||
eventPayload.action === 'created' ||
|
||||
eventPayload.action === 'deleted'
|
||||
) {
|
||||
await this.onTeamChangeInOrganization(
|
||||
eventPayload as TeamCreatedEvent | TeamDeletedEvent,
|
||||
);
|
||||
} else if (eventPayload.action === 'edited') {
|
||||
await this.onTeamEditedInOrganization(
|
||||
eventPayload as TeamEditedEvent,
|
||||
orgs,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#membership
|
||||
if (params.topic.includes('membership')) {
|
||||
this.onMembershipChangedInTeam(eventPayload as MembershipEvent, orgs);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
private async onInstallationChange(
|
||||
event: InstallationCreatedEvent,
|
||||
applicableOrgs: string[],
|
||||
) {
|
||||
if (!this.connection) {
|
||||
throw new Error('Not initialized');
|
||||
}
|
||||
|
||||
const org = event.installation.account.login;
|
||||
const { headers, type: tokenType } =
|
||||
await this.options.githubCredentialsProvider.getCredentials({
|
||||
url: `${this.options.githubUrl}/${org}`,
|
||||
});
|
||||
const client = graphql.defaults({
|
||||
baseUrl: this.options.gitHubConfig.apiBaseUrl,
|
||||
headers,
|
||||
});
|
||||
|
||||
const { users } = await getOrganizationUsers(
|
||||
client,
|
||||
org,
|
||||
tokenType,
|
||||
this.options.userTransformer,
|
||||
);
|
||||
|
||||
const { groups } = await getOrganizationTeams(
|
||||
client,
|
||||
org,
|
||||
this.defaultMultiOrgTeamTransformer.bind(this),
|
||||
);
|
||||
|
||||
// Fetch group memberships of users in case they already exist and
|
||||
// have memberships in groups from other applicable orgs
|
||||
for (const userOrg of applicableOrgs) {
|
||||
const { headers: orgHeaders } =
|
||||
await this.options.githubCredentialsProvider.getCredentials({
|
||||
url: `${this.options.githubUrl}/${userOrg}`,
|
||||
});
|
||||
const orgClient = graphql.defaults({
|
||||
baseUrl: this.options.gitHubConfig.apiBaseUrl,
|
||||
headers: orgHeaders,
|
||||
});
|
||||
|
||||
const { groups: userGroups } = await getOrganizationTeamsFromUsers(
|
||||
orgClient,
|
||||
userOrg,
|
||||
users.map(
|
||||
u =>
|
||||
u.metadata.annotations?.[ANNOTATION_GITHUB_USER_LOGIN] ||
|
||||
u.metadata.name,
|
||||
),
|
||||
this.defaultMultiOrgTeamTransformer.bind(this),
|
||||
);
|
||||
|
||||
assignGroupsToUsers(users, userGroups);
|
||||
}
|
||||
|
||||
const { added, removed } = this.createAddEntitiesOperation([
|
||||
...users,
|
||||
...groups,
|
||||
]);
|
||||
await this.connection.applyMutation({
|
||||
type: 'delta',
|
||||
removed,
|
||||
added,
|
||||
});
|
||||
}
|
||||
|
||||
private async onMemberChangeInOrganization(
|
||||
event: OrganizationMemberAddedEvent | OrganizationMemberRemovedEvent,
|
||||
applicableOrgs: string[],
|
||||
) {
|
||||
if (!this.connection) {
|
||||
throw new Error('Not initialized');
|
||||
}
|
||||
|
||||
const userTransformer =
|
||||
this.options.userTransformer || defaultUserTransformer;
|
||||
const { name, avatar_url: avatarUrl, email, login } = event.membership.user;
|
||||
const org = event.organization.login;
|
||||
const { headers } =
|
||||
await this.options.githubCredentialsProvider.getCredentials({
|
||||
url: `${this.options.githubUrl}/${org}`,
|
||||
});
|
||||
const client = graphql.defaults({
|
||||
baseUrl: this.options.gitHubConfig.apiBaseUrl,
|
||||
headers,
|
||||
});
|
||||
|
||||
const { orgs } = await getOrganizationsFromUser(client, login);
|
||||
const userApplicableOrgs = orgs.filter(o => applicableOrgs.includes(o));
|
||||
|
||||
let updateMemberships: boolean;
|
||||
let createDeltaOperation: CreateDeltaOperation;
|
||||
if (event.action === 'member_removed') {
|
||||
if (userApplicableOrgs.length) {
|
||||
// If the user is still associated with another applicable org then we don't want to remove
|
||||
// them, just update the entity to remove any potential group memberships from the old org
|
||||
createDeltaOperation = this.createAddEntitiesOperation.bind(this);
|
||||
updateMemberships = true;
|
||||
} else {
|
||||
// User is no longer part of any applicable orgs so we can remove it,
|
||||
// no need to take memberships into account
|
||||
createDeltaOperation = this.createRemoveEntitiesOperation.bind(this);
|
||||
updateMemberships = false;
|
||||
}
|
||||
} else {
|
||||
// We're not sure if this user was already added as part of another applicable org
|
||||
// so grab the latest memberships (potentially from teams of other orgs) to ensure
|
||||
// we're not accidentally omitting them
|
||||
createDeltaOperation = this.createAddEntitiesOperation.bind(this);
|
||||
updateMemberships = true;
|
||||
}
|
||||
|
||||
const user = (await userTransformer(
|
||||
{
|
||||
name,
|
||||
avatarUrl,
|
||||
login,
|
||||
email: email ?? undefined,
|
||||
},
|
||||
{
|
||||
org,
|
||||
client,
|
||||
query: '',
|
||||
},
|
||||
)) as UserEntity;
|
||||
|
||||
if (updateMemberships) {
|
||||
for (const userOrg of userApplicableOrgs) {
|
||||
const { headers: orgHeaders } =
|
||||
await this.options.githubCredentialsProvider.getCredentials({
|
||||
url: `${this.options.githubUrl}/${userOrg}`,
|
||||
});
|
||||
const orgClient = graphql.defaults({
|
||||
baseUrl: this.options.gitHubConfig.apiBaseUrl,
|
||||
headers: orgHeaders,
|
||||
});
|
||||
|
||||
const { groups } = await getOrganizationTeamsFromUsers(
|
||||
orgClient,
|
||||
userOrg,
|
||||
[login],
|
||||
this.defaultMultiOrgTeamTransformer.bind(this),
|
||||
);
|
||||
|
||||
assignGroupsToUsers([user], groups);
|
||||
}
|
||||
}
|
||||
|
||||
const { added, removed } = createDeltaOperation([user]);
|
||||
await this.connection.applyMutation({
|
||||
type: 'delta',
|
||||
removed,
|
||||
added,
|
||||
});
|
||||
}
|
||||
|
||||
private async onTeamChangeInOrganization(
|
||||
event: TeamCreatedEvent | TeamDeletedEvent,
|
||||
) {
|
||||
if (!this.connection) {
|
||||
throw new Error('Not initialized');
|
||||
}
|
||||
|
||||
const org = event.organization.login;
|
||||
const { headers } =
|
||||
await this.options.githubCredentialsProvider.getCredentials({
|
||||
url: `${this.options.githubUrl}/${org}`,
|
||||
});
|
||||
const client = graphql.defaults({
|
||||
baseUrl: this.options.gitHubConfig.apiBaseUrl,
|
||||
headers,
|
||||
});
|
||||
|
||||
const { name, html_url: url, description, slug } = event.team;
|
||||
const group = (await this.defaultMultiOrgTeamTransformer(
|
||||
{
|
||||
name,
|
||||
slug,
|
||||
editTeamUrl: `${url}/edit`,
|
||||
combinedSlug: `${org}/${slug}`,
|
||||
description: description ?? undefined,
|
||||
parentTeam: { slug: event.team?.parent?.slug || '' } as GithubTeam,
|
||||
// entity will be removed or is new
|
||||
members: [],
|
||||
},
|
||||
{
|
||||
org,
|
||||
client,
|
||||
query: '',
|
||||
},
|
||||
)) as Entity;
|
||||
|
||||
const createDeltaOperation =
|
||||
event.action === 'created'
|
||||
? this.createAddEntitiesOperation.bind(this)
|
||||
: this.createRemoveEntitiesOperation.bind(this);
|
||||
const { added, removed } = createDeltaOperation([group]);
|
||||
|
||||
await this.connection.applyMutation({
|
||||
type: 'delta',
|
||||
removed,
|
||||
added,
|
||||
});
|
||||
}
|
||||
|
||||
private async onTeamEditedInOrganization(
|
||||
event: TeamEditedEvent,
|
||||
applicableOrgs: string[],
|
||||
) {
|
||||
if (!this.connection) {
|
||||
throw new Error('Not initialized');
|
||||
}
|
||||
|
||||
const org = event.organization.login;
|
||||
const { headers, type: tokenType } =
|
||||
await this.options.githubCredentialsProvider.getCredentials({
|
||||
url: `${this.options.githubUrl}/${org}`,
|
||||
});
|
||||
const client = graphql.defaults({
|
||||
baseUrl: this.options.gitHubConfig.apiBaseUrl,
|
||||
headers,
|
||||
});
|
||||
|
||||
const teamSlug = event.team.slug;
|
||||
const { group } = await getOrganizationTeam(
|
||||
client,
|
||||
org,
|
||||
teamSlug,
|
||||
this.defaultMultiOrgTeamTransformer.bind(this),
|
||||
);
|
||||
|
||||
const { users } = await getOrganizationUsers(
|
||||
client,
|
||||
org,
|
||||
tokenType,
|
||||
this.options.userTransformer,
|
||||
);
|
||||
|
||||
const usersFromChangedGroup =
|
||||
group.spec.members?.map(m =>
|
||||
stringifyEntityRef(parseEntityRef(m, { defaultKind: 'user' })),
|
||||
) || [];
|
||||
const usersToRebuild = users.filter(u =>
|
||||
usersFromChangedGroup.includes(stringifyEntityRef(u)),
|
||||
);
|
||||
|
||||
// Update memberships of associated members of this group in case the group entity ref changed
|
||||
for (const userOrg of applicableOrgs) {
|
||||
const { headers: orgHeaders } =
|
||||
await this.options.githubCredentialsProvider.getCredentials({
|
||||
url: `${this.options.githubUrl}/${userOrg}`,
|
||||
});
|
||||
const orgClient = graphql.defaults({
|
||||
baseUrl: this.options.gitHubConfig.apiBaseUrl,
|
||||
headers: orgHeaders,
|
||||
});
|
||||
|
||||
const { groups } = await getOrganizationTeamsFromUsers(
|
||||
orgClient,
|
||||
userOrg,
|
||||
usersToRebuild.map(
|
||||
u =>
|
||||
u.metadata.annotations?.[ANNOTATION_GITHUB_USER_LOGIN] ||
|
||||
u.metadata.name,
|
||||
),
|
||||
this.defaultMultiOrgTeamTransformer.bind(this),
|
||||
);
|
||||
|
||||
assignGroupsToUsers(usersToRebuild, groups);
|
||||
}
|
||||
|
||||
const oldName = event.changes.name?.from || '';
|
||||
const oldSlug = oldName.toLowerCase().replaceAll(/\s/gi, '-');
|
||||
const oldGroup = (await this.defaultMultiOrgTeamTransformer(
|
||||
{
|
||||
name: event.changes.name?.from,
|
||||
slug: oldSlug,
|
||||
combinedSlug: `${org}/${oldSlug}`,
|
||||
description: event.changes.description?.from,
|
||||
parentTeam: { slug: event.team?.parent?.slug || '' } as GithubTeam,
|
||||
// entity will be removed
|
||||
members: [],
|
||||
},
|
||||
{
|
||||
org,
|
||||
client,
|
||||
query: '',
|
||||
},
|
||||
)) as Entity;
|
||||
|
||||
// Remove the old group entity in case the entity ref is now different
|
||||
const { removed } = this.createRemoveEntitiesOperation([oldGroup]);
|
||||
const { added } = this.createAddEntitiesOperation([
|
||||
...usersToRebuild,
|
||||
group,
|
||||
]);
|
||||
await this.connection.applyMutation({
|
||||
type: 'delta',
|
||||
removed,
|
||||
added,
|
||||
});
|
||||
}
|
||||
|
||||
private async onMembershipChangedInTeam(
|
||||
event: MembershipEvent,
|
||||
applicableOrgs: string[],
|
||||
) {
|
||||
if (!this.connection) {
|
||||
throw new Error('Not initialized');
|
||||
}
|
||||
|
||||
// The docs are saying I will receive the slug for the removed event,
|
||||
// but the types don't reflect that,
|
||||
// so I will just check to be sure the slug is there
|
||||
// https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#membership
|
||||
if (!('slug' in event.team)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const org = event.organization.login;
|
||||
const { headers } =
|
||||
await this.options.githubCredentialsProvider.getCredentials({
|
||||
url: `${this.options.githubUrl}/${org}`,
|
||||
});
|
||||
const client = graphql.defaults({
|
||||
baseUrl: this.options.gitHubConfig.apiBaseUrl,
|
||||
headers,
|
||||
});
|
||||
|
||||
const teamSlug = event.team.slug;
|
||||
const { group } = await getOrganizationTeam(
|
||||
client,
|
||||
org,
|
||||
teamSlug,
|
||||
this.defaultMultiOrgTeamTransformer.bind(this),
|
||||
);
|
||||
|
||||
const userTransformer =
|
||||
this.options.userTransformer || defaultUserTransformer;
|
||||
const { name, avatar_url: avatarUrl, email, login } = event.member;
|
||||
const user = (await userTransformer(
|
||||
{
|
||||
name,
|
||||
avatarUrl,
|
||||
login,
|
||||
email: email ?? undefined,
|
||||
},
|
||||
{
|
||||
org,
|
||||
client,
|
||||
query: '',
|
||||
},
|
||||
)) as UserEntity;
|
||||
|
||||
const { orgs } = await getOrganizationsFromUser(client, login);
|
||||
const userApplicableOrgs = orgs.filter(o => applicableOrgs.includes(o));
|
||||
for (const userOrg of userApplicableOrgs) {
|
||||
const { headers: orgHeaders } =
|
||||
await this.options.githubCredentialsProvider.getCredentials({
|
||||
url: `${this.options.githubUrl}/${userOrg}`,
|
||||
});
|
||||
const orgClient = graphql.defaults({
|
||||
baseUrl: this.options.gitHubConfig.apiBaseUrl,
|
||||
headers: orgHeaders,
|
||||
});
|
||||
|
||||
const { groups } = await getOrganizationTeamsFromUsers(
|
||||
orgClient,
|
||||
userOrg,
|
||||
[login],
|
||||
this.defaultMultiOrgTeamTransformer.bind(this),
|
||||
);
|
||||
|
||||
assignGroupsToUsers([user], groups);
|
||||
}
|
||||
|
||||
const { added, removed } = this.createAddEntitiesOperation([user, group]);
|
||||
await this.connection.applyMutation({
|
||||
type: 'delta',
|
||||
removed,
|
||||
added,
|
||||
});
|
||||
}
|
||||
|
||||
private schedule(schedule: GithubMultiOrgEntityProviderOptions['schedule']) {
|
||||
if (!schedule || schedule === 'manual') {
|
||||
return;
|
||||
@@ -301,9 +810,11 @@ export class GithubMultiOrgEntityProvider implements EntityProvider {
|
||||
team: GithubTeam,
|
||||
ctx: TransformerContext,
|
||||
): Promise<GroupEntity | undefined> {
|
||||
const result = this.options.teamTransformer
|
||||
? await this.options.teamTransformer(team, ctx)
|
||||
: await defaultOrganizationTeamTransformer(team, ctx);
|
||||
if (this.options.teamTransformer) {
|
||||
return await this.options.teamTransformer(team, ctx);
|
||||
}
|
||||
|
||||
const result = await defaultOrganizationTeamTransformer(team, ctx);
|
||||
|
||||
if (result) {
|
||||
result.metadata.namespace = ctx.org.toLocaleLowerCase('en-US');
|
||||
@@ -333,6 +844,32 @@ export class GithubMultiOrgEntityProvider implements EntityProvider {
|
||||
)
|
||||
.filter(Boolean) as string[];
|
||||
}
|
||||
|
||||
private createAddEntitiesOperation(entities: Entity[]) {
|
||||
return {
|
||||
removed: [],
|
||||
added: entities.map(entity => ({
|
||||
locationKey: `github-multi-org-provider:${this.options.id}`,
|
||||
entity: withLocations(
|
||||
`https://${this.options.gitHubConfig.host}`,
|
||||
entity,
|
||||
),
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
private createRemoveEntitiesOperation(entities: Entity[]) {
|
||||
return {
|
||||
added: [],
|
||||
removed: entities.map(entity => ({
|
||||
locationKey: `github-multi-org-provider:${this.options.id}`,
|
||||
entity: withLocations(
|
||||
`https://${this.options.gitHubConfig.host}`,
|
||||
entity,
|
||||
),
|
||||
})),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Helps wrap the timing and logging behaviors
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @backstage/plugin-catalog-backend-module-incremental-ingestion
|
||||
|
||||
## 0.3.2-next.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/plugin-catalog-backend@1.9.1-next.2
|
||||
- @backstage/config@1.0.7
|
||||
|
||||
## 0.3.2-next.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user