kitchen-yansible 0.0.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: da916e8bba8652a388e29c59ae55a3b284f235079feaf3ab5c5be934abe1aae2
4
+ data.tar.gz: 3cfd7f8c238238cec9d607dbd4e1fe2b5be81983524bce8d57af1f811d90c3d2
5
+ SHA512:
6
+ metadata.gz: 66ff510329b64430d75ae50aba3580e862d434ee9dd5788c47d99b6d201c5a8477b88a5c5b94207b892139758f544feb32efb72a6b9f6b88114eddae1c85ace6
7
+ data.tar.gz: 076f4e1cac5146328c00a9bf261845d6f9815358ad863cdca8383209ab11acd58d9cc254f881ab7bfae9def630bf3a9cfe050f20ae4b095fbbc7954a0e30797e
@@ -0,0 +1,59 @@
1
+ # Author: Eugene Akhmetkhanov <axmetishe+github@gmail.com>
2
+ # Date: 03-01-2020
3
+ #
4
+ # Licensed to the Apache Software Foundation (ASF) under one
5
+ # or more contributor license agreements. See the NOTICE file
6
+ # distributed with this work for additional information
7
+ # regarding copyright ownership. The ASF licenses this file
8
+ # to you under the Apache License, Version 2.0 (the
9
+ # "License"); you may not use this file except in compliance
10
+ # with the License. You may obtain a copy of the License at
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing,
15
+ # software distributed under the License is distributed on an
16
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17
+ # KIND, either express or implied. See the License for the
18
+ # specific language governing permissions and limitations
19
+ # under the License.
20
+
21
+ $LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
22
+ require 'kitchen-yansible/version'
23
+
24
+ Gem::Specification.new do |s|
25
+ s.name = 'kitchen-yansible'
26
+ s.license = 'Apache-2.0'
27
+ s.version = Kitchen::Yansible::VERSION
28
+ s.authors = ['Eugene Akhmetkhanov']
29
+ s.email = ['axmetishe+github@gmail.com']
30
+ s.homepage = 'https://github.com/axmetishe/kitchen-yansible'
31
+ s.summary = 'Yet Another Ansible Test-Kitchen Provisioner'
32
+ s.files = (Dir.glob('{lib}/**/*') + ['kitchen-yansible.gemspec']).sort
33
+ s.platform = Gem::Platform::RUBY
34
+ s.require_paths = ['lib']
35
+ s.rubyforge_project = '[none]'
36
+ s.description = <<-EOF
37
+ Yet Another Ansible Test Kitchen Provisioner
38
+
39
+ Features:
40
+ - Local and remote execution using single provisioner
41
+ - Local Ansible sandbox configuration using Virtualenv
42
+ - Local execution using Ansible from PATH
43
+ - Remote Ansible installation via Pip and Virtualenv
44
+ - Dependency management
45
+ - Path based
46
+ - Git repositories
47
+ - Drivers
48
+ - Docker
49
+ - Vagrant
50
+ - Platforms
51
+ - RHEL-based - CentOS, Fedora, Amazon Linux, Oracle Linux
52
+ - Debian-based - Debian, Ubuntu
53
+ - Windows via PS Remoting (Local executor only)
54
+
55
+ EOF
56
+
57
+ s.add_runtime_dependency 'test-kitchen', '~> 2.0'
58
+ s.add_runtime_dependency 'rugged', '~> 0.25'
59
+ end
@@ -0,0 +1,90 @@
1
+ # Author: Eugene Akhmetkhanov <axmetishe+github@gmail.com>
2
+ # Date: 11-01-2020
3
+ #
4
+ # Licensed to the Apache Software Foundation (ASF) under one
5
+ # or more contributor license agreements. See the NOTICE file
6
+ # distributed with this work for additional information
7
+ # regarding copyright ownership. The ASF licenses this file
8
+ # to you under the Apache License, Version 2.0 (the
9
+ # "License"); you may not use this file except in compliance
10
+ # with the License. You may obtain a copy of the License at
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing,
15
+ # software distributed under the License is distributed on an
16
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17
+ # KIND, either express or implied. See the License for the
18
+ # specific language governing permissions and limitations
19
+ # under the License.
20
+
21
+ require 'rugged'
22
+
23
+ module Kitchen
24
+ module Yansible
25
+ module Tools
26
+ module Dependencies
27
+ def git_clone(name, url, path)
28
+ info("Cloning '#{name}' Git repository.")
29
+ Rugged::Repository.clone_at(url, path, { :ignore_cert_errors => true })
30
+ end
31
+
32
+ def prepare_dependencies(dependencies)
33
+ dependencies.each do |dependency|
34
+ info("Processing '#{dependency[:name]}' dependency.")
35
+ dependency_target_path = File.join(dependencies_tmp_dir, dependency[:name])
36
+ if dependency.key?(:path)
37
+ info('Processing as path type.')
38
+ if File.exist?(dependency[:path])
39
+ copy_dirs(dependency[:path], dependency_target_path)
40
+ else
41
+ warn("Dependency path '#{dependency[:path]}' doesn't exists. Omitting copy operation.")
42
+ end
43
+ end
44
+ if dependency.key?(:repo)
45
+ if dependency[:repo].downcase == 'git'
46
+ info('Processing as Git repository.')
47
+ begin
48
+ repo = Rugged::Repository.new(dependency_target_path)
49
+ if repo.remotes.first.url.eql?(dependency[:url])
50
+ warn("Dependency cloned already.")
51
+ else
52
+ warn("Removing directory #{dependency_target_path} due to repository origin difference.")
53
+ FileUtils.remove_entry_secure(dependency_target_path)
54
+ git_clone(dependency[:name], dependency[:url], dependency_target_path)
55
+ end
56
+ rescue
57
+ if File.exist?(dependency_target_path)
58
+ warn("Dependency path '#{dependency_target_path}' is not a valid Git repository. Removing then.")
59
+ FileUtils.remove_entry_secure(dependency_target_path)
60
+ end
61
+ repo = git_clone(dependency[:name], dependency[:url], dependency_target_path)
62
+ end
63
+
64
+ raw_ref = dependency.key?(:ref) ? dependency[:ref] : 'master'
65
+ begin
66
+ repo.rev_parse(raw_ref)
67
+ rescue
68
+ message = unindent(<<-MSG)
69
+
70
+ ===============================================================================
71
+ Invalid Git reference - #{raw_ref}
72
+ Please check '#{dependency[:name]}' dependency configuration.
73
+ ===============================================================================
74
+ MSG
75
+ raise UserError, message
76
+ end
77
+
78
+ info("Resetting '#{dependency[:name]}' repository to '#{raw_ref}' reference.")
79
+ repo.checkout(raw_ref, {:strategy => :force})
80
+ repo.close
81
+ else
82
+ raise UserError, "Working with '#{dependency[:repo]}' repository is not implemented yet."
83
+ end
84
+ end
85
+ end unless dependencies.nil?
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,111 @@
1
+ # Author: Eugene Akhmetkhanov <axmetishe+github@gmail.com>
2
+ # Date: 07-01-2020
3
+ #
4
+ # Licensed to the Apache Software Foundation (ASF) under one
5
+ # or more contributor license agreements. See the NOTICE file
6
+ # distributed with this work for additional information
7
+ # regarding copyright ownership. The ASF licenses this file
8
+ # to you under the Apache License, Version 2.0 (the
9
+ # "License"); you may not use this file except in compliance
10
+ # with the License. You may obtain a copy of the License at
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing,
15
+ # software distributed under the License is distributed on an
16
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17
+ # KIND, either express or implied. See the License for the
18
+ # specific language governing permissions and limitations
19
+ # under the License.
20
+
21
+ require 'open3'
22
+
23
+ module Kitchen
24
+ module Yansible
25
+ module Tools
26
+ module Exec
27
+ def unindent(s)
28
+ s.gsub(/^#{s.scan(/^[ \t]+(?=\S)/).min}/, '')
29
+ end
30
+
31
+ def check_command(command, args: '')
32
+ "command -v #{command}" + "#{(" #{args}" unless args.empty?)}"
33
+ end
34
+
35
+ def command_exists(command)
36
+ check_command(command, :args => '&>/dev/null')
37
+ end
38
+
39
+ def local_command_path(command, args: '')
40
+ system(check_command(command, args))
41
+ end
42
+
43
+ def local_command_exists(command)
44
+ "#{local_command_path(command, :args => '&>/dev/null')}"
45
+ end
46
+
47
+ def print_cmd_parameters(command, env = {})
48
+ env_vars = []
49
+ env.each { |k,v| env_vars.push("#{k}=#{v}") }
50
+ message = unindent(<<-MSG)
51
+
52
+ ===============================================================================
53
+ Environment:
54
+ #{env_vars.join("\n ")}
55
+ Command line:
56
+ #{command}
57
+ ===============================================================================
58
+ MSG
59
+ debug(message)
60
+ end
61
+
62
+ def print_cmd_error(stderr, proc)
63
+ message = unindent(<<-MSG)
64
+
65
+ ===============================================================================
66
+ Command returned '#{proc.exitstatus}'.
67
+ stderr: '#{stderr.read}'
68
+ ===============================================================================
69
+ MSG
70
+ debug(message)
71
+ raise UserError, message unless proc.success?
72
+ end
73
+
74
+ def execute_local_command(command, env: {}, opts: {}, print_stdout: false, return_stdout: false)
75
+ print_cmd_parameters(command, env)
76
+
77
+ # noinspection RubyUnusedLocalVariable
78
+ Open3.popen3(env, command, opts) { |stdin, stdout, stderr, thread|
79
+ if print_stdout
80
+ while (line = stdout.gets)
81
+ puts line
82
+ end
83
+ end
84
+ proc = thread.value
85
+
86
+ print_cmd_error(stderr, proc)
87
+ return_stdout ? stdout.read : proc.success?
88
+ }
89
+ end
90
+
91
+ # Helpers
92
+ def sudo_env(pm)
93
+ s = @config[:https_proxy] ? "https_proxy=#{@config[:https_proxy]}" : nil
94
+ p = @config[:http_proxy] ? "http_proxy=#{@config[:http_proxy]}" : nil
95
+ n = @config[:no_proxy] ? "no_proxy=#{@config[:no_proxy]}" : nil
96
+ p || s ? "#{sudo('env')} #{p} #{s} #{n} #{pm}" : "#{sudo(pm)}"
97
+ end
98
+
99
+ # Taken from https://github.com/test-kitchen/test-kitchen/blob/master/lib/kitchen/provisioner/base.rb
100
+ def sudo(script)
101
+ "sudo -E #{script}"
102
+ end
103
+
104
+ def detect_platform
105
+ @instance.driver.diagnose[:name] == 'docker' ?
106
+ @instance.driver.diagnose[:platform].to_s : @instance.platform.name.to_s
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,179 @@
1
+ # Author: Eugene Akhmetkhanov <axmetishe+github@gmail.com>
2
+ # Date: 07-01-2020
3
+ #
4
+ # Licensed to the Apache Software Foundation (ASF) under one
5
+ # or more contributor license agreements. See the NOTICE file
6
+ # distributed with this work for additional information
7
+ # regarding copyright ownership. The ASF licenses this file
8
+ # to you under the Apache License, Version 2.0 (the
9
+ # "License"); you may not use this file except in compliance
10
+ # with the License. You may obtain a copy of the License at
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing,
15
+ # software distributed under the License is distributed on an
16
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17
+ # KIND, either express or implied. See the License for the
18
+ # specific language governing permissions and limitations
19
+ # under the License.
20
+
21
+ module Kitchen
22
+ module Yansible
23
+ module Tools
24
+ module Files
25
+ ANSIBLE_INVENTORY = "inventory.yml"
26
+
27
+ def inventory_file
28
+ File.join(instance_tmp_dir, ANSIBLE_INVENTORY)
29
+ end
30
+
31
+ def remote_file_path(file_path, fallback: nil)
32
+ if @config[:remote_executor]
33
+ File.join(@config[:root_path], file_path)
34
+ else
35
+ fallback.nil? ? file_path : fallback
36
+ end
37
+ end
38
+
39
+ def prepare_ansible_config
40
+ if @config[:ansible_config]
41
+ copy_files(@config[:ansible_config], File.join(sandbox_path, @config[:ansible_config]))
42
+ end
43
+ end
44
+
45
+ def prepare_playbook_file
46
+ if @config[:remote_executor]
47
+ copy_files(@config[:playbook], File.join(sandbox_path, @config[:playbook]))
48
+ end
49
+ end
50
+
51
+ def prepare_inventory_file
52
+ if @config[:remote_executor]
53
+ copy_files(inventory_file, File.join(sandbox_path, ANSIBLE_INVENTORY))
54
+ end
55
+ end
56
+
57
+ def generate_sandbox_path(directory)
58
+ path = File.join(sandbox_path, directory)
59
+ Dir.mkdir(path) unless File.exist?(path)
60
+ path
61
+ end
62
+
63
+ def executor_tmp_dir
64
+ if !@executor_tmp_dir && !instance.nil?
65
+ @executor_tmp_dir = File.join(config[:kitchen_root], %w[ .kitchen yansible ])
66
+ end
67
+ Dir.mkdir(@executor_tmp_dir) unless File.exist?(@executor_tmp_dir)
68
+ @executor_tmp_dir
69
+ end
70
+
71
+ def instance_tmp_dir
72
+ if !@instance_tmp_dir && !instance.nil?
73
+ @instance_tmp_dir = File.join(executor_tmp_dir, @instance.name)
74
+ end
75
+ Dir.mkdir(@instance_tmp_dir) unless File.exist?(@instance_tmp_dir)
76
+ @instance_tmp_dir
77
+ end
78
+
79
+ def dependencies_tmp_dir
80
+ if !@dependencies_tmp_dir && !instance.nil?
81
+ @dependencies_tmp_dir = File.join(instance_tmp_dir, 'dependencies')
82
+ end
83
+ Dir.mkdir(@dependencies_tmp_dir) unless File.exist?(@dependencies_tmp_dir)
84
+ @dependencies_tmp_dir
85
+ end
86
+
87
+ def venv_root
88
+ if !@venv_root && !instance.nil?
89
+ @venv_root = File.join(instance_tmp_dir, 'venv')
90
+ end
91
+ @venv_root
92
+ end
93
+
94
+ def generate_inventory(inventory_file, remote: false)
95
+ connection = @instance.transport.instance_variable_get(:@connection_options)
96
+ transport_conf = @instance.transport.diagnose
97
+ host_conn_vars = {}
98
+
99
+ debug("===> Connection options")
100
+ debug(connection.to_s)
101
+ debug("===> Transport options")
102
+ debug(transport_conf.to_s)
103
+ if remote
104
+ debug("Generating inventory stub for execution on remote target")
105
+ host_conn_vars['ansible_connection'] = 'local'
106
+ host_conn_vars['ansible_host'] = 'localhost'
107
+ else
108
+ debug("Generating inventory for execution on local host with remote targets")
109
+ host_conn_vars['ansible_connection'] = transport_conf[:name] if transport_conf[:name]
110
+ host_conn_vars['ansible_password'] = connection[:password] if connection[:password]
111
+
112
+ case transport_conf[:name]
113
+ when 'winrm'
114
+ host_conn_vars['ansible_host'] = URI.parse(connection[:endpoint]).hostname
115
+ host_conn_vars['ansible_user'] = connection[:user] if connection[:user]
116
+ host_conn_vars['ansible_winrm_transport'] = @config[:ansible_winrm_auth_transport] if @config[:ansible_winrm_auth_transport]
117
+ host_conn_vars['ansible_winrm_scheme'] = transport_conf[:winrm_transport] == :ssl ? 'https' : 'http'
118
+ host_conn_vars['ansible_winrm_server_cert_validation'] = @config[:ansible_winrm_cert_validation] if @config[:ansible_winrm_cert_validation]
119
+ when 'ssh'
120
+ host_conn_vars['ansible_host'] = connection[:hostname]
121
+ host_conn_vars['ansible_user'] = connection[:username] if connection[:username]
122
+ host_conn_vars['ansible_port'] = connection[:port] if connection[:port]
123
+ host_conn_vars['ansible_ssh_retries'] = connection[:connection_retries] if connection[:connection_retries]
124
+ host_conn_vars['ansible_private_key_file'] = connection[:keys].first if connection[:keys]
125
+ host_conn_vars['ansible_host_key_checking'] = @config[:ansible_host_key_checking] if @config[:ansible_host_key_checking]
126
+ else
127
+ message = unindent(<<-MSG)
128
+
129
+ ===============================================================================
130
+ Unsupported transport - #{transport_conf[:name]}
131
+ SSH and WinRM transports are allowed.
132
+ ===============================================================================
133
+ MSG
134
+ raise UserError, message
135
+ end
136
+ end
137
+
138
+ # noinspection RubyStringKeysInHashInspection
139
+ inv = { 'all' => { 'hosts' => { @instance.name => host_conn_vars } } }
140
+
141
+ File.open(inventory_file, 'w') do |file|
142
+ file.write inv.to_yaml
143
+ end
144
+ end
145
+
146
+ def copy_files(src, dst, overwrite: true)
147
+ debug("Copy '#{src}' to '#{dst}'")
148
+
149
+ FileUtils.copy_entry(src, dst, remove_destination=overwrite)
150
+ end
151
+
152
+ def copy_dirs(src, dst, reject: '.git')
153
+ expand_path=File.expand_path(src)
154
+ if File.exist?(expand_path)
155
+ debug("Copy '#{src}' to '#{dst}'.")
156
+ debug("'#{src}' expanded to '#{expand_path}'")
157
+ Dir.glob("#{expand_path}/**/{*,.*}").reject{|f| f[reject]}.each do |file|
158
+ target = dst + file.sub(expand_path, '')
159
+ if File.file?(file)
160
+ FileUtils.copy_entry(file, target, remove_destination: true)
161
+ else
162
+ FileUtils.mkdir_p(target) unless File.exist?(target)
163
+ end
164
+ end
165
+ else
166
+ debug("Path '#{src}' doesn't exists. Omitting copy operation.")
167
+ end
168
+ end
169
+
170
+ def copy_dirs_to_sandbox(src, dst: src, reject: '.git')
171
+ dest = generate_sandbox_path(dst)
172
+ debug("'#{src}' => '#{dest}', reject => '#{reject}'.")
173
+
174
+ copy_dirs(src, dest, reject: reject)
175
+ end
176
+ end
177
+ end
178
+ end
179
+ end