#! /bin/sh -e
# shellcheck disable=SC2086,SC2089,SC2090
# SC2086: Double quote to prevent globbing and word splitting.
# SC2089: Quotes/backslashes will be treated literally. Use an array.
# SC2090: Quotes/backslashes in this variable will not be respected.

APPOPTICS_API_TOKEN=
FUNCTION_SERVICE_MAPPINGS=
CLOUDWATCH_LOG_GROUPS=
ACTION=
INTERACTIVE=yes
DEBUG=no
CLOUDFORMATION_STACK_NAME="appoptics-lambda-forwarder"
REGION=
STAGING_S3="s3://rc-files-t2/lambda-forwarder"
RC_VERSION=
TMP_DIR="/tmp/appoptics/"

APPOPTICS_SERVERLESS_APP="arn:aws:serverlessrepo:us-east-1:085151004374:applications/AppOptics-Lambda-Forwarder"

print_usage_and_exit() {
    printf "Valid options:\n"
    printf "  --action=<val>  The action this script should perform (mandatory)\n"
    printf "                  Values:\n"
    printf "                  install - install the AppOptics Lambda Forwarder\n"
    printf "                  remove - remove the AppOptics Lambda Forwarder\n"
    printf "                  add-mapping - add mapping from a Lambda function to an\n"
    printf "                                AppOptics service (see --service-mappings)\n"
    printf "                  remove-mapping - remove mapping from a Lambda function to an\n"
    printf "                                   AppOptics service (see --service-mappings)\n"
    printf "                  list-mapping - list mappings from Lambda functions to\n"
    printf "                                 AppOptics services\n"
    printf "                  install-rc - install an RC version of the AppOptics Lambda \n"
    printf "                               Forwarder (see --version)\n"
    printf "\n"
    printf "  --api-token=<val>  Unique AppOptics API token\n"
    printf "\n"
    printf "  --debug  Run script in debug mode (all AWS commands will be logged)\n"
    printf "\n"
    printf "  --interactive=yes|no  Run script in interactive mode (default: yes)\n"
    printf "                        If run in interactive mode the script will ask for\n"
    printf "                        additional options\n"
    printf "\n"
    printf "  --region=<val>  A valid AWS Region name in which to run the commands. If not \n"
    printf "                  set then the region configured for AWS CLI will be used. (e.g.\n"
    printf "                  us-east-1)\n"
    printf "\n"
    printf "  --service-mappings=<val>  A list of mappings between a Lambda function name,\n"
    printf "                            an AppOptics service name and optionally a hostname\n"
    printf "                            alias (default: <empty>). Multiple mappings should\n"
    printf "                            be comma-separated, e.g. \"funcA:serviceA,funcB:servi\n"
    printf "                            ceB:aliasB\". A mapping must be of the format <functi\n"
    printf "                            on>:<service>[:<alias>] and will be stored as a\n"
    printf "                            Subscription filter name in CloudWatch\n"
    printf "\n"
    printf "  --stack-name=<val>  The name that is associated with a CloudFormation stack.\n"
    printf "                      Name must be unique in the Region in which you are\n"
    printf "                      creating the stack (default: appoptics-lambda-forwarder)\n"
    printf "\n"
    printf "  --version=<val>  An RC version of the AppOptics Lambda Forwarder to be \n"
    printf "                   installed. Only works in combination with --action=install-rc\n"
    printf "\n"
    printf "  -h --help  Print this help text\n"
    printf "\n"
    printf "Examples:\n"
    printf "Install Forwarder and service mappings (interactive):\n"
    printf "%s --action=install\n" "$0"
    printf "\n"
    printf "Install Forwarder and service mappings (non-interactive):\n"
    printf "%s --action=install --api-token=my-api-token --service-mappings=funcA:serviceA,funcB:serviceB\n" "$0"
    printf "\n"
    printf "Uninstall Forwarder and service mappings:\n"
    printf "%s --action=remove\n" "$0"
    printf "\n"
    printf "Add service mappings, one with hostname alias, one without:\n"
    printf "%s --action=add-mapping --service-mappings=funcA:serviceA:aliasA,funcB:serviceB\n" "$0"
    printf "\n"
    printf "Remove service mappings:\n"
    printf "%s --action=remove-mapping --service-mappings=funcA:serviceA,funcB:serviceB\n" "$0"
    printf "\n"
    printf "List service mappings:\n"
    printf "%s --action=list-mapping\n" "$0"
    exit 1
}

