diff --git a/docs/guides/functions.md b/docs/guides/functions.md index dc1a469b8..230cb0a36 100644 --- a/docs/guides/functions.md +++ b/docs/guides/functions.md @@ -298,11 +298,12 @@ In service configuration, images can be configured via `provider.ecr.images`. To Additionally, you can define arguments that will be passed to the `docker build` command via the following properties: - `buildArgs`: With the `buildArgs` property, you can define arguments that will be passed to `docker build` command with `--build-arg` flag. They might be later referenced via `ARG` within your `Dockerfile`. (See [Documentation](https://docs.docker.com/engine/reference/builder/#arg)) +- `buildOptions`: With the `buildOptions` property, you can define options that will be passed to the `docker build` command. (See [Documentation](https://docs.docker.com/engine/reference/commandline/image_build/#options)) - `cacheFrom`: The `cacheFrom` property can be used to specify which images to use as a source for layer caching in the `docker build` command with `--cache-from` flag. (See [Documentation](https://docs.docker.com/engine/reference/builder/#usage)) - `platform`: The `platform` property can be used to specify the architecture target in the `docker build` command with the `--platform` flag. If not specified, Docker will build for your computer's architecture by default. AWS Lambda typically uses `x86` architecture unless otherwise specified in the Lambda's runtime settings. In order to avoid runtime errors when building on an ARM-based machine (e.g. Apple M1 Mac), `linux/amd64` must be used here. The options for this flag are `linux/amd64` (`x86`-based Lambdas), `linux/arm64` (`arm`-based Lambdas), or `windows/amd64`. (See [Documentation](https://docs.docker.com/engine/reference/builder/#from)) - `provenance` Use the `provenance` property to disable multi-architecture manifest generated from BuildKit or `docker buildx`, allows the architecture specified in `platform` to be recognized by AWS Lambda during deployment. -When `uri` is defined for an image, `buildArgs`, `cacheFrom`, and `platform` cannot be defined. +When `uri` is defined for an image, `buildArgs`, `buildOptions`, `cacheFrom`, and `platform` cannot be defined. Example configuration diff --git a/docs/guides/serverless.yml.md b/docs/guides/serverless.yml.md index 76605699e..a5eb5e1ad 100644 --- a/docs/guides/serverless.yml.md +++ b/docs/guides/serverless.yml.md @@ -390,6 +390,15 @@ provider: file: Dockerfile.dev buildArgs: STAGE: ${sls:stage} + buildOptions: + [ + '--tag', + 'v1.0.0', + '--add-host', + 'example.com:0.0.0.0', + '--ssh', + 'default=/path/to/private/key/id_rsa', + ] cacheFrom: - my-image:latest ``` diff --git a/lib/plugins/aws/provider.js b/lib/plugins/aws/provider.js index ae9402512..e182e28be 100644 --- a/lib/plugins/aws/provider.js +++ b/lib/plugins/aws/provider.js @@ -1144,6 +1144,7 @@ class AwsProvider { path: { type: 'string' }, file: { type: 'string' }, buildArgs: { type: 'object', additionalProperties: { type: 'string' } }, + buildOptions: { type: 'array', items: { type: 'string' } }, cacheFrom: { type: 'array', items: { type: 'string' } }, platform: { type: 'string' }, provenance: { type: 'string' }, @@ -2331,6 +2332,7 @@ Object.defineProperties( imagePath, imageFilename, buildArgs, + buildOptions, cacheFrom, platform, provenance, @@ -2377,6 +2379,7 @@ Object.defineProperties( pathToDockerfile, ...buildArgsArr, ...cacheFromArr, + ...buildOptions, imagePath, ]; @@ -2515,6 +2518,7 @@ Object.defineProperties( const { imageUri, imageName } = resolveImageUriOrName(); const defaultDockerfile = 'Dockerfile'; const defaultBuildArgs = {}; + const defaultBuildOptions = []; const defaultCacheFrom = []; const defaultScanOnPush = false; const defaultPlatform = ''; @@ -2561,6 +2565,12 @@ Object.defineProperties( 'ECR_IMAGE_BOTH_URI_AND_BUILDARGS_DEFINED_ERROR' ); } + if (imageDefinedInProvider.uri && imageDefinedInProvider.buildOptions) { + throw new ServerlessError( + `You can't use the "buildOptions" and the "uri" properties at the same time "${imageName}"`, + 'ECR_IMAGE_URI_AND_BUILDOPTIONS_DEFINED_ERROR' + ); + } if (imageDefinedInProvider.uri && imageDefinedInProvider.cacheFrom) { throw new ServerlessError( `The "cacheFrom" property cannot be used with "uri" property "${imageName}"`, @@ -2585,6 +2595,7 @@ Object.defineProperties( imagePath: imageDefinedInProvider.path, imageFilename: imageDefinedInProvider.file || defaultDockerfile, buildArgs: imageDefinedInProvider.buildArgs || defaultBuildArgs, + buildOptions: imageDefinedInProvider.buildOptions || defaultBuildOptions, cacheFrom: imageDefinedInProvider.cacheFrom || defaultCacheFrom, platform: imageDefinedInProvider.platform || defaultPlatform, provenance: imageDefinedInProvider.provenance || defaultProvenance, @@ -2601,6 +2612,7 @@ Object.defineProperties( imagePath: imageDefinedInProvider, imageFilename: defaultDockerfile, buildArgs: imageDefinedInProvider.buildArgs || defaultBuildArgs, + buildOptions: imageDefinedInProvider.buildOptions || defaultBuildOptions, cacheFrom: imageDefinedInProvider.cacheFrom || defaultCacheFrom, platform: imageDefinedInProvider.platform || defaultPlatform, provenance: imageDefinedInProvider.provenance || defaultProvenance, diff --git a/test/unit/lib/plugins/aws/provider.test.js b/test/unit/lib/plugins/aws/provider.test.js index 2477cff37..1dba40617 100644 --- a/test/unit/lib/plugins/aws/provider.test.js +++ b/test/unit/lib/plugins/aws/provider.test.js @@ -795,6 +795,35 @@ aws_secret_access_key = CUSTOMSECRET }); describe('when resolving images', () => { + it('should fail if `functions[].image` references image with both buildOptions and uri', async () => { + await expect( + runServerless({ + fixture: 'function', + command: 'package', + configExt: { + provider: { + ecr: { + images: { + invalidimage: { + buildOptions: ['--no-cache'], + uri: '000000000000.dkr.ecr.sa-east-1.amazonaws.com/test-lambda-docker@sha256:6bb600b4d6e1d7cf521097177dd0c4e9ea373edb91984a505333be8ac9455d38', + }, + }, + }, + }, + functions: { + fnProviderInvalidImage: { + image: 'invalidimage', + }, + }, + }, + }) + ).to.be.eventually.rejected.and.have.property( + 'code', + 'ECR_IMAGE_URI_AND_BUILDOPTIONS_DEFINED_ERROR' + ); + }); + it('should fail if `functions[].image` references image with both path and uri', async () => { await expect( runServerless({ @@ -1659,6 +1688,65 @@ aws_secret_access_key = CUSTOMSECRET ]); }); + it('should work correctly when image is defined with `buildOptions` set', async () => { + const awsRequestStubMap = { + ...baseAwsRequestStubMap, + ECR: { + ...baseAwsRequestStubMap.ECR, + describeRepositories: describeRepositoriesStub.resolves({ + repositories: [{ repositoryUri }], + }), + createRepository: createRepositoryStub, + }, + }; + const { + awsNaming, + cfTemplate, + fixtureData: { servicePath: serviceDir }, + } = await runServerless({ + fixture: 'ecr', + command: 'package', + awsRequestStubMap, + modulesCacheStub, + configExt: { + provider: { + ecr: { + images: { + baseimage: { + path: './', + file: 'Dockerfile.dev', + buildOptions: ['--ssh', 'default=/path/to/file'], + }, + }, + }, + }, + }, + }); + + const functionCfLogicalId = awsNaming.getLambdaLogicalId('foo'); + const functionCfConfig = cfTemplate.Resources[functionCfLogicalId].Properties; + const versionCfConfig = Object.values(cfTemplate.Resources).find( + (resource) => + resource.Type === 'AWS::Lambda::Version' && + resource.Properties.FunctionName.Ref === functionCfLogicalId + ).Properties; + + expect(functionCfConfig.Code.ImageUri).to.deep.equal(`${repositoryUri}@sha256:${imageSha}`); + expect(versionCfConfig.CodeSha256).to.equal(imageSha); + expect(describeRepositoriesStub).to.be.calledOnce; + expect(createRepositoryStub.notCalled).to.be.true; + expect(spawnExtStub).to.be.calledWith('docker', [ + 'build', + '-t', + `${awsNaming.getEcrRepositoryName()}:baseimage`, + '-f', + path.join(serviceDir, 'Dockerfile.dev'), + '--ssh', + 'default=/path/to/file', + './', + ]); + }); + it('should work correctly when image is defined with `buildArgs` set', async () => { const awsRequestStubMap = { ...baseAwsRequestStubMap,