opsicle 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|