Merge pull request #24843 from stijnbrouwers/bugfix/awsCodeCommit-getByUrl

Fix host for AWS Code Commit integration and add region
This commit is contained in:
Ben Lambert
2024-06-11 11:53:02 +02:00
committed by GitHub
8 changed files with 161 additions and 40 deletions
+7
View File
@@ -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
+48 -4
View File
@@ -20,7 +20,34 @@ To use this integration, add configuration to your `app-config.yaml`:
```yaml
integrations:
awsCodeCommit:
- accessKeyId: ${AWS_ACCESS_KEY_ID}
- 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:
- region: eu-west-1
accessKeyId: ${AWS_ACCESS_KEY_ID}
secretAccessKey: ${AWS_SECRET_ACCESS_KEY}
```
@@ -34,10 +61,27 @@ 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).
Each entry is a structure with the following **required** elements:
- `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}/--/catalog-info.yaml`
@@ -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();
@@ -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
});
});
+1
View File
@@ -35,6 +35,7 @@ export type AwsCodeCommitIntegrationConfig = {
secretAccessKey?: string;
roleArn?: string;
externalId?: string;
region: string;
};
// @public
@@ -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', () => {
@@ -29,9 +29,12 @@ describe('readAwsCodeCommitIntegrationConfig', () => {
}
it('reads all values (default)', () => {
const output = readAwsCodeCommitIntegrationConfig(buildConfig({}));
const output = readAwsCodeCommitIntegrationConfig(
buildConfig({ region: 'us-east-1' }),
);
expect(output).toEqual({
host: AMAZON_AWS_CODECOMMIT_HOST,
host: `us-east-1.${AMAZON_AWS_CODECOMMIT_HOST}`,
region: `us-east-1`,
});
});
@@ -42,14 +45,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 +75,7 @@ describe('readAwsCodeCommitIntegrationConfigs', () => {
secretAccessKey: 'secret',
externalId: 'external-id',
roleArn: 'role-arn',
region: 'region',
},
]),
);
@@ -79,15 +85,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([]);
});
});
@@ -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.getString('region');
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);
}