puppet-herald 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 (50) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +22 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +5 -0
  5. data/README.md +73 -0
  6. data/Rakefile +6 -0
  7. data/bin/puppet-herald +13 -0
  8. data/config.ru +2 -0
  9. data/db/migrate/20141211165540_create_nodes.rb +10 -0
  10. data/db/migrate/20141211171305_create_reports.rb +16 -0
  11. data/db/migrate/20141211171326_create_log_entries.rb +13 -0
  12. data/db/schema.rb +47 -0
  13. data/db/seeds.rb +0 -0
  14. data/lib/puppet-herald.rb +37 -0
  15. data/lib/puppet-herald/app.rb +112 -0
  16. data/lib/puppet-herald/cli.rb +53 -0
  17. data/lib/puppet-herald/database.rb +50 -0
  18. data/lib/puppet-herald/javascript.rb +34 -0
  19. data/lib/puppet-herald/models/log-entry.rb +3 -0
  20. data/lib/puppet-herald/models/node.rb +7 -0
  21. data/lib/puppet-herald/models/report.rb +78 -0
  22. data/lib/puppet-herald/public/.bowerrc +3 -0
  23. data/lib/puppet-herald/public/app.js +27 -0
  24. data/lib/puppet-herald/public/bower.json +15 -0
  25. data/lib/puppet-herald/public/components/artifact/artifact-directive.js +12 -0
  26. data/lib/puppet-herald/public/components/artifact/artifact-directive_test.js +17 -0
  27. data/lib/puppet-herald/public/components/artifact/artifact.js +31 -0
  28. data/lib/puppet-herald/public/components/directives/directives.js +5 -0
  29. data/lib/puppet-herald/public/components/directives/status-button.html +7 -0
  30. data/lib/puppet-herald/public/components/directives/status-button.js +54 -0
  31. data/lib/puppet-herald/public/components/filters/filters.js +12 -0
  32. data/lib/puppet-herald/public/css/herald.css +38 -0
  33. data/lib/puppet-herald/public/img/shield97.svg +17 -0
  34. data/lib/puppet-herald/public/node/node.html +30 -0
  35. data/lib/puppet-herald/public/node/node.js +24 -0
  36. data/lib/puppet-herald/public/nodes/nodes.html +29 -0
  37. data/lib/puppet-herald/public/nodes/nodes.js +24 -0
  38. data/lib/puppet-herald/public/report/report.html +24 -0
  39. data/lib/puppet-herald/public/report/report.js +24 -0
  40. data/lib/puppet-herald/stubs/puppet.rb +29 -0
  41. data/lib/puppet-herald/version.rb +20 -0
  42. data/lib/puppet-herald/views/app.erb +42 -0
  43. data/lib/puppet-herald/views/err500.erb +31 -0
  44. data/puppet-herald.gemspec +62 -0
  45. data/spec/model_helper.rb +2 -0
  46. data/spec/spec_helper.rb +37 -0
  47. data/spec/support/active_record.rb +17 -0
  48. data/spec/unit/fixtures/changed-notify.yaml +278 -0
  49. data/spec/unit/models/report_spec.rb +38 -0
  50. metadata +331 -0
