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): add eks addon datasource #33272

Open
wants to merge 29 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
fbf6e8b
feat(23410): eks addon refresh
ivankatliarchuk Dec 24, 2024
0364b73
Merge branch 'main' into feat-23410
ivankatliarchuk Dec 24, 2024
d314263
feat(23410): update package json
ivankatliarchuk Dec 24, 2024
7264215
update pnpm-lock.yaml
ivankatliarchuk Dec 24, 2024
33414f4
feat(23410): update as per linter
ivankatliarchuk Dec 25, 2024
e784702
feat(23410): update dependencies
ivankatliarchuk Dec 25, 2024
d0775cc
feat(23410): fix linters
ivankatliarchuk Dec 25, 2024
b071797
feat(23410): fix linters
ivankatliarchuk Dec 25, 2024
dffe55e
feat(23410): added support for versioning
ivankatliarchuk Dec 26, 2024
1a64d1a
feat(23410): added tests for schema. prettier-fix
ivankatliarchuk Dec 26, 2024
d551b46
feat(23410): fix all tests
ivankatliarchuk Dec 26, 2024
8c7a10b
feat(23410): update package.json deps and loc
ivankatliarchuk Dec 26, 2024
6663a43
Merge branch 'main' into feat-23410
ivankatliarchuk Dec 26, 2024
1e59984
Merge branch 'main' into feat-23410
ivankatliarchuk Dec 26, 2024
2c5724a
feat(23410): increase code coverage
ivankatliarchuk Dec 26, 2024
e9a9022
feat(23410): prettier fix
ivankatliarchuk Dec 26, 2024
339b63f
feat(23410): prettier fix
ivankatliarchuk Dec 26, 2024
23c386b
feat(23410): prettier fix
ivankatliarchuk Dec 26, 2024
6824963
feat(23410): cover all lines for aws-eks-addon versioning
ivankatliarchuk Dec 26, 2024
a9b061a
Merge branch 'main' into feat-23410
ivankatliarchuk Dec 26, 2024
36f1ac6
feat(datasource): split versioning
ivankatliarchuk Dec 27, 2024
69046a8
feat(datasource): split versioning
ivankatliarchuk Dec 27, 2024
fddca17
Apply suggestions from code review
ivankatliarchuk Jan 30, 2025
1d9597c
feat(datasource): add eks addon datasource
ivankatliarchuk Jan 30, 2025
7c1ca1b
feat(datasource): add eks addon datasource. merge with main
ivankatliarchuk Jan 30, 2025
5eda62a
feat(datasource): add eks addon datasource. merge with main
ivankatliarchuk Jan 30, 2025
568d2aa
Merge branch 'main' into feat-23410
ivankatliarchuk Jan 31, 2025
faea0d2
feat(datasource): add eks addon datasource. uncomment versioning
ivankatliarchuk Jan 31, 2025
3edbb44
feat(datasource): add eks addon datasource. added default additionalB…
ivankatliarchuk Jan 31, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/modules/datasource/api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ArtifactoryDatasource } from './artifactory';
import { AwsEKSAddonDataSource } from './aws-eks-addon';
import { AwsMachineImageDatasource } from './aws-machine-image';
import { AwsRdsDatasource } from './aws-rds';
import { AzureBicepResourceDatasource } from './azure-bicep-resource';
Expand Down Expand Up @@ -70,6 +71,7 @@ const api = new Map<string, DatasourceApi>();
export default api;

api.set(ArtifactoryDatasource.id, new ArtifactoryDatasource());
api.set(AwsEKSAddonDataSource.id, new AwsEKSAddonDataSource());
api.set(AwsMachineImageDatasource.id, new AwsMachineImageDatasource());
api.set(AwsRdsDatasource.id, new AwsRdsDatasource());
api.set(AzureBicepResourceDatasource.id, new AzureBicepResourceDatasource());
Expand Down
264 changes: 264 additions & 0 deletions lib/modules/datasource/aws-eks-addon/index.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
import {
type AddonInfo,
DescribeAddonVersionsCommand,
type DescribeAddonVersionsResponse,
EKSClient,
} from '@aws-sdk/client-eks';
import { mockClient } from 'aws-sdk-client-mock';
import { getPkgReleases } from '..';
import { logger } from '../../../../test/util';

