diff --git a/.changeset/breezy-bees-care.md b/.changeset/breezy-bees-care.md
new file mode 100644
index 0000000000..9a5b59fe03
--- /dev/null
+++ b/.changeset/breezy-bees-care.md
@@ -0,0 +1,5 @@
+---
+'@backstage/catalog-client': minor
+---
+
+Add `queryEntities` method to `CatalogApi`.
diff --git a/.changeset/bright-kids-raise.md b/.changeset/bright-kids-raise.md
new file mode 100644
index 0000000000..e0d69e0c71
--- /dev/null
+++ b/.changeset/bright-kids-raise.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-todo-backend': patch
+---
+
+todo-backend is now exposed as a plugin which uses the new plugin system
diff --git a/.changeset/brown-islands-own.md b/.changeset/brown-islands-own.md
deleted file mode 100644
index d91ab89fd8..0000000000
--- a/.changeset/brown-islands-own.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-azure-devops': patch
----
-
-Fixed `AssignedToCurrentUsersTeams` & `CreatedByCurrentUsersTeams` filter in `AzurePullRequestsPage` component.
diff --git a/.changeset/clean-planes-join.md b/.changeset/clean-planes-join.md
new file mode 100644
index 0000000000..dd8280f553
--- /dev/null
+++ b/.changeset/clean-planes-join.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-permission-node': patch
+---
+
+Changed the `createPermissionIntegrationRouter` API to allow `getResources`, `resourceType` and `rules` to be optional
diff --git a/.changeset/cool-clocks-prove.md b/.changeset/cool-clocks-prove.md
new file mode 100644
index 0000000000..dfd7b8c5eb
--- /dev/null
+++ b/.changeset/cool-clocks-prove.md
@@ -0,0 +1,5 @@
+---
+'@backstage/cli': patch
+---
+
+Fixed a bug where additional backend package entry points where not properly marked as internal during local development.
diff --git a/.changeset/create-app-1676993958.md b/.changeset/create-app-1676993958.md
new file mode 100644
index 0000000000..b50d431d4b
--- /dev/null
+++ b/.changeset/create-app-1676993958.md
@@ -0,0 +1,5 @@
+---
+'@backstage/create-app': patch
+---
+
+Bumped create-app version.
diff --git a/.changeset/curvy-pets-hang.md b/.changeset/curvy-pets-hang.md
new file mode 100644
index 0000000000..3a2d11ac12
--- /dev/null
+++ b/.changeset/curvy-pets-hang.md
@@ -0,0 +1,5 @@
+---
+'@backstage/repo-tools': patch
+---
+
+Tweaked type dependency check to trim wildcard type imports.
diff --git a/.changeset/eight-falcons-explode.md b/.changeset/eight-falcons-explode.md
deleted file mode 100644
index d9a1a2fe1f..0000000000
--- a/.changeset/eight-falcons-explode.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/backend-app-api': patch
----
-
-Fixed an issue were the log redaction didn't properly escape RegExp characters.
diff --git a/.changeset/eight-radios-bake.md b/.changeset/eight-radios-bake.md
new file mode 100644
index 0000000000..173739a625
--- /dev/null
+++ b/.changeset/eight-radios-bake.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-scaffolder-backend': patch
+---
+
+throw error from catalog:fetch scaffolder action when entity is null and optional is false
diff --git a/.changeset/eighty-geese-return.md b/.changeset/eighty-geese-return.md
new file mode 100644
index 0000000000..49361de8e7
--- /dev/null
+++ b/.changeset/eighty-geese-return.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-backend-module-msgraph': patch
+---
+
+Revert using `baseUrl` for MS Graph provider as it breaks token retrieval
diff --git a/.changeset/flat-cups-itch.md b/.changeset/flat-cups-itch.md
deleted file mode 100644
index 250393c21a..0000000000
--- a/.changeset/flat-cups-itch.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-'@backstage/plugin-scaffolder-react': patch
-'@backstage/plugin-scaffolder': patch
----
-
-lock versions of `@rjsf/*-beta` packages
diff --git a/.changeset/flat-kids-occur.md b/.changeset/flat-kids-occur.md
new file mode 100644
index 0000000000..4e4c002928
--- /dev/null
+++ b/.changeset/flat-kids-occur.md
@@ -0,0 +1,6 @@
+---
+'@backstage/plugin-scaffolder-react': minor
+'@backstage/plugin-scaffolder': minor
+---
+
+Move `useTaskStream`, `TaskBorder`, `TaskLogStream` and `TaskSteps` into `scaffolder-react`.
diff --git a/.changeset/flat-peaches-act.md b/.changeset/flat-peaches-act.md
new file mode 100644
index 0000000000..e4b4138ee5
--- /dev/null
+++ b/.changeset/flat-peaches-act.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-techdocs-backend': patch
+---
+
+Techdocs backend explains a bit more about what might have caused the docs not being found when building techdocs.
diff --git a/.changeset/forty-snails-clap.md b/.changeset/forty-snails-clap.md
new file mode 100644
index 0000000000..f5aef12d9d
--- /dev/null
+++ b/.changeset/forty-snails-clap.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-backend-module-incremental-ingestion': minor
+---
+
+Added endpoint to get a list of known incremental entity providers
diff --git a/.changeset/four-lizards-grin.md b/.changeset/four-lizards-grin.md
new file mode 100644
index 0000000000..6fa5f57243
--- /dev/null
+++ b/.changeset/four-lizards-grin.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-backend': patch
+---
+
+Fix export of `defaultCatalogCollatorEntityTransformer`.
diff --git a/.changeset/fuzzy-trains-search.md b/.changeset/fuzzy-trains-search.md
new file mode 100644
index 0000000000..229d25f3b5
--- /dev/null
+++ b/.changeset/fuzzy-trains-search.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog': patch
+---
+
+Fix icon alignment in `CatalogSearchResultListItem`
diff --git a/.changeset/gentle-pears-clean.md b/.changeset/gentle-pears-clean.md
new file mode 100644
index 0000000000..d92c3709ae
--- /dev/null
+++ b/.changeset/gentle-pears-clean.md
@@ -0,0 +1,5 @@
+---
+'@backstage/eslint-plugin': patch
+---
+
+Fixing a bug that we should check internal dependencies too
diff --git a/.changeset/great-trains-jam.md b/.changeset/great-trains-jam.md
new file mode 100644
index 0000000000..91133bfe32
--- /dev/null
+++ b/.changeset/great-trains-jam.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-techdocs': patch
+---
+
+Fix bug that caused next and previous links not to work with certain versions of mkdocs-material
diff --git a/.changeset/grumpy-bikes-begin.md b/.changeset/grumpy-bikes-begin.md
new file mode 100644
index 0000000000..a98e6651f5
--- /dev/null
+++ b/.changeset/grumpy-bikes-begin.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-tech-insights-backend': patch
+---
+
+Add DB index to improve latency of latest fact query
diff --git a/.changeset/happy-boxes-arrive.md b/.changeset/happy-boxes-arrive.md
new file mode 100644
index 0000000000..85998c1bdd
--- /dev/null
+++ b/.changeset/happy-boxes-arrive.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-scaffolder-react': patch
+---
+
+fix bug with `hasErrors` returning false when dealing with empty objects
diff --git a/.changeset/honest-nails-bake.md b/.changeset/honest-nails-bake.md
new file mode 100644
index 0000000000..03d5a080c9
--- /dev/null
+++ b/.changeset/honest-nails-bake.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-octopus-deploy': minor
+---
+
+Initial version
diff --git a/.changeset/khaki-poems-run.md b/.changeset/khaki-poems-run.md
new file mode 100644
index 0000000000..74db902c02
--- /dev/null
+++ b/.changeset/khaki-poems-run.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-entity-validation': patch
+---
+
+Fixed bug in EntityVaidation plugin where string in text box was unable to be updated.
diff --git a/.changeset/lazy-badgers-rule.md b/.changeset/lazy-badgers-rule.md
deleted file mode 100644
index 748b9dbadc..0000000000
--- a/.changeset/lazy-badgers-rule.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-tech-insights-backend-module-jsonfc': patch
----
-
-Surface the cause of the json rules engine
diff --git a/.changeset/light-sheep-trade.md b/.changeset/light-sheep-trade.md
new file mode 100644
index 0000000000..480ffc973e
--- /dev/null
+++ b/.changeset/light-sheep-trade.md
@@ -0,0 +1,5 @@
+---
+'@backstage/cli': patch
+---
+
+Updated the ESLint plugin configuration that is enabled through `yarn start --check` to only pick up valid source files.
diff --git a/.changeset/long-nails-pump.md b/.changeset/long-nails-pump.md
new file mode 100644
index 0000000000..d9eefc0249
--- /dev/null
+++ b/.changeset/long-nails-pump.md
@@ -0,0 +1,5 @@
+---
+'@backstage/cli': patch
+---
+
+Added a new `migrate package-exports` command that synchronizes package exports fields in all `package.json`s.
diff --git a/.changeset/long-wolves-drive.md b/.changeset/long-wolves-drive.md
new file mode 100644
index 0000000000..1c3ffadb5a
--- /dev/null
+++ b/.changeset/long-wolves-drive.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-octopus-deploy': patch
+---
+
+Added Octopus Deploy logo
diff --git a/.changeset/metal-suns-rhyme.md b/.changeset/metal-suns-rhyme.md
new file mode 100644
index 0000000000..3fefdca7c6
--- /dev/null
+++ b/.changeset/metal-suns-rhyme.md
@@ -0,0 +1,5 @@
+---
+'@backstage/cli': patch
+---
+
+Bumped the `msw` version in templates to 1.0.0
diff --git a/.changeset/new-jobs-deny.md b/.changeset/new-jobs-deny.md
new file mode 100644
index 0000000000..e7b5bff71a
--- /dev/null
+++ b/.changeset/new-jobs-deny.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-react': minor
+---
+
+Add possibility to re-use EntityPicker for filters with multiple select.
diff --git a/.changeset/nice-planets-wave.md b/.changeset/nice-planets-wave.md
new file mode 100644
index 0000000000..2af1a14d83
--- /dev/null
+++ b/.changeset/nice-planets-wave.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-linguist-backend': minor
+---
+
+**BREAKING** The linguist-backend `createRouter` now requires that the `tokenManger` is passed to the router.
diff --git a/.changeset/ninety-turtles-wait.md b/.changeset/ninety-turtles-wait.md
new file mode 100644
index 0000000000..683583528f
--- /dev/null
+++ b/.changeset/ninety-turtles-wait.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-techdocs': patch
+---
+
+The HTML tag attributes in the documentation content inserted to shadow DOM is preserved to improve accessibility
diff --git a/.changeset/odd-fireants-bathe.md b/.changeset/odd-fireants-bathe.md
new file mode 100644
index 0000000000..d4b7f5dc5a
--- /dev/null
+++ b/.changeset/odd-fireants-bathe.md
@@ -0,0 +1,6 @@
+---
+'@backstage/plugin-adr-backend': patch
+'@backstage/cli': patch
+---
+
+Adjust express dependencies to be the same as the rest of the project
diff --git a/.changeset/odd-waves-rescue.md b/.changeset/odd-waves-rescue.md
new file mode 100644
index 0000000000..e435bc9962
--- /dev/null
+++ b/.changeset/odd-waves-rescue.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-scaffolder': patch
+---
+
+Create an "Other Templates" group when groups are given to scaffolder/next.
diff --git a/.changeset/old-foxes-shave.md b/.changeset/old-foxes-shave.md
new file mode 100644
index 0000000000..4cd0808103
--- /dev/null
+++ b/.changeset/old-foxes-shave.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-api-docs': patch
+---
+
+Add ApiDefinitionDialog component for fast access to the API definition in ProvidedApiCards and ConsumedApiCards.
diff --git a/.changeset/perfect-cheetahs-serve.md b/.changeset/perfect-cheetahs-serve.md
deleted file mode 100644
index 08f7c1da21..0000000000
--- a/.changeset/perfect-cheetahs-serve.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-scaffolder-backend': minor
----
-
-Added the option to overwrite files in the `targetPath` of the `template:fetch` action
diff --git a/.changeset/perfect-mayflies-greet.md b/.changeset/perfect-mayflies-greet.md
new file mode 100644
index 0000000000..7e57507dae
--- /dev/null
+++ b/.changeset/perfect-mayflies-greet.md
@@ -0,0 +1,5 @@
+---
+'@backstage/cli': patch
+---
+
+Updated frontend plugin template to use some more recent features
diff --git a/.changeset/pink-falcons-serve.md b/.changeset/pink-falcons-serve.md
deleted file mode 100644
index 8cc6859c25..0000000000
--- a/.changeset/pink-falcons-serve.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-'@backstage/plugin-scaffolder': minor
-'@backstage/plugin-scaffolder-react': minor
----
-
-Embed scaffolder workflow in other components
diff --git a/.changeset/polite-chicken-do.md b/.changeset/polite-chicken-do.md
new file mode 100644
index 0000000000..33e4149253
--- /dev/null
+++ b/.changeset/polite-chicken-do.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-vault-backend': patch
+---
+
+Ignore the `eslint` error
diff --git a/.changeset/polite-falcons-jump.md b/.changeset/polite-falcons-jump.md
new file mode 100644
index 0000000000..ab9209913d
--- /dev/null
+++ b/.changeset/polite-falcons-jump.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-org': patch
+---
+
+Fix small UI-glitch in MemberListCard
diff --git a/.changeset/polite-wombats-smash.md b/.changeset/polite-wombats-smash.md
new file mode 100644
index 0000000000..98569e1799
--- /dev/null
+++ b/.changeset/polite-wombats-smash.md
@@ -0,0 +1,5 @@
+---
+'@backstage/errors': patch
+---
+
+Added `NotImplementedError`, which can be used when the server does not recognize the request method and is incapable of supporting it for any resource.
diff --git a/.changeset/pre.json b/.changeset/pre.json
index 9f9a48e8dc..d3d0c1d42d 100644
--- a/.changeset/pre.json
+++ b/.changeset/pre.json
@@ -2,199 +2,253 @@
"mode": "pre",
"tag": "next",
"initialVersions": {
- "example-app": "0.2.79",
- "@backstage/app-defaults": "1.1.0",
- "example-backend": "0.2.79",
- "@backstage/backend-app-api": "0.3.0",
- "@backstage/backend-common": "0.18.0",
- "@backstage/backend-defaults": "0.1.5",
- "example-backend-next": "0.0.7",
- "@backstage/backend-plugin-api": "0.3.0",
- "@backstage/backend-tasks": "0.4.1",
- "@backstage/backend-test-utils": "0.1.32",
- "@backstage/catalog-client": "1.3.0",
- "@backstage/catalog-model": "1.1.5",
- "@backstage/cli": "0.22.1",
+ "example-app": "0.2.80",
+ "@backstage/app-defaults": "1.2.0",
+ "example-backend": "0.2.80",
+ "@backstage/backend-app-api": "0.4.0",
+ "@backstage/backend-common": "0.18.2",
+ "@backstage/backend-defaults": "0.1.7",
+ "@backstage/backend-dev-utils": "0.1.0",
+ "example-backend-next": "0.0.8",
+ "@backstage/backend-plugin-api": "0.4.0",
+ "@backstage/backend-tasks": "0.4.3",
+ "@backstage/backend-test-utils": "0.1.34",
+ "@backstage/catalog-client": "1.3.1",
+ "@backstage/catalog-model": "1.2.0",
+ "@backstage/cli": "0.22.2",
"@backstage/cli-common": "0.1.11",
- "@backstage/codemods": "0.1.42",
+ "@backstage/codemods": "0.1.43",
"@backstage/config": "1.0.6",
"@backstage/config-loader": "1.1.8",
- "@backstage/core-app-api": "1.4.0",
- "@backstage/core-components": "0.12.3",
- "@backstage/core-plugin-api": "1.3.0",
- "@backstage/create-app": "0.4.36",
- "@backstage/dev-utils": "1.0.11",
+ "@backstage/core-app-api": "1.5.0",
+ "@backstage/core-components": "0.12.4",
+ "@backstage/core-plugin-api": "1.4.0",
+ "@backstage/create-app": "0.4.37",
+ "@backstage/dev-utils": "1.0.12",
"e2e-test": "0.2.0",
"@backstage/errors": "1.1.4",
+ "@backstage/eslint-plugin": "0.1.0",
"@backstage/integration": "1.4.2",
"@backstage/integration-aws-node": "0.1.1",
- "@backstage/integration-react": "1.1.9",
+ "@backstage/integration-react": "1.1.10",
"@backstage/release-manifests": "0.0.8",
- "@backstage/repo-tools": "0.1.1",
- "@techdocs/cli": "1.3.0",
- "techdocs-cli-embedded-app": "0.2.78",
- "@backstage/test-utils": "1.2.4",
- "@backstage/theme": "0.2.16",
+ "@backstage/repo-tools": "0.1.2",
+ "@techdocs/cli": "1.3.2",
+ "techdocs-cli-embedded-app": "0.2.79",
+ "@backstage/test-utils": "1.2.5",
+ "@backstage/theme": "0.2.17",
"@backstage/types": "1.0.2",
"@backstage/version-bridge": "1.0.3",
- "@backstage/plugin-adr": "0.3.0",
- "@backstage/plugin-adr-backend": "0.2.5",
- "@backstage/plugin-adr-common": "0.2.5",
- "@backstage/plugin-airbrake": "0.3.14",
- "@backstage/plugin-airbrake-backend": "0.2.13",
- "@backstage/plugin-allure": "0.1.30",
- "@backstage/plugin-analytics-module-ga": "0.1.25",
- "@backstage/plugin-apache-airflow": "0.2.7",
- "@backstage/plugin-api-docs": "0.8.14",
- "@backstage/plugin-api-docs-module-protoc-gen-doc": "0.1.0",
- "@backstage/plugin-apollo-explorer": "0.1.7",
- "@backstage/plugin-app-backend": "0.3.40",
- "@backstage/plugin-auth-backend": "0.17.3",
- "@backstage/plugin-auth-node": "0.2.9",
- "@backstage/plugin-azure-devops": "0.2.5",
- "@backstage/plugin-azure-devops-backend": "0.3.19",
+ "@backstage/plugin-adr": "0.4.0",
+ "@backstage/plugin-adr-backend": "0.3.0",
+ "@backstage/plugin-adr-common": "0.2.6",
+ "@backstage/plugin-airbrake": "0.3.15",
+ "@backstage/plugin-airbrake-backend": "0.2.15",
+ "@backstage/plugin-allure": "0.1.31",
+ "@backstage/plugin-analytics-module-ga": "0.1.26",
+ "@backstage/plugin-apache-airflow": "0.2.8",
+ "@backstage/plugin-api-docs": "0.9.0",
+ "@backstage/plugin-api-docs-module-protoc-gen-doc": "0.1.1",
+ "@backstage/plugin-apollo-explorer": "0.1.8",
+ "@backstage/plugin-app-backend": "0.3.42",
+ "@backstage/plugin-auth-backend": "0.18.0",
+ "@backstage/plugin-auth-node": "0.2.11",
+ "@backstage/plugin-azure-devops": "0.2.6",
+ "@backstage/plugin-azure-devops-backend": "0.3.21",
"@backstage/plugin-azure-devops-common": "0.3.0",
- "@backstage/plugin-azure-sites": "0.1.3",
- "@backstage/plugin-azure-sites-backend": "0.1.2",
+ "@backstage/plugin-azure-sites": "0.1.4",
+ "@backstage/plugin-azure-sites-backend": "0.1.4",
"@backstage/plugin-azure-sites-common": "0.1.0",
- "@backstage/plugin-badges": "0.2.38",
- "@backstage/plugin-badges-backend": "0.1.34",
- "@backstage/plugin-bazaar": "0.2.3",
- "@backstage/plugin-bazaar-backend": "0.2.3",
+ "@backstage/plugin-badges": "0.2.39",
+ "@backstage/plugin-badges-backend": "0.1.36",
+ "@backstage/plugin-bazaar": "0.2.4",
+ "@backstage/plugin-bazaar-backend": "0.2.5",
"@backstage/plugin-bitbucket-cloud-common": "0.2.3",
- "@backstage/plugin-bitrise": "0.1.41",
- "@backstage/plugin-catalog": "1.7.2",
- "@backstage/plugin-catalog-backend": "1.7.0",
- "@backstage/plugin-catalog-backend-module-aws": "0.1.13",
- "@backstage/plugin-catalog-backend-module-azure": "0.1.11",
- "@backstage/plugin-catalog-backend-module-bitbucket": "0.2.7",
- "@backstage/plugin-catalog-backend-module-bitbucket-cloud": "0.1.7",
- "@backstage/plugin-catalog-backend-module-bitbucket-server": "0.1.5",
- "@backstage/plugin-catalog-backend-module-gerrit": "0.1.8",
- "@backstage/plugin-catalog-backend-module-github": "0.2.3",
- "@backstage/plugin-catalog-backend-module-gitlab": "0.1.11",
- "@backstage/plugin-catalog-backend-module-incremental-ingestion": "0.1.1",
- "@backstage/plugin-catalog-backend-module-ldap": "0.5.7",
- "@backstage/plugin-catalog-backend-module-msgraph": "0.4.6",
- "@backstage/plugin-catalog-backend-module-openapi": "0.1.6",
- "@backstage/plugin-catalog-common": "1.0.10",
- "@internal/plugin-catalog-customized": "0.0.6",
- "@backstage/plugin-catalog-graph": "0.2.26",
- "@backstage/plugin-catalog-graphql": "0.3.17",
- "@backstage/plugin-catalog-import": "0.9.4",
- "@backstage/plugin-catalog-node": "1.3.1",
- "@backstage/plugin-catalog-react": "1.2.4",
- "@backstage/plugin-cicd-statistics": "0.1.16",
- "@backstage/plugin-cicd-statistics-module-gitlab": "0.1.10",
- "@backstage/plugin-circleci": "0.3.14",
- "@backstage/plugin-cloudbuild": "0.3.14",
- "@backstage/plugin-code-climate": "0.1.14",
- "@backstage/plugin-code-coverage": "0.2.7",
- "@backstage/plugin-code-coverage-backend": "0.2.6",
- "@backstage/plugin-codescene": "0.1.9",
- "@backstage/plugin-config-schema": "0.1.37",
- "@backstage/plugin-cost-insights": "0.12.3",
+ "@backstage/plugin-bitrise": "0.1.42",
+ "@backstage/plugin-catalog": "1.8.0",
+ "@backstage/plugin-catalog-backend": "1.7.2",
+ "@backstage/plugin-catalog-backend-module-aws": "0.1.15",
+ "@backstage/plugin-catalog-backend-module-azure": "0.1.13",
+ "@backstage/plugin-catalog-backend-module-bitbucket": "0.2.9",
+ "@backstage/plugin-catalog-backend-module-bitbucket-cloud": "0.1.9",
+ "@backstage/plugin-catalog-backend-module-bitbucket-server": "0.1.7",
+ "@backstage/plugin-catalog-backend-module-gerrit": "0.1.10",
+ "@backstage/plugin-catalog-backend-module-github": "0.2.5",
+ "@backstage/plugin-catalog-backend-module-gitlab": "0.1.13",
+ "@backstage/plugin-catalog-backend-module-incremental-ingestion": "0.2.0",
+ "@backstage/plugin-catalog-backend-module-ldap": "0.5.9",
+ "@backstage/plugin-catalog-backend-module-msgraph": "0.5.0",
+ "@backstage/plugin-catalog-backend-module-openapi": "0.1.8",
+ "@backstage/plugin-catalog-common": "1.0.11",
+ "@internal/plugin-catalog-customized": "0.0.7",
+ "@backstage/plugin-catalog-graph": "0.2.27",
+ "@backstage/plugin-catalog-graphql": "0.3.18",
+ "@backstage/plugin-catalog-import": "0.9.5",
+ "@backstage/plugin-catalog-node": "1.3.3",
+ "@backstage/plugin-catalog-react": "1.3.0",
+ "@backstage/plugin-cicd-statistics": "0.1.17",
+ "@backstage/plugin-cicd-statistics-module-gitlab": "0.1.11",
+ "@backstage/plugin-circleci": "0.3.15",
+ "@backstage/plugin-cloudbuild": "0.3.15",
+ "@backstage/plugin-code-climate": "0.1.15",
+ "@backstage/plugin-code-coverage": "0.2.8",
+ "@backstage/plugin-code-coverage-backend": "0.2.8",
+ "@backstage/plugin-codescene": "0.1.10",
+ "@backstage/plugin-config-schema": "0.1.38",
+ "@backstage/plugin-cost-insights": "0.12.4",
"@backstage/plugin-cost-insights-common": "0.1.1",
- "@backstage/plugin-dynatrace": "1.0.4",
- "@backstage/plugin-events-backend": "0.2.1",
- "@backstage/plugin-events-backend-module-aws-sqs": "0.1.2",
- "@backstage/plugin-events-backend-module-azure": "0.1.2",
- "@backstage/plugin-events-backend-module-bitbucket-cloud": "0.1.2",
- "@backstage/plugin-events-backend-module-gerrit": "0.1.2",
- "@backstage/plugin-events-backend-module-github": "0.1.2",
- "@backstage/plugin-events-backend-module-gitlab": "0.1.2",
- "@backstage/plugin-events-backend-test-utils": "0.1.2",
- "@backstage/plugin-events-node": "0.2.1",
- "@internal/plugin-todo-list": "1.0.9",
- "@internal/plugin-todo-list-backend": "1.0.9",
+ "@backstage/plugin-dynatrace": "2.0.0",
+ "@backstage/plugin-entity-feedback": "0.1.0",
+ "@backstage/plugin-entity-feedback-backend": "0.1.0",
+ "@backstage/plugin-entity-feedback-common": "0.1.0",
+ "@backstage/plugin-entity-validation": "0.1.0",
+ "@backstage/plugin-events-backend": "0.2.3",
+ "@backstage/plugin-events-backend-module-aws-sqs": "0.1.4",
+ "@backstage/plugin-events-backend-module-azure": "0.1.4",
+ "@backstage/plugin-events-backend-module-bitbucket-cloud": "0.1.4",
+ "@backstage/plugin-events-backend-module-gerrit": "0.1.4",
+ "@backstage/plugin-events-backend-module-github": "0.1.4",
+ "@backstage/plugin-events-backend-module-gitlab": "0.1.4",
+ "@backstage/plugin-events-backend-test-utils": "0.1.4",
+ "@backstage/plugin-events-node": "0.2.3",
+ "@internal/plugin-todo-list": "1.0.10",
+ "@internal/plugin-todo-list-backend": "1.0.10",
"@internal/plugin-todo-list-common": "1.0.8",
- "@backstage/plugin-explore": "0.3.45",
- "@backstage/plugin-explore-backend": "0.0.2",
+ "@backstage/plugin-explore": "0.4.0",
+ "@backstage/plugin-explore-backend": "0.0.4",
"@backstage/plugin-explore-common": "0.0.1",
- "@backstage/plugin-explore-react": "0.0.25",
- "@backstage/plugin-firehydrant": "0.1.31",
- "@backstage/plugin-fossa": "0.2.46",
- "@backstage/plugin-gcalendar": "0.3.10",
- "@backstage/plugin-gcp-projects": "0.3.33",
- "@backstage/plugin-git-release-manager": "0.3.27",
- "@backstage/plugin-github-actions": "0.5.14",
- "@backstage/plugin-github-deployments": "0.1.45",
- "@backstage/plugin-github-issues": "0.2.3",
- "@backstage/plugin-github-pull-requests-board": "0.1.8",
- "@backstage/plugin-gitops-profiles": "0.3.32",
- "@backstage/plugin-gocd": "0.1.20",
- "@backstage/plugin-graphiql": "0.2.46",
- "@backstage/plugin-graphql-backend": "0.1.30",
- "@backstage/plugin-home": "0.4.30",
- "@backstage/plugin-ilert": "0.2.3",
- "@backstage/plugin-jenkins": "0.7.13",
- "@backstage/plugin-jenkins-backend": "0.1.30",
- "@backstage/plugin-jenkins-common": "0.1.12",
- "@backstage/plugin-kafka": "0.3.14",
- "@backstage/plugin-kafka-backend": "0.2.33",
- "@backstage/plugin-kubernetes": "0.7.7",
- "@backstage/plugin-kubernetes-backend": "0.9.1",
- "@backstage/plugin-kubernetes-common": "0.5.1",
- "@backstage/plugin-lighthouse": "0.3.14",
- "@backstage/plugin-newrelic": "0.3.32",
- "@backstage/plugin-newrelic-dashboard": "0.2.7",
- "@backstage/plugin-org": "0.6.4",
- "@backstage/plugin-org-react": "0.1.3",
- "@backstage/plugin-pagerduty": "0.5.7",
- "@backstage/plugin-periskop": "0.1.12",
- "@backstage/plugin-periskop-backend": "0.1.11",
- "@backstage/plugin-permission-backend": "0.5.15",
+ "@backstage/plugin-explore-react": "0.0.26",
+ "@backstage/plugin-firehydrant": "0.1.32",
+ "@backstage/plugin-fossa": "0.2.47",
+ "@backstage/plugin-gcalendar": "0.3.11",
+ "@backstage/plugin-gcp-projects": "0.3.34",
+ "@backstage/plugin-git-release-manager": "0.3.28",
+ "@backstage/plugin-github-actions": "0.5.15",
+ "@backstage/plugin-github-deployments": "0.1.46",
+ "@backstage/plugin-github-issues": "0.2.4",
+ "@backstage/plugin-github-pull-requests-board": "0.1.9",
+ "@backstage/plugin-gitops-profiles": "0.3.33",
+ "@backstage/plugin-gocd": "0.1.21",
+ "@backstage/plugin-graphiql": "0.2.47",
+ "@backstage/plugin-graphql-backend": "0.1.32",
+ "@backstage/plugin-graphql-voyager": "0.1.0",
+ "@backstage/plugin-home": "0.4.31",
+ "@backstage/plugin-ilert": "0.2.4",
+ "@backstage/plugin-jenkins": "0.7.14",
+ "@backstage/plugin-jenkins-backend": "0.1.32",
+ "@backstage/plugin-jenkins-common": "0.1.13",
+ "@backstage/plugin-kafka": "0.3.15",
+ "@backstage/plugin-kafka-backend": "0.2.35",
+ "@backstage/plugin-kubernetes": "0.7.8",
+ "@backstage/plugin-kubernetes-backend": "0.9.3",
+ "@backstage/plugin-kubernetes-common": "0.6.0",
+ "@backstage/plugin-lighthouse": "0.4.0",
+ "@backstage/plugin-lighthouse-backend": "0.1.0",
+ "@backstage/plugin-lighthouse-common": "0.1.0",
+ "@backstage/plugin-linguist": "0.1.0",
+ "@backstage/plugin-linguist-backend": "0.1.0",
+ "@backstage/plugin-linguist-common": "0.1.0",
+ "@backstage/plugin-microsoft-calendar": "0.1.0",
+ "@backstage/plugin-newrelic": "0.3.33",
+ "@backstage/plugin-newrelic-dashboard": "0.2.8",
+ "@backstage/plugin-org": "0.6.5",
+ "@backstage/plugin-org-react": "0.1.4",
+ "@backstage/plugin-pagerduty": "0.5.8",
+ "@backstage/plugin-periskop": "0.1.13",
+ "@backstage/plugin-periskop-backend": "0.1.13",
+ "@backstage/plugin-permission-backend": "0.5.17",
"@backstage/plugin-permission-common": "0.7.3",
- "@backstage/plugin-permission-node": "0.7.3",
- "@backstage/plugin-permission-react": "0.4.9",
- "@backstage/plugin-playlist": "0.1.5",
- "@backstage/plugin-playlist-backend": "0.2.3",
+ "@backstage/plugin-permission-node": "0.7.5",
+ "@backstage/plugin-permission-react": "0.4.10",
+ "@backstage/plugin-playlist": "0.1.6",
+ "@backstage/plugin-playlist-backend": "0.2.5",
"@backstage/plugin-playlist-common": "0.1.4",
- "@backstage/plugin-proxy-backend": "0.2.34",
- "@backstage/plugin-rollbar": "0.4.14",
- "@backstage/plugin-rollbar-backend": "0.1.37",
- "@backstage/plugin-scaffolder": "1.10.0",
- "@backstage/plugin-scaffolder-backend": "1.10.0",
- "@backstage/plugin-scaffolder-backend-module-cookiecutter": "0.2.15",
- "@backstage/plugin-scaffolder-backend-module-rails": "0.4.8",
- "@backstage/plugin-scaffolder-backend-module-sentry": "0.1.0",
- "@backstage/plugin-scaffolder-backend-module-yeoman": "0.2.13",
- "@backstage/plugin-scaffolder-common": "1.2.4",
- "@backstage/plugin-scaffolder-react": "1.0.0",
- "@backstage/plugin-search": "1.0.7",
- "@backstage/plugin-search-backend": "1.2.1",
- "@backstage/plugin-search-backend-module-elasticsearch": "1.1.1",
- "@backstage/plugin-search-backend-module-pg": "0.5.1",
- "@backstage/plugin-search-backend-node": "1.1.1",
+ "@backstage/plugin-proxy-backend": "0.2.36",
+ "@backstage/plugin-rollbar": "0.4.15",
+ "@backstage/plugin-rollbar-backend": "0.1.39",
+ "@backstage/plugin-scaffolder": "1.11.0",
+ "@backstage/plugin-scaffolder-backend": "1.11.0",
+ "@backstage/plugin-scaffolder-backend-module-cookiecutter": "0.2.17",
+ "@backstage/plugin-scaffolder-backend-module-rails": "0.4.10",
+ "@backstage/plugin-scaffolder-backend-module-sentry": "0.1.2",
+ "@backstage/plugin-scaffolder-backend-module-yeoman": "0.2.15",
+ "@backstage/plugin-scaffolder-common": "1.2.5",
+ "@backstage/plugin-scaffolder-node": "0.1.0",
+ "@backstage/plugin-scaffolder-react": "1.1.0",
+ "@backstage/plugin-search": "1.1.0",
+ "@backstage/plugin-search-backend": "1.2.3",
+ "@backstage/plugin-search-backend-module-elasticsearch": "1.1.3",
+ "@backstage/plugin-search-backend-module-pg": "0.5.3",
+ "@backstage/plugin-search-backend-node": "1.1.3",
"@backstage/plugin-search-common": "1.2.1",
- "@backstage/plugin-search-react": "1.4.0",
- "@backstage/plugin-sentry": "0.4.7",
- "@backstage/plugin-shortcuts": "0.3.6",
- "@backstage/plugin-sonarqube": "0.6.2",
- "@backstage/plugin-sonarqube-backend": "0.1.5",
- "@backstage/plugin-sonarqube-react": "0.1.1",
- "@backstage/plugin-splunk-on-call": "0.4.3",
- "@backstage/plugin-stack-overflow": "0.1.10",
- "@backstage/plugin-stack-overflow-backend": "0.1.9",
- "@backstage/plugin-tech-insights": "0.3.6",
- "@backstage/plugin-tech-insights-backend": "0.5.6",
- "@backstage/plugin-tech-insights-backend-module-jsonfc": "0.1.24",
- "@backstage/plugin-tech-insights-common": "0.2.9",
- "@backstage/plugin-tech-insights-node": "0.3.8",
- "@backstage/plugin-tech-radar": "0.6.0",
- "@backstage/plugin-techdocs": "1.4.3",
- "@backstage/plugin-techdocs-addons-test-utils": "1.0.9",
- "@backstage/plugin-techdocs-backend": "1.5.1",
- "@backstage/plugin-techdocs-module-addons-contrib": "1.0.9",
- "@backstage/plugin-techdocs-node": "1.4.4",
- "@backstage/plugin-techdocs-react": "1.1.2",
- "@backstage/plugin-todo": "0.2.16",
- "@backstage/plugin-todo-backend": "0.1.37",
- "@backstage/plugin-user-settings": "0.6.2",
- "@backstage/plugin-user-settings-backend": "0.1.4",
- "@backstage/plugin-vault": "0.1.8",
- "@backstage/plugin-vault-backend": "0.2.6",
- "@backstage/plugin-xcmetrics": "0.2.34"
+ "@backstage/plugin-search-react": "1.5.0",
+ "@backstage/plugin-sentry": "0.5.0",
+ "@backstage/plugin-shortcuts": "0.3.7",
+ "@backstage/plugin-sonarqube": "0.6.3",
+ "@backstage/plugin-sonarqube-backend": "0.1.7",
+ "@backstage/plugin-sonarqube-react": "0.1.2",
+ "@backstage/plugin-splunk-on-call": "0.4.4",
+ "@backstage/plugin-stack-overflow": "0.1.11",
+ "@backstage/plugin-stack-overflow-backend": "0.1.11",
+ "@backstage/plugin-tech-insights": "0.3.7",
+ "@backstage/plugin-tech-insights-backend": "0.5.8",
+ "@backstage/plugin-tech-insights-backend-module-jsonfc": "0.1.26",
+ "@backstage/plugin-tech-insights-common": "0.2.10",
+ "@backstage/plugin-tech-insights-node": "0.4.0",
+ "@backstage/plugin-tech-radar": "0.6.1",
+ "@backstage/plugin-techdocs": "1.5.0",
+ "@backstage/plugin-techdocs-addons-test-utils": "1.0.10",
+ "@backstage/plugin-techdocs-backend": "1.5.3",
+ "@backstage/plugin-techdocs-module-addons-contrib": "1.0.10",
+ "@backstage/plugin-techdocs-node": "1.5.0",
+ "@backstage/plugin-techdocs-react": "1.1.3",
+ "@backstage/plugin-todo": "0.2.17",
+ "@backstage/plugin-todo-backend": "0.1.39",
+ "@backstage/plugin-user-settings": "0.7.0",
+ "@backstage/plugin-user-settings-backend": "0.1.6",
+ "@backstage/plugin-vault": "0.1.9",
+ "@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"
},
- "changesets": []
+ "changesets": [
+ "breezy-bees-care",
+ "bright-kids-raise",
+ "create-app-1676993958",
+ "curvy-pets-hang",
+ "eight-radios-bake",
+ "eighty-geese-return",
+ "four-lizards-grin",
+ "gentle-pears-clean",
+ "grumpy-bikes-begin",
+ "happy-boxes-arrive",
+ "honest-nails-bake",
+ "long-nails-pump",
+ "new-jobs-deny",
+ "nice-planets-wave",
+ "ninety-turtles-wait",
+ "odd-fireants-bathe",
+ "old-foxes-shave",
+ "perfect-mayflies-greet",
+ "polite-chicken-do",
+ "polite-falcons-jump",
+ "quick-schools-dress",
+ "quick-tables-listen",
+ "rare-buses-swim",
+ "rare-grapes-count",
+ "rare-pandas-hope",
+ "silly-suits-run",
+ "six-melons-rhyme",
+ "slimy-lobsters-kneel",
+ "slow-insects-cheat",
+ "slow-suits-grab",
+ "spotty-bees-tell",
+ "spotty-wasps-complain",
+ "swift-bags-wave",
+ "ten-tigers-marry",
+ "thin-candles-wait",
+ "twenty-jeans-speak",
+ "yellow-bananas-yawn"
+ ]
}
diff --git a/.changeset/purple-days-film.md b/.changeset/purple-days-film.md
new file mode 100644
index 0000000000..09d760c33e
--- /dev/null
+++ b/.changeset/purple-days-film.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-techdocs-node': patch
+---
+
+Fixed bug that prevented deletion of stale files when non-empty directories where tried to be deleted as well.
diff --git a/.changeset/quick-coats-sneeze.md b/.changeset/quick-coats-sneeze.md
new file mode 100644
index 0000000000..24f7f70e83
--- /dev/null
+++ b/.changeset/quick-coats-sneeze.md
@@ -0,0 +1,5 @@
+---
+'@techdocs/cli': minor
+---
+
+Running `@techdocs/cli generate` with the `--verbose` flag will now print the mkdocs output.
diff --git a/.changeset/quick-schools-dress.md b/.changeset/quick-schools-dress.md
new file mode 100644
index 0000000000..511709461e
--- /dev/null
+++ b/.changeset/quick-schools-dress.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-techdocs-addons-test-utils': patch
+---
+
+Fix bug/problem with test helper
diff --git a/.changeset/quick-tables-listen.md b/.changeset/quick-tables-listen.md
new file mode 100644
index 0000000000..fab9ef7969
--- /dev/null
+++ b/.changeset/quick-tables-listen.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-stackstorm': minor
+---
+
+Add StackStorm plugin to Backstage which is interfacing with StackStorm API. It allows users to view workflow executions, packs and actions. For installation and configuration instructions please view [README.md](https://github.com/backstage/backstage/tree/master/plugins/stackstorm/README.md)
diff --git a/.changeset/quiet-ghosts-float.md b/.changeset/quiet-ghosts-float.md
new file mode 100644
index 0000000000..dc8df90206
--- /dev/null
+++ b/.changeset/quiet-ghosts-float.md
@@ -0,0 +1,12 @@
+---
+'@backstage/plugin-entity-validation': patch
+'@backstage/core-components': patch
+'@backstage/plugin-kubernetes': patch
+'@backstage/plugin-scaffolder': patch
+'@backstage/plugin-circleci': patch
+'@backstage/plugin-playlist': patch
+'@backstage/plugin-techdocs': patch
+'@backstage/plugin-explore': patch
+---
+
+Switch to using `LinkButton` instead of the deprecated `Button`
diff --git a/.changeset/rare-buses-swim.md b/.changeset/rare-buses-swim.md
new file mode 100644
index 0000000000..4626343ca3
--- /dev/null
+++ b/.changeset/rare-buses-swim.md
@@ -0,0 +1,5 @@
+---
+'@backstage/backend-tasks': patch
+---
+
+Added the adapted query to mysql and sqlite3 databases to not returning warning on logs
diff --git a/.changeset/rare-grapes-count.md b/.changeset/rare-grapes-count.md
new file mode 100644
index 0000000000..fdeb653f3c
--- /dev/null
+++ b/.changeset/rare-grapes-count.md
@@ -0,0 +1,45 @@
+---
+'@backstage/plugin-catalog-backend-module-incremental-ingestion': patch
+'@backstage/plugin-catalog-backend-module-bitbucket-server': patch
+'@backstage/plugin-catalog-backend-module-bitbucket-cloud': patch
+'@backstage/plugin-events-backend-module-bitbucket-cloud': patch
+'@backstage/plugin-catalog-backend-module-msgraph': patch
+'@backstage/plugin-catalog-backend-module-gerrit': patch
+'@backstage/plugin-catalog-backend-module-github': patch
+'@backstage/plugin-catalog-backend-module-gitlab': patch
+'@backstage/plugin-events-backend-module-aws-sqs': patch
+'@backstage/plugin-catalog-backend-module-azure': patch
+'@backstage/plugin-events-backend-module-gerrit': patch
+'@backstage/plugin-events-backend-module-github': patch
+'@backstage/plugin-events-backend-module-gitlab': patch
+'@backstage/plugin-events-backend-module-azure': patch
+'@backstage/plugin-catalog-backend-module-aws': patch
+'@backstage/backend-plugin-api': patch
+'@backstage/backend-test-utils': patch
+'@backstage/plugin-scaffolder-backend': patch
+'@backstage/backend-defaults': patch
+'@backstage/backend-app-api': patch
+'@backstage/core-plugin-api': patch
+'@backstage/plugin-scaffolder-react': patch
+'@backstage/plugin-techdocs-backend': patch
+'@backstage/backend-common': patch
+'@backstage/plugin-catalog-backend': patch
+'@backstage/plugin-sonarqube-react': patch
+'@backstage/catalog-model': patch
+'@backstage/plugin-catalog-common': patch
+'@backstage/plugin-events-backend': patch
+'@backstage/plugin-jenkins-common': patch
+'@backstage/plugin-techdocs-react': patch
+'@backstage/plugin-catalog-react': patch
+'@backstage/plugin-cost-insights': patch
+'@backstage/plugin-catalog-node': patch
+'@backstage/test-utils': patch
+'@backstage/plugin-app-backend': patch
+'@backstage/plugin-events-node': patch
+'@backstage/plugin-scaffolder': patch
+'@backstage/plugin-sonarqube': patch
+'@backstage/plugin-catalog': patch
+'@backstage/plugin-jenkins': patch
+---
+
+Internal refactor of `/alpha` exports.
diff --git a/.changeset/rare-pandas-hope.md b/.changeset/rare-pandas-hope.md
new file mode 100644
index 0000000000..6e8e81fcfb
--- /dev/null
+++ b/.changeset/rare-pandas-hope.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-playlist': patch
+---
+
+Internal update to work with newer versions of react-router
diff --git a/.changeset/renovate-3672e5e.md b/.changeset/renovate-3672e5e.md
new file mode 100644
index 0000000000..c94eec2245
--- /dev/null
+++ b/.changeset/renovate-3672e5e.md
@@ -0,0 +1,5 @@
+---
+'@backstage/cli': patch
+---
+
+Updated dependency `del` to `^7.0.0`.
diff --git a/.changeset/renovate-4775edd.md b/.changeset/renovate-4775edd.md
new file mode 100644
index 0000000000..6472fac254
--- /dev/null
+++ b/.changeset/renovate-4775edd.md
@@ -0,0 +1,8 @@
+---
+'@backstage/plugin-catalog-graphql': patch
+---
+
+Updated dependency `@graphql-codegen/cli` to `^3.0.0`.
+Updated dependency `@graphql-codegen/graphql-modules-preset` to `^3.0.0`.
+Updated dependency `@graphql-codegen/typescript` to `^3.0.0`.
+Updated dependency `@graphql-codegen/typescript-resolvers` to `^3.0.0`.
diff --git a/.changeset/renovate-488b7e7.md b/.changeset/renovate-488b7e7.md
new file mode 100644
index 0000000000..35940f474b
--- /dev/null
+++ b/.changeset/renovate-488b7e7.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-auth-backend': patch
+---
+
+Updated dependency `@types/passport-microsoft` to `^1.0.0`.
diff --git a/.changeset/renovate-80f8787.md b/.changeset/renovate-80f8787.md
new file mode 100644
index 0000000000..addac46355
--- /dev/null
+++ b/.changeset/renovate-80f8787.md
@@ -0,0 +1,6 @@
+---
+'@backstage/plugin-techdocs-react': patch
+'@backstage/plugin-techdocs': patch
+---
+
+Updated dependency `jss` to `~10.10.0`.
diff --git a/.changeset/renovate-fb85ae7.md b/.changeset/renovate-fb85ae7.md
new file mode 100644
index 0000000000..920469850c
--- /dev/null
+++ b/.changeset/renovate-fb85ae7.md
@@ -0,0 +1,125 @@
+---
+'@backstage/backend-common': patch
+'@backstage/backend-test-utils': patch
+'@backstage/catalog-client': patch
+'@backstage/cli': patch
+'@backstage/config-loader': patch
+'@backstage/core-app-api': patch
+'@backstage/core-components': patch
+'@backstage/core-plugin-api': patch
+'@backstage/integration-react': patch
+'@backstage/integration': patch
+'@backstage/release-manifests': patch
+'@backstage/test-utils': patch
+'@backstage/plugin-adr-backend': patch
+'@backstage/plugin-adr': patch
+'@backstage/plugin-airbrake-backend': patch
+'@backstage/plugin-airbrake': patch
+'@backstage/plugin-allure': patch
+'@backstage/plugin-analytics-module-ga': patch
+'@backstage/plugin-apache-airflow': patch
+'@backstage/plugin-api-docs': patch
+'@backstage/plugin-apollo-explorer': patch
+'@backstage/plugin-app-backend': patch
+'@backstage/plugin-auth-backend': patch
+'@backstage/plugin-auth-node': patch
+'@backstage/plugin-azure-devops-backend': patch
+'@backstage/plugin-azure-devops': patch
+'@backstage/plugin-azure-sites-backend': patch
+'@backstage/plugin-azure-sites': patch
+'@backstage/plugin-badges': patch
+'@backstage/plugin-bitbucket-cloud-common': patch
+'@backstage/plugin-bitrise': patch
+'@backstage/plugin-catalog-backend-module-azure': patch
+'@backstage/plugin-catalog-backend-module-bitbucket-cloud': patch
+'@backstage/plugin-catalog-backend-module-bitbucket-server': patch
+'@backstage/plugin-catalog-backend-module-bitbucket': patch
+'@backstage/plugin-catalog-backend-module-gerrit': patch
+'@backstage/plugin-catalog-backend-module-github': patch
+'@backstage/plugin-catalog-backend-module-gitlab': patch
+'@backstage/plugin-catalog-backend-module-msgraph': patch
+'@backstage/plugin-catalog-backend': patch
+'@backstage/plugin-catalog-graphql': patch
+'@backstage/plugin-catalog-import': patch
+'@backstage/plugin-circleci': patch
+'@backstage/plugin-cloudbuild': patch
+'@backstage/plugin-code-climate': patch
+'@backstage/plugin-code-coverage-backend': patch
+'@backstage/plugin-code-coverage': patch
+'@backstage/plugin-codescene': patch
+'@backstage/plugin-config-schema': patch
+'@backstage/plugin-cost-insights': patch
+'@backstage/plugin-dynatrace': patch
+'@backstage/plugin-entity-feedback-backend': patch
+'@backstage/plugin-entity-feedback': patch
+'@backstage/plugin-entity-validation': patch
+'@backstage/plugin-explore-backend': patch
+'@backstage/plugin-explore-react': patch
+'@backstage/plugin-explore': patch
+'@backstage/plugin-firehydrant': patch
+'@backstage/plugin-fossa': patch
+'@backstage/plugin-gcalendar': patch
+'@backstage/plugin-gcp-projects': patch
+'@backstage/plugin-git-release-manager': patch
+'@backstage/plugin-github-actions': patch
+'@backstage/plugin-github-deployments': patch
+'@backstage/plugin-github-issues': patch
+'@backstage/plugin-github-pull-requests-board': patch
+'@backstage/plugin-gitops-profiles': patch
+'@backstage/plugin-gocd': patch
+'@backstage/plugin-graphiql': patch
+'@backstage/plugin-graphql-backend': patch
+'@backstage/plugin-graphql-voyager': patch
+'@backstage/plugin-home': patch
+'@backstage/plugin-ilert': patch
+'@backstage/plugin-jenkins-backend': patch
+'@backstage/plugin-jenkins': patch
+'@backstage/plugin-kafka': patch
+'@backstage/plugin-kubernetes-backend': patch
+'@backstage/plugin-kubernetes': patch
+'@backstage/plugin-lighthouse': patch
+'@backstage/plugin-linguist-backend': patch
+'@backstage/plugin-linguist': patch
+'@backstage/plugin-microsoft-calendar': patch
+'@backstage/plugin-newrelic': patch
+'@backstage/plugin-octopus-deploy': patch
+'@backstage/plugin-org-react': patch
+'@backstage/plugin-org': patch
+'@backstage/plugin-pagerduty': patch
+'@backstage/plugin-periskop-backend': patch
+'@backstage/plugin-periskop': patch
+'@backstage/plugin-permission-backend': patch
+'@backstage/plugin-permission-common': patch
+'@backstage/plugin-permission-node': patch
+'@backstage/plugin-playlist-backend': patch
+'@backstage/plugin-playlist': patch
+'@backstage/plugin-proxy-backend': patch
+'@backstage/plugin-rollbar-backend': patch
+'@backstage/plugin-rollbar': patch
+'@backstage/plugin-scaffolder-backend-module-cookiecutter': patch
+'@backstage/plugin-scaffolder-backend': patch
+'@backstage/plugin-scaffolder': patch
+'@backstage/plugin-search': patch
+'@backstage/plugin-sentry': patch
+'@backstage/plugin-shortcuts': patch
+'@backstage/plugin-sonarqube-backend': patch
+'@backstage/plugin-sonarqube': patch
+'@backstage/plugin-splunk-on-call': patch
+'@backstage/plugin-stack-overflow-backend': patch
+'@backstage/plugin-stack-overflow': patch
+'@backstage/plugin-stackstorm': patch
+'@backstage/plugin-tech-insights': patch
+'@backstage/plugin-tech-radar': patch
+'@backstage/plugin-techdocs-addons-test-utils': patch
+'@backstage/plugin-techdocs-backend': patch
+'@backstage/plugin-techdocs-module-addons-contrib': patch
+'@backstage/plugin-techdocs': patch
+'@backstage/plugin-todo-backend': patch
+'@backstage/plugin-todo': patch
+'@backstage/plugin-user-settings': patch
+'@backstage/plugin-vault-backend': patch
+'@backstage/plugin-vault': patch
+'@backstage/plugin-xcmetrics': patch
+---
+
+Updated dependency `msw` to `^1.0.0`.
diff --git a/.changeset/rich-clocks-approve.md b/.changeset/rich-clocks-approve.md
new file mode 100644
index 0000000000..319a8a7c9c
--- /dev/null
+++ b/.changeset/rich-clocks-approve.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-lighthouse-backend': patch
+---
+
+Update README.md; typo with plugin name & removed unneeded "import { Router } from 'express';"
diff --git a/.changeset/rich-wombats-rescue.md b/.changeset/rich-wombats-rescue.md
new file mode 100644
index 0000000000..d62f38cb4a
--- /dev/null
+++ b/.changeset/rich-wombats-rescue.md
@@ -0,0 +1,5 @@
+---
+'@backstage/core-components': patch
+---
+
+Updated the `aria-label` of the `HeaderTabs` component.
diff --git a/.changeset/short-mayflies-fix.md b/.changeset/short-mayflies-fix.md
new file mode 100644
index 0000000000..9d68a85da7
--- /dev/null
+++ b/.changeset/short-mayflies-fix.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-pagerduty': patch
+---
+
+Update readme link for discord channel
diff --git a/.changeset/silly-suits-run.md b/.changeset/silly-suits-run.md
new file mode 100644
index 0000000000..ec371e4077
--- /dev/null
+++ b/.changeset/silly-suits-run.md
@@ -0,0 +1,7 @@
+---
+'@backstage/plugin-catalog-backend': minor
+---
+
+Add /entities/by-query endpoint returning paginated entities.
+
+The endpoint supports cursor base pagination and server side sorting of the entities
diff --git a/.changeset/silver-lies-rest.md b/.changeset/silver-lies-rest.md
new file mode 100644
index 0000000000..34b77f5375
--- /dev/null
+++ b/.changeset/silver-lies-rest.md
@@ -0,0 +1,7 @@
+---
+'@backstage/plugin-catalog-backend-module-gitlab': patch
+---
+
+The configuration key `branch` of the `GitlabDiscoveryEntityProvider` has been deprecated in favor of the configuration key `fallbackBranch`.
+It will be reused in future release to enforce a concrete branch to be used in catalog file discovery.
+To migrate to the new configuration value, rename `branch` to `fallbackBranch`.
diff --git a/.changeset/six-melons-rhyme.md b/.changeset/six-melons-rhyme.md
new file mode 100644
index 0000000000..dbb878fc89
--- /dev/null
+++ b/.changeset/six-melons-rhyme.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-scaffolder-react': patch
+---
+
+scaffolder/next: Added the ability to get the fields definition in the schema in the validation function
diff --git a/.changeset/slimy-lobsters-kneel.md b/.changeset/slimy-lobsters-kneel.md
new file mode 100644
index 0000000000..40ff476a64
--- /dev/null
+++ b/.changeset/slimy-lobsters-kneel.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-linguist-backend': patch
+---
+
+Fixed bug in LinguistBackendApi that caused initial batch of entities to be skipped.
diff --git a/.changeset/slow-insects-cheat.md b/.changeset/slow-insects-cheat.md
new file mode 100644
index 0000000000..70b7bbf69b
--- /dev/null
+++ b/.changeset/slow-insects-cheat.md
@@ -0,0 +1,7 @@
+---
+'@backstage/plugin-techdocs': minor
+'@backstage/plugin-catalog': minor
+'@backstage/plugin-explore': patch
+---
+
+Add ability to pass icon as function to have ability to customize it by search item
diff --git a/.changeset/slow-pens-compare.md b/.changeset/slow-pens-compare.md
new file mode 100644
index 0000000000..158cb6f760
--- /dev/null
+++ b/.changeset/slow-pens-compare.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-backend': patch
+---
+
+Fix a bug where the batch fetch by ref endpoint did not work in conjunction with filtering (e.g. if authorization was enabled).
diff --git a/.changeset/slow-suits-grab.md b/.changeset/slow-suits-grab.md
new file mode 100644
index 0000000000..4a741cac6b
--- /dev/null
+++ b/.changeset/slow-suits-grab.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-backend-module-msgraph': patch
+---
+
+properly support custom graph api URL
diff --git a/.changeset/spotty-bees-tell.md b/.changeset/spotty-bees-tell.md
new file mode 100644
index 0000000000..5267d8a6a0
--- /dev/null
+++ b/.changeset/spotty-bees-tell.md
@@ -0,0 +1,6 @@
+---
+'@backstage/plugin-catalog-backend-module-incremental-ingestion': patch
+'@backstage/plugin-catalog-backend-module-aws': patch
+---
+
+Fix missing `dependencies` in `package.json`
diff --git a/.changeset/spotty-coats-clean.md b/.changeset/spotty-coats-clean.md
deleted file mode 100644
index 1a48da5688..0000000000
--- a/.changeset/spotty-coats-clean.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-adr': patch
----
-
-Render the common `` component when the `backstage.io/adr-location` annotation is missing from the component
diff --git a/.changeset/spotty-turtles-reply.md b/.changeset/spotty-turtles-reply.md
new file mode 100644
index 0000000000..2dfc12a176
--- /dev/null
+++ b/.changeset/spotty-turtles-reply.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-techdocs': patch
+---
+
+Copy to clipboard now works in a not secure context.
diff --git a/.changeset/spotty-wasps-complain.md b/.changeset/spotty-wasps-complain.md
new file mode 100644
index 0000000000..7b1e42b2a0
--- /dev/null
+++ b/.changeset/spotty-wasps-complain.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-scaffolder-react': patch
+---
+
+Fix alignment bug for owners on `TemplateCard`
diff --git a/.changeset/swift-bags-wave.md b/.changeset/swift-bags-wave.md
new file mode 100644
index 0000000000..8f57c7f89b
--- /dev/null
+++ b/.changeset/swift-bags-wave.md
@@ -0,0 +1,6 @@
+---
+'@backstage/plugin-sonarqube-react': patch
+'@backstage/plugin-sonarqube': patch
+---
+
+Moved imports from `/alpha` to main public exports.
diff --git a/.changeset/ten-tigers-marry.md b/.changeset/ten-tigers-marry.md
new file mode 100644
index 0000000000..f90f7b3ea2
--- /dev/null
+++ b/.changeset/ten-tigers-marry.md
@@ -0,0 +1,16 @@
+---
+'@backstage/plugin-proxy-backend': patch
+---
+
+The proxy-backend plugin now supports reviving request bodies that have previously been consumed by an express middleware (e.g. `express.json()`). This is done by setting `reviveConsumedRequestBodies: true` on the proxy `RouterOptions`. In order to preserve the current behavior, the proxy will **not** revive request bodies by default.
+
+The following is an example of a proxy `createRouter` invocation that revives request bodies:
+
+```diff
+const router = await createRouter({
+ config,
+ logger,
+ discovery,
++ reviveConsumedRequestBodies: true,
+});
+```
diff --git a/.changeset/thin-candles-wait.md b/.changeset/thin-candles-wait.md
new file mode 100644
index 0000000000..c7e18367b5
--- /dev/null
+++ b/.changeset/thin-candles-wait.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-sonarqube': patch
+---
+
+Show a more appropriate icon if there are no code smells and/or vulnerabilities.
diff --git a/.changeset/tricky-jars-film.md b/.changeset/tricky-jars-film.md
new file mode 100644
index 0000000000..9533634614
--- /dev/null
+++ b/.changeset/tricky-jars-film.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-backend-module-incremental-ingestion': patch
+---
+
+Move `@backstage/backend-defaults` to `devDependencies`
diff --git a/.changeset/twelve-cars-push.md b/.changeset/twelve-cars-push.md
new file mode 100644
index 0000000000..599d456d2b
--- /dev/null
+++ b/.changeset/twelve-cars-push.md
@@ -0,0 +1,20 @@
+---
+'@backstage/backend-plugin-api': patch
+'@backstage/backend-test-utils': patch
+'@backstage/backend-dev-utils': patch
+'@backstage/backend-defaults': patch
+'@backstage/backend-app-api': patch
+'@backstage/backend-common': patch
+'@backstage/backend-tasks': patch
+'@backstage/config-loader': patch
+'@backstage/cli-common': patch
+'@backstage/create-app': patch
+'@backstage/test-utils': patch
+'@backstage/dev-utils': patch
+'@backstage/codemods': patch
+'@backstage/config': patch
+'@backstage/theme': patch
+'@backstage/cli': patch
+---
+
+Updated link to docs.
diff --git a/.changeset/twenty-jeans-speak.md b/.changeset/twenty-jeans-speak.md
new file mode 100644
index 0000000000..fddcc2eedd
--- /dev/null
+++ b/.changeset/twenty-jeans-speak.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-auth-backend': patch
+---
+
+Added authentication provider for Azure Easy Authentication.
diff --git a/.changeset/unlucky-sloths-explain.md b/.changeset/unlucky-sloths-explain.md
new file mode 100644
index 0000000000..3d5f963a73
--- /dev/null
+++ b/.changeset/unlucky-sloths-explain.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-techdocs-module-addons-contrib': patch
+---
+
+Fixed bug in IssueLink component where the URL was not generated properly when the repository was located inside a Gitlab subgroup
diff --git a/.changeset/weak-coats-allow.md b/.changeset/weak-coats-allow.md
new file mode 100644
index 0000000000..31fc592326
--- /dev/null
+++ b/.changeset/weak-coats-allow.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-linguist': patch
+---
+
+Updated `README.md` to removed images with refresh button as this feature was not implemented in the current version
diff --git a/.changeset/what-is-going-on-babe.md b/.changeset/what-is-going-on-babe.md
new file mode 100644
index 0000000000..c22d69d1de
--- /dev/null
+++ b/.changeset/what-is-going-on-babe.md
@@ -0,0 +1,5 @@
+---
+'@backstage/backend-app-api': patch
+---
+
+Add support for `NotImplementedError`, properly returning 501 as status code.
diff --git a/.changeset/wicked-lions-repeat.md b/.changeset/wicked-lions-repeat.md
new file mode 100644
index 0000000000..c48545c6bc
--- /dev/null
+++ b/.changeset/wicked-lions-repeat.md
@@ -0,0 +1,6 @@
+---
+'@techdocs/cli': minor
+'@backstage/plugin-techdocs-node': minor
+---
+
+Added support for an HTTPS proxy for techdocs AWS S3 requests
diff --git a/.changeset/wicked-spoons-call.md b/.changeset/wicked-spoons-call.md
new file mode 100644
index 0000000000..c57860349b
--- /dev/null
+++ b/.changeset/wicked-spoons-call.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-newrelic': patch
+---
+
+Updated installation instructions
diff --git a/.changeset/witty-geckos-design.md b/.changeset/witty-geckos-design.md
new file mode 100644
index 0000000000..d482332c23
--- /dev/null
+++ b/.changeset/witty-geckos-design.md
@@ -0,0 +1,9 @@
+---
+'@backstage/plugin-linguist-backend': patch
+---
+
+Allow kind to be configurable
+
+```ts
+return createRouter({ schedule: schedule, kind: ['Component'] }, { ...env });
+```
diff --git a/.changeset/yellow-bananas-yawn.md b/.changeset/yellow-bananas-yawn.md
new file mode 100644
index 0000000000..d9c522a429
--- /dev/null
+++ b/.changeset/yellow-bananas-yawn.md
@@ -0,0 +1,5 @@
+---
+'@backstage/repo-tools': patch
+---
+
+The API report generation process is now able to detect and generate reports for additional entry points declared in the package `"exports"` field.
diff --git a/.changeset/young-schools-double.md b/.changeset/young-schools-double.md
new file mode 100644
index 0000000000..8cba2ec8f3
--- /dev/null
+++ b/.changeset/young-schools-double.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-react': patch
+---
+
+Do not close `Autocomplete` powered multiple-selection filters when checking boxes
diff --git a/.changeset/young-scissors-cough.md b/.changeset/young-scissors-cough.md
new file mode 100644
index 0000000000..fa35588224
--- /dev/null
+++ b/.changeset/young-scissors-cough.md
@@ -0,0 +1,5 @@
+---
+'@backstage/backend-defaults': patch
+---
+
+Added a workaround for the cyclic dependency bug across `@backstage/backend-common` and `@backstage/backend-app-api`.
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 82d868a408..10f1ac188c 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -4,78 +4,80 @@
# The last matching pattern takes precedence.
# https://help.github.com/articles/about-codeowners/
-* @backstage/reviewers
-yarn.lock @backstage/reviewers @backstage-service
-*/yarn.lock @backstage/reviewers @backstage-service
-/.changeset/cost-insights-* @backstage/reviewers @backstage/silver-lining
-/.changeset @backstage/reviewers @backstage/techdocs-core
-/cypress/src/integration/plugins/techdocs.spec.ts @backstage/reviewers @backstage/techdocs-core
-/docs/assets/search @backstage/reviewers @backstage/techdocs-core
-/docs/features/search @backstage/reviewers @backstage/techdocs-core
-/docs/features/techdocs @backstage/reviewers @backstage/techdocs-core
-/docs/plugins/integrating-search-into-plugins.md @backstage/reviewers @backstage/techdocs-core
-/packages/techdocs-cli @backstage/reviewers @backstage/techdocs-core
-/packages/techdocs-cli-embedded-app @backstage/reviewers @backstage/techdocs-core
-/plugins/adr @backstage/reviewers @kuangp
-/plugins/adr-* @backstage/reviewers @kuangp
-/plugins/allure @backstage/reviewers @deepak-bhardwaj-ps
-/plugins/apache-airflow @backstage/reviewers @cmpadden
-/plugins/api-docs @backstage/reviewers @backstage/sda-se-reviewers
-/plugins/azure-devops @backstage/reviewers @marleypowell @awanlin
-/plugins/azure-devops-backend @backstage/reviewers @marleypowell @awanlin
-/plugins/azure-devops-common @backstage/reviewers @marleypowell @awanlin
-/plugins/bitbucket-cloud-common @backstage/reviewers @pjungermann
-/plugins/bitrise @backstage/reviewers @backstage/sda-se-reviewers
-/plugins/catalog @backstage/reviewers @backstage/catalog-core
-/plugins/catalog-* @backstage/reviewers @backstage/catalog-core
-/plugins/catalog-backend-module-aws @backstage/reviewers @backstage/catalog-core @pjungermann
-/plugins/catalog-backend-module-bitbucket-cloud @backstage/reviewers @backstage/catalog-core @pjungermann
-/plugins/catalog-backend-module-msgraph @backstage/reviewers @backstage/catalog-core @pjungermann
-/plugins/catalog-graph @backstage/reviewers @backstage/catalog-core @backstage/sda-se-reviewers
-/plugins/circleci @backstage/reviewers @adamdmharvey
-/plugins/cloudbuild @backstage/reviewers @trivago/ebarrios
-/plugins/code-coverage @backstage/reviewers @alde @nissayeva
-/plugins/code-coverage-backend @backstage/reviewers @alde @nissayeva
-/plugins/cost-insights @backstage/reviewers @backstage/silver-lining
-/plugins/cost-insights-* @backstage/reviewers @backstage/silver-lining
-/plugins/events-backend @backstage/reviewers @pjungermann
-/plugins/events-backend-module-aws-sqs @backstage/reviewers @pjungermann
-/plugins/events-backend-module-azure @backstage/reviewers @pjungermann
-/plugins/events-backend-module-bitbucket-cloud @backstage/reviewers @pjungermann
-/plugins/events-backend-module-gerrit @backstage/reviewers @pjungermann
-/plugins/events-backend-module-github @backstage/reviewers @pjungermann
-/plugins/events-backend-module-gitlab @backstage/reviewers @pjungermann
-/plugins/events-backend-test-utils @backstage/reviewers @pjungermann
-/plugins/events-node @backstage/reviewers @pjungermann
-/plugins/explore @backstage/reviewers @backstage/sda-se-reviewers
-/plugins/explore-react @backstage/reviewers @backstage/sda-se-reviewers
-/plugins/fossa @backstage/reviewers @backstage/sda-se-reviewers
-/plugins/gcalendar @backstage/reviewers @szubster @ptychu @kielosz @alexrybch
-/plugins/git-release-manager @backstage/reviewers @erikengervall
-/plugins/home @backstage/reviewers @backstage/techdocs-core
-/plugins/ilert @backstage/reviewers @yacut
-/plugins/jenkins @backstage/reviewers @timja
-/plugins/jenkins-backend @backstage/reviewers @timja
-/plugins/kafka @backstage/reviewers @nirga @andrewthauer
-/plugins/kafka-backend @backstage/reviewers @nirga @andrewthauer
-/plugins/kubernetes @backstage/reviewers @backstage/warpspeed
-/plugins/kubernetes-* @backstage/reviewers @backstage/warpspeed
-/plugins/newrelic-dashboard @backstage/reviewers @mufaddal7
-/plugins/playlist @backstage/reviewers @kuangp
-/plugins/playlist-* @backstage/reviewers @kuangp
-/plugins/rollbar @backstage/reviewers @andrewthauer
-/plugins/rollbar-backend @backstage/reviewers @andrewthauer
-/plugins/scaffolder-backend-module-rails @backstage/reviewers @angeliski
-/plugins/scaffolder-backend-module-yeoman @backstage/reviewers @pawelmitka
-/plugins/search @backstage/reviewers @backstage/techdocs-core
-/plugins/search-* @backstage/reviewers @backstage/techdocs-core
-/plugins/sonarqube @backstage/reviewers @backstage/sda-se-reviewers
-/plugins/stack-overflow @backstage/reviewers @backstage/techdocs-core
-/plugins/stack-overflow-backend @backstage/reviewers @backstage/techdocs-core
-/plugins/techdocs @backstage/reviewers @backstage/techdocs-core
-/plugins/techdocs-* @backstage/reviewers @backstage/techdocs-core
-/plugins/user-settings-backend @backstage/reviewers @backstage/sda-se-reviewers
-/tech-insights-backend @backstage/reviewers @xantier @iain-b
-/tech-insights-backend-module-jsonfc @backstage/reviewers @xantier @iain-b
-/tech-insights-tech-insights-common @backstage/reviewers @xantier @iain-b
-/tech-insights-tech-insights-node @backstage/reviewers @xantier @iain-b
+* @backstage/maintainers
+yarn.lock @backstage/maintainers @backstage-service
+*/yarn.lock @backstage/maintainers @backstage-service
+/.changeset/*.md
+/cypress/src/integration/plugins/techdocs.spec.ts @backstage/techdocs-core
+/docs/assets/search @backstage/techdocs-core
+/docs/features/search @backstage/techdocs-core
+/docs/features/techdocs @backstage/techdocs-core
+/docs/plugins/integrating-search-into-plugins.md @backstage/techdocs-core
+/packages/techdocs-cli @backstage/techdocs-core
+/packages/techdocs-cli-embedded-app @backstage/techdocs-core
+/plugins/adr @backstage/maintainers @kuangp
+/plugins/adr-* @backstage/maintainers @kuangp
+/plugins/allure @backstage/maintainers @deepak-bhardwaj-ps
+/plugins/apache-airflow @backstage/maintainers @cmpadden
+/plugins/api-docs @backstage/maintainers @backstage/sda-se-reviewers
+/plugins/azure-devops @backstage/maintainers @marleypowell @awanlin
+/plugins/azure-devops-backend @backstage/maintainers @marleypowell @awanlin
+/plugins/azure-devops-common @backstage/maintainers @marleypowell @awanlin
+/plugins/bitbucket-cloud-common @backstage/maintainers @pjungermann
+/plugins/bitrise @backstage/maintainers @backstage/sda-se-reviewers
+/plugins/catalog @backstage/maintainers @backstage/catalog-core
+/plugins/catalog-* @backstage/maintainers @backstage/catalog-core
+/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-graph @backstage/maintainers @backstage/catalog-core @backstage/sda-se-reviewers
+/plugins/circleci @backstage/maintainers @adamdmharvey
+/plugins/cloudbuild @backstage/maintainers @trivago/ebarrios
+/plugins/code-coverage @backstage/maintainers @alde @nissayeva
+/plugins/code-coverage-backend @backstage/maintainers @alde @nissayeva
+/plugins/cost-insights @backstage/maintainers @backstage/silver-lining
+/plugins/cost-insights-* @backstage/maintainers @backstage/silver-lining
+/plugins/entity-feedback @backstage/maintainers @kuangp
+/plugins/entity-feedback-* @backstage/maintainers @kuangp
+/plugins/events-backend @backstage/maintainers @pjungermann
+/plugins/events-backend-module-aws-sqs @backstage/maintainers @pjungermann
+/plugins/events-backend-module-azure @backstage/maintainers @pjungermann
+/plugins/events-backend-module-bitbucket-cloud @backstage/maintainers @pjungermann
+/plugins/events-backend-module-gerrit @backstage/maintainers @pjungermann
+/plugins/events-backend-module-github @backstage/maintainers @pjungermann
+/plugins/events-backend-module-gitlab @backstage/maintainers @pjungermann
+/plugins/events-backend-test-utils @backstage/maintainers @pjungermann
+/plugins/events-node @backstage/maintainers @pjungermann
+/plugins/explore @backstage/maintainers @backstage/sda-se-reviewers
+/plugins/explore-react @backstage/maintainers @backstage/sda-se-reviewers
+/plugins/fossa @backstage/maintainers @backstage/sda-se-reviewers
+/plugins/gcalendar @backstage/maintainers @szubster @ptychu @kielosz @alexrybch
+/plugins/git-release-manager @backstage/maintainers @erikengervall
+/plugins/home @backstage/techdocs-core
+/plugins/ilert @backstage/maintainers @yacut
+/plugins/jenkins @backstage/maintainers @timja
+/plugins/jenkins-backend @backstage/maintainers @timja
+/plugins/kafka @backstage/maintainers @nirga @andrewthauer
+/plugins/kafka-backend @backstage/maintainers @nirga @andrewthauer
+/plugins/kubernetes @backstage/maintainers @backstage/warpspeed
+/plugins/kubernetes-* @backstage/maintainers @backstage/warpspeed
+/plugins/microsoft-calendar @backstage/maintainers @abhay-soni-developer
+/plugins/newrelic-dashboard @backstage/maintainers @mufaddal7
+/plugins/playlist @backstage/maintainers @kuangp
+/plugins/playlist-* @backstage/maintainers @kuangp
+/plugins/rollbar @backstage/maintainers @andrewthauer
+/plugins/rollbar-backend @backstage/maintainers @andrewthauer
+/plugins/scaffolder-backend-module-rails @backstage/maintainers @angeliski
+/plugins/scaffolder-backend-module-yeoman @backstage/maintainers @pawelmitka
+/plugins/search @backstage/techdocs-core
+/plugins/search-* @backstage/techdocs-core
+/plugins/sonarqube @backstage/maintainers @backstage/sda-se-reviewers
+/plugins/stack-overflow @backstage/techdocs-core
+/plugins/stack-overflow-backend @backstage/techdocs-core
+/plugins/techdocs @backstage/techdocs-core
+/plugins/techdocs-* @backstage/techdocs-core
+/plugins/user-settings-backend @backstage/maintainers @backstage/sda-se-reviewers
+/tech-insights-backend @backstage/maintainers @xantier @iain-b
+/tech-insights-backend-module-jsonfc @backstage/maintainers @xantier @iain-b
+/tech-insights-tech-insights-common @backstage/maintainers @xantier @iain-b
+/tech-insights-tech-insights-node @backstage/maintainers @xantier @iain-b
diff --git a/.github/ISSUE_TEMPLATE/bug.yaml b/.github/ISSUE_TEMPLATE/bug.yaml
index 782b5fdfa0..57595621e4 100644
--- a/.github/ISSUE_TEMPLATE/bug.yaml
+++ b/.github/ISSUE_TEMPLATE/bug.yaml
@@ -1,6 +1,6 @@
name: '🐛 Bug Report'
description: 'Submit a bug report to help us improve'
-title: '🐛 Bug Report: '
+title: '🐛 Bug Report:
'
labels:
- bug
body:
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index e9f23287b7..e4b99b3be1 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -6,7 +6,7 @@ contact_links:
url: 'https://github.com/backstage/backstage/discussions'
- about: 'Alternatively, you can use the Backstage Community Discord'
name: Chat
- url: 'https://discord.gg/MUpMjP2'
+ url: 'https://discord.gg/backstage-687207715902193673'
- about: 'Please check the FAQ before filing new issues'
name: 'Backstage FAQ'
url: 'https://backstage.io/docs/FAQ'
diff --git a/.github/ISSUE_TEMPLATE/feature.yaml b/.github/ISSUE_TEMPLATE/feature.yaml
index b5e06d48cf..b2d5a6be81 100644
--- a/.github/ISSUE_TEMPLATE/feature.yaml
+++ b/.github/ISSUE_TEMPLATE/feature.yaml
@@ -1,6 +1,6 @@
name: 🚀 Feature
description: 'Submit a proposal for a new feature'
-title: '🚀 Feature: '
+title: '🚀 Feature: '
labels: [enhancement]
body:
- type: markdown
diff --git a/.github/ISSUE_TEMPLATE/plugin.yaml b/.github/ISSUE_TEMPLATE/plugin.yaml
index a36c427981..a7f5216631 100644
--- a/.github/ISSUE_TEMPLATE/plugin.yaml
+++ b/.github/ISSUE_TEMPLATE/plugin.yaml
@@ -1,6 +1,6 @@
name: 🔌 Plugin
description: 'Submit a proposal for a new Plugin'
-title: '🔌 Plugin: '
+title: '🔌 Plugin: '
labels: [plugin]
body:
- type: markdown
diff --git a/.github/ISSUE_TEMPLATE/rfc.yaml b/.github/ISSUE_TEMPLATE/rfc.yaml
index 775c64e674..8e73992dc8 100644
--- a/.github/ISSUE_TEMPLATE/rfc.yaml
+++ b/.github/ISSUE_TEMPLATE/rfc.yaml
@@ -1,6 +1,6 @@
name: 💬 RFC
description: 'Request For Comments (RFC) from the community'
-title: '💬 RFC: '
+title: '💬 RFC: '
labels: [rfc]
body:
- type: markdown
diff --git a/.github/ISSUE_TEMPLATE/ux_component.yaml b/.github/ISSUE_TEMPLATE/ux_component.yaml
index 453f1e8278..c528356eb3 100644
--- a/.github/ISSUE_TEMPLATE/ux_component.yaml
+++ b/.github/ISSUE_TEMPLATE/ux_component.yaml
@@ -1,6 +1,6 @@
name: 🦄 UX Component
description: 'For designers to request UX components to be added to the Backstage Storybook'
-title: '🦄 UX Component: '
+title: '🦄 UX Component: '
labels: [design]
body:
- type: markdown
diff --git a/.github/uffizzi/docker-compose.uffizzi.yml b/.github/uffizzi/docker-compose.uffizzi.yml
index 49039cad2a..76a7b4eafc 100644
--- a/.github/uffizzi/docker-compose.uffizzi.yml
+++ b/.github/uffizzi/docker-compose.uffizzi.yml
@@ -10,9 +10,10 @@ services:
image: '${BACKSTAGE_IMAGE}'
environment:
- POSTGRES_HOST: db
+ POSTGRES_HOST: localhost
+ POSTGRES_PORT: 5432
POSTGRES_USER: postgres
- POSTGRES_PASSWORD: example
+ POSTGRES_PASSWORD: kiTMoTsiEuyQ43GrL4Hv
NODE_ENV: production
deploy:
resources:
@@ -26,4 +27,8 @@ services:
db:
image: postgres
environment:
- POSTGRES_PASSWORD: example
+ POSTGRES_PASSWORD: kiTMoTsiEuyQ43GrL4Hv
+ deploy:
+ resources:
+ limits:
+ memory: 250M
diff --git a/.github/uffizzi/uffizzi.production.app-config.yaml b/.github/uffizzi/uffizzi.production.app-config.yaml
index d4b70641da..30cffa11c9 100644
--- a/.github/uffizzi/uffizzi.production.app-config.yaml
+++ b/.github/uffizzi/uffizzi.production.app-config.yaml
@@ -9,13 +9,17 @@ backend:
baseUrl: ${UFFIZZI_URL}
auth:
keys:
- # random mock key for uffizi deployments
+ # random mock key for Uffizzi deployments
- secret: 5TXvdjVZFxF7qf9K5RAYRDoGrLzJooqa
listen:
port: 7007
database:
- client: better-sqlite3
- connection: ':memory:'
+ client: pg
+ connection:
+ host: ${POSTGRES_HOST}
+ port: ${POSTGRES_PORT}
+ user: ${POSTGRES_USER}
+ password: ${POSTGRES_PASSWORD}
cache:
store: memory
cors:
diff --git a/.github/vale/Vocab/Backstage/accept.txt b/.github/vale/Vocab/Backstage/accept.txt
index 98160a496d..63fa474115 100644
--- a/.github/vale/Vocab/Backstage/accept.txt
+++ b/.github/vale/Vocab/Backstage/accept.txt
@@ -9,6 +9,7 @@ Airbrake
Airbrakes
Alaria
Alef
+allowlisted
Anddddd
Apdex
api
@@ -23,6 +24,7 @@ Avro
backend's
backported
backporting
+BEPs
Bigtable
Billett
bitbucket
@@ -31,6 +33,7 @@ Blackbox
bool
boolean
Brex
+bugfixes
builtins
callout
CDNs
@@ -70,6 +73,7 @@ cron
cronjobs
crontab
css
+daemonsets
Datadog
dataflow
dayjs
@@ -94,6 +98,7 @@ dockerfiles
Dockerize
dockerode
Docusaurus
+Dominik
DOMPurify
don'ts
dynatrace
@@ -118,6 +123,7 @@ Firekube
Firestore
Fiverr
Francesco
+Frontside
gerrit
Gerrit
gitbeaker
@@ -137,6 +143,7 @@ Hackathons
haproxy
hardcoded
Helidon
+Henneke
Heroku
hoc
horizontalpodautoscalers
@@ -166,17 +173,20 @@ jsonnet
jsx
JWTs
Kaewkasi
+Kaswell
Keyv
Knex
KPIs
+Kuang
kubectl
kubernetes
-kubernetes
+Lainfiesta
ldap
learnings
Leasot
lerna
Lerna
+limitranges
LocalStack
lockdown
lockfile
@@ -231,6 +241,7 @@ OAuth
octokit
oidc
Okta
+Olausson
Oldsberg
onboarding
Onboarding
@@ -334,6 +345,7 @@ subfolders
subheader
subheaders
subkey
+subpath
subroutes
subtree
superfences
@@ -410,8 +422,3 @@ zod
Zolotusky
zoomable
zsh
-Lainfiesta
-allowlisted
-Dominik
-Henneke
-Kuang
diff --git a/.github/workflows/automate_changeset_feedback.yml b/.github/workflows/automate_changeset_feedback.yml
index e18aa9d428..f96a9fbb39 100644
--- a/.github/workflows/automate_changeset_feedback.yml
+++ b/.github/workflows/automate_changeset_feedback.yml
@@ -1,6 +1,7 @@
name: Automate changeset feedback
on:
pull_request_target:
+ branches: ['master']
permissions:
pull-requests: write
@@ -28,13 +29,13 @@ jobs:
ref: 'refs/pull/${{ github.event.pull_request.number }}/merge'
- name: fetch base
run: git fetch --depth 1 origin ${{ github.base_ref }}
- - uses: backstage/actions/changeset-feedback@v0.5.13
+ - uses: backstage/actions/changeset-feedback@v0.6.2
name: Generate feedback
with:
- diffRef: 'origin/master'
+ diff-ref: 'origin/master'
marker:
issue-number: ${{ github.event.pull_request.number }}
- botUsername: backstage-goalie[bot]
+ bot-username: backstage-goalie[bot]
app-id: ${{ secrets.BACKSTAGE_GOALIE_APPLICATION_ID }}
private-key: ${{ secrets.BACKSTAGE_GOALIE_PRIVATE_KEY }}
installation-id: ${{ secrets.BACKSTAGE_GOALIE_INSTALLATION_ID }}
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 10db591f4c..18af36c8c2 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -33,7 +33,7 @@ jobs:
registry-url: https://registry.npmjs.org/ # Needed for auth
- name: yarn install
- uses: backstage/actions/yarn-install@v0.5.13
+ uses: backstage/actions/yarn-install@v0.6.2
with:
cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }}
@@ -63,7 +63,7 @@ jobs:
registry-url: https://registry.npmjs.org/ # Needed for auth
- name: yarn install
- uses: backstage/actions/yarn-install@v0.5.13
+ uses: backstage/actions/yarn-install@v0.6.2
with:
cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }}
@@ -181,7 +181,7 @@ jobs:
registry-url: https://registry.npmjs.org/ # Needed for auth
- name: yarn install
- uses: backstage/actions/yarn-install@v0.5.13
+ uses: backstage/actions/yarn-install@v0.6.2
with:
cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }}
diff --git a/.github/workflows/cron.yml b/.github/workflows/cron.yml
index dc221c1a1e..be27a732d6 100644
--- a/.github/workflows/cron.yml
+++ b/.github/workflows/cron.yml
@@ -8,7 +8,7 @@ jobs:
cron:
runs-on: ubuntu-latest
steps:
- - uses: backstage/actions/cron@v0.5.13
+ - uses: backstage/actions/cron@v0.6.2
with:
app-id: ${{ secrets.BACKSTAGE_GOALIE_APPLICATION_ID }}
private-key: ${{ secrets.BACKSTAGE_GOALIE_PRIVATE_KEY }}
diff --git a/.github/workflows/deploy_docker-image.yml b/.github/workflows/deploy_docker-image.yml
index d8470d4124..60affcb328 100644
--- a/.github/workflows/deploy_docker-image.yml
+++ b/.github/workflows/deploy_docker-image.yml
@@ -26,7 +26,7 @@ jobs:
registry-url: https://registry.npmjs.org/ # Needed for auth
- name: yarn install
- uses: backstage/actions/yarn-install@v0.5.13
+ uses: backstage/actions/yarn-install@v0.6.2
with:
cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }}
@@ -50,7 +50,7 @@ jobs:
uses: docker/setup-buildx-action@v2
- name: Build and push
- uses: docker/build-push-action@v3
+ uses: docker/build-push-action@v4
with:
context: './example-app'
file: ./example-app/packages/backend/Dockerfile
diff --git a/.github/workflows/deploy_microsite.yml b/.github/workflows/deploy_microsite.yml
index 1f87c30af2..ec7124ed35 100644
--- a/.github/workflows/deploy_microsite.yml
+++ b/.github/workflows/deploy_microsite.yml
@@ -14,7 +14,8 @@ jobs:
env:
CI: true
- NODE_OPTIONS: --max-old-space-size=4096
+ NODE_OPTIONS: --max-old-space-size=7168
+ DOCUSAURUS_SSR_CONCURRENCY: 5
steps:
- uses: actions/checkout@v3
@@ -32,7 +33,7 @@ jobs:
run: yarn install --immutable
- name: microsite yarn install
run: yarn install --immutable
- working-directory: microsite
+ working-directory: microsite-next
- name: storybook yarn install
run: yarn install --immutable
working-directory: storybook
@@ -42,20 +43,20 @@ jobs:
- name: build microsite
run: yarn build
- working-directory: microsite
+ working-directory: microsite-next
- name: build storybook
run: yarn build-storybook
working-directory: storybook
- name: move storybook dist into microsite
- run: mv storybook/dist/ microsite/build/backstage/storybook
+ run: mv storybook/dist/ microsite-next/build/storybook
- name: Check the build output
- run: ls microsite/build/backstage && ls microsite/build/backstage/storybook
+ run: ls microsite-next/build && ls microsite-next/build/storybook
- name: Deploy both microsite and storybook to gh-pages
uses: JamesIves/github-pages-deploy-action@v4.4.1
with:
branch: gh-pages
- folder: microsite/build/backstage
+ folder: microsite-next/build
diff --git a/.github/workflows/deploy_nightly.yml b/.github/workflows/deploy_nightly.yml
index 5f337ace3e..49907042b5 100644
--- a/.github/workflows/deploy_nightly.yml
+++ b/.github/workflows/deploy_nightly.yml
@@ -26,7 +26,7 @@ jobs:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
- name: yarn install
- uses: backstage/actions/yarn-install@v0.5.13
+ uses: backstage/actions/yarn-install@v0.6.2
with:
cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }}
diff --git a/.github/workflows/deploy_packages.yml b/.github/workflows/deploy_packages.yml
index c05b850883..058f4f6089 100644
--- a/.github/workflows/deploy_packages.yml
+++ b/.github/workflows/deploy_packages.yml
@@ -68,7 +68,7 @@ jobs:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
- name: yarn install
- uses: backstage/actions/yarn-install@v0.5.13
+ uses: backstage/actions/yarn-install@v0.6.2
with:
cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }}
@@ -145,7 +145,7 @@ jobs:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
- name: yarn install
- uses: backstage/actions/yarn-install@v0.5.13
+ uses: backstage/actions/yarn-install@v0.6.2
with:
cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }}
diff --git a/.github/workflows/issue.yaml b/.github/workflows/issue.yaml
index d16d0b55d7..e64f919bdb 100644
--- a/.github/workflows/issue.yaml
+++ b/.github/workflows/issue.yaml
@@ -10,4 +10,4 @@ jobs:
if: github.repository == 'backstage/backstage'
steps:
- name: Issue sync
- uses: backstage/actions/issue-sync@v0.5.13
+ uses: backstage/actions/issue-sync@v0.6.2
diff --git a/.github/workflows/pr-review-comment.yaml b/.github/workflows/pr-review-comment.yaml
index 11b590809a..52db29c699 100644
--- a/.github/workflows/pr-review-comment.yaml
+++ b/.github/workflows/pr-review-comment.yaml
@@ -35,7 +35,7 @@ jobs:
const prNumber = artifact.name.slice('pr_number-'.length)
core.setOutput('pr-number', prNumber);
- - uses: backstage/actions/re-review@v0.5.13
+ - uses: backstage/actions/re-review@v0.6.2
with:
app-id: ${{ secrets.BACKSTAGE_GOALIE_APPLICATION_ID }}
private-key: ${{ secrets.BACKSTAGE_GOALIE_PRIVATE_KEY }}
diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml
index acbd11aece..db7d141abd 100644
--- a/.github/workflows/pr.yaml
+++ b/.github/workflows/pr.yaml
@@ -18,12 +18,12 @@ jobs:
if: github.repository == 'backstage/backstage' && ( github.event.pull_request || github.event.issue.pull_request )
steps:
- name: PR sync
- uses: backstage/actions/pr-sync@v0.5.13
+ uses: backstage/actions/pr-sync@v0.6.2
with:
github-token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }}
app-id: ${{ secrets.BACKSTAGE_GOALIE_APPLICATION_ID }}
private-key: ${{ secrets.BACKSTAGE_GOALIE_PRIVATE_KEY }}
installation-id: ${{ secrets.BACKSTAGE_GOALIE_INSTALLATION_ID }}
project-id: PVT_kwDOBFKqdc02LQ
- excluded-users: ${{ secrets.OOO_USERS }}
+ auto-assign: false
owning-teams: '@backstage/techdocs-core'
diff --git a/.github/workflows/sync_code-formatting.yml b/.github/workflows/sync_code-formatting.yml
index 19cc5f8045..b299f314a7 100644
--- a/.github/workflows/sync_code-formatting.yml
+++ b/.github/workflows/sync_code-formatting.yml
@@ -20,12 +20,12 @@ jobs:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
- name: yarn install
- uses: backstage/actions/yarn-install@v0.5.13
+ uses: backstage/actions/yarn-install@v0.6.2
with:
cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }}
- name: Run Prettier on ADOPTERS.md
- uses: creyD/prettier_action@v4.2
+ uses: creyD/prettier_action@v4.3
with:
# Modifies commit only if prettier autofixed the ADOPTERS.md
prettier_options: --config docs/prettier.config.js --write ADOPTERS.md
diff --git a/.github/workflows/sync_release-manifest.yml b/.github/workflows/sync_release-manifest.yml
index c6d00ecd28..8cf6991aaf 100644
--- a/.github/workflows/sync_release-manifest.yml
+++ b/.github/workflows/sync_release-manifest.yml
@@ -53,10 +53,6 @@ jobs:
github-token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }}
# TODO(Rugvip): Remove the create-app dispatch once we've been on the release version for a while
script: |
- const releaseVersion = require('./backstage/package.json').version;
- if(releaseVersion.includes('next')) {
- return;
- }
console.log('Dispatching upgrade helper sync');
await github.rest.actions.createWorkflowDispatch({
owner: 'backstage',
@@ -65,6 +61,6 @@ jobs:
ref: 'master',
inputs: {
version: require('./backstage/packages/create-app/package.json').version,
- releaseVersion
+ releaseVersion: require('./backstage/package.json').version
},
});
diff --git a/.github/workflows/sync_snyk-github-issues.yml b/.github/workflows/sync_snyk-github-issues.yml
index 9dcc9f63ef..a89b7c8e65 100644
--- a/.github/workflows/sync_snyk-github-issues.yml
+++ b/.github/workflows/sync_snyk-github-issues.yml
@@ -23,7 +23,7 @@ jobs:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
- name: yarn install
- uses: backstage/actions/yarn-install@v0.5.13
+ uses: backstage/actions/yarn-install@v0.6.2
with:
cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }}
diff --git a/.github/workflows/uffizzi-build.yml b/.github/workflows/uffizzi-build.yml
index a23216a9cb..d8776d74c7 100644
--- a/.github/workflows/uffizzi-build.yml
+++ b/.github/workflows/uffizzi-build.yml
@@ -27,7 +27,7 @@ jobs:
registry-url: https://registry.npmjs.org/
- name: yarn install
- uses: backstage/actions/yarn-install@v0.5.13
+ uses: backstage/actions/yarn-install@v0.6.2
with:
cache-prefix: linux-v16
@@ -58,7 +58,7 @@ jobs:
tags: type=raw,value=60d
- name: Build Image
- uses: docker/build-push-action@v3
+ uses: docker/build-push-action@v4
with:
context: .
file: packages/backend/Dockerfile
@@ -89,16 +89,11 @@ jobs:
name: preview-spec
path: docker-compose.rendered.yml
retention-days: 2
- - name: Serialize PR Event to File
- run: |
- cat << EOF > event.json
- ${{ toJSON(github.event) }}
- EOF
- name: Upload PR Event as Artifact
uses: actions/upload-artifact@v3
with:
name: preview-spec
- path: event.json
+ path: ${{ github.event_path }}
retention-days: 2
delete-preview:
@@ -107,14 +102,9 @@ jobs:
if: ${{ github.event.action == 'closed' }}
steps:
# If this PR is closing, we will not render a compose file nor pass it to the next workflow.
- - name: Serialize PR Event to File
- run: |
- cat << EOF > event.json
- ${{ toJSON(github.event) }}
- EOF
- name: Upload PR Event as Artifact
uses: actions/upload-artifact@v3
with:
name: preview-spec
- path: event.json
+ path: ${{ github.event_path }}
retention-days: 2
diff --git a/.github/workflows/uffizzi-preview.yaml b/.github/workflows/uffizzi-preview.yaml
index 3c344f5a78..6390671bbd 100644
--- a/.github/workflows/uffizzi-preview.yaml
+++ b/.github/workflows/uffizzi-preview.yaml
@@ -11,6 +11,7 @@ jobs:
cache-compose-file:
name: Cache Compose File
runs-on: ubuntu-latest
+ if: ${{ github.event.workflow_run.conclusion == 'success' }}
outputs:
compose-file-cache-key: ${{ env.COMPOSE_FILE_HASH }}
pr-number: ${{ env.PR_NUMBER }}
@@ -47,7 +48,7 @@ jobs:
run: |
echo 'EVENT_JSON<> $GITHUB_ENV
cat event.json >> $GITHUB_ENV
- echo 'EOF' >> $GITHUB_ENV
+ echo -e '\nEOF' >> $GITHUB_ENV
- name: Hash Rendered Compose File
id: hash
@@ -77,6 +78,7 @@ jobs:
name: Use Remote Workflow to Preview on Uffizzi
needs:
- cache-compose-file
+ if: ${{ github.event.workflow_run.conclusion == 'success' }}
uses: UffizziCloud/preview-action/.github/workflows/reusable.yaml@v2
with:
# If this workflow was triggered by a PR close event, cache-key will be an empty string
diff --git a/.github/workflows/verify_e2e-linux.yml b/.github/workflows/verify_e2e-linux.yml
index f687e4f5a4..4fda0749c0 100644
--- a/.github/workflows/verify_e2e-linux.yml
+++ b/.github/workflows/verify_e2e-linux.yml
@@ -52,7 +52,7 @@ jobs:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
- name: yarn install
- uses: backstage/actions/yarn-install@v0.5.13
+ uses: backstage/actions/yarn-install@v0.6.2
with:
cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }}
diff --git a/.github/workflows/verify_e2e-windows.yml b/.github/workflows/verify_e2e-windows.yml
index 8a49b8fe31..145eefbd26 100644
--- a/.github/workflows/verify_e2e-windows.yml
+++ b/.github/workflows/verify_e2e-windows.yml
@@ -49,7 +49,7 @@ jobs:
with:
node-version: ${{ matrix.node-version }}
- name: Add msbuild to PATH
- uses: microsoft/setup-msbuild@v1.1.3
+ uses: microsoft/setup-msbuild@v1.3.1
- name: setup chrome
uses: browser-actions/setup-chrome@latest
- name: yarn install
diff --git a/.github/workflows/verify_kubernetes.yml b/.github/workflows/verify_kubernetes.yml
index 7fbcf9fa3f..83280f8e85 100644
--- a/.github/workflows/verify_kubernetes.yml
+++ b/.github/workflows/verify_kubernetes.yml
@@ -28,7 +28,7 @@ jobs:
registry-url: https://registry.npmjs.org/ # Needed for auth
- name: yarn install
- uses: backstage/actions/yarn-install@v0.5.13
+ uses: backstage/actions/yarn-install@v0.6.2
with:
cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }}
diff --git a/.github/workflows/verify_microsite-next.yml b/.github/workflows/verify_microsite-next.yml
index ea0cbae0be..6c9edf2031 100644
--- a/.github/workflows/verify_microsite-next.yml
+++ b/.github/workflows/verify_microsite-next.yml
@@ -17,6 +17,7 @@ jobs:
env:
CI: true
NODE_OPTIONS: --max-old-space-size=4096
+ DOCUSAURUS_SSR_CONCURRENCY: 5
steps:
- uses: actions/checkout@v3
@@ -28,10 +29,16 @@ jobs:
# Skip caching of microsite dependencies, it keeps the global cache size
# smaller, which make Windows builds a lot faster for the rest of the project.
+ - name: top-level install
+ run: yarn install --immutable
+
- name: yarn install
run: yarn install --immutable
working-directory: microsite-next
+ - name: build API reference
+ run: yarn build:api-docs
+
- name: verify yarn dependency duplicates
run: node scripts/verify-lockfile-duplicates.js
diff --git a/.github/workflows/verify_storybook.yml b/.github/workflows/verify_storybook.yml
index 0e63393af8..8cde5b6e9b 100644
--- a/.github/workflows/verify_storybook.yml
+++ b/.github/workflows/verify_storybook.yml
@@ -35,7 +35,7 @@ jobs:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
- name: yarn install
- uses: backstage/actions/yarn-install@v0.5.13
+ uses: backstage/actions/yarn-install@v0.6.2
with:
cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }}
- name: storybook yarn install
diff --git a/.prettierignore b/.prettierignore
index 8f1fae0456..ad2bd355ed 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -5,6 +5,7 @@ coverage
*.hbs
templates
api-report.md
+*-api-report.md
cli-report.md
plugins/scaffolder-backend/sample-templates
.vscode
diff --git a/ADOPTERS.md b/ADOPTERS.md
index 7ed897f351..068928260d 100644
--- a/ADOPTERS.md
+++ b/ADOPTERS.md
@@ -9,7 +9,6 @@ _You can do this by using the [Adopter form](https://info.backstage.spotify.com/
| [bol.com](https://www.bol.com) | [@acierto](https://github.com/acierto), [@clanghout](https://github.com/clanghout) | Initial work being done to unify platform tooling. |
| [DFDS](https://www.dfds.com) | [@carlsendk](https://github.com/carlsendk) | V2 self-service platform. |
| [Roadie](https://roadie.io) | [@dtuite](https://github.com/dtuite) | Hosted, managed Backstage with easy set-up |
-| [Roku](https://www.roku.com) | [@timurista](https://github.com/timurista) | Initial work on Cloud engineering service platform. |
| [SDA SE](https://sda.se) | [@dschwank](https://github.com/dschwank), [@iammnils](https://github.com/iammnils) | Central place for developing and sharing services in our insurance ecosystem. |
| [H-E-B](https://www.heb.com) | [@german-j-rodriguez](https://github.com/german-j-rodriguez) | Initial work on Engineering Portal service platform. |
| [American Airlines](https://www.aa.com) | [@paulpach](https://github.com/paulpach) | Central place for developers to develop and maintain applications |
@@ -159,7 +158,7 @@ _You can do this by using the [Adopter form](https://info.backstage.spotify.com/
| [Doctolib](https://doctolib.engineering/) | [@djiit](https://github.com/djiit) | Rails modularization effort awareness, tech organization discoverability. Improving the daily workflows and collaboration processes of our engineers. |
| [Twilio](https://www.twilio.com) | [Kyle Smith](https://github.com/knksmith57) | Developer portal, universal software catalog, and centralized taxonomy platform. |
| [OVHcloud](https://www.ovhcloud.com/fr/) | [Jean-Philippe Blary](https://github.com/blaryjp), [Arnaud Bauer](mailto:arnaud.bauer@ovhcloud.com), [Flavien Chantelot](https://github.com/Dorn-) | We're providing Backstage to our collaborators to ease their daily jobs, and let them extends it using plugins. |
-| [Procter & Gamble](https://us.pg.com/) | [Binita Nayak](https://github.com/binitan), [Josh Rose](https://github.com/joshuarose), [RJ Winkler](https://github.com/rjwink) | P&G leverages Backstage to build internal developer portal to ensure developers' happiness. This developer portal shall act as single source of information needed by development teams to seamlessly create, find and maintain their software components/resources/documentation. |
+| [Procter & Gamble](https://us.pg.com/) | [Binita Nayak](https://github.com/binitan), | P&G leverages Backstage to build internal developer portal to ensure developers' happiness. This developer portal shall act as single source of information needed by development teams to seamlessly create, find and maintain their software components/resources/documentation. |
| [SANS Institute](https://www.sans.org) | [Christopher Klewin](mailto:cklewin@sans.org) | Developer portal for centralized visibility, reporting, and tooling across multiple organizations. |
| [Okay](https://www.okayhq.com/) | [Tomas Barreto](mailto:tomas@okayhq.com) | Service catalog, developer portal, and technical documentation |
| [Kaluza](https://www.kaluza.com) | [James Condron](mailto:james.condron@kaluza.com) | To provide an automated golden path to developers, with a focus on discovery and documentation |
@@ -186,7 +185,7 @@ _You can do this by using the [Adopter form](https://info.backstage.spotify.com/
| [Cho Tot](https://www.chotot.com) | [Chotot Team](mailto:sre@chotot.vn) | Internal developer portal, service catalog with CI/CD tools. |
| [William Hill](https://www.williamhillgroup.com/) | [Pat Mills](mailto:pat.mills@williamhill.com), [Nathan Flynn](mailto:nflynn@williamhill.co.uk), and [Nishkarsh Raj](mailto:nishkarsh.raj@williamhill.co.uk) | William Hill are leveraging Backstage to build our Engineering Portal. Our mission is to centralize the software catalog inventory to enable service discoverability, reduce the onboarding time for new Engineers, provide a single pane of glass to accelerate Developer Productivity and Save Engineers time. Our aspiration is to create an InnerSource community focussed on organization-wide patterns that are re-usable and can be self-served with the Scaffolder. |
| [Vodafone NewZealand Limited](https://vodafone.co.nz) | [Ankit Gupta](mailto:ankit.gupta@vodafone.nz), [DevOps COE](mailto:devopstooling@vodafone.nz) | Vodafone NZ are leveraging Backstage to build centralised and self service Engineering Portal. Our mission is to standardised Pipeline templates across the Engineering teams, One shop stop to create the pipelines and repository with a template approach which reduces creation part from days to minutes and no wait time for developers. A unified view for Azure DevOps pipeline, Azure Repo pull requests, Deployment status from Azure RedHat Openshift-ArgoCD and SonarQube Security and code quality scans report on a single pan to provide a streamlined view for all microservices across the app stack. |
-| [Coamo](http://www.coamo.com.br) | [@holiiveira](https://github.com/holiiveira), [@gpxlnx](https://github.com/gpxlnx) | We're starting to use it as the main tool of a DevOps platform. Our goal is to provide software templates, centralize our software catalog enabling efficient service discovery, and make it easy to manage the entire software ecosystem in one place. |
+| [Coamo](http://www.coamo.com.br) | [Henrique Oliveira](https://www.linkedin.com/in/oliveirah/), [Guilherme Xavier](https://br.linkedin.com/in/guilhermexavierlnx) | We're starting to use it as the main tool of a DevOps platform. Our goal is to provide software templates, centralize our software catalog enabling efficient service discovery, and make it easy to manage the entire software ecosystem in one place. |
| [Exclaimer](https://www.exclaimer.com/) | [Marley Powell](mailto:marley.powell@exclaimer.com) | Developer portal, service catalog and documentation |
| [Edge Consulting](https://egde.no/) | [Øyvind Brekkhus Sandåker](mailto:oyvindbs@egde.no) | Using it as a Software Catalog to get an overview of our systems, for ourselves and our customers. |
| [CRV](https://crv4all.com) | [Pepijn Schildkamp](mailto:pepijn.schildkamp@crv4all.com) |
@@ -228,3 +227,6 @@ _You can do this by using the [Adopter form](https://info.backstage.spotify.com/
| [Booking.com](https://www.linkedin.com/company/booking.com/) | [Mesut Yilmazyildirim](https://www.linkedin.com/in/myilmazyildirim) | We are adopting Backstage as the new reliability platform inside the company. We are migrating UIs of our internal developer tools to Backstage for a better user experience.
| [Swissquote Bank](https://swissquote.com/company/jobs/open-positions) | [Bruno Rocha](https://www.linkedin.com/in/bruno-rocha1/) | Integrating Backstage as the visualization layer & tactical overview of our services and teams.
| [XP Inc.](https://www.linkedin.com/company/xpinc/) | [Gabriel Santos](https://www.linkedin.com/in/gabriel-santos-6bb740a/) | Developer Portal Catalog (components, APIs, resources, users, groups) and organization relations.
+| [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. |
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index f3de7bf0e5..c2df005a1c 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -72,7 +72,7 @@ Have you started using Backstage? Adding your company to [ADOPTERS](ADOPTERS.md)
So...feel ready to jump in? Let's do this. 👏🏻💯
-Start by reading our [Getting Started for Contributors](https://backstage.io/docs/getting-started/contributors) page to get yourself setup with a fresh copy of Backstage ready for your contributions. If you need help, just jump into our [Discord chatroom](https://discord.gg/MUpMjP2).
+Start by reading our [Getting Started for Contributors](https://backstage.io/docs/getting-started/contributors) page to get yourself setup with a fresh copy of Backstage ready for your contributions. If you need help, just jump into our [Discord chatroom](https://discord.gg/backstage-687207715902193673).
## Coding Guidelines
@@ -136,7 +136,7 @@ Changesets **are** needed for new packages, as that is what triggers the package
1. Run `yarn changeset`
2. Select which packages you want to include a changeset for
-3. Select impact of change that you're introducing, using `minor` for breaking changes and `patch` otherwise. We do not use `major` changes while packages are at version `0.x`.
+3. Select impact of the change you're introducing. If the package you are changing is at version `0.x`, use `minor` for breaking changes and `patch` otherwise. If the package is at `1.0.0` or higher, use `major` for breaking changes, `minor` for backwards compatible API changes, and `patch` otherwise. See the [Semantic Versioning specification](https://semver.org/#semantic-versioning-specification-semver) for more details.
4. Explain your changes in the generated changeset. See [examples of well written changesets](https://backstage.io/docs/getting-started/contributors#writing-changesets).
5. Add generated changeset to Git
6. Push the commit with your changeset to the branch associated with your PR
diff --git a/README.md b/README.md
index 8a2bd338d9..60324a7789 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
[](https://opensource.org/licenses/Apache-2.0)
[](https://www.cncf.io/projects)
[](https://github.com/backstage/backstage/actions?query=workflow%3A%22Main+Master+Build%22)
-[](https://discord.gg/EBHEGzX)
+[](https://discord.gg/backstage-687207715902193673)

[](https://codecov.io/gh/backstage/backstage)
[](https://github.com/backstage/backstage/releases)
@@ -45,7 +45,7 @@ Check out [the documentation](https://backstage.io/docs/getting-started) on how
## Community
-- [Discord chatroom](https://discord.gg/MUpMjP2) - Get support or discuss the project
+- [Discord chatroom](https://discord.gg/backstage-687207715902193673) - Get support or discuss the project
- [Contributing to Backstage](https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md) - Start here if you want to contribute
- [RFCs](https://github.com/backstage/backstage/labels/rfc) - Help shape the technical direction
- [FAQ](https://backstage.io/docs/FAQ) - Frequently Asked Questions
diff --git a/STYLE.md b/STYLE.md
index 87e0644759..e38354ae1d 100644
--- a/STYLE.md
+++ b/STYLE.md
@@ -94,6 +94,13 @@ This section describes guidelines for designing public APIs. It can also be appl
/* ... */
}
+ // In order to make a private constructor available for testing you can use a
+ // static factory marked as `@internal`, which will not show up in the public API.
+ /** @internal */
+ static forTesting(internalOptions?: { ... }) {
+ return new DefaultImageLoader(internalOptions);
+ }
+
private constructor(/* ... */) {
/* ... */
}
diff --git a/app-config.yaml b/app-config.yaml
index dd72051189..22f3f4c9c5 100644
--- a/app-config.yaml
+++ b/app-config.yaml
@@ -17,7 +17,7 @@ app:
- title: Discord Chatroom
icon: chat
links:
- - url: https://discord.gg/MUpMjP2
+ - url: https://discord.gg/backstage-687207715902193673
title: '#backstage'
backend:
@@ -118,6 +118,11 @@ proxy:
headers:
Authorization: 'Api-Token ${DYNATRACE_ACCESS_TOKEN}'
+ '/stackstorm':
+ target: https://your.stackstorm.instance.com/api
+ headers:
+ St2-Api-Key: ${ST2_API_KEY}
+
organization:
name: My Company
@@ -441,5 +446,8 @@ apacheAirflow:
gocd:
baseUrl: https://your.gocd.instance.com
+stackstorm:
+ webUrl: https://your.stackstorm.webui.instance.com
+
permission:
enabled: true
diff --git a/cypress/.eslintrc.js b/cypress/.eslintrc.js
index a77de4a629..07f264452d 100644
--- a/cypress/.eslintrc.js
+++ b/cypress/.eslintrc.js
@@ -2,15 +2,6 @@ module.exports = {
extends: [require.resolve('@backstage/cli/config/eslint.backend')],
rules: {
'no-console': 0,
- 'import/no-extraneous-dependencies': [
- 'error',
- {
- devDependencies: true,
- optionalDependencies: false,
- peerDependencies: false,
- bundledDependencies: false,
- },
- ],
'jest/valid-expect': 'off',
'jest/expect-expect': 'off',
'no-restricted-syntax': 'off',
diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts
new file mode 100644
index 0000000000..fd4db77d61
--- /dev/null
+++ b/cypress/support/commands.ts
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+
+// ***********************************************
+// This example commands.ts shows you how to
+// create various custom commands and overwrite
+// existing commands.
+//
+// For more comprehensive examples of custom
+// commands please read more here:
+// https://on.cypress.io/custom-commands
+// ***********************************************
+//
+//
+// -- This is a parent command --
+// Cypress.Commands.add('login', (email, password) => { ... })
+//
+//
+// -- This is a child command --
+// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
+//
+//
+// -- This is a dual command --
+// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
+//
+//
+// -- This will overwrite an existing command --
+// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
+//
+// declare global {
+// namespace Cypress {
+// interface Chainable {
+// login(email: string, password: string): Chainable
+// drag(subject: string, options?: Partial): Chainable
+// dismiss(subject: string, options?: Partial): Chainable
+// visit(originalFn: CommandOriginalFn, url: string, options: Partial): Chainable
+// }
+// }
+// }
diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts
new file mode 100644
index 0000000000..c71790471e
--- /dev/null
+++ b/cypress/support/e2e.ts
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+
+// This example support/e2e.ts is processed and
+// loaded automatically before your test files.
+//
+// This is a great place to put global configuration and
+// behavior that modifies Cypress.
+//
+// You can change the location of this file or turn off
+// automatically serving support files with the
+// 'supportFile' configuration option.
+//
+// You can read more here:
+// https://on.cypress.io/configuration
+// ***********************************************************
+
+// Import commands.js using ES2015 syntax:
+import './commands';
+
+// Alternatively you can use CommonJS syntax:
+// require('./commands')
diff --git a/cypress/yarn.lock b/cypress/yarn.lock
index ded043cdaf..36dd0b056d 100644
--- a/cypress/yarn.lock
+++ b/cypress/yarn.lock
@@ -1415,22 +1415,22 @@ __metadata:
linkType: hard
"typescript@npm:^4.1.3":
- version: 4.9.4
- resolution: "typescript@npm:4.9.4"
+ version: 4.9.5
+ resolution: "typescript@npm:4.9.5"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
- checksum: e782fb9e0031cb258a80000f6c13530288c6d63f1177ed43f770533fdc15740d271554cdae86701c1dd2c83b082cea808b07e97fd68b38a172a83dbf9e0d0ef9
+ checksum: ee000bc26848147ad423b581bd250075662a354d84f0e06eb76d3b892328d8d4440b7487b5a83e851b12b255f55d71835b008a66cbf8f255a11e4400159237db
languageName: node
linkType: hard
"typescript@patch:typescript@^4.1.3#~builtin":
- version: 4.9.4
- resolution: "typescript@patch:typescript@npm%3A4.9.4#~builtin::version=4.9.4&hash=a1c5e5"
+ version: 4.9.5
+ resolution: "typescript@patch:typescript@npm%3A4.9.5#~builtin::version=4.9.5&hash=a1c5e5"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
- checksum: 37f6e2c3c5e2aa5934b85b0fddbf32eeac8b1bacf3a5b51d01946936d03f5377fe86255d4e5a4ae628fd0cd553386355ad362c57f13b4635064400f3e8e05b9d
+ checksum: 2eee5c37cad4390385db5db5a8e81470e42e8f1401b0358d7390095d6f681b410f2c4a0c496c6ff9ebd775423c7785cdace7bcdad76c7bee283df3d9718c0f20
languageName: node
linkType: hard
diff --git a/docs/FAQ.md b/docs/FAQ.md
index fcd5542f74..5b93f07d78 100644
--- a/docs/FAQ.md
+++ b/docs/FAQ.md
@@ -231,7 +231,7 @@ We take security seriously. When it comes to packages and code we scan our
repositories periodically and update our packages to the latest versions. When
it comes to deployment of Backstage within an organisation it depends on the
deployment and security setup in your organisation. Reach out to us on
-[Discord](https://discord.gg/MUpMjP2) if you have specific queries.
+[Discord](https://discord.gg/backstage-687207715902193673) if you have specific queries.
Please report sensitive security issues via Spotify's
[bug-bounty program](https://hackerone.com/spotify) rather than GitHub.
diff --git a/docs/README.md b/docs/README.md
deleted file mode 100644
index c63a12b589..0000000000
--- a/docs/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Documentation
-
-The Backstage documentation is available at https://backstage.io/docs
diff --git a/docs/api/utility-apis.md b/docs/api/utility-apis.md
index a69c874307..44c00d1ab7 100644
--- a/docs/api/utility-apis.md
+++ b/docs/api/utility-apis.md
@@ -290,9 +290,7 @@ The figure below shows the relationship between
FooApi via the
fooApiRef.
-
-
-
+
The current method for connecting Utility API providers and consumers is via the
React tree using an `ApiProvider`, which is added to the `AppProvider` of the
diff --git a/docs/assets/auth/gcp-iap-jwt-audience-code-popup.png b/docs/assets/auth/gcp-iap-jwt-audience-code-popup.png
new file mode 100644
index 0000000000..72b69b3b01
Binary files /dev/null and b/docs/assets/auth/gcp-iap-jwt-audience-code-popup.png differ
diff --git a/docs/assets/backend-system/architecture-building-blocks.drawio.svg b/docs/assets/backend-system/architecture-building-blocks.drawio.svg
new file mode 100644
index 0000000000..7a65f3a725
--- /dev/null
+++ b/docs/assets/backend-system/architecture-building-blocks.drawio.svg
@@ -0,0 +1,224 @@
+
diff --git a/docs/assets/header.png b/docs/assets/header.png
new file mode 100644
index 0000000000..3c39971d36
Binary files /dev/null and b/docs/assets/header.png differ
diff --git a/docs/assets/permissions/permission-framework-overview.drawio.svg b/docs/assets/permissions/permission-framework-overview.drawio.svg
index c7e628f8a6..3740384748 100644
--- a/docs/assets/permissions/permission-framework-overview.drawio.svg
+++ b/docs/assets/permissions/permission-framework-overview.drawio.svg
@@ -1,4 +1,4 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/docs/assets/software-catalog/software-model-entities.drawio.svg b/docs/assets/software-catalog/software-model-entities.drawio.svg
old mode 100755
new mode 100644
index e14cc93e62..d087583245
--- a/docs/assets/software-catalog/software-model-entities.drawio.svg
+++ b/docs/assets/software-catalog/software-model-entities.drawio.svg
@@ -1,4 +1,4 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/docs/auth/bitbucket/provider.md b/docs/auth/bitbucket/provider.md
index 36859dab45..b03201893c 100644
--- a/docs/auth/bitbucket/provider.md
+++ b/docs/auth/bitbucket/provider.md
@@ -61,7 +61,7 @@ how this is done. Note that for the Bitbucket provider, you'll want to use
factory.
The `@backstage/plugin-auth-backend` plugin also comes with two built-in
-resolves that can be used if desired. The first one is the
+resolvers that can be used if desired. The first one is the
`bitbucketUsernameSignInResolver`, which identifies users by matching their
Bitbucket username to `bitbucket.org/username` annotations of `User` entities in
the catalog. Note that you must populate your catalog with matching entities or
diff --git a/docs/auth/bitbucketServer/provider.md b/docs/auth/bitbucketServer/provider.md
new file mode 100644
index 0000000000..96c7db263e
--- /dev/null
+++ b/docs/auth/bitbucketServer/provider.md
@@ -0,0 +1,52 @@
+---
+id: provider
+title: Bitbucket Server Authentication Provider
+sidebar_label: Bitbucket Server
+description: Adding Bitbucket Server OAuth as an authentication provider in Backstage
+---
+
+The Backstage `core-plugin-api` package comes with a Bitbucket Server authentication provider that can authenticate
+users using Bitbucket Server. This does **NOT** work with Bitbucket Cloud.
+
+## Create an Application Link in Bitbucket Server
+
+To add Bitbucket Server authentication, you must create an outgoing application link. Follow the steps described in
+the [Bitbucket Server documentation](https://confluence.atlassian.com/bitbucketserver/configure-an-outgoing-link-1108483656.html)
+to create one.
+
+## Configuration
+
+The provider configuration can then be added to your `app-config.yaml` under the root `auth` configuration:
+
+```yaml
+auth:
+ environment: development
+ providers:
+ bitbucketServer:
+ development:
+ host: bitbucket.org
+ clientId: ${AUTH_BITBUCKET_SERVER_CLIENT_ID}
+ clientSecret: ${AUTH_BITBUCKET_SERVER_CLIENT_SECRET}
+```
+
+The Bitbucket Server provider is a structure with two configuration keys:
+
+- `clientId`: The client ID that was generated by Bitbucket, e.g. `b0f868455c15dcdff5c5fb5d173ae684`.
+- `clientSecret`: The client secret tied to the generated client ID.
+
+## Adding the provider to the Backstage frontend
+
+To add the provider to the frontend, add the `bitbucketServerAuthApi` reference and `SignInPage` component as shown
+in [Adding the provider to the sign-in page](../index.md#adding-the-provider-to-the-sign-in-page).
+
+## Using Bitbucket Server for sign-in
+
+In order to use the Bitbucket Server provider for sign-in, you must configure it with a `signIn.resolver`. See
+the [Sign-In Resolver documentation](../identity-resolver.md) for more details on how this is done. Note that for the
+Bitbucket Server provider, you'll want to use `bitbucketServer` as the provider ID,
+and `providers.bitbucketServer.create` for the provider factory.
+
+The `@backstage/plugin-auth-backend` plugin also comes with a built-in resolver that can be used if desired.
+The `emailMatchingUserEntityProfileEmail` identifies users by matching their Bitbucket Server email address to the email
+address of `User` entities in the catalog. Note that you must populate your catalog with matching entities or users will
+not be able to sign in with this resolver.
diff --git a/docs/auth/google/gcp-iap-auth.md b/docs/auth/google/gcp-iap-auth.md
index d6303ced6f..36eece85c5 100644
--- a/docs/auth/google/gcp-iap-auth.md
+++ b/docs/auth/google/gcp-iap-auth.md
@@ -29,7 +29,9 @@ auth:
jwtHeader: x-custom-header # Optional: Only if you are using a custom header for the IAP JWT
```
-You can find the project number and service ID in the Google Cloud Console.
+The full `audience` value can be obtained by visiting your [Identity-Aware Proxy Google Cloud console](https://console.cloud.google.com/security/iap), selecting your project, finding your Backend Service to proxy, clicking the 3 vertical dots then "Get JWT Audience Code", and copying from the resulting popup, which will look similar to the following:
+
+
This config section must be in place for the provider to load at all. Now let's
add the provider itself.
@@ -41,7 +43,7 @@ besides the config section above, it also needs to be given one or more
callbacks in actual code as well as described below.
Add a `providerFactories` entry to the router in
-`packages/backend/plugin/auth.ts`.
+`packages/backend/src/plugins/auth.ts`.
```ts
import { providers } from '@backstage/plugin-auth-backend';
@@ -75,7 +77,10 @@ export default async function createPlugin(
// GSuite or similar, based on the IAP token sub/email claims
const id = iapToken.email.split('@')[0];
const sub = stringifyEntityRef({ kind: 'User', name: id });
- const ent = [sub, stringifyEntityRef({ kind: 'Group', name: 'team-name' });
+ const ent = [
+ sub,
+ stringifyEntityRef({ kind: 'Group', name: 'team-name' }),
+ ];
return ctx.issueToken({ claims: { sub, ent } });
},
},
diff --git a/docs/auth/index.md b/docs/auth/index.md
index fa185c0c23..8e15830aa8 100644
--- a/docs/auth/index.md
+++ b/docs/auth/index.md
@@ -18,6 +18,7 @@ Backstage comes with many common authentication providers in the core library:
- [Auth0](auth0/provider.md)
- [Azure](microsoft/provider.md)
- [Bitbucket](bitbucket/provider.md)
+- [Bitbucket Server](bitbucketServer/provider.md)
- [Cloudflare Access](cloudflare/access.md)
- [GitHub](github/provider.md)
- [GitLab](gitlab/provider.md)
diff --git a/docs/auth/microsoft/azure-easyauth.md b/docs/auth/microsoft/azure-easyauth.md
new file mode 100644
index 0000000000..3571a3e5c5
--- /dev/null
+++ b/docs/auth/microsoft/azure-easyauth.md
@@ -0,0 +1,120 @@
+---
+id: azure-easy-auth
+title: Azure EasyAuth Provider
+sidebar_label: Azure EasyAuth
+description: Adding Azure's EasyAuth Proxy as an authentication provider in Backstage
+---
+
+## Backstage Changes
+
+Add the following into your `app-config.yaml` or `app-config.production.yaml` file
+
+Add a `providerFactories` entry to the router in
+`packages/backend/src/plugins/auth.ts`.
+
+```ts
+import { providers } from '@backstage/plugin-auth-backend';
+
+export default async function createPlugin(
+ env: PluginEnvironment,
+): Promise {
+ const authProviderFactories = {
+ 'azure-easyauth': providers.easyAuth.create({
+ signIn: {
+ resolver: async (info, ctx) => {
+ const {
+ fullProfile: { id },
+ } = info.result;
+
+ if (!id) {
+ throw new Error('User profile contained no id');
+ }
+
+ return await ctx.signInWithCatalogUser({
+ annotations: {
+ 'graph.microsoft.com/user-id': id,
+ },
+ });
+ },
+ },
+ }),
+ };
+
+ return await createRouter({
+ logger: env.logger,
+ config: env.config,
+ database: env.database,
+ discovery: env.discovery,
+ tokenManager: env.tokenManager,
+ providerFactories: authProviderFactories,
+ });
+}
+```
+
+Now the backend is ready to serve auth requests on the
+`/api/auth/azure-easyauth/refresh` endpoint. All that's left is to update the frontend
+sign-in mechanism to poll that endpoint through the IAP, on the user's behalf.
+
+## Frontend Changes
+
+It is recommended to use the `ProxiedSignInPage` for this provider, which is
+installed in `packages/app/src/App.tsx` like this:
+
+```diff
++import { ProxiedSignInPage } from '@backstage/core-components';
+
+ const app = createApp({
+ components: {
++ SignInPage: props => ,
+```
+
+See the [Sign-In with Proxy Providers](../index.md#sign-in-with-proxy-providers) section for more information.
+
+## Azure Configuration
+
+How to configure azure depends on the service you're enable AAD auth on the app service.
+
+### Azure App Services
+
+To use EasyAuth with App Services, turn on Active Directory authentication
+You must also enable the token store.
+
+The following example shows how to do this via a bicep template:
+
+```bicep
+resource webApp 'Microsoft.Web/sites@2022-03-01' existing = {
+ name: 'MY-WEBAPP-NAME'
+
+ resource authConfig 'config' = {
+ name: 'authsettingsV2'
+ properties: {
+ globalValidation: {
+ redirectToProvider: 'AzureActiveDirectory'
+ requireAuthentication: true
+ unauthenticatedClientAction: 'RedirectToLoginPage'
+ }
+ login: {
+ tokenStore: {
+ enabled: true
+ }
+ }
+ platform: {
+ enabled: true
+ }
+ identityProviders: {
+ azureActiveDirectory: {
+ enabled: true
+ login: {
+ loginParameters: [ 'domain_hint=MYCOMPANY.COM' ]
+ }
+ registration: {
+ clientId: 'CLIENT-ID'
+ clientSecretSettingName: 'CLIENT-SECRET-NAME'
+ openIdIssuer: 'https://sts.windows.net/${tenant().tenantId}/v2.0'
+ }
+ }
+ }
+ }
+ }
+}
+```
diff --git a/docs/backend-system/architecture/01-index.md b/docs/backend-system/architecture/01-index.md
index 5e6fb60ee6..aff730b0d1 100644
--- a/docs/backend-system/architecture/01-index.md
+++ b/docs/backend-system/architecture/01-index.md
@@ -1,23 +1,27 @@
---
id: index
title: Backend System Architecture
-sidebar_label: System Architecture
+sidebar_label: Overview
# prettier-ignore
description: The structure and architecture of the new Backend System and its component parts
---
-# Overview
+> **DISCLAIMER: The new backend system is in alpha, and still under active development. While we have reviewed the interfaces carefully, they may still be iterated on before the stable release.**
+
+## Building Blocks
This section introduces the high-level building blocks upon which this new
system is built. These are all concepts that exist in our current system in one
way or another, but they have all been lifted up to be first class concerns in
-the new system.
+the new system. Regardless of whether you are setting up your own backstage
+instance, developing plugins, or extending plugins with new features, it is
+important to understand these concepts.
-## Building Blocks
+The diagram below provides an overview of the different building blocks, and the other blocks that each of them interact with.
-This section introduces the high-level building blocks upon which this new system is built. These are all concepts that exist in our current system in one way or another, but they have all been lifted up to be first class concerns in the new system.
+
-
+> NOTE: These are all concepts that existed in our old backend system in one way or another, but they have now all been lifted up to be first class concerns.
### Backend
@@ -33,7 +37,7 @@ Plugins provide the actual features, just like in our existing system. They oper
Services provide utilities to help make it simpler to implement plugins, so that each plugin doesn't need to implement everything from scratch. There are both many built-in services, like the ones for logging, database access, and reading configuration, but you can also import third-party services, or create your own.
-Services are also a customization point for individual backend installations. You can both override services with your own implementations, as well as make smaller customizations to existing services.
+Services are also a customization point for individual backend installations. You can override services with your own implementations, as well as make smaller customizations to existing services.
### Extension Points
@@ -41,7 +45,7 @@ Many plugins have ways in which you can extend them, for example entity provider
Extension Points look a little bit like services, since you depended on them just like you would a service. A key difference is that extension points are registered and provided by plugins themselves, based on what customizations each individual plugin wants to expose.
-Extension Points are also exported separately from the plugin instance itself, and a single plugin can also expose multiple different extension points at once. This makes it easier to evolve and deprecated individual Extension Points over time, rather than dealing with a single large API surface.
+Extension Points are also exported separately from the plugin instance itself, and a single plugin can also expose multiple different extension points at once. This makes it easier to evolve and deprecate individual Extension Points over time, rather than dealing with a single large API surface.
### Modules
@@ -49,22 +53,20 @@ Modules use the plugin Extension Points to add new features for plugins. They mi
Each module may only extend a single plugin, and the module must be deployed together with that plugin in the same backend instance. Modules may however only communicate with their plugin through its registered extension points.
-Just like plugins, modules also have access to services and can depend on their own service implementations. They will however share services with the plugin that they extend, there are no module-specific service implementations.
+Just like plugins, modules also have access to services and can depend on their own service implementations. They will however share services with the plugin that they extend - there are no module-specific service implementations.
## Package structure
A detailed explanation of the package architecture can be found in the
[Backstage Architecture
Overview](../../overview/architecture-overview.md#package-architecture). The
-most important packages to consider for this system are `backend`,
-`plugin--backend`, `plugin--node`, and
-`plugin--backend-module-`.
+most important packages to consider for this system are the following:
- `plugin--backend` houses the implementation of the backend plugins
themselves.
-- `plugin--node` houses the extension points and any other utilities
+- `plugin--node` houses the backend plugin's extension points and any other utilities
that modules or other plugins might need.
- `plugin--backend-module-` houses the modules that extend
- the plugins via the extension points.
+ the plugin via its extension points.
- `backend` is the backend itself that wires everything together to something
that you can deploy.
diff --git a/docs/backend-system/architecture/02-backends.md b/docs/backend-system/architecture/02-backends.md
index c73a899863..b6079d71b4 100644
--- a/docs/backend-system/architecture/02-backends.md
+++ b/docs/backend-system/architecture/02-backends.md
@@ -3,12 +3,17 @@ id: backends
title: Backend Instances
sidebar_label: Backend
# prettier-ignore
-description: Service APIs for backend plugins
+description: Backend instances
---
-The new Backstage backend system is being built to help make it simpler to install backend plugins and to keep projects up to date. It also changes the foundation to one that makes it a lot easier to evolve plugins and the system itself with minimal disruption or cause for breaking changes. You can read more about the reasoning in the [original RFC](https://github.com/backstage/backstage/issues/11611).
+> **DISCLAIMER: The new backend system is in alpha, and still under active development. While we have reviewed the interfaces carefully, they may still be iterated on before the stable release.**
-One of the goals of the new system was to reduce the code needed for setting up a Backstage backend and installing plugins. This is an example of how you create, add features, and start up your backend in the new system:
+## The Backend Instance
+
+This is the main entry point for creating a backend. It does not have any functionality in and of itself, but is simply responsible for wiring things together.
+It is up to you to decide how many different backends you want to deploy. You can have all features in a single one, or split things out into multiple smaller deployments.
+
+Below is a simple example of a backend that installs only the catalog plugin and starts it up.
```ts
import { createBackend } from '@backstage/backend-defaults';
@@ -24,4 +29,12 @@ backend.add(catalogPlugin());
await backend.start();
```
-One notable change that helped achieve this much slimmer backend setup is the introduction of a system for dependency injection, which is very similar to the one in the Backstage frontend.
+`createBackend` is responsible for creating your backend instance, and wiring up all the services that you have provided. It deals with creating default implementations of all the [core services](../core-services/01-index.md) that are used by the plugins, and also provides a way to override the default implementations with your own. You can read more about creating services and overriding them in the [building backends docs](../building-backends/01-index.md).
+
+The backend instance has the ability to add features to the backend which are done using the `.add` method. Features are either plugins or modules, and you can read more about them in the [building plugins and modules docs](../building-plugins-and-modules/01-index.md). By default, a backend instance has no default features, and the services are responsible for wiring everything together.
+
+At a high level, when you call `createBackend`, it will create a new backend instance, which has a registry of all the services that are currently registered, and by adding features to the backend instance and calling the `.start()` method it will ensure that all the dependencies are wired up correctly and the `registerInit` methods are called in the correct order.
+
+Underneath the hood, `createBackend` calls `createSpecializedBackend` from `@backstage/backend-app-api` which is responsible for actually creating the backend instance, but with no services or no features. You can think of `createBackend` more of a 'batteries included' approach, and `createSpecializedBackend` a little more low level.
+
+As mentioned previously there's also the ability to create multiple of these backends in your project so that you can split apart your backend and deploy different backends that can scale independently of each other. For instance you might choose to deploy a backend with only the catalog plugin enabled, and one with just the scaffolder plugin enabled. We've provided some tools to be able to share services and defaults across your backend system, and you can find out more about that in the [shared environments docs](../building-backends/01-index.md#shared-environments).
diff --git a/docs/backend-system/architecture/03-services.md b/docs/backend-system/architecture/03-services.md
index 27fea27db9..8a86d90efb 100644
--- a/docs/backend-system/architecture/03-services.md
+++ b/docs/backend-system/architecture/03-services.md
@@ -1,103 +1,229 @@
---
id: services
-title: Backend Service APIs
-sidebar_label: Service APIs
+title: Backend Services
+sidebar_label: Services
# prettier-ignore
-description: Service APIs for backend plugins
+description: Services for backend plugins
---
-## Backend Services
+> **DISCLAIMER: The new backend system is in alpha, and still under active development. While we have reviewed the interfaces carefully, they may still be iterated on before the stable release.**
-The default backend provides several [core services](https://github.com/backstage/backstage/blob/master/packages/backend-plugin-api/src/services/definitions/coreServices.ts) out of the box which includes access to configuration, logging, databases and more.
-Service dependencies are declared using their `ServiceRef`s in the `deps` section of the plugin or module, and the implementations are then forwarded to the `init` method of the plugin or module.
+Backend services provide shared functionality available to all backend plugins and modules. They are made available through service references that embed a type that represents the service interface, similar to how [Utility APIs](../../api/utility-apis.md) work in the Backstage frontend system. To use a service in your plugin or module you request an implementation of that service using the service reference.
-### Service References
+The system surrounding services exists to provide a level of indirection between the service interfaces and their implementation. It is an implementation of dependency injection, where each backend instance is the dependency injection container. The implementation for each service is provided by a service factory, which encapsulates the logic for how each service instance is created.
-A `ServiceRef` is a named reference to an interface which are later used to resolve the concrete service implementation. Conceptually this is very similar to `ApiRef`s in the frontend.
-Services is what provides common utilities that previously resided in the `PluginEnvironment` such as Config, Logging and Database.
+## Service Interfaces
-On startup the backend will make sure that the services are initialized before being passed to the plugin/module that depend on them.
-ServiceRefs contain a scope which is used to determine if the serviceFactory creating the service will create a new instance scoped per plugin/module or if it will be shared. `plugin` scoped services will be created once per plugin/module and `root` scoped services will be created once per backend instance.
+Service interfaces can be any TypeScript type, but it is best to make it an object interface with a number of methods. General guidelines for interface design apply: keep them simple and lean, with few but powerful methods. Take care to avoid locking down the ways in which individual methods can evolve. Often you want to stick to a method with an options object as its only parameter, and return a result object. If there is any reason for uncertainty about whether the method should be async or not, always make it async. For example, a minimal interface should often use the following pattern:
-#### Defining a Service
+```ts
+export interface FooService {
+ foo(options: FooOptions): Promise;
+}
+```
+
+## Service References
+
+Once you have defined a service interface, you need to create a service reference using the `createServiceRef` function. This will create a `ServiceRef` instance, which is a reference that you export in order to allow users to interact with your service. Conceptually this is very similar to `ApiRef`s in the frontend system. For example:
+
+```ts
+import { createServiceRef } from '@backstage/backend-plugin-api';
+
+export interface FooService {
+ foo(options: FooOptions): Promise;
+}
+
+export const fooServiceRef = createServiceRef({
+ id: 'example.foo', // the owner of this service is in this case the 'example' plugin
+});
+```
+
+The `fooServiceRef` that we create above should be exported, and can then be used to declare a dependency on the `FooService` interface and receive an implementation of it at runtime.
+
+When creating a service reference you need to give it an ID. This ID needs to be globally unique, and should generally be of the format `'.'`. For more naming patters surrounding services, see the [naming patterns](./07-naming-patterns.md#services) page.
+
+A note on naming: the frontend and backend systems intentionally use the separate names "APIs" and "Services" for concepts that are quite similar. This is to avoid confusion between the two, both in documentation and discussion, but also in code. While the two systems are quite similar, they are not identical, and they can't be used interchangeably.
+
+## Service Factories
+
+In order to be able to depend on a service interface through a service reference, we of course also need to have some way of creating the concrete implementation of it. To encapsulate that logic we use service factories, which define both how service instances are created, as well as what other services they depend on for their implementation.
+
+Service factories can come from many different sources. There are built-in service factories, external ones that you can import from other packages, and you can also create your own. Specific service factories are installed within each backend instance, which acts as the dependency injection container. For any given backend instance there can only be a single designated service factory for each service.
+
+To define a service factory, we use `createServiceFactory`:
+
+```ts
+import { createServiceFactory } from '@backstage/backend-plugin-api';
+
+class DefaultFooService implements FooService {
+ async foo(options: FooOptions): Promise {
+ // ...
+ }
+}
+
+export const fooServiceFactory = createServiceFactory({
+ service: fooServiceRef,
+ deps: { bar: barServiceRef },
+ factory({ bar }) {
+ return new DefaultFooService(bar);
+ },
+});
+```
+
+To create a service factory we need to provide a reference to the `service` for which the factory will create instances, a `deps` object which lists the other services that the factory depends on, and a `factory` function which will be called to create the service instance. The backend system will call the `factory` function with an object that contains the service instances for each of the dependencies listed in the `deps` object. If a service implementation does not depend on any other services, the `deps` are left as an empty object (`{}`). The `factory` function must return a value that implements the service interface.
+
+If you need the creation of the service instance to be asynchronous, you can make the `factory` function async. For example:
+
+```ts
+export const fooServiceFactory = createServiceFactory({
+ service: fooServiceRef,
+ deps: {},
+ async factory() {
+ const foo = new DefaultFooService();
+ await foo.init();
+ return foo;
+ },
+});
+```
+
+Note that circular dependencies among service factories are not allowed. This is verified at runtime, and your backend instance will refuse to start up if it detects any conflicts. Likewise, the backend will also fail to start up if a service factory depends on a service that is not provided by any registered service factory.
+
+## Service Factory Options
+
+To install a service factory in a backend instance, we pass it in through the `services` option to `createBackend`:
+
+```ts
+const backend = createBackend({
+ services: [fooServiceFactory()],
+});
+```
+
+Note that we call `fooServiceFactory` to create the service factory instance. This is because `createServiceFactory` always returns a factory function that creates the actual service factory. This is done to always allow for options to be added to the service factory in the future, without breaking existing code. To add options to your service factory, you wrap the object passed to `createServiceFactory` in a callback that accepts the desired options. For example:
+
+```ts
+export interface FooFactoryOptions {
+ mode: 'eager' | 'lazy';
+}
+
+export const fooServiceFactory = createServiceFactory(
+ (options?: FooFactoryOptions) => ({
+ service: fooServiceRef,
+ deps: { bar: barServiceRef },
+ factory({ bar }) {
+ return new DefaultFooService(bar, options?.mode);
+ },
+ }),
+);
+```
+
+This lets us use the options to customize the factory implementation in any way we want. From the outside the service factory looks just like before, except that we're now also able to pass options when installing the factory:
+
+```ts
+const backend = createBackend({
+ services: [fooServiceFactory({ mode: 'eager' })],
+});
+```
+
+## Core Services
+
+The backend system provides a number of core service definitions that both help implement the main functionality of the backend, but also provide a set of utilities for common concerns, such as logging, database access, job scheduling, and so on. These core services will always be present in a backend instance created with `createBackend`, and they can all be overridden with custom implementations if needed.
+
+The service references for all core services are exported via their own `coreServices` object, available from the `@backstage/backend-plugin-api` package. For example, the logging service is accessible via `coreServices.logger`.
+
+You can read more about what core services there are and how to use them in the [core services](../core-services/01-index.md) section.
+
+## Service Scope
+
+By default services are scoped to individual plugins, meaning that separate instances of the service will be created for each plugin. For example, in our `fooFactory` above, a separate instance of `DefaultFooService` will be created for every plugin that depends on the service. This both makes it possible to tailor the service implementations for the individual plugins, and also ensures some level of separation between plugins.
+
+The service scope is defined during the call to `createServiceRef`, with plugin scope being the default. Our above definition of the `fooServiceRef` is therefore equivalent to the following:
+
+```ts
+export const fooServiceRef = createServiceRef({
+ scope: 'plugin',
+ id: 'example.foo',
+});
+```
+
+There are only two possible scopes for services, `'plugin'` and `'root'`.
+
+## Root Scoped Services
+
+If a service is defined as a root scoped service, the implementation created by the factory will be shared across all plugins and services. One other differentiating factory for root scoped services is that they are always initialized, regardless of whether any plugins depend on them or not. This makes them suitable for implementing backend-wide concerns that are not specific to any individual plugin.
+
+There is a limitation in the usage of root scoped services, which is that their implementation can only depend on other root scoped services. Plugin scoped services on the other hand can depend on both root and plugin scoped services. Because of this limitation, one of the main reasons to define a root scoped services is to make it possible for other root scoped services to depend on it.
+
+Because of these limitations and particular use-cases for root scoped services, they tend to be more rare than plugin scoped services. In general, you should prefer defining a service as plugin scoped, unless you are implementing either of the two mentioned use-cases.
+
+Some services come in pairs of a plugin and a root scoped service definition. For example, the `rootLogger` service is a root scoped service, while the `logger` service is a plugin scoped service. The `rootLogger` service houses the main logging implementation, while the `logger` service simply builds upon the `rootLogger` to add plugin specific labels. This division exists so that other root scoped services also have access to a logging service, but it is always preferable if the split can be avoided. If you do end up implementing this pattern, the root scoped service should be prefixed with `root`, this is to encourage use of the plugin scoped service instead.
+
+## Plugin Metadata
+
+Plugin scoped services have access to a plugin metadata service, which is a special service provided by the backend system that is not possible to override. The plugin metadata service provides information about the plugin that a service instance is being created for. It is itself a plugin scoped service, and can be depended on like any other service through the `coreServices.pluginMetadata` reference.
+
+The plugin metadata service is the base for all plugin specific customizations for services. For example, the default implementation of the plugin scoped logger service uses the plugin metadata service to attach the plugin ID as a field in all log messages:
+
+```ts
+export const loggerServiceFactory = createServiceFactory({
+ service: coreServices.logger,
+ deps: {
+ rootLogger: coreServices.rootLogger,
+ pluginMetadata: coreServices.pluginMetadata,
+ },
+ factory({ rootLogger, pluginMetadata }) {
+ return rootLogger.child({ plugin: pluginMetadata.getId() });
+ },
+});
+```
+
+## Root Context for Service Factories
+
+Some services may benefit from having a context that is shared across all instances of a service. This of course only applies to plugin scoped services, as root scoped services only ever have a single instance. The root context could for example be used for sharing a common connection pool for database access, generated secrets for development, or any other kind of shared facility. Note that you should not use this to share state between plugins in production, as that would be a violation of the [plugin isolation rule](./04-plugins.md#rules-of-plugins).
+
+The root context is defined as part of the service factory by passing the `createRootContext` option:
+
+```ts
+export const fooServiceFactory = createServiceFactory({
+ service: fooServiceRef,
+ deps: { rootLogger: coreServices.rootLogger, bar: barServiceRef },
+ createRootContext({ rootLogger }) {
+ return new FooRootContext(rootLogger);
+ }
+ factory({ bar }, ctx) {
+ return ctx.forPlugin(bar)
+ },
+});
+```
+
+Whatever value is returned by the `createRootContext` function will shared and passed as the second argument to each invocation of the `factory` function. That way you can create a shared context that is used in the creation of each plugin instance. Unlike the `factory` function, the `createRootContext` function will only receive root scoped services as its dependencies, but just like the `factory` function, it can also be `async`.
+
+## Default Service Factories
+
+There are a lot of services that are installed in any standard Backstage backend instance by default. You can expect these services to always exist, and do not need to take any additional steps to make them available. This is not necessarily true for services that you import from external packages, as the user of your plugin or module might not have installed a factory for that service in their backend. In order to avoid having to ask integrators of your plugin to install a service factory for a service that you depend on, it is possible to define a default factory for a service.
+
+Default service factories are defined as part of the service reference by passing the `defaultFactory` option to `createServiceRef`:
```ts
import {
createServiceFactory,
- coreServices,
+ createServiceRef,
} from '@backstage/backend-plugin-api';
-import { ExampleImpl } from './ExampleImpl';
-export interface ExampleApi {
- doSomething(): Promise;
-}
-
-export const exampleServiceRef = createServiceRef({
- id: 'example',
- scope: 'plugin', // can be 'root' or 'plugin'
-
- // The defaultFactory is optional to implement but it will be used if no
- // other factory is provided to the backend. This allows for the backend
- // to provide a default implementation of the service without having to wire
- // it beforehand.
+export const fooServiceRef = createServiceRef({
+ id: 'example.foo',
defaultFactory: async service =>
createServiceFactory({
service,
- deps: {
- logger: coreServices.logger,
- plugin: coreServices.pluginMetadata,
- },
- // This root context method is only available for plugin scoped services.
- // It's only called once per backend instance.
- // Logger is available at the root context level as it's also a root
- // scoped service.
- createRootContext({ logger }) {
- return new ExampleImplFactory({ logger });
- },
- // Plugin is available as it's a plugin scoped service and will be
- // created once per plugin. The logger can be had here too if needed.
- // Both this anc the root context can also be async.
- factory({ logger, plugin }, rootContext) {
- // This block will be executed once for every plugin that depends on
- // this service
- logger.info('Initializing example service plugin instance');
- return rootContext.forPlugin(plugin.getId());
+ deps: {},
+ factory() {
+ return new DefaultFooService();
},
}),
});
```
-### Overriding Services
+Note that we don't use the `fooServiceRef` when creating our service factory, but instead use the `service` parameter in the default factory callback. This is because attempting to use `fooServiceRef` directly would result in a circular reference.
-In this example we replace the default root logger service implementation with a custom one that streams logs to GCP. The `rootLoggerServiceRef` has a `'root'` scope, meaning there are no plugin-specific instances of this service.
+If a service defines a default factory, that factory will be used if there is no explicit factory registered in the backend for that service. This allows users of your service to directly import and use a service, without worrying about whether it is installed or not. It is recommended to always define a default factory for any service that you are exporting for use in other plugins or modules.
-```ts
-import {
- createServiceFactory,
- rootLoggerServiceRef,
- LoggerService,
-} from '@backstage/backend-plugin-api';
-
-// This custom implementation would typically live separately from
-// the backend setup code, either nearby such as in
-// packages/backend/src/services/logger/GoogleCloudLogger.ts
-// Or you can let it live in its own library package.
-class GoogleCloudLogger implements LoggerService {
- static factory = createServiceFactory({
- service: rootLoggerServiceRef,
- deps: {},
- factory() {
- return new GoogleCloudLogger();
- },
- });
- // custom implementation here ...
-}
-
-// packages/backend/src/index.ts
-const backend = createBackend({
- services: [
- // supplies additional or replacement services to the backend
- GoogleCloudLogger.factory(),
- ],
-});
-```
+When defining a default factory for a service, it is possible for it to end up with duplicate implementations at runtime. This applies both to any shared root context in your factory, as well as plugin specific instances of your service. This is because package dependency version ranges may not line up perfectly, causing duplicate installations of the same package. This can happen both for two different plugins using the same service, but also across a plugin and its modules. If your service would break in this scenario, you should not define a default factory for it, but instead require that users of your service explicitly install a factory in their backend instance.
diff --git a/docs/backend-system/architecture/04-plugins.md b/docs/backend-system/architecture/04-plugins.md
index 6f077a0d2d..04271b17da 100644
--- a/docs/backend-system/architecture/04-plugins.md
+++ b/docs/backend-system/architecture/04-plugins.md
@@ -6,29 +6,28 @@ sidebar_label: Plugins
description: Backend plugins
---
-## Creating Plugins
+> **DISCLAIMER: The new backend system is in alpha, and still under active development. While we have reviewed the interfaces carefully, they may still be iterated on before the stable release.**
-Plugins are created using the `createBackendPlugin` function. All plugins must have an ID and a register method. Plugins may also accept an options object, which can be either optional or required. The options are passed to the second parameter of the register method, and the options type is inferred and forwarded to the returned plugin factory function.
+Plugins provide the actual base features of a Backstage backend. Each plugin operates completely independently of all other plugins and they only communicate with each other through network calls. This means that there is a strong degree of isolation between plugins, and that each plugin can be considered a separate microservice. While a default Backstage project has all plugins installed within a single backend, it is also possible to split this setup into multiple backends, with each backend housing one or more plugins.
+
+## Defining a Plugin
+
+Plugins are created using the `createBackendPlugin` function, and should typically be exported from a plugin package. All plugins must have an ID and a `register` method, where the ID matches the plugin ID in the package name, without the `-backend` suffix. See also the [dedicated section](./07-naming-patterns.md) about proper naming patterns.
```ts
+// plugins/example-backend/src/plugin.ts
import {
- configServiceRef,
coreServices,
createBackendPlugin,
} from '@backstage/backend-plugin-api';
-// export type ExamplePluginOptions = { exampleOption: boolean };
export const examplePlugin = createBackendPlugin({
- // unique id for the plugin
- id: 'example',
- // It's possible to provide options to the plugin
- // register(env, options: ExamplePluginOptions) {
+ pluginId: 'example',
register(env) {
env.registerInit({
deps: {
logger: coreServices.logger,
},
- // logger is provided by the backend based on the dependency on loggerServiceRef above.
async init({ logger }) {
logger.info('Hello from example plugin');
},
@@ -37,32 +36,60 @@ export const examplePlugin = createBackendPlugin({
});
```
-The plugin can then be installed in the backend using the returned plugin factory function:
+The `env` object passed to the `register` callback contains different methods that declare the external surface of the plugin. The `env.registerInit` method is used to register an initialization function that is run when the backend starts up. The `deps` argument is used to declare service dependencies, and the `init` callback is passed an object with the resolved dependencies. In this case, we declare a dependency on the logger service, which is one of the core services available to all Backstage backend plugins. For a full list of core services as well as documentation for each services, see the [core services section](../core-services/01-index.md). Plugins can of course also depend on services exported by other libraries.
+
+The `createBackendPlugin` return value is exported as `examplePlugin`, which is a factory function used to create the actual plugin instance. For example, to install the plugin in your backend instance, you would do the following:
```ts
backend.add(examplePlugin());
```
-If we wanted our plugin to accept options as well, we'd accept the options as the second parameter of the register method:
+The reason for why our plugin instance has been wrapped up in a factory function is so that you can always chose to add options to your plugin in the future, without breaking existing usage. To add options you wrap the object passed to `createBackendPlugin` in a callback that accepts the desired options. For example:
```ts
-export const examplePlugin = createBackendPlugin({
- id: 'example',
- register(env, options?: { silent?: boolean }) {
- env.registerInit({
- deps: { logger: coreServices.logger },
- async init({ logger }) {
- if (!options?.silent) {
- logger.info('Hello from example plugin');
- }
- },
- });
- },
-});
+export interface ExamplePluginOptions {
+ skipHello: boolean;
+}
+
+export const examplePlugin = createBackendPlugin(
+ (options?: ExamplePluginOptions) => ({
+ pluginId: 'example',
+ register(env) {
+ env.registerInit({
+ deps: {
+ logger: coreServices.logger,
+ },
+ async init({ logger }) {
+ if (!options?.skipHello) {
+ logger.info('Hello from example plugin');
+ }
+ },
+ });
+ },
+ }),
+);
```
-Passing the option to the plugin during installation looks like this:
+Now your plugin accepts an optional options object, which can be used to configure each plugin instance. To supply options to the plugin, you pass them to the plugin factory method:
```ts
-backend.add(examplePlugin({ silent: true }));
+backend.add(examplePlugin({ skipHello: true }));
```
+
+It is also possible to make the options required, simply remove the `?` from the parameter declaration. This will be reflected in the returned factory function, which will now require the options parameter.
+
+Options are a simple way to allow for more lightweight customization of a plugin, but they do not allow for more complex extensions that require access to services. For that, you need to create and register extension points for your plugin, which are covered in the [next section](./05-extension-points.md).
+
+## Rules of Plugins
+
+The following rules apply to the production setup of Backstage plugins in the broader Backstage plugin ecosystem. Any plugin that is maintained under the `@backstage` package namespace should follow these rules, and it is recommended that all widely distributed plugins follow these rules as well.
+
+An exception to these rules are made for development or test setups, where shortcuts can be take in order to streamline development and keep things simple.
+
+### Scalable
+
+Plugins must always be designed to be horizontally scalable. This means that you should not keep any state in memory, or make sure that replicating this state across multiple instances is not an issue. Plugins should either be stateless, or store their state in an external service, such as a database.
+
+### Isolated
+
+Plugins must never communicate with each other directly through code, they may only communicate over the network. Plugins that wish to expose an external interface for other plugins and modules to use are recommended to do so though a [node-library](../../local-dev/cli-build-system.md#package-roles) package. The library should export an API client service to make calls to your plugin, or similar construct.
diff --git a/docs/backend-system/architecture/05-extension-points.md b/docs/backend-system/architecture/05-extension-points.md
index d74a4690c9..6d320037ba 100644
--- a/docs/backend-system/architecture/05-extension-points.md
+++ b/docs/backend-system/architecture/05-extension-points.md
@@ -6,11 +6,17 @@ sidebar_label: Extension Points
description: Extension points of backend plugins
---
-### Extension Points
+> **DISCLAIMER: The new backend system is in alpha, and still under active development. While we have reviewed the interfaces carefully, they may still be iterated on before the stable release.**
-Modules depend on extension points just as a regular dependency by specifying it in the `deps` section.
+While plugins are able to accept options for lightweight forms of customization and extension, you quickly hit a limit where you need something more powerful to allow users to extend your plugin. For this purpose, the backend system provides a mechanism for plugins to provide extension points, which can be used to expose deeper customizations for your plugin. Extension points are used by modules, which are installed in the backend adjacent to plugins. Modules are covered more in-depth in the [next section](./06-modules.md).
-#### Defining an Extension Point
+Extension points are quite similar to services, in that they both encapsulate an interface in a reference object. The key difference is that extension points are registered and provided by plugins themselves, and do not have any factory associated with them. Extension points for a given plugin are also only accessible to modules that extend that same plugin.
+
+Extension points should always be exported from a plugin node library package, for example `@backstage/plugin-catalog-node`. This is to allow for modules to avoid a direct dependency on the plugin, and make it easier to evolve extension points over time. You can export as many different extension points as you want, just be mindful of the complexity of the API surface. It is however often better to export multiple extension points with few methods, rather than few extension points with many methods, as that tends to be easier to maintain.
+
+## Defining an Extension Point
+
+Extension points are created using the `createExtensionPoint` method from `@backstage/backend-plugin-api`. You need to provide the type, as well as an ID.
```ts
import { createExtensionPoint } from '@backstage/backend-plugin-api';
@@ -25,6 +31,54 @@ export const scaffolderActionsExtensionPoint =
});
```
-#### Registering an Extension Point
+## Registering an Extension Point
-Extension points are registered by a plugin and extended by modules.
+For modules to be able to use your extension point, an implementation of it must be registered by the plugin. This is done using the `registerExtensionPoint` method in the `register` callback of the plugin definition.
+
+```ts
+class ActionsExtension implements ScaffolderActionsExtensionPoint {
+ addActions(...actions: TemplateAction[]): void { ... }
+
+ getRegisteredActions() { ... }
+}
+
+export const scaffolderPlugin = createBackendPlugin(
+ {
+ pluginId: 'scaffolder',
+ register(env) {
+ const actionsExtensions = new ActionsExtension();
+ env.registerExtensionPoint(
+ scaffolderActionsExtensionPoint,
+ actionsExtensions,
+ );
+
+ env.registerInit({
+ deps: { ... },
+ async init({ ... }) {
+ const actions = actionsExtension.getRegisteredActions();
+
+ // Use the registered actions when setting up the scaffolder ...
+ },
+ });
+ },
+ },
+);
+```
+
+There are a couple of things to note here. The first is that our `ActionsExtension` class both implements the `ScaffolderActionsExtensionPoint` interface, but also has additional public methods. These methods won't be available to the modules that use this extension, but we can use them here in the implementation of our plugin. Note also that the `ActionsExtension` class is _not_ exported to the outside, since it's only for the internal use of this plugin during its setup phase.
+
+The second is that we create our `ActionsExtension` instance within the `register` method, and then access it directly in our `init` method. This is both safe to do and an intended convenience. All modules that extend our plugin will be completely initialized before our plugin gets initialized, which means that at the point where our `init` method is called, all actions have been added and can be accessed.
+
+## Extension Point Design
+
+Designing the extension point interface requires careful consideration. It is a public API surface that your plugin will need to maintain over time. Keep in mind that the installation of modules is an intentional action by the user, meaning you can always design an extension point interface for additions only. For example, there is no need for the `scaffolderActionsExtensionPoint` to support removal of actions, since the user can just uninstall the module that added the action.
+
+Another pattern that can be used is a type of singleton pattern where the extension point is used to add or override some default behavior. For example, let's say the scaffolder wants to expose a way to customize the execution of template tasks. It wouldn't really make sense to allow multiple modules to each add their own task runners, so instead we use a setter to ensure that only one task runner is installed, throwing an error if any other module tries to install another task runner.
+
+```ts
+interface ScaffolderTaskRunnerExtensionPoint {
+ setTaskRunner(taskRunner: TaskRunner): void;
+}
+```
+
+If you want to make breaking changes to an extension point that already has some usage, we recommend that you instead deprecate the existing one and create a new one with a different name. You might want to use a completely new name, but you can also suffix the existing one with a version number, for example `scaffolderActionsV2ExtensionPoint`.
diff --git a/docs/backend-system/architecture/06-modules.md b/docs/backend-system/architecture/06-modules.md
index 428e51fafa..b4a2e8338a 100644
--- a/docs/backend-system/architecture/06-modules.md
+++ b/docs/backend-system/architecture/06-modules.md
@@ -6,35 +6,40 @@ sidebar_label: Modules
description: Modules for backend plugins
---
-## Creating Modules
+> **DISCLAIMER: The new backend system is in alpha, and still under active development. While we have reviewed the interfaces carefully, they may still be iterated on before the stable release.**
-Some facts about modules
+Backend modules are used to extend [plugins](./04-plugins.md) with additional features or change existing behavior. They must always be installed in the same backend instance as the plugin that they extend, and may only extend a single plugin. Modules interact with their target plugin using the [extension points](./05-extension-points.md) registered by the plugin, while also being able to depend on the [services](./03-services.md) of that plugin.
-- A Module is able to extend a plugin with additional functionality using the `ExtensionPoint`s registered by the plugin.
-- A module can only extend one plugin but can interact with multiple `ExtensionPoint`s registered by that plugin.
-- A module is always initialized before the plugin it extends.
+Both modules and plugins register an `init` method that is called during startup. In order to ensure that modules have registered all their extensions before the plugin starts up, all modules for each plugin are completely initialized before the plugin itself is initialized. In practice this means that all promises returned by each `init` method of the modules need to resolve before the plugin `init` method is called. This also means that it is not possible to further interact with the extension points once the `init` method has resolved.
-A module depends on the `ExtensionPoint`s exported by the target plugin's library package, for example `@backstage/plugin-catalog-node`, and does not directly declare a dependency on the plugin package itself.
+A module depends on the extension points exported by the target plugin's library package, for example `@backstage/plugin-catalog-node`, and does not directly declare a dependency on the plugin package itself. This is to avoid a direct dependency and potentially cause duplicate installations of the plugin package, while duplicate installations of library packages should always be supported.
-Here's an example on how to create a module that adds a new processor using the `catalogProcessingExtensionPoint`:
+## A Practical Example
+
+The following is an example on how to create a module that adds a new processor using the `catalogProcessingExtensionPoint`:
```ts
import { createBackendModule } from '@backstage/backend-plugin-api';
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node';
-import { MyCustomProcessor } from './processor';
+import { MyCustomProcessor } from './MyCustomProcessor';
-export const exampleCustomProcessorCatalogModule = createBackendModule({
+export const catalogModuleExampleCustomProcessor = createBackendModule({
moduleId: 'exampleCustomProcessor',
pluginId: 'catalog',
register(env) {
env.registerInit({
deps: {
catalog: catalogProcessingExtensionPoint,
+ logger: coreServices.logger,
},
async init({ catalog }) {
- catalog.addProcessor(new MyCustomProcessor());
+ catalog.addProcessor(new MyCustomProcessor(logger));
},
});
},
});
```
+
+Notice that we're placing the extension point we want to interact with in the `deps` option, while also depending on the logger service at the same time. When initializing modules we can depend on both extension points and services interchangeably. You can also depend on multiple extension points at once, in case the implementation of the module requires it.
+
+It is typically best to keep modules slim and to each only add a single new feature. It is often the case that it is better to create two separate modules rather than one that provides both features. The one limitation here is that modules can not interact with each other and need to be self contained.
diff --git a/docs/backend-system/architecture/07-naming-patterns.md b/docs/backend-system/architecture/07-naming-patterns.md
new file mode 100644
index 0000000000..b541cc09ba
--- /dev/null
+++ b/docs/backend-system/architecture/07-naming-patterns.md
@@ -0,0 +1,96 @@
+---
+id: naming-patterns
+title: Backend System Naming Patterns
+sidebar_label: Naming Patterns
+# prettier-ignore
+description: Naming patterns in the backend system
+---
+
+> **DISCLAIMER: The new backend system is in alpha, and still under active development. While we have reviewed the interfaces carefully, they may still be iterated on before the stable release.**
+
+These are the naming patterns to adhere to within the backend system. They help us keep exports consistent across packages and make it easier to understand the usage and intent of exports.
+
+### Plugins
+
+| Description | Pattern | Examples |
+| ----------- | ------------ | ----------------------------------- |
+| export | `Plugin` | `catalogPlugin`, `scaffolderPlugin` |
+| ID | `''` | `'catalog'`, `'scaffolder'` |
+
+Example:
+
+```ts
+export const catalogPlugin = createBackendPlugin({
+ pluginId: 'catalog',
+ ...
+})
+```
+
+### Modules
+
+| Description | Pattern | Examples |
+| ----------- | ---------------------------- | ----------------------------------- |
+| export | `Module` | `catalogModuleGithubEntityProvider` |
+| ID | `''` | `'githubEntityProvider'` |
+
+Example:
+
+```ts
+export const catalogModuleGithubEntityProvider = createBackendModule({
+ pluginId: 'catalog',
+ moduleId: 'githubEntityProvider',
+ ...
+})
+```
+
+### Extensions
+
+| Description | Pattern | Examples |
+| ----------- | -------------------------------- | -------------------------------------- |
+| Interface | `ExtensionPoint` | `CatalogProcessingExtensionPoint` |
+| Reference | `ExtensionPoint` | `catalogProcessingExtensionPoint` |
+| ID | `'.'` | `'catalog.processing'`, `'foo.barBaz'` |
+
+Example:
+
+```ts
+export interface CatalogProcessingExtensionPoint {
+ ...
+}
+
+export const catalogProcessingExtensionPoint = createExtensionPoint({
+ id: 'catalog.processing',
+ ...
+})
+```
+
+### Services
+
+| Description | Pattern | Examples |
+| ----------- | ---------------------- | -------------------------------------------------- |
+| Interface | `Service` | `LoggerService`, `DatabaseService` |
+| Reference | `ServiceRef` | `loggerServiceRef`, `databaseServiceRef` |
+| ID | `.` | `'core.rootHttpRouter'`, `'catalog.catalogClient'` |
+| Factory | `ServiceFactory` | `loggerServiceFactory`, `databaseServiceFactory` |
+
+Example:
+
+```ts
+export interface CatalogClientService {
+ ...
+}
+
+export const catalogClientServiceRef = createServiceRef({
+ id: 'catalog.catalogClient',
+ ...
+})
+
+export const catalogClientServiceFactory = createServiceFactory({
+ service: catalogClientServiceRef,
+ ...
+})
+```
+
+An exception to the above service reference naming pattern has been made for the all of the core services in the core API. The `@backstage/backend-plugin-api` makes all core service references available via a single `coreServices` collection. Likewise, the `@backstage/backend-test-utils` exports all mock service implementations via a single `mockServices` collection. This means that the table above is slightly misleading, since `loggerServiceRef` and `databaseServiceRef` are instead available as `coreServices.logger` and `coreService.database`. We recommend that plugins avoid this patterns unless they have a very large number of services that they need to export.
+
+While it is often preferred to prefix root scoped services with `Root`, it is not required. For example, `RootHttpRouterService` and `RootLifecycleService` follow this pattern, but `ConfigService` doesn't and it is a root scoped service.
diff --git a/docs/backend-system/building-backends/01-index.md b/docs/backend-system/building-backends/01-index.md
index b9879f4b24..66bf68ab07 100644
--- a/docs/backend-system/building-backends/01-index.md
+++ b/docs/backend-system/building-backends/01-index.md
@@ -6,7 +6,177 @@ sidebar_label: Overview
description: Building backends using the new backend system
---
+> **DISCLAIMER: The new backend system is in alpha, and still under active development. While we have reviewed the interfaces carefully, they may still be iterated on before the stable release.**
+
> NOTE: If you have an existing backend that is not yet using the new backend
> system, see [migrating](./08-migrating.md).
+This section covers how to set up and customize your own Backstage backend. It covers some aspects of how backend instances fit into the larger system, but for a more in-depth explanation of the role of backends in the backend system, see [the architecture section](../architecture/02-backends.md).
+
# Overview
+
+A minimal Backstage backend is very lightweight. It is a single package with a `package.json` file and a `src/index.ts` file, not counting surrounding tooling and documentation. The package is typically placed within the `packages/backend` folder of a Backstage monorepo, but that is up to you. The backend package is part of any project created with `@backstage/create-app`, so you typically do not need to create it yourself.
+
+When you create a new project with `@backstage/create-app`, you'll get a backend package with a `src/index.ts` that looks something like this:
+
+```ts
+import { createBackend } from '@backstage/backend-defaults';
+import { appPlugin } from '@backstage/plugin-app-backend';
+import { catalogPlugin } from '@backstage/plugin-catalog-backend';
+import {
+ scaffolderPlugin,
+ catalogModuleTemplateKind,
+} from '@backstage/plugin-scaffolder-backend';
+
+const backend = createBackend();
+
+backend.add(appPlugin());
+backend.add(catalogPlugin());
+backend.add(catalogModuleTemplateKind());
+backend.add(scaffolderPlugin());
+
+backend.start();
+```
+
+There will be a couple more plugins and modules in the initial setup, but the overall layout is the same.
+
+What we're doing in this file is creating a new backend using `createBackend`, and then installing a collection of different plugins and modules that we want to be part of that backend. Plugins are standalone features, while modules augment existing plugins. Each module can only target a single plugin, and that plugin must also be present in the same backend. Finally, we start up the backend by calling the `start` method.
+
+## Customization
+
+Apart from installing existing plugins and modules in the backend, there are a couple of different ways in which you can customize your backend installation.
+
+### Configuration
+
+Perhaps the most accessible way is though static configuration, which you can read more about in the documentation for how to [write configuration](../../conf/writing.md). Many different aspects of the backend can be configured, including both the behavior of the backend itself, as well as many plugins or modules. You'll need to refer to the documentation of each plugin or module to see what configuration is available. Also be sure to check out the documentation of the [core services](../core-services/01-index.md), as that also covers how to configure those.
+
+### Services
+
+Speaking of services, they are another important point of customization. Services allow you to make deeper and broader customizations of the backend. They are similar to [Utility APIs](../../api/utility-apis.md) in the frontend system, using dependency injection to make common functionality available to plugins and modules. You can read more about services in the [architecture section](../architecture/03-services.md).
+
+There is a core set of services that must be installed in all backends, for things like logging, database access, serving HTTP, and so on. Luckily, all of these services are installed by default when you use `createBackend` from the `@backstage/backend-defaults` package, which is what you have in a standard setup.
+
+All of these services can be replaced with your own implementations if you need to customize them. The simplest way to do this is to use the existing service implementations but with additional options. Many of the core services can be customized this way, but not all, as they don't all have meaningful options.
+
+For example, let's say we want to customize the core configuration service to enable remote configuration loading. That would look something like this:
+
+```ts
+import { configServiceFactory } from '@backstage/backend-app-api';
+
+const backend = createBackend({
+ services: [
+ configServiceFactory({
+ remote: { reloadIntervalSeconds: 60 },
+ }),
+ ],
+});
+```
+
+This will make it possible to pass URLs as configuration targets, and those URLs will be polled every 60 seconds for changes.
+
+There is one exception to the above, which is the built-in `PluginMetadataService` that is provided by the framework and is not possible to override.
+
+### Custom Service Implementations
+
+When overriding services you are not limited to the existing implementations, you can also provide your own custom service factories. This will let you globally override services with completely custom implementations, or build on existing implementations to add additional logic.
+
+To override a service, you provide it in the `services` option just like above, but this time we need to use `createServiceFactory` to create our factory. For example, if you want to replace the default `LoggerService` with your own, it might look like this:
+
+```ts
+const backend = createBackend({
+ services: [
+ createServiceFactory({
+ service: coreServices.logger,
+ deps: {
+ rootLogger: coreServices.rootLogger,
+ plugin: coreServices.pluginMetadata,
+ config: coreServices.config,
+ },
+ factory({ rootLogger, plugin, config }) {
+ const labels = readCustomLogLabelsForPlugin(config, plugin); // custom logic
+ return rootLogger.child(labels);
+ },
+ }),
+ ],
+});
+```
+
+The `LoggerService` is responsible for creating a specialized logger instance for each plugin, while the `RootLoggerService` is the actual logging implementation. The default implementation of `LoggerService` will decorate the logger with a `plugin` label that contains the plugin ID. Here in our custom implementation we read out additional labels from the configuration and add those as well.
+
+This example touches on the fact that services can have different scopes, being either scoped to individual plugins or the root backend instance. To read more about this, see the [architecture section](../architecture/03-services.md).
+
+## Split Into Multiple Backends
+
+> NOTE: Splitting into multiple backends is an advanced deployment pattern that requires significant effort and there are not yet many built-in tools in the framework to help you out. Only use this if necessary.
+
+A more advanced way to deploy Backstage is to split the backend plugins into multiple different backend deployments. Both the [deployment documentation](../../deployment/scaling.md) and [Threat Model](../../overview/threat-model.md#trust-model) explain the benefits of this, so here we'll focus on how to do it.
+
+To create a separate backend we need to create an additional backend package. This package will be built and deployed separately from your existing backend. There is currently no template to create a backend via `yarn new`, so the quickest way is to copy the new package and modify. The naming is up to you and it depends on how you are splitting things and up. For this example we'll just use a simple suffix. You might end up with a directory structure like this:
+
+```text
+packages/
+ backend-a/
+ src/
+ index.ts
+ package.json <- "name": "backend-a"
+ backend-b/
+ src/
+ index.ts
+ package.json <- "name": "backend-b"
+```
+
+You can now trim down the `src/index.ts` files to only include the plugins and modules that you want to be part of that backend. For example, if you want to split out the scaffolder plugin, you might end up with something like this:
+
+```ts
+// packages/backend-a/src/index.ts, imports omitted
+const backend = createBackend();
+
+backend.add(appPlugin());
+backend.add(catalogPlugin());
+backend.add(catalogModuleTemplateKind());
+backend.start();
+```
+
+And `backend-b`, don't forget to clean up dependencies in `package.json` as well:
+
+```ts
+// packages/backend-b/src/index.ts, imports omitted
+const backend = createBackend();
+
+backend.add(scaffolderPlugin());
+backend.start();
+```
+
+We've now split the backend into two separate deployments, but we still need to make sure that they can communicate with each other. This is the hard and somewhat tedious part, as Backstage currently doesn't provide an out of the box solution that solves this. You'll need to manually configure the two backends with custom implementations of the `DiscoveryService` and have them return the correct URLs for each other. Likewise, you'll also need to provide a custom implementation of the `DiscoveryApi` in the frontend, unless you surface the two backends via a proxy that handles the routing instead.
+
+### Shared Environments
+
+To make it a bit easier to manage multiple backends, it's possible to create a shared environment that can be used across multiple backends. You would typically house it in a separate package that can be referenced by backends in your monorepo, or published to a package registry for broader use.
+
+A shared environment contains a set of service implementations that should be used across all backends. These services will override the default ones, but if a service is provided directly to the backend, it will override the one in the shared environment.
+
+A shared environment is defined using `createSharedEnvironment`. In this example we place it in a new and separate package called `backend-env`:
+
+```ts
+// packages/backend-env/src/index.ts
+import { createSharedEnvironment } from '@backstage/backend-plugin-api';
+import { customDiscoveryServiceFactory } from './customDiscoveryServiceFactory';
+
+export const env = createSharedEnvironment({
+ services: [
+ customDiscoveryServiceFactory(), // custom DiscoveryService implementation
+ ],
+});
+```
+
+And passed on to backends using the `env` option:
+
+```ts
+// packages/backend-b/src/index.ts, imports omitted
+import { env } from '@internal/backend-env';
+
+const backend = createBackend({ env });
+
+backend.add(scaffolderPlugin());
+backend.start();
+```
diff --git a/docs/backend-system/building-backends/08-migrating.md b/docs/backend-system/building-backends/08-migrating.md
index 84f801f15d..567476532f 100644
--- a/docs/backend-system/building-backends/08-migrating.md
+++ b/docs/backend-system/building-backends/08-migrating.md
@@ -6,4 +6,428 @@ sidebar_label: Migration Guide
description: How to migrate existing backends to the new backend system
---
-# Overview
+> **DISCLAIMER: The new backend system is in alpha, and still under active development. As such, it is not considered stable, and it is not recommended to migrate production backends to the new backend system until it has a stable release.**
+
+## Overview
+
+This section describes how to migrate an existing Backstage backend service
+package (typically in `packages/backend`) to use the new backend system.
+
+One of the main benefits of the new backend system is that it abstracts away the
+way that plugins and their dependencies are wired up, leading to a significantly
+simplified backend package that rarely if ever needs to change when plugins or
+their dependencies evolve. You generally don't have to convert all of your
+internal plugins and support classes themselves to the backend system first -
+the migration here will mostly deal with wiring and using compatibility wrappers
+where possible in the backend package itself. We hope that you will find that
+you end up with a much smaller, easier to understand, and easier to maintain
+package as a result of these steps, and then being able to [migrate
+plugins](../building-plugins-and-modules/08-migrating.md) as a separate
+endeavour later.
+
+## Overall Structure
+
+Your typical backend package has a few overall component parts:
+
+- An `index.ts` file that houses all of the creation and wiring together of all
+ of the plugins and their dependencies
+- A `types.ts` file that defines the "environment", i.e. the various
+ dependencies that get created by the backend and passed down into each plugin
+- A `plugins` folder which has one file for each plugin, e.g.
+ `plugins/catalog.ts`
+
+The index file has this overall shape:
+
+```ts
+import todo from './plugins/todo'; // repeated for N plugins
+
+function makeCreateEnv(config: Config) {
+ return (plugin: string): PluginEnvironment => {
+ // ... build per-plugin environment
+ };
+}
+
+async function main() {
+ // ... early init
+ const createEnv = makeCreateEnv(config);
+ const todoEnv = useHotMemoize(module, () => createEnv('todo')); // repeated for N plugins
+ const apiRouter = Router();
+ apiRouter.use('/todo', await todo(todoEnv)); // repeated for N plugins
+ // ... wire up and start http server
+}
+
+module.hot?.accept();
+main().catch(...);
+```
+
+## Migrating the Index File
+
+This migration will try to leave the `plugins` folder unchanged initially, first
+focusing on removing the environment type and reducing the index file to its
+bare minimum. Then as a later step, we can reduce the `plugins` folder bit by
+bit, replacing those files generally with one-liners in the index file instead.
+
+Let's start by establishing the basis of your new index file. You may want to
+comment out its old contents, or renaming the old file to `index.backup.ts` for
+reference and making a new blank one to work on - whichever works best for you.
+These are our new blank contents in the index file:
+
+```ts
+// packages/backend/src/index.ts
+import { createBackend } from '@backstage/backend-defaults';
+
+const backend = createBackend();
+backend.start();
+```
+
+Note that the environment builder and the `main` dance are entirely gone.
+
+We'll also want to add some backend system packages as dependencies. Run the
+following command:
+
+```bash
+# from the repository root
+yarn add --cwd packages/backend @backstage/backend-defaults @backstage/backend-plugin-api
+```
+
+You should now be able to start this up with the familiar `yarn workspace
+backend start` command locally and seeing some logs scroll by. But it'll just be
+a blank service with no real features added. So let's stop it with `Ctrl+C` and
+reintroduce some plugins into the mix.
+
+```diff
+ import { createBackend } from '@backstage/backend-defaults';
++import { legacyPlugin } from '@backstage/backend-common';
+
+ const backend = createBackend();
++backend.add(legacyPlugin('todo', import('./plugins/todo')));
+ backend.start();
+```
+
+The `todo` plugin used above is just an example and you may not have it enabled
+in your own backend. Feel free to change it to some other plugin that you
+actually have in your `plugins` folder, for example
+`backend.add(legacyPlugin('catalog', import('./plugins/catalog')))`.
+
+The `legacyPlugin` helper makes it easy to bridge the gap between the old-style
+plugin files and the new backend system. It ensures that the dependencies that
+you used to have to declare by hand in your env are gathered behind the scenes,
+then passes them into the relevant `createPlugin` export function, and makes
+sure that the route handler it returns is passed into the HTTP router with the
+given prefix.
+
+## Handling Custom Environments
+
+In the simple case, what we did above is sufficient, TypeScript is happy, and
+the backend runs with the new feature. If they do, feel free to skip this entire
+section, and deleting `types.ts`.
+
+Sometimes though, type errors can be reported on the newly added line, saying
+that parts of the `PluginEnvironment` type do not match. This happens when the
+environment was changed from the defaults, perhaps with your own custom
+additions. If this is the case in your installation, you still aren't out of
+luck - you can build a customized `legacyPlugin` function.
+
+```diff
+ import { createBackend } from '@backstage/backend-defaults';
+-import { legacyPlugin } from '@backstage/backend-common';
++import { makeLegacyPlugin, loggerToWinstonLogger } from '@backstage/backend-common';
++import { coreServices } from '@backstage/backend-plugin-api';
+
++const legacyPlugin = makeLegacyPlugin(
++ {
++ cache: coreServices.cache,
++ config: coreServices.config,
++ database: coreServices.database,
++ discovery: coreServices.discovery,
++ logger: coreServices.logger,
++ permissions: coreServices.permissions,
++ scheduler: coreServices.scheduler,
++ tokenManager: coreServices.tokenManager,
++ reader: coreServices.urlReader,
++ identity: coreServices.identity,
++ // ... and your own additions
++ },
++ {
++ logger: log => loggerToWinstonLogger(log),
++ },
++);
+
+ const backend = createBackend();
+ backend.add(legacyPlugin('todo', import('./plugins/todo')));
+ backend.start();
+```
+
+The first argument to `makeLegacyPlugin` is the mapping from environment keys to
+references to actual [backend system services](../architecture/03-services.md).
+The second argument allows you to "tweak" the types of those services to
+something more fitting to your env. For example, you'll see that the logger
+service API type was changed from the raw Winston logger of old, to a different,
+custom API, so we use a helper function to transform that particular one.
+
+To make additions as mentioned above to the environment, you will start to get
+into the weeds of how the backend system wiring works. You'll need to have a
+service reference and a service factory that performs the actual creation of
+your service. Please see [the services
+article](../architecture/03-services.md#defining-a-service) to learn how to
+create a service ref and its default factory. You can place that code directly
+in the index file for now if you want, or near the actual implementation class
+in question.
+
+In this example, we'll assume that your added environment field is named
+`example`, and the created ref is named `exampleServiceRef`.
+
+```diff
++import { exampleServiceRef } from ''; // if the definition is elsewhere
+
+ const legacyPlugin = makeLegacyPlugin(
+ {
+ // ... the above core services still go here
++ example: exampleServiceRef
+ },
+ {
+ logger: log => loggerToWinstonLogger(log),
+ },
+ );
+```
+
+After this, your backend will know how to instantiate your thing on demand and
+place it in the legacy plugin environment.
+
+> NOTE: If you happen to be dealing with a service ref that does NOT have a
+> default implementation, but rather has a separate service factory, then you
+> will also need to import that factory and pass it to the `services` array
+> argument of `createBackend`.
+
+## Cleaning Up the Plugins Folder
+
+For plugins that are private and your own, you can follow a [dedicated migration
+guide](../building-plugins-and-modules/08-migrating.md) as you see fit, at a
+later time.
+
+For third party backend plugins, in particular the larger core plugins that are
+maintained by the Backstage maintainers, you may find that they have already
+been migrated to the new backend system. This section describes some specific
+such migrations you can make.
+
+> NOTE: For each of these, note that your backend still needs to have a
+> dependency (e.g. in `packages/backend/package.json`) to those plugin packages,
+> and they still need to be configured properly in your app-config. Those
+> mechanisms still work just the same as they used to in the old backend system.
+
+### The App Plugin
+
+The app backend plugin that serves the frontend from the backend can trivially
+be used in its new form.
+
+```diff
+ // packages/backend/src/index.ts
++import { appPlugin } from '@backstage/plugin-app-backend';
+
+ const backend = createBackend();
++backend.add(appPlugin({ appPackageName: 'app' }));
+```
+
+This is an example of how options can be passed into some backend plugins. The
+app plugin specifically needs to know the name of the package that holds the
+frontend code. This is the `"name"` field in that package's `package.json`,
+typically found in your `packages/app` folder. By default it's just plain "app".
+
+You should be able to delete the `plugins/app.ts` file at this point.
+
+### The Catalog Plugin
+
+A basic installation of the catalog plugin looks as follows.
+
+```diff
+ // packages/backend/src/index.ts
++import { catalogPlugin } from '@backstage/plugin-catalog-backend';
++import { catalogModuleTemplateKind } from '@backstage/plugin-scaffolder-backend';
+
+ const backend = createBackend();
++backend.add(catalogPlugin());
++backend.add(catalogModuleTemplateKind());
+```
+
+Note that this also installs a module from the scaffolder, namely the one which
+enables the use of the `Template` kind. In the unlikely event that you do not
+use templates at all, you can remove those lines.
+
+If you have other customizations made to `plugins/catalog.ts`, such as adding
+custom processors or entity providers, read on. Otherwise, you should be able to
+just delete that file at this point.
+
+You will use the [extension points](../architecture/05-extension-points.md)
+mechanism to extend or tweak the functionality of the plugin. To do that,
+you'll make your own bespoke [module](../architecture/06-modules.md) which
+depends on the appropriate extension point and interacts with it.
+
+```diff
+ // packages/backend/src/index.ts
++import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node';
++import { createBackendModule } from '@backstage/backend-plugin-api';
+
++const catalogModuleCustomExtensions = createBackendModule({
++ pluginId: 'catalog', // name of the plugin that the module is targeting
++ moduleId: 'customExtensions',
++ register(env) {
++ env.registerInit({
++ deps: {
++ catalog: catalogProcessingExtensionPoint,
++ // ... and other dependencies as needed
++ },
++ init({ catalog /* ..., other dependencies */ }) {
++ // Here you have the opportunity to interact with the extension
++ // point before the plugin itself gets instantiated
++ catalog.addEntityProvider(new MyEntityProvider()); // just an example
++ catalog.addProcessor(new MyProcessor()); // just an example
++ },
++ });
++ },
++});
+
+ const backend = createBackend();
+ backend.add(catalogPlugin());
+ backend.add(catalogModuleTemplateKind());
++backend.add(catalogModuleCustomExtensions());
+```
+
+This also requires that you have a dependency on the corresponding node package,
+if you didn't already have one.
+
+```bash
+# from the repository root
+yarn add --cwd packages/backend @backstage/plugin-catalog-node
+```
+
+Here we've placed the module directly in the backend index file just to get
+going easily, but feel free to move it out to where it fits best. As you migrate
+your entire plugin flora to the new backend system, you will probably make more
+and more of these modules as "first class" things, living right next to the
+implementations that they represent, and being exported from there.
+
+### The Events Plugin
+
+A basic installation of the events plugin looks as follows.
+
+```diff
+ // packages/backend/src/index.ts
++import { eventsPlugin } from '@backstage/plugin-events-backend';
+
+ const backend = createBackend();
++backend.add(eventsPlugin());
+```
+
+If you have other customizations made to `plugins/events.ts`, such as adding
+custom subscribers, read on. Otherwise, you should be able to just delete that
+file at this point.
+
+You will use the [extension points](../architecture/05-extension-points.md)
+mechanism to extend or tweak the functionality of the plugin. To do that,
+you'll make your own bespoke [module](../architecture/06-modules.md) which
+depends on the appropriate extension point and interacts with it.
+
+```diff
+ // packages/backend/src/index.ts
++import { eventsExtensionPoint } from '@backstage/plugin-events-node';
++import { createBackendModule } from '@backstage/backend-plugin-api';
+
++const eventsModuleCustomExtensions = createBackendModule({
++ pluginId: 'events', // name of the plugin that the module is targeting
++ moduleId: 'customExtensions',
++ register(env) {
++ env.registerInit({
++ deps: {
++ events: eventsExtensionPoint,
++ // ... and other dependencies as needed
++ },
++ init({ events /* ..., other dependencies */ }) {
++ // Here you have the opportunity to interact with the extension
++ // point before the plugin itself gets instantiated
++ events.addSubscribers(new MySubscriber()); // just an example
++ },
++ });
++ },
++});
+
+ const backend = createBackend();
+ backend.add(eventsPlugin());
++backend.add(eventsModuleCustomExtensions());
+```
+
+This also requires that you have a dependency on the corresponding node package,
+if you didn't already have one.
+
+```bash
+# from the repository root
+yarn add --cwd packages/backend @backstage/plugin-events-node
+```
+
+Here we've placed the module directly in the backend index file just to get
+going easily, but feel free to move it out to where it fits best. As you migrate
+your entire plugin flora to the new backend system, you will probably make more
+and more of these modules as "first class" things, living right next to the
+implementations that they represent, and being exported from there.
+
+### The Scaffolder Plugin
+
+A basic installation of the scaffolder plugin looks as follows.
+
+```diff
+ // packages/backend/src/index.ts
++import { scaffolderPlugin } from '@backstage/plugin-scaffolder-backend';
+
+ const backend = createBackend();
++backend.add(scaffolderPlugin());
+```
+
+If you have other customizations made to `plugins/scaffolder.ts`, such as adding
+custom actions, read on. Otherwise, you should be able to just delete that file
+at this point.
+
+You will use the [extension points](../architecture/05-extension-points.md)
+mechanism to extend or tweak the functionality of the plugin. To do that,
+you'll make your own bespoke [module](../architecture/06-modules.md) which
+depends on the appropriate extension point and interacts with it.
+
+```diff
+ // packages/backend/src/index.ts
++import { scaffolderActionsExtensionPoint } from '@backstage/plugin-scaffolder-node';
++import { createBackendModule } from '@backstage/backend-plugin-api';
+
++const scaffolderModuleCustomExtensions = createBackendModule({
++ pluginId: 'scaffolder', // name of the plugin that the module is targeting
++ moduleId: 'customExtensions',
++ register(env) {
++ env.registerInit({
++ deps: {
++ scaffolder: scaffolderActionsExtensionPoint,
++ // ... and other dependencies as needed
++ },
++ init({ scaffolder /* ..., other dependencies */ }) {
++ // Here you have the opportunity to interact with the extension
++ // point before the plugin itself gets instantiated
++ scaffolder.addActions(new MyAction()); // just an example
++ },
++ });
++ },
++});
+
+ const backend = createBackend();
+ backend.add(scaffolderPlugin());
++backend.add(scaffolderModuleCustomExtensions());
+```
+
+This also requires that you have a dependency on the corresponding node package,
+if you didn't already have one.
+
+```bash
+# from the repository root
+yarn add --cwd packages/backend @backstage/plugin-scaffolder-node
+```
+
+Here we've placed the module directly in the backend index file just to get
+going easily, but feel free to move it out to where it fits best. As you migrate
+your entire plugin flora to the new backend system, you will probably make more
+and more of these modules as "first class" things, living right next to the
+implementations that they represent, and being exported from there.
diff --git a/docs/backend-system/building-plugins-and-modules/01-index.md b/docs/backend-system/building-plugins-and-modules/01-index.md
index fff82286ad..1556b6b377 100644
--- a/docs/backend-system/building-plugins-and-modules/01-index.md
+++ b/docs/backend-system/building-plugins-and-modules/01-index.md
@@ -6,7 +6,246 @@ sidebar_label: Overview
description: Building backend plugins and modules using the new backend system
---
+> **DISCLAIMER: The new backend system is in alpha, and still under active development. While we have reviewed the interfaces carefully, they may still be iterated on before the stable release.**
+
> NOTE: If you have an existing backend and/or backend plugins that are not yet
> using the new backend system, see [migrating](./08-migrating.md).
-# Overview
+This section covers how to build your own backend [plugins](../architecture/04-plugins.md) and
+[modules](../architecture/06-modules.md). They are sometimes collectively referred to as
+backend _features_, and are the building blocks that adopters add to their
+[backends](../architecture/02-backends.md).
+
+## Creating a new Plugin
+
+This guide assumes that you already have a Backend project set up. Even if you only want to develop a single plugin for publishing, we still recommend that you do so in a standard Backstage monorepo project, as you often end up needing multiple packages. For instructions on how to set up a new project, see our [getting started](../../getting-started/index.md#prerequisites) documentation.
+
+To create a Backend plugin, run `yarn new`, select `backend-plugin`, and fill out the rest of the prompts. This will create a new package at `plugins/-backend`, which will be the main entrypoint for your plugin.
+
+## Plugins
+
+A basic backend plugin might look as follows:
+
+```ts
+import {
+ createBackendPlugin,
+ coreServices,
+} from '@backstage/backend-plugin-api';
+import { createExampleRouter } from './router';
+
+export const examplePlugin = createBackendPlugin({
+ pluginId: 'example',
+ register(env) {
+ env.registerInit({
+ deps: {
+ // Declare dependencies to services that you want to consume
+ logger: coreServices.logger,
+ httpRouter: coreServices.httpRouter,
+ },
+ async init({
+ // Requested service instances get injected as per above
+ logger,
+ httpRouter,
+ }) {
+ // Perform your initialization and access the services as needed
+ const example = createExampleRouter(logger);
+ logger.info('Hello from example plugin');
+ httpRouter.use(example);
+ },
+ });
+ },
+});
+```
+
+When you depend on `plugin` scoped services, you'll receive an instance of them
+that's specific to your plugin. In the example above, the logger might tag
+messages with your plugin ID, and the HTTP router might prefix API routes with
+your plugin ID, depending on the implementation used.
+
+See [the article on naming patterns](../architecture/07-naming-patterns.md) for
+details on how to best choose names/IDs for plugins and related backend system
+items.
+
+## Modules
+
+Backend modules are used to extend [plugins](../architecture/04-plugins.md) with
+additional features or change existing behavior. They must always be installed
+in the same backend instance as the plugin that they extend, and may only extend
+a single plugin. Modules interact with their target plugin using the [extension
+points](../architecture/05-extension-points.md) registered by the plugin, while also being
+able to depend on the [services](../architecture/03-services.md) of that plugin.
+That last point is worth reiterating: injected `plugin` scoped services will be
+the exact
+same ones as the target plugin will receive later, i.e. they will be scoped
+using the target `pluginId` of the module.
+
+A module depends on the extension points exported by the target plugin's library
+package, for example `@backstage/plugin-catalog-node`, and does not directly
+declare a dependency on the plugin package itself. This is to avoid a direct
+dependency and potentially cause duplicate installations of the plugin package,
+while duplicate installations of library packages should always be supported.
+
+The following is an example of how to create a module that adds a new processor
+using the `catalogProcessingExtensionPoint`:
+
+```ts
+import { createBackendModule } from '@backstage/backend-plugin-api';
+import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node';
+import { MyCustomProcessor } from './MyCustomProcessor';
+
+export const catalogModuleExampleCustomProcessor = createBackendModule({
+ moduleId: 'exampleCustomProcessor',
+ pluginId: 'catalog',
+ register(env) {
+ env.registerInit({
+ deps: {
+ catalog: catalogProcessingExtensionPoint,
+ logger: coreServices.logger,
+ },
+ async init({ catalog }) {
+ catalog.addProcessor(new MyCustomProcessor(logger));
+ },
+ });
+ },
+});
+```
+
+See [the article on naming patterns](../architecture/07-naming-patterns.md) for
+details on how to best choose names/IDs for modules and related backend system
+items.
+
+Notice that we're placing the extension point we want to interact with in the
+`deps` option, while also depending on the logger service at the same time. When
+initializing modules we can depend on both extension points and services
+interchangeably. You can also depend on multiple extension points at once, in
+case the implementation of the module requires it.
+
+It is typically best to keep modules slim and to each only add a single new
+feature. It is often the case that it is better to create two separate modules
+rather than one that provides both features. The one limitation here is that
+modules can not interact with each other and need to be self contained.
+
+### HTTP Handlers
+
+Since modules have access to the same services as the plugin they extend, they
+are also able to register their own HTTP handlers. For more information about
+the HTTP service, see [core services](../core-services/01-index.md). When
+registering HTTP handlers, it is important to try to avoid any future conflict
+with the plugin itself, or other modules. A recommended naming pattern is to
+register the handlers under the `/modules/` path, where ``
+is the kebab-case ID of the module, for example
+`/modules/example-custom-processor/v1/validators`. In a standard backend setup
+the full path would then be
+`/api/catalog/modules/example-custom-processor/v1/validators`.
+
+### Database Access
+
+The same applies for modules that perform their own migrations and interact with
+the database. They will run on the same logical database instance as the target
+plugin, so care must be taken to choose table names that do not risk colliding
+with those of the plugin. A recommended naming pattern is `__
`, for example the `@backstage/backend-tasks` package creates
+tables named `backstage_backend_tasks__
`. If you use the default [`Knex`
+migration facilities](https://knexjs.org/guide/migrations.html), you will also
+want to make sure that it uses similarly prefixed migration state tables for its
+internal bookkeeping needs, so they do not collide with the main ones used by
+the plugin itself. You can do this as follows:
+
+```ts
+await knex.migrate.latest({
+ directory: migrationsDir,
+ tableName: 'backstage_backend_tasks__knex_migrations',
+});
+```
+
+## Customization
+
+There are several ways of configuring and customizing plugins and modules.
+
+### Extension Points
+
+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).
+
+### Configuration
+
+Your plugin or module can leverage the app configuration to configure its own
+internal behavior. You do this by adding a dependency on `coreServices.config`
+and reading from that. This pattern is a good fit especially for customization
+that needs to be different across environments.
+
+```ts
+import { coreServices } from '@backstage/backend-plugin-api';
+
+export const examplePlugin = createBackendPlugin({
+ pluginId: 'example',
+ register(env) {
+ env.registerInit({
+ deps: { config: coreServices.config },
+ async init({ config }) {
+ // Here you can read from the current config as you see fit, e.g.:
+ const value = config.getOptionalString('example.value');
+ },
+ });
+ },
+});
+```
+
+Before adding custom configuration options, make sure to read [the configuration
+docs](../../conf/index.md), in particular the section on [defining configuration
+for your own plugins](../../conf/defining.md) which explains how to establish a
+configuration schema for your specific plugin.
+
+### Options
+
+You'll have noted that the return values from `createBackendPlugin` and
+`createBackendModule` are actually factory functions. These can be made to
+accept options that shall be passed in at initialization time.
+
+This pattern can be a good fit for fairly simple, static configuration values.
+
+```ts
+export interface ExampleOptions {
+ silent?: boolean;
+}
+
+export const examplePlugin = createBackendPlugin(
+ (options?: ExampleOptions) => ({
+ pluginId: 'example',
+ register(env) {
+ env.registerInit({
+ deps: {
+ // Omitted dependencies but they remain the same as above
+ },
+ async init(
+ {
+ /* ... */
+ },
+ ) {
+ // Here you can access the given options and act accordingly, e.g.:
+ if (!options?.silent) {
+ // ...
+ }
+ },
+ });
+ },
+ }),
+);
+```
+
+The return type from `createBackendPlugin` and `createBackendModule` will mimic
+this, resulting in a factory function that accepts an optional options object.
+You can also make it required to pass in options, by removing the optionality
+(the question mark on the options) above.
+
+```ts
+backend.add(examplePlugin({ silent: true }));
+```
+
+Use this pattern sparingly. There is a big convenience benefit in allowing
+people to easily install backend plugins without having to always pass in a
+large number of options, and these options cannot easily be made dynamic based
+on the environment etc.
diff --git a/docs/backend-system/building-plugins-and-modules/02-testing.md b/docs/backend-system/building-plugins-and-modules/02-testing.md
index e8a8619f72..7f93fab45b 100644
--- a/docs/backend-system/building-plugins-and-modules/02-testing.md
+++ b/docs/backend-system/building-plugins-and-modules/02-testing.md
@@ -6,21 +6,163 @@ sidebar_label: Testing
description: Learn how to test your backend plugins and modules
---
-Utilities for testing backend plugins and modules are available in `@backstage/backend-test-utils`.
-`startTestBackend` returns a server which can be used together with `supertest` to test the plugins.
+> **DISCLAIMER: The new backend system is in alpha, and still under active development. While we have reviewed the interfaces carefully, they may still be iterated on before the stable release.**
+
+Utilities for testing backend plugins and modules are available in
+`@backstage/backend-test-utils`. This section describes those facilities.
+
+## Testing Backend Plugins and Modules
+
+To facilitate testing of backend plugins and modules, the
+`@backstage/backend-test-utils` package provides a `startTestBackend` function
+which starts up an entire backend harness, complete with a number of mock
+services. You can then provide overrides for services whose behavior you need to
+adjust for the test run. The function also accepts a number of _features_ (a
+collective term for backend [plugins](../architecture/04-plugins.md) and
+[modules](../architecture/06-modules.md)), that are the subjects of the test.
+
+The function returns an HTTP server instance which can be used together with
+e.g. `supertest` to easily test the actual REST service surfaces of plugins who
+register routes with [the HTTP router service
+API](../core-services/01-index.md).
```ts
-import { startTestBackend } from '@backstage/backend-test-utils';
+import { mockServices, startTestBackend } from '@backstage/backend-test-utils';
import request from 'supertest';
+import { myPlugin } from './plugin.ts';
+
+describe('myPlugin', () => {
+ it('can serve values from config', async () => {
+ const fakeConfig = { myPlugin: { value: 7 } };
-describe('My plugin tests', () => {
- it('should return 200', async () => {
const { server } = await startTestBackend({
features: [myPlugin()],
+ services: [mockServices.config.factory({ data: fakeConfig })],
});
- const response = await request(server).get('/api/example/hello');
+ const response = await request(server).get('/api/example/get-value');
expect(response.status).toBe(200);
+ expect(response.body).toEqual({ value: 7 });
});
});
```
+
+This example shows how to access the mock service factories and
+pass options to them, which will override the default mock services.
+
+The returned server also has a `port()` method which returns the dynamically
+bound listening port. You can use this to perform lower level network
+interactions with the running test service.
+
+## Testing Remote Service Interactions
+
+If your backend plugin or service interacts with external services using HTTP
+calls, we recommend leveraging the `msw` package to intercept actual outgoing
+requests and return mock responses. This lets you stub out remote services
+rather than the local clients, leading to more thorough and robust tests. You
+can read more about how it works [in their documentation](https://mswjs.io/).
+
+The `@backstage/backend-test-utils` package exports a `setupRequestMockHandlers`
+function which ensures that the correct `jest` lifecycle hooks are invoked to
+set up and tear down your `msw` instance, and enables the option that completely
+rejects requests that don't match one of your mock rules. This ensures that your
+tests cannot accidentally leak traffic into production from tests.
+
+Example:
+
+```ts
+import { setupRequestMockHandlers } from '@backstage/backend-test-utils';
+import { rest } from 'msw';
+import { setupServer } from 'msw/node';
+
+describe('read from remote', () => {
+ const worker = setupServer();
+ setupRequestMockHandlers(worker);
+
+ it('should auth and read successfully', async () => {
+ expect.assertions(1);
+
+ worker.use(
+ rest.get('https://remote-server.com/api/v3/foo', (req, res, ctx) => {
+ expect(req.headers.get('authorization')).toBe('Bearer fake');
+ return res(
+ ctx.status(200),
+ ctx.set('Content-Type', 'application/json'),
+ ctx.body(JSON.stringify({ value: 7 })),
+ );
+ }),
+ );
+
+ // exercise your plugin or service as usual, with real clients
+ });
+});
+```
+
+## Testing Database Interactions
+
+The `@backstage/backend-test-utils` package includes facilities for testing your
+plugins' interactions with databases, including spinning up `testcontainers`
+powered Docker images with real database engines to connect to.
+
+The base setup for such a test could look as follows:
+
+```ts
+// MyDatabaseClass.test.ts
+import { TestDatabaseId, TestDatabases } from '@backstage/backend-test-utils';
+import { MyDatabaseClass, type FooTableRow } from './MyDatabaseClass';
+
+describe('MyDatabaseClass', () => {
+ // Change this to the set of constants that you actually actively intend to
+ // support. This create call must be made inside a describe block. Make sure
+ // to create only one TestDatabases instance per file, since spinning up
+ // "physical" databases to test against is much costlier than creating the
+ // "logical" databases within them that the individual tests use.
+ const databases = TestDatabases.create({
+ ids: ['POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'],
+ });
+
+ // Just an example of how to conveniently bundle up the setup code
+ async function createSubject(databaseId: TestDatabaseId) {
+ const knex = await databases.init(databaseId);
+ const subject = new MyDatabaseClass({ database: knex });
+ await subject.runMigrations();
+ return { knex, subject };
+ }
+
+ describe('foo', () => {
+ // Easily run the exact same test onto all supported databases
+ it.each(databases.eachSupportedId())(
+ 'should run foo on %p',
+ async databaseId => {
+ const { knex, subject } = await createSubject(databaseId);
+ // raw knex is available for underlying manipulation
+ await knex('foo').insert({ value: 2 });
+ // drive your system under test as usual
+ await expect(subject.foos()).resolves.toEqual([{ value: 2 }]);
+ });
+ });
+```
+
+If you want to pass the test database instance into backend plugins or services,
+you can supply it in the form of a mock instance of `coreServices.database` to
+your test database.
+
+```ts
+const { knex, subject } = await createSubject(databaseId);
+const { server } = await startTestBackend({
+ features: [myPlugin()],
+ services: [[coreServices.database, { getClient: async () => knex }]],
+});
+```
+
+When running locally, the tests only run against SQLite for the sake of speed.
+When the `CI` environment variable is set, all given database engines are used.
+
+If you do not want or are unable to use docker based database engines, e.g. if
+your CI environment is able to supply databases natively, the `TestDatabases`
+support custom connection strings through the use of environment variables that
+it'll take into account when present.
+
+- `BACKSTAGE_TEST_DATABASE_POSTGRES13_CONNECTION_STRING`
+- `BACKSTAGE_TEST_DATABASE_POSTGRES9_CONNECTION_STRING`
+- `BACKSTAGE_TEST_DATABASE_MYSQL8_CONNECTION_STRING`
diff --git a/docs/backend-system/building-plugins-and-modules/08-migrating.md b/docs/backend-system/building-plugins-and-modules/08-migrating.md
index 2f3fd5ebcc..cf5aa8106f 100644
--- a/docs/backend-system/building-plugins-and-modules/08-migrating.md
+++ b/docs/backend-system/building-plugins-and-modules/08-migrating.md
@@ -6,4 +6,231 @@ sidebar_label: Migration Guide
description: How to migrate existing backend plugins to the new backend system
---
-# Overview
+> **DISCLAIMER: The new backend system is in alpha, and still under active development. While we have reviewed the interfaces carefully, they may still be iterated on before the stable release.**
+
+Migrating an existing backend plugin to the new backend system is fairly straightforward. The process is similar across the majority of plugins which just return a `Router` that is then wired up in the `index.ts` file of your backend. The primary thing that we need to do is to make sure that the dependencies that are required by the plugin are available, and then registering the router with the HTTP router service.
+
+Let's look at an example of migrating the Kubernetes backend plugin. In the existing (old) system, the kubernetes backend is structured like this:
+
+```ts
+// @backstage/plugin-kubernetes-backend/src/service/router.ts
+
+import { KubernetesBuilder } from './KubernetesBuilder';
+export interface RouterOptions {
+ logger: Logger;
+ config: Config;
+ catalogApi: CatalogApi;
+ clusterSupplier?: KubernetesClustersSupplier;
+ discovery: PluginEndpointDiscovery;
+}
+
+export async function createRouter(
+ options: RouterOptions,
+): Promise {
+ const { router } = await KubernetesBuilder.createBuilder(options)
+ .setClusterSupplier(options.clusterSupplier)
+ .build();
+ return router;
+}
+```
+
+We can re-use the `router` created by the `KubernetesBuilder` in the new backend system. We only need to make sure that the dependencies specified in `RouterOptions` above are available. All of them are part of the `coreServices` which makes migration easy.
+
+```ts
+import {
+ coreServices,
+ createBackendPlugin,
+} from '@backstage/backend-plugin-api';
+import { catalogServiceRef } from '@backstage/plugin-catalog-node';
+import { Router } from 'express';
+import { KubernetesBuilder } from './KubernetesBuilder';
+
+export const kubernetesPlugin = createBackendPlugin({
+ pluginId: 'kubernetes',
+ register(env) {
+ env.registerInit({
+ deps: {
+ logger: coreServices.logger,
+ config: coreServices.config,
+ catalogApi: catalogServiceRef,
+ discovery: coreServices.discovery,
+ // The http router service is used to register the router created by the KubernetesBuilder.
+ http: coreServices.httpRouter,
+ },
+ async init({ config, logger, catalogApi, discovery, http }) {
+ const { router } = await KubernetesBuilder.createBuilder({
+ config,
+ logger,
+ catalogApi,
+ discovery,
+ }).build();
+
+ // We register the router with the http service.
+ http.use(router);
+ },
+ });
+ },
+});
+```
+
+Done! Users of this plugin are now able to import the `kubernetesPlugin` and register it in their backend using
+
+```ts
+// packages/backend/src/index.ts
+import { kubernetesPlugin } from '@backstage/plugin-kubernetes-backend';
+backend.add(kubernetesPlugin);
+```
+
+There's one thing missing that those sharp eyed readers might have noticed: the `clusterSupplier` option is missing from the original plugin. Let's add it and discuss the alternatives.
+
+One alternative is to pass the `ClusterSupplier` in as options to the plugin, which is quick and easy but not very flexible, and also hard to evolve without introducing breaking changes as it changes the public API for the plugin. Having complex types passed in directly to the plugin also clutters the backend setup code and makes it harder to read.
+
+Options are primarily used for simple configuration values that are not complex types. In this case we want to allow users to register their own `ClusterSupplier` implementations to the plugin. This is where the new backend system's [extension points](../architecture/05-extension-points.md) come in handy, but let's look at doing this with options first.
+
+```ts
+/* omitted imports but they remain the same as above */
+
+export interface KubernetesOptions {
+ clusterSupplier?: KubernetesClustersSupplier;
+}
+
+const kubernetesPlugin = createBackendPlugin((options: KubernetesOptions) => ({
+ pluginId: 'kubernetes',
+ register(env) {
+ env.registerInit({
+ deps: {
+ /* omitted dependencies but they remain the same as above */
+ },
+ async init({ config, logger, catalogApi, discovery, http }) {
+ const { router } = await KubernetesBuilder.createBuilder({
+ config,
+ logger,
+ catalogApi,
+ discovery,
+ })
+ .setClusterSupplier(options.clusterSupplier)
+ .build();
+ http.use(router);
+ },
+ });
+ },
+}));
+```
+
+The above would allow users to specify their own `ClusterSupplier` implementation to the plugin like this:
+
+```ts
+backend.add(
+ kubernetesPlugin({ clusterSupplier: new MyCustomClusterSupplier() }),
+);
+```
+
+Just to echo what was said above, this is not a very flexible solution and will for example be problematic to keep backwards compatible if we start evolving the options to for example accept multiple suppliers or tweak the `ClusterSupplier` interface.
+
+The new [extension points](../architecture/05-extension-points.md) API allows [modules](../architecture/06-modules.md) to add functionality into the backend plugin itself, in this case an additional `ClusterSupplier`.
+
+The kubernetes backend plugin only supports one `ClusterSupplier` at this time but let's look at how we could add support for multiple suppliers using extension points. This allows users to install several modules that add their own `ClusterSupplier` implementations to the plugin like this:
+
+```ts
+backend.add(kubernetesPlugin());
+backend.add(kubernetesGoogleContainerEngineClusterSupplier());
+backend.add(kubernetesElasticContainerEngine());
+```
+
+Now let's look at how to implement this with extension points. First we need to define the extension point itself. As the extension point will be used by other modules, it's common practice to export these from a shared package so that they can be imported by other modules and plugins.
+
+We'll go ahead and create a `@backstage/plugin-kubernetes-node` package for this and from there we'll export the extension point.
+
+```ts
+import { createExtensionPoint } from '@backstage/backend-plugin-api';
+
+export interface KubernetesClusterSupplierExtensionPoint {
+ addClusterSupplier(supplier: KubernetesClustersSupplier): void;
+}
+
+/**
+ * An extension point that allows other plugins to add cluster suppliers.
+ * @public
+ */
+export const kubernetesClustersSupplierExtensionPoint =
+ createExtensionPoint({
+ id: 'kubernetes.cluster-supplier',
+ });
+```
+
+Now we can use this extension point in the kubernetes backend plugin to register the extension point for modules to use.
+
+```ts
+import { kubernetesClustersSupplierExtensionPoint, KubernetesClusterSupplierExtensionPoint } from '@backstage/plugin-kubernetes-node';
+
+// Our internal implementation of the extension point, should not be exported.
+class ClusterSupplier implements KubernetesClusterSupplierExtensionPoint {
+ private clusterSuppliers: KubernetesClustersSupplier | undefined;
+
+ // This method is private and only used internally to retrieve the registered supplier.
+ getClusterSupplier() {
+ return this.clusterSuppliers;
+ }
+
+ addClusterSupplier(supplier: KubernetesClustersSupplier) {
+ // We can remove this check once the plugin support multiple suppliers.
+ if(this.clusterSuppliers) {
+ throw new Error('Multiple Kubernetes cluster suppliers is not supported at this time');
+ }
+ this.clusterSuppliers = supplier;
+ }
+}
+
+export const kubernetesPlugin = createBackendPlugin({
+ pluginId: 'kubernetes',
+ register(env) {
+ const extensionPoint = new ClusterSupplier();
+ // We register the extension point with the backend, which allows modules to
+ // register their own ClusterSupplier.
+ env.registerExtensionPoint(
+ kubernetesClustersSupplierExtensionPoint,
+ extensionPoint,
+ );
+
+ env.registerInit({
+ deps: {
+ ... omitted ...
+ },
+ async init({ config, logger, catalogApi, discovery, http }) {
+ const { router } = await KubernetesBuilder.createBuilder({
+ config,
+ logger,
+ catalogApi,
+ discovery,
+ })
+ // We pass in the registered supplier from the extension point.
+ .setClusterSupplier(extensionPoint.getClusterSupplier())
+ .build();
+ http.use(router);
+ },
+ });
+ },
+});
+```
+
+And that's it! Modules can now be built that add clusters into to the kubernetes backend plugin, here's an example of a module that adds a `GoogleContainerEngineSupplier` to the kubernetes backend.
+
+```ts
+import { kubernetesClustersSupplierExtensionPoint } from '@backstage/plugin-kubernetes-node';
+
+export const kubernetesGoogleContainerEngineClusterSupplier =
+ createBackendModule({
+ pluginId: 'kubernetes',
+ moduleId: 'gke.supplier',
+ register(env) {
+ env.registerInit({
+ deps: {
+ supplier: kubernetesClustersSupplierExtensionPoint,
+ },
+ async init({ supplier }) {
+ supplier.addClusterSupplier(new GoogleContainerEngineSupplier());
+ },
+ });
+ },
+ });
+```
diff --git a/docs/backend-system/core-services/01-index.md b/docs/backend-system/core-services/01-index.md
index cb1ec157ae..c688055c96 100644
--- a/docs/backend-system/core-services/01-index.md
+++ b/docs/backend-system/core-services/01-index.md
@@ -1,11 +1,13 @@
---
id: index
title: Core Backend Service APIs
-sidebar_label: Core Services
+sidebar_label: Overview
# prettier-ignore
description: Core backend service APIs
---
+> **DISCLAIMER: The new backend system is in alpha, and still under active development. While we have reviewed the interfaces carefully, they may still be iterated on before the stable release.**
+
The default backend provides several [core services](https://github.com/backstage/backstage/blob/master/packages/backend-plugin-api/src/services/definitions/coreServices.ts) out of the box which includes access to configuration, logging, URL Readers, databases and more.
All core services are available through the `coreServices` namespace in the `@backstage/backend-plugin-api` package.
@@ -18,6 +20,8 @@ import { coreServices } from '@backstage/backend-plugin-api';
One of the most common services is the HTTP router service which is used to expose HTTP endpoints for other plugins to consume.
+### Using the service
+
The following example shows how to register a HTTP router for the `example` plugin.
This single route will be available at the `/api/example/hello` path.
@@ -29,7 +33,7 @@ import {
import { Router } from 'express';
createBackendPlugin({
- id: 'example',
+ pluginId: 'example',
register(env) {
env.registerInit({
deps: { http: coreServices.httpRouter },
@@ -46,9 +50,33 @@ createBackendPlugin({
});
```
-## Logging and Configuration Service
+### Configuring the service
-It is common for plugins to need access to configuration values and log messages.
+There's additional configuration that you can optionally pass to setup the `httpRouter` core service.
+
+- `getPath` - Can be used to generate a path for each plugin. Currently defaults to `/api/${pluginId}`
+
+You can configure these additional options by adding an override for the core service when calling `createBackend` like follows:
+
+```ts
+import { httpRouterServiceFactory } from '@backstage/backend-app-api';
+
+const backend = createBackend({
+ services: [
+ httpRouterServiceFactory({
+ getPath: (pluginId: string) => `/plugins/${pluginId}`,
+ }),
+ ],
+});
+```
+
+## Root HTTP Router
+
+The root HTTP router is a service that allows you to register routes on the root of the backend service. This is useful for things like health checks, or other routes that you want to expose on the root of the backend service. It is used as the base router that backs the `httpRouter` service. Most likely you won't need to use this service directly, but rather use the `httpRouter` service.
+
+### Using the service
+
+The following example shows how to get the root HTTP router service in your `example` backend plugin to register a health check route.
```ts
import {
@@ -58,17 +86,588 @@ import {
import { Router } from 'express';
createBackendPlugin({
- id: 'example',
+ pluginId: 'example',
+ register(env) {
+ env.registerInit({
+ deps: {
+ rootHttpRouter: coreServices.rootHttpRouter,
+ },
+ async init({ rootHttpRouter }) {
+ const router = Router();
+ router.get('/health', (request, response) => {
+ response.send('OK');
+ });
+
+ rootHttpRouter.use(router);
+ },
+ });
+ },
+});
+```
+
+### Configuring the service
+
+There's additional options that you can pass to configure the root HTTP Router service. These options are passed when you call `createBackend`.
+
+- `indexPath` - optional path to forward all unmatched requests to. Defaults to `/api/app` which is the `app-backend` plugin responsible for serving the frontend application through the backend.
+
+- `configure` - this is an optional function that you can use to configure the `express` instance. This is useful if you want to add your own middleware to the root router, such as logging, or other things that you want to do before the request is handled by the backend. It's also useful to override the order in which middleware is applied.
+
+You can configure the root HTTP Router service by passing the options to the `createBackend` function.
+
+```ts
+import { rootHttpRouterServiceFactory } from '@backstage/backend-app-api';
+
+const backend = createBackend({
+ services: [
+ rootHttpRouterServiceFactory({
+ configure: ({ app, middleware, routes, config, logger, lifecycle }) => {
+ // the built in middleware is provided through an option in the configure function
+ app.use(middleware.helmet());
+ app.use(middleware.cors());
+ app.use(middleware.compression());
+
+ // you can add you your own middleware in here
+ app.use(custom.logging());
+
+ // here the routes that are registered by other plugins
+ app.use(routes);
+
+ // some other middleware that comes after the other routes
+ app.use(middleware.notFound());
+ app.use(middleware.error());
+ },
+ }),
+ ],
+});
+```
+
+## Config
+
+This service allows you to read configuration values out of your `app-config` YAML files.
+
+### Using the service
+
+The following example shows how you can use the default config service to be able to get a config value, and then log it to the console.
+
+```ts
+import {
+ coreServices,
+ createBackendPlugin,
+} from '@backstage/backend-plugin-api';
+
+createBackendPlugin({
+ pluginId: 'example',
register(env) {
env.registerInit({
deps: {
log: coreServices.logger,
config: coreServices.config,
},
- async init({ config, log }) {
- log.warn('Brace yourself for more log output');
- const url = config.getString('backend.baseUrl');
- log.info(`Backend URL is running on ${url}`);
+ async init({ log, config }) {
+ const baseUrl = config.getString('backend.baseUrl');
+ log.warn(`The backend is running at ${baseUrl}`);
+ },
+ });
+ },
+});
+```
+
+### Configuring the service
+
+There's additional configuration that you can optionally pass to setup the `config` core service.
+
+- `argv` - Override the arguments that are passed to the config loader, instead of using `process.argv`
+- `remote` - Configure remote configuration loading
+
+You can configure these additional options by adding an override for the core service when calling `createBackend` like follows:
+
+```ts
+import { configServiceFactory } from '@backstage/backend-app-api';
+
+const backend = createBackend({
+ services: [
+ configServiceFactory({
+ argv: [
+ '--config',
+ '/backstage/app-config.development.yaml',
+ '--config',
+ '/backstage/app-config.yaml',
+ ],
+ remote: { reloadIntervalSeconds: 60 },
+ }),
+ ],
+});
+```
+
+## Logging
+
+This service allows plugins to output logging information. There are actually two logger services: a root logger, and a plugin logger which is bound to individual plugins, so that you will get nice messages with the plugin ID referenced in the log lines.
+
+### Using the service
+
+The following example shows how to get the logger in your `example` backend plugin and create a warning message that will be printed nicely to the console.
+
+```ts
+import {
+ coreServices,
+ createBackendPlugin,
+} from '@backstage/backend-plugin-api';
+
+createBackendPlugin({
+ pluginId: 'example',
+ register(env) {
+ env.registerInit({
+ deps: {
+ log: coreServices.logger,
+ },
+ async init({ log }) {
+ log.warn("Here's a nice log line that's a warning!");
+ },
+ });
+ },
+});
+```
+
+### Root Logger
+
+The root logger is the logger that is used by other root services. It's where the implementation lies for creating child loggers around the backstage ecosystem including child loggers for plugins with the correct metadata and annotations.
+
+If you want to override the implementation for logging across all of the backend, this is the service that you should override.
+
+### Configuring the service
+
+The following example is how you can override the root logger service to add additional metadata to all log lines.
+
+```ts
+import { coreServices } from '@backstage/backend-plugin-api';
+import { WinstonLogger } from '@backstage/backend-app-api';
+
+const backend = createBackend({
+ services: [
+ createServiceFactory({
+ service: coreServices.rootLogger,
+ deps: {
+ config: coreServices.config,
+ },
+ async factory({ config }) {
+ const logger = WinstonLogger.create({
+ meta: {
+ service: 'backstage',
+ // here's some additional information that is not part of the
+ // original implementation
+ podName: 'myk8spod',
+ },
+ level: process.env.LOG_LEVEL || 'info',
+ format:
+ process.env.NODE_ENV === 'production'
+ ? format.json()
+ : WinstonLogger.colorFormat(),
+ transports: [new transports.Console()],
+ });
+
+ return logger;
+ },
+ }),
+ ],
+});
+```
+
+## Cache
+
+This service lets your plugin interact with a cache. It is bound to your plugin too, so that you will only set and get values in your plugin's private namespace.
+
+### Using the service
+
+The following example shows how to get a cache client in your `example` backend plugin and setting and getting values from the cache.
+
+```ts
+import {
+ coreServices,
+ createBackendPlugin,
+} from '@backstage/backend-plugin-api';
+
+createBackendPlugin({
+ pluginId: 'example',
+ register(env) {
+ env.registerInit({
+ deps: {
+ cache: coreServices.cache,
+ },
+ async init({ cache }) {
+ const { key, value } = { key: 'test:key', value: 'bob' };
+ await cache.set(key, value, { ttl: 1000 });
+
+ // .. some other stuff.
+
+ await cache.get(key); // 'bob'
+ },
+ });
+ },
+});
+```
+
+## Database
+
+This service lets your plugins get a `knex` client hooked up to a database which is configured in your `app-config` YAML files, for your persistence needs.
+
+If there's no config provided in `backend.database` then you will automatically get a simple in-memory SQLite 3 database for your plugin whose contents will be lost when the service restarts.
+
+This service is scoped per plugin too, so that table names do not conflict across plugins.
+
+### Using the service
+
+The following example shows how to get access to the database service in your `example` backend plugin and getting a client for interacting with the database. It also runs some migrations from a certain directory for your plugin.
+
+```ts
+import {
+ coreServices,
+ createBackendPlugin,
+} from '@backstage/backend-plugin-api';
+import { resolvePackagePath } from '@backstage/backend-common';
+
+createBackendPlugin({
+ pluginId: 'example',
+ register(env) {
+ env.registerInit({
+ deps: {
+ database: coreServices.database,
+ },
+ async init({ database }) {
+ const client = await database.getClient();
+ const migrationsDir = resolvePackagePath(
+ '@internal/my-plugin',
+ 'migrations',
+ );
+ if (!database.migrations?.skip) {
+ await client.migrate.latest({
+ directory: migrationsDir,
+ });
+ }
+ },
+ });
+ },
+});
+```
+
+## Discovery
+
+When building plugins, you might find that you will need to look up another plugin's base URL to be able to communicate with it. This could be for example an HTTP route or some `ws` protocol URL. For this we have a discovery service which can provide both internal and external base URLs for a given a plugin ID.
+
+### Using the service
+
+The following example shows how to get the discovery service in your `example` backend plugin and making a request to both the internal and external base URLs for the `derp` plugin.
+
+```ts
+import {
+ coreServices,
+ createBackendPlugin,
+} from '@backstage/backend-plugin-api';
+import { fetch } from 'node-fetch';
+
+createBackendPlugin({
+ pluginId: 'example',
+ register(env) {
+ env.registerInit({
+ deps: {
+ discovery: coreServices.discovery,
+ },
+ async init({ discovery }) {
+ const url = await discoverty.getBaseUrl('derp'); // can also use discovery.getBaseUrl to retrieve external URL
+ const response = await fetch(`${url}/hello`);
+ },
+ });
+ },
+});
+```
+
+## Identity
+
+When working with backend plugins, you might find that you will need to interact with the `auth-backend` plugin to both authenticate backstage tokens, and to deconstruct them to get the user's entity ref and/or ownership claims out of them.
+
+### Using the service
+
+The following example shows how to get the identity service in your `example` backend plugin and retrieve the user's entity ref and ownership claims for the incoming request.
+
+```ts
+import {
+ coreServices,
+ createBackendPlugin,
+} from '@backstage/backend-plugin-api';
+import { Router } from 'express';
+
+createBackendPlugin({
+ pluginId: 'example',
+ register(env) {
+ env.registerInit({
+ deps: {
+ identity: coreServices.identity,
+ http: coreServices.httpRouter,
+ },
+ async init({ http, identity }) {
+ const router = Router();
+ router.get('/test-me', (request, response) => {
+ // use the identity service to pull out the header from the request and get the user
+ const {
+ identity: { userEntityRef, ownershipEntityRefs },
+ } = await identity.getIdentity({
+ request,
+ });
+
+ // send the decoded and validated things back to the user
+ response.json({
+ userEntityRef,
+ ownershipEntityRefs,
+ });
+ });
+
+ http.use(router);
+ },
+ });
+ },
+});
+```
+
+### Configuring the service
+
+There's additional configuration that you can optionally pass to setup the `identity` core service.
+
+- `issuer` - Set an optional issuer for validation of the JWT token
+- `algorithms` - `alg` header for validation of the JWT token, defaults to `ES256`. More info on supported algorithms can be found in the [`jose` library documentation](https://github.com/panva/jose)
+
+You can configure these additional options by adding an override for the core service when calling `createBackend` like follows:
+
+```ts
+import { identityServiceFactory } from '@backstage/backend-app-api';
+
+const backend = createBackend({
+ services: [
+ identityServiceFactory({
+ issuer: 'backstage',
+ algorithms: ['ES256', 'RS256'],
+ }),
+ ],
+});
+```
+
+## Lifecycle
+
+This service allows your plugins to register hooks for cleaning up resources as the service is shutting down (e.g. when a pod is being torn down, or when pressing `Ctrl+C` during local development). Other core services also leverage this same mechanism internally to stop themselves cleanly.
+
+### Using the service
+
+The following example shows how to get the lifecycle service in your `example` backend plugin to clean up a long running interval when the service is shutting down.
+
+```ts
+import {
+ coreServices,
+ createBackendPlugin,
+} from '@backstage/backend-plugin-api';
+
+createBackendPlugin({
+ pluginId: 'example',
+ register(env) {
+ env.registerInit({
+ deps: {
+ lifecycle: coreServices.lifecycle,
+ logger: coreServices.logger,
+ },
+ async init({ lifecycle, logger }) {
+ // some example work that we want to stop when shutting down
+ const interval = setInterval(async () => {
+ await fetch('http://google.com/keepalive').then(r => r.json());
+ // do some other stuff.
+ }, 1000);
+
+ lifecycle.addShutdownHook(() => clearInterval(interval));
+ },
+ });
+ },
+});
+```
+
+## Root Lifecycle
+
+This service is the same as the lifecycle service, but should only be used by the root services. This is also where the implementation for the actual lifecycle hooks are collected and executed, so if you want to override the implementation of how those are processed, you should override this service.
+
+### Configure the service
+
+The following example shows how to override the default implementation of the lifecycle service with something that listens on different process events to the original.
+
+```ts
+class MyCustomLifecycleService implements RootLifecycleService {
+ constructor(private readonly logger: LoggerService) {}
+
+ #isCalled = false;
+ #shutdownTasks: Array<{
+ hook: LifecycleServiceShutdownHook;
+ options?: LifecycleServiceShutdownOptions;
+ }> = [];
+
+ addShutdownHook(
+ hook: LifecycleServiceShutdownHook,
+ options?: LifecycleServiceShutdownOptions,
+ ): void {
+ this.#shutdownTasks.push({ hook, options });
+ }
+
+ async shutdown(): Promise {
+ if (this.#isCalled) {
+ return;
+ }
+ this.#isCalled = true;
+
+ this.logger.info(`Running ${this.#shutdownTasks.length} shutdown tasks...`);
+ await Promise.all(
+ this.#shutdownTasks.map(async ({ hook, options }) => {
+ const logger = options?.logger ?? this.logger;
+ try {
+ await hook();
+ logger.info(`Shutdown hook succeeded`);
+ } catch (error) {
+ logger.error(`Shutdown hook failed, ${error}`);
+ }
+ }),
+ );
+ }
+}
+
+const backend = createBackend({
+ services: [
+ createServiceFactory({
+ service: coreServices.rootLifecycle,
+ deps: {
+ logger: coreServices.rootLogger,
+ },
+ async factory({ logger }) {
+ return new MyCustomLifecycleService(logger);
+ },
+ }),
+ ],
+});
+```
+
+## Permissions
+
+This service allows your plugins to ask [the permissions framework](https://backstage.io/docs/permissions/overview) for authorization of user actions.
+
+### Using the service
+
+The following example shows how to get the permissions service in your `example` backend to check to see if the user is allowed to perform a certain action with a custom permission rule.
+
+```ts
+import {
+ coreServices,
+ createBackendPlugin,
+} from '@backstage/backend-plugin-api';
+import { Router } from 'express';
+
+createBackendPlugin({
+ pluginId: 'example',
+ register(env) {
+ env.registerInit({
+ deps: {
+ permissions: coreServices.permissions,
+ http: coreServices.httpRouter,
+ },
+ async init({ permissions, http }) {
+ const router = Router();
+ router.get('/test-me', (request, response) => {
+ // use the identity service to pull out the token from request headers
+ const { token } = await identity.getIdentity({
+ request,
+ });
+
+ // ask the permissions framework what the decision is for the permission
+ const permissionResponse = await permissions.authorize(
+ [
+ {
+ permission: myCustomPermission,
+ },
+ ],
+ { token },
+ );
+ });
+
+ http.use(router);
+ },
+ });
+ },
+});
+```
+
+## Scheduler
+
+When writing plugins, you sometimes want to have things running on a schedule, or something similar to cron jobs that are distributed through instances that your backend plugin is running on. We supply a task scheduler for this purpose that is scoped per plugin so that you can create these tasks and orchestrate their execution.
+
+### Using the service
+
+The following example shows how to get the scheduler service in your `example` backend to issue a scheduled task that runs across your instances at a given interval.
+
+```ts
+import {
+ coreServices,
+ createBackendPlugin,
+} from '@backstage/backend-plugin-api';
+import { fetch } from 'node-fetch';
+
+createBackendPlugin({
+ pluginId: 'example',
+ register(env) {
+ env.registerInit({
+ deps: {
+ scheduler: coreServices.scheduler,
+ },
+ async init({ scheduler }) {
+ await scheduler.scheduleTask({
+ frequency: { minutes: 10 },
+ timeout: { seconds: 30 },
+ id: 'ping-google',
+ fn: async () => {
+ await fetch('http://google.com/ping');
+ },
+ });
+ },
+ });
+ },
+});
+```
+
+## URL Readers
+
+Plugins will require communication with certain integrations that users have configured. Popular integrations are things like Version Control Systems (VSC), such as GitHub, BitBucket GitLab etc. These integrations are configured in the `integrations` section of the `app-config.yaml` file.
+
+These URL readers are basically wrappers with authentication for files and folders that could be stored in these VCS repositories.
+
+### Using the service
+
+The following example shows how to get the URL Reader service in your `example` backend plugin to read a file and a directory from a GitHub repository.
+
+```ts
+import {
+ coreServices,
+ createBackendPlugin,
+} from '@backstage/backend-plugin-api';
+import os from 'os';
+
+createBackendPlugin({
+ pluginId: 'example',
+ register(env) {
+ env.registerInit({
+ deps: {
+ urlReader: coreServices.urlReader,
+ },
+ async init({ urlReader }) {
+ const buffer = await urlReader
+ .read('https://github.com/backstage/backstage/blob/master/README.md')
+ .then(r => r.buffer());
+
+ const tmpDir = os.tmpdir();
+ const directory = await urlReader
+ .readTree(
+ 'https://github.com/backstage/backstage/tree/master/packages/backend',
+ )
+ .then(tree => tree.dir({ targetDir: tmpDir }));
},
});
},
diff --git a/docs/backend-system/index.md b/docs/backend-system/index.md
index ca13bc188c..87eb008129 100644
--- a/docs/backend-system/index.md
+++ b/docs/backend-system/index.md
@@ -1,15 +1,15 @@
---
id: index
title: The Backend System
-sidebar_label: Overview
+sidebar_label: Introduction
# prettier-ignore
-description: The backend system
+description: The Backend System
---
-> **DISCLAIMER: The new backend system is under active development and is not considered stable**
+> **DISCLAIMER: The new backend system is in alpha, and still under active development. While we have reviewed the interfaces carefully, they may still be iterated on before the stable release.**
## Status
-The new backend system is under active development, and only a small number of plugins have been migrated so far. It is possible to try it out, but it is not recommended to use this new system in production yet.
+The new backend system is in alpha, and only a small number of plugins have been migrated so far. It is possible to try it out, but it is not recommended to use this new system in production yet.
-You can find an example backend setup in [the backend-next package](https://github.com/backstage/backstage/tree/master/packages/backend-next).
+You can find an example backend setup in [the `backend-next` package](https://github.com/backstage/backstage/tree/master/packages/backend-next).
diff --git a/docs/dls/design.md b/docs/dls/design.md
index a2091aeb10..3444ff3ad2 100644
--- a/docs/dls/design.md
+++ b/docs/dls/design.md
@@ -26,7 +26,7 @@ that growth, check out the issues in this GitHub repo!
The Backstage Design Team is small but mighty, and we truly cherish the amazing
opportunity we have to work with the Backstage Open Source community! Have an
idea? A component request? Feel free to communicate with us via
-[Discord](https://discord.gg/EBHEGzX) (_#design_ channel). Collaboration trumps
+[Discord](https://discord.gg/backstage-687207715902193673) (_#design_ channel). Collaboration trumps
individual speed, and we want to work with you to make Backstage work for all of
our users.
@@ -38,7 +38,7 @@ loop! Keep an eye on our
where we're headed and review the
[open design issues](https://github.com/backstage/backstage/issues?q=is%3Aopen+is%3Aissue+label%3Adesign),
to see if you can help. We'll also be posting updates in the _#design_ channel
-on [Discord](https://discord.gg/EBHEGzX). Not only that, we want to keep you
+on [Discord](https://discord.gg/backstage-687207715902193673). Not only that, we want to keep you
informed on the decisions we've made and why we've made them.
## 🛠 Our Practice
@@ -101,14 +101,14 @@ focused on building up our core Backstage user experience. We’ll be labeling o
issues in GitHub with ‘design’ and/or ‘storybook’ - so feel free to browse and
tackle the tasks that interest you. If you have any questions regarding an
issue, you can ask them in the comments section of the issue or on
-[Discord](https://discord.gg/EBHEGzX). We absolutely adore our external
+[Discord](https://discord.gg/backstage-687207715902193673). We absolutely adore our external
contributors and will send you virtual
[semlas](https://en.wikipedia.org/wiki/Semla) for your contributions!
### Request a component
Create an issue (label it design and assign it to katz95) or send us a message
-on [Discord](https://discord.gg/EBHEGzX) (_#design_ channel) with details of
+on [Discord](https://discord.gg/backstage-687207715902193673) (_#design_ channel) with details of
what the component is and its relevant use cases. Your request will be reviewed
by our design team and you should hear back from us within 1-2 business days.
We’ll get back to you and let you know whether your requested component will get
@@ -124,7 +124,7 @@ components we’ve designed to the Storybook as well.
Community to share our design assets. You can duplicate our UI Kit and design
your own plugin for Backstage.
-**[Discord](https://discord.gg/EBHEGzX)** - all design questions should be
+**[Discord](https://discord.gg/backstage-687207715902193673)** - all design questions should be
directed to the _#design_ channel.
**[Customize Backstage's look and feel](https://backstage.io/docs/getting-started/app-custom-theme)** -
@@ -142,4 +142,4 @@ scenes, we’re setting up a few foundational elements to make sure that
contributing to Backstage as a designer is easy. From styling guidelines to UX
principles to Figma documents, we’ll make sure you’re equipped to chip in on
this project. We’re excited to work with you! In the meantime, we’d love to hear
-from you on [Discord](https://discord.gg/EBHEGzX).
+from you on [Discord](https://discord.gg/backstage-687207715902193673).
diff --git a/docs/features/kubernetes/configuration.md b/docs/features/kubernetes/configuration.md
index 41d72b2be2..837574aa9e 100644
--- a/docs/features/kubernetes/configuration.md
+++ b/docs/features/kubernetes/configuration.md
@@ -402,6 +402,41 @@ view the Kubernetes API docs for your Kubernetes version (e.g.
[API Groups for v1.22](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#-strong-api-groups-strong-)
)
+### `objectTypes` (optional)
+
+Overrides for the Kubernetes object types fetched from the cluster. The default object types are:
+
+- pods
+- services
+- configmaps
+- limitranges
+- deployments
+- replicasets
+- horizontalpodautoscalers
+- jobs
+- cronjobs
+- ingresses
+- statefulsets
+- daemonsets
+
+You may use this config to override the default object types if you only want a subset of
+the default ones. However, it's currently not supported to fetch object types other
+than the ones specified in the default types.
+
+Example:
+
+```yaml
+---
+kubernetes:
+ objectTypes:
+ - configmaps
+ - deployments
+ - limitranges
+ - pods
+ - services
+ - statefulsets
+```
+
### Role Based Access Control
The current RBAC permissions required are read-only cluster wide, the below
@@ -441,6 +476,13 @@ rules:
- get
- list
- watch
+ - apiGroups:
+ - metrics.k8s.io
+ resources:
+ - pods
+ verbs:
+ - get
+ - list
```
## Surfacing your Kubernetes components as part of an entity
diff --git a/docs/features/kubernetes/proxy.md b/docs/features/kubernetes/proxy.md
new file mode 100644
index 0000000000..5848d62e3e
--- /dev/null
+++ b/docs/features/kubernetes/proxy.md
@@ -0,0 +1,87 @@
+---
+id: proxy
+title: Kubernetes Backend Proxy Endpoint
+sidebar_label: Proxy
+description: Interacting with the Kubernetes API in Backstage plugins
+---
+
+[Contributors](https://backstage.io/docs/overview/glossary#backstage-user-profiles) wanting to
+create developer portal experiences based on data from Kubernetes (e.g. for
+interacting with [Custom
+Resources](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/)
+beyond the default behaviors of the existing Kubernetes plugin) can leverage the
+Kubernetes backend plugin's proxy endpoint to allow them to make arbitrary
+requests to the [REST
+API](https://kubernetes.io/docs/reference/using-api/api-concepts/).
+
+Here is a snippet fetching namespaces from a cluster configured with the
+`google` [auth provider](https://backstage.io/docs/features/kubernetes/configuration#clustersauthprovider):
+
+```typescript
+import {
+ discoveryApiRef,
+ googleAuthApiRef,
+ useApi,
+} from '@backstage/core-plugin-api';
+
+const CLUSTER_NAME = ''; // use a known cluster name
+
+// get a bearer token from Google
+const googleAuthApi = useApi(googleAuthApiRef);
+const token = await googleAuthApi.getAccessToken(
+ 'https://www.googleapis.com/auth/cloud-platform',
+);
+
+const discoveryApi = useApi(discoveryApiRef);
+const kubernetesBaseUrl = await discoveryApi.getBaseUrl('kubernetes');
+const kubernetesProxyEndpoint = `${kubernetesBaseUrl}/proxy`;
+
+// fetch namespaces
+await fetch(`${kubernetesProxyEndpoint}/api/v1/namespaces`, {
+ method: 'GET',
+ headers: {
+ 'X-Kubernetes-Cluster': CLUSTER_NAME,
+ Authorization: `Bearer ${token}`,
+ },
+});
+```
+
+## How it works
+
+The proxy will interpret the
+[`X-Kubernetes-Cluster`
+header](https://backstage.io/docs/reference/plugin-kubernetes-backend.header_kubernetes_cluster)
+as the name of the cluster to target. This name will be compared to each cluster
+returned by all the configured [cluster
+locators](https://backstage.io/docs/features/kubernetes/configuration#clusterlocatormethods)
+-- the first cluster whose [`name` field](https://backstage.io/docs/features/kubernetes/configuration#clustersname) matches
+the value in the header will be targeted.
+
+Then the request will be forwarded verbatim (but with the endpoint's base URL
+prefix stripped) to the cluster.
+
+## Authentication
+
+Until some security and permission decisions are made (see [this
+conversation](https://github.com/backstage/backstage/pull/13026/files#r1029376939)
+for context), contributors consuming the proxy endpoint in their plugin code are
+responsible for negotiating their own bearer token out-of-band. This requires
+knowing some auth details about the cluster being contacted -- in practice, only
+clusters with [client side auth
+providers](https://backstage.io/docs/features/kubernetes/authentication#client-side-providers) can reasonably be reached.
+
+The proxy has no provisions for mTLS, so it cannot be used to connect to
+clusters using the [x509 Client
+Certs](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#x509-client-certs)
+authentication strategy. [Bearer
+tokens](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#putting-a-bearer-token-in-a-request)
+will be forwarded as-is.
+
+## Other known limitations
+
+The proxy as it was released in [Backstage
+1.9](https://github.com/backstage/backstage/blob/master/docs/releases/v1.9.0-changelog.md#patch-changes-15)
+has a known bug:
+
+- [#15901](https://github.com/backstage/backstage/issues/15901) - it cannot
+ reliably target clusters who share the same name with another located cluster.
diff --git a/docs/features/search/architecture.md b/docs/features/search/architecture.md
index f1183cec5f..62f3a68ff6 100644
--- a/docs/features/search/architecture.md
+++ b/docs/features/search/architecture.md
@@ -9,7 +9,9 @@ to support a wide variety of search engines, while providing a simple developer
experience for plugin developers, and a good out-of-the-box experience for
Backstage end-users.
-
+
+
+
At a base-level, we want to support the following:
diff --git a/docs/features/search/how-to-guides.md b/docs/features/search/how-to-guides.md
index 7267e9fd5b..b464168724 100644
--- a/docs/features/search/how-to-guides.md
+++ b/docs/features/search/how-to-guides.md
@@ -114,6 +114,42 @@ of the `SearchType` component.
> Check out the documentation around [integrating search into plugins](../../plugins/integrating-search-into-plugins.md#create-a-collator) for how to create your own collator.
+## How to customize fields in the Software Catalog index
+
+Sometimes you will might want to have ability to control
+which data passes to search index in catalog collator, or to customize data for specific kind.
+You can easily do that by passing `entityTransformer` callback to `DefaultCatalogCollatorFactory`.
+You can either just simply amend default behaviour, or even to write completely new document
+(which should follow some required basic structure though).
+
+> `authorization` and `location` cannot be modified via a `entityTransformer`, `location` can be modified only through `locationTemplate`.
+
+```diff
+// packages/backend/src/plugins/search.ts
+
+const entityTransformer: CatalogCollatorEntityTransformer = (entity: Entity) => {
+ if (entity.kind === 'SomeKind') {
+ return {
+ // customize here output for 'SomeKind' kind
+ };
+ }
+
+ return {
+ // and customize default output
+ ...defaultCatalogCollatorEntityTransformer(entity),
+ text: 'my super cool text',
+ };
+};
+
+indexBuilder.addCollator({
+ collator: DefaultCatalogCollatorFactory.fromConfig(env.config, {
+ discovery: env.discovery,
+ tokenManager: env.tokenManager,
++ entityTransformer,
+ }),
+});
+```
+
## How to limit what can be searched in the Software Catalog
The Software Catalog includes a wealth of information about the components,
@@ -174,3 +210,170 @@ const highlightOverride = {
[obj-mode]: https://nodejs.org/dist/latest-v16.x/docs/api/stream.html#stream_object_mode
[read-stream]: https://nodejs.org/dist/latest-v16.x/docs/api/stream.html#readable-streams
[async-gen]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of#iterating_over_async_generators
+
+## How to render search results using extensions
+
+Extensions for search results let you customize components used to render search result items, It is possible to provide your own search result item extensions or use the ones provided by plugin packages:
+
+### 1. Providing an extension in your plugin package
+
+Using the example below, you can provide an extension to be used as a default result item:
+
+```tsx
+// plugins/your-plugin/src/plugin.ts
+import { createPlugin } from '@backstage/core-plugin-api';
+import { createSearchResultListItemExtension } from '@backstage/plugin-search-react';
+
+const plugin = createPlugin({ id: 'YOUR_PLUGIN_ID' });
+
+export const YourSearchResultListItemExtension = plugin.provide(
+ createSearchResultListItemExtension({
+ name: 'YourSearchResultListItem',
+ component: () =>
+ import('./components').then(m => m.YourSearchResultListItem),
+ }),
+);
+```
+
+If your list item accept props, you can extend the `SearchResultListItemExtensionProps` with your component specific props:
+
+```tsx
+export const YourSearchResultListItemExtension: (
+ props: SearchResultListItemExtensionProps,
+) => JSX.Element | null = plugin.provide(
+ createSearchResultListItemExtension({
+ name: 'YourSearchResultListItem',
+ component: () =>
+ import('./components').then(m => m.YourSearchResultListItem),
+ }),
+);
+```
+
+Additionally, you can define a predicate function that receives a result and returns whether your extension should be used to render it or not:
+
+```tsx
+// plugins/your-plugin/src/plugin.ts
+import { createPlugin } from '@backstage/core-plugin-api';
+import { createSearchResultListItemExtension } from '@backstage/plugin-search-react';
+
+const plugin = createPlugin({ id: 'YOUR_PLUGIN_ID' });
+
+export const YourSearchResultListItemExtension = plugin.provide(
+ createSearchResultListItemExtension({
+ name: 'YourSearchResultListItem',
+ component: () =>
+ import('./components').then(m => m.YourSearchResultListItem),
+ // Only results matching your type will be rendered by this extension
+ predicate: result => result.type === 'YOUR_RESULT_TYPE',
+ }),
+);
+```
+
+Remember to export your new extension:
+
+```tsx
+// plugins/your-plugin/src/index.ts
+export { YourSearchResultListItem } from './plugin.ts';
+```
+
+For more details, see the [createSearchResultListItemExtension](https://backstage.io/docs/reference/plugin-search-react.createsearchresultlistitemextension) API reference.
+
+### 2. Using an extension in your Backstage app
+
+Now that you know how a search result item is provided, let's finally see how they can be used, for example, to compose a page in your application:
+
+```tsx
+// packages/app/src/components/searchPage.tsx
+import React from 'react';
+
+import { Grid, Paper } from '@material-ui/core';
+import BuildIcon from '@material-ui/icons/Build';
+
+import {
+ Page,
+ Header,
+ Content,
+ DocsIcon,
+ CatalogIcon,
+} from '@backstage/core-components';
+import { SearchBar, SearchResult } from '@backstage/plugin-search-react';
+
+// Your search result item extension
+import { YourSearchResultListItem } from '@backstage/your-plugin';
+
+// Extensions provided by other plugin developers
+import { ToolSearchResultListItem } from '@backstage/plugin-explore';
+import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs';
+import { CatalogSearchResultListItem } from '@internal/plugin-catalog-customized';
+
+// This example omits other components, like filter and pagination
+const SearchPage = () => (
+
+
+
+
+
+
+
+
+
+
+
+
+ } />
+ } />
+ } />
+
+
+
+
+
+);
+
+export const searchPage = ;
+```
+
+> **Important**: A default result item extension should be placed as the last child, so it can be used only when no other extensions match the result being rendered. If a non-default extension is specified, the `DefaultResultListItem` component will be used.
+
+As another example, here's a search modal that renders results with extensions:
+
+```tsx
+// packages/app/src/components/searchModal.tsx
+import React from 'react';
+
+import { DialogContent, DialogTitle, Paper } from '@material-ui/core';
+import BuildIcon from '@material-ui/icons/Build';
+
+import { DocsIcon, CatalogIcon } from '@backstage/core-components';
+import { SearchBar, SearchResult } from '@backstage/plugin-search-react';
+
+// Your search result item extension
+import { YourSearchResultListItem } from '@backstage/your-plugin';
+
+// Extensions provided by other plugin developers
+import { ToolSearchResultListItem } from '@backstage/plugin-explore';
+import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs';
+import { CatalogSearchResultListItem } from '@internal/plugin-catalog-customized';
+
+export const SearchModal = ({ toggleModal }: { toggleModal: () => void }) => (
+ <>
+
+
+
+
+
+
+
+ } />
+ } />
+ } />
+ {/* As a "default" extension, it does not define a predicate function,
+ so it must be the last child to render results that do not match the above extensions */}
+
+
+
+ >
+);
+```
+
+There are other more specific search results layout components that also accept result item extensions, check their documentation: [SearchResultList](https://backstage.io/storybook/?path=/story/plugins-search-searchresultlist--with-result-item-extensions) and [SearchResultGroup](https://backstage.io/storybook/?path=/story/plugins-search-searchresultgroup--with-result-item-extensions).
diff --git a/docs/features/software-catalog/external-integrations.md b/docs/features/software-catalog/external-integrations.md
index da4eb3d229..5fc2537712 100644
--- a/docs/features/software-catalog/external-integrations.md
+++ b/docs/features/software-catalog/external-integrations.md
@@ -89,23 +89,23 @@ export class FrobsProvider implements EntityProvider {
private readonly reader: UrlReader;
private connection?: EntityProviderConnection;
- /** [1] **/
+ /** [1] */
constructor(env: string, reader: UrlReader) {
this.env = env;
this.reader = reader;
}
- /** [2] **/
+ /** [2] */
getProviderName(): string {
return `frobs-${this.env}`;
}
- /** [3] **/
+ /** [3] */
async connect(connection: EntityProviderConnection): Promise {
this.connection = connection;
}
- /** [4] **/
+ /** [4] */
async run(): Promise {
if (!this.connection) {
throw new Error('Not initialized');
@@ -116,10 +116,10 @@ export class FrobsProvider implements EntityProvider {
);
const data = JSON.parse(await response.buffer()).toString();
- /** [5] **/
+ /** [5] */
const entities: Entity[] = frobsToEntities(data);
- /** [6] **/
+ /** [6] */
await this.connection.applyMutation({
type: 'full',
entities: entities.map(entity => ({
diff --git a/docs/features/software-catalog/system-model.md b/docs/features/software-catalog/system-model.md
index 409f3e8450..af359bb18a 100644
--- a/docs/features/software-catalog/system-model.md
+++ b/docs/features/software-catalog/system-model.md
@@ -63,6 +63,17 @@ BigTable databases, Pub/Sub topics, S3 buckets or CDNs. Modelling them together
with components and systems will better allow us to visualize resource
footprint, and create tooling around them.
+## Organizational Entities
+
+### User
+
+A user describes a person, such as an employee, a contractor, or similar.
+
+### Group
+
+A group describes an organizational entity, such as for example a team, a
+business unit, or a loose collection of people in an interest group.
+
## Ecosystem Modeling
A large catalogue of components, APIs and resources can be highly granular and
@@ -108,3 +119,20 @@ domain would come with some documentation on how to accept payments for a new
product or use-case, share the same entity types in their APIs, and integrate
well with each other. Other domains could be “Content Ingestion”, “Ads” or
“Search”.
+
+## Other
+
+### Location
+
+A location is a marker that references other places to look for catalog data.
+
+### Type
+
+The type field in the system has no set meaning. It is up to the user to assign their own types and use them as desired, such as for link validation or creating custom UI components. Some common pre-defined types are depicted in the
+[ecosystem modeling diagram](#ecosystem-modeling).
+
+### Template
+
+A template definition describes both the parameters that are rendered in the
+frontend part of the scaffolding wizard, and the steps that are executed when
+scaffolding that component.
diff --git a/docs/features/software-templates/migrating-from-v1beta2-to-v1beta3.md b/docs/features/software-templates/migrating-from-v1beta2-to-v1beta3.md
index 6e37a4df3e..f9d2c4eb0c 100644
--- a/docs/features/software-templates/migrating-from-v1beta2-to-v1beta3.md
+++ b/docs/features/software-templates/migrating-from-v1beta2-to-v1beta3.md
@@ -211,7 +211,7 @@ input:
### Summary
-Of course, we're always available on [discord](https://discord.gg/MUpMjP2) if
+Of course, we're always available on [discord](https://discord.gg/backstage-687207715902193673) if
you're stuck or something's not working as expected. You can also
[raise an issue](https://github.com/backstage/backstage/issues/new/choose) with
feedback or bugs!
diff --git a/docs/features/software-templates/testing-scaffolder-alpha.md b/docs/features/software-templates/testing-scaffolder-alpha.md
new file mode 100644
index 0000000000..76479b137e
--- /dev/null
+++ b/docs/features/software-templates/testing-scaffolder-alpha.md
@@ -0,0 +1,234 @@
+---
+id: testing-scaffolder-alpha
+title: 'Experimental: Testing out the alpha Scaffolder plugin'
+# prettier-ignore
+description: Docs on the upcoming breaking release for the scaffolder plugin
+---
+
+## What's `scaffolder/next`?
+
+The `alpha` version, or as you might have seen referred to in other places the `scaffolder/next` release, is a new version of the `scaffolder` plugin that will be the first breaking change to the plugin, so you can also think of it as `@backstage/plugin-scaffolder@2.0.0`.
+Its mostly a rewrite of a lot of the frontend components and pages that had very limited test coverage, which made adding new features to the `scaffolder` plugin quite hard, and we were lacking in confidence when making changes.
+
+There is of course some other things that have changed when re-writing this, which are essentially what has caused some breaking changes.
+Now, this is not like previous scaffolder changes where you would have to change all of your templates as this is only the frontend plugin that is going to have breaking changes. You can read more about the [breaking changes](#breaking-changes) below.
+
+## What's new?
+
+First off, the main dependency that we have for the frontend which is responsible for rendering the `JSONSchema` into `material-ui` components is [react-jsonschema-form](https://github.com/rjsf-team/react-jsonschema-form).
+This dependency in the current version of the plugin is 3.x.x, which is now 2 major versions out of date. Long story short, `v4` of this plugin contained some bug fixes, and new features but we we're unable to upgrade due to some issues with having support for `material-ui@v4`, so we had to wait for `v5` to be released, and because of the `FieldExtensions` and how they are very tightly coupled to the `react-jsonschema-form` library, we also wanted to make sure that this release was stable before getting people to migrate their `Field Extensions`.
+
+With that in mind, this release has `v5` of `react-jsonschema-form`, and with that comes all the new features and bugfixes in `v4` that we were waiting for - one of the main ones being the ability to use `if / then / else` syntax in the `template.yaml` definitions! 🎉
+
+We've also rebuilt how validation works in the `scaffolder` components, which now means that we've opened the ability to have `async` validation functions in your `Field Extensions`.
+
+Some of the pages have gotten a little bit of an overhaul in terms of UI based on some research and feedback from the community and internally.
+
+- The `TemplateList` page has gotten some new `Card` components which show a little more information than the previous version with a little `material-ui` standards.
+- The `WizardPage` has received some new updates with the stepper now running horizontally, and the `Review` step being a dedicated step in the stepper.
+- The `OngoingTask` page now does not show the logs by default, and instead has a much cleaner interface for tracking the ongoing steps and the pipeline of actions that are currently showing.
+ - You can also now provide your own `OutputsComponent` which can be used to render the outputs from an ongoing / completed task in a way that suits your templates the best. For instance, if your template produces `Pull Requests`, it could be useful to render these in an interactive way where you can see the statuses of each of these `Pull Requests` in the `Ongoing Task` page.
+
+There's also a lot of bug fixes, and other things, but these are the main ones that we wanted to highlight.
+
+## How do I test out the `alpha` version?
+
+With the release of [`v1.11.0`](https://github.com/backstage/backstage/releases/tag/v1.11.0) it's now possible to run the `scaffolder/next` plugin and it be a drop in replacement for the current version that you use today. This means that you can start using the new code, and start testing it out. Once we have collected enough feedback, and squashed any bugs that might block us from releasing, it will be promoted from the `/alpha` exports and replace the existing code leading to breaking changes if you haven't already made these changes as part of this testing pilot. Those that have chosen to opt into this testing pilot means that once we promote it from the `/alpha` exports, you will need to update your code to point to the original exports from the `scaffolder` plugin, just like the code is today but with the [breaking changes](#breaking-changes) that you already made to your `Custom Field Extensions`.
+
+It's also worth calling out that if you do test this out, and find some issues or something not working out as expected, feel free to raise an issue in the [repo](https://github.com/backstage/backstage) or reach out to us on Discord!
+
+### Make the required changes to `App.tsx`
+
+The `ScaffolderPage` router has a completely different export for the `scaffolder/next` work, so you will want to change any import from the old `ScaffolderPage` to the new `NextScaffolderPage`
+
+```diff
+- import { ScaffolderPage } from '@backstage/plugin-scaffolder';
++ import { NextScaffolderPage } from '@backstage/plugin-scaffolder/alpha';
+
+```
+
+And this API should be the exact same as the previous Router, so you should be able to make a change like the following further down in this file:
+
+```diff
+
+ entity?.metadata?.tags?.includes('recommended') ?? false,
+ },
+ ]}
+ />
+ }
+ >
+
+
+ ... other extensions
+
+
+
+ ... other layouts
+
+
+```
+
+### Make the required changes to your `CustomFieldExtensions`
+
+There's differently named function for creating field extensions part of the `/alpha` exports as these are the ones that can contain breaking changes because of the breaking changes that have been applied in `react-jsonschema-form`.
+
+Let's take the following example:
+
+```ts
+export const EntityNamePickerFieldExtension = scaffolderPlugin.provide(
+ createScaffolderFieldExtension({
+ component: EntityNamePicker,
+ name: 'EntityNamePicker',
+ validation: entityNamePickerValidation,
+ schema: EntityNamePickerSchema,
+ }),
+);
+```
+
+References for `createScaffolderFieldExtension` have an `/alpha` version of `createNextScaffolderFieldExtension`, which should be used instead.
+
+```diff
+-import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder';
++import { createNextScaffolderFieldExtension } from '@backstage/plugin-scaffolder/alpha';
+
+export const EntityNamePickerFieldExtension = scaffolderPlugin.provide(
+- createScaffolderFieldExtension({
++ createNextScaffolderFieldExtension({
+ component: EntityNamePicker,
+ name: 'EntityNamePicker',
+ validation: entityNamePickerValidation,
+ }),
+);
+```
+
+Once you've done this you will find that you will have two squiggly lines under the properties that are passed in. One for the component and one for the validation (if provided.)
+
+Let's take the following code for the `EntityNamePicker` component:
+
+```ts
+export const EntityNamePicker = (
+ props: FieldExtensionComponentProps,
+) => {
+ const {
+ onChange,
+ required,
+ schema: { title = 'Name', description = 'Unique name of the component' },
+ rawErrors,
+ formData,
+ uiSchema: { 'ui:autofocus': autoFocus },
+ idSchema,
+ placeholder,
+ } = props;
+ ...
+}
+```
+
+There's another `/alpha` export that you need to replace `FieldExtensionComponentProps` with which is the `NextFieldExtensionComponentProps`.
+
+```diff
+- import { FieldExtensionComponentProps } from '@backstage/plugin-scaffolder-react';
++ import { NextFieldExtensionComponentProps } from '@backstage/plugin-scaffolder-react/alpha';
+
+export const EntityNamePicker = (
+- props: FieldExtensionComponentProps,
++ props: NextFieldExtensionComponentProps,
+) => {
+ const {
+ onChange,
+ required,
+ schema: { title = 'Name', description = 'Unique name of the component' },
+ rawErrors,
+ formData,
+- uiSchema: { 'ui:autofocus': autoFocus },
++ uiSchema: { 'ui:autofocus': autoFocus } = {},
+ idSchema,
+ placeholder,
+ } = props;
+ ...
+}
+```
+
+You'll notice that there's an additional change here, which is that we're now defaulting the `uiSchema` to an empty object. This is because the `uiSchema` is now optional, and if you don't provide it, it will be `undefined` instead of an empty object. There's more around this in the [breaking changes](#breaking-changes) section.
+
+To fix the previous validation error, you will need to change the import for the `FieldValidation` type that is used in the `validation` function.
+
+Let's take the following example of the validation function:
+
+```ts
+import { FieldValidation } from '@rjsf/utils';
+import { KubernetesValidatorFunctions } from '@backstage/catalog-model';
+
+export const entityNamePickerValidation = (
+ value: string,
+ validation: FieldValidation,
+) => {
+ if (!KubernetesValidatorFunctions.isValidObjectName(value)) {
+ validation.addError(
+ 'Must start and end with an alphanumeric character, and contain only alphanumeric characters, hyphens, underscores, and periods. Maximum length is 63 characters.',
+ );
+ }
+};
+```
+
+You will need to change the import for `FieldValidation` to point at the new `react-jsonschema-form` dependency.
+
+> Note: you will probably need to install this dependency too, by using `yarn add @rjsf/utils` in the package where you define these validation functions, this could also be in the `packages/app` folder, so you can install it there if needed.
+
+```diff
+- import { FieldValidation } from '@rjsf/core';
++ import { FieldValidation } from '@rjsf/utils;
+import { KubernetesValidatorFunctions } from '@backstage/catalog-model';
+
+export const entityNamePickerValidation = (
+ value: string,
+ validation: FieldValidation,
+) => {
+```
+
+## Breaking Changes
+
+Once we fully release the code that is in the `/alpha` exports right now onto the current API and release v2.0.0 of `@backstage/plugin-scaffolder` the breaking changes will be as follows:
+
+### `uiSchema` is now optional
+
+Later releases of `react-jsonschema-form` have made the `uiSchema` optional, and if you don't provide it, it will be `undefined` instead of an empty object. This means that you will need to make sure that you're defaulting the `uiSchema` to an empty object if you're using it in your code.
+
+```diff
+ const {
+ onChange,
+ required,
+ schema: { title = 'Name', description = 'Unique name of the component' },
+ rawErrors,
+ formData,
+- uiSchema: { 'ui:autofocus': autoFocus },
++ uiSchema: { 'ui:autofocus': autoFocus } = {},
+ idSchema,
+ placeholder,
+ } = props;
+```
+
+### `formData` can also be `undefined`
+
+If you were using the `formData` and assuming that it was set to an empty object when building `Field Extensions` that return objects, then this will be `undefined` now due to a change in the `react-jsonschema-form` library.
+
+```diff
+ const {
+ onChange,
+ required,
+ schema: { title = 'Name', description = 'Unique name of the component' },
+ rawErrors,
+- formData,
++ formData = {}, // or maybe some other default value that you would prefer
+ uiSchema: { 'ui:autofocus': autoFocus } = {},
+ idSchema,
+ placeholder,
+ } = props;
+```
diff --git a/docs/features/software-templates/writing-custom-actions.md b/docs/features/software-templates/writing-custom-actions.md
index dfbd4b0a42..ffe3312501 100644
--- a/docs/features/software-templates/writing-custom-actions.md
+++ b/docs/features/software-templates/writing-custom-actions.md
@@ -168,5 +168,6 @@ scaffolder backend:
| Scaffolder Git Actions | [plugin-scaffolder-git-actions](https://www.npmjs.com/package/@mdude2314/backstage-plugin-scaffolder-git-actions) | [Drew Hill](https://github.com/arhill05) |
| Azure Pipeline Actions | [scaffolder-backend-module-azure-pipelines](https://www.npmjs.com/package/@parfuemerie-douglas/scaffolder-backend-module-azure-pipelines) | [Parfümerie Douglas](https://github.com/Parfuemerie-Douglas) |
| Azure Repository Actions | [scaffolder-backend-module-azure-repositories](https://www.npmjs.com/package/@parfuemerie-douglas/scaffolder-backend-module-azure-repositories) | [Parfümerie Douglas](https://github.com/Parfuemerie-Douglas) |
+| Snyk Import Project | [plugin-scaffolder-backend-module-snyk](https://www.npmjs.com/package/@ma11hewthomas/plugin-scaffolder-backend-module-snyk) | [Matthew Thomas](https://github.com/Ma11hewThomas) |
Have fun! 🚀
diff --git a/docs/features/software-templates/writing-custom-field-extensions.md b/docs/features/software-templates/writing-custom-field-extensions.md
index be00453795..20575a0b4a 100644
--- a/docs/features/software-templates/writing-custom-field-extensions.md
+++ b/docs/features/software-templates/writing-custom-field-extensions.md
@@ -35,7 +35,7 @@ import FormControl from '@material-ui/core/FormControl';
/*
This is the actual component that will get rendered in the form
*/
-export const ValidateKebabCaseExtension = ({
+export const ValidateKebabCase = ({
onChange,
rawErrors,
required,
@@ -96,7 +96,7 @@ import {
import {
ValidateKebabCase,
validateKebabCaseValidation,
-} from './ValidateKebabCase';
+} from './ValidateKebabCase/ValidateKebabCaseExtension';
export const ValidateKebabCaseFieldExtension = scaffolderPlugin.provide(
createScaffolderFieldExtension({
@@ -173,7 +173,7 @@ spec:
title: Name
type: string
description: My custom name for the component
- ui:field: ValidateKebabCaseExtension
+ ui:field: ValidateKebabCase
steps:
[...]
```
diff --git a/docs/features/software-templates/writing-templates.md b/docs/features/software-templates/writing-templates.md
index 71aa0ab758..a272f05f3f 100644
--- a/docs/features/software-templates/writing-templates.md
+++ b/docs/features/software-templates/writing-templates.md
@@ -112,11 +112,11 @@ steps which would be rendered as different steps in the scaffolder plugin
frontend.
Each `Step` is `JSONSchema` with some extra goodies for styling what it might
-look like in the frontend. For these steps we rely very heavily on this library:
-https://github.com/rjsf-team/react-jsonschema-form. They have some great docs
-too here: https://react-jsonschema-form.readthedocs.io/ and a playground where
-you can play around with some examples here
-https://rjsf-team.github.io/react-jsonschema-form.
+look like in the frontend. For these steps we rely very heavily on this
+[library](https://github.com/rjsf-team/react-jsonschema-form). They have some
+[great docs](https://rjsf-team.github.io/react-jsonschema-form/docs/) and a
+[playground](https://rjsf-team.github.io/react-jsonschema-form) where you can
+play around with some examples.
There's another option for that library called `uiSchema` which we've taken
advantage of, and we've merged it with the existing `JSONSchema` that you
diff --git a/docs/features/techdocs/addons.md b/docs/features/techdocs/addons.md
index fe1058b2ca..bd709ee15f 100644
--- a/docs/features/techdocs/addons.md
+++ b/docs/features/techdocs/addons.md
@@ -39,7 +39,9 @@ representative of physical spaces in the TechDocs UI:
- `Component`: A [proposed-but-not-yet-implemented](https://github.com/backstage/backstage/issues/11109)
virtual location, aimed at simplifying a common type of Addon.
-
+
+
+
### Addon Registry
@@ -125,6 +127,7 @@ discover available Addons, we've compiled a list of them here:
| [``](https://backstage.io/docs/reference/plugin-techdocs-module-addons-contrib.expandablenavigation) | `@backstage/plugin-techdocs-module-addons-contrib` | Allows TechDocs users to expand or collapse the entire TechDocs main navigation, and keeps the user's preferred state between documentation sites. |
| [``](https://backstage.io/docs/reference/plugin-techdocs-module-addons-contrib.reportissue) | `@backstage/plugin-techdocs-module-addons-contrib` | Allows TechDocs users to select a portion of text on a TechDocs page and open an issue against the repository that contains the documentation, populating the issue description with the selected text according to a configurable template. |
| [``](https://backstage.io/docs/reference/plugin-techdocs-module-addons-contrib.textsize) | `@backstage/plugin-techdocs-module-addons-contrib` | This TechDocs addon allows users to customize text size on documentation pages, they can select how much they want to increase or decrease the font size via slider or buttons. The default value for font size is 100% and this setting is kept in the browser's local storage whenever it is changed. |
+| [``](https://backstage.io/docs/reference/plugin-techdocs-module-addons-contrib.lightbox) | `@backstage/plugin-techdocs-module-addons-contrib` | This TechDocs addon allows users to open images in a light-box on documentation pages, they can navigate between images if there are several on one page. The image size of the light-box image is the same as the image size on the document page. |
Got an Addon to contribute? Feel free to add a row above!
diff --git a/docs/features/techdocs/architecture.md b/docs/features/techdocs/architecture.md
index d781f71917..2cb8543ccd 100644
--- a/docs/features/techdocs/architecture.md
+++ b/docs/features/techdocs/architecture.md
@@ -9,7 +9,9 @@ description: Documentation on TechDocs Architecture
When you deploy Backstage (with TechDocs enabled by default), you get a basic
out-of-the box experience.
-
+
+
+
> Note: See below for our recommended deployment architecture which takes care
> of stability, scalability and speed. Also look at the
@@ -42,7 +44,9 @@ storage system (e.g. AWS S3, GCS or Azure Blob Storage). Read more in
This is how we recommend deploying TechDocs in a production environment.
-
+
+
+
The key difference in the recommended deployment approach is where the docs are
built.
diff --git a/docs/features/techdocs/cli.md b/docs/features/techdocs/cli.md
index 682c6a8f8f..6b2e4383b8 100644
--- a/docs/features/techdocs/cli.md
+++ b/docs/features/techdocs/cli.md
@@ -176,29 +176,28 @@ Usage: techdocs-cli publish [options]
Publish generated TechDocs site to an external storage AWS S3, Google GCS, etc.
Options:
- --publisher-type (Required always) awsS3 | googleGcs | azureBlobStorage
- - same as techdocs.publisher.type in Backstage
- app-config.yaml
- --storage-name (Required always) In case of AWS/GCS, use the bucket
- name. In case of Azure, use container name. Same as
- techdocs.publisher.[TYPE].bucketName
- --entity (Required always) Entity uid separated by / in
- namespace/kind/name order (case-sensitive). Example:
- default/Component/myEntity
- --legacyUseCaseSensitiveTripletPaths Publishes objects with cased entity triplet prefix when set (e.g. namespace/Kind/name).
- Only use if your TechDocs backend is configured the same way
- --azureAccountName (Required for Azure) specify when --publisher-type
- azureBlobStorage
- --azureAccountKey Azure Storage Account key to use for authentication.
- If not specified, you must set AZURE_TENANT_ID,
- AZURE_CLIENT_ID & AZURE_CLIENT_SECRET as environment
- variables.
- --awsRoleArn Optional AWS ARN of role to be assumed.
- --awsEndpoint Optional AWS endpoint to send requests to.
- --awsS3ForcePathStyle Optional AWS S3 option to force path style.
- --directory Path of the directory containing generated files to
- publish (default: "./site/")
- -h, --help display help for command
+ --publisher-type (Required always) awsS3 | googleGcs | azureBlobStorage | openStackSwift - same as techdocs.publisher.type in Backstage app-config.yaml
+ --storage-name (Required always) In case of AWS/GCS, use the bucket name. In case of Azure, use container name. Same as
+ techdocs.publisher.[TYPE].bucketName
+ --entity (Required always) Entity uid separated by / in namespace/kind/name order (case-sensitive). Example: default/Component/myEntity
+ --legacyUseCaseSensitiveTripletPaths Publishes objects with cased entity triplet prefix when set (e.g. namespace/Kind/name). Only use if your TechDocs backend is configured
+ the same way. (default: false)
+ --azureAccountName (Required for Azure) specify when --publisher-type azureBlobStorage
+ --azureAccountKey Azure Storage Account key to use for authentication. If not specified, you must set AZURE_TENANT_ID, AZURE_CLIENT_ID &
+ AZURE_CLIENT_SECRET as environment variables.
+ --awsRoleArn Optional AWS ARN of role to be assumed.
+ --awsEndpoint Optional AWS endpoint to send requests to.
+ --awsProxy Optional Proxy to use for AWS requests.
+ --awsS3sse Optional AWS S3 Server Side Encryption.
+ --awsS3ForcePathStyle Optional AWS S3 option to force path style.
+ --awsBucketRootPath Optional sub-directory to store files in Amazon S3
+ --osCredentialId (Required for OpenStack) specify when --publisher-type openStackSwift
+ --osSecret (Required for OpenStack) specify when --publisher-type openStackSwift
+ --osAuthUrl (Required for OpenStack) specify when --publisher-type openStackSwift
+ --osSwiftUrl (Required for OpenStack) specify when --publisher-type openStackSwift
+ --gcsBucketRootPath Optional sub-directory to store files in Google cloud storage
+ --directory Path of the directory containing generated files to publish (default: "./site/")
+ -h, --help display help for command
```
### Migrate content for case-insensitive access
diff --git a/docs/features/techdocs/configuration.md b/docs/features/techdocs/configuration.md
index 097d31f760..49ee9d72c2 100644
--- a/docs/features/techdocs/configuration.md
+++ b/docs/features/techdocs/configuration.md
@@ -49,9 +49,9 @@ techdocs:
# will be broken in these scenarios.
legacyCopyReadmeMdToIndexMd: false
- # techdocs.builder can be either 'local' or 'external.
+ # techdocs.builder can be either 'local' or 'external'.
# Using the default build strategy, if builder is set to 'local' and you open a TechDocs page,
- # techdocs-backend will try to generate the docs, publish to storage and show the generated docs afterwords.
+ # techdocs-backend will try to generate the docs, publish to storage and show the generated docs afterwards.
# This is the "Basic" setup of the TechDocs Architecture.
# Using the default build strategy, if builder is set to 'external' (or anything other than 'local'), techdocs-backend
# will only fetch the docs and will NOT try to generate and publish.
@@ -136,6 +136,11 @@ techdocs:
# https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-s3/interfaces/s3clientconfig.html#endpoint
endpoint: ${AWS_ENDPOINT}
+ # (Optional) HTTPS proxy to use for S3 Requests
+ # Defaults to using no proxy
+ # This allows docs to be published and read from behind a proxy
+ httpsProxy: ${HTTPS_PROXY}
+
# (Optional) Whether to use path style URLs when communicating with S3.
# Defaults to false.
# This allows providers like LocalStack, Minio and Wasabi (and possibly others) to be used to host tech docs.
diff --git a/docs/features/techdocs/getting-started.md b/docs/features/techdocs/getting-started.md
index c2070e7840..3f7edd23a0 100644
--- a/docs/features/techdocs/getting-started.md
+++ b/docs/features/techdocs/getting-started.md
@@ -89,11 +89,15 @@ const AppRoutes = () => {
I know, you're curious to see how it looks, aren't you? See the image below:
-
+
+
+
By clicking the open new issue button, you will be redirected to the new issue page according to the source code provider you are using:
-
+
+
+
That's it! Now, we need the TechDocs Backend plugin for the frontend to work.
@@ -262,12 +266,12 @@ You will have to install the `mkdocs` and `mkdocs-techdocs-core` package from
pip, as well as `graphviz` and `plantuml` from your OS package manager (e.g.
apt).
-You can do so by including the following lines in the last step of your
+You can do so by including the following lines right above `USER node` of your
`Dockerfile`:
```Dockerfile
RUN apt-get update && apt-get install -y python3 python3-pip
-RUN pip3 install mkdocs-techdocs-core==1.0.1
+RUN pip3 install mkdocs-techdocs-core==1.1.7
```
Please be aware that the version requirement could change, you need to check our
diff --git a/docs/features/techdocs/using-cloud-storage.md b/docs/features/techdocs/using-cloud-storage.md
index f38a806413..aaf2758241 100644
--- a/docs/features/techdocs/using-cloud-storage.md
+++ b/docs/features/techdocs/using-cloud-storage.md
@@ -230,7 +230,7 @@ If you are deploying Backstage to Amazon EC2, Amazon ECS, or Amazon EKS, you do
not need to obtain the access keys separately. They can be made available in the
environment automatically by defining appropriate IAM role with access to the
bucket. Read more in the
-[official AWS documentation for using IAM roles.](https://docs.aws.amazon.com/general/latest/gr/aws-access-keys-best-practices.html#use-roles).
+[official AWS documentation for using IAM roles](https://docs.aws.amazon.com/general/latest/gr/aws-access-keys-best-practices.html#use-roles).
**4b. Authentication using app-config.yaml**
diff --git a/docs/getting-started/configuration.md b/docs/getting-started/configuration.md
index 0441d3f589..a436321fe2 100644
--- a/docs/getting-started/configuration.md
+++ b/docs/getting-started/configuration.md
@@ -136,9 +136,7 @@ frontend, in our tutorial it would be `http://localhost:3000`. The
`Authorization callback URL` will point to the auth backend, which will most
likely be `http://localhost:7007/api/auth/github/handler/frame`.
-
-
-
+
Take note of the `Client ID` and the `Client Secret`. Open `app-config.yaml`,
and add your `clientId` and `clientSecret` to this file. It should end up
@@ -214,9 +212,7 @@ name to identify this token and put it in the notes field. Choose a number of
days for expiration. If you have a hard time picking a number, we suggest to go
for 7 days, it's a lucky number.
-
-
-
+
Set the scope to your likings. For this tutorial, selecting `repo` and `workflow` is required as the scaffolding job in this guide configures a GitHub actions workflow for the newly created project.
@@ -262,21 +258,18 @@ otherwise something went terribly wrong.
- Register a new component, by going to `create` and choose
`Register existing component`
-
-
-
+
+
+ 
- As URL use `https://github.com/backstage/demo/blob/master/catalog-info.yaml`.
This is used by our [demo site](https://demo.backstage.io).
-
-
-
-- Hit `Analyze` and review the changes. Apply them if correct
+ 
-
-
-
+- Hit `Analyze` and review the changes. Apply them if correct.
+
+ 
- You should receive a message that your entities have been added.
- If you go back to `Home`, you should be able to find `demo`. You should be
@@ -286,14 +279,12 @@ otherwise something went terribly wrong.
- Go to `create` and choose to create a website with the `Example Node.js Template`
- Type in a name, let's use `tutorial` and click `Next Step`
-
-
-
+
+
- You should see the following screen:
-
-
-
+
+
- For host, it should default to github.com
- As owner, type your GitHub username
@@ -308,7 +299,7 @@ Achievement unlocked. You've set up an installation of the core Backstage App,
made it persistent, and configured it so you are now able to use software
templates.
-Let us know how your experience was: [on discord](https://discord.gg/EBHEGzX),
+Let us know how your experience was: [on discord](https://discord.gg/backstage-687207715902193673),
file issues for any
[feature](https://github.com/backstage/backstage/issues/new?labels=help+wanted&template=feature_template.md)
or
diff --git a/docs/getting-started/configure-app-with-plugins.md b/docs/getting-started/configure-app-with-plugins.md
index 1bfabc335c..835da0cd49 100644
--- a/docs/getting-started/configure-app-with-plugins.md
+++ b/docs/getting-started/configure-app-with-plugins.md
@@ -12,56 +12,64 @@ infrastructure needs - CI/CD, monitoring, auditing, and more.
The following steps assume that you have
[created a Backstage app](./create-an-app.md) and want to add an existing plugin
-to it. We are using the
+to it.
+
+We are using the
[CircleCI](https://github.com/backstage/backstage/blob/master/plugins/circleci/README.md)
-plugin in this example.
+plugin in this example, which is designed to show CI/CD pipeline information attached
+to an entity in the software catalog.
1. Add the plugin's npm package to the repo:
-```bash
-yarn workspace app add @backstage/plugin-circleci
-```
+ ```bash
+ # From your Backstage root directory
+ yarn add --cwd packages/app @backstage/plugin-circleci
+ ```
-Note the plugin is added to the `app` package, rather than the root
-package.json. Backstage Apps are set up as monorepos with
-[yarn workspaces](https://classic.yarnpkg.com/en/docs/workspaces/). Since
-CircleCI is a frontend UI plugin, it goes in `app` rather than `backend`.
+ Note the plugin is added to the `app` package, rather than the root
+ `package.json`. Backstage Apps are set up as monorepos with
+ [Yarn workspaces](https://classic.yarnpkg.com/en/docs/workspaces/). Since
+ CircleCI is a frontend UI plugin, it goes in `app` rather than `backend`.
2. Add the `EntityCircleCIContent` extension to the entity pages in the app:
-```diff
- // packages/app/src/components/catalog/EntityPage.tsx
-+import {
-+ EntityCircleCIContent,
-+ isCircleCIAvailable,
-+} from '@backstage/plugin-circleci';
+ ```diff
+ // packages/app/src/components/catalog/EntityPage.tsx
+ +import {
+ + EntityCircleCIContent,
+ + isCircleCIAvailable,
+ +} from '@backstage/plugin-circleci';
-...
- const cicdContent = (
-
- ...
-+
-+
-+ ;
-
- );
-```
+ ...
+ const cicdContent = (
+
+ ...
+ +
+ +
+ + ;
+
+ );
+ ```
-This is just one example, but each Backstage instance may integrate content or
-cards to suit their needs on different pages, tabs, etc. Note that stand-alone
-plugins that are not "attached" to the Software Catalog would be added outside
-the `EntityPage`.
+ This is just one example, but each Backstage instance may integrate content or
+ cards to suit their needs on different pages, tabs, etc. In addition, while some
+ plugins such as this example are designed to annotate or support specific software
+ catalog entities, others may be intended to be used in a stand-alone fashion and
+ would be added outside the `EntityPage`, such as being added to the main navigation.
-4. [Optional] Add proxy config:
+3. _[Optional]_ Add a proxy config:
-```yaml
-// app-config.yaml
-proxy:
- '/circleci/api':
- target: https://circleci.com/api/v1.1
- headers:
- Circle-Token: ${CIRCLECI_AUTH_TOKEN}
-```
+ Plugins that collect data off of external services may require the use of a proxy service.
+ This plugin accesses the CircleCI REST API, and thus requires a proxy definition.
+
+ ```yaml
+ // app-config.yaml
+ proxy:
+ '/circleci/api':
+ target: https://circleci.com/api/v1.1
+ headers:
+ Circle-Token: ${CIRCLECI_AUTH_TOKEN}
+ ```
### Adding a plugin page to the Sidebar
diff --git a/docs/getting-started/create-an-app.md b/docs/getting-started/create-an-app.md
index 05c12ffb75..535bc848fc 100644
--- a/docs/getting-started/create-an-app.md
+++ b/docs/getting-started/create-an-app.md
@@ -34,9 +34,7 @@ npx @backstage/create-app
This will create a new Backstage App inside the current folder. The name of the
app-folder is the name that was provided when prompted.
-
-
-
+
Inside that directory, it will generate all the files and folder structure
needed for you to run your app.
diff --git a/docs/getting-started/homepage.md b/docs/getting-started/homepage.md
index 7db41d7768..9fa91e694f 100644
--- a/docs/getting-started/homepage.md
+++ b/docs/getting-started/homepage.md
@@ -82,16 +82,9 @@ const routes = (
Let's update the route for "Home" in the Backstage sidebar to point to the new homepage. We'll also add a Sidebar item to quickly open Catalog.
-
-
-
-
-
-
-
Before
-
After
-
-
+| Before | After |
+| --------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
+|  |  |
The code for the Backstage sidebar is most likely inside your [`packages/app/src/components/Root/Root.tsx`](https://github.com/backstage/backstage/blob/master/packages/app/src/components/Root/Root.tsx).
@@ -121,7 +114,9 @@ export const Root = ({ children }: PropsWithChildren<{}>) => (
That's it! You should now have _(although slightly boring)_ a homepage!
-
+
+
+
In the next steps, we will make it interesting and useful!
diff --git a/docs/getting-started/index.md b/docs/getting-started/index.md
index 962919015b..2617cfb654 100644
--- a/docs/getting-started/index.md
+++ b/docs/getting-started/index.md
@@ -54,9 +54,7 @@ npx @backstage/create-app@latest
The wizard will ask you for the name of the app, which will also be the name of the directory
-
-
-
+
### Run the Backstage app
@@ -69,9 +67,7 @@ cd my-backstage-app
yarn dev
```
-
-
-
+
It might take a little while, but as soon as the message
`[0] webpack compiled successfully` appears, you can open a browser and directly
@@ -80,16 +76,14 @@ You can start exploring the demo immediately. Please note that the in-memory
database will be cleared when you restart the app, so you'll most likely want to
carry on with the database steps.
-
-
-
+
In the next part of this tutorial, you'll learn how to change to a persistent
database, configure authentication, and add your first integration. Continue
with [getting started: Configuring Backstage](configuration.md).
Share your experiences, comments, or suggestions with us:
-[on discord](https://discord.gg/EBHEGzX), file issues for any
+[on discord](https://discord.gg/backstage-687207715902193673), file issues for any
[feature](https://github.com/backstage/backstage/issues/new?labels=help+wanted&template=feature_template.md)
or
[plugin suggestions](https://github.com/backstage/backstage/issues/new?labels=plugin&template=plugin_template.md&title=%5BPlugin%5D+THE+PLUGIN+NAME),
diff --git a/docs/getting-started/running-backstage-locally.md b/docs/getting-started/running-backstage-locally.md
index e7e96316f4..d56bdf7990 100644
--- a/docs/getting-started/running-backstage-locally.md
+++ b/docs/getting-started/running-backstage-locally.md
@@ -86,7 +86,7 @@ option will be GitHub. To setup GitHub authentication in Backstage, see
---
Congratulations! That should be it. Let us know how it went
-[on discord](https://discord.gg/EBHEGzX), file issues for any
+[on discord](https://discord.gg/backstage-687207715902193673), file issues for any
[feature](https://github.com/backstage/backstage/issues/new?labels=help+wanted&template=feature_template.md)
or
[plugin suggestions](https://github.com/backstage/backstage/issues/new?labels=plugin&template=plugin_template.md&title=%5BPlugin%5D+THE+PLUGIN+NAME),
diff --git a/docs/integrations/aws-s3/discovery.md b/docs/integrations/aws-s3/discovery.md
index 748aeb2392..4a2b953bc9 100644
--- a/docs/integrations/aws-s3/discovery.md
+++ b/docs/integrations/aws-s3/discovery.md
@@ -63,7 +63,7 @@ As this provider is not one of the default providers, you will first need to ins
the AWS catalog plugin:
```bash
-# From the Backstage root directory
+# From your Backstage root directory
yarn add --cwd packages/backend @backstage/plugin-catalog-backend-module-aws
```
diff --git a/docs/integrations/azure/discovery.md b/docs/integrations/azure/discovery.md
index 2156f60814..6cb135900e 100644
--- a/docs/integrations/azure/discovery.md
+++ b/docs/integrations/azure/discovery.md
@@ -47,6 +47,11 @@ catalog:
frequency: { minutes: 30 }
# supports ISO duration, "human duration" as used in code
timeout: { minutes: 3 }
+ yourSecondProviderId: # identifies your dataset / provider independent of config changes
+ organization: myorg
+ project: '*' # this will match all projects
+ repository: '*' # this will match all repos
+ path: /catalog-info.yaml
anotherProviderId: # another identifier
organization: myorg
project: myproject
@@ -62,7 +67,7 @@ The parameters available are:
- **`host:`** _(optional)_ Leave empty for Cloud hosted, otherwise set to your self-hosted instance host.
- **`organization:`** Your Organization slug (or Collection for on-premise users). Required.
-- **`project:`** Your project slug. Required.
+- **`project:`** _(optional)_ Your project slug. Wildcards are supported as show on the examples above. If not set, all projects will be searched. For a project name containing spaces, use both single and double quotes as in `project: '"My Project Name"'`.
- **`repository:`** _(optional)_ The repository name. Wildcards are supported as show on the examples above. If not set, all repositories will be searched.
- **`path:`** _(optional)_ Where to find catalog-info.yaml files. Defaults to /catalog-info.yaml.
- **`schedule`** _(optional)_:
@@ -144,7 +149,7 @@ When using a custom pattern, the target is composed of five parts:
- The base instance URL, `https://dev.azure.com` in this case
- The organization name which is required, `myorg` in this case
-- The project name which is required, `myproject` in this case
+- The project name which is optional, `myproject` in this case. This defaults to \*, which scans all the projects where the token has access to.
- The repository blob to scan, which accepts \* wildcard tokens and must be
added after `_git/`. This can simply be `*` to scan all repositories in the
project.
diff --git a/docs/integrations/azure/locations.md b/docs/integrations/azure/locations.md
index 3b3e3dd5ed..1fc476f418 100644
--- a/docs/integrations/azure/locations.md
+++ b/docs/integrations/azure/locations.md
@@ -28,3 +28,5 @@ The configuration is a structure with two elements:
- `host`: The DevOps host; only `dev.azure.com` is supported.
- `token` (optional): A personal access token as expected by Azure DevOps.
+
+> Note: The token should just be provided as the raw token generated by Azure DevOps using the format `raw_token` with no base64 encoding. Formatting and base64'ing is handled by dependent libraries handling the Azure DevOps API
diff --git a/docs/integrations/gerrit/discovery.md b/docs/integrations/gerrit/discovery.md
index 7ed05d2b3b..5d822effb5 100644
--- a/docs/integrations/gerrit/discovery.md
+++ b/docs/integrations/gerrit/discovery.md
@@ -17,7 +17,7 @@ As this provider is not one of the default providers, you will first need to ins
the Gerrit provider plugin:
```bash
-# From the Backstage root directory
+# From your Backstage root directory
yarn add --cwd packages/backend @backstage/plugin-catalog-backend-module-gerrit
```
diff --git a/docs/integrations/gitlab/discovery.md b/docs/integrations/gitlab/discovery.md
index dc0b911b3d..4c448d3892 100644
--- a/docs/integrations/gitlab/discovery.md
+++ b/docs/integrations/gitlab/discovery.md
@@ -21,10 +21,10 @@ catalog:
gitlab:
yourProviderId:
host: gitlab-host # Identifies one of the hosts set up in the integrations
- branch: main # Optional. Uses `master` as default
- group: example-group # Optional. Group and subgroup (if needed) to look for repositories. If not present the whole project will be scanned
+ fallbackBranch: main # Optional. Fallback to be used if there is no default branch configured at the Gitlab repository. It is only used, if `branch` is undefined. Uses `master` as default
+ group: example-group # Optional. Group and subgroup (if needed) to look for repositories. If not present the whole instance will be scanned
entityFilename: catalog-info.yaml # Optional. Defaults to `catalog-info.yaml`
- projectPattern: /[\s\S]*/ # Optional. Filters found projects based on provided patter. Defaults to `/[\s\S]*/`, what means to not filter anything
+ projectPattern: '[\s\S]*' # Optional. Filters found projects based on provided patter. Defaults to `[\s\S]*`, which means to not filter anything
schedule: # optional; same options as in TaskScheduleDefinition
# supports cron, ISO duration, "human duration" as used in code
frequency: { minutes: 30 }
@@ -36,7 +36,7 @@ As this provider is not one of the default providers, you will first need to ins
the gitlab catalog plugin:
```bash
-# From the Backstage root directory
+# From your Backstage root directory
yarn add --cwd packages/backend @backstage/plugin-catalog-backend-module-gitlab
```
diff --git a/docs/integrations/gitlab/org.md b/docs/integrations/gitlab/org.md
new file mode 100644
index 0000000000..2ab72b4bd6
--- /dev/null
+++ b/docs/integrations/gitlab/org.md
@@ -0,0 +1,34 @@
+---
+id: org
+title: GitLab Organizational Data
+sidebar_label: Org Data
+description: Importing users and groups from a GitLab organization into Backstage
+---
+
+The Backstage catalog can be set up to ingest organizational data - users and
+teams - directly from an organization in GitLab. The result
+is a hierarchy of
+[`User`](../../features/software-catalog/descriptor-format.md#kind-user) and
+[`Group`](../../features/software-catalog/descriptor-format.md#kind-group) kind
+entities that mirror your org setup.
+
+```yaml
+integrations:
+ gitlab:
+ - host: gitlab.com
+ token: ${GITLAB_TOKEN}
+```
+
+This will query all users and groups from your gitlab installation. Depending on the size
+of the Gitlab Instance, this can take some time and resources.
+
+The token that is used for the Organization Integration, has to be an Admin Personal Access Token (PAT).
+
+```yaml
+catalog:
+ providers:
+ gitlab:
+ yourProviderId:
+ host: gitlab.com
+ orgEnabled: true
+```
diff --git a/docs/integrations/ldap/org.md b/docs/integrations/ldap/org.md
index dbd3f4ff43..f8b9a18c55 100644
--- a/docs/integrations/ldap/org.md
+++ b/docs/integrations/ldap/org.md
@@ -37,7 +37,6 @@ schedule it:
```diff
// packages/backend/src/plugins/catalog.ts
-+import { Duration } from 'luxon';
+import { LdapOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-ldap';
export default async function createPlugin(
diff --git a/docs/local-dev/cli-build-system.md b/docs/local-dev/cli-build-system.md
index cc364d0ca5..2eb695e3a3 100644
--- a/docs/local-dev/cli-build-system.md
+++ b/docs/local-dev/cli-build-system.md
@@ -556,7 +556,22 @@ The overrides in a single `package.json` may for example look like this:
},
```
-If you want to configure editor integration for tests we recommend executing the bundled configuration directly with Jest rather than running through the Yarn test script. For example, with the Jest extension for VS Code the configuration would look something like this:
+### Debugging Jest Tests
+
+For your productivity working with unit tests it's quite essential to have your debugging configured in IDE. It will help you to identify the root cause of the issue faster.
+
+#### IntelliJ IDEA
+
+1. Update Jest configuration template by:
+
+- Click on "Edit Configurations" on top panel
+- In the modal dialog click on link "Edit configuration templates..." located in the bottom left corner.
+- In "Jest package" you have to point to relative path of jest module (it will be suggested by IntelliJ), i.e. `~/proj/backstage/node_modules/jest`
+- In "Jest config" point to your jest configuration file, use absolute path for that, i.e. `--config /Users/user/proj/backstage/packages/cli/config/jest.js --runInBand`
+
+2. Now you can run any tests by clicking on green arrow located on `describe` or `it`.
+
+#### VS Code
```jsonc
{
@@ -571,7 +586,7 @@ If you want to configure editor integration for tests we recommend executing the
}
```
-If you also want to enable source maps when debugging tests, you can do so by setting the `ENABLE_SOURCE_MAPS` environment variable. For example, a complete launch configuration for VS Code debugging may look like this:
+A complete launch configuration for VS Code debugging may look like this:
```json
{
@@ -583,9 +598,6 @@ If you also want to enable source maps when debugging tests, you can do so by se
"disableOptimisticBPs": true,
"program": "${workspaceFolder}/node_modules/.bin/jest",
"cwd": "${workspaceFolder}",
- "env": {
- "ENABLE_SOURCE_MAPS": "true"
- },
"args": [
"--config",
"node_modules/@backstage/cli/config/jest.js",
@@ -638,8 +650,34 @@ The following is an excerpt of a typical setup of an isomorphic library package:
"files": ["dist"],
```
+## Subpath Exports
+
+The Backstage CLI supports implementation of subpath exports through the `"exports"` field in `package.json`. It might for example look like this:
+
+```json
+ "name": "@backstage/plugin-foo",
+ "exports": {
+ ".": "./src/index.ts",
+ "./components": "./src/components.ts",
+ },
+```
+
+This in turn would allow you to import anything exported in `src/index.ts` via `@backstage/plugins-foo`, and `src/components.ts` via `@backstage/plugins-foo/components`. Note that patterns are not supported, meaning the exports may not contain `*` wildcards.
+
+As with the rest of the Backstage CLI build system, the setup is optimized for local development, which is why the `"exports"` targets point directly to source files. The `package build` command will detect the `"exports"` field and automatically generate the corresponding `dist` files, and the `prepublish` command will rewrite the `"exports"` field to point to the `dist` files, as well as generating folder-based entry points for backwards compatibility.
+
+TypeScript support is currently handled though the `typesVersions` field, as there is not yet a module resolution mode that works well with `"exports"`. You can craft the `typesVersions` yourself, but it will also be automatically generated by the `migrate package-exports` command.
+
+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
+```
+
## Experimental Type Build
+> Note: Experimental type builds are deprecated and will be removed in the future. They have been replaced by [subpath exports](#subpath-exports).
+
The Backstage CLI has an experimental feature where multiple different type definition files can be generated for different release stages. The release stages are marked in the [TSDoc](https://tsdoc.org/) for each individual export, using either `@public`, `@alpha`, or `@beta`. Rather than just building a single `index.d.ts` file, the build process will instead output `index.d.ts`, `index.beta.d.ts`, and `index.alpha.d.ts`. Each of these files will have exports from more unstable release stages stripped, meaning that `index.d.ts` will omit all exports marked with `@alpha` or `@beta`, while `index.beta.d.ts` will omit all exports marked with `@alpha`.
This feature is aimed at projects that publish to package registries and wish to maintain different levels of API stability within each package. There is no need to use this within a single monorepo, as it has no effect due to only applying to built and published packages.
diff --git a/docs/local-dev/cli-commands.md b/docs/local-dev/cli-commands.md
index b43825d733..174fb3e348 100644
--- a/docs/local-dev/cli-commands.md
+++ b/docs/local-dev/cli-commands.md
@@ -120,7 +120,6 @@ Build a package for production deployment or publishing
Options:
--role <name> Run the command with an explicit package role
--minify Minify the generated code. Does not apply to app or backend packages.
- --experimental-type-build Enable experimental type build. Does not apply to app or backend packages.
--skip-build-dependencies Skip the automatic building of local dependencies. Applies to backend packages only.
--stats If bundle stats are available, write them to the output directory. Applies to app packages only.
--config <path> Config files to load instead of app-config.yaml. Applies to app packages only. (default: [])
@@ -355,7 +354,7 @@ package. This essentially calls `yarn pack` in each included package and unpacks
the resulting archive in the target `workspace-dir`.
```text
-Usage: backstage-cli build-workspace [options] <workspace-dir>
+Usage: backstage-cli build-workspace [options]
```
## create-github-app
@@ -368,7 +367,7 @@ Launches a browser to create the App through GitHub and saves the result as a
YAML file that can be referenced in the GitHub integration configuration.
```text
-Usage: backstage-cli create-github-app <github-org>
+Usage: backstage-cli create-github-app
```
## info
diff --git a/docs/openapi/definitions/auth.yaml b/docs/openapi/definitions/auth.yaml
index 863544c71a..1ed649da9c 100644
--- a/docs/openapi/definitions/auth.yaml
+++ b/docs/openapi/definitions/auth.yaml
@@ -19,7 +19,7 @@ info:
version: 0.1.1-alpha.8
externalDocs:
description: Backstage official documentation
- url: https://github.com/backstage/backstage/blob/master/docs/README.md
+ url: https://backstage.io/docs
servers:
- url: http://localhost:7007/api/auth/
tags:
diff --git a/docs/overview/roadmap.md b/docs/overview/roadmap.md
index 2ac211bf97..8e3d73ded6 100644
--- a/docs/overview/roadmap.md
+++ b/docs/overview/roadmap.md
@@ -111,4 +111,4 @@ guidelines to get started.
If you have specific questions about the roadmap, please create an
[issue](https://github.com/backstage/backstage/issues/new/choose), ping us on
-[Discord](https://discord.gg/qxsEfa8Vq8), or [book time](https://info.backstage.spotify.com/office-hours) with the Spotify team.
+[Discord](https://discord.gg/backstage-687207715902193673), or [book time](https://info.backstage.spotify.com/office-hours) with the Spotify team.
diff --git a/docs/overview/support.md b/docs/overview/support.md
index 662a37c6ae..a0444c4d34 100644
--- a/docs/overview/support.md
+++ b/docs/overview/support.md
@@ -4,7 +4,7 @@ title: Support and community
description: Support and Community Details and Links
---
-- [Discord chatroom](https://discord.gg/MUpMjP2) - Get support or discuss the
+- [Discord chatroom](https://discord.gg/backstage-687207715902193673) - Get support or discuss the
project.
- [Stack Overflow](https://stackoverflow.com/questions/tagged/backstage) - Browse or ask questions on Stack Overflow.
- [Good First Issues](https://github.com/backstage/backstage/contribute) - Start
diff --git a/docs/overview/threat-model.md b/docs/overview/threat-model.md
index 57fafd1c28..a6435f8cc5 100644
--- a/docs/overview/threat-model.md
+++ b/docs/overview/threat-model.md
@@ -28,6 +28,8 @@ Other responsibilities include protecting the integrity of configuration files a
The integrator is ultimately responsible for auditing usage of internal and external plugins as these run on the host system and have access to configuration and secrets. When installing plugins from sources like NPM, you should vet these in the same way that you would vet any other package installed from that source.
+The integrator is also responsible for maintaining the resolved NPM dependencies of their Backstage project. This involves ensuring that `yarn.lock` receives updated versions of packages that have vulnerabilities, when those fixed versions are in range of what the Backstage packages request in their respective `package.json` files. This is commonly done by employing automated tooling such as [Dependabot](https://dependabot.com/), [Snyk](https://snyk.io/), and/or [Renovate](https://docs.renovatebot.com/) on your own repository. When fixed versions exist that are _not_ in range of what Backstage packages request, or when larger operations such as switching out an entire dependency for another one is required, maintainers collaborate with contributors to try to address those dependency declarations in the main project as soon as possible.
+
## Common Backend Configuration
There are many common facilities that are configured centrally and available to all Backstage backend plugins. For example there is a `DatabaseManager` that provides access to a SQL database, `TaskScheduler` for scheduling long-running tasks, `Logger` as a general logging facility, and `UrlReader` for reading content from external sources. These are all configured either directly in code, or within the `backend` block of the static configuration. The appropriate care needs to be taken to ensure that any secrets remain confidential and no malicious configuration is injected.
diff --git a/docs/overview/what-is-backstage.md b/docs/overview/what-is-backstage.md
index 38b67e81de..f18993770a 100644
--- a/docs/overview/what-is-backstage.md
+++ b/docs/overview/what-is-backstage.md
@@ -5,7 +5,7 @@ title: What is Backstage?
description: Backstage is an open platform for building developer portals. Powered by a centralized software catalog, Backstage restores order to your microservices and infrastructure
---
-
+
[Backstage](https://backstage.io/) is an open platform for building developer
portals. Powered by a centralized software catalog, Backstage restores order to
@@ -61,4 +61,4 @@ Backstage is a CNCF Incubation project after graduating from Sandbox. Read the a
administrators together in one place.
If you have questions or want support, please join our
-[Discord chatroom](https://discord.gg/EBHEGzX).
+[Discord chatroom](https://discord.gg/backstage-687207715902193673).
diff --git a/docs/permissions/getting-started.md b/docs/permissions/getting-started.md
index d2c467ff08..e71601b703 100644
--- a/docs/permissions/getting-started.md
+++ b/docs/permissions/getting-started.md
@@ -47,7 +47,8 @@ The permissions framework uses a new `permission-backend` plugin to accept autho
1. Add `@backstage/plugin-permission-backend` as a dependency of your Backstage backend:
```bash
-$ yarn workspace backend add @backstage/plugin-permission-backend
+# From your Backstage root directory
+$ yarn add --cwd packages/backend @backstage/plugin-permission-backend
```
2. Add the following to a new file, `packages/backend/src/plugins/permission.ts`. This adds the permission-backend router, and configures it with a policy which allows everything.
diff --git a/docs/permissions/overview.md b/docs/permissions/overview.md
index b972814aeb..693f848c82 100644
--- a/docs/permissions/overview.md
+++ b/docs/permissions/overview.md
@@ -34,6 +34,8 @@ The permission framework was designed with a few key properties in mind:
4. An authorization decision is sent to the plugin from the permission backend.
+5. The user is either granted access or an error is shown. The plugin is responsible for implementing a response to the user.
+
## How do I get started?
See the "[getting started](./getting-started.md)" permission documentation for Backstage integrators.
diff --git a/docs/permissions/plugin-authors/01-setup.md b/docs/permissions/plugin-authors/01-setup.md
index 18de410612..c368505849 100644
--- a/docs/permissions/plugin-authors/01-setup.md
+++ b/docs/permissions/plugin-authors/01-setup.md
@@ -26,6 +26,9 @@ The source code is available here:
git checkout master -- plugins/example-todo-list/
git checkout master -- plugins/example-todo-list-backend/
git checkout master -- plugins/example-todo-list-common/
+ sed -i '' 's/workspace:\^/\*/g' plugins/example-todo-list/package.json
+ sed -i '' 's/workspace:\^/\*/g' plugins/example-todo-list-backend/package.json
+ sed -i '' 's/workspace:\^/\*/g' plugins/example-todo-list-common/package.json
for file in plugins/*; do mv "$file" "$OLDPWD/${file/example-todo/todo}"; done
cd -
```
@@ -36,16 +39,17 @@ The source code is available here:
2. Add these packages as dependencies for your Backstage app:
- ```
- $ yarn workspace backend add @internal/plugin-todo-list-backend@^1.0.0 @internal/plugin-todo-list-common@^1.0.0
- $ yarn workspace app add @internal/plugin-todo-list@^1.0.0
+ ```sh
+ # From your Backstage root directory
+ $ yarn add --cwd packages/backend @internal/plugin-todo-list-backend @internal/plugin-todo-list-common
+ $ yarn add --cwd packages/app @internal/plugin-todo-list
```
3. Include the backend and frontend plugin in your application:
Create a new `packages/backend/src/plugins/todolist.ts` with the following content:
- ```javascript
+ ```typescript
import { DefaultIdentityClient } from '@backstage/plugin-auth-node';
import { createRouter } from '@internal/plugin-todo-list-backend';
import { Router } from 'express';
diff --git a/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md b/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md
index 241b175987..44a3e43718 100644
--- a/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md
+++ b/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md
@@ -100,7 +100,7 @@ Edit `plugins/todo-list-backend/src/service/router.ts`:
Pass the `permissions` object to the plugin in `packages/backend/src/plugins/todolist.ts`:
```diff
- import { IdentityClient } from '@backstage/plugin-auth-backend';
+ import { DefaultIdentityClient } from '@backstage/plugin-auth-node';
import { createRouter } from '@internal/plugin-todo-list-backend';
import { Router } from 'express';
import { PluginEnvironment } from '../types';
@@ -112,7 +112,7 @@ Pass the `permissions` object to the plugin in `packages/backend/src/plugins/tod
}: PluginEnvironment): Promise {
return await createRouter({
logger,
- identity: new IdentityClient({
+ identity: DefaultIdentityClient.create({
discovery,
issuer: await discovery.getExternalBaseUrl('auth'),
}),
@@ -146,7 +146,7 @@ In order to test the logic above, the integrators of your backstage instance nee
- async handle(): Promise {
+ async handle(
+ request: PolicyQuery,
-+ user?: BackstageIdentityResponse,
++ _user?: BackstageIdentityResponse,
+ ): Promise {
+ if (isPermission(request.permission, todoListCreatePermission)) {
+ return {
@@ -204,7 +204,7 @@ First we'll clean up the `plugins/todo-list-backend/src/service/router.test.ts`:
const router = await createRouter({
logger: getVoidLogger(),
identity: {} as DefaultIdentityClient,
-+ permissions: toPermissionEvaluator,
++ permissions: permissionEvaluator,
});
app = express().use(router);
});
@@ -235,7 +235,7 @@ Then we want to update the `plugins/todo-list-backend/src/service/standaloneServ
+ ServerTokenManager,
} from '@backstage/backend-common';
import { DefaultIdentityClient } from '@backstage/plugin-auth-node';
- import { ServerPermissionClient } from '@backstage/plugin-permission-node';
++ import { ServerPermissionClient } from '@backstage/plugin-permission-node';
import { Server } from 'http';
import { Logger } from 'winston';
import { createRouter } from './router';
@@ -285,4 +285,44 @@ Then we want to update the `plugins/todo-list-backend/src/service/standaloneServ
module.hot?.accept();
```
+Finally, we need to update `plugins/todo-list-backend/src/plugin.ts`:
+
+```diff
+ import { loggerToWinstonLogger } from '@backstage/backend-common';
+ import {
+ coreServices,
+ createBackendPlugin,
+ } from '@backstage/backend-plugin-api';
+ import { createRouter } from './service/router';
+
+ /**
+ * The example TODO list backend plugin.
+ *
+ * @alpha
+ */
+ export const exampleTodoListPlugin = createBackendPlugin({
+ pluginId: 'exampleTodoList',
+ register(env) {
+ env.registerInit({
+ deps: {
+ identity: coreServices.identity,
+ logger: coreServices.logger,
+ httpRouter: coreServices.httpRouter,
++ permissions: coreServices.permissions,
+ },
+- async init({ identity, logger, httpRouter }) {
++ async init({ identity, logger, httpRouter, permissions }) {
+ httpRouter.use(
+ await createRouter({
+ identity,
+ logger: loggerToWinstonLogger(logger),
+ permissions,
+ }),
+ );
+ },
+ });
+ },
+ });
+```
+
Now when you run `yarn tsc` you should have no more errors.
diff --git a/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md b/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md
index 8913b6d438..b063727c71 100644
--- a/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md
+++ b/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md
@@ -76,7 +76,7 @@ This enables decisions based on characteristics of the resource, but it's import
Install the missing module:
```
-$ yarn workspace @internal/plugin-todo-list-backend add @backstage/plugin-permission-node
+$ yarn workspace @internal/plugin-todo-list-backend add @backstage/plugin-permission-node zod
```
Create a new `plugins/todo-list-backend/src/service/rules.ts` file and append the following code:
@@ -89,7 +89,8 @@ import { Todo, TodoFilter } from './todos';
export const createTodoListPermissionRule = makeCreatePermissionRule<
Todo,
- TodoFilter
+ TodoFilter,
+ typeof TODO_LIST_RESOURCE_TYPE
>();
export const isOwner = createTodoListPermissionRule({
@@ -97,8 +98,8 @@ export const isOwner = createTodoListPermissionRule({
description: 'Should allow only if the todo belongs to the user',
resourceType: TODO_LIST_RESOURCE_TYPE,
paramsSchema: z.object({
- userId: z.string().describe('User ID to match on the resource')
- })
+ userId: z.string().describe('User ID to match on the resource'),
+ }),
apply: (resource: Todo, { userId }) => {
return resource.author === userId;
},
@@ -187,6 +188,7 @@ Make sure `todoListConditions` and `createTodoListConditionalDecision` are expor
```diff
export * from './service/router';
+ export * from './conditionExports';
+ export { exampleTodoListPlugin } from './plugin';
```
## Test the authorized update endpoint
@@ -209,7 +211,6 @@ Let's go back to the permission policy's handle function and try to authorize ou
+ import {
+ todoListCreatePermission,
+ todoListUpdatePermission,
-+ TODO_LIST_RESOURCE_TYPE,
+ } from '@internal/plugin-todo-list-common';
+ import {
+ todoListConditions,
@@ -217,7 +218,11 @@ Let's go back to the permission policy's handle function and try to authorize ou
+ } from '@internal/plugin-todo-list-backend';
...
-
+ async handle(
+ request: PolicyQuery,
+- _user?: BackstageIdentityResponse,
++ user?: BackstageIdentityResponse,
+ ): Promise {
if (isPermission(request.permission, todoListCreatePermission)) {
return {
result: AuthorizeResult.ALLOW,
@@ -236,6 +241,7 @@ Let's go back to the permission policy's handle function and try to authorize ou
return {
result: AuthorizeResult.ALLOW,
};
+ }
```
For any incoming update requests, we now return a _Conditional Decision_. We are saying:
diff --git a/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md b/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md
index 8ddcffdcd9..e84f53f176 100644
--- a/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md
+++ b/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md
@@ -15,7 +15,7 @@ One possible solution may leverage the batching functionality to authorize all o
- res.json(getAll())
+ const items = getAll();
+ const decisions = await permissions.authorize(
-+ items.map(({ id }) => ({ permission: todosListRead, resourceRef: id })),
++ items.map(({ id }) => ({ permission: todoListReadPermission, resourceRef: id })),
+ );
+ const filteredItems = decisions.filter(
@@ -53,7 +53,7 @@ Let's add another permission to the plugin.
resourceType: TODO_LIST_RESOURCE_TYPE,
});
+
-+ export const todosListRead = createPermission({
++ export const todoListReadPermission = createPermission({
+ name: 'todos.list.read',
+ attributes: { action: 'read' },
+ resourceType: TODO_LIST_RESOURCE_TYPE,
@@ -81,10 +81,9 @@ So far we've only used the `PermissionEvaluator.authorize` method, which will ev
import {
todosListCreate,
todosListUpdate,
-+ todosListRead,
++ todoListReadPermission,
TODO_LIST_RESOURCE_TYPE,
} from './permissions';
-+ import { rules } from './rules';
+ const transformConditions: ConditionTransformer = createConditionTransformer(Object.values(rules));
@@ -95,7 +94,7 @@ So far we've only used the `PermissionEvaluator.authorize` method, which will ev
+ );
+
+ const decision = (
-+ await permissions.authorizeConditional([{ permission: todosListRead }], {
++ await permissions.authorizeConditional([{ permission: todoListReadPermission }], {
+ token,
+ })
+ )[0];
@@ -110,7 +109,6 @@ So far we've only used the `PermissionEvaluator.authorize` method, which will ev
+ } else {
+ res.json(getAll());
+ }
-+ }
- res.json(getAll());
});
```
@@ -121,7 +119,7 @@ Since `TodoFilter` used in our plugin matches the structure of the conditions ob
## Test the authorized read endpoint
-Let's update our permission policy to return a conditional result whenever a `todosListRead` permission is received. In this case, we can reuse the decision returned for the `todosListCreate` permission.
+Let's update our permission policy to return a conditional result whenever a `todoListReadPermission` permission is received. In this case, we can reuse the decision returned for the `todosListCreate` permission.
```diff
// packages/backend/src/plugins/permission.ts
@@ -132,7 +130,6 @@ import {
todoListCreatePermission,
todoListUpdatePermission,
+ todoListReadPermission,
- TODO_LIST_RESOURCE_TYPE,
} from '@internal/plugin-todo-list-common';
...
diff --git a/docs/permissions/plugin-authors/05-frontend-authorization.md b/docs/permissions/plugin-authors/05-frontend-authorization.md
index d494ea60ba..753ce56461 100644
--- a/docs/permissions/plugin-authors/05-frontend-authorization.md
+++ b/docs/permissions/plugin-authors/05-frontend-authorization.md
@@ -56,7 +56,11 @@ Let's make the following changes in `plugins/todo-list/src/components/TodoListPa
- Add
-
+ {!loadingPermission && (
-+