Skip to content

Commit

Permalink
feat(ami-cloudformation-parameter): Perform update in a change set
Browse files Browse the repository at this point in the history
  • Loading branch information
akash1810 committed Sep 26, 2024
1 parent 26b2418 commit 861bd87
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 21 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
package magenta.deployment_type

import magenta.deployment_type.CloudFormationDeploymentTypeParameters._
import magenta.tasks.UpdateCloudFormationTask.LookupByTags
import magenta.tasks.{
CheckUpdateEventsTask,
UpdateAmiCloudFormationParameterTask
CheckChangeSetCreatedTask,
CloudFormationParameters,
CloudFormationStackMetadata,
CreateAmiUpdateChangeSetTask,
DeleteChangeSetTask,
ExecuteChangeSetTask
}
import org.joda.time.DateTime

object AmiCloudFormationParameter
extends DeploymentType
Expand All @@ -31,19 +37,49 @@ object AmiCloudFormationParameter
val cloudFormationStackLookupStrategy =
getCloudFormationStackLookupStrategy(pkg, target, reporter)

// TODO wrap this CloudFormation update in a ChangeSet. See `Cloudformation.scala`.
val changeSetName = s"${target.stack.name}-${new DateTime().getMillis}"

val stackLookup = new CloudFormationStackMetadata(
cloudFormationStackLookupStrategy,
changeSetName,

// We're updating an AMI. The stack should exist, so never opt to create it.
createStackIfAbsent = false
)

val stackTags = cloudFormationStackLookupStrategy match {
case LookupByTags(tags) => Some(tags)
case _ => None
}

val amiLookupFn = getLatestAmi(pkg, target, reporter, resources.lookup)

val unresolvedParameters = new CloudFormationParameters(
target,
stackTags,
Map.empty,
amiParameterMap,
amiLookupFn
)

