diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml
index 6e4fdad214..158310b5ea 100644
--- a/.github/workflows/cli.yml
+++ b/.github/workflows/cli.yml
@@ -2,13 +2,9 @@ name: CLI Test
on:
pull_request:
- paths:
- - '.github/workflows/cli.yml'
- - 'packages/cli/**'
- - 'packages/create-app/**'
- - 'packages/core/**'
- - 'packages/core-api/**'
- - 'yarn.lock'
+ paths-ignore:
+ - 'microsite/**'
+
jobs:
build:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index cf7622d692..9981493868 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -60,7 +60,35 @@ Have you started using Backstage? Adding your company to [ADOPTERS](ADOPTERS.md)
# 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) 👏🏻💯
+So...feel ready to jump in? Let's do this. 👏🏻💯
+
+To run a Backstage app, you will need to have the following installed:
+
+- [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
+- [NodeJS](https://nodejs.org/en/download/) - Active LTS Release, currently v12
+- [yarn](https://classic.yarnpkg.com/en/docs/install)
+
+After cloning this repo, open a terminal window and start the example app using the following commands from the project root:
+
+```bash
+yarn install # Install dependencies
+
+yarn start # Start dev server, use --check to enable linting and type-checks
+```
+
+The final `yarn start` command should open a local instance of Backstage in your browser, otherwise open one of the URLs printed in the terminal.
+
+Depending on the work you're doing, you often also want to run the example backend. Start the backend in a separate terminal session using the following:
+
+```bash
+cd packages/backend
+
+yarn start
+
+yarn lerna run mock-data # Populate the backend with mock data
+```
+
+And that's it! You are good to go 👍
If you need help, just jump into our [Discord chatroom](https://discord.gg/MUpMjP2).
diff --git a/README.md b/README.md
index 56932a1741..fa8151cbfa 100644
--- a/README.md
+++ b/README.md
@@ -49,27 +49,33 @@ The Backstage platform consists of a number of different components:
- [**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.
-## Getting started
+## Getting Started
-To run a Backstage app, you will need to have the following installed:
+There are two different ways to get started with Backstage, either by creating a standalone app, or by cloning this repo. Which method you use depends on what you're planning to do.
-- [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
-- [NodeJS](https://nodejs.org/en/download/) - Active LTS Release, currently v12
-- [yarn](https://classic.yarnpkg.com/en/docs/install)
+Creating a standalone instance makes it simpler to customize the application for your needs whilst staying up to date with the project. You will also depend on `@backstage` packages from NPM, making the project much smaller. This is the recommended approach if you want to kick the tyres of Backstage or setup your own instance.
-After cloning this repo, open a terminal window and start the example app using the following commands from the project root:
+On the other hand, if you want to contribute plugins or to the project in general, it's easier to fork and clone this project. That will let you stay up to date with the latest changes, and gives you an easier path to make Pull Requests towards this repo.
+
+### Creating a Standalone App
+
+Backstage provides the `@backstage/create-app` package to scaffold standalone instances of Backstage. You will need to have
+[NodeJS](https://nodejs.org/en/download/) Active LTS Release installed
+(currently v12), and [yarn](https://classic.yarnpkg.com/en/docs/install). You will also need to have [Docker](https://docs.docker.com/engine/install/) installed to use some features like Software Templates and TechDocs.
+
+Using `npx` you can then run the following to create an app in a chosen subdirectory of your current working directory:
```bash
-yarn install # Install dependencies
-
-yarn start # Start dev server, use --check to enable linting and type-checks
+npx @backstage/create-app
```
-The final `yarn start` command should open a local instance of Backstage in your browser, otherwise open one of the URLs printed in the terminal.
+You will be taken through a wizard to create your app, and the output should look something like this. You can read more about this process [here](docs/getting-started/create-an-app.md)
-And that's it! You are good to go 👍
+### Contributing to Backstage
-### Next step
+You can read more in our [CONTRIBUTING.md](./CONTRIBUTING.md#get-started) guide, which can help you get setup with a Backstage development environment.
+
+### Next steps
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.
@@ -77,15 +83,10 @@ Take a look at the [Getting Started](docs/getting-started/index.md) guide to lea
- [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
-
-We would love your help in building Backstage! See [CONTRIBUTING](CONTRIBUTING.md) for more information.
-
## Community
- [Discord chatroom](https://discord.gg/MUpMjP2) - Get support or discuss the project
diff --git a/app-config.yaml b/app-config.yaml
index c5c7a3674c..54684b10a9 100644
--- a/app-config.yaml
+++ b/app-config.yaml
@@ -35,6 +35,12 @@ techdocs:
sentry:
organization: spotify
+rollbar:
+ organization: spotify
+ accountToken:
+ $secret:
+ env: ROLLBAR_ACCOUNT_TOKEN
+
newrelic:
api:
baseUrl: 'https://api.newrelic.com/v2'
@@ -62,7 +68,7 @@ auth:
clientSecret:
$secret:
env: AUTH_GITHUB_CLIENT_SECRET
- enterpriseInstanceUrl:
+ enterpriseInstanceUrl:
$secret:
env: AUTH_GITHUB_ENTERPRISE_INSTANCE_URL
gitlab:
diff --git a/docs/README.md b/docs/README.md
index cdbfbc5578..b5ec0f1307 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -27,7 +27,6 @@ better yet, a pull request.
- [Overview](features/software-catalog/index.md)
- [System model](features/software-catalog/system-model.md)
- [YAML File Format](features/software-catalog/descriptor-format.md)
- - [Populating the catalog](features/software-catalog/populating.md)
- [Extending the model](features/software-catalog/extending-the-model.md)
- [External integrations](features/software-catalog/external-integrations.md)
- [API](features/software-catalog/api.md)
diff --git a/docs/conf/reading.md b/docs/conf/reading.md
index 9979f0360e..21b5ce5737 100644
--- a/docs/conf/reading.md
+++ b/docs/conf/reading.md
@@ -98,7 +98,7 @@ A good pattern for reading optional configuration values is to use the `??`
operator. For example:
```ts
-const title = config.getString('my-plugin.title') ?? 'My Plugin';
+const title = config.getOptionalString('my-plugin.title') ?? 'My Plugin';
```
To read required configuration, simply use the methods without `Optional`, for
diff --git a/docs/features/software-catalog/bsc-edit.png b/docs/features/software-catalog/bsc-edit.png
new file mode 100644
index 0000000000..d1ac1f52e5
Binary files /dev/null and b/docs/features/software-catalog/bsc-edit.png differ
diff --git a/docs/features/software-catalog/bsc-extend.png b/docs/features/software-catalog/bsc-extend.png
new file mode 100644
index 0000000000..54d660ea70
Binary files /dev/null and b/docs/features/software-catalog/bsc-extend.png differ
diff --git a/docs/features/software-catalog/bsc-register-1.png b/docs/features/software-catalog/bsc-register-1.png
new file mode 100644
index 0000000000..117b2ea8ea
Binary files /dev/null and b/docs/features/software-catalog/bsc-register-1.png differ
diff --git a/docs/features/software-catalog/bsc-register-2.png b/docs/features/software-catalog/bsc-register-2.png
new file mode 100644
index 0000000000..fd1ea7b998
Binary files /dev/null and b/docs/features/software-catalog/bsc-register-2.png differ
diff --git a/docs/features/software-catalog/bsc-search.png b/docs/features/software-catalog/bsc-search.png
new file mode 100644
index 0000000000..8e417cb076
Binary files /dev/null and b/docs/features/software-catalog/bsc-search.png differ
diff --git a/docs/features/software-catalog/bsc-starred.png b/docs/features/software-catalog/bsc-starred.png
new file mode 100644
index 0000000000..27db19c842
Binary files /dev/null and b/docs/features/software-catalog/bsc-starred.png differ
diff --git a/docs/features/software-catalog/extending-the-model.md b/docs/features/software-catalog/extending-the-model.md
index e69de29bb2..855334fd09 100644
--- a/docs/features/software-catalog/extending-the-model.md
+++ b/docs/features/software-catalog/extending-the-model.md
@@ -0,0 +1,38 @@
+# Extending the model
+
+Backstage natively supports tracking of the following component
+[`type`](descriptor-format.md)'s:
+
+- Services
+- Websites
+- Libraries
+- Documentation
+- Other
+
+
+
+Since these types are likely not the only kind of software you will want to
+track in Backstage, it is possible to
+
+It is possible to add your own software types that fits your organization's data
+model. Inside Spotify our model has grown significantly over the years, and now
+includes ML models, Apps, data pipelines and many more.
+
+## Adding a new type
+
+TODO: Describe what changes are needed to add a new type that shows up in the
+catalog.
+
+## The Other type
+
+It might be tempting to put software that doesn't fit into any of the existing
+types into Other. There are a few reasons why we advice against this; firstly,
+we have found that it is preferred to match the conceptual model that your
+engineers have when describing your sofware. Secondly, Backstage helps your
+engineers manage their software by integrating the infratrucure tooling through
+plugins. Different plugins are used for managing different types of components.
+
+For example, the
+[Lighthouse plugin](https://github.com/spotify/backstage/tree/master/plugins/lighthouse)
+only makes sense for Websites. The more specific you can be in how you model
+your software, the easier it is to provide plugins that are contextual.
diff --git a/docs/features/software-catalog/external-integrations.md b/docs/features/software-catalog/external-integrations.md
index e69de29bb2..1f2863acf3 100644
--- a/docs/features/software-catalog/external-integrations.md
+++ b/docs/features/software-catalog/external-integrations.md
@@ -0,0 +1,8 @@
+# External integrations
+
+Backstage natively supports storing software components in
+[metadata YAML files](descriptor-format.md). However, companies that already
+have an existing system for keeping track of software and its owners can
+integrate such systems with Backstage.
+
+TODO: Describe the API contract.
diff --git a/docs/features/software-catalog/index.md b/docs/features/software-catalog/index.md
index d4d9bf1e2d..06785515f6 100644
--- a/docs/features/software-catalog/index.md
+++ b/docs/features/software-catalog/index.md
@@ -6,25 +6,109 @@ The Backstage Service Catalog — actually, a software catalog, since it include
more than just services — is a centralized system that keeps track of ownership
and metadata for all the software in your ecosystem (services, websites,
libraries, data pipelines, etc). The catalog is built around the concept of
-[metadata yaml files](../../architecture-decisions/adr002-default-catalog-file-format.md#format)
-stored together with the code, which are then harvested and visualized in
-Backstage.
+[metadata YAML files](descriptor-format.md) stored together with the code, which
+are then harvested and visualized in Backstage.

-We have also found that the service catalog is a great way to organise the
-infrastructure tools you use to manage the software as well. This is how
-Backstage creates one developer portal for all your tools. Rather than asking
-teams to jump between different infrastructure UI’s (and incurring additional
-cognitive overhead each time they make a context switch), most of these tools
-can be organised around the entities in the catalog.
+## How it works
-## Using the Service Catalog
+Backstage and the Backstage Service Catalog makes it easy for one team to manage
+10 services — and makes it possible for your company to manage thousands of
+them.
-TODO
+More specifically, the Service Catalog enables two main use-cases:
+
+1. Helping teams manage and maintain the software they own. Teams get a uniform
+ view of all their software; services, libraries, websites, ML models — you
+ name it, Backstage knows all about it.
+2. Makes all the software in your company, and who owns it, discoverable. No
+ more orphan software hiding in the dark corners of your software ecosystem.

+## Adding components to the catalog
+
+The source of truth for the components in your service catalog are
+[metadata YAML files](descriptor-format.md) stored in source control (GitHub,
+GitHub Enterprise, GitLab, ...).
+
+There are 3 ways to add components to the catalog:
+
+1. Manually register components
+2. Creating new components through Backstage
+3. Integrating with and [external source](external-integrations.md)
+
+### Manually register components
+
+Users can register new components by going to `/create` and clicking the
+**REGSITER EXISTING COMPONENT** button:
+
+
+
+Backstage expects the full URL to the YAML in your source control. Example:
+
+```
+https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/artist-lookup-component.yaml
+```
+
+_More examples can be found
+[here](https://github.com/spotify/backstage/tree/master/packages/catalog-model/examples)._
+
+
+
+It is important to note that any kind of software can be registered in
+Backstage. Even if the software is not maintained by your company (SaaS
+offering, for example) it is still useful to create components for tracking
+ownership.
+
+### Creating new components through Backstage
+
+All software created through the
+[Backstage Software Templates](../software-templates/index.md) are automatically
+registered in the catalog.
+
+### Updating component metadata
+
+Teams owning the components are responsible for maintaining the metadata about
+them, and do so using their normal Git workflow.
+
+
+
+Once the change has been merged, Backstage will automatically show the updated
+metadata in the service catalog after a short while.
+
+## Finding software in the catlog
+
+By default the service catalog shows components owned by the team of the logged
+in user. But you can also switch to _All_ to see all the components across your
+companie's software ecosystem. Basic inline _search_ and _column filtering_
+makes it easy to browse a big set of components.
+
+
+
+## Starring components
+
+For easy and quick access to components you visit frequently, Backstage supports
+_starring_ of components:
+
+
+
+## Integrated tooling through plugins
+
+The service catalog is a great way to organise the infrastructure tools you use
+to manage the software. This is how Backstage creates one developer portal for
+all your tools. Rather than asking teams to jump between different
+infrastructure UI’s (and incurring additional cognitive overhead each time they
+make a context switch), most of these tools can be organised around the entities
+in the catalog.
+
+
+
+The Backstage platform can be customized by incorporating
+[existing open source plugins](https://github.com/spotify/backstage/tree/master/plugins),
+or by [building your own](../../plugins/index.md).
+
## Links
- [[Blog post] Backstage Service Catalog released in alpha](https://backstage.io/blog/2020/06/22/backstage-service-catalog-alpha)
diff --git a/docs/features/software-catalog/populating.md b/docs/features/software-catalog/populating.md
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/docs/features/software-catalog/system-model.md b/docs/features/software-catalog/system-model.md
index e69de29bb2..9485df20d0 100644
--- a/docs/features/software-catalog/system-model.md
+++ b/docs/features/software-catalog/system-model.md
@@ -0,0 +1,96 @@
+# Backstage System Model
+
+We believe that a strong shared understanding and terminology around systems,
+software and resources leads to a better Backstage experience.
+
+_This description originates from
+[this RFC](https://github.com/spotify/backstage/issues/390). Note that some of
+the concpets are not yet supported in Backstage._
+
+## Concepts
+
+We model our technology using these five concepts (further explained below):
+
+- **Domains** are a high-level grouping of systems
+- **Systems** encapsulate the implementation of APIs
+- **APIs** are the boundaries between different components and systems
+- **Components** are pieces of software
+- **Resources** are physical or virtual infrastructure needed to operate a
+ system
+
+
+
+### Domain
+
+While systems are the basic level of encapsulation for resources, components and
+APIs, it is often useful to group a collection of systems that share
+terminology, domain models, business purpose, or documentation, i.e. they form a
+bounded context.
+
+For example, it would make sense if the different systems in the “Payments”
+domain would come with some documentation on how to accept payments for a new
+product or use-case, share the same entity types in their APIs, and integrate
+well with each other.
+
+### System
+
+With increasing complexity in software, we believe that systems form an
+important abstraction level to help us reason about software ecosystems. Systems
+are a useful concept in that they allow us to ignore the implementation details
+of a certain functionality for consumers, while allowing the owning team to make
+changes as they see fit (leading to low coupling).
+
+A system, in this sense, is a collection of resources and components that
+exposes one or several APIs. Components and resources in a system are typically
+owned by the same team and are expected to co-evolve. As such, systems usually
+consist of at most a handful of components.
+
+For example, a playlist management system might encapsulate a backend service to
+update playlists, a backend service to query them, and a database to store them.
+It could expose an RPC API, a daily snapshots dataset, and an event stream of
+playlist updates.
+
+### Component
+
+A component is a piece of software, for example a mobile feature, web site,
+backend service or data pipeline (list not exhaustive). A component can be
+tracked in source control, or use some existing open source or commercial
+software.
+
+A component can implement APIs for other components to consume. It might depend
+on the resources of the system it belongs to, and APIs from other components or
+other systems. All other aspects of the component, e.g. any code dependencies,
+must be encapsulated.
+
+### API
+
+We believe APIs form an important (maybe the most important) abstraction that
+allows large software ecosystems to scale. Thus, APIs are a first class citizen
+in the Backstage model and the primary way to discover existing functionality in
+the ecosystem.
+
+APIs are implemented by components and form boundaries between components and
+systems. They might be defined using an RPC IDL (eg Protobuf, GraphQL, ...), a
+data schema (eg Avro, TFRecord, ...), or as code interfaces. In any case, APIs
+exposed by components need to be in a known machine-readable format so we can
+build further tooling and analysis on top.
+
+Some APIs might be exposed by the system, making them available for any other
+Spotify component to consume. Those public APIs must be documented and humanly
+discoverable in Backstage.
+
+### Resource
+
+Resources are the infrastructure a system needs to operate, like BigTable
+databases, Pub/Sub topics, S3 buckets or CDNs. Modelling them together with
+components and systems will better allow us to visualize resource footprint, and
+create tooling around them.
+
+## Current status
+
+Backstage currently supports Components and APIs.
+
+## Links
+
+- [Original RFC](https://github.com/spotify/backstage/issues/390)
+- [YAML file format](../../architecture-decisions/adr002-default-catalog-file-format.md)
diff --git a/docs/features/software-templates/index.md b/docs/features/software-templates/index.md
index c7358518cd..d1631af70c 100644
--- a/docs/features/software-templates/index.md
+++ b/docs/features/software-templates/index.md
@@ -5,6 +5,10 @@ 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
diff --git a/docs/overview/what-is-backstage.md b/docs/overview/what-is-backstage.md
index 86b5417d5f..c8d659db9a 100644
--- a/docs/overview/what-is-backstage.md
+++ b/docs/overview/what-is-backstage.md
@@ -14,15 +14,15 @@ to create a streamlined development environment from end to end.
Out of the box, Backstage includes:
-- [Backstage Service Catalog](https://github.com/spotify/backstage/blob/master/docs/features/software-catalog/index.md)
- for managing all your software (microservices, libraries, data pipelines,
+- [Backstage Service Catalog](/docs/features/software-catalog/index.md) for
+ managing all your software (microservices, libraries, data pipelines,
websites, ML models, etc.)
-- [Backstage Software Templates](https://github.com/spotify/backstage/blob/master/docs/features/software-templates/index.md)
- for quickly spinning up new projects and standardizing your tooling with your
+- [Backstage Software Templates](/docs/features/software-templates/index.md) for
+ quickly spinning up new projects and standardizing your tooling with your
organization’s best practices
-- [Backstage TechDocs](https://github.com/spotify/backstage/tree/master/docs/features/techdocs)
- for making it easy to create, maintain, find, and use technical documentation,
- using a "docs like code" approach
+- [Backstage TechDocs](/docs/features/techdocs) for making it easy to create,
+ maintain, find, and use technical documentation, using a "docs like code"
+ approach
- Plus, a growing ecosystem of
[open source plugins](https://github.com/spotify/backstage/tree/master/plugins)
that further expand Backstage’s customizability and functionality
diff --git a/docs/verify-links.js b/docs/verify-links.js
index 8c865309f9..0a3b08e425 100755
--- a/docs/verify-links.js
+++ b/docs/verify-links.js
@@ -17,12 +17,21 @@
const { resolve: resolvePath, dirname } = require('path');
const fs = require('fs-extra');
-const fetch = require('node-fetch');
const recursive = require('recursive-readdir');
const projectRoot = resolvePath(__dirname, '..');
async function verifyUrl(basePath, url) {
+ // Avoid having absolute URL links within docs/, so that links work on the site
+ if (
+ url.match(
+ /https:\/\/github.com\/spotify\/backstage\/(tree|blob)\/master\/docs\//,
+ ) &&
+ basePath.match(/^(?:docs|microsite)\//)
+ ) {
+ return { url, basePath, problem: 'absolute' };
+ }
+
url = url.replace(/#.*$/, '');
url = url.replace(
/https:\/\/github.com\/spotify\/backstage\/(tree|blob)\/master/,
@@ -39,7 +48,7 @@ async function verifyUrl(basePath, url) {
: resolvePath(dirname(resolvePath(projectRoot, basePath)), url);
const exists = await fs.pathExists(path);
if (!exists) {
- return { url, basePath };
+ return { url, basePath, problem: 'missing' };
}
}
@@ -76,8 +85,14 @@ async function main() {
if (badUrls.length) {
console.log(`Found ${badUrls.length} bad links within repo`);
- for (const { url, basePath } of badUrls) {
- console.error(`Unable to reach ${url}, linked from ${basePath}`);
+ for (const { url, basePath, problem } of badUrls) {
+ if (problem === 'missing') {
+ console.error(`Unable to reach ${url}, linked from ${basePath}`);
+ } else if (problem === 'absolute') {
+ console.error(`Link to docs/ should be replaced by a relative URL`);
+ console.error(` From: ${basePath}`);
+ console.error(` To: ${url}`);
+ }
}
process.exit(1);
}
diff --git a/microsite/blog/2020-04-30-how-to-quickly-set-up-backstage.md b/microsite/blog/2020-04-30-how-to-quickly-set-up-backstage.md
index 7b1fbb2d11..65ee02f6a0 100644
--- a/microsite/blog/2020-04-30-how-to-quickly-set-up-backstage.md
+++ b/microsite/blog/2020-04-30-how-to-quickly-set-up-backstage.md
@@ -21,7 +21,7 @@ A Backstage app is a modern monorepo web project that is built using Backstage p
More specifically, a Backstage app includes the core packages and APIs that provide base functionality to the app. The actual UX is provided by plugins. As an example, when you first load the `/` page of the app, the content is provided by the `welcome` plugin.
-Plugins are the essential building blocks of Backstage and extend the platform by providing additional features and functionality. Read more about [Backstage plugins](https://github.com/spotify/backstage/tree/master/docs/getting-started) on GitHub.
+Plugins are the essential building blocks of Backstage and extend the platform by providing additional features and functionality. Read more about [Backstage plugins](/docs/getting-started) on GitHub.
## A personalized platform
@@ -50,7 +50,7 @@ yarn start
And you are good to go! 👍
-Read the full documentation on how to [create an app](https://github.com/spotify/backstage/blob/master/docs/getting-started/create-an-app.md) on GitHub.
+Read the full documentation on how to [create an app](/docs/getting-started/create-an-app.md) on GitHub.
## What do I get? (Let's get technical...)
diff --git a/microsite/blog/2020-05-22-phase-2-service-catalog.md b/microsite/blog/2020-05-22-phase-2-service-catalog.md
index 631931bb82..682965d2dd 100644
--- a/microsite/blog/2020-05-22-phase-2-service-catalog.md
+++ b/microsite/blog/2020-05-22-phase-2-service-catalog.md
@@ -27,7 +27,7 @@ Quote from [Platform Nuts & Bolts: Extendable Data Models](https://www.kislayver
Entities, or what we refer to as “components” in Backstage, represent all software, including services, websites, libraries, data pipelines, and so forth. The focus of Phase 2 will be on adding an entity model in Backstage that makes it easy for engineers to create and manage the software components they own.
-With the ability to create a plethora of components in Backstage, how does one keep track of all the software in the ecosystem? Therein lies the highlight feature of Phase 2: the [Service Catalog](https://github.com/spotify/backstage/milestone/4). The service catalog — or software catalog — is a centralized system that keeps track of ownership and metadata about all software in your ecosystem. The catalog is built around the concept of [metadata yaml files](https://github.com/spotify/backstage/blob/master/docs/architecture-decisions/adr002-default-catalog-file-format.md) stored together with the code, which are then harvested and visualized in Backstage.
+With the ability to create a plethora of components in Backstage, how does one keep track of all the software in the ecosystem? Therein lies the highlight feature of Phase 2: the [Service Catalog](https://github.com/spotify/backstage/milestone/4). The service catalog — or software catalog — is a centralized system that keeps track of ownership and metadata about all software in your ecosystem. The catalog is built around the concept of [metadata yaml files](/docs/architecture-decisions/adr002-default-catalog-file-format.md) stored together with the code, which are then harvested and visualized in Backstage.

diff --git a/microsite/blog/2020-06-22-backstage-service-catalog-alpha.md b/microsite/blog/2020-06-22-backstage-service-catalog-alpha.md
index a352faf0cb..0928b8769b 100644
--- a/microsite/blog/2020-06-22-backstage-service-catalog-alpha.md
+++ b/microsite/blog/2020-06-22-backstage-service-catalog-alpha.md
@@ -24,7 +24,7 @@ With these insights we decided to re-focus our efforts towards the most requeste
## What is the service catalog?
-The Backstage Service Catalog — actually, a software catalog, since it includes more than just services — is a centralized system that keeps track of ownership and metadata for all the software in your ecosystem (services, websites, libraries, data pipelines, etc). The catalog is built around the concept of [metadata yaml files](https://github.com/spotify/backstage/blob/master/docs/architecture-decisions/adr002-default-catalog-file-format.md#format) stored together with the code, which are then harvested and visualized in Backstage.
+The Backstage Service Catalog — actually, a software catalog, since it includes more than just services — is a centralized system that keeps track of ownership and metadata for all the software in your ecosystem (services, websites, libraries, data pipelines, etc). The catalog is built around the concept of [metadata yaml files](/docs/architecture-decisions/adr002-default-catalog-file-format.md#format) stored together with the code, which are then harvested and visualized in Backstage.
This was our pitch for the virtues of a service catalog when we first [announced](https://backstage.io/blog/2020/05/22/phase-2-service-catalog) it as part of Phase 2:
diff --git a/microsite/blog/2020-07-01-how-to-enable-authentication-in-backstage-using-passport.md b/microsite/blog/2020-07-01-how-to-enable-authentication-in-backstage-using-passport.md
index 5111f5c726..ccdd8b34c6 100644
--- a/microsite/blog/2020-07-01-how-to-enable-authentication-in-backstage-using-passport.md
+++ b/microsite/blog/2020-07-01-how-to-enable-authentication-in-backstage-using-passport.md
@@ -39,7 +39,7 @@ Getting started is really straightforward, and can be broadly broken down into f
4. Add the provider to the backend.
5. Add a frontend Auth Utility API.
-For full details, take a look at our [“Adding authentication providers” documentation](https://github.com/spotify/backstage/blob/master/docs/auth/add-auth-provider.md) and at the [excellent documentation](http://www.passportjs.org/docs/) provided by Passport.
+For full details, take a look at our [“Adding authentication providers” documentation](/docs/auth/add-auth-provider.md) and at the [excellent documentation](http://www.passportjs.org/docs/) provided by Passport.
## Interested in contributing to the next steps for authentication?
diff --git a/microsite/blog/2020-08-05-announcing-backstage-software-templates.md b/microsite/blog/2020-08-05-announcing-backstage-software-templates.md
index 79b5ec45fd..6143442782 100644
--- a/microsite/blog/2020-08-05-announcing-backstage-software-templates.md
+++ b/microsite/blog/2020-08-05-announcing-backstage-software-templates.md
@@ -35,11 +35,11 @@ Since the templates can be customized to integrate with your existing infrastruc
### Golden Paths pave the way
-You can customize Backstage Software Templates to fit your organization’s standards. Using Go instead of Java? CircleCI instead of Jenkins? Serverless instead of Kubernetes? GCP instead of AWS? [Make your own recipes for any software component](https://github.com/spotify/backstage/blob/master/docs/features/software-templates/adding-templates.md) and your best practices will be baked right in.
+You can customize Backstage Software Templates to fit your organization’s standards. Using Go instead of Java? CircleCI instead of Jenkins? Serverless instead of Kubernetes? GCP instead of AWS? [Make your own recipes for any software component](/docs/features/software-templates/adding-templates.md) and your best practices will be baked right in.
## Getting started
-The sample Software Templates are available under `/create`. If you're setting up Backstage for the first time, follow [Getting Started with Backstage](https://github.com/spotify/backstage/blob/master/docs/getting-started/index.md) and go to `http://localhost:3000/create`. If you’ve already been running Backstage locally, run the command `yarn lerna run mock-data` to load the new sample templates into the Service Catalog first.
+The sample Software Templates are available under `/create`. If you're setting up Backstage for the first time, follow [Getting Started with Backstage](/docs/getting-started/index.md) and go to `http://localhost:3000/create`. If you’ve already been running Backstage locally, run the command `yarn lerna run mock-data` to load the new sample templates into the Service Catalog first.

@@ -69,7 +69,7 @@ New components, of course, get added automatically to the Backstage Service Cata
## Define your standards
-Backstage ships with four example templates, but since these are likely not the (only) ones you want to promote inside your company, the next step is to add [your own templates](https://github.com/spotify/backstage/blob/master/docs/features/software-templates/adding-templates.md). Using Backstage’s Software Templates feature, it’s easy to help your engineers get started building software with your organization’s best practices built-in.
+Backstage ships with four example templates, but since these are likely not the (only) ones you want to promote inside your company, the next step is to add [your own templates](/docs/features/software-templates/adding-templates.md). Using Backstage’s Software Templates feature, it’s easy to help your engineers get started building software with your organization’s best practices built-in.
We have learned that one of the keys to getting these standards adopted is to keep an open process. Templates are code. By making it clear to your engineers that you are open to pull requests, and that teams with different needs can add their own templates, you are on the path of striking a good balance between autonomy and standardization.
diff --git a/mkdocs.yml b/mkdocs.yml
index 2380001203..adfdf6ae0a 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -25,7 +25,6 @@ nav:
- Overview: 'features/software-catalog/index.md'
- System model: 'features/software-catalog/system-model.md'
- YAML File Format: 'features/software-catalog/descriptor-format.md'
- - Populating the catalog: 'features/software-catalog/populating.md'
- Extending the model: 'features/software-catalog/extending-the-model.md'
- External integrations: 'features/software-catalog/external-integrations.md'
- API: 'features/software-catalog/api.md'
diff --git a/packages/backend/src/plugins/catalog.ts b/packages/backend/src/plugins/catalog.ts
index 41e4226001..fdac6dd938 100644
--- a/packages/backend/src/plugins/catalog.ts
+++ b/packages/backend/src/plugins/catalog.ts
@@ -29,8 +29,9 @@ import { useHotCleanup } from '@backstage/backend-common';
export default async function createPlugin({
logger,
database,
+ config,
}: PluginEnvironment) {
- const locationReader = new LocationReaders(logger);
+ const locationReader = new LocationReaders({ logger, config });
const db = await DatabaseManager.createDatabase(database, { logger });
const entitiesCatalog = new DatabaseEntitiesCatalog(db);
diff --git a/packages/backend/src/plugins/rollbar.ts b/packages/backend/src/plugins/rollbar.ts
index 8899251db2..c6a2c27e9b 100644
--- a/packages/backend/src/plugins/rollbar.ts
+++ b/packages/backend/src/plugins/rollbar.ts
@@ -17,6 +17,9 @@
import { createRouter } from '@backstage/plugin-rollbar-backend';
import type { PluginEnvironment } from '../types';
-export default async function createPlugin({ logger }: PluginEnvironment) {
- return await createRouter({ logger });
+export default async function createPlugin({
+ logger,
+ config,
+}: PluginEnvironment) {
+ return await createRouter({ logger, config });
}
diff --git a/packages/catalog-model/src/EntityPolicies.ts b/packages/catalog-model/src/EntityPolicies.ts
index 6bebd877d7..8cc51e5aa8 100644
--- a/packages/catalog-model/src/EntityPolicies.ts
+++ b/packages/catalog-model/src/EntityPolicies.ts
@@ -25,6 +25,7 @@ import {
import {
ApiEntityV1alpha1Policy,
ComponentEntityV1alpha1Policy,
+ GroupEntityV1alpha1Policy,
LocationEntityV1alpha1Policy,
TemplateEntityV1alpha1Policy,
} from './kinds';
@@ -75,6 +76,7 @@ export class EntityPolicies implements EntityPolicy {
]),
EntityPolicies.anyOf([
new ComponentEntityV1alpha1Policy(),
+ new GroupEntityV1alpha1Policy(),
new LocationEntityV1alpha1Policy(),
new TemplateEntityV1alpha1Policy(),
new ApiEntityV1alpha1Policy(),
diff --git a/packages/catalog-model/src/kinds/GroupEntityV1alpha1.test.ts b/packages/catalog-model/src/kinds/GroupEntityV1alpha1.test.ts
new file mode 100644
index 0000000000..94dcc2f14f
--- /dev/null
+++ b/packages/catalog-model/src/kinds/GroupEntityV1alpha1.test.ts
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { EntityPolicy } from '../types';
+import {
+ GroupEntityV1alpha1,
+ GroupEntityV1alpha1Policy,
+} from './GroupEntityV1alpha1';
+
+describe('GroupV1alpha1Policy', () => {
+ let entity: GroupEntityV1alpha1;
+ let policy: EntityPolicy;
+
+ beforeEach(() => {
+ entity = {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Group',
+ metadata: {
+ name: 'doe-squad',
+ title: 'Doe Squad',
+ description: 'A squad for John and Jane',
+ },
+ spec: {
+ type: 'squad',
+ parent: 'group-a',
+ ancestors: ['group-a', 'global-synergies', 'acme-corp'],
+ children: ['child-a', 'child-b'],
+ descendants: ['desc-a', 'desc-b'],
+ },
+ };
+ policy = new GroupEntityV1alpha1Policy();
+ });
+
+ it('happy path: accepts valid data', async () => {
+ await expect(policy.enforce(entity)).resolves.toBe(entity);
+ });
+
+ it('silently accepts v1beta1 as well', async () => {
+ (entity as any).apiVersion = 'backstage.io/v1beta1';
+ await expect(policy.enforce(entity)).resolves.toBe(entity);
+ });
+
+ it('rejects unknown apiVersion', async () => {
+ (entity as any).apiVersion = 'backstage.io/v1beta0';
+ await expect(policy.enforce(entity)).rejects.toThrow(/apiVersion/);
+ });
+
+ it('rejects unknown kind', async () => {
+ (entity as any).kind = 'Wizard';
+ await expect(policy.enforce(entity)).rejects.toThrow(/kind/);
+ });
+
+ it('rejects missing type', async () => {
+ delete (entity as any).spec.type;
+ await expect(policy.enforce(entity)).rejects.toThrow(/type/);
+ });
+
+ it('rejects wrong type', async () => {
+ (entity as any).spec.type = 7;
+ await expect(policy.enforce(entity)).rejects.toThrow(/type/);
+ });
+
+ it('rejects empty type', async () => {
+ (entity as any).spec.type = '';
+ await expect(policy.enforce(entity)).rejects.toThrow(/type/);
+ });
+
+ it('accepts missing parent', async () => {
+ delete (entity as any).spec.parent;
+ await expect(policy.enforce(entity)).resolves.toBe(entity);
+ });
+
+ it('rejects empty parent', async () => {
+ (entity as any).spec.parent = '';
+ await expect(policy.enforce(entity)).rejects.toThrow(/parent/);
+ });
+
+ it('rejects missing ancestors', async () => {
+ delete (entity as any).spec.ancestors;
+ await expect(policy.enforce(entity)).rejects.toThrow(/ancestor/);
+ });
+
+ it('accepts empty ancestors', async () => {
+ (entity as any).spec.ancestors = [''];
+ await expect(policy.enforce(entity)).resolves.toBe(entity);
+ });
+
+ it('rejects missing children', async () => {
+ delete (entity as any).spec.children;
+ await expect(policy.enforce(entity)).rejects.toThrow(/children/);
+ });
+
+ it('accepts empty children', async () => {
+ (entity as any).spec.children = [''];
+ await expect(policy.enforce(entity)).resolves.toBe(entity);
+ });
+
+ it('rejects missing descendants', async () => {
+ delete (entity as any).spec.descendants;
+ await expect(policy.enforce(entity)).rejects.toThrow(/descendants/);
+ });
+
+ it('accepts empty descendants', async () => {
+ (entity as any).spec.descendants = [''];
+ await expect(policy.enforce(entity)).resolves.toBe(entity);
+ });
+});
diff --git a/packages/catalog-model/src/kinds/GroupEntityV1alpha1.ts b/packages/catalog-model/src/kinds/GroupEntityV1alpha1.ts
new file mode 100644
index 0000000000..c1d39d5b44
--- /dev/null
+++ b/packages/catalog-model/src/kinds/GroupEntityV1alpha1.ts
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import * as yup from 'yup';
+import type { Entity } from '../entity/Entity';
+import type { EntityPolicy } from '../types';
+
+const API_VERSION = ['backstage.io/v1alpha1', 'backstage.io/v1beta1'] as const;
+const KIND = 'Group' as const;
+
+export interface GroupEntityV1alpha1 extends Entity {
+ apiVersion: typeof API_VERSION[number];
+ kind: typeof KIND;
+ spec: {
+ type: string;
+ parent?: string;
+ ancestors: string[];
+ children: string[];
+ descendants: string[];
+ };
+}
+
+export class GroupEntityV1alpha1Policy implements EntityPolicy {
+ private schema: yup.Schema;
+
+ constructor() {
+ this.schema = yup.object>({
+ apiVersion: yup.string().required().oneOf(API_VERSION),
+ kind: yup.string().required().equals([KIND]),
+ spec: yup
+ .object({
+ type: yup.string().required().min(1),
+ parent: yup.string().notRequired().min(1),
+ ancestors: yup.array(yup.string()).required(),
+ children: yup.array(yup.string()).required(),
+ descendants: yup.array(yup.string()).required(),
+ })
+ .required(),
+ });
+ }
+
+ async enforce(envelope: Entity): Promise {
+ return await this.schema.validate(envelope, { strict: true });
+ }
+}
diff --git a/packages/catalog-model/src/kinds/index.ts b/packages/catalog-model/src/kinds/index.ts
index 2ecddcbf85..6e8d27d204 100644
--- a/packages/catalog-model/src/kinds/index.ts
+++ b/packages/catalog-model/src/kinds/index.ts
@@ -19,6 +19,11 @@ export type {
ComponentEntityV1alpha1 as ComponentEntity,
ComponentEntityV1alpha1,
} from './ComponentEntityV1alpha1';
+export { GroupEntityV1alpha1Policy } from './GroupEntityV1alpha1';
+export type {
+ GroupEntityV1alpha1 as GroupEntity,
+ GroupEntityV1alpha1,
+} from './GroupEntityV1alpha1';
export { LocationEntityV1alpha1Policy } from './LocationEntityV1alpha1';
export type {
LocationEntityV1alpha1 as LocationEntity,
diff --git a/packages/cli/e2e-test/helpers.js b/packages/cli/e2e-test/helpers.js
index 84807119c3..580f53cbe8 100644
--- a/packages/cli/e2e-test/helpers.js
+++ b/packages/cli/e2e-test/helpers.js
@@ -60,6 +60,9 @@ async function runPlain(cmd, options) {
});
return stdout.trim();
} catch (error) {
+ if (error.stdout) {
+ process.stdout.write(error.stdout);
+ }
if (error.stderr) {
process.stderr.write(error.stderr);
}
diff --git a/packages/create-app/templates/default-app/packages/backend/src/plugins/catalog.ts b/packages/create-app/templates/default-app/packages/backend/src/plugins/catalog.ts
index 9df4570963..aa0538a6d8 100644
--- a/packages/create-app/templates/default-app/packages/backend/src/plugins/catalog.ts
+++ b/packages/create-app/templates/default-app/packages/backend/src/plugins/catalog.ts
@@ -12,9 +12,10 @@ import { useHotCleanup } from '@backstage/backend-common';
export default async function createPlugin({
logger,
+ config,
database,
}: PluginEnvironment) {
- const locationReader = new LocationReaders(logger);
+ const locationReader = new LocationReaders({ logger, config });
const db = await DatabaseManager.createDatabase(database, { logger });
const entitiesCatalog = new DatabaseEntitiesCatalog(db);
diff --git a/plugins/catalog-backend/migrations/20200809202832_add_bootstrap_location.js b/plugins/catalog-backend/migrations/20200809202832_add_bootstrap_location.js
new file mode 100644
index 0000000000..379928493d
--- /dev/null
+++ b/plugins/catalog-backend/migrations/20200809202832_add_bootstrap_location.js
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// @ts-check
+
+/**
+ * @param {import('knex')} knex
+ */
+exports.up = async function up(knex) {
+ // Adds a single 'bootstrap' location that can be used to trigger work in processors.
+ // This is primarily here to fulfill foreign key constraints.
+ await knex('locations').insert({
+ id: require('uuid').v4(),
+ type: 'bootstrap',
+ target: 'bootstrap',
+ });
+};
+
+/**
+ * @param {import('knex')} knex
+ */
+exports.down = async function down(knex) {
+ await knex('locations')
+ .where({
+ type: 'bootstrap',
+ target: 'bootstrap',
+ })
+ .del();
+};
diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json
index 0fd55126d8..24e342882f 100644
--- a/plugins/catalog-backend/package.json
+++ b/plugins/catalog-backend/package.json
@@ -24,6 +24,7 @@
"dependencies": {
"@backstage/backend-common": "^0.1.1-alpha.18",
"@backstage/catalog-model": "^0.1.1-alpha.18",
+ "@backstage/config": "^0.1.1-alpha.18",
"@types/express": "^4.17.6",
"express": "^4.17.1",
"express-promise-router": "^3.0.3",
diff --git a/plugins/catalog-backend/src/catalog/DatabaseLocationsCatalog.test.ts b/plugins/catalog-backend/src/catalog/DatabaseLocationsCatalog.test.ts
index 679e5b43c1..958e864a8e 100644
--- a/plugins/catalog-backend/src/catalog/DatabaseLocationsCatalog.test.ts
+++ b/plugins/catalog-backend/src/catalog/DatabaseLocationsCatalog.test.ts
@@ -17,6 +17,12 @@
import { DatabaseManager } from '../database';
import { DatabaseLocationsCatalog } from './DatabaseLocationsCatalog';
+const bootstrapLocation = {
+ id: expect.any(String),
+ type: 'bootstrap',
+ target: 'bootstrap',
+};
+
describe('DatabaseLocationsCatalog', () => {
let catalog: DatabaseLocationsCatalog;
@@ -35,9 +41,12 @@ describe('DatabaseLocationsCatalog', () => {
await expect(
catalog.location('dd12620d-0436-422f-93bd-929aa0788123'),
).resolves.toEqual(expect.objectContaining({ data: location }));
- await expect(catalog.locations()).resolves.toEqual([
- expect.objectContaining({ data: location }),
- ]);
+ await expect(catalog.locations()).resolves.toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({ data: location }),
+ expect.objectContaining({ data: bootstrapLocation }),
+ ]),
+ );
});
it('does not return duplicates of rows because of logs', async () => {
@@ -60,11 +69,12 @@ describe('DatabaseLocationsCatalog', () => {
catalog.logUpdateSuccess(location1.id),
).resolves.toBeUndefined();
const locations = await catalog.locations();
- expect(locations.length).toBe(2);
+ expect(locations.length).toBe(3);
expect(locations).toEqual(
expect.arrayContaining([
expect.objectContaining({ data: location1 }),
expect.objectContaining({ data: location2 }),
+ expect.objectContaining({ data: bootstrapLocation }),
]),
);
});
diff --git a/plugins/catalog-backend/src/database/CommonDatabase.test.ts b/plugins/catalog-backend/src/database/CommonDatabase.test.ts
index 21dde4ca74..b86b4ef959 100644
--- a/plugins/catalog-backend/src/database/CommonDatabase.test.ts
+++ b/plugins/catalog-backend/src/database/CommonDatabase.test.ts
@@ -24,6 +24,15 @@ import type {
DbLocationsRowWithStatus,
} from './types';
+const bootstrapLocation = {
+ id: expect.any(String),
+ type: 'bootstrap',
+ target: 'bootstrap',
+ message: null,
+ status: null,
+ timestamp: null,
+};
+
describe('CommonDatabase', () => {
let db: Database;
let entityRequest: DbEntityRequest;
@@ -85,8 +94,12 @@ describe('CommonDatabase', () => {
await db.addLocation(input);
const locations = await db.locations();
- expect(locations).toEqual([output]);
- const location = await db.location(locations[0].id);
+ expect(locations).toEqual(
+ expect.arrayContaining([output, bootstrapLocation]),
+ );
+ const location = await db.location(
+ locations.find(l => l.type !== 'bootstrap')!.id,
+ );
expect(location).toEqual(output);
// If we add 2 new update log events,
@@ -105,20 +118,21 @@ describe('CommonDatabase', () => {
DatabaseLocationUpdateLogStatus.FAIL,
);
- expect(await db.locations()).toEqual([
- {
- ...output,
- status: DatabaseLocationUpdateLogStatus.FAIL,
- timestamp: expect.any(String),
- },
- ]);
-
- await db.transaction(tx => db.removeLocation(tx, locations[0].id));
-
- await expect(db.locations()).resolves.toEqual([]);
- await expect(db.location(locations[0].id)).rejects.toThrow(
- /Found no location/,
+ await expect(db.locations()).resolves.toEqual(
+ expect.arrayContaining([
+ bootstrapLocation,
+ {
+ ...output,
+ status: DatabaseLocationUpdateLogStatus.FAIL,
+ timestamp: expect.any(String),
+ },
+ ]),
);
+
+ await db.transaction(tx => db.removeLocation(tx, location.id));
+
+ await expect(db.locations()).resolves.toEqual([bootstrapLocation]);
+ await expect(db.location(location.id)).rejects.toThrow(/Found no location/);
});
describe('addEntity', () => {
diff --git a/plugins/catalog-backend/src/ingestion/LocationReaders.ts b/plugins/catalog-backend/src/ingestion/LocationReaders.ts
index e5ef410456..7e91af7c59 100644
--- a/plugins/catalog-backend/src/ingestion/LocationReaders.ts
+++ b/plugins/catalog-backend/src/ingestion/LocationReaders.ts
@@ -15,6 +15,7 @@
*/
import { getVoidLogger } from '@backstage/backend-common';
+import { Config, ConfigReader } from '@backstage/config';
import {
Entity,
EntityPolicies,
@@ -31,6 +32,7 @@ import { GitlabApiReaderProcessor } from './processors/GitlabApiReaderProcessor'
import { GitlabReaderProcessor } from './processors/GitlabReaderProcessor';
import { UrlReaderProcessor } from './processors/UrlReaderProcessor';
import { LocationRefProcessor } from './processors/LocationEntityProcessor';
+import { StaticLocationProcessor } from './processors/StaticLocationProcessor';
import * as result from './processors/results';
import {
LocationProcessor,
@@ -47,6 +49,12 @@ import { LocationReader, ReadLocationResult } from './types';
// The max amount of nesting depth of generated work items
const MAX_DEPTH = 10;
+type Options = {
+ logger?: Logger;
+ config?: Config;
+ processors?: LocationProcessor[];
+};
+
/**
* Implements the reading of a location through a series of processor tasks.
*/
@@ -54,10 +62,16 @@ export class LocationReaders implements LocationReader {
private readonly logger: Logger;
private readonly processors: LocationProcessor[];
- static defaultProcessors(
- entityPolicy: EntityPolicy = new EntityPolicies(),
- ): LocationProcessor[] {
+ static defaultProcessors(options: {
+ config?: Config;
+ entityPolicy?: EntityPolicy;
+ }): LocationProcessor[] {
+ const {
+ config = new ConfigReader({}, 'missing-config'),
+ entityPolicy = new EntityPolicies(),
+ } = options;
return [
+ StaticLocationProcessor.fromConfig(config),
new FileReaderProcessor(),
new GithubReaderProcessor(),
new GithubApiReaderProcessor(),
@@ -71,10 +85,11 @@ export class LocationReaders implements LocationReader {
];
}
- constructor(
- logger: Logger = getVoidLogger(),
- processors: LocationProcessor[] = LocationReaders.defaultProcessors(),
- ) {
+ constructor({
+ logger = getVoidLogger(),
+ config,
+ processors = LocationReaders.defaultProcessors({ config }),
+ }: Options) {
this.logger = logger;
this.processors = processors;
}
diff --git a/plugins/catalog-backend/src/ingestion/processors/StaticLocationProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/StaticLocationProcessor.ts
new file mode 100644
index 0000000000..6a2d1096cc
--- /dev/null
+++ b/plugins/catalog-backend/src/ingestion/processors/StaticLocationProcessor.ts
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { LocationSpec } from '@backstage/catalog-model';
+import * as result from './results';
+import { Config } from '@backstage/config';
+import { LocationProcessorEmit } from './types';
+
+export class StaticLocationProcessor implements StaticLocationProcessor {
+ static fromConfig(config: Config): StaticLocationProcessor {
+ const locations: LocationSpec[] = [];
+
+ const lConfigs = config.getOptionalConfigArray('catalog.locations') ?? [];
+ for (const lConfig of lConfigs) {
+ const type = lConfig.getString('type');
+ const target = lConfig.getString('target');
+ locations.push({ type, target });
+ }
+
+ return new StaticLocationProcessor(locations);
+ }
+
+ constructor(private readonly staticLocations: LocationSpec[]) {}
+
+ async readLocation(
+ location: LocationSpec,
+ _optional: boolean,
+ emit: LocationProcessorEmit,
+ ): Promise {
+ if (location.type !== 'bootstrap') {
+ return false;
+ }
+
+ for (const staticLocation of this.staticLocations) {
+ emit(result.location(staticLocation, false));
+ }
+
+ return true;
+ }
+}
diff --git a/plugins/catalog-backend/src/service/standaloneServer.ts b/plugins/catalog-backend/src/service/standaloneServer.ts
index 5c7ccb23f5..0b532d1664 100644
--- a/plugins/catalog-backend/src/service/standaloneServer.ts
+++ b/plugins/catalog-backend/src/service/standaloneServer.ts
@@ -14,7 +14,11 @@
* limitations under the License.
*/
-import { createServiceBuilder } from '@backstage/backend-common';
+import {
+ createServiceBuilder,
+ loadBackendConfig,
+} from '@backstage/backend-common';
+import { ConfigReader } from '@backstage/config';
import { Server } from 'http';
import { Logger } from 'winston';
import { HigherOrderOperations } from '..';
@@ -34,12 +38,13 @@ export async function startStandaloneServer(
options: ServerOptions,
): Promise {
const logger = options.logger.child({ service: 'catalog-backend' });
+ const config = ConfigReader.fromConfigs(await loadBackendConfig());
logger.debug('Creating application...');
const db = await DatabaseManager.createInMemoryDatabase({ logger });
const entitiesCatalog = new DatabaseEntitiesCatalog(db);
const locationsCatalog = new DatabaseLocationsCatalog(db);
- const locationReader = new LocationReaders();
+ const locationReader = new LocationReaders({ logger, config });
const higherOrderOperation = new HigherOrderOperations(
entitiesCatalog,
locationsCatalog,
diff --git a/plugins/register-component/README.md b/plugins/register-component/README.md
index 729a38955c..d324b3941f 100644
--- a/plugins/register-component/README.md
+++ b/plugins/register-component/README.md
@@ -1,5 +1,92 @@
-# [WIP] register-component
+# Register component plugin
Welcome to the register-component plugin!
This plugin allows you to submit your Backstage component using your software's YAML config.
+
+When installed it is accessible on [localhost:3000/register-component](localhost:3000/register-component).
+
+
+
+
+
+
+
+## Standalone setup
+
+0. Install plugin and its dependency `plugin-catalog`
+
+```bash
+yarn add @backstage/plugin-register-component -W
+yarn add @backstage/plugin-catalog -W
+```
+
+1. Add dependencies to the active plugins list
+
+```typescript
+// packages/app/src/plugins.ts
+export { plugin as RegisterComponent } from '@backstage/plugin-register-component';
+export { plugin as CatalogPlugin } from '@backstage/plugin-catalog';
+```
+
+2. Create `packages/app/src/apis.ts` and register all the needed plugins
+
+```typescript
+import {
+ alertApiRef,
+ AlertApiForwarder,
+ ApiRegistry,
+ ConfigApi,
+ errorApiRef,
+ ErrorApiForwarder,
+ ErrorAlerter,
+} from '@backstage/core';
+
+import { catalogApiRef, CatalogClient } from '@backstage/plugin-catalog';
+
+export const apis = (config: ConfigApi) => {
+ const backendUrl = config.getString('backend.baseUrl');
+
+ const builder = ApiRegistry.builder();
+
+ const alertApi = builder.add(alertApiRef, new AlertApiForwarder());
+ const errorApi = builder.add(
+ errorApiRef,
+ new ErrorAlerter(alertApi, new ErrorApiForwarder()),
+ );
+
+ builder.add(
+ catalogApiRef,
+ new CatalogClient({
+ apiOrigin: backendUrl,
+ basePath: '/catalog',
+ }),
+ );
+
+ return builder.build();
+};
+```
+
+3. Pass `apis` to createApp
+
+```typescript
+// packages/app/src/App.tsx
+import { apis } from './apis';
+
+const app = createApp({
+ apis,
+ plugins: Object.values(plugins),
+});
+```
+
+## Running
+
+Just run the backstage.
+
+```
+yarn start && yarn --cwd packages/backend start
+```
+
+## Usage
+
+Pretty straightforward, navigate to [localhost:3000/register-component](localhost:3000/register-component) and enter your component's YAML config URL.
diff --git a/plugins/register-component/src/assets/screenshot-1.png b/plugins/register-component/src/assets/screenshot-1.png
new file mode 100644
index 0000000000..ffaebbb2f5
Binary files /dev/null and b/plugins/register-component/src/assets/screenshot-1.png differ
diff --git a/plugins/register-component/src/assets/screenshot-2.png b/plugins/register-component/src/assets/screenshot-2.png
new file mode 100644
index 0000000000..e4e5dc579c
Binary files /dev/null and b/plugins/register-component/src/assets/screenshot-2.png differ
diff --git a/plugins/register-component/src/assets/screenshot-3.png b/plugins/register-component/src/assets/screenshot-3.png
new file mode 100644
index 0000000000..4bbac74d82
Binary files /dev/null and b/plugins/register-component/src/assets/screenshot-3.png differ
diff --git a/plugins/register-component/src/assets/screenshot-4.png b/plugins/register-component/src/assets/screenshot-4.png
new file mode 100644
index 0000000000..f45f333135
Binary files /dev/null and b/plugins/register-component/src/assets/screenshot-4.png differ
diff --git a/plugins/register-component/src/assets/screenshot-5.png b/plugins/register-component/src/assets/screenshot-5.png
new file mode 100644
index 0000000000..a50decd748
Binary files /dev/null and b/plugins/register-component/src/assets/screenshot-5.png differ
diff --git a/plugins/rollbar-backend/README.md b/plugins/rollbar-backend/README.md
index aacd0e9b6d..8cd08abed3 100644
--- a/plugins/rollbar-backend/README.md
+++ b/plugins/rollbar-backend/README.md
@@ -4,7 +4,18 @@ Simple plugin that proxies requests to the [Rollbar](https://rollbar.com) API.
## Setup
-A `ROLLBAR_ACCOUNT_TOKEN` environment variable must be set to a read access account token.
+The following values are read from the configuration file.
+
+```yaml
+rollbar:
+ organization: spotify
+ accountToken:
+ $secret:
+ env: ROLLBAR_ACCOUNT_TOKEN
+```
+
+_NOTE: The `ROLLBAR_ACCOUNT_TOKEN` environment variable must be set to a read
+access account token._
## Links
diff --git a/plugins/rollbar-backend/package.json b/plugins/rollbar-backend/package.json
index 102e233756..4b4a45b9dd 100644
--- a/plugins/rollbar-backend/package.json
+++ b/plugins/rollbar-backend/package.json
@@ -21,6 +21,7 @@
},
"dependencies": {
"@backstage/backend-common": "^0.1.1-alpha.18",
+ "@backstage/config": "^0.1.1-alpha.18",
"@types/express": "^4.17.6",
"axios": "^0.19.2",
"camelcase-keys": "^6.2.2",
diff --git a/plugins/rollbar-backend/src/service/router.test.ts b/plugins/rollbar-backend/src/service/router.test.ts
index 9ec0dbc7b5..f5dc2f1c3d 100644
--- a/plugins/rollbar-backend/src/service/router.test.ts
+++ b/plugins/rollbar-backend/src/service/router.test.ts
@@ -15,6 +15,7 @@
*/
import { getVoidLogger } from '@backstage/backend-common';
+import { ConfigReader } from '@backstage/config';
import express from 'express';
import request from 'supertest';
import { RollbarApi } from '../api';
@@ -37,6 +38,9 @@ describe('createRouter', () => {
const router = await createRouter({
rollbarApi,
logger: getVoidLogger(),
+ config: ConfigReader.fromConfigs([
+ { context: 'abc', data: { rollbar: { accountToken: 'foo' } } },
+ ]),
});
app = express().use(router);
});
diff --git a/plugins/rollbar-backend/src/service/router.ts b/plugins/rollbar-backend/src/service/router.ts
index 09491b0d00..0a8c58a9d7 100644
--- a/plugins/rollbar-backend/src/service/router.ts
+++ b/plugins/rollbar-backend/src/service/router.ts
@@ -14,15 +14,17 @@
* limitations under the License.
*/
-import { errorHandler } from '@backstage/backend-common';
-import { Logger } from 'winston';
-import Router from 'express-promise-router';
import express from 'express';
+import Router from 'express-promise-router';
+import { Logger } from 'winston';
+import { errorHandler } from '@backstage/backend-common';
+import { Config } from '@backstage/config';
import { RollbarApi } from '../api';
export interface RouterOptions {
rollbarApi?: RollbarApi;
logger: Logger;
+ config: Config;
}
export async function createRouter(
@@ -30,7 +32,10 @@ export async function createRouter(
): Promise {
const router = Router();
const logger = options.logger.child({ plugin: 'rollbar' });
- const accessToken = !options.rollbarApi ? getRollbarAccountToken(logger) : '';
+ const config = options.config.getConfig('rollbar');
+ const accessToken = !options.rollbarApi
+ ? getRollbarAccountToken(config, logger)
+ : '';
if (options.rollbarApi || accessToken) {
const rollbarApi =
@@ -82,17 +87,20 @@ export async function createRouter(
return router;
}
-function getRollbarAccountToken(logger: Logger) {
- const token = process.env.ROLLBAR_ACCOUNT_TOKEN || '';
+function getRollbarAccountToken(config: Config, logger: Logger) {
+ const token =
+ config.getOptionalString('accountToken') ||
+ process.env.ROLLBAR_ACCOUNT_TOKEN ||
+ '';
if (!token) {
if (process.env.NODE_ENV !== 'development') {
throw new Error(
- 'Rollbar token must be provided in ROLLBAR_ACCOUNT_TOKEN environment variable to start the API.',
+ 'The rollbar.accountToken must be provided in config to start the API.',
);
}
logger.warn(
- 'Failed to initialize rollbar backend, set ROLLBAR_ACCOUNT_TOKEN environment variable to start the API.',
+ 'Failed to initialize rollbar backend, set rollbar.accountToken in config to start the API.',
);
}
diff --git a/plugins/rollbar-backend/src/service/standaloneServer.ts b/plugins/rollbar-backend/src/service/standaloneServer.ts
index ecd5c072fb..a96d30d836 100644
--- a/plugins/rollbar-backend/src/service/standaloneServer.ts
+++ b/plugins/rollbar-backend/src/service/standaloneServer.ts
@@ -14,9 +14,13 @@
* limitations under the License.
*/
-import { createServiceBuilder } from '@backstage/backend-common';
import { Server } from 'http';
import { Logger } from 'winston';
+import {
+ createServiceBuilder,
+ loadBackendConfig,
+} from '@backstage/backend-common';
+import { ConfigReader } from '@backstage/config';
import { createRouter } from './router';
export interface ServerOptions {
@@ -24,14 +28,16 @@ export interface ServerOptions {
enableCors: boolean;
logger: Logger;
}
+
export async function startStandaloneServer(
options: ServerOptions,
): Promise {
const logger = options.logger.child({ service: 'rollbar-backend' });
+ const config = ConfigReader.fromConfigs(await loadBackendConfig());
logger.debug('Creating application...');
- const router = await createRouter({ logger });
+ const router = await createRouter({ logger, config });
const service = createServiceBuilder(module)
.enableCors({ origin: 'http://localhost:3000' })
diff --git a/plugins/rollbar/src/components/RollbarLayout/RollbarLayout.tsx b/plugins/rollbar/src/components/RollbarLayout/RollbarLayout.tsx
index 7bda63ea5f..c0524fabc5 100644
--- a/plugins/rollbar/src/components/RollbarLayout/RollbarLayout.tsx
+++ b/plugins/rollbar/src/components/RollbarLayout/RollbarLayout.tsx
@@ -17,6 +17,7 @@
import React, { ReactNode } from 'react';
import {
Header,
+ HeaderLabel,
Page,
pageTheme,
Content,
@@ -36,7 +37,10 @@ export const RollbarLayout = ({ title = 'Dashboard', children }: Props) => {
+ >
+
+
+
diff --git a/plugins/rollbar/src/components/RollbarPage/RollbarPage.test.tsx b/plugins/rollbar/src/components/RollbarPage/RollbarPage.test.tsx
index 2b0268500d..11263ed820 100644
--- a/plugins/rollbar/src/components/RollbarPage/RollbarPage.test.tsx
+++ b/plugins/rollbar/src/components/RollbarPage/RollbarPage.test.tsx
@@ -15,7 +15,12 @@
*/
import * as React from 'react';
-import { ApiProvider, ApiRegistry } from '@backstage/core';
+import {
+ ApiProvider,
+ ApiRegistry,
+ ConfigApi,
+ configApiRef,
+} from '@backstage/core';
import { wrapInTestApp } from '@backstage/test-utils';
import { render } from '@testing-library/react';
import { RollbarApi, rollbarApiRef } from '../../api/RollbarApi';
@@ -30,11 +35,20 @@ describe('RollbarPage component', () => {
const rollbarApi: Partial = {
getAllProjects: () => Promise.resolve(projects),
};
+ const config: Partial = {
+ getString: () => 'foo',
+ getOptionalString: () => 'foo',
+ };
const renderWrapped = (children: React.ReactNode) =>
render(
wrapInTestApp(
-
+
{children}
,
),
diff --git a/plugins/rollbar/src/components/RollbarPage/RollbarPage.tsx b/plugins/rollbar/src/components/RollbarPage/RollbarPage.tsx
index ec07c0fa48..0460b4a308 100644
--- a/plugins/rollbar/src/components/RollbarPage/RollbarPage.tsx
+++ b/plugins/rollbar/src/components/RollbarPage/RollbarPage.tsx
@@ -16,18 +16,27 @@
import React from 'react';
import { useAsync } from 'react-use';
-import { useApi } from '@backstage/core';
+import { configApiRef, useApi } from '@backstage/core';
import { rollbarApiRef } from '../../api/RollbarApi';
import { RollbarLayout } from '../RollbarLayout/RollbarLayout';
import { RollbarProjectTable } from './RollbarProjectTable';
export const RollbarPage = () => {
+ const configApi = useApi(configApiRef);
const rollbarApi = useApi(rollbarApiRef);
- const { value, loading } = useAsync(() => rollbarApi.getAllProjects());
+ const org =
+ configApi.getOptionalString('rollbar.organization') ??
+ configApi.getString('organization.name');
+ const { value, loading, error } = useAsync(() => rollbarApi.getAllProjects());
return (
-
+
);
};
diff --git a/plugins/rollbar/src/components/RollbarPage/RollbarProjectTable.tsx b/plugins/rollbar/src/components/RollbarPage/RollbarProjectTable.tsx
index bb8299b57e..fdf90c468e 100644
--- a/plugins/rollbar/src/components/RollbarPage/RollbarProjectTable.tsx
+++ b/plugins/rollbar/src/components/RollbarPage/RollbarProjectTable.tsx
@@ -17,9 +17,14 @@
import React from 'react';
import { Link as RouterLink } from 'react-router-dom';
import { Link } from '@material-ui/core';
+import { Alert } from '@material-ui/lab';
+import OpenInNewIcon from '@material-ui/icons/OpenInNew';
import { Table, TableColumn } from '@backstage/core';
import { RollbarProject } from '../../api/types';
+const projectUrl = (org: string, id: number) =>
+ `https://rollbar.com/${org}/all/items/?projects=${id}`;
+
const columns: TableColumn[] = [
{
title: 'ID',
@@ -32,7 +37,6 @@ const columns: TableColumn[] = [
title: 'Name',
field: 'name',
type: 'string',
- align: 'left',
highlight: true,
render: (row: Partial) => (
@@ -44,29 +48,58 @@ const columns: TableColumn[] = [
title: 'Status',
field: 'status',
type: 'string',
- align: 'left',
+ },
+ {
+ title: 'Open',
+ width: '10%',
+ render: (row: any) => (
+
+
+
+ ),
},
];
type Props = {
projects: RollbarProject[];
loading: boolean;
+ organization: string;
+ error?: any;
};
-export const RollbarProjectTable = ({ projects, loading }: Props) => {
+export const RollbarProjectTable = ({
+ projects,
+ organization,
+ loading,
+ error,
+}: Props) => {
+ if (error) {
+ return (
+