psychic-runner 0.0.3 → 0.0.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 69224ee73f0d75190d851b16319792a4dc766f34
4
- data.tar.gz: 1cddbfe6844bf53e58a6b15113b57c18b823a9f6
3
+ metadata.gz: 0c7368904ef64ffdf44ad5037d40e0a94b91148f
4
+ data.tar.gz: f6d93730030b6d77ac4b702fa10432e0dba6893c
5
5
  SHA512:
6
- metadata.gz: 7784c2bf1669787a6dc29eb096dd21e6afec25204a5d44e43c6f92bd342490065d3fbb4f5acad414865e8637023812f6b1a284a5fce5309251e1ee6f61b946e7
7
- data.tar.gz: e42b2f1153877d7d04fb91a0c7cd19438e35f30b5f82669cb776b27fe5768ab17a93c15404a947fcbd44258d843d02510eafcca1825d14066b8bcfdb834a2d43
6
+ metadata.gz: b318bf6e0204400ff58acc0683316a9dfd64264214bf7a11143224c905e3870b2d8b2d5bb8886efedab8027314d98b88b0e88726695d988d34062d199e215dfb
7
+ data.tar.gz: e8e491d518e1b2fdec5b5dd66fc9384cd8592af0d3a2fc48185fc5cec051540f79cf3478930a3902219d6bfc9f7c3c4cfec108d0837d3c14ee4a9de6e1181137
data/.travis.yml CHANGED
@@ -1,30 +1,10 @@
1
- language: php
2
- php:
3
- - "5.6"
4
- - "5.5"
5
- - "5.4"
6
- - "5.3"
7
- - hhvm
8
-
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.0
4
+ - 2.0.0
5
+ - 1.9.3
6
+ - jruby
9
7
  matrix:
10
8
  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
9
+ - rvm: jruby
25
10
 
26
- notifications:
27
- email:
28
- - jamie.hannaford@rackspace.com
29
- - glen.campbell@rackspace.com
30
- - shaunak.kashyap@rackspace.com
data/lib/psychic/cli.rb CHANGED
@@ -1,11 +1,33 @@
1
+ require 'English'
1
2
  require 'thor'
2
3
  require 'psychic/runner'
3
4
 
4
5
  module Psychic
5
6
  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)
7
+ BUILT_IN_TASKS = %w(bootstrap)
8
+
9
+ class << self
10
+ # Override Thor's start to strip extra_args from ARGV before it's processed
11
+ attr_accessor :extra_args
12
+
13
+ def start(given_args = ARGV, config = {})
14
+ if given_args && (split_pos = given_args.index('--'))
15
+ @extra_args = given_args.slice(split_pos + 1, given_args.length)
16
+ given_args = given_args.slice(0, split_pos)
17
+ end
18
+ super given_args, config
19
+ end
20
+ end
21
+
22
+ no_commands do
23
+ def extra_args
24
+ self.class.extra_args
25
+ end
26
+ end
27
+
28
+ desc 'task <name>', 'Executes any task by name'
29
+ def task(task_name)
30
+ result = runner.execute_task(task_name, *extra_args)
9
31
  result.error!
10
32
  say_status :success, task_name
11
33
  rescue Psychic::Shell::ExecutionError => e
@@ -13,25 +35,49 @@ module Psychic
13
35
  say e.execution_result if e.execution_result
14
36
  end
15
37
 
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
38
+ BUILT_IN_TASKS.each do |task_name|
39
+ desc task_name, "Executes the #{task_name} task"
40
+ define_method(task_name) do
41
+ task(task_name)
42
+ end
43
+ end
44
+
45
+ desc 'sample <name>', 'Executes a code sample'
46
+ # rubocop:disable Metrics/LineLength
47
+ method_option :interactive, desc: 'Prompt for parameters?', enum: %w(always missing), lazy_default: 'missing'
48
+ method_option :parameters, desc: 'YAML file containing key/value parameters. Default: psychic-parameters.yaml'
49
+ method_option :parameter_mode, desc: 'How should the parameters be passed?', enum: %w(tokens arguments env)
50
+ method_option :dry_run, desc: 'Do not execute - just show what command would be run', lazy_default: true
51
+ # rubocop:enable Metrics/LineLength
52
+ def sample(*sample_names)
53
+ sample_names.each do | sample_name |
54
+ say_status :executing, sample_name
55
+ begin
56
+ run_sample sample_name
57
+ rescue Errno::ENOENT
58
+ say_status :failed, "No code sample found for #{sample_name}", :red
59
+ # TODO: Fail on missing? Fail fast?
60
+ end
61
+ end
26
62
  end
