repertoire-repertoire-devtools 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/README.txt +12 -0
  2. data/bin/postgresql_crontab_sweep +96 -53
  3. metadata +4 -4
data/README.txt CHANGED
@@ -0,0 +1,12 @@
1
+ This gem contains tools for use in configuring Hyperstudio servers and workstations. Currently it contains:
2
+
3
+ Process management tools:
4
+
5
+ - merb_persist: adaptation of Apple's mongrel_rails_persist launchd helper, for merb
6
+ - postgresql_crontab_sweep: crontab like tasks for postgresql (esp. for full text indexing)
7
+
8
+ All are ruby applications; see individual files for more information. To install:
9
+
10
+ sudo gem install repertoire-repertoire-devtools --source http://gems.github.com
11
+
12
+ [ repetition in the gem name is not a typo - it's a side effect of github's gem naming system ]
@@ -5,6 +5,8 @@
5
5
  require "rubygems"
6
6
  require "pg"
7
7
 
8
+ require 'benchmark'
9
+
8
10
  #
9
11
  # Basic support for crontab-type tasks within PostgreSQL
10
12
  #
@@ -19,33 +21,36 @@ require "pg"
19
21
  # Before running, create the crontab table as follows:
20
22
  #
21
23
  # CREATE TABLE crontab(id SERIAL PRIMARY KEY,
22
- # notice TEXT NOT NULL,
23
- # task TEXT NOT NULL,
24
- # interval INTERVAL NOT NULL,
25
- # last_updated_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT 'epoch',
26
- # failing BOOLEAN NOT NULL DEFAULT false,
27
- # message TEXT);
24
+ # role TEXT,
25
+ # notice TEXT NOT NULL,
26
+ # task TEXT NOT NULL,
27
+ # interval INTERVAL NOT NULL,
28
+ # last_updated_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT 'epoch',
29
+ # time_elapsed INTERVAL,
30
+ # failing BOOLEAN NOT NULL DEFAULT false,
31
+ # message TEXT);
28
32
  #
29
33
  # This script makes a single sweep through the crontab list, executing pending tasks. To use,
30
34
  # run as a cron script (standard UNIX), or launchd task (OS X).
31
35
  #
32
- # If you would like the script to set up the database table and a basic launchd task, you can use
33
- # the following shortcut:
34
- # `sudo postgresql_crontab_sweep install <database-name>`
35
- # on the command line (OS X only). This will generate the relevant files, tables, and start the
36
- # update polling process.
36
+ # If you would like the script to set up the database table and a OS X launchd task, you can use
37
+ # the 'install' shortcut. For more information, see the help page available at 'postgresql_crontab_sweep -h'
37
38
  #
38
39
  # Each task is executed in a separate transaction, so they fail or succeed separately. If the
39
40
  # task commits, its timestamp is updated. If it aborts, last_updated_at will show the most
40
41
  # recent successful run, and the failing and message fields will be set.
41
42
  #
42
43
  # Parameters: -d <database> -p <port> -h <host> -u <user> -p <password> [ all are optional ]
43
- # or: install <database>
44
+ # or: install <database> [ -i <interval in seconds> ]
44
45
  #
45
46
  # Because this script should only be run locally to the database, it expects postgres to accept
46
47
  # your user without a password prompt (see pg_hba.conf to configure). Usually this user will
47
- # be 'postgres', the db admin role. When writing tasks, keep in mind that this user will
48
- # execute them. You may need to qualify tables in separate schemas, and grant access to appropriately.
48
+ # be 'postgres', the db admin role.
49
+ #
50
+ # When writing tasks, keep in mind that this user will execute them, unless the 'role' column has been
51
+ # filled. In this case, a SET ROLE <role> command will execute first, which allows the task to be
52
+ # written as though the provided role were executing it (most importantly, you can assume user's schema
53
+ # as default. You will need to grant this role to the superuser before this feature can be used, however.
49
54
  #
50
55
  # Christopher York 12/8/2008
51
56
  #
@@ -57,7 +62,7 @@ def run(dbparams)
57
62
  forking = false
58
63
 
59
64
  # get the pending database cron tasks
