Merge branch 'github' into timurista
@@ -1,7 +1,8 @@
|
||||
| Organization | Contact | Description of Use |
|
||||
| ---------------------------------- | ------------------------------------------ | ----------------------------------------------------------------------------------- |
|
||||
| [Spotify](https://www.spotify.com) | [@alund](https://github.com/alund) | Main interface towards all of Spotify's infrastructure and technical documentation. |
|
||||
| [bol.com](https://www.bol.com) | [@RoyJacobs](https://github.com/RoyJacobs) | Initial work being done to unify platform tooling. |
|
||||
| [DFDS](https://www.dfds.com) | [@carlsendk](https://github.com/carlsendk) | V2 self-service platform. |
|
||||
| [Roadie](https://roadie.io) | [@dtuite](https://github.com/dtuite) | Hosted, managed Backstage with easy set-up |
|
||||
| [Roku](https://www.roku.com) | [@timurista](https://github.com/timurista) | Initial work on Cloud engineering service platform. |
|
||||
| Organization | Contact | Description of Use |
|
||||
| ---------------------------------- | ------------------------------------------------------------ | ----------------------------------------------------------------------------------- |
|
||||
| [Spotify](https://www.spotify.com) | [@alund](https://github.com/alund) | Main interface towards all of Spotify's infrastructure and technical documentation. |
|
||||
| [bol.com](https://www.bol.com) | [@RoyJacobs](https://github.com/RoyJacobs) | Initial work being done to unify platform tooling. |
|
||||
| [DFDS](https://www.dfds.com) | [@carlsendk](https://github.com/carlsendk) | V2 self-service platform. |
|
||||
| [Roadie](https://roadie.io) | [@dtuite](https://github.com/dtuite) | Hosted, managed Backstage with easy set-up |
|
||||
| [Roku](https://www.roku.com) | [@timurista](https://github.com/timurista) | Initial work on Cloud engineering service platform. |
|
||||
| [H-E-B](https://www.heb.com) | [@german-j-rodriguez](https://github.com/german-j-rodriguez) | Initial work on Engineering Portal service platform. |
|
||||
|
||||
@@ -54,6 +54,10 @@ If you are proposing a feature:
|
||||
- Remember that this is a volunteer-driven project, and that contributions
|
||||
are welcome :)
|
||||
|
||||
## Add your company to ADOPTERS
|
||||
|
||||
Have you started using Backstage? Adding your company to [ADOPTERS](ADOPTERS.md) really helps the project.
|
||||
|
||||
# Get Started!
|
||||
|
||||
So...feel ready to jump in? Let's do this. Head over to the [Getting Started guide](https://github.com/spotify/backstage#getting-started) 👏🏻💯
|
||||
|
||||
@@ -59,11 +59,9 @@ The Backstage platform consists of a number of different components:
|
||||
- **app** - Main web application that users interact with. It's built up by a number of different _Plugins_. This repo contains an example implementation of an app (located in `packages/app`) and you can easily get started with your own app by [creating one](docs/create-an-app.md).
|
||||
- [**plugins**](https://github.com/spotify/backstage/tree/master/plugins) - Each plugin is treated as a self-contained web app and can include almost any type of content. Plugins all use a common set of platform API's and reusable UI components. Plugins can fetch data either from the _backend_ or through any RESTful API exposed through the _proxy_.
|
||||
- [**service catalog**](https://github.com/spotify/backstage/tree/master/packages/backend) - Service that holds the model of your software ecosystem, including organisational information and what team owns what software. The backend also has a Plugin model for extending its graph.
|
||||
- **proxy** \* - Terminates HTTPS and exposes any RESTful API to Plugins.
|
||||
- [**proxy**](https://github.com/spotify/backstage/tree/master/plugins/proxy-backend) - Terminates HTTPS and exposes any RESTful API to Plugins.
|
||||
- **identity** - A backend service that holds your organisation's metadata.
|
||||
|
||||
_\* not yet released_
|
||||
|
||||
## Getting started
|
||||
|
||||
To run a Backstage app, you will need to have the following installed:
|
||||
@@ -86,17 +84,16 @@ And that's it! You are good to go 👍
|
||||
|
||||
### Next step
|
||||
|
||||
Take a look at the [Getting Started](docs/getting-started/README.md) guide to learn more about how to extend the functionality with Plugins.
|
||||
Take a look at the [Getting Started](docs/getting-started/index.md) guide to learn how to set up Backstage, and how to develop on the platform.
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Getting Started](docs/getting-started/README.md)
|
||||
- [Create a Backstage App](docs/create-an-app.md)
|
||||
- [Architecture](docs/overview/architecture-terminology.md) ([Decisions](docs/architecture-decisions))
|
||||
- [API references](docs/reference/README.md)
|
||||
- [Designing for Backstage](docs/design.md)
|
||||
- [Main documentation](docs/README.md)
|
||||
- [Service Catalog](docs/features/software-catalog/index.md)
|
||||
- [Create a Backstage App](docs/getting-started/create-an-app.md)
|
||||
- [Architecture](docs/overview/architecture-terminology.md) ([Decisions](docs/architecture-decisions/index.md))
|
||||
- [Designing for Backstage](docs/dls/design.md)
|
||||
- [Storybook - UI components](http://storybook.backstage.io)
|
||||
- [Contributing to Storybook](docs/getting-started/contributing-to-storybook.md)
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -109,6 +106,7 @@ We would love your help in building Backstage! See [CONTRIBUTING](CONTRIBUTING.m
|
||||
- [RFCs](https://github.com/spotify/backstage/labels/rfc) - Help shape the technical direction
|
||||
- [FAQ](docs/FAQ.md) - Frequently Asked Questions
|
||||
- [Code of Conduct](CODE_OF_CONDUCT.md) - This is how we roll
|
||||
- [Adopters](ADOPTERS.md) - Companies already using Backstage
|
||||
- [Blog](https://backstage.io/blog/) - Announcements and updates
|
||||
- [Newsletter](https://mailchi.mp/spotify/backstage-community)
|
||||
- Give us a star ⭐️ - If you are using Backstage or think it is an interesting project, we would love a star ❤️
|
||||
|
||||
@@ -17,7 +17,7 @@ better yet, a pull request.
|
||||
- [Create an App](getting-started/create-an-app.md)
|
||||
- App configuration
|
||||
- [Configuring App with plugins](getting-started/configure-app-with-plugins.md)
|
||||
- [Customize the look-and-feel of your App](getting-started/customize-app-look-and-feel.md)
|
||||
- [Customize the look-and-feel of your App](getting-started/app-custom-theme.md)
|
||||
- Deployment scenarios
|
||||
- [Kubernetes](getting-started/deployment-k8s.md)
|
||||
- [Other](getting-started/deployment-other.md)
|
||||
@@ -32,8 +32,12 @@ better yet, a pull request.
|
||||
- [API](features/software-catalog/api.md)
|
||||
- Software creation templates
|
||||
- [Overview](features/software-templates/index.md)
|
||||
- [Configure templates](features/software-templates/configure-templates.md)
|
||||
- [Adding templates](features/software-templates/adding-templates.md)
|
||||
- Extending the Scaffolder:
|
||||
- [Overview](features/software-templates/extending/index.md)
|
||||
- [Create your own Templater](features/software-templates/extending/create-your-own-templater.md)
|
||||
- [Create your own Publisher](features/software-templates/extending/create-your-own-publisher.md)
|
||||
- [Create your own Preparer](features/software-templates/extending/create-your-own-preparer.md)
|
||||
- Docs-like-code
|
||||
- [Overview](features/techdocs/README.md)
|
||||
- [Getting Started](features/techdocs/getting-started.md)
|
||||
@@ -48,7 +52,7 @@ better yet, a pull request.
|
||||
- [Overview](plugins/index.md)
|
||||
- [Existing plugins](plugins/existing-plugins.md)
|
||||
- [Creating a new plugin](plugins/create-a-plugin.md)
|
||||
- [Developing a plugin](plugins/developing-plugins.md)
|
||||
- [Developing a plugin](plugins/plugin-development.md)
|
||||
- [Structure of a plugin](plugins/structure-of-a-plugin.md)
|
||||
- Backends and APIs
|
||||
- [Proxying](plugins/proxying.md)
|
||||
@@ -67,7 +71,7 @@ better yet, a pull request.
|
||||
- [Glossary](auth/glossary.md)
|
||||
- Designing for Backstage
|
||||
- [Backstage Design Language System (DLS)](dls/design.md)
|
||||
- [Storybook -- reusable UI components](dls/storybook.md)
|
||||
- [Storybook -- reusable UI components](http://storybook.backstage.io)
|
||||
- [Contributing to Storybook](dls/contributing-to-storybook.md)
|
||||
- [Figma resources](dls/figma.md)
|
||||
- API references
|
||||
|
||||
@@ -9,10 +9,11 @@ to various third party APIs.
|
||||
There are occasions when the user wants to perform actions towards third party
|
||||
services that require authorization via OAuth. Backstage provides standardized
|
||||
[Utility APIs](../api/utility-apis.md) such as the
|
||||
[GoogleAuthApi](../../packages/core-api/src/apis/definitions/auth.ts) for that
|
||||
use-case. Backstage also includes a set of implementations of these APIs that
|
||||
integrate with the [auth-backend](../../plugins/auth-backend) plugin to provide
|
||||
a popup-based OAuth flow.
|
||||
[GoogleAuthApi](https://github.com/spotify/backstage/blob/master/packages/core-api/src/apis/definitions/auth.ts)
|
||||
for that use-case. Backstage also includes a set of implementations of these
|
||||
APIs that integrate with the
|
||||
[auth-backend](https://github.com/spotify/backstage/tree/master/plugins/auth-backend)
|
||||
plugin to provide a popup-based OAuth flow.
|
||||
|
||||
## Background
|
||||
|
||||
@@ -51,8 +52,9 @@ easier to make authenticated requests inside a plugin.
|
||||
## OAuth Flow
|
||||
|
||||
The following describes the OAuth flow implemented by the
|
||||
[auth-backend](../../plugins/auth-backend) and
|
||||
[DefaultAuthConnector](../../packages/core-api/src/lib/AuthConnector/DefaultAuthConnector.ts)
|
||||
[auth-backend](https://github.com/spotify/backstage/tree/master/plugins/auth-backend)
|
||||
and
|
||||
[DefaultAuthConnector](https://github.com/spotify/backstage/blob/master/packages/core-api/src/lib/AuthConnector/DefaultAuthConnector.ts)
|
||||
in `@backstage/core-api`.
|
||||
|
||||
Component and APIs can request Access or ID Tokens from any available Auth
|
||||
|
||||
@@ -311,3 +311,114 @@ component, but there will always be one ultimate owner.
|
||||
Apart from being a string, the software catalog leaves the format of this field
|
||||
open to implementers to choose. Most commonly, it is set to the ID or email of a
|
||||
group of people in an organizational structure.
|
||||
|
||||
## Kind: Template
|
||||
|
||||
Describes the following entity kind:
|
||||
|
||||
| Field | Value |
|
||||
| -------------------- | ----------------------- |
|
||||
| `apiVersion` | `backstage.io/v1alpha1` |
|
||||
| `Kind: Templatekind` | `Template` |
|
||||
|
||||
A Template describes a skeleton for use with the Scaffolder. It is used for
|
||||
describing what templating library is supported, and also for documenting the
|
||||
variables that the template requires using
|
||||
[JSON Forms Schema](https://jsonforms.io/).
|
||||
|
||||
Descriptor files for this kind may look as follows.
|
||||
|
||||
```yaml
|
||||
apiVersion: backstage.io/v1alpha1
|
||||
kind: Template
|
||||
metadata:
|
||||
name: react-ssr-template
|
||||
title: React SSR Template
|
||||
description:
|
||||
Next.js application skeleton for creating isomorphic web applications.
|
||||
tags:
|
||||
- Recommended
|
||||
- React
|
||||
spec:
|
||||
owner: web@example.com
|
||||
templater: cookiecutter
|
||||
type: website
|
||||
path: '.'
|
||||
schema:
|
||||
required:
|
||||
- component_id
|
||||
- description
|
||||
properties:
|
||||
component_id:
|
||||
title: Name
|
||||
type: string
|
||||
description: Unique name of the component
|
||||
description:
|
||||
title: Description
|
||||
type: string
|
||||
description: Description of the component
|
||||
```
|
||||
|
||||
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 `Template`, respectively.
|
||||
|
||||
### `metadata.title` [required]
|
||||
|
||||
The nice display name for the template as a string, e.g. `React SSR Template`.
|
||||
This field is required as is used to reference the template to the user instead
|
||||
of the `metadata.name` field.
|
||||
|
||||
### `metadata.tags` [optional]
|
||||
|
||||
A list of strings that can be associated with the template, e.g.
|
||||
`['Recommended', 'React']`.
|
||||
|
||||
This list will also be used in the frontend to display to the user so you can
|
||||
potentially search and group templates by these tags.
|
||||
|
||||
### `spec.type` [optional]
|
||||
|
||||
The type of component as a string, e.g. `website`. This field is optional but
|
||||
recommended.
|
||||
|
||||
The software catalog accepts any type value, but an organisation should take
|
||||
great care to establish a proper taxonomy for these. Tools including Backstage
|
||||
itself may read this field and behave differently depending on its value. For
|
||||
example, a website type component may present tooling in the Backstage interface
|
||||
that is specific to just websites.
|
||||
|
||||
The current set of well-known and common values for this field is:
|
||||
|
||||
- `service` - a backend service, typically exposing an API
|
||||
- `website` - a website
|
||||
- `library` - a software library, such as an NPM module or a Java library
|
||||
|
||||
### `spec.templater` [required]
|
||||
|
||||
The templating library that is supported by the template skeleton as a string,
|
||||
e.g `cookiecutter`.
|
||||
|
||||
Different skeletons will use different templating syntax, so it's common that
|
||||
the template will need to be run with a particular piece of software.
|
||||
|
||||
This key will be used to identify the correct templater which is registered into
|
||||
the `TemplatersBuilder`.
|
||||
|
||||
The values which are available by default are:
|
||||
|
||||
- `cookiecutter` - [cookiecutter](https://github.com/cookiecutter/cookiecutter).
|
||||
|
||||
### `spec.path` [optional]
|
||||
|
||||
The string location where the templater should be run if it is not on the same
|
||||
level as the `template.yaml` definition, e.g. `./cookiecutter/skeleton`.
|
||||
|
||||
This will set the `cwd` when running the templater to the folder path that you
|
||||
specify relative to the `template.yaml` definition.
|
||||
|
||||
This is also particularly useful when you have multiple template definitions in
|
||||
the same repository but only a single `template.yaml` registered in backstage.
|
||||
|
||||
@@ -21,4 +21,6 @@ can be organised around the entities in the catalog.
|
||||
|
||||
## Using the Service Catalog
|
||||
|
||||
TODO
|
||||
|
||||

|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
# Adding your own Templates
|
||||
|
||||
Templates are stored in the **Service Catalog** under a kind `Template`. The
|
||||
minimum that the a template skeleton needs is a `template.yaml` but it would be
|
||||
good to also have some files in there that can be templated in.
|
||||
|
||||
A simple `template.yaml` definition might look something like this:
|
||||
|
||||
```yaml
|
||||
apiVersion: backstage.io/v1alpha1
|
||||
kind: Template
|
||||
metadata:
|
||||
# unique name per namespace for the template
|
||||
name: react-ssr-template
|
||||
# title of the template
|
||||
title: React SSR Template
|
||||
# a description of the template
|
||||
description: Next.js application skeleton for creating isomorphic web applications.
|
||||
# some tags to display in the frontend
|
||||
tags:
|
||||
- Recommended
|
||||
- React
|
||||
spec:
|
||||
# which templater key to use in the templaters builder
|
||||
templater: cookiecutter
|
||||
# what does this template create
|
||||
type: website
|
||||
# if the template is not in the current directory where this definition is kept then specfiy
|
||||
path: './template'
|
||||
# the schema for the form which is displayed in the frontend.
|
||||
# should follow JSON schema for forms: https://jsonforms.io/
|
||||
schema:
|
||||
required:
|
||||
- component_id
|
||||
- description
|
||||
properties:
|
||||
component_id:
|
||||
title: Name
|
||||
type: string
|
||||
description: Unique name of the component
|
||||
description:
|
||||
title: Description
|
||||
type: string
|
||||
description: Description of the component
|
||||
```
|
||||
|
||||
[Template Entity](../software-catalog/descriptor-format.md#kind-template)
|
||||
contains more information about the required fields.
|
||||
|
||||
Once we have a `template.yaml` ready, we can then add it to the service catalog
|
||||
for use by the scaffolder.
|
||||
|
||||
Currently the catalog supports loading definitions from Github + Local Files. To
|
||||
load from other places, not only will there need to be another preparer, but the
|
||||
support to load the location will also need to be added to the Catalog.
|
||||
|
||||
For loading from a file the following command should work when the backend is
|
||||
running:
|
||||
|
||||
```sh
|
||||
curl \
|
||||
--location \
|
||||
--request POST 'localhost:7000/catalog/locations' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw "{\"type\": \"file\", \"target\": \"${YOUR PATH HERE}/template.yaml\"}"
|
||||
```
|
||||
|
||||
If loading from a git location, you can run the following
|
||||
|
||||
```sh
|
||||
curl \
|
||||
--location \
|
||||
--request POST 'localhost:7000/catalog/locations' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw "{\"type\": \"github\", \"target\": \"https://${YOUR GITHUB REPO}blob/master/${PATH TO FOLDER}/template.yaml\"}"
|
||||
```
|
||||
|
||||
This should then have added the catalog, and also should now be listed under the
|
||||
create page at http://localhost:3000/create.
|
||||
|
||||
Alternatively, if you want to get setup with some mock templates that are
|
||||
already provided for you, you can run the following to load those templates:
|
||||
|
||||
```
|
||||
yarn lerna run mock-data
|
||||
```
|
||||
|
||||
The `type` field which is chosen in the request to add the `template.yaml` to
|
||||
the Service Catalog here, will be come the `PreparerKey` which will be used to
|
||||
select the `Preparer` when creating a job.
|
||||
|
||||
### Adding form values in the Scaffolder Wizard
|
||||
|
||||
The `spec.schema` property in the
|
||||
[Template Entity](../software-catalog/descriptor-format.md#kind-template) is a
|
||||
`yaml` version of the JSON Form Schema standard.
|
||||
|
||||
Here you can define the key/values and then the wizard will convert this to a
|
||||
form for the user to fill in when your template is selected.
|
||||
|
||||
You can find out much more about the standard and how to use it here:
|
||||
https://jsonforms.io
|
||||
|
||||
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
@@ -0,0 +1,97 @@
|
||||
# Create your own Preparer
|
||||
|
||||
Preparers are responsible for reading the location of the definition of a
|
||||
[Template Entity](../../software-catalog/descriptor-format.md#kind-template) and
|
||||
making a temporary folder with the contents of the selected skeleton.
|
||||
|
||||
Currently, we provide two different providers that can parse two different
|
||||
location protocols:
|
||||
|
||||
- `file://`
|
||||
- `github://`
|
||||
|
||||
These two are added to the `PreparersBuilder` and then passed into the
|
||||
`createRouter` function of the `@spotify/plugin-scaffolder-backend`
|
||||
|
||||
An full example backend can be found
|
||||
[here](https://github.com/spotify/backstage/blob/d91c10f654475a60829fa33a5c81018e517a319a/packages/backend/src/plugins/scaffolder.ts),
|
||||
but it looks something like the following
|
||||
|
||||
```ts
|
||||
import {
|
||||
createRouter,
|
||||
FilePreparer,
|
||||
GithubPreparer,
|
||||
Preparers,
|
||||
} from '@backstage/plugin-scaffolder-backend';
|
||||
import type { PluginEnvironment } from '../types';
|
||||
|
||||
export default async function createPlugin({ logger }: PluginEnvironment) {
|
||||
const preparers = new Preparers();
|
||||
|
||||
const filePreparer = new FilePreparer();
|
||||
const githubPreparer = new GithubPreparer();
|
||||
|
||||
preparers.register('file', filePreparer);
|
||||
preparers.register('github', githubPreparer);
|
||||
|
||||
return await createRouter({
|
||||
preparers,
|
||||
templaters,
|
||||
logger,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
As you can see in the above code, a `PreparerBuilder` is created, and then two
|
||||
of the `preparers` are registered with the different protocols that they accept.
|
||||
|
||||
The `protocol` is set on the
|
||||
[Template Entity](../../software-catalog/descriptor-format.md#kind-template)
|
||||
when added to the service catalog. You can see more about this `PreparerKey`
|
||||
here in [Register your own template](../adding-templates.md)
|
||||
|
||||
**note:** Currently the catalog supports loading definitions from Github + Local
|
||||
Files, which translate into the two `PreparerKeys` `file` and `github`. To load
|
||||
from other places, not only will there need to be another preparer, but the
|
||||
support to load the location will also need to be added to the Catalog.
|
||||
|
||||
### Creating your own Preparer to add to the `PreparerBuilder`
|
||||
|
||||
All preparers need to implement the `PreparerBase` type.
|
||||
|
||||
That type looks like the following:
|
||||
|
||||
```ts
|
||||
export type PreparerBase = {
|
||||
prepare(
|
||||
template: TemplateEntityV1alpha1,
|
||||
opts: { logger: Logger },
|
||||
): Promise<string>;
|
||||
};
|
||||
```
|
||||
|
||||
The `prepare` function will be given the
|
||||
[Template Entity](../../software-catalog/descriptor-format.md#kind-template)
|
||||
along with the source of where the `template.yaml` was loaded from under the
|
||||
`metedata.annotations.managed-by-location` property.
|
||||
|
||||
Now it's up to you to implement a function which can go and fetch the skeleton
|
||||
and put the contents into a temporary directory and return that directory path.
|
||||
|
||||
Some good examples exist here:
|
||||
|
||||
- https://github.com/spotify/backstage/blob/master/plugins/scaffolder-backend/src/scaffolder/stages/prepare/file.ts
|
||||
- https://github.com/spotify/backstage/blob/master/plugins/scaffolder-backend/src/scaffolder/stages/prepare/github.ts
|
||||
|
||||
### Registerinng your own Preparer
|
||||
|
||||
You can register the preparer that you have created with the `PreparerBuilder`
|
||||
by using the `PreparerKey` from the Catalog, for example like this:
|
||||
|
||||
```ts
|
||||
const preparers = new Preparers();
|
||||
preparers.register('gcs', new GoogleCloudStoragePreparer());
|
||||
```
|
||||
|
||||
And then pass this in to the `createRouter` function.
|
||||
@@ -0,0 +1,92 @@
|
||||
# Create your own Publisher
|
||||
|
||||
Publishers are responsible for pushing and storing the templated skeleton after
|
||||
the values have been templated by the `Templater`. See
|
||||
[Create your own templater](./create-your-own-templater.md) for more info.
|
||||
|
||||
They recieve a directory or location where the templater has sucessfully run on,
|
||||
and is now ready to store somewhere. They also get given some other options
|
||||
which are sent from the frontend, such as the `storePath` which is a string of
|
||||
where the frontend thinks we should save this templated folder.
|
||||
|
||||
Currently we provide the following `publishers`:
|
||||
|
||||
- `github`
|
||||
|
||||
This publisher is passed through to the `createRouter` function of the
|
||||
`@spotify/plugin-scaffolder-backend`. Currently only one publisher is supported,
|
||||
but PR's are always welcome.
|
||||
|
||||
An full example backend can be found
|
||||
[here](https://github.com/spotify/backstage/blob/d91c10f654475a60829fa33a5c81018e517a319a/packages/backend/src/plugins/scaffolder.ts),
|
||||
but it looks something like the following
|
||||
|
||||
```ts
|
||||
import {
|
||||
createRouter,
|
||||
GithubPublisher,
|
||||
} from '@backstage/plugin-scaffolder-backend';
|
||||
import { Octokit } from '@octokit/rest';
|
||||
import type { PluginEnvironment } from '../types';
|
||||
|
||||
export default async function createPlugin({ logger }: PluginEnvironment) {
|
||||
const githubClient = new Octokit({ auth: process.env.GITHUB_ACCESS_TOKEN });
|
||||
const publisher = new GithubPublisher({ client: githubClient });
|
||||
|
||||
return await createRouter({
|
||||
publisher,
|
||||
logger,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
The publisher will always be called with the location from the selected
|
||||
`Preparer`.
|
||||
|
||||
### Create your own Publisher and register it with the Scaffolder
|
||||
|
||||
All `publishers` need to implement the `PublisherBase` type.
|
||||
|
||||
That type looks like the following:
|
||||
|
||||
```ts
|
||||
export type PublisherBase = {
|
||||
publish(opts: {
|
||||
entity: TemplateEntityV1alpha1;
|
||||
values: RequiredTemplateValues & Record<string, JsonValue>;
|
||||
directory: string;
|
||||
}): Promise<{ remoteUrl: string }>;
|
||||
};
|
||||
```
|
||||
|
||||
The `publisher` function will be called with an `options` object which contains
|
||||
the following:
|
||||
|
||||
- `entity` - the
|
||||
[Template Entity](../../software-catalog/descriptor-format.md#kind-template)
|
||||
which is currently being scaffolded
|
||||
- `values` - a json object which will resemble the `spec.schema` from the
|
||||
[Template Entity](../../software-catalog/descriptor-format.md#kind-template)
|
||||
which is defined here under spec.schema`. More info can be found here
|
||||
[Register your own template](../adding-templates.md#adding-form-values-in-the-scaffolder-wizard)
|
||||
- `directory` - a string containing the returned path from the `templater`. See
|
||||
more information here in
|
||||
[Create your own templater](./create-your-own-templater.md)
|
||||
|
||||
Now it's up to you to implement the `publish` function and return
|
||||
`{ remoteUrl: string }` which can be used to identify the finished product.
|
||||
|
||||
Some good examples exist here:
|
||||
|
||||
- https://github.com/spotify/backstage/blob/master/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts
|
||||
|
||||
### Registering your own Publisher
|
||||
|
||||
Currently, we only support one `publisher` (PR's welcome), but you can register
|
||||
any single `publisher` with the `createRouter`.
|
||||
|
||||
```ts
|
||||
return await createRouter({
|
||||
publisher: new MyGitlabPublisher(),
|
||||
});
|
||||
```
|
||||
@@ -0,0 +1,146 @@
|
||||
# Creating your own Templater
|
||||
|
||||
Templaters are responsible for taking the directory path for the skeleton
|
||||
returned by the preparers, and then executing the templating command on top of
|
||||
the file and returning the completed template path. This may or may not be the
|
||||
same directory as the input directory.
|
||||
|
||||
They also recieve additional values from the frontend, which can be used to
|
||||
interpolate into the skeleton files.
|
||||
|
||||
Currently we provide the following templaters:
|
||||
|
||||
- `cookiecutter`
|
||||
|
||||
This templater is added the `TemplaterBuilder` and then passed into the
|
||||
`createRouter` function of the `@spotify/plugin-scaffolder-backend`
|
||||
|
||||
An full example backend can be found
|
||||
[here](https://github.com/spotify/backstage/blob/d91c10f654475a60829fa33a5c81018e517a319a/packages/backend/src/plugins/scaffolder.ts),
|
||||
but it looks something like the following
|
||||
|
||||
```ts
|
||||
import {
|
||||
CookieCutter,
|
||||
createRouter,
|
||||
Templaters,
|
||||
} from '@backstage/plugin-scaffolder-backend';
|
||||
import type { PluginEnvironment } from '../types';
|
||||
|
||||
export default async function createPlugin({ logger }: PluginEnvironment) {
|
||||
const templaters = new Templaters();
|
||||
const cookiecutterTemplater = new CookieCutter();
|
||||
templaters.register('cookiecutter', cookiecutterTemplater);
|
||||
|
||||
return await createRouter({
|
||||
templaters,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
As you can see in the above code a `TemplaterBuilder` is created and the default
|
||||
`cookiecutter` `templater` is registered under the key `cookicutter`.
|
||||
|
||||
This `TemplaterKey` is used to select the correct templater from the
|
||||
`spec.templater` in the
|
||||
[Template Entity](../../software-catalog/descriptor-format.md#kind-template).
|
||||
|
||||
If you wish to add a new templater you'll need to register it with the
|
||||
`TemplaterBuilder`.
|
||||
|
||||
### Creating your own Templater to add to the `TemplaterBuilder`
|
||||
|
||||
All templaters need to implement the `TemplaterBase` type.
|
||||
|
||||
That type looks like the following:
|
||||
|
||||
```ts
|
||||
export type TemplaterRunOptions = {
|
||||
directory: string;
|
||||
values: RequiredTemplateValues & Record<string, JsonValue>;
|
||||
logStream?: Writable;
|
||||
dockerClient: Docker;
|
||||
};
|
||||
|
||||
export type TemplaterBase = {
|
||||
run(opts: TemplaterRunOptions): Promise<TemplaterRunResult>;
|
||||
};
|
||||
```
|
||||
|
||||
The `run` function will be given a `TemplaterRunOptions` object which is as
|
||||
follows:
|
||||
|
||||
- `directory`- the skeleton directory returned from the `Preparer`, more info at
|
||||
[Create your own preparer](./create-your-own-preparer.md).
|
||||
- `values` - a json object which will resemble the `spec.schema` from the
|
||||
[Template Entity](../../software-catalog/descriptor-format.md#kind-template)
|
||||
which is defined here under spec.schema`. More info can be found here
|
||||
[Register your own template](../adding-templates.md#adding-form-values-in-the-scaffolder-wizard)
|
||||
- `logStream` - a stream that you can write to for displaying in the frontend.
|
||||
- `dockerClient` - a [dockerode](https://github.com/apocas/dockerode) client to
|
||||
be able to run docker containers.
|
||||
|
||||
_note_ currently the templaters that we provide are basically docker action
|
||||
containers that are run on top of the skeleton folder. This keeps dependencies
|
||||
to a minimal for running backstage scaffolder, but you don't /have/ to use
|
||||
docker. You could create your own templater that spins up an EC2 instance and
|
||||
downloads the folder and does everything using an AMI if you want. It's entirely
|
||||
up to you!
|
||||
|
||||
Now it's up to you to implement the `run` function, and then return a
|
||||
`TemplaterRunResult` which is `{ resultDir: string }`.
|
||||
|
||||
Some good examples exist here:
|
||||
|
||||
- https://github.com/spotify/backstage/blob/master/plugins/scaffolder-backend/src/scaffolder/stages/templater/cookiecutter.ts
|
||||
|
||||
### Registering your own Templater
|
||||
|
||||
If you try to process a
|
||||
[Template Entity](../../software-catalog/descriptor-format.md#kind-template)
|
||||
with a new `spec.templater` value, you'll need to register that with the
|
||||
`TemplaterBuilder`.
|
||||
|
||||
For example let's say you have the following
|
||||
[Template Entity](../../software-catalog/descriptor-format.md#kind-template):
|
||||
|
||||
```yaml
|
||||
apiVersion: backstage.io/v1alpha1
|
||||
kind: Template
|
||||
metadata:
|
||||
name: react-ssr-template
|
||||
title: React SSR Template
|
||||
description:
|
||||
Next.js application skeleton for creating isomorphic web applications.
|
||||
tags:
|
||||
- Recommended
|
||||
- React
|
||||
spec:
|
||||
owner: web@example.com
|
||||
templater: handlebars
|
||||
type: website
|
||||
path: '.'
|
||||
schema:
|
||||
required:
|
||||
- component_id
|
||||
- description
|
||||
properties:
|
||||
component_id:
|
||||
title: Name
|
||||
type: string
|
||||
description: Unique name of the component
|
||||
description:
|
||||
title: Description
|
||||
type: string
|
||||
description: Description of the component
|
||||
```
|
||||
|
||||
You see that the `spec.templater` is set as `handlebars`, you'll need to
|
||||
register this with the `TemplaterBuilder` like so:
|
||||
|
||||
```ts
|
||||
const templaters = new Templaters();
|
||||
templaters.register('handlebars', new HandlebarsTemplater());
|
||||
```
|
||||
|
||||
And then pass this in to the `createRouter` function.
|
||||
@@ -0,0 +1,87 @@
|
||||
## Extending the Scaffolder
|
||||
|
||||
Welcome. Take a seat. You're at the Scaffolder Documentation.
|
||||
|
||||
So - You wanna create stuff inside your company from some prebaked templates?
|
||||
You're at the right place.
|
||||
|
||||
This guide is gonna take you through how the Scaffolder in Backstage works.
|
||||
We'll dive into some jargon and run through whats going on in the backend to be
|
||||
able to create these templates. There's also more guides that you might find
|
||||
useful at the bottom of this document. At it's core, theres 3 simple stages.
|
||||
|
||||
1. Pick a skeleton
|
||||
2. Template some variables into the skeleton
|
||||
3. Send the templated skeleton somewhere
|
||||
|
||||
These three steps are translated to the folllowing stages under the hood in the
|
||||
scaffolder that you will need to know:
|
||||
|
||||
1. Prepare
|
||||
2. Template
|
||||
3. Publish
|
||||
|
||||
Each of these steps can be configured for your own use case, but we provide some
|
||||
sensible defaults too.
|
||||
|
||||
Lets dive a little deeper into these phases.
|
||||
|
||||
### Glossary and Jargon
|
||||
|
||||
**Preparer** - The preparer is responsible for fetching the skeleton code and
|
||||
placing it into a directory and then will return that directory. It is
|
||||
registered with the `Preparers` with a particular type, which is then used in
|
||||
the router to pick the correct `Preparer` to run for the `Template` entity.
|
||||
|
||||
**Templater** - The templater is responsible for actually running the chosen
|
||||
templater on top of the previously returned temporary directory from the
|
||||
**Preprarer**. We advise making these docker containers as it can keep all
|
||||
dependencies, for example Cookiecutter, self contained and not a dependency on
|
||||
the host machine.
|
||||
|
||||
**Publisher** - The publisher is responsible for taking the finished directory,
|
||||
and publishing it to a remote registry. This could be a Git repository or
|
||||
something similar. Right now, the scaffolder only supports one publishing method
|
||||
for the entire lifecycle, but it could be configured from the frontend and
|
||||
passed through to the scaffolder backend.
|
||||
|
||||
### How it works
|
||||
|
||||
The main of the heavy lifting is done in the
|
||||
[router.ts](https://github.com/spotify/backstage/blob/master/plugins/scaffolder-backend/src/service/router.ts#L93)
|
||||
file in the `scaffolder-backend` plugin.
|
||||
|
||||
There are 2 routes defined in the router. `POST /v1/jobs` and
|
||||
`GET /v1/job/:jobId`
|
||||
|
||||
To create a scaffolding job, a JSON object containing the
|
||||
[Template Entity](../../software-catalog/descriptor-format.md#kind-template) +
|
||||
additional templating values must be posted as the post body.
|
||||
|
||||
```js
|
||||
{
|
||||
"template": {
|
||||
"apiVersion": "backstage/v1alpha1",
|
||||
"kind": "Template",
|
||||
// more stuff here
|
||||
},
|
||||
"values": {
|
||||
"component_id": "test",
|
||||
"description": "somethingelse"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The values should represent something that is valid with the `schema` part of
|
||||
the [Template Entity](../../software-catalog/descriptor-format.md#kind-template)
|
||||
|
||||
Once that has been posted, a job will be setup with different stages. And the
|
||||
job processor will complete each stage before moving onto the next stage, whilst
|
||||
collecting logs and mutating the running job.
|
||||
|
||||
Here's some futher reading that you might find useful:
|
||||
|
||||
- [Adding your own Template](../adding-templates.md)
|
||||
- [Creating your own Templater](./create-your-own-templater.md)
|
||||
- [Creating your own Publisher](./create-your-own-publisher.md)
|
||||
- [Creating your own Preparer](./create-your-own-preparer.md)
|
||||
@@ -0,0 +1,59 @@
|
||||
# Software Templates
|
||||
|
||||
The Software Templates part of Backstage is a tool that can help you create
|
||||
Components inside Backstage. It by default has the ability to load skeletons of
|
||||
code, template in some variables and then publish the template to some location
|
||||
like GitHub.
|
||||
|
||||
### Getting Started
|
||||
|
||||
The Software Templates are available under `/create`, and if you've followed
|
||||
[Getting Started with Backstage](../../getting-started), you should be able to
|
||||
reach `http://localhost:3000/create`.
|
||||
|
||||
You should get something that looks similar to this:
|
||||
|
||||

|
||||
|
||||
### Choose a template
|
||||
|
||||
When you select a template that you want to create, you'll be taken to the next
|
||||
page which may or may not look different for each template. Each template can
|
||||
ask for different input variables, and they are then passed to the templater
|
||||
internally.
|
||||
|
||||

|
||||
|
||||
After filling in these variables, you'll get some more fields to fill out which
|
||||
are required for backstage usage. The owner, which is a `user` in the backstage
|
||||
system, and the `storePath` which right now must be a Github Organisation and a
|
||||
non-existing github repository name in the format `organistaion/reponame`.
|
||||
|
||||

|
||||
|
||||
### Run!
|
||||
|
||||
Once you've entered values and confirmed, you'll then get a modal with live
|
||||
progress of what is currently happening with the creation of your template.
|
||||
|
||||

|
||||
|
||||
It shouldn't take too long, and you'll have a success screen!
|
||||
|
||||

|
||||
|
||||
If it fails, you'll be able to click on each section to get the log from the
|
||||
step that failed which can be helpful to debug.
|
||||
|
||||

|
||||
|
||||
### View Component in Catalog
|
||||
|
||||
When it's been created you'll see the `View in Catalog` button, which will take
|
||||
you to the registered component in the catalog:
|
||||
|
||||

|
||||
|
||||
And then you'll also be able to see it in the Catalog View table
|
||||
|
||||

|
||||
|
||||
@@ -1 +1,109 @@
|
||||
# Getting Started
|
||||
|
||||
> TechDocs is not feature complete and currently you can't set up a complete
|
||||
> end-to-end working TechDocs plugin without customizing the plugin itself.
|
||||
|
||||
> With TechDocs V.0 you can expect a demonstration of how to integrate docs into
|
||||
> Backstage. Currently it can create docs using
|
||||
> [mkdocs](https://www.mkdocs.org/), as well as reading published docs. If you
|
||||
> publish generated docs and passing in a storageUrl in your `app-config.yaml`
|
||||
> you can view it in Backstage by going to
|
||||
> `http://localhost:3000/docs/<remote-folder>`.
|
||||
|
||||
Getting started with TechDocs is easy. TechDocs functions as a plugin to
|
||||
Backstage, why you will need to use Backstage to use TechDocs.
|
||||
|
||||
## What is Backstage?
|
||||
|
||||
Backstage is an open platform for building developer portals. It’s based on the
|
||||
developer portal we’ve been using internally at Spotify for over four years.
|
||||
[Read more](https://github.com/spotify/backstage).
|
||||
|
||||
## Prerequisities
|
||||
|
||||
In order to use Backstage and TechDocs, you will need to have the following
|
||||
installed:
|
||||
|
||||
- [Node.js](https://nodejs.org) Active LTS (long term support), currently v12
|
||||
- [Yarn](https://yarnpkg.com/getting-started/install)
|
||||
|
||||
## Creating a new Backstage app
|
||||
|
||||
> If you have already created a Backstage application for this purpose, jump to
|
||||
> [Installing TechDocs](#installing-techdocs), otherwise complete this step.
|
||||
|
||||
To create a new Backstage application for us to set up TechDocs, you will need
|
||||
to run the following command:
|
||||
|
||||
```bash
|
||||
npx @backstage/cli create-app
|
||||
```
|
||||
|
||||
You will then be prompted to enter a name for your application. Once you do so,
|
||||
this will create a new Backstage application for you in a new folder. For
|
||||
example, if we chose the name `hello-world` for our application, it would create
|
||||
a new `hello-world` folder containing our new Backstage application.
|
||||
|
||||
## Installing TechDocs
|
||||
|
||||
Inside of our new Backstage application, TechDocs is not provided by default.
|
||||
For this reason we will need to manually set up TechDocs. It should take less
|
||||
than a minute.
|
||||
|
||||
### Adding the package
|
||||
|
||||
We will need to add our plugin to your Backstage application. To do so, you can
|
||||
navigate to your new Backstage application folder and then run a single command
|
||||
to install TechDocs.
|
||||
|
||||
```bash
|
||||
cd hello-world/
|
||||
```
|
||||
|
||||
Then you need to navigate to your `packages/app` folder to install TechDocs:
|
||||
|
||||
```bash
|
||||
cd packages/app
|
||||
yarn add @backstage/plugin-techdocs
|
||||
```
|
||||
|
||||
After a short while, it should successfully install the TechDocs plugin. Now we
|
||||
just need to set up some basic configuration!
|
||||
|
||||
Enter the following command:
|
||||
|
||||
```bash
|
||||
yarn install
|
||||
```
|
||||
|
||||
Add this to `packages/app/src/plugins.ts`:
|
||||
|
||||
```typescript
|
||||
export { plugin as TechDocs } from '@backstage/plugin-techdocs';
|
||||
```
|
||||
|
||||
### Setting the configuration
|
||||
|
||||
TechDocs allows for configuration of the docs storage URL through your
|
||||
app-config file. The URL provided here is demo docs used to testing.
|
||||
|
||||
To use the demo docs, add the following lines to `app-config.yaml`:
|
||||
|
||||
```yaml
|
||||
techdocs:
|
||||
storageUrl: https://techdocs-mock-sites.storage.googleapis.com
|
||||
```
|
||||
|
||||
## Run Backstage Locally
|
||||
|
||||
Change folder to your Backstage application root.
|
||||
|
||||
```bash
|
||||
yarn start
|
||||
```
|
||||
|
||||
Open browser at [http://localhost:3000/docs/](http://localhost:3000/docs/)
|
||||
|
||||
## Extra Reading
|
||||
|
||||
[Back to Docs](README.md)
|
||||
|
||||
@@ -1 +1,108 @@
|
||||
# Publishing Documentation
|
||||
|
||||
## Prerequisities
|
||||
|
||||
- [Docker](https://docs.docker.com/get-docker/)
|
||||
- Static file hosting
|
||||
|
||||
## Create documentation
|
||||
|
||||
Create a directory that contains your documentation. Inside this directory you
|
||||
should create a file called `mkdocs.yml`. As an example you can create a
|
||||
directory called `hello-docs` in your home directory (also known as `~`). Below
|
||||
is a basic example of how it could look.
|
||||
|
||||
The `~/hello-docs/mkdocs.yml` file should have the following content:
|
||||
|
||||
```yaml
|
||||
site_name: 'example-docs'
|
||||
|
||||
nav:
|
||||
- Home: index.md
|
||||
|
||||
plugins:
|
||||
- techdocs-core
|
||||
```
|
||||
|
||||
And then the `~/hello-docs/docs/index.md` should have the following content:
|
||||
|
||||
```md
|
||||
# example docs
|
||||
|
||||
This is a basic example of documentation.
|
||||
```
|
||||
|
||||
## Build documentation
|
||||
|
||||
```bash
|
||||
cd ~/hello-docs/
|
||||
docker run -it -w /content -v $(pwd):/content spotify/techdocs build
|
||||
```
|
||||
|
||||
You should now have a folder called `~/hello-docs/site/`.
|
||||
|
||||
## Deploy to a file server
|
||||
|
||||
In order to serve documentation to TechDocs, our Backstage plugin needs to
|
||||
download the HTML rendered from the
|
||||
[Create documentation](#create-documentation) step above. This will likely exist
|
||||
on an external file server, or a storage solution such as Google Cloud Storage.
|
||||
|
||||
When deploying documentation, it should be deployed on that file server /
|
||||
storage solution with the following convention: `{id}/{file}`. For example, if
|
||||
we wanted to upload the `getting-started/index.html` file for the `backstage`
|
||||
documentation site, we would upload it to our file server as
|
||||
`backstage/getting-started/index.html`.
|
||||
|
||||
To explain further how this would look like for multiple documentation sites,
|
||||
take a look at this example file tree that would be represented on your file
|
||||
server:
|
||||
|
||||
```md
|
||||
/backstage/index.html /backstage/getting-started/index.html
|
||||
/backstage/contributing/index.html /mkdocs/index.html
|
||||
/mkdocs/plugin-development/index.html
|
||||
/mkdocs/plugin-development/debugging/index.html
|
||||
```
|
||||
|
||||
In this file tree, we have two documentation sites available: `backstage` and
|
||||
`mkdocs`. Each of them expose several pages. Let's say both of these are hosted
|
||||
on `http://example.com` as the server URL.
|
||||
|
||||
When you configure the TechDocs plugin in Backstage to use `http://example.com`
|
||||
as the file server / storage solution, it will translate the following URLs to
|
||||
the file server:
|
||||
|
||||
| Backstage URL | File Server URL |
|
||||
| --------------------------------------------------------- | ------------------------------------------------------- |
|
||||
| https://demo.backstage.io/docs/backstage/ | http://example.com/backstage/index.html |
|
||||
| https://demo.backstage.io/docs/mkdocs/plugin-development/ | http://example.com/mkdocs/plugin-development/index.html |
|
||||
|
||||
Then deploying new sites is easy. It's as simple as copying over the `site/`
|
||||
folder produced in the [Create documentation](#create-documentation) step above
|
||||
and copying it over to the file server / storage solution under the ID of the
|
||||
documentation site. It will then become immediately available in Backstage under
|
||||
the same ID as you can see in the table above.
|
||||
|
||||
So, if the URL to your file server is `http://example.com/`, your
|
||||
`~/hello-docs/site` folder containing the documentation should be accessible at
|
||||
`http://example.com/hello-docs/`.
|
||||
|
||||
## Configure TechDocs to read from file server
|
||||
|
||||
In order for Backstage to show your documentation, it needs to know where you
|
||||
uploaded it.
|
||||
|
||||
Make sure you have Backstage set up using
|
||||
[TechDocs getting started](getting-started.md)
|
||||
|
||||
To point Backstage to your docs storage, add or change the following lines in
|
||||
your Backstage `app-config.yaml`:
|
||||
|
||||
```yaml
|
||||
techdocs:
|
||||
storageUrl: http://example.com
|
||||
```
|
||||
|
||||
You can now start Backstage using `yarn start` and open up your browser at
|
||||
`http://localhost:3000/docs/hello-docs` to view your docs.
|
||||
|
||||
@@ -50,7 +50,8 @@ system resources and slow things down.
|
||||
|
||||
## Package Scripts
|
||||
|
||||
There are many commands to be found in the root [package.json](package.json),
|
||||
There are many commands to be found in the root
|
||||
[package.json](https://github.com/spotify/backstage/blob/master/package.json),
|
||||
here are some useful ones:
|
||||
|
||||
```python
|
||||
@@ -80,7 +81,9 @@ yarn diff # Make sure all plugins are up to date with the latest plugin template
|
||||
yarn create-plugin # Create a new plugin
|
||||
```
|
||||
|
||||
> See [package.json](/package.json) for other yarn commands/options.
|
||||
> See
|
||||
> [package.json](https://github.com/spotify/backstage/blob/master/package.json)
|
||||
> for other yarn commands/options.
|
||||
|
||||
[Next Step - Create a Backstage plugin](../plugins/create-a-plugin.md)
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ for your new plugin directly by navigating to
|
||||
`http://localhost:3000/my-plugin`._
|
||||
|
||||
<p align='center'>
|
||||
<img src='https://github.com/spotify/backstage/raw/master/docs/getting-started/my-plugin_screenshot.png' width='600' alt='my plugin'>
|
||||
<img src='https://github.com/spotify/backstage/raw/master/docs/plugins/my-plugin_screenshot.png' width='600' alt='my plugin'>
|
||||
</p>
|
||||
|
||||
You can also serve the plugin in isolation by running `yarn start` in the plugin
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# Existing plugins
|
||||
|
||||
## Open source plugins
|
||||
|
||||
The full list of open source plugins can be found
|
||||
[here](https://github.com/spotify/backstage/tree/master/plugins).
|
||||
|
||||
## Plugin gallery
|
||||
|
||||
TODO: In the future we would like to have something similar to
|
||||
https://grafana.com/grafana/plugins
|
||||
|
||||
@@ -5,15 +5,14 @@ Backstage is a single-page application composed of a set of plugins.
|
||||
Our goal for the plugin ecosystem is that the definition of a plugin is flexible
|
||||
enough to allow you to expose pretty much any kind of infrastructure or software
|
||||
development tool as a plugin in Backstage. By following strong
|
||||
[design guidelines](https://github.com/spotify/backstage/blob/master/docs/design.md)
|
||||
we ensure the the overall user experience stays consistent between plugins.
|
||||
[design guidelines](../dls/design.md) we ensure the the overall user experience
|
||||
stays consistent between plugins.
|
||||
|
||||

|
||||
|
||||
## Creating a plugin
|
||||
|
||||
To create a plugin, follow the steps outlined
|
||||
[here](https://github.com/spotify/backstage/blob/master/docs/getting-started/create-a-plugin.md).
|
||||
To create a plugin, follow the steps outlined [here](create-a-plugin.md).
|
||||
|
||||
## Suggesting a plugin
|
||||
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
## NPM
|
||||
|
||||
NPM packages are published through CI/CD in the
|
||||
[.github/workflows/master.yml](../../.github/workflows/master.yml) workflow.
|
||||
Every commit that is merged to master will be checked for new versions of all
|
||||
public packages, and any new versions will automatically be published to NPM.
|
||||
[.github/workflows/master.yml](https://github.com/spotify/backstage/blob/master/.github/workflows/master.yml)
|
||||
workflow. Every commit that is merged to master will be checked for new versions
|
||||
of all public packages, and any new versions will automatically be published to
|
||||
NPM.
|
||||
|
||||
### Creating a new release
|
||||
|
||||
|
||||
@@ -102,6 +102,6 @@ environment you will probably face challenges like CORS policies and/or
|
||||
backend-side authorization. To smooth this process out you can use proxy -
|
||||
either the one you already have (like nginx/haproxy/etc) or the proxy-backend
|
||||
plugin that we provide for the backstage backend.
|
||||
[Read more](../../plugins/proxy-backend/README.md)
|
||||
[Read more](https://github.com/spotify/backstage/blob/master/plugins/proxy-backend/README.md)
|
||||
|
||||
[Back to Getting Started](../README.md)
|
||||
|
||||
@@ -15,7 +15,7 @@ nav:
|
||||
- Create an App: 'getting-started/create-an-app.md'
|
||||
- App configuration:
|
||||
- Configuring App with plugins: 'getting-started/configure-app-with-plugins.md'
|
||||
- Customize the look-and-feel of your App: 'getting-started/customize-app-look-and-feel.md'
|
||||
- Customize the look-and-feel of your App: 'getting-started/app-custom-theme.md'
|
||||
- Deployment scenarios:
|
||||
- Kubernetes: 'getting-started/deployment-k8s.md'
|
||||
- Other: 'getting-started/deployment-other.md'
|
||||
@@ -30,8 +30,12 @@ nav:
|
||||
- API: 'features/software-catalog/api.md'
|
||||
- Software creation templates:
|
||||
- Overview: 'features/software-templates/index.md'
|
||||
- Configure templates: 'features/software-templates/configure-templates.md'
|
||||
- Adding templates: 'features/software-templates/adding-templates.md'
|
||||
- Extending the Scaffolder:
|
||||
- Overview: 'features/software-templates/extending/index.md'
|
||||
- Create your own Templater: 'features/software-templates/extending/create-your-own-templater.md'
|
||||
- Create your own Publisher: 'features/software-templates/extending/create-your-own-publisher.md'
|
||||
- Create your own Preparer: 'features/software-templates/extending/create-your-own-preparer.md'
|
||||
- Docs-like-code:
|
||||
- Overview: 'features/techdocs/README.md'
|
||||
- Getting Started: 'features/techdocs/getting-started.md'
|
||||
@@ -46,7 +50,7 @@ nav:
|
||||
- Overview: 'plugins/index.md'
|
||||
- Existing plugins: 'plugins/existing-plugins.md'
|
||||
- Creating a new plugin: 'plugins/create-a-plugin.md'
|
||||
- Developing a plugin: 'plugins/developing-plugins.md'
|
||||
- Developing a plugin: 'plugins/plugin-development.md'
|
||||
- Structure of a plugin: 'plugins/structure-of-a-plugin.md'
|
||||
- Backends and APIs:
|
||||
- Proxying: 'plugins/proxying.md'
|
||||
@@ -65,7 +69,7 @@ nav:
|
||||
- Glossary: 'auth/glossary.md'
|
||||
- Designing for Backstage:
|
||||
- Backstage Design Language System (DLS): 'dls/design.md'
|
||||
- Storybook -- reusable UI components: 'dls/storybook.md'
|
||||
- Storybook -- reusable UI components: 'http://storybook.backstage.io'
|
||||
- Contributing to Storybook: 'dls/contributing-to-storybook.md'
|
||||
- Figma resources: 'dls/figma.md'
|
||||
- API references:
|
||||
|
||||
@@ -43,6 +43,9 @@ async function getConfig() {
|
||||
),
|
||||
},
|
||||
|
||||
// A bit more opinionated
|
||||
testMatch: ['**/?(*.)test.{js,jsx,mjs,ts,tsx}'],
|
||||
|
||||
// Default behaviour is to not apply transforms for node_modules, but we still want
|
||||
// to apply the esm-transformer to .esm.js files, since that's what we use in backstage packages.
|
||||
transformIgnorePatterns: [
|
||||
@@ -55,13 +58,37 @@ async function getConfig() {
|
||||
options.setupFilesAfterEnv = ['<rootDir>/setupTests.ts'];
|
||||
}
|
||||
|
||||
return {
|
||||
...options,
|
||||
// We read all "jest" config fields in package.json files all the way to the filesystem root.
|
||||
// All configs are merged together to create the final config, with longer paths taking precedence.
|
||||
// The merging of the configs is shallow, meaning e.g. all transforms are replaced if new ones are defined.
|
||||
const pkgJsonConfigs = [];
|
||||
let currentPath = process.cwd();
|
||||
|
||||
// If the package has a jest object in package.json we merge that config in. This is the recommended
|
||||
// location for configuring tests.
|
||||
...require(path.resolve('package.json')).jest,
|
||||
};
|
||||
// Some sanity check to avoid infinite loop
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const packagePath = path.resolve(currentPath, 'package.json');
|
||||
const exists = fs.pathExistsSync(packagePath);
|
||||
if (exists) {
|
||||
try {
|
||||
const data = fs.readJsonSync(packagePath);
|
||||
if (data.jest) {
|
||||
pkgJsonConfigs.unshift(data.jest);
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Failed to parse package.json file reading jest configs, ${error}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const newPath = path.dirname(currentPath);
|
||||
if (newPath === currentPath) {
|
||||
break;
|
||||
}
|
||||
currentPath = newPath;
|
||||
}
|
||||
|
||||
return Object.assign(options, ...pkgJsonConfigs);
|
||||
}
|
||||
|
||||
module.exports = getConfig();
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"dashify": "^2.0.0",
|
||||
"diff": "^4.0.2",
|
||||
"eslint": "^7.1.0",
|
||||
"eslint-formatter-friendly": "^7.0.0",
|
||||
"eslint-plugin-import": "^2.20.2",
|
||||
"eslint-plugin-monorepo": "^0.2.1",
|
||||
"fork-ts-checker-webpack-plugin": "^4.0.5",
|
||||
|
||||
@@ -18,12 +18,12 @@ import { Command } from 'commander';
|
||||
import { run } from '../lib/run';
|
||||
import { paths } from '../lib/paths';
|
||||
|
||||
export default async (cmd: Command) => {
|
||||
export default async (cmd: Command, cmdArgs: string[]) => {
|
||||
const args = [
|
||||
'--ext=js,jsx,ts,tsx',
|
||||
'--max-warnings=0',
|
||||
'--format=codeframe',
|
||||
paths.targetDir,
|
||||
'--format=eslint-formatter-friendly',
|
||||
...(cmdArgs ?? [paths.targetDir]),
|
||||
];
|
||||
if (cmd.fix) {
|
||||
args.push('--fix');
|
||||
|
||||
@@ -29,13 +29,16 @@ export const transforms = (
|
||||
): Transforms => {
|
||||
const { isDev } = options;
|
||||
|
||||
const extraTransforms = isDev ? ['react-hot-loader'] : [];
|
||||
|
||||
const loaders = [
|
||||
{
|
||||
test: /\.(tsx?)$/,
|
||||
exclude: /node_modules/,
|
||||
loader: require.resolve('@sucrase/webpack-loader'),
|
||||
options: {
|
||||
transforms: ['typescript', 'jsx', 'react-hot-loader'],
|
||||
transforms: ['typescript', 'jsx', ...extraTransforms],
|
||||
production: !isDev,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -43,7 +46,8 @@ export const transforms = (
|
||||
exclude: /node_modules/,
|
||||
loader: require.resolve('@sucrase/webpack-loader'),
|
||||
options: {
|
||||
transforms: ['jsx', 'react-hot-loader'],
|
||||
transforms: ['jsx', ...extraTransforms],
|
||||
production: !isDev,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -51,7 +55,10 @@ export const transforms = (
|
||||
use: [
|
||||
{
|
||||
loader: require.resolve('@sucrase/webpack-loader'),
|
||||
options: { transforms: ['jsx'] },
|
||||
options: {
|
||||
transforms: ['jsx', ...extraTransforms],
|
||||
production: !isDev,
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: require.resolve('@svgr/webpack'),
|
||||
|
||||
@@ -4,11 +4,11 @@ Backstage is a single-page application composed of a set of plugins.
|
||||
|
||||
Our goal for the plugin ecosystem is that the definition of a plugin is flexible enough to allow you to expose pretty much any kind of infrastructure or software development tool as a plugin in Backstage. By following strong [design guidelines](https://github.com/spotify/backstage/blob/master/docs/design.md) we ensure the the overall user experience stays consistent between plugins.
|
||||
|
||||

|
||||

|
||||
|
||||
## Creating a plugin
|
||||
|
||||
To create a plugin, follow the steps outlined [here](https://github.com/spotify/backstage/blob/master/docs/getting-started/create-a-plugin.md).
|
||||
To create a plugin, follow the steps outlined [here](https://github.com/spotify/backstage/blob/master/docs/plugins/create-a-plugin.md).
|
||||
|
||||
## Suggesting a plugin
|
||||
|
||||
|
||||
@@ -16,49 +16,56 @@
|
||||
|
||||
import express from 'express';
|
||||
import { AuthProviderRouteHandlers } from '../providers/types';
|
||||
import { NotFoundError } from '@backstage/backend-common';
|
||||
|
||||
export type EnvironmentHandlers = {
|
||||
[key: string]: AuthProviderRouteHandlers;
|
||||
};
|
||||
|
||||
export class EnvironmentHandler implements AuthProviderRouteHandlers {
|
||||
constructor(private readonly providers: EnvironmentHandlers) {}
|
||||
constructor(
|
||||
private readonly providerId: string,
|
||||
private readonly providers: EnvironmentHandlers,
|
||||
) {}
|
||||
|
||||
private getProviderForEnv(req: express.Request): AuthProviderRouteHandlers {
|
||||
private getProviderForEnv(
|
||||
req: express.Request,
|
||||
res: express.Response,
|
||||
): AuthProviderRouteHandlers | undefined {
|
||||
const env = req.query.env?.toString();
|
||||
if (!this.providers.hasOwnProperty(env)) {
|
||||
throw new NotFoundError(
|
||||
`No environment for ${env} found in this provider`,
|
||||
);
|
||||
|
||||
if (this.providers.hasOwnProperty(env)) {
|
||||
return this.providers[env];
|
||||
}
|
||||
return this.providers[env];
|
||||
|
||||
res.status(404).send(
|
||||
`Missing configuration.
|
||||
<br>
|
||||
<br>
|
||||
For this flow to work you need to supply a valid configuration for the "${env}" environment of the "${this.providerId}" provider.`,
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async start(req: express.Request, res: express.Response): Promise<void> {
|
||||
const provider = this.getProviderForEnv(req);
|
||||
await provider.start(req, res);
|
||||
const provider = this.getProviderForEnv(req, res);
|
||||
await provider?.start(req, res);
|
||||
}
|
||||
|
||||
async frameHandler(
|
||||
req: express.Request,
|
||||
res: express.Response,
|
||||
): Promise<void> {
|
||||
const provider = this.getProviderForEnv(req);
|
||||
await provider.frameHandler(req, res);
|
||||
const provider = this.getProviderForEnv(req, res);
|
||||
await provider?.frameHandler(req, res);
|
||||
}
|
||||
|
||||
async refresh(req: express.Request, res: express.Response): Promise<void> {
|
||||
const provider = this.getProviderForEnv(req);
|
||||
if (provider.refresh) {
|
||||
await provider.refresh(req, res);
|
||||
}
|
||||
const provider = this.getProviderForEnv(req, res);
|
||||
await provider?.refresh?.(req, res);
|
||||
}
|
||||
|
||||
async logout(req: express.Request, res: express.Response): Promise<void> {
|
||||
const provider = this.getProviderForEnv(req);
|
||||
if (provider.logout) {
|
||||
await provider.logout(req, res);
|
||||
}
|
||||
const provider = this.getProviderForEnv(req, res);
|
||||
await provider?.logout?.(req, res);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,16 +135,16 @@ export function createGithubProvider(
|
||||
logger: Logger,
|
||||
tokenIssuer: TokenIssuer,
|
||||
) {
|
||||
const providerId = 'github';
|
||||
const envProviders: EnvironmentHandlers = {};
|
||||
|
||||
for (const [env, envConfig] of Object.entries(providerConfig)) {
|
||||
const config = (envConfig as unknown) as OAuthProviderConfig;
|
||||
const { secure, appOrigin } = config;
|
||||
const callbackURLParam = `?env=${env}`;
|
||||
const opts = {
|
||||
clientID: config.clientId,
|
||||
clientSecret: config.clientSecret,
|
||||
callbackURL: `${baseUrl}/github/handler/frame${callbackURLParam}`,
|
||||
callbackURL: `${baseUrl}/${providerId}/handler/frame?env=${env}`,
|
||||
};
|
||||
|
||||
if (!opts.clientID || !opts.clientSecret) {
|
||||
@@ -163,12 +163,12 @@ export function createGithubProvider(
|
||||
envProviders[env] = new OAuthProvider(new GithubAuthProvider(opts), {
|
||||
disableRefresh: true,
|
||||
persistScopes: true,
|
||||
providerId: 'github',
|
||||
providerId,
|
||||
secure,
|
||||
baseUrl,
|
||||
appOrigin,
|
||||
tokenIssuer,
|
||||
});
|
||||
}
|
||||
return new EnvironmentHandler(envProviders);
|
||||
return new EnvironmentHandler(providerId, envProviders);
|
||||
}
|
||||
|
||||
@@ -142,6 +142,7 @@ export function createGitlabProvider(
|
||||
logger: Logger,
|
||||
tokenIssuer: TokenIssuer,
|
||||
) {
|
||||
const providerId = 'gitlab';
|
||||
const envProviders: EnvironmentHandlers = {};
|
||||
|
||||
for (const [env, envConfig] of Object.entries(providerConfig)) {
|
||||
@@ -152,12 +153,10 @@ export function createGitlabProvider(
|
||||
clientSecret,
|
||||
audience,
|
||||
} = (envConfig as unknown) as OAuthProviderConfig;
|
||||
const callbackURLParam = `?env=${env}`;
|
||||
|
||||
const opts = {
|
||||
clientID: clientId,
|
||||
clientSecret: clientSecret,
|
||||
callbackURL: `${baseUrl}/gitlab/handler/frame${callbackURLParam}`,
|
||||
callbackURL: `${baseUrl}/${providerId}/handler/frame?env=${env}`,
|
||||
baseURL: audience,
|
||||
};
|
||||
|
||||
@@ -176,12 +175,12 @@ export function createGitlabProvider(
|
||||
|
||||
envProviders[env] = new OAuthProvider(new GitlabAuthProvider(opts), {
|
||||
disableRefresh: true,
|
||||
providerId: 'gitlab',
|
||||
providerId,
|
||||
secure,
|
||||
baseUrl,
|
||||
appOrigin,
|
||||
tokenIssuer,
|
||||
});
|
||||
}
|
||||
return new EnvironmentHandler(envProviders);
|
||||
return new EnvironmentHandler(providerId, envProviders);
|
||||
}
|
||||
|
||||
@@ -154,16 +154,16 @@ export function createGoogleProvider(
|
||||
logger: Logger,
|
||||
tokenIssuer: TokenIssuer,
|
||||
) {
|
||||
const providerId = 'google';
|
||||
const envProviders: EnvironmentHandlers = {};
|
||||
|
||||
for (const [env, envConfig] of Object.entries(providerConfig)) {
|
||||
const config = (envConfig as unknown) as OAuthProviderConfig;
|
||||
const { secure, appOrigin } = config;
|
||||
const callbackURLParam = `?env=${env}`;
|
||||
const opts = {
|
||||
clientID: config.clientId,
|
||||
clientSecret: config.clientSecret,
|
||||
callbackURL: `${baseUrl}/google/handler/frame${callbackURLParam}`,
|
||||
callbackURL: `${baseUrl}/${providerId}/handler/frame?env=${env}`,
|
||||
};
|
||||
|
||||
if (!opts.clientID || !opts.clientSecret) {
|
||||
@@ -181,12 +181,12 @@ export function createGoogleProvider(
|
||||
|
||||
envProviders[env] = new OAuthProvider(new GoogleAuthProvider(opts), {
|
||||
disableRefresh: false,
|
||||
providerId: 'google',
|
||||
providerId,
|
||||
secure,
|
||||
baseUrl,
|
||||
appOrigin,
|
||||
tokenIssuer,
|
||||
});
|
||||
}
|
||||
return new EnvironmentHandler(envProviders);
|
||||
return new EnvironmentHandler(providerId, envProviders);
|
||||
}
|
||||
|
||||
@@ -152,16 +152,16 @@ export function createOAuth2Provider(
|
||||
logger: Logger,
|
||||
tokenIssuer: TokenIssuer,
|
||||
) {
|
||||
const providerId = 'oauth2';
|
||||
const envProviders: EnvironmentHandlers = {};
|
||||
|
||||
for (const [env, envConfig] of Object.entries(providerConfig)) {
|
||||
const config = (envConfig as unknown) as GenericOAuth2ProviderConfig;
|
||||
const { secure, appOrigin } = config;
|
||||
const callbackURLParam = `?env=${env}`;
|
||||
const opts = {
|
||||
clientID: config.clientId,
|
||||
clientSecret: config.clientSecret,
|
||||
callbackURL: `${baseUrl}/oauth2/handler/frame${callbackURLParam}`,
|
||||
callbackURL: `${baseUrl}/${providerId}/handler/frame?env=${env}`,
|
||||
authorizationURL: config.authorizationURL,
|
||||
tokenURL: config.tokenURL,
|
||||
};
|
||||
@@ -186,7 +186,7 @@ export function createOAuth2Provider(
|
||||
|
||||
envProviders[env] = new OAuthProvider(new OAuth2AuthProvider(opts), {
|
||||
disableRefresh: false,
|
||||
providerId: 'oauth2',
|
||||
providerId,
|
||||
secure,
|
||||
baseUrl,
|
||||
appOrigin,
|
||||
@@ -194,5 +194,5 @@ export function createOAuth2Provider(
|
||||
});
|
||||
}
|
||||
|
||||
return new EnvironmentHandler(envProviders);
|
||||
return new EnvironmentHandler(providerId, envProviders);
|
||||
}
|
||||
|
||||
@@ -47,17 +47,16 @@ type PrivateInfo = {
|
||||
};
|
||||
|
||||
export class OktaAuthProvider implements OAuthProviderHandlers {
|
||||
|
||||
private readonly _strategy: any;
|
||||
|
||||
/**
|
||||
/**
|
||||
* Due to passport-okta-oauth forcing options.state = true,
|
||||
* passport-oauth2 requires express-session to be installed
|
||||
* so that the 'state' parameter of the oauth2 flow can be stored.
|
||||
* This implementation of StateStore matches the NullStore found within
|
||||
* passport-oauth2, which is the StateStore implementation used when options.state = false,
|
||||
* allowing us to avoid using express-session in order to integrate with Okta.
|
||||
*/
|
||||
*/
|
||||
private _store: StateStore = {
|
||||
store(_req: express.Request, cb: any) {
|
||||
cb(null, null);
|
||||
@@ -65,25 +64,27 @@ export class OktaAuthProvider implements OAuthProviderHandlers {
|
||||
verify(_req: express.Request, _state: string, cb: any) {
|
||||
cb(null, true);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
constructor(options: OAuthProviderOptions) {
|
||||
this._strategy = new OktaStrategy({
|
||||
passReqToCallback: false as true,
|
||||
...options,
|
||||
store: this._store,
|
||||
response_type: 'code',
|
||||
}, (
|
||||
accessToken: any,
|
||||
refreshToken: any,
|
||||
params: any,
|
||||
rawProfile: passport.Profile,
|
||||
done: PassportDoneCallback<OAuthResponse, PrivateInfo>,
|
||||
) => {
|
||||
const profile = makeProfileInfo(rawProfile, params.id_token);
|
||||
this._strategy = new OktaStrategy(
|
||||
{
|
||||
passReqToCallback: false as true,
|
||||
...options,
|
||||
store: this._store,
|
||||
response_type: 'code',
|
||||
},
|
||||
(
|
||||
accessToken: any,
|
||||
refreshToken: any,
|
||||
params: any,
|
||||
rawProfile: passport.Profile,
|
||||
done: PassportDoneCallback<OAuthResponse, PrivateInfo>,
|
||||
) => {
|
||||
const profile = makeProfileInfo(rawProfile, params.id_token);
|
||||
|
||||
done(
|
||||
undefined,
|
||||
done(
|
||||
undefined,
|
||||
{
|
||||
providerInfo: {
|
||||
idToken: params.id_token,
|
||||
@@ -96,13 +97,14 @@ export class OktaAuthProvider implements OAuthProviderHandlers {
|
||||
{
|
||||
refreshToken,
|
||||
},
|
||||
)
|
||||
});
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async start(
|
||||
req: express.Request,
|
||||
options: Record<string, string>
|
||||
options: Record<string, string>,
|
||||
): Promise<RedirectInfo> {
|
||||
const providerOptions = {
|
||||
...options,
|
||||
@@ -113,7 +115,7 @@ export class OktaAuthProvider implements OAuthProviderHandlers {
|
||||
}
|
||||
|
||||
async handler(
|
||||
req: express.Request
|
||||
req: express.Request,
|
||||
): Promise<{ response: OAuthResponse; refreshToken: string }> {
|
||||
const { response, privateInfo } = await executeFrameHandlerStrategy<
|
||||
OAuthResponse,
|
||||
@@ -148,7 +150,6 @@ export class OktaAuthProvider implements OAuthProviderHandlers {
|
||||
},
|
||||
profile,
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private async populateIdentity(
|
||||
@@ -173,17 +174,17 @@ export function createOktaProvider(
|
||||
logger: Logger,
|
||||
tokenIssuer: TokenIssuer,
|
||||
) {
|
||||
const providerId = 'okta';
|
||||
const envProviders: EnvironmentHandlers = {};
|
||||
|
||||
for (const [env, envConfig] of Object.entries(providerConfig)) {
|
||||
const config = (envConfig as unknown) as OAuthProviderConfig;
|
||||
const { secure, appOrigin } = config;
|
||||
const callbackURLParam = `?env=${env}`;
|
||||
const opts = {
|
||||
audience: config.audience,
|
||||
clientID: config.clientId,
|
||||
clientSecret: config.clientSecret,
|
||||
callbackURL: `${baseUrl}/okta/handler/frame${callbackURLParam}`,
|
||||
callbackURL: `${baseUrl}/${providerId}/handler/frame?env=${env}`,
|
||||
};
|
||||
|
||||
if (!opts.clientID || !opts.clientSecret || !opts.audience) {
|
||||
@@ -201,7 +202,7 @@ export function createOktaProvider(
|
||||
|
||||
envProviders[env] = new OAuthProvider(new OktaAuthProvider(opts), {
|
||||
disableRefresh: false,
|
||||
providerId: 'okta',
|
||||
providerId,
|
||||
secure,
|
||||
baseUrl,
|
||||
appOrigin,
|
||||
@@ -209,5 +210,5 @@ export function createOktaProvider(
|
||||
});
|
||||
}
|
||||
|
||||
return new EnvironmentHandler(envProviders);
|
||||
return new EnvironmentHandler(providerId, envProviders);
|
||||
}
|
||||
|
||||
@@ -147,5 +147,5 @@ export function createSamlProvider(
|
||||
envProviders[env] = new SamlAuthProvider(opts);
|
||||
}
|
||||
|
||||
return new EnvironmentHandler(envProviders);
|
||||
return new EnvironmentHandler('saml', envProviders);
|
||||
}
|
||||
|
||||
@@ -14,13 +14,13 @@ To run it within the backend do:
|
||||
|
||||
1. Register the router in `packages/backend/src/index.ts`:
|
||||
|
||||
```
|
||||
```ts
|
||||
const proxyEnv = useHotMemoize(module, () => createEnv('proxy'));
|
||||
|
||||
const service = createServiceBuilder(module)
|
||||
.loadConfig(configReader)
|
||||
/** several different routers */
|
||||
.addRouter('/', await proxy(proxyEnv));
|
||||
.loadConfig(configReader)
|
||||
/** several different routers */
|
||||
.addRouter('/', await proxy(proxyEnv));
|
||||
```
|
||||
|
||||
2. Start the backend
|
||||
@@ -33,5 +33,4 @@ This will launch the full example backend.
|
||||
|
||||
## Links
|
||||
|
||||
- (http-proxy-middleware)[https://www.npmjs.com/package/http-proxy-middleware]
|
||||
- (The Backstage homepage)[https://backstage.io]
|
||||
- [http-proxy-middleware](https://www.npmjs.com/package/http-proxy-middleware)
|
||||
|
||||
@@ -8,6 +8,7 @@ metadata:
|
||||
- Recommended
|
||||
- React
|
||||
spec:
|
||||
owner: web@example.com
|
||||
templater: cookiecutter
|
||||
type: website
|
||||
path: '.'
|
||||
|
||||
@@ -8,6 +8,7 @@ metadata:
|
||||
- Recommended
|
||||
- Java
|
||||
spec:
|
||||
owner: service@example.com
|
||||
templater: cookiecutter
|
||||
type: service
|
||||
path: '.'
|
||||
|
||||
@@ -14,14 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Publisher } from './types';
|
||||
import { PublisherBase } from './types';
|
||||
import { Octokit } from '@octokit/rest';
|
||||
|
||||
import { JsonValue } from '@backstage/config';
|
||||
import { RequiredTemplateValues } from '../templater';
|
||||
import { Repository, Remote, Signature, Cred } from 'nodegit';
|
||||
|
||||
export class GithubPublisher implements Publisher {
|
||||
export class GithubPublisher implements PublisherBase {
|
||||
private client: Octokit;
|
||||
constructor({ client }: { client: Octokit }) {
|
||||
this.client = client;
|
||||
|
||||
@@ -21,7 +21,7 @@ import { JsonValue } from '@backstage/config';
|
||||
* Publisher is in charge of taking a folder created by
|
||||
* the templater, and pushing it to a remote storage
|
||||
*/
|
||||
export type Publisher = {
|
||||
export type PublisherBase = {
|
||||
/**
|
||||
*
|
||||
* @param opts object containing the template entity from the service
|
||||
|
||||
@@ -26,13 +26,13 @@ import {
|
||||
RequiredTemplateValues,
|
||||
StageContext,
|
||||
TemplaterBuilder,
|
||||
Publisher,
|
||||
PublisherBase,
|
||||
} from '../scaffolder';
|
||||
|
||||
export interface RouterOptions {
|
||||
preparers: PreparerBuilder;
|
||||
templaters: TemplaterBuilder;
|
||||
publisher: Publisher;
|
||||
publisher: PublisherBase;
|
||||
|
||||
logger: Logger;
|
||||
dockerClient: Docker;
|
||||
@@ -55,18 +55,6 @@ export async function createRouter(
|
||||
const jobProcessor = new JobProcessor();
|
||||
|
||||
router
|
||||
.get('/v1/job/:jobId/stage/:index/log', ({ params }, res) => {
|
||||
const job = jobProcessor.get(params.jobId);
|
||||
|
||||
if (!job) {
|
||||
res.status(404).send({ error: 'job not found' });
|
||||
return;
|
||||
}
|
||||
|
||||
const { log } = job.stages[Number(params.index)] ?? { log: [] };
|
||||
|
||||
res.send(log.join(''));
|
||||
})
|
||||
.get('/v1/job/:jobId', ({ params }, res) => {
|
||||
const job = jobProcessor.get(params.jobId);
|
||||
|
||||
@@ -126,7 +114,7 @@ export async function createRouter(
|
||||
{
|
||||
name: 'Publish template',
|
||||
handler: async (ctx: StageContext<{ resultDir: string }>) => {
|
||||
ctx.logger.info('Should not store the template');
|
||||
ctx.logger.info('Will now store the template');
|
||||
const { remoteUrl } = await publisher.publish({
|
||||
entity: ctx.entity,
|
||||
values: ctx.values,
|
||||
|
||||
@@ -23,17 +23,12 @@ import {
|
||||
Lifecycle,
|
||||
Page,
|
||||
pageTheme,
|
||||
Progress,
|
||||
SupportButton,
|
||||
useApi,
|
||||
} from '@backstage/core';
|
||||
import { catalogApiRef } from '@backstage/plugin-catalog';
|
||||
import {
|
||||
Button,
|
||||
Grid,
|
||||
LinearProgress,
|
||||
Link,
|
||||
Typography,
|
||||
} from '@material-ui/core';
|
||||
import { Button, Grid } from '@material-ui/core';
|
||||
import React, { useEffect } from 'react';
|
||||
import { Link as RouterLink } from 'react-router-dom';
|
||||
import useStaleWhileRevalidate from 'swr';
|
||||
@@ -95,14 +90,7 @@ export const ScaffolderPage: React.FC<{}> = () => {
|
||||
documentation, ...).
|
||||
</SupportButton>
|
||||
</ContentHeader>
|
||||
<Typography variant="body2" paragraph style={{ fontStyle: 'italic' }}>
|
||||
<strong>NOTE!</strong> This feature is WIP. You can follow progress{' '}
|
||||
<Link href="https://github.com/spotify/backstage/milestone/11">
|
||||
here
|
||||
</Link>
|
||||
.
|
||||
</Typography>
|
||||
{!templates && isValidating && <LinearProgress />}
|
||||
{!templates && isValidating && <Progress />}
|
||||
<Grid container>
|
||||
{templates &&
|
||||
templates.map(template => {
|
||||
|
||||
@@ -2,6 +2,13 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@babel/code-frame@7.0.0":
|
||||
version "7.0.0"
|
||||
resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz#06e2ab19bdb535385559aabb5ba59729482800f8"
|
||||
integrity sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==
|
||||
dependencies:
|
||||
"@babel/highlight" "^7.0.0"
|
||||
|
||||
"@babel/code-frame@7.5.5":
|
||||
version "7.5.5"
|
||||
resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz#bc0782f6d69f7b7d49531219699b988f669a8f9d"
|
||||
@@ -5950,7 +5957,7 @@ chalk@^3.0.0:
|
||||
ansi-styles "^4.1.0"
|
||||
supports-color "^7.1.0"
|
||||
|
||||
chalk@^4.0.0, chalk@^4.1.0:
|
||||
chalk@^4.0.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a"
|
||||
integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==
|
||||
@@ -8114,6 +8121,17 @@ eslint-config-prettier@^6.0.0:
|
||||
dependencies:
|
||||
get-stdin "^6.0.0"
|
||||
|
||||
eslint-formatter-friendly@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.npmjs.org/eslint-formatter-friendly/-/eslint-formatter-friendly-7.0.0.tgz#32a4998ababa0a39994aed629b831fda7dabc864"
|
||||
integrity sha512-WXg2D5kMHcRxIZA3ulxdevi8/BGTXu72pfOO5vXHqcAfClfIWDSlOljROjCSOCcKvilgmHz1jDWbvFCZHjMQ5w==
|
||||
dependencies:
|
||||
"@babel/code-frame" "7.0.0"
|
||||
chalk "2.4.2"
|
||||
extend "3.0.2"
|
||||
strip-ansi "5.2.0"
|
||||
text-table "0.2.0"
|
||||
|
||||
eslint-import-resolver-node@^0.3.3:
|
||||
version "0.3.3"
|
||||
resolved "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.3.tgz#dbaa52b6b2816b50bc6711af75422de808e98404"
|
||||
@@ -8574,7 +8592,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2:
|
||||
assign-symbols "^1.0.0"
|
||||
is-extendable "^1.0.1"
|
||||
|
||||
extend@^3.0.0, extend@~3.0.2:
|
||||
extend@3.0.2, extend@^3.0.0, extend@~3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
|
||||
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
|
||||
|
||||