27
63
 
28
64
  private
29
65
 
66
+ def run_sample(sample_name)
67
+ result = runner.run_sample(sample_name, *extra_args)
68
+ if options.dry_run
69
+ say_status :dry_run, sample_name
70
+ else
71
+ result.error!
72
+ say_status :success, sample_name
73
+ end
74
+ end
75
+
30
76
  def runner
31
- # Psychic::Shell.shell = shell
32
- @runner ||= Psychic::Runner.new
77
+ runner_opts = Util.symbolized_hash(options).merge(
78
+ cwd: Dir.pwd, cli: shell, parameters: options.parameters
79
+ )
80
+ @runner ||= Psychic::Runner.new(runner_opts)
33
81
  end
34
82
  end
35
83
  end
36
-
37
- # require 'psychic/commands/exec'
@@ -1,11 +1,15 @@
1
1
  module Psychic
2
2
  class Runner
3
3
  module BaseRunner
4
+ DEFAULT_PARAMS_FILE = 'psychic-parameters.yaml'
5
+
4
6
  include Psychic::Shell
5
7
  include Psychic::Logger
6
8
 
7
9
  attr_reader :known_tasks
8
10
  attr_reader :cwd
11
+ attr_reader :env
12
+ attr_reader :hints
9
13
 
10
14
  module ClassMethods
11
15
  attr_accessor :magic_file_pattern
@@ -23,11 +27,19 @@ module Psychic
23
27
  base.extend(ClassMethods)
24
28
  end
25
29
 
26
- def initialize(opts = {})
27
- opts[:cwd] ||= Dir.pwd
30
+ def initialize(opts = {}, _hints = {})
31
+ @cwd = opts[:cwd] ||= Dir.pwd
32
+ @hints = Psychic::Util.stringified_hash(opts[:hints] || load_hints || {})
33
+ if @hints['options']
34
+ opts.merge! Psychic::Util.symbolized_hash(@hints['options'])
35
+ end
28
36
  @logger = opts[:logger] || new_logger
29
- @cwd = opts[:cwd]
30
- @opts = opts
37
+ @env = opts[:env] || ENV.to_hash
38
+ @parameters = load_parameters(opts[:parameters])
39
+ @cli, @interactive_mode = opts[:cli], opts[:interactive]
40
+ @parameter_mode, @restore_mode, @dry_run = opts[:parameter_mode], opts[:restore_mode], opts[:dry_run]
41
+ # Make sure to delete any option that isn't a MixLib::ShellOut option
42
+ @shell_opts = opts.select { |key, _| Psychic::Shell::AVAILABLE_OPTIONS.include? key }
31
43
  end
32
44
 
33
45
  def respond_to_missing?(task, include_all = false)
@@ -46,7 +58,7 @@ module Psychic
46
58
  def execute(command, *args)
47
59
  full_cmd = [command, *args].join(' ')
48
60
  logger.info("Executing #{full_cmd}")
49
- shell.execute(full_cmd, @opts)
61
+ shell.execute(full_cmd, @shell_opts) unless dry_run?
50
62
  end
51
63
 
52
64
  def command_for_task(task, *_args)
@@ -63,6 +75,34 @@ module Psychic
63
75
  def active?
64
76
  self.class.magic_file_pattern ? false : Dir["#{@cwd}/#{self.class.magic_file_pattern}"]
65
77
  end
78
+
79
+ def dry_run?
80
+ @dry_run == true
81
+ end
82
+
83
+ private
84
+
85
+ def load_hints
86
+ hints_file = Dir["#{@cwd}/psychic-hints.{yaml,yml}"].first
87
+ YAML.load(File.read(hints_file)) unless hints_file.nil?
88
+ end
89
+
90
+ def load_parameters(parameters)
91
+ if parameters.nil? || parameters.is_a?(String)
92
+ load_parameters_file(parameters)
93
+ else
94
+ parameters
95
+ end
96
+ end
97
+
98
+ def load_parameters_file(file = nil)
99
+ if file.nil?
100
+ file ||= File.expand_path(DEFAULT_PARAMS_FILE, cwd)
101
+ return {} unless File.exist? file
102
+ end
103
+ parameters = Psychic::Util.replace_tokens(File.read(file), @env)
104
+ YAML.load(parameters)
105
+ end
66
106
  end
67
107
  end
68
108
  end
