opsicle 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +8 -8
  2. data/.gitignore +3 -0
  3. data/.travis.yml +6 -1
  4. data/Gemfile +3 -0
  5. data/Guardfile +5 -0
  6. data/README.markdown +44 -17
  7. data/bin/opsicle +36 -3
  8. data/lib/opsicle.rb +2 -3
  9. data/lib/opsicle/commands.rb +6 -0
  10. data/lib/opsicle/commands/deploy.rb +39 -0
  11. data/lib/opsicle/{list.rb → commands/list.rb} +0 -2
  12. data/lib/opsicle/{ssh.rb → commands/ssh.rb} +0 -3
  13. data/lib/opsicle/commands/ssh_key.rb +40 -0
  14. data/lib/opsicle/config.rb +1 -0
  15. data/lib/opsicle/deployment.rb +59 -0
  16. data/lib/opsicle/deployments.rb +22 -0
  17. data/lib/opsicle/monitor.rb +12 -0
  18. data/lib/opsicle/monitor/app.rb +147 -0
  19. data/lib/opsicle/monitor/panel.rb +98 -0
  20. data/lib/opsicle/monitor/panels/deployments.rb +42 -0
  21. data/lib/opsicle/monitor/panels/header.rb +48 -0
  22. data/lib/opsicle/monitor/panels/help.rb +33 -0
  23. data/lib/opsicle/monitor/screen.rb +83 -0
  24. data/lib/opsicle/monitor/spy/dataspyable.rb +19 -0
  25. data/lib/opsicle/monitor/spy/deployments.rb +53 -0
  26. data/lib/opsicle/monitor/subpanel.rb +55 -0
  27. data/lib/opsicle/monitor/translatable.rb +33 -0
  28. data/lib/opsicle/stack.rb +22 -0
  29. data/lib/opsicle/version.rb +1 -1
  30. data/opsicle.gemspec +1 -1
  31. data/spec/opsicle/client_spec.rb +6 -6
  32. data/spec/opsicle/commands/deploy_spec.rb +50 -0
  33. data/spec/opsicle/{list_spec.rb → commands/list_spec.rb} +7 -6
  34. data/spec/opsicle/commands/ssh_key_spec.rb +75 -0
  35. data/spec/opsicle/{ssh_spec.rb → commands/ssh_spec.rb} +24 -24
  36. data/spec/opsicle/config_spec.rb +12 -11
  37. data/spec/opsicle/monitor/app_spec.rb +63 -0
  38. data/spec/opsicle/monitor/panel_spec.rb +162 -0
  39. data/spec/opsicle/monitor/screen_spec.rb +121 -0
  40. data/spec/opsicle/monitor/spy/deployments_spec.rb +41 -0
  41. data/spec/opsicle/monitor/subpanel_spec.rb +199 -0
  42. data/spec/spec_helper.rb +2 -1
  43. metadata +44 -16
  44. data/Gemfile.lock +0 -75
  45. data/lib/opsicle/deploy.rb +0 -25
  46. data/spec/opsicle/deploy_spec.rb +0 -29
