Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

taskIds can have at most 100 items. #266

Closed
majoras-masque opened this issue Dec 22, 2022 · 4 comments
Closed

taskIds can have at most 100 items. #266

majoras-masque opened this issue Dec 22, 2022 · 4 comments

Comments

@majoras-masque
Copy link

Looks like this script might only work with ECS Services that have <=100 tasks in them, we're getting the following line repeating over and over:

An error occurred (InvalidParameterException) when calling the DescribeTasks operation: taskIds can have at most 100 items.

But then the script says:
Service updated successfully, new task definition running.

However, the service does not actually get updated. What's more, we are running the script as follows:
ecs-deploy --skip-deployments-check -t 2100 -r ${env.AWS_REGION} -c {params.CLUSTER_NAME} -n {params.SERVICE_NAME} -i {env.AWS_ACCT_ID}.dkr.ecr.${env.AWS_REGION}.amazonaws.com/api-server:${params.TAG}

Even if the script isn't handling pagination properly, I'd expect it not to matter since we're adding the --skip-deployments-check flag?

Anyone else running into this?

@nagibyro
Copy link

nagibyro commented Jan 3, 2023

Also ran into this issue while trying to deploy a service that has greater than 100 tasks running.

Looks like the issue is https://github.com/silinternational/ecs-deploy/blob/develop/ecs-deploy#L469-L471 -- the --tasks arg only allows up to 100 ids and the code doesn't check if there could be more.

A quick fix option maybe to change the jq query from

jq ".tasks[]| if .taskDefinitionArn == \"$NEW_TASKDEF\" then . else empty end`

to

jq "[limit(100;.tasks[])]| if .taskDefinitionArn == \"$NEW_TASKDEF\" then . else empty end

Though this probably needs to be put down into a loop to check a 100 at a time. I'm not completely clear on what the implications would be to only test a subset of all running tasks.

@majoras-masque
Copy link
Author

There's a PR to fix. We merged this branch into develop to get other fixes (and solve a few merge conflicts along the way), and everything seems to be working better for us! It also resolved the issue of --skip-deployments-check flag not working.

#230

@tsgoff
Copy link
Contributor

tsgoff commented Sep 12, 2024

Based on 3.10.0 this version with runtimePlatform and fixed ShellCheck is working for me with more than 100 Tasks running

#!/usr/bin/env bash

# Setup default values for variables
VERSION="3.10.0"
CLUSTER=false
SERVICE=false
TASK_DEFINITION=false
TASK_DEFINITION_FILE=false
MAX_DEFINITIONS=0
AWS_ASSUME_ROLE=false
IMAGE=false
MIN=false
MAX=false
TIMEOUT=90
VERBOSE=false
TAGVAR=false
TAGONLY=""
ENABLE_ROLLBACK=false
USE_MOST_RECENT_TASK_DEFINITION=false
AWS_CLI=$(which aws)
AWS_ECS="$AWS_CLI --output json ecs"
FORCE_NEW_DEPLOYMENT=false
SKIP_DEPLOYMENTS_CHECK=false
RUN_TASK=false
RUN_TASK_LAUNCH_TYPE=false
RUN_TASK_PLATFORM_VERSION=false
RUN_TASK_NETWORK_CONFIGURATION=false
RUN_TASK_WAIT_FOR_SUCCESS=false
TASK_DEFINITION_TAGS=false
COPY_TASK_DEFINITION_TAGS=false

