ansible-wrapper 0.2.1 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 502f430dd8b6fca43b1847722b1bdd51ae6a8df1eda0b8f2f035c93f6091ecc4
4
- data.tar.gz: 48070b75f6cbcac9f1c90baa5371f7c651572699d342f219ba738ed2c011d3b8
3
+ metadata.gz: 2e5caf4e2e2b9582a3d4440288160b30e51895cab153c2cb6194fcc38f682144
4
+ data.tar.gz: fc87385c8a099a7fb357a9d89a2b21c024dffa0534861b4afa9165def73d30cb
5
5
  SHA512:
6
- metadata.gz: 5f7b489399ab99c55c69fa11793e8c24e0cf6860c6c4bdaf5637f2ba0540a2006ce395c96ad20d7f9334bbd4af32677fbf260a0cce5a2fb16c891724e62af52a
7
- data.tar.gz: 7bb2ed85834fe9b2e4699e38c129c14739560e9a3a7aa7f4e65740f08fc8ebefeb0575c85a0b5ef0e585529f7432feae786aee2d7ef6fbbf6dffe419bff1c784
6
+ metadata.gz: ed22098776f0eb16adcf65707310e85c982d04a050505d0ffcfb8fb701ccce106f665d1bf6710bf25f738c41e650e32ca404f958304bd0c97bcacf858030db6f
7
+ data.tar.gz: 1d1ae31bbfd9cc8c4f1453bebc24d47a9a2e29496cf27efd924e6c3ec567e97becc489319aee5fe9bfe3752c180199b22574cb737d2c668eb728734bda7ff4c1
data/README.md CHANGED
@@ -4,13 +4,14 @@
4
4
  [![Build Status](https://travis-ci.com/pgeraghty/ansible-wrapper-ruby.svg?branch=master)](https://travis-ci.com/pgeraghty/ansible-wrapper-ruby)
5
5
  [![Coverage Status](https://coveralls.io/repos/github/pgeraghty/ansible-wrapper-ruby/badge.svg?branch=master)](https://coveralls.io/github/pgeraghty/ansible-wrapper-ruby?branch=master)
6
6
  [![Code Climate](https://codeclimate.com/github/pgeraghty/ansible-wrapper-ruby/badges/gpa.svg)](https://codeclimate.com/github/pgeraghty/ansible-wrapper-ruby)
7
+ [![Documentation](http://inch-ci.org/github/pgeraghty/ansible-wrapper-ruby.svg?branch=master)](http://inch-ci.org/github/pgeraghty/ansible-wrapper-ruby)
7
8
 
8
9
  #### A lightweight Ruby wrapper around Ansible that allows for ad-hoc commands and playbook execution. The primary purpose is to support easy streaming output.
9
10
 
10
11
  ## Requirements
11
12
 
12
13
  Ensure [Ansible](http://docs.ansible.com/intro_getting_started.html) is installed and available to shell commands i.e. in PATH.
13
- [Tested](https://travis-ci.org/pgeraghty/ansible-wrapper-ruby) with Ansible versions 1.9.4 and 2.0.0.2, but please create an issue if you use a version that fails.
14
+ [Tested](https://travis-ci.org/pgeraghty/ansible-wrapper-ruby) with Ansible versions 2.0.2 thru 2.8.5 and Ruby 2.1+, but please create an issue if you use a version that fails.
14
15
 
15
16
  ## Installation
16
17
 
@@ -72,9 +73,9 @@ A['all -i localhost, --list-hosts'] # alias for Ansible::AdHoc.run
72
73
  A << '-i localhost, spec/fixtures/mock_playbook.yml' # alias for Ansible::Playbook.stream
73
74
  ```
74
75
 
75
- ## Coming Soon
76
+ ## Examples
76
77
 
77
- * Streaming output example using Sinatra
78
+ * For a streaming output example using Sinatra, see the [examples/streaming](examples/streaming) folder.
78
79
 
79
80
  ## Development
80
81
 
@@ -3,6 +3,8 @@ lib = File.expand_path('../lib', __FILE__)
3
3
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  require 'ansible/version'
5
5
 
6
+ # TODO set required ruby version??
7
+
6
8
  Gem::Specification.new do |spec|
7
9
  spec.name = 'ansible-wrapper'
8
10
  spec.version = Ansible::VERSION
@@ -3,6 +3,8 @@ require 'ansible/ad_hoc'
3
3
  require 'ansible/playbook'
4
4
  require 'ansible/output'
5
5
 
6
+ # A lightweight Ruby wrapper around Ansible that allows for ad-hoc commands and playbook execution.
7
+ # The primary purpose is to support easy streaming output.
6
8
  module Ansible
7
9
  include Ansible::Config
8
10
  include Ansible::Methods
@@ -10,6 +12,8 @@ module Ansible
10
12
 
11
13
  extend self
12
14
 
15
+ # Enables shortcuts
16
+ # @see ansible/shortcuts.rb
13
17
  def enable_shortcuts!
14
18
  require 'ansible/shortcuts'
15
19
  end
@@ -2,21 +2,41 @@ require 'ansible/config'
2
2
  require 'json'
3
3
 
4
4
  module Ansible
5
+ # Ansible Ad-Hoc methods
5
6
  module Methods
7
+ # executable that runs Ansible Ad-Hoc commands
6
8
  BIN = 'ansible'
7
9
 
8
- def one_off cmd
10
+ # Run an Ad-Hoc Ansible command
11
+ # @param cmd [String] the Ansible command to execute
12
+ # @return [String] the output
13
+ # @example Run a simple shell command with an inline inventory that only contains localhost
14
+ # one_off 'all -c local -a "echo hello"'
15
+ def one_off(cmd)
16
+ # TODO if debug then puts w/ colour
9
17
  `#{config.to_s "#{BIN} #{cmd}"}`
10
18
  end
11
19
  alias :[] :one_off
12
20
 
13
- def list_hosts cmd
21
+ # Ask Ansible to list hosts
22
+ # @param cmd [String] the Ansible command to execute
23
+ # @return [String] the output
24
+ # @example List hosts with an inline inventory that only contains localhost
25
+ # list_hosts 'all -i localhost,'
26
+ def list_hosts(cmd)
14
27
  output = one_off("#{cmd} --list-hosts").gsub!(/\s+hosts.*:\n/, '').strip
15
28
  output.split("\n").map(&:strip)
16
29
  end
17
30
 
18
- def parse_host_vars(host, inv_file, filter = 'hostvars[inventory_hostname]')
19
- cmd = "all -m debug -a 'var=#{filter}' -i #{inv_file} -l #{host}"
31
+ # Fetches host variables via Ansible's debug module
32
+ # @param host [String] the +<host-pattern>+ for target host(s)
33
+ # @param inv [String] the inventory host path or comma-separated host list
34
+ # @param filter [String] the variable filter
35
+ # @return [Hash] the variables pertaining to the host
36
+ # @example List variables for localhost
37
+ # parse_host_vars 'localhost', 'localhost,'
38
+ def parse_host_vars(host, inv, filter = 'hostvars[inventory_hostname]')
39
+ cmd = "all -m debug -a 'var=#{filter}' -i #{inv} -l #{host}"
20
40
  json = self[cmd].split(/>>|=>/).last
21
41
 
22
42
  # remove any colour added to console output
@@ -31,11 +51,18 @@ module Ansible
31
51
  end
32
52
  end
33
53
 
54
+ # Provides static access to Ad-Hoc methods
34
55
  module AdHoc
35
- include Ansible::Config
36
- include Ansible::Methods
56
+ include Config
57
+ include Methods
37
58
 
38
59
  extend self
60
+
61
+ # Run an Ad-Hoc Ansible command
62
+ # @see Methods#one_off
63
+ # @param cmd [String] the Ansible command to execute
64
+ # @return [String] the output
65
+ # @since 0.2.1
39
66
  alias :run :one_off
40
67
  end
41
68
  end
@@ -1,31 +1,41 @@
1
1
  module Ansible
2
+ # Ansible configuration
2
3
  module Config
3
4
  PATH = 'lib/ansible/'
4
5
  # IP_OR_HOSTNAME = /((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3})$|^((([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9]))\n/
5
6
  SKIP_HOSTVARS = %w(ansible_version inventory_dir inventory_file inventory_hostname inventory_hostname_short group_names groups omit playbook_dir)
6
7
  VERSION = `ansible --version`.split("\n").first.split.last rescue nil # nil when Ansible not installed
7
8
 
9
+ # Default configuration options
8
10
  DefaultConfig = Struct.new(:env, :extra_vars, :params) do
11
+ # @!attribute env
12
+ # @return [Hash] environment variables
13
+ # @!attribute params
14
+ # @return [Hash] parameters
15
+ # @!attribute extra_vars
16
+ # @return [Hash] extra variables to pass to Ansible
17
+
9
18
  def initialize
10
19
  self.env = {
11
20
  'ANSIBLE_FORCE_COLOR' => 'True',
12
21
  'ANSIBLE_HOST_KEY_CHECKING' => 'False'
13
22
  }
14
23
 
15
- self.params = {
16
- debug: false
17
- }
18
-
19
- # options
20
24
  self.extra_vars = {
21
25
  # skip creation of .retry files
22
26
  'retry_files_enabled' => 'False'
23
27
  }
24
28
  # TODO support --ssh-common-args, --ssh-extra-args
25
29
  # e.g. ansible-playbook --ssh-common-args="-o ServerAliveInterval=60" -i inventory install.yml
30
+
31
+ self.params = {
32
+ debug: false
33
+ }
26
34
  end
27
35
 
36
+ # Pass additional options to Ansible
28
37
  # NB: --extra-vars can also accept JSON string, see http://stackoverflow.com/questions/25617273/pass-array-in-extra-vars-ansible
38
+ # @return [String] command-line options
29
39
  def options
30
40
  x = extra_vars.each_with_object('--extra-vars=\'') { |kv, a| a << "#{kv.first}=\"#{kv.last}\" " }.strip+'\'' if extra_vars unless extra_vars.empty?
31
41
  # can test with configure { |config| config.extra_vars.clear }
@@ -33,6 +43,9 @@ module Ansible
33
43
  [x, '--ssh-extra-args=\'-o UserKnownHostsFile=/dev/null\'']*' '
34
44
  end
35
45
 
46
+ # Output configuration as a string for the command-line
47
+ # @param cmd [String] command to be appended to the command-line produced
48
+ # @return [Config, DefaultConfig] the configuration
36
49
  def to_s(cmd)
37
50
  entire_cmd = [env.each_with_object([]) { |kv, a| a << kv*'=' } * ' ', cmd, options]*' '
38
51
  puts entire_cmd if params[:debug]
@@ -40,6 +53,8 @@ module Ansible
40
53
  end
41
54
  end
42
55
 
56
+ # Create and yield configuration
57
+ # @return [Config, DefaultConfig] the configuration
43
58
  def configure
44
59
  @config ||= DefaultConfig.new
45
60
  yield(@config) if block_given?
@@ -48,6 +63,8 @@ module Ansible
48
63
  block_given? ? self : @config
49
64
  end
50
65
 
66
+ # accessor for config
67
+ # @return [DefaultConfig] the configuration
51
68
  def config
52
69
  @config || configure
53
70
  end
@@ -2,49 +2,91 @@ require 'strscan'
2
2
  require 'erb'
3
3
 
4
4
  module Ansible
5
+ # Output module provides formatting of Ansible output
5
6
  module Output
6
- COLOR = {
7
- '1' => 'font-weight: bold',
8
- '30' => 'color: black',
9
- '31' => 'color: red',
10
- '32' => 'color: green',
11
- '33' => 'color: yellow',
12
- '34' => 'color: blue',
13
- '35' => 'color: magenta',
14
- '36' => 'color: cyan',
15
- '37' => 'color: white',
16
- '90' => 'color: grey'
17
- }
18
-
19
- def self.to_html(line, stream='')
20
- s = StringScanner.new(ERB::Util.h line)
21
- while(!s.eos?)
22
- if s.scan(/\e\[([0-1])?[;]?(3[0-7]|90|1)m/)
23
- bold, colour = s[1], s[2]
24
- styles = []
25
-
26
- styles << COLOR[bold] if bold.to_i == 1
27
- styles << COLOR[colour]
28
-
29
- span =
30
- # in case of invalid colours, although this may be impossible
31
- if styles.compact.empty?
32
- %{<span>}
33
- else
34
- %{<span style="#{styles*'; '};">}
35
- end
36
-
37
- stream << span
38
- elsif s.scan(/\e\[0m/)
39
- stream << %{</span>}
40
- elsif s.scan(/\e\[[^0]*m/)
41
- stream << '<span>'
7
+ # Generate HTML for an output string formatted with ANSI escape sequences representing colours and styling
8
+ # @param ansi [String] an output string formatted with escape sequences to represent formatting
9
+ # @param stream [String] a stream or string (that supports +<<+) to which generated HTML will be appended
10
+ # @return the stream provided or a new String
11
+ # @example List hosts with an inline inventory that only contains localhost
12
+ # to_html "\e[90mGrey\e[0m" => '<span style="color: grey;">Grey</span>'
13
+ def self.to_html(ansi, stream='')
14
+ Ansi2Html.new(ansi).to_html stream
15
+ end
16
+
17
+ # Converter for strings containing with ANSI escape sequences
18
+ class Ansi2Html
19
+ # Hash of colors to convert shell colours to CSS
20
+ COLOR = {
21
+ '1' => 'font-weight: bold',
22
+ '30' => 'color: black',
23
+ '31' => 'color: red',
24
+ '32' => 'color: green',
25
+ '33' => 'color: yellow',
26
+ '34' => 'color: blue',
27
+ '35' => 'color: magenta',
28
+ '36' => 'color: cyan',
29
+ '37' => 'color: white',
30
+ '90' => 'color: grey'
31
+ }
32
+
33
+ SUPPORTED_STYLE_PATTERN = /\e\[([0-1])?[;]?(3[0-7]|90|1)m/
34
+ END_ESCAPE_SEQUENCE_PATTERN = /\e\[0m/
35
+ UNSUPPORTED_STYLE_PATTERN = /\e\[[^0]*m/
36
+ IGNORED_OUTPUT = /./m
37
+
38
+ OPEN_SPAN_TAG = %{<span>}
39
+ CLOSE_SPAN_TAG = %{</span>}
40
+
41
+ # Create StringScanner for string
42
+ # @param line [String] a stream or string (that supports +<<+) to which generated HTML will be appended
43
+ def initialize(line)
44
+ # ensure any HTML tag characters are escaped
45
+ @strscan = StringScanner.new(ERB::Util.h line)
46
+ end
47
+
48
+ # Generate HTML from string formatted with ANSI escape sequences
49
+ # @return [String, IO] the HTML
50
+ def to_html(stream)
51
+ until @strscan.eos?
52
+ stream << generate_html
53
+ end
54
+
55
+ stream
56
+ end
57
+
58
+
59
+ private
60
+
61
+ # Scan string and generate HTML
62
+ def generate_html
63
+ if @strscan.scan SUPPORTED_STYLE_PATTERN
64
+ open_tag
65
+ elsif @strscan.scan END_ESCAPE_SEQUENCE_PATTERN
66
+ CLOSE_SPAN_TAG
67
+ elsif @strscan.scan UNSUPPORTED_STYLE_PATTERN
68
+ OPEN_SPAN_TAG
42
69
  else
43
- stream << s.scan(/./m)
70
+ @strscan.scan IGNORED_OUTPUT
44
71
  end
45
72
  end
46
73
 
47
- stream
74
+ # Generate opening HTML tag, which may contain a style attribute
75
+ # @return [String] opening tag
76
+ def open_tag
77
+ bold, colour = @strscan[1], @strscan[2]
78
+ styles = []
79
+
80
+ styles << COLOR[bold] if bold.to_i == 1
81
+ styles << COLOR[colour]
82
+
83
+ # in case of invalid colours, although this may be impossible
84
+ if styles.compact.empty?
85
+ OPEN_SPAN_TAG
86
+ else
87
+ %{<span style="#{styles*'; '};">}
88
+ end
89
+ end
48
90
  end
49
91
  end
50
92
  end
@@ -2,38 +2,53 @@ require 'ansible/config'
2
2
  require 'ansible/safe_pty'
3
3
 
4
4
  module Ansible
5
+ # Ansible Playbook methods
5
6
  module PlaybookMethods
7
+ # executable that runs Ansible Playbooks
6
8
  BIN = 'ansible-playbook'
7
9
 
8
- def playbook pb
10
+ # Run playbook, returning output
11
+ # @param pb [String] path to playbook
12
+ # @return [String] output
13
+ def playbook(pb)
14
+ # TODO if debug then puts w/ colour
9
15
  `#{config.to_s "#{BIN} #{pb}"}`
10
16
  end
11
17
  alias :<< :playbook
12
18
 
13
- def stream pb
14
- # Use PTY because otherwise output is buffered
15
- SafePty.spawn config.to_s("#{BIN} #{pb}") do |r,w,p| # add -vvvv here for verbose
19
+ # Stream execution of a playbook using PTY because otherwise output is buffered
20
+ # @param pb [String] path to playbook
21
+ # @return [Integer] exit status
22
+ def stream(pb)
23
+ cmd = config.to_s("#{BIN} #{pb}")
24
+
25
+ SafePty.spawn cmd do |r,_,_| # add -vvvv here for verbose
16
26
  until r.eof? do
17
27
  line = r.gets
18
28
  block_given? ? yield(line) : puts(line)
19
29
 
20
- raise "FAILED: #{line}" if line.include?('fatal: [')
21
- raise Playbook::Exception.new("ERROR: #{line}") if line.include?('ERROR!')
22
- # TODO raise if contains FAILED!
30
+ case line
31
+ when /fatal: \[/ then raise Playbook::Exception.new("FAILED: #{line}")
32
+ when /ERROR!/,/FAILED!/ then raise Playbook::Exception.new("ERROR: #{line}")
33
+ end
23
34
  end
24
35
  end
25
36
  end
26
37
  end
27
- end
28
38
 
29
- module Ansible
39
+ # Provides static access to Playbook methods
30
40
  module Playbook
31
- include Ansible::Config
32
- include Ansible::PlaybookMethods
41
+ include Config
42
+ include PlaybookMethods
33
43
 
34
44
  extend self
45
+
46
+ # Run playbook, returning output
47
+ # @param pb [String] path to playbook
48
+ # @return [String] output
35
49
  alias :run :playbook
36
50
 
51
+ # Exception to represent Playbook failures
37
52
  class Exception < RuntimeError; end
38
53
  end
39
54
  end
@@ -1,6 +1,10 @@
1
1
  require 'pty'
2
2
 
3
+ # Wrapper for PTY pseudo-terminal
3
4
  module Ansible::SafePty
5
+ # Spawns process for command
6
+ # @param command [String] command
7
+ # @return [Integer] exit status
4
8
  def self.spawn(command)
5
9
 
6
10
  PTY.spawn(command) do |r,w,p|
@@ -1,10 +1,16 @@
1
1
  module Ansible
2
2
  extend self
3
3
 
4
+ # shortcut for executing an Ad-Hoc command
5
+ # @param cmd [String] the command-line to pass
6
+ # @see AdHoc#run
4
7
  def [](cmd)
5
8
  AdHoc.run cmd
6
9
  end
7
10
 
11
+ # shortcut to run a Playbook, streaming the output
12
+ # @param cmd [String] the command-line to pass
13
+ # @see Playbook#stream
8
14
  def <<(cmd)
9
15
  Playbook.stream cmd
10
16
  end
@@ -1,3 +1,3 @@
1
1
  module Ansible
2
- VERSION = '0.2.1'
2
+ VERSION = '0.2.2'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ansible-wrapper
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paul Geraghty
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-09-24 00:00:00.000000000 Z
11
+ date: 2019-09-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json