kknife 0.1.1

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.
@@ -0,0 +1,210 @@
1
+ # Author: Matt Hoyle (<matt@deployable.co>)
2
+ # Copyright: Copyright (c) 2013 Deployable Ltd.
3
+ # License: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ require 'kknife/dbg'
18
+ require 'kknife/lookup'
19
+ require 'chef/knife'
20
+ require 'chef/application/knife'
21
+
22
+ ### Knifecmd
23
+ # Used as the base for the knife command.
24
+ # Stories information about the run and the
25
+ # Command tree
26
+
27
+ class Knifecmd
28
+
29
+ include Dbg
30
+
31
+ attr_accessor :shortcuts, :ambiguities, :commands, :cmd_found, :cmd_left, :knife_commands
32
+
33
+ def initialize( argopts = {} )
34
+
35
+ # Root command is k.. or knife
36
+ @root = 'knife'
37
+
38
+ opts = {
39
+ :commands => ARGV.dup.freeze,
40
+ :cmd_found => [],
41
+ :cmd_left => ARGV.dup,
42
+ :shortcuts => {},
43
+ :ambiguities => {},
44
+ :config_build => '~/.chef/.kknife.built.config',
45
+ :config_user => '~/.chef/.kknife.user.config',
46
+ }.merge argopts
47
+
48
+ @commands = opts[:commands]
49
+ @cmd_found = opts[:cmd_found]
50
+ @cmd_left = opts[:cmd_left]
51
+ @shortcuts = opts[:shortcuts]
52
+ @ambiguities = opts[:ambiguities]
53
+ @config_build = File.expand_path opts[:config_build]
54
+ @config_user = File.expand_path opts[:config_user]
55
+
56
+ @cmd_root = Command.new( @root, :controller => self )
57
+
58
+ # Instead of trawling for chef files each time we can load a previous config
59
+ lookup_files unless lookup_config
60
+
61
+ # Now process the knife commands into a tree of Command
62
+ load_commands
63
+
64
+ # Then setup to find commands
65
+ reset_found
66
+ end
67
+
68
+
69
+ # Look up the knife commands from a prebuilt kknife config
70
+ def lookup_config
71
+ # pull in a config if it exists
72
+ return false unless File.exists? @config_build
73
+
74
+ @knife_commands = JSON.parse File.open( @config_build, 'r' ){|f| f.read }
75
+ end
76
+
77
+
78
+ # Look up the knife commands from knife library files
79
+ def lookup_files
80
+ # pull in the standard knife files
81
+ Chef::Knife.load_commands
82
+ @knife_commands = Chef::Knife.subcommands_by_category
83
+ end
84
+
85
+
86
+ # Write out a built config file
87
+ def write_config
88
+ raise "No directory [#{File.dirname( @config_build )}]" unless Dir.exists? File.dirname( @config_build )
89
+ File.open( @config_build, 'w' ){|f| f.write @knife_commands.to_json }
90
+ end
91
+
92
+
93
+ # Remove a built config file
94
+ def clear_config
95
+ FileUtils.rm @config_build if File.exists? @config_build
96
+ end
97
+
98
+
99
+ # Turn the knife command arrays into a Command tree
100
+ def load_commands
101
+ # loop over the commands, put them into Command
102
+ @knife_commands.each do |category,command_arr|
103
+ dbg 'category', category
104
+ command_arr.each do |command|
105
+ dbg 'command', command
106
+ commands = command.split( /_/ )
107
+ @cmd_root.add_command commands
108
+ end
109
+ end
110
+ end
111
+
112
+
113
+ # Start with out root Command and print the command tree
114
+ def print_tree
115
+ @cmd_root.pp
116
+ end
117
+ def print
118
+ @cmd_root.pp_single
119
+ end
120
+
121
+ # Start at the root command and go down the tree
122
+ # looking for each command
123
+ def lookup( commands = @commands )
124
+ dbg 'lookup', commands
125
+ reset_found commands
126
+
127
+ begin
128
+ @cmd_root.process_lookup commands
129
+
130
+ rescue Command::AmbiguousCommand => e
131
+ raise "error [#{commands.to_s}] #{e}"
132
+
133
+ rescue Command::NotFoundCommand => e
134
+ raise "error [#{commands.to_s}] #{e}"
135
+
136
+ rescue Command::NoMoreSubCommands
137
+ dbg "end of knife lookups", @cmd_found.join(','), @cmd_left.join(',')
138
+
139
+ rescue Command::NoMoreSuppliedCommands
140
+ dbg "end of argv lookups", @cmd_found.join(','), @cmd_left.join(',')
141
+
142
+ end
143
+
144
+ @cmd_found
145
+ end
146
+
147
+
148
+ # if the Command lookup needs to split a command
149
+ # remove the command from what's left and split it
150
+ # into the componenets
151
+ def cmd_split( split_command_array )
152
+ # first remove the previous entry with _
153
+ @cmd_left.shift
154
+
155
+ # then prefix the new split entires
156
+ @cmd_left.unshift *split_command_array
157
+ end
158
+
159
+
160
+ # if the Command lookup find a command, add it to found
161
+ # and take it away from whats left
162
+ def found_cmd( command )
163
+ dbg 'found_cmd b4 ', command, @cmd_found.join(','), '|', @cmd_left.join(',')
164
+ @cmd_found.push command
165
+ @cmd_left.shift
166
+ dbg 'found_cmd aft', command, @cmd_found.join(','), '|', @cmd_left.join(',')
167
+ end
168
+
169
+
170
+ # if the Command lookup hits a shortcut, adjust local
171
+ # variables to match
172
+ def found_shortcut( shortcut )
173
+ dbg 'found_shortcut b4 ', shortcut.join(','), '|', @cmd_left.join(',')
174
+ @cmd_left.shift
175
+ @cmd_left.unshift *shortcut
176
+ dbg 'found_shortcut aft', shortcut.join(','), '|', @cmd_left.join(',')
177
+ end
178
+
179
+
180
+ # reset the command found instance variables
181
+ def reset_found( commands = [] )
182
+ @cmd_found = []
183
+ @cmd_left = commands.dup
184
+ end
185
+
186
+
187
+ # return the found command as a space seperated string
188
+ def found_string
189
+ ([ @root ] + @cmd_found ).join(' ')
190
+ end
191
+
192
+
193
+ # resolve a list of commands into real commands
194
+ def resolve( commands )
195
+ lookup commands
196
+ @cmd_found + @cmd_left
197
+ end
198
+
199
+ # Run the knife command
200
+ def run( commands = ARGV )
201
+
202
+ # mess with argv
203
+ ARGV.replace resolve( commands )
204
+
205
+ # Run knife directly
206
+ Chef::Application::Knife.new.run
207
+
208
+ end
209
+
210
+ end
data/lib/kknife/log.rb ADDED
@@ -0,0 +1,56 @@
1
+ # Author: Matt Hoyle (<matt@deployable.co>)
2
+ # Copyright: Copyright (c) 2013 Deployable Ltd.
3
+ # License: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ require 'logger'
18
+
19
+ ### Log - a global logger instance for classes
20
+
21
+ # Allows you to call a single logger instance easily from your classes
22
+
23
+ # ```
24
+ # class Yours
25
+ # include Log
26
+ # def method
27
+ # log.info 'something'
28
+ # end
29
+ # end
30
+ # ```
31
+
32
+ module Log
33
+
34
+ DefaultIO = STDOUT
35
+
36
+ def log
37
+ Logging.log
38
+ end
39
+
40
+ def self.replace( io )
41
+ l = Logger.new io
42
+ l.level = @log.level
43
+ @log = l
44
+ end
45
+
46
+ def self.create
47
+ l = Logger.new DefaultIO
48
+ l.level = Logger::INFO
49
+ l
50
+ end
51
+
52
+ def self.log
53
+ @log ||= create
54
+ end
55
+
56
+ end
@@ -0,0 +1,50 @@
1
+ # Author: Matt Hoyle (<matt@deployable.co>)
2
+ # Copyright: Copyright (c) 2013 Deployable Ltd.
3
+ # License: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ ### Lookup
18
+
19
+ # Provides some lookups commands not based on knife commands
20
+ # Ambiguities are helpers in the case of an ambiguous "find"
21
+ # Shortcuts will be used if all other command lookups fail
22
+
23
+ module Lookup
24
+
25
+ Shortcuts = {
26
+ 'ff' => [ 'from', 'file' ],
27
+ 'db' => [ 'data', 'bag' ],
28
+ 'cs' => [ 'cookbook', 'site' ],
29
+ 'bd' => [ 'bulk', 'delete' ],
30
+ 'ne' => [ 'node', 'edit' ],
31
+ 'ns' => [ 'node', 'show' ],
32
+ 'rl' => [ 'run', 'list' ],
33
+ }
34
+
35
+ Ambiguities = {
36
+ 'd' => ['download'],
37
+ 'e' => ['environment'],
38
+ 'r' => ['role'],
39
+ 'c' => ['cookbook'],
40
+ }
41
+
42
+ def self.shortcut( key )
43
+ Shortcuts[key] if Shortcuts.has_key? key
44
+ end
45
+
46
+ def self.ambiguity( key )
47
+ Ambiguities[key] if Ambiguities.has_key? key
48
+ end
49
+
50
+ end
@@ -0,0 +1,3 @@
1
+ module Kknife
2
+ VERSION = '0.1.1'
3
+ end
data/lib/kknife.rb ADDED
@@ -0,0 +1,71 @@
1
+ # Author: Matt Hoyle (<matt@deployable.co>)
2
+ # Copyright: Copyright (c) 2013 Deployable Ltd.
3
+ # License: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ require 'kknife/command'
18
+ require 'kknife/knifecmd'
19
+ require 'kknife/dbg'
20
+ require 'trollop'
21
+
22
+
23
+ class Kknife
24
+
25
+ include Dbg
26
+
27
+ def run( commands = ARGV )
28
+
29
+ # Trollop argv parsing
30
+ opts = Trollop::options do
31
+ version "kknife 0.1.1 (c) 2013 Matt Hoyle. Deployable Ltd."
32
+ banner <<-EOS
33
+ kknife - chef knife command shortcuts
34
+
35
+ Usage: k [options] <knife sub commands>
36
+
37
+ Where [options] are:
38
+ EOS
39
+ #opt :config, "User command shortcuts and ambiguities"
40
+ opt :test, "Test lookup, don't execute knife"
41
+ opt :debug, "Turn on debug output"
42
+ opt :list, "List commands"
43
+ stop_on_unknown()
44
+ end
45
+
46
+ debug_logger.level = Logger::DEBUG if opts[:debug]
47
+
48
+ # Create the knife controller
49
+ k = Knifecmd.new( :argv => ARGV )
50
+
51
+ # Do the optional bits
52
+ #k.user_config if opts[:config]
53
+ k.print if opts[:debug]
54
+
55
+ # Then run what we've been asked to
56
+ if opts[:list]
57
+ # Print the list of commands
58
+ k.print unless opts[:debug]
59
+
60
+ elsif opts[:test]
61
+ # Don't run knife, just print what would have been run
62
+ printf "%s\n", k.resolve( ARGV ).join(' ')
63
+
64
+ else
65
+ # Otherwise run knife directly with the lookedup ARGV
66
+ k.run
67
+ end
68
+
69
+ end
70
+
71
+ end
@@ -0,0 +1,26 @@
1
+ # Author: Matt Hoyle (<matt@deployable.co>)
2
+ # Copyright: Copyright (c) 2013 Deployable Ltd.
3
+ # License: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ require 'spec_helper'
18
+
19
+ describe Command do
20
+
21
+ before :each do
22
+ @c = Command.new
23
+ end
24
+
25
+
26
+ end
data/spec/helpers.rb ADDED
@@ -0,0 +1,82 @@
1
+ # Author: Matt Hoyle (<matt@deployable.co>)
2
+ # Copyright: Copyright (c) 2013 Deployable Ltd.
3
+ # License: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ module Helpers
18
+
19
+ # path current executing ruby bin
20
+ RUBY = File.join(
21
+ RbConfig::CONFIG['bindir'],
22
+ RbConfig::CONFIG['ruby_install_name']
23
+ ).sub(/.*\s.*/m, '"\&"')
24
+
25
+
26
+ def self.runner( command )
27
+ i,o,e,t = Open3.popen3( "#{command}" )
28
+ i.close
29
+ out = o.read
30
+ err = e.read
31
+ return [out,err,t]
32
+ end
33
+
34
+ def self.krunner( commands )
35
+ lib = File.expand_path( '../lib', File.dirname(__FILE__) )
36
+ bin = File.expand_path( '../bin/k', File.dirname(__FILE__) )
37
+ arg = commands.join(' ')
38
+ runner "#{RUBY} -I#{lib} #{bin} #{arg}"
39
+ end
40
+
41
+
42
+ require 'rest_client'
43
+ require 'chef_zero/server'
44
+
45
+ def self.create_chef_server( host = 'localhost', port = 80, env = 'test', node = 'nodeo' )
46
+
47
+ server = ChefZero::Server.new( host: 'localhost', port: 4000 )
48
+ scheme = 'http'
49
+ uri = "#{scheme}://#{host}:#{port}"
50
+ server.start_background
51
+
52
+ env_obj = {
53
+ :name => env,
54
+ :description => "descenv",
55
+ :cookbook_versions => {},
56
+ :json_class => "Chef::Environment",
57
+ :chef_type => "environment",
58
+ :default_attributes => {},
59
+ :override_attributes => {},
60
+ }
61
+
62
+ er = RestClient.post "#{uri}/environments",
63
+ env_obj.to_json, :content_type => :json, :accept => :json
64
+ puts er.code unless er.code == 200 or er.code == 201
65
+
66
+ node_obj = {
67
+ :chef_type => "node",
68
+ :json_class => "Chef::Node",
69
+ :name => node,
70
+ :chef_environment => env,
71
+ :run_list => []
72
+ }
73
+ #printf "%s %s\n", r.code, r.to_str
74
+
75
+ nr = RestClient.post "#{uri}/nodes",
76
+ node_obj.to_json, :content_type => :json, :accept => :json
77
+ puts nr.code unless nr.code == 200 or nr.code == 201
78
+
79
+ return server
80
+ end
81
+
82
+ end
data/spec/k_spec.rb ADDED
@@ -0,0 +1,116 @@
1
+ # Author: Matt Hoyle (<matt@deployable.co>)
2
+ # Copyright: Copyright (c) 2013 Deployable Ltd.
3
+ # License: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ require 'spec_helper'
18
+ require './spec/helpers'
19
+
20
+ RSpec.configure do |c|
21
+ c.include Helpers
22
+ end
23
+
24
+
25
+ describe "k command" do
26
+
27
+
28
+
29
+ ListString = 'knife user show'
30
+ Helpers.create_chef_server( 'localhost', 4000 )
31
+
32
+
33
+ it "outputs debug with -d" do
34
+ ARGV.replace %w( -d list )
35
+
36
+ Chef::Application::Knife.any_instance.stub(:run).and_return( true )
37
+
38
+ # replace the debug STDERR logger with our testable
39
+ output = StringIO.new
40
+ Dbg.replace output
41
+
42
+ expect { Kknife.new.run }.to match_stdout( ListString )
43
+ expect( output.string ).to match( /end of knife lookups \[list\] \[\]/ )
44
+
45
+ end
46
+
47
+
48
+ it "lists command with -l" do
49
+ ARGV.replace %w( -l )
50
+
51
+ expect { Kknife.new.run }.to match_stdout( ListString )
52
+ end
53
+
54
+
55
+ it "only dumps command when using test option" do
56
+ ARGV.replace %w( -t node list )
57
+
58
+ expect { Kknife.new.run }.to stdout( "node list\n" )
59
+ end
60
+
61
+
62
+ it "display the options in help" do
63
+ ARGV.replace %w( -h )
64
+
65
+ expect{
66
+ begin
67
+ Kknife.new.run
68
+ rescue SystemExit
69
+ end
70
+ }.to match_stdout( /-t.*-d.*-l/m )
71
+
72
+ end
73
+
74
+
75
+ it "display correct version with -v " do
76
+
77
+ ARGV.replace %w( -v )
78
+
79
+ expect{
80
+ begin
81
+ Kknife.new.run
82
+ rescue SystemExit
83
+ end
84
+ }.to stdout( "kknife 0.1.1 (c) 2013 Matt Hoyle. Deployable Ltd.\n" )
85
+
86
+ end
87
+
88
+
89
+ it "runs a knife command correctly" do
90
+
91
+ o,e,t = Helpers.krunner %w( environment show test )
92
+
93
+ expect( o ).to match( /^name:\s+test$/ )
94
+
95
+ end
96
+
97
+
98
+ it "passes options onto knife correctly" do
99
+
100
+ o,e,t = Helpers.krunner %w( environment show test -F json )
101
+
102
+ expect( o ).to match( /"name": "test"/ )
103
+
104
+ end
105
+
106
+
107
+ it "runs a knife command search correctly" do
108
+
109
+ o,e,t = Helpers.krunner %w( n s nodeo )
110
+
111
+ expect( o ).to match( /Node Name: nodeo/ )
112
+ expect( o ).to match( /Environment: test/ )
113
+ end
114
+
115
+
116
+ end