kitchen-ansiblepush 0.3.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 82964df38e42759e837ed505d7f760afaff977cb
4
+ data.tar.gz: 75caa47b0cbc04aaf182fc12e1e6fecd5364b658
5
+ SHA512:
6
+ metadata.gz: 1cf540b68aa64e6d803f41f7f8345f483e907552bf8477d349998958864b5445da5fc3760a1002c8f23aec5b55bb1f036ee6f86dff142a10f9df70776f7ceff6
7
+ data.tar.gz: f0f45bd5780a607f2c1fbe6bc1cdbe3b3abe43d719164ef56ead52f6c31ce175c6ace4fd04445e4c7eb9cfb51bb3e90d7a2b3017b352d05b81f83c3a107f3cc2
data/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # kitchen-ansiblepush
2
+
3
+ A test-kitchen plugin that adds the support for ansible in push mode
4
+
5
+ ## TODO
6
+ * Tests
7
+
8
+ ## Intro
9
+ This kitchen plugin adds ansible as a provisioner in push mode. Ansible will run from your host rather than run from guest machines.
10
+
11
+ ## How to install
12
+
13
+ ### Ruby gem
14
+ ```
15
+ gem install kitchen-ansiblepush
16
+ ```
17
+
18
+ ### To install from code
19
+ ```
20
+ git clone git@github.com:ahelal/kitchen-ansiblepush.git
21
+ cd kitchen-ansiblepush
22
+ gem build kitchen-ansiblepush.gemspec
23
+ sudo gem install kitchen-ansiblepush-<version>.gem
24
+ ```
25
+
26
+ ## kitchen.yml Options
27
+ ```yaml
28
+ provisioner :
29
+ ## required options
30
+ name : ansible_push
31
+ playbook : "../../plays/web.yml" # Path to Play yaml
32
+ ##
33
+ ## Optional argument
34
+ ansible_config : "/path/to/ansible/ansible.cfg" # path to ansible config file
35
+ verbose : "vvvv" # verbose level v, vv, vvv, vvvv
36
+ diff : true # print file diff
37
+ mygroup : "web" # ansible group
38
+ raw_arguments : "--timeout=200"
39
+ extra_vars : "@vars.yml"
40
+ tags : [ "that", "this" ]
41
+ skip_tags : [ "notme", "orme" ]
42
+ start_at_task : [ "five" ]
43
+ # Hash of other groups
44
+ groups :
45
+ db :
46
+ - db01
47
+ sudo : true
48
+ sudo_user : root
49
+ remote_user : ubuntu
50
+ private_key : "/path..../id_rsa"
51
+ ask_vault_pass : true
52
+ vault_password_file : "/..../file"
53
+ host_key_checking : false
54
+ generate_inv : true
55
+
56
+ ```
57
+ ## idempotency test
58
+ If you want to check your code is idempotent you can use the idempotency_test. Essentially, this will run Ansible twice and check nothing changed in the next run. If something changed it will list the tasks. Note: If your using Ansible callback in your config this might conflict.
59
+ ```yaml
60
+ idempotency_test: True
61
+ ```
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env ruby
2
+ require 'yaml'
3
+ require 'json'
4
+ require 'kitchen-ansible/util-inventory.rb'
5
+
6
+ all = []
7
+ groups = Hash.new
8
+ hosts = Hash.new
9
+ if File.exist?(TEMP_GROUP_FILE)
10
+ groups = YAML::load_file TEMP_GROUP_FILE
11
+ end
12
+
13
+ Dir.glob(TEMP_INV_DIR + '/ansiblepush_host_*.yml') do |inv_yml|
14
+ vm = YAML::load_file inv_yml
15
+ vm.each do |host, host_attr|
16
+ if host_attr["mygroup"]
17
+ if host_attr["mygroup"].is_a?(Hash)
18
+ host_attr["mygroup"].each do | group |
19
+ groups[group] ||= []
20
+ groups[group] << host
21
+ end
22
+ elsif host_attr["mygroup"].kind_of?(String)
23
+ groups[host_attr["mygroup"]] ||= []
24
+ groups[host_attr["mygroup"]] << host
25
+ end
26
+ end
27
+ host_attr.delete("mygroup")
28
+ hosts[host] = host_attr
29
+ all << host
30
+ end
31
+ end
32
+
33
+ inventory = { "all" => all, "_meta" => { "hostvars" => hosts }}
34
+
35
+
36
+
37
+ inventory = groups.merge(inventory)
38
+
39
+ # Print our inventory
40
+ puts JSON.pretty_generate(inventory)
@@ -0,0 +1,97 @@
1
+ import json
2
+ import os
3
+ import errno
4
+
5
+ change_dir = "/tmp/kitchen_ansible_callback"
6
+ change_file = change_dir + "/changes"
7
+
8
+ class CallbackModule(object):
9
+ def __init__(self):
10
+ if not os.path.exists(change_dir):
11
+ os.makedirs(change_dir)
12
+
13
+ try:
14
+ os.remove(change_file)
15
+ except OSError as e: # this would be "except OSError, e:" before Python 2.6
16
+ if e.errno != errno.ENOENT: # errno.ENOENT = no such file or directory
17
+ raise # re-raise exception if a different error occured
18
+
19
+
20
+
21
+ def write_changed_to_file(self, host, res, name=None):
22
+ changed_data = dict()
23
+ invocation = res.get("invocation", {})
24
+ changed_data["changed_module_args"] = invocation.get("module_args", "")
25
+ changed_data["changed_module_name"] = invocation.get("module_name", "")
26
+ changed_data["host"] = host
27
+ if name:
28
+ changed_data["task_name"] = name
29
+ changed_data["changed_msg"] = res.get("msg", "")
30
+
31
+ try:
32
+ with open(change_file, 'at') as the_file:
33
+ the_file.write(json.dumps(changed_data) + "\n")
34
+ except Exception, e:
35
+ print "Ansible callback idempotency: Write to file failed '%s' error:'%s'" % (change_file, e)
36
+ exit(1)
37
+
38
+ def on_any(self, *args, **kwargs):
39
+ pass
40
+
41
+ def runner_on_failed(self, host, res, ignore_errors=False):
42
+ pass
43
+
44
+ def runner_on_ok(self, host, res):
45
+ if res.get("changed"):
46
+ self.write_changed_to_file(host, res, self.current_task)
47
+
48
+ def runner_on_skipped(self, host, item=None):
49
+ pass
50
+
51
+ def runner_on_unreachable(self, host, res):
52
+ pass
53
+
54
+ def runner_on_no_hosts(self):
55
+ pass
56
+
57
+ def runner_on_async_poll(self, host, res, jid, clock):
58
+ pass
59
+
60
+ def runner_on_async_ok(self, host, res, jid):
61
+ pass
62
+
63
+ def runner_on_async_failed(self, host, res, jid):
64
+ pass
65
+
66
+ def playbook_on_start(self):
67
+ pass
68
+
69
+ def playbook_on_notify(self, host, handler):
70
+ pass
71
+
72
+ def playbook_on_no_hosts_matched(self):
73
+ pass
74
+
75
+ def playbook_on_no_hosts_remaining(self):
76
+ pass
77
+
78
+ def playbook_on_task_start(self, name, is_conditional):
79
+ self.current_task = name
80
+
81
+ def playbook_on_vars_prompt(self, varname, private=True, prompt=None, encrypt=None, confirm=False, salt_size=None, salt=None, default=None):
82
+ pass
83
+
84
+ def playbook_on_setup(self):
85
+ pass
86
+
87
+ def playbook_on_import_for_host(self, host, imported_file):
88
+ pass
89
+
90
+ def playbook_on_not_import_for_host(self, host, missing_file):
91
+ pass
92
+
93
+ def playbook_on_play_start(self, name):
94
+ pass
95
+
96
+ def playbook_on_stats(self, stats):
97
+ pass
@@ -0,0 +1,35 @@
1
+ # encoding: utf-8
2
+
3
+ $:.unshift File.expand_path('../lib', __FILE__)
4
+ require 'kitchen-ansible/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "kitchen-ansiblepush"
8
+ gem.version = Kitchen::AnsiblePush::VERSION
9
+ gem.authors = ["Adham Helal"]
10
+ gem.email = ["adham.helal@gmail.com"]
11
+ gem.licenses = ['MIT']
12
+ gem.homepage = "https://github.com/ahelal/kitchen-ansiblepush"
13
+ gem.summary = "ansible provisioner for test-kitchen"
14
+ candidates = Dir.glob("{lib}/**/*") + ['README.md', 'kitchen-ansiblepush.gemspec', 'callback/changes_callback.py']
15
+ gem.files = candidates.sort
16
+ gem.platform = Gem::Platform::RUBY
17
+ gem.require_paths = ['lib']
18
+ gem.executables = ['kitchen-ansible-inventory']
19
+ gem.rubyforge_project = '[none]'
20
+ gem.description = <<-EOF
21
+ == DESCRIPTION:
22
+
23
+ Ansible push Provisioner for Test Kitchen
24
+
25
+ == FEATURES:
26
+
27
+ Supports running ansible in push mode
28
+
29
+ EOF
30
+
31
+ gem.add_runtime_dependency 'test-kitchen'
32
+ gem.add_development_dependency 'rspec'
33
+ gem.add_development_dependency 'pry'
34
+
35
+ end
@@ -0,0 +1,30 @@
1
+ TEMP_INV_DIR = ".kitchen/ansiblepush"
2
+ TEMP_GROUP_FILE = "#{TEMP_INV_DIR}/ansiblepush_groups_inventory.yml"
3
+
4
+
5
+
6
+ def write_instance_inventory(name, host, mygroup, instance_connection_option)
7
+ Dir.mkdir TEMP_INV_DIR if !File.exist?(TEMP_INV_DIR)
8
+ port = instance_connection_option[:port]
9
+ keys = instance_connection_option[:keys]
10
+ user = instance_connection_option[:user]
11
+
12
+ temp_hash = Hash.new
13
+ temp_hash["ansible_ssh_host"] = host
14
+ temp_hash["ansible_ssh_port"] = port if port
15
+ temp_hash["ansible_ssh_private_key_file"] = keys[0] if keys
16
+ temp_hash["ansible_ssh_user"] = user if user
17
+ temp_hash["mygroup"] = mygroup if mygroup
18
+
19
+ host = { name => temp_hash }
20
+ File.open("%s/ansiblepush_host_%s.yml" % [TEMP_INV_DIR, name], "w") do |file|
21
+ file.write host.to_yaml
22
+ end
23
+ end
24
+
25
+ def write_group_inventory(groups)
26
+ Dir.mkdir TEMP_INV_DIR if !File.exist?(TEMP_INV_DIR)
27
+ File.open(TEMP_GROUP_FILE, "w") do |file|
28
+ file.write groups.to_yaml
29
+ end
30
+ end
@@ -0,0 +1,5 @@
1
+ module Kitchen
2
+ module AnsiblePush
3
+ VERSION = "0.3.1"
4
+ end
5
+ end
@@ -0,0 +1,242 @@
1
+ require 'kitchen'
2
+ require 'kitchen/provisioner/base'
3
+ require 'kitchen-ansible/util-inventory.rb'
4
+ require 'json'
5
+
6
+ module Kitchen
7
+
8
+ class Busser
9
+ def non_suite_dirs
10
+ %w{data}
11
+ end
12
+ end
13
+
14
+ module Provisioner
15
+ class AnsiblePush < Base
16
+ kitchen_provisioner_api_version 2
17
+ default_config :ansible_config, nil
18
+ default_config :verbose, nil
19
+ default_config :diff, nil
20
+ default_config :groups, nil
21
+ default_config :extra_vars, nil
22
+ default_config :sudo, nil
23
+ default_config :sudo_user, nil
24
+ default_config :remote_user, nil
25
+ default_config :private_key, nil
26
+ default_config :ask_vault_pass, nil
27
+ default_config :vault_password_file, nil
28
+ default_config :limit, nil
29
+ default_config :tags, nil
30
+ default_config :skip_tags, nil
31
+ default_config :start_at_task, nil
32
+ default_config :host_key_checking, false
33
+ default_config :mygroup, nil
34
+ default_config :playbook, nil
35
+ default_config :generate_inv, true
36
+ default_config :raw_arguments, nil
37
+ default_config :idempotency_test, false
38
+
39
+ # For tests disable if not needed
40
+ default_config :chef_bootstrap_url, "https://www.getchef.com/chef/install.sh"
41
+
42
+ def prepare_command
43
+ validate_config
44
+ prepare_inventory
45
+ complie_config
46
+ end
47
+
48
+ def install_command
49
+ # Must install chef for busser and serverspec to work :(
50
+ info("*************** AnsiblePush install_command ***************")
51
+ omnibus_download_dir = config[:omnibus_cachier] ? "/tmp/vagrant-cache/omnibus_chef" : "/tmp"
52
+ chef_url = config[:chef_bootstrap_url]
53
+ <<-INSTALL
54
+ sh -c '
55
+ #{Util.shell_helpers}
56
+ if [ ! -d "/opt/chef" ]
57
+ then
58
+ echo "-----> Installing Chef Omnibus"
59
+ mkdir -p #{omnibus_download_dir}
60
+ if [ ! -x #{omnibus_download_dir}/install.sh ]
61
+ then
62
+ do_download #{chef_url} #{omnibus_download_dir}/install.sh
63
+ fi
64
+
65
+ sudo sh #{omnibus_download_dir}/install.sh -d #{omnibus_download_dir}
66
+ echo "-----> End Installing Chef Omnibus"
67
+ fi
68
+
69
+ # Fix for https://github.com/test-kitchen/busser/issues/12
70
+ if [ -h /usr/bin/ruby ]; then
71
+ L=$(readlink -f /usr/bin/ruby)
72
+ sudo rm /usr/bin/ruby
73
+ sudo ln -s $L /usr/bin/ruby
74
+ fi
75
+ '
76
+ INSTALL
77
+
78
+ end
79
+
80
+ def run_command
81
+ info("*************** AnsiblePush run ***************")
82
+ exec_command(@command_env, @command, "ansible-playbook")
83
+ # idempotency test
84
+ if config[:idempotency_test]
85
+ info("*************** idempotency test ***************")
86
+ @command_env["ANSIBLE_CALLBACK_PLUGINS"] = "#{File.dirname(__FILE__)}/../../../callback/"
87
+ exec_command(@command_env, @command, "ansible-playbook")
88
+ # Check ansible callback if changes has occured in the second run
89
+ file_path = "/tmp/kitchen_ansible_callback/changes"
90
+ if File.file?(file_path)
91
+ task = 0
92
+ info("idempotency test [Failed]")
93
+ File.open(file_path, "r") do |f|
94
+ f.each_line do |line|
95
+ task += 1
96
+ info(" #{task}> #{line.strip}")
97
+ end
98
+ end
99
+ raise "idempotency test Failed. Number of non idemptent tasks: #{task}"
100
+
101
+ else
102
+ info("idempotency test [passed]")
103
+ end
104
+ end
105
+ info("*************** AnsiblePush end run *******************")
106
+ debug("[#{name}] Converge completed (#{config[:sleep]}s).")
107
+ return nil
108
+ end
109
+
110
+ protected
111
+
112
+ def exec_command(env, command, desc)
113
+ debug("env=%s command=%s" % [env, command] )
114
+ system(env, "#{command}")
115
+ exit_code = $?.exitstatus
116
+ debug("ansible-playbook exit code = #{exit_code}")
117
+ if exit_code.to_i != 0
118
+ raise "%s returned a non zeroo '%s'. Please see the output above." % [ desc, exit_code.to_s ]
119
+ end
120
+ end
121
+
122
+ def prepare_inventory
123
+ @machine_name = instance.to_str.gsub(/[<>]/, '').split("-").drop(1).join("-")
124
+ @instance_connection_option = instance.transport.instance_variable_get(:@connection_options)
125
+ hostname = @instance_connection_option[:hostname]
126
+ debug("instance_connection_option=" + @instance_connection_option.to_s)
127
+ write_instance_inventory(@machine_name, hostname, config[:mygroup], @instance_connection_option)
128
+ end
129
+
130
+ def complie_config()
131
+ debug("compile_config")
132
+ options = []
133
+ options << "--extra-vars=#{self.get_extra_vars_argument}" if config[:extra_vars]
134
+ options << "--sudo" if config[:sudo]
135
+ options << "--sudo-user=#{config[:sudo_user]}" if config[:sudo_user]
136
+ options << "--user=#{config[:remote_user]}" if self.get_remote_user
137
+ options << "--private-key=#{config[:private_key]}" if config[:private_key]
138
+ options << "#{self.get_verbosity_argument}" if config[:verbose]
139
+ options << "--diff" if config[:diff]
140
+ options << "--ask-sudo-pass" if config[:ask_sudo_pass]
141
+ options << "--ask-vault-pass" if config[:ask_vault_pass]
142
+ options << "--vault-password-file=#{config[:vault_password_file]}" if config[:vault_password_file]
143
+ options << "--tags=%s" % self.as_list_argument(config[:tags]) if config[:tags]
144
+ options << "--skip-tags=%s" % self.as_list_argument(config[:skip_tags]) if config[:skip_tags]
145
+ options << "--start-at-task=#{config[:start_at_task]}" if config[:start_at_task]
146
+ options << "--inventory-file=`which kitchen-ansible-inventory`" if config[:generate_inv]
147
+ ##options << "--inventory-file=#{ssh_inv}," if ssh_inv
148
+
149
+ # By default we limit by the current machine,
150
+ if config[:limit]
151
+ options << "--limit=#{as_list_argument(config[:limit])}"
152
+ else
153
+ options << "--limit=#{@machine_name}"
154
+ end
155
+
156
+ #Add raw argument as final thing
157
+ options << config[:raw_arguments] if config[:raw_arguments]
158
+
159
+ @command = (%w(ansible-playbook) << options << config[:playbook]).flatten.join(" ")
160
+ debug("Ansible push command= %s" % @command)
161
+ @command_env = {
162
+ "PYTHONUNBUFFERED" => "1", # Ensure Ansible output isn't buffered
163
+ "ANSIBLE_FORCE_COLOR" => "true",
164
+ "ANSIBLE_HOST_KEY_CHECKING" => "#{config[:host_key_checking]}",
165
+ }
166
+ @command_env["ANSIBLE_CONFIG"]=config[:ansible_config] if config[:ansible_config]
167
+
168
+ info("Ansible push compile conig done")
169
+ end
170
+
171
+ def validate_config()
172
+ if !config[:playbook]
173
+ raise 'No playbook defined. Please specify one in .kitchen.yml'
174
+ end
175
+
176
+ if !File.exist?(config[:playbook])
177
+ raise "playbook '%s' could not be found. Please check path" % config[:playbook]
178
+ end
179
+
180
+ if config[:vault_password_file] and !File.exist?(config[:vault_password_file])
181
+ raise "Vault password '%s' could not be found. Please check path" % config[:vault_password_file]
182
+ end
183
+
184
+ # Validate that extra_vars is either a hash, or a path to an existing file
185
+ if config[:extra_vars]
186
+ extra_vars_is_valid = config[:extra_vars].kind_of?(Hash) || config[:extra_vars].kind_of?(String)
187
+ if config[:extra_vars].kind_of?(String)
188
+ # Accept the usage of '@' (e.g. '@vars.yml' and 'vars.yml' are both supported)
189
+ match_data = /^@?(.+)$/.match(config[:extra_vars])
190
+ extra_vars_path = match_data[1].to_s
191
+ expanded_path = Pathname.new(extra_vars_path).expand_path(Dir.pwd)
192
+ extra_vars_is_valid = expanded_path.exist?
193
+ if extra_vars_is_valid
194
+ @extra_vars = '@' + extra_vars_path
195
+ end
196
+ end
197
+ if !extra_vars_is_valid
198
+ raise "ansible extra_vars is in valid type: %s value: %s" % [config[:extra_vars].class.to_s, config[:extra_vars].to_s]
199
+ end
200
+ end
201
+ info("Ansible push config validated")
202
+ end
203
+
204
+ def get_extra_vars_argument()
205
+ if config[:extra_vars].kind_of?(String) and config[:extra_vars] =~ /^@.+$/
206
+ # A JSON or YAML file is referenced (requires Ansible 1.3+)
207
+ return config[:extra_vars]
208
+ else
209
+ # Expected to be a Hash after config validation. (extra_vars as
210
+ # JSON requires Ansible 1.2+, while YAML requires Ansible 1.3+)
211
+ return config[:extra_vars].to_json
212
+ end
213
+ end
214
+
215
+ def get_remote_user
216
+ if config[:remote_user]
217
+ return config[:remote_user]
218
+ elsif @instance_connection_option[:username]
219
+ config[:remote_user] = @instance_connection_option[:username]
220
+ return @instance_connection_option[:username]
221
+ else
222
+ return nil
223
+ end
224
+ end
225
+
226
+ def as_list_argument(v)
227
+ v.kind_of?(Array) ? v.join(',') : v
228
+ end
229
+
230
+ def get_verbosity_argument
231
+ if config[:verbose].to_s =~ /^v+$/
232
+ # ansible-playbook accepts "silly" arguments like '-vvvvv' as '-vvvv' for now
233
+ return "-#{config[:verbose]}"
234
+ else
235
+ # safe default, in case input strays
236
+ return '-v'
237
+ end
238
+ end
239
+
240
+ end
241
+ end
242
+ end
metadata ADDED
@@ -0,0 +1,102 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kitchen-ansiblepush
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.1
5
+ platform: ruby
6
+ authors:
7
+ - Adham Helal
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-05-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: test-kitchen
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: |+
56
+ == DESCRIPTION:
57
+
58
+ Ansible push Provisioner for Test Kitchen
59
+
60
+ == FEATURES:
61
+
62
+ Supports running ansible in push mode
63
+
64
+ email:
65
+ - adham.helal@gmail.com
66
+ executables:
67
+ - kitchen-ansible-inventory
68
+ extensions: []
69
+ extra_rdoc_files: []
70
+ files:
71
+ - README.md
72
+ - callback/changes_callback.py
73
+ - kitchen-ansiblepush.gemspec
74
+ - lib/kitchen-ansible/util-inventory.rb
75
+ - lib/kitchen-ansible/version.rb
76
+ - lib/kitchen/provisioner/ansible_push.rb
77
+ - bin/kitchen-ansible-inventory
78
+ homepage: https://github.com/ahelal/kitchen-ansiblepush
79
+ licenses:
80
+ - MIT
81
+ metadata: {}
82
+ post_install_message:
83
+ rdoc_options: []
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - '>='
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - '>='
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ requirements: []
97
+ rubyforge_project: '[none]'
98
+ rubygems_version: 2.0.14
99
+ signing_key:
100
+ specification_version: 4
101
+ summary: ansible provisioner for test-kitchen
102
+ test_files: []