Ejemplo n.º 1
0
def test_decrypt_secret_environment_variables(mock_decrypt_for_service,
                                              mock_is_shared_secret,
                                              mock_is_secret_ref):
    mock_environment = {
        "MY": "aaa",
        "SECRET": "SECRET(123)",
        "SECRET_SHARED": "SHARED_SECRET(abc)",
    }
    mock_is_secret_ref.side_effect = lambda val: "SECRET" in val
    mock_is_shared_secret.side_effect = lambda val: "SHARED" in val
    mock_decrypt_for_service.side_effect = [{
        "SECRET": "123"
    }, {
        "SECRET_SHARED": "abc"
    }]

    ret = decrypt_secret_environment_variables(
        secret_provider_name="vault",
        environment=mock_environment,
        soa_dir="/nail/blah",
        service_name="universe",
        cluster_name="mesosstage",
        secret_provider_kwargs={"some": "config"},
    )
    assert ret == {"SECRET": "123", "SECRET_SHARED": "abc"}

    assert mock_decrypt_for_service.call_args_list == [
        mock.call(
            {"SECRET": "SECRET(123)"},
            "universe",
            "vault",
            "/nail/blah",
            "mesosstage",
            {
                "some": "config",
                "vault_num_uses": 2
            },
        ),
        mock.call(
            {"SECRET_SHARED": "SHARED_SECRET(abc)"},
            SHARED_SECRET_SERVICE,
            "vault",
            "/nail/blah",
            "mesosstage",
            {
                "some": "config",
                "vault_num_uses": 2
            },
        ),
    ]
Ejemplo n.º 2
0
def run_docker_container(
    docker_client,
    service,
    instance,
    docker_url,
    volumes,
    interactive,
    command,
    healthcheck,
    healthcheck_only,
    user_port,
    instance_config,
    secret_provider_name,
    soa_dir=DEFAULT_SOA_DIR,
    dry_run=False,
    json_dict=False,
    framework=None,
    secret_provider_kwargs={},
    skip_secrets=False,
):
    """docker-py has issues running a container with a TTY attached, so for
    consistency we execute 'docker run' directly in both interactive and
    non-interactive modes.

    In non-interactive mode when the run is complete, stop the container and
    remove it (with docker-py).
    """
    if user_port:
        if check_if_port_free(user_port):
            chosen_port = user_port
        else:
            print(
                PaastaColors.red(
                    "The chosen port is already in use!\n"
                    "Try specifying another one, or omit (--port|-o) and paasta will find a free one for you"
                ),
                file=sys.stderr,
            )
            sys.exit(1)
    else:
        chosen_port = pick_random_port(service)
    environment = instance_config.get_env_dictionary()
    if not skip_secrets:
        try:
            secret_environment = decrypt_secret_environment_variables(
                secret_provider_name=secret_provider_name,
                environment=environment,
                soa_dir=soa_dir,
                service_name=service,
                cluster_name=instance_config.cluster,
                secret_provider_kwargs=secret_provider_kwargs,
            )
        except Exception as e:
            print(
                f"Failed to retrieve secrets with {e.__class__.__name__}: {e}")
            print(
                "If you don't need the secrets for local-run, you can add --skip-secrets"
            )
            sys.exit(1)
        environment.update(secret_environment)
    local_run_environment = get_local_run_environment_vars(
        instance_config=instance_config,
        port0=chosen_port,
        framework=framework)
    environment.update(local_run_environment)
    net = instance_config.get_net()
    memory = instance_config.get_mem()
    container_name = get_container_name()
    docker_params = instance_config.format_docker_parameters()

    healthcheck_mode, healthcheck_data = get_healthcheck_for_instance(
        service, instance, instance_config, chosen_port, soa_dir=soa_dir)
    if healthcheck_mode is None:
        container_port = None
        interactive = True
    elif not user_port and not healthcheck and not healthcheck_only:
        container_port = None
    else:
        try:
            container_port = instance_config.get_container_port()
        except AttributeError:
            container_port = None

    simulate_healthcheck = (healthcheck_only
                            or healthcheck) and healthcheck_mode is not None

    docker_run_args = dict(
        memory=memory,
        chosen_port=chosen_port,
        container_port=container_port,
        container_name=container_name,
        volumes=volumes,
        env=environment,
        interactive=interactive,
        detach=simulate_healthcheck,
        docker_hash=docker_url,
        command=command,
        net=net,
        docker_params=docker_params,
    )
    docker_run_cmd = get_docker_run_cmd(**docker_run_args)
    joined_docker_run_cmd = " ".join(docker_run_cmd)

    if dry_run:
        if json_dict:
            print(json.dumps(docker_run_args))
        else:
            print(json.dumps(docker_run_cmd))
        return 0
    else:
        print("Running docker command:\n%s" %
              PaastaColors.grey(joined_docker_run_cmd))

    merged_env = {**os.environ, **environment}

    if interactive or not simulate_healthcheck:
        # NOTE: This immediately replaces us with the docker run cmd. Docker
        # run knows how to clean up the running container in this situation.
        wrapper_path = shutil.which("paasta_docker_wrapper")
        # To properly simulate mesos, we pop the PATH, which is not available to
        # The executor
        merged_env.pop("PATH")
        execlpe(wrapper_path, *docker_run_cmd, merged_env)
        # For testing, when execlpe is patched out and doesn't replace us, we
        # still want to bail out.
        return 0

    container_started = False
    container_id = None
    try:
        (returncode, output) = _run(docker_run_cmd, env=merged_env)
        if returncode != 0:
            print(
                "Failure trying to start your container!"
                "Returncode: %d"
                "Output:"
                "%s"
                ""
                "Fix that problem and try again."
                "http://y/paasta-troubleshooting" % (returncode, output),
                sep="\n",
            )
            # Container failed to start so no need to cleanup; just bail.
            sys.exit(1)
        container_started = True
        container_id = get_container_id(docker_client, container_name)
        print("Found our container running with CID %s" % container_id)

        if simulate_healthcheck:
            healthcheck_result = simulate_healthcheck_on_service(
                instance_config=instance_config,
                docker_client=docker_client,
                container_id=container_id,
                healthcheck_mode=healthcheck_mode,
                healthcheck_data=healthcheck_data,
                healthcheck_enabled=healthcheck,
            )

        def _output_exit_code():
            returncode = docker_client.inspect_container(
                container_id)["State"]["ExitCode"]
            print(f"Container exited: {returncode})")

        if healthcheck_only:
            if container_started:
                _output_exit_code()
                _cleanup_container(docker_client, container_id)
            if healthcheck_mode is None:
                print(
                    "--healthcheck-only, but no healthcheck is defined for this instance!"
                )
                sys.exit(1)
            elif healthcheck_result is True:
                sys.exit(0)
            else:
                sys.exit(1)

        running = docker_client.inspect_container(
            container_id)["State"]["Running"]
        if running:
            print("Your service is now running! Tailing stdout and stderr:")
            for line in docker_client.attach(container_id,
                                             stderr=True,
                                             stream=True,
                                             logs=True):
                # writing to sys.stdout.buffer lets us write the raw bytes we
                # get from the docker client without having to convert them to
                # a utf-8 string
                sys.stdout.buffer.write(line)
                sys.stdout.flush()
        else:
            _output_exit_code()
            returncode = 3

    except KeyboardInterrupt:
        returncode = 3

    # Cleanup if the container exits on its own or interrupted.
    if container_started:
        returncode = docker_client.inspect_container(
            container_id)["State"]["ExitCode"]
        _cleanup_container(docker_client, container_id)
    return returncode