ecl 1.2.1 → 3.0.0.pre.alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +0 -1
  3. data/Gemfile +1 -0
  4. data/Gemfile.lock +48 -0
  5. data/README.md +112 -89
  6. data/Rakefile +1 -0
  7. data/eclair.gemspec +7 -4
  8. data/exe/ecl +62 -4
  9. data/lib/eclair.rb +21 -18
  10. data/lib/eclair/color.rb +3 -0
  11. data/lib/eclair/config.rb +68 -21
  12. data/lib/eclair/grid.rb +242 -274
  13. data/lib/eclair/group_item.rb +33 -0
  14. data/lib/eclair/item.rb +42 -0
  15. data/lib/eclair/less_viewer.rb +2 -1
  16. data/lib/eclair/provider.rb +15 -0
  17. data/lib/eclair/providers/ec2.rb +2 -0
  18. data/lib/eclair/providers/ec2/ec2_group_item.rb +16 -0
  19. data/lib/eclair/providers/ec2/ec2_item.rb +136 -0
  20. data/lib/eclair/providers/ec2/ec2_provider.rb +118 -0
  21. data/lib/eclair/providers/gce.rb +2 -0
  22. data/lib/eclair/providers/gce/gce_group_item.rb +15 -0
  23. data/lib/eclair/providers/gce/gce_item.rb +117 -0
  24. data/lib/eclair/providers/gce/gce_provider.rb +34 -0
  25. data/lib/eclair/providers/k8s.rb +2 -0
  26. data/lib/eclair/providers/k8s/k8s_group_item.rb +15 -0
  27. data/lib/eclair/providers/k8s/k8s_item.rb +92 -0
  28. data/lib/eclair/providers/k8s/k8s_provider.rb +34 -0
  29. data/lib/eclair/version.rb +2 -1
  30. data/out.gif +0 -0
  31. data/templates/eclrc.template +81 -35
  32. metadata +60 -27
  33. data/bin/console +0 -10
  34. data/lib/eclair/cell.rb +0 -101
  35. data/lib/eclair/column.rb +0 -52
  36. data/lib/eclair/console.rb +0 -56
  37. data/lib/eclair/group.rb +0 -55
  38. data/lib/eclair/helpers/aws_helper.rb +0 -157
  39. data/lib/eclair/helpers/benchmark_helper.rb +0 -19
  40. data/lib/eclair/helpers/common_helper.rb +0 -24
  41. data/lib/eclair/instance.rb +0 -165
  42. data/lib/eclair/matcher.rb +0 -19
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+ module Eclair
3
+ class GroupItem < Item
4
+ attr_reader :label
5
+ attr_accessor :visible
6
+
7
+ def initialize label, items
8
+ @label = label
9
+ @items = items
10
+ @visible = true
11
+ end
12
+
13
+ def toggle_select
14
+ if @items.all?(&:selected)
15
+ @items.each{|i| i.select(false) }
16
+ else
17
+ @items.each{|i| i.select(true) }
18
+ end
19
+ end
20
+
21
+ def select state
22
+ @items.each{|i| i.select(state) }
23
+ end
24
+
25
+ def length
26
+ @items.length
27
+ end
28
+
29
+ def color
30
+ [Curses::COLOR_WHITE, -1, Curses::A_BOLD]
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+ require "eclair/config"
3
+
4
+ module Eclair
5
+ class Item
6
+ include ConfigHelper
7
+ attr_accessor :selected, :visible
8
+
9
+ def initialize
10
+ @selected = false
11
+ @visible = true
12
+ end
13
+
14
+ def toggle_select
15
+ @selected = !@selected
16
+ end
17
+
18
+ def select state
19
+ @selected = state
20
+ end
21
+
22
+ def id
23
+ raise "Not Implemented"
24
+ end
25
+
26
+ def command
27
+ raise "Not Implemented"
28
+ end
29
+
30
+ def header
31
+ raise "Not Implemented"
32
+ end
33
+
34
+ def title
35
+ raise "Not Implemented"
36
+ end
37
+
38
+ def search_key
39
+ raise "Not Implemented"
40
+ end
41
+ end
42
+ end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Eclair
2
3
  module LessViewer
3
4
  extend self
@@ -8,7 +9,7 @@ module Eclair
8
9
  q.flush
