You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by da...@apache.org on 2017/01/27 16:53:28 UTC

[07/36] incubator-trafficcontrol git commit: Work in progress of automating postinstall

Work in progress of automating postinstall


Project: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/commit/c0545f7d
Tree: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/tree/c0545f7d
Diff: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/diff/c0545f7d

Branch: refs/heads/master
Commit: c0545f7d2690aed814fd1f9f1cb57e3f5db7b116
Parents: 5be5a7c
Author: peryder <pe...@cisco.com>
Authored: Wed Nov 9 17:05:21 2016 -0500
Committer: Dan Kirkwood <da...@gmail.com>
Committed: Fri Jan 27 09:52:53 2017 -0700

----------------------------------------------------------------------
 .../install/bin/build_trafficops_perl_library   |  44 +-
 traffic_ops/install/bin/input.json              |  82 ++++
 traffic_ops/install/bin/postinstall-new         | 419 +++++++++++++++++++
 traffic_ops/install/lib/InstallUtils.pm         |  42 +-
 4 files changed, 553 insertions(+), 34 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/c0545f7d/traffic_ops/install/bin/build_trafficops_perl_library
----------------------------------------------------------------------
diff --git a/traffic_ops/install/bin/build_trafficops_perl_library b/traffic_ops/install/bin/build_trafficops_perl_library
index 684916d..33eda0c 100755
--- a/traffic_ops/install/bin/build_trafficops_perl_library
+++ b/traffic_ops/install/bin/build_trafficops_perl_library
@@ -1,5 +1,6 @@
 #!/usr/bin/perl
 #
+# Copyright 2015 Comcast Cable Communications Management, LLC
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -14,6 +15,10 @@
 # limitations under the License.
 #
 
+use lib qw(/opt/traffic_ops/install/lib /opt/traffic_ops/install/lib/perl5 /opt/traffic_ops/app/local/lib/perl5 /opt/traffic_ops/app/lib);
+$ENV{PATH}     = "/opt/traffic_ops/install/bin:$ENV{PATH}";
+$ENV{PERL5LIB} = "/opt/traffic_ops/install/lib:/opt/traffic_ops/install/lib/perl5:/opt/traffic_ops/app/local/lib/perl5:/opt/traffic_ops/app/lib";
+
 use strict;
 use warnings;
 use Getopt::Std;
@@ -21,6 +26,9 @@ use InstallUtils;
 
 our ($opt_i);
 
