Dev commited on 2018-06-16 00:55:00
Showing 9 changed files, with 269 additions and 69 deletions.
... | ... |
@@ -2,19 +2,19 @@ |
2 | 2 |
- name: apache2 |
3 | 3 |
version: 2.4.18-2ubuntu3.8 |
4 | 4 |
desired_state: installed |
5 |
- - name: php5 |
|
6 |
- version: 5.5.9+dfsg-1ubuntu4.25 |
|
5 |
+ - name: php7.0 |
|
6 |
+ version: 7.0.30-0ubuntu0.16.04.1 |
|
7 | 7 |
desired_state: installed |
8 |
- - name: libapache2-mod-php5 |
|
9 |
- version: 5.5.9+dfsg-1ubuntu4.25 |
|
8 |
+ - name: libapache2-mod-php |
|
9 |
+ version: 1:7.0+35ubuntu6.1 |
|
10 | 10 |
desired_state: installed |
11 | 11 |
|
12 | 12 |
- files: |
13 | 13 |
- path: /tmp/hello_world.php |
14 |
- owner: apache2 |
|
15 |
- group: apache2 |
|
16 |
- mode: 0644 |
|
17 |
- local_file: hello_world.php |
|
14 |
+ owner: dev |
|
15 |
+ group: dev |
|
16 |
+ mode: '0664' |
|
17 |
+ local_file: /home/dev/src/rcm/resources/hello_world.php |
|
18 | 18 |
desired_state: present |
19 | 19 |
|
20 | 20 |
- services: |
... | ... |
@@ -1,2 +1,71 @@ |
1 |
+require_relative '../objects/rcm_file' |
|
2 |
+require_relative '../rcm_utils' |
|
3 |
+require 'fileutils' |
|
4 |
+ |
|
1 | 5 |
module RCM |
6 |
+ class FileManager |
|
7 |
+ def get_id_to_name_mapping(file) |
|
8 |
+ id_to_name = {} |
|
9 |
+ ::File.open(file, 'r').each do |l| |
|
10 |
+ matches = /(?<name>\w+):.+:(?<id>\d+):.*\s/.match(l) |
|
11 |
+ id_to_name[matches[:id]] = matches[:name] |
|
2 | 12 |
end |
13 |
+ id_to_name |
|
14 |
+ end |
|
15 |
+ |
|
16 |
+ def apply_attributes(file) |
|
17 |
+ raise 'apply_attributes only works with ::RCM::File' unless file.is_a?(::RCM::File) |
|
18 |
+ |
|
19 |
+ # Will throw exception if something poopy happens |
|
20 |
+ ::FileUtils.chown(file.owner, file.group, file.path) |
|
21 |
+ ::FileUtils.chmod(file.mode, file.path) |
|
22 |
+ |
|
23 |
+ file.changed = true |
|
24 |
+ file |
|
25 |
+ end |
|
26 |
+ |
|
27 |
+ def copy(file) |
|
28 |
+ dir = ::File.dirname(file.path) |
|
29 |
+ ::FileUtils.mkdir_p(dir) |
|
30 |
+ ::FileUtils.copy(file.src_path, file.path, remove_destination: true) |
|
31 |
+ |
|
32 |
+ file.changed = true |
|
33 |
+ file |
|
34 |
+ end |
|
35 |
+ |
|
36 |
+ def remove(file) |
|
37 |
+ file_existed = ::File.file?(file.path) |
|
38 |
+ ::FileUtils.rm(file.path) |
|
39 |
+ file.changed = true if file_existed |
|
40 |
+ file |
|
41 |
+ end |
|
42 |
+ |
|
43 |
+ def initialize(logger) |
|
44 |
+ @logger = logger |
|
45 |
+ end |
|
46 |
+ |
|
47 |
+ def get_current_state(wanted_files) |
|
48 |
+ got = {} |
|
49 |
+ uids = get_id_to_name_mapping('/etc/passwd') |
|
50 |
+ gids = get_id_to_name_mapping('/etc/group') |
|
51 |
+ wanted_files.each do |path, p| |
|
52 |
+ f = ::RCM::File.new('', '', '', '', '') |
|
53 |
+ # Put in an empty object if file does not exist |
|
54 |
+ unless ::File.exist?(path) |
|
55 |
+ got[path] = f |
|
56 |
+ next |
|
57 |
+ end |
|
58 |
+ |
|
59 |
+ f_stat = ::File.stat(path) |
|
60 |
+ f.path = path |
|
61 |
+ f.owner = uids[f_stat.uid.to_s] |
|
62 |
+ f.group = gids[f_stat.gid.to_s] |
|
63 |
+ f.mode = f_stat.mode.to_s(8)[-4, 4] |
|
64 |
+ got[path] = f |
|
65 |
+ end |
|
66 |
+ |
|
67 |
+ got |
|
68 |
+ end |
|
69 |
+ end |
|
70 |
+end |
|
71 |
+ |
... | ... |
@@ -5,10 +5,22 @@ module RCM |
5 | 5 |
APT_GET = '/usr/bin/apt-get'.freeze |
6 | 6 |
DPKG_QUERY = '/usr/bin/dpkg-query'.freeze |
7 | 7 |
APT_CACHE = '/usr/bin/apt-cache'.freeze |
8 |
+ APT_PARTIAL_DIR = '/var/lib/apt/lists/partial'.freeze |
|
9 |
+ |
|
10 |
+ def initialize(logger) |
|
11 |
+ @logger = logger |
|
12 |
+ end |
|
8 | 13 |
|
9 | 14 |
def update() |
15 |
+ @logger.info('Updating apt cache.') |
|
16 |
+ unless ::File.readable?(APT_PARTIAL_DIR) |
|
17 |
+ @logger.warn('Skipping apt cache update as we are not running with right privilege.') |
|
18 |
+ return |
|
19 |
+ end |
|
10 | 20 |
status = ::RCM.cmd("#{APT_GET} update") |
11 |
- raise "Updating apt cache failed. \n\n#{status[:error]}\n\nstdout:\n#{status[:output]}" |
|
21 |
+ unless status[:exit_code] == 0 |
|
22 |
+ @logger.warn("Updating apt cache failed. \n\n#{status[:error]}\n\nstdout:\n#{status[:output]}") |
|
23 |
+ end |
|
12 | 24 |
end |
13 | 25 |
|
14 | 26 |
def status(pkg_name) |
... | ... |
@@ -16,6 +28,8 @@ module RCM |
16 | 28 |
end |
17 | 29 |
|
18 | 30 |
def install(pkg_name, version = '') |
31 |
+ raise "\n\nPlease ensure you are running with root privileges" unless ::File.readable?(APT_PARTIAL_DIR) |
|
32 |
+ |
|
19 | 33 |
name = '' |
20 | 34 |
version_string = "=#{version}" unless version.empty? |
21 | 35 |
if pkg_name.is_a?(String) |
... | ... |
@@ -31,6 +45,8 @@ module RCM |
31 | 45 |
end |
32 | 46 |
|
33 | 47 |
def multi_install(pkgs) |
48 |
+ raise "\n\nPlease ensure you are running with root privileges\n\n" unless ::File.readable?(APT_PARTIAL_DIR) |
|
49 |
+ |
|
34 | 50 |
raise 'multi_install expects array of ::RCM.Package' unless pkgs.is_a?(Array) |
35 | 51 |
|
36 | 52 |
# Construct string of packages to install in one go. |
... | ... |
@@ -60,10 +76,33 @@ module RCM |
60 | 76 |
end |
61 | 77 |
|
62 | 78 |
def remove(pkg_name) |
79 |
+ raise "\n\nPlease ensure you are running with root privileges\n\n" unless ::File.readable?(APT_PARTIAL_DIR) |
|
80 |
+ |
|
63 | 81 |
# Remove is idempotent and returns with success even if package is not installed. |
64 | 82 |
status = ::RCM.cmd("#{APT_GET} remove --yes #{pkg_name}") |
65 | 83 |
error_message = "Removing #{pkg_name} failed!\n\nstderr:\n#{status[:error]}\n\nstdout:\n#{status[:output]}" |
66 | 84 |
raise error_message unless status[:exit_code] == 0 |
67 | 85 |
end |
86 |
+ |
|
87 |
+ def get_current_state(wanted_packages) |
|
88 |
+ got = {} |
|
89 |
+ wanted_packages.each do |name, p| |
|
90 |
+ state = ::RCM::Package::REMOVED |
|
91 |
+ version = 'idk' |
|
92 |
+ status = status(p.name) |
|
93 |
+ |
|
94 |
+ if status[:exit_code] == 0 |
|
95 |
+ state = ::RCM::Package::INSTALLED if status[:output] =~ 'ok installed' |
|
96 |
+ version = /Version: (?<version>\d+.+)/.match(status[:output])[:version] |
|
97 |
+ end |
|
98 |
+ |
|
99 |
+ current_pkg = ::RCM::Package.new(name, version, state) |
|
100 |
+ |
|
101 |
+ got[name] = current_pkg |
|
102 |
+ end |
|
103 |
+ |
|
104 |
+ got |
|
105 |
+ |
|
106 |
+ end |
|
68 | 107 |
end |
69 | 108 |
end |
... | ... |
@@ -1,23 +1,28 @@ |
1 |
+require 'fileutils' |
|
2 |
+ |
|
1 | 3 |
module RCM |
2 | 4 |
class File |
3 |
- attr_accessor :path, :owner, :group, :mode, :checksum |
|
5 |
+ attr_accessor :path, :owner, :group, :mode, :src_path, :changed |
|
4 | 6 |
|
5 | 7 |
def ==(other) |
6 | 8 |
return false unless other.is_a?(RCM::File) |
7 | 9 |
|
8 |
- @path == other.path && |
|
10 |
+ return false if @path.empty? || other.path.empty? |
|
11 |
+ |
|
12 |
+ ::FileUtils.compare_file(@path, other.path) && |
|
9 | 13 |
@owner == other.owner && |
10 | 14 |
@group == other.group && |
11 |
- @mode == other.mode && |
|
12 |
- @checksum == other.checksum |
|
15 |
+ @mode == other.mode |
|
16 |
+ |
|
13 | 17 |
end |
14 | 18 |
|
15 |
- def initialize(path, owner, group, mode, checksum) |
|
19 |
+ def initialize(path, owner, group, mode, src_path) |
|
16 | 20 |
@path = path |
17 | 21 |
@owner = owner |
18 | 22 |
@group = group |
19 | 23 |
@mode = mode |
20 |
- @checksum = checksum |
|
24 |
+ @src_path = src_path |
|
25 |
+ @changed = false |
|
21 | 26 |
end |
22 | 27 |
|
23 | 28 |
def to_s |
... | ... |
@@ -25,7 +30,8 @@ module RCM |
25 | 30 |
"Owner = #{@owner}\n" + |
26 | 31 |
"Group = #{@group}\n" + |
27 | 32 |
"Mode = #{@mode}\n" + |
28 |
- "Checksum = #{@checksum}" |
|
33 |
+ "Source Path = #{src_path}\n" + |
|
34 |
+ "Changed = #{@changed}" |
|
29 | 35 |
end |
30 | 36 |
end |
31 | 37 |
end |
... | ... |
@@ -1,28 +1,47 @@ |
1 | 1 |
module RCM |
2 | 2 |
class Service |
3 |
- attr_accessor :name, :installed, :definition_path, :running, :enabled, :depends |
|
3 |
+ attr_accessor :name, :depends_file, :depends_package |
|
4 | 4 |
|
5 | 5 |
def ==(other) |
6 | 6 |
return false unless other.is_a?(RCM::Service) |
7 | 7 |
|
8 |
- @name == other.name && @installed == other.installed && @definition_path == other.definition_path && |
|
9 |
- @running == other.running && @enabled == other.enabled |
|
8 |
+ return false unless @name == other.name |
|
9 |
+ |
|
10 |
+ @depends_file.each do |dep_name, dep_object| |
|
11 |
+ other_depends = other.depends_file |
|
12 |
+ # Return false if dependencies are not same. |
|
13 |
+ return false unless other_depends.key?(dep_name) |
|
14 |
+ |
|
15 |
+ # Return false if any of the attributes does not match |
|
16 |
+ unless other_depends[dep_name].checksum == dep_object.checksum && |
|
17 |
+ other_depends[dep_name].mode == dep_object.mode && |
|
18 |
+ other_depends[dep_name].owner == dep_object.owner && |
|
19 |
+ other_depends[dep_name].group == dep_object.group && |
|
20 |
+ other_depends[dep_name].path == dep_object.path |
|
21 |
+ return false |
|
22 |
+ end |
|
23 |
+ end |
|
24 |
+ |
|
25 |
+ @depends_package.each do |dep_name, dep_object| |
|
26 |
+ other_depends = other.depends_package |
|
27 |
+ unless other_depends[dep_name].name == dep_object.name && |
|
28 |
+ other_depends[dep_name].version == dep_object.version |
|
29 |
+ return false |
|
30 |
+ end |
|
31 |
+ end |
|
32 |
+ |
|
33 |
+ true |
|
10 | 34 |
end |
11 | 35 |
|
12 |
- def initialize(name, installed, definition_path, running, enabled) |
|
36 |
+ def initialize(name, depends_file, depends_package) |
|
13 | 37 |
@name = name |
14 |
- @installed = installed |
|
15 |
- @definition_path = definition_path |
|
16 |
- @running = running |
|
17 |
- # Enabled to run on startup (SystemD-esque definition) |
|
18 |
- @enabled = enabled |
|
38 |
+ @depends_file = depends_file |
|
39 |
+ @depends_package = depends_package |
|
19 | 40 |
end |
20 | 41 |
|
21 | 42 |
def to_s |
22 | 43 |
"Name = #{@name}\n" + |
23 |
- "Installed = #{@installed}\n" + |
|
24 |
- "Definition Path = #{@definition_path}\n" + |
|
25 |
- "Running = #{@running}\nStart at boot = #{@enabled}" |
|
44 |
+ "Depends = #{@depends}\n" |
|
26 | 45 |
end |
27 | 46 |
end |
28 | 47 |
end |
... | ... |
@@ -1,13 +1,21 @@ |
1 |
+#!/usr/bin/ruby |
|
2 |
+ |
|
1 | 3 |
require_relative 'objects/rcm_file' |
2 | 4 |
require_relative 'objects/rcm_package' |
3 | 5 |
require_relative 'objects/rcm_service' |
4 | 6 |
require_relative 'managers/rcm_package_manager' |
7 |
+require_relative 'managers/rcm_file_manager' |
|
8 |
+require_relative 'managers/rcm_service_manager' |
|
9 |
+require_relative 'rcm_utils' |
|
5 | 10 |
require 'yaml' |
11 |
+require 'logger' |
|
6 | 12 |
|
7 | 13 |
module RCM |
8 |
- PACKAGES = 'packages' |
|
9 |
- FILES = 'files' |
|
10 |
- SERVICES = 'services' |
|
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', '') |
|
11 | 19 |
|
12 | 20 |
@@wanted = { |
13 | 21 |
PACKAGES => {}, |
... | ... |
@@ -27,9 +35,23 @@ module RCM |
27 | 35 |
SERVICES => [] |
28 | 36 |
} |
29 | 37 |
|
30 |
- @@pkg_mgr = ::RCM::Apt.new() |
|
38 |
+ @@logger = ::Logger.new(STDOUT) |
|
39 |
+ |
|
40 |
+ @@pkg_mgr = ::RCM::Apt.new(@@logger) |
|
41 |
+ @@file_mgr = ::RCM::FileManager.new(@@logger) |
|
42 |
+ @@svc_mgr = ::RCM::ServiceManager.new(@@logger) |
|
43 |
+ |
|
44 |
+ def self.configure_logger |
|
45 |
+ # Can be exposed as settings but was expanding the scope as this |
|
46 |
+ # object will need to be passed to all other objects |
|
47 |
+ @@logger.level = ::Logger::DEBUG |
|
48 |
+ @@logger.progname = 'rcm' |
|
49 |
+ end |
|
31 | 50 |
|
32 | 51 |
def self.parse_packages(markup) |
52 |
+ @@logger.info("Environment = #{RCM_ENV}") |
|
53 |
+ @@pkg_mgr.update unless RCM_ENV == 'dev' |
|
54 |
+ |
|
33 | 55 |
markup.each do |d| |
34 | 56 |
# ensure package version exists, if mentioned. |
35 | 57 |
version = d.fetch('version', '') |
... | ... |
@@ -52,80 +74,109 @@ module RCM |
52 | 74 |
end |
53 | 75 |
end |
54 | 76 |
|
77 |
+ def self.parse_files(markup) |
|
78 |
+ markup.each do |d| |
|
79 |
+ raise "\n\n'#{d['local_file']}' is not readable.\n\n" unless ::File.readable?(d['local_file']) |
|
80 |
+ f = RCM::File.new(d['path'], d['owner'], d['group'], d['mode'], d['local_file']) |
|
81 |
+ @@wanted[FILES][d['path']] = f |
|
82 |
+ end |
|
83 |
+ end |
|
84 |
+ |
|
85 |
+ def self.parse_services(markup) |
|
86 |
+ pkgs = @@wanted[PACKAGES] |
|
87 |
+ files = @@wanted[FILES] |
|
88 |
+ file_dependencies = {} |
|
89 |
+ package_dependencies = {} |
|
90 |
+ markup.each do |d| |
|
91 |
+ dependencies = d.fetch('dependencies', {}) |
|
92 |
+ dependencies.each do |dep| |
|
93 |
+ case dep |
|
94 |
+ when PACKAGES |
|
95 |
+ raise "\n\nPlease ensure #{dep['name']} is managed by this program before adding it as a dependency.\n\n" unless pkgs.key?(dep['name']) |
|
96 |
+ p = pkgs[dep['name']] |
|
97 |
+ package_dependencies[p.name] = p |
|
98 |
+ when FILES |
|
99 |
+ raise "\n\nPlease ensure #{dep['path']} is managed by this program before adding it as a dependency.\n\n" unless files.key?(dep['path']) |
|
100 |
+ |
|
101 |
+ f = files[dep['path']] |
|
102 |
+ file_dependencies[f.path] = f |
|
103 |
+ end |
|
104 |
+ end |
|
105 |
+ s = RCM::Service.new(d['name'], file_dependencies, package_dependencies) |
|
106 |
+ @@wanted[SERVICES][d['name']] = s |
|
107 |
+ end |
|
108 |
+ end |
|
55 | 109 |
|
56 |
- def whachuwant() # or parse_config |
|
57 |
- env = ENV.fetch('ENVIRONMENT', '') |
|
110 |
+ def whachuwant # or parse_config |
|
58 | 111 |
# Consume config from YAML, and convert it to usable objects in @@wanted. |
59 |
- config_file = env.empty? ? 'config.yaml' : "config_#{env}.yaml" |
|
112 |
+ config_file = RCM_ENV.empty? ? 'config.yaml' : "config_#{RCM_ENV}.yaml" |
|
60 | 113 |
|
61 | 114 |
raise "#{config_file} not found." unless ::File.readable?(config_file) |
62 | 115 |
|
63 | 116 |
config = YAML.load_file(config_file) |
64 | 117 |
config.each do |yaml_objects| |
65 |
- yaml_objects.each do |coll, defs| |
|
66 |
- case coll |
|
118 |
+ yaml_objects.each do |collection, resource_definition| |
|
119 |
+ case collection |
|
67 | 120 |
when PACKAGES |
68 |
- parse_packages(defs) |
|
121 |
+ parse_packages(resource_definition) |
|
69 | 122 |
|
70 | 123 |
when FILES |
71 |
- defs.each do |d| |
|
72 |
- f = RCM::File.new(d['path'], d['owner'], d['group'], d['mode'], 'idk') |
|
73 |
- @@wanted[FILES][d['path']] = f |
|
74 |
- end |
|
124 |
+ parse_files(resource_definition) |
|
75 | 125 |
|
76 | 126 |
when SERVICES |
77 |
- defs.each do |d| |
|
78 |
- puts d |
|
79 |
- end |
|
127 |
+ parse_services(resource_definition) |
|
80 | 128 |
end |
81 | 129 |
end |
82 | 130 |
end |
83 | 131 |
|
84 | 132 |
end |
85 | 133 |
|
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] |
|
134 |
+ def whachugot # or get_current_state |
|
135 |
+ @@got[PACKAGES] = @@pkg_mgr.get_current_state(@@wanted[PACKAGES]) |
|
136 |
+ @@got[FILES] = @@file_mgr.get_current_state(@@wanted[FILES]) |
|
137 |
+ # @@got[SERVICES] = @@svc_mgr.get_current_state(@@wanted[SERVICES]) |
|
96 | 138 |
end |
97 | 139 |
|
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() |
|
140 |
+ def crackalackin_packages |
|
108 | 141 |
@@wanted[PACKAGES].each do |pkg_name, pkg_obj| |
109 | 142 |
current_pkg = @@got[PACKAGES][pkg_name] |
110 | 143 |
|
111 |
- next if current_pkg.state == ::RCM::Package::INSTALLED && |
|
112 |
- current_pkg.version == pkg_obj.version |
|
144 |
+ next if current_pkg == pkg_obj |
|
113 | 145 |
|
114 | 146 |
# Set the version correctly |
115 | 147 |
current_pkg.version = pkg_obj.version |
116 | 148 |
@@crackalackin[PACKAGES].push(current_pkg) |
117 | 149 |
end |
150 |
+ @@logger.debug('Missing packages:' + @@crackalackin[PACKAGES].join("\n")) |
|
151 |
+ end |
|
152 |
+ |
|
153 |
+ def crackalackin_files |
|
154 |
+ @@wanted[FILES].each do |path, file_obj| |
|
155 |
+ file_on_fs = @@got[FILES][path] |
|
156 |
+ |
|
157 |
+ next if file_on_fs == file_obj |
|
118 | 158 |
|
119 |
- puts @@crackalackin[PACKAGES] |
|
159 |
+ # Set the version correctly |
|
160 |
+ @@crackalackin[FILES].push(file_obj) |
|
161 |
+ end |
|
162 |
+ @@logger.debug('Missing files:' + @@crackalackin[FILES].join("\n")) |
|
163 |
+ puts @@crackalackin[FILES] |
|
120 | 164 |
end |
121 | 165 |
|
122 | 166 |
def cracka_stop_lackin() |
123 | 167 |
puts 'not implemented' |
124 | 168 |
end |
125 |
- module_function :whachuwant, :whachugot, :crackalackin_packages, :cracka_stop_lackin |
|
169 |
+ |
|
170 |
+ def main |
|
171 |
+ self.configure_logger |
|
172 |
+ end |
|
173 |
+ module_function :whachuwant, :whachugot, :crackalackin_packages, :crackalackin_files, |
|
174 |
+ :cracka_stop_lackin, :main |
|
126 | 175 |
|
127 | 176 |
end |
128 | 177 |
|
178 |
+RCM.main |
|
129 | 179 |
RCM.whachuwant |
130 | 180 |
RCM.whachugot |
131 | 181 |
RCM.crackalackin_packages |
182 |
+RCM.crackalackin_files |
|
132 | 183 |