sp-job 0.1.17

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,91 @@
1
+ #
2
+ # Copyright (c) 2011-2017 Cloudware S.A. All rights reserved.
3
+ #
4
+ # This file is part of sp-job.
5
+ #
6
+ # sp-job is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU Affero General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # sp-job is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Affero General Public License
17
+ # along with sp-job. If not, see <http://www.gnu.org/licenses/>.
18
+ #
19
+ # encoding: utf-8
20
+ #
21
+ ### IMPORTANT - serious this is important
22
+ # YOU must require 'rmagick' on the script that uses this class, should be used like this
23
+ #
24
+ # How to use this to implement a customized image conversion
25
+ #
26
+ # require 'rmagick'
27
+ # require 'sp-job'
28
+ # require 'sp/job/back_burner'
29
+ # require 'sp/job/uploaded_image_converter'
30
+ #
31
+ # class CLASSNAME
32
+ # extend SP::Job::Common
33
+ # extend SP::Job::UploadedImageConverter
34
+ #
35
+ # def self.perform(job)
36
+ #
37
+ # ... Your code before the image conversion ...
38
+ #
39
+ # SP::Job::UploadedImageConverter.perform(job)
40
+ #
41
+ # ... your code after the conversion ...
42
+ #
43
+ # end
44
+ # end
45
+ #
46
+ # Backburner.work
47
+ #
48
+
49
+ module SP
50
+ module Job
51
+ module UploadedImageConverter
52
+ extend SP::Job::Common
53
+
54
+ def self.perform (job)
55
+
56
+ throw Exception.new("i18n_entity_id_must_be_defined") if job[:entity_id].nil? || job[:entity_id].to_i == 0
57
+
58
+ step = 100 / (job[:copies].size + 1)
59
+ original = File.join($config[:paths][:temporary_uploads], job[:original])
60
+ destination = File.join($config[:paths][:uploads_storage], job[:entity], id_to_path(job[:entity_id]), job[:folder])
61
+
62
+ #
63
+ # Read the original image, any format that image magic can handle will be ok
64
+ #
65
+ FileUtils::mkdir_p destination
66
+ image = Magick::Image.read(original).first
67
+ update_progress(step: step, message: 'i18n_reading_original_$image', image: job[:original])
68
+
69
+ barrier = true # To force progress on first scalling
70
+
71
+ #
72
+ # Iterate the copies array
73
+ #
74
+ job[:copies].each do |copy|
75
+ img_copy = image.copy()
76
+ img_copy.change_geometry(copy[:geometry].to_s) do |cols, rows, img|
77
+ img.resize!(cols, rows)
78
+ end
79
+ img_copy.write(File.join(destination, copy[:name]))
80
+ update_progress(step: step, message: 'i18n_scalling_image_$name$geometry', name: copy[:name], geometry: copy[:geometry], barrier: barrier)
81
+ logger.debug("Scaled to geometry #{copy[:geometry]}")
82
+ barrier = false
83
+ end
84
+
85
+ # Closing arguments, all done
86
+ update_progress(status: 'completed', message: 'i18n_image_conversion_complete', link: File.join('/',job[:entity], id_to_path(job[:entity_id]), job[:folder], 'logo_template.png'))
87
+ end
88
+
89
+ end # UploadedImageConverter
90
+ end # Job
91
+ end # SP
@@ -0,0 +1,24 @@
1
+ #
2
+ # Copyright (c) 2011-2016 Cloudware S.A. All rights reserved.
3
+ #
4
+ # This file is part of sp-job.
5
+ #
6
+ # sp-job is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU Affero General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # sp-job is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Affero General Public License
17
+ # along with sp-job. If not, see <http://www.gnu.org/licenses/>.
18
+ #
19
+
20
+ module SP
21
+ module Job
22
+ VERSION = File.read(File.expand_path('../../../../VERSION', __FILE__)).strip
23
+ end
24
+ end
@@ -0,0 +1,46 @@
1
+ #
2
+ # Copyright (c) 2011-2017 Cloudware S.A. All rights reserved.
3
+ #
4
+ # This file is part of sp-job.
5
+ #
6
+ # sp-job is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU Affero General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # sp-job is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Affero General Public License
17
+ # along with sp-job. If not, see <http://www.gnu.org/licenses/>.
18
+ #
19
+ # encoding: utf-8
20
+ #
21
+
22
+ module SP
23
+ module Job
24
+
25
+ class Worker < Backburner::Workers::Simple
26
+
27
+ def initialize (tube_names=nil)
28
+ super(tube_names)
29
+ end
30
+
31
+ def start
32
+ prepare
33
+ loop do
34
+ work_one_job
35
+ unless connection.connected?
36
+ log_error "Connection to beanstalk closed, exiting now"
37
+ exit
38
+ end
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ end # Worker
45
+ end # Module Job
46
+ end # Module SP
@@ -0,0 +1,452 @@
1
+ # require 'byebug'
2
+ require 'json'
3
+ require 'erb'
4
+ require 'ostruct'
5
+ require 'awesome_print'
6
+ require 'os'
7
+ require 'fileutils'
8
+ require 'etc'
9
+
10
+ def safesudo(cmd)
11
+ unless true == system(cmd)
12
+ system("sudo #{cmd}")
13
+ end
14
+ end
15
+
16
+ def create_directory (path)
17
+
18
+ if ! Dir.exists?(path)
19
+ if OS.mac?
20
+ if path.match("^/usr/local/")
21
+ info = Etc.getpwnam(Etc.getlogin)
22
+ puts "\t* Creating '#{path}'...".yellow
23
+ safesudo("mkdir -p #{path}")
24
+ if 0 != $?.exitstatus
25
+ puts "\t* Failed to create #{path}".red
26
+ end
27
+ next_parent_path = File.join("/usr/local", path.split(File::SEPARATOR).map {|x| x=="" ? File::SEPARATOR : x}[1..-1][2])
28
+ if ! next_parent_path
29
+ throw "Unable to create path #{path} - parent not found!"
30
+ end
31
+ safesudo("chown -R #{info.name}:#{Etc.getgrgid(info.gid).name} #{next_parent_path}")
32
+ if 0 != $?.exitstatus
33
+ puts "\t* Failed to change ownership to #{path}".red
34
+ end
35
+ else
36
+ safesudo("mkdir -p #{path}")
37
+ if 0 != $?.exitstatus
38
+ puts "\t* Failed to create #{path}".red
39
+ end
40
+ end
41
+ else
42
+ if path.match("^/home/")
43
+ safesudo("mkdir -p #{path}")
44
+ else
45
+ safesudo("mkdir -p #{path}")
46
+ end
47
+ if 0 != $?.exitstatus
48
+ puts "\t* Failed to create #{path}".red
49
+ end
50
+ end
51
+ if ! OS.mac? && !path.match("^/home/")
52
+ safesudo("chown #{$user}:#{$group} #{path}")
53
+ else
54
+ safesudo("chown #{$user}:#{$group} #{path}")
55
+ end
56
+ if 0 != $?.exitstatus
57
+ puts "\t* Failed to change ownership to #{path}".red
58
+ end
59
+ if ! OS.mac? && !path.match("^/home/")
60
+ safesudo("chmod 755 #{path}")
61
+ else
62
+ safesudo("chmod 755 #{path}")
63
+ end
64
+ if 0 != $?.exitstatus
65
+ puts "\t* Failed to change permissions to #{path}".red
66
+ end
67
+ end
68
+
69
+ end
70
+
71
+ def diff_and_write (contents:, path:, diff: true, dry_run: false)
72
+
73
+ if OS.mac?
74
+ create_directory File.dirname path
75
+ end
76
+
77
+ if ! File.exists?(path)
78
+ if contents.length == 0
79
+ puts "\t* #{path} does not exist and it's empty, ignored".green
80
+ return
81
+ else
82
+ safesudo("touch #{path}")
83
+ end
84
+ end
85
+
86
+ if true == diff
87
+ tmp_file = Tempfile.new File.basename path
88
+ FileUtils::mkdir_p File.dirname tmp_file
89
+ File.write(tmp_file,contents)
90
+ diff_contents = %x[diff -u #{path} #{tmp_file.path} 2>/dev/null]
91
+ if 0 == $?.exitstatus
92
+ puts "\t* #{path} not changed".green
93
+ return
94
+ end
95
+ if File.exists?(path)
96
+ puts "\t* #{path} changed:".red
97
+ puts diff_contents
98
+ else
99
+ puts "\t* #{path} does not exist. Will be created".blue
100
+ end
101
+
102
+ end
103
+ puts "\t* Writing #{path}".green
104
+ unless dry_run
105
+ if OS.mac? || File.writable?(path) || path.match("^/home/")
106
+ File.write(path, contents)
107
+ else
108
+ safesudo("chown #{$user}:#{$group} #{path}")
109
+ File.write(path, contents)
110
+ safesudo("chown root:root #{path}")
111
+ end
112
+ end
113
+ FileUtils.rm(tmp_file)
114
+ end
115
+
116
+ def get_config
117
+
118
+ hostname = %x[hostname -s].strip
119
+ @project = Dir.pwd
120
+ @user_home = File.expand_path('~')
121
+
122
+ #
123
+ # Pick file named 'hostname', or use 'developer' as basefile
124
+ #
125
+ if File.exists?("#{@project}/configure/#{hostname}.yml")
126
+ conf = YAML.load_file("#{@project}/configure/#{hostname}.yml")
127
+ conf['file_name'] = hostname
128
+ else
129
+ conf = YAML.load_file("#{@project}/configure/developer.yml")
130
+ conf['file_name'] = 'developer'
131
+ end
132
+
133
+ #
134
+ # Follow configuration dependencies and merge the configurations
135
+ #
136
+ configs = [ conf ]
137
+ loop do
138
+ break if conf['extends'].nil?
139
+ ancestor = conf['extends']
140
+ conf = YAML.load_file("#{@project}/configure/#{ancestor}.yml")
141
+ conf['file_name'] = ancestor || 'developer'
142
+ configs << conf
143
+ end
144
+
145
+ (configs.size - 2).downto(0).each do |i|
146
+ puts "Step #{i}: merging '#{configs[i]['file_name']}' with '#{configs[i+1]['file_name']}'"
147
+ configs[i].config_merge(configs[i+1])
148
+ end
149
+
150
+ conf = configs[0]
151
+
152
+ #
153
+ # Allow overide of project directory
154
+ #
155
+ conf['paths']['project'] ||= @project
156
+
157
+ #
158
+ # Resolve user and group if needed
159
+ #
160
+ if conf['user'].nil?
161
+ conf['user'] = %x[id -u -nr].strip
162
+ end
163
+ if conf['group'].nil?
164
+ conf['group'] = %x[id -g -nr].strip
165
+ end
166
+
167
+ #
168
+ # Pre-cook the connection string
169
+ #
170
+ dbname = conf['db']['dbname']
171
+ dbuser = conf['db']['user']
172
+ dbhost = conf['db']['host']
173
+ dbpass = conf['db']['password'] || ''
174
+ conf['db']['connection_string'] = "host=#{dbhost} dbname=#{dbname} user=#{dbuser}#{dbpass.size != 0 ? ' password='+ dbpass : '' }"
175
+
176
+ #
177
+ # Resolve project and user relative paths
178
+ #
179
+ conf['paths'].each do |name, path|
180
+ if path.start_with? '$project'
181
+ conf['paths'][name] = path.sub('$project', conf['paths']['project'] || @project)
182
+ elsif path.start_with? '$user_home'
183
+ conf['paths'][name] = path.sub('$user_home', @user_home)
184
+ end
185
+ end
186
+
187
+ conf.clean_keys!
188
+
189
+ ap conf
190
+ return JSON.parse(conf.to_json, object_class: OpenStruct), conf
191
+ end
192
+
193
+ desc 'Update project configuration: action=overwrite => update system,user,project; action => hotfix update project only; other no change (dryrun)'
194
+ task :configure, [ :action ] do |task, args|
195
+
196
+ # Monkey patch for configuration deep merge
197
+ class ::Hash
198
+
199
+ def config_merge (second)
200
+
201
+ second.each do |skey, sval|
202
+ if self.has_key?(skey+'!')
203
+ self[skey] = self[skey+'!']
204
+ self.delete(skey+'!')
205
+ next
206
+ elsif skey[-1] == '!'
207
+ tkey = skey[0..-2]
208
+ if self.has_key?(tkey)
209
+ if Array === self[tkey] && Array === sval
210
+ self[tkey] = self[tkey] | sval
211
+ elsif Hash === self[tkey] && Hash === sval
212
+ self[tkey].config_merge(sval)
213
+ else
214
+ raise "Error can't merge #{skey} with different types"
215
+ end
216
+ end
217
+ end
218
+
219
+ if ! self.has_key?(skey)
220
+ self[skey] = sval
221
+ else
222
+ if Array === self[skey] && Array === sval
223
+ self[skey] = self[skey] | sval
224
+ elsif Hash === self[skey] && Hash === sval
225
+ self[skey].config_merge(sval)
226
+ end
227
+ end
228
+ end
229
+ end
230
+
231
+ def clean_keys!
232
+ tmp = Hash.new
233
+
234
+ self.each do |key, val|
235
+ if Hash === val
236
+ val.clean_keys!
237
+ end
238
+
239
+ if key[-1] == '!'
240
+ tmp[key[0..-2]] = val
241
+ self.delete(key)
242
+ end
243
+ end
244
+
245
+ self.merge! tmp
246
+ end
247
+
248
+ end
249
+
250
+ if args[:action] == 'overwrite'
251
+ dry_run = false
252
+ action = 'overwrite'
253
+ elsif args[:action] == 'hotfix'
254
+ dry_run = false
255
+ action = 'hotfix'
256
+ else
257
+ dry_run = true
258
+ action = 'dry-run'
259
+ end
260
+
261
+ #
262
+ # Read the configuration into ostruct @config will be accessible to the ERB templates
263
+ #
264
+ @config, conf = get_config()
265
+
266
+ #
267
+ # Resolve project and user again to create the relative paths
268
+ #
269
+ conf['paths'].each do |name, path|
270
+ if path.start_with? '$project'
271
+ conf['paths'][name] = path.sub('$project', conf['paths']['project'] || @project)
272
+ FileUtils.mkdir_p conf['paths'][name]
273
+ elsif path.start_with? '$user_home'
274
+ conf['paths'][name] = path.sub('$user_home', @user_home)
275
+ FileUtils.mkdir_p conf['paths'][name]
276
+ end
277
+ end
278
+
279
+
280
+ # Set helper variables on the task context
281
+ $user = @config.user
282
+ $group = @config.group
283
+ @project = Dir.pwd
284
+ @user_home = File.expand_path('~')
285
+ diff_before_copy = true
286
+
287
+ #
288
+ # Create required paths
289
+ #
290
+ if @config.nginx_broker && @config.nginx_broker.nginx && @config.nginx_broker.nginx.paths
291
+ @config.nginx_broker.nginx.paths.each do |path|
292
+ if OS.mac? && @config.nginx_broker.nginx.suffix
293
+ path = path.sub('nginx-broker', "nginx-broker#{@config.nginx_broker.nginx.suffix}")
294
+ end
295
+ create_directory "#{@config.prefix}#{path}"
296
+ end
297
+ end
298
+ if @config.nginx_epaper && @config.nginx_epaper.nginx && @config.nginx_epaper.nginx.paths
299
+ @config.nginx_epaper.nginx.paths.each do |path|
300
+ create_directory "#{@config.prefix}#{path}"
301
+ end
302
+ end
303
+
304
+ #
305
+ # Configure system, projects and user files
306
+ #
307
+ locations = {}
308
+ used_locations = []
309
+ if action == 'dry-run' || action == 'overwrite'
310
+ paths = { 'system' => @config.prefix, 'project' => @project, 'user' => @user_home}
311
+ else
312
+ paths = { 'project' => @project }
313
+ end
314
+ paths.each do |src, dest|
315
+ puts "Configuring #{src.upcase}"
316
+
317
+ # List all .erb files in hidden and visible folders
318
+ erblist = Dir.glob("#{@project}/configure/#{src}/.**/*.erb") +
319
+ Dir.glob("#{@project}/configure/#{src}/**/*.erb")
320
+
321
+ erblist.each do |template|
322
+ dst_file = template.sub("#{@project}/configure/#{src}", "#{dest}").sub(/\.erb$/, '')
323
+
324
+ # developer exception
325
+ if OS.mac? && @config.nginx_broker && @config.nginx_broker.nginx.suffix
326
+ dst_file = dst_file.sub('nginx-broker', "nginx-broker#{@config.nginx_broker.nginx.suffix}")
327
+ end
328
+
329
+ # Nginx Locations must be filtered, only handle locations that are used
330
+ m = /.*\.location$/.match(dst_file)
331
+ if m
332
+ locations[dst_file] = template
333
+ next
334
+ end
335
+
336
+ # Filter nginx vhosts that do not have and entry, only install the vhosts that have an entry in nginx-xxxxx
337
+ m = /.*(nginx-broker|nginx-epaper)\/conf\.d\/(.*)\.conf$/.match(dst_file)
338
+ if m && m.size == 3
339
+ key_l1 = m[1].gsub('-', '_')
340
+ if conf[key_l1].nil? or conf[key_l1][m[2]].nil?
341
+ puts "Filtered #{m[1]} - #{m[2]} - #{dst_file}".yellow
342
+ next
343
+ end
344
+ end
345
+ # do not touch config files on top folder if that nginx is not requested
346
+ m = /.*(nginx-broker|nginx-epaper)\/(.*)$/.match(dst_file)
347
+ if m && m.size == 3
348
+ key_l1 = m[1].gsub('-', '_')
349
+ if conf[key_l1].nil?
350
+ puts "Filtered #{m[1]} - #{m[2]} - #{dst_file}".yellow
351
+ next
352
+ end
353
+ end
354
+
355
+ # 2nd filtered
356
+ if @config.erb_exclusion_list
357
+ base_filename = File.basename(dst_file)
358
+ if @config.erb_exclusion_list.include?(base_filename)
359
+ puts "Filtered #{base_filename}".yellow
360
+ next
361
+ end
362
+ end
363
+
364
+ # puts "Expanding #{template}".red
365
+ # Now expand the template
366
+ file_contents = ERB.new(File.read(template), nil, '-').result()
367
+
368
+ if /.*(nginx-broker|nginx-epaper)\/conf\.d\/(.*)\.conf$/.match(dst_file)
369
+ includes = file_contents.scan(/^\s*include\s+conf\.d\/(.*)\.location\;/)
370
+ includes.each do |loc|
371
+ used_locations << loc[0]
372
+ end
373
+ end
374
+
375
+ # Write text expanded configuration file
376
+ create_directory(File.dirname dst_file)
377
+ diff_and_write(contents: file_contents,
378
+ path: dst_file,
379
+ diff: diff_before_copy,
380
+ dry_run: dry_run
381
+ )
382
+ end
383
+ end
384
+
385
+ #
386
+ # configure the nginx locations that are used
387
+ #
388
+ if action == 'dry-run' || action == 'overwrite'
389
+ if used_locations.size
390
+ puts "Configuring NGINX LOCATIONS"
391
+ locations.each do |dst_file, template|
392
+ m = /.*\/(.*).location$/.match dst_file
393
+ if used_locations.include? m[1]
394
+ # Write text expanded configuration file
395
+ create_directory(File.dirname dst_file)
396
+ diff_and_write(contents: ERB.new(File.read(template), nil, '-').result(),
397
+ path: dst_file,
398
+ diff: diff_before_copy,
399
+ dry_run: dry_run
400
+ )
401
+ end
402
+ end
403
+ end
404
+ end
405
+
406
+ #
407
+ # Configure JOBS
408
+ #
409
+ if action == 'dry-run' || action == 'overwrite'
410
+ puts "Configuring JOBS"
411
+ @config.jobs.to_h.each do |name, job|
412
+ @job_name = name
413
+ @job_description = "TODO Description"
414
+ @job_dir = "#{@config.paths.working_directory}/jobs/#{@job_name}"
415
+
416
+ puts " #{name}:"
417
+ if File.exists? "#{@job_dir}/conf.json.erb"
418
+ template = "#{@job_dir}/conf.json.erb"
419
+ else
420
+ template = "#{@config.paths.working_directory}/jobs/default_conf.json.erb"
421
+ end
422
+ unless File.exists? template
423
+ throw "Missing #{template} => configuration file for #{@job_name}"
424
+ end
425
+ if OS.mac?
426
+ create_directory("/usr/local/var/lock/#{@job_name}/")
427
+ end
428
+ create_directory "#{@config.prefix}/etc/#{@job_name}"
429
+ create_directory "#{@config.prefix}/var/log/#{@job_name}"
430
+ diff_and_write(contents: JSON.pretty_generate(JSON.parse(ERB.new(File.read(template), nil, '-').result())),
431
+ path: "#{@config.prefix}/etc/#{@job_name}/conf.json",
432
+ diff: diff_before_copy,
433
+ dry_run: dry_run
434
+ )
435
+
436
+ if File.exists? "#{@job_dir}/service.erb"
437
+ template = "#{@job_dir}/service.erb"
438
+ else
439
+ template = "#{@config.paths.working_directory}/jobs/default.service.erb"
440
+ end
441
+ unless File.exists? template
442
+ throw "Missing service file for #{@job_name}"
443
+ end
444
+
445
+ diff_and_write(contents: ERB.new(File.read(template)).result(),
446
+ path: "#{@config.prefix}/lib/systemd/system/#{@job_name}@.service",
447
+ diff: diff_before_copy,
448
+ dry_run: dry_run
449
+ )
450
+ end
451
+ end
452
+ end