def leaves(self, branch): """Get the last octets in oid that extend beyond branch. Args: branch: OID branch to check against Returns: leaves: The last octets of oid """ # Initialize key variables leaves = None oid = self.oid # Valid OID? if self.valid_format() is False: log_message = ('OID {} has incorrect format'.format(oid)) log.log2die(51443, log_message) # Valid branch? oid_methods = OIDstring(branch) if oid_methods.valid_format() is False: log_message = ('Branch {} has incorrect format'.format(branch)) log.log2die(51444, log_message) # Process OID and branch if branch in oid: if oid[:len(branch)] == branch: leaves = oid[len(branch):] # Return return leaves
def create(self): """Create a good config and set the PATTOO_CONFIGDIR variable. Args: None Returns: self.config_directory: Directory where the config is placed """ # Initialize key variables filenames = { '{}{}pattoo.yaml'.format(self._config_directory, os.sep): self._config, '{}{}pattoo_agent.yaml'.format(self._config_directory, os.sep): self._agent_config, '{}{}pattoo_server.yaml'.format(self._config_directory, os.sep): self._server_config } for filename, content in filenames.items(): # Write to pattoo.yaml try: f_handle = open(filename, 'w') except PermissionError: log.log2die( 1019, '''\ Insufficient permissions for creating the file: {}'''.format(filename)) else: with f_handle: yaml.dump(content, f_handle, default_flow_style=False) # Return return self._config_directory
def start(self): """Start the daemon. Args: None Returns: None """ # Check for a pidfile to see if the daemon already runs pid = _pid(self.pidfile) # Die if already running if bool(pid) is True: log_message = ( 'PID file: {} already exists. Daemon already running?' ''.format(self.pidfile)) log.log2die(1073, log_message) # Start the daemon self._daemonize() # Log success log_message = ('Daemon {} started - PID file: {}' ''.format(self.name, self.pidfile)) log.log2info(1070, log_message) # Run code for daemon self.run()
def _export(self, metadata): """Export pertinent information. Method to store the passphrase for future retrieval and name the file by the fingerprint of the class. The method also adds a wrapper to the name of the file to assist in searching for the public private key pair, it finds the pair it owns. Args: None Returns: None """ # Initialize variables directory = self._keys_directory filepath = os.path.abspath( os.path.join(directory, '{0}-{1}'.format(metadata.fingerprint, self.email))) # Export data try: fh_ = open(filepath, 'w') except PermissionError: log.log2die( 1095, '''\ Insufficient permissions for writing the file:{}'''.format(filepath)) except: log.log2die(1096, 'Error writing file:{}'.format(filepath)) else: with fh_: fh_.write(metadata.passphrase) # Allow RW file access to only the current user os.chmod(filepath, stat.S_IRUSR | stat.S_IWUSR)
def insert_row(row): """Create a User table entry. Args: row: DbRowUser object Returns: None """ # Verify values if bool(row) is False or isinstance(row, DbRowUser) is False: log_message = 'Invalid user type being inserted' log.log2die(20070, log_message) # Lowercase the name username = row.username.strip()[:MAX_KEYPAIR_LENGTH] password = row.password[:MAX_KEYPAIR_LENGTH] first_name = row.first_name.strip()[:MAX_KEYPAIR_LENGTH] last_name = row.last_name.strip()[:MAX_KEYPAIR_LENGTH] role = int(row.role) password_expired = int(row.password_expired) enabled = int(bool(row.enabled)) # Insert row = _User(username=username.encode(), password=crypt.crypt(password).encode(), first_name=first_name.encode(), last_name=last_name.encode(), role=role, password_expired=password_expired, enabled=enabled) with db.db_modify(20054, die=True) as session: session.add(row)
def insert_row(code, name=''): """Create a Language table entry. Args: code: Language code name: Language code name Returns: None """ # Verify values if bool(name) is False or isinstance(name, str) is False: name = 'Change me. Language name not provided.' if bool(code) is False or isinstance(code, str) is False: log_message = 'Language code "{}" is invalid'.format(code) log.log2die(20033, log_message) # Lowercase the code code = code.strip().lower()[:MAX_KEYPAIR_LENGTH] name = name.strip()[:MAX_KEYPAIR_LENGTH] # Insert row = Language(code=code.encode(), name=name.encode()) with db.db_modify(20032, die=True) as session: session.add(row)
def start(self): """Start the daemon. Args: None Returns: None """ # Check for a pidfile to see if the daemon already runs try: with open(self.pidfile, 'r') as pf_handle: pid = int(pf_handle.read().strip()) except IOError: pid = None if pid: log_message = ( 'PID file: {} already exists. Daemon already running?' ''.format(self.pidfile)) log.log2die(1062, log_message) # Start the daemon self._daemonize() # Log success log_message = ('Daemon {} started - PID file: {}' ''.format(self.name, self.pidfile)) log.log2info(1070, log_message) # Run code for daemon self.run()
def normalized_timestamp(_pi, timestamp=None): """Normalize timestamp to a multiple of 'polling_interval' seconds. Args: timestamp: epoch style timestamp in millseconds _pi: Polling interval for data in milliseconds. Defaults to assuming 1000 if 'None'. Returns: value: Normalized value """ # Initialize key variables if bool(_pi) is True: if isinstance(_pi, int) is False: log_message = ('''\ Invalid non-integer "polling_interval" value of {}'''.format(_pi)) log.log2die(1029, log_message) else: polling_interval = abs(_pi) else: # Don't allow 0 values for polling_interval polling_interval = 1000 # Process data if (timestamp is None) or (data.is_numeric(timestamp) is False): value = (int(time.time() * 1000) // polling_interval) * polling_interval else: value = (int(timestamp) // polling_interval) * polling_interval # Return return value
def create(self): """Create a good config and set the PATTOO_CONFIGDIR variable. Args: None Returns: self.config_directory: Directory where the config is placed """ # Initialize key variables agent_config = '{}{}pattoo_agent.yaml'.format( self._config_directory, os.sep) # Write good_config to file for key, config_ in sorted(self._config.items()): config_file = ( '{}{}{}.yaml'.format(self._config_directory, os.sep, key)) with open(config_file, 'w') as f_handle: yaml.dump(config_, f_handle, default_flow_style=False) # Write to pattoo_agent.yaml try: f_handle = open(agent_config, 'w') except PermissionError: log.log2die(50500, '''\ Insufficient permissions for creating the file:{}'''.format(f_handle)) else: with f_handle: yaml.dump(self._agent_config, f_handle, default_flow_style=False) # Return return self._config_directory
def update(_df): """Import data into the . Args: _df: Pandas DataFrame with the following headings ['language', 'key', 'translation'] Returns: None """ # Initialize key variables languages = {} headings_expected = [ 'language', 'key', 'translation'] headings_actual = [] valid = True count = 0 # Test columns for item in _df.columns: headings_actual.append(item) for item in headings_actual: if item not in headings_expected: valid = False if valid is False: log_message = ('''Imported data must have the following headings "{}"\ '''.format('", "'.join(headings_expected))) log.log2die(20082, log_message) # Process the DataFrame for _, row in _df.iterrows(): # Initialize variables at the top of the loop count += 1 code = row['language'].lower() key = str(row['key']) translation = str(row['translation']) # Store the idx_language value in a dictionary to improve speed idx_language = languages.get(code) if bool(idx_language) is False: idx_language = language.exists(code) if bool(idx_language) is True: languages[code] = idx_language else: log_message = ('''\ Language code "{}" on line {} of imported translation file not found during \ key-pair data importation. Please correct and try again. All other valid \ entries have been imported.\ '''.format(code, count)) log.log2see(20041, log_message) continue # Update the database if agent_xlate_exists(idx_language, key) is True: # Update the record update_row(key, translation, idx_language) else: # Insert a new record insert_row(key, translation, idx_language)
def _process_error(log_message, exception_error, check_reachability, check_existence, system_error=False): """Process the SNMP error. Args: params_dict: Dict of SNMP parameters to try Returns: alive: True if contactable """ # Initialize key varialbes _contactable = True exists = True if system_error is False: error_name = 'EasySNMPError' else: error_name = 'SystemError' # Check existence of OID if check_existence is True: if system_error is False: if isinstance( exception_error, easysnmp.exceptions.EasySNMPUnknownObjectIDError) is True: exists = False return (_contactable, exists) elif isinstance( exception_error, easysnmp.exceptions.EasySNMPNoSuchNameError) is True: exists = False return (_contactable, exists) elif isinstance( exception_error, easysnmp.exceptions.EasySNMPNoSuchObjectError) is True: exists = False return (_contactable, exists) elif isinstance( exception_error, easysnmp.exceptions.EasySNMPNoSuchInstanceError) is True: exists = False return (_contactable, exists) else: exists = False return (_contactable, exists) # Checking if the target is reachable if check_reachability is True: _contactable = False exists = False return (_contactable, exists) # Die an agonizing death! log_message = ('{}: {}'.format(error_name, log_message)) log.log2die(55569, log_message) return None
def pattoo_config(file_name, config_directory, config_dict): """Create configuration file. Args: file_name: Name of the configuration file without its file extension config_directory: Full path to the configuration directory config_dict: A dictionary containing the configuration values. Returns: The path to the configuration file """ # Initialize key variables config_file = os.path.join(config_directory, '{}.yaml'.format(file_name)) # Say what we are doing print('\nConfiguring {} file.\n'.format(config_file)) # Get configuration config = read_config(config_file, config_dict) # Check validity of directories, if any if bool(config) is False: # Set default config = config_dict # Iterate over config dict for _, value in sorted(config.items()): if isinstance(value, dict) is True: for secondary_key in value.keys(): if 'directory' in secondary_key: if os.sep not in value.get(secondary_key): log.log2die_safe( 1019, '{} is an invalid directory'.format(value)) # Attempt to create directory full_directory = os.path.expanduser( value.get(secondary_key)) if os.path.isdir(full_directory) is False: print('Creating: {}'.format(full_directory)) files.mkdir(full_directory) # Recursively set file ownership to pattoo user and group if getpass.getuser() == 'root': shared.chown(full_directory) # Write file try: f_handle = open(config_file, 'w') except PermissionError: log.log2die( 1076, '''\ Insufficient permissions for creating the file:{}'''.format(config_file)) else: with f_handle: yaml.dump(config, f_handle, default_flow_style=False) return config_file
def execute(command, die=True): """Run the command UNIX CLI command and record output. Args: command: CLI command to execute die: Die if errors found Returns: returncode: Return code of command execution """ # Initialize key variables messages = [] stdoutdata = ''.encode() stderrdata = ''.encode() returncode = 1 # Run update_targets script do_command_list = list(command.split(' ')) # Create the subprocess object try: process = subprocess.Popen(do_command_list, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdoutdata, stderrdata = process.communicate() returncode = process.returncode except: (etype, evalue, etraceback) = sys.exc_info() log_message = ('''\ Command failure: [Exception:{}, Exception Instance: {}, Stack Trace: {}]\ '''.format(etype, evalue, etraceback)) log.log2warning(1052, log_message) # Crash if the return code is not 0 if returncode != 0: # Print the Return Code header messages.append('Return code:{}'.format(returncode)) # Print the STDOUT for line in stdoutdata.decode().split('\n'): messages.append('STDOUT: {}'.format(line)) # Print the STDERR for line in stderrdata.decode().split('\n'): messages.append('STDERR: {}'.format(line)) # Log message for log_message in messages: log.log2warning(1042, log_message) # Die if required after error found if bool(die) is True: log.log2die(1044, 'Command Failed: {}'.format(command)) # Return return returncode
def _get(query): """Get pattoo API server GraphQL query results. Args: query: GraphQL query string Returns: result: Dict of JSON response """ # Initialize key variables success = False config = Config() result = None # Get the data from the GraphQL API url = config.web_api_server_url() try: response = requests.get(url, params={'query': query}) # Trigger HTTP errors if present response.raise_for_status() success = True except requests.exceptions.Timeout as err: # Maybe set up for a retry, or continue in a retry loop log_message = ('''\ Timeout when attempting to access {}. Message: {}\ '''.format(url, err)) log.log2die(20121, log_message) except requests.exceptions.TooManyRedirects as err: # Tell the user their URL was bad and try a different one log_message = ('''\ Too many redirects when attempting to access {}. Message: {}\ '''.format(url, err)) log.log2die(20116, log_message) except requests.exceptions.HTTPError as err: log_message = ('''\ HTTP error when attempting to access {}. Message: {}\ '''.format(url, err)) log.log2die(20118, log_message) except requests.exceptions.RequestException as err: # catastrophic error. bail. log_message = ('''\ Exception when attempting to access {}. Message: {}\ '''.format(url, err)) log.log2die(20119, log_message) except: log_message = ('''API Failure: [{}, {}, {}]\ '''.format(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2])) log.log2die(20120, log_message) # Process the data if bool(success) is True: result = response.json() return result
def stop(self): """Stop the daemon. Args: None Returns: None """ # Get the pid from the pidfile try: with open(self.pidfile, 'r') as pf_handle: pid = int(pf_handle.read().strip()) except IOError: pid = None if not pid: log_message = ('PID file: {} does not exist. Daemon not running?' ''.format(self.pidfile)) log.log2warning(1063, log_message) # Not an error in a restart return # Try killing the daemon process try: while 1: if self.lockfile is None: os.kill(pid, signal.SIGTERM) else: time.sleep(0.3) if os.path.exists(self.lockfile) is True: continue else: os.kill(pid, signal.SIGTERM) time.sleep(0.3) except OSError as err: error = str(err.args) if error.find("No such process") > 0: self.delpid() self.dellock() else: log_message = (str(err.args)) log_message = ('{} - PID file: {}'.format( log_message, self.pidfile)) log.log2die(1068, log_message) except: log_message = ('Unknown daemon "stop" error for PID file: {}' ''.format(self.pidfile)) log.log2die(1066, log_message) # Log success self.delpid() self.dellock() log_message = ('Daemon {} stopped - PID file: {}' ''.format(self.name, self.pidfile)) log.log2info(1071, log_message)
def _import(self): """Import private key information. Method to import the fingerprint and passphrase of the owned public / private key pair of the user. The method also looks for a wrapper on the file to distinguish the public private key pair the user currently owns. Args: None Returns: result: Tuple of (fingerprint, passphrase), None if nonexistent """ # Initialize key variables result = None directory = self._keys_directory # Get list of key files whose names end with the wrapper key = [ _file for _file in os.listdir(directory) if _file.endswith(self.email) ] # Process data count = len(key) if count > 1: # Die if we have duplicate keys found in the directory log_message = '''\ More than one agent_id "{}" key-pair stores found'''.format(self.email) log.log2die(1062, log_message) elif count == 1: # Get passphrase filename = key[0] filepath = os.path.abspath(os.path.join(directory, filename)) try: fh_ = open(filepath, 'r') except PermissionError: log.log2die( 1091, '''\ Insufficient permissions for reading the file:{}'''.format(filepath)) except: log.log2die(1093, 'Error reading file:{}'.format(filepath)) else: with fh_: passphrase = fh_.read() # Basic validation if isinstance(passphrase, str) is False: log.log2die(1094, 'Corrupted keyfile:{}'.format(filepath)) # Retrieve fingerprint from the filename fingerprint = filename.split('-')[0] result = _METADATA(fingerprint=fingerprint, passphrase=passphrase) return result
def _process_language(args): """Process language cli arguments. Args: args: CLI argparse parser arguments Returns: None """ # Initialize key variables if bool(language.exists(args.code)) is True: log_message = 'Language code "{}" already exists.'.format(args.code) log.log2die(20044, log_message) else: language.insert_row(args.code, args.name)
def __init__(self, username): """Initialize the class. Args: username: Name of user Returns: None """ # Initialize key variables if bool(exists(username)) is True: self._username = username else: log_message = 'Username "{}" does not exist.'.format(username) log.log2die(20155, log_message)
def _process_language(args): """Process language cli arguments. Args: args: CLI argparse parser arguments Returns: None """ # Initialize key variables if bool(language.exists(args.code)) is True: language.update_name(args.code, args.name) else: log_message = 'Language code "{}" not found.'.format(args.code) log.log2die(20005, log_message)
def query(self): """Query all remote targets for data. Args: None Returns: None """ # Initialize key variables ttl = 3 config = Config() interval = config.polling_interval() agent_ip_address = config.agent_ip_address() # Start BACnet daemon ip_with_subnet_mask = '{}/32'.format(agent_ip_address) try: bacnet = BAC0.connect(ip=ip_with_subnet_mask, bbmdTTL=ttl) except: log_message = ('''\ Cannot start BACnet daemon on IP address {}. Please check configuration or \ other daemons that could be using BACnet'''.format(agent_ip_address)) log.log2die(60010, log_message) # Post data to the remote server while True: # Get start time ts_start = time() # Get system data agentdata = collector.poll(bacnet) # Post to remote server server = PostAgent(agentdata) # Post data success = server.post() # Purge cache if success is True if success is True: server.purge() # Sleep duration = time() - ts_start sleep(abs(interval - duration))
def ip_targets(self): """Get targets. Args: None Returns: result: result """ # Get result key = 'ip_targets' result = self._agent_config.get(key, None) if result is None: log_message = '"{}" not found in configuration file'.format(key) log.log2die(50001, log_message) return result
def _process_pair_xlate_group(args): """Process pair_xlate_group cli arguments. Args: args: CLI argparse parser arguments Returns: None """ # Initialize key variables if bool(pair_xlate_group.exists(args.name)) is True: log_message = ('''\ Agent group name "{}" already exists.'''.format(args.name)) log.log2die(20057, log_message) else: pair_xlate_group.insert_row(args.name)
def __init__(self, oid): """Initialize the class. Args: oid: OID to process Returns: None """ # Initialize key variables self.oid = str(oid) # OID must be string if data.is_numeric(self.oid): log_message = ('OID value {} is not a string.'.format(self.oid)) log.log2die(51380, log_message)
def stop(self): """Stop the daemon. Args: None Returns: None """ # Check for a pidfile to see if the daemon already runs pid = _pid(self.pidfile) if bool(pid) is False: log_message = ('PID file: {} does not exist. Daemon not running?' ''.format(self.pidfile)) log.log2warning(1063, log_message) # Not an error in a restart return # Try killing the daemon process try: os.kill(pid, signal.SIGTERM) except OSError as err: error = str(err.args) if error.find('No such process') > 0: self.delpid() self.dellock() else: log_message = (str(err.args)) log_message = ('{} - PID file: {}'.format( log_message, self.pidfile)) log.log2die(1068, log_message) except: log_message = ('Unknown daemon "stopped" error for PID file: {}' ''.format(self.pidfile)) log.log2die(1066, log_message) # Log success self.delpid() self.dellock() log_message = ('Daemon {} stopped - PID file: {}' ''.format(self.name, self.pidfile)) log.log2info(1071, log_message)
def db_modify(error_code, die=True, close=True): """Provide a transactional scope around Update / Insert operations. From https://docs.sqlalchemy.org/en/13/orm/session_basics.html Args: error_code: Error code to use in messages die: Die if True close: Close session if True. GraphQL mutations sometimes require the session to remain open. Returns: None """ # Initialize key variables prefix = 'Unable to modify database.' # Create session from pool session = POOL() # Setup basic functions try: yield session session.commit() except Exception as exception_error: session.rollback() log_message = '{}. Error: "{}"'.format(prefix, exception_error) if bool(die) is True: log.log2die(error_code, log_message) else: log.log2info(error_code, log_message) except: session.rollback() log_message = '{}. Unknown error'.format(prefix) if bool(die) is True: log.log2die(error_code, log_message) else: log.log2info(error_code, log_message) finally: # Return the Connection to the pool if bool(close) is True: session.close()
def _mysql(): """Create database tables. Args: None Returns: None """ # Initialize key variables config = Config() pool_size = config.db_pool_size() max_overflow = config.db_max_overflow() # Add MySQL to the pool engine = create_engine(URL, echo=True, encoding='utf8', max_overflow=max_overflow, pool_size=pool_size, pool_recycle=3600) # Try to create the database print('??: Attempting to Connect to configured database.') try: sql_string = ('''\ ALTER DATABASE {} CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci\ '''.format(config.db_name())) engine.execute(sql_string) except: log_message = ('''\ ERROR: Cannot connect to database "{}" on server "{}". Verify database server \ is started. Verify database is created. Verify that the configured database \ authentication is correct.'''.format(config.db_name(), config.db_hostname())) log.log2die(20086, log_message) # Apply schemas print('OK: Database connected.') print('??: Attempting to create database tables.') BASE.metadata.create_all(engine) print('OK: Database tables created.')
def agent_ip_address(self): """Get list polling target information in configuration file. Args: None Returns: result: IP address """ # Initialize key variables result = [] # Get configuration snippet key = 'agent_ip_address' result = self._agent_config.get(key) if result is None: log_message = '"{}" not found in configuration file'.format(key) log.log2die(60002, log_message) return result
def _process_agent(args): """Process agent cli arguments. Args: args: CLI arguments Returns: None """ # Validate parameters if bool(agent_group.idx_exists(args.idx_agent_group)) is False: log_message = ('idx_agent_group "{}" not found.'.format( args.idx_agent_group)) log.log2die(20068, log_message) if bool(agent.idx_exists(args.idx_agent)) is False: log_message = ('idx_agent "{}" not found.'.format(args.idx_agent)) log.log2die(20060, log_message) # Assign agent.assign(args.idx_agent, args.idx_agent_group)
def node_x(self): """Get the third to last node of OID. Args: None Returns: Last node """ # Initialize key variables oid = self.oid # Valid OID? if self.valid_format() is False: log_message = ('OID {} has incorrect format'.format(oid)) log.log2die(51447, log_message) # Process data nodes = oid.split('.') return int(nodes[-3])
def _process_pair_xlate_group(args): """Process pair_xlate_group cli arguments. Args: args: CLI argparse parser arguments Returns: None """ # Initialize key variables exists = pair_xlate_group.idx_exists(args.idx_pair_xlate_group) if bool(exists) is True: # Check for duplicates duplicate = pair_xlate_group.exists(args.name) if bool(duplicate) is True: log_message = ('''\ Translation group "{}" already exists.'''.format(args.name)) log.log2die(20075, log_message) # Update if args.idx_pair_xlate_group != 1: pair_xlate_group.update_name(args.idx_pair_xlate_group, args.name) else: log_message = 'Cannot change Translation group "1".' log.log2die(20072, log_message) else: log_message = ('''\ Translation group "{}" not found.'''.format(args.idx_pair_xlate_group)) log.log2die(20074, log_message)