pd-blender 0.0.1

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.
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