for i in "$@"; do
    if [ -n "$i" ]; then
        case $i in
            --help)
            print_usage_and_exit
            ;;
            -h)
            print_usage_and_exit
            ;;
            --action=*)
            ACTION=$(echo "$i" | awk -F= '{print $2}' | tr -d "\"'")
            if [ "$ACTION" != "install" ] && [ "$ACTION" != "remove" ] \
            && [ "$ACTION" != "add-mapping" ] && [ "$ACTION" != "remove-mapping" ] && [ "$ACTION" != "list-mapping" ] \
            && [ "$ACTION" != "install-rc" ]; then
                printf "Invalid value given for option --action\n"
                print_usage_and_exit
            fi
            ;;
            --debug)
            DEBUG=yes
            ;;
            --interactive=*)
            INTERACTIVE=$(echo "$i" | awk -F= '{print $2}' | tr -d "\"'")
            if [ "$INTERACTIVE" != "yes" ] && [ "$INTERACTIVE" != "no" ]; then
                printf "Invalid value given for option --interactive\n"
                print_usage_and_exit
            fi
            ;;
            --api-token=*)
            APPOPTICS_API_TOKEN=$(echo "$i" | awk -F= '{print $2}' | tr -d "\"'")
            ;;
            --service-mappings=*)
            FUNCTION_SERVICE_MAPPINGS=$(echo "$i" | awk -F= '{print $2}' | tr -d "\"'")
            ;;
            --stack-name=*)
            CLOUDFORMATION_STACK_NAME=$(echo "$i" | awk -F= '{print $2}' | tr -d "\"'")
            if [ -z "$CLOUDFORMATION_STACK_NAME" ]; then
                printf "Option --stack-name cannot be empty\n"
                print_usage_and_exit
            fi
            ;;
            --version=*)
            RC_VERSION=$(echo "$i" | awk -F= '{print $2}' | tr -d "\"'")
            ;;
            --region=*)
            REGION=$(echo "$i" | awk -F= '{print $2}' | tr -d "\"'")
            REGION="--region $REGION"
            ;;
            *)
            printf "Unknown or incomplete option: %s\n" "$i"
            print_usage_and_exit
            ;;
        esac
    fi
done

if [ -z "$ACTION" ]; then
    printf "Please set an action via the --action option\n"
    print_usage_and_exit
fi

# setup debug commands
DBG_BEFORE=
DBG_INNER=
DBG_AFTER=
if [ "$DEBUG" = "yes" ]; then
    DBG_BEFORE="eval printf \"\n======= Running AWS command =======\n\""
    DBG_INNER="eval set -x"
    DBG_AFTER="eval printf \"===================================\n\""
fi

# test AWS CLI tool
if [ -z "$(which aws)" ]; then
    printf "Command 'aws' not found. Please install the aws-cli tool.\n"
    exit 1
fi

# test AWS CLI credentials
printf "Testing AWS CLI..."
set +e
if ! aws cloudformation describe-stacks 1>/dev/null 2>/dev/null; then
    printf "error\n"
    printf "Problem with the AWS CLI credentials. Please ensure that AWS CLI is properly configured and has the appropriate permissions.\n"
    exit 1
fi
set -e
printf "done\n"

####################################################################################
############################# COMMON FUNCTIONS #####################################
####################################################################################

