cupper 0.0.1 → 0.1.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 +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
|