@@ -3,23 +3,14 @@ module Psychic
3
3
  class HotRunner
4
4
  include BaseRunner
5
5
  def initialize(opts = {})
6
- hints = opts.delete :hints
7
6
  super
8
- @hints = Psychic::Util.stringified_hash(hints || load_hints || {})
9
- @tasks = @hints['tasks'] || {}
7
+ @tasks = hints['tasks'] || {}
10
8
  @known_tasks = @tasks.keys
11
9
  end
12
10
 
13
11
  def [](task_name)
14
12
  @tasks[task_name]
15
13
  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
14
  end
24
15
  end
25
16
  end
@@ -1,13 +1,16 @@
1
1
  module Psychic
2
2
  class Runner
3
3
  module SampleRunner
4
+ def find_sample(code_sample)
5
+ find_in_hints(code_sample) || Psychic::Util.find_file_by_alias(code_sample, cwd)
6
+ end
7
+
4
8
  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')
9
+ sample_file = find_sample(code_sample)
10
+ absolute_sample_file = File.expand_path(sample_file, cwd)
11
+ process_parameters(absolute_sample_file)
12
+ command = build_command(code_sample, sample_file)
8
13
  if command
9
- variables = { sample: code_sample, sample_file: sample_file }
10
- command = Psychic::Util.replace_tokens(command, variables)
11
14
  execute(command, *args)
12
15
  else
13
16
  run_sample_file(sample_file)
@@ -18,32 +21,78 @@ module Psychic
18
21
  execute("./#{sample_file}", *args) # Assuming Bash, but should detect Windows and use PowerShell
19
22
  end
20
23
 
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'")
24
+ def process_parameters(sample_file)
25
+ if templated?
26
+ backup_and_overwrite(sample_file)
26
27
 
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)
28
+ template = File.read(sample_file)
29
+ # Default token pattern/replacement (used by php-opencloud) should be configurable
30
+ token_handler = RegexpTokenHandler.new(template, /'\{(\w+)\}'/, "'\\1'")
31
+ confirm_or_update_parameters(token_handler.tokens)
32
+ File.write(sample_file, token_handler.render(@parameters))
33
+ end
32
34
  end
33
35
 
34
36
  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)
37
+ @parameter_mode == 'tokens'
38
+ end
39
+
40
+ def interactive?
41
+ !@interactive_mode.nil?
42
+ end
43
+
44
+ protected
45
+
46
+ def find_in_hints(code_sample)
47
+ return unless hints['samples']
48
+ hints['samples'].each do |k, v|
49
+ return v if k.downcase == code_sample.downcase
50
+ end
51
+ nil
52
+ end
53
+
54
+ def build_command(code_sample, sample_file)
55
+ command = command_for_task('run_sample')
56
+ return nil if command.nil?
57
+
58
+ command_params = { sample: code_sample, sample_file: sample_file }
59
+ command_params.merge!(@parameters) unless @parameters.nil?
60
+ Psychic::Util.replace_tokens(command, command_params)
61
+ end
62
+
63
+ def backup_and_overwrite(file)
64
+ backup_file = "#{file}.bak"
65
+ if File.exist? backup_file
66
+ if should_restore?(backup_file, file)
67
+ FileUtils.mv(backup_file, file)
68
+ else
69
+ abort 'Please clear out old backups before rerunning' if File.exist? backup_file
70
+ end
71
+ end
72
+ FileUtils.cp(file, backup_file)
73
+ end
74
+
75
+ def should_restore?(file, orig, timing = :before)
76
+ return true if [timing, 'always']. include? restore_mode
77
+ if interactive?
78
+ @cli.yes? "Would you like to #{file} to #{orig} before running the sample?"
79
+ end
80
+ end
81
+
82
+ def prompt(key)
83
+ if value
84
+ return value unless @interactive_mode == 'always'
85
+ new_value = @cli.ask "Please set a value for #{key} (or enter to confirm #{value.inspect}): "
86
+ new_value.empty? ? value : new_value
87
+ else
88
+ @cli.ask "Please set a value for #{key}: "
89
+ end
90
+ end
91
+
92
+ def confirm_or_update_parameters(required_parameters)
93
+ required_parameters.each do | key |
94
+ @parameters[key] = prompt(key)
95
+ end if interactive?
47
96
  end
48
97
  end
49
98
  end
@@ -1,5 +1,5 @@
1
1
  module Psychic
2
2
  class Runner
3
- VERSION = '0.0.3'
3
+ VERSION = '0.0.4'
4
4
  end
5
5
  end
