Merge branch 'master' of https://github.com/spotify/backstage into samiram/pagerduty-plugin

This commit is contained in:
Samira Mokaram
2020-12-04 11:04:21 +01:00
396 changed files with 11718 additions and 2582 deletions
+8
View File
@@ -0,0 +1,8 @@
---
'@backstage/plugin-circleci': patch
'@backstage/plugin-jenkins': patch
---
Refactor to support ADR004 module exporting.
For more information, see https://backstage.io/docs/architecture-decisions/adrs-adr004.
+2
View File
@@ -131,6 +131,7 @@ npm
nvm
oauth
Oauth
oidc
Okta
Oldsberg
onboarding
@@ -229,3 +230,4 @@ yaml
Zalando
Zhou
Zolotusky
zoomable
+1 -1
View File
@@ -19,4 +19,4 @@ jobs:
# Calls out to `changeset version`, but also runs prettier
version: yarn release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }}
+1 -1
View File
@@ -6,4 +6,4 @@ registry "https://registry.npmjs.org/"
disable-self-update-check true
lastUpdateCheck 1580389148099
yarn-path ".yarn/releases/yarn-1.22.1.js"
network-timeout 600000
network-timeout 300000
+2 -2
View File
@@ -1,6 +1,6 @@
# Process for becoming a maintainer
## Your organization is not yet a maintainer
## a) Your organization is not yet a maintainer
- Express interest to the sponsors that your organization is interested in becoming a maintainer. Becoming a maintainer generally means that you are going to be spending substantial time on Backstage for the foreseeable future. You should have domain expertise and be extremely proficient in TypeScript.
- We will expect you to start contributing increasingly complicated PRs, under the guidance of the existing maintainers.
@@ -8,7 +8,7 @@
- As you gain experience with the code base and our standards, we will ask you to do code reviews for incoming PRs.
- After a period of approximately 2-3 months of working together and making sure we see eye to eye, the existing sponsors and maintainers will confer and decide whether to grant maintainer status or not. We make no guarantees on the length of time this will take, but 2-3 months is the approximate goal.
## Your organization is currently a maintainer
## b) Your organization is currently a maintainer
To become a maintainer you need to demonstrate the following:
+6 -3
View File
@@ -38,7 +38,7 @@ proxy:
headers:
Authorization:
$env: TRAVISCI_AUTH_TOKEN
travis-api-version: 3
travis-api-version: '3'
'/newrelic/apm/api':
target: https://api.newrelic.com/v2
@@ -72,8 +72,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
@@ -296,6 +296,9 @@ costInsights:
bigQuery:
name: BigQuery
icon: search
events:
name: Events
icon: data
metrics:
DAU:
name: Daily Active Users
+103 -44
View File
@@ -6,57 +6,71 @@ description: Documentation on Auth backend classes
## How Does Authentication Work?
The Backstage application can use various authentication providers for
authentication. A provider has to implement an `AuthProviderRouteHandlers`
interface for handling authentication. This interface consists of four methods.
Each of these methods is hosted at an endpoint `/auth/[provider]/method`, where
`method` performs a certain operation as follows:
The Backstage application can use various external authentication providers for
authentication. An external provider is wrapped using an
`AuthProviderRouteHandlers` interface for handling authentication. This
interface consists of four methods. Each of these methods is hosted at an
endpoint (by default) `/api/auth/[provider]/method`, where `method` performs a
certain operation as follows:
```
/auth/[provider]/start -> start
/auth/[provider]/handler/frame -> frameHandler
/auth/[provider]/refresh -> refresh
/auth/[provider]/logout -> logout
/auth/[provider]/start -> Initiate a login from the web page
/auth/[provider]/handler/frame -> Handle a finished authentication operation
/auth/[provider]/refresh -> Refresh the validity of a login
/auth/[provider]/logout -> Log out a logged-in user
```
For more information on how these methods are used and for which purpose, refer
to the [OAuth documentation](oauth.md).
The flow is as follows:
For details on the parameters, input and output conditions for each method,
refer to the type documentation under
`plugins/auth-backend/src/providers/types.ts`.
1. A user attempts to sign in.
2. A popup window is opened, pointing to the `auth` endpoint. That endpoint does
initial preparations and then re-directs the user to an external
authenticator, still inside the popup.
3. The authenticator validates the user and returns the result of the validation
(success OR failure), to the wrapper's endpoint (`handler/frame`).
4. The `handler/frame` rendered b´webpage will issue the appropriate response to
the webpage that opened the popup window, and the popup is closed.
5. The user signs out by clicking on a UI interface and the webpage makes a
request to logout the user.
There are currently two different classes for two authentication mechanisms that
implement this interface: an `OAuthAdapter` for [OAuth](https://oauth.net/2/)
based mechanisms and a `SAMLAuthProvider` for
[SAML](http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0.html)
based mechanisms.
[SAML](http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0.html).
### OAuth mechanisms
If you do not have an `OAuth2` or `SAML` based authentication provider, look in
the section [below](#implementing-your-own-auth-wrapper).
### OAuth Mechanisms
For more information on how these methods are used and for which purpose, refer
to the [OAuth documentation](oauth.md).
Currently OAuth is assumed to be the de facto authentication mechanism for
Backstage based applications.
Backstage comes with a "batteries-included" set of supported commonly used OAuth
providers: Okta, GitHub, Google, GitLab, and a generic OAuth2 provider.
providers: Okta, GitHub, Google, GitLab, and a generic OAuth2 provider. For a
list of available providers, look at the available wrappers in
`backstage/plugins/auth-backend/src/providers/`.
All of these use the authorization flow of OAuth2 to implement authentication.
All of these use the **authorization flow** of OAuth2 to implement
authentication.
If your authentication provider is any of the above mentioned (except generic
OAuth2) providers, you can configure them by setting the right variables in
`app-config.yaml` under the `auth` section.
If your authentication provider is any of the above mentioned providers, you can
configure them by setting the right variables in `app-config.yaml` under the
`auth` section.
### Configuration
Each authentication provider (except SAML) needs five parameters: an OAuth
client ID, a client secret, an authorization endpoint and a token endpoint, and
an app origin. The app origin is the URL at which the frontend of the
application is hosted, and it is read from the `app.baseUrl` config. This is
required because the application opens a popup window to perform the
authentication, and once the flow is completed, the popup window sends a
`postMessage` to the frontend application to indicate the result of the
operation. Also this URL is used to verify that authentication requests are
coming from only this endpoint.
client ID, a client secret, an authorization endpoint, a token endpoint, and an
app origin. The app origin is the URL at which the frontend of the application
is hosted, and it is read from the `app.baseUrl` config. This is required
because the application opens a popup window to perform the authentication, and
once the flow is completed, the popup window sends a `postMessage` to the
frontend application to indicate the result of the operation. Also this URL is
used to verify that authentication requests are coming from only this endpoint.
These values are configured via the `app-config.yaml` present in the root of
your app folder.
@@ -85,20 +99,60 @@ auth:
...
```
## Technical Notes
## Implementing Your Own Auth Wrapper
### OAuthEnvironmentHandler
The core interface of any auth wrapper is the `AuthProviderRouteHandlers`
interface. This interface has four methods corresponding to the API described in
the initial section. Any auth wrapper will have to implement this interface.
The concept of an "env" is core to the way the auth backend works. It uses an
When initiating a login, a pop-up window is created by the frontend, to allow
the user to initiate a login. This login request is done to the `/start`
endpoint which is handled by the `start` method.
The `start` method re-directs to the external auth provider who authenticates
the request and re-directs the request to the `/frame/handler` endpoint, which
is handled by the `frameHandler` method.
The `frameHandler` returns an HTML response, containing a script that does a
`postMessage` to the frontend's window, containing the result of the request.
The `WebMessageResponse` type is the message sent by the `postMessage` to the
frontend.
A `postMessageResponse` utility function wraps the logic of generating a
`postMessage` response that ensures that CORS is successfully handled. This
function takes an `express.Response`, a `WebMessageResponse` and the URL of the
frontend (`appOrigin`) as parameters and return an HTML page with the script and
the message.
### OAuth Wrapping Interfaces.
Each OAuth external provider is supported by a corresponding
[Passport](https://github.com/jaredhanson/passport) strategy. For a generic
OAuth2 provider, passport has a `passport-oauth2` strategy. The strategy class
handles the implementation details of working with each provider.
Each strategy is wrapped by an `OAuthHandlers` interface.
This interface cannot be directly used as an Express HTTP request handler. To do
so, `OAuthHandlers` are wrapped in an `OAuthAdapter`, which implements the
`AuthProviderRouterHandlers` interface.
#### Env
The concept of an `env` is core to the way the auth backend works. It uses an
`env` query parameter to identify the environment in which the application is
running (`development`, `staging`, `production`, etc). Each runtime can support
multiple environments at the same time and the right handler for each request is
identified and dispatched to based on the `env` parameter. All
`AuthProviderRouteHandlers` are wrapped within an `OAuthEnvironmentHandler`.
running (`development`, `staging`, `production`, etc). Each runtime can
simultaneously support multiple environments at the same time and the right
handler for each request is identified and dispatched to, based on the `env`
parameter.
To instantiate multiple OAuth providers for different environments, use
`OAuthEnvironmentHandler` is a utility wrapper for an `OAuthHandlers` that
implements the `AuthProviderRouteHandlers` interface while supporting multiple
`env`s.
To instantiate OAuth providers (the same but for different environments), use
`OAuthEnvironmentHandler.mapConfig`. It's a helper to iterate over a
configuration object that is a map of environment to configurations. See one of
configuration object that is a map of environments to configurations. See one of
the existing OAuth providers for an example of how it is used.
Given the following configuration:
@@ -113,13 +167,18 @@ production:
```
The `OAuthEnvironmentHandler.mapConfig(config, envConfig => ...)` call will
split the `config` by the top level `development` and `production` keys, and
pass on each block as `envConfig`.
split the config by the top level `development` and `production` keys, and pass
on each block as `envConfig`.
For a list of currently available providers, look in the `factories` module
located in `plugins/auth-backend/src/providers/factories.ts`
For convenience, the `AuthProviderFactory` is a factory function that has to be
implemented which can then generate a `AuthProviderRouteHandlers` for a given
provider.
### OAuth2 provider
All of the supported providers provide an `AuthProviderFactory` that returns an
`OAuthEnvironmentHandler`, capable of handling authentication for multiple
environments.
### OAuth2 Provider
The `oauth2` provider abstracts a generic **OAuth2 + OIDC** based authentication
provider. What this means is that after the application has been given
+1 -1
View File
@@ -93,6 +93,6 @@ sign-in methods.
More details are provided in dedicated sections of the documentation.
- [OAuth](./oauth.md): Description of the generic OAuth flow implemented by the
[auth-backend](../../plugins/auth-backend).
[auth-backend](https://github.com/backstage/backstage/tree/master/plugins/auth-backend).
- [Glossary](./glossary.md): Glossary of some common terms related to the auth
flows.
@@ -31,6 +31,7 @@ we recommend that you name them `catalog-info.yaml`.
- [Kind: Resource](#kind-resource)
- [Kind: System](#kind-system)
- [Kind: Domain](#kind-domain)
- [Kind: Location](#kind-location)
## Overall Shape Of An Entity
@@ -381,7 +382,7 @@ spec:
type: website
lifecycle: production
owner: artist-relations@example.com
implementsApis:
providesApis:
- artist-api
```
@@ -445,12 +446,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:
@@ -861,3 +885,58 @@ This kind is not yet defined, but is reserved [for future use](system-model.md).
## Kind: Domain
This kind is not yet defined, but is reserved [for future use](system-model.md).
## Kind: Location
Describes the following entity kind:
| Field | Value |
| ------------ | ----------------------- |
| `apiVersion` | `backstage.io/v1alpha1` |
| `kind` | `Location` |
A location is a marker that references other places to look for catalog data.
Descriptor files for this kind may look as follows.
```yaml
apiVersion: backstage.io/v1alpha1
kind: Location
metadata:
name: org-data
spec:
type: url
targets:
- http://github.com/myorg/myproject/org-data-dump/catalog-info-staff.yaml
- http://github.com/myorg/myproject/org-data-dump/catalog-info-consultants.yaml
```
In addition to the [common envelope metadata](#common-to-all-kinds-the-metadata)
shape, this kind has the following structure.
### `apiVersion` and `kind` [required]
Exactly equal to `backstage.io/v1alpha1` and `Location`, respectively.
### `spec.type` [optional]
The single location type, that's common to the targets specified in the spec. If
it is left out, it is inherited from the location type that originally read the
entity data. For example, if you have a `url` type location, that when read
results in a `Location` kind entity with no `spec.type`, then the referenced
targets in the entity will implicitly also be of `url` type. This is useful
because you can define a hierarchy of things in a directory structure using
relative target paths (see below), and it will work out no matter if it's
consumed locally on disk from a `file` location, or as uploaded on a VCS.
### `spec.target` [optional]
A single target as a string. Can be either an absolute path/URL (depending on
the type), or a relative path such as `./details/catalog-info.yaml` which is
resolved relative to the location of this Location entity itself.
### `spec.targets` [optional]
A list of targets as strings. They can all be either absolute paths/URLs
(depending on the type), or relative paths such as `./details/catalog-info.yaml`
which are resolved relative to the location of this Location entity itself.
+2 -2
View File
@@ -51,7 +51,7 @@ spec:
type: service
lifecycle: experimental
owner: group:pet-managers
implementsApis:
providesApis:
- petstore
- internal/streetlights
- hello-world
@@ -66,7 +66,7 @@ catalog that is of kind `Group`, namespace `default` (which, actually, also can
be left out in its own yaml file because that's the default value there too),
and name `pet-managers`.
The entries in `implementsApis` are also references. In this case, none of them
The entries in `providesApis` are also references. In this case, none of them
needs to specify a kind since we know from the context that that's the only kind
that's supported here. The second entry specifies a namespace but the other ones
don't, and in this context, the default is to refer to the same namespace as the
@@ -45,17 +45,28 @@ entity, but there will always be one ultimate owner.
This relation is commonly generated based on `spec.owner` of the owned entity,
where present.
### `consumesApi` and `providesApi`
### `providesApi` and `apiProvidedBy`
A relation with an [API](descriptor-format.md#kind-api) entity, typically from a
[Component](descriptor-format.md#kind-component) or
[System](descriptor-format.md#kind-system).
These relations express that a component or system either exposes an API -
meaning that it hosts callable endpoints from which you can consume that API -
or that they are dependent on being able to consume that API.
These relations express that a component or system exposes an API - meaning that
it hosts callable endpoints from which you can consume that API.
This relation is commonly generated based on `spec.implementsApis` of the
This relation is commonly generated based on `spec.providesApis` of the
component or system in question.
### `consumesApi` and `apiConsumedBy`
A relation with an [API](descriptor-format.md#kind-api) entity, typically from a
[Component](descriptor-format.md#kind-component) or
[System](descriptor-format.md#kind-system).
These relations express that a component or system consumes an API - meaning
that it depends on endpoints of the API.
This relation is commonly generated based on `spec.consumesApis` of the
component or system in question.
### `dependsOn` and `dependencyOf`
+2 -2
View File
@@ -9,7 +9,7 @@ description: Documentation on TechDocs Architecture
When you deploy Backstage (with TechDocs enabled by default), you get a basic
out-of-the box experience.
![TechDocs Architecture diagram](../../assets/techdocs/architecture-basic.drawio.svg)
<img data-zoomable src="../../assets/techdocs/architecture-basic.drawio.svg" alt="TechDocs Architecture diagram" />
> Note: See below for our recommended deployment architecture which takes care
> of stability, scalability and speed.
@@ -43,7 +43,7 @@ channel to talk about it.
This is how we recommend deploying TechDocs in production environment.
![TechDocs Architecture diagram](../../assets/techdocs/architecture-recommended.drawio.svg)
<img data-zoomable src="../../assets/techdocs/architecture-recommended.drawio.svg" alt="TechDocs Architecture diagram" />
The key difference in the recommended deployment approach is where the docs are
built.
@@ -6,7 +6,53 @@ description: Documentation on How Configuring App with plugins
## Adding existing plugins to your app
Coming soon!
The following steps assume that you have created a new Backstage app and want to
add an existing plugin to it. We are using the
[CircleCI](https://github.com/backstage/backstage/blob/master/plugins/circleci/README.md)
plugin in this example.
1. Add the plugin's NPM package to the repo:
```bash
yarn add @backstage/plugin-circleci
```
2. Add the plugin itself:
```js
// packages/app/src/plugins.ts
export { plugin as Circleci } from '@backstage/plugin-circleci';
```
3. Register the plugin router:
```jsx
// packages/app/src/components/catalog/EntityPage.tsx
import { Router as CircleCIRouter } from '@backstage/plugin-circleci';
// Then somewhere inside <EntityPageLayout>
<EntityPageLayout.Content
path="/ci-cd/*"
title="CI/CD"
element={<CircleCIRouter />}
/>;
```
Note that stand-alone plugins that are not "attached" to the Software Catalog
would be added outside the `EntityPage`.
4. [Optional] Add proxy config:
```yaml
// app-config.yaml
proxy:
'/circleci/api':
target: https://circleci.com/api/v1.1
headers:
Circle-Token:
$env: CIRCLECI_AUTH_TOKEN
```
### Adding a plugin page to the Sidebar
+5 -5
View File
@@ -79,11 +79,11 @@ want to ensure some stability.
### [`cli`](https://github.com/backstage/backstage/tree/master/packages/cli/)
The main toolchain used for Backstage development. The interface that is
considered for stability are the various commands and options passed to those
commands, as well as the environment variables read by the CLI. The build output
may change over time and is not considered a breaking change unless it is likely
to affect external tooling.
The main toolchain used for Backstage development. The various CLI commands and
options passed to those commands, as well as the environment variables read by
the CLI, are considered to be the interface that the stability index refers to.
The build output may change over time and is not considered a breaking change
unless it is likely to affect external tooling.
Stability: `2`
+20 -13
View File
@@ -52,19 +52,7 @@ configuration will lead to the proxy acting on backend requests to
The value inside each route is either a simple URL string, or an object on the
format accepted by
[http-proxy-middleware](https://www.npmjs.com/package/http-proxy-middleware). It
is also possible to limit the forwarded HTTP methods with the configuration
`allowedMethods`, for example `allowedMethods: ['GET']` to enforce read-only
access.
By default, the proxy will only forward safe HTTP request headers to the target.
Those are based on the headers that are considered safe for CORS and includes
headers like `content-type` or `last-modified`, as well as all headers that are
set by the proxy. If the proxy should forward other headers like
`authorization`, this must be enabled by the `allowedHeaders` config, for
example `allowedHeaders: ['Authorization']`. This should help to not
accidentally forward confidential headers (`cookie`, `X-Auth-Request-User`) to
third-parties.
[http-proxy-middleware](https://www.npmjs.com/package/http-proxy-middleware).
If the value is a string, it is assumed to correspond to:
@@ -85,3 +73,22 @@ except with the following caveats for convenience:
`'^/api/proxy/larger-example/v1/': '/'` is added. That means that a request to
`/api/proxy/larger-example/v1/some/path` will be translated to a request to
`http://larger.example.com:8080/svc.v1/some/path`.
There are also additional settings:
- `allowedMethods`: Limit the forwarded HTTP methods. For example
`allowedMethods: ['GET']` enforces read-only access.
- `allowedHeaders`: A list of headers that should be forwarded to and received
from the target.
By default, the proxy will only forward safe HTTP request headers to the target.
Those are based on the headers that are considered safe for CORS and includes
headers like `content-type` or `last-modified`, as well as all headers that are
set by the proxy. If the proxy should forward other headers like
`authorization`, this must be enabled by the `allowedHeaders` config, for
example `allowedHeaders: ['Authorization']`. This should help to not
accidentally forward confidential headers (`cookie`, `X-Auth-Request-User`) to
third-parties.
The same logic applies to headers that are sent from the target back to the
frontend.
+9 -5
View File
@@ -4,7 +4,7 @@ title: createPlugin
description: Documentation on createPlugin
---
Taking a plugin config as argument and returns a new plugin.
Takes a plugin config as an argument and returns a new plugin.
## Plugin Config
@@ -28,18 +28,22 @@ type PluginHooks = {
### Creating a basic plugin
Showcasing adding multiple routes, a feature flag and a redirect.
Showcasing adding a route and a feature flag.
```jsx
import { createPlugin } from '@backstage/core';
import { createPlugin, createRouteRef } from '@backstage/core';
import ExampleComponent from './components/ExampleComponent';
export const rootRouteRef = createRouteRef({
path: '/new-plugin',
title: 'New Plugin',
});
export default createPlugin({
id: 'new-plugin',
register({ router, featureFlags }) {
router.addRoute(rootRouteRef, ExampleComponent);
featureFlags.register('enable-example-component');
router.registerRoute('/new-plugin', ExampleComponent);
},
});
```
+5
View File
@@ -204,3 +204,8 @@ For more information about custom pages, click [here](https://docusaurus.io/docs
# Full Documentation
Full documentation can be found on the [website](https://docusaurus.io/).
## Additional notes
- If you want to make images zoomable on click, add the `data-zoomable` attribute to your `img` element.
- In a docs or blog `.md` file, convert `![This is image](/microsite/static/img/code.png)` syntax to `<img data-zoomable src="/microsite/static/img/code.png" alt="This is image" />`
+12
View File
@@ -0,0 +1,12 @@
---
title: Kubernetes
author: Spotify
authorUrl: https://github.com/spotify
category: Kubernetes
description: Surfaces components in a Kubernetes container orchestration environment into the Backstage catalog.
documentation: https://github.com/backstage/backstage/tree/master/plugins/kubernetes
iconUrl: https://raw.githubusercontent.com/cncf/artwork/master/projects/kubernetes/icon/color/kubernetes-icon-color.png
npmPackageName: '@backstage/plugin-kubernetes'
tags:
- kubernetes
- k8s
+1 -1
View File
@@ -17,7 +17,7 @@
"@spotify/prettier-config": "^9.0.0",
"docusaurus": "^2.0.0-alpha.66",
"js-yaml": "^3.14.0",
"prettier": "^2.2.0"
"prettier": "^2.2.1"
},
"prettier": "@spotify/prettier-config"
}
+5 -1
View File
@@ -86,7 +86,11 @@ const siteConfig = {
},
// Add custom scripts here that would be placed in <script> tags.
scripts: ['https://buttons.github.io/buttons.js'],
scripts: [
'https://buttons.github.io/buttons.js',
'https://unpkg.com/medium-zoom@1.0.6/dist/medium-zoom.min.js',
'/js/medium-zoom.js',
],
// On page navigation for the current documentation page.
onPageNav: 'separate',
+5
View File
@@ -1094,3 +1094,8 @@ code {
margin: 0 1.5em;
}
}
/* Zoomed images using the medium-zoom library should be on top of screen. */
.medium-zoom-image {
z-index: 10000;
}
+11
View File
@@ -0,0 +1,11 @@
// Ref: https://github.com/francoischalifour/medium-zoom#options
window.addEventListener(
'load',
() => {
mediumZoom('[data-zoomable]', {
margin: 20,
background: '#000',
});
},
false,
);
+4 -4
View File
@@ -5204,10 +5204,10 @@ prepend-http@^2.0.0:
resolved "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=
prettier@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.2.0.tgz#8a03c7777883b29b37fb2c4348c66a78e980418b"
integrity sha512-yYerpkvseM4iKD/BXLYUkQV5aKt4tQPqaGW6EsZjzyu0r7sVZZNPJW4Y8MyKmicp6t42XUPcBVA+H6sB3gqndw==
prettier@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.2.1.tgz#795a1a78dd52f073da0cd42b21f9c91381923ff5"
integrity sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==
prismjs@^1.17.1:
version "1.21.0"
+62
View File
@@ -1,5 +1,67 @@
# example-app
## 0.2.5
### Patch Changes
- Updated dependencies [7eb8bfe4a]
- Updated dependencies [fe7257ff0]
- Updated dependencies [a2cfa311a]
- Updated dependencies [69f38457f]
- Updated dependencies [bec334b33]
- Updated dependencies [303c5ea17]
- Updated dependencies [b4488ddb0]
- Updated dependencies [4a655c89d]
- Updated dependencies [08835a61d]
- Updated dependencies [a9fd599f7]
- Updated dependencies [8a16e8af8]
- Updated dependencies [bcc211a08]
- Updated dependencies [00670a96e]
- Updated dependencies [da2ad65cb]
- Updated dependencies [ebf37bbae]
- @backstage/plugin-api-docs@0.3.1
- @backstage/plugin-cost-insights@0.4.2
- @backstage/plugin-sentry@0.2.4
- @backstage/plugin-welcome@0.2.2
- @backstage/cli@0.4.0
- @backstage/catalog-model@0.4.0
- @backstage/plugin-catalog-import@0.3.0
- @backstage/plugin-scaffolder@0.3.2
- @backstage/plugin-kubernetes@0.3.1
- @backstage/plugin-techdocs@0.3.1
- @backstage/plugin-catalog@0.2.5
- @backstage/test-utils@0.1.4
- @backstage/plugin-circleci@0.2.3
- @backstage/plugin-cloudbuild@0.2.3
- @backstage/plugin-github-actions@0.2.3
- @backstage/plugin-jenkins@0.3.2
- @backstage/plugin-lighthouse@0.2.4
- @backstage/plugin-register-component@0.2.3
- @backstage/plugin-rollbar@0.2.5
- @backstage/plugin-search@0.2.2
## 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
+22 -21
View File
@@ -1,43 +1,44 @@
{
"name": "example-app",
"version": "0.2.3",
"version": "0.2.5",
"private": true,
"bundled": true,
"dependencies": {
"@backstage/catalog-model": "^0.3.0",
"@backstage/cli": "^0.3.1",
"@backstage/catalog-model": "^0.4.0",
"@backstage/cli": "^0.4.0",
"@backstage/core": "^0.3.2",
"@backstage/plugin-api-docs": "^0.2.2",
"@backstage/plugin-catalog": "^0.2.3",
"@backstage/plugin-circleci": "^0.2.2",
"@backstage/plugin-cloudbuild": "^0.2.2",
"@backstage/plugin-cost-insights": "^0.4.1",
"@backstage/plugin-api-docs": "^0.3.1",
"@backstage/plugin-catalog": "^0.2.5",
"@backstage/plugin-catalog-import": "^0.3.0",
"@backstage/plugin-circleci": "^0.2.3",
"@backstage/plugin-cloudbuild": "^0.2.3",
"@backstage/plugin-cost-insights": "^0.4.2",
"@backstage/plugin-explore": "^0.2.1",
"@backstage/plugin-gcp-projects": "^0.2.1",
"@backstage/plugin-github-actions": "^0.2.2",
"@backstage/plugin-github-actions": "^0.2.3",
"@backstage/plugin-gitops-profiles": "^0.2.1",
"@backstage/plugin-graphiql": "^0.2.1",
"@backstage/plugin-jenkins": "^0.3.1",
"@backstage/plugin-kubernetes": "^0.3.0",
"@backstage/plugin-lighthouse": "^0.2.3",
"@backstage/plugin-jenkins": "^0.3.2",
"@backstage/plugin-kubernetes": "^0.3.1",
"@backstage/plugin-lighthouse": "^0.2.4",
"@backstage/plugin-newrelic": "^0.2.1",
"@backstage/plugin-pagerduty": "0.2.1",
"@backstage/plugin-register-component": "^0.2.2",
"@backstage/plugin-rollbar": "^0.2.3",
"@backstage/plugin-scaffolder": "^0.3.1",
"@backstage/plugin-search": "^0.2.1",
"@backstage/plugin-sentry": "^0.2.3",
"@backstage/plugin-register-component": "^0.2.3",
"@backstage/plugin-rollbar": "^0.2.5",
"@backstage/plugin-scaffolder": "^0.3.2",
"@backstage/plugin-sentry": "^0.2.4",
"@backstage/plugin-search": "^0.2.2",
"@backstage/plugin-tech-radar": "^0.3.0",
"@backstage/plugin-techdocs": "^0.2.3",
"@backstage/plugin-techdocs": "^0.3.1",
"@backstage/plugin-user-settings": "^0.2.2",
"@backstage/plugin-welcome": "^0.2.1",
"@backstage/test-utils": "^0.1.3",
"@backstage/plugin-welcome": "^0.2.2",
"@backstage/test-utils": "^0.1.4",
"@backstage/theme": "^0.2.1",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@octokit/rest": "^18.0.0",
"@roadiehq/backstage-plugin-buildkite": "^0.1.3",
"@roadiehq/backstage-plugin-github-insights": "^0.2.15",
"@roadiehq/backstage-plugin-github-insights": "^0.2.16",
"@roadiehq/backstage-plugin-github-pull-requests": "^0.6.3",
"@roadiehq/backstage-plugin-travis-ci": "^0.2.8",
"history": "^5.0.0",
+5
View File
@@ -34,6 +34,7 @@ import { Router as TechRadarRouter } from '@backstage/plugin-tech-radar';
import { Router as LighthouseRouter } from '@backstage/plugin-lighthouse';
import { Router as RegisterComponentRouter } from '@backstage/plugin-register-component';
import { Router as SettingsRouter } from '@backstage/plugin-user-settings';
import { Router as ImportComponentRouter } from '@backstage/plugin-catalog-import';
import { Route, Routes, Navigate } from 'react-router';
import { EntityPage } from './components/catalog/EntityPage';
@@ -67,6 +68,10 @@ const catalogRouteRef = createRouteRef({
const AppRoutes = () => (
<Routes>
<Navigate key="/" to="/catalog" />
<Route
path="/catalog-import/*"
element={<ImportComponentRouter catalogRouteRef={catalogRouteRef} />}
/>
<Route
path={`${catalogRouteRef.path}/*`}
element={<CatalogRouter EntityPage={EntityPage} />}
@@ -13,67 +13,70 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ApiEntity, Entity } from '@backstage/catalog-model';
import { EmptyState } from '@backstage/core';
import {
isPluginApplicableToEntity as isTravisCIAvailable,
RecentTravisCIBuildsWidget,
Router as TravisCIRouter,
} from '@roadiehq/backstage-plugin-travis-ci';
ApiDefinitionCard,
Router as ApiDocsRouter,
} from '@backstage/plugin-api-docs';
import {
AboutCard,
EntityPageLayout,
useEntity,
} from '@backstage/plugin-catalog';
import {
isPluginApplicableToEntity as isCircleCIAvailable,
Router as CircleCIRouter,
} from '@backstage/plugin-circleci';
import {
isPluginApplicableToEntity as isCloudbuildAvailable,
Router as CloudbuildRouter,
} from '@backstage/plugin-cloudbuild';
import {
isPluginApplicableToEntity as isGitHubActionsAvailable,
RecentWorkflowRunsCard,
Router as GitHubActionsRouter,
} from '@backstage/plugin-github-actions';
import {
Router as CloudbuildRouter,
isPluginApplicableToEntity as isCloudbuildAvailable,
} from '@backstage/plugin-cloudbuild';
import {
Router as JenkinsRouter,
isPluginApplicableToEntity as isJenkinsAvailable,
LatestRunCard as JenkinsLatestRunCard,
Router as JenkinsRouter,
} from '@backstage/plugin-jenkins';
import {
isPluginApplicableToEntity as isCircleCIAvailable,
Router as CircleCIRouter,
} from '@backstage/plugin-circleci';
import { Router as ApiDocsRouter } from '@backstage/plugin-api-docs';
import { Router as SentryRouter } from '@backstage/plugin-sentry';
import { EmbeddedDocsRouter as DocsRouter } from '@backstage/plugin-techdocs';
import { Router as KubernetesRouter } from '@backstage/plugin-kubernetes';
import {
Router as GitHubInsightsRouter,
isPluginApplicableToEntity as isGitHubAvailable,
ReadMeCard,
LanguagesCard,
ReleasesCard,
} from '@roadiehq/backstage-plugin-github-insights';
import React, { ReactNode } from 'react';
import {
AboutCard,
EntityPageLayout,
useEntity,
} from '@backstage/plugin-catalog';
import { Entity } from '@backstage/catalog-model';
import { Button, Grid } from '@material-ui/core';
import { EmptyState } from '@backstage/core';
import {
EmbeddedRouter as LighthouseRouter,
LastLighthouseAuditCard,
isPluginApplicableToEntity as isLighthouseAvailable,
} from '@backstage/plugin-lighthouse/';
LastLighthouseAuditCard,
} from '@backstage/plugin-lighthouse';
import { Router as SentryRouter } from '@backstage/plugin-sentry';
import { EmbeddedDocsRouter as DocsRouter } from '@backstage/plugin-techdocs';
import { Button, Grid } from '@material-ui/core';
import {
isPluginApplicableToEntity as isBuildkiteAvailable,
Router as BuildkiteRouter,
} from '@roadiehq/backstage-plugin-buildkite';
import {
isPluginApplicableToEntity as isGitHubAvailable,
LanguagesCard,
ReadMeCard,
ReleasesCard,
Router as GitHubInsightsRouter,
} from '@roadiehq/backstage-plugin-github-insights';
import {
Router as PullRequestsRouter,
isPluginApplicableToEntity as isPullRequestsAvailable,
PullRequestsStatsCard,
Router as PullRequestsRouter,
} from '@roadiehq/backstage-plugin-github-pull-requests';
import {
isPluginApplicableToEntity as isPagerDutyAvailable,
PagerDutyCard,
} from '@backstage/plugin-pagerduty';
import {
Router as BuildkiteRouter,
isPluginApplicableToEntity as isBuildkiteAvailable,
} from '@roadiehq/backstage-plugin-buildkite';
isPluginApplicableToEntity as isTravisCIAvailable,
RecentTravisCIBuildsWidget,
Router as TravisCIRouter,
} from '@roadiehq/backstage-plugin-travis-ci';
import React, { ReactNode } from 'react';
export const CICDSwitcher = ({ entity }: { entity: Entity }) => {
// This component is just an example of how you can implement your company's logic in entity page.
@@ -138,7 +141,7 @@ const RecentCICDRunsSwitcher = ({ entity }: { entity: Entity }) => {
);
};
const OverviewContent = ({ entity }: { entity: Entity }) => (
const ComponentOverviewContent = ({ entity }: { entity: Entity }) => (
<Grid container spacing={3} alignItems="stretch">
<Grid item md={6}>
<AboutCard entity={entity} variant="gridItem" />
@@ -178,7 +181,7 @@ const ServiceEntityPage = ({ entity }: { entity: Entity }) => (
<EntityPageLayout.Content
path="/"
title="Overview"
element={<OverviewContent entity={entity} />}
element={<ComponentOverviewContent entity={entity} />}
/>
<EntityPageLayout.Content
path="/ci-cd/*"
@@ -223,7 +226,7 @@ const WebsiteEntityPage = ({ entity }: { entity: Entity }) => (
<EntityPageLayout.Content
path="/"
title="Overview"
element={<OverviewContent entity={entity} />}
element={<ComponentOverviewContent entity={entity} />}
/>
<EntityPageLayout.Content
path="/ci-cd/*"
@@ -262,12 +265,13 @@ const WebsiteEntityPage = ({ entity }: { entity: Entity }) => (
/>
</EntityPageLayout>
);
const DefaultEntityPage = ({ entity }: { entity: Entity }) => (
<EntityPageLayout>
<EntityPageLayout.Content
path="/*"
title="Overview"
element={<OverviewContent entity={entity} />}
element={<ComponentOverviewContent entity={entity} />}
/>
<EntityPageLayout.Content
path="/docs/*"
@@ -277,8 +281,7 @@ const DefaultEntityPage = ({ entity }: { entity: Entity }) => (
</EntityPageLayout>
);
export const EntityPage = () => {
const { entity } = useEntity();
export const ComponentEntityPage = ({ entity }: { entity: Entity }) => {
switch (entity?.spec?.type) {
case 'service':
return <ServiceEntityPage entity={entity} />;
@@ -288,3 +291,47 @@ export const EntityPage = () => {
return <DefaultEntityPage entity={entity} />;
}
};
const ApiOverviewContent = ({ entity }: { entity: Entity }) => (
<Grid container spacing={3}>
<Grid item md={6}>
<AboutCard entity={entity} />
</Grid>
</Grid>
);
const ApiDefinitionContent = ({ entity }: { entity: ApiEntity }) => (
<Grid container spacing={3}>
<Grid item xs={12}>
<ApiDefinitionCard apiEntity={entity} />
</Grid>
</Grid>
);
const ApiEntityPage = ({ entity }: { entity: Entity }) => (
<EntityPageLayout>
<EntityPageLayout.Content
path="/*"
title="Overview"
element={<ApiOverviewContent entity={entity} />}
/>
<EntityPageLayout.Content
path="/definition/*"
title="Definition"
element={<ApiDefinitionContent entity={entity as ApiEntity} />}
/>
</EntityPageLayout>
);
export const EntityPage = () => {
const { entity } = useEntity();
switch (entity?.kind?.toLowerCase()) {
case 'component':
return <ComponentEntityPage entity={entity} />;
case 'api':
return <ApiEntityPage entity={entity} />;
default:
return <DefaultEntityPage entity={entity} />;
}
};
+1
View File
@@ -37,6 +37,7 @@ export { plugin as Kubernetes } from '@backstage/plugin-kubernetes';
export { plugin as Cloudbuild } from '@backstage/plugin-cloudbuild';
export { plugin as CostInsights } from '@backstage/plugin-cost-insights';
export { plugin as GitHubInsights } from '@roadiehq/backstage-plugin-github-insights';
export { plugin as CatalogImport } from '@backstage/plugin-catalog-import';
export { plugin as UserSettings } from '@backstage/plugin-user-settings';
export { plugin as PagerDuty } from '@backstage/plugin-pagerduty';
export { plugin as Buildkite } from '@roadiehq/backstage-plugin-buildkite';
+18
View File
@@ -1,5 +1,23 @@
# @backstage/backend-common
## 0.3.3
### Patch Changes
- 612368274: Allow the `backend.listen.port` config to be both a number or a string.
- Updated dependencies [4e7091759]
- Updated dependencies [b4488ddb0]
- @backstage/config-loader@0.4.0
## 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
+10 -10
View File
@@ -29,7 +29,7 @@ export interface Config {
/** Address of the interface that the backend should bind to. */
address?: string;
/** Port that the backend should listen to. */
port?: number;
port?: string | number;
};
/** HTTPS configuration for the backend. If omitted the backend will serve HTTP */
@@ -79,8 +79,15 @@ export interface Config {
optionsSuccessStatus?: number;
};
/** */
csp?: object;
/**
* Content Security Policy options.
*
* The keys are the plain policy ID, e.g. "upgrade-insecure-requests". The
* values are on the format that the helmet library expects them, as an
* array of strings. There is also the special value false, which means to
* remove the default value that Backstage puts in place for that policy.
*/
csp?: { [policyId: string]: string[] | false };
};
/** Configuration for integrations towards various external repository provider systems */
@@ -89,7 +96,6 @@ export interface Config {
azure?: Array<{
/**
* The hostname of the given Azure instance
* @visibility frontend
*/
host: string;
/**
@@ -103,7 +109,6 @@ export interface Config {
bitbucket?: Array<{
/**
* The hostname of the given Bitbucket instance
* @visibility frontend
*/
host: string;
/**
@@ -113,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;
/**
@@ -132,7 +136,6 @@ export interface Config {
github?: Array<{
/**
* The hostname of the given GitHub instance
* @visibility frontend
*/
host: string;
/**
@@ -142,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;
}>;
@@ -156,7 +157,6 @@ export interface Config {
gitlab?: Array<{
/**
* The hostname of the given GitLab instance
* @visibility frontend
*/
host: string;
/**
+5 -5
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/backend-common",
"description": "Common functionality library for Backstage backends",
"version": "0.3.1",
"version": "0.3.3",
"main": "src/index.ts",
"types": "src/index.ts",
"private": false,
@@ -31,8 +31,8 @@
"dependencies": {
"@backstage/cli-common": "^0.1.1",
"@backstage/config": "^0.1.1",
"@backstage/config-loader": "^0.3.0",
"@backstage/integration": "^0.1.1",
"@backstage/config-loader": "^0.4.0",
"@backstage/integration": "^0.1.2",
"@types/cors": "^2.8.6",
"@types/express": "^4.17.6",
"archiver": "^5.0.2",
@@ -67,8 +67,8 @@
}
},
"devDependencies": {
"@backstage/cli": "^0.3.1",
"@backstage/test-utils": "^0.1.3",
"@backstage/cli": "^0.4.0",
"@backstage/test-utils": "^0.1.4",
"@types/archiver": "^3.1.1",
"@types/compression": "^1.7.0",
"@types/concat-stream": "^1.6.0",
@@ -235,6 +235,40 @@ describe('GithubUrlReader', () => {
expect(indexMarkdownFile.toString()).toBe('# Test\n');
});
it('includes the subdomain in the github url', async () => {
worker.resetHandlers();
worker.use(
rest.get(
'https://ghe.github.com/backstage/mock/archive/repo.tar.gz',
(_, res, ctx) =>
res(
ctx.status(200),
ctx.set('Content-Type', 'application/x-gzip'),
ctx.body(repoBuffer),
),
),
);
const processor = new GithubUrlReader(
{
host: 'ghe.github.com',
apiBaseUrl: 'https://api.github.com',
},
{ treeResponseFactory },
);
const response = await processor.readTree(
'https://ghe.github.com/backstage/mock/tree/repo/docs',
);
const files = await response.files();
expect(files.length).toBe(1);
const indexMarkdownFile = await files[0].content();
expect(indexMarkdownFile.toString()).toBe('# Test\n');
});
it('must specify a branch', async () => {
const processor = new GithubUrlReader(
{
@@ -179,7 +179,7 @@ export class GithubUrlReader implements UrlReader {
name: repoName,
ref,
protocol,
source,
resource,
full_name,
filepath,
} = parseGitUri(url);
@@ -194,8 +194,9 @@ export class GithubUrlReader implements UrlReader {
// TODO(Rugvip): use API to fetch URL instead
const response = await fetch(
new URL(
`${protocol}://${source}/${full_name}/archive/${ref}.tar.gz`,
`${protocol}://${resource}/${full_name}/archive/${ref}.tar.gz`,
).toString(),
getRawRequestOptions(this.config),
);
if (!response.ok) {
const message = `Failed to read tree from ${url}, ${response.status} ${response.statusText}`;
@@ -126,7 +126,7 @@ describe('TarArchiveResponse', () => {
const res = new TarArchiveResponse(stream, 'mock-repo/docs/', '/tmp');
const dir = await res.dir();
expect(dir).toMatch(/^\/tmp\/.*$/);
expect(dir).toMatch(/^[\/\\]tmp[\/\\].*$/);
await expect(
fs.readFile(resolvePath(dir, 'index.md'), 'utf8'),
).resolves.toBe('# Test\n');
@@ -126,7 +126,7 @@ describe('ZipArchiveResponse', () => {
const res = new ZipArchiveResponse(stream, 'mock-repo/docs/', '/tmp');
const dir = await res.dir();
expect(dir).toMatch(/^\/tmp\/.*$/);
expect(dir).toMatch(/^[\/\\]tmp[\/\\].*$/);
await expect(
fs.readFile(resolvePath(dir, 'index.md'), 'utf8'),
).resolves.toBe('# Test\n');
@@ -0,0 +1,36 @@
/*
* 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.
*/
import { applyCspDirectives } from './ServiceBuilderImpl';
describe('ServiceBuilderImpl', () => {
describe('applyCspDirectives', () => {
it('copies actual values', () => {
const result = applyCspDirectives({ key: ['value'] });
expect(result).toEqual(
expect.objectContaining({
'default-src': ["'self'"],
key: ['value'],
}),
);
});
it('removes false value keys', () => {
const result = applyCspDirectives({ 'upgrade-insecure-requests': false });
expect(result!['upgrade-insecure-requests']).toBeUndefined();
});
});
});
@@ -18,7 +18,7 @@ import { Config } from '@backstage/config';
import compression from 'compression';
import cors from 'cors';
import express, { Router } from 'express';
import helmet from 'helmet';
import helmet, { HelmetOptions } from 'helmet';
import * as http from 'http';
import stoppable from 'stoppable';
import { Logger } from 'winston';
@@ -56,7 +56,7 @@ const DEFAULT_CSP = {
'script-src': ["'self'"],
'script-src-attr': ["'none'"],
'style-src': ["'self'", 'https:', "'unsafe-inline'"],
'upgrade-insecure-requests': [],
'upgrade-insecure-requests': [] as string[],
};
export class ServiceBuilderImpl implements ServiceBuilder {
@@ -64,7 +64,7 @@ export class ServiceBuilderImpl implements ServiceBuilder {
private host: string | undefined;
private logger: Logger | undefined;
private corsOptions: cors.CorsOptions | undefined;
private cspOptions: CspOptions | undefined;
private cspOptions: Record<string, string[] | false> | undefined;
private httpsSettings: HttpsSettings | undefined;
private enableMetrics: boolean = true;
private routers: [string, Router][];
@@ -85,7 +85,10 @@ export class ServiceBuilderImpl implements ServiceBuilder {
const baseOptions = readBaseOptions(backendConfig);
if (baseOptions.listenPort) {
this.port = baseOptions.listenPort;
this.port =
typeof baseOptions.listenPort === 'string'
? parseInt(baseOptions.listenPort, 10)
: baseOptions.listenPort;
}
if (baseOptions.listenHost) {
this.host = baseOptions.listenHost;
@@ -154,20 +157,11 @@ export class ServiceBuilderImpl implements ServiceBuilder {
host,
logger,
corsOptions,
cspOptions,
httpsSettings,
helmetOptions,
} = this.getOptions();
app.use(
helmet({
contentSecurityPolicy: {
directives: {
...DEFAULT_CSP,
...cspOptions,
},
},
}),
);
app.use(helmet(helmetOptions));
if (corsOptions) {
app.use(cors(corsOptions));
}
@@ -214,16 +208,38 @@ export class ServiceBuilderImpl implements ServiceBuilder {
host: string;
logger: Logger;
corsOptions?: cors.CorsOptions;
cspOptions?: CspOptions;
httpsSettings?: HttpsSettings;
helmetOptions: HelmetOptions;
} {
return {
port: this.port ?? DEFAULT_PORT,
host: this.host ?? DEFAULT_HOST,
logger: this.logger ?? getRootLogger(),
corsOptions: this.corsOptions,
cspOptions: this.cspOptions,
httpsSettings: this.httpsSettings,
helmetOptions: {
contentSecurityPolicy: {
directives: applyCspDirectives(this.cspOptions),
},
},
};
}
}
export function applyCspDirectives(
directives: Record<string, string[] | false> | undefined,
): CspOptions | undefined {
const result: CspOptions = { ...DEFAULT_CSP };
if (directives) {
for (const [key, value] of Object.entries(directives)) {
if (value === false) {
delete result[key];
} else {
result[key] = value;
}
}
}
return result;
}
@@ -0,0 +1,51 @@
/*
* 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.
*/
import { ConfigReader } from '@backstage/config';
import { readCspOptions } from './config';
describe('config', () => {
describe('readCspOptions', () => {
it('reads valid values', () => {
const config = ConfigReader.fromConfigs([
{ context: '', data: { csp: { key: ['value'] } } },
]);
expect(readCspOptions(config)).toEqual(
expect.objectContaining({
key: ['value'],
}),
);
});
it('accepts false', () => {
const config = ConfigReader.fromConfigs([
{ context: '', data: { csp: { key: false } } },
]);
expect(readCspOptions(config)).toEqual(
expect.objectContaining({
key: false,
}),
);
});
it('rejects invalid value types', () => {
const config = ConfigReader.fromConfigs([
{ context: '', data: { csp: { key: [4] } } },
]);
expect(() => readCspOptions(config)).toThrow(/wanted string-array/);
});
});
});
@@ -18,7 +18,7 @@ import { Config } from '@backstage/config';
import { CorsOptions } from 'cors';
export type BaseOptions = {
listenPort?: number;
listenPort?: string | number;
listenHost?: string;
};
@@ -89,8 +89,19 @@ export function readBaseOptions(config: Config): BaseOptions {
});
}
const port = config.getOptional('listen.port');
if (
typeof port !== 'undefined' &&
typeof port !== 'number' &&
typeof port !== 'string'
) {
throw new Error(
`Invalid type in config for key 'backend.listen.post', got ${typeof port}, wanted string or number`,
);
}
return removeUnknown({
listenPort: config.getOptionalNumber('listen.port'),
listenPort: port,
listenHost: config.getOptionalString('listen.host'),
baseUrl: config.getOptionalString('baseUrl'),
});
@@ -134,24 +145,33 @@ export function readCorsOptions(config: Config): CorsOptions | undefined {
* Attempts to read a CSP options object from the root of a config object.
*
* @param config The root of a backend config object
* @returns A CSP options object, or undefined if not specified
* @returns A CSP options object, or undefined if not specified. Values can be
* false as well, which means to remove the default behavior for that
* key.
*
* @example
* ```yaml
* backend:
* csp:
* connect-src: ["'self'", 'http:', 'https:']
* upgrade-insecure-requests: false
* ```
*/
export function readCspOptions(config: Config): CspOptions | undefined {
export function readCspOptions(
config: Config,
): Record<string, string[] | false> | undefined {
const cc = config.getOptionalConfig('csp');
if (!cc) {
return undefined;
}
const result: CspOptions = {};
const result: Record<string, string[] | false> = {};
for (const key of cc.keys()) {
result[key] = cc.getStringArray(key);
if (cc.get(key) === false) {
result[key] = false;
} else {
result[key] = cc.getStringArray(key);
}
}
return result;
+48
View File
@@ -1,5 +1,53 @@
# example-backend
## 0.2.5
### Patch Changes
- Updated dependencies [ae95c7ff3]
- Updated dependencies [b4488ddb0]
- Updated dependencies [612368274]
- Updated dependencies [6a6c7c14e]
- Updated dependencies [08835a61d]
- Updated dependencies [a9fd599f7]
- Updated dependencies [e42402b47]
- Updated dependencies [bcc211a08]
- Updated dependencies [3619ea4c4]
- @backstage/plugin-techdocs-backend@0.3.1
- @backstage/plugin-catalog-backend@0.3.0
- @backstage/backend-common@0.3.3
- @backstage/plugin-proxy-backend@0.2.2
- @backstage/catalog-model@0.4.0
- @backstage/plugin-kubernetes-backend@0.2.1
- @backstage/plugin-app-backend@0.3.2
- example-app@0.2.5
- @backstage/plugin-auth-backend@0.2.5
- @backstage/plugin-scaffolder-backend@0.3.3
## 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 -1
View File
@@ -7,7 +7,7 @@ WORKDIR /usr/src/app
# and along with yarn.lock and the root package.json, that's enough to run yarn install.
ADD yarn.lock package.json skeleton.tar ./
RUN yarn install --frozen-lockfile --production
RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)"
# This will copy the contents of the dist-workspace when running the build-image command.
# Do not use this Dockerfile outside of that command, as it will copy in the source code instead.
+13 -13
View File
@@ -1,6 +1,6 @@
{
"name": "example-backend",
"version": "0.2.3",
"version": "0.2.5",
"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.3",
"@backstage/catalog-model": "^0.4.0",
"@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.2",
"@backstage/plugin-auth-backend": "^0.2.5",
"@backstage/plugin-catalog-backend": "^0.3.0",
"@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-scaffolder-backend": "^0.3.2",
"@backstage/plugin-kubernetes-backend": "^0.2.1",
"@backstage/plugin-proxy-backend": "^0.2.2",
"@backstage/plugin-rollbar-backend": "^0.1.4",
"@backstage/plugin-scaffolder-backend": "^0.3.3",
"@backstage/plugin-sentry-backend": "^0.1.3",
"@backstage/plugin-techdocs-backend": "^0.2.2",
"@backstage/plugin-techdocs-backend": "^0.3.1",
"@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.5",
"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.4.0",
"@types/dockerode": "^2.5.32",
"@types/express": "^4.17.6",
"@types/express-serve-static-core": "^4.17.5",
+2
View File
@@ -28,6 +28,7 @@ export default async function createPlugin(env: PluginEnvironment) {
entitiesCatalog,
locationsCatalog,
higherOrderOperation,
locationAnalyzer,
} = await builder.build();
useHotCleanup(
@@ -39,6 +40,7 @@ export default async function createPlugin(env: PluginEnvironment) {
entitiesCatalog,
locationsCatalog,
higherOrderOperation,
locationAnalyzer,
logger: env.logger,
});
}
+9
View File
@@ -1,5 +1,14 @@
# @backstage/catalog-client
## 0.3.2
### Patch Changes
- Updated dependencies [08835a61d]
- Updated dependencies [a9fd599f7]
- Updated dependencies [bcc211a08]
- @backstage/catalog-model@0.4.0
## 0.3.1
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/catalog-client",
"version": "0.3.1",
"version": "0.3.2",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -20,12 +20,12 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/catalog-model": "^0.3.0",
"@backstage/catalog-model": "^0.4.0",
"@backstage/config": "^0.1.1",
"cross-fetch": "^3.0.6"
},
"devDependencies": {
"@backstage/cli": "^0.3.1",
"@backstage/cli": "^0.4.0",
"@types/jest": "^26.0.7",
"msw": "^0.21.2"
},
+49
View File
@@ -1,5 +1,54 @@
# @backstage/catalog-model
## 0.4.0
### Minor Changes
- bcc211a08: k8s-plugin: refactor approach to use annotation based label-selector
### Patch Changes
- 08835a61d: Add support for relative targets and implicit types in Location entities.
- a9fd599f7: Add Analyze location endpoint to catalog backend. Add catalog-import plugin and replace import-component with it. To start using Analyze location endpoint, you have add it to the `createRouter` function options in the `\backstage\packages\backend\src\plugins\catalog.ts` file:
```ts
export default async function createPlugin(env: PluginEnvironment) {
const builder = new CatalogBuilder(env);
const {
entitiesCatalog,
locationsCatalog,
higherOrderOperation,
locationAnalyzer, //<--
} = await builder.build();
return await createRouter({
entitiesCatalog,
locationsCatalog,
higherOrderOperation,
locationAnalyzer, //<--
logger: env.logger,
});
}
```
## 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
@@ -0,0 +1,10 @@
# Examples with relative paths
These examples use Location entities with relative paths
[see #3513](https://github.com/backstage/backstage/pull/3513).
The `examples` sibling directory will be replaced with these contents no sooner
than December 16 2020, to give consumers time to adopt the above change.
The contents of this directory is here for consumers who are on the edge and
want to use the new facility before December 16.
@@ -0,0 +1,8 @@
apiVersion: backstage.io/v1alpha1
kind: Location
metadata:
name: acme-corp
description: A collection of all Backstage example Groups
spec:
targets:
- ./acme/org.yaml
@@ -0,0 +1,11 @@
apiVersion: backstage.io/v1alpha1
kind: Group
metadata:
name: backstage
description: The backstage sub-department
spec:
type: sub-department
parent: infrastructure
ancestors: [infrastructure, acme-corp]
children: [team-a, team-b]
descendants: [team-a, team-b]
@@ -0,0 +1,11 @@
apiVersion: backstage.io/v1alpha1
kind: Group
metadata:
name: boxoffice
description: The boxoffice sub-department
spec:
type: sub-department
parent: infrastructure
ancestors: [infrastructure, acme-corp]
children: [team-c, team-d]
descendants: [team-c, team-d]
@@ -0,0 +1,11 @@
apiVersion: backstage.io/v1alpha1
kind: Group
metadata:
name: infrastructure
description: The infra department
spec:
type: department
parent: acme-corp
ancestors: [acme-corp]
children: [backstage, boxoffice]
descendants: [backstage, boxoffice, team-a, team-b, team-c, team-d]
@@ -0,0 +1,26 @@
apiVersion: backstage.io/v1alpha1
kind: Group
metadata:
name: acme-corp
description: The acme-corp organization
spec:
type: organization
ancestors: []
children: [infrastructure]
descendants:
[infrastructure, backstage, boxoffice, team-a, team-b, team-c, team-d]
---
apiVersion: backstage.io/v1alpha1
kind: Location
metadata:
name: example-groups
description: A collection of all Backstage example Groups
spec:
targets:
- ./infrastructure-group.yaml
- ./boxoffice-group.yaml
- ./backstage-group.yaml
- ./team-a-group.yaml
- ./team-b-group.yaml
- ./team-c-group.yaml
- ./team-d-group.yaml
@@ -0,0 +1,58 @@
apiVersion: backstage.io/v1alpha1
kind: Group
metadata:
name: team-a
description: Team A
spec:
type: team
parent: backstage
ancestors: [backstage, infrastructure, acme-corp]
children: []
descendants: []
---
apiVersion: backstage.io/v1alpha1
kind: User
metadata:
name: breanna.davison
spec:
profile:
displayName: Breanna Davison
email: breanna-davison@example.com
picture: https://example.com/staff/breanna.jpeg
memberOf: [team-a]
---
apiVersion: backstage.io/v1alpha1
kind: User
metadata:
name: janelle.dawe
spec:
profile:
displayName: Janelle Dawe
email: janelle-dawe@example.com
picture: https://example.com/staff/janelle.jpeg
memberOf: [team-a]
---
apiVersion: backstage.io/v1alpha1
kind: User
metadata:
name: nigel.manning
spec:
profile:
displayName: Nigel Manning
email: nigel-manning@example.com
picture: https://example.com/staff/nigel.jpeg
memberOf: [team-a]
---
# This user is added as an example, to make it more easy for the "Guest"
# sign-in option to demonstrate some entities being owned. In a regular org,
# a guest user would probably not be registered like this.
apiVersion: backstage.io/v1alpha1
kind: User
metadata:
name: guest
spec:
profile:
displayName: Guest User
email: guest@example.com
picture: https://example.com/staff/the-ceos-dog.jpeg
memberOf: [team-a]
@@ -0,0 +1,66 @@
apiVersion: backstage.io/v1alpha1
kind: Group
metadata:
name: team-b
description: Team B
spec:
type: team
parent: backstage
ancestors: [backstage, infrastructure, acme-corp]
children: []
descendants: []
---
apiVersion: backstage.io/v1alpha1
kind: User
metadata:
name: amelia.park
spec:
profile:
displayName: Amelia Park
email: amelia-park@example.com
picture: https://example.com/staff/amelia.jpeg
memberOf: [team-b]
---
apiVersion: backstage.io/v1alpha1
kind: User
metadata:
name: colette.brock
spec:
profile:
displayName: Colette Brock
email: colette-brock@example.com
picture: https://example.com/staff/colette.jpeg
memberOf: [team-b]
---
apiVersion: backstage.io/v1alpha1
kind: User
metadata:
name: jenny.doe
spec:
profile:
displayName: Jenny Doe
email: jenny-doe@example.com
picture: https://example.com/staff/jenny.jpeg
memberOf: [team-b]
---
apiVersion: backstage.io/v1alpha1
kind: User
metadata:
name: jonathon.page
spec:
profile:
displayName: Jonathon Page
email: jonathon-page@example.com
picture: https://example.com/staff/jonathon.jpeg
memberOf: [team-b]
---
apiVersion: backstage.io/v1alpha1
kind: User
metadata:
name: justine.barrow
spec:
profile:
displayName: Justine Barrow
email: justine-barrow@example.com
picture: https://example.com/staff/justine.jpeg
memberOf: [team-b]
@@ -0,0 +1,66 @@
apiVersion: backstage.io/v1alpha1
kind: Group
metadata:
name: team-c
description: Team C
spec:
type: team
parent: boxoffice
ancestors: [boxoffice, infrastructure, acme-corp]
children: []
descendants: []
---
apiVersion: backstage.io/v1alpha1
kind: User
metadata:
name: calum.leavy
spec:
profile:
displayName: Calum Leavy
email: calum-leavy@example.com
picture: https://example.com/staff/calum.jpeg
memberOf: [team-c]
---
apiVersion: backstage.io/v1alpha1
kind: User
metadata:
name: frank.tiernan
spec:
profile:
displayName: Frank Tiernan
email: frank-tiernan@example.com
picture: https://example.com/staff/frank.jpeg
memberOf: [team-c]
---
apiVersion: backstage.io/v1alpha1
kind: User
metadata:
name: peadar.macmahon
spec:
profile:
displayName: Peadar MacMahon
email: peadar-macmahon@example.com
picture: https://example.com/staff/peadar.jpeg
memberOf: [team-c]
---
apiVersion: backstage.io/v1alpha1
kind: User
metadata:
name: sarah.gilroy
spec:
profile:
displayName: Sarah Gilroy
email: sarah-gilroy@example.com
picture: https://example.com/staff/sarah.jpeg
memberOf: [team-c]
---
apiVersion: backstage.io/v1alpha1
kind: User
metadata:
name: tara.macgovern
spec:
profile:
displayName: Tara MacGovern
email: tara-macgovern@example.com
picture: https://example.com/staff/tara.jpeg
memberOf: [team-c]
@@ -0,0 +1,33 @@
apiVersion: backstage.io/v1alpha1
kind: Group
metadata:
name: team-d
description: Team D
spec:
type: team
parent: boxoffice
ancestors: [boxoffice, infrastructure, acme-corp]
children: []
descendants: []
---
apiVersion: backstage.io/v1alpha1
kind: User
metadata:
name: eva.macdowell
spec:
profile:
displayName: Eva MacDowell
email: eva-macdowell@example.com
picture: https://example.com/staff/eva.jpeg
memberOf: [team-d]
---
apiVersion: backstage.io/v1alpha1
kind: User
metadata:
name: lucy.sheehan
spec:
profile:
displayName: Lucy Sheehan
email: lucy-sheehan@example.com
picture: https://example.com/staff/lucy.jpeg
memberOf: [team-d]
@@ -0,0 +1,12 @@
apiVersion: backstage.io/v1alpha1
kind: Location
metadata:
name: example-apis
description: A collection of all Backstage example APIs
spec:
targets:
- ./apis/hello-world-api.yaml
- ./apis/petstore-api.yaml
- ./apis/spotify-api.yaml
- ./apis/streetlights-api.yaml
- ./apis/swapi-graphql.yaml
@@ -0,0 +1,16 @@
apiVersion: backstage.io/v1alpha1
kind: Location
metadata:
name: example-components
description: A collection of all Backstage example components
spec:
targets:
- ./components/artist-lookup-component.yaml
- ./components/petstore-component.yaml
- ./components/playback-order-component.yaml
- ./components/podcast-api-component.yaml
- ./components/queue-proxy-component.yaml
- ./components/searcher-component.yaml
- ./components/playback-lib-component.yaml
- ./components/www-artist-component.yaml
- ./components/shuffle-api-component.yaml
@@ -0,0 +1,48 @@
apiVersion: backstage.io/v1alpha1
kind: API
metadata:
name: hello-world
description: Hello World example for gRPC
spec:
type: grpc
lifecycle: deprecated
owner: team-c
definition: |
// Copyright 2015 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
syntax = "proto3";
option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";
package helloworld;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
@@ -0,0 +1,124 @@
apiVersion: backstage.io/v1alpha1
kind: API
metadata:
name: petstore
description: The petstore API
tags:
- store
- rest
spec:
type: openapi
lifecycle: experimental
owner: team-c
definition: |
openapi: "3.0.0"
info:
version: 1.0.0
title: Swagger Petstore
license:
name: MIT
servers:
- url: http://petstore.swagger.io/v1
paths:
/pets:
get:
summary: List all pets
operationId: listPets
tags:
- pets
parameters:
- name: limit
in: query
description: How many items to return at one time (max 100)
required: false
schema:
type: integer
format: int32
responses:
'200':
description: A paged array of pets
headers:
x-next:
description: A link to the next page of responses
schema:
type: string
content:
application/json:
schema:
$ref: "#/components/schemas/Pets"
default:
description: unexpected error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
post:
summary: Create a pet
operationId: createPets
tags:
- pets
responses:
'201':
description: Null response
default:
description: unexpected error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/pets/{petId}:
get:
summary: Info for a specific pet
operationId: showPetById
tags:
- pets
parameters:
- name: petId
in: path
required: true
description: The id of the pet to retrieve
schema:
type: string
responses:
'200':
description: Expected response to a valid request
content:
application/json:
schema:
$ref: "#/components/schemas/Pet"
default:
description: unexpected error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
components:
schemas:
Pet:
type: object
required:
- id
- name
properties:
id:
type: integer
format: int64
name:
type: string
tag:
type: string
Pets:
type: array
items:
$ref: "#/components/schemas/Pet"
Error:
type: object
required:
- code
- message
properties:
code:
type: integer
format: int32
message:
type: string
@@ -0,0 +1,17 @@
apiVersion: backstage.io/v1alpha1
kind: API
metadata:
name: spotify
description: The Spotify web API
tags:
- spotify
- rest
annotations:
# The annotation is deprecated, we use placeholders (see below) instead, remove it later.
backstage.io/definition-at-location: 'url:https://raw.githubusercontent.com/APIs-guru/openapi-directory/master/APIs/spotify.com/v1/swagger.yaml'
spec:
type: openapi
lifecycle: production
owner: team-a
definition:
$text: https://github.com/APIs-guru/openapi-directory/blob/master/APIs/spotify.com/v1/swagger.yaml
@@ -0,0 +1,221 @@
apiVersion: backstage.io/v1alpha1
kind: API
metadata:
name: streetlights
description: The Smartylighting Streetlights API allows you to remotely manage the city lights.
tags:
- mqtt
spec:
type: asyncapi
lifecycle: production
owner: team-c
definition: |
asyncapi: 2.0.0
info:
title: Streetlights API
version: '1.0.0'
description: |
The Smartylighting Streetlights API allows you to remotely manage the city lights.
### Check out its awesome features:
* Turn a specific streetlight on/off 🌃
* Dim a specific streetlight 😎
* Receive real-time information about environmental lighting conditions 📈
license:
name: Apache 2.0
url: https://www.apache.org/licenses/LICENSE-2.0
servers:
production:
url: api.streetlights.smartylighting.com:{port}
protocol: mqtt
description: Test broker
variables:
port:
description: Secure connection (TLS) is available through port 8883.
default: '1883'
enum:
- '1883'
- '8883'
security:
- apiKey: []
- supportedOauthFlows:
- streetlights:on
- streetlights:off
- streetlights:dim
- openIdConnectWellKnown: []
defaultContentType: application/json
channels:
smartylighting/streetlights/1/0/event/{streetlightId}/lighting/measured:
description: The topic on which measured values may be produced and consumed.
parameters:
streetlightId:
$ref: '#/components/parameters/streetlightId'
subscribe:
summary: Receive information about environmental lighting conditions of a particular streetlight.
operationId: receiveLightMeasurement
traits:
- $ref: '#/components/operationTraits/kafka'
message:
$ref: '#/components/messages/lightMeasured'
smartylighting/streetlights/1/0/action/{streetlightId}/turn/on:
parameters:
streetlightId:
$ref: '#/components/parameters/streetlightId'
publish:
operationId: turnOn
traits:
- $ref: '#/components/operationTraits/kafka'
message:
$ref: '#/components/messages/turnOnOff'
smartylighting/streetlights/1/0/action/{streetlightId}/turn/off:
parameters:
streetlightId:
$ref: '#/components/parameters/streetlightId'
publish:
operationId: turnOff
traits:
- $ref: '#/components/operationTraits/kafka'
message:
$ref: '#/components/messages/turnOnOff'
smartylighting/streetlights/1/0/action/{streetlightId}/dim:
parameters:
streetlightId:
$ref: '#/components/parameters/streetlightId'
publish:
operationId: dimLight
traits:
- $ref: '#/components/operationTraits/kafka'
message:
$ref: '#/components/messages/dimLight'
components:
messages:
lightMeasured:
name: lightMeasured
title: Light measured
summary: Inform about environmental lighting conditions for a particular streetlight.
contentType: application/json
traits:
- $ref: '#/components/messageTraits/commonHeaders'
payload:
$ref: "#/components/schemas/lightMeasuredPayload"
turnOnOff:
name: turnOnOff
title: Turn on/off
summary: Command a particular streetlight to turn the lights on or off.
traits:
- $ref: '#/components/messageTraits/commonHeaders'
payload:
$ref: "#/components/schemas/turnOnOffPayload"
dimLight:
name: dimLight
title: Dim light
summary: Command a particular streetlight to dim the lights.
traits:
- $ref: '#/components/messageTraits/commonHeaders'
payload:
$ref: "#/components/schemas/dimLightPayload"
schemas:
lightMeasuredPayload:
type: object
properties:
lumens:
type: integer
minimum: 0
description: Light intensity measured in lumens.
sentAt:
$ref: "#/components/schemas/sentAt"
turnOnOffPayload:
type: object
properties:
command:
type: string
enum:
- on
- off
description: Whether to turn on or off the light.
sentAt:
$ref: "#/components/schemas/sentAt"
dimLightPayload:
type: object
properties:
percentage:
type: integer
description: Percentage to which the light should be dimmed to.
minimum: 0
maximum: 100
sentAt:
$ref: "#/components/schemas/sentAt"
sentAt:
type: string
format: date-time
description: Date and time when the message was sent.
securitySchemes:
apiKey:
type: apiKey
in: user
description: Provide your API key as the user and leave the password empty.
supportedOauthFlows:
type: oauth2
description: Flows to support OAuth 2.0
flows:
implicit:
authorizationUrl: 'https://authserver.example/auth'
scopes:
'streetlights:on': Ability to switch lights on
'streetlights:off': Ability to switch lights off
'streetlights:dim': Ability to dim the lights
password:
tokenUrl: 'https://authserver.example/token'
scopes:
'streetlights:on': Ability to switch lights on
'streetlights:off': Ability to switch lights off
'streetlights:dim': Ability to dim the lights
clientCredentials:
tokenUrl: 'https://authserver.example/token'
scopes:
'streetlights:on': Ability to switch lights on
'streetlights:off': Ability to switch lights off
'streetlights:dim': Ability to dim the lights
authorizationCode:
authorizationUrl: 'https://authserver.example/auth'
tokenUrl: 'https://authserver.example/token'
refreshUrl: 'https://authserver.example/refresh'
scopes:
'streetlights:on': Ability to switch lights on
'streetlights:off': Ability to switch lights off
'streetlights:dim': Ability to dim the lights
openIdConnectWellKnown:
type: openIdConnect
openIdConnectUrl: 'https://authserver.example/.well-known'
parameters:
streetlightId:
description: The ID of the streetlight.
schema:
type: string
messageTraits:
commonHeaders:
headers:
type: object
properties:
my-app-header:
type: integer
minimum: 0
maximum: 100
operationTraits:
kafka:
bindings:
kafka:
clientId: my-app-id
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,12 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: artist-lookup
description: Artist Lookup
tags:
- java
- data
spec:
type: service
lifecycle: experimental
owner: team-a
@@ -0,0 +1,13 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: petstore
description: Petstore
spec:
type: service
lifecycle: experimental
owner: team-c
implementsApis:
- petstore
- streetlights
- hello-world
@@ -0,0 +1,9 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: playback-sdk
description: Audio and video playback SDK
spec:
type: library
lifecycle: experimental
owner: team-c
@@ -0,0 +1,12 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: playback-order
description: Playback Order
tags:
- java
- playback
spec:
type: service
lifecycle: production
owner: user:guest
@@ -0,0 +1,11 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: podcast-api
description: Podcast API
tags:
- java
spec:
type: service
lifecycle: experimental
owner: team-b
@@ -0,0 +1,12 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: queue-proxy
description: Queue Proxy
tags:
- go
- website
spec:
type: website
lifecycle: production
owner: team-b
@@ -0,0 +1,11 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: searcher
description: Searcher
tags:
- go
spec:
type: service
lifecycle: production
owner: user:guest
@@ -0,0 +1,11 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: shuffle-api
description: Shuffle API
tags:
- go
spec:
type: service
lifecycle: production
owner: user:guest
@@ -0,0 +1,9 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: www-artist
description: Artist main website
spec:
type: website
lifecycle: production
owner: team-a
@@ -0,0 +1,7 @@
# Example Entities
NOTE: These are being replaced with the contents of the `examples-relative`
sibling directory, after December 16 2020. If you are using this example data
you will need to be using a released version of the catalog backend which
supports relative targets in Location entities
[see #3513](https://github.com/backstage/backstage/pull/3513) after that time.
@@ -42,3 +42,17 @@ spec:
email: nigel-manning@example.com
picture: https://example.com/staff/nigel.jpeg
memberOf: [team-a]
---
# This user is added as an example, to make it more easy for the "Guest"
# sign-in option to demonstrate some entities being owned. In a regular org,
# a guest user would probably not be registered like this.
apiVersion: backstage.io/v1alpha1
kind: User
metadata:
name: guest
spec:
profile:
displayName: Guest User
email: guest@example.com
picture: https://example.com/staff/the-ceos-dog.jpeg
memberOf: [team-a]
@@ -6,7 +6,7 @@ metadata:
spec:
type: grpc
lifecycle: deprecated
owner: grpc@example.com
owner: team-c
definition: |
// Copyright 2015 gRPC authors.
//
@@ -9,7 +9,7 @@ metadata:
spec:
type: openapi
lifecycle: experimental
owner: pets@example.com
owner: team-c
definition: |
openapi: "3.0.0"
info:
@@ -12,6 +12,6 @@ metadata:
spec:
type: openapi
lifecycle: production
owner: spotify@example.com
owner: team-a
definition:
$text: https://github.com/APIs-guru/openapi-directory/blob/master/APIs/spotify.com/v1/swagger.yaml
@@ -8,7 +8,7 @@ metadata:
spec:
type: asyncapi
lifecycle: production
owner: streetlights@example.com
owner: team-c
definition: |
asyncapi: 2.0.0
info:
@@ -6,7 +6,7 @@ metadata:
spec:
type: graphql
lifecycle: production
owner: yoda@dagobah.space
owner: team-b
definition: |
schema {
query: Root
@@ -9,4 +9,4 @@ metadata:
spec:
type: service
lifecycle: experimental
owner: artists@example.com
owner: team-a
@@ -6,7 +6,7 @@ metadata:
spec:
type: service
lifecycle: experimental
owner: pets@example.com
owner: team-c
implementsApis:
- petstore
- streetlights
@@ -6,4 +6,4 @@ metadata:
spec:
type: library
lifecycle: experimental
owner: players@example.com
owner: team-c
@@ -9,4 +9,4 @@ metadata:
spec:
type: service
lifecycle: production
owner: guest
owner: user:guest
@@ -8,4 +8,4 @@ metadata:
spec:
type: service
lifecycle: experimental
owner: players@example.com
owner: team-b
@@ -9,4 +9,4 @@ metadata:
spec:
type: website
lifecycle: production
owner: tools@example.com
owner: team-b
@@ -8,4 +8,4 @@ metadata:
spec:
type: service
lifecycle: production
owner: guest
owner: user:guest
@@ -8,4 +8,4 @@ metadata:
spec:
type: service
lifecycle: production
owner: guest
owner: user:guest
@@ -6,4 +6,4 @@ metadata:
spec:
type: website
lifecycle: production
owner: artists@example.com
owner: team-a
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/catalog-model",
"version": "0.3.0",
"version": "0.4.0",
"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.4.0",
"@types/express": "^4.17.6",
"@types/jest": "^26.0.7",
"@types/lodash": "^4.14.151",
+8 -8
View File
@@ -84,6 +84,14 @@ export function parseEntityName(
* @param context The context of defaults that the parsing happens within
* @returns The compound form of the reference
*/
export function parseEntityRef(
ref: EntityRef,
context?: { defaultKind: string; defaultNamespace: string },
): {
kind: string;
namespace: string;
name: string;
};
export function parseEntityRef(
ref: EntityRef,
context?: { defaultKind: string },
@@ -100,14 +108,6 @@ export function parseEntityRef(
namespace: string;
name: string;
};
export function parseEntityRef(
ref: EntityRef,
context?: { defaultKind: string; defaultNamespace: string },
): {
kind: string;
namespace: string;
name: string;
};
export function parseEntityRef(
ref: EntityRef,
context: EntityRefContext = {},
@@ -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,15 +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(),
kubernetes: yup
.object<any>({
selector: yup
.object<any>({
matchLabels: yup.object<any>().required(),
})
.required(),
})
.notRequired(),
providesApis: yup.array(yup.string().required()).notRequired(),
consumesApis: yup.array(yup.string().required()).notRequired(),
})
.required(),
});
@@ -50,14 +43,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[];
kubernetes?: {
selector: {
matchLabels: {
[key: string]: string;
};
};
};
providesApis?: string[];
consumesApis?: string[];
};
}
@@ -54,9 +54,9 @@ describe('LocationV1alpha1Validator', () => {
await expect(validator.check(entity)).resolves.toBe(false);
});
it('rejects missing type', async () => {
it('accepts missing type', async () => {
delete (entity as any).spec.type;
await expect(validator.check(entity)).rejects.toThrow(/type/);
await expect(validator.check(entity)).resolves.toBe(true);
});
it('rejects wrong type', async () => {
@@ -26,7 +26,7 @@ const schema = yup.object<Partial<LocationEntityV1alpha1>>({
kind: yup.string().required().equals([KIND]),
spec: yup
.object({
type: yup.string().required().min(1),
type: yup.string().notRequired().min(1),
target: yup.string().notRequired().min(1),
targets: yup.array(yup.string().required()).notRequired(),
})
@@ -37,7 +37,7 @@ export interface LocationEntityV1alpha1 extends Entity {
apiVersion: typeof API_VERSION[number];
kind: typeof KIND;
spec: {
type: string;
type?: string;
target?: string;
targets?: string[];
};
@@ -33,7 +33,9 @@ export const RELATION_OWNER_OF = 'ownerOf';
* A relation with an API entity, typically from a component or system
*/
export const RELATION_CONSUMES_API = 'consumesApi';
export const RELATION_API_CONSUMED_BY = 'apiConsumedBy';
export const RELATION_PROVIDES_API = 'providesApi';
export const RELATION_API_PROVIDED_BY = 'apiProvidedBy';
/**
* A relation denoting a dependency on another entity.
+5 -1
View File
@@ -15,5 +15,9 @@
*/
export type { Location, LocationSpec } from './types';
export { locationSchema, locationSpecSchema } from './validation';
export {
locationSchema,
locationSpecSchema,
analyzeLocationSchema,
} from './validation';
export { LOCATION_ANNOTATION } from './annotation';
@@ -34,3 +34,10 @@ export const locationSchema = yup
})
.noUnknown()
.required();
export const analyzeLocationSchema = yup
.object<{ location: LocationSpec }>({
location: locationSpecSchema,
})
.noUnknown()
.required();
+25
View File
@@ -1,5 +1,30 @@
# @backstage/cli
## 0.4.0
### Minor Changes
- 00670a96e: sort product panels and navigation menu by greatest cost
update tsconfig.json to use ES2020 api
### Patch Changes
- b4488ddb0: Added a type alias for PositionError = GeolocationPositionError
- 4a655c89d: Bump versions of `esbuild` and `rollup-plugin-esbuild`
- 8a16e8af8: Support `.npmrc` when building with private NPM registries
- Updated dependencies [4e7091759]
- Updated dependencies [b4488ddb0]
- @backstage/config-loader@0.4.0
## 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
+12
View File
@@ -97,3 +97,15 @@ declare module '*.module.sass' {
const classes: { readonly [key: string]: string };
export default classes;
}
// NOTE(freben): Both the fix, and the placement of the fix, are not great.
//
// The fix is because the PositionError was renamed to
// GeolocationPositionError outside of our control, and react-use is dependent
// on the old name.
//
// The placement is because it's the one location we have at the moment, where
// a central .d.ts file is imported by the frontend and can be amended.
//
// After both TS and react-use are bumped high enough, this should be removed.
type PositionError = GeolocationPositionError;
+1 -1
View File
@@ -11,7 +11,7 @@
"incremental": true,
"isolatedModules": true,
"jsx": "react",
"lib": ["DOM", "DOM.Iterable", "ScriptHost", "ES2019", "ESNext.Promise"],
"lib": ["DOM", "DOM.Iterable", "ScriptHost", "ES2020", "ESNext.Promise"],
"module": "ESNext",
"moduleResolution": "node",
"noEmit": false,

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