potluck-nginx 0.0.7 → 0.0.8

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4fc8a1ec53a80c8a2914316ea35e955a79851fde0c32b59c2d26b22c573e4d3f
4
- data.tar.gz: 1f5beb340d4e719f11f98806a7d96844e9c71b11aef0de8441de516d170c0a24
3
+ metadata.gz: 0dcb1ed538b28f2b282437d29431bb42fb10990a90a19e2e727d92afa5b0bf90
4
+ data.tar.gz: 1fb1479069a6338705dd54715730305fbc1d7c77100e469fca9f17b929afd5d2
5
5
  SHA512:
6
- metadata.gz: 2852025b7fa30b945c8450b23d9efbd725b5abf3dcc41875dff7a860e85a182074c7e47ea3bf8cf7442e7ebab0a715d56296cea541b5a1c9ad9906369aeaa3fc
7
- data.tar.gz: 99cef178c6195136c566f55948b5b64068a56ffaa918f4bdea05c7f193d9161ad05796a7a33a3c4dfc0637d1ecb491a818dac35c36516c6a241b581a119f999e
6
+ metadata.gz: 0f36dba8e9127d134450841b88037ddca7daca64beb7cc20ed4ed49d271addb1e898d15125d6377a30f324415e6984f7325c5627b5e3398cecddf0b84b1efaa4
7
+ data.tar.gz: 82539b84346b059ba1e65b9a61e3e29e66c2b58897c12d96bc9a4fed54024545b7e783ce0eaea385ad406071fea606108cabf144b19fa8bf03cd06b47d05abb4
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.7
1
+ 0.0.8
@@ -0,0 +1,388 @@
1
+ # frozen_string_literal: true
2
+
3
+ require('delegate')
4
+
5
+ module Potluck
6
+ class Nginx < Service
7
+ # Public: Class for building and generating Nginx config file content. An instance of this class is
8
+ # passed to the block given to Nginx.new and Nginx#config.
9
+ #
10
+ # The name NginxConfig was chosen over Config because this is specifically for generating an Nginx
11
+ # config file--it is not for configuring the Potluck::Nginx class or an instance of it.
12
+ #
13
+ # Examples
14
+ #
15
+ # config = NginxConfig.new do |c|
16
+ # c.server do
17
+ # c.access_log('/path/to/access.log')
18
+ # c.add_header('X-Greeting', "'hello' always")
19
+ # end
20
+ # end
21
+ #
22
+ # # Add more configuration.
23
+ # config.modify do |c|
24
+ # c.server do
25
+ # c.add_header('X-Subject', "'world' always")
26
+ # end
27
+ # end
28
+ class NginxConfig
29
+ # Internal: Wrapper for config values that should be overwritten when set again (by default, repeat
30
+ # directives are appended to the config--they do not replace any previous occurrence). Used by
31
+ # NginxConfig#add_directive when passed soft: true.
32
+ class SoftValue < SimpleDelegator
33
+ end
34
+
35
+ # Internal: Regex for matching raw text items in the config hash. Raw text that gets added at various
36
+ # points uses an incrementing Symbol key of the form :"raw[0]", :"raw[1]", :"raw[2]", ....
37
+ RAW_KEY_REGEX = /^raw\[(?<index>\d+)\]$/
38
+
39
+ # Public: Create a new instance.
40
+ #
41
+ # block - Block passed to #modify for defining the config.
42
+ def initialize(&block)
43
+ @config = {}
44
+ @context = []
45
+
46
+ modify(&block) if block
47
+ end
48
+
49
+ # Public: Modify this config.
50
+ #
51
+ # block - Block to execute for modifying the config. Self is passed to the block, accepting any method
52
+ # method called on it and transforming it into an Nginx configuration directive.
53
+ #
54
+ # Examples
55
+ #
56
+ # config = NginxConfig.new
57
+ #
58
+ # config.modify do |c|
59
+ # c.server do
60
+ # c.listen('80')
61
+ # c.listen('[::]:80')
62
+ # c.server_names('hello.world', 'hi.there')
63
+ # end
64
+ # end
65
+ #
66
+ # Returns self.
67
+ def modify(&block)
68
+ block&.call(self)
69
+
70
+ self
71
+ end
72
+
73
+ # Public: Append Nginx config content via raw text or hash content.
74
+ #
75
+ # content - String or Hash of Nginx config content (any other type is gracefully ignored). See
76
+ # #add_hash and #add_raw.
77
+ #
78
+ # Returns self.
79
+ def <<(content)
80
+ case content
81
+ when Hash
82
+ add_hash(content)
83
+ when String
84
+ add_raw(content)
85
+ end
86
+
87
+ self
88
+ end
89
+
90
+ # Public: Add an Nginx directive. See #add_block_directive and #add_directive.
91
+ #
92
+ # name - Symbol name of the directive.
93
+ # args - Zero or more Object directive values.
94
+ # kwargs - Hash of optional meta information for defining the directive.
95
+ # block - Block that adds child directives.
96
+ #
97
+ # Returns nil.
98
+ # Raises NoMethodError if the named method is a defined private method (this replicates standard
99
+ # behavior for private methods).
100
+ def method_missing(name, *args, **kwargs, &block)
101
+ if private_method?(name)
102
+ raise(NoMethodError, "private method '#{name}' called for an instance of #{self.class.name}")
103
+ end
104
+
105
+ if block
106
+ add_block_directive(name, *args, &block)
107
+ elsif !args.empty?
108
+ add_directive(name, *args, **kwargs)
109
+ end
110
+
111
+ nil
112
+ end
113
+
114
+ # Public: Determine if this instance handles a particular method call.
115
+ #
116
+ # name - Symbol name of the method.
117
+ # include_private - Boolean indicating if private methods should be included in the check.
118
+ #
119
+ # Returns false if the method is private and include_private is false, and true otherwise (since
120
+ # #method_missing is implemented and accepts any method name).
121
+ def respond_to_missing?(name, include_private = false)
122
+ private_method?(name) ? include_private : true
123
+ end
124
+
125
+ # Public: Generate the Nginx config file content.
126
+ #
127
+ # Returns the String content.
128
+ def to_s
129
+ to_nginx_config(@config)
130
+ end
131
+
132
+ # Public: Get the value of a directive.
133
+ #
134
+ # keys - One or more String directive names, Symbol raw keys (e.g. :"raw[0]"), or Integer array
135
+ # indexes.
136
+ #
137
+ # Returns the String, Array, or Hash directive value.
138
+ def dig(*keys)
139
+ @config.dig(*keys)
140
+ end
141
+
142
+ private
143
+
144
+ # Internal: Transform a hash into raw content for an Nginx configuration file.
145
+ #
146
+ # hash - Hash config definition.
147
+ # indent: - Integer number of spaces to indent (used when the method is called recursively and should
148
+ # not be set explicitly).
149
+ #
150
+ # Returns the String content.
151
+ def to_nginx_config(hash, indent: 0)
152
+ hash.each.with_object(+'') do |(name, items), str|
153
+ if name.kind_of?(Symbol) && name.match?(RAW_KEY_REGEX)
154
+ str << items.gsub(/^(?=.)/, ' ' * indent)
155
+ str << "\n" unless str.end_with?("\n")
156
+
157
+ next
158
+ end
159
+
160
+ items.each do |item|
161
+ if item.kind_of?(Hash)
162
+ str << "#{' ' * indent}#{name} {\n" \
163
+ "#{to_nginx_config(item, indent: indent + 2)}" \
164
+ "#{' ' * indent}}\n"
165
+ else
166
+ str << "#{' ' * indent}#{name} #{item};\n"
167
+ end
168
+ end
169
+ end
170
+ end
171
+
172
+ # Internal: Get the hash for the current context stack.
173
+ #
174
+ # Returns the contextual Hash.
175
+ def contextual_config
176
+ @context.empty? ? @config : @config.dig(*@context)
177
+ end
178
+
179
+ # Internal: Add an Nginx block directive.
180
+ #
181
+ # name - String or Symbol name of the block directive.
182
+ # args - Zero or more Object values that are converted to strings (via #to_s) and joined to name with
183
+ # spaces; an optional last Integer item specifies the index into the array of directive values
184
+ # (used when there is more than one of the same directive, such as with server blocks).
185
+ # block - Block that defines child directives.
186
+ #
187
+ # Examples
188
+ #
189
+ # add_block_directive('server') do |c|
190
+ # c.listen('8080')
191
+ # end
192
+ #
193
+ # add_block_directive('server', 1) do |c|
194
+ # c.listen('4433')
195
+ # c.server_name('hi.there')
196
+ # end
197
+ #
198
+ # add_block_directive('server', 0) do |c|
199
+ # c.server_name('hello.world')
200
+ # end
201
+ #
202
+ # to_s
203
+ #
204
+ # # => "server {\n" +
205
+ # # " listen 8080;\n"
206
+ # # " server_name hello.world;\n"
207
+ # # "}\n"
208
+ # # "server {\n" +
209
+ # # " listen 4433;\n"
210
+ # # " server_name hi.there;\n"
211
+ # # "}\n"
212
+ #
213
+ # Returns nothing.
214
+ def add_block_directive(name, *args, &block)
215
+ index = args.pop if args.last.kind_of?(Integer)
216
+ context = "#{name} #{args.join(' ')}".strip
217
+ directives = (contextual_config[context] ||= [])
218
+ index ||= directives.size - 1
219
+
220
+ unless directives[index]
221
+ directives << {}
222
+ index = directives.size - 1
223
+ end
224
+
225
+ @context << context << index
226
+
227
+ block.call(self)
228
+
229
+ @context.pop(2)
230
+ end
231
+
232
+ # Internal: Add an Nginx (non-block) directive.
233
+ #
234
+ # Repeated calls with the same name accumulate into an array. If there are two or more args,
235
+ # subsequent calls with the same name and same first arg will overwrite the original value for that
236
+ # name and first arg (see the examples if this is unclear).
237
+ #
238
+ # If args is empty or only contains nil and/or empty string values, any previously-added directive
239
+ # with the same name is removed.
240
+ #
241
+ # If soft: true is passed, value is added but treated as 'soft'. Soft values are all removed as soon
242
+ # as a non-soft value is added. This is used to set up default values that can be optionally
243
+ # overwritten.
244
+ #
245
+ # name - String or Symbol name of the directive.
246
+ # args - Zero or more Object values that are converted to strings (via #to_s) and joined to each
247
+ # other with spaces.
248
+ # kwargs - Hash of optional meta information. Only :soft is used. Any others will be ignored.
249
+ #
250
+ # Examples
251
+ #
252
+ # add_directive('add_header', 'Referrer-Policy', "'same-origin' always")
253
+ # add_directive('add_header', 'X-Frame-Options', "'SAMEORIGIN' always")
254
+ # add_directive('add_header', 'X-Frame-Options', "'DENY' always")
255
+ # to_s
256
+ #
257
+ # # => "add_header Referrer-Policy 'same-origin' always;\n" +
258
+ # # "add_header X-Frame-Options 'DENY' always;\n"
259
+ #
260
+ # add_directive('add_header', 'Referrer-Policy', nil)
261
+ # to_s
262
+ #
263
+ # # => "add_header X-Frame-Options 'DENY' always;\n"
264
+ #
265
+ # access_log('/first/path/to/access.log', soft: true)
266
+ # access_log('/second/path/to/access.log', soft: true)
267
+ # to_s
268
+ #
269
+ # # => "access_log /first/path/to/access.log;\n" +
270
+ # # "access_log /second/path/to/access.log;\n"
271
+ #
272
+ # access_log('/third/path/to/access.log')
273
+ # to_s
274
+ #
275
+ # # => "access_log /third/path/to/access.log;\n"
276
+ #
277
+ # Returns nothing.
278
+ def add_directive(name, *args, **kwargs)
279
+ name = name.to_s
280
+ directive = (contextual_config[name] ||= [])
281
+ soft = kwargs[:soft]
282
+
283
+ key = "#{args.first} " if args.size >= 2
284
+ value = args.join(' ').strip
285
+ value = nil if value.empty? || value == key&.strip
286
+ value = SoftValue.new(value) if soft
287
+
288
+ unless soft
289
+ directive.reject! do |item|
290
+ item.kind_of?(SoftValue)
291
+ end
292
+ end
293
+
294
+ if key
295
+ index = directive.index do |item|
296
+ item.start_with?(key)
297
+ end
298
+ end
299
+
300
+ if index
301
+ value.nil? ? directive.delete_at(index) : (directive[index] = value)
302
+ elsif value.nil?
303
+ directive.clear
304
+ else
305
+ directive << value
306
+ end
307
+
308
+ contextual_config.delete(name) if directive.empty?
309
+ end
310
+
311
+ # Internal: Add Nginx config content using a hash.
312
+ #
313
+ # hash - Hash of config content. Keys are String directive names or the Symbol :raw or :raw[<i>]
314
+ # (where <i> is an Integer). Values are a Hash for block directive content, an Array of
315
+ # Object--each of which will have #to_s called on it--for repeated directives, or an Object
316
+ # which will have #to_s called on it.
317
+ #
318
+ # Examples
319
+ #
320
+ # add_hash({
321
+ # 'server' => {
322
+ # 'access_log' => '/path/to/access.log',
323
+ # 'add_header' => [
324
+ # "Referrer-Policy 'same-origin' always",
325
+ # "X-Frame-Options 'DENY' always",
326
+ # ],
327
+ # 'raw[0]': 'return 404',
328
+ # },
329
+ # })
330
+ #
331
+ # Returns nothing.
332
+ def add_hash(hash)
333
+ hash.each do |key, value|
334
+ if key == :raw || key.match?(RAW_KEY_REGEX)
335
+ add_raw(value)
336
+ next
337
+ end
338
+
339
+ case value
340
+ when Hash
341
+ add_block_directive(key) do
342
+ add_hash(value)
343
+ end
344
+ when Array
345
+ value.each do |item|
346
+ add_directive(key, item)
347
+ end
348
+ else
349
+ add_directive(key, value)
350
+ end
351
+ end
352
+ end
353
+
354
+ # Internal: Add raw Nginx config content using a string.
355
+ #
356
+ # content - String config content.
357
+ #
358
+ # Returns nothing.
359
+ def add_raw(content)
360
+ contextual_config[next_raw_key] = content
361
+ end
362
+
363
+ # Internal: Get the next hash key used for raw content. If the current context has, say, keys :raw[0]
364
+ # and :raw[1], then :raw[2] will be returned.
365
+ #
366
+ # Returns the Symbol key.
367
+ def next_raw_key
368
+ index = contextual_config
369
+ .keys
370
+ .grep(RAW_KEY_REGEX)
371
+ .map { |k| k[RAW_KEY_REGEX, :index].to_i }
372
+ .last
373
+
374
+ :"raw[#{(index || -1) + 1}]"
375
+ end
376
+
377
+ # Internal: Determine if a method is defined and is private.
378
+ #
379
+ # name - Symbol name of the method.
380
+ #
381
+ # Returns the boolean result.
382
+ def private_method?(name)
383
+ @@private_methods ||= self.class.private_instance_methods(false)
384
+ @@private_methods.include?(name)
385
+ end
386
+ end
387
+ end
388
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Potluck
4
4
  class Nginx < Service
5
- VERSION = '0.0.7'
5
+ VERSION = '0.0.8'
6
6
  end
7
7
  end