locd 0.1.3

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.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +108 -0
  3. data/.gitmodules +9 -0
  4. data/.qb-options.yml +4 -0
  5. data/.rspec +3 -0
  6. data/.travis.yml +5 -0
  7. data/.yardopts +7 -0
  8. data/Gemfile +10 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +72 -0
  11. data/Rakefile +6 -0
  12. data/VERSION +1 -0
  13. data/config/default.yml +24 -0
  14. data/doc/files/design/domains_and_labels.md +117 -0
  15. data/doc/files/topics/agents.md +16 -0
  16. data/doc/files/topics/launchd.md +15 -0
  17. data/doc/files/topics/plists.md +39 -0
  18. data/doc/include/plist.md +3 -0
  19. data/exe/locd +24 -0
  20. data/lib/locd.rb +75 -0
  21. data/lib/locd/agent.rb +1186 -0
  22. data/lib/locd/agent/job.rb +142 -0
  23. data/lib/locd/agent/proxy.rb +111 -0
  24. data/lib/locd/agent/rotate_logs.rb +82 -0
  25. data/lib/locd/agent/site.rb +174 -0
  26. data/lib/locd/agent/system.rb +270 -0
  27. data/lib/locd/cli.rb +4 -0
  28. data/lib/locd/cli/command.rb +9 -0
  29. data/lib/locd/cli/command/agent.rb +310 -0
  30. data/lib/locd/cli/command/base.rb +243 -0
  31. data/lib/locd/cli/command/job.rb +110 -0
  32. data/lib/locd/cli/command/main.rb +201 -0
  33. data/lib/locd/cli/command/proxy.rb +177 -0
  34. data/lib/locd/cli/command/rotate_logs.rb +152 -0
  35. data/lib/locd/cli/command/site.rb +47 -0
  36. data/lib/locd/cli/table.rb +157 -0
  37. data/lib/locd/config.rb +237 -0
  38. data/lib/locd/config/base.rb +93 -0
  39. data/lib/locd/errors.rb +65 -0
  40. data/lib/locd/label.rb +61 -0
  41. data/lib/locd/launchctl.rb +209 -0
  42. data/lib/locd/logging.rb +360 -0
  43. data/lib/locd/newsyslog.rb +402 -0
  44. data/lib/locd/pattern.rb +193 -0
  45. data/lib/locd/proxy.rb +272 -0
  46. data/lib/locd/proxymachine.rb +34 -0
  47. data/lib/locd/util.rb +49 -0
  48. data/lib/locd/version.rb +26 -0
  49. data/locd.gemspec +66 -0
  50. metadata +262 -0
