cupper 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +9 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +16 -0
  5. data/Gemfile +18 -0
  6. data/README.md +30 -0
  7. data/Rakefile +6 -0
  8. data/bin/cupper +1 -1
  9. data/cupper.gemspec +28 -0
  10. data/lib/cupper.rb +18 -1
  11. data/lib/cupper/cli.rb +63 -0
  12. data/lib/cupper/collect.rb +51 -0
  13. data/lib/cupper/cookbook.rb +71 -0
  14. data/lib/cupper/cookbook_file.rb +10 -0
  15. data/lib/cupper/cupperfile.rb +22 -0
  16. data/lib/cupper/entity.rb +63 -0
  17. data/lib/cupper/environment.rb +177 -0
  18. data/lib/cupper/errors.rb +84 -0
  19. data/lib/cupper/ohai_plugins.rb +13 -0
  20. data/lib/cupper/platform_collector.rb +40 -0
  21. data/lib/cupper/plugins/cupper/arch.rb +43 -0
  22. data/lib/cupper/plugins/cupper/debian.rb +48 -0
  23. data/lib/cupper/plugins/ohai/dpci.rb +30 -0
  24. data/lib/cupper/plugins/ohai/files.rb +68 -0
  25. data/lib/cupper/plugins/ohai/init_system.rb +14 -0
  26. data/lib/cupper/plugins/ohai/pacman.rb +31 -0
  27. data/lib/cupper/plugins/ohai/pkg_deps.rb +30 -0
  28. data/lib/cupper/plugins/ohai/pkg_manager.rb +31 -0
  29. data/lib/cupper/plugins/ohai/services.rb +20 -0
  30. data/lib/cupper/project.rb +40 -0
  31. data/lib/cupper/recipe.rb +240 -0
  32. data/lib/cupper/templates/CupperFile.erb +1 -0
  33. data/lib/cupper/templates/_cookbook_file.erb +9 -0
  34. data/lib/cupper/templates/_groups.erb +10 -0
  35. data/lib/cupper/templates/_links.erb +10 -0
  36. data/lib/cupper/templates/_package.erb +19 -0
  37. data/lib/cupper/templates/_services.erb +7 -0
  38. data/lib/cupper/templates/_templates.erb +11 -0
  39. data/lib/cupper/templates/_users.erb +10 -0
  40. data/lib/cupper/templates/cookbook_file.erb +1 -0
  41. data/lib/cupper/templates/recipe.erb +7 -0
  42. data/lib/cupper/version.rb +3 -0
  43. data/templates/locales/en.yml +17 -0
  44. 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