cloudstrap 0.29.1.pre

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 79075784a2e532fa8f708f83ab14d4d2d4f7f262
4
+ data.tar.gz: a36f02ccc53bbba3cd7c6985c9dc778a016ebcbf
5
+ SHA512:
6
+ metadata.gz: db973e404ee5707cbcb8b5af935815b8aca99df65a77b9f908f362c39cd2a4aa7e389e82227f62321305df04dcdb1f56d93fa2bdcd4be83c7921e3be7fbf925e
7
+ data.tar.gz: 68112e361736d5a9912f3638a4706547a1d23c4eb9108a96b7bd8ace7b438a699741ea8f8b04f0edfdd92c3083f4a1108870f711ccaa06f442e6b265bdc05bc3
data/.gitignore ADDED
@@ -0,0 +1,55 @@
1
+
2
+ # Created by https://www.gitignore.io/api/ruby
3
+
4
+ ### Ruby ###
5
+ *.gem
6
+ *.rbc
7
+ /.config
8
+ /coverage/
9
+ /InstalledFiles
10
+ /pkg/
11
+ /spec/reports/
12
+ /spec/examples.txt
13
+ /test/tmp/
14
+ /test/version_tmp/
15
+ /tmp/
16
+
17
+ # Used by dotenv library to load environment variables.
18
+ # .env
19
+
20
+ ## Specific to RubyMotion:
21
+ .dat*
22
+ .repl_history
23
+ build/
24
+ *.bridgesupport
25
+ build-iPhoneOS/
26
+ build-iPhoneSimulator/
27
+
28
+ ## Specific to RubyMotion (use of CocoaPods):
29
+ #
30
+ # We recommend against adding the Pods directory to your .gitignore. However
31
+ # you should judge for yourself, the pros and cons are mentioned at:
32
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
33
+ #
34
+ # vendor/Pods/
35
+
36
+ ## Documentation cache and generated files:
37
+ /.yardoc/
38
+ /_yardoc/
39
+ /doc/
40
+ /rdoc/
41
+
42
+ ## Environment normalization:
43
+ /.bundle/
44
+ /vendor/bundle
45
+ /lib/bundler/man/
46
+
47
+ # for a library or gem, you might want to ignore these files since the code is
48
+ # intended to run in multiple environments; otherwise, check them in:
49
+ # Gemfile.lock
50
+ # .ruby-version
51
+ # .ruby-gemset
52
+
53
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
54
+ .rvmrc
55
+ .cache/**
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+
2
+ The MIT License (MIT)
3
+ Copyright © 2016 Chris Olstrom <chris@olstrom.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the “Software”), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.org ADDED
@@ -0,0 +1,114 @@
1
+ #+TITLE: Cloudstrap
2
+ #+SUBTITLE: Strapping Boots to Clouds
3
+ #+LATEX: \pagebreak
4
+
5
+ * Overview
6
+
7
+ =cloudstrap= straps your boots to the cloud. Think of it as sort of a
8
+ meta-bootstrap tool. It handles pre-bootstrapping, primarily. Once complete, you
9
+ should have an environment suitable for launching your bootstrapper.
10
+
11
+ * Why does this exist?
12
+
13
+ To simplify deployment by replacing manual process with glorious automation.
14
+
15
+ * Installation
16
+
17
+ #+BEGIN_SRC shell
18
+ gem install --pre cloudstrap
19
+ #+END_SRC
20
+
21
+ * Usage
22
+
23
+ Simply run the automated bootstrap tool. You can configure it via ENV or =config.yaml=
24
+
25
+ #+BEGIN_SRC shell
26
+ env BOOTSTRAP_WITHOUT_HUMAN_OVERSIGHT=true cloudstrap
27
+ #+END_SRC
28
+
29
+ The output describes the results, and all settings used in the process.
30
+
31
+ #+BEGIN_EXAMPLE
32
+ # Cloudstrap v0.28.0.pre
33
+
34
+ # General Configuration
35
+ # These settings can be configured via ENV or config.yaml
36
+
37
+ BOOTSTRAP_REGION=us-west-2
38
+ BOOTSTRAP_CACHE_PATH=/Users/colstrom/cloudstrap/.cache
39
+ BOOTSTRAP_VPC_CIDR_BLOCK=10.0.0.0/16
40
+ BOOTSTRAP_PUBLIC_CIDR_BLOCK=10.0.0.0/24
41
+ BOOTSTRAP_PRIVATE_CIDR_BLOCK=10.0.1.0/24
42
+ BOOTSTRAP_AMI_OWNER=099720109477
43
+ BOOTSTRAP_UBUNTU_RELEASE=14.04
44
+ BOOTSTRAP_INSTANCE_TYPE=t2.micro
45
+ BOOTSTRAP_SSH_DIR=/Users/colstrom/cloudstrap/.ssh
46
+ BOOTSTRAP_SSH_USERNAME=ubuntu
47
+ BOOTSTRAP_HDP_DIR=/Users/colstrom/cloudstrap
48
+
49
+ # Cached Configuration
50
+ # These settings can be overridden via ENV only
51
+
52
+ BOOTSTRAP_USERNAME=colstrom
53
+ BOOTSTRAP_UUID=97cb1464-cf44-479e-be47-9a212131ad4c
54
+ BOOTSTRAP_TAG=lkg@colstrom/97cb1464-cf44-479e-be47-9a212131ad4c
55
+ BOOTSTRAP_AMI=ami-70b67d10
56
+ BOOTSTRAP_VPC_ID=vpc-0e93a16a
57
+ BOOTSTRAP_INTERNET_GATEWAY_ID=igw-942af8f0
58
+ BOOTSTRAP_JUMPBOX_SECURITY_GROUP=sg-43f9183a
59
+ BOOTSTRAP_PRIVATE_SUBNET_ID=subnet-b7047dc1
60
+ BOOTSTRAP_PUBLIC_SUBNET_ID=subnet-9c1c65ea
61
+ BOOTSTRAP_ROUTE_TABLE_ID=rtb-9d28dcfa
62
+ BOOTSTRAP_AVAILABILITY_ZONE=us-west-2b
63
+ BOOTSTRAP_JUMPBOX_ID=i-051c248563a2c3f54
64
+ BOOTSTRAP.JUMPBOX_IP=52.89.237.123
65
+ BOOTSTRAP_WITHOUT_HUMAN_OVERSIGHT=true
66
+
67
+ # Additional Information
68
+ # These do not need configuration, and are presented as a debugging checklist
69
+
70
+ # Public IPs Enabled in Public Subnet? true
71
+ # Gateway Attached to VPC? true
72
+ # Route to Internet via Gateway? true
73
+ # Route to NAT Gateway from Private Subnet? true
74
+ # SSH Allowed to Jumpbox? true
75
+ # SSH Key uploaded to AWS? 1a:c4:ec:25:94:90:ed:ee:c2:66:2e:3c:9c:c8:7a:12
76
+ # HDP bootstrap.properties configured? true
77
+ # Jumpbox Running? true
78
+
79
+ # No version specified for HDP Bootstrap, falling back to default version
80
+ INFO [0942f874] Running /usr/bin/env rm -f /home/ubuntu/.ssh/id_rsa as ubuntu@52.89.237.123
81
+ INFO [0942f874] Finished in 11.547 seconds with exit status 0 (successful).
82
+ INFO Uploading /Users/colstrom/cloudstrap/.ssh/lkg@colstrom/97cb1464-cf44-479e-be47-9a212131ad4c 100.0%
83
+ INFO [bec7b898] Running /usr/bin/env chmod -w /home/ubuntu/.ssh/id_rsa as ubuntu@52.89.237.123
84
+ INFO [bec7b898] Finished in 0.048 seconds with exit status 0 (successful).
85
+ INFO Uploading /Users/colstrom/bootstrap.properties 100.0%
86
+ INFO [e091db46] Running /usr/bin/env apt install --assume-yes genisoimage aria2 as ubuntu@54.89.237.123
87
+ INFO [e091db46] Finished in 8.157 seconds with exit status 0 (successful).
88
+ INFO [1fa8cae1] Running /usr/bin/env aria2c --continue=true --dir=/opt --out=bootstrap.deb https://s3-us-west-2.amazonaws.com/hcp-concourse/hcp-bootstrap_1.2.30%2Bmaster.77bb464.20160819000448_amd64.deb as ubuntu@52.89.237.123
89
+ INFO [1fa8cae1] Finished in 3.395 seconds with exit status 0 (successful).
90
+ INFO [577d4fdc] Running /usr/bin/env dpkg --install /opt/bootstrap.deb as ubuntu@52.89.237.123
91
+ INFO [577d4fdc] Finished in 3.055 seconds with exit status 0 (successful).
92
+
93
+ Contratulations! Your Jumpbox is ready. This instance has been configured with
94
+ everything you need to deploy your very own Stackato. The SSH key used by this
95
+ process has been uploaded to your Jumpbox, along with a bootstrap.properties
96
+ file that has been configured to match the settings used here.
97
+
98
+ Human oversight has been disabled.
99
+
100
+ Now would be a good time for tea and swordplay (https://xkcd.com/303/). What
101
+ happens next is non-interactive and will take approximately 30 minutes.
102
+
103
+ INFO [03a68da8] Running /usr/bin/env bootstrap install bootstrap.properties as ubuntu@52.89.237.123
104
+ INFO [03a68da8] Finished in 1457.406 seconds with exit status 0 (successful).
105
+ #+END_EXAMPLE
106
+
107
+ * License
108
+
109
+ =cloudstrap= is available under the [[https://tldrlegal.com/license/mit-license][MIT License]]. See ~LICENSE.txt~ for the
110
+ full text. The software it deploys has its own license.
111
+
112
+ * Contributors
113
+
114
+ - [[https://colstrom.github.io/][Chris Olstrom]] | [[mailto:chris@olstrom.com][e-mail]] | [[https://twitter.com/ChrisOlstrom][Twitter]]
data/bin/cloudstrap ADDED
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'pastel'
4
+
5
+ if ENV['HACKING']
6
+ require_relative '../lib/cloudstrap'
7
+ else
8
+ require 'cloudstrap'
9
+ end
10
+
11
+ gem = Gem::Specification
12
+ .find_all_by_name('cloudstrap')
13
+ .sort_by { |spec| spec.version }
14
+ .last
15
+
16
+ config = StackatoLKG::Config.new
17
+ agent = StackatoLKG::BootstrapAgent.new
18
+
19
+ puts <<-EOS
20
+ # #{gem.name} v#{gem.version}
21
+ # #{gem.summary}
22
+
23
+ # General Configuration
24
+ # These settings can be configured via ENV or config.yaml
25
+
26
+ BOOTSTRAP_REGION=#{config.region}
27
+ BOOTSTRAP_CACHE_PATH=#{config.cache_path}
28
+ BOOTSTRAP_VPC_CIDR_BLOCK=#{config.vpc_cidr_block}
29
+ BOOTSTRAP_PUBLIC_CIDR_BLOCK=#{config.public_cidr_block}
30
+ BOOTSTRAP_PRIVATE_CIDR_BLOCK=#{config.private_cidr_block}
31
+ BOOTSTRAP_AMI_OWNER=#{config.ami_owner}
32
+ BOOTSTRAP_UBUNTU_RELEASE=#{config.ubuntu_release}
33
+ BOOTSTRAP_INSTANCE_TYPE=#{config.instance_type}
34
+ BOOTSTRAP_SSH_DIR=#{config.ssh_dir}
35
+ BOOTSTRAP_SSH_USERNAME=#{config.ssh_username}
36
+ BOOTSTRAP_HDP_DIR=#{config.hdp_dir}
37
+ BOOTSTRAP_HDP_BOOTSTRAP_ORIGIN=#{config.hdp_origin}
38
+ BOOTSTRAP_HDP_BOOTSTRAP_VERSION=#{config.hdp_version}
39
+ BOOTSTRAP_HDP_BOOTSTRAP_PACKAGE_URL=#{config.hdp_package_url}
40
+ BOOTSTRAP_PROPERTIES_SEED_URL=#{config.bootstrap_properties_seed_url}
41
+
42
+ # Cached Configuration
43
+ # These settings can be overridden via ENV only
44
+
45
+ BOOTSTRAP_USERNAME=#{agent.username}
46
+ BOOTSTRAP_UUID=#{agent.uuid}
47
+ BOOTSTRAP_TAG=#{agent.bootstrap_tag}
48
+ BOOTSTRAP_AMI=#{agent.ami}
49
+ BOOTSTRAP_VPC_ID=#{agent.vpc}
50
+ BOOTSTRAP_INTERNET_GATEWAY_ID=#{agent.internet_gateway}
51
+ BOOTSTRAP_NAT_GATEWAY_ID=#{agent.nat_gateway}
52
+ BOOTSTRAP_JUMPBOX_SECURITY_GROUP=#{agent.jumpbox_security_group}
53
+ BOOTSTRAP_PRIVATE_SUBNET_ID=#{agent.private_subnet}
54
+ BOOTSTRAP_PUBLIC_SUBNET_ID=#{agent.public_subnet}
55
+ BOOTSTRAP_ROUTE_TABLE_ID=#{agent.route_table}
56
+ BOOTSTRAP_PRIVATE_ROUTE_TABLE_ID=#{agent.private_route_table}
57
+ BOOTSTRAP_NAT_ROUTE_ASSOCIATION_ID=#{agent.nat_route_association}
58
+ BOOTSTRAP_PRIVATE_AVAILABILITY_ZONE=#{agent.private_availability_zone}
59
+ BOOTSTRAP_PUBLIC_AVAILABILITY_ZONE=#{agent.public_availability_zone}
60
+ BOOTSTRAP_JUMPBOX_ID=#{agent.jumpbox}
61
+ BOOTSTRAP_JUMPBOX_IP=#{agent.jumpbox_ip}
62
+ BOOTSTRAP_WITHOUT_HUMAN_OVERSIGHT=#{!agent.requires_human_oversight?}
63
+
64
+ # Additional Information
65
+ # These do not need configuration, and are presented as a debugging checklist
66
+
67
+ # Public IPs Enabled in Public Subnet? #{agent.enable_public_ips}
68
+ # Gateway Attached to VPC? #{agent.attach_gateway}
69
+ # Route to Internet via Gateway? #{agent.default_route}
70
+ # Route to NAT Gateway from Private Subnet? #{agent.nat_route}
71
+ # SSH Allowed to Jumpbox? #{agent.allow_ssh}
72
+ # SSH Key uploaded to AWS? #{agent.upload_ssh_key}
73
+ # HDP bootstrap.properties configured? #{agent.configure_hdp}
74
+ # Jumpbox Running? #{agent.jumpbox_running?}
75
+
76
+ EOS
77
+
78
+ if agent.jumpbox_running?
79
+ agent.configure_jumpbox
80
+
81
+ STDERR.puts Pastel.new.green <<-EOS
82
+
83
+ Congratulations! Your Jumpbox is ready. This instance has been configured with
84
+ everything you need to deploy your very own Stackato. The SSH key used by this
85
+ process has been uploaded to your Jumpbox, along with a bootstrap.properties
86
+ file that has been configured to match the settings used here.
87
+ EOS
88
+
89
+ if agent.requires_human_oversight?
90
+ STDERR.puts <<-EOS
91
+ When you are ready to proceed, the following command will do the rest:
92
+
93
+ ssh -l ubuntu -i #{agent.ssh_key.private_file} #{agent.jumpbox_ip} bootstrap
94
+
95
+ EOS
96
+ else
97
+ STDERR.puts Pastel.new.bold.bright_yellow.on_blue 'Human oversight has been disabled.'
98
+
99
+ STDERR.puts Pastel.new.blue <<-EOS
100
+
101
+ Now would be a good time for tea and swordplay (https://xkcd.com/303/). What
102
+ happens next is non-interactive and will take approximately 30 minutes.
103
+ EOS
104
+ agent.launch
105
+ end
106
+
107
+ else
108
+ STDERR.puts Pastel.new.red <<-EOS
109
+ Your Jumpbox (#{agent.jumpbox}) is not in a "running" state. It is normal for
110
+ the first boot to take several minutes as the instance configures itself.
111
+
112
+ If you wait a few minutes and try again, it should work.
113
+ EOS
114
+ abort
115
+ end
@@ -0,0 +1,24 @@
1
+ Gem::Specification.new do |gem|
2
+ gem.name = 'cloudstrap'
3
+ gem.version = `git describe --tags --abbrev=0`.chomp + '.pre'
4
+ gem.licenses = 'MIT'
5
+ gem.authors = ['Chris Olstrom']
6
+ gem.email = 'chris@olstrom.com'
7
+ gem.homepage = 'https://github.com/colstrom/cloudstrap'
8
+ gem.summary = 'Strapping Boots to Clouds'
9
+
10
+ gem.files = `git ls-files`.split("\n")
11
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
12
+ gem.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
13
+ gem.require_paths = ['lib']
14
+
15
+ gem.add_runtime_dependency 'aws-sdk', '~> 2.5', '>= 2.5.0'
16
+ gem.add_runtime_dependency 'contracts', '~> 0.14', '>= 0.14.0'
17
+ gem.add_runtime_dependency 'retries', '~> 0.0.5', '>= 0.0.5'
18
+ gem.add_runtime_dependency 'moneta', '~> 0.8', '>= 0.8.0'
19
+ gem.add_runtime_dependency 'sshkey', '~> 1.8', '>= 1.8.0'
20
+ gem.add_runtime_dependency 'sshkit', '~> 1.11', '>= 1.11.0'
21
+ gem.add_runtime_dependency 'java-properties', '~> 0.1', '>= 0.1.1'
22
+ gem.add_runtime_dependency 'pastel', '~> 0.6', '>= 0.6.0'
23
+ gem.add_runtime_dependency 'faraday', '~> 0.9', '>= 0.9.0'
24
+ end
data/lib/cloudstrap.rb ADDED
@@ -0,0 +1,3 @@
1
+ require_relative 'cloudstrap/amazon'
2
+ require_relative 'cloudstrap/bootstrap_agent'
3
+ require_relative 'cloudstrap/config'
@@ -0,0 +1,2 @@
1
+ require_relative 'amazon/ec2'
2
+ require_relative 'amazon/iam'
@@ -0,0 +1,377 @@
1
+ require 'aws-sdk'
2
+ require 'contracts'
3
+ require_relative 'service'
4
+
5
+ module StackatoLKG
6
+ module Amazon
7
+ class EC2 < Service
8
+ Contract None => ArrayOf[::Aws::EC2::Types::Vpc]
9
+ def vpcs
10
+ @vpcs ||= vpcs!
11
+ end
12
+
13
+ Contract None => ArrayOf[::Aws::EC2::Types::Vpc]
14
+ def vpcs!
15
+ @vpcs = call_api(:describe_vpcs).vpcs
16
+ end
17
+
18
+ Contract None => ArrayOf[::Aws::EC2::Types::Subnet]
19
+ def subnets
20
+ @subnets ||= subnets!
21
+ end
22
+
23
+ Contract None => ArrayOf[::Aws::EC2::Types::Subnet]
24
+ def subnets!
25
+ @subnets = call_api(:describe_subnets).subnets
26
+ end
27
+
28
+ Contract None => ArrayOf[::Aws::EC2::Types::RouteTable]
29
+ def route_tables
30
+ @route_tables ||= route_tables!
31
+ end
32
+
33
+ Contract None => ArrayOf[::Aws::EC2::Types::RouteTable]
34
+ def route_tables!
35
+ @route_tables = call_api(:describe_route_tables).route_tables
36
+ end
37
+
38
+ Contract String => ::Aws::EC2::Types::RouteTable
39
+ def create_route_table(vpc_id)
40
+ call_api(:create_route_table, vpc_id: vpc_id).route_table
41
+ .tap { route_tables! }
42
+ end
43
+
44
+ Contract None => ArrayOf[::Aws::EC2::Types::NatGateway]
45
+ def nat_gateways
46
+ @nat_gateways ||= nat_gateways!
47
+ end
48
+
49
+ Contract None => ArrayOf[::Aws::EC2::Types::NatGateway]
50
+ def nat_gateways!
51
+ @nat_gateways ||= call_api(:describe_nat_gateways).nat_gateways
52
+ end
53
+
54
+ Contract String, String => ::Aws::EC2::Types::NatGateway
55
+ def create_nat_gateway(subnet_id, allocation_id)
56
+ call_api(:create_nat_gateway, subnet_id: subnet_id, allocation_id: allocation_id).nat_gateway
57
+ .tap { nat_gateways! }
58
+ end
59
+
60
+ Contract None => ArrayOf[::Aws::EC2::Types::InternetGateway]
61
+ def internet_gateways
62
+ @internet_gateways ||= internet_gateways!
63
+ end
64
+
65
+ Contract None => ArrayOf[::Aws::EC2::Types::InternetGateway]
66
+ def internet_gateways!
67
+ @internet_gateways = call_api(:describe_internet_gateways).internet_gateways
68
+ end
69
+
70
+ Contract String => Bool
71
+ def internet_gateway_exist?(internet_gateway_id)
72
+ ! internet_gateways.select { |igw| igw.internet_gateway_id == internet_gateway_id }.empty?
73
+ end
74
+
75
+ Contract String, String => Bool
76
+ def internet_gateway_attached?(internet_gateway_id, vpc_id)
77
+ (internet_gateway_exist?(internet_gateway_id) ? internet_gateways : internet_gateways!)
78
+ .flat_map { |internet_gateway| internet_gateway.attachments }
79
+ .any? { |attachment| attachment.vpc_id == vpc_id }
80
+ end
81
+
82
+ Contract String, String => Bool
83
+ def attach_internet_gateway(internet_gateway_id, vpc_id)
84
+ call_api(:attach_internet_gateway,
85
+ internet_gateway_id: internet_gateway_id,
86
+ vpc_id: vpc_id).successful?
87
+ rescue ::Aws::EC2::Errors::ResourceAlreadyAssociated
88
+ internet_gateway_attached? internet_gateway_id, vpc_id
89
+ end
90
+
91
+ Contract String, String, String => Bool
92
+ def create_route(destination_cidr_block, gateway_id, route_table_id)
93
+ call_api(:create_route,
94
+ route_table_id: route_table_id,
95
+ destination_cidr_block: destination_cidr_block,
96
+ gateway_id: gateway_id).successful?
97
+ .tap { route_tables! }
98
+ rescue ::Aws::EC2::Errors::RouteAlreadyExists
99
+ route_tables!
100
+ .select { |route_table| route_table.route_table_id = route_table_id }
101
+ .flat_map { |route_table| route_table.routes }
102
+ .select { |route| route.destination_cidr_block == destination_cidr_block }
103
+ .any? { |route| route.gateway_id == gateway_id || route.nat_gateway_id == gateway_id }
104
+ end
105
+
106
+ Contract String, String => String
107
+ def associate_route_table(route_table_id, subnet_id)
108
+ call_api(:associate_route_table,
109
+ route_table_id: route_table_id,
110
+ subnet_id: subnet_id).association_id
111
+ .tap { route_tables! }
112
+ end
113
+
114
+ Contract None => ::Aws::EC2::Types::InternetGateway
115
+ def create_internet_gateway
116
+ call_api(:create_internet_gateway).internet_gateway
117
+ .tap { internet_gateways! }
118
+ end
119
+
120
+ Contract None => ArrayOf[::Aws::EC2::Types::Instance]
121
+ def instances
122
+ @instances ||= instances!
123
+ end
124
+
125
+ Contract None => ArrayOf[::Aws::EC2::Types::Instance]
126
+ def instances!
127
+ @instances = call_api(:describe_instances)
128
+ .reservations
129
+ .flat_map(&:instances)
130
+ end
131
+
132
+ Contract None => ArrayOf[::Aws::EC2::Types::Address]
133
+ def addresses
134
+ @addresses ||= addresses!
135
+ end
136
+
137
+ Contract None => ArrayOf[::Aws::EC2::Types::Address]
138
+ def addresses!
139
+ @addresses = call_api(:describe_addresses).addresses
140
+ end
141
+
142
+ Contract None => ArrayOf[::Aws::EC2::Types::Address]
143
+ def unassociated_addresses
144
+ addresses
145
+ .select { |address| address.domain == 'vpc' }
146
+ .select { |address| address.association_id == nil }
147
+ end
148
+
149
+ Contract None => Maybe[String]
150
+ def unassociated_address
151
+ unassociated_addresses.map(&:allocation_id).sample
152
+ end
153
+
154
+ Contract None => String
155
+ def create_address
156
+ call_api(:allocate_address).allocation_id
157
+ .tap { addresses! }
158
+ end
159
+
160
+ Contract None => ArrayOf[::Aws::EC2::Types::SecurityGroup]
161
+ def security_groups
162
+ @security_groups ||= security_groups!
163
+ end
164
+
165
+ Contract None => ArrayOf[::Aws::EC2::Types::SecurityGroup]
166
+ def security_groups!
167
+ @security_groups = call_api(:describe_security_groups).security_groups
168
+ end
169
+
170
+ Contract None => ArrayOf[::Aws::EC2::Types::TagDescription]
171
+ def tags
172
+ @tags ||= tags!
173
+ end
174
+
175
+ Contract None => ArrayOf[::Aws::EC2::Types::TagDescription]
176
+ def tags!
177
+ @tags = call_api(:describe_tags).tags
178
+ end
179
+
180
+ Contract KeywordArgs[
181
+ image_id: String,
182
+ instance_type: String,
183
+ key_name: Optional[String],
184
+ client_token: Optional[String],
185
+ network_interfaces: Optional[ArrayOf[Hash]]
186
+ ] => ::Aws::EC2::Types::Instance
187
+ def create_instance(**properties)
188
+ call_api(:run_instances, properties.merge(min_count: 1, max_count: 2)).instances.first
189
+ .tap { instances! }
190
+ end
191
+
192
+ Contract None => ArrayOf[::Aws::EC2::Types::Image]
193
+ def images
194
+ @images ||= images!
195
+ end
196
+
197
+ Contract None => ArrayOf[::Aws::EC2::Types::Image]
198
+ def images!
199
+ @images ||= call_api(:describe_images,
200
+ owners: [config.ami_owner],
201
+ filters: [
202
+ { name: 'virtualization-type', values: ['hvm'] },
203
+ { name: 'architecture', values: ['x86_64'] },
204
+ { name: 'root-device-type', values: ['ebs'] },
205
+ { name: 'block-device-mapping.volume-type', values: ['gp2'] }
206
+ ])
207
+ .images
208
+ .reject { |image| image.sriov_net_support.nil? }
209
+ end
210
+
211
+ Contract RespondTo[:to_s] => ArrayOf[::Aws::EC2::Types::Image]
212
+ def ubuntu_images(release)
213
+ images
214
+ .select { |image| image.name.start_with? 'ubuntu/images/' }
215
+ .select { |image| image.name.include? release.to_s }
216
+ end
217
+
218
+ Contract RespondTo[:to_s] => ::Aws::EC2::Types::Image
219
+ def latest_ubuntu(release)
220
+ ubuntu_images(release)
221
+ .sort_by { |image| image.creation_date }
222
+ .last
223
+ end
224
+
225
+ Contract None => ArrayOf[::Aws::EC2::Types::KeyPairInfo]
226
+ def key_pairs
227
+ @key_pairs ||= key_pairs!
228
+ end
229
+
230
+ Contract None => ArrayOf[::Aws::EC2::Types::KeyPairInfo]
231
+ def key_pairs!
232
+ @key_pairs = call_api(:describe_key_pairs).key_pairs
233
+ end
234
+
235
+ Contract String => Maybe[String]
236
+ def key_fingerprint(key_name)
237
+ key_pairs
238
+ .select { |ssh| ssh.key_name == key_name }
239
+ .map { |ssh| ssh.key_fingerprint }
240
+ .first
241
+ end
242
+
243
+ Contract String, String => String
244
+ def import_key_pair(key_name, public_key_material)
245
+ call_api(:import_key_pair, key_name: key_name, public_key_material: public_key_material).key_fingerprint
246
+ .tap { key_pairs! }
247
+ rescue ::Aws::EC2::Errors::InvalidKeyPairDuplicate
248
+ key_fingerprint(key_name)
249
+ end
250
+
251
+ Contract None => ::Aws::EC2::Types::Vpc
252
+ def create_vpc
253
+ call_api(:create_vpc, cidr_block: config.vpc_cidr_block).vpc
254
+ .tap { vpcs! }
255
+ end
256
+
257
+ Contract KeywordArgs[
258
+ cidr_block: Optional[String],
259
+ vpc_id: Optional[String],
260
+ subnet_id: Optional[String]
261
+ ] => ArrayOf[::Aws::EC2::Types::Subnet]
262
+ def subnets(cidr_block: nil, vpc_id: nil, subnet_id: nil)
263
+ subnets
264
+ .select { |subnet| subnet_id.nil? || subnet.subnet_id == subnet_id }
265
+ .select { |subnet| vpc_id.nil? || subnet.vpc_id == vpc_id }
266
+ .select { |subnet| cidr_block.nil? || subnet.cidr_block == cidr_block }
267
+ end
268
+
269
+ Contract Args[Any] => Any
270
+ def subnets!(**properties)
271
+ subnets!
272
+ subnets(properties)
273
+ end
274
+
275
+ Contract Args[Any] => Maybe[::Aws::EC2::Types::Subnet]
276
+ def subnet(**properties)
277
+ subnets(properties).first
278
+ end
279
+
280
+ Contract Args[Any] => Any
281
+ def subnet!(**properties)
282
+ subnets!
283
+ subnet(properties)
284
+ end
285
+
286
+ Contract KeywordArgs[
287
+ cidr_block: String,
288
+ vpc_id: String
289
+ ] => ::Aws::EC2::Types::Subnet
290
+ def create_subnet(**properties)
291
+ call_api(:create_subnet, properties).subnet
292
+ .tap { subnets! }
293
+ rescue ::Aws::EC2::Errors::InvalidSubnetConflict
294
+ subnet(properties) || subnet!(properties)
295
+ end
296
+
297
+ Contract String => Bool
298
+ def map_public_ip_on_launch?(subnet_id)
299
+ subnets(subnet_id: subnet_id)
300
+ .map { |subnet| subnet.map_public_ip_on_launch }
301
+ .first == true
302
+ end
303
+
304
+ Contract String, Bool => Bool
305
+ def map_public_ip_on_launch(subnet_id, value)
306
+ call_api(:modify_subnet_attribute,
307
+ subnet_id: subnet_id,
308
+ map_public_ip_on_launch: { value: value }
309
+ ).successful?
310
+ .tap { subnets! }
311
+ end
312
+
313
+ Contract RespondTo[:to_s], RespondTo[:to_i], RespondTo[:to_s], String => Bool
314
+ def authorize_security_group_ingress(ip_protocol, port, cidr_ip, group_id)
315
+ call_api(:authorize_security_group_ingress,
316
+ group_id: group_id,
317
+ ip_protocol: ip_protocol.to_s,
318
+ from_port: port.to_i,
319
+ to_port: port.to_i,
320
+ cidr_ip: cidr_ip.to_s
321
+ ).successful?
322
+ .tap { security_groups! }
323
+ rescue ::Aws::EC2::Errors::InvalidPermissionDuplicate
324
+ true
325
+ end
326
+
327
+ Contract RespondTo[:to_s], String => String
328
+ def create_security_group(group_name, vpc_id)
329
+ call_api(:create_security_group,
330
+ group_name: group_name.to_s,
331
+ description: group_name.to_s,
332
+ vpc_id: vpc_id).group_id
333
+ .tap { security_groups! }
334
+ rescue ::Aws::EC2::Errors::InvalidGroupDuplicate
335
+ security_group(group_name.to_s, vpc_id)
336
+ end
337
+
338
+ Contract RespondTo[:to_s], String => String
339
+ def security_group(group_name, vpc_id)
340
+ security_groups
341
+ .select { |sg| sg.vpc_id == vpc_id }
342
+ .select { |sg| sg.group_name == group_name }
343
+ .first
344
+ .group_id
345
+ end
346
+
347
+ Contract ArrayOf[String], ArrayOf[{ key: String, value: String}] => Bool
348
+ def create_tags(resources, tags)
349
+ call_api(:create_tags, resources: resources, tags: tags).successful?
350
+ .tap { tags! }
351
+ end
352
+
353
+ Contract String, Args[String] => Bool
354
+ def assign_name(name, *resources)
355
+ create_tags(resources, [{ key: 'Name', value: name } ])
356
+ end
357
+
358
+ Contract KeywordArgs[
359
+ type: Optional[String],
360
+ key: Optional[String],
361
+ value: Optional[String]
362
+ ] => ArrayOf[::Aws::EC2::Types::TagDescription]
363
+ def tagged(type: nil, key: nil, value: nil)
364
+ tags
365
+ .select { |tag| type.nil? || tag.resource_type == type }
366
+ .select { |tag| tag.key == (key || 'Name') }
367
+ .select { |tag| value.nil? || tag.value == value }
368
+ end
369
+
370
+ private
371
+
372
+ def client
373
+ Aws::EC2::Client
374
+ end
375
+ end
376
+ end
377
+ end