Пример #1
0
def get_settings_value(ctx: click.Context, key: str, fmt: str = "json"):
    """
    Show a settings value.

    The key must be dotted path to its location in the settings file.
    """
    if not os.path.isfile(ctx.obj["settings_path"]):
        ctx.exit(1)

    settings = load_settings(ctx.obj["settings_path"]) or {}
    item = locate_settings_entry(settings, key)
    if not item:
        ctx.exit(1)
    parent, entry, key_tail, index = item

    if fmt == "json":
        click.echo(json.dumps(entry, indent=2))
    elif fmt == "string":
        click.echo(str(entry))
    elif fmt == "yaml":
        click.echo(yaml.dump(entry, indent=2))
Пример #2
0
def configure(ctx: click.Context,
              token: str = None,
              default_api_url: str = None):
    settings_path = ctx.obj["settings_path"]
    settings = load_settings(settings_path) or {}

    # set default url for API calls
    api_url = get_api_url(settings)
    if default_api_url:
        api_url = urlparse(default_api_url)
        api_url = \
            "{}://{}{}".format(
                api_url.scheme,
                api_url.netloc,
                api_url.path)
    api_url = api_url or DEFAULT_PROOFDOCK_API_URL

    set_settings(settings, api_url, token)
    save_settings(settings, settings_path)
    click.echo(
        click.style("Configuration saved at {}".format(settings_path),
                    fg='green'))
Пример #3
0
def discover(package: str,
             discovery_path: str = "./discovery.json",
             no_system_info: bool = False,
             no_install: bool = False) -> Discovery:
    """Discover capabilities and experiments."""
    settings = load_settings()
    try:
        notify(settings, DiscoverFlowEvent.DiscoverStarted, package)
        discovery = disco(package_name=package,
                          discover_system=not no_system_info,
                          download_and_install=not no_install)
    except DiscoveryFailed as err:
        notify(settings, DiscoverFlowEvent.DiscoverFailed, package, err)
        logger.debug("Failed to discover {}".format(package), exc_info=err)
        logger.fatal(str(err))
        return

    with open(discovery_path, "w") as d:
        d.write(json.dumps(discovery, indent=2, default=encoder))
    logger.info("Discovery outcome saved in {p}".format(p=discovery_path))

    notify(settings, DiscoverFlowEvent.DiscoverCompleted, discovery)
    return discovery
Пример #4
0
def set_settings_value(ctx: click.Context, key: str, value: str = None):
    """
    Set a settings value.
    The value must be a valid JSON string so that it can be interpreted
    with the appropriate type.

    The key must be dotted path to its location in the settings file.
    """
    if not os.path.isfile(ctx.obj["settings_path"]):
        ctx.exit(1)

    settings = load_settings(ctx.obj["settings_path"]) or {}
    item = locate_settings_entry(settings, key)
    if not item:
        ctx.exit(1)
    parent, entry, key_tail, index = item

    value = json.loads(value)
    if key_tail is not None:
        parent[key_tail] = value
    elif index is not None:
        parent[index] = value
    save_settings(settings, ctx.obj["settings_path"])
Пример #5
0
def settings(settings_file: str) -> Settings:
    return load_settings(settings_file)
