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

Lambda Parameters are missing when deploying with CodeDeploy #3434

Closed
1 of 5 tasks
jonny-rimek opened this issue Jul 25, 2019 · 20 comments
Closed
1 of 5 tasks

Lambda Parameters are missing when deploying with CodeDeploy #3434

jonny-rimek opened this issue Jul 25, 2019 · 20 comments
Labels
needs-triage This issue or PR still needs to be triaged.

Comments

@jonny-rimek
Copy link

Note: for support questions, please first reference our documentation, then use Stackoverflow. This repository's issues are intended for feature requests and bug reports.

  • I'm submitting a ...

    • 🪲 bug report
    • 🚀 feature request
    • 📚 construct library gap
    • ☎️ security issue or vulnerability => Please see policy
    • ❓ support request => Please see note at the top of this template.
  • What is the current behavior?
    If the current behavior is a 🪲bug🪲: Please provide the steps to reproduce

Can't deploy a golang-lambda function through CodeDeploy. CloudFormationCreateUpdateStackAction and CloudFormationCreateReplaceChangeSetAction + CloudFormationExecuteChangeSetAction both don't work. But when I deploy with cdk deploy from my local machine everything works as intended

Error Message:

Parameters: [getPictureCodeS3VersionKey7D69AA08, getPictureCodeArtifactHash67916A64, getPictureCodeS3Bucket4AE043CC] must have values (Service: AmazonCloudFormation; Status Code: 400; Error Code: ValidationError; Request ID: f89c844e-aeed-11e9-a28d-0f1fe7196728)

I double checked the generated cfn template and it contains the custom parameters for the lambda

  "Parameters": {
    "getPictureCodeS3Bucket4AE043CC": {
      "Type": "String",
      "Description": "S3 bucket for asset \"FirstCdkStackStack/getPicture/Code\""
    },
    "getPictureCodeS3VersionKey7D69AA08": {
      "Type": "String",
      "Description": "S3 key for asset version \"FirstCdkStackStack/getPicture/Code\""
    },
    "getPictureCodeArtifactHash67916A64": {
      "Type": "String",
      "Description": "Artifact hash for asset \"FirstCdkStackStack/getPicture/Code\""
    }
  }

this is my cdk code. I added all the files to the artifacts in case something was missing.

	const getPicture = new lambda.Function(this, 'getPicture', {
		code: lambda.Code.asset('handler'),
		handler: 'main',
		runtime: lambda.Runtime.GO_1_X,
	})

	const repo = new codecommit.Repository(this, 'Repo', {
		repositoryName: 'FirstCDKStackStack'
	});

	const sourceOutput = new codepipeline.Artifact();

	const sourceAction = new codepipeline_actions.CodeCommitSourceAction({
		actionName: 'CodeCommit',
		repository: repo,
		output: sourceOutput,
	});
	
	var project = new codebuild.PipelineProject(this, 'MyProject2', {
		buildSpec: codebuild.BuildSpec.fromObject({
			version: '0.2',
			phases: {
				install: {
					commands: [
						"go get github.com/aws/aws-lambda-go/events",
						"go get github.com/aws/aws-lambda-go/lambda",
					]
				},
				build: {
					commands: [
						"go build -o handler/main handler/get-picture.go",
						"ls",
					],
				},
			},
			artifacts: {
				files: ["*", "bin/*", "lib/*", "handler/*" ],
			}
		}),
	});

	const goBuildOutput = new codepipeline.Artifact();

	const goBuildAction = new codepipeline_actions.CodeBuildAction({
		actionName: 'goBuild',
		project,
		input: sourceOutput,
		outputs: [goBuildOutput], 
	});

	var project = new codebuild.PipelineProject(this, 'MyProject', {
		buildSpec: codebuild.BuildSpec.fromObject({
			version: '0.2',
			phases: {
				install: {
					commands: [
						"npm install",
						"npm install -g cdk",
						"npm install -g typescript",
					]
				},
				build: {
					commands: [
						"tsc",
						"cdk synth",
					],
				},
			},
			artifacts: {
				files: ["*", "bin/*", "lib/*", "cdk.out/*", "handler/*" ],
			}
		}),
	});

	const cfnBuildOutput = new codepipeline.Artifact();

	const cfnBuildAction = new codepipeline_actions.CodeBuildAction({
		actionName: 'cfnBuild',
		project,
		input: goBuildOutput,
		outputs: [cfnBuildOutput], 
	});
	const pipeline = new codepipeline.Pipeline(this, 'MyPipeline', {
		stages: [
			{
			stageName: 'source',
			actions: [sourceAction],
			},
			{
			stageName: 'build-go',
			actions: [goBuildAction],
			},
			{
			stageName: 'build-cfn',
			actions: [cfnBuildAction],
			},
		],
	});

	const changeSetOutput = new codepipeline.Artifact();

	pipeline.addStage({
		stageName: 'changeset',
		actions: [
			new codepipeline_actions.CloudFormationCreateUpdateStackAction({
				templatePath: cfnBuildOutput.atPath('cdk.out/FirstCdkStackStack.template.json'),
				adminPermissions: true,
				stackName: 'FirstCdkStackStack',
				actionName: 'create_update_stack',
				output: changeSetOutput,
			})
		]
	})

