puppet-herald 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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>