@@ -0,0 +1,50 @@
1
+ require 'fileutils'
2
+
3
+ module PuppetHerald
4
+ class Database
5
+
6
+ @@dbconn = nil
7
+ @@passfile = nil
8
+
9
+ def self.dbconn= dbconn
10
+ @@dbconn = dbconn
11
+ end
12
+
13
+ def self.passfile= passfile
14
+ @@passfile = passfile
15
+ end
16
+
17
+ def self.validate! echo=false
18
+ db = URI.parse(@@dbconn)
19
+ connection = {}
20
+ if db.scheme.to_s.empty?
21
+ raise "Database scheme couldn't be empty! Connection string given: #{@@dbconn}"
22
+ end
23
+ if ['sqlite', 'sqlite3'].include? db.scheme
24
+ file = File.expand_path("#{db.host}#{db.path}")
25
+ FileUtils.touch file
26
+ connection[:adapter] = 'sqlite3'
27
+ connection[:database] = file
28
+ else
29
+ dbname = db.path[1..-1]
30
+ connection[:adapter] = db.scheme == 'postgres' ? 'postgresql' : db.scheme
31
+ connection[:host] = db.host
32
+ connection[:port] = db.port unless db.port.nil?
33
+ connection[:username] = db.user.nil? ? dbname : db.user
34
+ connection[:password] = File.read(@@passfile).strip
35
+ connection[:database] = dbname
36
+ connection[:encoding] = 'utf8'
37
+ end
38
+ if echo
39
+ copy = connection.dup
40
+ copy[:password] = '***' unless copy[:password].nil?
41
+ puts "Using #{copy.inspect} for database."
42
+ end
43
+ return connection
44
+ end
45
+
46
+ def self.setup app
47
+ ActiveRecord::Base.establish_connection(validate!)
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,34 @@
1
+ require 'puppet-herald'
2
+ require 'uglifier'
3
+
4
+ module PuppetHerald
5
+ class Javascript
6
+
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)
17
+ all = Dir.chdir(public_dir) { Dir.glob('**/*.js') }
18
+ @@files = all.reverse.reject { |file| file.match(/_test\.js$/) }
19
+ end
20
+ return @@files
21
+ end
22
+
23
+ def self.uglify mapname
24
+ sources = files.collect { |file| File.read("#{@@base}/#{file}") }
25
+ source = sources.join "\n"
26
+ uglifier = Uglifier.new(:source_map_url => mapname)
27
+ uglified, source_map = uglifier.compile_with_map(source)
28
+ return {
29
+ 'js' => uglified,
30
+ 'js.map' => source_map
31
+ }
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,3 @@
1
+ class LogEntry < ActiveRecord::Base
2
+ belongs_to :report
3
+ end
@@ -0,0 +1,7 @@
1
+ class Node < ActiveRecord::Base
2
+ has_many :reports, dependent: :delete_all
3
+
4
+ def self.get_with_reports id
5
+ Node.where("id = ?", id).includes(:reports).first
6
+ end
7
+ end
@@ -0,0 +1,78 @@
1
+ require 'puppet-herald/models/log-entry'
2
+ require 'puppet-herald/models/node'
3
+ require 'puppet-herald/stubs/puppet'
4
+
5
+ class Report < ActiveRecord::Base
6
+ belongs_to :node
7
+ has_many :log_entries, dependent: :delete_all
8
+
9
+ def self.get_with_log_entries id
10
+ Report.where("id = ?", id).includes(:log_entries).first
11
+ end
12
+
13
+ def self.create_from_yaml yaml
14
+ parsed = parse_yaml yaml
15
+ report = Report.new
16
+
17
+ parse_properties parsed, report
18
+ parse_logs parsed, report
19
+ node = parse_node parsed, report
20
+
21
+ report.save
22
+ node.save
23
+ return report
24
+ end
25
+
26
+ private
27
+
28
+ def self.parse_node parsed, report
29
+ node = Node.find_by_name(parsed.host)
30
+ if node.nil?
31
+ node = Node.new
32
+ node.name = parsed.host
33
+ node.no_of_reports = 0
34
+ end
35
+ report.node = node
36
+ node.reports << report
37
+ node.no_of_reports += 1
38
+ node.status = parsed.status
39
+ node.last_run = parsed.time
40
+ return node
41
+ end
42
+
43
+ def self.parse_properties parsed, report
44
+ attr_to_copy = [
45
+ 'status', 'environment',
46
+ 'transaction_uuid', 'time',
47
+ 'puppet_version', 'kind',
48
+ 'host', 'configuration_version'
49
+ ]
50
+ copy_attrs parsed, report, attr_to_copy
51
+ end
52
+
53
+ def self.parse_logs parsed, report
54
+ parsed.logs.each do |in_log|
55
+ log = LogEntry.new
56
+ attr_to_copy = [
57
+ 'level', 'message',
58
+ 'source', 'time'
59
+ ]
60
+ copy_attrs in_log, log, attr_to_copy
61
+ log.report = report
62
+ report.log_entries << log
63
+ end
64
+ end
65
+
66
+ def self.parse_yaml yaml
67
+ require 'yaml'
68
+ raw = YAML.parse yaml
69
+ raw.to_ruby
70
+ end
71
+
72
+ def self.copy_attrs from, to, attrs
73
+ attrs.each do |at|
74
+ value = from.send at
75
+ to.send "#{at}=", value
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,3 @@
1
+ {
2
+ "directory": "bower_components"
3
+ }
@@ -0,0 +1,27 @@
1
+ (function(){
2
+ 'use strict';
3
+
4
+ var app = angular.module('herald' , [
5
+ 'ngRoute',
6
+ 'herald.nodes',
7
+ 'herald.node',
8
+ 'herald.report',
9
+ 'herald.filters',
10
+ 'herald.artifact'
11
+ ]);
12
+
13
+ app.config(['$routeProvider', function($routeProvider) {
14
+ $routeProvider.otherwise({redirectTo: '/nodes'});
15
+ }]);
16
+
17
+ app.factory('appService', function() {
18
+ return {
19
+ page: 'home'
20
+ };
21
+ });
22
+
23
+ app.controller('AppController', ['appService', function(appService) {
24
+ this.page = appService.page;
25
+ }]);
26
+
27
+ })();
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "puppet-herald",
3
+ "description": "Report processor for Puppet",
4
+ "version": "0.1.0",
5
+ "homepage": "https://github.com/wavesoftware/puppet-herald",
6
+ "license": "Apache 2.0",
7
+ "private": false,
8
+ "dependencies": {
9
+ "angular": "1.3.x",
10
+ "angular-route": "1.3.x",
11
+ "angular-loader": "1.3.x",
12
+ "angular-mocks": "~1.3.x",
13
+ "html5-boilerplate": "~4.3.0"
14
+ }
15
+ }
@@ -0,0 +1,12 @@
1
+ 'use strict';
2
+
3
+ angular.module('herald.artifact.artifact-directive', [])
4
+
5
+ .directive('appVersion', function() {
6
+ return {
7
+ restrict: 'A',
8
+ controller: 'ArtifactController as artifact',
9
+ transclude: true,
10
+ template: '<span>{{ artifact.version }}</span>'
11
+ };
12
+ });
@@ -0,0 +1,17 @@
1
+ 'use strict';
2
+
3
+ describe('herald.artifact module', function() {
4
+ beforeEach(module('myApp.artifact'));
5
+
6
+ describe('app-version directive', function() {
7
+ it('should print current version', function() {
8
+ module(function($provide) {
9
+ $provide.value('artifact', { version: 'TEST_VER' });
10
+ });
11
+ inject(function($compile, $rootScope) {
12
+ var element = $compile('<span app-version></span>')($rootScope);
13
+ expect(element.text()).toEqual('TEST_VER');
14
+ });
15
+ });
16
+ });
17
+ });
@@ -0,0 +1,31 @@
1
+ 'use strict';
2
+
3
+ (function(){
4
+
5
+ 'use strict';
6
+
7
+ var module = angular.module('herald.artifact', [
8
+ 'herald.artifact.artifact-directive'
9
+ ]);
10
+
11
+ module.controller('ArtifactController', ['$http', function($http) {
12
+ this.version = '0.0.0';
13
+ this.homepage = '#';
14
+ this.summary = null;
15
+ this.package = null;
16
+ this.license = null;
17
+ this.name = null;
18
+
19
+ var self = this;
20
+
21
+ $http.get('/version.json').success(function(data) {
22
+ self.version = data.version;
23
+ self.homepage = data.homepage;
24
+ self.summary = data.summary;
25
+ self.package = data.package;
26
+ self.license = data.license;
27
+ self.name = data.name;
28
+ })
29
+ }]);
30
+
31
+ })();
@@ -0,0 +1,5 @@
1
+ 'use strict';
2
+
3
+ angular.module('herald.directives', [
4
+ 'herald.directives.status-button'
5
+ ]);
@@ -0,0 +1,7 @@
1
+ <button
2
+ type="button" class="
3
+ btn btn-xs btn-{{ status | colorizeStatus }}
4
+ glyphicon glyphicon-{{ status | iconizeStatus }}
5
+ "
6
+ ng-click="navigate()"
7
+ aria-hidden="true"> {{ status | uppercase }}</button>
@@ -0,0 +1,54 @@
1
+ (function() {
2
+
3
+ 'use strict';
4
+
5
+ var module = angular.module('herald.directives.status-button', [
6
+
7
+ ]);
8
+
9
+ module.controller('StatusButtonController', ['$location', '$scope', function($location, $scope) {
10
+
11
+ $scope.$location = $location;
12
+
13
+ $scope.navigate = function() {
14
+ var target = this.route.replace(':id', this.id);
15
+ this.$location.path(target);
16
+ };
17
+
18
+ }]);
19
+
20
+ module.directive('ngStatusButton', function() {
21
+ return {
22
+ restrict: 'E',
23
+ scope: {
24
+ status: '=',
25
+ id: '=',
26
+ route: '='
27
+ },
28
+ controller: 'StatusButtonController',
29
+ templateUrl: 'components/directives/status-button.html'
30
+ };
31
+ });
32
+
33
+ module.filter('colorizeStatus', function() {
34
+ return function(input) {
35
+ switch(input) {
36
+ case 'unchanged': return 'success';
37
+ case 'changed': return 'info';
38
+ case 'failed': return 'danger';
39
+ default: return 'default';
40
+ }
41
+ }
42
+ });
43
+
44
+ module.filter('iconizeStatus', function() {
45
+ return function(input) {
46
+ switch(input) {
47
+ case 'unchanged': return 'ok';
48
+ case 'changed': return 'pencil';
49
+ case 'failed': return 'remove';
50
+ default: return 'asterisk';
51
+ }
52
+ }
53
+ });
54
+ })();
@@ -0,0 +1,12 @@
1
+ 'use strict';
2
+
3
+ angular.module('herald.filters', [
4
+ ])
5
+
6
+ .filter('capitalize', function() {
7
+ return function(input) {
8
+ var first = input.substring(0, 1);
9
+ var rest = input.substring(1);
10
+ return first.toUpperCase() + rest;
11
+ };
12
+ });
@@ -0,0 +1,38 @@
1
+ .herald-table-report > tbody > tr {
2
+ font-family: monospace;
3
+ background-color: #2C001E;
4
+ white-space: pre;
5
+ color: #777;
6
+ }
7
+
8
+ .table-hover.herald-table-report > tbody > tr:hover {
9
+ background-color: #360025;
10
+ }
11
+
12
+ .table-condensed.herald-table-report > tbody > tr > td {
13
+ padding: 1px 10px;
14
+ }
15
+
16
+ .herald-table-report > tbody > tr > td {
17
+ border-color: #2C001E;
18
+ }
19
+
20
+ .herald-table-report > tbody > tr > td.herald-log-notice {
21
+ color: #eeeeec;
22
+ }
23
+
24
+ .herald-table-report > tbody > tr > td.herald-log-debug {
25
+ color: #079898;
26
+ }
27
+
28
+ .herald-table-report > tbody > tr > td.herald-log-err {
29
+ color: #ef2929;
30
+ }
31
+
32
+ .herald-table-report > tbody > tr > td.herald-log-warning {
33
+ color: #e9be00;
34
+ }
35
+
36
+ .herald-table-report > tbody > tr > td.herald-log-info {
37
+ color: #4f920f;
38
+ }
@@ -0,0 +1,17 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
3
+ <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
4
+ <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
5
+ width="48px" height="48px" viewBox="0 0 48 48" enable-background="new 0 0 48 48" xml:space="preserve">
6
+ <g>
7
+ <path d="M44.083,7.732L24.745,1.693c-0.484-0.151-1.006-0.151-1.49,0L3.916,7.732c-1.044,0.326-1.755,1.292-1.755,2.386
8
+ c0,26.74,13.511,33.13,20.77,36.563c0.339,0.16,0.704,0.24,1.069,0.24s0.73-0.08,1.069-0.24
9
+ c7.258-3.434,20.769-9.825,20.769-36.563C45.838,9.024,45.127,8.058,44.083,7.732z M24,41.653
10
+ C17.108,38.342,7.701,32.857,7.184,11.95L24,6.698l16.815,5.251C40.298,32.856,30.892,38.341,24,41.653z"/>
11
+ <path d="M23.239,13.104l-1.989,6.593l-6.886-0.145c-0.35-0.007-0.663,0.215-0.771,0.548c-0.108,0.333,0.015,0.697,0.302,0.896
12
+ l5.656,3.93l-2.266,6.504c-0.115,0.331,0,0.697,0.282,0.903c0.283,0.206,0.668,0.202,0.946-0.01l5.485-4.165l5.485,4.165
13
+ c0.278,0.211,0.663,0.215,0.946,0.01c0.282-0.206,0.397-0.572,0.282-0.903l-2.266-6.504l5.656-3.93
14
+ c0.287-0.199,0.41-0.564,0.302-0.896c-0.108-0.333-0.422-0.556-0.771-0.548l-6.886,0.145l-1.989-6.593
15
+ c-0.102-0.335-0.41-0.564-0.76-0.564S23.341,12.769,23.239,13.104z"/>
16
+ </g>
17
+ </svg>
@@ -0,0 +1,30 @@
1
+ <div class="panel panel-primary" ng-controller="NodeController as ctrl">
2
+ <!-- Default panel contents -->
3
+ <div class="panel-heading">Reports for {{ ctrl.node.name }} <span class="badge">{{ ctrl.node.reports.length }}</span></div>
4
+
5
+ <!-- Table -->
6
+ <table class="table table-striped table-hover">
7
+ <thead>
8
+ <tr>
9
+ <th>#</th>
10
+ <th>Configuration version</th>
11
+ <th>Status</th>
12
+ <th>Environment</th>
13
+ <th>Last run</th>
14
+ </tr>
15
+ </thead>
16
+ <tbody>
17
+ <tr ng-repeat="report in ctrl.node.reports | orderBy:'-time'">
18
+ <td>{{ $index + 1 }}</td>
19
+ <td>{{ report.configuration_version }}</td>
20
+ <td><ng-status-button
21
+ status="report.status"
22
+ id="report.id"
23
+ route="'/report/:id'"
24
+ ></ng-status-button></td>
25
+ <td>{{ report.environment }}</td>
26
+ <td am-time-ago="report.time"></td>
27
+ </tr>
28
+ </tbody>
29
+ </table>
30
+ </div>