Skip to content

Commit

Permalink
Add instance update and reactivation
Browse files Browse the repository at this point in the history
Adds functionality to perform Bottlerocket version upgrades and
reactivates drained instances after successful upgrade.

[Issue: #9]
  • Loading branch information
Will Moore committed Apr 20, 2021
1 parent e393a8f commit fa949bb
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 2 deletions.
48 changes: 47 additions & 1 deletion updater/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"strings"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ecs"
"github.com/aws/aws-sdk-go/service/ssm"
)
Expand Down Expand Up @@ -200,7 +201,7 @@ func (u *updater) sendCommand(instanceIDs []string, ssmCommand string) (string,
return commandID, nil
}

func (u *updater) checkSSMCommandOutput(commandID string, instanceIDs []string) ([]string, error) {
func (u *updater) checkSSMCommandResult(commandID string, instanceIDs []string) ([]string, error) {
updateCandidates := make([]string, 0)
for _, v := range instanceIDs {
resp, err := u.ssm.GetCommandInvocation(&ssm.GetCommandInvocationInput{
Expand Down Expand Up @@ -234,3 +235,48 @@ func (u *updater) checkSSMCommandOutput(commandID string, instanceIDs []string)
}
return updateCandidates, nil
}

// getActiveVersion queries a Bottlerocket instance to determine the active version in use by the instance.
// Takes an SSM Command ID and EC2ID as parameters and returns the active version in use.
func (u *updater) getActiveVersion(commandID string, instanceID string) (string, error) {
ec2ID := string(instanceID)
resp, err := u.ssm.GetCommandInvocation(&ssm.GetCommandInvocationInput{
CommandId: aws.String(commandID),
InstanceId: aws.String(ec2ID),
})
if err != nil {
return "", fmt.Errorf("failed to retrieve command invocation output: %#v", err)
}

type Version struct {
Version string `json:"version"`
}

type Image struct {
Image Version `json:"image"`
}

type Partition struct {
ActivePartition Image `json:"active_partition"`
}

var version Partition
err = json.Unmarshal([]byte(aws.StringValue(resp.StandardOutputContent)), &version)
if err != nil {
log.Printf("failed to unmarshal command invocation output: %#v", err)
}
updatedVersion := version.ActivePartition.Image.Version
return updatedVersion, nil
}

// waitUntilOk takes an EC2 ID as a parameter and wait until the specified EC2 instnace is in an OK status.
func (u *updater) waitUntilOk(ec2ID string) error {
err := u.ec2.WaitUntilInstanceStatusOk(&ec2.DescribeInstanceStatusInput{
InstanceIds: []*string{aws.String(ec2ID)},
})
if err != nil {
log.Printf("Instance %s failed to restart: %#v", ec2ID, err)
return err
}
return nil
}
39 changes: 38 additions & 1 deletion updater/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ecs"
"github.com/aws/aws-sdk-go/service/ssm"
)
Expand All @@ -22,6 +23,7 @@ type updater struct {
cluster string
ecs *ecs.ECS
ssm *ssm.SSM
ec2 *ec2.EC2
}

func main() {
Expand Down Expand Up @@ -50,6 +52,7 @@ func _main() error {
cluster: *flagCluster,
ecs: ecs.New(sess, aws.NewConfig().WithLogLevel(aws.LogDebugWithHTTPBody)),
ssm: ssm.New(sess, aws.NewConfig().WithLogLevel(aws.LogDebugWithHTTPBody)),
ec2: ec2.New(sess, aws.NewConfig().WithLogLevel(aws.LogDebugWithHTTPBody)),
}

listedInstances, err := u.listContainerInstances()
Expand Down Expand Up @@ -78,7 +81,7 @@ func _main() error {
return err
}

candidates, err := u.checkSSMCommandOutput(commandID, instances)
candidates, err := u.checkSSMCommandResult(commandID, instances)
if err != nil {
return err
}
Expand All @@ -96,6 +99,40 @@ func _main() error {
continue
}
log.Printf("Instance %s drained", ec2ID)

checkedEC2IDAsSlice := []string{ec2ID}
if len(checkedEC2IDAsSlice) != 0 {
_, err := u.sendCommand(checkedEC2IDAsSlice, "apiclient update apply -r")
if err != nil {
log.Printf("%#v", err)
}

err = u.waitUntilOk(ec2ID)
if err != nil {
log.Printf("%#v", err)
}

err = u.activateInstance(aws.String(containerInstance))
if err != nil {
log.Printf("%#v", err)
}

updateStatus, err := u.sendCommand(checkedEC2IDAsSlice, "apiclient update check")
if err != nil {
log.Printf("%#v", err)
}

updateResult, _ := u.checkSSMCommandResult(updateStatus, checkedEC2IDAsSlice)
if err != nil {
log.Printf("%#v", err)
}
if len(updateResult) == 0 {
log.Printf("Update successfully applied")
}

updatedVersion, err := u.getActiveVersion(updateStatus, ec2ID)
log.Printf("Instance %s updated to Bottlerocket: %s", ec2ID, updatedVersion)
}
}
return nil
}

0 comments on commit fa949bb

Please sign in to comment.