Ejemplo n.º 1
0
def test_save_configuration(tmp_path):
    configuration_file = tmp_path.joinpath("zebr0.conf")

    client = zebr0.Client("http://127.0.0.1:8000", levels=["lorem", "ipsum"], cache=1, configuration_file=configuration_file)
    client.save_configuration()

    assert configuration_file.read_text(zebr0.ENCODING) == '{"url": "http://127.0.0.1:8000", "levels": ["lorem", "ipsum"], "cache": 1}'
Ejemplo n.º 2
0
def test_read_ok(tmp_path, server):
    file = tmp_path.joinpath("file")
    file.write_text("content")
    server.data = {"template": "{{ '" + str(file) + "' | read }}"}
    client = zebr0.Client("http://127.0.0.1:8000", configuration_file=Path(""))

    assert client.get("template") == "content"
Ejemplo n.º 3
0
def test_recursive_render(server):
    server.data = {
        "answer": "42",
        "template": "the answer is {{ 'answer' | get }}"
    }
    client = zebr0.Client("http://127.0.0.1:8000", configuration_file=Path(""))

    assert client.get("template") == "the answer is 42"
Ejemplo n.º 4
0
def test_configuration_file(server, tmp_path):
    configuration_file = tmp_path.joinpath("zebr0.conf")
    configuration_file.write_text('{"url": "http://127.0.0.1:8000", "levels": ["lorem", "ipsum"], "cache": 1}', zebr0.ENCODING)

    server.data = {"lorem/ipsum/dolor": "sit amet"}
    client = zebr0.Client(configuration_file=configuration_file)

    assert client.get("dolor") == "sit amet"
Ejemplo n.º 5
0
def test_get_latest_image_fixed(server):
    server.data = {
        "aws-region": region,
        "aws-ami-criteria": GET_LATEST_IMAGE_FIXED_CRITERIA
    }

    client = zebr0_aws.Client(zebr0.Client("http://localhost:8000"))

    assert client.get_latest_image().get("ImageId") == "ami-06ec8443c2a35b0ba"
Ejemplo n.º 6
0
def test_get_latest_image_wrong(server):
    server.data = {
        "aws-region": region,
        "aws-ami-criteria": GET_LATEST_IMAGE_WRONG_CRITERIA
    }

    client = zebr0_aws.Client(zebr0.Client("http://localhost:8000"))

    assert client.get_latest_image() is None
Ejemplo n.º 7
0
def test_recursive_nested_render(server):
    server.data = {
        "what": "memes",
        "star_wars/what": "droids",
        "star_wars/punctuation_mark": ".",
        "star_wars/slang/punctuation_mark": ", duh!",
        "template": "these aren't the {{ 'what' | get }} you're looking for{{ 'punctuation_mark' | get }}"
    }
    client = zebr0.Client("http://127.0.0.1:8000", levels=["star_wars", "slang"], configuration_file=Path(""))

    assert client.get("template") == "these aren't the droids you're looking for, duh!"
Ejemplo n.º 8
0
def test_get_latest_image_rolling(server):
    server.data = {
        "aws-region": region,
        "aws-ami-criteria": GET_LATEST_IMAGE_ROLLING_CRITERIA
    }

    client = zebr0_aws.Client(zebr0.Client("http://localhost:8000"))

    one_week_ago = datetime.utcnow() - timedelta(weeks=1)
    creation_date = datetime.fromisoformat(client.get_latest_image().get(
        "CreationDate")[:-1])  # removes the "Z" at the end of aws dates
    assert creation_date > one_week_ago
Ejemplo n.º 9
0
def test_get_bucket(server):
    server.data = {
        "aws-region": region,
        "aws-stack-name": stack_name,
        "aws-stack-config": GET_BUCKET_CONFIG
    }

    client = zebr0_aws.Client(zebr0.Client("http://localhost:8000"))
    client.create_stack()

    assert client.get_bucket(
    ) is not None  # todo: obviously this is not enough, but it will do for now
