From dcb75ee4c0c8b1b36d809510a4c93e5ccc2b1b91 Mon Sep 17 00:00:00 2001 From: "Stijn Brouwers (EISMEA)" Date: Sat, 18 May 2024 09:00:33 +0200 Subject: [PATCH 1/6] bugfix(code-commit-integration): Fix host for codecommit integrations Signed-off-by: Stijn Brouwers (EISMEA) --- docs/integrations/aws-codecommit/locations.md | 8 +++---- .../AwsCodeCommitIntegration.test.ts | 15 ++++++------ .../src/awsCodeCommit/config.test.ts | 15 ++++++------ .../integration/src/awsCodeCommit/config.ts | 24 +++++++++---------- 4 files changed, 29 insertions(+), 33 deletions(-) diff --git a/docs/integrations/aws-codecommit/locations.md b/docs/integrations/aws-codecommit/locations.md index 05a13e5b21..c91c995289 100644 --- a/docs/integrations/aws-codecommit/locations.md +++ b/docs/integrations/aws-codecommit/locations.md @@ -20,7 +20,8 @@ To use this integration, add configuration to your `app-config.yaml`: ```yaml integrations: awsCodeCommit: - - accessKeyId: ${AWS_ACCESS_KEY_ID} + - region: eu-west-1 + accessKeyId: ${AWS_ACCESS_KEY_ID} secretAccessKey: ${AWS_SECRET_ACCESS_KEY} ``` @@ -34,10 +35,7 @@ instruct the AWS CodeCommit reader to assume a role before accessing CodeCommit: ```yaml integrations: awsCodeCommit: - - accessKeyId: ${AWS_ACCESS_KEY_ID} - secretAccessKey: ${AWS_SECRET_ACCESS_KEY} + - region: eu-west-1 roleArn: 'arn:aws:iam::xxxxxxxxxxxx:role/example-role' externalId: 'some-id' # optional ``` - -When no entries are added, the AWS CodeCommit reader will add a default entry that uses the [standard credentials provider chain](https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/auth/DefaultAWSCredentialsProviderChain.html). diff --git a/packages/integration/src/awsCodeCommit/AwsCodeCommitIntegration.test.ts b/packages/integration/src/awsCodeCommit/AwsCodeCommitIntegration.test.ts index 14b3c6c73b..76ccd59af6 100644 --- a/packages/integration/src/awsCodeCommit/AwsCodeCommitIntegration.test.ts +++ b/packages/integration/src/awsCodeCommit/AwsCodeCommitIntegration.test.ts @@ -30,30 +30,29 @@ describe('AwsCodeCommitIntegration', () => { secretAccessKey: ' secret key ', roleArn: `role arn`, externalId: `external id`, + region: `region`, }, ], }, }), }); expect(integrations.list().length).toBe(1); // including default - expect(integrations.list()[0].config.host).toBe(AMAZON_AWS_CODECOMMIT_HOST); + expect(integrations.list()[0].config.host).toBe( + `region.${AMAZON_AWS_CODECOMMIT_HOST}`, + ); expect(integrations.list()[0].config.accessKeyId).toBe('access key'); expect(integrations.list()[0].config.secretAccessKey).toBe('secret key'); expect(integrations.list()[0].config.roleArn).toBe(`role arn`); expect(integrations.list()[0].config.externalId).toBe(`external id`); + expect(integrations.list()[0].config.region).toBe(`region`); }); - it('has a working factory with default values when no data provided', () => { + it('does not have a working factory with default values when no data provided', () => { const integrations = AwsCodeCommitIntegration.factory({ config: new ConfigReader({ integrations: {}, }), }); - expect(integrations.list().length).toBe(1); // including default - expect(integrations.list()[0].config.host).toBe(AMAZON_AWS_CODECOMMIT_HOST); - expect(integrations.list()[0].config.accessKeyId).toBeUndefined(); - expect(integrations.list()[0].config.secretAccessKey).toBeUndefined(); - expect(integrations.list()[0].config.roleArn).toBeUndefined(); - expect(integrations.list()[0].config.externalId).toBeUndefined(); + expect(integrations.list().length).toBe(0); // including default }); it('returns the basics', () => { diff --git a/packages/integration/src/awsCodeCommit/config.test.ts b/packages/integration/src/awsCodeCommit/config.test.ts index 217485b45e..637189f1f8 100644 --- a/packages/integration/src/awsCodeCommit/config.test.ts +++ b/packages/integration/src/awsCodeCommit/config.test.ts @@ -31,7 +31,8 @@ describe('readAwsCodeCommitIntegrationConfig', () => { it('reads all values (default)', () => { const output = readAwsCodeCommitIntegrationConfig(buildConfig({})); expect(output).toEqual({ - host: AMAZON_AWS_CODECOMMIT_HOST, + host: `us-east-1.${AMAZON_AWS_CODECOMMIT_HOST}`, + region: `us-east-1`, }); }); @@ -42,14 +43,16 @@ describe('readAwsCodeCommitIntegrationConfig', () => { secretAccessKey: 'fake-secret-key', externalId: 'fake-external-id', roleArn: 'fake-role-arn', + region: 'fake-region', }), ); expect(output).toEqual({ - host: AMAZON_AWS_CODECOMMIT_HOST, + host: `fake-region.${AMAZON_AWS_CODECOMMIT_HOST}`, accessKeyId: 'fake-key', secretAccessKey: 'fake-secret-key', externalId: 'fake-external-id', roleArn: 'fake-role-arn', + region: 'fake-region', }); }); }); @@ -70,6 +73,7 @@ describe('readAwsCodeCommitIntegrationConfigs', () => { secretAccessKey: 'secret', externalId: 'external-id', roleArn: 'role-arn', + region: 'region', }, ]), ); @@ -79,15 +83,12 @@ describe('readAwsCodeCommitIntegrationConfigs', () => { secretAccessKey: 'secret', externalId: 'external-id', roleArn: 'role-arn', + region: 'region', }); }); it('adds a default entry when missing', () => { const output = readAwsCodeCommitIntegrationConfigs(buildConfig([])); - expect(output).toEqual([ - { - host: AMAZON_AWS_CODECOMMIT_HOST, - }, - ]); + expect(output).toEqual([]); }); }); diff --git a/packages/integration/src/awsCodeCommit/config.ts b/packages/integration/src/awsCodeCommit/config.ts index 0ebd62519f..9b8acccb6f 100644 --- a/packages/integration/src/awsCodeCommit/config.ts +++ b/packages/integration/src/awsCodeCommit/config.ts @@ -48,6 +48,11 @@ export type AwsCodeCommitIntegrationConfig = { * (Optional) External ID to use when assuming role */ externalId?: string; + + /** + * region to use for AWS (default: us-east-1) + */ + region: string; }; /** @@ -64,7 +69,10 @@ export function readAwsCodeCommitIntegrationConfig( const secretAccessKey = config.getOptionalString('secretAccessKey')?.trim(); const roleArn = config.getOptionalString('roleArn'); const externalId = config.getOptionalString('externalId'); - const host = AMAZON_AWS_CODECOMMIT_HOST; + const region = config.getOptionalString('region') || 'us-east-1'; + const host = + config.getOptionalString('host') || + `${region}.${AMAZON_AWS_CODECOMMIT_HOST}`; return { host, @@ -72,6 +80,7 @@ export function readAwsCodeCommitIntegrationConfig( secretAccessKey, roleArn, externalId, + region, }; } @@ -85,16 +94,5 @@ export function readAwsCodeCommitIntegrationConfig( export function readAwsCodeCommitIntegrationConfigs( configs: Config[], ): AwsCodeCommitIntegrationConfig[] { - // First read all the explicit integrations - const result = configs.map(readAwsCodeCommitIntegrationConfig); - - // If no explicit console.aws.amazon.com integration was added, put one in the list as - // a convenience - if (!result.some(c => c.host === AMAZON_AWS_CODECOMMIT_HOST)) { - result.push({ - host: AMAZON_AWS_CODECOMMIT_HOST, - }); - } - - return result; + return configs.map(readAwsCodeCommitIntegrationConfig); } From 786a8d0443a1d3b4ac65ab97dfbaefc2df263594 Mon Sep 17 00:00:00 2001 From: "Stijn Brouwers (EISMEA)" Date: Sat, 18 May 2024 09:24:29 +0200 Subject: [PATCH 2/6] feat(codecommit-integration): Make region mandatory Signed-off-by: Stijn Brouwers (EISMEA) --- docs/integrations/aws-codecommit/locations.md | 4 ++++ packages/integration/src/awsCodeCommit/config.test.ts | 4 +++- packages/integration/src/awsCodeCommit/config.ts | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/integrations/aws-codecommit/locations.md b/docs/integrations/aws-codecommit/locations.md index c91c995289..42b45f7905 100644 --- a/docs/integrations/aws-codecommit/locations.md +++ b/docs/integrations/aws-codecommit/locations.md @@ -39,3 +39,7 @@ integrations: roleArn: 'arn:aws:iam::xxxxxxxxxxxx:role/example-role' externalId: 'some-id' # optional ``` + +Each entry is a structure with the following required elements: + +- `region`: The AWS region to connect to, to communicate with the CodeCommit services. If no host is provided, this value will be used to determine the host url. diff --git a/packages/integration/src/awsCodeCommit/config.test.ts b/packages/integration/src/awsCodeCommit/config.test.ts index 637189f1f8..5bac85545c 100644 --- a/packages/integration/src/awsCodeCommit/config.test.ts +++ b/packages/integration/src/awsCodeCommit/config.test.ts @@ -29,7 +29,9 @@ describe('readAwsCodeCommitIntegrationConfig', () => { } it('reads all values (default)', () => { - const output = readAwsCodeCommitIntegrationConfig(buildConfig({})); + const output = readAwsCodeCommitIntegrationConfig( + buildConfig({ region: 'us-east-1' }), + ); expect(output).toEqual({ host: `us-east-1.${AMAZON_AWS_CODECOMMIT_HOST}`, region: `us-east-1`, diff --git a/packages/integration/src/awsCodeCommit/config.ts b/packages/integration/src/awsCodeCommit/config.ts index 9b8acccb6f..83c449ad16 100644 --- a/packages/integration/src/awsCodeCommit/config.ts +++ b/packages/integration/src/awsCodeCommit/config.ts @@ -69,7 +69,7 @@ export function readAwsCodeCommitIntegrationConfig( const secretAccessKey = config.getOptionalString('secretAccessKey')?.trim(); const roleArn = config.getOptionalString('roleArn'); const externalId = config.getOptionalString('externalId'); - const region = config.getOptionalString('region') || 'us-east-1'; + const region = config.getString('region'); const host = config.getOptionalString('host') || `${region}.${AMAZON_AWS_CODECOMMIT_HOST}`; From 2b8ea39a92ebf2ed95213fd5bd19655ebf516ded Mon Sep 17 00:00:00 2001 From: "Stijn Brouwers (EISMEA)" Date: Tue, 21 May 2024 10:47:47 +0200 Subject: [PATCH 3/6] bugfix(codecommit-integration): Fix unit tests and generate api-reports Signed-off-by: Stijn Brouwers (EISMEA) --- .../reading/AwsCodeCommitUrlReader.test.ts | 81 +++++++++++++++++-- .../src/api/ScmIntegrationsApi.test.ts | 2 +- packages/integration/api-report.md | 1 + 3 files changed, 77 insertions(+), 7 deletions(-) diff --git a/packages/backend-common/src/reading/AwsCodeCommitUrlReader.test.ts b/packages/backend-common/src/reading/AwsCodeCommitUrlReader.test.ts index 6263d073fc..a17f0c6c61 100644 --- a/packages/backend-common/src/reading/AwsCodeCommitUrlReader.test.ts +++ b/packages/backend-common/src/reading/AwsCodeCommitUrlReader.test.ts @@ -212,7 +212,7 @@ describe('AwsCodeCommitUrlReader', () => { integrations: {}, }); - expect(entries).toHaveLength(1); + expect(entries).toHaveLength(0); }); it('creates a reader with credentials correctly configured', () => { @@ -221,6 +221,7 @@ describe('AwsCodeCommitUrlReader', () => { host: AMAZON_AWS_CODECOMMIT_HOST, accessKeyId: 'fakekey', secretAccessKey: 'fakekey', + region: 'fakeregion', }); const entries = createReader({ @@ -236,6 +237,7 @@ describe('AwsCodeCommitUrlReader', () => { const awsCodeCommitIntegrations = []; awsCodeCommitIntegrations.push({ host: AMAZON_AWS_CODECOMMIT_HOST, + region: 'fakeregion', }); const entries = createReader({ @@ -247,10 +249,32 @@ describe('AwsCodeCommitUrlReader', () => { expect(entries).toHaveLength(1); }); + it('creates a reader without a region', () => { + const awsCodeCommitIntegrations: any[] = []; + awsCodeCommitIntegrations.push({ + accessKeyId: 'fakekey', + secretAccessKey: 'fakekey', + }); + + expect(() => { + createReader({ + integrations: { + awsCodeCommit: awsCodeCommitIntegrations, + }, + }); + }).toThrow( + "Missing required config value at 'integrations.awsCodeCommit[0].region' in 'mock-config'", + ); + }); + describe('predicates', () => { const readers = createReader({ integrations: { - awsCodeCommit: [{}], + awsCodeCommit: [ + { + region: 'fakeregion', + }, + ], }, }); const predicate = readers[0].predicate; @@ -259,7 +283,7 @@ describe('AwsCodeCommitUrlReader', () => { expect( predicate( new URL( - 'https://eu-west-1.console.aws.amazon.com/codesuite/codecommit/repositories/my-repo', + 'https://fakeregion.console.aws.amazon.com/codesuite/codecommit/repositories/my-repo', ), ), ).toBe(true); @@ -269,12 +293,54 @@ describe('AwsCodeCommitUrlReader', () => { expect( predicate( new URL( - 'https://eu-west-1.console.aws.amazon.com/codesuite/codecommit/repositories/my-repo/browse/--/catalog-info.yaml?region=eu-west-1', + 'https://fakeregion.console.aws.amazon.com/codesuite/codecommit/repositories/my-repo/browse/--/catalog-info.yaml?region=eu-west-1', ), ), ).toBe(true); }); + it('returns true for a url with the full path and the correct host when the host overrules the region', () => { + const predicateWithHost = createReader({ + integrations: { + awsCodeCommit: [ + { + host: 'fakehost', + region: 'fakeregion', + }, + ], + }, + })[0].predicate; + + expect( + predicateWithHost( + new URL( + 'https://fakehost/codesuite/codecommit/repositories/my-repo/browse/--/catalog-info.yaml?region=eu-west-1', + ), + ), + ).toBe(true); + }); + + it('returns false for a url with the full path and the wrong host when the host overrules the region', () => { + const predicateWithHost = createReader({ + integrations: { + awsCodeCommit: [ + { + host: 'fakehost', + region: 'fakeregion', + }, + ], + }, + })[0].predicate; + + expect( + predicateWithHost( + new URL( + 'https://fakeregion.console.aws.amazon.com/codesuite/codecommit/repositories/my-repo/browse/--/catalog-info.yaml?region=eu-west-1', + ), + ), + ).toBe(false); + }); + it('returns false for an incorrect host', () => { expect(predicate(new URL('https://amazon.com'))).toBe(false); }); @@ -304,6 +370,7 @@ describe('AwsCodeCommitUrlReader', () => { host: 'amazonaws.com', accessKeyId: 'fake-access-key', secretAccessKey: 'fake-secret-key', + region: 'fakeregion', }, ], }, @@ -348,6 +415,7 @@ describe('AwsCodeCommitUrlReader', () => { host: AMAZON_AWS_CODECOMMIT_HOST, accessKeyId: 'fake-access-key', secretAccessKey: 'fake-secret-key', + region: 'fakeregion', }, ], }, @@ -403,6 +471,7 @@ describe('AwsCodeCommitUrlReader', () => { host: AMAZON_AWS_CODECOMMIT_HOST, accessKeyId: 'fake-access-key', secretAccessKey: 'fake-secret-key', + region: 'fakeregion', }, ], }, @@ -509,9 +578,9 @@ describe('AwsCodeCommitUrlReader', () => { }); const config = new ConfigReader({ - host: AMAZON_AWS_CODECOMMIT_HOST, accessKeyId: 'fake-access-key', secretAccessKey: 'fake-secret-key', + region: 'fakeregion', }); const credsManager = DefaultAwsCredentialsManager.fromConfig(config); @@ -527,7 +596,7 @@ describe('AwsCodeCommitUrlReader', () => { it('returns contents of a file in a repository', async () => { const response = await awsCodeCommitUrlReader.readTree( - 'https://eu-west-1.console.aws.amazon.com/codesuite/codecommit/repositories/my-test-techdocs', + 'https://fakeregion.console.aws.amazon.com/codesuite/codecommit/repositories/my-test-techdocs', ); const files = await response.files(); diff --git a/packages/integration-react/src/api/ScmIntegrationsApi.test.ts b/packages/integration-react/src/api/ScmIntegrationsApi.test.ts index 0a1f1b1b8f..fb53bbdb12 100644 --- a/packages/integration-react/src/api/ScmIntegrationsApi.test.ts +++ b/packages/integration-react/src/api/ScmIntegrationsApi.test.ts @@ -26,6 +26,6 @@ describe('scmIntegrationsApiRef', () => { it('should be instantiated', () => { const i = ScmIntegrationsApi.fromConfig(new ConfigReader({})); - expect(i.list().length).toBe(7); // The default ones + expect(i.list().length).toBe(6); // The default ones }); }); diff --git a/packages/integration/api-report.md b/packages/integration/api-report.md index 4945847751..e5a655a667 100644 --- a/packages/integration/api-report.md +++ b/packages/integration/api-report.md @@ -35,6 +35,7 @@ export type AwsCodeCommitIntegrationConfig = { secretAccessKey?: string; roleArn?: string; externalId?: string; + region: string; }; // @public From 23ee9abacc8992f2db36a66b5e399a55d2edc9e2 Mon Sep 17 00:00:00 2001 From: "Stijn Brouwers (EISMEA)" Date: Tue, 21 May 2024 11:02:24 +0200 Subject: [PATCH 4/6] bugfix(codecommit-integration): Add changeset Signed-off-by: Stijn Brouwers (EISMEA) --- .changeset/spicy-ears-raise.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/spicy-ears-raise.md diff --git a/.changeset/spicy-ears-raise.md b/.changeset/spicy-ears-raise.md new file mode 100644 index 0000000000..a53ed9c721 --- /dev/null +++ b/.changeset/spicy-ears-raise.md @@ -0,0 +1,7 @@ +--- +'@backstage/integration-react': patch +'@backstage/backend-common': patch +'@backstage/integration': patch +--- + +Fix AWS CodeCommit integration by allowing to change the host From 697c44f4db21a4ca1738636e51b60256dd5e13a7 Mon Sep 17 00:00:00 2001 From: "Stijn Brouwers (EISMEA)" Date: Thu, 30 May 2024 08:02:54 +0200 Subject: [PATCH 5/6] feat(codecommit-integration): Add some documentation to locations.md Signed-off-by: Stijn Brouwers (EISMEA) --- docs/integrations/aws-codecommit/locations.md | 46 ++++++++++++++++++- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/docs/integrations/aws-codecommit/locations.md b/docs/integrations/aws-codecommit/locations.md index 42b45f7905..fc9d2cfb1c 100644 --- a/docs/integrations/aws-codecommit/locations.md +++ b/docs/integrations/aws-codecommit/locations.md @@ -17,6 +17,32 @@ plugin. To use this integration, add configuration to your `app-config.yaml`: +```yaml +integrations: + awsCodeCommit: + - region: eu-west-1 +``` + +This most basic example can only be used if you are running Backstage on an instance that has an IAM identity with the following policies: + +``` +{ + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "codecommit:GetFile", + "codecommit:GetFolder" + ], + "Resource": "*", + "Effect": "Allow" + } + ] +} +``` + +If you are running Backstage outside AWS and want to use Access key authentication, you can use the following configuration: + ```yaml integrations: awsCodeCommit: @@ -40,6 +66,22 @@ integrations: externalId: 'some-id' # optional ``` -Each entry is a structure with the following required elements: +Each entry is a structure with the following **required** elements: -- `region`: The AWS region to connect to, to communicate with the CodeCommit services. If no host is provided, this value will be used to determine the host url. +- `region`: The AWS region to connect to, to communicate with the CodeCommit services + +The configuration can also provide a `host` property like the other integrations. If it is not provided, it will be determined from the provided region. +i.e. When you provide `eu-west-1` as the region, Backstage will use the host `eu-west-1.console.aws.amazon.com` to check whether a provided url matches the integration. + +## Register an entity on AWS CodeCommit + +To register an entity that is stored in an AWS CodeCommit repository, you need to fetch the URL from the AWS Console: + +- Navigate to the Code Commit service in the AWS Console ([https://console.aws.amazon.com/codecommit/home](https://console.aws.amazon.com/codecommit/home)) +- _Optional: Make sure you select the correct region if you don't use the default one (us-east-1)_ +- Select the repository where the entity file is stored +- Inside the repository navigate to the backstage entity file (`catalog-info.yaml`) and open the file +- Now you can copy-paste the URL from your browser inside Backstage + +The format would be something like: +`https://{region}.console.aws.amazon.com/codesuite/codecommit/repositories/{reponame}/browse/refs/heads/{branch}/--{/path-to-entity-file}/catalog-info.yaml` From ad91b87b0acaf328a1aaa199245c06494381069d Mon Sep 17 00:00:00 2001 From: "Stijn Brouwers (EISMEA)" Date: Thu, 30 May 2024 08:04:21 +0200 Subject: [PATCH 6/6] docs(codecommit-integration): Fix url format in locations.md Signed-off-by: Stijn Brouwers (EISMEA) --- docs/integrations/aws-codecommit/locations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/aws-codecommit/locations.md b/docs/integrations/aws-codecommit/locations.md index fc9d2cfb1c..35187b1b89 100644 --- a/docs/integrations/aws-codecommit/locations.md +++ b/docs/integrations/aws-codecommit/locations.md @@ -84,4 +84,4 @@ To register an entity that is stored in an AWS CodeCommit repository, you need t - Now you can copy-paste the URL from your browser inside Backstage The format would be something like: -`https://{region}.console.aws.amazon.com/codesuite/codecommit/repositories/{reponame}/browse/refs/heads/{branch}/--{/path-to-entity-file}/catalog-info.yaml` +`https://{region}.console.aws.amazon.com/codesuite/codecommit/repositories/{reponame}/browse/refs/heads/{branch}/--/catalog-info.yaml`