#!/bin/bash
function debug() {
COLOR='\033[01;32m' # bold yellow
RESET='\033[00;00m' # normal white
MESSAGE=${@:-"${RESET}Error: No message passed"}
echo -e "${COLOR}${MESSAGE}${RESET}"
}
function warning() {
COLOR='\033[01;34m' # bold yellow
RESET='\033[00;00m' # normal white
MESSAGE=${@:-"${RESET}Error: No message passed"}
echo -e "${COLOR}${MESSAGE}${RESET}"
}
function error() {
COLOR='\033[01;31m' # bold red
RESET='\033[00;00m' # normal white
MESSAGE=${@:-"${RESET}Error: No message passed"}
echo -e "${COLOR}${MESSAGE}${RESET}"
}
function usage() {
set -e
cat <<EOM
##### ecs-deploy #####
Required arguments:
-P | --ecs-compose-project-name Name of project to deploy
-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
-c | --cluster Name of ECS cluster
-f | --docker-compose-filepath docker-compose.yaml file path.
Optional arguments:
-l | --admin-files-location Admin files for environment variables
-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.
-v | --verbose Verbose output
-e | --env Environment variable
Requirements:
aws: AWS Command Line Interface
jq: Command-line JSON processor
ecs-cli: ECS Command Line Interface
Examples:
ecs-deploy --aws-access-key XXXXXXXXXXXXXXXXXX --aws-secret-key SDSDFDFDFRXCCXCXCXCXCXCCDrereye --region us-west-2
--docker-compose-filepath docker-compose.yml --cluster default --ecs-compose-project-name hz-claims-apigee
./ecs-deploy.sh $(cat argumentsfile.txt)
Add all required arguments in datafile.txt.
If you are going to create a new task and service / update task and service you must provide argument -P {project_name}.
EOM
exit 2
}
if [ $# == 0 ]; then
error "Please see usage of arguments"
usage;
fi
# Check requirements
function require {
command -v "$1" > /dev/null 2>&1 || {
warning "One of the required software is not installed...."
if [ "$1" == "jq" ]; then
debug " Please install $1" >&2;
debug '[INFO] To Install jq...'
debug "sudo curl -o /usr/local/bin/jq https://github.com/stedolan/jq/releases/download/jq-1.4/jq-linux-x86_64"
debug "sudo chmod +x /usr/local/bin/jq"
fi
if [ "$1" == "ecs-cli" ]; then
debug "[INFO] Installing AWS ECS CLI..."
sudo curl -o /usr/local/bin/ecs-cli https://s3.amazonaws.com/amazon-ecs-cli/ecs-cli-linux-amd64-latest
debug "Making $1 executable..."
sudo chmod +x /usr/local/bin/ecs-cli
fi
if [ "$1" == "aws" ]; then
debug "[INFO] Installing AWS CLI..."
sudo rm -rf /var/jenkins_home/tmp/
HOME=$HOME
TMP=${HOME}/tmp
TMP_AWSCLI=${TMP}/aws-cli-bundle
mkdir -p $TMP_AWSCLI
curl "https://s3.amazonaws.com/aws-cli/awscli-bundle.zip" -o "${TMP_AWSCLI}/awscli-bundle.zip"
unzip ${TMP_AWSCLI}/awscli-bundle.zip -d /${TMP_AWSCLI}
sudo ${TMP_AWSCLI}/awscli-bundle/install -i /usr/local/aws -b /usr/local/bin/aws
fi
}
}
# Setup default values for variables
CLUSTER=false
SERVICE_NAME=false
TASK_DEFINITION=false
AWS_DEFAULT_REGION=false
MIN=false
MAX=false
VERBOSE=false
AWS_CLI=/usr/local/bin/aws
AWS_ECS="$AWS_CLI ecs --output json"
ECS_COMPOSE_PROJECT_NAME=false
DOCKER_COMPOSE_FILE_PATH=false
ENV_VAR=false
ADM_FILE_LOCATION=false
ECS_ENV_VAR_NAME=false
ENV_FILE=".env"
DICT_FILE="final-env-vars.txt"
DICT_FILE_PATH=false
DICT_FILE_TEMP="final-env-vars-temp.txt"
# 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
;;
-l|--admin-files-location)
ADM_FILE_LOCATION="$2"
shift # past argument
;;
-c|--cluster)
CLUSTER="$2"
shift # past argument
;;
-e|--environment)
ENV_VAR="$2"
shift # past argument
;;
-m|--min)
MIN="$2"
shift
;;
-M|--max)
MAX="$2"
shift
;;
-D|--desired-count)
DESIRED="$2"
shift
;;
-f|--docker-compose-filepath)
DOCKER_COMPOSE_FILE_PATH="$2"
shift
;;
-P|--ecs-compose-project-name)
ECS_COMPOSE_PROJECT_NAME="$2"
shift
;;
-E|--ecs-environment-name)
ECS_ENV_VAR_NAME="$2"
shift
;;
-v|--verbose)
VERBOSE=true
;;
*)
usage
exit 2
;;
esac
shift # past argument or value
done
function configureEnvironment {
debug "Started envirinment configuration method"
if [ $VERBOSE == true ]; then
set -x
fi
if [ $ECS_COMPOSE_PROJECT_NAME == false ]; then
error "Please provide project name"
usage
exit 1
else
TASK_DEFINITION="ecscompose-$ECS_COMPOSE_PROJECT_NAME"
SERVICE_NAME="ecscompose-service-$ECS_COMPOSE_PROJECT_NAME"
fi
if [ $CLUSTER == false ]; then
error "CLUSTER is required. You can pass the value using -c or --cluster"
exit 1
fi
if [ $DOCKER_COMPOSE_FILE_PATH == false ]; then
error "DOCKER-COMPOSE file is required. You can pass the value using -f for docker compose file"
exit 1
fi
#DICT_FILE_PATH=`$(dirname "${DOCKER_COMPOSE_FILE_PATH}")`
DICT_FILE_PATH=$(dirname $(readlink -f "$DOCKER_COMPOSE_FILE_PATH"))
rm -rf $DICT_FILE_PATH/$DICT_FILE
rm -rf $DICT_FILE_PATH/$DICT_FILE_TEMP
echo -n "" > $DICT_FILE_PATH/$DICT_FILE
#cat > $DICT_FILE_PATH/$DICT_FILE <<- "EOF"
#EOF
echo -n "" > $DICT_FILE_PATH/$DICT_FILE_TEMP
#cat > $DICT_FILE_PATH/$DICT_FILE_TEMP <<- "EOF"
#EOF
debug "Successfully configured envirinment Variables"
}
function configureDeploymentArguments {
debug "Configuring deployment Variables"
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"
else
DESIRED_COUNT="--desired-count 1"
fi
debug "Desired count is : $DESIRED_COUNT"
debug "Deployment configure is completed."
}
function registerTaskDefinition(){
`ecs-cli compose --file $DOCKER_COMPOSE_FILE_PATH --project-name $ECS_COMPOSE_PROJECT_NAME create`
[ $? != 0 ] && \
error "Registered task definition failed !" && exit 100
debug "Registered task definition successfully"
}
function createOrUpdateService(){
configureDeploymentArguments
SERVICE_DETAILS=`$AWS_ECS describe-services --cluster $CLUSTER --service $SERVICE_NAME | jq '.services[]|.serviceName'`
SERVICE_STATUS=`$AWS_ECS describe-services --cluster $CLUSTER --service $SERVICE_NAME | jq '.services[]|.status'`
SERVICE_DETAILS=`sed -e 's/^"//' -e 's/"$//' <<<"$SERVICE_DETAILS"`
if [ -z "$SERVICE_DETAILS" ] || [ $SERVICE_DETAILS != $SERVICE_NAME ] || [ $SERVICE_STATUS == '"INACTIVE"' ]; then
if [ ! -z "$SERVICE_STATUS" ] && [ $SERVICE_STATUS == '"INACTIVE"' ]; then
debug "Deleting service $SERVICE_DETAILS which status is : $SERVICE_STATUS"
deleteService
debug "Wating for service $SERVICE_DETAILS to update"
sleep 60
fi
createService
elif [ $SERVICE_DETAILS == $SERVICE_NAME ] && [ $SERVICE_STATUS == '"ACTIVE"' ]; then
debug "Service already exists: $SERVICE_DETAILS, Updating the service"
updateService
fi
}
function deleteService(){
DELETE_SERVICE=`$AWS_ECS delete-service --service "$SERVICE_NAME" --cluster "$CLUSTER"`
[ $? != 0 ] && \
error "Creating service failed !" && exit 100
debug "Service deleted successfully $DELETE_SERVICE"
}
function createService(){
# Create the service
debug "Creating service"
CREATE_SERVICE=`$AWS_ECS create-service --service-name "$SERVICE_NAME" $DESIRED_COUNT \
--cluster "$CLUSTER" \
--task-definition $TASK_DEFINITION`
[ $? != 0 ] && \
error "Creating service failed !" && exit 100
debug "Service created successfully"
}
function updateService(){
# Update the service
debug "Updating Service"
UPDATE_SERVICE=`$AWS_ECS --region $AWS_DEFAULT_REGION update-service --service "$SERVICE_NAME" $DESIRED_COUNT --cluster "$CLUSTER" --task-definition $TASK_DEFINITION $DEPLOYMENT_CONFIG`
[ $? != 0 ] && \
error "Updating service failed !" && exit 100
debug "Service Updated successfully"
}
function prepareDockerComposeFIle() {
echo $ENV_VAR | sed -n 1'p' | tr ',' '\n' | while read envVar; do
debug $envVar
printf '%s\n' /environment:/a ' - '$envVar . w q | ex -s $DOCKER_COMPOSE_FILE_PATH
done
}
function verifyAndUpdateEnvFiles() {
fileName=$1
if [ -f "$fileName" ]; then
result=$(grep -i "env_file:" "$DOCKER_COMPOSE_FILE_PATH")
if [ -z $result ]; then
debug "label env_file is not found in docker-compose file, creating label env_file: "
sed -i -e '$a\ env_file:\' $DOCKER_COMPOSE_FILE_PATH
fi
printf '%s\n' /env_file:/a ' - '$fileName . w q | ex -s $DOCKER_COMPOSE_FILE_PATH
else
debug "File $fileName not found."
fi
}
function updateEnvValues(){
fileName=$1
START='${'
END='}'
if [ -f "$fileName" ]; then
cat $fileName >> $ADM_FILE_LOCATION/tempVal.txt
while IFS= read -r line || [[ -n "$line" ]]; do
echo "$line" >> $ADM_FILE_LOCATION/tempVal.txt
done < "$fileName"
while IFS="=" read -r key value
do
keyResult=$(grep -i ${key} $DICT_FILE_PATH/$DICT_FILE)
if [ ! -z $keyResult ]; then
grep -v "${keyResult}" $DICT_FILE_PATH/$DICT_FILE > $DICT_FILE_PATH/$DICT_FILE_TEMP; mv $DICT_FILE_PATH/$DICT_FILE_TEMP $DICT_FILE_PATH/$DICT_FILE
#echo "$(grep -v "${keyResult}" dict.txt)" >dict.txt
echo "$START${key}$END=${value}">>$DICT_FILE_PATH/$DICT_FILE
else
echo "$START${key}$END=${value}">>$DICT_FILE_PATH/$DICT_FILE
fi
done < $ADM_FILE_LOCATION/tempVal.txt
rm -f $ADM_FILE_LOCATION/tempVal.txt
fi
}
function prepareEnvFile() {
if [ $ADM_FILE_LOCATION != false ] && [ -d "$ADM_FILE_LOCATION" ]; then
fileNames=( "$ADM_FILE_LOCATION/${ECS_COMPOSE_PROJECT_NAME}-${ECS_ENV_VAR_NAME}.env" "$ADM_FILE_LOCATION/default-${ECS_ENV_VAR_NAME}.env" "$ADM_FILE_LOCATION/default.env" "${ECS_ENV_VAR_NAME}.env" "default.env" )
fileNamesForEnV=( "default.env" "${ECS_ENV_VAR_NAME}.env" "$ADM_FILE_LOCATION/default.env" "$ADM_FILE_LOCATION/default-${ECS_ENV_VAR_NAME}.env" "$ADM_FILE_LOCATION/${ECS_COMPOSE_PROJECT_NAME}-${ECS_ENV_VAR_NAME}.env")
for t in "${fileNames[@]}"
do
# debug "Getting environment file, and the file is: $t"
verifyAndUpdateEnvFiles $t
done
for tmpFile in "${fileNamesForEnV[@]}"
do
#debug "Getting file,to update environment values: $tmpFile"
updateEnvValues $tmpFile
done
sed -i -e 's/=/ /g' $DICT_FILE_PATH/$DICT_FILE
else
if [ -f "$DOCKER_COMPOSE_FILE_PATH" ]; then
result=$(grep -i "[[:alnum:]].env" "$DOCKER_COMPOSE_FILE_PATH")
if [ -z $result ] ; then
debug "label env_file: with out any values will throw YAML parse error. Deleting label env_file: "
sed -i '/env_file:/d' $DOCKER_COMPOSE_FILE_PATH
fi
fi
debug "No admin directory for env files exists at $ADM_FILE_LOCATION"
fi
}
function createEnvironmentSection() {
debug "Checking for environment argument, if provided then the values will be added to docker compose file, environment variables take precedence over env files."
if [ $ENV_VAR != false ]; then
result=$(grep -i "environment:" "$DOCKER_COMPOSE_FILE_PATH")
if [ -z $result ]; then
debug "label environment: does not exists, creating label environment: "
sed -i -e '$a\ environment:\' $DOCKER_COMPOSE_FILE_PATH
fi
prepareDockerComposeFIle
fi
}
function substituteEnvVarsInDockerCompose() {
debug "Substitute EnvVars In Docker Compose"
sed '/./!d;s/\([^ ]*\) *\(.*\)/\\|\1|s||\2|g/' $DICT_FILE_PATH/$DICT_FILE | sed -i -f - $DOCKER_COMPOSE_FILE_PATH
# awk -F "[ )]" 'NR == FNR {a[$1] = $2; next} {sub($(NF-1), a[$(NF-1)]); print}' dict.txt $DOCKER_COMPOSE_FILE_PATH
}
function removeCommentedLines() {
#grep -v "^#" $DOCKER_COMPOSE_FILE_PATH > $DOCKER_COMPOSE_FILE_PATH
echo "$(grep -v "^#" $DOCKER_COMPOSE_FILE_PATH)" >$DOCKER_COMPOSE_FILE_PATH
}
function deployToECS(){
debug "Checking for required tools (AWS CLI, ECS CLI and JQ)"
require aws
# Check for jq, Command-line JSON processor
require jq
# Check for ecs-cli, Command-line ecs-cli
require ecs-cli
debug "Checked for required tools. Done"
debug "Configuring Environment variables"
configureEnvironment
debug "Checking for available environment files to be used."
removeCommentedLines
prepareEnvFile
createEnvironmentSection
substituteEnvVarsInDockerCompose
debug "Registering task definition"
registerTaskDefinition
debug "Calling create or update service"
createOrUpdateService
exit 0
}
deployToECS
This will take of existing services or it will create a new services. This script built in Shell. Also it will take care any environment values that need to be substituted at run time in docker-compose.yml.
It also have lot of features. Due to time i am not describing each and every function.
Hope this this will help those people who are working in CI ,CD jobs and you want your Jenkins should deploy to AWS after every success build then this tool can do that.
I used https://github.com/silinternational/ecs-deploy for some part. Here you find detailed documentation. But most of my script is different from the original.
Sample docker-compose file as follows.
version: '2'
services:
config-server:
image: registry.myworks.com/config-server:1.2.0-SNAPSHOT
container_name: "config-server"
mem_limit: 1048576000
cpu_shares: 100
environment:
- SERVER_PORT=8080
- SERVICE_8080_NAME=config-server
- SERVICE_8080_TAGS=api
labels:
api-id: "config-server"
api-version: "1.2.0-SNAPSHOT"
restart: always
logging:
driver: awslogs
options:
awslogs-region: us-east-1
awslogs-group: development-horizon-01
awslogs-stream-prefix: "config-server"
env_file:
- /var/jenkins_home/workspace/lh-spring-cloud-config-DevDeployer/ecs-admin-files/config-server-dev.env
It also have lot of features. Due to time i am not describing each and every function.
Hope this this will help those people who are working in CI ,CD jobs and you want your Jenkins should deploy to AWS after every success build then this tool can do that.
I used https://github.com/silinternational/ecs-deploy for some part. Here you find detailed documentation. But most of my script is different from the original.
Sample docker-compose file as follows.
version: '2'
services:
config-server:
image: registry.myworks.com/config-server:1.2.0-SNAPSHOT
container_name: "config-server"
mem_limit: 1048576000
cpu_shares: 100
environment:
- SERVER_PORT=8080
- SERVICE_8080_NAME=config-server
- SERVICE_8080_TAGS=api
labels:
api-id: "config-server"
api-version: "1.2.0-SNAPSHOT"
restart: always
logging:
driver: awslogs
options:
awslogs-region: us-east-1
awslogs-group: development-horizon-01
awslogs-stream-prefix: "config-server"
env_file:
- /var/jenkins_home/workspace/lh-spring-cloud-config-DevDeployer/ecs-admin-files/config-server-dev.env
Be the first to comment
You can use [html][/html], [css][/css], [php][/php] and more to embed the code. Urls are automatically hyperlinked. Line breaks and paragraphs are automatically generated.