Skip to content

Commit

Permalink
Add EnvironmentVariablesOverride instead of ResourceTrigger
Browse files Browse the repository at this point in the history
  • Loading branch information
benbridts committed Feb 28, 2021
1 parent b8a405b commit 0cfef1f
Show file tree
Hide file tree
Showing 9 changed files with 150 additions and 26 deletions.
Empty file added modules-template.yaml
Empty file.
8 changes: 8 additions & 0 deletions resource_providers/cloudar_codebuild_build/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ it from CodeBuild when it is replaced with a new version, or when the build fail
debugging it's recommended to add: `DeletionPolicy: Retain` and `UpdateReplacePolicy: Retain` to the
resource.

If you want to trigger based on changing outputs of other resources, you can add those as `EnvironmentVariablesOverride`
in a dummy variable. Changing these overrides will always create a new build, so as long as you have something that changes
to refer to, this should trigger the build again.

## Example
There is an example template, `example.yaml`. It will work after you run `cfn submit` to deploy the resource provider in your account (you need docker running to do that).

Expand All @@ -20,3 +24,7 @@ There is an example template, `example.yaml`. It will work after you run `cfn su
## Contract Tests
- To run the contract tests you need to have a CodeBuild Project that matches the name that's defined in `overrides.json`
- Running the contract tests will start builds in that project

1. Run `cfn submit --dry-run` to build the resource provider
1. Run `sam local start-lambda`
1. Open op a new terminal and run `cfn test` (with the right role / credentials)
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,38 @@
"typeName": "Cloudar::CodeBuild::Build",
"description": "Start a CodeBuild Build. It's highly recommended to either write the CodeBuild logs to S3/CloudWatch, or to have prevent this resource from being deleted when CloudFormation performs a rollback",
"sourceUrl": "https://github.com/WeAreCloudar/cloudformation-samples.git",
"definitions": {},
"definitions": {
"EnvironmentVariable" : {
"type" : "object",
"additionalProperties" : false,
"properties" : {
"Value" : {
"type" : "string"
},
"Type" : {
"type" : "string",
"enum": ["PLAINTEXT", "PARAMETER_STORE", "SECRETS_MANAGER"]
},
"Name" : {
"type" : "string",
"minLength": 1
}
},
"required" : [ "Value", "Name" ]
}
},
"properties": {
"ProjectName": {
"description": "The name of the AWS CodeBuild build project to start running a build",
"type": "string",
"pattern": "^[a-zA-Z0-9_-]{2,}$"
},
"ResourceTrigger": {
"description": "Change this value to trigger a new build",
"type": "string"
"EnvironmentVariablesOverride": {
"type": "array",
"uniqueItems": false,
"items": {
"$ref": "#/definitions/EnvironmentVariable"
}
},
"BuildId": {
"description": "The Identifier of the build",
Expand All @@ -37,10 +59,10 @@
],
"createOnlyProperties": [
"/properties/ProjectName",
"/properties/ResourceTrigger"
"/properties/EnvironmentVariablesOverride"
],
"writeOnlyProperties": [
"/properties/ResourceTrigger"
"/properties/EnvironmentVariablesOverride"
],
"primaryIdentifier": [
"/properties/BuildId"
Expand Down
11 changes: 5 additions & 6 deletions resource_providers/cloudar_codebuild_build/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ To declare this entity in your AWS CloudFormation template, use the following sy
"Type" : "Cloudar::CodeBuild::Build",
"Properties" : {
"<a href="#projectname" title="ProjectName">ProjectName</a>" : <i>String</i>,
"<a href="#resourcetrigger" title="ResourceTrigger">ResourceTrigger</a>" : <i>String</i>,
"<a href="#environmentvariablesoverride" title="EnvironmentVariablesOverride">EnvironmentVariablesOverride</a>" : <i>[ <a href="environmentvariable.md">EnvironmentVariable</a>, ... ]</i>,
}
}
</pre>
Expand All @@ -24,7 +24,8 @@ To declare this entity in your AWS CloudFormation template, use the following sy
Type: Cloudar::CodeBuild::Build
Properties:
<a href="#projectname" title="ProjectName">ProjectName</a>: <i>String</i>
<a href="#resourcetrigger" title="ResourceTrigger">ResourceTrigger</a>: <i>String</i>
<a href="#environmentvariablesoverride" title="EnvironmentVariablesOverride">EnvironmentVariablesOverride</a>: <i>
- <a href="environmentvariable.md">EnvironmentVariable</a></i>
</pre>

## Properties
Expand All @@ -41,13 +42,11 @@ _Pattern_: <code>^[a-zA-Z0-9_-]{2,}$</code>

_Update requires_: [Replacement](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-replacement)

#### ResourceTrigger

Change this value to trigger a new build
#### EnvironmentVariablesOverride

_Required_: No

_Type_: String
_Type_: List of <a href="environmentvariable.md">EnvironmentVariable</a>

_Update requires_: [Replacement](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-replacement)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Cloudar::CodeBuild::Build EnvironmentVariable

## Syntax

To declare this entity in your AWS CloudFormation template, use the following syntax:

### JSON

<pre>
{
"<a href="#value" title="Value">Value</a>" : <i>String</i>,
"<a href="#type" title="Type">Type</a>" : <i>String</i>,
"<a href="#name" title="Name">Name</a>" : <i>String</i>
}
</pre>

### YAML

<pre>
<a href="#value" title="Value">Value</a>: <i>String</i>
<a href="#type" title="Type">Type</a>: <i>String</i>
<a href="#name" title="Name">Name</a>: <i>String</i>
</pre>

## Properties

#### Value

_Required_: Yes

_Type_: String

_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt)

#### Type

_Required_: No

_Type_: String

_Allowed Values_: <code>PLAINTEXT</code> | <code>PARAMETER_STORE</code> | <code>SECRETS_MANAGER</code>

_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt)

#### Name

_Required_: Yes

_Type_: String

_Minimum_: <code>1</code>

_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt)

4 changes: 3 additions & 1 deletion resource_providers/cloudar_codebuild_build/example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,6 @@ Resources:
Properties:
ProjectName: !Ref CodeBuildProject
# Trigger is optional, usually you would feed some that you know will change into ut
ResourceTrigger: !Ref Trigger
EnvironmentVariablesOverride:
- Name: DUMMY
Value: !Ref Trigger
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,20 @@
)

from .constants import BuildStatus, TYPE_NAME
from .models import ResourceModel
from .models import ResourceModel, EnvironmentVariable


def create_start(session: SessionProxy, model: ResourceModel) -> "Action":
codebuild = session.client("codebuild")

build = codebuild.start_build(projectName=model.ProjectName)["build"]
kwargs = {"projectName": model.ProjectName}
# Optional parameter
if model.EnvironmentVariablesOverride:
kwargs["environmentVariablesOverride"] = [
_environment_variable(x) for x in model.EnvironmentVariablesOverride
]

build = codebuild.start_build(**kwargs)["build"]
model.BuildId = build["id"]
model.Arn = build["arn"]
model.BuildNumber = build["buildNumber"]
Expand Down Expand Up @@ -99,5 +106,16 @@ def _read_model(session: SessionProxy, model: ResourceModel) -> None:
model.BuildId = build["id"]
model.Arn = build["arn"]
model.BuildNumber = build["buildNumber"]
# we defined EnvironmentVariablesOverride as writeOnlyProperty, because we can't tell the difference
# between reading an override and reading en environment variable set on the project

# model is mutable, so we don't have to return something
return


def _environment_variable(environment_variable: EnvironmentVariable):
# Name and Value are required by the schema
output = {"name": environment_variable.Name, "value": environment_variable.Value}
if environment_variable.Type:
output["type"] = environment_variable.Type
return output
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
# Use this logger to forward log messages to CloudWatch Logs.
LOG = logging.getLogger(__name__)


resource = Resource(TYPE_NAME, ResourceModel)
test_entrypoint = resource.test_entrypoint

Expand Down Expand Up @@ -75,15 +74,8 @@ def _action_handler(
if session is None:
raise InternalFailure("No AWS credentials found: no session set")

# ResourceTrigger is part of the desiredResourceState, but we consider it a write only property
# We can't return those properties. The only reason we have this property is to force a create
# So we can safely delete it, we don't need it anymore
model.ResourceTrigger = None

# Get next action
next_action: HandlerAction = HandlerAction[
callback_context.get("next_action", start_action.name)
]
next_action: HandlerAction = HandlerAction[callback_context.get("next_action", start_action.name)]

# Some exceptions are always handled the same, so we can catch them here instead of in the actions
codebuild_exceptions = session.client("codebuild").exceptions
Expand All @@ -97,6 +89,11 @@ def _action_handler(
except codebuild_exceptions.InvalidInputException as e:
raise InvalidRequest() from e

# EnvironmentVariablesOverride is part of the desiredResourceState, but we consider it a write only property
# (because it's not possible to always read it correctly). We can't return write only properties. according to the
# contract. Once we executed all our actions we can delete it.
model.EnvironmentVariablesOverride = None

# Handle cases where there is no next action to take
if next_action is HandlerAction.DELETE_COMPLETE:
# Delete handler cannot return a Model
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class ResourceHandlerRequest(BaseResourceHandlerRequest):
@dataclass
class ResourceModel(BaseModel):
ProjectName: Optional[str]
ResourceTrigger: Optional[str]
EnvironmentVariablesOverride: Optional[Sequence["_EnvironmentVariable"]]
BuildId: Optional[str]
Arn: Optional[str]
BuildNumber: Optional[float]
Expand All @@ -56,7 +56,7 @@ def _deserialize(
recast_object(cls, json_data, dataclasses)
return cls(
ProjectName=json_data.get("ProjectName"),
ResourceTrigger=json_data.get("ResourceTrigger"),
EnvironmentVariablesOverride=deserialize_list(json_data.get("EnvironmentVariablesOverride"), EnvironmentVariable),
BuildId=json_data.get("BuildId"),
Arn=json_data.get("Arn"),
BuildNumber=json_data.get("BuildNumber"),
Expand All @@ -67,3 +67,27 @@ def _deserialize(
_ResourceModel = ResourceModel


@dataclass
class EnvironmentVariable(BaseModel):
Value: Optional[str]
Type: Optional[str]
Name: Optional[str]

@classmethod
def _deserialize(
cls: Type["_EnvironmentVariable"],
json_data: Optional[Mapping[str, Any]],
) -> Optional["_EnvironmentVariable"]:
if not json_data:
return None
return cls(
Value=json_data.get("Value"),
Type=json_data.get("Type"),
Name=json_data.get("Name"),
)


# work around possible type aliasing issues when variable has same name as a model
_EnvironmentVariable = EnvironmentVariable


0 comments on commit 0cfef1f

Please sign in to comment.