sp-job 0.1.17

Sign up to get free protection for your applications and to get access to all the features.
@@ -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