get_var_api_token() {
    if [ -z "$APPOPTICS_API_TOKEN" ]; then
        if [ "$INTERACTIVE" = "yes" ]; then
            printf "Please enter your AppOptics API token: "
            read -r APPOPTICS_API_TOKEN
        fi
        if [ -z "$APPOPTICS_API_TOKEN" ]; then
            printf "Please set an API token via the --api-token option or interactively\n"
            print_usage_and_exit
        fi
    fi
    APPOPTICS_API_TOKEN=$(echo "$APPOPTICS_API_TOKEN" | sed 's/:.*//g')
}

get_var_service_mapping() {
    INFO=$1
    if [ -z "$FUNCTION_SERVICE_MAPPINGS" ]; then
        if [ "$INTERACTIVE" = "yes" ]; then
            NEW_MAPPING=init
            while [ -n "$NEW_MAPPING" ]; do
                printf "Service Mapping (%sempty line when done): " "$INFO"
                read -r NEW_MAPPING
                if [ -n "$NEW_MAPPING" ]; then
                    if [ -n "$FUNCTION_SERVICE_MAPPINGS" ]; then
                        FUNCTION_SERVICE_MAPPINGS="$FUNCTION_SERVICE_MAPPINGS,$NEW_MAPPING"
                    else
                        FUNCTION_SERVICE_MAPPINGS="$NEW_MAPPING"
                    fi
                fi
            done
        fi
    fi
    
    FUNCTION_SERVICE_MAPPINGS=$(echo "$FUNCTION_SERVICE_MAPPINGS" | sed 's/,/ /g')
}

verify_var_service_mapping() {
    for MAPPING in $FUNCTION_SERVICE_MAPPINGS; do
        FUNCTION=$(echo "$MAPPING" | awk -F: '{print $1}')
        SERVICE=$(echo "$MAPPING" | awk -F: '{print $2}')
        ALIAS=$(echo "$MAPPING" | awk -F: '{print $3}')
        if [ -z "$FUNCTION" ]; then
            printf "The service mapping %s doesn't seem to contain a function name. Please make sure the mapping is of the format <function>:<service>[:<alias>]\n" "$MAPPING"
            exit 1
        fi
        if [ -z "$SERVICE" ]; then
            printf "The service mapping %s doesn't seem to contain a service name. Please make sure the mapping is of the format <function>:<service>[:<alias>]\n" "$MAPPING"
            exit 1
        fi
        if echo "$SERVICE" | grep -q '/'; then
            printf "Invalid character '/' in service name %s.\n" "$SERVICE"
            exit 1
        fi
        if echo "$ALIAS" | grep -q '/'; then
            printf "Invalid character '/' in alias %s.\n" "$ALIAS"
            exit 1
        fi
    done
}

get_stack_name() {
    $DBG_BEFORE
    CLOUDFORMATION_STACK_NAME_ID=$($DBG_INNER; aws cloudformation describe-stacks \
    --query "Stacks[?Tags[?Value==\`$APPOPTICS_SERVERLESS_APP\`]].[StackId,StackName]" \
    --no-paginate \
    --output text \
    $REGION)
    $DBG_AFTER
    
    if [ "$CLOUDFORMATION_STACK_NAME_ID" = "null" ] || [ -z "$CLOUDFORMATION_STACK_NAME_ID" ]; then
        printf "error: Empty StackId/StackName returned\n"
        printf "It looks like the AppOptics Lambda Forwarder is currently not installed.\n"
        exit 1
    fi
    
    CLOUDFORMATION_STACK_ID=$(echo "$CLOUDFORMATION_STACK_NAME_ID" | awk '{print $1}')
    CLOUDFORMATION_STACK_NAME=$(echo "$CLOUDFORMATION_STACK_NAME_ID" | awk '{print $2}')
}

get_lambda_function_name() {
    $DBG_BEFORE
    LAMBDA_FUNCTION_NAME=$($DBG_INNER; aws cloudformation describe-stack-resources \
    --stack-name "$CLOUDFORMATION_STACK_NAME" \
    --logical-resource-id AoForwarderFunction \
    --query 'StackResources[0].PhysicalResourceId' \
    --output text \
    $REGION)
    $DBG_AFTER
    
    if [ "$LAMBDA_FUNCTION_NAME" = "null" ]; then
        printf "error: Empty PhysicalResourceId returned\n"
        printf "Error while retrieving the Lambda function name from AWS CloudFormation stack %s\n" "$CLOUDFORMATION_STACK_NAME"
        exit 1
    fi
}

