Module: MovableInk::AWS::EC2

Included in:
MovableInk::AWS
Defined in:
lib/movable_ink/aws/ec2.rb

Instance Method Summary collapse

Instance Method Details

#all_instances(region: my_region, no_filter: false) ⇒ Object

[View source]

41
42
43
44
# File 'lib/movable_ink/aws/ec2.rb', line 41

def all_instances(region: my_region, no_filter: false)
  @all_instances ||= {}
  @all_instances[region] ||= load_all_instances(region, no_filter: no_filter)
end

#assign_ip_address(role:) ⇒ Object

[View source]

236
237
238
239
240
241
242
243
# File 'lib/movable_ink/aws/ec2.rb', line 236

def assign_ip_address(role:)
  run_with_backoff do
    ec2.associate_address({
      instance_id: instance_id,
      allocation_id: available_elastic_ips(role: role).sample.allocation_id
    })
  end
end

#available_elastic_ips(role:) ⇒ Object

[View source]

232
233
234
# File 'lib/movable_ink/aws/ec2.rb', line 232

def available_elastic_ips(role:)
  unassigned_elastic_ips.select { |address| address.tags.detect { |t| t.key == 'mi:roles' && t.value == role } }
end

#default_filterObject

[View source]

46
47
48
49
50
51
52
53
54
55
# File 'lib/movable_ink/aws/ec2.rb', line 46

def default_filter
  [{
    name: 'instance-state-name',
    values: ['running']
  },
  {
    name: 'tag:mi:env',
    values: [mi_env]
  }]
end

#ec2(region: my_region) ⇒ Object

[View source]

7
8
9
10
# File 'lib/movable_ink/aws/ec2.rb', line 7

def ec2(region: my_region)
  @ec2_client ||= {}
  @ec2_client[region] ||= Aws::EC2::Client.new(region: region)
end

#elastic_ipsObject

[View source]

222
223
224
225
226
# File 'lib/movable_ink/aws/ec2.rb', line 222

def elastic_ips
  @all_elastic_ips ||= run_with_backoff do
    ec2.describe_addresses.addresses
  end
end

#instance_ip_addresses_by_role(role:, exclude_roles: [], region: my_region, availability_zone: nil, exact_match: false, use_cache: true) ⇒ Object

[View source]

203
204
205
# File 'lib/movable_ink/aws/ec2.rb', line 203

def instance_ip_addresses_by_role(role:, exclude_roles: [], region: my_region, availability_zone: nil, exact_match: false, use_cache: true)
  private_ip_addresses(instances(role: role, exclude_roles: exclude_roles, region: region, availability_zone: availability_zone, exact_match: exact_match, use_cache: use_cache))
end

#instance_ip_addresses_by_role_ordered(role:, exclude_roles: [], region: my_region, exact_match: false) ⇒ Object

[View source]

207
208
209
210
211
212
# File 'lib/movable_ink/aws/ec2.rb', line 207

def instance_ip_addresses_by_role_ordered(role:, exclude_roles: [], region: my_region, exact_match: false)
  instances = instances(role: role, exclude_roles: exclude_roles, region: region, exact_match: exact_match)
  instances_in_my_az = instances.select { |instance| instance.placement.availability_zone == availability_zone }
  ordered_instances = instances_in_my_az.shuffle + (instances - instances_in_my_az).shuffle
  private_ip_addresses(ordered_instances)
end

#instance_tagsObject

[View source]

67
68
69
70
71
72
73
# File 'lib/movable_ink/aws/ec2.rb', line 67

def instance_tags
  @instance_tags ||= run_with_backoff(quiet: true) do
    ec2.describe_tags({
      filters: [{ name: 'resource-id', values: [instance_id] } ]
    }).tags
  end
end

#instances(role:, exclude_roles: [], region: my_region, availability_zone: nil, exact_match: false, use_cache: true, discovery_type: 'ec2') ⇒ Object

