-
Notifications
You must be signed in to change notification settings - Fork 4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(assets): docker images from tar file (#15438)
Allows to use an existing tarball for an container image. It loads the image from the tarball instead of building the image from a Dockerfile. Fixes #15419 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
- Loading branch information
Showing
7 changed files
with
281 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
export * from './image-asset'; | ||
export * from './tarball-asset'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import * as fs from 'fs'; | ||
import * as path from 'path'; | ||
import * as ecr from '@aws-cdk/aws-ecr'; | ||
import { AssetStaging, Stack, Stage } from '@aws-cdk/core'; | ||
import { Construct } from 'constructs'; | ||
|
||
// keep this import separate from other imports to reduce chance for merge conflicts with v2-main | ||
// eslint-disable-next-line | ||
import { IAsset } from '@aws-cdk/assets'; | ||
// keep this import separate from other imports to reduce chance for merge conflicts with v2-main | ||
// eslint-disable-next-line no-duplicate-imports, import/order | ||
import { Construct as CoreConstruct } from '@aws-cdk/core'; | ||
|
||
/** | ||
* Options for TarballImageAsset | ||
*/ | ||
export interface TarballImageAssetProps { | ||
/** | ||
* Path to the tarball. | ||
*/ | ||
readonly tarballFile: string; | ||
} | ||
|
||
/** | ||
* An asset that represents a Docker image. | ||
* | ||
* The image will loaded from an existing tarball and uploaded to an ECR repository. | ||
*/ | ||
export class TarballImageAsset extends CoreConstruct implements IAsset { | ||
/** | ||
* The full URI of the image (including a tag). Use this reference to pull | ||
* the asset. | ||
*/ | ||
public imageUri: string; | ||
|
||
/** | ||
* Repository where the image is stored | ||
*/ | ||
public repository: ecr.IRepository; | ||
|
||
/** | ||
* A hash of the source of this asset, which is available at construction time. As this is a plain | ||
* string, it can be used in construct IDs in order to enforce creation of a new resource when | ||
* the content hash has changed. | ||
* @deprecated use assetHash | ||
*/ | ||
public readonly sourceHash: string; | ||
|
||
/** | ||
* A hash of this asset, which is available at construction time. As this is a plain string, it | ||
* can be used in construct IDs in order to enforce creation of a new resource when the content | ||
* hash has changed. | ||
*/ | ||
public readonly assetHash: string; | ||
|
||
constructor(scope: Construct, id: string, props: TarballImageAssetProps) { | ||
super(scope, id); | ||
|
||
if (!fs.existsSync(props.tarballFile)) { | ||
throw new Error(`Cannot find file at ${props.tarballFile}`); | ||
} | ||
|
||
const stagedTarball = new AssetStaging(scope, 'Staging', { sourcePath: props.tarballFile }); | ||
|
||
this.sourceHash = stagedTarball.assetHash; | ||
this.assetHash = stagedTarball.assetHash; | ||
|
||
const stage = Stage.of(this); | ||
const relativePathInOutDir = stage ? path.relative(stage.assetOutdir, stagedTarball.absoluteStagedPath) : stagedTarball.absoluteStagedPath; | ||
|
||
const stack = Stack.of(this); | ||
const location = stack.synthesizer.addDockerImageAsset({ | ||
sourceHash: stagedTarball.assetHash, | ||
executable: [ | ||
'sh', | ||
'-c', | ||
`docker load -i ${relativePathInOutDir} | sed "s/Loaded image: //g"`, | ||
], | ||
}); | ||
|
||
this.repository = ecr.Repository.fromRepositoryName(this, 'Repository', location.repositoryName); | ||
this.imageUri = location.imageUri; | ||
} | ||
} | ||
|
Empty file.
150 changes: 150 additions & 0 deletions
150
packages/@aws-cdk/aws-ecr-assets/test/tarball-asset.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
import * as fs from 'fs'; | ||
import * as path from 'path'; | ||
import { expect as ourExpect, haveResource } from '@aws-cdk/assert-internal'; | ||
import * as iam from '@aws-cdk/aws-iam'; | ||
import * as cxschema from '@aws-cdk/cloud-assembly-schema'; | ||
import { App, Stack } from '@aws-cdk/core'; | ||
import * as cxapi from '@aws-cdk/cx-api'; | ||
import { testFutureBehavior } from 'cdk-build-tools/lib/feature-flag'; | ||
import { TarballImageAsset } from '../lib'; | ||
|
||
/* eslint-disable quote-props */ | ||
|
||
const flags = { [cxapi.NEW_STYLE_STACK_SYNTHESIS_CONTEXT]: true }; | ||
|
||
describe('image asset', () => { | ||
testFutureBehavior('test instantiating Asset Image', flags, App, (app) => { | ||
// GIVEN | ||
const stack = new Stack(app); | ||
const assset = new TarballImageAsset(stack, 'Image', { | ||
tarballFile: __dirname + '/demo-tarball/empty.tar', | ||
}); | ||
|
||
// WHEN | ||
const asm = app.synth(); | ||
|
||
// THEN | ||
const manifestArtifact = getAssetManifest(asm); | ||
const manifest = readAssetManifest(manifestArtifact); | ||
|
||
expect(Object.keys(manifest.files ?? {}).length).toBe(1); | ||
expect(Object.keys(manifest.dockerImages ?? {}).length).toBe(1); | ||
|
||
expect(manifest.dockerImages?.[assset.assetHash]?.destinations?.['current_account-current_region']).toStrictEqual( | ||
{ | ||
assumeRoleArn: 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-image-publishing-role-${AWS::AccountId}-${AWS::Region}', | ||
imageTag: assset.assetHash, | ||
repositoryName: 'cdk-hnb659fds-container-assets-${AWS::AccountId}-${AWS::Region}', | ||
}, | ||
); | ||
|
||
expect(manifest.dockerImages?.[assset.assetHash]?.source).toStrictEqual( | ||
{ | ||
executable: [ | ||
'sh', | ||
'-c', | ||
`docker load -i asset.${assset.assetHash}.tar | sed "s/Loaded image: //g"`, | ||
], | ||
}, | ||
); | ||
}); | ||
|
||
testFutureBehavior('asset.repository.grantPull can be used to grant a principal permissions to use the image', flags, App, (app) => { | ||
// GIVEN | ||
const stack = new Stack(app); | ||
const user = new iam.User(stack, 'MyUser'); | ||
const asset = new TarballImageAsset(stack, 'Image', { | ||
tarballFile: 'test/demo-tarball/empty.tar', | ||
}); | ||
|
||
// WHEN | ||
asset.repository.grantPull(user); | ||
|
||
// THEN | ||
ourExpect(stack).to(haveResource('AWS::IAM::Policy', { | ||
PolicyDocument: { | ||
'Statement': [ | ||
{ | ||
'Action': [ | ||
'ecr:BatchCheckLayerAvailability', | ||
'ecr:GetDownloadUrlForLayer', | ||
'ecr:BatchGetImage', | ||
], | ||
'Effect': 'Allow', | ||
'Resource': { | ||
'Fn::Join': [ | ||
'', | ||
[ | ||
'arn:', | ||
{ | ||
'Ref': 'AWS::Partition', | ||
}, | ||
':ecr:', | ||
{ | ||
'Ref': 'AWS::Region', | ||
}, | ||
':', | ||
{ | ||
'Ref': 'AWS::AccountId', | ||
}, | ||
':repository/', | ||
{ | ||
'Fn::Sub': 'cdk-hnb659fds-container-assets-${AWS::AccountId}-${AWS::Region}', | ||
}, | ||
], | ||
], | ||
}, | ||
}, | ||
{ | ||
'Action': 'ecr:GetAuthorizationToken', | ||
'Effect': 'Allow', | ||
'Resource': '*', | ||
}, | ||
], | ||
'Version': '2012-10-17', | ||
}, | ||
'PolicyName': 'MyUserDefaultPolicy7B897426', | ||
'Users': [ | ||
{ | ||
'Ref': 'MyUserDC45028B', | ||
}, | ||
], | ||
})); | ||
}); | ||
|
||
testFutureBehavior('docker directory is staged if asset staging is enabled', flags, App, (app) => { | ||
const stack = new Stack(app); | ||
const image = new TarballImageAsset(stack, 'MyAsset', { | ||
tarballFile: 'test/demo-tarball/empty.tar', | ||
}); | ||
|
||
const session = app.synth(); | ||
|
||
expect(fs.existsSync(path.join(session.directory, `asset.${image.assetHash}.tar`))).toBeDefined(); | ||
}); | ||
|
||
test('fails if the file does not exist', () => { | ||
const stack = new Stack(); | ||
// THEN | ||
expect(() => { | ||
new TarballImageAsset(stack, 'MyAsset', { | ||
tarballFile: `/does/not/exist/${Math.floor(Math.random() * 9999)}`, | ||
}); | ||
}).toThrow(/Cannot find file at/); | ||
|
||
}); | ||
}); | ||
|
||
function isAssetManifest(x: cxapi.CloudArtifact): x is cxapi.AssetManifestArtifact { | ||
return x instanceof cxapi.AssetManifestArtifact; | ||
} | ||
|
||
function getAssetManifest(asm: cxapi.CloudAssembly): cxapi.AssetManifestArtifact { | ||
const manifestArtifact = asm.artifacts.filter(isAssetManifest)[0]; | ||
if (!manifestArtifact) { throw new Error('no asset manifest in assembly'); } | ||
return manifestArtifact; | ||
} | ||
|
||
function readAssetManifest(manifestArtifact: cxapi.AssetManifestArtifact): cxschema.AssetManifest { | ||
return JSON.parse(fs.readFileSync(manifestArtifact.file, { encoding: 'utf-8' })); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters