controlrepo 1.0.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.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/README.md +274 -0
- data/controlrepo.gemspec +26 -0
- data/lib/controlrepo.rb +323 -0
- data/lib/controlrepo/beaker.rb +185 -0
- data/lib/controlrepo/class.rb +28 -0
- data/lib/controlrepo/group.rb +61 -0
- data/lib/controlrepo/node.rb +43 -0
- data/lib/controlrepo/rake_tasks.rb +150 -0
- data/lib/controlrepo/test.rb +124 -0
- data/lib/controlrepo/testconfig.rb +136 -0
- data/templates/.fixtures.yml.erb +24 -0
- data/templates/Gemfile.erb +5 -0
- data/templates/Rakefile.erb +1 -0
- data/templates/acceptance_test_spec.rb.erb +18 -0
- data/templates/nodeset.yaml.erb +9 -0
- data/templates/spec_helper.rb.erb +7 -0
- data/templates/spec_helper_acceptance.rb.erb +24 -0
- data/templates/test_spec.rb.erb +13 -0
- metadata +162 -0
@@ -0,0 +1,185 @@
|
|
1
|
+
class Controlrepo
|
2
|
+
class Beaker
|
3
|
+
def self.facts_to_vagrant_box(facts)
|
4
|
+
# Gets the most similar vagrant box to the facts set provided, will accept a single fact
|
5
|
+
# se or an array
|
6
|
+
|
7
|
+
if facts.is_a?(Array)
|
8
|
+
returnval = []
|
9
|
+
facts.each do |fact|
|
10
|
+
returnval << self.facts_to_vagrant_box(fact)
|
11
|
+
end
|
12
|
+
return returnval
|
13
|
+
end
|
14
|
+
|
15
|
+
begin
|
16
|
+
if facts['os']['distro']['id'] == 'Ubuntu'
|
17
|
+
os = 'ubuntu'
|
18
|
+
version = facts['os']['distro']['release']['major']
|
19
|
+
end
|
20
|
+
rescue
|
21
|
+
# Do nothing, this is the easiest way to handle the hash bing in different formats
|
22
|
+
end
|
23
|
+
|
24
|
+
begin
|
25
|
+
if facts['os']['distro']['id'] == 'Debian'
|
26
|
+
os = 'Debian'
|
27
|
+
version = facts['os']['distro']['release']['major']
|
28
|
+
end
|
29
|
+
rescue
|
30
|
+
# Do nothing
|
31
|
+
end
|
32
|
+
|
33
|
+
begin
|
34
|
+
if facts['os']['family'] == "RedHat"
|
35
|
+
os = 'centos'
|
36
|
+
version = "#{facts['os']['release']['major']}.#{facts['os']['release']['minor']}"
|
37
|
+
end
|
38
|
+
rescue
|
39
|
+
# Do nothing
|
40
|
+
end
|
41
|
+
|
42
|
+
return "UNKNOWN" unless os.is_a?(String)
|
43
|
+
|
44
|
+
if facts['os']['architecture'] =~ /64/
|
45
|
+
arch = '64'
|
46
|
+
else
|
47
|
+
arch = '32'
|
48
|
+
end
|
49
|
+
|
50
|
+
"puppetlabs/#{os}-#{version}-#{arch}-puppet"
|
51
|
+
end
|
52
|
+
|
53
|
+
# This will take a fact set and return the beaker platform of that machine
|
54
|
+
# This is necissary as beaker needs the platform set up correctly to know which
|
55
|
+
# commands to run when we do stuff. Personally I would prefer beaker to detect the
|
56
|
+
# platform as it would not be that hard, especially once puppet is installed, oh well.
|
57
|
+
def self.facts_to_platform(facts)
|
58
|
+
if facts.is_a?(Array)
|
59
|
+
returnval = []
|
60
|
+
facts.each do |fact|
|
61
|
+
returnval << self.facts_to_platform(fact)
|
62
|
+
end
|
63
|
+
return returnval
|
64
|
+
end
|
65
|
+
|
66
|
+
begin
|
67
|
+
if facts['os']['family'] == 'RedHat'
|
68
|
+
platform = 'el'
|
69
|
+
version = facts['os']['release']['major']
|
70
|
+
end
|
71
|
+
rescue
|
72
|
+
# Do nothing, this is the easiest way to handle the hash bing in different formats
|
73
|
+
end
|
74
|
+
|
75
|
+
begin
|
76
|
+
if facts['os']['distro']['id'] == 'Ubuntu'
|
77
|
+
platform = 'ubuntu'
|
78
|
+
version = facts['os']['distro']['release']['major']
|
79
|
+
end
|
80
|
+
rescue
|
81
|
+
# Do nothing, this is the easiest way to handle the hash bing in different formats
|
82
|
+
end
|
83
|
+
|
84
|
+
begin
|
85
|
+
if facts['os']['distro']['id'] == 'Debian'
|
86
|
+
platform = 'Debian'
|
87
|
+
version = facts['os']['distro']['release']['major']
|
88
|
+
end
|
89
|
+
rescue
|
90
|
+
# Do nothing
|
91
|
+
end
|
92
|
+
|
93
|
+
if facts['os']['architecture'] =~ /64/
|
94
|
+
arch = '64'
|
95
|
+
else
|
96
|
+
arch = '32'
|
97
|
+
end
|
98
|
+
|
99
|
+
"#{platform}-#{version}-#{arch}"
|
100
|
+
end
|
101
|
+
|
102
|
+
# This little method will deploy a Controlrepo object to a host, just using r10k deploy
|
103
|
+
def self.deploy_controlrepo_on(host, repo = Controlrepo.new())
|
104
|
+
require 'beaker-rspec'
|
105
|
+
require 'controlrepo'
|
106
|
+
|
107
|
+
if host.is_a?(Array)
|
108
|
+
hosts.each do |single_host|
|
109
|
+
deploy_controlrepo_on(single_host)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Use a beaker helper to do the install (*nix only)
|
114
|
+
install_r10k_on(host)
|
115
|
+
|
116
|
+
# Use beaker to install git
|
117
|
+
host.install_package('git')
|
118
|
+
|
119
|
+
# copy the file over to the host (Maybe I should be changing the directory here??)
|
120
|
+
scp_to(host,repo.r10k_config_file,'/tmp/r10k.yaml')
|
121
|
+
|
122
|
+
# Do an r10k deploy
|
123
|
+
r10k_deploy(host,{
|
124
|
+
:puppetfile => true,
|
125
|
+
:configfile => '/tmp/r10k.yaml',
|
126
|
+
})
|
127
|
+
end
|
128
|
+
|
129
|
+
# This actually provisions a node and checks that puppet will be able to run and
|
130
|
+
# be idempotent. It hacks the beaker NetworkManager object to do this. The reason
|
131
|
+
# is that beaker is designed to run in the following order:
|
132
|
+
# 1. Spin up nodes
|
133
|
+
# 2. Run all tests
|
134
|
+
# 3. Kill all nodes
|
135
|
+
#
|
136
|
+
# This is not helpful for us. We want to be able to test all of our classes on
|
137
|
+
# all of our nodes, this could be a lot of vms and having them all running at once
|
138
|
+
# would be a real kick in the dick for whatever system was running it.
|
139
|
+
def self.provision_and_test(host,puppet_class,opts = {},repo = Controlrepo.new)
|
140
|
+
opts = {:runs_before_idempotency => 1}.merge(opts)
|
141
|
+
opts = {:check_idempotency => true}.merge(opts)
|
142
|
+
opts = {:deploy_controlrepo => true}.merge(opts)
|
143
|
+
|
144
|
+
|
145
|
+
raise "Hosts must be a single host object, not an array" if host.is_a?(Array)
|
146
|
+
raise "Class must be a single Class [String], not an array" unless puppet_class.is_a?(String)
|
147
|
+
|
148
|
+
# Create our own NWM object that we are going to interact with
|
149
|
+
# Note here that 'options', 'logger' and are exposed within the rspec tests
|
150
|
+
# if this is run outside of that context it will fail
|
151
|
+
network_manager = ::Beaker::NetworkManager.new(options,logger)
|
152
|
+
|
153
|
+
# Hack the network manager to smash our host in there without provisioning
|
154
|
+
network_manager.instance_variable_set(:@hosts,host)
|
155
|
+
|
156
|
+
# Now that we have a working network manager object, we can provision, but only if
|
157
|
+
# we need to, ahhh smart...
|
158
|
+
unless host.up?
|
159
|
+
network_manager.provision
|
160
|
+
network_manager.proxy_package_manager
|
161
|
+
network_manager.validate
|
162
|
+
network_manager.configure
|
163
|
+
end
|
164
|
+
|
165
|
+
if opts[:deploy_controlrepo]
|
166
|
+
# Get the code onto the host
|
167
|
+
Controlrepo::Beaker.deploy_controlrepo_on(host,repo)
|
168
|
+
end
|
169
|
+
|
170
|
+
# Actually run the tests
|
171
|
+
manifest = "include #{puppet_class}"
|
172
|
+
|
173
|
+
opts[:runs_before_idempotency].times do
|
174
|
+
apply_manifest_on(host,manifest,{:catch_failures => true})
|
175
|
+
end
|
176
|
+
|
177
|
+
if opts[:check_idempotency]
|
178
|
+
apply_manifest_on(host,manifest,{:catch_changes => true})
|
179
|
+
end
|
180
|
+
|
181
|
+
network_manager.cleanup
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class Controlrepo
|
2
|
+
class Class
|
3
|
+
@@all = []
|
4
|
+
|
5
|
+
attr_accessor :name
|
6
|
+
def initialize(name)
|
7
|
+
@name = name
|
8
|
+
@@all << self
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.find(class_name)
|
12
|
+
@@all.each do |cls|
|
13
|
+
if class_name.is_a?(Controlrepo::Class)
|
14
|
+
if cls = class_name
|
15
|
+
return cls
|
16
|
+
end
|
17
|
+
elsif cls.name == class_name
|
18
|
+
return cls
|
19
|
+
end
|
20
|
+
end
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.all
|
25
|
+
@@all
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'controlrepo/class'
|
2
|
+
require 'controlrepo/node'
|
3
|
+
|
4
|
+
class Controlrepo
|
5
|
+
class Group
|
6
|
+
@@all = []
|
7
|
+
|
8
|
+
# Work out how to do class veriables so that I can keep track of all the groups easily
|
9
|
+
attr_accessor :name
|
10
|
+
attr_accessor :members
|
11
|
+
|
12
|
+
# You need to pass in an array of strings for members, not objects, it will find the objects
|
13
|
+
# by itself, and yes it will reference them, not just create additional ones, woo!
|
14
|
+
def initialize(name = nil, members = [])
|
15
|
+
@name = name
|
16
|
+
@members = []
|
17
|
+
|
18
|
+
if members.any?
|
19
|
+
member_objects = []
|
20
|
+
members.each do |member|
|
21
|
+
# Try to find the type for each member
|
22
|
+
if Controlrepo::Class.find(member)
|
23
|
+
member_objects << Controlrepo::Class.find(member)
|
24
|
+
elsif Controlrepo::Node.find(member)
|
25
|
+
member_objects << Controlrepo::Node.find(member)
|
26
|
+
else
|
27
|
+
raise "#{member} was not found in the list of nodes or classes!"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Check that they are all the same type
|
32
|
+
if member_objects.all? { |item| item.is_a?(Controlrepo::Class) }
|
33
|
+
type = Controlrepo::Class
|
34
|
+
elsif member_objects.all? { |item| item.is_a?(Controlrepo::Node) }
|
35
|
+
type = Controlrepo::Node
|
36
|
+
else
|
37
|
+
raise 'Groups must contain either all nodes or all classes. Either there was a mix, or something was spelled wrong'
|
38
|
+
end
|
39
|
+
|
40
|
+
# Smash it into the instance variable
|
41
|
+
@members = member_objects
|
42
|
+
end
|
43
|
+
|
44
|
+
# Finally add it to the list of all grops
|
45
|
+
@@all << self
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.find(group_name)
|
49
|
+
@@all.each do |group|
|
50
|
+
if group.name == group_name
|
51
|
+
return group
|
52
|
+
end
|
53
|
+
end
|
54
|
+
nil
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.all
|
58
|
+
@@all
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'controlrepo'
|
2
|
+
|
3
|
+
class Controlrepo
|
4
|
+
class Node
|
5
|
+
@@all = []
|
6
|
+
|
7
|
+
|
8
|
+
attr_accessor :name
|
9
|
+
attr_accessor :beaker_node
|
10
|
+
attr_accessor :fact_set
|
11
|
+
|
12
|
+
def initialize(name)
|
13
|
+
@name = name
|
14
|
+
@beaker_node = nil
|
15
|
+
|
16
|
+
# If we can't find the factset it will fail, so just catch that error and ignore it
|
17
|
+
begin
|
18
|
+
@fact_set = Controlrepo.facts[(Controlrepo.facts_files.index{|facts_file| File.basename(facts_file,'.json') == name})]
|
19
|
+
rescue TypeError
|
20
|
+
@fact_set = nil
|
21
|
+
end
|
22
|
+
@@all << self
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.find(node_name)
|
27
|
+
@@all.each do |node|
|
28
|
+
if node_name.is_a?(Controlrepo::Node)
|
29
|
+
if node = node_name
|
30
|
+
return node
|
31
|
+
end
|
32
|
+
elsif node.name == node_name
|
33
|
+
return node
|
34
|
+
end
|
35
|
+
end
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.all
|
40
|
+
@@all
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'controlrepo'
|
2
|
+
require 'pathname'
|
3
|
+
|
4
|
+
@repo = nil
|
5
|
+
@config = nil
|
6
|
+
|
7
|
+
task :generate_fixtures do
|
8
|
+
repo = Controlrepo.new
|
9
|
+
raise ".fixtures.yml already exits, we won't overwrite because we are scared" if File.exists?(File.expand_path('./.fixtures.yml',repo.root))
|
10
|
+
File.write(File.expand_path('./.fixtures.yml',repo.root),repo.fixtures)
|
11
|
+
end
|
12
|
+
|
13
|
+
task :hiera_setup do
|
14
|
+
repo = Controlrepo.new
|
15
|
+
current_config = repo.hiera_config
|
16
|
+
current_config.each do |key, value|
|
17
|
+
if value.is_a?(Hash)
|
18
|
+
if value.has_key?(:datadir)
|
19
|
+
current_config[key][:datadir] = Pathname.new(repo.hiera_data).relative_path_from(Pathname.new(File.expand_path('..',repo.hiera_config_file))).to_s
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
puts "Changing hiera config from \n#{repo.hiera_config}\nto\n#{current_config}"
|
24
|
+
repo.hiera_config = current_config
|
25
|
+
end
|
26
|
+
|
27
|
+
task :generate_nodesets do
|
28
|
+
require 'controlrepo/beaker'
|
29
|
+
require 'net/http'
|
30
|
+
require 'json'
|
31
|
+
|
32
|
+
repo = Controlrepo.new
|
33
|
+
|
34
|
+
begin
|
35
|
+
Dir.mkdir("#{repo.root}/spec/acceptance")
|
36
|
+
puts "Created #{repo.root}/spec/acceptance"
|
37
|
+
rescue Errno::EEXIST
|
38
|
+
# Do nothing, this is okay
|
39
|
+
end
|
40
|
+
|
41
|
+
begin
|
42
|
+
Dir.mkdir("#{repo.root}/spec/acceptance/nodesets")
|
43
|
+
puts "Created #{repo.root}/spec/acceptance/nodesets"
|
44
|
+
rescue Errno::EEXIST
|
45
|
+
# Do nothing, this is okay
|
46
|
+
end
|
47
|
+
|
48
|
+
facts = repo.facts
|
49
|
+
facts.each do |fact_set|
|
50
|
+
boxname = Controlrepo_beaker.facts_to_vagrant_box(fact_set)
|
51
|
+
platform = Controlrepo_beaker.facts_to_platform(fact_set)
|
52
|
+
response = Net::HTTP.get(URI.parse("https://atlas.hashicorp.com/api/v1/box/#{boxname}"))
|
53
|
+
url = 'URL goes here'
|
54
|
+
|
55
|
+
unless response =~ /404 Not Found/
|
56
|
+
box_info = JSON.parse(response)
|
57
|
+
box_info['current_version']['providers'].each do |provider|
|
58
|
+
if provider['name'] == 'virtualbox'
|
59
|
+
url = provider['original_url']
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Use an ERB template to write the files
|
65
|
+
template_dir = File.expand_path('../../templates',File.dirname(__FILE__))
|
66
|
+
fixtures_template = File.read(File.expand_path('./nodeset.yaml.erb',template_dir))
|
67
|
+
output_file = File.expand_path("spec/acceptance/nodesets/#{fact_set['fqdn']}.yml",repo.root)
|
68
|
+
if File.exists?(output_file) == false
|
69
|
+
File.write(output_file,ERB.new(fixtures_template, nil, '-').result(binding))
|
70
|
+
puts "Created #{output_file}"
|
71
|
+
else
|
72
|
+
puts "#{output_file} already exists, not going to overwrite because scared"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
task :controlrepo_autotest_prep do
|
78
|
+
require 'controlrepo/testconfig'
|
79
|
+
@repo = Controlrepo.new
|
80
|
+
@config = Controlrepo::TestConfig.new("#{@repo.spec_dir}/controlrepo.yaml")
|
81
|
+
|
82
|
+
# Deploy r10k to a temp dir
|
83
|
+
@config.r10k_deploy_local(@repo)
|
84
|
+
|
85
|
+
# Create the other directories we need
|
86
|
+
FileUtils.mkdir_p("#{@repo.tempdir}/spec/classes")
|
87
|
+
FileUtils.mkdir_p("#{@repo.tempdir}/spec/acceptance/nodesets")
|
88
|
+
|
89
|
+
# Copy our nodesets over
|
90
|
+
FileUtils.cp_r("#{@repo.spec_dir}/acceptance/nodesets","#{@repo.tempdir}/spec/acceptance")
|
91
|
+
|
92
|
+
# Create the Rakefile so that we can take advantage of the existing tasks
|
93
|
+
@config.write_rakefile(@repo.tempdir, "spec/classes/**/*_spec.rb")
|
94
|
+
|
95
|
+
# Create spec_helper.rb
|
96
|
+
@config.write_spec_helper("#{@repo.tempdir}/spec",@repo)
|
97
|
+
|
98
|
+
# Create spec_helper_accpetance.rb
|
99
|
+
@config.write_spec_helper_acceptance("#{@repo.tempdir}/spec",@repo)
|
100
|
+
|
101
|
+
# Create Gemfile
|
102
|
+
@config.write_gemfile(@repo.tempdir)
|
103
|
+
|
104
|
+
# Deduplicate and write the tests (Spec and Acceptance)
|
105
|
+
Controlrepo::Test.deduplicate(@config.tests).each do |test|
|
106
|
+
@config.write_spec_test("#{@repo.tempdir}/spec/classes",test)
|
107
|
+
@config.write_acceptance_test("#{@repo.tempdir}/spec/acceptance",test)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Parse the current hiera config, modify, and write it to the temp dir
|
111
|
+
hiera_config = @repo.hiera_config
|
112
|
+
hiera_config.each do |setting,value|
|
113
|
+
if value.is_a?(Hash)
|
114
|
+
if value.has_key?(:datadir)
|
115
|
+
hiera_config[setting][:datadir] = "#{@repo.temp_environmentpath}/#{@config.environment}/#{value[:datadir]}"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
File.write("#{@repo.temp_environmentpath}/#{@config.environment}/hiera.yaml",hiera_config.to_yaml)
|
120
|
+
|
121
|
+
@config.create_fixtures_symlinks(@repo)
|
122
|
+
end
|
123
|
+
|
124
|
+
task :controlrepo_autotest_spec do
|
125
|
+
Dir.chdir(@repo.tempdir) do
|
126
|
+
#`bundle install --binstubs`
|
127
|
+
#`bin/rake spec_standalone`
|
128
|
+
exec("bundle install --binstubs; bundle exec rake spec_standalone")
|
129
|
+
end
|
130
|
+
# TODO: Look at how this outputs and see if it needs to be improved
|
131
|
+
end
|
132
|
+
|
133
|
+
task :controlrepo_spec => [
|
134
|
+
:controlrepo_autotest_prep,
|
135
|
+
:controlrepo_autotest_spec
|
136
|
+
]
|
137
|
+
|
138
|
+
task :r10k_deploy_local do
|
139
|
+
require 'controlrepo/testconfig'
|
140
|
+
@repo = Controlrepo.new
|
141
|
+
@config = Controlrepo::TestConfig.new("#{repo.spec_dir}/controlrepo.yaml")
|
142
|
+
|
143
|
+
# Deploy r10k to a temp dir
|
144
|
+
config.r10k_deploy_local(repo)
|
145
|
+
end
|
146
|
+
|
147
|
+
# TODO: We could use rspec's tagging abilities to choose which if the acceptance tests to run.
|
148
|
+
|
149
|
+
|
150
|
+
|