Refactoring to have config parsing in its own file.
Dev

Dev commited on 2018-06-19 05:36:30
Showing 3 changed files, with 182 additions and 131 deletions.

... ...
@@ -1,3 +1,7 @@
1
+- logging:
2
+  - level: debug
3
+    file: STDOUT
4
+
1 5
 - packages:
2 6
   - name: apache2
3 7
     version: 2.4.18-2ubuntu3.8
... ...
@@ -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