upman-daemon 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/bin/upman ADDED
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env ruby
2
+ require 'optparse'
3
+
4
+ options = {:action => :run}
5
+
6
+ options = {}
7
+ version = "0.0.1"
8
+ daemonize_help = "run daemonized in the background (default: true)"
9
+ pidfile_help = "the pid filename (default: /var/run/upman.pid)"
10
+ logfile_help = "the log filename (default: /var/log/upman.log)"
11
+ include_help = "an additional $LOAD_PATH"
12
+ debug_help = "set $DEBUG to true"
13
+ warn_help = "enable warnings"
14
+
15
+ op = OptionParser.new
16
+ op.banner = "An example of how to daemonize upman."
17
+ op.separator ""
18
+ op.separator "Usage: upman [options]"
19
+ op.separator ""
20
+
21
+ op.separator "Process options:"
22
+ op.on("-d", "--daemonize", daemonize_help) { options[:daemonize] = true }
23
+ op.on("-p", "--pid PIDFILE", pidfile_help) { |value| options[:pidfile] = value }
24
+ op.on("-l", "--log LOGFILE", logfile_help) { |value| options[:logfile] = value }
25
+ op.separator ""
26
+
27
+ op.separator "Ruby options:"
28
+ op.on("-I", "--include PATH", include_help) { |value| $LOAD_PATH.unshift(*value.split(":").map { |v| File.expand_path(v) }) }
29
+ op.on("--debug", debug_help) { $DEBUG = true }
30
+ op.on("--warn", warn_help) { $-w = true }
31
+ op.separator ""
32
+
33
+ op.separator "Common options:"
34
+ op.on("-h", "--help") { puts op.to_s; exit }
35
+ op.on("-v", "--version") { puts version; exit }
36
+ op.separator ""
37
+
38
+ op.parse!(ARGV)
39
+ require_relative '../lib/upman' unless options[:action] == :help
40
+
41
+ case options[:action]
42
+ when :help then
43
+ puts op.to_s
44
+ when :version then
45
+ puts Upman::Core::Version.VERSION
46
+ else
47
+ Upman.run!(options)
48
+ end
@@ -0,0 +1,34 @@
1
+ module Upman
2
+ module Core
3
+ module Config
4
+ extend self
5
+ using SymbolizeHelper
6
+
7
+ @_config = {}
8
+ attr_reader :_config
9
+
10
+ def load!(filename, options = {})
11
+ newsets = YAML::load_file(filename).deep_symbolize_keys
12
+ newsets = newsets[options[:env].to_sym] if options[:env] && newsets[options[:env].to_sym]
13
+ deep_merge!(@_config, newsets)
14
+ end
15
+
16
+ def get!(key)
17
+ return @_config[key]
18
+ end
19
+
20
+ # Deep merging of hashes
21
+ # deep_merge by Stefan Rusterholz, see http://www.ruby-forum.com/topic/142809
22
+ def deep_merge!(target, data)
23
+ merger = proc {|key, v1, v2|
24
+ Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2}
25
+ target.merge! data, &merger
26
+ end
27
+
28
+ def method_missing(name, *args, &block)
29
+ @_config[name.to_sym] ||
30
+ fail(NoMethodError, "unknown configuration root #{name}", caller)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,157 @@
1
+ require 'fileutils'
2
+ require 'thread'
3
+
4
+ module Upman
5
+ module Core
6
+ class Daemon
7
+
8
+ include ::Upman::Utils::Helper
9
+
10
+ attr_reader :options, :quit
11
+
12
+ def initialize(options)
13
+ @options = options
14
+ options[:logfile] = File.expand_path(logfile) if logfile? # daemonization might change CWD so expand any relative paths in advance
15
+ options[:pidfile] = File.expand_path(pidfile) if pidfile? # (ditto)
16
+
17
+
18
+ end
19
+
20
+ def daemonize?
21
+ options[:daemonize]
22
+ end
23
+
24
+ def logfile
25
+ options[:logfile]
26
+ end
27
+
28
+ def pidfile
29
+ options[:pidfile]
30
+ end
31
+
32
+ def logfile?
33
+ !logfile.nil?
34
+ end
35
+
36
+ def pidfile?
37
+ !pidfile.nil?
38
+ end
39
+
40
+
41
+ #--------------------------------------------------------------------------
42
+
43
+ def run!
44
+
45
+ check_pid
46
+ daemonize if daemonize?
47
+ write_pid
48
+ trap_signals
49
+
50
+ if logfile?
51
+ redirect_output
52
+ elsif daemonize?
53
+ suppress_output
54
+ end
55
+
56
+ api = Upman::Utils::Api.new
57
+ unless api.check_connection
58
+ return nil
59
+ end
60
+
61
+ threads = []
62
+
63
+ Thread.abort_on_exception = true
64
+
65
+ threads << Thread.new do
66
+ server = Upman::Server::Socket.new
67
+ server.run!
68
+ end
69
+
70
+ threads << Thread.new do
71
+ worker = Upman::Core::Worker.new
72
+ worker.run!
73
+ end
74
+
75
+ trap("INT") do
76
+ warn "CTRL+C - Shutdown all threads"
77
+ threads.each { |t| Thread.kill t }
78
+ exit 130
79
+ end
80
+
81
+ threads.each { |t| t.join }
82
+
83
+ end
84
+
85
+ #==========================================================================
86
+ # DAEMONIZING, PID MANAGEMENT, and OUTPUT REDIRECTION
87
+ #==========================================================================
88
+
89
+ def daemonize
90
+ exit if fork
91
+ Process.setsid
92
+ exit if fork
93
+ Dir.chdir "/"
94
+ end
95
+
96
+ def redirect_output
97
+ FileUtils.mkdir_p(File.dirname(logfile), :mode => 0755)
98
+ FileUtils.touch logfile
99
+ File.chmod(0644, logfile)
100
+ $stderr.reopen(logfile, 'a')
101
+ $stdout.reopen($stderr)
102
+ $stdout.sync = $stderr.sync = true
103
+ end
104
+
105
+ def suppress_output
106
+ $stderr.reopen('/dev/null', 'a')
107
+ $stdout.reopen($stderr)
108
+ end
109
+
110
+ def write_pid
111
+ if pidfile?
112
+ begin
113
+ File.open(pidfile, ::File::CREAT | ::File::EXCL | ::File::WRONLY) {|f| f.write("#{Process.pid}")}
114
+ at_exit {File.delete(pidfile) if File.exists?(pidfile)}
115
+ rescue Errno::EEXIST
116
+ check_pid
117
+ retry
118
+ end
119
+ end
120
+ end
121
+
122
+ def check_pid
123
+ if pidfile?
124
+ case pid_status(pidfile)
125
+ when :running, :not_owned
126
+ puts "A server is already running. Check #{pidfile}"
127
+ exit(1)
128
+ when :dead
129
+ File.delete(pidfile)
130
+ end
131
+ end
132
+ end
133
+
134
+ def pid_status(pidfile)
135
+ return :exited unless File.exists?(pidfile)
136
+ pid = ::File.read(pidfile).to_i
137
+ return :dead if pid == 0
138
+ Process.kill(0, pid)
139
+ :running
140
+ rescue Errno::ESRCH
141
+ :dead
142
+ rescue Errno::EPERM
143
+ :not_owned
144
+ end
145
+
146
+ #==========================================================================
147
+ # SIGNAL HANDLING
148
+ #==========================================================================
149
+
150
+ def trap_signals
151
+ trap(:QUIT) do # graceful shutdown
152
+ @quit = true
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,15 @@
1
+ module Upman
2
+ module Core
3
+ class ExtensionBase
4
+
5
+ include Upman::Utils::Helper
6
+
7
+ def register
8
+ fail "There is no register method defined in this extension."
9
+ end
10
+
11
+
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,38 @@
1
+ module Upman
2
+ module Core
3
+ class Worker
4
+
5
+ include Upman::Utils::Helper
6
+
7
+ def initialize
8
+ @stop_signal = true
9
+ end
10
+
11
+ def run!
12
+ _banner
13
+ while @stop_signal
14
+ self.perform
15
+ sleep(::Upman::Core::Config.daemon[:interval])
16
+ end
17
+
18
+ end
19
+
20
+ def shutdown
21
+ info "Stopping working thread"
22
+ @stop_signal = false
23
+ end
24
+
25
+
26
+ def perform
27
+ info "Doing some work"
28
+ end
29
+
30
+ private
31
+
32
+ def _banner
33
+ info "Running UpMan"
34
+ end
35
+
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Upman
6
+ module Extensions
7
+ # Get install history on debian system
8
+ class GetInstallHistory < ::Upman::Core::ExtensionBase
9
+ REQUEST_PATH = 'getInstallHistory'
10
+
11
+ def register
12
+ Servlet
13
+ end
14
+
15
+ # Servlet action for WEBrick
16
+ class Servlet < ::Upman::Server::BaseServlet
17
+
18
+ attr_accessor :param_since
19
+
20
+ # rubocop:disable Naming/MethodName
21
+ def do_GET(request, response)
22
+ # rubocop:enable Naming/MethodName
23
+
24
+ super(request, response)
25
+
26
+ unless (@param_since = get_param('since', 'date', false))
27
+ return nil
28
+ end
29
+
30
+ if (body = perform_action)
31
+ response = ok(response, body)
32
+ end
33
+ end
34
+
35
+ def perform_action
36
+ result = []
37
+ i = 0
38
+ hist_lines = fetch_history.split("\n")
39
+ hist_lines.each do |history|
40
+ fill_up(result, i, hist_lines, history) if i.even?
41
+ i += 1
42
+ end
43
+ result.to_json
44
+ end
45
+
46
+ private
47
+
48
+ def fetch_history
49
+ command = '(zcat $(ls -tr /var/log/apt/history.log*.gz); '\
50
+ 'cat /var/log/apt/history.log) ' \
51
+ '2>/dev/null | egrep \'^(Start-Date:|Commandline:)\' | '\
52
+ 'grep -v aptdaemon'
53
+ `#{command}`
54
+ end
55
+
56
+ def fill_up(result, line_index, hist_lines, history)
57
+ line = hist_lines[line_index + 1].sub('Commandline: ', '')
58
+ command = parse_command(line)
59
+ return nil if command.empty?
60
+
61
+ command[:start_date] = DateTime.parse(history.sub('Start-Date: ', ''))
62
+
63
+ if @param_since.instance_of?(Date)
64
+ if command[:start_date] >= @param_since
65
+ result.append command
66
+ end
67
+ else
68
+ result.append command
69
+ end
70
+ end
71
+
72
+ def parse_command(command_line)
73
+ package = {}
74
+ regexp = /(apt(-get)?)(( [\-\w]*)*) (install|upgrade|purge)(.*)?/
75
+ if (matches = command_line.match(regexp))
76
+ package[:action] = matches[5].strip
77
+
78
+ package[:options] = []
79
+ matches[3].split(' ').each { |name| package[:options].append name }
80
+
81
+ package[:packages] = []
82
+ matches[6].split(' ').each { |name| package[:packages].append name }
83
+ end
84
+ package
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,49 @@
1
+ module Upman
2
+ module Extensions
3
+ class GetInstalledPackages < ::Upman::Core::ExtensionBase
4
+ REQUEST_PATH = "getInstalledPackages"
5
+
6
+ def register
7
+ return Servlet
8
+ end
9
+
10
+ class Servlet < ::Upman::Server::BaseServlet
11
+
12
+ attr_accessor :requested
13
+
14
+ include ::Upman::Utils::Parser
15
+
16
+ # rubocop:disable Naming/MethodName
17
+ def do_GET(request, response)
18
+ # rubocop:enable Naming/MethodName
19
+
20
+ super(request, response)
21
+
22
+ @requested = get_param('requested', 'int')
23
+
24
+ if (body = perform_action)
25
+ response = ok(response, body)
26
+ end
27
+ end
28
+
29
+ def perform_action
30
+ result = []
31
+ extended_states = File.open('/var/lib/apt/extended_states', 'rb', &:read)
32
+ extended_states.split("\n\n").each do |chunk|
33
+ installed_packages = _get_hashed_values(chunk)
34
+ unless @requested.empty?
35
+ if installed_packages['auto_installed'] == @requested
36
+ result.append installed_packages
37
+ end
38
+ else
39
+ result.append installed_packages
40
+ end
41
+
42
+ end
43
+ result.to_json
44
+ end
45
+
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,26 @@
1
+ module Upman
2
+ module Extensions
3
+ class InstallPackage < ::Upman::Core::ExtensionBase
4
+
5
+ REQUEST_PATH = "installPackage"
6
+
7
+ def register
8
+ return Servlet
9
+ end
10
+
11
+ class Servlet < WEBrick::HTTPServlet::AbstractServlet
12
+ def do_GET request, response
13
+ response.status = 200
14
+ response['Content-Type'] = 'application/json'
15
+ response.body = self.perform_action
16
+ end
17
+
18
+
19
+ def perform_action
20
+ "installPackage"
21
+ end
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,28 @@
1
+ module Upman
2
+ module Extensions
3
+ class Ping < ::Upman::Core::ExtensionBase
4
+ REQUEST_PATH = "ping"
5
+
6
+ def register
7
+ return Servlet
8
+ end
9
+
10
+ class Servlet < ::Upman::Server::BaseServlet
11
+
12
+ # rubocop:disable Naming/MethodName
13
+ def do_GET(request, response)
14
+ # rubocop:enable Naming/MethodName
15
+
16
+ super(request, response)
17
+
18
+ response = ok(response, perform_action)
19
+ end
20
+
21
+ def perform_action
22
+ "{\"pong\": true, \"message\": \"upman-#{::Upman::Version::VERSION} - Daemon is running\"}"
23
+ end
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,65 @@
1
+ module Upman
2
+ module Server
3
+ class BaseServlet < WEBrick::HTTPServlet::AbstractServlet
4
+
5
+ include ::Upman::Utils::Helper
6
+
7
+ attr_accessor :query, :response, :error_msg
8
+
9
+ def initialize(server, *options)
10
+ super(server, *options)
11
+ end
12
+
13
+ # rubocop:disable Naming/MethodName
14
+ def do_GET(_request, response)
15
+ # rubocop:enable Naming/MethodName
16
+
17
+ info "Process API request #{_request.path}"
18
+
19
+ @response = response
20
+ @query = _request.query
21
+ end
22
+
23
+ def ok(response, body)
24
+ response.status = 200
25
+ response['Content-Type'] = 'application/json'
26
+ response.body = body
27
+ response
28
+ end
29
+
30
+ def bad_request(response, body)
31
+ response.status = 400
32
+ response['Content-Type'] = 'application/json'
33
+ response.body = "{\"status\": \"bad_request\", \"message\": \"#{body}\"}"
34
+ response
35
+ end
36
+
37
+ def get_param(key, validate, require = false)
38
+ if @query.nil?
39
+ return ''
40
+ end
41
+
42
+ unless @query.include? key
43
+ unless require
44
+ return ''
45
+ else
46
+ return nil
47
+ end
48
+ end
49
+ unless validate.nil?
50
+ if validate == 'date'
51
+ begin
52
+ @query[key] = DateTime.parse(@query[key])
53
+ rescue
54
+ msg = "Invalid content for param #{key}, expected type: date - found #{@query[key]}"
55
+ bad_request(@response, msg)
56
+ fail msg
57
+ return false
58
+ end
59
+ end
60
+ end
61
+ @query[key]
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,66 @@
1
+ require 'openssl'
2
+ require 'webrick'
3
+ require 'webrick/https'
4
+
5
+ module Upman
6
+ module Server
7
+ class Socket
8
+
9
+ include ::Upman::Utils::Helper
10
+
11
+ include WEBrick
12
+
13
+ attr_reader :server
14
+
15
+ def initialize
16
+ @config = ::Upman::Core::Config.daemon
17
+
18
+ end
19
+
20
+ def run!
21
+
22
+ server = HTTPServer.new(
23
+ BindAddress: @config[:listen],
24
+ :Port => @config[:port],
25
+ :SSLEnable => @config[:ssl],
26
+ :debugger => false,
27
+ :daemonize => true,
28
+ :SSLPrivateKey => OpenSSL::PKey::RSA.new(
29
+ File.open(@config[:key]).read),
30
+ :SSLCertificate => OpenSSL::X509::Certificate.new(
31
+ File.open(@config[:cert]).read),
32
+ :SSLCertName => [["CN", WEBrick::Utils::getservername]],
33
+ Logger: WEBrick::Log.new("/dev/null"),
34
+ AccessLog: [],
35
+ )
36
+ _register_extensions(server)
37
+
38
+ protocol = @config[:ssl] ? 'https://' : 'http://'
39
+ info "Starting listener on #{protocol}#{@config[:listen]}:#{@config[:port]}"
40
+
41
+ server.start
42
+
43
+ end
44
+
45
+ private
46
+
47
+ def _dynload(str)
48
+ str.split('::').inject(Object) do |mod, class_name|
49
+ mod.const_get(class_name)
50
+ end
51
+ end
52
+
53
+
54
+ def _register_extensions(server)
55
+
56
+ @config[:extensions].each do |extension|
57
+ require_relative "../../upman/extensions/#{extension}"
58
+ ext_obj = _dynload("Upman::Extensions::#{extension.split('_').map(&:capitalize).join('')}")
59
+ info "Register /#{ext_obj ::REQUEST_PATH}.do for Upman::Extensions::#{extension.split('_').map(&:capitalize).join('')}"
60
+ ext_class = ext_obj .new
61
+ server.mount "/#{ext_obj ::REQUEST_PATH}.do", ext_class.register
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,61 @@
1
+ require 'rest-client'
2
+ require 'json'
3
+
4
+ module Upman
5
+ module Utils
6
+ class Api
7
+
8
+ include ::Upman::Utils::Helper
9
+
10
+ def check_connection
11
+ response = self.get "status"
12
+ if response.nil?
13
+ false
14
+ end
15
+
16
+ unless response.code == 200
17
+ fail "Could not connect to API endpoint"
18
+ false
19
+ end
20
+ success "API Endpoint connected to Forman #{JSON.parse(response.body)["version"]} using API v#{JSON.parse(response.body)["api_version"]}."
21
+ true
22
+ end
23
+
24
+
25
+ def post(api_endoint, data)
26
+ begin
27
+ RestClient::Request.execute method: :post, url: _url + api_endoint, data: data, content_type: :json, accept: :json, user: Upman::Core::Config.foreman_api[:username], password: Upman::Core::Config.foreman_api[:password]
28
+ rescue RestClient::Unauthorized
29
+ fail "API Endpoint could not validate your credentials. Please check username or password"
30
+ rescue OpenSSL::SSL::SSLError
31
+ fail "Could not establish a secure connection to our API Endpoint"
32
+ rescue RestClient::NotFound
33
+ fail "API Endpoint route could not found"
34
+ rescue Errno::ECONNREFUSED
35
+ fail "Can not connect to Foreman instance on #{_url}"
36
+ end
37
+
38
+ end
39
+
40
+ def get(api_endoint)
41
+ begin
42
+ RestClient::Request.execute method: :get, url: _url + api_endoint, content_type: :json, accept: :json, user: Upman::Core::Config.foreman_api[:username], password: Upman::Core::Config.foreman_api[:password]
43
+ rescue RestClient::Unauthorized
44
+ fail "API Endpoint could not validate your credentials. Please check username or password"
45
+ rescue OpenSSL::SSL::SSLError
46
+ fail "Could not establish a secure connection to our API Endpoint"
47
+ rescue RestClient::NotFound
48
+ fail "API Endpoint route could not found"
49
+ rescue Errno::ECONNREFUSED
50
+ fail "Can not connect to your Foreman instance on #{_url}"
51
+ end
52
+ end
53
+
54
+ def _url
55
+ config = ::Upman::Core::Config.foreman_api
56
+ protocol = config[:ssl] ? 'https://' : 'http://'
57
+ "#{protocol}#{config[:host]}:#{config[:port]}/api/v#{config[:api_version]}/"
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,7 @@
1
+ module Upman
2
+ module Utils
3
+ class Files
4
+
5
+ end
6
+ end
7
+ end