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.
- checksums.yaml +8 -8
- data/.gitignore +3 -0
- data/.travis.yml +6 -1
- data/Gemfile +3 -0
- data/Guardfile +5 -0
- data/README.markdown +44 -17
- data/bin/opsicle +36 -3
- data/lib/opsicle.rb +2 -3
- data/lib/opsicle/commands.rb +6 -0
- data/lib/opsicle/commands/deploy.rb +39 -0
- data/lib/opsicle/{list.rb → commands/list.rb} +0 -2
- data/lib/opsicle/{ssh.rb → commands/ssh.rb} +0 -3
- data/lib/opsicle/commands/ssh_key.rb +40 -0
- data/lib/opsicle/config.rb +1 -0
- data/lib/opsicle/deployment.rb +59 -0
- data/lib/opsicle/deployments.rb +22 -0
- data/lib/opsicle/monitor.rb +12 -0
- data/lib/opsicle/monitor/app.rb +147 -0
- data/lib/opsicle/monitor/panel.rb +98 -0
- data/lib/opsicle/monitor/panels/deployments.rb +42 -0
- data/lib/opsicle/monitor/panels/header.rb +48 -0
- data/lib/opsicle/monitor/panels/help.rb +33 -0
- data/lib/opsicle/monitor/screen.rb +83 -0
- data/lib/opsicle/monitor/spy/dataspyable.rb +19 -0
- data/lib/opsicle/monitor/spy/deployments.rb +53 -0
- data/lib/opsicle/monitor/subpanel.rb +55 -0
- data/lib/opsicle/monitor/translatable.rb +33 -0
- data/lib/opsicle/stack.rb +22 -0
- data/lib/opsicle/version.rb +1 -1
- data/opsicle.gemspec +1 -1
- data/spec/opsicle/client_spec.rb +6 -6
- data/spec/opsicle/commands/deploy_spec.rb +50 -0
- data/spec/opsicle/{list_spec.rb → commands/list_spec.rb} +7 -6
- data/spec/opsicle/commands/ssh_key_spec.rb +75 -0
- data/spec/opsicle/{ssh_spec.rb → commands/ssh_spec.rb} +24 -24
- data/spec/opsicle/config_spec.rb +12 -11
- data/spec/opsicle/monitor/app_spec.rb +63 -0
- data/spec/opsicle/monitor/panel_spec.rb +162 -0
- data/spec/opsicle/monitor/screen_spec.rb +121 -0
- data/spec/opsicle/monitor/spy/deployments_spec.rb +41 -0
- data/spec/opsicle/monitor/subpanel_spec.rb +199 -0
- data/spec/spec_helper.rb +2 -1
- metadata +44 -16
- data/Gemfile.lock +0 -75
- data/lib/opsicle/deploy.rb +0 -25
- 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,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
|