9
10
  out << "\n"
10
11
  end
11
-
12
+
12
13
  def text(str, width = str.length)
13
14
  super(CodeRay.scan(str, :ruby).term, width)
14
15
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+ module Eclair
3
+ module Provider
4
+ extend self
5
+ attr_accessor :items
6
+
7
+ def filter_items search_buffer
8
+ @items.select{ |item| item&.search_key&.include?(search_buffer.downcase) || item.selected }
9
+ end
10
+
11
+ def require_prepare
12
+ raise "Not prepared" unless @prepared
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,2 @@
1
+ # frozen_string_literal: true
2
+ require "eclair/providers/ec2/ec2_provider"
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+ require "eclair/group_item"
3
+
4
+ module Eclair
5
+ class EC2GroupItem < GroupItem
6
+ def header
7
+ running = @items.count{|i| i.instance.state[:code] == 16}
8
+ all = @items.count
9
+
10
+ <<-EOS
11
+ Group #{label}
12
+ #{running} Instances Running / #{all} Instances Total
13
+ EOS
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+ require 'shellwords'
3
+ require "aws-sdk-ec2"
4
+
5
+ require "eclair/item"
6
+ require "eclair/providers/ec2/ec2_provider"
7
+
8
+ module Eclair
9
+ class EC2Item < Item
10
+ attr_reader :instance
11
+
12
+ def initialize instance
13
+ super()
14
+ @instance = instance
15
+ end
16
+
17
+ def id
18
+ @instance.id
19
+ end
20
+
21
+ def color
22
+ if @selected
23
+ [Curses::COLOR_YELLOW, -1, Curses::A_BOLD]
24
+ elsif !connectable?
25
+ [Curses::COLOR_BLACK, -1, Curses::A_BOLD]
26
+ else
27
+ [Curses::COLOR_WHITE, -1]
28
+ end
29
+ end
30
+
31
+ def command
32
+ hosts = [@instance.public_ip_address, @instance.private_ip_address].compact
33
+ ports = config.ssh_ports
34
+ ssh_options = config.ssh_options
35
+ ssh_command = config.ssh_command
36
+ username = config.ssh_username.call(image)
37
+ key_cmd = config.ssh_keys[@instance.key_name] ? "-i #{config.ssh_keys[@instance.key_name]}" : ""
38
+ format = config.exec_format
39
+
40
+ joined_cmd = hosts.map do |host|
41
+ ports.map do |port|
42
+ {
43
+ "{ssh_command}" => ssh_command,
44
+ "{ssh_options}" => ssh_options,
45
+ "{port}" => port,
46
+ "{ssh_key}" => key_cmd,
47
+ "{username}" => username,
48
+ "{host}" => host,
49
+ }.reduce(format) { |cmd,pair| cmd.sub(pair[0],pair[1].to_s) }
50
+ end
51
+ end.join(" || ")
52
+ # puts joined_cmd
53
+ "echo Attaching to #{Shellwords.escape(name)} \\[#{@instance.instance_id}\\] && #{joined_cmd}"
54
+ end
55
+
56
+ def header
57
+ <<-EOS
58
+ #{name} (#{instance_id}) [#{state[:name]}]
59
+ launched at #{launch_time.to_time}
60
+ EOS
61
+ end
62
+
63
+ def image
64
+ @image ||= provider.find_image_by_id(@instance.image_id)
65
+ end
66
+
67
+ def vpc
68
+ @vpc ||= provider.find_vpc_by_id(@instance.vpc_id)
69
+ end
70
+
71
+ def header
72
+ <<-EOS
73
+ #{name} (#{@instance.instance_id}) [#{@instance.state[:name]}]
74
+ launched at #{@instance.launch_time.to_time}
75
+ EOS
76
+ end
77
+
78
+ def label
79
+ " - #{name} [#{launched_at}]"
80
+ end
81
+
82
+ def info
83
+ {
84
+ instance: @instance,
85
+ image: provider.image_loaded? ? image : "ami info not loaded yet",
86
+ security_groups: provider.security_group_loaded? ? security_groups : "sg info not loaded yet",
87
+ }
88
+ end
89
+
90
+ def connectable?
91
+ ![32, 48, 80].include?(@instance.state[:code])
92
+ end
93
+
94
+ def name
95
+ return @name if @name
96
+ begin
97
+ tag = @instance.tags.find{|t| t.key == "Name"}
98
+ @name = tag ? tag.value : "noname"
99
+ rescue
100
+ @name = "terminated"
101
+ end
102
+ end
103
+
104
+ def security_groups
105
+ @security_groups ||= @instance.security_groups.map{|sg| provider.find_security_group_by_id(sg.group_id)}
106
+ end
107
+
108
+ def search_key
109
+ name.downcase
110
+ end
111
+
112
+ private
113
+
114
+ def launched_at
115
+ diff = Time.now - @instance.launch_time
116
+ {
117
+ "year" => 31557600,
118
+ "month" => 2592000,
119
+ "day" => 86400,
120
+ "hour" => 3600,
121
+ "minute" => 60,
122
+ "second" => 1
123
+ }.each do |unit,v|
124
+ if diff >= v
125
+ value = (diff/v).to_i
126
+ return "#{value} #{unit}#{value > 1 ? "s" : ""}"
127
+ end
128
+ end
129
+ "now"
130
+ end
131
+
132
+ def provider
133
+ EC2Provider
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+ require "aws-sdk-ec2"
3
+ require "eclair/provider"
4
+ require "eclair/providers/ec2/ec2_item"
5
+ require "eclair/providers/ec2/ec2_group_item"
6
+
7
+ module Eclair
8
+ module EC2Provider
9
+ extend Provider
10
+ extend self
11
+
12
+ def group_class
13
+ EC2GroupItem
14
+ end
15
+
16
+ def item_class
17
+ EC2Item
18
+ end
19
+
20
+ def prepare keyword
21
+ Thread.abort_on_exception = true
22
+
23
+ @instances ||= fetch_instances keyword
24
+
25
+ image_ids = @instances.map(&:image_id).uniq
26
+ @image_thread = Thread.new do
27
+ @images = fetch_images(image_ids)
28
+ @id_to_image = @images.map{|i| [i.image_id, i]}.to_h
29
+ end
30
+
31
+ @sg_thread = Thread.new do
32
+ @security_groups = fetch_security_groups
33
+ @id_to_sg = @security_groups.map{|i| [i.group_id, i]}.to_h
34
+ end
35
+
36
+ @vpc_thread = Thread.new do
37
+ @vpcs = fetch_vpcs
38
+ @id_to_vpc = @vpcs.map{|i| [i.vpc_id, i]}.to_h
39
+ end
40
+
41
+ @items = @instances.map{|i| EC2Item.new(i)}
42
+
43
+ @prepared = true
44
+ end
45
+
46
+ def image_loaded?
47
+ !@sg_thread.alive?
48
+ end
49
+
50
+ def images
51
+ @image_thread.join if @image_thread.alive?
52
+ @images
53
+ end
54
+
55
+ def find_image_by_id id
56
+ @image_thread.join if @image_thread.alive?
57
+ @id_to_image[id]
58
+ end
59
+
60
+ def security_group_loaded?
61
+ !@sg_thread.alive?
62
+ end
63
+
64
+ def security_groups
65
+ @sg_thread.join if @sg_thread.alive?
66
+ @security_groups
67
+ end
68
+
69
+ def find_security_group_by_id id
70
+ @sg_thread.join if @sg_thread.alive?
71
+ @id_to_sg[id]
72
+ end
73
+
74
+ def vpc_loaded?
75
+ !@vpc_thread.alive?
76
+ end
77
+
78
+ def vpcs
79
+ @vpc_thread.join if @vpc_thread.alive?
80
+ @vpcs
81
+ end
82
+
83
+ def find_vpc_by_id id
84
+ @vpc_thread.join if @vpc_thread.alive?
85
+ @id_to_vpc[id]
86
+ end
87
+
88
+ private
89
+
90
+ def ec2_client
91
+ @ec2_client ||= Aws::EC2::Client.new
92
+ end
93
+
94
+ def fetch_instances keyword
95
+ filter = if keyword.empty? then {} else { filters: [{name: "tag:Name", values: ["*#{keyword}*"]}] } end
96
+
97
+ ec2_client.describe_instances(filter).map{ |resp|
98
+ resp.data.reservations.map do |rsv|
99
+ rsv.instances
100
+ end
101
+ }.flatten
102
+ end
103
+
104
+ def fetch_security_groups
105
+ ec2_client.describe_security_groups.map{ |resp|
106
+ resp.security_groups
107
+ }.flatten
108
+ end
109
+
110
+ def fetch_images image_ids
111
+ ec2_client.describe_images(image_ids: image_ids).images.flatten
112
+ end
113
+
114
+ def fetch_vpcs
115
+ ec2_client.describe_vpcs.map{|resp| resp.vpcs}.flatten
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,2 @@
1
+ # frozen_string_literal: true
2
+ require "eclair/providers/gce/gce_provider"
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+ require "eclair/group_item"
3
+
4
+ module Eclair
5
+ class GCEGroupItem < GroupItem
6
+ def header
7
+ all = @items.count
8
+
9
+ <<-EOS
10
+ Group #{label}
11
+ #{all} instances Total
12
+ EOS
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+ require 'eclair/item'
3
+ require 'eclair/providers/gce/gce_provider'
4
+ require 'time'
5
+
6
+ module Eclair
7
+ class GCEItem < Item
8
+ attr_reader :instance
9
+
10
+ def initialize instance
11
+ super()
12
+ @instance = instance
13
+ end
14
+
15
+ def id
16
+ @instance["id"]
17
+ end
18
+
19
+ def color
20
+ if @selected
21
+ [Curses::COLOR_YELLOW, -1, Curses::A_BOLD]
22
+ elsif !connectable?
23
+ [Curses::COLOR_BLACK, -1, Curses::A_BOLD]
24
+ else
25
+ [Curses::COLOR_WHITE, -1]
26
+ end
27
+ end
28
+
29
+ def header
30
+ <<-EOS
31
+ #{name} (#{public_ip_address} #{private_ip_address})
32
+ launched at #{launch_time.to_time}
33
+ #{description}
34
+ EOS
35
+ end
36
+
37
+ def label
38
+ " - #{name} [#{launched_at}]"
39
+ end
40
+
41
+ def description
42
+ @instance["description"]
43
+ end
44
+
45
+ def name
46
+ @instance["name"]
47
+ end
48
+
49
+ def public_ip_address
50
+ @instance.dig("networkInterfaces", 0, "accessConfigs", 0, "natIP")
51
+ end
52
+
53
+ def private_ip_address
54
+ @instance.dig("networkInterfaces", 0, "networkIP")
55
+ end
56
+
57
+ def command
58
+ hosts = [private_ip_address, public_ip_address].compact
59
+ ports = config.ssh_ports
60
+ ssh_options = config.ssh_options
61
+ ssh_command = config.ssh_command
62
+ username = "ubuntu"
63
+ format = config.exec_format
64
+
65
+ joined_cmd = hosts.map do |host|
66
+ ports.map do |port|
67
+ {
68
+ "{ssh_command}" => ssh_command,
69
+ "{ssh_options}" => ssh_options,
70
+ "{port}" => port,
71
+ "{username}" => username,
72
+ "{host}" => host,
73
+ "{ssh_key}" => ""
74
+ }.reduce(format) { |cmd,pair| cmd.sub(pair[0],pair[1].to_s) }
75
+ end
76
+ end.join(" || ")
77
+ # puts joined_cmd
78
+ "echo Attaching to #{name} \\[#{name}\\] && #{joined_cmd}"
79
+ end
80
+
81
+ def connectable?
82
+ status == "RUNNING"
83
+ end
84
+
85
+ def search_key
86
+ name.downcase
87
+ end
88
+
89
+ private
90
+
91
+ def status
92
+ @instance["status"]
93
+ end
94
+
95
+ def launch_time
96
+ Time.parse(@instance["creationTimestamp"]).localtime
97
+ end
98
+
99
+ def launched_at
100
+ diff = Time.now - launch_time
101
+ {
102
+ "year" => 31557600,
103
+ "month" => 2592000,
104
+ "day" => 86400,
105
+ "hour" => 3600,
106
+ "minute" => 60,
107
+ "second" => 1
108
+ }.each do |unit,v|
109
+ if diff >= v
110
+ value = (diff/v).to_i
111
+ return "#{value} #{unit}#{value > 1 ? "s" : ""}"
112
+ end
113
+ end
114
+ "now"
115
+ end
116
+ end
117
+ end