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 |