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.
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