Ejemplo n.º 10
0
def test_create_stack(server):
    server.data = {
        "aws-region": region,
        "aws-stack-name": stack_name,
        "aws-stack-config": CREATE_STACK_CONFIG
    }

    client = zebr0_aws.Client(zebr0.Client("http://localhost:8000"))
    client.create_stack()

    boto3_client = boto3.client(service_name="s3", region_name=region)
    assert any(bucket
               for bucket in boto3_client.list_buckets().get("Buckets", [])
               if bucket.get("Name").startswith(stack_name + "-bucket-"))
Ejemplo n.º 11
0
def run(url: str,
        levels: Optional[List[str]],
        cache: int,
        configuration_file: Path,
        reports_path: Path,
        key: str,
        attempts: int = ATTEMPTS_DEFAULT,
        pause: float = PAUSE_DEFAULT,
        **_) -> None:
    """
    Fetches a script from the key-value server and executes its tasks.
    Execution reports are written after each task.
    On failure, the output is displayed and the loop stops.
    Should you run the script again, successful tasks will be skipped.

    :param url: (zebr0) URL of the key-value server, defaults to https://hub.zebr0.io
    :param levels: (zebr0) levels of specialization (e.g. ["mattermost", "production"] for a <project>/<environment>/<key> structure), defaults to []
    :param cache: (zebr0) in seconds, the duration of the cache of http responses, defaults to 300 seconds
    :param configuration_file: (zebr0) path to the configuration file, defaults to /etc/zebr0.conf for a system-wide configuration
    :param reports_path: Path to the reports' directory
    :param key: the script's key
    :param attempts: maximum number of attempts before reporting a failure
    :param pause: delay in seconds between two attempts
    """

    reports_path.mkdir(parents=True,
                       exist_ok=True)  # make sure the parent directory exists

    client = zebr0.Client(url, levels, cache, configuration_file)
    for task, status, report_path in recursive_fetch_script(
            client, key, reports_path):
        if status == Status.SUCCESS:
            print("skipping:", json.dumps(task))
            continue

        print("executing:", json.dumps(task))
        report = execute(
            **task, attempts=attempts,
            pause=pause) if COMMAND in task.keys() else fetch_to_disk(
                **task, client=client)
        report_path.write_text(json.dumps(report, indent=2),
                               encoding=zebr0.ENCODING)

        if report.get(STATUS) == Status.SUCCESS:
            print("success!")
            continue

        print("error:", json.dumps(report.get(OUTPUT), indent=2))
        break
Ejemplo n.º 12
0
def show(url: str, levels: Optional[List[str]], cache: int,
         configuration_file: Path, reports_path: Path, key: str, **_) -> None:
    """
    Fetches a script from the key-value server and displays its tasks along with their current status.

    :param url: (zebr0) URL of the key-value server, defaults to https://hub.zebr0.io
    :param levels: (zebr0) levels of specialization (e.g. ["mattermost", "production"] for a <project>/<environment>/<key> structure), defaults to []
    :param cache: (zebr0) in seconds, the duration of the cache of http responses, defaults to 300 seconds
    :param configuration_file: (zebr0) path to the configuration file, defaults to /etc/zebr0.conf for a system-wide configuration
    :param reports_path: Path to the reports' directory
    :param key: the script's key
    """

    client = zebr0.Client(url, levels, cache, configuration_file)
    for task, status, _ in recursive_fetch_script(client, key, reports_path):
        print(f"{status}: {json.dumps(task)}")
Ejemplo n.º 13
0
def test_create_instance(server):
    server.data = {
        "aws-region": region,
        "aws-stack-name": stack_name,
        "aws-stack-config": CREATE_INSTANCE_CONFIG,
        "aws-ami-criteria": GET_LATEST_IMAGE_ROLLING_CRITERIA,
        "aws-instance-type": "t3.micro",
        "aws-volume-size": "8",
        "aws-user-data": "#cloud-config"
    }

    client = zebr0_aws.Client(zebr0.Client("http://localhost:8000"))
    client.create_stack()

    assert client.create_instance(
    ) is not None  # todo: obviously this is not enough, but it will do for now
