├── .github
└── workflows
│ └── test_via_docker.yml
├── .gitignore
├── .rspec
├── .travis.yml
├── CHANGELOG.md
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── ansible-wrapper.gemspec
├── bin
├── console
├── rake
└── setup
├── examples
└── streaming
│ ├── Gemfile
│ ├── Gemfile.lock
│ ├── config.ru
│ └── run.rb
├── lib
├── ansible-wrapper.rb
└── ansible
│ ├── ad_hoc.rb
│ ├── config.rb
│ ├── output.rb
│ ├── playbook.rb
│ ├── safe_pty.rb
│ ├── shortcuts.rb
│ └── version.rb
└── spec
├── ansible
├── ad_hoc_spec.rb
├── ansible_spec.rb
├── output_spec.rb
└── playbook_spec.rb
├── fixtures
├── fail_playbook.yml
├── failure_then_ignored_error_playbook.yml
├── ignored_error_playbook.yml
├── ignored_error_then_failure_playbook.yml
├── ignored_errors_playbook.yml
└── mock_playbook.yml
└── spec_helper.rb
/.github/workflows/test_via_docker.yml:
--------------------------------------------------------------------------------
1 | name: Test using different Ansible & Ruby versions
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 | strategy:
9 | matrix:
10 | ansible:
11 | - 2.9.6
12 | - 2.8.10
13 | - 2.7.16
14 | - 2.6.20
15 | - 2.5.15
16 | - 2.4.6
17 | - 2.3.3
18 | - 2.2.3
19 | - 2.1.6
20 | - 2.0.2
21 | steps:
22 | - uses: actions/checkout@v1
23 | - name: Test against Ruby 2.7.0
24 | env:
25 | RUBY_VERSION: 2.7.0
26 | ANSIBLE_VERSION: ${{ matrix.ansible }}
27 | run: >
28 | docker run --rm -v $PWD:/app pgeraghty/ansible-ruby:$RUBY_VERSION-$ANSIBLE_VERSION
29 | /bin/sh -c "cp -r /app /tmp/app && cd /tmp/app && bundle install --jobs=3 --retry=3 &&
30 | COVERALLS_REPO_TOKEN=${{ secrets.COVERALLS_REPO_TOKEN }} COVERALLS_RUN_LOCALLY=true bundle exec rake"
31 |
32 | - name: Test against Ruby 2.6.5
33 | env:
34 | RUBY_VERSION: 2.6.5
35 | ANSIBLE_VERSION: ${{ matrix.ansible }}
36 | run: >
37 | docker run --rm -v $PWD:/app pgeraghty/ansible-ruby:$RUBY_VERSION-$ANSIBLE_VERSION
38 | /bin/sh -c "cp -r /app /tmp/app && cd /tmp/app && bundle install --jobs=3 --retry=3 && bundle exec rake"
39 |
40 | - name: Test against Ruby 2.5.7
41 | env:
42 | RUBY_VERSION: 2.5.7
43 | ANSIBLE_VERSION: ${{ matrix.ansible }}
44 | run: >
45 | docker run --rm -v $PWD:/app pgeraghty/ansible-ruby:$RUBY_VERSION-$ANSIBLE_VERSION
46 | /bin/sh -c "cp -r /app /tmp/app && cd /tmp/app && bundle install --jobs=3 --retry=3 && bundle exec rake"
47 |
48 | - name: Test against Ruby 2.4.9
49 | env:
50 | RUBY_VERSION: 2.4.9
51 | ANSIBLE_VERSION: ${{ matrix.ansible }}
52 | run: >
53 | docker run --rm -v $PWD:/app pgeraghty/ansible-ruby:$RUBY_VERSION-$ANSIBLE_VERSION
54 | /bin/sh -c "cp -r /app /tmp/app && cd /tmp/app && bundle install --jobs=3 --retry=3 && bundle exec rake"
55 |
56 | - name: Test against Ruby 2.3.7
57 | env:
58 | RUBY_VERSION: 2.3.7
59 | ANSIBLE_VERSION: ${{ matrix.ansible }}
60 | run: >
61 | docker run --rm -v $PWD:/app pgeraghty/ansible-ruby:$RUBY_VERSION-$ANSIBLE_VERSION
62 | /bin/sh -c "cp -r /app /tmp/app && cd /tmp/app && bundle install --jobs=3 --retry=3 && bundle exec rake"
63 |
64 | - name: Test against Ruby 2.2.7
65 | env:
66 | RUBY_VERSION: 2.2.7
67 | ANSIBLE_VERSION: ${{ matrix.ansible }}
68 | run: >
69 | docker run --rm -v $PWD:/app pgeraghty/ansible-ruby:$RUBY_VERSION-$ANSIBLE_VERSION
70 | /bin/sh -c "cp -r /app /tmp/app && cd /tmp/app && bundle install --jobs=3 --retry=3 && bundle exec rake"
71 |
72 | - name: Test against Ruby 2.1.10
73 | env:
74 | RUBY_VERSION: 2.1.10
75 | ANSIBLE_VERSION: ${{ matrix.ansible }}
76 | run: >
77 | docker run --rm -v $PWD:/app pgeraghty/ansible-ruby:$RUBY_VERSION-$ANSIBLE_VERSION
78 | /bin/sh -c "cp -r /app /tmp/app && cd /tmp/app && bundle install --jobs=3 --retry=3 && bundle exec rake"
79 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.bundle/
2 | /.idea/
3 | /.yardoc
4 | /Gemfile.lock
5 | /_yardoc/
6 | /coverage/
7 | /doc/
8 | /pkg/
9 | /spec/reports/
10 | /tmp/
11 | .ruby-gemset
12 | .ruby-version
13 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --format documentation
2 | --color
3 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: bash
2 | services:
3 | - docker
4 | env:
5 | global:
6 | - CC_TEST_REPORTER_ID=2c0e6ce9e61357e6f15cf76a915b3868235d93e0b849218220242e456bfaa2aa
7 | matrix:
8 | - ANSIBLE_VERSION=2.8.10
9 | - ANSIBLE_VERSION=2.7.16
10 | - ANSIBLE_VERSION=2.6.20
11 | - ANSIBLE_VERSION=2.5.15
12 | - ANSIBLE_VERSION=2.4.6
13 | - ANSIBLE_VERSION=2.3.3
14 | - ANSIBLE_VERSION=2.2.3
15 | - ANSIBLE_VERSION=2.1.6
16 | - ANSIBLE_VERSION=2.0.2
17 | before_script:
18 | - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
19 | - chmod +x ./cc-test-reporter
20 | script:
21 | - >
22 | RUBY=2.7.0 && docker run --rm -e TRAVIS -e TRAVIS_JOB_ID -e TRAVIS_BRANCH -e TRAVIS_PULL_REQUEST
23 | -e CC_TEST_REPORTER_ID -it -v $PWD:/app pgeraghty/ansible-ruby:$RUBY-$ANSIBLE_VERSION /bin/sh -c
24 | "cd /app && bundle install --jobs=3 --retry=3 &&
25 | ./cc-test-reporter before-build &&
26 | CI=true bundle exec rake && rm Gemfile.lock"
27 | - >
28 | RUBY=2.6.5 && docker run --rm -it -v $PWD:/app pgeraghty/ansible-ruby:$RUBY-$ANSIBLE_VERSION
29 | /bin/sh -c "cp -r /app /tmp/app && cd /tmp/app && bundle install --jobs=3 --retry=3 && bundle exec rake"
30 | - >
31 | RUBY=2.5.7 && docker run --rm -it -v $PWD:/app pgeraghty/ansible-ruby:$RUBY-$ANSIBLE_VERSION
32 | /bin/sh -c "cd /app && bundle install --jobs=3 --retry=3 && bundle exec rake"
33 | - >
34 | RUBY=2.4.9 && docker run --rm -it -v $PWD:/app pgeraghty/ansible-ruby:$RUBY-$ANSIBLE_VERSION
35 | /bin/sh -c "cd /app && bundle install --jobs=3 --retry=3 && bundle exec rake"
36 | - >
37 | RUBY=2.3.7 && docker run --rm -it -v $PWD:/app pgeraghty/ansible-ruby:$RUBY-$ANSIBLE_VERSION
38 | /bin/sh -c "cd /app && bundle install --jobs=3 --retry=3 && bundle exec rake"
39 | - >
40 | RUBY=2.2.7 && docker run --rm -it -v $PWD:/app pgeraghty/ansible-ruby:$RUBY-$ANSIBLE_VERSION
41 | /bin/sh -c "cd /app && bundle install --jobs=3 --retry=3 && bundle exec rake"
42 | - >
43 | RUBY=2.1.10 && docker run --rm -it -v $PWD:/app pgeraghty/ansible-ruby:$RUBY-$ANSIBLE_VERSION
44 | /bin/sh -c "cd /app && bundle install --jobs=3 --retry=3 && bundle exec rake"
45 | after_script:
46 | - RUBY=2.6.4 && docker run --rm -e TRAVIS -e TRAVIS_JOB_ID -e TRAVIS_BRANCH -e TRAVIS_PULL_REQUEST
47 | -e TRAVIS_TEST_RESULT -e CC_TEST_REPORTER_ID -it -v $PWD:/app pgeraghty/ansible-ruby:$RUBY-$ANSIBLE_VERSION
48 | /bin/sh -c "cd /app && ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT"
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## 0.3.0
4 |
5 | ### Breaking changes
6 | `Playbook.stream` has been refactored to include a new `raise_on_failure` parameter.
7 | This is part of a change in behaviour for this method to no longer raise exceptions by default
8 | and stream the entire Playbook execution.
9 |
10 | To previous behaviour can be obtained by passing `raise_on_failure: :during`
11 | to the `stream`method i.e.
12 |
13 | ```ruby
14 | Playbook.stream "some_playbook.yml", raise_on_failure: :during
15 | ```
16 |
17 | Alternatively, if you'd still like to raise exception for failures, but only after output has finished streaming, you can use:
18 | ```ruby
19 | Playbook.stream "some_playbook.yml", raise_on_failure: :after
20 | ```
21 |
22 | In addition to the breaking changes above,`Playbook.stream` now handles tasks that
23 | are skipped according to Ansible's output.
24 | This should include tasks which have `ignore_errors` set.
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | # Specify your gem's dependencies in ansible-wrapper.gemspec
4 | gemspec
5 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Paul Geraghty
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Ansible Wrapper
2 |
3 | [](http://badge.fury.io/rb/ansible-wrapper)
4 | [](https://travis-ci.com/pgeraghty/ansible-wrapper-ruby)
5 | [](https://coveralls.io/github/pgeraghty/ansible-wrapper-ruby?branch=master)
6 | [](https://codeclimate.com/github/pgeraghty/ansible-wrapper-ruby)
7 | [](http://inch-ci.org/github/pgeraghty/ansible-wrapper-ruby)
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.
10 |
11 | ## Requirements
12 |
13 | Ensure [Ansible](http://docs.ansible.com/intro_getting_started.html) is installed and available to shell commands i.e. in PATH.
14 | [Tested](https://travis-ci.org/pgeraghty/ansible-wrapper-ruby) with Ansible versions 2.0.2 thru 2.9.6 and Ruby 2.1+, but please create an issue if you use a version that fails.
15 |
16 | ## Installation
17 |
18 | Add this line to your application's Gemfile:
19 |
20 | ```ruby
21 | gem 'ansible-wrapper'
22 | ```
23 |
24 | And then execute:
25 |
26 | $ bundle
27 |
28 | Or install it yourself as:
29 |
30 | $ gem install ansible-wrapper
31 |
32 | ## Usage
33 |
34 | ### Ad-hoc commands
35 |
36 | ```ruby
37 | Ansible::AdHoc.run 'all -i localhost, --list-hosts'
38 | ```
39 |
40 | ```ruby
41 | Ansible::AdHoc.run 'all -m shell -a "echo Test" -i localhost,'
42 | ```
43 |
44 | ### Playbooks
45 |
46 | ```ruby
47 | Ansible::Playbook.run '-i localhost, spec/fixtures/mock_playbook.yml'
48 | ```
49 |
50 | ```ruby
51 | Ansible::Playbook.stream('-i localhost, spec/fixtures/mock_playbook.yml') # defaults to standard output
52 | ```
53 |
54 | ```ruby
55 | Ansible::Playbook.stream('-i localhost, spec/fixtures/mock_playbook.yml') { |line_of_output| puts line_of_output }
56 | ```
57 |
58 | ### Shortcuts
59 |
60 | To enable shortcuts:
61 |
62 | ```ruby
63 | Ansible.enable_shortcuts!
64 | ```
65 |
66 | You can then access Ansible via the `A` alias and use the following syntax:
67 |
68 | ```ruby
69 | A['all -i localhost, --list-hosts'] # alias for Ansible::AdHoc.run
70 | ```
71 |
72 | ```ruby
73 | A << '-i localhost, spec/fixtures/mock_playbook.yml' # alias for Ansible::Playbook.stream
74 | ```
75 |
76 | ## Examples
77 |
78 | * For a streaming output example using Sinatra, see the [examples/streaming](examples/streaming) folder.
79 |
80 | ## Development
81 |
82 | After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
83 |
84 | To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
85 |
86 | ## Contributing
87 |
88 | Bug reports and pull requests are welcome on GitHub at https://github.com/pgeraghty/ansible-wrapper-ruby.
89 |
90 |
91 | ## License
92 |
93 | The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
94 |
95 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require "bundler/gem_tasks"
2 | require "rspec/core/rake_task"
3 |
4 | RSpec::Core::RakeTask.new(:spec)
5 |
6 | task :default => :spec
7 |
--------------------------------------------------------------------------------
/ansible-wrapper.gemspec:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | lib = File.expand_path('../lib', __FILE__)
3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4 | require 'ansible/version'
5 |
6 | # TODO set required ruby version??
7 |
8 | Gem::Specification.new do |spec|
9 | spec.name = 'ansible-wrapper'
10 | spec.version = Ansible::VERSION
11 | spec.authors = ['Paul Geraghty']
12 | spec.email = ['muse@appsthatcould.be']
13 |
14 | spec.summary = %q{Ruby wrapper around Ansible}
15 | spec.description = %q{A lightweight Ruby wrapper around Ansible that allows for ad-hoc commands and playbook execution.}
16 | spec.homepage = 'https://github.com/pgeraghty/ansible-wrapper-ruby'
17 | spec.license = 'MIT'
18 |
19 | # Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
20 | # delete this section to allow pushing this gem to any host.
21 | # if spec.respond_to?(:metadata)
22 | # spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'"
23 | # else
24 | # raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
25 | # end
26 |
27 | spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
28 | spec.bindir = 'exe'
29 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
30 | spec.require_paths = ['lib']
31 |
32 | spec.add_dependency 'json'
33 |
34 | spec.add_development_dependency 'bundler'
35 | spec.add_development_dependency 'rake', '~> 10.0'
36 | spec.add_development_dependency 'rspec'
37 | spec.add_development_dependency 'coveralls'
38 | spec.add_development_dependency 'json', '~> 1.8.4'
39 | end
40 |
--------------------------------------------------------------------------------
/bin/console:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require 'bundler/setup'
4 | require 'ansible-wrapper'
5 |
6 | # You can add fixtures and/or initialization code here to make experimenting
7 | # with your gem easier. You can also use a different console, if you like.
8 |
9 | # (If you use this, don't forget to add pry to your Gemfile!)
10 | # require "pry"
11 | # Pry.start
12 |
13 | require 'irb'
14 | IRB.start
15 |
--------------------------------------------------------------------------------
/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'rake' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("rake", "rake")
30 |
--------------------------------------------------------------------------------
/bin/setup:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -euo pipefail
3 | IFS=$'\n\t'
4 |
5 | bundle install
6 |
7 | # Do any other automated setup that you need to do here
8 |
--------------------------------------------------------------------------------
/examples/streaming/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | gem 'sinatra'
4 | gem 'thin'
5 |
6 | gem 'ansible-wrapper', path: '../../' #github: 'pgeraghty/ansible-wrapper-ruby'
--------------------------------------------------------------------------------
/examples/streaming/Gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: ../..
3 | specs:
4 | ansible-wrapper (0.2.0)
5 | json
6 |
7 | GEM
8 | remote: https://rubygems.org/
9 | specs:
10 | daemons (1.3.1)
11 | eventmachine (1.2.7)
12 | json (2.3.0)
13 | mustermann (1.0.3)
14 | rack (2.0.8)
15 | rack-protection (2.0.7)
16 | rack
17 | sinatra (2.0.7)
18 | mustermann (~> 1.0)
19 | rack (~> 2.0)
20 | rack-protection (= 2.0.7)
21 | tilt (~> 2.0)
22 | thin (1.7.2)
23 | daemons (~> 1.0, >= 1.0.9)
24 | eventmachine (~> 1.0, >= 1.0.4)
25 | rack (>= 1, < 3)
26 | tilt (2.0.9)
27 |
28 | PLATFORMS
29 | ruby
30 |
31 | DEPENDENCIES
32 | ansible-wrapper!
33 | sinatra
34 | thin
35 |
36 | BUNDLED WITH
37 | 1.17.3
38 |
--------------------------------------------------------------------------------
/examples/streaming/config.ru:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require './run'
3 |
4 | run Sinatra::Application
--------------------------------------------------------------------------------
/examples/streaming/run.rb:
--------------------------------------------------------------------------------
1 | require 'sinatra'
2 |
3 | require 'ansible-wrapper'
4 |
5 | set server: 'thin'
6 | set :environment, :production
7 | set :bind, '0.0.0.0'
8 | set :port, 4567
9 |
10 | Process.setproctitle 'Ansible Streaming Example'
11 |
12 |
13 | CONSOLE_OUTPUT_START = %{
14 |
15 |
16 |
17 |
52 |
53 | }
54 |
55 | CONSOLE_OUTPUT_END = '
'
56 |
57 |
58 | get '/' do
59 | "Ansible #{Ansible::Config::VERSION}"
60 | end
61 |
62 | get '/streaming' do
63 | #content_type 'text/plain'
64 | stream do |out|
65 | out << CONSOLE_OUTPUT_START
66 | Ansible.stream ['-i', 'localhost,', File.expand_path('../../../spec/fixtures/mock_playbook.yml', __FILE__)]*' ' do |line|
67 | Ansible::Output.to_html line, out
68 | end
69 | out << CONSOLE_OUTPUT_END
70 | end
71 | end
72 |
--------------------------------------------------------------------------------
/lib/ansible-wrapper.rb:
--------------------------------------------------------------------------------
1 | require 'ansible/version'
2 | require 'ansible/ad_hoc'
3 | require 'ansible/playbook'
4 | require 'ansible/output'
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.
8 | module Ansible
9 | include Ansible::Config
10 | include Ansible::Methods
11 | include Ansible::PlaybookMethods
12 |
13 | extend self
14 |
15 | # Enables shortcuts
16 | # @see ansible/shortcuts.rb
17 | def enable_shortcuts!
18 | require 'ansible/shortcuts'
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/lib/ansible/ad_hoc.rb:
--------------------------------------------------------------------------------
1 | require 'ansible/config'
2 | require 'json'
3 |
4 | module Ansible
5 | # Ansible Ad-Hoc methods
6 | module Methods
7 | # executable that runs Ansible Ad-Hoc commands
8 | BIN = 'ansible'
9 |
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
17 | `#{config.to_s "#{BIN} #{cmd}"}`
18 | end
19 | alias :[] :one_off
20 |
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)
27 | output = one_off("#{cmd} --list-hosts").gsub!(/\s+hosts.*:\n/, '').strip
28 | output.split("\n").map(&:strip)
29 | end
30 |
31 | # Fetches host variables via Ansible's debug module
32 | # @param host [String] the ++ 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}"
40 | json = self[cmd].split(/>>|=>/).last
41 |
42 | # remove any colour added to console output
43 | # TODO move to Output module as #bleach, perhaps use term-ansicolor
44 | # possibly replace regexp with /\e\[(?:(?:[349]|10)[0-7]|[0-9]|[34]8;5;\d{1,3})?m/
45 | # possibly use ANSIBLE_NOCOLOR? or --nocolor
46 | json = json.strip.chomp.gsub(/\e\[[0-1][;]?(3[0-7]|90|1)?m/, '')
47 |
48 | hostvars = JSON.parse(json)
49 |
50 | hostvars[filter]
51 | end
52 | end
53 |
54 | # Provides static access to Ad-Hoc methods
55 | module AdHoc
56 | include Config
57 | include Methods
58 |
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
66 | alias :run :one_off
67 | end
68 | end
--------------------------------------------------------------------------------
/lib/ansible/config.rb:
--------------------------------------------------------------------------------
1 | module Ansible
2 | # Ansible configuration
3 | module Config
4 | PATH = 'lib/ansible/'
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/
6 | SKIP_HOSTVARS = %w(ansible_version inventory_dir inventory_file inventory_hostname inventory_hostname_short group_names groups omit playbook_dir)
7 | VERSION = `ansible --version`.split("\n").first.split.last rescue nil # nil when Ansible not installed
8 |
9 | # Default configuration options
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 |
18 | def initialize
19 | self.env = {
20 | 'ANSIBLE_FORCE_COLOR' => 'True',
21 | 'ANSIBLE_HOST_KEY_CHECKING' => 'False'
22 | }
23 |
24 | self.extra_vars = {
25 | # skip creation of .retry files
26 | 'retry_files_enabled' => 'False'
27 | }
28 | # TODO support --ssh-common-args, --ssh-extra-args
29 | # e.g. ansible-playbook --ssh-common-args="-o ServerAliveInterval=60" -i inventory install.yml
30 |
31 | self.params = {
32 | debug: false
33 | }
34 | end
35 |
36 | # Pass additional options to Ansible
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
39 | def options
40 | x = extra_vars.each_with_object('--extra-vars=\'') { |kv, a| a << "#{kv.first}=\"#{kv.last}\" " }.strip+'\'' if extra_vars unless extra_vars.empty?
41 | # can test with configure { |config| config.extra_vars.clear }
42 |
43 | [x, '--ssh-extra-args=\'-o UserKnownHostsFile=/dev/null\'']*' '
44 | end
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
49 | def to_s(cmd)
50 | entire_cmd = [env.each_with_object([]) { |kv, a| a << kv*'=' } * ' ', cmd, options]*' '
51 | puts entire_cmd if params[:debug]
52 | entire_cmd
53 | end
54 | end
55 |
56 | # Create and yield configuration
57 | # @return [Config, DefaultConfig] the configuration
58 | def configure
59 | @config ||= DefaultConfig.new
60 | yield(@config) if block_given?
61 |
62 | # allow chaining if block given
63 | block_given? ? self : @config
64 | end
65 |
66 | # accessor for config
67 | # @return [DefaultConfig] the configuration
68 | def config
69 | @config || configure
70 | end
71 | end
72 | end
--------------------------------------------------------------------------------
/lib/ansible/output.rb:
--------------------------------------------------------------------------------
1 | require 'strscan'
2 | require 'erb'
3 |
4 | module Ansible
5 | # Output module provides formatting of Ansible output
6 | module Output
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" => 'Grey'
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 = %{}
39 | CLOSE_SPAN_TAG = %{}
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
69 | else
70 | @strscan.scan IGNORED_OUTPUT
71 | end
72 | end
73 |
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 | %{}
88 | end
89 | end
90 | end
91 | end
92 | end
--------------------------------------------------------------------------------
/lib/ansible/playbook.rb:
--------------------------------------------------------------------------------
1 | require 'ansible/config'
2 | require 'ansible/safe_pty'
3 |
4 | module Ansible
5 | # Ansible Playbook methods
6 | module PlaybookMethods
7 | # executable that runs Ansible Playbooks
8 | BIN = 'ansible-playbook'
9 |
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
15 | `#{config.to_s "#{BIN} #{pb}"}`
16 | end
17 | alias :<< :playbook
18 |
19 | # Stream execution of a playbook using PTY because otherwise output is buffered
20 | # @param pb [String] path to playbook
21 | # @param raise_on_failure [Symbol] Specifies if streaming should raise an exception when a Playbook failure occurs.
22 | # Defaults to +:false+, can also be +:during+ to raise as soon as an error occurs or +:after+ to allow all output to stream first.
23 | # @raise [Playbook::Exception] if +raise_on_failure+ is truthy
24 | # @return [Integer] exit status
25 | def stream(pb, raise_on_failure: false)
26 | cmd = config.to_s("#{BIN} #{pb}")
27 | error_at_line = {}
28 |
29 | pid = SafePty.spawn cmd do |r,_,_| # add -vvvv here for verbose
30 | line_num = 0
31 |
32 | until r.eof? do
33 | line = r.gets
34 | line_num += 1
35 |
36 | block_given? ? yield(line) : puts(line)
37 |
38 | # track errors in output by line
39 | if raise_on_failure
40 | case line
41 | when /(fatal|failed): \[/ then error_at_line[line_num] ||= "FAILED: #{line}"
42 | when /ERROR!/, /FAILED!/ then error_at_line[line_num] ||= "ERROR: #{line}"
43 | # allow errors on previous line to be ignored
44 | when /...ignoring/ then error_at_line.delete(line_num-1)
45 | end
46 |
47 | if raise_on_failure == :during
48 | # trigger failure unless it was ignored
49 | fatal_unskipped_error = error_at_line[line_num-1]
50 | raise Playbook::Exception.new(fatal_unskipped_error) if fatal_unskipped_error
51 | end
52 | end
53 | end
54 | end
55 |
56 | if raise_on_failure
57 | # at this point, all output has been streamed
58 | fatal_unskipped_error = error_at_line.first
59 | raise Playbook::Exception.new(fatal_unskipped_error.last) if fatal_unskipped_error
60 | end
61 |
62 | pid
63 | end
64 | end
65 |
66 | # Provides static access to Playbook methods
67 | module Playbook
68 | include Config
69 | include PlaybookMethods
70 |
71 | extend self
72 |
73 | # Run playbook, returning output
74 | # @param pb [String] path to playbook
75 | # @return [String] output
76 | alias :run :playbook
77 |
78 | # Exception to represent Playbook failures
79 | class Exception < RuntimeError; end
80 | end
81 | end
82 |
--------------------------------------------------------------------------------
/lib/ansible/safe_pty.rb:
--------------------------------------------------------------------------------
1 | require 'pty'
2 |
3 | # Wrapper for PTY pseudo-terminal
4 | module Ansible::SafePty
5 | # Spawns process for command
6 | # @param command [String] command
7 | # @return [Integer] exit status
8 | def self.spawn(command)
9 |
10 | PTY.spawn(command) do |r,w,p|
11 | begin
12 | yield r,w,p if block_given?
13 | rescue Errno::EIO
14 | nil # ignore Errno::EIO: Input/output error @ io_fillbuf
15 | ensure
16 | Process.wait p
17 | end
18 | end
19 |
20 | $?.exitstatus
21 | end
22 | end
--------------------------------------------------------------------------------
/lib/ansible/shortcuts.rb:
--------------------------------------------------------------------------------
1 | module Ansible
2 | extend self
3 |
4 | # shortcut for executing an Ad-Hoc command
5 | # @param cmd [String] the command-line to pass
6 | # @see AdHoc#run
7 | def [](cmd)
8 | AdHoc.run cmd
9 | end
10 |
11 | # shortcut to run a Playbook, streaming the output
12 | # @param cmd [String] the command-line to pass
13 | # @see Playbook#stream
14 | def <<(cmd)
15 | Playbook.stream cmd
16 | end
17 | end
18 | A = Ansible unless defined?(A)
--------------------------------------------------------------------------------
/lib/ansible/version.rb:
--------------------------------------------------------------------------------
1 | module Ansible
2 | VERSION = '0.3.0'
3 | end
4 |
--------------------------------------------------------------------------------
/spec/ansible/ad_hoc_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | module Ansible
4 | describe AdHoc do
5 | before(:all) do
6 | disable_host_key_checking
7 | end
8 |
9 | describe '.run' do
10 | it 'can execute a basic ad-hoc Ansible command on localhost' do
11 | expect(AdHoc.run 'all -i localhost, --list-hosts').to match /localhost/
12 | end
13 | end
14 |
15 | describe '.list_hosts' do
16 | it 'can list hosts for an inline inventory' do
17 | inline_inv = %w(localhost 99.99.99.99)
18 | cmd = "all -i #{inline_inv*','}, --list-hosts"
19 |
20 | expect(AdHoc.list_hosts cmd).to match inline_inv
21 | end
22 | end
23 |
24 | describe '.parse_host_vars' do
25 | it 'can parse and return default host vars' do
26 | host_vars = AdHoc.parse_host_vars 'all', 'localhost,'
27 | expect(host_vars['inventory_hostname']).to match 'localhost'
28 | end
29 | end
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/spec/ansible/ansible_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Ansible do
4 | it 'has a version number' do
5 | expect(Ansible::Config::VERSION).not_to be nil
6 | end
7 |
8 | it 'can configure environment variables' do
9 | expect(Ansible.config.to_s '').not_to include 'SOME_ENV_VAR'
10 |
11 | expect {
12 | Ansible.configure { |config| config.env['SOME_ENV_VAR'] = 'False' }
13 | }.to change { Ansible.config.env['SOME_ENV_VAR'] }.from(nil).to('False')
14 |
15 | expect(Ansible.config.to_s '').to include 'SOME_ENV_VAR=False'
16 | end
17 |
18 | pending 'check Config params debug output' do
19 | fail
20 | end
21 |
22 | before { suppress_output }
23 | it 'can run via shortcut methods when enabled' do
24 | Ansible.enable_shortcuts!
25 | disable_host_key_checking
26 |
27 | cmd = '-i localhost, spec/fixtures/mock_playbook.yml'
28 | expect(A << cmd).to be_a Integer
29 | expect(A['all -i localhost, --list-hosts']).to match /localhost/
30 | end
31 |
32 | before { suppress_output }
33 | it 'can be included' do
34 | module Nodes
35 | include Ansible
36 |
37 | extend self
38 |
39 | def install!(ip)
40 | stream ['-i', ip, 'spec/fixtures/mock_playbook.yml']*' '
41 | end
42 | end
43 |
44 | expect(Nodes.install! 'localhost,').to be_a Integer
45 | end
46 | end
--------------------------------------------------------------------------------
/spec/ansible/output_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | module Ansible
4 | describe Output do
5 | describe '.to_html' do
6 | context 'can convert ANSI escape sequence colours to HTML styles' do
7 | it 'ignores text without escape sequences' do
8 | output = "Plain\nText\nHere"
9 | expect(Output.to_html output).to eq output
10 | end
11 |
12 | it 'opens a span tag with style when the appropriate sequence is detected' do
13 | output = "\e[31mRed"
14 | expect(Output.to_html output).to match /Red/
15 | end
16 |
17 | it 'ignores unstyled text before an escape sequence' do
18 | output = "Default \e[31mRed"
19 | expect(Output.to_html output).to match /Default Red/
20 | end
21 |
22 | it 'closes a span tag when the appropriate sequence is detected' do
23 | output = "\e[32mGreen\e[0m"
24 | expect(Output.to_html output).to eq %{Green}
25 | end
26 |
27 | it 'ignores unstyled text after an escape sequence' do
28 | output = "\e[90mGrey\e[0mDefault"
29 | expect(Output.to_html output).to eq %{GreyDefault}
30 | end
31 |
32 | it 'ignores newlines' do
33 | output = "\e[32mGreen\e[0m\n"
34 | expect(Output.to_html output).to eq %{Green\n}
35 | end
36 |
37 | it 'ignores tags left open' do
38 | output = "\e[0m\n\e[0;32mGreen\e[0m\n\e[0;34mBlue"
39 | expect(Output.to_html output).to eq %{\nGreen\nBlue}
40 | end
41 |
42 | it 'handles bold output alongside colour with dual styles in a single tag' do
43 | output = "\e[1;35mBold Magenta\e[0m"
44 | expect(Output.to_html output).to eq %{Bold Magenta}
45 | end
46 |
47 | it 'scrubs unsupported escape sequences' do
48 | output = "\e[38;5;118mBright Green - unsupported\e[0m"
49 | expect(Output.to_html output).to eq "Bright Green - unsupported"
50 | end
51 |
52 | it 'handles some malformed cases (missing semicolon)' do
53 | output = "\e[132mBright Green"
54 | expect(Output.to_html output).to eq 'Bright Green'
55 | end
56 |
57 | # This code may be entirely unreachable as regexp appears to be very specific
58 | it 'handles situations where no style attribute should be added to the tag' do
59 | output = "\e[0;99Nothing\e[0m"
60 |
61 | s = instance_double("StringScanner")
62 | allow(s).to receive(:eos?).and_return(false, true)
63 | allow(s).to receive(:scan).and_return(true, '')
64 | allow(s).to receive(:[]).and_return(nil, nil)
65 |
66 | class_double("StringScanner", new: s).as_stubbed_const
67 |
68 | expect(Output.to_html output).to match //
69 | end
70 |
71 | it 'correctly formats output of a streamed playbook' do
72 | output = ''
73 | Ansible.stream('-i localhost, spec/fixtures/mock_playbook.yml') do |line|
74 | output << Ansible::Output.to_html(line)
75 | end
76 |
77 | expect(output).to match /ok/
78 | end
79 |
80 | it 'includes original stream content alongside formatted output of a streamed playbook' do
81 | output = 'Some tag
'
82 | Ansible.stream('-i localhost, spec/fixtures/mock_playbook.yml') do |line|
83 | Ansible::Output.to_html(line, output)
84 | end
85 |
86 | expect(output).to match /^Some tag<\/h1>/
87 | expect(output).to match /ok/
88 | end
89 |
90 | context 'for a non-existent playbook' do
91 | it 'raises on failures when requested' do
92 | expect {
93 | Ansible.stream('-i localhost, does_not_exist.yml', raise_on_failure: :during) { next }
94 | }.to raise_error(Ansible::Playbook::Exception, /ERROR! the playbook: does_not_exist.yml could not be found/)
95 | end
96 |
97 | output = ''
98 |
99 | it 'does not handle failures otherwise' do
100 | expect {
101 | Ansible.stream('-i localhost, does_not_exist.yml') do |line|
102 | output << Ansible::Output.to_html(line)
103 | end
104 | }.not_to raise_error
105 | end
106 |
107 | it 'outputs an HTML error message' do
108 | expect(output).to match /ERROR! the playbook: does_not_exist.yml could not be found<\/span>/
109 | end
110 | end
111 | end
112 | end
113 | end
114 | end
--------------------------------------------------------------------------------
/spec/ansible/playbook_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | module Ansible
4 | describe Playbook do
5 | before(:all) do
6 | disable_host_key_checking
7 | end
8 |
9 | describe '.run' do
10 | it 'can execute a basic Ansible Playbook command on localhost' do
11 | expect(Playbook.run '-i localhost, spec/fixtures/mock_playbook.yml --list-hosts').to match /localhost/
12 | end
13 |
14 | before { suppress_output }
15 | it 'can execute a basic Ansible Playbook' do
16 | expect(Playbook.run '-i localhost, spec/fixtures/mock_playbook.yml').to match /TASK(.?) \[Test task\]/
17 | end
18 | end
19 |
20 | describe '.stream' do
21 | it 'can stream the output from execution of an Ansible Playbook' do
22 | cmd = '-i localhost, spec/fixtures/mock_playbook.yml'
23 |
24 | expect { |b| Playbook.stream cmd, &b }.to yield_control
25 | expect(Playbook.stream(cmd) { |l| next }).to be_a Integer
26 | expect(Playbook.stream(cmd) { |l| break l if l[/PLAY \[Testing Playbook\]/] }).to match /Testing Playbook/
27 | end
28 |
29 | context 'for a non-existent playbook' do
30 | it 'raises an error when requested' do
31 | expect {
32 | Playbook.stream('-i localhost, file_not_found.yml --list-hosts',
33 | raise_on_failure: :during) { next }
34 | }.to raise_exception(Playbook::Exception, /could not be found/)
35 | end
36 |
37 | it 'provides output otherwise' do
38 | expect {
39 | Playbook.stream('-i localhost, file_not_found.yml --list-hosts')
40 | }.to output(/could not be found/).to_stdout
41 | end
42 | end
43 |
44 | # TODO add this for Ad-Hoc
45 | context 'upon a fatal outcome (unreachable node)' do
46 | it 'raises an error when requested and ceases output' do
47 | output = ""
48 |
49 | expect {
50 | Playbook.stream('-i localhost, spec/fixtures/fail_playbook.yml',
51 | raise_on_failure: :during) { |line| output << line }
52 | }.to raise_exception(Playbook::Exception, (/FAILED/ && /fatal/)) # && /UNREACHABLE/
53 |
54 | expect(output).to_not include('PLAY RECAP')
55 | end
56 |
57 | it 'provides output otherwise' do # { |l| next }
58 | expect {
59 | Playbook.stream('-i localhost, spec/fixtures/fail_playbook.yml')
60 | }.to output(/PLAY RECAP/).to_stdout
61 | end
62 | end
63 |
64 | context 'continues to stream output despite failures' do
65 | it 'by default' do
66 | expect {
67 | Playbook.stream('-i localhost, spec/fixtures/fail_playbook.yml')
68 | }.to output(/PLAY RECAP/).to_stdout
69 | end
70 |
71 | it 'when raise_on_failure is set to :after' do
72 | expect {
73 | Playbook.stream('-i localhost, spec/fixtures/fail_playbook.yml', raise_on_failure: :after)
74 | }.to raise_exception(Playbook::Exception).and output(/PLAY RECAP/).to_stdout
75 | end
76 |
77 | it 'but not when raise_on_failure is :during' do
78 | output = ""
79 |
80 | expect {
81 | Playbook.stream('-i localhost, spec/fixtures/fail_playbook.yml',
82 | raise_on_failure: :during) { |line| output << line }
83 | }.to raise_exception(Playbook::Exception)
84 |
85 | expect(output).to_not include('PLAY RECAP')
86 | end
87 | end
88 |
89 | it 'continues to stream output despite failures when requested' do
90 | expect {
91 | Playbook.stream('-i localhost, spec/fixtures/fail_playbook.yml', raise_on_failure: :after)
92 | }.to raise_exception(Playbook::Exception).and output(/PLAY RECAP/).to_stdout
93 | end
94 |
95 | context 'where ignore_errors is set for tasks' do
96 | it 'skips a single failure when ignored' do
97 | expect {
98 | Playbook.stream('-i localhost, spec/fixtures/ignored_errors_playbook.yml', raise_on_failure: :during) { |l| next }
99 | }.not_to raise_exception
100 | end
101 |
102 | it 'skips multiple failures when they are ignored' do
103 | expect {
104 | Playbook.stream('-i localhost, spec/fixtures/ignored_errors_playbook.yml', raise_on_failure: :during) { |l| next }
105 | }.not_to raise_exception
106 | end
107 |
108 | it 'skips failures when they are ignored, but still reports later failures' do
109 | expect {
110 | Playbook.stream('-i localhost, spec/fixtures/ignored_error_then_failure_playbook.yml',
111 | raise_on_failure: :during) { |l| next }
112 | }.to raise_exception(Playbook::Exception)
113 | end
114 |
115 | it 'skips failures when they are ignored, but still reports earlier failures' do
116 | expect {
117 | Playbook.stream('-i localhost, spec/fixtures/failure_then_ignored_error_playbook.yml',
118 | raise_on_failure: :during) { |l| next }
119 | }.to raise_exception(Playbook::Exception)
120 | end
121 | end
122 |
123 | it 'defaults to standard output when streaming an Ansible Playbook if no block is given' do
124 | expect {
125 | Playbook.stream '-i localhost, spec/fixtures/mock_playbook.yml'
126 | }.to output(/Test task/).to_stdout
127 | end
128 |
129 | it 'returns a warning as part of the output when the inventory does not exist' do
130 | # TODO should probably raise an error for this behaviour (perhaps switch to pending)
131 | expect { Playbook.stream '-i localhost spec/fixtures/mock_playbook.yml' }.to output(/Unable to parse|Host file not found/).to_stdout
132 | end
133 | end
134 | end
135 | end
--------------------------------------------------------------------------------
/spec/fixtures/fail_playbook.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: Testing Playbook
4 | hosts: all
5 | gather_facts: no
6 | connection: ssh
7 |
8 | tasks:
9 | - name: Test task
10 | ping:
--------------------------------------------------------------------------------
/spec/fixtures/failure_then_ignored_error_playbook.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: Testing Playbook
4 | hosts: all
5 | gather_facts: no
6 | connection: local
7 |
8 | tasks:
9 | - name: Notice that this file does not exist
10 | shell: cat non_existent_file_20200309b
11 | - name: Ignore that this file does not exist
12 | shell: cat non_existent_file_20200309a
13 | ignore_errors: yes
--------------------------------------------------------------------------------
/spec/fixtures/ignored_error_playbook.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: Ignore single failure
4 | hosts: all
5 | gather_facts: no
6 | connection: local
7 |
8 | tasks:
9 | - name: Ignore that this file does not exist
10 | shell: cat non_existent_file_20200309a
11 | ignore_errors: yes
--------------------------------------------------------------------------------
/spec/fixtures/ignored_error_then_failure_playbook.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: Testing Playbook
4 | hosts: all
5 | gather_facts: no
6 | connection: local
7 |
8 | tasks:
9 | - name: Ignore that this file does not exist
10 | shell: cat non_existent_file_20200309a
11 | ignore_errors: yes
12 | - name: Notice that this file does not exist
13 | shell: cat non_existent_file_20200309b
--------------------------------------------------------------------------------
/spec/fixtures/ignored_errors_playbook.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: Ignore multiple failures
4 | hosts: all
5 | gather_facts: no
6 | connection: local
7 |
8 | tasks:
9 | - name: Ignore that this file does not exist (skipped failure 1)
10 | shell: cat non_existent_file_20200309a
11 | ignore_errors: yes
12 | - name: Ignore that this file does not exist (skipped failure 2)
13 | shell: cat non_existent_file_20200309b
14 | ignore_errors: yes
--------------------------------------------------------------------------------
/spec/fixtures/mock_playbook.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: Testing Playbook
4 | hosts: all
5 | gather_facts: no
6 | connection: local
7 |
8 | tasks:
9 | - name: Test task
10 | register: test_msg
11 | changed_when: "'Test' in test_msg.stderr"
12 | shell: echo 'Test'
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2 | require 'coveralls'
3 | Coveralls.wear!
4 |
5 | require 'ansible-wrapper'
6 |
7 | def disable_host_key_checking
8 | Ansible.configure { |config| config.env['ANSIBLE_HOST_KEY_CHECKING'] = 'False' }
9 | end
10 |
11 | def suppress_output
12 | allow($stdout).to receive(:puts)
13 | allow($stdout).to receive(:write)
14 | end
--------------------------------------------------------------------------------