Refactoring. Adding pkg mgmt.
Dev

Dev commited on 2018-06-15 07:44:47
Showing 13 changed files, with 259 additions and 95 deletions.

... ...
@@ -16,3 +16,11 @@
16 16
     mode: 0644
17 17
     local_file: hello_world.php
18 18
     desired_state: present
19
+
20
+- services:
21
+  - name: apache2
22
+    dependencies:
23
+    - packages:
24
+      - name: libapache2-mod-php5
25
+    - files:
26
+      - path: /tmp/hello_world.php
... ...
@@ -0,0 +1,26 @@
1
+- packages:
2
+  - name: apache2
3
+    version: 2.4.18-2ubuntu3.8
4
+    desired_state: installed
5
+  - name: php5
6
+    version: 5.5.9+dfsg-1ubuntu4.25
7
+    desired_state: installed
8
+  - name: libapache2-mod-php5
9
+    version: 5.5.9+dfsg-1ubuntu4.25
10
+    desired_state: installed
11
+
12
+- files:
13
+  - path: /tmp/hello_world.php
14
+    owner: apache2
15
+    group: apache2
16
+    mode: 0644
17
+    local_file: hello_world.php
18
+    desired_state: present
19
+
20
+- services:
21
+  - name: apache2
22
+    dependencies:
23
+    - packages:
24
+      - name: libapache2-mod-php5
25
+    - files:
26
+      - path: /tmp/hello_world.php
... ...
@@ -0,0 +1,2 @@
1
+module RCM
2
+end
... ...
@@ -0,0 +1,69 @@
1
+require_relative '../rcm_utils'
2
+
3
+module RCM
4
+  class Apt
5
+    APT_GET = '/usr/bin/apt-get'.freeze
6
+    DPKG_QUERY = '/usr/bin/dpkg-query'.freeze
7
+    APT_CACHE = '/usr/bin/apt-cache'.freeze
8
+
9
+    def update()
10
+      status = ::RCM.cmd("#{APT_GET} update")
11
+      raise "Updating apt cache failed. \n\n#{status[:error]}\n\nstdout:\n#{status[:output]}"
12
+    end
13
+
14
+    def status(pkg_name)
15
+      ::RCM.cmd("#{DPKG_QUERY} --status #{pkg_name}")
16
+    end
17
+
18
+    def install(pkg_name, version = '')
19
+      name = ''
20
+      version_string = "=#{version}" unless version.empty?
21
+      if pkg_name.is_a?(String)
22
+        name = pkg_name
23
+        version_string = "=#{version}" unless version.empty?
24
+      elsif pkg_name.is_a?(::RCM::Package)
25
+        name = pkg_name.name
26
+        v = pkg_name.version
27
+        version_string = "=#{v}" unless v.empty?
28
+      end
29
+
30
+      ::RCM.cmd("#{APT_GET} install --yes #{name}#{version_string}")
31
+    end
32
+
33
+    def multi_install(pkgs)
34
+      raise 'multi_install expects array of ::RCM.Package' unless pkgs.is_a?(Array)
35
+
36
+      # Construct string of packages to install in one go.
37
+      pkgs_string = ''
38
+      pkgs.each do |pkg|
39
+        version_string = "=#{pkg.version}" unless pkg.version.empty?
40
+        pkgs_string += "#{pkg.name}#{version_string} "
41
+      end
42
+
43
+      ::RCM.cmd("#{APT_GET} install --yes #{pkgs_string}")
44
+    end
45
+
46
+    def get_versions(pkg_name)
47
+      name = ''
48
+      if pkg_name.is_a?(String)
49
+        name = pkg_name
50
+      elsif pkg_name.is_a?(::RCM::Package)
51
+        name = pkg_name.name
52
+      end
53
+      status = ::RCM.cmd("#{APT_CACHE} madison #{name}")
54
+      return [] if !status[:output] || status[:output] =~ /Unable to locate package/
55
+
56
+      # Collect all versions by splitting on new line, and then collecting second
57
+      # field from every line with delimiter ' | '
58
+      available_versions = status[:output].split("\n").collect { |l| l.split(' | ')[1] }
59
+      available_versions.uniq
60
+    end
61
+
62
+    def remove(pkg_name)
63
+      # Remove is idempotent and returns with success even if package is not installed.
64
+      status = ::RCM.cmd("#{APT_GET} remove --yes #{pkg_name}")
65
+      error_message = "Removing #{pkg_name} failed!\n\nstderr:\n#{status[:error]}\n\nstdout:\n#{status[:output]}"
66
+      raise error_message unless status[:exit_code] == 0
67
+    end
68
+  end
69
+end
... ...
@@ -0,0 +1,2 @@
1
+module RCM
2
+end
... ...
@@ -0,0 +1,31 @@
1
+module RCM
2
+  class File
3
+    attr_accessor :path, :owner, :group, :mode, :checksum
4
+
5
+    def ==(other)
6
+      return false unless other.is_a?(RCM::File)
7
+
8
+      @path == other.path &&
9
+          @owner == other.owner &&
10
+          @group == other.group &&
11
+          @mode == other.mode &&
12
+          @checksum == other.checksum
13
+    end
14
+
15
+    def initialize(path, owner, group, mode, checksum)
16
+      @path = path
17
+      @owner = owner
18
+      @group = group
19
+      @mode = mode
20
+      @checksum = checksum
21
+    end
22
+
23
+    def to_s
24
+      "Path = #{@path}\n" +
25
+      "Owner = #{@owner}\n" +
26
+      "Group = #{@group}\n" +
27
+      "Mode = #{@mode}\n" +
28
+      "Checksum = #{@checksum}"
29
+    end
30
+  end
31
+end
... ...
@@ -0,0 +1,29 @@
1
+module RCM
2
+  class Package
3
+    attr_accessor :name, :version, :state
4
+
5
+    # Public state constants
6
+    INSTALLED = 'installed'.freeze
7
+    REMOVED = 'removed'.freeze
8
+
9
+    def ==(other)
10
+      return false unless other.is_a?(RCM::Package)
11
+
12
+      @name == other.name &&
13
+      @version == other.version &&
14
+      @state == other.state
15
+    end
16
+
17
+    def initialize(name, version, state)
18
+      @name = name
19
+      @version = version
20
+      @state = state
21
+    end
22
+
23
+    def to_s
24
+      "Name = #{@name}\n" +
25
+      "Version = #{@version}\n" +
26
+      "State = #{@state}\n"
27
+    end
28
+  end
29
+end
... ...
@@ -1,6 +1,6 @@
1 1
 module RCM
