Blob Blame History Raw
#!/usr/bin/perl -w
# Copyright Lars Vogdt <>
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the
# Free Software Foundation, Inc.,
# 51 Franklin Street,
# Fifth Floor,
# Boston, MA  02110-1301,
# USA.
use strict;
use warnings;
use Data::Dumper;
use File::Basename;
use Getopt::Long;
use Scalar::Util qw(reftype);
use YAML::Tiny;

my $pillar_id_path='pillar/id';
my @wanted=qw(aliases responsible virt_cluster weburls hostusage partners description documentation);
my $print_help='';
my $full_html='';
my $dumpforredmine='';
my $header='';
my $footer='';
my $table_style="width='1820' border='1' style='table-layout:fixed;'";
my $td_style="style='word-wrap:break-word; padding-left:.5em'";
my $td_color_even='#f6f6f6';
my $td_color_odd='fcf8e3';
my $freeipa_user_url='';

sub getFiles($$) {
    my $path=shift;
    my $ending=shift;
    opendir(DIR,"$path") || die ("Could not open $path: $! $?!\n");
    my @Files= grep {/.*\Q$ending\E/} readdir(DIR);
    my $number = @Files;
    if ( $number gt 0 ){
      my @return;
      foreach (@Files){
        next if ( $_ eq "." );
        next if ( $_ eq ".." );
        push @return,$_;
     return \@return;
 return 0;

sub usage($){
    my $exitcode=shift || 1;
    print <<EOF

$0 Usage Information:

 $0 [OPTION]

 -d <path> | --pillardir <path>  : Path to the files with the Pillar information.
 -t        | --tableonly         : Print only the HTML table (for inclusion in Redmine).

 -r        | --dumpforredmine    : Print including a short manual for direct copy and paste into the Redmine wiki.
 -h        | --help              : This help

Use this script to generate a HTML page (or just a HTML table), based on the 
static pillar information provided in YAML files.
Currently the list of tags that end up in the HTML is hardcoded in the script. If you 
want to change it, please edit the line starting with 
  my \@wanted=...


   exit $exitcode;

my $redmine_header="

This is a placeholder for some wiki pages containing more details about machines and their setups. The list below is generated based on the [pillar/id]( information in [our Salt Repo in Gitlab](

You need a GIT checkout of the repository and the following perl modules installed on your system:

  sudo zypper in 'perl(File::Basename)' 'perl(Getopt::Long)' 'perl(Scalar::Util)' 'perl(YAML::Tiny)'

After that you should be able to call the script from the root of the checked out Salt repository:

  bin/ -t

* If you are administrator of a machine, please keep the information of the machine in GIT up to date, so others might be able to step in if there is a problem with the machine and you are not available.
* If you are 'just interested' or needed to fix something on the machine, please get in contact with the administrator mentioned

List of machines


    'h|help'            => \$print_help,
    'd|pillardir=s'     => \$pillar_id_path,
    'r|dumpforredmine'  => \$dumpforredmine,
    't|tableonly'       => \$full_html,

usage(0) if ($print_help);

my $sls_files=getFiles($pillar_id_path,'.sls');

if (! $full_html){
$header="<!doctype hmtl>
    <meta charset='utf-8'>
    <title>Machine list</title>
    <style type='text/css' media='print'>
          size: auto; /* auto is the initial value */
          margin: 2mm 4mm 2mm 4mm; /* this affects the margin in the printer settings */
	  thead {display: table-header-group;}
	<style type='text/css' media='screen'>
      /* thead { display: block; } */
      ul { list-style-type: disc; 
           padding-left:1.5em; }
$footer="  </body>

if (! $dumpforredmine){
	print "$header";

my $javascript="
function sortTable(n) {
  var table, rows, switching, i, x, y, shouldSwitch, dir, switchcount = 0;
  table = document.getElementById('machines');
  switching = true;
  // Set the sorting direction to ascending:
  dir = 'asc';
  /* Make a loop that will continue until
  no switching has been done: */
  while (switching) {
    // Start by saying: no switching is done:
    switching = false;
    rows = table.rows;
    /* Loop through all table rows (except the
    first, which contains table headers): */
    for (i = 1; i < (rows.length - 1); i++) {
      // Start by saying there should be no switching:
      shouldSwitch = false;
      /* Get the two elements you want to compare,
      one from current row and one from the next: */
      x = rows[i].getElementsByTagName('TD')[n];
      y = rows[i + 1].getElementsByTagName('TD')[n];
      /* Check if the two rows should switch place,
      based on the direction, asc or desc: */
      if (dir == 'asc') {
        if (x.innerHTML.toLowerCase() > y.innerHTML.toLowerCase()) {
          // If so, mark as a switch and break the loop:
          shouldSwitch = true;
      } else if (dir == 'desc') {
        if (x.innerHTML.toLowerCase() < y.innerHTML.toLowerCase()) {
          // If so, mark as a switch and break the loop:
          shouldSwitch = true;
    if (shouldSwitch) {
      /* If a switch has been marked, make the switch
      and mark that a switch has been done: */
      rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
      switching = true;
      // Each time a switch is done, increase this count by 1:
      switchcount ++;
    } else {
      /* If no switching has been done AND the direction is asc,
      set the direction to desc and run the while loop again. */
      if (switchcount == 0 && dir == 'asc') {
        dir = 'desc';
        switching = true;

if ( "$sls_files" ne "0" ){
    my $i=1;
	if ( $dumpforredmine ){
		print "$redmine_header\n";	
  # print "$javascript\n";
    print "<table id='machines' $table_style><theader>\n";
#	print "|# ";
#	print "|Hostname ";
	print "<th>#</th><th>Hostname</th>";
	my $td=1;
    foreach my $entry (sort(@wanted)){
	    print "<td>$entry";
        print "</td>\n";
        print "</theader></tbody>\n";
#    foreach my $entry (sort(@wanted)){
#            print "|--- ";
#    }
#        print "|\n";

    for my $file (sort (@$sls_files)){
	my $hostname=basename("$file",'.sls');
	$hostname=~ s/_/./g;
   	print "<tr><td>$i</td></td>$hostname</td>";
	my $yaml = YAML::Tiny->read("$pillar_id_path/$file");
	my $grains=$yaml->[0];
	# print Data::Dumper->Dump([$grains])."\n";
	foreach my $entry (sort(@wanted)){
		print "<td>";
		if (defined($grains->{'grains'}->{$entry})){
			my $type=reftype $grains->{'grains'}->{$entry};
			if (defined($type) && "$type" eq 'ARRAY'){
				print "<ul>\n";
				if (@{$grains->{'grains'}->{$entry}} > 0){
					foreach my $string (sort(@{$grains->{'grains'}->{$entry}})){
						if ("$string" =~ m/^http.*/){
							print "<li>[$string]($string)</li>\n";
						else {
							if ("$entry" eq "partners"){
								print "<li>[$string](#$string)</li>\n";
							elsif ("$entry" eq "responsible"){
								print "<li>[$string]($freeipa_user_url/$string)</li>\n";
							else {
								print "<li>$string</li>\n";
				print "</ul>\”";
			else {
				print "$grains->{'grains'}->{$entry}" if ($grains->{'grains'}->{$entry} ne "");
		else {
			print "&nbsp;";
	print "</td></tr>\n";
	print "</tbody></table>\n";
print "$footer\n";