fingerpuppet 0.0.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.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2013 Puppet Labs, info@puppetlabs.com
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,56 @@
1
+ Usage : fingerpuppet <commandstring>
2
+
3
+ Steps for using the API:
4
+ 1:) fingerpuppet --init --certname my.cert.name --server my.server.name
5
+ Builds the config file
6
+ Generates the certificate and CSR
7
+ Submits the CSR to the Puppetmaster
8
+
9
+ 2:) On puppetmaster: puppet cert sign my.cert.name
10
+
11
+ 3:) restapi.rb --install
12
+ Downloads the signed certificate and installs it
13
+
14
+ 4:) ...
15
+
16
+ 5:) Profit!
17
+
18
+ Your Puppetmaster must be configured to allow requests other than certificate requests.
19
+ See http://docs.puppetlabs.com/guides/rest_auth_conf.html for more information.
20
+
21
+ The certname can be specified with --certname or with optional CERTNAME argument to many options.
22
+
23
+ You may want to use the '-dcn' options to print the cURL equivalent command.
24
+
25
+ -d, --debug runs in debug mode
26
+ -h, --help Displays this help
27
+ -c, --curl Use commandline curl rather than Net::HTTP.
28
+ -n, --nop No-Op mode. Don't perform action, just output debugging data. Implies --debug.
29
+
30
+ --server SERVER The server address of your Puppetmaster.
31
+ --certname CERTNAME The certname you wish to use when connecting to your Puppetmaster
32
+ --file FILENAME The file to send to the Puppetmaster.
33
+ --output FILENAME The file to save any output to.
34
+ --state STATE The desired state you want to set.
35
+
36
+ --init Initialize application. Generate config file, certificate and submit CSR. Requires --certname and --server.
37
+ --install Download and install signed certificate.
38
+
39
+ --catalog Download the catalog compiled for your app's certname. Quite often just the default Node.
40
+ --delete [CERTNAME] Remove certificate and facts about a node from Puppetmaster.
41
+ --facts [CERTNAME] Retrieve the facts known about a given certname.
42
+ --insert [CERTNAME] Send facts for a given certname to the Puppetmaster. Requires --file.
43
+ --node [CERTNAME] Retrieve the node information (including facts) known about a given certname.
44
+ --search QUERY Retrieve the nodes matching a comma separated query string (e.g. kernel=Linux,virtual=vmware)
45
+
46
+ --certificate [CERTNAME] Retrieve the certificate for a given certname or 'ca'.
47
+ --cert_status [CERTNAME] Retrieve the certificate status for a given certname. Set the status by using --state.
48
+ --cert_revocation_list Retrieve and display the certifiacte revocation list from the master.
49
+ --sign [CERTNAME] Instruct the Puppetmaster to sign a certificate. Requires significate privileges in auth.conf.
50
+
51
+ --file_metadata PATH Retrieve the metadata for a file.
52
+ --getfile PATH Download a file from the Puppetmaster. Save to --file or output on stdout.
53
+
54
+ --resource RESOURCE Returns a list of resources (e.g. user) or information about a resource (e.g. 'user/elvis')
55
+ --report [CERTNAME] Sends a YAML report to the Puppetmaster. Requires --file or --state.
56
+ --status Check to make sure the Puppetmaster is alive and well.
@@ -0,0 +1,216 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'fingerpuppet/restapi.rb'
5
+
6
+ options = {}
7
+ optparse = OptionParser.new { |opts|
8
+ opts.banner = "Usage : restapi.rb <commandstring>
9
+
10
+ Steps for using the API:
11
+ 1:) fingerpuppet --init --certname my.cert.name --server my.server.name
12
+ Builds the config file
13
+ Generates the certificate and CSR
14
+ Submits the CSR to the Puppetmaster
15
+
16
+ 2:) On puppetmaster: puppet cert sign my.cert.name
17
+
18
+ 3:) restapi.rb --install
19
+ Downloads the signed certificate and installs it
20
+
21
+ 4:) ...
22
+
23
+ 5:) Profit!
24
+
25
+ Your Puppetmaster must be configured to allow requests other than certificate requests.
26
+ See http://docs.puppetlabs.com/guides/rest_auth_conf.html for more information.
27
+
28
+ The certname can be specified with --certname or with optional CERTNAME argument to many options.
29
+
30
+ You may want to use the '-dcn' options to print the cURL equivalent command.
31
+
32
+ "
33
+
34
+ options[:show]=false
35
+ opts.on("-d", "--debug", "runs in debug mode") do
36
+ options[:debug] = true
37
+ #restAPI.debug
38
+ end
39
+
40
+ opts.on("-h", "--help", "Displays this help") do
41
+ puts opts
42
+ exit
43
+ end
44
+
45
+ opts.on("-c", "--curl", "Use commandline curl rather than Net::HTTP.") do
46
+ options[:curl] = true
47
+ end
48
+
49
+ opts.on("-n", "--nop", "No-Op mode. Don't perform action, just output debugging data. Implies --debug.") do
50
+ options[:nop] = true
51
+ options[:debug] = true
52
+ end
53
+
54
+ opts.separator('')
55
+
56
+ opts.on("--server SERVER", "The server address of your Puppetmaster.") do |server|
57
+ options[:server] = server
58
+ end
59
+
60
+ opts.on("--certname CERTNAME", "The certname you wish to use when connecting to your Puppetmaster") do |certname|
61
+ options[:certname] = certname
62
+ end
63
+
64
+ opts.on("--file FILENAME", "The file to send to the Puppetmaster.") do |filename|
65
+ options[:filename] = filename
66
+ end
67
+
68
+ opts.on("--output FILENAME", "The file to save any output to.") do |filename|
69
+ options[:output] = filename
70
+ end
71
+
72
+ opts.on("--state STATE", "The desired state you want to set.") do |state|
73
+ options[:state] = state
74
+ end
75
+
76
+ opts.separator('')
77
+
78
+ opts.on("--init", "Initialize application. Generate config file, certificate and submit CSR. Requires --certname and --server.") do
79
+ options[:action] = 'init'
80
+ end
81
+
82
+ opts.on("--install", "Download and install signed certificate.") do
83
+ options[:action] = 'install'
84
+ end
85
+
86
+ opts.separator('')
87
+
88
+ opts.on("--catalog", "Download the catalog compiled for your app's certname. Quite often just the default Node.") do
89
+ options[:action] = 'catalog'
90
+ end
91
+
92
+ opts.on("--delete [CERTNAME]", "Remove certificate and facts about a node from Puppetmaster.") do |certname|
93
+ options[:action] = 'delete'
94
+ options[:certname] ||= certname
95
+ end
96
+
97
+ opts.on("--facts [CERTNAME]", "Retrieve the facts known about a given certname.") do |certname|
98
+ options[:action] = 'facts'
99
+ options[:certname] ||= certname
100
+ end
101
+
102
+ opts.on("--insert [CERTNAME]", "Send facts for a given certname to the Puppetmaster. Requires --file.") do |certname|
103
+ options[:action] = 'insert'
104
+ options[:certname] ||= certname
105
+ end
106
+
107
+ opts.on("--node [CERTNAME]", "Retrieve the node information (including facts) known about a given certname.") do |certname|
108
+ options[:action] = 'node'
109
+ options[:certname] ||= certname
110
+ end
111
+
112
+ opts.on("--search QUERY", Array, "Retrieve the nodes matching a comma separated query string (e.g. kernel=Linux,virtual=vmware)") do |query|
113
+ options[:action] = 'search'
114
+ options[:query] = query
115
+ end
116
+
117
+ opts.separator('')
118
+
119
+ opts.on("--certificate [CERTNAME]", "Retrieve the certificate for a given certname or 'ca'.") do |certname|
120
+ options[:action] = 'certificate'
121
+ options[:certname] ||= certname
122
+ end
123
+
124
+ opts.on("--cert_status [CERTNAME]", "Retrieve the certificate status for a given certname. Set the status by using --state.") do |certname|
125
+ options[:action] = 'certificate_status'
126
+ options[:certname] ||= certname
127
+ end
128
+
129
+ opts.on("--cert_revocation_list", "Retrieve and display the certifiacte revocation list from the master.") do
130
+ options[:action] = 'certificate_revocation_list'
131
+ end
132
+
133
+ opts.on("--sign [CERTNAME]", "Instruct the Puppetmaster to sign a certificate. Requires significate privileges in auth.conf.") do |certname|
134
+ options[:action] = 'sign'
135
+ options[:certname] ||= certname
136
+ end
137
+
138
+ opts.separator('')
139
+
140
+ opts.on("--file_metadata PATH", "Retrieve the metadata for a file.") do |path|
141
+ options[:action] = 'file_metadata'
142
+ options[:argument] = path
143
+ end
144
+
145
+ opts.on("--getfile PATH", "Download a file from the Puppetmaster. Save to --file or output on stdout.") do |path|
146
+ options[:action] = 'getfile'
147
+ options[:argument] = path
148
+ end
149
+
150
+ opts.separator('')
151
+
152
+ opts.on("--resource RESOURCE", "Returns a list of resources (e.g. user) or information about a resource (e.g. 'user/elvis')") do |resource|
153
+ options[:action] = 'resource'
154
+ options[:argument] = resource
155
+ end
156
+
157
+ opts.on("--report [CERTNAME]", "Sends a YAML report to the Puppetmaster. Requires --file or --state.") do |certname|
158
+ options[:action] = 'report'
159
+ options[:certname] ||= certname
160
+ end
161
+
162
+ opts.on("--status", "Check to make sure the Puppetmaster is alive and well.") do
163
+ options[:action] = 'status'
164
+ end
165
+ }
166
+
167
+ begin
168
+ optparse.parse!
169
+
170
+ restAPI = Fingerpuppet::RestAPI.new( options )
171
+ # if certname isn't specified, let's default to our certname, except for init
172
+ if options[:action] != 'init'
173
+ options[:certname] ||= restAPI.certname
174
+ end
175
+
176
+ case options[:action]
177
+ when 'init'
178
+ restAPI.init(options[:certname], options[:server])
179
+ when 'install'
180
+ restAPI.install
181
+ when 'catalog'
182
+ restAPI.catalog
183
+ when 'status'
184
+ restAPI.status
185
+ when 'facts'
186
+ restAPI.facts(options[:certname])
187
+ when 'node'
188
+ restAPI.node(options[:certname])
189
+ when 'search'
190
+ restAPI.search(options[:query])
191
+ when 'insert'
192
+ restAPI.insert(options[:certname], options[:filename])
193
+ when 'delete'
194
+ restAPI.delete(options[:certname])
195
+ when 'file_metadata'
196
+ restAPI.file_metadata(options[:argument])
197
+ when 'getfile'
198
+ restAPI.getfile(options[:argument])
199
+ when 'certificate'
200
+ restAPI.certificate(options[:certname])
201
+ when 'sign'
202
+ restAPI.sign(options[:certname])
203
+ when 'certificate_status'
204
+ restAPI.certificate_status(options[:certname], options[:state])
205
+ when 'certificate_revocation_list'
206
+ restAPI.certificate_revocation_list
207
+ when 'resource'
208
+ restAPI.resource(options[:argument])
209
+ when 'report'
210
+ restAPI.report(options[:certname], options[:filename], options[:state])
211
+ else
212
+ puts 'Use -h/--help for usage documentation.'
213
+ end
214
+ rescue Exception => e
215
+ puts e
216
+ end
@@ -0,0 +1,331 @@
1
+ require 'yaml'
2
+ require "net/https"
3
+
4
+ module Fingerpuppet
5
+ class RestAPI
6
+ attr_accessor :server, :certname
7
+
8
+ def initialize( options={} )
9
+ @debug = options[:debug]
10
+ @curl = options[:curl]
11
+ @nop = options[:nop]
12
+ @output = options[:output]
13
+ @configdir = File.expand_path('~/.fingerpuppet')
14
+
15
+ begin
16
+ config = YAML.load_file("#{@configdir}/config.yaml")
17
+ @server = config['server']
18
+ @certname = config['certname']
19
+ rescue Exception => e
20
+ puts 'Initializing API...'
21
+ end
22
+ end
23
+
24
+ # a helper that allows one to use either commandline curl or Net::HTTP
25
+ # this is useful mostly with -dcn to just print out the curl commandline
26
+ # you would use to accomplish what you're trying to do
27
+ def command( opts={} )
28
+ # this allows a global @output var, but to also override that per call
29
+ opts[:output] ||= @output
30
+
31
+ if @curl
32
+ curl(opts)
33
+ else
34
+ data = rest(opts)
35
+
36
+ if opts[:output]
37
+ save(opts[:output], data)
38
+ else
39
+ # When using the API, you will probably want to consume this data rather than just printing it.
40
+ # You might use YAML::load(data) or JSON::parse(data) depending on what's being returned
41
+ puts data
42
+ end
43
+ end
44
+ end
45
+
46
+ def save(path, data)
47
+ file = File.new(path, 'w')
48
+ file.syswrite(data)
49
+ file.close
50
+ end
51
+
52
+ def rest( opts={} )
53
+ opts[:type] ||= 'yaml'
54
+ uri = "/production/#{opts[:action]}/#{opts[:argument]}"
55
+
56
+ http = Net::HTTP.new(@server, 8140)
57
+ http.use_ssl = true
58
+
59
+ unless opts[:noauth]
60
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
61
+
62
+ store = OpenSSL::X509::Store.new
63
+ store.add_cert(OpenSSL::X509::Certificate.new(File.read("#{@configdir}/ca_crt.pem")))
64
+ http.cert_store = store
65
+
66
+ http.key = OpenSSL::PKey::RSA.new(File.read("#{@configdir}/#{@certname}.key"))
67
+ http.cert = OpenSSL::X509::Certificate.new(File.read("#{@configdir}/#{@certname}.pem"))
68
+ end
69
+
70
+ case opts[:method]
71
+ when 'PUT'
72
+ request = Net::HTTP::Put.new(uri)
73
+ request["Content-Type"] = "text/#{opts[:type]}"
74
+
75
+ if opts[:file]
76
+ # set the body to the binary contents of :file
77
+ file = File.open(opts[:file], 'rb')
78
+ request.body = file.read
79
+ else
80
+ # set the body to the string value of :data
81
+ request.body = opts[:data]
82
+ end
83
+
84
+ when 'DELETE'
85
+ request = Net::HTTP::Delete.new(uri)
86
+ when 'HEAD'
87
+ request = Net::HTTP::Head.new(uri)
88
+ else
89
+ # default to a GET request
90
+ request = Net::HTTP::Get.new(uri)
91
+ end
92
+
93
+ request["Accept"] = opts[:type]
94
+
95
+ if @debug
96
+ puts '------ HTTP Request ------'
97
+ puts request.to_yaml
98
+ puts '--------------------------'
99
+ end
100
+
101
+ return @nop ? '' : http.request(request).body
102
+ end
103
+
104
+ #def command(action, argument='', method='GET', type='yaml', output=false, file=false, data=false)
105
+ def curl( opts={} )
106
+ opts[:type] ||= 'yaml'
107
+
108
+ if opts[:noauth]
109
+ auth = '-k '
110
+ else
111
+ auth = "--cert #{@configdir}/#{@certname}.pem --key #{@configdir}/#{@certname}.key --cacert #{@configdir}/ca_crt.pem"
112
+ end
113
+
114
+ output = opts[:output] ? "-o #{opts[:output]}" : ''
115
+ header = "-H 'Accept: #{opts[:type]}'"
116
+
117
+ case opts[:method]
118
+ when 'PUT'
119
+ methodstr = '-X PUT'
120
+ header = "-H 'Content-Type: text/#{opts[:type]}'"
121
+
122
+ if opts[:file]
123
+ filestr = "--data-binary @#{opts[:file]}"
124
+ end
125
+
126
+ if opts[:data]
127
+ datastr = "--data '#{opts[:data]}'"
128
+ end
129
+
130
+ when 'DELETE'
131
+ methodstr = '-X DELETE'
132
+ when 'HEAD'
133
+ methodstr = '-I'
134
+ else
135
+ # default to a GET request
136
+ methodstr = ''
137
+ end
138
+
139
+ uri = "https://#{@server}:8140/production/#{opts[:action]}/#{opts[:argument]}"
140
+ cmd = "curl #{auth} #{methodstr} #{output} #{filestr} #{datastr} #{header} \"#{uri}\"" #quoted uri for fact ampersands
141
+ if @debug
142
+ puts cmd
143
+ else
144
+ if not system(cmd)
145
+ raise StandardError, 'cURL execution failed.'
146
+ end
147
+ puts # newline after curl output
148
+ end
149
+ end
150
+
151
+ def init(certname, server)
152
+ if certname == nil || server == nil
153
+ puts "Must set server and certname to initialize API"
154
+ exit
155
+ end
156
+
157
+ @certname = certname
158
+ @server = server
159
+
160
+ if File::directory?( @configdir )
161
+ require 'fileutils'
162
+ FileUtils.rm_rf( @configdir )
163
+ end
164
+
165
+ Dir.mkdir( @configdir )
166
+ configfile = File.new("#{@configdir}/config.yaml", 'w')
167
+ configfile.syswrite("version: 0.1\n")
168
+ configfile.syswrite("certname: #{@certname}\n")
169
+ configfile.syswrite("server: #{@server}\n")
170
+ configfile.close
171
+
172
+ begin
173
+ if not system("openssl genrsa -out #{@configdir}/#{@certname}.key 1024")
174
+ raise StandardError, 'Certificate generation failed.'
175
+ end
176
+
177
+ if not system("openssl req -new -key #{@configdir}/#{@certname}.key -subj '/CN=#{@certname}' -out #{@configdir}/#{@certname}.csr")
178
+ raise StandardError, 'CSR generation failed.'
179
+ end
180
+
181
+ self.command( { :action => 'certificate_request',
182
+ :argument => @certname,
183
+ :file => "#{@configdir}/#{@certname}.csr",
184
+ :type => 'plain',
185
+ :method => 'PUT',
186
+ :noauth => true } )
187
+
188
+ puts "CSR submitted. Now go sign it on the server and rerun this with --install."
189
+ rescue Exception => e
190
+ puts "Failure: #{e.message}"
191
+ end
192
+ end
193
+
194
+ def install
195
+ begin
196
+ self.command( { :action => 'certificate',
197
+ :argument => @certname,
198
+ :output => "#{@configdir}/#{@certname}.pem",
199
+ :type => 's',
200
+ :noauth => true } )
201
+
202
+ self.command( { :action => 'certificate',
203
+ :argument => 'ca',
204
+ :output => "#{@configdir}/ca_crt.pem",
205
+ :type => 's',
206
+ :noauth => true } )
207
+
208
+ puts "Certificate installed. API ready for use."
209
+ rescue Exception => e
210
+ puts "Failure: #{e.message}"
211
+ end
212
+ end
213
+
214
+ def catalog
215
+ self.command( { :action => 'catalog',
216
+ :argument => @certname } )
217
+ end
218
+
219
+ def status
220
+ self.command( { :action => 'status',
221
+ :argument => 'no_key' } )
222
+ end
223
+
224
+ def facts(node)
225
+ self.command( { :action => 'facts',
226
+ :argument => node } )
227
+ end
228
+
229
+ def search(query)
230
+ query.map!{ |item| "facts.#{item}"}
231
+ self.command( { :action => 'facts_search',
232
+ :argument => "search?#{query.join('&')}" } )
233
+ end
234
+
235
+ def node(node)
236
+ self.command( { :action => 'node',
237
+ :argument => node } )
238
+ end
239
+
240
+ def insert(node, path)
241
+ self.command( { :action => 'facts',
242
+ :argument => node,
243
+ :method => 'PUT',
244
+ :file => path } )
245
+ end
246
+
247
+ def file_metadata(path)
248
+ self.command( { :action => 'file_content',
249
+ :argument => path,
250
+ :type => 'yaml' } )
251
+ end
252
+
253
+ def getfile(path)
254
+ self.command( { :action => 'file_content',
255
+ :argument => path,
256
+ :type => 'raw' } )
257
+ end
258
+
259
+ def delete(node)
260
+ self.certificate_status(node, 'revoked')
261
+ self.command( { :action => 'certificate_status',
262
+ :argument => node,
263
+ :method => 'DELETE',
264
+ :type => 'pson' } )
265
+ end
266
+
267
+ def certificate(node)
268
+ self.command( { :action => 'certificate',
269
+ :argument => node,
270
+ :type => 's' } )
271
+ end
272
+
273
+ def sign(node)
274
+ self.command( { :action => 'certificate_status',
275
+ :argument => node,
276
+ :method => 'PUT',
277
+ :type => 'pson',
278
+ :noauth => true,
279
+ :data => "{\"desired_state\":\"signed\"}" } )
280
+ end
281
+
282
+ def certificate_revocation_list
283
+ self.command( { :action => 'certificate_revocation_list',
284
+ :argument => 'ca',
285
+ :type => 's' } )
286
+ end
287
+
288
+ def certificate_status(node, state=nil)
289
+ if node == nil
290
+ self.command( { :action => 'certificate_statuses',
291
+ :action => 'no_key',
292
+ :type => 'pson' } )
293
+ elsif state == nil
294
+ self.command( { :action => 'certificate_status',
295
+ :argument => node,
296
+ :type => 'pson' } )
297
+ else
298
+ self.command( { :action => 'certificate_status',
299
+ :argument => node,
300
+ :method => 'PUT',
301
+ :type => 'pson',
302
+ :data => "{\"desired_state\":\"#{state}\"}" } )
303
+ end
304
+ end
305
+
306
+ def resource(resource)
307
+ self.command( { :action => 'resource',
308
+ :argument => resource } )
309
+ end
310
+
311
+ def report(node, file, data=nil)
312
+ if data
313
+ self.command( { :action => 'report',
314
+ :argument => node,
315
+ :method => 'PUT',
316
+ :data => data } )
317
+ else
318
+ self.command( { :action => 'report',
319
+ :argument => node,
320
+ :method => 'PUT',
321
+ :file => file } )
322
+ end
323
+ end
324
+
325
+ def debug
326
+ puts "@configdir: #{@configdir}"
327
+ puts " @server: #{@server}"
328
+ puts " @certname: #{@certname}"
329
+ end
330
+ end
331
+ end
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fingerpuppet
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - Ben Ford
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2013-03-28 00:00:00 -07:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description: " A simple library and tool to interact with Puppet's REST API without needing Puppet itself installed.\n"
22
+ email: binford2k@gmail.com
23
+ executables:
24
+ - fingerpuppet
25
+ extensions: []
26
+
27
+ extra_rdoc_files: []
28
+
29
+ files:
30
+ - README.md
31
+ - LICENSE
32
+ - bin/fingerpuppet
33
+ - lib/fingerpuppet/restapi.rb
34
+ has_rdoc: true
35
+ homepage: http://github.com/binford2k/fingerpuppet
36
+ licenses: []
37
+
38
+ post_install_message:
39
+ rdoc_options: []
40
+
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ segments:
48
+ - 0
49
+ version: "0"
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ segments:
55
+ - 0
56
+ version: "0"
57
+ requirements: []
58
+
59
+ rubyforge_project:
60
+ rubygems_version: 1.3.6
61
+ signing_key:
62
+ specification_version: 3
63
+ summary: A simple library and tool to interact with Puppet's REST API without needing Puppet itself installed.
64
+ test_files: []
65
+