puppet-herald 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +22 -0
- data/.rspec +2 -0
- data/Gemfile +5 -0
- data/README.md +73 -0
- data/Rakefile +6 -0
- data/bin/puppet-herald +13 -0
- data/config.ru +2 -0
- data/db/migrate/20141211165540_create_nodes.rb +10 -0
- data/db/migrate/20141211171305_create_reports.rb +16 -0
- data/db/migrate/20141211171326_create_log_entries.rb +13 -0
- data/db/schema.rb +47 -0
- data/db/seeds.rb +0 -0
- data/lib/puppet-herald.rb +37 -0
- data/lib/puppet-herald/app.rb +112 -0
- data/lib/puppet-herald/cli.rb +53 -0
- data/lib/puppet-herald/database.rb +50 -0
- data/lib/puppet-herald/javascript.rb +34 -0
- data/lib/puppet-herald/models/log-entry.rb +3 -0
- data/lib/puppet-herald/models/node.rb +7 -0
- data/lib/puppet-herald/models/report.rb +78 -0
- data/lib/puppet-herald/public/.bowerrc +3 -0
- data/lib/puppet-herald/public/app.js +27 -0
- data/lib/puppet-herald/public/bower.json +15 -0
- data/lib/puppet-herald/public/components/artifact/artifact-directive.js +12 -0
- data/lib/puppet-herald/public/components/artifact/artifact-directive_test.js +17 -0
- data/lib/puppet-herald/public/components/artifact/artifact.js +31 -0
- data/lib/puppet-herald/public/components/directives/directives.js +5 -0
- data/lib/puppet-herald/public/components/directives/status-button.html +7 -0
- data/lib/puppet-herald/public/components/directives/status-button.js +54 -0
- data/lib/puppet-herald/public/components/filters/filters.js +12 -0
- data/lib/puppet-herald/public/css/herald.css +38 -0
- data/lib/puppet-herald/public/img/shield97.svg +17 -0
- data/lib/puppet-herald/public/node/node.html +30 -0
- data/lib/puppet-herald/public/node/node.js +24 -0
- data/lib/puppet-herald/public/nodes/nodes.html +29 -0
- data/lib/puppet-herald/public/nodes/nodes.js +24 -0
- data/lib/puppet-herald/public/report/report.html +24 -0
- data/lib/puppet-herald/public/report/report.js +24 -0
- data/lib/puppet-herald/stubs/puppet.rb +29 -0
- data/lib/puppet-herald/version.rb +20 -0
- data/lib/puppet-herald/views/app.erb +42 -0
- data/lib/puppet-herald/views/err500.erb +31 -0
- data/puppet-herald.gemspec +62 -0
- data/spec/model_helper.rb +2 -0
- data/spec/spec_helper.rb +37 -0
- data/spec/support/active_record.rb +17 -0
- data/spec/unit/fixtures/changed-notify.yaml +278 -0
- data/spec/unit/models/report_spec.rb +38 -0
- 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,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,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,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,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>
|