2 2
   class Service
3
-    attr_accessor :name, :installed, :definition_path, :running, :enabled
3
+    attr_accessor :name, :installed, :definition_path, :running, :enabled, :depends
4 4
 
5 5
     def ==(other)
6 6
       return false unless other.is_a?(RCM::Service)
... ...
@@ -1,43 +0,0 @@
1
-module RCM
2
-  class File
3
-    attr_accessor :path, :owner, :group, :mode, :local_content_md5, :target_content_md5, :current_state, :desired_state
4
-
5
-    def ==(other)
6
-      return false unless other.is_a?(RCM::File)
7
-
8
-      @path == other.path &&
9
-          @owner == other.owner &&
10
-          @group == other.group &&
11
-          @mode == other.mode &&
12
-          @content_md5 == other.content_md5 &&
13
-          @desired_state == other.desired_state &&
14
-          @current_state == other.current_state
15
-    end
16
-
17
-    def initialize(path, owner, group, mode, local_content_md5, target_content_md5, current_state, desired_state)
18
-      @path = path
19
-      @owner = owner
20
-      @group = group
21
-      @mode = mode
22
-      @local_content_md5 = local_content_md5
23
-      @target_content_md5 = target_content_md5
24
-      @current_state = current_state
25
-      @desired_state = desired_state
26
-    end
27
-
28
-    def lackin(other)
29
-      raise 'not implemented.'
30
-    end
31
-
32
-    def to_s
33
-      "Path = #{@path}\n" +
34
-      "Owner = #{@owner}\n" +
35
-      "Group = #{@group}\n" +
36
-      "Mode = #{@mode}\n" +
37
-      "Local Content MD5 = #{@local_content_md5}\n" +
38
-      "Target Content MD5 = #{@target_content_md5}\n" +
39
-      "Desired State = #{@desired_state}\n" +
40
-      "Current State = #{@current_state}"
41
-    end
42
-  end
43
-end
... ...
@@ -1,29 +0,0 @@
1
-module RCM
2
-  class Package
3
-    attr_accessor :name, :version, :current_state, :desired_state
4
-
5
-    def ==(other)
6
-      return false unless other.is_a?(RCM::Package)
7
-
8
-      @installed = other.installed &&
9
-      @name == other.name &&
10
-      @version == other.version &&
11
-      @current_state == other.current_state &&
12
-      @desired_state == other.desired_state
13
-    end
14
-
15
-    def initialize(name, version, current_state, desired_state)
16
-      @name = name
17
-      @version = version
18
-      @current_state = current_state
19
-      @desired_state = desired_state
20
-    end
21
-
22
-    def to_s
23
-      "Name = #{@name}\n" +
24
-      "Version = #{@version}\n" +
25
-      "Current State = #{@current_state}\n" +
26
-      "Desired State = #{@desired_state}"
27
-    end
28
-  end
29
-end
... ...
@@ -1,21 +1,22 @@
1 1
 require 'open3'