import { AwsEKSAddonDataSource } from '.';

const datasource = AwsEKSAddonDataSource.id;
const eksMock = mockClient(EKSClient);

function mockDescribeAddonVersionsCommand(
result: DescribeAddonVersionsResponse,
): void {
eksMock.reset();
eksMock.on(DescribeAddonVersionsCommand).resolves(result);
}

function mockDescribeAddonVersionsCommandWithRegion(
result: DescribeAddonVersionsResponse,
): void {
eksMock.reset();
eksMock
.on(DescribeAddonVersionsCommand)
.callsFake(async (input, getClient) => {
const client = getClient();
const region = await client.config.region();
return {
...result,
// put the client region as nextToken
// so that when we assert on the snapshot, we also verify that region from packageName is
// passed to aws client.
nextToken: region,
};
});
}

const addonInfo: AddonInfo = {
addonName: 'vpc-cni',
type: 'networking',
addonVersions: [
{
addonVersion: 'v1.19.0-eksbuild.1',
architecture: ['amd64', 'arm64'],
compatibilities: [
{
clusterVersion: '1.31',
defaultVersion: true,
},
{
clusterVersion: '1.30',
defaultVersion: true,
},
{
clusterVersion: '1.29',
defaultVersion: true,
},
],
},
{
addonVersion: 'v1.18.1-eksbuild.1',
architecture: ['amd64', 'arm64'],
compatibilities: [
{
clusterVersion: '1.30',
defaultVersion: false,
},
],
},
{
addonVersion: 'v1.18.2-eksbuild.1',
architecture: ['amd64', 'arm64'],
compatibilities: [
{
clusterVersion: '1.30',
platformVersions: ['*'],
defaultVersion: true,
},
],
},
],
publisher: 'eks',
owner: 'aws',
};