Ejemplo n.º 14
0
def delete(url: str, levels: Optional[List[str]], cache: int, configuration_file: Path, **_) -> None:
    zebr0_client = zebr0.Client(url, levels, cache, configuration_file)
    sts_client = sts.Client(zebr0_client)
    iam_client = iam.Client(zebr0_client, sts_client)
    ec2_client = ec2.Client(zebr0_client, iam_client)
    route53_client = route53.Client(zebr0_client)

    route53_client.destroy_dns_entry_if_needed()
    ec2_client.destroy_address_if_needed()
    ec2_client.destroy_instance_if_needed()
    iam_client.delete_old_access_keys()
    iam_client.delete_user_if_needed()
    iam_client.delete_policy_if_needed()
    ec2_client.destroy_internet_gateway_if_needed()
    ec2_client.destroy_subnet_if_needed()
    ec2_client.destroy_vpc_if_needed()
Ejemplo n.º 15
0
def create(url: str, levels: Optional[List[str]], cache: int, configuration_file: Path, **_) -> None:
    zebr0_client = zebr0.Client(url, levels, cache, configuration_file)
    sts_client = sts.Client(zebr0_client)
    iam_client = iam.Client(zebr0_client, sts_client)
    ec2_client = ec2.Client(zebr0_client, iam_client)
    route53_client = route53.Client(zebr0_client)
    s3_client = s3.Client(zebr0_client)

    s3_client.create_bucket_if_needed()
    vpc_id = ec2_client.create_vpc_if_needed()
    subnet_id = ec2_client.create_subnet_if_needed(vpc_id)
    ec2_client.create_internet_gateway_if_needed(vpc_id)
    iam_client.create_policy_if_needed()
    iam_client.create_user_if_needed()
    instance_id = ec2_client.create_instance_if_needed(subnet_id)
    address = ec2_client.create_address_if_needed(instance_id)
    route53_client.create_dns_entry_if_needed(address)
Ejemplo n.º 16
0
def test_full(server):
    server.data = {
        "aws-region": region,
        "aws-stack-name": stack_name,
        "aws-stack-config": FULL_CONFIG,
        "aws-ami-criteria": GET_LATEST_IMAGE_ROLLING_CRITERIA,
        "aws-instance-type": "t3.micro",
        "aws-volume-size": "8",
        "aws-user-data": FULL_USER_DATA
    }

    client = zebr0_aws.Client(zebr0.Client("http://localhost:8000"))
    client.create_stack()

    ip = client.create_instance()

    heal_check.warning_tolerant_check("http://" + ip + ":2501")
Ejemplo n.º 17
0
def debug(url: str, levels: Optional[List[str]], cache: int,
          configuration_file: Path, reports_path: Path, key: str, **_) -> None:
    """
    Fetches a script from the key-value server and executes its tasks through user interaction.
    Useful for debugging scripts in a test environment.

    :param url: (zebr0) URL of the key-value server, defaults to https://hub.zebr0.io
    :param levels: (zebr0) levels of specialization (e.g. ["mattermost", "production"] for a <project>/<environment>/<key> structure), defaults to []
    :param cache: (zebr0) in seconds, the duration of the cache of http responses, defaults to 300 seconds
    :param configuration_file: (zebr0) path to the configuration file, defaults to /etc/zebr0.conf for a system-wide configuration
    :param reports_path: Path to the reports' directory
    :param key: the script's key
    """

    reports_path.mkdir(parents=True,
                       exist_ok=True)  # make sure the parent directory exists

    client = zebr0.Client(url, levels, cache, configuration_file)
    for task, status, report_path in recursive_fetch_script(
            client, key, reports_path):
        if status == Status.SUCCESS:
            print("already executed:", json.dumps(task))
            print("(s)kip, (e)xecute anyway, or (q)uit?")
        else:
            print("next:", json.dumps(task))
            print("(e)xecute, (s)kip, or (q)uit?")

        choice = sys.stdin.readline().strip()
        if choice == "e":
            report = execute(
                **task,
                attempts=1) if COMMAND in task.keys() else fetch_to_disk(
                    **task, client=client)
            print("success!" if report.get(STATUS) == Status.SUCCESS else
                  f"error: {json.dumps(report.get(OUTPUT), indent=2)}")

            print("write report? (y)es or (n)o")
            choice = sys.stdin.readline().strip()
            if choice == "y":
                report_path.write_text(json.dumps(report, indent=2),
                                       encoding=zebr0.ENCODING)
        elif not choice == "s":
            break
