forger 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.gitmodules +0 -0
  4. data/.rspec +3 -0
  5. data/CHANGELOG.md +147 -0
  6. data/Gemfile +6 -0
  7. data/Gemfile.lock +136 -0
  8. data/Guardfile +19 -0
  9. data/LICENSE.txt +22 -0
  10. data/README.md +249 -0
  11. data/Rakefile +6 -0
  12. data/docs/example/.env +2 -0
  13. data/docs/example/.env.development +2 -0
  14. data/docs/example/.env.production +3 -0
  15. data/docs/example/app/scripts/hello.sh +3 -0
  16. data/docs/example/app/user-data/bootstrap.sh +35 -0
  17. data/docs/example/config/development.yml +8 -0
  18. data/docs/example/profiles/default.yml +11 -0
  19. data/docs/example/profiles/spot.yml +20 -0
  20. data/exe/forger +14 -0
  21. data/forger.gemspec +38 -0
  22. data/lib/forger.rb +29 -0
  23. data/lib/forger/ami.rb +10 -0
  24. data/lib/forger/aws_service.rb +7 -0
  25. data/lib/forger/base.rb +42 -0
  26. data/lib/forger/clean.rb +13 -0
  27. data/lib/forger/cleaner.rb +5 -0
  28. data/lib/forger/cleaner/ami.rb +45 -0
  29. data/lib/forger/cli.rb +67 -0
  30. data/lib/forger/command.rb +67 -0
  31. data/lib/forger/completer.rb +161 -0
  32. data/lib/forger/completer/script.rb +6 -0
  33. data/lib/forger/completer/script.sh +10 -0
  34. data/lib/forger/config.rb +20 -0
  35. data/lib/forger/core.rb +51 -0
  36. data/lib/forger/create.rb +155 -0
  37. data/lib/forger/create/error_messages.rb +58 -0
  38. data/lib/forger/create/params.rb +106 -0
  39. data/lib/forger/dotenv.rb +30 -0
  40. data/lib/forger/help.rb +9 -0
  41. data/lib/forger/help/ami.md +13 -0
  42. data/lib/forger/help/clean/ami.md +22 -0
  43. data/lib/forger/help/compile.md +5 -0
  44. data/lib/forger/help/completion.md +22 -0
  45. data/lib/forger/help/completion_script.md +3 -0
  46. data/lib/forger/help/create.md +7 -0
  47. data/lib/forger/help/upload.md +10 -0
  48. data/lib/forger/help/wait/ami.md +12 -0
  49. data/lib/forger/hook.rb +33 -0
  50. data/lib/forger/profile.rb +64 -0
  51. data/lib/forger/script.rb +46 -0
  52. data/lib/forger/script/compile.rb +40 -0
  53. data/lib/forger/script/compress.rb +62 -0
  54. data/lib/forger/script/templates/ami_creation.sh +12 -0
  55. data/lib/forger/script/templates/auto_terminate.sh +11 -0
  56. data/lib/forger/script/templates/auto_terminate_after_timeout.sh +5 -0
  57. data/lib/forger/script/templates/cloudwatch.sh +3 -0
  58. data/lib/forger/script/templates/extract_aws_ec2_scripts.sh +48 -0
  59. data/lib/forger/script/upload.rb +99 -0
  60. data/lib/forger/scripts/auto_terminate.sh +14 -0
  61. data/lib/forger/scripts/auto_terminate/after_timeout.sh +18 -0
  62. data/lib/forger/scripts/auto_terminate/functions.sh +130 -0
  63. data/lib/forger/scripts/auto_terminate/functions/amazonlinux2.sh +10 -0
  64. data/lib/forger/scripts/auto_terminate/functions/ubuntu.sh +11 -0
  65. data/lib/forger/scripts/auto_terminate/setup.sh +31 -0
  66. data/lib/forger/scripts/cloudwatch.sh +24 -0
  67. data/lib/forger/scripts/cloudwatch/configure.sh +84 -0
  68. data/lib/forger/scripts/cloudwatch/install.sh +3 -0
  69. data/lib/forger/scripts/cloudwatch/install/amazonlinux2.sh +4 -0
  70. data/lib/forger/scripts/cloudwatch/install/ubuntu.sh +23 -0
  71. data/lib/forger/scripts/cloudwatch/service.sh +3 -0
  72. data/lib/forger/scripts/cloudwatch/service/amazonlinux2.sh +11 -0
  73. data/lib/forger/scripts/cloudwatch/service/ubuntu.sh +8 -0
  74. data/lib/forger/scripts/shared/functions.sh +78 -0
  75. data/lib/forger/setting.rb +52 -0
  76. data/lib/forger/template.rb +13 -0
  77. data/lib/forger/template/context.rb +32 -0
  78. data/lib/forger/template/helper.rb +17 -0
  79. data/lib/forger/template/helper/ami_helper.rb +33 -0
  80. data/lib/forger/template/helper/core_helper.rb +127 -0
  81. data/lib/forger/template/helper/partial_helper.rb +71 -0
  82. data/lib/forger/template/helper/script_helper.rb +53 -0
  83. data/lib/forger/template/helper/ssh_key_helper.rb +21 -0
  84. data/lib/forger/version.rb +3 -0
  85. data/lib/forger/wait.rb +12 -0
  86. data/lib/forger/waiter.rb +5 -0
  87. data/lib/forger/waiter/ami.rb +61 -0
  88. data/spec/fixtures/demo_project/config/settings.yml +22 -0
  89. data/spec/fixtures/demo_project/config/test.yml +9 -0
  90. data/spec/fixtures/demo_project/profiles/default.yml +33 -0
  91. data/spec/lib/cli_spec.rb +41 -0
  92. data/spec/lib/params_spec.rb +71 -0
  93. data/spec/spec_helper.rb +33 -0
  94. metadata +354 -0