describe('modules/datasource/aws-eks-addon/index', () => {
describe('getPkgReleases()', () => {
it.each<{ des: string; req: DescribeAddonVersionsResponse }>`
des | req
${'null'} | ${{}}
${'empty'} | ${{ addons: [] }}
${'emptyVersion'} | ${{ addons: [{}] }}
`('returned $des addons to be null', async ({ req }) => {
mockDescribeAddonVersionsCommand(req);
const res = await getPkgReleases({
datasource,
packageName:
'{"kubernetesVersion":"1.30","addonName":"non-existing-addon"}',
});
expect(res).toBeNull();
expect(eksMock.calls()).toHaveLength(1);
expect(eksMock.call(0).args[0].input).toEqual({
kubernetesVersion: '1.30',
addonName: 'non-existing-addon',
maxResults: 1,
});
viceice marked this conversation as resolved.
Show resolved Hide resolved
});

it('with addonName not supplied', async () => {
const res = await getPkgReleases({
datasource,
packageName: '{"kubernetesVersion":"1.30"}',
});
expect(res).toBeNull();
expect(logger.logger.error).toHaveBeenCalledTimes(1);
});

it('with addonName only', async () => {
mockDescribeAddonVersionsCommand({ addons: [addonInfo] });
const res = await getPkgReleases({
datasource,
packageName: '{"addonName":"vpc-cni"}',
});
expect(res?.releases).toHaveLength(3);
ivankatliarchuk marked this conversation as resolved.
Show resolved Hide resolved
expect(res).toEqual({
releases: [
{
version: 'v1.18.1-eksbuild.1',
compatibleWith: ['1.30'],
default: false,
},
{
version: 'v1.18.2-eksbuild.1',
compatibleWith: ['1.30'],
default: true,
},
{
version: 'v1.19.0-eksbuild.1',
compatibleWith: ['1.31', '1.30', '1.29'],
default: true,
},
],
});
expect(eksMock.call(0).args[0].input).toEqual({
addonName: 'vpc-cni',
maxResults: 1,
});
viceice marked this conversation as resolved.
Show resolved Hide resolved
});

it('with addon and profile', async () => {
mockDescribeAddonVersionsCommand({ addons: [] });
await getPkgReleases({
datasource,
packageName: '{"addonName":"vpc-cni-not-exist", "profile":"paradox"}',
});
expect(eksMock.calls()).toHaveLength(1);
});

it('with addon and region', async () => {
mockDescribeAddonVersionsCommand({ addons: [] });
await getPkgReleases({
datasource,
packageName: '{"addonName":"vpc-cni-not-exist", "region":"usa"}',
});
expect(eksMock.calls()).toHaveLength(1);
viceice marked this conversation as resolved.
Show resolved Hide resolved
});

it('with addonName and default only config', async () => {
mockDescribeAddonVersionsCommand({ addons: [addonInfo] });
const res = await getPkgReleases({
datasource,
packageName: '{"addonName":"vpc-cni", "default":true}',
});
expect(eksMock.call(0).args[0].input).toEqual({
viceice marked this conversation as resolved.
Show resolved Hide resolved
addonName: 'vpc-cni',
maxResults: 1,
});
expect(res?.releases).toHaveLength(2);
ivankatliarchuk marked this conversation as resolved.
Show resolved Hide resolved
expect(res).toEqual({
releases: [
{
version: 'v1.18.2-eksbuild.1',
compatibleWith: ['1.30'],
default: true,
},
{
version: 'v1.19.0-eksbuild.1',
compatibleWith: ['1.31', '1.30', '1.29'],
default: true,
},
],
});
});

it('with matched addon to return all versions of the addon', async () => {
const vpcCniAddonInfo: AddonInfo = {
addonName: 'vpc-cni',
type: 'networking',
addonVersions: [
{
addonVersion: 'v1.18.1-eksbuild.1',
architecture: ['amd64', 'arm64'],
compatibilities: [
{
clusterVersion: '1.30',
platformVersions: ['*'],
defaultVersion: false,
},
],
requiresConfiguration: false,
},
{
addonVersion: 'v1.18.2-eksbuild.1',
architecture: ['amd64', 'arm64'],
compatibilities: [
{
clusterVersion: '1.30',
platformVersions: ['*'],
defaultVersion: false,
},
],
requiresConfiguration: false,
},
// a bad addonVersion that's missing the basic fields.
{},
],
publisher: 'eks',
owner: 'aws',
};

mockDescribeAddonVersionsCommandWithRegion({
addons: [vpcCniAddonInfo],
});
const res = await getPkgReleases({
datasource,
packageName:
'{"kubernetesVersion":"1.30","addonName":"vpc-cni","region":"mars-east-1"}',
});
expect(res).toEqual({
releases: [
{
version: 'v1.18.1-eksbuild.1',
compatibleWith: ['1.30'],
default: false,
},
{
version: 'v1.18.2-eksbuild.1',
compatibleWith: ['1.30'],
default: false,
},
],
});
expect(eksMock.call(0).args[0].input).toEqual({
viceice marked this conversation as resolved.
Show resolved Hide resolved
kubernetesVersion: '1.30',
addonName: 'vpc-cni',
maxResults: 1,
});
});
});
});
96 changes: 96 additions & 0 deletions lib/modules/datasource/aws-eks-addon/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import {
type AddonInfo,
type AddonVersionInfo,
type Compatibility,
DescribeAddonVersionsCommand,
type DescribeAddonVersionsCommandInput,
type DescribeAddonVersionsCommandOutput,
EKSClient,
} from '@aws-sdk/client-eks';
import { fromNodeProviderChain } from '@aws-sdk/credential-providers';
import { logger } from '../../../logger';
import { cache } from '../../../util/cache/package/decorator';
// import * as awsEksAddonVersioning from '../../versioning/aws-eks-addon';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// import * as awsEksAddonVersioning from '../../versioning/aws-eks-addon';
import * as awsEksAddonVersioning from '../../versioning/aws-eks-addon';

