desmond 0.5.4

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,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: []