Merge branch 'github' into timurista

This commit is contained in:
Tim Urista
2020-07-16 13:51:06 -07:00
54 changed files with 1138 additions and 163 deletions
+8 -7
View File
@@ -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. |
+4
View File
@@ -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) 👏🏻💯
+8 -10
View File
@@ -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 ❤️
+8 -4
View File
@@ -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
+8 -6
View File
@@ -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
View File
@@ -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.
+2
View File
@@ -21,4 +21,6 @@ can be organised around the entities in the catalog.
## Using the Service Catalog
TODO
![](service-catalog-home.png)
@@ -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
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

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)
+59
View File
@@ -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:
![Create Image](./assets/create.png)
### 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.
![Enter some variables](./assets/template-picked.png)
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`.
![Enter backstage vars](./assets/template-picked-2.png)
### 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.
![Templating Running](./assets/running.png)
It shouldn't take too long, and you'll have a success screen!
![Templating Complete](./assets/complete.png)
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.
![Templating failed](./assets/failed.png)
### 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:
![Catalog](./assets/go-to-catalog.png)
And then you'll also be able to see it in the Catalog View table
![Catalog](./assets/added-to-the-catalog-list.png)
+108
View File
@@ -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. Its based on the
developer portal weve 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)
+1 -1
View File
@@ -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
View File
+11
View File
@@ -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
+3 -4
View File
@@ -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.
![plugin](my-plugin_screenshot.png)
## 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
+4 -3
View File
@@ -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
+1 -1
View File
@@ -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)
+8 -4
View File
@@ -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:
+33 -6
View File
@@ -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();
+1
View File
@@ -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",
+3 -3
View File
@@ -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');
+10 -3
View File
@@ -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'),
+2 -2
View File
@@ -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.
![plugin](../docs/getting-started/my-plugin_screenshot.png)
![plugin](../docs/plugins/my-plugin_screenshot.png)
## 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);
}
+5 -6
View File
@@ -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 => {
+20 -2
View File
@@ -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==