minicron 0.2 → 0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,194 @@
1
+ require 'insidious'
2
+ require 'minicron'
3
+ require 'minicron/transport/client'
4
+
5
+ module Minicron
6
+ module CLI
7
+ class Commands
8
+ # Add the `minicron db` command
9
+ def self.add_db_cli_command(cli)
10
+ cli.command :db do |c|
11
+ c.syntax = 'minicron db [setup]'
12
+ c.description = 'Sets up the minicron database schema.'
13
+
14
+ c.action do |args, opts|
15
+ # Check that exactly one argument has been passed
16
+ if args.length != 1
17
+ fail ArgumentError, 'A valid command to run is required! See `minicron help db`'
18
+ end
19
+
20
+ # Parse the file and cli config options
21
+ Minicron::CLI.parse_config(opts)
22
+
23
+ # These are inlined as we only need them in this use case
24
+ require 'rake'
25
+ require 'minicron/hub/app'
26
+ require 'sinatra/activerecord/rake'
27
+
28
+ # Setup the db
29
+ Minicron::Hub::App.setup_db
30
+
31
+ # Tell activerecord where the db folder is, it assumes it is in db/
32
+ Sinatra::ActiveRecordTasks.db_dir = Minicron::HUB_PATH + '/db'
33
+
34
+ # Adjust the task name
35
+ task = args.first == 'setup' ? 'load' : args.first
36
+
37
+ # Run the task
38
+ Rake.application['db:schema:' + task].invoke
39
+ end
40
+ end
41
+ end
42
+
43
+ # Add the `minicron server` command
44
+ def self.add_server_cli_command(cli)
45
+ cli.command :server do |c|
46
+ c.syntax = 'minicron server [start|stop|status]'
47
+ c.description = 'Controls the minicron server.'
48
+ c.option '--host STRING', String, "The host for the server to listen on. Default: #{Minicron.config['server']['host']}"
49
+ c.option '--port STRING', Integer, "How port for the server to listed on. Default: #{Minicron.config['server']['port']}"
50
+ c.option '--path STRING', String, "The path on the host. Default: #{Minicron.config['server']['path']}"
51
+ c.option '--debug', "Enable debug mode. Default: #{Minicron.config['server']['debug']}"
52
+
53
+ c.action do |args, opts|
54
+ # Parse the file and cli config options
55
+ Minicron::CLI.parse_config(opts)
56
+
57
+ # If we get no arguments then default the action to start
58
+ action = args.first.nil? ? 'start' : args.first
59
+
60
+ # Get an instance of insidious and set the pid file
61
+ insidious = Insidious.new(
62
+ :pid_file => '/tmp/minicron.pid',
63
+ :daemonize => Minicron.config['server']['debug'] == false
64
+ )
65
+
66
+ case action
67
+ when 'start'
68
+ insidious.start! do
69
+ # Run the execution monitor (this runs in a separate thread)
70
+ monitor = Minicron::Monitor.new
71
+ monitor.start!
72
+
73
+ # Start the server!
74
+ Minicron::Transport::Server.start!(
75
+ Minicron.config['server']['host'],
76
+ Minicron.config['server']['port'],
77
+ Minicron.config['server']['path']
78
+ )
79
+ end
80
+ when 'stop'
81
+ insidious.stop!
82
+ when 'status'
83
+ if insidious.running?
84
+ puts 'minicron is running'
85
+ else
86
+ puts 'minicron is not running'
87
+ end
88
+ else
89
+ fail ArgumentError, 'Invalid action, expected [start|stop|status]. See `minicron help server`'
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ # Add the `minicron run [command]` command
96
+ # @yieldparam output [String] output from the cli
97
+ def self.add_run_cli_command(cli)
98
+ # Add the run command to the cli
99
+ cli.command :run do |c|
100
+ c.syntax = "minicron run 'command -option value'"
101
+ c.description = 'Runs the command passed as an argument.'
102
+ c.option '--mode STRING', String, "How to capture the command output, each 'line' or each 'char'? Default: #{Minicron.config['cli']['mode']}"
103
+ c.option '--dry-run', "Run the command without sending the output to the server. Default: #{Minicron.config['cli']['dry_run']}"
104
+
105
+ c.action do |args, opts|
106
+ # Check that exactly one argument has been passed
107
+ if args.length != 1
108
+ fail ArgumentError, 'A valid command to run is required! See `minicron help run`'
109
+ end
110
+
111
+ # Parse the file and cli config options
112
+ Minicron::CLI.parse_config(opts)
113
+
114
+ begin
115
+ # Set up the job and get the job and execution ids
116
+ unless Minicron.config['cli']['dry_run']
117
+ # Get a faye instance so we can send data about the job
118
+ faye = Minicron::Transport::Client.new(
119
+ Minicron.config['client']['scheme'],
120
+ Minicron.config['client']['host'],
121
+ Minicron.config['client']['port'],
122
+ Minicron.config['client']['path']
123
+ )
124
+
125
+ # Set up the job and get the jexecution and job ids back from the server
126
+ ids = setup_job(args.first, faye)
127
+ end
128
+
129
+ # Execute the command and yield the output
130
+ Minicron::CLI.run_command(args.first, :mode => Minicron.config['cli']['mode'], :verbose => Minicron.config['verbose']) do |output|
131
+ # We need to handle the yielded output differently based on it's type
132
+ case output[:type]
133
+ when :status
134
+ unless Minicron.config['cli']['dry_run']
135
+ faye.send(:job_id => ids[:job_id], :execution_id => ids[:execution_id], :type => :status, :message => output[:output])
136
+ end
137
+ when :command
138
+ unless Minicron.config['cli']['dry_run']
139
+ faye.send(:job_id => ids[:job_id], :execution_id => ids[:execution_id], :type => :output, :message => output[:output])
140
+ end
141
+ end
142
+
143
+ yield output[:output] unless output[:type] == :status
144
+ end
145
+ rescue Exception => e
146
+ # Send the exception message to the server and yield it
147
+ unless Minicron.config['cli']['dry_run']
148
+ faye.send(:job_id => ids[:job_id], :execution_id => ids[:execution_id], :type => :output, :message => e.message)
149
+ end
150
+
151
+ raise Exception, e
152
+ ensure
153
+ # Ensure that all messages are delivered and that we
154
+ unless Minicron.config['cli']['dry_run']
155
+ faye.ensure_delivery
156
+ faye.tidy_up
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end
162
+
163
+ private
164
+
165
+ # Setup a job by sending the SETUP command to the server
166
+ #
167
+ # @param command [String] the job command
168
+ # @param faye a faye client instance
169
+ # @return [Hash] the job_id and execution_id
170
+ def self.setup_job(command, faye)
171
+ # Get the fully qualified domain name of the currnet host
172
+ fqdn = Minicron.get_fqdn
173
+
174
+ # Get the short hostname of the current host
175
+ hostname = Minicron.get_hostname
176
+
177
+ # Get the md5 hash for the job
178
+ job_hash = Minicron::Transport.get_job_hash(command, fqdn)
179
+
180
+ # Fire up eventmachine
181
+ faye.ensure_em_running
182
+
183
+ # Setup the job on the server
184
+ ids = faye.setup(job_hash, command, fqdn, hostname)
185
+
186
+ # Wait until we get the execution id
187
+ faye.ensure_delivery
188
+
189
+ # Return the ids
190
+ ids
191
+ end
192
+ end
193
+ end
194
+ end
@@ -1,6 +1,6 @@
1
1
  # The minicron module
