Updated to include full new postinstall with new perl modules

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

Branch: refs/heads/master
Commit: bae4375757ceb18358c4bd2dcf5ce873ec225e02
Parents: 4a2657b
Author: peryder <pery...@cisco.com>
Authored: Thu Dec 1 17:05:28 2016 -0500
Committer: Dan Kirkwood <dang...@gmail.com>
Committed: Fri Jan 27 09:52:53 2017 -0700

----------------------------------------------------------------------
 traffic_ops/install/bin/input.json              | 138 +++-
 traffic_ops/install/bin/postinstall-new         |  15 +-
 .../install/bin/postinstall-new-integrated      | 763 +++++++++++++++++++
 traffic_ops/install/lib/BuildPerlDeps.pm        |  99 +++
 traffic_ops/install/lib/GenerateCert.pm         | 232 ++++++
 traffic_ops/install/lib/InstallUtils.pm         | 268 ++++---
 traffic_ops/install/lib/ProfileCleanup.pm       | 192 +++++
 7 files changed, 1580 insertions(+), 127 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/bae43757/traffic_ops/install/bin/input.json
----------------------------------------------------------------------
diff --git a/traffic_ops/install/bin/input.json 
b/traffic_ops/install/bin/input.json
index 8428eec..fd9f0e6 100644
--- a/traffic_ops/install/bin/input.json
+++ b/traffic_ops/install/bin/input.json
@@ -1,11 +1,11 @@
 {
-       "testdb.conf": [
+       "/opt/traffic_ops/app/conf/production/database.conf": [
                {
                        "Database type": "mysql",
                        "config_var": "type"
                },
                {
-                       "Database name": "traffic_ops",
+                       "Database name": "traffic_ops_db",
                        "config_var": "dbname"
                },
                {
@@ -16,26 +16,28 @@
                        "Database port number": "3306",
                        "config_var": "port"
                },
-                {
-                        "Root database user": "root",
-                        "config_var": "root_user"
-                },
-                {
-                        "Root database password": "default",
-                        "config_var": "root_passwd"
-                }
+               {
+                       "Traffic Ops database user": "traffic_ops",
+                       "config_var": "user"
+               },
+               {
+                       "Traffic Ops database password": "default",
+                       "config_var": "password",
+                        "hidden": "1"
+               }
        ],
-       "testtodb.conf": [
-                {
-                        "Traffic Ops database user": "root",
-                        "config_var": "dbAdminUser"
-                },
+       "/opt/traffic_ops/app/db/dbconf.yml": [
+               {
+                       "Database server root (admin) username": "root",
+                       "config_var": "dbAdminUser"
+               },
                 {
-                        "Password for Traffic Ops database user": "default",
-                        "config_var": "dbAdminPw"
-                }
+                       "Database server admin password": "default",
+                       "config_var": "dbAdminPw",
+                        "hidden": "1"
+               }
        ],
-       "testcdn.conf": [
+       "/opt/traffic_ops/app/conf/cdn.conf": [
                {
                        "Generate a new secret?": "yes",
                        "config_var": "genSecret"
@@ -45,7 +47,7 @@
                        "config_var": "keepSecrets"
                }
        ],
-       "testldap.conf": [
+       "/opt/traffic_ops/app/conf/ldap.conf": [
                {
                        "Do you want to set up LDAP?": "no",
                        "config_var": "setupLdap"
@@ -60,23 +62,101 @@
                },
                {
                        "LDAP Admin Password": "",
-                       "config_var": "password"
+                       "config_var": "password",
+                        "hidden": "1"
                },
                {
                        "LDAP Search Base": "",
                        "config_var": "search_base"
                }
        ],
-       "testpost_install.json": [],
-       "testusers.json": [
+       "/opt/traffic_ops/install/data/json/users.json": [
+                {
+                        "Administration username for Traffic Ops": "root",
+                        "config_var": "tmAdminUser"
+                },
+                {
+                        "Password for the admin user": "default",
+                        "config_var": "tmAdminPw",
+                        "hidden": "1"
+                }
+       ],
+       "/opt/traffic_ops/install/data/profiles/": [],
+       "/opt/traffic_ops/install/bin/openssl_configuration.json": [
                {
-                       "Administration username for Traffic Ops": "admin",
-                       "config_var": "tmAdminUser"
+                       "Country Name (2 letter code)": "XX",
+                       "config_var": "country"
                },
                {
-                       "Password for the admin user": "default",
-                       "config_var": "tmAdminPw"
-               }
+                       "State or Province Name (full name)": "Default State",
+                       "config_var": "state"
+               },
+               {
+                       "Locality Name (eg, city)": "Default City",
+                       "config_var": "locality"
+               },
+               {
+                       "Organization Name (eg, company)": "Default Company 
Ltd",
+                       "config_var": "company"
+               },
+               {
+                       "Organizational Unit Name (eg, section)": "",
+                       "config_var": "org_unit"
+               },
+               {
+                       "Common Name (eg, your name or your server's 
hostname)": "example.com",
+                       "config_var": "common_name"
+               },
+                {
+                        "RSA Passphrase": "password",
+                        "config_var": "rsaPassword",
+                        "hidden": "1"
+                }
        ],
-       "testprofiles/": []
+        "/opt/traffic_ops/install/data/json/profiles.json": [
+                {
+                       "Traffic Ops url": "https://localhost";,
+                       "config_var": "tm.url"
+                },
+               {
+                       "Human-readable CDN Name.  (No whitespace, please)": 
"kabletown_cdn",
+                       "config_var": "cdn_name"
+               },
+                {
+                        "Health Polling Interval (milliseconds)": "8000",
+                        "config_var": "health_polling_int"
+                },
+                {
+                        "DNS sub-domain for which your CDN is authoritative": 
"cdn1.kabletown.net",
+                        "config_var": "dns_subdomain"
+                },
+                {
+                        "TLD SOA admin": "traffic_ops",
+                        "config_var": "soa_admin"
+                },
+                {
+                        "TrafficServer Drive Prefix": "/dev/sd",
+                        "config_var": "driver_prefix"
+                },
+                {
+                        "TrafficServer RAM Drive Prefix": "/dev/ram",
+                        "config_var": "ram_drive_prefix"
+                },
+                {
+                        "TrafficServer RAM Drive Letters (comma separated)": 
"0,1,2,3,4,5,6,7",
+                        "config_var": "ram_drive_letters"
+                },
+                {
+                        "Health Threshold Load Average": "25",
+                        "config_var": "health_thresh_load_avg"
+                },
+                {
+                        "Health Threshold Available Bandwidth in Kbps": 
"1750000",
+                        "config_var": "health_thresh_kbps"
+                },
+                {
+                        "Traffic Server Health Connection Timeout 
(milliseconds)": "2000",
+                        "config_var": "health_connect_timeout"
+                }
+        ]
 }

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/bae43757/traffic_ops/install/bin/postinstall-new
----------------------------------------------------------------------
diff --git a/traffic_ops/install/bin/postinstall-new 
b/traffic_ops/install/bin/postinstall-new
index b97d58d..cb4cc56 100755
--- a/traffic_ops/install/bin/postinstall-new
+++ b/traffic_ops/install/bin/postinstall-new
@@ -39,6 +39,7 @@ sub errorOut {
 }
 
 # outputs logging messages to terminal and log file
+# TODO: move to InstallUtils
 sub logger {
     my $output = shift;
     my $type   = shift;
@@ -247,7 +248,8 @@ sub sanityCheckDefaults {
 # 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 default inputs which
-#  is not located in the input config file it will output a warning message.
+#  is not located in the input config file it will output a warning message. 
If in auto mode the default answer will be
+#  used. If in interactive mode the user will be prompted with the default 
question missing from the config file
 #
 # 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
@@ -259,9 +261,8 @@ sub sanityCheckConfig {
     foreach my $file ( ( keys $::defaultInputs ) ) {
         if ( !defined $userInput->{$file} ) {
             logger( "File \'$file\' found in defaults but not config file", 
"warn" );
-            next;
-        }
-
+            $userInput->{$file} = [];
+       }
         foreach my $defaultValue ( @{ $::defaultInputs->{$file} } ) {
 
             my $found = 0;
@@ -308,10 +309,6 @@ sub sanityCheckConfig {
     logger( "File sanity check complete - found $diffs difference(s)", "info" 
) if ( $diffs > 0 );
 }
 
-sub writeOutputConf {
-    writeJson( $::outputConfigFile, \%::outputConf );
-}
-
 # 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
 
@@ -331,7 +328,7 @@ sub getDefaults {
                 "config_var"                          => "hostname",
             },
             {
-                "Database port number" => 3306,
+                "Database port number" => "3306",
                 "config_var"           => "port"
             },
             {

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/bae43757/traffic_ops/install/bin/postinstall-new-integrated
----------------------------------------------------------------------
diff --git a/traffic_ops/install/bin/postinstall-new-integrated 
b/traffic_ops/install/bin/postinstall-new-integrated
new file mode 100755
index 0000000..529cce8
--- /dev/null
+++ b/traffic_ops/install/bin/postinstall-new-integrated
@@ -0,0 +1,763 @@
+#!/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 POSIX;
+use File::Basename qw{dirname};
+use File::Path qw{make_path};
+use InstallUtils qw{ :all };
+use BuildPerlDeps qw{ :all };
+use GenerateCert qw{ :all };
+use ProfileCleanup qw { :all };
+use Digest::SHA1 qw(sha1_hex);
+use Data::Dumper qw(Dumper);
+use Scalar::Util qw(looks_like_number);
+
+use Getopt::Long;
+
+# paths of the output configuration files
+our $databaseConfFile = "/opt/traffic_ops/app/conf/production/database.conf";
+our $dbConfFile       = "/opt/traffic_ops/app/db/dbconf.yml";
+our $cdnConfFile      = "/opt/traffic_ops/app/conf/cdn.conf";
+our $ldapConfFile     = "/opt/traffic_ops/app/conf/ldap.conf";
+our $usersConfFile    = "/opt/traffic_ops/install/data/json/users.json";
+our $profilesConfFile = "/opt/traffic_ops/install/data/profiles/";
+our $opensslConfFile  = 
"/opt/traffic_ops/install/bin/openssl_configuration.json";
+our $paramConfFile    = "/opt/traffic_ops/install/data/json/profiles.json";
+
+our $profile_dir      = "/opt/traffic_ops/install/data/profiles/";
+our $post_install_cfg = "/opt/traffic_ops/install/data/json/post_install.json";
+
+our $reconfigure_defaults = "/opt/traffic_ops/.reconfigure_defaults";
+
+our $parameters;
+
+my $reconfigure_file = "/opt/traffic_ops/.reconfigure";
+my $reconfigure;
+my $dumpDefaults;
+
+# log file for the installer
+our $logFile = "/var/log/traffic_ops/postinstall.log";
+
+# maximum size the log file should be before rotating it - rotating it copies 
the current log
+#  file to the same name appended with .bkp replacing the old backup if any is 
there
+my $maxLogSize = 1000000;    #bytes
+
+# log file for cpan - this log becomes large and is rotated every install
+our $cpanLogFile = "/var/log/traffic_ops/cpan.log";
+
+# configuration file output with answers which can be used as input to 
postinstall
+our $outputConfigFile = "/var/log/traffic_ops/configuration_file.json";
+
+sub getDbDriver {
+    return "mymysql";
+}
+
+sub getInstallPath {
+    my $relPath = shift;
+    return join( '/', "/tmp/traffic_ops", $relPath );
+}
+
+# given a var to the hash of config_var and question, will return the question
+sub getConfigQuestion {
+    my $var = shift;
+    foreach my $key ( keys $var ) {
+        if ( $key ne "hidden" && $key ne "config_var" ) {
+            return $key;
+        }
+    }
+}
+
+# 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
+#
+# Determines if the script is being run in complete interactive mode and 
prompts user - otherwise
+#  returns answer to question in config or defaults
+
+sub getField {
+    my $question      = shift;
+    my $config_answer = shift;
+    my $hidden        = shift;
+
+    # if there is no config file and not in automatic mode prompt for all 
questions with default answers
+    if ( !$::inputFile && !$::automatic ) {
+        if ($hidden) {
+            return promptPasswordVerify($question);
+        }
+        else {
+            return promptUser( $question, $config_answer );
+        }
+    }
+
+    return $config_answer;
+}
+
+# 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} ) {
+        logger( "No $fileName found in config", "error" );
+    }
+
+    logger( "===========$fileName===========", "info" );
+
+    foreach my $var ( @{ $userInput->{$fileName} } ) {
+
+        my $question = getConfigQuestion($var);
+        my $hidden   = $var->{"hidden"} if ( exists $var->{"hidden"} );
+        my $answer   = $config{ $var->{"config_var"} } =
+          getField( $question, $var->{$question}, $hidden );
+
+        $config{ $var->{"config_var"} } = $answer;
+        if ( !$hidden ) {
+            logger( "$question:  $answer", "info" );
+        }
+    }
+    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
+#
+# 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 %dbconf = getConfig( $userInput, $dbFileName );
+
+    make_path( dirname($dbFileName), { mode => 0755 } );
+    writeJson( $dbFileName, \%dbconf );
+    logger( "Database configuration has been saved", "info" );
+
+    # 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, '>', $toDBFileName )
+      or errorOut("Can't write to $toDBFileName!");
+    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{user}/$dbconf{password}\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;
+
+    my %cdnConfiguration = getConfig( $userInput, $fileName );
+
+    # First,  read existing one -- already loaded with a bunch of stuff
+    my $cdnConf;
+    if ( -f $fileName ) {
+        $cdnConf = Safe->new->rdo($fileName)
+          or errorOut("Error loading $fileName: $@");
+    }
+    if ( lc $cdnConfiguration{genSecret} =~ /^y(?:es)?/ ) {
+        my @secrets   = @{ $cdnConf->{secrets} };
+        my $newSecret = randomWord();
+        unshift @secrets, randomWord();
+        if (   $cdnConfiguration{keepSecrets} > 0
+            && $#secrets > $cdnConfiguration{keepSecrets} - 1 )
+        {
+
+            # Shorten the array to requested length
+            $#secrets = $cdnConfiguration{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" ) {
+        logger( "Not setting up ldap", "info" );
+
+        return;
+    }
+
+    my %ldapConf = getConfig( $userInput, $fileName );
+
+    make_path( dirname($fileName), { mode => 0755 } );
+    writeJson( $fileName, \%ldapConf );
+}
+
+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 );
+    $user{password} = $config{tmAdminPw};
+    return \%user;
+}
+
+sub generateProfilesDir {
+    my $userInput = shift;
+    my $fileName  = shift;
+
+    my $userIn = $userInput->{$fileName};
+}
+
+sub generateOpenSSLConf {
+    my $userInput = shift;
+    my $fileName  = shift;
+
+    if ( !defined $userInput->{$fileName} ) {
+        logger( "No OpenSSL Configuration - questions will be asked", "info" );
+        writeJson( $fileName, my %emptyConfig );
+        return;
+    }
+
+    my %config = getConfig( $userInput, $fileName );
+
+    writeJson( $fileName, \%config );
+}
+
+sub generateParamConf {
+    my $userInput = shift;
+    my $fileName  = shift;
+
+    my %config = getConfig( $userInput, $fileName );
+    return \%config;
+}
+
+# check default values for missing config_var parameter
+sub sanityCheckDefaults {
+    foreach my $file ( ( keys $::defaultInputs ) ) {
+        foreach my $defaultValue ( @{ $::defaultInputs->{$file} } ) {
+            my $question = getConfigQuestion($defaultValue);
+
+            if ( !defined $defaultValue->{"config_var"}
+                || $defaultValue->{"config_var"} eq "" )
+            {
+                errorOut( "Question \'$question\' in file \'$file\' has no 
config_var" );
+            }
+        }
+    }
+}
+
+# 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 default inputs which
+#  is not located in the input config file 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 $::defaultInputs ) ) {
+        if ( !defined $userInput->{$file} ) {
+            logger( "File \'$file\' found in defaults but not config file", 
"warn" );
+            $userInput->{$file} = [];
+        }
+
+        foreach my $defaultValue ( @{ $::defaultInputs->{$file} } ) {
+
+            my $found = 0;
+            foreach my $configValue ( @{ $userInput->{$file} } ) {
+                if ( $defaultValue->{"config_var"} eq 
$configValue->{"config_var"} ) {
+                    $found = 1;
+                }
+            }
+
+            # if the question is not found in the config file add it from 
defaults
+            if ( !$found ) {
+                logger( "Value " . Dumper($defaultValue) . "found in defaults 
but not in \'$file\'", "warn" );
+
+                my $question = getConfigQuestion($defaultValue);
+
+                my %temp;
+
+                # in automatic mode add the missing question with default 
answer
+                if ($::automatic) {
+                    logger( "Adding question \'$question\' with default answer 
\'$defaultValue->{$question}\'", "info" );
+
+                    %temp = (
+                        "config_var" => $defaultValue->{"config_var"},
+                        $question    => $defaultValue->{$question}
+                    );
+                }
+
+                # in interactive mode prompt the user for answer to missing 
question
+                else {
+                    logger( "Prompting user for answer", "info" );
+                    my $answer;
+                    if ( exists $defaultValue->{"hidden"}
+                        && $defaultValue->{"hidden"} )
+                    {
+                        $answer = promptPasswordVerify($question);
+                        %temp   = (
+                            "config_var" => $defaultValue->{"config_var"},
+                            $question    => $answer,
+                            "hidden"     => "true"
+                        );
+                    }
+                    else {
+                        $answer = promptUser( $question, 
$defaultValue->{$question} );
+                        %temp = (
+                            "config_var" => $defaultValue->{"config_var"},
+                            $question    => $answer
+                        );
+                    }
+                }
+                push $userInput->{$file}, \%temp;
+
+                $diffs++;
+            }
+        }
+    }
+
+    logger( "File sanity check complete - found $diffs differences", "info" )
+      if ( $::debug && $diffs == 0 );
+    logger( "File sanity check complete - found $diffs difference(s)", "info" )
+      if ( $diffs > 0 );
+}
+
+# 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 {
+        $::databaseConfFile => [
+            {
+                "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"
+            },
+            {
+                "Traffic Ops database user" => "traffic_ops",
+                "config_var"                => "user"
+            },
+            {
+                "Password for Traffic Ops database user" => "",
+                "config_var"                             => "password",
+                "hidden"                                 => "true"
+            }
+        ],
+        $::dbConfFile => [
+            {
+                "Database server root (admin) user" => "root",
+                "config_var"                        => "dbAdminUser"
+            },
+            {
+                "Password for database server admin" => "",
+                "config_var"                         => "dbAdminPw",
+                "hidden"                             => "true"
+            }
+        ],
+        $::cdnConfFile => [
+            {
+                "Generate a new secret?" => "yes",
+                "config_var"             => "genSecret"
+            },
+            {
+                "Number of secrets to keep?" => "10",
+                "config_var"                 => "keepSecrets"
+            }
+        ],
+        $::ldapConfFile => [
+            {
+                "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",
+                "hidden"              => "true"
+            },
+            {
+                "LDAP Search Base" => "",
+                "config_var"       => "search_base"
+            }
+        ],
+        $::usersConfFile => [
+            {
+                "Administration username for Traffic Ops" => "admin",
+                "config_var"                              => "tmAdminUser"
+            },
+            {
+                "Password for the admin user" => "",
+                "config_var"                  => "tmAdminPw",
+                "hidden"                      => "true"
+            }
+        ],
+        $::profilesConfFile => [],
+        $::opensslConfFile  => [
+            {
+                "Country Name (2 letter code)" => "XX",
+                "config_var"                   => "country"
+            },
+            {
+                "State or Province Name (full name)" => "San Jose",
+                "config_var"                         => "state"
+            },
+            {
+                "Locality Name (eg, city)" => "Default City",
+                "config_var"               => "locality"
+            },
+            {
+                "Organization Name (eg, company)" => "Default Company Ltd",
+                "config_var"                      => "company"
+            },
+            {
+                "Organizational Unit Name (eg, section)" => "",
+                "config_var"                             => "org_unit"
+            },
+            {
+                "Common Name (eg, your name or your server's hostname)" => 
"cisco.com",
+                "config_var"                                            => 
"common_name"
+            },
+            {
+                "RSA Passphrase" => "",
+                "config_var"     => "rsaPassword",
+                "hidden"         => "true"
+            }
+        ],
+        $::paramConfFile = [
+            {
+                "Traffic Ops url" => "https://localhost";,
+                "config_var"      => "tm.url"
+            },
+            {
+                "Human-readable CDN Name.  (No whitespace, please)" => 
"kabletown_cdn",
+                "config_var"                                        => 
"cdn_name"
+            },
+            {
+                "Health Polling Interval (milliseconds)" => "8000",
+                "config_var"                             => 
"health_polling_int"
+            },
+            {
+                "DNS sub-domain for which your CDN is authoritative" => 
"cdn1.kabletown.net",
+                "config_var"                                         => 
"dns_subdomain"
+            },
+            {
+                "TLD SOA admin" => "traffic_ops",
+                "config_var"    => "soa_admin"
+            },
+            {
+                "TrafficServer Drive Prefix" => "/dev/sd",
+                "config_var"                 => "driver_prefix"
+            },
+            {
+                "TrafficServer RAM Drive Prefix" => "/dev/ram",
+                "config_var"                     => "ram_drive_prefix"
+            },
+            {
+                "TrafficServer RAM Drive Letters (comma separated)" => 
"0,1,2,3,4,5,6,7",
+                "config_var"                                        => 
"ram_drive_letters"
+            },
+            {
+                "Health Threshold Load Average" => "25",
+                "config_var"                    => "health_thresh_load_avg"
+            },
+            {
+                "Health Threshold Available Bandwidth in Kbps" => "1750000",
+                "config_var"                                   => 
"health_thresh_kbps"
+            },
+            {
+                "Traffic Server Health Connection Timeout (milliseconds)" => 
"2000",
+                "config_var"                                              => 
"health_connect_timeout"
+            }
+
+        ]
+    };
+}
+
+sub setupDatabase {
+    my $todbconf    = shift;
+    my $opensslconf = shift;
+
+    #
+    # Call mysql initialization script.
+    #
+    logger( "Creating database with user: $todbconf->{dbAdminUser}", "info" );
+    my $result = execCommand( "/opt/traffic_ops/install/bin/create_db", 
$todbconf->{dbAdminUser}, $todbconf->{dbAdminPw} );
+    if ( $result != 0 ) {
+        errorOut("Failed to create the database");
+    }
+
+    logger( "Setting up database", "info" );
+    chdir("/opt/traffic_ops/app");
+    $result = execCommand( "/usr/bin/perl", "db/admin.pl", "--env=production", 
"setup" );
+
+    if ( $result != 0 ) {
+        errorOut("Database initialization failed");
+    }
+    else {
+        logger( "Database initialization succeeded", "info" );
+    }
+
+    $result = execCommand( "/opt/traffic_ops/install/bin/dataload", 
$todbconf->{dbAdminUser}, $todbconf->{dbAdminPw} );
+    if ( $result != 0 ) {
+        logger( "Failed to load seed data", "error" );
+    }
+
+    logger( "Downloading MaxMind data", "info" );
+    chdir("/opt/traffic_ops/app/public/routing");
+    $result = execCommand( "/usr/bin/wget 
http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.mmdb.gz"; );
+    if ( $result != 0 ) {
+        logger( "Failed to download MaxMind data", "error" );
+    }
+
+    logger( "Copying coverage zone file to public dir", "info" );
+    $result = execCommand("/bin/mv 
/opt/traffic_ops/app/public/coverage-zone.json .");
+    if ( $result != 0 ) {
+        logger( "Failed to copy coverage zone file", "error" );
+    }
+
+    if ( -x "/usr/bin/openssl" ) {
+        logger( "Installing SSL Certificates", "info" );
+        $result = GenerateCert::createCert($opensslconf);
+
+        if ( $result != 0 ) {
+            errorOut("SSL Certificate Installation failed");
+        }
+        else {
+            logger( "SSL Certificates have been installed", "info" );
+        }
+    }
+    else {
+        logger( "Unable to install SSL certificates as openssl is not 
installed",                                     "error" );
+        logger( "Install openssl and then run 
/opt/traffic_ops/install/bin/generateCert to install SSL certificates", "error" 
);
+        exit 4;
+    }
+
+}
+
+# -d     - Debug Mode:       More output to the terminal
+# -a     - Automatic mode:   If there are questions in the config file which 
do not have answers, the script
+#                            will look to the defaults for the answer. If the 
answer is not in the defaults
+#                            the script will exit
+# -h     - Help:             Basic command line help menu
+# -cfile - Input File:       The input config file used to ask and answer 
questions
+
+sub main {
+    our $inputFile = "";
+    our $automatic = 0;
+    our $debug     = 0;
+    my $help = 0;
+
+    my $usageString = "Usage: postinstall [-a] [-d] -cfile=[config_file]\n";
+
+    GetOptions(
+        "cfile=s"     => \$inputFile,
+        "automatic"   => \$automatic,
+        "reconfigure" => \$reconfigure,
+        "defaults"    => \$dumpDefaults,
+        "debug"       => \$debug,
+        "help"        => \$help
+    ) or die($usageString);
+
+    # stores the default questions and answers
+    our $defaultInputs = getDefaults();
+
+    if ($help) {
+        print $usageString;
+        exit(0);
+    }
+
+    if ( $ENV{USER} ne "root" ) {
+        errorOut("You must run this script as the root user");
+    }
+
+    logger( "Starting postinstall", "info" );
+
+    if ($::debug) {
+        logger( "Debug is on", "info" );
+    }
+
+    if ($::automatic) {
+        logger( "Running in automatic mode", "info" );
+    }
+
+    if ( -f $reconfigure_file ) {
+        logger( "$reconfigure_file file is reprecated - please remove and 
rerun postinstall", "error" );
+        exit(-1);
+    }
+
+    if ($dumpDefaults) {
+        logger( "Writing default configuration file to $outputConfigFile", 
"info" );
+        writeJson( $outputConfigFile, $::defaultInputs );
+        exit(0);
+    }
+
+    if ($reconfigure) {
+        logger( "Postinstall is in reconfigure mode", "info" );
+    }
+    else {
+        logger( "Postinstall not in reconfigure mode", "info" );
+    }
+
+    # check if the user has root access
+    if ( $ENV{USER} ne "root" ) {
+        errorOut("You must run this script as the root user");
+    }
+
+    rotateLog($cpanLogFile);
+
+    if ( -s $::logFile > $maxLogSize ) {
+        logger( "Postinstall log above max size of $maxLogSize bytes - 
rotating", "info" );
+        rotateLog($logFile);
+    }
+
+    # used to store the questions and answers provided by the user
+    my $userInput;
+
+    # if no input file provided use the defaults
+    if ( $::inputFile eq "" ) {
+        logger( "No input file given - using defaults", "info" );
+        $userInput = $::defaultInputs;
+    }
+    else {
+        logger( "Using input file $::inputFile", "info" );
+
+        # check if the input file exists
+        errorOut("File \'$::inputFile\' not found") if ( !-f $::inputFile );
+
+        # read and store the input file
+        $userInput = readJson($::inputFile);
+    }
+
+    # sanity check the defaults if running them automatically
+    sanityCheckDefaults();
+
+    # check the input config file against the defaults to check for missing 
questions
+    sanityCheckConfig($userInput) if ( $inputFile ne "" );
+
+    chdir("/opt/traffic_ops/install/bin");
+
+    # if the reconfigure file exists or reconfigure is set then rebuild the 
perl deps
+    if ( -f $reconfigure_file || $reconfigure ) {
+        my $rc = BuildPerlDeps::build(1);
+        if ( $rc != 0 ) {
+            errorOut( "Failed to install perl dependencies, check the console 
output and rerun postinstall once you've resolved the error" );
+        }
+        $rc = execCommand( "./download_web_deps", "-i" );
+        if ( $rc != 0 ) {
+            errorOut( "Failed to install Traffic Ops Web dependencies, check 
the console output and rerun postinstall once you've resolved the error" );
+        }
+    }
+    else {
+        my $rc = BuildPerlDeps::build();
+        if ( $rc != 0 ) {
+            errorOut( "Failed to install perl dependencies, check the console 
output and rerun postinstall once you've resolved the error" );
+        }
+        $rc = execCommand( "./download_web_deps", "-i" );
+        if ( $rc != 0 ) {
+            errorOut( "Failed to install Traffic Ops Web dependencies, check 
the console output and rerun postinstall once you've resolved the error" );
+        }
+    }
+
+    # The generator functions handle checking input/default/automatic mode
+
+    # todbconf will be used later when setting up the database
+    my $todbconf = generateDbConf( $userInput, $::databaseConfFile, 
$::dbConfFile );
+    generateCdnConf( $userInput, $::cdnConfFile );
+    generateLdapConf( $userInput, $::ldapConfFile );
+    my $adminconf = generateUsersConf( $userInput, $::usersConfFile );
+    generateProfilesDir( $userInput, $::profilesConfFile );
+    generateOpenSSLConf( $userInput, $::opensslConfFile );
+    my $paramconf = generateParamConf( $userInput, $::paramConfFile );
+
+    # if the reconfigure file exists or the reconfigure command line arg is 
set then setup the database
+    if ( -f $reconfigure_file || $reconfigure ) {
+        if ($::automatic) {
+            setupDatabase( $todbconf, $::opensslConfFile );
+        }
+        else {
+            setupDatabase( $todbconf, 0 );
+        }
+    }
+
+    # remove the reconfigure file if it exists
+    if ( -f $reconfigure_file ) {
+        logger( "Removing reconfigure file", "info" );
+        unlink($reconfigure_file);
+    }
+
+    logger( "Starting Traffic Ops", "info" );
+    execCommand("/sbin/service traffic_ops start");
+
+    logger( "Waiting for Traffic Ops to start", "info" );
+
+    if ( !profiles_exist( $adminconf, $paramconf->{"tm.url"} ) ) {
+        print "Creating default profiles...\n";
+        replace_profile_templates( $paramconf, $paramconf->{"tm.url"} );
+        import_profiles($adminconf);
+        profiles_exist($adminconf);    # call again to create 
$reconfigure_defaults file if import was successful
+    }
+    else {
+        print "Not creating default profiles.\n";
+    }
+}
+
+main;
+
+logger( "Postinstall complete\n", "info" );
+
+# vi:syntax=perl

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/bae43757/traffic_ops/install/lib/BuildPerlDeps.pm
----------------------------------------------------------------------
diff --git a/traffic_ops/install/lib/BuildPerlDeps.pm 
b/traffic_ops/install/lib/BuildPerlDeps.pm
new file mode 100644
index 0000000..302d5c3
--- /dev/null
+++ b/traffic_ops/install/lib/BuildPerlDeps.pm
@@ -0,0 +1,99 @@
+#!/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.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+use lib qw(/opt/traffic_ops/install/lib /opt/traffic_ops/lib/perl5 
/opt/traffic_ops/app/lib);
+
+package BuildPerlDeps;
+
+use InstallUtils qw{ :all };
+
+use base qw{ Exporter };
+our @EXPORT_OK = qw{ build };
+our %EXPORT_TAGS = ( all => \@EXPORT_OK );
+
+sub build {
+    my $opt_i = shift;
+
+    my @dependencies = ( "expat-devel", "mod_ssl", "mkisofs", "libpcap", 
"libpcap-devel", "libcurl", "libcurl-devel", "mysql-server", "mysql-devel", 
"openssl", "openssl-devel", "cpan", "gcc", "make", "pkgconfig", "automake", 
"autoconf", "libtool", "gettext", "libidn-devel" );
+
+    my $msg = << 'EOF';
+
+This script will build and package the required Traffic Ops perl modules.
+In order to complete this operation, Development tools such as the gcc
+compiler will be installed on this machine.
+
+EOF
+
+    $ENV{PERL_MM_USE_DEFAULT}    = 1;
+    $ENV{PERL_MM_NONINTERACTIVE} = 1;
+    $ENV{AUTOMATED_TESTING}      = 1;
+
+    my $result;
+
+    if ( $ENV{USER} ne "root" ) {
+        errorOut("You must run this script as the root user");
+    }
+
+    logger( $msg, "info" );
+
+    chdir("/opt/traffic_ops/app");
+
+    if ( defined $opt_i && $opt_i == 1 ) {
+        if ( !-x "/usr/bin/yum" ) {
+            errorOut("You must install 'yum'");
+        }
+
+        logger( "Installing dependencies", "info" );
+        $result = execCommand( "/usr/bin/yum", "install", @dependencies );
+        if ( $result != 0 ) {
+            errorOut("Dependency installation failed, look through the output 
and correct the problem");
+        }
+        logger( "Building perl modules", "info" );
+
+        $result = execCommand( "/opt/traffic_ops/install/bin/cpan.sh", 
"pi_custom_log=" . $::cpanLogFile, "/opt/traffic_ops/install/bin/yaml.txt" );
+        if ( $result != 0 ) {
+            errorOut("Failed to install YAML, look through the output and 
correct the problem");
+        }
+
+        $result = execCommand( "/opt/traffic_ops/install/bin/cpan.sh", 
"pi_custom_log=" . $::cpanLogFile, "/opt/traffic_ops/install/bin/carton.txt" );
+        if ( $result != 0 ) {
+            errorOut("Failed to install Carton, look through the output and 
correct the problem");
+        }
+    }
+
+    $result = execCommand( "/usr/local/bin/carton", "install", "--deployment", 
"--cached" );
+    if ( $result != 0 ) {
+        errorOut("Failure to build required perl modules, check the output and 
correct the problem");
+    }
+
+    if ( !-s "/opt/traffic_ops/lib/perl5" ) {
+        logger( "Linking perl libraries...", "info" );
+        if ( !-d "/opt/traffic_ops/lib" ) {
+            mkdir("/opt/traffic_ops/lib");
+        }
+        symlink( "/opt/traffic_ops/app/local/lib/perl5", 
"/opt/traffic_ops/lib/perl5" );
+        execCommand( "/bin/chown", "-R", "trafops:trafops", 
"/opt/traffic_ops/lib" );
+    }
+    logger( "Installing perl scripts", "info" );
+    chdir("/opt/traffic_ops/app/local/bin");
+    my $rc = execCommand( "/bin/cp", "-R", ".", "/opt/traffic_ops/app/bin" );
+    if ( $rc != 0 ) {
+        logger( "Failed to copy perl scripts to /opt/traffic_ops/app/bin", 
"error" );
+    }
+
+    return 0;
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/bae43757/traffic_ops/install/lib/GenerateCert.pm
----------------------------------------------------------------------
diff --git a/traffic_ops/install/lib/GenerateCert.pm 
b/traffic_ops/install/lib/GenerateCert.pm
new file mode 100644
index 0000000..efdcc96
--- /dev/null
+++ b/traffic_ops/install/lib/GenerateCert.pm
@@ -0,0 +1,232 @@
+#!/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.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+package GenerateCert;
+
+use strict;
+
+use lib qw(/opt/traffic_ops/install/lib /opt/traffic_ops/lib/perl5 
/opt/traffic_ops/app/lib);
+
+use base qw{ Exporter };
+our @EXPORT_OK = qw{ createCert };
+our %EXPORT_TAGS = ( all => \@EXPORT_OK );
+
+use JSON;
+use InstallUtils;
+use File::Temp;
+use Data::Dumper;
+use File::Copy;
+use InstallUtils qw{ :all };
+
+my $ca       = "/etc/pki/tls/certs/localhost.ca";
+my $csr      = "/etc/pki/tls/certs/localhost.csr";
+my $cert     = "/etc/pki/tls/certs/localhost.crt";
+my $cdn_conf = "/opt/traffic_ops/app/conf/cdn.conf";
+my $key      = "/etc/pki/tls/private/localhost.key";
+my $msg      = << 'EOF';
+
+       We're now running a script to generate a self signed X509 SSL 
certificate.
+
+EOF
+
+sub writeCdn_conf {
+    my $cdn_conf = shift;
+
+    # listen param to be inserted
+    my $listen_str = 
"https://[::]:443?cert=${cert}&key=${key}&ca=${ca}&verify=0x00&ciphers=AES128-GCM-SHA256:HIGH:!RC4:!MD5:!aNULL:!EDH:!ED";;
+
+    # load as perl hash to find string to be replaced
+    my $cdnh = do $cdn_conf;
+    if ( exists $cdnh->{hypnotoad} ) {
+        $cdnh->{hypnotoad}{listen} = [$listen_str];
+    }
+    else {
+
+        # add the whole hypnotoad config without affecting anything else in 
the config
+        $cdnh->{hypnotoad} = {
+            listen   => [$listen_str],
+            user     => 'trafops',
+            group    => 'trafops',
+            pid_file => '/var/run/traffic_ops.pid',
+            workers  => 48,
+        };
+    }
+
+    # dump conf data in compact but readable form
+    my $dumper = Data::Dumper->new( [$cdnh] );
+    $dumper->Indent(1)->Terse(1)->Quotekeys(0);
+
+    # write whole config to temp file in pwd (keeps in same filesystem)
+    my $tmpfile = File::Temp->new( DIR => '.' );
+    print $tmpfile $dumper->Dump();
+    close $tmpfile;
+
+    # make backup of current file
+    my $backup_num = 0;
+    my $backup_name;
+    do {
+        $backup_num++;
+        $backup_name = "$cdn_conf.backup$backup_num";
+    } while ( -e $backup_name );
+    rename( $cdn_conf, $backup_name ) or die("rename(): $!");
+
+    # rename temp file to cdn.conf and set ownership/permissions same as backup
+    my @stats = stat($backup_name);
+    my ( $uid, $gid, $perm ) = @stats[ 4, 5, 2 ];
+    move( "$tmpfile", $cdn_conf ) or die("move(): $!");
+
+    chown $uid, $gid, $cdn_conf;
+    chmod $perm, $cdn_conf;
+}
+
+# execOpenssl takes a description of the command being done, and an array of 
arguments to OpenSSL,
+# and tries to execute the command, on failure prompting the user to retry.
+# The description should be capitalized, but not terminated with punctuation.
+# Returns the OpenSSL exit code.
+sub execOpenssl {
+    my ( $description, @args ) = @_;
+    logger( $description, "info" );
+    my $result = 1;
+    while ( $result != 0 ) {
+        $result = InstallUtils::execCommand( "openssl", @args );
+        if ( $result != 0 ) {
+            my $ans = "";
+            while ( $ans !~ /^[yY]/ && $ans !~ /^[nN]/ ) {
+                $ans = InstallUtils::promptUser( $description . " failed. Try 
again (y/n)", "y" );
+            }
+            if ( $ans =~ /^[nN]/ ) {
+                return $result;
+            }
+        }
+    }
+    return $result;
+}
+
+sub createCert {
+
+    # the file used for ssl configuration
+    my $opensslconf = shift;
+
+    if ( !defined $opensslconf ) {
+        logger( "No input file - running openssl configuration in interactive 
mode", "info" );
+    }
+
+    logger( $msg, "info" );
+
+    logger( "Postinstall SSL Certificate Creation", "info" );
+
+    my $params;
+    my $passphrase;
+
+    # load the parameters for the certificate
+    if ( defined $opensslconf ) {
+        my $config = InstallUtils::readJson($opensslconf);
+        if ( defined $config->{country} ) {
+
+            # the parameters to auto generate the certificate
+            $params = 
"/C=$config->{country}/ST=$config->{state}/L=$config->{locality}/O=$config->{company}/OU=$config->{org_unit}/CN=$config->{common_name}/";
+
+            $passphrase = $config->{rsaPassword};
+        }
+    }
+
+    if ( execOpenssl( "Generating an RSA Private Server Key", "genrsa", 
"-des3", "-out", "server.key", "-passout", "pass:$passphrase", "1024" ) != 0 ) {
+        exit 1;
+    }
+    logger( "The server key has been generated", "info" );
+
+    if ($params) {
+        if ( execOpenssl( "Creating a Certificate Signing Request (CSR)", 
"req", "-new", "-key", "server.key", "-out", "server.csr", "-passin", 
"pass:$passphrase", "-subj", $params ) != 0 ) {
+            exit 1;
+        }
+    }
+    else {
+        if ( execOpenssl( "Creating a Certificate Signing Request (CSR)", 
"req", "-new", "-key", "server.key", "-out", "server.csr", "-passin", 
"pass:$passphrase" ) != 0 ) {
+            exit 1;
+        }
+    }
+
+    logger( "The Certificate Signing Request has been generated", "info" );
+
+    InstallUtils::execCommand( "/bin/mv", "server.key", "server.key.orig" );
+
+    if ( execOpenssl( "Removing the pass phrase from the server key", "rsa", 
"-in", "server.key.orig", "-out", "server.key", "-passin", "pass:$passphrase" ) 
!= 0 ) {
+        exit 1;
+    }
+    logger( "The pass phrase has been removed from the server key", "info" );
+
+    if ( execOpenssl( "Generating a Self-signed certificate", "x509", "-req", 
"-days", "365", "-in", "server.csr", "-signkey", "server.key", "-out", 
"server.crt" ) != 0 ) {
+        exit 1;
+    }
+    logger( "A server key and self signed certificate has been generated", 
"info" );
+
+    logger( "Installing the server key and server certificate", "info" );
+
+    my $result = InstallUtils::execCommand( "/bin/cp", "server.key", "$key" );
+    if ( $result != 0 ) {
+        errorOut("Failed to install the private server key");
+    }
+    $result = InstallUtils::execCommand( "/bin/chmod", "600",             
"$key" );
+    $result = InstallUtils::execCommand( "/bin/chown", "trafops:trafops", 
"$key" );
+
+    if ( $result != 0 ) {
+        errorOut("Failed to install the private server key");
+    }
+
+    logger( "The private key has been installed",     "info" );
+    logger( "Installing the self signed certificate", "info" );
+
+    $result = InstallUtils::execCommand( "/bin/cp", "server.crt", "$cert" );
+
+    if ( $result != 0 ) {
+        errorOut("Failed to install the self signed certificate");
+    }
+
+    $result = InstallUtils::execCommand( "/bin/chmod", "600",             
"$cert" );
+    $result = InstallUtils::execCommand( "/bin/chown", "trafops:trafops", 
"$cert" );
+
+    if ( $result != 0 ) {
+        errorOut("Failed to install the self signed certificate");
+    }
+
+    logger( "Saving the self signed csr", "info" );
+    $result = InstallUtils::execCommand( "/bin/cp", "server.csr", "$csr" );
+
+    if ( $result != 0 ) {
+        errorOut("Failed to save the self signed csr");
+    }
+    $result = InstallUtils::execCommand( "/bin/chmod", "664",             
"$csr" );
+    $result = InstallUtils::execCommand( "/bin/chown", "trafops:trafops", 
"$csr" );
+
+    writeCdn_conf($cdn_conf);
+
+    my $msg = << 'EOF';
+
+        The self signed certificate has now been installed. 
+
+        You may obtain a certificate signed by a Certificate Authority using 
the
+        server.csr file saved in the current directory.  Once you have obtained
+        a signed certificate, copy it to /etc/pki/tls/certs/localhost.crt and
+        restart Traffic Ops.
+
+EOF
+
+    logger( $msg, "info" );
+
+    return 0;
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/bae43757/traffic_ops/install/lib/InstallUtils.pm
----------------------------------------------------------------------
diff --git a/traffic_ops/install/lib/InstallUtils.pm 
b/traffic_ops/install/lib/InstallUtils.pm
index 4d45e7d..72665d1 100644
--- a/traffic_ops/install/lib/InstallUtils.pm
+++ b/traffic_ops/install/lib/InstallUtils.pm
@@ -24,126 +24,216 @@ package InstallUtils;
 
 
 use Term::ReadPassword;
+use JSON;
+use IO::Pipe;
 use base qw{ Exporter };
-our @EXPORT_OK = qw{ execCommand randomWord promptUser promptRequired 
promptPassword promptPasswordVerify trim readJson writeJson writePerl};
+our @EXPORT_OK = qw{ execCommand randomWord promptUser promptRequired 
promptPassword promptPasswordVerify trim readJson writeJson writePerl errorOut 
logger rotateLog};
 our %EXPORT_TAGS = ( all => \@EXPORT_OK );
 
 sub execCommand {
-       my ( $cmd, @args ) = @_;
-       system( $cmd, @args );
-       my $result = $? >> 8;
-       return $result;
+    my ( $command, @args ) = @_; 
+
+    my $pipe = IO::Pipe->new;
+    my $pid;
+    my $result = 0;
+    my $customLog = "";
+
+    # find log file in args and remove if found 
+    # TODO: More documentation here
+    foreach my $var (@args) {
+        if ( index($var, "pi_custom_log=") != -1 ) {
+            $customLog = (split(/=/, $var))[1];
+            splice(@args, index($var, "pi_custom_log="), 1);
+            logger("Using custom log \'$customLog\'", "info");
+        }
+    }
+    
+    # create pipe between child and parent and redirect output from child to 
parent for logging
+    my $child = open READER, '-|';
+    defined $child or die "pipe/fork: $!\n";
+    if ($child) { #parent
+        while ( $line = <READER> ) {
+            # log all output from child pipe
+            if ($customLog ne "") {
+                logger($line, "info", $customLog);
+            }
+            else {
+                logger($line, "info");
+            }
+        }
+    }
+    else { #child
+        # redirect stderr to stdout so parent can read
+        open STDERR, '>&STDOUT';
+        exec($command, @args) or exit(1);
+    }
+}
+
+sub errorOut {
+    logger( @_, "error" );
+    die;
+}
+
+# moves a log to file to a backup file with the same name appended with .bkp
+# This function is intended to keep log file sizes low and is called from 
postinstall
+sub rotateLog {
+    my $logFileName = shift;
+    
+    if ( !-f $logFileName ) {
+        logger("Log file \'$logFileName\' does not exist - not rotating log", 
"error");
+        return;
+    }
+
+    execCommand('/bin/mv', '-f', $logFileName, $logFileName . '.bkp');
+    logger("Rotated log $logFileName", "info");
+}
+
+# outputs logging messages to terminal and log file
+sub logger {
+    my $output = shift;
+    my $type   = shift;
+   
+    # optional custom log file to use instead of main log file used by 
postinstall
+    # cpan uses a custom log file because of its size
+    my $customLogFile = shift;
+
+    my $message = $output;
+    if (index($message, "\n") == -1) {
+        $message = $message . "\n";
+    }
+
+    # if in debug mode or message is more critical than info print to console
+    if ( $::debug || ( defined $type && $type ne "" && $type ne "info" ) ) {
+        print($message);
+    }
+
+    # output to log file
+    my $fh;
+    my $result = 0;
+    if ( defined $customLogFile && $customLogFile ne "" ) {
+        open $fh, '>>', $customLogFile or die("Couldn't open log file 
\'$::customLogFile\'");     
+        $result = 1;
+    }
+    else {
+        if ($::logFile) {
+            open $fh, '>>', $::logFile or die("Couldn't open log file 
\'$::logFile\'");
+            $result = 1;
+        }
+    }
+    
+    if ( $result ) {
+        print $fh localtime . ": " . uc($type) . ' ' . $message;
+        close $fh;
+    }
 }
 
 sub randomWord {
-       my $length = shift || 12;
-       my $secret = '';
-       while ( length($secret) < $length ) {
-               my $c = chr( rand(0x7F) );
-               if ( $c =~ /\w/ ) {
-                       $secret .= $c;
-               }
-       }
-       return $secret;
+    my $length = shift || 12;
+    my $secret = '';
+    while ( length($secret) < $length ) {
+        my $c = chr( rand(0x7F) );
+        if ( $c =~ /\w/ ) {
+            $secret .= $c;
+        }
+    }
+    return $secret;
 }
 
 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 $_;
-       }
+    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 promptRequired {
-       my $val = '';
-       while ( length($val) == 0 ) {
-               $val = promptUser(@_);
-       }
-       return $val;
+    my $val = '';
+    while ( length($val) == 0 ) {
+        $val = promptUser(@_);
+    }
+    return $val;
 }
 
 sub promptPassword {
-       my $prompt = shift;
-       my $pw = promptRequired( $prompt, '', 1 );
-       return $pw;
+    my $prompt = shift;
+    my $pw = promptRequired( $prompt, '', 1 );
+    return $pw;
 }
 
 sub promptPasswordVerify {
-       my $prompt = shift;
-       my $pw     = shift;
-
-       while (1) {
-               $pw = promptPassword($prompt);
-               my $verify = promptPassword("Re-Enter $prompt");
-               last if $pw eq $verify;
-               print "\nError: passwords do not match, try again.\n\n";
-       }
-       return $pw;
+    my $prompt = shift;
+    my $pw     = shift;
+
+    while (1) {
+        $pw = promptPassword($prompt);
+        my $verify = promptPassword("Re-Enter $prompt");
+        last if $pw eq $verify;
+        print "\nError: passwords do not match, try again.\n\n";
+    }
+    return $pw;
 }
 
 sub trim {
-       my $str = shift;
+    my $str = shift;
 
-       $str =~ s/^\s+//;
-       $str =~ s/^\s+$//;
+    $str =~ s/^\s+//;
+    $str =~ s/^\s+$//;
 
-       return $str;
+    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);
+    my $file = shift;
+    open( my $fh, '<', $file ) or die("open(): $!");
+    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;
+    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;
-}
+    my $file = shift;
+    my $data = shift;
 
-1;
+    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;
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/bae43757/traffic_ops/install/lib/ProfileCleanup.pm
----------------------------------------------------------------------
diff --git a/traffic_ops/install/lib/ProfileCleanup.pm 
b/traffic_ops/install/lib/ProfileCleanup.pm
new file mode 100644
index 0000000..3606002
--- /dev/null
+++ b/traffic_ops/install/lib/ProfileCleanup.pm
@@ -0,0 +1,192 @@
+
+package ProfileCleanup;
+
+use InstallUtils qw{ :all };
+use WWW::Curl::Easy;
+use LWP::UserAgent;
+
+use base qw{ Exporter };
+our @EXPORT_OK = qw{ replace_profile_templates import_profiles profiles_exist 
};
+our %EXPORT_TAGS = ( all => \@EXPORT_OK );
+
+sub profile_replace {
+    my ($profile) = @_;
+    my $profile_bak = $profile . ".bak";
+    rename( $profile, $profile_bak ) or die("rename(): $!");
+    open( my $fh,  '<', $profile_bak ) or die("open(): $!");
+    open( my $ofh, '>', $profile )     or die("open(): $!");
+    while (<$fh>) {
+        s/{{.TmUrl}}/$::parameters->{'tm.url'}/g;
+        s/{{.TmInfoUrl}}/$::parameters->{"tminfo.url"}/g;
+        s/{{.TmInstanceName}}/$::parameters->{"cdnname"}/g;
+        
s/{{.GeolocationPollingUrl}}/$::parameters->{"geolocation.polling.url"}/g;
+        
s/{{.Geolocation6PollingUrl}}/$::parameters->{"geolocation6.polling.url"}/g;
+        s/{{.TmUrl}}/$::parameters->{'tm.url'}/g;
+        s/{{.TmToolName}}/Traffic Ops/g;
+        
s/{{.HealthPollingInterval}}/$::parameters->{"health.polling.interval"}/g;
+        
s/{{.CoveragezonePollingUrl}}/$::parameters->{"coveragezone.polling.url"}/g;
+        s/{{.DomainName}}/$::parameters->{"domainname"}/g;
+        s/{{.TldSoaAdmin}}/$::parameters->{"tld.soa.admin"}/g;
+        s/{{.DrivePrefix}}/$::parameters->{"Drive_Prefix"}/g;
+        
s/{{.HealthThresholdLoadavg}}/$::parameters->{"health.threshold.loadavg"}/g;
+        
s/{{.HealthThresholdAvailableBandwidthInKbps}}/$::parameters->{"health.threshold.availableBandwidthInKbps"}/g;
+        s/{{.RAMDrivePrefix}}/$::parameters->{"RAM_Drive_Prefix"}/g;
+        s/{{.RAMDriveLetters}}/$::parameters->{"RAM_Drive_Letters"}/g;
+        
s/{{.HealthConnectionTimeout}}/$::parameters->{"health.connection.timeout"}/g;
+        s#{{.CronOrtSyncds}}#*/15 * * * * root /opt/ort/traffic_ops_ort.pl 
syncds warn $::parameters->{'tm.url'} $tmAdminUser:$tmAdminPw > 
/tmp/ort/syncds.log 2>&1#g;
+        print $ofh $_;
+    }
+    close $fh;
+    close $ofh;
+    unlink $profile_bak;
+}
+
+sub replace_profile_templates {
+    my $conf = shift;
+
+    $::parameters->{'tm.url'}                                    = 
$conf->{"tm.url"};
+    $::parameters->{"tminfo.url"}                                = 
"$::parameters->{'tm.url'}/info";
+    $::parameters->{"cdnname"}                                   = 
$conf->{"cdn_name"};
+    $::parameters->{"geolocation.polling.url"}                   = 
"$::parameters->{'tm.url'}/routing/GeoIP2-City.mmdb.gz";
+    $::parameters->{"geolocation6.polling.url"}                  = 
"$::parameters->{'tm.url'}/routing/GeoIP2-Cityv6.mmdb.gz";
+    $::parameters->{"health.polling.interval"}                   = 
$conf->{"health_polling_int"};
+    $::parameters->{"coveragezone.polling.url"}                  = 
"$::parameters->{'tm.url'}/routing/coverage-zone.json";
+    $::parameters->{"domainname"}                                = 
$conf->{"dns_subdomain"};
+    $::parameters->{"tld.soa.admin"}                             = 
$conf->{"soa_admin"};
+    $::parameters->{"Drive_Prefix"}                              = 
$conf->{"driver_prefix"};
+    $::parameters->{"RAM_Drive_Prefix"}                          = 
$conf->{"ram_drive_prefix"};
+    $::parameters->{"RAM_Drive_Letters"}                         = 
$conf->{"ram_drive_letters"};
+    $::parameters->{"health.threshold.loadavg"}                  = 
$conf->{"health_thresh_load_avg"};
+    $::parameters->{"health.threshold.availableBandwidthInKbps"} = 
$conf->{"health_thresh_kbps"};
+    $::parameters->{"health.connection.timeout"}                 = 
$conf->{"health_connect_timeout"};
+
+    profile_replace( $::profile_dir . "profile.global.traffic_ops" );
+    profile_replace( $::profile_dir . "profile.traffic_monitor.traffic_ops" );
+    profile_replace( $::profile_dir . "profile.traffic_router.traffic_ops" );
+    profile_replace( $::profile_dir . "profile.trafficserver_edge.traffic_ops" 
);
+    profile_replace( $::profile_dir . "profile.trafficserver_mid.traffic_ops" 
);
+    writeJson( $::post_install_cfg, $::parameters );
+}
+
+# Takes the Traffic Ops URI, user, and password.
+# Returns the cookie, or the empty string on error
+sub get_traffic_ops_cookie {
+    my ( $uri, $user, $pass ) = @_;
+
+    my $loginUri = "/api/1.2/user/login";
+
+    my $curl          = WWW::Curl::Easy->new;
+    my $response_body = "";
+    open( my $fileb, ">", \$response_body );
+    my $loginData = JSON::encode_json( { u => $user, p => $pass } );
+    $curl->setopt( WWW::Curl::Easy::CURLOPT_URL,            $uri . $loginUri );
+    $curl->setopt( WWW::Curl::Easy::CURLOPT_SSL_VERIFYPEER, 0 );
+    $curl->setopt( WWW::Curl::Easy::CURLOPT_HEADER,         1 );               
   # include header in response
+    $curl->setopt( WWW::Curl::Easy::CURLOPT_NOBODY,         1 );               
   # disclude body in response
+    $curl->setopt( WWW::Curl::Easy::CURLOPT_POST,           1 );
+    $curl->setopt( WWW::Curl::Easy::CURLOPT_POSTFIELDS,     $loginData );
+    $curl->setopt( WWW::Curl::Easy::CURLOPT_WRITEDATA,      $fileb );          
   # put response in this var
+    $curl->perform();
+
+    my $cookie = $response_body;
+    if ( $cookie =~ /mojolicious=(.*); expires/ ) {
+        $cookie = $1;
+    }
+    else {
+        $cookie = "";
+    }
+    return $cookie;
+}
+
+# Takes the filename of a Traffic Ops (TO) profile to import, the TO URI, and 
the TO login cookie
+sub profile_import_single {
+    my ( $profileFilename, $uri, $trafficOpsCookie ) = @_;
+    logger( "Importing Profiles with: " . "curl -v -k -X POST -H \"Cookie: 
mojolicious=$trafficOpsCookie\" -F \"filename=$profileFilename\" -F 
\"profile_to_import=\@$profileFilename\" $uri/profile/doImport", "info" );
+    my $rc = execCommand("curl -v -k -X POST -H \"Cookie: 
mojolicious=$trafficOpsCookie\" -F \"filename=$profileFilename\" -F 
\"profile_to_import=\@$profileFilename\" $uri/profile/doImport");
+    if ( $rc != 0 ) {
+        logger( "Failed to import Traffic Ops profile, check the console 
output and rerun postinstall once you've resolved the error", "error" );
+    }
+}
+
+sub import_profiles {
+    my $config = shift;
+    logger( "Importing profiles...", "info" );
+
+    my $toUri  = $::parameters->{'tm.url'};
+    my $toUser = $config->{"username"};
+    my $toPass = $config->{"password"};
+
+    my $toCookie = get_traffic_ops_cookie( $toUri, $toUser, $toPass );
+
+    logger( "Got cookie: " . $toCookie, "info" );
+
+    # \todo use an array?
+    logger( "Importing Global profile...", "info" );
+    profile_import_single( $::profile_dir . "profile.global.traffic_ops", 
$toUri, $toCookie );
+    logger( "Importing Traffic Monitor profile...", "info" );
+    profile_import_single( $::profile_dir . 
"profile.traffic_monitor.traffic_ops", $toUri, $toCookie );
+    logger( "Importing Traffic Router profile...", "info" );
+    profile_import_single( $::profile_dir . 
"profile.traffic_router.traffic_ops", $toUri, $toCookie );
+    logger( "Importing TrafficServer Edge profile...", "info" );
+    profile_import_single( $::profile_dir . 
"profile.trafficserver_edge.traffic_ops", $toUri, $toCookie );
+    logger( "Importing TrafficServer Mid profile...", "info" );
+    profile_import_single( $::profile_dir . 
"profile.trafficserver_mid.traffic_ops", $toUri, $toCookie );
+    logger( "Finished Importing Profiles.", "info" );
+}
+
+sub profiles_exist {
+    my $config = shift;
+    my $tmurl  = shift;
+
+    if ( -f $::reconfigure_defaults ) {
+        logger( "Default profiles were previously created. Remove " . 
$::reconfigure_defaults . " to create again", "warn" );
+        return 1;
+    }
+
+    $::parameters->{'tm.url'} = $tmurl;
+
+    my $uri = $::parameters->{'tm.url'};
+    my $toCookie = get_traffic_ops_cookie( $::parameters->{'tm.url'}, 
$config->{"username"}, $config->{"password"} );
+
+    my $profileEndpoint = "/api/1.2/profiles.json";
+
+    my $ua = LWP::UserAgent->new;
+    $ua->ssl_opts( verify_hostname => 0, SSL_verify_mode => 0x00 );
+    my $req = HTTP::Request->new( GET => $uri . $profileEndpoint );
+    $req->header( 'Cookie' => "mojolicious=" . $toCookie );
+    my $resp = $ua->request($req);
+
+    if ( !$resp->is_success ) {
+        logger( "Error checking if profiles exist: " . $resp->status_line, 
"error" );
+        return 1;    # return true, so we don't attempt to create profiles
+    }
+    my $message = $resp->decoded_content;
+
+    my $profiles = JSON->new->utf8->decode($message);
+    if (   ( !defined $profiles->{"response"} )
+        || ( ref $profiles->{"response"} ne 'ARRAY' ) )
+    {
+        logger( "Error checking if profiles exist: invalid JSON: $message", 
"error" );
+        return 1;    # return true, so we don't attempt to create profiles
+    }
+
+    my $num_profiles = scalar( @{ $profiles->{"response"} } );
+    logger( "Existing Profile Count: $num_profiles", "info" );
+
+    my %initial_profiles = (
+        "INFLUXDB"      => 1,
+        "RIAK_ALL"      => 1,
+        "TRAFFIC_STATS" => 1
+    );
+
+    my $profiles_response = $profiles->{"response"};
+    foreach my $profile (@$profiles_response) {
+        if ( !exists $initial_profiles{ $profile->{"name"} } ) {
+            logger( "Found existing profile (" . $profile->{"name"} . ")", 
"info" );
+            open( my $reconfigure_defaults_file, '>', $::reconfigure_defaults 
) or die("Failed to open() $reconfigure_defaults: $!");
+            close($reconfigure_defaults_file);
+            return 1;
+        }
+    }
+    return 0;
+}

Reply via email to