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 |