2 2
 
3 3
 module RCM
4
-  module_function :cmd
5 4
 
6 5
   def cmd(command)
7 6
     stdin, stdout, stderr, wait_thr = Open3.popen3(command)
8 7
     output = stdout.gets(nil)
9 8
     stdout.close
10
-    errors = stderr.gets(nil)
9
+    error = stderr.gets(nil)
11 10
     stderr.close
12 11
     exit_code = Integer(wait_thr.value)
13 12
 
14 13
     {
15 14
       exit_code: exit_code,
16 15
       output: output,
17
-      errors: errors
16
+      error: error
18 17
     }
19 18
 
20 19
   end
20
+
21
+  module_function :cmd
21 22
 end
... ...
@@ -1,6 +1,7 @@
1
-require_relative 'rcm_file'
2
-require_relative 'rcm_package'
3
-require_relative 'rcm_service'
1
+require_relative 'objects/rcm_file'
2
+require_relative 'objects/rcm_package'
3
+require_relative 'objects/rcm_service'
4
+require_relative 'managers/rcm_package_manager'
4 5
 require 'yaml'
5 6
 
6 7
 module RCM
... ...
@@ -9,42 +10,72 @@ module RCM
9 10
   SERVICES = 'services'
10 11
 
11 12
   @@wanted = {
12
-    PACKAGES => [],
13
-    FILES => [],
14
-    SERVICES => []
13
+    PACKAGES => {},
14
+    FILES => {},
15
+    SERVICES => {}
15 16
   }
16 17
 
17 18
   @@got = {
19
+    PACKAGES => {},
20
+    FILES => {},
21
+    SERVICES => {}
22
+  }
23
+
24
+  @@crackalackin = {
18 25
       PACKAGES => [],
19 26
       FILES => [],
20 27
       SERVICES => []
21 28
   }
22 29
 
23
-  def converge(old_state, new_state)
24
-    if old_state.is_a?(RCM::File)
30
+  @@pkg_mgr = ::RCM::Apt.new()
31
+
32
+  def self.parse_packages(markup)
33
+    markup.each do |d|
34
+      # ensure package version exists, if mentioned.
35
+      version = d.fetch('version', '')
36
+      desired_state = d.fetch('desired_state', ::RCM::Package::INSTALLED)
37
+
38
+      valid_package_states = [::RCM::Package::INSTALLED, ::RCM::Package::REMOVED]
39
+
40
+      if (valid_package_states & [desired_state]).empty?
41
+        raise "Unknown desired_state: #{desired_state}. Valid states: #{valid_package_states.join(', ')}"
25 42
       end