//this doesnt work either
// pipeline.addStage({
	// 	stageName: 'changeset',
	// 	actions: [
	// 		new codepipeline_actions.CloudFormationCreateReplaceChangeSetAction({
	// 			templatePath: cfnBuildOutput.atPath('cdk.out/FirstCdkStackStack.template.json'),
	// 			adminPermissions: true,
	// 			stackName: 'FirstCdkStackStack',
	// 			actionName: 'create_change_set',
	// 			output: changeSetOutput,
	// 			changeSetName: 'changeset',
	// 		})
	// 	]
	// })

	// pipeline.addStage({
	// 	stageName: 'deploy',
	// 	actions: [
	// 		new codepipeline_actions.CloudFormationExecuteChangeSetAction({
	// 			stackName: 'FirstCdkStackStack',
	// 			actionName: 'execute_change_set',
	// 			changeSetName: 'changeset',
	// 		})
	// 	]
	// })

  }
}
  • What is the expected behavior (or behavior of feature suggested)?

CloudDeploy should behave like cdk deploy

  • What is the motivation / use case for changing the behavior or adding this feature?
  • Please tell us about your environment:

    • CDK CLI Version: 1.1.0 (build 1a11e96)
    • Module Version: xx.xx.xx
    • OS: Windows10 locally, the default image in CodeBuild
    • Language: TypeScript
  • Other information (e.g. detailed explanation, stacktraces, related issues, suggestions how to fix, links for us to have context, eg. associated pull-request, stackoverflow, gitter, etc)

DQIkGGp.zip this is the output that gets passed to the deploy stage.

@jonny-rimek jonny-rimek added the needs-triage This issue or PR still needs to be triaged. label Jul 25, 2019
@skinny85
Copy link
Contributor

@jimbo5644535 thanks for opening the issue. The parameters you see are because you're using assets as your Lambda code. Which means you need to provide values for them in the CloudFormation Actions that are doing the deployment.

See our docs for an example: https://docs.aws.amazon.com/cdk/latest/guide/codepipeline_example.html?shortFooter=true

Thanks,
Adam

@jonny-rimek
Copy link
Author

Thanks, I wasn't aware of this page. I tried to piece it together from those code snippets.

Any specific reason this is not in the cdk examples?^^

looks like the parameter overwrite during deloyment is the crucial thing here.

...props.lambdaCode.assign(lambdaBuildOutput.s3Location),

is this the actual syntax (with three points) or should this indicate that it is an example. Didn't use typescript before I started with cdk. If I understand correctly we need to overwrite 3 parameters in total.

I'll bash my head against this tomorrow. Thanks for the fast reply and CDK in general, it's a huge step up from plain cfn or aws sam^^

@skinny85
Copy link
Contributor

