import type { DescribeImagesResult, Image } from '@aws-sdk/client-ec2'; import { DescribeImagesCommand, EC2Client } from '@aws-sdk/client-ec2'; import { mockClient } from 'aws-sdk-client-mock'; import * as hostRules from '../../../util/host-rules.ts'; import { getDigest, getPkgReleases } from '../index.ts'; import { AwsMachineImageDatasource } from './index.ts'; const datasource = AwsMachineImageDatasource.id; const ec2Mock = mockClient(EC2Client); /** * Testdata for mock implementation of EC2Client * image1 to image3 from oldest to newest image */ const image1: Image = { Architecture: 'x86_64', CreationDate: '2021-08-13T17:47:12.000Z', ImageId: 'ami-02ce3d9008cab69cb', ImageLocation: 'amazon/amazon-eks-node-1.21-v20210813', ImageType: 'machine', Public: true, OwnerId: '602401143452', PlatformDetails: 'Linux/UNIX', UsageOperation: 'RunInstances', State: 'available', BlockDeviceMappings: [ { DeviceName: '/dev/xvda', Ebs: { DeleteOnTermination: true, SnapshotId: 'snap-0546beb61976e8017', VolumeSize: 20, VolumeType: 'gp2', Encrypted: false, }, }, ], Description: 'EKS Kubernetes Worker AMI with AmazonLinux2 image, (k8s: 1.21.2, docker: 19.03.13ce-1.amzn2, containerd: 1.4.6-2.amzn2)', EnaSupport: true, Hypervisor: 'xen', ImageOwnerAlias: 'amazon', Name: 'amazon-eks-node-1.21-v20210813', RootDeviceName: '/dev/xvda', RootDeviceType: 'ebs', SriovNetSupport: 'simple', VirtualizationType: 'hvm', }; const image2: Image = { Architecture: 'x86_64', CreationDate: '2021-08-26T19:31:41.000Z', DeprecationTime: '2021-08-14T17:47:12.000Z', ImageId: 'ami-020d418c09883b165', ImageLocation: 'amazon/amazon-eks-node-1.21-v20210826', ImageType: 'machine', Public: true, OwnerId: '602401143452', PlatformDetails: 'Linux/UNIX', UsageOperation: 'RunInstances', State: 'available', BlockDeviceMappings: [ { DeviceName: '/dev/xvda', Ebs: { DeleteOnTermination: true, SnapshotId: 'snap-01ba16a8ec8087603', VolumeSize: 20, VolumeType: 'gp2', Encrypted: false, }, }, ], Description: 'EKS Kubernetes Worker AMI with AmazonLinux2 image, (k8s: 1.21.2, docker: 19.03.13ce-1.amzn2, containerd: 1.4.6-2.amzn2)', EnaSupport: true, Hypervisor: 'xen', ImageOwnerAlias: 'amazon', Name: 'amazon-eks-node-1.21-v20210826', RootDeviceName: '/dev/xvda', RootDeviceType: 'ebs', SriovNetSupport: 'simple', VirtualizationType: 'hvm', }; const image3: Image = { Architecture: 'x86_64', CreationDate: '2021-09-14T22:00:24.000Z', ImageId: 'ami-05f83986b0fe58ada', ImageLocation: 'amazon/amazon-eks-node-1.21-v20210914', ImageType: 'machine', Public: true, OwnerId: '602401143452', PlatformDetails: 'Linux/UNIX', UsageOperation: 'RunInstances', State: 'available', BlockDeviceMappings: [ { DeviceName: '/dev/xvda', Ebs: { DeleteOnTermination: true, SnapshotId: 'snap-0c6f79c3983fd8e1a', VolumeSize: 20, VolumeType: 'gp2', Encrypted: false, }, }, ], Description: 'EKS Kubernetes Worker AMI with AmazonLinux2 image, (k8s: 1.21.2, docker: 19.03.13ce-1.amzn2, containerd: 1.4.6-2.amzn2)', EnaSupport: true, Hypervisor: 'xen', ImageOwnerAlias: 'amazon', Name: 'amazon-eks-node-1.21-v20210914', RootDeviceName: '/dev/xvda', RootDeviceType: 'ebs', SriovNetSupport: 'simple', VirtualizationType: 'hvm', }; const mock3Images: DescribeImagesResult = { Images: [image3, image1, image2], }; const mock1Image: DescribeImagesResult = { Images: [image3], }; const mockEmpty: DescribeImagesResult = {}; function mockDescribeImagesCommand(result: DescribeImagesResult): void { ec2Mock.reset(); ec2Mock.on(DescribeImagesCommand).resolves(result); } describe('modules/datasource/aws-machine-image/index', () => { beforeEach(() => { hostRules.clear(); }); describe('getSortedAwsMachineImages()', () => { it('with 3 returned images', async () => { mockDescribeImagesCommand(mock3Images); const ec2DataSource = new AwsMachineImageDatasource(); const res = await ec2DataSource.getSortedAwsMachineImages( '[{"Name":"owner-id","Values":["602401143452"]},{"Name":"name","Values":["3images"]}]', ); expect(res).toStrictEqual([image1, image2, image3]); expect(ec2Mock.calls()).toHaveLength(1); }); it('with 1 returned image', async () => { mockDescribeImagesCommand(mock1Image); const ec2DataSource = new AwsMachineImageDatasource(); const res = await ec2DataSource.getSortedAwsMachineImages( '[{"Name":"owner-id","Values":["602401143452"]},{"Name":"name","Values":["1image"]}]', ); expect(res).toStrictEqual([image3]); expect(ec2Mock.calls()).toHaveLength(1); }); it('prefers host rule credentials over the configured profile', async () => { hostRules.add({ hostType: datasource, username: 'access-key-id', password: 'secret-access-key', token: 'session-token', }); mockDescribeImagesCommand(mock1Image); const ec2DataSource = new AwsMachineImageDatasource(); await ec2DataSource.getSortedAwsMachineImages( '[{"Name":"name","Values":["host-rule-credentials"]},{"profile":"ignored-profile"}]', ); const ec2 = ec2Mock.call(0).thisValue as EC2Client; await expect(ec2.config.credentials()).resolves.toEqual({ accessKeyId: 'access-key-id', secretAccessKey: 'secret-access-key', sessionToken: 'session-token', $source: { CREDENTIALS_CODE: 'e', }, }); }); it('without returned images', async () => { mockDescribeImagesCommand(mockEmpty); const ec2DataSource = new AwsMachineImageDatasource(); const res = await ec2DataSource.getSortedAwsMachineImages( '[{"Name":"owner-id","Values":["602401143452"]},{"Name":"name","Values":["noiamge"]}]', ); expect(res).toStrictEqual([]); expect(ec2Mock.calls()).toHaveLength(1); }); }); describe('getDigest()', () => { it('without newValue, without returned images to be null', async () => { mockDescribeImagesCommand(mockEmpty); const res = await getDigest({ datasource, packageName: '[{"Name":"owner-id","Values":["602401143452"]},{"Name":"name","Values":["without newValue, without returned images to be null"]}]', }); expect(res).toBeNull(); }); it('without newValue, with one matching image to return that image', async () => { mockDescribeImagesCommand(mock1Image); const res = await getDigest({ datasource, packageName: '[{"Name":"owner-id","Values":["602401143452"]},{"Name":"name","Values":["without newValue, with one matching image to return that image"]}]', }); expect(res).toStrictEqual(image3.Name); }); it('without newValue, with newest image missing a name to be null', async () => { const { Name, ...imageWithoutName } = image3; mockDescribeImagesCommand({ Images: [imageWithoutName] }); const res = await getDigest({ datasource, packageName: '[{"Name":"owner-id","Values":["602401143452"]}]', }); expect(res).toBeNull(); }); it('without newValue, with 3 matching image to return the newest image', async () => { mockDescribeImagesCommand(mock3Images); const res = await getDigest({ datasource, packageName: '[{"Name":"owner-id","Values":["602401143452"]},{"Name":"name","Values":["without newValue, with 3 matching image to return the newest image"]}]', }); expect(res).toStrictEqual(image3.Name); }); it('with matching newValue, with 3 matching image to return the matching image', async () => { mockDescribeImagesCommand(mock3Images); const res = await getDigest( { datasource, packageName: '[{"Name":"owner-id","Values":["602401143452"]},{"Name":"name","Values":["with matching newValue, with 3 matching image to return the matching image"]}]', }, image1.ImageId, ); expect(res).toStrictEqual(image1.Name); }); it('with not matching newValue, with 3 matching images to return the matching image', async () => { mockDescribeImagesCommand(mock3Images); const res = await getDigest( { datasource, packageName: '[{"Name":"owner-id","Values":["602401143452"]},{"Name":"name","Values":["with not matching newValue, with 3 matching images to return the matching image"]}]', }, 'will never match', ); expect(res).toBeNull(); }); }); describe('getPkgReleases()', () => { it('without returned images to be null', async () => { mockDescribeImagesCommand(mockEmpty); const res = await getPkgReleases({ datasource, packageName: '[{"Name":"owner-id","Values":["602401143452"]},{"Name":"name","Values":["without returned images to be null"]}]', }); expect(res).toBeNull(); }); it('with one matching image to return that image', async () => { mockDescribeImagesCommand(mock1Image); const res = await getPkgReleases({ datasource, packageName: '[{"Name":"owner-id","Values":["602401143452"]},{"Name":"name","Values":["with one matching image to return that image"]}]', }); expect(res).toEqual({ releases: [ { isDeprecated: false, newDigest: image3.Name, releaseTimestamp: image3.CreationDate, version: image3.ImageId, }, ], }); }); it('with one deprecated matching image to return that image', async () => { mockDescribeImagesCommand({ Images: [image2] }); const res = await getPkgReleases({ datasource, packageName: '[{"Name":"owner-id","Values":["602401143452"]},{"Name":"name","Values":["with one deprecated matching image to return that image"]}]', }); expect(res).toEqual({ releases: [ { isDeprecated: true, newDigest: image2.Name, releaseTimestamp: image2.CreationDate, version: image2.ImageId, }, ], }); }); it('with 3 matching images returns all images as releases, not just the newest', async () => { mockDescribeImagesCommand(mock3Images); const res = await getPkgReleases({ datasource, packageName: '[{"Name":"owner-id","Values":["602401143452"]},{"Name":"name","Values":["with 3 matching images returns all"]}]', }); expect(res).toEqual({ releases: [ { isDeprecated: false, newDigest: image1.Name, releaseTimestamp: image1.CreationDate, version: image1.ImageId, }, { isDeprecated: true, newDigest: image2.Name, releaseTimestamp: image2.CreationDate, version: image2.ImageId, }, { isDeprecated: false, newDigest: image3.Name, releaseTimestamp: image3.CreationDate, version: image3.ImageId, }, ], }); }); }); 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', }, ]); }); }); });