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; +}