repertoire-repertoire-devtools 0.0.1 → 0.0.2

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.
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