From 454d17c9902cc2e21dc46be8594cc4830e1e66fd Mon Sep 17 00:00:00 2001 From: secustor Date: Tue, 12 Dec 2023 11:45:57 +0100 Subject: [PATCH 01/35] refactor(backend-common/GithubUrlReader): only call fetch inside fetchResponse Signed-off-by: secustor --- .changeset/eight-rice-cross.md | 5 ++ .../src/reading/GithubUrlReader.ts | 55 ++++++++----------- 2 files changed, 28 insertions(+), 32 deletions(-) create mode 100644 .changeset/eight-rice-cross.md diff --git a/.changeset/eight-rice-cross.md b/.changeset/eight-rice-cross.md new file mode 100644 index 0000000000..e40f731a4e --- /dev/null +++ b/.changeset/eight-rice-cross.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +Do not call fetch directly but rather use fetchResponse facility diff --git a/packages/backend-common/src/reading/GithubUrlReader.ts b/packages/backend-common/src/reading/GithubUrlReader.ts index 764431386d..175815cabf 100644 --- a/packages/backend-common/src/reading/GithubUrlReader.ts +++ b/packages/backend-common/src/reading/GithubUrlReader.ts @@ -107,7 +107,7 @@ export class GithubUrlReader implements UrlReader { let response: Response; try { - response = await fetch(ghUrl, { + response = await this.fetchResponse(ghUrl, { headers: { ...credentials?.headers, ...(options?.etag && { 'If-None-Match': options.etag }), @@ -125,38 +125,13 @@ export class GithubUrlReader implements UrlReader { signal: options?.signal as any, }); } catch (e) { - throw new Error(`Unable to read ${url}, ${e}`); + throw e; } - if (response.status === 304) { - throw new NotModifiedError(); - } - - if (response.ok) { - return ReadUrlResponseFactory.fromNodeJSReadable(response.body, { - etag: response.headers.get('ETag') ?? undefined, - lastModifiedAt: parseLastModified( - response.headers.get('Last-Modified'), - ), - }); - } - - let message = `${url} could not be read as ${ghUrl}, ${response.status} ${response.statusText}`; - if (response.status === 404) { - throw new NotFoundError(message); - } - - // GitHub returns a 403 response with a couple of headers indicating rate - // limit status. See more in the GitHub docs: - // https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting - if ( - response.status === 403 && - response.headers.get('X-RateLimit-Remaining') === '0' - ) { - message += ' (rate limit exceeded)'; - } - - throw new Error(message); + return ReadUrlResponseFactory.fromNodeJSReadable(response.body, { + etag: response.headers.get('ETag') ?? undefined, + lastModifiedAt: parseLastModified(response.headers.get('Last-Modified')), + }); } async readTree( @@ -350,10 +325,26 @@ export class GithubUrlReader implements UrlReader { const response = await fetch(urlAsString, init); if (!response.ok) { - const message = `Request failed for ${urlAsString}, ${response.status} ${response.statusText}`; + let message = `Request failed for ${urlAsString}, ${response.status} ${response.statusText}`; + + if (response.status === 304) { + throw new NotModifiedError(); + } + if (response.status === 404) { throw new NotFoundError(message); } + + // GitHub returns a 403 response with a couple of headers indicating rate + // limit status. See more in the GitHub docs: + // https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting + if ( + response.status === 403 && + response.headers.get('X-RateLimit-Remaining') === '0' + ) { + message += ' (rate limit exceeded)'; + } + throw new Error(message); } From 88fcc3b5e56d839b32492ddee45c40fc010514bf Mon Sep 17 00:00:00 2001 From: secustor Date: Tue, 12 Dec 2023 11:53:04 +0100 Subject: [PATCH 02/35] docs(backend-common/GithubUrlReader): put fetchResponse in code block Signed-off-by: secustor --- .changeset/eight-rice-cross.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/eight-rice-cross.md b/.changeset/eight-rice-cross.md index e40f731a4e..1628276817 100644 --- a/.changeset/eight-rice-cross.md +++ b/.changeset/eight-rice-cross.md @@ -2,4 +2,4 @@ '@backstage/backend-common': patch --- -Do not call fetch directly but rather use fetchResponse facility +Do not call fetch directly but rather use `fetchResponse` facility From 42b4db71ded631e9a286c6b19c6fe16fc211f184 Mon Sep 17 00:00:00 2001 From: secustor Date: Tue, 12 Dec 2023 14:37:11 +0100 Subject: [PATCH 03/35] make fetchResponse protected to allow overwriting Signed-off-by: secustor --- packages/backend-common/src/reading/GithubUrlReader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend-common/src/reading/GithubUrlReader.ts b/packages/backend-common/src/reading/GithubUrlReader.ts index 175815cabf..fed6fd9430 100644 --- a/packages/backend-common/src/reading/GithubUrlReader.ts +++ b/packages/backend-common/src/reading/GithubUrlReader.ts @@ -317,7 +317,7 @@ export class GithubUrlReader implements UrlReader { return repo.default_branch; } - private async fetchResponse( + protected async fetchResponse( url: string | URL, init: RequestInit, ): Promise { From f4d77420496ff7bb9092a56509bcd643208beac4 Mon Sep 17 00:00:00 2001 From: secustor Date: Thu, 14 Dec 2023 15:50:16 +0100 Subject: [PATCH 04/35] remove unnecessary try catch block Signed-off-by: secustor --- .../src/reading/GithubUrlReader.ts | 39 ++++++++----------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/packages/backend-common/src/reading/GithubUrlReader.ts b/packages/backend-common/src/reading/GithubUrlReader.ts index fed6fd9430..6fd24afc08 100644 --- a/packages/backend-common/src/reading/GithubUrlReader.ts +++ b/packages/backend-common/src/reading/GithubUrlReader.ts @@ -105,28 +105,23 @@ export class GithubUrlReader implements UrlReader { credentials, ); - let response: Response; - try { - response = await this.fetchResponse(ghUrl, { - headers: { - ...credentials?.headers, - ...(options?.etag && { 'If-None-Match': options.etag }), - ...(options?.lastModifiedAfter && { - 'If-Modified-Since': options.lastModifiedAfter.toUTCString(), - }), - Accept: 'application/vnd.github.v3.raw', - }, - // TODO(freben): The signal cast is there because pre-3.x versions of - // node-fetch have a very slightly deviating AbortSignal type signature. - // The difference does not affect us in practice however. The cast can - // be removed after we support ESM for CLI dependencies and migrate to - // version 3 of node-fetch. - // https://github.com/backstage/backstage/issues/8242 - signal: options?.signal as any, - }); - } catch (e) { - throw e; - } + const response = await this.fetchResponse(ghUrl, { + headers: { + ...credentials?.headers, + ...(options?.etag && { 'If-None-Match': options.etag }), + ...(options?.lastModifiedAfter && { + 'If-Modified-Since': options.lastModifiedAfter.toUTCString(), + }), + Accept: 'application/vnd.github.v3.raw', + }, + // TODO(freben): The signal cast is there because pre-3.x versions of + // node-fetch have a very slightly deviating AbortSignal type signature. + // The difference does not affect us in practice however. The cast can + // be removed after we support ESM for CLI dependencies and migrate to + // version 3 of node-fetch. + // https://github.com/backstage/backstage/issues/8242 + signal: options?.signal as any, + }); return ReadUrlResponseFactory.fromNodeJSReadable(response.body, { etag: response.headers.get('ETag') ?? undefined, From 36d598bf98ff2389fac3b100d3880c8646b942ad Mon Sep 17 00:00:00 2001 From: Adam Vollrath Date: Tue, 19 Dec 2023 01:18:55 -0600 Subject: [PATCH 05/35] Sanitize input between PR deployment workflows. Signed-off-by: Adam Vollrath --- .github/workflows/uffizzi-preview.yaml | 28 +++++++++----------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/.github/workflows/uffizzi-preview.yaml b/.github/workflows/uffizzi-preview.yaml index a9cce41fce..79f735c352 100644 --- a/.github/workflows/uffizzi-preview.yaml +++ b/.github/workflows/uffizzi-preview.yaml @@ -48,47 +48,37 @@ jobs: let fs = require('fs'); fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/preview-spec.zip`, Buffer.from(download.data)); - - name: 'Unzip artifact' - run: unzip preview-spec.zip + - name: 'Accept event from first stage' + run: unzip preview-spec.zip event.json - name: Read Event into ENV run: | - echo 'EVENT_JSON<> $GITHUB_ENV - cat event.json >> $GITHUB_ENV - echo -e '\nEOF' >> $GITHUB_ENV + echo PR_NUMBER=$(jq '.number | tonumber' < event.json) >> $GITHUB_ENV + echo ACTION=$(jq --raw-output '.action | tostring | [scan("\\w+")][0]' < event.json) >> $GITHUB_ENV + echo GIT_REF=$(jq --raw-output '.pull_request.head.sha | tostring | [scan("\\w+")][0]' < event.json) >> $GITHUB_ENV - name: Hash Rendered Manifests File id: hash # If the previous workflow was triggered by a PR close event, we will not have a manifests file artifact. - if: ${{ fromJSON(env.EVENT_JSON).action != 'closed' }} + if: ${{ env.ACTION != 'closed' }} run: | + unzip preview-spec.zip manifests.rendered.yml ls echo "MANIFESTS_FILE_HASH=$(md5sum manifests.rendered.yml | awk '{ print $1 }')" >> $GITHUB_ENV - name: Cache Manifests File - if: ${{ fromJSON(env.EVENT_JSON).action != 'closed' }} + if: ${{ env.ACTION != 'closed' }} uses: actions/cache@v3.3.2 with: path: manifests.rendered.yml key: ${{ env.MANIFESTS_FILE_HASH }} - - name: Read PR Number From Event Object - id: pr - run: echo "PR_NUMBER=${{ fromJSON(env.EVENT_JSON).number }}" >> $GITHUB_ENV - - - name: Read Event Type from Event Object - id: action - run: echo "ACTION=${{ fromJSON(env.EVENT_JSON).action }}" >> $GITHUB_ENV - - - name: Read Git Ref From Event Object - id: ref - run: echo "GIT_REF=${{ fromJSON(env.EVENT_JSON).pull_request.head.sha }}" >> $GITHUB_ENV - - name: DEBUG - Print Job Outputs if: ${{ runner.debug }} run: | echo "PR number: ${{ env.PR_NUMBER }}" echo "Git Ref: ${{ env.GIT_REF }}" + echo "Action: ${{ env.ACTION }}" echo "Manifests file hash: ${{ env.MANIFESTS_FILE_HASH }}" cat event.json From 08faa6ed060da7ca940b9f0534bc2fc6d853b672 Mon Sep 17 00:00:00 2001 From: Adam Vollrath Date: Tue, 19 Dec 2023 12:27:49 -0600 Subject: [PATCH 06/35] Update to store user input in a safer place. Signed-off-by: Adam Vollrath --- .github/workflows/uffizzi-preview.yaml | 32 +++++++++++++------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/uffizzi-preview.yaml b/.github/workflows/uffizzi-preview.yaml index 79f735c352..cd1f6f939c 100644 --- a/.github/workflows/uffizzi-preview.yaml +++ b/.github/workflows/uffizzi-preview.yaml @@ -13,10 +13,10 @@ jobs: runs-on: ubuntu-latest if: ${{ github.event.workflow_run.conclusion == 'success' }} outputs: - manifests-cache-key: ${{ env.MANIFESTS_FILE_HASH }} - git-ref: ${{ env.GIT_REF }} - pr-number: ${{ env.PR_NUMBER }} - action: ${{ env.ACTION }} + manifests-cache-key: ${{ steps.hash.outputs.MANIFESTS_FILE_HASH }} + git-ref: ${{ steps.event.outputs.GIT_REF }} + pr-number: ${{ steps.event.outputs.PR_NUMBER }} + action: ${{ steps.event.outputs.ACTION }} steps: - name: Harden Runner uses: step-security/harden-runner@eb238b55efaa70779f274895e782ed17c84f2895 # v2.6.1 @@ -52,34 +52,35 @@ jobs: run: unzip preview-spec.zip event.json - name: Read Event into ENV + id: event run: | - echo PR_NUMBER=$(jq '.number | tonumber' < event.json) >> $GITHUB_ENV - echo ACTION=$(jq --raw-output '.action | tostring | [scan("\\w+")][0]' < event.json) >> $GITHUB_ENV - echo GIT_REF=$(jq --raw-output '.pull_request.head.sha | tostring | [scan("\\w+")][0]' < event.json) >> $GITHUB_ENV + echo PR_NUMBER=$(jq '.number | tonumber' < event.json) >> $GITHUB_OUTPUT + echo ACTION=$(jq --raw-output '.action | tostring | [scan("\\w+")][0]' < event.json) >> $GITHUB_OUTPUT + echo GIT_REF=$(jq --raw-output '.pull_request.head.sha | tostring | [scan("\\w+")][0]' < event.json) >> $GITHUB_OUTPUT - name: Hash Rendered Manifests File id: hash # If the previous workflow was triggered by a PR close event, we will not have a manifests file artifact. - if: ${{ env.ACTION != 'closed' }} + if: ${{ steps.event.outputs.ACTION != 'closed' }} run: | unzip preview-spec.zip manifests.rendered.yml ls - echo "MANIFESTS_FILE_HASH=$(md5sum manifests.rendered.yml | awk '{ print $1 }')" >> $GITHUB_ENV + echo "MANIFESTS_FILE_HASH=$(md5sum manifests.rendered.yml | awk '{ print $1 }')" >> $GITHUB_OUTPUT - name: Cache Manifests File - if: ${{ env.ACTION != 'closed' }} + if: ${{ steps.event.outputs.ACTION != 'closed' }} uses: actions/cache@v3.3.2 with: path: manifests.rendered.yml - key: ${{ env.MANIFESTS_FILE_HASH }} + key: ${{ steps.hash.outputs.MANIFESTS_FILE_HASH }} - name: DEBUG - Print Job Outputs if: ${{ runner.debug }} run: | - echo "PR number: ${{ env.PR_NUMBER }}" - echo "Git Ref: ${{ env.GIT_REF }}" - echo "Action: ${{ env.ACTION }}" - echo "Manifests file hash: ${{ env.MANIFESTS_FILE_HASH }}" + echo "PR number: ${{ steps.event.outputs.PR_NUMBER }}" + echo "Git Ref: ${{ steps.event.outputs.GIT_REF }}" + echo "Action: ${{ steps.event.outputs.ACTION }}" + echo "Manifests file hash: ${{ steps.hash.outputs.MANIFESTS_FILE_HASH }}" cat event.json deploy-uffizzi-preview: @@ -132,7 +133,6 @@ jobs: - name: Fetch cached Manifests File id: cache - # if: ${{ contains(fromJSON('["create", "update"]'), env.UFFIZZI_ACTION) }} uses: actions/cache@v3 with: path: manifests.rendered.yml From c01835945b63e3e49cb34641c9417048343c121f Mon Sep 17 00:00:00 2001 From: Adam Vollrath Date: Thu, 4 Jan 2024 10:59:41 -0600 Subject: [PATCH 07/35] Limit permissions of GHA token in Uffizzi second stage workflow. Signed-off-by: Adam Vollrath --- .github/workflows/uffizzi-preview.yaml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/uffizzi-preview.yaml b/.github/workflows/uffizzi-preview.yaml index cd1f6f939c..93c42e6c71 100644 --- a/.github/workflows/uffizzi-preview.yaml +++ b/.github/workflows/uffizzi-preview.yaml @@ -12,6 +12,8 @@ jobs: name: Cache Manifests File runs-on: ubuntu-latest if: ${{ github.event.workflow_run.conclusion == 'success' }} + permissions: + actions: read outputs: manifests-cache-key: ${{ steps.hash.outputs.MANIFESTS_FILE_HASH }} git-ref: ${{ steps.event.outputs.GIT_REF }} @@ -84,14 +86,14 @@ jobs: cat event.json deploy-uffizzi-preview: - permissions: - contents: read - pull-requests: write - id-token: write name: Deploy to Uffizzi Virtual Cluster needs: - cache-manifests-file if: ${{ github.event.workflow_run.conclusion == 'success' && needs.cache-manifests-file.outputs.action != 'closed' }} + permissions: + contents: read + pull-requests: write + id-token: write runs-on: ubuntu-latest steps: - name: Checkout From 9f4cb8a8f797981b802aee49a5d0190deab5820f Mon Sep 17 00:00:00 2001 From: Adam Vollrath Date: Thu, 4 Jan 2024 11:18:53 -0600 Subject: [PATCH 08/35] Enforce harden-runner policy. Signed-off-by: Adam Vollrath --- .github/workflows/uffizzi-preview.yaml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/uffizzi-preview.yaml b/.github/workflows/uffizzi-preview.yaml index 93c42e6c71..c1659097a7 100644 --- a/.github/workflows/uffizzi-preview.yaml +++ b/.github/workflows/uffizzi-preview.yaml @@ -13,7 +13,9 @@ jobs: runs-on: ubuntu-latest if: ${{ github.event.workflow_run.conclusion == 'success' }} permissions: - actions: read + # "If you specify the access for any of these scopes, all of those that are not specified are set to none." + # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions + actions: read # Access cache outputs: manifests-cache-key: ${{ steps.hash.outputs.MANIFESTS_FILE_HASH }} git-ref: ${{ steps.event.outputs.GIT_REF }} @@ -23,7 +25,10 @@ jobs: - name: Harden Runner uses: step-security/harden-runner@eb238b55efaa70779f274895e782ed17c84f2895 # v2.6.1 with: - egress-policy: audit + disable-sudo: true + egress-policy: block + allowed-endpoints: > + api.github.com:443 - name: 'Download artifacts' # Fetch output (zip archive) from the workflow run that triggered this workflow. From eb81f4228ade0a2b39c5fe458b7a7284e80be63f Mon Sep 17 00:00:00 2001 From: David Festal Date: Wed, 10 Jan 2024 18:05:28 +0100 Subject: [PATCH 09/35] Rename `backend-plugin-manager` package and make it public Signed-off-by: David Festal --- .changeset/clean-mails-bathe.md | 5 ++ .../.eslintrc.js | 0 .../CHANGELOG.md | 2 +- .../README.md | 18 ++--- .../api-report.md | 2 +- .../catalog-info.yaml | 10 +++ .../config.d.ts | 0 .../package.json | 7 +- .../src/__testUtils__/testUtils.ts | 0 .../src/index.ts | 0 .../src/loader/CommonJSModuleLoader.ts | 0 .../src/loader/index.ts | 0 .../src/loader/types.ts | 0 .../src/manager/index.ts | 0 .../src/manager/plugin-manager.test.ts | 2 +- .../src/manager/plugin-manager.ts | 0 .../src/manager/types.ts | 0 .../src/scanner/index.ts | 0 .../scanner/plugin-scanner-watcher.test.ts | 0 .../src/scanner/plugin-scanner.test.ts | 0 .../src/scanner/plugin-scanner.ts | 0 .../src/scanner/types.ts | 0 .../backend-plugin-manager/catalog-info.yaml | 10 --- yarn.lock | 68 +++++++++---------- 24 files changed, 62 insertions(+), 62 deletions(-) create mode 100644 .changeset/clean-mails-bathe.md rename packages/{backend-plugin-manager => backend-dynamic-feature-service}/.eslintrc.js (100%) rename packages/{backend-plugin-manager => backend-dynamic-feature-service}/CHANGELOG.md (99%) rename packages/{backend-plugin-manager => backend-dynamic-feature-service}/README.md (64%) rename packages/{backend-plugin-manager => backend-dynamic-feature-service}/api-report.md (98%) create mode 100644 packages/backend-dynamic-feature-service/catalog-info.yaml rename packages/{backend-plugin-manager => backend-dynamic-feature-service}/config.d.ts (100%) rename packages/{backend-plugin-manager => backend-dynamic-feature-service}/package.json (92%) rename packages/{backend-plugin-manager => backend-dynamic-feature-service}/src/__testUtils__/testUtils.ts (100%) rename packages/{backend-plugin-manager => backend-dynamic-feature-service}/src/index.ts (100%) rename packages/{backend-plugin-manager => backend-dynamic-feature-service}/src/loader/CommonJSModuleLoader.ts (100%) rename packages/{backend-plugin-manager => backend-dynamic-feature-service}/src/loader/index.ts (100%) rename packages/{backend-plugin-manager => backend-dynamic-feature-service}/src/loader/types.ts (100%) rename packages/{backend-plugin-manager => backend-dynamic-feature-service}/src/manager/index.ts (100%) rename packages/{backend-plugin-manager => backend-dynamic-feature-service}/src/manager/plugin-manager.test.ts (99%) rename packages/{backend-plugin-manager => backend-dynamic-feature-service}/src/manager/plugin-manager.ts (100%) rename packages/{backend-plugin-manager => backend-dynamic-feature-service}/src/manager/types.ts (100%) rename packages/{backend-plugin-manager => backend-dynamic-feature-service}/src/scanner/index.ts (100%) rename packages/{backend-plugin-manager => backend-dynamic-feature-service}/src/scanner/plugin-scanner-watcher.test.ts (100%) rename packages/{backend-plugin-manager => backend-dynamic-feature-service}/src/scanner/plugin-scanner.test.ts (100%) rename packages/{backend-plugin-manager => backend-dynamic-feature-service}/src/scanner/plugin-scanner.ts (100%) rename packages/{backend-plugin-manager => backend-dynamic-feature-service}/src/scanner/types.ts (100%) delete mode 100644 packages/backend-plugin-manager/catalog-info.yaml diff --git a/.changeset/clean-mails-bathe.md b/.changeset/clean-mails-bathe.md new file mode 100644 index 0000000000..c9afc34f90 --- /dev/null +++ b/.changeset/clean-mails-bathe.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-dynamic-feature-service': minor +--- + +New `backend-dynamic-feature-service` package, for the discovery of dynamic frontend and backend plugins (and modules) and the loading of the backend ones inside the backend application. diff --git a/packages/backend-plugin-manager/.eslintrc.js b/packages/backend-dynamic-feature-service/.eslintrc.js similarity index 100% rename from packages/backend-plugin-manager/.eslintrc.js rename to packages/backend-dynamic-feature-service/.eslintrc.js diff --git a/packages/backend-plugin-manager/CHANGELOG.md b/packages/backend-dynamic-feature-service/CHANGELOG.md similarity index 99% rename from packages/backend-plugin-manager/CHANGELOG.md rename to packages/backend-dynamic-feature-service/CHANGELOG.md index 109c2e8409..07b118e771 100644 --- a/packages/backend-plugin-manager/CHANGELOG.md +++ b/packages/backend-dynamic-feature-service/CHANGELOG.md @@ -1,4 +1,4 @@ -# @backstage/backend-plugin-manager +# @backstage/backend-dynamic-feature-service ## 0.0.5-next.2 diff --git a/packages/backend-plugin-manager/README.md b/packages/backend-dynamic-feature-service/README.md similarity index 64% rename from packages/backend-plugin-manager/README.md rename to packages/backend-dynamic-feature-service/README.md index c9fd666fb3..211148ee10 100644 --- a/packages/backend-plugin-manager/README.md +++ b/packages/backend-dynamic-feature-service/README.md @@ -1,10 +1,6 @@ -# @backstage/backend-plugin-manager +# @backstage/backend-dynamic-feature-service -This package adds experimental support for **dynamic backend plugins**, according to the content of the proposal in [RFC 18390](https://github.com/backstage/backstage/issues/18390) - -## Status - -**This package is EXPERIMENTAL, and is subject to change according to the discussions and conclusions that happen around the RFC mentioned above.** +This package adds experimental support for **dynamic backend features (plugins and modules)**, according to the content of the proposal in [RFC 18390](https://github.com/backstage/backstage/issues/18390) ## Testing the backend dynamic plugins feature @@ -12,9 +8,9 @@ In order to test the dynamic backend plugins feature provided by this package, e ## How it works -The backend plugin manager is a service that scans a configured root directory (`dynamicPlugins.rootDirectory` in the app config) for dynamic plugin packages, and loads them dynamically. +The dynamic plugin manager is a service that scans a configured root directory (`dynamicPlugins.rootDirectory` in the app config) for dynamic plugin packages, and loads them dynamically. -In the `backend-next` application, it can be enabled by adding the `backend-plugin-manager` as a dependency in the `package.json` and the following lines in the `src/index.ts` file: +In the `backend-next` application, it can be enabled by adding the `backend-dynamic-feature-service` as a dependency in the `package.json` and the following lines in the `src/index.ts` file: ```ts const backend = createBackend(); @@ -36,9 +32,9 @@ Points 2 and 3 can be done by the `export-dynamic-plugin` CLI command used to pe ### About the `export-dynamic-plugin` command -The `export-dynamic-plugin` CLI command, used the dynamic plugin examples provided in the [showcase repository](https://github.com/janus-idp/dynamic-backend-plugins-showcase), is part of a `@backstage/cli` fork (`@dfatwork-pkgs/backstage-cli@0.22.9-next.6`), and can be used to help packaging the dynamic plugins according to the constraints mentioned above, in order to allow straightforward testing of the dynamic plugins feature. +The `export-dynamic-plugin` CLI command, used the dynamic plugin examples provided in the [showcase repository](https://github.com/janus-idp/dynamic-backend-plugins-showcase), is part of the `@janus-idp/cli` package, and can be used to help packaging the dynamic plugins according to the constraints mentioned above, in order to allow straightforward testing of the dynamic plugins feature. -However the `backend-plugin-manager` experimental package does **not** depend on the use of this additional CLI command, and in future steps of this backend dynamic plugin work, the use of such a dedicated command might not even be necessary. +However the `backend-dynamic-feature-service` experimental package does **not** depend on the use of this additional CLI command, and in future steps of this backend dynamic plugin work, the use of such a dedicated command might not even be necessary. ### About the support of the legacy backend system @@ -49,4 +45,4 @@ This is why the API related to the old backend is already marked as deprecated. ### Future work -The current implementation of the backend plugin manager is a first step towards the final implementation of the dynamic backend plugins feature, which will be completed / simplified in future steps, as the status of the backstage codebase allows it. +The current implementation of the dynamic plugin manager is a first step towards the final implementation of the dynamic features loading, which will be completed / simplified in future steps, as the status of the backstage codebase allows it. diff --git a/packages/backend-plugin-manager/api-report.md b/packages/backend-dynamic-feature-service/api-report.md similarity index 98% rename from packages/backend-plugin-manager/api-report.md rename to packages/backend-dynamic-feature-service/api-report.md index dc5973a3ab..caf2cf01ec 100644 --- a/packages/backend-plugin-manager/api-report.md +++ b/packages/backend-dynamic-feature-service/api-report.md @@ -1,4 +1,4 @@ -## API Report File for "@backstage/backend-plugin-manager" +## API Report File for "@backstage/backend-dynamic-feature-service" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). diff --git a/packages/backend-dynamic-feature-service/catalog-info.yaml b/packages/backend-dynamic-feature-service/catalog-info.yaml new file mode 100644 index 0000000000..1269a5c406 --- /dev/null +++ b/packages/backend-dynamic-feature-service/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-backend-dynamic-feature-service + title: '@backstage/backend-dynamic-feature-service' + description: Backstage backend service to handle dynamic features +spec: + lifecycle: experimental + type: backstage-node-library + owner: maintainers diff --git a/packages/backend-plugin-manager/config.d.ts b/packages/backend-dynamic-feature-service/config.d.ts similarity index 100% rename from packages/backend-plugin-manager/config.d.ts rename to packages/backend-dynamic-feature-service/config.d.ts diff --git a/packages/backend-plugin-manager/package.json b/packages/backend-dynamic-feature-service/package.json similarity index 92% rename from packages/backend-plugin-manager/package.json rename to packages/backend-dynamic-feature-service/package.json index 45344f4c94..cd95d6a5ae 100644 --- a/packages/backend-plugin-manager/package.json +++ b/packages/backend-dynamic-feature-service/package.json @@ -1,8 +1,7 @@ { - "name": "@backstage/backend-plugin-manager", - "description": "Backstage plugin management backend", - "version": "0.0.5-next.2", - "private": true, + "name": "@backstage/backend-dynamic-feature-service", + "description": "Backstage dynamic feature service", + "version": "0.0.0", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend-plugin-manager/src/__testUtils__/testUtils.ts b/packages/backend-dynamic-feature-service/src/__testUtils__/testUtils.ts similarity index 100% rename from packages/backend-plugin-manager/src/__testUtils__/testUtils.ts rename to packages/backend-dynamic-feature-service/src/__testUtils__/testUtils.ts diff --git a/packages/backend-plugin-manager/src/index.ts b/packages/backend-dynamic-feature-service/src/index.ts similarity index 100% rename from packages/backend-plugin-manager/src/index.ts rename to packages/backend-dynamic-feature-service/src/index.ts diff --git a/packages/backend-plugin-manager/src/loader/CommonJSModuleLoader.ts b/packages/backend-dynamic-feature-service/src/loader/CommonJSModuleLoader.ts similarity index 100% rename from packages/backend-plugin-manager/src/loader/CommonJSModuleLoader.ts rename to packages/backend-dynamic-feature-service/src/loader/CommonJSModuleLoader.ts diff --git a/packages/backend-plugin-manager/src/loader/index.ts b/packages/backend-dynamic-feature-service/src/loader/index.ts similarity index 100% rename from packages/backend-plugin-manager/src/loader/index.ts rename to packages/backend-dynamic-feature-service/src/loader/index.ts diff --git a/packages/backend-plugin-manager/src/loader/types.ts b/packages/backend-dynamic-feature-service/src/loader/types.ts similarity index 100% rename from packages/backend-plugin-manager/src/loader/types.ts rename to packages/backend-dynamic-feature-service/src/loader/types.ts diff --git a/packages/backend-plugin-manager/src/manager/index.ts b/packages/backend-dynamic-feature-service/src/manager/index.ts similarity index 100% rename from packages/backend-plugin-manager/src/manager/index.ts rename to packages/backend-dynamic-feature-service/src/manager/index.ts diff --git a/packages/backend-plugin-manager/src/manager/plugin-manager.test.ts b/packages/backend-dynamic-feature-service/src/manager/plugin-manager.test.ts similarity index 99% rename from packages/backend-plugin-manager/src/manager/plugin-manager.test.ts rename to packages/backend-dynamic-feature-service/src/manager/plugin-manager.test.ts index 405fd629f2..487729687f 100644 --- a/packages/backend-plugin-manager/src/manager/plugin-manager.test.ts +++ b/packages/backend-dynamic-feature-service/src/manager/plugin-manager.test.ts @@ -47,7 +47,7 @@ import { PluginScanner } from '../scanner/plugin-scanner'; import { findPaths } from '@backstage/cli-common'; import { createMockDirectory } from '@backstage/backend-test-utils'; -describe('backend-plugin-manager', () => { +describe('backend-dynamic-feature-service', () => { const mockDir = createMockDirectory(); describe('loadPlugins', () => { diff --git a/packages/backend-plugin-manager/src/manager/plugin-manager.ts b/packages/backend-dynamic-feature-service/src/manager/plugin-manager.ts similarity index 100% rename from packages/backend-plugin-manager/src/manager/plugin-manager.ts rename to packages/backend-dynamic-feature-service/src/manager/plugin-manager.ts diff --git a/packages/backend-plugin-manager/src/manager/types.ts b/packages/backend-dynamic-feature-service/src/manager/types.ts similarity index 100% rename from packages/backend-plugin-manager/src/manager/types.ts rename to packages/backend-dynamic-feature-service/src/manager/types.ts diff --git a/packages/backend-plugin-manager/src/scanner/index.ts b/packages/backend-dynamic-feature-service/src/scanner/index.ts similarity index 100% rename from packages/backend-plugin-manager/src/scanner/index.ts rename to packages/backend-dynamic-feature-service/src/scanner/index.ts diff --git a/packages/backend-plugin-manager/src/scanner/plugin-scanner-watcher.test.ts b/packages/backend-dynamic-feature-service/src/scanner/plugin-scanner-watcher.test.ts similarity index 100% rename from packages/backend-plugin-manager/src/scanner/plugin-scanner-watcher.test.ts rename to packages/backend-dynamic-feature-service/src/scanner/plugin-scanner-watcher.test.ts diff --git a/packages/backend-plugin-manager/src/scanner/plugin-scanner.test.ts b/packages/backend-dynamic-feature-service/src/scanner/plugin-scanner.test.ts similarity index 100% rename from packages/backend-plugin-manager/src/scanner/plugin-scanner.test.ts rename to packages/backend-dynamic-feature-service/src/scanner/plugin-scanner.test.ts diff --git a/packages/backend-plugin-manager/src/scanner/plugin-scanner.ts b/packages/backend-dynamic-feature-service/src/scanner/plugin-scanner.ts similarity index 100% rename from packages/backend-plugin-manager/src/scanner/plugin-scanner.ts rename to packages/backend-dynamic-feature-service/src/scanner/plugin-scanner.ts diff --git a/packages/backend-plugin-manager/src/scanner/types.ts b/packages/backend-dynamic-feature-service/src/scanner/types.ts similarity index 100% rename from packages/backend-plugin-manager/src/scanner/types.ts rename to packages/backend-dynamic-feature-service/src/scanner/types.ts diff --git a/packages/backend-plugin-manager/catalog-info.yaml b/packages/backend-plugin-manager/catalog-info.yaml deleted file mode 100644 index bc1845fe49..0000000000 --- a/packages/backend-plugin-manager/catalog-info.yaml +++ /dev/null @@ -1,10 +0,0 @@ -apiVersion: backstage.io/v1alpha1 -kind: Component -metadata: - name: backstage-backend-plugin-manager - title: '@backstage/backend-plugin-manager' - description: Backstage plugin management backend -spec: - lifecycle: experimental - type: backstage-node-library - owner: maintainers diff --git a/yarn.lock b/yarn.lock index 03478d5b50..c77da56725 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3344,6 +3344,40 @@ __metadata: languageName: unknown linkType: soft +"@backstage/backend-dynamic-feature-service@workspace:packages/backend-dynamic-feature-service": + version: 0.0.0-use.local + resolution: "@backstage/backend-dynamic-feature-service@workspace:packages/backend-dynamic-feature-service" + dependencies: + "@backstage/backend-app-api": "workspace:^" + "@backstage/backend-common": "workspace:^" + "@backstage/backend-plugin-api": "workspace:^" + "@backstage/backend-tasks": "workspace:^" + "@backstage/backend-test-utils": "workspace:^" + "@backstage/cli": "workspace:^" + "@backstage/cli-common": "workspace:^" + "@backstage/cli-node": "workspace:^" + "@backstage/config": "workspace:^" + "@backstage/config-loader": "workspace:^" + "@backstage/errors": "workspace:^" + "@backstage/plugin-auth-node": "workspace:^" + "@backstage/plugin-catalog-backend": "workspace:^" + "@backstage/plugin-events-backend": "workspace:^" + "@backstage/plugin-events-node": "workspace:^" + "@backstage/plugin-permission-common": "workspace:^" + "@backstage/plugin-permission-node": "workspace:^" + "@backstage/plugin-scaffolder-node": "workspace:^" + "@backstage/plugin-search-backend-node": "workspace:^" + "@backstage/plugin-search-common": "workspace:^" + "@backstage/types": "workspace:^" + "@types/express": ^4.17.6 + chokidar: ^3.5.3 + express: ^4.17.1 + lodash: ^4.17.21 + wait-for-expect: ^3.0.2 + winston: ^3.2.1 + languageName: unknown + linkType: soft + "@backstage/backend-openapi-utils@workspace:^, @backstage/backend-openapi-utils@workspace:packages/backend-openapi-utils": version: 0.0.0-use.local resolution: "@backstage/backend-openapi-utils@workspace:packages/backend-openapi-utils" @@ -3380,40 +3414,6 @@ __metadata: languageName: unknown linkType: soft -"@backstage/backend-plugin-manager@workspace:packages/backend-plugin-manager": - version: 0.0.0-use.local - resolution: "@backstage/backend-plugin-manager@workspace:packages/backend-plugin-manager" - dependencies: - "@backstage/backend-app-api": "workspace:^" - "@backstage/backend-common": "workspace:^" - "@backstage/backend-plugin-api": "workspace:^" - "@backstage/backend-tasks": "workspace:^" - "@backstage/backend-test-utils": "workspace:^" - "@backstage/cli": "workspace:^" - "@backstage/cli-common": "workspace:^" - "@backstage/cli-node": "workspace:^" - "@backstage/config": "workspace:^" - "@backstage/config-loader": "workspace:^" - "@backstage/errors": "workspace:^" - "@backstage/plugin-auth-node": "workspace:^" - "@backstage/plugin-catalog-backend": "workspace:^" - "@backstage/plugin-events-backend": "workspace:^" - "@backstage/plugin-events-node": "workspace:^" - "@backstage/plugin-permission-common": "workspace:^" - "@backstage/plugin-permission-node": "workspace:^" - "@backstage/plugin-scaffolder-node": "workspace:^" - "@backstage/plugin-search-backend-node": "workspace:^" - "@backstage/plugin-search-common": "workspace:^" - "@backstage/types": "workspace:^" - "@types/express": ^4.17.6 - chokidar: ^3.5.3 - express: ^4.17.1 - lodash: ^4.17.21 - wait-for-expect: ^3.0.2 - winston: ^3.2.1 - languageName: unknown - linkType: soft - "@backstage/backend-tasks@workspace:^, @backstage/backend-tasks@workspace:packages/backend-tasks": version: 0.0.0-use.local resolution: "@backstage/backend-tasks@workspace:packages/backend-tasks" From 928efbc54a253d1814c248117051ac27c486daaf Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Fri, 22 Dec 2023 13:15:34 -0600 Subject: [PATCH 10/35] Updated auth module default export Signed-off-by: Andre Wanlin --- .changeset/brave-ghosts-pay.md | 5 +++++ .changeset/heavy-moose-reflect.md | 5 +++++ plugins/auth-backend-module-microsoft-provider/src/index.ts | 2 +- plugins/auth-backend-module-pinniped-provider/src/index.ts | 2 +- 4 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 .changeset/brave-ghosts-pay.md create mode 100644 .changeset/heavy-moose-reflect.md diff --git a/.changeset/brave-ghosts-pay.md b/.changeset/brave-ghosts-pay.md new file mode 100644 index 0000000000..84d255ea0d --- /dev/null +++ b/.changeset/brave-ghosts-pay.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend-module-pinniped-provider': minor +--- + +**BREAKING** The `authModulePinnipedProvider` is now the default export and should be used like this in your backend: `backend.add(import('@backstage/plugin-auth-backend-module-pinniped-provider'));` diff --git a/.changeset/heavy-moose-reflect.md b/.changeset/heavy-moose-reflect.md new file mode 100644 index 0000000000..639e3c8bf4 --- /dev/null +++ b/.changeset/heavy-moose-reflect.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend-module-microsoft-provider': minor +--- + +**BREAKING** The `authModuleMicrosoftProvider` is now the default export and should be used like this in your backend: `backend.add(import('@backstage/plugin-auth-backend-module-microsoft-provider'));` diff --git a/plugins/auth-backend-module-microsoft-provider/src/index.ts b/plugins/auth-backend-module-microsoft-provider/src/index.ts index ae4fbf5926..735b6716c5 100644 --- a/plugins/auth-backend-module-microsoft-provider/src/index.ts +++ b/plugins/auth-backend-module-microsoft-provider/src/index.ts @@ -21,5 +21,5 @@ */ export { microsoftAuthenticator } from './authenticator'; -export { authModuleMicrosoftProvider } from './module'; +export { authModuleMicrosoftProvider as default } from './module'; export { microsoftSignInResolvers } from './resolvers'; diff --git a/plugins/auth-backend-module-pinniped-provider/src/index.ts b/plugins/auth-backend-module-pinniped-provider/src/index.ts index df4cd58603..bc2bea4c7e 100644 --- a/plugins/auth-backend-module-pinniped-provider/src/index.ts +++ b/plugins/auth-backend-module-pinniped-provider/src/index.ts @@ -21,4 +21,4 @@ */ export { pinnipedAuthenticator, PinnipedStrategyCache } from './authenticator'; -export { authModulePinnipedProvider } from './module'; +export { authModulePinnipedProvider as default } from './module'; From 25b2d7223813ee23f66528037b4fc1c56c0ff31d Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Fri, 22 Dec 2023 14:17:09 -0600 Subject: [PATCH 11/35] Updated so a breaking change is not needed Signed-off-by: Andre Wanlin --- .changeset/brave-ghosts-pay.md | 4 ++-- .changeset/heavy-moose-reflect.md | 4 ++-- .../api-report.md | 4 +++- .../src/deprecated.ts | 23 +++++++++++++++++++ .../src/index.ts | 1 + .../api-report.md | 4 +++- .../src/deprecated.ts | 23 +++++++++++++++++++ .../src/index.ts | 1 + 8 files changed, 58 insertions(+), 6 deletions(-) create mode 100644 plugins/auth-backend-module-microsoft-provider/src/deprecated.ts create mode 100644 plugins/auth-backend-module-pinniped-provider/src/deprecated.ts diff --git a/.changeset/brave-ghosts-pay.md b/.changeset/brave-ghosts-pay.md index 84d255ea0d..d8981fa53b 100644 --- a/.changeset/brave-ghosts-pay.md +++ b/.changeset/brave-ghosts-pay.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-auth-backend-module-pinniped-provider': minor +'@backstage/plugin-auth-backend-module-pinniped-provider': patch --- -**BREAKING** The `authModulePinnipedProvider` is now the default export and should be used like this in your backend: `backend.add(import('@backstage/plugin-auth-backend-module-pinniped-provider'));` +Deprecated the `authModulePinnipedProvider` export. A default export is now available and should be used like this in your backend: `backend.add(import('@backstage/plugin-auth-backend-module-pinniped-provider'));` diff --git a/.changeset/heavy-moose-reflect.md b/.changeset/heavy-moose-reflect.md index 639e3c8bf4..011d3fc023 100644 --- a/.changeset/heavy-moose-reflect.md +++ b/.changeset/heavy-moose-reflect.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-auth-backend-module-microsoft-provider': minor +'@backstage/plugin-auth-backend-module-microsoft-provider': patch --- -**BREAKING** The `authModuleMicrosoftProvider` is now the default export and should be used like this in your backend: `backend.add(import('@backstage/plugin-auth-backend-module-microsoft-provider'));` +Deprecated the `authModuleMicrosoftProvider` export. A default export is now available and should be used like this in your backend: `backend.add(import('@backstage/plugin-auth-backend-module-microsoft-provider'));` diff --git a/plugins/auth-backend-module-microsoft-provider/api-report.md b/plugins/auth-backend-module-microsoft-provider/api-report.md index 21bf77d545..c54b986864 100644 --- a/plugins/auth-backend-module-microsoft-provider/api-report.md +++ b/plugins/auth-backend-module-microsoft-provider/api-report.md @@ -11,7 +11,9 @@ import { PassportProfile } from '@backstage/plugin-auth-node'; import { SignInResolverFactory } from '@backstage/plugin-auth-node'; // @public (undocumented) -export const authModuleMicrosoftProvider: () => BackendFeature; +const authModuleMicrosoftProvider: () => BackendFeature; +export { authModuleMicrosoftProvider }; +export default authModuleMicrosoftProvider; // @public (undocumented) export const microsoftAuthenticator: OAuthAuthenticator< diff --git a/plugins/auth-backend-module-microsoft-provider/src/deprecated.ts b/plugins/auth-backend-module-microsoft-provider/src/deprecated.ts new file mode 100644 index 0000000000..c961e1c942 --- /dev/null +++ b/plugins/auth-backend-module-microsoft-provider/src/deprecated.ts @@ -0,0 +1,23 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { authModuleMicrosoftProvider as deprecatedAuthModuleMicrosoftProvider } from './module'; + +/** + * @public + * @deprecated Use default import instead + */ +export { deprecatedAuthModuleMicrosoftProvider as authModuleMicrosoftProvider }; diff --git a/plugins/auth-backend-module-microsoft-provider/src/index.ts b/plugins/auth-backend-module-microsoft-provider/src/index.ts index 735b6716c5..45b7b719cd 100644 --- a/plugins/auth-backend-module-microsoft-provider/src/index.ts +++ b/plugins/auth-backend-module-microsoft-provider/src/index.ts @@ -23,3 +23,4 @@ export { microsoftAuthenticator } from './authenticator'; export { authModuleMicrosoftProvider as default } from './module'; export { microsoftSignInResolvers } from './resolvers'; +export * from './deprecated'; diff --git a/plugins/auth-backend-module-pinniped-provider/api-report.md b/plugins/auth-backend-module-pinniped-provider/api-report.md index 430099713e..3a8b6b5bf6 100644 --- a/plugins/auth-backend-module-pinniped-provider/api-report.md +++ b/plugins/auth-backend-module-pinniped-provider/api-report.md @@ -11,7 +11,9 @@ import { Strategy } from 'openid-client'; import { TokenSet } from 'openid-client'; // @public (undocumented) -export const authModulePinnipedProvider: () => BackendFeature; +const authModulePinnipedProvider: () => BackendFeature; +export { authModulePinnipedProvider }; +export default authModulePinnipedProvider; // @public (undocumented) export const pinnipedAuthenticator: OAuthAuthenticator< diff --git a/plugins/auth-backend-module-pinniped-provider/src/deprecated.ts b/plugins/auth-backend-module-pinniped-provider/src/deprecated.ts new file mode 100644 index 0000000000..f11cdc13e6 --- /dev/null +++ b/plugins/auth-backend-module-pinniped-provider/src/deprecated.ts @@ -0,0 +1,23 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { authModulePinnipedProvider as deprecatedAuthModulePinnipedProvider } from './module'; + +/** + * @public + * @deprecated Use default import instead + */ +export { deprecatedAuthModulePinnipedProvider as authModulePinnipedProvider }; diff --git a/plugins/auth-backend-module-pinniped-provider/src/index.ts b/plugins/auth-backend-module-pinniped-provider/src/index.ts index bc2bea4c7e..f4b789644d 100644 --- a/plugins/auth-backend-module-pinniped-provider/src/index.ts +++ b/plugins/auth-backend-module-pinniped-provider/src/index.ts @@ -22,3 +22,4 @@ export { pinnipedAuthenticator, PinnipedStrategyCache } from './authenticator'; export { authModulePinnipedProvider as default } from './module'; +export * from './deprecated'; From e508999a639d7f10d6919e33941e84037f7c15ab Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Wed, 10 Jan 2024 16:22:33 -0600 Subject: [PATCH 12/35] Adjustment based on feedback Signed-off-by: Andre Wanlin --- .../auth-backend-module-microsoft-provider/api-report.md | 8 +++++--- .../src/deprecated.ts | 3 ++- .../auth-backend-module-pinniped-provider/api-report.md | 8 +++++--- .../src/deprecated.ts | 2 +- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/plugins/auth-backend-module-microsoft-provider/api-report.md b/plugins/auth-backend-module-microsoft-provider/api-report.md index c54b986864..33600c10d8 100644 --- a/plugins/auth-backend-module-microsoft-provider/api-report.md +++ b/plugins/auth-backend-module-microsoft-provider/api-report.md @@ -10,10 +10,12 @@ import { PassportOAuthAuthenticatorHelper } from '@backstage/plugin-auth-node'; import { PassportProfile } from '@backstage/plugin-auth-node'; import { SignInResolverFactory } from '@backstage/plugin-auth-node'; +// @public @deprecated (undocumented) +export const authModuleMicrosoftProvider: () => BackendFeature; + // @public (undocumented) -const authModuleMicrosoftProvider: () => BackendFeature; -export { authModuleMicrosoftProvider }; -export default authModuleMicrosoftProvider; +const authModuleMicrosoftProvider_2: () => BackendFeature; +export default authModuleMicrosoftProvider_2; // @public (undocumented) export const microsoftAuthenticator: OAuthAuthenticator< diff --git a/plugins/auth-backend-module-microsoft-provider/src/deprecated.ts b/plugins/auth-backend-module-microsoft-provider/src/deprecated.ts index c961e1c942..3c671b6c25 100644 --- a/plugins/auth-backend-module-microsoft-provider/src/deprecated.ts +++ b/plugins/auth-backend-module-microsoft-provider/src/deprecated.ts @@ -20,4 +20,5 @@ import { authModuleMicrosoftProvider as deprecatedAuthModuleMicrosoftProvider } * @public * @deprecated Use default import instead */ -export { deprecatedAuthModuleMicrosoftProvider as authModuleMicrosoftProvider }; +export const authModuleMicrosoftProvider = + deprecatedAuthModuleMicrosoftProvider; diff --git a/plugins/auth-backend-module-pinniped-provider/api-report.md b/plugins/auth-backend-module-pinniped-provider/api-report.md index 3a8b6b5bf6..182ea8ba21 100644 --- a/plugins/auth-backend-module-pinniped-provider/api-report.md +++ b/plugins/auth-backend-module-pinniped-provider/api-report.md @@ -10,10 +10,12 @@ import { OAuthAuthenticator } from '@backstage/plugin-auth-node'; import { Strategy } from 'openid-client'; import { TokenSet } from 'openid-client'; +// @public @deprecated (undocumented) +export const authModulePinnipedProvider: () => BackendFeature; + // @public (undocumented) -const authModulePinnipedProvider: () => BackendFeature; -export { authModulePinnipedProvider }; -export default authModulePinnipedProvider; +const authModulePinnipedProvider_2: () => BackendFeature; +export default authModulePinnipedProvider_2; // @public (undocumented) export const pinnipedAuthenticator: OAuthAuthenticator< diff --git a/plugins/auth-backend-module-pinniped-provider/src/deprecated.ts b/plugins/auth-backend-module-pinniped-provider/src/deprecated.ts index f11cdc13e6..0dd2ed36bd 100644 --- a/plugins/auth-backend-module-pinniped-provider/src/deprecated.ts +++ b/plugins/auth-backend-module-pinniped-provider/src/deprecated.ts @@ -20,4 +20,4 @@ import { authModulePinnipedProvider as deprecatedAuthModulePinnipedProvider } fr * @public * @deprecated Use default import instead */ -export { deprecatedAuthModulePinnipedProvider as authModulePinnipedProvider }; +export const authModulePinnipedProvider = deprecatedAuthModulePinnipedProvider; From 765af3b39791f2aff77e7a3b4bb60b53635adf1b Mon Sep 17 00:00:00 2001 From: Fer Date: Thu, 21 Dec 2023 11:37:13 -0300 Subject: [PATCH 13/35] Add missing imports on example code Signed-off-by: Fer --- plugins/events-backend/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/events-backend/README.md b/plugins/events-backend/README.md index 6fa02a7e6a..2fd980f003 100644 --- a/plugins/events-backend/README.md +++ b/plugins/events-backend/README.md @@ -42,6 +42,7 @@ Add the following to `makeCreateEnv` ```diff // packages/backend/src/index.ts ++ import { DefaultEventBroker } from '@backstage/plugin-events-backend'; + const eventBroker = new DefaultEventBroker(root.child({ type: 'plugin' })); ``` @@ -49,6 +50,7 @@ Then update plugin environment to include the event broker. ```diff // packages/backend/src/types.ts ++ import { EventBroker } from '@backstage/plugin-events-node'; + eventBroker: EventBroker; ``` From 92ea61543164c2978bdc21ac977026c62bde1528 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 10 Jan 2024 11:25:27 +0100 Subject: [PATCH 14/35] chore: added chantgeset Signed-off-by: blam Signed-off-by: blam --- .changeset/ninety-impalas-knock.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/ninety-impalas-knock.md diff --git a/.changeset/ninety-impalas-knock.md b/.changeset/ninety-impalas-knock.md new file mode 100644 index 0000000000..78bd55dc1c --- /dev/null +++ b/.changeset/ninety-impalas-knock.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-events-backend': patch +--- + +Update `README.md` From 7e7319bcb00d4c6f9ce4ffffd2234727ad1e17c7 Mon Sep 17 00:00:00 2001 From: Daniel Laird Date: Fri, 12 Jan 2024 15:31:56 +0000 Subject: [PATCH 15/35] Ensure teamReviewer list contains unique team names before sending to API Signed-off-by: Daniel Laird --- .../src/actions/githubPullRequest.test.ts | 4 ++-- .../src/actions/githubPullRequest.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts index 23ac4bda45..c5b1038a01 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts @@ -377,7 +377,7 @@ describe('createPublishGithubPullRequestAction', () => { branchName: 'new-app', description: 'This PR is really good', reviewers: ['foobar'], - teamReviewers: ['team-foo'], + teamReviewers: ['team-foo', 'team-foo', 'team-bar'], }; mockDir.setContent({ [workspacePath]: {} }); @@ -401,7 +401,7 @@ describe('createPublishGithubPullRequestAction', () => { repo: 'myrepo', pull_number: 123, reviewers: ['foobar'], - team_reviewers: ['team-foo'], + team_reviewers: ['team-foo', 'team-bar'], }); }); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts index 0763d4a474..2d8dc7ce8d 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts @@ -372,7 +372,7 @@ export const createPublishGithubPullRequestAction = ( repo: pr.repo, pull_number: pr.number, reviewers, - team_reviewers: teamReviewers, + team_reviewers: teamReviewers ? [...new Set(teamReviewers)] : undefined, }); const addedUsers = result.data.requested_reviewers?.join(', ') ?? ''; const addedTeams = result.data.requested_teams?.join(', ') ?? ''; From 547030034df4d263a28fe423b3e7a9db2ddf9b38 Mon Sep 17 00:00:00 2001 From: Daniel Laird Date: Fri, 12 Jan 2024 15:33:29 +0000 Subject: [PATCH 16/35] Add Changeset Signed-off-by: Daniel Laird --- .changeset/fair-spies-rescue.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fair-spies-rescue.md diff --git a/.changeset/fair-spies-rescue.md b/.changeset/fair-spies-rescue.md new file mode 100644 index 0000000000..2b49d4b969 --- /dev/null +++ b/.changeset/fair-spies-rescue.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-github': minor +--- + +Ensure `teamReviewers` list is unique before calling API From c3249d6c11569d086f199db65880f58592cdfb54 Mon Sep 17 00:00:00 2001 From: armandocomellas1 Date: Fri, 12 Jan 2024 11:31:19 -0600 Subject: [PATCH 17/35] Add code to handle URL Reader from GCS with wildcard \* Signed-off-by: armandocomellas1 --- .changeset/thirty-cats-help.md | 5 +++++ .../catalog-backend/src/modules/core/UrlReaderProcessor.ts | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 .changeset/thirty-cats-help.md diff --git a/.changeset/thirty-cats-help.md b/.changeset/thirty-cats-help.md new file mode 100644 index 0000000000..70ae78e3c1 --- /dev/null +++ b/.changeset/thirty-cats-help.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Change script in **UrlReaderProcessor.ts** Replacing the line code 127 'const { pathname: filepath } = new URL(location)' with to handle URL Reader from GCS with wildcard \* diff --git a/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.ts b/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.ts index 5e31dcbd1a..c568f03a81 100644 --- a/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.ts +++ b/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.ts @@ -17,7 +17,6 @@ import { UrlReader } from '@backstage/backend-common'; import { Entity } from '@backstage/catalog-model'; import { assertError } from '@backstage/errors'; -import parseGitUrl from 'git-url-parse'; import limiterFactory from 'p-limit'; import { Logger } from 'winston'; import { LocationSpec } from '@backstage/plugin-catalog-common'; @@ -124,7 +123,8 @@ export class UrlReaderProcessor implements CatalogProcessor { ): Promise<{ response: { data: Buffer; url: string }[]; etag?: string }> { // Does it contain globs? I.e. does it contain asterisks or question marks // (no curly braces for now) - const { filepath } = parseGitUrl(location); + + const { pathname: filepath } = new URL(location); if (filepath?.match(/[*?]/)) { const limiter = limiterFactory(5); const response = await this.options.reader.search(location, { etag }); From 41b25258bff4869bf6394d7cf353764e1cf7d558 Mon Sep 17 00:00:00 2001 From: armandocomellas1 Date: Fri, 12 Jan 2024 11:36:42 -0600 Subject: [PATCH 18/35] Fixing the linter error with the naming the variable inside the changeset thirty-cats-help.md Signed-off-by: armandocomellas1 --- .changeset/thirty-cats-help.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/thirty-cats-help.md b/.changeset/thirty-cats-help.md index 70ae78e3c1..107fb7584b 100644 --- a/.changeset/thirty-cats-help.md +++ b/.changeset/thirty-cats-help.md @@ -2,4 +2,4 @@ '@backstage/plugin-catalog-backend': patch --- -Change script in **UrlReaderProcessor.ts** Replacing the line code 127 'const { pathname: filepath } = new URL(location)' with to handle URL Reader from GCS with wildcard \* +Change script in **UrlReaderProcessor.ts** Replacing the line code 127 **const { pathname: filepath } = new URL(location)** with to handle URL Reader from GCS with wildcard \* From 1de52a9c60d6ba373a8c9f1b2cbb05f198f957fa Mon Sep 17 00:00:00 2001 From: armandocomellas1 Date: Fri, 12 Jan 2024 11:40:45 -0600 Subject: [PATCH 19/35] Fixing the linter error with the naming the variable inside the changeset thirty-cats-help.md Signed-off-by: armandocomellas1 --- .changeset/thirty-cats-help.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/thirty-cats-help.md b/.changeset/thirty-cats-help.md index 107fb7584b..bb6011ca9c 100644 --- a/.changeset/thirty-cats-help.md +++ b/.changeset/thirty-cats-help.md @@ -2,4 +2,4 @@ '@backstage/plugin-catalog-backend': patch --- -Change script in **UrlReaderProcessor.ts** Replacing the line code 127 **const { pathname: filepath } = new URL(location)** with to handle URL Reader from GCS with wildcard \* +Change script in **UrlReaderProcessor.ts** Replacing the line code 127 with method (new URL) to handle URL Reader from GCS with wildcard \* From b6b11677582ffe03c5df05b8e9130d2e2c62d96b Mon Sep 17 00:00:00 2001 From: secustor Date: Fri, 12 Jan 2024 20:05:19 +0100 Subject: [PATCH 20/35] chore: revert exposing of fetchResponse Signed-off-by: secustor --- packages/backend-common/src/reading/GithubUrlReader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend-common/src/reading/GithubUrlReader.ts b/packages/backend-common/src/reading/GithubUrlReader.ts index 6fd24afc08..67f6a975c1 100644 --- a/packages/backend-common/src/reading/GithubUrlReader.ts +++ b/packages/backend-common/src/reading/GithubUrlReader.ts @@ -312,7 +312,7 @@ export class GithubUrlReader implements UrlReader { return repo.default_branch; } - protected async fetchResponse( + private async fetchResponse( url: string | URL, init: RequestInit, ): Promise { From b5f6a1dd00150ff2270464abd434ce6345be7469 Mon Sep 17 00:00:00 2001 From: Jamie Klassen Date: Fri, 12 Jan 2024 17:03:38 -0500 Subject: [PATCH 21/35] document catalog cluster locator method and arrange the cluster locator method subsections in alphabetical order. Signed-off-by: Jamie Klassen --- docs/features/kubernetes/configuration.md | 59 +++++++++++++++++++++-- 1 file changed, 54 insertions(+), 5 deletions(-) diff --git a/docs/features/kubernetes/configuration.md b/docs/features/kubernetes/configuration.md index 494e51320a..2b43d199b6 100644 --- a/docs/features/kubernetes/configuration.md +++ b/docs/features/kubernetes/configuration.md @@ -65,20 +65,63 @@ This is an array used to determine where to retrieve cluster configuration from. Valid cluster locator methods are: - [`catalog`](#catalog) -- [`localKubectlProxy`](#localkubectlproxy) - [`config`](#config) - [`gke`](#gke) +- [`localKubectlProxy`](#localkubectlproxy) - [custom `KubernetesClustersSupplier`](#custom-kubernetesclusterssupplier) #### `catalog` -This cluster locator method will read cluster information from the catalog. +This cluster locator method will gather +[Resources](https://backstage.io/docs/features/software-catalog/system-model#resource) +of +[type](https://backstage.io/docs/features/software-catalog/descriptor-format#spectype-required-4) +`kubernetes-cluster` from the catalog and treat them as clusters for the +purposes of the Kubernetes plugin. In order for a resource to be detected by +this method, it must also have the following +[annotations](https://backstage.io/docs/features/software-catalog/descriptor-format#annotations-optional) +(as seen +[here](https://github.com/backstage/backstage/blob/86baccb2d7d378baed74eaebf017c60b410986e5/plugins/kubernetes-backend/src/cluster-locator/CatalogClusterLocator.ts#L51-L61) +in the code): -#### `localKubectlProxy` +- [`kubernetes.io/api-server`](https://backstage.io/docs/reference/plugin-kubernetes-common.annotation_kubernetes_api_server/), + denoting the base URL of the Kubernetes control plane +- [`kubernetes.io/api-server-certificate-authority`](https://backstage.io/docs/reference/plugin-kubernetes-common.annotation_kubernetes_api_server_ca/), + containing a base64-encoded certificate authority bundle in PEM format; + Backstage will check that the control plane presents a certificate signed by + this authority. +- [`kubernetes.io/auth-provider`](https://backstage.io/docs/reference/plugin-kubernetes-common.annotation_kubernetes_auth_provider/), + denoting the strategy to use to authenticate with the control plane. -This cluster locator method will assume a locally running [`kubectl proxy`](https://kubernetes.io/docs/tasks/extend-kubernetes/http-proxy-access-api/#using-kubectl-to-start-a-proxy-server) process using the default port (8001). +There are many other annotations that can be applied to a cluster resource to +configure the way Backstage communicates, documented +[here](https://backstage.io/docs/reference/plugin-kubernetes-common#variables) +in the API reference. Here is a YAML snippet illustrating an example of a +cluster in the catalog: -NOTE: This cluster locator method is for local development only and should not be used in production. +```yaml +apiVersion: backstage.io/v1alpha1 +kind: Resource +metadata: + name: my-cluster + annotations: + kubernetes.io/api-server: 'https://127.0.0.1:53725' + kubernetes.io/api-server-certificate-authority: # base64-encoded CA + kubernetes.io/auth-provider: 'oidc' + kubernetes.io/oidc-token-provider: 'microsoft' + kubernetes.io/skip-metrics-lookup: 'true' +spec: + type: kubernetes-cluster + owner: user:guest +``` + +Note that it is insecure to store a Kubernetes service account token in an +annotation on a catalog entity (where it could easily be accidentally revealed +by the catalog API) -- therefore there is no annotation corresponding to the +[`serviceAccountToken` field](#clustersserviceaccounttoken-optional) used by +the [`config`](#config) cluster locator. Accordingly, the catalog cluster +locator does not support the [`serviceAccount`](#clustersauthprovider) auth +strategy. #### `config` @@ -388,6 +431,12 @@ Defaults to `false`. Array of key value labels used to filter out clusters which don't have the matching [resource labels](https://cloud.google.com/resource-manager/docs/creating-managing-labels). +#### `localKubectlProxy` + +This cluster locator method will assume a locally running [`kubectl proxy`](https://kubernetes.io/docs/tasks/extend-kubernetes/http-proxy-access-api/#using-kubectl-to-start-a-proxy-server) process using the default port (8001). + +NOTE: This cluster locator method is for local development only and should not be used in production. + #### Custom `KubernetesClustersSupplier` If the configuration-based cluster locators do not work for your use-case, From 4ef6f1bde6c33af863cd35fc97407e4dfecbbb6d Mon Sep 17 00:00:00 2001 From: Jamie Klassen Date: Fri, 12 Jan 2024 18:09:14 -0500 Subject: [PATCH 22/35] mention ingestion procedure synergy Signed-off-by: Jamie Klassen --- docs/features/kubernetes/configuration.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/features/kubernetes/configuration.md b/docs/features/kubernetes/configuration.md index 2b43d199b6..612d57334d 100644 --- a/docs/features/kubernetes/configuration.md +++ b/docs/features/kubernetes/configuration.md @@ -123,6 +123,15 @@ the [`config`](#config) cluster locator. Accordingly, the catalog cluster locator does not support the [`serviceAccount`](#clustersauthprovider) auth strategy. +This method can be quite helpful when used in combination with an ingestion +procedure like the +[`GkeEntityProvider`](https://backstage.io/docs/reference/plugin-catalog-backend-module-gcp.gkeentityprovider/) +(installation documented +[here](https://github.com/backstage/backstage/tree/master/plugins/catalog-backend-module-gcp#installation)) +or the +[`AwsEKSClusterProcessor`](https://backstage.io/docs/reference/plugin-catalog-backend-module-aws.awseksclusterprocessor/) +to automatically update the set of clusters tracked by Backstage. + #### `config` This cluster locator method will read cluster information from your app-config From e8ce05959e30c1e1028b15d429546741c65fa854 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Fri, 12 Jan 2024 13:58:56 +0100 Subject: [PATCH 23/35] add router to the app root MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/silent-horses-raise.md | 2 +- .../src/extensions/AppRoot.tsx | 33 +++- packages/frontend-plugin-api/api-report.md | 33 ++++ packages/frontend-plugin-api/package.json | 32 ++-- .../extensions/createRouterExtension.test.tsx | 167 ++++++++++++++++++ .../src/extensions/createRouterExtension.tsx | 84 +++++++++ .../src/extensions/index.ts | 1 + .../src/app/createExtensionTester.test.tsx | 12 +- .../src/app/createExtensionTester.tsx | 135 +------------- .../src/app/renderInTestApp.test.tsx | 12 +- 10 files changed, 352 insertions(+), 159 deletions(-) create mode 100644 packages/frontend-plugin-api/src/extensions/createRouterExtension.test.tsx create mode 100644 packages/frontend-plugin-api/src/extensions/createRouterExtension.tsx diff --git a/.changeset/silent-horses-raise.md b/.changeset/silent-horses-raise.md index de2671c3da..a03d662b03 100644 --- a/.changeset/silent-horses-raise.md +++ b/.changeset/silent-horses-raise.md @@ -4,4 +4,4 @@ '@backstage/frontend-app-api': patch --- -Added `elements` and `wrappers` inputs to `app/root`, that let you add things to the root of the React tree above the layout. You can use the `createAppRootElementExtension` and `createAppRootWrapperExtension` extension creator, respectively, to conveniently create such extensions. +Added `elements`, `wrappers`, and `router` inputs to `app/root`, that let you add things to the root of the React tree above the layout. You can use the `createAppRootElementExtension`, `createAppRootWrapperExtension`, and `createRouterExtension` extension creator, respectively, to conveniently create such extensions. These are all optional, and if you do not supply a router a default one will be used (`BrowserRouter` in regular runs, `MemoryRouter` in tests/CI). diff --git a/packages/frontend-app-api/src/extensions/AppRoot.tsx b/packages/frontend-app-api/src/extensions/AppRoot.tsx index 883074ca00..2b709f4e42 100644 --- a/packages/frontend-app-api/src/extensions/AppRoot.tsx +++ b/packages/frontend-app-api/src/extensions/AppRoot.tsx @@ -17,6 +17,7 @@ import React, { ComponentType, Fragment, + PropsWithChildren, ReactNode, useContext, useState, @@ -26,6 +27,7 @@ import { createAppRootWrapperExtension, createExtension, createExtensionInput, + createRouterExtension, createSignInPageExtension, } from '@backstage/frontend-plugin-api'; import { @@ -46,6 +48,10 @@ export const AppRoot = createExtension({ name: 'root', attachTo: { id: 'app', input: 'root' }, inputs: { + router: createExtensionInput( + { component: createRouterExtension.componentDataRef }, + { singleton: true, optional: true }, + ), signInPage: createExtensionInput( { component: createSignInPageExtension.componentDataRef }, { singleton: true, optional: true }, @@ -80,7 +86,10 @@ export const AppRoot = createExtension({ return { element: ( - + {content} ), @@ -133,12 +142,18 @@ function SignInPageWrapper({ export interface AppRouterProps { children?: ReactNode; SignInPageComponent?: ComponentType; + RouterComponent?: ComponentType>; +} + +function DefaultRouter(props: PropsWithChildren<{}>) { + const configApi = useApi(configApiRef); + const basePath = getBasePath(configApi); + return {props.children}; } /** * App router and sign-in page wrapper. * - * @public * @remarks * * The AppRouter provides the routing context and renders the sign-in page. @@ -147,7 +162,11 @@ export interface AppRouterProps { * the app, while providing routing and route tracking for the app. */ export function AppRouter(props: AppRouterProps) { - const { children, SignInPageComponent } = props; + const { + children, + SignInPageComponent, + RouterComponent = DefaultRouter, + } = props; const configApi = useApi(configApiRef); const basePath = getBasePath(configApi); @@ -183,15 +202,15 @@ export function AppRouter(props: AppRouterProps) { ); return ( - + {children} - + ); } return ( - + {children} - + ); } diff --git a/packages/frontend-plugin-api/api-report.md b/packages/frontend-plugin-api/api-report.md index 6113c946ee..1d42d8bab8 100644 --- a/packages/frontend-plugin-api/api-report.md +++ b/packages/frontend-plugin-api/api-report.md @@ -687,6 +687,39 @@ export function createRouteRef< } >; +// @public +export function createRouterExtension< + TConfig extends {}, + TInputs extends AnyExtensionInputMap, +>(options: { + namespace?: string; + name?: string; + attachTo?: { + id: string; + input: string; + }; + configSchema?: PortableSchema; + disabled?: boolean; + inputs?: TInputs; + Component: ComponentType< + PropsWithChildren<{ + inputs: Expand>; + config: TConfig; + }> + >; +}): ExtensionDefinition; + +// @public (undocumented) +export namespace createRouterExtension { + const // (undocumented) + componentDataRef: ConfigurableExtensionDataRef< + React_2.ComponentType<{ + children?: React_2.ReactNode; + }>, + {} + >; +} + // @public (undocumented) export function createSchemaFromZod( schemaCreator: (zImpl: typeof z) => ZodSchema, diff --git a/packages/frontend-plugin-api/package.json b/packages/frontend-plugin-api/package.json index 4722cd1bff..007d76764b 100644 --- a/packages/frontend-plugin-api/package.json +++ b/packages/frontend-plugin-api/package.json @@ -22,6 +22,21 @@ "prepack": "backstage-cli package prepack", "postpack": "backstage-cli package postpack" }, + "dependencies": { + "@backstage/core-components": "workspace:^", + "@backstage/core-plugin-api": "workspace:^", + "@backstage/types": "workspace:^", + "@backstage/version-bridge": "workspace:^", + "@material-ui/core": "^4.12.4", + "@types/react": "^16.13.1 || ^17.0.0", + "lodash": "^4.17.21", + "zod": "^3.22.4", + "zod-to-json-schema": "^3.21.4" + }, + "peerDependencies": { + "react": "^16.13.1 || ^17.0.0 || ^18.0.0", + "react-router-dom": "6.0.0-beta.0 || ^6.3.0" + }, "devDependencies": { "@backstage/cli": "workspace:^", "@backstage/frontend-app-api": "workspace:^", @@ -33,20 +48,5 @@ }, "files": [ "dist" - ], - "peerDependencies": { - "react": "^16.13.1 || ^17.0.0 || ^18.0.0", - "react-router-dom": "6.0.0-beta.0 || ^6.3.0" - }, - "dependencies": { - "@backstage/core-components": "workspace:^", - "@backstage/core-plugin-api": "workspace:^", - "@backstage/types": "workspace:^", - "@backstage/version-bridge": "workspace:^", - "@material-ui/core": "^4.12.4", - "@types/react": "^16.13.1 || ^17.0.0", - "lodash": "^4.17.21", - "zod": "^3.22.4", - "zod-to-json-schema": "^3.21.4" - } + ] } diff --git a/packages/frontend-plugin-api/src/extensions/createRouterExtension.test.tsx b/packages/frontend-plugin-api/src/extensions/createRouterExtension.test.tsx new file mode 100644 index 0000000000..7109e1a8aa --- /dev/null +++ b/packages/frontend-plugin-api/src/extensions/createRouterExtension.test.tsx @@ -0,0 +1,167 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createSpecializedApp } from '@backstage/frontend-app-api'; +import { render, screen } from '@testing-library/react'; +import React from 'react'; +import { MockConfigApi } from '@backstage/test-utils'; +import { MemoryRouter } from 'react-router-dom'; +import { createSchemaFromZod } from '../schema/createSchemaFromZod'; +import { coreExtensionData } from '../wiring/coreExtensionData'; +import { createExtension } from '../wiring/createExtension'; +import { createExtensionInput } from '../wiring/createExtensionInput'; +import { createExtensionOverrides } from '../wiring/createExtensionOverrides'; +import { createPageExtension } from './createPageExtension'; +import { createRouterExtension } from './createRouterExtension'; + +describe('createRouterExtension', () => { + it('works with simple options and no props', async () => { + const extension = createRouterExtension({ + namespace: 'test', + Component: ({ children }) => ( + +
{children}
+
+ ), + }); + + expect(extension).toEqual({ + $$type: '@backstage/ExtensionDefinition', + version: 'v1', + kind: 'app-router-component', + namespace: 'test', + attachTo: { id: 'app/root', input: 'router' }, + disabled: false, + inputs: {}, + output: { + component: expect.anything(), + }, + factory: expect.any(Function), + toString: expect.any(Function), + }); + + const app = createSpecializedApp({ + features: [ + createExtensionOverrides({ + extensions: [ + extension, + createPageExtension({ + namespace: 'test', + defaultPath: '/', + loader: async () =>
, + }), + ], + }), + ], + }); + + render(app.createRoot()); + + await expect( + screen.findByTestId('test-contents'), + ).resolves.toBeInTheDocument(); + await expect( + screen.findByTestId('test-router'), + ).resolves.toBeInTheDocument(); + }); + + it('works with complex options and props', async () => { + const schema = createSchemaFromZod(z => z.object({ name: z.string() })); + + const extension = createRouterExtension({ + namespace: 'test', + name: 'test', + configSchema: schema, + inputs: { + children: createExtensionInput({ + element: coreExtensionData.reactElement, + }), + }, + Component: ({ inputs, config, children }) => ( + +
+ {children} +
+
+ ), + }); + + expect(extension).toEqual({ + $$type: '@backstage/ExtensionDefinition', + version: 'v1', + kind: 'app-router-component', + namespace: 'test', + name: 'test', + attachTo: { id: 'app/root', input: 'router' }, + configSchema: schema, + disabled: false, + inputs: { + children: createExtensionInput({ + element: coreExtensionData.reactElement, + }), + }, + output: { + component: expect.anything(), + }, + factory: expect.any(Function), + toString: expect.any(Function), + }); + + const app = createSpecializedApp({ + features: [ + createExtensionOverrides({ + extensions: [ + extension, + createExtension({ + namespace: 'test', + attachTo: { + id: 'app-router-component:test/test', + input: 'children', + }, + output: { element: coreExtensionData.reactElement }, // doesn't matter + factory: () => ({ element:
}), + }), + createPageExtension({ + namespace: 'test', + defaultPath: '/', + loader: async () =>
, + }), + ], + }), + ], + config: new MockConfigApi({ + app: { + extensions: [ + { + 'app-router-component:test/test': { config: { name: 'Robin' } }, + }, + ], + }, + }), + }); + + render(app.createRoot()); + + await expect( + screen.findByTestId('test-contents'), + ).resolves.toBeInTheDocument(); + await expect( + screen.findByTestId('test-router-Robin-1'), + ).resolves.toBeInTheDocument(); + }); +}); diff --git a/packages/frontend-plugin-api/src/extensions/createRouterExtension.tsx b/packages/frontend-plugin-api/src/extensions/createRouterExtension.tsx new file mode 100644 index 0000000000..32d61f7e60 --- /dev/null +++ b/packages/frontend-plugin-api/src/extensions/createRouterExtension.tsx @@ -0,0 +1,84 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React, { ComponentType, PropsWithChildren } from 'react'; +import { PortableSchema } from '../schema/types'; +import { + AnyExtensionInputMap, + ExtensionDefinition, + ResolvedExtensionInputs, + createExtension, +} from '../wiring/createExtension'; +import { createExtensionDataRef } from '../wiring/createExtensionDataRef'; +import { Expand } from '../types'; + +/** + * Creates an extension that replaces the router implementation at the app root. + * This is useful to be able to for example replace the BrowserRouter with a + * MemoryRouter in tests, or to add additional props to a BrowserRouter. + * + * @public + */ +export function createRouterExtension< + TConfig extends {}, + TInputs extends AnyExtensionInputMap, +>(options: { + namespace?: string; + name?: string; + attachTo?: { id: string; input: string }; + configSchema?: PortableSchema; + disabled?: boolean; + inputs?: TInputs; + Component: ComponentType< + PropsWithChildren<{ + inputs: Expand>; + config: TConfig; + }> + >; +}): ExtensionDefinition { + return createExtension({ + kind: 'app-router-component', + namespace: options.namespace, + name: options.name, + attachTo: options.attachTo ?? { id: 'app/root', input: 'router' }, + configSchema: options.configSchema, + disabled: options.disabled, + inputs: options.inputs, + output: { + component: createRouterExtension.componentDataRef, + }, + factory({ inputs, config }) { + const Component = (props: PropsWithChildren<{}>) => { + return ( + + {props.children} + + ); + }; + return { + component: Component, + }; + }, + }); +} + +/** @public */ +export namespace createRouterExtension { + export const componentDataRef = + createExtensionDataRef>>( + 'app.router.wrapper', + ); +} diff --git a/packages/frontend-plugin-api/src/extensions/index.ts b/packages/frontend-plugin-api/src/extensions/index.ts index 40e9f6f56d..562cb728cb 100644 --- a/packages/frontend-plugin-api/src/extensions/index.ts +++ b/packages/frontend-plugin-api/src/extensions/index.ts @@ -17,6 +17,7 @@ export { createApiExtension } from './createApiExtension'; export { createAppRootElementExtension } from './createAppRootElementExtension'; export { createAppRootWrapperExtension } from './createAppRootWrapperExtension'; +export { createRouterExtension } from './createRouterExtension'; export { createPageExtension } from './createPageExtension'; export { createNavItemExtension } from './createNavItemExtension'; export { createNavLogoExtension } from './createNavLogoExtension'; diff --git a/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx b/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx index 40baaa3ac9..d53344f7fd 100644 --- a/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx +++ b/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx @@ -219,10 +219,14 @@ describe('createExtensionTester', () => { fireEvent.click(await screen.findByRole('button', { name: 'See details' })); await waitFor(() => - expect(analyticsApiMock.getEvents()[0]).toMatchObject({ - action: 'click', - subject: 'See details', - }), + expect(analyticsApiMock.getEvents()).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + action: 'click', + subject: 'See details', + }), + ]), + ), ); }); }); diff --git a/packages/frontend-test-utils/src/app/createExtensionTester.tsx b/packages/frontend-test-utils/src/app/createExtensionTester.tsx index 2984af9dbf..a98879fbba 100644 --- a/packages/frontend-test-utils/src/app/createExtensionTester.tsx +++ b/packages/frontend-test-utils/src/app/createExtensionTester.tsx @@ -14,29 +14,20 @@ * limitations under the License. */ -import React, { - ComponentType, - Fragment, - ReactNode, - useContext, - useState, -} from 'react'; +import React from 'react'; import { MemoryRouter, Link } from 'react-router-dom'; import { RenderResult, render } from '@testing-library/react'; import { createSpecializedApp } from '@backstage/frontend-app-api'; import { ExtensionDefinition, IconComponent, - IdentityApi, RouteRef, - configApiRef, coreExtensionData, - createAppRootWrapperExtension, createExtension, createExtensionInput, createExtensionOverrides, createNavItemExtension, - useApi, + createRouterExtension, useRouteRef, } from '@backstage/frontend-plugin-api'; import { MockConfigApi } from '@backstage/test-utils'; @@ -44,15 +35,7 @@ import { JsonArray, JsonObject, JsonValue } from '@backstage/types'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { resolveExtensionDefinition } from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { createSignInPageExtension } from '../../../frontend-plugin-api/src/extensions/createSignInPageExtension'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports import { toInternalExtensionDefinition } from '../../../frontend-plugin-api/src/wiring/createExtension'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { InternalAppContext } from '../../../frontend-app-api/src/wiring/InternalAppContext'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { SignInPageProps } from '../../../core-plugin-api'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { getBasePath } from '../../../core-app-api/src/app/AppRouter'; const NavItem = (props: { routeRef: RouteRef; @@ -102,113 +85,6 @@ const TestAppNavExtension = createExtension({ }, }); -const AuthenticationProvider = (props: { - signInPage?: ComponentType; - children: ReactNode; -}) => { - const { signInPage: SignInPage, children } = props; - const configApi = useApi(configApiRef); - const signOutTargetUrl = getBasePath(configApi) || '/'; - - const internalAppContext = useContext(InternalAppContext); - if (!internalAppContext) { - throw new Error('AppRouter must be rendered within the AppProvider'); - } - - const { appIdentityProxy } = internalAppContext; - const [identityApi, setIdentityApi] = useState(); - - if (!SignInPage) { - appIdentityProxy.setTarget( - { - getUserId: () => 'guest', - getIdToken: async () => undefined, - getProfile: () => ({ - email: 'guest@example.com', - displayName: 'Guest', - }), - getProfileInfo: async () => ({ - email: 'guest@example.com', - displayName: 'Guest', - }), - getBackstageIdentity: async () => ({ - type: 'user', - userEntityRef: 'user:default/guest', - ownershipEntityRefs: ['user:default/guest'], - }), - getCredentials: async () => ({}), - signOut: async () => {}, - }, - { signOutTargetUrl }, - ); - - return children; - } - - if (!identityApi) { - return ; - } - - appIdentityProxy.setTarget(identityApi, { - signOutTargetUrl, - }); - - return children; -}; - -const TestAppRootExtension = createExtension({ - namespace: 'app', - name: 'root', - attachTo: { id: 'app', input: 'root' }, - inputs: { - signInPage: createExtensionInput( - { component: createSignInPageExtension.componentDataRef }, - { singleton: true, optional: true }, - ), - children: createExtensionInput( - { element: coreExtensionData.reactElement }, - { singleton: true }, - ), - elements: createExtensionInput( - { element: coreExtensionData.reactElement }, - { optional: true }, - ), - wrappers: createExtensionInput( - { component: createAppRootWrapperExtension.componentDataRef }, - { optional: true }, - ), - }, - output: { - element: coreExtensionData.reactElement, - }, - factory({ inputs }) { - const SignInPage = inputs.signInPage?.output.component; - - let content: React.ReactNode = ( - <> - {inputs.elements.map(el => ( - {el.output.element} - ))} - {inputs.children.output.element} - - ); - - for (const wrapper of inputs.wrappers) { - content = {content}; - } - - return { - element: ( - - - {content} - - - ), - }; - }, -}); - /** @public */ export class ExtensionTester { /** @internal */ @@ -302,7 +178,12 @@ export class ExtensionTester { extensions: [ ...this.#extensions.map(extension => extension.definition), TestAppNavExtension, - TestAppRootExtension, + createRouterExtension({ + namespace: 'test', + Component: ({ children }) => ( + {children} + ), + }), ], }), ], diff --git a/packages/frontend-test-utils/src/app/renderInTestApp.test.tsx b/packages/frontend-test-utils/src/app/renderInTestApp.test.tsx index a6783d43a3..a56ae2f140 100644 --- a/packages/frontend-test-utils/src/app/renderInTestApp.test.tsx +++ b/packages/frontend-test-utils/src/app/renderInTestApp.test.tsx @@ -56,9 +56,13 @@ describe('renderInTestApp', () => { fireEvent.click(screen.getByRole('link', { name: 'See details' })); - expect(analyticsApiMock.getEvents()[0]).toMatchObject({ - action: 'click', - subject: 'See details', - }); + expect(analyticsApiMock.getEvents()).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + action: 'click', + subject: 'See details', + }), + ]), + ); }); }); From 398b44907c2487393fc106b45a8f9c754d84216a Mon Sep 17 00:00:00 2001 From: bnechyporenko Date: Sun, 14 Jan 2024 14:51:17 +0100 Subject: [PATCH 24/35] Updated Template.v1beta3.schema.json, added a missing "presentation" field Signed-off-by: Bogdan Nechyporenko Signed-off-by: bnechyporenko --- .../src/Template.v1beta3.schema.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/plugins/scaffolder-common/src/Template.v1beta3.schema.json b/plugins/scaffolder-common/src/Template.v1beta3.schema.json index d4be9b7538..d7698511d8 100644 --- a/plugins/scaffolder-common/src/Template.v1beta3.schema.json +++ b/plugins/scaffolder-common/src/Template.v1beta3.schema.json @@ -139,6 +139,24 @@ } ] }, + "presentation": { + "type": "object", + "description": "A way to redefine the labels for actionable buttons.", + "properties": { + "backButtonText": { + "type": "string", + "description": "A button which return the user to one step back." + }, + "createButtonText": { + "type": "string", + "description": "A button which start the execution of the template." + }, + "reviewButtonText": { + "type": "string", + "description": "A button which open the review step to verify the input prior to start the execution." + } + } + }, "steps": { "type": "array", "description": "A list of steps to execute.", From 178b8d8a70b7ba877236b8a9763877f71c534b7e Mon Sep 17 00:00:00 2001 From: bnechyporenko Date: Sun, 14 Jan 2024 14:51:50 +0100 Subject: [PATCH 25/35] Updated Template.v1beta3.schema.json, added a missing "presentation" field Signed-off-by: Bogdan Nechyporenko Signed-off-by: bnechyporenko --- .changeset/fresh-mirrors-shake.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fresh-mirrors-shake.md diff --git a/.changeset/fresh-mirrors-shake.md b/.changeset/fresh-mirrors-shake.md new file mode 100644 index 0000000000..a722182986 --- /dev/null +++ b/.changeset/fresh-mirrors-shake.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-common': patch +--- + +Updated Template.v1beta3.schema.json, added a missing "presentation" field From 331389fccdfa9d96410cf67c1cf5e3c57b2052d7 Mon Sep 17 00:00:00 2001 From: bnechyporenko Date: Sun, 14 Jan 2024 14:54:50 +0100 Subject: [PATCH 26/35] wip Signed-off-by: Bogdan Nechyporenko Signed-off-by: bnechyporenko --- .../src/Template.v1beta3.schema.json | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/plugins/scaffolder-common/src/Template.v1beta3.schema.json b/plugins/scaffolder-common/src/Template.v1beta3.schema.json index d7698511d8..d4be9b7538 100644 --- a/plugins/scaffolder-common/src/Template.v1beta3.schema.json +++ b/plugins/scaffolder-common/src/Template.v1beta3.schema.json @@ -139,24 +139,6 @@ } ] }, - "presentation": { - "type": "object", - "description": "A way to redefine the labels for actionable buttons.", - "properties": { - "backButtonText": { - "type": "string", - "description": "A button which return the user to one step back." - }, - "createButtonText": { - "type": "string", - "description": "A button which start the execution of the template." - }, - "reviewButtonText": { - "type": "string", - "description": "A button which open the review step to verify the input prior to start the execution." - } - } - }, "steps": { "type": "array", "description": "A list of steps to execute.", From 04a57a034db2d90fe49d2c889222617f4190e91e Mon Sep 17 00:00:00 2001 From: bnechyporenko Date: Sun, 14 Jan 2024 14:57:15 +0100 Subject: [PATCH 27/35] wip Signed-off-by: Bogdan Nechyporenko Signed-off-by: bnechyporenko --- .../src/Template.v1beta3.schema.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/plugins/scaffolder-common/src/Template.v1beta3.schema.json b/plugins/scaffolder-common/src/Template.v1beta3.schema.json index d4be9b7538..d7698511d8 100644 --- a/plugins/scaffolder-common/src/Template.v1beta3.schema.json +++ b/plugins/scaffolder-common/src/Template.v1beta3.schema.json @@ -139,6 +139,24 @@ } ] }, + "presentation": { + "type": "object", + "description": "A way to redefine the labels for actionable buttons.", + "properties": { + "backButtonText": { + "type": "string", + "description": "A button which return the user to one step back." + }, + "createButtonText": { + "type": "string", + "description": "A button which start the execution of the template." + }, + "reviewButtonText": { + "type": "string", + "description": "A button which open the review step to verify the input prior to start the execution." + } + } + }, "steps": { "type": "array", "description": "A list of steps to execute.", From 7b8e551a830cf57f36720c8f4701a46e9505ace2 Mon Sep 17 00:00:00 2001 From: lshwayne96 Date: Mon, 15 Jan 2024 12:37:57 +0800 Subject: [PATCH 28/35] Fix errors when deleting SQS messages Signed-off-by: lshwayne96 --- .changeset/shaggy-coins-happen.md | 8 ++++++++ .../src/publisher/AwsSqsConsumingEventPublisher.ts | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 .changeset/shaggy-coins-happen.md diff --git a/.changeset/shaggy-coins-happen.md b/.changeset/shaggy-coins-happen.md new file mode 100644 index 0000000000..c20a8e0cb2 --- /dev/null +++ b/.changeset/shaggy-coins-happen.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-events-backend-module-aws-sqs': patch +--- + +Fix errors when deleting SQS messages: + +- If zero messages were received, skip deletion to avoid `EmptyBatchRequest` error from the SQS client. +- If zero failures were returned from the SQS client during deletion, skip error logging. diff --git a/plugins/events-backend-module-aws-sqs/src/publisher/AwsSqsConsumingEventPublisher.ts b/plugins/events-backend-module-aws-sqs/src/publisher/AwsSqsConsumingEventPublisher.ts index 4f4e6ea55d..a2f6a00fe7 100644 --- a/plugins/events-backend-module-aws-sqs/src/publisher/AwsSqsConsumingEventPublisher.ts +++ b/plugins/events-backend-module-aws-sqs/src/publisher/AwsSqsConsumingEventPublisher.ts @@ -111,7 +111,7 @@ export class AwsSqsConsumingEventPublisher implements EventPublisher { } private async deleteMessages(messages?: Message[]): Promise { - if (!messages) { + if (!messages || messages.length === 0) { return; } @@ -129,7 +129,7 @@ export class AwsSqsConsumingEventPublisher implements EventPublisher { const result = await this.sqs.send( new DeleteMessageBatchCommand(deleteParams), ); - if (result.Failed) { + if (result.Failed && result.Failed.length > 0) { this.logger.error( `Failed to delete ${result.Failed!.length} of ${ messages.length From 7689c72085b82d45fa40d1c342730fa3c940c43b Mon Sep 17 00:00:00 2001 From: Stefan Buck Date: Mon, 15 Jan 2024 13:39:06 +0100 Subject: [PATCH 29/35] update snyk logo Signed-off-by: Stefan Buck --- microsite/data/plugins/snyk-security.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microsite/data/plugins/snyk-security.yaml b/microsite/data/plugins/snyk-security.yaml index 4abd068247..8c22d44510 100644 --- a/microsite/data/plugins/snyk-security.yaml +++ b/microsite/data/plugins/snyk-security.yaml @@ -5,6 +5,6 @@ authorUrl: https://snyk.io category: Security description: View Snyk scanned vulnerabilities and license compliance of your components directly in Backstage. documentation: https://github.com/snyk-tech-services/backstage-plugin-snyk/blob/main/README.md -iconUrl: https://storage.googleapis.com/snyk-technical-services.appspot.com/snyk-logo-vertical-black.png +iconUrl: https://github.com/snyk-tech-services/backstage-plugin-snyk/assets/109112986/95eb1b06-b3e8-4910-9233-11926e02fa18 npmPackageName: 'backstage-plugin-snyk' addedDate: '2021-01-22' From d16f85f237d54d5e501131bab7c0336b1ee3a0cc Mon Sep 17 00:00:00 2001 From: Heikki Hellgren Date: Mon, 15 Jan 2024 14:40:59 +0200 Subject: [PATCH 30/35] feat: show first scaffolder output text by default usually there's only one output text that could be shown automatically when the output is shown. Signed-off-by: Heikki Hellgren --- .changeset/sixty-plums-switch.md | 5 +++++ .../DefaultTemplateOutputs.test.tsx | 10 +++++++++- .../TemplateOutputs/DefaultTemplateOutputs.tsx | 17 +++++++++-------- .../components/TemplateOutputs/TextOutputs.tsx | 6 +++++- 4 files changed, 28 insertions(+), 10 deletions(-) create mode 100644 .changeset/sixty-plums-switch.md diff --git a/.changeset/sixty-plums-switch.md b/.changeset/sixty-plums-switch.md new file mode 100644 index 0000000000..8da07609c6 --- /dev/null +++ b/.changeset/sixty-plums-switch.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-react': patch +--- + +Show first scaffolder output text by default diff --git a/plugins/scaffolder-react/src/next/components/TemplateOutputs/DefaultTemplateOutputs.test.tsx b/plugins/scaffolder-react/src/next/components/TemplateOutputs/DefaultTemplateOutputs.test.tsx index 8366dcadf8..4c631b86b0 100644 --- a/plugins/scaffolder-react/src/next/components/TemplateOutputs/DefaultTemplateOutputs.test.tsx +++ b/plugins/scaffolder-react/src/next/components/TemplateOutputs/DefaultTemplateOutputs.test.tsx @@ -25,7 +25,10 @@ describe('', () => { it('should render template output', async () => { const output = { links: [{ title: 'Link 1', url: 'https://backstage.io/' }], - text: [{ title: 'Text 1', content: 'Hello, **world**!' }], + text: [ + { title: 'Text 1', content: 'Hello, **world**!' }, + { title: 'Text 2', content: 'Hello, **mars**!' }, + ], }; const { getByRole } = await renderInTestApp( @@ -37,6 +40,11 @@ describe('', () => { }, ); + // first text output default visible + expect(getByRole('heading', { level: 2 }).innerHTML).toBe( + output.text[0].title, + ); + // test link outputs for (const link of output.links ?? []) { expect( diff --git a/plugins/scaffolder-react/src/next/components/TemplateOutputs/DefaultTemplateOutputs.tsx b/plugins/scaffolder-react/src/next/components/TemplateOutputs/DefaultTemplateOutputs.tsx index a495e4ca9e..c105c5a571 100644 --- a/plugins/scaffolder-react/src/next/components/TemplateOutputs/DefaultTemplateOutputs.tsx +++ b/plugins/scaffolder-react/src/next/components/TemplateOutputs/DefaultTemplateOutputs.tsx @@ -28,17 +28,18 @@ import { TextOutputs } from './TextOutputs'; export const DefaultTemplateOutputs = (props: { output?: ScaffolderTaskOutput; }) => { - const [textOutputIndex, setTextOutputIndex] = useState(); + const { output } = props; + const [textOutputIndex, setTextOutputIndex] = useState( + output?.text?.length ? 0 : undefined, + ); const textOutput = useMemo( () => - textOutputIndex !== undefined - ? props.output?.text?.[textOutputIndex] - : null, - [props.output, textOutputIndex], + textOutputIndex !== undefined ? output?.text?.[textOutputIndex] : null, + [output, textOutputIndex], ); - if (!props.output) { + if (!output) { return null; } @@ -48,11 +49,11 @@ export const DefaultTemplateOutputs = (props: { - + diff --git a/plugins/scaffolder-react/src/next/components/TemplateOutputs/TextOutputs.tsx b/plugins/scaffolder-react/src/next/components/TemplateOutputs/TextOutputs.tsx index 7e64603e92..8f61aa6a30 100644 --- a/plugins/scaffolder-react/src/next/components/TemplateOutputs/TextOutputs.tsx +++ b/plugins/scaffolder-react/src/next/components/TemplateOutputs/TextOutputs.tsx @@ -47,7 +47,11 @@ export const TextOutputs = (props: { startIcon={} component="div" color="primary" - onClick={() => setIndex?.(index !== i ? i : undefined)} + onClick={() => { + if (index !== i) { + setIndex?.(i); + } + }} variant={index === i ? 'outlined' : undefined} > {title} From 9373b4c88ef97306d7f699d21428e5b7f0942f8f Mon Sep 17 00:00:00 2001 From: Daniel Laird Date: Mon, 15 Jan 2024 14:21:23 +0000 Subject: [PATCH 31/35] Resolve PR feedback Signed-off-by: Daniel Laird --- .changeset/fair-spies-rescue.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/fair-spies-rescue.md b/.changeset/fair-spies-rescue.md index 2b49d4b969..d7f1ea5275 100644 --- a/.changeset/fair-spies-rescue.md +++ b/.changeset/fair-spies-rescue.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-scaffolder-backend-module-github': minor +'@backstage/plugin-scaffolder-backend-module-github': patch --- Ensure `teamReviewers` list is unique before calling API From f5da6523c03fbfc8f6de6e72cfbc47de0a4386da Mon Sep 17 00:00:00 2001 From: armandocomellas1 Date: Mon, 15 Jan 2024 18:01:06 -0600 Subject: [PATCH 32/35] Change the description header of the changeset and also add a unit test for new URL method Signed-off-by: armandocomellas1 --- .changeset/thirty-cats-help.md | 2 +- .../modules/core/UrlReaderProcessor.test.ts | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/.changeset/thirty-cats-help.md b/.changeset/thirty-cats-help.md index bb6011ca9c..9c5d24180d 100644 --- a/.changeset/thirty-cats-help.md +++ b/.changeset/thirty-cats-help.md @@ -2,4 +2,4 @@ '@backstage/plugin-catalog-backend': patch --- -Change script in **UrlReaderProcessor.ts** Replacing the line code 127 with method (new URL) to handle URL Reader from GCS with wildcard \* +Parse the URL using a different method rather than `git-url-parse` to support wildcards for URLs which are not VCS providers diff --git a/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.test.ts b/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.test.ts index c9575ce5cd..0a3bec7570 100644 --- a/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.test.ts +++ b/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.test.ts @@ -213,4 +213,31 @@ describe('UrlReaderProcessor', () => { expect(reader.search).toHaveBeenCalledTimes(1); }); + + it('parser return valid URL with wildcard *', async () => { + const logger = getVoidLogger(); + + const reader: jest.Mocked = { + readUrl: jest.fn(), + readTree: jest.fn(), + search: jest.fn().mockImplementation(async () => []), + }; + + const processor = new UrlReaderProcessor({ reader, logger }); + + const emit = jest.fn(); + + await processor.readLocation( + { + type: 'url', + target: 'https://storage.cloud.google.com/ah-backstage-poc-catalog/*', + }, + false, + emit, + defaultEntityDataParser, + mockCache, + ); + + expect(reader.search).toHaveReturned(); + }); }); From cbf15616cd7d1e0c1adddb6b257afd8c105982e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 16 Jan 2024 08:55:24 +0100 Subject: [PATCH 33/35] exit pre mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/pre.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/pre.json b/.changeset/pre.json index 8190034fe5..2ca69b284d 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -1,5 +1,5 @@ { - "mode": "pre", + "mode": "exit", "tag": "next", "initialVersions": { "example-app": "0.2.90", From f6e4c2a9353fc6707e9334608dca5f5ac7a676e5 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 16 Jan 2024 10:24:31 +0100 Subject: [PATCH 34/35] chore: update the test to ensure that it's called with the correct arguments Signed-off-by: blam --- .../src/modules/core/UrlReaderProcessor.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.test.ts b/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.test.ts index 0a3bec7570..e2489df4b4 100644 --- a/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.test.ts +++ b/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.test.ts @@ -238,6 +238,9 @@ describe('UrlReaderProcessor', () => { mockCache, ); - expect(reader.search).toHaveReturned(); + expect(reader.search).toHaveBeenCalledWith( + 'https://storage.cloud.google.com/ah-backstage-poc-catalog/*', + { etag: undefined }, + ); }); }); From 5fe6600824ca5df919fa80f8577a3083b8ca7472 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 16 Jan 2024 11:32:44 +0100 Subject: [PATCH 35/35] add oauth dialog and alert display to the root elements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Patrik Oldsberg Co-authored-by: blam Co-authored-by: Camila Belo Co-authored-by: Philipp Hugenroth Co-authored-by: Vincenzo Scamporlino Signed-off-by: Fredrik Adelöw --- .changeset/weak-pans-accept.md | 5 +++ package.json | 3 ++ .../src/extensions/elements.tsx | 45 +++++++++++++++++++ .../src/wiring/createApp.test.tsx | 4 ++ .../frontend-app-api/src/wiring/createApp.tsx | 6 +++ 5 files changed, 63 insertions(+) create mode 100644 .changeset/weak-pans-accept.md create mode 100644 packages/frontend-app-api/src/extensions/elements.tsx diff --git a/.changeset/weak-pans-accept.md b/.changeset/weak-pans-accept.md new file mode 100644 index 0000000000..5e75f52058 --- /dev/null +++ b/.changeset/weak-pans-accept.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-app-api': patch +--- + +add oauth dialog and alert display to the root elements diff --git a/package.json b/package.json index 5fbce0ad70..4d052f2ec5 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,11 @@ }, "scripts": { "dev": "concurrently 'yarn start' 'yarn start-backend'", + "dev:next": "concurrently 'yarn start:next' 'yarn start-backend:next'", "start": "yarn workspace example-app start", "start-backend": "yarn workspace example-backend start", + "start:next": "yarn workspace example-app-next start", + "start-backend:next": "yarn workspace example-backend-next start", "build:backend": "yarn workspace example-backend build", "build:all": "backstage-cli repo build --all", "build:api-reports": "yarn build:api-reports:only --tsc", diff --git a/packages/frontend-app-api/src/extensions/elements.tsx b/packages/frontend-app-api/src/extensions/elements.tsx new file mode 100644 index 0000000000..0a764583bb --- /dev/null +++ b/packages/frontend-app-api/src/extensions/elements.tsx @@ -0,0 +1,45 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; +import { + createAppRootElementExtension, + createSchemaFromZod, +} from '@backstage/frontend-plugin-api'; +import React from 'react'; + +export const oauthRequestDialogAppRootElement = createAppRootElementExtension({ + namespace: 'app', + name: 'oauth-request-dialog', + element: , +}); + +export const alertDisplayAppRootElement = createAppRootElementExtension({ + namespace: 'app', + name: 'alert-display', + configSchema: createSchemaFromZod(z => + z.object({ + transientTimeoutMs: z.number().default(5000), + anchorOrigin: z + .object({ + vertical: z.enum(['top', 'bottom']).default('top'), + horizontal: z.enum(['left', 'center', 'right']).default('center'), + }) + .default({}), + }), + ), + element: ({ config }) => , +}); diff --git a/packages/frontend-app-api/src/wiring/createApp.test.tsx b/packages/frontend-app-api/src/wiring/createApp.test.tsx index e6ddc03e38..43bb4499b1 100644 --- a/packages/frontend-app-api/src/wiring/createApp.test.tsx +++ b/packages/frontend-app-api/src/wiring/createApp.test.tsx @@ -259,6 +259,10 @@ describe('createApp', () => { ] ] + elements [ + + + ] ] components [ diff --git a/packages/frontend-app-api/src/wiring/createApp.tsx b/packages/frontend-app-api/src/wiring/createApp.tsx index b25bad03bc..7b2f17193e 100644 --- a/packages/frontend-app-api/src/wiring/createApp.tsx +++ b/packages/frontend-app-api/src/wiring/createApp.tsx @@ -78,6 +78,10 @@ import { apis as defaultApis } from '../../../app-defaults/src/defaults'; import { Route } from 'react-router-dom'; import { SidebarItem } from '@backstage/core-components'; import { DarkTheme, LightTheme } from '../extensions/themes'; +import { + oauthRequestDialogAppRootElement, + alertDisplayAppRootElement, +} from '../extensions/elements'; import { extractRouteInfoFromAppNode } from '../routing/extractRouteInfoFromAppNode'; import { appLanguageApiRef, @@ -116,6 +120,8 @@ export const builtinExtensions = [ DefaultNotFoundErrorPageComponent, LightTheme, DarkTheme, + oauthRequestDialogAppRootElement, + alertDisplayAppRootElement, ...DefaultApis, ].map(def => resolveExtensionDefinition(def));