desmond 0.5.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: cc12e128251786d7865236f627e20573ad622606
4
+ data.tar.gz: b65335f02f5d8332fab20b96cf93f0574444525e
5
+ SHA512:
6
+ metadata.gz: 4c45fc27e36157d1c9ab3b26a143276f78fcae7e20611aa40da0cc43bf186faf8a9d1b20a63a2bdeba28b583c06fcb48c07be0140b576526dc49b969b32c409b
7
+ data.tar.gz: b8a68ef6e23f7b3294e1a8b7a20649a1d2d5f81008b69e39e0efcc371a99e029baf5de664a36b894a77b7940d646f64422ca1b5ce84377f3411bcfe8da4d309c
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rake'
4
+ require 'daemons'
5
+ require_relative '../lib/desmond'
6
+
7
+ #
8
+ # script to run the desmond workers in background processes
9
+ # easily manageable using start/stop/restart commands
10
+ #
11
+
12
+ # set_daemon really early, so it true as early as possible
13
+ DesmondConfig.send :set_daemon
14
+ current_dir = Dir.pwd
15
+ options = { backtrace: true, log_dir: File.join(current_dir, 'log/') }
16
+ options = options.merge(dir_mode: :normal, dir: ARGV[1]) if ARGV.size > 1
17
+ Daemons.run_proc("desmond_for_#{DesmondConfig.app_id}", options) do
18
+ begin
19
+ Dir.chdir(current_dir)
20
+ Rake.application.init
21
+ Rake.application.load_rakefile
22
+ Rake::Task['desmond:run'].invoke
23
+ rescue Exception => e
24
+ DesmondConfig.logger.error "Fatal error in desmond process: #{e.message}"
25
+ DesmondConfig.logger.error e.backtrace.join("\n\t")
26
+ raise
27
+ end
28
+ end
@@ -0,0 +1,185 @@
1
+ require 'que'
2
+ require 'active_record'
3
+ require 'yaml'
4
+ require 'aws-sdk'
5
+ require 'active_support/hash_with_indifferent_access'
6
+
7
+ require_relative 'desmond/monkey_patches'
8
+ require_relative 'desmond/utils/pg_util'
9
+ require_relative 'desmond/utils/s3_util'
10
+ require_relative 'desmond/utils/log_censor'
11
+
12
+ require_relative 'desmond/execution_error'
13
+
14
+ require_relative 'desmond/streams/base'
15
+ require_relative 'desmond/streams/csv'
16
+ require_relative 'desmond/streams/database'
17
+ require_relative 'desmond/streams/s3'
18
+
19
+ require_relative 'desmond/job_run_finders'
20
+ require_relative 'desmond/jobs/base'
21
+ require_relative 'desmond/jobs/base_no_job_id'
22
+ require_relative 'desmond/jobs/pg_export'
23
+ require_relative 'desmond/jobs/export'
24
+ require_relative 'desmond/jobs/import'
25
+ require_relative 'desmond/jobs/unload'
26
+ require_relative 'desmond/jobs/sequential'
27
+
28
+ require_relative 'desmond/models/job_run'
29
+
30
+ ##
31
+ # manages the gem's configuration
32
+ #
33
+ class DesmondConfig
34
+ @is_daemon = false
35
+ @logger = Logger.new(STDOUT)
36
+ @exception_notifier = []
37
+ class << self
38
+ attr_accessor :logger
39
+ attr_reader :is_daemon
40
+ end
41
+
42
+ ##
43
+ # determins the environment we are running in:
44
+ # - development
45
+ # - staging
46
+ # - production
47
+ #
48
+ def self.environment
49
+ (ENV['RACK_ENV'] || 'development').to_sym
50
+ end
51
+ ##
52
+ # set where the desmond configuration file is located
53
+ # and reload the configration
54
+ #
55
+ def self.config_file=(file)
56
+ @config = load_config_file(file)
57
+ end
58
+ ##
59
+ # retrieve the desmond configuration
60
+ #
61
+ def self.config
62
+ self.config_file = 'config/desmond.yml' if @config.nil?
63
+ @config
64
+ end
65
+
66
+ ##
67
+ # retrieves the app_id from the config, defaults to 'desmond'
68
+ #
69
+ def self.app_id
70
+ config['app_id'] || 'desmond'
71
+ end
72
+
73
+ ##
74
+ # adds the given argument to the list of block to be called when
75
+ # an uncaught exception occurs during job execution
76
+ #
77
+ # blocks will be called with the arguments (exception, job_class, job_run)
78
+ #
79
+ def self.add_exception_notifier(&block)
80
+ @exception_notifier << block
81
+ end
82
+ def self.clear_exception_notifier
83
+ @exception_notifier = []
84
+ end
85
+ def self.register_with_exception_notifier(options={})
86
+ options.each do |notifier_name, options|
87
+ ExceptionNotifier.register_exception_notifier(notifier_name, options)
88
+ end
89
+ DesmondConfig.add_exception_notifier do |exception, job_class, job_run|
90
+ ExceptionNotifier.notify_exception(exception, :data => { :class => job_class, run: job_run })
91
+ end
92
+ end
93
+
94
+ ##
95
+ # change 'app_id' to +value+
96
+ # only use this in the 'test' environment otherwise the change
97
+ # will not be shared with the worker processes, use the desmond.yml
98
+ # configuration file instead
99
+ #
100
+ def self.app_id=(value)
101
+ fail 'Do not use this DesmondConfig.app_id= outside of "test"' if self.environment != :test
102
+ config['app_id'] = value
103
+ end
104
+ ##
105
+ # retrieve the location of the ActiveRecord database configuration file.
106
+ # should really only be used when developing desmond, otherwise the using app
107
+ # should establish the ActiveRecord connections
108
+ #
109
+ def self.database
110
+ load_config_file(config['database'] || 'config/database.yml')
111
+ end
112
+ ##
113
+ # determines whether the tasks are allowed to use the configured
114
+ # connection instead of using the users credentials
115
+ #
116
+ def self.system_connection_allowed?
117
+ config['system_connection_allowed'] || false
118
+ end
119
+ ##
120
+ # change 'system_connection_allowed' to +value+
121
+ # only use this in the 'test' environment otherwise the change
122
+ # will not be shared with the worker processes, use the desmond.yml
123
+ # configuration file instead
124
+ #
125
+ def self.system_connection_allowed=(value)
126
+ fail 'Do not use this DesmondConfig.system_connection_allowed= outside of "test"' if self.environment != :test
127
+ config['system_connection_allowed'] = value
128
+ end
129
+
130
+ def self.load_config_file(file)
131
+ return {} unless File.exist?(file)
132
+ ActiveSupport::HashWithIndifferentAccess.new(YAML.load_file(file))
133
+ end
134
+ private_class_method :load_config_file
135
+
136
+ def self.set_daemon
137
+ @is_daemon = true
138
+ end
139
+ private_class_method :set_daemon
140
+
141
+ def self.exception_notification(exception, job_class, job_run)
142
+ @exception_notifier.each do |thing|
143
+ thing.call(exception, job_class, job_run) rescue nil
144
+ end
145
+ end
146
+ private_class_method :exception_notification
147
+ end
148
+
149
+ # configure ActiveRecord, but the app using us should really do this,
150
+ # mostly included for development purposes on desmond itself
151
+ if ActiveRecord::Base.configurations.empty?
152
+ ActiveRecord::Base.configurations = DesmondConfig.database
153
+ ActiveRecord::Base.establish_connection DesmondConfig.environment
154
+ end
155
+
156
+ # configure que
157
+ Que.connection = ActiveRecord
158
+ ActiveRecord::Base.schema_format = :sql # otherwise the que_jobs table gets missed
159
+ Que.mode = :sync if DesmondConfig.environment == :test
160
+ Que.mode = :off if DesmondConfig.environment != :test
161
+
162
+ # configure log censoring, so that password and AWS secret keys don't end up in the logs
163
+ CENSORED_KEYS = %w(password secret_access_key)
164
+ Que.log_formatter = proc do |data|
165
+ tmp = ActiveSupport::HashWithIndifferentAccess.new(data)
166
+ if tmp.include?(:job) && !tmp[:job].nil?
167
+ tmp[:job][:args] = tmp[:job][:args].map do |arg|
168
+ censor_hash_keys(arg, CENSORED_KEYS) if arg.is_a?(Hash)
169
+ end
170
+ end
171
+ tmp.to_json
172
+ end
173
+
174
+ #
175
+ # overwriting the get logger method of Que, to always return
176
+ # Desmond's configured logger. Que tends to overwrite the logger
177
+ # in background situations, which we'll ignore by this.
178
+ #
179
+ module Que
180
+ class << self
181
+ def logger
182
+ DesmondConfig.logger
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,42 @@
1
+ namespace :desmond do
2
+ def pid_dir
3
+ fetch(:pid_dir, "#{fetch(:release_path)}/tmp/pids")
4
+ end
5
+
6
+ def cmd(cmd)
7
+ "cd #{current_path};#{env} #{workers} #{que_command} #{cmd} #{pid_dir}"
8
+ end
9
+
10
+ desc 'Stop the desmond process'
11
+ task :stop do
12
+ on roles(:app) do
13
+ within release_path do
14
+ with rack_env: fetch(:rack_env), que_worker_count: fetch(:que_num_workers, 1) do
15
+ execute :bundle, :exec, :desmond, 'stop', pid_dir
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ desc 'Start the desmond process'
22
+ task :start do
23
+ on roles(:app) do
24
+ within release_path do
25
+ with rack_env: fetch(:rack_env), que_worker_count: fetch(:que_num_workers, 1) do
26
+ execute :bundle, :exec, :desmond, 'start', pid_dir
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ desc 'Restart the desmond process'
33
+ task :restart do
34
+ on roles(:app) do
35
+ within release_path do
36
+ with rack_env: fetch(:rack_env), que_worker_count: fetch(:que_num_workers, 1) do
37
+ execute :bundle, :exec, :desmond, 'restart', pid_dir
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,84 @@
1
+ require_relative '../desmond'
2
+ require 'fileutils'
3
+
4
+ #
5
+ # returns migration files in the given directory +dirname+
6
+ #
7
+ def migrations(dirname)
8
+ Dir.foreach(dirname).select do |entry|
9
+ !entry.start_with?('.')
10
+ end
11
+ end
12
+
13
+ #
14
+ # returns the next migration number to use from the given directory +dirname+
15
+ #
16
+ def next_migration_number(dirname)
17
+ current_migration_number_str = migrations(dirname).map do |file|
18
+ File.basename(file).split('_').first
19
+ end.max || '0000'
20
+ current_migration_number = current_migration_number_str.to_i
21
+
22
+ num_digits = current_migration_number_str.size
23
+ if num_digits > 5
24
+ # timestamp format
25
+ [Time.now.utc.strftime('%Y%m%d%H%M%S'), format('%.14d', current_migration_number + 1)].max
26
+ else
27
+ # counter format
28
+ format("%.#{num_digits}d", current_migration_number + 1)
29
+ end
30
+ end
31
+
32
+ #
33
+ # copy migration +source_file+ to migrations folder +dest_folder+
34
+ # if it doesn't exist ther yet
35
+ #
36
+ def copy_migration(source_file, dest_folder)
37
+ # check if migration already exists
38
+ migration_exists = migrations(dest_folder).map do |file|
39
+ File.basename(file).split('_')[1..-1].join('_').eql?(File.basename(source_file))
40
+ end.any?
41
+ return if migration_exists
42
+
43
+ # copy migration file
44
+ migration_number = next_migration_number(dest_folder)
45
+ base_migration_name = File.basename source_file
46
+ migration_name = "#{migration_number}_#{base_migration_name}"
47
+ dest_file = File.join dest_folder, migration_name
48
+ FileUtils.cp(source_file, dest_file)
49
+ end
50
+
51
+ # hook into db:migrate to create our migrations and migrate Que
52
+ namespace :db do
53
+ task :migrate => :desmond_migrate
54
+
55
+ task :desmond_migrate do
56
+ Rake::Task['desmond:migrate'].invoke
57
+ end
58
+ end
59
+
60
+ namespace :desmond do
61
+ desc 'Setup database for desmond manually'
62
+ task :migrate do
63
+ Que.migrate! version: 3
64
+
65
+ migration = File.join File.dirname(__FILE__), 'migrations', 'add_desmond_job_runs.rb'
66
+ copy_migration(migration, 'db/migrate')
67
+ end
68
+
69
+ desc 'Start daemon for desmond'
70
+ task :run do
71
+ require 'que/rake_tasks' # so that que's tasks don't get directly included in using apps
72
+ ActiveRecord::Base.logger = DesmondConfig.logger
73
+ DesmondConfig.send :set_daemon
74
+ Rake::Task['que:work'].invoke
75
+ end
76
+
77
+ desc 'Clear job runs and queues'
78
+ task :clear do
79
+ require 'que/rake_tasks' # so that que's tasks don't get directly included in using apps
80
+ ActiveRecord::Base.logger = DesmondConfig.logger
81
+ Rake::Task['que:clear'].invoke
82
+ ActiveRecord::Base.connection.execute('TRUNCATE desmond_job_runs')
83
+ end
84
+ end
metadata ADDED
@@ -0,0 +1,226 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: desmond
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.4
5
+ platform: ruby
6
+ authors:
7
+ - Tobias Thiel
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-03-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '4.2'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '6'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '4.2'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '6'
33
+ - !ruby/object:Gem::Dependency
34
+ name: activesupport
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '4.2'
40
+ - - "<"
41
+ - !ruby/object:Gem::Version
42
+ version: '6'
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '4.2'
50
+ - - "<"
51
+ - !ruby/object:Gem::Version
52
+ version: '6'
53
+ - !ruby/object:Gem::Dependency
54
+ name: pg
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '0.17'
60
+ - - "<"
61
+ - !ruby/object:Gem::Version
62
+ version: '2.0'
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0.17'
70
+ - - "<"
71
+ - !ruby/object:Gem::Version
72
+ version: '2.0'
73
+ - !ruby/object:Gem::Dependency
74
+ name: que
75
+ requirement: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - "~>"
78
+ - !ruby/object:Gem::Version
79
+ version: '0.9'
80
+ type: :runtime
81
+ prerelease: false
82
+ version_requirements: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - "~>"
85
+ - !ruby/object:Gem::Version
86
+ version: '0.9'
87
+ - !ruby/object:Gem::Dependency
88
+ name: daemons
89
+ requirement: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - "~>"
92
+ - !ruby/object:Gem::Version
93
+ version: '1.2'
94
+ type: :runtime
95
+ prerelease: false
96
+ version_requirements: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - "~>"
99
+ - !ruby/object:Gem::Version
100
+ version: '1.2'
101
+ - !ruby/object:Gem::Dependency
102
+ name: aws-sdk
103
+ requirement: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: '2.0'
108
+ - - "<"
109
+ - !ruby/object:Gem::Version
110
+ version: '4.0'
111
+ type: :runtime
112
+ prerelease: false
113
+ version_requirements: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '2.0'
118
+ - - "<"
119
+ - !ruby/object:Gem::Version
120
+ version: '4.0'
121
+ - !ruby/object:Gem::Dependency
122
+ name: rake
123
+ requirement: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - "~>"
126
+ - !ruby/object:Gem::Version
127
+ version: '12.0'
128
+ type: :runtime
129
+ prerelease: false
130
+ version_requirements: !ruby/object:Gem::Requirement
131
+ requirements:
132
+ - - "~>"
133
+ - !ruby/object:Gem::Version
134
+ version: '12.0'
135
+ - !ruby/object:Gem::Dependency
136
+ name: rspec
137
+ requirement: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ type: :development
143
+ prerelease: false
144
+ version_requirements: !ruby/object:Gem::Requirement
145
+ requirements:
146
+ - - ">="
147
+ - !ruby/object:Gem::Version
148
+ version: '0'
149
+ - !ruby/object:Gem::Dependency
150
+ name: simplecov
151
+ requirement: !ruby/object:Gem::Requirement
152
+ requirements:
153
+ - - ">="
154
+ - !ruby/object:Gem::Version
155
+ version: '0'
156
+ type: :development
157
+ prerelease: false
158
+ version_requirements: !ruby/object:Gem::Requirement
159
+ requirements:
160
+ - - ">="
161
+ - !ruby/object:Gem::Version
162
+ version: '0'
163
+ - !ruby/object:Gem::Dependency
164
+ name: rubocop
165
+ requirement: !ruby/object:Gem::Requirement
166
+ requirements:
167
+ - - ">="
168
+ - !ruby/object:Gem::Version
169
+ version: '0'
170
+ type: :development
171
+ prerelease: false
172
+ version_requirements: !ruby/object:Gem::Requirement
173
+ requirements:
174
+ - - ">="
175
+ - !ruby/object:Gem::Version
176
+ version: '0'
177
+ - !ruby/object:Gem::Dependency
178
+ name: sinatra-activerecord
179
+ requirement: !ruby/object:Gem::Requirement
180
+ requirements:
181
+ - - ">="
182
+ - !ruby/object:Gem::Version
183
+ version: '0'
184
+ type: :development
185
+ prerelease: false
186
+ version_requirements: !ruby/object:Gem::Requirement
187
+ requirements:
188
+ - - ">="
189
+ - !ruby/object:Gem::Version
190
+ version: '0'
191
+ description: Background tasks & Exporting and importing data out of Amazon AWS RedShift
192
+ email: tobi@605.tv
193
+ executables:
194
+ - desmond
195
+ extensions: []
196
+ extra_rdoc_files: []
197
+ files:
198
+ - bin/desmond
199
+ - lib/desmond.rb
200
+ - lib/desmond/capistrano.rb
201
+ - lib/desmond/rake.rb
202
+ homepage: http://605.tv
203
+ licenses:
204
+ - MIT
205
+ metadata: {}
206
+ post_install_message:
207
+ rdoc_options: []
208
+ require_paths:
209
+ - lib
210
+ required_ruby_version: !ruby/object:Gem::Requirement
211
+ requirements:
212
+ - - ">="
213
+ - !ruby/object:Gem::Version
214
+ version: '0'
215
+ required_rubygems_version: !ruby/object:Gem::Requirement
216
+ requirements:
217
+ - - ">="
218
+ - !ruby/object:Gem::Version
219
+ version: '0'
220
+ requirements: []
221
+ rubyforge_project:
222
+ rubygems_version: 2.6.12
223
+ signing_key:
224
+ specification_version: 4
225
+ summary: Background tasks & CSV Export/Import
226
+ test_files: []