pd-blender 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/.rubocop.yml +2 -0
  4. data/.travis.yml +10 -0
  5. data/Gemfile +6 -0
  6. data/LICENSE.txt +14 -0
  7. data/README.md +342 -0
  8. data/Rakefile +21 -0
  9. data/bin/blend +20 -0
  10. data/blender.gemspec +36 -0
  11. data/lib/blender.rb +67 -0
  12. data/lib/blender/cli.rb +71 -0
  13. data/lib/blender/configuration.rb +45 -0
  14. data/lib/blender/discovery.rb +41 -0
  15. data/lib/blender/drivers/base.rb +40 -0
  16. data/lib/blender/drivers/compound.rb +29 -0
  17. data/lib/blender/drivers/ruby.rb +55 -0
  18. data/lib/blender/drivers/shellout.rb +63 -0
  19. data/lib/blender/drivers/ssh.rb +93 -0
  20. data/lib/blender/drivers/ssh_multi.rb +102 -0
  21. data/lib/blender/event_dispatcher.rb +45 -0
  22. data/lib/blender/exceptions.rb +26 -0
  23. data/lib/blender/handlers/base.rb +39 -0
  24. data/lib/blender/handlers/doc.rb +73 -0
  25. data/lib/blender/job.rb +73 -0
  26. data/lib/blender/lock/flock.rb +64 -0
  27. data/lib/blender/log.rb +24 -0
  28. data/lib/blender/rspec.rb +68 -0
  29. data/lib/blender/rspec/stub_registry.rb +45 -0
  30. data/lib/blender/scheduled_job.rb +66 -0
  31. data/lib/blender/scheduler.rb +114 -0
  32. data/lib/blender/scheduler/dsl.rb +160 -0
  33. data/lib/blender/scheduling_strategies/base.rb +30 -0
  34. data/lib/blender/scheduling_strategies/default.rb +37 -0
  35. data/lib/blender/scheduling_strategies/per_host.rb +38 -0
  36. data/lib/blender/scheduling_strategies/per_task.rb +37 -0
  37. data/lib/blender/tasks/base.rb +72 -0
  38. data/lib/blender/tasks/ruby.rb +31 -0
  39. data/lib/blender/tasks/shell_out.rb +30 -0
  40. data/lib/blender/tasks/ssh.rb +25 -0
  41. data/lib/blender/timer.rb +54 -0
  42. data/lib/blender/utils/refinements.rb +45 -0
  43. data/lib/blender/utils/thread_pool.rb +54 -0
  44. data/lib/blender/utils/ui.rb +51 -0
  45. data/lib/blender/version.rb +20 -0
  46. data/spec/blender/blender_rspec.rb +31 -0
  47. data/spec/blender/discovery_spec.rb +16 -0
  48. data/spec/blender/drivers/ssh_multi_spec.rb +16 -0
  49. data/spec/blender/drivers/ssh_spec.rb +17 -0
  50. data/spec/blender/dsl_spec.rb +19 -0
  51. data/spec/blender/event_dispatcher_spec.rb +17 -0
  52. data/spec/blender/job_spec.rb +42 -0
  53. data/spec/blender/lock_spec.rb +129 -0
  54. data/spec/blender/scheduled_job_spec.rb +30 -0
  55. data/spec/blender/scheduler_spec.rb +140 -0
  56. data/spec/blender/scheduling_strategies/default_spec.rb +75 -0
  57. data/spec/blender/utils/refinements_spec.rb +16 -0
  58. data/spec/blender/utils/thread_pool_spec.rb +16 -0
  59. data/spec/blender_spec.rb +37 -0
  60. data/spec/data/example.rb +12 -0
  61. data/spec/spec_helper.rb +35 -0
  62. metadata +304 -0
