forger 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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