""" iTunes store connector. Provides query_store() function that performs the actual I/O. """ import subprocess from availability import config from availability import exceptions from availability.connectors import loggly logger = loggly.get_current_logger() def _sanitize_output(text): """Remove password from output text. Args: text (str): Text that may contain the password. Returns: str: Text with password redacted. """ if config.ITUNES_PASSWORD and config.ITUNES_PASSWORD in text: return text.replace(config.ITUNES_PASSWORD, '[REDACTED]') return text def query_store(itunes_vendor_id): """ Query iTunes with the iTMSTransporter utility. Note: multiple instances of this utility should not be called in parallel on the same machine. Args: itunes_vendor_id (str): Owner's unique product ID that was used to send the release to iTunes. Returns: str: Standard output of the iTMSTransporter utility, which we expect to be a valid XML. Raises: StoreRequestError: Error sending request to remote store. """ cmd = _get_itunes_command(itunes_vendor_id) # If we ever upgrade to Python 3.5+ – there is a simpler way with # subprocess.run(). process = subprocess.Popen( cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = [ out.decode() for out in process.communicate(timeout=config.ITUNES_TIMEOUT)] # Sanitize stderr to remove password before logging or raising exceptions sanitized_stderr = _sanitize_output(stderr) # iTMSTransporter always has something in stderr. logger.info( 'iTMSTransporter stderr', resources=dict(itunes_vendor_id=itunes_vendor_id, stderr=sanitized_stderr)) if process.returncode != 0: # Raise the exception we know how to handle on the upper level # preserving available details that will be saved in DB. raise exceptions.StoreRequestError( 'iTMSTransporter exit code: %d, stderr: %s' % ( process.returncode, sanitized_stderr)) return stdout def _get_itunes_command(itunes_vendor_id): """Create iTMSTransporter command for subprocess call. Args: itunes_vendor_id (str): Owner's unique product ID that was used to send the release to iTunes. Returns: list: command as it should be called by subprocess. """ if not isinstance(itunes_vendor_id, str): itunes_vendor_id = str(itunes_vendor_id) command = [ config.ITUNES_CMD_BIN, '-u', config.ITUNES_USER, '-p', config.ITUNES_PASSWORD, '-m', 'status', '-outputFormat', 'xml', '-vendor_id', itunes_vendor_id ] command.extend(config.ITUNES_CMD_EXTRA_ARGS) return command