2019-07-07 12:52:55 -08:00
package devops
import (
2019-07-08 12:21:22 -08:00
"encoding/json"
2019-07-07 12:52:55 -08:00
"fmt"
2019-07-08 19:13:41 -08:00
"gopkg.in/go-playground/validator.v9"
"log"
"os"
"os/exec"
"path/filepath"
"regexp"
"strconv"
"strings"
2019-07-07 12:52:55 -08:00
"geeks-accelerator/oss/saas-starter-kit/example-project/internal/platform/tests"
2019-07-08 12:21:22 -08:00
"github.com/aws/aws-sdk-go/aws"
2019-07-07 12:52:55 -08:00
"github.com/aws/aws-sdk-go/aws/awserr"
2019-07-08 19:13:41 -08:00
"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
"github.com/aws/aws-sdk-go/service/ec2"
2019-07-08 12:21:22 -08:00
"github.com/aws/aws-sdk-go/service/ecr"
"github.com/aws/aws-sdk-go/service/ecs"
2019-07-08 19:13:41 -08:00
"github.com/aws/aws-sdk-go/service/elbv2"
2019-07-08 12:21:22 -08:00
"github.com/aws/aws-sdk-go/service/iam"
2019-07-07 12:52:55 -08:00
"github.com/aws/aws-sdk-go/service/secretsmanager"
2019-07-08 12:21:22 -08:00
"github.com/iancoleman/strcase"
2019-07-07 12:52:55 -08:00
"github.com/pkg/errors"
)
2019-07-08 12:21:22 -08:00
2019-07-08 19:13:41 -08:00
var requiredDeployIamPermissions = [ ] string {
"secretsmanager:GetSecretValue" ,
"ecr:GetAuthorizationToken" ,
"ecr:ListImages" ,
"ecr:DescribeRepositories" ,
"ecr:CreateRepository" ,
"ecs:CreateCluster" ,
"ecs:DescribeClusters" ,
"esc:RegisterTaskDefinition" ,
"cloudwatchlogs:DescribeLogGroups" ,
"cloudwatchlogs:CreateLogGroup" ,
"iam:CreateServiceLinkedRole" ,
"iam:PutRolePolicy" ,
2019-07-08 12:21:22 -08:00
}
2019-07-07 12:52:55 -08:00
// requiredCmdsBuild proves a list of required executables for completing build.
var requiredCmdsDeploy = [ ] [ ] string {
[ ] string { "docker" , "version" , "-f" , "{{.Client.Version}}" } ,
}
2019-07-08 19:13:41 -08:00
// NewServiceDeployRequest generated a new request for executing deploy for a given set of flags.
func NewServiceDeployRequest ( log * log . Logger , flags * ServiceDeployFlags ) ( * serviceDeployRequest , error ) {
2019-07-07 12:52:55 -08:00
2019-07-08 19:13:41 -08:00
log . Println ( "Validate flags." )
{
errs := validator . New ( ) . Struct ( flags )
if errs != nil {
return nil , errs
}
log . Printf ( "\t%s\tFlags ok." , tests . Success )
}
2019-07-07 12:52:55 -08:00
2019-07-08 19:13:41 -08:00
log . Println ( "\tVerify AWS credentials." )
var awsCreds * AwsCredentials
{
var err error
awsCreds , err = GetAwsCredentials ( flags . Env )
2019-07-07 12:52:55 -08:00
if err != nil {
2019-07-08 19:13:41 -08:00
return nil , err
2019-07-07 12:52:55 -08:00
}
2019-07-08 19:13:41 -08:00
log . Printf ( "\t\t\tAccessKeyID: %s" , awsCreds . AccessKeyID )
log . Printf ( "\t\t\tRegion: %s" , awsCreds . Region )
log . Printf ( "\t%s\tAWS credentials valid." , tests . Success )
2019-07-07 12:52:55 -08:00
}
2019-07-08 19:13:41 -08:00
log . Println ( "Generate deploy request." )
var req * serviceDeployRequest
2019-07-08 12:21:22 -08:00
{
2019-07-08 19:13:41 -08:00
req = & serviceDeployRequest {
// Required flags.
serviceName : flags . ServiceName ,
env : flags . Env ,
awsCreds : awsCreds ,
// Optional flags.
projectRoot : flags . ProjectRoot ,
projectName : flags . ProjectName ,
dockerFile : flags . DockerFile ,
enableLambdaVPC : flags . EnableLambdaVPC ,
enableEcsElb : flags . EnableEcsElb ,
noBuild : flags . NoBuild ,
noDeploy : flags . NoDeploy ,
noCache : flags . NoCache ,
noPush : flags . NoPush ,
}
2019-07-08 12:21:22 -08:00
// When project root directory is empty or set to current working path, then search for the project root by locating
// the go.mod file.
2019-07-08 19:13:41 -08:00
log . Println ( "\tDetermining the project root directory." )
{
if flags . ProjectRoot == "" || flags . ProjectRoot == "." {
log . Println ( "\tAttempting to location project root directory from current working directory." )
2019-07-08 12:21:22 -08:00
2019-07-08 19:13:41 -08:00
var err error
req . goModFile , err = findProjectGoModFile ( )
if err != nil {
return nil , err
}
req . projectRoot = filepath . Dir ( req . goModFile )
} else {
log . Println ( "\t\tUsing supplied project root directory." )
req . goModFile = filepath . Join ( flags . ProjectRoot , "go.mod" )
2019-07-08 12:21:22 -08:00
}
2019-07-08 19:13:41 -08:00
log . Printf ( "\t\t\tproject root: %s" , req . projectRoot )
log . Printf ( "\t\t\tgo.mod: %s" , req . goModFile )
2019-07-08 12:21:22 -08:00
}
log . Println ( "\tExtracting go module name from go.mod." )
2019-07-08 19:13:41 -08:00
{
var err error
req . goModName , err = loadGoModName ( req . goModFile )
if err != nil {
return nil , err
}
log . Printf ( "\t\t\tmodule name: %s" , req . goModName )
2019-07-07 12:52:55 -08:00
}
2019-07-08 12:21:22 -08:00
log . Println ( "\tDetermining the project name." )
2019-07-08 19:13:41 -08:00
{
if flags . ProjectName != "" {
req . projectName = flags . ProjectName
log . Printf ( "\t\tUse provided value." )
} else {
req . projectName = filepath . Base ( req . goModName )
log . Printf ( "\t\tSet from go module." )
}
log . Printf ( "\t\t\tproject name: %s" , req . projectName )
2019-07-08 12:21:22 -08:00
}
log . Println ( "\tAttempting to locate service directory from project root directory." )
{
2019-07-08 19:13:41 -08:00
if flags . DockerFile != "" {
req . dockerFile = flags . DockerFile
log . Printf ( "\t\tUse provided value." )
} else {
log . Printf ( "\t\tFind from project root looking for Dockerfile." )
var err error
req . dockerFile , err = findServiceDockerFile ( req . projectRoot , req . serviceName )
if err != nil {
return nil , err
}
2019-07-08 12:21:22 -08:00
}
2019-07-08 19:13:41 -08:00
req . serviceDir = filepath . Dir ( flags . DockerFile )
log . Printf ( "\t\t\tservice directory: %s" , req . serviceDir )
log . Printf ( "\t\t\tdockerfile: %s" , req . dockerFile )
2019-07-08 12:21:22 -08:00
}
2019-07-08 19:13:41 -08:00
2019-07-08 12:21:22 -08:00
log . Println ( "\tSet defaults not defined in env vars." )
{
2019-07-08 19:13:41 -08:00
// Set default AWS ECR Repository Name.
req . ecrRepositoryName = req . projectName
log . Printf ( "\t\t\tSet ECR Repository Name to '%s'." , req . ecrRepositoryName )
2019-07-08 12:21:22 -08:00
2019-07-08 19:13:41 -08:00
// Set default AWS ECR Regsistry Max Images.
req . ecrRepositoryMaxImages = defaultAwsRegistryMaxImages
log . Printf ( "\t\t\tSet ECR Regsistry Max Images to '%d'." , req . ecrRepositoryMaxImages )
// Set default AWS ECS Cluster Name.
req . ecsClusterName = req . projectName + "-" + req . env
log . Printf ( "\t\t\tSet ECS Cluster Name to '%s'." , req . ecsClusterName )
// Set default AWS ECS Service Name.
req . ecsServiceName = req . serviceName + "-" + req . env
log . Printf ( "\t\t\tSet ECS Service Name to '%s'." , req . ecsServiceName )
2019-07-08 12:21:22 -08:00
2019-07-08 19:13:41 -08:00
// Set default Cloudwatch Log Group Name.
req . cloudWatchLogGroupName = fmt . Sprintf ( "logs/env_%s/aws/ecs/cluster_%s/service_%s" , req . env , req . ecsClusterName , req . serviceName )
log . Printf ( "\t\t\tSet CloudWatch Log Group Name to '%s'." , req . cloudWatchLogGroupName )
// Set default EC2 Security Group Name.
req . ec2SecurityGroupName = req . ecsClusterName
log . Printf ( "\t\t\tSet ECS Security Group Name to '%s'." , req . ec2SecurityGroupName )
// Set ECS configs based on specified env.
if flags . Env == "prod" {
req . ecsServiceMinimumHealthyPercent = aws . Int64 ( 100 )
req . ecsServiceMaximumPercent = aws . Int64 ( 200 )
req . elbDeregistrationDelay = aws . Int ( 300 )
} else {
req . ecsServiceMinimumHealthyPercent = aws . Int64 ( 100 )
req . ecsServiceMaximumPercent = aws . Int64 ( 200 )
// force staging to deploy immediately without waiting for connections to drain
req . elbDeregistrationDelay = aws . Int ( 0 )
2019-07-08 12:21:22 -08:00
}
2019-07-08 19:13:41 -08:00
req . ecsServiceDesiredCount = 1
req . escServiceHealthCheckGracePeriodSeconds = aws . Int64 ( 60 )
2019-07-08 12:21:22 -08:00
log . Printf ( "\t%s\tDefaults set." , tests . Success )
}
2019-07-08 19:13:41 -08:00
log . Println ( "\tValidate request." )
errs := validator . New ( ) . Struct ( req )
if errs != nil {
return nil , errs
}
log . Printf ( "\t%s\tNew request generated." , tests . Success )
}
return req , nil
}
// Run is the main entrypoint for deploying a service for a given target env.
func ServiceDeploy ( log * log . Logger , req * serviceDeployRequest ) error {
//
log . Println ( "Verify required commands are installed." )
for _ , cmdVals := range requiredCmdsDeploy {
cmd := exec . Command ( cmdVals [ 0 ] , cmdVals [ 1 : ] ... )
cmd . Env = os . Environ ( )
out , err := cmd . CombinedOutput ( )
if err != nil {
return errors . WithMessagef ( err , "failed to execute %s - %s\n%s" , strings . Join ( cmdVals , " " ) , string ( out ) )
}
log . Printf ( "\t%s\t%s - %s" , tests . Success , cmdVals [ 0 ] , string ( out ) )
2019-07-07 12:52:55 -08:00
}
2019-07-08 12:21:22 -08:00
// Pull the current env variables to be passed in for command execution.
envVars := EnvVars ( os . Environ ( ) )
// Load the ECR repository.
log . Println ( "ECR - Get or create repository." )
var awsRepo * ecr . Repository
{
svc := ecr . New ( awsCreds . Session ( ) )
descRes , err := svc . DescribeRepositories ( & ecr . DescribeRepositoriesInput {
RepositoryNames : [ ] * string { aws . String ( awsCreds . EcrRepositoryName ) } ,
} )
if err != nil {
if aerr , ok := err . ( awserr . Error ) ; ! ok || aerr . Code ( ) != ecr . ErrCodeRepositoryNotFoundException {
return errors . Wrapf ( err , "failed to describe repository '%s'" , awsCreds . EcrRepositoryName )
}
} else if len ( descRes . Repositories ) > 0 {
awsRepo = descRes . Repositories [ 0 ]
2019-07-08 19:13:41 -08:00
}
2019-07-08 12:21:22 -08:00
2019-07-08 19:13:41 -08:00
if awsRepo == nil {
// If no repository was found, create one.
createRes , err := svc . CreateRepository ( & ecr . CreateRepositoryInput {
RepositoryName : aws . String ( awsCreds . EcrRepositoryName ) ,
Tags : [ ] * ecr . Tag {
& ecr . Tag { Key : aws . String ( awsTagNameProject ) , Value : aws . String ( flags . ProjectName ) } ,
& ecr . Tag { Key : aws . String ( awsTagNameEnv ) , Value : aws . String ( flags . Env ) } ,
} ,
} )
if err != nil {
return errors . Wrapf ( err , "failed to create repository '%s'" , awsCreds . EcrRepositoryName )
}
awsRepo = createRes . Repository
log . Printf ( "\t\tCreated: %s." , * awsRepo . RepositoryArn )
} else {
2019-07-08 12:21:22 -08:00
log . Printf ( "\t\tFound: %s." , * awsRepo . RepositoryArn )
log . Println ( "\t\tChecking old ECR images." )
delIds , err := EcrPurgeImages ( awsCreds )
if err != nil {
return err
}
// If there are image IDs to delete, delete them.
if len ( delIds ) > 0 {
log . Printf ( "\t\tDeleted %d images that exceeded limit of %d" , len ( delIds ) , awsCreds . EcrRepositoryMaxImages )
for _ , imgId := range delIds {
log . Printf ( "\t\t\t%s" , * imgId . ImageTag )
}
}
}
if len ( flags . BuildTags ) > 0 {
if flags . ReleaseImage == "" {
flags . ReleaseImage = * awsRepo . RepositoryUri + ":" + flags . BuildTags [ 0 ]
}
} else if flags . ReleaseImage == "" {
tag1 := flags . Env + "-" + flags . ServiceName
flags . BuildTags = append ( flags . BuildTags , tag1 )
if v := os . Getenv ( "CI_COMMIT_REF_NAME" ) ; v != "" {
tag2 := tag1 + "-" + v
flags . BuildTags = append ( flags . BuildTags , tag2 )
flags . ReleaseImage = * awsRepo . RepositoryUri + ":" + tag2
} else {
flags . ReleaseImage = * awsRepo . RepositoryUri + ":" + tag1
}
}
log . Printf ( "\t\trelease image: %s" , flags . ReleaseImage )
2019-07-07 12:52:55 -08:00
2019-07-08 12:21:22 -08:00
log . Printf ( "\t\ttags: %s" , strings . Join ( flags . BuildTags , " " ) )
log . Printf ( "\t%s\tRelease image valid." , tests . Success )
log . Println ( "ECR - Retrieve authorization token used for docker login." )
dockerLogin , err := GetEcrLogin ( awsCreds )
if err != nil {
return err
}
log . Println ( "\t\texecute docker login" )
_ , err = execCmds ( flags . ProjectRoot , & envVars , dockerLogin )
if err != nil {
return err
}
log . Printf ( "\t%s\tDocker login complete." , tests . Success )
2019-07-07 12:52:55 -08:00
}
2019-07-08 12:21:22 -08:00
// Do the docker build.
if flags . NoBuild == false {
cmdVals := [ ] string {
"docker" ,
"build" ,
"--file=" + flags . DockerFile ,
"--build-arg" , "service=" + flags . ServiceName ,
"--build-arg" , "env=" + flags . Env ,
2019-07-08 19:13:41 -08:00
"-t" , flags . ReleaseImage ,
2019-07-08 12:21:22 -08:00
}
// Append the build tags.
2019-07-08 19:13:41 -08:00
var builtImageTags [ ] string
for _ , t := range flags . BuildTags {
imageTag := flags . ReleaseImage + ":" + t
if imageTag == flags . ReleaseImage {
// skip duplicate image tags
continue
}
2019-07-08 12:21:22 -08:00
cmdVals = append ( cmdVals , "-t" )
2019-07-08 19:13:41 -08:00
cmdVals = append ( cmdVals , imageTag )
builtImageTags = append ( builtImageTags , imageTag )
2019-07-08 12:21:22 -08:00
}
if flags . NoCache == true {
cmdVals = append ( cmdVals , "--no-cache" )
}
cmdVals = append ( cmdVals , "." )
log . Printf ( "starting docker build: \n\t\t%s" , strings . Join ( cmdVals , " " ) )
out , err := execCmds ( flags . ProjectRoot , & envVars , cmdVals )
if err != nil {
return err
}
// Push the newly built docker container to the registry.
if flags . NoPush == false {
2019-07-08 19:13:41 -08:00
log . Printf ( "\t\tpush release image %s" , flags . ReleaseImage )
_ , err = execCmds ( flags . ProjectRoot , & envVars , [ ] string { "docker" , "push" , flags . ReleaseImage } )
2019-07-08 12:21:22 -08:00
if err != nil {
return err
}
// Push all the build tags.
2019-07-08 19:13:41 -08:00
for _ , t := range builtImageTags {
2019-07-08 12:21:22 -08:00
log . Printf ( "\t\tpush tag %s" , t )
2019-07-08 19:13:41 -08:00
_ , err = execCmds ( flags . ProjectRoot , & envVars , [ ] string { "docker" , "push" , t } )
2019-07-08 12:21:22 -08:00
if err != nil {
return err
}
}
}
log . Printf ( "\t%s\tbuild complete.\n" , tests . Success )
if flags . Debug {
log . Println ( string ( out [ 0 ] ) )
}
2019-07-07 12:52:55 -08:00
}
2019-07-08 12:21:22 -08:00
// Exit and don't continue if skip deploy.
if flags . NoDeploy == true {
return nil
2019-07-07 12:52:55 -08:00
}
2019-07-08 12:21:22 -08:00
log . Println ( "Datadog - Get API Key" )
var datadogApiKey string
{
// Load Datadog API Key which can be either stored in an env var or in AWS Secrets Manager.
// 1. Check env vars for [DEV|STAGE|PROD]_DD_API_KEY and DD_API_KEY
datadogApiKey = getTargetEnv ( flags . Env , "DD_API_KEY" )
// 2. Check AWS Secrets Manager for datadog entry prefixed with target env.
if datadogApiKey == "" {
prefixedSecretId := strings . ToUpper ( flags . Env ) + "/DATADOG"
var err error
datadogApiKey , err = GetAwsSecretValue ( awsCreds , prefixedSecretId )
if err != nil {
if aerr , ok := errors . Cause ( err ) . ( awserr . Error ) ; ! ok || aerr . Code ( ) != secretsmanager . ErrCodeResourceNotFoundException {
return err
}
}
}
// 3. Check AWS Secrets Manager for datadog entry.
if datadogApiKey == "" {
secretId := "DATADOG"
datadogApiKey , err = GetAwsSecretValue ( awsCreds , secretId )
if err != nil {
if aerr , ok := errors . Cause ( err ) . ( awserr . Error ) ; ! ok || aerr . Code ( ) != secretsmanager . ErrCodeResourceNotFoundException {
return err
}
}
}
2019-07-07 12:52:55 -08:00
2019-07-08 12:21:22 -08:00
if datadogApiKey != "" {
log . Printf ( "\t%s\tAPI Key set.\n" , tests . Success )
2019-07-07 12:52:55 -08:00
} else {
2019-07-08 12:21:22 -08:00
log . Printf ( "\t%s\tAPI Key NOT set.\n" , tests . Failed )
2019-07-07 12:52:55 -08:00
}
}
2019-07-08 12:21:22 -08:00
log . Println ( "CloudWatch Logs - Get or Create Log Group" )
{
2019-07-08 19:13:41 -08:00
svc := cloudwatchlogs . New ( awsCreds . Session ( ) )
/ * var logGroup * cloudwatchlogs . LogGroup
err := svc . DescribeLogGroupsPages ( & cloudwatchlogs . DescribeLogGroupsInput {
LogGroupNamePrefix : aws . String ( awsCreds . CloudWatchLogGroupName ) ,
} , func ( res * cloudwatchlogs . DescribeLogGroupsOutput , lastPage bool ) bool {
for _ , lg := range res . LogGroups {
if * lg . LogGroupName == awsCreds . CloudWatchLogGroupName {
logGroup = lg
return false
}
}
return ! lastPage
} )
if err != nil {
return errors . Wrapf ( err , "failed to describe log groups for prefix '%s'" , awsCreds . CloudWatchLogGroupName )
} * /
// If no log group was found, create one.
_ , err = svc . CreateLogGroup ( & cloudwatchlogs . CreateLogGroupInput {
LogGroupName : aws . String ( awsCreds . CloudWatchLogGroupName ) ,
Tags : map [ string ] * string {
awsTagNameProject : aws . String ( flags . ProjectName ) ,
awsTagNameEnv : aws . String ( flags . Env ) ,
} ,
} )
2019-07-08 12:21:22 -08:00
if err != nil {
2019-07-08 19:13:41 -08:00
if aerr , ok := err . ( awserr . Error ) ; ! ok || aerr . Code ( ) != cloudwatchlogs . ErrCodeResourceAlreadyExistsException {
return errors . Wrapf ( err , "failed to create log group '%s'" , awsCreds . CloudWatchLogGroupName )
}
2019-07-08 12:21:22 -08:00
log . Printf ( "\t\tFound: %s." , awsCreds . CloudWatchLogGroupName )
2019-07-08 19:13:41 -08:00
} else {
log . Printf ( "\t\tCreated: %s." , awsCreds . CloudWatchLogGroupName )
2019-07-08 12:21:22 -08:00
}
log . Printf ( "\t%s\tUsing Log Group '%s'.\n" , tests . Success , awsCreds . CloudWatchLogGroupName )
2019-07-07 12:52:55 -08:00
}
2019-07-08 12:21:22 -08:00
log . Println ( "ECS - Get or Create Cluster" )
var ecsCluster * ecs . Cluster
{
2019-07-08 19:13:41 -08:00
svc := ecs . New ( awsCreds . Session ( ) )
descRes , err := svc . DescribeClusters ( & ecs . DescribeClustersInput {
Clusters : [ ] * string { aws . String ( awsCreds . EcsClusterName ) } ,
} )
2019-07-08 12:21:22 -08:00
if err != nil {
2019-07-08 19:13:41 -08:00
if aerr , ok := err . ( awserr . Error ) ; ! ok || aerr . Code ( ) != ecs . ErrCodeClusterNotFoundException {
return errors . Wrapf ( err , "failed to describe cluster '%s'" , awsCreds . EcsClusterName )
}
} else if len ( descRes . Clusters ) > 0 {
ecsCluster = descRes . Clusters [ 0 ]
2019-07-08 12:21:22 -08:00
}
2019-07-07 12:52:55 -08:00
2019-07-08 19:13:41 -08:00
if ecsCluster == nil {
// If no repository was found, create one.
createRes , err := svc . CreateCluster ( & ecs . CreateClusterInput {
ClusterName : aws . String ( awsCreds . EcsClusterName ) ,
Tags : [ ] * ecs . Tag {
& ecs . Tag { Key : aws . String ( awsTagNameProject ) , Value : aws . String ( flags . ProjectName ) } ,
& ecs . Tag { Key : aws . String ( awsTagNameEnv ) , Value : aws . String ( flags . Env ) } ,
} ,
} )
if err != nil {
return errors . Wrapf ( err , "failed to create cluster '%s'" , awsCreds . EcsClusterName )
}
ecsCluster = createRes . Cluster
2019-07-08 12:21:22 -08:00
log . Printf ( "\t\tCreated: %s." , * ecsCluster . ClusterArn )
} else {
log . Printf ( "\t\tFound: %s." , * ecsCluster . ClusterArn )
// The number of services that are running on the cluster in an ACTIVE state.
// You can view these services with ListServices.
log . Printf ( "\t\t\tActiveServicesCount: %d." , * ecsCluster . ActiveServicesCount )
// The number of tasks in the cluster that are in the PENDING state.
log . Printf ( "\t\t\tPendingTasksCount: %d." , * ecsCluster . PendingTasksCount )
// The number of container instances registered into the cluster. This includes
// container instances in both ACTIVE and DRAINING status.
log . Printf ( "\t\t\tRegisteredContainerInstancesCount: %d." , * ecsCluster . RegisteredContainerInstancesCount )
// The number of tasks in the cluster that are in the RUNNING state.
log . Printf ( "\t\t\tRunningTasksCount: %d." , * ecsCluster . RunningTasksCount )
}
2019-07-07 12:52:55 -08:00
2019-07-08 12:21:22 -08:00
// The status of the cluster. The valid values are ACTIVE or INACTIVE. ACTIVE
// indicates that you can register container instances with the cluster and
// the associated instances can accept tasks.
log . Printf ( "\t\t\tStatus: %s." , * ecsCluster . Status )
2019-07-07 12:52:55 -08:00
2019-07-08 12:21:22 -08:00
log . Printf ( "\t%s\tUsing ECS Cluster '%s'.\n" , tests . Success , * ecsCluster . ClusterName )
2019-07-07 12:52:55 -08:00
}
2019-07-08 12:21:22 -08:00
log . Println ( "ECS - Register task definition" )
var taskDef * ecs . TaskDefinition
{
// List of placeholders that can be used in task definition and replaced on deployment.
placeholders := map [ string ] string {
"{SERVICE}" : flags . ServiceName ,
2019-07-08 19:13:41 -08:00
"{RELEASE_IMAGE}" : flags . ReleaseImage ,
2019-07-08 12:21:22 -08:00
"{ECS_CLUSTER}" : awsCreds . EcsClusterName ,
"{ECS_SERVICE}" : awsCreds . EcsServiceName ,
2019-07-08 19:13:41 -08:00
"{AWS_REGION}" : awsCreds . Region ,
2019-07-08 12:21:22 -08:00
"{AWSLOGS_GROUP}" : awsCreds . CloudWatchLogGroupName ,
"{ENV}" : flags . Env ,
"{DATADOG_APIKEY}" : datadogApiKey ,
}
2019-07-07 12:52:55 -08:00
2019-07-08 12:21:22 -08:00
// Loop through all the placeholders and create a list of keys to search json.
var pks [ ] string
for k , _ := range placeholders {
pks = append ( pks , k )
}
2019-07-07 12:52:55 -08:00
2019-07-08 12:21:22 -08:00
// Generate new regular expression for finding placeholders.
expr := "(" + strings . Join ( pks , "|" ) + ")"
r , err := regexp . Compile ( expr )
if err != nil {
return err
}
2019-07-07 12:52:55 -08:00
2019-07-08 12:21:22 -08:00
// Read the defined json task definition.
dat , err := EcsReadTaskDefinition ( flags . ServiceDir , flags . Env )
if err != nil {
return err
}
2019-07-07 12:52:55 -08:00
2019-07-08 12:21:22 -08:00
// Replace placeholders used in the JSON task definition.
{
jsonStr := string ( dat )
2019-07-07 12:52:55 -08:00
2019-07-08 12:21:22 -08:00
matches := r . FindAllString ( jsonStr , - 1 )
2019-07-07 12:52:55 -08:00
2019-07-08 12:21:22 -08:00
if len ( matches ) > 0 {
log . Println ( "\t\tUpdating placeholders." )
2019-07-07 12:52:55 -08:00
2019-07-08 12:21:22 -08:00
replaced := make ( map [ string ] bool )
for _ , m := range matches {
if replaced [ m ] {
continue
}
replaced [ m ] = true
2019-07-07 12:52:55 -08:00
2019-07-08 12:21:22 -08:00
newVal := placeholders [ m ]
log . Printf ( "\t\t\t%s -> %s" , m , newVal )
jsonStr = strings . Replace ( jsonStr , m , newVal , - 1 )
}
}
dat = [ ] byte ( jsonStr )
}
//if flags.Debug {
// log.Println(string(dat))
//}
log . Println ( "\t\tParse JSON to task definition." )
taskDefInput , err := parseTaskDefinitionInput ( dat )
2019-07-07 12:52:55 -08:00
if err != nil {
return err
}
2019-07-08 19:13:41 -08:00
// If a task definition value is empty, populate it with the default value.
2019-07-08 12:21:22 -08:00
if taskDefInput . Family == nil || * taskDefInput . Family == "" {
taskDefInput . Family = & flags . ServiceName
}
if len ( taskDefInput . ContainerDefinitions ) > 0 {
if taskDefInput . ContainerDefinitions [ 0 ] . Name == nil || * taskDefInput . ContainerDefinitions [ 0 ] . Name == "" {
taskDefInput . ContainerDefinitions [ 0 ] . Name = & awsCreds . EcsServiceName
}
if taskDefInput . ContainerDefinitions [ 0 ] . Image == nil || * taskDefInput . ContainerDefinitions [ 0 ] . Image == "" {
2019-07-08 19:13:41 -08:00
taskDefInput . ContainerDefinitions [ 0 ] . Image = & flags . ReleaseImage
2019-07-08 12:21:22 -08:00
}
}
//if flags.Debug {
// d, _ := json.Marshal(taskDef)
// log.Println(string(d))
//}
log . Printf ( "\t\t\tFamily: %s" , * taskDefInput . Family )
log . Printf ( "\t\t\tExecutionRoleArn: %s" , * taskDefInput . ExecutionRoleArn )
if taskDefInput . TaskRoleArn != nil {
log . Printf ( "\t\t\tTaskRoleArn: %s" , * taskDefInput . TaskRoleArn )
}
if taskDefInput . NetworkMode != nil {
log . Printf ( "\t\t\tNetworkMode: %s" , * taskDefInput . NetworkMode )
}
log . Printf ( "\t\t\tTaskDefinitions: %d" , len ( taskDefInput . ContainerDefinitions ) )
// If memory or cpu for the task is not set, need to compute from container definitions.
if ( taskDefInput . Cpu == nil || * taskDefInput . Cpu == "" ) || ( taskDefInput . Memory == nil || * taskDefInput . Memory == "" ) {
log . Println ( "\t\tCompute CPU and Memory for task definition." )
var (
totalMemory int64
totalCpu int64
)
for _ , c := range taskDefInput . ContainerDefinitions {
if c . Memory != nil {
totalMemory = totalMemory + * c . Memory
} else if c . MemoryReservation != nil {
totalMemory = totalMemory + * c . MemoryReservation
} else {
totalMemory = totalMemory + 1
}
if c . Cpu != nil {
totalCpu = totalCpu + * c . Cpu
} else {
totalCpu = totalCpu + 1
}
}
log . Printf ( "\t\t\tContainer Definitions has defined total memory %d and cpu %d" , totalMemory , totalCpu )
var (
selectedMemory int64
selectedCpu int64
)
if totalMemory < 8192 {
if totalMemory > 7168 {
selectedMemory = 8192
if totalCpu >= 2048 {
selectedCpu = 4096
} else if totalCpu >= 1024 {
selectedCpu = 2048
} else {
selectedCpu = 1024
}
} else if totalMemory > 6144 {
selectedMemory = 7168
if totalCpu >= 2048 {
selectedCpu = 4096
} else if totalCpu >= 1024 {
selectedCpu = 2048
} else {
selectedCpu = 1024
}
} else if totalMemory > 5120 || totalCpu >= 1024 {
selectedMemory = 6144
if totalCpu >= 2048 {
selectedCpu = 4096
} else if totalCpu >= 1024 {
selectedCpu = 2048
} else {
selectedCpu = 1024
}
} else if totalMemory > 4096 {
selectedMemory = 5120
if totalCpu >= 512 {
selectedCpu = 1024
} else {
selectedCpu = 512
}
} else if totalMemory > 3072 {
selectedMemory = 4096
if totalCpu >= 512 {
selectedCpu = 1024
} else {
selectedCpu = 512
}
} else if totalMemory > 2048 || totalCpu >= 512 {
selectedMemory = 3072
if totalCpu >= 512 {
selectedCpu = 1024
} else {
selectedCpu = 512
}
} else if totalMemory > 1024 || totalCpu >= 256 {
selectedMemory = 2048
if totalCpu >= 256 {
if totalCpu >= 512 {
selectedCpu = 1024
} else {
selectedCpu = 512
}
} else {
selectedCpu = 256
}
} else if totalMemory > 512 {
selectedMemory = 1024
if totalCpu >= 256 {
selectedCpu = 512
} else {
selectedCpu = 256
}
} else {
selectedMemory = 512
selectedCpu = 256
}
}
log . Printf ( "\t\t\tSelected memory %d and cpu %d" , selectedMemory , selectedCpu )
taskDefInput . Memory = aws . String ( strconv . Itoa ( int ( selectedMemory ) ) )
taskDefInput . Cpu = aws . String ( strconv . Itoa ( int ( selectedCpu ) ) )
}
log . Printf ( "\t%s\tLoaded task definition complete.\n" , tests . Success )
// The execution role is the IAM role that executes ECS actions such as pulling the image and storing the
// application logs in cloudwatch.
if taskDefInput . ExecutionRoleArn == nil || * taskDefInput . ExecutionRoleArn == "" {
2019-07-08 19:13:41 -08:00
if flags . EcsExecutionRoleArn != "" {
taskDefInput . ExecutionRoleArn = & flags . EcsExecutionRoleArn
log . Printf ( "\t%s\tExecutionRoleArn updated.\n" , tests . Success )
} else {
svc := iam . New ( awsCreds . Session ( ) )
2019-07-08 12:21:22 -08:00
2019-07-08 19:13:41 -08:00
/ *
res , err := svc . CreateServiceLinkedRole ( & iam . CreateServiceLinkedRoleInput {
AWSServiceName : aws . String ( "ecs.amazonaws.com" ) ,
Description : aws . String ( "" ) ,
2019-07-08 12:21:22 -08:00
} )
if err != nil {
2019-07-08 19:13:41 -08:00
return errors . Wrapf ( err , "failed to register task definition '%s'" , * taskDef . Family )
2019-07-08 12:21:22 -08:00
}
taskDefInput . ExecutionRoleArn = res . Role . Arn
2019-07-08 19:13:41 -08:00
* /
2019-07-08 12:21:22 -08:00
2019-07-08 19:13:41 -08:00
// Find or create role for ExecutionRoleArn.
{
roleName := fmt . Sprintf ( "ecsExecutionRole%s%s" , flags . ProjectNameCamel ( ) , strcase . ToCamel ( flags . Env ) )
log . Printf ( "\tAppend ExecutionRoleArn to task definition input for role %s." , roleName )
2019-07-08 12:21:22 -08:00
2019-07-08 19:13:41 -08:00
res , err := svc . GetRole ( & iam . GetRoleInput {
RoleName : aws . String ( roleName ) ,
2019-07-08 12:21:22 -08:00
} )
if err != nil {
2019-07-08 19:13:41 -08:00
if aerr , ok := err . ( awserr . Error ) ; ! ok || aerr . Code ( ) != iam . ErrCodeNoSuchEntityException {
return errors . Wrapf ( err , "failed to find task role '%s'" , roleName )
}
2019-07-08 12:21:22 -08:00
}
2019-07-08 19:13:41 -08:00
if res . Role != nil {
taskDefInput . ExecutionRoleArn = res . Role . Arn
log . Printf ( "\t\t\tFound role '%s'" , * taskDefInput . ExecutionRoleArn )
} else {
// If no repository was found, create one.
res , err := svc . CreateRole ( & iam . CreateRoleInput {
RoleName : aws . String ( roleName ) ,
Description : aws . String ( fmt . Sprintf ( "Provides access to other AWS service resources that are required to run Amazon ECS tasks for %s. " , flags . ProjectName ) ) ,
AssumeRolePolicyDocument : aws . String ( "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":[\"ecs.amazonaws.com\"]},\"Action\":[\"sts:AssumeRole\"]}]}" ) ,
Tags : [ ] * iam . Tag {
& iam . Tag { Key : aws . String ( awsTagNameProject ) , Value : aws . String ( flags . ProjectName ) } ,
& iam . Tag { Key : aws . String ( awsTagNameEnv ) , Value : aws . String ( flags . Env ) } ,
} ,
2019-07-08 12:21:22 -08:00
2019-07-08 19:13:41 -08:00
} )
if err != nil {
return errors . Wrapf ( err , "failed to create task role '%s'" , roleName )
}
taskDefInput . ExecutionRoleArn = res . Role . Arn
log . Printf ( "\t\t\tCreated role '%s'" , * taskDefInput . ExecutionRoleArn )
}
policyArns := [ ] string {
"arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" ,
}
for _ , policyArn := range policyArns {
_ , err = svc . AttachRolePolicy ( & iam . AttachRolePolicyInput {
PolicyArn : aws . String ( policyArn ) ,
RoleName : aws . String ( roleName ) ,
} )
if err != nil {
return errors . Wrapf ( err , "failed to attach policy '%s' to task role '%s'" , policyArn , roleName )
}
log . Printf ( "\t\t\t\tAttached Policy '%s'" , policyArn )
}
log . Printf ( "\t%s\tExecutionRoleArn updated.\n" , tests . Success )
}
}
2019-07-08 12:21:22 -08:00
}
// The task role is the IAM role used by the task itself to access other AWS Services. To access services
// like S3, SQS, etc then those permissions would need to be covered by the TaskRole.
if taskDefInput . TaskRoleArn == nil || * taskDefInput . TaskRoleArn == "" {
2019-07-08 19:13:41 -08:00
if flags . EcsTaskRoleArn != "" {
taskDefInput . TaskRoleArn = & flags . EcsTaskRoleArn
log . Printf ( "\t%s\tTaskRoleArn updated.\n" , tests . Success )
} else {
svc := iam . New ( awsCreds . Session ( ) )
// Find or create the default service policy.
var policyArn string
{
policyName := fmt . Sprintf ( "%s%sServices" , flags . ProjectNameCamel ( ) , strcase . ToCamel ( flags . Env ) )
log . Printf ( "\tFind default service policy %s." , policyName )
var policyVersionId string
err = svc . ListPoliciesPages ( & iam . ListPoliciesInput { } , func ( res * iam . ListPoliciesOutput , lastPage bool ) bool {
for _ , p := range res . Policies {
if * p . PolicyName == policyName {
policyArn = * p . Arn
policyVersionId = * p . DefaultVersionId
return false
}
2019-07-08 12:21:22 -08:00
}
2019-07-08 19:13:41 -08:00
return ! lastPage
} )
if err != nil {
return errors . Wrap ( err , "failed to list IAM policies" )
2019-07-08 12:21:22 -08:00
}
2019-07-08 19:13:41 -08:00
if policyArn != "" {
log . Printf ( "\t\t\tFound policy '%s' versionId '%s'" , policyArn , policyVersionId )
2019-07-08 12:21:22 -08:00
2019-07-08 19:13:41 -08:00
res , err := svc . GetPolicyVersion ( & iam . GetPolicyVersionInput {
PolicyArn : aws . String ( policyArn ) ,
VersionId : aws . String ( policyVersionId ) ,
} )
if err != nil {
if aerr , ok := err . ( awserr . Error ) ; ! ok || aerr . Code ( ) != iam . ErrCodeNoSuchEntityException {
return errors . Wrapf ( err , "failed to read policy '%s' version '%s'" , policyName , policyVersionId )
}
}
2019-07-08 12:21:22 -08:00
2019-07-08 19:13:41 -08:00
// Compare policy documents and add any missing actions for each statement by matching Sid.
var curDoc IamPolicyDocument
err = json . Unmarshal ( [ ] byte ( * res . PolicyVersion . Document ) , & curDoc )
if err != nil {
return errors . Wrap ( err , "failed to json decode policy document" )
2019-07-08 12:21:22 -08:00
}
2019-07-08 19:13:41 -08:00
var updateDoc bool
for _ , baseStmt := range baseServicePolicyDocument . Statement {
var found bool
for curIdx , curStmt := range curDoc . Statement {
if baseStmt . Sid != curStmt . Sid {
continue
}
2019-07-08 12:21:22 -08:00
2019-07-08 19:13:41 -08:00
for _ , baseAction := range baseStmt . Action {
var hasAction bool
for _ , curAction := range curStmt . Action {
if baseAction == curAction {
hasAction = true
break
}
2019-07-08 12:21:22 -08:00
}
2019-07-08 19:13:41 -08:00
if ! hasAction {
log . Printf ( "\t\t\t\tAdded new action %s for '%s'" , curStmt . Sid )
curStmt . Action = append ( curStmt . Action , baseAction )
curDoc . Statement [ curIdx ] = curStmt
updateDoc = true
}
2019-07-08 12:21:22 -08:00
}
2019-07-08 19:13:41 -08:00
}
2019-07-08 12:21:22 -08:00
2019-07-08 19:13:41 -08:00
if ! found {
log . Printf ( "\t\t\t\tAdded new statement '%s'" , baseStmt . Sid )
curDoc . Statement = append ( curDoc . Statement , baseStmt )
updateDoc = true
2019-07-08 12:21:22 -08:00
}
}
2019-07-08 19:13:41 -08:00
if updateDoc {
dat , err := json . Marshal ( curDoc )
if err != nil {
return errors . Wrap ( err , "failed to json encode policy document" )
}
2019-07-08 12:21:22 -08:00
2019-07-08 19:13:41 -08:00
_ , err = svc . CreatePolicyVersion ( & iam . CreatePolicyVersionInput {
PolicyArn : aws . String ( policyArn ) ,
PolicyDocument : aws . String ( string ( dat ) ) ,
SetAsDefault : aws . Bool ( true ) ,
} )
if err != nil {
if aerr , ok := err . ( awserr . Error ) ; ! ok || aerr . Code ( ) != iam . ErrCodeNoSuchEntityException {
return errors . Wrapf ( err , "failed to read policy '%s' version '%s'" , policyName , policyVersionId )
}
}
}
} else {
dat , err := json . Marshal ( baseServicePolicyDocument )
2019-07-08 12:21:22 -08:00
if err != nil {
return errors . Wrap ( err , "failed to json encode policy document" )
}
2019-07-08 19:13:41 -08:00
// If no repository was found, create one.
res , err := svc . CreatePolicy ( & iam . CreatePolicyInput {
PolicyName : aws . String ( policyName ) ,
Description : aws . String ( fmt . Sprintf ( "Defines access for %s services. " , flags . ProjectName ) ) ,
2019-07-08 12:21:22 -08:00
PolicyDocument : aws . String ( string ( dat ) ) ,
2019-07-08 19:13:41 -08:00
2019-07-08 12:21:22 -08:00
} )
if err != nil {
2019-07-08 19:13:41 -08:00
return errors . Wrapf ( err , "failed to create task policy '%s'" , policyName )
2019-07-08 12:21:22 -08:00
}
2019-07-08 19:13:41 -08:00
policyArn = * res . Policy . Arn
2019-07-08 12:21:22 -08:00
2019-07-08 19:13:41 -08:00
log . Printf ( "\t\t\tCreated policy '%s'" , policyArn )
2019-07-08 12:21:22 -08:00
}
2019-07-08 19:13:41 -08:00
log . Printf ( "\t%s\tConfigured default service policy.\n" , tests . Success )
2019-07-08 12:21:22 -08:00
}
2019-07-08 19:13:41 -08:00
// Find or create role for TaskRoleArn.
{
roleName := fmt . Sprintf ( "ecsTaskRole%s%s" , flags . ProjectNameCamel ( ) , strcase . ToCamel ( flags . Env ) )
log . Printf ( "\tAppend TaskRoleArn to task definition input for role %s." , roleName )
2019-07-08 12:21:22 -08:00
2019-07-08 19:13:41 -08:00
res , err := svc . GetRole ( & iam . GetRoleInput {
RoleName : aws . String ( roleName ) ,
} )
if err != nil {
if aerr , ok := err . ( awserr . Error ) ; ! ok || aerr . Code ( ) != iam . ErrCodeNoSuchEntityException {
return errors . Wrapf ( err , "failed to find task role '%s'" , roleName )
}
2019-07-08 12:21:22 -08:00
}
2019-07-08 19:13:41 -08:00
if res . Role != nil {
taskDefInput . TaskRoleArn = res . Role . Arn
log . Printf ( "\t\t\tFound role '%s'" , * taskDefInput . TaskRoleArn )
} else {
// If no repository was found, create one.
res , err := svc . CreateRole ( & iam . CreateRoleInput {
RoleName : aws . String ( roleName ) ,
Description : aws . String ( fmt . Sprintf ( "Allows ECS tasks for %s to call AWS services on your behalf. " , flags . ProjectName ) ) ,
AssumeRolePolicyDocument : aws . String ( "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":[\"ecs-tasks.amazonaws.com\"]},\"Action\":[\"sts:AssumeRole\"]}]}" ) ,
Tags : [ ] * iam . Tag {
& iam . Tag { Key : aws . String ( awsTagNameProject ) , Value : aws . String ( flags . ProjectName ) } ,
& iam . Tag { Key : aws . String ( awsTagNameEnv ) , Value : aws . String ( flags . Env ) } ,
} ,
} )
if err != nil {
return errors . Wrapf ( err , "failed to create task role '%s'" , roleName )
}
taskDefInput . TaskRoleArn = res . Role . Arn
log . Printf ( "\t\t\tCreated role '%s'" , * taskDefInput . TaskRoleArn )
//_, err = svc.UpdateAssumeRolePolicy(&iam.UpdateAssumeRolePolicyInput{
// PolicyDocument: ,
// RoleName: aws.String(roleName),
//})
//if err != nil {
// return errors.Wrapf(err, "failed to create task role '%s'", roleName)
//}
}
2019-07-08 12:21:22 -08:00
2019-07-08 19:13:41 -08:00
_ , err = svc . AttachRolePolicy ( & iam . AttachRolePolicyInput {
PolicyArn : aws . String ( policyArn ) ,
RoleName : aws . String ( roleName ) ,
2019-07-08 12:21:22 -08:00
} )
if err != nil {
2019-07-08 19:13:41 -08:00
return errors . Wrapf ( err , "failed to attach policy '%s' to task role '%s'" , policyArn , roleName )
2019-07-08 12:21:22 -08:00
}
2019-07-08 19:13:41 -08:00
log . Printf ( "\t%s\tTaskRoleArn updated.\n" , tests . Success )
2019-07-08 12:21:22 -08:00
}
}
}
log . Println ( "\tRegister new task definition." )
2019-07-08 19:13:41 -08:00
{
svc := ecs . New ( awsCreds . Session ( ) )
2019-07-07 12:52:55 -08:00
2019-07-08 19:13:41 -08:00
// Registers a new task.
res , err := svc . RegisterTaskDefinition ( taskDefInput )
if err != nil {
return errors . Wrapf ( err , "failed to register task definition '%s'" , * taskDefInput . Family )
}
taskDef = res . TaskDefinition
log . Printf ( "\t\tRegistered: %s." , * taskDef . TaskDefinitionArn )
log . Printf ( "\t\t\tRevision: %d." , * taskDef . Revision )
log . Printf ( "\t\t\tStatus: %s." , * taskDef . Status )
2019-07-07 12:52:55 -08:00
2019-07-08 19:13:41 -08:00
log . Printf ( "\t%s\tTask definition registered.\n" , tests . Success )
}
}
2019-07-07 12:52:55 -08:00
2019-07-08 12:21:22 -08:00
log . Println ( "ECS - Find Service" )
var ecsService * ecs . Service
{
svc := ecs . New ( awsCreds . Session ( ) )
2019-07-07 12:52:55 -08:00
2019-07-08 12:21:22 -08:00
res , err := svc . DescribeServices ( & ecs . DescribeServicesInput {
Services : [ ] * string { aws . String ( awsCreds . EcsServiceName ) } ,
} )
if err != nil {
if aerr , ok := err . ( awserr . Error ) ; ! ok || aerr . Code ( ) != ecs . ErrCodeServiceNotFoundException {
return errors . Wrapf ( err , "failed to describe service '%s'" , awsCreds . EcsServiceName )
}
} else if len ( res . Services ) > 0 {
ecsService = res . Services [ 0 ]
log . Printf ( "\t\tFound: %s." , * ecsService . ServiceArn )
// The desired number of instantiations of the task definition to keep running
// on the service. This value is specified when the service is created with
// CreateService, and it can be modified with UpdateService.
log . Printf ( "\t\t\tDesiredCount: %d." , * ecsService . DesiredCount )
// The number of tasks in the cluster that are in the PENDING state.
log . Printf ( "\t\t\tPendingCount: %d." , * ecsService . PendingCount )
// The number of tasks in the cluster that are in the RUNNING state.
log . Printf ( "\t\t\tRunningCount: %d." , * ecsService . RunningCount )
// The status of the service. The valid values are ACTIVE, DRAINING, or INACTIVE.
log . Printf ( "\t\t\tStatus: %s." , * ecsService . Status )
log . Printf ( "\t%s\tUsing ECS Service '%s'.\n" , tests . Success , * ecsService . ServiceName )
} else {
log . Printf ( "\t%s\tExisting ECS Service not found.\n" , tests . Success )
}
2019-07-07 12:52:55 -08:00
}
2019-07-08 12:21:22 -08:00
// If the service exists update the service, else create a new service.
2019-07-08 19:13:41 -08:00
if ecsService != nil && * ecsService . Status != "INACTIVE" {
2019-07-08 12:21:22 -08:00
log . Println ( "ECS - Update Service" )
svc := ecs . New ( awsCreds . Session ( ) )
// If the desired count is zero because it was spun down for termination of staging env, update to launch
// with at least once task running for the service.
desiredCount := * ecsService . DesiredCount
if desiredCount == 0 {
desiredCount = 1
2019-07-07 12:52:55 -08:00
}
2019-07-08 12:21:22 -08:00
_ , err = svc . UpdateService ( & ecs . UpdateServiceInput {
// The short name or full Amazon Resource Name (ARN) of the cluster that your
// service is running on. If you do not specify a cluster, the default cluster
// is assumed.
Cluster : ecsCluster . ClusterName ,
2019-07-08 19:13:41 -08:00
2019-07-08 12:21:22 -08:00
// The name of the service to update.
Service : ecsService . ServiceName ,
2019-07-08 19:13:41 -08:00
2019-07-08 12:21:22 -08:00
// The number of instantiations of the task to place and keep running in your
// service.
DesiredCount : aws . Int64 ( desiredCount ) ,
2019-07-08 19:13:41 -08:00
2019-07-08 12:21:22 -08:00
// Whether to force a new deployment of the service. Deployments are not forced
// by default. You can use this option to trigger a new deployment with no service
// definition changes. For example, you can update a service's tasks to use
// a newer Docker image with the same image/tag combination (my_image:latest)
// or to roll Fargate tasks onto a newer platform version.
ForceNewDeployment : aws . Bool ( false ) ,
2019-07-08 19:13:41 -08:00
2019-07-08 12:21:22 -08:00
// The period of time, in seconds, that the Amazon ECS service scheduler should
// ignore unhealthy Elastic Load Balancing target health checks after a task
// has first started. This is only valid if your service is configured to use
// a load balancer. If your service's tasks take a while to start and respond
// to Elastic Load Balancing health checks, you can specify a health check grace
// period of up to 1,800 seconds. During that time, the ECS service scheduler
// ignores the Elastic Load Balancing health check status. This grace period
// can prevent the ECS service scheduler from marking tasks as unhealthy and
// stopping them before they have time to come up.
HealthCheckGracePeriodSeconds : ecsService . HealthCheckGracePeriodSeconds ,
2019-07-08 19:13:41 -08:00
2019-07-08 12:21:22 -08:00
// The family and revision (family:revision) or full ARN of the task definition
// to run in your service. If a revision is not specified, the latest ACTIVE
// revision is used. If you modify the task definition with UpdateService, Amazon
// ECS spawns a task with the new version of the task definition and then stops
// an old task after the new version is running.
TaskDefinition : taskDef . TaskDefinitionArn ,
} )
if err != nil {
return errors . Wrapf ( err , "failed to update service '%s'" , * ecsService . ServiceName )
2019-07-07 12:52:55 -08:00
}
2019-07-08 12:21:22 -08:00
log . Printf ( "\t%s\tUpdated ECS Service '%s'.\n" , tests . Success , * ecsService . ServiceName )
} else {
2019-07-08 19:13:41 -08:00
log . Println ( "EC2 - Find Subnets" )
var subnetsIDs [ ] string
var vpcId string
{
svc := ec2 . New ( awsCreds . Session ( ) )
var subnets [ ] * ec2 . Subnet
if len ( flags . Ec2SubnetIds ) == 0 {
log . Println ( "\t\tFind all subnets are that default for each available AZ." )
err := svc . DescribeSubnetsPages ( & ec2 . DescribeSubnetsInput { } , func ( res * ec2 . DescribeSubnetsOutput , lastPage bool ) bool {
for _ , s := range res . Subnets {
if * s . DefaultForAz {
subnets = append ( subnets , s )
}
}
return ! lastPage
} )
if err != nil {
return errors . Wrap ( err , "failed to find default subnets" )
}
} else {
log . Println ( "\t\tFind all subnets for the IDs provided." )
err := svc . DescribeSubnetsPages ( & ec2 . DescribeSubnetsInput {
SubnetIds : aws . StringSlice ( flags . Ec2SubnetIds ) ,
} , func ( res * ec2 . DescribeSubnetsOutput , lastPage bool ) bool {
for _ , s := range res . Subnets {
subnets = append ( subnets , s )
}
return ! lastPage
} )
if err != nil {
return errors . Wrapf ( err , "failed to find subnets: %s" , strings . Join ( flags . Ec2SubnetIds , ", " ) )
} else if len ( flags . Ec2SubnetIds ) != len ( subnets ) {
return errors . Errorf ( "failed to find all subnets, expected %d, got %d" , len ( flags . Ec2SubnetIds ) != len ( subnets ) )
}
}
if len ( subnets ) == 0 {
return errors . New ( "failed to find any subnets, expected at least 1" )
}
for _ , s := range subnets {
if s . VpcId == nil {
continue
}
if vpcId == "" {
vpcId = * s . VpcId
} else if vpcId != * s . VpcId {
return errors . Errorf ( "invalid subnet %s, all subnets should belong to the same VPC, expected %s, got %s" , * s . SubnetId , vpcId , * s . VpcId )
}
subnetsIDs = append ( subnetsIDs , * s . SubnetId )
log . Printf ( "\t\t\t%s" , * s . SubnetId )
}
log . Printf ( "\t%s\tFound %d subnets.\n" , len ( subnets ) )
}
log . Println ( "EC2 - Find Security Group" )
{
svc := ec2 . New ( awsCreds . Session ( ) )
log . Printf ( "\t\tFind security group '%s'.\n" , flags . Ec2SecurityGroupName )
var securityGroupId string
err := svc . DescribeSecurityGroupsPages ( & ec2 . DescribeSecurityGroupsInput {
GroupNames : aws . StringSlice ( [ ] string { flags . Ec2SecurityGroupName } ) ,
} , func ( res * ec2 . DescribeSecurityGroupsOutput , lastPage bool ) bool {
for _ , s := range res . SecurityGroups {
if * s . GroupName == flags . Ec2SecurityGroupName {
securityGroupId = * s . GroupId
break
}
}
return ! lastPage
} )
if err != nil {
return errors . Wrapf ( err , "failed to find security group '%s'" , flags . Ec2SecurityGroupName )
}
if securityGroupId == "" {
// If no security group was found, create one.
_ , err = svc . CreateSecurityGroup ( & ec2 . CreateSecurityGroupInput {
// The name of the security group.
// Constraints: Up to 255 characters in length. Cannot start with sg-.
// Constraints for EC2-Classic: ASCII characters
// Constraints for EC2-VPC: a-z, A-Z, 0-9, spaces, and ._-:/()#,@[]+=&;{}!$*
// GroupName is a required field
GroupName : aws . String ( flags . Ec2SecurityGroupName ) ,
// A description for the security group. This is informational only.
// Constraints: Up to 255 characters in length
// Constraints for EC2-Classic: ASCII characters
// Constraints for EC2-VPC: a-z, A-Z, 0-9, spaces, and ._-:/()#,@[]+=&;{}!$*
// Description is a required field
Description : aws . String ( fmt . Sprintf ( "Security group for %s running on ECS cluster %s" , flags . ProjectName , awsCreds . EcsClusterName ) ) ,
// [EC2-VPC] The ID of the VPC. Required for EC2-VPC.
VpcId : aws . String ( vpcId ) ,
} )
if err != nil {
return errors . Wrapf ( err , "failed to create cluster '%s'" , awsCreds . EcsClusterName )
}
log . Printf ( "\t\tCreated: %s." , flags . Ec2SecurityGroupName )
} else {
log . Printf ( "\t\tFound: %s." , flags . Ec2SecurityGroupName )
}
ingressInputs := [ ] * ec2 . AuthorizeSecurityGroupIngressInput {
// Enable services to be publicly available via HTTP port 80
& ec2 . AuthorizeSecurityGroupIngressInput {
IpProtocol : aws . String ( "tcp" ) ,
CidrIp : aws . String ( "0.0.0.0/0" ) ,
FromPort : aws . Int64 ( 80 ) ,
ToPort : aws . Int64 ( 80 ) ,
} ,
// Allow all services in the security group to access other services via HTTP port 80.
& ec2 . AuthorizeSecurityGroupIngressInput {
IpProtocol : aws . String ( "tcp" ) ,
SourceSecurityGroupName : aws . String ( flags . Ec2SecurityGroupName ) ,
FromPort : aws . Int64 ( 80 ) ,
ToPort : aws . Int64 ( 80 ) ,
} ,
}
// When we are not using an Elastic Load Balancer, services need to support direct access via HTTPS.
// HTTPS is terminated via the web server and not on the Load Balancer.
if ! flags . EnableElb {
// Enable services to be publicly available via HTTPS port 443
ingressInputs = append ( ingressInputs , & ec2 . AuthorizeSecurityGroupIngressInput {
IpProtocol : aws . String ( "tcp" ) ,
CidrIp : aws . String ( "0.0.0.0/0" ) ,
FromPort : aws . Int64 ( 443 ) ,
ToPort : aws . Int64 ( 443 ) ,
} )
// Allow all services in the security group to access other services via HTTPS port 443.
ingressInputs = append ( ingressInputs , & ec2 . AuthorizeSecurityGroupIngressInput {
IpProtocol : aws . String ( "tcp" ) ,
SourceSecurityGroupName : aws . String ( flags . Ec2SecurityGroupName ) ,
FromPort : aws . Int64 ( 443 ) ,
ToPort : aws . Int64 ( 443 ) ,
} )
}
// Add all the default ingress to the security group.
for _ , ingressInput := range ingressInputs {
_ , err = svc . AuthorizeSecurityGroupIngress ( ingressInput )
if err != nil {
return errors . Wrapf ( err , "failed to add ingress for securuty group '%s'" , flags . Ec2SecurityGroupName )
}
}
log . Printf ( "\t%s\tUsing Security Group '%s'.\n" , tests . Success , flags . Ec2SecurityGroupName )
}
// If an Elastic Load Balancer is enabled, then ensure one exists else create one.
var ecsELBs [ ] * ecs . LoadBalancer
if flags . EnableElb {
log . Println ( "EC2 - Find Elastic Load Balance" )
svc := elbv2 . New ( awsCreds . Session ( ) )
// Set default EBL if needed.
var maintainELB bool
if flags . EnableElb && flags . ElbName == "" {
if ! strings . Contains ( awsCreds . EcsClusterName , flags . Env ) && ! strings . Contains ( flags . ServiceName , flags . Env ) {
// When a custom cluster name is provided and/or service name, ensure the ELB contains the current env.
flags . ElbName = fmt . Sprintf ( "%s-%s-%s" , awsCreds . EcsClusterName , flags . ServiceName , flags . Env )
} else {
// Default value when when custom cluster/service name is supplied.
flags . ElbName = fmt . Sprintf ( "%s-%s" , awsCreds . EcsClusterName , flags . ServiceName )
}
log . Printf ( "\t\t\tSet ELB Name to '%s'." , flags . ElbName )
// When not ELB name is provided and is assigned by us, we should manage the associated target groups
// and other properties.
maintainELB = true
}
var elb * elbv2 . LoadBalancer
err := svc . DescribeLoadBalancersPages ( & elbv2 . DescribeLoadBalancersInput {
Names : [ ] * string { aws . String ( flags . ElbName ) } ,
} , func ( res * elbv2 . DescribeLoadBalancersOutput , lastPage bool ) bool {
for _ , lb := range res . LoadBalancers {
if * lb . LoadBalancerName == flags . ElbName {
elb = lb
return false
}
}
return ! lastPage
} )
if err != nil {
if aerr , ok := err . ( awserr . Error ) ; ! ok || aerr . Code ( ) != elbv2 . ErrCodeLoadBalancerNotFoundException {
return errors . Wrapf ( err , "failed to describe load balance '%s'" , flags . ElbName )
}
}
if elb == nil {
// If no repository was found, create one.
createRes , err := svc . CreateLoadBalancer ( & elbv2 . CreateLoadBalancerInput {
// The name of the load balancer.
// This name must be unique per region per account, can have a maximum of 32
// characters, must contain only alphanumeric characters or hyphens, must not
// begin or end with a hyphen, and must not begin with "internal-".
// Name is a required field
Name : aws . String ( flags . ElbName ) ,
// [Application Load Balancers] The type of IP addresses used by the subnets
// for your load balancer. The possible values are ipv4 (for IPv4 addresses)
// and dualstack (for IPv4 and IPv6 addresses).
IpAddressType : aws . String ( "dualstack" ) ,
// The nodes of an Internet-facing load balancer have public IP addresses. The
// DNS name of an Internet-facing load balancer is publicly resolvable to the
// public IP addresses of the nodes. Therefore, Internet-facing load balancers
// can route requests from clients over the internet.
// The nodes of an internal load balancer have only private IP addresses. The
// DNS name of an internal load balancer is publicly resolvable to the private
// IP addresses of the nodes. Therefore, internal load balancers can only route
// requests from clients with access to the VPC for the load balancer.
Scheme : aws . String ( "Internet-facing" ) ,
// [Application Load Balancers] The IDs of the security groups for the load
// balancer.
SecurityGroups : aws . StringSlice ( [ ] string { flags . Ec2SecurityGroupName } ) ,
// The IDs of the public subnets. You can specify only one subnet per Availability
// Zone. You must specify either subnets or subnet mappings.
// [Application Load Balancers] You must specify subnets from at least two Availability
// Zones.
Subnets : aws . StringSlice ( subnetsIDs ) ,
// The type of load balancer.
Type : aws . String ( "application" ) ,
// One or more tags to assign to the load balancer.
Tags : [ ] * elbv2 . Tag {
& elbv2 . Tag { Key : aws . String ( awsTagNameProject ) , Value : aws . String ( flags . ProjectName ) } ,
& elbv2 . Tag { Key : aws . String ( awsTagNameEnv ) , Value : aws . String ( flags . Env ) } ,
} ,
} )
if err != nil {
return errors . Wrapf ( err , "failed to create cluster '%s'" , awsCreds . EcsClusterName )
}
elb = createRes . LoadBalancers [ 0 ]
log . Printf ( "\t\tCreated: %s." , * elb . LoadBalancerArn )
} else {
log . Printf ( "\t\tFound: %s." , * elb . LoadBalancerArn )
}
// The state code. The initial state of the load balancer is provisioning. After
// the load balancer is fully set up and ready to route traffic, its state is
// active. If the load balancer could not be set up, its state is failed.
log . Printf ( "\t\t\tState: %s." , * elb . State . Code )
if maintainELB {
targetGroupInputs := [ ] * elbv2 . CreateTargetGroupInput {
// Default target group for HTTP via port 80.
& elbv2 . CreateTargetGroupInput {
// The name of the target group.
// This name must be unique per region per account, can have a maximum of 32
// characters, must contain only alphanumeric characters or hyphens, and must
// not begin or end with a hyphen.
// Name is a required field
Name : aws . String ( fmt . Sprintf ( "%s-http" , * elb . LoadBalancerName ) ) ,
// The port on which the targets receive traffic. This port is used unless you
// specify a port override when registering the target. If the target is a Lambda
// function, this parameter does not apply.
Port : aws . Int64 ( 80 ) ,
// The protocol to use for routing traffic to the targets. For Application Load
// Balancers, the supported protocols are HTTP and HTTPS. For Network Load Balancers,
// the supported protocols are TCP, TLS, UDP, or TCP_UDP. A TCP_UDP listener
// must be associated with a TCP_UDP target group. If the target is a Lambda
// function, this parameter does not apply.
Protocol : aws . String ( "HTTP" ) ,
// Indicates whether health checks are enabled. If the target type is lambda,
// health checks are disabled by default but can be enabled. If the target type
// is instance or ip, health checks are always enabled and cannot be disabled.
HealthCheckEnabled : aws . Bool ( true ) ,
// The approximate amount of time, in seconds, between health checks of an individual
// target. For HTTP and HTTPS health checks, the range is 5–300 seconds. For
// TCP health checks, the supported values are 10 and 30 seconds. If the target
// type is instance or ip, the default is 30 seconds. If the target type is
// lambda, the default is 35 seconds.
HealthCheckIntervalSeconds : aws . Int64 ( 30 ) ,
// [HTTP/HTTPS health checks] The ping path that is the destination on the targets
// for health checks. The default is /.
HealthCheckPath : aws . String ( "/ping" ) ,
// The protocol the load balancer uses when performing health checks on targets.
// For Application Load Balancers, the default is HTTP. For Network Load Balancers,
// the default is TCP. The TCP protocol is supported for health checks only
// if the protocol of the target group is TCP, TLS, UDP, or TCP_UDP. The TLS,
// UDP, and TCP_UDP protocols are not supported for health checks.
HealthCheckProtocol : aws . String ( "HTTP" ) ,
// The amount of time, in seconds, during which no response from a target means
// a failed health check. For target groups with a protocol of HTTP or HTTPS,
// the default is 5 seconds. For target groups with a protocol of TCP or TLS,
// this value must be 6 seconds for HTTP health checks and 10 seconds for TCP
// and HTTPS health checks. If the target type is lambda, the default is 30
// seconds.
HealthCheckTimeoutSeconds : aws . Int64 ( 5 ) ,
// The number of consecutive health checks successes required before considering
// an unhealthy target healthy. For target groups with a protocol of HTTP or
// HTTPS, the default is 5. For target groups with a protocol of TCP or TLS,
// the default is 3. If the target type is lambda, the default is 5.
HealthyThresholdCount : aws . Int64 ( 3 ) ,
// The number of consecutive health check failures required before considering
// a target unhealthy. For target groups with a protocol of HTTP or HTTPS, the
// default is 2. For target groups with a protocol of TCP or TLS, this value
// must be the same as the healthy threshold count. If the target type is lambda,
// the default is 2.
UnhealthyThresholdCount : aws . Int64 ( 3 ) ,
// [HTTP/HTTPS health checks] The HTTP codes to use when checking for a successful
// response from a target.
Matcher : & elbv2 . Matcher {
HttpCode : aws . String ( "200" ) ,
} ,
// The type of target that you must specify when registering targets with this
// target group. You can't specify targets for a target group using more than
// one target type.
//
// * instance - Targets are specified by instance ID. This is the default
// value. If the target group protocol is UDP or TCP_UDP, the target type
// must be instance.
//
// * ip - Targets are specified by IP address. You can specify IP addresses
// from the subnets of the virtual private cloud (VPC) for the target group,
// the RFC 1918 range (10.0.0.0/8, 172.16.0.0/12, and 192.168.0.0/16), and
// the RFC 6598 range (100.64.0.0/10). You can't specify publicly routable
// IP addresses.
//
// * lambda - The target groups contains a single Lambda function.
TargetType : aws . String ( "ip" ) ,
// The identifier of the virtual private cloud (VPC). If the target is a Lambda
// function, this parameter does not apply.
VpcId : aws . String ( vpcId ) ,
} ,
// Default target group for HTTPS via port 443.
& elbv2 . CreateTargetGroupInput {
Name : aws . String ( fmt . Sprintf ( "%s-https" , * elb . LoadBalancerName ) ) ,
Port : aws . Int64 ( 443 ) ,
Protocol : aws . String ( "HTTPS" ) ,
HealthCheckEnabled : aws . Bool ( true ) ,
HealthCheckIntervalSeconds : aws . Int64 ( 30 ) ,
HealthCheckPath : aws . String ( "/ping" ) ,
HealthCheckProtocol : aws . String ( "HTTPS" ) ,
HealthCheckTimeoutSeconds : aws . Int64 ( 5 ) ,
HealthyThresholdCount : aws . Int64 ( 3 ) ,
UnhealthyThresholdCount : aws . Int64 ( 3 ) ,
Matcher : & elbv2 . Matcher {
HttpCode : aws . String ( "200" ) ,
} ,
TargetType : aws . String ( "ip" ) ,
VpcId : aws . String ( vpcId ) ,
} ,
}
for _ , targetGroupInput := range targetGroupInputs {
var targetGroup * elbv2 . TargetGroup
err = svc . DescribeTargetGroupsPages ( & elbv2 . DescribeTargetGroupsInput {
LoadBalancerArn : elb . LoadBalancerArn ,
Names : [ ] * string { aws . String ( flags . ElbName ) } ,
} , func ( res * elbv2 . DescribeTargetGroupsOutput , lastPage bool ) bool {
for _ , tg := range res . TargetGroups {
if * tg . TargetGroupName == * targetGroupInput . Name {
targetGroup = tg
return false
}
}
return ! lastPage
} )
if err != nil {
if aerr , ok := err . ( awserr . Error ) ; ! ok || aerr . Code ( ) != elbv2 . ErrCodeTargetGroupNotFoundException {
return errors . Wrapf ( err , "failed to describe target group '%s'" , * targetGroupInput . Name )
}
}
if targetGroup == nil {
// If no target group was found, create one.
createRes , err := svc . CreateTargetGroup ( targetGroupInput )
if err != nil {
return errors . Wrapf ( err , "failed to create target group '%s'" , * targetGroupInput . Name )
}
targetGroup = createRes . TargetGroups [ 0 ]
log . Printf ( "\t\tAdded target group: %s." , * targetGroup . TargetGroupArn )
} else {
log . Printf ( "\t\tHas target group: %s." , * targetGroup . TargetGroupArn )
}
ecsELBs = append ( ecsELBs , & ecs . LoadBalancer {
// The name of the container (as it appears in a container definition) to associate
// with the load balancer.
ContainerName : aws . String ( awsCreds . EcsServiceName ) ,
// The port on the container to associate with the load balancer. This port
// must correspond to a containerPort in the service's task definition. Your
// container instances must allow ingress traffic on the hostPort of the port
// mapping.
ContainerPort : targetGroup . Port ,
// The full Amazon Resource Name (ARN) of the Elastic Load Balancing target
// group or groups associated with a service or task set.
TargetGroupArn : targetGroup . TargetGroupArn ,
} )
if flags . elbDeregistrationDelay != - 1 {
// If no target group was found, create one.
_ , err = svc . ModifyTargetGroupAttributes ( & elbv2 . ModifyTargetGroupAttributesInput {
TargetGroupArn : targetGroup . TargetGroupArn ,
Attributes : [ ] * elbv2 . TargetGroupAttribute {
& elbv2 . TargetGroupAttribute {
// The name of the attribute.
Key : aws . String ( "deregistration_delay.timeout_seconds" ) ,
// The value of the attribute.
Value : aws . String ( strconv . Itoa ( flags . elbDeregistrationDelay ) ) ,
} ,
} ,
} )
if err != nil {
return errors . Wrapf ( err , "failed to modify target group '%s' attributes" , * targetGroupInput . Name )
}
log . Printf ( "\t\t\tSet sttributes." )
}
}
}
log . Printf ( "\t%s\tUsing ELB '%s'.\n" , tests . Success , * elb . LoadBalancerName )
}
2019-07-08 12:21:22 -08:00
log . Println ( "ECS - Create Service" )
2019-07-08 19:13:41 -08:00
{
2019-07-08 12:21:22 -08:00
2019-07-08 19:13:41 -08:00
svc := ecs . New ( awsCreds . Session ( ) )
var assignPublicIp * string
if len ( ecsELBs ) == 0 {
assignPublicIp = aws . String ( "ENABLED" )
} else {
assignPublicIp = aws . String ( "DISABLED" )
}
createRes , err := svc . CreateService ( & ecs . CreateServiceInput {
// The short name or full Amazon Resource Name (ARN) of the cluster that your
// service is running on. If you do not specify a cluster, the default cluster
// is assumed.
Cluster : ecsCluster . ClusterName ,
// The name of your service. Up to 255 letters (uppercase and lowercase), numbers,
// and hyphens are allowed. Service names must be unique within a cluster, but
// you can have similarly named services in multiple clusters within a Region
// or across multiple Regions.
//
// ServiceName is a required field
ServiceName : aws . String ( awsCreds . EcsServiceName ) ,
// Optional deployment parameters that control how many tasks run during the
// deployment and the ordering of stopping and starting tasks.
DeploymentConfiguration : & ecs . DeploymentConfiguration {
// Refer to documentation for flags.ecsServiceMaximumPercent
MaximumPercent : aws . Int64 ( int64 ( flags . ecsServiceMaximumPercent ) ) ,
// Refer to documentation for flags.ecsServiceMinimumHealthyPercent
MinimumHealthyPercent : aws . Int64 ( int64 ( flags . ecsServiceMinimumHealthyPercent ) ) ,
} ,
// Refer to documentation for flags.ecsServiceDesiredCount.
DesiredCount : aws . Int64 ( int64 ( flags . ecsServiceDesiredCount ) ) ,
// Specifies whether to enable Amazon ECS managed tags for the tasks within
// the service. For more information, see Tagging Your Amazon ECS Resources
// (https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-using-tags.html)
// in the Amazon Elastic Container Service Developer Guide.
EnableECSManagedTags : aws . Bool ( false ) ,
// The period of time, in seconds, that the Amazon ECS service scheduler should
// ignore unhealthy Elastic Load Balancing target health checks after a task
// has first started. This is only valid if your service is configured to use
// a load balancer. If your service's tasks take a while to start and respond
// to Elastic Load Balancing health checks, you can specify a health check grace
// period of up to 2,147,483,647 seconds. During that time, the ECS service
// scheduler ignores health check status. This grace period can prevent the
// ECS service scheduler from marking tasks as unhealthy and stopping them before
// they have time to come up.
HealthCheckGracePeriodSeconds : aws . Int64 ( int64 ( flags . escServiceHealthCheckGracePeriodSeconds ) ) ,
// The launch type on which to run your service. For more information, see Amazon
// ECS Launch Types (https://docs.aws.amazon.com/AmazonECS/latest/developerguide/launch_types.html)
// in the Amazon Elastic Container Service Developer Guide.
LaunchType : aws . String ( "FARGATE" ) ,
// A load balancer object representing the load balancer to use with your service.
LoadBalancers : ecsELBs ,
// The network configuration for the service. This parameter is required for
// task definitions that use the awsvpc network mode to receive their own elastic
// network interface, and it is not supported for other network modes. For more
// information, see Task Networking (https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-networking.html)
// in the Amazon Elastic Container Service Developer Guide.
NetworkConfiguration : & ecs . NetworkConfiguration {
AwsvpcConfiguration : & ecs . AwsVpcConfiguration {
// Whether the task's elastic network interface receives a public IP address.
// The default value is DISABLED.
AssignPublicIp : assignPublicIp ,
// The security groups associated with the task or service. If you do not specify
// a security group, the default security group for the VPC is used. There is
// a limit of 5 security groups that can be specified per AwsVpcConfiguration.
// All specified security groups must be from the same VPC.
SecurityGroups : aws . StringSlice ( [ ] string { flags . Ec2SecurityGroupName } ) ,
// The subnets associated with the task or service. There is a limit of 16 subnets
// that can be specified per AwsVpcConfiguration.
// All specified subnets must be from the same VPC.
// Subnets is a required field
Subnets : aws . StringSlice ( subnetsIDs ) ,
} ,
} ,
// The family and revision (family:revision) or full ARN of the task definition
// to run in your service. If a revision is not specified, the latest ACTIVE
// revision is used. If you modify the task definition with UpdateService, Amazon
// ECS spawns a task with the new version of the task definition and then stops
// an old task after the new version is running.
TaskDefinition : taskDef . TaskDefinitionArn ,
// The metadata that you apply to the service to help you categorize and organize
// them. Each tag consists of a key and an optional value, both of which you
// define. When a service is deleted, the tags are deleted as well. Tag keys
// can have a maximum character length of 128 characters, and tag values can
// have a maximum length of 256 characters.
Tags : [ ] * ecs . Tag {
& ecs . Tag { Key : aws . String ( awsTagNameProject ) , Value : aws . String ( flags . ProjectName ) } ,
& ecs . Tag { Key : aws . String ( awsTagNameEnv ) , Value : aws . String ( flags . Env ) } ,
} ,
} )
if err != nil {
return errors . Wrapf ( err , "failed to create service '%s'" , awsCreds . EcsServiceName )
}
ecsService = createRes . Service
log . Printf ( "\t%s\tCreated ECS Service '%s'.\n" , tests . Success , * ecsService . ServiceName )
}
2019-07-08 12:21:22 -08:00
2019-07-07 12:52:55 -08:00
}
2019-07-08 12:21:22 -08:00
2019-07-08 19:13:41 -08:00
// If Elastic cache is enabled, need to add ingress to security group
2019-07-08 12:21:22 -08:00
return nil
}