[View source]

181
182
183
184
185
186
187
188
189
# File 'lib/movable_ink/aws/ec2.rb', line 181

def instances(role:, exclude_roles: [], region: my_region, availability_zone: nil, exact_match: false, use_cache: true, discovery_type: 'ec2')
  if discovery_type == 'ec2'
    instances_with_ec2_discovery(role: role, exclude_roles: exclude_roles, region: region, availability_zone: availability_zone, exact_match: exact_match, use_cache: use_cache)
  elsif discovery_type == 'consul'
    instances_with_consul_discovery(role: role, region: region, availability_zone: availability_zone)
  else
    raise MovableInk::AWS::Errors::InvalidDiscoveryTypeError
  end
end

#instances_with_consul_discovery(role:, region: my_region, availability_zone: nil) ⇒ Object

[View source]

110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/movable_ink/aws/ec2.rb', line 110

def instances_with_consul_discovery(role:, region: my_region, availability_zone: nil)
  if role == nil || role == ''
    raise MovableInk::AWS::Errors::RoleNameRequiredError
  end

  consul_service_options = {
    dc: datacenter(region: region),
    stale: true,
    cached: true,
    passing: true,
  }
  consul_service_options[:node_meta] = "availability_zone:#{availability_zone}" unless availability_zone.nil?

  # We replace underscores with dashes in the role name in order to comply with
  # consul service naming conventions while still retaining the role name we use
  # within MI configuration
  Diplomat::Health.service(role.gsub('_', '-'), consul_service_options).map { |endpoint|
    if endpoint.Node.dig('Meta', 'external-source') == 'kubernetes'
      map_k8s_consul_endpoint(endpoint, role)
    else
      map_ec2_consul_endpoint(endpoint)
    end
  }
end

#instances_with_ec2_discovery(role:, exclude_roles: [], region: my_region, availability_zone: nil, exact_match: false, use_cache: true) ⇒ Object

[View source]

79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/movable_ink/aws/ec2.rb', line 79

def instances_with_ec2_discovery(role:, exclude_roles: [], region: my_region, availability_zone: nil, exact_match: false, use_cache: true)
  roles = role.split(/\s*,\s*/)
  if use_cache == false
    filter = default_filter.push({
      name: 'tag:mi:roles',
      values: roles
    })
    instances = load_all_instances(region, filter: filter)
  else
    instances = all_instances(region: region).select { |instance|
      instance.tags.select{ |tag| tag.key == 'mi:roles' }.detect { |tag|
        tag_roles = tag.value.split(/\s*,\s*/)
        if exact_match
          tag_roles == roles
        else
          exclude_roles.push('decommissioned')
          tag_roles.any? { |tag_role| roles.include?(tag_role) } && !tag_roles.any? { |role| exclude_roles.include?(role) }
        end
      }
    }
  end

  if availability_zone
    instances.select { |instance|
      instance.placement.availability_zone == availability_zone
    }
  else
    instances
  end
end

#load_all_instances(region, no_filter: false, filter: nil) ⇒ Object

[View source]

57
58
59
60
61
62
63
64
65
# File 'lib/movable_ink/aws/ec2.rb', line 57

def load_all_instances(region, no_filter: false, filter: nil)
  filters = no_filter ? nil : (filter || default_filter)

  run_with_backoff do
    ec2(region: region).describe_instances(filters: filters).flat_map do |resp|
      resp.reservations.flat_map(&:instances)
    end
  end
end

#load_mi_envObject

[View source]

25
26
27
28
29
30
31
# File 'lib/movable_ink/aws/ec2.rb', line 25

def load_mi_env
  instance_tags
    .detect { |tag| tag.key == 'mi:env' }
    .value
rescue NoMethodError
  raise MovableInk::AWS::Errors::NoEnvironmentTagError
end

#map_ec2_consul_endpoint(endpoint) ⇒ Object

