Merge branch 'master' into add-global-pg-plugin-schema-flag
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/dev-utils': patch
|
||||
---
|
||||
|
||||
Add theme switcher to sidebar of dev app.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-kubernetes': patch
|
||||
---
|
||||
|
||||
The Kubernetes plugin will now re-fetch the kubernetes objects every ten seconds (not current configurable), this allows users to track the progress of deployments without refreshing the browser.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-sentry': patch
|
||||
---
|
||||
|
||||
feature: sentry-plugin allows passing search query for listing issues
|
||||
@@ -1,9 +0,0 @@
|
||||
---
|
||||
'@backstage/test-utils': patch
|
||||
'@backstage/test-utils-core': patch
|
||||
---
|
||||
|
||||
Migrates all utility methods from `test-utils-core` into `test-utils` and delete exports from the old package.
|
||||
This should have no impact since this package is considered internal and have no usages outside core packages.
|
||||
|
||||
Notable changes are that the testing tool `msw.setupDefaultHandlers()` have been deprecated in favour of `setupRequestMockHandlers()`.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-techdocs': patch
|
||||
---
|
||||
|
||||
Reader will now scroll to the top of the page when navigating between pages
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/catalog-client': patch
|
||||
---
|
||||
|
||||
Update to the right version of @backstage/errors
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
'@backstage/plugin-gcp-projects': patch
|
||||
---
|
||||
|
||||
UI updates to GCP-projects plugin
|
||||
|
||||
Adds the following to the project list page:
|
||||
|
||||
- pagination
|
||||
- filtering
|
||||
- sorting
|
||||
- rows per page
|
||||
- show/hide columns
|
||||
|
||||
Makes breadcrumb a link back to project list for the project details and new project views.
|
||||
|
||||
In project list page, updates New project button to use RouterLink instead of `href` to avoid login prompt.
|
||||
|
||||
In project details view, links to project details and logs now work, clicking on these will open the project or logs in GCP in new tab.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
remove double config dep
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder': patch
|
||||
---
|
||||
|
||||
Surfacing all components of the ScaffolderPage outside of the plugin so you can customize the page
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/dev-utils': patch
|
||||
---
|
||||
|
||||
Migrated to using `@backstage/app-defaults`.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/cli-common': patch
|
||||
---
|
||||
|
||||
Add docs to the last export of cli-common
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-azure-devops': patch
|
||||
---
|
||||
|
||||
Azure DevOps frontend refactoring items from issue #7641
|
||||
|
||||
- Remove backend setup documentation and linked to the Azure DevOps backend plugin for these instructions
|
||||
- Improved documentation to be easier to expand with new features in the future
|
||||
- Removed Router based on feedback from maintainers
|
||||
- Added tests for `getBuildResultComponent` and `getBuildStateComponent` from the BuildTable
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder': patch
|
||||
---
|
||||
|
||||
Scaffolder: Enable back Template action buttons if template fails to execute
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-common': patch
|
||||
---
|
||||
|
||||
Added the `isDatabaseConflictError` function.
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
'@backstage/create-app': patch
|
||||
---
|
||||
|
||||
Migrated the app template use the new `@backstage/app-defaults` for the `createApp` import, since the `createApp` exported by `@backstage/app-core-api` will be removed in the future.
|
||||
|
||||
To migrate an existing application, add the latest version of `@backstage/app-defaults` as a dependency in `packages/app/package.json`, and make the following change to `packages/app/src/App.tsx`:
|
||||
|
||||
```diff
|
||||
-import { createApp, FlatRoutes } from '@backstage/core-app-api';
|
||||
+import { createApp } from '@backstage/app-defaults';
|
||||
+import { FlatRoutes } from '@backstage/core-app-api';
|
||||
```
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-azure-devops-backend': patch
|
||||
---
|
||||
|
||||
Added duration (startTime and finishTime) and identity (uniqueName) to the RepoBuild results. Also did a bit of refactoring to help finish up the backend items in issue #7641
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/cli': minor
|
||||
---
|
||||
|
||||
Tweaked style insertion logic to make sure that JSS stylesheets always receive the highest priority.
|
||||
@@ -1,26 +0,0 @@
|
||||
---
|
||||
'@backstage/backend-common': patch
|
||||
'@backstage/catalog-model': patch
|
||||
'@backstage/cli': patch
|
||||
'@backstage/config-loader': patch
|
||||
'@backstage/core-app-api': patch
|
||||
'@backstage/errors': patch
|
||||
'@backstage/search-common': patch
|
||||
'@backstage/test-utils': patch
|
||||
'@backstage/plugin-app-backend': patch
|
||||
'@backstage/plugin-catalog-backend': patch
|
||||
'@backstage/plugin-catalog-backend-module-ldap': patch
|
||||
'@backstage/plugin-catalog-graphql': patch
|
||||
'@backstage/plugin-catalog-react': patch
|
||||
'@backstage/plugin-config-schema': patch
|
||||
'@backstage/plugin-scaffolder': patch
|
||||
'@backstage/plugin-scaffolder-backend': patch
|
||||
'@backstage/plugin-scaffolder-backend-module-cookiecutter': patch
|
||||
'@backstage/plugin-scaffolder-backend-module-rails': patch
|
||||
'@backstage/plugin-scaffolder-backend-module-yeoman': patch
|
||||
'@backstage/plugin-scaffolder-common': patch
|
||||
'@backstage/plugin-search': patch
|
||||
'@backstage/plugin-shortcuts': patch
|
||||
---
|
||||
|
||||
Switch to use the json and observable types from `@backstage/types`
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/techdocs-common': patch
|
||||
---
|
||||
|
||||
Support optional bucketRootPath configuration parameter in S3 and GCS publishers
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
'@backstage/backend-common': patch
|
||||
---
|
||||
|
||||
Make sure that server builder `start()` propagates errors (such as failing to bind to the required port) properly and doesn't resolve the promise prematurely.
|
||||
|
||||
After this change, the backend logger will be able to actually capture the error as it happens:
|
||||
|
||||
```
|
||||
2021-11-11T10:54:21.334Z backstage info Initializing http server
|
||||
2021-11-11T10:54:21.335Z backstage error listen EADDRINUSE: address already in use :::7000 code=EADDRINUSE errno=-48 syscall=listen address=:: port=7000
|
||||
```
|
||||
@@ -1,8 +0,0 @@
|
||||
---
|
||||
'@backstage/config': patch
|
||||
'@backstage/core-app-api': patch
|
||||
'@backstage/core-plugin-api': patch
|
||||
'@backstage/plugin-scaffolder-backend': patch
|
||||
---
|
||||
|
||||
Start using the new `@backstage/types` package. Initially, this means using the `Observable` and `Json*` types from there. The types also remain in their old places but deprecated, and will be removed in a future release.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
'@backstage/config': patch
|
||||
'@backstage/core-app-api': patch
|
||||
'@backstage/core-plugin-api': patch
|
||||
---
|
||||
|
||||
Replace usage of test-utils-core with test-utils
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-search-backend-node': patch
|
||||
---
|
||||
|
||||
Handle special case when filter array has single value optimizing Lunr search behaviour.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/core-plugin-api': minor
|
||||
---
|
||||
|
||||
Removed the unused `UserFlags` type.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/core-plugin-api': patch
|
||||
---
|
||||
|
||||
Deprecated the `theme` property on `AppTheme`, replacing it with `Provider`. See https://backstage.io/docs/api/deprecations#app-theme for more details.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/test-utils': patch
|
||||
---
|
||||
|
||||
Migrated to using `createSpecializedApp`.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-sentry': patch
|
||||
---
|
||||
|
||||
fix: sentry-plugin correction for allowed period values
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/core-app-api': patch
|
||||
---
|
||||
|
||||
Api cleanup, adding `@public` where necessary and tweaking some comments
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
'@backstage/core-app-api': patch
|
||||
---
|
||||
|
||||
The `createApp` function from `@backstage/core-app-api` has been deprecated, with two new options being provided as a replacement.
|
||||
|
||||
The first and most commonly used one is `createApp` from the new `@backstage/app-defaults` package, which behaves just like the existing `createApp`. In the future this method is likely to be expanded to add more APIs and other pieces into the default setup, for example the Utility APIs from `@backstage/integration-react`.
|
||||
|
||||
The other option that we now provide is to use `createSpecializedApp` from `@backstage/core-app-api`. This is a more low-level API where you need to provide a full set of options, including your own `components`, `icons`, `defaultApis`, and `themes`. The `createSpecializedApp` way of creating an app is particularly useful if you are not using `@backstage/core-components` or MUI, as it allows you to avoid those dependencies completely.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/core-components': patch
|
||||
---
|
||||
|
||||
Deprecated `DismissbleBannerClassKey` and fixed the typo to make `DismissableBannerClassKey`
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/errors': patch
|
||||
---
|
||||
|
||||
Add `stringifyError` that is useful for logging e.g. `Something went wrong, ${stringifyError(e)}`
|
||||
@@ -52,6 +52,7 @@ configmaps
|
||||
configs
|
||||
const
|
||||
cookiecutter
|
||||
cron
|
||||
css
|
||||
Datadog
|
||||
dataflow
|
||||
@@ -162,6 +163,8 @@ Monorepo
|
||||
monorepos
|
||||
msgraph
|
||||
msw
|
||||
mutex
|
||||
mutexes
|
||||
mysql
|
||||
namespace
|
||||
namespaced
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
|
||||
env:
|
||||
CI: true
|
||||
NODE_OPTIONS: --max-old-space-size=4096
|
||||
NODE_OPTIONS: --max-old-space-size=8192
|
||||
|
||||
name: Node ${{ matrix.node-version }} on ${{ matrix.os }}
|
||||
steps:
|
||||
@@ -40,6 +40,8 @@ jobs:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Add msbuild to PATH
|
||||
uses: microsoft/setup-msbuild@v1.0.2
|
||||
- name: setup chrome
|
||||
uses: browser-actions/setup-chrome@latest
|
||||
- name: yarn install
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
|
||||
@@ -53,6 +53,8 @@ jobs:
|
||||
id: yarn-cache
|
||||
if: steps.cache-modules.outputs.cache-hit != 'true'
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- name: setup chrome
|
||||
uses: browser-actions/setup-chrome@latest
|
||||
- name: cache global yarn cache
|
||||
uses: actions/cache@v2
|
||||
if: steps.cache-modules.outputs.cache-hit != 'true'
|
||||
@@ -72,8 +74,3 @@ jobs:
|
||||
run: |
|
||||
sudo sysctl fs.inotify.max_user_watches=524288
|
||||
yarn e2e-test run
|
||||
env:
|
||||
POSTGRES_HOST: localhost
|
||||
POSTGRES_PORT: ${{ job.services.postgres.ports[5432] }}
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
name: 'Snyk Github Issue Sync'
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 */4 * * *'
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [14.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
# Beginning of yarn setup, keep in sync between all workflows, see ci.yml
|
||||
- name: use node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
registry-url: https://registry.npmjs.org/ # Needed for auth
|
||||
- name: cache all node_modules
|
||||
id: cache-modules
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: '**/node_modules'
|
||||
key: ${{ runner.os }}-v${{ matrix.node-version }}-node_modules-${{ hashFiles('yarn.lock', '**/package.json') }}
|
||||
- name: find location of global yarn cache
|
||||
id: yarn-cache
|
||||
if: steps.cache-modules.outputs.cache-hit != 'true'
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- name: cache global yarn cache
|
||||
uses: actions/cache@v2
|
||||
if: steps.cache-modules.outputs.cache-hit != 'true'
|
||||
with:
|
||||
path: ${{ steps.yarn-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- name: yarn install
|
||||
run: yarn install --frozen-lockfile
|
||||
# End of yarn setup
|
||||
|
||||
- name: Create Snyk report
|
||||
uses: snyk/actions/node@master
|
||||
continue-on-error: true # Snyk CLI exits with error when vulnerabilities are found
|
||||
with:
|
||||
args: >
|
||||
--yarn-workspaces
|
||||
--org=backstage-dgh
|
||||
--strict-out-of-sync=false
|
||||
--json-file-output=snyk.json
|
||||
json: true
|
||||
env:
|
||||
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
||||
- name: Update Github issues
|
||||
run: yarn ts-node scripts/snyk-github-issue-sync.ts
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
+67
-61
@@ -1,61 +1,67 @@
|
||||
| Organization | Contact | Description of Use |
|
||||
| ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [Spotify](https://www.spotify.com) | [@leemills83](https://github.com/leemills83) | Main interface towards all of Spotify's infrastructure and technical documentation. |
|
||||
| [bol.com](https://www.bol.com) | [@sagacity](https://github.com/sagacity) | Initial work being done to unify platform tooling. |
|
||||
| [DFDS](https://www.dfds.com) | [@carlsendk](https://github.com/carlsendk) | V2 self-service platform. |
|
||||
| [Roadie](https://roadie.io) | [@dtuite](https://github.com/dtuite) | Hosted, managed Backstage with easy set-up |
|
||||
| [Roku](https://www.roku.com) | [@timurista](https://github.com/timurista) | Initial work on Cloud engineering service platform. |
|
||||
| [SDA SE](https://sda.se) | [@Fox32](https://github.com/Fox32) | Central place for developing and sharing services in our insurance ecosystem. |
|
||||
| [H-E-B](https://www.heb.com) | [@german-j-rodriguez](https://github.com/german-j-rodriguez) | Initial work on Engineering Portal service platform. |
|
||||
| [American Airlines](https://www.aa.com) | [@paulpach](https://github.com/paulpach) | Central place for developers to develop and maintain applications |
|
||||
| [Kiwi.com](https://kiwi.com) | [@aexvir](https://github.com/aexvir) | Replacing the frontend of [The Zoo](https://github.com/kiwicom/the-zoo), their service registry. |
|
||||
| [Voi](https://www.voiscooters.com/) | [@K-Phoen](https://github.com/K-Phoen) | Developer portal, main gateway to our infrastructure, documentation and internal tooling. |
|
||||
| [Talkdesk](https://www.talkdesk.com) | [@jaime-talkdesk](https://github.com/jaime-talkdesk) | Initial work for Engineering Portal and Self Provisioning to R&D |
|
||||
| [Wealthsimple](https://www.wealthsimple.com) | [@andrewthauer](https://github.com/andrewthauer) | Developer portal, service catalog, documentation and tooling |
|
||||
| [Grab](https://www.grab.com) | [@althafh](https://github.com/althafh) | Initial work as a unified interface for all of Grab's internal tooling |
|
||||
| [Telenor Sweden](https://www.telenor.se) | [@O5ten](https://github.com/O5ten) | Building a developer portal for scaffolding projects towards our unified build environment and microservice stacks |
|
||||
| [Fiverr](https://www.fiverr.com) | [@nirga](https://github.com/nirga) | Unifying separate tools that developers are using today (i.e. monitoring, dead letter queues management, etc.) into a single platform. |
|
||||
| [Zalando SE](https://www.zalando.de) | [@leviferreira](https://github.com/leviferreira) | Building V2 of the Internal Development Portal. |
|
||||
| [LegalZoom](https://legalzoom.com) | [@backjo](https://github.com/backjo) | Developer portal - hub for all engineering projects and metadata. |
|
||||
| [Expedia Group](https://www.expediagroup.com) | [Mike Turner](mailto:miturner@expediagroup.com), [Sneha Kumar](mailto:snkumar@expediagroup.com), [@guillermomanzo](https://github.com/guillermomanzo), [Erik Lindgren](https://github.com/lindgren) | EG Common Developer Toolkit |
|
||||
| [Paddle.com](https://paddle.com) | [Ioannis Georgoulas](https://github.com/geototti21) | Developer portal (Tech Docs, Service Catalog, Internal Tooling), we use vanilla Backstage FE and custom BE implementation in Go |
|
||||
| [Acast.com](https://acast.com) | [Olle Lundberg](https://github.com/lndbrg) | Developer portal with tech docs, service catalog and a bunch of other internal tooling |
|
||||
| [Lunar](https://lunar.app) | [Jacob Valdemar](https://github.com/JacobValdemar) | Internal developer portal for service overview and insights, API documentation, technical guides, onboarding guides and RFC's. |
|
||||
| [Trendyol](https://trendyol.com) | [Gamze Senturk](https://github.com/gmzsenturk), [Mert Can Bilgic](https://github.com/mertcb) | The Developer Portal has been called `Pandora`. Provides an overview of Trendyol tech ecosystem. TechDocs, Catalog, Custom Plugins and Theme. |
|
||||
| [Peloton](https://www.onepeloton.com/) | [Jim Haughwout](https://github.com/JimHaughwout) | Creating our first developer portal and tech-docs. Exploring Service Catalog, Tech Insights and Cost Insights as well. |
|
||||
| [TELUS](https://telus.com) | [Seb Barre](https://github.com/sbarre) | The Go-to place to find answers about development and delivery at TELUS. |
|
||||
| [Brex](https://www.brex.com/) | [Vamsi Chitters](https://github.com/vamsikc) | A centralized UI to understand how a service fits in the whole Brex architecture and manage a team’s engineering dependencies. |
|
||||
| [Oriflame](https://www.oriflame.com/) | [Oriflame](https://github.com/oriflame) | Internal developer portal for services, single page apps and packages overview, API documentation, technical guides, tech-radar and more. |
|
||||
| [Booz Allen Hamilton](https://www.boozallen.com/) | [Jason Miller](https://github.com/JasonMiller-BAH) | Developer portal for a full-stack software development ecosystem that accelerates consistent and repeatable Modern Software Development practices for internal innovation and investments. |
|
||||
| [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 🌕🚀🧑🚀 |
|
||||
| [FundApps](https://www.fundapps.co/) | [Elliot Greenwood](https://github.com/egnwd) | Developer Portal - A place for us to keep track of our projects and documentation for all services and processes |
|
||||
| [DAZN](https://dazn.com/) | [Lou Bichard](https://twitter.com/loujaybee), [Marco Crivellaro](https://github.com/crivetechie), [Alex Hollerith](mailto:alex.hollerith@dazn.com) | Ingesting all of DAZN's repos for the catalog, migrating our internal platform apps (pull request boards, release information, inner source marketplace etc) to Backstage plugins (where applicable). |
|
||||
| [HelloFresh](https://www.hellofresh.de/) | [@iammuho](https://github.com/iammuho), [@ElenaForester](https://github.com/ElenaForester), [@diegomarangoni](https://github.com/diegomarangoni) | Our developer portal at HelloFresh - Spread across an organisation of 500+ engineers globally. |
|
||||
| [FactSet](https://www.factset.com/) | [@kuangp](https://github.com/kuangp) | Developer portal to provide discoverability to all internal components, APIs, documentation, and scaffold templates with integrations to our internal infrastructure tools. |
|
||||
| [Workrise](https://www.workrise.com/) | [Michael Rode](https://github.com/michaelrode) | Developer portal, main gateway to our infrastructure, documentation and internal tooling. |
|
||||
| [RedVentures](https://www.redventures.com/) | [Chris Diaz](https://github.com/codingdiaz) | Developer portal that brings everything an engineer needs to provide value into a single pane of glass. |
|
||||
| [MavTek](https://www.mavtek.com/) | [@fgascon](https://github.com/fgascon) | Developer portal focused on standardizing practices, centralizing documentation and streamlining developer practices. |
|
||||
| [QuintoAndar](https://www.quintoandar.com.br/) | [@quintoandar](https://github.com/quintoandar) | Developer portal, services catalog and centralization of service metrics. |
|
||||
| [empathy.co](https://empathy.co/) | [@guillermotti](https://github.com/guillermotti) | Developer portal for tech docs, service catalog, plugin discovery and much more. |
|
||||
| [creditas.com](https://creditas.com/) | [@aureliosaraiva](https://github.com/aureliosaraiva) [@Creditas](https://github.com/creditas) | Centralization of all services, standards, documentation, etc. We started the deployment process. |
|
||||
| [Prisjakt](https://www.prisjakt.nu) / [PriceSpy](https://pricespy.co.uk) | [@kennylindahl](https://github.com/kennylindahl) | Internal developer portal - Documentation, scaffolding, software catalog, TechRadar, Gitlab org data integration |
|
||||
| [Powerspike](https://powerspike.tv/) | [@trelore](https://github.com/trelore) | Developer portal for documentation of core libraries and repositories. |
|
||||
| [2U](https://2u.com) | [Andrew Thal](https://github.com/athal7) | Development team home-base, promoting service discoverability, resource dependencies, and tech radar |
|
||||
| [Taxfix](https://taxfix.de/) | [Sami Ur Rehman](https://github.com/samiurrehman92) | Developer's portal with software catalog at it's core. Hosts API Specs, Tech Docs, Tech Radar and some custom plugins. |
|
||||
| [Busuu](https://busuu.com/) | [Adam Tester](https://github.com/adamtester) | Developer portal with service catalog, API docs, Event docs, service templating, and cost insights. |
|
||||
| [Loadsmart](https://loadsmart.com/) | [Loadsmart](https://github.com/loadsmart) | Improve services visibility and operations for service owners and developers. |
|
||||
| [Monzo](https://monzo.com/) | [@WillSewell](https://github.com/WillSewell), [@joechrisellis](https://github.com/joechrisellis) | Developer portal showing metadata and docs for over 2000 microservices. We have built a number of plugins such as a UI for our system to measure [software excellence](https://monzo.com/blog/2021/09/15/how-we-measure-software-excellence), and a UI to show deployment and config change events. |
|
||||
| [Vaimo](https://www.vaimo.com) | [@vaimo-magnus](https://github.com/vaimo-magnus) | Developer Portal for our developers at Vaimo, currently docs and self-service towards our internal PaaS based on k8s. Plans to extend the catalog into Projects, Environments etc |
|
||||
| [Wayfair](https://www.wayfair.com) | [@fransan](https://github.com/fransan), [@errskipower](https://github.com/errskipower), [@hrrs](https://github.com/hrrs) | Developer portal for service catalog, technical documentation, and APIs. |
|
||||
| [CircleHD](https://www.circlehd.com) | [@circlehddev](https://github.com/circlehddev) | Developer Portal for internal dev team across the globe |
|
||||
| [CastDesk](https://castdesk.com) | [@circlehddev](https://github.com/circlehddev) | Developer Portal for internal dev team across the globe |
|
||||
| [Santagostino](https://santagostino.it) | [@santagostino](https://github.com/santagostino) | Developer portal, gateway to our infrastructure, documentation, service catalog and internal tooling. |
|
||||
| [Peak](https://peak.ai) | [Luke Beamish](https://github.com/lukebeamish-peak) | Developer portal for all internal engineers to access documentation and tooling. |
|
||||
| [Gelato](https://gelato.com/) | [Dmitry Makarenko](https://github.com/dmitry-makarenko-gelato) | Developer portal: documentation, service templates, org structure, service catalog, plugins for integration with internal and third-party systems🚀. |
|
||||
| [GoCardless](https://gocardless.com/) | [James Turley](https://github.com/tragiclifestories) | Developer portal: documentation, service templates, org structure, service catalog, plugins for integration with internal systems. |
|
||||
| [Box](https://www.box.com) | [@kielosz](https://github.com/kielosz), [@jluk-box](https://github.com/jluk-box), [@ptychu](https://github.com/ptychu), [@alexrybch](https://github.com/alexrybch), [@szubster](https://github.com/szubster) | Developer portal for service catalog, integration with internal systems, new service onboarding. |
|
||||
| [Bazaarvoice](https://www.bazaarvoice.com) | [@niallmccullagh](https://github.com/niallmccullagh) | Developer portal for service catalog and scaffolds, publishing Github docs and API documentation, visualising our internal tech radar and our product engineering org structure. |
|
||||
| [Krateo PlatformOps](https://www.krateo.io) | [@projectkerberus](https://github.com/projectkerberus) | A multi-cloud control plane to create, manage and deploy any kind of resource easily and centrally via a Developer Portal that centralizes via a self-service catalog the templating and ownership of services, the available documentation, the overview of the components that compose an entire domain and all the data of the service lifecycle. |
|
||||
| Organization | Contact | Description of Use |
|
||||
| ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [Spotify](https://www.spotify.com) | [@leemills83](https://github.com/leemills83) | Main interface towards all of Spotify's infrastructure and technical documentation. |
|
||||
| [bol.com](https://www.bol.com) | [@sagacity](https://github.com/sagacity) | Initial work being done to unify platform tooling. |
|
||||
| [DFDS](https://www.dfds.com) | [@carlsendk](https://github.com/carlsendk) | V2 self-service platform. |
|
||||
| [Roadie](https://roadie.io) | [@dtuite](https://github.com/dtuite) | Hosted, managed Backstage with easy set-up |
|
||||
| [Roku](https://www.roku.com) | [@timurista](https://github.com/timurista) | Initial work on Cloud engineering service platform. |
|
||||
| [SDA SE](https://sda.se) | [@Fox32](https://github.com/Fox32) | Central place for developing and sharing services in our insurance ecosystem. |
|
||||
| [H-E-B](https://www.heb.com) | [@german-j-rodriguez](https://github.com/german-j-rodriguez) | Initial work on Engineering Portal service platform. |
|
||||
| [American Airlines](https://www.aa.com) | [@paulpach](https://github.com/paulpach) | Central place for developers to develop and maintain applications |
|
||||
| [Kiwi.com](https://kiwi.com) | [@aexvir](https://github.com/aexvir) | Replacing the frontend of [The Zoo](https://github.com/kiwicom/the-zoo), their service registry. |
|
||||
| [Voi](https://www.voiscooters.com/) | [@K-Phoen](https://github.com/K-Phoen) | Developer portal, main gateway to our infrastructure, documentation and internal tooling. |
|
||||
| [Talkdesk](https://www.talkdesk.com) | [@jaime-talkdesk](https://github.com/jaime-talkdesk) | Initial work for Engineering Portal and Self Provisioning to R&D |
|
||||
| [Wealthsimple](https://www.wealthsimple.com) | [@andrewthauer](https://github.com/andrewthauer) | Developer portal, service catalog, documentation and tooling |
|
||||
| [Grab](https://www.grab.com) | [@althafh](https://github.com/althafh) | Initial work as a unified interface for all of Grab's internal tooling |
|
||||
| [Telenor Sweden](https://www.telenor.se) | [@O5ten](https://github.com/O5ten) | Building a developer portal for scaffolding projects towards our unified build environment and microservice stacks |
|
||||
| [Fiverr](https://www.fiverr.com) | [@nirga](https://github.com/nirga) | Unifying separate tools that developers are using today (i.e. monitoring, dead letter queues management, etc.) into a single platform. |
|
||||
| [Zalando SE](https://www.zalando.de) | [@leviferreira](https://github.com/leviferreira) | Building V2 of the Internal Development Portal. |
|
||||
| [LegalZoom](https://legalzoom.com) | [@backjo](https://github.com/backjo) | Developer portal - hub for all engineering projects and metadata. |
|
||||
| [Expedia Group](https://www.expediagroup.com) | [Mike Turner](mailto:miturner@expediagroup.com), [Sneha Kumar](mailto:snkumar@expediagroup.com), [@guillermomanzo](https://github.com/guillermomanzo), [Erik Lindgren](https://github.com/lindgren) | EG Common Developer Toolkit |
|
||||
| [Paddle.com](https://paddle.com) | [Ioannis Georgoulas](https://github.com/geototti21) | Developer portal (Tech Docs, Service Catalog, Internal Tooling), we use vanilla Backstage FE and custom BE implementation in Go |
|
||||
| [Acast.com](https://acast.com) | [Olle Lundberg](https://github.com/lndbrg) | Developer portal with tech docs, service catalog and a bunch of other internal tooling |
|
||||
| [Lunar](https://lunar.app) | [Jacob Valdemar](https://github.com/JacobValdemar) | Internal developer portal for service overview and insights, API documentation, technical guides, onboarding guides and RFC's. |
|
||||
| [Trendyol](https://trendyol.com) | [Gamze Senturk](https://github.com/gmzsenturk), [Mert Can Bilgic](https://github.com/mertcb) | The Developer Portal has been called `Pandora`. Provides an overview of Trendyol tech ecosystem. TechDocs, Catalog, Custom Plugins and Theme. |
|
||||
| [Peloton](https://www.onepeloton.com/) | [Jim Haughwout](https://github.com/JimHaughwout) | Creating our first developer portal and tech-docs. Exploring Service Catalog, Tech Insights and Cost Insights as well. |
|
||||
| [TELUS](https://telus.com) | [Seb Barre](https://github.com/sbarre) | The Go-to place to find answers about development and delivery at TELUS. |
|
||||
| [Brex](https://www.brex.com/) | [Vamsi Chitters](https://github.com/vamsikc) | A centralized UI to understand how a service fits in the whole Brex architecture and manage a team’s engineering dependencies. |
|
||||
| [Oriflame](https://www.oriflame.com/) | [Oriflame](https://github.com/oriflame) | Internal developer portal for services, single page apps and packages overview, API documentation, technical guides, tech-radar and more. |
|
||||
| [Booz Allen Hamilton](https://www.boozallen.com/) | [Jason Miller](https://github.com/JasonMiller-BAH) | Developer portal for a full-stack software development ecosystem that accelerates consistent and repeatable Modern Software Development practices for internal innovation and investments. |
|
||||
| [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 🌕🚀🧑🚀 |
|
||||
| [FundApps](https://www.fundapps.co/) | [Elliot Greenwood](https://github.com/egnwd) | Developer Portal - A place for us to keep track of our projects and documentation for all services and processes |
|
||||
| [DAZN](https://dazn.com/) | [Lou Bichard](https://twitter.com/loujaybee), [Marco Crivellaro](https://github.com/crivetechie), [Alex Hollerith](mailto:alex.hollerith@dazn.com) | Ingesting all of DAZN's repos for the catalog, migrating our internal platform apps (pull request boards, release information, inner source marketplace etc) to Backstage plugins (where applicable). |
|
||||
| [HelloFresh](https://www.hellofresh.de/) | [@iammuho](https://github.com/iammuho), [@ElenaForester](https://github.com/ElenaForester), [@diegomarangoni](https://github.com/diegomarangoni) | Our developer portal at HelloFresh - Spread across an organisation of 500+ engineers globally. |
|
||||
| [FactSet](https://www.factset.com/) | [@kuangp](https://github.com/kuangp) | Developer portal to provide discoverability to all internal components, APIs, documentation, and scaffold templates with integrations to our internal infrastructure tools. |
|
||||
| [Workrise](https://www.workrise.com/) | [Michael Rode](https://github.com/michaelrode) | Developer portal, main gateway to our infrastructure, documentation and internal tooling. |
|
||||
| [RedVentures](https://www.redventures.com/) | [Chris Diaz](https://github.com/codingdiaz) | Developer portal that brings everything an engineer needs to provide value into a single pane of glass. |
|
||||
| [MavTek](https://www.mavtek.com/) | [@fgascon](https://github.com/fgascon) | Developer portal focused on standardizing practices, centralizing documentation and streamlining developer practices. |
|
||||
| [QuintoAndar](https://www.quintoandar.com.br/) | [@quintoandar](https://github.com/quintoandar) | Developer portal, services catalog and centralization of service metrics. |
|
||||
| [empathy.co](https://empathy.co/) | [@guillermotti](https://github.com/guillermotti) | Developer portal for tech docs, service catalog, plugin discovery and much more. |
|
||||
| [creditas.com](https://creditas.com/) | [@aureliosaraiva](https://github.com/aureliosaraiva) [@Creditas](https://github.com/creditas) | Centralization of all services, standards, documentation, etc. We started the deployment process. |
|
||||
| [Prisjakt](https://www.prisjakt.nu) / [PriceSpy](https://pricespy.co.uk) | [@kennylindahl](https://github.com/kennylindahl) | Internal developer portal - Documentation, scaffolding, software catalog, TechRadar, Gitlab org data integration |
|
||||
| [Powerspike](https://powerspike.tv/) | [@trelore](https://github.com/trelore) | Developer portal for documentation of core libraries and repositories. |
|
||||
| [2U](https://2u.com) | [Andrew Thal](https://github.com/athal7) | Development team home-base, promoting service discoverability, resource dependencies, and tech radar |
|
||||
| [Taxfix](https://taxfix.de/) | [Sami Ur Rehman](https://github.com/samiurrehman92) | Developer's portal with software catalog at it's core. Hosts API Specs, Tech Docs, Tech Radar and some custom plugins. |
|
||||
| [Busuu](https://busuu.com/) | [Adam Tester](https://github.com/adamtester) | Developer portal with service catalog, API docs, Event docs, service templating, and cost insights. |
|
||||
| [Loadsmart](https://loadsmart.com/) | [Loadsmart](https://github.com/loadsmart) | Improve services visibility and operations for service owners and developers. |
|
||||
| [Monzo](https://monzo.com/) | [@WillSewell](https://github.com/WillSewell), [@joechrisellis](https://github.com/joechrisellis) | Developer portal showing metadata and docs for over 2000 microservices. We have built a number of plugins such as a UI for our system to measure [software excellence](https://monzo.com/blog/2021/09/15/how-we-measure-software-excellence), and a UI to show deployment and config change events. |
|
||||
| [Vaimo](https://www.vaimo.com) | [@vaimo-magnus](https://github.com/vaimo-magnus) | Developer Portal for our developers at Vaimo, currently docs and self-service towards our internal PaaS based on k8s. Plans to extend the catalog into Projects, Environments etc |
|
||||
| [Wayfair](https://www.wayfair.com) | [@fransan](https://github.com/fransan), [@errskipower](https://github.com/errskipower), [@hrrs](https://github.com/hrrs) | Developer portal for service catalog, technical documentation, and APIs. |
|
||||
| [CircleHD](https://www.circlehd.com) | [@circlehddev](https://github.com/circlehddev) | Developer Portal for internal dev team across the globe |
|
||||
| [CastDesk](https://castdesk.com) | [@circlehddev](https://github.com/circlehddev) | Developer Portal for internal dev team across the globe |
|
||||
| [Santagostino](https://santagostino.it) | [@santagostino](https://github.com/santagostino) | Developer portal, gateway to our infrastructure, documentation, service catalog and internal tooling. |
|
||||
| [Peak](https://peak.ai) | [Luke Beamish](https://github.com/lukebeamish-peak) | Developer portal for all internal engineers to access documentation and tooling. |
|
||||
| [Gelato](https://gelato.com/) | [Dmitry Makarenko](https://github.com/dmitry-makarenko-gelato) | Developer portal: documentation, service templates, org structure, service catalog, plugins for integration with internal and third-party systems🚀. |
|
||||
| [GoCardless](https://gocardless.com/) | [James Turley](https://github.com/tragiclifestories) | Developer portal: documentation, service templates, org structure, service catalog, plugins for integration with internal systems. |
|
||||
| [Box](https://www.box.com) | [@kielosz](https://github.com/kielosz), [@jluk-box](https://github.com/jluk-box), [@ptychu](https://github.com/ptychu), [@alexrybch](https://github.com/alexrybch), [@szubster](https://github.com/szubster) | Developer portal for service catalog, integration with internal systems, new service onboarding. |
|
||||
| [Bazaarvoice](https://www.bazaarvoice.com) | [@niallmccullagh](https://github.com/niallmccullagh) | Developer portal for service catalog and scaffolds, publishing Github docs and API documentation, visualising our internal tech radar and our product engineering org structure. |
|
||||
| [Krateo PlatformOps](https://www.krateo.io) | [@projectkerberus](https://github.com/projectkerberus) | A multi-cloud control plane to create, manage and deploy any kind of resource easily and centrally via a Developer Portal that centralizes via a self-service catalog the templating and ownership of services, the available documentation, the overview of the components that compose an entire domain and all the data of the service lifecycle. |
|
||||
| [Adevinta](https://www.adevinta.com) | [Ray Sinnema](https://github.com/RemonSinnema) | Showcase shared services to our internal customers. |
|
||||
| [Splunk](https://www.splunk.com) | [@tonytamsf](https://github.com/tonytamsf) | Developer portal as a centralized place to find people, services, documentation, escalation policies and give bravos. This portal is also being used as a centralized search engine for engineering specific documentation. |
|
||||
| [SoundCloud](https://www.soundcloud.com) | [Julio Zynger](https://github.com/julioz) | Developer portal as a [humane registry](https://martinfowler.com/bliki/HumaneRegistry.html) for the organization: catalog of people, services, documentation, feature toggles, escalation policies, etc. |
|
||||
| [Volvofinans Bank](https://www.volvofinans.se) | [Johan Hammar](https://github.com/johanhammar) | Developer portal enabling engineers to manage and explore software and documentation. |
|
||||
| [Palo Alto Networks](https://www.paloaltonetworks.com) | [Jeremy Guarini](https://github.com/jeremyguarini), [Brian Lomeland](https://github.com/bbbmmmlll), [Palo Alto Networks](https://github.com/PaloAltoNetworks) | Developer portal, service catalog, documentation and tooling |
|
||||
| [Signal Iduna Group](https://www.signal-iduna.de/) | [Jonas Thomsen](https://github.com/JoThomsen) | Developer Portal, documentation, monitoring, service catalog for our insurance ecosystem
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
---
|
||||
id: deprecations
|
||||
title: Deprecations
|
||||
description: A list of active and past deprecations
|
||||
---
|
||||
|
||||
## Introduction
|
||||
|
||||
This page contains extended documentation for some of the deprecations in
|
||||
various parts of Backstage. It is not an exhaustive list as most deprecation
|
||||
only come in the form of a changelog notice and a console warning. The
|
||||
deprecations listed here are the ones that need a bit more guidance than what
|
||||
fits in a console message.
|
||||
|
||||
### App Theme
|
||||
|
||||
`Released 2021-11-12 in @backstage/core-plugin-api v0.1.13`
|
||||
|
||||
In order to provide more flexibility in what types of themes can be used and how
|
||||
they are applied, the `theme` property on the `AppTheme` type is being
|
||||
deprecated and replaced by a `Provider` property instead. The `Provider`
|
||||
property is a React component that will be mounted at the root of the app
|
||||
whenever that theme is active. This also removes the tight connection to MUI and
|
||||
opens up for other type of themes, and removes the hardcoded usage of
|
||||
`<CssBaseline>`.
|
||||
|
||||
To migrate an existing theme, remove the `theme` property and move it over to a
|
||||
new `Provider` component, using `ThemeProvider` from MUI to provide the new
|
||||
theme, along with `<CssBaseline>`. For example a theme that currently looks like
|
||||
this:
|
||||
|
||||
```tsx
|
||||
const darkTheme = {
|
||||
id: 'dark',
|
||||
title: 'Dark Theme',
|
||||
variant: 'dark',
|
||||
icon: <DarkIcon />,
|
||||
theme: darkTheme,
|
||||
};
|
||||
```
|
||||
|
||||
Would be migrated to the following:
|
||||
|
||||
```tsx
|
||||
const darkTheme = {
|
||||
id: 'dark',
|
||||
title: 'Dark Theme',
|
||||
variant: 'dark',
|
||||
icon: <DarkIcon />,
|
||||
Provider: ({ children }) => (
|
||||
<ThemeProvider theme={darkTheme}>
|
||||
<CssBaseline>{children}</CssBaseline>
|
||||
</ThemeProvider>
|
||||
),
|
||||
};
|
||||
```
|
||||
+10
-8
@@ -67,13 +67,15 @@ production build.
|
||||
|
||||
## Configuration Files
|
||||
|
||||
It is possible to have multiple configuration files, both to support different
|
||||
environments, but also to define configuration that is local to specific
|
||||
packages. The configuration files to load are selected using a `--config <path>`
|
||||
flag, and it is possible to load any number of files. Paths are relative to the
|
||||
working directory of the executed process, for example `package/backend`. This
|
||||
means that to select a config file in the repo root when running the backend,
|
||||
you would use `--config ../../my-config.yaml`.
|
||||
It is possible to have multiple configuration files (bundled and/or remote),
|
||||
both to support different environments, but also to define configuration that is
|
||||
local to specific packages. The configuration files to load are selected using a
|
||||
`--config <local-path|url>` flag, and it is possible to load any number of
|
||||
files. Paths are relative to the working directory of the executed process, for
|
||||
example `package/backend`. This means that to select a config file in the repo
|
||||
root when running the backend, you would use `--config ../../my-config.yaml`,
|
||||
and for config file on a config server you would use
|
||||
`--config https://some.domain.io/app-config.yaml`
|
||||
|
||||
If no `config` flags are specified, the default behavior is to load
|
||||
`app-config.yaml` and, if it exists, `app-config.local.yaml` from the repo root.
|
||||
@@ -85,7 +87,7 @@ are NOT loaded. To include them you need to explicitly include them with a flag,
|
||||
for example:
|
||||
|
||||
```shell
|
||||
yarn start --config ../../app-config.yaml --config ../../app-config.staging.yaml
|
||||
yarn start --config ../../app-config.yaml --config ../../app-config.staging.yaml --config https://some.domain.io/app-config.yaml
|
||||
```
|
||||
|
||||
All loaded configuration files are merged together using the following rules:
|
||||
|
||||
@@ -109,6 +109,25 @@ const routes = (
|
||||
);
|
||||
```
|
||||
|
||||
### Using the Search Modal
|
||||
|
||||
In `Root.tsx`, add the `SidebarSearchModal` component:
|
||||
|
||||
```bash
|
||||
import { SidebarSearchModal } from '@backstage/plugin-search';
|
||||
|
||||
export const Root = ({ children }: PropsWithChildren<{}>) => (
|
||||
<SidebarPage>
|
||||
<Sidebar>
|
||||
<SidebarLogo />
|
||||
<SidebarSearchModal />
|
||||
<SidebarDivider />
|
||||
...
|
||||
```
|
||||
|
||||
For more information about using `Root.tsx`, please see
|
||||
[the changelog](https://github.com/backstage/backstage/blob/master/packages/create-app/CHANGELOG.md#0315).
|
||||
|
||||
## Adding Search to the Backend
|
||||
|
||||
Add the following plugins into your backend app:
|
||||
|
||||
@@ -172,6 +172,7 @@ This page itself can be exported as a routable extension in the plugin:
|
||||
```ts
|
||||
export const CustomCatalogIndexPage = myPlugin.provide(
|
||||
createRoutableExtension({
|
||||
name: 'CustomCatalogIndexPage',
|
||||
component: () =>
|
||||
import('./components/CustomCatalogPage').then(m => m.CustomCatalogPage),
|
||||
mountPoint: catalogRouteRef,
|
||||
|
||||
@@ -502,6 +502,8 @@ spec:
|
||||
lifecycle: production
|
||||
owner: artist-relations-team
|
||||
system: artist-engagement-portal
|
||||
dependsOn:
|
||||
- resource:default/artists-db
|
||||
providesApis:
|
||||
- artist-api
|
||||
```
|
||||
|
||||
@@ -125,8 +125,8 @@ input schema.
|
||||
input:
|
||||
- address: '{{ json parameters.address }}'
|
||||
+ address: ${{ parameters.address }}
|
||||
- number: '{{ parameters.number }}'
|
||||
+ number: ${{ parameters.number }} # this will now make sure that the type of number is a number 🙏
|
||||
- test: '{{ parameters.test }}'
|
||||
+ test: ${{ parameters.test }} # this will now make sure that the type of test is a number 🙏
|
||||
```
|
||||
|
||||
## `parseRepoUrl` is now a `filter`
|
||||
|
||||
@@ -156,4 +156,16 @@ return await createRouter({
|
||||
});
|
||||
```
|
||||
|
||||
### List of custom action packages
|
||||
|
||||
Here is a list of Open Source custom actions that you can add to your Backstage
|
||||
scaffolder backend:
|
||||
|
||||
| Name | Package | Owner |
|
||||
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- |
|
||||
| Yeoman | [plugin-scaffolder-backend-module-yeoman](https://www.npmjs.com/package/@backstage/plugin-scaffolder-backend-module-yeoman) | [Backstage](https://backstage.io) |
|
||||
| Cookiecutter | [plugin-scaffolder-backend-module-cookiecutter](https://www.npmjs.com/package/@backstage/plugin-scaffolder-backend-module-cookiecutter) | [Backstage](https://backstage.io) |
|
||||
| Rails | [plugin-scaffolder-backend-module-rails](https://www.npmjs.com/package/@backstage/plugin-scaffolder-backend-module-rails) | [Backstage](https://backstage.io) |
|
||||
| HTTP requests | [scaffolder-backend-module-http-request](https://www.npmjs.com/package/@roadiehq/scaffolder-backend-module-http-request) | [Roadie](https://roadie.io) |
|
||||
|
||||
Have fun! 🚀
|
||||
|
||||
@@ -27,20 +27,27 @@ You can create your own Field Extension by using the
|
||||
|
||||
```tsx
|
||||
//packages/app/scaffolder/MyCustomExtension/MyCustomExtension.tsx
|
||||
import React from 'react';
|
||||
import { FieldProps, FieldValidation } from '@rjsf/core';
|
||||
import FormControl from '@material-ui/core/FormControl';
|
||||
import { KubernetesValidatorFunctions } from '@backstage/catalog-model';
|
||||
/*
|
||||
This is the actual component that will get rendered in the form
|
||||
*/
|
||||
export const MyCustomExtension = ({ onChange, required }: FieldProps<string>) => {
|
||||
export const MyCustomExtension = ({
|
||||
onChange,
|
||||
rawErrors,
|
||||
required,
|
||||
formData,
|
||||
}: FieldProps<string>) => {
|
||||
return (
|
||||
<FormControl
|
||||
<FormControl
|
||||
margin="normal"
|
||||
required={required}
|
||||
error={rawErrors?.length > 0 && !formData}
|
||||
onChange={onChange}
|
||||
>
|
||||
)
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -63,10 +70,10 @@ export const myCustomValidation = (
|
||||
```tsx
|
||||
// packages/app/scaffolder/MyCustomExtension/extensions.ts
|
||||
|
||||
/*
|
||||
/*
|
||||
This is where the magic happens and creates the custom field extension.
|
||||
|
||||
Note that if you're writing extensions part of a separate plugin,
|
||||
Note that if you're writing extensions part of a separate plugin,
|
||||
then please use `plugin.provide` from there instead and export it part of your `plugin.ts` rather than re-using the `scaffolder.plugin`.
|
||||
*/
|
||||
|
||||
|
||||
@@ -512,7 +512,8 @@ Usage: backstage-cli config:check [options]
|
||||
|
||||
Options:
|
||||
--package <name> Only load config schema that applies to the given package
|
||||
--lax Do not require environment variables to be set
|
||||
--lax Do not require environment variables to be set
|
||||
--frontend Only validate the frontend configuration
|
||||
--config <path> Config files to load instead of app-config.yaml (default: [])
|
||||
-h, --help display help for command
|
||||
```
|
||||
|
||||
@@ -136,6 +136,7 @@ a component:
|
||||
```ts
|
||||
export const FooPage = plugin.provide(
|
||||
createRoutableExtension({
|
||||
name: 'FooPage',
|
||||
component: () => import('./components/FooPage').then(m => m.FooPage),
|
||||
mountPoint: fooPageRouteRef,
|
||||
}),
|
||||
@@ -417,6 +418,7 @@ export const myPlugin = createPlugin({
|
||||
|
||||
export const MyPage = myPlugin.provide(
|
||||
createRoutableExtension({
|
||||
name: 'MyPage',
|
||||
component: () => import('./components/MyPage').then(m => m.MyPage),
|
||||
mountPoint: rootRouteRef,
|
||||
}),
|
||||
|
||||
@@ -65,6 +65,7 @@ export const examplePlugin = createPlugin({
|
||||
// Each extension should also be exported from your plugin package.
|
||||
export const ExamplePage = examplePlugin.provide(
|
||||
createRoutableExtension({
|
||||
name: 'ExamplePage',
|
||||
// The component needs to be lazy-loaded. It's what will actually be rendered in the end.
|
||||
component: () =>
|
||||
import('./components/ExampleComponent').then(m => m.ExampleComponent),
|
||||
|
||||
@@ -73,6 +73,7 @@ export const examplePlugin = createPlugin({
|
||||
|
||||
export const ExamplePage = examplePlugin.provide(
|
||||
createRoutableExtension({
|
||||
name: 'ExamplePage',
|
||||
component: () =>
|
||||
import('./components/ExampleComponent').then(m => m.ExampleComponent),
|
||||
mountPoint: rootRouteRef,
|
||||
|
||||
@@ -151,7 +151,10 @@
|
||||
{
|
||||
"type": "subcategory",
|
||||
"label": "GitLab",
|
||||
"ids": ["integrations/gitlab/locations"]
|
||||
"ids": [
|
||||
"integrations/gitlab/locations",
|
||||
"integrations/gitlab/discovery"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "subcategory",
|
||||
@@ -253,7 +256,8 @@
|
||||
"type": "subcategory",
|
||||
"label": "API Reference",
|
||||
"ids": ["reference/index"]
|
||||
}
|
||||
},
|
||||
"api/deprecations"
|
||||
],
|
||||
"Tutorials": [
|
||||
"tutorials/journey",
|
||||
@@ -275,7 +279,8 @@
|
||||
"architecture-decisions/adrs-adr008",
|
||||
"architecture-decisions/adrs-adr009",
|
||||
"architecture-decisions/adrs-adr010",
|
||||
"architecture-decisions/adrs-adr011"
|
||||
"architecture-decisions/adrs-adr011",
|
||||
"architecture-decisions/adrs-adr012"
|
||||
],
|
||||
"FAQ": ["FAQ"]
|
||||
}
|
||||
|
||||
@@ -100,6 +100,7 @@ nav:
|
||||
- Org Data: 'integrations/github/org.md'
|
||||
- GitLab:
|
||||
- Locations: 'integrations/gitlab/locations.md'
|
||||
- Discovery: 'integrations/gitlab/discovery.md'
|
||||
- Google GCS:
|
||||
- Locations: 'integrations/google-cloud-storage/locations.md'
|
||||
- LDAP:
|
||||
@@ -163,6 +164,7 @@ nav:
|
||||
- API Reference:
|
||||
- Guides:
|
||||
- Utility APIs: 'api/utility-apis.md'
|
||||
- Deprecations: 'api/deprecations.md'
|
||||
- Tutorials:
|
||||
- Future developer journey: 'tutorials/journey.md'
|
||||
- Migrating away from @backstage/core: 'tutorials/migrating-away-from-core.md'
|
||||
|
||||
+6
-6
@@ -45,23 +45,21 @@
|
||||
},
|
||||
"resolutions": {
|
||||
"**/@graphql-codegen/cli/**/ws": "^7.4.6",
|
||||
"**/@roadiehq/**/@backstage/core": "*",
|
||||
"**/@roadiehq/**/@backstage/plugin-catalog": "*",
|
||||
"**/@roadiehq/**/@backstage/catalog-model": "*",
|
||||
"graphql-language-service-interface": "2.8.2",
|
||||
"graphql-language-service-parser": "1.9.0"
|
||||
"**/@roadiehq/**/@backstage/catalog-model": "*"
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@microsoft/api-documenter": "^7.13.47",
|
||||
"@microsoft/api-documenter": "^7.13.68",
|
||||
"@microsoft/api-extractor": "^7.18.7",
|
||||
"@microsoft/api-extractor-model": "^7.13.5",
|
||||
"@microsoft/tsdoc": "^0.13.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/webpack": "^5.28.0",
|
||||
"@changesets/cli": "^2.14.0",
|
||||
"@octokit/rest": "^18.12.0",
|
||||
"@spotify/prettier-config": "^11.0.0",
|
||||
"@types/webpack": "^5.28.0",
|
||||
"command-exists": "^1.2.9",
|
||||
"concurrently": "^6.0.0",
|
||||
"eslint-plugin-notice": "^0.9.10",
|
||||
@@ -69,8 +67,10 @@
|
||||
"husky": "^6.0.0",
|
||||
"lerna": "^4.0.0",
|
||||
"lint-staged": "^11.1.2",
|
||||
"minimist": "^1.2.5",
|
||||
"prettier": "^2.2.1",
|
||||
"shx": "^0.3.2",
|
||||
"ts-node": "^10.4.0",
|
||||
"yarn-lock-check": "^1.0.5"
|
||||
},
|
||||
"prettier": "@spotify/prettier-config",
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
extends: [require.resolve('@backstage/cli/config/eslint')],
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
# @backstage/app-defaults
|
||||
|
||||
This package provides a default wiring of a Backstage app that avoids boilerplate when setting up a standard Backstage app.
|
||||
|
||||
## Installation
|
||||
|
||||
Install the package via Yarn:
|
||||
|
||||
```sh
|
||||
cd packages/app
|
||||
yarn add @backstage/app-defaults
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md)
|
||||
- [Backstage Documentation](https://backstage.io/docs)
|
||||
@@ -0,0 +1,26 @@
|
||||
## API Report File for "@backstage/app-defaults"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import { AppComponents } from '@backstage/core-app-api';
|
||||
import { AppIcons } from '@backstage/core-app-api';
|
||||
import { AppOptions } from '@backstage/core-app-api';
|
||||
import { AppTheme } from '@backstage/core-plugin-api';
|
||||
import { BackstageApp } from '@backstage/core-app-api';
|
||||
import { IconComponent } from '@backstage/core-plugin-api';
|
||||
|
||||
// @public
|
||||
export function createApp(
|
||||
options?: Omit<AppOptions, keyof OptionalAppOptions> & OptionalAppOptions,
|
||||
): BackstageApp;
|
||||
|
||||
// @public
|
||||
export type OptionalAppOptions = {
|
||||
icons?: Partial<AppIcons> & {
|
||||
[key in string]: IconComponent;
|
||||
};
|
||||
themes?: (Partial<AppTheme> & Omit<AppTheme, 'theme'>)[];
|
||||
components?: Partial<AppComponents>;
|
||||
};
|
||||
```
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"name": "@backstage/app-defaults",
|
||||
"description": "Provides the default wiring of a Backstage App",
|
||||
"version": "0.1.0",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "dist/index.esm.js",
|
||||
"types": "dist/index.d.ts"
|
||||
},
|
||||
"homepage": "https://backstage.io",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/backstage/backstage",
|
||||
"directory": "packages/app-defaults"
|
||||
},
|
||||
"keywords": [
|
||||
"backstage"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"scripts": {
|
||||
"build": "backstage-cli build --outputs types,esm",
|
||||
"lint": "backstage-cli lint",
|
||||
"test": "backstage-cli test",
|
||||
"prepack": "backstage-cli prepack",
|
||||
"postpack": "backstage-cli postpack",
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core-components": "^0.7.3",
|
||||
"@backstage/core-app-api": "^0.1.20",
|
||||
"@backstage/core-plugin-api": "^0.1.13",
|
||||
"@backstage/theme": "^0.2.13",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"react": "^16.12.0",
|
||||
"react-router-dom": "6.0.0-beta.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.8.2",
|
||||
"@backstage/test-utils": "^0.1.21",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^11.2.5",
|
||||
"@types/jest": "^26.0.7",
|
||||
"@types/node": "^14.14.32",
|
||||
"@types/react": "*"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2020 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 { screen } from '@testing-library/react';
|
||||
import { renderWithEffects } from '@backstage/test-utils';
|
||||
import React, { PropsWithChildren } from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { createApp } from './createApp';
|
||||
|
||||
describe('Optional ThemeProvider', () => {
|
||||
it('should render app with user-provided ThemeProvider', async () => {
|
||||
const components = {
|
||||
NotFoundErrorPage: () => null,
|
||||
BootErrorPage: () => null,
|
||||
Progress: () => null,
|
||||
Router: MemoryRouter,
|
||||
ErrorBoundaryFallback: () => null,
|
||||
ThemeProvider: ({ children }: PropsWithChildren<{}>) => (
|
||||
<main role="main">{children}</main>
|
||||
),
|
||||
};
|
||||
|
||||
const App = createApp({ components }).getProvider();
|
||||
|
||||
await renderWithEffects(<App />);
|
||||
|
||||
expect(screen.getByRole('main')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2020 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 { apis, components, icons, themes } from './defaults';
|
||||
import {
|
||||
AppTheme,
|
||||
BackstagePlugin,
|
||||
IconComponent,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import {
|
||||
AppComponents,
|
||||
AppOptions,
|
||||
AppIcons,
|
||||
createSpecializedApp,
|
||||
} from '@backstage/core-app-api';
|
||||
|
||||
/**
|
||||
* Creates a new Backstage App using a default set of components, icons and themes unless
|
||||
* they are explicitly provided.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function createApp(
|
||||
options?: Omit<AppOptions, keyof OptionalAppOptions> & OptionalAppOptions,
|
||||
) {
|
||||
return createSpecializedApp({
|
||||
...options,
|
||||
apis: options?.apis ?? [],
|
||||
bindRoutes: options?.bindRoutes,
|
||||
components: {
|
||||
...components,
|
||||
...options?.components,
|
||||
},
|
||||
configLoader: options?.configLoader,
|
||||
defaultApis: apis,
|
||||
icons: {
|
||||
...icons,
|
||||
...options?.icons,
|
||||
},
|
||||
plugins: (options?.plugins as BackstagePlugin<any, any>[]) ?? [],
|
||||
themes: options?.themes ?? themes,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The set of app options that {@link createApp} will provide defaults for
|
||||
* if they are not passed in explicitly.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type OptionalAppOptions = {
|
||||
/**
|
||||
* A set of icons to override the default icons with.
|
||||
*
|
||||
* The override is applied for each icon individually.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
icons?: Partial<AppIcons> & {
|
||||
[key in string]: IconComponent;
|
||||
};
|
||||
|
||||
/**
|
||||
* A set of themes that override all of the default app themes.
|
||||
*
|
||||
* If this option is provided none of the default themes will be used.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
themes?: (Partial<AppTheme> & Omit<AppTheme, 'theme'>)[]; // TODO: simplify once AppTheme is updated
|
||||
|
||||
/**
|
||||
* A set of components to override the default components with.
|
||||
*
|
||||
* The override is applied for each icon individually.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
components?: Partial<AppComponents>;
|
||||
};
|
||||
@@ -0,0 +1,274 @@
|
||||
/*
|
||||
* Copyright 2020 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 {
|
||||
AlertApiForwarder,
|
||||
NoOpAnalyticsApi,
|
||||
ErrorApiForwarder,
|
||||
ErrorAlerter,
|
||||
GoogleAuth,
|
||||
GithubAuth,
|
||||
OAuth2,
|
||||
OktaAuth,
|
||||
GitlabAuth,
|
||||
Auth0Auth,
|
||||
MicrosoftAuth,
|
||||
BitbucketAuth,
|
||||
OAuthRequestManager,
|
||||
WebStorage,
|
||||
UrlPatternDiscovery,
|
||||
SamlAuth,
|
||||
OneLoginAuth,
|
||||
UnhandledErrorForwarder,
|
||||
AtlassianAuth,
|
||||
} from '@backstage/core-app-api';
|
||||
|
||||
import {
|
||||
createApiFactory,
|
||||
alertApiRef,
|
||||
analyticsApiRef,
|
||||
errorApiRef,
|
||||
discoveryApiRef,
|
||||
oauthRequestApiRef,
|
||||
googleAuthApiRef,
|
||||
githubAuthApiRef,
|
||||
oauth2ApiRef,
|
||||
oktaAuthApiRef,
|
||||
gitlabAuthApiRef,
|
||||
auth0AuthApiRef,
|
||||
microsoftAuthApiRef,
|
||||
storageApiRef,
|
||||
configApiRef,
|
||||
samlAuthApiRef,
|
||||
oneloginAuthApiRef,
|
||||
oidcAuthApiRef,
|
||||
bitbucketAuthApiRef,
|
||||
atlassianAuthApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
|
||||
export const apis = [
|
||||
createApiFactory({
|
||||
api: discoveryApiRef,
|
||||
deps: { configApi: configApiRef },
|
||||
factory: ({ configApi }) =>
|
||||
UrlPatternDiscovery.compile(
|
||||
`${configApi.getString('backend.baseUrl')}/api/{{ pluginId }}`,
|
||||
),
|
||||
}),
|
||||
createApiFactory({
|
||||
api: alertApiRef,
|
||||
deps: {},
|
||||
factory: () => new AlertApiForwarder(),
|
||||
}),
|
||||
createApiFactory({
|
||||
api: analyticsApiRef,
|
||||
deps: {},
|
||||
factory: () => new NoOpAnalyticsApi(),
|
||||
}),
|
||||
createApiFactory({
|
||||
api: errorApiRef,
|
||||
deps: { alertApi: alertApiRef },
|
||||
factory: ({ alertApi }) => {
|
||||
const errorApi = new ErrorAlerter(alertApi, new ErrorApiForwarder());
|
||||
UnhandledErrorForwarder.forward(errorApi, { hidden: false });
|
||||
return errorApi;
|
||||
},
|
||||
}),
|
||||
createApiFactory({
|
||||
api: storageApiRef,
|
||||
deps: { errorApi: errorApiRef },
|
||||
factory: ({ errorApi }) => WebStorage.create({ errorApi }),
|
||||
}),
|
||||
createApiFactory({
|
||||
api: oauthRequestApiRef,
|
||||
deps: {},
|
||||
factory: () => new OAuthRequestManager(),
|
||||
}),
|
||||
createApiFactory({
|
||||
api: googleAuthApiRef,
|
||||
deps: {
|
||||
discoveryApi: discoveryApiRef,
|
||||
oauthRequestApi: oauthRequestApiRef,
|
||||
configApi: configApiRef,
|
||||
},
|
||||
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
|
||||
GoogleAuth.create({
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
environment: configApi.getOptionalString('auth.environment'),
|
||||
}),
|
||||
}),
|
||||
createApiFactory({
|
||||
api: microsoftAuthApiRef,
|
||||
deps: {
|
||||
discoveryApi: discoveryApiRef,
|
||||
oauthRequestApi: oauthRequestApiRef,
|
||||
configApi: configApiRef,
|
||||
},
|
||||
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
|
||||
MicrosoftAuth.create({
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
environment: configApi.getOptionalString('auth.environment'),
|
||||
}),
|
||||
}),
|
||||
createApiFactory({
|
||||
api: githubAuthApiRef,
|
||||
deps: {
|
||||
discoveryApi: discoveryApiRef,
|
||||
oauthRequestApi: oauthRequestApiRef,
|
||||
configApi: configApiRef,
|
||||
},
|
||||
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
|
||||
GithubAuth.create({
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
defaultScopes: ['read:user'],
|
||||
environment: configApi.getOptionalString('auth.environment'),
|
||||
}),
|
||||
}),
|
||||
createApiFactory({
|
||||
api: oktaAuthApiRef,
|
||||
deps: {
|
||||
discoveryApi: discoveryApiRef,
|
||||
oauthRequestApi: oauthRequestApiRef,
|
||||
configApi: configApiRef,
|
||||
},
|
||||
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
|
||||
OktaAuth.create({
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
environment: configApi.getOptionalString('auth.environment'),
|
||||
}),
|
||||
}),
|
||||
createApiFactory({
|
||||
api: gitlabAuthApiRef,
|
||||
deps: {
|
||||
discoveryApi: discoveryApiRef,
|
||||
oauthRequestApi: oauthRequestApiRef,
|
||||
configApi: configApiRef,
|
||||
},
|
||||
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
|
||||
GitlabAuth.create({
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
environment: configApi.getOptionalString('auth.environment'),
|
||||
}),
|
||||
}),
|
||||
createApiFactory({
|
||||
api: auth0AuthApiRef,
|
||||
deps: {
|
||||
discoveryApi: discoveryApiRef,
|
||||
oauthRequestApi: oauthRequestApiRef,
|
||||
configApi: configApiRef,
|
||||
},
|
||||
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
|
||||
Auth0Auth.create({
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
environment: configApi.getOptionalString('auth.environment'),
|
||||
}),
|
||||
}),
|
||||
createApiFactory({
|
||||
api: oauth2ApiRef,
|
||||
deps: {
|
||||
discoveryApi: discoveryApiRef,
|
||||
oauthRequestApi: oauthRequestApiRef,
|
||||
configApi: configApiRef,
|
||||
},
|
||||
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
|
||||
OAuth2.create({
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
environment: configApi.getOptionalString('auth.environment'),
|
||||
}),
|
||||
}),
|
||||
createApiFactory({
|
||||
api: samlAuthApiRef,
|
||||
deps: {
|
||||
discoveryApi: discoveryApiRef,
|
||||
configApi: configApiRef,
|
||||
},
|
||||
factory: ({ discoveryApi, configApi }) =>
|
||||
SamlAuth.create({
|
||||
discoveryApi,
|
||||
environment: configApi.getOptionalString('auth.environment'),
|
||||
}),
|
||||
}),
|
||||
createApiFactory({
|
||||
api: oneloginAuthApiRef,
|
||||
deps: {
|
||||
discoveryApi: discoveryApiRef,
|
||||
oauthRequestApi: oauthRequestApiRef,
|
||||
configApi: configApiRef,
|
||||
},
|
||||
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
|
||||
OneLoginAuth.create({
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
environment: configApi.getOptionalString('auth.environment'),
|
||||
}),
|
||||
}),
|
||||
createApiFactory({
|
||||
api: oidcAuthApiRef,
|
||||
deps: {
|
||||
discoveryApi: discoveryApiRef,
|
||||
oauthRequestApi: oauthRequestApiRef,
|
||||
configApi: configApiRef,
|
||||
},
|
||||
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
|
||||
OAuth2.create({
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
provider: {
|
||||
id: 'oidc',
|
||||
title: 'Your Identity Provider',
|
||||
icon: () => null,
|
||||
},
|
||||
environment: configApi.getOptionalString('auth.environment'),
|
||||
}),
|
||||
}),
|
||||
createApiFactory({
|
||||
api: bitbucketAuthApiRef,
|
||||
deps: {
|
||||
discoveryApi: discoveryApiRef,
|
||||
oauthRequestApi: oauthRequestApiRef,
|
||||
configApi: configApiRef,
|
||||
},
|
||||
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
|
||||
BitbucketAuth.create({
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
defaultScopes: ['team'],
|
||||
environment: configApi.getOptionalString('auth.environment'),
|
||||
}),
|
||||
}),
|
||||
createApiFactory({
|
||||
api: atlassianAuthApiRef,
|
||||
deps: {
|
||||
discoveryApi: discoveryApiRef,
|
||||
oauthRequestApi: oauthRequestApiRef,
|
||||
configApi: configApiRef,
|
||||
},
|
||||
factory: ({ discoveryApi, oauthRequestApi, configApi }) => {
|
||||
return AtlassianAuth.create({
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
environment: configApi.getOptionalString('auth.environment'),
|
||||
});
|
||||
},
|
||||
}),
|
||||
];
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2020 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 { render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { OptionallyWrapInRouter } from './components';
|
||||
|
||||
describe('OptionallyWrapInRouter', () => {
|
||||
it('should wrap with router if not yet inside a router', async () => {
|
||||
render(<OptionallyWrapInRouter>Test</OptionallyWrapInRouter>);
|
||||
|
||||
expect(screen.getByText('Test')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not wrap with router if already inside a router', async () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<OptionallyWrapInRouter>Test</OptionallyWrapInRouter>
|
||||
</MemoryRouter>,
|
||||
);
|
||||
|
||||
expect(screen.getByText('Test')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2021 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 React, { ReactNode } from 'react';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import { ErrorPanel, Progress, ErrorPage } from '@backstage/core-components';
|
||||
import {
|
||||
MemoryRouter,
|
||||
useInRouterContext,
|
||||
BrowserRouter,
|
||||
} from 'react-router-dom';
|
||||
import {
|
||||
AppComponents,
|
||||
BootErrorPageProps,
|
||||
ErrorBoundaryFallbackProps,
|
||||
} from '@backstage/core-plugin-api';
|
||||
|
||||
export function OptionallyWrapInRouter({ children }: { children: ReactNode }) {
|
||||
if (useInRouterContext()) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
return <MemoryRouter>{children}</MemoryRouter>;
|
||||
}
|
||||
|
||||
const DefaultNotFoundPage = () => (
|
||||
<ErrorPage status="404" statusMessage="PAGE NOT FOUND" />
|
||||
);
|
||||
|
||||
const DefaultBootErrorPage = ({ step, error }: BootErrorPageProps) => {
|
||||
let message = '';
|
||||
if (step === 'load-config') {
|
||||
message = `The configuration failed to load, someone should have a look at this error: ${error.message}`;
|
||||
} else if (step === 'load-chunk') {
|
||||
message = `Lazy loaded chunk failed to load, try to reload the page: ${error.message}`;
|
||||
}
|
||||
// TODO: figure out a nicer way to handle routing on the error page, when it can be done.
|
||||
return (
|
||||
<OptionallyWrapInRouter>
|
||||
<ErrorPage status="501" statusMessage={message} />
|
||||
</OptionallyWrapInRouter>
|
||||
);
|
||||
};
|
||||
|
||||
const DefaultErrorBoundaryFallback = ({
|
||||
error,
|
||||
resetError,
|
||||
plugin,
|
||||
}: ErrorBoundaryFallbackProps) => {
|
||||
return (
|
||||
<ErrorPanel
|
||||
title={`Error in ${plugin?.getId()}`}
|
||||
defaultExpanded
|
||||
error={error}
|
||||
>
|
||||
<Button variant="outlined" onClick={resetError}>
|
||||
Retry
|
||||
</Button>
|
||||
</ErrorPanel>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a set of default components to pass along to {@link @backstage/core-app-api#createApp}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const components: AppComponents = {
|
||||
Progress,
|
||||
Router: BrowserRouter,
|
||||
NotFoundErrorPage: DefaultNotFoundPage,
|
||||
BootErrorPage: DefaultBootErrorPage,
|
||||
ErrorBoundaryFallback: DefaultErrorBoundaryFallback,
|
||||
};
|
||||
+25
-41
@@ -18,6 +18,9 @@ import { IconComponent } from '@backstage/core-plugin-api';
|
||||
import MuiApartmentIcon from '@material-ui/icons/Apartment';
|
||||
import MuiBrokenImageIcon from '@material-ui/icons/BrokenImage';
|
||||
import MuiCategoryIcon from '@material-ui/icons/Category';
|
||||
import MuiCreateNewFolderIcon from '@material-ui/icons/CreateNewFolder';
|
||||
import MuiSubjectIcon from '@material-ui/icons/Subject';
|
||||
import MuiSearchIcon from '@material-ui/icons/Search';
|
||||
import MuiChatIcon from '@material-ui/icons/Chat';
|
||||
import MuiDashboardIcon from '@material-ui/icons/Dashboard';
|
||||
import MuiDocsIcon from '@material-ui/icons/Description';
|
||||
@@ -32,46 +35,27 @@ import MuiPeopleIcon from '@material-ui/icons/People';
|
||||
import MuiPersonIcon from '@material-ui/icons/Person';
|
||||
import MuiWarningIcon from '@material-ui/icons/Warning';
|
||||
|
||||
type AppIconsKey =
|
||||
| 'brokenImage'
|
||||
| 'catalog'
|
||||
| 'chat'
|
||||
| 'dashboard'
|
||||
| 'docs'
|
||||
| 'email'
|
||||
| 'github'
|
||||
| 'group'
|
||||
| 'help'
|
||||
| 'kind:api'
|
||||
| 'kind:component'
|
||||
| 'kind:domain'
|
||||
| 'kind:group'
|
||||
| 'kind:location'
|
||||
| 'kind:system'
|
||||
| 'kind:user'
|
||||
| 'user'
|
||||
| 'warning';
|
||||
|
||||
export type AppIcons = { [key in AppIconsKey]: IconComponent };
|
||||
|
||||
export const defaultAppIcons: AppIcons = {
|
||||
brokenImage: MuiBrokenImageIcon,
|
||||
export const icons = {
|
||||
brokenImage: MuiBrokenImageIcon as IconComponent,
|
||||
// To be confirmed: see https://github.com/backstage/backstage/issues/4970
|
||||
catalog: MuiMenuBookIcon,
|
||||
chat: MuiChatIcon,
|
||||
dashboard: MuiDashboardIcon,
|
||||
docs: MuiDocsIcon,
|
||||
email: MuiEmailIcon,
|
||||
github: MuiGitHubIcon,
|
||||
group: MuiPeopleIcon,
|
||||
help: MuiHelpIcon,
|
||||
'kind:api': MuiExtensionIcon,
|
||||
'kind:component': MuiMemoryIcon,
|
||||
'kind:domain': MuiApartmentIcon,
|
||||
'kind:group': MuiPeopleIcon,
|
||||
'kind:location': MuiLocationOnIcon,
|
||||
'kind:system': MuiCategoryIcon,
|
||||
'kind:user': MuiPersonIcon,
|
||||
user: MuiPersonIcon,
|
||||
warning: MuiWarningIcon,
|
||||
catalog: MuiMenuBookIcon as IconComponent,
|
||||
scaffolder: MuiCreateNewFolderIcon as IconComponent,
|
||||
techdocs: MuiSubjectIcon as IconComponent,
|
||||
search: MuiSearchIcon as IconComponent,
|
||||
chat: MuiChatIcon as IconComponent,
|
||||
dashboard: MuiDashboardIcon as IconComponent,
|
||||
docs: MuiDocsIcon as IconComponent,
|
||||
email: MuiEmailIcon as IconComponent,
|
||||
github: MuiGitHubIcon as IconComponent,
|
||||
group: MuiPeopleIcon as IconComponent,
|
||||
help: MuiHelpIcon as IconComponent,
|
||||
'kind:api': MuiExtensionIcon as IconComponent,
|
||||
'kind:component': MuiMemoryIcon as IconComponent,
|
||||
'kind:domain': MuiApartmentIcon as IconComponent,
|
||||
'kind:group': MuiPeopleIcon as IconComponent,
|
||||
'kind:location': MuiLocationOnIcon as IconComponent,
|
||||
'kind:system': MuiCategoryIcon as IconComponent,
|
||||
'kind:user': MuiPersonIcon as IconComponent,
|
||||
user: MuiPersonIcon as IconComponent,
|
||||
warning: MuiWarningIcon as IconComponent,
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
|
||||
export { apis } from './apis';
|
||||
export { components } from './components';
|
||||
export { icons } from './icons';
|
||||
export { themes } from './themes';
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2021 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 React from 'react';
|
||||
import { darkTheme, lightTheme } from '@backstage/theme';
|
||||
import DarkIcon from '@material-ui/icons/Brightness2';
|
||||
import LightIcon from '@material-ui/icons/WbSunny';
|
||||
import { ThemeProvider } from '@material-ui/core/styles';
|
||||
import CssBaseline from '@material-ui/core/CssBaseline';
|
||||
import { AppTheme } from '@backstage/core-plugin-api';
|
||||
|
||||
export const themes: AppTheme[] = [
|
||||
{
|
||||
id: 'light',
|
||||
title: 'Light Theme',
|
||||
variant: 'light',
|
||||
icon: <LightIcon />,
|
||||
theme: lightTheme,
|
||||
Provider: ({ children }) => (
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<CssBaseline>{children}</CssBaseline>
|
||||
</ThemeProvider>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'dark',
|
||||
title: 'Dark Theme',
|
||||
variant: 'dark',
|
||||
icon: <DarkIcon />,
|
||||
theme: darkTheme,
|
||||
Provider: ({ children }) => (
|
||||
<ThemeProvider theme={darkTheme}>
|
||||
<CssBaseline>{children}</CssBaseline>
|
||||
</ThemeProvider>
|
||||
),
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides the default wiring of a Backstage App
|
||||
*
|
||||
* @packageDocumentation
|
||||
*/
|
||||
|
||||
export { createApp } from './createApp';
|
||||
export type { OptionalAppOptions } from './createApp';
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2020 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 '@testing-library/jest-dom';
|
||||
@@ -4,6 +4,7 @@
|
||||
"private": true,
|
||||
"bundled": true,
|
||||
"dependencies": {
|
||||
"@backstage/app-defaults": "^0.1.0",
|
||||
"@backstage/catalog-model": "^0.9.5",
|
||||
"@backstage/cli": "^0.8.0",
|
||||
"@backstage/core-app-api": "^0.1.18",
|
||||
|
||||
@@ -26,7 +26,8 @@ import {
|
||||
RELATION_PART_OF,
|
||||
RELATION_PROVIDES_API,
|
||||
} from '@backstage/catalog-model';
|
||||
import { createApp, FlatRoutes } from '@backstage/core-app-api';
|
||||
import { createApp } from '@backstage/app-defaults';
|
||||
import { FlatRoutes } from '@backstage/core-app-api';
|
||||
import {
|
||||
AlertDisplay,
|
||||
OAuthRequestDialog,
|
||||
@@ -93,7 +94,6 @@ const app = createApp({
|
||||
// Custom icon example
|
||||
alert: AlarmIcon,
|
||||
},
|
||||
|
||||
components: {
|
||||
SignInPage: props => {
|
||||
return (
|
||||
|
||||
@@ -29,7 +29,7 @@ import LogoIcon from './LogoIcon';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import { GraphiQLIcon } from '@backstage/plugin-graphiql';
|
||||
import { Settings as SidebarSettings } from '@backstage/plugin-user-settings';
|
||||
import { SidebarSearch } from '@backstage/plugin-search';
|
||||
import { SidebarSearchModal } from '@backstage/plugin-search';
|
||||
import { Shortcuts } from '@backstage/plugin-shortcuts';
|
||||
import {
|
||||
Sidebar,
|
||||
@@ -79,7 +79,7 @@ export const Root = ({ children }: PropsWithChildren<{}>) => (
|
||||
<SidebarPage>
|
||||
<Sidebar>
|
||||
<SidebarLogo />
|
||||
<SidebarSearch />
|
||||
<SidebarSearchModal />
|
||||
<SidebarDivider />
|
||||
{/* Global nav, not org-specific */}
|
||||
<SidebarItem icon={HomeIcon} to="catalog" text="Home" />
|
||||
|
||||
@@ -1,5 +1,28 @@
|
||||
# @backstage/backend-common
|
||||
|
||||
## 0.9.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 8c4cad0bf2: AWSS3UrlReader now throws a `NotModifiedError` (exported from @backstage/backend-common) when s3 returns a 304 response.
|
||||
- 0611f3b3e2: Reading app config from a remote server
|
||||
- Updated dependencies
|
||||
- @backstage/config-loader@0.7.2
|
||||
|
||||
## 0.9.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 96cfa561eb: Adjusted some API exports
|
||||
- 10615525f3: Switch to use the json and observable types from `@backstage/types`
|
||||
- 1be8d2abdb: Any set configurations which have been tagged with a visibility 'secret', are now redacted from log lines.
|
||||
- Updated dependencies
|
||||
- @backstage/config@0.1.11
|
||||
- @backstage/cli-common@0.1.5
|
||||
- @backstage/errors@0.1.4
|
||||
- @backstage/integration@0.6.9
|
||||
- @backstage/config-loader@0.7.1
|
||||
|
||||
## 0.9.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -211,6 +211,12 @@ export type ErrorHandlerOptions = {
|
||||
logClientErrors?: boolean;
|
||||
};
|
||||
|
||||
// @public
|
||||
export type FromReadableArrayOptions = Array<{
|
||||
data: Readable;
|
||||
path: string;
|
||||
}>;
|
||||
|
||||
// @public (undocumented)
|
||||
export function getRootLogger(): winston.Logger;
|
||||
|
||||
@@ -368,6 +374,9 @@ export class GitlabUrlReader implements UrlReader {
|
||||
|
||||
export { isChildPath };
|
||||
|
||||
// @public
|
||||
export function isDatabaseConflictError(e: unknown): boolean;
|
||||
|
||||
// @public
|
||||
export function loadBackendConfig(options: {
|
||||
logger: Logger_2;
|
||||
@@ -419,15 +428,13 @@ export type ReadTreeResponse = {
|
||||
etag: string;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
// @public
|
||||
export type ReadTreeResponseDirOptions = {
|
||||
targetDir?: string;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ReadTreeResponseFactory {
|
||||
// Warning: (ae-forgotten-export) The symbol "FromReadableArrayOptions" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// (undocumented)
|
||||
fromReadableArray(
|
||||
options: FromReadableArrayOptions,
|
||||
@@ -442,7 +449,7 @@ export interface ReadTreeResponseFactory {
|
||||
): Promise<ReadTreeResponse>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
// @public
|
||||
export type ReadTreeResponseFactoryOptions = {
|
||||
stream: Readable;
|
||||
subpath?: string;
|
||||
@@ -580,7 +587,7 @@ export type UrlReader = {
|
||||
search(url: string, options?: SearchOptions): Promise<SearchResponse>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
// @public
|
||||
export type UrlReaderPredicateTuple = {
|
||||
predicate: (url: URL) => boolean;
|
||||
reader: UrlReader;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/backend-common",
|
||||
"description": "Common functionality library for Backstage backends",
|
||||
"version": "0.9.7",
|
||||
"version": "0.9.9",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"private": false,
|
||||
@@ -29,13 +29,14 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/cli-common": "^0.1.4",
|
||||
"@backstage/config": "^0.1.10",
|
||||
"@backstage/config-loader": "^0.7.0",
|
||||
"@backstage/errors": "^0.1.3",
|
||||
"@backstage/integration": "^0.6.7",
|
||||
"@backstage/cli-common": "^0.1.5",
|
||||
"@backstage/config": "^0.1.11",
|
||||
"@backstage/config-loader": "^0.7.2",
|
||||
"@backstage/errors": "^0.1.4",
|
||||
"@backstage/integration": "^0.6.9",
|
||||
"@backstage/types": "^0.1.1",
|
||||
"@google-cloud/storage": "^5.8.0",
|
||||
"@lerna/project": "^4.0.0",
|
||||
"@octokit/rest": "^18.5.3",
|
||||
"@types/cors": "^2.8.6",
|
||||
"@types/dockerode": "^3.2.1",
|
||||
@@ -78,8 +79,8 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.8.0",
|
||||
"@backstage/test-utils": "^0.1.18",
|
||||
"@backstage/cli": "^0.8.2",
|
||||
"@backstage/test-utils": "^0.1.21",
|
||||
"@types/archiver": "^5.1.0",
|
||||
"@types/compression": "^1.7.0",
|
||||
"@types/concat-stream": "^1.6.0",
|
||||
@@ -99,7 +100,7 @@
|
||||
"http-errors": "^1.7.3",
|
||||
"jest": "^26.0.1",
|
||||
"mock-fs": "^5.1.0",
|
||||
"msw": "^0.29.0",
|
||||
"msw": "^0.35.0",
|
||||
"mysql2": "^2.2.5",
|
||||
"recursive-readdir": "^2.2.2",
|
||||
"supertest": "^6.1.3"
|
||||
|
||||
@@ -18,9 +18,41 @@ import { resolve as resolvePath } from 'path';
|
||||
import parseArgs from 'minimist';
|
||||
import { Logger } from 'winston';
|
||||
import { findPaths } from '@backstage/cli-common';
|
||||
import { Config, ConfigReader } from '@backstage/config';
|
||||
import {
|
||||
loadConfigSchema,
|
||||
loadConfig,
|
||||
ConfigSchema,
|
||||
ConfigTarget,
|
||||
} from '@backstage/config-loader';
|
||||
import { AppConfig, Config, ConfigReader } from '@backstage/config';
|
||||
import { JsonValue } from '@backstage/types';
|
||||
import { loadConfig } from '@backstage/config-loader';
|
||||
|
||||
import { isValidUrl } from './urls';
|
||||
|
||||
import { setRootLoggerRedactionList } from './logging/rootLogger';
|
||||
|
||||
// Fetch the schema and get all the secrets to pass to the rootLogger for redaction
|
||||
const updateRedactionList = (
|
||||
schema: ConfigSchema,
|
||||
configs: AppConfig[],
|
||||
logger: Logger,
|
||||
) => {
|
||||
const secretAppConfigs = schema.process(configs, { visibility: ['secret'] });
|
||||
const secretConfig = ConfigReader.fromConfigs(secretAppConfigs);
|
||||
const values = new Set<string>();
|
||||
const data = secretConfig.get();
|
||||
|
||||
JSON.parse(
|
||||
JSON.stringify(data),
|
||||
(_, v) => typeof v === 'string' && values.add(v),
|
||||
);
|
||||
|
||||
logger.info(
|
||||
`${values.size} secrets found in the config which will be redacted`,
|
||||
);
|
||||
|
||||
setRootLoggerRedactionList(Array.from(values));
|
||||
};
|
||||
|
||||
export class ObservableConfigProxy implements Config {
|
||||
private config: Config = new ConfigReader({});
|
||||
@@ -149,16 +181,29 @@ export async function loadBackendConfig(options: {
|
||||
argv: string[];
|
||||
}): Promise<Config> {
|
||||
const args = parseArgs(options.argv);
|
||||
const configPaths: string[] = [args.config ?? []].flat();
|
||||
|
||||
const config = new ObservableConfigProxy(options.logger);
|
||||
const configTargets: ConfigTarget[] = [args.config ?? []]
|
||||
.flat()
|
||||
.map(arg => (isValidUrl(arg) ? { url: arg } : { path: resolvePath(arg) }));
|
||||
|
||||
/* eslint-disable-next-line no-restricted-syntax */
|
||||
const paths = findPaths(__dirname);
|
||||
|
||||
// TODO(hhogg): This is fetching _all_ of the packages of the monorepo
|
||||
// in order to find the secrets for redactions, however we only care about
|
||||
// the backend ones, we need to find a way to exclude the frontend packages.
|
||||
const { Project } = require('@lerna/project');
|
||||
const project = new Project(paths.targetDir);
|
||||
const packages = await project.getPackages();
|
||||
const schema = await loadConfigSchema({
|
||||
dependencies: packages.map((p: any) => p.name),
|
||||
});
|
||||
|
||||
const config = new ObservableConfigProxy(options.logger);
|
||||
const configs = await loadConfig({
|
||||
configRoot: paths.targetRoot,
|
||||
configPaths: configPaths.map(opt => resolvePath(opt)),
|
||||
configPaths: [],
|
||||
configTargets: configTargets,
|
||||
watch: {
|
||||
onChange(newConfigs) {
|
||||
options.logger.info(
|
||||
@@ -187,5 +232,9 @@ export async function loadBackendConfig(options: {
|
||||
|
||||
config.setConfig(ConfigReader.fromConfigs(configs));
|
||||
|
||||
// Subscribe to config changes and update the redaction list for logging
|
||||
updateRedactionList(schema, configs, options.logger);
|
||||
config.subscribe(() => updateRedactionList(schema, configs, options.logger));
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -13,13 +13,14 @@
|
||||
* 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 } from '@backstage/config';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import {
|
||||
ensureDatabaseExists,
|
||||
createNameOverride,
|
||||
ensureDatabaseExists,
|
||||
normalizeConnection,
|
||||
createSchemaOverride,
|
||||
ensureSchemaExists,
|
||||
@@ -182,7 +183,7 @@ export class DatabaseManager {
|
||||
);
|
||||
|
||||
return {
|
||||
// include base connection if client type has not been overriden
|
||||
// include base connection if client type has not been overridden
|
||||
...(overridden ? {} : baseConnection),
|
||||
...connection,
|
||||
};
|
||||
|
||||
@@ -28,3 +28,4 @@ export {
|
||||
} from './connection';
|
||||
|
||||
export type { PluginDatabaseManager } from './types';
|
||||
export { isDatabaseConflictError } from './util';
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Tries to deduce whether a thrown error is a database conflict.
|
||||
*
|
||||
* @public
|
||||
* @param e - A thrown error
|
||||
* @returns True if the error looks like it was a conflict error thrown by a
|
||||
* known database engine
|
||||
*/
|
||||
export function isDatabaseConflictError(e: unknown) {
|
||||
const message = (e as any)?.message;
|
||||
|
||||
return (
|
||||
typeof message === 'string' &&
|
||||
(/SQLITE_CONSTRAINT: UNIQUE/.test(message) ||
|
||||
/unique constraint/.test(message))
|
||||
);
|
||||
}
|
||||
@@ -15,5 +15,5 @@
|
||||
*/
|
||||
|
||||
export * from './formats';
|
||||
export * from './rootLogger';
|
||||
export { createRootLogger, getRootLogger, setRootLogger } from './rootLogger';
|
||||
export * from './voidLogger';
|
||||
|
||||
@@ -15,7 +15,12 @@
|
||||
*/
|
||||
|
||||
import * as winston from 'winston';
|
||||
import { createRootLogger, getRootLogger, setRootLogger } from './rootLogger';
|
||||
import {
|
||||
createRootLogger,
|
||||
getRootLogger,
|
||||
setRootLogger,
|
||||
setRootLoggerRedactionList,
|
||||
} from './rootLogger';
|
||||
|
||||
describe('rootLogger', () => {
|
||||
it('can replace the default logger', () => {
|
||||
@@ -30,7 +35,20 @@ describe('rootLogger', () => {
|
||||
);
|
||||
});
|
||||
|
||||
describe('createRootLoger', () => {
|
||||
it('redacts given secrets', () => {
|
||||
const logger = createRootLogger();
|
||||
jest.spyOn(logger, 'write');
|
||||
setRootLoggerRedactionList(['SECRET-1', 'SECRET_2', 'SECRET.3']);
|
||||
logger.info('Logging SECRET-1 and SECRET_2 and SECRET.3');
|
||||
|
||||
expect(logger.write).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
message: 'Logging [REDACTED] and [REDACTED] and [REDACTED]',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
describe('createRootLogger', () => {
|
||||
it('creates a new logger', () => {
|
||||
const oldLogger = getRootLogger();
|
||||
const newLogger = createRootLogger();
|
||||
|
||||
@@ -18,8 +18,10 @@ import { merge } from 'lodash';
|
||||
import * as winston from 'winston';
|
||||
import { LoggerOptions } from 'winston';
|
||||
import { coloredFormat } from './formats';
|
||||
import { escapeRegExp } from '../util/escapeRegExp';
|
||||
|
||||
let rootLogger: winston.Logger;
|
||||
let redactionRegExp: RegExp;
|
||||
|
||||
/** @public */
|
||||
export function getRootLogger(): winston.Logger {
|
||||
@@ -31,6 +33,30 @@ export function setRootLogger(newLogger: winston.Logger) {
|
||||
rootLogger = newLogger;
|
||||
}
|
||||
|
||||
export function setRootLoggerRedactionList(redactionList: string[]) {
|
||||
if (redactionList.length) {
|
||||
redactionRegExp = new RegExp(
|
||||
`(${redactionList.map(escapeRegExp).join('|')})`,
|
||||
'g',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A winston formatting function that finds occurrences of filteredKeys
|
||||
* and replaces them with the corresponding identifier.
|
||||
*/
|
||||
function redactLogLine(info: winston.Logform.TransformableInfo) {
|
||||
// TODO(hhogg): The logger is created before the config is loaded,
|
||||
// because the logger is needed in the config loader. There is a risk of
|
||||
// a secret being logged out during the config loading stage.
|
||||
if (redactionRegExp) {
|
||||
info.message = info.message.replace(redactionRegExp, '[REDACTED]');
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export function createRootLogger(
|
||||
options: winston.LoggerOptions = {},
|
||||
@@ -41,6 +67,7 @@ export function createRootLogger(
|
||||
{
|
||||
level: env.LOG_LEVEL || 'info',
|
||||
format: winston.format.combine(
|
||||
winston.format(redactLogLine)(),
|
||||
env.NODE_ENV === 'production' ? winston.format.json() : coloredFormat,
|
||||
),
|
||||
defaultMeta: {
|
||||
|
||||
@@ -27,6 +27,7 @@ import { UrlReaderPredicateTuple } from './types';
|
||||
import AWSMock from 'aws-sdk-mock';
|
||||
import aws from 'aws-sdk';
|
||||
import path from 'path';
|
||||
import { NotModifiedError } from '@backstage/errors';
|
||||
|
||||
const treeResponseFactory = DefaultReadTreeResponseFactory.create({
|
||||
config: new ConfigReader({}),
|
||||
@@ -131,29 +132,37 @@ describe('AwsS3UrlReader', () => {
|
||||
});
|
||||
|
||||
describe('read', () => {
|
||||
AWSMock.setSDKInstance(aws);
|
||||
AWSMock.mock(
|
||||
'S3',
|
||||
'getObject',
|
||||
Buffer.from(
|
||||
require('fs').readFileSync(
|
||||
path.resolve(__dirname, '__fixtures__/awsS3/awsS3-mock-object.yaml'),
|
||||
let awsS3UrlReader: AwsS3UrlReader;
|
||||
|
||||
beforeAll(() => {
|
||||
AWSMock.setSDKInstance(aws);
|
||||
AWSMock.mock(
|
||||
'S3',
|
||||
'getObject',
|
||||
Buffer.from(
|
||||
require('fs').readFileSync(
|
||||
path.resolve(
|
||||
__dirname,
|
||||
'__fixtures__/awsS3/awsS3-mock-object.yaml',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
const s3 = new aws.S3();
|
||||
const awsS3UrlReader = new AwsS3UrlReader(
|
||||
new AwsS3Integration(
|
||||
readAwsS3IntegrationConfig(
|
||||
new ConfigReader({
|
||||
host: 'amazonaws.com',
|
||||
accessKeyId: 'fake-access-key',
|
||||
secretAccessKey: 'fake-secret-key',
|
||||
}),
|
||||
);
|
||||
|
||||
const s3 = new aws.S3();
|
||||
awsS3UrlReader = new AwsS3UrlReader(
|
||||
new AwsS3Integration(
|
||||
readAwsS3IntegrationConfig(
|
||||
new ConfigReader({
|
||||
host: 'amazonaws.com',
|
||||
accessKeyId: 'fake-access-key',
|
||||
secretAccessKey: 'fake-secret-key',
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
{ s3, treeResponseFactory },
|
||||
);
|
||||
{ s3, treeResponseFactory },
|
||||
);
|
||||
});
|
||||
|
||||
it('returns contents of an object in a bucket', async () => {
|
||||
const response = await awsS3UrlReader.read(
|
||||
@@ -176,32 +185,39 @@ describe('AwsS3UrlReader', () => {
|
||||
});
|
||||
|
||||
describe('readUrl', () => {
|
||||
AWSMock.setSDKInstance(aws);
|
||||
let awsS3UrlReader: AwsS3UrlReader;
|
||||
|
||||
AWSMock.mock(
|
||||
'S3',
|
||||
'getObject',
|
||||
Buffer.from(
|
||||
require('fs').readFileSync(
|
||||
path.resolve(__dirname, '__fixtures__/awsS3/awsS3-mock-object.yaml'),
|
||||
beforeAll(() => {
|
||||
AWSMock.setSDKInstance(aws);
|
||||
|
||||
AWSMock.mock(
|
||||
'S3',
|
||||
'getObject',
|
||||
Buffer.from(
|
||||
require('fs').readFileSync(
|
||||
path.resolve(
|
||||
__dirname,
|
||||
'__fixtures__/awsS3/awsS3-mock-object.yaml',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
|
||||
const s3 = new aws.S3();
|
||||
const s3 = new aws.S3();
|
||||
|
||||
const awsS3UrlReader = new AwsS3UrlReader(
|
||||
new AwsS3Integration(
|
||||
readAwsS3IntegrationConfig(
|
||||
new ConfigReader({
|
||||
host: 'amazonaws.com',
|
||||
accessKeyId: 'fake-access-key',
|
||||
secretAccessKey: 'fake-secret-key',
|
||||
}),
|
||||
awsS3UrlReader = new AwsS3UrlReader(
|
||||
new AwsS3Integration(
|
||||
readAwsS3IntegrationConfig(
|
||||
new ConfigReader({
|
||||
host: 'amazonaws.com',
|
||||
accessKeyId: 'fake-access-key',
|
||||
secretAccessKey: 'fake-secret-key',
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
{ s3, treeResponseFactory },
|
||||
);
|
||||
{ s3, treeResponseFactory },
|
||||
);
|
||||
});
|
||||
|
||||
it('returns contents of an object in a bucket', async () => {
|
||||
const response = await awsS3UrlReader.readUrl(
|
||||
@@ -223,40 +239,89 @@ describe('AwsS3UrlReader', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('readUrl with etag', () => {
|
||||
let awsS3UrlReader: AwsS3UrlReader;
|
||||
|
||||
beforeAll(() => {
|
||||
AWSMock.setSDKInstance(aws);
|
||||
|
||||
AWSMock.mock('S3', 'getObject', (_, callback) => {
|
||||
callback({ statusCode: 304 }, null);
|
||||
});
|
||||
|
||||
const s3 = new aws.S3();
|
||||
|
||||
awsS3UrlReader = new AwsS3UrlReader(
|
||||
new AwsS3Integration(
|
||||
readAwsS3IntegrationConfig(
|
||||
new ConfigReader({
|
||||
host: 'amazonaws.com',
|
||||
accessKeyId: 'fake-access-key',
|
||||
secretAccessKey: 'fake-secret-key',
|
||||
}),
|
||||
),
|
||||
),
|
||||
{ s3, treeResponseFactory },
|
||||
);
|
||||
});
|
||||
|
||||
it('returns contents of an object in a bucket', async () => {
|
||||
await expect(
|
||||
awsS3UrlReader.readUrl(
|
||||
'https://test-bucket.s3.us-east-2.amazonaws.com/awsS3-mock-object.yaml',
|
||||
{
|
||||
etag: 'abc123',
|
||||
},
|
||||
),
|
||||
).rejects.toThrow(NotModifiedError);
|
||||
});
|
||||
});
|
||||
|
||||
describe('readTree', () => {
|
||||
const object: aws.S3.Types.Object = {
|
||||
Key: 'awsS3-mock-object.yaml',
|
||||
};
|
||||
const objectList: aws.S3.ObjectList = [object];
|
||||
const output: aws.S3.Types.ListObjectsV2Output = {
|
||||
Contents: objectList,
|
||||
};
|
||||
AWSMock.setSDKInstance(aws);
|
||||
AWSMock.mock('S3', 'listObjectsV2', output);
|
||||
let awsS3UrlReader: AwsS3UrlReader;
|
||||
|
||||
AWSMock.mock(
|
||||
'S3',
|
||||
'getObject',
|
||||
Buffer.from(
|
||||
require('fs').readFileSync(
|
||||
path.resolve(__dirname, '__fixtures__/awsS3/awsS3-mock-object.yaml'),
|
||||
),
|
||||
),
|
||||
);
|
||||
beforeAll(() => {
|
||||
const object: aws.S3.Types.Object = {
|
||||
Key: 'awsS3-mock-object.yaml',
|
||||
};
|
||||
|
||||
const s3 = new aws.S3();
|
||||
const awsS3UrlReader = new AwsS3UrlReader(
|
||||
new AwsS3Integration(
|
||||
readAwsS3IntegrationConfig(
|
||||
new ConfigReader({
|
||||
host: '.amazonaws.com',
|
||||
accessKeyId: 'fake-access-key',
|
||||
secretAccessKey: 'fake-secret-key',
|
||||
}),
|
||||
const objectList: aws.S3.ObjectList = [object];
|
||||
const output: aws.S3.Types.ListObjectsV2Output = {
|
||||
Contents: objectList,
|
||||
};
|
||||
|
||||
AWSMock.setSDKInstance(aws);
|
||||
AWSMock.mock('S3', 'listObjectsV2', output);
|
||||
|
||||
AWSMock.mock(
|
||||
'S3',
|
||||
'getObject',
|
||||
Buffer.from(
|
||||
require('fs').readFileSync(
|
||||
path.resolve(
|
||||
__dirname,
|
||||
'__fixtures__/awsS3/awsS3-mock-object.yaml',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
{ s3, treeResponseFactory },
|
||||
);
|
||||
);
|
||||
|
||||
const s3 = new aws.S3();
|
||||
awsS3UrlReader = new AwsS3UrlReader(
|
||||
new AwsS3Integration(
|
||||
readAwsS3IntegrationConfig(
|
||||
new ConfigReader({
|
||||
host: '.amazonaws.com',
|
||||
accessKeyId: 'fake-access-key',
|
||||
secretAccessKey: 'fake-secret-key',
|
||||
}),
|
||||
),
|
||||
),
|
||||
{ s3, treeResponseFactory },
|
||||
);
|
||||
});
|
||||
|
||||
it('returns contents of an object in a bucket', async () => {
|
||||
const response = await awsS3UrlReader.readTree(
|
||||
'https://test.s3.us-east-2.amazonaws.com',
|
||||
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
} from './types';
|
||||
import getRawBody from 'raw-body';
|
||||
import { AwsS3Integration, ScmIntegrations } from '@backstage/integration';
|
||||
import { ForwardedError } from '@backstage/errors';
|
||||
import { ForwardedError, NotModifiedError } from '@backstage/errors';
|
||||
import { ListObjectsV2Output, ObjectList } from 'aws-sdk/clients/s3';
|
||||
|
||||
const parseURL = (
|
||||
@@ -163,6 +163,10 @@ export class AwsS3UrlReader implements UrlReader {
|
||||
etag: etag,
|
||||
};
|
||||
} catch (e) {
|
||||
if (e.statusCode === 304) {
|
||||
throw new NotModifiedError();
|
||||
}
|
||||
|
||||
throw new ForwardedError('Could not retrieve file from S3', e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
AzureIntegration,
|
||||
readAzureIntegrationConfig,
|
||||
} from '@backstage/integration';
|
||||
import { msw } from '@backstage/test-utils';
|
||||
import { setupRequestMockHandlers } from '@backstage/test-utils';
|
||||
import fs from 'fs-extra';
|
||||
import mockFs from 'mock-fs';
|
||||
import { rest } from 'msw';
|
||||
@@ -51,7 +51,7 @@ describe('AzureUrlReader', () => {
|
||||
});
|
||||
|
||||
const worker = setupServer();
|
||||
msw.setupDefaultHandlers(worker);
|
||||
setupRequestMockHandlers(worker);
|
||||
|
||||
describe('read', () => {
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
BitbucketIntegration,
|
||||
readBitbucketIntegrationConfig,
|
||||
} from '@backstage/integration';
|
||||
import { msw } from '@backstage/test-utils';
|
||||
import { setupRequestMockHandlers } from '@backstage/test-utils';
|
||||
import fs from 'fs-extra';
|
||||
import mockFs from 'mock-fs';
|
||||
import { rest } from 'msw';
|
||||
@@ -72,7 +72,7 @@ describe('BitbucketUrlReader', () => {
|
||||
});
|
||||
|
||||
const worker = setupServer();
|
||||
msw.setupDefaultHandlers(worker);
|
||||
setupRequestMockHandlers(worker);
|
||||
|
||||
describe('readUrl', () => {
|
||||
worker.use(
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { NotFoundError, NotModifiedError } from '@backstage/errors';
|
||||
import { msw } from '@backstage/test-utils';
|
||||
import { setupRequestMockHandlers } from '@backstage/test-utils';
|
||||
import { rest } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
import { getVoidLogger } from '../logging';
|
||||
@@ -28,7 +28,7 @@ const fetchUrlReader = new FetchUrlReader();
|
||||
describe('FetchUrlReader', () => {
|
||||
const worker = setupServer();
|
||||
|
||||
msw.setupDefaultHandlers(worker);
|
||||
setupRequestMockHandlers(worker);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
GitHubIntegration,
|
||||
readGitHubIntegrationConfig,
|
||||
} from '@backstage/integration';
|
||||
import { msw } from '@backstage/test-utils';
|
||||
import { setupRequestMockHandlers } from '@backstage/test-utils';
|
||||
import fs from 'fs-extra';
|
||||
import mockFs from 'mock-fs';
|
||||
import { rest } from 'msw';
|
||||
@@ -73,7 +73,7 @@ const tmpDir = os.platform() === 'win32' ? 'C:\\tmp' : '/tmp';
|
||||
|
||||
describe('GithubUrlReader', () => {
|
||||
const worker = setupServer();
|
||||
msw.setupDefaultHandlers(worker);
|
||||
setupRequestMockHandlers(worker);
|
||||
|
||||
beforeEach(() => {
|
||||
mockFs({
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { msw } from '@backstage/test-utils';
|
||||
import { setupRequestMockHandlers } from '@backstage/test-utils';
|
||||
import fs from 'fs-extra';
|
||||
import mockFs from 'mock-fs';
|
||||
import { rest } from 'msw';
|
||||
@@ -77,7 +77,7 @@ describe('GitlabUrlReader', () => {
|
||||
});
|
||||
|
||||
const worker = setupServer();
|
||||
msw.setupDefaultHandlers(worker);
|
||||
setupRequestMockHandlers(worker);
|
||||
|
||||
describe('read', () => {
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -20,6 +20,7 @@ export { GithubUrlReader } from './GithubUrlReader';
|
||||
export { GitlabUrlReader } from './GitlabUrlReader';
|
||||
export { AwsS3UrlReader } from './AwsS3UrlReader';
|
||||
export type {
|
||||
FromReadableArrayOptions,
|
||||
ReaderFactory,
|
||||
ReadTreeOptions,
|
||||
ReadTreeResponse,
|
||||
|
||||
@@ -24,24 +24,41 @@ import { Config } from '@backstage/config';
|
||||
* @public
|
||||
*/
|
||||
export type UrlReader = {
|
||||
/* Used to read a single file and return its content. */
|
||||
/**
|
||||
* Reads a single file and return its content.
|
||||
*/
|
||||
read(url: string): Promise<Buffer>;
|
||||
|
||||
/**
|
||||
* A replacement for the read method that supports options and complex responses.
|
||||
* Reads a single file and return its content.
|
||||
*
|
||||
* Use this whenever it is available, as the read method will be deprecated and
|
||||
* eventually removed in the future.
|
||||
* @remarks
|
||||
*
|
||||
* This is a replacement for the read method that supports options and
|
||||
* complex responses.
|
||||
*
|
||||
* Use this whenever it is available, as the read method will be
|
||||
* deprecated and eventually removed in a future release.
|
||||
*/
|
||||
readUrl?(url: string, options?: ReadUrlOptions): Promise<ReadUrlResponse>;
|
||||
|
||||
/* Used to read a file tree and download as a directory. */
|
||||
/**
|
||||
* Reads a full or partial file tree.
|
||||
*/
|
||||
readTree(url: string, options?: ReadTreeOptions): Promise<ReadTreeResponse>;
|
||||
/* Used to search a file in a tree using a glob pattern. */
|
||||
|
||||
/**
|
||||
* Searches for a file in a tree using a glob pattern.
|
||||
*/
|
||||
search(url: string, options?: SearchOptions): Promise<SearchResponse>;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
/**
|
||||
* A predicate that decides whether a specific {@link UrlReader} can handle a
|
||||
* given URL.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type UrlReaderPredicateTuple = {
|
||||
predicate: (url: URL) => boolean;
|
||||
reader: UrlReader;
|
||||
@@ -49,7 +66,7 @@ export type UrlReaderPredicateTuple = {
|
||||
|
||||
/**
|
||||
* A factory function that can read config to construct zero or more
|
||||
* UrlReaders along with a predicate for when it should be used.
|
||||
* {@link UrlReader}s along with a predicate for when it should be used.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
@@ -66,21 +83,28 @@ export type ReaderFactory = (options: {
|
||||
*/
|
||||
export type ReadUrlOptions = {
|
||||
/**
|
||||
* An etag can be provided to check whether readUrl's response has changed from a previous execution.
|
||||
* An ETag which can be provided to check whether a
|
||||
* {@link UrlReader.readUrl} response has changed from a previous execution.
|
||||
*
|
||||
* In the readUrl() response, an etag is returned along with the data. The etag is a unique identifer
|
||||
* of the data, usually the commit SHA or etag from the target.
|
||||
* @remarks
|
||||
*
|
||||
* When an etag is given in ReadUrlOptions, readUrl will first compare the etag against the etag
|
||||
* on the target. If they match, readUrl will throw a NotModifiedError indicating that the readUrl
|
||||
* response will not differ from the previous response which included this particular etag. If they
|
||||
* do not match, readUrl will return the rest of ReadUrlResponse along with a new etag.
|
||||
* In the {@link UrlReader.readUrl} response, an ETag is returned along with
|
||||
* the data. The ETag is a unique identifier of the data, usually the commit
|
||||
* SHA or ETag from the target.
|
||||
*
|
||||
* When an ETag is given in ReadUrlOptions, {@link UrlReader.readUrl} will
|
||||
* first compare the ETag against the ETag of the target. If they match,
|
||||
* {@link UrlReader.readUrl} will throw a
|
||||
* {@link @backstage/errors#NotModifiedError} indicating that the response
|
||||
* will not differ from the previous response which included this particular
|
||||
* ETag. If they do not match, {@link UrlReader.readUrl} will return the rest
|
||||
* of the response along with a new ETag.
|
||||
*/
|
||||
etag?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* A response object for readUrl operations.
|
||||
* A response object for {@link UrlReader.readUrl} operations.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
@@ -92,13 +116,16 @@ export type ReadUrlResponse = {
|
||||
|
||||
/**
|
||||
* Etag returned by content provider.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* Can be used to compare and cache responses when doing subsequent calls.
|
||||
*/
|
||||
etag?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* An options object for readTree operations.
|
||||
* An options object for {@link UrlReader.readTree} operations.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
@@ -106,6 +133,8 @@ export type ReadTreeOptions = {
|
||||
/**
|
||||
* A filter that can be used to select which files should be included.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* The path passed to the filter function is the relative path from the URL
|
||||
* that the file tree is fetched from, without any leading '/'.
|
||||
*
|
||||
@@ -113,56 +142,82 @@ export type ReadTreeOptions = {
|
||||
* at https://github.com/my/repo/blob/master/my-dir/my-subdir/my-file.txt will
|
||||
* be represented as my-subdir/my-file.txt
|
||||
*
|
||||
* If no filter is provided all files are extracted.
|
||||
* If no filter is provided, all files are extracted.
|
||||
*/
|
||||
filter?(path: string, info?: { size: number }): boolean;
|
||||
|
||||
/**
|
||||
* An etag can be provided to check whether readTree's response has changed from a previous execution.
|
||||
* An ETag which can be provided to check whether a
|
||||
* {@link UrlReader.readTree} response has changed from a previous execution.
|
||||
*
|
||||
* In the readTree() response, an etag is returned along with the tree blob. The etag is a unique identifer
|
||||
* of the tree blob, usually the commit SHA or etag from the target.
|
||||
* @remarks
|
||||
*
|
||||
* When an etag is given in ReadTreeOptions, readTree will first compare the etag against the etag
|
||||
* on the target branch. If they match, readTree will throw a NotModifiedError indicating that the readTree
|
||||
* response will not differ from the previous response which included this particular etag. If they
|
||||
* do not match, readTree will return the rest of ReadTreeResponse along with a new etag.
|
||||
* In the {@link UrlReader.readTree} response, an ETag is returned along with
|
||||
* the tree blob. The ETag is a unique identifier of the tree blob, usually
|
||||
* the commit SHA or ETag from the target.
|
||||
*
|
||||
* When an ETag is given as a request option, {@link UrlReader.readTree} will
|
||||
* first compare the ETag against the ETag on the target branch. If they
|
||||
* match, {@link UrlReader.readTree} will throw a
|
||||
* {@link @backstage/errors#NotModifiedError} indicating that the response
|
||||
* will not differ from the previous response which included this particular
|
||||
* ETag. If they do not match, {@link UrlReader.readTree} will return the
|
||||
* rest of the response along with a new ETag.
|
||||
*/
|
||||
etag?: string;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
/**
|
||||
* Options that control {@link ReadTreeResponse.dir} execution.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type ReadTreeResponseDirOptions = {
|
||||
/** The directory to write files to. Defaults to the OS tmpdir or `backend.workingDirectory` if set in config */
|
||||
/**
|
||||
* The directory to write files to.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* Defaults to the OS tmpdir, or `backend.workingDirectory` if set in config.
|
||||
*/
|
||||
targetDir?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* A response object for readTree operations.
|
||||
* A response object for {@link UrlReader.readTree} operations.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type ReadTreeResponse = {
|
||||
/**
|
||||
* files() returns an array of all the files inside the tree and corresponding functions to read their content.
|
||||
* Returns an array of all the files inside the tree, and corresponding
|
||||
* functions to read their content.
|
||||
*/
|
||||
files(): Promise<ReadTreeResponseFile[]>;
|
||||
|
||||
/**
|
||||
* Returns the tree contents as a binary archive, using a stream.
|
||||
*/
|
||||
archive(): Promise<NodeJS.ReadableStream>;
|
||||
|
||||
/**
|
||||
* dir() extracts the tree response into a directory and returns the path of the directory.
|
||||
* Extracts the tree response into a directory and returns the path of the
|
||||
* directory.
|
||||
*/
|
||||
dir(options?: ReadTreeResponseDirOptions): Promise<string>;
|
||||
|
||||
/**
|
||||
* Etag returned by content provider.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* Can be used to compare and cache responses when doing subsequent calls.
|
||||
*/
|
||||
etag: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a single file in a readTree response.
|
||||
* Represents a single file in a {@link UrlReader.readTree} response.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
@@ -171,7 +226,11 @@ export type ReadTreeResponseFile = {
|
||||
content(): Promise<Buffer>;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
/**
|
||||
* Options that control execution of {@link ReadTreeResponseFactory} methods.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type ReadTreeResponseFactoryOptions = {
|
||||
// A binary stream of a tar archive.
|
||||
stream: Readable;
|
||||
@@ -183,11 +242,21 @@ export type ReadTreeResponseFactoryOptions = {
|
||||
// Filter passed on from the ReadTreeOptions
|
||||
filter?: (path: string, info?: { size: number }) => boolean;
|
||||
};
|
||||
/** @public */
|
||||
|
||||
/**
|
||||
* Options that control {@link ReadTreeResponseFactory.fromReadableArray}
|
||||
* execution.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type FromReadableArrayOptions = Array<{
|
||||
// Data in the form of a readable
|
||||
/**
|
||||
* The raw data itself.
|
||||
*/
|
||||
data: Readable;
|
||||
// A string containing the filepath of the data
|
||||
/**
|
||||
* The filepath of the data.
|
||||
*/
|
||||
path: string;
|
||||
}>;
|
||||
|
||||
@@ -213,7 +282,7 @@ export type SearchOptions = {
|
||||
/**
|
||||
* An etag can be provided to check whether the search response has changed from a previous execution.
|
||||
*
|
||||
* In the search() response, an etag is returned along with the files. The etag is a unique identifer
|
||||
* In the search() response, an etag is returned along with the files. The etag is a unique identifier
|
||||
* of the current tree, usually the commit SHA or etag from the target.
|
||||
*
|
||||
* When an etag is given in SearchOptions, search will first compare the etag against the etag
|
||||
@@ -236,7 +305,7 @@ export type SearchResponse = {
|
||||
files: SearchResponseFile[];
|
||||
|
||||
/**
|
||||
* A unique identifer of the current remote tree, usually the commit SHA or etag from the target.
|
||||
* A unique identifier of the current remote tree, usually the commit SHA or etag from the target.
|
||||
*/
|
||||
etag: string;
|
||||
};
|
||||
|
||||
@@ -174,27 +174,27 @@ export class ServiceBuilderImpl implements ServiceBuilder {
|
||||
const server: http.Server = httpsSettings
|
||||
? await createHttpsServer(app, httpsSettings, logger)
|
||||
: createHttpServer(app, logger);
|
||||
const stoppableServer = stoppable(server, 0);
|
||||
|
||||
useHotCleanup(this.module, () =>
|
||||
stoppableServer.stop((e: any) => {
|
||||
if (e) console.error(e);
|
||||
}),
|
||||
);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
app.on('error', e => {
|
||||
logger.error(`Failed to start up on port ${port}, ${e}`);
|
||||
function handleStartupError(e: unknown) {
|
||||
server.close();
|
||||
reject(e);
|
||||
}
|
||||
|
||||
server.on('error', handleStartupError);
|
||||
|
||||
server.listen(port, host, () => {
|
||||
server.off('error', handleStartupError);
|
||||
logger.info(`Listening on ${host}:${port}`);
|
||||
resolve(stoppableServer);
|
||||
});
|
||||
|
||||
const stoppableServer = stoppable(
|
||||
server.listen(port, host, () => {
|
||||
logger.info(`Listening on ${host}:${port}`);
|
||||
}),
|
||||
0,
|
||||
);
|
||||
|
||||
useHotCleanup(this.module, () =>
|
||||
stoppableServer.stop((e: any) => {
|
||||
if (e) console.error(e);
|
||||
}),
|
||||
);
|
||||
|
||||
resolve(stoppableServer);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2021 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 { isValidUrl } from './urls';
|
||||
|
||||
describe('isValidUrl', () => {
|
||||
it('should return true for url', () => {
|
||||
const validUrl = isValidUrl('http://some.valid.url');
|
||||
expect(validUrl).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for absolute path', () => {
|
||||
const validUrl = isValidUrl('/some/absolute/path');
|
||||
expect(validUrl).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for relative path', () => {
|
||||
const validUrl = isValidUrl('../some/relative/path');
|
||||
expect(validUrl).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
|
||||
export function isValidUrl(url: string): boolean {
|
||||
try {
|
||||
// eslint-disable-next-line no-new
|
||||
new URL(url);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2021 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 { escapeRegExp } from './escapeRegExp';
|
||||
|
||||
describe('escapeRegExp', () => {
|
||||
test('does not escape non-regex characters', () => {
|
||||
expect(escapeRegExp('Backstage Backstage')).toBe('Backstage Backstage');
|
||||
});
|
||||
|
||||
test('all the characters', () => {
|
||||
expect(escapeRegExp('^$\\.*+?()[]{}|')).toBe(
|
||||
'\\^\\$\\\\\\.\\*\\+\\?\\(\\)\\[\\]\\{\\}\\|',
|
||||
);
|
||||
});
|
||||
|
||||
test('character: ^', () => {
|
||||
expect(escapeRegExp('^')).toBe('\\^');
|
||||
});
|
||||
|
||||
test('character: $', () => {
|
||||
expect(escapeRegExp('$')).toBe('\\$');
|
||||
});
|
||||
|
||||
test('character: \\', () => {
|
||||
expect(escapeRegExp('\\')).toBe('\\\\');
|
||||
});
|
||||
|
||||
test('character: .', () => {
|
||||
expect(escapeRegExp('.')).toBe('\\.');
|
||||
});
|
||||
|
||||
test('character: *', () => {
|
||||
expect(escapeRegExp('*')).toBe('\\*');
|
||||
});
|
||||
|
||||
test('character: +', () => {
|
||||
expect(escapeRegExp('+')).toBe('\\+');
|
||||
});
|
||||
|
||||
test('character: ?', () => {
|
||||
expect(escapeRegExp('?')).toBe('\\?');
|
||||
});
|
||||
|
||||
test('character: (', () => {
|
||||
expect(escapeRegExp('(')).toBe('\\(');
|
||||
});
|
||||
|
||||
test('character: )', () => {
|
||||
expect(escapeRegExp(')')).toBe('\\)');
|
||||
});
|
||||
|
||||
test('character: [', () => {
|
||||
expect(escapeRegExp('[')).toBe('\\[');
|
||||
});
|
||||
|
||||
test('character: ]', () => {
|
||||
expect(escapeRegExp(']')).toBe('\\]');
|
||||
});
|
||||
|
||||
test('character: {', () => {
|
||||
expect(escapeRegExp('{')).toBe('\\{');
|
||||
});
|
||||
|
||||
test('character: }', () => {
|
||||
expect(escapeRegExp('}')).toBe('\\}');
|
||||
});
|
||||
|
||||
test('character: |', () => {
|
||||
expect(escapeRegExp('|')).toBe('\\|');
|
||||
});
|
||||
});
|
||||
+7
-17
@@ -14,21 +14,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
BuildResult,
|
||||
BuildStatus,
|
||||
} from 'azure-devops-node-api/interfaces/BuildInterfaces';
|
||||
|
||||
export type RepoBuild = {
|
||||
id?: number;
|
||||
title: string;
|
||||
link?: string;
|
||||
status?: BuildStatus;
|
||||
result?: BuildResult;
|
||||
queueTime?: Date;
|
||||
source: string;
|
||||
};
|
||||
|
||||
export type RepoBuildOptions = {
|
||||
top?: number;
|
||||
/**
|
||||
* Escapes a given string to be used inside a RegExp.
|
||||
*
|
||||
* Taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
|
||||
*/
|
||||
export const escapeRegExp = (text: string) => {
|
||||
return text.replace(/[.*+?^${}(\)|[\]\\]/g, '\\$&');
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
extends: [require.resolve('@backstage/cli/config/eslint.backend')],
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
# @backstage/backend-tasks
|
||||
|
||||
Common distributed task management for Backstage backends.
|
||||
|
||||
## Usage
|
||||
|
||||
Add the library to your backend package:
|
||||
|
||||
```sh
|
||||
# From your Backstage root directory
|
||||
cd packages/backend
|
||||
yarn add @backstage/backend-tasks
|
||||
```
|
||||
|
||||
then make use of its facilities as necessary:
|
||||
|
||||
```typescript
|
||||
import { TaskScheduler } from '@backstage/backend-tasks';
|
||||
import { Duration } from 'luxon';
|
||||
|
||||
const scheduler = TaskScheduler.fromConfig(rootConfig).forPlugin('my-plugin');
|
||||
|
||||
await scheduler.scheduleTask({
|
||||
id: 'refresh-things',
|
||||
frequency: Duration.fromObject({ minutes: 10 }),
|
||||
fn: async () => {
|
||||
await entityProvider.run();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md)
|
||||
- [Backstage Documentation](https://github.com/backstage/backstage/blob/master/docs/README.md)
|
||||
@@ -0,0 +1,45 @@
|
||||
## API Report File for "@backstage/backend-tasks"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import { AbortSignal as AbortSignal_2 } from 'node-abort-controller';
|
||||
import { Config } from '@backstage/config';
|
||||
import { DatabaseManager } from '@backstage/backend-common';
|
||||
import { Duration } from 'luxon';
|
||||
import { Logger as Logger_2 } from 'winston';
|
||||
|
||||
// @public
|
||||
export interface PluginTaskScheduler {
|
||||
scheduleTask(task: TaskDefinition): Promise<void>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface TaskDefinition {
|
||||
fn: TaskFunction;
|
||||
frequency: Duration;
|
||||
id: string;
|
||||
initialDelay?: Duration;
|
||||
signal?: AbortSignal_2;
|
||||
timeout: Duration;
|
||||
}
|
||||
|
||||
// @public
|
||||
export type TaskFunction =
|
||||
| ((abortSignal: AbortSignal_2) => void | Promise<void>)
|
||||
| (() => void | Promise<void>);
|
||||
|
||||
// @public
|
||||
export class TaskScheduler {
|
||||
constructor(databaseManager: DatabaseManager, logger: Logger_2);
|
||||
forPlugin(pluginId: string): PluginTaskScheduler;
|
||||
// (undocumented)
|
||||
static fromConfig(
|
||||
config: Config,
|
||||
options?: {
|
||||
databaseManager?: DatabaseManager;
|
||||
logger?: Logger_2;
|
||||
},
|
||||
): TaskScheduler;
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2020 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) {
|
||||
//
|
||||
// tasks
|
||||
//
|
||||
await knex.schema.createTable('backstage_backend_tasks__tasks', table => {
|
||||
table.comment('Tasks used for scheduling work on multiple workers');
|
||||
table
|
||||
.text('id')
|
||||
.primary()
|
||||
.notNullable()
|
||||
.comment('The unique ID of this particular task');
|
||||
table
|
||||
.text('settings_json')
|
||||
.notNullable()
|
||||
.comment('JSON serialized object with properties for this task');
|
||||
table
|
||||
.dateTime('next_run_start_at')
|
||||
.notNullable()
|
||||
.comment('The next time that the task should be started');
|
||||
table
|
||||
.text('current_run_ticket')
|
||||
.nullable()
|
||||
.comment('A unique ticket for the current task run');
|
||||
table
|
||||
.dateTime('current_run_started_at')
|
||||
.nullable()
|
||||
.comment('The time that the current task run started');
|
||||
table
|
||||
.dateTime('current_run_expires_at')
|
||||
.nullable()
|
||||
.comment('The time that the current task run will time out');
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.down = async function down(knex) {
|
||||
//
|
||||
// tasks
|
||||
//
|
||||
await knex.schema.dropTable('backstage_backend_tasks__tasks');
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"name": "@backstage/backend-tasks",
|
||||
"description": "Common distributed task management library for Backstage backends",
|
||||
"version": "0.1.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "dist/index.cjs.js",
|
||||
"types": "dist/index.d.ts"
|
||||
},
|
||||
"homepage": "https://backstage.io",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/backstage/backstage",
|
||||
"directory": "packages/backend-tasks"
|
||||
},
|
||||
"keywords": [
|
||||
"backstage"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
"build": "backstage-cli build --outputs cjs,types",
|
||||
"lint": "backstage-cli lint",
|
||||
"test": "backstage-cli test",
|
||||
"prepack": "backstage-cli prepack",
|
||||
"postpack": "backstage-cli postpack",
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.9.8",
|
||||
"@backstage/config": "^0.1.11",
|
||||
"@backstage/errors": "^0.1.4",
|
||||
"@backstage/types": "^0.1.1",
|
||||
"@types/luxon": "^2.0.4",
|
||||
"knex": "^0.95.1",
|
||||
"lodash": "^4.17.21",
|
||||
"luxon": "^2.0.2",
|
||||
"node-abort-controller": "^3.0.1",
|
||||
"uuid": "^8.0.0",
|
||||
"winston": "^3.2.1",
|
||||
"zod": "^3.9.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/backend-test-utils": "^0.1.8",
|
||||
"@backstage/cli": "^0.8.1",
|
||||
"jest": "^26.0.1",
|
||||
"wait-for-expect": "^3.0.2"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"migrations/**/*.{js,d.ts}"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2021 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 { resolvePackagePath } from '@backstage/backend-common';
|
||||
import { Knex } from 'knex';
|
||||
import { DB_MIGRATIONS_TABLE } from './tables';
|
||||
|
||||
const migrationsDir = resolvePackagePath(
|
||||
'@backstage/backend-tasks',
|
||||
'migrations',
|
||||
);
|
||||
|
||||
export async function migrateBackendTasks(knex: Knex): Promise<void> {
|
||||
await knex.migrate.latest({
|
||||
directory: migrationsDir,
|
||||
tableName: DB_MIGRATIONS_TABLE,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
|
||||
export const DB_MIGRATIONS_TABLE = 'backstage_backend_tasks__knex_migrations';
|
||||
export const DB_TASKS_TABLE = 'backstage_backend_tasks__tasks';
|
||||
|
||||
export type DbTasksRow = {
|
||||
id: string;
|
||||
settings_json: string;
|
||||
next_run_start_at: Date;
|
||||
current_run_ticket?: string;
|
||||
current_run_started_at?: Date | string;
|
||||
current_run_expires_at?: Date | string;
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Common distributed task management library for Backstage backends
|
||||
*
|
||||
* @packageDocumentation
|
||||
*/
|
||||
|
||||
export * from './tasks';
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user