List(
UpdateAmiCloudFormationParameterTask(
new CreateAmiUpdateChangeSetTask(
region = target.region,
stackLookup = stackLookup,
unresolvedParameters = unresolvedParameters
),
new CheckChangeSetCreatedTask(
target.region,
stackLookup = stackLookup,
duration = secondsToWaitForChangeSetCreation(pkg, target, reporter)
),
new ExecuteChangeSetTask(
target.region,
cloudFormationStackLookupStrategy,
amiParameterMap,
getLatestAmi(pkg, target, reporter, resources.lookup),
target.parameters.stage,
target.stack
stackLookup
),
new CheckUpdateEventsTask(
new DeleteChangeSetTask(
target.region,
cloudFormationStackLookupStrategy
stackLookup
)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,6 @@ class CloudFormation(tagger: BuildTags)
"If set to true then the cloudformation stack will be created if it doesn't already exist"
).default(true)

val secondsToWaitForChangeSetCreation: Param[Duration] = Param
.waitingSecondsFor(
"secondsToWaitForChangeSetCreation",
"the change set to be created"
)
.default(ofMinutes(15))

val manageStackPolicyDefault = true
val manageStackPolicyLookupKey = "cloudformation:manage-stack-policy"
val manageStackPolicyParam = Param[Boolean](
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import magenta.tasks.UpdateCloudFormationTask.{
}
import magenta.{DeployReporter, DeployTarget, DeploymentPackage, Lookup}

import java.time.Duration
import java.time.Duration.ofMinutes

object CloudFormationDeploymentTypeParameters {
type TagCriteria = Map[String, String]
type CfnParam = String
Expand Down Expand Up @@ -101,6 +104,13 @@ trait CloudFormationDeploymentTypeParameters {
""".stripMargin
).default(false)

val secondsToWaitForChangeSetCreation: Param[Duration] = Param
.waitingSecondsFor(
"secondsToWaitForChangeSetCreation",
"the change set to be created"
)
.default(ofMinutes(15))

def getCloudFormationStackLookupStrategy(
pkg: DeploymentPackage,
target: DeployTarget,
Expand Down
33 changes: 33 additions & 0 deletions magenta-lib/src/main/scala/magenta/tasks/AWS.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ import magenta.deployment_type.{
MustBePresent,
MustNotBePresent
}
import magenta.tasks.CloudFormationParameters.InputParameter.toAWS
import magenta.tasks.CloudFormationParameters.{
ExistingParameter,
InputParameter
}
import magenta.{
ApiRoleCredentials,
ApiStaticCredentials,
Expand Down Expand Up @@ -764,6 +769,34 @@ object CloudFormation {
.build()
)

/** Update the parameters of a CloudFormation stack using a change set.
*/
def createParameterUpdateChangeSet(
client: CloudFormationClient,
changeSetName: String,
stackName: String,
currentStackTags: Map[String, String],
parameters: Seq[Parameter],
maybeRole: Option[String]
): Unit = {
val cfnTags: Iterable[CfnTag] = currentStackTags.map { case (key, value) =>
CfnTag.builder().key(key).value(value).build()
}

val request = CreateChangeSetRequest
.builder()
.changeSetType(ChangeSetType.UPDATE)
.stackName(stackName)
.changeSetName(changeSetName)
.usePreviousTemplate(true)
.parameters(parameters.asJava)
.tags(cfnTags.toSeq: _*)
.capabilities(Capability.CAPABILITY_NAMED_IAM)
.roleARN(maybeRole.orNull)

client.createChangeSet(request.build())
}

def createChangeSet(
reporter: DeployReporter,
name: String,
Expand Down
62 changes: 59 additions & 3 deletions magenta-lib/src/main/scala/magenta/tasks/ChangeSetTasks.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,62 @@ import java.time.Duration
import scala.jdk.CollectionConverters._
import scala.util.{Success, Try}

class CreateAmiUpdateChangeSetTask(
region: Region,
stackLookup: CloudFormationStackMetadata,
val unresolvedParameters: CloudFormationParameters
)(implicit val keyRing: KeyRing)
extends Task {

override def execute(
resources: DeploymentResources,
stopFlag: => Boolean
): Unit = {
if (!stopFlag) {
CloudFormation.withCfnClient(keyRing, region, resources) { cfnClient =>
STS.withSTSclient(keyRing, region, resources) { stsClient =>
val accountNumber = STS.getAccountNumber(stsClient)

val (stackName, _, existingParameters, currentTags) =
stackLookup.lookup(resources.reporter, cfnClient)

val maybeExecutionRole = CloudFormation.getExecutionRole(keyRing)
maybeExecutionRole.foreach(role =>
resources.reporter.verbose(s"Using execution role $role")
)

val parameters = CloudFormationParameters
.resolve(
resources.reporter,
unresolvedParameters,
accountNumber,
List.empty,
existingParameters
)
.fold(
resources.reporter.fail(_),
identity
)
val awsParameters =
CloudFormationParameters.convertInputParametersToAws(parameters)

CloudFormation.createParameterUpdateChangeSet(
client = cfnClient,
changeSetName = stackLookup.changeSetName,
stackName = stackName,
currentStackTags = currentTags,
parameters = awsParameters,
maybeRole = maybeExecutionRole
)
}
}
}
}

override def description: String =
s"Create change set ${stackLookup.changeSetName} for stack ${stackLookup.strategy} to update AMI parameters "
}

class CreateChangeSetTask(
region: Region,
templatePath: S3Path,
Expand Down Expand Up @@ -155,7 +211,7 @@ class CheckChangeSetCreatedTask(
region: Region,
stackLookup: CloudFormationStackMetadata,
override val duration: Duration
)(implicit val keyRing: KeyRing, artifactClient: S3Client)
)(implicit val keyRing: KeyRing)
extends Task
with RepeatedPollingCheck {

Expand Down Expand Up @@ -236,7 +292,7 @@ class CheckChangeSetCreatedTask(
class ExecuteChangeSetTask(
region: Region,
stackLookup: CloudFormationStackMetadata
)(implicit val keyRing: KeyRing, artifactClient: S3Client)
)(implicit val keyRing: KeyRing)
extends Task {
override def execute(
resources: DeploymentResources,
Expand Down Expand Up @@ -303,7 +359,7 @@ class ExecuteChangeSetTask(
class DeleteChangeSetTask(
region: Region,
stackLookup: CloudFormationStackMetadata
)(implicit val keyRing: KeyRing, artifactClient: S3Client)
)(implicit val keyRing: KeyRing)
extends Task {
override def execute(
resources: DeploymentResources,
Expand Down

0 comments on commit 861bd87

Please sign in to comment.