opsicle 0.1.0 → 0.2.0

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