hammer_cli 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +5 -0
- data/README.md +105 -0
- data/bin/hammer +53 -0
- data/config/cli_config.template.yml +14 -0
- data/doc/design.png +0 -0
- data/doc/design.uml +24 -0
- data/hammer_cli_complete +13 -0
- data/lib/hammer_cli/abstract.rb +95 -0
- data/lib/hammer_cli/apipie/command.rb +75 -0
- data/lib/hammer_cli/apipie/options.rb +97 -0
- data/lib/hammer_cli/apipie/read_command.rb +58 -0
- data/lib/hammer_cli/apipie/resource.rb +54 -0
- data/lib/hammer_cli/apipie/write_command.rb +35 -0
- data/lib/hammer_cli/apipie.rb +3 -0
- data/lib/hammer_cli/autocompletion.rb +46 -0
- data/lib/hammer_cli/exception_handler.rb +69 -0
- data/lib/hammer_cli/exit_codes.rb +23 -0
- data/lib/hammer_cli/logger.rb +50 -0
- data/lib/hammer_cli/logger_watch.rb +14 -0
- data/lib/hammer_cli/main.rb +53 -0
- data/lib/hammer_cli/messages.rb +55 -0
- data/lib/hammer_cli/option_formatters.rb +13 -0
- data/lib/hammer_cli/output/adapter/abstract.rb +27 -0
- data/lib/hammer_cli/output/adapter/base.rb +143 -0
- data/lib/hammer_cli/output/adapter/silent.rb +17 -0
- data/lib/hammer_cli/output/adapter/table.rb +53 -0
- data/lib/hammer_cli/output/adapter.rb +6 -0
- data/lib/hammer_cli/output/definition.rb +17 -0
- data/lib/hammer_cli/output/dsl.rb +56 -0
- data/lib/hammer_cli/output/fields.rb +128 -0
- data/lib/hammer_cli/output/output.rb +27 -0
- data/lib/hammer_cli/output.rb +6 -0
- data/lib/hammer_cli/settings.rb +48 -0
- data/lib/hammer_cli/shell.rb +49 -0
- data/lib/hammer_cli/validator.rb +114 -0
- data/lib/hammer_cli/version.rb +5 -0
- data/lib/hammer_cli.rb +13 -0
- metadata +189 -0
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'rest_client'
|
2
|
+
require 'logging'
|
3
|
+
|
4
|
+
module HammerCLI
|
5
|
+
class ExceptionHandler
|
6
|
+
|
7
|
+
def initialize options={}
|
8
|
+
@output = options[:output] or raise "Missing option output"
|
9
|
+
@logger = Logging.logger['Exception']
|
10
|
+
end
|
11
|
+
|
12
|
+
def mappings
|
13
|
+
[
|
14
|
+
[Exception, :handle_general_exception], # catch all
|
15
|
+
[RestClient::ResourceNotFound, :handle_not_found],
|
16
|
+
[RestClient::Unauthorized, :handle_unauthorized],
|
17
|
+
]
|
18
|
+
end
|
19
|
+
|
20
|
+
def handle_exception e, options={}
|
21
|
+
@options = options
|
22
|
+
handler = mappings.reverse.find { |m| e.class.respond_to?(:"<=") ? e.class <= m[0] : false }
|
23
|
+
return send(handler[1], e) if handler
|
24
|
+
raise e
|
25
|
+
end
|
26
|
+
|
27
|
+
protected
|
28
|
+
|
29
|
+
def print_error error
|
30
|
+
error = error.join("\n") if error.kind_of? Array
|
31
|
+
@logger.error error
|
32
|
+
|
33
|
+
if @options[:heading]
|
34
|
+
@output.print_error @options[:heading], error
|
35
|
+
else
|
36
|
+
@output.print_error error
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def log_full_error e
|
41
|
+
backtrace = e.backtrace || []
|
42
|
+
@logger.error "\n\n#{e.class} (#{e.message}):\n " +
|
43
|
+
backtrace.join("\n ")
|
44
|
+
"\n\n"
|
45
|
+
end
|
46
|
+
|
47
|
+
def handle_general_exception e
|
48
|
+
print_error "Error: " + e.message
|
49
|
+
log_full_error e
|
50
|
+
HammerCLI::EX_SOFTWARE
|
51
|
+
end
|
52
|
+
|
53
|
+
def handle_not_found e
|
54
|
+
print_error e.message
|
55
|
+
log_full_error e
|
56
|
+
HammerCLI::EX_NOT_FOUND
|
57
|
+
end
|
58
|
+
|
59
|
+
def handle_unauthorized e
|
60
|
+
print_error "Invalid username or password"
|
61
|
+
log_full_error e
|
62
|
+
HammerCLI::EX_UNAUTHORIZED
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module HammerCLI
|
2
|
+
# taken from sysexits.h
|
3
|
+
EX_OK = 0 # successful termination
|
4
|
+
EX_USAGE = 64 # command line usage error
|
5
|
+
EX_DATAERR = 65 # data format error
|
6
|
+
EX_NOINPUT = 66 # cannot open input
|
7
|
+
EX_NOUSER = 67 # addressee unknown
|
8
|
+
EX_NOHOST = 68 # host name unknown
|
9
|
+
EX_UNAVAILABLE = 69 # service unavailable
|
10
|
+
EX_SOFTWARE = 70 # internal software error
|
11
|
+
EX_OSERR = 71 # system error (e.g., can't fork)
|
12
|
+
EX_OSFILE = 72 # critical OS file missing
|
13
|
+
EX_CANTCREAT = 73 # can't create (user) output file
|
14
|
+
EX_IOERR = 74 # input/output error
|
15
|
+
EX_TEMPFAIL = 75 # temp failure; user is invited to retry
|
16
|
+
EX_PROTOCOL = 76 # remote error in protocol
|
17
|
+
EX_NOPERM = 77 # permission denied
|
18
|
+
EX_CONFIG = 78 # configuration error
|
19
|
+
|
20
|
+
# non POSIX codes
|
21
|
+
EX_NOT_FOUND = 128 # resource was not found
|
22
|
+
EX_UNAUTHORIZED = 129 # authorization failed
|
23
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'logging'
|
3
|
+
|
4
|
+
module HammerCLI
|
5
|
+
module Logger
|
6
|
+
|
7
|
+
Logging.color_scheme('bright',
|
8
|
+
:levels => {
|
9
|
+
:info => :green,
|
10
|
+
:warn => :yellow,
|
11
|
+
:error => :red,
|
12
|
+
:fatal => [:white, :on_red]},
|
13
|
+
:date => :blue,
|
14
|
+
:logger => :cyan,
|
15
|
+
:line => :yellow,
|
16
|
+
:file => :yellow,
|
17
|
+
:method => :yellow)
|
18
|
+
|
19
|
+
pattern = "[%5l %d %c] %m\n"
|
20
|
+
COLOR_LAYOUT = Logging::Layouts::Pattern.new(:pattern => pattern, :color_scheme => 'bright')
|
21
|
+
NOCOLOR_LAYOUT = Logging::Layouts::Pattern.new(:pattern => pattern, :color_scheme => nil)
|
22
|
+
DEFAULT_LOG_DIR = '/var/log/foreman'
|
23
|
+
|
24
|
+
log_dir = File.expand_path(HammerCLI::Settings[:log_dir] || DEFAULT_LOG_DIR)
|
25
|
+
begin
|
26
|
+
FileUtils.mkdir_p(log_dir, :mode => 0750)
|
27
|
+
rescue Errno::EACCES => e
|
28
|
+
puts "No permissions to create log dir #{log_dir}"
|
29
|
+
end
|
30
|
+
|
31
|
+
logger = Logging.logger.root
|
32
|
+
filename = "#{log_dir}/hammer.log"
|
33
|
+
begin
|
34
|
+
logger.appenders = ::Logging.appenders.rolling_file('configure',
|
35
|
+
:filename => filename,
|
36
|
+
:layout => NOCOLOR_LAYOUT,
|
37
|
+
:truncate => false,
|
38
|
+
:keep => 5,
|
39
|
+
:size => HammerCLI::Settings[:log_size] || 1024*1024) # 1MB
|
40
|
+
# set owner and group (it's ignored if attribute is nil)
|
41
|
+
FileUtils.chown HammerCLI::Settings[:log_owner], HammerCLI::Settings[:log_group], filename
|
42
|
+
rescue ArgumentError => e
|
43
|
+
puts "File #{filename} not writeable, won't log anything to file!"
|
44
|
+
end
|
45
|
+
|
46
|
+
logger.level = HammerCLI::Settings[:log_level]
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'awesome_print'
|
2
|
+
|
3
|
+
module HammerCLI
|
4
|
+
module Logger
|
5
|
+
module Watch
|
6
|
+
def watch(label, obj, options={})
|
7
|
+
if debug?
|
8
|
+
options = { :plain => HammerCLI::Settings[:watch_plain], :indent => -2 }.merge(options)
|
9
|
+
debug label + "\n" + obj.ai(options)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'highline/import'
|
2
|
+
|
3
|
+
module HammerCLI
|
4
|
+
|
5
|
+
class MainCommand < AbstractCommand
|
6
|
+
|
7
|
+
option ["-v", "--verbose"], :flag, "be verbose"
|
8
|
+
option ["-c", "--config"], "CFG_FILE", "path to custom config file"
|
9
|
+
|
10
|
+
option ["-u", "--username"], "USERNAME", "username to access the remote system"
|
11
|
+
option ["-p", "--password"], "PASSWORD", "password to access the remote system"
|
12
|
+
|
13
|
+
option "--version", :flag, "show version" do
|
14
|
+
puts "hammer-%s" % HammerCLI.version
|
15
|
+
exit(HammerCLI::EX_OK)
|
16
|
+
end
|
17
|
+
|
18
|
+
option ["-P", "--ask-pass"], :flag, "Ask for password" do
|
19
|
+
context[:password] = get_password()
|
20
|
+
''
|
21
|
+
end
|
22
|
+
|
23
|
+
option "--autocomplete", "LINE", "Get list of possible endings" do |line|
|
24
|
+
line = line.split
|
25
|
+
line.shift
|
26
|
+
endings = self.class.autocomplete(line).map { |l| l[0] }
|
27
|
+
puts endings.join(' ')
|
28
|
+
exit(HammerCLI::EX_OK)
|
29
|
+
end
|
30
|
+
|
31
|
+
def password=(password)
|
32
|
+
context[:password] = password.nil? ? ENV['FOREMAN_PASSWORD'] : password
|
33
|
+
end
|
34
|
+
|
35
|
+
def username=(username)
|
36
|
+
context[:username] = username.nil? ? ENV['FOREMAN_USERNAME'] : username
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def get_password(prompt="Enter Password ")
|
42
|
+
ask(prompt) {|q| q.echo = false}
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
# extend MainCommand
|
52
|
+
require 'hammer_cli/shell'
|
53
|
+
|
@@ -0,0 +1,55 @@
|
|
1
|
+
|
2
|
+
module HammerCLI
|
3
|
+
module Messages
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
def success_message_for action
|
10
|
+
self.class.success_message_for action
|
11
|
+
end
|
12
|
+
|
13
|
+
def success_message
|
14
|
+
self.class.success_message
|
15
|
+
end
|
16
|
+
|
17
|
+
def failure_message_for action
|
18
|
+
self.class.failure_message_for action
|
19
|
+
end
|
20
|
+
|
21
|
+
def failure_message
|
22
|
+
self.class.failure_message
|
23
|
+
end
|
24
|
+
|
25
|
+
def handle_exception e
|
26
|
+
exception_handler.handle_exception e, :heading => failure_message
|
27
|
+
end
|
28
|
+
|
29
|
+
module ClassMethods
|
30
|
+
def success_message_for action, msg=nil
|
31
|
+
@success_message ||= {}
|
32
|
+
@success_message[action] = msg unless msg.nil?
|
33
|
+
@success_message[action]
|
34
|
+
end
|
35
|
+
|
36
|
+
def success_message msg=nil
|
37
|
+
success_message_for :default, msg
|
38
|
+
end
|
39
|
+
|
40
|
+
def failure_message_for action, msg=nil
|
41
|
+
@failure_message ||= {}
|
42
|
+
@failure_message[action] = msg unless msg.nil?
|
43
|
+
@failure_message[action]
|
44
|
+
end
|
45
|
+
|
46
|
+
def failure_message msg=nil
|
47
|
+
failure_message_for :default, msg
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
|
2
|
+
module HammerCLI::Output::Adapter
|
3
|
+
|
4
|
+
class Abstract
|
5
|
+
|
6
|
+
def print_message msg
|
7
|
+
puts msg
|
8
|
+
end
|
9
|
+
|
10
|
+
def print_error msg, details=nil
|
11
|
+
details = details.split("\n") if details.kind_of? String
|
12
|
+
|
13
|
+
if details
|
14
|
+
indent = " "
|
15
|
+
$stderr.puts msg+":"
|
16
|
+
$stderr.puts indent + details.join("\n"+indent)
|
17
|
+
else
|
18
|
+
$stderr.puts msg
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def print_records fields, data, heading=nil
|
23
|
+
raise NotImplementedError
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
module HammerCLI::Output::Adapter
|
2
|
+
class Base < Abstract
|
3
|
+
|
4
|
+
HEADING_LINE_WIDTH = 80
|
5
|
+
GROUP_INDENT = " "*2
|
6
|
+
LABEL_DIVIDER = ": "
|
7
|
+
|
8
|
+
def print_records fields, data, heading=nil
|
9
|
+
self.fields = fields
|
10
|
+
|
11
|
+
puts "-"*HEADING_LINE_WIDTH
|
12
|
+
puts " " + heading.to_s
|
13
|
+
puts "-"*HEADING_LINE_WIDTH
|
14
|
+
|
15
|
+
data.each do |d|
|
16
|
+
fields.collect do |f|
|
17
|
+
render_field(f, d)
|
18
|
+
end
|
19
|
+
puts
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
|
25
|
+
def render_Field(field, data, indent="")
|
26
|
+
puts indent.to_s+" "+format_value(field, data).to_s
|
27
|
+
end
|
28
|
+
|
29
|
+
def render_Label(field, data, indent="")
|
30
|
+
render_label(field, indent)
|
31
|
+
puts
|
32
|
+
indent = indent.to_s + GROUP_INDENT
|
33
|
+
|
34
|
+
field.output_definition.fields.collect do |f|
|
35
|
+
render_field(f, data, indent)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def render_LabeledField(field, data, indent="")
|
40
|
+
render_label(field, indent)
|
41
|
+
puts format_value(field, data).to_s
|
42
|
+
end
|
43
|
+
|
44
|
+
def render_KeyValue(field, data, indent="")
|
45
|
+
render_label(field, indent)
|
46
|
+
params = field.get_value(data) || []
|
47
|
+
print "%{name} => %{value}" % params
|
48
|
+
end
|
49
|
+
|
50
|
+
def render_Collection(field, data, indent="")
|
51
|
+
render_label(field, indent)
|
52
|
+
puts
|
53
|
+
indent = indent.to_s + " "*(label_width_for_list(self.fields)+LABEL_DIVIDER.size)
|
54
|
+
|
55
|
+
field.get_value(data).each do |d|
|
56
|
+
field.output_definition.fields.collect do |f|
|
57
|
+
render_field(f, d, indent)
|
58
|
+
end
|
59
|
+
puts
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def render_Joint(field, data, indent="")
|
64
|
+
render_label(field, indent)
|
65
|
+
data = field.get_value(data)
|
66
|
+
puts field.attributes.collect{|attr| data[attr] }.join(" ")
|
67
|
+
end
|
68
|
+
|
69
|
+
def format_Date(string_date)
|
70
|
+
t = DateTime.parse(string_date.to_s)
|
71
|
+
t.strftime("%Y/%m/%d %H:%M:%S")
|
72
|
+
rescue ArgumentError
|
73
|
+
""
|
74
|
+
end
|
75
|
+
|
76
|
+
def format_List(list)
|
77
|
+
list.join(", ") if list
|
78
|
+
end
|
79
|
+
|
80
|
+
def format_OSName(os)
|
81
|
+
"%{name} %{major}.%{minor}" % os
|
82
|
+
end
|
83
|
+
|
84
|
+
def format_Server(server)
|
85
|
+
"%{name} (%{url})" % server
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
def render_field(field, data, indent="")
|
90
|
+
renderer = find_renderer(field)
|
91
|
+
renderer.call(field, data, indent)
|
92
|
+
end
|
93
|
+
|
94
|
+
def render_label(field, indent="")
|
95
|
+
if field.label
|
96
|
+
w = label_width_for_list(self.fields) - indent.size + 1
|
97
|
+
print indent.to_s+" %#{-w}s" % (field.label.to_s+LABEL_DIVIDER)
|
98
|
+
else
|
99
|
+
print indent.to_s+" "
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def format_value(field, data)
|
104
|
+
format_method = "format_"+field_type(field.class)
|
105
|
+
|
106
|
+
value = field.get_value(data)
|
107
|
+
value = send(format_method, value) if respond_to?(format_method, true)
|
108
|
+
value
|
109
|
+
end
|
110
|
+
|
111
|
+
def find_renderer(field)
|
112
|
+
field.class.ancestors.each do |cls|
|
113
|
+
render_method = "render_"+field_type(cls)
|
114
|
+
return method(render_method) if respond_to?(render_method, true)
|
115
|
+
end
|
116
|
+
raise "No renderer found for field %s" % field.class
|
117
|
+
end
|
118
|
+
|
119
|
+
def field_type(field_class)
|
120
|
+
field_class.name.split("::")[-1]
|
121
|
+
end
|
122
|
+
|
123
|
+
def label_width_for(field)
|
124
|
+
if field.respond_to?(:output_definition)
|
125
|
+
label_width_for_list(field.output_definition.fields)+GROUP_INDENT.size
|
126
|
+
elsif field.respond_to?(:label)
|
127
|
+
field.label.size+LABEL_DIVIDER.size rescue 0
|
128
|
+
else
|
129
|
+
0
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def label_width_for_list(fields)
|
134
|
+
fields.inject(0) do |result, f|
|
135
|
+
width = label_width_for(f)
|
136
|
+
(width > result) ? width : result
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
attr_accessor :fields
|
141
|
+
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'table_print'
|
2
|
+
|
3
|
+
module HammerCLI::Output::Adapter
|
4
|
+
|
5
|
+
class Table < Base
|
6
|
+
|
7
|
+
def print_records fields, data, heading=nil
|
8
|
+
|
9
|
+
rows = data.collect do |d|
|
10
|
+
row = {}
|
11
|
+
fields.each do |f|
|
12
|
+
row[f.label.to_sym] = f.get_value(d)
|
13
|
+
end
|
14
|
+
row
|
15
|
+
end
|
16
|
+
|
17
|
+
options = fields.collect do |f|
|
18
|
+
{ f.label.to_sym => {
|
19
|
+
:formatters => [ Formatter.new(self, "format_"+field_type(f.class)) ] } }
|
20
|
+
end
|
21
|
+
|
22
|
+
printer = TablePrint::Printer.new(rows, options)
|
23
|
+
output = printer.table_print
|
24
|
+
dashes = /\n(-+)\n/.match output
|
25
|
+
puts dashes[1]
|
26
|
+
puts output
|
27
|
+
puts dashes[1]
|
28
|
+
end
|
29
|
+
|
30
|
+
def print_heading heading, size
|
31
|
+
size = heading.size if heading.size > size
|
32
|
+
puts '-' * size
|
33
|
+
puts ' ' * ((size-heading.size)/2) + heading
|
34
|
+
puts '-' * size
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
class Formatter
|
40
|
+
def initialize adapter, method
|
41
|
+
@adapter = adapter
|
42
|
+
@method = method
|
43
|
+
end
|
44
|
+
|
45
|
+
def format value
|
46
|
+
if @adapter.respond_to?(@method, true)
|
47
|
+
value = @adapter.send(@method, value)
|
48
|
+
end
|
49
|
+
value
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module HammerCLI::Output
|
2
|
+
|
3
|
+
class Dsl
|
4
|
+
|
5
|
+
def initialize options={}
|
6
|
+
@current_path = options[:path] || []
|
7
|
+
end
|
8
|
+
|
9
|
+
def fields
|
10
|
+
@fields ||= []
|
11
|
+
@fields
|
12
|
+
end
|
13
|
+
|
14
|
+
def build &block
|
15
|
+
self.instance_eval &block
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
def field key, label, type=nil, options={}, &block
|
21
|
+
options[:path] = current_path.clone << key
|
22
|
+
options[:label] = label
|
23
|
+
type ||= HammerCLI::Output::DataField
|
24
|
+
custom_field type, options, &block
|
25
|
+
end
|
26
|
+
|
27
|
+
def custom_field type, options={}, &block
|
28
|
+
self.fields << type.new(options, &block)
|
29
|
+
end
|
30
|
+
|
31
|
+
def label label, &block
|
32
|
+
options = {}
|
33
|
+
options[:path] = current_path.clone
|
34
|
+
options[:label] = label
|
35
|
+
custom_field HammerCLI::Output::Fields::Label, options, &block
|
36
|
+
end
|
37
|
+
|
38
|
+
def from key
|
39
|
+
self.current_path.push key
|
40
|
+
yield if block_given?
|
41
|
+
self.current_path.pop
|
42
|
+
end
|
43
|
+
|
44
|
+
def collection key, label, options={}, &block
|
45
|
+
field key, label, HammerCLI::Output::Fields::Collection, options, &block
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
def current_path
|
50
|
+
@current_path ||= []
|
51
|
+
@current_path
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|