import { Datasource } from '../datasource';
import type { GetReleasesConfig, ReleaseResult } from '../types';
import { EksAddonsFilter } from './schema';

export class AwsEKSAddonDataSource extends Datasource {
static readonly id = 'aws-eks-addon';

// override readonly defaultVersioning = awsEksAddonVersioning.id;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not using the eks addon versioning?

Suggested change
// override readonly defaultVersioning = awsEksAddonVersioning.id;
override readonly defaultVersioning = awsEksAddonVersioning.id;

override readonly caching = true;
private readonly clients: Record<string, EKSClient> = {};

override readonly defaultConfig: Record<string, unknown> | undefined = {
commitMessageTopic: '{{datasource}}',
commitMessageExtra: '{{currentVersion}} to {{{newVersion}}}',
};

constructor() {
super(AwsEKSAddonDataSource.id);
}

@cache({
namespace: `datasource-${AwsEKSAddonDataSource.id}`,
key: ({ packageName }: GetReleasesConfig) => `getReleases:${packageName}`,
})
async getReleases({
packageName: serializedFilter,
}: GetReleasesConfig): Promise<ReleaseResult | null> {
const res = EksAddonsFilter.safeParse(serializedFilter);
if (!res.success) {
logger.error(
ivankatliarchuk marked this conversation as resolved.
Show resolved Hide resolved
{ err: res.error, serializedFilter },
'Error parsing eks-addons config.',
);
return null;
}

const filter = res.data;
const input: DescribeAddonVersionsCommandInput = {
kubernetesVersion: filter?.kubernetesVersion,
addonName: filter?.addonName,
maxResults: 1,
};

const cmd = new DescribeAddonVersionsCommand(input);
ivankatliarchuk marked this conversation as resolved.
Show resolved Hide resolved
const response: DescribeAddonVersionsCommandOutput =
ivankatliarchuk marked this conversation as resolved.
Show resolved Hide resolved
await this.getClient(filter).send(cmd);
const addons: AddonInfo[] = response.addons ?? [];
return {
releases: addons
.flatMap((addon: AddonInfo): AddonVersionInfo[] | undefined => {
ivankatliarchuk marked this conversation as resolved.
Show resolved Hide resolved
return addon.addonVersions;
})
.map((versionInfo: AddonVersionInfo | undefined) => ({
ivankatliarchuk marked this conversation as resolved.
Show resolved Hide resolved
version: versionInfo?.addonVersion ?? '',
default:
versionInfo?.compatibilities?.some(
(comp: Compatibility): boolean | undefined => comp.defaultVersion,
) ?? false,
compatibleWith: versionInfo?.compatibilities?.flatMap(
(comp: Compatibility): string | undefined => comp.clusterVersion,
),
}))
.filter((release) => release.version && release.version !== '')
.filter((release): boolean => {
ivankatliarchuk marked this conversation as resolved.
Show resolved Hide resolved
if (filter.default) {
return release.default && release.default === filter.default;
}
return true;
}),
};
}

private getClient({ region, profile }: EksAddonsFilter): EKSClient {
const cacheKey = `${region ?? 'default'}#${profile ?? 'default'}`;
if (!(cacheKey in this.clients)) {
this.clients[cacheKey] = new EKSClient({
region: region ?? undefined,
ivankatliarchuk marked this conversation as resolved.
Show resolved Hide resolved
credentials: fromNodeProviderChain(profile ? { profile } : undefined),
});
}
return this.clients[cacheKey];
}
}
Loading
Loading