@@ -0,0 +1,36 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'blender/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'pd-blender'
8
+ spec.version = Blender::VERSION
9
+ spec.authors = ['Ranjib Dey']
10
+ spec.email = ['ranjib@pagerduty.com']
11
+ spec.summary = %q{A modular orchestration engine}
12
+ spec.description = %q{Discover hosts, run tasks against them and control their execution order}
13
+ spec.homepage = 'http://github.com/PagerDuty/blender'
14
+ spec.license = 'Apache 2'
15
+ spec.bindir = 'bin'
16
+ spec.executables = ['blend']
17
+
18
+ spec.files = `git ls-files -z`.split("\x0")
19
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
+ spec.require_paths = ['lib']
21
+
22
+ spec.add_dependency 'highline'
23
+ spec.add_dependency 'thor'
24
+ spec.add_dependency 'mixlib-shellout'
25
+ spec.add_dependency 'mixlib-log'
26
+ spec.add_dependency 'net-ssh'
27
+ spec.add_dependency 'rufus-scheduler'
28
+
29
+ spec.add_development_dependency 'bundler'
30
+ spec.add_development_dependency 'rake'
31
+ spec.add_development_dependency 'rspec'
32
+ spec.add_development_dependency 'rubocop'
33
+ spec.add_development_dependency 'simplecov'
34
+ spec.add_development_dependency 'yard'
35
+ spec.add_development_dependency 'pry'
36
+ end
@@ -0,0 +1,67 @@
1
+ #
2
+ # Author:: Ranjib Dey (<ranjib@pagerduty.com>)
3
+ # Copyright:: Copyright (c) 2014 PagerDuty, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ require 'blender/version'
19
+ require 'blender/scheduler'
20
+ require 'blender/log'
21
+ require 'blender/drivers/shellout'
22
+ require 'json'
23
+ require 'blender/configuration'
24
+
25
+ # Top level module that holds all blender related libraries under this namespace
26
+ module Blender
27
+ # Trigger a blender run. If a block is given, then an object of
28
+ # Blender::Scheduler is yielded, otherwise the argument is treated as a
29
+ # command and executed with local shellout driver
30
+ #
31
+ # @param name [String] Name of the run
32
+ #
33
+ # @return [void]
34
+ def self.blend(name, config_file = nil)
35
+ scheduler = Scheduler.new(name)
36
+ if config_file
37
+ configure(config_file)
38
+ end
39
+ if block_given?
40
+ yield scheduler
41
+ else
42
+ scheduler.task(name)
43
+ end
44
+ scheduler.run
45
+ scheduler
46
+ end
47
+
48
+ def self.configure(file)
49
+ data = JSON.parse(File.read(file))
50
+ if data['log_level']
51
+ Blender::Log.level = data['log_level'].to_sym
52
+ end
53
+ if data['log_file']
54
+ Blender::Log.init(data['log_file'])
55
+ end
56
+ if data['load_paths']
57
+ data['load_paths'].each do |path|
58
+ $LOAD_PATH.unshift(path)
59
+ end
60
+ end
61
+ if data['scheduler']
62
+ data['scheduler'].each do |key, value|
63
+ Blender::Configuration[key.to_sym] = value
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,71 @@
1
+ #
2
+ # Author:: Ranjib Dey (<ranjib@pagerduty.com>)
3
+ # Copyright:: Copyright (c) 2014 PagerDuty, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ require 'thor'
19
+ require 'blender'
20
+ require 'blender/timer'
21
+
22
+ module Blender
23
+ class CLI < Thor
24
+
25
+ def self.exit_on_failure?
26
+ true
27
+ end
28
+
29
+ default_command :from_file
30
+ package_name 'Blender'
31
+
32
+ desc 'from_file ', 'Run blender job from a file'
33
+ method_option :file,
34
+ default: 'Blendfile',
35
+ type: :string,
36
+ aliases: '-f'
37
+
38
+ method_option :config_file,
39
+ default: nil,
40
+ type: :string,
41
+ aliases: '-c',
42
+ banner: 'Provide additional configuration via json file'
43
+
44
+ method_option :noop,
45
+ default: false,
46
+ type: :boolean,
47
+ aliases: '-n',
48
+ banner: 'No-op mode, run blender without executing jobs'
49
+
50
+ def from_file
51
+ Configuration[:noop] = options[:noop]
52
+ des = File.read(options[:file])
53
+ $LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(options[:file]), 'lib')))
54
+ Blender.blend(options[:file], options[:config_file]) do |sch|
55
+ sch.instance_eval(des, __FILE__, __LINE__)
56
+ end
57
+ end
58
+
59
+ desc 'schedule', 'Run blender in daemon mode, with job scheduled in periodic interval'
60
+ method_option :schedule,
61
+ default: 'Schedule_it',
62
+ type: :string,
63
+ aliases: '-s'
64
+ def schedule
65
+ sched = Blender::Timer.new
66
+ des = File.read(options[:schedule])
67
+ sched.instance_eval(des, __FILE__, __LINE__)
68
+ sched.join
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,45 @@
1
+ #
2
+ # Author:: Ranjib Dey (<ranjib@pagerduty.com>)
3
+ # Copyright:: Copyright (c) 2014 PagerDuty, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ require 'singleton'
19
+ require 'thread'
20
+
21
+ module Blender
22
+ class Configuration
23
+ include Singleton
24
+
25
+ attr_reader :data, :mutex
26
+
27
+ def initialize
28
+ @data = Hash.new{|h,k| h[k] = Hash.new}
29
+ @data[:noop] = false
30
+ @mutex = Mutex.new
31
+ end
32
+
33
+ def self.[]=(key, value)
34
+ instance.mutex.synchronize do
35
+ instance.data[key] = value
36
+ end
37
+ end
38
+
39
+ def self.[](key)
40
+ instance.mutex.synchronize do
41
+ instance.data[key]
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,41 @@
1
+ #
2
+ # Author:: Ranjib Dey (<ranjib@pagerduty.com>)
3
+ # Copyright:: Copyright (c) 2014 PagerDuty, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ require 'blender/utils/refinements'
19
+ require 'blender/configuration'
20
+
21
+ module Blender
22
+ module Discovery
23
+ include Blender::Utils::Refinements
24
+
25
+ def build_discovery(type, opts = {})
26
+ disco_klass = Blender::Discovery.const_get(camelcase(type.to_s).to_sym)
27
+ disco_opts = Blender::Configuration[type].merge(opts)
28
+ disco_klass.new(symbolize(disco_opts))
29
+ end
30
+
31
+ def search_with_config(type, opts = {})
32
+ options = opts.dup
33
+ search_opts = options.delete(:search)
34
+ build_discovery(type, options).search(search_opts)
35
+ end
36
+
37
+ def search(type, options = nil)
38
+ search_with_config(type, search: options)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,40 @@
1
+ #
2
+ # Author:: Ranjib Dey (<ranjib@pagerduty.com>)
3
+ # Copyright:: Copyright (c) 2014 PagerDuty, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ require 'blender/log'
19
+
20
+ module Blender
21
+ module Driver
22
+ class Base
23
+ attr_reader :config, :events, :stdout, :stderr
24
+
25
+ ExecOutput = Struct.new(:exitstatus, :stdout, :stderr)
26
+
27
+ def initialize(config = {})
28
+ cfg = config.dup
29
+ @stdout = cfg.delete(:stdout) || File.open(File::NULL, 'w')
30
+ @stderr = cfg.delete(:stderr) || File.open(File::NULL, 'w')
31
+ @events = cfg.delete(:events) or fail 'Events needed'
32
+ @config = cfg
33
+ end
34
+
35
+ def execute(tasks, hosts)
36
+ raise RuntimeError, 'this method must be overridden'
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,29 @@
1
+ #
2
+ # Author:: Ranjib Dey (<ranjib@pagerduty.com>)
3
+ # Copyright:: Copyright (c) 2014 PagerDuty, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ module Blender
18
+ module Driver
19
+ class Compound
20
+ def execute(tasks, hosts)
21
+ hosts.each do |host|
22
+ tasks.each do |task|
23
+ task.driver.execute([task], [host])
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,55 @@
1
+ #
2
+ # Author:: Ranjib Dey (<ranjib@pagerduty.com>)
3
+ # Copyright:: Copyright (c) 2014 PagerDuty, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ require 'blender/drivers/base'
19
+
20
+ module Blender
21
+ module Driver
22
+ class Ruby < Base
23
+
24
+ def execute(tasks, hosts)
25
+ tasks.each do |task|
26
+ hosts.each do |host|
27
+ cmd = run_command(task.command, host)
28
+ if cmd.exitstatus != 0 and !task.metadata[:ignore_failure]
29
+ raise ExecutionFailed, cmd.stderr
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ def run_command(command, host)
36
+ exit_status = 0
37
+ err = ''
38
+ current_stdout = STDOUT.clone
39
+ current_stderr = STDERR.clone
40
+ begin
41
+ STDOUT.reopen(stdout)
42
+ STDERR.reopen(stderr)
43
+ command.call(host)
44
+ rescue StandardError => e
45
+ err = e.message + "\nBacktrace:" + e.backtrace.join("\n")
46
+ exit_status = -1
47
+ ensure
48
+ STDOUT.reopen(current_stdout)
49
+ STDERR.reopen(current_stderr)
50
+ end
51
+ ExecOutput.new(exit_status, '', err)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,63 @@
1
+ #
2
+ # Author:: Ranjib Dey (<ranjib@pagerduty.com>)
3
+ # Copyright:: Copyright (c) 2014 PagerDuty, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ require 'mixlib/shellout'
19
+
20
+ module Blender
21
+ module Driver
22
+ class ShellOut < Base
23
+
24
+ def initialize(config = {})
25
+ @options = {}
26
+ cfg = config.dup
27
+ [:user, :group, :cwd, :umask, :returns, :environment, :timeout].each do |key|
28
+ if cfg.key?(key)
29
+ @options[key] = cfg.delete(key)
30
+ end
31
+ end
32
+ super(cfg)
33
+ end
34
+
35
+ def execute(tasks, hosts)
36
+ verify_local_host!(hosts)
37
+ tasks.each do |task|
38
+ cmd = run_command(task.command)
39
+ if cmd.exitstatus != 0 and !task.metadata[:ignore_failure]
40
+ raise ExecutionFailed, cmd.stderr
41
+ end
42
+ end
43
+ end
44
+
45
+ def run_command(command)
46
+ cmd = Mixlib::ShellOut.new(command, @options)
47
+ begin
48
+ cmd.live_stream = stdout
49
+ cmd.run_command
50
+ ExecOutput.new(cmd.exitstatus, cmd.stdout, cmd.stderr)
51
+ rescue Errno::ENOENT => e
52
+ ExecOutput.new(-1, '', e.message)
53
+ end
54
+ end
55
+
56
+ def verify_local_host!(hosts)
57
+ unless hosts.all?{|h|h == 'localhost'}
58
+ raise UnsupportedFeature, 'This driver does not support any host other than localhost'
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end