get_lambda_function_arn() {
    $DBG_BEFORE
    LAMBDA_FUNCTION_ARN=$($DBG_INNER; aws lambda get-function \
    --function-name "$LAMBDA_FUNCTION_NAME" \
    --query 'Configuration.FunctionArn' \
    --output text \
    $REGION)
    $DBG_AFTER
    
    if [ "$LAMBDA_FUNCTION_ARN" = "null" ]; then
        printf "error: Empty FunctionArn returned\n"
        printf "Error while retrieving the ARN from Lambda function %s\n" "$LAMBDA_FUNCTION_NAME"
        exit 1
    fi
}

get_log_groups() {
    $DBG_BEFORE
    CLOUDWATCH_LOG_GROUPS=$($DBG_INNER; aws logs describe-log-groups \
    --query 'logGroups[].logGroupName' \
    --no-paginate \
    --output text \
    $REGION)
    $DBG_AFTER
    
    if [ "$CLOUDWATCH_LOG_GROUPS" = "null" ]; then
        printf "error: Empty logGroupName returned\n"
        printf "Error while retrieving CloudWatch Log groups\n"
        exit 1
    fi
}

####################################################################################
############################### INSTALLATION #######################################
####################################################################################

install_forwarder() {
    printf "Creating an AWS CloudFormation change set for stack AppOptics-Lambda-Forwarder.."
    $DBG_BEFORE
    CHANGESET_ID=$($DBG_INNER; aws serverlessrepo create-cloud-formation-change-set \
    --application-id $APPOPTICS_SERVERLESS_APP \
    --stack-name $CLOUDFORMATION_STACK_NAME \
    --capabilities CAPABILITY_RESOURCE_POLICY CAPABILITY_IAM \
    --parameter-overrides "Name=AppOpticsApiToken,Value=$APPOPTICS_API_TOKEN" \
    --query 'ChangeSetId' \
    --output text \
    $REGION)
    $DBG_AFTER

    if [ "$CHANGESET_ID" = "null" ]; then
        printf "error: Empty ChangeSetId returned\n"
        exit 1
    fi

    CHANGESET_EXECUTE=yes
    STATUS=
    LOOP_COUNT=0
    while [ "$STATUS" != "CREATE_COMPLETE" ]; do
        LOOP_COUNT=$((LOOP_COUNT + 1))
        if [ $LOOP_COUNT -gt 60 ]; then
            printf "timeout\n"
            printf "Could not create changeset within 1 minute (current changeset status: %s)\n" "$STATUS"
            exit 1
        fi
        
        printf "."
        sleep 1
        
        # get execution status
        $DBG_BEFORE
        STATUS=$($DBG_INNER; aws cloudformation describe-change-set \
        --change-set-name $CHANGESET_ID \
        --query 'Status' \
        --no-paginate \
        --output text \
        $REGION)
        $DBG_AFTER
        
        if [ "$STATUS" = "FAILED" ]; then
            $DBG_BEFORE
            REASON=$($DBG_INNER; aws cloudformation describe-change-set \
            --change-set-name $CHANGESET_ID \
            --query 'StatusReason' \
            --no-paginate \
            --output text \
            $REGION)
            $DBG_AFTER
            
            if echo "$REASON" | grep -q 'No updates are to be performed'; then
                # everything up-to-date, no need to fail the  script
                CHANGESET_EXECUTE=no
                break
            else
                printf "error\n"
                printf "Reason: %s\n" "$REASON"
                exit 1
            fi
        fi
    done

    # get updated stack name
    $DBG_BEFORE
    CLOUDFORMATION_STACK_NAME=$($DBG_INNER; aws cloudformation describe-change-set \
    --change-set-name $CHANGESET_ID \
    --query 'StackName' \
    --no-paginate \
    --output text \
    $REGION)
    $DBG_AFTER

    if [ "$CLOUDFORMATION_STACK_NAME" = "null" ]; then
        printf "error: Empty StackName returned\n"
        exit 1
    fi

    printf "done\n"
    
    ############################
    
    if [ "$CHANGESET_EXECUTE" = "yes" ]; then
        printf "Executing the AWS CloudFormation change set.."
        $DBG_BEFORE
        eval "$($DBG_INNER; aws cloudformation execute-change-set --change-set-name $CHANGESET_ID $REGION)"
        $DBG_AFTER

        STATUS=
        LOOP_COUNT=0
        while [ "$STATUS" != "CREATE_COMPLETE" ] && [ "$STATUS" != "UPDATE_COMPLETE" ]; do
            LOOP_COUNT=$((LOOP_COUNT + 1))
            if [ $LOOP_COUNT -gt 60 ]; then
                printf "timeout\n"
                printf "Could not create/update stack within 5 minutes (current stack status: %s)\n" "$STATUS"
                exit 1
            fi
            
            printf "."
            sleep 5
            
            # get execution status
            $DBG_BEFORE
            STATUS=$($DBG_INNER; aws cloudformation describe-stacks \
            --stack-name $CLOUDFORMATION_STACK_NAME \
            --query 'Stacks[0].StackStatus' \
            --no-paginate \
            --output text \
            $REGION)
            $DBG_AFTER
        done

        printf "done\n"
    else
        printf "The AWS CloudFormation stack AppOptics-Lambda-Forwarder is already up-to-date. Deleting change set..."
        $DBG_BEFORE
        eval "$($DBG_INNER; aws cloudformation delete-change-set --change-set-name $CHANGESET_ID $REGION)"
        $DBG_AFTER
        printf "done\n"
    fi
}