26 43
 
44
+      unless version.empty?
45
+        available_versions = @@pkg_mgr.get_versions(d['name'])
46
+        if (available_versions & [version]).empty?
47
+          raise "\n\n#{d['name']} = #{version} is not available.\nAvailable versions: #{available_versions.join(', ')}"
48
+        end
49
+      end
50
+      p = RCM::Package.new(d['name'], version, desired_state)
51
+      @@wanted[PACKAGES][d['name']] = p
52
+    end
27 53
   end
28 54
 
29
-  def whachuwant()
55
+
56
+  def whachuwant() # or parse_config
57
+    env = ENV.fetch('ENVIRONMENT', '')
30 58
     # Consume config from YAML, and convert it to usable objects in @@wanted.
31
-    raise 'config.yaml not found.' unless ::File.file?('config.yaml')
59
+    config_file = env.empty? ? 'config.yaml' : "config_#{env}.yaml"
60
+
61
+    raise "#{config_file} not found." unless ::File.readable?(config_file)
32 62
 
33
-    config = YAML.load_file('config.yaml')
63
+    config = YAML.load_file(config_file)
34 64
     config.each do |yaml_objects|
35 65
       yaml_objects.each do |coll, defs|
36 66
         case coll
37 67
         when PACKAGES
68
+          parse_packages(defs)
69
+
70
+        when FILES
38 71
           defs.each do |d|
39
-            p = RCM::Package.new(d['name'], d['version'], 'idk', d['desired_state'])
40
-            @@wanted[PACKAGES].push(p)
72
+            f = RCM::File.new(d['path'], d['owner'], d['group'], d['mode'], 'idk')
73
+            @@wanted[FILES][d['path']] = f
41 74
           end
42 75
 
43
-        when FILES
76
+        when SERVICES
44 77
           defs.each do |d|
45
-            f = RCM::File.new(d['path'], d['owner'], d['group'], d['mode'], 'idk',
46
-                              'idk', 'idk', d[''])
47
-            @@wanted[FILES].push(f)
78
+            puts d
48 79
           end
49 80
         end
50 81
       end
... ...
@@ -52,12 +83,49 @@ module RCM
52 83
 
53 84
   end
54 85
 
55
-  def whachugot()
86
+  def whachugot() # or get_current_state
87
+    @@wanted[PACKAGES].each do |name, p|
88
+      state = ::RCM::Package::REMOVED
89
+      version = 'idk'
90
+      begin
91
+        status = @@pkg_mgr.status(p.name)
92
+
93
+        if status[:exit_code] == 0
94
+          state = ::RCM::Package::INSTALLED if status[:output] =~ 'ok installed'
95
+          version = /Version: (?<version>\d+.+)/.match(status[:output])[:version]
96
+        end
97
+
98
+        current_pkg = ::RCM::Package.new(name, version, state)
99
+
100
+        @@got[PACKAGES][name] = current_pkg
101
+      rescue Exception => e
102
+        puts e.message
103
+      end
104
+    end
105
+  end
106
+
107
+  def crackalackin_packages()
108
+    @@wanted[PACKAGES].each do |pkg_name, pkg_obj|
109
+      current_pkg = @@got[PACKAGES][pkg_name]
110
+
111
+      next if current_pkg.state == ::RCM::Package::INSTALLED &&
112
+          current_pkg.version == pkg_obj.version
56 113
 
114
+      # Set the version correctly
115
+      current_pkg.version = pkg_obj.version
116
+      @@crackalackin[PACKAGES].push(current_pkg)
57 117
     end
58 118
 
59
-  module_function :whachuwant, :whachugot
119
+    puts @@crackalackin[PACKAGES]
120
+  end
121
+
122
+  def cracka_stop_lackin()
123
+    puts 'not implemented'
124
+  end
125
+  module_function :whachuwant, :whachugot, :crackalackin_packages, :cracka_stop_lackin
60 126
 
61 127
 end
62 128
 
63 129
 RCM.whachuwant
130
+RCM.whachugot
131
+RCM.crackalackin_packages
64 132