kitchen-nodes-lobatoa 0.10.0

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