Skip to content

Commit

Permalink
Fix usage of source artifacts in deploy stages when build stage is di…
Browse files Browse the repository at this point in the history
…sabled (awslabs#334)

**Why?**

When the build stage is disabled, the deploy stages incorrectly tried to
use the build output artifact. These should use the source artifact
instead.

**Linked issues**

* awslabs#236
* awslabs#318
  • Loading branch information
sbkok authored Mar 3, 2021
1 parent 8164ca5 commit c3d5c33
Show file tree
Hide file tree
Showing 7 changed files with 474 additions and 41 deletions.
2 changes: 2 additions & 0 deletions samples/sample-etl-pipeline/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
account_id: 111111111111
bucket_name: banking-etl-bucket-source
object_key: input.zip
build:
enabled: False
deploy:
provider: s3
targets:
Expand Down
1 change: 1 addition & 0 deletions samples/sample-etl-pipeline/scripts/some_etl_script.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ echo "Doing some ETL tasks... This could also be done with a custom CodeBuild Im
cat big_data.txt

echo "You can optionally bundle the buildspec.yml in the source zip and have the commands executed that way.."
echo "Don't forget to enable the build stage to support this"
Original file line number Diff line number Diff line change
Expand Up @@ -275,62 +275,93 @@ def generate(self):
"region": self.region or ADF_DEPLOYMENT_REGION,
"run_order": self.run_order
}
input_artifacts = self._get_input_artifacts()
if input_artifacts:
action_props["input_artifacts"] = input_artifacts
output_artifacts = self._get_output_artifacts()
if output_artifacts:
action_props["output_artifacts"] = output_artifacts
if _role:
action_props["role_arn"] = _role
if self.category == 'Manual':
del action_props['region']
if self.category == 'Build' and not self.target:
action_props["input_artifacts"] = [
_codepipeline.CfnPipeline.InputArtifactProperty(
name="output-source"
)
]
action_props["output_artifacts"] = [
_codepipeline.CfnPipeline.OutputArtifactProperty(
name="{0}-build".format(self.map_params['name'])
)
]
if self.category == 'Build' and self.target:
action_props["input_artifacts"] = [
_codepipeline.CfnPipeline.InputArtifactProperty(
name="{0}-build".format(self.map_params['name'])
)
]
if not self.map_params.get('default_providers', {}).get('build', {}).get('enabled', True):
action_props["input_artifacts"] = [
_codepipeline.CfnPipeline.InputArtifactProperty(
name="output-source"
)
]

return _codepipeline.CfnPipeline.ActionDeclarationProperty(
**action_props
)

def _get_base_input_artifact_name(self):
"""
Determine the name for the input artifact for this action.
Returns:
str: The output artifact name as a string
"""
use_output_source = (
not self.target or
not self.map_params.get('default_providers', {}).get('build', {}).get('enabled', True)
)
if use_output_source:
return "output-source"
return "{0}-build".format(self.map_params['name'])

def _get_input_artifacts(self):
"""
Generate the list of input artifacts that are required for this action
Returns:
list<CfnPipeline.InputArtifactProperty>: The Input Artifacts
"""
if not self.category in ['Build', 'Deploy']:
return []
input_artifacts = [
_codepipeline.CfnPipeline.InputArtifactProperty(
name=self._get_base_input_artifact_name(),
),
]
if self.category == 'Deploy':
action_props["input_artifacts"] = [
_codepipeline.CfnPipeline.InputArtifactProperty(
name="{0}-build".format(self.map_params['name'])
)
]
if self.provider == "CloudFormation" and self.target.get('properties', {}).get('outputs') and self.action_mode != 'CHANGE_SET_REPLACE':
action_props["output_artifacts"] = [
_codepipeline.CfnPipeline.OutputArtifactProperty(
name=self.target.get('properties', {}).get('outputs')
)
]
for override in self.target.get('properties', {}).get('param_overrides', []):
if self.provider == "CloudFormation" and override.get('inputs') and self.action_mode != "CHANGE_SET_EXECUTE":
action_props["input_artifacts"].append(
input_artifacts.append(
_codepipeline.CfnPipeline.InputArtifactProperty(
name=override.get('inputs')
)
)
return input_artifacts

def _get_base_output_artifact_name(self):
"""
Determine the name for the output artifact for this action.
Returns:
str: The output artifact name as a string
"""
if self.category == 'Source':
action_props["output_artifacts"] = [
return "output-source"
if self.category == 'Build' and not self.target:
return "{0}-build".format(self.map_params['name'])
if self.category == 'Deploy' and self.provider == "CloudFormation":
outputs_name = self.target.get('properties', {}).get('outputs', '')
if outputs_name and self.action_mode != 'CHANGE_SET_REPLACE':
return outputs_name
return ''

def _get_output_artifacts(self):
"""
Generate the list of output artifacts that are required for this action
Returns:
list<CfnPipeline.OutputArtifactProperty>: The Output Artifacts
"""
output_artifact_name = self._get_base_output_artifact_name()
if output_artifact_name:
return [
_codepipeline.CfnPipeline.OutputArtifactProperty(
name="output-source"
)
name=output_artifact_name
),
]
return []

return _codepipeline.CfnPipeline.ActionDeclarationProperty(
**action_props
)

class Pipeline(core.Construct):
_import_arns = [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: MIT-0

# pylint: skip-file

BASE_MAP_PARAMS = {
'default_providers': {
'source': {
'properties': {
'account_id': 123456123456,
}
},
'build': {},
'deploy': {},
},
'name': 'name',
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: MIT-0

# pylint: skip-file

from mock import patch
from cdk_constructs.adf_codepipeline import Action
from adf_codepipeline_test_constants import BASE_MAP_PARAMS

@patch('cdk_constructs.adf_codepipeline._codepipeline.CfnPipeline.ActionDeclarationProperty')
@patch('cdk_constructs.adf_codepipeline.Action._get_output_artifacts')
@patch('cdk_constructs.adf_codepipeline.Action._get_input_artifacts')
def test_generates_with_input_and_output_artifacts_when_given(input_mock, output_mock, action_decl_mock):
action_decl_mock.side_effect = lambda **x: x
mocked_input_value = 'InputArtifacts'
mocked_output_value = 'OutputArtifacts'
input_mock.return_value = mocked_input_value
output_mock.return_value = mocked_output_value
action = Action(
map_params=BASE_MAP_PARAMS,
category='Build',
provider='CodeBuild',
)
assert action.config['input_artifacts'] == mocked_input_value
assert action.config['output_artifacts'] == mocked_output_value


@patch('cdk_constructs.adf_codepipeline._codepipeline.CfnPipeline.ActionDeclarationProperty')
@patch('cdk_constructs.adf_codepipeline.Action._get_output_artifacts')
@patch('cdk_constructs.adf_codepipeline.Action._get_input_artifacts')
def test_generates_without_input_and_output_artifacts(input_mock, output_mock, action_decl_mock):
action_decl_mock.side_effect = lambda **x: x
mocked_value = None
input_mock.return_value = mocked_value
output_mock.return_value = mocked_value
action = Action(
map_params=BASE_MAP_PARAMS,
category='Build',
provider='CodeBuild',
)
assert not 'input_artifacts' in action.config
assert not 'output_artifacts' in action.config
Loading

0 comments on commit c3d5c33

Please sign in to comment.