-
Notifications
You must be signed in to change notification settings - Fork 13
/
plugin_template.rb
316 lines (286 loc) · 11.8 KB
/
plugin_template.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
# Copy this file to <your module>/lib/puppet_x/simpkv/<plugin type>_plugin.rb,
# read all the documentation in this file and address the FIXMEs.
#
###############################################################################
# SIMPKV PLUGIN REQUIREMENTS
# - The plugin type derived from the plugin's base filename must be unique
# over **all** plugins loaded.
# - The simpkv adapter will only load the first plugin it finds of any given
# type.
# - The simpkv adapter will emit a warning when multiple plugin files for the
# same type are detected.
#
# - The plugin code must implement the API in this template.
#
# - The plugin code **must** protect from cross-puppet-environment contamination.
# Different versions of the module containing this plugin may be loaded
# into the puppetserver at the same time. So, unlike normal Ruby library
# code for which only one version will be loaded at a time (e.g., gems
# installed in the puppetserver), you have to explicitly design this plugin
# code to prevent cross-environment-contamination. This is why the plugin
# architecture requires this class to be anonymous and loads it appropriately.
# You **must** provide similar protections for any supporting Ruby code that you
# package in the module (e.g., a separate connector class). If you are not
# sure how to do this, just keep all of your plugin code within its anonymous
# class.
#
# - The plugin code must allow multiple instances to be instantiated and run
# concurrently.
#
# - The plugin code is responsible for executing any appropriate retry logic
# on failed backend operations.
#
# - The plugin code must protect itself from hung operations.
#
# - When accessing the backend in the put(), get(), ... methods, the plugin code
# should catch exceptions, convert them to meaningful error messages and then
# return the failed status in its public API.
#
# - If your plugin uses Ruby Gems that do not come standard with Puppet Ruby,
# you must list them as requirements in your plugin's documentation and
# should provide instructions on how to install those Gems.
###############################################################################
# Each plugin **MUST** be an anonymous class accessible only through
# a `plugin_class` local variable.
# DO NOT CHANGE THE LINE BELOW!!!!
plugin_class = Class.new do
# WARNING:
# In typical Ruby code, using constants and class methods is quite normal.
# Unfortunately, you cannot use constants or class methods in an anonymous
# class, as they will be added to the Class Object, itself, and will not be
# available to the anonymous class. In other words, you will be tearing your
# hair out trying to figure out why normal Ruby code does not work here!
###### Public Plugin API ######
# Construct an instance of this plugin setting its instance name
#
# @param name Name to ascribe to this plugin instance
#
def initialize(name)
# save this off, because the simpkv adapter will access it through a getter
# (defined below), when constructing log messages
@name = name
# You can use the Puppet object for logging
Puppet.debug("#{@name} simpkv plugin constructed")
end
# Configure this plugin instance using global and plugin-specific
# configuration found in options
#
# FIXME: The description below is informational for you as a developer.
# Insert the appropriate description of configuration your plugin
# supports.
#
# The simpkv adapter will call this method before any of the public API methods
# retrieve or change keystore state (i.e., delete(), deletetree(), exists(),
# that get(), list(), put()).
#
# The plugin-specific configuration will be found in
# `options['backends'][ options['backend'] ]`
#
# For example,
# {
# # global options
# 'environment' => 'production', # <== environment for the node
# 'softfail' => false, # <== for use by simpkv Puppet functions
#
# # specific backend config to use
# 'backend' => 'example_1', # <== tells you which config to use, i.e.,
# # the key in 'backends' Hash ----
# |
# # config for all backend instances |
# 'backends' => { |
# 'default => { # <== config for an instance of 'file' |
# 'type' => 'file', # <== plugin to use |
# 'id' => 'config_1', # <== unique instance id |
# ... |
# }, |
# ... |
# 'example_1' => { # <== config for an instance of 'example' <-----
# 'type' => 'example', # <== plugin to use
# 'id' => 'config_1', # <== unique instance id
# 'foo' => 'bar', # <== config specific to 'example' plugin
# ...
# },
# 'example_2' => { # <== config for another instance of 'example'
# 'type' => 'example',
# 'id' => 'config_2',
# 'foo' => 'baz,'
# ...
# }
# }
#
# @param options Hash of global simpkv and backend-specific options
# @raise RuntimeError if any required configuration is missing from options
# or this object can't set up any stateful objects it needs to do its work
# (e.g., file directory, connection to a backend)
def configure(options)
# FIXME: insert validation and set up code here
# Be sure to create 'globals' and 'environments' sub-folders off of the
# root directory.
Puppet.debug("#{@name} simpkv plugin configured")
end
# @return unique identifier assigned to this plugin instance
def name
@name
end
# The remaining methods in this API map one-for-one to those in
# simpkv's Puppet function API.
#
# IMPORTANT API NOTES:
#
# - An instance of this plugin class will persist through a single catalog
# run.
# - Other instances of this plugin class may be running concurrently in
# the same process.
#
# * Make sure your code is multi-thread safe if you are using any
# mechanisms that would cause concurrency problems!
#
# - All key values persisted and returned are Strings. Other software in
# the simpkv function chain is responsible for serializing non-String
# values into Strings for plugins to persist and then deserializing
# Strings retrieved by plugins back into objects.
#
# - Each of the API methods return a results object that is a Hash
# with 2 keys:
#
# * :result - The result of the operation. Operation specific.
# * :err_msg - A String you set to an error message that is meaningful
# to the end user, upon failure.
#
# - Although the simpkv adapter will rescue any exceptions thrown, each of
# these methods should do its very best to rescue all exceptions itself,
# and then convert the exceptions to a failed status result with a
# meaningful error message.
#
# >> Only you have the domain knowledge <<
# >> to create useful error messages! <<
#
# - If your plugin connects to an external service, you are strongly
# encouraged to build timeouts and retry logic into your backend
# operations.
#
# * The simpkv adapter does not currently protect against hung operations.
# * Only you have domain knowledge to know when a connection is hung
# and when a retry of a failed operaton is appropriate.
# Deletes a `key` from the configured backend.
#
# @param key String key
#
# @return results Hash
# * :result - Boolean indicating whether operation succeeded
# * :err_msg - String. Explanatory text upon failure; nil otherwise.
#
def delete(key)
# FIXME: insert code that connects to the backend and affects the delete
# operation
#
# - This delete should be done atomically
# - Convert any exceptions into a failed status result with a meaningful
# error message.
#
{ :result => false, :err_msg => 'FIXME: not implemented' }
end
# Deletes a whole folder from the configured backend.
#
# @param keydir String key folder path
#
# @return results Hash
# * :result - Boolean indicating whether operation succeeded
# * :err_msg - String. Explanatory text upon failure; nil otherwise.
#
def deletetree(keydir)
# FIXME: insert code that connects to the backend and affects the deletetree
# operation
#
# - If supported, this deletetree should be done atomically. If not,
# it can be best-effort.
# - Convert any exceptions into a failed status result with a meaningful
# error message.
#
{ :result => false, :err_msg => 'FIXME: not implemented' }
end
# Returns whether key or key folder exists in the configured backend.
#
# @param key String key or key folder to check
#
# @return results Hash
# * :result - Boolean indicating whether key/key folder exists;
# nil if could not be determined
# * :err_msg - String. Explanatory text when status could not be
# determined; nil otherwise.
#
def exists(key)
# FIXME: insert code that connects to the backend and affects the exists
# operation
#
# - Convert any exceptions into a failed status result with a meaningful
# error message.
#
{ :result => nil, :err_msg => 'FIXME: not implemented' }
end
# Retrieves the value stored at `key` from the configured backend.
#
# @param key String key
#
# @return results Hash
# * :result - String. Retrieved value for the key; nil if could not
# be retrieved
# * :err_msg - String. Explanatory text upon failure; nil otherwise.
#
def get(key)
# FIXME: insert code that connects to the backend and affects the get
# operation
#
# - If possible, this get should be done atomically
# - Convert any exceptions into a failed status result with a meaningful
# error message.
#
{ :result => nil, :err_msg => 'FIXME: not implemented' }
end
# Returns a listing of all keys/info pairs and sub-folders in a folder
#
# The list operation does not recurse through any sub-folders. Only
# information about the specified key folder is returned.
#
# This implementation is best effort. It will attempt to retrieve the
# information in a folder and only fail if the folder itself cannot be
# accessed. Individual key retrieval failures will be ignored.
#
# @return results Hash
# * :result - Hash of retrieved key and sub-folder info; nil if the
# retrieval operation failed
#
# * :keys - Hash of the key/value pairs for keys in the folder
# * :folders - Array of sub-folder names
#
# * :err_msg - String. Explanatory text upon failure; nil otherwise.
#
def list(keydir)
# FIXME: insert code that connects to the backend and affects the list
# operation
#
# - Convert any exceptions into a failed status result with a meaningful
# error message.
#
{ :result => nil, :err_msg => 'FIXME: not implemented' }
end
# Sets the data at `key` to a `value` in the configured backend.
#
# @param key String key
# @param value String value
#
# @return results Hash
# * :result - Boolean indicating whether operation succeeded
# * :err_msg - String. Explanatory text upon failure; nil otherwise.
#
def put(key, value)
# FIXME: insert code that connects to the backend and affects the put
# operation
#
# - This delete should be done atomically
# - Convert any exceptions into a failed status result with a meaningful
# error message.
#
{ :result => false, :err_msg => 'FIXME: not implemented' }
end
end