psychic-runner 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
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')