config_o_mat 0.1.0
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 +7 -0
- data/.gitignore +9 -0
- data/.rspec +1 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +26 -0
- data/Gemfile.lock +56 -0
- data/LICENSE +202 -0
- data/NOTICE +2 -0
- data/README.md +15 -0
- data/Rakefile +35 -0
- data/bin/config_o_mat-configurator +49 -0
- data/bin/config_o_mat-meta_configurator +49 -0
- data/config_o_mat.gemspec +49 -0
- data/design/configurator_lifecycle_test.dot +91 -0
- data/design/original_design.md +45 -0
- data/lib/config_o_mat/configurator/cond/first_run.rb +29 -0
- data/lib/config_o_mat/configurator/cond/next_state.rb +29 -0
- data/lib/config_o_mat/configurator/cond/profiles_to_apply.rb +29 -0
- data/lib/config_o_mat/configurator/cond/retries_left.rb +37 -0
- data/lib/config_o_mat/configurator/cond/services_to_reload.rb +29 -0
- data/lib/config_o_mat/configurator/cond.rb +17 -0
- data/lib/config_o_mat/configurator/memory.rb +93 -0
- data/lib/config_o_mat/configurator/op/apply_all_profiles.rb +35 -0
- data/lib/config_o_mat/configurator/op/commit_staged_profile.rb +35 -0
- data/lib/config_o_mat/configurator/op/compile_templates.rb +41 -0
- data/lib/config_o_mat/configurator/op/connect_to_appconfig.rb +35 -0
- data/lib/config_o_mat/configurator/op/generate_all_templates.rb +66 -0
- data/lib/config_o_mat/configurator/op/next_tick.rb +48 -0
- data/lib/config_o_mat/configurator/op/notify_systemd_start.rb +29 -0
- data/lib/config_o_mat/configurator/op/parse_cli.rb +93 -0
- data/lib/config_o_mat/configurator/op/refresh_all_profiles.rb +63 -0
- data/lib/config_o_mat/configurator/op/refresh_profile.rb +65 -0
- data/lib/config_o_mat/configurator/op/reload_one_service.rb +73 -0
- data/lib/config_o_mat/configurator/op/stage_one_profile.rb +33 -0
- data/lib/config_o_mat/configurator/op/wait_retry.rb +41 -0
- data/lib/config_o_mat/configurator/op.rb +17 -0
- data/lib/config_o_mat/configurator.rb +122 -0
- data/lib/config_o_mat/flip_flopper/cond/service_status.rb +29 -0
- data/lib/config_o_mat/flip_flopper/cond.rb +17 -0
- data/lib/config_o_mat/flip_flopper/memory.rb +51 -0
- data/lib/config_o_mat/flip_flopper/op/check_service_status.rb +72 -0
- data/lib/config_o_mat/flip_flopper/op/determine_running_instance.rb +56 -0
- data/lib/config_o_mat/flip_flopper/op/report_failure.rb +36 -0
- data/lib/config_o_mat/flip_flopper/op/start_activating_instance.rb +34 -0
- data/lib/config_o_mat/flip_flopper/op/stop_activating_instance.rb +34 -0
- data/lib/config_o_mat/flip_flopper/op/stop_initial_instance.rb +34 -0
- data/lib/config_o_mat/flip_flopper/op.rb +17 -0
- data/lib/config_o_mat/flip_flopper.rb +46 -0
- data/lib/config_o_mat/meta_configurator/memory.rb +73 -0
- data/lib/config_o_mat/meta_configurator/op/generate_systemd_config.rb +80 -0
- data/lib/config_o_mat/meta_configurator/op/parse_meta_cli.rb +103 -0
- data/lib/config_o_mat/meta_configurator/op.rb +17 -0
- data/lib/config_o_mat/meta_configurator.rb +44 -0
- data/lib/config_o_mat/shared/cond/early_exit.rb +29 -0
- data/lib/config_o_mat/shared/cond.rb +17 -0
- data/lib/config_o_mat/shared/op/load_meta_config.rb +188 -0
- data/lib/config_o_mat/shared/op.rb +17 -0
- data/lib/config_o_mat/shared/systemd_interface.rb +93 -0
- data/lib/config_o_mat/shared/types.rb +248 -0
- data/lib/config_o_mat/version.rb +19 -0
- data/lib/version.rb +19 -0
- data/systemd/teak-configurator-restart-service@.path +27 -0
- data/systemd/teak-configurator-restart-service@.service +21 -0
- data/systemd/teak-configurator-start-service@.path +27 -0
- data/systemd/teak-configurator-start-service@.service +21 -0
- data/systemd/teak-configurator-stop-service@.path +27 -0
- data/systemd/teak-configurator-stop-service@.service +21 -0
- data/systemd/teak-configurator.service +68 -0
- data/systemd/teak-metaconfigurator.service +57 -0
- metadata +213 -0
@@ -0,0 +1,188 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2021 Teak.io, Inc.
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
require 'lifecycle_vm/op_base'
|
18
|
+
require 'config_o_mat/shared/types'
|
19
|
+
require 'config_o_mat/shared/systemd_interface'
|
20
|
+
|
21
|
+
require 'logsformyfamily'
|
22
|
+
|
23
|
+
require 'securerandom'
|
24
|
+
require 'yaml'
|
25
|
+
|
26
|
+
module ConfigOMat
|
27
|
+
module Op
|
28
|
+
class LoadMetaConfig < LifecycleVM::OpBase
|
29
|
+
module DeepMerge
|
30
|
+
refine Hash do
|
31
|
+
def deep_merge(other)
|
32
|
+
dup.deep_merge!(other)
|
33
|
+
end
|
34
|
+
|
35
|
+
def deep_merge!(other)
|
36
|
+
merge!(other) do |key, this_val, other_val|
|
37
|
+
if this_val.respond_to?(:deep_merge) &&
|
38
|
+
this_val.is_a?(other_val.class)
|
39
|
+
this_val.deep_merge(other_val)
|
40
|
+
else
|
41
|
+
other_val
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
refine Array do
|
48
|
+
def deep_merge(other)
|
49
|
+
dup.deep_merge!(other)
|
50
|
+
end
|
51
|
+
|
52
|
+
def deep_merge!(other)
|
53
|
+
concat(other)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
using DeepMerge
|
59
|
+
|
60
|
+
LOG_TYPES = %i[stdout file].freeze
|
61
|
+
LOG_CONFIG_KEYS = %i[log_level log_type log_file].freeze
|
62
|
+
|
63
|
+
reads :configuration_directory, :logs_directory, :env
|
64
|
+
writes :profile_defs, :template_defs, :service_defs, :dependencies,
|
65
|
+
:refresh_interval, :client_id, :logger, :retry_count, :retries_left,
|
66
|
+
:retry_wait, :region, :systemd_interface
|
67
|
+
|
68
|
+
def call
|
69
|
+
default_config = {
|
70
|
+
refresh_interval: 5,
|
71
|
+
client_id: env.fetch('INVOCATION_ID') { SecureRandom.uuid },
|
72
|
+
retry_count: 3,
|
73
|
+
retry_wait: 2,
|
74
|
+
services: [],
|
75
|
+
templates: [],
|
76
|
+
profiles: [],
|
77
|
+
region: nil
|
78
|
+
}
|
79
|
+
|
80
|
+
# TODO: I would like to make this configurable. I think the trick
|
81
|
+
# Sequel uses for its model classes (< Sequel::Model(source_dataset))
|
82
|
+
# might be appropriate for how this system works?
|
83
|
+
# do: Op::LoadMetaConfig(parser: Yaml.method(&:safe_load))
|
84
|
+
parser = proc { |file| YAML.safe_load(file, symbolize_names: true) }
|
85
|
+
file_ending = '.conf'
|
86
|
+
|
87
|
+
files =
|
88
|
+
Dir.children(configuration_directory)
|
89
|
+
.lazy
|
90
|
+
.select { |f| f.end_with?(file_ending) }
|
91
|
+
.map { |f| File.join(configuration_directory, f) }
|
92
|
+
.select { |f| File.file?(f) }
|
93
|
+
.to_a
|
94
|
+
.sort!
|
95
|
+
|
96
|
+
loaded_files =
|
97
|
+
files.each_with_object({}) do |file, hash|
|
98
|
+
hash[file] = parser.call(File.read(file))
|
99
|
+
rescue StandardError => e
|
100
|
+
error file, e
|
101
|
+
end
|
102
|
+
|
103
|
+
# If we couldn't load a configuration file then it's probably not worth
|
104
|
+
# exploding on any objects we fail to initialize -- that's an obvious
|
105
|
+
# consequence and the additional noise might mask the root cause.
|
106
|
+
return if errors?
|
107
|
+
|
108
|
+
merged_config =
|
109
|
+
loaded_files.each_with_object(default_config) do |(_filename, config), memo|
|
110
|
+
memo.deep_merge!(config)
|
111
|
+
end
|
112
|
+
|
113
|
+
instantiate = proc do |key, klass|
|
114
|
+
merged_config[key].each_with_object({}) do |(name, obj), defs|
|
115
|
+
definition = klass.new(obj)
|
116
|
+
definition.validate!
|
117
|
+
defs[name] = definition
|
118
|
+
rescue StandardError => e
|
119
|
+
error key, { name => e }
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
logger&.info(:log_config, configuration: merged_config.slice(*LOG_CONFIG_KEYS))
|
124
|
+
|
125
|
+
self.service_defs = instantiate.call(:services, Service)
|
126
|
+
self.template_defs = instantiate.call(:templates, Template)
|
127
|
+
self.profile_defs = instantiate.call(:profiles, Profile)
|
128
|
+
|
129
|
+
self.logger = LogsForMyFamily::Logger.new if !logger
|
130
|
+
|
131
|
+
log_type = merged_config[:log_type]&.to_sym || :stdout
|
132
|
+
error :log_type, "must be one of #{LOG_TYPES.map(&:to_s)}" if log_type && !LOG_TYPES.include?(log_type)
|
133
|
+
|
134
|
+
log_level = merged_config[:log_level]&.to_sym
|
135
|
+
if log_level && !LogsForMyFamily::Logger::LEVELS.include?(log_level)
|
136
|
+
error :log_level, "must be one of #{LogsForMyFamily::Logger::LEVELS}"
|
137
|
+
end
|
138
|
+
|
139
|
+
backend =
|
140
|
+
if log_type == :file
|
141
|
+
log_file = merged_config[:log_file] || 'configurator.log'
|
142
|
+
if logs_directory.nil?
|
143
|
+
error :log_type, 'must set logs directory with -l or $LOGS_DIRECTORY to set log_type to file'
|
144
|
+
else
|
145
|
+
FileLogWriter.new(File.join(logs_directory, log_file))
|
146
|
+
end
|
147
|
+
else
|
148
|
+
StdoutLogWriter.new
|
149
|
+
end
|
150
|
+
|
151
|
+
# If we couldn't initialize our logger (or anything else) then bail here before
|
152
|
+
# we try to use it.
|
153
|
+
return if errors?
|
154
|
+
|
155
|
+
logger.filter_level(log_level) if log_level
|
156
|
+
logger.backends = [backend]
|
157
|
+
|
158
|
+
# Re-log our merged config with our configured logger.
|
159
|
+
logger.info(:parsed_config, configuration: merged_config)
|
160
|
+
|
161
|
+
self.refresh_interval = merged_config[:refresh_interval]
|
162
|
+
self.client_id = merged_config[:client_id]
|
163
|
+
self.retry_count = merged_config[:retry_count]
|
164
|
+
self.retries_left = retry_count
|
165
|
+
self.retry_wait = merged_config[:retry_wait]
|
166
|
+
self.region = merged_config[:region]
|
167
|
+
|
168
|
+
self.dependencies = service_defs.each_with_object({}) do |(name, service), template_to_services|
|
169
|
+
service.templates.each do |template|
|
170
|
+
template = template.to_sym
|
171
|
+
if !template_defs.key?(template)
|
172
|
+
error :services, { name => "references undefined template #{template}" }
|
173
|
+
else
|
174
|
+
# Listing the same template multiple times is acceptable. Since we allow
|
175
|
+
# merging config files, and this deep merges the service dependency list,
|
176
|
+
# it's quite possible that a service could inadvertently declare the same
|
177
|
+
# dependency twice in a way that's not easy to untangle.
|
178
|
+
template_to_services[template] ||= Set.new
|
179
|
+
template_to_services[template] << name
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
self.systemd_interface = SystemdInterface.new(DBus.system_bus)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2021 Teak.io, Inc.
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
Dir[File.join(__dir__, 'op', '*')].sort.each { |file| require file }
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2021 Teak.io, Inc.
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
require 'dbus'
|
18
|
+
|
19
|
+
module ConfigOMat
|
20
|
+
class SystemdInterface
|
21
|
+
SERVICE_NAME = 'org.freedesktop.systemd1'
|
22
|
+
OBJECT_PATH = '/org/freedesktop/systemd1'
|
23
|
+
MANAGER_INTERFACE = 'org.freedesktop.systemd1.Manager'
|
24
|
+
|
25
|
+
UNIT_INTERFACE = 'org.freedesktop.systemd1.Unit'
|
26
|
+
UNIT_STATE = 'ActiveState'
|
27
|
+
|
28
|
+
def initialize(sysbus = nil)
|
29
|
+
if sysbus
|
30
|
+
@sysd_service = sysbus[SERVICE_NAME]
|
31
|
+
obj = @sysd_service[OBJECT_PATH]
|
32
|
+
@sysd_iface = obj[MANAGER_INTERFACE]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def service_interface(unit_name)
|
37
|
+
unit_path = @sysd_iface.GetUnit("#{unit_name}.service")
|
38
|
+
unit_obj = @sysd_service[unit_path]
|
39
|
+
unit_obj[UNIT_INTERFACE]
|
40
|
+
rescue DBus::Error
|
41
|
+
nil
|
42
|
+
end
|
43
|
+
|
44
|
+
def service_status(unit_name)
|
45
|
+
iface = service_interface(unit_name)
|
46
|
+
if iface
|
47
|
+
iface[UNIT_STATE]
|
48
|
+
else
|
49
|
+
'inactive'
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def enable_restart_paths(units)
|
54
|
+
path_units = units.map { |unit| "teak-configurator-restart-service@#{unit}.path" }
|
55
|
+
enable_and_start(path_units)
|
56
|
+
end
|
57
|
+
|
58
|
+
def enable_start_stop_paths(units)
|
59
|
+
path_units = units.flat_map do |unit|
|
60
|
+
["teak-configurator-start-service@#{unit}.path", "teak-configurator-stop-service@#{unit}.path"]
|
61
|
+
end
|
62
|
+
enable_and_start(path_units)
|
63
|
+
end
|
64
|
+
|
65
|
+
def daemon_reload
|
66
|
+
@sysd_iface.Reload()
|
67
|
+
end
|
68
|
+
|
69
|
+
def ==(other)
|
70
|
+
eql?(other)
|
71
|
+
end
|
72
|
+
|
73
|
+
def eql?(other)
|
74
|
+
return false if !other.is_a?(self.class)
|
75
|
+
return false if other.instance_variable_get(:@sysd_service) != @sysd_service
|
76
|
+
true
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def enable_and_start(units)
|
82
|
+
@sysd_iface.EnableUnitFiles(
|
83
|
+
units,
|
84
|
+
true, # Runtime
|
85
|
+
false # force
|
86
|
+
)
|
87
|
+
daemon_reload
|
88
|
+
units.each do |unit|
|
89
|
+
@sysd_iface.StartUnit(unit, 'replace')
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,248 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2021 Teak.io, Inc.
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
require 'json'
|
18
|
+
require 'yaml'
|
19
|
+
require 'digest'
|
20
|
+
|
21
|
+
module ConfigOMat
|
22
|
+
class LogWriter
|
23
|
+
def call(level_name, event_type, merged_data)
|
24
|
+
merged_data[:level] = level_name
|
25
|
+
merged_data[:event_type] = event_type
|
26
|
+
|
27
|
+
write { "#{JSON.generate(merged_data)}\n" }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class StdoutLogWriter < LogWriter
|
32
|
+
def write
|
33
|
+
$stdout.write(yield)
|
34
|
+
$stdout.flush
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class FileLogWriter < LogWriter
|
39
|
+
attr_reader :file_path
|
40
|
+
|
41
|
+
def initialize(file_path)
|
42
|
+
@file_path = file_path
|
43
|
+
end
|
44
|
+
|
45
|
+
def write
|
46
|
+
File.open(@file_path, 'a') { |f| f.write(yield) }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class ConfigItem
|
51
|
+
class ValidationError < RuntimeError
|
52
|
+
attr_reader :errors
|
53
|
+
|
54
|
+
def initialize(errors)
|
55
|
+
@errors = errors
|
56
|
+
super(errors.to_s)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
attr_reader :errors
|
61
|
+
|
62
|
+
def errors?
|
63
|
+
instance_variable_defined?(:@errors) && !(@errors.nil? || @errors.empty?)
|
64
|
+
end
|
65
|
+
|
66
|
+
def error(field, message)
|
67
|
+
@errors ||= {}
|
68
|
+
@errors[field] ||= []
|
69
|
+
@errors[field] << message
|
70
|
+
end
|
71
|
+
|
72
|
+
def validate!
|
73
|
+
validate
|
74
|
+
raise ValidationError.new(errors) if errors?
|
75
|
+
end
|
76
|
+
|
77
|
+
def eql?(other)
|
78
|
+
return false if !other.is_a?(self.class)
|
79
|
+
true
|
80
|
+
end
|
81
|
+
|
82
|
+
def ==(other)
|
83
|
+
eql?(other)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
class Service < ConfigItem
|
88
|
+
RESTART_MODES = %i[restart flip_flop].freeze
|
89
|
+
|
90
|
+
attr_reader :systemd_unit, :restart_mode, :templates
|
91
|
+
|
92
|
+
def initialize(opts)
|
93
|
+
@systemd_unit = (opts[:systemd_unit] || '')
|
94
|
+
@restart_mode = opts[:restart_mode]&.to_sym
|
95
|
+
@templates = opts[:templates]
|
96
|
+
|
97
|
+
if @restart_mode == :flip_flop && !@systemd_unit.include?('@')
|
98
|
+
@systemd_unit = "#{@systemd_unit}@"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def validate
|
103
|
+
error :templates, 'must be present' if @templates.nil? || @templates.empty?
|
104
|
+
unless @templates.is_a?(Array) && @templates.all? { |v| v.is_a?(String) }
|
105
|
+
error :templates, 'must be an array of strings'
|
106
|
+
end
|
107
|
+
error :systemd_unit, 'must be present' if @systemd_unit.nil? || @systemd_unit.empty? || @systemd_unit == '@'
|
108
|
+
error :restart_mode, "must be one of #{RESTART_MODES}" unless RESTART_MODES.include?(@restart_mode)
|
109
|
+
|
110
|
+
if @restart_mode == :flip_flop && !@systemd_unit.end_with?('@')
|
111
|
+
error :systemd_unit, 'must not contain an instance (anything after a @)'
|
112
|
+
end
|
113
|
+
|
114
|
+
if restart_mode == :restart && @systemd_unit.end_with?('@')
|
115
|
+
error :systemd_unit, 'must not be a naked instantiated unit when restart_mode=restart'
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def hash
|
120
|
+
systemd_unit.hash ^ restart_mode.hash ^ templates.hash
|
121
|
+
end
|
122
|
+
|
123
|
+
def eql?(other)
|
124
|
+
return false if !super(other)
|
125
|
+
return false if other.systemd_unit != systemd_unit || other.restart_mode != restart_mode || other.templates != templates
|
126
|
+
true
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
class Template < ConfigItem
|
131
|
+
attr_reader :src, :dst
|
132
|
+
|
133
|
+
def initialize(opts)
|
134
|
+
@src = opts[:src]
|
135
|
+
@dst = opts[:dst]
|
136
|
+
end
|
137
|
+
|
138
|
+
def validate
|
139
|
+
error :src, 'must be present' if @src.nil? || @src.empty?
|
140
|
+
error :dst, 'must be present' if @dst.nil? || @dst.empty?
|
141
|
+
end
|
142
|
+
|
143
|
+
def hash
|
144
|
+
src.hash ^ dst.hash
|
145
|
+
end
|
146
|
+
|
147
|
+
def eql?(other)
|
148
|
+
return false if !super(other)
|
149
|
+
return false if other.src != src || other.dst != dst
|
150
|
+
true
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
class Profile < ConfigItem
|
155
|
+
attr_reader :application, :environment, :profile
|
156
|
+
|
157
|
+
def initialize(opts)
|
158
|
+
@application = opts[:application]
|
159
|
+
@environment = opts[:environment]
|
160
|
+
@profile = opts[:profile]
|
161
|
+
end
|
162
|
+
|
163
|
+
def validate
|
164
|
+
error :application, 'must be present' if @application.nil? || @application.empty?
|
165
|
+
error :environment, 'must be present' if @environment.nil? || @environment.empty?
|
166
|
+
error :profile, 'must be present' if @profile.nil? || @profile.empty?
|
167
|
+
end
|
168
|
+
|
169
|
+
def hash
|
170
|
+
application.hash ^ environment.hash ^ profile.hash
|
171
|
+
end
|
172
|
+
|
173
|
+
def eql?(other)
|
174
|
+
return false if !super(other)
|
175
|
+
return false if other.application != application || other.environment != environment || other.profile != profile
|
176
|
+
true
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
class LoadedProfile < ConfigItem
|
181
|
+
attr_reader :name, :version, :contents
|
182
|
+
|
183
|
+
PARSERS = {
|
184
|
+
'text/plain' => proc { |str| str },
|
185
|
+
'application/json' => proc { |str| JSON.parse(str, symbolize_names: true) },
|
186
|
+
'application/x-yaml' => proc { |str| YAML.safe_load(str, symbolize_names: true) }
|
187
|
+
}.freeze
|
188
|
+
|
189
|
+
def initialize(name, version, contents, content_type)
|
190
|
+
@name = name
|
191
|
+
@version = version
|
192
|
+
|
193
|
+
parser = PARSERS[content_type]
|
194
|
+
|
195
|
+
if parser
|
196
|
+
begin
|
197
|
+
@contents = parser.call(contents)
|
198
|
+
rescue StandardError => e
|
199
|
+
error :contents, e
|
200
|
+
end
|
201
|
+
else
|
202
|
+
error :content_type, "must be one of #{PARSERS.keys}"
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def validate
|
207
|
+
error :name, 'must be present' if @name.nil? || @name.empty?
|
208
|
+
error :name, 'must be a Symbol' unless @name.is_a?(Symbol)
|
209
|
+
error :version, 'must be present' if @version.nil? || @version.empty?
|
210
|
+
error :contents, 'must be present' if @contents.nil? || @contents.empty?
|
211
|
+
end
|
212
|
+
|
213
|
+
def hash
|
214
|
+
@name.hash ^ @version.hash ^ @contents.hash
|
215
|
+
end
|
216
|
+
|
217
|
+
def to_h
|
218
|
+
@contents
|
219
|
+
end
|
220
|
+
|
221
|
+
def eql?(other)
|
222
|
+
return false if !super(other)
|
223
|
+
return false if other.version != version || other.contents != contents || other.name != name
|
224
|
+
true
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
class GeneratedTemplate < ConfigItem
|
229
|
+
attr_reader :digest
|
230
|
+
|
231
|
+
def initialize(contents)
|
232
|
+
@digest = Digest::SHA256.hexdigest(contents)
|
233
|
+
end
|
234
|
+
|
235
|
+
def validate
|
236
|
+
end
|
237
|
+
|
238
|
+
def hash
|
239
|
+
@digest.hash
|
240
|
+
end
|
241
|
+
|
242
|
+
def eql?(other)
|
243
|
+
return false if !super(other)
|
244
|
+
return false if other.digest != digest
|
245
|
+
true
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2021 Teak.io, Inc.
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
module ConfigOMat
|
18
|
+
VERSION = "0.1.0"
|
19
|
+
end
|
data/lib/version.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2021 Teak.io, Inc.
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
module ConfigOMat
|
18
|
+
VERSION = "0.1.0"
|
19
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# Copyright 2021 Teak.io, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
# By enabling this unit for a service, we ensure that after the service starts systemd
|
16
|
+
# listens for touches on the configurator's restart file, and will run teak-configurator-restart-service@%i.service
|
17
|
+
# when the file is touched.
|
18
|
+
#
|
19
|
+
# This allows the configurator to restart systemd services without privileged access.
|
20
|
+
[Unit]
|
21
|
+
Description=Touch a file to restart %i.service
|
22
|
+
|
23
|
+
[Path]
|
24
|
+
PathModified=/run/teak-configurator/%i.restart
|
25
|
+
|
26
|
+
[Install]
|
27
|
+
WantedBy=%i.service
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# Copyright 2021 Teak.io, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
[Unit]
|
16
|
+
Description=Restart %i.service
|
17
|
+
|
18
|
+
[Service]
|
19
|
+
Type=oneshot
|
20
|
+
ExecStart=/usr/bin/systemctl restart %i.service
|
21
|
+
RemainAfterExit=no
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# Copyright 2021 Teak.io, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
# By enabling this unit for a service, we ensure that after the service starts systemd
|
16
|
+
# listens for touches on the configurator's start file, and will run teak-configurator-start-service@%i.service
|
17
|
+
# when the file is touched.
|
18
|
+
#
|
19
|
+
# This allows the configurator to start systemd services without privileged access.
|
20
|
+
[Unit]
|
21
|
+
Description=Touch a file to start %i.service
|
22
|
+
|
23
|
+
[Path]
|
24
|
+
PathModified=/run/teak-configurator/%i.start
|
25
|
+
|
26
|
+
[Install]
|
27
|
+
WantedBy=%i.service
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# Copyright 2021 Teak.io, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
[Unit]
|
16
|
+
Description=Start %i.service
|
17
|
+
|
18
|
+
[Service]
|
19
|
+
Type=oneshot
|
20
|
+
ExecStart=/usr/bin/systemctl start %i.service
|
21
|
+
RemainAfterExit=no
|