def main(): """Main""" # Parse commandline arguments parser = FinfoArgumentParser(config_file=resilient.get_config_file()) opts = parser.parse_args() # Connect to Resilient client = resilient.get_client(opts) # If no field is specified, list them all if not opts.fieldname: if opts.types: list_types(client) elif opts.field_values: list_fields_values(client, opts.field_type) elif opts.csv: list_fields_csv(client, opts.field_type) else: list_fields(client, opts.field_type) exit(0) # Find the field and display its properties field_data = find_field(client, opts.fieldname, opts.field_type) if field_data: if opts.json: print_json(field_data) else: print_details(field_data) exit(0) else: print(u"Field '{}' was not found.".format(opts.fieldname)) exit(1)
def xtest_with_env_variable(self, monkeypatch, tmp_config_file): """ Test with environment variable $APP_CONFIG_FILE. """ monkeypatch.setenv("APP_CONFIG_FILE", str(tmp_config_file)) result = resilient.get_config_file() assert result == tmp_config_file
def test_with_filename_exists_local(self, mock_os_path_exists): """ Test with filename specified and not exists in home path. """ test_file_name = "test_file" result = resilient.get_config_file(filename=test_file_name) assert result == test_file_name
def __init__(self): super(KeyringUtils, self).__init__() config_file = resilient.get_config_file() print(u"Configuration file: {}".format(config_file)) # Read configuration options. if config_file: config_path = resilient.ensure_unicode(config_file) config_path = os.path.expanduser(config_path) if os.path.exists(config_path): try: self.config = configparser.ConfigParser(interpolation=None) with open(config_path, 'r', encoding='utf-8') as f: first_byte = f.read(1) if first_byte != u'\ufeff': # Not a BOM, no need to skip first byte f.seek(0) self.config.read_file(f) except Exception as exc: logger.warn(u"Couldn't read config file '%s': %s", config_path, exc) self.config = None else: logger.warn(u"Couldn't read config file '%s'", config_file) else: logger.warn(u"Couldn't read config file")
def test_generate_with_env_variable(self, monkeypatch, tmp_config_file): """ Test generate with environment variable $APP_CONFIG_FILE. """ monkeypatch.setenv("APP_CONFIG_FILE", str(tmp_config_file)) result = get_config_file(generate_filename=True) assert result == tmp_config_file
def main(): parser = ExampleArgumentParser(config_file=resilient.get_config_file()) opts = parser.parse_args() # Create SimpleClient for a REST connection to the Resilient services client = resilient.get_client(opts) if opts["create"]: create_incident(client, opts["create"], opts["attach"]) if opts["list"]: show_incident_list(client, opts["query"]) if opts["get"]: generic_get(client, opts["get"]) if opts["post"]: generic_post(client, opts["post"][0], opts["post"][1]) if opts["update"]: generic_update(client, opts["update"][0], opts["update"][1]) if opts["patch"]: generic_patch(client, opts["patch"][0], opts["patch"][1]) if opts["delete"]: generic_delete(client, opts["delete"]) if opts["search"]: generic_search(client, opts["search"])
def generate_code(args): """generate template code components from functions""" parser = AppArgumentParser(config_file=resilient.get_config_file()) (opts, extra) = parser.parse_known_args() client = resilient.get_client(opts) if args.cmd == "extract" and args.output: extract_to_res(client, args.exportfile, args.messagedestination, args.function, args.workflow, args.rule, args.field, args.datatable, args.task, args.script, args.artifacttype, args.output, args.zip) elif args.reload: codegen_reload_package(client, args) elif args.package: # codegen an installable package output_base = os.path.join(os.curdir, args.package) codegen_package(client, args.exportfile, args.package, args.messagedestination, args.function, args.workflow, args.rule, args.field, args.datatable, args.task, args.script, args.artifacttype, os.path.expanduser(output_base)) elif args.function: # codegen a component for one or more functions if len(args.function) > 1: default_name = "functions.py" else: default_name = "{}.py".format(args.function[0]) output_dir = os.path.expanduser(opts["componentsdir"] or os.curdir) output_file = args.output or default_name if not output_file.endswith(".py"): output_file = output_file + ".py" codegen_functions(client, args.exportfile, args.function, args.workflow, args.rule, args.artifacttype, output_dir, output_file)
def selftest(args): """loop through every selftest for every eligible package, call and store returned state, print out package and their selftest states""" components = defaultdict(list) # custom entry_point only for selftest functions selftest_entry_points = [ ep for ep in pkg_resources.iter_entry_points( 'resilient.circuits.selftest') ] for ep in selftest_entry_points: components[ep.dist].append(ep) if len(selftest_entry_points) == 0: LOG.info("No selftest entry points found.") return None # Generate opts array necessary for ResilientComponent instantiation opts = AppArgumentParser( config_file=resilient.get_config_file()).parse_args("", None) # make a copy install_list = list(args.install_list) if args.install_list else [] for dist, component_list in components.items(): if args.install_list is None or dist.project_name in install_list: # remove name from list if dist.project_name in install_list: install_list.remove(dist.project_name) # add an entry for the package LOG.info("%s: ", dist.project_name) for ep in component_list: # load the entry point f_selftest = ep.load() try: # f_selftest is the selftest function, we pass the selftest resilient options in case it wants to use it start_time_milliseconds = int(round(time.time() * 1000)) status = f_selftest(opts) end_time_milliseconds = int(round(time.time() * 1000)) delta_milliseconds = end_time_milliseconds - start_time_milliseconds delta_seconds = delta_milliseconds / 1000 if status["state"] is not None: LOG.info("\t%s: %s, Elapsed time: %f seconds", ep.name, status["state"], delta_seconds) except Exception as e: LOG.error("Error while calling %s. Exception: %s", ep.name, str(e)) continue # any missed packages? if len(install_list): LOG.warning("%s not found. Check package name(s)", install_list)
def test_generate_with_filename(self): """ Test generate with filename specified. """ test_file_name = "test_file" result = get_config_file(filename=test_file_name, generate_filename=True) assert result == test_file_name
def __init__(self, auto_load_components=True, config_file=None): super(App, self).__init__() # Read the configuration options self.action_component = None self.component_loader = None self.auto_load_components = auto_load_components self.config_file = config_file or resilient.get_config_file() self.do_initialization()
def main(): """ program main """ config_file = resilient.get_config_file() parser = ExampleArgumentParser(config_file) opts = parser.parse_args() inc_types = opts["itype"] inc_queue = opts["queue"] # Create SimpleClient for a REST connection to the Resilient services client = resilient.get_client(opts) # Discovered Date will be set to the current time time_now = int(time.time() * 1000) try: uri = '/incidents' rf_config = configparser.ConfigParser() rf_config.read(config_file) rf_opts = dict(rf_config.items('fn_risk_fabric')) result = get_action_plans(rf_opts) for ap in result: if 'AssignToQueueName' in ap and ap[ 'AssignToQueueName'] == inc_queue: # Construct the basic incident DTO that will be posted description = ap['Notes'] if 'Notes' in ap else "" ActionPlanGUID = ap['ActionPlanGUID'] properties = {"rf_actionplanguid": ActionPlanGUID} new_incident = { "name": ap['Title'], "description": description, "incident_type_ids": inc_types, "properties": properties, "discovered_date": time_now } # Create the incident incident = client.post(uri, new_incident) inc_id = incident["id"] params = { 'ActionPlanGUID': ActionPlanGUID, 'Comment': "Created Resilient Incident ID #" + str(inc_id) } result = set_action_plan_comment(rf_opts, params) print("Created incident {}".format(inc_id)) except resilient.SimpleHTTPException as ecode: print("create failed : {}".format(ecode))
def test_with_filename_not_exists_local(self, mock_os_path_not_exists): """ Test with filename specified and exists in home path. """ test_file_name = "test_file" test_file_abs = os.path.expanduser( os.path.join("~", ".resilient", test_file_name)) result = resilient.get_config_file(filename=test_file_name) assert result == test_file_abs
def main(): """Main""" parser = ExportArgumentParser(config_file=resilient.get_config_file()) opts = parser.parse_args() # Export the data export_context = ExportContext(opts) export_context.export_json() print("Done.")
def customize_resilient(args): """import customizations to the resilient server""" parser = AppArgumentParser(config_file=resilient.get_config_file()) (opts, extra) = parser.parse_known_args() client = resilient.get_client(opts) # Call each of the 'customize' entry points to get type definitions, # then apply them to the resilient server entry_points = pkg_resources.iter_entry_points('resilient.circuits.customize') do_customize_resilient(client, entry_points, args.yflag, args.install_list)
def main(): """Main""" # Parse the commandline arguments and config file config = resilient.get_config_file() print("Configuration file: {}".format(config)) parser = resilient.ArgumentParser(config_file=config) opts = parser.parse_args() # Create SimpleClient for a REST connection to the Resilient services resilient_client = resilient.get_client(opts) # Report the list of users and groups report_users_and_groups(resilient_client)
def main(): """main""" # Parse the commandline arguments and config file config = resilient.get_config_file() print("Configuration file: {}".format(config)) parser = ReportArgumentParser(config_file=config) opts = parser.parse_args() # Create SimpleClient for a REST connection to the Resilient services resilient_client = resilient.get_client(opts) # Do the reports phases_report(opts, resilient_client)
def __init__(self, auto_load_components=True, config_file=None, ALLOW_UNRECOGNIZED=False, IS_SELFTEST=False): super(App, self).__init__() # Read the configuration options self.ALLOW_UNRECOGNIZED = ALLOW_UNRECOGNIZED resilient_constants.ALLOW_UNRECOGNIZED = ALLOW_UNRECOGNIZED self.IS_SELFTEST = IS_SELFTEST self.action_component = None self.component_loader = None self.auto_load_components = auto_load_components self.config_file = config_file or get_config_file() self.do_initialization()
def main(): """ program main """ config_file = resilient.get_config_file() parser = ExampleArgumentParser(config_file) opts = parser.parse_args() inc_types = opts["itype"] inc_limit = opts["limit"] # Create SimpleClient for a REST connection to the Resilient services client = resilient.get_client(opts) # Discovered Date will be set to the current time time_now = int(time.time() * 1000) try: uri = '/incidents' rf_config = configparser.ConfigParser() rf_config.read(config_file) rf_opts = dict(rf_config.items('fn_risk_fabric')) params = {'Limit': inc_limit} result = get_risk_model_instances(rf_opts, params) for ap in result['Records']: # Construct the basic incident DTO that will be posted inc_name = ap['RiskModelName'] inc_description = ap['Threats'] + ', ' + ap[ 'FocusEntityCaption'] + ', #' + str(ap['ID']) new_incident = { "name": inc_name, "description": inc_description, "incident_type_ids": inc_types, "discovered_date": time_now } # Create the incident incident = client.post(uri, new_incident) inc_id = incident["id"] print("Created incident {}".format(inc_id)) except resilient.SimpleHTTPException as ecode: print("create failed : {}".format(ecode))
def main(): """ program main """ parser = ExampleArgumentParser(config_file=resilient.get_config_file()) opts = parser.parse_args() inc_name = opts["name"] inc_desc = opts["description"] inc_types = opts["itype"] # Create SimpleClient for a REST connection to the Resilient services client = resilient.get_client(opts) # Discovered Date will be set to the current time time_now = int(time.time() * 1000) # Construct the basic incident DTO that will be posted new_incident = { "name": inc_name, "description": inc_desc, "incident_type_ids": inc_types, "discovered_date": time_now, "properties": {} } # Add the specified values for any custom fields, # per the command-line arguments provided. # Within the incident JSON structure, the values for custom fields # are all contained within a dictionary value named 'properties'. for custom in opts["custom"]: (field_name, field_value) = custom.split("=", 1) print("{} = {}".format(field_name, field_value)) new_incident["properties"][field_name] = field_value try: uri = '/incidents' # Create the incident incident = client.post(uri, new_incident) inc_id = incident["id"] print("Created incident {}".format(inc_id)) except resilient.SimpleHTTPException as ecode: print("create failed : {}".format(ecode))
def check_connect(): """ Use openssl and python requests to check the connection with Resilient :return: """ arg_parser = resilient.ArgumentParser(resilient.get_config_file()) host = arg_parser.getopt("resilient", "host") # # Use Openssl first # print("-------------------------------------") print("Using openssl to connect to resilient") print("-------------------------------------") command = "openssl s_client -connect {}:443".format(host) user = arg_parser.getopt("resilient", "email") password = arg_parser.getopt("resilient", "password") process = subprocess.Popen("/bin/bash", stdin=subprocess.PIPE, stdout=subprocess.PIPE) out, err = process.communicate(command) cafile = arg_parser.getopt("resilient", "cafile") verify = True if cafile is not None and cafile == "false": verify = False print(out) if err is not None: print(err) print("---------------------------------------------") print("Using python requests to connect to resilient") print("---------------------------------------------") rest_url = "https://{}:443/rest/session".format(host) data = '{"email": "' + user + '","password":"******", "interactive": true}' try: header = {"Content-Type": "application/json"} resp = requests.post(rest_url, data=data, headers=header, verify=verify) print("\tResponse: " + str(resp)) except Exception as e: print("\tConnection failed!!") print("\t" + str(e))
def create_authenticated_client(): """create_authenticated_client uses the resilient package to gather values from a standard app.config file; the configuration file used for an Integration Server or App Host App. This means all credentials needed to run this module can be kept separate and we can also avoid var prompts. Note: If your running this module on a host other than localhost, that host needs to have an app.config file or you need to copy one over. :return: An authenticated rest client to CP4S or Resilient :rtype: SimpleClient """ import resilient # Create Resilient API Client resilient_parser = resilient.ArgumentParser( config_file=resilient.get_config_file()) resilient_opts = resilient_parser.parse_known_args() # Instantiate a client using the gathered opts return resilient.get_client(resilient_opts[0])
def get_resilient_client(path_config_file=None): """ Return a SimpleClient for Resilient REST API using configurations options from provided path_config_file or from ~/.resilient/app.config :param path_config_file: Path to app.config file to use :return: SimpleClient for Resilient REST API :rtype: SimpleClient """ LOG.info("Connecting to Resilient Appliance...") if not path_config_file: path_config_file = get_config_file() config_parser = ArgumentParser(config_file=path_config_file) opts = config_parser.parse_known_args()[0] return get_client(opts)
def main(): parser = ExportArgumentParser(config_file=resilient.get_config_file()) opts = parser.parse_args() first_json = get_json_from_file(opts.get("first_json_file")) second_json = get_json_from_file(opts.get("second_json_file")) if first_json is None or second_json is None: raise Exception("Invalid file provided.") if first_json.get("incidents") is None or second_json.get( "incidents") is None: raise Exception("Invalid JSON provided, incidents object not found.") first_incidents = first_json.get("incidents") second_incidents_array = second_json.get("incidents") second_incidents = [] for incident in second_incidents_array: incident_id = incident.get("id") if incident_id is not None: second_incidents.append(incident_id) incidents = [] for incident in first_incidents: incident_id = incident.get("id") # if valid incident if incident_id is None: continue # if the incident already exists, we don't want to add it if incident_id not in second_incidents: incidents.append(incident) incidents += second_incidents_array with open(opts.get("output_json_file"), "w") as outfile: json.dump({"incidents": incidents}, outfile) outfile.write("\n") print("Successfully merged JSON files into {}.".format( opts.get("output_json_file")))
def main(): """ program main """ parser = ExampleArgumentParser(config_file=resilient.get_config_file()) opts = parser.parse_args() # Create SimpleClient for a REST connection to the Resilient services client = resilient.get_client(opts) inc_id = opts["incid"] desc = opts["desc"] try: uri = '/incidents/{}'.format(inc_id) incident = client.get(uri) # Create a patch object. You need to pass it the base object (the thing being patched). This # object contains the old values, which are sent to the server. patch = resilient.Patch(incident) patch.add_value("description", desc) print(''' At this point, we have a copy of the specified incident. If you want to trigger a conflict to see what will happen, then you can do so now. Press the Enter key to continue''') input() # Apply the patch and overwrite any conflicts. client.patch(uri, patch, overwrite_conflict=True) # Confirm that our change was applied. This is not something that you'd typically need to do since the # patch applied successfully, but this illustrates that the change was applied for the purposes of this # example. assert desc == client.get(uri)["description"] except resilient.SimpleHTTPException as ecode: print("patch failed : {}".format(ecode))
def connect(self): print("----------------") print("Ready to connect") print("----------------") print("Read config information from app.confg ...") arg_parser = resilient.ArgumentParser( resilient.get_config_file()).parse_args(args=self.other_args) host = arg_parser.host email = arg_parser.email password = arg_parser.password org = arg_parser.org api_key_id = arg_parser.api_key_id api_key_secret = arg_parser.api_key_secret cafile = arg_parser.cafile verify = True if cafile is not None and cafile == "false": verify = False url = "https://{}:443".format(host) print("Connecting to {} using user:{}, and org:{}".format( url, email, org)) print("Validate cert: {}".format(verify)) args = {"base_url": url, "verify": verify, "org_name": org} self.res_client = resilient.SimpleClient(**args) if email is not None and password is not None: session = self.res_client.connect(email, password) if session is not None: user_name = session.get("user_displayname", "Not found") print("User display name is : {}".format(user_name)) print("Done") else: self.res_client.set_api_key(api_key_id=api_key_id, api_key_secret=api_key_secret)
def get_configs(path_config_file=None, ALLOW_UNRECOGNIZED=False): """ Gets all the configs that are defined in the app.config file Uses the path to the config file from the parameter Or uses the `get_config_file()` method in resilient if None :param path_config_file: path to the app.config to parse :type path_config_file: str :param ALLOW_UNRECOGNIZED: bool to specify if AppArgumentParser will allow unknown comandline args or not. Default is False :type ALLOW_UNRECOGNIZED: bool :return: dictionary of all the configs in the app.config file :rtype: dict """ from resilient import get_config_file from resilient_circuits.app_argument_parser import AppArgumentParser if not path_config_file: path_config_file = get_config_file() configs = AppArgumentParser(config_file=path_config_file).parse_args( ALLOW_UNRECOGNIZED=ALLOW_UNRECOGNIZED) return configs
def main(): """ program main """ parser = ExampleArgumentParser(config_file=resilient.get_config_file()) opts = parser.parse_args() inc_name = opts["name"] inc_desc = opts["description"] inc_types = opts["itype"] # Create SimpleClient for a REST connection to the Resilient services client = resilient.get_client(opts) # Discovered Date will be set to the current time time_now = int(time.time() * 1000) # Construct the basic incident DTO that will be posted new_incident = { "name": inc_name, "description": inc_desc, "incident_type_ids": inc_types, "discovered_date": time_now } try: uri = '/incidents' # Create the incident incident = client.post(uri, new_incident) inc_id = incident["id"] print("Created incident {}".format(inc_id)) except resilient.SimpleHTTPException as ecode: print("create failed : {}".format(ecode))
def __init__(self, config_file=None): self.config_file = config_file or resilient.get_config_file() super(OptParser, self).__init__(config_file=self.config_file) # # Note this is a trick used by resilient-circuits. resilient.ArgumentParser will # validate the arguments of the command line. Since we use command line # argument of input/output files, we don't want that validation, so we # erase them before we call parse_args(). So parse_args() only # reads from app.config # sys.argv = sys.argv[0:1] self.opts = self.parse_args() if self.config: for section in self.config.sections(): # # Handle sections other than [resilient] in app.config # items = dict((item.lower(), self.config.get(section, item)) for item in self.config.options(section)) self.opts.update({section: items}) resilient.parse_parameters(self.opts)
def do_function(self, arg): """Execute a function""" if not arg: print("function command requires a function-name") return parser = AppArgumentParser(config_file=resilient.get_config_file()) (opts, more) = parser.parse_known_args() client = resilient.get_client(opts) args = iter(shlex.split(arg)) try: function_name = next(args) function_def = client.get("/functions/{}?handle_format=names".format(function_name)) param_defs = dict({fld["uuid"]: fld for fld in client.get("/types/__function/fields?handle_format=names")}) function_params = {} for param in function_def["view_items"]: param_uuid = param["content"] param_def = param_defs[param_uuid] prompt = "{} ({}, {}): ".format(param_def["name"], param_def["input_type"], param_def["tooltip"]) try: arg = next(args) except StopIteration: arg = None function_params[param_def["name"]] = get_input(param_def["input_type"], prompt, arg) action_message = { "function": { "name": function_name }, "inputs": function_params } message = json.dumps(action_message, indent=2) print(message) self._submit_action("function", message) except Exception as e: print(e)
def __init__(self, config_file=None): # Temporary logging handler until the real one is created later temp_handler = logging.StreamHandler() temp_handler.setFormatter( logging.Formatter( '%(asctime)s %(levelname)s [%(module)s] %(message)s')) temp_handler.setLevel(logging.INFO) logging.getLogger().addHandler(temp_handler) config_file = config_file or resilient.get_config_file() super(AppArgumentParser, self).__init__(config_file=config_file) default_components_dir = self.getopt( self.DEFAULT_APP_SECTION, "componentsdir") or self.DEFAULT_COMPONENTS_DIR default_noload = self.getopt(self.DEFAULT_APP_SECTION, "noload") or "" default_log_dir = self.getopt(self.DEFAULT_APP_SECTION, "logdir") or APP_LOG_DIR default_log_level = self.getopt(self.DEFAULT_APP_SECTION, "loglevel") or self.DEFAULT_LOG_LEVEL default_log_file = self.getopt(self.DEFAULT_APP_SECTION, "logfile") or self.DEFAULT_LOG_FILE # STOMP port is usually 65001 default_stomp_port = self.getopt( self.DEFAULT_APP_SECTION, "stomp_port") or self.DEFAULT_STOMP_PORT # For some environments the STOMP TLS certificate will be different from the REST API cert default_stomp_cafile = self.getopt(self.DEFAULT_APP_SECTION, "stomp_cafile") or None default_stomp_url = self.getopt(self.DEFAULT_APP_SECTION, "stomp_host") or self.getopt( self.DEFAULT_APP_SECTION, "host") default_stomp_timeout = self.getopt( self.DEFAULT_APP_SECTION, "stomp_timeout") or self.DEFAULT_STOMP_TIMEOUT default_stomp_max_retries = self.getopt( self.DEFAULT_APP_SECTION, "stomp_max_retries") or self.DEFAULT_STOMP_MAX_RETRIES default_max_connection_retries = self.getopt( self.DEFAULT_APP_SECTION, "max_connection_retries") or self.DEFAULT_MAX_CONNECTION_RETRIES default_no_prompt_password = self.getopt( self.DEFAULT_APP_SECTION, "no_prompt_password") or self.DEFAULT_NO_PROMPT_PASS default_no_prompt_password = self._is_true(default_no_prompt_password) default_test_actions = self._is_true( self.getopt(self.DEFAULT_APP_SECTION, "test_actions")) or False default_test_host = self.getopt(self.DEFAULT_APP_SECTION, "test_host") or None default_test_port = self.getopt(self.DEFAULT_APP_SECTION, "test_port") or None default_log_responses = self.getopt(self.DEFAULT_APP_SECTION, "log_http_responses") or "" default_resource_prefix = self.getopt(self.DEFAULT_APP_SECTION, "resource_prefix") or None default_num_workers = self.getopt( self.DEFAULT_APP_SECTION, "num_workers") or self.DEFAULT_NUM_WORKERS logging.getLogger().removeHandler(temp_handler) self.add_argument("--stomp-host", type=str, default=default_stomp_url, help="Resilient server STOMP host url") self.add_argument("--stomp-port", type=int, default=default_stomp_port, help="Resilient server STOMP port number") self.add_argument("--stomp-cafile", type=str, action="store", default=default_stomp_cafile, help="Resilient server STOMP TLS certificate") self.add_argument( "--stomp-timeout", type=int, action="store", default=os.environ.get('RESILIENT_STOMP_TIMEOUT', default_stomp_timeout), help="Resilient server STOMP timeout for connections") self.add_argument( "--stomp-max-retries", type=int, action="store", default=os.environ.get('RESILIENT_STOMP_MAX_RETRIES', default_stomp_max_retries), help="Resilient server STOMP max retries before failing") self.add_argument( "--max-connection-retries", type=int, action="store", default=os.environ.get('RESILIENT_MAX_CONNECTION_RETRIES') or 0 if os.environ.get("APP_HOST_CONTAINER") else default_max_connection_retries, help= "Resilient max retries when connecting to Resilient or 0 for unlimited" ) self.add_argument( "--resource-prefix", type=str, action="store", default=os.environ.get('RESOURCE_PREFIX', default_resource_prefix), help="Cloud Pak for Security resource path for host and STOMP URLs" ) self.add_argument("--componentsdir", type=str, default=default_components_dir, help="Circuits components auto-load directory") self.add_argument("--noload", type=str, default=default_noload, help="List of components that should not be loaded") self.add_argument("--logdir", type=str, default=default_log_dir, help="Directory for log files") self.add_argument("--loglevel", type=str, default=default_log_level, help="Log level") self.add_argument("--logfile", type=str, default=default_log_file, help="File to log to") self.add_argument("--no-prompt-password", type=bool, default=default_no_prompt_password, help="Never prompt for password on stdin") self.add_argument("--test-actions", action="store_true", default=default_test_actions, help="Enable submitting test actions?") self.add_argument("--test-host", type=str, action="store", default=default_test_host, help=("For use with --test-actions option. " "Host or IP to bind test server to.")) self.add_argument("--test-port", type=int, action="store", default=default_test_port, help=("For use with --test-actions option. " "Port to bind test server to.")) self.add_argument("--log-http-responses", type=str, default=default_log_responses, help=("Log all responses from Resilient " "REST API to this directory")) self.add_argument( "--num-workers", type=int, default=default_num_workers, help=("Number of FunctionWorkers to use. " "Number of Functions that can run in parallel"))