hammer_cli 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +13 -5
- data/config/cli_config.template.yml +12 -3
- data/doc/creating_apipie_commands.md +53 -56
- data/doc/creating_commands.md +25 -19
- data/doc/developer_docs.md +3 -2
- data/doc/development_tips.md +3 -3
- data/doc/i18n.md +3 -3
- data/doc/installation.md +24 -207
- data/doc/installation_deb.md +48 -0
- data/doc/installation_gem.md +30 -0
- data/doc/installation_rpm.md +53 -0
- data/doc/installation_source.md +31 -0
- data/doc/option_builders.md +77 -0
- data/doc/option_normalizers.md +5 -5
- data/doc/writing_a_plugin.md +9 -9
- data/lib/hammer_cli.rb +1 -0
- data/lib/hammer_cli/abstract.rb +21 -8
- data/lib/hammer_cli/apipie.rb +1 -2
- data/lib/hammer_cli/apipie/command.rb +37 -64
- data/lib/hammer_cli/apipie/option_builder.rb +69 -0
- data/lib/hammer_cli/apipie/options.rb +1 -61
- data/lib/hammer_cli/clamp.rb +15 -0
- data/lib/hammer_cli/i18n.rb +14 -2
- data/lib/hammer_cli/logger.rb +1 -1
- data/lib/hammer_cli/option_builder.rb +43 -0
- data/lib/hammer_cli/options/option_definition.rb +6 -0
- data/lib/hammer_cli/output/adapter/abstract.rb +2 -2
- data/lib/hammer_cli/output/adapter/base.rb +6 -3
- data/lib/hammer_cli/output/adapter/table.rb +19 -2
- data/lib/hammer_cli/output/definition.rb +4 -0
- data/lib/hammer_cli/output/fields.rb +9 -0
- data/lib/hammer_cli/output/formatters.rb +20 -8
- data/lib/hammer_cli/utils.rb +1 -1
- data/lib/hammer_cli/version.rb +1 -1
- data/locale/hammer-cli.pot +103 -79
- data/test/unit/abstract_test.rb +62 -0
- data/test/unit/apipie/command_test.rb +12 -197
- data/test/unit/apipie/option_builder_test.rb +110 -0
- data/test/unit/i18n_test.rb +50 -0
- data/test/unit/option_builder_test.rb +33 -0
- data/test/unit/output/adapter/abstract_test.rb +26 -3
- data/test/unit/output/adapter/base_test.rb +20 -3
- data/test/unit/output/adapter/csv_test.rb +2 -2
- data/test/unit/output/adapter/table_test.rb +24 -1
- data/test/unit/output/definition_test.rb +13 -0
- data/test/unit/output/formatters_test.rb +17 -1
- data/test/unit/utils_test.rb +1 -1
- metadata +101 -88
- data/lib/hammer_cli/apipie/read_command.rb +0 -41
- data/lib/hammer_cli/apipie/write_command.rb +0 -49
- data/test/unit/apipie/read_command_test.rb +0 -37
- data/test/unit/apipie/write_command_test.rb +0 -41
@@ -0,0 +1,31 @@
|
|
1
|
+
### Installation from SOURCE
|
2
|
+
|
3
|
+
If you can install hammer from git checkouts, you will just need `rake` installed on your system.
|
4
|
+
|
5
|
+
#### Step 1: clone the sources
|
6
|
+
Clone and install CLI core
|
7
|
+
|
8
|
+
```bash
|
9
|
+
$ git clone https://github.com/theforeman/hammer-cli.git
|
10
|
+
$ cd hammer-cli
|
11
|
+
$ rake install
|
12
|
+
$ cd ..
|
13
|
+
```
|
14
|
+
|
15
|
+
clone plugin with foreman commands
|
16
|
+
|
17
|
+
```bash
|
18
|
+
$ git clone https://github.com/theforeman/hammer-cli-foreman.git
|
19
|
+
$ cd hammer-cli-foreman
|
20
|
+
$ rake install
|
21
|
+
$ cd ..
|
22
|
+
```
|
23
|
+
|
24
|
+
and optionally other plugins.
|
25
|
+
|
26
|
+
|
27
|
+
#### Step 2: enable and configure the plugins
|
28
|
+
You'll have to copy configuration files to proper locations manually.
|
29
|
+
Please check our [configuration instructions](installation.md#configuration)
|
30
|
+
and see how to proceed.
|
31
|
+
|
@@ -0,0 +1,77 @@
|
|
1
|
+
Option builders
|
2
|
+
---------------
|
3
|
+
|
4
|
+
Option builders offer a mechanism for dynamic option creation.
|
5
|
+
This is useful in situations when there's a common pattern or options
|
6
|
+
can be created automatically based on some data.
|
7
|
+
|
8
|
+
The [builder's interface](https://github.com/theforeman/hammer-cli/blob/master/lib/hammer_cli/option_builder.rb)
|
9
|
+
is very simple. It has to define the only method `build(builder_params={})` that returns
|
10
|
+
array of `HammerCLI::Options::OptionDefinition` instances.
|
11
|
+
|
12
|
+
Example of a simple option builder follows:
|
13
|
+
```ruby
|
14
|
+
class DimensionsOptionBuilder < AbstractOptionBuilder
|
15
|
+
|
16
|
+
def initialize(option_names)
|
17
|
+
@option_names = option_names
|
18
|
+
end
|
19
|
+
|
20
|
+
def build(builder_params={})
|
21
|
+
opts = []
|
22
|
+
@option_names.each do |name|
|
23
|
+
opts << option("--#{name}-height", "#{name.upcase}_HEIGHT", "Height of the #{name}")
|
24
|
+
opts << option("--#{name}-width", "#{name.upcase}_WIDTH", "Width of the #{name}")
|
25
|
+
end
|
26
|
+
opts
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
```
|
31
|
+
|
32
|
+
Each command class has an option builder defined in method `option_builder`.
|
33
|
+
It is executed by calling class method `build_options`.
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
class HouseCommand < HammerCLI::AbstractCommand
|
37
|
+
|
38
|
+
# define your own option builder
|
39
|
+
def self.option_builder
|
40
|
+
DimensionsOptionBuilder.new(["door", "window"])
|
41
|
+
end
|
42
|
+
|
43
|
+
# create the options at class level
|
44
|
+
build_options
|
45
|
+
end
|
46
|
+
```
|
47
|
+
|
48
|
+
```
|
49
|
+
hammer house -h
|
50
|
+
Usage:
|
51
|
+
hammer house [OPTIONS]
|
52
|
+
|
53
|
+
--door-height DOOR_HEIGHT Height of the door
|
54
|
+
--door-width DOOR_WIDTH Width of the door
|
55
|
+
--window-height WINDOW_HEIGHT Height of the window
|
56
|
+
--window-width WINDOW_WIDTH Width of the window
|
57
|
+
-h, --help print help
|
58
|
+
```
|
59
|
+
|
60
|
+
The default option builder is `OptionBuilderContainer` that
|
61
|
+
is useful for chaining multiple builders. Command's class
|
62
|
+
method `custom_option_builders` is there exactly for this reason. It's output
|
63
|
+
is passed to the container and can be used for defining custom builders.
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
def self.custom_option_builders
|
67
|
+
[
|
68
|
+
DimensionsOptionBuilder.new(["door", "window"]),
|
69
|
+
IdentifierOptionBuilder.new,
|
70
|
+
SomeOtherCoolBuilder.new
|
71
|
+
]
|
72
|
+
end
|
73
|
+
```
|
74
|
+
|
75
|
+
If an option with the same `--flag` is already defined (either statically or from another builder)
|
76
|
+
any other option with the same flag is ignored.
|
77
|
+
|
data/doc/option_normalizers.md
CHANGED
@@ -15,7 +15,7 @@ option "--enabled", "ENABLED", "Should the host be enabled?",
|
|
15
15
|
```
|
16
16
|
|
17
17
|
#### Description of valid values
|
18
|
-
There is method `description` that should return a help string. It's value is used in output of `-h`,
|
18
|
+
There is a method `description` that should return a help string. It's value is used in the output of `-h`,
|
19
19
|
which can then look for example like this:
|
20
20
|
|
21
21
|
```
|
@@ -23,15 +23,15 @@ which can then look for example like this:
|
|
23
23
|
One of true/false, yes/no, 1/0.
|
24
24
|
```
|
25
25
|
|
26
|
-
Abstract normalizer returns empty string by default.
|
26
|
+
Abstract normalizer returns an empty string by default.
|
27
27
|
|
28
28
|
|
29
29
|
#### Check and format passed values
|
30
30
|
|
31
31
|
Normalizer's method `format` is used for such checks. The method behaves as a filter taking
|
32
|
-
string value as it's input and returning value of any type.
|
32
|
+
a string value as it's input and returning a value of any type.
|
33
33
|
|
34
|
-
If the value is not valid `ArgumentError` with an appropriate message should be risen.
|
34
|
+
If the value is not valid, `ArgumentError` with an appropriate message should be risen.
|
35
35
|
Implementation in `Bool` normalizer is a good example of such functionality:
|
36
36
|
|
37
37
|
```ruby
|
@@ -50,7 +50,7 @@ end
|
|
50
50
|
|
51
51
|
#### Value completion
|
52
52
|
|
53
|
-
Normalizers can also provide completion of option values via method `complete`. It takes one argument - current option value at the time the completion was requested. In the simplest cases the method returns array of all possible values. More complex completions can use the current value argument for building the return values.
|
53
|
+
Normalizers can also provide completion of option values via method `complete`. It takes one argument - the current option value at the time the completion was requested. In the simplest cases the method returns array of all possible values. More complex completions can use the current value argument for building the return values.
|
54
54
|
|
55
55
|
We distinguish two types of offered completion strings:
|
56
56
|
|
data/doc/writing_a_plugin.md
CHANGED
@@ -4,7 +4,7 @@ Writing your own Hammer plugin
|
|
4
4
|
In this tutorial we will create a simple hello world plugin.
|
5
5
|
|
6
6
|
Hammer plugins are nothing but gems. Details on how to build a gem can be found for example at [rubygems.org](http://guides.rubygems.org/make-your-own-gem/).
|
7
|
-
In the first part of this tutorial we will briefly guide you through the process of creating a very simple gem. First of all you will need rubygems package installed on your system.
|
7
|
+
In the first part of this tutorial we will briefly guide you through the process of creating a very simple gem. First of all you will need the rubygems package installed on your system.
|
8
8
|
|
9
9
|
Create the basic gem structure in a project subdirectory of your choice:
|
10
10
|
```
|
@@ -44,7 +44,7 @@ end
|
|
44
44
|
```
|
45
45
|
More details about the gemspec structure is again at [rubygems.org](http://guides.rubygems.org/specification-reference/).
|
46
46
|
|
47
|
-
We'll have to specify the
|
47
|
+
We'll have to specify the plugin version in `lib/hammer_cli_hello/version.rb`:
|
48
48
|
```ruby
|
49
49
|
module HammerCLIHello
|
50
50
|
def self.version
|
@@ -59,12 +59,10 @@ $ gem build ./hammer_cli_hello.gemspec
|
|
59
59
|
$ gem install hammer_cli_hello-0.0.1.gem
|
60
60
|
```
|
61
61
|
|
62
|
-
|
62
|
+
Place your module's config file into `~/.hammer/cli.modules.d/`.
|
63
63
|
```yaml
|
64
|
-
:
|
65
|
-
|
66
|
-
# - hammer_cli_foreman
|
67
|
-
# - hammer_cli_katello_bridge
|
64
|
+
:hello:
|
65
|
+
:enable_module: true
|
68
66
|
```
|
69
67
|
|
70
68
|
|
@@ -75,12 +73,14 @@ $ hammer -v > /dev/null
|
|
75
73
|
|
76
74
|
You should see a message saying that your module was loaded (second line in the sample output).
|
77
75
|
```
|
78
|
-
[ INFO 2013-10-16 11:19:06 Init]
|
76
|
+
[ INFO 2013-10-16 11:19:06 Init] Initialization of Hammer CLI (0.1.0) has started...
|
79
77
|
[ INFO 2013-10-16 11:19:06 Init] Extension module hammer_cli_hello loaded
|
78
|
+
[ INFO 2013-10-16 11:19:06 Init] Configuration from the file /root/.hammer/cli_config.yml has been loaded
|
79
|
+
[ INFO 2013-10-16 11:19:06 Init] Configuration from the file /root/.hammer/cli.modiles.d/hello.yml has been loaded
|
80
80
|
[ INFO 2013-10-16 11:19:06 HammerCLI::MainCommand] Called with options: {"verbose"=>true}
|
81
81
|
```
|
82
82
|
|
83
|
-
Done. Your first hammer plugin is installed.
|
83
|
+
Done. Your first hammer plugin is installed. Unfortunately it does not contain any commands yet. So let's start adding some to finally enjoy real results.
|
84
84
|
|
85
85
|
Optionally you can add a Rakefile and build and install the gem with `rake install`
|
86
86
|
```ruby
|
data/lib/hammer_cli.rb
CHANGED
data/lib/hammer_cli/abstract.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'hammer_cli/exception_handler'
|
2
2
|
require 'hammer_cli/logger_watch'
|
3
3
|
require 'hammer_cli/options/option_definition'
|
4
|
-
require 'clamp'
|
4
|
+
require 'hammer_cli/clamp'
|
5
5
|
require 'logging'
|
6
6
|
|
7
7
|
module HammerCLI
|
@@ -134,6 +134,26 @@ module HammerCLI
|
|
134
134
|
HammerCLI.interactive?
|
135
135
|
end
|
136
136
|
|
137
|
+
def self.option_builder
|
138
|
+
@option_builder ||= OptionBuilderContainer.new
|
139
|
+
@option_builder.builders = custom_option_builders
|
140
|
+
@option_builder
|
141
|
+
end
|
142
|
+
|
143
|
+
def self.custom_option_builders
|
144
|
+
[]
|
145
|
+
end
|
146
|
+
|
147
|
+
def self.build_options(builder_params={})
|
148
|
+
option_builder.build(builder_params).each do |option|
|
149
|
+
# skip switches that are already defined
|
150
|
+
next if option.nil? or option.switches.any? {|s| find_option(s) }
|
151
|
+
|
152
|
+
declared_options << option
|
153
|
+
block ||= option.default_conversion_block
|
154
|
+
define_accessors_for(option, &block)
|
155
|
+
end
|
156
|
+
end
|
137
157
|
|
138
158
|
protected
|
139
159
|
|
@@ -207,16 +227,9 @@ module HammerCLI
|
|
207
227
|
end
|
208
228
|
|
209
229
|
def self.option(switches, type, description, opts = {}, &block)
|
210
|
-
formatter = opts.delete(:format)
|
211
|
-
context_target = opts.delete(:context_target)
|
212
|
-
|
213
230
|
HammerCLI::Options::OptionDefinition.new(switches, type, description, opts).tap do |option|
|
214
231
|
declared_options << option
|
215
|
-
|
216
|
-
option.value_formatter = formatter
|
217
|
-
option.context_target = context_target
|
218
232
|
block ||= option.default_conversion_block
|
219
|
-
|
220
233
|
define_accessors_for(option, &block)
|
221
234
|
end
|
222
235
|
end
|
data/lib/hammer_cli/apipie.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require File.join(File.dirname(__FILE__), '../abstract')
|
2
|
+
require File.join(File.dirname(__FILE__), '../messages')
|
2
3
|
require File.join(File.dirname(__FILE__), 'options')
|
3
4
|
require File.join(File.dirname(__FILE__), 'resource')
|
4
5
|
|
@@ -8,24 +9,7 @@ module HammerCLI::Apipie
|
|
8
9
|
|
9
10
|
include HammerCLI::Apipie::Resource
|
10
11
|
include HammerCLI::Apipie::Options
|
11
|
-
|
12
|
-
def self.identifiers(*keys)
|
13
|
-
@identifiers ||= {}
|
14
|
-
keys.each do |key|
|
15
|
-
if key.is_a? Hash
|
16
|
-
@identifiers.merge!(key)
|
17
|
-
else
|
18
|
-
@identifiers.update(key => HammerCLI.option_accessor_name(key))
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
def validate_options
|
24
|
-
super
|
25
|
-
if self.class.declared_identifiers
|
26
|
-
validator.any(*self.class.declared_identifiers.values).required
|
27
|
-
end
|
28
|
-
end
|
12
|
+
include HammerCLI::Messages
|
29
13
|
|
30
14
|
def self.desc(desc=nil)
|
31
15
|
super(desc) || resource.action(action).apidoc[:apis][0][:short_description] || " "
|
@@ -33,67 +17,56 @@ module HammerCLI::Apipie
|
|
33
17
|
" "
|
34
18
|
end
|
35
19
|
|
36
|
-
|
20
|
+
def self.custom_option_builders
|
21
|
+
builders = super
|
22
|
+
builders += [
|
23
|
+
OptionBuilder.new(resource.action(action), :require_options => false)
|
24
|
+
] if resource_defined?
|
25
|
+
builders
|
26
|
+
end
|
37
27
|
|
38
|
-
def
|
39
|
-
self.
|
40
|
-
value = find_option("--"+identifier.to_s).of(self).read
|
41
|
-
return [value, identifier] if value
|
42
|
-
end
|
43
|
-
[nil, nil]
|
28
|
+
def self.apipie_options(*args)
|
29
|
+
self.build_options(*args)
|
44
30
|
end
|
45
31
|
|
46
|
-
def
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
return true if superclass.respond_to?(:identifier?, true) and superclass.identifier?(key)
|
51
|
-
end
|
52
|
-
return false
|
32
|
+
def execute
|
33
|
+
d = send_request
|
34
|
+
print_data(d)
|
35
|
+
return HammerCLI::EX_OK
|
53
36
|
end
|
54
37
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
38
|
+
protected
|
39
|
+
|
40
|
+
def send_request
|
41
|
+
if resource && resource.has_action?(action)
|
42
|
+
resource.call(action, request_params, request_headers)
|
60
43
|
else
|
61
|
-
|
44
|
+
raise HammerCLI::OperationNotSupportedError, "The server does not support such operation."
|
62
45
|
end
|
63
46
|
end
|
64
47
|
|
65
|
-
|
66
|
-
|
67
|
-
def self.setup_identifier_options
|
68
|
-
identifier_option(:id, _("resource id"), declared_identifiers[:id]) if identifier? :id
|
69
|
-
identifier_option(:name, _("resource name"), declared_identifiers[:name]) if identifier? :name
|
70
|
-
identifier_option(:label, _("resource label"), declared_identifiers[:label]) if identifier? :label
|
48
|
+
def request_headers
|
49
|
+
{}
|
71
50
|
end
|
72
51
|
|
73
|
-
def
|
74
|
-
|
52
|
+
def request_params
|
53
|
+
method_options
|
54
|
+
end
|
75
55
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
option option_switch, name.to_s.upcase, desc, :attribute_name => attr_name do |value|
|
80
|
-
name_to_id(value, name, resource)
|
81
|
-
end
|
82
|
-
end
|
56
|
+
def print_data(data)
|
57
|
+
print_collection(output_definition, data) unless output_definition.empty?
|
58
|
+
print_success_message(data) unless success_message.nil?
|
83
59
|
end
|
84
60
|
|
85
|
-
def
|
86
|
-
|
87
|
-
|
61
|
+
def success_message_params(response)
|
62
|
+
response
|
63
|
+
end
|
88
64
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
raise _("%{resource} with %{option} '%{value}' not found") % msg_opts if results.empty?
|
95
|
-
raise _("%{resource} with %{option} '%{value}' found more than once") % msg_opts if results.count > 1
|
96
|
-
results[0]['id']
|
65
|
+
def print_success_message(response)
|
66
|
+
print_message(
|
67
|
+
success_message,
|
68
|
+
success_message_params(response)
|
69
|
+
)
|
97
70
|
end
|
98
71
|
|
99
72
|
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
|
2
|
+
module HammerCLI::Apipie
|
3
|
+
|
4
|
+
class OptionBuilder
|
5
|
+
|
6
|
+
def initialize(action, options={})
|
7
|
+
@action = action
|
8
|
+
@require_options = options[:require_options].nil? ? true : options[:require_options]
|
9
|
+
end
|
10
|
+
|
11
|
+
def build(builder_params={})
|
12
|
+
filter = Array(builder_params[:without])
|
13
|
+
|
14
|
+
options_for_params(@action.params, filter)
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_writer :require_options
|
18
|
+
def require_options?
|
19
|
+
@require_options
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
|
24
|
+
def options_for_params(params, filter)
|
25
|
+
opts = []
|
26
|
+
params.each do |p|
|
27
|
+
next if filter.include?(p.name) || filter.include?(p.name.to_sym)
|
28
|
+
if p.expected_type == :hash
|
29
|
+
opts += options_for_params(p.params, filter)
|
30
|
+
else
|
31
|
+
opts << create_option(p)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
opts
|
35
|
+
end
|
36
|
+
|
37
|
+
def create_option(param)
|
38
|
+
HammerCLI::Options::OptionDefinition.new(
|
39
|
+
option_switch(param),
|
40
|
+
option_type(param),
|
41
|
+
option_desc(param),
|
42
|
+
option_opts(param)
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
def option_switch(param)
|
47
|
+
'--' + param.name.gsub('_', '-')
|
48
|
+
end
|
49
|
+
|
50
|
+
def option_type(param)
|
51
|
+
param.name.upcase.gsub('-', '_')
|
52
|
+
end
|
53
|
+
|
54
|
+
def option_desc(param)
|
55
|
+
param.description || " "
|
56
|
+
end
|
57
|
+
|
58
|
+
def option_opts(param)
|
59
|
+
opts = {}
|
60
|
+
opts[:required] = true if (param.required? and require_options?)
|
61
|
+
# FIXME: There is a bug in apipie, it does not produce correct expected type for Arrays
|
62
|
+
# When it's fixed, we should test param["expected_type"] == "array"
|
63
|
+
opts[:format] = HammerCLI::Options::Normalizers::List.new if param.validator.include? "Array"
|
64
|
+
return opts
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|