singularity_dsl 1.2.5

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 (49) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +10 -0
  5. data/.singularityrc +12 -0
  6. data/Gemfile +6 -0
  7. data/Gemfile.lock +52 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +38 -0
  10. data/Rakefile +24 -0
  11. data/bin/singularity_runner +5 -0
  12. data/lib/singularity_dsl/application.rb +76 -0
  13. data/lib/singularity_dsl/cli/cli.rb +125 -0
  14. data/lib/singularity_dsl/cli/table.rb +32 -0
  15. data/lib/singularity_dsl/dsl/batch.rb +23 -0
  16. data/lib/singularity_dsl/dsl/changeset.rb +39 -0
  17. data/lib/singularity_dsl/dsl/components.rb +7 -0
  18. data/lib/singularity_dsl/dsl/dsl.rb +71 -0
  19. data/lib/singularity_dsl/dsl/event_store.rb +34 -0
  20. data/lib/singularity_dsl/dsl/registry.rb +60 -0
  21. data/lib/singularity_dsl/dsl/runner.rb +67 -0
  22. data/lib/singularity_dsl/dsl/utils.rb +28 -0
  23. data/lib/singularity_dsl/errors.rb +35 -0
  24. data/lib/singularity_dsl/files.rb +20 -0
  25. data/lib/singularity_dsl/git_helper.rb +94 -0
  26. data/lib/singularity_dsl/runstate.rb +30 -0
  27. data/lib/singularity_dsl/stdout.rb +16 -0
  28. data/lib/singularity_dsl/task.rb +30 -0
  29. data/lib/singularity_dsl/tasks/rake.rb +31 -0
  30. data/lib/singularity_dsl/tasks/rspec.rb +32 -0
  31. data/lib/singularity_dsl/tasks/rubocop.rb +54 -0
  32. data/lib/singularity_dsl/tasks/shell_task.rb +90 -0
  33. data/lib/singularity_dsl/version.rb +6 -0
  34. data/lib/singularity_dsl.rb +9 -0
  35. data/singularity_dsl.gemspec +31 -0
  36. data/spec/singularity_dsl/application_spec.rb +92 -0
  37. data/spec/singularity_dsl/dsl/batch_spec.rb +30 -0
  38. data/spec/singularity_dsl/dsl/changeset_spec.rb +54 -0
  39. data/spec/singularity_dsl/dsl/dsl_spec.rb +45 -0
  40. data/spec/singularity_dsl/dsl/event_store_spec.rb +44 -0
  41. data/spec/singularity_dsl/dsl/registry_spec.rb +39 -0
  42. data/spec/singularity_dsl/dsl/runner_spec.rb +39 -0
  43. data/spec/singularity_dsl/dsl/stubs/tasks/dummy_task.rb +4 -0
  44. data/spec/singularity_dsl/dsl/utils_spec.rb +33 -0
  45. data/spec/singularity_dsl/git_helper_spec.rb +72 -0
  46. data/spec/singularity_dsl/runstate_spec.rb +73 -0
  47. data/spec/singularity_dsl/task_spec.rb +54 -0
  48. data/spec/singularity_dsl/tasks/shell_task_spec.rb +119 -0
  49. metadata +231 -0