+my $auto = $ARGV[0];
+print $auto;
+
 my @dependencies = (
 	"expat-devel",   "mod_ssl",      "mkisofs",     "libpcap",  "libpcap-devel", "libcurl",
 	"libcurl-devel", "mysql-server", "mysql-devel", "openssl",  "cpan",          "gcc",
@@ -34,38 +42,6 @@ compiler will be installed on this machine.
 
 EOF
 
-sub promptUser {
-	my ( $promptString, $defaultValue, $noEcho ) = @_;
-
-	if ($defaultValue) {
-		print $promptString, " [", $defaultValue, "]:  ";
-	}
-	else {
-		print $promptString, ":  ";
-	}
-
-	if ( defined $noEcho && $noEcho ) {
-		my $response = read_password('');
-		if ( ( !defined $response || $response eq '' ) && ( defined $defaultValue && $defaultValue ne '' ) ) {
-			$response = $defaultValue;
-		}
-		return $response;
-	}
-	else {
-		$| = 1;
-		$_ = <STDIN>;
-		chomp;
-
-		if ("$defaultValue") {
-			return $_ ? $_ : $defaultValue;
-		}
-		else {
-			return $_;
-		}
-		return $_;
-	}
-}
-
 sub trim {
 	my $str = shift;
 
@@ -96,7 +72,9 @@ if ( $ENV{USER} ne "root" ) {
 
 print $msg;
 
-promptUser( "Hit ENTER to continue", "" );
+if ( !$auto ) {
+	InstallUtils::promptUser( "Hit ENTER to continue", "" );
+}
 
 chdir("/opt/traffic_ops/app");
 

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/c0545f7d/traffic_ops/install/bin/input.json
----------------------------------------------------------------------
diff --git a/traffic_ops/install/bin/input.json b/traffic_ops/install/bin/input.json
new file mode 100644
index 0000000..8428eec
--- /dev/null
+++ b/traffic_ops/install/bin/input.json
@@ -0,0 +1,82 @@
+{
+	"testdb.conf": [
+		{
+			"Database type": "mysql",
+			"config_var": "type"
+		},
+		{
+			"Database name": "traffic_ops",
+			"config_var": "dbname"
+		},
+		{
+			"Database server hostname IP or FQDN": "localhost",
+			"config_var": "hostname"
+		},
+		{
+			"Database port number": "3306",
+			"config_var": "port"
+		},
+                {
+                        "Root database user": "root",
+                        "config_var": "root_user"
+                },
+                {
+                        "Root database password": "default",
+                        "config_var": "root_passwd"
+                }
+	],
+	"testtodb.conf": [
+                {
+                        "Traffic Ops database user": "root",
+                        "config_var": "dbAdminUser"
+                },
+                {
+                        "Password for Traffic Ops database user": "default",
+                        "config_var": "dbAdminPw"
+                }
+	],
+	"testcdn.conf": [
+		{
+			"Generate a new secret?": "yes",
+			"config_var": "genSecret"
+		},
+		{
+			"Number of secrets to keep?": "10",
+			"config_var": "keepSecrets"
+		}
+	],
+	"testldap.conf": [
+		{
+			"Do you want to set up LDAP?": "no",
+			"config_var": "setupLdap"
+		},
+		{
+			"LDAP server hostname": "",
+			"config_var": "hostname"
+		},
+		{
+			"LDAP Admin DN": "",
+			"config_var": "admin_dn"
+		},
+		{
+			"LDAP Admin Password": "",
+			"config_var": "password"
+		},
+		{
+			"LDAP Search Base": "",
+			"config_var": "search_base"
+		}
+	],
+	"testpost_install.json": [],
+	"testusers.json": [
+		{
+			"Administration username for Traffic Ops": "admin",
+			"config_var": "tmAdminUser"
+		},
+		{
+			"Password for the admin user": "default",
+			"config_var": "tmAdminPw"
+		}
+	],
+	"testprofiles/": []
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/c0545f7d/traffic_ops/install/bin/postinstall-new
----------------------------------------------------------------------
diff --git a/traffic_ops/install/bin/postinstall-new b/traffic_ops/install/bin/postinstall-new
new file mode 100755
index 0000000..0228b5a
--- /dev/null
+++ b/traffic_ops/install/bin/postinstall-new
@@ -0,0 +1,419 @@
+#!/usr/bin/perl
+
+use lib qw(/opt/traffic_ops/install/lib /opt/traffic_ops/install/lib/perl5 /opt/traffic_ops/app/local/lib/perl5 /opt/traffic_ops/app/lib);
+$ENV{PATH}     = "/opt/traffic_ops/install/bin:$ENV{PATH}";
+$ENV{PERL5LIB} = "/opt/traffic_ops/install/lib:/opt/traffic_ops/install/lib/perl5:/opt/traffic_ops/app/local/lib/perl5:/opt/traffic_ops/app/lib";
+
+use strict;
+use warnings;
+
+use Safe;
+use JSON;
+use File::Basename qw{dirname};
+use File::Path qw{make_path};
+use InstallUtils qw{ :all };
+use Digest::SHA1 qw(sha1_hex);
+use Data::Dumper qw(Dumper);
+use Scalar::Util qw(looks_like_number);
+
+use Getopt::Long;
+
+sub errorOut {
+	die @_;
+}
+
+sub getDbDriver {
+	return "mymysql";
+}
+
+sub getInstallPath {
+	my $relPath = shift;
+	return join( '/', "/tmp/traffic_ops", $relPath );
+}
+
+# question: The question given in the config file
+# config_answer: The answer given in the config file - if no config file given will be defaultInput
+# fileName: The name of the output config file given by the input config file
+#
+# Determines an answer to the questions asked. If an input question and answer pair is given, will return the 
+#  answer. If a question is given but no answer, it will prompt the user if interactive mode is enabled, but if
+#  interactive mode is not enabled it will return the default answer to the question. If there is no default answer
+#  to the question and interactive mode is not enabled it will return an error and quit.
+
+sub getField {
+	my $question = shift;
+	my $config_answer = shift;
+	my $fileName = shift;
+
+	# if answer provided in config file use it
+	if ($config_answer) {
+		return $config_answer;
+	}
+	else {
+		# if no config value and in interactive mode prompt user
+                if ($::interactive) {
+                        return promptUser($question);
+                }
+
+		# if no answer given in input file attempt to use default answer
+		foreach my $var (@{ $::defaultInputs->{$fileName} }) {
+			if ( defined $var->{$question} ) {
+				return $var->{$question};
+			}
+		}
+		
+		#No way of getting answer
+		errorOut("No config answer given\n");
+	}	
+	
+	errorOut("error: end of function");
+}
+
+# userInput: The entire input config file which is either user input or the defaults
+# fileName: The name of the output config file given by the input config file
+#
+# Loops through an input config file and determines answers to each question using getField
+#  and returns the hash of answers
+
+sub getConfig {
+	my $userInput = shift;
+	my $fileName = shift;
+
+    	my %config;
+	
+	if (!defined $userInput->{$fileName}) {
+		print "Error: No $fileName found in config\n";
+	}	
+	
+	if ($::debug) {
+		print "===========$fileName===========\n";
+	}
+
+	foreach my $var (@{ $userInput->{$fileName} }) {
+        	my $question = ( (keys $var)[0] eq "config_var" ? (keys $var)[1] : (keys $var)[0] );
+        	my $answer = $config{$var->{"config_var"}} = getField($question, $var->{$question}, $fileName);
+    		
+		$config{$var->{"config_var"}} = $answer;
+		if ($::debug) {
+			print "$question:  $answer\n";
+		}
+	}
+	return %config;
+}
+
+# userInput: The entire input config file which is either user input or the defaults
+# dbFileName: The filename of the output config file for the database
+# toDBFileName: The filename of the output config file for the Traffic Ops database
+# dbAccessFileName: The filename of DBAccess
+#
+# Generates a config file for the database based on the questions and answers in the input config file
+
+sub generateDbConf {
+	my $userInput        = shift;
+	my $dbFileName       = shift;
+	my $toDBFileName     = shift;
+	my $dbAccessFileName = shift;
+
+	my %dbconf = getConfig($userInput, $dbFileName);	
+
+	make_path( dirname($dbFileName), { mode => 0755 } );
+	writeJson( $dbFileName, \%dbconf );
+	
+    	# broken out into separate file/config area
+	my %todbconf = getConfig($userInput, $toDBFileName);	
+
+	# No YAML library installed, but this is a simple file..
+	open( my $fh, '>', $dbAccessFileName ) or errorOut("Can't write to $dbAccessFileName $!");
+	print $fh "version: 1.0\n";
+	print $fh "name: dbconf.yml\n\n";
+	print $fh "production:\n";
+	print $fh "        driver: ", getDbDriver() . "\n";
+	print $fh "        open: tcp:$dbconf{hostname}:$dbconf{port}*$dbconf{dbname}/$dbconf{root_user}/$dbconf{root_passwd}\n";
+	close $fh;
+
+	return \%todbconf;
+}
+
+# userInput: The entire input config file which is either user input or the defaults
+# fileName: The filename of the output config file
+#
+# Generates a config file for the CDN
+
+sub generateCdnConf {
+	my $userInput = shift;
+	my $fileName  = shift;
+
+	# First,  read existing one -- already loaded with a bunch of stuff
+	my $cdnConf = Safe->new->rdo($fileName) or errorOut("Error loading $fileName: $@");
+
+	my %cdnconf = getConfig($userInput, $fileName);
+	
+	if (! looks_like_number($cdnconf{keepSecrets}) ) {
+		errorOut("Number of secrets to keep must be a number\n");
+	}
+
+	if ( lc $cdnconf{genSecret} =~ /^y(?:es)?/ ) {
+		my @secrets   = @{ $cdnConf->{secrets} };
+		my $newSecret = randomWord();
+		unshift @secrets, randomWord();
+		if ( $cdnconf{keepSecrets} > 0 && $#secrets > $cdnconf{keepSecrets} - 1 ) {
+
+			# Shorten the array to requested length
+			$#secrets = $cdnconf{keepSecrets} - 1;
+		}
+	}
+	writePerl( $fileName, $cdnConf );
+}
+
+# userInput: The entire input config file which is either user input or the defaults
+# fileName: The filename of the output config file
+#
+# Generates an LDAP config file
+
+sub generateLdapConf {
+	my $userInput = shift;
+	my $fileName  = shift;
+
+	my $useLdap = $userInput->{$fileName}[0]->{"Do you want to set up LDAP?"};
+	
+	if ($useLdap eq "no" || $useLdap eq "n" ) {
+		if ($::debug) {
+			print "Not setting up ldap\n";
+		}
+		return;
+	}
+
+	my %ldapConf = getConfig($userInput, $fileName);
+	
+	if ( $::debug && $ldapConf{setupLdap} eq "no" ) {
+		print "Not setting up ldap\n";	
+		return;	
+	}
+
+	make_path( dirname($fileName), { mode => 0755 } );
+	writeJson( $fileName, \%ldapConf );
+}
+
+sub generatePostInstallConf {
+	my $userInput = shift;
+	my $fileName  = shift;
+
+	my $userIn = $userInput->{$fileName};
+}
+
+sub generateUsersConf {
+	my $userInput = shift;
+	my $fileName  = shift;
+	
+	my %user = ();
+	my %config = getConfig($userInput, $fileName);
+
+        $user{username} = $config{tmAdminUser};
+        $user{password} = sha1_hex($config{tmAdminPw});
+
+        writeJson( $fileName, \%user );
+}
+
+sub generateProfilesDir {
+	my $userInput = shift;
+	my $fileName  = shift;
+
+	my $userIn = $userInput->{$fileName};
+}
+
+# userInput: The entire input config file which is either user input or the defaults
+#
+# Checks the input config file against the default inputs. If there is a question located in the input config file
+#  which is not present in the defaults it will output a warning message.
+#
+# This does not check the other way meaning questions which are present in defaults but not present in the input config
+#  file will not be checked
+
+sub sanityCheckConfig {
+	my $userInput = shift;
+	my $diffs = 0;
+		
+	foreach my $file ( (keys $userInput) ) {	
+		if (!defined $::defaultInputs->{$file}) {
+			print "Warning: File \'$file\' found in input but not defaults\n";
+			next;
+		}
+		
+		my $counter = 0;
+		foreach my $value (@ { $userInput->{$file} }) {
+			if ( !defined $::defaultInputs->{$file}[$counter]->{"config_var"} ) {
+				print "Warning: Value " . Dumper($value) . "found in file \'$file\' but not defaults\n";
+				$diffs++;
+			}
+			$counter++;
+		}		
+	}
+    
+	if ($::debug && $diffs == 0) {
+		print "File sanity check complete - found $diffs differences\n";
+	}
+	
+	if ($diffs > 0) {
+		print "File sanity check complete - found $diffs difference(s)\n";
+	}	
+}
+    
+# A function which returns the default inputs data structure. These questions and answers will be used if there is no
+#  user input config file or if there are questions in the input config file which do not have answers
+
+sub getDefaults{
+	return {
+		"testdb.conf" => [
+			{
+                             "Database type" => "mysql",
+                             "config_var" => "type"
+                     },
+                     { 
+                             "Database name" => "traffic_ops",
+                             "config_var" => "dbname"
+                     },
+                     {
+                             "Database server hostname IP or FQDN" => "localhost",
+                             "config_var" => "hostname"
+                     },      
+                     {       
+                             "Database port number" => 3306,
+                             "config_var" => "port"
+                     },      
+                {
+                        "Root database user" => "root",
+                        "config_var" => "root_user"
+                },
+                {
+                        "Root database password" => "default",
+                        "config_var" => "root_passwd"
+                }
+             	],
+       		"testtodb.conf" => [
+                {
+                        "Traffic Ops database user" => "root",
+                        "config_var" => "dbAdminUser"
+                },
+                {
+                        "Password for Traffic Ops database user" => "default",
+                        "config_var" => "dbAdminPw"
+                }             	
+		],
+       		"testcdn.conf" => [
+                     {
+                             "Generate a new secret?" => "yes",
+                             "config_var" => "genSecret"
+                     },
+                     {
+                             "Number of secrets to keep?" => "10",
+                             "config_var" => "keepSecrets"
+                     }
+             	],
+        	"testldap.conf" => [
+                {
+                        "Do you want to set up LDAP?" => "no",
+                        "config_var" => "setupLdap"
+                },
+                {
+                        "LDAP server hostname" => "",
+                        "config_var" => "hostname"
+                },
+                {
+                        "LDAP Admin DN" => "",
+                        "config_var" => "admin_dn"
+                },
+                {
+                        "LDAP Admin Password" => "",
+                        "config_var" => "password"
+                },
+                {
+                        "LDAP Search Base" => "",
+                        "config_var" => "search_base"
+                }
+        ],
+        "testpost_install.json" => [],
+        "testusers.json" => [
+                {
+                        "Administration username for Traffic Ops" => "admin",
+                        "config_var" => "tmAdminUser"
+                },
+                {
+                        "Password for the admin user" => "default",
+                        "config_var" => "tmAdminPw"
+                }
+	],
+        "testprofiles/" => []
+        };
+}
+
+# -d     - Debug Mode:       More output to the terminal
+# -i     - Interactive mode: Any questions which do not have answersin config file will result in blocking prompts going
+#                             to the user
+# -h     - Help:             Basic command line help menu
+# -cfile - Input File:       The input config file used to ask and answer questions
+
+# In interactive mode: prompt if no value in cfile
+
+# Not in interactive mode: if answer in cfile, use if. If no answer in cfile, use answer in default. 
+#  if no answer in default die
+
+sub main {
+        my $inputFile = "";
+        my $help = 0;
+        our $interactive = 0;
+        our $debug = 0;
+
+        GetOptions( "cfile=s"   => \$inputFile,
+                "i"  => \$interactive,
+                "d" => \$debug,
+                "h" => \$help,
+                "help" => \$help)
+        or die ("Error in command line arguments");
+	
+	# stores the default questions and answers
+	our $defaultInputs = getDefaults();
+	
+	if ($help) {
+		print "Usage: postinstall [-i] [-d] -cfile=[config_file]\n";
+		exit(0);
+	}
+
+	if ($::debug) {
+		print "Debug is on\n";
+	} 
+	
+	if ($::debug && $::interactive) {
+		print "Running in interactive mode\n";
+	}
+	
+	# used to store the questions and answers - will either be input config file or defaults
+	my $userInput;
+
+	if ($inputFile eq "") {
+		print "No input file given - using defaults\n";
+		$userInput = $::defaultInputs;
+	}
+	else {
+		print "Using input file $inputFile\n";
+		$userInput = readJson($inputFile);
+	}
+	
+	# check the input config file against the defaults to check for missing questions
+	if ($inputFile ne "") {
+		sanityCheckConfig($userInput);
+	}
+
+	# The generator functions handle checking input/default/interactive mode
+
+	# todbconf will be used later when setting up the database
+	my $todbconf = generateDbConf( $userInput, 'testdb.conf', 'testtodb.conf', 'testdbconf.yml' );
+	generateCdnConf( $userInput, 'testcdn.conf' );
+	generateLdapConf( $userInput, 'testldap.conf' );
+	generatePostInstallConf( $userInput, 'testpost_install.json' );
+	generateUsersConf( $userInput, 'testusers.json' );
+	generateProfilesDir( $userInput, 'testprofiles/' );
+}
+
+main;
+
+# vi:syntax=perl

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/c0545f7d/traffic_ops/install/lib/InstallUtils.pm
----------------------------------------------------------------------
diff --git a/traffic_ops/install/lib/InstallUtils.pm b/traffic_ops/install/lib/InstallUtils.pm
index 0574468..ddd1c22 100644
--- a/traffic_ops/install/lib/InstallUtils.pm
+++ b/traffic_ops/install/lib/InstallUtils.pm
@@ -25,7 +25,7 @@ package InstallUtils;
 
 use Term::ReadPassword;
 use base qw{ Exporter };
-our @EXPORT_OK = qw{ execCommand randomWord promptUser promptRequired promptPassword promptPasswordVerify trim};
+our @EXPORT_OK = qw{ execCommand randomWord promptUser promptRequired promptPassword promptPasswordVerify trim readJson writeJson writePerl};
 our %EXPORT_TAGS = ( all => \@EXPORT_OK );
 
 sub execCommand {
@@ -64,6 +64,10 @@ sub promptUser {
 		}
 		return $response;
 	}
+	elsif ( $::auto && defined $defaultValue ) {
+		print( "$defaultValue\n" );
+		return $defaultValue;
+	}
 	else {
 		$| = 1;
 		$_ = <STDIN>;
@@ -96,6 +100,11 @@ sub promptPassword {
 sub promptPasswordVerify {
 	my $prompt = shift;
 	my $pw     = shift;
+	
+	if ( $::auto && defined $pw ) {
+		print( "$prompt: $pw\n" );
+		return $pw;
+	}
 
 	while (1) {
 		$pw = promptPassword($prompt);
@@ -115,4 +124,35 @@ sub trim {
 	return $str;
 }
 
+sub readJson {
+	my $file = shift;
+	open( my $fh, '<', $file ) or return;
+	local $/;    # slurp mode
+	my $text = <$fh>;
+	undef $fh;
+	return JSON->new->utf8->decode($text);
+}
+
+sub writeJson {
+	my $file = shift;
+	open( my $fh, '>', $file ) or die("open(): $!");
+	foreach my $data (@_) {
+		my $json_text = JSON->new->utf8->pretty->encode($data);
+		print $fh $json_text, "\n";
+	}
+	close $fh;
+}
+
+sub writePerl {
+	my $file = shift;
+ 	my $data = shift;
+ 
+ 	open( my $fh, '>', $file ) or die("open(): $!");
+ 	my $dumper = Data::Dumper->new([ $data ]);
+ 
+ 	# print without var names and with simple indentation
+ 	print $fh $dumper->Terse(1)->Dump();
+ 	close $fh;
+}
+
 1;