uberinstaller 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/.gitignore +20 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +66 -0
  5. data/Rakefile +7 -0
  6. data/TODO +14 -0
  7. data/bin/uberinstaller +6 -0
  8. data/lib/uberinstaller.rb +24 -0
  9. data/lib/uberinstaller/cli.rb +51 -0
  10. data/lib/uberinstaller/commander.rb +112 -0
  11. data/lib/uberinstaller/config.rb +55 -0
  12. data/lib/uberinstaller/exception.rb +40 -0
  13. data/lib/uberinstaller/exceptions/command_not_processable.rb +13 -0
  14. data/lib/uberinstaller/exceptions/invalid_folder.rb +13 -0
  15. data/lib/uberinstaller/exceptions/invalid_json.rb +13 -0
  16. data/lib/uberinstaller/exceptions/invalid_local_package.rb +13 -0
  17. data/lib/uberinstaller/exceptions/invalid_package.rb +13 -0
  18. data/lib/uberinstaller/exceptions/invalid_ppa.rb +13 -0
  19. data/lib/uberinstaller/exceptions/invalid_url.rb +13 -0
  20. data/lib/uberinstaller/exceptions/json_file_not_found.rb +13 -0
  21. data/lib/uberinstaller/exceptions/json_parse_error.rb +13 -0
  22. data/lib/uberinstaller/exceptions/missing_local_package.rb +13 -0
  23. data/lib/uberinstaller/exceptions/missing_url.rb +13 -0
  24. data/lib/uberinstaller/exceptions/multiple_local_files_not_supported.rb +13 -0
  25. data/lib/uberinstaller/exceptions/multiple_repositories_not_supported.rb +13 -0
  26. data/lib/uberinstaller/exceptions/no_preprocessor_exception.rb +13 -0
  27. data/lib/uberinstaller/exceptions/parser_argument_error.rb +13 -0
  28. data/lib/uberinstaller/exceptions/wrong_architecture.rb +13 -0
  29. data/lib/uberinstaller/exceptions/wrong_version.rb +13 -0
  30. data/lib/uberinstaller/installer.rb +284 -0
  31. data/lib/uberinstaller/logger.rb +113 -0
  32. data/lib/uberinstaller/package_manager.rb +27 -0
  33. data/lib/uberinstaller/package_managers/apt.rb +18 -0
  34. data/lib/uberinstaller/package_managers/base.rb +87 -0
  35. data/lib/uberinstaller/package_managers/dpkg.rb +15 -0
  36. data/lib/uberinstaller/package_managers/git.rb +15 -0
  37. data/lib/uberinstaller/parser.rb +51 -0
  38. data/lib/uberinstaller/platform.rb +103 -0
  39. data/lib/uberinstaller/runner.rb +218 -0
  40. data/lib/uberinstaller/utils.rb +13 -0
  41. data/lib/uberinstaller/version.rb +3 -0
  42. data/uberinstaller.gemspec +35 -0
  43. metadata +243 -0
