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