controlrepo 1.0.0

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