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
data/LICENSE
ADDED
@@ -0,0 +1,5 @@
|
|
1
|
+
This program and entire repository is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version.
|
2
|
+
|
3
|
+
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
4
|
+
|
5
|
+
You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/.
|
data/README.md
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
Hammer - the CLI tool for foreman
|
2
|
+
=================================
|
3
|
+
|
4
|
+
![Design draft](doc/design.png)
|
5
|
+
|
6
|
+
As the diagram shows, the CLI consist of almost generic framework (shell-like environment, autocompletion, command help text, option evaluation and command invocation) and set of plugins defining the actual commands. This setup is flexible and allows us to easily install different sets of commands for different products. The plugins are independant and can implement any action as an command, so that besides commands calling Foreman API you can have commands calling varius admin tasks, etc.
|
7
|
+
|
8
|
+
|
9
|
+
The CLI has
|
10
|
+
|
11
|
+
- Git-like subcomands
|
12
|
+
- system shell autocompletion for commands and options
|
13
|
+
- shell-like environment with autocompletion and history where the commands can be run directly
|
14
|
+
- commands extensible via plugins
|
15
|
+
- been implemented in Ruby
|
16
|
+
|
17
|
+
|
18
|
+
If you are interested you can help us by sending patches or filing bugs and feature requests (there is CLI catgory in Redmine)
|
19
|
+
|
20
|
+
|
21
|
+
How to run
|
22
|
+
----------
|
23
|
+
|
24
|
+
The work is in progress and there are still no builds ready, but instaling from sources is easy. You will need rake, bundler.
|
25
|
+
Clone and install CLI core
|
26
|
+
|
27
|
+
$ git clone git@github.com:theforeman/hammer-cli.git
|
28
|
+
$ cd hammer-cli
|
29
|
+
$ rake install
|
30
|
+
$ cd ..
|
31
|
+
|
32
|
+
|
33
|
+
clone plugin with foreman commands
|
34
|
+
|
35
|
+
$ git clone git@github.com:theforeman/hammer-cli-foreman.git
|
36
|
+
$ cd hammer-cli-foreman
|
37
|
+
$ rake install
|
38
|
+
$ cd ..
|
39
|
+
|
40
|
+
and configure. Configuration is by default looked for in ~/.foreman/ or in /etc/foreman/.
|
41
|
+
Optionally you can put your configuration in ./config/ or point hammer
|
42
|
+
to some other location using -c CONF_FILE option
|
43
|
+
|
44
|
+
You can start with config file template we created for you and update it to suit your needs. E.g.:
|
45
|
+
|
46
|
+
$ cp hammer-cli/config/cli_config.template.yaml ~/.foreman/cli_config.yml
|
47
|
+
|
48
|
+
and run
|
49
|
+
|
50
|
+
$ hammer -h
|
51
|
+
Usage:
|
52
|
+
hammer [OPTIONS] SUBCOMMAND [ARG] ...
|
53
|
+
|
54
|
+
Parameters:
|
55
|
+
SUBCOMMAND subcommand
|
56
|
+
[ARG] ... subcommand arguments
|
57
|
+
|
58
|
+
Subcommands:
|
59
|
+
shell Interactive Shell
|
60
|
+
architecture Manipulate Foreman's architectures.
|
61
|
+
compute_resource Manipulate Foreman's architectures.
|
62
|
+
domain Manipulate Foreman's domains.
|
63
|
+
organization Manipulate Foreman's organizations.
|
64
|
+
subnet Manipulate Foreman's subnets.
|
65
|
+
user Manipulate Foreman's users.
|
66
|
+
|
67
|
+
Options:
|
68
|
+
-v, --verbose be verbose
|
69
|
+
-u, --username USERNAME username to access the remote system
|
70
|
+
-p, --password PASSWORD password to access the remote system
|
71
|
+
--version show version
|
72
|
+
--autocomplete LINE Get list of possible endings
|
73
|
+
-h, --help print help
|
74
|
+
|
75
|
+
|
76
|
+
Autocompletion
|
77
|
+
--------------
|
78
|
+
|
79
|
+
It is necessary to copy script hammer_cli_complete to the bash_completion.d directory.
|
80
|
+
|
81
|
+
$ sudo cp hammer-cli/hammer_cli_complete /etc/bash_completion.d/
|
82
|
+
|
83
|
+
Then in new shell the completion should work.
|
84
|
+
|
85
|
+
|
86
|
+
How to test
|
87
|
+
------------
|
88
|
+
|
89
|
+
Development of almost all the code was test driven.
|
90
|
+
|
91
|
+
$ bundle install
|
92
|
+
$ bundle exec "rake test"
|
93
|
+
|
94
|
+
should work in any of the cli related repos. Generated coverage reports are stored in ./coverage directory.
|
95
|
+
|
96
|
+
License
|
97
|
+
-------
|
98
|
+
|
99
|
+
This project is licensed under the GPLv3+.
|
100
|
+
|
101
|
+
|
102
|
+
Acknowledgements
|
103
|
+
----------------
|
104
|
+
|
105
|
+
Thanks to Brian Gupta for the initial work and a great name.
|
data/bin/hammer
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'clamp'
|
5
|
+
|
6
|
+
# create fake command instance to use some global args before we start
|
7
|
+
class PreParser < Clamp::Command
|
8
|
+
option ["-v", "--verbose"], :flag, "be verbose"
|
9
|
+
option ["-c", "--config"], "CFG_FILE", "path to custom config file"
|
10
|
+
end
|
11
|
+
|
12
|
+
preparser = PreParser.new File.basename($0), {}
|
13
|
+
begin
|
14
|
+
preparser.parse ARGV
|
15
|
+
rescue
|
16
|
+
end
|
17
|
+
|
18
|
+
# load user's settings
|
19
|
+
require 'hammer_cli/settings'
|
20
|
+
|
21
|
+
CFG_PATH = ['./config/cli_config.yml', '~/.foreman/cli_config.yml', '/etc/foreman/cli_config.yml']
|
22
|
+
|
23
|
+
if preparser.config
|
24
|
+
CFG_PATH.unshift preparser.config
|
25
|
+
end
|
26
|
+
|
27
|
+
HammerCLI::Settings.load_from_file CFG_PATH
|
28
|
+
|
29
|
+
# setup logging
|
30
|
+
require 'hammer_cli/logger'
|
31
|
+
logger = Logging.logger['Init']
|
32
|
+
|
33
|
+
if preparser.verbose?
|
34
|
+
root_logger = Logging.logger.root
|
35
|
+
root_logger.appenders = root_logger.appenders << ::Logging.appenders.stderr(:layout => HammerCLI::Logger::COLOR_LAYOUT)
|
36
|
+
end
|
37
|
+
|
38
|
+
# log which config was loaded (now when we have logging)
|
39
|
+
HammerCLI::Settings.path_history.each do |path|
|
40
|
+
logger.info "Configuration from the file #{path} has been loaded"
|
41
|
+
end
|
42
|
+
|
43
|
+
# load hammer core
|
44
|
+
require 'hammer_cli'
|
45
|
+
|
46
|
+
# load modules set in config
|
47
|
+
modules = HammerCLI::Settings[:modules] || []
|
48
|
+
modules.each do |m|
|
49
|
+
require m
|
50
|
+
logger.info "Extension module #{m} loaded"
|
51
|
+
end
|
52
|
+
|
53
|
+
exit HammerCLI::MainCommand.run || 0
|
@@ -0,0 +1,14 @@
|
|
1
|
+
:modules:
|
2
|
+
# - hammer_cli_foreman
|
3
|
+
# - hammer_cli_katello_bridge
|
4
|
+
|
5
|
+
:host: 'https://localhost/'
|
6
|
+
:username: 'admin'
|
7
|
+
:password: 'changeme'
|
8
|
+
|
9
|
+
# :watch_plain: true disables color output of logger.watch in Clamp commands
|
10
|
+
:watch_plain: false
|
11
|
+
|
12
|
+
#:log_dir: '/var/log/foreman'
|
13
|
+
:log_dir: '~/.foreman/log'
|
14
|
+
:log_level: 'error'
|
data/doc/design.png
ADDED
Binary file
|
data/doc/design.uml
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
@startuml
|
2
|
+
|
3
|
+
|
4
|
+
[Foreman API] as FAPI
|
5
|
+
FAPI -up-> [Foreman Server]
|
6
|
+
[Foreman Commands Plugin] as FCP
|
7
|
+
FCP -up-> FAPI
|
8
|
+
|
9
|
+
[Katello CLI via system call] as KCLI
|
10
|
+
KCLI -up-> [Katello Server]
|
11
|
+
[Katello Commands Plugin] as KCP
|
12
|
+
KCP -up-> KCLI
|
13
|
+
|
14
|
+
package "CLI Framework" {
|
15
|
+
() "Command Plugins" as CP
|
16
|
+
[Shell]
|
17
|
+
[Autocompletion]
|
18
|
+
}
|
19
|
+
|
20
|
+
CP -up-> [Other Commands Plugin(s)]
|
21
|
+
CP -up-> FCP
|
22
|
+
CP -up-> KCP
|
23
|
+
|
24
|
+
@enduml
|
data/hammer_cli_complete
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'hammer_cli/autocompletion'
|
2
|
+
require 'hammer_cli/exception_handler'
|
3
|
+
require 'hammer_cli/logger_watch'
|
4
|
+
require 'clamp'
|
5
|
+
require 'logging'
|
6
|
+
|
7
|
+
module HammerCLI
|
8
|
+
|
9
|
+
class AbstractCommand < Clamp::Command
|
10
|
+
|
11
|
+
extend Autocompletion
|
12
|
+
class << self
|
13
|
+
attr_accessor :validation_block
|
14
|
+
end
|
15
|
+
|
16
|
+
def run(arguments)
|
17
|
+
exit_code = super(arguments)
|
18
|
+
raise "exit code must be integer" unless exit_code.is_a? Integer
|
19
|
+
return exit_code
|
20
|
+
rescue => e
|
21
|
+
# do not catch Clamp errors
|
22
|
+
raise if e.class <= Clamp::UsageError || e.class <= Clamp::HelpWanted
|
23
|
+
handle_exception e
|
24
|
+
end
|
25
|
+
|
26
|
+
def parse(arguments)
|
27
|
+
super(arguments)
|
28
|
+
validate_options
|
29
|
+
logger.info "Called with options: %s" % options.inspect
|
30
|
+
rescue HammerCLI::Validator::ValidationError => e
|
31
|
+
signal_usage_error e.message
|
32
|
+
end
|
33
|
+
|
34
|
+
def execute
|
35
|
+
HammerCLI::EX_OK
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.validate_options &block
|
39
|
+
self.validation_block = block
|
40
|
+
end
|
41
|
+
|
42
|
+
def validate_options
|
43
|
+
validator.run &self.class.validation_block if self.class.validation_block
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
def output
|
48
|
+
@output ||= HammerCLI::Output::Output.new
|
49
|
+
end
|
50
|
+
|
51
|
+
def exception_handler
|
52
|
+
@exception_handler ||= exception_handler_class.new :output => output
|
53
|
+
end
|
54
|
+
|
55
|
+
protected
|
56
|
+
|
57
|
+
def logger name=self.class
|
58
|
+
logger = Logging.logger[name]
|
59
|
+
logger.extend(HammerCLI::Logger::Watch) if not logger.respond_to? :watch
|
60
|
+
logger
|
61
|
+
end
|
62
|
+
|
63
|
+
def validator
|
64
|
+
options = self.class.recognised_options.collect{|opt| opt.of(self)}
|
65
|
+
@validator ||= HammerCLI::Validator.new(options)
|
66
|
+
end
|
67
|
+
|
68
|
+
def handle_exception e
|
69
|
+
exception_handler.handle_exception(e)
|
70
|
+
end
|
71
|
+
|
72
|
+
def exception_handler_class
|
73
|
+
#search for exception handler class in parent modules/classes
|
74
|
+
module_list = self.class.name.to_s.split('::').inject([Object]) do |mod, class_name|
|
75
|
+
mod << mod[-1].const_get(class_name)
|
76
|
+
end
|
77
|
+
module_list.reverse.each do |mod|
|
78
|
+
return mod.send(:exception_handler_class) if mod.respond_to? :exception_handler_class
|
79
|
+
end
|
80
|
+
return HammerCLI::ExceptionHandler
|
81
|
+
end
|
82
|
+
|
83
|
+
def all_options
|
84
|
+
self.class.recognised_options.inject({}) do |h, opt|
|
85
|
+
h[opt.attribute_name] = send(opt.read_method)
|
86
|
+
h
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def options
|
91
|
+
all_options.reject {|key, value| value.nil? }
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'options')
|
2
|
+
require File.join(File.dirname(__FILE__), 'resource')
|
3
|
+
|
4
|
+
module HammerCLI::Apipie
|
5
|
+
|
6
|
+
class Command < HammerCLI::AbstractCommand
|
7
|
+
|
8
|
+
include HammerCLI::Apipie::Resource
|
9
|
+
include HammerCLI::Apipie::Options
|
10
|
+
|
11
|
+
def initialize(*args)
|
12
|
+
setup_identifier_options
|
13
|
+
super(*args)
|
14
|
+
end
|
15
|
+
|
16
|
+
def setup_identifier_options
|
17
|
+
self.class.identifier_option(:id, "resource id")
|
18
|
+
self.class.identifier_option(:name, "resource name")
|
19
|
+
self.class.identifier_option(:label, "resource label")
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.identifiers *keys
|
23
|
+
@identifiers ||= {}
|
24
|
+
keys.each do |key|
|
25
|
+
if key.is_a? Hash
|
26
|
+
@identifiers.merge!(key)
|
27
|
+
else
|
28
|
+
@identifiers.update(key => key)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def validate_options
|
34
|
+
super
|
35
|
+
validator.any(*self.class.declared_identifiers.values).required
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
|
40
|
+
def get_identifier
|
41
|
+
self.class.declared_identifiers.keys.each do |identifier|
|
42
|
+
value = find_option("--"+identifier.to_s).of(self).read
|
43
|
+
return [value, identifier] if value
|
44
|
+
end
|
45
|
+
[nil, nil]
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.identifier? key
|
49
|
+
if @identifiers
|
50
|
+
return true if @identifiers.keys.include? key
|
51
|
+
else
|
52
|
+
return true if superclass.respond_to?(:identifier?, true) and superclass.identifier?(key)
|
53
|
+
end
|
54
|
+
return false
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.declared_identifiers
|
58
|
+
if @identifiers
|
59
|
+
return @identifiers
|
60
|
+
elsif superclass.respond_to?(:declared_identifiers, true)
|
61
|
+
superclass.declared_identifiers
|
62
|
+
else
|
63
|
+
{}
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def self.identifier_option(name, desc)
|
70
|
+
attr_name = declared_identifiers[name]
|
71
|
+
option "--"+name.to_s, name.to_s.upcase, desc, :attribute_name => attr_name if self.identifier? name
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'hammer_cli/option_formatters'
|
2
|
+
|
3
|
+
module HammerCLI::Apipie
|
4
|
+
module Options
|
5
|
+
|
6
|
+
def self.included(base)
|
7
|
+
base.extend(ClassMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
def all_method_options
|
11
|
+
method_options_for_params(self.class.method_doc["params"], true)
|
12
|
+
end
|
13
|
+
|
14
|
+
def method_options
|
15
|
+
method_options_for_params(self.class.method_doc["params"], false)
|
16
|
+
end
|
17
|
+
|
18
|
+
def method_options_for_params params, include_nil=true
|
19
|
+
opts = {}
|
20
|
+
params.each do |p|
|
21
|
+
if p["expected_type"] == "hash"
|
22
|
+
opts[p["name"]] = method_options_for_params(p["params"], include_nil)
|
23
|
+
elsif respond_to?(p["name"], true)
|
24
|
+
opts[p["name"]] = send(p["name"])
|
25
|
+
else
|
26
|
+
opts[p["name"]] = nil
|
27
|
+
end
|
28
|
+
end
|
29
|
+
opts.reject! {|key, value| value.nil? } unless include_nil
|
30
|
+
opts
|
31
|
+
end
|
32
|
+
|
33
|
+
module ClassMethods
|
34
|
+
|
35
|
+
def apipie_options options={}
|
36
|
+
raise "Specify apipie resource first." unless resource_defined?
|
37
|
+
|
38
|
+
filter = options[:without] || []
|
39
|
+
filter = Array(filter)
|
40
|
+
|
41
|
+
options_for_params(method_doc["params"], filter)
|
42
|
+
end
|
43
|
+
|
44
|
+
protected
|
45
|
+
|
46
|
+
def options_for_params params, filter
|
47
|
+
params.each do |p|
|
48
|
+
next if filter.include? p["name"].to_s or filter.include? p["name"].to_sym
|
49
|
+
if p["expected_type"] == "hash"
|
50
|
+
options_for_params(p["params"], filter)
|
51
|
+
else
|
52
|
+
create_option p
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def create_option param
|
58
|
+
option(
|
59
|
+
option_switches(param),
|
60
|
+
option_type(param),
|
61
|
+
option_desc(param),
|
62
|
+
option_opts(param),
|
63
|
+
&option_formatter(param)
|
64
|
+
)
|
65
|
+
end
|
66
|
+
|
67
|
+
def option_switches param
|
68
|
+
'--' + param["name"].gsub('_', '-')
|
69
|
+
end
|
70
|
+
|
71
|
+
def option_type param
|
72
|
+
param["name"].upcase.gsub('-', '_')
|
73
|
+
end
|
74
|
+
|
75
|
+
def option_desc param
|
76
|
+
desc = param["description"].gsub(/<\/?[^>]+?>/, "")
|
77
|
+
return " " if desc.empty?
|
78
|
+
return desc
|
79
|
+
end
|
80
|
+
|
81
|
+
def option_opts param
|
82
|
+
opts = {}
|
83
|
+
opts[:required] = true if param["required"]
|
84
|
+
return opts
|
85
|
+
end
|
86
|
+
|
87
|
+
def option_formatter param
|
88
|
+
# FIXME: There is a bug in apipie, it does not produce correct expected type for Arrays
|
89
|
+
# When it's fixed, we should test param["expected_type"] == "array"
|
90
|
+
if param["validator"].include? "Array"
|
91
|
+
HammerCLI::OptionFormatters.method(:list)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'hammer_cli/output/dsl'
|
2
|
+
|
3
|
+
module HammerCLI::Apipie
|
4
|
+
|
5
|
+
class ReadCommand < Command
|
6
|
+
|
7
|
+
def self.output definition=nil, &block
|
8
|
+
dsl = HammerCLI::Output::Dsl.new
|
9
|
+
dsl.build &block
|
10
|
+
|
11
|
+
output_definition.append definition.fields unless definition.nil?
|
12
|
+
output_definition.append dsl.fields
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.heading heading=nil
|
16
|
+
@heading = heading if heading
|
17
|
+
@heading
|
18
|
+
end
|
19
|
+
|
20
|
+
def output_definition
|
21
|
+
self.class.output_definition
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.output_definition
|
25
|
+
@output_definition ||= HammerCLI::Output::Definition.new
|
26
|
+
@output_definition
|
27
|
+
end
|
28
|
+
|
29
|
+
def output
|
30
|
+
@output ||= HammerCLI::Output::Output.new :definition => output_definition
|
31
|
+
end
|
32
|
+
|
33
|
+
def execute
|
34
|
+
d = retrieve_data
|
35
|
+
logger.watch "Retrieved data: ", d
|
36
|
+
print_data d
|
37
|
+
return HammerCLI::EX_OK
|
38
|
+
end
|
39
|
+
|
40
|
+
protected
|
41
|
+
def retrieve_data
|
42
|
+
raise "resource or action not defined" unless self.class.resource_defined?
|
43
|
+
resource.send(action, request_params)[0]
|
44
|
+
end
|
45
|
+
|
46
|
+
def print_data(records)
|
47
|
+
output.print_records(records, self.class.heading)
|
48
|
+
end
|
49
|
+
|
50
|
+
def request_params
|
51
|
+
method_options
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module HammerCLI::Apipie
|
2
|
+
module Resource
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.extend(ClassMethods)
|
6
|
+
end
|
7
|
+
|
8
|
+
def resource
|
9
|
+
@resource ||= self.class.resource.new resource_config
|
10
|
+
@resource
|
11
|
+
end
|
12
|
+
|
13
|
+
def action
|
14
|
+
self.class.action
|
15
|
+
end
|
16
|
+
|
17
|
+
def resource_config
|
18
|
+
config = {}
|
19
|
+
config[:base_url] = HammerCLI::Settings[:host]
|
20
|
+
config[:username] = context[:username] || HammerCLI::Settings[:username] || ENV['FOREMAN_USERNAME']
|
21
|
+
config[:password] = context[:password] || HammerCLI::Settings[:password] || ENV['FOREMAN_PASSWORD']
|
22
|
+
config
|
23
|
+
end
|
24
|
+
|
25
|
+
module ClassMethods
|
26
|
+
|
27
|
+
|
28
|
+
def resource resource=nil, action=nil
|
29
|
+
@api_resource = resource unless resource.nil?
|
30
|
+
@api_action = action unless action.nil?
|
31
|
+
return @api_resource if @api_resource
|
32
|
+
return superclass.resource
|
33
|
+
end
|
34
|
+
|
35
|
+
def action action=nil
|
36
|
+
@api_action = action unless action.nil?
|
37
|
+
@api_action
|
38
|
+
end
|
39
|
+
|
40
|
+
def method_doc
|
41
|
+
@api_resource.doc["methods"].each do |method|
|
42
|
+
return method if method["name"] == @api_action.to_s
|
43
|
+
end
|
44
|
+
raise "No method documentation found for #{@api_resource}##{@api_action}"
|
45
|
+
end
|
46
|
+
|
47
|
+
def resource_defined?
|
48
|
+
not (@api_resource.nil? or @api_action.nil?)
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'hammer_cli/messages'
|
2
|
+
|
3
|
+
module HammerCLI::Apipie
|
4
|
+
|
5
|
+
class WriteCommand < Command
|
6
|
+
|
7
|
+
include HammerCLI::Messages
|
8
|
+
|
9
|
+
def execute
|
10
|
+
send_request
|
11
|
+
print_message
|
12
|
+
return HammerCLI::EX_OK
|
13
|
+
end
|
14
|
+
|
15
|
+
protected
|
16
|
+
|
17
|
+
def print_message
|
18
|
+
msg = success_message
|
19
|
+
output.print_message msg unless msg.nil?
|
20
|
+
end
|
21
|
+
|
22
|
+
def send_request
|
23
|
+
raise "resource or action not defined" unless self.class.resource_defined?
|
24
|
+
resource.send(action, request_params)[0]
|
25
|
+
end
|
26
|
+
|
27
|
+
def request_params
|
28
|
+
method_options
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module HammerCLI
|
2
|
+
module Autocompletion
|
3
|
+
|
4
|
+
def autocomplete(line, prefix=[])
|
5
|
+
endings = []
|
6
|
+
formated_prefix = prefix.join(' ')
|
7
|
+
|
8
|
+
if line.length == 0 # look for possible next words
|
9
|
+
all_options = collect_all_options
|
10
|
+
endings = all_options.keys.map { |e| [e, formated_prefix] }
|
11
|
+
elsif line.length == 1 && !(find_subcommand(line[0]) || find_option(line[0])) # look for endings
|
12
|
+
all_options = collect_all_options
|
13
|
+
endings = all_options.select { |k,v| k if k.start_with? line[0] }.keys.map { |e| [e, formated_prefix] }
|
14
|
+
else # dive into subcommands
|
15
|
+
subcommand = find_subcommand line[0]
|
16
|
+
if subcommand
|
17
|
+
command = line.shift
|
18
|
+
prefix << command
|
19
|
+
endings = subcommand.subcommand_class.autocomplete(line, prefix)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
endings
|
23
|
+
end
|
24
|
+
|
25
|
+
def collect_all_options
|
26
|
+
all_options = {}
|
27
|
+
|
28
|
+
if has_subcommands?
|
29
|
+
recognised_subcommands.each do |item|
|
30
|
+
|
31
|
+
label, _ = item.help
|
32
|
+
all_options[label] = item
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
recognised_options.each do |item|
|
37
|
+
label, _ = item.help
|
38
|
+
label.split(',').each do |option|
|
39
|
+
all_options[option.split[0]] = item
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
all_options
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|