kknife 0.1.1

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