puppet-herald 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +15 -7
  2. data/.rubocop.yml +31 -0
  3. data/.rubocop_todo.yml +6 -0
  4. data/.travis.yml +7 -7
  5. data/Gemfile +5 -9
  6. data/README.md +152 -16
  7. data/Rakefile +67 -6
  8. data/bin/puppet-herald +1 -1
  9. data/config.ru +2 -2
  10. data/db/migrate/20141211165540_create_nodes.rb +5 -3
  11. data/db/migrate/20141211171305_create_reports.rb +12 -10
  12. data/db/migrate/20141211171326_create_log_entries.rb +9 -7
  13. data/db/schema.rb +24 -26
  14. data/lib/puppet-herald.rb +59 -21
  15. data/lib/puppet-herald/app/api.rb +111 -0
  16. data/lib/puppet-herald/app/configuration.rb +70 -0
  17. data/lib/puppet-herald/app/frontend.rb +61 -0
  18. data/lib/puppet-herald/{views → app/views}/app.erb +5 -8
  19. data/lib/puppet-herald/{views → app/views}/err500.erb +1 -4
  20. data/lib/puppet-herald/application.rb +27 -0
  21. data/lib/puppet-herald/cli.rb +66 -45
  22. data/lib/puppet-herald/client.rb +33 -0
  23. data/lib/puppet-herald/database.rb +84 -40
  24. data/lib/puppet-herald/javascript.rb +23 -17
  25. data/lib/puppet-herald/models/log-entry.rb +10 -3
  26. data/lib/puppet-herald/models/node.rb +15 -5
  27. data/lib/puppet-herald/models/report.rb +70 -63
  28. data/lib/puppet-herald/public/app.js +9 -8
  29. data/lib/puppet-herald/public/components/directives/status-button.html +1 -1
  30. data/lib/puppet-herald/public/components/directives/status-button.js +5 -3
  31. data/lib/puppet-herald/public/components/filters/filters.js +9 -4
  32. data/lib/puppet-herald/public/components/page.js +34 -0
  33. data/lib/puppet-herald/public/node/node.html +3 -1
  34. data/lib/puppet-herald/public/node/node.js +7 -4
  35. data/lib/puppet-herald/public/nodes/nodes.js +3 -2
  36. data/lib/puppet-herald/public/report/report.html +4 -1
  37. data/lib/puppet-herald/public/report/report.js +5 -3
  38. data/lib/puppet-herald/stubs/puppet.rb +20 -9
  39. data/lib/puppet-herald/version.rb +17 -7
  40. data/package.json +8 -3
  41. data/puppet-herald.gemspec +3 -6
  42. data/spec/integration/application_spec.rb +175 -0
  43. data/spec/integration/models/node_spec.rb +4 -4
  44. data/spec/integration/models/report_spec.rb +7 -7
  45. data/spec/spec_helper.rb +12 -7
  46. data/spec/support/active_record.rb +6 -10
  47. data/spec/support/reconnectdb.rb +13 -0
  48. data/spec/unit/puppet-herald/cli_spec.rb +45 -13
  49. data/spec/unit/puppet-herald/client_spec.rb +23 -0
  50. data/spec/unit/puppet-herald/database_spec.rb +8 -9
  51. data/spec/unit/puppet-herald/javascript_spec.rb +8 -13
  52. data/spec/unit/puppet-herald_spec.rb +4 -4
  53. data/test/javascript/karma.conf.js +43 -5
  54. data/test/javascript/src/app_test.js +90 -0
  55. data/test/javascript/src/components/artifact/artifact-directive_test.js +36 -0
  56. data/test/javascript/src/components/artifact/artifact_test.js +64 -0
  57. data/test/javascript/src/components/directives/status-button_test.js +159 -0
  58. data/test/javascript/src/components/filters/filters_test.js +35 -0
  59. data/test/javascript/src/node/node_test.js +87 -0
  60. data/test/javascript/src/nodes/nodes_test.js +56 -0
  61. data/test/javascript/src/report/report_test.js +94 -0
  62. metadata +98 -68
  63. data/lib/puppet-herald/app.rb +0 -103
  64. data/lib/puppet-herald/public/components/artifact/artifact-directive_test.js +0 -17
  65. data/spec/integration/app_spec.rb +0 -21
@@ -1,8 +1,5 @@
1
1
  <!DOCTYPE html>