data/lib/psychic/shell.rb CHANGED
@@ -4,6 +4,13 @@ module Psychic
4
4
  autoload :ExecutionError, 'psychic/shell/execution_result'
5
5
  autoload :MixlibShellOutExecutor, 'psychic/shell/mixlib_shellout_executor'
6
6
 
7
+ AVAILABLE_OPTIONS = [
8
+ # All MixLib::ShellOut options - though we don't use most of these
9
+ :cwd, :domain, :password, :user, :group, :umask,
10
+ :timeout, :returns, :live_stream, :live_stdout,
11
+ :live_stderr, :input, :logger, :log_level, :log_tag, :env
12
+ ]
13
+
7
14
  class << self
8
15
  attr_writer :shell
9
16
  end
@@ -12,7 +12,7 @@ module Psychic
12
12
  @template.scan(@token_pattern).flatten.uniq
13
13
  end
14
14
 
15
- def replace(variables = {})
15
+ def render(variables = {})
16
16
  @template.gsub(@token_pattern) do
17
17
  full_match = Regexp.last_match[0]
18
18
  key = Regexp.last_match[1]
@@ -32,7 +32,7 @@ module Psychic
32
32
  @template.tags
33
33
  end
34
34
 
35
- def replace(variables = {})
35
+ def render(variables = {})
36
36
  Mustache.render(@template, variables)
37
37
  end
38
38
  end
data/lib/psychic/util.rb CHANGED
@@ -4,8 +4,8 @@ module Psychic
4
4
  class Util
5
5
  module Hashable
6
6
  def to_hash
7
- instance_variables.each_with_object({}) do |var,hash|
8
- hash[var.to_s.delete("@")] = instance_variable_get(var)
7
+ instance_variables.each_with_object({}) do |var, hash|
8
+ hash[var.to_s.delete('@')] = instance_variable_get(var)
9
9
  end
10
10
  end
11
11
  end
@@ -30,6 +30,20 @@ module Psychic
30
30
  end
31
31
  end
32
32
 
33
+ def self.symbolized_hash(obj)
34
+ if obj.is_a?(Hash)
35
+ obj.each_with_object({}) do |(k, v), h|
36
+ h[k.to_sym] = symbolized_hash(v)
37
+ end
38
+ elsif obj.is_a?(Array)
39
+ obj.each_with_object([]) do |e, a|
40
+ a << symbolized_hash(e)
41
+ end
42
+ else
43
+ obj
44
+ end
45
+ end
46
+
33
47
  def self.relativize(file, base_path)
34
48
  absolute_file = File.absolute_path(file)
35
49
  absolute_base_path = File.absolute_path(base_path)
@@ -48,9 +62,9 @@ module Psychic
48
62
 
49
63
  def self.replace_tokens(template, variables, token_regexp = nil, token_replacement = nil)
50
64
  if token_regexp.nil?
51
- MustacheTokenHandler.new(template).replace(variables)
65
+ MustacheTokenHandler.new(template).render(variables)
52
66
  else
53
- RegexpTokenHandler.new(template, token_regexp, token_replacement).replace(variables)
67
+ RegexpTokenHandler.new(template, token_regexp, token_replacement).render(variables)
54
68
  end
55
69
  end
56
70
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: psychic-runner
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Max Lincoln
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-12-03 00:00:00.000000000 Z
11
+ date: 2014-12-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -185,11 +185,9 @@ files:
185
185
  - ".travis.yml"
186
186
  - Gemfile
187
187
  - LICENSE.txt
188
- - README.md
189
188
  - Rakefile
190
189
  - bin/psychic
191
190
  - lib/psychic/cli.rb
192
- - lib/psychic/commands/exec.rb
193
191
  - lib/psychic/logger.rb
194
192
  - lib/psychic/runner.rb
195
193
  - lib/psychic/runner/base_runner.rb
data/README.md DELETED
@@ -1,70 +0,0 @@
1
- **php-opencloud**
2
- =============
3
- PHP SDK for OpenStack/Rackspace APIs
4
-
5
- [![Latest Stable Version](https://poser.pugx.org/rackspace/php-opencloud/v/stable.png)](https://packagist.org/packages/rackspace/php-opencloud) [![Travis CI](https://secure.travis-ci.org/rackspace/php-opencloud.png)](https://travis-ci.org/rackspace/php-opencloud) [![Total Downloads](https://poser.pugx.org/rackspace/php-opencloud/downloads.png)](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).
@@ -1,19 +0,0 @@
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')