The three points syntax, ...props.lambdaCode.assign(lambdaBuildOutput.s3Location),, is just a TypeScript shorthand. The method LambdaCode.assign() returns the type { [name: string]: any } (an object with string keys and arbitrary values), the same type that the parameterOverrides property expects. The ... splice the object from assign() into the object that is the value for parameterOverrides. It would be the same if you wrote parameterOverrides: props.lambdaCode.assign(lambdaBuildOutput.s3Location).

@nikhilbhoj
Copy link

Hi skinny85,

Is the example mentioned in the link https://docs.aws.amazon.com/cdk/latest/guide/codepipeline_example.html?shortFooter=true

working as I was trying to make in work but I am getting error when I had pushed my lambda code (index.js) in the lambda directory.

Here is the below excerpt i the Build phase.

Container] 2019/07/26 06:58:23 Running command npm run build
npm ERR! path /codebuild/output/src844156381/src/package.json
npm ERR! code ENOENT
npm ERR! errno -2
npm ERR! syscall open
npm ERR! enoent ENOENT: no such file or directory, open '/codebuild/output/src844156381/src/package.json'
npm ERR! enoent This is related to npm not being able to find a file.
npm ERR! enoent

May be I am missing a trick, I am not sure.

I suspect this second CDK build step which has below steps

build: {
commands: [
'npm run build',
'npm run cdk synth -- -o dist'
],
},
},
artifacts: {
'base-directory': 'dist',
files: [
'LambdaStack.template.json',
],
},

has to given information on the LambdaStack

@jonny-rimek
Copy link
Author

jonny-rimek commented Jul 26, 2019

@skinny85 thanks for the clarification.

the rfc for CD in cdk is also proposing to use an extra build stage that executes cdk deploy instead of using codedeploy, so I guess I'll try the same for the time being.

@skinny85
Copy link
Contributor

@nikhilbhoj the error message is about a file src/package.json. That doesn't seem correct. What's the src directory, and why is it being used? There is no mention of src in the buildspec you quoted.

Make sure you're using the correct directory in the buildspec.

@nikhilbhoj
Copy link

nikhilbhoj commented Jul 27, 2019

Hi @skinny85 ,

What I have done is follow the link above from AWS cdk documentation
I have created a lambdarepo which is codecommit repo outside of codepipeline.
Then I have my lambad code in directory structure as lambda and then I pushed my index.js and package.json file in the codecommit.
Then My Code build Step of codepipeline works.
Then in my second step which is two part a) Lambda Build and b) CDK build part, of which
my Lambda build is working but the CDK build part is failing at npm run build.

And here is the code build output.

[Container] 2019/07/26 06:58:05 Waiting for agent ping 
[Container] 2019/07/26 06:58:07 Waiting for DOWNLOAD_SOURCE 
[Container] 2019/07/26 06:58:08 Phase is DOWNLOAD_SOURCE 
[Container] 2019/07/26 06:58:08 CODEBUILD_SRC_DIR=/codebuild/output/src844156381/src 
[Container] 2019/07/26 06:58:08 YAML location is /codebuild/readonly/buildspec.yml 
[Container] 2019/07/26 06:58:08 Processing environment variables 
[Container] 2019/07/26 06:58:08 Moving to directory /codebuild/output/src844156381/src 
[Container] 2019/07/26 06:58:08 Registering with agent 
[Container] 2019/07/26 06:58:08 Phases found in YAML: 2 
[Container] 2019/07/26 06:58:08  INSTALL: 3 commands 
[Container] 2019/07/26 06:58:08  BUILD: 3 commands 
[Container] 2019/07/26 06:58:08 Phase complete: DOWNLOAD_SOURCE State: SUCCEEDED 
[Container] 2019/07/26 06:58:08 Phase context status code:  Message:  
[Container] 2019/07/26 06:58:08 Entering phase INSTALL 
[Container] 2019/07/26 06:58:08 Running command npm install 
npm WARN saveError ENOENT: no such file or directory, open '/codebuild/output/src844156381/src/package.json' 
npm notice created a lockfile as package-lock.json. You should commit this file. 
npm WARN enoent ENOENT: no such file or directory, open '/codebuild/output/src844156381/src/package.json' 
npm WARN src No description 
npm WARN src No repository field. 
npm WARN src No README data 
npm WARN src No license field. 
 