Пример #6
0
def init(ctx: click.Context, discovery_path: str = "./discovery.json",
         experiment_path: str = "./experiment.json") -> Experiment:
    """Initialize a new experiment from discovered capabilities."""
    settings = load_settings(ctx.obj["settings_path"])
    notify(settings, InitFlowEvent.InitStarted)
    click.secho(
        "You are about to create an experiment.\n"
        "This wizard will walk you through each step so that you can build\n"
        "the best experiment for your needs.\n"
        "\n"
        "An experiment is made up of three elements:\n"
        "- a steady-state hypothesis [OPTIONAL]\n"
        "- an experimental method\n"
        "- a set of rollback activities [OPTIONAL]\n"
        "\n"
        "Only the method is required. Also your experiment will\n"
        "not run unless you define at least one activity (probe or action)\n"
        "within it",
        fg="blue")

    discovery = None
    if discovery_path and os.path.exists(discovery_path):
        with open(discovery_path) as d:
            discovery = json.loads(d.read())
    else:
        click.echo("No discovery was found, let's create an empty experiment")

    base_experiment = {
        "version": "1.0.0",
        "title": "",
        "description": "N/A",
        "tags": []
    }

    s = click.style

    title = click.prompt(s("Experiment's title", fg='green'), type=str)
    base_experiment["title"] = title

    click.secho(
        "\nA steady state hypothesis defines what 'normality' "
        "looks like in your system\n"
        "The steady state hypothesis is a collection of "
        "conditions that are used,\n"
        "at the beginning of an experiment, to decide if the "
        "system is in a recognised\n"
        "'normal' state. The steady state conditions are then "
        "used again when your experiment\n"
        " is complete to detect where your system may have "
        "deviated in an interesting,\n"
        "weakness-detecting way\n"
        "\n"
        "Initially you may not know what your steady state "
        "hypothesis is\n"
        "and so instead you might create an experiment "
        "without one\n"
        "This is why the stead state hypothesis is optional.", fg="blue")
    m = s('Do you want to define a steady state hypothesis now?',
          dim=True)
    if click.confirm(m):
        hypo = {}

        title = click.prompt(s("Hypothesis's title", fg='green'), type=str)
        hypo["title"] = title
        hypo["probes"] = []

        if discovery:
            activities = []
            for a in discovery["activities"]:
                if a["type"] == "probe":
                    activities.append((a["name"], a))

            click.secho(
                "\nYou may now define probes that will determine\n"
                "the steady-state of your system.",
                fg="blue")
            add_activities(activities, hypo["probes"], with_tolerance=True)

        base_experiment["steady-state-hypothesis"] = hypo

    if discovery:
        base_experiment["method"] = []
        click.secho(
            "\nAn experiment's method contains actions "
            "and probes. Actions\n"
            "vary real-world events in your system to determine if your\n"
            "steady-state hypothesis is maintained when those events occur.\n"
            "\n"
            "An experimental method can also contain probes to gather"
            " additional\n"
            "information about your system as your method is executed.",
            fg="blue")

        m = s('Do you want to define an experimental method?', dim=True)
        if click.confirm(m):
            activities = [(a["name"], a) for a in discovery["activities"]]
            add_activities(activities, base_experiment["method"])

        click.secho(
            "\nAn experiment may optionally define a set of remedial"
            " actions\nthat are used to rollback the system to a given"
            " state.",
            fg="blue")
        m = s('Do you want to add some rollbacks now?', dim=True)
        if click.confirm(m):
            rollbacks = []
            activities = []
            for a in discovery["activities"]:
                if a["type"] == "action":
                    activities.append((a["name"], a))
            add_activities(activities, rollbacks)
            base_experiment["rollbacks"] = rollbacks

    if is_yaml(experiment_path):
        output = yaml.dump(base_experiment,
                           indent=4,
                           default_flow_style=False,
                           sort_keys=False)
    else:
        output = json.dumps(base_experiment, indent=4, default=encoder)

    with open(experiment_path, "w") as e:
        e.write(output)

    click.echo(
        "\nExperiment created and saved in '{e}'".format(e=experiment_path))

    notify(settings, InitFlowEvent.InitCompleted, base_experiment)
    return base_experiment
Пример #7
0
def test_do_not_fail_when_settings_do_not_exist():
    assert load_settings(os.path.join(settings_dir,
                                      "no_settings.yaml")) is None
Пример #8
0
def test_load_unsafe_settings():
    settings = load_settings(os.path.join(settings_dir,
                                          "unsafe-settings.yaml"))
    assert settings is None
Пример #9
0
def test_load_settings():
    settings = load_settings(os.path.join(settings_dir, "settings.yaml"))
    assert "notifications" in settings
Пример #10
0
def test_get_loaded_settings():
    settings = load_settings(os.path.join(settings_dir, "settings.yaml"))
    assert get_loaded_settings() is settings
