Dev commited on 2018-06-15 07:44:47
Showing 13 changed files, with 259 additions and 95 deletions.
| ... | ... |
@@ -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,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,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,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 |