def load_experiment(experiment_source: str, settings: Settings = None, verify_tls: bool = True) -> Experiment: """ Load an experiment from the given source. The source may be a local file or a HTTP(s) URL. If the endpoint requires authentication, please set the appropriate entry in the settings file, under the `auths:` section, keyed by domain. For instance: ```yaml auths: mydomain.com: type: basic value: XYZ otherdomain.com: type: bearer value: UIY localhost:8081: type: digest value: UIY ``` Set `verify_tls` to `False` if the source is a over a self-signed certificate HTTP endpoint to instruct the loader to not verify the certificates. """ with controls(level="loader", context=experiment_source) as control: if os.path.exists(experiment_source): parsed = parse_experiment_from_file(experiment_source) control.with_state(parsed) return parsed p = urlparse(experiment_source) if not p.scheme and not os.path.exists(p.path): raise InvalidSource('Path "{}" does not exist.'.format(p.path)) if p.scheme not in ("http", "https"): raise InvalidSource( "'{}' is not a supported source scheme.".format(p.scheme)) headers = {"Accept": "application/json, application/x-yaml"} if settings: auths = settings.get("auths", []) for domain in auths: if domain == p.netloc: auth = auths[domain] headers["Authorization"] = "{} {}".format( auth["type"], auth["value"]) break r = requests.get(experiment_source, headers=headers, verify=verify_tls) if r.status_code != 200: raise InvalidSource("Failed to fetch the experiment: {}".format( r.text)) logger.debug("Fetched experiment: \n{}".format(r.text)) parsed = parse_experiment_from_http(r) control.with_state(parsed) return parsed
def load_experiment(experiment_source: str, settings: Settings = None) -> Experiment: """ Load an experiment from the given source. The source may be a local file or a HTTP(s) URL. If the endpoint requires authentication, please set the appropriate entry in the settings file, under the `auths:` section, keyed by domain. For instance: ```yaml auths: mydomain.com: type: basic value: XYZ otherdomain.com: type: bearer value: UIY localhost:8081: type: digest value: UIY ``` """ if os.path.exists(experiment_source): return parse_experiment_from_file(experiment_source) p = urlparse(experiment_source) if not p.scheme and not os.path.exists(p.path): raise InvalidSource('Path "{}" does not exist.'.format(p.path)) if p.scheme not in ("http", "https"): raise InvalidSource("'{}' is not a supported source scheme.".format( p.scheme)) headers = {"Accept": "application/json, application/x-yaml"} if settings: auths = settings.get("auths", []) for domain in auths: if domain == p.netloc: auth = auths[domain] headers["Authorization"] = '{} {}'.format( auth["type"], auth["value"]) break r = requests.get(experiment_source, headers=headers) if r.status_code != 200: raise InvalidSource("Failed to fetch the experiment: {}".format( r.text)) logger.debug("Fetched experiment: \n{}".format(r.text)) return parse_experiment_from_http(r)
def parse_experiment_from_http(response: requests.Response) -> Experiment: """ Parse the given experiment from the request's `response`. """ content_type = response.headers.get("Content-Type") if 'application/json' in content_type: return response.json() elif 'application/x-yaml' in content_type or 'text/yaml' in content_type: try: return yaml.safe_load(response.text) except yaml.YAMLError as ye: raise InvalidSource("Failed parsing YAML experiment: {}".format( str(ye))) elif 'text/plain' in content_type: content = response.text try: return json.loads(content) except JSONDecodeError: try: return yaml.safe_load(content) except yaml.YAMLError: pass raise InvalidExperiment( "only files with json, yaml or yml extensions are supported")
def set_run_context(settings): try: update_settings_from_env(settings) ensure_settings_are_valid(settings) except Exception as x: logger.debug(x) logger.error( "Your experiment results will not be uploaded to the cloud. " + str(x)) raise Exception(str(x)) params = click.get_current_context().params # configure upload options try: vcs_info = vcs_information_factory().as_dict(params.get('source')) settings = add_to_run_context(settings, 'vcs', vcs_info) add_to_run_context(settings, 'no_upload', False) except Exception as ex: logger.debug(ex) logger.warning( "Your experiment results will not be uploaded to the cloud. " "Run an experiment within your repository.") add_to_run_context(settings, 'no_upload', True) add_to_run_context(settings, 'description', params.get('description')) # set experiment path and verify if it is on local drive source = params.get('source') filename = click.format_filename(source) if not os.path.exists(filename): raise InvalidSource('Path "{}" does not exist.'.format(filename)) add_to_run_context(settings, 'path', source) trigger = 'manual' if 'CHAOS_TASK_ID' in os.environ: trigger = 'ci' task = None if trigger == 'ci': task = { 'id': os.environ.get('CHAOS_TASK_ID', None), 'uri': os.environ.get('CHAOS_TASK_URI', None) } add_to_run_context(settings, 'trigger', trigger) add_to_run_context(settings, 'task', task)
def parse_experiment_from_file(path: str) -> Experiment: """ Parse the given experiment from `path` and return it. """ with io.open(path) as f: p, ext = os.path.splitext(path) if ext in (".yaml", ".yml"): try: return yaml.safe_load(f) except yaml.YAMLError as ye: raise InvalidSource( "Failed parsing YAML experiment: {}".format(str(ye))) elif ext == ".json": return json.load(f) raise InvalidExperiment( "only files with json, yaml or yml extensions are supported")
def switch_team_during_verification_run( source: str, # noqa: C901 settings: Settings) -> bool: """ Verification may be run in a different team than the active team the user selected. Rather than preventing the verification from running, try to switch to the appropriate team's context for the duration of this run. It's all in memory and not changed on disk. """ if not has_chaosiq_extension_configured(settings): logger.fatal( "Please signin to ChaosIQ services first with `$ chaos signin`") return False base_url = get_endpoint_url(settings) verify_tls = get_verify_tls(settings) default_org = get_default_org(settings) team = get_default_team(default_org) if not team: logger.fatal("Please select a default team with `$chaos team`") return False team_id = team["id"] token = get_auth_token(settings, base_url) if not token: logger.fatal( "Please signin to ChaosIQ services first with `$ chaos signin`") p = urlparse(source) if p.scheme.lower() in ["http", "https"]: r = requests.get(source, headers={"Authorization": "Bearer {}".format(token)}, verify=verify_tls) if r.status_code != 200: logger.fatal("Failed to retrieve verification at '{}': {}".format( source, r.text)) return False experiment = r.json() experiment_team_id = get_team_id(experiment) if experiment_team_id: team_id = experiment_team_id else: if not os.path.exists(p.path): raise InvalidSource('Path "{}" does not exist.'.format(source)) experiment = parse_experiment_from_file(source) experiment_team_id = get_team_id(experiment) if experiment_team_id: team_id = experiment_team_id if not team_id: logger.fatal( "Failed to lookup the team identifier from the verification. " "Are you trying to run a verification using an experiment you " "created manually? This is not possible right now unfortunately.") return False if team["id"] != team_id: team_url = urls.team(urls.org(urls.base(base_url), organization_id=default_org["id"]), team_id=team_id) r = request_team(team_url, token, verify_tls) if r.status_code != 200: logger.fatal("You cannot access the team owning this verification." "Please request them to join the team.") return False team = r.json() if default_org["id"] != team["org_id"]: logger.fatal( "You must be signed in to the appropriate organization to run " "this verification. Please run `$ chaos signin`.") return False logger.debug("Running a verification in a team different from the " "active one. Activating '{}' for this run.".format( team["name"])) set_default_team(default_org, { "id": team_id, "default": True, "name": team["name"] }) return True