@@ -0,0 +1,98 @@
1
+ require 'curses'
2
+ require 'opsicle/monitor/translatable'
3
+ require 'opsicle/monitor/subpanel'
4
+
5
+
6
+ module Opsicle
7
+ module Monitor
8
+ class Panel
9
+
10
+ include Monitor::Translatable
11
+
12
+ attr_reader :height
13
+ attr_reader :width
14
+ attr_reader :top
15
+ attr_reader :left
16
+ attr_reader :dividers
17
+ attr_reader :divider_length
18
+
19
+ def initialize(height, width, top, left, structure = [], opts = {})
20
+ @height = height
21
+ @width = width
22
+ @top = top
23
+ @left = left
24
+
25
+ @dividers = {
26
+ :left => opts[:divider_l].to_s,
27
+ :right => opts[:divider_r].to_s,
28
+ }
29
+
30
+ @divider_length = @dividers.values.map(&:length).inject(:+)
31
+
32
+ @window = Curses::Window.new(@height, @width, @top, @left)
33
+
34
+ @subpanels = build_subpanels(structure)
35
+
36
+ @spies = {} # data sources for #structure
37
+ end
38
+
39
+ def close
40
+ @window.close
41
+ end
42
+
43
+ def refresh
44
+ @subpanels.each(&:refresh) # build changes
45
+
46
+ @window.refresh # push changes to window
47
+ end
48
+
49
+ def refresh_spies
50
+ @spies.each { |_, s| s.refresh } # refresh data sources
51
+ end
52
+
53
+ private
54
+
55
+ def build_subpanels(structure)
56
+ subpanels = []
57
+
58
+ structure.each_with_index do |row, i|
59
+ next if row.nil? # skip blank rows
60
+
61
+ row_cols = row.map { |e| e[0] }.inject(:+)
62
+
63
+ col_width = (@width / row_cols) + @divider_length
64
+
65
+ col_i = 0
66
+
67
+ row.each_with_index do |(tag_cols, data_l, data_r), row_i|
68
+ first_col = row_i == 0
69
+ last_col = row_i + 1 == row.length
70
+
71
+ tag_col_width = if last_col
72
+ @width - ((row_cols - tag_cols) * col_width)
73
+ else
74
+ tag_cols * col_width
75
+ end
76
+
77
+ subpanels << Monitor::Subpanel.new(
78
+ @window,
79
+ 1,
80
+ tag_col_width,
81
+ i,
82
+ col_i,
83
+ :data_l => data_l,
84
+ :data_r => data_r,
85
+ :divider_l => (@dividers[:left] unless first_col),
86
+ :divider_r => (@dividers[:right] unless last_col)
87
+ )
88
+
89
+ col_i += tag_col_width
90
+ end
91
+ end
92
+
93
+ subpanels
94
+ end
95
+
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,42 @@
1
+ module Opsicle
2
+ module Monitor
3
+ module Panels
4
+ class Deployments < Monitor::Panel
5
+
6
+ def initialize(height, width, top, left)
7
+ super(height, width, top, left, structure(height), :divider_r => " ")
8
+
9
+ @spies[:deployments] = Monitor::Spy::Deployments.new
10
+ end
11
+
12
+ def structure(height)
13
+ # [
14
+ # [relative_column_width, data_left, data_right]
15
+ # ]
16
+ s = [
17
+ [ # table header slots
18
+ [1, translate[:heading][:status], nil],
19
+ [1, translate[:heading][:created_at], nil],
20
+ [1, translate[:heading][:completed_at], nil],
21
+ [1, translate[:heading][:user], nil],
22
+ [1, translate[:heading][:command], nil]
23
+ ],
24
+ ]
25
+
26
+ (0...(height - 1)).each do |i|
27
+ s << [ # table row slots
28
+ [1, -> { @spies[:deployments][i][:status] }, nil],
29
+ [1, -> { @spies[:deployments][i][:created_at] }, nil],
30
+ [1, -> { @spies[:deployments][i][:completed_at] }, nil],
31
+ [1, -> { @spies[:deployments][i][:user] }, nil],
32
+ [1, -> { @spies[:deployments][i][:command] }, nil]
33
+ ]
34
+ end
35
+
36
+ s
37
+ end
38
+
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,48 @@
1
+ require 'time'
2
+ require 'opsicle/monitor/panel'
3
+ require 'opsicle/stack'
4
+
5
+ module Opsicle
6
+ module Monitor
7
+ module Panels
8
+ class Header < Opsicle::Monitor::Panel
9
+
10
+ attr_accessor :panel_main
11
+
12
+ def initialize(height, width, top, left)
13
+ @stack = Opsicle::Stack.new(App.client)
14
+
15
+ super(height, width, top, left, structure, :divider_r => " ")
16
+ end
17
+
18
+ def structure
19
+ @panel_main = nil # set by Display::Screen#main_panel=
20
+
21
+ # [
22
+ # [relative_column_width, data_left, data_right]
23
+ # ]
24
+ [
25
+ [
26
+ [2, translate[:program], nil],
27
+ [2, -> {
28
+ [:deployments, :help].map do |e|
29
+ translate[:menu][(e == @panel_main ? :active : :inactive)][e]
30
+ end.join(" ")
31
+ }, nil],
32
+ [1, nil, -> { Time.now.strftime("%T %z") }],
33
+ ],
34
+ [
35
+ [1, nil, nil],
36
+ ],
37
+ [
38
+ [1, "Stack name:", @stack.name],
39
+ [1, nil, nil],
40
+ [1, nil, nil]
41
+ ]
42
+ ]
43
+ end
44
+
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,33 @@
1
+ module Opsicle
2
+ module Monitor
3
+ module Panels
4
+ class Help < Monitor::Panel
5
+
6
+ def initialize(height, width, top, left)
7
+ super(height, width, top, left, structure(height), :divider_r => " ")
8
+ end
9
+
10
+ def structure(height)
11
+ # [
12
+ # [relative_column_width, data_left, data_right]
13
+ # ]
14
+ s = [
15
+ [
16
+ [1, "'h' : Show this help screen", nil],
17
+ ],
18
+ [
19
+ [1, "'d' : Show deployment list on this OpsWorks stack", nil],
20
+ ],
21
+ [
22
+ [1, "'b' : Open OpsWorks screen for this stack in your browser", nil],
23
+ ],
24
+ [
25
+ [1, "'q' : Quit", nil],
26
+ ]
27
+ ]
28
+ end
29
+
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,83 @@
1
+ require 'curses'
2
+
3
+ module Opsicle
4
+ module Monitor
5
+ class Screen
6
+
7
+ attr_reader :height
8
+ attr_reader :width
9
+
10
+ def initialize
11
+ Curses.init_screen
12
+ Curses.nl
13
+ Curses.noecho
14
+ Curses.curs_set(0)
15
+
16
+ @height = term_height
17
+ @width = term_width
18
+
19
+ @panels = { # attach panels, defining height, width, top, left
20
+ :header => Monitor::Panels::Header.new( 6, @width, 0, 0),
21
+ }
22
+
23
+ self.panel_main = :deployments
24
+
25
+ Curses.refresh
26
+ rescue
27
+ close
28
+
29
+ raise
30
+ end
31
+
32
+ def close
33
+ @panels.each { |pname, panel| panel.close } if @panels
34
+
35
+ Curses.close_screen
36
+ end
37
+
38
+ def refresh
39
+ @panels.each { |pname, panel| panel.refresh }
40
+ end
41
+
42
+ def refresh_spies
43
+ @panels.each { |pname, panel| panel.refresh_spies }
44
+ end
45
+
46
+ def next_key
47
+ Curses.getch
48
+ end
49
+
50
+ def missized?
51
+ @height != term_height || @width != term_width
52
+ end
53
+
54
+ def panel_main
55
+ @panels[:header].panel_main
56
+ end
57
+
58
+ def panel_main=(pname)
59
+ @panels[:header].panel_main = pname
60
+
61
+ @panels[:main].close if @panels[:main]
62
+
63
+ @panels[:main] = case pname
64
+ when :deployments
65
+ Monitor::Panels::Deployments.new((@height - 4), @width, 4, 0)
66
+ when :help
67
+ Monitor::Panels::Help.new((@height - 4), @width, 4, 0)
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ def term_height
74
+ (ENV['LINES'] || Curses.lines).to_i
75
+ end
76
+
77
+ def term_width
78
+ (ENV['COLUMNS'] || Curses.cols).to_i
79
+ end
80
+
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,19 @@
1
+ module Opsicle
2
+ module Monitor
3
+ module Spy
4
+ module Dataspyable
5
+
6
+ include Enumerable
7
+
8
+ def each(&block)
9
+ @data.each(&block)
10
+ end
11
+
12
+ def [](key)
13
+ @data[key] || {}
14
+ end
15
+
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,53 @@
1
+ require 'opsicle/deployments'
2
+
3
+ module Opsicle
4
+ module Monitor
5
+ module Spy
6
+ class Deployments
7
+
8
+ include Spy::Dataspyable
9
+
10
+ def initialize
11
+ @deployments = Opsicle::Deployments.new(Opsicle::Monitor::App.client)
12
+ refresh
13
+ end
14
+
15
+ def refresh
16
+ h = []
17
+
18
+ @deployments.data.each do |deployment|
19
+ # Massage the API data for our uses
20
+ h << {
21
+ :deployment_id => deployment[:deployment_id],
22
+ :stack_id => deployment[:stack_id],
23
+ :app_id => deployment[:app_id],
24
+ :created_at => format_date(deployment[:created_at]),
25
+ :completed_at => format_date(deployment[:completed_at]),
26
+ :duration => deployment[:duration],
27
+ :user => user_from_arn(deployment[:iam_user_arn]),
28
+ :comment => deployment[:comment],
29
+ :command => deployment[:command][:name],
30
+ :name => deployment[:name],
31
+ :args => deployment[:args],
32
+ :value => deployment[:value],
33
+ :status => deployment[:status],
34
+ :custom_json => deployment[:custom_json],
35
+ :instance_ids => deployment[:instance_ids]
36
+ }
37
+ end
38
+
39
+ @data = h
40
+ end
41
+
42
+ def user_from_arn(amazon_resource_name)
43
+ /(?::user\/)?([^:\n]+)$/.match(amazon_resource_name).to_a[1].to_s
44
+ end
45
+
46
+ def format_date(date)
47
+ date ? Time.parse(date).strftime("%T %m/%d") : ""
48
+ end
49
+
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,55 @@
1
+ module Opsicle
2
+ module Monitor
3
+ class Subpanel
4
+
5
+ attr_reader :height
6
+ attr_reader :width
7
+ attr_reader :top
8
+ attr_reader :left
9
+ attr_reader :data
10
+ attr_reader :dividers
11
+ attr_reader :content_width
12
+
13
+ def initialize(window, height, width, top, left, opts = {})
14
+ @window = window
15
+ @height = height
16
+ @width = width
17
+ @top = top
18
+ @left = left
19
+
20
+ @data = {
21
+ :left => opts[:data_l] || '',
22
+ :right => opts[:data_r] || '',
23
+ }
24
+
25
+ @dividers = {
26
+ :left => opts[:divider_l] || '',
27
+ :right => opts[:divider_r] || '',
28
+ }
29
+
30
+ @content_width = @width - @dividers.values.map(&:length).inject(:+)
31
+ end
32
+
33
+ def refresh
34
+ new_current = content(@data[:left], @data[:right], @content_width)
35
+
36
+ unless new_current == @data[:current] # don't redraw if hasn't changed
37
+ @window.setpos(@top, @left)
38
+ @window.addstr(@dividers[:left] + new_current + @dividers[:right])
39
+ end
40
+
41
+ @data[:current] = new_current
42
+ end
43
+
44
+ private
45
+
46
+ def content(data_l, data_r, width)
47
+ l = data_l.is_a?(Proc) ? data_l.call : data_l
48
+ r = data_r.is_a?(Proc) ? data_r.call : data_r
49
+
50
+ ("#{l}%#{width - l.to_s.length}s" % r)[0...width]
51
+ end
52
+
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,33 @@
1
+ module Opsicle
2
+ module Monitor
3
+ module Translatable
4
+
5
+ def translate
6
+ {
7
+ program: "Opsicle Stack Monitor v#{Opsicle::VERSION}",
8
+ menu: {
9
+ inactive: {
10
+ deployments: "(d)eployments",
11
+ instances: "(i)nstances",
12
+ apps: "(a)pps",
13
+ help: "(h)elp"
14
+ },
15
+ active: {
16
+ deployments: "DEPLOYMENTS",
17
+ instances: "INSTANCES",
18
+ apps: "APPS",
19
+ help: "HELP"
20
+ },
21
+ },
22
+ heading: {
23
+ status: "STATUS",
24
+ created_at: "STARTED",
25
+ completed_at: "COMPLETED",
26
+ user: "USER",
27
+ command: "COMMAND"
28
+ }
29
+ }
30
+ end
31
+ end
32
+ end
33
+ end