up to date in 0.078s 
 
[Container] 2019/07/26 06:58:09 Running command npm install -g cdk 
/usr/local/bin/cdk -> /usr/local/lib/node_modules/cdk/bin/cdk 
 
> [email protected] postinstall /usr/local/lib/node_modules/cdk/node_modules/core-js 
> node scripts/postinstall || echo "ignore" 
 
·[96mThank you for using core-js (·[94m https://github.com/zloirock/core-js ·[96m) for polyfilling JavaScript standard library!·[0m 
 
·[96mThe project needs your help! Please consider supporting of core-js on Open Collective or Patreon: ·[0m 
·[96m>·[94m https://opencollective.com/core-js ·[0m 
·[96m>·[94m https://www.patreon.com/zloirock ·[0m 
 
·[96mAlso, the author of core-js (·[94m https://github.com/zloirock ·[96m) is looking for a good job -)·[0m 
 
+ [email protected] 
added 238 packages in 11.805s 
 
[Container] 2019/07/26 06:58:21 Running command npm install -g typescript 
/usr/local/bin/tsc -> /usr/local/lib/node_modules/typescript/bin/tsc 
/usr/local/bin/tsserver -> /usr/local/lib/node_modules/typescript/bin/tsserver 
+ [email protected] 
added 1 package in 1.117s 
 
[Container] 2019/07/26 06:58:23 Phase complete: INSTALL State: SUCCEEDED 
[Container] 2019/07/26 06:58:23 Phase context status code:  Message:  
[Container] 2019/07/26 06:58:23 Entering phase PRE_BUILD 
[Container] 2019/07/26 06:58:23 Phase complete: PRE_BUILD State: SUCCEEDED 
[Container] 2019/07/26 06:58:23 Phase context status code:  Message:  
[Container] 2019/07/26 06:58:23 Entering phase BUILD 
[Container] 2019/07/26 06:58:23 Running command ls -ltr 
total 8 
drwxr-xr-x 2 root root 4096 Jul 26 06:58 lambda 
-rw-r--r-- 1 root root   27 Jul 26 06:58 package-lock.json 
 
[Container] 2019/07/26 06:58:23 Running command npm run build 
npm ERR! path /codebuild/output/src844156381/src/package.json 
npm ERR! code ENOENT 
npm ERR! errno -2 
npm ERR! syscall open 
npm ERR! enoent ENOENT: no such file or directory, open '/codebuild/output/src844156381/src/package.json' 
npm ERR! enoent This is related to npm not being able to find a file. 
npm ERR! enoent  
 
npm ERR! A complete log of this run can be found in: 
npm ERR!     /root/.npm/_logs/2019-07-26T06_58_24_079Z-debug.log 
 
[Container] 2019/07/26 06:58:24 Command did not exit successfully npm run build exit status 254 
[Container] 2019/07/26 06:58:24 Phase complete: BUILD State: FAILED 
[Container] 2019/07/26 06:58:24 Phase context status code: COMMAND_EXECUTION_ERROR Message: Error while executing command: npm run build. Reason: exit status 254 
[Container] 2019/07/26 06:58:24 Entering phase POST_BUILD 
[Container] 2019/07/26 06:58:24 Phase complete: POST_BUILD State: SUCCEEDED 
[Container] 2019/07/26 06:58:24 Phase context status code:  Message:  
[Container] 2019/07/26 06:58:24 Expanding base directory path: dist 
[Container] 2019/07/26 06:58:24 Assembling file list 
[Container] 2019/07/26 06:58:24 Expanding dist 
[Container] 2019/07/26 06:58:24 Skipping invalid artifact path dist 
[Container] 2019/07/26 06:58:24 Phase complete: UPLOAD_ARTIFACTS State: FAILED 
[Container] 2019/07/26 06:58:24 Phase context status code: CLIENT_ERROR Message: no matching base directory path found for dist 

I do not have src in my buildspec file.

Here is buildpase details where it is failing(CdkBuild455F642E-q5wchZGs5EDo).

{
  "version": "0.2",
  "phases": {
    "install": {
      "commands": [
        "npm install",
        "npm install -g cdk",
        "npm install -g typescript"
      ]
    },
    "build": {
      "commands": [
        "ls -ltr",
        "npm run build",
        "npm run cdk synth -- -o dist"
      ]
    }
  },
  "artifacts": {
    "base-directory": "dist",
    "files": [
      "LambdaStack.template.json"
    ]
  }
}

And here is Lambdabuild project details (buildspec) this is working.

{
  "version": "0.2",
  "phases": {
    "install": {
      "commands": [
        "cd lambda",
        "npm install"
      ]
    },
    "build": {
      "commands": "npm run build"
    }
  },
  "artifacts": {
    "base-directory": "lambda",
    "files": [
      "index.js",
      "node_modules/**/*"
    ]
  }
}

Here is my pipeline

image

And here is my codecommit repo

image

I am not sure if this will help.

My assumption is that CDKbuild phase does not have lambdastack file for cdk synth command.

@skinny85
Copy link
Contributor

Did you commit your package.json file? The output of the ls -ltr command that is executed during the build shows this:

drwxr-xr-x 2 root root 4096 Jul 26 06:58 lambda 
-rw-r--r-- 1 root root   27 Jul 26 06:58 package-lock.json 

There is no package.json file present there, only package-lock.json.

@nikhilbhoj
Copy link

I did not commit the package.json file and the package-lock.json was created during npm install as per my assumption.

[Container] 2019/07/26 06:58:08 Running command npm install
npm WARN saveError ENOENT: no such file or directory, open '/codebuild/output/src844156381/src/package.json'
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN enoent ENOENT: no such file or directory, open '/codebuild/output/src844156381/src/package.json'
npm WARN src No description
npm WARN src No repository field.
npm WARN src No README data
npm WARN src No license field.

I had only commited the lambda folder having index.js and package.json.

Can you please share the directory structure for this pipeline project ?

Mine is as below.

image

And here is the directory for lambda which I have commited to my codecommitrepo (lambdarepo).

image

@skinny85
Copy link
Contributor

If you didn't commit these files, how are you expecting to successfully run a build using them on CodeBuild....?

@nikhilbhoj
Copy link

Hi @skinny85 ,

I was able to make this work, as pointed out by you when I had commited every thing into my codecommit repo.

Thanks for point out.

@skinny85
Copy link
Contributor

skinny85 commented Aug 1, 2019

Glad you were able to figure it out :)

@jonny-rimek
Copy link
Author

@skinny85 I went with the original proposal after all, it works as expected.

But I'm unclear how to handle multiple lambdas, normally I would specify the path in the code property.

For reference, I'm building a stepfunction that is triggered on putObject in a bucket, the first lambda is downloading the csv converting it to parquet and uploading the file to another bucket, with the second lambda I want to query the parquet file with athena. The last step would be to read the result and write it to dynamodb.

@Apoorva-Wathodkar
Copy link

Hi @jonny-rimek , were you able to figure out how to handle multiple lambdas in this? I am implementing a similar use case as you described in the last comment.

@jonny-rimek
Copy link
Author

@Apoorva-Wathodkar well, I'm not using code deploy any more I simply run cdk deploy in the CI/CD

@wange011
Copy link

Hi @Apoorva-Wathodkar, I managed to get an implementation working by adding multiple functions to the file specified by the build artifact.

In my case, a CloudFormation template is first synthesized using a CodeBuild with the build artifacts containing the file with the lambda handler code. From what I gather, when CloudFormation creates a ChangeSet using the template, this lambda code needs to be passed in as a parameter.

That's where the parameter_overrides and extra_inputs from the example (https://docs.aws.amazon.com/cdk/latest/guide/codepipeline_example.html?shortFooter=true) comes from. The aws_lambda.CfnParametersCode object is given the bucket_name, object_key, and object_version of the resulting build artifacts containing the lambda handler code. That's what allows the lambda function in the stack to find the handler code. I'm not certain if/why the extra_inputs=[lambda_build_output] is necessary though.

So if the lambda handler file exports multiple functions, all you have to do is specify the handler function for each lambda. For example, if handler.js contains exports.firstFunc and exports.secFunc, one lambda can have the parameter handler="handler.firstFunc", and the other can have the parameter handler="handler.secFunc". Alternatively, I believe you can define multiple files as artifacts in the CodeBuild buildspec and specify the files used by doing the following: handler="your_file_name.your_function_name".

@skinny85
Copy link
Contributor

Multiple Lambdas are very similar to one Lambda, and depend on whether your functions share the same bundle, or not.

  1. If they do, then it's very simple; all Lambdas that share bundles use the same CfnParametersCode instance for their code property, but just differ in the entrypoint. So, like @wange011 said, you would have:
const cfnParamsCode = lambda.Code.fromCfnParameters();
const function1 = new lambda.Function(this, 'Function1', {
  code: cfnParamsCode,
  // other function properties...
  handler: 'handler.firstFunc',
});
const function2 = new lambda.Function(this, 'Function2', {
  code: cfnParamsCode,
  // other function properties...
  handler: 'handler.secondFunc',
});

All of the rest is the same as the example.

  1. If the functions use different bundles, you would have a separate instance of CfnParametersCode for each, and then a separate build artifact in your lambda-bundling CodeBuild project; so, something like this:
// Lambda stacks
const cfnParamsCode1 = lambda.Code.fromCfnParameters();
const function1 = new lambda.Function(this, 'Function1', {
  code: cfnParamsCode1,
  // other function properties...
  handler: 'handler.firstFunc',
});
const cfnParamsCode2 = lambda.Code.fromCfnParameters();
const function2 = new lambda.Function(this, 'Function2', {
  code: cfnParamsCode2,
  // other function properties...
  handler: 'handler.secondFunc',
});

// CodePipeline stacks
const lambdaBuildOutput1 = new codepipeline.Artifact('LambdaBuildOutput1');
const lambdaBuildOutput2 = new codepipeline.Artifact('LambdaBuildOutput2');

              new codebuild.PipelineProject(this, 'Build', {
                buildSpec: codebuild.BuildSpec.fromObject({
                  version: '0.2',
                  phases: {
                     // build all your Lambda functions here...
                  },
                  // save the generated files in the correct artifacts
                  artifacts: {
                    'secondary-artifacts': {
                      'LambdaBuildOutput1': {
                        'base-directory': 'lambda-build-output1',
                        files: ['**/*'],
                      },
                      'LambdaBuildOutput2': {
                        'base-directory': 'lambda-build-output2',
                        files: ['**/*'],
                      },
                    },
                  },
                }),
              });

// then, in the CFN deploy action:

            new codepipeline_actions.CloudFormationCreateUpdateStackAction({
              // ...
              parameterOverrides: {
                ...props.cfnParamsCode1.assign(lambdaBuildOutput1.s3Location),
                ...props.cfnParamsCode2.assign(lambdaBuildOutput2.s3Location),
              },

You can check out this example as well.

Thanks,
Adam

@Spider-ant
Copy link

Spider-ant commented Jun 3, 2021

Hi @skinny85 ,

Can this method be used for lambda layers as well?
e.g.

const lambdaLayer = new lambda.LayerVersion(this, 'LambdaLayer', {
    code: cfnParamsCode2,
    compatibleRuntimes: [
        lambda.Runtime.NODEJS_8_10
    ],
});

@skinny85
Copy link
Contributor

skinny85 commented Jun 3, 2021

@Spider-ant I personally have not tried it, but I don't see why it would not 🙂.

@Spider-ant
Copy link

Using python and base on the example above an error is thrown when attempting to assign() twice in the parameter overrides of the CFN deploy action.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs-triage This issue or PR still needs to be triaged.
Projects
None yet
Development

No branches or pull requests

6 participants