60
- pending = cron_conn.exec("SELECT id, notice, task, interval, last_updated_at FROM crontab WHERE last_updated_at + interval < now();")
65
+ pending = cron_conn.exec("SELECT * FROM crontab WHERE last_updated_at + interval < now();")
61
66
 
62
67
  pending.each do |crontask|
63
68
  # fork a sub-process for each task, with its own database connection
@@ -68,8 +73,12 @@ def run(dbparams)
68
73
  # all postgresql cron tasks take place in their own transaction (which is the reason for this script - not possible in plpgsql)
69
74
  task_conn.transaction do
70
75
  puts crontask[:notice]
71
- task_conn.exec("UPDATE crontab SET last_updated_at = now(), failing = false, message = NULL WHERE id = #{ crontask['id'] }")
72
- task_conn.exec(crontask['task'])
76
+ task_conn.exec("SET ROLE #{ crontask['role'] }") if crontask['role']
77
+ time_elapsed = Benchmark.realtime do
78
+ task_conn.exec(crontask['task'])
79
+ end
80
+ task_conn.exec("RESET ROLE")
81
+ task_conn.exec("UPDATE crontab SET last_updated_at = now(), time_elapsed = '#{ time_elapsed }', failing = false, message = NULL WHERE id = #{ crontask['id'] }")
73
82
  end
74
83
  rescue PGError => e
75
84
  STDERR.puts "ERROR! #{ Time.now } - #{ e.message }"
@@ -84,50 +93,74 @@ def run(dbparams)
84
93
  Process.wait if forking # avoid zombies by waiting for child processes to finish
85
94
  end
86
95
 
87
- def install(dbparams)
88
- launchd_plist = <<-END_OF_PLIST
89
- <?xml version="1.0" encoding="UTF-8"?>
90
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
91
- <plist version="1.0">
92
- <dict>
93
- <key>Label</key>
94
- <string>edu.mit.hyperstudio.postgresql.crontab.plist</string>
95
- <key>OnDemand</key>
96
- <false/>
97
- <key>ProgramArguments</key>
98
- <array>
99
- <string>/usr/bin/postgresql_crontab_sweep</string>
100
- <string>-d</string>
101
- <string>#{dbparams['dbname']}</string>
102
- </array>
103
- <key>StartCalendarInterval</key>
104
- <dict>
105
- <key>StartInterval</key>
106
- <integer>5</integer>
107
- </dict>
108
- </dict>
109
- </plist>
110
- END_OF_PLIST
111
-
112
- create_table = <<-END_OF_CREATE_TABLE
96
+ def install(dbparams, script_args, sweep_interval)
97
+ require 'osx/cocoa'
98
+ include OSX
99
+
100
+ create_table_stmt = <<-END_OF_CREATE_TABLE
113
101
  CREATE TABLE crontab(id SERIAL PRIMARY KEY,
114
102
  notice TEXT NOT NULL,
103
+ role TEXT,
115
104
  task TEXT NOT NULL,
116
105
  interval INTERVAL NOT NULL,
117
106
  last_updated_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT 'epoch',
107
+ time_elapsed INTERVAL,
118
108
  failing BOOLEAN NOT NULL DEFAULT false,
119
109
  message TEXT);
120
110
  END_OF_CREATE_TABLE
121
111
 
122
- File.open('/Library/LaunchDaemons/edu.mit.hyperstudio.postgresql.crontab.plist', 'w') { |f| f.write(launchd_plist) }
123
-
112
+ # create database crontab table
113
+ puts "Creating crontab table in database #{ dbparams[:dbname] }"
124
114
  conn = PGconn.open(dbparams)
125
- conn.exec(create_table)
115
+ begin
116
+ conn.exec(create_table_stmt)
117
+ rescue PGError => e
118
+ STDERR.puts "WARNING: #{ Time.now } - #{ e.message }"
119
+ end
126
120
  conn.close
