# SDPEnv.py #------------------------------------------------------------------------------ # Copyright (c) Perforce Software, Inc., 2007-2014. All rights reserved # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1 Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL PERFORCE # SOFTWARE, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH # DAMAGE. #------------------------------------------------------------------------------ # Utilities for creating or validating an environment based on a master configuration file from __future__ import print_function import os import os.path import sys import subprocess import socket import shutil import glob import re import textwrap import logging import argparse from collections import OrderedDict # Python 2.7/3.3 compatibility. def python3(): return sys.version_info[0] >= 3 if python3(): from configparser import ConfigParser from io import StringIO else: from ConfigParser import ConfigParser from StringIO import StringIO import P4 MODULE_NAME = 'SDPEnv' DEFAULT_CFG_FILE = 'sdp_master_config.ini' DEFAULT_LOG_FILE = '%s.log' % MODULE_NAME DEFAULT_VERBOSITY = 'INFO' LOGGER_NAME = '%s.py' % MODULE_NAME DEFAULTS_SECTION = 'Defaults' # Standard configure lines for an instance - these are arguments to # p4 configure set <values below> STANDARD_CONFIGURE_LINES = """db.peeking=2 dm.user.noautocreate=2 dm.user.resetpassword=1 filesys.P4ROOT.min=1G filesys.depot.min=1G filesys.P4JOURNAL.min=1G spec.hashbuckets=99 monitor=1 server=3 net.tcpsize=128k server.commandlimits=2""".split("\n") class SDPException(Exception): "Base exceptions" pass class SDPConfigException(SDPException): "Exceptions in config" pass def joinpath(root, *path_elts): "Deals with drive roots or a full path as the root" if len(root) > 0 and root[-1] == ":": return os.path.join(root, os.sep, *path_elts) else: return os.path.join(root, *path_elts) def running_as_administrator(): "Makes sure current process has admin rights" output = "" try: output = subprocess.check_output("net session", universal_newlines=True, stderr=subprocess.STDOUT, shell=True) except subprocess.CalledProcessError as e: output = e.output return not re.search(r"Access is denied", output) def find_record(rec_list, key_field, search_key): "Returns dictionary found in list of them" for rec in rec_list: if rec[key_field].lower() == search_key.lower(): return rec return None class SDPInstance(object): "A single instance" def __init__(self, config, section): "Expects a configparser" self._attrs = {} def_attrs = "p4name sdp_service_type sdp_hostname sdp_instance sdp_p4port_number sdp_os_username".split() def_attrs.extend("metadata_root depotdata_root logdata_root".split()) for def_attr in def_attrs: self._attrs[def_attr] = "" for item in config.items(section): self._attrs[item[0]] = item[1] self._init_dirs() def __iter__(self): for attr in self._attrs.keys(): yield self._attrs[attr] def _init_dirs(self): "Initialises directory names for this instance" curr_scriptdir = os.path.dirname(os.path.realpath(__file__)) self._attrs["sdp_common_bin_dir"] = os.path.abspath(os.path.join(curr_scriptdir, '..', 'p4', 'common', 'bin')) self._attrs["depotdata_dir"] = joinpath(self.depotdata_root, 'p4', self.sdp_instance) self._attrs["metadata_dir"] = joinpath(self.metadata_root, 'p4', self.sdp_instance) self._attrs["logdata_dir"] = joinpath(self.logdata_root, 'p4', self.sdp_instance) self._attrs["bin_dir"] = joinpath(self.depotdata_root, 'p4', self.sdp_instance, 'bin') self._attrs["common_bin_dir"] = joinpath(self.depotdata_root, 'p4', 'common', 'bin') self._attrs["root_dir"] = joinpath(self.metadata_root, 'p4', self.sdp_instance, 'root') self._attrs["logs_dir"] = joinpath(self.logdata_root, 'p4', self.sdp_instance, 'logs') self._attrs["sdp_config_dir"] = joinpath(self.depotdata_root, 'p4', 'config') def __getitem__(self, name): return object.__getattribute__(self, '_attrs').get(name) def is_current_host(self): "Checks against current hostname" return socket.gethostname().lower() == self._attrs["sdp_hostname"].lower() def __getattribute__(self, name): "Only allow appropriate attributes" if name in ["_attrs", "_init_dirs", "get", "is_current_host"]: return object.__getattribute__(self, name) else: if not name in object.__getattribute__(self, '_attrs'): raise AttributeError("Unknown attribute '%s'" % name) return object.__getattribute__(self, '_attrs').get(name, "") class SDPConfig(object): "The main class to process SDP configurations" def __init__(self, config_data=None): self.config = None self.instances = {} self.commands = [] # List of command files to run - and their order parser = argparse.ArgumentParser( formatter_class=argparse.RawDescriptionHelpFormatter, description=textwrap.dedent('''\ NAME SDPEnv.py VERSION 1.0.0 DESCRIPTION Create the environment for an SDP (Server Deployment Package) EXAMPLES SDPEnv.py -h EXIT CODES Zero indicates normal completion. Non-zero indicates an error. '''), epilog="Copyright (c) 2008-2014 Perforce Software, Inc. " "Provided for use as defined in the Perforce Consulting Services Agreement." ) parser.add_argument('-y', '--yes', action='store_true', help="Perform actual changes such as directory creation and file copying.") parser.add_argument('-c', '--config_filename', default=DEFAULT_CFG_FILE, help="Config file, relative or absolute path. Default: " + DEFAULT_CFG_FILE) parser.add_argument('-r', '--report', action='store_true', help="Report on current configuration") parser.add_argument('-L', '--log', default=DEFAULT_LOG_FILE, help="Default: " + DEFAULT_LOG_FILE) parser.add_argument('-v', '--verbosity', nargs='?', const="INFO", default=DEFAULT_VERBOSITY, choices=('DEBUG', 'WARNING', 'INFO', 'ERROR', 'FATAL'), help="Output verbosity level. Default is: " + DEFAULT_VERBOSITY) self.options = parser.parse_args() self.logger = logging.getLogger(LOGGER_NAME) self.logger.setLevel(self.options.verbosity) h = logging.StreamHandler() # df = datestamp formatter; bf= basic formatter. # df = logging.Formatter('%(asctime)s %(levelname)s: %(message)s', datefmt='%Y/%m/%d %H:%M:%S') bf = logging.Formatter('%(levelname)s: %(message)s') h.setFormatter(bf) self.logger.addHandler(h) self.logger.debug("Command Line Options: %s\n" % self.options) if not self.options.yes: self.logger.info("Running in reporting mode: use -y or --yes to perform actions.") self._read_config(self.options.config_filename, config_data) def _read_config(self, config_filename, config_data): "Read the configuration file" if config_data: # testing config_file = StringIO(config_data) else: if not os.path.exists(config_filename): raise SDPException("Master config file not found: '%s'" % config_filename) config_file = open(config_filename) self.config = ConfigParser() self.config.readfp(config_file) self.logger.info("Found the following sections: %s" % self.config.sections()) for section in self.config.sections(): self.instances[section] = SDPInstance(self.config, section) def isvalid_config(self): "Check configuration read is valid" required_options = "p4name sdp_service_type sdp_hostname sdp_instance sdp_p4port_number sdp_os_username".split() required_options.extend("metadata_root depotdata_root logdata_root".split()) errors = [] for instance_name in self.instances.keys(): missing = [] fields = {} instance = self.instances[instance_name] # Check for require fields for opt in required_options: if instance[opt] == "": missing.append(opt) else: fields[opt] = instance[opt] if missing: errors.append("The following required options are missing '%s' in instance '%s'" % ( ", ".join(missing), instance_name)) # Check for numeric fields field_name = "sdp_p4port_number" if field_name in fields: if not instance[field_name].isdigit(): errors.append("%s must be numeric in instance '%s'" % (field_name.upper(), instance_name)) # Check for restricted values field_name = "sdp_service_type" if field_name in fields: valid_service_types = "standard replica forwarding-replica build-server".split() if not instance[field_name] in valid_service_types: errors.append("%s must be one of '%s' in instance '%s'" % (field_name.upper(), ", ".join(valid_service_types), instance_name)) if errors: raise SDPConfigException("\n".join(errors)) return True def get_master_instance_name(self): "Assumes valid config" #TODO - this assumes only one standard section for instance_name in self.instances: if self.instances[instance_name]["sdp_service_type"] == "standard": return instance_name raise SDPConfigException("No master section found") def write_master_config_ini(self): "Write the appropriate configure values" common_settings = """p4name sdp_os_username sdp_p4serviceuser metadata_root depotdata_root logdata_root sdp_p4superuser admin_pass_filename mailfrom maillist mailhost python keepckps keeplogs limit_one_daily_checkpoint""".split() instance_names = self.instances.keys() lines = [] lines.append("# Global sdp_config.ini") lines.append("") for instance_name in instance_names: instance = self.instances[instance_name] lines.append("\n[%s:%s]" % (instance["sdp_instance"], instance["sdp_hostname"])) lines.append("%s=%s:%s" % ("p4port", instance["sdp_hostname"], instance["sdp_p4port_number"])) for setting in common_settings: lines.append("%s=%s" % (setting, instance[setting])) sdp_config_file = "sdp_config.ini" self.commands.append(sdp_config_file) self.logger.info("Config file written: %s" % sdp_config_file) with open(sdp_config_file, "w") as fh: for line in lines: fh.write("%s\n" % line) def get_configure_bat_contents(self): "Return the information to be written into configure bat files per instance" cmd_lines = {} # indexed by instance name instance_names = self.instances.keys() master_name = self.get_master_instance_name() cmd_lines[master_name] = [] master_instance = self.instances[master_name] p4cmd = "p4 -p %s:%s -u %s" % (master_instance["sdp_hostname"], master_instance["sdp_p4port_number"], master_instance["sdp_p4superuser"]) p4configurecmd = "%s configure set" % (p4cmd) path = joinpath(master_instance["depotdata_root"], "p4", master_instance["sdp_instance"], "checkpoints", "p4_%s" % master_instance["sdp_instance"]) cmd_lines[master_name].append("%s %s#journalPrefix=%s" % (p4configurecmd, master_instance["p4name"], path)) cmd_lines[master_name].append("%s serverid %s" % (p4cmd, master_instance["p4name"])) log_files = {3: 'errors.csv', 7: 'events.csv', 8: 'integrity.csv'} for i in log_files.keys(): cmd_lines[master_name].append("%s %s#serverlog.file.%d=%s" % (p4configurecmd, master_instance["p4name"], i, joinpath(master_instance["logs_dir"], log_files[i]))) cmd_lines[master_name].append("%s %s#serverlog.retain.%d=7" % (p4configurecmd, master_instance["p4name"], i)) for l in STANDARD_CONFIGURE_LINES: if l.strip(): cmd_lines[master_name].append("%s %s" % (p4configurecmd, l.strip())) # Now set up all the config variables for replication for instance_name in [s for s in instance_names if s != master_name]: instance = self.instances[instance_name] if not instance["sdp_service_type"] in ["replica", "forwarding-replica", "build-server"]: continue path = joinpath(instance["depotdata_root"], "p4", instance["sdp_instance"], "checkpoints", "p4_%s" % instance["sdp_instance"]) cmd_lines[master_name].append("%s %s#journalPrefix=%s" % (p4configurecmd, instance["p4name"], path)) path = joinpath(instance["depotdata_root"], "p4", instance["sdp_instance"], "p4tickets.txt") cmd_lines[master_name].append('%s %s#P4PORT=%s' % (p4configurecmd, instance["p4name"], instance["sdp_p4port_number"])) cmd_lines[master_name].append('%s %s#P4TARGET=%s:%s' % (p4configurecmd, instance["p4name"], master_instance["sdp_hostname"], master_instance["sdp_p4port_number"])) tickets_path = joinpath(instance["depotdata_dir"], "p4tickets.txt") cmd_lines[master_name].append('%s %s#P4TICKETS=%s' % (p4configurecmd, instance["p4name"], tickets_path)) log_path = joinpath(instance["logs_dir"], "%s.log" % instance["p4name"]) cmd_lines[master_name].append('%s %s#P4LOG=%s' % (p4configurecmd, instance["p4name"], log_path)) cmd_lines[master_name].append('%s %s#server=3' % (p4configurecmd, instance["p4name"])) cmd_lines[master_name].append('%s "%s#startup.1=pull -i 1"' % (p4configurecmd, instance["p4name"])) for i in range(2, 6): cmd_lines[master_name].append('%s "%s#startup.%d=pull -u -i 1"' % (p4configurecmd, instance["p4name"], i)) cmd_lines[master_name].append('%s "%s#lbr.replication=readonly"' % (p4configurecmd, instance["p4name"])) if instance["sdp_service_type"] in ["replica", "build-server", "forwarding-replica"]: cmd_lines[master_name].append('%s "%s#db.replication=readonly"' % (p4configurecmd, instance["p4name"])) if instance["sdp_service_type"] in ["forwarding-replica"]: cmd_lines[master_name].append('%s "%s#rpl.forward.all=1"' % (p4configurecmd, instance["p4name"])) cmd_lines[master_name].append('%s "%s#serviceUser=%s"' % (p4configurecmd, instance["p4name"], instance["p4name"])) if not instance.p4name in cmd_lines: cmd_lines[instance.p4name] = [] line = "p4 -p %s:%s -u %s serverid %s" % (instance["sdp_hostname"], instance["sdp_p4port_number"], instance["sdp_p4superuser"], instance["p4name"]) cmd_lines[instance.p4name].append(line) return cmd_lines def write_configure_bat_contents(self, cmd_lines): "Write the appropriate configure bat files for respective instances" command_files = [] for instance_name in cmd_lines.keys(): command_file = "configure_%s.bat" % (instance_name) command_files.append(command_file) with open(command_file, "w") as fh: for line in cmd_lines[instance_name]: fh.write("%s\n" % line) return command_files def copy_ignore_errors(self, sourcefile, dest): "Ignore errors when trying to copy file - target exists" try: shutil.copy(sourcefile, dest) except Exception as e: if not re.search("Permission denied:", str(e)): self.logger.info("Error copying '%s': %s" % (sourcefile, e)) def get_instance_dirs(self): "Get a list of instance dirs valid on the current machine" dirlist = [] instance_names = sorted(self.instances.keys()) for instance_name in instance_names: instance = self.instances[instance_name] # Only create dirs when we are on the correct hostname if not instance.is_current_host(): self.logger.info("Ignoring directories on '%s' for instance '%s'" % (instance["sdp_hostname"], instance["sdp_instance"])) continue dirlist.extend([ instance.root_dir, instance.logdata_dir, instance.bin_dir, joinpath(instance.depotdata_dir, 'tmp'), joinpath(instance.depotdata_dir, 'depots'), joinpath(instance.depotdata_dir, 'checkpoints'), joinpath(instance.depotdata_dir, 'ssl'), instance.common_bin_dir, joinpath(instance.common_bin_dir, 'triggers'), joinpath(instance.metadata_dir, 'root', 'save'), joinpath(instance.metadata_dir, 'offline_db'), instance.logs_dir, instance.sdp_config_dir ]) return dirlist def checkdirs(self, dirlist): "Check that the directories exist" pass def mkdirs(self, dirlist): "Make all appropriate directories on this machine" if not self.options.yes: self.logger.info("The following directories would be created with the -y/--yes flag") for mdir in dirlist: self.logger.info(" %s" % mdir) return for dir in dirlist: if not os.path.exists(dir): self.logger.info("Creating dir '%s'" % dir) os.makedirs(dir) instance_names = self.instances.keys() curr_scriptdir = os.path.dirname(os.path.realpath(__file__)) for instance_name in instance_names: instance = self.instances[instance_name] # Only create dirs when we are on the correct hostname if not instance.is_current_host(): self.logger.info("Ignoring directories on '%s' for instance '%s'" % (instance["sdp_hostname"], instance["sdp_instance"])) continue self.logger.info("Copying files on '%s' for instance '%s'" % (instance["sdp_hostname"], instance["sdp_instance"])) for filename in glob.glob(os.path.join(instance.sdp_common_bin_dir, '*.*')): self.logger.info("Copying '%s' to '%s'" % (filename, instance.common_bin_dir)) self.copy_ignore_errors(filename, instance.common_bin_dir) for filename in glob.glob(os.path.join(instance.sdp_common_bin_dir, 'triggers', '*.*')): self.logger.info("Copying '%s' to '%s'" % (filename, os.path.join(instance.common_bin_dir, 'triggers'))) self.copy_ignore_errors(filename, os.path.join(instance.common_bin_dir, 'triggers')) for filename in ['daily_backup.bat', 'p4verify.bat', 'replica_status.bat', 'sync_replica.bat', 'weekly_backup.bat', 'weekly_sync_replica.bat']: self.logger.info("Creating instance bat file '%s'" % (os.path.join(instance.bin_dir, filename))) self.create_instance_bat(instance.bin_dir, filename, instance['sdp_instance'], instance.common_bin_dir) self.copy_ignore_errors(os.path.join(curr_scriptdir, 'p4.exe'), instance.bin_dir) self.copy_ignore_errors(os.path.join(curr_scriptdir, 'p4d.exe'), instance.bin_dir) self.copy_ignore_errors(os.path.join(curr_scriptdir, 'p4d.exe'), os.path.join(instance.bin_dir, 'p4s.exe')) shutil.copy(os.path.join(curr_scriptdir, 'sdp_config.ini'), instance.sdp_config_dir) def bat_file_hostname_guard_lines(self, hostname): lines = ['@echo off', 'FOR /F "usebackq" %%i IN (`hostname`) DO SET HOSTNAME=%%i', 'if /i "%s" NEQ "%s" (' % ('%HOSTNAME%', hostname), ' echo ERROR: This command file should only be run on machine with hostname "%s"' % (hostname), ' exit /b 1', ')', '@echo on'] return lines def get_service_install_cmds(self): "Configure any services on the current machine" cmds = {} instance_names = sorted(self.instances.keys()) for instance_name in instance_names: instance = self.instances[instance_name] hostname = instance['sdp_hostname'].lower() self.logger.info("Creating service configure commands on '%s' for instance '%s' in install_services_%s.bat" % ( hostname, instance["sdp_instance"], hostname)) # Install services if hostname not in cmds: cmds[hostname] = [] instsrv = joinpath(instance['depotdata_root'], 'p4', 'common', 'bin', 'instsrv.exe') cmd = '%s p4_%s "%s"' % (instsrv, instance['sdp_instance'], os.path.join(instance.bin_dir, 'p4s.exe')) cmds[hostname].append(cmd) p4cmd = os.path.join(instance.bin_dir, 'p4.exe') cmds[hostname].append('%s set -S p4_%s P4ROOT=%s' % (p4cmd, instance['sdp_instance'], instance.root_dir)) cmds[hostname].append('%s set -S p4_%s P4JOURNAL=%s' % (p4cmd, instance['sdp_instance'], os.path.join(instance.logs_dir, 'journal'))) cmds[hostname].append('%s set -S p4_%s P4NAME=%s' % (p4cmd, instance['sdp_instance'], instance['p4name'])) cmds[hostname].append('%s set -S p4_%s P4PORT=%s' % (p4cmd, instance['sdp_instance'], instance['sdp_p4port_number'])) log_path = joinpath(instance["logs_dir"], "%s.log" % instance["p4name"]) cmds[hostname].append('%s set -S p4_%s P4LOG=%s' % (p4cmd, instance['sdp_instance'], log_path)) return cmds def write_service_install_cmds(self, cmds): "Configure any services on the various machines" command_files = [] if not cmds: return command_files for instance_name in self.instances: instance = self.instances[instance_name] hostname = instance["sdp_hostname"].lower() if hostname in cmds: command_file = "install_services_%s.bat" % hostname if not command_file in command_files: command_files.append(command_file) with open(command_file, "w") as fh: # Write a safeguarding header for specific hostname lines = self.bat_file_hostname_guard_lines(hostname) lines.extend(cmds[hostname]) for line in lines: fh.write("%s\n" % line) return command_files def create_instance_bat(self, destdir, fname, instance, common_bin_dir): "Creates instance specific batch files which call common one" hdrlines = """::----------------------------------------------------------------------------- :: Copyright (c) 2012-2014 Perforce Software, Inc. Provided for use as defined in :: the Perforce Consulting Services Agreement. ::----------------------------------------------------------------------------- set ORIG_DIR=%CD% """.split("\n") with open(os.path.join(destdir, fname), "w") as fh: for line in hdrlines: fh.write("%s\n" % line.strip()) fh.write("\n") fh.write('cd /d "%s"\n' % common_bin_dir) fh.write('@call %s %s\n' % (fname, instance)) fh.write('cd /d %ORIG_DIR%\n') # Validate: # - master server on this host first - then any replica servers # - server exists (so serverid) # - server is of correct type # - adminpass file exists # - adminpass file is correct # - any replica users exist as service users # - replica users have long password timeout # - configure variables are as expected (warn if different - not always an error) # - if on replica then P4TICKETS variables def report_config(self): "Reports on whether the current configuration is valid - talks to master server" if not self.options.report: return errors = [] info = [] p4 = P4.P4() m_name = self.get_master_instance_name() m_instance = self.instances[m_name] if m_instance.is_current_host(): p4.port = m_instance.sdp_p4port_number p4.user = m_instance.sdp_p4superuser p4.connect() server_defined = False servers = p4.run_servers() m_svr = find_record(servers, 'ServerID', m_name) if not m_svr: errors.append("Error: no 'server' record defined for instance '%s' - use 'p4 server' to define" % m_name) elif m_svr['Services'] != 'standard': errors.append("Error: 'server' record for instance '%s' must have 'Services: standard'" % m_name) info.append("Server record exists for master instance '%s'" % m_name) users = p4.run_users("-a") for i_name in [inst for inst in self.instances.keys() if inst != m_name]: instance = self.instances[i_name] svr = find_record(servers, 'ServerID', i_name) if not svr: errors.append("Error: no 'server' record defined for instance '%s' - use 'p4 server' to define" % i_name) else: info.append("Server record exists for instance '%s'" % i_name) if svr['Services'] != instance.sdp_service_type: errors.append("Error: 'server' record for instance '%s' defines Services as '%s' - expected '%s'" % (i_name, svr['Services'],instance.sdp_service_type)) user = find_record(users, 'User', instance.p4name) if not user: errors.append("Error: no 'user' record defined for instance '%s' - use 'p4 user' to define a service user" % i_name) else: info.append("User record exists for instance '%s'" % i_name) if user['Type'] != 'service': errors.append("Error: 'user' record for instance '%s' defines Type as '%s' - expected '%s'" % (i_name, user['Type'], 'service')) p4.disconnect() print("\nThe following environment values were checked:") print("\n".join(info)) if errors: print("\nThe following ERRORS/WARNINGS encountered:") print("\n".join(errors)) else: print("\nNo ERRORS/WARNINGS found.") def process_config(self): "Process and produce the various files" self.isvalid_config() if self.options.yes and not running_as_administrator(): raise SDPException("This action must be run with Administrator rights") if self.options.report: self.report_config() return self.write_master_config_ini() dirs = self.get_instance_dirs() if self.options.yes: self.mkdirs(dirs) else: self.checkdirs(dirs) cmds = self.get_service_install_cmds() command_files = self.write_service_install_cmds(cmds) cmd_lines = self.get_configure_bat_contents() command_files.extend(self.write_configure_bat_contents(cmd_lines)) print("\n\n") if self.options.yes: print("Please run the following commands:") else: print("The following commands have been created - but you are in report mode so no directories have been created") for cmd in command_files: print(" %s" % cmd) print("You will also need to seed the replicas from a checkpoint and run the appropriate commands on those machines") def main(): "Initialization. Process command line argument and initialize logging." sdpconfig = SDPConfig() sdpconfig.process_config() if __name__ == '__main__': main()
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#2 | 10996 | Robert Cowham |
Remove P4 requirement for basic SDPEnv.py - moved to report_env.py instead. Remove (oudataed) copies from OS Setup for now - duplicates cause a problem |
||
#1 | 10638 | C. Thomas Tyler | Populate perforce_software-sdp-dev. | ||
//guest/perforce_software/sdp/main/Server/setup/SDPEnv.py | |||||
#1 | 10148 | C. Thomas Tyler | Promoted the Perforce Server Deployment Package to The Workshop. |