#!/bin/bash ############################################################ # # Get a list of components of an Amazon Connect instance # VERSION=1.3.5 SCRIPT_VERSION="$(basename $0) $VERSION" USAGE=$(cat <&2; exit 2; } version() { echo -e "$SCRIPT_VERSION"; exit; } dos2unix() { tr -d '\r'; } error() { if [ "$#" -ne 0 ]; then if [[ ! "$1" =~ ^[[:digit:]]+$ ]]; then # Not an AWS CLI error cat <&2 Error: $* EOD else # An AWS CLI error line_no=$1 cf=$2 manifest=$3 if [ -n "$cf" -a -n "$manifest" -a -n "$skip_cf_errors" ]; then # TEMPCF reused and needs to be emptied afterwards cat $manifest | jq -r "select(.Name != \"$cf\")" > $TEMPCF cat $TEMPCF > $manifest echo "\"$cf\" skipped and removed from $manifest." echo # Return ok to allow the main process to continue without this CF > $TEMPCF return 0 else cat <&2 Error at line ${line_no}. Recommended actions: 1. Create all required prompts on the target instance 2. Publish all in-scope contact flows and contact flow modules (or use -s to skip them) 3. Install the latest AWS CLI: https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html EOD fi fi fi exit 1 } hex_cmd= if [ -x "$(command -v xxd)" ]; then hex_cmd="xxd -u -p -c1" elif [ -x "$(command -v hexdump)" ]; then hex_cmd="hexdump -v -e '/1 \"%02X\n\"'" elif [ -x "$(command -v od)" ]; then hex_cmd="od -An -vtx1 | tr [:lower:] [:upper:] | for i in \$(cat); do echo \$i; done" fi test -z "$hex_cmd" && error "Cannot find any hex conversion commands. Please install one of these: xxd, hexdump, od" hex_code() { printf '%s' "$1" | eval "$hex_cmd" | while read x; do printf "%%%s" "$x"; done } path_encode() { old_lc_collate=$LC_COLLATE LC_COLLATE=C local length="${#1}" for (( i = 0; i < length; i++ )); do local c="${1:$i:1}" case $c in [a-zA-Z0-9.~_-]) printf '%s' "$c";; *) x=$(hex_code "$c"); echo -n ${x//%0D/};; esac done LC_COLLATE=$old_lc_collate } # AWS CLI needs to output JSON export AWS_DEFAULT_OUTPUT=json aws_cli_encoding=UTF-8 aws_cli_out_filter() { if [ -n "$codepage" -a "$codepage" != "$aws_cli_encoding" ]; then cat > $TEMPOF iconv -f $codepage -t $aws_cli_encoding $TEMPOF 2> /dev/null test $? -eq 0 || cat $TEMPOF else cat fi } add_json_attribute() { attr_name=$1 attr_value=$2 key_id=$3 manifest=$4 #echo "Adding $attr_name=\"$attr_value\" to $key_id in $manifest" # TEMPCF reused and needs to be emptied afterwards cat $manifest | jq -rs "map(if .Id == \"$key_id\" then . + {$attr_name: \"${attr_value//\"/\\\"}\"} else . end) | .[]" > $TEMPCF cat $TEMPCF > $manifest > $TEMPCF # jq -r "map(.Id |= if .Id == \"$key_id\" then . else . end)" } aws_connect() { local cmd="" local ii for ii; do [[ "$ii" == *" "* ]] && cmd="$cmd \"$ii\"" || cmd="$cmd $ii" done echo "aws connect$profile_flag$cmd" >> "$aws_cli_log" eval "aws connect$profile_flag$cmd | aws_cli_out_filter" 2> $TEMPERR local ret=$? if [ -s $TEMPERR ]; then cat $TEMPERR | tee -a "$aws_cli_log" >&2 fi return $ret } TEMPFILE=$(mktemp) TEMPFILE2=${TEMPFILE}_2 TEMPCF=${TEMPFILE}_cf TEMPOF=${TEMPFILE}_of TEMPERR=${TEMPFILE}_err trap 'rm -r -- $TEMPFILE $TEMPFILE2 $TEMPCF $TEMPERR' EXIT touch $TEMPFILE $TEMPFILE2 $TEMPCF $TEMPOF $TEMPERR maxitems=999 forced= skip_cf_errors= ignore_improper_extended_ascii= ignore_prefix= jq_prefix_filter= jq_prefix_filter_text= # codepage=${LANG##*.} eval $(locale -k LC_CTYPE | grep charmap) codepage=${charmap:-$aws_cli_encoding} codepage=${codepage##*.} while getopts "?fsevp:c:G:C:" arg; do case "$arg" in f) forced=on;; s) skip_cf_errors=on;; e) ignore_improper_extended_ascii=on;; v) version;; p) profile=$OPTARG;; c) contact_flow_prefix=$OPTARG;; G) ignore_prefix=$OPTARG jq_prefix_filter=" | select(.Name | test(\"^($ignore_prefix)\") | not)" jq_prefix_filter_text=" excluding those with names prefixed with \"$ignore_prefix\"" ;; C) codepage=$OPTARG;; *) usage;; esac done shift $((OPTIND-1)) if [ "$(hex_code "é")" != "%C3%A9" ]; then echo "WARNING: This system may not encode Extended ASCII characters properly." >&2 if [ -n "$ignore_improper_extended_ascii" ]; then echo "Proceed regardless as the -e option is specified." >&2 else cat <&2 If your instance component names contain Extended ASCII characters, such as accented letters like é, this system will encode those names differently from standard encoding. If you are sure that your component names do not contain Extended ASCII characters, you may proceed regardless by running the command again with the -e option. EOD exit 1 fi fi instance_alias_dir=$1 instance_alias=$(basename "$instance_alias_dir") # Backward compatibility - if it is set, ignore the argument profile=${profile:-$2} profile_flag=${profile:+ --profile $profile} # Backward compatibility - if it is set, ignore the argument contact_flow_prefix=${contact_flow_prefix:-$3} contact_flow_prefix_filter=${contact_flow_prefix:+" | select(.Name | test(\"^($contact_flow_prefix|Default ).*\"))"} contact_flow_prefix_text="${contact_flow_prefix:+ with names prefixed with \"$contact_flow_prefix\" or Default}" if [ -z "$instance_alias_dir" ]; then usage fi if [ -d "$instance_alias_dir" ]; then if [ -n "$forced" ]; then echo "Existing Alias directory forcefully removed: \"$instance_alias_dir\"" rm -fr "$instance_alias_dir" else error "Alias directory already exists. Remove and try again (or use -f): \"$instance_alias_dir\"" fi fi cat < "$aws_cli_log" # First aws cli; only mkdir if successful. aws_connect list-instances --max-items $maxitems > $TEMPFILE || error $LINENO instance_id=$(cat $TEMPFILE | jq -r ".InstanceSummaryList[] | select(.InstanceAlias == \"$instance_alias\")" | tee "$instance_alias_dir/instance.json" | jq -r ".Id" | dos2unix) echo "Instance ID: $instance_id" test -n "$instance_id" || error "Instance $instance_alias does not exist" cat "$instance_alias_dir/instance.json" | grep : | sed -e's/ *"/instance_/' -e's/": /=/' -e's/,$//' > "$instance_alias_dir/instance.var" echo "aws_Profile=\"$profile\"" >> "$instance_alias_dir/instance.var" echo "instance_contact_flow_prefix=\"$contact_flow_prefix\"" >> "$instance_alias_dir/instance.var" ############################################################ # # Prompts # aws_connect list-prompts \ --instance-id $instance_id \ --max-items $maxitems \ > $TEMPFILE || error $LINENO cat $TEMPFILE | jq -r ".PromptSummaryList |= sort_by(.Name) | .[][]" | tee "$instance_alias_dir/prompts.json" | echo -e "\n$(jq -s "length") prompts listed in \"$instance_alias_dir/prompts.json\"" ############################################################ # # Hours of operations # aws_connect list-hours-of-operations \ --instance-id $instance_id \ --max-items $maxitems \ > $TEMPFILE || error $LINENO cat $TEMPFILE | # jq -r ".HoursOfOperationSummaryList |= sort_by(.Name) | .[][]" | jq -r ".HoursOfOperationSummaryList[]$jq_prefix_filter" | jq -s "sort_by(.Name) | .[]" | tee "$instance_alias_dir/hours.json" | echo -e "\n$(jq -s "length") hours of operations listed in \"$instance_alias_dir/hours.json\"$jq_prefix_filter_text" jq -r ".Id + \" \" + .Name" "$instance_alias_dir/hours.json" | dos2unix | while read hour_id hour_name; do echo "Exporting hours of operation $hour_name" hour_name_encoded=$(path_encode "$hour_name") aws_connect describe-hours-of-operation \ --instance-id $instance_id \ --hours-of-operation-id $hour_id \ > "$instance_alias_dir/hour_$hour_name_encoded.json" || error $LINENO done test $? -eq 0 || error ############################################################ # # Queues # aws_connect list-queues \ --instance-id $instance_id \ --max-items $maxitems \ --queue-types "STANDARD" \ > $TEMPFILE || error $LINENO cat $TEMPFILE | jq -r ".QueueSummaryList[] | select(.QueueType != \"AGENT\")$jq_prefix_filter" | jq -s "sort_by(.Name) | .[]" | tee "$instance_alias_dir/queues.json" | echo -e "\n$(jq -s "length") queues listed in \"$instance_alias_dir/queues.json\"$jq_prefix_filter_text" jq -r ".Id + \" \" + .Name" "$instance_alias_dir/queues.json" | dos2unix | while read queue_id queue_name; do echo "Exporting queue $queue_name" queue_name_encoded=$(path_encode "$queue_name") aws_connect describe-queue \ --instance-id $instance_id \ --queue-id $queue_id \ > "$instance_alias_dir/queue_$queue_name_encoded.json" || error $LINENO done test $? -eq 0 || error ############################################################ # # Routing Profiles # aws_connect list-routing-profiles \ --instance-id $instance_id \ --max-items $maxitems \ > $TEMPFILE || error $LINENO cat $TEMPFILE | jq -r ".RoutingProfileSummaryList[]$jq_prefix_filter" | jq -s "sort_by(.Name) | .[]" | tee "$instance_alias_dir/routings.json" | echo -e "\n$(jq -s "length") routing profiles listed in \"$instance_alias_dir/routings.json\"$jq_prefix_filter_text" jq -r ".Id + \" \" + .Name" "$instance_alias_dir/routings.json" | dos2unix | while read routing_id routing_name; do echo "Exporting routing profile $routing_name" routing_name_encoded=$(path_encode "$routing_name") aws_connect describe-routing-profile \ --instance-id $instance_id \ --routing-profile-id $routing_id | jq -r "del(.RoutingProfile.NumberOfAssociatedQueues, .RoutingProfile.NumberOfAssociatedUsers)" \ > "$instance_alias_dir/routing_$routing_name_encoded.json" || error $LINENO aws_connect list-routing-profile-queues \ --instance-id $instance_id \ --routing-profile-id $routing_id \ --max-items $maxitems \ > "$instance_alias_dir/routingQs_$routing_name_encoded.json" || error $LINENO done test $? -eq 0 || error ############################################################ # # Contact Flow Modules # aws_connect list-contact-flow-modules \ --instance-id $instance_id \ --max-items $maxitems \ > $TEMPFILE || error $LINENO cat $TEMPFILE | jq -r ".ContactFlowModulesSummaryList[]${contact_flow_prefix_filter}${jq_prefix_filter}" | jq -s "sort_by(.Name) | .[]" | tee "$instance_alias_dir/modules.json" | echo -e "\n$(jq -s "length") contact flow modules listed in \"$instance_alias_dir/modules.json\"$contact_flow_prefix_text$jq_prefix_filter_text" ############################################################ # # Export Contact Flow Modules # cat "$instance_alias_dir/modules.json" > $TEMPFILE jq -r ".Id + \" \" + .Name" $TEMPFILE | dos2unix | while read module_id module_name; do echo "Exporting contact flow module $module_name" module_name_encoded=$(path_encode "$module_name") aws_connect describe-contact-flow-module \ --instance-id $instance_id \ --contact-flow-module-id $module_id > $TEMPCF \ || error $LINENO "$module_name" "$instance_alias_dir/modules.json" if [ -s $TEMPCF ]; then # Status == "saved" is considered a failure, # not to copy an unpublished contact flow module cfm_status=$(jq -r ".ContactFlowModule.Status" $TEMPCF | dos2unix) if [ "$cfm_status" == "published" ]; then cat $TEMPCF | jq -r '.ContactFlowModule.Content' > "$instance_alias_dir/module_$module_name_encoded.json" cfm_description=$(cat $TEMPCF | jq -r '.ContactFlowModule.Description') add_json_attribute Description "$cfm_description" $module_id "$instance_alias_dir/modules.json" else echo echo "$module_name: Contact flow module not published" # Let error decide if continue or not error $LINENO "$module_name" "$instance_alias_dir/modules.json" fi fi done test $? -eq 0 || error ############################################################ # # Contact Flows # aws_connect list-contact-flows \ --instance-id $instance_id \ --max-items $maxitems \ > $TEMPFILE || error $LINENO cat $TEMPFILE | jq -r ".ContactFlowSummaryList[]${contact_flow_prefix_filter}${jq_prefix_filter}" | jq -s "sort_by(.Name) | .[]" | tee "$instance_alias_dir/flows.json" | echo -e "\n$(jq -s "length") contact flows listed in \"$instance_alias_dir/flows.json\"$contact_flow_prefix_text$jq_prefix_filter_text" ############################################################ # # Export Contact Flows # cat "$instance_alias_dir/flows.json" > $TEMPFILE jq -r ".Id + \" \" + .Name" $TEMPFILE | dos2unix | while read flow_id flow_name; do echo "Exporting contact flow $flow_name" aws_connect describe-contact-flow \ --instance-id $instance_id \ --contact-flow-id $flow_id > $TEMPCF \ 2> /dev/null # || error $LINENO "$flow_name" "$instance_alias_dir/flows.json" # AWS CLI seems not returning error for unpublished # Need to check for an empty $TEMPCF flow_name_encoded=$(path_encode "$flow_name") if [ -s $TEMPCF ]; then cat $TEMPCF | jq -r '.ContactFlow.Content' > "$instance_alias_dir/flow_$flow_name_encoded.json" cf_description=$(cat $TEMPCF | jq -r '.ContactFlow.Description') add_json_attribute Description "$cf_description" $flow_id "$instance_alias_dir/flows.json" else echo echo "$flow_name: Contact flow not published" error $LINENO "$flow_name" "$instance_alias_dir/flows.json" fi done test $? -eq 0 || error echo echo "All done"