@@ -0,0 +1,237 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ # Requirements
5
+ # =======================================================================
6
+
7
+ # Stdlib
8
+ # -----------------------------------------------------------------------
9
+ require 'pathname'
10
+
11
+ # Deps
12
+ # -----------------------------------------------------------------------
13
+
14
+ # Project / Package
15
+ # -----------------------------------------------------------------------
16
+
17
+
18
+ # Refinements
19
+ # =======================================================================
20
+
21
+ using NRSER
22
+ using NRSER::Types
23
+
24
+
25
+ # Definitions
26
+ # =======================================================================
27
+
28
+ # @todo document Locd::Config class.
29
+ class Locd::Config
30
+
31
+ # Constants
32
+ # ===================================================================
33
+
34
+ # Absolute path to the default config file (`//config/default.yml`).
35
+ #
36
+ # Has to be defined as a constant because no other config is loaded
37
+ # before it and it contains the ENV var prefix.
38
+ #
39
+ # @return [Pathname]
40
+ #
41
+ DEFAULT_CONFIG_PATH = Locd::ROOT / 'config' / 'default.yml'
42
+
43
+
44
+ # Absolute path to the dev override config, which will be loaded
45
+ # last if it exists.
46
+ #
47
+ # @return [Pathname]
48
+ #
49
+ DEV_CONFIG_PATH = Locd::ROOT / 'dev' / 'config.yml'
50
+
51
+
52
+ # What to split string keys into key path segments on.
53
+ #
54
+ # @return [String]
55
+ #
56
+ KEY_SEPARATOR = '.'
57
+
58
+
59
+ # Attributes
60
+ # ============================================================================
61
+
62
+ # Absolute path to the built-in default configuration file in use.
63
+ #
64
+ # @return [Pathname]
65
+ #
66
+ attr_reader :default_config_path
67
+
68
+
69
+ # Constructor
70
+ # ===================================================================
71
+
72
+ # Instantiate a new `Locd::Config`.
73
+ def initialize default_config_path: DEFAULT_CONFIG_PATH,
74
+ dev_config_path: DEV_CONFIG_PATH
75
+ @default_config_path = default_config_path.to_pn
76
+ @dev_config_path = dev_config_path.to_pn
77
+
78
+ @from_files = {}
79
+
80
+ load_file default_config_path
81
+
82
+ if user_config_path.exist?
83
+ load_file user_config_path
84
+ end
85
+
86
+ if dev_config_path && dev_config_path.exist?
87
+ load_file dev_config_path
88
+ end
89
+ end # #initialize
90
+
91
+
92
+ # Instance Methods
93
+ # ===================================================================
94
+
95
+ protected
96
+ # ========================================================================
97
+
98
+
99
+ # @todo Document load_file method.
100
+ #
101
+ # @param [type] arg_name
102
+ # @todo Add name param description.
103
+ #
104
+ # @return [return_type]
105
+ # @todo Document return value.
106
+ #
107
+ def load_file path
108
+ bnd = binding
109
+
110
+ path.
111
+ to_pn.
112
+ read.
113
+ thru { |contents|
114
+ YAML.load contents
115
+ }.
116
+ map_leaves { |path, leaf|
117
+ case leaf
118
+ when String
119
+ bnd.erb leaf
120
+ else
121
+ leaf
122
+ end
123
+ }.
124
+ thru { |tree|
125
+ @from_files.deep_merge! tree
126
+ }
127
+
128
+ nil
129
+ end # #load_file
130
+
131
+ public # end protected ***************************************************
132
+
133
+
134
+ def key_path_for *key
135
+ key.flat_map { |k| k.to_s.split KEY_SEPARATOR }
136
+ end
137
+
138
+
139
+ def from_files *key, type: nil
140
+ key_path = key_path_for *key
141
+ value = @from_files.dig Locd::GEM_NAME, *key_path
142
+
143
+ if value.nil?
144
+ nil
145
+ else
146
+ parse_and_check value, type: type
147
+ end
148
+ end
149
+
150
+
151
+ def env_key_for *key
152
+ [
153
+ from_files( :namespace, :env, type: t.non_empty_str ),
154
+ *key_path_for( *key )
155
+ ].join( '_' ).upcase
156
+ end
157
+
158
+
159
+ def parse_and_check value, type: nil
160
+ return value if type.nil?
161
+
162
+ if value.is_a?( String ) && type.has_from_s?
163
+ type.from_s value
164
+ else
165
+ type.check value
166
+ end
167
+ end
168
+
169
+
170
+ def from_env *key, type: nil
171
+ env_key = env_key_for *key
172
+
173
+ value = ENV[env_key]
174
+
175
+ case value
176
+ when nil, ''
177
+ nil
178
+ else
179
+ parse_and_check value, type: type
180
+ end
181
+ end
182
+
183
+
184
+ def get *key, type: nil, default: nil
185
+ env_value = from_env *key, type: type
186
+ return env_value unless env_value.nil?
187
+
188
+ files_value = from_files *key, type: type
189
+ return files_value unless files_value.nil?
190
+
191
+ parse_and_check default, type: type
192
+ end
193
+
194
+ alias_method :[], :get
195
+
196
+
197
+ def to_h
198
+ @from_files.merge \
199
+ "locd" => @from_files["locd"].map_leaves { |key_path, value|
200
+ env_value = from_env *key_path
201
+
202
+ if env_value.nil?
203
+ value
204
+ else
205
+ env_value
206
+ end
207
+ }
208
+ end
209
+
210
+
211
+ # @return [Pathname]
212
+ # Absolute path to Loc'd home directory (it's workspace).
213
+ # Directory may not exist, or may be something else like a file.
214
+ #
215
+ def home_dir
216
+ self[:home, type: t.abs_path].to_pn
217
+ end
218
+
219
+
220
+ def log_dir
221
+ self[
222
+ :log_dir,
223
+ type: t.abs_path,
224
+ default: (home_dir / 'log')
225
+ ]
226
+ end
227
+
228
+
229
+ # @return [Pathname]
230
+ # Absolute path to the user config file, which may not exist, but
231
+ # will be loaded if it does.
232
+ #
233
+ def user_config_path
234
+ home_dir / 'config.yml'
235
+ end
236
+
237
+ end # class Locd::Config
@@ -0,0 +1,93 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ # Requirements
5
+ # =======================================================================
6
+
7
+ # Stdlib
8
+ # -----------------------------------------------------------------------
9
+
10
+ # Deps
11
+ # -----------------------------------------------------------------------
12
+
13
+ # Project / Package
14
+ # -----------------------------------------------------------------------
15
+
16
+
17
+ # Refinements
18
+ # =======================================================================
19
+
20
+
21
+
22
+ # Definitions
23
+ # =======================================================================
24
+
25
+
26
+ # @todo document Locd::Config::Base class.
27
+ class Locd::Config::Base
28
+
29
+ # Constants
30
+ # ======================================================================
31
+
32
+
33
+ # Class Methods
34
+ # ======================================================================
35
+
36
+
37
+ # Attributes
38
+ # ======================================================================
39
+
40
+
41
+ # TODO document `key_separator` attribute.
42
+ #
43
+ # @return [attr_type]
44
+ #
45
+ attr_reader :key_separator
46
+
47
+
48
+ # Constructor
49
+ # ======================================================================
50
+
51
+ # Instantiate a new `Locd::Config::Base`.
52
+ def initialize source:, parent: nil, key_separator: '.'
53
+ @source = source
54
+ @parent = parent
55
+ @key_separator = key_separator
56
+ end # #initialize
57
+
58
+
59
+ # Instance Methods
60
+ # ======================================================================
61
+
62
+ def key_path_for *key
63
+ key.flat_map { |k| k.to_s.split key_separator }
64
+ end
65
+
66
+
67
+ def dig *key_path
68
+ if key_path.length == 1
69
+ if source.key? key_path[0]
70
+ return source[key_path[0]]
71
+ end
72
+ else
73
+ just_before = source.dig key_path[0...-1]
74
+ if just_before.respond_to?( :key? ) &&
75
+ just_before.key? key_path[-1]
76
+ return just_before[key_path[-1]]
77
+ end
78
+ end
79
+
80
+ if parent
81
+ parent.dig key_path
82
+ else
83
+ nil
84
+ end
85
+ end
86
+
87
+
88
+ def get *key
89
+ dig key_path_for( *key )
90
+ end
91
+
92
+
93
+ end # class Locd::Config::Base
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Refinements
4
+ # =======================================================================
5
+
6
+ using NRSER
7
+
8
+
9
+ # Definitions
10
+ # =======================================================================
11
+
12
+ module Locd
13
+
14
+ # Classes
15
+ # ----------------------------------------------------------------------------
16
+
17
+ # Base class for errors that are to be expected and should be handled
18
+ # gracefully.
19
+ #
20
+ class HandledError < StandardError; end
21
+
22
+
23
+ # Base class for {HandledError} that happen during request processing which
24
+ # should result in useful error responses to the user.
25
+ #
26
+ class RequestError < HandledError
27
+ STATUS_CODE = 400
28
+ STATUS_MESSAGE = "Bad Request"
29
+
30
+ def self.status
31
+ "#{ self::STATUS_CODE } #{ self::STATUS_MESSAGE }"
32
+ end
33
+
34
+
35
+ # TODO document `text` attribute.
36
+ #
37
+ # @return [attr_type]
38
+ #
39
+ attr_reader :text
40
+
41
+
42
+ def initialize text = self.class::STATUS_MESSAGE
43
+ @text = text
44
+ super "#{ self.class.status }\n\n#{ text }"
45
+ end
46
+
47
+ def to_http_response
48
+ Locd.http_response_for self.class.status, text
49
+ end
50
+
51
+ def to_proxy_machine_cmd
52
+ { close: to_http_response }
53
+ end
54
+ end
55
+
56
+
57
+ # Raised when we couldn't find what the request seems to be looking for
58
+ # (`404 Not Found` in HTTP).
59
+ #
60
+ class NotFoundError < RequestError
61
+ STATUS_CODE = 404
62
+ STATUS_MESSAGE = "Not Found"
63
+ end
64
+
65
+ end # module Locd
data/lib/locd/label.rb ADDED
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Refinements
4
+ # =======================================================================
5
+
6
+ using NRSER
7
+
8
+
9
+ # Definitions
10
+ # =======================================================================
11
+
12
+ # Static method namespace for dealing with labels.
13
+ #
14
+ module Locd::Label
15
+
16
+ def self.url_for label, tld:, port:
17
+ "http://#{ label }:#{ port }"
18
+ end
19
+
20
+
21
+ # Simple transformation of "glob-style" label patterns to {Regexp}.
22
+ #
23
+ # **_Right now only handles the `*` wildcard!_**
24
+ #
25
+ # @param [String] string
26
+ # Glob-style pattern to search for in {Locd::Agent#label} strings.
27
+ #
28
+ # @param [Boolean] exact:
29
+ # When `false`, the returned Regexp will match any part of label
30
+ # strings.
31
+ #
32
+ # When `true`, the generated Regexp will only match if `pattern` matches
33
+ # entire label strings.
34
+ #
35
+ # @param [Boolean] ignore_case:
36
+ # Makes the Regexp case-insensitive.
37
+ #
38
+ #
39
+ def self.regexp_for_glob string, exact: false, ignore_case: false
40
+ string.
41
+ # Uncommon option: `-1` causes empty segments to be included, which
42
+ # fixes the issue of `*` at start or end
43
+ split( '*', -1 ).
44
+ map( &Regexp.method( :escape ) ).
45
+ join( '.*' ).
46
+ thru { |regexp_string|
47
+ # Add `\A` and `\z` caps if `exact` is set
48
+ regexp_string = "\\A#{ regexp_string }\\z" if exact
49
+
50
+ Regexp.new regexp_string, ignore_case
51
+ }
52
+ end
53
+
54
+
55
+ def self.compare label_a, label_b
56
+ [label_a, label_b].
57
+ map { |label| label.split( '.' ).reverse }.
58
+ thru { |a, b| a <=> b }
59
+ end
60
+
61
+ end # module Locd::Label