Dev commited on 2018-06-19 05:36:30
Showing 3 changed files, with 182 additions and 131 deletions.
... | ... |
@@ -0,0 +1,159 @@ |
1 |
+module RCM |
|
2 |
+ class ConfigManager |
|
3 |
+ PACKAGES = 'packages'.freeze |
|
4 |
+ FILES = 'files'.freeze |
|
5 |
+ SERVICES = 'services'.freeze |
|
6 |
+ LOGGING = 'logging'.freeze |
|
7 |
+ |
|
8 |
+ @wanted = { |
|
9 |
+ PACKAGES => {}, |
|
10 |
+ FILES => {}, |
|
11 |
+ SERVICES => {} |
|
12 |
+ } |
|
13 |
+ |
|
14 |
+ @logger = nil |
|
15 |
+ @env = '' |
|
16 |
+ |
|
17 |
+ VALID_LOG_LEVELS = %w[fatal error info debug].freeze |
|
18 |
+ |
|
19 |
+ def initialize() |
|
20 |
+ @env = ENV.fetch('ENVIRONMENT', '') |
|
21 |
+ config_file = @env.empty? ? 'config.yaml' : "config_#{@env}.yaml" |
|
22 |
+ |
|
23 |
+ raise "#{config_file} not found." unless ::File.readable?(config_file) |
|
24 |
+ |
|
25 |
+ config = YAML.load_file(config_file) |
|
26 |
+ config.each do |yaml_objects| |
|
27 |
+ yaml_objects.each do |collection, resource_definition| |
|
28 |
+ case collection |
|
29 |
+ when LOGGING |
|
30 |
+ configure_logging(resource_definition) |
|
31 |
+ |
|
32 |
+ when PACKAGES |
|
33 |
+ parse_packages(resource_definition) |
|
34 |
+ |
|
35 |
+ when FILES |
|
36 |
+ parse_files(resource_definition) |
|
37 |
+ |
|
38 |
+ when SERVICES |
|
39 |
+ parse_services(resource_definition) |
|
40 |
+ end # of case |
|
41 |
+ end # of iterating over yaml_objects |
|
42 |
+ end # of iterating over config |
|
43 |
+ end |
|
44 |
+ |
|
45 |
+ def logger |
|
46 |
+ @logger |
|
47 |
+ end |
|
48 |
+ |
|
49 |
+ def packages |
|
50 |
+ @wanted[PACKAGES] |
|
51 |
+ end |
|
52 |
+ |
|
53 |
+ def files |
|
54 |
+ @wanted[FILES] |
|
55 |
+ end |
|
56 |
+ |
|
57 |
+ def services |
|
58 |
+ @wanted[SERVICES] |
|
59 |
+ end |
|
60 |
+ |
|
61 |
+ def configure_logging(markup) |
|
62 |
+ file = markup[0]['file'] |
|
63 |
+ device = '' |
|
64 |
+ level = markup[0]['level'] |
|
65 |
+ |
|
66 |
+ if (VALID_LOG_LEVELS & [level]).empty? |
|
67 |
+ raise "Invalid log level '#{level}'. Valid log levels are: #{VALID_LOG_LEVELS.join(', ')}" |
|
68 |
+ end |
|
69 |
+ |
|
70 |
+ case file |
|
71 |
+ when 'STDOUT' |
|
72 |
+ device = STDOUT |
|
73 |
+ |
|
74 |
+ when 'STDERR' |
|
75 |
+ device = STDERR |
|
76 |
+ |
|
77 |
+ else |
|
78 |
+ # treat whatever is written as file |
|
79 |
+ device = ::File.open(file, 'a+') |
|
80 |
+ end |
|
81 |
+ |
|
82 |
+ @logger = ::Logger.new(device) |
|
83 |
+ @logger.level = level |
|
84 |
+ @logger.progname = 'rcm' |
|
85 |
+ @logger |
|
86 |
+ end |
|
87 |
+ |
|
88 |
+ def parse_packages(markup) |
|
89 |
+ @logger.info("Environment = #{@env}") |
|
90 |
+ |
|
91 |
+ markup.each do |d| |
|
92 |
+ # ensure package version exists, if mentioned. |
|
93 |
+ version = d.fetch('version', '') |
|
94 |
+ desired_state = d.fetch('desired_state', ::RCM::Package::INSTALLED) |
|
95 |
+ |
|
96 |
+ valid_package_states = [::RCM::Package::INSTALLED, ::RCM::Package::REMOVED] |
|
97 |
+ |
|
98 |
+ if (valid_package_states & [desired_state]).empty? |
|
99 |
+ raise "Unknown desired_state: #{desired_state}. Valid states: #{valid_package_states.join(', ')}" |
|
100 |
+ end |
|
101 |
+ |
|
102 |
+ unless version.empty? |
|
103 |
+ # Convenience... check if the version mentioned in config is correct. |
|
104 |
+ pkg_mgr = ::RCM::Apt.new(@logger) |
|
105 |
+ available_versions = pkg_mgr.get_versions(d['name']) |
|
106 |
+ if (available_versions & [version]).empty? |
|
107 |
+ raise "\n\n#{d['name']} = #{version} is not available.\nAvailable versions: #{available_versions.join(', ')}" |
|
108 |
+ end |
|
109 |
+ end |
|
110 |
+ p = RCM::Package.new(d['name'], version, desired_state) |
|
111 |
+ @wanted[PACKAGES][d['name']] = p |
|
112 |
+ end |
|
113 |
+ end |
|
114 |
+ |
|
115 |
+ def parse_files(markup) |
|
116 |
+ valid_file_states = [::RCM::File::PRESENT, ::RCM::File::ABSENT] |
|
117 |
+ markup.each do |d| |
|
118 |
+ raise "\n\nFile state can only be one of #{valid_file_states.join(', ')}" if (valid_file_states | [d['desired_state']]).empty? |
|
119 |
+ raise "\n\n'#{d['local_file']}' is not readable.\n\n" if d['desired_state'] == ::RCM::File::PRESENT && !::File.readable?(d['local_file']) |
|
120 |
+ f = RCM::File.new(d['path'], d['owner'], d['group'], d['mode'], d['local_file'], d['desired_state']) |
|
121 |
+ @wanted[FILES][d['path']] = f |
|
122 |
+ end |
|
123 |
+ end |
|
124 |
+ |
|
125 |
+ def parse_services(markup) |
|
126 |
+ pkgs = @wanted[PACKAGES] |
|
127 |
+ files = @wanted[FILES] |
|
128 |
+ file_dependencies = {} |
|
129 |
+ package_dependencies = {} |
|
130 |
+ markup.each do |d| |
|
131 |
+ dependencies = d.fetch('dependencies', {}) |
|
132 |
+ dependencies.each do |values| |
|
133 |
+ values.each do |dep_type, dep| |
|
134 |
+ case dep_type |
|
135 |
+ when PACKAGES |
|
136 |
+ dep.each do |pkg_definition| |
|
137 |
+ pkg_name = pkg_definition['name'] |
|
138 |
+ raise "\n\nPlease ensure #{pkg_name} is managed by this program before adding it as a dependency.\n\n" unless pkgs.key?(pkg_name) |
|
139 |
+ p = pkgs[pkg_name] |
|
140 |
+ package_dependencies[pkg_name] = p |
|
141 |
+ end |
|
142 |
+ when FILES |
|
143 |
+ dep.each do |file_defnition| |
|
144 |
+ path = file_defnition['path'] |
|
145 |
+ raise "\n\nPlease ensure #{path} is managed by this program before adding it as a dependency.\n\n" unless files.key?(path) |
|
146 |
+ |
|
147 |
+ f = files[path] |
|
148 |
+ file_dependencies[path] = f |
|
149 |
+ end |
|
150 |
+ end |
|
151 |
+ end |
|
152 |
+ end |
|
153 |
+ s = RCM::Service.new(d['name'], file_dependencies, package_dependencies) |
|
154 |
+ @wanted[SERVICES][d['name']] = s |
|
155 |
+ end |
|
156 |
+ end |
|
157 |
+ |
|
158 |
+ end # of class ConfigManager |
|
159 |
+end # module |
... | ... |
@@ -3,6 +3,7 @@ |
3 | 3 |
require_relative 'objects/rcm_file' |
4 | 4 |
require_relative 'objects/rcm_package' |
5 | 5 |
require_relative 'objects/rcm_service' |
6 |
+require_relative 'managers/rcm_config_manager' |
|
6 | 7 |
require_relative 'managers/rcm_package_manager' |
7 | 8 |
require_relative 'managers/rcm_file_manager' |
8 | 9 |
require_relative 'managers/rcm_service_manager' |
... | ... |
@@ -11,137 +12,27 @@ require 'yaml' |
11 | 12 |
require 'logger' |
12 | 13 |
|
13 | 14 |
module RCM |
14 |
- PACKAGES = 'packages'.freeze |
|
15 |
- FILES = 'files'.freeze |
|
16 |
- SERVICES = 'services'.freeze |
|
17 |
- VALID_LOG_LEVELS = %w(fatal error info debug).freeze |
|
18 |
- RCM_ENV = ENV.fetch('ENVIRONMENT', '') |
|
19 |
- |
|
20 |
- @wanted = { |
|
21 |
- PACKAGES => {}, |
|
22 |
- FILES => {}, |
|
23 |
- SERVICES => {} |
|
24 |
- } |
|
25 | 15 |
|
16 |
+ @config_mgr = ::RCM::ConfigManager.new() |
|
26 | 17 |
@got = { |
27 |
- PACKAGES => {}, |
|
28 |
- FILES => {}, |
|
29 |
- SERVICES => {} |
|
18 |
+ ::RCM::ConfigManager::PACKAGES => {}, |
|
19 |
+ ::RCM::ConfigManager::FILES => {}, |
|
20 |
+ ::RCM::ConfigManager::SERVICES => {} |
|
30 | 21 |
} |
31 | 22 |
|
32 |
- @logger = ::Logger.new(STDOUT) |
|
33 |
- |
|
23 |
+ @logger = @config_mgr.logger |
|
34 | 24 |
@pkg_mgr = ::RCM::Apt.new(@logger) |
35 | 25 |
@file_mgr = ::RCM::FileManager.new(@logger) |
36 | 26 |
@svc_mgr = ::RCM::ServiceManager.new(@logger) |
37 | 27 |
|
38 |
- def self.configure_logger |
|
39 |
- # Can be exposed as settings but was expanding the scope as this |
|
40 |
- # object will need to be passed to all other objects |
|
41 |
- @logger.level = ::Logger::DEBUG |
|
42 |
- @logger.progname = 'rcm' |
|
43 |
- end |
|
44 |
- |
|
45 |
- def self.parse_packages(markup) |
|
46 |
- @logger.info("Environment = #{RCM_ENV}") |
|
47 |
- @pkg_mgr.update unless RCM_ENV == 'dev' |
|
48 |
- |
|
49 |
- markup.each do |d| |
|
50 |
- # ensure package version exists, if mentioned. |
|
51 |
- version = d.fetch('version', '') |
|
52 |
- desired_state = d.fetch('desired_state', ::RCM::Package::INSTALLED) |
|
53 |
- |
|
54 |
- valid_package_states = [::RCM::Package::INSTALLED, ::RCM::Package::REMOVED] |
|
55 |
- |
|
56 |
- if (valid_package_states & [desired_state]).empty? |
|
57 |
- raise "Unknown desired_state: #{desired_state}. Valid states: #{valid_package_states.join(', ')}" |
|
58 |
- end |
|
59 |
- |
|
60 |
- unless version.empty? |
|
61 |
- available_versions = @pkg_mgr.get_versions(d['name']) |
|
62 |
- if (available_versions & [version]).empty? |
|
63 |
- raise "\n\n#{d['name']} = #{version} is not available.\nAvailable versions: #{available_versions.join(', ')}" |
|
64 |
- end |
|
65 |
- end |
|
66 |
- p = RCM::Package.new(d['name'], version, desired_state) |
|
67 |
- @wanted[PACKAGES][d['name']] = p |
|
68 |
- end |
|
69 |
- end |
|
70 |
- |
|
71 |
- def self.parse_files(markup) |
|
72 |
- valid_file_states = [::RCM::File::PRESENT, ::RCM::File::ABSENT] |
|
73 |
- markup.each do |d| |
|
74 |
- raise "\n\nFile state can only be one of #{valid_file_states.join (', ')}" if (valid_file_states | [d['desired_state']]).empty? |
|
75 |
- raise "\n\n'#{d['local_file']}' is not readable.\n\n" if d['desired_state'] == ::RCM::File::PRESENT && !::File.readable?(d['local_file']) |
|
76 |
- f = RCM::File.new(d['path'], d['owner'], d['group'], d['mode'], d['local_file'], d['desired_state']) |
|
77 |
- @wanted[FILES][d['path']] = f |
|
78 |
- end |
|
79 |
- end |
|
80 |
- |
|
81 |
- def self.parse_services(markup) |
|
82 |
- pkgs = @wanted[PACKAGES] |
|
83 |
- files = @wanted[FILES] |
|
84 |
- file_dependencies = {} |
|
85 |
- package_dependencies = {} |
|
86 |
- markup.each do |d| |
|
87 |
- dependencies = d.fetch('dependencies', {}) |
|
88 |
- dependencies.each do |values| |
|
89 |
- values.each do |dep_type, dep| |
|
90 |
- case dep_type |
|
91 |
- when PACKAGES |
|
92 |
- dep.each do |pkg_definition| |
|
93 |
- pkg_name = pkg_definition['name'] |
|
94 |
- raise "\n\nPlease ensure #{pkg_name} is managed by this program before adding it as a dependency.\n\n" unless pkgs.key?(pkg_name) |
|
95 |
- p = pkgs[pkg_name] |
|
96 |
- package_dependencies[pkg_name] = p |
|
97 |
- end |
|
98 |
- when FILES |
|
99 |
- dep.each do |file_defnition| |
|
100 |
- path = file_defnition['path'] |
|
101 |
- raise "\n\nPlease ensure #{path} is managed by this program before adding it as a dependency.\n\n" unless files.key?(path) |
|
102 |
- |
|
103 |
- f = files[path] |
|
104 |
- file_dependencies[path] = f |
|
105 |
- end |
|
106 |
- end |
|
107 |
- end |
|
108 |
- |
|
109 |
- end |
|
110 |
- s = RCM::Service.new(d['name'], file_dependencies, package_dependencies) |
|
111 |
- @wanted[SERVICES][d['name']] = s |
|
112 |
- end |
|
113 |
- end |
|
114 |
- |
|
115 |
- def self.whachuwant # or parse_config |
|
116 |
- # Consume config from YAML, and convert it to usable objects in @wanted. |
|
117 |
- config_file = RCM_ENV.empty? ? 'config.yaml' : "config_#{RCM_ENV}.yaml" |
|
118 |
- |
|
119 |
- raise "#{config_file} not found." unless ::File.readable?(config_file) |
|
120 |
- |
|
121 |
- config = YAML.load_file(config_file) |
|
122 |
- config.each do |yaml_objects| |
|
123 |
- yaml_objects.each do |collection, resource_definition| |
|
124 |
- case collection |
|
125 |
- when PACKAGES |
|
126 |
- parse_packages(resource_definition) |
|
127 |
- |
|
128 |
- when FILES |
|
129 |
- parse_files(resource_definition) |
|
130 |
- |
|
131 |
- when SERVICES |
|
132 |
- parse_services(resource_definition) |
|
133 |
- end |
|
134 |
- end |
|
135 |
- end |
|
136 |
- |
|
137 |
- end |
|
138 |
- |
|
139 |
- def self.converge_packages |
|
28 |
+ def self.converge_packages(current_state_of_packages) |
|
140 | 29 |
# We'll install them in one go (minor optimization) |
141 | 30 |
missing_packages = [] |
142 | 31 |
|
143 |
- @wanted[PACKAGES].each do |pkg_name, pkg_obj| |
|
144 |
- current_pkg = @got[PACKAGES][pkg_name] |
|
32 |
+ @pkg_mgr.update unless @env == 'dev' |
|
33 |
+ |
|
34 |
+ @config_mgr.packages.each do |pkg_name, pkg_obj| |
|
35 |
+ current_pkg = current_state_of_packages[pkg_name] |
|
145 | 36 |
|
146 | 37 |
if current_pkg == pkg_obj |
147 | 38 |
@logger.info("#{pkg_name} is in expected state.") |
... | ... |
@@ -160,12 +51,11 @@ module RCM |
160 | 51 |
end |
161 | 52 |
|
162 | 53 |
@pkg_mgr.install(missing_packages) unless missing_packages.empty? |
163 |
- |
|
164 | 54 |
end |
165 | 55 |
|
166 |
- def self.converge_files |
|
167 |
- @wanted[FILES].each do |path, file_obj| |
|
168 |
- file_on_fs = @got[FILES][path] |
|
56 |
+ def self.converge_files(current_state_of_files) |
|
57 |
+ @config_mgr.files.each do |path, file_obj| |
|
58 |
+ file_on_fs = current_state_of_files[path] |
|
169 | 59 |
|
170 | 60 |
if file_on_fs.state == ::RCM::File::PRESENT && file_obj.state == ::RCM::File::ABSENT |
171 | 61 |
@logger.info("#{path} is present on disk when it should not be. Removing...") |
... | ... |
@@ -186,20 +75,18 @@ module RCM |
186 | 75 |
end |
187 | 76 |
|
188 | 77 |
def self.restart_services_if_needed |
189 |
- @wanted[SERVICES].values.each do |svc_obj| |
|
78 |
+ @config_mgr.services.values.each do |svc_obj| |
|
190 | 79 |
@svc_mgr.restart(svc_obj) if @svc_mgr.dependencies_changed?(svc_obj) |
191 | 80 |
end |
192 | 81 |
end |
193 | 82 |
|
194 | 83 |
def main |
195 |
- configure_logger |
|
196 |
- whachuwant |
|
197 | 84 |
# Installing packages creates file that we want to delete. |
198 | 85 |
# So finish managing packages and then deal with files. |
199 |
- @got[PACKAGES] = @pkg_mgr.get_current_state(@wanted[PACKAGES]) |
|
200 |
- converge_packages |
|
201 |
- @got[FILES] = @file_mgr.get_current_state(@wanted[FILES]) |
|
202 |
- converge_files |
|
86 |
+ current_state_of_packages = @pkg_mgr.get_current_state(@config_mgr.packages) |
|
87 |
+ converge_packages(current_state_of_packages) |
|
88 |
+ current_state_of_files = @file_mgr.get_current_state(@config_mgr.files) |
|
89 |
+ converge_files(current_state_of_files) |
|
203 | 90 |
restart_services_if_needed |
204 | 91 |
end |
205 | 92 |
|
206 | 93 |