psychic-runner 0.0.2
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.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/.rspec +2 -0
- data/.rubocop.yml +2 -0
- data/.rubocop_todo.yml +29 -0
- data/.travis.yml +30 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +70 -0
- data/Rakefile +12 -0
- data/bin/psychic +4 -0
- data/lib/psychic/cli.rb +37 -0
- data/lib/psychic/commands/exec.rb +19 -0
- data/lib/psychic/logger.rb +17 -0
- data/lib/psychic/runner/base_runner.rb +68 -0
- data/lib/psychic/runner/cold/shell_script_runner.rb +38 -0
- data/lib/psychic/runner/cold_runner_registry.rb +31 -0
- data/lib/psychic/runner/hot_runner.rb +25 -0
- data/lib/psychic/runner/sample_runner.rb +50 -0
- data/lib/psychic/runner/version.rb +5 -0
- data/lib/psychic/runner.rb +37 -0
- data/lib/psychic/shell/execution_result.rb +42 -0
- data/lib/psychic/shell/mixlib_shellout_executor.rb +62 -0
- data/lib/psychic/shell.rb +19 -0
- data/lib/psychic/tokens.rb +39 -0
- data/lib/psychic/util.rb +108 -0
- data/psychic-runner.gemspec +33 -0
- data/spec/psychic/runner/cold/shell_script_runner_spec.rb +80 -0
- data/spec/psychic/runner/hot_runner_spec.rb +63 -0
- data/spec/psychic/runner_spec.rb +45 -0
- data/spec/spec_helper.rb +47 -0
- metadata +239 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 67df9a60685ee7062b1b4a26ef84a74157338023
|
4
|
+
data.tar.gz: 5fd8577946b451d79ac3b7b928538089d66f512e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8ef2abd35ba79d48c86b27389731b6b6962dcd793a4810b144b439d1b846351f7052cc213a4d77463c468f7dc22ba825180e723244e45eab4001026c5b0f8a62
|
7
|
+
data.tar.gz: f254488c0fa32cd13e8f8c7477988d3d61b7a5b165e08d31626f07834d0341fd33097ac85eeb101ae73d3fbeb1e7b9d5a8aea864065b620eb8bc02ab6afd9a5e
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
data/.rubocop_todo.yml
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# This configuration was generated by `rubocop --auto-gen-config`
|
2
|
+
# on 2014-11-20 15:08:06 -0500 using RuboCop version 0.27.1.
|
3
|
+
# The point is for the user to remove these configuration records
|
4
|
+
# one by one as the offenses are removed from the code base.
|
5
|
+
# Note that changes in the inspected code, or installation of new
|
6
|
+
# versions of RuboCop, may require this file to be generated again.
|
7
|
+
|
8
|
+
# Offense count: 7
|
9
|
+
# Configuration parameters: AllowURI, URISchemes.
|
10
|
+
Metrics/LineLength:
|
11
|
+
Max: 104
|
12
|
+
|
13
|
+
# Offense count: 1
|
14
|
+
# Configuration parameters: CountComments.
|
15
|
+
Metrics/MethodLength:
|
16
|
+
Max: 11
|
17
|
+
|
18
|
+
# Offense count: 14
|
19
|
+
Style/Documentation:
|
20
|
+
Enabled: false
|
21
|
+
|
22
|
+
# Offense count: 1
|
23
|
+
# Configuration parameters: MinBodyLength.
|
24
|
+
Style/GuardClause:
|
25
|
+
Enabled: false
|
26
|
+
|
27
|
+
# Offense count: 2
|
28
|
+
Style/RegexpLiteral:
|
29
|
+
MaxSlashes: 0
|
data/.travis.yml
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
language: php
|
2
|
+
php:
|
3
|
+
- "5.6"
|
4
|
+
- "5.5"
|
5
|
+
- "5.4"
|
6
|
+
- "5.3"
|
7
|
+
- hhvm
|
8
|
+
|
9
|
+
matrix:
|
10
|
+
allow_failures:
|
11
|
+
- php: hhvm
|
12
|
+
|
13
|
+
branches:
|
14
|
+
only:
|
15
|
+
- master
|
16
|
+
- working
|
17
|
+
|
18
|
+
before_script:
|
19
|
+
- composer install --prefer-source
|
20
|
+
- vendor/bin/parallel-lint --exclude vendor .
|
21
|
+
- vendor/bin/php-cs-fixer fix --dry-run --level psr2 .
|
22
|
+
|
23
|
+
after_script:
|
24
|
+
- php vendor/bin/coveralls -v
|
25
|
+
|
26
|
+
notifications:
|
27
|
+
email:
|
28
|
+
- jamie.hannaford@rackspace.com
|
29
|
+
- glen.campbell@rackspace.com
|
30
|
+
- shaunak.kashyap@rackspace.com
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Max Lincoln
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
**php-opencloud**
|
2
|
+
=============
|
3
|
+
PHP SDK for OpenStack/Rackspace APIs
|
4
|
+
|
5
|
+
[](https://packagist.org/packages/rackspace/php-opencloud) [](https://travis-ci.org/rackspace/php-opencloud) [](https://packagist.org/packages/rackspace/php-opencloud)
|
6
|
+
|
7
|
+
For SDKs in different languages, see http://developer.rackspace.com.
|
8
|
+
|
9
|
+
The PHP SDK should work with most OpenStack-based cloud deployments,
|
10
|
+
though it specifically targets the Rackspace public cloud. In
|
11
|
+
general, whenever a Rackspace deployment is substantially different
|
12
|
+
than a pure OpenStack one, a separate Rackspace subclass is provided
|
13
|
+
so that you can still use the SDK with a pure OpenStack instance
|
14
|
+
(for example, see the `OpenStack` class (for OpenStack) and the
|
15
|
+
`Rackspace` subclass).
|
16
|
+
|
17
|
+
Requirements
|
18
|
+
------------
|
19
|
+
* PHP >=5.3.3
|
20
|
+
* cURL extension for PHP
|
21
|
+
|
22
|
+
Installation
|
23
|
+
------------
|
24
|
+
You must install this library through Composer:
|
25
|
+
|
26
|
+
```bash
|
27
|
+
# Install Composer
|
28
|
+
curl -sS https://getcomposer.org/installer | php
|
29
|
+
|
30
|
+
# Require php-opencloud as a dependency
|
31
|
+
php composer.phar require rackspace/php-opencloud
|
32
|
+
```
|
33
|
+
|
34
|
+
Once you have installed the library, you will need to load Composer's autoloader (which registers all the required
|
35
|
+
namespaces):
|
36
|
+
|
37
|
+
```php
|
38
|
+
require 'vendor/autoload.php';
|
39
|
+
```
|
40
|
+
|
41
|
+
And you're ready to go!
|
42
|
+
|
43
|
+
You can also check out the [Getting Started guide](docs/getting-started.md) for a quick tutorial.
|
44
|
+
|
45
|
+
- - -
|
46
|
+
|
47
|
+
Alternatively, if you would like to fork or clone the repository into a directory (to work and submit pull requests),
|
48
|
+
you will need to execute:
|
49
|
+
|
50
|
+
```bash
|
51
|
+
php composer.phar install
|
52
|
+
```
|
53
|
+
|
54
|
+
Instead of the `require` command. You can also specify the `--no-dev` option if you do not want to install phpDocumentor
|
55
|
+
(which has lots of vendor folders).
|
56
|
+
|
57
|
+
Support and Feedback
|
58
|
+
--------------------
|
59
|
+
Your feedback is appreciated! If you have specific problems or bugs with this SDK, please file an issue on Github. We
|
60
|
+
also have a [mailing list](https://groups.google.com/forum/#!forum/php-opencloud), so feel free to join to keep up to
|
61
|
+
date with all the latest changes and announcements to the library.
|
62
|
+
|
63
|
+
For general feedback and support requests, send an email to sdk-support@rackspace.com.
|
64
|
+
|
65
|
+
You can also find assistance via IRC on #rackspace at freenode.net.
|
66
|
+
|
67
|
+
Contributing
|
68
|
+
------------
|
69
|
+
If you'd like to contribute to the project, or require help running the unit/acceptance tests, please view the
|
70
|
+
[contributing guidelines](https://github.com/rackspace/php-opencloud/blob/master/CONTRIBUTING.md).
|
data/Rakefile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rubocop/rake_task'
|
3
|
+
require 'rake/notes/rake_task'
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
|
6
|
+
task default: [:spec, :rubocop, :notes]
|
7
|
+
|
8
|
+
RSpec::Core::RakeTask.new('spec')
|
9
|
+
RuboCop::RakeTask.new(:rubocop) do |task|
|
10
|
+
# abort rake on failure
|
11
|
+
task.fail_on_error = true
|
12
|
+
end
|
data/bin/psychic
ADDED
data/lib/psychic/cli.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'psychic/runner'
|
3
|
+
|
4
|
+
module Psychic
|
5
|
+
class CLI < Thor
|
6
|
+
desc 'run_task <name>', 'Executes a custom task by name'
|
7
|
+
def run_task(task_name, *args)
|
8
|
+
result = runner.execute_task(task_name, *args)
|
9
|
+
result.error!
|
10
|
+
say_status :success, task_name
|
11
|
+
rescue Psychic::Shell::ExecutionError => e
|
12
|
+
say_status :failed, task_name, :red
|
13
|
+
say e.execution_result if e.execution_result
|
14
|
+
end
|
15
|
+
|
16
|
+
desc 'run_sample <name>', 'Executes a code sample'
|
17
|
+
def run_sample(sample_name, *args)
|
18
|
+
result = runner.run_sample(sample_name, *args)
|
19
|
+
result.error!
|
20
|
+
say_status :success, sample_name
|
21
|
+
rescue Errno::ENOENT => e
|
22
|
+
say_status :failed, "No code sample found for #{sample_name}", :red
|
23
|
+
rescue Psychic::Shell::ExecutionError => e
|
24
|
+
say_status :failed, "Executing sample #{sample_name}", :red
|
25
|
+
say e.execution_result if e.execution_result
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def runner
|
31
|
+
# Psychic::Shell.shell = shell
|
32
|
+
@runner ||= Psychic::Runner.new
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# require 'psychic/commands/exec'
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Psychic
|
2
|
+
module Commands
|
3
|
+
class Exec < Thor
|
4
|
+
desc 'task <name>', 'Executes a custom task by name'
|
5
|
+
def task(task_name)
|
6
|
+
# Psychic::Shell.shell = shell
|
7
|
+
runner = Psychic::Runner.new
|
8
|
+
result = runner.public_send(task_name.to_sym)
|
9
|
+
result.error!
|
10
|
+
say_status :success, task_name
|
11
|
+
rescue Psychic::Shell::ExecutionError => e
|
12
|
+
say_status :failed, task_name, :red
|
13
|
+
say e.execution_result if e.execution_result
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
Psychic::CLI.register(Psychic::Commands::Exec, 'exec', 'exec <task>', 'Execute things via psychic')
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module Psychic
|
4
|
+
module Logger
|
5
|
+
def logger
|
6
|
+
@logger ||= new_logger
|
7
|
+
end
|
8
|
+
|
9
|
+
def new_logger(_io = $stdout, _level = :debug)
|
10
|
+
::Logger.new(STDOUT)
|
11
|
+
end
|
12
|
+
|
13
|
+
def log_level=(level)
|
14
|
+
logger.level = level
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Psychic
|
2
|
+
class Runner
|
3
|
+
module BaseRunner
|
4
|
+
include Psychic::Shell
|
5
|
+
include Psychic::Logger
|
6
|
+
|
7
|
+
attr_reader :known_tasks
|
8
|
+
attr_reader :cwd
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
attr_accessor :magic_file_pattern
|
12
|
+
|
13
|
+
def register_runner
|
14
|
+
Psychic::Runner::ColdRunnerRegistry.register(self)
|
15
|
+
end
|
16
|
+
|
17
|
+
def magic_file(pattern) # rubocop:disable Style/TrivialAccessors
|
18
|
+
@magic_file_pattern = pattern
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.included(base)
|
23
|
+
base.extend(ClassMethods)
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(opts = {})
|
27
|
+
opts[:cwd] ||= Dir.pwd
|
28
|
+
@logger = opts[:logger] || new_logger
|
29
|
+
@cwd = opts[:cwd]
|
30
|
+
@opts = opts
|
31
|
+
end
|
32
|
+
|
33
|
+
def respond_to_missing?(task, include_all = false)
|
34
|
+
return true if known_tasks.include?(task.to_s)
|
35
|
+
super
|
36
|
+
end
|
37
|
+
|
38
|
+
def method_missing(task, *args, &block)
|
39
|
+
execute_task(task, *args)
|
40
|
+
rescue Psychic::Runner::TaskNotImplementedError
|
41
|
+
super
|
42
|
+
end
|
43
|
+
|
44
|
+
# Reserved words
|
45
|
+
|
46
|
+
def execute(command, *args)
|
47
|
+
full_cmd = [command, *args].join(' ')
|
48
|
+
logger.info("Executing #{full_cmd}")
|
49
|
+
shell.execute(full_cmd, @opts)
|
50
|
+
end
|
51
|
+
|
52
|
+
def command_for_task(task, *_args)
|
53
|
+
task_name = task.to_s
|
54
|
+
self[task_name]
|
55
|
+
end
|
56
|
+
|
57
|
+
def execute_task(task, *args)
|
58
|
+
command = command_for_task(task, *args)
|
59
|
+
fail Psychic::Runner::TaskNotImplementedError if command.nil?
|
60
|
+
execute(command, *args)
|
61
|
+
end
|
62
|
+
|
63
|
+
def active?
|
64
|
+
self.class.magic_file_pattern ? false : Dir["#{@cwd}/#{self.class.magic_file_pattern}"]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Psychic
|
2
|
+
class Runner
|
3
|
+
module Cold
|
4
|
+
class ShellScriptRunner
|
5
|
+
include BaseRunner
|
6
|
+
EXTENSIONS = ['.sh', '']
|
7
|
+
magic_file 'scripts/*'
|
8
|
+
register_runner
|
9
|
+
|
10
|
+
def initialize(opts)
|
11
|
+
super
|
12
|
+
@known_tasks = Dir["#{@cwd}/scripts/*"].map do | script |
|
13
|
+
File.basename(script, File.extname(script)) if EXTENSIONS.include?(File.extname(script))
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def [](task_name)
|
18
|
+
task = task_name.to_s
|
19
|
+
script = Dir["#{@cwd}/scripts/#{task}{.sh,}"].first
|
20
|
+
if script
|
21
|
+
cmd = Psychic::Util.relativize(script, @cwd)
|
22
|
+
cmd = [cmd, args_for_task(task_name)].compact.join(' ')
|
23
|
+
"./#{cmd}" unless cmd.to_s.start_with? '/'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def args_for_task(task)
|
28
|
+
# HACK: Need a better way to deal with args
|
29
|
+
'{{sample_file}}' if task == 'run_sample'
|
30
|
+
end
|
31
|
+
|
32
|
+
def active?
|
33
|
+
true
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Psychic
|
2
|
+
class Runner
|
3
|
+
class ColdRunnerRegistry
|
4
|
+
include Psychic::Logger
|
5
|
+
|
6
|
+
BUILT_IN_DIR = File.expand_path('../cold', __FILE__)
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def autoload_runners!
|
10
|
+
# Load built-in runners
|
11
|
+
Dir["#{BUILT_IN_DIR}/*.rb"].each do |cold_runner_file|
|
12
|
+
require cold_runner_file
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def runner_classes
|
17
|
+
@runner_classes ||= Set.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def register(klass)
|
21
|
+
runner_classes.add klass
|
22
|
+
end
|
23
|
+
|
24
|
+
def active_runners(opts)
|
25
|
+
runners = runner_classes.map { |k| k.new(opts) }
|
26
|
+
runners.select(&:active?)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Psychic
|
2
|
+
class Runner
|
3
|
+
class HotRunner
|
4
|
+
include BaseRunner
|
5
|
+
def initialize(opts = {})
|
6
|
+
hints = opts.delete :hints
|
7
|
+
super
|
8
|
+
@hints = Psychic::Util.stringified_hash(hints || load_hints || {})
|
9
|
+
@tasks = @hints['tasks'] || {}
|
10
|
+
@known_tasks = @tasks.keys
|
11
|
+
end
|
12
|
+
|
13
|
+
def [](task_name)
|
14
|
+
@tasks[task_name]
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def load_hints
|
20
|
+
hints_file = Dir["#{@cwd}/psychic-hints.{yaml,yml}"].first
|
21
|
+
YAML.load(File.read(hints_file)) unless hints_file.nil?
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Psychic
|
2
|
+
class Runner
|
3
|
+
module SampleRunner
|
4
|
+
def run_sample(code_sample, *args)
|
5
|
+
sample_file = Psychic::Util.find_file_by_alias(code_sample, cwd)
|
6
|
+
process_template(sample_file) if templated?
|
7
|
+
command = command_for_task('run_sample')
|
8
|
+
if command
|
9
|
+
variables = { sample: code_sample, sample_file: sample_file }
|
10
|
+
command = Psychic::Util.replace_tokens(command, variables)
|
11
|
+
execute(command, *args)
|
12
|
+
else
|
13
|
+
run_sample_file(sample_file)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def run_sample_file(sample_file, *args)
|
18
|
+
execute("./#{sample_file}", *args) # Assuming Bash, but should detect Windows and use PowerShell
|
19
|
+
end
|
20
|
+
|
21
|
+
def process_template(sample_file)
|
22
|
+
absolute_sample_file = File.expand_path(sample_file, cwd)
|
23
|
+
template = File.read(absolute_sample_file)
|
24
|
+
# Default token pattern/replacement (used by php-opencloud) should be configurable
|
25
|
+
content = Psychic::Util.replace_tokens(template, variables, /'\{(\w+)\}'/, "'\\1'")
|
26
|
+
|
27
|
+
# Backup and overwrite
|
28
|
+
backup_file = "#{absolute_sample_file}.bak"
|
29
|
+
fail 'Please clear out old backups before rerunning' if File.exist? backup_file
|
30
|
+
FileUtils.cp(absolute_sample_file, backup_file)
|
31
|
+
File.write(absolute_sample_file, content)
|
32
|
+
end
|
33
|
+
|
34
|
+
def templated?
|
35
|
+
# Probably not the best way to turn this on/off
|
36
|
+
true unless variables.nil?
|
37
|
+
end
|
38
|
+
|
39
|
+
def variables
|
40
|
+
# ... or
|
41
|
+
variables_file = Dir["#{cwd}/psychic-variables.{yaml,yml}"].first
|
42
|
+
return nil unless variables_file
|
43
|
+
environment_variables = ENV.to_hash
|
44
|
+
environment_variables.merge!(@opts[:env]) if @opts[:env]
|
45
|
+
variables = Psychic::Util.replace_tokens(File.read(variables_file), environment_variables)
|
46
|
+
YAML.load(variables)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'psychic/runner/version'
|
2
|
+
|
3
|
+
autoload :YAML, 'yaml'
|
4
|
+
|
5
|
+
module Psychic
|
6
|
+
autoload :Util, 'psychic/util'
|
7
|
+
autoload :Logger, 'psychic/logger'
|
8
|
+
autoload :Shell, 'psychic/shell'
|
9
|
+
class Runner
|
10
|
+
autoload :BaseRunner, 'psychic/runner/base_runner'
|
11
|
+
autoload :SampleRunner, 'psychic/runner/sample_runner'
|
12
|
+
autoload :HotRunner, 'psychic/runner/hot_runner'
|
13
|
+
autoload :CompoundRunner, 'psychic/runner/compound_runner'
|
14
|
+
autoload :ColdRunnerRegistry, 'psychic/runner/cold_runner_registry'
|
15
|
+
class TaskNotImplementedError < NotImplementedError; end
|
16
|
+
ColdRunnerRegistry.autoload_runners!
|
17
|
+
|
18
|
+
include BaseRunner
|
19
|
+
include SampleRunner
|
20
|
+
attr_reader :runners, :hot_runner, :cold_runners
|
21
|
+
|
22
|
+
def initialize(opts = { cwd: Dir.pwd })
|
23
|
+
fail 'cwd is required' unless opts[:cwd]
|
24
|
+
super
|
25
|
+
@hot_runner = HotRunner.new(opts)
|
26
|
+
@cold_runners = ColdRunnerRegistry.active_runners(opts)
|
27
|
+
@runners = [@hot_runner, @cold_runners].flatten
|
28
|
+
@known_tasks = @runners.map(&:known_tasks).uniq
|
29
|
+
end
|
30
|
+
|
31
|
+
def [](task_name)
|
32
|
+
runner = runners.find { |r| r.command_for_task(task_name) }
|
33
|
+
return nil unless runner
|
34
|
+
runner[task_name]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'English'
|
2
|
+
|
3
|
+
module Psychic
|
4
|
+
module Shell
|
5
|
+
class ExecutionError < StandardError
|
6
|
+
attr_accessor :execution_result
|
7
|
+
end
|
8
|
+
|
9
|
+
class ExecutionResult
|
10
|
+
attr_reader :exitstatus
|
11
|
+
attr_reader :stdout
|
12
|
+
attr_reader :stderr
|
13
|
+
# coerce_value String, ->(v) { v.force_encoding('utf-8') }
|
14
|
+
|
15
|
+
def initialize(results)
|
16
|
+
@exitstatus = results.fetch(:exitstatus)
|
17
|
+
# Needs to be UTF-8 to serialize as YAML
|
18
|
+
@stdout = results.fetch(:stdout).force_encoding('utf-8')
|
19
|
+
@stderr = results.fetch(:stderr).force_encoding('utf-8')
|
20
|
+
end
|
21
|
+
|
22
|
+
def error!
|
23
|
+
if @exitstatus != 0
|
24
|
+
error = ExecutionError.new
|
25
|
+
error.execution_result = self
|
26
|
+
fail error
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_s
|
31
|
+
''"
|
32
|
+
Execution Result:
|
33
|
+
exitstatus: #{exitstatus}
|
34
|
+
stdout:
|
35
|
+
#{stdout}
|
36
|
+
stderr:
|
37
|
+
#{stderr}
|
38
|
+
"''
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'mixlib/shellout'
|
2
|
+
|
3
|
+
module Psychic
|
4
|
+
module Shell
|
5
|
+
class IOToLog < IO
|
6
|
+
def initialize(logger)
|
7
|
+
@logger = logger
|
8
|
+
@buffer = ''
|
9
|
+
end
|
10
|
+
|
11
|
+
def write(string)
|
12
|
+
(@buffer + string).lines.each do |line|
|
13
|
+
if line.end_with? "\n"
|
14
|
+
@buffer = ''
|
15
|
+
@logger.info(line.rstrip)
|
16
|
+
else
|
17
|
+
@buffer = line
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class MixlibShellOutExecutor
|
24
|
+
include Psychic::Logger
|
25
|
+
attr_reader :shell
|
26
|
+
|
27
|
+
MIXLIB_SHELLOUT_EXCEPTION_CLASSES = Mixlib::ShellOut.constants.map do|name|
|
28
|
+
klass = Mixlib::ShellOut.const_get(name)
|
29
|
+
if klass.is_a?(Class) && klass <= RuntimeError
|
30
|
+
klass
|
31
|
+
else
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
end.compact
|
35
|
+
|
36
|
+
def execute(command, opts)
|
37
|
+
@logger = opts.delete(:logger) || logger
|
38
|
+
@shell = Mixlib::ShellOut.new(command, opts)
|
39
|
+
@shell.live_stream = IOToLog.new(@logger)
|
40
|
+
@shell.run_command
|
41
|
+
execution_result
|
42
|
+
rescue SystemCallError, *MIXLIB_SHELLOUT_EXCEPTION_CLASSES, TypeError => e
|
43
|
+
# See https://github.com/opscode/mixlib-shellout/issues/62
|
44
|
+
execution_error = ExecutionError.new(e)
|
45
|
+
execution_error.execution_result = execution_result
|
46
|
+
raise execution_error
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def execution_result
|
52
|
+
return nil if shell.nil?
|
53
|
+
|
54
|
+
ExecutionResult.new(
|
55
|
+
exitstatus: shell.exitstatus,
|
56
|
+
stdout: shell.stdout,
|
57
|
+
stderr: shell.stderr
|
58
|
+
)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Psychic
|
2
|
+
module Shell
|
3
|
+
autoload :ExecutionResult, 'psychic/shell/execution_result'
|
4
|
+
autoload :ExecutionError, 'psychic/shell/execution_result'
|
5
|
+
autoload :MixlibShellOutExecutor, 'psychic/shell/mixlib_shellout_executor'
|
6
|
+
|
7
|
+
class << self
|
8
|
+
attr_writer :shell
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.shell
|
12
|
+
@shell ||= MixlibShellOutExecutor.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def shell
|
16
|
+
Psychic::Shell.shell
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
autoload :Mustache, 'mustache'
|
2
|
+
|
3
|
+
module Psychic
|
4
|
+
class RegexpTokenHandler
|
5
|
+
def initialize(template, token_pattern, token_replacement)
|
6
|
+
@template = template
|
7
|
+
@token_pattern = token_pattern
|
8
|
+
@token_replacement = token_replacement
|
9
|
+
end
|
10
|
+
|
11
|
+
def tokens
|
12
|
+
@template.scan(@token_pattern).flatten.uniq
|
13
|
+
end
|
14
|
+
|
15
|
+
def replace(variables = {})
|
16
|
+
@template.gsub(@token_pattern) do
|
17
|
+
full_match = Regexp.last_match[0]
|
18
|
+
key = Regexp.last_match[1]
|
19
|
+
value = variables[key]
|
20
|
+
value = @token_replacement.gsub('\\1', value.to_s) unless @token_replacement.nil?
|
21
|
+
full_match.gsub(@token_pattern, value)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class MustacheTokenHandler
|
27
|
+
def initialize(template)
|
28
|
+
@template = Mustache::Template.new(template)
|
29
|
+
end
|
30
|
+
|
31
|
+
def tokens
|
32
|
+
@template.tags
|
33
|
+
end
|
34
|
+
|
35
|
+
def replace(variables = {})
|
36
|
+
Mustache.render(@template, variables)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/psychic/util.rb
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
module Psychic
|
2
|
+
autoload :RegexpTokenHandler, 'psychic/tokens'
|
3
|
+
autoload :MustacheTokenHandler, 'psychic/tokens'
|
4
|
+
class Util
|
5
|
+
# Returns a new Hash with all key values coerced to strings. All keys
|
6
|
+
# within a Hash are coerced by calling #to_s and hashes with arrays
|
7
|
+
# and other hashes are traversed.
|
8
|
+
#
|
9
|
+
# @param obj [Object] the hash to be processed. While intended for
|
10
|
+
# hashes, this method safely processes arbitrary objects
|
11
|
+
# @return [Object] a converted hash with all keys as strings
|
12
|
+
def self.stringified_hash(obj)
|
13
|
+
if obj.is_a?(Hash)
|
14
|
+
obj.each_with_object({}) do |(k, v), h|
|
15
|
+
h[k.to_s] = stringified_hash(v)
|
16
|
+
end
|
17
|
+
elsif obj.is_a?(Array)
|
18
|
+
obj.each_with_object([]) do |e, a|
|
19
|
+
a << stringified_hash(e)
|
20
|
+
end
|
21
|
+
else
|
22
|
+
obj
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.relativize(file, base_path)
|
27
|
+
absolute_file = File.absolute_path(file)
|
28
|
+
absolute_base_path = File.absolute_path(base_path)
|
29
|
+
Pathname.new(absolute_file).relative_path_from Pathname.new(absolute_base_path)
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.slugify(*labels)
|
33
|
+
labels.map do |label|
|
34
|
+
label.downcase.gsub(/[\.\s-]/, '_')
|
35
|
+
end.join('-')
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.find_file_by_alias(file_alias, search_path, ignored_patterns = nil)
|
39
|
+
FileFinder.new(search_path, ignored_patterns).find_file(file_alias)
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.replace_tokens(template, variables, token_regexp = nil, token_replacement = nil)
|
43
|
+
if token_regexp.nil?
|
44
|
+
MustacheTokenHandler.new(template).replace(variables)
|
45
|
+
else
|
46
|
+
RegexpTokenHandler.new(template, token_regexp, token_replacement).replace(variables)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class FileFinder
|
52
|
+
attr_reader :search_path, :ignored_patterns
|
53
|
+
|
54
|
+
def initialize(search_path, ignored_patterns)
|
55
|
+
@search_path = search_path
|
56
|
+
@ignored_patterns = ignored_patterns || read_gitignore(search_path)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Finds a file by loosely matching the file name to a scenario name
|
60
|
+
def find_file(name)
|
61
|
+
return name if File.exist? File.expand_path(name, search_path)
|
62
|
+
|
63
|
+
# Filter out ignored filesFind the first file, not including generated files
|
64
|
+
files = potential_files(name).select do |f|
|
65
|
+
!ignored? f
|
66
|
+
end
|
67
|
+
|
68
|
+
# Select the shortest path, likely the best match
|
69
|
+
file = files.min_by(&:length)
|
70
|
+
|
71
|
+
fail Errno::ENOENT, "No file was found for #{name} within #{search_path}" if file.nil?
|
72
|
+
Psychic::Util.relativize(file, search_path)
|
73
|
+
end
|
74
|
+
|
75
|
+
def potential_files(name)
|
76
|
+
slugified_name = Psychic::Util.slugify(name)
|
77
|
+
glob_string = "#{search_path}/**/*#{slugified_name}*.*"
|
78
|
+
potential_files = Dir.glob(glob_string, File::FNM_CASEFOLD)
|
79
|
+
potential_files.concat Dir.glob(glob_string.gsub('_', '-'), File::FNM_CASEFOLD)
|
80
|
+
potential_files.concat Dir.glob(glob_string.gsub('_', ''), File::FNM_CASEFOLD)
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
# @api private
|
86
|
+
def read_gitignore(dir)
|
87
|
+
gitignore_file = "#{dir}/.gitignore"
|
88
|
+
File.read(gitignore_file)
|
89
|
+
rescue
|
90
|
+
''
|
91
|
+
end
|
92
|
+
|
93
|
+
# @api private
|
94
|
+
def ignored?(target_file)
|
95
|
+
# Trying to match the git ignore rules but there's some discrepencies.
|
96
|
+
ignored_patterns.split.find do |pattern|
|
97
|
+
# if git ignores a folder, we should ignore all files it contains
|
98
|
+
pattern = "#{pattern}**" if pattern[-1] == '/'
|
99
|
+
started_with_slash = pattern.start_with? '/'
|
100
|
+
|
101
|
+
pattern.gsub!(%r{\A/}, '') # remove leading slashes since we're searching from root
|
102
|
+
file = Psychic::Util.relativize(target_file, search_path)
|
103
|
+
ignored = file.fnmatch? pattern
|
104
|
+
ignored || (file.fnmatch? "**/#{pattern}" unless started_with_slash)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'psychic/runner/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'psychic-runner'
|
8
|
+
spec.version = Psychic::Runner::VERSION
|
9
|
+
spec.authors = ['Max Lincoln']
|
10
|
+
spec.email = ['max@devopsy.com']
|
11
|
+
spec.summary = 'Psychic runs anything.'
|
12
|
+
spec.description = 'Provides cross-project aliases for running tasks or similar code samples.'
|
13
|
+
# spec.homepage = ''
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
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 'thor', '~> 0.19'
|
22
|
+
spec.add_dependency 'mixlib-shellout', '~> 1.3' # Used for MRI
|
23
|
+
# spec.add_dependency "buff-shell_out", "~> 0.1" # Used for JRuby
|
24
|
+
spec.add_dependency 'mustache', '~> 0.99'
|
25
|
+
spec.add_development_dependency 'bundler', '~> 1.7'
|
26
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
27
|
+
spec.add_development_dependency 'rake-notes'
|
28
|
+
spec.add_development_dependency 'simplecov'
|
29
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
30
|
+
spec.add_development_dependency 'rubocop', '~> 0.18', '<= 0.27'
|
31
|
+
spec.add_development_dependency 'rubocop-rspec', '~> 1.2'
|
32
|
+
spec.add_development_dependency 'aruba'
|
33
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Psychic
|
2
|
+
class Runner
|
3
|
+
module Cold
|
4
|
+
RSpec.describe ShellScriptRunner do
|
5
|
+
let(:shell) { Psychic::Shell.shell = double('shell') }
|
6
|
+
subject { described_class.new(cwd: current_dir) }
|
7
|
+
|
8
|
+
shared_context 'with scripts/*.sh files' do
|
9
|
+
before(:each) do
|
10
|
+
write_file 'scripts/bootstrap.sh', ''
|
11
|
+
write_file 'scripts/compile.sh', ''
|
12
|
+
write_file 'scripts/foo.ps1', ''
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
shared_context 'with scripts/* (no extension) files' do
|
17
|
+
before(:each) do
|
18
|
+
write_file 'scripts/bootstrap', ''
|
19
|
+
write_file 'scripts/compile', ''
|
20
|
+
write_file 'scripts/.foo', ''
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe 'respond_to?' do
|
25
|
+
shared_examples 'detects matching scripts' do
|
26
|
+
it 'returns true if a matching script exists' do
|
27
|
+
expect(subject.respond_to? :bootstrap).to be true
|
28
|
+
expect(subject.respond_to? :compile).to be true
|
29
|
+
end
|
30
|
+
it 'returns false if a matching script does not exists' do
|
31
|
+
expect(subject.respond_to? :foo).to be false
|
32
|
+
expect(subject.respond_to? :bar).to be false
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'with scripts/*.sh files' do
|
37
|
+
include_context 'with scripts/*.sh files' do
|
38
|
+
include_examples 'detects matching scripts'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'with scripts/* (no extension) files' do
|
43
|
+
include_context 'with scripts/* (no extension) files' do
|
44
|
+
include_examples 'detects matching scripts'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe '#method_missing' do
|
50
|
+
context 'matching a task' do
|
51
|
+
context 'with scripts/*.sh files' do
|
52
|
+
include_context 'with scripts/*.sh files' do
|
53
|
+
it 'executes the script command' do
|
54
|
+
expect(shell).to receive(:execute).with('./scripts/bootstrap.sh', cwd: current_dir)
|
55
|
+
subject.bootstrap
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context 'with scripts/* (no extension) files' do
|
61
|
+
include_context 'with scripts/* (no extension) files' do
|
62
|
+
it 'executes the script command' do
|
63
|
+
expect(shell).to receive(:execute).with('./scripts/bootstrap', cwd: current_dir)
|
64
|
+
subject.bootstrap
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context 'not matching a task' do
|
71
|
+
it 'raises an error' do
|
72
|
+
# Use foo to ensure it doesn't match ps1 or hidden (. prefixed) files
|
73
|
+
expect { subject.foo }.to raise_error(NoMethodError)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Psychic
|
2
|
+
class Runner
|
3
|
+
RSpec.describe HotRunner do
|
4
|
+
let(:task_map) do
|
5
|
+
{
|
6
|
+
'bootstrap' => 'foo',
|
7
|
+
'compile' => 'bar',
|
8
|
+
'execute' => 'baz'
|
9
|
+
}
|
10
|
+
end
|
11
|
+
let(:shell) { Psychic::Shell.shell = double('shell') }
|
12
|
+
subject { described_class.new(cwd: current_dir, hints: task_map) }
|
13
|
+
|
14
|
+
shared_examples 'runs tasks' do
|
15
|
+
describe 'respond_to?' do
|
16
|
+
it 'returns true for task ids' do
|
17
|
+
task_map.each_key do |key|
|
18
|
+
expect(subject.respond_to? key).to be true
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'returns false for anything else' do
|
23
|
+
expect(subject.respond_to? 'max').to be false
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe '#method_missing' do
|
28
|
+
context 'matching a task' do
|
29
|
+
it 'executes the task command' do
|
30
|
+
expect(shell).to receive(:execute).with('foo', cwd: current_dir)
|
31
|
+
subject.bootstrap
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'not matching a task' do
|
36
|
+
it 'raises an error' do
|
37
|
+
expect { subject.spin_around }.to raise_error(NoMethodError)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'task map stored in psychic-hints.yml' do
|
44
|
+
let(:hints) do
|
45
|
+
{ 'tasks' => task_map }
|
46
|
+
end
|
47
|
+
before(:each) do
|
48
|
+
write_file 'psychic-hints.yml', YAML.dump(hints)
|
49
|
+
end
|
50
|
+
subject { described_class.new(cwd: current_dir) }
|
51
|
+
include_examples 'runs tasks'
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'hints passed as a parameter' do
|
55
|
+
let(:hints) do
|
56
|
+
{ 'tasks' => task_map }
|
57
|
+
end
|
58
|
+
subject { described_class.new(cwd: current_dir, hints: hints) }
|
59
|
+
include_examples 'runs tasks'
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Psychic
|
4
|
+
RSpec.describe Runner do
|
5
|
+
subject { described_class.new(cwd: current_dir) }
|
6
|
+
context 'when psychic-hints.yml exists' do
|
7
|
+
let(:hints) do
|
8
|
+
{
|
9
|
+
'tasks' =>
|
10
|
+
{
|
11
|
+
'bootstrap' => 'foo',
|
12
|
+
'compile' => 'bar',
|
13
|
+
'execute' => 'baz'
|
14
|
+
}
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
before(:each) do
|
19
|
+
write_file 'psychic-hints.yml', YAML.dump(hints)
|
20
|
+
end
|
21
|
+
|
22
|
+
describe 'initialize' do
|
23
|
+
it 'should create a HotRunner for the specified directory' do
|
24
|
+
expect(subject.hot_runner).to be_an_instance_of(Psychic::Runner::HotRunner)
|
25
|
+
expect(subject.cwd).to eq(current_dir)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'when scripts/* exist' do
|
31
|
+
before(:each) do
|
32
|
+
write_file 'scripts/bootstrap.sh', ''
|
33
|
+
write_file 'scripts/foo.sh', ''
|
34
|
+
end
|
35
|
+
|
36
|
+
describe 'initialize' do
|
37
|
+
it 'should create a cold runner for ShellScriptRunner' do
|
38
|
+
expect(subject.cold_runners).to include(
|
39
|
+
an_instance_of(Psychic::Runner::Cold::ShellScriptRunner)
|
40
|
+
)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
SimpleCov.start
|
3
|
+
|
4
|
+
require 'rspec'
|
5
|
+
require 'psychic/runner'
|
6
|
+
require 'aruba'
|
7
|
+
require 'aruba/api'
|
8
|
+
|
9
|
+
# Config required for project
|
10
|
+
RSpec.configure do | config |
|
11
|
+
config.include Aruba::Api
|
12
|
+
config.before(:example) do
|
13
|
+
@aruba_timeout_seconds = 30
|
14
|
+
clean_current_dir
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Configs recommended by RSpec
|
19
|
+
RSpec.configure do |config|
|
20
|
+
config.warnings = true
|
21
|
+
config.disable_monkey_patching!
|
22
|
+
config.filter_run :focus
|
23
|
+
config.run_all_when_everything_filtered = true
|
24
|
+
|
25
|
+
config.expect_with :rspec do |expectations|
|
26
|
+
# This option will default to `true` in RSpec 4.
|
27
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
28
|
+
end
|
29
|
+
|
30
|
+
config.mock_with :rspec do |mocks|
|
31
|
+
# Prevents you from mocking or stubbing a method that does not exist on
|
32
|
+
# a real object. This is generally recommended, and will default to
|
33
|
+
# `true` in RSpec 4.
|
34
|
+
mocks.verify_partial_doubles = true
|
35
|
+
end
|
36
|
+
|
37
|
+
if config.files_to_run.one?
|
38
|
+
# Use the documentation formatter for detailed output,
|
39
|
+
# unless a formatter has already been configured
|
40
|
+
# (e.g. via a command-line flag).
|
41
|
+
config.default_formatter = 'doc'
|
42
|
+
end
|
43
|
+
|
44
|
+
config.profile_examples = 10
|
45
|
+
config.order = :random
|
46
|
+
Kernel.srand config.seed
|
47
|
+
end
|
metadata
ADDED
@@ -0,0 +1,239 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: psychic-runner
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Max Lincoln
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-12-03 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: thor
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.19'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.19'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: mixlib-shellout
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.3'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: mustache
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.99'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.99'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.7'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.7'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '10.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '10.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rake-notes
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: simplecov
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rspec
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '3.0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '3.0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: rubocop
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0.18'
|
132
|
+
- - "<="
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: '0.27'
|
135
|
+
type: :development
|
136
|
+
prerelease: false
|
137
|
+
version_requirements: !ruby/object:Gem::Requirement
|
138
|
+
requirements:
|
139
|
+
- - "~>"
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0.18'
|
142
|
+
- - "<="
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '0.27'
|
145
|
+
- !ruby/object:Gem::Dependency
|
146
|
+
name: rubocop-rspec
|
147
|
+
requirement: !ruby/object:Gem::Requirement
|
148
|
+
requirements:
|
149
|
+
- - "~>"
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: '1.2'
|
152
|
+
type: :development
|
153
|
+
prerelease: false
|
154
|
+
version_requirements: !ruby/object:Gem::Requirement
|
155
|
+
requirements:
|
156
|
+
- - "~>"
|
157
|
+
- !ruby/object:Gem::Version
|
158
|
+
version: '1.2'
|
159
|
+
- !ruby/object:Gem::Dependency
|
160
|
+
name: aruba
|
161
|
+
requirement: !ruby/object:Gem::Requirement
|
162
|
+
requirements:
|
163
|
+
- - ">="
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '0'
|
166
|
+
type: :development
|
167
|
+
prerelease: false
|
168
|
+
version_requirements: !ruby/object:Gem::Requirement
|
169
|
+
requirements:
|
170
|
+
- - ">="
|
171
|
+
- !ruby/object:Gem::Version
|
172
|
+
version: '0'
|
173
|
+
description: Provides cross-project aliases for running tasks or similar code samples.
|
174
|
+
email:
|
175
|
+
- max@devopsy.com
|
176
|
+
executables:
|
177
|
+
- psychic
|
178
|
+
extensions: []
|
179
|
+
extra_rdoc_files: []
|
180
|
+
files:
|
181
|
+
- ".gitignore"
|
182
|
+
- ".rspec"
|
183
|
+
- ".rubocop.yml"
|
184
|
+
- ".rubocop_todo.yml"
|
185
|
+
- ".travis.yml"
|
186
|
+
- Gemfile
|
187
|
+
- LICENSE.txt
|
188
|
+
- README.md
|
189
|
+
- Rakefile
|
190
|
+
- bin/psychic
|
191
|
+
- lib/psychic/cli.rb
|
192
|
+
- lib/psychic/commands/exec.rb
|
193
|
+
- lib/psychic/logger.rb
|
194
|
+
- lib/psychic/runner.rb
|
195
|
+
- lib/psychic/runner/base_runner.rb
|
196
|
+
- lib/psychic/runner/cold/shell_script_runner.rb
|
197
|
+
- lib/psychic/runner/cold_runner_registry.rb
|
198
|
+
- lib/psychic/runner/hot_runner.rb
|
199
|
+
- lib/psychic/runner/sample_runner.rb
|
200
|
+
- lib/psychic/runner/version.rb
|
201
|
+
- lib/psychic/shell.rb
|
202
|
+
- lib/psychic/shell/execution_result.rb
|
203
|
+
- lib/psychic/shell/mixlib_shellout_executor.rb
|
204
|
+
- lib/psychic/tokens.rb
|
205
|
+
- lib/psychic/util.rb
|
206
|
+
- psychic-runner.gemspec
|
207
|
+
- spec/psychic/runner/cold/shell_script_runner_spec.rb
|
208
|
+
- spec/psychic/runner/hot_runner_spec.rb
|
209
|
+
- spec/psychic/runner_spec.rb
|
210
|
+
- spec/spec_helper.rb
|
211
|
+
homepage:
|
212
|
+
licenses:
|
213
|
+
- MIT
|
214
|
+
metadata: {}
|
215
|
+
post_install_message:
|
216
|
+
rdoc_options: []
|
217
|
+
require_paths:
|
218
|
+
- lib
|
219
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
220
|
+
requirements:
|
221
|
+
- - ">="
|
222
|
+
- !ruby/object:Gem::Version
|
223
|
+
version: '0'
|
224
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
225
|
+
requirements:
|
226
|
+
- - ">="
|
227
|
+
- !ruby/object:Gem::Version
|
228
|
+
version: '0'
|
229
|
+
requirements: []
|
230
|
+
rubyforge_project:
|
231
|
+
rubygems_version: 2.4.2
|
232
|
+
signing_key:
|
233
|
+
specification_version: 4
|
234
|
+
summary: Psychic runs anything.
|
235
|
+
test_files:
|
236
|
+
- spec/psychic/runner/cold/shell_script_runner_spec.rb
|
237
|
+
- spec/psychic/runner/hot_runner_spec.rb
|
238
|
+
- spec/psychic/runner_spec.rb
|
239
|
+
- spec/spec_helper.rb
|