121
+
122
+ # create and install the crontab launchd task
123
+ script_path = File.expand_path(File.dirname(__FILE__) + '/' + File.basename(__FILE__))
124
+ plist_label = "edu.mit.hyperstudio.postgresql.crontab"
125
+ plist_path = "/Library/LaunchDaemons/#{plist_label}.plist"
126
+
127
+ if ! test(?d,File.dirname(plist_path))
128
+ raise unless system('/bin/mkdir', '-m', '0755', plist_path)
129
+ raise unless system('/usr/sbin/chown', 'root:wheel', plist_path)
130
+ end
131
+
132
+ h = {}
133
+ h['Label'] = plist_label
134
+ h['OnDemand'] = false
135
+ h['ProgramArguments'] = [script_path, script_args.to_a].flatten.compact
136
+ h['StartCalendarInterval'] = { 'StartInterval' => sweep_interval }
137
+
138
+ data = NSPropertyListSerialization.dataFromPropertyList_format_errorDescription(h, NSPropertyListXMLFormat_v1_0, nil)
139
+ plist = NSString.alloc.initWithData_encoding(data, NSUTF8StringEncoding)
140
+ File.open(plist_path, 'w') { |f| f.puts plist }
141
+
142
+ `/bin/launchctl unload #{plist_path} >/dev/null 2>&1`
143
+ raise unless system('/bin/launchctl', 'load', plist_path)
144
+ puts "Loaded #{plist_path}\nPostgreSQL crontab sweeper should persist across reboot."
145
+ puts "To stop: /bin/launchctl unload -w #{plist_path}"
146
+
147
+ exit
127
148
  end
128
149
 
129
150
 
130
- # parse params, with defaults
151
+ # parse params, with defaults; handle usage
152
+
153
+ usage = <<EOU
154
+ Usage: #{File.basename($0)} -h | [-d <database>] [-p <port> ] [-u <user>] [-P <password>] [install]
155
+ This is a sweep script for Repertoire's PostgreSQL crontab support. Run once to sweep through the crontab table,
156
+ executing each task in a separate transaction/process. Or run with the 'install' parameter to create the
157
+ crontab table and start periodic launchd task to run it. See in-script comments for more details.
158
+ EOU
159
+
160
+ if ARGV.empty? || ARGV.include?('-h')
161
+ $stderr.puts usage
162
+ exit
163
+ end
131
164
 
132
165
  opts = {
133
166
  :dbname => 'hyperstudio_development',
@@ -135,16 +168,26 @@ opts = {
135
168
  :port => 5432,
136
169
  :host => 'localhost'
137
170
  }
171
+ args = {}
172
+ installing = false
173
+ sweep_interval = 5
138
174
 
139
175
  while opt = ARGV.shift
140
176
  case opt
141
- when '-d' then opts['dbname'] = ARGV.shift
142
- when '-p' then opts['port'] = ARGV.shfit
143
- when '-u' then opts['user'] = ARGV.shift
144
- when '-p' then opts['password'] = ARGV.shift
145
- when 'install' then install(opts) && exit
177
+ when '-d' then opts[:dbname] = args['-d'] = ARGV.shift
178
+ when '-p' then opts[:port] = args['-p'] = ARGV.shift
179
+ when '-u' then opts[:user] = args['-u'] = ARGV.shift
180
+ when '-P' then opts[:password] = args['-P'] = ARGV.shift
181
+ when 'install' then installing = true
182
+ when '-i' then sweep_interval = ARGV.shift
183
+ else raise "Unkown argument #{opt}"
146
184
  end
147
185
  end
148
186
 
149
- run(opts)
187
+ if installing
188
+ install(opts, args, sweep_interval)
189
+ else
190
+ run(opts)
191
+ end
192
+
150
193
  exit
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: repertoire-repertoire-devtools
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Christopher York
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-12-15 00:00:00 -08:00
12
+ date: 2009-01-02 00:00:00 -08:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -17,9 +17,9 @@ dependencies:
17
17
  version_requirement:
18
18
  version_requirements: !ruby/object:Gem::Requirement
19
19
  requirements:
20
- - - ">"
20
+ - - ">="
21
21
  - !ruby/object:Gem::Version
22
- version: 0.0.0
22
+ version: "0"
23
23
  version:
24
24
  description: Development and server tools for use with Hyperstudio Repertoire
25
25
  email: yorkc@mit.edu