Merge remote-tracking branch 'upstream/master' into mcalus3/add-catalog-import-plugin

This commit is contained in:
Marek Calus
2020-11-29 18:18:11 +01:00
137 changed files with 1766 additions and 495 deletions
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-app-backend': patch
---
Warn if the app-backend can't start-up because the static directory that should be served is unavailable.
-7
View File
@@ -1,7 +0,0 @@
---
'@backstage/plugin-api-docs': minor
---
APIs now have real entity pages that are customizable in the app.
Therefore the old entity page from this plugin is removed.
See the `packages/app` on how to create and customize the API entity page.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/backend-common': patch
---
Added support for passing false as a CSP field value, to drop it from the defaults in the backend
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/cli': patch
---
Make versions:bump install new versions of dependencies that were within the specified range
-6
View File
@@ -1,6 +0,0 @@
---
'@backstage/catalog-model': patch
'@backstage/plugin-catalog-backend': patch
---
Start emitting all known relation types from the core entity kinds, based on their spec data.
+1
View File
@@ -230,3 +230,4 @@ yaml
Zalando
Zhou
Zolotusky
zoomable
+1 -1
View File
@@ -19,4 +19,4 @@ jobs:
# Calls out to `changeset version`, but also runs prettier
version: yarn release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }}
+2 -2
View File
@@ -1,6 +1,6 @@
# Process for becoming a maintainer
## Your organization is not yet a maintainer
## a) Your organization is not yet a maintainer
- Express interest to the sponsors that your organization is interested in becoming a maintainer. Becoming a maintainer generally means that you are going to be spending substantial time on Backstage for the foreseeable future. You should have domain expertise and be extremely proficient in TypeScript.
- We will expect you to start contributing increasingly complicated PRs, under the guidance of the existing maintainers.
@@ -8,7 +8,7 @@
- As you gain experience with the code base and our standards, we will ask you to do code reviews for incoming PRs.
- After a period of approximately 2-3 months of working together and making sure we see eye to eye, the existing sponsors and maintainers will confer and decide whether to grant maintainer status or not. We make no guarantees on the length of time this will take, but 2-3 months is the approximate goal.
## Your organization is currently a maintainer
## b) Your organization is currently a maintainer
To become a maintainer you need to demonstrate the following:
+2 -2
View File
@@ -66,8 +66,8 @@ sentry:
rollbar:
organization: my-company
accountToken:
$env: ROLLBAR_ACCOUNT_TOKEN
# NOTE: The rollbar-backend & accountToken key may be deprecated in the future (replaced by a proxy config)
accountToken: my-rollbar-account-token
lighthouse:
baseUrl: http://localhost:3003
+103 -44
View File
@@ -6,57 +6,71 @@ description: Documentation on Auth backend classes
## How Does Authentication Work?
The Backstage application can use various authentication providers for
authentication. A provider has to implement an `AuthProviderRouteHandlers`
interface for handling authentication. This interface consists of four methods.
Each of these methods is hosted at an endpoint `/auth/[provider]/method`, where
`method` performs a certain operation as follows:
The Backstage application can use various external authentication providers for
authentication. An external provider is wrapped using an
`AuthProviderRouteHandlers` interface for handling authentication. This
interface consists of four methods. Each of these methods is hosted at an
endpoint (by default) `/api/auth/[provider]/method`, where `method` performs a
certain operation as follows:
```
/auth/[provider]/start -> start
/auth/[provider]/handler/frame -> frameHandler
/auth/[provider]/refresh -> refresh
/auth/[provider]/logout -> logout
/auth/[provider]/start -> Initiate a login from the web page
/auth/[provider]/handler/frame -> Handle a finished authentication operation
/auth/[provider]/refresh -> Refresh the validity of a login
/auth/[provider]/logout -> Log out a logged-in user
```
For more information on how these methods are used and for which purpose, refer
to the [OAuth documentation](oauth.md).
The flow is as follows:
For details on the parameters, input and output conditions for each method,
refer to the type documentation under
`plugins/auth-backend/src/providers/types.ts`.
1. A user attempts to sign in.
2. A popup window is opened, pointing to the `auth` endpoint. That endpoint does
initial preparations and then re-directs the user to an external
authenticator, still inside the popup.
3. The authenticator validates the user and returns the result of the validation
(success OR failure), to the wrapper's endpoint (`handler/frame`).
4. The `handler/frame` rendered b´webpage will issue the appropriate response to
the webpage that opened the popup window, and the popup is closed.
5. The user signs out by clicking on a UI interface and the webpage makes a
request to logout the user.
There are currently two different classes for two authentication mechanisms that
implement this interface: an `OAuthAdapter` for [OAuth](https://oauth.net/2/)
based mechanisms and a `SAMLAuthProvider` for
[SAML](http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0.html)
based mechanisms.
[SAML](http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0.html).
### OAuth mechanisms
If you do not have an `OAuth2` or `SAML` based authentication provider, look in
the section [below](#implementing-your-own-auth-wrapper).
### OAuth Mechanisms
For more information on how these methods are used and for which purpose, refer
to the [OAuth documentation](oauth.md).
Currently OAuth is assumed to be the de facto authentication mechanism for
Backstage based applications.
Backstage comes with a "batteries-included" set of supported commonly used OAuth
providers: Okta, GitHub, Google, GitLab, and a generic OAuth2 provider.
providers: Okta, GitHub, Google, GitLab, and a generic OAuth2 provider. For a
list of available providers, look at the available wrappers in
`backstage/plugins/auth-backend/src/providers/`.
All of these use the authorization flow of OAuth2 to implement authentication.
All of these use the **authorization flow** of OAuth2 to implement
authentication.
If your authentication provider is any of the above mentioned (except generic
OAuth2) providers, you can configure them by setting the right variables in
`app-config.yaml` under the `auth` section.
If your authentication provider is any of the above mentioned providers, you can
configure them by setting the right variables in `app-config.yaml` under the
`auth` section.
### Configuration
Each authentication provider (except SAML) needs five parameters: an OAuth
client ID, a client secret, an authorization endpoint and 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.
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.
These values are configured via the `app-config.yaml` present in the root of
your app folder.
@@ -85,20 +99,60 @@ auth:
...
```
## Technical Notes
## Implementing Your Own Auth Wrapper
### OAuthEnvironmentHandler
The core interface of any auth wrapper is the `AuthProviderRouteHandlers`
interface. This interface has four methods corresponding to the API described in
the initial section. Any auth wrapper will have to implement this interface.
The concept of an "env" is core to the way the auth backend works. It uses an
When initiating a login, a pop-up window is created by the frontend, to allow
the user to initiate a login. This login request is done to the `/start`
endpoint which is handled by the `start` method.
The `start` method re-directs to the external auth provider who authenticates
the request and re-directs the request to the `/frame/handler` endpoint, which
is handled by the `frameHandler` method.
The `frameHandler` returns an HTML response, containing a script that does a
`postMessage` to the frontend's window, containing the result of the request.
The `WebMessageResponse` type is the message sent by the `postMessage` to the
frontend.
A `postMessageResponse` utility function wraps the logic of generating a
`postMessage` response that ensures that CORS is successfully handled. This
function takes an `express.Response`, a `WebMessageResponse` and the URL of the
frontend (`appOrigin`) as parameters and return an HTML page with the script and
the message.
### OAuth Wrapping Interfaces.
Each OAuth external provider is supported by a corresponding
[Passport](https://github.com/jaredhanson/passport) strategy. For a generic
OAuth2 provider, passport has a `passport-oauth2` strategy. The strategy class
handles the implementation details of working with each provider.
Each strategy is wrapped by an `OAuthHandlers` interface.
This interface cannot be directly used as an Express HTTP request handler. To do
so, `OAuthHandlers` are wrapped in an `OAuthAdapter`, which implements the
`AuthProviderRouterHandlers` interface.
#### Env
The concept of an `env` is core to the way the auth backend works. It uses an
`env` query parameter to identify the environment in which the application is
running (`development`, `staging`, `production`, etc). Each runtime can support
multiple environments at the same time and the right handler for each request is
identified and dispatched to based on the `env` parameter. All
`AuthProviderRouteHandlers` are wrapped within an `OAuthEnvironmentHandler`.
running (`development`, `staging`, `production`, etc). Each runtime can
simultaneously support multiple environments at the same time and the right
handler for each request is identified and dispatched to, based on the `env`
parameter.
To instantiate multiple OAuth providers for different environments, use
`OAuthEnvironmentHandler` is a utility wrapper for an `OAuthHandlers` that
implements the `AuthProviderRouteHandlers` interface while supporting multiple
`env`s.
To instantiate OAuth providers (the same but for different environments), use
`OAuthEnvironmentHandler.mapConfig`. It's a helper to iterate over a
configuration object that is a map of environment to configurations. See one of
configuration object that is a map of environments to configurations. See one of
the existing OAuth providers for an example of how it is used.
Given the following configuration:
@@ -113,13 +167,18 @@ production:
```
The `OAuthEnvironmentHandler.mapConfig(config, envConfig => ...)` call will
split the `config` by the top level `development` and `production` keys, and
pass on each block as `envConfig`.
split the config by the top level `development` and `production` keys, and pass
on each block as `envConfig`.
For a list of currently available providers, look in the `factories` module
located in `plugins/auth-backend/src/providers/factories.ts`
For convenience, the `AuthProviderFactory` is a factory function that has to be
implemented which can then generate a `AuthProviderRouteHandlers` for a given
provider.
### OAuth2 provider
All of the supported providers provide an `AuthProviderFactory` that returns an
`OAuthEnvironmentHandler`, capable of handling authentication for multiple
environments.
### OAuth2 Provider
The `oauth2` provider abstracts a generic **OAuth2 + OIDC** based authentication
provider. What this means is that after the application has been given
@@ -381,7 +381,7 @@ spec:
type: website
lifecycle: production
owner: artist-relations@example.com
implementsApis:
providesApis:
- artist-api
```
@@ -445,12 +445,35 @@ group of people in an organizational structure.
### `spec.implementsApis` [optional]
**NOTE**: This field was marked for deprecation on Nov 25nd, 2020. It will be
removed entirely from the model on Dec 14th, 2020 in the repository and will not
be present in released packages following the next release after that. Please
update your code to not consume this field before the removal date.
Links APIs that are implemented by the component, e.g. `artist-api`. This field
is optional.
The software catalog expects a list of one or more strings that references the
names of other entities of the `kind` `API`.
This field has the same behavior as `spec.providesApis`.
### `spec.providesApis` [optional]
Links APIs that are provided by the component, e.g. `artist-api`. This field is
optional.
The software catalog expects a list of one or more strings that references the
names of other entities of the `kind` `API`.
### `spec.consumesApis` [optional]
Links APIs that are consumed by the component, e.g. `artist-api`. This field is
optional.
The software catalog expects a list of one or more strings that references the
names of other entities of the `kind` `API`.
## Kind: Template
Describes the following entity kind:
+2 -2
View File
@@ -51,7 +51,7 @@ spec:
type: service
lifecycle: experimental
owner: group:pet-managers
implementsApis:
providesApis:
- petstore
- internal/streetlights
- hello-world
@@ -66,7 +66,7 @@ catalog that is of kind `Group`, namespace `default` (which, actually, also can
be left out in its own yaml file because that's the default value there too),
and name `pet-managers`.
The entries in `implementsApis` are also references. In this case, none of them
The entries in `providesApis` are also references. In this case, none of them
needs to specify a kind since we know from the context that that's the only kind
that's supported here. The second entry specifies a namespace but the other ones
don't, and in this context, the default is to refer to the same namespace as the
@@ -45,17 +45,28 @@ entity, but there will always be one ultimate owner.
This relation is commonly generated based on `spec.owner` of the owned entity,
where present.
### `consumesApi` and `providesApi`
### `providesApi` and `apiProvidedBy`
A relation with an [API](descriptor-format.md#kind-api) entity, typically from a
[Component](descriptor-format.md#kind-component) or
[System](descriptor-format.md#kind-system).
These relations express that a component or system either exposes an API -
meaning that it hosts callable endpoints from which you can consume that API -
or that they are dependent on being able to consume that API.
These relations express that a component or system exposes an API - meaning that
it hosts callable endpoints from which you can consume that API.
This relation is commonly generated based on `spec.implementsApis` of the
This relation is commonly generated based on `spec.providesApis` of the
component or system in question.
### `consumesApi` and `apiConsumedBy`
A relation with an [API](descriptor-format.md#kind-api) entity, typically from a
[Component](descriptor-format.md#kind-component) or
[System](descriptor-format.md#kind-system).
These relations express that a component or system consumes an API - meaning
that it depends on endpoints of the API.
This relation is commonly generated based on `spec.consumesApis` of the
component or system in question.
### `dependsOn` and `dependencyOf`
+2 -2
View File
@@ -9,7 +9,7 @@ description: Documentation on TechDocs Architecture
When you deploy Backstage (with TechDocs enabled by default), you get a basic
out-of-the box experience.
![TechDocs Architecture diagram](../../assets/techdocs/architecture-basic.drawio.svg)
<img data-zoomable src="../../assets/techdocs/architecture-basic.drawio.svg" alt="TechDocs Architecture diagram" />
> Note: See below for our recommended deployment architecture which takes care
> of stability, scalability and speed.
@@ -43,7 +43,7 @@ channel to talk about it.
This is how we recommend deploying TechDocs in production environment.
![TechDocs Architecture diagram](../../assets/techdocs/architecture-recommended.drawio.svg)
<img data-zoomable src="../../assets/techdocs/architecture-recommended.drawio.svg" alt="TechDocs Architecture diagram" />
The key difference in the recommended deployment approach is where the docs are
built.
+5 -5
View File
@@ -79,11 +79,11 @@ want to ensure some stability.
### [`cli`](https://github.com/backstage/backstage/tree/master/packages/cli/)
The main toolchain used for Backstage development. The interface that is
considered for stability are the various commands and options passed to those
commands, as well as the environment variables read by the CLI. The build output
may change over time and is not considered a breaking change unless it is likely
to affect external tooling.
The main toolchain used for Backstage development. The various CLI commands and
options passed to those commands, as well as the environment variables read by
the CLI, are considered to be the interface that the stability index refers to.
The build output may change over time and is not considered a breaking change
unless it is likely to affect external tooling.
Stability: `2`
+5
View File
@@ -204,3 +204,8 @@ For more information about custom pages, click [here](https://docusaurus.io/docs
# Full Documentation
Full documentation can be found on the [website](https://docusaurus.io/).
## Additional notes
- If you want to make images zoomable on click, add the `data-zoomable` attribute to your `img` element.
- In a docs or blog `.md` file, convert `![This is image](/microsite/static/img/code.png)` syntax to `<img data-zoomable src="/microsite/static/img/code.png" alt="This is image" />`
+5 -1
View File
@@ -86,7 +86,11 @@ const siteConfig = {
},
// Add custom scripts here that would be placed in <script> tags.
scripts: ['https://buttons.github.io/buttons.js'],
scripts: [
'https://buttons.github.io/buttons.js',
'https://unpkg.com/medium-zoom@1.0.6/dist/medium-zoom.min.js',
'/js/medium-zoom.js',
],
// On page navigation for the current documentation page.
onPageNav: 'separate',
+5
View File
@@ -1094,3 +1094,8 @@ code {
margin: 0 1.5em;
}
}
/* Zoomed images using the medium-zoom library should be on top of screen. */
.medium-zoom-image {
z-index: 10000;
}
+11
View File
@@ -0,0 +1,11 @@
// Ref: https://github.com/francoischalifour/medium-zoom#options
window.addEventListener(
'load',
() => {
mediumZoom('[data-zoomable]', {
margin: 20,
background: '#000',
});
},
false,
);
+22
View File
@@ -1,5 +1,27 @@
# example-app
## 0.2.4
### Patch Changes
- Updated dependencies [294295453]
- Updated dependencies [f3bb55ee3]
- Updated dependencies [4b53294a6]
- Updated dependencies [6f70ed7a9]
- Updated dependencies [ab94c9542]
- Updated dependencies [3a201c5d5]
- Updated dependencies [f538e2c56]
- Updated dependencies [2daf18e80]
- Updated dependencies [069cda35f]
- Updated dependencies [8697dea5b]
- Updated dependencies [b623cc275]
- @backstage/cli@0.3.2
- @backstage/plugin-api-docs@0.3.0
- @backstage/plugin-techdocs@0.3.0
- @backstage/plugin-catalog@0.2.4
- @backstage/catalog-model@0.3.1
- @backstage/plugin-rollbar@0.2.4
## 0.2.3
### Patch Changes
+7 -7
View File
@@ -1,14 +1,14 @@
{
"name": "example-app",
"version": "0.2.3",
"version": "0.2.4",
"private": true,
"bundled": true,
"dependencies": {
"@backstage/catalog-model": "^0.3.0",
"@backstage/cli": "^0.3.1",
"@backstage/catalog-model": "^0.3.1",
"@backstage/cli": "^0.3.2",
"@backstage/core": "^0.3.2",
"@backstage/plugin-api-docs": "^0.2.2",
"@backstage/plugin-catalog": "^0.2.3",
"@backstage/plugin-api-docs": "^0.3.0",
"@backstage/plugin-catalog": "^0.2.4",
"@backstage/plugin-catalog-import": "^0.2.0",
"@backstage/plugin-circleci": "^0.2.2",
"@backstage/plugin-cloudbuild": "^0.2.2",
@@ -23,12 +23,12 @@
"@backstage/plugin-lighthouse": "^0.2.3",
"@backstage/plugin-newrelic": "^0.2.1",
"@backstage/plugin-register-component": "^0.2.2",
"@backstage/plugin-rollbar": "^0.2.3",
"@backstage/plugin-rollbar": "^0.2.4",
"@backstage/plugin-scaffolder": "^0.3.1",
"@backstage/plugin-sentry": "^0.2.3",
"@backstage/plugin-search": "^0.2.1",
"@backstage/plugin-tech-radar": "^0.3.0",
"@backstage/plugin-techdocs": "^0.2.3",
"@backstage/plugin-techdocs": "^0.3.0",
"@backstage/plugin-user-settings": "^0.2.2",
"@backstage/plugin-welcome": "^0.2.1",
"@backstage/test-utils": "^0.1.3",
+9
View File
@@ -1,5 +1,14 @@
# @backstage/backend-common
## 0.3.2
### Patch Changes
- 3aa7efb3f: Added support for passing false as a CSP field value, to drop it from the defaults in the backend
- b3d4e4e57: Move the frontend visibility declarations of integrations config from @backstage/backend-common to @backstage/integration
- Updated dependencies [b3d4e4e57]
- @backstage/integration@0.1.2
## 0.3.1
### Patch Changes
-7
View File
@@ -96,7 +96,6 @@ export interface Config {
azure?: Array<{
/**
* The hostname of the given Azure instance
* @visibility frontend
*/
host: string;
/**
@@ -110,7 +109,6 @@ export interface Config {
bitbucket?: Array<{
/**
* The hostname of the given Bitbucket instance
* @visibility frontend
*/
host: string;
/**
@@ -120,7 +118,6 @@ export interface Config {
token?: string;
/**
* The base url for the BitBucket API, for example https://api.bitbucket.org/2.0
* @visibility frontend
*/
apiBaseUrl?: string;
/**
@@ -139,7 +136,6 @@ export interface Config {
github?: Array<{
/**
* The hostname of the given GitHub instance
* @visibility frontend
*/
host: string;
/**
@@ -149,12 +145,10 @@ export interface Config {
token?: string;
/**
* The base url for the GitHub API, for example https://api.github.com
* @visibility frontend
*/
apiBaseUrl?: string;
/**
* The base url for GitHub raw resources, for example https://raw.githubusercontent.com
* @visibility frontend
*/
rawBaseUrl?: string;
}>;
@@ -163,7 +157,6 @@ export interface Config {
gitlab?: Array<{
/**
* The hostname of the given GitLab instance
* @visibility frontend
*/
host: string;
/**
+3 -3
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/backend-common",
"description": "Common functionality library for Backstage backends",
"version": "0.3.1",
"version": "0.3.2",
"main": "src/index.ts",
"types": "src/index.ts",
"private": false,
@@ -32,7 +32,7 @@
"@backstage/cli-common": "^0.1.1",
"@backstage/config": "^0.1.1",
"@backstage/config-loader": "^0.3.0",
"@backstage/integration": "^0.1.1",
"@backstage/integration": "^0.1.2",
"@types/cors": "^2.8.6",
"@types/express": "^4.17.6",
"archiver": "^5.0.2",
@@ -67,7 +67,7 @@
}
},
"devDependencies": {
"@backstage/cli": "^0.3.1",
"@backstage/cli": "^0.3.2",
"@backstage/test-utils": "^0.1.3",
"@types/archiver": "^3.1.1",
"@types/compression": "^1.7.0",
+24
View File
@@ -1,5 +1,29 @@
# example-backend
## 0.2.4
### Patch Changes
- Updated dependencies [50eff1d00]
- Updated dependencies [ff1301d28]
- Updated dependencies [4b53294a6]
- Updated dependencies [3aa7efb3f]
- Updated dependencies [1ec19a3f4]
- Updated dependencies [ab94c9542]
- Updated dependencies [3a201c5d5]
- Updated dependencies [2daf18e80]
- Updated dependencies [069cda35f]
- Updated dependencies [b3d4e4e57]
- Updated dependencies [700a212b4]
- @backstage/plugin-auth-backend@0.2.4
- @backstage/plugin-app-backend@0.3.1
- @backstage/plugin-techdocs-backend@0.3.0
- @backstage/backend-common@0.3.2
- @backstage/plugin-catalog-backend@0.2.3
- @backstage/catalog-model@0.3.1
- @backstage/plugin-rollbar-backend@0.1.4
- example-app@0.2.4
## 0.2.3
### Patch Changes
+10 -10
View File
@@ -1,6 +1,6 @@
{
"name": "example-backend",
"version": "0.2.3",
"version": "0.2.4",
"main": "dist/index.cjs.js",
"types": "src/index.ts",
"private": true,
@@ -18,24 +18,24 @@
"migrate:create": "knex migrate:make -x ts"
},
"dependencies": {
"@backstage/backend-common": "^0.3.1",
"@backstage/catalog-model": "^0.3.0",
"@backstage/backend-common": "^0.3.2",
"@backstage/catalog-model": "^0.3.1",
"@backstage/config": "^0.1.1",
"@backstage/plugin-app-backend": "^0.3.0",
"@backstage/plugin-auth-backend": "^0.2.3",
"@backstage/plugin-catalog-backend": "^0.2.2",
"@backstage/plugin-app-backend": "^0.3.1",
"@backstage/plugin-auth-backend": "^0.2.4",
"@backstage/plugin-catalog-backend": "^0.2.3",
"@backstage/plugin-graphql-backend": "^0.1.3",
"@backstage/plugin-kubernetes-backend": "^0.2.0",
"@backstage/plugin-proxy-backend": "^0.2.1",
"@backstage/plugin-rollbar-backend": "^0.1.3",
"@backstage/plugin-rollbar-backend": "^0.1.4",
"@backstage/plugin-scaffolder-backend": "^0.3.2",
"@backstage/plugin-sentry-backend": "^0.1.3",
"@backstage/plugin-techdocs-backend": "^0.2.2",
"@backstage/plugin-techdocs-backend": "^0.3.0",
"@gitbeaker/node": "^25.2.0",
"@octokit/rest": "^18.0.0",
"azure-devops-node-api": "^10.1.1",
"dockerode": "^3.2.0",
"example-app": "^0.2.3",
"example-app": "^0.2.4",
"express": "^4.17.1",
"express-promise-router": "^3.0.3",
"knex": "^0.21.6",
@@ -45,7 +45,7 @@
"winston": "^3.2.1"
},
"devDependencies": {
"@backstage/cli": "^0.3.1",
"@backstage/cli": "^0.3.2",
"@types/dockerode": "^2.5.32",
"@types/express": "^4.17.6",
"@types/express-serve-static-core": "^4.17.5",
+18
View File
@@ -1,5 +1,23 @@
# @backstage/catalog-model
## 0.3.1
### Patch Changes
- ab94c9542: Add `providesApis` and `consumesApis` to the component entity spec.
- 2daf18e80: Start emitting all known relation types from the core entity kinds, based on their spec data.
- 069cda35f: Marked the field `spec.implementsApis` on `Component` entities for deprecation on Dec 14th, 2020.
Code that consumes these fields should remove those usages as soon as possible and migrate to using
relations instead. Producers should fill the field `spec.providesApis` instead, which has the same
semantic.
After Dec 14th, the fields will be removed from types and classes of the Backstage repository. At
the first release after that, they will not be present in released packages either.
If your catalog-info.yaml files still contain this field after the deletion, they will still be
valid and your ingestion will not break, but they won't be visible in the types for consuming code, and the expected relations will not be generated based on them either.
## 0.3.0
### Minor Changes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/catalog-model",
"version": "0.3.0",
"version": "0.3.1",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -29,7 +29,7 @@
"yup": "^0.29.3"
},
"devDependencies": {
"@backstage/cli": "^0.3.1",
"@backstage/cli": "^0.3.2",
"@types/express": "^4.17.6",
"@types/jest": "^26.0.7",
"@types/lodash": "^4.14.151",
@@ -34,6 +34,8 @@ describe('ComponentV1alpha1Validator', () => {
lifecycle: 'production',
owner: 'me',
implementsApis: ['api-0'],
providesApis: ['api-0'],
consumesApis: ['api-0'],
},
};
});
@@ -121,4 +123,44 @@ describe('ComponentV1alpha1Validator', () => {
(entity as any).spec.implementsApis = [];
await expect(validator.check(entity)).resolves.toBe(true);
});
it('accepts missing providesApis', async () => {
delete (entity as any).spec.providesApis;
await expect(validator.check(entity)).resolves.toBe(true);
});
it('rejects empty providesApis', async () => {
(entity as any).spec.providesApis = [''];
await expect(validator.check(entity)).rejects.toThrow(/providesApis/);
});
it('rejects undefined providesApis', async () => {
(entity as any).spec.providesApis = [undefined];
await expect(validator.check(entity)).rejects.toThrow(/providesApis/);
});
it('accepts no providesApis', async () => {
(entity as any).spec.providesApis = [];
await expect(validator.check(entity)).resolves.toBe(true);
});
it('accepts missing consumesApis', async () => {
delete (entity as any).spec.consumesApis;
await expect(validator.check(entity)).resolves.toBe(true);
});
it('rejects empty consumesApis', async () => {
(entity as any).spec.consumesApis = [''];
await expect(validator.check(entity)).rejects.toThrow(/consumesApis/);
});
it('rejects undefined consumesApis', async () => {
(entity as any).spec.consumesApis = [undefined];
await expect(validator.check(entity)).rejects.toThrow(/consumesApis/);
});
it('accepts no consumesApis', async () => {
(entity as any).spec.consumesApis = [];
await expect(validator.check(entity)).resolves.toBe(true);
});
});
@@ -30,6 +30,8 @@ const schema = yup.object<Partial<ComponentEntityV1alpha1>>({
lifecycle: yup.string().required().min(1),
owner: yup.string().required().min(1),
implementsApis: yup.array(yup.string().required()).notRequired(),
providesApis: yup.array(yup.string().required()).notRequired(),
consumesApis: yup.array(yup.string().required()).notRequired(),
kubernetes: yup
.object<any>({
selector: yup
@@ -50,7 +52,14 @@ export interface ComponentEntityV1alpha1 extends Entity {
type: string;
lifecycle: string;
owner: string;
/**
* @deprecated This field will disappear on Dec 14th, 2020. Please remove
* any consuming code. The new field providesApis provides the
* same functionality like before.
*/
implementsApis?: string[];
providesApis?: string[];
consumesApis?: string[];
kubernetes?: {
selector: {
matchLabels: {
+9
View File
@@ -1,5 +1,14 @@
# @backstage/cli
## 0.3.2
### Patch Changes
- 294295453: Only load config that applies to the target package for frontend build and serve tasks. Also added `--package <name>` flag to scope the config schema used by the `config:print` and `config:check` commands.
- f538e2c56: Make versions:bump install new versions of dependencies that were within the specified range as well as install new versions of transitive @backstage dependencies.
- 8697dea5b: Bump Rollup
- b623cc275: Narrow down the version range of rollup-plugin-esbuild to avoid breaking change in newer version
## 0.3.1
### Patch Changes
+4 -4
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/cli",
"description": "CLI for developing Backstage plugins and apps",
"version": "0.3.1",
"version": "0.3.2",
"private": false,
"publishConfig": {
"access": "public"
@@ -86,9 +86,9 @@
"react-hot-loader": "^4.12.21",
"recursive-readdir": "^2.2.2",
"replace-in-file": "^6.0.0",
"rollup": "2.23.x",
"rollup": "2.33.x",
"rollup-plugin-dts": "1.4.13",
"rollup-plugin-esbuild": "^2.0.0",
"rollup-plugin-esbuild": "2.3.x",
"rollup-plugin-peer-deps-external": "^2.2.2",
"rollup-plugin-postcss": "^3.1.1",
"rollup-plugin-typescript2": "^0.27.3",
@@ -111,7 +111,7 @@
"yn": "^4.0.0"
},
"devDependencies": {
"@backstage/backend-common": "^0.3.1",
"@backstage/backend-common": "^0.3.2",
"@backstage/config": "^0.1.1",
"@backstage/core": "^0.3.2",
"@backstage/dev-utils": "^0.1.4",
+7 -1
View File
@@ -14,16 +14,22 @@
* limitations under the License.
*/
import fs from 'fs-extra';
import { Command } from 'commander';
import { buildBundle } from '../../lib/bundler';
import { parseParallel, PARALLEL_ENV_VAR } from '../../lib/parallel';
import { loadCliConfig } from '../../lib/config';
import { paths } from '../../lib/paths';
export default async (cmd: Command) => {
const { name } = await fs.readJson(paths.resolveTarget('package.json'));
await buildBundle({
entry: 'src/index',
parallel: parseParallel(process.env[PARALLEL_ENV_VAR]),
statsJsonEnabled: cmd.stats,
...(await loadCliConfig(cmd.config)),
...(await loadCliConfig({
args: cmd.config,
fromPackage: name,
})),
});
};
+7 -1
View File
@@ -14,15 +14,21 @@
* limitations under the License.
*/
import fs from 'fs-extra';
import { Command } from 'commander';
import { serveBundle } from '../../lib/bundler';
import { loadCliConfig } from '../../lib/config';
import { paths } from '../../lib/paths';
export default async (cmd: Command) => {
const { name } = await fs.readJson(paths.resolveTarget('package.json'));
const waitForExit = await serveBundle({
entry: 'src/index',
checksEnabled: cmd.check,
...(await loadCliConfig(cmd.config)),
...(await loadCliConfig({
args: cmd.config,
fromPackage: name,
})),
});
await waitForExit();
+4 -1
View File
@@ -21,7 +21,10 @@ import { loadCliConfig } from '../../lib/config';
import { ConfigSchema, ConfigVisibility } from '@backstage/config-loader';
export default async (cmd: Command) => {
const { schema, appConfigs } = await loadCliConfig(cmd.config);
const { schema, appConfigs } = await loadCliConfig({
args: cmd.config,
fromPackage: cmd.package,
});
const visibility = getVisiblityOption(cmd);
const data = serializeConfigData(appConfigs, schema, visibility);
+4 -1
View File
@@ -18,5 +18,8 @@ import { Command } from 'commander';
import { loadCliConfig } from '../../lib/config';
export default async (cmd: Command) => {
await loadCliConfig(cmd.config);
await loadCliConfig({
args: cmd.config,
fromPackage: cmd.package,
});
};
+8
View File
@@ -141,6 +141,10 @@ export function registerCommands(program: CommanderStatic) {
program
.command('config:print')
.option(
'--package <name>',
'Only load config schema that applies to the given package',
)
.option('--frontend', 'Print only the frontend configuration')
.option('--with-secrets', 'Include secrets in the printed configuration')
.option(
@@ -153,6 +157,10 @@ export function registerCommands(program: CommanderStatic) {
program
.command('config:check')
.option(
'--package <name>',
'Only load config schema that applies to the given package',
)
.option(...configOption)
.description(
'Validate that the given configuration loads and matches schema',
+7 -1
View File
@@ -14,15 +14,21 @@
* limitations under the License.
*/
import fs from 'fs-extra';
import { Command } from 'commander';
import { serveBundle } from '../../lib/bundler';
import { loadCliConfig } from '../../lib/config';
import { paths } from '../../lib/paths';
export default async (cmd: Command) => {
const { name } = await fs.readJson(paths.resolveTarget('package.json'));
const waitForExit = await serveBundle({
entry: 'dev/index',
checksEnabled: cmd.check,
...(await loadCliConfig(cmd.config)),
...(await loadCliConfig({
args: cmd.config,
fromPackage: name,
})),
});
await waitForExit();
@@ -25,6 +25,7 @@ import { withLogCollector } from '@backstage/test-utils';
const REGISTRY_VERSIONS: { [name: string]: string } = {
'@backstage/core': '1.0.6',
'@backstage/core-api': '1.0.7',
'@backstage/theme': '2.0.0',
};
@@ -54,11 +55,8 @@ const lockfileMock = `${HEADER}
version "1.0.3"
`;
// This resulting lockfile isn't a real world example, since it doesn't include the package bumps
// This is the lockfile that we produce to unlock versions before we run yarn install
const lockfileMockResult = `${HEADER}
"@backstage/core-api@^1.0.3", "@backstage/core-api@^1.0.6":
version "1.0.6"
"@backstage/core@^1.0.5":
version "1.0.6"
dependencies:
@@ -121,15 +119,16 @@ describe('bump', () => {
expect(logs.filter(Boolean)).toEqual([
'Checking for updates of @backstage/theme',
'Checking for updates of @backstage/core',
'Checking for updates of @backstage/core-api',
'Some packages are outdated, updating',
'Removing lockfile entry for @backstage/core@^1.0.3 to bump to 1.0.6',
'Removing lockfile entry for @backstage/core-api@^1.0.6 to bump to 1.0.7',
'Removing lockfile entry for @backstage/core-api@^1.0.3 to bump to 1.0.7',
'Bumping @backstage/theme in b to ^2.0.0',
"Running 'yarn install' to install new versions",
'Removing duplicate dependencies from yarn.lock',
"Running 'yarn install' to remove duplicates from node_modules",
]);
expect(runObj.runPlain).toHaveBeenCalledTimes(2);
expect(runObj.runPlain).toHaveBeenCalledTimes(3);
expect(runObj.runPlain).toHaveBeenCalledWith(
'yarn',
'info',
@@ -143,7 +142,7 @@ describe('bump', () => {
'@backstage/theme',
);
expect(runObj.run).toHaveBeenCalledTimes(2);
expect(runObj.run).toHaveBeenCalledTimes(1);
expect(runObj.run).toHaveBeenCalledWith('yarn', ['install']);
const lockfileContents = await fs.readFile('/yarn.lock', 'utf8');
+64 -32
View File
@@ -41,6 +41,9 @@ type PkgVersionInfo = {
export default async () => {
const lockfilePath = paths.resolveTargetRoot('yarn.lock');
const lockfile = await Lockfile.load(lockfilePath);
const findTargetVersion = createVersionFinder();
// First we discover all Backstage dependencies within our own repo
const dependencyMap = await mapDependencies(paths.targetDir);
@@ -48,20 +51,16 @@ export default async () => {
// Next check with the package registry to see which dependency ranges we need to bump
const versionBumps = new Map<string, PkgVersionInfo[]>();
// Track package versions that we want to remove from yarn.lock in order to trigger a bump
const unlocked = Array<{ name: string; range: string; latest: string }>();
const unlocked = Array<{ name: string; range: string; target: string }>();
await workerThreads(16, dependencyMap.entries(), async ([name, pkgs]) => {
console.log(`Checking for updates of ${name}`);
const info = await fetchPackageInfo(name);
const latest = info['dist-tags'].latest;
if (!latest) {
throw new Error(`No latest version found for ${name}`);
}
const target = await findTargetVersion(name);
for (const pkg of pkgs) {
if (semver.satisfies(latest, pkg.range)) {
if (semver.minVersion(pkg.range)?.version !== latest) {
unlocked.push({ name, range: pkg.range, latest });
if (semver.satisfies(target, pkg.range)) {
if (semver.minVersion(pkg.range)?.version !== target) {
unlocked.push({ name, range: pkg.range, target });
}
continue;
}
versionBumps.set(
@@ -69,12 +68,32 @@ export default async () => {
(versionBumps.get(pkg.name) ?? []).concat({
name,
location: pkg.location,
range: `^${latest}`, // TODO(Rugvip): Option to use something else than ^?
range: `^${target}`, // TODO(Rugvip): Option to use something else than ^?
}),
);
}
});
// Check for updates of transitive backstage dependencies
await workerThreads(16, lockfile.keys(), async name => {
// Only check @backstage packages and friends, we don't want this to do a full update of all deps
if (!includedFilter(name)) {
return;
}
const target = await findTargetVersion(name);
for (const entry of lockfile.get(name) ?? []) {
// Ignore lockfile entries that don't satisfy the version range, since
// these can't cause the package to be locked to an older version
if (!semver.satisfies(target, entry.range)) {
continue;
}
// Unlock all entries that are within range but on the old version
unlocked.push({ name, range: entry.range, target });
}
});
console.log();
// Write all discovered version bumps to package.json in this repo
@@ -85,17 +104,21 @@ export default async () => {
console.log();
if (unlocked.length > 0) {
const lockfile = await Lockfile.load(lockfilePath);
for (const { name, range, latest } of unlocked) {
const removed = new Set<string>();
for (const { name, range, target } of unlocked) {
// Don't bother removing lockfile entries if they're already on the correct version
const existingEntry = lockfile.get(name)?.find(e => e.range === range);
if (existingEntry?.version === latest) {
if (existingEntry?.version === target) {
continue;
}
console.log(
`Removing lockfile entry for ${name}@${range} to bump to ${latest}`,
);
lockfile.remove(name, range);
const key = JSON.stringify({ name, range });
if (!removed.has(key)) {
removed.add(key);
console.log(
`Removing lockfile entry for ${name}@${range} to bump to ${target}`,
);
lockfile.remove(name, range);
}
}
await lockfile.save();
}
@@ -126,24 +149,13 @@ export default async () => {
console.log();
// Finally we make sure the new lockfile doesn't have any duplicates
const lockfile = await Lockfile.load(lockfilePath);
const result = lockfile.analyze({
const dedupLockfile = await Lockfile.load(lockfilePath);
const result = dedupLockfile.analyze({
filter: includedFilter,
});
if (result.newVersions.length > 0) {
console.log();
console.log('Removing duplicate dependencies from yarn.lock');
lockfile.replaceVersions(result.newVersions);
await lockfile.save();
console.log(
"Running 'yarn install' to remove duplicates from node_modules",
);
console.log();
await run('yarn', ['install']);
console.log();
throw new Error('Duplicate versions present after package bump');
}
const forbiddenNewRanges = result.newRanges.filter(({ name }) =>
@@ -158,6 +170,26 @@ export default async () => {
}
};
function createVersionFinder() {
const found = new Map<string, string>();
return async function findTargetVersion(name: string) {
const existing = found.get(name);
if (existing) {
return existing;
}
console.log(`Checking for updates of ${name}`);
const info = await fetchPackageInfo(name);
const latest = info['dist-tags'].latest;
if (!latest) {
throw new Error(`No latest version found for ${name}`);
}
found.set(name, latest);
return latest;
};
}
async function workerThreads<T>(
count: number,
items: IterableIterator<T>,
+6 -6
View File
@@ -38,11 +38,11 @@ describe('forwardFileImports', () => {
expect(plugin.name).toBe('forward-file-imports');
});
it('should call through to original external option', () => {
it('should call through to original external option', async () => {
const plugin = forwardFileImports({ include: /\.png$/ });
const external = jest.fn((id: string) => id.endsWith('external'));
const options = plugin.options?.call(context, { external })!;
const options = (await plugin.options?.call(context, { external }))!;
if (typeof options.external !== 'function') {
throw new Error('options.external is not a function');
}
@@ -70,12 +70,12 @@ describe('forwardFileImports', () => {
).toThrow('Unknown importer of file module ./my-image.png');
});
it('should handle original external array', () => {
it('should handle original external array', async () => {
const plugin = forwardFileImports({ include: /\.png$/ });
const options = plugin.options?.call(context, {
const options = (await plugin.options?.call(context, {
external: ['my-external'],
})!;
}))!;
if (typeof options.external !== 'function') {
throw new Error('options.external is not a function');
}
@@ -106,7 +106,7 @@ describe('forwardFileImports', () => {
it('should extract files', async () => {
const plugin = forwardFileImports({ include: /\.png$/ });
const options = plugin.options?.call(context, {})!;
const options = (await plugin.options?.call(context, {}))!;
if (typeof options.external !== 'function') {
throw new Error('options.external is not a function');
}
+43 -3
View File
@@ -18,14 +18,23 @@ import { loadConfig, loadConfigSchema } from '@backstage/config-loader';
import { ConfigReader } from '@backstage/config';
import { paths } from './paths';
export async function loadCliConfig(configArgs: string[]) {
const configPaths = configArgs.map(arg => paths.resolveTarget(arg));
type Options = {
args: string[];
fromPackage?: string;
};
export async function loadCliConfig(options: Options) {
const configPaths = options.args.map(arg => paths.resolveTarget(arg));
// Consider all packages in the monorepo when loading in config
const LernaProject = require('@lerna/project');
const project = new LernaProject(paths.targetDir);
const packages = await project.getPackages();
const localPackageNames = packages.map((p: any) => p.name);
const localPackageNames = options.fromPackage
? findPackages(packages, options.fromPackage)
: packages.map((p: any) => p.name);
const schema = await loadConfigSchema({
dependencies: localPackageNames,
});
@@ -61,3 +70,34 @@ export async function loadCliConfig(configArgs: string[]) {
throw error;
}
}
function findPackages(packages: any[], fromPackage: string): string[] {
const PackageGraph = require('@lerna/package-graph');
const graph = new PackageGraph(packages);
const targets = new Set<string>();
const searchNames = [fromPackage];
while (searchNames.length) {
const name = searchNames.pop()!;
if (targets.has(name)) {
continue;
}
const node = graph.get(name);
if (!node) {
throw new Error(`Package '${name}' not found`);
}
targets.add(name);
// Workaround for Backstage main repo only, since the CLI has some artificial devDependencies
if (name !== '@backstage/cli') {
searchNames.push(...node.localDependencies.keys());
}
}
return Array.from(targets);
}
@@ -100,10 +100,16 @@ export class Lockfile {
private readonly data: LockfileData,
) {}
/** Get the entries for a single package in the lockfile */
get(name: string): LockfileQueryEntry[] | undefined {
return this.packages.get(name);
}
/** Returns the name of all packages available in the lockfile */
keys(): IterableIterator<string> {
return this.packages.keys();
}
/** Analyzes the lockfile to identify possible actions and warnings for the entries */
analyze(options?: { filter?: (name: string) => boolean }): AnalyzeResult {
const { filter } = options ?? {};
@@ -16,6 +16,7 @@
import mockFs from 'mock-fs';
import { collectConfigSchemas } from './collect';
import path from 'path';
const mockSchema = {
type: 'object',
@@ -56,7 +57,7 @@ describe('collectConfigSchemas', () => {
await expect(collectConfigSchemas(['a'])).resolves.toEqual([
{
path: 'node_modules/a/package.json',
path: path.join('node_modules', 'a', 'package.json'),
value: mockSchema,
},
]);
@@ -114,15 +115,15 @@ describe('collectConfigSchemas', () => {
await expect(collectConfigSchemas(['a'])).resolves.toEqual([
{
path: 'node_modules/b/package.json',
path: path.join('node_modules', 'b', 'package.json'),
value: { ...mockSchema, title: 'b' },
},
{
path: 'node_modules/c1/package.json',
path: path.join('node_modules', 'c1', 'package.json'),
value: { ...mockSchema, title: 'c1' },
},
{
path: 'node_modules/d1/package.json',
path: path.join('node_modules', 'd1', 'package.json'),
value: { ...mockSchema, title: 'd1' },
},
]);
@@ -163,15 +164,15 @@ describe('collectConfigSchemas', () => {
await expect(collectConfigSchemas(['a', 'b', 'c'])).resolves.toEqual([
{
path: 'node_modules/a/package.json',
path: path.join('node_modules', 'a', 'package.json'),
value: { ...mockSchema, title: 'inline' },
},
{
path: 'node_modules/b/schema.json',
path: path.join('node_modules', 'b', 'schema.json'),
value: { ...mockSchema, title: 'external' },
},
{
path: 'node_modules/c/schema.d.ts',
path: path.join('node_modules', 'c', 'schema.d.ts'),
value: {
$schema: 'http://json-schema.org/draft-07/schema#',
type: 'object',
@@ -223,7 +224,11 @@ describe('collectConfigSchemas', () => {
});
await expect(collectConfigSchemas(['a'])).rejects.toThrow(
'Invalid schema in node_modules/a/schema.d.ts, missing Config export',
`Invalid schema in ${path.join(
'node_modules',
'a',
'schema.d.ts',
)}, missing Config export`,
);
});
});
+6
View File
@@ -1,5 +1,11 @@
# @backstage/core-api
## 0.2.3
### Patch Changes
- 700a212b4: bug fix: issue 3223 - detect mismatching origin and indicate it in the message at auth failure
## 0.2.2
### Patch Changes
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/core-api",
"description": "Internal Core API used by Backstage plugins and apps",
"version": "0.2.2",
"version": "0.2.3",
"private": false,
"publishConfig": {
"access": "public",
@@ -42,7 +42,7 @@
"zen-observable": "^0.8.15"
},
"devDependencies": {
"@backstage/cli": "^0.3.0",
"@backstage/cli": "^0.3.2",
"@backstage/test-utils-core": "^0.1.1",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
@@ -156,6 +156,16 @@ describe('showLoginPopup', () => {
expect(addEventListenerSpy).toBeCalledTimes(1);
expect(removeEventListenerSpy).toBeCalledTimes(0);
const listener = addEventListenerSpy.mock.calls[0][1] as EventListener;
listener({
source: popupMock,
origin: 'origin',
data: {
type: 'config_info',
targetOrigin: 'http://localhost',
},
} as MessageEvent);
setTimeout(() => {
popupMock.closed = true;
}, 150);
@@ -167,4 +177,42 @@ describe('showLoginPopup', () => {
expect(addEventListenerSpy).toBeCalledTimes(1);
expect(removeEventListenerSpy).toBeCalledTimes(1);
});
it('should indicate if origin does not match', async () => {
const openSpy = jest
.spyOn(window, 'open')
.mockReturnValue({ closed: false } as Window);
const addEventListenerSpy = jest.spyOn(window, 'addEventListener');
const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener');
const popupMock = { closed: false };
openSpy.mockReturnValue(popupMock as Window);
const payloadPromise = showLoginPopup({
url: 'url',
name: 'name',
origin: 'origin',
});
const listener = addEventListenerSpy.mock.calls[0][1] as EventListener;
listener({
source: popupMock,
origin: 'origin',
data: {
type: 'config_info',
targetOrigin: 'http://differenthost',
},
} as MessageEvent);
setTimeout(() => {
popupMock.closed = true;
}, 150);
await expect(payloadPromise).rejects.toThrow(
'Login failed, Incorrect app origin, expected http://differenthost',
);
expect(openSpy).toBeCalledTimes(1);
expect(addEventListenerSpy).toBeCalledTimes(1);
expect(removeEventListenerSpy).toBeCalledTimes(1);
});
});
+14 -1
View File
@@ -79,6 +79,8 @@ export function showLoginPopup(options: LoginPopupOptions): Promise<any> {
`menubar=no,location=no,resizable=no,scrollbars=no,status=no,width=${width},height=${height},top=${top},left=${left}`,
);
let targetOrigin = '';
if (!popup || typeof popup.closed === 'undefined' || popup.closed) {
reject(new Error('Failed to open auth popup.'));
return;
@@ -92,6 +94,12 @@ export function showLoginPopup(options: LoginPopupOptions): Promise<any> {
return;
}
const { data } = event;
if (data.type === 'config_info') {
targetOrigin = data.targetOrigin;
return;
}
if (data.type !== 'authorization_response') {
return;
}
@@ -111,7 +119,12 @@ export function showLoginPopup(options: LoginPopupOptions): Promise<any> {
const intervalId = setInterval(() => {
if (popup.closed) {
const error = new Error('Login failed, popup was closed');
const errMessage = `Login failed, ${
targetOrigin !== window.location.origin
? `Incorrect app origin, expected ${targetOrigin}`
: 'popup was closed'
}`;
const error = new Error(errMessage);
error.name = 'PopupClosedError';
reject(error);
done();
+11 -11
View File
@@ -37,28 +37,28 @@
"recursive-readdir": "^2.2.2"
},
"devDependencies": {
"@backstage/backend-common": "^0.3.1",
"@backstage/catalog-model": "^0.3.0",
"@backstage/cli": "^0.3.1",
"@backstage/backend-common": "^0.3.2",
"@backstage/catalog-model": "^0.3.1",
"@backstage/cli": "^0.3.2",
"@backstage/config": "^0.1.1",
"@backstage/core": "^0.3.2",
"@backstage/plugin-api-docs": "^0.2.2",
"@backstage/plugin-app-backend": "^0.3.0",
"@backstage/plugin-auth-backend": "^0.2.3",
"@backstage/plugin-catalog": "^0.2.3",
"@backstage/plugin-catalog-backend": "^0.2.2",
"@backstage/plugin-api-docs": "^0.3.0",
"@backstage/plugin-app-backend": "^0.3.1",
"@backstage/plugin-auth-backend": "^0.2.4",
"@backstage/plugin-catalog": "^0.2.4",
"@backstage/plugin-catalog-backend": "^0.2.3",
"@backstage/plugin-circleci": "^0.2.2",
"@backstage/plugin-explore": "^0.2.1",
"@backstage/plugin-github-actions": "^0.2.2",
"@backstage/plugin-lighthouse": "^0.2.3",
"@backstage/plugin-proxy-backend": "^0.2.1",
"@backstage/plugin-register-component": "^0.2.2",
"@backstage/plugin-rollbar-backend": "^0.1.3",
"@backstage/plugin-rollbar-backend": "^0.1.4",
"@backstage/plugin-scaffolder": "^0.3.1",
"@backstage/plugin-scaffolder-backend": "^0.3.2",
"@backstage/plugin-tech-radar": "^0.3.0",
"@backstage/plugin-techdocs": "^0.2.3",
"@backstage/plugin-techdocs-backend": "^0.2.2",
"@backstage/plugin-techdocs": "^0.3.0",
"@backstage/plugin-techdocs-backend": "^0.3.0",
"@backstage/plugin-user-settings": "^0.2.2",
"@backstage/test-utils": "^0.1.3",
"@backstage/theme": "^0.2.1",
@@ -25,7 +25,6 @@
"@backstage/plugin-auth-backend": "^{{version '@backstage/plugin-auth-backend'}}",
"@backstage/plugin-catalog-backend": "^{{version '@backstage/plugin-catalog-backend'}}",
"@backstage/plugin-proxy-backend": "^{{version '@backstage/plugin-proxy-backend'}}",
"@backstage/plugin-rollbar-backend": "^{{version '@backstage/plugin-rollbar-backend'}}",
"@backstage/plugin-scaffolder-backend": "^{{version '@backstage/plugin-scaffolder-backend'}}",
"@backstage/plugin-techdocs-backend": "^{{version '@backstage/plugin-techdocs-backend'}}",
"@octokit/rest": "^18.0.0",
+6
View File
@@ -1,5 +1,11 @@
# @backstage/integration
## 0.1.2
### Patch Changes
- b3d4e4e57: Move the frontend visibility declarations of integrations config from @backstage/backend-common to @backstage/integration
## 0.1.1
### Patch Changes
+45
View File
@@ -0,0 +1,45 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export interface Config {
integrations?: {
azure?: Array<{
/** @visibility frontend */
host: string;
}>;
bitbucket?: Array<{
/** @visibility frontend */
host: string;
/** @visibility frontend */
apiBaseUrl?: string;
}>;
github?: Array<{
/** @visibility frontend */
host: string;
/** @visibility frontend */
apiBaseUrl?: string;
/** @visibility frontend */
rawBaseUrl?: string;
}>;
gitlab?: Array<{
/** @visibility frontend */
host: string;
}>;
};
}
+6 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/integration",
"version": "0.1.1",
"version": "0.1.2",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -24,10 +24,12 @@
"git-url-parse": "^11.4.0"
},
"devDependencies": {
"@backstage/cli": "^0.3.0",
"@backstage/cli": "^0.3.2",
"@types/jest": "^26.0.7"
},
"files": [
"dist"
]
"dist",
"config.d.ts"
],
"configSchema": "config.d.ts"
}
+18
View File
@@ -1,5 +1,23 @@
# @backstage/plugin-api-docs
## 0.3.0
### Minor Changes
- f3bb55ee3: APIs now have real entity pages that are customizable in the app.
Therefore the old entity page from this plugin is removed.
See the `packages/app` on how to create and customize the API entity page.
### Patch Changes
- 6f70ed7a9: Replace usage of implementsApis with relations
- Updated dependencies [6f70ed7a9]
- Updated dependencies [ab94c9542]
- Updated dependencies [2daf18e80]
- Updated dependencies [069cda35f]
- @backstage/plugin-catalog@0.2.4
- @backstage/catalog-model@0.3.1
## 0.2.2
### Patch Changes
+1 -1
View File
@@ -21,7 +21,7 @@ Right now, the following API formats are supported:
Other formats are displayed as plain text, but this can easily be extended.
To fill the catalog with APIs, [provide entities of kind API](https://backstage.io/docs/features/software-catalog/descriptor-format#kind-api).
To link that an component implements an API, see [`implementsApis` property on components](https://backstage.io/docs/features/software-catalog/descriptor-format#specimplementsapis-optional).
To link that a component provides or consumes an API, see the [`providesApis`](https://backstage.io/docs/features/software-catalog/descriptor-format#specprovidesapis-optional) and [`consumesApis`](https://backstage.io/docs/features/software-catalog/descriptor-format#specconsumesapis-optional) properties on the Component kind.
## Links
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-api-docs",
"version": "0.2.2",
"version": "0.3.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -20,9 +20,9 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/catalog-model": "^0.3.0",
"@backstage/catalog-model": "^0.3.1",
"@backstage/core": "^0.3.2",
"@backstage/plugin-catalog": "^0.2.3",
"@backstage/plugin-catalog": "^0.2.4",
"@backstage/theme": "^0.2.1",
"@kyma-project/asyncapi-react": "^0.14.2",
"@material-icons/font": "^1.0.2",
@@ -40,7 +40,7 @@
"swagger-ui-react": "^3.31.1"
},
"devDependencies": {
"@backstage/cli": "^0.3.1",
"@backstage/cli": "^0.3.2",
"@backstage/dev-utils": "^0.1.4",
"@backstage/test-utils": "^0.1.3",
"@testing-library/jest-dom": "^5.10.1",
@@ -28,7 +28,7 @@ spec:
type: service
lifecycle: production
owner: guest
implementsApis:
providesApis:
- example-api
`;
@@ -47,11 +47,10 @@ export const MissingImplementsApisEmptyState = () => {
missing="field"
title="No APIs implemented by this entity"
description={
<Typography>
<>
Components can implement APIs that are displayed on this page. You
need to fill the <code>implementsApis</code> field to enable this
tool.
</Typography>
need to fill the <code>providesApis</code> field to enable this tool.
</>
}
action={
<>
@@ -71,7 +70,7 @@ export const MissingImplementsApisEmptyState = () => {
<Button
variant="contained"
color="primary"
href="https://backstage.io/docs/features/software-catalog/descriptor-format#specimplementsapis-optional"
href="https://backstage.io/docs/features/software-catalog/descriptor-format#specprovidesapis-optional"
>
Read more
</Button>
+3 -2
View File
@@ -15,14 +15,15 @@
*/
import React from 'react';
import { Entity } from '@backstage/catalog-model';
import { Entity, RELATION_PROVIDES_API } from '@backstage/catalog-model';
import { Route, Routes } from 'react-router';
import { catalogRoute } from '../routes';
import { EntityPageApi } from './EntityPageApi';
import { MissingImplementsApisEmptyState } from './MissingImplementsApisEmptyState';
const isPluginApplicableToEntity = (entity: Entity) => {
return ((entity.spec?.implementsApis as string[]) || []).length > 0;
// TODO: Also support RELATION_CONSUMES_API
return entity.relations?.some(r => r.type === RELATION_PROVIDES_API);
};
export const Router = ({ entity }: { entity: Entity }) =>
@@ -22,7 +22,7 @@ import * as React from 'react';
import { apiDocsConfigRef } from '../../config';
import { ApiExplorerTable } from './ApiExplorerTable';
const entites: Entity[] = [
const entities: Entity[] = [
{
apiVersion: 'backstage.io/v1alpha1',
kind: 'API',
@@ -70,7 +70,7 @@ describe('ApiCatalogTable component', () => {
const rendered = render(
wrapInTestApp(
<ApiProvider apis={apiRegistry}>
<ApiExplorerTable entities={entites} loading={false} />
<ApiExplorerTable entities={entities} loading={false} />
</ApiProvider>,
),
);
@@ -14,8 +14,16 @@
* limitations under the License.
*/
import { ComponentEntity } from '@backstage/catalog-model';
import {
ComponentEntity,
RELATION_PROVIDES_API,
} from '@backstage/catalog-model';
export const useComponentApiNames = (entity: ComponentEntity) => {
return (entity.spec?.implementsApis as string[]) || [];
// TODO: This code doesn't handle namespaces and kinds correctly, but will be removed soon
return (
entity.relations
?.filter(r => r.type === RELATION_PROVIDES_API)
?.map(r => r.target.name) || []
);
};
+9
View File
@@ -1,5 +1,14 @@
# @backstage/plugin-app-backend
## 0.3.1
### Patch Changes
- ff1301d28: Warn if the app-backend can't start-up because the static directory that should be served is unavailable.
- Updated dependencies [3aa7efb3f]
- Updated dependencies [b3d4e4e57]
- @backstage/backend-common@0.3.2
## 0.3.0
### Minor Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-app-backend",
"version": "0.3.0",
"version": "0.3.1",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -20,7 +20,7 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/backend-common": "^0.3.0",
"@backstage/backend-common": "^0.3.2",
"@backstage/config-loader": "^0.3.0",
"@backstage/config": "^0.1.1",
"@types/express": "^4.17.6",
@@ -31,7 +31,7 @@
"yn": "^4.0.0"
},
"devDependencies": {
"@backstage/cli": "^0.3.0",
"@backstage/cli": "^0.3.2",
"@types/supertest": "^2.0.8",
"msw": "^0.20.5",
"supertest": "^4.0.2"
+14
View File
@@ -1,5 +1,19 @@
# @backstage/plugin-auth-backend
## 0.2.4
### Patch Changes
- 50eff1d00: Allow the backend to register custom AuthProviderFactories
- 700a212b4: bug fix: issue 3223 - detect mismatching origin and indicate it in the message at auth failure
- Updated dependencies [3aa7efb3f]
- Updated dependencies [ab94c9542]
- Updated dependencies [2daf18e80]
- Updated dependencies [069cda35f]
- Updated dependencies [b3d4e4e57]
- @backstage/backend-common@0.3.2
- @backstage/catalog-model@0.3.1
## 0.2.3
### Patch Changes
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-auth-backend",
"version": "0.2.3",
"version": "0.2.4",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -20,9 +20,9 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/backend-common": "^0.3.1",
"@backstage/backend-common": "^0.3.2",
"@backstage/catalog-client": "^0.3.1",
"@backstage/catalog-model": "^0.3.0",
"@backstage/catalog-model": "^0.3.1",
"@backstage/config": "^0.1.1",
"@types/express": "^4.17.6",
"compression": "^1.7.4",
@@ -55,7 +55,7 @@
"yn": "^4.0.0"
},
"devDependencies": {
"@backstage/cli": "^0.3.1",
"@backstage/cli": "^0.3.2",
"@types/body-parser": "^1.19.0",
"@types/cookie-parser": "^1.4.2",
"@types/express-session": "^1.17.2",
+8
View File
@@ -15,3 +15,11 @@
*/
export * from './service/router';
export * from './providers';
// flow package provides 2 functions
// ensuresXRequestedWith and postMessageResponse to safely handle CORS requests for login. The WebMessageResponse type in flow is used to type the response from the login-popup
export * from './lib/flow';
// OAuth wrapper over a passport or a custom `startegy`.
export * from './lib/oauth';
@@ -81,6 +81,52 @@ describe('oauth helpers', () => {
expect(mockResponse.end).toBeCalledWith(expect.stringContaining(encoded));
});
it('should call postMessage twice but only one of them with target *', () => {
let responseBody = '';
const mockResponse = ({
end: jest.fn(body => {
responseBody = body;
return this;
}),
setHeader: jest.fn().mockReturnThis(),
} as unknown) as express.Response;
const data: WebMessageResponse = {
type: 'authorization_response',
response: {
providerInfo: {
accessToken: 'ACCESS_TOKEN',
idToken: 'ID_TOKEN',
expiresInSeconds: 10,
scope: 'email',
},
profile: {
email: 'foo@bar.com',
},
backstageIdentity: {
id: 'a',
idToken: 'a.b.c',
},
},
};
postMessageResponse(mockResponse, appOrigin, data);
expect(responseBody.match(/.postMessage\(/g)).toHaveLength(2);
expect(
responseBody.match(/.postMessage\([a-zA-z.()]*, \'\*\'\)/g),
).toHaveLength(1);
const errData: WebMessageResponse = {
type: 'authorization_response',
error: new Error('Unknown error occurred'),
};
postMessageResponse(mockResponse, appOrigin, errData);
expect(responseBody.match(/.postMessage\(/g)).toHaveLength(2);
expect(
responseBody.match(/.postMessage\([a-zA-z.()]*, \'\*\'\)/g),
).toHaveLength(1);
});
it('handles single quotes and unicode chars safely', () => {
const mockResponse = ({
end: jest.fn().mockReturnThis(),
@@ -38,10 +38,24 @@ export const postMessageResponse = (
// data.
// TODO: Make target app origin configurable globally
//
// postMessage fails silently if the targetOrigin is disallowed.
// So 2 postMessages are sent from the popup to the parent window.
// First, the origin being used to post the actual authorization response is
// shared with the parent window with a postMessage with targetOrigin '*'.
// Second, the actual authorization response is sent with the app origin
// as the targetOrigin.
// If the first message was received but the actual auth response was
// never received, the event listener can conclude that targetOrigin
// was disallowed, indicating potential misconfiguration.
//
const script = `
var json = decodeURIComponent('${base64Data}');
var authResponse = decodeURIComponent('${base64Data}');
var origin = decodeURIComponent('${base64Origin}');
(window.opener || window.parent).postMessage(JSON.parse(json), origin);
var originInfo = {'type': 'config_info', 'targetOrigin': origin};
(window.opener || window.parent).postMessage(originInfo, '*');
(window.opener || window.parent).postMessage(JSON.parse(authResponse), origin);
window.close();
`;
const hash = crypto.createHash('sha256').update(script).digest('base64');
@@ -15,3 +15,5 @@
*/
export { ensuresXRequestedWith, postMessageResponse } from './authFlowHelpers';
export type { WebMessageResponse } from './types';
@@ -149,12 +149,12 @@ export class Auth0AuthProvider implements OAuthHandlers {
}
export const createAuth0Provider: AuthProviderFactory = ({
providerId,
globalConfig,
config,
tokenIssuer,
}) =>
OAuthEnvironmentHandler.mapConfig(config, envConfig => {
const providerId = 'auth0';
const clientId = envConfig.getString('clientId');
const clientSecret = envConfig.getString('clientSecret');
const domain = envConfig.getString('domain');
@@ -24,9 +24,9 @@ import { createSamlProvider } from './saml';
import { createAuth0Provider } from './auth0';
import { createMicrosoftProvider } from './microsoft';
import { createOneLoginProvider } from './onelogin';
import { AuthProviderFactory, AuthProviderFactoryOptions } from './types';
import { AuthProviderFactory } from './types';
const factories: { [providerId: string]: AuthProviderFactory } = {
export const factories: { [providerId: string]: AuthProviderFactory } = {
google: createGoogleProvider,
github: createGithubProvider,
gitlab: createGitlabProvider,
@@ -38,15 +38,3 @@ const factories: { [providerId: string]: AuthProviderFactory } = {
oidc: createOidcProvider,
onelogin: createOneLoginProvider,
};
export function createAuthProvider(
providerId: string,
options: AuthProviderFactoryOptions,
) {
const factory = factories[providerId];
if (!factory) {
throw Error(`No auth provider available for '${providerId}'`);
}
return factory(options);
}
@@ -137,12 +137,12 @@ export class GithubAuthProvider implements OAuthHandlers {
}
export const createGithubProvider: AuthProviderFactory = ({
providerId,
globalConfig,
config,
tokenIssuer,
}) =>
OAuthEnvironmentHandler.mapConfig(config, envConfig => {
const providerId = 'github';
const clientId = envConfig.getString('clientId');
const clientSecret = envConfig.getString('clientSecret');
const enterpriseInstanceUrl = envConfig.getOptionalString(
@@ -140,12 +140,12 @@ export class GitlabAuthProvider implements OAuthHandlers {
}
export const createGitlabProvider: AuthProviderFactory = ({
providerId,
globalConfig,
config,
tokenIssuer,
}) =>
OAuthEnvironmentHandler.mapConfig(config, envConfig => {
const providerId = 'gitlab';
const clientId = envConfig.getString('clientId');
const clientSecret = envConfig.getString('clientSecret');
const audience = envConfig.getString('audience');
@@ -175,6 +175,7 @@ export class GoogleAuthProvider implements OAuthHandlers {
}
export const createGoogleProvider: AuthProviderFactory = ({
providerId,
globalConfig,
config,
logger,
@@ -182,7 +183,6 @@ export const createGoogleProvider: AuthProviderFactory = ({
catalogApi,
}) =>
OAuthEnvironmentHandler.mapConfig(config, envConfig => {
const providerId = 'google';
const clientId = envConfig.getString('clientId');
const clientSecret = envConfig.getString('clientSecret');
const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
+13 -1
View File
@@ -14,4 +14,16 @@
* limitations under the License.
*/
export { createAuthProvider } from './factories';
export { factories as defaultAuthProviderFactories } from './factories';
// Export the minimal interface required for implementing a
// custom Authorization Handler
export type {
AuthProviderRouteHandlers,
AuthProviderFactoryOptions,
AuthProviderFactory,
} from './types';
// These types are needed for a postMessage from the login pop-up
// to the frontend
export type { AuthResponse, BackstageIdentity, ProfileInfo } from './types';
@@ -206,13 +206,12 @@ export class MicrosoftAuthProvider implements OAuthHandlers {
}
export const createMicrosoftProvider: AuthProviderFactory = ({
providerId,
globalConfig,
config,
tokenIssuer,
}) =>
OAuthEnvironmentHandler.mapConfig(config, envConfig => {
const providerId = 'microsoft';
const clientId = envConfig.getString('clientId');
const clientSecret = envConfig.getString('clientSecret');
const tenantID = envConfig.getString('tenantId');
@@ -157,12 +157,12 @@ export class OAuth2AuthProvider implements OAuthHandlers {
}
export const createOAuth2Provider: AuthProviderFactory = ({
providerId,
globalConfig,
config,
tokenIssuer,
}) =>
OAuthEnvironmentHandler.mapConfig(config, envConfig => {
const providerId = 'oauth2';
const clientId = envConfig.getString('clientId');
const clientSecret = envConfig.getString('clientSecret');
const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
@@ -171,12 +171,12 @@ export class OidcAuthProvider implements OAuthHandlers {
}
export const createOidcProvider: AuthProviderFactory = ({
providerId,
globalConfig,
config,
tokenIssuer,
}) =>
OAuthEnvironmentHandler.mapConfig(config, envConfig => {
const providerId = 'oidc';
const clientId = envConfig.getString('clientId');
const clientSecret = envConfig.getString('clientSecret');
const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
@@ -168,12 +168,12 @@ export class OktaAuthProvider implements OAuthHandlers {
}
export const createOktaProvider: AuthProviderFactory = ({
providerId,
globalConfig,
config,
tokenIssuer,
}) =>
OAuthEnvironmentHandler.mapConfig(config, envConfig => {
const providerId = 'okta';
const clientId = envConfig.getString('clientId');
const clientSecret = envConfig.getString('clientSecret');
const audience = envConfig.getString('audience');
@@ -147,12 +147,12 @@ export class OneLoginProvider implements OAuthHandlers {
}
export const createOneLoginProvider: AuthProviderFactory = ({
providerId,
globalConfig,
config,
tokenIssuer,
}) =>
OAuthEnvironmentHandler.mapConfig(config, envConfig => {
const providerId = 'onelogin';
const clientId = envConfig.getString('clientId');
const clientSecret = envConfig.getString('clientSecret');
const issuer = envConfig.getString('issuer');
@@ -121,12 +121,12 @@ type SAMLProviderOptions = {
};
export const createSamlProvider: AuthProviderFactory = ({
providerId,
globalConfig,
config,
tokenIssuer,
}) => {
const url = new URL(globalConfig.baseUrl);
const providerId = 'saml';
const entryPoint = config.getString('entryPoint');
const issuer = config.getString('issuer');
const opts = {
@@ -113,6 +113,7 @@ export interface AuthProviderRouteHandlers {
}
export type AuthProviderFactoryOptions = {
providerId: string;
globalConfig: AuthProviderConfig;
config: Config;
logger: Logger;
+23 -6
View File
@@ -14,6 +14,14 @@
* limitations under the License.
*/
import express from 'express';
import Router from 'express-promise-router';
import cookieParser from 'cookie-parser';
import { Logger } from 'winston';
import {
defaultAuthProviderFactories,
AuthProviderFactory,
} from '../providers';
import {
NotFoundError,
PluginDatabaseManager,
@@ -21,20 +29,18 @@ import {
} from '@backstage/backend-common';
import { CatalogClient } from '@backstage/catalog-client';
import { Config } from '@backstage/config';
import cookieParser from 'cookie-parser';
import express from 'express';
import Router from 'express-promise-router';
import { Logger } from 'winston';
import { createOidcRouter, DatabaseKeyStore, TokenFactory } from '../identity';
import { createAuthProvider } from '../providers';
import session from 'express-session';
import passport from 'passport';
type ProviderFactories = { [s: string]: AuthProviderFactory };
export interface RouterOptions {
logger: Logger;
database: PluginDatabaseManager;
config: Config;
discovery: PluginEndpointDiscovery;
providerFactories?: ProviderFactories;
}
export async function createRouter({
@@ -42,6 +48,7 @@ export async function createRouter({
config,
discovery,
database,
providerFactories,
}: RouterOptions): Promise<express.Router> {
const router = Router();
@@ -74,13 +81,23 @@ export async function createRouter({
router.use(express.urlencoded({ extended: false }));
router.use(express.json());
const allProviderFactories = {
...defaultAuthProviderFactories,
...providerFactories,
};
const providersConfig = config.getConfig('auth.providers');
const providers = providersConfig.keys();
for (const providerId of providers) {
logger.info(`Configuring provider, ${providerId}`);
try {
const provider = createAuthProvider(providerId, {
const providerFactory = allProviderFactories[providerId];
if (!providerFactory) {
throw Error(`No auth provider available for '${providerId}'`);
}
const provider = providerFactory({
providerId,
globalConfig: { baseUrl: authUrl, appUrl },
config: providersConfig.getConfig(providerId),
logger,
+29
View File
@@ -1,5 +1,34 @@
# @backstage/plugin-catalog-backend
## 0.2.3
### Patch Changes
- 1ec19a3f4: Ignore empty YAML documents. Having a YAML file like this is now ingested without an error:
```yaml
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: web
spec:
type: website
---
```
This behaves now the same way as Kubernetes handles multiple documents in a single YAML file.
- ab94c9542: Add `providesApis` and `consumesApis` to the component entity spec.
- 2daf18e80: Start emitting all known relation types from the core entity kinds, based on their spec data.
- Updated dependencies [3aa7efb3f]
- Updated dependencies [ab94c9542]
- Updated dependencies [2daf18e80]
- Updated dependencies [069cda35f]
- Updated dependencies [b3d4e4e57]
- @backstage/backend-common@0.3.2
- @backstage/catalog-model@0.3.1
## 0.2.2
### Patch Changes
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-backend",
"version": "0.2.2",
"version": "0.2.3",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -21,8 +21,8 @@
},
"dependencies": {
"@azure/msal-node": "^1.0.0-alpha.8",
"@backstage/backend-common": "^0.3.1",
"@backstage/catalog-model": "^0.3.0",
"@backstage/backend-common": "^0.3.2",
"@backstage/catalog-model": "^0.3.1",
"@backstage/config": "^0.1.1",
"@octokit/graphql": "^4.5.6",
"@types/express": "^4.17.6",
@@ -48,7 +48,7 @@
"yup": "^0.29.3"
},
"devDependencies": {
"@backstage/cli": "^0.3.1",
"@backstage/cli": "^0.3.2",
"@backstage/test-utils": "^0.1.3",
"@types/core-js": "^2.5.4",
"@types/git-url-parse": "^9.0.0",
@@ -68,12 +68,14 @@ describe('BuiltinKindsEntityProcessor', () => {
owner: 'o',
lifecycle: 'l',
implementsApis: ['a'],
providesApis: ['b'],
consumesApis: ['c'],
},
};
await processor.postProcessEntity(entity, location, emit);
expect(emit).toBeCalledTimes(4);
expect(emit).toBeCalledTimes(8);
expect(emit).toBeCalledWith({
type: 'relation',
relation: {
@@ -106,6 +108,38 @@ describe('BuiltinKindsEntityProcessor', () => {
target: { kind: 'API', namespace: 'default', name: 'a' },
},
});
expect(emit).toBeCalledWith({
type: 'relation',
relation: {
source: { kind: 'API', namespace: 'default', name: 'b' },
type: 'apiProvidedBy',
target: { kind: 'Component', namespace: 'default', name: 'n' },
},
});
expect(emit).toBeCalledWith({
type: 'relation',
relation: {
source: { kind: 'Component', namespace: 'default', name: 'n' },
type: 'providesApi',
target: { kind: 'API', namespace: 'default', name: 'b' },
},
});
expect(emit).toBeCalledWith({
type: 'relation',
relation: {
source: { kind: 'API', namespace: 'default', name: 'c' },
type: 'apiConsumedBy',
target: { kind: 'Component', namespace: 'default', name: 'n' },
},
});
expect(emit).toBeCalledWith({
type: 'relation',
relation: {
source: { kind: 'Component', namespace: 'default', name: 'n' },
type: 'consumesApi',
target: { kind: 'API', namespace: 'default', name: 'c' },
},
});
});
it('generates relations for api entities', async () => {
@@ -26,8 +26,10 @@ import {
locationEntityV1alpha1Validator,
LocationSpec,
parseEntityRef,
RELATION_API_CONSUMED_BY,
RELATION_API_PROVIDED_BY,
RELATION_CHILD_OF,
RELATION_CONSUMES_API,
RELATION_HAS_MEMBER,
RELATION_MEMBER_OF,
RELATION_OWNED_BY,
@@ -138,6 +140,18 @@ export class BuiltinKindsEntityProcessor implements CatalogProcessor {
RELATION_PROVIDES_API,
RELATION_API_PROVIDED_BY,
);
doEmit(
component.spec.providesApis,
{ defaultKind: 'API', defaultNamespace: selfRef.namespace },
RELATION_PROVIDES_API,
RELATION_API_PROVIDED_BY,
);
doEmit(
component.spec.consumesApis,
{ defaultKind: 'API', defaultNamespace: selfRef.namespace },
RELATION_CONSUMES_API,
RELATION_API_CONSUMED_BY,
);
}
/*
@@ -115,6 +115,41 @@ describe('parseEntityYaml', () => {
]);
});
it('should handle empty yaml documents', () => {
// This happens if the user accidentially adds a "---"
// at the end of a file
const results = Array.from(
parseEntityYaml(
Buffer.from(
`
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: web
spec:
type: website
---
`,
'utf8',
),
testLoc,
),
);
expect(results).toEqual([
result.entity(testLoc, {
apiVersion: 'backstage.io/v1alpha1',
kind: 'Component',
metadata: {
name: 'web',
},
spec: {
type: 'website',
},
}),
]);
});
it('should emit parsing errors', () => {
const results = Array.from(
parseEntityYaml(Buffer.from('`', 'utf8'), testLoc),
@@ -40,6 +40,9 @@ export function* parseEntityYaml(
const json = document.toJSON();
if (lodash.isPlainObject(json)) {
yield result.entity(location, json as Entity);
} else if (json === null) {
// Ignore null values, these happen if there is an empty document in the
// YAML file, for example if --- is added to the end of the file.
} else {
const message = `Expected object at root, got ${typeof json}`;
yield result.generalError(location, message);
+12
View File
@@ -1,5 +1,17 @@
# @backstage/plugin-catalog
## 0.2.4
### Patch Changes
- 6f70ed7a9: Replace usage of implementsApis with relations
- Updated dependencies [4b53294a6]
- Updated dependencies [ab94c9542]
- Updated dependencies [2daf18e80]
- Updated dependencies [069cda35f]
- @backstage/plugin-techdocs@0.3.0
- @backstage/catalog-model@0.3.1
## 0.2.3
### Patch Changes
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog",
"version": "0.2.3",
"version": "0.2.4",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -22,10 +22,10 @@
},
"dependencies": {
"@backstage/catalog-client": "^0.3.1",
"@backstage/catalog-model": "^0.3.0",
"@backstage/catalog-model": "^0.3.1",
"@backstage/core": "^0.3.2",
"@backstage/plugin-scaffolder": "^0.3.1",
"@backstage/plugin-techdocs": "^0.2.3",
"@backstage/plugin-techdocs": "^0.3.0",
"@backstage/theme": "^0.2.1",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
@@ -43,7 +43,7 @@
"swr": "^0.3.0"
},
"devDependencies": {
"@backstage/cli": "^0.3.1",
"@backstage/cli": "^0.3.2",
"@backstage/dev-utils": "^0.1.4",
"@backstage/test-utils": "^0.1.3",
"@microsoft/microsoft-graph-types": "^1.25.0",
@@ -18,6 +18,7 @@ import {
Entity,
ENTITY_DEFAULT_NAMESPACE,
RELATION_OWNED_BY,
RELATION_PROVIDES_API,
serializeEntityRef,
} from '@backstage/catalog-model';
import {
@@ -111,6 +112,8 @@ type AboutCardProps = {
export function AboutCard({ entity, variant }: AboutCardProps) {
const classes = useStyles();
const codeLink = getCodeLinkInfo(entity);
// TODO: Also support RELATION_CONSUMES_API here
const hasApis = entity.relations?.some(r => r.type === RELATION_PROVIDES_API);
return (
<Card className={variant === 'gridItem' ? classes.gridItemCard : ''}>
@@ -146,9 +149,9 @@ export function AboutCard({ entity, variant }: AboutCardProps) {
}/${entity.kind}/${entity.metadata.name}`}
/>
<IconLinkVertical
disabled={!entity.spec?.implementsApis}
disabled={!hasApis}
label="View API"
title={!entity.spec?.implementsApis ? 'No APIs available' : ''}
title={hasApis ? '' : 'No APIs available'}
icon={<ExtensionIcon />}
href="api"
/>
+1 -1
View File
@@ -40,7 +40,7 @@
"react-use": "^15.3.3"
},
"devDependencies": {
"@backstage/cli": "^0.3.1",
"@backstage/cli": "^0.3.2",
"@backstage/dev-utils": "^0.1.4",
"@backstage/test-utils": "^0.1.3",
"@testing-library/jest-dom": "^5.10.1",
+1 -1
View File
@@ -39,7 +39,7 @@
"react-use": "^15.3.3"
},
"devDependencies": {
"@backstage/cli": "^0.3.1",
"@backstage/cli": "^0.3.2",
"@backstage/dev-utils": "^0.1.4",
"@backstage/test-utils": "^0.1.3",
"@testing-library/jest-dom": "^5.10.1",
+1 -1
View File
@@ -46,7 +46,7 @@
"yup": "^0.29.3"
},
"devDependencies": {
"@backstage/cli": "^0.3.1",
"@backstage/cli": "^0.3.2",
"@backstage/dev-utils": "^0.1.4",
"@backstage/test-utils": "^0.1.3",
"@testing-library/jest-dom": "^5.10.1",
+1 -1
View File
@@ -33,7 +33,7 @@
"react-use": "^15.3.3"
},
"devDependencies": {
"@backstage/cli": "^0.3.1",
"@backstage/cli": "^0.3.2",
"@backstage/dev-utils": "^0.1.4",
"@backstage/test-utils": "^0.1.3",
"@testing-library/jest-dom": "^5.10.1",
+1 -1
View File
@@ -31,7 +31,7 @@
"react-use": "^15.3.3"
},
"devDependencies": {
"@backstage/cli": "^0.3.1",
"@backstage/cli": "^0.3.2",
"@backstage/dev-utils": "^0.1.4",
"@backstage/test-utils": "^0.1.3",
"@testing-library/jest-dom": "^5.10.1",
+1 -1
View File
@@ -40,7 +40,7 @@
"react-use": "^15.3.3"
},
"devDependencies": {
"@backstage/cli": "^0.3.1",
"@backstage/cli": "^0.3.2",
"@backstage/dev-utils": "^0.1.4",
"@backstage/test-utils": "^0.1.3",
"@testing-library/jest-dom": "^5.10.1",
+1 -1
View File
@@ -32,7 +32,7 @@
"react-use": "^15.3.3"
},
"devDependencies": {
"@backstage/cli": "^0.3.1",
"@backstage/cli": "^0.3.2",
"@backstage/dev-utils": "^0.1.4",
"@backstage/test-utils": "^0.1.3",
"@testing-library/jest-dom": "^5.10.1",
+1 -1
View File
@@ -43,7 +43,7 @@
"react-use": "^15.3.3"
},
"devDependencies": {
"@backstage/cli": "^0.3.1",
"@backstage/cli": "^0.3.2",
"@backstage/dev-utils": "^0.1.4",
"@backstage/test-utils": "^0.1.3",
"@testing-library/jest-dom": "^5.10.1",
+1 -1
View File
@@ -36,7 +36,7 @@
"react-use": "^15.3.3"
},
"devDependencies": {
"@backstage/cli": "^0.3.1",
"@backstage/cli": "^0.3.2",
"@backstage/dev-utils": "^0.1.4",
"@backstage/test-utils": "^0.1.3",
"@testing-library/jest-dom": "^5.10.1",

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