Merge remote-tracking branch 'upstream/master' into mcalus3/add-catalog-import-plugin
This commit is contained in:
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
Make versions:bump install new versions of dependencies that were within the specified range
|
||||
@@ -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.
|
||||
@@ -230,3 +230,4 @@ yaml
|
||||
Zalando
|
||||
Zhou
|
||||
Zolotusky
|
||||
zoomable
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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.
|
||||
|
||||

|
||||
<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.
|
||||
|
||||

|
||||
<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.
|
||||
|
||||
@@ -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`
|
||||
|
||||
|
||||
@@ -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 `` syntax to `<img data-zoomable src="/microsite/static/img/code.png" alt="This is image" />`
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// Ref: https://github.com/francoischalifour/medium-zoom#options
|
||||
window.addEventListener(
|
||||
'load',
|
||||
() => {
|
||||
mediumZoom('[data-zoomable]', {
|
||||
margin: 20,
|
||||
background: '#000',
|
||||
});
|
||||
},
|
||||
false,
|
||||
);
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
Vendored
-7
@@ -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;
|
||||
/**
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
})),
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
Vendored
+45
@@ -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;
|
||||
}>;
|
||||
};
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
+5
-6
@@ -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>
|
||||
|
||||
@@ -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) || []
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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`;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
+35
-1
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user