kitchen-nodes-lobatoa 0.10.0

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,24 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'github_changelog_generator/task'
3
+ require 'rspec/core/rake_task'
4
+ require 'rubocop/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:test)
7
+
8
+ RuboCop::RakeTask.new(:style) do |task|
9
+ task.options << '--display-cop-names'
10
+ end
11
+
12
+ GitHubChangelogGenerator::RakeTask.new :changelog do |config|
13
+ config.future_release = Kitchen::Provisioner::NODES_VERSION
14
+ config.enhancement_labels = ['enhancement']
15
+ config.bug_labels = ['bug']
16
+ config.exclude_labels = %w(duplicate question invalid wontfix no_changelog)
17
+ config.exclude_tags = [
18
+ 'v0.1.0.dev', 'v0.2.0.dev', 'v0.2.0.dev.1',
19
+ 'v0.2.0.dev.2', 'v0.2.0.dev.3', 'v0.2.0.dev.4',
20
+ 'v0.6.4.dev', 'v0.6.3', 'v0.6.2', 'v0.6.1'
21
+ ]
22
+ end
23
+
24
+ task default: [:test, :style]
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'kitchen/provisioner/nodes_version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'kitchen-nodes-lobatoa'
8
+ spec.version = Kitchen::Provisioner::NODES_VERSION
9
+ spec.authors = ['Matt Wrock']
10
+ spec.email = ['matt@mattwrock.com']
11
+ spec.description = 'A Test Kitchen Provisioner for Chef Nodes'
12
+ spec.summary = spec.description
13
+ spec.homepage = ''
14
+ spec.license = 'Apache 2.0'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = []
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_dependency 'net-ping'
22
+ spec.add_dependency 'win32-security'
23
+ spec.add_dependency 'test-kitchen'
24
+
25
+ spec.add_development_dependency 'bundler', '~> 1.3'
26
+ spec.add_development_dependency 'fakefs', '~> 0.4'
27
+ spec.add_development_dependency 'github_changelog_generator', '1.11.3'
28
+ spec.add_development_dependency 'rake'
29
+ spec.add_development_dependency 'rspec', '~> 3.2'
30
+ spec.add_development_dependency 'rubocop', '~> 0.37', '>= 0.37.1'
31
+ end
@@ -0,0 +1,25 @@
1
+ module Kitchen
2
+ module Provisioner
3
+ # Locates active IPs that are not localhost
4
+ # there are separate implementations for
5
+ # different kitchen transports
6
+ module Finder
7
+ @finder_registry = {}
8
+
9
+ def self.for_transport(transport, state)
10
+ @finder_registry.each do |registered_transport, finder|
11
+ if transport.class <= registered_transport
12
+ return finder.new(transport.connection(state))
13
+ end
14
+ end
15
+ end
16
+
17
+ def self.register_finder(transport, finder)
18
+ @finder_registry[transport] = finder
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ require 'kitchen/provisioner/finder/ssh'
25
+ require 'kitchen/provisioner/finder/winrm'
@@ -0,0 +1,107 @@
1
+ module Kitchen
2
+ module Transport
3
+ class Ssh < Kitchen::Transport::Base
4
+ # Monkey patch of test-kitchen ssh transport
5
+ # that returns stdout
6
+ class Connection < Kitchen::Transport::Base::Connection
7
+ def node_execute(command, &block)
8
+ return if command.nil?
9
+ out, exit_code = node_execute_with_exit_code(command, &block)
10
+
11
+ if exit_code.nonzero?
12
+ raise Transport::SshFailed,
13
+ "SSH exited (#{exit_code}) for command: [#{command}]"
14
+ end
15
+ out
16
+ rescue Net::SSH::Exception => ex
17
+ raise SshFailed, "SSH command failed (#{ex.message})"
18
+ end
19
+
20
+ # rubocop:disable Metrics/AbcSize
21
+ def node_execute_with_exit_code(command)
22
+ exit_code = nil
23
+ out = []
24
+ session.open_channel do |channel|
25
+ channel.request_pty
26
+ channel.exec(command) do |_ch, _success|
27
+ channel.on_data do |_ch, data|
28
+ out << data
29
+ yield data if block_given?
30
+ end
31
+
32
+ channel.on_extended_data do |_ch, _type, data|
33
+ out << data
34
+ yield data if block_given?
35
+ end
36
+
37
+ channel.on_request('exit-status') do |_ch, data|
38
+ exit_code = data.read_long
39
+ end
40
+ end
41
+ end
42
+ session.loop
43
+ [out.join("\n"), exit_code]
44
+ end
45
+ # rubocop:enable Metrics/AbcSize
46
+ end
47
+ end
48
+ end
49
+
50
+ module Provisioner
51
+ module Finder
52
+ # SSH implementation for returning active non-localhost IPs
53
+ class Ssh
54
+ IP4REGEX = /(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/
55
+
56
+ Finder.register_finder(Kitchen::Transport::Ssh, self)
57
+
58
+ def initialize(connection)
59
+ @connection = connection
60
+ end
61
+
62
+ def find_ips
63
+ ips = []
64
+ 5.times do
65
+ begin
66
+ ips = run_ifconfig
67
+ rescue Kitchen::Transport::TransportFailed
68
+ ips = run_ip_addr
69
+ end
70
+ return ips unless ips.empty?
71
+ sleep 0.5
72
+ end
73
+ ips
74
+ end
75
+
76
+ def find_fqdn
77
+ @connection.node_execute('hostname -f').chomp.chomp
78
+ end
79
+
80
+ private
81
+
82
+ def run_ifconfig
83
+ response = @connection.node_execute('/sbin/ifconfig -a')
84
+ ips = []
85
+ response.split(/^\S+/).each do |device|
86
+ next if !device.include?('RUNNING') || device.include?('LOOPBACK')
87
+ next if IP4REGEX.match(device).nil?
88
+ ips << IP4REGEX.match(device)[1]
89
+ end
90
+ ips.compact
91
+ end
92
+
93
+ def run_ip_addr
94
+ response = @connection.node_execute('/sbin/ip -4 addr show')
95
+ ips = []
96
+ response.split(/^[0-9]+: /).each do |device|
97
+ next if device.include?('LOOPBACK') || device.include?('NO-CARRIER')
98
+ next if device == ''
99
+ found_ips = IP4REGEX.match(device)
100
+ ips << IP4REGEX.match(device)[1] unless found_ips.nil?
101
+ end
102
+ ips.compact
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,49 @@
1
+ module Kitchen
2
+ module Transport
3
+ class Winrm < Kitchen::Transport::Base
4
+ # Monkey patch of test-kitchen winrm transport
5
+ # that returns stdout
6
+ class Connection < Kitchen::Transport::Base::Connection
7
+ def node_execute(command, &block)
8
+ unelevated_session.run(command, &block)
9
+ end
10
+ end
11
+ end
12
+ end
13
+
14
+ module Provisioner
15
+ module Finder
16
+ # WinRM implementation for returning active non-localhost IPs
17
+ class Winrm
18
+ Finder.register_finder(Kitchen::Transport::Winrm, self)
19
+
20
+ def initialize(connection)
21
+ @connection = connection
22
+ end
23
+
24
+ def find_ips
25
+ out = @connection.node_execute(
26
+ '(ipconfig) -match \'IPv[46] Address\''
27
+ )
28
+ data = []
29
+ out.stdout.lines.each do |line|
30
+ data << Regexp.last_match[1] if line.chomp =~ /:\s*(\S+)/
31
+ end
32
+ data
33
+ end
34
+
35
+ def find_fqdn
36
+ out = @connection.node_execute <<-EOS
37
+ [System.Net.Dns]::GetHostByName($env:computername) |
38
+ FL HostName |
39
+ Out-String |
40
+ % { \"{0}\" -f $_.Split(':')[1].Trim() }
41
+ EOS
42
+ data = ''
43
+ data = out.stdout.chomp unless out.stdout.nil?
44
+ data
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,164 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: Matt Wrock (<matt@mattwrock.com>)
4
+ #
5
+ # Copyright (C) 2015, Matt Wrock
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the 'License');
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an 'AS IS' BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+
19
+ require 'kitchen'
20
+ require 'kitchen/provisioner/chef_zero'
21
+ require 'kitchen/provisioner/finder'
22
+ require 'kitchen/provisioner/run_list_expansion_from_kitchen'
23
+ require 'net/ping'
24
+ require 'chef/run_list'
25
+
26
+ module Kitchen
27
+ module Provisioner
28
+ # Nodes provisioner for Kitchen.
29
+ #
30
+ # @author Matt Wrock <matt@mattwrock.com>
31
+ class Nodes < ChefZero
32
+ default_config :reset_node_files, false
33
+
34
+ def create_sandbox
35
+ create_node
36
+ ensure
37
+ super
38
+ end
39
+
40
+ def create_node
41
+ FileUtils.mkdir_p(node_dir) unless Dir.exist?(node_dir)
42
+
43
+ node_def = if File.exist?(node_file)
44
+ updated_node_def
45
+ else
46
+ node_template
47
+ end
48
+ return unless node_def
49
+
50
+ File.open(node_file, 'w') do |out|
51
+ out << JSON.pretty_generate(node_def)
52
+ end
53
+ end
54
+
55
+ def updated_node_def
56
+ if config[:reset_node_files]
57
+ node_template
58
+ else
59
+ nil
60
+ end
61
+ end
62
+
63
+ def state_file
64
+ @state_file ||= Kitchen::StateFile.new(
65
+ config[:kitchen_root],
66
+ instance.name
67
+ ).read
68
+ end
69
+
70
+ def ipaddress
71
+ state = state_file
72
+
73
+ if %w(127.0.0.1 localhost).include?(state[:hostname])
74
+ return get_reachable_guest_address(state)
75
+ end
76
+ state[:hostname]
77
+ end
78
+
79
+ def fqdn
80
+ state = state_file
81
+ begin
82
+ [:username, :password].each do |prop|
83
+ state[prop] = instance.driver[prop] if instance.driver[prop]
84
+ end
85
+ Finder.for_transport(instance.transport, state).find_fqdn
86
+ rescue
87
+ nil
88
+ end
89
+ end
90
+
91
+ def recipes
92
+ rl = config[:run_list].map do |item|
93
+ ::Chef::RunList::RunListItem.new item
94
+ end
95
+ rle = RunListExpansionFromKitchen.new(
96
+ chef_environment,
97
+ rl,
98
+ nil,
99
+ config[:roles_path]
100
+ )
101
+ rle.expand
102
+ rle.recipes
103
+ end
104
+
105
+ def chef_environment
106
+ env = '_default'
107
+ if config[:client_rb] && config[:client_rb][:environment]
108
+ env = config[:client_rb][:environment]
109
+ end
110
+ env
111
+ end
112
+
113
+ # rubocop:disable Metrics/AbcSize
114
+ def node_template
115
+ {
116
+ id: instance.name,
117
+ chef_environment: chef_environment,
118
+ automatic: {
119
+ ipaddress: ipaddress,
120
+ platform: instance.platform.name.split('-')[0].downcase,
121
+ fqdn: fqdn,
122
+ recipes: recipes
123
+ },
124
+ normal: config[:attributes],
125
+ run_list: config[:run_list],
126
+ named_run_list: config[:named_run_list]
127
+ }
128
+ end
129
+ # rubocop:enable Metrics/AbcSize
130
+
131
+ def node_dir
132
+ config[:nodes_path] || File.join(config[:test_base_path], 'nodes')
133
+ end
134
+
135
+ def node_file
136
+ File.join(node_dir, "#{instance.name}.json")
137
+ end
138
+
139
+ def get_reachable_guest_address(state)
140
+ active_ips(instance.transport, state).each do |address|
141
+ next if address == '127.0.0.1'
142
+ return address if reachable?(address)
143
+ end
144
+ end
145
+
146
+ def reachable?(address)
147
+ Net::Ping::External.new.ping(address) ||
148
+ Net::Ping::TCP.new(address, 5985).ping ||
149
+ Net::Ping::TCP.new(address, 5986).ping ||
150
+ Net::Ping::TCP.new(address, 22).ping
151
+ end
152
+
153
+ def active_ips(transport, state)
154
+ # inject creds into state for legacy drivers
155
+ [:username, :password].each do |prop|
156
+ state[prop] = instance.driver[prop] if instance.driver[prop]
157
+ end
158
+ ips = Finder.for_transport(transport, state).find_ips
159
+ raise 'Unable to retrieve IPs' if ips.empty?
160
+ ips
161
+ end
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: Matt Wrock (<matt@mattwrock.com>)
4
+ #
5
+ # Copyright (C) 2015, Matt Wrock
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+
19
+ module Kitchen
20
+ # Version string for Nodes Kitchen driver
21
+ module Provisioner
22
+ NODES_VERSION = '0.10.0'.freeze
23
+ end
24
+ end
@@ -0,0 +1,38 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: Matt Wrock (<matt@mattwrock.com>)
4
+ #
5
+ # Copyright (C) 2015, Matt Wrock
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the 'License');
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an 'AS IS' BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+
19
+ require 'chef/role'
20
+
21
+ module Kitchen
22
+ module Provisioner
23
+ # fetches roles from kitchen roles directory
24
+ class RunListExpansionFromKitchen < ::Chef::RunList::RunListExpansion
25
+ def initialize(environment, run_list_items, source = nil, role_dir = nil)
26
+ @role_dir = role_dir
27
+ super(environment, run_list_items, source)
28
+ end
29
+
30
+ def fetch_role(name, included_by)
31
+ role_file = File.join(@role_dir, name)
32
+ ::Chef::Role.from_disk(role_file)
33
+ rescue ::Chef::Exceptions::RoleNotFound
34
+ role_not_found(name, included_by)
35
+ end
36
+ end
37
+ end
38
+ end