####################################################################################
################################## REMOVAL #########################################
####################################################################################

remove_forwarder() {
    printf "Retrieving information needed for removing the stack.."
    
    get_stack_name
    printf "."
    get_lambda_function_name
    printf "."
    
    # get log group associated with lambda function (will not fail the script in case we cannot find it)
    $DBG_BEFORE
    LAMBDA_CLOUDWATCH_LOG_GROUP=$($DBG_INNER; aws logs describe-log-groups \
    --query "logGroups[?ends_with(logGroupName, \`$LAMBDA_FUNCTION_NAME\`)].logGroupName" \
    --no-paginate \
    --output text \
    $REGION)
    $DBG_AFTER
    
    printf "done\n"
    
    if [ -n "$LAMBDA_CLOUDWATCH_LOG_GROUP" ] && [ "$LAMBDA_CLOUDWATCH_LOG_GROUP" != "null" ]; then
        printf "Removing Cloudwatch Log group %s..." "$LAMBDA_CLOUDWATCH_LOG_GROUP"
        $DBG_BEFORE
        eval "$($DBG_INNER; aws logs delete-log-group --log-group-name $LAMBDA_CLOUDWATCH_LOG_GROUP $REGION)"
        $DBG_AFTER
        printf "done\n"
    fi
    
    printf "Removing AWS CloudFormation stack %s.." "$CLOUDFORMATION_STACK_NAME"
    $DBG_BEFORE
    eval "$($DBG_INNER; aws cloudformation delete-stack \
    --stack-name $CLOUDFORMATION_STACK_NAME \
    $REGION)"
    $DBG_AFTER
    
    STATUS=
    LOOP_COUNT=0
    while [ "$STATUS" != "DELETE_COMPLETE" ]; do
        LOOP_COUNT=$((LOOP_COUNT + 1))
        if [ $LOOP_COUNT -gt 60 ]; then
            printf "timeout\n"
            printf "Could not remove stack within 2 minutes (current stack status: %s)\n" "$STATUS"
            exit 1
        fi
        
        printf "."
        sleep 2
        
        # get execution status
        $DBG_BEFORE
        STATUS=$($DBG_INNER; aws cloudformation describe-stacks \
        --stack-name $CLOUDFORMATION_STACK_ID \
        --query 'Stacks[0].StackStatus' \
        --no-paginate \
        --output text \
        $REGION)
        $DBG_AFTER
    done

    printf "done\n"
}

