Merge branch 'master' into 3965-breadcrumbs
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-common': patch
|
||||
---
|
||||
|
||||
pass registered logger to requestLoggingHandler
|
||||
@@ -1,29 +0,0 @@
|
||||
---
|
||||
'@backstage/create-app': patch
|
||||
---
|
||||
|
||||
Pass on plugin database management instance that is now required by the scaffolder plugin.
|
||||
|
||||
To apply this change to an existing application, add the following to `src/plugins/scaffolder.ts`:
|
||||
|
||||
```diff
|
||||
export default async function createPlugin({
|
||||
logger,
|
||||
config,
|
||||
+ database,
|
||||
}: PluginEnvironment) {
|
||||
|
||||
// ...omitted...
|
||||
|
||||
return await createRouter({
|
||||
preparers,
|
||||
templaters,
|
||||
publishers,
|
||||
logger,
|
||||
config,
|
||||
dockerClient,
|
||||
entityClient,
|
||||
+ database,
|
||||
});
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/integration': minor
|
||||
---
|
||||
|
||||
Make `ScmIntegration.resolveUrl` mandatory.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-circleci': patch
|
||||
---
|
||||
|
||||
Migrated to new composability API, exporting the plugin instance as `circleCIPlugin`, the entity page content as `EntityCircleCIContent`, and entity conditional as `isCircleCIAvailable`.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-search': minor
|
||||
---
|
||||
|
||||
Migrated to new composability API, exporting the plugin instance as `searchPlugin`, and page as `SearchPage`. Due to the old router component also being called `SearchPage`, this is a breaking change. The old page component is now exported as `Router`, which can be used to maintain the old behavior.
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/backend-common': patch
|
||||
---
|
||||
|
||||
Implement `UrlReader.search` for the other providers (Azure, Bitbucket, GitLab) as well.
|
||||
|
||||
The `UrlReader` subclasses now are implemented in terms of the respective `Integration` class.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend': patch
|
||||
---
|
||||
|
||||
Don't respond to a request twice if an entity has not been found.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-cloudbuild': patch
|
||||
---
|
||||
|
||||
Migrate to new composability API, exporting the plugin instance as `cloudbuildPlugin`, the entity content as `EntityCloudbuildContent`, the entity conditional as `isCloudbuildAvailable`, and entity cards as `EntityLatestCloudbuildRunCard` and `EntityLatestCloudbuildsForBranchCard`.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-register-component': patch
|
||||
---
|
||||
|
||||
Migrated to new composability API, exporting the plugin instance as `registerComponentPlugin`, and page as `RegisterComponentPage`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-user-settings': patch
|
||||
---
|
||||
|
||||
Use routed tabs to link to every settings page.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend': minor
|
||||
---
|
||||
|
||||
Introduced `v2` Scaffolder REST API, which uses an implementation that is database backed, making the scaffolder instances stateless. The `createRouter` function now requires a `PluginDatabaseManager` instance to be passed in, commonly available as `database` in the plugin environment in the backend.
|
||||
|
||||
This API should be considered unstable until used by the scaffolder frontend.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
Tweak error message in lockfile parsing to include more information.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-techdocs': patch
|
||||
---
|
||||
|
||||
Migrated to new composability API, exporting the plugin instance as `techdocsPlugin`, the top-level page as `TechdocsPage`, and the entity content as `EntityTechdocsContent`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-react': patch
|
||||
---
|
||||
|
||||
Limit the props that are forwarded to the `Link` component in the `EntityRefLink`.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/backend-common': patch
|
||||
---
|
||||
|
||||
Slight refactoring in support of a future search implementation in `UrlReader`. Mostly moving code around.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-pagerduty': patch
|
||||
---
|
||||
|
||||
Migrated to new composability API, exporting the plugin instance as `pagerDutyPlugin`, entity card as `EntityPagerDutyCard`, and entity conditional as `isPagerDutyAvailable`.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/test-utils': patch
|
||||
---
|
||||
|
||||
Added `mountedRoutes` option to `wrapInTestApp`, allowing routes to be associated to concrete paths to make `useRouteRef` usable in tested components.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-jenkins': patch
|
||||
---
|
||||
|
||||
Migrate to new composability API, exporting the plugin instance as `jenkinsPlugin`, the entity content as `EntityJenkinsContent`, the entity conditional as `isJenkinsAvailable`, and the entity card as `EntityLatestJenkinsRunCard`.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-github-actions': patch
|
||||
---
|
||||
|
||||
Migrate to new composability API, exporting the plugin instance as `githubActionsPlugin`, the entity content as `EntityGithubActionsContent`, entity conditional as `isGithubActionsAvailable`, and entity cards as `EntityLatestGithubActionRunCard`, `EntityLatestGithubActionsForBranchCard`, and `EntityRecentGithubActionsRunsCard`.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-newrelic': patch
|
||||
---
|
||||
|
||||
Migrate to new composability API, exporting the plugin instance as `newRelicPlugin`, and the root page as `NewRelicPage`.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-kubernetes': patch
|
||||
---
|
||||
|
||||
Migrate to new composability API, exporting the plugin instance as `kubernetesPlugin` and entity content as `EntityKubernetesContent`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog': patch
|
||||
---
|
||||
|
||||
Migrate about card to new composability API, exporting the entity cards as `EntityAboutCard`.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-kafka': patch
|
||||
---
|
||||
|
||||
Migrate to new composability API, exporting the plugin instance as `kafkaPlugin`, entity content as `EntityKafkaContent`, and entity conditional as `isKafkaAvailable`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-auth-backend': patch
|
||||
---
|
||||
|
||||
Migrated the package from using moment to Luxon. #4278
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/core': patch
|
||||
---
|
||||
|
||||
Export Select component
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-techdocs': patch
|
||||
---
|
||||
|
||||
Enhance API calls to support trapping 500 errors from techdocs-backend
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/core': patch
|
||||
---
|
||||
|
||||
Make sure that SidebarItems are also active when on sub route.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend': patch
|
||||
---
|
||||
|
||||
Fix parsing of the path to default to empty string not undefined if git-url-parse throws something we don't expect. Fixes the error `The "path" argument must be of type string.` when preparing.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/core': patch
|
||||
---
|
||||
|
||||
Fixed type inference of `createRouteRef`.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-gitops-profiles': patch
|
||||
---
|
||||
|
||||
Migrated to new composability API, exporting the plugin instance as `gitopsProfilesPlugin` and pages as `GitopsProfilesClusterListPage`, `GitopsProfilesClusterPage`, and `GitopsProfilesCreatePage`.
|
||||
@@ -7,6 +7,7 @@
|
||||
* @backstage/maintainers
|
||||
/docs/features/techdocs @backstage/techdocs-core
|
||||
/docs/features/search @backstage/techdocs-core
|
||||
/docs/assets/search @backstage/techdocs-core
|
||||
/plugins/cost-insights @backstage/silver-lining
|
||||
/plugins/cloudbuild @trivago/ebarrios
|
||||
/plugins/search @backstage/techdocs-core
|
||||
|
||||
@@ -119,6 +119,7 @@ Kumar
|
||||
learnings
|
||||
lerna
|
||||
Lerna
|
||||
lockfile
|
||||
Luxon
|
||||
magiclink
|
||||
mailto
|
||||
@@ -249,6 +250,7 @@ transpilation
|
||||
Tuite
|
||||
ui
|
||||
unmanaged
|
||||
unregister
|
||||
untracked
|
||||
upvote
|
||||
url
|
||||
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
run: yarn prettier:check
|
||||
|
||||
- name: validate config
|
||||
run: yarn backstage-cli config:check
|
||||
run: yarn backstage-cli config:check --lax
|
||||
|
||||
- name: lint
|
||||
run: yarn lerna -- run lint --since origin/master
|
||||
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
COMMIT_SHA_BEFORE: '${{ github.event.before }}'
|
||||
|
||||
- name: validate config
|
||||
run: yarn backstage-cli config:check
|
||||
run: yarn backstage-cli config:check --lax
|
||||
|
||||
- name: lint
|
||||
run: yarn lerna -- run lint
|
||||
|
||||
@@ -48,11 +48,14 @@ jobs:
|
||||
target_url: "https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}"
|
||||
});
|
||||
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '14'
|
||||
|
||||
- name: yarn install
|
||||
run: yarn --cwd cypress install
|
||||
|
||||
# This is required because the environment_url param that Tugboat uses
|
||||
# to tell us where the preview is located isn't supported unless you
|
||||
# specify the custom Accept header when getting the deployment_status,
|
||||
@@ -77,9 +80,25 @@ jobs:
|
||||
});
|
||||
console.log(result);
|
||||
return result.data.environment_url;
|
||||
- name: echo tugboat preview url
|
||||
run: |
|
||||
curl ${{steps.get-status-env.outputs.result}}
|
||||
|
||||
- name: cypress run
|
||||
uses: cypress-io/github-action@v2
|
||||
env:
|
||||
CYPRESS_baseUrl: ${{steps.get-status-env.outputs.result}}
|
||||
with:
|
||||
config-file: ./cypress.json
|
||||
working-directory: ./cypress
|
||||
browser: chrome
|
||||
install: false
|
||||
headless: true
|
||||
|
||||
- name: update artifact
|
||||
if: ${{ always() }}
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: cypress-videos
|
||||
path: ./cypress/cypress/videos
|
||||
|
||||
- name: set status
|
||||
if: ${{ failure() }}
|
||||
uses: actions/github-script@v3
|
||||
@@ -94,6 +113,7 @@ jobs:
|
||||
context: 'Backstage Tugboat E2E Tests',
|
||||
target_url: "https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}"
|
||||
});
|
||||
|
||||
- name: set status
|
||||
if: ${{ success() }}
|
||||
uses: actions/github-script@v3
|
||||
|
||||
@@ -53,6 +53,7 @@ Check out [the documentation](https://backstage.io/docs/getting-started) on how
|
||||
- [Adopters](ADOPTERS.md) - Companies already using Backstage
|
||||
- [Blog](https://backstage.io/blog/) - Announcements and updates
|
||||
- [Newsletter](https://mailchi.mp/spotify/backstage-community) - Subscribe to our email newsletter
|
||||
- [Backstage Community Sessions](https://github.com/backstage/community) - Join monthly meetup and explore Backstage community
|
||||
- Give us a star ⭐️ - If you are using Backstage or think it is an interesting project, we would love a star ❤️
|
||||
|
||||
## License
|
||||
|
||||
@@ -73,6 +73,9 @@ organization:
|
||||
name: My Company
|
||||
|
||||
# Reference documentation http://backstage.io/docs/features/techdocs/configuration
|
||||
# Note: After experimenting with basic setup, use CI/CD to generate docs
|
||||
# and an external cloud storage when deploying TechDocs for production use-case.
|
||||
# https://backstage.io/docs/features/techdocs/how-to-guides#how-to-migrate-from-techdocs-basic-to-recommended-deployment-approach
|
||||
techdocs:
|
||||
builder: 'local' # Alternatives - 'external'
|
||||
generators:
|
||||
@@ -305,6 +308,10 @@ auth:
|
||||
$env: AUTH_OAUTH2_AUTH_URL
|
||||
tokenUrl:
|
||||
$env: AUTH_OAUTH2_TOKEN_URL
|
||||
###
|
||||
# provide a list of scopes as needed for your OAuth2 Server:
|
||||
#
|
||||
# scope: saml-login-selector openid profile email
|
||||
oidc:
|
||||
development:
|
||||
metadataUrl:
|
||||
|
||||
@@ -4,6 +4,15 @@ metadata:
|
||||
name: backstage
|
||||
description: |
|
||||
Backstage is an open-source developer portal that puts the developer experience first.
|
||||
links:
|
||||
- title: Website
|
||||
url: http://backstage.io
|
||||
- title: Documentation
|
||||
url: https://backstage.io/docs
|
||||
- title: Storybook
|
||||
url: https://backstage.io/storybook
|
||||
- title: Discord Chat
|
||||
url: https://discord.com/invite/EBHEGzX
|
||||
annotations:
|
||||
github.com/project-slug: backstage/backstage
|
||||
backstage.io/techdocs-ref: url:https://github.com/backstage/backstage
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
module.exports = {
|
||||
extends: [require.resolve('@backstage/cli/config/eslint.backend')],
|
||||
rules: {
|
||||
'no-console': 0,
|
||||
'import/no-extraneous-dependencies': [
|
||||
'error',
|
||||
{
|
||||
devDependencies: true,
|
||||
optionalDependencies: false,
|
||||
peerDependencies: false,
|
||||
bundledDependencies: false,
|
||||
},
|
||||
],
|
||||
'jest/expect-expect': 'off',
|
||||
'no-restricted-syntax': 'off',
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
# Cypress Tests for Backstage
|
||||
|
||||
Hey 👋 Welcome to the Cypress tests for Backstage. They're designed to be run against the `packages/app` folder in the main repo, and be some form of smoke tests to make sure that we don't break any core functionality.
|
||||
|
||||
They run part of the PR build, and are triggered from the `.github/workflows/tugboat.yml` file.
|
||||
|
||||
The main app gets built up part of a [Tugboat Build](https://tugboat.qa), which when complete, sends a `deployment event` to the PR triggering the aforementioned workflow.
|
||||
|
||||
### Running Locally
|
||||
|
||||
In order to make typescript happy, this `cypress` package is separate from all the Jest dependencies in the monorepo workspaces setup.
|
||||
|
||||
You can run the e2e tests locally pointing at your local running version like so:
|
||||
|
||||
```sh
|
||||
cd cypress
|
||||
yarn install
|
||||
yarn cypress run
|
||||
```
|
||||
|
||||
You can open up the `cypress` console by using `yarn cypress open`.
|
||||
|
||||
You can also run towards any Backstage installation by using the Cypress Environment Variable overrides.
|
||||
|
||||
```sh
|
||||
CYPRESS_baseUrl="http://demo.backstage.io" yarn cypress run
|
||||
```
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"baseUrl": "http://localhost:7000",
|
||||
"integrationFolder": "./src/integration",
|
||||
"supportFile": "./src/support",
|
||||
"fixturesFolder": "./src/fixures",
|
||||
"pluginsFile": "./src/plugins",
|
||||
"defaultCommandTimeout": 10000
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "@backstage/cypress-tests",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"cypress": "^6.4.0",
|
||||
"typescript": "^4.1.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/// <reference types="cypress" />
|
||||
import 'os';
|
||||
|
||||
describe('Catalog', () => {
|
||||
describe('default entities', () => {
|
||||
it('displays the correct amount of entities from default config', () => {
|
||||
cy.loginAsGuest();
|
||||
|
||||
cy.visit('/catalog');
|
||||
|
||||
cy.contains('Owned (7)').should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export default () => {};
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/// <reference types="cypress" />
|
||||
|
||||
Cypress.Commands.add('loginAsGuest', () => {
|
||||
cy.visit('/', {
|
||||
onLoad: (win: Window) =>
|
||||
win.localStorage.setItem('@backstage/core:SignInPage:provider', 'guest'),
|
||||
});
|
||||
});
|
||||
|
||||
export {};
|
||||
@@ -13,19 +13,14 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import parseGitUrl from 'git-url-parse';
|
||||
|
||||
export type UrlType = 'file' | 'tree';
|
||||
|
||||
export function urlType(url: string): UrlType {
|
||||
const { filepathtype, filepath } = parseGitUrl(url);
|
||||
|
||||
if (filepathtype === 'tree' || filepathtype === 'file') {
|
||||
return filepathtype;
|
||||
} else if (filepath?.match(/\.ya?ml$/)) {
|
||||
return 'file';
|
||||
declare module 'zombie';
|
||||
declare module 'pgtools';
|
||||
declare namespace Cypress {
|
||||
interface Chainable {
|
||||
/**
|
||||
* Login as guest
|
||||
* @example cy.loginAsGuests
|
||||
*/
|
||||
loginAsGuest(): Chainable<Element>;
|
||||
}
|
||||
|
||||
return 'tree';
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"declaration": true,
|
||||
"declarationMap": false,
|
||||
"esModuleInterop": true,
|
||||
"experimentalDecorators": false,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"importHelpers": false,
|
||||
"incremental": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "react",
|
||||
"lib": ["DOM", "DOM.Iterable", "ScriptHost", "ES2020", "ESNext.Promise"],
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"noEmit": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noImplicitAny": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"pretty": true,
|
||||
"removeComments": false,
|
||||
"resolveJsonModule": true,
|
||||
"sourceMap": false,
|
||||
"skipLibCheck": false,
|
||||
"strict": true,
|
||||
"strictBindCallApply": true,
|
||||
"strictFunctionTypes": true,
|
||||
"strictNullChecks": true,
|
||||
"strictPropertyInitialization": true,
|
||||
"stripInternal": true,
|
||||
"target": "ES2019",
|
||||
"types": ["node", "cypress"]
|
||||
}
|
||||
}
|
||||
+1409
File diff suppressed because it is too large
Load Diff
@@ -119,7 +119,7 @@ Plugins supply their APIs through the `apis` option of `createPlugin`, for
|
||||
example:
|
||||
|
||||
```ts
|
||||
export const plugin = createPlugin({
|
||||
export const techdocsPlugin = createPlugin({
|
||||
id: 'techdocs',
|
||||
apis: [
|
||||
createApiFactory({
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<svg host="65bd71144e" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="789px" height="766px" viewBox="-0.5 -0.5 789 766" content="<mxfile host="bd2205bb-07f8-4b61-b1c1-5174fe4ebe37" modified="2021-01-14T13:46:42.842Z" agent="5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Code/1.52.0 Chrome/83.0.4103.122 Electron/9.3.5 Safari/537.36" etag="jW9IV2PM6529z9FP4d6-" version="13.10.0" type="embed"><diagram id="AOZgdlUmH_6GT6Gt5u4e" name="Page-1">7V1bc6M4Fv41rpp5CMVNAh4TJ5ntre7qbGdqdvqRYMWmG4MXcGLvrx8JSYAuNtgG59JJV3VASALOd3R0zqcjMnGmy80febhafMlmKJnY5mwzca4ntu3ZPv6fFGxpAXQALZjn8YwWWU3Bffx/xApNVrqOZ6gQKpZZlpTxSiyMsjRFUSmUhXmePYvVHrNEvOsqnCOl4D4KE7X0v/GsXNBSH5hN+b9QPF/wO1smu/IQRj/nebZO2f0mtvNY/dDLy5D3xeoXi3CWPbeKnJuJM82zrKRHy80UJUS0XGy03e2Oq/Vz5ygt+zSALm3xFCZrxB+5erByy4XxvIhLdL8KI3L+jPGeOFeLcpngMwsfhsWKQvAYbxDu9uoxTpJplmR51dyxral5CXB5UebZT9S6YpoQAp+0yNKyVQ6m5B8uZ8+G8hJtdr6gVYsNayPKlqjMt7gKawACpnlME90goOfPDa6Wy9BYtDHlhSHTpXnddyNPfMBEukO8zjsXrwteUryepUgTzfBAZqdZXi6yeZaGyU1TelWNTiLJaxOfNXU+Z9mKyfwHKssts0rhusxERNAmLv8mzQ0PsNPvrUvXG9Z1dbJlJ0UZ5uUlMU24IM1SxMtuY/J6tEE64zWiJCyKOKKFrIp1AshELPshxlLM1nnEanlMcfETzhGr5us1IUdJWMZPYvc6VFnTuyzGN641CHKjuuVzBxC7oI/AWjW6gSUVblvVVqRCsfs+ninfx5RUjfbYKF79jr100Q1+saFucaDOMtTXf20vp9HzZuluzOW/s23w14/igrsZ+2QuDvcuBJJ4nuLjBD2WI8hfM+gUSHrL37FcRf62P5b8gf3O1Vt2FF6DegONzGFCNHMWP+HDeVm9Oy16yOUSfFOh3nsdGTVU3AnxXUOHnqVBLxgNPJ1n/QGeatbEadlRcbP5yBRGnTcAcO7pRm2PzFpSZg7fLCwWFWbWyLZMHA9QI1Oos2TOADLtMSXjoHdFDqNtEmMlzu1uDX6g6v75oS6oA+2v6xJ3gzge1Gu3wGvTcwCB4TsiLhpggG84rgqND05Hxla1/XK1wgV3WJYVIXI5IU4xDJdE+pUV+patS1S9OntA+L91xsxTgcI8WrSLTJSgJRORdyX2ZBhG1fttXUwNm3etLZZ1Bku5FFUiRxjr8KGqQGwgCdQ4+i2bF+HnQRjPKwJVHIXJJbuwjGezKiBkkQO+DbiagGv90BV1hhWKRnhE5XEk30TVG8fWDGh7gPFsaygMt+K5cPiKlca+XSXreZxeUHW4IFdwzPoBoOS8i9OcZWpsss4/GQRC1Y/8Wi6wTPHQr7ArVDfktz9RtLjOInwJ929OwzJMsjluXp3elNHvwyHM3JK3jK/igro+YYcUF1Q37Q4x69rwEG+TCGJC2HMui8aE13R1y6oXnJ/nZVb7cg9Hlt7v1/RvZc3AqqELTrSD3/K8WotO0g6NU7bHhn/YbhFBS0LQ1oeXurE9hPnWhpeuo6B0RuqbHxPe2zQ4C65nvrHg823diJzQVpQ8J+dNu+qsocwHG65taht4tKxNbQPmcks0+alk98EcdSAGCI4XtBWls74V2JJitThttbVMKEJLsTZUcAr53tmX7XpiRztY/COIdj3borLv3MW54g4pc3bwwTmdm3dhAmX31Q56cs+j2T/HUgC/Z5OXeZPOCSGAcb5JwgILneJ6nyU5OwrDe3qEysj4AFsC2wuMoP3jCth7fJ5rY++cFXs1JFWx/4SRmOfYWGdkxH8Ot8QYfOC8Z1C7nmto3JpgJGi5ypzZg+HeSO1vMB8GdrsjrRX8Zn2+vYgvZAcILpLXw0e6Q3mMpUj0akz/hw/ftv/jjeP/KG6CvIYJLM+wRlrwd2XSjDlLu1wYuT5gQunnTMlvZpuB0c+XGsoFAi87niTv/qDRtCsfRhtt2PuH0lhRg6sZNc4oo+ZQRfel+NSC3l5Fl+sDb08mjNpa5jMss5eaqyGDPN4OsAVHjJnv0AXbNfzx9Ox9uUfbfJr/jC4cHW0mDaIO7ul1kU0StB7oO6PLKB4zo+tFfHpe1KsSMZQlrMhXlyQyxGq1Vrz8Zu9FvLIG6xZKtAoMgTzfDidj3Zr2MUw65bqxAEzSnbOTIX/D+NUZ5Rw/rQnS5lGNNUSALgmbSr5Yhakga44VkdAFDfzIorhn2KsN/l3JyCRh4wULBcnVatlBgflvfCVdLx8qEip7JLfhQSqOMtdJWbTwpw+yA/+3F6beVj8DWQTTFzTK185p5lhRqqfAcdYU8/4Z5k1k2zjf3wXf+6S49lyZ544m85xmxJzgV/feUABeFO1D4pwW3Ca0xeArCLwO0HcQDgftUWA1mg0Kp4RfQ6gJTec7+6KNJZgnHhUOta9AQ3Z2zl7MHSFzUz5/+K3KBzQp4S0d/15PatKMFxw8302z5QprRNp/XnvT+eJ1Oh3HHUBlVoJQ46fCAczUi05KRy3+SnSrPRTb+nq2TDkahgiaeh0a1yI50lYGx7fb2tVZ34L+oCbMU+OnO4xVnfGZPhTkF1/DkROJ/kT5krx9tYaLgcUTliZhjV4uFyhVr31D5TonpvIbd7w1o+dz+ICSiTavqNuPlh1y1a9OSPdXddZyS+9YnHiEtWLbn9l9J7XD29ZUait2mjHTsHzGF9brQPTsRKr/AgqdAkOm9rLHxwKdyurxQTdQaF4vI17efeobnfNsdiz0MElQks3zcElUoOVdCddabldXYls18903gd2rmgEt2czwPWLtuEyXlgwHiMtc3d6ol+VNTttdedw2mCFCXA++pDchBj3Hxjz16tO54x2iEuwdgzM6Gy5UnY1XEf4Alpgz2Lbq3YnNH+HPSxp/VzT+buDrkmLHioDAyxI14pcfajt1EC/XZbLeEhsDNGwMOBdpt4ffZz5gJ79vmWS8K95gtECzdUII/HfoCkoc/UnWAJq+IacFOLoNMCwSFeyBO4QOdMYBnToQ6FTgj5BlEV9n0XopWfdTFu2Gt+6D4ukEEp4Wl3B7r6qlggmGALNHVsZb3UN8WpDgBYYlBuuOG2jXVw1fs4sYKDlpR8Hjv5V44U1NobptKXAIj15x2YGt32YyQtYVf6tT5mZLZ5ix4LEMkM4yN7zfV2K5v6AynIVl+EsYbuBLnAw25Bq3XLdVwx+ASoC6bKQBpuGKjjNv0tmK6mTvvabvCVs3UCdlzQckRpqUoY5wGwDa/6xR9ZJ3eRahoojTeV8w1bSYIfl6KaWmxfNYmq+3DIozcA0QiEBrxvBY+zJgZ7bbcUB/wvP/5gPoAz85NtamOqhLyz0J5cNWWt6HNgz6gaAeX6odSxu8weftw7ThfUwC5/6w7lgzgNdJqB0Ebu2u8YIvYVp9dcJkRqBG+kFpUnt40xxV7j4h7r+hZfZUH68S4s4JcQDLFcDdx1idRnMOxaWfV8UUqAQO4PstzxAG8HjyvAzBcClDvRf1mgUBu3+i7uQofsA7lU4/anUPmKIW7f1qstoaQm3rLqKh8zGg3NFweyu9Tib5IONHlgXjBNU26WaDIvpJPT7nPeYZSW/al+X0K1kuV2IwgK1xgzQxrn94jItPm7+0QFWl+WsWzs0/</diagram></mxfile>" style="background-color: rgb(255, 255, 255);">
|
||||
<svg host="65bd71144e" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="789px" height="766px" viewBox="-0.5 -0.5 789 766" content="<mxfile host="be2f1433-9da5-431b-92db-92882262d7d1" modified="2021-02-10T13:37:29.724Z" agent="5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Code/1.53.0 Chrome/87.0.4280.141 Electron/11.2.1 Safari/537.36" etag="EtMH9Eo_7GPdazvr9TBW" version="14.2.4" type="embed"><diagram id="AOZgdlUmH_6GT6Gt5u4e" name="Page-1">7V1bc+I6Ev41VM08hPJNvjwmJJkzWzN1ssmps2cejRHgGWOxtklgf/1KlmSsC2DABsIkUzWxZcmX/lqt7k8tpWcPZssvWTiffkcjmPQsY7Ts2fc9y7IdF/9PClaswLJpwSSLR7TIXBe8xP+DrNBgpYt4BHOhYoFQUsRzsTBCaQqjQigLswy9idXGKBGfOg8nUCl4icJELf1PPCqmtNQHxrr8DxhPpvzJpsGuDMPo1yRDi5Q9r2fZ4/KHXp6F/F6sfj4NR+itVmQ/9OxBhlBBj2bLAUyIaLnYaLvHDVer985gWjRp4Dq0xWuYLCB/5fLFihUXxts0LuDLPIzI+RvGu2ffTYtZgs9MfBjmcwrBOF5CfNu7cZwkA5SgrGxuW+bAuAW4PC8y9AvWrhiG6wKftEBpUSsHA/IPl6tfwz7wFWYFXNaK2Nd9gWgGi2yFq7CrIAC0CdNEJwjo+dsaV9NhaEzrmPLCkOnSpLr3Wp74gIl0g3jtKxevA84pXs9UpAlHuCOzU5QVUzRBaZg8rEvvyt5JJHlv4LN1nW8IzZnMf8KiWDGrFC4KJCICl3HxD2ne9wA7/VG7dL9kty5PVuwkL8KsuCWmCRekKIW87DEmn0cbpCNeI0rCPI8jWsiqmO2CnKNFFjGpeUxP8QtNIKvm0yIi0K2akMEkLOJX0XLqUGVNn1CM36TSIJcbVaZBngXEW9B3Yq3WuoElFa5q1eakQr75OZ4hP8eQVI3eca141Tc20kUnOLqrX3TPNjku9Z5tg4569uLv1e0gelvOnKUx+xdaBX//zG+4Sm4Tsdi7d9nWJJ6k+DiB4+LS5W+bjiJ/y+/KsgLrurTZlXr/JWgz0IjYTYgijuJXfDgpym+nRcNMLsEPFepda0eooOIuhu/0deiZmr4QdAaezm/+AE+1YmK3s1XcrEDX67wWgHOOt2FbZFaTMnPnRmE+LTEzOw5pxP7gamTq6sYFuwWZNhiBcUg7J4fRKomxEmfWbg0eUnX/NqwKqjD6z0WBbwM5HtQnN8Gl6TlwQd+3RVw0wAC/bzsqND44HhlL1fbb+RwXPGFZlnTHbY+4vG44I9IvrdAzWhSw/HT2gu5/F4iZpxyGWTStFxkwgTMmIu9OvFO/3y/v/lgVU8Pm3WuLZZ3BUi5ElcggxjoclhWIDSRhGEe/ZvMi/D4Q43lHoIqjMLllF2bxaFSGeywuwI8Bdz1wr++6os6wQtEId6g8tuRpq3pjW5oObbXQny0NQeGULBYOTrHSlMhJY9g8WUzi9IYqyA2pi2PU9iBlw9h7BtQBTh8EAqiBxmPR8SJtGGmNZ/lnMcXdBBuDErtcBfXTXzCa3qMIX8L3NwZhESZoQk8eiujzB77bXFLHJ1yQAjDoCmB3H++TCKJHuHIui7VJr8jpmpXPORvPy8z65QaOLX3e7+nvypqBVUMXrFi6YMX0vEqLjtIOjZMm2nTBgn8MxiKCpoSgpQ83dS52GyOyNtx0bAWlExLd/Jiw3Eafc956nhsLPltVjcgJbUWpcnK+bleerQnyfburQKmvCfM6q17GDL9gEU17CsXOFGvNrxvbFKjOlgOPltXZcsDc+pbp8r1Z7kAMQmwvqCvfzvpmYEnKWmPF1dYyR+maigWjglPo+533shxPvNGGeYADqHo9o6Py99xpuuMuLnOf8MHp3KWrMKoymW0FGjLbO6VFtU0F7hc2HBoP6YRQDhjlB2xVsNApri8oydhRGL7QI2xa+h9gS2B7QT+o/zgC9h4fOevY2yfFXg16Vey/YiQmGTbViPT3b+GKmIIPnIVOLZK7joejXtVRCjqClqvMiX0i7t9UHgzzitzdDk4jd6XKLhCcLq+B1/UEsxhLkejVgR5VI++Hd9+69+N14/0oToI8jgDT65sdJQw4Mi3HXKVNDoxcHzChNHOllBHSCPrNPKm2HCBw3v4kxQt79aZN+TTa+MXa3pU66jU8PhB6jd1Jr9lX0X0p4jVdb6uiy/WBtyWTRm0tMySm0UjN1YBB7m972IID+swP1wGrhfvz9c37/gJX2SD7Fd3YOiJO6kTvKU1CJj880HREl1E8ZETXi/jK8qpcWcKKfHXTAm3Mh2vFyx92LeKVNdg0GiqwC+Txtj0Z62bND+HmKXuOBWCQ29kbOfd3jF+Vkc7x05ogbUZxV10E6JK4qeTzeZgKsuZYEQnd0MCPTLt7fWu+xL9LGRkkbLxhoSC5Wk5kKDD/g6+ki9mwpKDQmDyGB6k4ylwkRV7Dn77IBvzfX5j6WP60ZBEMX9AoXzumGV1FqZ4Cx0lT1JtnqK8j27Xz/UPwvY+Kazvyq21N5rrdejTaeEECOCva+8Q5NbgN1xKDryDwdoC+gXDYa42DdgLmhGrC8wPPPWVjCuaJR4VtrUvQkJ07Ry/mjpCxKZsMP5UZhwYlvKXjz9WgJo14wd7j3QDN5lgj0ubj2rteOVUl7HHcgauMSq6r8VPdFszUWQelg6aTJbrVaottPduSK1tDCHFq9cwWyZbWTNq+VdeunfVN12/VhHlq/PSEsapyStNhTn7xORw5NekvmM3I15czuBhYPGBpEuDo5WIKU/XaMywWGTGVz9zx1vSeb+EQJj1tptJuP1p2yFW/OiG3v6vyomt6x+LEffSRGwDFXFVLqtm79OrLknVmzOibPuMLq3kgenYk1X/jCjcFfZnaQ+NxDo9l9XgvbCk0r6YRb5++No3Oeb48xidMEpigSRbOiArUvCvhWs3t2pUqV458L+vA7qJGQFM2M3zRWT0u0yU+uy3EZY5u9dU75k0OXGjTRojruef0JsSg59CYp5p9OnW8Q1SCfWPQnbPB9+O4/PAHsMSc1pZlb06V/gh/zmn8HdH4O4GvS7PtKgIC5yVqxJ0jKju1Fy+3y2RdMBsDNGxM+wm0jXVhM0PCfMCd/L5pkP6ueIPRFI4WCSHwr9AVbJOjdw2/L6cF2LolNSwSFeyB04YO7IwDds/x6FTgS8hyiO9RtJhR6248ZogEpNJarCNn8do39xXAShjbBuJ2ICFucgzq62VNFW7QBtwN8jZ+y3XMJHfXFMN52wm0M7B9X7OSGShZawfB47+XiOKSB1ndKhW3E59fceqBpV+G0kFeFv/MY0ZvU2e6n+EkzgvBePfYWnbjOyzCUViE+BDjk/As8T/CdISH+0/558s36i0YC+BLBA626RofXrdJkd8C7+DqUpf2Qz7QAV9yd8ZDOppT9Wy81PWasHUCdXzW7GfR0fjs6ti5FqD99wKWH/mUoQjmeZxOmoKp5tC0Se5L+Tc1UsjUbCbTKs7qVgW6vbG6WsTh7kyNOwzor9gVWH4AvecOaDpL3QrKuhzeo1Deb1rmOrTh1NvidqUNXuvj9n7acB2DwKl38e1qBPB2sm97gVu5a7zge5iWm14YzAhUSA+VJpWHN8hgSDbGIiz/M5yh1+p4nhB3TqRzaOYAvn2M1akz51CcJ7oo0kDlcgBfnHmCMICHlqclC9rLL2o8A7iePbCaZ/VuhFfY/vkyNq8AhqhFW7doVlu7rrb1Ls5h52u48o3aW4jp7aSd9zJ+ZA4xTmBlkx6WMKI7/PExb0yp520pUb+T5XIkBgNYGjdIE+P6LcS4vsYN2m/rPUJLL9vcluAytmbTJKUpIDcOc3T77uk2mzhgWzZ8uv5DHbTzr/8Yiv3wfw==</diagram></mxfile>" style="background-color: rgb(255, 255, 255);">
|
||||
<defs/>
|
||||
<g>
|
||||
<rect x="560" y="484" width="140" height="140" fill="#21c0a5" stroke="#006658" pointer-events="all"/>
|
||||
<rect x="420" y="484" width="140" height="140" fill="#21c0a5" stroke="#006658" pointer-events="all"/>
|
||||
<path d="M 664.5 595 L 664.5 705 L 595.54 705" fill="none" stroke="#006658" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 590.29 705 L 597.29 701.5 L 595.54 705 L 597.29 708.5 Z" fill="#006658" stroke="#006658" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<rect x="420" y="110" width="140" height="140" fill="#21c0a5" stroke="#006658" pointer-events="all"/>
|
||||
<rect x="420" y="110" width="135" height="140" fill="#21c0a5" stroke="#006658" pointer-events="all"/>
|
||||
<rect x="420" y="299" width="280" height="140" fill="#21c0a5" stroke="#006658" pointer-events="all"/>
|
||||
<rect x="560" y="110" width="140" height="140" fill="#21c0a5" stroke="#006658" pointer-events="all"/>
|
||||
<rect x="565" y="110" width="135" height="140" fill="#21c0a5" stroke="#006658" pointer-events="all"/>
|
||||
<rect x="90" y="469.25" width="110" height="90" fill="#21c0a5" stroke="#006658" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
@@ -66,24 +66,26 @@
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="415" y="90" width="210" height="20" fill="none" stroke="none" pointer-events="all"/>
|
||||
<rect x="419.59" y="80" width="140" height="30" fill="none" stroke="none" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 100px; margin-left: 520px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 1px; height: 1px; padding-top: 95px; margin-left: 422px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #5C5C5C; line-height: 1.2; pointer-events: all; white-space: nowrap; ">
|
||||
@backstage/plugin-search-backend
|
||||
@backstage/
|
||||
<br/>
|
||||
plugin-search-backend
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="520" y="104" fill="#5C5C5C" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
@backstage/plugin-search-backend
|
||||
<text x="422" y="99" fill="#5C5C5C" font-family="Helvetica" font-size="12px">
|
||||
@backstage/...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="90" y="433.75" width="160" height="30" fill="none" stroke="none" pointer-events="all"/>
|
||||
<rect x="90" y="433.75" width="150" height="30" fill="none" stroke="none" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
@@ -138,9 +140,9 @@
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 630 364.25 L 758 364.3 L 758 177.3 L 661.87 177.25" fill="none" stroke="#006658" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 656.62 177.25 L 663.62 173.75 L 661.87 177.25 L 663.62 180.75 Z" fill="#006658" stroke="#006658" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<rect x="415" y="279" width="280" height="20" fill="none" stroke="none" pointer-events="all"/>
|
||||
<path d="M 636.37 364.25 L 758 364.3 L 758 177.3 L 655.5 177.25" fill="none" stroke="#006658" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 631.12 364.25 L 638.12 360.75 L 636.37 364.25 L 638.12 367.75 Z" fill="#006658" stroke="#006658" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<rect x="420" y="279" width="270" height="20" fill="none" stroke="none" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
@@ -266,7 +268,7 @@
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 220px; margin-left: 351px;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 220px; margin-left: 350px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #5C5C5C; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">
|
||||
Pass Search
|
||||
@@ -280,7 +282,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="351" y="223" fill="#5C5C5C" font-family="Helvetica" font-size="11px" text-anchor="middle">
|
||||
<text x="350" y="223" fill="#5C5C5C" font-family="Helvetica" font-size="11px" text-anchor="middle">
|
||||
Pass Search...
|
||||
</text>
|
||||
</switch>
|
||||
@@ -352,8 +354,8 @@
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 49px; height: 1px; padding-top: 165px; margin-left: 606px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #FFFFFF; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
|
||||
<font style="font-size: 9px">
|
||||
Gather Documents
|
||||
<font style="font-size: 7px">
|
||||
Gather Documents From Plugins
|
||||
</font>
|
||||
</div>
|
||||
</div>
|
||||
@@ -366,7 +368,7 @@
|
||||
</g>
|
||||
<path d="M 444.17 349.5 C 444.17 341.22 453.31 334.5 464.59 334.5 C 470.01 334.5 475.2 336.08 479.03 338.89 C 482.86 341.71 485.01 345.52 485.01 349.5 L 485.01 379 C 485.01 387.28 475.87 394 464.59 394 C 453.31 394 444.17 387.28 444.17 379 Z" fill="#21c0a5" stroke="#006658" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<path d="M 485.01 349.5 C 485.01 357.78 475.87 364.5 464.59 364.5 C 453.31 364.5 444.17 357.78 444.17 349.5" fill="none" stroke="#006658" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<path d="M 550 364.25 L 517.5 364.3 L 485.01 364.3" fill="none" stroke="#006658" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 550 364.25 L 517.5 364.3 L 485.01 364.25" fill="none" stroke="#006658" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<rect x="550" y="324.25" width="80" height="80" fill="#21c0a5" stroke="#006658" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
@@ -375,16 +377,14 @@
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #FFFFFF; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
|
||||
<font style="font-size: 11px">
|
||||
Collate Documents
|
||||
<br/>
|
||||
Or Metadata
|
||||
Register Document / Metadata Collation Handler(s)
|
||||
</font>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="590" y="368" fill="#FFFFFF" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
Collate Docum...
|
||||
Register Docu...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
@@ -427,11 +427,11 @@
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="560" y="110" width="80" height="20" fill="none" stroke="none" pointer-events="all"/>
|
||||
<rect x="565" y="110" width="80" height="20" fill="none" stroke="none" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 120px; margin-left: 600px;">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 120px; margin-left: 605px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #FFFFFF; line-height: 1.2; pointer-events: all; white-space: nowrap; ">
|
||||
<font style="font-size: 9px">
|
||||
@@ -441,7 +441,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="600" y="123" fill="#FFFFFF" font-family="Helvetica" font-size="11px" text-anchor="middle">
|
||||
<text x="605" y="123" fill="#FFFFFF" font-family="Helvetica" font-size="11px" text-anchor="middle">
|
||||
Index Processi...
|
||||
</text>
|
||||
</switch>
|
||||
@@ -529,10 +529,29 @@
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="565" y="80" width="130" height="30" fill="none" stroke="none" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 1px; height: 1px; padding-top: 95px; margin-left: 567px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #5C5C5C; line-height: 1.2; pointer-events: all; white-space: nowrap; ">
|
||||
@backstage/
|
||||
<br/>
|
||||
plugin-search-indexer
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="567" y="99" fill="#5C5C5C" font-family="Helvetica" font-size="12px">
|
||||
@backstage/...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
</g>
|
||||
<switch>
|
||||
<g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/>
|
||||
<a transform="translate(0,-5)" xlink:href="https://desk.draw.io/support/solutions/articles/16000042487" target="_blank">
|
||||
<a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank">
|
||||
<text text-anchor="middle" font-size="10px" x="50%" y="100%">
|
||||
Viewer does not support full SVG 1.1
|
||||
</text>
|
||||
|
||||
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 46 KiB |
@@ -76,14 +76,16 @@ by also providing the `cert` configuration.
|
||||
|
||||
### Configuration
|
||||
|
||||
Each authentication provider (except SAML) needs five parameters: an OAuth
|
||||
client ID, a client secret, an authorization endpoint, a token endpoint, and an
|
||||
app origin. The app origin is the URL at which the frontend of the application
|
||||
is hosted, and it is read from the `app.baseUrl` config. This is required
|
||||
because the application opens a popup window to perform the authentication, and
|
||||
once the flow is completed, the popup window sends a `postMessage` to the
|
||||
frontend application to indicate the result of the operation. Also this URL is
|
||||
used to verify that authentication requests are coming from only this endpoint.
|
||||
Each authentication provider (except SAML) needs six parameters: an OAuth client
|
||||
ID, a client secret, an authorization endpoint, a token endpoint, an optional
|
||||
list of scopes (as a string separated by spaces) that may be required by the
|
||||
OAuth2 Server to enable end-user sign-on, and an app origin. The app origin is
|
||||
the URL at which the frontend of the application is hosted, and it is read from
|
||||
the `app.baseUrl` config. This is required because the application opens a popup
|
||||
window to perform the authentication, and once the flow is completed, the popup
|
||||
window sends a `postMessage` to the frontend application to indicate the result
|
||||
of the operation. Also this URL is used to verify that authentication requests
|
||||
are coming from only this endpoint.
|
||||
|
||||
These values are configured via the `app-config.yaml` present in the root of
|
||||
your app folder.
|
||||
@@ -109,6 +111,18 @@ auth:
|
||||
development:
|
||||
clientId:
|
||||
$env:
|
||||
oauth2:
|
||||
development:
|
||||
clientId:
|
||||
$env: AUTH_OAUTH2_CLIENT_ID
|
||||
clientSecret:
|
||||
$env: AUTH_OAUTH2_CLIENT_SECRET
|
||||
authorizationUrl:
|
||||
$env: AUTH_OAUTH2_AUTH_URL
|
||||
tokenUrl:
|
||||
$env: AUTH_OAUTH2_TOKEN_URL
|
||||
scope:
|
||||
$env: AUTH_OAUTH2_SCOPE
|
||||
saml:
|
||||
entryPoint:
|
||||
$env: AUTH_SAML_ENTRY_POINT
|
||||
|
||||
@@ -24,12 +24,18 @@ Backstage ecosystem.
|
||||
|
||||
## Project roadmap
|
||||
|
||||
| Version | Description |
|
||||
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Backstage Search V.0 ✅ | Search Frontend letting you search through the entities of the software catalog. [See V.0 Use Cases.](#backstage-search-v0) |
|
||||
| Backstage Search V.1 ⌛ | Basic “out-of-the-box” in-memory indexing process of entities, and their metadata, registered to the Software Catalog. [See V.1 Use Cases.](#backstage-search-v1) |
|
||||
| Backstage Search V.2 ⌛ | Search Backend responsible for the indexing process of entities, and their metadata, registered to the Software Catalog. [See V.2 Use Cases.](#backstage-search-v2) |
|
||||
| Backstage Search V.3 ⌛ | Standardized Search API lets you index other plugins data to the search engine of choice. [See V.3 Use Cases.](#backstage-search-v3) |
|
||||
| Version | Description |
|
||||
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| Backstage Search v0 ✅ | Search Frontend letting you search through the entities of the software catalog. [See v0 Use Cases.](#backstage-search-v0) |
|
||||
| [Backstage Search V0.5 ⌛][v0.5] | Foundations for the architecture. |
|
||||
| [Backstage Search v1 ⌛][v1] | Basic “out-of-the-box” in-memory indexing process of entities, and their metadata, registered to the Software Catalog. [See v1 Use Cases.](#backstage-search-v1) |
|
||||
| [Backstage Search v2 ⌛][v2] | Search Backend responsible for the indexing process of entities, and their metadata, registered to the Software Catalog. [See v2 Use Cases.](#backstage-search-v2) |
|
||||
| [Backstage Search v3 ⌛][v3] | Standardized Search API lets you index other plugins data to the search engine of choice. [See v3 Use Cases.](#backstage-search-v3) |
|
||||
|
||||
[v0.5]: https://github.com/backstage/backstage/milestone/25
|
||||
[v1]: https://github.com/backstage/backstage/milestone/26
|
||||
[v2]: https://github.com/backstage/backstage/milestone/27
|
||||
[v3]: https://github.com/backstage/backstage/milestone/28
|
||||
|
||||
## Use Cases
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@ description: Documentation on Search Architecture
|
||||
|
||||
# Search Architecture
|
||||
|
||||
> _This is a proposed architecture which has not been implemented yet. We are
|
||||
> still looking for feedback to improve the architecture to fit your use-case,
|
||||
> see [this open issue](https://github.com/backstage/backstage/issues/4078)._
|
||||
> _This is a proposed architecture which has not been implemented yet. Find our
|
||||
> milestones to follow our progress on the
|
||||
> [Search Roadmap](./README.md#project-roadmap)._
|
||||
|
||||
Below you can explore the Search Architecture. Our aim with this architecture is
|
||||
to support a wide variety of search engines, while providing a simple developer
|
||||
@@ -20,7 +20,10 @@ Backstage end-users.
|
||||
At a base-level, we want to support the following:
|
||||
|
||||
- We aim to enable the capability to search across the entire Backstage
|
||||
ecosystem by decoupling search from content management.
|
||||
ecosystem including, but not limited to, entities in the software catalog.
|
||||
Searchable content won't be required to relate directly to the software
|
||||
catalog, but by convention, we may encourage loose relationships using
|
||||
well-known field names or attributes.
|
||||
- We aim to enable the capability to deploy Backstage using any search engine,
|
||||
by providing an integration and translation layer between the core search
|
||||
plugin and search engine specific logic that can be extended for different
|
||||
@@ -29,11 +32,17 @@ At a base-level, we want to support the following:
|
||||
|
||||
More advanced use-cases we hope to support with this architecture include:
|
||||
|
||||
- It should be easy for any plugin to expose new content to search. (e.g. entity
|
||||
metadata, documentation from TechDocs)
|
||||
- It should be easy for any plugin to append relevant metadata to existing
|
||||
- It should be possible for any plugin to expose new content to search. (e.g.
|
||||
entity metadata, documentation from TechDocs)
|
||||
- It should be possible for any plugin to append relevant metadata to existing
|
||||
content in search. (e.g. location (path) for TechDocs page)
|
||||
- It should be easy to refine search queries (e.g. ranking, scoring, etc.)
|
||||
- It should be easy to customize the search UI
|
||||
- It should be easy to add search functionality to any Backstage plugin or
|
||||
- It should be possible to refine search queries (e.g. ranking, scoring, etc.)
|
||||
- It should be possible to customize the search UI
|
||||
- It should be possible to add search functionality to any Backstage plugin or
|
||||
deployment
|
||||
|
||||
Architecture non-goals:
|
||||
|
||||
- At this time, we do not intend to directly support event-driven or incremental
|
||||
index management. Instead, we'll be focused on scheduled, bulk index
|
||||
management.
|
||||
|
||||
@@ -27,11 +27,15 @@ REPOSITORY_URL='https://github.com/org/repo'
|
||||
git clone $REPOSITORY_URL
|
||||
cd repo
|
||||
|
||||
# Install @techdocs/cli, mkdocs and mkdocs plugins
|
||||
npm install -g @techdocs/cli
|
||||
pip install mkdocs-techdocs-core==0.*
|
||||
|
||||
# Generate
|
||||
npx @techdocs/cli generate
|
||||
techdocs-cli generate --no-docker
|
||||
|
||||
# Publish
|
||||
npx @techdocs/cli publish --publisher-type awsS3 --storage-name <bucket/container> --entity <Namespace/Kind/Name>
|
||||
techdocs-cli publish --publisher-type awsS3 --storage-name <bucket/container> --entity <Namespace/Kind/Name>
|
||||
```
|
||||
|
||||
That's it!
|
||||
@@ -40,14 +44,16 @@ Take a look at
|
||||
[`techdocs-cli` README](https://github.com/backstage/techdocs-cli) for the
|
||||
complete command reference, details, and options.
|
||||
|
||||
## 1. Setup a workflow
|
||||
## Steps
|
||||
|
||||
### 1. Setup a workflow
|
||||
|
||||
The TechDocs workflow should trigger on CI when any changes are made in the
|
||||
repository containing the documentation files. You can be specific and configure
|
||||
the workflow to be triggered only when files inside the `docs/` directory or
|
||||
`mkdocs.yml` are changed.
|
||||
|
||||
## 2. Prepare step
|
||||
### 2. Prepare step
|
||||
|
||||
The first step on the CI is to clone your documentation source repository in a
|
||||
working directory. This is almost always the first step in most CI workflows.
|
||||
@@ -62,7 +68,7 @@ step.
|
||||
|
||||
Eventually we are trying to do a `git clone <https://path/to/docs-repository/>`.
|
||||
|
||||
## 3. Generate step
|
||||
### 3. Generate step
|
||||
|
||||
Install [`npx`](https://www.npmjs.com/package/npx) to use it for running
|
||||
`techdocs-cli`. Or you can install using `npm install -g @techdocs/cli`.
|
||||
@@ -78,7 +84,7 @@ npx @techdocs/cli generate --no-docker --source-dir PATH_TO_REPO --output-dir ./
|
||||
`PATH_TO_REPO` should be the location in the file path where the prepare step
|
||||
above clones the repository.
|
||||
|
||||
## 4. Publish step
|
||||
### 4. Publish step
|
||||
|
||||
Depending on your cloud storage provider (AWS, Google Cloud, or Azure), set the
|
||||
necessary authentication environment variables.
|
||||
@@ -96,3 +102,68 @@ npx @techdocs/cli publish --publisher-type <awsS3|googleGcs> --storage-name <buc
|
||||
|
||||
The updated TechDocs site built in this workflow is now ready to be served by
|
||||
the TechDocs plugin in your Backstage app.
|
||||
|
||||
## Example: GitHub Actions CI and AWS S3
|
||||
|
||||
Here is an example workflow using GitHub Actions CI and AWS S3 storage. You can
|
||||
use any CI and any other
|
||||
[TechDocs supported cloud storage providers](README.md#platforms-supported).
|
||||
|
||||
Add a `.github/workflows/techdocs.yml` file in your
|
||||
[Software Template(s)](../software-templates/index.md) like this -
|
||||
|
||||
```yaml
|
||||
name: Publish TechDocs Site
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
# You can even set it to run only when TechDocs related files are updated.
|
||||
# paths:
|
||||
# - "docs/**"
|
||||
# - "mkdocs.yml"
|
||||
|
||||
jobs:
|
||||
publish-techdocs-site:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# The following secrets are required in your CI environment for publishing files to AWS S3.
|
||||
# e.g. You can use GitHub Organization secrets to set them for all existing and new repositories.
|
||||
env:
|
||||
TECHDOCS_S3_BUCKET_NAME: ${{ secrets.TECHDOCS_S3_BUCKET_NAME }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_REGION: ${{ secrets.AWS_REGION }}
|
||||
ENTITY_NAMESPACE: 'default'
|
||||
ENTITY_KIND: 'Component'
|
||||
ENTITY_NAME: 'my-doc-entity'
|
||||
# In a Software template, Scaffolder will replace {{cookiecutter.component_id | jsonify}}
|
||||
# with the correct entity name. This is same as metadata.name in the entity's catalog-info.yaml
|
||||
# ENTITY_NAME: '{{ cookiecutter.component_id | jsonify }}'
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- uses: actions/setup-node@v2
|
||||
- uses: actions/setup-python@v2
|
||||
|
||||
- name: Install techdocs-cli
|
||||
run: sudo npm install -g @techdocs/cli
|
||||
|
||||
- name: Install mkdocs and mkdocs plugins
|
||||
run: python -m pip install mkdocs-techdocs-core==0.*
|
||||
|
||||
- name: Generate docs site
|
||||
run: techdocs-cli generate --no-docker --verbose
|
||||
|
||||
- name: Publish docs site
|
||||
run:
|
||||
techdocs-cli publish --publisher-type awsS3 --storage-name
|
||||
$TECHDOCS_S3_BUCKET_NAME --entity
|
||||
$ENTITY_NAMESPACE/$ENTITY_KIND/$ENTITY_NAME
|
||||
```
|
||||
|
||||
When the new repository is scaffolded or new documentation updates are
|
||||
committed, the GitHub Action workflow will publish the TechDocs site, which can
|
||||
be viewed in your Backstage app.
|
||||
|
||||
@@ -157,15 +157,17 @@ techdocs:
|
||||
builder: 'local'
|
||||
```
|
||||
|
||||
Set `techdocs.builder` to `'local'` if you want your TechDocs Backend to be
|
||||
responsible for generating documentation sites. If set to `'external'`,
|
||||
Backstage will assume that the sites are being generated on each entity's CI/CD
|
||||
pipeline, and are being stored in a storage somewhere.
|
||||
Note that we recommend generating docs on CI/CD instead. Read more in the
|
||||
"Basic" and "Recommended" sections of the
|
||||
[TechDocs Architecture](architecture.md). But if you want to get started quickly
|
||||
set `techdocs.builder` to `'local'` so that TechDocs Backend is responsible for
|
||||
generating documentation sites. If set to `'external'`, Backstage will assume
|
||||
that the sites are being generated on each entity's CI/CD pipeline, and are
|
||||
being stored in a storage somewhere.
|
||||
|
||||
When `techdocs.builder` is set to `'external'`, TechDocs becomes more or less a
|
||||
read-only experience where it serves static files from a storage containing all
|
||||
the generated documentation. Read more in the "Basic" and "Recommended" sections
|
||||
of the [TechDocs Architecture](architecture.md).
|
||||
the generated documentation.
|
||||
|
||||
### Choosing storage (publisher)
|
||||
|
||||
|
||||
@@ -99,13 +99,15 @@ Are you missing a plugin for your favorite tool? Please
|
||||
[suggest a new one](https://github.com/backstage/backstage/issues/new?labels=plugin&template=plugin_template.md&title=%5BPlugin%5D+THE+PLUGIN+NAME).
|
||||
Chances are that someone will jump in and help build it.
|
||||
|
||||
### Community Initiatives 🧑🤝🧑 (Coming soon)
|
||||
### Community Initiatives 🧑🤝🧑
|
||||
|
||||
- **Backstage Monthly Meetup** - A space for the community to come together to
|
||||
share and learn about the latest happenings in Backstage.
|
||||
- [**Backstage Community Sessions**](https://github.com/backstage/community#meetups) -
|
||||
A monthly meetup for the community to come together to share and learn about
|
||||
the latest happenings in Backstage.
|
||||
|
||||
- **Backstage Hackathons** - Open to everyone in our Backstage community, a
|
||||
celebration of you, the project and building awesome things together
|
||||
- **Backstage Hackathons** - (Coming soon) Open to everyone in our Backstage
|
||||
community, a celebration of you, the project and building awesome things
|
||||
together
|
||||
|
||||
### Completed milestones ✅
|
||||
|
||||
|
||||
@@ -384,11 +384,3 @@ A frontend plugin that provides a page where the user can tweak various
|
||||
settings.
|
||||
|
||||
Stability: `1`
|
||||
|
||||
### `welcome` [GitHub](https://github.com/backstage/backstage/tree/master/plugins/welcome/)
|
||||
|
||||
A plugin that can be used to welcome the user to Backstage.
|
||||
|
||||
Stability: `0`. This used to be the start page for the example app, but has been
|
||||
replaced by the catalog plugin. It is still viewable at `/welcome` but may be
|
||||
removed.
|
||||
|
||||
@@ -14,7 +14,7 @@ Here's a code sample:
|
||||
import { createPlugin } from '@backstage/core';
|
||||
|
||||
export default createPlugin({
|
||||
id: 'welcome',
|
||||
id: 'plugin-name',
|
||||
register({ featureFlags }) {
|
||||
featureFlags.register('enable-example-feature');
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# AlertApi
|
||||
|
||||
The AlertApi type is defined at
|
||||
[packages/core-api/src/apis/definitions/AlertApi.ts:29](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/AlertApi.ts#L29).
|
||||
[packages/core-api/src/apis/definitions/AlertApi.ts:29](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/AlertApi.ts#L29).
|
||||
|
||||
The following Utility API implements this type: [alertApiRef](./README.md#alert)
|
||||
|
||||
@@ -38,7 +38,7 @@ export type AlertMessage = {
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/core-api/src/apis/definitions/AlertApi.ts:19](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/AlertApi.ts#L19).
|
||||
[packages/core-api/src/apis/definitions/AlertApi.ts:19](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/AlertApi.ts#L19).
|
||||
|
||||
Referenced by: [post](#post), [alert\$](#alert).
|
||||
|
||||
@@ -67,7 +67,7 @@ export type Observable<T> = {
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/core-api/src/types.ts:53](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/types.ts#L53).
|
||||
[packages/core-api/src/types.ts:53](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/types.ts#L53).
|
||||
|
||||
Referenced by: [alert\$](#alert).
|
||||
|
||||
@@ -86,7 +86,7 @@ export type Observer<T> = {
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/core-api/src/types.ts:24](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/types.ts#L24).
|
||||
[packages/core-api/src/types.ts:24](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/types.ts#L24).
|
||||
|
||||
Referenced by: [Observable](#observable).
|
||||
|
||||
@@ -109,6 +109,6 @@ export type Subscription = {
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/core-api/src/types.ts:33](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/types.ts#L33).
|
||||
[packages/core-api/src/types.ts:33](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/types.ts#L33).
|
||||
|
||||
Referenced by: [Observable](#observable).
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# AppThemeApi
|
||||
|
||||
The AppThemeApi type is defined at
|
||||
[packages/core-api/src/apis/definitions/AppThemeApi.ts:56](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/AppThemeApi.ts#L56).
|
||||
[packages/core-api/src/apis/definitions/AppThemeApi.ts:56](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/AppThemeApi.ts#L56).
|
||||
|
||||
The following Utility API implements this type:
|
||||
[appThemeApiRef](./README.md#apptheme)
|
||||
@@ -81,7 +81,7 @@ export type AppTheme = {
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/core-api/src/apis/definitions/AppThemeApi.ts:25](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/AppThemeApi.ts#L25).
|
||||
[packages/core-api/src/apis/definitions/AppThemeApi.ts:25](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/AppThemeApi.ts#L25).
|
||||
|
||||
Referenced by: [getInstalledThemes](#getinstalledthemes).
|
||||
|
||||
@@ -92,7 +92,7 @@ export type BackstagePalette = Palette & <a href="#paletteadditions">Palette
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/theme/src/types.ts:74](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/theme/src/types.ts#L74).
|
||||
[packages/theme/src/types.ts:74](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/theme/src/types.ts#L74).
|
||||
|
||||
Referenced by: [BackstageTheme](#backstagetheme).
|
||||
|
||||
@@ -107,7 +107,7 @@ export interface BackstageTheme extends Theme {
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/theme/src/types.ts:81](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/theme/src/types.ts#L81).
|
||||
[packages/theme/src/types.ts:81](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/theme/src/types.ts#L81).
|
||||
|
||||
Referenced by: [AppTheme](#apptheme).
|
||||
|
||||
@@ -136,7 +136,7 @@ export type Observable<T> = {
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/core-api/src/types.ts:53](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/types.ts#L53).
|
||||
[packages/core-api/src/types.ts:53](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/types.ts#L53).
|
||||
|
||||
Referenced by: [activeThemeId\$](#activethemeid).
|
||||
|
||||
@@ -155,7 +155,7 @@ export type Observer<T> = {
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/core-api/src/types.ts:24](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/types.ts#L24).
|
||||
[packages/core-api/src/types.ts:24](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/types.ts#L24).
|
||||
|
||||
Referenced by: [Observable](#observable).
|
||||
|
||||
@@ -170,7 +170,7 @@ export type PageTheme = {
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/theme/src/types.ts:103](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/theme/src/types.ts#L103).
|
||||
[packages/theme/src/types.ts:103](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/theme/src/types.ts#L103).
|
||||
|
||||
Referenced by: [BackstageTheme](#backstagetheme).
|
||||
|
||||
@@ -183,7 +183,7 @@ export type PageThemeSelector = {
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/theme/src/types.ts:77](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/theme/src/types.ts#L77).
|
||||
[packages/theme/src/types.ts:77](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/theme/src/types.ts#L77).
|
||||
|
||||
Referenced by: [BackstageTheme](#backstagetheme).
|
||||
|
||||
@@ -243,7 +243,7 @@ type PaletteAdditions = {
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/theme/src/types.ts:23](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/theme/src/types.ts#L23).
|
||||
[packages/theme/src/types.ts:23](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/theme/src/types.ts#L23).
|
||||
|
||||
Referenced by: [BackstagePalette](#backstagepalette).
|
||||
|
||||
@@ -266,6 +266,6 @@ export type Subscription = {
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/core-api/src/types.ts:33](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/types.ts#L33).
|
||||
[packages/core-api/src/types.ts:33](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/types.ts#L33).
|
||||
|
||||
Referenced by: [Observable](#observable).
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# BackstageIdentityApi
|
||||
|
||||
The BackstageIdentityApi type is defined at
|
||||
[packages/core-api/src/apis/definitions/auth.ts:134](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/auth.ts#L134).
|
||||
[packages/core-api/src/apis/definitions/auth.ts:134](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/auth.ts#L134).
|
||||
|
||||
The following Utility APIs implement this type:
|
||||
|
||||
@@ -17,8 +17,12 @@ The following Utility APIs implement this type:
|
||||
|
||||
- [oauth2ApiRef](./README.md#oauth2)
|
||||
|
||||
- [oidcAuthApiRef](./README.md#oidcauth)
|
||||
|
||||
- [oktaAuthApiRef](./README.md#oktaauth)
|
||||
|
||||
- [oneloginAuthApiRef](./README.md#oneloginauth)
|
||||
|
||||
- [samlAuthApiRef](./README.md#samlauth)
|
||||
|
||||
## Members
|
||||
@@ -70,7 +74,7 @@ export type AuthRequestOptions = {
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/core-api/src/apis/definitions/auth.ts:40](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/auth.ts#L40).
|
||||
[packages/core-api/src/apis/definitions/auth.ts:40](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/auth.ts#L40).
|
||||
|
||||
Referenced by: [getBackstageIdentity](#getbackstageidentity).
|
||||
|
||||
@@ -79,7 +83,7 @@ Referenced by: [getBackstageIdentity](#getbackstageidentity).
|
||||
<pre>
|
||||
export type BackstageIdentity = {
|
||||
/**
|
||||
* The Backstage user ID.
|
||||
* The backstage user ID.
|
||||
*/
|
||||
id: string;
|
||||
|
||||
@@ -91,6 +95,6 @@ export type BackstageIdentity = {
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/core-api/src/apis/definitions/auth.ts:147](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/auth.ts#L147).
|
||||
[packages/core-api/src/apis/definitions/auth.ts:147](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/auth.ts#L147).
|
||||
|
||||
Referenced by: [getBackstageIdentity](#getbackstageidentity).
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Config
|
||||
|
||||
The Config type is defined at
|
||||
[packages/config/src/types.ts:32](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/config/src/types.ts#L32).
|
||||
[packages/config/src/types.ts:32](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/config/src/types.ts#L32).
|
||||
|
||||
The following Utility API implements this type:
|
||||
[configApiRef](./README.md#config)
|
||||
@@ -140,7 +140,7 @@ export type Config = {
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/config/src/types.ts:32](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/config/src/types.ts#L32).
|
||||
[packages/config/src/types.ts:32](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/config/src/types.ts#L32).
|
||||
|
||||
Referenced by: [getConfig](#getconfig), [getOptionalConfig](#getoptionalconfig),
|
||||
[getConfigArray](#getconfigarray),
|
||||
@@ -153,7 +153,7 @@ export type JsonArray = <a href="#jsonvalue">JsonValue</a>[]
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/config/src/types.ts:18](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/config/src/types.ts#L18).
|
||||
[packages/config/src/types.ts:18](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/config/src/types.ts#L18).
|
||||
|
||||
Referenced by: [JsonValue](#jsonvalue).
|
||||
|
||||
@@ -164,7 +164,7 @@ export type JsonObject = { [key in string]?: <a href="#jsonvalue">JsonValue</a>
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/config/src/types.ts:17](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/config/src/types.ts#L17).
|
||||
[packages/config/src/types.ts:17](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/config/src/types.ts#L17).
|
||||
|
||||
Referenced by: [JsonValue](#jsonvalue).
|
||||
|
||||
@@ -181,7 +181,7 @@ export type JsonValue =
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/config/src/types.ts:19](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/config/src/types.ts#L19).
|
||||
[packages/config/src/types.ts:19](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/config/src/types.ts#L19).
|
||||
|
||||
Referenced by: [get](#get), [getOptional](#getoptional),
|
||||
[JsonObject](#jsonobject), [JsonArray](#jsonarray), [Config](#config).
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# DiscoveryApi
|
||||
|
||||
The DiscoveryApi type is defined at
|
||||
[packages/core-api/src/apis/definitions/DiscoveryApi.ts:30](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/DiscoveryApi.ts#L30).
|
||||
[packages/core-api/src/apis/definitions/DiscoveryApi.ts:30](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/DiscoveryApi.ts#L30).
|
||||
|
||||
The following Utility API implements this type:
|
||||
[discoveryApiRef](./README.md#discovery)
|
||||
@@ -12,7 +12,7 @@ The following Utility API implements this type:
|
||||
|
||||
Returns the HTTP base backend URL for a given plugin, without a trailing slash.
|
||||
|
||||
This method must always be called just before making a request. as opposed to
|
||||
This method must always be called just before making a request, as opposed to
|
||||
fetching the URL when constructing an API client. That is to ensure that more
|
||||
flexible routing patterns can be supported.
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# ErrorApi
|
||||
|
||||
The ErrorApi type is defined at
|
||||
[packages/core-api/src/apis/definitions/ErrorApi.ts:53](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/ErrorApi.ts#L53).
|
||||
[packages/core-api/src/apis/definitions/ErrorApi.ts:53](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/ErrorApi.ts#L53).
|
||||
|
||||
The following Utility API implements this type: [errorApiRef](./README.md#error)
|
||||
|
||||
@@ -41,7 +41,7 @@ type Error = {
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/core-api/src/apis/definitions/ErrorApi.ts:24](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/ErrorApi.ts#L24).
|
||||
[packages/core-api/src/apis/definitions/ErrorApi.ts:24](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/ErrorApi.ts#L24).
|
||||
|
||||
Referenced by: [post](#post), [error\$](#error).
|
||||
|
||||
@@ -58,7 +58,7 @@ export type ErrorContext = {
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/core-api/src/apis/definitions/ErrorApi.ts:33](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/ErrorApi.ts#L33).
|
||||
[packages/core-api/src/apis/definitions/ErrorApi.ts:33](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/ErrorApi.ts#L33).
|
||||
|
||||
Referenced by: [post](#post), [error\$](#error).
|
||||
|
||||
@@ -87,7 +87,7 @@ export type Observable<T> = {
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/core-api/src/types.ts:53](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/types.ts#L53).
|
||||
[packages/core-api/src/types.ts:53](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/types.ts#L53).
|
||||
|
||||
Referenced by: [error\$](#error).
|
||||
|
||||
@@ -106,7 +106,7 @@ export type Observer<T> = {
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/core-api/src/types.ts:24](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/types.ts#L24).
|
||||
[packages/core-api/src/types.ts:24](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/types.ts#L24).
|
||||
|
||||
Referenced by: [Observable](#observable).
|
||||
|
||||
@@ -129,6 +129,6 @@ export type Subscription = {
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/core-api/src/types.ts:33](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/types.ts#L33).
|
||||
[packages/core-api/src/types.ts:33](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/types.ts#L33).
|
||||
|
||||
Referenced by: [Observable](#observable).
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# FeatureFlagsApi
|
||||
|
||||
The FeatureFlagsApi type is defined at
|
||||
[packages/core-api/src/apis/definitions/FeatureFlagsApi.ts:60](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/FeatureFlagsApi.ts#L60).
|
||||
[packages/core-api/src/apis/definitions/FeatureFlagsApi.ts:60](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/FeatureFlagsApi.ts#L60).
|
||||
|
||||
The following Utility API implements this type:
|
||||
[featureFlagsApiRef](./README.md#featureflags)
|
||||
@@ -68,7 +68,7 @@ export type FeatureFlag = {
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/core-api/src/apis/definitions/FeatureFlagsApi.ts:31](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/FeatureFlagsApi.ts#L31).
|
||||
[packages/core-api/src/apis/definitions/FeatureFlagsApi.ts:31](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/FeatureFlagsApi.ts#L31).
|
||||
|
||||
Referenced by: [registerFlag](#registerflag),
|
||||
[getRegisteredFlags](#getregisteredflags).
|
||||
@@ -83,7 +83,7 @@ export enum FeatureFlagState {
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/core-api/src/apis/definitions/FeatureFlagsApi.ts:36](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/FeatureFlagsApi.ts#L36).
|
||||
[packages/core-api/src/apis/definitions/FeatureFlagsApi.ts:36](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/FeatureFlagsApi.ts#L36).
|
||||
|
||||
Referenced by: [FeatureFlagsSaveOptions](#featureflagssaveoptions).
|
||||
|
||||
@@ -108,6 +108,6 @@ export type FeatureFlagsSaveOptions = {
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/core-api/src/apis/definitions/FeatureFlagsApi.ts:44](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/FeatureFlagsApi.ts#L44).
|
||||
[packages/core-api/src/apis/definitions/FeatureFlagsApi.ts:44](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/FeatureFlagsApi.ts#L44).
|
||||
|
||||
Referenced by: [save](#save).
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# IdentityApi
|
||||
|
||||
The IdentityApi type is defined at
|
||||
[packages/core-api/src/apis/definitions/IdentityApi.ts:22](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/IdentityApi.ts#L22).
|
||||
[packages/core-api/src/apis/definitions/IdentityApi.ts:22](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/IdentityApi.ts#L22).
|
||||
|
||||
The following Utility API implements this type:
|
||||
[identityApiRef](./README.md#identity)
|
||||
@@ -76,6 +76,6 @@ export type ProfileInfo = {
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/core-api/src/apis/definitions/auth.ts:162](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/auth.ts#L162).
|
||||
[packages/core-api/src/apis/definitions/auth.ts:162](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/auth.ts#L162).
|
||||
|
||||
Referenced by: [getProfile](#getprofile).
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# OAuthApi
|
||||
|
||||
The OAuthApi type is defined at
|
||||
[packages/core-api/src/apis/definitions/auth.ts:67](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/auth.ts#L67).
|
||||
[packages/core-api/src/apis/definitions/auth.ts:67](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/auth.ts#L67).
|
||||
|
||||
The following Utility APIs implement this type:
|
||||
|
||||
@@ -15,8 +15,12 @@ The following Utility APIs implement this type:
|
||||
|
||||
- [oauth2ApiRef](./README.md#oauth2)
|
||||
|
||||
- [oidcAuthApiRef](./README.md#oidcauth)
|
||||
|
||||
- [oktaAuthApiRef](./README.md#oktaauth)
|
||||
|
||||
- [oneloginAuthApiRef](./README.md#oneloginauth)
|
||||
|
||||
## Members
|
||||
|
||||
### getAccessToken()
|
||||
@@ -82,7 +86,7 @@ export type AuthRequestOptions = {
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/core-api/src/apis/definitions/auth.ts:40](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/auth.ts#L40).
|
||||
[packages/core-api/src/apis/definitions/auth.ts:40](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/auth.ts#L40).
|
||||
|
||||
Referenced by: [getAccessToken](#getaccesstoken).
|
||||
|
||||
@@ -108,6 +112,6 @@ export type OAuthScope = string | string[]
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/core-api/src/apis/definitions/auth.ts:38](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/auth.ts#L38).
|
||||
[packages/core-api/src/apis/definitions/auth.ts:38](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/auth.ts#L38).
|
||||
|
||||
Referenced by: [getAccessToken](#getaccesstoken).
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# OAuthRequestApi
|
||||
|
||||
The OAuthRequestApi type is defined at
|
||||
[packages/core-api/src/apis/definitions/OAuthRequestApi.ts:99](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L99).
|
||||
[packages/core-api/src/apis/definitions/OAuthRequestApi.ts:99](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L99).
|
||||
|
||||
The following Utility API implements this type:
|
||||
[oauthRequestApiRef](./README.md#oauthrequest)
|
||||
@@ -73,7 +73,7 @@ export type AuthProvider = {
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/core-api/src/apis/definitions/OAuthRequestApi.ts:27](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L27).
|
||||
[packages/core-api/src/apis/definitions/OAuthRequestApi.ts:27](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L27).
|
||||
|
||||
Referenced by: [AuthRequesterOptions](#authrequesteroptions),
|
||||
[PendingAuthRequest](#pendingauthrequest).
|
||||
@@ -97,7 +97,7 @@ export type AuthRequester<AuthResponse> = (
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/core-api/src/apis/definitions/OAuthRequestApi.ts:66](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L66).
|
||||
[packages/core-api/src/apis/definitions/OAuthRequestApi.ts:66](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L66).
|
||||
|
||||
Referenced by: [createAuthRequester](#createauthrequester).
|
||||
|
||||
@@ -122,7 +122,7 @@ export type AuthRequesterOptions<AuthResponse> = {
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/core-api/src/apis/definitions/OAuthRequestApi.ts:43](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L43).
|
||||
[packages/core-api/src/apis/definitions/OAuthRequestApi.ts:43](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L43).
|
||||
|
||||
Referenced by: [createAuthRequester](#createauthrequester).
|
||||
|
||||
@@ -151,7 +151,7 @@ export type Observable<T> = {
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/core-api/src/types.ts:53](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/types.ts#L53).
|
||||
[packages/core-api/src/types.ts:53](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/types.ts#L53).
|
||||
|
||||
Referenced by: [authRequest\$](#authrequest).
|
||||
|
||||
@@ -170,7 +170,7 @@ export type Observer<T> = {
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/core-api/src/types.ts:24](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/types.ts#L24).
|
||||
[packages/core-api/src/types.ts:24](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/types.ts#L24).
|
||||
|
||||
Referenced by: [Observable](#observable).
|
||||
|
||||
@@ -205,7 +205,7 @@ export type PendingAuthRequest = {
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/core-api/src/apis/definitions/OAuthRequestApi.ts:77](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L77).
|
||||
[packages/core-api/src/apis/definitions/OAuthRequestApi.ts:77](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L77).
|
||||
|
||||
Referenced by: [authRequest\$](#authrequest).
|
||||
|
||||
@@ -228,6 +228,6 @@ export type Subscription = {
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/core-api/src/types.ts:33](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/types.ts#L33).
|
||||
[packages/core-api/src/types.ts:33](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/types.ts#L33).
|
||||
|
||||
Referenced by: [Observable](#observable).
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# OpenIdConnectApi
|
||||
|
||||
The OpenIdConnectApi type is defined at
|
||||
[packages/core-api/src/apis/definitions/auth.ts:99](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/auth.ts#L99).
|
||||
[packages/core-api/src/apis/definitions/auth.ts:99](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/auth.ts#L99).
|
||||
|
||||
The following Utility APIs implement this type:
|
||||
|
||||
@@ -13,8 +13,12 @@ The following Utility APIs implement this type:
|
||||
|
||||
- [oauth2ApiRef](./README.md#oauth2)
|
||||
|
||||
- [oidcAuthApiRef](./README.md#oidcauth)
|
||||
|
||||
- [oktaAuthApiRef](./README.md#oktaauth)
|
||||
|
||||
- [oneloginAuthApiRef](./README.md#oneloginauth)
|
||||
|
||||
## Members
|
||||
|
||||
### getIdToken()
|
||||
@@ -66,6 +70,6 @@ export type AuthRequestOptions = {
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/core-api/src/apis/definitions/auth.ts:40](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/auth.ts#L40).
|
||||
[packages/core-api/src/apis/definitions/auth.ts:40](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/auth.ts#L40).
|
||||
|
||||
Referenced by: [getIdToken](#getidtoken).
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# ProfileInfoApi
|
||||
|
||||
The ProfileInfoApi type is defined at
|
||||
[packages/core-api/src/apis/definitions/auth.ts:117](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/auth.ts#L117).
|
||||
[packages/core-api/src/apis/definitions/auth.ts:117](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/auth.ts#L117).
|
||||
|
||||
The following Utility APIs implement this type:
|
||||
|
||||
@@ -17,8 +17,12 @@ The following Utility APIs implement this type:
|
||||
|
||||
- [oauth2ApiRef](./README.md#oauth2)
|
||||
|
||||
- [oidcAuthApiRef](./README.md#oidcauth)
|
||||
|
||||
- [oktaAuthApiRef](./README.md#oktaauth)
|
||||
|
||||
- [oneloginAuthApiRef](./README.md#oneloginauth)
|
||||
|
||||
- [samlAuthApiRef](./README.md#samlauth)
|
||||
|
||||
## Members
|
||||
@@ -67,7 +71,7 @@ export type AuthRequestOptions = {
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/core-api/src/apis/definitions/auth.ts:40](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/auth.ts#L40).
|
||||
[packages/core-api/src/apis/definitions/auth.ts:40](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/auth.ts#L40).
|
||||
|
||||
Referenced by: [getProfile](#getprofile).
|
||||
|
||||
@@ -95,6 +99,6 @@ export type ProfileInfo = {
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/core-api/src/apis/definitions/auth.ts:162](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/auth.ts#L162).
|
||||
[packages/core-api/src/apis/definitions/auth.ts:162](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/auth.ts#L162).
|
||||
|
||||
Referenced by: [getProfile](#getprofile).
|
||||
|
||||
@@ -12,7 +12,7 @@ Used to report alerts and forward them to the app
|
||||
Implemented type: [AlertApi](./AlertApi.md)
|
||||
|
||||
ApiRef:
|
||||
[alertApiRef](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/AlertApi.ts#L41)
|
||||
[alertApiRef](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/AlertApi.ts#L41)
|
||||
|
||||
### appTheme
|
||||
|
||||
@@ -21,7 +21,7 @@ API Used to configure the app theme, and enumerate options
|
||||
Implemented type: [AppThemeApi](./AppThemeApi.md)
|
||||
|
||||
ApiRef:
|
||||
[appThemeApiRef](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/AppThemeApi.ts#L80)
|
||||
[appThemeApiRef](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/AppThemeApi.ts#L80)
|
||||
|
||||
### auth0Auth
|
||||
|
||||
@@ -32,7 +32,7 @@ Implemented types: [OpenIdConnectApi](./OpenIdConnectApi.md),
|
||||
[BackstageIdentityApi](./BackstageIdentityApi.md), [SessionApi](./SessionApi.md)
|
||||
|
||||
ApiRef:
|
||||
[auth0AuthApiRef](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/auth.ts#L275)
|
||||
[auth0AuthApiRef](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/auth.ts#L275)
|
||||
|
||||
### config
|
||||
|
||||
@@ -41,7 +41,7 @@ Used to access runtime configuration
|
||||
Implemented type: [Config](./Config.md)
|
||||
|
||||
ApiRef:
|
||||
[configApiRef](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/ConfigApi.ts#L22)
|
||||
[configApiRef](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/ConfigApi.ts#L25)
|
||||
|
||||
### discovery
|
||||
|
||||
@@ -50,7 +50,7 @@ Provides service discovery of backend plugins
|
||||
Implemented type: [DiscoveryApi](./DiscoveryApi.md)
|
||||
|
||||
ApiRef:
|
||||
[discoveryApiRef](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/DiscoveryApi.ts#L44)
|
||||
[discoveryApiRef](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/DiscoveryApi.ts#L44)
|
||||
|
||||
### error
|
||||
|
||||
@@ -59,7 +59,7 @@ Used to report errors and forward them to the app
|
||||
Implemented type: [ErrorApi](./ErrorApi.md)
|
||||
|
||||
ApiRef:
|
||||
[errorApiRef](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/ErrorApi.ts#L65)
|
||||
[errorApiRef](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/ErrorApi.ts#L65)
|
||||
|
||||
### featureFlags
|
||||
|
||||
@@ -68,7 +68,7 @@ Used to toggle functionality in features across Backstage
|
||||
Implemented type: [FeatureFlagsApi](./FeatureFlagsApi.md)
|
||||
|
||||
ApiRef:
|
||||
[featureFlagsApiRef](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/FeatureFlagsApi.ts#L83)
|
||||
[featureFlagsApiRef](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/FeatureFlagsApi.ts#L83)
|
||||
|
||||
### githubAuth
|
||||
|
||||
@@ -79,7 +79,7 @@ Implemented types: [OAuthApi](./OAuthApi.md),
|
||||
[BackstageIdentityApi](./BackstageIdentityApi.md), [SessionApi](./SessionApi.md)
|
||||
|
||||
ApiRef:
|
||||
[githubAuthApiRef](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/auth.ts#L232)
|
||||
[githubAuthApiRef](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/auth.ts#L232)
|
||||
|
||||
### gitlabAuth
|
||||
|
||||
@@ -90,7 +90,7 @@ Implemented types: [OAuthApi](./OAuthApi.md),
|
||||
[BackstageIdentityApi](./BackstageIdentityApi.md), [SessionApi](./SessionApi.md)
|
||||
|
||||
ApiRef:
|
||||
[gitlabAuthApiRef](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/auth.ts#L262)
|
||||
[gitlabAuthApiRef](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/auth.ts#L262)
|
||||
|
||||
### googleAuth
|
||||
|
||||
@@ -102,7 +102,7 @@ Implemented types: [OAuthApi](./OAuthApi.md),
|
||||
[BackstageIdentityApi](./BackstageIdentityApi.md), [SessionApi](./SessionApi.md)
|
||||
|
||||
ApiRef:
|
||||
[googleAuthApiRef](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/auth.ts#L215)
|
||||
[googleAuthApiRef](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/auth.ts#L215)
|
||||
|
||||
### identity
|
||||
|
||||
@@ -111,7 +111,7 @@ Provides access to the identity of the signed in user
|
||||
Implemented type: [IdentityApi](./IdentityApi.md)
|
||||
|
||||
ApiRef:
|
||||
[identityApiRef](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/IdentityApi.ts#L54)
|
||||
[identityApiRef](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/IdentityApi.ts#L53)
|
||||
|
||||
### microsoftAuth
|
||||
|
||||
@@ -123,7 +123,7 @@ Implemented types: [OAuthApi](./OAuthApi.md),
|
||||
[BackstageIdentityApi](./BackstageIdentityApi.md), [SessionApi](./SessionApi.md)
|
||||
|
||||
ApiRef:
|
||||
[microsoftAuthApiRef](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/auth.ts#L289)
|
||||
[microsoftAuthApiRef](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/auth.ts#L289)
|
||||
|
||||
### oauth2
|
||||
|
||||
@@ -135,7 +135,7 @@ Implemented types: [OAuthApi](./OAuthApi.md),
|
||||
[BackstageIdentityApi](./BackstageIdentityApi.md), [SessionApi](./SessionApi.md)
|
||||
|
||||
ApiRef:
|
||||
[oauth2ApiRef](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/auth.ts#L303)
|
||||
[oauth2ApiRef](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/auth.ts#L303)
|
||||
|
||||
### oauthRequest
|
||||
|
||||
@@ -144,7 +144,19 @@ An API for implementing unified OAuth flows in Backstage
|
||||
Implemented type: [OAuthRequestApi](./OAuthRequestApi.md)
|
||||
|
||||
ApiRef:
|
||||
[oauthRequestApiRef](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L130)
|
||||
[oauthRequestApiRef](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L130)
|
||||
|
||||
### oidcAuth
|
||||
|
||||
Example of how to use oidc custom provider
|
||||
|
||||
Implemented types: [OAuthApi](./OAuthApi.md),
|
||||
[OpenIdConnectApi](./OpenIdConnectApi.md),
|
||||
[ProfileInfoApi](./ProfileInfoApi.md),
|
||||
[BackstageIdentityApi](./BackstageIdentityApi.md), [SessionApi](./SessionApi.md)
|
||||
|
||||
ApiRef:
|
||||
[oidcAuthApiRef](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/auth.ts#L317)
|
||||
|
||||
### oktaAuth
|
||||
|
||||
@@ -156,7 +168,19 @@ Implemented types: [OAuthApi](./OAuthApi.md),
|
||||
[BackstageIdentityApi](./BackstageIdentityApi.md), [SessionApi](./SessionApi.md)
|
||||
|
||||
ApiRef:
|
||||
[oktaAuthApiRef](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/auth.ts#L245)
|
||||
[oktaAuthApiRef](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/auth.ts#L245)
|
||||
|
||||
### oneloginAuth
|
||||
|
||||
Provides authentication towards OneLogin APIs and identities
|
||||
|
||||
Implemented types: [OAuthApi](./OAuthApi.md),
|
||||
[OpenIdConnectApi](./OpenIdConnectApi.md),
|
||||
[ProfileInfoApi](./ProfileInfoApi.md),
|
||||
[BackstageIdentityApi](./BackstageIdentityApi.md), [SessionApi](./SessionApi.md)
|
||||
|
||||
ApiRef:
|
||||
[oneloginAuthApiRef](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/auth.ts#L338)
|
||||
|
||||
### samlAuth
|
||||
|
||||
@@ -166,7 +190,7 @@ Implemented types: [ProfileInfoApi](./ProfileInfoApi.md),
|
||||
[BackstageIdentityApi](./BackstageIdentityApi.md), [SessionApi](./SessionApi.md)
|
||||
|
||||
ApiRef:
|
||||
[samlAuthApiRef](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/auth.ts#L317)
|
||||
[samlAuthApiRef](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/auth.ts#L331)
|
||||
|
||||
### storage
|
||||
|
||||
@@ -175,4 +199,4 @@ Provides the ability to store data which is unique to the user
|
||||
Implemented type: [StorageApi](./StorageApi.md)
|
||||
|
||||
ApiRef:
|
||||
[storageApiRef](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/StorageApi.ts#L68)
|
||||
[storageApiRef](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/StorageApi.ts#L68)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# SessionApi
|
||||
|
||||
The SessionApi type is defined at
|
||||
[packages/core-api/src/apis/definitions/auth.ts:190](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/auth.ts#L190).
|
||||
[packages/core-api/src/apis/definitions/auth.ts:190](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/auth.ts#L190).
|
||||
|
||||
The following Utility APIs implement this type:
|
||||
|
||||
@@ -17,8 +17,12 @@ The following Utility APIs implement this type:
|
||||
|
||||
- [oauth2ApiRef](./README.md#oauth2)
|
||||
|
||||
- [oidcAuthApiRef](./README.md#oidcauth)
|
||||
|
||||
- [oktaAuthApiRef](./README.md#oktaauth)
|
||||
|
||||
- [oneloginAuthApiRef](./README.md#oneloginauth)
|
||||
|
||||
- [samlAuthApiRef](./README.md#samlauth)
|
||||
|
||||
## Members
|
||||
@@ -77,7 +81,7 @@ export type Observable<T> = {
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/core-api/src/types.ts:53](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/types.ts#L53).
|
||||
[packages/core-api/src/types.ts:53](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/types.ts#L53).
|
||||
|
||||
Referenced by: [sessionState\$](#sessionstate).
|
||||
|
||||
@@ -96,7 +100,7 @@ export type Observer<T> = {
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/core-api/src/types.ts:24](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/types.ts#L24).
|
||||
[packages/core-api/src/types.ts:24](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/types.ts#L24).
|
||||
|
||||
Referenced by: [Observable](#observable).
|
||||
|
||||
@@ -112,7 +116,7 @@ export enum SessionState {
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/core-api/src/apis/definitions/auth.ts:182](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/auth.ts#L182).
|
||||
[packages/core-api/src/apis/definitions/auth.ts:182](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/auth.ts#L182).
|
||||
|
||||
Referenced by: [sessionState\$](#sessionstate).
|
||||
|
||||
@@ -135,6 +139,6 @@ export type Subscription = {
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/core-api/src/types.ts:33](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/types.ts#L33).
|
||||
[packages/core-api/src/types.ts:33](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/types.ts#L33).
|
||||
|
||||
Referenced by: [Observable](#observable).
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# StorageApi
|
||||
|
||||
The StorageApi type is defined at
|
||||
[packages/core-api/src/apis/definitions/StorageApi.ts:31](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/StorageApi.ts#L31).
|
||||
[packages/core-api/src/apis/definitions/StorageApi.ts:31](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/StorageApi.ts#L31).
|
||||
|
||||
The following Utility API implements this type:
|
||||
[storageApiRef](./README.md#storage)
|
||||
@@ -79,7 +79,7 @@ export type Observable<T> = {
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/core-api/src/types.ts:53](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/types.ts#L53).
|
||||
[packages/core-api/src/types.ts:53](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/types.ts#L53).
|
||||
|
||||
Referenced by: [observe\$](#observe), [StorageApi](#storageapi).
|
||||
|
||||
@@ -98,7 +98,7 @@ export type Observer<T> = {
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/core-api/src/types.ts:24](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/types.ts#L24).
|
||||
[packages/core-api/src/types.ts:24](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/types.ts#L24).
|
||||
|
||||
Referenced by: [Observable](#observable).
|
||||
|
||||
@@ -144,7 +144,7 @@ export interface StorageApi {
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/core-api/src/apis/definitions/StorageApi.ts:31](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/StorageApi.ts#L31).
|
||||
[packages/core-api/src/apis/definitions/StorageApi.ts:31](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/StorageApi.ts#L31).
|
||||
|
||||
Referenced by: [forBucket](#forbucket).
|
||||
|
||||
@@ -158,7 +158,7 @@ export type StorageValueChange<T = any> = {
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/core-api/src/apis/definitions/StorageApi.ts:21](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/apis/definitions/StorageApi.ts#L21).
|
||||
[packages/core-api/src/apis/definitions/StorageApi.ts:21](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/apis/definitions/StorageApi.ts#L21).
|
||||
|
||||
Referenced by: [observe\$](#observe), [StorageApi](#storageapi).
|
||||
|
||||
@@ -181,6 +181,6 @@ export type Subscription = {
|
||||
</pre>
|
||||
|
||||
Defined at
|
||||
[packages/core-api/src/types.ts:33](https://github.com/backstage/backstage/blob/ca535f2f66c3a4980c80f4b1a049dfd07569010e/packages/core-api/src/types.ts#L33).
|
||||
[packages/core-api/src/types.ts:33](https://github.com/backstage/backstage/blob/a4dbd8353cfa4d4d4334473e2c33afcda64e130d/packages/core-api/src/types.ts#L33).
|
||||
|
||||
Referenced by: [Observable](#observable).
|
||||
|
||||
@@ -1115,6 +1115,10 @@ code {
|
||||
font-size: 115%;
|
||||
}
|
||||
|
||||
.post pre {
|
||||
color: #e4e4e4;
|
||||
}
|
||||
|
||||
.Block__GIF {
|
||||
width: 190px;
|
||||
height: auto;
|
||||
|
||||
@@ -1,5 +1,91 @@
|
||||
# example-app
|
||||
|
||||
## 0.2.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 07bafa248: Add configurable `scope` for oauth2 auth provider.
|
||||
|
||||
Some OAuth2 providers require certain scopes to facilitate a user sign-in using the Authorization Code flow.
|
||||
This change adds the optional `scope` key to auth.providers.oauth2. An example is:
|
||||
|
||||
```yaml
|
||||
auth:
|
||||
providers:
|
||||
oauth2:
|
||||
development:
|
||||
clientId:
|
||||
$env: DEV_OAUTH2_CLIENT_ID
|
||||
clientSecret:
|
||||
$env: DEV_OAUTH2_CLIENT_SECRET
|
||||
authorizationUrl:
|
||||
$env: DEV_OAUTH2_AUTH_URL
|
||||
tokenUrl:
|
||||
$env: DEV_OAUTH2_TOKEN_URL
|
||||
scope: saml-login-selector openid profile email
|
||||
```
|
||||
|
||||
This tells the OAuth 2.0 AS to perform a SAML login and return OIDC information include the `profile`
|
||||
and `email` claims as part of the ID Token.
|
||||
|
||||
- Updated dependencies [753bb4c40]
|
||||
- Updated dependencies [6ed2b47d6]
|
||||
- Updated dependencies [b33fa4cf4]
|
||||
- Updated dependencies [d36660721]
|
||||
- Updated dependencies [6b26c9f41]
|
||||
- Updated dependencies [b3f0c3811]
|
||||
- Updated dependencies [302795d10]
|
||||
- Updated dependencies [9ec66c345]
|
||||
- Updated dependencies [53d3e2d62]
|
||||
- Updated dependencies [ca559171b]
|
||||
- Updated dependencies [53348f0af]
|
||||
- Updated dependencies [f5e564cd6]
|
||||
- Updated dependencies [68dd79d83]
|
||||
- Updated dependencies [29a138636]
|
||||
- Updated dependencies [14aef4b94]
|
||||
- Updated dependencies [41af18227]
|
||||
- Updated dependencies [1df75733e]
|
||||
- Updated dependencies [02d6803e8]
|
||||
- Updated dependencies [b288a291e]
|
||||
- Updated dependencies [025c0c7bf]
|
||||
- Updated dependencies [e5da858d7]
|
||||
- Updated dependencies [9230d07e7]
|
||||
- Updated dependencies [f5f45744e]
|
||||
- Updated dependencies [0fe8ff5be]
|
||||
- Updated dependencies [c5ab91ce3]
|
||||
- Updated dependencies [64b9efac2]
|
||||
- Updated dependencies [19d354c78]
|
||||
- Updated dependencies [7716d1d70]
|
||||
- Updated dependencies [8f3443427]
|
||||
- Updated dependencies [b51ee6ece]
|
||||
- Updated dependencies [accdfeb30]
|
||||
- Updated dependencies [804502a5c]
|
||||
- @backstage/plugin-catalog-import@0.4.0
|
||||
- @backstage/plugin-catalog@0.3.1
|
||||
- @backstage/plugin-kubernetes@0.3.9
|
||||
- @backstage/plugin-rollbar@0.3.0
|
||||
- @backstage/plugin-scaffolder@0.5.0
|
||||
- @backstage/plugin-cost-insights@0.8.1
|
||||
- @backstage/plugin-circleci@0.2.8
|
||||
- @backstage/plugin-search@0.3.0
|
||||
- @backstage/plugin-cloudbuild@0.2.9
|
||||
- @backstage/plugin-register-component@0.2.9
|
||||
- @backstage/plugin-sentry@0.3.5
|
||||
- @backstage/plugin-jenkins@0.3.9
|
||||
- @backstage/plugin-api-docs@0.4.5
|
||||
- @backstage/plugin-lighthouse@0.2.10
|
||||
- @backstage/plugin-techdocs@0.5.6
|
||||
- @backstage/plugin-pagerduty@0.2.8
|
||||
- @backstage/plugin-org@0.3.6
|
||||
- @backstage/plugin-github-actions@0.3.2
|
||||
- @backstage/plugin-explore@0.2.5
|
||||
- @backstage/plugin-newrelic@0.2.5
|
||||
- @backstage/plugin-catalog-react@0.0.3
|
||||
- @backstage/plugin-kafka@0.2.2
|
||||
- @backstage/core@0.6.1
|
||||
- @backstage/plugin-gitops-profiles@0.2.5
|
||||
- @backstage/plugin-tech-radar@0.3.5
|
||||
|
||||
## 0.2.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
+27
-28
@@ -1,40 +1,39 @@
|
||||
{
|
||||
"name": "example-app",
|
||||
"version": "0.2.14",
|
||||
"version": "0.2.15",
|
||||
"private": true,
|
||||
"bundled": true,
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.7.1",
|
||||
"@backstage/cli": "^0.6.0",
|
||||
"@backstage/core": "^0.6.0",
|
||||
"@backstage/plugin-api-docs": "^0.4.4",
|
||||
"@backstage/plugin-catalog": "^0.3.0",
|
||||
"@backstage/plugin-catalog-react": "^0.0.2",
|
||||
"@backstage/plugin-catalog-import": "^0.3.7",
|
||||
"@backstage/plugin-circleci": "^0.2.7",
|
||||
"@backstage/plugin-cloudbuild": "^0.2.8",
|
||||
"@backstage/plugin-cost-insights": "^0.8.0",
|
||||
"@backstage/plugin-explore": "^0.2.4",
|
||||
"@backstage/core": "^0.6.1",
|
||||
"@backstage/plugin-api-docs": "^0.4.5",
|
||||
"@backstage/plugin-catalog": "^0.3.1",
|
||||
"@backstage/plugin-catalog-react": "^0.0.3",
|
||||
"@backstage/plugin-catalog-import": "^0.4.0",
|
||||
"@backstage/plugin-circleci": "^0.2.8",
|
||||
"@backstage/plugin-cloudbuild": "^0.2.9",
|
||||
"@backstage/plugin-cost-insights": "^0.8.1",
|
||||
"@backstage/plugin-explore": "^0.2.5",
|
||||
"@backstage/plugin-gcp-projects": "^0.2.4",
|
||||
"@backstage/plugin-github-actions": "^0.3.1",
|
||||
"@backstage/plugin-gitops-profiles": "^0.2.4",
|
||||
"@backstage/plugin-github-actions": "^0.3.2",
|
||||
"@backstage/plugin-gitops-profiles": "^0.2.5",
|
||||
"@backstage/plugin-graphiql": "^0.2.7",
|
||||
"@backstage/plugin-org": "^0.3.5",
|
||||
"@backstage/plugin-jenkins": "^0.3.8",
|
||||
"@backstage/plugin-kafka": "^0.2.1",
|
||||
"@backstage/plugin-kubernetes": "^0.3.8",
|
||||
"@backstage/plugin-lighthouse": "^0.2.9",
|
||||
"@backstage/plugin-newrelic": "^0.2.4",
|
||||
"@backstage/plugin-pagerduty": "0.2.7",
|
||||
"@backstage/plugin-register-component": "^0.2.8",
|
||||
"@backstage/plugin-rollbar": "^0.2.9",
|
||||
"@backstage/plugin-scaffolder": "^0.4.2",
|
||||
"@backstage/plugin-sentry": "^0.3.4",
|
||||
"@backstage/plugin-search": "^0.2.7",
|
||||
"@backstage/plugin-tech-radar": "^0.3.4",
|
||||
"@backstage/plugin-techdocs": "^0.5.5",
|
||||
"@backstage/plugin-org": "^0.3.6",
|
||||
"@backstage/plugin-jenkins": "^0.3.9",
|
||||
"@backstage/plugin-kafka": "^0.2.2",
|
||||
"@backstage/plugin-kubernetes": "^0.3.9",
|
||||
"@backstage/plugin-lighthouse": "^0.2.10",
|
||||
"@backstage/plugin-newrelic": "^0.2.5",
|
||||
"@backstage/plugin-pagerduty": "0.2.8",
|
||||
"@backstage/plugin-register-component": "^0.2.9",
|
||||
"@backstage/plugin-rollbar": "^0.3.0",
|
||||
"@backstage/plugin-scaffolder": "^0.5.0",
|
||||
"@backstage/plugin-sentry": "^0.3.5",
|
||||
"@backstage/plugin-search": "^0.3.0",
|
||||
"@backstage/plugin-tech-radar": "^0.3.5",
|
||||
"@backstage/plugin-techdocs": "^0.5.6",
|
||||
"@backstage/plugin-user-settings": "^0.2.5",
|
||||
"@backstage/plugin-welcome": "^0.2.5",
|
||||
"@backstage/theme": "^0.2.3",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
@@ -54,7 +53,7 @@
|
||||
"zen-observable": "^0.8.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@backstage/test-utils": "^0.1.7",
|
||||
"@testing-library/cypress": "^7.0.1",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
SignInPage,
|
||||
} from '@backstage/core';
|
||||
import { Router as CatalogRouter } from '@backstage/plugin-catalog';
|
||||
import { Router as ImportComponentRouter } from '@backstage/plugin-catalog-import';
|
||||
import { CatalogImportPage } from '@backstage/plugin-catalog-import';
|
||||
import { ExplorePage } from '@backstage/plugin-explore';
|
||||
import { Router as GraphiQLRouter } from '@backstage/plugin-graphiql';
|
||||
import { Router as LighthouseRouter } from '@backstage/plugin-lighthouse';
|
||||
@@ -69,10 +69,7 @@ const catalogRouteRef = createRouteRef({
|
||||
const routes = (
|
||||
<FlatRoutes>
|
||||
<Navigate key="/" to="/catalog" />
|
||||
<Route
|
||||
path="/catalog-import"
|
||||
element={<ImportComponentRouter catalogRouteRef={catalogRouteRef} />}
|
||||
/>
|
||||
<Route path="/catalog-import" element={<CatalogImportPage />} />
|
||||
<Route
|
||||
path={`${catalogRouteRef.path}`}
|
||||
element={<CatalogRouter EntityPage={EntityPage} />}
|
||||
|
||||
@@ -28,7 +28,11 @@ import {
|
||||
ProvidedApisCard,
|
||||
ProvidingComponentsCard,
|
||||
} from '@backstage/plugin-api-docs';
|
||||
import { AboutCard, EntityPageLayout } from '@backstage/plugin-catalog';
|
||||
import {
|
||||
AboutCard,
|
||||
EntityLinksCard,
|
||||
EntityPageLayout,
|
||||
} from '@backstage/plugin-catalog';
|
||||
import { useEntity } from '@backstage/plugin-catalog-react';
|
||||
import {
|
||||
isPluginApplicableToEntity as isCircleCIAvailable,
|
||||
@@ -177,6 +181,9 @@ const ComponentOverviewContent = ({ entity }: { entity: Entity }) => (
|
||||
<PagerDutyCard entity={entity} />
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item md={4} sm={6}>
|
||||
<EntityLinksCard entity={entity} />
|
||||
</Grid>
|
||||
<RecentCICDRunsSwitcher entity={entity} />
|
||||
{isGitHubAvailable(entity) && (
|
||||
<>
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
samlAuthApiRef,
|
||||
microsoftAuthApiRef,
|
||||
oneloginAuthApiRef,
|
||||
oauth2ApiRef,
|
||||
oidcAuthApiRef,
|
||||
} from '@backstage/core';
|
||||
|
||||
@@ -32,6 +33,12 @@ export const providers = [
|
||||
message: 'Sign In using OpenId Connect',
|
||||
apiRef: oidcAuthApiRef,
|
||||
},
|
||||
{
|
||||
id: 'oauth2-auth-provider',
|
||||
title: 'OAuth 2.0',
|
||||
message: 'Sign In using OAuth 2.0',
|
||||
apiRef: oauth2ApiRef,
|
||||
},
|
||||
{
|
||||
id: 'google-auth-provider',
|
||||
title: 'Google',
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export { plugin as WelcomePlugin } from '@backstage/plugin-welcome';
|
||||
export { plugin as LighthousePlugin } from '@backstage/plugin-lighthouse';
|
||||
export { plugin as CatalogPlugin } from '@backstage/plugin-catalog';
|
||||
export { plugin as ScaffolderPlugin } from '@backstage/plugin-scaffolder';
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
# @backstage/backend-common
|
||||
|
||||
## 0.5.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- ffffea8e6: Minor updates to reflect the changes in `@backstage/integration` that made the fields `apiBaseUrl` and `apiUrl` mandatory.
|
||||
- 82b2c11b6: Set explicit content-type in error handler responses.
|
||||
- 965e200c6: Slight refactoring in support of a future search implementation in `UrlReader`. Mostly moving code around.
|
||||
- 5a5163519: Implement `UrlReader.search` which implements glob matching.
|
||||
- Updated dependencies [ffffea8e6]
|
||||
- @backstage/integration@0.4.0
|
||||
|
||||
## 0.5.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/backend-common",
|
||||
"description": "Common functionality library for Backstage backends",
|
||||
"version": "0.5.2",
|
||||
"version": "0.5.3",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"private": false,
|
||||
@@ -32,7 +32,7 @@
|
||||
"@backstage/cli-common": "^0.1.1",
|
||||
"@backstage/config": "^0.1.2",
|
||||
"@backstage/config-loader": "^0.5.1",
|
||||
"@backstage/integration": "^0.3.2",
|
||||
"@backstage/integration": "^0.4.0",
|
||||
"@octokit/rest": "^18.0.12",
|
||||
"@types/cors": "^2.8.6",
|
||||
"@types/express": "^4.17.6",
|
||||
@@ -50,6 +50,7 @@
|
||||
"knex": "^0.21.6",
|
||||
"lodash": "^4.17.15",
|
||||
"logform": "^2.1.1",
|
||||
"minimatch": "^3.0.4",
|
||||
"minimist": "^1.2.5",
|
||||
"morgan": "^1.10.0",
|
||||
"selfsigned": "^1.10.7",
|
||||
@@ -68,7 +69,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.0",
|
||||
"@backstage/test-utils": "^0.1.5",
|
||||
"@backstage/test-utils": "^0.1.7",
|
||||
"@types/archiver": "^5.1.0",
|
||||
"@types/compression": "^1.7.0",
|
||||
"@types/concat-stream": "^1.6.0",
|
||||
|
||||
@@ -63,10 +63,10 @@ export function errorHandler(
|
||||
return (
|
||||
error: Error,
|
||||
_request: Request,
|
||||
response: Response,
|
||||
res: Response,
|
||||
next: NextFunction,
|
||||
) => {
|
||||
if (response.headersSent) {
|
||||
if (res.headersSent) {
|
||||
// If the headers have already been sent, do not send the response again
|
||||
// as this will throw an error in the backend.
|
||||
next(error);
|
||||
@@ -80,7 +80,9 @@ export function errorHandler(
|
||||
logger.error(error);
|
||||
}
|
||||
|
||||
response.status(status).send(message);
|
||||
res.status(status);
|
||||
res.setHeader('content-type', 'text/plain');
|
||||
res.send(message);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -14,18 +14,22 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as os from 'os';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import {
|
||||
AzureIntegration,
|
||||
readAzureIntegrationConfig,
|
||||
} from '@backstage/integration';
|
||||
import { msw } from '@backstage/test-utils';
|
||||
import fs from 'fs-extra';
|
||||
import mockFs from 'mock-fs';
|
||||
import path from 'path';
|
||||
import { rest } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import * as os from 'os';
|
||||
import path from 'path';
|
||||
import { NotModifiedError } from '../errors';
|
||||
import { getVoidLogger } from '../logging';
|
||||
import { AzureUrlReader } from './AzureUrlReader';
|
||||
import { msw } from '@backstage/test-utils';
|
||||
import { ReadTreeResponseFactory } from './tree';
|
||||
import { NotModifiedError } from '../errors';
|
||||
|
||||
const logger = getVoidLogger();
|
||||
|
||||
@@ -36,6 +40,16 @@ const treeResponseFactory = ReadTreeResponseFactory.create({
|
||||
const tmpDir = os.platform() === 'win32' ? 'C:\\tmp' : '/tmp';
|
||||
|
||||
describe('AzureUrlReader', () => {
|
||||
beforeEach(() => {
|
||||
mockFs({
|
||||
[tmpDir]: mockFs.directory(),
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
const worker = setupServer();
|
||||
msw.setupDefaultHandlers(worker);
|
||||
|
||||
@@ -143,22 +157,18 @@ describe('AzureUrlReader', () => {
|
||||
});
|
||||
|
||||
describe('readTree', () => {
|
||||
beforeEach(() => {
|
||||
mockFs({
|
||||
[tmpDir]: mockFs.directory(),
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
const repoBuffer = fs.readFileSync(
|
||||
path.resolve('src', 'reading', '__fixtures__', 'mock-main.zip'),
|
||||
);
|
||||
|
||||
const processor = new AzureUrlReader(
|
||||
{ host: 'dev.azure.com' },
|
||||
new AzureIntegration(
|
||||
readAzureIntegrationConfig(
|
||||
new ConfigReader({
|
||||
host: 'dev.azure.com',
|
||||
}),
|
||||
),
|
||||
),
|
||||
{ treeResponseFactory },
|
||||
);
|
||||
|
||||
@@ -257,4 +267,79 @@ describe('AzureUrlReader', () => {
|
||||
expect(indexMarkdownFile.toString()).toBe('# Test\n');
|
||||
});
|
||||
});
|
||||
|
||||
describe('search', () => {
|
||||
const repoBuffer = fs.readFileSync(
|
||||
path.resolve('src', 'reading', '__fixtures__', 'mock-main.zip'),
|
||||
);
|
||||
|
||||
const processor = new AzureUrlReader(
|
||||
new AzureIntegration(
|
||||
readAzureIntegrationConfig(
|
||||
new ConfigReader({
|
||||
host: 'dev.azure.com',
|
||||
}),
|
||||
),
|
||||
),
|
||||
{ treeResponseFactory },
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
worker.use(
|
||||
rest.get(
|
||||
'https://dev.azure.com/org-name/project-name/_apis/git/repositories/repo-name/items',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/zip'),
|
||||
ctx.body(repoBuffer),
|
||||
),
|
||||
),
|
||||
rest.get(
|
||||
// https://docs.microsoft.com/en-us/rest/api/azure/devops/git/commits/get%20commits?view=azure-devops-rest-6.0#on-a-branch
|
||||
'https://dev.azure.com/org-name/project-name/_apis/git/repositories/repo-name/commits',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
count: 2,
|
||||
value: [
|
||||
{
|
||||
commitId: '123abc2',
|
||||
comment: 'second commit',
|
||||
},
|
||||
{
|
||||
commitId: '123abc1',
|
||||
comment: 'first commit',
|
||||
},
|
||||
],
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('works for the naive case', async () => {
|
||||
const result = await processor.search(
|
||||
'https://dev.azure.com/org-name/project-name/_git/repo-name?path=%2F**%2Findex.*&version=GBmaster',
|
||||
);
|
||||
expect(result.etag).toBe('123abc2');
|
||||
expect(result.files.length).toBe(1);
|
||||
expect(result.files[0].url).toBe(
|
||||
'https://dev.azure.com/org-name/project-name/_git/repo-name?path=%2Fdocs%2Findex.md&version=GBmaster',
|
||||
);
|
||||
await expect(result.files[0].content()).resolves.toEqual(
|
||||
Buffer.from('# Test\n'),
|
||||
);
|
||||
});
|
||||
|
||||
it('throws NotModifiedError when same etag', async () => {
|
||||
await expect(
|
||||
processor.search(
|
||||
'https://dev.azure.com/org-name/project-name/_git/repo-name?path=**/index.*&version=GBmaster',
|
||||
{ etag: '123abc2' },
|
||||
),
|
||||
).rejects.toThrow(NotModifiedError);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,38 +15,41 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
AzureIntegrationConfig,
|
||||
readAzureIntegrationConfigs,
|
||||
getAzureFileFetchUrl,
|
||||
getAzureDownloadUrl,
|
||||
getAzureRequestOptions,
|
||||
AzureIntegration,
|
||||
getAzureCommitsUrl,
|
||||
getAzureDownloadUrl,
|
||||
getAzureFileFetchUrl,
|
||||
getAzureRequestOptions,
|
||||
ScmIntegrations,
|
||||
} from '@backstage/integration';
|
||||
import fetch from 'cross-fetch';
|
||||
import parseGitUrl from 'git-url-parse';
|
||||
import { Minimatch } from 'minimatch';
|
||||
import { Readable } from 'stream';
|
||||
import { NotFoundError, NotModifiedError } from '../errors';
|
||||
import { ReadTreeResponseFactory } from './tree';
|
||||
import { stripFirstDirectoryFromPath } from './tree/util';
|
||||
import {
|
||||
ReaderFactory,
|
||||
ReadTreeOptions,
|
||||
ReadTreeResponse,
|
||||
SearchOptions,
|
||||
SearchResponse,
|
||||
UrlReader,
|
||||
} from './types';
|
||||
import { ReadTreeResponseFactory } from './tree';
|
||||
|
||||
export class AzureUrlReader implements UrlReader {
|
||||
static factory: ReaderFactory = ({ config, treeResponseFactory }) => {
|
||||
const configs = readAzureIntegrationConfigs(
|
||||
config.getOptionalConfigArray('integrations.azure') ?? [],
|
||||
);
|
||||
return configs.map(options => {
|
||||
const reader = new AzureUrlReader(options, { treeResponseFactory });
|
||||
const predicate = (url: URL) => url.host === options.host;
|
||||
const integrations = ScmIntegrations.fromConfig(config);
|
||||
return integrations.azure.list().map(integration => {
|
||||
const reader = new AzureUrlReader(integration, { treeResponseFactory });
|
||||
const predicate = (url: URL) => url.host === integration.config.host;
|
||||
return { reader, predicate };
|
||||
});
|
||||
};
|
||||
|
||||
constructor(
|
||||
private readonly options: AzureIntegrationConfig,
|
||||
private readonly integration: AzureIntegration,
|
||||
private readonly deps: { treeResponseFactory: ReadTreeResponseFactory },
|
||||
) {}
|
||||
|
||||
@@ -55,7 +58,10 @@ export class AzureUrlReader implements UrlReader {
|
||||
|
||||
let response: Response;
|
||||
try {
|
||||
response = await fetch(builtUrl, getAzureRequestOptions(this.options));
|
||||
response = await fetch(
|
||||
builtUrl,
|
||||
getAzureRequestOptions(this.integration.config),
|
||||
);
|
||||
} catch (e) {
|
||||
throw new Error(`Unable to read ${url}, ${e}`);
|
||||
}
|
||||
@@ -82,7 +88,7 @@ export class AzureUrlReader implements UrlReader {
|
||||
|
||||
const commitsAzureResponse = await fetch(
|
||||
getAzureCommitsUrl(url),
|
||||
getAzureRequestOptions(this.options),
|
||||
getAzureRequestOptions(this.integration.config),
|
||||
);
|
||||
if (!commitsAzureResponse.ok) {
|
||||
const message = `Failed to read tree from ${url}, ${commitsAzureResponse.status} ${commitsAzureResponse.statusText}`;
|
||||
@@ -99,7 +105,9 @@ export class AzureUrlReader implements UrlReader {
|
||||
|
||||
const archiveAzureResponse = await fetch(
|
||||
getAzureDownloadUrl(url),
|
||||
getAzureRequestOptions(this.options, { Accept: 'application/zip' }),
|
||||
getAzureRequestOptions(this.integration.config, {
|
||||
Accept: 'application/zip',
|
||||
}),
|
||||
);
|
||||
if (!archiveAzureResponse.ok) {
|
||||
const message = `Failed to read tree from ${url}, ${archiveAzureResponse.status} ${archiveAzureResponse.statusText}`;
|
||||
@@ -116,8 +124,38 @@ export class AzureUrlReader implements UrlReader {
|
||||
});
|
||||
}
|
||||
|
||||
async search(url: string, options?: SearchOptions): Promise<SearchResponse> {
|
||||
const { filepath } = parseGitUrl(url);
|
||||
const matcher = new Minimatch(filepath);
|
||||
|
||||
// TODO(freben): For now, read the entire repo and filter through that. In
|
||||
// a future improvement, we could be smart and try to deduce that non-glob
|
||||
// prefixes (like for filepaths such as some-prefix/**/a.yaml) can be used
|
||||
// to get just that part of the repo.
|
||||
const treeUrl = new URL(url);
|
||||
treeUrl.searchParams.delete('path');
|
||||
treeUrl.pathname = treeUrl.pathname.replace(/\/+$/, '');
|
||||
|
||||
const tree = await this.readTree(treeUrl.toString(), {
|
||||
etag: options?.etag,
|
||||
filter: path => matcher.match(stripFirstDirectoryFromPath(path)),
|
||||
});
|
||||
const files = await tree.files();
|
||||
|
||||
return {
|
||||
etag: tree.etag,
|
||||
files: files.map(file => ({
|
||||
url: this.integration.resolveUrl({
|
||||
url: `/${file.path}`,
|
||||
base: url,
|
||||
}),
|
||||
content: file.content,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
toString() {
|
||||
const { host, token } = this.options;
|
||||
const { host, token } = this.integration.config;
|
||||
return `azure{host=${host},authed=${Boolean(token)}}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,11 +15,16 @@
|
||||
*/
|
||||
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import {
|
||||
BitbucketIntegration,
|
||||
readBitbucketIntegrationConfig,
|
||||
} from '@backstage/integration';
|
||||
import { msw } from '@backstage/test-utils';
|
||||
import fs from 'fs-extra';
|
||||
import mockFs from 'mock-fs';
|
||||
import { rest } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import { NotModifiedError } from '../errors';
|
||||
import { BitbucketUrlReader } from './BitbucketUrlReader';
|
||||
@@ -30,19 +35,45 @@ const treeResponseFactory = ReadTreeResponseFactory.create({
|
||||
});
|
||||
|
||||
const bitbucketProcessor = new BitbucketUrlReader(
|
||||
{ host: 'bitbucket.org', apiBaseUrl: 'https://api.bitbucket.org/2.0' },
|
||||
new BitbucketIntegration(
|
||||
readBitbucketIntegrationConfig(
|
||||
new ConfigReader({
|
||||
host: 'bitbucket.org',
|
||||
apiBaseUrl: 'https://api.bitbucket.org/2.0',
|
||||
}),
|
||||
),
|
||||
),
|
||||
{ treeResponseFactory },
|
||||
);
|
||||
|
||||
const hostedBitbucketProcessor = new BitbucketUrlReader(
|
||||
{
|
||||
host: 'bitbucket.mycompany.net',
|
||||
apiBaseUrl: 'https://api.bitbucket.mycompany.net/rest/api/1.0',
|
||||
},
|
||||
new BitbucketIntegration(
|
||||
readBitbucketIntegrationConfig(
|
||||
new ConfigReader({
|
||||
host: 'bitbucket.mycompany.net',
|
||||
apiBaseUrl: 'https://api.bitbucket.mycompany.net/rest/api/1.0',
|
||||
}),
|
||||
),
|
||||
),
|
||||
{ treeResponseFactory },
|
||||
);
|
||||
|
||||
const tmpDir = os.platform() === 'win32' ? 'C:\\tmp' : '/tmp';
|
||||
|
||||
describe('BitbucketUrlReader', () => {
|
||||
beforeEach(() => {
|
||||
mockFs({
|
||||
[tmpDir]: mockFs.directory(),
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
const worker = setupServer();
|
||||
msw.setupDefaultHandlers(worker);
|
||||
|
||||
describe('implementation', () => {
|
||||
it('rejects unknown targets', async () => {
|
||||
await expect(
|
||||
@@ -54,19 +85,6 @@ describe('BitbucketUrlReader', () => {
|
||||
});
|
||||
|
||||
describe('readTree', () => {
|
||||
beforeEach(() => {
|
||||
mockFs({
|
||||
'/tmp': mockFs.directory(),
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
const worker = setupServer();
|
||||
msw.setupDefaultHandlers(worker);
|
||||
|
||||
const repoBuffer = fs.readFileSync(
|
||||
path.resolve(
|
||||
'src',
|
||||
@@ -247,12 +265,153 @@ describe('BitbucketUrlReader', () => {
|
||||
expect(() => {
|
||||
/* eslint-disable no-new */
|
||||
new BitbucketUrlReader(
|
||||
{
|
||||
host: 'bitbucket.mycompany.net',
|
||||
},
|
||||
new BitbucketIntegration(
|
||||
readBitbucketIntegrationConfig(
|
||||
new ConfigReader({
|
||||
host: 'bitbucket.mycompany.net',
|
||||
}),
|
||||
),
|
||||
),
|
||||
{ treeResponseFactory },
|
||||
);
|
||||
}).toThrowError('must configure an explicit apiBaseUrl');
|
||||
});
|
||||
});
|
||||
|
||||
describe('search hosted', () => {
|
||||
const repoBuffer = fs.readFileSync(
|
||||
path.resolve(
|
||||
'src',
|
||||
'reading',
|
||||
'__fixtures__',
|
||||
'bitbucket-repo-with-commit-hash.zip',
|
||||
),
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
worker.use(
|
||||
rest.get(
|
||||
'https://api.bitbucket.org/2.0/repositories/backstage/mock',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
mainbranch: {
|
||||
type: 'branch',
|
||||
name: 'master',
|
||||
},
|
||||
}),
|
||||
),
|
||||
),
|
||||
rest.get(
|
||||
'https://bitbucket.org/backstage/mock/get/master.zip',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/zip'),
|
||||
ctx.set(
|
||||
'content-disposition',
|
||||
'attachment; filename=backstage-mock-12ab34cd56ef.zip',
|
||||
),
|
||||
ctx.body(repoBuffer),
|
||||
),
|
||||
),
|
||||
rest.get(
|
||||
'https://api.bitbucket.org/2.0/repositories/backstage/mock/commits/master',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
values: [{ hash: '12ab34cd56ef78gh90ij12kl34mn56op78qr90st' }],
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('works for the naive case', async () => {
|
||||
const result = await bitbucketProcessor.search(
|
||||
'https://bitbucket.org/backstage/mock/src/master/**/index.*',
|
||||
);
|
||||
expect(result.etag).toBe('12ab34cd56ef');
|
||||
expect(result.files.length).toBe(1);
|
||||
expect(result.files[0].url).toBe(
|
||||
'https://bitbucket.org/backstage/mock/src/master/docs/index.md',
|
||||
);
|
||||
await expect(result.files[0].content()).resolves.toEqual(
|
||||
Buffer.from('# Test\n'),
|
||||
);
|
||||
});
|
||||
|
||||
it('throws NotModifiedError when same etag', async () => {
|
||||
await expect(
|
||||
bitbucketProcessor.search(
|
||||
'https://bitbucket.org/backstage/mock/src/master/**/index.*',
|
||||
{ etag: '12ab34cd56ef' },
|
||||
),
|
||||
).rejects.toThrow(NotModifiedError);
|
||||
});
|
||||
});
|
||||
|
||||
describe('search private', () => {
|
||||
const privateBitbucketRepoBuffer = fs.readFileSync(
|
||||
path.resolve(
|
||||
'src',
|
||||
'reading',
|
||||
'__fixtures__',
|
||||
'bitbucket-server-repo.zip',
|
||||
),
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
worker.use(
|
||||
rest.get(
|
||||
'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/archive?format=zip&prefix=mock&path=docs',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/zip'),
|
||||
ctx.set(
|
||||
'content-disposition',
|
||||
'attachment; filename=backstage-mock.zip',
|
||||
),
|
||||
ctx.body(privateBitbucketRepoBuffer),
|
||||
),
|
||||
),
|
||||
rest.get(
|
||||
'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/commits',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
values: [{ id: '12ab34cd56ef78gh90ij12kl34mn56op78qr90st' }],
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('works for the naive case', async () => {
|
||||
const result = await hostedBitbucketProcessor.search(
|
||||
'https://bitbucket.mycompany.net/projects/backstage/repos/mock/browse/**/index.*?at=master',
|
||||
);
|
||||
expect(result.etag).toBe('12ab34cd56ef');
|
||||
expect(result.files.length).toBe(1);
|
||||
expect(result.files[0].url).toBe(
|
||||
'https://bitbucket.mycompany.net/projects/backstage/repos/mock/browse/docs/index.md?at=master',
|
||||
);
|
||||
await expect(result.files[0].content()).resolves.toEqual(
|
||||
Buffer.from('# Test\n'),
|
||||
);
|
||||
});
|
||||
|
||||
it('throws NotModifiedError when same etag', async () => {
|
||||
await expect(
|
||||
hostedBitbucketProcessor.search(
|
||||
'https://bitbucket.mycompany.net/projects/backstage/repos/mock/browse/**/index.*?at=master',
|
||||
{ etag: '12ab34cd56ef' },
|
||||
),
|
||||
).rejects.toThrow(NotModifiedError);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,22 +15,26 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
BitbucketIntegrationConfig,
|
||||
BitbucketIntegration,
|
||||
getBitbucketDefaultBranch,
|
||||
getBitbucketDownloadUrl,
|
||||
getBitbucketFileFetchUrl,
|
||||
getBitbucketRequestOptions,
|
||||
readBitbucketIntegrationConfigs,
|
||||
ScmIntegrations,
|
||||
} from '@backstage/integration';
|
||||
import fetch from 'cross-fetch';
|
||||
import parseGitUrl from 'git-url-parse';
|
||||
import { Minimatch } from 'minimatch';
|
||||
import { Readable } from 'stream';
|
||||
import { NotFoundError, NotModifiedError } from '../errors';
|
||||
import { ReadTreeResponseFactory } from './tree';
|
||||
import { stripFirstDirectoryFromPath } from './tree/util';
|
||||
import {
|
||||
ReaderFactory,
|
||||
ReadTreeOptions,
|
||||
ReadTreeResponse,
|
||||
SearchOptions,
|
||||
SearchResponse,
|
||||
UrlReader,
|
||||
} from './types';
|
||||
|
||||
@@ -39,45 +43,43 @@ import {
|
||||
* the one exposed by Bitbucket Cloud itself.
|
||||
*/
|
||||
export class BitbucketUrlReader implements UrlReader {
|
||||
private readonly config: BitbucketIntegrationConfig;
|
||||
private readonly treeResponseFactory: ReadTreeResponseFactory;
|
||||
|
||||
static factory: ReaderFactory = ({ config, treeResponseFactory }) => {
|
||||
const configs = readBitbucketIntegrationConfigs(
|
||||
config.getOptionalConfigArray('integrations.bitbucket') ?? [],
|
||||
);
|
||||
return configs.map(provider => {
|
||||
const reader = new BitbucketUrlReader(provider, { treeResponseFactory });
|
||||
const predicate = (url: URL) => url.host === provider.host;
|
||||
const integrations = ScmIntegrations.fromConfig(config);
|
||||
return integrations.bitbucket.list().map(integration => {
|
||||
const reader = new BitbucketUrlReader(integration, {
|
||||
treeResponseFactory,
|
||||
});
|
||||
const predicate = (url: URL) => url.host === integration.config.host;
|
||||
return { reader, predicate };
|
||||
});
|
||||
};
|
||||
|
||||
constructor(
|
||||
config: BitbucketIntegrationConfig,
|
||||
deps: { treeResponseFactory: ReadTreeResponseFactory },
|
||||
private readonly integration: BitbucketIntegration,
|
||||
private readonly deps: { treeResponseFactory: ReadTreeResponseFactory },
|
||||
) {
|
||||
const { host, apiBaseUrl, token, username, appPassword } = config;
|
||||
const {
|
||||
host,
|
||||
apiBaseUrl,
|
||||
token,
|
||||
username,
|
||||
appPassword,
|
||||
} = integration.config;
|
||||
|
||||
if (!apiBaseUrl) {
|
||||
throw new Error(
|
||||
`Bitbucket integration for '${host}' must configure an explicit apiBaseUrl`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!token && username && !appPassword) {
|
||||
} else if (!token && username && !appPassword) {
|
||||
throw new Error(
|
||||
`Bitbucket integration for '${host}' has configured a username but is missing a required appPassword.`,
|
||||
);
|
||||
}
|
||||
|
||||
this.config = config;
|
||||
this.treeResponseFactory = deps.treeResponseFactory;
|
||||
}
|
||||
|
||||
async read(url: string): Promise<Buffer> {
|
||||
const bitbucketUrl = getBitbucketFileFetchUrl(url, this.config);
|
||||
const options = getBitbucketRequestOptions(this.config);
|
||||
const bitbucketUrl = getBitbucketFileFetchUrl(url, this.integration.config);
|
||||
const options = getBitbucketRequestOptions(this.integration.config);
|
||||
|
||||
let response: Response;
|
||||
try {
|
||||
@@ -108,10 +110,13 @@ export class BitbucketUrlReader implements UrlReader {
|
||||
throw new NotModifiedError();
|
||||
}
|
||||
|
||||
const downloadUrl = await getBitbucketDownloadUrl(url, this.config);
|
||||
const downloadUrl = await getBitbucketDownloadUrl(
|
||||
url,
|
||||
this.integration.config,
|
||||
);
|
||||
const archiveBitbucketResponse = await fetch(
|
||||
downloadUrl,
|
||||
getBitbucketRequestOptions(this.config),
|
||||
getBitbucketRequestOptions(this.integration.config),
|
||||
);
|
||||
if (!archiveBitbucketResponse.ok) {
|
||||
const message = `Failed to read tree from ${url}, ${archiveBitbucketResponse.status} ${archiveBitbucketResponse.statusText}`;
|
||||
@@ -121,7 +126,7 @@ export class BitbucketUrlReader implements UrlReader {
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return await this.treeResponseFactory.fromZipArchive({
|
||||
return await this.deps.treeResponseFactory.fromZipArchive({
|
||||
stream: (archiveBitbucketResponse.body as unknown) as Readable,
|
||||
subpath: filepath,
|
||||
etag: lastCommitShortHash,
|
||||
@@ -129,8 +134,36 @@ export class BitbucketUrlReader implements UrlReader {
|
||||
});
|
||||
}
|
||||
|
||||
async search(url: string, options?: SearchOptions): Promise<SearchResponse> {
|
||||
const { filepath } = parseGitUrl(url);
|
||||
const matcher = new Minimatch(filepath);
|
||||
|
||||
// TODO(freben): For now, read the entire repo and filter through that. In
|
||||
// a future improvement, we could be smart and try to deduce that non-glob
|
||||
// prefixes (like for filepaths such as some-prefix/**/a.yaml) can be used
|
||||
// to get just that part of the repo.
|
||||
const treeUrl = url.replace(filepath, '').replace(/\/+$/, '');
|
||||
|
||||
const tree = await this.readTree(treeUrl, {
|
||||
etag: options?.etag,
|
||||
filter: path => matcher.match(stripFirstDirectoryFromPath(path)),
|
||||
});
|
||||
const files = await tree.files();
|
||||
|
||||
return {
|
||||
etag: tree.etag,
|
||||
files: files.map(file => ({
|
||||
url: this.integration.resolveUrl({
|
||||
url: `/${file.path}`,
|
||||
base: url,
|
||||
}),
|
||||
content: file.content,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
toString() {
|
||||
const { host, token, username, appPassword } = this.config;
|
||||
const { host, token, username, appPassword } = this.integration.config;
|
||||
let authed = Boolean(token);
|
||||
if (!authed) {
|
||||
authed = Boolean(username && appPassword);
|
||||
@@ -143,18 +176,18 @@ export class BitbucketUrlReader implements UrlReader {
|
||||
|
||||
let branch = ref;
|
||||
if (!branch) {
|
||||
branch = await getBitbucketDefaultBranch(url, this.config);
|
||||
branch = await getBitbucketDefaultBranch(url, this.integration.config);
|
||||
}
|
||||
|
||||
const isHosted = resource === 'bitbucket.org';
|
||||
// Bitbucket Server https://docs.atlassian.com/bitbucket-server/rest/7.9.0/bitbucket-rest.html#idp222
|
||||
const commitsApiUrl = isHosted
|
||||
? `${this.config.apiBaseUrl}/repositories/${project}/${repoName}/commits/${branch}`
|
||||
: `${this.config.apiBaseUrl}/projects/${project}/repos/${repoName}/commits`;
|
||||
? `${this.integration.config.apiBaseUrl}/repositories/${project}/${repoName}/commits/${branch}`
|
||||
: `${this.integration.config.apiBaseUrl}/projects/${project}/repos/${repoName}/commits`;
|
||||
|
||||
const commitsResponse = await fetch(
|
||||
commitsApiUrl,
|
||||
getBitbucketRequestOptions(this.config),
|
||||
getBitbucketRequestOptions(this.integration.config),
|
||||
);
|
||||
if (!commitsResponse.ok) {
|
||||
const message = `Failed to retrieve commits from ${commitsApiUrl}, ${commitsResponse.status} ${commitsResponse.statusText}`;
|
||||
|
||||
@@ -16,7 +16,12 @@
|
||||
|
||||
import fetch from 'cross-fetch';
|
||||
import { NotFoundError } from '../errors';
|
||||
import { ReaderFactory, ReadTreeResponse, UrlReader } from './types';
|
||||
import {
|
||||
ReaderFactory,
|
||||
ReadTreeResponse,
|
||||
SearchResponse,
|
||||
UrlReader,
|
||||
} from './types';
|
||||
|
||||
/**
|
||||
* A UrlReader that does a plain fetch of the URL.
|
||||
@@ -68,10 +73,14 @@ export class FetchUrlReader implements UrlReader {
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
readTree(): Promise<ReadTreeResponse> {
|
||||
async readTree(): Promise<ReadTreeResponse> {
|
||||
throw new Error('FetchUrlReader does not implement readTree');
|
||||
}
|
||||
|
||||
async search(): Promise<SearchResponse> {
|
||||
throw new Error('FetchUrlReader does not implement search');
|
||||
}
|
||||
|
||||
toString() {
|
||||
return 'fetch{}';
|
||||
}
|
||||
|
||||
@@ -15,15 +15,26 @@
|
||||
*/
|
||||
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { GithubCredentialsProvider } from '@backstage/integration';
|
||||
import {
|
||||
GithubCredentialsProvider,
|
||||
GitHubIntegration,
|
||||
readGitHubIntegrationConfig,
|
||||
} from '@backstage/integration';
|
||||
import { msw } from '@backstage/test-utils';
|
||||
import fs from 'fs-extra';
|
||||
import mockFs from 'mock-fs';
|
||||
import { rest } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import { NotFoundError, NotModifiedError } from '../errors';
|
||||
import { GithubUrlReader } from './GithubUrlReader';
|
||||
import {
|
||||
GhBlobResponse,
|
||||
GhBranchResponse,
|
||||
GhRepoResponse,
|
||||
GhTreeResponse,
|
||||
GithubUrlReader,
|
||||
} from './GithubUrlReader';
|
||||
import { ReadTreeResponseFactory } from './tree';
|
||||
|
||||
const treeResponseFactory = ReadTreeResponseFactory.create({
|
||||
@@ -35,26 +46,45 @@ const mockCredentialsProvider = ({
|
||||
} as unknown) as GithubCredentialsProvider;
|
||||
|
||||
const githubProcessor = new GithubUrlReader(
|
||||
{
|
||||
host: 'github.com',
|
||||
apiBaseUrl: 'https://api.github.com',
|
||||
},
|
||||
new GitHubIntegration(
|
||||
readGitHubIntegrationConfig(
|
||||
new ConfigReader({
|
||||
host: 'github.com',
|
||||
apiBaseUrl: 'https://api.github.com',
|
||||
}),
|
||||
),
|
||||
),
|
||||
{ treeResponseFactory, credentialsProvider: mockCredentialsProvider },
|
||||
);
|
||||
|
||||
const gheProcessor = new GithubUrlReader(
|
||||
{
|
||||
host: 'ghe.github.com',
|
||||
apiBaseUrl: 'https://ghe.github.com/api/v3',
|
||||
},
|
||||
new GitHubIntegration(
|
||||
readGitHubIntegrationConfig(
|
||||
new ConfigReader({
|
||||
host: 'ghe.github.com',
|
||||
apiBaseUrl: 'https://ghe.github.com/api/v3',
|
||||
}),
|
||||
),
|
||||
),
|
||||
{ treeResponseFactory, credentialsProvider: mockCredentialsProvider },
|
||||
);
|
||||
|
||||
const tmpDir = os.platform() === 'win32' ? 'C:\\tmp' : '/tmp';
|
||||
|
||||
describe('GithubUrlReader', () => {
|
||||
const worker = setupServer();
|
||||
|
||||
msw.setupDefaultHandlers(worker);
|
||||
|
||||
beforeEach(() => {
|
||||
mockFs({
|
||||
[tmpDir]: mockFs.directory(),
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
@@ -69,6 +99,10 @@ describe('GithubUrlReader', () => {
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
* read
|
||||
*/
|
||||
|
||||
describe('read', () => {
|
||||
it('should use the headers from the credentials provider to the fetch request when doing read', async () => {
|
||||
expect.assertions(2);
|
||||
@@ -84,7 +118,7 @@ describe('GithubUrlReader', () => {
|
||||
|
||||
worker.use(
|
||||
rest.get(
|
||||
'https://api.github.com/repos/backstage/mock/tree/contents/?ref=main',
|
||||
'https://ghe.github.com/api/v3/repos/backstage/mock/tree/contents/?ref=main',
|
||||
(req, res, ctx) => {
|
||||
expect(req.headers.get('authorization')).toBe(
|
||||
mockHeaders.Authorization,
|
||||
@@ -101,23 +135,17 @@ describe('GithubUrlReader', () => {
|
||||
),
|
||||
);
|
||||
|
||||
await githubProcessor.read(
|
||||
await gheProcessor.read(
|
||||
'https://github.com/backstage/mock/tree/blob/main',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
* readTree
|
||||
*/
|
||||
|
||||
describe('readTree', () => {
|
||||
beforeEach(() => {
|
||||
mockFs({
|
||||
'/tmp': mockFs.directory(),
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
const repoBuffer = fs.readFileSync(
|
||||
path.resolve(
|
||||
'src',
|
||||
@@ -128,29 +156,31 @@ describe('GithubUrlReader', () => {
|
||||
);
|
||||
|
||||
const reposGithubApiResponse = {
|
||||
id: '123',
|
||||
id: 123,
|
||||
full_name: 'backstage/mock',
|
||||
default_branch: 'main',
|
||||
branches_url:
|
||||
'https://api.github.com/repos/backstage/mock/branches{/branch}',
|
||||
archive_url:
|
||||
'https://api.github.com/repos/backstage/mock/{archive_format}{/ref}',
|
||||
};
|
||||
} as Partial<GhRepoResponse>;
|
||||
|
||||
const reposGheApiResponse = {
|
||||
...reposGithubApiResponse,
|
||||
id: 123,
|
||||
full_name: 'backstage/mock',
|
||||
default_branch: 'main',
|
||||
branches_url:
|
||||
'https://ghe.github.com/api/v3/repos/backstage/mock/branches{/branch}',
|
||||
archive_url:
|
||||
'https://ghe.github.com/api/v3/repos/backstage/mock/{archive_format}{/ref}',
|
||||
};
|
||||
} as Partial<GhRepoResponse>;
|
||||
|
||||
const branchesApiResponse = {
|
||||
name: 'main',
|
||||
commit: {
|
||||
sha: 'etag123abc',
|
||||
},
|
||||
};
|
||||
} as Partial<GhBranchResponse>;
|
||||
|
||||
beforeEach(() => {
|
||||
worker.use(
|
||||
@@ -381,9 +411,13 @@ describe('GithubUrlReader', () => {
|
||||
expect(() => {
|
||||
/* eslint-disable no-new */
|
||||
new GithubUrlReader(
|
||||
{
|
||||
host: 'ghe.mycompany.net',
|
||||
},
|
||||
new GitHubIntegration(
|
||||
readGitHubIntegrationConfig(
|
||||
new ConfigReader({
|
||||
host: 'ghe.mycompany.net',
|
||||
}),
|
||||
),
|
||||
),
|
||||
{
|
||||
treeResponseFactory,
|
||||
credentialsProvider: mockCredentialsProvider,
|
||||
@@ -392,4 +426,351 @@ describe('GithubUrlReader', () => {
|
||||
}).toThrowError('must configure an explicit apiBaseUrl');
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
* search
|
||||
*/
|
||||
|
||||
describe('search', () => {
|
||||
const repoBuffer = fs.readFileSync(
|
||||
path.resolve(
|
||||
'src',
|
||||
'reading',
|
||||
'__fixtures__',
|
||||
'backstage-mock-etag123.tar.gz',
|
||||
),
|
||||
);
|
||||
|
||||
const githubTreeContents: GhTreeResponse['tree'] = [
|
||||
{
|
||||
path: 'mkdocs.yml',
|
||||
type: 'blob',
|
||||
url: 'https://api.github.com/repos/backstage/mock/git/blobs/1',
|
||||
},
|
||||
{
|
||||
path: 'docs',
|
||||
type: 'tree',
|
||||
url: 'https://api.github.com/repos/backstage/mock/git/trees/2',
|
||||
},
|
||||
{
|
||||
path: 'docs/index.md',
|
||||
type: 'blob',
|
||||
url: 'https://api.github.com/repos/backstage/mock/git/blobs/3',
|
||||
},
|
||||
];
|
||||
|
||||
const gheTreeContents: GhTreeResponse['tree'] = [
|
||||
{
|
||||
path: 'mkdocs.yml',
|
||||
type: 'blob',
|
||||
url: 'https://ghe.github.com/api/v3/repos/backstage/mock/git/blobs/1',
|
||||
},
|
||||
{
|
||||
path: 'docs',
|
||||
type: 'tree',
|
||||
url: 'https://ghe.github.com/api/v3/repos/backstage/mock/git/trees/2',
|
||||
},
|
||||
{
|
||||
path: 'docs/index.md',
|
||||
type: 'blob',
|
||||
url: 'https://ghe.github.com/api/v3/repos/backstage/mock/git/blobs/3',
|
||||
},
|
||||
];
|
||||
|
||||
// Tarballs
|
||||
beforeEach(() => {
|
||||
worker.use(
|
||||
rest.get(
|
||||
'https://api.github.com/repos/backstage/mock/tarball/etag123abc',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/x-gzip'),
|
||||
ctx.set(
|
||||
'content-disposition',
|
||||
'attachment; filename=backstage-mock-etag123.tar.gz',
|
||||
),
|
||||
ctx.body(repoBuffer),
|
||||
),
|
||||
),
|
||||
rest.get(
|
||||
'https://ghe.github.com/api/v3/repos/backstage/mock/tarball/etag123abc',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/x-gzip'),
|
||||
ctx.set(
|
||||
'content-disposition',
|
||||
'attachment; filename=backstage-mock-etag123.tar.gz',
|
||||
),
|
||||
ctx.body(repoBuffer),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
// Repo details
|
||||
beforeEach(() => {
|
||||
const githubResponse = {
|
||||
id: 123,
|
||||
full_name: 'backstage/mock',
|
||||
default_branch: 'main',
|
||||
branches_url:
|
||||
'https://api.github.com/repos/backstage/mock/branches{/branch}',
|
||||
archive_url:
|
||||
'https://api.github.com/repos/backstage/mock/{archive_format}{/ref}',
|
||||
trees_url:
|
||||
'https://api.github.com/repos/backstage/mock/git/trees{/sha}',
|
||||
} as Partial<GhRepoResponse>;
|
||||
|
||||
const gheResponse = {
|
||||
id: 123,
|
||||
full_name: 'backstage/mock',
|
||||
default_branch: 'main',
|
||||
branches_url:
|
||||
'https://ghe.github.com/api/v3/repos/backstage/mock/branches{/branch}',
|
||||
archive_url:
|
||||
'https://ghe.github.com/api/v3/repos/backstage/mock/{archive_format}{/ref}',
|
||||
trees_url:
|
||||
'https://ghe.github.com/api/v3/repos/backstage/mock/git/trees{/sha}',
|
||||
} as Partial<GhRepoResponse>;
|
||||
|
||||
worker.use(
|
||||
rest.get('https://api.github.com/repos/backstage/mock', (_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/json'),
|
||||
ctx.json(githubResponse),
|
||||
),
|
||||
),
|
||||
rest.get(
|
||||
'https://ghe.github.com/api/v3/repos/backstage/mock',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/json'),
|
||||
ctx.json(gheResponse),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
// Branch details
|
||||
beforeEach(() => {
|
||||
const response = {
|
||||
name: 'main',
|
||||
commit: {
|
||||
sha: 'etag123abc',
|
||||
},
|
||||
} as Partial<GhBranchResponse>;
|
||||
|
||||
worker.use(
|
||||
rest.get(
|
||||
'https://api.github.com/repos/backstage/mock/branches/main',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/json'),
|
||||
ctx.json(response),
|
||||
),
|
||||
),
|
||||
rest.get(
|
||||
'https://ghe.github.com/api/v3/repos/backstage/mock/branches/main',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/json'),
|
||||
ctx.json(response),
|
||||
),
|
||||
),
|
||||
rest.get(
|
||||
'https://api.github.com/repos/backstage/mock/branches/branchDoesNotExist',
|
||||
(_, res, ctx) => res(ctx.status(404)),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
// Blobs
|
||||
beforeEach(() => {
|
||||
const blob1Response = {
|
||||
content: Buffer.from('site_name: Test\n').toString('base64'),
|
||||
} as Partial<GhBlobResponse>;
|
||||
|
||||
const blob3Response = {
|
||||
content: Buffer.from('# Test\n').toString('base64'),
|
||||
} as Partial<GhBlobResponse>;
|
||||
|
||||
worker.use(
|
||||
rest.get(
|
||||
'https://api.github.com/repos/backstage/mock/git/blobs/1',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/json'),
|
||||
ctx.json(blob1Response),
|
||||
),
|
||||
),
|
||||
rest.get(
|
||||
'https://api.github.com/repos/backstage/mock/git/blobs/3',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/json'),
|
||||
ctx.json(blob3Response),
|
||||
),
|
||||
),
|
||||
rest.get(
|
||||
'https://ghe.github.com/api/v3/repos/backstage/mock/git/blobs/1',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/json'),
|
||||
ctx.json(blob1Response),
|
||||
),
|
||||
),
|
||||
rest.get(
|
||||
'https://ghe.github.com/api/v3/repos/backstage/mock/git/blobs/3',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/json'),
|
||||
ctx.json(blob3Response),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
async function runTests(reader: GithubUrlReader, baseUrl: string) {
|
||||
const r1 = await reader.search(
|
||||
`${baseUrl}/backstage/mock/tree/main/**/*`,
|
||||
);
|
||||
expect(r1.etag).toBe('etag123abc');
|
||||
expect(r1.files.length).toBe(2);
|
||||
|
||||
const r2 = await reader.search(
|
||||
`${baseUrl}/backstage/mock/tree/main/**/*`,
|
||||
{ etag: 'somethingElse' },
|
||||
);
|
||||
expect(r2.etag).toBe('etag123abc');
|
||||
expect(r2.files.length).toBe(2);
|
||||
|
||||
const r3 = await reader.search(`${baseUrl}/backstage/mock/tree/main/o`);
|
||||
expect(r3.files.length).toBe(0);
|
||||
|
||||
const r4 = await reader.search(
|
||||
`${baseUrl}/backstage/mock/tree/main/*docs*`,
|
||||
);
|
||||
expect(r4.files.length).toBe(1);
|
||||
expect(r4.files[0].url).toBe(
|
||||
`${baseUrl}/backstage/mock/tree/main/mkdocs.yml`,
|
||||
);
|
||||
await expect(r4.files[0].content()).resolves.toEqual(
|
||||
Buffer.from('site_name: Test\n'),
|
||||
);
|
||||
|
||||
const r5 = await reader.search(
|
||||
`${baseUrl}/backstage/mock/tree/main/*/index.*`,
|
||||
);
|
||||
expect(r5.files.length).toBe(1);
|
||||
expect(r5.files[0].url).toBe(
|
||||
`${baseUrl}/backstage/mock/tree/main/docs/index.md`,
|
||||
);
|
||||
await expect(r5.files[0].content()).resolves.toEqual(
|
||||
Buffer.from('# Test\n'),
|
||||
);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
it('succeeds on github when going via repo listing', async () => {
|
||||
worker.use(
|
||||
rest.get(
|
||||
'https://api.github.com/repos/backstage/mock/git/trees/etag123abc',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/json'),
|
||||
ctx.json({
|
||||
truncated: false,
|
||||
tree: githubTreeContents,
|
||||
} as Partial<GhTreeResponse>),
|
||||
),
|
||||
),
|
||||
);
|
||||
await runTests(githubProcessor, 'https://github.com');
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
it('succeeds on github when going via readTree', async () => {
|
||||
worker.use(
|
||||
rest.get(
|
||||
'https://api.github.com/repos/backstage/mock/git/trees/etag123abc',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/json'),
|
||||
ctx.json({
|
||||
truncated: true,
|
||||
tree: [],
|
||||
} as Partial<GhTreeResponse>),
|
||||
),
|
||||
),
|
||||
);
|
||||
await runTests(githubProcessor, 'https://github.com');
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
it('succeeds on ghe when going via repo listing', async () => {
|
||||
worker.use(
|
||||
rest.get(
|
||||
'https://ghe.github.com/api/v3/repos/backstage/mock/git/trees/etag123abc',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/json'),
|
||||
ctx.json({
|
||||
truncated: false,
|
||||
tree: gheTreeContents,
|
||||
} as Partial<GhTreeResponse>),
|
||||
),
|
||||
),
|
||||
);
|
||||
await runTests(gheProcessor, 'https://ghe.github.com');
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
it('succeeds on ghe when going via readTree', async () => {
|
||||
worker.use(
|
||||
rest.get(
|
||||
'https://ghe.github.com/api/v3/repos/backstage/mock/git/trees/etag123abc',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/json'),
|
||||
ctx.json({
|
||||
truncated: true,
|
||||
tree: [],
|
||||
} as Partial<GhTreeResponse>),
|
||||
),
|
||||
),
|
||||
);
|
||||
await runTests(gheProcessor, 'https://ghe.github.com');
|
||||
});
|
||||
|
||||
it('throws NotModifiedError when same etag', async () => {
|
||||
await expect(
|
||||
githubProcessor.search(
|
||||
'https://githib.com/backstage/mock/tree/main/**/*',
|
||||
{ etag: 'etag123abc' },
|
||||
),
|
||||
).rejects.toThrow(NotModifiedError);
|
||||
});
|
||||
|
||||
it('throws NotFoundError when missing branch', async () => {
|
||||
await expect(
|
||||
githubProcessor.search(
|
||||
'https://githib.com/backstage/mock/tree/branchDoesNotExist/**/*',
|
||||
),
|
||||
).rejects.toThrow(NotFoundError);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,12 +17,13 @@
|
||||
import {
|
||||
getGitHubFileFetchUrl,
|
||||
GithubCredentialsProvider,
|
||||
GitHubIntegrationConfig,
|
||||
readGitHubIntegrationConfigs,
|
||||
GitHubIntegration,
|
||||
ScmIntegrations,
|
||||
} from '@backstage/integration';
|
||||
import { RestEndpointMethodTypes } from '@octokit/rest';
|
||||
import fetch from 'cross-fetch';
|
||||
import parseGitUrl from 'git-url-parse';
|
||||
import { Minimatch } from 'minimatch';
|
||||
import { Readable } from 'stream';
|
||||
import { NotFoundError, NotModifiedError } from '../errors';
|
||||
import { ReadTreeResponseFactory } from './tree';
|
||||
@@ -30,45 +31,53 @@ import {
|
||||
ReaderFactory,
|
||||
ReadTreeOptions,
|
||||
ReadTreeResponse,
|
||||
SearchOptions,
|
||||
SearchResponse,
|
||||
SearchResponseFile,
|
||||
UrlReader,
|
||||
} from './types';
|
||||
|
||||
export type GhRepoResponse = RestEndpointMethodTypes['repos']['get']['response']['data'];
|
||||
export type GhBranchResponse = RestEndpointMethodTypes['repos']['getBranch']['response']['data'];
|
||||
export type GhTreeResponse = RestEndpointMethodTypes['git']['getTree']['response']['data'];
|
||||
export type GhBlobResponse = RestEndpointMethodTypes['git']['getBlob']['response']['data'];
|
||||
|
||||
/**
|
||||
* A processor that adds the ability to read files from GitHub v3 APIs, such as
|
||||
* the one exposed by GitHub itself.
|
||||
*/
|
||||
export class GithubUrlReader implements UrlReader {
|
||||
static factory: ReaderFactory = ({ config, treeResponseFactory }) => {
|
||||
const configs = readGitHubIntegrationConfigs(
|
||||
config.getOptionalConfigArray('integrations.github') ?? [],
|
||||
);
|
||||
return configs.map(provider => {
|
||||
const credentialsProvider = GithubCredentialsProvider.create(provider);
|
||||
const reader = new GithubUrlReader(provider, {
|
||||
const integrations = ScmIntegrations.fromConfig(config);
|
||||
return integrations.github.list().map(integration => {
|
||||
const credentialsProvider = GithubCredentialsProvider.create(
|
||||
integration.config,
|
||||
);
|
||||
const reader = new GithubUrlReader(integration, {
|
||||
treeResponseFactory,
|
||||
credentialsProvider,
|
||||
});
|
||||
const predicate = (url: URL) => url.host === provider.host;
|
||||
const predicate = (url: URL) => url.host === integration.config.host;
|
||||
return { reader, predicate };
|
||||
});
|
||||
};
|
||||
|
||||
constructor(
|
||||
private readonly config: GitHubIntegrationConfig,
|
||||
private readonly integration: GitHubIntegration,
|
||||
private readonly deps: {
|
||||
treeResponseFactory: ReadTreeResponseFactory;
|
||||
credentialsProvider: GithubCredentialsProvider;
|
||||
},
|
||||
) {
|
||||
if (!config.apiBaseUrl && !config.rawBaseUrl) {
|
||||
if (!integration.config.apiBaseUrl && !integration.config.rawBaseUrl) {
|
||||
throw new Error(
|
||||
`GitHub integration for '${config.host}' must configure an explicit apiBaseUrl and rawBaseUrl`,
|
||||
`GitHub integration '${integration.title}' must configure an explicit apiBaseUrl or rawBaseUrl`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async read(url: string): Promise<Buffer> {
|
||||
const ghUrl = getGitHubFileFetchUrl(url, this.config);
|
||||
const ghUrl = getGitHubFileFetchUrl(url, this.integration.config);
|
||||
const { headers } = await this.deps.credentialsProvider.getCredentials({
|
||||
url,
|
||||
});
|
||||
@@ -106,39 +115,136 @@ export class GithubUrlReader implements UrlReader {
|
||||
throw new NotModifiedError();
|
||||
}
|
||||
|
||||
const { filepath } = parseGitUrl(url);
|
||||
const { headers } = await this.deps.credentialsProvider.getCredentials({
|
||||
url,
|
||||
});
|
||||
|
||||
// archive_url looks like "https://api.github.com/repos/owner/repo/{archive_format}{/ref}"
|
||||
const archive = await this.fetchResponse(
|
||||
repoDetails.repo.archive_url
|
||||
.replace('{archive_format}', 'tarball')
|
||||
.replace('{/ref}', `/${commitSha}`),
|
||||
return this.doReadTree(
|
||||
repoDetails.repo.archive_url,
|
||||
commitSha,
|
||||
filepath,
|
||||
{ headers },
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
async search(url: string, options?: SearchOptions): Promise<SearchResponse> {
|
||||
const repoDetails = await this.getRepoDetails(url);
|
||||
const commitSha = repoDetails.branch.commit.sha!;
|
||||
|
||||
if (options?.etag && options.etag === commitSha) {
|
||||
throw new NotModifiedError();
|
||||
}
|
||||
|
||||
const { filepath } = parseGitUrl(url);
|
||||
const { headers } = await this.deps.credentialsProvider.getCredentials({
|
||||
url,
|
||||
});
|
||||
|
||||
const files = await this.doSearch(
|
||||
url,
|
||||
repoDetails.repo.trees_url,
|
||||
repoDetails.repo.archive_url,
|
||||
commitSha,
|
||||
filepath,
|
||||
{ headers },
|
||||
);
|
||||
|
||||
const { filepath } = parseGitUrl(url);
|
||||
return { files, etag: commitSha };
|
||||
}
|
||||
|
||||
toString() {
|
||||
const { host, token } = this.integration.config;
|
||||
return `github{host=${host},authed=${Boolean(token)}}`;
|
||||
}
|
||||
|
||||
private async doReadTree(
|
||||
archiveUrl: string,
|
||||
sha: string,
|
||||
subpath: string,
|
||||
init: RequestInit,
|
||||
options?: ReadTreeOptions,
|
||||
): Promise<ReadTreeResponse> {
|
||||
// archive_url looks like "https://api.github.com/repos/owner/repo/{archive_format}{/ref}"
|
||||
const archive = await this.fetchResponse(
|
||||
archiveUrl
|
||||
.replace('{archive_format}', 'tarball')
|
||||
.replace('{/ref}', `/${sha}`),
|
||||
init,
|
||||
);
|
||||
|
||||
return await this.deps.treeResponseFactory.fromTarArchive({
|
||||
// TODO(Rugvip): Underlying implementation of fetch will be node-fetch, we probably want
|
||||
// to stick to using that in exclusively backend code.
|
||||
stream: (archive.body as unknown) as Readable,
|
||||
subpath: filepath,
|
||||
etag: commitSha,
|
||||
subpath,
|
||||
etag: sha,
|
||||
filter: options?.filter,
|
||||
});
|
||||
}
|
||||
|
||||
toString() {
|
||||
const { host, token } = this.config;
|
||||
return `github{host=${host},authed=${Boolean(token)}}`;
|
||||
private async doSearch(
|
||||
url: string,
|
||||
treesUrl: string,
|
||||
archiveUrl: string,
|
||||
sha: string,
|
||||
query: string,
|
||||
init: RequestInit,
|
||||
): Promise<SearchResponseFile[]> {
|
||||
function pathToUrl(path: string): string {
|
||||
// TODO(freben): Use the integration package facility for this instead
|
||||
// pathname starts as /backstage/backstage/blob/master/<path>
|
||||
const updated = new URL(url);
|
||||
const base = updated.pathname.split('/').slice(1, 5).join('/');
|
||||
updated.pathname = `${base}/${path}`;
|
||||
return updated.toString();
|
||||
}
|
||||
|
||||
const matcher = new Minimatch(query.replace(/^\/+/, ''));
|
||||
|
||||
// trees_url looks like "https://api.github.com/repos/octocat/Hello-World/git/trees{/sha}"
|
||||
const recursiveTree: GhTreeResponse = await this.fetchJson(
|
||||
treesUrl.replace('{/sha}', `/${sha}?recursive=true`),
|
||||
init,
|
||||
);
|
||||
|
||||
// The simple case is that we got the entire tree in a single operation.
|
||||
if (!recursiveTree.truncated) {
|
||||
const matching = recursiveTree.tree.filter(
|
||||
item =>
|
||||
item.type === 'blob' &&
|
||||
item.path &&
|
||||
item.url &&
|
||||
matcher.match(item.path),
|
||||
);
|
||||
|
||||
return matching.map(item => ({
|
||||
url: pathToUrl(item.path!),
|
||||
content: async () => {
|
||||
const blob: GhBlobResponse = await this.fetchJson(item.url!, init);
|
||||
return Buffer.from(blob.content, 'base64');
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
// For larger repos, we leverage readTree and filter through that instead
|
||||
const tree = await this.doReadTree(archiveUrl, sha, '', init, {
|
||||
filter: path => matcher.match(path),
|
||||
});
|
||||
const files = await tree.files();
|
||||
|
||||
return files.map(file => ({
|
||||
url: pathToUrl(file.path),
|
||||
content: file.content,
|
||||
}));
|
||||
}
|
||||
|
||||
private async getRepoDetails(
|
||||
url: string,
|
||||
): Promise<{
|
||||
repo: RestEndpointMethodTypes['repos']['get']['response']['data'];
|
||||
branch: RestEndpointMethodTypes['repos']['getBranch']['response']['data'];
|
||||
repo: GhRepoResponse;
|
||||
branch: GhBranchResponse;
|
||||
}> {
|
||||
const parsed = parseGitUrl(url);
|
||||
const { ref, full_name } = parsed;
|
||||
@@ -151,13 +257,13 @@ export class GithubUrlReader implements UrlReader {
|
||||
url,
|
||||
});
|
||||
|
||||
const repo: RestEndpointMethodTypes['repos']['get']['response']['data'] = await this.fetchJson(
|
||||
`${this.config.apiBaseUrl}/repos/${full_name}`,
|
||||
const repo: GhRepoResponse = await this.fetchJson(
|
||||
`${this.integration.config.apiBaseUrl}/repos/${full_name}`,
|
||||
{ headers },
|
||||
);
|
||||
|
||||
// branches_url looks like "https://api.github.com/repos/owner/repo/branches{/branch}"
|
||||
const branch: RestEndpointMethodTypes['repos']['getBranch']['response']['data'] = await this.fetchJson(
|
||||
const branch: GhBranchResponse = await this.fetchJson(
|
||||
repo.branches_url.replace('{/branch}', `/${ref || repo.default_branch}`),
|
||||
{ headers },
|
||||
);
|
||||
|
||||
@@ -20,11 +20,16 @@ import fs from 'fs-extra';
|
||||
import mockFs from 'mock-fs';
|
||||
import { rest } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import { getVoidLogger } from '../logging';
|
||||
import { GitlabUrlReader } from './GitlabUrlReader';
|
||||
import { ReadTreeResponseFactory } from './tree';
|
||||
import { NotModifiedError, NotFoundError } from '../errors';
|
||||
import {
|
||||
GitLabIntegration,
|
||||
readGitLabIntegrationConfig,
|
||||
} from '@backstage/integration';
|
||||
|
||||
const logger = getVoidLogger();
|
||||
|
||||
@@ -33,22 +38,44 @@ const treeResponseFactory = ReadTreeResponseFactory.create({
|
||||
});
|
||||
|
||||
const gitlabProcessor = new GitlabUrlReader(
|
||||
{
|
||||
host: 'gitlab.com',
|
||||
apiBaseUrl: 'https://gitlab.com/api/v4',
|
||||
},
|
||||
new GitLabIntegration(
|
||||
readGitLabIntegrationConfig(
|
||||
new ConfigReader({
|
||||
host: 'gitlab.com',
|
||||
apiBaseUrl: 'https://gitlab.com/api/v4',
|
||||
baseUrl: 'https://gitlab.com',
|
||||
}),
|
||||
),
|
||||
),
|
||||
{ treeResponseFactory },
|
||||
);
|
||||
|
||||
const hostedGitlabProcessor = new GitlabUrlReader(
|
||||
{
|
||||
host: 'gitlab.mycompany.com',
|
||||
apiBaseUrl: 'https://gitlab.mycompany.com/api/v4',
|
||||
},
|
||||
new GitLabIntegration(
|
||||
readGitLabIntegrationConfig(
|
||||
new ConfigReader({
|
||||
host: 'gitlab.mycompany.com',
|
||||
apiBaseUrl: 'https://gitlab.mycompany.com/api/v4',
|
||||
baseUrl: 'https://gitlab.mycompany.com',
|
||||
}),
|
||||
),
|
||||
),
|
||||
{ treeResponseFactory },
|
||||
);
|
||||
|
||||
const tmpDir = os.platform() === 'win32' ? 'C:\\tmp' : '/tmp';
|
||||
|
||||
describe('GitlabUrlReader', () => {
|
||||
beforeEach(() => {
|
||||
mockFs({
|
||||
[tmpDir]: mockFs.directory(),
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
const worker = setupServer();
|
||||
msw.setupDefaultHandlers(worker);
|
||||
|
||||
@@ -154,16 +181,6 @@ describe('GitlabUrlReader', () => {
|
||||
});
|
||||
|
||||
describe('readTree', () => {
|
||||
beforeEach(() => {
|
||||
mockFs({
|
||||
'/tmp': mockFs.directory(),
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
const archiveBuffer = fs.readFileSync(
|
||||
path.resolve('src', 'reading', '__fixtures__', 'gitlab-archive.zip'),
|
||||
);
|
||||
@@ -379,17 +396,81 @@ describe('GitlabUrlReader', () => {
|
||||
};
|
||||
await expect(fnGithub).rejects.toThrow(NotFoundError);
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw error when apiBaseUrl is missing', () => {
|
||||
expect(() => {
|
||||
/* eslint-disable no-new */
|
||||
new GitlabUrlReader(
|
||||
{
|
||||
host: 'gitlab.mycompany.com',
|
||||
},
|
||||
{ treeResponseFactory },
|
||||
);
|
||||
}).toThrowError('must configure an explicit apiBaseUrl');
|
||||
describe('search', () => {
|
||||
const archiveBuffer = fs.readFileSync(
|
||||
path.resolve('src', 'reading', '__fixtures__', 'gitlab-archive.zip'),
|
||||
);
|
||||
|
||||
const projectGitlabApiResponse = {
|
||||
id: 11111111,
|
||||
default_branch: 'main',
|
||||
};
|
||||
|
||||
const branchGitlabApiResponse = {
|
||||
commit: {
|
||||
id: 'sha123abc',
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
worker.use(
|
||||
rest.get(
|
||||
'https://gitlab.com/api/v4/projects/backstage%2Fmock/repository/archive.zip?sha=main',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/zip'),
|
||||
ctx.set(
|
||||
'content-disposition',
|
||||
'attachment; filename="mock-main-sha123abc.zip"',
|
||||
),
|
||||
ctx.body(archiveBuffer),
|
||||
),
|
||||
),
|
||||
rest.get(
|
||||
'https://gitlab.com/api/v4/projects/backstage%2Fmock',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/json'),
|
||||
ctx.json(projectGitlabApiResponse),
|
||||
),
|
||||
),
|
||||
rest.get(
|
||||
'https://gitlab.com/api/v4/projects/backstage%2Fmock/repository/branches/main',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/json'),
|
||||
ctx.json(branchGitlabApiResponse),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('works for the naive case', async () => {
|
||||
const result = await gitlabProcessor.search(
|
||||
'https://gitlab.com/backstage/mock/tree/main/**/index.*',
|
||||
);
|
||||
expect(result.etag).toBe('sha123abc');
|
||||
expect(result.files.length).toBe(1);
|
||||
expect(result.files[0].url).toBe(
|
||||
'https://gitlab.com/backstage/mock/tree/main/docs/index.md',
|
||||
);
|
||||
await expect(result.files[0].content()).resolves.toEqual(
|
||||
Buffer.from('# Test\n'),
|
||||
);
|
||||
});
|
||||
|
||||
it('throws NotModifiedError when same etag', async () => {
|
||||
await expect(
|
||||
gitlabProcessor.search(
|
||||
'https://gitlab.com/backstage/mock/tree/main/**/index.*',
|
||||
{ etag: 'sha123abc' },
|
||||
),
|
||||
).rejects.toThrow(NotModifiedError);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,54 +17,51 @@
|
||||
import {
|
||||
getGitLabFileFetchUrl,
|
||||
getGitLabRequestOptions,
|
||||
GitLabIntegrationConfig,
|
||||
readGitLabIntegrationConfigs,
|
||||
GitLabIntegration,
|
||||
ScmIntegrations,
|
||||
} from '@backstage/integration';
|
||||
import fetch from 'cross-fetch';
|
||||
import parseGitUrl from 'git-url-parse';
|
||||
import { Minimatch } from 'minimatch';
|
||||
import { Readable } from 'stream';
|
||||
import { NotFoundError, NotModifiedError } from '../errors';
|
||||
import { ReadTreeResponseFactory } from './tree';
|
||||
import { stripFirstDirectoryFromPath } from './tree/util';
|
||||
import {
|
||||
ReaderFactory,
|
||||
ReadTreeOptions,
|
||||
ReadTreeResponse,
|
||||
SearchOptions,
|
||||
SearchResponse,
|
||||
UrlReader,
|
||||
} from './types';
|
||||
import parseGitUrl from 'git-url-parse';
|
||||
import { Readable } from 'stream';
|
||||
|
||||
export class GitlabUrlReader implements UrlReader {
|
||||
private readonly treeResponseFactory: ReadTreeResponseFactory;
|
||||
|
||||
static factory: ReaderFactory = ({ config, treeResponseFactory }) => {
|
||||
const configs = readGitLabIntegrationConfigs(
|
||||
config.getOptionalConfigArray('integrations.gitlab') ?? [],
|
||||
);
|
||||
return configs.map(provider => {
|
||||
const reader = new GitlabUrlReader(provider, { treeResponseFactory });
|
||||
const predicate = (url: URL) => url.host === provider.host;
|
||||
const integrations = ScmIntegrations.fromConfig(config);
|
||||
return integrations.gitlab.list().map(integration => {
|
||||
const reader = new GitlabUrlReader(integration, {
|
||||
treeResponseFactory,
|
||||
});
|
||||
const predicate = (url: URL) => url.host === integration.config.host;
|
||||
return { reader, predicate };
|
||||
});
|
||||
};
|
||||
|
||||
constructor(
|
||||
private readonly config: GitLabIntegrationConfig,
|
||||
deps: { treeResponseFactory: ReadTreeResponseFactory },
|
||||
) {
|
||||
this.treeResponseFactory = deps.treeResponseFactory;
|
||||
|
||||
if (!config.apiBaseUrl) {
|
||||
throw new Error(
|
||||
`GitLab integration for '${config.host}' must configure an explicit apiBaseUrl`,
|
||||
);
|
||||
}
|
||||
}
|
||||
private readonly integration: GitLabIntegration,
|
||||
private readonly deps: { treeResponseFactory: ReadTreeResponseFactory },
|
||||
) {}
|
||||
|
||||
async read(url: string): Promise<Buffer> {
|
||||
const builtUrl = await getGitLabFileFetchUrl(url, this.config);
|
||||
const builtUrl = await getGitLabFileFetchUrl(url, this.integration.config);
|
||||
|
||||
let response: Response;
|
||||
try {
|
||||
response = await fetch(builtUrl, getGitLabRequestOptions(this.config));
|
||||
response = await fetch(
|
||||
builtUrl,
|
||||
getGitLabRequestOptions(this.integration.config),
|
||||
);
|
||||
} catch (e) {
|
||||
throw new Error(`Unable to read ${url}, ${e}`);
|
||||
}
|
||||
@@ -91,9 +88,11 @@ export class GitlabUrlReader implements UrlReader {
|
||||
// https://docs.gitlab.com/ee/api/README.html#namespaced-path-encoding
|
||||
const projectGitlabResponse = await fetch(
|
||||
new URL(
|
||||
`${this.config.apiBaseUrl}/projects/${encodeURIComponent(full_name)}`,
|
||||
`${this.integration.config.apiBaseUrl}/projects/${encodeURIComponent(
|
||||
full_name,
|
||||
)}`,
|
||||
).toString(),
|
||||
getGitLabRequestOptions(this.config),
|
||||
getGitLabRequestOptions(this.integration.config),
|
||||
);
|
||||
if (!projectGitlabResponse.ok) {
|
||||
const msg = `Failed to read tree from ${url}, ${projectGitlabResponse.status} ${projectGitlabResponse.statusText}`;
|
||||
@@ -111,11 +110,11 @@ export class GitlabUrlReader implements UrlReader {
|
||||
// the provided sha.
|
||||
const branchGitlabResponse = await fetch(
|
||||
new URL(
|
||||
`${this.config.apiBaseUrl}/projects/${encodeURIComponent(
|
||||
`${this.integration.config.apiBaseUrl}/projects/${encodeURIComponent(
|
||||
full_name,
|
||||
)}/repository/branches/${branch}`,
|
||||
).toString(),
|
||||
getGitLabRequestOptions(this.config),
|
||||
getGitLabRequestOptions(this.integration.config),
|
||||
);
|
||||
if (!branchGitlabResponse.ok) {
|
||||
const message = `Failed to read tree (branch) from ${url}, ${branchGitlabResponse.status} ${branchGitlabResponse.statusText}`;
|
||||
@@ -133,10 +132,10 @@ export class GitlabUrlReader implements UrlReader {
|
||||
|
||||
// https://docs.gitlab.com/ee/api/repositories.html#get-file-archive
|
||||
const archiveGitLabResponse = await fetch(
|
||||
`${this.config.apiBaseUrl}/projects/${encodeURIComponent(
|
||||
`${this.integration.config.apiBaseUrl}/projects/${encodeURIComponent(
|
||||
full_name,
|
||||
)}/repository/archive.zip?sha=${branch}`,
|
||||
getGitLabRequestOptions(this.config),
|
||||
getGitLabRequestOptions(this.integration.config),
|
||||
);
|
||||
if (!archiveGitLabResponse.ok) {
|
||||
const message = `Failed to read tree (archive) from ${url}, ${archiveGitLabResponse.status} ${archiveGitLabResponse.statusText}`;
|
||||
@@ -146,7 +145,7 @@ export class GitlabUrlReader implements UrlReader {
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return await this.treeResponseFactory.fromZipArchive({
|
||||
return await this.deps.treeResponseFactory.fromZipArchive({
|
||||
stream: (archiveGitLabResponse.body as unknown) as Readable,
|
||||
subpath: filepath,
|
||||
etag: commitSha,
|
||||
@@ -154,8 +153,33 @@ export class GitlabUrlReader implements UrlReader {
|
||||
});
|
||||
}
|
||||
|
||||
async search(url: string, options?: SearchOptions): Promise<SearchResponse> {
|
||||
const { filepath } = parseGitUrl(url);
|
||||
const matcher = new Minimatch(filepath);
|
||||
|
||||
// TODO(freben): For now, read the entire repo and filter through that. In
|
||||
// a future improvement, we could be smart and try to deduce that non-glob
|
||||
// prefixes (like for filepaths such as some-prefix/**/a.yaml) can be used
|
||||
// to get just that part of the repo.
|
||||
const treeUrl = url.replace(filepath, '').replace(/\/+$/, '');
|
||||
|
||||
const tree = await this.readTree(treeUrl, {
|
||||
etag: options?.etag,
|
||||
filter: path => matcher.match(stripFirstDirectoryFromPath(path)),
|
||||
});
|
||||
const files = await tree.files();
|
||||
|
||||
return {
|
||||
etag: tree.etag,
|
||||
files: files.map(file => ({
|
||||
url: this.integration.resolveUrl({ url: `/${file.path}`, base: url }),
|
||||
content: file.content,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
toString() {
|
||||
const { host, token } = this.config;
|
||||
const { host, token } = this.integration.config;
|
||||
return `gitlab{host=${host},authed=${Boolean(token)}}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ import { NotAllowedError } from '../errors';
|
||||
import {
|
||||
ReadTreeOptions,
|
||||
ReadTreeResponse,
|
||||
SearchOptions,
|
||||
SearchResponse,
|
||||
UrlReader,
|
||||
UrlReaderPredicateTuple,
|
||||
} from './types';
|
||||
@@ -60,6 +62,18 @@ export class UrlReaderPredicateMux implements UrlReader {
|
||||
throw new NotAllowedError(`Reading from '${url}' is not allowed`);
|
||||
}
|
||||
|
||||
async search(url: string, options?: SearchOptions): Promise<SearchResponse> {
|
||||
const parsed = new URL(url);
|
||||
|
||||
for (const { predicate, reader } of this.readers) {
|
||||
if (predicate(parsed)) {
|
||||
return await reader.search(url, options);
|
||||
}
|
||||
}
|
||||
|
||||
throw new NotAllowedError(`Reading from '${url}' is not allowed`);
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `predicateMux{readers=${this.readers.map(t => t.reader).join(',')}`;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export type { UrlReader, ReadTreeResponse } from './types';
|
||||
export type { UrlReader, ReadTreeResponse, SearchResponse } from './types';
|
||||
export { UrlReaders } from './UrlReaders';
|
||||
export { AzureUrlReader } from './AzureUrlReader';
|
||||
export { BitbucketUrlReader } from './BitbucketUrlReader';
|
||||
|
||||
@@ -25,15 +25,12 @@ import {
|
||||
ReadTreeResponseDirOptions,
|
||||
ReadTreeResponseFile,
|
||||
} from '../types';
|
||||
import { stripFirstDirectoryFromPath } from './util';
|
||||
|
||||
// Tar types for `Parse` is not a proper constructor, but it should be
|
||||
const TarParseStream = (Parse as unknown) as { new (): ParseStream };
|
||||
|
||||
const pipeline = promisify(pipelineCb);
|
||||
// Matches a directory name + one `/` at the start of any string,
|
||||
// containing any character except `/` one or more times, and ending with a `/`
|
||||
// e.g. Will match `dirA/` in `dirA/dirB/file.ext`
|
||||
const directoryNameRegex = /^[^\/]+\//;
|
||||
|
||||
/**
|
||||
* Wraps a tar archive stream into a tree response reader.
|
||||
@@ -84,7 +81,7 @@ export class TarArchiveResponse implements ReadTreeResponse {
|
||||
|
||||
// File path relative to the root extracted directory. Will remove the
|
||||
// top level dir name from the path since its name is hard to predetermine.
|
||||
const relativePath = entry.path.replace(directoryNameRegex, '');
|
||||
const relativePath = stripFirstDirectoryFromPath(entry.path);
|
||||
|
||||
if (this.subPath) {
|
||||
if (!relativePath.startsWith(this.subPath)) {
|
||||
@@ -161,7 +158,7 @@ export class TarArchiveResponse implements ReadTreeResponse {
|
||||
filter: path => {
|
||||
// File path relative to the root extracted directory. Will remove the
|
||||
// top level dir name from the path since its name is hard to predetermine.
|
||||
const relativePath = path.replace(directoryNameRegex, '');
|
||||
const relativePath = stripFirstDirectoryFromPath(path);
|
||||
if (this.subPath && !relativePath.startsWith(this.subPath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -24,11 +24,7 @@ import {
|
||||
ReadTreeResponseDirOptions,
|
||||
ReadTreeResponseFile,
|
||||
} from '../types';
|
||||
|
||||
// Matches a directory name + one `/` at the start of any string,
|
||||
// containing any character except / one or more times, and ending with a `/`
|
||||
// e.g. Will match `dirA/` in `dirA/dirB/file.ext`
|
||||
const directoryNameRegex = /^[^\/]+\//;
|
||||
import { stripFirstDirectoryFromPath } from './util';
|
||||
|
||||
/**
|
||||
* Wraps a zip archive stream into a tree response reader.
|
||||
@@ -65,18 +61,13 @@ export class ZipArchiveResponse implements ReadTreeResponse {
|
||||
this.read = true;
|
||||
}
|
||||
|
||||
// Will remove the top level dir name from the path since its name is hard to predetermine.
|
||||
private stripTopDirectory(path: string): string {
|
||||
return path.replace(directoryNameRegex, '');
|
||||
}
|
||||
|
||||
// File path relative to the root extracted directory or a sub directory if subpath is set.
|
||||
private getInnerPath(path: string): string {
|
||||
return path.slice(this.subPath.length);
|
||||
}
|
||||
|
||||
private shouldBeIncluded(entry: Entry): boolean {
|
||||
const strippedPath = this.stripTopDirectory(entry.path);
|
||||
const strippedPath = stripFirstDirectoryFromPath(entry.path);
|
||||
|
||||
if (this.subPath) {
|
||||
if (!strippedPath.startsWith(this.subPath)) {
|
||||
@@ -104,7 +95,7 @@ export class ZipArchiveResponse implements ReadTreeResponse {
|
||||
|
||||
if (this.shouldBeIncluded(entry)) {
|
||||
files.push({
|
||||
path: this.getInnerPath(this.stripTopDirectory(entry.path)),
|
||||
path: this.getInnerPath(stripFirstDirectoryFromPath(entry.path)),
|
||||
content: () => entry.buffer(),
|
||||
});
|
||||
} else {
|
||||
@@ -153,7 +144,7 @@ export class ZipArchiveResponse implements ReadTreeResponse {
|
||||
// as a zip can have files with directories without directory entries
|
||||
if (entry.type === 'File' && this.shouldBeIncluded(entry)) {
|
||||
const entryPath = this.getInnerPath(
|
||||
this.stripTopDirectory(entry.path),
|
||||
stripFirstDirectoryFromPath(entry.path),
|
||||
);
|
||||
const dirname = platformPath.dirname(entryPath);
|
||||
if (dirname) {
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// Matches a directory name + one `/` at the start of any string,
|
||||
// containing any character except `/` one or more times, and ending with a `/`
|
||||
// e.g. Will match `dirA/` in `dirA/dirB/file.ext`
|
||||
const directoryNameRegex = /^[^\/]+\//;
|
||||
|
||||
// Removes the first segment of a forward-slash-separated path
|
||||
export function stripFirstDirectoryFromPath(path: string): string {
|
||||
return path.replace(directoryNameRegex, '');
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import { ReadTreeResponseFactory } from './tree';
|
||||
export type UrlReader = {
|
||||
read(url: string): Promise<Buffer>;
|
||||
readTree(url: string, options?: ReadTreeOptions): Promise<ReadTreeResponse>;
|
||||
search(url: string, options?: SearchOptions): Promise<SearchResponse>;
|
||||
};
|
||||
|
||||
export type UrlReaderPredicateTuple = {
|
||||
@@ -103,3 +104,51 @@ export type ReadTreeResponseFile = {
|
||||
path: string;
|
||||
content(): Promise<Buffer>;
|
||||
};
|
||||
|
||||
/**
|
||||
* An options object for search operations.
|
||||
*/
|
||||
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
|
||||
* 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
|
||||
* on the target branch. If they match, search will throw a NotModifiedError indicating that the search
|
||||
* response will not differ from the previous response which included this particular etag. If they mismatch,
|
||||
* search will return the rest of SearchResponse along with a new etag.
|
||||
*/
|
||||
etag?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* The output of a search operation.
|
||||
*/
|
||||
export type SearchResponse = {
|
||||
/**
|
||||
* The files that matched the search query.
|
||||
*/
|
||||
files: SearchResponseFile[];
|
||||
|
||||
/**
|
||||
* A unique identifer of the current remote tree, usually the commit SHA or etag from the target.
|
||||
*/
|
||||
etag: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a single file in a search response.
|
||||
*/
|
||||
export type SearchResponseFile = {
|
||||
/**
|
||||
* The full URL to the file.
|
||||
*/
|
||||
url: string;
|
||||
|
||||
/**
|
||||
* The binary contents of the file.
|
||||
*/
|
||||
content(): Promise<Buffer>;
|
||||
};
|
||||
|
||||
@@ -161,7 +161,7 @@ export class ServiceBuilderImpl implements ServiceBuilder {
|
||||
app.use(cors(corsOptions));
|
||||
}
|
||||
app.use(compression());
|
||||
app.use(requestLoggingHandler());
|
||||
app.use(requestLoggingHandler(logger));
|
||||
for (const [root, route] of this.routers) {
|
||||
app.use(root, route);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user