Merge branch 'master' of github.com:backstage/backstage into RoadieHQ-add-export-for-in-memory-broker

This commit is contained in:
Brian Fletcher
2023-03-08 09:41:46 +00:00
507 changed files with 8245 additions and 1143 deletions
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder-backend': patch
---
Make identity valid if subject of token is a backstage server-2-server auth token
+7
View File
@@ -0,0 +1,7 @@
---
'@backstage/core-components': patch
---
Button labels in the sidebar (previously displayed in uppercase) will be displayed in the case that is provided without any transformations.
For example, a sidebar button with the label "Search" will appear as Search, "search" will appear as search, "SEARCH" will appear as SEARCH etc.
This can potentially affect any overriding styles previously applied to change the appearance of Button labels in the Sidebar.
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/plugin-scaffolder-react': patch
'@backstage/plugin-scaffolder': patch
---
Make scaffolder adhere to page themes by using page `fontColor` consistently. If your theme overwrites template list or card headers, review those styles.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/create-app': patch
---
Bumped create-app version.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder': minor
---
Update `EntityPicker` to use the fully qualified entity ref instead of the humanized version.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-search-react': patch
---
Correctly disable next button in `SearchPagination` on last page
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-backend-module-puppetdb': minor
---
Initial version of the plugin.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/backend-tasks': minor
---
add functionality to get descriptions from the scheduler for triggering
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-backend-module-msgraph': patch
---
Fixed msgraph catalog backend to use user.select option when fetching user from AzureAD
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder-backend': patch
---
catalog write action should allow any shape of object
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-kubernetes': patch
---
GitLab can now be used as an `oidcTokenProvider` for Kubernetes clusters
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-adr-backend': patch
---
Support MADR v3 format
+19
View File
@@ -0,0 +1,19 @@
---
'@backstage/backend-common': patch
---
Adds config option `backend.database.role` to set ownership for newly created schemas and tables in Postgres
The example config below connects to the database as user `v-backstage-123` but sets the ownership of
the create schemas and tables to `backstage`
```yaml
backend:
database:
client: pg
pluginDivisionMode: schema
role: backstage
connection:
user: v-backstage-123
...
```
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder-backend-module-cookiecutter': patch
---
allow container runner to be undefined in cookiecutter plugin
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-api-docs': patch
---
Fix dark theme Swagger's clear button font color.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-linguist-backend': patch
---
Added support for linguist-js options using the linguistJSOptions in the plugin, the available config can be found [here](https://www.npmjs.com/package/linguist-js#API).
+29 -2
View File
@@ -210,44 +210,63 @@
"@backstage/plugin-vault-backend": "0.2.8",
"@backstage/plugin-xcmetrics": "0.2.35",
"@backstage/plugin-octopus-deploy": "0.0.0",
"@backstage/plugin-stackstorm": "0.0.0"
"@backstage/plugin-stackstorm": "0.0.0",
"@backstage/plugin-catalog-backend-module-puppetdb": "0.0.1"
},
"changesets": [
"afraid-trees-stare",
"backend-token-authentication",
"breezy-bees-care",
"bright-kids-raise",
"clean-lemons-jump",
"clean-planes-join",
"clever-dogs-cheat",
"cool-clocks-prove",
"cool-feet-speak",
"create-app-1676993958",
"create-app-1678209629",
"curvy-pets-hang",
"eight-radios-bake",
"eighty-chairs-roll",
"eighty-geese-return",
"empty-books-occur",
"famous-sloths-tie",
"fifty-beds-dress",
"flat-kids-occur",
"flat-peaches-act",
"forty-snails-clap",
"four-lizards-grin",
"fresh-hairs-switch",
"fuzzy-trains-search",
"gentle-bears-love",
"gentle-pears-clean",
"great-trains-jam",
"grumpy-bikes-begin",
"happy-boxes-arrive",
"honest-clouds-shout",
"honest-nails-bake",
"khaki-poems-run",
"light-bees-end",
"light-sheep-trade",
"long-nails-pump",
"long-wolves-drive",
"lovely-tigers-look",
"mean-toys-itch",
"metal-suns-rhyme",
"mighty-games-turn",
"mighty-years-own",
"new-jobs-deny",
"nice-planets-wave",
"nine-bikes-applaud",
"ninety-turtles-wait",
"odd-fireants-bathe",
"odd-oranges-tease",
"odd-waves-rescue",
"old-foxes-shave",
"olive-berries-poke",
"orange-experts-hug",
"perfect-mayflies-greet",
"pink-dolls-unite",
"polite-chicken-do",
"polite-falcons-jump",
"polite-wombats-smash",
@@ -267,8 +286,13 @@
"renovate-fb85ae7",
"rich-clocks-approve",
"rich-wombats-rescue",
"rotten-cats-matter",
"rotten-panthers-share",
"selfish-hats-wait",
"short-mayflies-fix",
"silent-dryers-end",
"silly-suits-run",
"silver-bikes-breathe",
"silver-lies-rest",
"six-melons-rhyme",
"slimy-lobsters-kneel",
@@ -282,6 +306,7 @@
"tall-hats-talk",
"ten-tigers-marry",
"thin-candles-wait",
"tiny-llamas-jump",
"tricky-jars-film",
"twelve-cars-push",
"twenty-jeans-speak",
@@ -292,9 +317,11 @@
"wicked-lions-repeat",
"wicked-spoons-call",
"wild-ads-pull",
"wild-bulldogs-suffer",
"witty-geckos-design",
"yellow-bananas-yawn",
"young-schools-double",
"young-scissors-cough"
"young-scissors-cough",
"young-walls-prove"
]
}
+19
View File
@@ -0,0 +1,19 @@
---
'@backstage/core-app-api': minor
---
`OAuth2` now gets ID tokens from a session with the `openid` scope explicitly
requested.
This should not be considered a breaking change, because spec-compliant OIDC
providers will already be returning ID tokens if and only if the `openid` scope
is granted.
This change makes the dependence explicit, and removes the burden on
OAuth2-based providers which require an ID token (e.g. this is done by various
default [auth
handlers](https://backstage.io/docs/auth/identity-resolver/#authhandler)) to add
`openid` to their default scopes. _That_ could carry another indirect benefit:
by removing `openid` from the default scopes for a provider, grants for
resource-specific access tokens can avoid requesting excess ID token-related
scopes.
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/backend-common': patch
'@backstage/plugin-catalog-backend-module-aws': patch
---
AwsS3UrlReader upgraded to use aws-sdk v3
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-org': patch
---
Add styling to the `MembersListCard` and `ComponentsGrid` to handle overflow text.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder': patch
---
Fix the scaffolder validator for arrays when the item is a field in the object
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog': minor
---
allow entity switch to render all cases that match the condition
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/core-components': patch
---
Use media queries to change layout instead of `isMobile` prop in `BackstagePage` component
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-backend-module-gitlab': patch
---
filter gitlab groups by prefix
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/core-plugin-api': minor
---
The GitLab auth provider can now be used to get OpenID tokens.
+1
View File
@@ -30,6 +30,7 @@ yarn.lock @backstage/maintainers @back
/plugins/catalog-backend-module-aws @backstage/maintainers @backstage/catalog-core @pjungermann
/plugins/catalog-backend-module-bitbucket-cloud @backstage/maintainers @backstage/catalog-core @pjungermann
/plugins/catalog-backend-module-msgraph @backstage/maintainers @backstage/catalog-core @pjungermann
/plugins/catalog-backend-module-puppetdb @backstage/maintainers @backstage/catalog-core @tdabasinskas
/plugins/catalog-graph @backstage/maintainers @backstage/catalog-core @backstage/sda-se-reviewers
/plugins/circleci @backstage/maintainers @adamdmharvey
/plugins/cloudbuild @backstage/maintainers @trivago/ebarrios
@@ -55,6 +55,7 @@ jobs:
context: './example-app'
file: ./example-app/packages/backend/Dockerfile
push: true
platforms: linux/amd64,linux/arm64
tags: |
ghcr.io/${{ github.repository_owner }}/backstage:latest
ghcr.io/${{ github.repository_owner }}/backstage:${{ github.event.client_payload.version }}
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
env:
CI: true
NODE_OPTIONS: --max-old-space-size=7168
NODE_OPTIONS: --max-old-space-size=8192
DOCUSAURUS_SSR_CONCURRENCY: 5
steps:
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
env:
CI: true
NODE_OPTIONS: --max-old-space-size=4096
NODE_OPTIONS: --max-old-space-size=8192
DOCUSAURUS_SSR_CONCURRENCY: 5
steps:
+5 -1
View File
@@ -21,7 +21,7 @@ _You can do this by using the [Adopter form](https://info.backstage.spotify.com/
| [Fiverr](https://www.fiverr.com) | [@nirga](https://github.com/nirga) | Unifying separate tools that developers are using today (i.e. monitoring, dead letter queues management, etc.) into a single platform. |
| [Zalando SE](https://www.zalando.de) | [@leviferreira](https://github.com/leviferreira) | Building V2 of the Internal Development Portal. |
| [LegalZoom](https://legalzoom.com) | [@backjo](https://github.com/backjo) | Developer portal - hub for all engineering projects and metadata. |
| [Expedia Group](https://www.expediagroup.com) | [@gman0922](https://github.com/gman0922), [Sheena Sharma](mailto:shesharma@expediagroup.com), [Alekhya Karuturi](mailto:akaruturi@expediagroup.com) | EG Developer Front Door |
| [Expedia Group](https://www.expediagroup.com) | [Guillermo Manzo](mailto:gmanzo@expediagroup.com), [Sheena Sharma](mailto:shesharma@expediagroup.com), [Alekhya Karuturi](mailto:akaruturi@expediagroup.com) | EG Developer Front Door |
| [Paddle.com](https://paddle.com) | [Ioannis Georgoulas](https://github.com/geototti21) | Developer portal (Tech Docs, Service Catalog, Internal Tooling), we use vanilla Backstage FE and custom BE implementation in Go |
| [Acast.com](https://acast.com) | [Olle Lundberg](https://github.com/lndbrg) | Developer portal with tech docs, service catalog and a bunch of other internal tooling |
| [Lunar](https://lunar.app) | [Bjørn Hald Sørensen](https://github.com/crevil) | Internal developer portal for service overview and insights, API documentation, technical guides, onboarding guides and RFC's. |
@@ -230,3 +230,7 @@ _You can do this by using the [Adopter form](https://info.backstage.spotify.com/
| [OVO Energy](https://www.ovoenergy.com/) | [Michael Wizner](https://github.com/mwz), [Dan Laird](https://github.com/dlaird-ovo), [Samantha Betts](https://github.com/sammbetts) | Developer Experience Tool with an aim to to improve processes, boost productivity, finding of information/docs and building tech engagement throughout the business.
| [MusicTribe](https://careers.musictribe.com/) | [Alex Ford](mailto:alex.j.ford@gmail.com), [Tiago Barbosa](https://github.com/t1agob) | We are starting to use Backstage as a developer portal to share API specifications and documentation, to quickly onboard new projects with the software templates, and to help developers discover software through the catalog.
| [Cazoo](https://www.cazoo.co.uk/) | [Abz Mungul](https://www.linkedin.com/in/abzmungul/), [Scott Edwards](https://www.linkedin.com/in/scott-edwards-tech/) | We're assessing Backstage as our developer platform at Cazoo with a focus on reducing cognitive load for our engineers. We're currently aiming for 3 outcomes: creating visibility into service ownership across teams, improving the discoverability of event schemas and relationships, and improving the discoverability of technical documentation and best practices. |
| [Gumtree](https://www.gumtree.com.au) | [Kumar Gaurav](https://www.linkedin.com/in/kumargaurav517) | We are starting to use it as a single place to find all component information in a distributed architecture. |
| [N26](https://n26.com) | [Alexei Timofti](https://www.linkedin.com/in/alexeitimofti) | We use Backstage for our service catalog and are actively looking into adopting other plugins like TechDocs, TechInsights and Software Templates. |
| [The LEGO Group](https://www.lego.com) | [Waqas Ali](https://www.linkedin.com/in/waqasali47) | We are building our internal develper portal on top of Backstage. |
+1 -59
View File
@@ -8,65 +8,7 @@ Contributions are welcome, and they are greatly appreciated! Every little bit he
Backstage is released under the Apache 2.0 License, and original creations contributed to this repo are accepted under the same license.
## Types of Contributions
### Report bugs
No one likes bugs. Report bugs as an issue [here](https://github.com/backstage/backstage/issues/new?template=bug_template.md).
### Fix bugs or build new features
Look through the GitHub issues for [bugs](https://github.com/backstage/backstage/labels/bug), [good first issues](https://github.com/backstage/backstage/labels/good%20first%20issue) or [help wanted](https://github.com/backstage/backstage/labels/help%20wanted).
### Build a plugin
The value of Backstage grows with every new plugin that gets added. Wouldn't it be fantastic if there was a plugin for every infrastructure project out there? We think so. And we would love your help.
A great reference example of a plugin can be found on [our blog](https://backstage.io/blog/2020/04/06/lighthouse-plugin) (thanks [@fastfrwrd](https://github.com/fastfrwrd)!)
What kind of plugins should/could be created? Some inspiration from the 120+ plugins that we have developed inside Spotify can be found [here](https://backstage.io/demos), but we will keep a running list of suggestions labeled with [[plugin]](https://github.com/backstage/backstage/labels/plugin).
### Suggesting a plugin
If you start developing a plugin that you aim to release as open source, we suggest that you create a [new Issue](https://github.com/backstage/backstage/issues/new?labels=plugin&template=plugin_template.md&title=%5BPlugin%5D+THE+PLUGIN+NAME). This helps the community know what plugins are in development.
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/backstage/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 or improve the website
The current documentation is very limited. Help us make the `/docs` folder come alive.
Docs are published to [backstage.io/docs](https://backstage.io/docs). If you
contribute to the documentation, you might want to preview your changes before
submitting them. You'll find the website sources under [/microsite](/microsite)
with instructions for building and locally serving the website in the
[README](/microsite#readme).
### Contribute to Storybook
We think the best way to ensure different plugins provide a consistent experience is through a solid set of reusable UI/UX components. Backstage uses [Storybook](http://backstage.io/storybook).
Either help us [create new components](https://github.com/backstage/backstage/labels/help%20wanted) or improve stories for the existing ones (look for files with `*.stories.tsx`).
### Submit Feedback
The best way to send feedback is to file [an issue](https://github.com/backstage/backstage/issues).
If you are proposing a feature:
- Explain in detail how it would work.
- Keep the scope as narrow as possible, to make it easier to implement.
- Use appropriate labels
- Remember that this is a volunteer-driven project, and that contributions
are welcome :)
### Add your company to `ADOPTERS`
Have you started using Backstage? Adding your company to [ADOPTERS](ADOPTERS.md) really helps the project, you can do this by filling out this [Adopter form](https://form.typeform.com/to/zcOaKikB).
You can find out more about the types of contributions over at [getting involved](https://backstage.io/docs/getting-started/getting-involved)!
## Get Started!
+6 -6
View File
@@ -16,13 +16,13 @@
Backstage unifies all your infrastructure tooling, services, and documentation to create a streamlined development environment from end to end.
![software-catalog](https://backstage.io/blog/assets/6/header.png)
![software-catalog](docs/assets/header.png)
Out of the box, Backstage includes:
- [Backstage Software Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview) for managing all your software (microservices, libraries, data pipelines, websites, ML models, etc.)
- [Backstage Software Templates](https://backstage.io/docs/features/software-templates/software-templates-index) for quickly spinning up new projects and standardizing your tooling with your organizations best practices
- [Backstage TechDocs](https://backstage.io/docs/features/techdocs/techdocs-overview) for making it easy to create, maintain, find, and use technical documentation, using a "docs like code" approach
- [Backstage Software Catalog](https://backstage.io/docs/features/software-catalog/) for managing all your software (microservices, libraries, data pipelines, websites, ML models, etc.)
- [Backstage Software Templates](https://backstage.io/docs/features/software-templates/) for quickly spinning up new projects and standardizing your tooling with your organizations best practices
- [Backstage TechDocs](https://backstage.io/docs/features/techdocs/) for making it easy to create, maintain, find, and use technical documentation, using a "docs like code" approach
- Plus, a growing ecosystem of [open source plugins](https://github.com/backstage/backstage/tree/master/plugins) that further expand Backstages customizability and functionality
Backstage was created by Spotify but is now hosted by the [Cloud Native Computing Foundation (CNCF)](https://www.cncf.io) as an Incubation level project. Read the announcement [here](https://backstage.io/blog/2022/03/16/backstage-turns-two#out-of-the-sandbox-and-into-incubation).
@@ -38,8 +38,8 @@ Check out [the documentation](https://backstage.io/docs/getting-started) on how
## Documentation
- [Main documentation](https://backstage.io/docs)
- [Software Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview)
- [Architecture](https://backstage.io/docs/overview/architecture-overview) ([Decisions](https://backstage.io/docs/architecture-decisions/adrs-overview))
- [Software Catalog](https://backstage.io/docs/features/software-catalog/)
- [Architecture](https://backstage.io/docs/overview/architecture-overview) ([Decisions](https://backstage.io/docs/architecture-decisions/))
- [Designing for Backstage](https://backstage.io/docs/dls/design)
- [Storybook - UI components](https://backstage.io/storybook)
+1 -1
View File
@@ -1,6 +1,6 @@
# Catalog Contrib
This directory contains various community contributions related to [the Backstage catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview).
This directory contains various community contributions related to [the Backstage catalog](https://backstage.io/docs/features/software-catalog/).
There is no guarantee of correctness or fitness of purpose of these
contributions, but we hope that they are helpful to someone!
+1 -1
View File
@@ -176,7 +176,7 @@ By far, our most-used plugin is our TechDocs plugin, which we use for creating
technical documentation. Our philosophy at Spotify is to treat "docs like code",
where you write documentation using the same workflow as you write your code.
This makes it easier to create, find, and update documentation.
[TechDocs is now open source.](https://backstage.io/docs/features/techdocs/techdocs-overview)
[TechDocs is now open source.](https://backstage.io/docs/features/techdocs/)
(See also:
"[Will Spotify's internal plugins be open sourced, too?](#will-spotifys-internal-plugins-be-open-sourced-too)"
above)
+5 -1
View File
@@ -18,7 +18,11 @@ Settings for local development:
- Name: Backstage (or your custom app name)
- Redirect URI: `http://localhost:7007/api/auth/gitlab/handler/frame`
- Scopes: `read_api` and `read_user`
- Scopes: `read_user` for sign-in. If you also need ID tokens (e.g. if you are
using the Kubernetes plugin and have clusters with `authProvider: oidc` and
[`oidcTokenProvider:
gitlab`](https://backstage.io/docs/features/kubernetes/configuration/#clustersoidctokenprovider-optional)),
add the `openid` scope.
## Configuration
@@ -168,7 +168,61 @@ Whenever you want to allow modules to configure your plugin dynamically, for
example in the way that the catalog backend lets catalog modules inject
additional entity providers, you can use the extension points mechanism. This is
described in detail with code examples in [the extension points architecture
article](../architecture/05-extension-points.md).
article](../architecture/05-extension-points.md), while the following is a more
slim example of how to implement an extension point for a plugin:
```ts
import { createExtensionPoint } from '@backstage/backend-plugin-api';
// This is the extension point interface, which is how modules interact with your plugin.
export interface ExamplesExtensionPoint {
addExample(example: Example): void;
}
// This is the extension point reference that encapsulates the above interface.
export const examplesExtensionPoint =
createExtensionPoint<ExamplesExtensionPoint>({
id: 'example.examples',
});
// This is the implementation of the extension point, which is internal to your plugin.
class ExamplesExtension implements ExamplesExtensionPoint {
#examples: Example[] = [];
addExample(example: Example): void {
this.#examples.push(example);
}
// Note that this method is internal to this implementation
getRegisteredExamples() {
return this.#examples;
}
}
// The following shows how your plugin would register the extension point
// and use the features that other modules have registered.
export const examplePlugin = createBackendPlugin({
pluginId: 'example',
register(env) {
const examplesExtensions = new ExamplesExtension();
env.registerExtensionPoint(examplesExtensionPoint, examplesExtensions);
env.registerInit({
deps: { logger: coreServices.logger },
async init({ logger }) {
// We can access `examplesExtension` directly, giving us access to the internal interface.
const examples = examplesExtension.getRegisteredExamples();
logger.info(`The following examples have been registered: ${examples}`);
},
});
},
});
```
This is a very common type of extension point, one where modules are given the opportunity to register features to be used by the plugin. In this case modules are able to register examples that are then used by our examples plugin.
Note that the public extension point interface only needs to expose the `addExample` method, while the `getRegisteredExamples()` method is kept internal to the plugin.
### Configuration
+3 -2
View File
@@ -160,8 +160,9 @@ auth:
audience: ${AUTH_OKTA_AUDIENCE}
```
The following values are supported out-of-the-box by the frontend: `google`, `microsoft`,
`okta`, `onelogin`.
The following values are supported out-of-the-box by the frontend: `gitlab` (the
application whose `clientId` is used by the auth provider should be granted the
`openid` scope), `google`, `microsoft`, `okta`, `onelogin`.
Take note that `oidcTokenProvider` is just the issuer for the token, you can use any
of these with an OIDC enabled cluster, like using `microsoft` as the issuer for a EKS
+1 -1
View File
@@ -14,7 +14,7 @@ websites, libraries, data pipelines, etc). The catalog is built around the
concept of [metadata YAML files](descriptor-format.md) stored together with the
code, which are then harvested and visualized in Backstage.
![software-catalog](https://backstage.io/blog/assets/6/header.png)
![software-catalog](../../assets/header.png)
## How it works
@@ -97,7 +97,7 @@ References for `createScaffolderFieldExtension` have an `/alpha` version of `cre
```diff
-import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder';
+import { createNextScaffolderFieldExtension } from '@backstage/plugin-scaffolder/alpha';
+import { createNextScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react/alpha';
export const EntityNamePickerFieldExtension = scaffolderPlugin.provide(
- createScaffolderFieldExtension({
+1 -1
View File
@@ -8,7 +8,7 @@ description: TechDocs CLI - a utility command line interface for managing TechDo
Utility command line interface for managing TechDocs sites in
[Backstage](https://github.com/backstage/backstage).
https://backstage.io/docs/features/techdocs/techdocs-overview
https://backstage.io/docs/features/techdocs/
## Features
+1 -1
View File
@@ -433,7 +433,7 @@ const app = createApp({
## How to add the documentation setup to your software templates
[Software Templates](https://backstage.io/docs/features/software-templates/software-templates-index)
[Software Templates](https://backstage.io/docs/features/software-templates/)
in Backstage is a tool that can help your users to create new components out of
already configured templates. It comes with a set of default templates to use,
but you can also
+3 -3
View File
@@ -71,9 +71,9 @@ app
good starting point for you to get to know Backstage.
- **packages/backend/**: We include a backend that helps power features such as
[Authentication](https://backstage.io/docs/auth/),
[Software Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview),
[Software Templates](https://backstage.io/docs/features/software-templates/software-templates-index)
and [TechDocs](https://backstage.io/docs/features/techdocs/techdocs-overview)
[Software Catalog](https://backstage.io/docs/features/software-catalog/),
[Software Templates](https://backstage.io/docs/features/software-templates/)
and [TechDocs](https://backstage.io/docs/features/techdocs/)
amongst other things.
### Troubleshooting
+66
View File
@@ -0,0 +1,66 @@
---
id: getting-involved
title: Getting Involved
# prettier-ignore
description: How can you help us build Backstage? We welcome contributions of all kinds, from documentation to code to design.
---
We encourage contributions of all kinds, from documentation to code to design, here's some ideas on how you can help us build and improve Backstage!
### Report bugs
No one likes bugs. Report bugs as an issue [here](https://github.com/backstage/backstage/issues/new?template=bug_template.md).
### Fix bugs or build new features
Look through the GitHub issues for [bugs](https://github.com/backstage/backstage/labels/bug), [good first issues](https://github.com/backstage/backstage/labels/good%20first%20issue) or [help wanted](https://github.com/backstage/backstage/labels/help%20wanted).
### Build a plugin
The value of Backstage grows with every new plugin that gets added. Wouldn't it be fantastic if there was a plugin for every infrastructure project out there? We think so. And we would love your help.
A great reference example of a plugin can be found on [our blog](https://backstage.io/blog/2020/04/06/lighthouse-plugin) (thanks [@fastfrwrd](https://github.com/fastfrwrd)!)
What kind of plugins should/could be created? Some inspiration from the 120+ plugins that we have developed inside Spotify can be found [here](https://backstage.io/demos), but we will keep a running list of suggestions labeled with [[plugin]](https://github.com/backstage/backstage/labels/plugin).
### Suggesting a plugin
If you start developing a plugin that you aim to release as open source, we suggest that you create a [new Issue](https://github.com/backstage/backstage/issues/new?labels=plugin&template=plugin_template.md&title=%5BPlugin%5D+THE+PLUGIN+NAME). This helps the community know what plugins are in development.
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/backstage/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 or improve the website
The current documentation is very limited. Help us make the `/docs` folder come alive.
Docs are published to [backstage.io/docs](https://backstage.io/docs). If you
contribute to the documentation, you might want to preview your changes before
submitting them. You'll find the website sources under [/microsite](https://github.com/backstage/backstage/tree/master/microsite)
with instructions for building and locally serving the website in the
[README](/microsite#readme).
### Contribute to Storybook
We think the best way to ensure different plugins provide a consistent experience is through a solid set of reusable UI/UX components. Backstage uses [Storybook](http://backstage.io/storybook).
Either help us [create new components](https://github.com/backstage/backstage/labels/help%20wanted) or improve stories for the existing ones (look for files with `*.stories.tsx`).
### Submit feedback
The best way to send feedback is to file [an issue](https://github.com/backstage/backstage/issues).
If you are proposing a feature:
- Explain in detail how it would work.
- Keep the scope as narrow as possible, to make it easier to implement.
- Use appropriate labels
- Remember that this is a volunteer-driven project, and that contributions
are welcome :)
### Add your company to `ADOPTERS`
Have you started using Backstage? Adding your company to [ADOPTERS](https://github.com/backstage/backstage/blob/master/ADOPTERS.md) really helps the project, you can do this by filling out this [Adopter form](https://form.typeform.com/to/zcOaKikB).
+6
View File
@@ -31,4 +31,10 @@ catalog:
yourProviderId:
host: gitlab.com
orgEnabled: true
group: org/teams # Optional. Must not end with slash. Accepts only groups under the provided path (which will be stripped)
groupPattern: '[\s\S]*' # Optional. Filters found groups based on provided pattern. Defaults to `[\s\S]*`, which means to not filter anything
```
When the `group` parameter is provided, the corresponding path prefix will be stripped out from each matching group
when computing the unique entity name. e.g. If `group` is `org/teams`, the name for `org/teams/avengers/gotg` will
be `avengers-gotg`.
+1 -1
View File
@@ -671,7 +671,7 @@ TypeScript support is currently handled though the `typesVersions` field, as the
To add subpath exports to an existing package, simply add the desired `"exports"` fields and then run the following command:
```bash
yarn backstage-cli package migrate package-exports
yarn backstage-cli migrate package-exports
```
## Experimental Type Build
@@ -146,7 +146,7 @@ Look at [DefaultTechDocsCollatorFactory test](https://github.com/backstage/backs
#### 6. Make your plugins collator discoverable for others
If you want to make your collator discoverable for other adopters, add it to the list of [plugins integrated to search](https://backstage.io/docs/features/search/search-overview#plugins-integrated-with-backstage-search).
If you want to make your collator discoverable for other adopters, add it to the list of [plugins integrated to search](https://backstage.io/docs/features/search/#plugins-integrated-with-backstage-search).
## Building a search experience into your plugin
File diff suppressed because it is too large Load Diff
+17 -1
View File
@@ -104,6 +104,22 @@ module.exports = {
from: '/docs',
to: '/docs/overview/what-is-backstage',
},
{
from: '/docs/features/software-catalog/software-catalog-overview',
to: '/docs/features/software-catalog/',
},
{
from: '/docs/features/software-templates/software-templates-index',
to: '/docs/features/software-templates/',
},
{
from: '/docs/features/techdocs/techdocs-overview',
to: '/docs/features/techdocs/',
},
{
from: '/docs/features/search/search-overview',
to: '/docs/features/search/',
},
],
},
],
@@ -181,7 +197,7 @@ module.exports = {
},
{
label: 'Software Catalog',
to: 'docs/features/software-catalog/software-catalog-overview',
to: 'docs/features/software-catalog/',
},
{
label: 'Create a Plugin',
+1
View File
@@ -46,6 +46,7 @@
"getting-started/keeping-backstage-updated",
"getting-started/concepts",
"getting-started/contributors",
"getting-started/getting-involved",
"getting-started/project-structure"
],
"Local Development": [
+3 -3
View File
@@ -188,7 +188,7 @@ const HomePage = () => {
<h1>Learn more about the software catalog</h1>
<Link
to="https://backstage.io/docs/features/software-catalog/software-catalog-overview"
to="https://backstage.io/docs/features/software-catalog/"
className="button button--secondary"
>
READ
@@ -312,7 +312,7 @@ const HomePage = () => {
<h1>Learn more about TechDocs</h1>
<Link
to="https://backstage.io/docs/features/techdocs/techdocs-overview"
to="https://backstage.io/docs/features/techdocs/"
className="button button--secondary"
>
DOCS
@@ -373,7 +373,7 @@ const HomePage = () => {
<h1>Learn more about Backstage Search</h1>
<Link
to="https://backstage.io/docs/features/search/search-overview"
to="https://backstage.io/docs/features/search/"
className="button button--secondary"
>
READ
@@ -0,0 +1,27 @@
import React from 'react';
import nominateStyles from './nominate.module.scss';
import Layout from '@theme/Layout';
import clsx from 'clsx';
import { BannerSection } from '@site/src/components/bannerSection/bannerSection';
import { BannerSectionGrid } from '../../components/bannerSection/bannerSectionGrid';
import { ContentBlock } from '@site/src/components/contentBlock/contentBlock';
const Nominate = () => {
return (
<Layout>
<div className={clsx(nominateStyles.nominatePage)}>
<BannerSection greyBackground>
<ContentBlock title={<h1>Contributor Spotlight nomination</h1>}>
<iframe
src="https://docs.google.com/forms/d/e/1FAIpQLSdiZ28O7vwHo6NrwirEzGSbuVyBANSv7ItHqRlgVvSz3Z5xqQ/viewform?embedded=true"
allowFullScreen
></iframe>
</ContentBlock>
</BannerSection>
</div>
</Layout>
);
};
export default Nominate;
@@ -0,0 +1,32 @@
.nominatePage {
font-size: 1.25rem;
&,
& > section,
& > section > div {
flex: 1;
display: flex;
flex-direction: column;
}
& > section > div > div {
flex: 1;
grid-template-rows: auto 1fr;
}
h1 {
font-size: 54px;
line-height: 56px;
word-break: break-word;
}
p {
text-align: justify;
}
iframe {
width: 100%;
height: 100%;
min-height: 400px;
}
}
+43 -43
View File
@@ -2623,90 +2623,90 @@ __metadata:
languageName: node
linkType: hard
"@swc/core-darwin-arm64@npm:1.3.36":
version: 1.3.36
resolution: "@swc/core-darwin-arm64@npm:1.3.36"
"@swc/core-darwin-arm64@npm:1.3.38":
version: 1.3.38
resolution: "@swc/core-darwin-arm64@npm:1.3.38"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
"@swc/core-darwin-x64@npm:1.3.36":
version: 1.3.36
resolution: "@swc/core-darwin-x64@npm:1.3.36"
"@swc/core-darwin-x64@npm:1.3.38":
version: 1.3.38
resolution: "@swc/core-darwin-x64@npm:1.3.38"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
"@swc/core-linux-arm-gnueabihf@npm:1.3.36":
version: 1.3.36
resolution: "@swc/core-linux-arm-gnueabihf@npm:1.3.36"
"@swc/core-linux-arm-gnueabihf@npm:1.3.38":
version: 1.3.38
resolution: "@swc/core-linux-arm-gnueabihf@npm:1.3.38"
conditions: os=linux & cpu=arm
languageName: node
linkType: hard
"@swc/core-linux-arm64-gnu@npm:1.3.36":
version: 1.3.36
resolution: "@swc/core-linux-arm64-gnu@npm:1.3.36"
"@swc/core-linux-arm64-gnu@npm:1.3.38":
version: 1.3.38
resolution: "@swc/core-linux-arm64-gnu@npm:1.3.38"
conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node
linkType: hard
"@swc/core-linux-arm64-musl@npm:1.3.36":
version: 1.3.36
resolution: "@swc/core-linux-arm64-musl@npm:1.3.36"
"@swc/core-linux-arm64-musl@npm:1.3.38":
version: 1.3.38
resolution: "@swc/core-linux-arm64-musl@npm:1.3.38"
conditions: os=linux & cpu=arm64 & libc=musl
languageName: node
linkType: hard
"@swc/core-linux-x64-gnu@npm:1.3.36":
version: 1.3.36
resolution: "@swc/core-linux-x64-gnu@npm:1.3.36"
"@swc/core-linux-x64-gnu@npm:1.3.38":
version: 1.3.38
resolution: "@swc/core-linux-x64-gnu@npm:1.3.38"
conditions: os=linux & cpu=x64 & libc=glibc
languageName: node
linkType: hard
"@swc/core-linux-x64-musl@npm:1.3.36":
version: 1.3.36
resolution: "@swc/core-linux-x64-musl@npm:1.3.36"
"@swc/core-linux-x64-musl@npm:1.3.38":
version: 1.3.38
resolution: "@swc/core-linux-x64-musl@npm:1.3.38"
conditions: os=linux & cpu=x64 & libc=musl
languageName: node
linkType: hard
"@swc/core-win32-arm64-msvc@npm:1.3.36":
version: 1.3.36
resolution: "@swc/core-win32-arm64-msvc@npm:1.3.36"
"@swc/core-win32-arm64-msvc@npm:1.3.38":
version: 1.3.38
resolution: "@swc/core-win32-arm64-msvc@npm:1.3.38"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
"@swc/core-win32-ia32-msvc@npm:1.3.36":
version: 1.3.36
resolution: "@swc/core-win32-ia32-msvc@npm:1.3.36"
"@swc/core-win32-ia32-msvc@npm:1.3.38":
version: 1.3.38
resolution: "@swc/core-win32-ia32-msvc@npm:1.3.38"
conditions: os=win32 & cpu=ia32
languageName: node
linkType: hard
"@swc/core-win32-x64-msvc@npm:1.3.36":
version: 1.3.36
resolution: "@swc/core-win32-x64-msvc@npm:1.3.36"
"@swc/core-win32-x64-msvc@npm:1.3.38":
version: 1.3.38
resolution: "@swc/core-win32-x64-msvc@npm:1.3.38"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
"@swc/core@npm:^1.3.36":
version: 1.3.36
resolution: "@swc/core@npm:1.3.36"
version: 1.3.38
resolution: "@swc/core@npm:1.3.38"
dependencies:
"@swc/core-darwin-arm64": 1.3.36
"@swc/core-darwin-x64": 1.3.36
"@swc/core-linux-arm-gnueabihf": 1.3.36
"@swc/core-linux-arm64-gnu": 1.3.36
"@swc/core-linux-arm64-musl": 1.3.36
"@swc/core-linux-x64-gnu": 1.3.36
"@swc/core-linux-x64-musl": 1.3.36
"@swc/core-win32-arm64-msvc": 1.3.36
"@swc/core-win32-ia32-msvc": 1.3.36
"@swc/core-win32-x64-msvc": 1.3.36
"@swc/core-darwin-arm64": 1.3.38
"@swc/core-darwin-x64": 1.3.38
"@swc/core-linux-arm-gnueabihf": 1.3.38
"@swc/core-linux-arm64-gnu": 1.3.38
"@swc/core-linux-arm64-musl": 1.3.38
"@swc/core-linux-x64-gnu": 1.3.38
"@swc/core-linux-x64-musl": 1.3.38
"@swc/core-win32-arm64-msvc": 1.3.38
"@swc/core-win32-ia32-msvc": 1.3.38
"@swc/core-win32-x64-msvc": 1.3.38
dependenciesMeta:
"@swc/core-darwin-arm64":
optional: true
@@ -2728,7 +2728,7 @@ __metadata:
optional: true
"@swc/core-win32-x64-msvc":
optional: true
checksum: 36e96e86d1ab8fd84eb51e7264a7ae781ee7e507d36b9ccc028d7f31a2ccf2949f074656e34139c970196774d24d55a54c3aa5c53d04eb6e3569c71cbfed0c0d
checksum: c55d30e57638bcd21f788add8490c3f3e71bfe027aa5a8b153e1b1b9686ecddd6deeaaa6a6b17717c7eab4c1e2a232b465b6755b6c891506fc0d03139badfbf7
languageName: node
linkType: hard
@@ -69,7 +69,7 @@ New components, of course, get added automatically to the Backstage Service Cata
## Define your standards
Backstage ships with four example templates, but since these are likely not the (only) ones you want to promote inside your company, the next step is to add [your own templates](https://backstage.io/docs/features/software-templates/software-templates-index). Using Backstages Software Templates feature, its easy to help your engineers get started building software with your organizations best practices built-in.
Backstage ships with four example templates, but since these are likely not the (only) ones you want to promote inside your company, the next step is to add [your own templates](https://backstage.io/docs/features/software-templates/). Using Backstages Software Templates feature, its easy to help your engineers get started building software with your organizations best practices built-in.
We have learned that one of the keys to getting these standards adopted is to keep an open process. Templates are code. By making it clear to your engineers that you are open to pull requests, and that teams with different needs can add their own templates, you are on the path of striking a good balance between autonomy and standardization.
@@ -40,7 +40,7 @@ More specifically, with this first iteration, you can:
- Choose your own storage solution for the documentation.
- Define your own API to interface with your documentation solution.
For a full overview, including getting started instructions, check out our [TechDocs Documentation](https://backstage.io/docs/features/techdocs/techdocs-overview).
For a full overview, including getting started instructions, check out our [TechDocs Documentation](https://backstage.io/docs/features/techdocs/).
But before you go there, let me tell you a bit about the TechDocs story — and why we believe TechDocs is such a powerful yet simple solution for great documentation.
@@ -97,6 +97,6 @@ Whichever situation youre in, we have you covered.
Weve built the foundation for the Backstage Search platform, and we can't wait to see the exciting engines, collators, and components the community builds on the platform.
You can check out our [project roadmap](https://backstage.io/docs/features/search/search-overview#project-roadmap) in our search documentation or track the progress of our [Beta milestone](https://github.com/backstage/backstage/milestone/27) and [GA milestone](https://github.com/backstage/backstage/milestone/28).
You can check out our [project roadmap](https://backstage.io/docs/features/search/#project-roadmap) in our search documentation or track the progress of our [Beta milestone](https://github.com/backstage/backstage/milestone/27) and [GA milestone](https://github.com/backstage/backstage/milestone/28).
For any questions, feedback or ideas about the Backstage Search platform, join us in the #search channel on [Discord](https://discord.gg/backstage-687207715902193673)!
+4 -4
View File
@@ -25,11 +25,11 @@ To start, lets define Backstage: its an open platform for building develop
Our definition of Backstage 1.0 includes:
- Backstage Core 1.0 libraries as the set of libraries to make the platform work
- [Backstage Software Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview) 1.0
- [Backstage Software Templates](https://backstage.io/docs/features/software-templates/software-templates-index) 1.0
- [Backstage TechDocs](https://backstage.io/docs/features/techdocs/techdocs-overview) 1.0
- [Backstage Software Catalog](https://backstage.io/docs/features/software-catalog/) 1.0
- [Backstage Software Templates](https://backstage.io/docs/features/software-templates/) 1.0
- [Backstage TechDocs](https://backstage.io/docs/features/techdocs/) 1.0
Coming soon: [Backstage Search](https://backstage.io/docs/features/search/search-overview) 1.0 will be included in the near future as part of the regular releases to the Backstage platform.
Coming soon: [Backstage Search](https://backstage.io/docs/features/search/) 1.0 will be included in the near future as part of the regular releases to the Backstage platform.
In terms of features, the maintainers will not be shipping new stuff as part of the major release but instead:
@@ -14,7 +14,7 @@ _TL;DR:_ Introducing the TechDocs Addon Framework — a way for us all to contri
<!--truncate-->
[TechDocs](https://backstage.io/docs/features/techdocs/techdocs-overview) is a centralized platform for publishing, viewing, and discovering technical documentation across an entire organization. It's a solid foundation! But TechDocs doesn't solve higher order documentation needs on its own such as: How do you create and reinforce a culture of documentation? How do you build trust in the quality of technical documentation?
[TechDocs](https://backstage.io/docs/features/techdocs/) is a centralized platform for publishing, viewing, and discovering technical documentation across an entire organization. It's a solid foundation! But TechDocs doesn't solve higher order documentation needs on its own such as: How do you create and reinforce a culture of documentation? How do you build trust in the quality of technical documentation?
To address this need, were proud to introduce the [TechDocs Addon Framework](https://github.com/backstage/backstage/issues/9636) — a way for us all to contribute and share additional features, TechDocs Addons, on top of the base docs-like-code experience. Using TechDocs Addons, you can customize the TechDocs experience to address some of these higher order needs.
@@ -1,11 +1,11 @@
---
# prettier-ignore
title: Releasing Backstage Search 1.0
title: Releasing Backstage Search 1.0
author: Emma Indal, Spotify
authorURL: https://www.linkedin.com/in/emma-indal
---
**TL;DR** If youve been waiting for Backstage Search to come out of beta, were excited to announce that [Backstage Search 1.0](https://backstage.io/docs/features/search/search-overview#backstage-search-10) is here!
**TL;DR** If youve been waiting for Backstage Search to come out of beta, were excited to announce that [Backstage Search 1.0](https://backstage.io/docs/features/search/#backstage-search-10) is here!
We first released the Backstage Search Platform over a year ago. Backstage Search Platform is a search experience built for you, by you.
@@ -4,13 +4,13 @@ author: Paul Cowan & Taras Mankovski
authorURL: https://frontside.com/
---
At the heart of [Backstage](https://backstage.io/) is the [Backstage Software Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview), which is a data store that allows an organization to centralize and visualize its many software services and components. Backstage inspects and transforms an organization's disparate software services and parts into a centralized data store. This blog post introduces the concept of incremental entity providers, which allow Backstage to scale ingestion to even larger datasets.
At the heart of [Backstage](https://backstage.io/) is the [Backstage Software Catalog](https://backstage.io/docs/features/software-catalog/), which is a data store that allows an organization to centralize and visualize its many software services and components. Backstage inspects and transforms an organization's disparate software services and parts into a centralized data store. This blog post introduces the concept of incremental entity providers, which allow Backstage to scale ingestion to even larger datasets.
![catalog pipeline](assets/2023-01-31/catalog-pipeline.png)
<!--truncate-->
A common use case is for an organization to want to surface ownership and metadata about repositories. Backstage provides a mechanism for discovering and transforming repository information into a standard data structure and persisting it into the Backstage [Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview). This process is known as ingestion, where all data is transformed into a standard Backstage data structure known as an entity. Entities in the Catalogs data store are accessible to the Backstage App via the REST API.
A common use case is for an organization to want to surface ownership and metadata about repositories. Backstage provides a mechanism for discovering and transforming repository information into a standard data structure and persisting it into the Backstage [Catalog](https://backstage.io/docs/features/software-catalog/). This process is known as ingestion, where all data is transformed into a standard Backstage data structure known as an entity. Entities in the Catalogs data store are accessible to the Backstage App via the REST API.
Data is transformed into entities via what is known as the ingestion and processing loop, which can be thought of as an [extract, transform and load (ETL) pipeline](https://en.wikipedia.org/wiki/Extract,_transform,_load), where raw data such as GitHub repositories are loaded from GitHub, transformed into entities and outputted to the Catalog.
@@ -0,0 +1,13 @@
---
title: PuppetDB Entity Provider
author: TDabasinskas
authorUrl: https://github.com/tdabasinskas
category: Configuration Management
description: Import nodes from PuppetDB into Backstage as Resource Entities
documentation: https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-puppetdb/README.md
iconUrl: https://avatars.githubusercontent.com/u/234268?s=200&v=4
npmPackageName: '@backstage/plugin-catalog-backend-module-puppetdb'
tags:
- puppet
- puppetdb
addedDate: '2023-02-06'
@@ -0,0 +1,12 @@
---
title: Harness Feature Flags
author: harness.io
authorUrl: https://github.com/harness/backstage-plugins
category: Feature Flags
description: This plugin lets you view Harness Feature Flags created in your project inside Backstage.
documentation: https://github.com/harness/backstage-plugins/tree/main/plugins/harness-feature-flags
iconUrl: https://static.harness.io/ng-static/images/favicon.png
npmPackageName: '@harnessio/backstage-plugin-feature-flags'
tags:
- feature-flags
addedDate: '2023-03-01'
@@ -5,7 +5,7 @@ authorUrl: https://github.com/mufaddal7
category: Monitoring
description: Easily view your New Relic Dashboards in Backstage, via real-time snapshots of your dashboards
documentation: https://github.com/backstage/backstage/tree/master/plugins/newrelic-dashboard
iconUrl: https://newrelic.com/themes/custom/curio/assets/mediakit/NR_logo_Horizontal_Rev.svg
iconUrl: https://newrelic.com/themes/custom/erno/assets/mediakit/new_relic_logo_vertical_white.svg
npmPackageName: '@backstage/plugin-newrelic-dashboard'
tags:
- performance
+1 -1
View File
@@ -24,7 +24,7 @@ const truncate = text =>
const newForDays = 100;
const addPluginDocsLink = '/docs/plugins/add-to-marketplace';
const defaultIconUrl = 'img/logo-gradient-on-dark.svg';
const defaultIconUrl = '/img/logo-gradient-on-dark.svg';
const Plugins = () => (
<main className="MainContent">
+13 -1
View File
@@ -1 +1,13 @@
<svg id="Logos" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 140 140"><defs><style>.cls-1{fill:#fff;}</style></defs><path class="cls-1" d="M19.13 103.13c9.35-6 20.33-16.34 16.16-28.5C33 68 29.87 62.33 29.45 55.17a43.84 43.84 0 0 1 2.66-17.83C40.49 14.85 65.4 4.2 87.92 11c20.85 6.26 35.2 30.48 26.56 51.58-5 12.18-7.22 21.58 3.9 31.05 3 2.57 10.33 6.42 10.31 11 0 6-11.74-1.27-13-2.31 1.49 2.6 16.24 18 6.85 19.08-8.64 1-16.27-11.06-21.47-16.25-8.72-8.7-7.21 10.55-7.25 14.53-.06 6.28-4.5 19-12.46 10.72-6.58-6.84-4.09-17.76-8.66-25.36-5-8.33-13.34 8.33-15.42 11.37-2.33 3.4-14 19.89-18.59 11.1-3.76-7.13 2.25-18.3 5.2-24.75-1.08 2.34-8.71 5.8-10.94 6.93-5 2.54-10.15 4.05-15.82 3.65-11.73-.84-2.73-7.07 2-10.15z"/></svg>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 140 140" style="enable-background:new 0 0 140 140;" xml:space="preserve">
<style type="text/css">
.st0{fill:#0D80D8;}
</style>
<path class="st0" d="M19.1,102.1c9.4-6,20.3-16.3,16.2-28.5C33,67,29.9,61.3,29.5,54.2C29.1,48.1,30,42,32.1,36.3
C40.5,13.8,65.4,3.2,87.9,10c20.8,6.3,35.2,30.5,26.6,51.6c-5,12.2-7.2,21.6,3.9,31c3,2.6,10.3,6.4,10.3,11c0,6-11.7-1.3-13-2.3
c1.5,2.6,16.2,18,6.8,19.1c-8.6,1-16.3-11.1-21.5-16.2c-8.7-8.7-7.2,10.6-7.2,14.5c-0.1,6.3-4.5,19-12.5,10.7
c-6.6-6.8-4.1-17.8-8.7-25.4c-5-8.3-13.3,8.3-15.4,11.4c-2.3,3.4-14,19.9-18.6,11.1c-3.8-7.1,2.2-18.3,5.2-24.8
c-1.1,2.3-8.7,5.8-10.9,6.9c-5,2.5-10.1,4.1-15.8,3.7C5.4,111.5,14.4,105.3,19.1,102.1L19.1,102.1z"/>
</svg>

Before

Width:  |  Height:  |  Size: 724 B

After

Width:  |  Height:  |  Size: 962 B

+1 -1
View File
@@ -47,7 +47,7 @@
"@types/react": "^17",
"@types/react-dom": "^17"
},
"version": "1.12.0-next.1",
"version": "1.12.0-next.2",
"dependencies": {
"@backstage/errors": "workspace:^",
"@manypkg/get-packages": "^1.1.3"
+10
View File
@@ -1,5 +1,15 @@
# @backstage/app-defaults
## 1.2.1-next.2
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.12.5-next.2
- @backstage/core-app-api@1.6.0-next.2
- @backstage/core-plugin-api@1.5.0-next.2
- @backstage/plugin-permission-react@0.4.11-next.2
## 1.2.1-next.1
### Patch Changes
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/app-defaults",
"description": "Provides the default wiring of a Backstage App",
"version": "1.2.1-next.1",
"version": "1.2.1-next.2",
"publishConfig": {
"access": "public",
"main": "dist/index.esm.js",
+64
View File
@@ -1,5 +1,69 @@
# example-app
## 0.2.81-next.2
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.12.5-next.2
- @backstage/plugin-scaffolder-react@1.2.0-next.2
- @backstage/plugin-techdocs-react@1.1.4-next.2
- @backstage/plugin-catalog-react@1.4.0-next.2
- @backstage/plugin-tech-insights@0.3.8-next.2
- @backstage/plugin-search-react@1.5.1-next.2
- @backstage/plugin-scaffolder@1.12.0-next.2
- @backstage/plugin-techdocs@1.6.0-next.2
- @backstage/plugin-explore@0.4.1-next.2
- @backstage/plugin-search@1.1.1-next.2
- @backstage/plugin-kubernetes@0.7.9-next.2
- @backstage/plugin-api-docs@0.9.1-next.2
- @backstage/core-app-api@1.6.0-next.2
- @backstage/plugin-org@0.6.6-next.2
- @backstage/core-plugin-api@1.5.0-next.2
- @backstage/app-defaults@1.2.1-next.2
- @backstage/cli@0.22.4-next.1
- @backstage/integration-react@1.1.11-next.2
- @backstage/plugin-airbrake@0.3.16-next.2
- @backstage/plugin-apache-airflow@0.2.9-next.2
- @backstage/plugin-azure-devops@0.2.7-next.2
- @backstage/plugin-azure-sites@0.1.5-next.2
- @backstage/plugin-badges@0.2.40-next.2
- @backstage/plugin-catalog-graph@0.2.28-next.2
- @backstage/plugin-catalog-import@0.9.6-next.2
- @backstage/plugin-circleci@0.3.16-next.2
- @backstage/plugin-cloudbuild@0.3.16-next.2
- @backstage/plugin-code-coverage@0.2.9-next.2
- @backstage/plugin-cost-insights@0.12.5-next.2
- @backstage/plugin-dynatrace@3.0.0-next.2
- @backstage/plugin-entity-feedback@0.1.1-next.2
- @backstage/plugin-gcalendar@0.3.12-next.2
- @backstage/plugin-gcp-projects@0.3.35-next.2
- @backstage/plugin-github-actions@0.5.16-next.2
- @backstage/plugin-gocd@0.1.22-next.2
- @backstage/plugin-graphiql@0.2.48-next.2
- @backstage/plugin-home@0.4.32-next.2
- @backstage/plugin-jenkins@0.7.15-next.2
- @backstage/plugin-kafka@0.3.16-next.2
- @backstage/plugin-lighthouse@0.4.1-next.2
- @backstage/plugin-linguist@0.1.1-next.2
- @backstage/plugin-microsoft-calendar@0.1.1-next.2
- @backstage/plugin-newrelic@0.3.34-next.2
- @backstage/plugin-newrelic-dashboard@0.2.9-next.2
- @backstage/plugin-pagerduty@0.5.9-next.2
- @backstage/plugin-playlist@0.1.7-next.2
- @backstage/plugin-rollbar@0.4.16-next.2
- @backstage/plugin-sentry@0.5.1-next.2
- @backstage/plugin-shortcuts@0.3.8-next.2
- @backstage/plugin-stack-overflow@0.1.12-next.2
- @backstage/plugin-stackstorm@0.1.0-next.2
- @backstage/plugin-tech-radar@0.6.2-next.2
- @backstage/plugin-techdocs-module-addons-contrib@1.0.11-next.2
- @backstage/plugin-todo@0.2.18-next.2
- @backstage/plugin-user-settings@0.7.1-next.2
- @internal/plugin-catalog-customized@0.0.8-next.2
- @backstage/plugin-permission-react@0.4.11-next.2
- @backstage/config@1.0.7-next.0
## 0.2.81-next.1
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "example-app",
"version": "0.2.81-next.1",
"version": "0.2.81-next.2",
"private": true,
"backstage": {
"role": "frontend"
+12
View File
@@ -1,5 +1,17 @@
# @backstage/backend-app-api
## 0.4.1-next.2
### Patch Changes
- Updated dependencies
- @backstage/plugin-auth-node@0.2.12-next.2
- @backstage/backend-tasks@0.5.0-next.2
- @backstage/backend-common@0.18.3-next.2
- @backstage/backend-plugin-api@0.4.1-next.2
- @backstage/plugin-permission-node@0.7.6-next.2
- @backstage/config@1.0.7-next.0
## 0.4.1-next.1
### Patch Changes
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/backend-app-api",
"description": "Core API used by Backstage backend apps",
"version": "0.4.1-next.1",
"version": "0.4.1-next.2",
"main": "src/index.ts",
"types": "src/index.ts",
"publishConfig": {
+28
View File
@@ -1,5 +1,33 @@
# @backstage/backend-common
## 0.18.3-next.2
### Patch Changes
- f75097868a7: Adds config option `backend.database.role` to set ownership for newly created schemas and tables in Postgres
The example config below connects to the database as user `v-backstage-123` but sets the ownership of
the create schemas and tables to `backstage`
```yaml
backend:
database:
client: pg
pluginDivisionMode: schema
role: backstage
connection:
user: v-backstage-123
...
```
- 87f0bbec175: AwsS3UrlReader upgraded to use aws-sdk v3
- Updated dependencies
- @backstage/backend-app-api@0.4.1-next.2
- @backstage/backend-plugin-api@0.4.1-next.2
- @backstage/config@1.0.7-next.0
- @backstage/integration@1.4.3-next.0
- @backstage/integration-aws-node@0.1.2-next.0
## 0.18.3-next.1
### Patch Changes
+2 -2
View File
@@ -6,7 +6,7 @@
/// <reference types="node" />
/// <reference types="webpack-env" />
import aws from 'aws-sdk';
import { AwsCredentialsManager } from '@backstage/integration-aws-node';
import { AwsS3Integration } from '@backstage/integration';
import { AzureIntegration } from '@backstage/integration';
import { BackendFeature } from '@backstage/backend-plugin-api';
@@ -67,9 +67,9 @@ import { Writable } from 'stream';
// @public
export class AwsS3UrlReader implements UrlReader {
constructor(
credsManager: AwsCredentialsManager,
integration: AwsS3Integration,
deps: {
s3: aws.S3;
treeResponseFactory: ReadTreeResponseFactory;
},
);
+4
View File
@@ -96,6 +96,8 @@ export interface Config {
* @default database
*/
pluginDivisionMode?: 'database' | 'schema';
/** Configures the ownership of newly created schemas in pg databases. */
role?: string;
/**
* Arbitrary config object to pass to knex when initializing
* (https://knexjs.org/#Installation-client). Most notable is the debug
@@ -125,6 +127,8 @@ export interface Config {
* This is merged recursively into the base knexConfig
*/
knexConfig?: object;
/** Configures the ownership of newly created schemas in pg databases. */
role?: string;
};
};
};
+10 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/backend-common",
"description": "Common functionality library for Backstage backends",
"version": "0.18.3-next.1",
"version": "0.18.3-next.2",
"main": "src/index.ts",
"types": "src/index.ts",
"publishConfig": {
@@ -46,6 +46,10 @@
"test:kubernetes": "backstage-cli package test -t KubernetesContainerRunner --no-watch"
},
"dependencies": {
"@aws-sdk/abort-controller": "^3.208.0",
"@aws-sdk/client-s3": "^3.208.0",
"@aws-sdk/credential-providers": "^3.208.0",
"@aws-sdk/types": "^3.208.0",
"@backstage/backend-app-api": "workspace:^",
"@backstage/backend-dev-utils": "workspace:^",
"@backstage/backend-plugin-api": "workspace:^",
@@ -54,6 +58,7 @@
"@backstage/config-loader": "workspace:^",
"@backstage/errors": "workspace:^",
"@backstage/integration": "workspace:^",
"@backstage/integration-aws-node": "workspace:^",
"@backstage/types": "workspace:^",
"@google-cloud/storage": "^6.0.0",
"@keyv/memcache": "^1.3.5",
@@ -67,7 +72,6 @@
"@types/luxon": "^3.0.0",
"@types/webpack-env": "^1.15.2",
"archiver": "^5.0.2",
"aws-sdk": "^2.840.0",
"base64-stream": "^1.0.0",
"compression": "^1.7.4",
"concat-stream": "^2.0.0",
@@ -90,6 +94,7 @@
"morgan": "^1.10.0",
"node-fetch": "^2.6.7",
"node-forge": "^1.3.1",
"pg": "^8.3.0",
"raw-body": "^2.4.1",
"request": "^2.88.2",
"selfsigned": "^2.0.0",
@@ -110,6 +115,7 @@
}
},
"devDependencies": {
"@aws-sdk/util-stream-node": "^3.208.0",
"@backstage/backend-test-utils": "workspace:^",
"@backstage/cli": "workspace:^",
"@types/archiver": "^5.1.0",
@@ -122,12 +128,14 @@
"@types/mock-fs": "^4.13.0",
"@types/morgan": "^1.9.0",
"@types/node-forge": "^1.3.0",
"@types/pg": "^8.6.6",
"@types/recursive-readdir": "^2.2.0",
"@types/stoppable": "^1.1.0",
"@types/supertest": "^2.0.8",
"@types/tar": "^6.1.1",
"@types/webpack-env": "^1.15.2",
"@types/yauzl": "^2.10.0",
"aws-sdk-client-mock": "^2.0.0",
"aws-sdk-mock": "^5.2.1",
"better-sqlite3": "^8.0.0",
"http-errors": "^2.0.0",
@@ -687,5 +687,59 @@ describe('DatabaseManager', () => {
}),
);
});
it('sets the owner config for plugin using default config', async () => {
const testManager = DatabaseManager.fromConfig(
new ConfigReader({
backend: {
database: {
client: 'pg',
connection: {
host: 'localhost',
database: 'foodb',
},
role: 'backstage',
plugin: {
testowner: {},
},
},
},
}),
);
await testManager.forPlugin('testowner').getClient();
const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1);
const [baseConfig] = mockCalls[0];
expect(baseConfig.data.role).toEqual('backstage');
});
it('sets the owner config for plugin using plugin config', async () => {
const testManager = DatabaseManager.fromConfig(
new ConfigReader({
backend: {
database: {
client: 'pg',
connection: {
host: 'localhost',
database: 'foodb',
},
role: 'backstage',
plugin: {
testowner: {
role: 'backstage-plugin',
},
},
},
},
}),
);
await testManager.forPlugin('testowner').getClient();
const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1);
const [baseConfig] = mockCalls[0];
expect(baseConfig.data.role).toEqual('backstage-plugin');
});
});
});
@@ -186,6 +186,13 @@ export class DatabaseManager {
};
}
private getRoleConfig(pluginId: string): string | undefined {
return (
this.config.getOptionalString(`${pluginPath(pluginId)}.role`) ??
this.config.getOptionalString('role')
);
}
/**
* Provides the knexConfig which should be used for a given plugin.
*
@@ -278,11 +285,13 @@ export class DatabaseManager {
*/
private getConfigForPlugin(pluginId: string): Knex.Config {
const { client } = this.getClientType(pluginId);
const role = this.getRoleConfig(pluginId);
return {
...this.getAdditionalKnexConfig(pluginId),
client,
connection: this.getConnectionConfig(pluginId),
...(role && { role }),
};
}
@@ -22,6 +22,7 @@ import { mergeDatabaseConfig } from '../config';
import { DatabaseConnector } from '../types';
import defaultNameOverride from './defaultNameOverride';
import defaultSchemaOverride from './defaultSchemaOverride';
import { Client } from 'pg';
/**
* Creates a knex postgres database connection
@@ -35,6 +36,17 @@ export function createPgDatabaseClient(
) {
const knexConfig = buildPgDatabaseConfig(dbConfig, overrides);
const database = knexFactory(knexConfig);
const role = dbConfig.getOptionalString('role');
if (role) {
database.client.pool.on(
'createSuccess',
async (_event: number, pgClient: Client) => {
await pgClient.query(`SET ROLE ${role}`);
},
);
}
return database;
}
@@ -147,10 +159,18 @@ export async function ensurePgSchemaExists(
...schemas: Array<string>
): Promise<void> {
const admin = createPgDatabaseClient(dbConfig);
const role = dbConfig.getOptionalString('role');
try {
const ensureSchema = async (database: string) => {
await admin.raw(`CREATE SCHEMA IF NOT EXISTS ??`, [database]);
if (role) {
await admin.raw(`CREATE SCHEMA IF NOT EXISTS ?? AUTHORIZATION ??`, [
database,
role,
]);
} else {
await admin.raw(`CREATE SCHEMA IF NOT EXISTS ??`, [database]);
}
};
await Promise.all(schemas.map(ensureSchema));
@@ -18,19 +18,25 @@ import { ConfigReader } from '@backstage/config';
import { JsonObject } from '@backstage/types';
import { getVoidLogger } from '../logging';
import { DefaultReadTreeResponseFactory } from './tree';
import { rest } from 'msw';
import { setupServer } from 'msw/node';
import { setupRequestMockHandlers } from '@backstage/backend-test-utils';
import { AwsS3UrlReader, parseUrl } from './AwsS3UrlReader';
import { DEFAULT_REGION, AwsS3UrlReader, parseUrl } from './AwsS3UrlReader';
import {
AwsS3Integration,
readAwsS3IntegrationConfig,
} from '@backstage/integration';
import { DefaultAwsCredentialsManager } from '@backstage/integration-aws-node';
import { UrlReaderPredicateTuple } from './types';
import AWSMock from 'aws-sdk-mock';
import aws from 'aws-sdk';
import path from 'path';
import { NotModifiedError } from '@backstage/errors';
import { mockClient } from 'aws-sdk-client-mock';
import {
S3Client,
ListObjectsV2Command,
ListObjectsV2Output,
GetObjectCommand,
S3ServiceException,
} from '@aws-sdk/client-s3';
import { sdkStreamMixin } from '@aws-sdk/util-stream-node';
import fs from 'fs';
const treeResponseFactory = DefaultReadTreeResponseFactory.create({
config: new ConfigReader({}),
@@ -97,7 +103,7 @@ describe('parseUrl', () => {
).toEqual({
path: 'a/puppy.jpg',
bucket: 'my.bucket-3',
region: '',
region: DEFAULT_REGION,
});
expect(
parseUrl('https://my.bucket-3.my-host.com/a/puppy.jpg', {
@@ -106,7 +112,7 @@ describe('parseUrl', () => {
).toEqual({
path: 'a/puppy.jpg',
bucket: 'my.bucket-3',
region: '',
region: DEFAULT_REGION,
});
expect(
parseUrl('https://ignored.my-host.com/my.bucket-3/a/puppy.jpg', {
@@ -116,14 +122,13 @@ describe('parseUrl', () => {
).toEqual({
path: 'a/puppy.jpg',
bucket: 'my.bucket-3',
region: '',
region: DEFAULT_REGION,
});
});
});
describe('AwsS3UrlReader', () => {
const worker = setupServer();
setupRequestMockHandlers(worker);
const s3Client = mockClient(S3Client);
const createReader = (config: JsonObject): UrlReaderPredicateTuple[] => {
return AwsS3UrlReader.factory({
@@ -232,17 +237,19 @@ describe('AwsS3UrlReader', () => {
});
beforeEach(() => {
worker.use(
rest.get(
'https://test-bucket.s3.amazonaws.com/awsS3-mock-object.yaml',
(_, res, ctx) =>
res(
ctx.status(200),
ctx.set('ETag', '123abc'),
ctx.body('site_name: Test'),
s3Client.reset();
s3Client.on(GetObjectCommand).resolves({
Body: sdkStreamMixin(
fs.createReadStream(
path.resolve(
__dirname,
'__fixtures__/awsS3/awsS3-mock-object.yaml',
),
),
),
);
ETag: '123abc',
});
});
it('returns contents of an object in a bucket', async () => {
@@ -280,17 +287,19 @@ describe('AwsS3UrlReader', () => {
});
beforeEach(() => {
worker.use(
rest.get(
'https://test-bucket.s3.amazonaws.com/awsS3-mock-object.yaml',
(_, res, ctx) =>
res(
ctx.status(200),
ctx.set('ETag', '123abc'),
ctx.body('site_name: Test'),
s3Client.reset();
s3Client.on(GetObjectCommand).resolves({
Body: sdkStreamMixin(
fs.createReadStream(
path.resolve(
__dirname,
'__fixtures__/awsS3/awsS3-mock-object.yaml',
),
),
),
);
ETag: '123abc',
});
});
it('returns contents of an object in a bucket via buffer', async () => {
@@ -340,17 +349,17 @@ describe('AwsS3UrlReader', () => {
});
beforeEach(() => {
worker.use(
rest.get(
'http://localhost:4566/test-bucket/awsS3-mock-object.yaml',
(_, res, ctx) =>
res(
ctx.status(200),
ctx.set('ETag', '123abc'),
ctx.body('site_name: Test'),
s3Client.on(GetObjectCommand).resolves({
Body: sdkStreamMixin(
fs.createReadStream(
path.resolve(
__dirname,
'__fixtures__/awsS3/awsS3-mock-object.yaml',
),
),
),
);
ETag: '123abc',
});
});
it('returns contents of an object in a bucket via buffer', async () => {
@@ -377,12 +386,13 @@ describe('AwsS3UrlReader', () => {
});
beforeEach(() => {
worker.use(
rest.get(
'https://test-bucket.s3.amazonaws.com/awsS3-mock-object.yaml',
(_, res, ctx) => res(ctx.status(304)),
),
);
s3Client.reset();
const t = new S3ServiceException({
name: '304',
$fault: 'client',
$metadata: { httpStatusCode: 304 },
});
s3Client.on(GetObjectCommand).rejects(t);
});
it('returns contents of an object in a bucket', async () => {
@@ -400,44 +410,43 @@ describe('AwsS3UrlReader', () => {
describe('readTree', () => {
let awsS3UrlReader: AwsS3UrlReader;
beforeAll(() => {
const object: aws.S3.Types.Object = {
beforeEach(() => {
s3Client.reset();
const object: Object = {
Key: 'awsS3-mock-object.yaml',
};
const objectList: aws.S3.ObjectList = [object];
const output: aws.S3.Types.ListObjectsV2Output = {
const objectList: Object[] = [object];
const output: ListObjectsV2Output = {
Contents: objectList,
};
AWSMock.setSDKInstance(aws);
AWSMock.mock('S3', 'listObjectsV2', output);
s3Client.on(ListObjectsV2Command).resolves(output);
AWSMock.mock(
'S3',
'getObject',
Buffer.from(
require('fs').readFileSync(
s3Client.on(GetObjectCommand).resolves({
Body: sdkStreamMixin(
fs.createReadStream(
path.resolve(
__dirname,
'__fixtures__/awsS3/awsS3-mock-object.yaml',
),
),
),
);
});
const config = new ConfigReader({
host: '.amazonaws.com',
accessKeyId: 'fake-access-key',
secretAccessKey: 'fake-secret-key',
});
const credsManager = DefaultAwsCredentialsManager.fromConfig(config);
const s3 = new aws.S3();
awsS3UrlReader = new AwsS3UrlReader(
new AwsS3Integration(
readAwsS3IntegrationConfig(
new ConfigReader({
host: '.amazonaws.com',
accessKeyId: 'fake-access-key',
secretAccessKey: 'fake-secret-key',
}),
),
),
{ s3, treeResponseFactory },
credsManager,
new AwsS3Integration(readAwsS3IntegrationConfig(config)),
{ treeResponseFactory },
);
});
@@ -14,10 +14,6 @@
* limitations under the License.
*/
// note: We do the import like this so that we don't get issues destructuring
// and it not being mocked by the aws-sdk-mock package which is unfortunate.
import aws from 'aws-sdk';
import { CredentialsOptions } from 'aws-sdk/lib/credentials';
import {
ReaderFactory,
ReadTreeOptions,
@@ -28,16 +24,29 @@ import {
SearchResponse,
UrlReader,
} from './types';
import {
AwsCredentialsManager,
DefaultAwsCredentialsManager,
} from '@backstage/integration-aws-node';
import {
AwsS3Integration,
ScmIntegrations,
AwsS3IntegrationConfig,
} from '@backstage/integration';
import { ForwardedError, NotModifiedError } from '@backstage/errors';
import { ListObjectsV2Output, ObjectList } from 'aws-sdk/clients/s3';
import { fromTemporaryCredentials } from '@aws-sdk/credential-providers';
import { AwsCredentialIdentityProvider } from '@aws-sdk/types';
import {
S3Client,
ListObjectsV2Command,
ListObjectsV2CommandOutput,
GetObjectCommand,
} from '@aws-sdk/client-s3';
import { AbortController } from '@aws-sdk/abort-controller';
import { ReadUrlResponseFactory } from './ReadUrlResponseFactory';
import { Readable } from 'stream';
const DEFAULT_REGION = 'us-east-1';
export const DEFAULT_REGION = 'us-east-1';
/**
* Path style URLs: https://s3.(region).amazonaws.com/(bucket)/(key)
@@ -105,14 +114,14 @@ export function parseUrl(
return {
path: pathname.substring(slashIndex + 1),
bucket: pathname.substring(0, slashIndex),
region: '',
region: DEFAULT_REGION,
};
}
return {
path: pathname,
bucket: host.substring(0, host.length - config.host.length - 1),
region: '',
region: DEFAULT_REGION,
};
}
@@ -124,18 +133,10 @@ export function parseUrl(
export class AwsS3UrlReader implements UrlReader {
static factory: ReaderFactory = ({ config, treeResponseFactory }) => {
const integrations = ScmIntegrations.fromConfig(config);
const credsManager = DefaultAwsCredentialsManager.fromConfig(config);
return integrations.awsS3.list().map(integration => {
const credentials = AwsS3UrlReader.buildCredentials(integration);
const s3 = new aws.S3({
apiVersion: '2006-03-01',
credentials,
endpoint: integration.config.endpoint,
s3ForcePathStyle: integration.config.s3ForcePathStyle,
});
const reader = new AwsS3UrlReader(integration, {
s3,
const reader = new AwsS3UrlReader(credsManager, integration, {
treeResponseFactory,
});
const predicate = (url: URL) =>
@@ -145,9 +146,9 @@ export class AwsS3UrlReader implements UrlReader {
};
constructor(
private readonly credsManager: AwsCredentialsManager,
private readonly integration: AwsS3Integration,
private readonly deps: {
s3: aws.S3;
treeResponseFactory: ReadTreeResponseFactory;
},
) {}
@@ -156,39 +157,93 @@ export class AwsS3UrlReader implements UrlReader {
* If accessKeyId and secretAccessKey are missing, the standard credentials provider chain will be used:
* https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/auth/DefaultAWSCredentialsProviderChain.html
*/
private static buildCredentials(
private static buildStaticCredentials(
accessKeyId: string,
secretAccessKey: string,
): AwsCredentialIdentityProvider {
return async () => {
return {
accessKeyId,
secretAccessKey,
};
};
}
private static async buildCredentials(
credsManager: AwsCredentialsManager,
region: string,
integration?: AwsS3Integration,
): aws.Credentials | CredentialsOptions | undefined {
): Promise<AwsCredentialIdentityProvider> {
// Fall back to the default credential chain if neither account ID
// nor explicit credentials are provided
if (!integration) {
return undefined;
return (await credsManager.getCredentialProvider()).sdkCredentialProvider;
}
const accessKeyId = integration.config.accessKeyId;
const secretAccessKey = integration.config.secretAccessKey;
let explicitCredentials: aws.Credentials | undefined;
let explicitCredentials: AwsCredentialIdentityProvider;
if (accessKeyId && secretAccessKey) {
explicitCredentials = new aws.Credentials({
explicitCredentials = AwsS3UrlReader.buildStaticCredentials(
accessKeyId,
secretAccessKey,
});
);
} else {
explicitCredentials = (await credsManager.getCredentialProvider())
.sdkCredentialProvider;
}
const roleArn = integration.config.roleArn;
if (roleArn) {
return new aws.ChainableTemporaryCredentials({
return fromTemporaryCredentials({
masterCredentials: explicitCredentials,
params: {
RoleSessionName: 'backstage-aws-s3-url-reader',
RoleArn: roleArn,
ExternalId: integration.config.externalId,
},
clientConfig: { region },
});
}
return explicitCredentials;
}
private async buildS3Client(
credsManager: AwsCredentialsManager,
region: string,
integration: AwsS3Integration,
): Promise<S3Client> {
const credentials = await AwsS3UrlReader.buildCredentials(
credsManager,
region,
integration,
);
const s3 = new S3Client({
region: region,
credentials: credentials,
endpoint: integration.config.endpoint,
forcePathStyle: integration.config.s3ForcePathStyle,
});
return s3;
}
private async retrieveS3ObjectData(stream: Readable): Promise<Readable> {
return new Promise((resolve, reject) => {
try {
const chunks: any[] = [];
stream.on('data', chunk => chunks.push(chunk));
stream.on('error', (e: Error) =>
reject(new ForwardedError('Unable to read stream', e)),
);
stream.on('end', () => resolve(Readable.from(Buffer.concat(chunks))));
} catch (e) {
throw new ForwardedError('Unable to parse the response data', e);
}
});
}
async read(url: string): Promise<Buffer> {
const response = await this.readUrl(url);
return response.buffer();
@@ -200,7 +255,12 @@ export class AwsS3UrlReader implements UrlReader {
): Promise<ReadUrlResponse> {
try {
const { path, bucket, region } = parseUrl(url, this.integration.config);
aws.config.update({ region: region });
const s3Client = await this.buildS3Client(
this.credsManager,
region,
this.integration,
);
const abortController = new AbortController();
let params;
if (options?.etag) {
@@ -215,44 +275,22 @@ export class AwsS3UrlReader implements UrlReader {
Key: path,
};
}
const request = this.deps.s3.getObject(params);
options?.signal?.addEventListener('abort', () => request.abort());
// Since we're consuming the read stream we need to consume headers and errors via events.
const etagPromise = new Promise<string | undefined>((resolve, reject) => {
request.on('httpHeaders', (status, headers) => {
if (status < 400) {
if (status === 200) {
resolve(headers.etag);
} else if (status !== 304 /* not modified */) {
reject(
new Error(
`S3 readUrl request received unexpected status '${status}' in response`,
),
);
}
}
});
request.on('error', error => reject(error));
request.on('complete', () =>
reject(
new Error('S3 readUrl request completed without receiving headers'),
),
);
options?.signal?.addEventListener('abort', () => abortController.abort());
const getObjectCommand = new GetObjectCommand(params);
const response = await s3Client.send(getObjectCommand, {
abortSignal: abortController.signal,
});
const stream = request.createReadStream();
stream.on('error', () => {
// The AWS SDK forwards request errors to the stream, so we need to
// ignore those errors here or the process will crash.
});
const s3ObjectData = await this.retrieveS3ObjectData(
response.Body as Readable,
);
const etag = response.ETag;
return ReadUrlResponseFactory.fromReadable(stream, {
etag: await etagPromise,
return ReadUrlResponseFactory.fromReadable(s3ObjectData, {
etag: etag,
});
} catch (e) {
if (e.statusCode === 304) {
if (e.$metadata && e.$metadata.httpStatusCode === 304) {
throw new NotModifiedError();
}
@@ -266,35 +304,49 @@ export class AwsS3UrlReader implements UrlReader {
): Promise<ReadTreeResponse> {
try {
const { path, bucket, region } = parseUrl(url, this.integration.config);
const allObjects: ObjectList = [];
const s3Client = await this.buildS3Client(
this.credsManager,
region,
this.integration,
);
const abortController = new AbortController();
const allObjects: String[] = [];
const responses = [];
let continuationToken: string | undefined;
let output: ListObjectsV2Output;
let output: ListObjectsV2CommandOutput;
do {
aws.config.update({ region: region });
const request = this.deps.s3.listObjectsV2({
const listObjectsV2Command = new ListObjectsV2Command({
Bucket: bucket,
ContinuationToken: continuationToken,
Prefix: path,
});
options?.signal?.addEventListener('abort', () => request.abort());
output = await request.promise();
options?.signal?.addEventListener('abort', () =>
abortController.abort(),
);
output = await s3Client.send(listObjectsV2Command, {
abortSignal: abortController.signal,
});
if (output.Contents) {
output.Contents.forEach(contents => {
allObjects.push(contents);
allObjects.push(contents.Key!);
});
}
continuationToken = output.NextContinuationToken;
} while (continuationToken);
for (let i = 0; i < allObjects.length; i++) {
const object = this.deps.s3.getObject({
const getObjectCommand = new GetObjectCommand({
Bucket: bucket,
Key: String(allObjects[i].Key),
Key: String(allObjects[i]),
});
const response = await s3Client.send(getObjectCommand);
const s3ObjectData = await this.retrieveS3ObjectData(
response.Body as Readable,
);
responses.push({
data: object.createReadStream(),
path: String(allObjects[i].Key),
data: s3ObjectData,
path: String(allObjects[i]),
});
}
+9
View File
@@ -1,5 +1,14 @@
# @backstage/backend-defaults
## 0.1.8-next.2
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.18.3-next.2
- @backstage/backend-app-api@0.4.1-next.2
- @backstage/backend-plugin-api@0.4.1-next.2
## 0.1.8-next.1
### Patch Changes
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/backend-defaults",
"description": "Backend defaults used by Backstage backend apps",
"version": "0.1.8-next.1",
"version": "0.1.8-next.2",
"main": "src/index.ts",
"types": "src/index.ts",
"publishConfig": {
+11
View File
@@ -1,5 +1,16 @@
# example-backend-next
## 0.0.9-next.2
### Patch Changes
- Updated dependencies
- @backstage/plugin-scaffolder-backend@1.12.0-next.2
- @backstage/backend-defaults@0.1.8-next.2
- @backstage/plugin-app-backend@0.3.43-next.2
- @backstage/plugin-catalog-backend@1.8.0-next.2
- @backstage/plugin-todo-backend@0.1.40-next.2
## 0.0.9-next.1
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "example-backend-next",
"version": "0.0.9-next.1",
"version": "0.0.9-next.2",
"main": "dist/index.cjs.js",
"types": "src/index.ts",
"license": "Apache-2.0",
+9
View File
@@ -1,5 +1,14 @@
# @backstage/backend-plugin-api
## 0.4.1-next.2
### Patch Changes
- Updated dependencies
- @backstage/plugin-auth-node@0.2.12-next.2
- @backstage/backend-tasks@0.5.0-next.2
- @backstage/config@1.0.7-next.0
## 0.4.1-next.1
### Patch Changes
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/backend-plugin-api",
"description": "Core API used by Backstage backend plugins",
"version": "0.4.1-next.1",
"version": "0.4.1-next.2",
"main": "src/index.ts",
"types": "src/index.ts",
"publishConfig": {
+12
View File
@@ -1,5 +1,17 @@
# @backstage/backend-tasks
## 0.5.0-next.2
### Minor Changes
- 1578276708a: add functionality to get descriptions from the scheduler for triggering
### Patch Changes
- Updated dependencies
- @backstage/backend-common@0.18.3-next.2
- @backstage/config@1.0.7-next.0
## 0.4.4-next.1
### Patch Changes
+11
View File
@@ -7,6 +7,7 @@ import { Config } from '@backstage/config';
import { DatabaseManager } from '@backstage/backend-common';
import { Duration } from 'luxon';
import { HumanDuration as HumanDuration_2 } from '@backstage/types';
import { JsonObject } from '@backstage/types';
import { Logger } from 'winston';
import { PluginDatabaseManager } from '@backstage/backend-common';
@@ -16,6 +17,7 @@ export type HumanDuration = HumanDuration_2;
// @public
export interface PluginTaskScheduler {
createScheduledTaskRunner(schedule: TaskScheduleDefinition): TaskRunner;
getScheduledTasks(): Promise<TaskDescriptor[]>;
scheduleTask(
task: TaskScheduleDefinition & TaskInvocationDefinition,
): Promise<void>;
@@ -27,6 +29,15 @@ export function readTaskScheduleDefinitionFromConfig(
config: Config,
): TaskScheduleDefinition;
// @public
export type TaskDescriptor = {
id: string;
scope: 'global' | 'local';
settings: {
version: number;
} & JsonObject;
};
// @public
export type TaskFunction =
| ((abortSignal: AbortSignal) => void | Promise<void>)
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/backend-tasks",
"description": "Common distributed task management library for Backstage backends",
"version": "0.4.4-next.1",
"version": "0.5.0-next.2",
"main": "src/index.ts",
"types": "src/index.ts",
"publishConfig": {
@@ -0,0 +1,85 @@
/*
* Copyright 2022 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Knex } from 'knex';
import { TestDatabases } from '@backstage/backend-test-utils';
import fs from 'fs';
const migrationsDir = `${__dirname}/../migrations`;
const migrationsFiles = fs.readdirSync(migrationsDir).sort();
async function migrateUpOnce(knex: Knex): Promise<void> {
await knex.migrate.up({ directory: migrationsDir });
}
async function migrateDownOnce(knex: Knex): Promise<void> {
await knex.migrate.down({ directory: migrationsDir });
}
async function migrateUntilBefore(knex: Knex, target: string): Promise<void> {
const index = migrationsFiles.indexOf(target);
if (index === -1) {
throw new Error(`Migration ${target} not found`);
}
for (let i = 0; i < index; i++) {
await migrateUpOnce(knex);
}
}
describe('migrations', () => {
const databases = TestDatabases.create({
ids: ['POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'],
});
it.each(databases.eachSupportedId())(
'20210928160613_init.js, %p',
async databaseId => {
const knex = await databases.init(databaseId);
await migrateUntilBefore(knex, '20210928160613_init.js');
await migrateUpOnce(knex);
await knex('backstage_backend_tasks__tasks').insert({
id: 'test',
settings_json: '{}',
next_run_start_at: knex.fn.now(),
});
await expect(knex('backstage_backend_tasks__tasks')).resolves.toEqual([
{
id: 'test',
settings_json: '{}',
next_run_start_at: expect.anything(),
current_run_ticket: null,
current_run_started_at: null,
current_run_expires_at: null,
},
]);
await migrateDownOnce(knex);
// This looks odd - you might expect a .toThrow at the end but that
// actually is flaky for some reason specifically on sqlite when
// performing multiple runs in sequence
await expect(knex('backstage_backend_tasks__tasks')).rejects.toEqual(
expect.anything(),
);
await knex.destroy();
},
60_000,
);
});
@@ -39,8 +39,9 @@ export class LocalTaskWorker {
this.logger.info(
`Task worker starting: ${this.taskId}, ${JSON.stringify(settings)}`,
);
let attemptNum = 1;
(async () => {
let attemptNum = 1;
for (;;) {
try {
if (settings.initialDelayDuration) {
@@ -60,6 +61,7 @@ export class LocalTaskWorker {
options?.signal,
);
}
this.logger.info(`Task worker finished: ${this.taskId}`);
attemptNum = 0;
break;
@@ -304,6 +304,46 @@ describe('PluginTaskManagerImpl', () => {
);
});
describe('can fetch task ids', () => {
it.each(databases.eachSupportedId())(
'can fetch both global and local task ids, %p',
async databaseId => {
const { manager } = await init(databaseId);
const fn = jest.fn();
await manager.scheduleTask({
id: 'task1',
timeout: Duration.fromMillis(5000),
frequency: Duration.fromMillis(5000),
fn,
scope: 'global',
});
await manager.scheduleTask({
id: 'task2',
timeout: Duration.fromMillis(5000),
frequency: Duration.fromMillis(5000),
fn,
scope: 'local',
});
await expect(manager.getScheduledTasks()).resolves.toEqual([
{
id: 'task1',
scope: 'global',
settings: expect.objectContaining({ cadence: 'PT5S' }),
},
{
id: 'task2',
scope: 'local',
settings: expect.objectContaining({ cadence: 'PT5S' }),
},
]);
},
60_000,
);
});
describe('parseDuration', () => {
it('should parse durations', () => {
expect(parseDuration({ milliseconds: 5000 })).toEqual('PT5S');
@@ -21,9 +21,11 @@ import { LocalTaskWorker } from './LocalTaskWorker';
import { TaskWorker } from './TaskWorker';
import {
PluginTaskScheduler,
TaskDescriptor,
TaskInvocationDefinition,
TaskRunner,
TaskScheduleDefinition,
TaskSettingsV2,
} from './types';
import { validateId } from './util';
@@ -32,6 +34,7 @@ import { validateId } from './util';
*/
export class PluginTaskSchedulerImpl implements PluginTaskScheduler {
private readonly localTasksById = new Map<string, LocalTaskWorker>();
private readonly allScheduledTasks: TaskDescriptor[] = [];
constructor(
private readonly databaseFactory: () => Promise<Knex>,
@@ -55,6 +58,14 @@ export class PluginTaskSchedulerImpl implements PluginTaskScheduler {
validateId(task.id);
const scope = task.scope ?? 'global';
const settings: TaskSettingsV2 = {
version: 2,
cadence: parseDuration(task.frequency),
initialDelayDuration:
task.initialDelay && parseDuration(task.initialDelay),
timeoutAfterDuration: parseDuration(task.timeout),
};
if (scope === 'global') {
const knex = await this.databaseFactory();
const worker = new TaskWorker(
@@ -63,37 +74,22 @@ export class PluginTaskSchedulerImpl implements PluginTaskScheduler {
knex,
this.logger.child({ task: task.id }),
);
await worker.start(
{
version: 2,
cadence: parseDuration(task.frequency),
initialDelayDuration:
task.initialDelay && parseDuration(task.initialDelay),
timeoutAfterDuration: parseDuration(task.timeout),
},
{
signal: task.signal,
},
);
await worker.start(settings, { signal: task.signal });
} else {
const worker = new LocalTaskWorker(task.id, task.fn, this.logger);
worker.start(
{
version: 2,
cadence: parseDuration(task.frequency),
initialDelayDuration:
task.initialDelay && parseDuration(task.initialDelay),
timeoutAfterDuration: parseDuration(task.timeout),
},
{
signal: task.signal,
},
const worker = new LocalTaskWorker(
task.id,
task.fn,
this.logger.child({ task: task.id }),
);
worker.start(settings, { signal: task.signal });
this.localTasksById.set(task.id, worker);
}
this.allScheduledTasks.push({
id: task.id,
scope: scope,
settings: settings,
});
}
createScheduledTaskRunner(schedule: TaskScheduleDefinition): TaskRunner {
@@ -103,6 +99,10 @@ export class PluginTaskSchedulerImpl implements PluginTaskScheduler {
},
};
}
async getScheduledTasks(): Promise<TaskDescriptor[]> {
return this.allScheduledTasks;
}
}
export function parseDuration(
@@ -19,6 +19,7 @@ export { TaskScheduler } from './TaskScheduler';
export type {
PluginTaskScheduler,
TaskFunction,
TaskDescriptor,
TaskInvocationDefinition,
TaskRunner,
TaskScheduleDefinition,
+36 -1
View File
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { HumanDuration } from '@backstage/types';
import { HumanDuration, JsonObject } from '@backstage/types';
import { CronTime } from 'cron';
import { Duration } from 'luxon';
import { z } from 'zod';
@@ -31,6 +31,28 @@ export type TaskFunction =
| ((abortSignal: AbortSignal) => void | Promise<void>)
| (() => void | Promise<void>);
/**
* A semi-opaque type to describe an actively scheduled task.
*
* @public
*/
export type TaskDescriptor = {
/**
* The unique identifier of the task.
*/
id: string;
/**
* The scope of the task.
*/
scope: 'global' | 'local';
/**
* The settings that control the task flow. This is a semi-opaque structure
* that is mainly there for debugging purposes. Do not make any assumptions
* about the contents of this field.
*/
settings: { version: number } & JsonObject;
};
/**
* Options that control the scheduling of a task.
*
@@ -307,6 +329,19 @@ export interface PluginTaskScheduler {
* @param schedule - The task schedule
*/
createScheduledTaskRunner(schedule: TaskScheduleDefinition): TaskRunner;
/**
* Returns all scheduled tasks registered to this scheduler.
*
* @remarks
*
* This method is useful for triggering tasks manually using the triggerTask
* functionality. Note that the returned tasks contain only tasks that have
* been initialized in this instance of the scheduler.
*
* @returns Scheduled tasks
*/
getScheduledTasks(): Promise<TaskDescriptor[]>;
}
function isValidOptionalDurationString(d: string | undefined): boolean {
+11
View File
@@ -1,5 +1,16 @@
# @backstage/backend-test-utils
## 0.1.35-next.2
### Patch Changes
- Updated dependencies
- @backstage/plugin-auth-node@0.2.12-next.2
- @backstage/backend-common@0.18.3-next.2
- @backstage/backend-app-api@0.4.1-next.2
- @backstage/backend-plugin-api@0.4.1-next.2
- @backstage/config@1.0.7-next.0
## 0.1.35-next.1
### Patch Changes
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/backend-test-utils",
"description": "Test helpers library for Backstage backends",
"version": "0.1.35-next.1",
"version": "0.1.35-next.2",
"main": "src/index.ts",
"types": "src/index.ts",
"publishConfig": {
+47
View File
@@ -1,5 +1,52 @@
# example-backend
## 0.2.81-next.2
### Patch Changes
- Updated dependencies
- @backstage/plugin-scaffolder-backend@1.12.0-next.2
- @backstage/plugin-search-backend-module-elasticsearch@1.1.4-next.2
- @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.27-next.2
- @backstage/plugin-auth-node@0.2.12-next.2
- @backstage/backend-tasks@0.5.0-next.2
- @backstage/plugin-adr-backend@0.3.1-next.2
- @backstage/backend-common@0.18.3-next.2
- @backstage/plugin-linguist-backend@0.2.0-next.2
- @backstage/plugin-scaffolder-backend-module-rails@0.4.11-next.2
- example-app@0.2.81-next.2
- @backstage/plugin-techdocs-backend@1.5.4-next.2
- @backstage/plugin-auth-backend@0.18.1-next.2
- @backstage/plugin-entity-feedback-backend@0.1.1-next.2
- @backstage/plugin-jenkins-backend@0.1.33-next.2
- @backstage/plugin-kubernetes-backend@0.9.4-next.2
- @backstage/plugin-permission-backend@0.5.18-next.2
- @backstage/plugin-permission-node@0.7.6-next.2
- @backstage/plugin-playlist-backend@0.2.6-next.2
- @backstage/plugin-search-backend@1.2.4-next.2
- @backstage/plugin-lighthouse-backend@0.1.1-next.2
- @backstage/plugin-search-backend-node@1.1.4-next.2
- @backstage/plugin-tech-insights-backend@0.5.9-next.2
- @backstage/plugin-tech-insights-node@0.4.1-next.2
- @backstage/plugin-app-backend@0.3.43-next.2
- @backstage/plugin-azure-devops-backend@0.3.22-next.2
- @backstage/plugin-azure-sites-backend@0.1.5-next.2
- @backstage/plugin-badges-backend@0.1.37-next.2
- @backstage/plugin-catalog-backend@1.8.0-next.2
- @backstage/plugin-catalog-node@1.3.4-next.2
- @backstage/plugin-code-coverage-backend@0.2.9-next.2
- @backstage/plugin-events-backend@0.2.4-next.2
- @backstage/plugin-explore-backend@0.0.5-next.2
- @backstage/plugin-graphql-backend@0.1.33-next.2
- @backstage/plugin-kafka-backend@0.2.36-next.2
- @backstage/plugin-proxy-backend@0.2.37-next.2
- @backstage/plugin-rollbar-backend@0.1.40-next.2
- @backstage/plugin-search-backend-module-pg@0.5.4-next.2
- @backstage/plugin-todo-backend@0.1.40-next.2
- @backstage/plugin-events-node@0.2.4-next.2
- @backstage/config@1.0.7-next.0
- @backstage/integration@1.4.3-next.0
## 0.2.81-next.1
### Patch Changes
+1 -1
View File
@@ -44,7 +44,7 @@ To debug the backend in [Visual Studio Code](https://code.visualstudio.com/):
If you want to use the catalog functionality, you need to add so called
locations to the backend. These are places where the backend can find some
entity descriptor data to consume and serve. For more information, see
[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog).
[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog).
For convenience we already include some statically configured example locations
in `app-config.yaml` under `catalog.locations`. For local development you can override these in your own `app-config.local.yaml`.

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