####################################################################################
############################## SERVICE MAPPING #####################################
####################################################################################

add_service_mapping() {
    if [ -z "$FUNCTION_SERVICE_MAPPINGS" ]; then
        printf "No service mappings to add.\n"
        return
    fi
    
    for MAPPING in $FUNCTION_SERVICE_MAPPINGS; do
        SERVICE=$(echo "$MAPPING" | awk -F: '{print $2}')
        if [ -z "$SERVICE" ]; then
            printf "The service mapping %s doesn't seem to contain a service name. Please make sure the mapping is of the format <function>:<service>[:<alias>]\n" "$MAPPING"
            return
        fi
    done
    
    printf "Retrieving information needed for adding service mappings.."
    
    get_stack_name
    printf "."
    get_lambda_function_name
    printf "."
    get_lambda_function_arn
    
    printf "done\n"
    
    for MAPPING in $FUNCTION_SERVICE_MAPPINGS; do
        FUNCTION=$(echo "$MAPPING" | awk -F: '{print $1}')
        SERVICE=$(echo "$MAPPING" | awk -F: '{print $2}')
        ALIAS=$(echo "$MAPPING" | awk -F: '{print $3}')
        
        CLOUDWATCH_LOG_GROUP="/aws/lambda/$FUNCTION"
        FILTER_NAME="/ao/$SERVICE/$ALIAS"
        
        printf "Checking Cloudwatch Log group %s..." "$CLOUDWATCH_LOG_GROUP"
        
        $DBG_BEFORE
        EXISTING_FILTER=$($DBG_INNER; aws logs describe-subscription-filters \
        --log-group-name $CLOUDWATCH_LOG_GROUP \
        --output text \
        $REGION)
        $DBG_AFTER
        
        if [ -n "$EXISTING_FILTER" ]; then
            printf "error\n"
            printf "Cloudwatch Log group %s already has a subscription filter, skipping\n" "$CLOUDWATCH_LOG_GROUP"
            continue
        fi
        
        printf "done\n"
        
        printf "Adding subscription filter %s to Cloudwatch Log group %s..." "$FILTER_NAME" "$CLOUDWATCH_LOG_GROUP"
        $DBG_BEFORE
        eval "$($DBG_INNER; aws logs put-subscription-filter \
        --log-group-name $CLOUDWATCH_LOG_GROUP \
        --filter-name $FILTER_NAME \
        --filter-pattern '?"\"ao-data\":" ?"REPORT RequestId:"' \
        --destination-arn $LAMBDA_FUNCTION_ARN \
        $REGION)"
        $DBG_AFTER
        printf "done\n"
    done
}

