kitchen-yansible 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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