Ejemplo n.º 18
0
def main(args: Optional[List[str]] = None) -> None:
    """
    usage: zebr0-lxd [-h] [-u <url>] [-l [<level> [<level> ...]]] [-c <duration>] [-f <path>] [--lxd-url <url>] {create,delete,start,stop} [key]

    LXD provisioning based on zebr0 key-value system.
    Fetches a stack from the key-value server and manages it on LXD.

    positional arguments:
      {create,delete,start,stop}
                            operation to execute on the stack
      key                   the stack's key, defaults to 'lxd-stack'

    optional arguments:
      -h, --help            show this help message and exit
      -u <url>, --url <url>
                            URL of the key-value server, defaults to https://hub.zebr0.io
      -l [<level> [<level> ...]], --levels [<level> [<level> ...]]
                            levels of specialization (e.g. "mattermost production" for a <project>/<environment>/<key> structure), defaults to ""
      -c <duration>, --cache <duration>
                            in seconds, the duration of the cache of http responses, defaults to 300 seconds
      -f <path>, --configuration-file <path>
                            path to the configuration file, defaults to /etc/zebr0.conf for a system-wide configuration
      --lxd-url <url>       URL of the LXD API (scheme is "http+unix", socket path is percent-encoded into the host field), defaults to "http+unix://%2Fvar%2Fsnap%2Flxd%2Fcommon%2Flxd%2Funix.socket"
    """

    argparser = zebr0.build_argument_parser(description="LXD provisioning based on zebr0 key-value system.\nFetches a stack from the key-value server and manages it on LXD.", formatter_class=argparse.RawDescriptionHelpFormatter)
    argparser.add_argument("command", choices=["create", "delete", "start", "stop"], help="operation to execute on the stack")
    argparser.add_argument("key", nargs="?", default="lxd-stack", help="the stack's key, defaults to 'lxd-stack'")
    argparser.add_argument("--lxd-url", default=URL_DEFAULT, help='URL of the LXD API (scheme is "http+unix", socket path is percent-encoded into the host field), defaults to "http+unix://%%2Fvar%%2Fsnap%%2Flxd%%2Fcommon%%2Flxd%%2Funix.socket"', metavar="<url>")
    args = argparser.parse_args(args)

    value = zebr0.Client(args.url, args.levels, args.cache, args.configuration_file).get(args.key)
    if not value:
        print(f"key '{args.key}' not found on server {args.url}")
        exit(1)

    stack = yaml.load(value, Loader=yaml.BaseLoader)
    if not isinstance(stack, dict):
        print(f"key '{args.key}' on server {args.url} is not a proper yaml or json dictionary")
        exit(1)

    getattr(Client(args.lxd_url), args.command + "_stack")(stack)
Ejemplo n.º 19
0
def test_ok(server, tmp_path, capsys, monkeypatch):
    server.data = {
        "script":
        ["echo one", {
            "command": "sleep 1 && echo two",
            "variant": "omicron"
        }]
    }

    configuration_file = tmp_path.joinpath("zebr0.conf")
    zebr0.Client("http://localhost:8000", ["lorem", "ipsum"], 1,
                 configuration_file).save_configuration()
    reports_path = tmp_path.joinpath("reports")

    zebr0_script.main(f"-r {reports_path} log".split())
    assert capsys.readouterr().out == ""

    zebr0_script.main(
        f"-f {configuration_file} -r {reports_path} show".split())
    assert capsys.readouterr().out == OK_OUTPUT1

    monkeypatch.setattr("sys.stdin", io.StringIO("e\nn\nq\n"))
    zebr0_script.main(
        f"-f {configuration_file} -r {reports_path} debug".split())
    assert capsys.readouterr().out == OK_OUTPUT2

    zebr0_script.main(f"-f {configuration_file} -r {reports_path} run".split())
    assert capsys.readouterr().out == OK_OUTPUT3

    zebr0_script.main(f"-r {reports_path} log".split())
    assert capsys.readouterr().out == OK_OUTPUT4.format(
        format_mtime(
            reports_path.joinpath("eb1cf90440f34ae3823016216940e5c2")),
        format_mtime(
            reports_path.joinpath("e3c088ff80db25e139905e254860b0e3")))

    zebr0_script.main(
        f"-f {configuration_file} -r {reports_path} show".split())
    assert capsys.readouterr().out == OK_OUTPUT5

    zebr0_script.main(f"-f {configuration_file} -r {reports_path} run".split())
    assert capsys.readouterr().out == OK_OUTPUT6
