diff --git a/.changeset/beige-deers-remember.md b/.changeset/beige-deers-remember.md new file mode 100644 index 0000000000..4fcba4f0c6 --- /dev/null +++ b/.changeset/beige-deers-remember.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-cost-insights': patch +--- + +Add missing `export` in configuration schema. diff --git a/.changeset/breezy-days-prove.md b/.changeset/breezy-days-prove.md deleted file mode 100644 index d3bc3c169e..0000000000 --- a/.changeset/breezy-days-prove.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder': minor ---- - -Add new bitbucket(Cloud/Server) integrations to ScaffolderClient. diff --git a/.changeset/cool-ties-share.md b/.changeset/cool-ties-share.md deleted file mode 100644 index fb76785fcf..0000000000 --- a/.changeset/cool-ties-share.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-common': patch ---- - -Gerrit integration: Added optional `cloneUrl` string to config. diff --git a/.changeset/curly-parrots-applaud.md b/.changeset/curly-parrots-applaud.md deleted file mode 100644 index d9443415eb..0000000000 --- a/.changeset/curly-parrots-applaud.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': minor ---- - -Update the `github:publish` action to allow passing required status check -contexts before merging to the main branch. diff --git a/.changeset/dirty-eagles-hunt.md b/.changeset/dirty-eagles-hunt.md deleted file mode 100644 index d3ad27dfc3..0000000000 --- a/.changeset/dirty-eagles-hunt.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-common': patch ---- - -Split BitbucketUrlReader into BitbucketCloudUrlReader and BitbucketServerUrlReader. Backwards compatible. diff --git a/.changeset/eighty-chicken-retire.md b/.changeset/eighty-chicken-retire.md deleted file mode 100644 index f6d66f5eba..0000000000 --- a/.changeset/eighty-chicken-retire.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-kubernetes-backend': patch ---- - -Add filtering to GKE cluster locator diff --git a/.changeset/empty-mugs-bow.md b/.changeset/empty-mugs-bow.md deleted file mode 100644 index d9d63e7f89..0000000000 --- a/.changeset/empty-mugs-bow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-plugin-api': patch ---- - -The authentication APIs are no longer `@alpha`. Since the `@backstage/core-plugin-api` has no `/alpha` entrypoint, the only effect of marking the APIs as `@alpha` was to hide them in documentation. They are still expected to be widely used and there will be a migration path if they are changed in the future. diff --git a/.changeset/fair-kings-retire.md b/.changeset/fair-kings-retire.md deleted file mode 100644 index 156632f6ae..0000000000 --- a/.changeset/fair-kings-retire.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/integration': minor ---- - -Gerrit UrlReader: Implemented `readTree` diff --git a/.changeset/fluffy-candles-learn.md b/.changeset/fluffy-candles-learn.md new file mode 100644 index 0000000000..6fe3f1e09b --- /dev/null +++ b/.changeset/fluffy-candles-learn.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': patch +--- + +Updates the `isKind`, `ìsComponentType`, and `isNamespace` to allow an array of possible values diff --git a/.changeset/fuzzy-seahorses-teach.md b/.changeset/fuzzy-seahorses-teach.md deleted file mode 100644 index 71ef28dd52..0000000000 --- a/.changeset/fuzzy-seahorses-teach.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Add instructions to `create-github-app` command. diff --git a/.changeset/giant-cheetahs-thank.md b/.changeset/giant-cheetahs-thank.md deleted file mode 100644 index a135fbc4a3..0000000000 --- a/.changeset/giant-cheetahs-thank.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-msgraph': patch ---- - -Add annotation `microsoft.com/email` when using the `defaultUserTransformer`. - -This will allow users of the Microsoft auth provider to utilize the predefined -SignIn resolver instead of maintaining their own. - -```typescript -// backend/plugins/auth.ts - -// [...] - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - return await createRouter({ - // [...] - providerFactories: { - microsoft: providers.microsoft.create({ - signIn: { - resolver: - providers.microsoft.resolvers.emailMatchingUserEntityAnnotation(), - }, - }), - }, - }); -} -``` diff --git a/.changeset/hip-adults-collect.md b/.changeset/hip-adults-collect.md deleted file mode 100644 index 97afdf3da2..0000000000 --- a/.changeset/hip-adults-collect.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': patch ---- - -Fixing broken types for `knex` when checking returned rows diff --git a/.changeset/honest-kids-live.md b/.changeset/honest-kids-live.md deleted file mode 100644 index e829d00d91..0000000000 --- a/.changeset/honest-kids-live.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Optimized the command order in `packages/backend/Dockerfile` as well as added the `--no-install-recommends` to the `apt-get install` and tweaked the installed packages. - -To apply this change to an existing app, update your `packages/backend/Dockerfile` to match the documented `Dockerfile` at https://backstage.io/docs/deployment/docker#host-build. diff --git a/.changeset/hot-peaches-give.md b/.changeset/hot-peaches-give.md deleted file mode 100644 index ac30e8d492..0000000000 --- a/.changeset/hot-peaches-give.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Add package global-agent to support behind a proxy for backstage-cli commands like versions:bump. diff --git a/.changeset/lazy-zebras-pay.md b/.changeset/lazy-zebras-pay.md deleted file mode 100644 index efcb62d7a9..0000000000 --- a/.changeset/lazy-zebras-pay.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Added some instruction comments to the generated config files, to clarify the -usage of `backend.baseUrl` and `backend.listen.host`. Importantly, it also per -default now listens on all IPv4 interfaces, to make it easier to take the step -over to production. If you want to do the same, update your -`app-config.production.yaml` as follows: - -```diff - backend: - listen: - port: 7007 -+ host: 0.0.0.0 -``` - -Also, updated the builtin backend Dockerfile to honor the -`app-config.production.yaml` file. If you want to do the same, change -`packages/backend/Dockerfile` as follows: - -```diff --COPY packages/backend/dist/bundle.tar.gz app-config.yaml ./ -+COPY packages/backend/dist/bundle.tar.gz app-config*.yaml ./ - RUN tar xzf bundle.tar.gz && rm bundle.tar.gz - --CMD ["node", "packages/backend", "--config", "app-config.yaml"] -+CMD ["node", "packages/backend", "--config", "app-config.yaml", "--config", "app-config.production.yaml"] -``` - -If you look carefully, this adds a glob match on app-config files. For those -that try out the build flows locally, you also want to make sure that the docker -daemon does NOT pick up any local/private config files that might contain -secrets. You should therefore also update your local `.dockerignore` file at the -same time: - -```diff -+*.local.yaml -``` diff --git a/.changeset/loud-jars-kick.md b/.changeset/loud-jars-kick.md new file mode 100644 index 0000000000..b53006f7f9 --- /dev/null +++ b/.changeset/loud-jars-kick.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-allure': patch +--- + +Add export for `isAllureReportAvailable` and `ALLURE_PROJECT_ID_ANNOTATION` so it can be used outside of plugin diff --git a/.changeset/mean-flowers-change.md b/.changeset/mean-flowers-change.md deleted file mode 100644 index 7634a20c84..0000000000 --- a/.changeset/mean-flowers-change.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': patch ---- - -Override default commit message and author details in GitHub, Azure, bitbucket diff --git a/.changeset/metal-pants-fly.md b/.changeset/metal-pants-fly.md deleted file mode 100644 index 7db2014377..0000000000 --- a/.changeset/metal-pants-fly.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-home': patch ---- - -star icons now have the same yellow color as the other star icons when a entity is favourite diff --git a/.changeset/moody-laws-boil.md b/.changeset/moody-laws-boil.md deleted file mode 100644 index f481e84044..0000000000 --- a/.changeset/moody-laws-boil.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/plugin-api-docs': patch -'@backstage/plugin-catalog': minor -'@backstage/plugin-catalog-react': minor ---- - -Use InfoCardVariants on custom cards variant attribute diff --git a/.changeset/moody-suns-smell.md b/.changeset/moody-suns-smell.md deleted file mode 100644 index b051335ad7..0000000000 --- a/.changeset/moody-suns-smell.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -'@backstage/integration': minor -'@backstage/integration-react': minor ---- - -Split `bitbucket` integration into `bitbucketCloud` and `bitbucketServer` -(backwards compatible). - -In order to migrate to the new integration configs, -move your configs from `integrations.bitbucket` -to `integrations.bitbucketCloud` or `integrations.bitbucketServer`. - -Migration example: - -**Before:** - -```yaml -integrations: - bitbucket: - - host: bitbucket.org - username: bitbucket_user - appPassword: app-password - - host: bitbucket-server.company.com - token: my-token -``` - -**After:** - -```yaml -integrations: - bitbucketCloud: - - username: bitbucket_user - appPassword: app-password - bitbucketServer: - - host: bitbucket-server.company.com - token: my-token -``` diff --git a/.changeset/nasty-humans-give.md b/.changeset/nasty-humans-give.md deleted file mode 100644 index 85ce3149ba..0000000000 --- a/.changeset/nasty-humans-give.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog': minor ---- - -Added the prop `NotFoundComponent` to `EntityLayout` which can be used to include a custom component when an entity is not found in the catalog diff --git a/.changeset/nervous-gorillas-approve.md b/.changeset/nervous-gorillas-approve.md new file mode 100644 index 0000000000..8c0d742b7b --- /dev/null +++ b/.changeset/nervous-gorillas-approve.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-catalog-backend-module-aws': patch +'@backstage/plugin-catalog-backend-module-gerrit': patch +--- + +Inline config interfaces diff --git a/.changeset/new-beds-argue.md b/.changeset/new-beds-argue.md deleted file mode 100644 index 8aa8acf09d..0000000000 --- a/.changeset/new-beds-argue.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-api-docs': patch ---- - -Bumped @asyncapi/react-component > openapi-sampler > json-pointer to resolve security vulnerability CVE-2021-23820 diff --git a/.changeset/old-bikes-study.md b/.changeset/old-bikes-study.md deleted file mode 100644 index f2452fcce3..0000000000 --- a/.changeset/old-bikes-study.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/integration': minor ---- - -Gerrit Integration: Added the `getGerritProjectsApiUrl` function diff --git a/.changeset/perfect-penguins-rescue.md b/.changeset/perfect-penguins-rescue.md deleted file mode 100644 index ab55355121..0000000000 --- a/.changeset/perfect-penguins-rescue.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-gerrit': minor ---- - -Initial version of the `plugin-catalog-backend-module-gerrit` plugin diff --git a/.changeset/pink-mayflies-rhyme.md b/.changeset/pink-mayflies-rhyme.md deleted file mode 100644 index fff17b28b3..0000000000 --- a/.changeset/pink-mayflies-rhyme.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-newrelic-dashboard': patch ---- - -Add `FetchApi` and related `fetchApiRef` which implement fetch. in order to included the Authorization Token to New Relic Dashboard Plugin, so that frontend can send an authenticated proxy call to backend diff --git a/.changeset/pre.json b/.changeset/pre.json index f598c9f725..aff34f172d 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -2,195 +2,157 @@ "mode": "pre", "tag": "next", "initialVersions": { - "example-app": "0.2.70", - "@backstage/app-defaults": "1.0.1", - "example-backend": "0.2.70", - "@backstage/backend-common": "0.13.2", - "@backstage/backend-tasks": "0.3.0", - "@backstage/backend-test-utils": "0.1.23", - "@backstage/catalog-client": "1.0.1", - "@backstage/catalog-model": "1.0.1", - "@backstage/cli": "0.17.0", - "@backstage/cli-common": "0.1.8", - "@backstage/codemods": "0.1.37", - "@backstage/config": "1.0.0", - "@backstage/config-loader": "1.1.0", - "@backstage/core-app-api": "1.0.1", - "@backstage/core-components": "0.9.3", - "@backstage/core-plugin-api": "1.0.1", - "@backstage/create-app": "0.4.26", - "@backstage/dev-utils": "1.0.1", + "example-app": "0.2.71", + "@backstage/app-defaults": "1.0.2", + "example-backend": "0.2.71", + "@backstage/backend-common": "0.13.3", + "@backstage/backend-tasks": "0.3.1", + "@backstage/backend-test-utils": "0.1.24", + "@backstage/catalog-client": "1.0.2", + "@backstage/catalog-model": "1.0.2", + "@backstage/cli": "0.17.1", + "@backstage/cli-common": "0.1.9", + "@backstage/codemods": "0.1.38", + "@backstage/config": "1.0.1", + "@backstage/config-loader": "1.1.1", + "@backstage/core-app-api": "1.0.2", + "@backstage/core-components": "0.9.4", + "@backstage/core-plugin-api": "1.0.2", + "@backstage/create-app": "0.4.27", + "@backstage/dev-utils": "1.0.2", "e2e-test": "0.2.0", "@backstage/errors": "1.0.0", - "@backstage/integration": "1.1.0", - "@backstage/integration-react": "1.0.1", - "@backstage/release-manifests": "0.0.2", - "@backstage/search-common": "0.3.3", - "@techdocs/cli": "1.1.0", - "techdocs-cli-embedded-app": "0.2.69", - "@backstage/techdocs-common": "0.11.14", - "@backstage/test-utils": "1.0.1", + "@backstage/integration": "1.2.0", + "@backstage/integration-react": "1.1.0", + "@backstage/release-manifests": "0.0.3", + "@backstage/search-common": "0.3.4", + "@techdocs/cli": "1.1.1", + "techdocs-cli-embedded-app": "0.2.70", + "@backstage/techdocs-common": "0.11.15", + "@backstage/test-utils": "1.1.0", "@backstage/theme": "0.2.15", "@backstage/types": "1.0.0", "@backstage/version-bridge": "1.0.1", - "@backstage/plugin-airbrake": "0.3.4", - "@backstage/plugin-airbrake-backend": "0.2.4", - "@backstage/plugin-allure": "0.1.20", - "@backstage/plugin-analytics-module-ga": "0.1.15", - "@backstage/plugin-apache-airflow": "0.1.12", - "@backstage/plugin-api-docs": "0.8.4", - "@backstage/plugin-app-backend": "0.3.31", - "@backstage/plugin-auth-backend": "0.13.0", - "@backstage/plugin-auth-node": "0.2.0", - "@backstage/plugin-azure-devops": "0.1.20", - "@backstage/plugin-azure-devops-backend": "0.3.10", - "@backstage/plugin-azure-devops-common": "0.2.2", - "@backstage/plugin-badges": "0.2.28", - "@backstage/plugin-badges-backend": "0.1.25", - "@backstage/plugin-bazaar": "0.1.19", - "@backstage/plugin-bazaar-backend": "0.1.15", - "@backstage/plugin-bitrise": "0.1.31", - "@backstage/plugin-catalog": "1.1.0", - "@backstage/plugin-catalog-backend": "1.1.0", - "@backstage/plugin-catalog-backend-module-aws": "0.1.4", - "@backstage/plugin-catalog-backend-module-azure": "0.1.2", - "@backstage/plugin-catalog-backend-module-bitbucket": "0.1.2", - "@backstage/plugin-catalog-backend-module-github": "0.1.2", - "@backstage/plugin-catalog-backend-module-gitlab": "0.1.2", - "@backstage/plugin-catalog-backend-module-ldap": "0.4.2", - "@backstage/plugin-catalog-backend-module-msgraph": "0.3.1", - "@backstage/plugin-catalog-common": "1.0.1", - "@backstage/plugin-catalog-graph": "0.2.16", - "@backstage/plugin-catalog-graphql": "0.3.8", - "@backstage/plugin-catalog-import": "0.8.7", - "@backstage/plugin-catalog-react": "1.0.1", - "@backstage/plugin-cicd-statistics": "0.1.6", - "@backstage/plugin-cicd-statistics-module-gitlab": "0.1.0", - "@backstage/plugin-circleci": "0.3.4", - "@backstage/plugin-cloudbuild": "0.3.4", - "@backstage/plugin-code-climate": "0.1.4", - "@backstage/plugin-code-coverage": "0.1.31", - "@backstage/plugin-code-coverage-backend": "0.1.29", - "@backstage/plugin-config-schema": "0.1.27", - "@backstage/plugin-cost-insights": "0.11.26", - "@backstage/plugin-explore": "0.3.35", - "@backstage/plugin-explore-react": "0.0.16", - "@backstage/plugin-firehydrant": "0.1.21", - "@backstage/plugin-fossa": "0.2.36", - "@backstage/plugin-gcalendar": "0.3.0", - "@backstage/plugin-gcp-projects": "0.3.23", - "@backstage/plugin-git-release-manager": "0.3.17", - "@backstage/plugin-github-actions": "0.5.4", - "@backstage/plugin-github-deployments": "0.1.35", - "@backstage/plugin-gitops-profiles": "0.3.22", - "@backstage/plugin-gocd": "0.1.10", - "@backstage/plugin-graphiql": "0.2.36", - "@backstage/plugin-graphql-backend": "0.1.21", - "@backstage/plugin-home": "0.4.20", - "@backstage/plugin-ilert": "0.1.30", - "@backstage/plugin-jenkins": "0.7.3", - "@backstage/plugin-jenkins-backend": "0.1.20", - "@backstage/plugin-jenkins-common": "0.1.3", - "@backstage/plugin-kafka": "0.3.4", - "@backstage/plugin-kafka-backend": "0.2.24", - "@backstage/plugin-kubernetes": "0.6.4", - "@backstage/plugin-kubernetes-backend": "0.5.0", - "@backstage/plugin-kubernetes-common": "0.2.9", - "@backstage/plugin-lighthouse": "0.3.4", - "@backstage/plugin-newrelic": "0.3.22", - "@backstage/plugin-newrelic-dashboard": "0.1.12", - "@backstage/plugin-org": "0.5.4", - "@backstage/plugin-pagerduty": "0.3.31", - "@backstage/plugin-periskop": "0.1.2", - "@backstage/plugin-periskop-backend": "0.1.2", - "@backstage/plugin-permission-backend": "0.5.6", - "@backstage/plugin-permission-common": "0.6.0", - "@backstage/plugin-permission-node": "0.6.0", - "@backstage/plugin-permission-react": "0.4.0", - "@backstage/plugin-proxy-backend": "0.2.25", - "@backstage/plugin-rollbar": "0.4.4", - "@backstage/plugin-rollbar-backend": "0.1.28", - "@backstage/plugin-scaffolder": "1.1.0", - "@backstage/plugin-scaffolder-backend": "1.1.0", - "@backstage/plugin-scaffolder-backend-module-cookiecutter": "0.2.6", - "@backstage/plugin-scaffolder-backend-module-rails": "0.3.6", - "@backstage/plugin-scaffolder-backend-module-yeoman": "0.2.4", - "@backstage/plugin-scaffolder-common": "1.0.1", - "@backstage/plugin-search": "0.8.0", - "@backstage/plugin-search-backend": "0.5.0", - "@backstage/plugin-search-backend-module-elasticsearch": "0.1.3", - "@backstage/plugin-search-backend-module-pg": "0.3.2", - "@backstage/plugin-search-backend-node": "0.6.0", - "@backstage/plugin-search-common": "0.3.3", - "@backstage/plugin-search-react": "0.1.0", - "@backstage/plugin-sentry": "0.3.42", - "@backstage/plugin-shortcuts": "0.2.5", - "@backstage/plugin-sonarqube": "0.3.4", - "@backstage/plugin-splunk-on-call": "0.3.28", - "@backstage/plugin-stack-overflow": "0.1.0", - "@backstage/plugin-stack-overflow-backend": "0.1.0", - "@backstage/plugin-tech-insights": "0.2.0", - "@backstage/plugin-tech-insights-backend": "0.3.0", - "@backstage/plugin-tech-insights-backend-module-jsonfc": "0.1.15", + "@backstage/plugin-adr": "0.1.0", + "@backstage/plugin-adr-backend": "0.1.0", + "@backstage/plugin-adr-common": "0.1.0", + "@backstage/plugin-airbrake": "0.3.5", + "@backstage/plugin-airbrake-backend": "0.2.5", + "@backstage/plugin-allure": "0.1.21", + "@backstage/plugin-analytics-module-ga": "0.1.16", + "@backstage/plugin-apache-airflow": "0.1.13", + "@backstage/plugin-api-docs": "0.8.5", + "@backstage/plugin-app-backend": "0.3.32", + "@backstage/plugin-auth-backend": "0.14.0", + "@backstage/plugin-auth-node": "0.2.1", + "@backstage/plugin-azure-devops": "0.1.21", + "@backstage/plugin-azure-devops-backend": "0.3.11", + "@backstage/plugin-azure-devops-common": "0.2.3", + "@backstage/plugin-badges": "0.2.29", + "@backstage/plugin-badges-backend": "0.1.26", + "@backstage/plugin-bazaar": "0.1.20", + "@backstage/plugin-bazaar-backend": "0.1.16", + "@backstage/plugin-bitrise": "0.1.32", + "@backstage/plugin-catalog": "1.2.0", + "@backstage/plugin-catalog-backend": "1.1.2", + "@backstage/plugin-catalog-backend-module-aws": "0.1.5", + "@backstage/plugin-catalog-backend-module-azure": "0.1.3", + "@backstage/plugin-catalog-backend-module-bitbucket": "0.1.3", + "@backstage/plugin-catalog-backend-module-gerrit": "0.1.0", + "@backstage/plugin-catalog-backend-module-github": "0.1.3", + "@backstage/plugin-catalog-backend-module-gitlab": "0.1.3", + "@backstage/plugin-catalog-backend-module-ldap": "0.4.3", + "@backstage/plugin-catalog-backend-module-msgraph": "0.3.2", + "@backstage/plugin-catalog-common": "1.0.2", + "@backstage/plugin-catalog-graph": "0.2.17", + "@backstage/plugin-catalog-graphql": "0.3.9", + "@backstage/plugin-catalog-import": "0.8.8", + "@backstage/plugin-catalog-react": "1.1.0", + "@backstage/plugin-cicd-statistics": "0.1.7", + "@backstage/plugin-cicd-statistics-module-gitlab": "0.1.1", + "@backstage/plugin-circleci": "0.3.5", + "@backstage/plugin-cloudbuild": "0.3.5", + "@backstage/plugin-code-climate": "0.1.5", + "@backstage/plugin-code-coverage": "0.1.32", + "@backstage/plugin-code-coverage-backend": "0.1.30", + "@backstage/plugin-codescene": "0.1.0", + "@backstage/plugin-config-schema": "0.1.28", + "@backstage/plugin-cost-insights": "0.11.27", + "@internal/plugin-todo-list": "1.0.1", + "@internal/plugin-todo-list-backend": "1.0.1", + "@internal/plugin-todo-list-common": "1.0.1", + "@backstage/plugin-explore": "0.3.36", + "@backstage/plugin-explore-react": "0.0.17", + "@backstage/plugin-firehydrant": "0.1.22", + "@backstage/plugin-fossa": "0.2.37", + "@backstage/plugin-gcalendar": "0.3.1", + "@backstage/plugin-gcp-projects": "0.3.24", + "@backstage/plugin-git-release-manager": "0.3.18", + "@backstage/plugin-github-actions": "0.5.5", + "@backstage/plugin-github-deployments": "0.1.36", + "@backstage/plugin-gitops-profiles": "0.3.23", + "@backstage/plugin-gocd": "0.1.11", + "@backstage/plugin-graphiql": "0.2.37", + "@backstage/plugin-graphql-backend": "0.1.22", + "@backstage/plugin-home": "0.4.21", + "@backstage/plugin-ilert": "0.1.31", + "@backstage/plugin-jenkins": "0.7.4", + "@backstage/plugin-jenkins-backend": "0.1.22", + "@backstage/plugin-jenkins-common": "0.1.4", + "@backstage/plugin-kafka": "0.3.5", + "@backstage/plugin-kafka-backend": "0.2.25", + "@backstage/plugin-kubernetes": "0.6.5", + "@backstage/plugin-kubernetes-backend": "0.5.1", + "@backstage/plugin-kubernetes-common": "0.2.10", + "@backstage/plugin-lighthouse": "0.3.5", + "@backstage/plugin-newrelic": "0.3.23", + "@backstage/plugin-newrelic-dashboard": "0.1.13", + "@backstage/plugin-org": "0.5.5", + "@backstage/plugin-pagerduty": "0.3.32", + "@backstage/plugin-periskop": "0.1.3", + "@backstage/plugin-periskop-backend": "0.1.3", + "@backstage/plugin-permission-backend": "0.5.7", + "@backstage/plugin-permission-common": "0.6.1", + "@backstage/plugin-permission-node": "0.6.1", + "@backstage/plugin-permission-react": "0.4.1", + "@backstage/plugin-proxy-backend": "0.2.26", + "@backstage/plugin-rollbar": "0.4.5", + "@backstage/plugin-rollbar-backend": "0.1.29", + "@backstage/plugin-scaffolder": "1.2.0", + "@backstage/plugin-scaffolder-backend": "1.2.0", + "@backstage/plugin-scaffolder-backend-module-cookiecutter": "0.2.7", + "@backstage/plugin-scaffolder-backend-module-rails": "0.4.0", + "@backstage/plugin-scaffolder-backend-module-yeoman": "0.2.5", + "@backstage/plugin-scaffolder-common": "1.1.0", + "@backstage/plugin-search": "0.8.1", + "@backstage/plugin-search-backend": "0.5.2", + "@backstage/plugin-search-backend-module-elasticsearch": "0.1.4", + "@backstage/plugin-search-backend-module-pg": "0.3.3", + "@backstage/plugin-search-backend-node": "0.6.1", + "@backstage/plugin-search-common": "0.3.4", + "@backstage/plugin-search-react": "0.2.0", + "@backstage/plugin-sentry": "0.3.43", + "@backstage/plugin-shortcuts": "0.2.6", + "@backstage/plugin-sonarqube": "0.3.5", + "@backstage/plugin-splunk-on-call": "0.3.29", + "@backstage/plugin-stack-overflow": "0.1.1", + "@backstage/plugin-stack-overflow-backend": "0.1.1", + "@backstage/plugin-tech-insights": "0.2.1", + "@backstage/plugin-tech-insights-backend": "0.4.0", + "@backstage/plugin-tech-insights-backend-module-jsonfc": "0.1.16", "@backstage/plugin-tech-insights-common": "0.2.4", - "@backstage/plugin-tech-insights-node": "0.2.9", - "@backstage/plugin-tech-radar": "0.5.11", - "@backstage/plugin-techdocs": "1.1.0", - "@backstage/plugin-techdocs-backend": "1.1.0", - "@backstage/plugin-techdocs-node": "1.1.0", - "@backstage/plugin-techdocs-react": "0.1.0", - "@backstage/plugin-todo": "0.2.6", - "@backstage/plugin-todo-backend": "0.1.28", - "@backstage/plugin-user-settings": "0.4.3", - "@backstage/plugin-xcmetrics": "0.2.24", - "@backstage/plugin-catalog-backend-module-gerrit": "0.0.0", - "@backstage/plugin-techdocs-module-addons-contrib": "0.0.0" + "@backstage/plugin-tech-insights-node": "0.3.0", + "@backstage/plugin-tech-radar": "0.5.12", + "@backstage/plugin-techdocs": "1.1.1", + "@backstage/plugin-techdocs-addons-test-utils": "1.0.0", + "@backstage/plugin-techdocs-backend": "1.1.1", + "@backstage/plugin-techdocs-module-addons-contrib": "1.0.0", + "@backstage/plugin-techdocs-node": "1.1.1", + "@backstage/plugin-techdocs-react": "1.0.0", + "@backstage/plugin-todo": "0.2.7", + "@backstage/plugin-todo-backend": "0.1.29", + "@backstage/plugin-user-settings": "0.4.4", + "@backstage/plugin-xcmetrics": "0.2.25" }, - "changesets": [ - "breezy-days-prove", - "cool-ties-share", - "curly-parrots-applaud", - "dirty-eagles-hunt", - "eighty-chicken-retire", - "fair-kings-retire", - "fuzzy-seahorses-teach", - "giant-cheetahs-thank", - "hip-adults-collect", - "honest-kids-live", - "hot-peaches-give", - "lazy-zebras-pay", - "mean-flowers-change", - "metal-pants-fly", - "moody-laws-boil", - "moody-suns-smell", - "nasty-humans-give", - "new-beds-argue", - "old-bikes-study", - "perfect-penguins-rescue", - "pink-mayflies-rhyme", - "renovate-bf43c44", - "search-dry-wolves-join", - "search-heavy-llamas-worry", - "seven-deers-rule", - "shiny-students-approve", - "short-dodos-sparkle", - "short-flies-collect", - "silver-readers-deliver", - "sixty-llamas-change", - "spotty-plums-rule", - "stale-pigs-reply", - "strong-mangos-sell", - "swift-bugs-share", - "swift-parrots-hammer", - "tasty-drinks-teach", - "techdocs-changeset-not-found", - "techdocs-nice-boats-wonder", - "techdocs-quick-owls-smile", - "techdocs-touch-screen-wipes", - "thick-bees-brush", - "tough-forks-carry", - "violet-steaks-knock", - "wild-pigs-work", - "wise-emus-wait" - ] + "changesets": [] } diff --git a/.changeset/reject-failed-index-tasks.md b/.changeset/reject-failed-index-tasks.md new file mode 100644 index 0000000000..3c669ce545 --- /dev/null +++ b/.changeset/reject-failed-index-tasks.md @@ -0,0 +1,5 @@ +--- +'@backstage/search-backend-node': patch +--- + +propagate indexing errors so they don't appear successful to the task scheduler diff --git a/.changeset/renovate-ad175cc.md b/.changeset/renovate-ad175cc.md new file mode 100644 index 0000000000..72e278146e --- /dev/null +++ b/.changeset/renovate-ad175cc.md @@ -0,0 +1,7 @@ +--- +'@backstage/core-components': patch +'@backstage/plugin-gcp-projects': patch +'@backstage/plugin-techdocs-module-addons-contrib': patch +--- + +Updated dependency `@react-hookz/web` to `^14.0.0`. diff --git a/.changeset/renovate-bf43c44.md b/.changeset/renovate-bf43c44.md deleted file mode 100644 index 696017c72b..0000000000 --- a/.changeset/renovate-bf43c44.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -'@backstage/backend-common': patch -'@backstage/cli': patch -'@backstage/config-loader': patch -'@backstage/create-app': patch -'@backstage/plugin-app-backend': patch -'@backstage/plugin-auth-backend': patch -'@backstage/plugin-catalog-backend-module-gerrit': patch -'@backstage/plugin-catalog-backend': patch -'@backstage/plugin-kubernetes-backend': patch -'@backstage/plugin-rollbar-backend': patch -'@backstage/plugin-scaffolder-backend-module-cookiecutter': patch -'@backstage/plugin-scaffolder-backend': patch -'@backstage/plugin-techdocs-backend': patch -'@backstage/plugin-techdocs-node': patch ---- - -Updated dependency `fs-extra` to `10.1.0`. diff --git a/.changeset/search-dry-wolves-join.md b/.changeset/search-dry-wolves-join.md deleted file mode 100644 index 2eb613b642..0000000000 --- a/.changeset/search-dry-wolves-join.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-search': patch ---- - -Fix issue with `HomePageSearchBar` requiring `SearchContext` diff --git a/.changeset/search-heavy-llamas-worry.md b/.changeset/search-heavy-llamas-worry.md deleted file mode 100644 index 2d77d6c4d6..0000000000 --- a/.changeset/search-heavy-llamas-worry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-search-react': patch ---- - -Export `useSearchContextCheck` hook to check if the search context is available diff --git a/.changeset/seven-deers-rule.md b/.changeset/seven-deers-rule.md deleted file mode 100644 index 6b1976cbfa..0000000000 --- a/.changeset/seven-deers-rule.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-search-backend-module-elasticsearch': patch ---- - -Prevent orphaned stale indices by permanently marking them for deletion so removal can be re-attempted if it failed previously diff --git a/.changeset/shiny-students-approve.md b/.changeset/shiny-students-approve.md deleted file mode 100644 index 77de3ffc04..0000000000 --- a/.changeset/shiny-students-approve.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': minor ---- - -Split `publish:bitbucket` into `publish:bitbucketCloud` and `publish:bitbucketServer`. - -In order to migrate from the deprecated action, you need to replace the use of action -`publish:bitbucket` in your templates with the use of either `publish:bitbucketCloud` -or `publish:bitbucketServer` - depending on which destination SCM provider you use. - -Additionally, these actions will not utilize `integrations.bitbucket` anymore, -but `integrations.bitbucketCloud` or `integrations.bitbucketServer` respectively. -You may or may not have migrated to these already. - -As described in a previous changeset, using these two replacement integrations configs -will not compromise use cases which still rely on `integrations.bitbucket` as this was -set up in a backwards compatible way. - -Additionally, please mind that the option `enableLFS` is only available (and always was) -for Bitbucket Server use cases and therefore, is not even part of the schema for -`publish:bitbucketCloud` anymore. diff --git a/.changeset/short-dodos-sparkle.md b/.changeset/short-dodos-sparkle.md deleted file mode 100644 index 342b8cc22b..0000000000 --- a/.changeset/short-dodos-sparkle.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-aws': patch ---- - -Fix S3 object URL creation at AwsS3EntityProvider by - -- handle absence of region config, -- handle regions with region-less URIs (us-east-1), -- apply URI encoding, -- and simplify the logic overall. diff --git a/.changeset/short-flies-collect.md b/.changeset/short-flies-collect.md deleted file mode 100644 index d393f92175..0000000000 --- a/.changeset/short-flies-collect.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/config-loader': patch ---- - -Handle empty config files gracefully diff --git a/.changeset/silver-readers-deliver.md b/.changeset/silver-readers-deliver.md deleted file mode 100644 index ce175f97be..0000000000 --- a/.changeset/silver-readers-deliver.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -'@backstage/cli': patch -'@backstage/codemods': patch -'@backstage/create-app': patch -'@techdocs/cli': patch ---- - -Bump `commander` to version 9.1.0 diff --git a/.changeset/sixty-llamas-change.md b/.changeset/sixty-llamas-change.md deleted file mode 100644 index a0d183d45c..0000000000 --- a/.changeset/sixty-llamas-change.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': patch -'@backstage/plugin-jenkins-backend': patch -'@backstage/plugin-search-backend': patch ---- - -Fixed issue in `PermissionEvaluator` instance check that would cause unexpected "invalid union" errors. diff --git a/.changeset/slimy-elephants-attend.md b/.changeset/slimy-elephants-attend.md new file mode 100644 index 0000000000..4add8f18a3 --- /dev/null +++ b/.changeset/slimy-elephants-attend.md @@ -0,0 +1,15 @@ +--- +'@backstage/plugin-catalog-backend': minor +--- + +Allow array as non-spread arguments at the `CatalogBuilder`. + +```typescript +builder.addEntityProvider(...getArrayOfProviders()); +``` + +can be simplified to + +```typescript +builder.addEntityProvider(getArrayOfProviders()); +``` diff --git a/.changeset/spotty-plums-rule.md b/.changeset/spotty-plums-rule.md deleted file mode 100644 index 45d1c8280f..0000000000 --- a/.changeset/spotty-plums-rule.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-search-react': patch -'@backstage/plugin-techdocs': patch ---- - -Removed dependency on `@backstage/core-app-api`. diff --git a/.changeset/stale-pigs-reply.md b/.changeset/stale-pigs-reply.md deleted file mode 100644 index 645648c93f..0000000000 --- a/.changeset/stale-pigs-reply.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Added a link to the `${GITHUB_TOKEN}` to document how to generate a token diff --git a/.changeset/strong-mangos-sell.md b/.changeset/strong-mangos-sell.md deleted file mode 100644 index 614d36cf52..0000000000 --- a/.changeset/strong-mangos-sell.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -'@backstage/plugin-auth-backend': patch ---- - -Add more common predefined sign-in resolvers to auth providers. - -Add the existing resolver to more providers (already available at `google`): - -- `providers.microsoft.resolvers.emailLocalPartMatchingUserEntityName()` -- `providers.okta.resolvers.emailLocalPartMatchingUserEntityName()` - -Add a new resolver for simple email-to-email matching: - -- `providers.google.resolvers.emailMatchingUserEntityProfileEmail()` -- `providers.microsoft.resolvers.emailMatchingUserEntityProfileEmail()` -- `providers.okta.resolvers.emailMatchingUserEntityProfileEmail()` diff --git a/.changeset/swift-bugs-share.md b/.changeset/swift-bugs-share.md deleted file mode 100644 index c018bdfacf..0000000000 --- a/.changeset/swift-bugs-share.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Updated the `create-github-app` command to throw an error if the organization argument is a user or a non existing organization. diff --git a/.changeset/swift-parrots-hammer.md b/.changeset/swift-parrots-hammer.md deleted file mode 100644 index 1b26742937..0000000000 --- a/.changeset/swift-parrots-hammer.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -'@backstage/backend-tasks': patch ---- - -Scheduled tasks now have an optional `scope` field. If unset, or having the -value `'global'`, the old behavior with cross-worker locking is retained. If -having the value `'local'`, there is no coordination across workers and the -behavior is more like `setInterval`. This can be used to replace usages of -`runPeriodically` helpers. diff --git a/.changeset/tasty-drinks-teach.md b/.changeset/tasty-drinks-teach.md deleted file mode 100644 index 4b4a5a748d..0000000000 --- a/.changeset/tasty-drinks-teach.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/backend-common': patch -'@backstage/plugin-auth-backend': patch -'@backstage/plugin-auth-node': patch ---- - -Update to `jose` 4.6.0 diff --git a/.changeset/techdocs-changeset-not-found.md b/.changeset/techdocs-changeset-not-found.md deleted file mode 100644 index 7ee5ec2b85..0000000000 --- a/.changeset/techdocs-changeset-not-found.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-techdocs': patch ---- - -Fixed bugs that prevented a 404 error from being shown when it should have been. diff --git a/.changeset/techdocs-nice-boats-wonder.md b/.changeset/techdocs-nice-boats-wonder.md deleted file mode 100644 index 434cde841e..0000000000 --- a/.changeset/techdocs-nice-boats-wonder.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-techdocs-node': patch ---- - -Allow usage of custom tags with sequence node type (such as the !ENV tag) in the techdocs mkdocs config. diff --git a/.changeset/techdocs-quick-owls-smile.md b/.changeset/techdocs-quick-owls-smile.md deleted file mode 100644 index db4202fb5b..0000000000 --- a/.changeset/techdocs-quick-owls-smile.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/plugin-techdocs-module-addons-contrib': minor ---- - -A package for contributed TechDocs addons. - -In this release it will introduce the ReportIssue addon, which lets you select text and open a GitHub/Gitlab issue. diff --git a/.changeset/techdocs-swans-check.md b/.changeset/techdocs-swans-check.md new file mode 100644 index 0000000000..750648ff8a --- /dev/null +++ b/.changeset/techdocs-swans-check.md @@ -0,0 +1,9 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Packages a set of tweaks to the TechDocs addons rendering process: + +- Prevents displaying sidebars until page styles are loaded and the sidebar position is updated; +- Prevents new sidebar locations from being created every time the reader page is rendered if these locations already exist; +- Centers the styles loaded event to avoid having multiple locations setting the opacity style in Shadow Dom causing the screen to flash multiple times. diff --git a/.changeset/techdocs-touch-screen-wipes.md b/.changeset/techdocs-touch-screen-wipes.md deleted file mode 100644 index b6467a4232..0000000000 --- a/.changeset/techdocs-touch-screen-wipes.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-techdocs-react': patch -'@backstage/plugin-techdocs-module-addons-contrib': patch ---- - -Updated the return type of `createTechDocsAddonExtension` to better reflect the fact that passing children to Addon components is not a valid use-case. diff --git a/.changeset/techdocs-ways-type.md b/.changeset/techdocs-ways-type.md new file mode 100644 index 0000000000..6e147f62a7 --- /dev/null +++ b/.changeset/techdocs-ways-type.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Change the `EntityDocsPage` path to be more specific and also add integration tests for `sub-routes` on this page. diff --git a/.changeset/techdocs-wolves-carry.md b/.changeset/techdocs-wolves-carry.md new file mode 100644 index 0000000000..01436ee15f --- /dev/null +++ b/.changeset/techdocs-wolves-carry.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-techdocs-react': patch +--- + +Creates a `TechDocsShadowDom` component that takes a tree of elements and an `onAppend` handler: + +- Calls the `onAppend` handler when appending the element tree to the shadow root; +- Also dispatches an event when styles are loaded to let transformers know that the computed styles are ready to be consumed. diff --git a/.changeset/thick-bees-brush.md b/.changeset/thick-bees-brush.md deleted file mode 100644 index 4ec068e0a1..0000000000 --- a/.changeset/thick-bees-brush.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-gcalendar': patch ---- - -fixed google API types dependencies diff --git a/.changeset/tough-forks-carry.md b/.changeset/tough-forks-carry.md deleted file mode 100644 index 88fd82b55d..0000000000 --- a/.changeset/tough-forks-carry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-org': patch ---- - -Fix ref to filtered catalog table view when using aggregated relations. diff --git a/.changeset/violet-steaks-knock.md b/.changeset/violet-steaks-knock.md deleted file mode 100644 index 66139be422..0000000000 --- a/.changeset/violet-steaks-knock.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/test-utils': patch ---- - -Added missing `Routes` element to wrap the `Route` elements of the test app wrapping. diff --git a/.changeset/wild-pigs-work.md b/.changeset/wild-pigs-work.md deleted file mode 100644 index 462b3b7233..0000000000 --- a/.changeset/wild-pigs-work.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-api-docs': patch ---- - -Updated the rendering of AsyncApi definitions to be able to resolve absolute HTTP $ref references. diff --git a/.changeset/wise-emus-wait.md b/.changeset/wise-emus-wait.md deleted file mode 100644 index 6f0e5348c7..0000000000 --- a/.changeset/wise-emus-wait.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/test-utils': patch ---- - -The internal elements created as part of the `mountedRoutes` implementation are now hidden during rendering. diff --git a/.dockerignore b/.dockerignore index e34ae2c37d..45f9f52059 100644 --- a/.dockerignore +++ b/.dockerignore @@ -3,6 +3,6 @@ docs cypress microsite node_modules -packages -!packages/backend/dist +packages/*/src +packages/*/node_modules plugins diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 3665a4952d..b4f825d221 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -5,6 +5,8 @@ # 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/search-* @backstage/reviewers @backstage/techdocs-core /.changeset/techdocs-* @backstage/reviewers @backstage/techdocs-core diff --git a/.github/renovate.json5 b/.github/renovate.json5 index ff351a5e0f..a8bc3448a5 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -3,7 +3,7 @@ extends: ['config:base', ':disableDependencyDashboard', ':gitSignOff'], rangeStrategy: 'update-lockfile', // @elastic/elasticsearch is ignored due to licensing issues. See #10992 - ignoreDeps: ['@elastic/elasticsearch'], + ignoreDeps: ['@elastic/elasticsearch', 'event-source-polyfill'], packageRules: [ { matchSourceUrlPrefixes: ['https://github.com/spotify/web-scripts'], diff --git a/.github/styles/vocab.txt b/.github/vale/Vocab/Backstage/accept.txt similarity index 98% rename from .github/styles/vocab.txt rename to .github/vale/Vocab/Backstage/accept.txt index acbc47a861..34c33c3e8c 100644 --- a/.github/styles/vocab.txt +++ b/.github/vale/Vocab/Backstage/accept.txt @@ -8,9 +8,6 @@ airbrake Anddddd Apdex api -Api -apis -args asciidoc async Atlassian @@ -19,7 +16,6 @@ autoscaling Autoscaling autoselect Avro -aws backported backporting Bigtable @@ -38,7 +34,6 @@ changesets Changesets chanwit Chanwit -ci CI/CD classname cli @@ -50,6 +45,8 @@ codeblocks Codecov codehilite Codehilite +codescene +CodeScene codemod codemods codeowners @@ -127,7 +124,6 @@ hoc horizontalpodautoscalers Hostname hotspots -html http https Iain @@ -224,6 +220,8 @@ performant Performant periskop Periskop +permissioned +permissioning plantuml Platformize Podman @@ -314,8 +312,8 @@ templaters Templaters theia thumbsup -toc todo +todos tolerations Tolerations toolchain @@ -339,7 +337,6 @@ unregistration untracked upsert upvote -url URIs URLs utils diff --git a/.github/vale/config.ini b/.github/vale/config.ini new file mode 100644 index 0000000000..36acb2c058 --- /dev/null +++ b/.github/vale/config.ini @@ -0,0 +1,6 @@ +StylesPath = . +Vocab = Backstage + +[*.md] +BasedOnStyles = Vale +Vale.Terms = NO diff --git a/.github/workflows/automate_area-labels.yml b/.github/workflows/automate_area-labels.yml index e829029525..3d616506e1 100644 --- a/.github/workflows/automate_area-labels.yml +++ b/.github/workflows/automate_area-labels.yml @@ -6,7 +6,7 @@ jobs: triage: runs-on: ubuntu-latest steps: - - uses: actions/labeler@v3 + - uses: actions/labeler@v4 with: repo-token: '${{ secrets.GITHUB_TOKEN }}' sync-labels: true diff --git a/.github/workflows/automate_changeset_feedback.yml b/.github/workflows/automate_changeset_feedback.yml index efcbaf210b..700eae7d0c 100644 --- a/.github/workflows/automate_changeset_feedback.yml +++ b/.github/workflows/automate_changeset_feedback.yml @@ -21,7 +21,7 @@ jobs: if: github.repository == 'backstage/backstage' && github.event.pull_request.user.login != 'backstage-service' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: # Fetch the commit that's merged into the base rather than the target ref # This will let us diff only the contents of the PR, without fetching more history @@ -39,7 +39,7 @@ jobs: node generate.js FETCH_HEAD > feedback.txt - name: Post Feedback - uses: actions/github-script@v5 + uses: actions/github-script@v6 env: ISSUE_NUMBER: ${{ github.event.pull_request.number }} with: diff --git a/.github/workflows/automate_merge_renovate_prs.yaml b/.github/workflows/automate_merge_renovate_prs.yaml new file mode 100644 index 0000000000..8de488cf05 --- /dev/null +++ b/.github/workflows/automate_merge_renovate_prs.yaml @@ -0,0 +1,85 @@ +name: Automate Merge Renovate PRs + +on: + workflow_dispatch: + schedule: + - cron: '*/10 * * * *' + +jobs: + diff: + runs-on: ubuntu-latest + steps: + - uses: actions/github-script@v6 + with: + script: | + const owner = "backstage"; + const repo = "backstage"; + const query = `{ + repository(owner: "backstage", name: "backstage") { + pullRequests(labels: ["dependencies"], last: 10, states: [OPEN]) { + nodes { + title + author { + login + } + number + mergeable + files(first: 1) { + nodes { + path + } + } + changedFiles + commits(last: 1) { + nodes { + commit { + statusCheckRollup { + state + } + } + } + } + reviewDecision + reviews(first: 10) { + nodes { + author { + login + } + } + } + } + } + } + }`; + + const date = new Date(); + if (date.getDay() === 2) { + console.log("Skipping auto merge because Tuesday is release day"); + return; + } + + const r = await github.graphql(query); + const mergable = r.repository.pullRequests.nodes.filter( + (pr) => + pr.author.login === "renovate" && + pr.mergeable === "MERGEABLE" && + pr.changedFiles === 1 && + pr.files.nodes[0].path.split("/").slice(-1)[0] === "yarn.lock" && + pr.commits.nodes[0].commit.statusCheckRollup.state === "SUCCESS" && + pr.reviewDecision === "APPROVED" + ); + + if (mergable.length === 0) { + console.log("no mergable PRs"); + return; + } + + for (const pr of mergable) { + console.log(`Merging #${pr.number} - ${pr.title}`); + await github.rest.pulls.merge({ + owner, + repo, + pull_number: pr.number, + }); + await new Promise((r) => setTimeout(r, 2000)); + } diff --git a/.github/workflows/automate_review-labels-scheduled.yaml b/.github/workflows/automate_review-labels-scheduled.yaml index 655581501a..3ba13cfd17 100644 --- a/.github/workflows/automate_review-labels-scheduled.yaml +++ b/.github/workflows/automate_review-labels-scheduled.yaml @@ -16,13 +16,13 @@ jobs: application_private_key: ${{ secrets.BACKSTAGE_GOALIE_PRIVATE_KEY }} organization: backstage - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 with: node-version: 14 - run: npm install codeowners - - uses: actions/github-script@v5 + - uses: actions/github-script@v6 id: fix-labels with: github-token: ${{ steps.get_workflow_token.outputs.token }} diff --git a/.github/workflows/automate_review-labels.yaml b/.github/workflows/automate_review-labels.yaml index 074d1271e0..cae71f6aaa 100644 --- a/.github/workflows/automate_review-labels.yaml +++ b/.github/workflows/automate_review-labels.yaml @@ -16,14 +16,14 @@ jobs: label: runs-on: ubuntu-latest steps: - - uses: actions/github-script@v5 + # THESE SCRIPTS SHOULD BE INLINED AND NOT IN REPO + # DUE TO THE TOKEN PERMISSIONS THAT ARE GIVEN + # TO THE WORKFLOW. THIS SCRIPT WILL AND SHOULD + # RUN FROM THE BASE REPO ALL TIMES. + - uses: actions/github-script@v6 id: fix-labels with: github-token: ${{ secrets.GITHUB_TOKEN }} - # THIS SCRIPT SHOULD BE INLINED AND NOT IN REPO - # DUE TO THE TOKEN PERMISSIONS THAT ARE GIVEN - # TO THE WORKFLOW. THIS SCRIPT WILL AND SHOULD - # RUN FROM THE BASE REPO ALL TIMES. script: | if (['opened', 'synchronize'].includes('${{ github.event.action }}')) { // if it's the author, always add awaiting-review label @@ -48,3 +48,37 @@ jobs: console.log(e) }); } + + - name: Add PRs to review board + uses: actions/github-script@v6 + if: github.event.action == 'opened' || github.event.action == 'reopened' + env: + PROJECT_ID: PN_kwDOBFKqdc02LQ + with: + github-token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} + script: | + const prLookup = await github.graphql(` + query($owner: String!, $repo: String!, $number: Int!){ + repository(owner: $owner, name: $repo) { + pullRequest(number: $number) { + id + } + } + } + `, context.issue); + + await github.graphql(` + mutation($projectId: ID!, $contentId: ID!) { + addProjectNextItem(input: { + projectId: $projectId, + contentId: $contentId, + }) { + projectNextItem { + id + } + } + } + `, { + projectId: process.env.PROJECT_ID, + contentId: prLookup.repository.pullRequest.id + }) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 31a315d044..0718c8a8b9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,14 +60,14 @@ jobs: INTEGRATION_TEST_AZURE_TOKEN: ${{ secrets.INTEGRATION_TEST_AZURE_TOKEN }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: fetch branch master run: git fetch origin master # Beginning of yarn setup, keep in sync between all workflows. # TODO(Rugvip): move this to composite action once all features we use are supported - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 + uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth @@ -75,7 +75,7 @@ jobs: # Cache every node_modules folder inside the monorepo - name: cache all node_modules id: cache-modules - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: '**/node_modules' # We use both yarn.lock and package.json as cache keys to ensure that @@ -91,7 +91,7 @@ jobs: run: echo "::set-output name=dir::$(yarn cache dir)" - name: cache global yarn cache - uses: actions/cache@v2 + uses: actions/cache@v3 if: steps.cache-modules.outputs.cache-hit != 'true' with: path: ${{ steps.yarn-cache.outputs.dir }} diff --git a/.github/workflows/deploy_microsite.yml b/.github/workflows/deploy_microsite.yml index 7931f7ade5..d0f8dc1e79 100644 --- a/.github/workflows/deploy_microsite.yml +++ b/.github/workflows/deploy_microsite.yml @@ -17,10 +17,10 @@ jobs: NODE_OPTIONS: --max-old-space-size=4096 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 + uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth @@ -55,8 +55,7 @@ jobs: run: ls microsite/build/backstage && ls microsite/build/backstage/storybook - name: Deploy both microsite and storybook to gh-pages - uses: JamesIves/github-pages-deploy-action@3.7.1 + uses: JamesIves/github-pages-deploy-action@v4.3.3 with: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - BRANCH: gh-pages - FOLDER: microsite/build/backstage + branch: gh-pages + folder: microsite/build/backstage diff --git a/.github/workflows/deploy_nightly.yml b/.github/workflows/deploy_nightly.yml index 85668bdc3a..c90894bce4 100644 --- a/.github/workflows/deploy_nightly.yml +++ b/.github/workflows/deploy_nightly.yml @@ -18,17 +18,17 @@ jobs: NODE_OPTIONS: --max-old-space-size=4096 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 # Beginning of yarn setup, keep in sync between all workflows, see ci.yml - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 + uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - name: cache all node_modules id: cache-modules - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: '**/node_modules' key: ${{ runner.os }}-v${{ matrix.node-version }}-node_modules-${{ hashFiles('yarn.lock', '**/package.json') }} @@ -37,7 +37,7 @@ jobs: if: steps.cache-modules.outputs.cache-hit != 'true' run: echo "::set-output name=dir::$(yarn cache dir)" - name: cache global yarn cache - uses: actions/cache@v2 + uses: actions/cache@v3 if: steps.cache-modules.outputs.cache-hit != 'true' with: path: ${{ steps.yarn-cache.outputs.dir }} diff --git a/.github/workflows/deploy_packages.yml b/.github/workflows/deploy_packages.yml index bcb4016ab9..b63507df0c 100644 --- a/.github/workflows/deploy_packages.yml +++ b/.github/workflows/deploy_packages.yml @@ -59,17 +59,17 @@ jobs: INTEGRATION_TEST_AZURE_TOKEN: ${{ secrets.INTEGRATION_TEST_AZURE_TOKEN }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 # Beginning of yarn setup, keep in sync between all workflows, see ci.yml - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 + uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - name: cache all node_modules id: cache-modules - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: '**/node_modules' key: ${{ runner.os }}-v${{ matrix.node-version }}-node_modules-${{ hashFiles('yarn.lock', '**/package.json') }} @@ -78,7 +78,7 @@ jobs: if: steps.cache-modules.outputs.cache-hit != 'true' run: echo "::set-output name=dir::$(yarn cache dir)" - name: cache global yarn cache - uses: actions/cache@v2 + uses: actions/cache@v3 if: steps.cache-modules.outputs.cache-hit != 'true' with: path: ${{ steps.yarn-cache.outputs.dir }} @@ -155,17 +155,17 @@ jobs: NODE_OPTIONS: --max-old-space-size=4096 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 # Beginning of yarn setup, keep in sync between all workflows, see ci.yml - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 + uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - name: cache all node_modules id: cache-modules - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: '**/node_modules' key: ${{ runner.os }}-v${{ matrix.node-version }}-node_modules-${{ hashFiles('yarn.lock', '**/package.json') }} @@ -174,7 +174,7 @@ jobs: if: steps.cache-modules.outputs.cache-hit != 'true' run: echo "::set-output name=dir::$(yarn cache dir)" - name: cache global yarn cache - uses: actions/cache@v2 + uses: actions/cache@v3 if: steps.cache-modules.outputs.cache-hit != 'true' with: path: ${{ steps.yarn-cache.outputs.dir }} diff --git a/.github/workflows/sync_approve_renovate_pr.yaml b/.github/workflows/sync_approve_renovate_pr.yaml new file mode 100644 index 0000000000..2d25bea223 --- /dev/null +++ b/.github/workflows/sync_approve_renovate_pr.yaml @@ -0,0 +1,37 @@ +name: Approve renovate lock file changes +on: + pull_request_target: + paths: + - '.github/workflows/sync_renovate-changesets.yml' + - '**/yarn.lock' + +jobs: + generate-changeset: + runs-on: ubuntu-latest + if: github.actor == 'renovate[bot]' && github.repository == 'backstage/backstage' + steps: + - name: Approve + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} + script: | + const owner = 'backstage'; + const repo = 'backstage'; + + const r = await github.rest.pulls.listFiles({ + owner, + repo, + pull_number: context.issue.number, + }); + + if (r.data.some((f) => f.filename.split('/').slice(-1)[0] !== 'yarn.lock')) { + console.log('skipping approval since some files are not yarn.lock'); + return; + } + + await github.rest.pulls.createReview({ + owner, + repo, + pull_number: context.issue.number, + event: 'APPROVE' + }) diff --git a/.github/workflows/sync_code-formatting.yml b/.github/workflows/sync_code-formatting.yml index c1a4b9e1d0..b6fce0e68d 100644 --- a/.github/workflows/sync_code-formatting.yml +++ b/.github/workflows/sync_code-formatting.yml @@ -9,20 +9,20 @@ jobs: name: Autofix Markdown files using Prettier runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: # Fetch changes to previous commit - required for 'only_changed' in Prettier action fetch-depth: 0 # Beginning of yarn setup, keep in sync between all workflows, see ci.yml - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 + uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - name: cache all node_modules id: cache-modules - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: '**/node_modules' key: ${{ runner.os }}-v${{ matrix.node-version }}-node_modules-${{ hashFiles('yarn.lock', '**/package.json') }} @@ -31,7 +31,7 @@ jobs: if: steps.cache-modules.outputs.cache-hit != 'true' run: echo "::set-output name=dir::$(yarn cache dir)" - name: cache global yarn cache - uses: actions/cache@v2 + uses: actions/cache@v3 if: steps.cache-modules.outputs.cache-hit != 'true' with: path: ${{ steps.yarn-cache.outputs.dir }} @@ -43,7 +43,7 @@ jobs: # End of yarn setup - name: Run Prettier on ADOPTERS.md - uses: creyD/prettier_action@v3.3 + uses: creyD/prettier_action@v4.2 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_dependabot-changesets.yml b/.github/workflows/sync_dependabot-changesets.yml index 58e0a1ac0f..d5f6f9a36c 100644 --- a/.github/workflows/sync_dependabot-changesets.yml +++ b/.github/workflows/sync_dependabot-changesets.yml @@ -11,7 +11,7 @@ jobs: if: github.actor == 'dependabot[bot]' && github.repository == 'backstage/backstage' steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 2 ref: ${{ github.head_ref }} @@ -21,7 +21,7 @@ jobs: git config --global user.email noreply@backstage.io git config --global user.name 'Github changeset workflow' - name: Generate changeset - uses: actions/github-script@v5 + uses: actions/github-script@v6 with: script: | const { promises: fs } = require('fs'); diff --git a/.github/workflows/sync_release-manifest.yml b/.github/workflows/sync_release-manifest.yml index 328c64fb58..816a3e632f 100644 --- a/.github/workflows/sync_release-manifest.yml +++ b/.github/workflows/sync_release-manifest.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: # Setup node & install deps before checkout, keeping install quick - - uses: actions/setup-node@v2 + - uses: actions/setup-node@v3 with: node-version: 16 - name: Install dependencies @@ -19,7 +19,7 @@ jobs: run: npm install semver@7.3.5 fs-extra@10.0.0 @manypkg/get-packages@1.1.1 - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: path: backstage # 'v' prefix is added here for the tag, we keep it out of the manifest logic @@ -27,7 +27,7 @@ jobs: # Checkout backstage/versions into /backstage/versions, which is where store the output - name: Checkout versions - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: repository: backstage/versions path: backstage/versions @@ -51,7 +51,7 @@ jobs: git push - name: Dispatch update-helper update - uses: actions/github-script@v5 + uses: actions/github-script@v6 with: 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 diff --git a/.github/workflows/sync_renovate-changesets.yml b/.github/workflows/sync_renovate-changesets.yml index 60eabde769..cd67fa9954 100644 --- a/.github/workflows/sync_renovate-changesets.yml +++ b/.github/workflows/sync_renovate-changesets.yml @@ -11,7 +11,7 @@ jobs: if: github.actor == 'renovate[bot]' && github.repository == 'backstage/backstage' steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 2 ref: ${{ github.head_ref }} @@ -21,7 +21,7 @@ jobs: git config --global user.email noreply@backstage.io git config --global user.name 'Github changeset workflow' - name: Generate changeset - uses: actions/github-script@v5 + uses: actions/github-script@v6 with: script: | const { promises: fs } = require("fs"); diff --git a/.github/workflows/sync_snyk-github-issues.yml b/.github/workflows/sync_snyk-github-issues.yml index 0bf0340f68..e45a36c8e8 100644 --- a/.github/workflows/sync_snyk-github-issues.yml +++ b/.github/workflows/sync_snyk-github-issues.yml @@ -14,17 +14,17 @@ jobs: node-version: [14.x] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 # Beginning of yarn setup, keep in sync between all workflows, see ci.yml - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 + uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - name: cache all node_modules id: cache-modules - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: '**/node_modules' key: ${{ runner.os }}-v${{ matrix.node-version }}-node_modules-${{ hashFiles('yarn.lock', '**/package.json') }} @@ -33,7 +33,7 @@ jobs: if: steps.cache-modules.outputs.cache-hit != 'true' run: echo "::set-output name=dir::$(yarn cache dir)" - name: cache global yarn cache - uses: actions/cache@v2 + uses: actions/cache@v3 if: steps.cache-modules.outputs.cache-hit != 'true' with: path: ${{ steps.yarn-cache.outputs.dir }} diff --git a/.github/workflows/sync_snyk-monitor.yml b/.github/workflows/sync_snyk-monitor.yml index bf50748dc0..d6adca927e 100644 --- a/.github/workflows/sync_snyk-monitor.yml +++ b/.github/workflows/sync_snyk-monitor.yml @@ -18,7 +18,7 @@ jobs: sync: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Monitor and Synchronize Snyk Policies uses: snyk/actions/node@master with: @@ -45,6 +45,6 @@ jobs: env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} - name: Upload Snyk report - uses: github/codeql-action/upload-sarif@v1 + uses: github/codeql-action/upload-sarif@v2 with: sarif_file: snyk.sarif diff --git a/.github/workflows/sync_version-packages.yml b/.github/workflows/sync_version-packages.yml index a6a544e6a5..926906338f 100644 --- a/.github/workflows/sync_version-packages.yml +++ b/.github/workflows/sync_version-packages.yml @@ -9,7 +9,7 @@ jobs: name: Create Changeset PR runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 0 token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} diff --git a/.github/workflows/verify_codeql.yml b/.github/workflows/verify_codeql.yml index a275e357a4..f3417ac582 100644 --- a/.github/workflows/verify_codeql.yml +++ b/.github/workflows/verify_codeql.yml @@ -35,7 +35,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can checkout the head. @@ -43,7 +43,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v2 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -54,7 +54,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v1 + uses: github/codeql-action/autobuild@v2 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -68,4 +68,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/verify_dco.yaml b/.github/workflows/verify_dco.yaml index e2da767394..2094327d23 100644 --- a/.github/workflows/verify_dco.yaml +++ b/.github/workflows/verify_dco.yaml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Verify DCO status for open pull requests - uses: actions/github-script@v5 + uses: actions/github-script@v6 with: script: | const owner = "backstage"; @@ -54,7 +54,7 @@ jobs: console.log(`creating comment on PR #${pull.number}`); const body = ` Thanks for the contribution! - All commits need to be DCO signed before merging. Please refer to the the [DCO section in CONTRIBUTING.md](https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md#developer-certificate-of-origin) or the [DCO](${checks.data.check_runs[0].html_url}) status for more info. + All commits need to be DCO signed before they are reviewed. Please refer to the the [DCO section in CONTRIBUTING.md](https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md#developer-certificate-of-origin) or the [DCO](${checks.data.check_runs[0].html_url}) status for more info. `; await github.rest.issues.createComment({ repo, diff --git a/.github/workflows/verify_docs-quality.yml b/.github/workflows/verify_docs-quality.yml index 55c1c23587..fe8849a085 100644 --- a/.github/workflows/verify_docs-quality.yml +++ b/.github/workflows/verify_docs-quality.yml @@ -11,11 +11,19 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 + + # Vale does not support file excludes, so we use the script to generate a list of files instead + # The action also does not allow args or a local config file to be passed in, so the files array + # also contains an "--config=.github/vale/config.ini" option + - name: generate vale args + id: generate + run: echo "::set-output name=args::$(node scripts/check-docs-quality.js --ci-args)" + - name: documentation quality check - uses: errata-ai/vale-action@v1.4.0 - # Whitelist excluding ADOPTERS, CHANGELOG and OWNERS (no exclude flag exists) + uses: errata-ai/vale-action@v1.5.0 with: - files: '[".changeset", ".github", "contrib", "docs", "microsite", "packages", "plugins", "CONTRIBUTING.md", "CODE_OF_CONDUCT.md", "GOVERNANCE.md", "README.md"]' + # This also contains --config=.github/vale/config.ini ... :/ + files: '${{ steps.generate.outputs.args }}' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/verify_e2e-linux.yml b/.github/workflows/verify_e2e-linux.yml index e333aaba0c..884d8bf172 100644 --- a/.github/workflows/verify_e2e-linux.yml +++ b/.github/workflows/verify_e2e-linux.yml @@ -38,17 +38,17 @@ jobs: name: Node ${{ matrix.node-version }} on ${{ matrix.os }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 # Beginning of yarn setup, keep in sync between all workflows, see ci.yml - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 + uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - name: cache all node_modules id: cache-modules - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: '**/node_modules' key: ${{ runner.os }}-v${{ matrix.node-version }}-node_modules-${{ hashFiles('yarn.lock', '**/package.json') }} @@ -59,7 +59,7 @@ jobs: - name: setup chrome uses: browser-actions/setup-chrome@latest - name: cache global yarn cache - uses: actions/cache@v2 + uses: actions/cache@v3 if: steps.cache-modules.outputs.cache-hit != 'true' with: path: ${{ steps.yarn-cache.outputs.dir }} @@ -78,3 +78,7 @@ jobs: yarn e2e-test run env: BACKSTAGE_TEST_DISABLE_DOCKER: 1 + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_HOST: localhost + POSTGRES_PORT: ${{ job.services.postgres.ports[5432] }} diff --git a/.github/workflows/verify_e2e-techdocs.yml b/.github/workflows/verify_e2e-techdocs.yml index b9dfdc23eb..c8f825796b 100644 --- a/.github/workflows/verify_e2e-techdocs.yml +++ b/.github/workflows/verify_e2e-techdocs.yml @@ -25,8 +25,8 @@ jobs: NODE_OPTIONS: --max-old-space-size=4096 steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-python@v3 - name: install dependencies run: yarn install --frozen-lockfile diff --git a/.github/workflows/verify_e2e-tugboat.yml b/.github/workflows/verify_e2e-tugboat.yml index 1054ccf579..04d9e686e2 100644 --- a/.github/workflows/verify_e2e-tugboat.yml +++ b/.github/workflows/verify_e2e-tugboat.yml @@ -10,12 +10,12 @@ jobs: # Set an initial commit status message to indicate that the tests are # running. - name: set pending status - uses: actions/github-script@v3 + uses: actions/github-script@v6 with: github-token: ${{secrets.GH_SERVICE_ACCOUNT_TOKEN}} debug: true script: | - return github.repos.createCommitStatus({ + return github.rest.repos.createCommitStatus({ owner: context.repo.owner, repo: context.repo.repo, sha: context.sha, @@ -34,12 +34,12 @@ jobs: # Set an initial commit status message to indicate that the tests are # running. - name: set pending status - uses: actions/github-script@v3 + uses: actions/github-script@v6 with: github-token: ${{secrets.GH_SERVICE_ACCOUNT_TOKEN}} debug: true script: | - return github.repos.createCommitStatus({ + return github.rest.repos.createCommitStatus({ owner: context.repo.owner, repo: context.repo.repo, sha: context.sha, @@ -49,8 +49,8 @@ jobs: target_url: "https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}" }); - - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 with: node-version: '14' @@ -65,12 +65,12 @@ jobs: # https://developer.github.com/changes/2016-04-06-deployment-and-deployment-status-enhancements/ - name: get deployment status id: get-status-env - uses: actions/github-script@v3 + uses: actions/github-script@v6 with: github-token: ${{secrets.GH_SERVICE_ACCOUNT_TOKEN}} result-encoding: string script: | - const result = await github.repos.getDeploymentStatus({ + const result = await github.rest.repos.getDeploymentStatus({ owner: context.repo.owner, repo: context.repo.repo, deployment_id: context.payload.deployment.id, @@ -95,18 +95,18 @@ jobs: - name: update artifact if: ${{ always() }} - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v3 with: name: cypress-videos path: ./cypress/cypress/videos - name: set status if: ${{ failure() }} - uses: actions/github-script@v3 + uses: actions/github-script@v6 with: github-token: ${{secrets.GH_SERVICE_ACCOUNT_TOKEN}} script: | - return github.repos.createCommitStatus({ + return github.rest.repos.createCommitStatus({ owner: context.repo.owner, repo: context.repo.repo, sha: context.sha, @@ -117,11 +117,11 @@ jobs: - name: set status if: ${{ success() }} - uses: actions/github-script@v3 + uses: actions/github-script@v6 with: github-token: ${{secrets.GH_SERVICE_ACCOUNT_TOKEN}} script: | - return github.repos.createCommitStatus({ + return github.rest.repos.createCommitStatus({ owner: context.repo.owner, repo: context.repo.repo, sha: context.sha, diff --git a/.github/workflows/verify_e2e-windows.yml b/.github/workflows/verify_e2e-windows.yml index becff56953..129c246a73 100644 --- a/.github/workflows/verify_e2e-windows.yml +++ b/.github/workflows/verify_e2e-windows.yml @@ -37,10 +37,10 @@ jobs: git config --global core.autocrlf false git config --global core.eol lf - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 + uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} - name: Add msbuild to PATH diff --git a/.github/workflows/verify_fossa.yml b/.github/workflows/verify_fossa.yml index 6126ae9bf2..8c6b25d2e4 100644 --- a/.github/workflows/verify_fossa.yml +++ b/.github/workflows/verify_fossa.yml @@ -11,7 +11,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install Fossa run: "curl -H 'Cache-Control: no-cache' https://raw.githubusercontent.com/fossas/fossa-cli/master/install.sh | bash" diff --git a/.github/workflows/verify_microsite.yml b/.github/workflows/verify_microsite.yml index 24a6ff5351..bd091b857e 100644 --- a/.github/workflows/verify_microsite.yml +++ b/.github/workflows/verify_microsite.yml @@ -19,10 +19,10 @@ jobs: NODE_OPTIONS: --max-old-space-size=4096 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 + uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} diff --git a/.github/workflows/verify_storybook.yml b/.github/workflows/verify_storybook.yml index 60f58f4c66..a13aa3ae71 100644 --- a/.github/workflows/verify_storybook.yml +++ b/.github/workflows/verify_storybook.yml @@ -19,19 +19,19 @@ jobs: chromatic: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 0 # Required to retrieve git history # Beginning of yarn setup, keep in sync between all workflows, see ci.yml - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 + uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - name: cache all node_modules id: cache-modules - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: '**/node_modules' key: ${{ runner.os }}-v${{ matrix.node-version }}-node_modules-${{ hashFiles('yarn.lock', '**/package.json') }} @@ -40,7 +40,7 @@ jobs: if: steps.cache-modules.outputs.cache-hit != 'true' run: echo "::set-output name=dir::$(yarn cache dir)" - name: cache global yarn cache - uses: actions/cache@v2 + uses: actions/cache@v3 if: steps.cache-modules.outputs.cache-hit != 'true' with: path: ${{ steps.yarn-cache.outputs.dir }} diff --git a/.github/workflows/verify_windows.yml b/.github/workflows/verify_windows.yml index 7156bec4f6..bb8aa9e5aa 100644 --- a/.github/workflows/verify_windows.yml +++ b/.github/workflows/verify_windows.yml @@ -24,11 +24,11 @@ jobs: INTEGRATION_TEST_AZURE_TOKEN: ${{ secrets.INTEGRATION_TEST_AZURE_TOKEN }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 # Beginning of yarn setup, keep in sync between all workflows, see ci.yml - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 + uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth @@ -36,7 +36,7 @@ jobs: id: yarn-cache run: echo "::set-output name=dir::$(yarn cache dir)" - name: cache global yarn cache - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ${{ steps.yarn-cache.outputs.dir }} key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }} diff --git a/.gitignore b/.gitignore index 60d0d6da3b..a54955ac73 100644 --- a/.gitignore +++ b/.gitignore @@ -130,6 +130,7 @@ site # Local configuration files *.local.yaml +!packages/create-app/templates/default-app/app-config.local.yaml # Sensitive credentials *-credentials.yaml diff --git a/ADOPTERS.md b/ADOPTERS.md index 904654890d..3915aee949 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -26,7 +26,7 @@ _If you're using Backstage in your organization, please try to add your company | [Acast.com](https://acast.com) | [Olle Lundberg](https://github.com/lndbrg) | Developer portal with tech docs, service catalog and a bunch of other internal tooling | | [Lunar](https://lunar.app) | [Jacob Valdemar](https://github.com/JacobValdemar) | Internal developer portal for service overview and insights, API documentation, technical guides, onboarding guides and RFC's. | | [Trendyol](https://trendyol.com) | [Gamze Senturk](https://github.com/gmzsenturk), [Mert Can Bilgic](https://github.com/mertcb) | The Developer Portal has been called `Pandora`. Provides an overview of Trendyol tech ecosystem. TechDocs, Catalog, Custom Plugins and Theme. | -| [Peloton](https://www.onepeloton.com/) | [Matt Waldron](https://github.com/daftgopher) | Creating our first developer portal and tech-docs. Exploring Service Catalog, Tech Insights and Cost Insights as well. | +| [Peloton](https://www.onepeloton.com/) | [Matt Waldron](https://github.com/daftgopher) | Creating our first developer portal and tech-docs. Exploring Service Catalog, Tech Insights and Cost Insights as well. | | [TELUS](https://telus.com) | [Seb Barre](https://github.com/sbarre) | The Go-to place to find answers about development and delivery at TELUS. | | [Brex](https://www.brex.com/) | [Vamsi Chitters](https://github.com/vamsikc) | A centralized UI to understand how a service fits in the whole Brex architecture and manage a team’s engineering dependencies. | | [Oriflame](https://www.oriflame.com/) | [Oriflame](https://github.com/oriflame) | Internal developer portal for services, single page apps and packages overview, API documentation, technical guides, tech-radar and more. | @@ -34,7 +34,7 @@ _If you're using Backstage in your organization, please try to add your company | [Netflix](https://www.netflix.com/) | [bleathem](https://github.com/bleathem) | Our Backstage implementation will be the front door to a unified experience connecting our internal platform products across important workflows with integrated knowledge and support. | | [b.well](https://www.icanbwell.com/) | [Jacob Rosales](https://github.com/jrosales) | Foundation for our engineering portal and cloud insights. | | [PagerDuty](https://www.pagerduty.com/) | [Mark Shaw](https://github.com/markshawtoronto) | Developer portal, initially focused on software templates and tech-docs. | -| [MoonShiner](https://moonshiner.at) | [Fabian Hippmann](https://github.com/FabianHippmann) | Developer portal - helps us keep track of our customer projects, onboard new developers & improve our development process 🌕🚀🧑‍🚀 | +| [MoonShiner](https://moonshiner.at) | [Fabian Hippmann](https://github.com/FabianHippmann) | Developer portal - helps us keep track of our customer projects, onboard new developers & improve our development process 🌕🚀🧑‍🚀 | | [FundApps](https://www.fundapps.co/) | [Elliot Greenwood](https://github.com/egnwd) | Developer Portal - A place for us to keep track of our projects and documentation for all services and processes | | [DAZN](https://dazn.com/) | [Lou Bichard](https://twitter.com/loujaybee), [Marco Crivellaro](https://github.com/crivetechie), [Alex Hollerith](mailto:alex.hollerith@dazn.com) | Ingesting all of DAZN's repos for the catalog, migrating our internal platform apps (pull request boards, release information, inner source marketplace etc) to Backstage plugins (where applicable). | | [HelloFresh](https://www.hellofresh.de/) | [@iammuho](https://github.com/iammuho), [@ElenaForester](https://github.com/ElenaForester), [@diegomarangoni](https://github.com/diegomarangoni) | Our developer portal at HelloFresh - Spread across an organisation of 500+ engineers globally. | @@ -113,8 +113,17 @@ _If you're using Backstage in your organization, please try to add your company | [SIX](https://www.six-group.com/) | [@jbadeau](https://github.com/jbadeau), [@tomassatka](https://github.com/tomassatka) | Internal DevOps portal hosting our software and dataset catalog, as well as custom plugins for observability, service virtualization, deployments, incident managment and quality metrics. | | [Raiffeisen Bank International](https://www.rbinternational.com/) | [Daniel Baumgartner](https://github.com/dabarbi) | From developers for developers: software catalog, techdocs and heavy use of scaffolder to drive reuse on engineering level forward. Part of inner source initiative. Multi national setup coming. | | [Spread Group](https://www.spreadgroup.com/) | [Luna Stadler](https://github.com/heyLu), [Iván González](https://github.com/ivangonzalezacuna) | Internal Developer Portal, an overview of all running software, architecture documentation and more; replacing and unifying a variety of internal tools. | -| [RD Station](https://rdstation.com) | [Rogerio Angeliski](https://github.com/angeliski), [Paula Assis](https://github.com/paulassis), [Guilherme Eric](https://github.com/guilhermeeric), [Daniela Adamatti](https://github.com/daniadamatti), [Luana Negreiros](https://github.com/luananegreiros) | Developer portal, scaffolding, services catalog. We are looking to centralize automations and information for the whole engineering team . | | -| [Resuelve Tu Deuda](https://resuelve.mx)| [Iván Álvarez](https://github.com/ivanhoe), [Jorge Medina](https://github.com/jorgearma1982) | Internal developer portal, service catalog, tech docs, api doc -| [Pachama](https://pachama.com/) | [Aron Gates](https://github.com/agates4) | Internal Developer Portal, a catalog of all microservices, architecture documentation, and templates to generate developer resources. | -| [SEEK](https://www.seek.com.au) | [Jahred Hope](https://github.com/jahredhope) | Developer portal for developer tooling and technical documentation. | -| [Marks & Spencer](https://www.marksandspencer.com/) | [Kamal Cheriyath](https://github.com/kcheriyath) | Centralised discovery, adoption and devops automation hub for Engineering & Architecture. | \ No newline at end of file +| [RD Station](https://rdstation.com) | [Rogerio Angeliski](https://github.com/angeliski), [Paula Assis](https://github.com/paulassis), [Guilherme Eric](https://github.com/guilhermeeric), [Daniela Adamatti](https://github.com/daniadamatti), [Luana Negreiros](https://github.com/luananegreiros) | Developer portal, scaffolding, services catalog. We are looking to centralize automations and information for the whole engineering team . | +| [Resuelve Tu Deuda](https://resuelve.mx) | [Iván Álvarez](https://github.com/ivanhoe), [Jorge Medina](https://github.com/jorgearma1982) | Internal developer portal, service catalog, tech docs, api doc | +| [Pachama](https://pachama.com/) | [Aron Gates](https://github.com/agates4) | Internal Developer Portal, a catalog of all microservices, architecture documentation, and templates to generate developer resources. | +| [SEEK](https://www.seek.com.au) | [Jahred Hope](https://github.com/jahredhope) | Developer portal for developer tooling and technical documentation. | +| [Marks & Spencer](https://www.marksandspencer.com/) | [Kamal Cheriyath](https://github.com/kcheriyath) | Centralised discovery, adoption and devops automation hub for Engineering & Architecture. | +| [McKesson](https://www.mckesson.com/) | [Agnel Antony](https://github.com/aantony2) | Internal Developer Platform for developer gated CI/CD templates, technical documentation, cloud automation service catalog, etc. | +| [World Fuel Services](https://www.wfscorp.com/) | [Anirudh Kurapathi](https://github.com/anirudhkurapati), [Alex Kwon](https://github.com/alexkwon), [Lester Hernandez](https://github.com/lhernandez-wfscorp), [Avi Boru](https://github.com/aviboru), [Vardhan Annapureddy](https://github.com/harshaaws) | Internal Developer Portal, service catalog product, API's, Software Templates, tech docs and more. | +| [leboncoin](https://www.leboncoin.fr/) | [Andy Ladjadj](https://github.com/aladjadj) | Centralize our multiple UI in a single portal. Simplify onbording, new features and harmonize how people search information. Core features (catalog,api,docs,scafolder) are good to start the adoption. status: internal beta. | +| [Contentful](https://www.contentful.com) | [James Bourne](https://github.com/jamesmbourne) | Centralized documentation of service ownership, APIs, and documentation, and new service creation with a custom scaffolder - [full case study with Roadie](https://roadie.io/case-studies/maintaining-velocity-through-hypergrowth-contentful/). | +| [Back Market](https://www.backmarket.com) | [Sami Farhat](https://github.com/skfarhat) | Internal Developer Portal featuring catalog, tech-radar, ownership management, component creation (scaffolder) and centralized infrastructure management -- probably more to come. | +| [Avalia Systems](https://avalia.io) | [Olivier Liechti](https://github.com/wasadigi), [Fabio Velloso](https://github.com/fabiovelloso) | Innersource, software analytics, knowledge base for 360 software assessments, collaborative applications, hub for tracking and sharing IP assets. | +| [Albert Heijn](https://ah.technology) | [Joost Hofman](https://github.com/joosthofman), [Reindrich Geerman](https://github.com/reinst) | Single point of entry for all our engineers (Developer portal), Tech radar, catalog, templates (paved roads) and tech documentation. | +| [Wise, formerly TransferWise](https://wise.com) | [Andrew Beveridge](https://github.com/beveradb) | It's early days for us, we're trying to start small with catalog, tech docs and unified search. Future ambitious vision includes scaffolder for one-click component addition, building out integrations with CI/CD tooling, kubernetes clusters, monitoring/alerting tooling etc. and aiming for a frictionless "golden path" for engineers! 🚀 | + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e4aad09207..eb60bc48e0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,7 +16,7 @@ No one likes bugs. Report bugs as an issue [here](https://github.com/backstage/b ### Fix bugs or build new features -Look through the GitHub issues for [bugs](https://github.com/backstage/backstage/labels/bugs), [good first issues](https://github.com/backstage/backstage/labels/good%20first%20issue) or [help wanted](https://github.com/backstage/backstage/labels/help%20wanted). +Look through the GitHub issues for [bugs](https://github.com/backstage/backstage/labels/bug), [good first issues](https://github.com/backstage/backstage/labels/good%20first%20issue) or [help wanted](https://github.com/backstage/backstage/labels/help%20wanted). ### Build a plugin @@ -78,7 +78,7 @@ If you're contributing to the backend or CLI tooling, be mindful of cross-platfo Also be sure to skim through our [ADRs](docs/architecture-decisions) to see if they cover what you're working on. In particular [ADR006: Avoid React.FC and React.SFC](docs/architecture-decisions/adr006-avoid-react-fc.md) is one to look out for. -If there are any updates in `markdown` file please make sure to run `yarn run lint:docs`. Though it is checked on `lint-staged`. It is required to install [vale](https://docs.errata.ai/vale/install) `1.4.0` separately and make sure it is accessed by global command. +If there are any updates in `markdown` file please make sure to run `yarn run lint:docs`. Though it is checked on `lint-staged`. It is required to install [vale](https://docs.errata.ai/vale/install) separately and make sure it is accessed by global command. ## Developer Certificate of Origin diff --git a/STYLE.md b/STYLE.md index 391b31ce50..b28c5da4fa 100644 --- a/STYLE.md +++ b/STYLE.md @@ -146,7 +146,7 @@ There are a few things to pay attention to, in order to make the documentation s API documenter will not recognize arrow functions as functions, but rather as a constant that shows up in the list of exported variables. By declaring functions using the `function` keyword, they will show up in the list of functions. They will also get a much nicer documentation page for the individual function that shows information about parameters and return types. -This also extends to React components, since API documenter doesn't have any special handling of those. By always defining exported React components using the `function` keyword, we make them show up among the list of functions in the API reference, where they are then easily discoverable through the `(props)` args (which you should be sure to include!). +This also extends to React components, since API documenter doesn't have any special handling of those. By always defining exported React components using the `function` keyword, we make them show up among the list of functions in the API reference, where they are then easily discoverable through the `(props)` argument (which you should be sure to include!). ![image](https://user-images.githubusercontent.com/4984472/133120461-59d74c3e-ebd9-44f9-900d-cc30f54a3cd2.png) diff --git a/app-config.yaml b/app-config.yaml index b3ca3c7203..eb51c59847 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -203,8 +203,6 @@ catalog: - Component - API - Resource - - Group - - User - Template - System - Domain @@ -243,7 +241,7 @@ catalog: # Add a location here to ingest it, for example from a URL: # # - type: url - # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-components.yaml + # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml # # For local development you can use a file location instead: # @@ -253,33 +251,23 @@ catalog: # File locations are relative to the current working directory of the # backend, for example packages/backend/. - # Backstage example components + # Backstage example entities - type: file - target: ../catalog-model/examples/all-components.yaml + target: ../catalog-model/examples/all.yaml + # Backstage example groups and users + - type: file + target: ../catalog-model/examples/acme-corp.yaml + rules: + - allow: [User, Group] # Example component for github-actions and TechDocs - type: file target: ../../plugins/github-actions/examples/sample.yaml # Example component for TechDocs - type: file target: ../../plugins/techdocs-backend/examples/documented-component/catalog-info.yaml - # Backstage example APIs - - type: file - target: ../catalog-model/examples/all-apis.yaml - # Backstage example resources - - type: file - target: ../catalog-model/examples/all-resources.yaml - # Backstage example systems - - type: file - target: ../catalog-model/examples/all-systems.yaml - # Backstage example domains - - type: file - target: ../catalog-model/examples/all-domains.yaml # Backstage example templates - type: file target: ../../plugins/scaffolder-backend/sample-templates/all-templates.yaml - # Backstage example groups and users - - type: file - target: ../catalog-model/examples/acme-corp.yaml # Backstage end-to-end tests of TechDocs - type: file target: ../../cypress/e2e-fixture.catalog.info.yaml @@ -378,6 +366,8 @@ auth: clientId: ${AUTH_ATLASSIAN_CLIENT_ID} clientSecret: ${AUTH_ATLASSIAN_CLIENT_SECRET} scopes: ${AUTH_ATLASSIAN_SCOPES} + myproxy: + development: {} costInsights: engineerCost: 200000 products: diff --git a/contrib/chart/backstage/Chart.lock b/contrib/chart/backstage/Chart.lock index d18a48a655..2a304bd917 100644 --- a/contrib/chart/backstage/Chart.lock +++ b/contrib/chart/backstage/Chart.lock @@ -1,6 +1,6 @@ dependencies: - name: postgresql repository: https://charts.bitnami.com/bitnami - version: 9.8.12 -digest: sha256:549b9a0cdf7b2e0ad949ebad853a467bf320928970a946fb0ef7e13e9bdb7a10 -generated: "2022-04-21T07:02:48.301491565Z" + version: 11.1.28 +digest: sha256:91deeef78a140252c1ec9fbdf011e5584fc26b67f5a5dc9b6c90be2eee838499 +generated: "2022-05-06T14:02:33.227808666Z" diff --git a/contrib/chart/backstage/Chart.yaml b/contrib/chart/backstage/Chart.yaml index 9770ef665c..43c1615d2f 100644 --- a/contrib/chart/backstage/Chart.yaml +++ b/contrib/chart/backstage/Chart.yaml @@ -18,7 +18,7 @@ sources: dependencies: - name: postgresql condition: postgresql.enabled - version: 9.8.12 + version: 11.1.28 repository: https://charts.bitnami.com/bitnami maintainers: diff --git a/contrib/docs/tutorials/help-im-behind-a-corporate-proxy.md b/contrib/docs/tutorials/help-im-behind-a-corporate-proxy.md index 8a4d6fb273..9ec3f1ca64 100644 --- a/contrib/docs/tutorials/help-im-behind-a-corporate-proxy.md +++ b/contrib/docs/tutorials/help-im-behind-a-corporate-proxy.md @@ -64,8 +64,8 @@ if (process.env.HTTPS_PROXY) { If your development environment is in the cloud (like with [AWS Cloud9](https://aws.amazon.com/cloud9/) or an instance of [Theia](https://theia-ide.org/)), you will need to update your configuration. -You will probably need to make some changes in `app-config.yaml` (or another config file like `app-config.local.yaml` if you've created it, see the [configuration doc](https://backstage.io/docs/conf/#supplying-configuration)). -The exact values will depend on your setup but for instance, if your public url is `https://your-public-url.com` and the port `3000` and `8080` are open: +You will probably need to make some changes in `app-config.yaml` (or another config file like `app-config.local.yaml` if you've created it, see the [configuration doc](https://backstage.io/docs/conf/#supplying-configuration)). +The exact values will depend on your setup but for instance, if your public URL is `https://your-public-url.com` and the port `3000` and `8080` are open: ```yaml app: diff --git a/cypress/README.md b/cypress/README.md index 2bb310e300..15338fe371 100644 --- a/cypress/README.md +++ b/cypress/README.md @@ -18,6 +18,17 @@ yarn install yarn cypress run ``` +Note that the tests expect to run against a built application, so you'll want to +run a Docker image and either create an `app-config.cypress.yaml` or pass the +necessary environment variables: + +```sh +yarn tsc +yarn build +yarn workspace example-backend build-image +docker run -p 7007:7007 example-backend +``` + You can open up the `cypress` console by using `yarn cypress open`. You can also run towards any Backstage installation by using the Cypress Environment Variable overrides. diff --git a/cypress/src/integration/plugins/catalog.spec.ts b/cypress/src/integration/plugins/catalog.spec.ts index 20dcdd9b8a..adf9b9a384 100644 --- a/cypress/src/integration/plugins/catalog.spec.ts +++ b/cypress/src/integration/plugins/catalog.spec.ts @@ -25,5 +25,62 @@ describe('Catalog', () => { cy.contains('Owned (10)').should('be.visible'); }); + + it('Should navigate to a docs tab', () => { + cy.loginAsGuest(); + + cy.visit('/catalog'); + + cy.contains('techdocs-e2e-fixture').click(); + + cy.location().should(loc => { + expect(loc.pathname).to.eq( + '/catalog/default/component/techdocs-e2e-fixture', + ); + }); + + cy.getCatalogDocsTab().click(); + + cy.getTechDocsShadowRoot() + .find('h1') + .contains('Home page') + .should('be.visible'); + }); + + it('Should navigate to a sub-route in docs tab', () => { + cy.loginAsGuest(); + + cy.visit('/catalog'); + + cy.contains('techdocs-e2e-fixture').click(); + + cy.location().should(loc => { + expect(loc.pathname).to.eq( + '/catalog/default/component/techdocs-e2e-fixture', + ); + }); + + cy.getCatalogDocsTab().click(); + + cy.getTechDocsShadowRoot() + .find('h1') + .contains('Home page') + .should('be.visible'); + + cy.getTechDocsShadowRoot().within(() => { + cy.getTechDocsNavigation().find('a').contains('Sub-page 1').click(); + }); + + cy.location().should(loc => { + expect(loc.pathname).to.eq( + '/catalog/default/component/techdocs-e2e-fixture/docs/sub-page-one/', + ); + }); + + cy.getTechDocsShadowRoot() + .find('h1') + .contains('Sub-page 1') + .should('be.visible'); + }); }); }); diff --git a/cypress/src/integration/plugins/techdocs.spec.ts b/cypress/src/integration/plugins/techdocs.spec.ts index d6049ea53d..1e69ba46ad 100644 --- a/cypress/src/integration/plugins/techdocs.spec.ts +++ b/cypress/src/integration/plugins/techdocs.spec.ts @@ -38,6 +38,7 @@ describe('TechDocs', () => { it('should navigate to the TechDocs home page from the URL', () => { cy.visit('/docs'); + cy.wait(500); cy.contains('Documentation'); @@ -45,15 +46,12 @@ describe('TechDocs', () => { it('should navigate to a specific TechDocs entity from the "Overview" tab', () => { cy.visit('/docs'); - cy.contains('techdocs-e2e-fixture') - .parents() - .eq(2) - .contains('Read Docs') - .click(); + + cy.contains('techdocs-e2e-fixture').click(); cy.location().should(loc => { expect(loc.pathname).to.eq( - '/docs/default/Component/techdocs-e2e-fixture', + '/docs/default/component/techdocs-e2e-fixture', ); }); }); @@ -84,7 +82,6 @@ describe('TechDocs', () => { cy.visit( '/docs/default/Component/techdocs-e2e-fixture/sub-page-two#section-23', ); - cy.waitSectionTwoPage(); // This is used to test the post-render behavior of the techdocs Reader cy.wait(500); @@ -101,10 +98,43 @@ describe('TechDocs', () => { }); }); + describe('Rendering TechDocs Addons', () => { + it('should render a content addon', () => { + cy.visit('/docs/default/Component/techdocs-e2e-fixture'); + + cy.contains('e2e Fixture Documentation'); + + // highlight a snippet of text + cy.getTechDocsShadowRoot() + .find('article > p') + .then($el => { + const el = $el[0]; + const document = el.ownerDocument; + const range = document.createRange(); + range.selectNodeContents(el); + document?.getSelection()?.removeAllRanges(); + document?.getSelection()?.addRange(range); + }); + + cy.document().trigger('selectionchange'); + + // wait for new issue default debounce time + cy.wait(600); + + // assert that the new issue button has a right url + cy.getTechDocsShadowRoot() + .contains('Open new Github issue') + .should( + 'have.attr', + 'href', + 'https://github.com/backstage/backstage/issues/new?title=Documentation%20feedback%3A%20This%20is%20a%20basic%20documentation%20used%20for%20end-to-end%20tests.&body=%23%23%20Documentation%20Feedback%20%F0%9F%93%9D%0A%0A%20%23%23%23%23%20The%20highlighted%20text%3A%20%0A%0A%20%3E%20This%20is%20a%20basic%20documentation%20used%20for%20end-to-end%20tests.%0A%0A%20%23%23%23%23%20The%20comment%20on%20the%20text%3A%20%0A%20_%3Ereplace%20this%20line%20with%20your%20comment%3C_%0A%0A%20___%0ABackstage%20URL%3A%20%3Chttp%3A%2F%2Flocalhost%3A7007%2Fdocs%2Fdefault%2FComponent%2Ftechdocs-e2e-fixture%3E%20%0AMarkdown%20URL%3A%20%3Chttps%3A%2F%2Fgithub.com%2Fbackstage%2Fbackstage%2Fblob%2Fmaster%2Fcypress%2Ffixtures%2Fdocs%2Findex.md%3E', + ); + }); + }); + describe('Navigating within TechDocs', () => { it('should navigate to a specific TechDocs page via the navigation bar', () => { cy.visit('/docs/default/Component/techdocs-e2e-fixture'); - cy.waitHomePage(); cy.getTechDocsShadowRoot().within(() => { cy.getTechDocsNavigation() @@ -118,14 +148,18 @@ describe('TechDocs', () => { describe('Navigating within a TechDocs page', () => { beforeEach(() => { cy.visit('/docs/default/Component/techdocs-e2e-fixture/sub-page-two'); - cy.waitSectionTwoPage(); }); + it('should navigate to a specific fragment within the page via the table of contents - Level 1', () => { return cy.getTechDocsShadowRoot().within(() => { // Section 3 - cy.getTechDocsTableOfContents().within(() => { - cy.get('> div > div > nav > ul > li:nth-child(3) > a').click(); - }); + cy.getTechDocsTableOfContents() + .find('a') + .contains('Section 2.3') + .click(); + + // wait scroll timeout + cy.wait(500); cy.isInViewport('#section-23'); }); @@ -136,20 +170,20 @@ describe('TechDocs', () => { cy.isNotInViewport('#sub-section-222'); // Section 2.2 cy.getTechDocsTableOfContents() - .find( - '> div > div > nav > ul > li:nth-child(2) > nav > ul > li:nth-child(2) > a', - ) + .find('a') + .contains('Section 2.2.2') .click(); + // wait scroll timeout + cy.wait(500); + cy.isInViewport('#sub-section-222'); }); }); it('should navigate to a specific TechDocs page fragment from a link', () => { return cy.getTechDocsShadowRoot().within(() => { - cy.get('.md-content > article') - .contains('Link to Section 1.1') - .click(); + cy.get('article').contains('Link to Section 1.1').click(); cy.location().should(loc => { expect(loc.pathname).to.eq( @@ -162,7 +196,7 @@ describe('TechDocs', () => { it('should navigate to the next page within a TechDocs entity', () => { return cy.getTechDocsShadowRoot().within(() => { - cy.get('.md-footer-nav__link--next').click(); + cy.get('footer a').contains('Sub-page 3').click(); cy.location().should(loc => { expect(loc.pathname).to.eq( @@ -174,7 +208,7 @@ describe('TechDocs', () => { it('should navigate to the previous page within a TechDocs entity', () => { return cy.getTechDocsShadowRoot().within(() => { - cy.get('.md-footer-nav__link--prev').click(); + cy.get('footer a').contains('Sub-page 1').click(); cy.location().should(loc => { expect(loc.pathname).to.eq( diff --git a/cypress/src/support/commands.ts b/cypress/src/support/commands.ts index 666dc157d7..02723ffc90 100644 --- a/cypress/src/support/commands.ts +++ b/cypress/src/support/commands.ts @@ -25,7 +25,7 @@ Cypress.Commands.add('loginAsGuest', () => { }); Cypress.Commands.add('getTechDocsShadowRoot', () => { - cy.get('[data-testid="techdocs-content-shadowroot"]').shadow(); + cy.get('[data-testid="techdocs-native-shadowroot"]').shadow(); }); Cypress.Commands.add('isNotInViewport', element => { @@ -57,11 +57,15 @@ Cypress.Commands.add('isInViewport', element => { }); Cypress.Commands.add('getTechDocsTableOfContents', () => { - cy.get('[data-md-component="toc"]'); + cy.get('[data-md-type="toc"]'); }); Cypress.Commands.add('getTechDocsNavigation', () => { - cy.get('[data-md-component="navigation"]'); + cy.get('[data-md-type="navigation"]'); +}); + +Cypress.Commands.add('getCatalogDocsTab', () => { + cy.get('button[role="tab"]').contains('Docs'); }); Cypress.Commands.add('mockSockJSNode', () => { diff --git a/cypress/src/types.d.ts b/cypress/src/types.d.ts index 838f5571ed..181b608a9b 100644 --- a/cypress/src/types.d.ts +++ b/cypress/src/types.d.ts @@ -42,6 +42,11 @@ declare namespace Cypress { * @example cy.getTechDocsNavigation */ getTechDocsNavigation(): Chainable; + /** + * Get the Catalog docs tab + * @example cy.getCatalogDocsTab + */ + getCatalogDocsTab(): Chainable; /** * Intercept the TechDocs API calls * @example cy.interceptTechDocsAPICalls diff --git a/cypress/yarn.lock b/cypress/yarn.lock index bb9aaecb89..c667cc6c03 100644 --- a/cypress/yarn.lock +++ b/cypress/yarn.lock @@ -1036,9 +1036,9 @@ type-fest@^0.21.3: integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== typescript@^4.1.3: - version "4.1.3" - resolved "https://registry.npmjs.org/typescript/-/typescript-4.1.3.tgz#519d582bd94cba0cf8934c7d8e8467e473f53bb7" - integrity sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg== + version "4.6.4" + resolved "https://registry.npmjs.org/typescript/-/typescript-4.6.4.tgz#caa78bbc3a59e6a5c510d35703f6a09877ce45e9" + integrity sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg== universalify@^2.0.0: version "2.0.0" diff --git a/docs/assets/permission/disabled-unregister-entity.png b/docs/assets/permission/disabled-unregister-entity.png new file mode 100644 index 0000000000..f7975a0ced Binary files /dev/null and b/docs/assets/permission/disabled-unregister-entity.png differ diff --git a/docs/assets/permission/permission-framework-overview.drawio.svg b/docs/assets/permission/permission-framework-overview.drawio.svg new file mode 100644 index 0000000000..c7e628f8a6 --- /dev/null +++ b/docs/assets/permission/permission-framework-overview.drawio.svg @@ -0,0 +1,4 @@ + + + +
User
User
Plugin
Plugin
Permission Response
Permission Response
Permission Framework
Permission Framework
Action
(Read request)
Action...
Resources
Resources
Resource read permission
Resource read...
Permission Policy
Permission Policy
Permission Request
Permission Request
Provided by Backstage
Provided by Backstage
Configured by integrators
Configured by integrators
Configured by plugin authors
Configured by plugin authors
Permission Backend
Permission hooks
Integration helpers
...
Permission Backend...
1
1
2
2
3
3
4
4
Text is not SVG - cannot display
\ No newline at end of file diff --git a/docs/assets/permission/permission-todo-list-page.png b/docs/assets/permission/permission-todo-list-page.png new file mode 100644 index 0000000000..cec2d66485 Binary files /dev/null and b/docs/assets/permission/permission-todo-list-page.png differ diff --git a/docs/assets/techdocs/addon-locations.png b/docs/assets/techdocs/addon-locations.png new file mode 100644 index 0000000000..0541f35af5 Binary files /dev/null and b/docs/assets/techdocs/addon-locations.png differ diff --git a/docs/assets/techdocs/report-issue-addon.png b/docs/assets/techdocs/report-issue-addon.png new file mode 100644 index 0000000000..4c61935f2f Binary files /dev/null and b/docs/assets/techdocs/report-issue-addon.png differ diff --git a/docs/assets/techdocs/report-issue-template.png b/docs/assets/techdocs/report-issue-template.png new file mode 100644 index 0000000000..35dca05e1f Binary files /dev/null and b/docs/assets/techdocs/report-issue-template.png differ diff --git a/docs/auth/add-auth-provider.md b/docs/auth/add-auth-provider.md index 07d6869fe3..bd2bcd1694 100644 --- a/docs/auth/add-auth-provider.md +++ b/docs/auth/add-auth-provider.md @@ -74,7 +74,7 @@ export class ProviderAAuthProvider implements OAuthProviderHandlers { clientID: options.clientId, clientSecret: options.clientSecret, callbackURL: options.callbackUrl, - passReqToCallback: false as true, + passReqToCallback: false, response_type: 'code', /// ... etc } @@ -153,7 +153,6 @@ export const createOktaProvider: AuthProviderFactory = ({ // Wrap the OAuthProviderHandlers with OAuthProvider, which implements AuthProviderRouteHandlers return OAuthProvider.fromConfig(globalConfig, provider, { - disableRefresh: false, providerId, tokenIssuer, }); @@ -230,7 +229,7 @@ name. ### Test the new provider You can `curl -i localhost:7007/api/auth/providerA/start` and which should -provide a `302` redirect with a `Location` header. Paste the url from that +provide a `302` redirect with a `Location` header. Paste the URL from that header into a web browser and you should be able to trigger the authorization flow. diff --git a/docs/auth/github/provider.md b/docs/auth/github/provider.md index 14b99bfea5..a8c6020cae 100644 --- a/docs/auth/github/provider.md +++ b/docs/auth/github/provider.md @@ -50,7 +50,7 @@ The GitHub provider is a structure with three configuration keys: - `clientSecret`: The client secret tied to the generated client ID. - `enterpriseInstanceUrl` (optional): The base URL for a GitHub Enterprise instance, e.g. `https://ghe..com`. Only needed for GitHub Enterprise. -- `callbackUrl` (optional): The callback url that GitHub will use when +- `callbackUrl` (optional): The callback URL that GitHub will use when initiating an OAuth flow, e.g. `https://your-intermediate-service.com/handler`. Only needed if Backstage is not the immediate receiver (e.g. one OAuth app for many backstage instances). diff --git a/docs/auth/google/gcp-iap-auth.md b/docs/auth/google/gcp-iap-auth.md index cdafeb1317..1ebfcaf15b 100644 --- a/docs/auth/google/gcp-iap-auth.md +++ b/docs/auth/google/gcp-iap-auth.md @@ -1,5 +1,5 @@ --- -id: provider +id: gcp-iap-auth title: Google Identity-Aware Proxy Provider sidebar_label: Google IAP # prettier-ignore @@ -43,7 +43,8 @@ Add a `providerFactories` entry to the router in `packages/backend/plugin/auth.ts`. ```ts -import { createGcpIapProvider } from '@backstage/plugin-auth-backend'; +import { providers } from '@backstage/plugin-auth-backend'; +import { stringifyEntityRef } from '@backstage/catalog-model'; export default async function createPlugin( env: PluginEnvironment, @@ -54,7 +55,7 @@ export default async function createPlugin( database: env.database, discovery: env.discovery, providerFactories: { - 'gcp-iap': createGcpIapProvider({ + 'gcp-iap': providers.gcpIap.create({ // Replace the auth handler if you want to customize the returned user // profile info (can be left out; the default implementation is shown // below which only returns the email). You may want to amend this code @@ -71,12 +72,10 @@ export default async function createPlugin( // Somehow compute the Backstage token claims. Just some dummy code // shown here, but you may want to query your LDAP server, or // GSuite or similar, based on the IAP token sub/email claims - const id = `user:default/${iapToken.email.split('@')[0]}`; - const fullEnt = ['group:default/team-name']; - const token = await ctx.tokenIssuer.issueToken({ - claims: { sub: id, ent: fullEnt }, - }); - return { id, token }; + const id = iapToken.email.split('@')[0]; + const sub = stringifyEntityRef({ kind: 'User', name: id }); + const ent = [sub, stringifyEntityRef({ kind: 'Group', name: 'team-name' }); + return ctx.issueToken({ claims: { sub, ent } }); }, }, }), @@ -91,23 +90,8 @@ sign-in mechanism to poll that endpoint through the IAP, on the user's behalf. ## Frontend Changes -All Backstage apps need a `SignInPage` to be configured. Its purpose is to -establish who the user is and what their identifying credentials are, blocking -rendering the rest of the UI until that's complete, and then keeping those -credentials fresh. - -When using IAP Proxy authentication, the Backstage UI will only be loaded once -the user has already successfully completely authenticated themselves with the -IAP and has an active session, so we don't want to make the user have to go -through a _second_ layer of authentication flows after that. - -As such, we want to not display a sign-in page visually at all. Instead, we will -pick a `SignInPage` implementation component which knows how to silently make -requests to the backend provider we configured above, and just trusting its -output to properly represent the current user. Luckily, Backstage comes with a -component for this purpose out of the box. - -Update your `createApp` call in `packages/app/src/App.tsx`, as follows. +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'; @@ -117,5 +101,4 @@ Update your `createApp` call in `packages/app/src/App.tsx`, as follows. + SignInPage: props => , ``` -After this, your app should be ready to leverage the Identity-Aware Proxy for -authentication! +See the [Sign-In with Proxy Providers](../index.md#sign-in-with-proxy-providers) section for more information. diff --git a/docs/auth/identity-resolver.md b/docs/auth/identity-resolver.md index 6544b0de7b..686c9a9dab 100644 --- a/docs/auth/identity-resolver.md +++ b/docs/auth/identity-resolver.md @@ -186,16 +186,59 @@ async ({ profile: { email } }, ctx) => { const ownershipRefs = getDefaultOwnershipRefs(entity); // The last step is to issue the token, where we might provide more options in the future. - const token = ctx.issueToken({ + return ctx.issueToken({ claims: { sub: stringifyEntityRef(entity), ent: ownershipRefs, }, }); - return { token }; }; ``` +## Sign-In without Users in the Catalog + +While populating the catalog with organizational data unlocks more powerful ways +to browse your software ecosystem, it might not always be a viable or prioritized +option. However, even if you do not have user entities populated in your catalog, you +can still sign in users. As there are currently no built-in sign-in resolvers for +this scenario you will need to implement your own. + +Signing in a user that doesn't exist in the catalog is as simple as skipping the +catalog lookup step from the above example. Rather than looking up the user, we +instead immediately issue a token using whatever information is available. One caveat +is that it can be tricky to determine the ownership references, although it can +be achieved for example through a lookup to an external service. You typically +want to at least use the user itself as a lone ownership reference. + +```ts +import { DEFAULT_NAMESPACE, stringifyEntityRef, } from '@backstage/catalog-model'; + +// This example only shows the resolver function itself. +async ({ profile }, ctx) => { + if (!profile.email) { + throw new Error( + 'Login failed, user profile does not contain an email', + ); + } + // We again use the local part of the email as the user name. + const [localPart] = profile.email.split('@'); + + // By using `stringifyEntityRef` we ensure that the reference is formatted correctly + const userEntityRef = stringifyEntityRef({ + kind: 'User', + name: localPart, + namespace: DEFAULT_NAMESPACE, + }); + + return ctx.issueToken({ + claims: { + sub: userEntityRef, + ent: [userEntityRef], + }, + }); +}, +``` + ## AuthHandler Similar to a custom sign-in resolver, you can also write a custom auth handler diff --git a/docs/auth/index.md b/docs/auth/index.md index 3beae72d0b..c34d244886 100644 --- a/docs/auth/index.md +++ b/docs/auth/index.md @@ -21,6 +21,7 @@ Backstage comes with many common authentication providers in the core library: - [GitHub](github/provider.md) - [GitLab](gitlab/provider.md) - [Google](google/provider.md) +- [Google IAP](google/gcp-iap-auth.md) - [Okta](okta/provider.md) - [OneLogin](onelogin/provider.md) - [OAuth2Proxy](oauth2-proxy/provider.md) @@ -126,6 +127,64 @@ allows allowing guest access: bindRoutes({ bind }) { ``` +## Sign-In with Proxy Providers + +Some auth providers are so-called "proxy" providers, meaning they're meant to be used +behind an authentication proxy. Examples of these are +[AWS ALB](https://github.com/backstage/backstage/blob/master/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md), +[GCP IAP](./google/gcp-iap-auth.md), and [OAuth2 Proxy](./oauth2-proxy/provider.md). + +When using a proxy provider, you'll end up wanting to use a different sign-in page, as +there is no need for further user interaction once you've signed in towards the proxy. +All the sign-in page needs to do is to call the `/refresh` endpoint of the auth providers +to get the existing session, which is exactly what the `ProxiedSignInPage` does. The only +thing you need to do to configure the `ProxiedSignInPage` is to pass the ID of the provider like this: + +```tsx +const app = createApp({ + ..., + components: { + SignInPage: props => , + }, +}); +``` + +A downside of this method is that it can be cumbersome to set up for local development. +As a workaround for this, it's possible to dynamically select the sign-in page based on +what environment the app is running in, and then use a different sign-in method for local +development, if one is needed at all. Depending on the exact setup, one might choose to +select the sign-in method based on the `process.env.NODE_ENV` environment variable, +by checking the `hostname` of the current location, or by accessing the configuration API +to read a configuration value. For example: + +```tsx +const app = createApp({ + ..., + components: { + SignInPage: props => { + const configApi = useApi(configApiRef); + if (configApi.getString('auth.environment') === 'development') { + return ( + + ); + } + return ; + }, + }, +}); +``` + +When using multiple auth providers like this, it's important that you configure the different +sign-in resolvers so that they resolve to the same identity regardless of the method used. + ## For Plugin Developers The Backstage frontend core APIs provide a set of Utility APIs for plugin developers diff --git a/docs/auth/oauth2-proxy/provider.md b/docs/auth/oauth2-proxy/provider.md index f366929fe8..966596e268 100644 --- a/docs/auth/oauth2-proxy/provider.md +++ b/docs/auth/oauth2-proxy/provider.md @@ -20,86 +20,52 @@ The provider configuration can be added to your `app-config.yaml` under the root ```yaml auth: - environment: development providers: oauth2proxy: {} ``` -Right now no configuration options are supported. To make use of the provider, -make sure that your `oauth2-proxy` is configured correctly and provides a custom -`X-OAUTH2-PROXY-ID-TOKEN` header. To do so, enable the -`--set-authorization-header=true` of your `oauth2-proxy` and forward the -`Authorization` header as `X-OAUTH2-PROXY-ID-TOKEN`. For more details check the -[configuration docs](https://oauth2-proxy.github.io/oauth2-proxy/configuration). +Right now no configuration options are supported, but the empty object is needed +to enable the provider in the auth backend. -_Example for kubernetes ingress:_ +To use the `oauth2proxy` provider you must also configure it with a sign-in resolver. +For more information about the sign-in process in general, see the +[Sign-in Identities and Resolvers](../identity-resolver.md) documentation. -```bash -# forward the authorization header from the auth request in the X-OAUTH2-PROXY-ID-TOKEN header -auth_request_set $name_upstream_authorization $upstream_http_authorization; -proxy_set_header X-OAUTH2-PROXY-ID-TOKEN $name_upstream_authorization; -``` +For the `oauth2proxy` provider, the sign-in result is quite different than other providers. +Because it's a proxy provider that can be configured to forward information through +arbitrary headers, the auth result simply just gives you access to the HTTP headers +of the incoming request. Using these you can either extract the information directly, +or grab ID or access tokens to look up additional information and/or validate the request. -## Adding the provider to the Backstage backend - -When using `oauth2proxy` auth you can configure it as described -[here](https://backstage.io/docs/auth/identity-resolver). - -- use the following code below to introduce changes to - `packages/backend/plugin/auth.ts`: +A simple sign-in resolver might for example look like this: ```ts - providerFactories: { - oauth2proxy: createOauth2ProxyProvider<{ - id: string; - email: string; - }>({ - authHandler: async input => { - const { email } = input.fullProfile; - - return { - profile: { - email, - }, - }; - }, - signIn: { - resolver: async (signInInfo, ctx) => { - const { preferred_username: id } = signInInfo.result.fullProfile; - const sub = `user:default/${id}`; - - const token = await ctx.tokenIssuer.issueToken({ - claims: { sub, ent: [`group:default/optional-user-group`] }, - }); - - return { id, token }; - }, - }, - }), - } +providers.oauth2Proxy.create({ + signIn: { + async resolver({ result }, ctx) { + const name = result.getHeader('x-forwarded-user'); + if (!name) { + throw new Error('Request did not contain a user') + } + return ctx.signInWithCatalogUser({ + entityRef: { name }, + }); + }, + }, +}), ``` ## Adding the provider to the Backstage frontend -All Backstage apps need a `SignInPage` to be configured. Its purpose is to -establish who the user is and what their identifying credentials are, blocking -rendering the rest of the UI until that's complete, and then keeping those -credentials fresh. - -When using the OAuth2-Proxy, the Backstage UI can only be accessed after the -user has already been authenticated at the proxy. Instead of showing the user -another login page when accessing Backstage, it will handle the login in the -background. Backstage provides for this case the `ProxiedSignInPage` component -which has no UI. - -Update your `createApp` call in `packages/app/src/App.tsx`, as follows. +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 => , ``` -After this, your app should be ready to leverage the OAuth2-Proxy for -authentication! +See the [Sign-In with Proxy Providers](../index.md#sign-in-with-proxy-providers) section for more information. diff --git a/docs/auth/troubleshooting.md b/docs/auth/troubleshooting.md new file mode 100644 index 0000000000..657809292a --- /dev/null +++ b/docs/auth/troubleshooting.md @@ -0,0 +1,101 @@ +--- +id: troubleshooting +title: Troubleshooting Auth +description: Guidance for various issues that one might run into when setting up authentication +--- + +Auth is tricky and doesn't always work as expected. Below you'll find some of the common +problems one might run into when setting up authentication, as well as some general +troubleshooting tips. + +## Sign-in fails with "... provider is not configured to support sign-in" + +This happens if you try to sign in using an auth provider that has not been +configured to allow sign-in. See the [Sign-in Identities and Resolvers](./identity-resolver.md) +page for information about how to configure and customize sign-in. + +As part of the 1.1 release of Backstage we removed the default implementations +of all sign-in resolvers. This was a necessary security fix as well as a step +towards providing more clarity in the configuration of the sign-in process. +You may encounter this error if you are upgrading from a previous version, in +which case you would need to configure a sign-in resolver as described above. + +## Auth fails with "Auth provider registered for ... is misconfigured" + +This will typically only happen during development, as in a production build the auth +backend will fail to start up altogether if a provider is misconfigured. + +Double check that your configuration for the provider is correct. Note that environment variables +such as `AUTH_OAUTH2_CLIENT_ID` must be set and will **NOT** be picked up from `.env` files. +You can use the `yarn backstage-cli config:print --lax` command to print your local configuration. + +The backend logs should also provide insight into why the configuration of the provider +fails. In working setup the backend should log something like `"Configuring provider, oauth2"`, +while it with otherwise log a warning like `"Skipping oauth2 auth provider, ..."`. + +## Auth fails with "Login failed; caused by NotAllowedError: Origin '...' is not allowed" + +This will happen if the origin of the configured `app.baseUrl` in the auth backend does not +match the origin that the frontend is being accessed at. Make sure that `app.baseUrl` matches +what a user sees in the browser address bar. + +If you wish to support multiple different origins at once, there is an experimental configuration +that lets you do this. The `auth.experimentalExtraAllowedOrigins` key accepts a list of origin +glob patterns where sign-in should be allowed from. + +## Sign-in fails with the error "User not found" + +Many built-in sign-in resolvers require user entities to be present in the catalog. This +error is encountered if authentication is successful, but a matching user entity is not +present in the catalog. If you wish to enable sign-in without having users be represented +in the catalog data, see the method that's documented in the +[sign-in resolver documentation](./identity-resolver.md#sign-in-without-users-in-the-catalog). + +If you want to customize this error message, you can create a custom sign-in resolver and +catch the `NotFoundError` thrown by `ctx.signInWithCatalogUser` or `ctx.findCatalogUser`. + +## General troubleshooting + +This section contains some general troubleshooting tips. + +### Stepping through authentication manually + +Authentication happens in a popup window that redirects to the identity providers authorization +endpoint. Once auth is complete the identity provider will redirect back to the auth backend, +which immediately serves a simple HTML page that posts the result back to the main window, which +then closes the popup. + +Because the popup is closed automatically it can sometimes be difficult to inspect the auth +flow, especially if one wants to debug the cookies that are being set. One way around this is to +manually head to the `/start` endpoint of the provider, which is the page that the popup will +point to initially. For example, if you want to troubleshoot GitHub auth locally, you'd head +to `http://localhost:7007/api/auth/github/start?env=development`. Note that the `env` parameter +needs to be set, and it's possible that you may need to set the `scope` parameter for some providers +as well. + +Once you've stepped through the auth flow you should end up at the `/handler/frame` endpoint, which displays +an empty page. This is where the result is normally posted back to the main window, but since we've +reached it using the manual flow that won't happen. You can still inspect the result though, both +by viewing the source code of the page, or printing the value of the `authResponse` variable in the console. + +### Inspecting the refresh call + +If you're running into problems with session persistence, such as users being signed out when reloading +the page, it will be something that's going wrong with the call to the `/refresh` endpoint of the +auth provider. Head to the network inspector and filter by `/refresh`. Find the `GET` request towards +`/api/auth//refresh` and inspect the request. + +Note that extra calls to the refresh endpoint may be made by the frontend in order to check whether +auth providers have an existing session. This means that there might be multiple calls, including some +that are failing. Make sure you're looking at the refresh call to the provider that you're troubleshooting, +and don't worry about other failing refresh calls. + +### Inspecting the contents of a Backstage token + +The Backstage token that's issues during sign-in is a plain JWT. You can inspect the contents using +any tool that supports JWTs, or you can parse the payload yourself in for example the browser console +or a Node.js REPL: + +```js +atob(token.split('.')[1]); +``` diff --git a/docs/conf/writing.md b/docs/conf/writing.md index a4da1e3df0..aef9620373 100644 --- a/docs/conf/writing.md +++ b/docs/conf/writing.md @@ -161,7 +161,7 @@ $file: ./my-secret.txt The `$include` keyword can be used to load configuration values from an external file. It's able to load and parse data from `.json`, `.yml`, and `.yaml` files. -It's also possible to include a url fragment (`#`) to point to a value at the +It's also possible to include a URL fragment (`#`) to point to a value at the given path in the file, using a dot-separated list of keys. For example, the following would read `my-secret-key` from `my-secrets.json`: diff --git a/docs/deployment/docker.md b/docs/deployment/docker.md index 01bc6279f2..6471ada7f2 100644 --- a/docs/deployment/docker.md +++ b/docs/deployment/docker.md @@ -96,8 +96,8 @@ root of the repo to speed up the build by reducing build context size: ```text .git node_modules -packages -!packages/backend/dist +packages/*/src +packages/*/node_modules plugins ``` diff --git a/docs/features/kubernetes/configuration.md b/docs/features/kubernetes/configuration.md index cb30451c7d..246e4dcd43 100644 --- a/docs/features/kubernetes/configuration.md +++ b/docs/features/kubernetes/configuration.md @@ -85,12 +85,14 @@ array. Users will see this value in the Software Catalog Kubernetes plugin. This determines how the Kubernetes client authenticates with the Kubernetes cluster. Valid values are: -| Value | Description | -| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `serviceAccount` | This will use a Kubernetes [service account](https://kubernetes.io/docs/reference/access-authn-authz/service-accounts-admin/) to access the Kubernetes API. When this is used the `serviceAccountToken` field should also be set. | -| `google` | This will use a user's Google auth token from the [Google auth plugin](https://backstage.io/docs/auth/) to access the Kubernetes API. | -| `aws` | This will use AWS credentials to access resources in EKS clusters | -| `googleServiceAccount` | This will use the Google Cloud service account credentials to access resources in clusters | +| Value | Description | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `serviceAccount` | This will use a Kubernetes [service account](https://kubernetes.io/docs/reference/access-authn-authz/service-accounts-admin/) to access the Kubernetes API. When this is used the `serviceAccountToken` field should also be set. | +| `google` | This will use a user's Google auth token from the [Google auth plugin](https://backstage.io/docs/auth/) to access the Kubernetes API. | +| `aws` | This will use AWS credentials to access resources in EKS clusters | +| `googleServiceAccount` | This will use the Google Cloud service account credentials to access resources in clusters | +| `azure` | This will use [Azure Identity](https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview) to access resources in clusters | +| `oidc` | This will use [Oidc Tokens](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#openid-connect-tokens) to authenticate to the Kubernetes API. When this is used the `oidcTokenProvider` field should also be set. | ##### `clusters.\*.skipTLSVerify` @@ -114,6 +116,33 @@ kubectl -n get secret $(kubectl -n get sa ); default: @@ -78,6 +79,7 @@ export const searchPage = ( ); } @@ -149,7 +151,6 @@ import { import { PluginEnvironment } from '../types'; import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; import { Router } from 'express'; -import { Duration } from 'luxon'; export default async function createPlugin( env: PluginEnvironment, @@ -163,9 +164,9 @@ export default async function createPlugin( }); const every10MinutesSchedule = env.scheduler.createScheduledTaskRunner({ - frequency: Duration.fromObject({ minutes: 10 }), - timeout: Duration.fromObject({ minutes: 15 }), - initialDelay: Duration.fromObject({ seconds: 3 }), + frequency: { minutes: 10 }, + timeout: { minutes: 15 }, + initialDelay: { seconds: 3 }, }); indexBuilder.addCollator({ @@ -267,6 +268,7 @@ an example: ); // ... @@ -294,20 +296,18 @@ which are responsible for providing documents number of collators with the `IndexBuilder` like this: ```typescript -import { Duration } from 'luxon'; - const indexBuilder = new IndexBuilder({ logger: env.logger, searchEngine }); const every10MinutesSchedule = env.scheduler.createScheduledTaskRunner({ - frequency: Duration.fromObject({ minutes: 10 }), - timeout: Duration.fromObject({ minutes: 15 }), - initialDelay: Duration.fromObject({ seconds: 3 }), + frequency: { minutes: 10 }, + timeout: { minutes: 15 }, + initialDelay: { seconds: 3 }, }); const everyHourSchedule = env.scheduler.createScheduledTaskRunner({ - frequency: Duration.fromObject({ hours: 1 }), - timeout: Duration.fromObject({ minutes: 90 }), - initialDelay: Duration.fromObject({ seconds: 3 }), + frequency: { hours: 1 }, + timeout: { minutes: 90 }, + initialDelay: { seconds: 3 }, }); indexBuilder.addCollator({ @@ -332,9 +332,9 @@ a scheduled `TaskRunner` to pass into the `schedule` value, like this: ```typescript {3} const every10MinutesSchedule = env.scheduler.createScheduledTaskRunner({ - frequency: Duration.fromObject({ minutes: 10 }), - timeout: Duration.fromObject({ minutes: 15 }), - initialDelay: Duration.fromObject({ seconds: 3 }), + frequency: { minutes: 10 }, + timeout: { minutes: 15 }, + initialDelay: { seconds: 3 }, }); indexBuilder.addCollator({ diff --git a/docs/features/search/how-to-guides.md b/docs/features/search/how-to-guides.md index c5f53c905f..64787ec3c1 100644 --- a/docs/features/search/how-to-guides.md +++ b/docs/features/search/how-to-guides.md @@ -67,11 +67,24 @@ getting started guide. import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend'; ``` -2. Register the `DefaultTechDocsCollatorFactory` with the IndexBuilder. +2. If there isn't an existing schedule you'd like to run the collator on, be + sure to create it first. Something like... + +```typescript +import { Duration } from 'luxon'; + +const every10MinutesSchedule = env.scheduler.createScheduledTaskRunner({ + frequency: Duration.fromObject({ seconds: 600 }), + timeout: Duration.fromObject({ seconds: 900 }), + initialDelay: Duration.fromObject({ seconds: 3 }), +}); +``` + +3. Register the `DefaultTechDocsCollatorFactory` with the IndexBuilder. ```typescript indexBuilder.addCollator({ - defaultRefreshIntervalSeconds: 600, + schedule: every10MinutesSchedule, factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, { discovery: env.discovery, logger: env.logger, @@ -398,6 +411,29 @@ export class YourSearchEngine implements SearchEngine { } ``` +## How to customize search results highlighting styling + +The default highlighting styling for matched terms in search results is your +browsers default styles for the `` HTML tag. If you want to customize +how highlighted terms look you can follow Backstage's guide on how to +[Customize the look-and-feel of your App](https://backstage.io/docs/getting-started/app-custom-theme) +to create an override with your preferred styling. + +For example, the following will result in highlighted terms to be bold & underlined: + +```jsx +const highlightOverride = { + BackstageHighlightedSearchResultText: { + highlight: { + color: 'inherit', + backgroundColor: 'inherit', + fontWeight: 'bold', + textDecoration: 'underline', + }, + }, +}; +``` + [obj-mode]: https://nodejs.org/docs/latest-v14.x/api/stream.html#stream_object_mode [read-stream]: https://nodejs.org/docs/latest-v14.x/api/stream.html#stream_readable_streams [async-gen]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of#iterating_over_async_generators diff --git a/docs/features/software-catalog/external-integrations.md b/docs/features/software-catalog/external-integrations.md index a344f478d7..d6353d1185 100644 --- a/docs/features/software-catalog/external-integrations.md +++ b/docs/features/software-catalog/external-integrations.md @@ -231,7 +231,6 @@ You should now be able to add this class to your backend in `packages/backend/src/plugins/catalog.ts`: ```diff -+import { Duration } from 'luxon'; +import { FrobsProvider } from '../path/to/class'; export default async function createPlugin( @@ -248,8 +247,8 @@ You should now be able to add this class to your backend in + await env.scheduler.scheduleTask({ + id: 'run_frobs_refresh', + fn: async () => { await frobs.run(); }, -+ frequency: Duration.fromObject({ minutes: 30 }), -+ timeout: Duration.fromObject({ minutes: 10 }), ++ frequency: { minutes: 30 }, ++ timeout: { minutes: 10 }, + }); ``` diff --git a/docs/features/techdocs/README.md b/docs/features/techdocs/README.md index 2ac90f791b..5ac275b8b4 100644 --- a/docs/features/techdocs/README.md +++ b/docs/features/techdocs/README.md @@ -76,17 +76,23 @@ TechDocs packages: TechDocs promoted to v1.0! To understand how this change affects the package, please check out our [versioning policy](https://backstage.io/docs/overview/versioning-policy). -### **Future work 🔮** +**v1.2** 🚧 -Some of the following items are coming soon and some are potential ideas. +With the Backstage 1.2 release, we plan to introduce the [TechDocs Addon Framework](https://github.com/backstage/backstage/issues/9636) for augmenting the TechDocs experience at read-time. + +In addition to the framework itself, we'll be open sourcing a `` addon, helping you to create a feedback loop that drives up documentation quality and fosters a documentation culture at your organization. + +### **Next** + +- What can we do in TechDocs to drive up documentation quality? + +### **Someday/Maybe** -- [TechDocs Addon Framework](https://github.com/backstage/backstage/issues/9636) - Contribute to and deploy from a marketplace of TechDocs Addons -- Addon: Highlight text and raise an Issue to create a feedback loop to drive up documentation quality - Addon: MDX (allows you to use JSX in your Markdown content) +- Can we go static site generator agnostic? - Better integration with - [Scaffolder V2](https://github.com/backstage/backstage/issues/2771) (e.g. easy to choose and plug documentation template with Software Templates) -- Static site generator agnostic + [Scaffolder V2](https://github.com/backstage/backstage/issues/2771) (e.g. easy to choose and apply documentation template with Software Templates) - Possible to configure several aspects about TechDocs (e.g. URL, homepage, theme) diff --git a/docs/features/techdocs/addons.md b/docs/features/techdocs/addons.md new file mode 100644 index 0000000000..5305e3bc68 --- /dev/null +++ b/docs/features/techdocs/addons.md @@ -0,0 +1,222 @@ +--- +id: addons +title: TechDocs Addons +description: How to find, use, or create TechDocs Addons. +--- + +> Note: This page contains documentation for features that have not yet been +> released on a [main-line version](https://backstage.io/docs/overview/versioning-policy#main-release-line) +> of Backstage and TechDocs. We plan to make Addons generally available in +> Backstage v1.2. + +## Concepts + +TechDocs is a centralized platform for publishing, viewing, and discovering +technical documentation across an entire organization. It's a solid foundation! +But it doesn't solve higher-order documentation needs on its own: how do you +create and reinforce a culture of documentation? How do you build trust in the +quality of technical documentation? + +TechDocs Addons are a mechanism by which you can customize the TechDocs +experience in order to try and address some of these higher-order needs. + +### Addons + +An Addon is just a react component. Like any react component, it can retrieve +and render data using normal Backstage or native hooks, APIs, and components. +Props can be used to configure its behavior, where appropriate. + +### Locations + +Addons declare a `location` where they will be rendered. Most locations are +representative of physical spaces in the TechDocs UI: + +- `Header`: For Addons which fill up the header from the right, on the same + line as the title. +- `Subheader`: For Addons that sit below the header but above all content. + This is a great location for tooling/configuration of TechDocs display. +- `Settings`: These addons are items added to the settings menu list and are designed to make + the reader experience customizable, for example accessibility options. +- `PrimarySidebar`: Left of the content, above of the navigation. +- `SecondarySidebar`: Right of the content, above the table of contents. +- `Content`: A special location intended for Addons which augment the + statically generated content of the documentation itself. +- `Component`: A [proposed-but-not-yet-implemented](https://github.com/backstage/backstage/issues/11109) + virtual location, aimed at simplifying a common type of Addon. + +TechDocs Addon Location Guide + +### Addon Registry + +The installation and configuration of Addons happens within a Backstage app's +frontend. Addons are imported from plugins and added underneath a registry +component called ``. This registry can be configured for both +the TechDocs Reader page as well as the Entity docs page. + +Addons are rendered in the order in which they are registered. + +## Installing and using Addons + +Addons can be installed and configured in much the same way as extensions for +other Backstage plugins: by adding them underneath an extension registry +component (``) under the route representing the TechDocs Reader +page in your `App.tsx`: + +```tsx +// packages/app/src/App.tsx + +import { TechDocsReaderPage } from '@backstage/plugin-techdocs'; +import { TechDocsAddons } from '@backstage/plugin-techdocs-react/alpha'; +import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; + +// ... + +}> + + + {/* Other addons can be added here. */} + +; +``` + +The process for configuring Addons on the documentation tab on the entity page +is very similar; instead of adding the `` registry under a +``, you'd add it as a child of ``: + +```tsx +// packages/app/src/components/catalog/EntityPage.tsx + +import { EntityLayout } from '@backstage/plugin-catalog'; +import { EntityTechdocsContent } from '@backstage/plugin-techdocs'; +import { TechDocsAddons } from '@backstage/plugin-techdocs-react/alpha'; +import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; + +// ... + + + + + + {/* Other addons can be added here. */} + + +; +``` + +Note that on the entity page, because the Catalog plugin is responsible for the +page header, TechDocs Addons whose location is `Header` will not be rendered. + +## Available Addons + +Addons can, in principle, be provided by any plugin! To make it easier to +discover available Addons, we've compiled a list of them here: + +| Addon | Package/Plugin | Description | +| ---------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [``](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. | + +Got an Addon to contribute? Feel free to add a row above! + +## Creating an Addon + +The simplest Addons are plain old react components that get rendered in +specific locations within a TechDocs site. To package such a react component as +an Addon, follow these steps: + +1. Write the component in your plugin like any other component +2. Create, provide, and export the component from your plugin + +```ts +// plugins/your-plugin/src/plugin.ts + +import { + createTechDocsAddonExtension, + TechDocsAddonLocations, +} from '@backstage/plugin-techdocs-react/alpha'; +import { CatGifComponent, CatGifComponentProps } from './addons'; + +// ... + +// You must "provide" your Addon, just like any extension, via your plugin. +export const CatGif = yourPlugin.provide( + // This function "creates" the Addon given a component and location. If your + // component can be configured via props, pass the prop type here too. + createTechDocsAddonExtension({ + name: 'CatGif', + location: TechDocsAddonLocations.Header, + component: CatGifComponent, + }), +); +``` + +### Addons in the Content location + +Beyond the "render a component in a region" use-case, it's also possible for +Addons to access and manipulate a TechDocs site's DOM; this could be used to, +for example, load and instantiate client-side diagramming libraries, replace +elements with dynamically loaded content, etc. + +This type of Addon is still expressed as a react component, but instead of +returning a react element to be rendered, it updates the DOM via side-effects +(e.g. with `useEffect`). Access to the DOM is made available via utility hooks +provided by the Addon framework. + +```tsx +// plugins/your-plugin/src/addons/MakeAllImagesCatGifs.tsx + +import React, { useEffect } from 'react'; +import { useShadowRootElements } from '@backstage/plugin-techdocs-react/alpha'; + +// This is a normal react component; in order to make it an Addon, you would +// still create and provide it via your plugin as described above. The only +// difference is that you'd set `location` to `TechDocsAddonLocations.Content`. +export const MakeAllImagesCatGifsAddon = () => { + // This hook can be used to get references to specific elements. If you need + // access to the whole shadow DOM, use the the underlying useShadowRoot() + // hook instead. + const images = useShadowRootElements(['img']); + + useEffect(() => { + images.forEach(img => { + if (img.src !== 'https://example.com/cat.gif') { + img.src = 'https://example.com/cat.gif'; + } + }); + }, [images]); + + // Nothing to render directly, so we can just return null. + return null; +}; +``` + +### Testing Addons + +Install `@backstage/plugin-techdocs-addons-test-utils` as a `devDependency` in +your plugin for access to utilities that make testing such Addons easier. + +A test for the above Addon might look something like this: + +```tsx +// plugins/your-plugin/src/addons/MakeAllImagesCatGifs.test.tsx +import { TechDocsAddonTester } from '@backstage/plugin-techdocs-addons-test-utils'; + +// Note: import your actual addon (the one provided by your plugin). +import { MakeAllImagesCatGifs } from '../plugin.ts'; + +describe('MakeAllImagesCatGifs', () => { + it('replaces img srcs with cat gif', async () => { + const { getByTestId } = await TechDocsAddonTester.buildAddonsInTechDocs([ + , + ]) + .withDom() + .renderWithEffects(); + + expect(getByTestId('fixture')).toHaveAttribute( + 'src', + 'https://example.com/cat.gif', + ); + }); +}); +``` diff --git a/docs/features/techdocs/cli.md b/docs/features/techdocs/cli.md index 9a7dccd5a6..52c389975e 100644 --- a/docs/features/techdocs/cli.md +++ b/docs/features/techdocs/cli.md @@ -120,7 +120,7 @@ Generate TechDocs documentation site using MkDocs. Options: --source-dir Source directory containing mkdocs.yml and docs/ directory. (default: ".") --output-dir Output directory containing generated TechDocs site. (default: "./site/") - --docker-image The mkdocs docker container to use (default: "spotify/techdocs:v1.0.2") + --docker-image The mkdocs docker container to use (default: "spotify/techdocs:v1.0.3") --no-pull Do not pull the latest docker image --no-docker Do not use Docker, use MkDocs executable and plugins in current user environment. --techdocs-ref The repository hosting documentation source files e.g. diff --git a/docs/features/techdocs/concepts.md b/docs/features/techdocs/concepts.md index e34f58bc70..2944197dfb 100644 --- a/docs/features/techdocs/concepts.md +++ b/docs/features/techdocs/concepts.md @@ -18,8 +18,8 @@ GitLab, etc.) and passes the files to the generator for next steps. There are two kinds of preparers available - -1. Common Git Preparer - Uses `git clone` on any repository url. -2. Url Reader - Uses source code hosting provider's API to download files. +1. Common Git Preparer - Uses `git clone` on any repository URL. +2. URL Reader - Uses source code hosting provider's API to download files. (Faster and recommended) ### TechDocs Generator @@ -96,10 +96,6 @@ Documentation generated by TechDocs is generated as static HTML sites. The TechDocs Reader was therefore created to be able to integrate pre-generated HTML sites with the Backstage UI. -The TechDocs Reader purpose is also to open up the opportunity to integrate -TechDocs widgets for a customized full-featured TechDocs experience. -([coming soon V.3](./README.md#project-roadmap)) - [TechDocs Reader](https://github.com/backstage/backstage/blob/master/plugins/techdocs/src/reader/README.md) ## Transformers @@ -110,3 +106,19 @@ transform the HTML content on pre and post render (e.g. rewrite docs links or modify css). [Transformers API docs](https://github.com/backstage/backstage/blob/master/plugins/techdocs/src/reader/README.md) + +## TechDocs Addons + +Addons (introduced in Backstage v1.2) are client-side, React-based extensions +that can be used to augment the TechDocs experience at read-time. They offer a +mechanism for configuring the TechDocs Reader to better suit your +organization's needs. + +Addons can dynamically load and display information anywhere in the TechDocs +Reader, including within the statically generated content itself. + +Addons should not be confused with `mkdocs` plugins, which may be used to +customize a TechDocs site's content at build-time. While it's possible to take +advantage of some `mkdocs` plugins, not all such plugins play well with +TechDocs (primarily, but not exclusively, for security reasons). Addons offer +an alternative. diff --git a/docs/features/techdocs/getting-started.md b/docs/features/techdocs/getting-started.md index 1eeebae903..f8bf98c0e0 100644 --- a/docs/features/techdocs/getting-started.md +++ b/docs/features/techdocs/getting-started.md @@ -54,8 +54,48 @@ const AppRoutes = () => { }; ``` -That's it! But now, we need the TechDocs Backend plugin for the frontend to -work. +It would be nice to decorate your pages with something else... Having a link that redirects you to a new issue page when you highlight text in your documentation would be really cool, right? Let's learn how to do this using the TechDocs Addon Framework! + +With the [TechDocs Addon framework](https://backstage.io/docs/features/techdocs/addons#installing-and-using-addons), you can render React components in documentation pages and these Addons can be provided by any Backstage plugin. The framework is exported by the [@backstage/plugin-techdocs-react](https://www.npmjs.com/package/@backstage/plugin-techdocs-react) package and there is a `` Addon in the [@backstage/plugin-techdocs-module-addons-contrib](https://www.npmjs.com/package/@backstage/plugin-techdocs-module-addons-contrib) package for you to use once you have these two dependencies installed: + +```diff +import { + DefaultTechDocsHome, + TechDocsIndexPage, + TechDocsReaderPage, +} from '@backstage/plugin-techdocs'; ++ import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++ import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; + +// ... + +const AppRoutes = () => { + + // ... other plugin routes + }> + + + } + > ++ ++ ++ + + ; +}; +``` + +I know, you're curious to see how it looks, aren't you? See the image below: + +TechDocs Report Issue Add-on + +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: + +TechDocs Report Issue Template + +That's it! Now, we need the TechDocs Backend plugin for the frontend to work. ## Adding TechDocs Backend plugin diff --git a/docs/features/techdocs/using-cloud-storage.md b/docs/features/techdocs/using-cloud-storage.md index 66eaf00663..5a53961e81 100644 --- a/docs/features/techdocs/using-cloud-storage.md +++ b/docs/features/techdocs/using-cloud-storage.md @@ -461,7 +461,7 @@ Since the new SDK doesn't use the old way authentication, we don't need the keys The new SDK needs the OpenStack Swift connection URL for connecting the Swift. So you need to add a new key called `openStackSwift.swiftUrl` and give the -OpenStack Swift url here. Example url should look like that: +OpenStack Swift URL here. Example URL should look like that: `https://example.com:6780/swift/v1` ##### That's it! diff --git a/docs/getting-started/project-structure.md b/docs/getting-started/project-structure.md index b67e847272..cbe84db2e0 100644 --- a/docs/getting-started/project-structure.md +++ b/docs/getting-started/project-structure.md @@ -24,7 +24,7 @@ the code. - [`.github/`](https://github.com/backstage/backstage/tree/master/.github) - Standard GitHub folder. It contains - amongst other things - our workflow definitions and templates. Worth noting is the - [styles](https://github.com/backstage/backstage/tree/master/.github/styles) + [vale](https://github.com/backstage/backstage/tree/master/.github/vale) sub-folder which is used for a markdown spellchecker. - [`.yarn/`](https://github.com/backstage/backstage/tree/master/.yarn) - @@ -212,9 +212,6 @@ future. common for companies to have their own npm registry, and this file makes sure that this folder always uses the public registry. -- [`.vale.ini`](https://github.com/backstage/backstage/tree/master/.vale.ini) - - [Spell checker](https://github.com/errata-ai/vale) for Markdown files. - - [`.yarnrc`](https://github.com/backstage/backstage/tree/master/.yarnrc) - Enforces "our" version of Yarn. diff --git a/docs/integrations/aws-s3/discovery.md b/docs/integrations/aws-s3/discovery.md index 316b5c5014..40739937a4 100644 --- a/docs/integrations/aws-s3/discovery.md +++ b/docs/integrations/aws-s3/discovery.md @@ -67,11 +67,11 @@ import { AwsS3EntityProvider } from '@backstage/plugin-catalog-backend-module-aw const builder = await CatalogBuilder.create(env); /** ... other processors and/or providers ... */ builder.addEntityProvider( - ...AwsS3EntityProvider.fromConfig(env.config, { + AwsS3EntityProvider.fromConfig(env.config, { logger: env.logger, schedule: env.scheduler.createScheduledTaskRunner({ - frequency: Duration.fromObject({ minutes: 30 }), - timeout: Duration.fromObject({ minutes: 3 }), + frequency: { minutes: 30 }, + timeout: { minutes: 3 }, }), }), ); diff --git a/docs/integrations/gerrit/discovery.md b/docs/integrations/gerrit/discovery.md index 5cd1f6204b..04fa3dac2a 100644 --- a/docs/integrations/gerrit/discovery.md +++ b/docs/integrations/gerrit/discovery.md @@ -30,11 +30,11 @@ import { Duration } from 'luxon'; const builder = await CatalogBuilder.create(env); /** ... other processors and/or providers ... */ builder.addEntityProvider( - ...GerritEntityProvider.fromConfig(env.config, { + GerritEntityProvider.fromConfig(env.config, { logger: env.logger, schedule: env.scheduler.createScheduledTaskRunner({ - frequency: Duration.fromObject({ minutes: 30 }), - timeout: Duration.fromObject({ minutes: 3 }), + frequency: { minutes: 30 }, + timeout: { minutes: 3 }, }), }), ); diff --git a/docs/integrations/gerrit/locations.md b/docs/integrations/gerrit/locations.md index dcbac65849..9bff2385e3 100644 --- a/docs/integrations/gerrit/locations.md +++ b/docs/integrations/gerrit/locations.md @@ -34,9 +34,9 @@ a structure with up to six elements: - `baseUrl` (optional): Needed if the Gerrit instance is not reachable at the base of the `host` option (e.g. `https://gerrit.company.com`) set the address here. This is the address that you would open in a browser. -- `cloneUrl` (optional): The base url for HTTP clones. Will default to `baseUrl` if +- `cloneUrl` (optional): The base URL for HTTP clones. Will default to `baseUrl` if not set. The address used to clone a repo is the `cloneUrl` plus the repo name. -- `gitilesBaseUrl` (optional): This is needed for creating a valid user-friendly url +- `gitilesBaseUrl` (optional): This is needed for creating a valid user-friendly URL that can be used for browsing the content of the provider. If not set a default value will be created in the same way as the "baseUrl" option. There is no requirement to have Gitiles for the Backstage Gerrit integration but without it diff --git a/docs/integrations/github/discovery.md b/docs/integrations/github/discovery.md index 3200a55bd2..a0c3fed153 100644 --- a/docs/integrations/github/discovery.md +++ b/docs/integrations/github/discovery.md @@ -60,8 +60,9 @@ And then add the processors to your catalog builder: ## Configuration To use the discovery processor, you'll need a GitHub integration -[set up](locations.md) with a `GITHUB_TOKEN`. Then you can add a location target -to the catalog configuration: +[set up](locations.md) with either a [Personal Access Token](../../getting-started/configuration.md#setting-up-a-github-integration) or [GitHub Apps](./github-apps.md). + +Then you can add a location target to the catalog configuration: ```yaml catalog: @@ -96,14 +97,12 @@ When using a custom pattern, the target is composed of three parts: ## GitHub API Rate Limits -GitHub [rate limits] API requests to 5,000 per hour (or more for Enterprise +GitHub [rate limits](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting) API requests to 5,000 per hour (or more for Enterprise accounts). The default Backstage catalog backend refreshes data every 100 seconds, which issues an API request for each discovered location. This means if you have more than ~140 catalog entities, you may get throttled by -rate limiting. This will soon be resolved once catalog refreshes make use of -ETags; to work around this in the meantime, you can change the refresh rate of -the catalog in your `packages/backend/src/plugins/catalog.ts` file: +rate limiting. You can change the refresh rate of the catalog in your `packages/backend/src/plugins/catalog.ts` file: ```typescript const builder = await CatalogBuilder.create(env); @@ -117,5 +116,3 @@ which carries a much higher rate limit at GitHub. This is true for any method of adding GitHub entities to the catalog, but especially easy to hit with automatic discovery. - -[rate limits]: https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting diff --git a/docs/integrations/github/org.md b/docs/integrations/github/org.md index 0460b1adbc..5482733a58 100644 --- a/docs/integrations/github/org.md +++ b/docs/integrations/github/org.md @@ -19,22 +19,72 @@ entities that mirror your org setup. ## Installation -See the [discovery](discovery.md) article for installation instructions. +This guide will use the Entity Provider method. If you for some reason prefer +the Processor method (not recommended), it is described separately below. + +The provider is not installed by default, therefore you have to add a dependency +to `@backstage/plugin-catalog-backend-module-github` to your backend package. + +```bash +# From your Backstage root directory +yarn add --cwd packages/backend @backstage/plugin-catalog-backend-module-github +``` + +> Note: When configuring to use a Provider instead of a Processor you do not +> need to add a _location_ pointing to your GitHub server/organization + +Update the catalog plugin initialization in your backend to add the provider and +schedule it: + +```diff + // packages/backend/src/plugins/catalog.ts ++import { GitHubOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-github'; + + export default async function createPlugin( + env: PluginEnvironment, + ): Promise { + const builder = await CatalogBuilder.create(env); + ++ // The org URL below needs to match a configured integrations.github entry ++ // specified in your app-config. ++ builder.addEntityProvider( ++ GitHubOrgEntityProvider.fromConfig(env.config, { ++ id: 'production', ++ orgUrl: 'https://github.com/backstage', ++ logger: env.logger, ++ schedule: env.scheduler.createScheduledTaskRunner({ ++ frequency: { minutes: 60 }, ++ timeout: { minutes: 15 }, ++ }), ++ }), ++ ); +``` ## Configuration -The following configuration enables an import of the teams and users under the -org `https://github.com/my-org-name` on public GitHub. +As mentioned above, you also must have some configuration in your app-config +that describes the targets that you want to import. This lets the entity +provider know what authorization to use, and what the API endpoints are. You may +or may not have such an entry already added since before: ```yaml -catalog: - locations: - - type: github-org - target: https://github.com/my-org-name - rules: - - allow: [User, Group] +integrations: + github: + # example for public github + - host: github.com + token: ${GITHUB_TOKEN} + # example for a private GitHub Enterprise instance + - host: ghe.example.net + apiBaseUrl: https://ghe.example.net/api/v3 + token: ${GHE_TOKEN} ``` +These examples use `${}` placeholders to reference environment variables. This +is often suitable for production setups, but also means that you will have to +supply those variables to the backend as it starts up. If you want, for local +development in particular, you can experiment first by putting the actual tokens +in a mirrored config directly in your `app-config.local.yaml` as well. + If Backstage is configured to use GitHub Apps authentication you must grant `Read-Only` access for `Members` under `Organization` in order to ingest users correctly. You can modify the app's permissions under the organization settings, @@ -47,11 +97,50 @@ that must be approved first before the changes are applied.** ![email](../../assets/integrations/github/email.png) -Locations point out the specific org(s) you want to import. The `type` of these -locations must be `github-org`, and the `target` must point to the exact URL of -some organization. You can have several such location entries if needed. +## Using a Processor instead of a Provider -The authorization for loading org information comes from a configured -[GitHub integration](locations.md#configuration). When using a personal access -token, the token needs to have at least the scopes `read:org`, `read:user`, and -`user:email` in the given `target`. +An alternative to using the Provider for ingesting organizational entities is to +use a Processor. This is the old way that's based on registering locations with +the proper type and target, triggering the processor to run. + +The drawback of this method is that it will leave orphaned Group/User entities +whenever they are deleted on your GitHub server, and you cannot control the +frequency with which they are refreshed, separately from other processors. + +### Processor Installation + +The `GithubOrgReaderProcessor` is not registered by default, so you have to +install and register it in the catalog plugin: + +```bash +# From your Backstage root directory +yarn add --cwd packages/backend @backstage/plugin-catalog-backend-module-github +``` + +```typescript +// packages/backend/src/plugins/catalog.ts +import { GithubOrgReaderProcessor } from '@backstage/plugin-catalog-backend-module-github'; +// ... +builder.addProcessor( + GithubOrgReaderProcessor.fromConfig(env.config, { logger: env.logger }), +); +``` + +### Processor Configuration + +The integration section of your app-config needs to be set up in the same way as +for the Entity Provider - see above. + +In addition to that, you typically want to add a few static locations to your +app-config, which reference your organizations to import. The following +configuration enables an import of the teams and users under the org +`https://github.com/my-org-name` on public GitHub. + +```yaml +catalog: + locations: + - type: github-org + target: https://github.com/my-org-name + rules: + - allow: [User, Group] +``` diff --git a/docs/integrations/google-cloud-storage/locations.md b/docs/integrations/google-cloud-storage/locations.md index 16e19ea350..e15cb6346c 100644 --- a/docs/integrations/google-cloud-storage/locations.md +++ b/docs/integrations/google-cloud-storage/locations.md @@ -48,7 +48,7 @@ you can check [this documentation page][google gcs docs]. To use this integration to import entities from a GCS bucket go to the Google console and browse the file you would like to import. Then copy the `Authenticated URL` and paste it into the text box in the `register component` -form. This url should look like +form. This URL should look like `https://storage.cloud.google.com///catalog-info.yaml`. [google gcs docs]: https://cloud.google.com/docs/authentication/production#auth-cloud-implicit-nodejs diff --git a/docs/integrations/ldap/org.md b/docs/integrations/ldap/org.md index 355bd04227..e7028f34f1 100644 --- a/docs/integrations/ldap/org.md +++ b/docs/integrations/ldap/org.md @@ -49,8 +49,8 @@ schedule it: + target: 'ldaps://ds.example.net', + logger: env.logger, + schedule: env.scheduler.createScheduledTaskRunner({ -+ frequency: Duration.fromObject({ minutes: 60 }), -+ timeout: Duration.fromObject({ minutes: 15 }), ++ frequency: { minutes: 60 }, ++ timeout: { minutes: 15 }, + }), + }), + ); diff --git a/docs/overview/roadmap.md b/docs/overview/roadmap.md index 8ecd7c4a38..106bf56f79 100644 --- a/docs/overview/roadmap.md +++ b/docs/overview/roadmap.md @@ -6,80 +6,101 @@ description: Roadmap of Backstage ## The Backstage Roadmap -Backstage is currently under rapid development. This page details the project’s +Backstage is currently under rapid development. This page details the project's public roadmap, the result of ongoing collaboration between the core maintainers and the broader Backstage community. -The Backstage roadmap lays out both [“what’s next”](#whats-next) and -[“future work”](#future-work). With "next" we mean features planned for release -within the ongoing quarter from January through March 2022. With "future" we -mean features in the radar, but not yet scheduled. +The Backstage roadmap lays out both [“what's next”](#whats-next) and ["future +work"](#future-work). With "next" we mean features planned for release within +the ongoing quarter from January through March 2022. With "future" we mean +features on the radar, but not yet scheduled. -| [What's next](#whats-next) | [Future work](#future-work) | -| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [Backstage 1.0 (and following versions)](#backstage-10-and-following-versions)
[Backstage Security Audit](#backstage-security-audit)
[Moving to Incubation in CNCF](#moving-to-incubation-in-cncf) | Backend Services
Security Plan (and Strategy)
TechDocs Addon framework
Composable Homepage 1.0
GraphQL
Telemetry
Improved UX design | +| [What's next](#whats-next) | [Future work](#future-work) | +| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | +| [Ease of onboarding](#ease-of-onboarding)
[Search 1.0](#search-1.0)
[TechDocs Addon framework](#techdocs-addon-framework)
[Backend Services (initial)](#backend-services-initial)
[Backstage Security Audit](#backstage-security-audit)
[SIGs for contributors](#sigs-for-contributors) | Security Plan (and Strategy)
Composable Homepage 1.0
GraphQL
Telemetry
Improved UX design | The long-term roadmap (12 - 36 months) is not detailed in the public roadmap. Third-party contributions are also not currently included in the roadmap. Let us -know about any ongoing developments and we’re happy to include them here as well. +know about any ongoing developments and we're happy to include them here as +well. -## What’s next +## What's next The feature set below is planned for the ongoing quarter, and grouped by theme. -The list order doesn’t necessarily reflect priority, and the development/release +The list order doesn't necessarily reflect priority, and the development/release cycle will vary based on maintainer schedules. -### Backstage 1.0 (and following versions) +### Ease of onboarding -During the first quarter of 2022, we plan to finalize and release version 1.0 of -the Backstage platform (which includes Core, -[Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview), -[Scaffolder](https://backstage.io/docs/features/software-templates/software-templates-index) -and [TechDocs](https://backstage.io/docs/features/techdocs/techdocs-overview)). -Included as part of this milestone: +A faster (with less development) and easier setup of a proof-of-concept +deployment, as part of the onboarding experience, has been a common and loud +suggestion from new adopters as well as analysts looking at Backstage. -- Deciding on the cadence of minor/weekly/daily releases to provide clarity on - the frequency and expectations for future versions of the platform and its - defining modules. -- Establish the support model to set the expectations from the adopters in their - respective use cases. +With this initiative we plan to start facing this important topic with the most +commonly used and challenging tasks. More in particular we plan to reduce the +effort required to go from zero to production in installing and customizing +Backstage, as well as reducing the effort required to populate the Software +Catalog. + +More iterations will be required in the following quarters, but this will be a +good improvement in the onboarding experience, especially for the benefit of new +adopters. + +### Search 1.0 + +Fix the few remaining issues to get Backstage Search platform up to 1.0 +([here](https://github.com/backstage/backstage/milestone/27) and +[here](https://github.com/backstage/backstage/milestone/28)). + +### TechDocs Addon framework + +Addons are TechDocs features that are added on top of the base docs-like-code +experience. An example would be a feature that showed comments on the page. We +plan to add an Addon framework and open source a selection of the Addons that we +use internally at Spotify. Further Addons can then be added by the Community. + +### Backend Services (initial) + +To better scale and maintain the Backstage instances, a backend services system +is planned to be introduced as part of the software architecture. This layer of +backend services will help in decoupling the various modules (e.g. Catalog and +Scaffolder) from the frontend experience. + +In this quarter we plan to start designing the new architecture, together with +the first experimentation and development of the software components. ### Backstage Security Audit +This is the continuation of the initiative started in the previous quarter. This +quarter will see the publication of the report describing the outcome of the +audit, together the first fixes and the development of some of the changes +required to address the vulnerabilities. + This initiative is the first of a broader Security Strategy for Backstage. The purpose of the Security Audit is to involve third-party companies in auditing -the platform and highlighting potential vulnerabilities. The benefit for the -adopters is clear: We want Backstage to be as secure as possible, and we want to -make it reliable through a specific initiative. This initiative in particular is -done together, and with the support of, the -[Cloud Native Computing Foundation (CNCF)](https://www.cncf.io/). +the platform. The benefit for the adopters is clear: we want Backstage to be as +secure as possible, and we want to make it reliable through a specific +initiative. -### Moving to Incubation in CNCF +This initiative is done together with, and with the support of, the [Cloud +Native Computing Foundation (CNCF)](https://www.cncf.io/). -The progress of the request can be seen -[here](https://github.com/cncf/toc/pull/717). +### SIGs for contributors + +The request to better coordinate the increasing number of contributions coming +from the various adopters' developers is loud and clear. We think that the +community is mature enough to start launching the SIGs (Special Interest Groups) +following the successful model of Kubernetes. ## Future work -The following feature list doesn’t represent a commitment to develop, and the -list order doesn’t reflect any priority or importance, but these features are on -the maintainers’ radar, with clear interest expressed by the community. +The following feature list doesn't represent a commitment to develop, and the +list order doesn't reflect any priority or importance, but these features are on +the maintainers' radar, with clear interest expressed by the community. -- **Backend Services:** To better scale and maintain the Backstage instances, a - backend layer of services is planned to be introduced as part of the software - architecture. This layer of backend services will help in decoupling the - various modules (e.g. Catalog and Scaffolder) from the frontend experience. - **Security Plan (and Strategy):** The purpose of the Security Strategy is to move another step along the path to maturing the platform, setting the expectations of any adopters from a security standpoint. -- **Search 1.0:** Fix the few remaining issues to get Backstage Search platform - up to 1.0 ([here](https://github.com/backstage/backstage/milestone/27) and - [here](https://github.com/backstage/backstage/milestone/28)). -- **TechDocs Addon framework and Addons:** Addons are TechDocs features that are - added on top of the base docs like code experience. An example would be a - feature that showed comments on the page. We plan to add an Addon framework - and open source a selection of the Addons that we use internally at Spotify. - Further Addons can then be added by the Community. - **Composable Homepage 1.0:** Driving this to 1.0 by adding some composable components. - **[GraphQL](https://graphql.org/) support:** Introduce the ability to query @@ -107,51 +128,5 @@ 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](http://calendly.com/spotify-backstage) with the Spotify team. - -## Completed milestones - -Read more about the completed (and released) features for reference. - -- [[Search] ElasticSearch integration](https://backstage.io/docs/features/search/search-engines#elasticsearch) -- [[Search] TechDocs search capabilities](https://backstage.io/docs/features/search/how-to-guides#how-to-index-techdocs-documents) -- [TechDocs Beta](https://backstage.spotify.com/blog/product-updates/techdocs-beta-has-landed) -- [[Home] Composable homepage](https://github.com/backstage/backstage/milestone/34) -- [[Search] Out-of-the-Box Implementation (Alpha)](https://github.com/backstage/backstage/milestone/26) -- [Deploy a product demo at `demo.backstage.io`](https://demo.backstage.io) -- [Kubernetes plugin - v1](https://github.com/backstage/backstage/tree/master/plugins/kubernetes) -- [Helm charts](https://github.com/backstage/backstage/tree/master/contrib/chart/backstage) -- [Backstage Design System 💅](https://backstage.io/blog/2020/09/30/backstage-design-system) -- [Cost Insights plugin 💸](https://engineering.atspotify.com/2020/09/29/managing-clouds-from-the-ground-up-cost-engineering-at-spotify/) -- [Donate Backstage to the CNCF 🎉](https://backstage.io/blog/2020/09/23/backstage-cncf-sandbox) -- [TechDocs v1](https://backstage.io/blog/2020/09/08/announcing-tech-docs) -- [Plugin marketplace](https://backstage.io/plugins) -- [Improved and move documentation to backstage.io](https://backstage.io/docs/overview/what-is-backstage) -- [Backstage Software Catalog (alpha)](https://backstage.io/blog/2020/06/22/backstage-service-catalog-alpha) -- [Backstage Software Templates (alpha)](https://backstage.io/blog/2020/08/05/announcing-backstage-software-templates) -- [Make it possible to add custom auth providers](https://backstage.io/blog/2020/07/01/how-to-enable-authentication-in-backstage-using-passport) -- [TechDocs v0](https://github.com/backstage/backstage/milestone/15) -- CI plugins: CircleCI, Jenkins, GitHub Actions and TravisCI -- [Service API documentation](https://github.com/backstage/backstage/pull/1737) -- Backstage Software Catalog can read from: GitHub, GitLab, - [Bitbucket](https://github.com/backstage/backstage/pull/1938) -- Support auth providers: Google, Okta, GitHub, GitLab, - [auth0](https://github.com/backstage/backstage/pull/1611), - [AWS](https://github.com/backstage/backstage/pull/1990) - -- [Donate Backstage to the CNCF 🎉](https://backstage.io/blog/2020/09/23/backstage-cncf-sandbox) -- [TechDocs v1](https://backstage.io/blog/2020/09/08/announcing-tech-docs) -- [Plugin marketplace](https://backstage.io/plugins) -- [Improved and move documentation to backstage.io](https://backstage.io/docs/overview/what-is-backstage) -- [Backstage Software Catalog (alpha)](https://backstage.io/blog/2020/06/22/backstage-service-catalog-alpha) -- [Backstage Software Templates (beta)](https://backstage.io/blog/2021/07/26/software-templates-are-now-in-beta) -- [Make it possible to add custom auth providers](https://backstage.io/blog/2020/07/01/how-to-enable-authentication-in-backstage-using-passport) -- [TechDocs v0](https://github.com/backstage/backstage/milestone/15) -- CI plugins: CircleCI, Jenkins, GitHub Actions and TravisCI -- [Service API documentation](https://github.com/backstage/backstage/pull/1737) -- Backstage Software Catalog can read from: GitHub, GitLab, - [Bitbucket](https://github.com/backstage/backstage/pull/1938) -- Support auth providers: Google, Okta, GitHub, GitLab, - [auth0](https://github.com/backstage/backstage/pull/1611), - [AWS](https://github.com/backstage/backstage/pull/1990) +[Discord](https://discord.gg/qxsEfa8Vq8), or [book +time](http://calendly.com/spotify-backstage) with the Spotify team. diff --git a/docs/permission/concepts.md b/docs/permission/concepts.md new file mode 100644 index 0000000000..e7d65b3583 --- /dev/null +++ b/docs/permission/concepts.md @@ -0,0 +1,25 @@ +--- +id: concepts +title: Concepts +description: A list of important permission framework concepts +--- + +### Permission + +Any action that a user performs within Backstage may be represented as a permission. More complex actions, like executing a software template, may require authorization for multiple permissions throughout the flow. Permissions are identified by a unique name and optionally include a set of attributes that describe the corresponding action. Plugins are responsible for defining and exposing the permissions they enforce. + +### Policy + +User permissions are authorized by a central, user-defined permission policy. At a high level, a policy is a function that receives a Backstage user and permission, and returns a decision to allow or deny. Policies are expressed as code, which decouples the framework from any particular authorization model, like role-based access control (RBAC) or attribute-based access control (ABAC). + +### Policy decision versus enforcement + +Two important responsibilities of any authorization system are to decide if a user can do something, and to enforce that decision. In the Backstage permission framework, policies are responsible for decisions and plugins (typically backends) are responsible for enforcing them. + +### Resources and rules + +In many cases, a permission represents a user's interaction with another object. This object likely has information that policy authors can use to define more granular access. The permission framework introduces two abstractions to account for this: resources and rules. Resources represent the objects that users interact with. Rules are predicate-based controls that tap into a resource's data. For example, the catalog plugin defines a resource for catalog entities and a rule to check if an entity has a given annotation. + +### Conditional decisions + +Rules need additional data before they can be used in a decision. For example, the catalog plugin's "has annotation" rule needs to know what annotation to look for on a given entity. Once a rule is bound to relevant information it forms a condition. Conditions are then used to return a conditional decision from a policy. Conditional decisions tell the permission framework to delegate evaluation to the plugin that owns the corresponding resource. Permission requests that result in a conditional decision are allowed if all of the provided conditions evaluate to be true. This conditional behavior avoids coupling between policies and resource schemas, and allows plugins to evaluate complex rules in an efficient way. For example, a plugin may convert a conditional decision to a database query instead of loading and filtering objects in memory. diff --git a/docs/permission/custom-rules.md b/docs/permission/custom-rules.md new file mode 100644 index 0000000000..0ff0bc230c --- /dev/null +++ b/docs/permission/custom-rules.md @@ -0,0 +1,107 @@ +--- +id: custom-rules +title: Defining custom permission rules +description: How to define custom permission rules for existing resources +--- + +For some use cases, you may want to define custom [rules](./concepts.md#resources-and-rules) in addition to the ones provided by a plugin. In the [previous section](./writing-a-policy.md) we used the `isEntityOwner` rule to control access for catalog entities. Let's extend this policy with a custom rule that checks what [system](https://backstage.io/docs/features/software-catalog/system-model#system) an entity is part of. + +## Define a custom rule + +Plugins should export a rule factory that provides type-safety that ensures compatibility with the plugin's backend. The catalog plugin exports `createCatalogPermissionRule` from `@backstage/plugin-catalog-backend/alpha` for this purpose. Note: the `/alpha` path segment is temporary until this API is marked as stable. For this example, we'll define the rule in `packages/backend/src/plugins/permission.ts`, but you can put it anywhere that's accessible by your `backend` package. + +```typescript +import type { Entity } from '@backstage/plugin-catalog-model'; +import { createCatalogPermissionRule } from '@backstage/plugin-catalog-backend/alpha'; +import { createConditionFactory } from '@backstage/plugin-permission-node'; + +export const isInSystemRule = createCatalogPermissionRule({ + name: 'IS_IN_SYSTEM', + description: 'Checks if an entity is part of the system provided', + resourceType: 'catalog-entity', + apply: (resource: Entity, systemRef: string) => { + if (!resource.relations) { + return false; + } + + return resource.relations + .filter(relation => relation.type === 'partOf') + .some(relation => relation.targetRef === systemRef); + }, + toQuery: (systemRef: string) => ({ + key: 'relations.partOf', + value: systemRef, + }), +}); + +const isInSystem = createConditionFactory(isInSystemRule); +``` + +For a more detailed explanation on defining rules, refer to the [documentation for plugin authors](./plugin-authors/03-adding-a-resource-permission-check.md#adding-support-for-conditional-decisions). + +## Provide the rule during plugin setup + +Now that we have a custom rule defined, we need provide it to the catalog plugin. This step is important because the catalog plugin will use the rule's `toQuery` and `apply` methods while evaluating conditional authorize results. There's no guarantee that the catalog and permission backends are running on the same server, so we must explicitly link the rule to ensure that it's available at runtime. + +The api for providing custom rules may differ between plugins, but there should typically be some integration point during the creation of the backend router. For the catalog, this integration point is exposed via `CatalogBuilder.addPermissionRules`. + +```typescript +// packages/backend/src/plugins/catalog.ts + +import { isInSystemRule } from './permission'; + +... + +export default async function createPlugin( + env: PluginEnvironment, +): Promise { + const builder = await CatalogBuilder.create(env); + builder.addPermissionRules(isInSystem); + ... + return router; +} +``` + +The new rule is now ready for use in a permission policy! + +## Use the rule in a policy + +Let's bring this all together by extending the example policy from the previous section. + +```diff +// packages/backend/src/plugins/permission.ts + ++ import { isInSystem } from './catalog'; + +... + +class TestPermissionPolicy implements PermissionPolicy { + async handle( + request: PolicyQuery, + user?: BackstageIdentityResponse, + ): Promise { + if (isResourcePermission(request.permission, 'catalog-entity')) { + return createCatalogConditionalDecision( + request.permission, +- catalogConditions.isEntityOwner( +- user?.identity.ownershipEntityRefs ?? [], +- ), ++ { ++ anyOf: [ ++ catalogConditions.isEntityOwner( ++ user?.identity.ownershipEntityRefs ?? [] ++ ), ++ isInSystem('interviewing') ++ ] ++ } + ); + } + + return { result: AuthorizeResult.ALLOW }; + } +``` + +The updated policy will allow catalog entity resource permissions if any of the following are true: + +- User owns the target entity +- Target entity is part of the `'interviewing'` system diff --git a/docs/permission/getting-started.md b/docs/permission/getting-started.md new file mode 100644 index 0000000000..b115b674d6 --- /dev/null +++ b/docs/permission/getting-started.md @@ -0,0 +1,157 @@ +--- +id: getting-started +title: Getting Started +description: How to get started with the permission framework as an integrator +--- + +If you prefer to watch a video instead, you can start with this video introduction: + + + +> Note: This video was recorded in the January 2022 Contributors Session using `@backstage/create-app@0.4.14`. Some aspects of the demo may have changed in later releases. + +Backstage integrators control permissions by writing a policy. In general terms, a policy is simply an async function which receives a request to authorize a specific action for a user and (optional) resource, and returns a decision on whether to authorize that permission. Integrators can implement their own policies from scratch, or adopt reusable policies written by others. + +## Prerequisites + +The permissions framework depends on a few other Backstage systems, which must be set up before we can dive into writing a policy. + +### Upgrade to the latest version of Backstage + +The permissions framework itself is new to Backstage and still evolving quickly. To ensure your version of Backstage has all the latest permission-related functionality, it’s important to upgrade to the latest version. The [Backstage upgrade helper](https://backstage.github.io/upgrade-helper/) is a great tool to help ensure that you’ve made all the necessary changes during the upgrade! + +### Enable backend-to-backend authentication + +Backend-to-backend authentication allows Backstage backend code to verify that a given request originates from elsewhere in the Backstage backend. This is useful for tasks like collation of catalog entities in the search index. This type of request shouldn’t be permissioned, so it’s important to configure this feature before trying to use the permissions framework. + +To set up backend-to-backend authentication, follow the [backend-to-backend authentication docs](../tutorials/backend-to-backend-auth.md). + +### Supply an identity resolver to populate group membership on sign in + +**Note**: If you are working off of an existing Backstage instance, you likely already have some form of an identity resolver set up. + +Like many other parts of Backstage, the permissions framework relies on information about group membership. This simplifies authoring policies through the use of groups, rather than requiring each user to be listed in the configuration. Group membership is also often useful for conditional permissions, for example allowing permissions to act on an entity to be granted when a user is a member of a group that owns that entity. + +[The IdentityResolver docs](../auth/identity-resolver.md) describe the process for resolving group membership on sign in. + +## Optionally add cookie-based authentication + +Asset requests initiated by the browser will not include a token in the `Authorization` header. If these requests check authorization through the permission framework, as done in plugins like TechDocs, then you'll need to set up cookie-based authentication. Refer to the ["Authenticate API requests"](https://github.com/backstage/backstage/blob/master/contrib/docs/tutorials/authenticate-api-requests.md) tutorial for a demonstration on how to implement this behavior. + +## Integrating the permission framework with your Backstage instance + +### 1. Set up the permission backend + +The permissions framework uses a new `permission-backend` plugin to accept authorization requests from other plugins across your Backstage instance. The Backstage backend does not include this permission backend by default, so you will need to add it: + +1. Add `@backstage/plugin-permission-backend` as a dependency of your Backstage backend: + +```bash +$ yarn workspace backend add @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. + +```typescript +import { IdentityClient } from '@backstage/plugin-auth-node'; +import { createRouter } from '@backstage/plugin-permission-backend'; +import { + AuthorizeResult, + PolicyDecision, +} from '@backstage/plugin-permission-common'; +import { PermissionPolicy } from '@backstage/plugin-permission-node'; +import { Router } from 'express'; +import { PluginEnvironment } from '../types'; + +class TestPermissionPolicy implements PermissionPolicy { + async handle(): Promise { + return { result: AuthorizeResult.ALLOW }; + } +} + +export default async function createPlugin( + env: PluginEnvironment, +): Promise { + return await createRouter({ + config: env.config, + logger: env.logger, + discovery: env.discovery, + policy: new TestPermissionPolicy(), + identity: IdentityClient.create({ + discovery: env.discovery, + issuer: await env.discovery.getExternalBaseUrl('auth'), + }), + }); +} +``` + +3. Wire up the permission policy in `packages/backend/src/index.ts`. [The index in the example backend](https://github.com/backstage/backstage/blob/master/packages/backend/src/index.ts) shows how to do this. You’ll need to import the module from the previous step, create a plugin environment, and add the router to the express app: + +```diff + import proxy from './plugins/proxy'; + import techdocs from './plugins/techdocs'; + import search from './plugins/search'; ++ import permission from './plugins/permission'; + + ... + + const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); + const searchEnv = useHotMemoize(module, () => createEnv('search')); + const appEnv = useHotMemoize(module, () => createEnv('app')); ++ const permissionEnv = useHotMemoize(module, () => createEnv('permission')); + + ... + + apiRouter.use('/techdocs', await techdocs(techdocsEnv)); + apiRouter.use('/proxy', await proxy(proxyEnv)); + apiRouter.use('/search', await search(searchEnv)); ++ apiRouter.use('/permission', await permission(permissionEnv)); +``` + +### 2. Enable and test the permissions system + +Now that the permission backend is running, it’s time to enable the permissions framework and make sure it’s working properly. + +1. Set the property `permission.enabled` to `true` in `app-config.yaml`. + +```yaml +permission: + enabled: true +``` + +2. Update the PermissionPolicy in `packages/backend/src/plugins/permission.ts` to disable a permission that’s easy for us to test. This policy rejects any attempt to delete a catalog entity: + +```diff + import { IdentityClient } from '@backstage/plugin-auth-node'; + import { createRouter } from '@backstage/plugin-permission-backend'; + import { + AuthorizeResult, + PolicyDecision, + } from '@backstage/plugin-permission-common'; +- import { PermissionPolicy } from '@backstage/plugin-permission-node'; ++ import { ++ PermissionPolicy, ++ PolicyQuery, ++ } from '@backstage/plugin-permission-node'; + import { Router } from 'express'; + import { PluginEnvironment } from '../types'; + + class TestPermissionPolicy implements PermissionPolicy { +- async handle(): Promise { ++ async handle(request: PolicyQuery): Promise { ++ if (request.permission.name === 'catalog.entity.delete') { ++ return { ++ result: AuthorizeResult.DENY, ++ }; ++ } ++ + return { result: AuthorizeResult.ALLOW }; + } + } +``` + +3. Now that you’ve made this change, you should find that the unregister entity menu option on the catalog entity page is disabled. + +![Entity detail page showing disabled unregister entity context menu entry](../assets/permission/disabled-unregister-entity.png) + +Now that the framework is fully configured, you can craft a permission policy that works best for your organization by utilizing a provided authorization method or by [writing your own policy](./writing-a-policy.md)! diff --git a/docs/permission/overview.md b/docs/permission/overview.md new file mode 100644 index 0000000000..8e87756faa --- /dev/null +++ b/docs/permission/overview.md @@ -0,0 +1,41 @@ +--- +id: overview +title: Overview +description: A high level overview of the Backstage permission framework +--- + +[The previous section](../auth/index.md) covered the various _authentication_ methods of Backstage, but Backstage can also _authorize_ specific data, APIs, or interface actions - meaning that Backstage has the ability to enforce rules about what type of access is allowed for a given user of a system. + +By default, Backstage endpoints are not protected, and all actions are available to anyone. However, configuring which users can access which resources and actions is a common need for many organizations. The permission framework allows integrators to achieve this through the use of granular permissioning for those resources and actions. + +The permission framework was designed with a few key properties in mind: + +- Flexibility: the framework allows integrators to configure many different authorization methods. This could include implementations like role-based access control (RBAC), attribute-based access control (ABAC), bespoke logic expressed in code, or integrations with external authorization providers. + +- Usability: the permission framework allows integrators to focus on configuring what they care about (the permission policy) by providing all of its moving parts out of the box. It also allows plugin authors to integrate support for permissions in their plugins without having to make any changes in Backstage core. + +## How does it work? + +- **Plugin authors** can add permission support in their plugins by declaring which resources from their plugins can be placed behind authorization and what types of actions users may take upon those resources. + +- **Contributors** can implement and share authorization methods (such as RBAC). + +- **Integrators** can author or configure policies that define which users can take certain actions upon which resources. + +![](../assets/permission/permission-framework-overview.drawio.svg) + +1. The user triggers a request to perform some action. The request specifies the authorization details using the permission specified by the plugin (in this case, a resource read action). + + - The action may be triggered by a user interacting with the UI, but it can also be a direct request to the plugin's backend. + +2. The plugin backend sends a request to the permission framework's backend with the authorization details. + +3. The permission framework's backend delegates the authorization decision to the permission policy, which is specified by the integrator using code, a provided authorization method (such as RBAC), or integrations with external authorization providers. + +4. An authorization decision is sent to the plugin from the permission backend. + +## How do I get started? + +See the "[getting started](./getting-started.md)" permission documentation for Backstage integrators. + +If you are a plugin author, see the permission [documentation for plugin authors](plugin-authors/01-setup.md) on how to integrate permissions into your plugin. diff --git a/docs/permission/plugin-authors/01-setup.md b/docs/permission/plugin-authors/01-setup.md new file mode 100644 index 0000000000..2f070f603b --- /dev/null +++ b/docs/permission/plugin-authors/01-setup.md @@ -0,0 +1,120 @@ +--- +id: 01-setup +title: 1. Tutorial setup +description: How to get started with the permission framework as a plugin author +--- + +The following tutorial is designed to help plugin authors add support for permissions to their plugins. We'll add support for permissions to example `todo-list` and `todo-list-backend` plugins, but the process should be similar for other plugins! + +The rest of this page is focused on adding the `todo-list` and `todo-list-backend` plugins to your Backstage instance. If you want to add support for permissions to your own plugin instead, feel free to skip to the [next section](./02-adding-a-basic-permission-check.md). + +## Setup for the Tutorial + +We will use a "Todo list" feature, composed of the `todo-list` and `todo-list-backend` plugins, as well as their dependency, `todo-list-common`. + +The source code is available here: + +- [todo-list](https://github.com/backstage/backstage/blob/master/plugins/example-todo-list) +- [todo-list-backend](https://github.com/backstage/backstage/blob/master/plugins/example-todo-list-backend) +- [todo-list-common](https://github.com/backstage/backstage/blob/master/plugins/example-todo-list-common) + +1. Copy-paste the three folders into the plugins folder of your backstage application repository (removing the `example-` prefix from each folder) or run the following script from the root of your backstage application: + + ```bash + $ cd $(mktemp -d) + git clone --depth 1 --quiet --no-checkout --filter=blob:none https://github.com/backstage/backstage.git . + git checkout master -- plugins/example-todo-list/ + git checkout master -- plugins/example-todo-list-backend/ + git checkout master -- plugins/example-todo-list-common/ + for file in plugins/*; do mv "$file" "$OLDPWD/${file/example-todo/todo}"; done + cd - + ``` + + The `plugins` directory of your project should now include `todo-list`, `todo-list-backend`, and `todo-list-common`. + + **Important**: if you are on **Windows**, make sure you have WSL and git installed on your machine before executing the script above. + +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 + ``` + +3. Include the backend and frontend plugin in your application: + + Create a new `packages/backend/src/plugins/todolist.ts` with the following content: + + ```javascript + import { IdentityClient } from '@backstage/plugin-auth-node'; + import { createRouter } from '@internal/plugin-todo-list-backend'; + import { Router } from 'express'; + import { PluginEnvironment } from '../types'; + + export default async function createPlugin({ + logger, + discovery, + }: PluginEnvironment): Promise { + return await createRouter({ + logger, + identity: IdentityClient.create({ + discovery, + issuer: await discovery.getExternalBaseUrl('auth'), + }), + }); + } + ``` + + Apply the following changes to `packages/backend/src/index.ts`: + + ```diff + import techdocs from './plugins/techdocs'; + + import todoList from './plugins/todolist'; + import search from './plugins/search'; + + ... + + const searchEnv = useHotMemoize(module, () => createEnv('search')); + const appEnv = useHotMemoize(module, () => createEnv('app')); + + const todoListEnv = useHotMemoize(module, () => createEnv('todolist')); + + ... + + apiRouter.use('/proxy', await proxy(proxyEnv)); + apiRouter.use('/search', await search(searchEnv)); + apiRouter.use('/permission', await permission(permissionEnv)); + + apiRouter.use('/todolist', await todoList(todoListEnv)); + // Add backends ABOVE this line; this 404 handler is the catch-all fallback + apiRouter.use(notFoundHandler()); + ``` + + Apply the following changes to `packages/app/src/App.tsx`: + + ```diff + + import { TodoListPage } from '@internal/plugin-todo-list'; + + ... + + }> + {searchPage} + + } /> + + } /> + + ``` + +Now if you start your application you should be able to reach the `/todo-list` page: + +![Todo List plugin page](../../assets/permission/permission-todo-list-page.png) + +--- + +## Integrate the new plugin + +If you play with the UI, you will notice that it is possible to perform a few actions: + +- create a new todo item (`POST /todos`) +- view todo items (`GET /todos`) +- edit an existing todo item (`PUT /todos`) + +Let's try to bring authorization on top of each one of them. diff --git a/docs/permission/plugin-authors/02-adding-a-basic-permission-check.md b/docs/permission/plugin-authors/02-adding-a-basic-permission-check.md new file mode 100644 index 0000000000..c73fe800b4 --- /dev/null +++ b/docs/permission/plugin-authors/02-adding-a-basic-permission-check.md @@ -0,0 +1,169 @@ +--- +id: 02-adding-a-basic-permission-check +title: 2. Adding a basic permission check +description: Explains how to add a basic permission check to a Backstage plugin +--- + +If the outcome of a permission check doesn't need to change for different [resources](../concepts.md#resources-and-rules), you can use a _basic permission check_. For this kind of check, we simply need to define a [permission](../concepts.md#resources-and-rules), and call `authorize` with it. + +For this tutorial, we'll use a basic permission check to authorize the `create` endpoint in our todo-backend. This will allow Backstage integrators to control whether each of their users is authorized to create todos by adjusting their [permission policy](../concepts.md#policy). + +We'll start by creating a new permission, and then we'll use the permission api to call `authorize` with it during todo creation. + +## Creating a new permission + +Let's navigate to the file `plugins/todo-list-common/src/permissions.ts` and add our first permission: + +```diff + import { createPermission } from '@backstage/plugin-permission-common'; + +- export const tempExamplePermission = createPermission({ +- name: 'temp.example.noop', +- attributes: {}, ++ export const todoListCreate = createPermission({ ++ name: 'todo.list.create', ++ attributes: { action: 'create' }, + }); +``` + +For this tutorial, we've automatically exported all permissions from this file (see `plugins/todo-list-common/src/index.ts`). + +> Note: All permissions authorized by your plugin should be exported from a ["common-library" package](https://backstage.io/docs/local-dev/cli-build-system#package-roles). This allows Backstage integrators to reference them in frontend components and permission policies. + +## Authorizing using the new permission + +Install the following module: + +``` +$ yarn workspace @internal/plugin-todo-list-backend \ + add @backstage/plugin-permission-common @internal/plugin-todo-list-common +``` + +Edit `plugins/todo-list-backend/src/service/router.ts`: + +```diff +... + +- import { InputError } from '@backstage/errors'; ++ import { InputError, NotAllowedError } from '@backstage/errors'; ++ import { PermissionEvaluator, AuthorizeResult } from '@backstage/plugin-permission-common'; ++ import { todoListCreate } from '@internal/plugin-todo-list-common'; + +... + + export interface RouterOptions { + logger: Logger; + identity: IdentityClient; ++ permissions: PermissionEvaluator; + } + + export async function createRouter( + options: RouterOptions, + ): Promise { +- const { logger, identity } = options; ++ const { logger, identity, permissions } = options; + + ... + + router.post('/todos', async (req, res) => { + const token = IdentityClient.getBearerToken(req.header('authorization')); + let author: string | undefined = undefined; + + const user = token ? await identity.authenticate(token) : undefined; + author = user?.identity.userEntityRef; ++ const decision = ( ++ await permissions.authorize([{ permission: todoListCreate }], { ++ token, ++ }) ++ )[0]; + ++ if (decision.result === AuthorizeResult.DENY) { ++ throw new NotAllowedError('Unauthorized'); ++ } + + if (!isTodoCreateRequest(req.body)) { + throw new InputError('Invalid payload'); + } + + const todo = add({ title: req.body.title, author }); + res.json(todo); + }); +``` + +Pass the `permissions` object to the plugin in `packages/backend/src/plugins/todolist.ts`: + +```diff + import { IdentityClient } from '@backstage/plugin-auth-backend'; + import { createRouter } from '@internal/plugin-todo-list-backend'; + import { Router } from 'express'; + import { PluginEnvironment } from '../types'; + + export default async function createPlugin({ + logger, + discovery, ++ permissions, + }: PluginEnvironment): Promise { + return await createRouter({ + logger, + identity: new IdentityClient({ + discovery, + issuer: await discovery.getExternalBaseUrl('auth'), + }), ++ permissions, + }); + } +``` + +That's it! Now your plugin is fully configured. Let's try to test the logic by denying the permission. + +## Test the authorized create endpoint + +Before running this step, please make sure you followed the steps described in [Getting started](../getting-started.md) section. + +In order to test the logic above, the integrators of your backstage instance need to change their permission policy to return `DENY` for our newly-created permission: + +```diff +// packages/backend/src/plugins/permission.ts + +- import { IdentityClient } from '@backstage/plugin-auth-node'; ++ import { ++ BackstageIdentityResponse, ++ IdentityClient ++ } from '@backstage/plugin-auth-node'; + import { + PermissionPolicy, ++ PolicyQuery, + } from '@backstage/plugin-permission-node'; ++ import { isPermission } from '@backstage/plugin-permission-common'; ++ import { todoListCreate } from '@internal/plugin-todo-list-common'; + + class TestPermissionPolicy implements PermissionPolicy { +- async handle(): Promise { ++ async handle( ++ request: PolicyQuery, ++ user?: BackstageIdentityResponse, ++ ): Promise { ++ if (isPermission(request.permission, todoListCreate)) { ++ return { ++ result: AuthorizeResult.DENY, ++ }; ++ } ++ + return { + result: AuthorizeResult.ALLOW, + }; + } +``` + +Now the frontend should show an error whenever you try to create a new Todo item. + +Let's flip the result back to `ALLOW` before moving on. + +```diff + if (isPermission(request.permission, todoListCreate)) { + return { +- result: AuthorizeResult.DENY, ++ result: AuthorizeResult.ALLOW, + }; + } +``` diff --git a/docs/permission/plugin-authors/03-adding-a-resource-permission-check.md b/docs/permission/plugin-authors/03-adding-a-resource-permission-check.md new file mode 100644 index 0000000000..e818557402 --- /dev/null +++ b/docs/permission/plugin-authors/03-adding-a-resource-permission-check.md @@ -0,0 +1,229 @@ +--- +id: 03-adding-a-resource-permission-check +title: 3. Adding a resource permission check +description: Explains how to add a resource permission check to a Backstage plugin +--- + +When performing updates (or other operations) on specific [resources](../concepts.md#resources-and-rules), the permissions framework allows for the decision to be based on characteristics of the resource itself. This means that it's possible to write policies that (for example) allow the operation for users that own a resource, and deny the operation otherwise. + +## Creating the update permission + +Let's add a new permission to the file `plugins/todo-list-common/src/permissions.ts` from [the previous section](./02-adding-a-basic-permission-check.md). + +```diff + import { createPermission } from '@backstage/plugin-permission-common'; + ++ export const TODO_LIST_RESOURCE_TYPE = 'todo-item'; ++ + export const todoListCreate = createPermission({ + name: 'todo.list.create', + attributes: { action: 'create' }, + }); ++ ++ export const todoListUpdate = createPermission({ ++ name: 'todo.list.update', ++ attributes: { action: 'update' }, ++ resourceType: TODO_LIST_RESOURCE_TYPE, ++ }); +``` + +Notice that unlike `todoListCreate`, the `todoListUpdate` permission contains a `resourceType` field. This field indicates to the permission framework that this permission is intended to be authorized in the context of a resource with type `'todo-item'`. You can use whatever string you like as the resource type, as long as you use the same value consistently for each type of resource. + +## Setting up authorization for the update permission + +To start, let's edit `plugins/todo-list-backend/src/service/router.ts` in the same manner as we did in the previous section: + +```diff +- import { todoListCreate } from '@internal/plugin-todo-list-common'; ++ import { todoListCreate, todoListUpdate } from '@internal/plugin-todo-list-common'; + + ... + + router.put('/todos', async (req, res) => { ++ const token = getBearerTokenFromAuthorizationHeader( ++ req.header('authorization'), ++ ); + + if (!isTodoUpdateRequest(req.body)) { + throw new InputError('Invalid payload'); + } ++ const decision = ( ++ await permissions.authorize( ++ [{ permission: todoListUpdate, resourceRef: req.body.id }], ++ { ++ token, ++ }, ++ ) ++ )[0]; ++ ++ if (decision.result !== AuthorizeResult.ALLOW) { ++ throw new NotAllowedError('Unauthorized'); ++ } + + res.json(update(req.body)); + }); +``` + +**Important:** Notice that we are passing an extra `resourceRef` field, with the `id` of the todo item as the value. + +This enables decisions based on characteristics of the resource, but it's important to note that policy authors will not have access to the resource ref inside of their permission policies. Instead, the policies will return conditional decisions, which we need to now support in our plugin. + +## Adding support for conditional decisions + +Install the missing module: + +``` +$ yarn workspace @internal/plugin-todo-list-backend add @backstage/plugin-permission-node +``` + +Create a new `plugins/todo-list-backend/src/service/rules.ts` file and append the following code: + +```typescript +import { makeCreatePermissionRule } from '@backstage/plugin-permission-node'; +import { TODO_LIST_RESOURCE_TYPE } from '@internal/plugin-todo-list-common'; +import { Todo, TodoFilter } from './todos'; + +export const createTodoListPermissionRule = makeCreatePermissionRule< + Todo, + TodoFilter +>(); + +export const isOwner = createTodoListPermissionRule({ + name: 'IS_OWNER', + description: 'Should allow only if the todo belongs to the user', + resourceType: TODO_LIST_RESOURCE_TYPE, + apply: (resource: Todo, userId: string) => { + return resource.author === userId; + }, + toQuery: (userId: string) => { + return { + property: 'author', + values: [userId], + }; + }, +}); + +export const rules = { isOwner }; +``` + +`makeCreatePermissionRule` is a helper used to ensure that rules created for this plugin use consistent types for the resource and query. + +> Note: To support custom rules defined by Backstage integrators, you must export `createTodoListPermissionRule` from the backend package and provide some way for custom rules to be passed in before the backend starts, likely via `createRouter`. + +We have created a new `isOwner` rule, which is going to be automatically used by the permission framework whenever a conditional response is returned in response to an authorized request with an attached `resourceRef`. +Specifically, the `apply` function is used to understand whether the passed resource should be authorized or not. + +Let's skip the `toQuery` function for now, we'll come back to that in the next section. + +Now, let's create the new endpoint by editing `plugins/todo-list-backend/src/service/router.ts`. This uses the `createPermissionIntegrationRouter` helper to add the APIs needed by the permission framework to your plugin. You'll need to supply: + +- `getResources`: a function that accepts an array of `resourceRefs` in the same format you expect to be passed to `authorize`, and returns an array of the corresponding resources. +- `resourceType`: the same value used in the permission rule above. +- `rules`: an array of all the permission rules you want to support in conditional decisions. + +```diff +... + +- import { add, getAll, update } from './todos'; ++ import { add, getAll, getTodo, update } from './todos'; ++ import { createPermissionIntegrationRouter } from '@backstage/plugin-permission-node'; ++ import { TODO_LIST_RESOURCE_TYPE } from '@internal/plugin-todo-list-common'; ++ import { rules } from './rules'; + + export async function createRouter( + options: RouterOptions, + ): Promise { + const { logger, identity, permissions } = options; + ++ const permissionIntegrationRouter = createPermissionIntegrationRouter({ ++ getResources: async resourceRefs => { ++ return resourceRefs.map(getTodo); ++ }, ++ resourceType: TODO_LIST_RESOURCE_TYPE, ++ rules: Object.values(rules), ++ }); + + const router = Router(); + router.use(express.json()); + ++ router.use(permissionIntegrationRouter); + + router.post('/todos', async (req, res) => { +``` + +## Provide utilities for policy authors + +Now that we have a new resource type and a corresponding rule, we need to export some utilities for policy authors to reference them. + +Create a new `plugins/todo-list-backend/src/conditionExports.ts` file and add the following code: + +```typescript +import { TODO_LIST_RESOURCE_TYPE } from '@internal/plugin-todo-list-common'; +import { createConditionExports } from '@backstage/plugin-permission-node'; +import { permissionRules } from './service/rules'; + +const { conditions, createConditionalDecision } = createConditionExports({ + pluginId: 'catalog', + resourceType: RESOURCE_TYPE_CATALOG_ENTITY, + rules: permissionRules, +}); + +export const todoListConditions = conditions; + +export const createTodoListConditionalDecision = createConditionalDecision; +``` + +Make sure `todoListConditions` and `createTodoListConditionalDecision` are exported from the `todo-list-backend` package. + +## Test the authorized update endpoint + +Let's go back to the permission policy's handle function and try to authorize our new permission with an `isOwner` condition. + +```diff + // packages/backend/src/plugins/permission.ts + + import { + BackstageIdentityResponse, + IdentityClient + } from '@backstage/plugin-auth-node'; + import { + PermissionPolicy, + PolicyQuery, + } from '@backstage/plugin-permission-node'; + import { isPermission } from '@backstage/plugin-permission-common'; +- import { todoListCreate } from '@internal/plugin-todo-list-common'; ++ import { ++ todoListCreate, ++ todoListUpdate, ++ TODO_LIST_RESOURCE_TYPE, ++ } from '@internal/plugin-todo-list-common'; ++ import { ++ todoListConditions, ++ createTodoListConditionalDecision, ++ } from '@internal/plugin-todo-list-backend'; + +... + + if (isPermission(request.permission, todoListCreate)) { + return { + result: AuthorizeResult.ALLOW, + }; + } + ++ if (isPermission(request.permission, todoListUpdate)) { ++ return createTodoListConditionalDecision( ++ request.permission, ++ todoListConditions.isOwner(user?.identity.userEntityRef), ++ ); ++ } ++ + return { + result: AuthorizeResult.ALLOW, + }; +``` + +For any incoming update requests, we now return a _Conditional Decision_. We are saying: + +> Hey permission framework, I can't make a decision alone. Please go to the plugin with id `todolist` and ask it to apply these conditions. + +To check that everything works as expected, you should now see an error in the UI whenever you try to edit an item that wasn’t created by you. Success! diff --git a/docs/permission/plugin-authors/04-authorizing-access-to-paginated-data.md b/docs/permission/plugin-authors/04-authorizing-access-to-paginated-data.md new file mode 100644 index 0000000000..582553832b --- /dev/null +++ b/docs/permission/plugin-authors/04-authorizing-access-to-paginated-data.md @@ -0,0 +1,149 @@ +--- +id: 04-authorizing-access-to-paginated-data +title: 4. Authorizing access to paginated data +description: Explains how to authorize access to paginated data in a Backstage plugin +--- + +Authorizing `GET /todos` is similar to the update endpoint, in that it should be possible to authorize access based on the characteristics of each resource. However, we'll need to authorize a list of resources for this endpoint. + +One possible solution may leverage the batching functionality to authorize all of the todos, and then returning only the ones for which the decision was `ALLOW`: + +```diff + router.get('/todos', async (req, res) => { ++ const token = IdentityClient.getBearerToken(req.header('authorization')); + +- res.json(getAll()) ++ const items = getAll(); ++ const decisions = await permissions.authorize( ++ items.map(({ id }) => ({ permission: todosListRead, resourceRef: id })), ++ ); + ++ const filteredItems = decisions.filter( ++ decision => decision.result === AuthorizeResult.ALLOW, ++ ); ++ res.json(filteredItems); + }); +``` + +This approach will work for simple cases, but it has a downside: it forces us to retrieve all the elements upfront and authorize them one by one. This forces the plugin implementation to handle concerns like pagination, which is currently handled by the data source. + +To avoid this situation, the permissions framework has support for filtering items in the data source itself. In this part of the tutorial, we'll describe the steps required to use that behavior. + +> Note: in order to perform authorization filtering in this way, the data source must allow filters to be logically combined with AND, OR, and NOT operators. The conditional decisions returned by the permissions framework use a [nested object](https://backstage.io/docs/reference/plugin-permission-common.permissioncriteria) to combine conditions. If you're implementing a filter API from scratch, we recommend using the same shape for ease of interoperability. If not, you'll need to implement a function which transforms the nested object into your own format. + +## Creating the read permission + +Let's add another permission to the plugin. + +```diff + // plugins/todo-list-backend/src/service/permissions.ts + + import { createPermission } from '@backstage/plugin-permission-common'; + + export const TODO_LIST_RESOURCE_TYPE = 'todo-item'; + + export const todoListCreate = createPermission({ + name: 'todo.list.create', + attributes: { action: 'create' }, + }); + + export const todoListUpdate = createPermission({ + name: 'todo.list.update', + attributes: { action: 'update' }, + resourceType: TODO_LIST_RESOURCE_TYPE, + }); ++ ++ export const todosListRead = createPermission({ ++ name: 'todos.list.read', ++ attributes: { action: 'read' }, ++ resourceType: TODO_LIST_RESOURCE_TYPE, ++ }); +``` + +## Using conditional policy decisions + +So far we've only used the `PermissionEvaluator.authorize` method, which will evaluate conditional decisions before returning a result. In this step, we want to evaluate conditional decisions within our plugin, so we'll use `PermissionEvaluator.authorizeConditional` instead. + +```diff +// plugins/todo-list-backend/src/service/router.ts + +- import { createPermissionIntegrationRouter } from '@backstage/plugin-permission-node'; ++ import { ++ createPermissionIntegrationRouter, ++ createConditionTransformer, ++ ConditionTransformer, ++ } from '@backstage/plugin-permission-node'; +- import { add, getAll, getTodo, update } from './todos'; ++ import { add, getAll, getTodo, TodoFilter, update } from './todos'; + import { + todosListCreate, + todosListUpdate, ++ todosListRead, + TODO_LIST_RESOURCE_TYPE, + } from './permissions'; ++ import { rules } from './rules'; + ++ const transformConditions: ConditionTransformer = createConditionTransformer(Object.values(rules)); + +- router.get('/todos', async (_req, res) => { ++ router.get('/todos', async (req, res) => { ++ const token = getBearerTokenFromAuthorizationHeader( ++ req.header('authorization'), ++ ); ++ ++ const decision = ( ++ await permissions.authorizeConditional([{ permission: todosListRead }], { ++ token, ++ }) ++ )[0]; ++ ++ if (decision.result === AuthorizeResult.DENY) { ++ throw new NotAllowedError('Unauthorized'); ++ } ++ ++ if (decision.result === AuthorizeResult.CONDITIONAL) { ++ const filter = transformConditions(decision.conditions); ++ res.json(getAll(filter)); ++ } else { ++ res.json(getAll()); ++ } ++ } +- res.json(getAll()); + }); +``` + +To make the process of handling conditional decisions easier, the permission framework provides a `createConditionTransformer` helper. This function accepts an array of permission rules, and returns a transformer function which converts the conditions to the format needed by the plugin using the `toQuery` method defined on each rule. + +Since `TodoFilter` used in our plugin matches the structure of the conditions object, we can directly pass the output of our condition transformer. If the filters were structured differently, we'd need to transform it further before passing it to the api. + +## 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. + +```diff +// packages/backend/src/plugins/permission.ts + +... + +import { + todoListCreate, + todoListUpdate, ++ todoListRead, + TODO_LIST_RESOURCE_TYPE, +} from '@internal/plugin-todo-list-common'; + +... + +- if (isPermission(request.permission, todoListUpdate)) { ++ if ( ++ isPermission(request.permission, todoListUpdate) || ++ isPermission(request.permission, todoListRead) ++ ) { + return createTodoListConditionalDecision( + request.permission, + todoListConditions.isOwner(user?.identity.userEntityRef), + ); + } +``` + +Once the changes to the permission policy are saved, the UI should show only the todo items you've created. diff --git a/docs/permission/writing-a-policy.md b/docs/permission/writing-a-policy.md new file mode 100644 index 0000000000..63f0667dcf --- /dev/null +++ b/docs/permission/writing-a-policy.md @@ -0,0 +1,134 @@ +--- +id: writing-a-policy +title: Writing a permission policy +description: How to write your own permission policy as a Backstage integrator +--- + +In the [previous section](./getting-started.md), we were able to set up the permission framework and make a simple change to our `TestPermissionPolicy` to confirm that policy is indeed wired up correctly. + +That policy looked like this: + +```typescript +// packages/backend/src/plugins/permission.ts + +class TestPermissionPolicy implements PermissionPolicy { + async handle(request: PolicyQuery): Promise { + if (request.permission.name === 'catalog.entity.delete') { + return { + result: AuthorizeResult.DENY, + }; + } + + return { result: AuthorizeResult.ALLOW }; + } +} +``` + +## What's in a policy? + +Let's break this down a bit further. The request object of type [PolicyQuery](https://backstage.io/docs/reference/plugin-permission-node.policyquery) is a simple wrapper around [the Permission object](https://backstage.io/docs/reference/plugin-permission-common.permission). This permission object encapsulates information about the action that the user is attempting to perform (See [the Concepts page](./concepts.md) for more details). + +In the policy above, we are checking to see if the provided action is a catalog entity delete action, which is the permission that the catalog plugin authors have created to represent the action of unregistering a catalog entity. If this is the case, we return a [Definitive Policy Decision](https://backstage.io/docs/reference/plugin-permission-common.definitivepolicydecision) of DENY. In all other cases, we return ALLOW (resulting in an allow-by-default behavior). + +As we confirmed in the previous section, we know that this now prevents us from unregistering catalog components. Hooray! But you may notice that this prevents _anyone_ from unregistering a component, which is not a very realistic policy. Let's improve this policy by disabling the unregister action _unless you are the owner of this component_. + +## Conditional decisions + +Let's change the policy to the following: + +```diff +- import { IdentityClient } from '@backstage/plugin-auth-node'; ++ import { ++ BackstageIdentityResponse, ++ IdentityClient ++ } from '@backstage/plugin-auth-node'; + import { + AuthorizeResult, + PolicyDecision, ++ isPermission, +} from '@backstage/plugin-permission-common'; ++ import { ++ catalogConditions, ++ createCatalogConditionalDecision, ++ } from '@backstage/plugin-catalog-backend/alpha'; ++ import { ++ catalogEntityDeletePermission, ++ } from '@backstage/plugin-catalog-common'; + + ... + + class TestPermissionPolicy implements PermissionPolicy { +- async handle(request: PolicyQuery): Promise { ++ async handle( ++ request: PolicyQuery, ++ user?: BackstageIdentityResponse, ++ ): Promise { +- if (request.permission.name === 'catalog.entity.delete') { ++ if (isPermission(request.permission, catalogEntityDeletePermission)) { +- return { +- result: AuthorizeResult.DENY, +- }; ++ return createCatalogConditionalDecision( ++ request.permission, ++ catalogConditions.isEntityOwner( ++ user?.identity.ownershipEntityRefs ?? [], ++ ), ++ ); + } + + return { result: AuthorizeResult.ALLOW }; + } + } +``` + +Let's walk through the new code that we just added. + +Instead of returning an Definitive Policy Decision, we use factory methods to construct a [Conditional Policy Decision](https://backstage.io/docs/reference/plugin-permission-common.conditionalpolicydecision) (See the [Concepts page](./concepts.md) for more details). Since the policy doesn't have enough information to determine if `user` is the entity owner, this criteria is encapsulated within the conditional decision. However, `createCatalogConditionalDecision` will not compile unless `request.permission` is a catalog entity [`ResourcePermission`](https://backstage.io/docs/reference/plugin-permission-common.resourcepermission). This type constraint ensures that policies return conditional decisions that are compatible with the requested permission. To address this, we use [`isPermission`](https://backstage.io/docs/reference/plugin-permission-common.ispermission) to ["narrow"](https://www.typescriptlang.org/docs/handbook/2/narrowing.html) the type of `request.permission` to `ResourcePermission<'catalog-entity'>`. This matches the runtime behavior that was in place before, but you'll notice that the type of `request.permission` has changed within the scope of that `if` statement. + +The `catalogConditions` object contains all of the rules defined by the catalog plugin. These rules can be combined to form a [`PermissionCriteria`](https://backstage.io/docs/reference/plugin-permission-common.permissioncriteria) object, but for this case we only need to use the `isEntityOwner` rule. This rule accepts a list of entity refs that represent User identity and Group membership used to determine ownership. The second argument to `PermissionPolicy#handle` provides us with a `BackstageIdentityResponse` object, from which we can grab the user's `ownershipEntityRefs`. We provide an empty array as a fallback since the user may be anonymous. + +You should now be able to see in your Backstage app that the unregister entity button is enabled for entities that you own, but disabled for all other entities! + +## Resource types + +Now let's say we want to prevent all actions on catalog entities unless performed by the owner. One way to achieve this may be to simply update the `if` statement and check for each permission. If you choose to write your policy this way, it will certainly work! However, it may be difficult to maintain as the policy grows, and it may not be obvious if certain permissions are left out. We can author this same policy in a more scalable way by checking the resource type of the requested permission. + +```diff +import { + AuthorizeResult, + PolicyDecision, +- isPermission, ++ isResourcePermission, +} from '@backstage/plugin-permission-common'; + import { + catalogConditions, + createCatalogConditionalDecision, + } from '@backstage/plugin-catalog-backend/alpha'; +- import { +- catalogEntityDeletePermission, +- } from '@backstage/plugin-catalog-common'; + +... + +class TestPermissionPolicy implements PermissionPolicy { + async handle( + request: PolicyQuery, + user?: BackstageIdentityResponse, + ): Promise { +- if (isPermission(request.permission, catalogEntityDeletePermission)) { ++ if (isResourcePermission(request.permission, 'catalog-entity')) { + return createCatalogConditionalDecision( + request.permission, + catalogConditions.isEntityOwner( + user?.identity.ownershipEntityRefs ?? [], + ), + ); + } + + return { result: AuthorizeResult.ALLOW }; + } +``` + +In this example, we use [`isResourcePermission`](https://backstage.io/docs/reference/plugin-permission-common.isresourcepermission) to match all permissions with a resource type of `catalog-entity`. Just like `isPermission`, this helper will "narrow" the type of `request.permission` and enable the use of `createCatalogConditionalDecision`. In addition to the behavior you observed before, you should also see that catalog entities are no longer visible unless you are the owner - success! + +_Note:_ Some catalog permissions do not have the `'catalog-entity'` resource type, such as [`catalogEntityCreatePermission`](https://github.com/backstage/backstage/blob/1e5e9fb9de9856a49e60fc70c38a4e4e94c69570/plugins/catalog-common/src/permissions.ts#L49). In those cases, a definitive decision is required because conditions can't be applied to an entity that does not exist yet. diff --git a/docs/releases/v1.2.0-changelog.md b/docs/releases/v1.2.0-changelog.md new file mode 100644 index 0000000000..30c780e7ae --- /dev/null +++ b/docs/releases/v1.2.0-changelog.md @@ -0,0 +1,2257 @@ +# Release v1.2.0 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.0 + +### Major Changes + +- 0ad901569f: The TechDocs Addon framework is now generally available. + +### Minor Changes + +- 52fddad92d: Introducing a package with utilities to help test TechDocs Addons. + +### Patch Changes + +- f84e0e2818: Fixed a bug preventing testing of TechDocs Addons whose dom contained `` tags. +- 7c398c6473: The `TechDocsAddonTester` class may now be extended if custom test configuration is needed. +- 614bd96e2a: Fixed a type bug preventing `buildAddonsInTechDocs().withApis()` from being called with multiple partial API implementations. +- Updated dependencies + - @backstage/core-components@0.9.4 + - @backstage/test-utils@1.1.0 + - @backstage/plugin-techdocs@1.1.1 + - @backstage/core-plugin-api@1.0.2 + - @backstage/plugin-catalog@1.2.0 + - @backstage/integration-react@1.1.0 + - @backstage/plugin-techdocs-react@1.0.0 + - @backstage/plugin-search-react@0.2.0 + - @backstage/core-app-api@1.0.2 + +## @backstage/plugin-techdocs-module-addons-contrib@1.0.0 + +### Major Changes + +- 0ad901569f: The TechDocs Addon framework is now generally available. + +### Minor Changes + +- 5f4dbd2b52: A package for contributed TechDocs addons. + + In this release it will introduce the ReportIssue addon, which lets you select text and open a GitHub/Gitlab issue. + +### Patch Changes + +- 10d86dedc0: Improved inline/type documentation for the addon. + +- 52419be116: Create a TechDocs `` addon that allows users to set a font size in the browser's local storage for the text of documentation pages. + + Here's an example on how to use it in a Backstage app: + + ```diff + import { + DefaultTechDocsHome, + TechDocsIndexPage, + TechDocsReaderPage, + } from '@backstage/plugin-techdocs'; + import { TechDocsAddons } from '@backstage/plugin-techdocs-react/alpha'; + +import { TextSize } from '@backstage/plugin-techdocs-module-addons-contrib'; + + const AppRoutes = () => { + + // other plugin routes + }> + + + } + > + + + + + + ; + }; + ``` + +- 075a9a067b: Updated the return type of `createTechDocsAddonExtension` to better reflect the fact that passing children to Addon components is not a valid use-case. + +- c25e880e36: Introducing the Expandable Navigation addon, which lets you expand and collapse the TechDocs main navigation and store your preference in local storage. + +- Updated dependencies + - @backstage/core-components@0.9.4 + - @backstage/integration@1.2.0 + - @backstage/core-plugin-api@1.0.2 + - @backstage/integration-react@1.1.0 + - @backstage/plugin-techdocs-react@1.0.0 + +## @backstage/plugin-techdocs-react@1.0.0 + +### Major Changes + +- 0ad901569f: The TechDocs Addon framework is now generally available. + +### Patch Changes + +- 52419be116: Create a new addon location called "Settings", it is designed for addons that allow users to customize the reading experience in documentation pages. + + Usage example: + + ```tsx + const TextSize = techdocsModuleAddonsContribPlugin.provide( + createTechDocsAddonExtension({ + name: 'TextSize', + location: TechDocsAddonLocations.Settings, + component: TextSizeAddon, + }), + ); + ``` + +- c25e880e36: Added overload signatures for `createTechDocsAddonExtension` to handle TechDocs addons without props. + +- 52fddad92d: The `TechDocsStorageApi` and its associated ref are now exported by `@backstage/plugin-techdocs-react`. The API interface, ref, and types are now deprecated in `@backstage/plugin-techdocs` and will be removed in a future release. + +- 075a9a067b: Updated the return type of `createTechDocsAddonExtension` to better reflect the fact that passing children to Addon components is not a valid use-case. + +- Updated dependencies + - @backstage/core-components@0.9.4 + - @backstage/core-plugin-api@1.0.2 + - @backstage/catalog-model@1.0.2 + +## @backstage/integration@1.2.0 + +### Minor Changes + +- e295ce87de: added the possibility to handle raw Gitlab URLs with nested namespaces + +- 6673babab9: Gerrit UrlReader: Implemented `readTree` + +- 1b4e1e2306: Split `bitbucket` integration into `bitbucketCloud` and `bitbucketServer` + (backwards compatible). + + In order to migrate to the new integration configs, + move your configs from `integrations.bitbucket` + to `integrations.bitbucketCloud` or `integrations.bitbucketServer`. + + Migration example: + + **Before:** + + ```yaml + integrations: + bitbucket: + - host: bitbucket.org + username: bitbucket_user + appPassword: app-password + - host: bitbucket-server.company.com + token: my-token + ``` + + **After:** + + ```yaml + integrations: + bitbucketCloud: + - username: bitbucket_user + appPassword: app-password + bitbucketServer: + - host: bitbucket-server.company.com + token: my-token + ``` + +- 566407bf8a: Gerrit Integration: Added the `getGerritProjectsApiUrl` function + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.1 + +## @backstage/integration-react@1.1.0 + +### Minor Changes + +- 1b4e1e2306: Split `bitbucket` integration into `bitbucketCloud` and `bitbucketServer` + (backwards compatible). + + In order to migrate to the new integration configs, + move your configs from `integrations.bitbucket` + to `integrations.bitbucketCloud` or `integrations.bitbucketServer`. + + Migration example: + + **Before:** + + ```yaml + integrations: + bitbucket: + - host: bitbucket.org + username: bitbucket_user + appPassword: app-password + - host: bitbucket-server.company.com + token: my-token + ``` + + **After:** + + ```yaml + integrations: + bitbucketCloud: + - username: bitbucket_user + appPassword: app-password + bitbucketServer: + - host: bitbucket-server.company.com + token: my-token + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.4 + - @backstage/integration@1.2.0 + - @backstage/core-plugin-api@1.0.2 + - @backstage/config@1.0.1 + +## @backstage/test-utils@1.1.0 + +### Minor Changes + +- 1da8b248c2: Added the options parameter to `renderWithEffects`, which if forwarded to the `render` function from `@testling-library/react`. Initially only the `wrapper` option is supported. +- 1da8b248c2: Added `createTestAppWrapper`, which returns a component that can be used as the `wrapper` option for `render` or `renderWithEffects`. + +### Patch Changes + +- 1da8b248c2: Fixed `renderInTestApp` so that it is able to re-render the result without removing the app wrapping. +- 7a5ddfd595: Added missing `Routes` element to wrap the `Route` elements of the test app wrapping. +- 7a5ddfd595: The internal elements created as part of the `mountedRoutes` implementation are now hidden during rendering. +- Updated dependencies + - @backstage/core-plugin-api@1.0.2 + - @backstage/config@1.0.1 + - @backstage/core-app-api@1.0.2 + - @backstage/plugin-permission-common@0.6.1 + - @backstage/plugin-permission-react@0.4.1 + +## @backstage/plugin-adr@0.1.0 + +### Minor Changes + +- e73075a301: Implement ADR plugin + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.4 + - @backstage/plugin-adr-common@0.1.0 + - @backstage/core-plugin-api@1.0.2 + - @backstage/plugin-catalog-react@1.1.0 + - @backstage/integration-react@1.1.0 + +## @backstage/plugin-adr-backend@0.1.0 + +### Minor Changes + +- e73075a301: Implement ADR plugin + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.3 + - @backstage/plugin-adr-common@0.1.0 + - @backstage/integration@1.2.0 + - @backstage/config@1.0.1 + - @backstage/plugin-search-common@0.3.4 + - @backstage/catalog-client@1.0.2 + - @backstage/catalog-model@1.0.2 + +## @backstage/plugin-adr-common@0.1.0 + +### Minor Changes + +- e73075a301: Implement ADR plugin + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.2.0 + - @backstage/plugin-search-common@0.3.4 + - @backstage/catalog-model@1.0.2 + +## @backstage/plugin-auth-backend@0.14.0 + +### Minor Changes + +- 2df2f01a29: Removed the explicit `disableRefresh` option from `OAuthAdapter`. Refresh can still be disabled for a provider by not implementing the `refresh` method. + +### Patch Changes + +- cac3ba68a2: Fixed a bug that was introduced in `0.13.1-next.0` which caused the `ent` claim of issued tokens to be dropped. + +- 5d268623dd: Updates the OAuth2 Proxy provider to require less infrastructure configuration. + + The auth result object of the OAuth2 Proxy now provides access to the request headers, both through the `headers` object as well as `getHeader` method. The existing logic that parses and extracts the user information from ID tokens is deprecated and will be removed in a future release. See the OAuth2 Proxy provider documentation for more details. + + The OAuth2 Proxy provider now also has a default `authHandler` implementation that reads the display name and email from the incoming request headers. + +- 2df2f01a29: The Auth0 adapter no longer disables session refreshing. + +- cfc0f19699: Updated dependency `fs-extra` to `10.1.0`. + +- 787ae0d541: Add more common predefined sign-in resolvers to auth providers. + + Add the existing resolver to more providers (already available at `google`): + + - `providers.microsoft.resolvers.emailLocalPartMatchingUserEntityName()` + - `providers.okta.resolvers.emailLocalPartMatchingUserEntityName()` + + Add a new resolver for simple email-to-email matching: + + - `providers.google.resolvers.emailMatchingUserEntityProfileEmail()` + - `providers.microsoft.resolvers.emailMatchingUserEntityProfileEmail()` + - `providers.okta.resolvers.emailMatchingUserEntityProfileEmail()` + +- 9ec4e0613e: Update to `jose` 4.6.0 + +- Updated dependencies + - @backstage/backend-common@0.13.3 + - @backstage/config@1.0.1 + - @backstage/plugin-auth-node@0.2.1 + - @backstage/catalog-client@1.0.2 + - @backstage/catalog-model@1.0.2 + +## @backstage/plugin-catalog@1.2.0 + +### Minor Changes + +- 4274844a8c: Use InfoCardVariants on custom cards variant attribute +- ceca63d149: Added the prop `NotFoundComponent` to `EntityLayout` which can be used to include a custom component when an entity is not found in the catalog + +### Patch Changes + +- 051fc60258: Lighthouse was reporting this button as having invalid aria- values, as the popover doesn't exist until clicked. This adds additional labels to the button to make it valid aria +- 2bcb0a0e2b: Sidebar NAV now includes aria-label. Component AboutField now uses h2 variant instead of subtitle2 (font properties unchanged) +- 3a74e203a8: Updated search result components to support rendering content with highlighted matched terms +- Updated dependencies + - @backstage/core-components@0.9.4 + - @backstage/core-plugin-api@1.0.2 + - @backstage/plugin-catalog-react@1.1.0 + - @backstage/integration-react@1.1.0 + - @backstage/plugin-search-react@0.2.0 + - @backstage/plugin-search-common@0.3.4 + - @backstage/catalog-client@1.0.2 + - @backstage/catalog-model@1.0.2 + - @backstage/plugin-catalog-common@1.0.2 + +## @backstage/plugin-catalog-backend-module-gerrit@0.1.0 + +### Minor Changes + +- 566407bf8a: Initial version of the `plugin-catalog-backend-module-gerrit` plugin + +### Patch Changes + +- 57f684f59c: Fix incorrect main path in `publishConfig` +- cfc0f19699: Updated dependency `fs-extra` to `10.1.0`. +- Updated dependencies + - @backstage/backend-common@0.13.3 + - @backstage/plugin-catalog-backend@1.1.2 + - @backstage/backend-tasks@0.3.1 + - @backstage/integration@1.2.0 + - @backstage/config@1.0.1 + - @backstage/catalog-model@1.0.2 + +## @backstage/plugin-catalog-react@1.1.0 + +### Minor Changes + +- 4274844a8c: Use InfoCardVariants on custom cards variant attribute + +### Patch Changes + +- 0418447669: Added menu parent role for menu items accessibility +- b7514d19ff: Update the rendering of links in the entity inspector so that values starting with `https?://` are rendered as links as well. +- b880c0e092: Fix `EntityTypeFilter` so generating available types is case insensitive +- 57f41fb8d6: Make Menu item on filters accessible through keyboard +- Updated dependencies + - @backstage/core-components@0.9.4 + - @backstage/integration@1.2.0 + - @backstage/core-plugin-api@1.0.2 + - @backstage/catalog-client@1.0.2 + - @backstage/catalog-model@1.0.2 + - @backstage/plugin-catalog-common@1.0.2 + - @backstage/plugin-permission-common@0.6.1 + - @backstage/plugin-permission-react@0.4.1 + +## @backstage/plugin-codescene@0.1.0 + +### Minor Changes + +- 9bc6e9493f: Add CodeScene plugin + +### Patch Changes + +- 95598f4fd5: Updated dependency `@testing-library/user-event` to `^14.0.0`. +- e462112be5: Updated dependency `rc-progress` to `3.3.2`. +- Updated dependencies + - @backstage/core-components@0.9.4 + - @backstage/core-plugin-api@1.0.2 + - @backstage/config@1.0.1 + +## @backstage/plugin-scaffolder@1.2.0 + +### Minor Changes + +- 06ab5218f9: Add new bitbucket(Cloud/Server) integrations to ScaffolderClient. + +### Patch Changes + +- 8dce7d5244: Allow validation for custom field extension with type object +- 70817dafc0: Updated dependency `use-immer` to `^0.7.0`. +- ace230b599: Updated dependency `@codemirror/legacy-modes` to `^0.20.0`. +- Updated dependencies + - @backstage/core-components@0.9.4 + - @backstage/integration@1.2.0 + - @backstage/core-plugin-api@1.0.2 + - @backstage/plugin-scaffolder-common@1.1.0 + - @backstage/plugin-catalog-react@1.1.0 + - @backstage/integration-react@1.1.0 + - @backstage/config@1.0.1 + - @backstage/catalog-client@1.0.2 + - @backstage/catalog-model@1.0.2 + - @backstage/plugin-catalog-common@1.0.2 + - @backstage/plugin-permission-react@0.4.1 + +## @backstage/plugin-scaffolder-backend@1.2.0 + +### Minor Changes + +- 9818112d12: Update the `github:publish` action to allow passing required status check + contexts before merging to the main branch. +- f8baf7df44: Added the ability to reference the user in the `template.yaml` manifest +- 8d5a2238a9: Split `publish:bitbucket` into `publish:bitbucketCloud` and `publish:bitbucketServer`. + + In order to migrate from the deprecated action, you need to replace the use of action + `publish:bitbucket` in your templates with the use of either `publish:bitbucketCloud` + or `publish:bitbucketServer` - depending on which destination SCM provider you use. + + Additionally, these actions will not utilize `integrations.bitbucket` anymore, + but `integrations.bitbucketCloud` or `integrations.bitbucketServer` respectively. + You may or may not have migrated to these already. + + As described in a previous changeset, using these two replacement integrations configs + will not compromise use cases which still rely on `integrations.bitbucket` as this was + set up in a backwards compatible way. + + Additionally, please mind that the option `enableLFS` is only available (and always was) + for Bitbucket Server use cases and therefore, is not even part of the schema for + `publish:bitbucketCloud` anymore. + +### Patch Changes + +- 0fc65cbf89: Override default commit message and author details in GitHub, Azure, bitbucket +- cfc0f19699: Updated dependency `fs-extra` to `10.1.0`. +- Updated dependencies + - @backstage/backend-common@0.13.3 + - @backstage/plugin-catalog-backend@1.1.2 + - @backstage/integration@1.2.0 + - @backstage/plugin-scaffolder-common@1.1.0 + - @backstage/config@1.0.1 + - @backstage/catalog-client@1.0.2 + - @backstage/catalog-model@1.0.2 + +## @backstage/plugin-scaffolder-backend-module-rails@0.4.0 + +### Minor Changes + +- 3d001a3bcf: **BREAKING**: Added a new `allowedImageNames` option, which needs to list any image name for it to be allowed as `imageName` input. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.3 + - @backstage/plugin-scaffolder-backend@1.2.0 + - @backstage/integration@1.2.0 + - @backstage/config@1.0.1 + +## @backstage/plugin-scaffolder-common@1.1.0 + +### Minor Changes + +- f8baf7df44: Added the ability to reference the user in the `template.yaml` manifest + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.2 + +## @backstage/plugin-search-react@0.2.0 + +### Minor Changes + +- bdbe620797: **BREAKING**: `SearchContextProviderForStorybook` and `SearchApiProviderForStorybook` has been deleted. New mock implementation of the `SearchApi` introduced. If you need to mock the api we recommend you to do the following: + + ```tsx + import { + searchApiRef, + MockSearchApi, + SearchContextProvider, + } from '@backstage/plugin-search-react'; + import { TestApiProvider } from '@backstage/test-utils'; + + + + + + ; + ``` + +### Patch Changes + +- 11a46863de: Export `useSearchContextCheck` hook to check if the search context is available +- a307a14be0: Removed dependency on `@backstage/core-app-api`. +- 3a74e203a8: Updated search result components to support rendering content with highlighted matched terms +- Updated dependencies + - @backstage/core-plugin-api@1.0.2 + - @backstage/plugin-search-common@0.3.4 + +## @backstage/plugin-tech-insights-backend@0.4.0 + +### Minor Changes + +- 3333e20b27: **BREAKING**: The `buildTechInsightsContext` function now takes an additional + field in its options argument: `tokenManager`. This is an instance of + `TokenManager`, which can be found in your backend initialization code's + `env`. + + ```diff + const builder = buildTechInsightsContext({ + logger: env.logger, + config: env.config, + database: env.database, + discovery: env.discovery, + scheduler: env.scheduler, + + tokenManager: env.tokenManager, + factRetrievers: [ /* ... */ ], + }); + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.3 + - @backstage/backend-tasks@0.3.1 + - @backstage/plugin-tech-insights-node@0.3.0 + - @backstage/config@1.0.1 + - @backstage/catalog-client@1.0.2 + - @backstage/catalog-model@1.0.2 + +## @backstage/plugin-tech-insights-node@0.3.0 + +### Minor Changes + +- 58e2c46151: **BREAKING**: The `FactRetrieverContext` type now contains an additional + field: `tokenManager`. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.3 + - @backstage/config@1.0.1 + +## @backstage/app-defaults@1.0.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.4 + - @backstage/core-plugin-api@1.0.2 + - @backstage/core-app-api@1.0.2 + - @backstage/plugin-permission-react@0.4.1 + +## @backstage/backend-common@0.13.3 + +### Patch Changes + +- e0a6360b80: Added a `stream()` method to complement the `buffer()` method on `ReadUrlResponse`. A `ReadUrlResponseFactory` utility class is now also available, providing a simple, consistent way to provide a valid `ReadUrlResponse`. + + This method, though optional for now, will be required on the responses of `UrlReader.readUrl()` implementations in a future release. + +- 4b811aafce: Implemented the `UrlReader.search()` method for Google Cloud Storage. Due to limitations in the underlying storage API, only prefix-based searches are supported right now (for example, `https://storage.cloud.google.com/your-bucket/some-path/*`). + +- 6673babab9: Gerrit integration: Added optional `cloneUrl` string to config. + +- 75bf9e1da9: Split BitbucketUrlReader into BitbucketCloudUrlReader and BitbucketServerUrlReader. Backwards compatible. + +- 28b0e4ddef: Update types to match the new version of `@keyv/redis` + +- 5fcbd86960: **DEPRECATION**: Server-to-server authentication tokens issued from a + `TokenManager` (specifically, `ServerTokenManager`) now has an expiry time set, + for one hour in the future from when issued. This improves security. The ability + to pass in and validate tokens that either have a missing `exp` claim, or an + `exp` claim that expired in the past, is now deprecated. Trying to do so will + lead to logged warnings, and in a future release will instead lead to errors. + + It was always the case that users of this interface were expected to call its + `getToken()` method for every outgoing call and never hold on to any given token + for reuse. But this now has become even more important advice to heed, and you + should verify that you do not hold on to and reuse tokens such as these in your + own code. + +- cfc0f19699: Updated dependency `fs-extra` to `10.1.0`. + +- 9ec4e0613e: Update to `jose` 4.6.0 + +- Updated dependencies + - @backstage/integration@1.2.0 + - @backstage/cli-common@0.1.9 + - @backstage/config@1.0.1 + - @backstage/config-loader@1.1.1 + +## @backstage/backend-tasks@0.3.1 + +### Patch Changes + +- 73480846dd: `TaskScheduleDefinition` has been updated to also accept an options object containing duration information in the form of days, hours, seconds and so on. This allows for scheduling without importing `luxon`. + + ```diff + -import { Duration } from 'luxon'; + // omitted other code + + const schedule = env.scheduler.createScheduledTaskRunner({ + - frequency: Duration.fromObject({ minutes: 10 }), + - timeout: Duration.fromObject({ minutes: 15 }), + + frequency: { minutes: 10 }, + + timeout: { minutes: 15 }, + // omitted other code + }); + ``` + +- cfd779a9bc: Scheduled tasks now have an optional `scope` field. If unset, or having the + value `'global'`, the old behavior with cross-worker locking is retained. If + having the value `'local'`, there is no coordination across workers and the + behavior is more like `setInterval`. This can be used to replace usages of + `runPeriodically` helpers. + +- ebbec677e1: Correctly set next run time for tasks + +- Updated dependencies + - @backstage/backend-common@0.13.3 + - @backstage/config@1.0.1 + +## @backstage/backend-test-utils@0.1.24 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.3 + - @backstage/cli@0.17.1 + - @backstage/config@1.0.1 + +## @backstage/catalog-client@1.0.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.2 + +## @backstage/catalog-model@1.0.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.1 + +## @backstage/cli@0.17.1 + +### Patch Changes + +- 52fb9920ac: Fixed coverage configuration when using `BACKSTAGE_NEXT_TESTS`. +- 6cd1f50ae1: Extended lint rule to prevents imports of stories or tests from production code. +- 97cce67ac7: Add instructions to `create-github-app` command. +- 08e12a3a14: Add package global-agent to support behind a proxy for backstage-cli commands like versions:bump. +- 4d8736eded: Changed Rollup configuration for TypeScript definition plugin to ignore `css`, + `scss`, `sass`, `svg`, `eot`, `woff`, `woff2` and `ttf` files. +- cfc0f19699: Updated dependency `fs-extra` to `10.1.0`. +- 2737777e02: Added the ability to help a user get started with a new organization +- 344ea56acc: Bump `commander` to version 9.1.0 +- 8ab2a8226b: Updated the `create-github-app` command to throw an error if the organization argument is a user or a non existing organization. +- 632be18bbc: Updated `create-github-app` command to prompt for read or write permissions to simplify setup. +- Updated dependencies + - @backstage/cli-common@0.1.9 + - @backstage/config@1.0.1 + - @backstage/release-manifests@0.0.3 + - @backstage/config-loader@1.1.1 + +## @backstage/cli-common@0.1.9 + +### Patch Changes + +- 6e830352d4: Updated dependency `@types/node` to `^16.0.0`. + +## @backstage/codemods@0.1.38 + +### Patch Changes + +- 344ea56acc: Bump `commander` to version 9.1.0 +- Updated dependencies + - @backstage/cli-common@0.1.9 + +## @backstage/config@1.0.1 + +### Patch Changes + +- 6e830352d4: Updated dependency `@types/node` to `^16.0.0`. + +## @backstage/config-loader@1.1.1 + +### Patch Changes + +- cfc0f19699: Updated dependency `fs-extra` to `10.1.0`. +- 9e8ef53498: Handle empty config files gracefully +- Updated dependencies + - @backstage/cli-common@0.1.9 + - @backstage/config@1.0.1 + +## @backstage/core-app-api@1.0.2 + +### Patch Changes + +- 1fae1f57c9: Fix SAML session schema to no longer require the (deprecated) id, to unbreak session data storage. +- Updated dependencies + - @backstage/core-plugin-api@1.0.2 + - @backstage/config@1.0.1 + +## @backstage/core-components@0.9.4 + +### Patch Changes + +- ac19f82936: Added ARIA landmark
to Page component and added ARIA landmark