Merge branch 'master' into scalable-sidebar-new-implementation

Signed-off-by: hiba-aldalaty <hibaaldalaty@gmail.com>
This commit is contained in:
hiba-aldalaty
2021-12-01 16:40:07 +00:00
499 changed files with 22153 additions and 2734 deletions
-6
View File
@@ -1,6 +0,0 @@
---
'@backstage/errors': patch
---
Deprecate `parseErrorResponse` in favour of `parseErrorResponseBody`. Deprecate `data` field inside `ErrorResponse` in favour of `body`.
Rename the error name for unknown errors from `unknown` to `error`.
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/plugin-azure-devops': patch
'@backstage/plugin-azure-devops-common': patch
---
feat: Created pull request card component and initial pull request dashboard page.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/integration': patch
---
Narrow the types returned by the request option functions, to only the specifics that they actually do return. The reason for this change is that a full `RequestInit` is unfortunate to return because it's different between `cross-fetch` and `node-fetch`.
-6
View File
@@ -1,6 +0,0 @@
---
'@backstage/plugin-tech-insights-backend': patch
'@backstage/plugin-tech-insights-backend-module-jsonfc': patch
---
Update README docs to use correct function/parameter names
+51
View File
@@ -0,0 +1,51 @@
---
'@backstage/create-app': patch
---
Incorporate usage of the tokenManager into the backend created using `create-app`.
In existing backends, update the `PluginEnvironment` to include a `tokenManager`:
```diff
// packages/backend/src/types.ts
...
import {
...
+ TokenManager,
} from '@backstage/backend-common';
export type PluginEnvironment = {
...
+ tokenManager: TokenManager;
};
```
Then, create a `ServerTokenManager`. This can either be a `noop` that requires no secret and validates all requests by default, or one that uses a secret from your `app-config.yaml` to generate and validate tokens.
```diff
// packages/backend/src/index.ts
...
import {
...
+ ServerTokenManager,
} from '@backstage/backend-common';
...
function makeCreateEnv(config: Config) {
...
// CHOOSE ONE
// TokenManager not requiring a secret
+ const tokenManager = ServerTokenManager.noop();
// OR TokenManager requiring a secret
+ const tokenManager = ServerTokenManager.fromConfig(config);
...
return (plugin: string): PluginEnvironment => {
...
- return { logger, cache, database, config, reader, discovery };
+ return { logger, cache, database, config, reader, discovery, tokenManager };
};
}
```
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-kubernetes-backend': patch
---
Added apiVersionOverrides config to allow for specifying api versions to use for kubernetes objects
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-scaffolder': patch
---
Bump `react-jsonschema-form`
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-auth-backend': patch
---
Switched to using the standardized JSON error responses for all provider endpoints.
+19
View File
@@ -0,0 +1,19 @@
---
'@backstage/config-loader': patch
'@backstage/plugin-auth-backend': patch
'@backstage/plugin-badges-backend': patch
'@backstage/plugin-bitrise': patch
'@backstage/plugin-catalog-backend': patch
'@backstage/plugin-catalog-backend-module-msgraph': patch
'@backstage/plugin-code-coverage-backend': patch
'@backstage/plugin-fossa': patch
'@backstage/plugin-jenkins-backend': patch
'@backstage/plugin-scaffolder-backend': patch
'@backstage/plugin-scaffolder-backend-module-cookiecutter': patch
'@backstage/plugin-sonarqube': patch
'@backstage/plugin-tech-insights-backend': patch
'@backstage/plugin-techdocs-backend': patch
'@backstage/plugin-todo-backend': patch
---
Align on usage of `cross-fetch` vs `node-fetch` in frontend vs backend packages, and remove some unnecessary imports of either one of them
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/cli': patch
---
Switch the default test coverage provider from the jest default one to `'v8'`, which provides much better coverage information when using the default Backstage test setup. This is considered a bug fix as the current coverage information is often very inaccurate.
+28
View File
@@ -0,0 +1,28 @@
---
'@backstage/plugin-techdocs-backend': minor
---
**BREAKING** `DefaultTechDocsCollator` has a new required option `tokenManager`. See the create-app changelog for how to create a `tokenManager` and add it to the `PluginEnvironment`. It can then be passed to the collator in `createPlugin`:
```diff
// packages/backend/src/plugins/search.ts
...
export default async function createPlugin({
...
+ tokenManager,
}: PluginEnvironment) {
...
indexBuilder.addCollator({
defaultRefreshIntervalSeconds: 600,
collator: DefaultTechDocsCollator.fromConfig(config, {
discovery,
logger,
+ tokenManager,
}),
});
...
}
```
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/plugin-permission-node': minor
'@backstage/plugin-permission-backend': patch
---
Rename and adjust permission policy return type to reduce nesting
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/cli': patch
---
Disable ES transforms in tests transformed by the `jestSucraseTransform.js`. This is not considered a breaking change since all code is already transpiled this way in the development setup.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/create-app': patch
---
DefaultTechDocsCollator is now included in the search backend, and the Search Page updated with the SearchType component that includes the techdocs type
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/backend-common': patch
---
Create a `TokenManager` interface and `ServerTokenManager` implementation to generate and validate server tokens for authenticated backend-to-backend API requests.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-kubernetes-backend': patch
---
Query CronJobs from Kubernetes with apiGroup BatchV1beta1
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/cli': minor
---
Remove the `backend:build-image` command from the CLI and added more deprecation warnings to other deprecated fields like `--lax` and `remove-plugin`
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/cli': patch
'@techdocs/cli': patch
---
Bump react-dev-utils to v12
-34
View File
@@ -1,34 +0,0 @@
---
'@backstage/backend-common': patch
'@backstage/cli': patch
'@backstage/core-app-api': patch
'@backstage/create-app': patch
'@backstage/techdocs-common': patch
'@backstage/plugin-auth-backend': patch
'@backstage/plugin-azure-devops-backend': patch
'@backstage/plugin-badges-backend': patch
'@backstage/plugin-bazaar-backend': patch
'@backstage/plugin-catalog-backend': patch
'@backstage/plugin-code-coverage-backend': patch
'@backstage/plugin-github-actions': patch
'@backstage/plugin-jenkins-backend': patch
'@backstage/plugin-proxy-backend': patch
'@backstage/plugin-rollbar-backend': patch
'@backstage/plugin-search-backend': patch
'@backstage/plugin-techdocs': patch
'@backstage/plugin-techdocs-backend': patch
---
Change default port of backend from 7000 to 7007.
This is due to the AirPlay Receiver process occupying port 7000 and preventing local Backstage instances on MacOS to start.
You can change the port back to 7000 or any other value by providing an `app-config.yaml` with the following values:
```
backend:
listen: 0.0.0.0:7123
baseUrl: http://localhost:7123
```
More information can be found here: https://backstage.io/docs/conf/writing
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-permission-common': minor
---
Accept configApi rather than enabled flag in PermissionClient constructor.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/cli': patch
---
build(dependencies): bump `style-loader` from 1.2.1 to 3.3.1
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/core-plugin-api': patch
---
Tweaked the logged deprecation warning for `createRouteRef` to hopefully make it more clear.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-search': patch
---
Add a new optional clearButton property to the SearchBar component. The default value for this new property is true.
-6
View File
@@ -1,6 +0,0 @@
---
'@backstage/plugin-config-schema': patch
'@backstage/plugin-scaffolder': patch
---
Fixed a missing `await` when throwing server side errors
-42
View File
@@ -1,42 +0,0 @@
---
'@backstage/plugin-catalog-backend': minor
---
**BREAKING** EntitiesSearchFilter fields have changed.
EntitiesSearchFilter now has only two fields: `key` and `value`. The `matchValueIn` and `matchValueExists` fields are no longer are supported. Previous filters written using the `matchValueIn` and `matchValueExists` fields can be rewritten as follows:
Filtering by existence of key only:
```diff
filter: {
{
key: 'abc',
- matchValueExists: true,
},
}
```
Filtering by key and values:
```diff
filter: {
{
key: 'abc',
- matchValueExists: true,
- matchValueIn: ['xyz'],
+ values: ['xyz'],
},
}
```
Negation of filters can now be achieved through a `not` object:
```
filter: {
not: {
key: 'abc',
values: ['xyz'],
},
}
```
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-catalog-backend': patch
---
Index User entities by displayName to be able to search by full name. Added displayName (if present) to the 'text' field in the indexed document.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-auth-backend': patch
---
Expose catalog lib in plugin-auth-backend, i.e `CatalogIdentityClient` class is exposed now.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/core-app-api': patch
---
Fixed a bug where `useRouteRef` would fail in situations where relative navigation was needed and the app was is mounted on a sub-path. This would typically show up as a failure to navigate to a tab on an entity page.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder-backend': patch
---
Add options to spawn in runCommand helper
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/cli': patch
---
Update the default routes to use id instead of title
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-kubernetes': patch
---
Added accordions to display information on Jobs and CronJobs in the kubernetes plugin. Updated the PodsTable with fewer default columns and the ability to pass in additional ones depending on the use case.
-63
View File
@@ -1,63 +0,0 @@
---
'@backstage/core-app-api': patch
'@backstage/test-utils': patch
---
The `ApiRegistry` from `@backstage/core-app-api` class has been deprecated and will be removed in a future release. To replace it, we have introduced two new helpers that are exported from `@backstage/test-utils`, namely `TestApiProvider` and `TestApiRegistry`.
These two new helpers are more tailored for writing tests and development setups, as they allow for partial implementations of each of the APIs.
When migrating existing code it is typically best to prefer usage of `TestApiProvider` when possible, so for example the following code:
```tsx
render(
<ApiProvider
apis={ApiRegistry.from([
[identityApiRef, mockIdentityApi as unknown as IdentityApi]
])}
>
{...}
</ApiProvider>
)
```
Would be migrated to this:
```tsx
render(
<TestApiProvider apis={[[identityApiRef, mockIdentityApi]]}>
{...}
</TestApiProvider>
)
```
In cases where the `ApiProvider` is used in a more standalone way, for example to reuse a set of APIs across multiple tests, the `TestApiRegistry` can be used instead. Note that the `TestApiRegistry` only has a single static factory method, `.from()`, and it is slightly different from the existing `.from()` method on `ApiRegistry` in that it doesn't require the API pairs to be wrapped in an outer array.
Usage that looks like this:
```ts
const apis = ApiRegistry.with(
identityApiRef,
mockIdentityApi as unknown as IdentityApi,
).with(configApiRef, new ConfigReader({}));
```
OR like this:
```ts
const apis = ApiRegistry.from([
[identityApiRef, mockIdentityApi as unknown as IdentityApi],
[configApiRef, new ConfigReader({})],
]);
```
Would be migrated to this:
```ts
const apis = TestApiRegistry.from(
[identityApiRef, mockIdentityApi],
[configApiRef, new ConfigReader({})],
);
```
If your app is still using the `ApiRegistry` to construct the `apis` for `createApp`, we recommend that you move over to use the new method of supplying API factories instead, using `createApiFactory`.
+17
View File
@@ -0,0 +1,17 @@
---
'@backstage/plugin-scaffolder': patch
---
Add group filtering to the scaffolder page so that individuals can surface specific templates to end users ahead of others, or group templates together. This can be accomplished by passing in a `groups` prop to the `ScaffolderPage`
```
<ScaffolderPage
groups={[
{
title: "Recommended",
filter: entity =>
entity?.metadata?.tags?.includes('recommended') ?? false,
},
]}
/>
```
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/core-components': patch
---
Pin sidebar by default for easier navigation
+27
View File
@@ -0,0 +1,27 @@
---
'@backstage/plugin-catalog-backend': minor
---
**BREAKING** `DefaultCatalogCollator` has a new required option `tokenManager`. See the create-app changelog for how to create a `tokenManager` and add it to the `PluginEnvironment`. It can then be passed to the collator in `createPlugin`:
```diff
// packages/backend/src/plugins/search.ts
...
export default async function createPlugin({
...
+ tokenManager,
}: PluginEnvironment) {
...
indexBuilder.addCollator({
defaultRefreshIntervalSeconds: 600,
collator: DefaultCatalogCollator.fromConfig(config, {
discovery,
+ tokenManager,
}),
});
...
}
```
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-graphiql': patch
---
Letting GraphiQL use headers
+10
View File
@@ -8,6 +8,7 @@
/docs/features/techdocs @backstage/techdocs-core
/docs/features/search @backstage/techdocs-core
/docs/assets/search @backstage/techdocs-core
/plugins/allure @backstage/reviewers @deepak-bhardwaj-ps
/plugins/code-coverage @backstage/reviewers @alde @nissayeva
/plugins/code-coverage-backend @backstage/reviewers @alde @nissayeva
/plugins/cost-insights @backstage/silver-lining
@@ -18,6 +19,15 @@
/plugins/techdocs-backend @backstage/techdocs-core
/plugins/ilert @backstage/reviewers @yacut
/plugins/home @backstage/techdocs-core
/plugins/azure-devops @backstage/reviewers @marleypowell @awanlin
/plugins/azure-devops-backend @backstage/reviewers @marleypowell @awanlin
/plugins/azure-devops-common @backstage/reviewers @marleypowell @awanlin
/plugins/kafka @backstage/reviewers @nirga
/plugins/kafka-backend @backstage/reviewers @nirga
/tech-insights-backend @backstage/reviewers @xantier @iain-b
/tech-insights-backend-module-jsonfc @backstage/reviewers @xantier @iain-b
/tech-insights-tech-insights-common @backstage/reviewers @xantier @iain-b
/tech-insights-tech-insights-node @backstage/reviewers @xantier @iain-b
/packages/embedded-techdocs-app @backstage/techdocs-core
/packages/search-common @backstage/techdocs-core
/packages/techdocs-cli @backstage/techdocs-core
-21
View File
@@ -1,21 +0,0 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 60
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- pinned
- security
- plugin
- help wanted
- good first issue
- rfc
# Label to use when marking an issue as stale
staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false
+1
View File
@@ -189,6 +189,7 @@ oidc
Okta
onboarding
Onboarding
OpenShift
orgs
pagerduty
pageview
+30
View File
@@ -0,0 +1,30 @@
name: 'Stale workflow'
on:
workflow_dispatch:
schedule:
- cron: '*/10 * * * *' # run every 10 minutes as it also removes labels.
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@main
id: stale
with:
stale-issue-message: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
days-before-issue-stale: 60
days-before-issue-close: 7
exempt-issue-labels: 'pinned,security,plugin,help wanted,good first issue,rfc'
stale-issue-label: stale
stale-pr-message: >
This PR has been automatically marked as stale because it has not had
recent activity from the author. It will be closed if no further activity occurs.
If you are the author and the PR has been closed, feel free to re-open the PR and continue the contribution!
days-before-pr-stale: 7
days-before-pr-close: 3
exempt-pr-labels: reviewer-approved,awaiting-review
stale-pr-label: stale
operations-per-run: 100
+6 -4
View File
@@ -66,8 +66,10 @@
| [Palo Alto Networks](https://www.paloaltonetworks.com) | [Jeremy Guarini](https://github.com/jeremyguarini), [Brian Lomeland](https://github.com/bbbmmmlll), [Palo Alto Networks](https://github.com/PaloAltoNetworks) | Developer portal, service catalog, documentation and tooling |
| [Signal Iduna Group](https://www.signal-iduna.de/) | [Jonas Thomsen](https://github.com/JoThomsen) | Developer Portal, documentation, monitoring, service catalog for our insurance ecosystem |
| [Tradeshift](https://www.tradeshift.com/) | [Soren Mathiasen](https://github.com/sorenmat) | Developer Portal: documentation, monitoring, service templates, service catalog for our micro services |
| [Unity](https://unity.com) | [Ted Cordery](https://github.com/TeddyBallGame) | A centralized service catalog with documentation for our service engineers. |
| [PicPay](https://www.picpay.com) | [Luis Baroni](https://github.com/lcsbaroni), [Renata Poluceno](https://github.com/renatapoluceno), [PicPay](https://github.com/picpay) | Developer portal for building services throught templates, service catalog with ownership of services, documentation and metrics providing autonomy and visibility for all. |
| [Unity](https://unity.com) | [Ted Cordery](https://github.com/TeddyBallGame) | A centralized service catalog with documentation for our service engineers. |
| [PicPay](https://www.picpay.com) | [Luis Baroni](https://github.com/lcsbaroni), [Renata Poluceno](https://github.com/renatapoluceno), [PicPay](https://github.com/picpay) | Developer portal for building services throught templates, service catalog with ownership of services, documentation and metrics providing autonomy and visibility for all. |
| [Epic Games](https://www.epicgames.com) | [Brian Jung](https://github.com/brian-at-epic), [Jeff Goldian](https://github.com/jeffgoldian-Epic) | Developer Portal: Service Catalog, Documentation, Software Templates and more making our internal teams' lives easier! |
| [Globo](https://globo.com) | [Carlos Gusmão](https://github.com/caeugusmao), [Guilherme Vierno](https://github.com/vierno), [Denis Aoki](https://github.com/dnsaoki2), [Maycon Dionisio](https://github.com/MayconDionisio), | Reduce the friction of accessing the information engineers need about Globo's digital services through a coherent and centralized experience. |
| [QBE](https://www.qbe.com/) | [Daniel Steel](https://github.com/danielsteelqbe), [Pete Jespers](https://github.com/petejespersqbe) | Developer portal allowing our global teams to explore and create applications, documentation and cloud infrastructure easily and quickly 🚀 |
| [Globo](https://globo.com) | [Carlos Gusmão](https://github.com/caeugusmao), [Guilherme Vierno](https://github.com/vierno), [Denis Aoki](https://github.com/dnsaoki2), [Maycon Dionisio](https://github.com/MayconDionisio), | Reduce the friction of accessing the information engineers need about Globo's digital services through a coherent and centralized experience. |
| [QBE](https://www.qbe.com/) | [Daniel Steel](https://github.com/danielsteelqbe), [Pete Jespers](https://github.com/petejespersqbe) | Developer portal allowing our global teams to explore and create applications, documentation and cloud infrastructure easily and quickly 🚀 |
| [LogMeIn](https://www.logmein.com) | [Lorenzo Orsatti](https://github.com/lorsatti) | Improve onboarding experience of new developers. Discover faster and painlessly developer documentation, API definitions and team information. Provide useful dev metrics in a central place. Provide easy-to-use templates for new services. |
| [Telstra](https://www.telstra.com.au) | [@kiranpatel11](https://github.com/kiranpatel11), [JasonC](https://github.com/JasonC17) | Primary usage: software catalog and templates<br/> Emerging usage : TechDocs, Explore Ecosystem, TechRadar, etc |
+5
View File
@@ -23,6 +23,11 @@ app:
title: '#backstage'
backend:
# Used for enabling authentication, secret is shared by all backend plugins
# See backend-to-backend-auth.md in the docs for information on the format
# auth:
# keys:
# - secret: ${BACKEND_SECRET}
baseUrl: http://localhost:7007
listen:
port: 7007
@@ -1,7 +1,13 @@
{{- $frontendUrl := urlParse .Values.appConfig.app.baseUrl}}
{{- $backendUrl := urlParse .Values.appConfig.backend.baseUrl}}
{{- $lighthouseUrl := urlParse .Values.appConfig.lighthouse.baseUrl}}
{{/* Determine the api type for the ingress */}}
{{- if lt .Capabilities.KubeVersion.Minor "19" }}
apiVersion: networking.k8s.io/v1beta1
{{- else if ge .Capabilities.KubeVersion.Minor "19" }}
apiVersion: networking.k8s.io/v1
{{- end }}
kind: Ingress
metadata:
name: {{ include "backstage.fullname" . }}-ingress
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

+2
View File
@@ -16,8 +16,10 @@ Backstage identity information in your app or plugins.
Backstage comes with many common authentication providers in the core library:
- [Atlassian](atlassian/provider.md)
- [Auth0](auth0/provider.md)
- [Azure](microsoft/provider.md)
- [Bitbucket](bitbucket/provider.md)
- [GitHub](github/provider.md)
- [GitLab](gitlab/provider.md)
- [Google](google/provider.md)
+3 -3
View File
@@ -7,9 +7,9 @@ description: How to build a Backstage Docker image for deployment
This section describes how to build a Backstage App into a deployable Docker
image. It is split into three sections, first covering the host build approach,
which is recommended due its speed and more efficient and often simpler caching.
The second section covers a full multi-stage Docker build, and the last section
covers how to deploy the frontend and backend as separate images.
which is recommended due to its speed and more efficient and often simpler
caching. The second section covers a full multi-stage Docker build, and the last
section covers how to deploy the frontend and backend as separate images.
Something that goes for all of these docker deployment strategies is that they
are stateless, so for a production deployment you will want to set up and
+20
View File
@@ -219,6 +219,26 @@ The custom resource's apiVersion.
The plural representing the custom resource.
### `apiVersionOverrides` (optional)
Overrides for the API versions used to make requests for the corresponding
objects. If using a legacy Kubernetes version, you may use this config to
override the default API versions to ones that are supported by your cluster.
Example:
```yaml
---
kubernetes:
apiVersionOverrides:
cronjobs: 'v1beta1'
```
For more information on which API versions are supported by your cluster, please
view the Kubernetes API docs for your Kubernetes version (e.g.
[API Groups for v1.22](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#-strong-api-groups-strong-)
)
### Role Based Access Control
The current RBAC permissions required are read-only cluster wide, for the
+13 -3
View File
@@ -154,13 +154,17 @@ import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend';
export default async function createPlugin({
logger,
discovery,
tokenManager,
}: PluginEnvironment) {
const searchEngine = new LunrSearchEngine({ logger });
const indexBuilder = new IndexBuilder({ logger, searchEngine });
indexBuilder.addCollator({
defaultRefreshIntervalSeconds: 600,
collator: new DefaultCatalogCollator({ discovery }),
collator: new DefaultCatalogCollator({
discovery,
tokenManager,
}),
});
const { scheduler } = await indexBuilder.build();
@@ -285,7 +289,10 @@ const indexBuilder = new IndexBuilder({ logger, searchEngine });
indexBuilder.addCollator({
defaultRefreshIntervalSeconds: 600,
collator: new DefaultCatalogCollator({ discovery }),
collator: new DefaultCatalogCollator({
discovery,
tokenManager,
}),
});
indexBuilder.addCollator({
@@ -303,6 +310,9 @@ its `defaultRefreshIntervalSeconds` value, like this:
```typescript {3}
indexBuilder.addCollator({
defaultRefreshIntervalSeconds: 600,
collator: new DefaultCatalogCollator({ discovery }),
collator: new DefaultCatalogCollator({
discovery,
tokenManager,
}),
});
```
+2 -1
View File
@@ -2,7 +2,7 @@
id: how-to-guides
title: Search "HOW TO" guides
sidebar_label: "HOW TO" guides
description: Search "HOW TO" guides
description: Search "HOW TO" guides
---
## How to implement your own Search API
@@ -74,6 +74,7 @@ indexBuilder.addCollator({
collator: DefaultTechDocsCollator.fromConfig(config, {
discovery,
logger,
tokenManager,
}),
});
```
+3 -3
View File
@@ -4,9 +4,9 @@ title: Search Engines
description: Choosing and configuring your search engine for Backstage
---
Backstage supports 2 search engines by default, an in-memory engine called Lunr
and ElasticSearch. You can configure your own search engines by implementing the
provided interface as mentioned in the
Backstage supports 3 search engines by default, an in-memory engine called Lunr,
ElasticSearch and Postgres. You can configure your own search engines by
implementing the provided interface as mentioned in the
[search backend documentation.](./getting-started.md#Backend)
Provided search engine implementations have their own way of constructing
@@ -53,3 +53,30 @@ You can do so by including the following lines in the last step of your
RUN apt-get update && apt-get install -y python3 python3-pip
RUN pip3 install cookiecutter
```
### Customizing the ScaffolderPage with Grouping and Filtering
Once you have more than a few software templates you may want to customize your
`ScaffolderPage` by grouping and surfacing certain templates together. You can
accomplish this by creating `groups` and passing them to your `ScaffolderPage`
like below
```
<ScaffolderPage
groups={[
{
title: "Recommended",
filter: entity =>
entity?.metadata?.tags?.includes('recommended') ?? false,
},
]}
/>
```
This code will group all templates with the 'recommended' tag together at the
top of the page above any other templates not filtered by this group or others.
You can also further customize groups by passing in a `titleComponent` instead
of a `title` which will be a component to use as the header instead of just the
default `ContentHeader` with the `title` set as it's value.
![Grouped Templates](../../assets/software-templates/grouped-templates.png)
+17 -14
View File
@@ -23,7 +23,7 @@ guide to do a repository-based installation.
- Access to a Linux-based operating system, such as Linux, MacOS or
[Windows Subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/)
- An account with elevated rights
- An account with elevated rights to install the dependencies
- `curl` or `wget` installed
- Node.js Active LTS Release installed (currently v14) using one of these
methods:
@@ -36,15 +36,16 @@ guide to do a repository-based installation.
- `yarn` [Installation](https://classic.yarnpkg.com/en/docs/install)
- `docker` [installation](https://docs.docker.com/engine/install/)
- `git` [installation](https://github.com/git-guides/install-git)
- If the system is not directly accessible over your network, the following
ports need to be opened: 3000, 7007
- If the system is not directly accessible over your network the following ports
need to be opened: 3000, 7007. This is quite uncommon, unless when you're
installing in a container, VM or remote system.
### Create your Backstage App
To install the Backstage Standalone app, we make use of `npx`, a tool to run
Node executables straight from the registry. Running the command below will
install Backstage. The wizard will create a subdirectory inside your current
working directory.
Node executables straight from the registry. This tool is part of your Node.js
installation. Running the command below will install Backstage. The wizard will
create a subdirectory inside your current working directory.
```bash
npx @backstage/create-app
@@ -78,12 +79,21 @@ yarn dev
It might take a little while, but as soon as the message
`[0] webpack compiled successfully` appears, you can open a browser and directly
navigate to your freshly installed Backstage portal at `http://localhost:3000`.
You can start exploring the demo immediately.
You can start exploring the demo immediately. Please note that the in-memory
database will be cleared when you restart the app, so you'll most likely want to
carry on with the database steps.
<p align='center'>
<img src='../assets/getting-started/portal.png' alt='Screenshot of the Backstage portal.'>
</p>
The most common next steps are to move to a persistent database, configure
authentication, and add a plugin:
- [Switching from SQLite to PostgresQL](https://backstage.io/docs/tutorials/switching-sqlite-postgres)
- [Setting up Authentication](https://backstage.io/docs/auth/)
- [Adding a plugin](https://backstage.io/docs/getting-started/configure-app-with-plugins)
Congratulations! That should be it. Let us know how it went:
[on discord](https://discord.gg/EBHEGzX), file issues for any
[feature](https://github.com/backstage/backstage/issues/new?labels=help+wanted&template=feature_template.md)
@@ -93,10 +103,3 @@ or
[bugs](https://github.com/backstage/backstage/issues/new?labels=bug&template=bug_template.md)
you have, and feel free to
[contribute](https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md)!
The most common next steps are to configure Backstage, add a plugin and moving
to a more persistent database:
- [Setting up Authentication](https://backstage.io/docs/auth/)
- [Switching from SQLite to PostgresQL](https://backstage.io/docs/tutorials/switching-sqlite-postgres)
- [Adding a plugin](https://backstage.io/docs/getting-started/configure-app-with-plugins)
+49
View File
@@ -0,0 +1,49 @@
---
id: discovery
title: Azure DevOps Discovery
sidebar_label: Discovery
# prettier-ignore
description: Automatically discovering catalog entities from repositories in an Azure DevOps organization
---
The Azure DevOps integration has a special discovery processor for discovering
catalog entities within an Azure DevOps. The processor will crawl the Azure
DevOps organization and register entities matching the configured path. This can
be useful as an alternative to static locations or manually adding things to the
catalog.
To use the discovery processor, you'll need a GitHub integration
[set up](locations.md) with a `AZURE_TOKEN`. Then you can add a location target
to the catalog configuration:
```yaml
catalog:
locations:
# Scan all repositories for a catalog-info.yaml in the root of the default branch
- type: azure-discovery
target: https://dev.azure.com/myorg/myproject
# Or use a custom pattern for a subset of all repositories with default repository
- type: azure-discovery
target: https://dev.azure.com/myorg/myproject/_git/service-*
# Or use a custom file format and location
- type: azure-discovery
target: https://dev.azure.com/myorg/myproject/_git/*?path=/src/*/catalog-info.yaml
```
Note the `azure-discovery` type, as this is not a regular `url` processor.
When using a custom pattern, the target is composed of five parts:
- The base instance URL, `https://dev.azure.com` in this case
- The organization name which is required, `myorg` in this case
- The project name which is required, `myproject` in this case
- The repository blob to scan, which accepts \* wildcard tokens and must be
added after `_git/`. This can simply be `*` to scan all repositories in the
project.
- The path within each repository to find the catalog YAML file. This will
usually be `/catalog-info.yaml`, `/src/*/catalog-info.yaml` or a similar
variation for catalog files stored in the root directory of each repository.
_Note:_ the path parameter follows the same rules as the search on Azure DevOps
web interface. For more details visit the
[official search documentation](https://docs.microsoft.com/en-us/azure/devops/project/search/get-started-search?view=azure-devops)
+1 -1
View File
@@ -27,7 +27,7 @@ point building on top of the previous one:
and the new APIs can be used in parallel. This deprecation must have been
released for at least two weeks before the deprecated API is removed in a
minor version bump.
- **3** - The time limit for the deprecation is 3 months instead of two days.
- **3** - The time limit for the deprecation is 3 months instead of two weeks.
TL;DR:
+8 -5
View File
@@ -261,10 +261,13 @@ analytics events captured.
Use it like this:
```tsx
import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
import { analyticsApiRef } from '@backstage/core-plugin-api';
import { MockAnalyticsApi, wrapInTestApp } from '@backstage/test-utils';
import { render, fireEvent, waitFor } from '@testing-library/react';
import { analyticsApiRef } from '@backstage/core-plugin-api';
import {
MockAnalyticsApi,
TestApiProvider,
wrapInTestApp,
} from '@backstage/test-utils';
describe('SomeComponent', () => {
it('should capture event on click', () => {
@@ -274,9 +277,9 @@ describe('SomeComponent', () => {
// Render the component being tested
const { getByText } = render(
wrapInTestApp(
<ApiProvider apis={ApiRegistry.from([[analyticsApiRef, apiSpy]])}>
<TestApiProvider apis={[[analyticsApiRef, apiSpy]]}>
<SomeComponentUnderTest />
</ApiProvider>,
</TestApiProvider>,
),
);
+68
View File
@@ -0,0 +1,68 @@
---
id: backend-to-backend-auth
title: Backend-to-Backend Authentication
description:
Guide for authenticating API requests between Backstage plugin backends
---
This tutorial describes the steps needed to handle _backend-to-backend
authentication_, which allows plugin backends to determine whether a given
request originates from a legitimate Backstage backend by verifying a token
signed with a shared secret. This system has limited use for now, but will be
needed to support the upcoming framework for permissions and authorization (see
[the PRFC on the topic](https://github.com/backstage/backstage/pull/7761) for
more details).
Backends have no concept of a Backstage identity, so instead they use a token
generated using a shared key stored in config. You can generate a unique key for
your app in a terminal, and set the `BACKEND_SECRET` environment variable to the
resulting value.
```bash
node -p 'require("crypto").randomBytes(24).toString("base64")'
```
Requests originating from a backend plugin can be authenticated by decorating
them with a backend token. Backend tokens can be generated using a
`TokenManager`, which can be passed to plugin backends via the
`PluginEnvironment`. The `TokenManager` provided in new Backstage instances
generated by `create-app` is a stub, which returns empty tokens and accepts any
input string as valid. To enable backend-to-backend authentication, you'll need
to instantiate a new one using the secret from your config instead:
```diff
// packages/backend/src/index.ts
function makeCreateEnv(config: Config) {
const root = getRootLogger();
const reader = UrlReaders.default({ logger: root, config });
const discovery = SingleHostDiscovery.fromConfig(config);
root.info(`Created UrlReader ${reader}`);
const cacheManager = CacheManager.fromConfig(config);
const databaseManager = DatabaseManager.fromConfig(config);
- const tokenManager = ServerTokenManager.noop();
+ const tokenManager = ServerTokenManager.fromConfig(config);
```
With this `tokenManager`, you can then generate a server token for requests:
```typescript
const { token } = await this.tokenManager.getToken();
const response = await fetch(pluginBackendApiUrl, {
method: 'GET',
headers: {
...headers,
Authorization: `Bearer ${token}`,
},
});
```
You can use the same `tokenManager` to authenticate tokens supplied on incoming
requests:
```typescript
await tokenManager.authenticate(token); // throws if token is invalid
```
+1 -1
View File
@@ -19,7 +19,7 @@
"@spotify/prettier-config": "^12.0.0",
"docusaurus": "^2.0.0-alpha.70",
"js-yaml": "^4.1.0",
"prettier": "^2.4.1",
"prettier": "^2.5.0",
"yarn-lock-check": "^1.0.5"
},
"prettier": "@spotify/prettier-config"
+5 -1
View File
@@ -125,7 +125,11 @@
{
"type": "subcategory",
"label": "Azure",
"ids": ["integrations/azure/locations", "integrations/azure/org"]
"ids": [
"integrations/azure/locations",
"integrations/azure/discovery",
"integrations/azure/org"
]
},
{
"type": "subcategory",
+4 -4
View File
@@ -5190,10 +5190,10 @@ prepend-http@^2.0.0:
resolved "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=
prettier@^2.4.1:
version "2.4.1"
resolved "https://registry.npmjs.org/prettier/-/prettier-2.4.1.tgz#671e11c89c14a4cfc876ce564106c4a6726c9f5c"
integrity sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA==
prettier@^2.5.0:
version "2.5.0"
resolved "https://registry.npmjs.org/prettier/-/prettier-2.5.0.tgz#a6370e2d4594e093270419d9cc47f7670488f893"
integrity sha512-FM/zAKgWTxj40rH03VxzIPdXmj39SwSjwG0heUcNFwI+EMZJnY93yAiKXM3dObIKAM5TA88werc8T/EwhB45eg==
prismjs@^1.22.0:
version "1.25.0"
+1
View File
@@ -88,6 +88,7 @@ nav:
- Discovery: 'integrations/aws-s3/discovery.md'
- Azure:
- Locations: 'integrations/azure/locations.md'
- Discovery: 'integrations/azure/discovery.md'
- Org Data: 'integrations/azure/org.md'
- Bitbucket:
- Locations: 'integrations/bitbucket/locations.md'
+2
View File
@@ -30,6 +30,8 @@
"prettier:check": "prettier --check .",
"lerna": "lerna",
"storybook": "yarn workspace storybook start",
"snyk:test": "npx snyk test --yarn-workspaces --strict-out-of-sync=false",
"snyk:test:package": "yarn snyk:test --include",
"build-storybook": "yarn workspace storybook build-storybook",
"techdocs-cli": "node scripts/techdocs-cli.js",
"techdocs-cli:dev": "cross-env TECHDOCS_CLI_DEV_MODE=true node scripts/techdocs-cli.js",
+1 -1
View File
@@ -66,7 +66,7 @@
"devDependencies": {
"@backstage/test-utils": "^0.1.22",
"@rjsf/core": "^3.2.1",
"@testing-library/cypress": "^7.0.1",
"@testing-library/cypress": "^8.0.2",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
+16 -1
View File
@@ -34,6 +34,7 @@ import {
SignInPage,
} from '@backstage/core-components';
import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs';
import { AzurePullRequestsPage } from '@backstage/plugin-azure-devops';
import {
CatalogEntityPage,
CatalogIndexPage,
@@ -175,7 +176,20 @@ const routes = (
>
{techDocsPage}
</Route>
<Route path="/create" element={<ScaffolderPage />}>
<Route
path="/create"
element={
<ScaffolderPage
groups={[
{
title: 'Recommended',
filter: entity =>
entity?.metadata?.tags?.includes('recommended') ?? false,
},
]}
/>
}
>
<ScaffolderFieldExtensions>
<LowerCaseValuePickerFieldExtension />
</ScaffolderFieldExtensions>
@@ -203,6 +217,7 @@ const routes = (
element={<CostInsightsLabelDataflowInstructionsPage />}
/>
<Route path="/settings" element={<UserSettingsPage />} />
<Route path="/azure-pull-requests" element={<AzurePullRequestsPage />} />
</FlatRoutes>
);
@@ -41,6 +41,7 @@ import {
SidebarSpace,
SidebarScrollWrapper,
} from '@backstage/core-components';
import { AzurePullRequestsIcon } from '@backstage/plugin-azure-devops';
const useSidebarLogoStyles = makeStyles({
root: {
@@ -94,6 +95,11 @@ export const Root = ({ children }: PropsWithChildren<{}>) => (
<SidebarItem icon={RuleIcon} to="lighthouse" text="Lighthouse" />
<SidebarItem icon={MoneyIcon} to="cost-insights" text="Cost Insights" />
<SidebarItem icon={GraphiQLIcon} to="graphiql" text="GraphiQL" />
<SidebarItem
icon={AzurePullRequestsIcon}
to="azure-pull-requests"
text="Azure PRs"
/>
</SidebarScrollWrapper>
<SidebarDivider />
<Shortcuts />
@@ -14,7 +14,6 @@
* limitations under the License.
*/
import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
import { EntityLayout } from '@backstage/plugin-catalog';
import {
DefaultStarredEntitiesApi,
@@ -22,7 +21,11 @@ import {
starredEntitiesApiRef,
} from '@backstage/plugin-catalog-react';
import { githubActionsApiRef } from '@backstage/plugin-github-actions';
import { MockStorageApi, renderInTestApp } from '@backstage/test-utils';
import {
MockStorageApi,
renderInTestApp,
TestApiProvider,
} from '@backstage/test-utils';
import React from 'react';
import { cicdContent } from './EntityPage';
@@ -45,22 +48,22 @@ describe('EntityPage Test', () => {
const mockedApi = {
listWorkflowRuns: jest.fn().mockResolvedValue([]),
getWorkflow: jest.fn(),
getWorkflowRun: jest.fn(),
reRunWorkflow: jest.fn(),
listJobsForWorkflowRun: jest.fn(),
downloadJobLogsForWorkflowRun: jest.fn(),
} as jest.Mocked<typeof githubActionsApiRef.T>;
const apis = ApiRegistry.with(githubActionsApiRef, mockedApi).with(
starredEntitiesApiRef,
new DefaultStarredEntitiesApi({ storageApi: MockStorageApi.create() }),
);
};
describe('cicdContent', () => {
it('Should render GitHub Actions View', async () => {
const rendered = await renderInTestApp(
<ApiProvider apis={apis}>
<TestApiProvider
apis={[
[githubActionsApiRef, mockedApi],
[
starredEntitiesApiRef,
new DefaultStarredEntitiesApi({
storageApi: MockStorageApi.create(),
}),
],
]}
>
<EntityProvider entity={entity}>
<EntityLayout>
<EntityLayout.Route path="/ci-cd" title="CI-CD">
@@ -68,7 +71,7 @@ describe('EntityPage Test', () => {
</EntityLayout.Route>
</EntityLayout>
</EntityProvider>
</ApiProvider>,
</TestApiProvider>,
);
expect(rendered.getByText('ExampleComponent')).toBeInTheDocument();
+21
View File
@@ -1,5 +1,26 @@
# @backstage/backend-common
## 0.9.11
### Patch Changes
- bab752e2b3: Change default port of backend from 7000 to 7007.
This is due to the AirPlay Receiver process occupying port 7000 and preventing local Backstage instances on MacOS to start.
You can change the port back to 7000 or any other value by providing an `app-config.yaml` with the following values:
```
backend:
listen: 0.0.0.0:7123
baseUrl: http://localhost:7123
```
More information can be found here: https://backstage.io/docs/conf/writing
- Updated dependencies
- @backstage/errors@0.1.5
## 0.9.10
### Patch Changes
+24
View File
@@ -526,6 +526,20 @@ export type SearchResponseFile = {
content(): Promise<Buffer>;
};
// @public
export class ServerTokenManager implements TokenManager {
// (undocumented)
authenticate(token: string): Promise<void>;
// (undocumented)
static fromConfig(config: Config): ServerTokenManager;
// (undocumented)
getToken(): Promise<{
token: string;
}>;
// (undocumented)
static noop(): TokenManager;
}
// @public (undocumented)
export type ServiceBuilder = {
loadConfig(config: Config): ServiceBuilder;
@@ -583,6 +597,16 @@ export interface StatusCheckHandlerOptions {
statusCheck?: StatusCheck;
}
// @public
export interface TokenManager {
// (undocumented)
authenticate: (token: string) => Promise<void>;
// (undocumented)
getToken: () => Promise<{
token: string;
}>;
}
// @public
export type UrlReader = {
read(url: string): Promise<Buffer>;
+14
View File
@@ -20,6 +20,20 @@ export interface Config {
};
backend: {
/** Backend configuration for when request authentication is enabled */
auth?: {
/** Keys shared by all backends for signing and validating backend tokens. */
keys: {
/**
* Secret for generating tokens. Should be a base64 string, recommended
* length is 24 bytes.
*
* @visibility secret
*/
secret: string;
}[];
};
baseUrl: string; // defined in core, but repeated here without doc
/** Address that the backend should listen to. */
+5 -4
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/backend-common",
"description": "Common functionality library for Backstage backends",
"version": "0.9.10",
"version": "0.9.11",
"main": "src/index.ts",
"types": "src/index.ts",
"private": false,
@@ -32,7 +32,7 @@
"@backstage/cli-common": "^0.1.6",
"@backstage/config": "^0.1.11",
"@backstage/config-loader": "^0.8.0",
"@backstage/errors": "^0.1.4",
"@backstage/errors": "^0.1.5",
"@backstage/integration": "^0.6.9",
"@backstage/types": "^0.1.1",
"@google-cloud/storage": "^5.8.0",
@@ -54,6 +54,7 @@
"git-url-parse": "^11.6.0",
"helmet": "^4.0.0",
"isomorphic-git": "^1.8.0",
"jose": "^1.27.1",
"keyv": "^4.0.3",
"keyv-memcache": "^1.2.5",
"knex": "^0.95.1",
@@ -80,8 +81,8 @@
}
},
"devDependencies": {
"@backstage/cli": "^0.9.0",
"@backstage/test-utils": "^0.1.22",
"@backstage/cli": "^0.9.1",
"@backstage/test-utils": "^0.1.23",
"@types/archiver": "^5.1.0",
"@types/compression": "^1.7.0",
"@types/concat-stream": "^1.6.0",
+1
View File
@@ -31,4 +31,5 @@ export * from './paths';
export * from './reading';
export * from './scm';
export * from './service';
export * from './tokens';
export * from './util';
@@ -0,0 +1,189 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ConfigReader } from '@backstage/config';
import { TokenManager } from './types';
import { ServerTokenManager } from './ServerTokenManager';
const emptyConfig = new ConfigReader({});
const configWithSecret = new ConfigReader({
backend: { auth: { keys: [{ secret: 'a-secret-key' }] } },
});
describe('ServerTokenManager', () => {
it('should throw if secret in config does not exist', () => {
expect(() => ServerTokenManager.fromConfig(emptyConfig)).toThrowError();
});
describe('getToken', () => {
it('should return a token if secret in config exists', async () => {
const tokenManager = ServerTokenManager.fromConfig(configWithSecret);
expect((await tokenManager.getToken()).token).toBeDefined();
});
it('should return a token string if using a noop TokenManager', async () => {
const tokenManager = ServerTokenManager.noop();
expect((await tokenManager.getToken()).token).toBeDefined();
});
});
describe('authenticate', () => {
it('should not throw if token is valid', async () => {
const tokenManager = ServerTokenManager.fromConfig(configWithSecret);
const { token } = await tokenManager.getToken();
await expect(tokenManager.authenticate(token)).resolves.not.toThrow();
});
it('should throw if token is invalid', async () => {
const tokenManager = ServerTokenManager.fromConfig(configWithSecret);
await expect(
tokenManager.authenticate('random-string'),
).rejects.toThrowError(/invalid server token/i);
});
it('should validate server tokens created by a different instance using the same secret', async () => {
const tokenManager1 = ServerTokenManager.fromConfig(configWithSecret);
const tokenManager2 = ServerTokenManager.fromConfig(configWithSecret);
const { token } = await tokenManager1.getToken();
await expect(tokenManager2.authenticate(token)).resolves.not.toThrow();
});
it('should validate server tokens created using any of the secrets', async () => {
const tokenManager1 = ServerTokenManager.fromConfig(
new ConfigReader({
backend: { auth: { keys: [{ secret: 'a1b2c3' }] } },
}),
);
const tokenManager2 = ServerTokenManager.fromConfig(
new ConfigReader({
backend: { auth: { keys: [{ secret: 'd4e5f6' }] } },
}),
);
const tokenManager3 = ServerTokenManager.fromConfig(
new ConfigReader({
backend: {
auth: { keys: [{ secret: 'a1b2c3' }, { secret: 'd4e5f6' }] },
},
}),
);
const { token: token1 } = await tokenManager1.getToken();
await expect(tokenManager3.authenticate(token1)).resolves.not.toThrow();
const { token: token2 } = await tokenManager2.getToken();
await expect(tokenManager3.authenticate(token2)).resolves.not.toThrow();
});
it('should throw for server tokens created using a different secret', async () => {
const tokenManager1 = ServerTokenManager.fromConfig(
new ConfigReader({
backend: { auth: { keys: [{ secret: 'a1b2c3' }] } },
}),
);
const tokenManager2 = ServerTokenManager.fromConfig(
new ConfigReader({
backend: { auth: { keys: [{ secret: 'd4e5f6' }] } },
}),
);
const { token } = await tokenManager1.getToken();
await expect(tokenManager2.authenticate(token)).rejects.toThrowError(
/invalid server token/i,
);
});
it('should throw for server tokens created using a noop TokenManager', async () => {
const noopTokenManager = ServerTokenManager.noop();
const tokenManager = ServerTokenManager.fromConfig(
new ConfigReader({
backend: { auth: { keys: [{ secret: 'a1b2c3' }] } },
}),
);
const { token } = await noopTokenManager.getToken();
await expect(tokenManager.authenticate(token)).rejects.toThrowError(
/invalid server token/i,
);
});
});
describe('ServerTokenManager.fromConfig', () => {
it('should throw if backend auth configuration is missing', () => {
expect(() =>
ServerTokenManager.fromConfig(new ConfigReader({})),
).toThrow();
});
it('should throw if no keys are included in the configuration', () => {
expect(() =>
ServerTokenManager.fromConfig(
new ConfigReader({
backend: { auth: { keys: [] } },
}),
),
).toThrow();
});
it('should throw if any key is missing a secret property', () => {
expect(() =>
ServerTokenManager.fromConfig(
new ConfigReader({
backend: {
auth: {
keys: [{ secret: '123' }, {}, { secret: '789' }],
},
},
}),
),
).toThrow();
});
});
describe('ServerTokenManager.noop', () => {
let noopTokenManager: TokenManager;
beforeEach(() => {
noopTokenManager = ServerTokenManager.noop();
});
it('should accept tokens it generates', async () => {
const { token } = await noopTokenManager.getToken();
await expect(noopTokenManager.authenticate(token)).resolves.not.toThrow();
});
it('should accept tokens generated by other noop token managers', async () => {
const noopTokenManager2 = ServerTokenManager.noop();
await expect(
noopTokenManager.authenticate(
(
await noopTokenManager2.getToken()
).token,
),
).resolves.not.toThrow();
});
it('should accept signed tokens', async () => {
const tokenManager = ServerTokenManager.fromConfig(configWithSecret);
await expect(
noopTokenManager.authenticate((await tokenManager.getToken()).token),
).resolves.not.toThrow();
});
});
});
@@ -0,0 +1,80 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { JWKS, JWK, JWT } from 'jose';
import { Config } from '@backstage/config';
import { AuthenticationError } from '@backstage/errors';
import { TokenManager } from './types';
class NoopTokenManager implements TokenManager {
async getToken() {
return { token: '' };
}
async authenticate() {}
}
/**
* Creates and validates tokens for use during backend-to-backend
* authentication.
*
* @public
*/
export class ServerTokenManager implements TokenManager {
private readonly verificationKeys: JWKS.KeyStore;
private readonly signingKey: JWK.Key;
static noop(): TokenManager {
return new NoopTokenManager();
}
static fromConfig(config: Config) {
return new ServerTokenManager(
config
.getConfigArray('backend.auth.keys')
.map(key => key.getString('secret')),
);
}
private constructor(secrets?: string[]) {
if (!secrets?.length) {
throw new Error(
'No secrets provided when constructing ServerTokenManager',
);
}
this.verificationKeys = new JWKS.KeyStore(
secrets.map(k => JWK.asKey({ kty: 'oct', k })),
);
this.signingKey = this.verificationKeys.all()[0];
}
async getToken(): Promise<{ token: string }> {
const jwt = JWT.sign({ sub: 'backstage-server' }, this.signingKey, {
algorithm: 'HS256',
});
return { token: jwt };
}
async authenticate(token: string): Promise<void> {
try {
JWT.verify(token, this.verificationKeys);
} catch (e) {
throw new AuthenticationError('Invalid server token');
}
}
}
@@ -0,0 +1,18 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { ServerTokenManager } from './ServerTokenManager';
export type { TokenManager } from './types';
@@ -0,0 +1,25 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Interface for creating and validating tokens.
*
* @public
*/
export interface TokenManager {
getToken: () => Promise<{ token: string }>;
authenticate: (token: string) => Promise<void>;
}
+22
View File
@@ -1,5 +1,27 @@
# example-backend
## 0.2.54
### Patch Changes
- Updated dependencies
- @backstage/plugin-kubernetes-backend@0.3.19
- @backstage/plugin-tech-insights-backend@0.1.2
- @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.1
- @backstage/plugin-auth-backend@0.4.9
- @backstage/plugin-scaffolder-backend@0.15.14
- @backstage/plugin-catalog-backend@0.18.0
- @backstage/plugin-kafka-backend@0.2.12
- @backstage/backend-common@0.9.11
- @backstage/plugin-azure-devops-backend@0.2.2
- @backstage/plugin-badges-backend@0.1.12
- @backstage/plugin-code-coverage-backend@0.1.15
- @backstage/plugin-jenkins-backend@0.1.8
- @backstage/plugin-proxy-backend@0.2.14
- @backstage/plugin-rollbar-backend@0.1.16
- @backstage/plugin-search-backend@0.2.7
- @backstage/plugin-techdocs-backend@0.10.9
## 0.2.52
### Patch Changes
+19 -19
View File
@@ -1,6 +1,6 @@
{
"name": "example-backend",
"version": "0.2.52",
"version": "0.2.54",
"main": "dist/index.cjs.js",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -24,35 +24,35 @@
"migrate:create": "knex migrate:make -x ts"
},
"dependencies": {
"@backstage/backend-common": "^0.9.10",
"@backstage/backend-common": "^0.9.11",
"@backstage/catalog-client": "^0.5.2",
"@backstage/catalog-model": "^0.9.7",
"@backstage/config": "^0.1.10",
"@backstage/integration": "^0.6.8",
"@backstage/plugin-app-backend": "^0.3.19",
"@backstage/plugin-auth-backend": "^0.4.8",
"@backstage/plugin-azure-devops-backend": "^0.2.1",
"@backstage/plugin-badges-backend": "^0.1.11",
"@backstage/plugin-catalog-backend": "^0.17.4",
"@backstage/plugin-code-coverage-backend": "^0.1.14",
"@backstage/plugin-auth-backend": "^0.4.9",
"@backstage/plugin-azure-devops-backend": "^0.2.2",
"@backstage/plugin-badges-backend": "^0.1.12",
"@backstage/plugin-catalog-backend": "^0.18.0",
"@backstage/plugin-code-coverage-backend": "^0.1.15",
"@backstage/plugin-graphql-backend": "^0.1.9",
"@backstage/plugin-jenkins-backend": "^0.1.7",
"@backstage/plugin-kubernetes-backend": "^0.3.18",
"@backstage/plugin-kafka-backend": "^0.2.11",
"@backstage/plugin-proxy-backend": "^0.2.13",
"@backstage/plugin-rollbar-backend": "^0.1.15",
"@backstage/plugin-scaffolder-backend": "^0.15.13",
"@backstage/plugin-jenkins-backend": "^0.1.8",
"@backstage/plugin-kubernetes-backend": "^0.3.19",
"@backstage/plugin-kafka-backend": "^0.2.12",
"@backstage/plugin-proxy-backend": "^0.2.14",
"@backstage/plugin-rollbar-backend": "^0.1.16",
"@backstage/plugin-scaffolder-backend": "^0.15.14",
"@backstage/plugin-scaffolder-backend-module-rails": "^0.1.7",
"@backstage/plugin-search-backend": "^0.2.6",
"@backstage/plugin-search-backend": "^0.2.7",
"@backstage/plugin-search-backend-node": "^0.4.2",
"@backstage/plugin-search-backend-module-elasticsearch": "^0.0.5",
"@backstage/plugin-search-backend-module-pg": "^0.2.1",
"@backstage/plugin-techdocs-backend": "^0.10.8",
"@backstage/plugin-tech-insights-backend": "^0.1.1",
"@backstage/plugin-techdocs-backend": "^0.10.9",
"@backstage/plugin-tech-insights-backend": "^0.1.2",
"@backstage/plugin-tech-insights-node": "^0.1.0",
"@backstage/plugin-tech-insights-backend-module-jsonfc": "^0.1.0",
"@backstage/plugin-tech-insights-backend-module-jsonfc": "^0.1.1",
"@backstage/plugin-todo-backend": "^0.1.13",
"@gitbeaker/node": "^30.2.0",
"@gitbeaker/node": "^34.6.0",
"@octokit/rest": "^18.5.3",
"azure-devops-node-api": "^11.0.1",
"dockerode": "^3.3.1",
@@ -68,7 +68,7 @@
"winston": "^3.2.1"
},
"devDependencies": {
"@backstage/cli": "^0.9.0",
"@backstage/cli": "^0.9.1",
"@types/dockerode": "^3.3.0",
"@types/express": "^4.17.6",
"@types/express-serve-static-core": "^4.17.5"
+3 -1
View File
@@ -33,6 +33,7 @@ import {
SingleHostDiscovery,
UrlReaders,
useHotMemoize,
ServerTokenManager,
} from '@backstage/backend-common';
import { Config } from '@backstage/config';
import healthcheck from './plugins/healthcheck';
@@ -60,6 +61,7 @@ function makeCreateEnv(config: Config) {
const root = getRootLogger();
const reader = UrlReaders.default({ logger: root, config });
const discovery = SingleHostDiscovery.fromConfig(config);
const tokenManager = ServerTokenManager.noop();
root.info(`Created UrlReader ${reader}`);
@@ -70,7 +72,7 @@ function makeCreateEnv(config: Config) {
const logger = root.child({ type: 'plugin', plugin });
const database = databaseManager.forPlugin(plugin);
const cache = cacheManager.forPlugin(plugin);
return { logger, cache, database, config, reader, discovery };
return { logger, cache, database, config, reader, discovery, tokenManager };
};
}
+6 -1
View File
@@ -59,6 +59,7 @@ export default async function createPlugin({
discovery,
config,
database,
tokenManager,
}: PluginEnvironment) {
// Initialize a connection to a search engine.
const searchEngine = await createSearchEngine({ config, logger, database });
@@ -68,7 +69,10 @@ export default async function createPlugin({
// particular collator gathers entities from the software catalog.
indexBuilder.addCollator({
defaultRefreshIntervalSeconds: 600,
collator: DefaultCatalogCollator.fromConfig(config, { discovery }),
collator: DefaultCatalogCollator.fromConfig(config, {
discovery,
tokenManager,
}),
});
indexBuilder.addCollator({
@@ -76,6 +80,7 @@ export default async function createPlugin({
collator: DefaultTechDocsCollator.fromConfig(config, {
discovery,
logger,
tokenManager,
}),
});
+2
View File
@@ -20,6 +20,7 @@ import {
PluginCacheManager,
PluginDatabaseManager,
PluginEndpointDiscovery,
TokenManager,
UrlReader,
} from '@backstage/backend-common';
@@ -30,4 +31,5 @@ export type PluginEnvironment = {
config: Config;
reader: UrlReader;
discovery: PluginEndpointDiscovery;
tokenManager: TokenManager;
};
+24
View File
@@ -1,5 +1,29 @@
# @backstage/cli
## 0.9.1
### Patch Changes
- dde216acf4: Switch the default test coverage provider from the jest default one to `'v8'`, which provides much better coverage information when using the default Backstage test setup. This is considered a bug fix as the current coverage information is often very inaccurate.
- 719cc87d2f: Disable ES transforms in tests transformed by the `jestSucraseTransform.js`. This is not considered a breaking change since all code is already transpiled this way in the development setup.
- bab752e2b3: Change default port of backend from 7000 to 7007.
This is due to the AirPlay Receiver process occupying port 7000 and preventing local Backstage instances on MacOS to start.
You can change the port back to 7000 or any other value by providing an `app-config.yaml` with the following values:
```
backend:
listen: 0.0.0.0:7123
baseUrl: http://localhost:7123
```
More information can be found here: https://backstage.io/docs/conf/writing
- ee055cf6db: Update the default routes to use id instead of title
- Updated dependencies
- @backstage/errors@0.1.5
## 0.9.0
### Minor Changes
+9 -9
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/cli",
"description": "CLI for developing Backstage plugins and apps",
"version": "0.9.0",
"version": "0.9.1",
"private": false,
"publishConfig": {
"access": "public"
@@ -31,7 +31,7 @@
"@backstage/cli-common": "^0.1.6",
"@backstage/config": "^0.1.11",
"@backstage/config-loader": "^0.8.0",
"@backstage/errors": "^0.1.4",
"@backstage/errors": "^0.1.5",
"@backstage/types": "^0.1.1",
"@hot-loader/react-dom": "^16.13.0",
"@lerna/package-graph": "^4.0.0",
@@ -90,7 +90,7 @@
"postcss": "^8.1.0",
"process": "^0.11.10",
"react": "^16.0.0",
"react-dev-utils": "^11.0.4",
"react-dev-utils": "^12.0.0-next.47",
"react-hot-loader": "^4.12.21",
"recursive-readdir": "^2.2.2",
"replace-in-file": "^6.0.0",
@@ -102,7 +102,7 @@
"rollup-pluginutils": "^2.8.2",
"run-script-webpack-plugin": "^0.0.11",
"semver": "^7.3.2",
"style-loader": "^1.2.1",
"style-loader": "^3.3.1",
"sucrase": "^3.20.2",
"tar": "^6.1.2",
"terser-webpack-plugin": "^5.1.3",
@@ -117,13 +117,13 @@
"yn": "^4.0.0"
},
"devDependencies": {
"@backstage/backend-common": "^0.9.10",
"@backstage/backend-common": "^0.9.11",
"@backstage/config": "^0.1.11",
"@backstage/core-components": "^0.7.4",
"@backstage/core-plugin-api": "^0.2.0",
"@backstage/core-app-api": "^0.1.21",
"@backstage/core-components": "^0.7.5",
"@backstage/core-plugin-api": "^0.2.1",
"@backstage/core-app-api": "^0.1.23",
"@backstage/dev-utils": "^0.2.13",
"@backstage/test-utils": "^0.1.22",
"@backstage/test-utils": "^0.1.23",
"@backstage/theme": "^0.2.13",
"@types/diff": "^5.0.0",
"@types/express": "^4.17.6",
+9
View File
@@ -20,8 +20,17 @@ import { buildBundle } from '../../lib/bundler';
import { parseParallel, PARALLEL_ENV_VAR } from '../../lib/parallel';
import { loadCliConfig } from '../../lib/config';
import { paths } from '../../lib/paths';
import chalk from 'chalk';
export default async (cmd: Command) => {
if (cmd.lax) {
console.warn(
chalk.yellow(
`[DEPRECATED] - The --lax option is deprecated and will be removed in the future. Please open an issue towards https://github.com/backstage/backstage that describes your use-case if you need the flag to stay around.`,
),
);
}
const { name } = await fs.readJson(paths.resolveTarget('package.json'));
await buildBundle({
entry: 'src/index',
@@ -1,95 +0,0 @@
/*
* Copyright 2020 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Command } from 'commander';
import { yellow } from 'chalk';
import fs from 'fs-extra';
import { join as joinPath, relative as relativePath } from 'path';
import { createDistWorkspace } from '../../lib/packager';
import { paths } from '../../lib/paths';
import { run } from '../../lib/run';
import { parseParallel, PARALLEL_ENV_VAR } from '../../lib/parallel';
const PKG_PATH = 'package.json';
export default async (cmd: Command) => {
// Skip the preparation steps if we're being asked for help
if (cmd.args.includes('--help')) {
await run('docker', ['image', 'build', '--help']);
return;
}
console.warn(
yellow(`
The backend:build-image command is deprecated and will be removed in the future.
Please use the backend:bundle command instead along with your own Docker setup.
https://backstage.io/docs/deployment/docker
`),
);
const pkgPath = paths.resolveTarget(PKG_PATH);
const pkg = await fs.readJson(pkgPath);
const appConfigs = await findAppConfigs();
const npmrc = (await fs.pathExists(paths.resolveTargetRoot('.npmrc')))
? ['.npmrc']
: [];
const tempDistWorkspace = await createDistWorkspace([pkg.name], {
buildDependencies: Boolean(cmd.build),
files: [
'package.json',
'yarn.lock',
...npmrc,
...appConfigs,
{ src: paths.resolveTarget('Dockerfile'), dest: 'Dockerfile' },
],
parallel: parseParallel(process.env[PARALLEL_ENV_VAR]),
skeleton: 'skeleton.tar',
});
console.log(`Dist workspace ready at ${tempDistWorkspace}`);
// all args are forwarded to docker build
await run('docker', ['image', 'build', '.', ...cmd.args], {
cwd: tempDistWorkspace,
});
await fs.remove(tempDistWorkspace);
};
/**
* Find all config files to copy into the image
*/
async function findAppConfigs(): Promise<string[]> {
const files = [];
for (const name of await fs.readdir(paths.targetRoot)) {
if (name.startsWith('app-config.') && name.endsWith('.yaml')) {
files.push(name);
}
}
if (paths.targetRoot !== paths.targetDir) {
const dirPath = relativePath(paths.targetRoot, paths.targetDir);
for (const name of await fs.readdir(paths.targetDir)) {
if (name.startsWith('app-config.') && name.endsWith('.yaml')) {
files.push(joinPath(dirPath, name));
}
}
}
return files;
}
+5 -12
View File
@@ -30,7 +30,10 @@ export function registerCommands(program: CommanderStatic) {
.command('app:build')
.description('Build an app for a production release')
.option('--stats', 'Write bundle stats to output directory')
.option('--lax', 'Do not require environment variables to be set')
.option(
'--lax',
'[DEPRECATED] - Do not require environment variables to be set',
)
.option(...configOption)
.action(lazy(() => import('./app/build').then(m => m.default)));
@@ -55,16 +58,6 @@ export function registerCommands(program: CommanderStatic) {
)
.action(lazy(() => import('./backend/bundle').then(m => m.default)));
program
.command('backend:build-image')
.allowUnknownOption(true)
.helpOption(', --backstage-cli-help') // Let docker handle --help
.option('--build', 'Build packages before packing them into the image')
.description(
'Bundles the package into a docker image. This command is deprecated and will be removed.',
)
.action(lazy(() => import('./backend/buildImage').then(m => m.default)));
program
.command('backend:dev')
.description('Start local development server with HMR for the backend')
@@ -115,7 +108,7 @@ export function registerCommands(program: CommanderStatic) {
program
.command('remove-plugin')
.description('Removes plugin in the current repository')
.description('[DEPRECATED] - Removes plugin in the current repository')
.action(
lazy(() => import('./remove-plugin/removePlugin').then(m => m.default)),
);
@@ -184,6 +184,12 @@ export const removeReferencesFromAppPackage = async (
};
export default async () => {
console.warn(
chalk.yellow(
'[DEPRECATED] - The remove-plugin command is deprecated and will be removed in the future.',
),
);
const questions: Question[] = [
{
type: 'input',
+9
View File
@@ -1,5 +1,14 @@
# @backstage/codemods
## 0.1.23
### Patch Changes
- Updated dependencies
- @backstage/core-app-api@0.1.23
- @backstage/core-plugin-api@0.2.1
- @backstage/core-components@0.7.5
## 0.1.22
### Patch Changes
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/codemods",
"description": "A collection of codemods for Backstage projects",
"version": "0.1.22",
"version": "0.1.23",
"private": false,
"publishConfig": {
"access": "public",
+2 -2
View File
@@ -41,10 +41,10 @@
"json-schema": "^0.4.0",
"json-schema-merge-allof": "^0.8.1",
"json-schema-traverse": "^1.0.0",
"node-fetch": "^2.6.1",
"typescript-json-schema": "^0.51.0",
"yaml": "^1.9.2",
"yup": "^0.32.9",
"node-fetch": "2.6.5"
"yup": "^0.32.9"
},
"devDependencies": {
"@types/jest": "^26.0.7",
+81
View File
@@ -1,5 +1,86 @@
# @backstage/core-app-api
## 0.1.23
### Patch Changes
- bab752e2b3: Change default port of backend from 7000 to 7007.
This is due to the AirPlay Receiver process occupying port 7000 and preventing local Backstage instances on MacOS to start.
You can change the port back to 7000 or any other value by providing an `app-config.yaml` with the following values:
```
backend:
listen: 0.0.0.0:7123
baseUrl: http://localhost:7123
```
More information can be found here: https://backstage.io/docs/conf/writing
- 000190de69: The `ApiRegistry` from `@backstage/core-app-api` class has been deprecated and will be removed in a future release. To replace it, we have introduced two new helpers that are exported from `@backstage/test-utils`, namely `TestApiProvider` and `TestApiRegistry`.
These two new helpers are more tailored for writing tests and development setups, as they allow for partial implementations of each of the APIs.
When migrating existing code it is typically best to prefer usage of `TestApiProvider` when possible, so for example the following code:
```tsx
render(
<ApiProvider
apis={ApiRegistry.from([
[identityApiRef, mockIdentityApi as unknown as IdentityApi]
])}
>
{...}
</ApiProvider>
)
```
Would be migrated to this:
```tsx
render(
<TestApiProvider apis={[[identityApiRef, mockIdentityApi]]}>
{...}
</TestApiProvider>
)
```
In cases where the `ApiProvider` is used in a more standalone way, for example to reuse a set of APIs across multiple tests, the `TestApiRegistry` can be used instead. Note that the `TestApiRegistry` only has a single static factory method, `.from()`, and it is slightly different from the existing `.from()` method on `ApiRegistry` in that it doesn't require the API pairs to be wrapped in an outer array.
Usage that looks like this:
```ts
const apis = ApiRegistry.with(
identityApiRef,
mockIdentityApi as unknown as IdentityApi,
).with(configApiRef, new ConfigReader({}));
```
OR like this:
```ts
const apis = ApiRegistry.from([
[identityApiRef, mockIdentityApi as unknown as IdentityApi],
[configApiRef, new ConfigReader({})],
]);
```
Would be migrated to this:
```ts
const apis = TestApiRegistry.from(
[identityApiRef, mockIdentityApi],
[configApiRef, new ConfigReader({})],
);
```
If your app is still using the `ApiRegistry` to construct the `apis` for `createApp`, we recommend that you move over to use the new method of supplying API factories instead, using `createApiFactory`.
- Updated dependencies
- @backstage/core-plugin-api@0.2.1
- @backstage/core-components@0.7.5
## 0.1.22
### Patch Changes
+5 -5
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/core-app-api",
"description": "Core app API used by Backstage apps",
"version": "0.1.22",
"version": "0.1.23",
"private": false,
"publishConfig": {
"access": "public",
@@ -30,9 +30,9 @@
},
"dependencies": {
"@backstage/app-defaults": "^0.1.1",
"@backstage/core-components": "^0.7.4",
"@backstage/core-components": "^0.7.5",
"@backstage/config": "^0.1.11",
"@backstage/core-plugin-api": "^0.2.0",
"@backstage/core-plugin-api": "^0.2.1",
"@backstage/theme": "^0.2.13",
"@backstage/types": "^0.1.1",
"@backstage/version-bridge": "^0.1.0",
@@ -47,8 +47,8 @@
"zen-observable": "^0.8.15"
},
"devDependencies": {
"@backstage/cli": "^0.9.0",
"@backstage/test-utils": "^0.1.22",
"@backstage/cli": "^0.9.1",
"@backstage/test-utils": "^0.1.23",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/react-hooks": "^7.0.2",
@@ -349,6 +349,78 @@ describe('Integration Test', () => {
});
});
it('getFeatureFlags should return feature flags', async () => {
const storageFlags = new LocalStorageFeatureFlags();
jest.spyOn(storageFlags, 'registerFlag');
const apis = [
noOpAnalyticsApi,
createApiFactory({
api: featureFlagsApiRef,
deps: { configApi: configApiRef },
factory() {
return storageFlags;
},
}),
];
const app = new AppManager({
apis,
defaultApis: [],
themes: [
{
id: 'light',
title: 'Light Theme',
variant: 'light',
Provider: ({ children }) => <>{children}</>,
},
],
icons,
plugins: [
createPlugin({
id: 'test',
featureFlags: [
{
name: 'foo',
},
],
register: p => p.featureFlags.register('name'),
}),
],
components,
configLoader: async () => [],
bindRoutes: ({ bind }) => {
bind(plugin1.externalRoutes, {
extRouteRef1: plugin1RouteRef,
extRouteRef2: plugin2RouteRef,
});
},
});
const Provider = app.getProvider();
const Router = app.getRouter();
await renderWithEffects(
<Provider>
<Router>
<Routes>
<Route path="/" element={<ExposedComponent />} />
<Route path="/foo" element={<HiddenComponent />} />
</Routes>
</Router>
</Provider>,
);
expect(storageFlags.registerFlag).toHaveBeenCalledWith({
name: 'name',
pluginId: 'test',
});
expect(storageFlags.registerFlag).toHaveBeenCalledWith({
name: 'foo',
pluginId: 'test',
});
});
it('should track route changes via analytics api', async () => {
const mockAnalyticsApi = new MockAnalyticsApi();
const apis = [createApiFactory(analyticsApiRef, mockAnalyticsApi)];
+19 -10
View File
@@ -272,17 +272,26 @@ export class AppManager implements BackstageApp {
const featureFlagsApi = this.getApiHolder().get(featureFlagsApiRef)!;
for (const plugin of this.plugins.values()) {
for (const output of plugin.output()) {
switch (output.type) {
case 'feature-flag': {
featureFlagsApi.registerFlag({
name: output.name,
pluginId: plugin.getId(),
});
break;
if ('getFeatureFlags' in plugin) {
for (const flag of plugin.getFeatureFlags()) {
featureFlagsApi.registerFlag({
name: flag.name,
pluginId: plugin.getId(),
});
}
} else {
for (const output of plugin.output()) {
switch (output.type) {
case 'feature-flag': {
featureFlagsApi.registerFlag({
name: output.name,
pluginId: plugin.getId(),
});
break;
}
default:
break;
}
default:
break;
}
}
}
@@ -100,23 +100,55 @@ describe('RouteResolver', () => {
expect(r.resolve(externalRef4, '/')?.({ x: '6x' })).toBe(undefined);
});
it('should resolve an absolute route and an app base path', () => {
it('should resolve an absolute route and sub route with an app base path', () => {
const r = new RouteResolver(
new Map([[ref1, '/my-route']]),
new Map(),
[{ routeRefs: new Set([ref1]), path: '/my-route', ...rest }],
new Map<RouteRef, string>([
[ref2, '/my-parent/:x'],
[ref1, '/my-route'],
]),
new Map<RouteRef, RouteRef>([[ref1, ref2]]),
[
{
routeRefs: new Set([ref2]),
path: '/my-parent/:x',
...rest,
children: [
MATCH_ALL_ROUTE,
{ routeRefs: new Set([ref1]), path: '/my-route', ...rest },
],
},
],
new Map(),
'/base',
);
expect(r.resolve(ref1, '/')?.()).toBe('/base/my-route');
expect(r.resolve(ref2, '/')?.({ x: '1x' })).toBe(undefined);
expect(r.resolve(subRef1, '/')?.()).toBe('/base/my-route/foo');
expect(r.resolve(subRef2, '/')?.({ a: '2a' })).toBe(
'/base/my-route/foo/2a',
expect(r.resolve(ref1, '/my-parent/1x')?.()).toBe(
'/base/my-parent/1x/my-route',
);
expect(r.resolve(ref1, '/base/my-parent/1x')?.()).toBe(
'/base/my-parent/1x/my-route',
);
expect(r.resolve(ref2, '/')?.({ x: '1x' })).toBe('/base/my-parent/1x');
expect(r.resolve(ref2, '/base')?.({ x: '1x' })).toBe('/base/my-parent/1x');
expect(r.resolve(ref3, '/')?.({ y: '1y' })).toBe(undefined);
expect(r.resolve(subRef1, '/my-parent/2x')?.()).toBe(
'/base/my-parent/2x/my-route/foo',
);
expect(r.resolve(subRef1, '/base/my-parent/2x')?.()).toBe(
'/base/my-parent/2x/my-route/foo',
);
expect(r.resolve(subRef2, '/my-parent/3x')?.({ a: '2a' })).toBe(
'/base/my-parent/3x/my-route/foo/2a',
);
expect(r.resolve(subRef2, '/base/my-parent/3x')?.({ a: '2a' })).toBe(
'/base/my-parent/3x/my-route/foo/2a',
);
expect(r.resolve(subRef3, '/')?.({ x: '5x' })).toBe(
'/base/my-parent/5x/bar',
);
expect(r.resolve(subRef4, '/')?.({ x: '6x', a: '4a' })).toBe(
'/base/my-parent/6x/bar/4a',
);
expect(r.resolve(subRef3, '/')?.({ x: '3x' })).toBe(undefined);
expect(r.resolve(subRef4, '/')?.({ x: '4x', a: '4a' })).toBe(undefined);
expect(r.resolve(externalRef1, '/')?.()).toBe(undefined);
expect(r.resolve(externalRef2, '/')?.()).toBe(undefined);
expect(r.resolve(externalRef3, '/')?.({ x: '5x' })).toBe(undefined);
@@ -207,6 +207,20 @@ export class RouteResolver {
return undefined;
}
// The location that we get passed in uses the full path, so start by trimming off
// the app base path prefix in case we're running the app on a sub-path.
let relativeSourceLocation: Parameters<typeof matchRoutes>[1];
if (typeof sourceLocation === 'string') {
relativeSourceLocation = this.trimPath(sourceLocation);
} else if (sourceLocation.pathname) {
relativeSourceLocation = {
...sourceLocation,
pathname: this.trimPath(sourceLocation.pathname),
};
} else {
relativeSourceLocation = sourceLocation;
}
// Next we figure out the base path, which is the combination of the common parent path
// between our current location and our target location, as well as the additional path
// that is the difference between the parent path and the base of our target location.
@@ -214,7 +228,7 @@ export class RouteResolver {
this.appBasePath +
resolveBasePath(
targetRef,
sourceLocation,
relativeSourceLocation,
this.routePaths,
this.routeParents,
this.routeObjects,
@@ -225,4 +239,15 @@ export class RouteResolver {
};
return routeFunc;
}
private trimPath(targetPath: string) {
if (!targetPath) {
return targetPath;
}
if (targetPath.startsWith(this.appBasePath)) {
return targetPath.slice(this.appBasePath.length);
}
return targetPath;
}
}
+9
View File
@@ -1,5 +1,14 @@
# @backstage/core-components
## 0.7.5
### Patch Changes
- 157530187a: Pin sidebar by default for easier navigation
- Updated dependencies
- @backstage/errors@0.1.5
- @backstage/core-plugin-api@0.2.1
## 0.7.4
### Patch Changes
+6 -6
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/core-components",
"description": "Core components used by Backstage plugins and apps",
"version": "0.7.4",
"version": "0.7.5",
"private": false,
"publishConfig": {
"access": "public",
@@ -30,8 +30,8 @@
},
"dependencies": {
"@backstage/config": "^0.1.11",
"@backstage/core-plugin-api": "^0.2.0",
"@backstage/errors": "^0.1.4",
"@backstage/core-plugin-api": "^0.2.1",
"@backstage/errors": "^0.1.5",
"@backstage/theme": "^0.2.13",
"@material-table/core": "^3.1.0",
"@material-ui/core": "^4.12.2",
@@ -67,9 +67,9 @@
"zen-observable": "^0.8.15"
},
"devDependencies": {
"@backstage/core-app-api": "^0.1.21",
"@backstage/cli": "^0.9.0",
"@backstage/test-utils": "^0.1.22",
"@backstage/core-app-api": "^0.1.23",
"@backstage/cli": "^0.9.1",
"@backstage/test-utils": "^0.1.23",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/react-hooks": "^7.0.2",
+8
View File
@@ -1,5 +1,13 @@
# @backstage/core-plugin-api
## 0.2.1
### Patch Changes
- 950b36393c: Deprecated `register` option of `createPlugin` and the `outputs` methods of the plugin instance.
Introduces the `featureFlags` property to define your feature flags instead.
## 0.2.0
### Minor Changes
+9 -2
View File
@@ -258,6 +258,7 @@ export type BackstagePlugin<
getId(): string;
output(): PluginOutput[];
getApis(): Iterable<AnyApiFactory>;
getFeatureFlags(): Iterable<PluginFeatureFlagConfig>;
provide<T>(extension: Extension<T>): T;
routes: Routes;
externalRoutes: ExternalRoutes;
@@ -463,7 +464,7 @@ export type FeatureFlag = {
pluginId: string;
};
// @public
// @public @deprecated
export type FeatureFlagOutput = {
type: 'feature-flag';
name: string;
@@ -682,14 +683,20 @@ export type PluginConfig<
register?(hooks: PluginHooks): void;
routes?: Routes;
externalRoutes?: ExternalRoutes;
featureFlags?: PluginFeatureFlagConfig[];
};
// @public
export type PluginFeatureFlagConfig = {
name: string;
};
// @public @deprecated
export type PluginHooks = {
featureFlags: FeatureFlagsHooks;
};
// @public
// @public @deprecated
export type PluginOutput = FeatureFlagOutput;
// @public
+4 -4
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/core-plugin-api",
"description": "Core API used by Backstage plugins",
"version": "0.2.0",
"version": "0.2.1",
"private": false,
"publishConfig": {
"access": "public",
@@ -43,9 +43,9 @@
"zen-observable": "^0.8.15"
},
"devDependencies": {
"@backstage/cli": "^0.9.0",
"@backstage/core-app-api": "^0.1.21",
"@backstage/test-utils": "^0.1.22",
"@backstage/cli": "^0.9.1",
"@backstage/core-app-api": "^0.1.23",
"@backstage/test-utils": "^0.1.23",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/react-hooks": "^7.0.2",
@@ -0,0 +1,89 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { createPlugin } from './Plugin';
describe('Plugin Feature Flag', () => {
it('should be able to register and receive feature flags', () => {
expect(
createPlugin({
id: 'test',
featureFlags: [{ name: 'test' }],
}).getFeatureFlags(),
).toEqual([{ name: 'test' }]);
expect(
createPlugin({
id: 'test',
register({ featureFlags }) {
featureFlags.register('blob');
},
}).getFeatureFlags(),
).toEqual([{ name: 'blob' }]);
expect(
createPlugin({
id: 'test',
register({ featureFlags }) {
featureFlags.register('blob');
},
featureFlags: [{ name: 'test' }],
}).getFeatureFlags(),
).toEqual([{ name: 'test' }, { name: 'blob' }]);
expect(
createPlugin({
id: 'test',
}).getFeatureFlags(),
).toEqual([]);
/* deprecated tests */
expect(
createPlugin({
id: 'test',
featureFlags: [{ name: 'test' }],
}).output(),
).toEqual([{ name: 'test', type: 'feature-flag' }]);
expect(
createPlugin({
id: 'test',
register({ featureFlags }) {
featureFlags.register('blob');
},
}).output(),
).toEqual([{ name: 'blob', type: 'feature-flag' }]);
expect(
createPlugin({
id: 'test',
register({ featureFlags }) {
featureFlags.register('blob');
},
featureFlags: [{ name: 'test' }],
}).output(),
).toEqual([
{ name: 'test', type: 'feature-flag' },
{ name: 'blob', type: 'feature-flag' },
]);
expect(
createPlugin({
id: 'test',
}).output(),
).toEqual([]);
});
});

Some files were not shown because too many files have changed in this diff Show More