ecl 1.2.1 → 3.0.0.pre.alpha1

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.
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