remove_service_mapping() {
    if [ -z "$FUNCTION_SERVICE_MAPPINGS" ]; then
        printf "No service mappings to remove.\n"
        return
    fi
    
    printf "Retrieving information needed for removing service mappings.."
    
    get_stack_name
    printf "."
    get_lambda_function_name
    printf "."
    get_lambda_function_arn
    
    printf "done\n"
    
    for MAPPING in $FUNCTION_SERVICE_MAPPINGS; do
        FUNCTION=$(echo "$MAPPING" | awk -F: '{print $1}')
        CLOUDWATCH_LOG_GROUP="/aws/lambda/$FUNCTION"
        
        printf "Checking Cloudwatch Log group %s..." "$CLOUDWATCH_LOG_GROUP"
        
        # check if subscription filter matches the forwarder ARN
        $DBG_BEFORE
        SUBSCRIPTION_NAME_ARN=$($DBG_INNER; aws logs describe-subscription-filters \
        --log-group-name $CLOUDWATCH_LOG_GROUP \
        --query 'subscriptionFilters[].[filterName,destinationArn]' \
        --no-paginate \
        --output text \
        $REGION)
        $DBG_AFTER
        
        FILTER_NAME=
        DESTINATION_ARN=
        if [ -n "$SUBSCRIPTION_NAME_ARN" ] && [ "$SUBSCRIPTION_NAME_ARN" != "null" ]; then
            FILTER_NAME=$(echo "$SUBSCRIPTION_NAME_ARN" | awk '{print $1}')
            DESTINATION_ARN=$(echo "$SUBSCRIPTION_NAME_ARN" | awk '{print $2}')
            if [ "$DESTINATION_ARN" != "$LAMBDA_FUNCTION_ARN" ]; then
                printf "error\n"
                printf "Cloudwatch Log group %s is not subscribed to the AppOptics Lambda Forwarder, skipping\n" "$CLOUDWATCH_LOG_GROUP"
                continue
            fi
        fi
        
        if [ -z "$FILTER_NAME" ]; then
            printf "error\n"
            printf "Cloudwatch Log group %s doesn't have a subscription filter, skipping\n" "$CLOUDWATCH_LOG_GROUP"
            continue
        fi
        
        printf "done\n"
        
        printf "Removing subscription filter %s from Cloudwatch Log group %s..." "$FILTER_NAME" "$CLOUDWATCH_LOG_GROUP"
        $DBG_BEFORE
        eval "$($DBG_INNER; aws logs delete-subscription-filter \
        --log-group-name $CLOUDWATCH_LOG_GROUP \
        --filter-name "$FILTER_NAME" \
        $REGION)"
        $DBG_AFTER
        printf "done\n"
    done
}

get_service_mapping() {
    printf "Retrieving service mappings (might take a while).."
    
    get_stack_name
    printf "."
    get_lambda_function_name
    printf "."
    get_lambda_function_arn
    printf "."
    get_log_groups
    printf "."
    
    for CLOUDWATCH_LOG_GROUP in $CLOUDWATCH_LOG_GROUPS; do
        # check if subscription filter matches the forwarder ARN
        $DBG_BEFORE
        SUBSCRIPTION_NAME_ARN=$($DBG_INNER; aws logs describe-subscription-filters \
        --log-group-name $CLOUDWATCH_LOG_GROUP \
        --query 'subscriptionFilters[].[filterName,destinationArn]' \
        --no-paginate \
        --output text \
        $REGION)
        $DBG_AFTER
        
        FILTER_NAME=
        DESTINATION_ARN=
        if [ -n "$SUBSCRIPTION_NAME_ARN" ] && [ "$SUBSCRIPTION_NAME_ARN" != "null" ]; then
            FILTER_NAME=$(echo "$SUBSCRIPTION_NAME_ARN" | awk '{print $1}')
            DESTINATION_ARN=$(echo "$SUBSCRIPTION_NAME_ARN" | awk '{print $2}')
            if [ "$DESTINATION_ARN" = "$LAMBDA_FUNCTION_ARN" ]; then
                FUNCTION=$(echo "$CLOUDWATCH_LOG_GROUP" | sed 's#/aws/lambda/##g')
                SERVICE=$(echo "$FILTER_NAME" | awk -F/ '{print $3}')
                ALIAS=$(echo "$FILTER_NAME" | awk -F/ '{print $4}')
                FUNCTION_SERVICE_MAPPINGS="$FUNCTION_SERVICE_MAPPINGS $FUNCTION:$SERVICE:$ALIAS"
            fi
        fi
        
        printf "."
    done
    
    printf "done\n"
}

print_service_mapping() {
    if [ -z "$FUNCTION_SERVICE_MAPPINGS" ]; then
        printf "====\n"
        printf "No mappings found.\n"
    fi
    for MAPPING in $FUNCTION_SERVICE_MAPPINGS; do
        FUNCTION=$(echo "$MAPPING" | awk -F: '{print $1}')
        SERVICE=$(echo "$MAPPING" | awk -F: '{print $2}')
        ALIAS=$(echo "$MAPPING" | awk -F: '{print $3}')
        
        printf "====\n"
        printf "Function name:\t%s\n" "$FUNCTION"
        printf "Service name:\t%s\n" "$SERVICE"
        printf "Hostname alias:\t%s\n" "$ALIAS"
    done
    
    printf "====\n"
}

