fingerpuppet 0.0.1

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