onceover 3.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 +2 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +17 -0
- data/README.md +504 -0
- data/Rakefile +2 -0
- data/bin/onceover +17 -0
- data/controlrepo.gemspec +38 -0
- data/factsets/CentOS-5.11-32.json +263 -0
- data/factsets/CentOS-5.11-64.json +263 -0
- data/factsets/CentOS-6.6-32.json +305 -0
- data/factsets/CentOS-6.6-64.json +342 -0
- data/factsets/CentOS-7.0-64.json +352 -0
- data/factsets/Debian-6.0.10-32.json +322 -0
- data/factsets/Debian-6.0.10-64.json +322 -0
- data/factsets/Debian-7.8-32.json +338 -0
- data/factsets/Debian-7.8-64.json +338 -0
- data/factsets/Ubuntu-12.04-32.json +328 -0
- data/factsets/Ubuntu-12.04-64.json +328 -0
- data/factsets/Ubuntu-14.04-32.json +337 -0
- data/factsets/Ubuntu-14.04-64.json +373 -0
- data/factsets/Windows_Server-2008r2-64.json +183 -0
- data/factsets/Windows_Server-2012r2-64.json +164 -0
- data/lib/onceover/beaker.rb +225 -0
- data/lib/onceover/beaker/spec_helper.rb +70 -0
- data/lib/onceover/class.rb +29 -0
- data/lib/onceover/cli.rb +46 -0
- data/lib/onceover/cli/init.rb +31 -0
- data/lib/onceover/cli/run.rb +72 -0
- data/lib/onceover/cli/show.rb +74 -0
- data/lib/onceover/cli/update.rb +48 -0
- data/lib/onceover/controlrepo.rb +527 -0
- data/lib/onceover/group.rb +85 -0
- data/lib/onceover/logger.rb +31 -0
- data/lib/onceover/node.rb +44 -0
- data/lib/onceover/rake_tasks.rb +113 -0
- data/lib/onceover/runner.rb +90 -0
- data/lib/onceover/test.rb +157 -0
- data/lib/onceover/testconfig.rb +233 -0
- data/templates/.fixtures.yml.erb +24 -0
- data/templates/Rakefile.erb +6 -0
- data/templates/acceptance_test_spec.rb.erb +66 -0
- data/templates/controlrepo.yaml.erb +38 -0
- data/templates/factsets_README.md.erb +7 -0
- data/templates/nodeset.yaml.erb +12 -0
- data/templates/pre_conditions_README.md.erb +24 -0
- data/templates/spec_helper.rb.erb +16 -0
- data/templates/spec_helper_acceptance.rb.erb +1 -0
- data/templates/test_spec.rb.erb +34 -0
- metadata +345 -0
@@ -0,0 +1,29 @@
|
|
1
|
+
class Onceover
|
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?(Onceover::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
|
+
logger.warn "Class #{class_name} not found"
|
22
|
+
nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.all
|
26
|
+
@@all
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/onceover/cli.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'cri'
|
2
|
+
|
3
|
+
class Onceover
|
4
|
+
class CLI
|
5
|
+
def self.command
|
6
|
+
@cmd ||= Cri::Command.define do
|
7
|
+
name 'onceover'
|
8
|
+
usage 'onceover <subcommand> [options]'
|
9
|
+
summary 'Tool for testing Puppet controlrepos'
|
10
|
+
|
11
|
+
flag :h, :help, 'Show help for this command' do |value, cmd|
|
12
|
+
puts cmd.help
|
13
|
+
exit 0
|
14
|
+
end
|
15
|
+
|
16
|
+
flag nil, :trace, 'Display stack traces on application crash'
|
17
|
+
flag :d, :debug, 'Enable debug loging'
|
18
|
+
optional :p, :path, 'Path to the root of the controlrepo'
|
19
|
+
optional nil, :environmentpath, 'Value of environmentpath from puppet.conf'
|
20
|
+
optional nil, :puppetfile, 'Location of the Puppetfile'
|
21
|
+
optional nil, :environment_conf, 'Location of environment.con'
|
22
|
+
optional nil, :facts_dir, 'Directory in which to find factsets'
|
23
|
+
optional nil, :spec_dir, 'Directory in which to find spec tests and config'
|
24
|
+
optional nil, :facts_files, 'List of factset files to use (Overrides --facts_dir)'
|
25
|
+
optional nil, :nodeset_file, 'YAML file containing node definitions'
|
26
|
+
optional nil, :tempdir, 'Temp directory to use, defaults to .controlrepo'
|
27
|
+
optional nil, :manifest, 'Path fo find manifests'
|
28
|
+
optional nil, :controlrepo_yaml, 'Path of controlrepo.yaml'
|
29
|
+
|
30
|
+
run do |opts, args, cmd|
|
31
|
+
puts cmd.help(:verbose => opts[:verbose])
|
32
|
+
exit 0
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Add the help
|
38
|
+
Onceover::CLI.command.add_command(Cri::Command.new_basic_help)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Add all of the other CLI components
|
43
|
+
require 'onceover/cli/show'
|
44
|
+
require 'onceover/cli/run'
|
45
|
+
require 'onceover/cli/init'
|
46
|
+
require 'onceover/cli/update'
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'cri'
|
2
|
+
require 'onceover/controlrepo'
|
3
|
+
require 'onceover/cli'
|
4
|
+
require 'onceover/runner'
|
5
|
+
require 'onceover/testconfig'
|
6
|
+
require 'onceover/logger'
|
7
|
+
|
8
|
+
class Onceover
|
9
|
+
class CLI
|
10
|
+
class Init
|
11
|
+
def self.command
|
12
|
+
@cmd ||= Cri::Command.define do
|
13
|
+
name 'init'
|
14
|
+
usage 'init'
|
15
|
+
summary 'Sets up a controlrepo for testing from scratch'
|
16
|
+
description <<-DESCRIPTION
|
17
|
+
This will generate all of the config files required for the onceover
|
18
|
+
tool to work.
|
19
|
+
DESCRIPTION
|
20
|
+
|
21
|
+
run do |opts, args, cmd|
|
22
|
+
Onceover::Controlrepo.init(Onceover::Controlrepo.new(opts))
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Register itself
|
31
|
+
Onceover::CLI.command.add_command(Onceover::CLI::Init.command)
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'cri'
|
2
|
+
require 'onceover/controlrepo'
|
3
|
+
require 'onceover/cli'
|
4
|
+
require 'onceover/runner'
|
5
|
+
require 'onceover/testconfig'
|
6
|
+
require 'onceover/logger'
|
7
|
+
|
8
|
+
class Onceover
|
9
|
+
class CLI
|
10
|
+
class Run
|
11
|
+
def self.command
|
12
|
+
@cmd ||= Cri::Command.define do
|
13
|
+
name 'run'
|
14
|
+
usage 'run [spec|acceptance]'
|
15
|
+
summary 'Runs either the spec or acceptance tests'
|
16
|
+
description <<-DESCRIPTION
|
17
|
+
This will run the full set of spec or acceptance tests.
|
18
|
+
This includes deploying using r10k and running all custom tests.
|
19
|
+
DESCRIPTION
|
20
|
+
|
21
|
+
optional :t, :tags, 'A list of tags. Only tests with these tags will be run'
|
22
|
+
optional :c, :classes, 'A list of classes. Only tests with these classes will be run'
|
23
|
+
optional :n, :nodes, 'A list of nodes. Only tests with these nodes will be run'
|
24
|
+
|
25
|
+
run do |opts, args, cmd|
|
26
|
+
puts cmd.help(:verbose => opts[:verbose])
|
27
|
+
exit 0
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class Spec
|
33
|
+
def self.command
|
34
|
+
@cmd ||= Cri::Command.define do
|
35
|
+
name 'spec'
|
36
|
+
usage 'spec'
|
37
|
+
summary 'Runs spec tests'
|
38
|
+
|
39
|
+
run do |opts, args, cmd|
|
40
|
+
repo = Onceover::Controlrepo.new(opts)
|
41
|
+
runner = Onceover::Runner.new(repo,Onceover::TestConfig.new(repo.controlrepo_yaml,opts),:spec)
|
42
|
+
runner.prepare!
|
43
|
+
runner.run_spec!
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class Acceptance
|
50
|
+
def self.command
|
51
|
+
@cmd ||= Cri::Command.define do
|
52
|
+
name 'acceptance'
|
53
|
+
usage 'acceptance'
|
54
|
+
summary 'Runs acceptance tests'
|
55
|
+
|
56
|
+
run do |opts, args, cmd|
|
57
|
+
repo = Onceover::Controlrepo.new(opts)
|
58
|
+
runner = Onceover::Runner.new(repo,Onceover::TestConfig.new(repo.controlrepo_yaml,opts),:acceptance)
|
59
|
+
runner.prepare!
|
60
|
+
runner.run_acceptance!
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Register itself
|
70
|
+
Onceover::CLI.command.add_command(Onceover::CLI::Run.command)
|
71
|
+
Onceover::CLI::Run.command.add_command(Onceover::CLI::Run::Spec.command)
|
72
|
+
Onceover::CLI::Run.command.add_command(Onceover::CLI::Run::Acceptance.command)
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'cri'
|
2
|
+
require 'onceover/controlrepo'
|
3
|
+
require 'onceover/cli'
|
4
|
+
require 'onceover/logger'
|
5
|
+
|
6
|
+
class Onceover
|
7
|
+
class CLI
|
8
|
+
class Show
|
9
|
+
def self.command
|
10
|
+
@cmd ||= Cri::Command.define do
|
11
|
+
name 'show'
|
12
|
+
usage 'show [controlrepo|puppetfile]'
|
13
|
+
summary 'Shows the current state things'
|
14
|
+
description <<-DESCRIPTION
|
15
|
+
Shows the state of either the controlrepo or the Puppetfile
|
16
|
+
DESCRIPTION
|
17
|
+
|
18
|
+
run do |opts, args, cmd|
|
19
|
+
# Print out the description
|
20
|
+
puts cmd.help(:verbose => opts[:verbose])
|
21
|
+
exit 0
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Repo
|
27
|
+
def self.command
|
28
|
+
@cmd ||= Cri::Command.define do
|
29
|
+
name 'repo'
|
30
|
+
usage 'repo [options]'
|
31
|
+
summary 'Shows the current state of the Controlrepo'
|
32
|
+
description <<-DESCRIPTION
|
33
|
+
Shows the state of the repo as the tool sees it.
|
34
|
+
Useful for debugging.
|
35
|
+
DESCRIPTION
|
36
|
+
|
37
|
+
run do |opts, args, cmd|
|
38
|
+
# Print out the description
|
39
|
+
puts Onceover::Controlrepo.new(opts).to_s
|
40
|
+
exit 0
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class Puppetfile
|
47
|
+
def self.command
|
48
|
+
@cmd ||= Cri::Command.define do
|
49
|
+
name 'puppetfile'
|
50
|
+
usage 'puppetfile [options]'
|
51
|
+
summary 'Shows the current state of the puppetfile'
|
52
|
+
description <<-DESCRIPTION
|
53
|
+
Shows the state of the puppetfile including current versions and
|
54
|
+
laetst versions of each module. Great for checking for updates.
|
55
|
+
To update all modules run `onceover update puppetfile`. (Hint: once
|
56
|
+
you have done the update, run the tests to make sure nothing breaks.)
|
57
|
+
DESCRIPTION
|
58
|
+
|
59
|
+
run do |opts, args, cmd|
|
60
|
+
# Print out the description
|
61
|
+
Onceover::Controlrepo.new(opts).print_puppetfile_table
|
62
|
+
exit 0
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Register itself
|
72
|
+
Onceover::CLI.command.add_command(Onceover::CLI::Show.command)
|
73
|
+
Onceover::CLI::Show.command.add_command(Onceover::CLI::Show::Repo.command)
|
74
|
+
Onceover::CLI::Show.command.add_command(Onceover::CLI::Show::Puppetfile.command)
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'cri'
|
2
|
+
require 'onceover/controlrepo'
|
3
|
+
require 'onceover/cli'
|
4
|
+
require 'onceover/logger'
|
5
|
+
|
6
|
+
class Onceover
|
7
|
+
class CLI
|
8
|
+
class Update
|
9
|
+
def self.command
|
10
|
+
@cmd ||= Cri::Command.define do
|
11
|
+
name 'update'
|
12
|
+
usage 'update puppetfile'
|
13
|
+
summary 'Updates stuff, currently only the Puppetfile'
|
14
|
+
|
15
|
+
run do |opts, args, cmd|
|
16
|
+
# Print out the description
|
17
|
+
puts cmd.help(:verbose => opts[:verbose])
|
18
|
+
exit 0
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Puppetfile
|
24
|
+
def self.command
|
25
|
+
@cmd ||= Cri::Command.define do
|
26
|
+
name 'puppetfile'
|
27
|
+
usage 'puppetfile'
|
28
|
+
summary 'Update all modules in the Puppetfile'
|
29
|
+
description <<-DESCRIPTION
|
30
|
+
Updates all modules to their latest version and writes that
|
31
|
+
file back onto the system over the original Puppetfile.
|
32
|
+
DESCRIPTION
|
33
|
+
|
34
|
+
run do |opts, args, cmd|
|
35
|
+
# Print out the description
|
36
|
+
Onceover::Controlrepo.new(opts).update_puppetfile
|
37
|
+
exit 0
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Register itself
|
47
|
+
Onceover::CLI.command.add_command(Onceover::CLI::Update.command)
|
48
|
+
Onceover::CLI::Update.command.add_command(Onceover::CLI::Update::Puppetfile.command)
|
@@ -0,0 +1,527 @@
|
|
1
|
+
require 'r10k/puppetfile'
|
2
|
+
require 'erb'
|
3
|
+
require 'json'
|
4
|
+
require 'yaml'
|
5
|
+
require 'find'
|
6
|
+
require 'pathname'
|
7
|
+
require 'onceover/beaker'
|
8
|
+
require 'onceover/logger'
|
9
|
+
include Onceover::Logger
|
10
|
+
|
11
|
+
begin
|
12
|
+
require 'pry'
|
13
|
+
rescue LoadError
|
14
|
+
# We don't care if i'ts not here, this is just used for
|
15
|
+
# debugging sometimes
|
16
|
+
end
|
17
|
+
|
18
|
+
class Onceover
|
19
|
+
class Controlrepo
|
20
|
+
attr_accessor :root
|
21
|
+
attr_accessor :puppetfile
|
22
|
+
attr_accessor :facts_files
|
23
|
+
attr_accessor :environmentpath
|
24
|
+
attr_accessor :role_regex
|
25
|
+
attr_accessor :profile_regex
|
26
|
+
attr_accessor :spec_dir
|
27
|
+
attr_accessor :temp_modulepath
|
28
|
+
attr_accessor :nodeset_file
|
29
|
+
attr_accessor :manifest
|
30
|
+
attr_accessor :tempdir
|
31
|
+
attr_accessor :controlrepo_yaml
|
32
|
+
attr_accessor :opts
|
33
|
+
|
34
|
+
# Create methods on self so that we can access these basic things without
|
35
|
+
# having to actually instantiate the class, I'm debating how much stuff
|
36
|
+
# I should be putting in here, we don't reeeally need to instantiate the
|
37
|
+
# object unless we want to modify it's parameters, so maybe everything.
|
38
|
+
# We shall see...
|
39
|
+
#
|
40
|
+
# And yeah I know this makes little sense, but it will look nicer to type, promise
|
41
|
+
#
|
42
|
+
# Also it's probably pretty memory hungry, but let's be honest, how many
|
43
|
+
# times would be be calling this? If we call it over and over you can just
|
44
|
+
# instantiate it anyway
|
45
|
+
def self.root
|
46
|
+
Onceover::Controlrepo.new.root
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.puppetfile
|
50
|
+
Onceover::Controlrepo.new.puppetfile
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.facts_files
|
54
|
+
Onceover::Controlrepo.new.facts_files
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.classes
|
58
|
+
Onceover::Controlrepo.new.classes
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.roles
|
62
|
+
Onceover::Controlrepo.new.roles
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.profiles
|
66
|
+
Onceover::Controlrepo.new.profiles
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.config
|
70
|
+
Onceover::Controlrepo.new.config
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.facts(filter = nil)
|
74
|
+
Onceover::Controlrepo.new.facts(filter)
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.hiera_config_file
|
78
|
+
Onceover::Controlrepo.new.hiera_config_file
|
79
|
+
end
|
80
|
+
#
|
81
|
+
# End class methods
|
82
|
+
#
|
83
|
+
|
84
|
+
def initialize(opts = {})
|
85
|
+
# When we initialize the object it is going to set some instance vars
|
86
|
+
@root = opts[:path] || Dir.pwd
|
87
|
+
@environmentpath = opts[:environmentpath] || 'etc/puppetlabs/code/environments'
|
88
|
+
@puppetfile = opts[:puppetfile] || File.expand_path('./Puppetfile',@root)
|
89
|
+
@environment_conf = opts[:environment_conf] || File.expand_path('./environment.conf',@root)
|
90
|
+
@facts_dir = opts[:facts_dir] || File.expand_path('./spec/factsets',@root)
|
91
|
+
@spec_dir = opts[:spec_dir] || File.expand_path('./spec',@root)
|
92
|
+
@facts_files = opts[:facts_files] || [Dir["#{@facts_dir}/*.json"],Dir["#{File.expand_path('../../../factsets',__FILE__)}/*.json"]].flatten
|
93
|
+
@nodeset_file = opts[:nodeset_file] || File.expand_path('./spec/acceptance/nodesets/onceover-nodes.yml',@root)
|
94
|
+
@role_regex = /role[s]?:{2}/
|
95
|
+
@profile_regex = /profile[s]?:{2}/
|
96
|
+
@tempdir = opts[:tempdir] || ENV['CONTROLREPO_temp'] || File.absolute_path('./.onceover')
|
97
|
+
$temp_modulepath = nil
|
98
|
+
@manifest = opts[:manifest] || config['manifest'] ? File.expand_path(config['manifest'],@root) : nil
|
99
|
+
@controlrepo_yaml = opts[:controlrepo_yaml] || "#{@spec_dir}/onceover.yaml"
|
100
|
+
@opts = opts
|
101
|
+
logger.level = :debug if @opts[:debug]
|
102
|
+
end
|
103
|
+
|
104
|
+
def to_s
|
105
|
+
require 'colored'
|
106
|
+
|
107
|
+
<<-END.gsub(/^\s{4}/,'')
|
108
|
+
#{'puppetfile'.green} #{@puppetfile}
|
109
|
+
#{'environment_conf'.green} #{@environment_conf}
|
110
|
+
#{'facts_dir'.green} #{@facts_dir}
|
111
|
+
#{'spec_dir'.green} #{@spec_dir}
|
112
|
+
#{'facts_files'.green} #{@facts_files}
|
113
|
+
#{'nodeset_file'.green} #{@nodeset_file}
|
114
|
+
#{'roles'.green} #{roles}
|
115
|
+
#{'profiles'.green} #{profiles}
|
116
|
+
#{'controlrepo.yaml'.green} #{@controlrepo_yaml}
|
117
|
+
END
|
118
|
+
end
|
119
|
+
|
120
|
+
def roles
|
121
|
+
classes.keep_if { |c| c =~ @role_regex }
|
122
|
+
end
|
123
|
+
|
124
|
+
def profiles
|
125
|
+
classes.keep_if { |c| c =~ @profile_regex }
|
126
|
+
end
|
127
|
+
|
128
|
+
def classes
|
129
|
+
# Get all of the possible places for puppet code and look for classes
|
130
|
+
code_dirs = self.config['modulepath']
|
131
|
+
# Remove interpolated references
|
132
|
+
code_dirs.delete_if { |dir| dir[0] == '$'}
|
133
|
+
|
134
|
+
# Make sure that the paths are relative to the controlrepo root
|
135
|
+
code_dirs.map! do |dir|
|
136
|
+
File.expand_path(dir,@root)
|
137
|
+
end
|
138
|
+
|
139
|
+
# Get all the classes from all of the manifests
|
140
|
+
classes = []
|
141
|
+
code_dirs.each do |dir|
|
142
|
+
classes << get_classes(dir)
|
143
|
+
end
|
144
|
+
classes.flatten
|
145
|
+
end
|
146
|
+
|
147
|
+
def facts(filter = nil)
|
148
|
+
# Returns an array facts hashes
|
149
|
+
all_facts = []
|
150
|
+
logger.debug "Reading factsets"
|
151
|
+
@facts_files.each do |file|
|
152
|
+
all_facts << read_facts(file)['values']
|
153
|
+
end
|
154
|
+
if filter
|
155
|
+
# Allow us to pass a hash of facts to filter by
|
156
|
+
raise "Filter param must be a hash" unless filter.is_a?(Hash)
|
157
|
+
all_facts.keep_if do |hash|
|
158
|
+
matches = []
|
159
|
+
filter.each do |filter_fact,value|
|
160
|
+
matches << keypair_is_in_hash(hash,filter_fact,value)
|
161
|
+
end
|
162
|
+
if matches.include? false
|
163
|
+
false
|
164
|
+
else
|
165
|
+
true
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
return all_facts
|
170
|
+
end
|
171
|
+
|
172
|
+
def print_puppetfile_table
|
173
|
+
require 'table_print'
|
174
|
+
require 'versionomy'
|
175
|
+
require 'colored'
|
176
|
+
require 'r10k/puppetfile'
|
177
|
+
|
178
|
+
# Load up the Puppetfile using R10k
|
179
|
+
logger.debug "Reading puppetfile from #{@root}"
|
180
|
+
puppetfile = R10K::Puppetfile.new(@root)
|
181
|
+
logger.debug "Loading modules from Puppetfile"
|
182
|
+
puppetfile.load!
|
183
|
+
|
184
|
+
output_array = []
|
185
|
+
puppetfile.modules.each do |mod|
|
186
|
+
return_hash = {}
|
187
|
+
logger.debug "Loading data for #{mod.full_name}"
|
188
|
+
return_hash[:full_name] = mod.full_name
|
189
|
+
if mod.is_a?(R10K::Module::Forge)
|
190
|
+
return_hash[:current_version] = mod.expected_version
|
191
|
+
return_hash[:latest_version] = mod.v3_module.current_release.version
|
192
|
+
current = Versionomy.parse(return_hash[:current_version])
|
193
|
+
latest = Versionomy.parse(return_hash[:latest_version])
|
194
|
+
if current.major < latest.major
|
195
|
+
return_hash[:out_of_date] = "Major".red
|
196
|
+
elsif current.minor < latest.minor
|
197
|
+
return_hash[:out_of_date] = "Minor".yellow
|
198
|
+
elsif current.tiny < latest.tiny
|
199
|
+
return_hash[:out_of_date] = "Tiny".green
|
200
|
+
else
|
201
|
+
return_hash[:out_of_date] = "No".green
|
202
|
+
end
|
203
|
+
else
|
204
|
+
return_hash[:current_version] = "N/A"
|
205
|
+
return_hash[:latest_version] = "N/A"
|
206
|
+
return_hash[:out_of_date] = "N/A"
|
207
|
+
end
|
208
|
+
output_array << return_hash
|
209
|
+
end
|
210
|
+
|
211
|
+
tp output_array, \
|
212
|
+
{:full_name => {:display_name => "Full Name"}}, \
|
213
|
+
{:current_version => {:display_name => "Current Version"}}, \
|
214
|
+
{:latest_version => {:display_name => "Latest Version"}}, \
|
215
|
+
{:out_of_date => {:display_name => "Out of Date?"}}
|
216
|
+
end
|
217
|
+
|
218
|
+
def update_puppetfile
|
219
|
+
require 'r10k/puppetfile'
|
220
|
+
|
221
|
+
# Read in the Puppetfile as a string and as an object
|
222
|
+
puppetfile_string = File.read(@puppetfile).split("\n")
|
223
|
+
puppetfile = R10K::Puppetfile.new(@root)
|
224
|
+
puppetfile.load!
|
225
|
+
|
226
|
+
# TODO: Make sure we can deal with :latest
|
227
|
+
|
228
|
+
puppetfile.modules.keep_if {|m| m.is_a?(R10K::Module::Forge)}
|
229
|
+
puppetfile.modules.each do |mod|
|
230
|
+
line_index = puppetfile_string.index {|l| l =~ /^\s*[^#]*#{mod.owner}[\/-]#{mod.name}/}
|
231
|
+
logger.debug "Getting latest version of #{mod.full_name}"
|
232
|
+
puppetfile_string[line_index].gsub!(mod.expected_version,mod.v3_module.current_release.version)
|
233
|
+
end
|
234
|
+
File.open(@puppetfile, 'w') {|f| f.write(puppetfile_string.join("\n")) }
|
235
|
+
puts "#{'changed'.yellow} #{@puppetfile}"
|
236
|
+
end
|
237
|
+
|
238
|
+
def fixtures
|
239
|
+
# Load up the Puppetfile using R10k
|
240
|
+
puppetfile = R10K::Puppetfile.new(@root)
|
241
|
+
modules = puppetfile.load
|
242
|
+
|
243
|
+
# Iterate over everything and seperate it out for the sake of readability
|
244
|
+
symlinks = []
|
245
|
+
forge_modules = []
|
246
|
+
repositories = []
|
247
|
+
|
248
|
+
modules.each do |mod|
|
249
|
+
logger.debug "Converting #{mod.to_s} to .fixtures.yml format"
|
250
|
+
# This logic could probably be cleaned up. A lot.
|
251
|
+
if mod.is_a? R10K::Module::Forge
|
252
|
+
if mod.expected_version.is_a?(Hash)
|
253
|
+
# Set it up as a symlink, because we are using local files in the Puppetfile
|
254
|
+
symlinks << {
|
255
|
+
'name' => mod.name,
|
256
|
+
'dir' => mod.expected_version[:path]
|
257
|
+
}
|
258
|
+
elsif mod.expected_version.is_a?(String)
|
259
|
+
# Set it up as a normal firge module
|
260
|
+
forge_modules << {
|
261
|
+
'name' => mod.name,
|
262
|
+
'repo' => mod.title,
|
263
|
+
'ref' => mod.expected_version
|
264
|
+
}
|
265
|
+
end
|
266
|
+
elsif mod.is_a? R10K::Module::Git
|
267
|
+
# Set it up as a git repo
|
268
|
+
repositories << {
|
269
|
+
'name' => mod.name,
|
270
|
+
# I know I shouldn't be doing this, but trust me, there are no methods
|
271
|
+
# anywhere that expose this value, I looked.
|
272
|
+
'repo' => mod.instance_variable_get(:@remote),
|
273
|
+
'ref' => mod.version
|
274
|
+
}
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
# also add synlinks for anything that is in environment.conf
|
279
|
+
code_dirs = self.config['modulepath']
|
280
|
+
code_dirs.delete_if { |dir| dir[0] == '$'}
|
281
|
+
code_dirs.each do |dir|
|
282
|
+
# We need to traverse down into these directories and create a symlink for each
|
283
|
+
# module we find because fixtures.yml is expecting the module's root not the
|
284
|
+
# root of modulepath
|
285
|
+
Dir["#{dir}/*"].each do |mod|
|
286
|
+
symlinks << {
|
287
|
+
'name' => File.basename(mod),
|
288
|
+
'dir' => Pathname.new(File.expand_path(mod)).relative_path_from(Pathname.new(@root))#File.expand_path(mod)
|
289
|
+
}
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
# Use an ERB template to write the files
|
294
|
+
Onceover::Controlrepo.evaluate_template('.fixtures.yml.erb',binding)
|
295
|
+
end
|
296
|
+
|
297
|
+
def hiera_config_file
|
298
|
+
# try to find the hiera.iyaml file
|
299
|
+
hiera_config_file = File.expand_path('./hiera.yaml',@spec_dir) if File.exist?(File.expand_path('./hiera.yaml',@spec_dir))
|
300
|
+
hiera_config_file = File.expand_path('./hiera.yaml',@root) if File.exist?(File.expand_path('./hiera.yaml',@root))
|
301
|
+
hiera_config_file
|
302
|
+
end
|
303
|
+
|
304
|
+
def hiera_config
|
305
|
+
begin
|
306
|
+
YAML.load_file(hiera_config_file)
|
307
|
+
rescue TypeError
|
308
|
+
puts "WARNING: Could not find hiera config file, continuing"
|
309
|
+
nil
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
def hiera_config=(data)
|
314
|
+
File.write(hiera_config_file,data.to_yaml)
|
315
|
+
end
|
316
|
+
|
317
|
+
def hiera_data
|
318
|
+
# This is going to try to find your hiera data directory, if you have named it something
|
319
|
+
# unexpected it won't work
|
320
|
+
possibe_datadirs = Dir["#{@root}/*/"]
|
321
|
+
possibe_datadirs.keep_if { |dir| dir =~ /hiera(?:.*data)?/i }
|
322
|
+
raise "There were too many directories that looked like hiera data: #{possibe_datadirs}" if possibe_datadirs.count > 1
|
323
|
+
File.expand_path(possibe_datadirs[0])
|
324
|
+
end
|
325
|
+
|
326
|
+
def config
|
327
|
+
# Parse the file
|
328
|
+
logger.debug "Reading #{@environment_conf}"
|
329
|
+
env_conf = File.read(@environment_conf)
|
330
|
+
env_conf = env_conf.split("\n")
|
331
|
+
|
332
|
+
# Delete commented out lines
|
333
|
+
env_conf.delete_if { |l| l =~ /^\s*#/}
|
334
|
+
|
335
|
+
# Map the lines into a hash
|
336
|
+
environment_config = {}
|
337
|
+
env_conf.each do |line|
|
338
|
+
environment_config.merge!(Hash[*line.split('=').map { |s| s.strip}])
|
339
|
+
end
|
340
|
+
|
341
|
+
# Finally, split the modulepath values and return
|
342
|
+
begin
|
343
|
+
environment_config['modulepath'] = environment_config['modulepath'].split(':')
|
344
|
+
rescue
|
345
|
+
raise "modulepath was not found in environment.conf, don't know where to look for roles & profiles"
|
346
|
+
end
|
347
|
+
return environment_config
|
348
|
+
end
|
349
|
+
|
350
|
+
def r10k_config_file
|
351
|
+
r10k_config_file = File.expand_path('./r10k.yaml',@spec_dir) if File.exist?(File.expand_path('./r10k.yaml',@spec_dir))
|
352
|
+
r10k_config_file = File.expand_path('./r10k.yaml',@root) if File.exist?(File.expand_path('./r10k.yaml',@root))
|
353
|
+
r10k_config_file
|
354
|
+
end
|
355
|
+
|
356
|
+
def r10k_config
|
357
|
+
YAML.load_file(r10k_config_file)
|
358
|
+
end
|
359
|
+
|
360
|
+
def r10k_config=(data)
|
361
|
+
File.write(r10k_config_file,data.to_yaml)
|
362
|
+
end
|
363
|
+
|
364
|
+
def temp_manifest
|
365
|
+
config['manifest'] ? File.expand_path(config['manifest'],@tempdir) : nil
|
366
|
+
end
|
367
|
+
|
368
|
+
def self.init(repo)
|
369
|
+
# This code will initialise a controlrepo with all of the config
|
370
|
+
# that it needs
|
371
|
+
require 'pathname'
|
372
|
+
require 'colored'
|
373
|
+
|
374
|
+
Onceover::Controlrepo.init_write_file(generate_controlrepo_yaml(repo),repo.controlrepo_yaml)
|
375
|
+
Onceover::Controlrepo.init_write_file(generate_nodesets(repo),repo.nodeset_file)
|
376
|
+
Onceover::Controlrepo.init_write_file(Onceover::Controlrepo.evaluate_template('pre_conditions_README.md.erb',binding),File.expand_path('./pre_conditions/README.md',repo.spec_dir))
|
377
|
+
Onceover::Controlrepo.init_write_file(Onceover::Controlrepo.evaluate_template('factsets_README.md.erb',binding),File.expand_path('./factsets/README.md',repo.spec_dir))
|
378
|
+
|
379
|
+
# Add .controlrepo to Gitignore
|
380
|
+
gitignore_path = File.expand_path('.gitignore',repo.root)
|
381
|
+
if File.exists? gitignore_path
|
382
|
+
gitignore_content = (File.open(gitignore_path,'r') {|f| f.read }).split("\n")
|
383
|
+
message = "#{'changed'.green}"
|
384
|
+
else
|
385
|
+
message = "#{'created'.green}"
|
386
|
+
gitignore_content = []
|
387
|
+
end
|
388
|
+
|
389
|
+
unless gitignore_content.include?(".controlrepo")
|
390
|
+
gitignore_content << ".controlrepo\n"
|
391
|
+
File.open(gitignore_path,'w') {|f| f.write(gitignore_content.join("\n")) }
|
392
|
+
puts "#{message} #{Pathname.new(gitignore_path).relative_path_from(Pathname.new(Dir.pwd)).to_s}"
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
def self.generate_controlrepo_yaml(repo)
|
397
|
+
# This will return a controlrepo.yaml that can be written to a file
|
398
|
+
Onceover::Controlrepo.evaluate_template('controlrepo.yaml.erb',binding)
|
399
|
+
end
|
400
|
+
|
401
|
+
def self.generate_nodesets(repo)
|
402
|
+
require 'onceover/beaker'
|
403
|
+
require 'net/http'
|
404
|
+
require 'json'
|
405
|
+
|
406
|
+
hosts_hash = {}
|
407
|
+
|
408
|
+
repo.facts.each do |fact_set|
|
409
|
+
node_name = File.basename(repo.facts_files[repo.facts.index(fact_set)],'.json')
|
410
|
+
boxname = Onceover::Beaker.facts_to_vagrant_box(fact_set)
|
411
|
+
platform = Onceover::Beaker.facts_to_platform(fact_set)
|
412
|
+
|
413
|
+
logger.debug "Querying hashicorp API for Vagrant box that matches #{boxname}"
|
414
|
+
response = Net::HTTP.get(URI.parse("https://atlas.hashicorp.com/api/v1/box/#{boxname}"))
|
415
|
+
url = 'URL goes here'
|
416
|
+
|
417
|
+
if response =~ /Not Found/i
|
418
|
+
comment_out = true
|
419
|
+
else
|
420
|
+
comment_out = false
|
421
|
+
box_info = JSON.parse(response)
|
422
|
+
box_info['current_version']['providers'].each do |provider|
|
423
|
+
if provider['name'] == 'virtualbox'
|
424
|
+
url = provider['original_url']
|
425
|
+
end
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
429
|
+
# Add the resulting info to the hosts hash. This is what the
|
430
|
+
# template will output
|
431
|
+
hosts_hash[node_name] = {
|
432
|
+
:platform => platform,
|
433
|
+
:boxname => boxname,
|
434
|
+
:url => url,
|
435
|
+
:comment_out => comment_out
|
436
|
+
}
|
437
|
+
end
|
438
|
+
|
439
|
+
# Use an ERB template
|
440
|
+
Onceover::Controlrepo.evaluate_template('nodeset.yaml.erb',binding)
|
441
|
+
end
|
442
|
+
|
443
|
+
def self.create_dirs_and_log(dir)
|
444
|
+
Pathname.new(dir).descend do |folder|
|
445
|
+
unless folder.directory?
|
446
|
+
FileUtils.mkdir(folder)
|
447
|
+
puts "#{'created'.green} #{folder.relative_path_from(Pathname.new(Dir.pwd)).to_s}"
|
448
|
+
end
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
452
|
+
def self.evaluate_template(template_name,bind)
|
453
|
+
logger.debug "Evaluating template #{template_name}"
|
454
|
+
template_dir = File.expand_path('../../templates',File.dirname(__FILE__))
|
455
|
+
template = File.read(File.expand_path("./#{template_name}",template_dir))
|
456
|
+
ERB.new(template, nil, '-').result(bind)
|
457
|
+
end
|
458
|
+
|
459
|
+
def self.init_write_file(contents,out_file)
|
460
|
+
Onceover::Controlrepo.create_dirs_and_log(File.dirname(out_file))
|
461
|
+
if File.exists?(out_file)
|
462
|
+
puts "#{'skipped'.yellow} #{Pathname.new(out_file).relative_path_from(Pathname.new(Dir.pwd)).to_s} #{'(exists)'.yellow}"
|
463
|
+
else
|
464
|
+
File.open(out_file,'w') {|f| f.write(contents)}
|
465
|
+
puts "#{'created'.green} #{Pathname.new(out_file).relative_path_from(Pathname.new(Dir.pwd)).to_s}"
|
466
|
+
end
|
467
|
+
end
|
468
|
+
|
469
|
+
private
|
470
|
+
|
471
|
+
def read_facts(facts_file)
|
472
|
+
file = File.read(facts_file)
|
473
|
+
begin
|
474
|
+
result = JSON.parse(file)
|
475
|
+
rescue JSON::ParserError
|
476
|
+
raise "Could not parse the JSON file, check that it is valid JSON and that the encoding is correct"
|
477
|
+
end
|
478
|
+
result
|
479
|
+
end
|
480
|
+
|
481
|
+
def keypair_is_in_hash(first_hash, key, value)
|
482
|
+
matches = []
|
483
|
+
if first_hash.has_key?(key)
|
484
|
+
if value.is_a?(Hash)
|
485
|
+
value.each do |k,v|
|
486
|
+
matches << keypair_is_in_hash(first_hash[key],k,v)
|
487
|
+
end
|
488
|
+
else
|
489
|
+
if first_hash[key] == value
|
490
|
+
matches << true
|
491
|
+
else
|
492
|
+
matches << false
|
493
|
+
end
|
494
|
+
end
|
495
|
+
else
|
496
|
+
matches << false
|
497
|
+
end
|
498
|
+
if matches.include? false
|
499
|
+
false
|
500
|
+
else
|
501
|
+
true
|
502
|
+
end
|
503
|
+
end
|
504
|
+
|
505
|
+
def get_classes(dir)
|
506
|
+
classes = []
|
507
|
+
# Recurse over all the pp files under the dir we are given
|
508
|
+
logger.debug "Searching puppet code for roles and profiles"
|
509
|
+
Dir["#{dir}/**/*.pp"].each do |manifest|
|
510
|
+
classname = find_classname(manifest)
|
511
|
+
# Add it to the array as long as it is not nil
|
512
|
+
classes << classname if classname
|
513
|
+
end
|
514
|
+
classes
|
515
|
+
end
|
516
|
+
|
517
|
+
def find_classname(filename)
|
518
|
+
file = File.new(filename, "r")
|
519
|
+
while (line = file.gets)
|
520
|
+
if line =~ /^class (\w+(?:::\w+)*)/
|
521
|
+
return $1
|
522
|
+
end
|
523
|
+
end
|
524
|
+
return nil
|
525
|
+
end
|
526
|
+
end
|
527
|
+
end
|