Ejemplo n.º 20
0
def test_cache(server):
    server.access_logs = []  # resetting server logs from previous tests

    server.data = {"ping": "pong", "yin": "yang"}
    client = zebr0.Client("http://127.0.0.1:8000", cache=1, configuration_file=Path(""))  # cache of 1 second for the purposes of the test

    assert client.get("ping") == "pong"  # "pong" is now in cache for "/ping"
    time.sleep(0.5)
    assert client.get("yin") == "yang"  # "yang" is now in cache for "/yin"
    time.sleep(0.1)
    server.data = {"ping": "peng", "yin": "yeng"}  # new values, shouldn't be used until cache has expired
    time.sleep(0.3)
    assert client.get("ping") == "pong"  # using cache for "/ping"
    time.sleep(0.2)
    assert client.get("ping") == "peng"  # 1.1 second has passed, cache has expired for "/ping", now fetching the new value
    assert client.get("yin") == "yang"  # still using cache for "/yin"
    time.sleep(0.5)
    assert client.get("yin") == "yeng"  # cache also has expired for "/yin", now fetching the new value

    assert server.access_logs == ["/ping", "/yin", "/ping", "/yin"]
Ejemplo n.º 21
0
def test_default_levels(server):
    server.data = {"knock-knock": "who's there?"}
    client = zebr0.Client("http://127.0.0.1:8000", configuration_file=Path(""))

    assert client.get("knock-knock") == "who's there?"
Ejemplo n.º 22
0
def test_deepest_level(server):
    server.data = {"lorem/ipsum/dolor": "sit amet"}
    client = zebr0.Client("http://127.0.0.1:8000", levels=["lorem", "ipsum"], configuration_file=Path(""))

    assert client.get("dolor") == "sit amet"
Ejemplo n.º 23
0
def test_intermediate_level(server):
    server.data = {"consectetur/elit": "sed do"}
    client = zebr0.Client("http://127.0.0.1:8000", levels=["consectetur", "adipiscing"], configuration_file=Path(""))

    assert client.get("elit") == "sed do"
Ejemplo n.º 24
0
def test_root_level(server):
    server.data = {"incididunt": "ut labore"}
    client = zebr0.Client("http://127.0.0.1:8000", levels=["eiusmod", "tempor"], configuration_file=Path(""))

    assert client.get("incididunt") == "ut labore"
Ejemplo n.º 25
0
def test_missing_key_and_default_value(server):
    server.data = {}
    client = zebr0.Client("http://127.0.0.1:8000", levels=["dolore", "magna"], configuration_file=Path(""))

    assert client.get("aliqua") == ""
    assert client.get("aliqua", default="default") == "default"
Ejemplo n.º 26
0
def test_strip(server):
    server.data = {"knock-knock": "\nwho's there?\n"}
    client = zebr0.Client("http://127.0.0.1:8000", configuration_file=Path(""))

    assert client.get("knock-knock", strip=False) == "\nwho's there?\n"
    assert client.get("knock-knock") == "who's there?"
Ejemplo n.º 27
0
def test_basic_render(server):
    server.data = {"template": "{{ url }} {{ levels[0] }} {{ levels[1] }}"}
    client = zebr0.Client("http://127.0.0.1:8000", levels=["lorem", "ipsum"], configuration_file=Path(""))

    assert client.get("template", template=False) == "{{ url }} {{ levels[0] }} {{ levels[1] }}"
    assert client.get("template") == "http://127.0.0.1:8000 lorem ipsum"
Ejemplo n.º 28
0
def client():
    return zebr0.Client("http://localhost:8000", configuration_file=Path(""))
Ejemplo n.º 29
0
def test_render_with_default(server):
    server.data = {"template": "{{ 'missing_key' | get('default') }}"}
    client = zebr0.Client("http://127.0.0.1:8000", configuration_file=Path(""))

    assert client.get("template") == "default"
Ejemplo n.º 30
0
def test_default_url():
    client = zebr0.Client(configuration_file=Path(""))

    assert client.get("domain-name") == "zebr0.io"