function usage() {
    cat <<EOM
##### ecs-deploy #####
Simple script for triggering blue/green deployments on Amazon Elastic Container Service
https://github.com/silinternational/ecs-deploy

One of the following is required:
    -n | --service-name          Name of service to deploy
    -d | --task-definition       Name of task definition to deploy

Required arguments:
    -k | --aws-access-key        AWS Access Key ID. May also be set as environment variable AWS_ACCESS_KEY_ID
    -s | --aws-secret-key        AWS Secret Access Key. May also be set as environment variable AWS_SECRET_ACCESS_KEY
    -r | --region                AWS Region Name. May also be set as environment variable AWS_DEFAULT_REGION
    -p | --profile               AWS Profile to use - If you set this aws-access-key, aws-secret-key and region are needed
    -c | --cluster               Name of ECS cluster
    -i | --image                 Name of Docker image to run, ex: repo/image:latest
                                 Format: [domain][:port][/repo][/][image][:tag]
                                 Examples: mariadb, mariadb:latest, silintl/mariadb,
                                           silintl/mariadb:latest, private.registry.com:8000/repo/image:tag
    --aws-instance-profile       Use the IAM role associated with this instance

Optional arguments:
    -a | --aws-assume-role       ARN for AWS Role to assume for ecs-deploy operations.
    -D | --desired-count         The number of instantiations of the task to place and keep running in your service.
    -m | --min                   minumumHealthyPercent: The lower limit on the number of running tasks during a deployment.
    -M | --max                   maximumPercent: The upper limit on the number of running tasks during a deployment.
    -t | --timeout               Default is 90s. Script monitors ECS Service for new task definition to be running.
    -e | --tag-env-var           Get image tag name from environment variable. If provided this will override value
                                       specified in image name argument.
    -to | --tag-only             New tag to apply to all images defined in the task (multi-container task).
                                       If provided this will override value specified in image name argument.
    --max-definitions            Number of Task Definition Revisions to persist before deregistering oldest revisions.
    --task-definition-file       File used as task definition to deploy
    --enable-rollback            Rollback task definition if new version is not running before TIMEOUT
    --force-new-deployment       Force a new deployment of the service. Default is false.
    --use-latest-task-def        Will use the most recently created task definition as it's base, rather than the last used.
    --skip-deployments-check     Skip deployments check for services that take too long to drain old tasks
    --run-task                   Run created task now. If you set this, service-name are not needed.
    --wait-for-success           Wait for task execution to complete and to receive the exitCode 0.
    --launch-type                The launch type on which to run your task.
                                       (https://docs.aws.amazon.com/cli/latest/reference/ecs/run-task.html)
    --platform-version           The Fargate platform version on which to run your task.
                                       (https://docs.aws.amazon.com/cli/latest/reference/ecs/run-task.html)
    --network-configuration      The network configuration for the task. 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. (https://docs.aws.amazon.com/cli/latest/reference/ecs/run-task.html)
    --copy-task-definition-tags  Copy the existing task definition tags to the new task definition revision
    -v | --verbose               Verbose output
         --version               Display the version

Requirements:
    aws:  AWS Command Line Interface
    jq:   Command-line JSON processor

Examples:
  Simple deployment of a service (Using env vars for AWS settings):

    ecs-deploy -c production1 -n doorman-service -i docker.repo.com/doorman:latest

  All options:

    ecs-deploy -k ABC123 -s SECRETKEY -r us-east-1 -c production1 -n doorman-service -i docker.repo.com/doorman -t 240 -e CI_TIMESTAMP -v

  Updating a task definition with a new image:

    ecs-deploy -d open-door-task -i docker.repo.com/doorman:17

  Using profiles (for STS delegated credentials, for instance):

    ecs-deploy -p PROFILE -c production1 -n doorman-service -i docker.repo.com/doorman -t 240 -e CI_TIMESTAMP -v

  Update just the tag on whatever image is found in ECS Task (supports multi-container tasks):

    ecs-deploy -c staging -n core-service -to 0.1.899 -i ignore

Notes:
  - If a tag is not found in image and an ENV var is not used via -e and a tag is not provided with -to, it will default the tag to "latest"
EOM

    exit 3
}




# Check requirements
function require() {
    command -v "$1" > /dev/null 2>&1 || {
        echo "Some of the required software is not installed:"
        echo "    please install $1" >&2;
        exit 4;
    }
}

function assumeRole() {

   temp_role=$(aws sts assume-role \
                    --role-arn "${AWS_ASSUME_ROLE}" \
                    --role-session-name "$(date +"%s")")

   export AWS_ACCESS_KEY_ID=$(echo $temp_role | jq .Credentials.AccessKeyId | xargs)
   export AWS_SECRET_ACCESS_KEY=$(echo $temp_role | jq .Credentials.SecretAccessKey | xargs)
   export AWS_SESSION_TOKEN=$(echo $temp_role | jq .Credentials.SessionToken | xargs)
}


function assumeRoleClean() {
   unset AWS_ACCESS_KEY_ID
   unset AWS_SECRET_ACCESS_KEY
   unset AWS_SESSION_TOKEN
}


# Check that all required variables/combinations are set
function assertRequiredArgumentsSet() {

    # AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION and AWS_PROFILE can be set as environment variables
    if [ -z ${AWS_ACCESS_KEY_ID+x} ]; then unset AWS_ACCESS_KEY_ID; fi
    if [ -z ${AWS_SECRET_ACCESS_KEY+x} ]; then unset AWS_SECRET_ACCESS_KEY; fi
    if [ -z ${AWS_DEFAULT_REGION+x} ];
      then unset AWS_DEFAULT_REGION
      else
              AWS_ECS="$AWS_ECS --region $AWS_DEFAULT_REGION"
    fi
    if [ -z ${AWS_PROFILE+x} ];
      then unset AWS_PROFILE
      else
              AWS_ECS="$AWS_ECS --profile $AWS_PROFILE"
    fi

    if [ $SERVICE == false ] && [ $TASK_DEFINITION == false ]; then
        echo "One of SERVICE or TASK DEFINITION is required. You can pass the value using -n / --service-name for a service, or -d / --task-definition for a task"
        exit 5
    fi
    if [ $SERVICE != false ] && [ $TASK_DEFINITION != false ]; then
        echo "Only one of SERVICE or TASK DEFINITION may be specified, but you supplied both"
        exit 6
    fi
    if [ $SERVICE != false ] && [ $CLUSTER == false ]; then
        echo "CLUSTER is required. You can pass the value using -c or --cluster"
        exit 7
    fi
    if [ $IMAGE == false ] && [ $FORCE_NEW_DEPLOYMENT == false ]; then
        echo "IMAGE is required. You can pass the value using -i or --image"
        exit 8
    fi
    if ! [[ $MAX_DEFINITIONS =~ ^-?[0-9]+$ ]]; then
        echo "MAX_DEFINITIONS must be numeric, or not defined."
        exit 9
    fi

    if [ $RUN_TASK == false ] && [ $RUN_TASK_LAUNCH_TYPE != false ]; then
        echo 'LAUNCH TYPE requires setting RUN TASK argument. You can set it using --run-task flag.'
        exit 10
    fi

    if [ $RUN_TASK == false ] && [ $RUN_TASK_NETWORK_CONFIGURATION != false ]; then
        echo 'NETWORK CONFIGURATION requires setting RUN TASK argument. You can set it using --run-task flag.'
        exit 11
    fi

    if [ $RUN_TASK == false ] && [ $RUN_TASK_WAIT_FOR_SUCCESS != false ]; then
        echo 'WAIT FOR SUCCESS requires setting RUN TASK argument. You can set it using --run-task flag.'
        exit 11
    fi

    if [ $RUN_TASK == false ] && [ $RUN_TASK_PLATFORM_VERSION != false ]; then
        echo 'PLATFORM VERSION requires setting RUN TASK argument. You can set it using --run-task flag.'
        exit 12
    fi

}

function parseImageName() {

    # Define regex for image name
    # This regex will create groups for:
    # - domain
    # - port
    # - repo
    # - image
    # - tag
    # If a group is missing it will be an empty string
    if [[ "x$TAGONLY" == "x" ]]; then
       imageRegex="^([a-zA-Z0-9\.\-]+):?([0-9]+)?/([a-zA-Z0-9\._\-]+)(/[\/a-zA-Z0-9\._\-]+)?:?([a-zA-Z0-9\._\-]+)?$"
    else
       imageRegex="^:?([a-zA-Z0-9\._-]+)?$"
    fi

    if [[ $IMAGE =~ $imageRegex ]]; then
      # Define variables from matching groups
      if [[ "x$TAGONLY" == "x" ]]; then
        domain=${BASH_REMATCH[1]}
        port=${BASH_REMATCH[2]}
        repo=${BASH_REMATCH[3]}
        img=${BASH_REMATCH[4]/#\//}
        tag=${BASH_REMATCH[5]}

        # Validate what we received to make sure we have the pieces needed
        if [[ "x$domain" == "x" ]]; then
          echo "Image name does not contain a domain or repo as expected. See usage for supported formats."
          exit 10;
        fi
        if [[ "x$repo" == "x" ]]; then
          echo "Image name is missing the actual image name. See usage for supported formats."
          exit 11;
        fi

        # When a match for image is not found, the image name was picked up by the repo group, so reset variables
        if [[ "x$img" == "x" ]]; then
         img=$repo
         repo=""
        fi
      else
        tag=${BASH_REMATCH[1]}
        domain=""
        port=""
        repo=""
        img=""
      fi
    else
      # check if using root level repo with format like mariadb or mariadb:latest
      rootRepoRegex="^([a-zA-Z0-9\-]+):?([a-zA-Z0-9\.\-]+)?$"
      if [[ $IMAGE =~ $rootRepoRegex ]]; then
        img=${BASH_REMATCH[1]}
        if [[ "x$img" == "x" ]]; then
          echo "Invalid image name. See usage for supported formats."
          exit 12
        fi
        tag=${BASH_REMATCH[2]}

        # for root level repo, initialize unused variables for checks when rebuilding image below
        domain=""
        port=""
        repo=""

      else
        echo "Unable to parse image name: $IMAGE, check the format and try again"
        exit 13
      fi
    fi

    # If tag is missing make sure we can get it from env var, or use latest as default
    if [[ "x$tag" == "x" ]]; then
      if [[ $TAGVAR == false ]]; then
        tag="latest"
      else
        tag=${!TAGVAR}
        if [[ "x$tag" == "x" ]]; then
          tag="latest"
        fi
      fi
    fi

    # Reassemble image name
    useImage=""
    if [[ "x$TAGONLY" == "x" ]]; then

      if [[ ! -z "$domain" ]]; then
        useImage="$domain"
      fi
      if [[ ! -z "$port" ]]; then
        useImage="$useImage:$port"
      fi
      if [[ ! -z "$repo" ]]; then
        useImage="$useImage/$repo"
      fi
      if [[ ! -z "$img" ]]; then
        if [[ -z "$useImage" ]]; then
          useImage="$img"
        else
          useImage="$useImage/$img"
        fi
      fi
      imageWithoutTag="$useImage"
      if [[ ! -z "$tag" ]]; then
        useImage="$useImage:$tag"
      fi

    else
      useImage="$TAGONLY"
    fi

    # If in test mode output $useImage
    if [ "$BASH_SOURCE" != "$0" ]; then
      echo $useImage
    fi
}

function getCurrentTaskDefinition() {
    if [ $SERVICE != false ]; then
      # Get current task definition arn from service
      TASK_DEFINITION_ARN=`$AWS_ECS describe-services --services $SERVICE --cluster $CLUSTER | jq -r .services[0].taskDefinition`
      TASK_DEFINITION=`$AWS_ECS describe-task-definition --task-def $TASK_DEFINITION_ARN`

      # For rollbacks
      LAST_USED_TASK_DEFINITION_ARN=$TASK_DEFINITION_ARN

      if [ $USE_MOST_RECENT_TASK_DEFINITION != false ]; then
        # Use the most recently created TD of the family; rather than the most recently used.
        TASK_DEFINITION_FAMILY=`$AWS_ECS describe-task-definition --task-def $TASK_DEFINITION_ARN | jq -r .taskDefinition.family`
        TASK_DEFINITION=`$AWS_ECS describe-task-definition --task-def $TASK_DEFINITION_FAMILY`
        TASK_DEFINITION_ARN=`$AWS_ECS describe-task-definition --task-def $TASK_DEFINITION_FAMILY | jq -r .taskDefinition.taskDefinitionArn`
      fi
    elif [ $TASK_DEFINITION != false ]; then
      # Get current task definition arn from family[:revision] (or arn)
      TASK_DEFINITION_ARN=`$AWS_ECS describe-task-definition --task-def $TASK_DEFINITION | jq -r .taskDefinition.taskDefinitionArn`
    fi

    # Get task definition using current task definition arn
    # If we're copying task definition tags to the new revision, also get current task definition tags
    if [[ "$COPY_TASK_DEFINITION_TAGS" == true ]]; then
      TASK_DEFINITION=`$AWS_ECS describe-task-definition --task-def $TASK_DEFINITION_ARN --include TAGS`
      TASK_DEFINITION_TAGS=$( echo "$TASK_DEFINITION" | jq ".tags" )
    else
      TASK_DEFINITION=`$AWS_ECS describe-task-definition --task-def $TASK_DEFINITION_ARN`
    fi
}

function createNewTaskDefJson() {

    if [ $TASK_DEFINITION_FILE == false ]; then
        taskDefinition="$TASK_DEFINITION"
    else
        taskDefinition="$(cat $TASK_DEFINITION_FILE)"
    fi

    # Get a JSON representation of the current task definition
    # + Update definition to use new image name
    # + Filter the def
    if [[ "x$TAGONLY" == "x" ]]; then
      DEF=$( echo "$taskDefinition" \
            | sed -e 's~"image":.*'"${imageWithoutTag}"'.*,~"image": "'"${useImage}"'",~g' \
            | jq '.taskDefinition' )
    else
      DEF=$( echo "$taskDefinition" \
            | sed -e "s|\(\"image\": *\".*:\)\(.*\)\"|\1${useImage}\"|g" \
            | jq '.taskDefinition' )
    fi

    # Default JQ filter for new task definition
    NEW_DEF_JQ_FILTER="family: .family, volumes: .volumes, containerDefinitions: .containerDefinitions, placementConstraints: .placementConstraints"

    # Some options in task definition should only be included in new definition if present in
    # current definition. If found in current definition, append to JQ filter.
    CONDITIONAL_OPTIONS=(networkMode taskRoleArn placementConstraints executionRoleArn runtimePlatform)
    for i in "${CONDITIONAL_OPTIONS[@]}"; do
      re=".*${i}.*"
      if [[ "$DEF" =~ $re ]]; then
        NEW_DEF_JQ_FILTER="${NEW_DEF_JQ_FILTER}, ${i}: .${i}"
      fi
    done

    # Updated jq filters for AWS Fargate
    REQUIRES_COMPATIBILITIES=$(echo "${DEF}" | jq -r '. | select(.requiresCompatibilities != null) | .requiresCompatibilities[]')
    if echo "${REQUIRES_COMPATIBILITIES[@]}" | grep -q "FARGATE"; then
      FARGATE_JQ_FILTER='requiresCompatibilities: .requiresCompatibilities, cpu: .cpu, memory: .memory'

      if [[ ! "$NEW_DEF_JQ_FILTER" =~ .*executionRoleArn.* ]]; then
        FARGATE_JQ_FILTER="${FARGATE_JQ_FILTER}, executionRoleArn: .executionRoleArn"
      fi
      NEW_DEF_JQ_FILTER="${NEW_DEF_JQ_FILTER}, ${FARGATE_JQ_FILTER}"
    fi

    # Build new DEF with jq filter
    NEW_DEF=$(echo "$DEF" | jq "{${NEW_DEF_JQ_FILTER}}")

    # If in test mode output $NEW_DEF
    if [ "$BASH_SOURCE" != "$0" ]; then
      echo "$NEW_DEF"
    fi
}

function registerNewTaskDefinition() {
    # Register the new task definition, and store its ARN
    if [[ "$COPY_TASK_DEFINITION_TAGS" == true && "$TASK_DEFINITION_TAGS" != false ]]; then
      NEW_TASKDEF=`$AWS_ECS register-task-definition --cli-input-json "$NEW_DEF" --tags "$TASK_DEFINITION_TAGS" | jq -r .taskDefinition.taskDefinitionArn`
    else
      NEW_TASKDEF=`$AWS_ECS register-task-definition --cli-input-json "$NEW_DEF" | jq -r .taskDefinition.taskDefinitionArn`
    fi
}

function rollback() {
    echo "Rolling back to ${LAST_USED_TASK_DEFINITION_ARN}"
    $AWS_ECS update-service --cluster $CLUSTER --service $SERVICE --task-definition $LAST_USED_TASK_DEFINITION_ARN > /dev/null
}

function updateServiceForceNewDeployment() {
    echo 'Force a new deployment of the service'
    $AWS_ECS update-service --cluster $CLUSTER --service $SERVICE --force-new-deployment > /dev/null
}

function updateService() {
    if [[ $(echo ${NEW_DEF} | jq ".containerDefinitions[0].healthCheck != null") == true ]]; then
        checkFieldName="healthStatus"
        checkFieldValue="HEALTHY"
    else
        checkFieldName="lastStatus"
        checkFieldValue="RUNNING"
    fi

    UPDATE_SERVICE_SUCCESS="false"
    DEPLOYMENT_CONFIG=""
    if [ $MAX != false ]; then
        DEPLOYMENT_CONFIG=",maximumPercent=$MAX"
    fi
    if [ $MIN != false ]; then
        DEPLOYMENT_CONFIG="$DEPLOYMENT_CONFIG,minimumHealthyPercent=$MIN"
    fi
    if [ ! -z "$DEPLOYMENT_CONFIG" ]; then
        DEPLOYMENT_CONFIG="--deployment-configuration ${DEPLOYMENT_CONFIG:1}"
    fi

    DESIRED_COUNT=""
    if [ ! -z ${DESIRED+undefined-guard} ]; then
        DESIRED_COUNT="--desired-count $DESIRED"
    fi

    # Update the service
    if ! UPDATE=$($AWS_ECS update-service --cluster "$CLUSTER" --service "$SERVICE" $DESIRED_COUNT --task-definition "$NEW_TASKDEF" $DEPLOYMENT_CONFIG); then
      echo "Update service failed. Response: ${UPDATE}"
      if [[ "${ENABLE_ROLLBACK}" != "false" ]]; then
        rollback
      fi
      exit 1
    fi

    # Clean up old task definitions if desired
    if [[ $MAX_DEFINITIONS -gt 0 ]]; then
        FAMILY_PREFIX=${TASK_DEFINITION_ARN##*:task-definition/}
        FAMILY_PREFIX=${FAMILY_PREFIX%*:[0-9]*}
        TASK_REVISIONS=`$AWS_ECS list-task-definitions --family-prefix $FAMILY_PREFIX --status ACTIVE --sort ASC`
        NUM_ACTIVE_REVISIONS=$(echo "$TASK_REVISIONS" | jq ".taskDefinitionArns|length")
        if [[ $NUM_ACTIVE_REVISIONS -gt $MAX_DEFINITIONS ]]; then
            LAST_OUTDATED_INDEX=$(($NUM_ACTIVE_REVISIONS - $MAX_DEFINITIONS - 1))
            for i in $(seq 0 $LAST_OUTDATED_INDEX); do
                OUTDATED_REVISION_ARN=$(echo "$TASK_REVISIONS" | jq -r ".taskDefinitionArns[$i]")

                echo "Deregistering outdated task revision: $OUTDATED_REVISION_ARN"

              $AWS_ECS deregister-task-definition --task-definition "$OUTDATED_REVISION_ARN" > /dev/null
            done
        fi
    fi
}

function waitForGreenDeployment {
  DEPLOYMENT_SUCCESS="false"
  every=2
  i=0
  echo "Waiting for service deployment to complete..."
  while [ $i -lt $TIMEOUT ]
  do
    DEPLOYMENTS=$($AWS_ECS describe-services --services $SERVICE --cluster $CLUSTER)
    NEW_DEPLOYMENT=$(echo $DEPLOYMENTS | jq ".services[].deployments[] | select(.taskDefinition == \"$NEW_TASKDEF\")")
    DEPLOYMENTS_COUNT=$(echo $DEPLOYMENTS | jq ".services[].deployments | length")
    
    # if this is empty, it means the new (expected) deploy hasnt picked up yet
    if [ -z "$NEW_DEPLOYMENT" ]; then
      sleep $every
      i=$(( $i + $every ))
      continue
    fi
    
    COUNT_DESIRED=$(echo $NEW_DEPLOYMENT | jq -r '.desiredCount')
    COUNT_LAUNCHED=$(echo $NEW_DEPLOYMENT | jq -r '.runningCount')

    # Compare counts of launched new containers vs. requested
    # and continue when all new services are launched.
    # If the wait time has passed, we need to roll back
    if [ "$COUNT_DESIRED" == "$COUNT_LAUNCHED" ] && [ $DEPLOYMENTS_COUNT -eq 1 ]; then
      echo "Service deployment successful."
      DEPLOYMENT_SUCCESS="true"
      # Exit the loop.
      i=$TIMEOUT
    else
      sleep $every
      i=$(( $i + $every ))
    fi
  done

  if [[ "${DEPLOYMENT_SUCCESS}" != "true" ]]; then
    if [[ "${ENABLE_ROLLBACK}" != "false" ]]; then
      rollback
    fi
    exit 1
  fi
}

function runTask {
  echo "Run task: $NEW_TASKDEF";
  AWS_ECS_RUN_TASK="$AWS_ECS run-task --cluster $CLUSTER --task-definition $NEW_TASKDEF"
  if [ $RUN_TASK_LAUNCH_TYPE != false ]; then
    AWS_ECS_RUN_TASK="$AWS_ECS_RUN_TASK --launch-type $RUN_TASK_LAUNCH_TYPE"
  fi

  if [ $RUN_TASK_PLATFORM_VERSION != false ]; then
    AWS_ECS_RUN_TASK="$AWS_ECS_RUN_TASK --platform-version $RUN_TASK_PLATFORM_VERSION"
  fi

  if [ $RUN_TASK_NETWORK_CONFIGURATION != false ]; then
    AWS_ECS_RUN_TASK="$AWS_ECS_RUN_TASK --network-configuration \"$RUN_TASK_NETWORK_CONFIGURATION\""
  fi

  TASK_ARN=$(eval $AWS_ECS_RUN_TASK | jq -r '.tasks[0].taskArn')
  echo "Executed task: $TASK_ARN"

  if [ $RUN_TASK_WAIT_FOR_SUCCESS == true ]; then
    RUN_TASK_SUCCESS=false
    every=10
    i=0
    while [ $i -lt $TIMEOUT ]
    do

        TASK_JSON=$($AWS_ECS describe-tasks --cluster "$CLUSTER"  --tasks "$TASK_ARN")

        TASK_STATUS=$(echo $TASK_JSON | jq -r  '.tasks[0].lastStatus')
        TASK_EXIT_CODE=$(echo $TASK_JSON | jq -r  '.tasks[0].containers[0].exitCode')

        if [ $TASK_STATUS == "STOPPED" ]; then
            echo "Task finished with status: $TASK_STATUS"
            if [ $TASK_EXIT_CODE != 0 ]; then
                echo "Task execution failed with exit code: $TASK_EXIT_CODE"
                exit 1
            fi
            RUN_TASK_SUCCESS=true
            break;
        fi

        echo "Checking task status every $every seconds. Status: $TASK_STATUS"

        sleep $every
        i=$(( $i + $every ))
    done

    if [ $RUN_TASK_SUCCESS == false ]; then
      echo "ERROR: New task run took longer than $TIMEOUT seconds"
      exit 1
    fi
  fi



  echo "Task $TASK_ARN executed successfully!"
  exit 0

}

######################################################
# When not being tested, run application as expected #
######################################################
if [ "$BASH_SOURCE" == "$0" ]; then
    set -o errexit
    set -o pipefail
    set -u
    set -e
    # If no args are provided, display usage information
    if [ $# == 0 ]; then usage; fi

    # Check for AWS, AWS Command Line Interface
    require aws
    # Check for jq, Command-line JSON processor
    require jq

    # Loop through arguments, two at a time for key and value
    while [[ $# -gt 0 ]]
    do
        key="$1"

        case $key in
            -k|--aws-access-key)
                AWS_ACCESS_KEY_ID="$2"
                shift # past argument
                ;;
            -s|--aws-secret-key)
                AWS_SECRET_ACCESS_KEY="$2"
                shift # past argument
                ;;
            -r|--region)
                AWS_DEFAULT_REGION="$2"
                shift # past argument
                ;;
            -p|--profile)
                AWS_PROFILE="$2"
                shift # past argument
                ;;
            --aws-instance-profile)
                echo "--aws-instance-profile is not yet in use"
                AWS_IAM_ROLE=true
                ;;
            -a|--aws-assume-role)
                AWS_ASSUME_ROLE="$2"
                shift
                ;;
            -c|--cluster)
                CLUSTER="$2"
                shift # past argument
                ;;
            -n|--service-name)
                SERVICE="$2"
                shift # past argument
                ;;
            -d|--task-definition)
                TASK_DEFINITION="$2"
                shift
                ;;
            -i|--image)
                IMAGE="$2"
                shift
                ;;
            -t|--timeout)
                TIMEOUT="$2"
                shift
                ;;
            -m|--min)
                MIN="$2"
                shift
                ;;
            -M|--max)
                MAX="$2"
                shift
                ;;
            -D|--desired-count)
                DESIRED="$2"
                shift
                ;;
            -e|--tag-env-var)
                TAGVAR="$2"
                shift
                ;;
            -to|--tag-only)
                TAGONLY="$2"
                shift
                ;;
            --max-definitions)
                MAX_DEFINITIONS="$2"
                shift
                ;;
            --task-definition-file)
                TASK_DEFINITION_FILE="$2"
                shift
                ;;
            --enable-rollback)
                ENABLE_ROLLBACK=true
                ;;
            --use-latest-task-def)
                USE_MOST_RECENT_TASK_DEFINITION=true
                ;;
            --force-new-deployment)
                FORCE_NEW_DEPLOYMENT=true
                ;;
            --skip-deployments-check)
                SKIP_DEPLOYMENTS_CHECK=true
                ;;
            --run-task)
                RUN_TASK=true
                ;;
            --launch-type)
                RUN_TASK_LAUNCH_TYPE="$2"
                shift
                ;;
            --platform-version)
                RUN_TASK_PLATFORM_VERSION="$2"
                shift
                ;;
            --wait-for-success)
                RUN_TASK_WAIT_FOR_SUCCESS=true
                ;;
            --network-configuration)
                RUN_TASK_NETWORK_CONFIGURATION="$2"
                shift
                ;;
            --copy-task-definition-tags)
                COPY_TASK_DEFINITION_TAGS=true
                ;;
            -v|--verbose)
                VERBOSE=true
                ;;
            --version)
                echo ${VERSION}
                exit 0
                ;;
            *)
                #If another key was given that is not empty display usage.
                if [[ ! -z "$key" ]]; then
                  usage
                  exit 2
                fi
            ;;
        esac
        shift # past argument or value
    done

    if [ $VERBOSE == true ]; then
        set -x
    fi

    # Check that required arguments are provided
    assertRequiredArgumentsSet

    if [[ "$AWS_ASSUME_ROLE" != false ]]; then
        assumeRole
    fi

    # Not required creation of new a task definition
    if [ $FORCE_NEW_DEPLOYMENT == true ]; then
        updateServiceForceNewDeployment
        if [[ $SKIP_DEPLOYMENTS_CHECK != true ]]; then
          waitForGreenDeployment
        fi
        exit 0
    fi

    # Determine image name
    parseImageName
    echo "Using image name: $useImage"

    # Get current task definition
    getCurrentTaskDefinition
    echo "Current task definition: $TASK_DEFINITION_ARN";

    # create new task definition json
    createNewTaskDefJson

    # register new task definition
    registerNewTaskDefinition
    echo "New task definition: $NEW_TASKDEF";

    # update service if needed
    if [ $SERVICE == false ]; then
        if [ $RUN_TASK == true ]; then
            runTask
        fi
        echo "Task definition updated successfully"
    else
        updateService

        if [[ $SKIP_DEPLOYMENTS_CHECK != true ]]; then
          waitForGreenDeployment
        fi
    fi

    if [[ "$AWS_ASSUME_ROLE" != false ]]; then
        assumeRoleClean
    fi

    exit 0

fi
#############################
# End application run logic #
#############################

@briskt
Copy link
Contributor

briskt commented Sep 13, 2024

Based on 3.10.0 this version with runtimePlatform and fixed ShellCheck is working for me with more than 100 Tasks running

Thank you, @tsgoff, it's good to hear it's working well for you. I don't believe we will expand the feature set of ecs-deploy to work with large numbers of tasks. Feel free to make the changes on your own fork and link to it here so others with the same requirement can benefit from it.

@briskt briskt closed this as not planned Won't fix, can't repro, duplicate, stale Sep 13, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

4 participants