Class: Ssm

Inherits:
Object
  • Object
show all
Defined in:
lib/sty/ssm.rb

Constant Summary collapse

MINIMAL_AGENT_VERSION =
'2.3.612.0'
DEFAULT_REGION =
'ap-southeast-2'
SSM_PLUGIN =
"/usr/local/bin/session-manager-plugin"

Instance Method Summary collapse

Constructor Details

#initializeSsm

Returns a new instance of Ssm.



11
12
13
14
15
# File 'lib/sty/ssm.rb', line 11

def initialize
  require 'aws-sdk-ec2'
  require 'aws-sdk-ssm'
  Aws.config.update(:http_proxy => ENV['https_proxy'])
end

Instance Method Details

#all_ec2_instancesObject



29
30
31
32
33
# File 'lib/sty/ssm.rb', line 29

def all_ec2_instances
  ec2.describe_instances.map do |page|
    page.reservations.map { |r| r.instances }
  end.flatten
end

#all_ssm_detailsObject



35
36
37
38
39
# File 'lib/sty/ssm.rb', line 35

def all_ssm_details
  ssm.describe_instance_information.map do |page|
    page.instance_information_list
  end.flatten
end

#connect(search) ⇒ Object



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/sty/ssm.rb', line 138

def connect(search)

  found = find(all_ec2_instances, search)
  if found.empty?
    puts "No instances found for search terms: #{search.join(', ')}"
    exit 1
  end
  target = refine(found) {|found| print_refine_instances(found, 'target')}

  begin
    resp = ssm.start_session(target: instance_id(target))
  rescue Exception => e
    puts red("ERROR! Unable to start session")
    puts white(e.message)
    exit 1
  end

  mappings = {session_id: 'SessionId', token_value: 'TokenValue', stream_url: 'StreamUrl'}
  reqest_str = resp.to_h.map {|k, v| [mappings[k], v] }.to_h.to_json
  exec "#{session_manager} \'#{reqest_str}\' ap-southeast-2 StartSession"
end

#ec2Object



21
22
23
# File 'lib/sty/ssm.rb', line 21

def ec2
  @ec2 = @ec2 || Aws::EC2::Client.new(region: region)
end

#extract_fields(inst) ⇒ Object



60
61
62
# File 'lib/sty/ssm.rb', line 60

def extract_fields(inst)
  [instance_name(inst), instance_id(inst)] + instance_ips(inst)
end

#find(instances, names) ⇒ Object



98
99
100
101
102
103
104
# File 'lib/sty/ssm.rb', line 98

def find(instances, names)
  re = prepare_regex(names)
  instances.select do |i|
    i.state.name == 'running' &&
        extract_fields(i).any? { |f| re.all? { |r| f =~ r } }
  end.sort{|a,b| instance_name(a) <=> instance_name(b)}
end

#instance_id(instance) ⇒ Object



47
48
49
# File 'lib/sty/ssm.rb', line 47

def instance_id(instance)
  instance.instance_id
end

#instance_ips(instance) ⇒ Object



56
57
58
# File 'lib/sty/ssm.rb', line 56

def instance_ips(instance)
  instance.network_interfaces.map { |n| n.private_ip_address }
end

#instance_name(instance) ⇒ Object



51
52
53
54
# File 'lib/sty/ssm.rb', line 51

def instance_name(instance)
  name_tag = instance.tags.select { |t| t.key == 'Name' }.first
  name_tag ? name_tag.value : "None"
end

#ping(instance) ⇒ Object



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/sty/ssm.rb', line 77

def ping(instance)
  #binding.pry
  status = ssm_details[instance_id(instance)]&.ping_status || 'Unavailable'
  version_str = ssm_details[instance_id(instance)]&.agent_version
  version = Gem::Version.new(version_str || '0')
  reference_version = Gem::Version.new(MINIMAL_AGENT_VERSION)
  old_version = version < reference_version

  result = "[#{status}#{old_version ? ', old agent' : ''}]"

  case
    when status == 'Online' && !old_version
      green(result)
    when status == 'Online' && old_version
      yellow(result)
    else
      red(result)
  end

end

#platform(instance) ⇒ Object



73
74
75
# File 'lib/sty/ssm.rb', line 73

def platform(instance)
  win?(instance) ? magenta("[Win] ") : ""
end

#prepare_regex(names) ⇒ Object



64
65
66
67
# File 'lib/sty/ssm.rb', line 64

def prepare_regex(names)
  names = ['.'] if names.empty?
  names.map { |n| Regexp.new(Regexp.quote(n), Regexp::IGNORECASE) }
end


106
107
108
109
110
111
# File 'lib/sty/ssm.rb', line 106

def print_refine_instances(instances, type)
  puts "Please refine #{type} instance:"
  instances.each_with_index do |inst, idx|
    puts "#{idx}: #{instance_id(inst)} #{ping(inst)} [#{instance_name(inst)}] #{platform(inst)}(#{instance_ips(inst).join(', ')}) "
  end
end

#refine(found) ⇒ Object



113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/sty/ssm.rb', line 113

def refine(found)
  if found.size > 1
    refine = nil
    loop do
      yield(found)
      refine = Integer(STDIN.gets.chomp) rescue false
      break if refine && refine < found.size
    end
    target = found[refine]
  else
    target = found.first
  end
  target
end

#regionObject



17
18
19
# File 'lib/sty/ssm.rb', line 17

def region
  ENV['AWS_REGION'] || DEFAULT_REGION
end

#session_managerObject



129
130
131
132
133
134
135
136
# File 'lib/sty/ssm.rb', line 129

def session_manager
  unless Pathname.new(SSM_PLUGIN).exist?
    puts red("SSM plugin is not found: #{SSM_PLUGIN}.")
    puts white("You must have SSM plugin to continue.\nSee: https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html")
    exit 1
  end
  SSM_PLUGIN
end

#ssmObject



25
26
27
# File 'lib/sty/ssm.rb', line 25

def ssm
  @ssm = @ssm || Aws::SSM::Client.new(region: region)
end

#ssm_detailsObject



41
42
43
44
45
# File 'lib/sty/ssm.rb', line 41

def ssm_details
  @ssm_details = @ssm_details || all_ssm_details.map do |i|
    [i.instance_id, i]
  end.to_h
end

#win?(instance) ⇒ Boolean

Returns:

  • (Boolean)


69
70
71
# File 'lib/sty/ssm.rb', line 69

def win?(instance)
  instance.platform =~ /windows/i
end