@@ -0,0 +1,67 @@
1
+ # encoding: utf-8
2
+
3
+ require 'singularity_dsl/dsl/batch'
4
+ require 'singularity_dsl/dsl/dsl'
5
+ require 'singularity_dsl/errors'
6
+ require 'singularity_dsl/runstate'
7
+
8
+ module SingularityDsl
9
+ # DSL classes & fxs
10
+ module Dsl
11
+ # class that runs Singularity::Dsl
12
+ class Runner
13
+ include SingularityDsl::Errors
14
+
15
+ attr_reader :state, :dsl
16
+
17
+ def initialize
18
+ @dsl = Dsl.new
19
+ @state = Runstate.new
20
+ end
21
+
22
+ def execute(batch = false, pass_errors = false)
23
+ @dsl.registry.run_list(batch).each do |task|
24
+ task.execute.tap do |status|
25
+ failed = task.failed_status status
26
+ record_failure task if failed
27
+ resource_fail task if failed && !pass_errors
28
+ end
29
+ end
30
+ end
31
+
32
+ def load_ex_script(path)
33
+ @dsl.instance_eval(::File.read path)
34
+ end
35
+
36
+ def post_actions
37
+ @dsl.error_proc.call if @state.error
38
+ @dsl.fail_proc.call if @state.failed
39
+ @dsl.success_proc.call unless @state.failed || @state.error
40
+ @dsl.always_proc.call
41
+ end
42
+
43
+ private
44
+
45
+ def record_failure(task)
46
+ failure = klass_failed(task)
47
+ failure += " #{task.task_name}" if task.task_name
48
+ @state.add_failure failure
49
+ end
50
+
51
+ def raise_dsl_set_err(dsl)
52
+ fail "Invalid object given #{dsl}"
53
+ end
54
+
55
+ def execute_task(task)
56
+ failed = false
57
+ begin
58
+ failed = task.execute
59
+ rescue ::StandardError => err
60
+ @state.add_error "#{err.message}\n#{err.backtrace}"
61
+ resource_err task
62
+ end
63
+ failed
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,28 @@
1
+ # encoding: utf-8
2
+
3
+ module SingularityDsl
4
+ # DSL classes & fxs
5
+ module Dsl
6
+ # Utility functions mixin module
7
+ module Utils
8
+ def task_name(klass)
9
+ klass.to_s.split(':').last
10
+ end
11
+
12
+ def task(klass)
13
+ task_name(klass).downcase.to_sym
14
+ end
15
+
16
+ def task_list
17
+ klasses = []
18
+ SingularityDsl.constants.each do |klass|
19
+ klass = SingularityDsl.const_get(klass)
20
+ next unless klass.is_a? Class
21
+ next unless klass < Task
22
+ klasses.push klass
23
+ end
24
+ klasses
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,35 @@
1
+ # encoding: utf-8
2
+
3
+ module SingularityDsl
4
+ # error & failure classes / methods
5
+ module Errors
6
+ class ResourceFail < RuntimeError
7
+ end
8
+
9
+ class ResourceError < RuntimeError
10
+ end
11
+
12
+ def klass_name(obj)
13
+ return obj.class if obj.class < Object
14
+ obj
15
+ end
16
+
17
+ def klass_error(klass)
18
+ klass = klass_name klass
19
+ "#{klass} threw an exception while executing"
20
+ end
21
+
22
+ def klass_failed(klass)
23
+ klass = klass_name klass
24
+ "#{klass} failed."
25
+ end
26
+
27
+ def resource_fail(klass)
28
+ fail ResourceFail, klass_failed(klass)
29
+ end
30
+
31
+ def resource_err(klass)
32
+ fail ResourceError, klass_error(klass)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,20 @@
1
+ # encoding: utf-8
2
+
3
+ module SingularityDsl
4
+ # File helper fx mixin
5
+ module Files
6
+ private
7
+
8
+ def files_in_path(path)
9
+ paths = [path] if ::File.file? path
10
+ paths = dir_glob path if ::File.directory? path
11
+ paths ||= []
12
+ paths
13
+ end
14
+
15
+ def dir_glob(dir)
16
+ dir = ::File.join dir, '**'
17
+ ::Dir.glob dir
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,94 @@
1
+ # encoding: utf-8
2
+
3
+ require 'mixlib/shellout'
4
+
5
+ module SingularityDsl
6
+ # wrapper class for rugged
7
+ class GitHelper
8
+ attr_reader :dir
9
+
10
+ def initialize
11
+ throw 'git not installed' unless git_installed
12
+ end
13
+
14
+ def clean_reset
15
+ fail 'failed to clean' unless (reset | clean) == 0
16
+ end
17
+
18
+ def checkout_remote(branch, remote)
19
+ remote_action branch, remote, 'checkout'
20
+ end
21
+
22
+ def merge_remote(branch, url)
23
+ remote_action branch, url, 'merge'
24
+ end
25
+
26
+ def diff_remote(branch, url, flags = '')
27
+ flags = flags.join ' ' if flags.kind_of? Array
28
+ cmd = remote_cmd branch, url, "diff #{flags}"
29
+ task = Mixlib::ShellOut.new cmd
30
+ task.run_command
31
+ task.stdout
32
+ end
33
+
34
+ def add_remote(url)
35
+ remote = remote_from_url url
36
+ exec("git remote add #{remote} #{url}")
37
+ fetch_all
38
+ end
39
+
40
+ def remove_remote(url)
41
+ remote = remote_from_url url
42
+ return 0 if remote.eql? 'origin'
43
+ exec("git remote rm #{remote}")
44
+ end
45
+
46
+ private
47
+
48
+ def remote_cmd(branch, url, action)
49
+ remote = remote_from_url url
50
+ "git #{action} #{remote}/#{branch}"
51
+ end
52
+
53
+ def remote_action(branch, url, action)
54
+ status = exec(remote_cmd branch, url, action)
55
+ fail "failed to #{action}" unless status == 0
56
+ status
57
+ end
58
+
59
+ def remote_from_url(url)
60
+ return 'origin' if url.nil? || !url
61
+ url.split(':').last.gsub('/', '_')
62
+ end
63
+
64
+ def fetch_all
65
+ exec 'git fetch --all'
66
+ end
67
+
68
+ def remotes
69
+ (`git remote`.split "\n") - ['origin']
70
+ end
71
+
72
+ def index_path
73
+ ::File.join(@repo.path, 'index')
74
+ end
75
+
76
+ def git_installed
77
+ !`which git`.empty?
78
+ end
79
+
80
+ def reset
81
+ exec 'git add . && git reset --hard'
82
+ end
83
+
84
+ def clean
85
+ exec 'git clean -fdx'
86
+ end
87
+
88
+ def exec(cmd)
89
+ task = Mixlib::ShellOut.new cmd
90
+ task.run_command
91
+ task.exitstatus
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,30 @@
1
+ # encoding: utf-8
2
+
3
+ module SingularityDsl
4
+ # abstraction class for the overall runtime state
5
+ class Runstate
6
+ attr_reader :errors, :failures, :error, :failed
7
+
8
+ def initialize
9
+ @error = false
10
+ @errors = []
11
+ @failed = false
12
+ @failures = []
13
+ end
14
+
15
+ def add_failure(fail_msg)
16
+ @failed = true
17
+ @failures.push fail_msg
18
+ end
19
+
20
+ def add_error(err_msg)
21
+ @error = true
22
+ @errors.push err_msg
23
+ end
24
+
25
+ def exit_code
26
+ return 1 if @error || @failed
27
+ 0
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,16 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rainbow'
4
+
5
+ module SingularityDsl
6
+ # mixin for output wrappers
7
+ module Stdout
8
+ def info(message)
9
+ puts Rainbow(message).cyan
10
+ end
11
+
12
+ def data(message)
13
+ puts Rainbow(message).green
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,30 @@
1
+ # encoding: utf-8
2
+
3
+ module SingularityDsl
4
+ # Task abstraction class
5
+ class Task
6
+ def initialize(&block)
7
+ instance_eval(&block) unless block.nil?
8
+ end
9
+
10
+ def validate_file(file)
11
+ throw "Cannot find #{file}" unless File.exist? file
12
+ end
13
+
14
+ def execute
15
+ fail 'SingularityDsl::Task::execute not implemented'
16
+ end
17
+
18
+ def failed_status(status)
19
+ ![nil, 0, false].include? status
20
+ end
21
+
22
+ def description
23
+ "Runs #{self.class} task"
24
+ end
25
+
26
+ def task_name
27
+ false
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,31 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rake'
4
+
5
+ # Rake Task
6
+ class Rake < Task
7
+ attr_accessor :target, :rake
8
+
9
+ def initialize(&block)
10
+ ::Rake.application.init
11
+ @rake = ::Rake.application
12
+ super(&block)
13
+ end
14
+
15
+ def target(target)
16
+ @target = target
17
+ @target.strip!
18
+ end
19
+
20
+ def execute
21
+ throw 'target is required' if @target.nil?
22
+ @rake.load_rakefile
23
+ ret = @rake[@target].invoke
24
+ return ret.count if ret.kind_of? Array
25
+ ret
26
+ end
27
+
28
+ def description
29
+ 'Simple resource to just wrap the Rake CLI'
30
+ end
31
+ end
@@ -0,0 +1,32 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rspec'
4
+
5
+ # RSpec Task
6
+ class RSpec < Task
7
+ attr_accessor :spec_dir, :config_file
8
+
9
+ def initialize(&block)
10
+ @spec_dir ||= './spec'
11
+ @config_file ||= './.rspec'
12
+ super(&block)
13
+ end
14
+
15
+ def config_file(file)
16
+ validate_file file
17
+ @config_file = file
18
+ end
19
+
20
+ def spec_dir(dir)
21
+ validate_file dir
22
+ @spec_dir = dir
23
+ end
24
+
25
+ def execute
26
+ ::RSpec::Core::Runner.run([@spec_dir])
27
+ end
28
+
29
+ def description
30
+ 'Run RSpec tests. Uses RSpec::Core'
31
+ end
32
+ end
@@ -0,0 +1,54 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubocop'
4
+
5
+ # Rubocop task
6
+ # Intentionally NOT a rake task - for some reason the syck YAML parser
7
+ # that Rubocop uses internally freaks out when a
8
+ # ::Rake::Application[:task].invoke is called from a task
9
+ class Rubocop < Task
10
+ # :files => specific files to run against
11
+ # :cfg_file => separate config file
12
+ def initialize(&block)
13
+ @default_config = './.rubocop.yml'
14
+ @files = []
15
+ @cfg_files = [@default_config]
16
+ @cfg_store = ::RuboCop::ConfigStore.new
17
+ super(&block)
18
+ end
19
+
20
+ def config_file(file)
21
+ validate_file file
22
+
23
+ return if @cfg_files.include? file
24
+ warn 'Loading multiple configs' if File.exist? @default_config
25
+
26
+ @cfg_files.push file
27
+
28
+ # this does a merge of options...
29
+ @cfg_store.options_config = file
30
+ end
31
+
32
+ def file(file)
33
+ validate_file file
34
+ @files.push file
35
+ end
36
+
37
+ def execute
38
+ inspector = ::RuboCop::FileInspector.new({})
39
+ # yes, have to pass in a block
40
+ inspector.process_files files, @cfg_store do
41
+ false
42
+ end
43
+ end
44
+
45
+ def description
46
+ 'Runs rubocop, loads .rubocop.yml from ./'
47
+ end
48
+
49
+ private
50
+
51
+ def files
52
+ ::RuboCop::TargetFinder.new(@cfg_store).find @files
53
+ end
54
+ end
@@ -0,0 +1,90 @@
1
+ # encoding: utf-8
2
+
3
+ require 'mixlib/shellout'
4
+ require 'singularity_dsl/stdout'
5
+
6
+ # shell-out resource for any ol commands
7
+ class ShellTask < SingularityDsl::Task
8
+ include SingularityDsl::Stdout
9
+
10
+ attr_reader :shell, :conditionals, :alternative, :no_fail
11
+ attr_writer :live_stream
12
+
13
+ def initialize(&block)
14
+ @live_stream = STDOUT
15
+ @conditionals = []
16
+ @no_fail = false
17
+ @alternative = 'echo "no alternative shell cmd defined"'
18
+ super(&block)
19
+ end
20
+
21
+ def condition(cmd)
22
+ invalid_cmd 'condition' unless cmd.is_a? String
23
+ @conditionals.push cmd
24
+ end
25
+
26
+ def no_fail(switch)
27
+ fail 'no_fail must be bool' unless bool? switch
28
+ @no_fail = switch
29
+ end
30
+
31
+ def alt(cmd)
32
+ invalid_cmd 'alt' unless cmd.is_a? String
33
+ @alternative = cmd
34
+ end
35
+
36
+ def command(cmd)
37
+ @task_name = cmd
38
+ @shell = setup_shell cmd
39
+ end
40
+
41
+ def task_name
42
+ return @task_name if @task_name
43
+ super
44
+ end
45
+
46
+ def execute
47
+ throw 'command never defined' if @shell.nil?
48
+ command @alternative unless evaluate_conditionals
49
+ @live_stream << log_shell if @live_stream
50
+ @shell.run_command
51
+ return 0 if @no_fail
52
+ @shell.exitstatus
53
+ end
54
+
55
+ def description
56
+ 'Runs a SH command using Mixlib::ShellOut'
57
+ end
58
+
59
+ private
60
+
61
+ def log_shell(pre = '', shell = false)
62
+ shell ||= @shell
63
+ pre ||= ''
64
+ log = "[ShellTask]:#{pre}:#{shell.command}"
65
+ data(log)
66
+ end
67
+
68
+ def bool?(val)
69
+ val.is_a?(TrueClass) || val.is_a?(FalseClass)
70
+ end
71
+
72
+ def setup_shell(cmd)
73
+ shell = ::Mixlib::ShellOut.new cmd
74
+ shell.live_stream = @live_stream if @live_stream
75
+ shell
76
+ end
77
+
78
+ def invalid_cmd(type)
79
+ throw "#{type} must be string"
80
+ end
81
+
82
+ def evaluate_conditionals
83
+ @conditionals.all? do |cmd|
84
+ shell = setup_shell cmd
85
+ log_shell '[conditional]', shell
86
+ shell.run_command
87
+ !failed_status(shell.exitstatus)
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,6 @@
1
+ # encoding: utf-8
2
+
3
+ # version const for gem
4
+ module SingularityDsl
5
+ VERSION = '1.2.5'
6
+ end
@@ -0,0 +1,9 @@
1
+ # encoding: utf-8
2
+
3
+ # YEAH, THAT'S RIGHT
4
+ if RUBY_PLATFORM =~ /mswin|mingw32|windows/
5
+ throw 'Sorry, wont run on mswin|mingw32|windows'
6
+ end
7
+
8
+ require 'singularity_dsl/application'
9
+ require 'singularity_dsl/dsl/components'
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'singularity_dsl/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'singularity_dsl'
8
+ spec.version = SingularityDsl::VERSION
9
+ spec.authors = ['chr0n1x']
10
+ spec.email = ['heilong24@gmail.com']
11
+ spec.description = %q{DSL for your SingularityCI instance.}
12
+ spec.summary = %q{}
13
+ spec.homepage = ''
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_dependency 'mixlib-shellout', '~> 1.4'
22
+ spec.add_dependency 'rainbow', '~> 2.0.0'
23
+ spec.add_dependency 'rake', '~> 10.3'
24
+ spec.add_dependency 'rspec', '~> 2.6'
25
+ spec.add_dependency 'rubocop', '~> 0.23.0'
26
+ spec.add_dependency 'terminal-table', '~> 1.4'
27
+ spec.add_dependency 'thor', '~> 0.19'
28
+
29
+ spec.add_development_dependency 'bundler', '~> 1.3'
30
+ spec.add_development_dependency 'gem-release'
31
+ end
@@ -0,0 +1,92 @@
1
+ # encoding: utf-8
2
+
3
+ require 'singularity_dsl/application'
4
+
5
+ describe 'Application' do
6
+ let(:app) { SingularityDsl::Application.new }
7
+
8
+ context '#initialize' do
9
+ it 'creates base runner' do
10
+ expect(app.runner).to be_kind_of SingularityDsl::Dsl::Runner
11
+ end
12
+ end
13
+
14
+ context '#load_script' do
15
+ it 'just calls runner.load_ex_script' do
16
+ stub_res = 'spooooooky ghooooost'
17
+ expect(app.runner).to(receive(:load_ex_script).and_return stub_res)
18
+ expect(app.load_script 'dummy').to eql stub_res
19
+ end
20
+ end
21
+
22
+ context '#run' do
23
+ it 'logs resource failures' do
24
+ # don't want the entire thing to exit...
25
+ app.stub(:post_task_runner_actions)
26
+ app.runner.stub(:execute).and_raise(SingularityDsl::Errors::ResourceFail)
27
+ expect(app).to receive(:log_resource_fail)
28
+ app.run
29
+ end
30
+
31
+ it 'runner post-script actions are evaulated on failure' do
32
+ app.stub(:post_task_runner_actions)
33
+ app.stub(:log_resource_fail)
34
+ app.runner.stub(:execute).and_raise(SingularityDsl::Errors::ResourceFail)
35
+ expect(app).to receive(:post_task_runner_actions)
36
+ app.run
37
+ end
38
+
39
+ it 'logs resource errors' do
40
+ app.stub(:post_task_runner_actions)
41
+ app.runner.stub(:execute).and_raise(SingularityDsl::Errors::ResourceError)
42
+ expect(app).to receive(:log_resource_error)
43
+ app.run
44
+ end
45
+
46
+ it 'runner post-script actions are evaulated on error' do
47
+ app.stub(:post_task_runner_actions)
48
+ app.stub(:log_resource_error)
49
+ app.runner.stub(:execute).and_raise(SingularityDsl::Errors::ResourceError)
50
+ expect(app).to receive(:post_task_runner_actions)
51
+ app.run
52
+ end
53
+
54
+ it 'runner post-script actions are evaulated on error' do
55
+ app.stub(:post_task_runner_actions)
56
+ app.stub(:execute)
57
+ expect(app.runner.state).to receive :exit_code
58
+ app.run
59
+ end
60
+ end
61
+
62
+ context '#post_task_runner_actions' do
63
+ it 'outputs warning when script fails' do
64
+ app.stub :script_warn
65
+ app.runner.stub :post_actions
66
+ app.stub :exit_run
67
+ app.runner.state.stub(:failed).and_return true
68
+ expect(app).to receive(:script_warn)
69
+ app.post_task_runner_actions
70
+ end
71
+
72
+ it 'outputs warning when script errors' do
73
+ app.stub :script_error
74
+ app.runner.stub :post_actions
75
+ app.stub :exit_run
76
+ app.runner.state.stub(:error).and_return true
77
+ expect(app).to receive(:script_error)
78
+ app.post_task_runner_actions
79
+ end
80
+ end
81
+
82
+ context '#change_list' do
83
+ it 'returns a sorted list' do
84
+ expect(app.change_list(%w(b c e a d)))
85
+ .to eql %w(a b c d e)
86
+ end
87
+
88
+ it 'handles empty lists' do
89
+ expect(app.change_list([])).to eql []
90
+ end
91
+ end
92
+ end