@@ -0,0 +1,62 @@
1
+ require 'fileutils'
2
+
3
+ class Forger::Script
4
+ class Compress < Forger::Base
5
+ def compress
6
+ reset
7
+ puts "Tarballing #{BUILD_ROOT}/app/scripts folder to scripts.tgz".colorize(:green)
8
+ tarball_path = create_tarball
9
+ save_scripts_info(tarball_path)
10
+ puts "Tarball created at #{tarball_path}"
11
+ end
12
+
13
+ def create_tarball
14
+ # https://apple.stackexchange.com/questions/14980/why-are-dot-underscore-files-created-and-how-can-i-avoid-them
15
+ sh "cd #{BUILD_ROOT}/app && dot_clean ." if system("type dot_clean > /dev/null")
16
+
17
+ # https://serverfault.com/questions/110208/different-md5sums-for-same-tar-contents
18
+ # Using tar czf directly results in a new m5sum each time because the gzip
19
+ # timestamp is included. So using: tar -c ... | gzip -n
20
+ sh "cd #{BUILD_ROOT}/app && tar -c scripts | gzip -n > scripts.tgz" # temporary app/scripts.tgz file
21
+
22
+ rename_with_md5!
23
+ end
24
+
25
+ def clean
26
+ FileUtils.rm_f("#{BUILD_ROOT}/scripts/scripts-#{md5sum}.tgz")
27
+ end
28
+
29
+ # Apppend a md5 to file after it's been created and moves it to
30
+ # output/scripts/scripts-[MD5].tgz
31
+ def rename_with_md5!
32
+ md5_path = "#{BUILD_ROOT}/scripts/scripts-#{md5sum}.tgz"
33
+ FileUtils.mkdir_p(File.dirname(md5_path))
34
+ FileUtils.mv("#{BUILD_ROOT}/app/scripts.tgz", md5_path)
35
+ md5_path
36
+ end
37
+
38
+ def save_scripts_info(scripts_name)
39
+ FileUtils.mkdir_p(File.dirname(SCRIPTS_INFO_PATH))
40
+ IO.write(SCRIPTS_INFO_PATH, scripts_name)
41
+ end
42
+
43
+ # cache this because the file will get removed
44
+ def md5sum
45
+ @md5sum ||= Digest::MD5.file("#{BUILD_ROOT}/app/scripts.tgz").to_s[0..7]
46
+ end
47
+
48
+ def sh(command)
49
+ puts "=> #{command}"
50
+ system command
51
+ end
52
+
53
+ # Only avaialble after script has been built.
54
+ def scripts_name
55
+ IO.read(SCRIPTS_INFO_PATH).strip
56
+ end
57
+
58
+ def reset
59
+ FileUtils.rm_f(SCRIPTS_INFO_PATH)
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,12 @@
1
+ #!/bin/bash -eux
2
+
3
+ # Create AMI Bundle
4
+ AMI_NAME="<%= @ami_name %>"
5
+ INSTANCE_ID=$(wget -q -O - http://169.254.169.254/latest/meta-data/instance-id)
6
+ REGION=$(aws configure get region)
7
+ # Note this will cause the instance to reboot. Not using the --no-reboot flag
8
+ # to ensure consistent AMI creation.
9
+ SOURCE_AMI_ID=$(wget -q -O - http://169.254.169.254/latest/meta-data/ami-id)
10
+ echo "$SOURCE_AMI_ID" > /var/log/source-ami-id.txt
11
+ mkdir -p /opt/forger/data
12
+ aws ec2 create-image --name "$AMI_NAME" --instance-id "$INSTANCE_ID" --region "$REGION" > /opt/forger/data/ami-id.txt
@@ -0,0 +1,11 @@
1
+ #!/bin/bash -eux
2
+
3
+ /opt/forger/auto_terminate/setup.sh
4
+
5
+ <% if @options[:auto_terminate] -%>
6
+ <% if @options[:ami_name] %>
7
+ /opt/forger/auto_terminate.sh later
8
+ <% else %>
9
+ /opt/forger/auto_terminate.sh now # terminate immediately
10
+ <% end -%>
11
+ <% end -%>
@@ -0,0 +1,5 @@
1
+ #!/bin/bash -eux
2
+
3
+ /opt/forger/auto_terminate/setup.sh
4
+
5
+ /opt/forger/auto_terminate.sh after_timeout
@@ -0,0 +1,3 @@
1
+ #!/bin/bash -eux
2
+
3
+ /opt/forger/cloudwatch.sh ec2
@@ -0,0 +1,48 @@
1
+ #!/bin/bash -eux
2
+
3
+ # Downloads and extract the scripts.
4
+ # The extracted folder from github looks like this:
5
+ # branch-name.tar.gz => forger-branch-name
6
+ # master.tar.gz => forger-master
7
+ # v1.0.0.tar.gz => forger-1.0.0
8
+ function extract_forger_scripts() {
9
+ local temp_folder
10
+ local url
11
+ local filename
12
+
13
+ rm -rf /opt/forger # clean start
14
+
15
+ temp_folder="/opt/forger-temp"
16
+ rm -rf "$temp_folder"
17
+ mkdir -p "$temp_folder"
18
+
19
+ (
20
+ cd "$temp_folder"
21
+
22
+ <%
23
+ # Examples:
24
+ # AWS_EC2_CODE=v1.0.0
25
+ # AWS_EC2_CODE=master
26
+ # AWS_EC2_CODE=branch-name
27
+ #
28
+ # https://github.com/tongueroo/forger/archive/v1.0.0.tar.gz
29
+ # https://github.com/tongueroo/forger/archive/master.tar.gz
30
+ code_version = ENV['AWS_EC2_CODE']
31
+ code_version ||= "v#{Forger::VERSION}"
32
+ %>
33
+ url="https://github.com/tongueroo/forger/archive/<%= code_version %>.tar.gz"
34
+ filename=$(basename "$url")
35
+ folder="${filename%.tar.gz}" # remove extension
36
+ folder="${folder#v}" # remove leading v character
37
+ folder="forger-$folder" # IE: forger-1.0.0
38
+
39
+ wget "$url"
40
+ tar zxf "$filename"
41
+
42
+ mv "$temp_folder/$folder/lib/forger/scripts" /opt/forger
43
+ rm -rf "$temp_folder"
44
+ chmod a+x -R /opt/forger
45
+ )
46
+ }
47
+
48
+ extract_forger_scripts
@@ -0,0 +1,99 @@
1
+ require 'filesize'
2
+ require 'aws-sdk-s3'
3
+ require 'fileutils'
4
+
5
+ # Class for forger upload command
6
+ class Forger::Script
7
+ class Upload < Forger::Base
8
+ def initialize(options={})
9
+ @options = options
10
+ @compile = @options[:compile] ? @options[:compile] : true
11
+ end
12
+
13
+ def run
14
+ compiler.compile_scripts if @compile
15
+ compressor.compress
16
+ upload(tarball_path)
17
+ compressor.clean
18
+ compiler.clean if @compile and Forger.settings["compile_clean"]
19
+ end
20
+
21
+ def upload(tarball_path)
22
+ puts "Uploading scripts.tgz (#{filesize}) to #{s3_dest}".colorize(:green)
23
+ obj = s3_resource.bucket(bucket_name).object(key)
24
+ start_time = Time.now
25
+ if @options[:noop]
26
+ puts "NOOP: Not uploading file to s3"
27
+ else
28
+ obj.upload_file(tarball_path)
29
+ end
30
+ time_took = pretty_time(Time.now-start_time).colorize(:green)
31
+ puts "Time to upload code to s3: #{time_took}"
32
+ end
33
+
34
+ def tarball_path
35
+ IO.read(SCRIPTS_INFO_PATH).strip
36
+ end
37
+
38
+ def filesize
39
+ Filesize.from(File.size(tarball_path).to_s + " B").pretty
40
+ end
41
+
42
+ def s3_dest
43
+ "s3://#{bucket_name}/#{key}"
44
+ end
45
+
46
+ def key
47
+ # Example key: ec2/development/scripts/scripts-md5
48
+ "#{dest_folder}/#{File.basename(tarball_path)}"
49
+ end
50
+
51
+ # Example:
52
+ # s3_folder: s3://infra-bucket/ec2
53
+ # bucket_name: infra-bucket
54
+ def bucket_name
55
+ s3_folder.sub('s3://','').split('/').first
56
+ end
57
+
58
+ # Removes s3://bucket-name and adds Forger.env. Example:
59
+ # s3_folder: s3://infra-bucket/ec2
60
+ # bucket_name: ec2/development/scripts
61
+ def dest_folder
62
+ folder = s3_folder.sub('s3://','').split('/')[1..-1].join('/')
63
+ "#{folder}/#{Forger.env}/scripts"
64
+ end
65
+
66
+ # s3_folder example:
67
+ def s3_folder
68
+ Forger.settings["s3_folder"]
69
+ end
70
+
71
+ def s3_resource
72
+ @s3_resource ||= Aws::S3::Resource.new
73
+ end
74
+
75
+ # http://stackoverflow.com/questions/4175733/convert-duration-to-hoursminutesseconds-or-similar-in-rails-3-or-ruby
76
+ def pretty_time(total_seconds)
77
+ minutes = (total_seconds / 60) % 60
78
+ seconds = total_seconds % 60
79
+ if total_seconds < 60
80
+ "#{seconds.to_i}s"
81
+ else
82
+ "#{minutes.to_i}m #{seconds.to_i}s"
83
+ end
84
+ end
85
+
86
+ def sh(command)
87
+ puts "=> #{command}"
88
+ system command
89
+ end
90
+
91
+ def compiler
92
+ @compiler ||= Compile.new(@options)
93
+ end
94
+
95
+ def compressor
96
+ @compressor ||= Compress.new(@options)
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,14 @@
1
+ #!/bin/bash -exu
2
+
3
+ if [ $# -eq 0 ]; then
4
+ command=$(basename "$0")
5
+ echo "Usage: $command WHEN"
6
+ echo "Examples:"
7
+ echo " $command now"
8
+ echo " $command later"
9
+ exit 1
10
+ fi
11
+ WHEN=$1 # now or later
12
+
13
+ source /opt/forger/auto_terminate/functions.sh
14
+ terminate "$WHEN"
@@ -0,0 +1,18 @@
1
+ #!/bin/bash -exu
2
+
3
+ # This is another copy the /opt/forger/auto_terminate.sh script because the
4
+ # /opt/forger/auto_terminate.sh also gets removed when it gets called. Specifically,
5
+ # it gets gets removed when "terminate after_ami" is called.
6
+
7
+ # There's this extra script that terminates after a timeout because sometimes:
8
+ # 1. the script stalls: IE: aws ec2 wait image-available and a custom wait_ami
9
+ # does this.
10
+ # 2. the user_data script breaks and stops before finishing, never reaching
11
+ # the terminate_later or terminate_now functions.
12
+ #
13
+
14
+ source /opt/forger/auto_terminate/functions.sh
15
+ # remove itself since at jobs survive reboots and if the ami gets created
16
+ # successfully we do not want this to be captured as part of the ami
17
+ rm -f /opt/forger/auto_terminate/after_timeout.sh
18
+ terminate now # hard code to now since only gets called via an at job
@@ -0,0 +1,130 @@
1
+ #!/bin/bash -eux
2
+
3
+ # Key is that instance will not be terminated if source image is the same as the
4
+ # original image id.
5
+ function terminate_instance() {
6
+ local SOURCE_AMI_ID
7
+ local AMI_ID
8
+
9
+ SOURCE_AMI_ID=$(curl -s http://169.254.169.254/latest/meta-data/ami-id)
10
+ AMI_ID=$(cat /opt/forger/data/ami-id.txt | jq -r '.ImageId')
11
+ if [ "$SOURCE_AMI_ID" = "$AMI_ID" ]; then
12
+ echo "The source ami and ami_id are the same: $AMI_ID"
13
+ echo "Will not terminate the instance for this case."
14
+ echo "This case can happen when an /etc/rc.local script to auto-terminate the "
15
+ echo "instance was capture from a previous AMI build."
16
+ return
17
+ fi
18
+
19
+ INSTANCE_ID=$(wget -q -O - http://169.254.169.254/latest/meta-data/instance-id)
20
+ SPOT_INSTANCE_REQUEST_ID=$(aws ec2 describe-instances --instance-ids "$INSTANCE_ID" | jq -r '.Reservations[].Instances[].SpotInstanceRequestId')
21
+
22
+ if [ -n "$SPOT_INSTANCE_REQUEST_ID" ]; then
23
+ cancel_spot_request
24
+ fi
25
+ aws ec2 terminate-instances --instance-ids "$INSTANCE_ID"
26
+ }
27
+
28
+ # on-demand instance example:
29
+ # $ aws ec2 describe-instances --instance-ids i-09482b1a6e330fbf7 | jq '.Reservations[].Instances[].SpotInstanceRequestId'
30
+ # null
31
+ # spot instance example:
32
+ # $ aws ec2 describe-instances --instance-ids i-08318bb7f33c216bd | jq '.Reservations[].Instances[].SpotInstanceRequestId'
33
+ # "sir-dzci5wsh"
34
+ function cancel_spot_request() {
35
+ aws ec2 cancel-spot-instance-requests --spot-instance-request-ids "$SPOT_INSTANCE_REQUEST_ID"
36
+ }
37
+
38
+ # When image doesnt exist at all, an empty string is returned.
39
+ function ami_state() {
40
+ local ami_id
41
+ ami_id=$1
42
+ aws ec2 describe-images --image-ids "$ami_id" --owners self | jq -r '.Images[].State'
43
+ }
44
+
45
+ function wait_for_ami() {
46
+ local name
47
+ name=$1
48
+
49
+ local x
50
+ local state
51
+ x=0
52
+
53
+ state=$(ami_state "$name")
54
+ while [ "$x" -lt 10 ] && [ "$state" != "available" ]; do
55
+ x=$((x+1))
56
+
57
+ state=$(ami_state "$name")
58
+ echo "state $state"
59
+ echo "sleeping for 60 seconds... times out at 10 minutes total"
60
+
61
+ type sleep
62
+ sleep 60
63
+ done
64
+
65
+ echo "final state $state"
66
+ }
67
+
68
+ function terminate() {
69
+ local when
70
+ when=$1
71
+
72
+ export PATH=/usr/local/bin:$PATH # for jq
73
+
74
+ if [ "$when" == "later" ]; then
75
+ terminate_later
76
+ elif [ "$when" == "after_ami" ]; then
77
+ terminate_after_ami
78
+ elif [ "$when" == "after_timeout" ]; then
79
+ terminate_after_timeout
80
+ else
81
+ terminate_now
82
+ fi
83
+ }
84
+
85
+ function terminate_later() {
86
+ schedule_termination
87
+ }
88
+
89
+ # This gets set up at the very beginning of the user_data script. This ensures
90
+ # that after a 45 minute timeout the instance will get cleaned up and terminated.
91
+ function terminate_after_timeout() {
92
+ rm -f /opt/forger/data/ami-id.txt # rm file possible stale file from previous ami
93
+
94
+ # Remove all old at jobs.
95
+ # Assume all at job are previous after_timeout.sh job from previous AMI.
96
+ # Note: Each new at job increments the id by 1. So each AMI will have a different
97
+ # at job number.
98
+ for i in $(atq | awk '{print $1}') ; do
99
+ at -r $i
100
+ done
101
+ echo "/opt/forger/auto_terminate/after_timeout.sh now" | at now + 45 minutes
102
+ }
103
+
104
+ function terminate_after_ami() {
105
+ local AMI_ID
106
+
107
+ unschedule_termination
108
+ AMI_ID=$(cat /opt/forger/data/ami-id.txt | jq -r '.ImageId')
109
+ if [ -n "$AMI_ID" ]; then
110
+ # wait for the ami to be successfully created before terminating the instance
111
+ # https://docs.aws.amazon.com/cli/latest/reference/ec2/wait/image-available.html
112
+ # It will poll every 15 seconds until a successful state has been reached. This will exit with a return code of 255 after 40 failed checks.
113
+ # so it'll wait for 10 mins max
114
+ # aws ec2 wait image-available --image-ids "$AMI_ID" --owners self
115
+
116
+ # For some reason aws ec2 wait image-available didnt work for amazonlinux2
117
+ # so using a custom version
118
+ wait_for_ami "$AMI_ID"
119
+ fi
120
+
121
+ terminate_instance
122
+ }
123
+
124
+ function terminate_now() {
125
+ terminate_instance
126
+ }
127
+
128
+ source "/opt/forger/shared/functions.sh"
129
+ os=$(os_name)
130
+ source "/opt/forger/auto_terminate/functions/${os}.sh"
@@ -0,0 +1,10 @@
1
+ #!/bin/bash -eux
2
+ function schedule_termination() {
3
+ chmod +x /etc/rc.d/rc.local
4
+ echo "/opt/forger/auto_terminate.sh after_ami >> /var/log/auto-terminate.log 2>&1" >> /etc/rc.d/rc.local
5
+ }
6
+
7
+ function unschedule_termination() {
8
+ grep -v auto_terminate.sh /etc/rc.d/rc.local > /etc/rc.d/rc.local.tmp
9
+ mv /etc/rc.d/rc.local.tmp /etc/rc.d/rc.local
10
+ }
@@ -0,0 +1,11 @@
1
+ #!/bin/bash -eux
2
+ function schedule_termination() {
3
+ chmod +x /etc/rc.local
4
+ sed -i 's/exit 0//' /etc/rc.local
5
+ echo "/opt/forger/auto_terminate.sh after_ami >> /var/log/auto-terminate.log 2>&1" >> /etc/rc.local
6
+ }
7
+
8
+ function unschedule_termination() {
9
+ grep -v terminate-myself /etc/rc.local > /etc/rc.local.tmp
10
+ mv /etc/rc.local{.tmp,}
11
+ }
@@ -0,0 +1,31 @@
1
+ #!/bin/bash -eux
2
+ function install_jq() {
3
+ if ! type jq > /dev/null ; then
4
+ wget "https://github.com/stedolan/jq/releases/download/jq-1.5/jq-linux64"
5
+ mv jq-linux64 /usr/local/bin/jq
6
+ chmod a+x /usr/local/bin/jq
7
+ fi
8
+ }
9
+
10
+ function configure_aws_cli() {
11
+ local home_dir
12
+ home_dir=${1:-/root} # default to /root
13
+ # Configure aws cli in case it is not yet configured
14
+ mkdir -p "$home_dir/.aws"
15
+ if [ ! -f "$home_dir/.aws/config" ]; then
16
+ EC2_AVAIL_ZONE=$(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)
17
+ EC2_REGION=${EC2_AVAIL_ZONE::-1}
18
+ cat >"$home_dir/.aws/config" <<CONFIGURE_AWS_CLI
19
+ [default]
20
+ region = $EC2_REGION
21
+ output = json
22
+ CONFIGURE_AWS_CLI
23
+ fi
24
+ }
25
+
26
+ function setup() {
27
+ install_jq
28
+ configure_aws_cli /root
29
+ }
30
+
31
+ setup