####################################################################################
################################ INSTALL RC ########################################
####################################################################################

install_rc() {
    if [ -z "$RC_VERSION" ]; then
        if [ "$INTERACTIVE" = "yes" ]; then
            printf "Please enter the RC version to install: "
            read -r RC_VERSION
        fi
        if [ -z "$RC_VERSION" ]; then
            printf "Please set version via the --version option or interactively\n"
            print_usage_and_exit
        fi
    fi
    
    printf "Retrieving information needed for installing an RC version.."
    
    get_stack_name
    printf "."
    get_lambda_function_name
    
    printf "done\n"
    printf "Checking for version %s..." "$RC_VERSION"
    
    $DBG_BEFORE
    ZIP_FILE=$($DBG_INNER; aws s3 ls "$STAGING_S3/$RC_VERSION/forwarder.zip" || true)
    $DBG_AFTER
    
    if [ -z "$ZIP_FILE" ]; then
            printf "error\n"
            printf "Version %s not found.\n" "$RC_VERSION"
            exit 1
    fi
    
    printf "done\n"
    printf "Downloading file to '%s'..." "$TMP_DIR"
    
    rm -f $TMP_DIR/forwarder.zip
    
    $DBG_BEFORE
    eval "$($DBG_INNER; aws s3 cp "$STAGING_S3/$RC_VERSION/forwarder.zip" $TMP_DIR >/dev/null 2>&1)"
    $DBG_AFTER
    
    if [ ! -f "$TMP_DIR/forwarder.zip" ]; then
        printf "error\n"
        printf "Could not download %s/%s/forwarder.zip\n" "$STAGING_S3" "$RC_VERSION"
        exit 1
    fi
    
    printf "done\n"
    printf "Updating Lambda function %s with version %s..." "$LAMBDA_FUNCTION_NAME" "$RC_VERSION"
    
    $DBG_BEFORE
    RESULT=$($DBG_INNER; aws lambda update-function-code \
    --function-name $LAMBDA_FUNCTION_NAME \
    --zip-file fileb://$TMP_DIR/forwarder.zip \
    --query 'LastUpdateStatus' \
    --output text \
    $REGION || true)
    $DBG_AFTER
    
    if [ "$RESULT" != "Successful" ]; then
        printf "error\n"
        printf "Could not update Lambda function.\n"
        exit 1
    fi
    
    printf "done\n"
    
    rm -f $TMP_DIR/forwarder.zip
}

####################################################################################
################################## MAIN ############################################
####################################################################################

if [ "$ACTION" = "install" ]; then
    get_var_api_token
    get_var_service_mapping "optional, format <function>:<service>[:<alias>], "
    verify_var_service_mapping
    
    printf "All required information provided. Installing the AppOptics-Lambda-Forwarder...\n"
    printf "\n"
    
    install_forwarder
    add_service_mapping
    
    printf "=====================================================\n"
    printf "AppOptics Lambda Forwarder successfully installed\n"
    printf "=====================================================\n"
fi

if [ "$ACTION" = "remove" ]; then
    printf "Removing the AppOptics-Lambda-Forwarder...\n"
    printf "\n"
    
    get_service_mapping
    remove_service_mapping
    remove_forwarder
    
    printf "=====================================================\n"
    printf "AppOptics Lambda Forwarder successfully removed\n"
    printf "=====================================================\n"
fi

if [ "$ACTION" = "add-mapping" ]; then
    get_var_service_mapping "format <function>:<service>[:<alias>], "
    verify_var_service_mapping
    add_service_mapping
fi

if [ "$ACTION" = "remove-mapping" ]; then
    get_var_service_mapping "format <function>, "
    remove_service_mapping
fi

if [ "$ACTION" = "list-mapping" ]; then
    get_service_mapping
    print_service_mapping
fi

if [ "$ACTION" = "install-rc" ]; then
    install_rc
fi