2
- <!--[if lt IE 7]> <html lang="en" class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
3
- <!--[if IE 7]> <html lang="en" class="no-js lt-ie9 lt-ie8"> <![endif]-->
4
- <!--[if IE 8]> <html lang="en" class="no-js lt-ie9"> <![endif]-->
5
- <!--[if gt IE 8]><!--> <html lang="en" class="no-js"> <!--<![endif]-->
2
+ <html lang="en">
6
3
  <head>
7
4
  <meta charset="utf-8">
8
5
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
@@ -0,0 +1,27 @@
1
+ require 'sinatra/base'
2
+
3
+ require 'puppet-herald/app/configuration'
4
+ require 'puppet-herald/app/api'
5
+ require 'puppet-herald/app/frontend'
6
+
7
+ # A module for Herald
8
+ module PuppetHerald
9
+ # Class for an Herald sinatra application
10
+ class Application < Sinatra::Application
11
+ use PuppetHerald::App::Configuration
12
+ use PuppetHerald::App::Frontend
13
+ use PuppetHerald::App::Api
14
+
15
+ class << self
16
+ # Executes the Herald application
17
+ #
18
+ # @param options [Hash] an extra options for Rack server
19
+ # @param block [block] an extra configuration block
20
+ # @return [Sinatra::Application] an Herald application
21
+ def run!(options = {}, &block)
22
+ PuppetHerald::App::Configuration.dbmigrate!
23
+ super options, *block
24
+ end
25
+ end
26
+ end
27
+ end
@@ -4,77 +4,98 @@ require 'puppet-herald'
4
4
  require 'puppet-herald/version'
5
5
  require 'puppet-herald/database'
6
6
 
7
+ # A module for Herald
7
8
  module PuppetHerald
9
+ # A CLI class
8
10
  class CLI
11
+ # Initialize CLI
12
+ # @return [CLI] an CLI object
13
+ def initialize
14
+ @logger = Logger.new STDOUT
15
+ @errlogger = Logger.new STDERR
16
+ self
17
+ end
18
+
19
+ # Logger for CLI interface (error and std)
20
+ # @return [Logger] logger for CLI
21
+ attr_reader :logger, :errlogger
9
22
 
10
- @@logger = Logger.new STDOUT
11
- @@errlogger = Logger.new STDERR
12
- @@retcode = 0
23
+ # Executes an Herald app from CLI
24
+ #
25
+ # @param argv [Array] an argv from CLI
26
+ # @return [Integer] a status code for program
27
+ def run!(argv = ARGV)
28
+ PuppetHerald.environment
13
29
 
14
- def self.retcode= val
15
- @@retcode = val if @@retcode == 0
30
+ options = parse_or_kill argv, 2
31
+ run_or_kill options, 1
32
+ Kernel.exit 0
16
33
  end
17
34
 
18
- def self.logger
19
- @@logger
35
+ protected
36
+
37
+ # Parse an ARGV command line arguments
38
+ # @param argv [Array] an argv from CLI
39
+ # @return [Hash] options to use by application
40
+ def parse(argv)
41
+ options = parser.process!(argv)
42
+
43
+ logger.info "Starting #{PuppetHerald::NAME} v#{PuppetHerald::VERSION} in #{PuppetHerald.environment}..."
44
+ PuppetHerald.database.dbconn = options[:dbconn]
45
+ PuppetHerald.database.passfile = options[:passfile]
46
+ PuppetHerald.database.spec(true)
47
+ options
20
48
  end
21
49
 
22
- def self.errlogger
23
- @@errlogger
50
+ private
51
+
52
+ def run_or_kill(options, retcode)
53
+ require 'puppet-herald/application'
54
+ PuppetHerald::Application.run! options
55
+ rescue StandardError => ex
56
+ bug = PuppetHerald.bug(ex)
57
+ errlogger.fatal "Unexpected error occured, mayby a bug?\n\n#{bug[:message]}\n\n#{bug[:help]}"
58
+ Kernel.exit retcode
24
59
  end
25
60
 
