Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(datasource/aws-machine-image): Add profile and region configuration support #24086

Merged
merged 15 commits into from Aug 29, 2023
Merged
37 changes: 37 additions & 0 deletions lib/modules/datasource/aws-machine-image/index.spec.ts
Expand Up @@ -397,4 +397,41 @@ describe('modules/datasource/aws-machine-image/index', () => {
});
});
});

describe('loadConfig()', () => {
const ec2DataSource = new AwsMachineImageDataSource();

it('loads filters without aws config', () => {
const res = ec2DataSource.loadConfig(
'[{"Name":"testname","Values":["testvalue"]}]'
);
expect(res).toEqual([
[
{
Name: 'testname',
Values: ['testvalue'],
},
],
{},
]);
});

it('loads filters with multiple aws configs', () => {
const res = ec2DataSource.loadConfig(
'[{"Name":"testname","Values":["testvalue"]},{"region":"us-west-2"},{"profile":"test-profile"},{"region":"eu-central-1"}]'
);
expect(res).toEqual([
[
{
Name: 'testname',
Values: ['testvalue'],
},
],
{
region: 'eu-central-1',
profile: 'test-profile',
},
]);
});
});
});
55 changes: 46 additions & 9 deletions lib/modules/datasource/aws-machine-image/index.ts
@@ -1,9 +1,15 @@
import { DescribeImagesCommand, EC2Client, Image } from '@aws-sdk/client-ec2';
import {
DescribeImagesCommand,
EC2Client,
Filter,
Image,
} from '@aws-sdk/client-ec2';
import { fromNodeProviderChain } from '@aws-sdk/credential-providers';
import { cache } from '../../../util/cache/package/decorator';
import { Lazy } from '../../../util/lazy';
import * as amazonMachineImageVersioning from '../../versioning/aws-machine-image';
import { Datasource } from '../datasource';
import type { GetReleasesConfig, ReleaseResult } from '../types';
import type { AwsClientConfig, ParsedConfig } from './types';

export class AwsMachineImageDataSource extends Datasource {
static readonly id = 'aws-machine-image';
Expand All @@ -29,16 +35,47 @@ export class AwsMachineImageDataSource extends Datasource {
},
};

private readonly ec2: Lazy<EC2Client>;

private readonly now: number;

constructor() {
super(AwsMachineImageDataSource.id);
this.ec2 = new Lazy(() => new EC2Client({}));
this.now = Date.now();
}

private isAmiFilter(config: Filter | AwsClientConfig): config is Filter {
return 'Name' in config && 'Values' in config;
}

private getEC2Client(config: AwsClientConfig): EC2Client {
const { profile, region } = config;
return new EC2Client({
region,
credentials: fromNodeProviderChain({ profile }),
});
}

private getAmiFilterCommand(filter: Filter[]): DescribeImagesCommand {
return new DescribeImagesCommand({
Filters: filter,
});
}

loadConfig(serializedAmiFilter: string): [Filter[], AwsClientConfig] {
const parsedConfig: ParsedConfig = JSON.parse(serializedAmiFilter);
const filters = [];
let config = {};
for (const elem of parsedConfig) {
if (this.isAmiFilter(elem)) {
// Separate actual AMI filters from aws client config
filters.push(elem);
} else {
// merge config objects if there are multiple
config = Object.assign(config, elem);
}
}
return [filters, config];
}

@cache({
namespace: `datasource-${AwsMachineImageDataSource.id}`,
key: (serializedAmiFilter: string) =>
Expand All @@ -47,10 +84,10 @@ export class AwsMachineImageDataSource extends Datasource {
async getSortedAwsMachineImages(
serializedAmiFilter: string
): Promise<Image[]> {
const cmd = new DescribeImagesCommand({
Filters: JSON.parse(serializedAmiFilter),
});
const matchingImages = await this.ec2.getValue().send(cmd);
const [amiFilter, clientConfig] = this.loadConfig(serializedAmiFilter);
const amiFilterCmd = this.getAmiFilterCommand(amiFilter);
const ec2Client = this.getEC2Client(clientConfig);
const matchingImages = await ec2Client.send(amiFilterCmd);
matchingImages.Images = matchingImages.Images ?? [];
return matchingImages.Images.sort((image1, image2) => {
const ts1 = image1.CreationDate
Expand Down
7 changes: 7 additions & 0 deletions lib/modules/datasource/aws-machine-image/readme.md
Expand Up @@ -61,6 +61,7 @@ Here's an example of using the regex manager:
module.exports = {
regexManagers: [
{
customType: 'regex',
fileMatch: ['.*'],
matchStrings: [
'.*amiFilter=(?<packageName>.*?)\n(.*currentImageName=(?<currentDigest>.*?)\n)?(.*\n)?.*?(?<depName>[a-zA-Z0-9-_:]*)[ ]*?[:|=][ ]*?["|\']?(?<currentValue>ami-[a-z0-9]{17})["|\']?.*',
Expand All @@ -79,6 +80,7 @@ Or as JSON:
'regexManagers':
[
{
'customType': 'regex',
'fileMatch': ['.*'],
'matchStrings':
[
Expand All @@ -102,6 +104,11 @@ my_ami1: ami-02ce3d9008cab69cb
# amiFilter=[{"Name":"owner-id","Values":["602401143452"]},{"Name":"name","Values":["amazon-eks-node-1.20-*"]}]
# currentImageName=unknown
my_ami2: ami-0083e9407e275acf2

# Using custom aws profile and region
# amiFilter=[{"Name":"owner-id","Values":["602401143452"]},{"Name":"name","Values":["amazon-eks-node-1.20-*"]},{"profile":"test","region":"eu-central-1"}]
# currentImageName=unknown
ami = "ami-0083e9407e275acf2"
```

```typescript
Expand Down
8 changes: 8 additions & 0 deletions lib/modules/datasource/aws-machine-image/types.ts
@@ -0,0 +1,8 @@
import type { Filter } from '@aws-sdk/client-ec2';

export interface AwsClientConfig {
region?: string;
profile?: string;
}

export type ParsedConfig = [Filter | AwsClientConfig];
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -142,6 +142,7 @@
"node": "^18.12.0 || >=20.0.0"
},
"dependencies": {
"@aws-sdk/credential-providers": "3.363.0",
"@aws-sdk/client-codecommit": "3.363.0",
"@aws-sdk/client-ec2": "3.363.0",
"@aws-sdk/client-ecr": "3.363.0",
Expand Down
83 changes: 83 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.