@@ -0,0 +1,113 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'colored'
4
+ require 'logger'
5
+
6
+ module Uberinstaller
7
+
8
+ # Handle application log
9
+ module Loggable
10
+ # @!attribute [r] loggers
11
+ # Hash of available loggers ( one for class in which logger is invoked )
12
+ @loggers = {}
13
+ # @!attribute [rw] log_path
14
+ # Path in which log files are saved ( default STDOUT )
15
+ @log_path = STDOUT
16
+ # @!attribute [rw] level
17
+ # Log level. Can be one of Logger::DEBUG, Logger::INFO, Logger::WARN, Logger::ERROR ( default Logger::ERROR )
18
+ @level = Logger::ERROR
19
+
20
+ # Global, memoized, lazy initialized instance of a logger
21
+ #
22
+ # This is the magical bit that gets mixed into your classes. Respond to Logger function.
23
+ #
24
+ # @return [Object] an instance of the logger class
25
+ def logger
26
+ classname = (self.is_a? Module) ? self : self.class.name
27
+ @logger ||= Loggable.logger_for(classname)
28
+ end
29
+
30
+ class << self
31
+ # @!attribute [rw] level
32
+ # Log output level. Can be one of Logger::DEBUG, Logger::INFO, Logger::WARN, Logger::ERROR ( default Logger::ERROR )
33
+ # @!attribute [rw] log_path
34
+ # Path in which log files are saved ( default STDOUT )
35
+ attr_accessor :level, :log_path
36
+
37
+ # Return the logger for a specific Class. If the instance is not found, creates it.
38
+ #
39
+ # @param classname [String] the name of the class for which a logger instance must be retrieved
40
+ # @return [Object] the instance of the logger class for the specified Class
41
+ def logger_for(classname)
42
+ @loggers[classname] ||= configure_logger_for(classname)
43
+ end
44
+
45
+ # Create and configure a logger for the specified Class
46
+ #
47
+ # @param classname [String] the name of the class for which a logger instance must be retrieved
48
+ # @return [Object] the instance of the logger class for the specified Class
49
+ def configure_logger_for(classname)
50
+ # handle case in which log path does not exists
51
+ begin
52
+ logger = Logger.new(@log_path)
53
+ rescue Errno::ENOENT
54
+ FileUtils.mkdir_p File.dirname @log_path
55
+ retry
56
+ end
57
+
58
+ logger.progname = classname
59
+ logger.level = @level
60
+ logger.formatter = proc { |severity, datetime, progname, msg|
61
+ case severity
62
+ when 'DEBUG'
63
+ spaciator = " *"
64
+ after_space = ""
65
+ colored = "white"
66
+ extra = ""
67
+ when 'INFO'
68
+ spaciator = " **"
69
+ after_space = " "
70
+ colored = ""
71
+ extra = ""
72
+ when 'WARN'
73
+ spaciator = " ***"
74
+ after_space = " "
75
+ colored = "yellow"
76
+ extra = ""
77
+ when 'ERROR'
78
+ spaciator = " ****"
79
+ after_space = ""
80
+ colored = "red"
81
+ extra = ""
82
+ when 'FATAL'
83
+ spaciator = "*****"
84
+ after_space = ""
85
+ colored = "red"
86
+ extra = "bold"
87
+ else
88
+ spaciator = " "
89
+ after_space = ""
90
+ colored = ""
91
+ extra = ""
92
+ end
93
+
94
+ formatted_output = " #{spaciator} [#{severity}]#{after_space} [#{datetime}] -- #{msg} { #{progname} }\n"
95
+ if @log_path == STDOUT or @log_path == STDERR
96
+ if colored.empty?
97
+ formatted_output
98
+ else
99
+ if extra.empty?
100
+ formatted_output.send(colored)
101
+ else
102
+ formatted_output.send(colored).send(extra)
103
+ end
104
+ end
105
+ else
106
+ formatted_output
107
+ end
108
+ }
109
+ logger
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'uberinstaller/config'
4
+ require 'uberinstaller/logger'
5
+
6
+ module Uberinstaller
7
+ module PackageManager
8
+
9
+ # Create a new PackageManager instance based on the type
10
+ #
11
+ # @param type [String] the type of package manager to create instance for
12
+ def self.new(type)
13
+ case type
14
+ when 'git' then package_manager = 'Git'
15
+ when 'local' then package_manager = Uberinstaller::Config.local_package_manager
16
+ when 'remote' then package_manager = Uberinstaller::Config.remote_package_manager
17
+ end
18
+
19
+ ("Uberinstaller::PackageManager::" + package_manager).split('::').inject(Object) {|scope,name| scope.const_get(name)}.new
20
+ end
21
+ end
22
+ end
23
+
24
+ require 'uberinstaller/package_managers/base'
25
+ require 'uberinstaller/package_managers/apt'
26
+ require 'uberinstaller/package_managers/dpkg'
27
+ require 'uberinstaller/package_managers/git'
@@ -0,0 +1,18 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'uberinstaller/package_manager'
4
+
5
+ module Uberinstaller
6
+ module PackageManager
7
+ # Apt-Get Package manager
8
+ class Apt < Base
9
+
10
+ def set_commands
11
+ @commands[:add_repository] = "apt-add-repository -y"
12
+ @commands[:install] = "DEBIAN_FRONTEND=gnome apt-get install -y"
13
+ @commands[:update] = "apt-get update"
14
+ @commands[:upgrade] = "apt-get upgrade"
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,87 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'uberinstaller/config'
4
+ require 'uberinstaller/logger'
5
+
6
+ require 'open3'
7
+
8
+ module Uberinstaller
9
+ module PackageManager
10
+ class Base
11
+ include Loggable
12
+
13
+ attr_reader :commands
14
+
15
+ # Create the package manager
16
+ def initialize
17
+ @commands = Hash.new
18
+ @commands = {
19
+ :add_repository => nil,
20
+ :info => nil,
21
+ :install => nil,
22
+ :search => nil,
23
+ :update => nil,
24
+ :upgrade => nil
25
+ }
26
+
27
+ set_commands
28
+ end
29
+
30
+ # This method is a stub, here only for reference
31
+ #
32
+ # In every subclass of PackageManager::Base this method must be redefined
33
+ # specifying the package manager specific command ( see Apt and Dpkg for
34
+ # example )
35
+ def set_commands; end
36
+
37
+ # Print to log for debug purposes
38
+ #
39
+ # @param action [String] the action that is being performed
40
+ # @param args [Array] an array of arguments for the specified action
41
+ def debug(action, args = [])
42
+ logger.debug "action : #{action}"
43
+ logger.debug "args : #{args.join(', ')}" unless args.empty?
44
+ logger.debug "command: #{make_command(action, args)}"
45
+ end
46
+
47
+ # Creates a command putting together action and arguments
48
+ #
49
+ # @param action [String] the action that is being performed
50
+ # @param args [Array] an array of arguments for the specified action
51
+ def make_command(action, args = [])
52
+ command = @commands[action.to_sym]
53
+ command += " '" + args.join(' ') + "'" unless args.empty?
54
+
55
+ command
56
+ end
57
+
58
+ # All execution is handled dinamically via this function
59
+ #
60
+ # If the method called is in the @commands Hash, the specified action is
61
+ # performed, otherwise a NoMethodError exception
62
+ #
63
+ # @raise [NoMethodError] if the method specified is not available
64
+ def method_missing(m, *args, &block)
65
+ if @commands.has_key? m
66
+ debug m, args
67
+
68
+ unless Config.dry_run
69
+ Open3.popen3(make_command(m, args)) { |stdin, stdout, stderr, wait_thr|
70
+ pid = wait_thr.pid # pid of the started process.
71
+ logger.debug "Running pid: #{pid}"
72
+
73
+ logger.debug stdout.readlines
74
+
75
+ exit_status = wait_thr.value.to_i # Process::Status object returned.
76
+ logger.debug "Exit status: #{exit_status}"
77
+ logger.error 'Some error happended during execution:' unless exit_status == 0
78
+ logger.error stderr.readlines unless exit_status == 0
79
+ }
80
+ end
81
+ else
82
+ raise NoMethodError
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,15 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'uberinstaller/package_manager'
4
+
5
+ module Uberinstaller
6
+ module PackageManager
7
+ # Dpkg package manager
8
+ class Dpkg < Base
9
+
10
+ def set_commands
11
+ @commands[:install] = "dpkg -i"
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'uberinstaller/package_manager'
4
+
5
+ module Uberinstaller
6
+ module PackageManager
7
+ # Git package manager ( a little bit of a hack really )
8
+ class Git < Base
9
+
10
+ def set_commands
11
+ @commands[:install] = "git clone"
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,51 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'uberinstaller/logger'
4
+ require 'uberinstaller/exception'
5
+
6
+ require 'json'
7
+
8
+ module Uberinstaller
9
+ class Parser
10
+ include Loggable
11
+
12
+ # @!attribute [rw] file
13
+ # the file to be parsed
14
+ # @!attribute [r] data
15
+ # an Hash containing data after parsing
16
+ attr_accessor :file
17
+ attr_reader :data
18
+
19
+ # Create the parser
20
+ def initialize(file, perform_parse = true)
21
+ if File.exists?(file)
22
+ @file = file
23
+ else
24
+ raise Uberinstaller::Exception::ParserArgumentError, file
25
+ end
26
+
27
+ @json = nil
28
+ @data = nil
29
+
30
+ run if perform_parse
31
+ self
32
+ end
33
+
34
+ def debug
35
+ @json
36
+ end
37
+
38
+ def run
39
+ begin
40
+ @json = IO.read(@file)
41
+ # Comments are stripped out! FUCK YEAH!
42
+ @data = JSON.parse @json, :symbolize_names => true
43
+ rescue JSON::ParserError
44
+ raise Uberinstaller::Exception::JsonParseError, @file
45
+ else
46
+ @data
47
+ end
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,103 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'uberinstaller/logger'
4
+
5
+ require 'hash_keyword_args'
6
+
7
+ module Uberinstaller
8
+ # http://stackoverflow.com/questions/170956/how-can-i-find-which-operating-system-my-ruby-program-is-running-on
9
+ class Platform
10
+ include Loggable
11
+
12
+ # @!attribute [r] architecture
13
+ # OS architecture information
14
+ # @!attribute [r] lsb
15
+ # LSB module information
16
+ # @!attribute [r] uname
17
+ # `uname` calls results
18
+ attr_reader :architecture, :lsb, :uname
19
+
20
+ # Get platform, detect ubuntu, detect ubuntu version, save lsb params
21
+ #
22
+ # @param opts [Hash]
23
+ # :lsb => the file containing LSB information
24
+ def initialize(opts = {})
25
+ @opts = opts.keyword_args(:lsb => '/etc/lsb-release')
26
+
27
+ @lsb = nil
28
+ @uname = nil
29
+
30
+ get_lsb_informations
31
+ get_arch_informations
32
+
33
+ @architecture = @uname[:machine]
34
+ end
35
+
36
+ # Check if platform is Ubuntu
37
+ def is_ubuntu?
38
+ return @lsb[:id] == 'Ubuntu' if @lsb[:id]
39
+ logger.fatal 'lsb is not set, impossible to get OS information'
40
+ false
41
+ end
42
+
43
+ # Reverse of is_ubuntu?
44
+ def is_not_ubuntu?
45
+ !is_ubuntu?
46
+ end
47
+
48
+ # Reverse of is_64bit?
49
+ def is_32bit?
50
+ !is_64bit?
51
+ end
52
+
53
+ # Check if system is running 64 bit OS
54
+ def is_64bit?
55
+ return @uname[:machine] == 'x86_64' if @uname[:machine]
56
+ logger.fatal 'uname is not set, impossible to get machine information'
57
+ false
58
+ end
59
+
60
+ private
61
+ # Detect OS architecture information
62
+ #
63
+ # Using a call to `uname` try to detect architecture informations.
64
+ # `uname` must be available on the system
65
+ def get_arch_informations
66
+ @uname ||= Hash.new
67
+ IO.popen 'uname -m' do |io| @uname[:machine] = io.read.strip end
68
+ IO.popen 'uname -n' do |io| @uname[:host] = io.read.strip end
69
+ IO.popen 'uname -srv' do |io| @uname[:kernel] = io.read.strip end
70
+ end
71
+
72
+ # Get OS information from LSB
73
+ #
74
+ # LSB must be aavailable on the system
75
+ def get_lsb_informations
76
+ # http://stackoverflow.com/a/1236075/715002
77
+ IO.popen "cat #{@opts.lsb}" do |io|
78
+ io.each do |line|
79
+ unless line.include? 'cat:' # check for error
80
+ @lsb ||= Hash.new
81
+
82
+ if line.include? 'DISTRIB_ID'
83
+ @lsb[:id] = get_lsb_value line
84
+ elsif line.include? 'DISTRIB_RELEASE'
85
+ @lsb[:release] = get_lsb_value line
86
+ elsif line.include? 'DISTRIB_CODENAME'
87
+ @lsb[:codename] = get_lsb_value line
88
+ elsif line.include? 'DISTRIB_DESCRIPTION'
89
+ @lsb[:description] = get_lsb_value line
90
+ end
91
+ else
92
+ logger.fatal "Platform has no #{@opts.lsb}, so it is not supported"
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ # Handy method to retrieve values from LSB pairs
99
+ def get_lsb_value(string)
100
+ string.split('=')[1].strip
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,218 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'uberinstaller/logger'
4
+
5
+ module Uberinstaller
6
+ class Runner
7
+ include Loggable
8
+
9
+ # @!attribute [r] packages
10
+ # the list of packages after configuration file is parsed
11
+ # @!attribute [r] parser
12
+ # the parser class used to parse configuration file
13
+ # @!attribute [r] platform
14
+ # the platform on which UberInstaller is running
15
+ # @!attribute [r] unprocessed
16
+ # check if the execution has already been done
17
+ attr_reader :packages, :parser, :platform, :unprocessed
18
+
19
+ # Initialize the Runner class
20
+ #
21
+ # @param file [String] the file name to be used for this execution
22
+ def initialize(file)
23
+ logger.info "Processing JSON file: #{file}"
24
+
25
+ # check if element has already been processed
26
+ @unprocessed = true
27
+
28
+ @parser = Parser.new file
29
+
30
+ @platform = Platform.new
31
+
32
+ logger.warn "Platform is not Ubuntu, please report any inconvenient behaviour" unless platform.is_ubuntu?
33
+
34
+ verify_architecture
35
+ verify_os_version
36
+
37
+ # This dummy commander is used to launch before all and after all scripts
38
+ @global_commander = Commander.new("Dummy package", { :cmd => { :after => "all.sh", :before => "all.sh" }})
39
+
40
+ @packages = parser.data[:packages]
41
+
42
+ get_nested_json
43
+ end
44
+
45
+ def install
46
+ logger.info 'Installing packages...'
47
+
48
+ @packages.each do |p|
49
+ pkg_name = p[0].to_s
50
+ pkg = p[1]
51
+
52
+ installer = Installer.new(pkg_name, pkg)
53
+ commander = Commander.new(pkg_name, pkg)
54
+
55
+ logger.info "Installing #{pkg_name}"
56
+
57
+ commander.before
58
+
59
+ case pkg[:type]
60
+ when 'system'
61
+ begin
62
+ installer.install 'system'
63
+ rescue Exception => e
64
+ logger.error e.message
65
+
66
+ pkg[:errors] = Array.new # add array to store errors
67
+ pkg[:errors] << e.message
68
+ end
69
+ when 'git'
70
+ begin
71
+ installer.install 'git'
72
+ rescue Exception => e
73
+ logger.error e.message
74
+
75
+ pkg[:errors] = Array.new # add array to store errors
76
+ pkg[:errors] << e.message
77
+ end
78
+ when 'local'
79
+ begin
80
+ installer.install 'local'
81
+ rescue Exception::MultipleLocalFilesNotSupported => e
82
+ logger.error e.message
83
+
84
+ pkg[:errors] = Array.new # add array to store errors
85
+ pkg[:errors] << e.message
86
+ end
87
+ else
88
+ logger.error "#{pkg_name} :type is not supported"
89
+ end
90
+
91
+ commander.after
92
+ end
93
+
94
+ logger.info 'Executing after all commands...'
95
+ @global_commander.after
96
+ end
97
+
98
+ # Preprocess all packages performing validation
99
+ def preprocess
100
+ logger.info 'Executing before all commands...'
101
+ @global_commander.before
102
+
103
+ logger.info 'Preprocessing packages...'
104
+ @packages.each do |p|
105
+ pkg_name = p[0].to_s
106
+ pkg = p[1]
107
+
108
+ logger.info "Package: #{pkg_name}"
109
+ logger.debug "Package content: #{pkg}"
110
+
111
+ # set pkg installation type based on existing key in the package definition
112
+ pkg[:type] = get_package_type pkg
113
+
114
+ installer = Installer.new(pkg_name, pkg)
115
+
116
+ case pkg[:type]
117
+ when 'system'
118
+ begin
119
+ installer.preprocess 'system'
120
+ rescue Exception::InvalidPackage, Exception::InvalidPpa => e
121
+ logger.error e.message
122
+
123
+ pkg[:skip] = true
124
+ pkg[:errors] = Array.new # add array to store errors
125
+ pkg[:errors] << e.message
126
+ end
127
+ when 'git'
128
+ begin
129
+ installer.preprocess 'git'
130
+ rescue Exception::InvalidFolder, Exception::MissingUrl, Exception::InvalidUrl => e
131
+ logger.error e.message
132
+
133
+ pkg[:skip] = true
134
+ pkg[:errors] = Array.new # add array to store errors
135
+ pkg[:errors] << e.message
136
+ end
137
+ when 'local'
138
+ begin
139
+ installer.preprocess 'local'
140
+ rescue Exception::MissingLocalPackage, Exception::InvalidLocalPackage => e
141
+ logger.error e.message
142
+
143
+ pkg[:skip] = true
144
+ pkg[:errors] = Array.new # add array to store errors
145
+ pkg[:errors] << e.message
146
+ end
147
+ else
148
+ logger.error "#{pkg_name} :type is not supported"
149
+ end
150
+ end
151
+
152
+ PackageManager.new('remote').update
153
+ end
154
+
155
+ # Verify that platform architecture match the one specified in the config file
156
+ #
157
+ # @raise [Exception::WrongArchitecture] if the architecture do not match configuration file
158
+ def verify_architecture
159
+ if parser.data[:meta][:arch]
160
+ unless parser.data[:meta][:arch] == 'system'
161
+ logger.debug 'Verifying architecture...'
162
+
163
+ unless parser.data[:meta][:arch] == platform.architecture
164
+ raise Exception::WrongArchitecture, parser.data[:meta][:arch]
165
+ else
166
+ logger.info "Architecture match installation file requirements"
167
+ end
168
+ end
169
+ else
170
+ logger.warn "Installation file does not specify a required architecture"
171
+ end
172
+ end
173
+
174
+ # Verify that the OS version match the one specified in the config file
175
+ #
176
+ # @raise [Exception::WrongVersion] if the version do not match
177
+ def verify_os_version
178
+ raise Exception::WrongVersion, parser.data[:meta][:version] unless parser.data[:meta][:version] == platform.lsb[:codename]
179
+ end
180
+
181
+ private
182
+ def get_nested_json
183
+ nested_packages = Hash.new
184
+
185
+ @packages.each do |p|
186
+ pkg_name = p[0].to_s
187
+ pkg = p[1]
188
+
189
+ if pkg.has_key? :json
190
+ installer = Installer.new(pkg_name, pkg)
191
+
192
+ begin
193
+ installer.preprocess 'json'
194
+ rescue Exception::JsonFileNotFound, Exception::InvalidJson => e
195
+ logger.error e.message
196
+
197
+ pkg[:skip] = true
198
+ pkg[:errors] = Array.new # add array to store errors
199
+ pkg[:errors] << e.message
200
+ else
201
+ file = File.join Config.json_path, pkg[:json] + '.json'
202
+ parser = Parser.new(file)
203
+ data = parser.data[:packages].each { |p| p[1][:type] = get_package_type p[1] }
204
+ nested_packages.merge! data
205
+ end
206
+ end
207
+ end
208
+
209
+ @packages.merge! nested_packages
210
+ end
211
+
212
+ def get_package_type(pkg)
213
+ return 'system' if pkg.has_key? :system
214
+ return 'git' if pkg.has_key? :git
215
+ return 'local' if pkg.has_key? :local
216
+ end
217
+ end
218
+ end