Пример #11
0
def run(
    ctx: click.Context,
    source: str,
    journal_path: str = "./journal.json",
    dry: Optional[str] = None,
    no_validation: bool = False,
    no_exit: bool = False,
    no_verify_tls: bool = False,
    rollback_strategy: str = "default",
    var: Dict[str, Any] = None,
    var_file: List[str] = None,
    hypothesis_strategy: str = "default",
    hypothesis_frequency: float = 1.0,
    fail_fast: bool = False,
) -> Journal:
    """Run the experiment loaded from SOURCE, either a local file or a
    HTTP resource. SOURCE can be formatted as JSON or YAML."""
    settings = load_settings(ctx.obj["settings_path"]) or {}
    has_deviated = False
    has_failed = False

    experiment_vars = merge_vars(var, var_file)

    load_global_controls(settings)

    try:
        experiment = load_experiment(source, settings, verify_tls=not no_verify_tls)
    except InvalidSource as x:
        logger.error(str(x))
        logger.debug(x)
        ctx.exit(1)

    notify(settings, RunFlowEvent.RunStarted, experiment)

    if not no_validation:
        try:
            ensure_experiment_is_valid(experiment)
        except ChaosException as x:
            logger.error(str(x))
            logger.debug(x)
            ctx.exit(1)

    experiment["dry"] = Dry.from_string(dry)
    settings.setdefault("runtime", {}).setdefault("rollbacks", {}).setdefault(
        "strategy", rollback_strategy
    )
    hypothesis_strategy = check_hypothesis_strategy_spelling(hypothesis_strategy)
    schedule = Schedule(
        continuous_hypothesis_frequency=hypothesis_frequency, fail_fast=fail_fast
    )

    journal = run_experiment(
        experiment,
        settings=settings,
        strategy=hypothesis_strategy,
        schedule=schedule,
        experiment_vars=experiment_vars,
    )
    has_deviated = journal.get("deviated", False)
    has_failed = journal["status"] != "completed"
    if "dry" in journal["experiment"]:
        journal["experiment"]["dry"] = dry
    with open(journal_path, "w") as r:
        json.dump(journal, r, indent=2, ensure_ascii=False, default=encoder)

    if journal["status"] == "completed":
        notify(settings, RunFlowEvent.RunCompleted, journal)
    elif has_failed:
        notify(settings, RunFlowEvent.RunFailed, journal)

        if has_deviated:
            notify(settings, RunFlowEvent.RunDeviated, journal)

    if (has_failed or has_deviated) and not no_exit:
        ctx.exit(1)

    return journal
Пример #12
0
def test_org(org):
    url = 'https://console.chaos-awesome-toolkit.com'
    token = 'XYZ'

    with requests_mock.mock() as m:
        m.head(url)
        m.get("{}/api/v1/organizations".format(url),
              json=[{
                  "id": "abc",
                  "name": "myorg"
              }, {
                  "id": "tyu",
                  "name": "otherorg"
              }])

        m.get("{}/api/v1/organizations/tyu/teams".format(url),
              json=[{
                  "id": "123",
                  "name": "myteam"
              }, {
                  "id": "456",
                  "name": "otherteam"
              }])

        runner = CliRunner()
        with NamedTemporaryFile(suffix="yaml") as settings_file:

            use_org_selected_org_index = '2'
            selected_team_index = '1'
            use_org_inputs = '\n'.join(
                [url, token, use_org_selected_org_index, selected_team_index])

            result = runner.invoke(cli,
                                   ["--settings", settings_file.name, "org"],
                                   input=use_org_inputs)

            assert result.exit_code == 0
            assert result.exception is None

            settings_file.seek(0)
            settings = load_settings(settings_file.name)

            assert 'console.chaos-awesome-toolkit.com' in settings["auths"]
            auth = settings["auths"]['console.chaos-awesome-toolkit.com']
            assert auth["type"] == "bearer"
            assert auth["value"] == "XYZ"

            assert "provider" in settings["controls"]["chaosiq-cloud"]
            provider = settings["controls"]["chaosiq-cloud"]["provider"]
            assert provider["module"] == "chaoscloud.controls"
            assert provider["type"] == "python"
            assert len(provider["arguments"]["organizations"]) == 1

            orgs = provider["arguments"]["organizations"][0]
            assert orgs["id"] == "tyu"
            assert orgs["name"] == "otherorg"
            assert orgs["teams"] == [{
                "id": "123",
                "name": "myteam",
                "default": True
            }]