2
2
  module Minicron
3
- VERSION = '0.2'
3
+ VERSION = '0.3'
4
4
  DEFAULT_CONFIG_FILE = '/etc/minicron.toml'
5
5
  BASE_PATH = File.expand_path('../../../', __FILE__)
6
6
  LIB_PATH = File.expand_path('../../', __FILE__)
@@ -1,4 +1,5 @@
1
1
  require 'shellwords'
2
+ require 'escape'
2
3
 
3
4
  module Minicron
4
5
  # Used to interact with the crontab on hosts over an ssh connection
@@ -13,24 +14,17 @@ module Minicron
13
14
  @ssh = ssh
14
15
  end
15
16
 
16
- # Escape the quotes in a command
17
- #
18
- # @param command [String]
19
- # @return [String]
20
- def escape_command(command)
21
- command.gsub(/\\|'/) { |c| "\\#{c}" }
22
- end
23
-
24
17
  # Build the minicron command to be used in the crontab
25
18
  #
26
19
  # @param command [String]
27
20
  # @param schedule [String]
28
21
  # @return [String]
29
22
  def build_minicron_command(command, schedule)
30
- # Escape any quotes in the command
31
- command = escape_command(command)
23
+ # Escape the command so it will work in bourne shells
24
+ command = Escape.shell_command(['minicron', 'run', command])
25
+ cron_command =Escape.shell_command(['/bin/bash', '-l', '-c', command])
32
26
 
33
- "#{schedule} root minicron run '#{command}'"
27
+ "#{schedule} root #{cron_command}"
34
28
  end
35
29
 
36
30
  # Used to find a string and replace it with another in the crontab by
@@ -63,7 +57,7 @@ module Minicron
63
57
  # If it's a delete
64
58
  if replace == ''
65
59
  # Check the original line is no longer there
66
- grep = conn.exec!("grep -F \"#{find}\" /etc/crontab.tmp").to_s.strip
60
+ grep = conn.exec!("grep -F #{find.shellescape} /etc/crontab.tmp").to_s.strip
67
61
 
68
62
  # Throw an exception if we can't see our new line at the end of the file
69
63
  if grep != replace
@@ -71,7 +65,7 @@ module Minicron
71
65
  end
72
66
  else
73
67
  # Check the updated line is there
74
- grep = conn.exec!("grep -F \"#{replace}\" /etc/crontab.tmp").to_s.strip
68
+ grep = conn.exec!("grep -F #{replace.shellescape} /etc/crontab.tmp").to_s.strip
75
69
 
76
70
  # Throw an exception if we can't see our new line at the end of the file
77
71
  if grep != replace
@@ -98,14 +92,15 @@ module Minicron
98
92
 
99
93
  # Prepare the line we are going to write to the crontab
100
94
  line = build_minicron_command(job.command, schedule)
101
- echo_line = "echo \"#{line}\" >> /etc/crontab && echo 'y' || echo 'n'"
95
+ escaped_line = line.shellescape
96
+ echo_line = "echo #{escaped_line} >> /etc/crontab && echo 'y' || echo 'n'"
102
97
 
103
98
  # Append it to the end of the crontab
104
99
  write = conn.exec!(echo_line).strip
105
100
 
106
101
  # Throw an exception if it failed
107
102
  if write != 'y'
108
- fail Exception, "Unable to write '#{line}' to the crontab"
103
+ fail Exception, "Unable to echo #{escaped_line} to the crontab"
109
104
  end
110
105
 
111
106
  # Check the line is there
@@ -104,7 +104,7 @@ module Minicron::Hub
104
104
  end
105
105
 
106
106
  def handle_exception(env, e, status)
107
- if Minicron.config['global']['trace']
107
+ if Minicron.config['trace']
108
108
  env['rack.errors'].puts(e)
109
109
  env['rack.errors'].puts(e.backtrace.join("\n"))
110
110
  env['rack.errors'].flush
@@ -114,7 +114,7 @@ module Minicron::Hub
114
114
  hash = { :error => e.to_s }
115
115
 
116
116
  # Display the full trace if tracing is enabled
117
- hash[:trace] = e.backtrace if Minicron.config['global']['trace']
117
+ hash[:trace] = e.backtrace if Minicron.config['trace']
118
118
 
119
119
  [status, { 'Content-Type' => 'application/json' }, [hash.to_json]]
120
120
  end
@@ -1,4 +1,3 @@
1
- require 'minicron'
2
1
  require 'minicron/transport/ssh'
3
2
 
4
3
  class Minicron::Hub::App
@@ -109,8 +108,8 @@ class Minicron::Hub::App
109
108
  ssh.close
110
109
 
111
110
  # Delete the pub/priv key pair
112
- private_key_path = Minicron.sanitize_filename(File.expand_path("~/.ssh/minicron_host_#{host.id}_rsa"))
113
- public_key_path = Minicron.sanitize_filename(File.expand_path("~/.ssh/minicron_host_#{host.id}_rsa.pub"))
111
+ private_key_path = File.expand_path("~/.ssh/minicron_host_#{host.id}_rsa")
112
+ public_key_path = File.expand_path("~/.ssh/minicron_host_#{host.id}_rsa.pub")
114
113
  File.delete(private_key_path)
115
114
  File.delete(public_key_path)
116
115
 
@@ -12,92 +12,87 @@
12
12
 
13
13
  ActiveRecord::Schema.define(version: 0) do
14
14
 
15
- create_table 'alerts', force: true do |t|
16
- t.integer 'schedule_id'
17
- t.integer 'execution_id'
18
- t.string 'kind', limit: 4, default: '', null: false
19
- t.datetime 'expected_at'
20
- t.string 'medium', limit: 9, default: '', null: false
21
- t.datetime 'sent_at', null: false
15
+ create_table "alerts", force: true do |t|
16
+ t.integer "schedule_id"
17
+ t.integer "execution_id"
18
+ t.string "kind", limit: 4, default: "", null: false
19
+ t.datetime "expected_at"
20
+ t.string "medium", limit: 9, default: "", null: false
21
+ t.datetime "sent_at", null: false
22
22
  end
23
23
 
24
- add_index 'alerts', ['execution_id'], name: 'execution_id', using: :btree
25
- add_index 'alerts', ['expected_at'], name: 'expected_at', using: :btree
26
- add_index 'alerts', ['kind'], name: 'kind', using: :btree
27
- add_index 'alerts', ['medium'], name: 'medium', using: :btree
28
- add_index 'alerts', ['schedule_id'], name: 'schedule_id', using: :btree
24
+ add_index "alerts", ["execution_id"], name: "execution_id", using: :btree
25
+ add_index "alerts", ["expected_at"], name: "expected_at", using: :btree
26
+ add_index "alerts", ["kind"], name: "kind", using: :btree
27
+ add_index "alerts", ["medium"], name: "medium", using: :btree
28
+ add_index "alerts", ["schedule_id"], name: "schedule_id", using: :btree
29
29
 
30
- create_table 'executions', force: true do |t|
31
- t.integer 'job_id', null: false
32
- t.datetime 'created_at', null: false
33
- t.datetime 'started_at'
34
- t.datetime 'finished_at'
35
- t.integer 'exit_status'
30
+ create_table "executions", force: true do |t|
31
+ t.integer "job_id", null: false
32
+ t.datetime "created_at", null: false
33
+ t.datetime "started_at"
34
+ t.datetime "finished_at"
35
+ t.integer "exit_status"
36
36
  end
37
37
 
38
- add_index 'executions', ['created_at'], name: 'created_at', using: :btree
39
- add_index 'executions', ['finished_at'], name: 'finished_at', using: :btree
40
- add_index 'executions', ['job_id'], name: 'job_id', using: :btree
41
- add_index 'executions', ['started_at'], name: 'started_at', using: :btree
38
+ add_index "executions", ["created_at"], name: "created_at", using: :btree
39
+ add_index "executions", ["finished_at"], name: "finished_at", using: :btree
40
+ add_index "executions", ["job_id"], name: "job_id", using: :btree
41
+ add_index "executions", ["started_at"], name: "started_at", using: :btree
42
42
 
43
- create_table 'hosts', force: true do |t|
44
- t.string 'name'
45
- t.string 'fqdn', default: '', null: false
46
- t.string 'host', default: '', null: false
47
- t.integer 'port', null: false
48
- t.text 'public_key'
49
- t.datetime 'created_at', null: false
50
- t.datetime 'updated_at', null: false
43
+ create_table "hosts", force: true do |t|
44
+ t.string "name"
45
+ t.string "fqdn", default: "", null: false
46
+ t.string "host", default: "", null: false
47
+ t.integer "port", null: false
48
+ t.text "public_key"
49
+ t.datetime "created_at", null: false
50
+ t.datetime "updated_at", null: false
51
51
  end
52
52
 
53
- add_index 'hosts', ['fqdn'], name: 'hostname', using: :btree
53
+ add_index "hosts", ["fqdn"], name: "hostname", using: :btree
54
54
 
55
- create_table 'job_execution_outputs', force: true do |t|
56
- t.integer 'execution_id', null: false
57
- t.integer 'seq', null: false
58
- t.text 'output', null: false
59
- t.datetime 'timestamp', null: false
55
+ create_table "job_execution_outputs", force: true do |t|
56
+ t.integer "execution_id", null: false
57
+ t.integer "seq", null: false
58
+ t.text "output", null: false
59
+ t.datetime "timestamp", null: false
60
60
  end
61
61
 
62
- add_index 'job_execution_outputs', ['execution_id'], name: 'execution_id', using: :btree
63
- add_index 'job_execution_outputs', ['seq'], name: 'seq', using: :btree
62
+ add_index "job_execution_outputs", ["execution_id"], name: "execution_id", using: :btree
63
+ add_index "job_execution_outputs", ["seq"], name: "seq", using: :btree
64
64
 
65
- create_table 'jobs', force: true do |t|
66
- t.string 'job_hash', limit: 32, default: '', null: false
67
- t.string 'name'
68
- t.text 'command', null: false
69
- t.integer 'host_id', null: false
70
- t.datetime 'created_at', null: false
71
- t.datetime 'updated_at', null: false
65
+ create_table "jobs", force: true do |t|
66
+ t.string "job_hash", limit: 32, default: "", null: false
67
+ t.string "name"
68
+ t.text "command", null: false
69
+ t.integer "host_id", null: false
70
+ t.datetime "created_at", null: false
71
+ t.datetime "updated_at", null: false
72
72
  end
73
73
 
74
- add_index 'jobs', ['created_at'], name: 'created_at', using: :btree
75
- add_index 'jobs', ['host_id'], name: 'host_id', using: :btree
76
- add_index 'jobs', ['job_hash'], name: 'job_hash', unique: true, using: :btree
74
+ add_index "jobs", ["created_at"], name: "created_at", using: :btree
75
+ add_index "jobs", ["host_id"], name: "host_id", using: :btree
76
+ add_index "jobs", ["job_hash"], name: "job_hash", unique: true, using: :btree
77
77
 
78
- create_table 'schedules', force: true do |t|
79
- t.integer 'job_id', null: false
80
- t.string 'minute', limit: 179
81
- t.string 'hour', limit: 71
82
- t.string 'day_of_the_month', limit: 92
83
- t.string 'month', limit: 25
84
- t.string 'day_of_the_week', limit: 20
85
- t.string 'special', limit: 9
86
- t.datetime 'created_at', null: false
87
- t.datetime 'updated_at', null: false
78
+ create_table "schedules", force: true do |t|
79
+ t.integer "job_id", null: false
80
+ t.string "minute", limit: 179
81
+ t.string "hour", limit: 71
82
+ t.string "day_of_the_month", limit: 92
83
+ t.string "month", limit: 25
84
+ t.string "day_of_the_week", limit: 20
85
+ t.string "special", limit: 9
86
+ t.datetime "created_at", null: false
87
+ t.datetime "updated_at", null: false
88
88
  end
89
89
 
90
- add_index 'schedules', ['day_of_the_month'], name: 'day_of_the_month', using: :btree
91
- add_index 'schedules', ['day_of_the_week'], name: 'day_of_the_week', using: :btree
92
- add_index 'schedules', ['hour'], name: 'hour', using: :btree
93
- add_index 'schedules', ['job_id'], name: 'job_id', using: :btree
94
- add_index 'schedules', ['minute'], name: 'minute', using: :btree
95
- add_index 'schedules', ['month'], name: 'month', using: :btree
96
- add_index 'schedules', ['special'], name: 'special', using: :btree
97
-
98
- create_table 'users', force: true do |t|
99
- t.integer 'email', null: false
100
- t.integer 'password', null: false
101
- end
90
+ add_index "schedules", ["day_of_the_month"], name: "day_of_the_month", using: :btree
91
+ add_index "schedules", ["day_of_the_week"], name: "day_of_the_week", using: :btree
92
+ add_index "schedules", ["hour"], name: "hour", using: :btree
93
+ add_index "schedules", ["job_id"], name: "job_id", using: :btree
94
+ add_index "schedules", ["minute"], name: "minute", using: :btree
95
+ add_index "schedules", ["month"], name: "month", using: :btree
96
+ add_index "schedules", ["special"], name: "special", using: :btree
102
97
 
103
98
  end