ansible-wrapper 0.2.1 → 0.2.2

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
  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