https://pulumi.com logo
#python
Title
# python
c

clean-engineer-75963

12/10/2019, 12:15 AM
I tried to create a Pulumi resource inside a context manager (
with
block) and it doesn't seem to be respecting the context. Details to follow...
I mentioned in an earlier message that I needed to create a Kubernetes Deployment, then port-forward into one of its Pods to configure the software.
That software is Hashicorp Vault, so I thought I could use the pulumi-vault provider to get that work done.
I got clever and wrote a context manager which starts the port-forward to the Vault pod and terminates it when the context closes.
And put all of the pulumi_vault resources inside the
with
block.
And that doesn't seem to be working at all.
The code looks like this:
Copy code
vault_provider = pulumi_vault.Provider(
    "vault-provider",
    address="<https://127.0.0.1:8200>",
    skip_tls_verify=True,
    token=corev1.Secret.get(
        "vault_init_results",
        pulumi.Output.concat(
            base_namespace.metadata["name"], "/vault-init-results")
    ).data["root_token"]

with PortForward(get_ready_vault_pod(), "8200"):
    vault_token = pulumi_vault.Token(
        "vault-token",
        display_name="my-token",
        policies=["my-policy"],
        renewable=True,
        ttl=20 * 60,
        __opts__=pulumi.ResourceOptions(provider=vault_provider),
    )
The context manager definitely works; I've tested it separately.
This is its code:
Copy code
class PortForward:
    """Context manager which starts a port-forward for the duration of the context.


    The Python Kubernetes client library does not yet support port-forwards, so
    this calls kubectl for now.


    <https://github.com/kubernetes-client/python/issues/166#issuecomment-504216584>
    """


    def __init__(self, pod, port, local_port=None):
        self._pod = pod
        self._remote_port = port


        if local_port is not None:
            self._local_port = local_port
        else:
            self._local_port = port


        self.address = f"127.0.0.1:{self._local_port}"


    def __enter__(self):
        <http://self.pf|self.pf> = pexpect.spawn("kubectl", ["port-forward",
            self._pod, f"{self._local_port}:{self._remote_port}"])
        self.pf.expect_exact(
            f"Forwarding from {self.address}"
            f" -> {self._remote_port}\r\n")
        self.pf.expect_exact(
            f"Forwarding from [::1]:{self._local_port}"
            f" -> {self._remote_port}\r\n")


        return self


    def __exit__(self, exc_type, exc_val, exc_tb):
        self.pf.terminate(force=True)


        return False
The error I get from
pulumi up
is this:
Copy code
Diagnostics:
  vault:index:Token (vault-token):
    error: Get <https://127.0.0.1:8200/v1/auth/token/lookup-self>: dial tcp 127.0.0.1:8200: connect: connection refused
Suggesting that the port-forward is not active while the resource is being created.
I also tested the PortForward with the hard-coded name of the Pod to forward to to cut out that
get_ready_vault_pod()
function, which normally goes and finds the right Vault pod to connect to.
w

white-balloon-205

12/10/2019, 1:38 AM
Yes - the resource creation is done in a separate process - and at a different point in time than the
Token
constructor is called. The latter causes the resource to be registered as part of the desired state, but the decision about whether to create or update a resource as a result and then the act of doing that is asynchronous wrt the resource being registered (the call the
Token
returning). I think what you really want here is either dynamic providers (a custom provider implemented inside your Pulumi program that can participate in the resource graph - https://www.pulumi.com/docs/intro/concepts/programming-model/#dynamicproviders) or lifecycle hooks (not yet available but tracked in https://github.com/pulumi/pulumi/issues/1691).
13 Views