26
- def self.run! argv=ARGV
27
- @@retcode = 0
28
- begin
29
- options = parse_options argv
30
- require 'puppet-herald/app'
31
- PuppetHerald::App.run! options
32
- rescue Exception => ex
33
- bug = PuppetHerald.bug(ex)
34
- errlogger.fatal "Unexpected error occured, mayby a bug?\n\n#{bug[:message]}\n\n#{bug[:help]}"
35
- self.retcode = 1
36
- end
37
- Kernel::exit @@retcode
61
+ def parse_or_kill(argv, retcode)
62
+ return parse argv
63
+ rescue StandardError => ex
64
+ errlogger.fatal "Database configuration is invalid!\n\n#{ex.message}"
65
+ Kernel.exit retcode
38
66
  end
39
67
 
40
- def self.parse_options argv
41
- usage = ""
42
- banner = <<-eos
68
+ def banner
69
+ txt = <<-eos
43
70
  #{PuppetHerald::NAME} v#{PuppetHerald::VERSION} - #{PuppetHerald::SUMMARY}
44
71
 
45
72
  #{PuppetHerald::DESCRIPTION}
46
73
 
47
- Usage: #{$0} [options]
74
+ Usage: #{$PROGRAM_NAME} [options]
48
75
 
49
76
  For --dbconn option you can use both PostgreSQL and SQLite3 (postgresql://host/db, sqlite://file/path).
50
77
  CAUTION! For security reasons, don't pass password in connection string, use --passfile option!
51
78
 
52
79
  eos
80
+ txt
81
+ end
82
+
83
+ def parser
53
84
  home = File.expand_path('~')
54
85
  defaultdb = "sqlite://#{home}/pherald.db"
55
86
  defaultdbpass = "#{home}/.pherald.pass"
56
- parser = Parser.new do |p|
87
+ Parser.new do |p|
57
88
  p.banner = banner
58
89
  p.version = PuppetHerald::VERSION
59
- p.option :bind, "Hostname to bind to", :default => 'localhost'
60
- p.option :port, "Port to use", :default => 11303, :value_satisfies => lambda {|x| x >= 100 && x <= 65000}
61
- p.option :dbconn, "Connection string to database, see info above", :default => defaultdb
62
- p.option :passfile, "If using postgresql, this file will be read for password to database", :default => defaultdbpass
63
- end
64
- options = parser.process!(argv)
65
-
66
- logger.info "Starting #{PuppetHerald::NAME} v#{PuppetHerald::VERSION} in #{PuppetHerald::environment}..."
67
- PuppetHerald::Database.dbconn = options[:dbconn]
68
- PuppetHerald::Database.passfile = options[:passfile]
69
- begin
70
- PuppetHerald::Database.spec :echo => true
71
- rescue Exception => ex
72
- errlogger.fatal "Database configuration is invalid!\n\n#{ex.message}"
73
- self.retcode = 2
74
- raise ex
90
+ p.option :bind, 'Hostname to bind to', default: 'localhost'
91
+ p.option :port, 'Port to use', default: 11_303, value_satisfies: ->(x) { x >= 1 && x <= 65_535 }
92
+ p.option :dbconn, 'Connection string to database, see info above', default: defaultdb
93
+ p.option(
94
+ :passfile,
95
+ 'If using postgresql, this file will be read for password to database',
96
+ default: defaultdbpass
97
+ )
75
98
  end
76
-
77
- return options
78
99
  end
79
100
  end
80
- end
101
+ end
@@ -0,0 +1,33 @@
1
+ require 'net/http'
2
+
3
+ # A module for Herald
4
+ module PuppetHerald
5
+ # A client class for Herald
6
+ class Client
7
+ # Constructs a client
8
+ #
9
+ # @param host [String] a host to connect to, default to +'localhost'+
10
+ # @param port [Integer] a port to connect to, default to +11303+
11
+ # @return [PuppetHerald::Client] a client instance
12
+ def initialize(host = 'localhost', port = 11_303)
13
+ @host = host
14
+ @port = port
15
+ self
16
+ end
17
+
18
+ # Process a puppet report and sends it to Herald
19
+ #
20
+ # @param report [Puppet::Transaction::Report] a puppet report
21
+ # @param block [Proc] a optional block that can modify request before sending
22
+ # @return [Boolean] true if everything is ok
23
+ def process(report, &block)
24
+ path = '/api/v1/reports'
25
+ header = { 'Content-Type' => 'application/yaml' }
26
+ req = Net::HTTP::Post.new(path, initheader = header) # rubocop:disable all
27
+ req.body = report.to_yaml
28
+ block.call(req) if block
29
+ Net::HTTP.new(@host, @port).start { |http| http.request(req) }
30
+ true
31
+ end
32
+ end
33
+ end
@@ -1,57 +1,101 @@
1
1
  require 'fileutils'
2
2
  require 'logger'
3
3
 
4
+ # A module for Herald
4
5
  module PuppetHerald
6
+ # A class for a database configuration
5
7
  class Database
8
+ def initialize
9
+ @dbconn = nil
10
+ @passfile = nil
11
+ @logger = Logger.new STDOUT
12
+ end
13
+
14
+ # Gets a logger for database
15
+ # @return [Logger] a logger
16
+ attr_reader :logger
17
+
18
+ # Sets a database connection
19
+ # @return [String] a dbconnection string
20
+ attr_writer :dbconn
6
21
 
7
- @@dbconn = nil
8
- @@passfile = nil
9
- @@logger = Logger.new STDOUT
22
+ # Sets a passfile
23
+ # @return [String] a password file
24
+ attr_writer :passfile
10
25
 
11
- def self.logger
12
- @@logger
26
+ # Compiles a spec for database creation
27
+ #
28
+ # @param log [Boolean] should log on screen?
29
+ # @return [Hash] a database configuration
30
+ def spec(log = false)
31
+ connection = process_spec
32
+ print_config(connection, log)
33
+ connection
13
34
  end
14
35
 
15
- def self.dbconn= dbconn
16
- @@dbconn = dbconn
36
+ private
37
+
38
+ def process_spec
39
+ match = validate_conn(@dbconn)
40
+ if %w(sqlite sqlite3).include? match[1]
41
+ connection = sqlite(match)
42
+ else
43
+ connection = postgresql(@dbconn, @passfile)
44
+ end
45
+ connection
17
46
  end
18
47
 
19
- def self.passfile= passfile
20
- @@passfile = passfile
48
+ def validate_conn(dbconn)
49
+ fail 'Connection is not set, can not validate database connection' if dbconn.nil?
50
+ match = dbconn.match(%r{^(sqlite3?|postgres(?:ql)?)://(.+)$})
51
+
52
+ fail "Invalid database connection string given: #{dbconn}" if match.nil?
53
+ match
54
+ end
55
+
56
+ def print_config(connection, log)
57
+ return unless log
58
+ copy = connection.dup
59
+ copy[:password] = '***' unless copy[:password].nil?
60
+ logger.info "Using #{copy.inspect} for database."
21
61
  end
22
62
 
23
- def self.spec echo=false
24
- return nil if @@dbconn.nil?
63
+ def sqlite(match)
25
64
  connection = {}
26
- match = @@dbconn.match(/^(sqlite3?|postgres(?:ql)?):\/\/(.+)$/)
27
- unless match
28
- raise "Invalid database connection string given: #{@@dbconn}"
29
- end
30
- if ['sqlite', 'sqlite3'].include? match[1]
31
- dbname = match[2]
32
- unless dbname.match /^(?:file:)?:mem/
33
- dbname = File.expand_path(dbname)
34
- FileUtils.touch dbname
35
- end
36
- connection[:adapter] = 'sqlite3'
37
- connection[:database] = dbname
38
- else
39
- db = URI.parse @@dbconn
40
- dbname = db.path[1..-1]
41
- connection[:adapter] = db.scheme == 'postgres' ? 'postgresql' : db.scheme
42
- connection[:host] = db.host
43
- connection[:port] = db.port unless db.port.nil?
44
- connection[:username] = db.user.nil? ? dbname : db.user
45
- connection[:password] = File.read(@@passfile).strip
46
- connection[:database] = dbname
47
- connection[:encoding] = 'utf8'
65
+ dbname = match[2]
66
+ unless dbname.match(/^(?:file:)?:mem/)
67
+ dbname = File.expand_path(dbname)
68
+ FileUtils.touch dbname
48
69
  end
49
- if echo
50
- copy = connection.dup
51
- copy[:password] = '***' unless copy[:password].nil?
52
- logger.info "Using #{copy.inspect} for database."
53
- end
54
- return connection
70
+ connection[:adapter] = 'sqlite3'
71
+ connection[:database] = dbname
72
+ connection
73
+ end
74
+
75
+ def postgresql(conn, passfile)
76
+ connection = {}
77
+ db = URI.parse conn
78
+ dbname = db.path[1..-1]
79
+ connection[:adapter] = pgscheme(db.scheme)
80
+ connection[:host] = db.host
81
+ connection[:username] = pguser(db.user, dbname)
82
+ connection[:password] = File.read(passfile).strip
83
+ connection[:database] = dbname
84
+ connection[:encoding] = 'utf8'
85
+ pgport(connection, db.port)
86
+ connection
87
+ end
88
+
89
+ def pgport(connection, port)
90
+ connection[:port] = port unless port.nil?
91
+ end
92
+
93
+ def pgscheme(scheme)
94
+ scheme == 'postgres' ? 'postgresql' : scheme
95
+ end
96
+
97
+ def pguser(user, dbname)
98
+ user.nil? ? dbname : user
55
99
  end
56
100
  end
57
- end
101
+ end
@@ -1,34 +1,40 @@
1
1
  require 'puppet-herald'
2
2
  require 'uglifier'
3
3
 
4
+ # A module for Herald
4
5
  module PuppetHerald
6
+ # A javascript processing class
5
7
  class Javascript
8
+ # Initialize JS class
9
+ def initialize
10
+ @files = nil
11
+ @base = 'lib/puppet-herald/public'
12
+ end
6
13
 
7
- @@files = nil
8
-
9
- @@base = 'lib/puppet-herald/public'
10
-
11
- def self.files
12
- if PuppetHerald::is_in_dev?
13
- @@files = nil
14
- end
15
- if @@files.nil?
16
- public_dir = PuppetHerald::relative_dir(@@base)
14
+ # Returns a list of JS files to be inserted into main HTML
15
+ # @return [Array] list of JS's
16
+ def files
17
+ @files = nil if PuppetHerald.in_dev?
18
+ if @files.nil?
19
+ public_dir = PuppetHerald.relative_dir(@base)
17
20
  all = Dir.chdir(public_dir) { Dir.glob('**/*.js') }
18
- @@files = all.reverse.reject { |file| file.match(/_test\.js$/) }
21
+ @files = all.reverse.reject { |file| file.match(/_test\.js$/) }
19
22
  end
20
- return @@files
23
+ @files
21
24
  end
22
25
 
23
- def self.uglify mapname
24
- sources = files.collect { |file| File.read("#{@@base}/#{file}") }
26
+ # Uglify an application JS's into one minified JS file
27
+ # @param mapname [String] name of source map to be put into uglified JS
28
+ # @return [Hash] a hash with uglified JS and source map
29
+ def uglify(mapname)
30
+ sources = files.collect { |file| File.read("#{@base}/#{file}") }
25
31
  source = sources.join "\n"
26
- uglifier = Uglifier.new(:source_map_url => mapname)
32
+ uglifier = Uglifier.new(source_map_url: mapname)
27
33
  uglified, source_map = uglifier.compile_with_map(source)
28
- return {
34
+ {
29
35
  'js' => uglified,
30
36
  'js.map' => source_map
31
37
  }
32
38
  end
33
39
  end
34
- end
40
+ end
@@ -1,3 +1,10 @@
1
- class LogEntry < ActiveRecord::Base
2
- belongs_to :report
3
- end
1
+ # A module for Herald
2
+ module PuppetHerald
3
+ # module for models
4
+ module Models
5
+ # A log entry model
6
+ class LogEntry < ActiveRecord::Base
7
+ belongs_to :report
8
+ end
9
+ end
10
+ end
@@ -1,7 +1,17 @@
1
- class Node < ActiveRecord::Base
2
- has_many :reports, dependent: :delete_all
1
+ # A module for Herald
2
+ module PuppetHerald
3
+ # module for models
4
+ module Models
5
+ # A node model
6
+ class Node < ActiveRecord::Base
7
+ has_many :reports, dependent: :delete_all
3
8
 
4
- def self.get_with_reports id
5
- Node.where("id = ?", id).includes(:reports).first
9
+ # Gets a node with prefetched reports
10
+ # @param id [Integer] a in of node to get
11
+ # @return [Node, nil] fetched node or nil
12
+ def self.get_with_reports(id)
13
+ Node.joins(:reports).includes(:reports).find_by_id(id)
14
+ end
15
+ end
6
16
  end
7
- end
17
+ end