cupper 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +16 -0
- data/Gemfile +18 -0
- data/README.md +30 -0
- data/Rakefile +6 -0
- data/bin/cupper +1 -1
- data/cupper.gemspec +28 -0
- data/lib/cupper.rb +18 -1
- data/lib/cupper/cli.rb +63 -0
- data/lib/cupper/collect.rb +51 -0
- data/lib/cupper/cookbook.rb +71 -0
- data/lib/cupper/cookbook_file.rb +10 -0
- data/lib/cupper/cupperfile.rb +22 -0
- data/lib/cupper/entity.rb +63 -0
- data/lib/cupper/environment.rb +177 -0
- data/lib/cupper/errors.rb +84 -0
- data/lib/cupper/ohai_plugins.rb +13 -0
- data/lib/cupper/platform_collector.rb +40 -0
- data/lib/cupper/plugins/cupper/arch.rb +43 -0
- data/lib/cupper/plugins/cupper/debian.rb +48 -0
- data/lib/cupper/plugins/ohai/dpci.rb +30 -0
- data/lib/cupper/plugins/ohai/files.rb +68 -0
- data/lib/cupper/plugins/ohai/init_system.rb +14 -0
- data/lib/cupper/plugins/ohai/pacman.rb +31 -0
- data/lib/cupper/plugins/ohai/pkg_deps.rb +30 -0
- data/lib/cupper/plugins/ohai/pkg_manager.rb +31 -0
- data/lib/cupper/plugins/ohai/services.rb +20 -0
- data/lib/cupper/project.rb +40 -0
- data/lib/cupper/recipe.rb +240 -0
- data/lib/cupper/templates/CupperFile.erb +1 -0
- data/lib/cupper/templates/_cookbook_file.erb +9 -0
- data/lib/cupper/templates/_groups.erb +10 -0
- data/lib/cupper/templates/_links.erb +10 -0
- data/lib/cupper/templates/_package.erb +19 -0
- data/lib/cupper/templates/_services.erb +7 -0
- data/lib/cupper/templates/_templates.erb +11 -0
- data/lib/cupper/templates/_users.erb +10 -0
- data/lib/cupper/templates/cookbook_file.erb +1 -0
- data/lib/cupper/templates/recipe.erb +7 -0
- data/lib/cupper/version.rb +3 -0
- data/templates/locales/en.yml +17 -0
- metadata +125 -8
@@ -0,0 +1,177 @@
|
|
1
|
+
require "pathname"
|
2
|
+
require "cupper/cupperfile"
|
3
|
+
require "cupper/version"
|
4
|
+
require 'colorize'
|
5
|
+
|
6
|
+
module Cupper
|
7
|
+
class Environment
|
8
|
+
# The `cwd` that this environment represents
|
9
|
+
attr_reader :cwd
|
10
|
+
|
11
|
+
# The persistent data directory where global data can be stored. It
|
12
|
+
# is up to the creator of the data in this directory to properly
|
13
|
+
# remove it when it is no longer needed.
|
14
|
+
#
|
15
|
+
# @return [Pathname]
|
16
|
+
attr_reader :data_dir
|
17
|
+
|
18
|
+
# The valid name for a Cupperfile for this environment.
|
19
|
+
attr_reader :cupperfile_name
|
20
|
+
|
21
|
+
# The directory to the directory where local, environment-specific
|
22
|
+
# data is stored.
|
23
|
+
attr_reader :local_data_path
|
24
|
+
|
25
|
+
# The directory where temporary files for Cupper go.
|
26
|
+
attr_reader :tmp_path
|
27
|
+
|
28
|
+
# The path where the plugins are stored (gems)
|
29
|
+
attr_reader :gems_path
|
30
|
+
|
31
|
+
def check_env(ex, root_path)
|
32
|
+
begin
|
33
|
+
raise ex if !root_path
|
34
|
+
rescue ex => ex
|
35
|
+
puts "#{ex.message}".red
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Initializes a new environment with the given options. The options
|
40
|
+
# is a hash where the main available key is `cwd`, which defines where
|
41
|
+
# the environment represents. There are other options available but
|
42
|
+
# they shouldn't be used in general. If `cwd` is nil, then it defaults
|
43
|
+
# to the `Dir.pwd` (which is the cwd of the executing process).
|
44
|
+
def initialize(opts=nil)
|
45
|
+
opts = {
|
46
|
+
cwd: nil,
|
47
|
+
local_data_path: nil,
|
48
|
+
cupperfile_name: nil,
|
49
|
+
}.merge(opts || {})
|
50
|
+
|
51
|
+
# Set the default working directory to look for the cupperfile
|
52
|
+
opts[:cwd] ||= ENV["cupper_CWD"] if ENV.key?("cupper_CWD")
|
53
|
+
opts[:cwd] ||= Dir.pwd
|
54
|
+
opts[:cwd] = Pathname.new(opts[:cwd])
|
55
|
+
if !opts[:cwd].directory?
|
56
|
+
raise Errors::EnvironmentNonExistentCWD, cwd: opts[:cwd].to_s
|
57
|
+
end
|
58
|
+
opts[:cwd] = opts[:cwd].expand_path
|
59
|
+
opts[:cupperfile_name] = 'Cupperfile'
|
60
|
+
|
61
|
+
# Set the Cupperfile name up. We append "Cupperfile" and "cupperfile" so that
|
62
|
+
# those continue to work as well, but anything custom will take precedence.
|
63
|
+
opts[:cupperfile_name] ||= ENV["cupper_cupperFILE"] if \
|
64
|
+
ENV.key?("cupper_cupperFILE")
|
65
|
+
opts[:cupperfile_name] = [opts[:cupperfile_name]] if \
|
66
|
+
opts[:cupperfile_name] && !opts[:cupperfile_name].is_a?(Array)
|
67
|
+
|
68
|
+
# Set instance variables for all the configuration parameters.
|
69
|
+
@cwd = opts[:cwd]
|
70
|
+
@cupperfile_name = opts[:cupperfile_name]
|
71
|
+
|
72
|
+
|
73
|
+
# Run checkpoint in a background thread on every environment
|
74
|
+
# initialization. The cache file will cause this to mostly be a no-op
|
75
|
+
# most of the time.
|
76
|
+
@checkpoint_thr = Thread.new do
|
77
|
+
Thread.current[:result] = nil
|
78
|
+
|
79
|
+
# If we disabled state and knowing what alerts we've seen, then
|
80
|
+
# disable the signature file.
|
81
|
+
signature_file = @data_dir.join("checkpoint_signature")
|
82
|
+
if ENV["cupper_CHECKPOINT_NO_STATE"].to_s != ""
|
83
|
+
signature_file = nil
|
84
|
+
end
|
85
|
+
|
86
|
+
Thread.current[:result] = Checkpoint.check(
|
87
|
+
product: "cupper",
|
88
|
+
version: VERSION,
|
89
|
+
signature_file: signature_file,
|
90
|
+
cache_file: @data_dir.join("checkpoint_cache"),
|
91
|
+
)
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
# Return a human-friendly string for pretty printed or inspected
|
97
|
+
# instances.
|
98
|
+
#
|
99
|
+
# @return [String]
|
100
|
+
def inspect
|
101
|
+
"#<#{self.class}: #{@cwd}>".encode('external')
|
102
|
+
end
|
103
|
+
|
104
|
+
def config_loader
|
105
|
+
return @config_loader if @config_loader
|
106
|
+
|
107
|
+
root_cupperfile = nil
|
108
|
+
if root_path
|
109
|
+
root_cupperfile = find_cupperfile(root_path, @cupperfile_name)
|
110
|
+
end
|
111
|
+
|
112
|
+
@config_loader = Config::Loader.new(
|
113
|
+
Config::VERSIONS, Config::VERSIONS_ORDER)
|
114
|
+
@config_loader.set(:root, root_cupperfile) if root_cupperfile
|
115
|
+
@config_loader
|
116
|
+
end
|
117
|
+
|
118
|
+
def environment(cupperfile, **opts)
|
119
|
+
path = File.expand_path(cupperfile, root_path)
|
120
|
+
file = File.basename(path)
|
121
|
+
path = File.dirname(path)
|
122
|
+
|
123
|
+
Util::SilenceWarnings.silence! do
|
124
|
+
Environment.new({
|
125
|
+
child: true,
|
126
|
+
cwd: path,
|
127
|
+
home_path: home_path,
|
128
|
+
ui_class: ui_class,
|
129
|
+
cupperfile_name: file,
|
130
|
+
}.merge(opts))
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def hook(name, opts=nil)
|
135
|
+
opts ||= {}
|
136
|
+
opts[:callable] ||= Action::Builder.new
|
137
|
+
opts[:runner] ||= action_runner
|
138
|
+
opts[:action_name] = name
|
139
|
+
opts[:env] = self
|
140
|
+
opts.delete(:runner).run(opts.delete(:callable), opts)
|
141
|
+
end
|
142
|
+
|
143
|
+
def root_path
|
144
|
+
return @root_path if defined?(@root_path)
|
145
|
+
|
146
|
+
root_finder = lambda do |path|
|
147
|
+
# Note: To remain compatible with Ruby 1.8, we have to use
|
148
|
+
# a `find` here instead of an `each`.
|
149
|
+
vf = find_cupperfile(path, @cupperfile_name)
|
150
|
+
return path if vf
|
151
|
+
return nil if path.root? || !File.exist?(path)
|
152
|
+
root_finder.call(path.parent)
|
153
|
+
end
|
154
|
+
|
155
|
+
@root_path = root_finder.call(cwd)
|
156
|
+
end
|
157
|
+
|
158
|
+
def unload
|
159
|
+
hook(:environment_unload)
|
160
|
+
end
|
161
|
+
|
162
|
+
def cupperfile
|
163
|
+
@cupperfile ||= cupperfile.new(config_loader, [:home, :root])
|
164
|
+
end
|
165
|
+
|
166
|
+
def find_cupperfile(search_path, filenames=nil)
|
167
|
+
filenames ||= ["cupperfile", "cupperfile"]
|
168
|
+
filenames.each do |cupperfile|
|
169
|
+
current_path = search_path.join(cupperfile)
|
170
|
+
return current_path if current_path.file?
|
171
|
+
end
|
172
|
+
|
173
|
+
nil
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
177
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'i18n'
|
2
|
+
|
3
|
+
module Cupper
|
4
|
+
|
5
|
+
module Errors
|
6
|
+
|
7
|
+
class CupperError < StandardError
|
8
|
+
attr_accessor :extra_data
|
9
|
+
|
10
|
+
def self.error_key(key=nil, namespace=nil)
|
11
|
+
define_method(:error_key) { key }
|
12
|
+
error_namespace(namespace) if namespace
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.error_message(message)
|
16
|
+
define_method(:error_message) { message }
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.error_namespace(namespace)
|
20
|
+
define_method(:error_namespace) { namespace }
|
21
|
+
end
|
22
|
+
|
23
|
+
def translate_error(opts)
|
24
|
+
return nil if !opts[:_key]
|
25
|
+
I18n.t("#{opts[:_namespace]}.#{opts[:_key]}", opts)
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize(*args)
|
29
|
+
key = args.shift if args.first.is_a?(Symbol)
|
30
|
+
message = args.shift if args.first.is_a?(Hash)
|
31
|
+
message ||= {}
|
32
|
+
@extra_data = message.dup
|
33
|
+
message[:_key] ||= error_key
|
34
|
+
message[:_namespace] ||= error_namespace
|
35
|
+
message[:_key] = key if key
|
36
|
+
I18n.load_path << File.expand_path("../../../templates/locales/en.yml", __FILE__)
|
37
|
+
|
38
|
+
if message[:_key]
|
39
|
+
message = translate_error(message)
|
40
|
+
else
|
41
|
+
message = error_message
|
42
|
+
end
|
43
|
+
|
44
|
+
super(message)
|
45
|
+
end
|
46
|
+
|
47
|
+
# The error message for this error. This is used if no error_key
|
48
|
+
# is specified for a translatable error message.
|
49
|
+
def error_message; "No error message"; end
|
50
|
+
|
51
|
+
# The default error namespace which is used for the error key.
|
52
|
+
# This can be overridden here or by calling the "error_namespace"
|
53
|
+
# class method.
|
54
|
+
def error_namespace; "cupper.errors"; end
|
55
|
+
|
56
|
+
# The key for the error message. This should be set using the
|
57
|
+
# {error_key} method but can be overridden here if needed.
|
58
|
+
def error_key; nil; end
|
59
|
+
|
60
|
+
# This is the exit code that should be used when exiting from
|
61
|
+
# this exception.
|
62
|
+
#
|
63
|
+
# @return [Integer]
|
64
|
+
def status_code; 1; end
|
65
|
+
|
66
|
+
protected
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
class EnvironmentNonExistentCWD < CupperError
|
71
|
+
error_key(:environment_non_existent_cwd)
|
72
|
+
end
|
73
|
+
|
74
|
+
class NoEnvironmentError < CupperError
|
75
|
+
error_key(:no_env)
|
76
|
+
end
|
77
|
+
|
78
|
+
class LocalDataDirectoryNotAccessible < CupperError
|
79
|
+
error_key(:local_data_dir_not_accessible)
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Cupper
|
2
|
+
class OhaiPlugin
|
3
|
+
|
4
|
+
attr_reader :plugins_path
|
5
|
+
|
6
|
+
def list
|
7
|
+
@plugins_path = File.expand_path '../../cupper/plugins/ohai', __FILE__
|
8
|
+
@plugins = Dir.entries(@plugins_path).reject{ |entry| entry == '.' || entry == '..' }
|
9
|
+
@plugins.each { |plugin| plugin.chomp!(".rb") }
|
10
|
+
@plugins.sort
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# REVIEW: This should be review in some point in the future
|
2
|
+
# The PlatformCollector is a module that defines all the methods used to extract
|
3
|
+
# the information about the platform. It is a little pointless in terms of implementation
|
4
|
+
# but is good to know what method should be implemented.
|
5
|
+
|
6
|
+
module Cupper
|
7
|
+
module PlatformCollector
|
8
|
+
def packages
|
9
|
+
raise NotImplementedError
|
10
|
+
end
|
11
|
+
|
12
|
+
def links
|
13
|
+
raise NotImplementedError
|
14
|
+
end
|
15
|
+
|
16
|
+
def services
|
17
|
+
raise NotImplementedError
|
18
|
+
end
|
19
|
+
|
20
|
+
def users
|
21
|
+
raise NotImplementedError
|
22
|
+
end
|
23
|
+
|
24
|
+
def executes
|
25
|
+
raise NotImplementedError
|
26
|
+
end
|
27
|
+
|
28
|
+
def directory
|
29
|
+
raise NotImplementedError
|
30
|
+
end
|
31
|
+
|
32
|
+
def files
|
33
|
+
raise NotImplementedError
|
34
|
+
end
|
35
|
+
|
36
|
+
def templates
|
37
|
+
raise NotImplementedError
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'cupper/platform_collector'
|
2
|
+
|
3
|
+
module Cupper
|
4
|
+
class Arch
|
5
|
+
include PlatformCollector
|
6
|
+
def packages(data_extraction)
|
7
|
+
packages = Array.new
|
8
|
+
data_extraction['pacman']['pacman'].each do |pkg|
|
9
|
+
packages.push(pkg)
|
10
|
+
end
|
11
|
+
packages
|
12
|
+
end
|
13
|
+
|
14
|
+
def files(data_extraction)
|
15
|
+
files = Array.new
|
16
|
+
data_extraction['files']['files'].each do |file|
|
17
|
+
files.push(file)
|
18
|
+
end
|
19
|
+
files
|
20
|
+
end
|
21
|
+
|
22
|
+
def services(data_extraction)
|
23
|
+
services = Array.new
|
24
|
+
data_extraction['services']['services'].each do |service|
|
25
|
+
services.push(service)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def users(data_extraction)
|
30
|
+
users = Array.new
|
31
|
+
data_extraction['etc']['etc']['passwd'].each do |user|
|
32
|
+
users.push(user)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def groups(data_extraction)
|
37
|
+
groups = Array.new
|
38
|
+
data_extraction['etc']['etc']['group'].each do |group|
|
39
|
+
groups.push(group)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'cupper/platform_collector'
|
2
|
+
|
3
|
+
module Cupper
|
4
|
+
class Debian
|
5
|
+
include PlatformCollector
|
6
|
+
def packages(data_extraction)
|
7
|
+
packages = Array.new
|
8
|
+
duplicated = Array.new
|
9
|
+
data_extraction['pkg_deps']['pkg_deps'].each do |dep|
|
10
|
+
duplicated << dep[1]
|
11
|
+
end
|
12
|
+
duplicated.flatten!.uniq!
|
13
|
+
data_extraction['packages']['packages'].each do |pkg|
|
14
|
+
packages.push(pkg) unless duplicated.include? pkg[0]
|
15
|
+
end
|
16
|
+
packages
|
17
|
+
end
|
18
|
+
|
19
|
+
def files(data_extraction)
|
20
|
+
files = Array.new
|
21
|
+
data_extraction['files']['files'].each do |file|
|
22
|
+
files.push(file)
|
23
|
+
end
|
24
|
+
files
|
25
|
+
end
|
26
|
+
|
27
|
+
def services(data_extraction)
|
28
|
+
services = Array.new
|
29
|
+
data_extraction['services']['services'].each do |service|
|
30
|
+
services.push(service)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def users(data_extraction)
|
35
|
+
users = Array.new
|
36
|
+
data_extraction['etc']['etc']['passwd'].each do |user|
|
37
|
+
users.push(user)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def groups(data_extraction)
|
42
|
+
groups = Array.new
|
43
|
+
data_extraction['etc']['etc']['group'].each do |group|
|
44
|
+
groups.push(group)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
|
2
|
+
Ohai.plugin(:Pci) do
|
3
|
+
provides 'dpci'
|
4
|
+
|
5
|
+
def from_cmd(cmd)
|
6
|
+
so = shell_out(cmd)
|
7
|
+
so.stdout.lines
|
8
|
+
end
|
9
|
+
|
10
|
+
collect_data(:default) do
|
11
|
+
dpci Mash.new
|
12
|
+
pcis from_cmd('lspci')
|
13
|
+
|
14
|
+
r_bus_slot_num = /\d+:[0-9a-fA-F]+\.\d\s/
|
15
|
+
r_slot_name = /^[A-z\s]+/
|
16
|
+
|
17
|
+
pcis.each_with_index do |pci, i|
|
18
|
+
bus_slot_num = pci.slice! r_bus_slot_num
|
19
|
+
slot_name = pci.slice! r_slot_name
|
20
|
+
pci.slice! /^:\s/
|
21
|
+
device_name = pci
|
22
|
+
|
23
|
+
dpci["pci_#{i}"] = {
|
24
|
+
"bus_slot_num" => bus_slot_num,
|
25
|
+
"slot_name" => slot_name,
|
26
|
+
"device_name" => device_name
|
27
|
+
}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
Ohai.plugin(:Files) do
|
2
|
+
provides 'files'
|
3
|
+
depends 'platform_family'
|
4
|
+
|
5
|
+
def from_cmd(cmd)
|
6
|
+
so = shell_out(cmd)
|
7
|
+
so.stdout.lines
|
8
|
+
end
|
9
|
+
|
10
|
+
def has_related_package?(file)
|
11
|
+
if %w{debian}.include? platform_family
|
12
|
+
related = shell_out("dpkg -S #{file}").stdout.chomp
|
13
|
+
elsif %w{arch}.include? platform_family
|
14
|
+
related = shell_out("pacman -Qo #{file}").stdout.chomp
|
15
|
+
end
|
16
|
+
!(related.empty?)
|
17
|
+
end
|
18
|
+
|
19
|
+
def related_to(file)
|
20
|
+
if %w{debian}.include? platform_family
|
21
|
+
pkg, null = shell_out("dpkg -S #{file}").stdout.chomp.split(' ', 2)
|
22
|
+
pkg.chomp!(':')
|
23
|
+
elsif %w{arch}.include? platform_family
|
24
|
+
pkg = shell_out("pacman -Qo #{file}").stdout.chomp.split[4]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def file_content(file)
|
29
|
+
shell_out("cat #{file}").stdout
|
30
|
+
end
|
31
|
+
|
32
|
+
def add_info(file)
|
33
|
+
shell_out("ls -al #{file}").stdout.split(' ',5)
|
34
|
+
end
|
35
|
+
|
36
|
+
def extract_files
|
37
|
+
subdir = true
|
38
|
+
cmd = 'file /etc/**'
|
39
|
+
result = Array.new
|
40
|
+
while subdir do
|
41
|
+
result << from_cmd(cmd)
|
42
|
+
cmd += '/**'
|
43
|
+
result.flatten!
|
44
|
+
subdir = false if result.last.match('cannot open')
|
45
|
+
end
|
46
|
+
result
|
47
|
+
end
|
48
|
+
|
49
|
+
collect_data(:linux) do
|
50
|
+
files Mash.new
|
51
|
+
extract_files.each do |file|
|
52
|
+
path, type = file.split(' ', 2)
|
53
|
+
type.chomp!
|
54
|
+
path.chomp!(':')
|
55
|
+
mode, null, owner, group, null = add_info(path)
|
56
|
+
rel = related_to(path) if has_related_package?(path)
|
57
|
+
content = file_content(path)
|
58
|
+
files[path] = {
|
59
|
+
'type' => type,
|
60
|
+
'mode' => mode,
|
61
|
+
'owner' => owner,
|
62
|
+
'group' => group,
|
63
|
+
'related' => rel,
|
64
|
+
'content' => content
|
65
|
+
}
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|