consul-template icon indicating copy to clipboard operation
consul-template copied to clipboard

Always parse template wait intervals to avoid unnecessary rendering.

Open vaLski opened this issue 11 months ago • 4 comments

Fix [GH-1140] by always parsing template wait intervals to avoid unnecessary rendering.

vaLski avatar Jan 03 '25 08:01 vaLski

How can we contribute our azure_key_vault_loader.py if we have finished our implementation, as https://www.dynaconf.com/development/contributing/ is currently not reachable?

Not sure why the pages under /development/* appear not to be published correctly, but the Markdown source of the contribution guide can be found at https://github.com/dynaconf/dynaconf/blob/master/docs/development/contributing.md.

sisp avatar Oct 29 '24 08:10 sisp

@ZhiliangWu We implemented something similar for GCP Secret manager by using a post_hook. I think the idea came from a similar discussion here in the Github tracker.

We are looking for toplevel keys starting with @gcp-secret and replace them with values loaded from the secret manager.

I am pasting the code below, it should be easily adaptable for you case.

Click me
from __future__ import annotations

from typing import Any, cast

import dynaconf
import google.cloud.secretmanager
import google_crc32c
from dynaconf.constants import DEFAULT_SETTINGS_FILES

__all__ = ["settings"]


def _resolve_from_secret_manager(dynaconf_settings):
    """Dynaconf settings hook to load secrets from GCP Secrets Manager.

    The hook will search each entry in the settings object for a value on the format: "@gcp-secret secret-id"

    @see: https://www.dynaconf.com/advanced/#hooks-for-the-solution

    :param dynaconf_settings: Dynaconf settings object
    :returns: settings resolved via GCP Secrets Manager to be merged into the settings object.
    """
    data: dict[str, Any] = {"dynaconf_merge": True}

    client = None

    for key in dynaconf_settings:
        value = dynaconf_settings.get(key)

        if isinstance(value, str) and value.startswith("@gcp-secret"):
            if client is None:
                client = google.cloud.secretmanager.SecretManagerServiceClient()

            secret_id = value.removeprefix("@gcp-secret").strip()
            try:
                project_id = dynaconf_settings["secret_manager_project_id"]
            except KeyError as e:
                msg = "Configuration error. 'secret_manager_project_id' must be set in Dynaconf settings."
                raise RuntimeError(msg) from e

            data[key] = _access_secret_version(project_id, secret_id, client=client)

    return data


def _access_secret_version(
    project_id: str,
    secret_id: str,
    *,
    version_id: str = "latest",
    client: google.cloud.secretmanager.SecretManagerServiceClient | None = None,
) -> str:
    """Access the payload for the given secret version if one exists.

    The version can be a version number as a string (e.g. "5") or an alias (e.g. "latest").

    Copied from https://cloud.google.com/secret-manager/docs/access-secret-version
    """
    client = client or google.cloud.secretmanager.SecretManagerServiceClient()

    name = f"projects/{project_id}/secrets/{secret_id}/versions/{version_id}"

    # Access the secret version.
    response = client.access_secret_version(request={"name": name})

    # Verify payload checksum.
    crc32c = google_crc32c.Checksum()
    crc32c.update(response.payload.data)
    if response.payload.data_crc32c != int(crc32c.hexdigest(), 16):
        msg = "Data corruption detected."
        raise RuntimeError(msg)

    return cast(
        str,
        response.payload.data.decode("UTF-8"),
    )


settings = dynaconf.Dynaconf(
    environments=True,
    lowercase_read=True,
    default_settings_paths=DEFAULT_SETTINGS_FILES,
    post_hooks=[_resolve_from_secret_manager],
)

aberres avatar Oct 30 '24 11:10 aberres

Hey @ZhiliangWu ,

The contrib doc page is unreleased, thanks for finding the right one @sisp. And thanks for sharing your solution, @aberres .

There are different approaches, and they have subtle differences that may suit different cases. There is also this one with converter for 1password, for example. For adding to the library maybe a loader would make sense (like we have for vault/redis), but we are in a feature freeze for v3 right now (trying to find some time to work on v4).

We have plans for making this more elegant/extensible, see https://github.com/dynaconf/dynaconf/issues/1152

pedro-psb avatar Oct 30 '24 12:10 pedro-psb

For now, we do this, which is probably where @aberres got the idea from. The difference is it loads secrets as they're accessed instead of on Dynaconf instantiation, making startup cost smaller. The Monkey Patch isn't super neat, but it's been rock solid for us.

See #1160 for a possibly neater solution.

For Pedro and Bruno: I'm sorry I disappeared guys :S. Long story. From now on, I'll try to dedicate more time to reviewing issues and contributing on weekends; I've set aside some time for this.

sebastian-correa avatar Nov 06 '24 14:11 sebastian-correa