File: inspector.rb

package info (click to toggle)
ruby-mongo 2.21.3-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 14,764 kB
  • sloc: ruby: 108,806; makefile: 5; sh: 2
file content (227 lines) | stat: -rw-r--r-- 7,126 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# frozen_string_literal: true
# rubocop:todo all

module AwsUtils
  class Inspector < Base

    def list_key_pairs
      ec2_client.describe_key_pairs.key_pairs.each do |key_pair|
        puts key_pair.key_name
      end
    end

    def assume_role_arn
      assume_role = detect_object(iam_client.list_roles, :roles, :role_name, AWS_AUTH_ASSUME_ROLE_NAME)
      if assume_role.nil?
        raise 'No user found, please run `aws setup-resources`'
      end
      assume_role.arn
    end

    def ecs_status(cluster_name: AWS_AUTH_ECS_CLUSTER_NAME,
      service_name: AWS_AUTH_ECS_SERVICE_NAME,
      get_public_ip: true, get_logs: true
    )
      service = ecs_client.describe_services(
        cluster: cluster_name,
        services: [service_name],
      ).services.first
      if service.nil?
        raise "No service #{service_name} in cluster #{cluster_name} - provision first"
      end

      # When Ruby driver tooling is used, task definition generation is
      # going up on each service launch, and service name is the fixed.
      # When testing in Evergreen, generation is fixed because we do not
      # change the task definition, but service name is different for
      # each test run.
      if service.task_definition =~ /:(\d+)$/
        generation = $1
        puts "Current task definition generation: #{generation} for service: #{service_name}"
      else
        raise 'Could not determine task definition generation'
      end

      colors = {
        'running' => :green,
        'pending' => :yellow,
        'stopped' => :red,
      }
      # Pending status in the API includes tasks in provisioning status as
      # show in the AWS console.
      #
      # The API returns the tasks unordered, in particular the latest task
      # may be in the middle of the list following relatively ancient tasks.
      # Collect all tasks in a single list and order them by generation.
      # We expect to have a single task per generation.
      tasks = []
      %w(running pending stopped).each do |status|
        resp = ecs_client.list_tasks(
          cluster: cluster_name,
          service_name: service_name,
          desired_status: status,
        )
        task_arns = resp.map(&:task_arns).flatten
        if task_arns.empty?
          next
        end
        ecs_client.describe_tasks(
          cluster: cluster_name,
          tasks: task_arns,
        ).each do |tbatch|
          unless tbatch.failures.empty?
            # The task list endpoint does not raise an exception if it can't
            # find the tasks, but reports "failures".
            puts "Failures for #{task_arns.join(', ')}:"
            tbatch.failures.each do |failure|
              puts "#{failure.arn}: #{failure.reason}"
              next
            end
          end
          tbatch.tasks.each do |task|
            tasks << task
          end
        end
      end

      tasks.each do |task|
        class << task
          def generation
            @generation ||= if task_definition_arn =~ /:(\d+)$/
              $1.to_i
            else
              raise 'Could not determine generation'
            end
          end

          def task_uuid
            @uuid ||= task_arn.split('/').last
          end
        end
      end

      tasks = tasks.sort_by do |task|
        -task.generation
      end.first(3)

      running_task = nil
      running_private_ip = nil
      running_public_ip = nil

      if tasks.empty?
        puts 'No tasks in the cluster'
      end
      tasks.each do |task|
        status = task.last_status.downcase

        status_ext = case status
        when 'stopped'
          ": #{task.stopped_reason}"
        else
          ''
        end
        decorated_status = Paint[status.upcase, colors[status]]
        puts "Task for generation #{task.generation}: #{decorated_status}#{status_ext} (uuid: #{task.task_uuid})"
        if status == 'running'
          puts "Task ARN: #{task.task_arn}"
          running_task ||= task
        end
        task.containers.each do |container|
          if container.reason
            puts container.reason
          end
        end

        if status == 'running'
          attachment = detect_object([task], :attachments, :type, 'ElasticNetworkInterface')
          ip = detect_object([attachment], :details, :name, 'privateIPv4Address')
          if ip
            private_ip = ip.value
            running_private_ip ||= private_ip
          end
          msg = "Private IP: #{private_ip}"
          if get_public_ip
            niid = detect_object([attachment], :details, :name, 'networkInterfaceId')
            network_interface = ec2_client.describe_network_interfaces(
              network_interface_ids: [niid.value],
            ).network_interfaces.first
            public_ip =  network_interface&.association&.public_ip
            running_public_ip ||= public_ip
            msg += ", public IP: #{public_ip}"
          end
          puts msg
        end
        puts
      end

      puts
      task_ids = []
      max_event_count = 5
      event_count = 0
      service = ecs_client.describe_services(
        cluster: cluster_name,
        services: [service_name],
      ).services.first
      if service.nil?
        puts 'Service is missing'
      else
        if service.events.empty?
          puts 'No events for service'
        else
          puts "Events for #{service.service_arn}:"
          service.events.each do |event|
            event_count += 1
            break if event_count > max_event_count
            if event.message =~ /\(task (\w+)\)/
              task_ids << $1
            end
            puts "#{event.created_at.strftime('%Y-%m-%d %H:%M:%S %z')} #{event.message}"
          end
        end
      end

      if get_logs && running_task
        puts
        log_stream_name = "task/ssh/#{running_task.task_uuid}"
        log_stream = logs_client.describe_log_streams(
          log_group_name: AWS_AUTH_ECS_LOG_GROUP,
          log_stream_name_prefix: log_stream_name,
        ).log_streams.first
        if log_stream
          log_events = logs_client.get_log_events(
            log_group_name: AWS_AUTH_ECS_LOG_GROUP,
            log_stream_name: log_stream_name,
            end_time: Time.now.to_i * 1000,
            limit: 100,
          ).events
          if log_events.any?
            puts "Task logs for task #{running_task.task_uuid}:"
            log_events.each do |event|
              puts "[#{Time.at(event.timestamp/1000r).strftime('%Y-%m-%d %H:%M:%S %z')}] #{event.message}"
            end
          else
            puts "No CloudWatch events in the log stream for task #{running_task.task_uuid}"
          end
        else
          puts "No CloudWatch log stream for task #{running_task.task_uuid}"
        end
      end

      if running_public_ip
        puts
        puts "ip=#{running_public_ip}"
        puts "ssh -o StrictHostKeyChecking=false root@#{running_public_ip}"
      end

      {
        private_ip: running_private_ip,
      }
    end

    private

    def ucfirst(str)
      str[0].upcase + str[1...str.length]
    end
  end
end