Continuous Integration
CI (Continuous Integration) is a continuous method of software development, where you continuously build and test iterative code changes.
This iterative process helps reduce the chance that you develop new code based on buggy or failed previous versions. The CDK CI/CD Wrapper can catch bugs early in the development cycle, and help ensure that all the code deployed to production complies with your established code standards.
The CI functionality of the CDK CI/CD Wrapper can be utilized in any software development process, it is not bound to infrastructure development or AWS CDK projects.
Common terms
PhaseCommand
PhaseCommand represent a single step that is executed by the CI/CD pipeline.
These are the available PhaseCommands types:
- NPMPhaseCommand - defines an NPM script execution
- ShellScriptPhaseCommand - defines a shell script execution
- ShellCommandPhaseCommand - defines a shell command for execution
- PythonPhaseCommand - defines a python script execution
- InlineShellPhaseCommand - internal move the given script file content to the build as is. This PhaseCommand is useful in situations where the CDK CI/CD Wrapper sources are not available, for example before network access or NPM registry setups.
Phases
The CDK CI/CD Wrapper has 7 phases which covers every CI/CD project lifecycle. Some of the stages like preDeploy
and postDeploy
are executed multiple times, while others are only executed once. When a phase is executed it needs to execute all the PhaseCommands defined for the phase in order until completion otherwise the step fails.
The following phases are available:
- initialize - initializes CI environment before the actual build process can be started. In this phase the networking and the NPM registry connection is established.
- preBuild - verifies the project is ready for the build
- build - builds the source code
- testing - runs testing activities to verify the quality of the product
- preDeploy - CD phase - prepares and verifies the environment for the deploy
- deploy - CD phase - deploys the CDK stacks to the stage. This phase can not be modified
- postDeploy - CD phase - verifies the environment after the deployment and finalize the environment setup
What are the default set of PhaseCommands for the CDK CI/CD Wrapper?
The CDK CI/CD Wrapper comes with a pre-set list of PhaseCommand definitions for each stage that provides a good starting point for any AWS CDK projects.
Initialize Phase
Type | PhaseCommand | Description |
---|---|---|
InlineShellPhaseCommand | CONFIGURE_HTTP_PROXY | This step configures the HTTP proxy in case it is needed for accessing external resources. You can read more about this in the networking guide. |
InlineShellPhaseCommand | ENVIRONMENT_PREPARATION | Populates the environment variables from ParameterStore. |
InlineShellPhaseCommand | NPM_LOGIN | Configures the private NPM registry in case it is needed. |
PreBuild Phase
Type | PhaseCommand | Description |
---|---|---|
NPMPhaseCommand | VALIDATE | Executes the npm run validate command. You can define the command by yourself or you can use CDK CI/CD Wrapper - CLI Validate command |
NPMPhaseCommand | CHECK_AUDIT | Executes the npm run audit command. You can define the command by yourself or you can our recommended audit definition. |
NPMPhaseCommand | NPM_CI | Runs the npm ci command and downloads all dependencies |
NPMPhaseCommand | CHECK_LINT | Executes the npm run lint command. You can define the command by yourself |
Build Phase
Type | PhaseCommand | Description |
---|---|---|
NPMPhaseCommand | BUILD | Executes the npm run build command. |
Testing Phase
Type | PhaseCommand | Description |
---|---|---|
NPMPhaseCommand | TEST | Executes the npm run test command. |
InlineShellPhaseCommand | CDK_SYNTH_WITH_LOOK_UP | Executes the cdk synth command to synthesize the CDK project code and runs the CDK NAG. This version allows the CDK to perform lookups. |
What are the available PhaseCommands?
Type | PhaseCommand | Description |
---|---|---|
InlineShellPhaseCommand | CONFIGURE_HTTP_PROXY | This step configures the HTTP proxy in case it is needed for accessing external resources. You can read more about this in the networking guide. |
InlineShellPhaseCommand | ENVIRONMENT_PREPARATION | Populates the environment variables from ParameterStore. |
InlineShellPhaseCommand | NPM_LOGIN | Configures the private NPM registry in case it is needed. |
NPMPhaseCommand | VALIDATE | Executes the npm run validate command. You can define the command by yourself or you can use CDK CI/CD Wrapper - CLI Validate command |
NPMPhaseCommand | CHECK_AUDIT | Executes the npm run audit command. You can define the command by yourself or you can our recommended audit definition. |
NPMPhaseCommand | NPM_CI | Runs the npm ci command and downloads all dependencies |
NPMPhaseCommand | CHECK_LINT | Executes the npm run lint command. You can define the command by yourself |
NPMPhaseCommand | BUILD | Executes the npm run build command. |
NPMPhaseCommand | TEST | Executes the npm run test command. |
InlineShellPhaseCommand | CDK_SYNTH_WITH_LOOK_UP | Executes the cdk synth command to synthesize the CDK project code and runs the CDK NAG. This version allows the CDK to perform lookups. |
InlineShellPhaseCommand | CDK_SYNTH_WITHOUT_LOOK_UP | Executes the cdk synth command to synthesize the CDK project code and runs the CDK NAG. This version does not allow the CDK to perform lookups. |
How to define a new PhaseCommand?
There are cases when a new command needs to be added to the CI/CD pipeline. As a first step you need to determine the type of the command. See the list of available PhaseCommand types, if none of those types seems to fit, you always have an option to define your own type.
Let's see the two most common cases you could encounter.
Define NPM script based PhaseCommand
First ensure the script
is defined in your package.json file and the scripts execution result the expected outcome. So your package.json might look like this:
{
...
"scripts":
{
...
"my-script": "ls"
...
},
...
}
Then you can create an NPMPhaseCommand with:
const myScriptPhaseCommand = new NPMPhaseCommand('my-script');
Now the command is ready, we need to include it into our desired phase. Once a default phase has been modified we require you to explicitly define the PhaseCommands for that phase.
The phase can be defined with the definePhase
method that is available on the VanillaPipelineBuilder.
const myScriptPhaseCommand = new NPMPhaseCommand('my-script');
PipelineBlueprint.builder()
.addStack((context) => {
new DemoStack(context.scope, 'DemoStack');
})
.definePhase(PipelinePhases.PRE_BUILD, [
PhaseCommands.VALIDATE,
myScriptPhaseCommand,
PhaseCommands.CHECK_AUDIT,
PhaseCommands.NPM_CI,
PhaseCommands.CHECK_LINT
])
.synth(app);
Here, you can see how to define the order of the commands, for the phase.
Define Shell command based PhaseCommand
You can create a ShellCommandPhaseCommand with:
const myScriptPhaseCommand = new sh('ls');
Now, the command is ready, we need to include into our desired phase. Once a default phase has been modified we require you to explicitly define the PhaseCommands for that phase.
The phase can be defined with the definePhase
method that is available on the VanillaPipelineBuilder.
PipelineBlueprint.builder()
.addStack((context) => {
new DemoStack(context.scope, 'DemoStack');
})
.definePhase(PipelinePhases.PRE_BUILD, [
PhaseCommands.VALIDATE,
sh('ls'),
PhaseCommands.CHECK_AUDIT,
PhaseCommands.NPM_CI,
PhaseCommands.CHECK_LINT
])
.synth(app);
How to define a new PhaseCommand Type?
Every PhaseCommand must implement the PhaseCommand interface that has only one required command property. This command property must contain an executable command that the sh
shell engine can execute, as this command will be added to the underlining AWS CodeBuild project buildSpec.yaml
as part of the commands list.
How to define the order of the PhaseCommands?
The execution order of the PhaseCommands follows the PhaseCommand position in the definition array.
const myScriptPhaseCommand = new NPMPhaseCommand('my-script');
PipelineBlueprint.builder()
.addStack((context) => {
new DemoStack(context.scope, 'DemoStack');
})
.definePhase(PipelinePhases.PRE_BUILD, [
PhaseCommands.VALIDATE,
myScriptPhaseCommand,
PhaseCommands.CHECK_AUDIT,
PhaseCommands.NPM_CI,
PhaseCommands.CHECK_LINT
])
.synth(app);
The npm run validation
will be executed before the npm run my-script
command.
Using BuildSpec files
The CDK CI/CD Wrapper allows to use the buildSpec.yaml
file to define the build process instead of the PhaseCommands.
To use the buildSpec.yaml
file you need configure it with the buildSpecFromFile()
method of the PipelineBlueprint
builder..
PipelineBlueprint.builder()
.buildSpecFromFile('path/to/buildSpec.yaml')
.addStack((context) => {
new DemoStack(context.scope, 'DemoStack');
})
.synth(app);
The buildSpec.yaml
file should be placed in the root of the project and should contain the build process definition.
It has to contain the commands to generate the build artifacts, run the tests and synthesize the CDK stacks.
version: 0.2
phases:
install:
runtime-versions:
nodejs: 20
commands:
- npm ci
build:
commands:
- npm run build
post_build:
commands:
- npm run test
- cdk synth
Note: The changes in the buildSpec.yaml
will be applied after the next pipeline self-mutation.
Inline BuildSpec definition
The buildSpec.yaml
file can be defined inline as well.
PipelineBlueprint.builder()
.buildSpec(cdk.BuildSpec.fromObject({
version: '0.2',
phases: {
install: {
runtime-versions: {
nodejs: 20
},
commands: [
'npm ci'
]
},
build: {
commands: [
'npm run build'
]
},
post_build: {
commands: [
'npm run test',
'cdk synth'
]
}
}
}))
.addStack((context) => {
new DemoStack(context.scope, 'DemoStack');
})
.synth(app);