merge master to samiram/fix-banner-pos

This commit is contained in:
Samira Mokaram
2020-09-25 10:17:23 +02:00
145 changed files with 3695 additions and 517 deletions
+2
View File
@@ -13,6 +13,8 @@ flags:
core:
paths:
- packages/core/
carryforward: true
core-api:
paths:
- packages/core-api/
carryforward: true
+4
View File
@@ -12,6 +12,10 @@ If you encounter issues while upgrading to a newer version, don't hesitate to re
- Renamed `SessionStateApi` to `SessionApi` and `logout` to `signOut`. Custom implementations of the `SingInPage` app-component will need to rename their `logout` function. The different auth provider items for the `UserSettingsMenu` have been consolidated into a single `ProviderSettingsItem`, meaning you need to replace existing usages of `OAuthProviderSettings` and `OIDCProviderSettings`. [#2555](https://github.com/spotify/backstage/pull/2555).
### @backstage/auth-backend
- The default mount path of backend plugins was changed to `/api/:pluginId`, and as part of that it was needed to enable configuration of the base path of the auth backend, so that it can construct redirect URLs correctly. The default base path is `/api/auth`, but you need to set this to `/auth` if you want to keep using the old path. Note that you will also need to reconfigure any allowed redirect URLs to include `/api` if you switch to the new recommended pattern. [#2562](https://github.com/spotify/backstage/pull/2562)
## v0.1.1-alpha.22
### @backstage/core
+4
View File
@@ -32,6 +32,10 @@ If you start developing a plugin that you aim to release as open source, we sugg
You can also use this process if you have an idea for a good plugin but you hope that someone else will pick up the work.
## Adding Non-code Contributions
Since there is such a large landscape of possible development, build, and deployment environments, we welcome community contributions in these areas in the [`/contrib`](https://github.com/spotify/backstage/tree/master/contrib) folder of the project. This is an excellent place to put things that help out the community at large, but which may not fit within the scope of the core product to support natively. Here, you will find Helm charts, alternative Docker images, and much more.
## Write Documentation
The current documentation is very limited. Help us make the `/docs` folder come alive.
+3 -3
View File
@@ -26,6 +26,8 @@ Out of the box, Backstage includes:
For more information go to [backstage.io](https://backstage.io) or join our [Discord chatroom](https://discord.gg/EBHEGzX).
🎉 Backstage is a CNCF Sandbox project. Read the announcement [here](https://backstage.io/blog/2020/09/23/backstage-cncf-sandbox).
## Project roadmap
A detailed project roadmap, including already delivered milestones, is available [here](https://backstage.io/docs/overview/roadmap).
@@ -51,11 +53,9 @@ Check out [the documentation](https://backstage.io/docs/getting-started) on how
- [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)
- [Newsletter](https://mailchi.mp/spotify/backstage-community) - Subscribe to our email newsletter
- Give us a star ⭐️ - If you are using Backstage or think it is an interesting project, we would love a star ❤️
Or, if you are an open source developer and are interested in joining our team, please reach out to [foss-opportunities@spotify.com ](mailto:foss-opportunities@spotify.com)
## License
Copyright 2020 Spotify AB.
+6 -2
View File
@@ -33,8 +33,8 @@ organization:
name: Spotify
techdocs:
storageUrl: http://localhost:7000/techdocs/static/docs
requestUrl: http://localhost:7000/techdocs/docs
storageUrl: http://localhost:7000/api/techdocs/static/docs
requestUrl: http://localhost:7000/api/techdocs/docs
generators:
techdocs: 'docker'
@@ -55,6 +55,10 @@ newrelic:
lighthouse:
baseUrl: http://localhost:3003
kubernetes:
clusterLocatorMethod: 'configMultiTenant'
clusters: []
catalog:
rules:
- allow: [Component, API, Group, Template, Location]
@@ -7,7 +7,7 @@ description: Architecture Decision Record (ADR) log on Default Catalog File Name
## Background
While the spec for the catalog file format is well described in
[ADR002](./adr002-default-catalog-file-format.md), guidance was note provided as
[ADR002](./adr002-default-catalog-file-format.md), guidance was not provided as
to the name of the catalog file.
Following discussion in
@@ -23,4 +23,4 @@ catalog-info.yaml
```
This name is a default, **not a requirement**. The catalog file will work with
Backstage irregardless of its name.
Backstage regardless of its name.
@@ -0,0 +1,69 @@
---
id: adrs-adr009
title: ADR009: Entity References
description: Architecture Decision Record (ADR) log on Entity References
---
## Background
While the spec for the catalog file format is well described in
[ADR002](./adr002-default-catalog-file-format.md), guidance was not provided as
to how one is expected to express references to other entities in the catalog.
There was also some confusion on how to reference entities in URLs in the
Backstage frontend.
Following discussion in
[Issue 1947](https://github.com/spotify/backstage/issues/1947), a decision was
made.
## Entity References in YAML files
The textual format, as written by humans, to reference entities by name is on
the following form, where square brackets denote optionality:
```
[<kind>:][<namespace>/]<name>
```
That is, it is composed of between one and three parts in this specific order,
without any additional encoding, with those exact separator characters.
Optionality of `kind` and `namespace` are contextual, and they may or may not
have default contextual fallback values.
When that format is insufficient or when machine made interchange formats wish
to express such relations in a more expressive form, a nested structure on the
following form can be used:
```yaml
kind: <kind>
namespace: <namespace>
name: <name>
```
Of these, only `name` is always required. Optionality of `kind` and `namespace`
are contextual, and they may or may not have default contextual fallback values.
All other possible key values in this structure are reserved for future use.
A system or user wanting to express a full entity name that is always valid,
shall supply the entire triplet whether using the string form or the compound
form.
A full description of the format can be found
[in the documentation](https://backstage.io/docs/features/software-catalog/references).
## Entity References in URLs
Where entities are referenced by name in the Backstage frontend, the URL
containing the reference shall take the following form:
```
:namespace/:kind/:name
```
All three parts are required under all circumstances. The default value for the
`namespace` in the catalog is the string `"default"`, if the entity does not
specify one explicitly in `metadata.namespace`.
This means that we do not encourage the string form of entity references to be
used as a single URL segment, due to the use of URL-unsafe characters leading to
possible risk, confusion, and uglier URLs.
+4 -3
View File
@@ -229,9 +229,10 @@ name.
### Test the new provider
You can `curl -i localhost:7000/auth/providerA/start` and which should provide a
`302` redirect with a `Location` header. Paste the url from that header into a
web browser and you should be able to trigger the authorization flow.
You can `curl -i localhost:7000/api/auth/providerA/start` and which should
provide a `302` redirect with a `Location` header. Paste the url from that
header into a web browser and you should be able to trigger the authorization
flow.
---
@@ -22,6 +22,8 @@ humans. However, the structure and semantics is the same in both cases.
- [Kind: Component](#kind-component)
- [Kind: Template](#kind-template)
- [Kind: API](#kind-api)
- [Kind: Group](#kind-group)
- [Kind: User](#kind-user)
## Overall Shape Of An Entity
@@ -571,3 +573,167 @@ group of people in an organizational structure.
The definition of the API, based on the format defined by `spec.type`. This
field is required.
## Kind: Group
Describes the following entity kind:
| Field | Value |
| ------------ | ----------------------- |
| `apiVersion` | `backstage.io/v1alpha1` |
| `kind` | `Group` |
A group describes an organizational entity, such as for example a team, a
business unit, or a loose collection of people in an interest group. Members of
these groups are modeled in the catalog as kind [`User`](#kind-user).
Descriptor files for this kind may look as follows.
```yaml
apiVersion: backstage.io/v1alpha1
kind: Group
metadata:
name: infrastructure
description: The infra business unit
spec:
type: business-unit
parent: ops
ancestors: [ops, global-synergies, acme-corp]
children: [backstage, other]
descendants: [backstage, other, team-a, team-b, team-c, team-d]
```
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 `Group`, respectively.
### `spec.type` [required]
The type of group as a string, e.g. `team`. There is currently no enforced set
of values for this field, so it is left up to the adopting organization to
choose a nomenclature that matches their org hierarchy.
Some common values for this field could be:
- `team`
- `business-unit`
- `product-area`
- `root` - as a common virtual root of the hierarchy, if desired
### `spec.parent` [optional]
The immediate parent group in the hierarchy, if any. Not all groups must have a
parent; the catalog supports multi-root hierarchies. Groups may however not have
more than one parent.
This field is an
[entity reference](https://backstage.io/docs/features/software-catalog/references),
with the default kind `Group` and the default namespace equal to the same
namespace as the user. Only `Group` entities may be referenced. Most commonly,
this field points to a group in the same namespace, so in those cases it is
sufficient to enter only the `metadata.name` field of that group.
### `spec.ancestors` [required]
The recursive list of parents up the hierarchy, by stepping through parents one
by one. The list must be present, but may be empty if `parent` is not present.
The first entry in the list is equal to `parent`, and then the following ones
are progressively farther up the hierarchy.
The entries of this array are
[entity references](https://backstage.io/docs/features/software-catalog/references),
with the default kind `Group` and the default namespace equal to the same
namespace as the user. Only `Group` entities may be referenced. Most commonly,
these entries point to groups in the same namespace, so in those cases it is
sufficient to enter only the `metadata.name` field of those groups.
### `spec.children` [required]
The immediate child groups of this group in the hierarchy (whose `parent` field
points to this group). The list must be present, but may be empty if there are
no child groups. The items are not guaranteed to be ordered in any particular
way.
The entries of this array are
[entity references](https://backstage.io/docs/features/software-catalog/references),
with the default kind `Group` and the default namespace equal to the same
namespace as the user. Only `Group` entities may be referenced. Most commonly,
these entries point to groups in the same namespace, so in those cases it is
sufficient to enter only the `metadata.name` field of those groups.
### `spec.descendants` [required]
The immediate and recursive child groups of this group in the hierarchy
(children, and children's children, etc.). The list must be present, but may be
empty if there are no child groups. The items are not guaranteed to be ordered
in any particular way.
The entries of this array are
[entity references](https://backstage.io/docs/features/software-catalog/references),
with the default kind `Group` and the default namespace equal to the same
namespace as the user. Only `Group` entities may be referenced. Most commonly,
these entries point to groups in the same namespace, so in those cases it is
sufficient to enter only the `metadata.name` field of those groups.
## Kind: User
Describes the following entity kind:
| Field | Value |
| ------------ | ----------------------- |
| `apiVersion` | `backstage.io/v1alpha1` |
| `kind` | `User` |
A user describes a person, such as an employee, a contractor, or similar. Users
belong to [`Group`](#kind-group) entities in the catalog.
These catalog user entries are connected to the way that authentication within
the Backstage ecosystem works. See the [auth](https://backstage.io/docs/auth)
section of the docs for a discussion of these concepts.
Descriptor files for this kind may look as follows.
```yaml
apiVersion: backstage.io/v1alpha1
kind: User
metadata:
name: jdoe
spec:
profile:
displayName: Jenny Doe
email: jenny-doe@example.com
picture: https://example.com/staff/jenny-with-party-hat.jpeg
memberOf: [team-b, employees]
```
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 `User`, respectively.
### `spec.profile` [optional]
Optional profile information about the user, mainly for display purposes. All
fields of this structure are also optional. The email would be a primary email
of some form, that the user may wish to be used for contacting them. The picture
is expected to be a URL pointing to an image that's representative of the user,
and that a browser could fetch and render on a profile page or similar.
### `spec.memberOf` [required]
The list of groups that the user is a direct member of (i.e., no transitive
memberships are listed here). The list must be present, but may be empty if the
user is not member of any groups. The items are not guaranteed to be ordered in
any particular way.
The entries of this array are
[entity references](https://backstage.io/docs/features/software-catalog/references),
with the default kind `Group` and the default namespace equal to the same
namespace as the user. Only `Group` entities may be referenced. Most commonly,
these entries point to groups in the same namespace, so in those cases it is
sufficient to enter only the `metadata.name` field of those groups.
@@ -0,0 +1,104 @@
---
id: references
title: Entity References
description: How to express references between entities
---
Entities commonly have a need to reference other entities. For example, a
[Component](descriptor-format.md#kind-component) entity may want to declare who
its owner is by mentioning a Group or User entity, and a User entity may want to
declare what Group entities it is a member of. This article describes how to
write those references in your yaml entity declaration files.
Each entity in the catalog is uniquely identified by the triplet of its
[kind](descriptor-format.md#apiversion-and-kind-required),
[namespace](descriptor-format.md#namespace-optional), and
[name](descriptor-format.md#name-required). But that's a lot to type out
manually, and in a lot of circumstances, both the kind and the namespace are
fixed, or possible to deduce, or could have sane default values. So in order to
help the writer, the catalog has a few tricks up its sleeve.
Each reference can be expressed in one of two ways: as a compact string, or as a
compound reference structure.
## String References
This is the most common alternative, that should be used in almost all
circumstances.
The string is on the form `[<kind>:][<namespace>/]<name>`, that is, it is
composed of between one and three parts in this specific order, without any
additional encoding:
- Optionally, the kind, followed by a colon
- Optionally, the namespace, followed by a forward slash
- The name
The name is always required. Depending on the context, you may be able to leave
out the kind and/or namespace. If you do, it is contextual what values will be
used, and the relevant documentation should specify which rule applies where.
All strings are case insensitive.
```yaml
# Example:
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: petstore
namespace: external-systems
description: Petstore
spec:
type: service
lifecycle: experimental
owner: group:pet-managers
implementsApis:
- petstore
- internal/streetlights
- hello-world
```
The field `spec.owner` is a reference. In this case, the string
`group:pet-managers` was given by the user. That means that the kind is `Group`,
the namespace is left out, and the name is `pet-managers`. In this context, the
namespace was chosen to fall back to the value `default` by the code that parsed
the reference, so the end result is that we expect to find another entity in the
catalog that is of kind `Group`, namespace `default` (which, actually, also can
be left out in its own yaml file because that's the default value there too),
and name `pet-managers`.
The entries in `implementsApis` are also references. In this case, none of them
need to specify a kind since we know from the context that that's the only kind
that's supported here. The second entry specifies a namespace but the other ones
don't, and in this context, the default is to refer to the same namespace as the
originating entity (`external-systems` here). So the three references
essentially expand to `api:external-systems/petstore`,
`api:internal/streetlights`, and `api:external-systems/hello-world`. We expect
there to exist three API kind entities in the catalog matching those references.
## Compound References
This is a more verbose version of a reference, where each part of the
kind-namespace-name triplet is expressed as a field in a structure. This format
can be used where necessary, such as if either of the three elements contain
colons or forward slashes. Avoid using it where possible, since it is harder to
read and write for humans.
```yaml
# Example:
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: petstore
description: Petstore
spec:
type: service
lifecycle: experimental
owner:
kind: Group
name: aegis-imports/pet-managers
```
In this example, the `spec.owner` has been broken apart since the name was
complex. The kind happened to be written with an uppercase letter G, which also
works. The namespace was left out just like in the string version above, which
is handled identically.
@@ -88,7 +88,7 @@ running:
```sh
curl \
--location \
--request POST 'localhost:7000/catalog/locations' \
--request POST 'localhost:7000/api/catalog/locations' \
--header 'Content-Type: application/json' \
--data-raw "{\"type\": \"file\", \"target\": \"${YOUR PATH HERE}/template.yaml\"}"
```
@@ -98,7 +98,7 @@ If loading from a Git location, you can run the following
```sh
curl \
--location \
--request POST 'localhost:7000/catalog/locations' \
--request POST 'localhost:7000/api/catalog/locations' \
--header 'Content-Type: application/json' \
--data-raw "{\"type\": \"github\", \"target\": \"https://${YOUR GITHUB REPO}blob/master/${PATH TO FOLDER}/template.yaml\"}"
```
+2 -2
View File
@@ -57,8 +57,8 @@ The default storage and request URLs:
```yaml
techdocs:
storageUrl: http://localhost:7000/techdocs/static/docs
requestUrl: http://localhost:7000/techdocs/docs
storageUrl: http://localhost:7000/api/techdocs/static/docs
requestUrl: http://localhost:7000/api/techdocs/docs
```
If you want `techdocs-backend` to manage building and publishing, you want
+10
View File
@@ -0,0 +1,10 @@
---
id: troubleshooting
title: Troubleshooting TechDocs
sidebar_label: Troubleshooting
description: Troubleshooting for TechDocs
---
- TechDocs will fail to clone your docs if you have a git config which overrides
the `https` protocol with `ssh` or something else. Make sure to remove your
git config locally when you try TechDocs.
+3 -3
View File
@@ -6,10 +6,10 @@ info:
**Provided by `@backstage/auth-backend`.**
The purpose of the Auth APIs in Backstage are to identify the user, and to provide a way for plugins
The purpose of the Auth APIs in Backstage are to identify the user, and to provide a way for plugins
to request access to 3rd party services on behalf of that user.
The API is supplied with a list of providers - such as `Google` or `Github` - and will add the endpoints
The API is supplied with a list of providers - such as `Google` or `Github` - and will add the endpoints
described below to each of those providers.
Read more about [User Authentication and Authorization in Backstage](https://github.com/spotify/backstage/blob/master/docs/auth/overview.md).
@@ -21,7 +21,7 @@ externalDocs:
description: Backstage official documentation
url: https://github.com/spotify/backstage/blob/master/docs/README.md
servers:
- url: http://localhost:7000/auth/
- url: http://localhost:7000/api/auth/
tags:
- name: provider
description: List of endpoints per provider
+17
View File
@@ -4,6 +4,23 @@ title: Architecture overview
description: Documentation on Architecture overview
---
## Terminology
Backstage is constructed out of three parts. We separate Backstage in this way
because we see three groups of contributors that work with Backstage in three
different ways.
- Core - Base functionality built by core developers in the open source project.
- App - The app is an instance of a Backstage app that is deployed and tweaked.
The app ties together core functionality with additional plugins. The app is
built and maintained by app developers, usually a productivity team within a
company.
- Plugins - Additional functionality to make your Backstage app useful for your
company. Plugins can be specific to a company or open sourced and reusable. At
Spotify we have over 100 plugins built by over 50 different teams. It has been
very powerful to get contributions from various infrastructure teams added
into a single unified developer experience.
## Overview
The following diagram shows how Backstage might look when deployed inside a
-20
View File
@@ -1,20 +0,0 @@
---
id: architecture-terminology
title: Architecture terminology
description: Documentation on Architecture terminology
---
Backstage is constructed out of three parts. We separate Backstage in this way
because we see three groups of contributors that work with Backstage in three
different ways.
- Core - Base functionality built by core devs in the open source project.
- App - The app is an instance of a Backstage app that is deployed and tweaked.
The app ties together core functionality with additional plugins. The app is
built and maintained by app developers, usually a productivity team within a
company.
- Plugins - Additional functionality to make your Backstage app useful for your
company. Plugins can be specific to a company or open sourced and reusable. At
Spotify we have over 100 plugins built by over 50 different teams. It has been
very powerful to get contributions from various infrastructure teams added
into a single unified developer experience.
+8 -1
View File
@@ -1,7 +1,8 @@
---
id: background
title: The Spotify Story
description: Documentation on Background and Story behind making of Backstage
description: Backstage was born out of necessity at Spotify. We found that as we grew, our
infrastructure was becoming more fragmented, our engineers less productive.
---
Backstage was born out of necessity at Spotify. We found that as we grew, our
@@ -29,3 +30,9 @@ building a new microservice using an automated template in Backstage. Create,
maintain, and find the documentation for all that software in Backstage.
One place for everything. Accessible to everyone.
Backstage was originally built by Spotify and then donated to the CNCF.
Backstage is currently in the Sandbox phase. Read the announcement
[here](https://backstage.io/blog/2020/09/23/backstage-cncf-sandbox).
<img src="https://backstage.io/img/cncf-white.svg" width="400" />
+2 -5
View File
@@ -13,10 +13,7 @@ description: Support and Community Details and Links
- [FAQ](../FAQ.md) - Frequently Asked Questions
- [Code of Conduct](../../CODE_OF_CONDUCT.md) - This is how we roll
- [Blog](https://backstage.io/blog/) - Announcements and updates
- [Newsletter](https://mailchi.mp/spotify/backstage-community)
- [Newsletter](https://mailchi.mp/spotify/backstage-community) - Subscribe to
our email newsletter
- Give us a star ⭐️ - If you are using Backstage or think it is an interesting
project, we would love a star ❤️
Or, if you are an open source developer and are interested in joining our team,
please reach out to
[foss-opportunities@spotify.com ](mailto:foss-opportunities@spotify.com)
+9 -2
View File
@@ -1,7 +1,7 @@
---
id: what-is-backstage
title: What is Backstage?
description: Backsatge is an open platform for building developer portals.
description: Backstage is an open platform for building developer portals.
Powered by a centralized service catalog, Backstage restores order to your microservices and infrastructure
---
@@ -33,7 +33,14 @@ Out of the box, Backstage includes:
[open source plugins](https://github.com/spotify/backstage/tree/master/plugins)
that further expand Backstages customizability and functionality
### Benefits
## Backstage and the CNCF
Backstage is a CNCF Sandbox project. Read the announcement
[here](https://backstage.io/blog/2020/09/23/backstage-cncf-sandbox).
<img src="https://backstage.io/img/cncf-white.svg" width="400" />
## Benefits
- For _engineering managers_, it allows you to maintain standards and best
practices across the organization, and can help you manage your whole tech
+1 -1
View File
@@ -115,7 +115,7 @@ export AUTH_GITHUB_CLIENT_SECRET=xxx
> Log into http://github.com
> Navigate to (Settings > Developer Settings > OAuth Apps > New OAuth App)[https://github.com/settings/applications/new]
> Set Homepage URL = http://localhost:3000
> Set Callback URL = http://localhost:7000/auth/github
> Set Callback URL = http://localhost:7000/api/auth/github
> Click [Register application]
> On the next page, copy and paste your new Client ID and Client Secret to the environment variables above, `AUTH_GITHUB_CLIENT_ID` & `AUTH_GITHUB_CLIENT_SECRET`
> Don't forget to `source` that profile file again if necessary.
+1 -1
View File
@@ -2,5 +2,5 @@
"packages": ["packages/*", "plugins/*"],
"npmClient": "yarn",
"useWorkspaces": true,
"version": "0.1.1-alpha.22"
"version": "0.1.1-alpha.23"
}
+1
View File
@@ -51,6 +51,7 @@ class Footer extends React.Component {
<a href="https://mailchi.mp/spotify/backstage-community">
Subscribe to our newsletter
</a>
<a href="https://www.cncf.io/sandbox-projects/">CNCF Sandbox</a>
</div>
<div>
<h5>More</h5>
+4 -2
View File
@@ -3,7 +3,6 @@
"Overview": [
"overview/what-is-backstage",
"overview/architecture-overview",
"overview/architecture-terminology",
"overview/roadmap",
"overview/vision",
"overview/background",
@@ -43,6 +42,7 @@
"features/software-catalog/configuration",
"features/software-catalog/system-model",
"features/software-catalog/descriptor-format",
"features/software-catalog/references",
"features/software-catalog/well-known-annotations",
"features/software-catalog/extending-the-model",
"features/software-catalog/external-integrations",
@@ -71,6 +71,7 @@
"features/techdocs/concepts",
"features/techdocs/architecture",
"features/techdocs/creating-and-publishing",
"features/techdocs/troubleshooting",
"features/techdocs/faqs"
]
}
@@ -158,7 +159,8 @@
"architecture-decisions/adrs-adr005",
"architecture-decisions/adrs-adr006",
"architecture-decisions/adrs-adr007",
"architecture-decisions/adrs-adr008"
"architecture-decisions/adrs-adr008",
"architecture-decisions/adrs-adr009"
],
"Contribute": ["../CONTRIBUTING"],
"Support": ["overview/support"],
+3 -1
View File
@@ -5,7 +5,6 @@ nav:
- Overview:
- What is Backstage?: 'overview/what-is-backstage.md'
- Backstage architecture: 'overview/architecture-overview.md'
- Architecture and terminology: 'overview/architecture-terminology.md'
- Roadmap: 'overview/roadmap.md'
- Vision: 'overview/vision.md'
- The Spotify story: 'overview/background.md'
@@ -32,6 +31,7 @@ nav:
- Configuration: 'features/software-catalog/configuration.md'
- System model: 'features/software-catalog/system-model.md'
- YAML File Format: 'features/software-catalog/descriptor-format.md'
- Entity References: 'features/software-catalog/references.md'
- Well-known Annotations: 'features/software-catalog/well-known-annotations.md'
- Extending the model: 'features/software-catalog/extending-the-model.md'
- External integrations: 'features/software-catalog/external-integrations.md'
@@ -51,6 +51,7 @@ nav:
- Concepts: 'features/techdocs/concepts.md'
- TechDocs Architecture: 'features/techdocs/architecture.md'
- Creating and Publishing Documentation: 'features/techdocs/creating-and-publishing.md'
- Troubleshooting: 'features/techdocs/troubleshooting.md'
- FAQ: 'features/techdocs/FAQ.md'
- Plugins:
- Overview: 'plugins/index.md'
@@ -104,6 +105,7 @@ nav:
- ADR006 - Avoid React.FC and React.SFC: 'architecture-decisions/adr006-avoid-react-fc.md'
- ADR007 - Use MSW for Network Request Mocking: 'architecture-decisions/adr007-use-msw-to-mock-service-requests.md'
- ADR008 - Default Catalog File Name: 'architecture-decisions/adr008-default-catalog-file-name.md'
- ADR009 - Entity References: 'architecture-decisions/adr009-entity-references.md'
- Contribute: '../CONTRIBUTING.md'
- Support: 'overview/support.md'
- FAQ: FAQ.md
+25 -25
View File
@@ -1,33 +1,33 @@
{
"name": "example-app",
"version": "0.1.1-alpha.22",
"version": "0.1.1-alpha.23",
"private": true,
"bundled": true,
"dependencies": {
"@backstage/catalog-model": "^0.1.1-alpha.22",
"@backstage/cli": "^0.1.1-alpha.22",
"@backstage/core": "^0.1.1-alpha.22",
"@backstage/plugin-api-docs": "^0.1.1-alpha.22",
"@backstage/plugin-catalog": "^0.1.1-alpha.22",
"@backstage/plugin-circleci": "^0.1.1-alpha.22",
"@backstage/plugin-explore": "^0.1.1-alpha.22",
"@backstage/plugin-gcp-projects": "^0.1.1-alpha.22",
"@backstage/plugin-github-actions": "^0.1.1-alpha.22",
"@backstage/plugin-gitops-profiles": "^0.1.1-alpha.22",
"@backstage/plugin-graphiql": "^0.1.1-alpha.22",
"@backstage/plugin-jenkins": "^0.1.1-alpha.22",
"@backstage/plugin-kubernetes": "^0.1.1-alpha.22",
"@backstage/plugin-lighthouse": "^0.1.1-alpha.22",
"@backstage/plugin-newrelic": "^0.1.1-alpha.22",
"@backstage/plugin-register-component": "^0.1.1-alpha.22",
"@backstage/plugin-rollbar": "^0.1.1-alpha.22",
"@backstage/plugin-scaffolder": "^0.1.1-alpha.22",
"@backstage/plugin-sentry": "^0.1.1-alpha.22",
"@backstage/plugin-tech-radar": "^0.1.1-alpha.22",
"@backstage/plugin-techdocs": "^0.1.1-alpha.22",
"@backstage/plugin-welcome": "^0.1.1-alpha.22",
"@backstage/test-utils": "^0.1.1-alpha.22",
"@backstage/theme": "^0.1.1-alpha.22",
"@backstage/catalog-model": "^0.1.1-alpha.23",
"@backstage/cli": "^0.1.1-alpha.23",
"@backstage/core": "^0.1.1-alpha.23",
"@backstage/plugin-api-docs": "^0.1.1-alpha.23",
"@backstage/plugin-catalog": "^0.1.1-alpha.23",
"@backstage/plugin-circleci": "^0.1.1-alpha.23",
"@backstage/plugin-explore": "^0.1.1-alpha.23",
"@backstage/plugin-gcp-projects": "^0.1.1-alpha.23",
"@backstage/plugin-github-actions": "^0.1.1-alpha.23",
"@backstage/plugin-gitops-profiles": "^0.1.1-alpha.23",
"@backstage/plugin-graphiql": "^0.1.1-alpha.23",
"@backstage/plugin-jenkins": "^0.1.1-alpha.23",
"@backstage/plugin-kubernetes": "^0.1.1-alpha.23",
"@backstage/plugin-lighthouse": "^0.1.1-alpha.23",
"@backstage/plugin-newrelic": "^0.1.1-alpha.23",
"@backstage/plugin-register-component": "^0.1.1-alpha.23",
"@backstage/plugin-rollbar": "^0.1.1-alpha.23",
"@backstage/plugin-scaffolder": "^0.1.1-alpha.23",
"@backstage/plugin-sentry": "^0.1.1-alpha.23",
"@backstage/plugin-tech-radar": "^0.1.1-alpha.23",
"@backstage/plugin-techdocs": "^0.1.1-alpha.23",
"@backstage/plugin-welcome": "^0.1.1-alpha.23",
"@backstage/test-utils": "^0.1.1-alpha.23",
"@backstage/theme": "^0.1.1-alpha.23",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@octokit/rest": "^18.0.0",
+1 -1
View File
@@ -31,7 +31,7 @@ describe('App', () => {
baseUrl: 'http://localhost:3003',
},
techdocs: {
storageUrl: 'http://localhost:7000/techdocs/static/docs',
storageUrl: 'http://localhost:7000/api/techdocs/static/docs',
},
},
context: 'test',
-12
View File
@@ -16,11 +16,8 @@
import {
errorApiRef,
discoveryApiRef,
UrlPatternDiscovery,
githubAuthApiRef,
createApiFactory,
configApiRef,
} from '@backstage/core';
import {
@@ -38,15 +35,6 @@ import {
} from '@roadiehq/backstage-plugin-github-pull-requests';
export const apis = [
// TODO(Rugvip): migrate to use /api
createApiFactory({
api: discoveryApiRef,
deps: { configApi: configApiRef },
factory: ({ configApi }) =>
UrlPatternDiscovery.compile(
`${configApi.getString('backend.baseUrl')}/{{ pluginId }}`,
),
}),
createApiFactory({
api: graphQlBrowseApiRef,
deps: { errorApi: errorApiRef, githubAuthApi: githubAuthApiRef },
+5 -5
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/backend-common",
"description": "Common functionality library for Backstage backends",
"version": "0.1.1-alpha.22",
"version": "0.1.1-alpha.23",
"main": "src/index.ts",
"types": "src/index.ts",
"private": false,
@@ -29,9 +29,9 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/cli-common": "^0.1.1-alpha.22",
"@backstage/config": "^0.1.1-alpha.22",
"@backstage/config-loader": "^0.1.1-alpha.22",
"@backstage/cli-common": "^0.1.1-alpha.23",
"@backstage/config": "^0.1.1-alpha.23",
"@backstage/config-loader": "^0.1.1-alpha.23",
"@types/cors": "^2.8.6",
"@types/express": "^4.17.6",
"compression": "^1.7.4",
@@ -58,7 +58,7 @@
}
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.22",
"@backstage/cli": "^0.1.1-alpha.23",
"@types/compression": "^1.7.0",
"@types/http-errors": "^1.6.3",
"@types/morgan": "^1.9.0",
+1 -1
View File
@@ -23,7 +23,7 @@ import { loadConfig } from '@backstage/config-loader';
export async function loadBackendConfig() {
const paths = findPaths(__dirname);
const configs = await loadConfig({
env: process.env.NODE_ENV,
env: process.env.NODE_ENV ?? 'development',
rootPaths: [paths.targetRoot, paths.targetDir],
shouldReadSecrets: true,
});
+18 -17
View File
@@ -1,6 +1,6 @@
{
"name": "example-backend",
"version": "0.1.1-alpha.22",
"version": "0.1.1-alpha.23",
"main": "dist/index.cjs.js",
"types": "src/index.ts",
"private": true,
@@ -18,25 +18,26 @@
"migrate:create": "knex migrate:make -x ts"
},
"dependencies": {
"@backstage/backend-common": "^0.1.1-alpha.22",
"@backstage/catalog-model": "^0.1.1-alpha.22",
"@backstage/config": "^0.1.1-alpha.22",
"@backstage/plugin-app-backend": "^0.1.1-alpha.22",
"@backstage/plugin-auth-backend": "^0.1.1-alpha.22",
"@backstage/plugin-catalog-backend": "^0.1.1-alpha.22",
"@backstage/plugin-graphql-backend": "^0.1.1-alpha.22",
"@backstage/plugin-identity-backend": "^0.1.1-alpha.22",
"@backstage/plugin-kubernetes-backend": "^0.1.1-alpha.22",
"@backstage/plugin-proxy-backend": "^0.1.1-alpha.22",
"@backstage/plugin-rollbar-backend": "^0.1.1-alpha.22",
"@backstage/plugin-scaffolder-backend": "^0.1.1-alpha.22",
"@backstage/plugin-sentry-backend": "^0.1.1-alpha.22",
"@backstage/plugin-techdocs-backend": "^0.1.1-alpha.22",
"@backstage/backend-common": "^0.1.1-alpha.23",
"@backstage/catalog-model": "^0.1.1-alpha.23",
"@backstage/config": "^0.1.1-alpha.23",
"@backstage/plugin-app-backend": "^0.1.1-alpha.23",
"@backstage/plugin-auth-backend": "^0.1.1-alpha.23",
"@backstage/plugin-catalog-backend": "^0.1.1-alpha.23",
"@backstage/plugin-graphql-backend": "^0.1.1-alpha.23",
"@backstage/plugin-identity-backend": "^0.1.1-alpha.23",
"@backstage/plugin-kubernetes-backend": "^0.1.1-alpha.23",
"@backstage/plugin-proxy-backend": "^0.1.1-alpha.23",
"@backstage/plugin-rollbar-backend": "^0.1.1-alpha.23",
"@backstage/plugin-scaffolder-backend": "^0.1.1-alpha.23",
"@backstage/plugin-sentry-backend": "^0.1.1-alpha.23",
"@backstage/plugin-techdocs-backend": "^0.1.1-alpha.23",
"@gitbeaker/node": "^23.5.0",
"@octokit/rest": "^18.0.0",
"dockerode": "^3.2.0",
"example-app": "^0.1.1-alpha.22",
"example-app": "^0.1.1-alpha.23",
"express": "^4.17.1",
"express-promise-router": "^3.0.3",
"knex": "^0.21.1",
"pg": "^8.3.0",
"pg-connection-string": "^2.3.0",
@@ -44,7 +45,7 @@
"winston": "^3.2.1"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.22",
"@backstage/cli": "^0.1.1-alpha.23",
"@types/dockerode": "^2.5.32",
"@types/express": "^4.17.6",
"@types/express-serve-static-core": "^4.17.5",
+16 -10
View File
@@ -22,12 +22,14 @@
* Happy hacking!
*/
import Router from 'express-promise-router';
import {
createDatabaseClient,
createServiceBuilder,
loadBackendConfig,
getRootLogger,
useHotMemoize,
notFoundHandler,
} from '@backstage/backend-common';
import { ConfigReader, AppConfig } from '@backstage/config';
import healthcheck from './plugins/healthcheck';
@@ -78,19 +80,23 @@ async function main() {
const graphqlEnv = useHotMemoize(module, () => createEnv('graphql'));
const appEnv = useHotMemoize(module, () => createEnv('app'));
const apiRouter = Router();
apiRouter.use('/catalog', await catalog(catalogEnv));
apiRouter.use('/rollbar', await rollbar(rollbarEnv));
apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv));
apiRouter.use('/sentry', await sentry(sentryEnv));
apiRouter.use('/auth', await auth(authEnv, '/api/auth'));
apiRouter.use('/identity', await identity(identityEnv));
apiRouter.use('/techdocs', await techdocs(techdocsEnv));
apiRouter.use('/kubernetes', await kubernetes(kubernetesEnv));
apiRouter.use('/proxy', await proxy(proxyEnv, '/api/proxy'));
apiRouter.use('/graphql', await graphql(graphqlEnv));
apiRouter.use(notFoundHandler());
const service = createServiceBuilder(module)
.loadConfig(configReader)
.addRouter('', await healthcheck(healthcheckEnv))
.addRouter('/catalog', await catalog(catalogEnv))
.addRouter('/rollbar', await rollbar(rollbarEnv))
.addRouter('/scaffolder', await scaffolder(scaffolderEnv))
.addRouter('/sentry', await sentry(sentryEnv))
.addRouter('/auth', await auth(authEnv))
.addRouter('/identity', await identity(identityEnv))
.addRouter('/techdocs', await techdocs(techdocsEnv))
.addRouter('/kubernetes', await kubernetes(kubernetesEnv))
.addRouter('/proxy', await proxy(proxyEnv, '/proxy'))
.addRouter('/graphql', await graphql(graphqlEnv))
.addRouter('/api', apiRouter)
.addRouter('', await app(appEnv));
await service.start().catch(err => {
+5 -6
View File
@@ -17,10 +17,9 @@
import { createRouter } from '@backstage/plugin-auth-backend';
import { PluginEnvironment } from '../types';
export default async function createPlugin({
logger,
database,
config,
}: PluginEnvironment) {
return await createRouter({ logger, config, database });
export default async function createPlugin(
{ logger, database, config }: PluginEnvironment,
basePath: string,
) {
return await createRouter({ logger, config, database, basePath });
}
+5 -2
View File
@@ -17,6 +17,9 @@
import { createRouter } from '@backstage/plugin-kubernetes-backend';
import { 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 });
}
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/catalog-model",
"version": "0.1.1-alpha.22",
"version": "0.1.1-alpha.23",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -20,7 +20,7 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/config": "^0.1.1-alpha.22",
"@backstage/config": "^0.1.1-alpha.23",
"@types/json-schema": "^7.0.5",
"@types/yup": "^0.28.2",
"json-schema": "^0.2.5",
@@ -29,7 +29,7 @@
"yup": "^0.29.1"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.22",
"@backstage/cli": "^0.1.1-alpha.23",
"@types/express": "^4.17.6",
"@types/jest": "^26.0.7",
"@types/lodash": "^4.14.151",
@@ -28,6 +28,7 @@ import {
GroupEntityV1alpha1Policy,
LocationEntityV1alpha1Policy,
TemplateEntityV1alpha1Policy,
UserEntityV1alpha1Policy,
} from './kinds';
import { EntityPolicy } from './types';
@@ -77,6 +78,7 @@ export class EntityPolicies implements EntityPolicy {
EntityPolicies.anyOf([
new ComponentEntityV1alpha1Policy(),
new GroupEntityV1alpha1Policy(),
new UserEntityV1alpha1Policy(),
new LocationEntityV1alpha1Policy(),
new TemplateEntityV1alpha1Policy(),
new ApiEntityV1alpha1Policy(),
@@ -17,6 +17,7 @@
export { entityMetaGeneratedFields } from './Entity';
export type { Entity, EntityMeta } from './Entity';
export * from './policies';
export { parseEntityName, serializeEntityRef } from './ref';
export {
entityHasChanges,
generateEntityEtag,
@@ -0,0 +1,362 @@
/*
* 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 { parseEntityName, parseEntityRef, serializeEntityRef } from './ref';
describe('ref', () => {
describe('parseEntityName', () => {
it('handles some omissions', () => {
expect(parseEntityName('a:b/c')).toEqual({
kind: 'a',
namespace: 'b',
name: 'c',
});
expect(() => parseEntityName('b/c')).toThrow(/kind/);
expect(parseEntityName('a:c')).toEqual({
kind: 'a',
namespace: 'default',
name: 'c',
});
expect(() => parseEntityName('c')).toThrow(/kind/);
});
it('rejects bad inputs', () => {
expect(() => parseEntityName(null as any)).toThrow();
expect(() => parseEntityName(7 as any)).toThrow();
expect(() => parseEntityName('a:b:c')).toThrow();
expect(() => parseEntityName('a/b/c')).toThrow();
expect(() => parseEntityName('a/b:c')).toThrow();
expect(() => parseEntityName('a:b/c/d')).toThrow();
expect(() => parseEntityName('a:b/c:d')).toThrow();
});
it('rejects empty parts in strings', () => {
// one is empty
expect(() => parseEntityName(':b/c')).toThrow();
expect(() => parseEntityName('a:/c')).toThrow();
expect(() => parseEntityName('a:b/')).toThrow();
// two are empty
expect(() => parseEntityName('a:/')).toThrow();
expect(() => parseEntityName(':b/')).toThrow();
expect(() => parseEntityName(':/c')).toThrow();
// three are empty
expect(() => parseEntityName(':/')).toThrow();
// one is left out, one empty
expect(() => parseEntityName('/c')).toThrow();
expect(() => parseEntityName('b/')).toThrow();
expect(() => parseEntityName(':c')).toThrow();
expect(() => parseEntityName('a:')).toThrow();
// nothing at all
expect(() => parseEntityName('')).toThrow();
});
it('rejects empty parts in compounds', () => {
// one is empty
expect(() =>
parseEntityName({ kind: '', namespace: 'b', name: 'c' }),
).toThrow();
expect(() => parseEntityName({ namespace: 'b', name: 'c' })).toThrow();
expect(() =>
parseEntityName({ kind: 'a', namespace: '', name: 'c' }),
).toThrow();
expect(() => parseEntityName({ kind: 'a', name: 'c' })).not.toThrow();
expect(() =>
parseEntityName({ kind: 'a', namespace: 'b', name: '' }),
).toThrow();
expect(() =>
parseEntityName({ kind: 'a', namespace: 'b' } as any),
).toThrow();
// two are empty
expect(() =>
parseEntityName({ kind: '', namespace: '', name: 'c' }),
).toThrow();
expect(() => parseEntityName({ name: 'c' })).toThrow();
expect(() =>
parseEntityName({ kind: '', namespace: 'b', name: '' }),
).toThrow();
expect(() => parseEntityName({ namespace: 'b' } as any)).toThrow();
expect(() =>
parseEntityName({ kind: 'a', namespace: '', name: '' }),
).toThrow();
expect(() => parseEntityName({ kind: 'a' } as any)).toThrow();
// three are empty
expect(() =>
parseEntityName({ kind: '', namespace: '', name: '' }),
).toThrow();
expect(() => parseEntityName({} as any)).toThrow();
// one is left out, one empty
expect(() => parseEntityName({ namespace: '', name: 'c' })).toThrow();
expect(() => parseEntityName({ namespace: 'b', name: '' })).toThrow();
expect(() => parseEntityName({ kind: '', name: 'c' })).toThrow();
expect(() => parseEntityName({ kind: 'a', name: '' })).toThrow();
});
it('adds defaults where necessary to strings', () => {
expect(
parseEntityName('a:b/c', { defaultKind: 'x', defaultNamespace: 'y' }),
).toEqual({ kind: 'a', namespace: 'b', name: 'c' });
expect(
parseEntityName('b/c', { defaultKind: 'x', defaultNamespace: 'y' }),
).toEqual({ kind: 'x', namespace: 'b', name: 'c' });
expect(
parseEntityName('a:c', { defaultKind: 'x', defaultNamespace: 'y' }),
).toEqual({ kind: 'a', namespace: 'y', name: 'c' });
expect(parseEntityName('a:c', { defaultKind: 'x' })).toEqual({
kind: 'a',
namespace: 'default',
name: 'c',
});
expect(
parseEntityName('c', { defaultKind: 'x', defaultNamespace: 'y' }),
).toEqual({ kind: 'x', namespace: 'y', name: 'c' });
expect(parseEntityName('c', { defaultKind: 'x' })).toEqual({
kind: 'x',
namespace: 'default',
name: 'c',
});
});
it('adds defaults where necessary to compounds', () => {
expect(
parseEntityName(
{ kind: 'a', namespace: 'b', name: 'c' },
{ defaultKind: 'x', defaultNamespace: 'y' },
),
).toEqual({ kind: 'a', namespace: 'b', name: 'c' });
expect(
parseEntityName(
{ namespace: 'b', name: 'c' },
{ defaultKind: 'x', defaultNamespace: 'y' },
),
).toEqual({ kind: 'x', namespace: 'b', name: 'c' });
expect(
parseEntityName(
{ kind: 'a', name: 'c' },
{ defaultKind: 'x', defaultNamespace: 'y' },
),
).toEqual({ kind: 'a', namespace: 'y', name: 'c' });
expect(
parseEntityName({ kind: 'a', name: 'c' }, { defaultKind: 'x' }),
).toEqual({ kind: 'a', namespace: 'default', name: 'c' });
expect(
parseEntityName(
{ name: 'c' },
{ defaultKind: 'x', defaultNamespace: 'y' },
),
).toEqual({ kind: 'x', namespace: 'y', name: 'c' });
expect(parseEntityName({ name: 'c' }, { defaultKind: 'x' })).toEqual({
kind: 'x',
namespace: 'default',
name: 'c',
});
// empty strings are errors, not defaults
expect(() =>
parseEntityName(
{ kind: '', namespace: 'b', name: 'c' },
{ defaultKind: 'x', defaultNamespace: 'y' },
),
).toThrow(/kind/);
expect(() =>
parseEntityName(
{ kind: 'a', namespace: '', name: 'c' },
{ defaultKind: 'x', defaultNamespace: 'y' },
),
).toThrow(/namespace/);
});
});
describe('parseEntityRef', () => {
it('handles some omissions', () => {
expect(parseEntityRef('a:b/c')).toEqual({
kind: 'a',
namespace: 'b',
name: 'c',
});
expect(parseEntityRef('b/c')).toEqual({
kind: undefined,
namespace: 'b',
name: 'c',
});
expect(parseEntityRef('a:c')).toEqual({
kind: 'a',
namespace: undefined,
name: 'c',
});
expect(parseEntityRef('c')).toEqual({
kind: undefined,
namespace: undefined,
name: 'c',
});
});
it('rejects bad inputs', () => {
expect(() => parseEntityRef(null as any)).toThrow();
expect(() => parseEntityRef(7 as any)).toThrow();
expect(() => parseEntityRef('a:b:c')).toThrow();
expect(() => parseEntityRef('a/b/c')).toThrow();
expect(() => parseEntityRef('a/b:c')).toThrow();
expect(() => parseEntityRef('a:b/c/d')).toThrow();
expect(() => parseEntityRef('a:b/c:d')).toThrow();
});
it('rejects empty parts in strings', () => {
// one is empty
expect(() => parseEntityRef(':b/c')).toThrow();
expect(() => parseEntityRef('a:/c')).toThrow();
expect(() => parseEntityRef('a:b/')).toThrow();
// two are empty
expect(() => parseEntityRef('a:/')).toThrow();
expect(() => parseEntityRef(':b/')).toThrow();
expect(() => parseEntityRef(':/c')).toThrow();
// three are empty
expect(() => parseEntityRef(':/')).toThrow();
// one is left out, one empty
expect(() => parseEntityRef('/c')).toThrow();
expect(() => parseEntityRef('b/')).toThrow();
expect(() => parseEntityRef(':c')).toThrow();
expect(() => parseEntityRef('a:')).toThrow();
// nothing at all
expect(() => parseEntityRef('')).toThrow();
});
it('rejects empty parts in compounds', () => {
// one is empty
expect(() =>
parseEntityRef({ kind: '', namespace: 'b', name: 'c' }),
).toThrow();
expect(() =>
parseEntityRef({ kind: 'a', namespace: '', name: 'c' }),
).toThrow();
expect(() =>
parseEntityRef({ kind: 'a', namespace: 'b', name: '' }),
).toThrow();
// two are empty
expect(() =>
parseEntityRef({ kind: '', namespace: '', name: 'c' }),
).toThrow();
expect(() =>
parseEntityRef({ kind: '', namespace: 'b', name: '' }),
).toThrow();
expect(() =>
parseEntityRef({ kind: 'a', namespace: '', name: '' }),
).toThrow();
// three are empty
expect(() =>
parseEntityRef({ kind: '', namespace: '', name: '' }),
).toThrow();
// one is left out, one empty
expect(() => parseEntityRef({ namespace: '', name: 'c' })).toThrow();
expect(() => parseEntityRef({ namespace: 'b', name: '' })).toThrow();
expect(() => parseEntityRef({ kind: '', name: 'c' })).toThrow();
expect(() => parseEntityRef({ kind: 'a', name: '' })).toThrow();
// nothing at all
expect(() => parseEntityRef({} as any)).toThrow();
});
it('adds defaults where necessary to strings', () => {
expect(
parseEntityRef('a:b/c', { defaultKind: 'x', defaultNamespace: 'y' }),
).toEqual({ kind: 'a', namespace: 'b', name: 'c' });
expect(
parseEntityRef('b/c', { defaultKind: 'x', defaultNamespace: 'y' }),
).toEqual({ kind: 'x', namespace: 'b', name: 'c' });
expect(
parseEntityRef('a:c', { defaultKind: 'x', defaultNamespace: 'y' }),
).toEqual({ kind: 'a', namespace: 'y', name: 'c' });
expect(
parseEntityRef('c', { defaultKind: 'x', defaultNamespace: 'y' }),
).toEqual({ kind: 'x', namespace: 'y', name: 'c' });
});
it('adds defaults where necessary to compounds', () => {
expect(
parseEntityRef(
{ kind: 'a', namespace: 'b', name: 'c' },
{ defaultKind: 'x', defaultNamespace: 'y' },
),
).toEqual({ kind: 'a', namespace: 'b', name: 'c' });
expect(
parseEntityRef(
{ namespace: 'b', name: 'c' },
{ defaultKind: 'x', defaultNamespace: 'y' },
),
).toEqual({ kind: 'x', namespace: 'b', name: 'c' });
expect(
parseEntityRef(
{ kind: 'a', name: 'c' },
{ defaultKind: 'x', defaultNamespace: 'y' },
),
).toEqual({ kind: 'a', namespace: 'y', name: 'c' });
expect(
parseEntityRef(
{ name: 'c' },
{ defaultKind: 'x', defaultNamespace: 'y' },
),
).toEqual({ kind: 'x', namespace: 'y', name: 'c' });
// empty strings are errors, not defaults
expect(() =>
parseEntityRef(
{ kind: '', namespace: 'b', name: 'c' },
{ defaultKind: 'x', defaultNamespace: 'y' },
),
).toThrow(/kind/);
expect(() =>
parseEntityRef(
{ kind: 'a', namespace: '', name: 'c' },
{ defaultKind: 'x', defaultNamespace: 'y' },
),
).toThrow(/namespace/);
});
});
describe('serializeEntityRef', () => {
it('handles partials', () => {
expect(
serializeEntityRef({ kind: 'a', namespace: 'b', name: 'c' }),
).toEqual('a:b/c');
expect(serializeEntityRef({ namespace: 'b', name: 'c' })).toEqual('b/c');
expect(serializeEntityRef({ kind: 'a', name: 'c' })).toEqual('a:c');
expect(serializeEntityRef({ name: 'c' })).toEqual('c');
});
it('picks the least complex form', () => {
expect(
serializeEntityRef({ kind: 'a', namespace: 'b', name: 'c' }),
).toEqual('a:b/c');
expect(serializeEntityRef({ namespace: 'b', name: 'c' })).toEqual('b/c');
expect(serializeEntityRef({ kind: 'a', name: 'c' })).toEqual('a:c');
expect(serializeEntityRef({ name: 'c' })).toEqual('c');
expect(
serializeEntityRef({ kind: 'a:x', namespace: 'b', name: 'c' }),
).toEqual({ kind: 'a:x', namespace: 'b', name: 'c' });
expect(
serializeEntityRef({ kind: 'a/x', namespace: 'b', name: 'c' }),
).toEqual({ kind: 'a/x', namespace: 'b', name: 'c' });
expect(
serializeEntityRef({ kind: 'a', namespace: 'b:x', name: 'c' }),
).toEqual({ kind: 'a', namespace: 'b:x', name: 'c' });
expect(
serializeEntityRef({ kind: 'a', namespace: 'b/x', name: 'c' }),
).toEqual({ kind: 'a', namespace: 'b/x', name: 'c' });
expect(
serializeEntityRef({ kind: 'a', namespace: 'b', name: 'c:x' }),
).toEqual({ kind: 'a', namespace: 'b', name: 'c:x' });
expect(
serializeEntityRef({ kind: 'a', namespace: 'b', name: 'c/x' }),
).toEqual({ kind: 'a', namespace: 'b', name: 'c/x' });
});
});
});
+164
View File
@@ -0,0 +1,164 @@
/*
* 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 { EntityName, EntityRef } from '../types';
/**
* The context of defaults that entity reference parsing happens within.
*/
type EntityRefContext = {
/** The default kind, if none is given in the reference */
defaultKind?: string;
/** The default namespace, if none is given in the reference */
defaultNamespace?: string;
};
/**
* Parses an entity reference, either on string or compound form, and always
* returns a complete entity name including kind, namespace and name.
*
* This function automatically assumes the default namespace "default" unless
* otherwise specified as part of the options, and will throw an error if no
* kind was specified in the input reference and no default kind was given.
*
* @param ref The reference to parse
* @param context The context of defaults that the parsing happens within
* @returns A complete entity name
*/
export function parseEntityName(
ref: EntityRef,
context: EntityRefContext = {},
): EntityName {
const { kind, namespace, name } = parseEntityRef(ref, {
defaultNamespace: 'default',
...context,
});
if (!kind) {
throw new Error(
`Entity reference ${namespace}/${name} did not contain a kind`,
);
}
return { kind, namespace, name };
}
/**
* Parses an entity reference, either on string or compound form, and returns
* a structure with a name, and optional kind and namespace.
*
* The options object can contain default values for the kind and namespace,
* that will be used if the input reference did not specify any.
*
* @param ref The reference to parse
* @param context The context of defaults that the parsing happens within
* @returns The compound form of the reference
*/
export function parseEntityRef(
ref: EntityRef,
context?: { defaultKind: string },
): {
kind: string;
namespace?: string;
name: string;
};
export function parseEntityRef(
ref: EntityRef,
context?: { defaultNamespace: string },
): {
kind?: string;
namespace: string;
name: string;
};
export function parseEntityRef(
ref: EntityRef,
context?: { defaultKind: string; defaultNamespace: string },
): {
kind: string;
namespace: string;
name: string;
};
export function parseEntityRef(
ref: EntityRef,
context: EntityRefContext = {},
): {
kind?: string;
namespace?: string;
name: string;
} {
if (!ref) {
throw new Error(`Entity reference must not be empty`);
}
if (typeof ref === 'string') {
const match = /^([^:/]+:)?([^:/]+\/)?([^:/]+)$/.exec(ref.trim());
if (!match) {
throw new Error(
`Entity reference "${ref}" was not on the form [<kind>:][<namespace>/]<name>`,
);
}
return {
kind: match[1]?.slice(0, -1) ?? context.defaultKind,
namespace: match[2]?.slice(0, -1) ?? context.defaultNamespace,
name: match[3],
};
}
const { kind, namespace, name } = ref;
if (kind === '') {
throw new Error('Entity reference kinds must not be empty');
} else if (namespace === '') {
throw new Error('Entity reference namespaces must not be empty');
} else if (!name) {
throw new Error('Entity references must contain a name');
}
return {
kind: kind ?? context.defaultKind,
namespace: namespace ?? context.defaultNamespace,
name,
};
}
/**
* Takes an entity reference or name, and outputs an entity reference on the
* most compact form possible. I.e. if the parts do not contain any
* special/reserved characters, it outputs the string form, otherwise it
* outputs the compound form.
*
* @param ref The reference to serialize
* @returns The same reference on either string or compound form
*/
export function serializeEntityRef(ref: {
kind?: string;
namespace?: string;
name: string;
}): EntityRef {
const { kind, namespace, name } = ref;
if (
kind?.includes(':') ||
kind?.includes('/') ||
namespace?.includes(':') ||
namespace?.includes('/') ||
name.includes(':') ||
name.includes('/')
) {
return { kind, namespace, name };
}
return `${kind ? `${kind}:` : ''}${namespace ? `${namespace}/` : ''}${name}`;
}
+1 -1
View File
@@ -18,5 +18,5 @@ export * from './entity';
export { EntityPolicies } from './EntityPolicies';
export * from './kinds';
export * from './location';
export type { EntityPolicy, JSONSchema } from './types';
export type { EntityName, EntityPolicy, EntityRef, JSONSchema } from './types';
export * from './validation';
@@ -0,0 +1,150 @@
/*
* 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 {
UserEntityV1alpha1,
UserEntityV1alpha1Policy,
} from './UserEntityV1alpha1';
describe('UserV1alpha1Policy', () => {
let entity: UserEntityV1alpha1;
let policy: EntityPolicy;
beforeEach(() => {
entity = {
apiVersion: 'backstage.io/v1alpha1',
kind: 'User',
metadata: {
name: 'doe',
},
spec: {
profile: {
displayName: 'John Doe',
email: 'john@doe.org',
picture: 'https://doe.org/john.jpeg',
},
memberOf: ['team-a', 'developers'],
},
};
policy = new UserEntityV1alpha1Policy();
});
it('happy path: accepts valid data', async () => {
await expect(policy.enforce(entity)).resolves.toBe(entity);
});
// root
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('spec accepts unknown additional fields', async () => {
(entity as any).spec.foo = 'data';
await expect(policy.enforce(entity)).resolves.toBe(entity);
});
// profile
it('accepts missing profile', async () => {
delete (entity as any).spec.profile;
await expect(policy.enforce(entity)).resolves.toBe(entity);
});
it('rejects wrong profile', async () => {
(entity as any).spec.profile = 7;
await expect(policy.enforce(entity)).rejects.toThrow(/profile/);
});
it('profile accepts missing displayName', async () => {
delete (entity as any).spec.profile.displayName;
await expect(policy.enforce(entity)).resolves.toBe(entity);
});
it('profile rejects wrong displayName', async () => {
(entity as any).spec.profile.displayName = 7;
await expect(policy.enforce(entity)).rejects.toThrow(/displayName/);
});
it('profile rejects empty displayName', async () => {
(entity as any).spec.profile.displayName = '';
await expect(policy.enforce(entity)).rejects.toThrow(/displayName/);
});
it('profile accepts missing email', async () => {
delete (entity as any).spec.profile.email;
await expect(policy.enforce(entity)).resolves.toBe(entity);
});
it('profile rejects wrong email', async () => {
(entity as any).spec.profile.email = 7;
await expect(policy.enforce(entity)).rejects.toThrow(/email/);
});
it('profile rejects empty email', async () => {
(entity as any).spec.profile.email = '';
await expect(policy.enforce(entity)).rejects.toThrow(/email/);
});
it('profile accepts missing picture', async () => {
delete (entity as any).spec.profile.picture;
await expect(policy.enforce(entity)).resolves.toBe(entity);
});
it('profile rejects wrong picture', async () => {
(entity as any).spec.profile.picture = 7;
await expect(policy.enforce(entity)).rejects.toThrow(/picture/);
});
it('profile rejects empty picture', async () => {
(entity as any).spec.profile.picture = '';
await expect(policy.enforce(entity)).rejects.toThrow(/picture/);
});
it('profile accepts unknown additional fields', async () => {
(entity as any).spec.profile.foo = 'data';
await expect(policy.enforce(entity)).resolves.toBe(entity);
});
// memberOf
it('rejects missing memberOf', async () => {
delete (entity as any).spec.memberOf;
await expect(policy.enforce(entity)).rejects.toThrow(/memberOf/);
});
it('rejects wrong memberOf', async () => {
(entity as any).spec.memberOf = 7;
await expect(policy.enforce(entity)).rejects.toThrow(/memberOf/);
});
it('rejects wrong memberOf item', async () => {
(entity as any).spec.memberOf[0] = 7;
await expect(policy.enforce(entity)).rejects.toThrow(/memberOf/);
});
});
@@ -0,0 +1,62 @@
/*
* 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 = 'User' as const;
export interface UserEntityV1alpha1 extends Entity {
apiVersion: typeof API_VERSION[number];
kind: typeof KIND;
spec: {
profile?: {
displayName?: string;
email?: string;
picture?: string;
};
memberOf: string[];
};
}
export class UserEntityV1alpha1Policy implements EntityPolicy {
private schema: yup.Schema<any>;
constructor() {
this.schema = yup.object<Partial<UserEntityV1alpha1>>({
apiVersion: yup.string().required().oneOf(API_VERSION),
kind: yup.string().required().equals([KIND]),
spec: yup
.object({
profile: yup
.object({
displayName: yup.string().min(1).notRequired(),
email: yup.string().min(1).notRequired(),
picture: yup.string().min(1).notRequired(),
})
.notRequired(),
memberOf: yup.array(yup.string()).required(),
})
.required(),
});
}
async enforce(envelope: Entity): Promise<Entity> {
return await this.schema.validate(envelope, { strict: true });
}
}
+9 -4
View File
@@ -14,6 +14,11 @@
* limitations under the License.
*/
export { ApiEntityV1alpha1Policy } from './ApiEntityV1alpha1';
export type {
ApiEntityV1alpha1 as ApiEntity,
ApiEntityV1alpha1,
} from './ApiEntityV1alpha1';
export { ComponentEntityV1alpha1Policy } from './ComponentEntityV1alpha1';
export type {
ComponentEntityV1alpha1 as ComponentEntity,
@@ -34,8 +39,8 @@ export type {
TemplateEntityV1alpha1 as TemplateEntity,
TemplateEntityV1alpha1,
} from './TemplateEntityV1alpha1';
export { ApiEntityV1alpha1Policy } from './ApiEntityV1alpha1';
export { UserEntityV1alpha1Policy } from './UserEntityV1alpha1';
export type {
ApiEntityV1alpha1 as ApiEntity,
ApiEntityV1alpha1,
} from './ApiEntityV1alpha1';
UserEntityV1alpha1 as UserEntity,
UserEntityV1alpha1,
} from './UserEntityV1alpha1';
+26
View File
@@ -34,3 +34,29 @@ export type EntityPolicy = {
};
export type JSONSchema = JSONSchema7 & { [key in string]?: JsonValue };
/**
* A complete entity name, with the full kind-namespace-name triplet.
*/
export type EntityName = {
kind: string;
namespace: string;
name: string;
};
/**
* A reference by name to an entity, either as a compact string representation,
* or as a compound reference structure.
*
* The string representation is on the form [<kind>:][<namespace>/]<name>.
*
* Left-out parts of the reference need to be handled by the application,
* either by rejecting the reference or by falling back to default values.
*/
export type EntityRef =
| string
| {
kind?: string;
namespace?: string;
name: string;
};
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/cli-common",
"description": "Common functionality used by cli, backend, and create-app",
"version": "0.1.1-alpha.22",
"version": "0.1.1-alpha.23",
"private": false,
"main": "src/index.ts",
"types": "src/index.ts",
+4 -4
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/cli",
"description": "CLI for developing Backstage plugins and apps",
"version": "0.1.1-alpha.22",
"version": "0.1.1-alpha.23",
"private": false,
"publishConfig": {
"access": "public"
@@ -28,9 +28,9 @@
"backstage-cli": "bin/backstage-cli"
},
"dependencies": {
"@backstage/cli-common": "^0.1.1-alpha.22",
"@backstage/config": "^0.1.1-alpha.22",
"@backstage/config-loader": "^0.1.1-alpha.22",
"@backstage/cli-common": "^0.1.1-alpha.23",
"@backstage/config": "^0.1.1-alpha.23",
"@backstage/config-loader": "^0.1.1-alpha.23",
"@hot-loader/react-dom": "^16.13.0",
"@lerna/package-graph": "^3.18.5",
"@lerna/project": "^3.18.0",
@@ -231,7 +231,11 @@ export default async (cmd: Command) => {
const privatePackage = cmd.private === false ? false : true;
const isMonoRepo = await fs.pathExists(paths.resolveTargetRoot('lerna.json'));
const appPackage = paths.resolveTargetRoot('packages/app');
const templateDir = paths.resolveOwn('templates/default-plugin');
const templateDir = paths.resolveOwn(
cmd.backend
? 'templates/default-backend-plugin'
: 'templates/default-plugin',
);
const tempDir = resolvePath(os.tmpdir(), answers.id);
const pluginDir = isMonoRepo
? paths.resolveTargetRoot('plugins', answers.id)
@@ -252,6 +256,7 @@ export default async (cmd: Command) => {
await createTemporaryPluginFolder(tempDir);
Task.section('Preparing files');
await templatingTask(templateDir, tempDir, {
...answers,
version,
@@ -267,7 +272,7 @@ export default async (cmd: Command) => {
Task.section('Building the plugin');
await buildPlugin(pluginDir);
if (await fs.pathExists(appPackage)) {
if ((await fs.pathExists(appPackage)) && !cmd.backend) {
Task.section('Adding plugin as dependency in app');
await addPluginDependencyToApp(paths.targetRoot, name, version);
+4
View File
@@ -61,6 +61,10 @@ export function registerCommands(program: CommanderStatic) {
program
.command('create-plugin')
.option(
'--backend',
'Create plugin with the backend dependencies as default',
)
.description('Creates a new plugin in the current repository')
.option('--scope <scope>', 'NPM scope')
.option('--npm-registry <URL>', 'NPM registry URL')
@@ -0,0 +1,3 @@
module.exports = {
extends: [require.resolve('@backstage/cli/config/eslint.backend')],
};
@@ -0,0 +1,14 @@
# {{id}}
Welcome to the {{id}} backend plugin!
_This plugin was created through the Backstage CLI_
## Getting started
Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn
start` in the root directory, and then navigating to [/{{id}}](http://localhost:3000/{{id}}).
You can also serve the plugin in isolation by running `yarn start` in the plugin directory.
This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads.
It is only meant for local development, and the setup for it can be found inside the [/dev](/dev) directory.
@@ -0,0 +1,44 @@
{
"name": "{{name}}",
"version": "{{version}}",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
{{#if privatePackage}} "private": {{privatePackage}},
{{/if}}
"publishConfig": {
{{#if npmRegistry}} "registry": "{{npmRegistry}}",
{{/if}}
"access": "public",
"main": "dist/index.cjs.js",
"types": "dist/index.d.ts"
},
"scripts": {
"start": "backstage-cli backend:dev",
"build": "backstage-cli backend:build",
"lint": "backstage-cli lint",
"test": "backstage-cli test",
"prepack": "backstage-cli prepack",
"postpack": "backstage-cli postpack",
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/backend-common": "^{{version}}",
"@backstage/config": "^{{version}}",
"@types/express": "^4.17.6",
"express": "^4.17.1",
"express-promise-router": "^3.0.3",
"winston": "^3.2.1",
"node-fetch": "^2.6.1",
"yn": "^4.0.0"
},
"devDependencies": {
"@backstage/cli": "^{{version}}",
"@types/supertest": "^2.0.8",
"supertest": "^4.0.2",
"msw": "^0.20.5"
},
"files": [
"dist"
]
}
@@ -0,0 +1,17 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './service/router';
@@ -0,0 +1,33 @@
/*
* 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 { getRootLogger } from '@backstage/backend-common';
import yn from 'yn';
import { startStandaloneServer } from './service/standaloneServer';
const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7000;
const enableCors = yn(process.env.PLUGIN_CORS, { default: false });
const logger = getRootLogger();
startStandaloneServer({ port, enableCors, logger }).catch(err => {
logger.error(err);
process.exit(1);
});
process.on('SIGINT', () => {
logger.info('CTRL+C pressed; exiting.');
process.exit(0);
});
@@ -0,0 +1,45 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { getVoidLogger } from '@backstage/backend-common';
import express from 'express';
import request from 'supertest';
import { createRouter } from './router';
describe('createRouter', () => {
let app: express.Express;
beforeAll(async () => {
const router = await createRouter({
logger: getVoidLogger(),
});
app = express().use(router);
});
beforeEach(() => {
jest.resetAllMocks();
});
describe('GET /health', () => {
it('returns ok', async () => {
const response = await request(app).get('/health');
expect(response.status).toEqual(200);
expect(response.body).toEqual({ status: 'ok' });
});
});
});
@@ -0,0 +1,40 @@
/*
* 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 { errorHandler } from '@backstage/backend-common';
import express from 'express';
import Router from 'express-promise-router';
import { Logger } from 'winston';
export interface RouterOptions {
logger: Logger;
}
export async function createRouter(
options: RouterOptions,
): Promise<express.Router> {
const { logger } = options;
const router = Router();
router.use(express.json());
router.get('/health', (_, response) => {
logger.info('PONG!');
response.send({ status: 'ok' });
});
router.use(errorHandler());
return router;
}
@@ -0,0 +1,47 @@
/*
* 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 { createServiceBuilder } from '@backstage/backend-common';
import { Server } from 'http';
import { Logger } from 'winston';
import { createRouter } from './router';
export interface ServerOptions {
port: number;
enableCors: boolean;
logger: Logger;
}
export async function startStandaloneServer(
options: ServerOptions,
): Promise<Server> {
const logger = options.logger.child({ service: '{{id}}-backend' });
logger.debug('Starting application server...');
const router = await createRouter({
logger,
});
const service = createServiceBuilder(module)
.enableCors({ origin: 'http://localhost:3000' })
.addRouter('/{{id}}', router);
return await service.start().catch(err => {
logger.error(err);
process.exit(1);
});
}
module.hot?.accept();
@@ -0,0 +1,18 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export {};
global.fetch = require('node-fetch');
@@ -41,7 +41,8 @@
"@testing-library/user-event": "^12.0.7",
"@types/jest": "^26.0.7",
"@types/node": "^12.0.0",
"jest-fetch-mock": "^3.0.3"
"msw": "^0.20.5",
"node-fetch": "^2.6.1"
},
"files": [
"dist"
@@ -1,18 +1,35 @@
import React from 'react';
import { render } from '@testing-library/react';
import mockFetch from 'jest-fetch-mock';
import ExampleComponent from './ExampleComponent';
import { ThemeProvider } from '@material-ui/core';
import { lightTheme } from '@backstage/theme';
import { rest } from 'msw';
import { setupServer } from 'msw/node';
describe('ExampleComponent', () => {
const server = setupServer();
// Enable API mocking before tests.
beforeAll(() => server.listen())
// Reset any runtime request handlers we may add during the tests.
afterEach(() => server.resetHandlers())
// Disable API mocking after the tests are done.
afterAll(() => server.close())
// setup mock response
beforeEach(() => {
server.use(rest.get('/*', (_, res, ctx) => res(ctx.status(200), ctx.json({}))))
})
it('should render', () => {
mockFetch.mockResponse(() => new Promise(() => {}));
const rendered = render(
<ThemeProvider theme={lightTheme}>
<ExampleComponent />
</ThemeProvider>,
);
expect(rendered.getByText('Welcome to {{ id }}!')).toBeInTheDocument();
);
expect(rendered.getByText('Welcome to {{ id }}!')).toBeInTheDocument();
});
});
@@ -1,11 +1,25 @@
import React from 'react';
import { render } from '@testing-library/react';
import mockFetch from 'jest-fetch-mock';
import ExampleFetchComponent from './ExampleFetchComponent';
import { rest } from 'msw';
import { setupServer } from 'msw/node';
describe('ExampleFetchComponent', () => {
const server = setupServer();
// Enable API mocking before tests.
beforeAll(() => server.listen())
// Reset any runtime request handlers we may add during the tests.
afterEach(() => server.resetHandlers())
// Disable API mocking after the tests are done.
afterAll(() => server.close())
// setup mock response
beforeEach(() => {
server.use(rest.get('https://randomuser.me/*', (_, res, ctx) => res(ctx.status(200), ctx.delay(2000), ctx.json({}))))
})
it('should render', async () => {
mockFetch.mockResponse(() => new Promise(() => {}));
const rendered = render(<ExampleFetchComponent />);
expect(await rendered.findByTestId('progress')).toBeInTheDocument();
});
@@ -1,3 +1,2 @@
import '@testing-library/jest-dom';
require('jest-fetch-mock').enableMocks();
global.fetch = require('node-fetch');
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/config-loader",
"description": "Config loading functionality used by Backstage backend, and CLI",
"version": "0.1.1-alpha.22",
"version": "0.1.1-alpha.23",
"private": false,
"publishConfig": {
"access": "public",
@@ -30,7 +30,7 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/config": "^0.1.1-alpha.22",
"@backstage/config": "^0.1.1-alpha.23",
"fs-extra": "^9.0.0",
"yaml": "^1.9.2",
"yup": "^0.29.1"
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/config",
"description": "Config API used by Backstage core, backend, and CLI",
"version": "0.1.1-alpha.22",
"version": "0.1.1-alpha.23",
"private": false,
"publishConfig": {
"access": "public",
+5 -5
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/core-api",
"description": "Internal Core API used by Backstage plugins and apps",
"version": "0.1.1-alpha.22",
"version": "0.1.1-alpha.23",
"private": false,
"publishConfig": {
"access": "public",
@@ -29,8 +29,8 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/config": "^0.1.1-alpha.22",
"@backstage/theme": "^0.1.1-alpha.22",
"@backstage/config": "^0.1.1-alpha.23",
"@backstage/theme": "^0.1.1-alpha.23",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@types/react": "^16.9",
@@ -41,8 +41,8 @@
"zen-observable": "^0.8.15"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.22",
"@backstage/test-utils-core": "^0.1.1-alpha.22",
"@backstage/cli": "^0.1.1-alpha.23",
"@backstage/test-utils-core": "^0.1.1-alpha.23",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
"@testing-library/user-event": "^12.0.7",
+6 -6
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/core",
"description": "Core API used by Backstage plugins and apps",
"version": "0.1.1-alpha.22",
"version": "0.1.1-alpha.23",
"private": false,
"publishConfig": {
"access": "public",
@@ -29,9 +29,9 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/config": "^0.1.1-alpha.22",
"@backstage/core-api": "0.1.1-alpha.22",
"@backstage/theme": "^0.1.1-alpha.22",
"@backstage/config": "^0.1.1-alpha.23",
"@backstage/core-api": "^0.1.1-alpha.23",
"@backstage/theme": "^0.1.1-alpha.23",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -54,8 +54,8 @@
"react-use": "^15.3.3"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.22",
"@backstage/test-utils": "^0.1.1-alpha.22",
"@backstage/cli": "^0.1.1-alpha.23",
"@backstage/test-utils": "^0.1.1-alpha.23",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
"@testing-library/user-event": "^12.0.7",
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/create-app",
"description": "Create app package for Backstage",
"version": "0.1.1-alpha.22",
"version": "0.1.1-alpha.23",
"private": false,
"publishConfig": {
"access": "public"
@@ -27,7 +27,7 @@
"start": "nodemon --"
},
"dependencies": {
"@backstage/cli-common": "^0.1.1-alpha.22",
"@backstage/cli-common": "^0.1.1-alpha.23",
"chalk": "^4.0.0",
"commander": "^6.1.0",
"fs-extra": "^9.0.0",
@@ -44,8 +44,8 @@ proxy:
changeOrigin: true
techdocs:
storageUrl: http://localhost:7000/techdocs/static/docs
requestUrl: http://localhost:7000/techdocs/docs
storageUrl: http://localhost:7000/api/techdocs/static/docs
requestUrl: http://localhost:7000/api/techdocs/docs
generators:
techdocs: 'docker'
@@ -12,7 +12,7 @@ describe('App', () => {
app: { title: 'Test' },
backend: { baseUrl: 'http://localhost:7000' },
techdocs: {
storageUrl: 'http://localhost:7000/techdocs/static/docs',
storageUrl: 'http://localhost:7000/api/techdocs/static/docs',
},
},
context: 'test',
@@ -31,6 +31,7 @@
"@gitbeaker/node": "^23.5.0",
"dockerode": "^3.2.0",
"express": "^4.17.1",
"express-promise-router": "^3.0.3",
"knex": "^0.21.1",
{{#if dbTypePG}}
"pg": "^8.3.0",
@@ -6,12 +6,14 @@
* Happy hacking!
*/
import Router from 'express-promise-router';
import {
createDatabaseClient,
createServiceBuilder,
loadBackendConfig,
getRootLogger,
useHotMemoize,
notFoundHandler,
} from '@backstage/backend-common';
import { ConfigReader, AppConfig } from '@backstage/config';
import auth from './plugins/auth';
@@ -51,14 +53,18 @@ async function main() {
const proxyEnv = useHotMemoize(module, () => createEnv('proxy'));
const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs'));
const apiRouter = Router();
apiRouter.use('/catalog', await catalog(catalogEnv))
apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv))
apiRouter.use('/auth', await auth(authEnv))
apiRouter.use('/identity', await identity(identityEnv))
apiRouter.use('/techdocs', await techdocs(techdocsEnv))
apiRouter.use('/proxy', await proxy(proxyEnv, '/api/proxy'))
apiRouter.use(notFoundHandler());
const service = createServiceBuilder(module)
.loadConfig(configReader)
.addRouter('/catalog', await catalog(catalogEnv))
.addRouter('/scaffolder', await scaffolder(scaffolderEnv))
.addRouter('/auth', await auth(authEnv))
.addRouter('/identity', await identity(identityEnv))
.addRouter('/techdocs', await techdocs(techdocsEnv))
.addRouter('/proxy', await proxy(proxyEnv, '/proxy'));
.addRouter('/api', apiRouter)
await service.start().catch(err => {
console.log(err);
+5 -5
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/dev-utils",
"description": "Utilities for developing Backstage plugins.",
"version": "0.1.1-alpha.22",
"version": "0.1.1-alpha.23",
"private": false,
"publishConfig": {
"access": "public",
@@ -29,10 +29,10 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/cli": "^0.1.1-alpha.22",
"@backstage/core": "^0.1.1-alpha.22",
"@backstage/test-utils": "^0.1.1-alpha.22",
"@backstage/theme": "^0.1.1-alpha.22",
"@backstage/cli": "^0.1.1-alpha.23",
"@backstage/core": "^0.1.1-alpha.23",
"@backstage/test-utils": "^0.1.1-alpha.23",
"@backstage/theme": "^0.1.1-alpha.23",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@testing-library/jest-dom": "^5.10.1",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "docgen",
"description": "Tool for generating API Documentation for itself",
"version": "0.1.1-alpha.22",
"version": "0.1.1-alpha.23",
"private": true,
"homepage": "https://backstage.io",
"repository": {
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "e2e-test",
"description": "E2E test for verifying Backstage packages",
"version": "0.1.1-alpha.22",
"version": "0.1.1-alpha.23",
"private": true,
"homepage": "https://backstage.io",
"repository": {
@@ -21,7 +21,7 @@
"test:e2e": "yarn start"
},
"devDependencies": {
"@backstage/cli-common": "^0.1.1-alpha.22",
"@backstage/cli-common": "^0.1.1-alpha.23",
"@types/fs-extra": "^9.0.1",
"@types/node": "^13.7.2",
"fs-extra": "^9.0.0",
+10 -3
View File
@@ -55,6 +55,9 @@ async function main() {
print('Creating a Backstage Plugin');
const pluginName = await createPlugin('test-plugin', appDir);
print('Creating a Backstage Backend Plugin');
await createPlugin('test-backend-plugin', appDir, ['--backend']);
print('Starting the app');
await testAppServe(pluginName, appDir);
@@ -238,8 +241,12 @@ async function overrideModuleResolutions(appDir: string, workspaceDir: string) {
/**
* Uses create-plugin command to create a new plugin in the app
*/
async function createPlugin(pluginName: string, appDir: string) {
const child = spawnPiped(['yarn', 'create-plugin'], {
async function createPlugin(
pluginName: string,
appDir: string,
options: string[] = [],
) {
const child = spawnPiped(['yarn', 'create-plugin', ...options], {
cwd: appDir,
});
@@ -384,7 +391,7 @@ async function testBackendStart(appDir: string, isPostgres: boolean) {
print('Try to fetch entities from the backend');
// Try fetch entities, should be ok
await fetch('http://localhost:7000/catalog/entities').then(res =>
await fetch('http://localhost:7000/api/catalog/entities').then(res =>
res.json(),
);
print('Entities fetched successfully');
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "storybook",
"version": "0.1.1-alpha.22",
"version": "0.1.1-alpha.23",
"description": "Storybook build for core package",
"private": true,
"scripts": {
@@ -14,7 +14,7 @@
]
},
"dependencies": {
"@backstage/theme": "^0.1.1-alpha.22"
"@backstage/theme": "^0.1.1-alpha.23"
},
"devDependencies": {
"@storybook/addon-actions": "^6.0.21",
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@techdocs/cli",
"description": "CLI for running TechDocs locally.",
"version": "0.1.1-alpha.22",
"version": "0.1.1-alpha.23",
"private": false,
"publishConfig": {
"access": "public"
@@ -44,7 +44,7 @@
"ext": "ts"
},
"dependencies": {
"@backstage/cli": "^0.1.1-alpha.22",
"@backstage/cli": "^0.1.1-alpha.23",
"commander": "^6.1.0",
"fs-extra": "^9.0.1",
"http-proxy": "^1.18.1",
@@ -82,6 +82,10 @@ Extensions:
## Changelog
### 0.0.9
- Change development status to 3 - Alpha
### 0.0.8
- Superfences and Codehilite doesn't work very well together (squidfunk/mkdocs-material#1604) so therefore the codehilite extension is replaced by pymdownx.highlight
@@ -14,13 +14,19 @@ See the License for the specific language governing permissions and
limitations under the License.
"""
from setuptools import setup, find_packages
from os import path
# read the contents of the README file in the current directory
this_dir = path.abspath(path.dirname(__file__))
with open(path.join(this_dir, "README.md"), encoding="utf-8") as file:
long_description = file.read()
setup(
name="mkdocs-techdocs-core",
version="0.0.8",
version="0.0.9",
description="A Mkdocs package that contains TechDocs defaults",
long_description="",
long_description=long_description,
long_description_content_type="text/markdown",
keywords="mkdocs",
url="https://github.com/spotify/backstage",
author="TechDocs Core",
@@ -37,7 +43,7 @@ setup(
"pymdown-extensions==7.1",
],
classifiers=[
"Development Status :: 1 - Planning",
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"Intended Audience :: Information Technology",
"License :: OSI Approved :: Apache Software License",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/test-utils-core",
"description": "Utilities to test Backstage core",
"version": "0.1.1-alpha.22",
"version": "0.1.1-alpha.23",
"private": false,
"publishConfig": {
"access": "public",
+5 -5
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/test-utils",
"description": "Utilities to test Backstage plugins and apps.",
"version": "0.1.1-alpha.22",
"version": "0.1.1-alpha.23",
"private": false,
"publishConfig": {
"access": "public",
@@ -29,10 +29,10 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/cli": "^0.1.1-alpha.22",
"@backstage/core-api": "^0.1.1-alpha.22",
"@backstage/test-utils-core": "^0.1.1-alpha.22",
"@backstage/theme": "^0.1.1-alpha.22",
"@backstage/cli": "^0.1.1-alpha.23",
"@backstage/core-api": "^0.1.1-alpha.23",
"@backstage/test-utils-core": "^0.1.1-alpha.23",
"@backstage/theme": "^0.1.1-alpha.23",
"@material-ui/core": "^4.11.0",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/theme",
"description": "material-ui theme for use with Backstage.",
"version": "0.1.1-alpha.22",
"version": "0.1.1-alpha.23",
"private": false,
"publishConfig": {
"access": "public",
@@ -31,7 +31,7 @@
"@material-ui/core": "^4.11.0"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.22"
"@backstage/cli": "^0.1.1-alpha.23"
},
"files": [
"dist"
+7 -7
View File
@@ -82,12 +82,12 @@ export const darkTheme = createTheme({
default: '#333333',
},
status: {
ok: '#1DB954',
warning: '#FF9800',
error: '#E22134',
running: '#2E77D0',
pending: '#FFED51',
aborted: '#757575',
ok: '#71CF88',
warning: '#FFB84D',
error: '#F84C55',
running: '#3488E3',
pending: '#FEF071',
aborted: '#9E9E9E',
},
bursts: {
fontColor: '#FEFEFE',
@@ -107,7 +107,7 @@ export const darkTheme = createTheme({
},
border: '#E6E6E6',
textContrast: '#FFFFFF',
textVerySubtle: '#DDD',
textVerySubtle: '#ececec',
textSubtle: '#EEEEEE',
highlight: '#FFFBCC',
errorBackground: '#FFEBEE',
+11 -9
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-api-docs",
"version": "0.1.1-alpha.22",
"version": "0.1.1-alpha.23",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -20,10 +20,10 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/catalog-model": "^0.1.1-alpha.22",
"@backstage/core": "^0.1.1-alpha.22",
"@backstage/plugin-catalog": "^0.1.1-alpha.22",
"@backstage/theme": "^0.1.1-alpha.22",
"@backstage/catalog-model": "^0.1.1-alpha.23",
"@backstage/core": "^0.1.1-alpha.23",
"@backstage/plugin-catalog": "^0.1.1-alpha.23",
"@backstage/theme": "^0.1.1-alpha.23",
"@kyma-project/asyncapi-react": "^0.11.0",
"@material-icons/font": "^1.0.2",
"@material-ui/core": "^4.11.0",
@@ -39,9 +39,9 @@
"swagger-ui-react": "^3.31.1"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.22",
"@backstage/dev-utils": "^0.1.1-alpha.22",
"@backstage/test-utils": "^0.1.1-alpha.22",
"@backstage/cli": "^0.1.1-alpha.23",
"@backstage/dev-utils": "^0.1.1-alpha.23",
"@backstage/test-utils": "^0.1.1-alpha.23",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
"@testing-library/user-event": "^12.0.7",
@@ -49,7 +49,9 @@
"@types/node": "^12.0.0",
"@types/react": "^16.9",
"@types/swagger-ui-react": "^3.23.3",
"jest-fetch-mock": "^3.0.3"
"jest-fetch-mock": "^3.0.3",
"msw": "^0.20.5",
"node-fetch": "^2.6.1"
},
"files": [
"dist"
+5 -5
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-app-backend",
"version": "0.1.1-alpha.22",
"version": "0.1.1-alpha.23",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -20,8 +20,8 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/backend-common": "^0.1.1-alpha.22",
"@backstage/config-loader": "^0.1.1-alpha.22",
"@backstage/backend-common": "^0.1.1-alpha.23",
"@backstage/config-loader": "^0.1.1-alpha.23",
"@types/express": "^4.17.6",
"express": "^4.17.1",
"express-promise-router": "^3.0.3",
@@ -30,9 +30,9 @@
"yn": "^4.0.0"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.22",
"@backstage/cli": "^0.1.1-alpha.23",
"@types/supertest": "^2.0.8",
"msw": "^0.19.5",
"msw": "^0.20.5",
"supertest": "^4.0.2"
},
"files": [
+4 -4
View File
@@ -34,8 +34,8 @@ Follow this link, [Create new OAuth App](https://github.com/settings/application
1. Set Application Name to `backstage-dev` or something along those lines.
1. You can set the Homepage URL to whatever you want to.
1. The Authorization Callback URL should match the redirect URI set in Backstage.
1. Set this to `http://localhost:7000/auth/github` for local development.
1. Set this to `http://{APP_FQDN}:{APP_BACKEND_PORT}/auth/github` for non-local deployments.
1. Set this to `http://localhost:7000/api/auth/github` for local development.
1. Set this to `http://{APP_FQDN}:{APP_BACKEND_PORT}/api/auth/github` for non-local deployments.
```bash
export AUTH_GITHUB_CLIENT_ID=x
@@ -78,14 +78,14 @@ export AUTH_AUTH0_CLIENT_SECRET=x
#### Creating an Azure AD App Registration
An Azure AD App Registration is required to be able to sign in using Azure AD and the Microsoft Graph API.
An Azure AD App Registration is required to be able to sign in using Azure AD and the Microsoft Graph API.
Click [here](https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/RegisteredApps) to create a new one.
- Click on the `New Registration` button.
- Give the app a name. e.g. `backstage-dev`
- Select `Accounts in this organizational directory only` under supported account types.
- Enter the callback URL for your backstage backend instance:
- For local development, this is likely `http://localhost:7000/auth/microsoft/handler/frame`
- For local development, this is likely `http://localhost:7000/api/auth/microsoft/handler/frame`
- For non-local deployments, this will be `https://{APP_FQDN}:{APP_BACKEND_PORT}/auth/microsoft/handler/frame`
- Click `Register`.
+6 -5
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-auth-backend",
"version": "0.1.1-alpha.22",
"version": "0.1.1-alpha.23",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -20,8 +20,8 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/backend-common": "^0.1.1-alpha.22",
"@backstage/config": "^0.1.1-alpha.22",
"@backstage/backend-common": "^0.1.1-alpha.23",
"@backstage/config": "^0.1.1-alpha.23",
"@types/express": "^4.17.6",
"compression": "^1.7.4",
"cookie-parser": "^1.4.5",
@@ -49,7 +49,7 @@
"yn": "^4.0.0"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.22",
"@backstage/cli": "^0.1.1-alpha.23",
"@types/body-parser": "^1.19.0",
"@types/cookie-parser": "^1.4.2",
"@types/jwt-decode": "2.2.1",
@@ -58,7 +58,8 @@
"@types/passport-google-oauth20": "^2.0.3",
"@types/passport-microsoft": "^0.0.0",
"@types/passport-saml": "^1.1.2",
"jest-fetch-mock": "^3.0.3"
"jest-fetch-mock": "^3.0.3",
"msw": "^0.20.5"
},
"files": [
"dist",
@@ -18,4 +18,4 @@ fi
echo "Downloading and starting SAML-IdP"
export NPM_CONFIG_REGISTRY=https://registry.npmjs.org
exec npx saml-idp --acsUrl "http://localhost:7000/auth/saml/handler/frame" --audience "http://localhost:7000" --port 7001
exec npx saml-idp --acsUrl "http://localhost:7000/api/auth/saml/handler/frame" --audience "http://localhost:7000" --port 7001
+3 -1
View File
@@ -28,6 +28,7 @@ export interface RouterOptions {
logger: Logger;
database: Knex;
config: Config;
basePath?: string;
}
export async function createRouter(
@@ -38,7 +39,8 @@ export async function createRouter(
const appUrl = options.config.getString('app.baseUrl');
const backendUrl = options.config.getString('backend.baseUrl');
const authUrl = `${backendUrl}/auth`;
// TODO(Rugvip): Replace with service discovery of external URL
const authUrl = backendUrl + (options.basePath ?? '/api/auth');
const keyDurationSeconds = 3600;
+5 -5
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-backend",
"version": "0.1.1-alpha.22",
"version": "0.1.1-alpha.23",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -20,9 +20,9 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/backend-common": "^0.1.1-alpha.22",
"@backstage/catalog-model": "^0.1.1-alpha.22",
"@backstage/config": "^0.1.1-alpha.22",
"@backstage/backend-common": "^0.1.1-alpha.23",
"@backstage/catalog-model": "^0.1.1-alpha.23",
"@backstage/config": "^0.1.1-alpha.23",
"@types/express": "^4.17.6",
"express": "^4.17.1",
"express-promise-router": "^3.0.3",
@@ -40,7 +40,7 @@
"yup": "^0.29.1"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.22",
"@backstage/cli": "^0.1.1-alpha.23",
"@types/git-url-parse": "^9.0.0",
"@types/lodash": "^4.14.151",
"@types/node-fetch": "^2.5.7",
+10 -10
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-graphql",
"version": "0.1.1-alpha.22",
"version": "0.1.1-alpha.23",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -20,9 +20,9 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/catalog-model": "^0.1.1-alpha.22",
"@backstage/backend-common": "^0.1.1-alpha.22",
"@backstage/config": "^0.1.1-alpha.22",
"@backstage/backend-common": "^0.1.1-alpha.23",
"@backstage/catalog-model": "^0.1.1-alpha.23",
"@backstage/config": "^0.1.1-alpha.23",
"@graphql-modules/core": "^0.7.17",
"apollo-server": "^2.16.1",
"graphql": "^15.3.0",
@@ -32,16 +32,16 @@
"winston": "^3.2.1"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.22",
"@types/express": "^4.17.7",
"@types/supertest": "^2.0.8",
"@backstage/cli": "^0.1.1-alpha.23",
"@graphql-codegen/cli": "^1.17.7",
"@graphql-codegen/typescript": "^1.17.7",
"@graphql-codegen/typescript-resolvers": "^1.17.7",
"ts-node": "^8.10.2",
"@types/express": "^4.17.7",
"@types/supertest": "^2.0.8",
"eslint-plugin-graphql": "^4.0.0",
"msw": "^0.19.5",
"supertest": "^4.0.2"
"msw": "^0.20.5",
"supertest": "^4.0.2",
"ts-node": "^8.10.2"
},
"files": [
"dist"
@@ -22,6 +22,7 @@ import { Config } from '@backstage/config';
import { CatalogClient } from '../service/client';
import GraphQLJSON, { GraphQLJSONObject } from 'graphql-type-json';
import { Entity } from '@backstage/catalog-model';
import typeDefs from '../schema';
export interface ModuleOptions {
logger: Logger;
@@ -31,8 +32,6 @@ export interface ModuleOptions {
export async function createModule(
options: ModuleOptions,
): Promise<GraphQLModule> {
const { default: typeDefs } = require('../schema');
const catalogClient = new CatalogClient(
options.config.getString('backend.baseUrl'),
);
+11 -10
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog",
"version": "0.1.1-alpha.22",
"version": "0.1.1-alpha.23",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -21,11 +21,11 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/catalog-model": "^0.1.1-alpha.22",
"@backstage/core": "^0.1.1-alpha.22",
"@backstage/plugin-scaffolder": "^0.1.1-alpha.22",
"@backstage/plugin-techdocs": "^0.1.1-alpha.22",
"@backstage/theme": "^0.1.1-alpha.22",
"@backstage/catalog-model": "^0.1.1-alpha.23",
"@backstage/core": "^0.1.1-alpha.23",
"@backstage/plugin-scaffolder": "^0.1.1-alpha.23",
"@backstage/plugin-techdocs": "^0.1.1-alpha.23",
"@backstage/theme": "^0.1.1-alpha.23",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -41,9 +41,9 @@
"swr": "^0.3.0"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.22",
"@backstage/dev-utils": "^0.1.1-alpha.22",
"@backstage/test-utils": "^0.1.1-alpha.22",
"@backstage/cli": "^0.1.1-alpha.23",
"@backstage/dev-utils": "^0.1.1-alpha.23",
"@backstage/test-utils": "^0.1.1-alpha.23",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
"@testing-library/react-hooks": "^3.3.0",
@@ -53,7 +53,8 @@
"jest-fetch-mock": "^3.0.3",
"msw": "^0.20.5",
"react-test-renderer": "^16.13.1",
"whatwg-fetch": "^3.4.0"
"whatwg-fetch": "^3.4.0",
"node-fetch": "^2.6.1"
},
"files": [
"dist"
+10 -8
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-circleci",
"version": "0.1.1-alpha.22",
"version": "0.1.1-alpha.23",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -21,10 +21,10 @@
"postpack": "backstage-cli postpack"
},
"dependencies": {
"@backstage/catalog-model": "^0.1.1-alpha.22",
"@backstage/core": "^0.1.1-alpha.22",
"@backstage/plugin-catalog": "^0.1.1-alpha.22",
"@backstage/theme": "^0.1.1-alpha.22",
"@backstage/catalog-model": "^0.1.1-alpha.23",
"@backstage/core": "^0.1.1-alpha.23",
"@backstage/plugin-catalog": "^0.1.1-alpha.23",
"@backstage/theme": "^0.1.1-alpha.23",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -38,15 +38,17 @@
"react-use": "^15.3.3"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.22",
"@backstage/dev-utils": "^0.1.1-alpha.22",
"@backstage/cli": "^0.1.1-alpha.23",
"@backstage/dev-utils": "^0.1.1-alpha.23",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
"@testing-library/user-event": "^12.0.7",
"@types/jest": "^26.0.7",
"@types/node": "^12.0.0",
"@types/react-lazylog": "^4.5.0",
"jest-fetch-mock": "^3.0.3"
"jest-fetch-mock": "^3.0.3",
"msw": "^0.20.5",
"node-fetch": "^2.6.1"
},
"files": [
"dist"
+9 -7
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-explore",
"version": "0.1.1-alpha.22",
"version": "0.1.1-alpha.23",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -21,8 +21,8 @@
"start": "backstage-cli plugin:serve"
},
"dependencies": {
"@backstage/core": "^0.1.1-alpha.22",
"@backstage/theme": "^0.1.1-alpha.22",
"@backstage/core": "^0.1.1-alpha.23",
"@backstage/theme": "^0.1.1-alpha.23",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -33,15 +33,17 @@
"react-use": "^15.3.3"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.22",
"@backstage/dev-utils": "^0.1.1-alpha.22",
"@backstage/test-utils": "^0.1.1-alpha.22",
"@backstage/cli": "^0.1.1-alpha.23",
"@backstage/dev-utils": "^0.1.1-alpha.23",
"@backstage/test-utils": "^0.1.1-alpha.23",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
"@testing-library/user-event": "^12.0.7",
"@types/jest": "^26.0.7",
"@types/node": "^12.0.0",
"jest-fetch-mock": "^3.0.3"
"jest-fetch-mock": "^3.0.3",
"msw": "^0.20.5",
"node-fetch": "^2.6.1"
},
"files": [
"dist"
+8 -7
View File
@@ -1,10 +1,9 @@
{
"name": "@backstage/plugin-gcp-projects",
"version": "0.1.1-alpha.22",
"version": "0.1.1-alpha.23",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
"private": true,
"publishConfig": {
"access": "public",
"main": "dist/index.esm.js",
@@ -21,8 +20,8 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/core": "^0.1.1-alpha.22",
"@backstage/theme": "^0.1.1-alpha.22",
"@backstage/core": "^0.1.1-alpha.23",
"@backstage/theme": "^0.1.1-alpha.23",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -32,14 +31,16 @@
"react-use": "^15.3.3"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.22",
"@backstage/dev-utils": "^0.1.1-alpha.22",
"@backstage/cli": "^0.1.1-alpha.23",
"@backstage/dev-utils": "^0.1.1-alpha.23",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
"@testing-library/user-event": "^12.0.7",
"@types/jest": "^26.0.7",
"@types/node": "^12.0.0",
"jest-fetch-mock": "^3.0.3"
"jest-fetch-mock": "^3.0.3",
"msw": "^0.20.5",
"node-fetch": "^2.6.1"
},
"files": [
"dist"
+11 -9
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-github-actions",
"version": "0.1.1-alpha.22",
"version": "0.1.1-alpha.23",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -21,11 +21,11 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/catalog-model": "^0.1.1-alpha.22",
"@backstage/core": "^0.1.1-alpha.22",
"@backstage/core-api": "^0.1.1-alpha.22",
"@backstage/plugin-catalog": "^0.1.1-alpha.22",
"@backstage/theme": "^0.1.1-alpha.22",
"@backstage/catalog-model": "^0.1.1-alpha.23",
"@backstage/core": "^0.1.1-alpha.23",
"@backstage/core-api": "^0.1.1-alpha.23",
"@backstage/plugin-catalog": "^0.1.1-alpha.23",
"@backstage/theme": "^0.1.1-alpha.23",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -40,14 +40,16 @@
"react-use": "^15.3.3"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.22",
"@backstage/dev-utils": "^0.1.1-alpha.22",
"@backstage/cli": "^0.1.1-alpha.23",
"@backstage/dev-utils": "^0.1.1-alpha.23",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
"@testing-library/user-event": "^12.0.7",
"@types/jest": "^26.0.7",
"@types/node": "^12.0.0",
"jest-fetch-mock": "^3.0.3"
"jest-fetch-mock": "^3.0.3",
"msw": "^0.20.5",
"node-fetch": "^2.6.1"
},
"files": [
"dist"
+8 -6
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-gitops-profiles",
"version": "0.1.1-alpha.22",
"version": "0.1.1-alpha.23",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -21,8 +21,8 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/core": "^0.1.1-alpha.22",
"@backstage/theme": "^0.1.1-alpha.22",
"@backstage/core": "^0.1.1-alpha.23",
"@backstage/theme": "^0.1.1-alpha.23",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -32,14 +32,16 @@
"react-use": "^15.3.3"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.22",
"@backstage/dev-utils": "^0.1.1-alpha.22",
"@backstage/cli": "^0.1.1-alpha.23",
"@backstage/dev-utils": "^0.1.1-alpha.23",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
"@testing-library/user-event": "^12.0.7",
"@types/jest": "^26.0.7",
"@types/node": "^12.0.0",
"jest-fetch-mock": "^3.0.3"
"jest-fetch-mock": "^3.0.3",
"msw": "^0.20.5",
"node-fetch": "^2.6.1"
},
"files": [
"dist"
+9 -7
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-graphiql",
"description": "Backstage plugin for browsing GraphQL APIs",
"version": "0.1.1-alpha.22",
"version": "0.1.1-alpha.23",
"private": false,
"publishConfig": {
"access": "public",
@@ -31,8 +31,8 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/core": "^0.1.1-alpha.22",
"@backstage/theme": "^0.1.1-alpha.22",
"@backstage/core": "^0.1.1-alpha.23",
"@backstage/theme": "^0.1.1-alpha.23",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -43,9 +43,9 @@
"react-use": "^15.3.3"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.22",
"@backstage/dev-utils": "^0.1.1-alpha.22",
"@backstage/test-utils": "^0.1.1-alpha.22",
"@backstage/cli": "^0.1.1-alpha.23",
"@backstage/dev-utils": "^0.1.1-alpha.23",
"@backstage/test-utils": "^0.1.1-alpha.23",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
"@testing-library/user-event": "^12.0.7",
@@ -53,7 +53,9 @@
"@types/jest": "^26.0.7",
"@types/node": "^12.0.0",
"jest-fetch-mock": "^3.0.3",
"react-router-dom": "6.0.0-beta.0"
"react-router-dom": "6.0.0-beta.0",
"msw": "^0.20.5",
"node-fetch": "^2.6.1"
},
"files": [
"dist"
+5 -5
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-graphql-backend",
"version": "0.1.1-alpha.22",
"version": "0.1.1-alpha.23",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -19,10 +19,10 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/config": "^0.1.1-alpha.22",
"@backstage/plugin-catalog-graphql": "^0.1.1-alpha.22",
"@backstage/backend-common": "^0.1.1-alpha.23",
"@backstage/config": "^0.1.1-alpha.23",
"@backstage/plugin-catalog-graphql": "^0.1.1-alpha.23",
"@graphql-modules/core": "^0.7.17",
"@backstage/backend-common": "^0.1.1-alpha.22",
"@types/express": "^4.17.6",
"apollo-server": "^2.16.1",
"apollo-server-express": "^2.16.1",
@@ -36,7 +36,7 @@
"yn": "^4.0.0"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.22",
"@backstage/cli": "^0.1.1-alpha.23",
"@types/supertest": "^2.0.8",
"eslint-plugin-graphql": "^4.0.0",
"msw": "^0.20.5",
+5 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-identity-backend",
"version": "0.1.1-alpha.22",
"version": "0.1.1-alpha.23",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -20,7 +20,7 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/backend-common": "^0.1.1-alpha.22",
"@backstage/backend-common": "^0.1.1-alpha.23",
"@types/express": "^4.17.6",
"compression": "^1.7.4",
"cors": "^2.8.5",
@@ -33,8 +33,9 @@
"yn": "^4.0.0"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.22",
"jest-fetch-mock": "^3.0.3"
"@backstage/cli": "^0.1.1-alpha.23",
"jest-fetch-mock": "^3.0.3",
"msw": "^0.20.5"
},
"files": [
"dist"
+10 -8
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-jenkins",
"version": "0.1.1-alpha.22",
"version": "0.1.1-alpha.23",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -21,10 +21,10 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/catalog-model": "^0.1.1-alpha.22",
"@backstage/core": "^0.1.1-alpha.22",
"@backstage/plugin-catalog": "^0.1.1-alpha.22",
"@backstage/theme": "^0.1.1-alpha.22",
"@backstage/catalog-model": "^0.1.1-alpha.23",
"@backstage/core": "^0.1.1-alpha.23",
"@backstage/plugin-catalog": "^0.1.1-alpha.23",
"@backstage/theme": "^0.1.1-alpha.23",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -36,15 +36,17 @@
"react-use": "^15.3.3"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.22",
"@backstage/dev-utils": "^0.1.1-alpha.22",
"@backstage/cli": "^0.1.1-alpha.23",
"@backstage/dev-utils": "^0.1.1-alpha.23",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
"@testing-library/user-event": "^12.0.7",
"@types/jest": "^26.0.7",
"@types/node": "^12.0.0",
"@types/testing-library__jest-dom": "^5.9.1",
"jest-fetch-mock": "^3.0.3"
"jest-fetch-mock": "^3.0.3",
"msw": "^0.20.5",
"node-fetch": "^2.6.1"
},
"files": [
"dist"

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