[View source]

157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/movable_ink/aws/ec2.rb', line 157

def map_ec2_consul_endpoint(endpoint)
  OpenStruct.new ({
    private_ip_address: endpoint.Node.dig('Address'),
    instance_id: endpoint.Node.dig('Meta', 'instance_id'),
    tags: [
      {
        key: 'Name',
        value: endpoint.Node.dig('Node')
      },
      {
        key: 'mi:roles',
        value: endpoint.Node.dig('Meta', 'mi_roles')
      },
      {
        key: 'mi:monitoring_roles',
        value: endpoint.Node.dig('Meta', 'mi_monitoring_roles')
      }
    ],
    placement: {
      availability_zone: endpoint.Node.dig('Meta', 'availability_zone')
    }
  })
end

#map_k8s_consul_endpoint(endpoint, role) ⇒ Object

[View source]

135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/movable_ink/aws/ec2.rb', line 135

def map_k8s_consul_endpoint(endpoint, role)
  OpenStruct.new ({
    private_ip_address: endpoint.Service.dig('Address'),
    instance_id: nil,
    tags: [
      {
        key: 'Name',
        value: endpoint.Service.dig('ID')
      },
      {
        key: 'mi:roles',
        value: role
      },
      {
        key: 'mi:monitoring_roles',
        value: role
      }
    ],
    placement: { availability_zone: nil }
  })
end

#meObject

[View source]

75
76
77
# File 'lib/movable_ink/aws/ec2.rb', line 75

def me
  @me ||= all_instances.select{|instance| instance.instance_id == instance_id}.first rescue nil
end

#mi_envObject

[View source]

16
17
18
19
20
21
22
23
# File 'lib/movable_ink/aws/ec2.rb', line 16

def mi_env
  @mi_env ||= if File.exist?(mi_env_cache_file_path)
    environments = JSON.parse(File.read(mi_env_cache_file_path))
    environments[my_region] || load_mi_env
  else
    load_mi_env
  end
end

#mi_env_cache_file_pathObject

[View source]

12
13
14
# File 'lib/movable_ink/aws/ec2.rb', line 12

def mi_env_cache_file_path
  '/etc/movableink/environments.json'
end

#private_ip_addresses(instances) ⇒ Object

[View source]

199
200
201
# File 'lib/movable_ink/aws/ec2.rb', line 199

def private_ip_addresses(instances)
  instances.map(&:private_ip_address)
end

#redis_by_role(role, port) ⇒ Object

[View source]

214
215
216
217
218
219
220
# File 'lib/movable_ink/aws/ec2.rb', line 214

def redis_by_role(role, port)
  instance_ip_addresses_by_role(role: role)
    .shuffle
    .inject([]) { |redii, instance|
      redii.push({"host" => instance, "port" => port})
    }
end

#statsd_hostObject

[View source]

195
196
197
# File 'lib/movable_ink/aws/ec2.rb', line 195

def statsd_host
  instance_ip_addresses_by_role(role: 'statsd', availability_zone: availability_zone, use_cache: false).sample
end

#thopterObject

[View source]

191
192
193
# File 'lib/movable_ink/aws/ec2.rb', line 191

def thopter
  private_ip_addresses(thopter_instance).first
end

#thopter_instanceObject

[View source]

33
34
35
36
37
38
39
# File 'lib/movable_ink/aws/ec2.rb', line 33

def thopter_instance
  @thopter_instance ||= all_instances(region: 'us-east-1').select do |instance|
    instance.tags.select{ |tag| tag.key == 'mi:roles' }.detect do |tag|
      tag.value.include?('thopter')
    end
  end
end

#unassigned_elastic_ipsObject

[View source]

228
229
230
# File 'lib/movable_ink/aws/ec2.rb', line 228

def unassigned_elastic_ips
  @unassigned_elastic_ips ||= elastic_ips.select { |address| address.association_id.nil? }
end