Example #1
0
    def __init__(
        self,
        image,
        cmd,
        working_dir,
        host_dir,
        memory_limit_mb=None,
        exposed_ports=None,
        entrypoint=None,
        env_vars=None,
        docker_client=None,
        container_opts=None,
        additional_volumes=None,
    ):
        """
        Initializes the class with given configuration. This does not automatically create or run the container.

        :param string image: Name of the Docker image to create container with
        :param string working_dir: Working directory for the container
        :param string host_dir: Directory in the host operating system that should be mounted to the ``working_dir`` on
            container
        :param list cmd: Command to pass to container
        :param int memory_limit_mb: Optional. Max limit of memory in MegaBytes this Lambda function can use.
        :param dict exposed_ports: Optional. Dict of ports to expose
        :param list entrypoint: Optional. Entry point process for the container. Defaults to the value in Dockerfile
        :param dict env_vars: Optional. Dict of environment variables to setup in the container
        """

        self._image = image
        self._cmd = cmd
        self._working_dir = working_dir
        self._host_dir = host_dir
        self._exposed_ports = exposed_ports
        self._entrypoint = entrypoint
        self._env_vars = env_vars
        self._memory_limit_mb = memory_limit_mb
        self._network_id = None
        self._container_opts = container_opts
        self._additional_volumes = additional_volumes

        # Use the given Docker client or create new one
        self.docker_client = docker_client or docker.from_env()

        # Runtime properties of the container. They won't have value until container is created or started
        self.id = None

        # Check if extensions preview is enabled
        self._extensions_preview_enabled = extensions_preview_enabled()

        # local RAPID defaults to 8080 as the port, however thats a common port. A port is chosen by
        # creating a socket and binding to it. This way a port gets chosen on our behalf.
        if self._extensions_preview_enabled:
            self._socket = socket.socket()
            self._socket.bind(("", 0))
            self.rapid_port_host = self._socket.getsockname()[1]
            self._socket.close()
    def __init__(self, layer_downloader, skip_pull_image, force_image_build, docker_client=None):
        """

        Parameters
        ----------
        layer_downloader samcli.local.layers.layer_downloader.LayerDownloader
            LayerDownloader to download layers locally
        skip_pull_image bool
            True if the image should not be pulled from DockerHub
        force_image_build bool
            True to download the layer and rebuild the image even if it exists already on the system
        docker_client docker.DockerClient
            Optional docker client object
        """
        self.layer_downloader = layer_downloader
        self.skip_pull_image = skip_pull_image
        self.force_image_build = force_image_build
        self.docker_client = docker_client or docker.from_env()
        self._extensions_preview_enabled = extensions_preview_enabled()
Example #3
0
    def __init__(
        self,
        function_name=None,
        function_memory=None,
        function_timeout=None,
        function_handler=None,
        variables=None,
        shell_env_values=None,
        override_values=None,
        aws_creds=None,
    ):
        """
        Initializes this class. It takes in two sets of properties:
            a) (Required) Function information
            b) (Optional) Environment variable configured on the function

        :param integer function_memory: Memory size of the function in megabytes
        :param integer function_timeout: Function's timeout in seconds
        :param string function_handler: Handler of the function
        :param dict variables: Optional. Dict whose key is the environment variable names and value is the default
            values for the variable.
        :param dict shell_env_values: Optional. Dict containing values for the variables grabbed from the shell's
            environment.
        :param dict override_values: Optional. Dict containing values for the variables that will override the values
            from ``default_values`` and ``shell_env_values``.
        :param dict aws_creds: Optional. Dictionary containing AWS credentials passed to the Lambda runtime through
            environment variables. It should contain "key", "secret", "region" and optional "sessiontoken" keys
        """

        self._extensions_preview_enabled = extensions_preview_enabled()

        self._function = {"memory": function_memory, "timeout": function_timeout, "handler": function_handler}

        if self._extensions_preview_enabled:
            self._function["name"] = function_name

        self.variables = variables or {}
        self.shell_env_values = shell_env_values or {}
        self.override_values = override_values or {}
        self.aws_creds = aws_creds or {}
Example #4
0
    def get_debug_settings(debug_port, debug_args_list, runtime, options):
        """
        Get Debug settings based on the Runtime

        Parameters
        ----------
        debug_port int
            Port to open for debugging in the container
        debug_args_list list(str)
            Additional debug args
        runtime str
            Lambda Function runtime
        options dict
            Additonal options needed (i.e delve Path)

        Returns
        -------
        tuple:DebugSettings (list, dict)
            Tuple of debug entrypoint and debug env vars

        """

        extensions_preview_on = extensions_preview_enabled()

        if extensions_preview_on:
            entry = ["/var/rapid/init", "--log-level", "error"]
            entrypoint_mapping = {
                Runtime.java8.value: DebugSettings(
                    entry,
                    debug_env_vars={
                        "_JAVA_OPTIONS": f"-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,quiet=y,address={debug_port} -XX:MaxHeapSize=2834432k -XX:MaxMetaspaceSize=163840k -XX:ReservedCodeCacheSize=81920k -XX:+UseSerialGC -XX:-TieredCompilation -Djava.net.preferIPv4Stack=true -Xshare:off"
                        + " ".join(debug_args_list)
                    },
                ),
                Runtime.java11.value: DebugSettings(
                    entry,
                    debug_env_vars={
                        "_JAVA_OPTIONS": f"-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,quiet=y,address=*:{debug_port} -XX:MaxHeapSize=2834432k -XX:MaxMetaspaceSize=163840k -XX:ReservedCodeCacheSize=81920k -XX:+UseSerialGC -XX:-TieredCompilation -Djava.net.preferIPv4Stack=true"
                        + " ".join(debug_args_list)
                    },
                ),
                Runtime.dotnetcore21.value: DebugSettings(
                    entry + ["/var/runtime/bootstrap"] + debug_args_list,
                    debug_env_vars={"_AWS_LAMBDA_DOTNET_DEBUGGING": "1"},
                ),
                Runtime.go1x.value: DebugSettings(
                    ["/var/runtime/aws-lambda-go"]
                    + debug_args_list
                    + ["-debug=true", "-delvePort=" + str(debug_port), "-delvePath=" + options.get("delvePath")],
                    debug_env_vars={},
                ),
                Runtime.nodejs10x.value: DebugSettings(
                    entry
                    + ["/var/lang/bin/node"]
                    + debug_args_list
                    + [
                        "/var/runtime/index.js",
                    ],
                    debug_env_vars={
                        "NODE_PATH": "/opt/nodejs/node_modules:/opt/nodejs/node10/node_modules:/var/runtime/node_module",
                        "NODE_OPTIONS": f"--inspect-brk=0.0.0.0:{str(debug_port)} --no-lazy --expose-gc --max-http-header-size 81920",
                    },
                ),
                Runtime.nodejs12x.value: DebugSettings(
                    entry
                    + ["/var/lang/bin/node"]
                    + debug_args_list
                    + [
                        "/var/runtime/index.js",
                    ],
                    debug_env_vars={
                        "NODE_PATH": "/opt/nodejs/node_modules:/opt/nodejs/node12/node_modules:/var/runtime/node_module",
                        "NODE_OPTIONS": f"--inspect-brk=0.0.0.0:{str(debug_port)} --no-lazy --expose-gc --max-http-header-size 81920",
                    },
                ),
                Runtime.python27.value: DebugSettings(
                    entry + ["/var/lang/bin/python2.7"] + debug_args_list + ["/var/runtime/bootstrap.py"],
                    debug_env_vars={},
                ),
                Runtime.python36.value: DebugSettings(
                    entry + ["/var/lang/bin/python3.6"] + debug_args_list + ["/var/runtime/bootstrap.py"],
                    debug_env_vars={},
                ),
                Runtime.python37.value: DebugSettings(
                    entry + ["/var/lang/bin/python3.7"] + debug_args_list + ["/var/runtime/bootstrap.py"],
                    debug_env_vars={},
                ),
                Runtime.python38.value: DebugSettings(
                    entry + ["/var/lang/bin/python3.8"] + debug_args_list + ["/var/runtime/bootstrap.py"],
                    debug_env_vars={},
                ),
            }
        else:
            entry = "/var/rapid/init"
            entrypoint_mapping = {
                Runtime.java8.value: DebugSettings(
                    entry,
                    debug_env_vars={
                        "_JAVA_OPTIONS": f"-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,quiet=y,address={debug_port} -XX:MaxHeapSize=2834432k -XX:MaxMetaspaceSize=163840k -XX:ReservedCodeCacheSize=81920k -XX:+UseSerialGC -XX:-TieredCompilation -Djava.net.preferIPv4Stack=true -Xshare:off"
                        + " ".join(debug_args_list)
                    },
                ),
                Runtime.java8al2.value: DebugSettings(
                    entry,
                    debug_env_vars={
                        "_JAVA_OPTIONS": f"-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,quiet=y,address={debug_port} -XX:MaxHeapSize=2834432k -XX:MaxMetaspaceSize=163840k -XX:ReservedCodeCacheSize=81920k -XX:+UseSerialGC -XX:-TieredCompilation -Djava.net.preferIPv4Stack=true -Xshare:off"
                        + " ".join(debug_args_list)
                    },
                ),
                Runtime.java11.value: DebugSettings(
                    entry,
                    debug_env_vars={
                        "_JAVA_OPTIONS": f"-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,quiet=y,address=*:{debug_port} -XX:MaxHeapSize=2834432k -XX:MaxMetaspaceSize=163840k -XX:ReservedCodeCacheSize=81920k -XX:+UseSerialGC -XX:-TieredCompilation -Djava.net.preferIPv4Stack=true"
                        + " ".join(debug_args_list)
                    },
                ),
                Runtime.dotnetcore21.value: DebugSettings(
                    [
                        "/var/rapid/init",
                        "--bootstrap",
                        "/var/runtime/bootstrap",
                        "--bootstrap-args",
                        json.dumps(debug_args_list),
                    ],
                    debug_env_vars={"_AWS_LAMBDA_DOTNET_DEBUGGING": "1"},
                ),
                Runtime.dotnetcore31.value: DebugSettings(
                    [
                        "/var/rapid/init",
                        "--bootstrap",
                        "/var/runtime/bootstrap",
                        "--bootstrap-args",
                        json.dumps(debug_args_list),
                    ],
                    debug_env_vars={"_AWS_LAMBDA_DOTNET_DEBUGGING": "1"},
                ),
                Runtime.go1x.value: DebugSettings(
                    ["/var/runtime/aws-lambda-go"]
                    + debug_args_list
                    + ["-debug=true", "-delvePort=" + str(debug_port), "-delvePath=" + options.get("delvePath")],
                    debug_env_vars={},
                ),
                Runtime.nodejs10x.value: DebugSettings(
                    [
                        "/var/rapid/init",
                        "--bootstrap",
                        "/var/lang/bin/node",
                        "--bootstrap-args",
                        json.dumps(
                            debug_args_list
                            + [
                                "--inspect-brk=0.0.0.0:" + str(debug_port),
                                "--nolazy",
                                "--expose-gc",
                                "--max-http-header-size",
                                "81920",
                                "/var/runtime/index.js",
                            ]
                        ),
                    ],
                    debug_env_vars={
                        "NODE_PATH": "/opt/nodejs/node_modules:/opt/nodejs/node10/node_modules:/var/runtime/node_modules"
                    },
                ),
                Runtime.nodejs12x.value: DebugSettings(
                    [
                        "/var/rapid/init",
                        "--bootstrap",
                        "/var/lang/bin/node",
                        "--bootstrap-args",
                        json.dumps(
                            debug_args_list
                            + [
                                "--inspect-brk=0.0.0.0:" + str(debug_port),
                                "--nolazy",
                                "--expose-gc",
                                "--max-http-header-size",
                                "81920",
                                "/var/runtime/index.js",
                            ]
                        ),
                    ],
                    debug_env_vars={
                        "NODE_PATH": "/opt/nodejs/node_modules:/opt/nodejs/node12/node_modules:/var/runtime/node_modules"
                    },
                ),
                Runtime.python27.value: DebugSettings(
                    [
                        "/var/rapid/init",
                        "--bootstrap",
                        "/usr/bin/python2.7",
                        "--bootstrap-args",
                        json.dumps(debug_args_list + ["/var/runtime/awslambda/bootstrap.py"]),
                    ],
                    debug_env_vars={},
                ),
                Runtime.python36.value: DebugSettings(
                    [
                        "/var/rapid/init",
                        "--bootstrap",
                        "/var/lang/bin/python3.6",
                        "--bootstrap-args",
                        json.dumps(debug_args_list + ["/var/runtime/awslambda/bootstrap.py"]),
                    ],
                    debug_env_vars={},
                ),
                Runtime.python37.value: DebugSettings(
                    [
                        "/var/rapid/init",
                        "--bootstrap",
                        "/var/lang/bin/python3.7",
                        "--bootstrap-args",
                        json.dumps(debug_args_list + ["/var/runtime/bootstrap"]),
                    ],
                    debug_env_vars={},
                ),
                Runtime.python38.value: DebugSettings(
                    [
                        "/var/rapid/init",
                        "--bootstrap",
                        "/var/lang/bin/python3.8",
                        "--bootstrap-args",
                        json.dumps(debug_args_list + ["/var/runtime/bootstrap.py"]),
                    ],
                    debug_env_vars={},
                ),
            }
        try:
            return entrypoint_mapping[runtime]
        except KeyError as ex:
            raise DebuggingNotSupported("Debugging is not currently supported for {}".format(runtime)) from ex
Example #5
0
    def invoke(self,
               function_config,
               event,
               debug_context=None,
               stdout=None,
               stderr=None):
        """
        Invoke the given Lambda function locally.

        ##### NOTE: THIS IS A LONG BLOCKING CALL #####
        This method will block until either the Lambda function completes or timed out, which could be seconds.
        A blocking call will block the thread preventing any other operations from happening. If you are using this
        method in a web-server or in contexts where your application needs to be responsive when function is running,
        take care to invoke the function in a separate thread. Co-Routines or micro-threads might not perform well
        because the underlying implementation essentially blocks on a socket, which is synchronous.

        :param FunctionConfig function_config: Configuration of the function to invoke
        :param event: String input event passed to Lambda function
        :param DebugContext debug_context: Debugging context for the function (includes port, args, and path)
        :param io.IOBase stdout: Optional. IO Stream to that receives stdout text from container.
        :param io.IOBase stderr: Optional. IO Stream that receives stderr text from container
        :raises Keyboard
        """
        timer = None

        # Update with event input
        environ = function_config.env_vars
        environ.add_lambda_event_body(event)
        # Generate a dictionary of environment variable key:values
        env_vars = environ.resolve()

        with self._get_code_dir(function_config.code_abs_path) as code_dir:
            container = LambdaContainer(
                function_config.runtime,
                function_config.handler,
                code_dir,
                function_config.layers,
                self._image_builder,
                memory_mb=function_config.memory,
                env_vars=env_vars,
                debug_options=debug_context,
            )

            try:

                # Start the container. This call returns immediately after the container starts
                self._container_manager.run(container)

                # Setup appropriate interrupt - timeout or Ctrl+C - before function starts executing.
                #
                # Start the timer **after** container starts. Container startup takes several seconds, only after which,
                # our Lambda function code will run. Starting the timer is a reasonable approximation that function has
                # started running.
                timer = self._configure_interrupt(function_config.name,
                                                  function_config.timeout,
                                                  container,
                                                  bool(debug_context))

                # NOTE: BLOCKING METHOD
                # Block the thread waiting to fetch logs from the container. This method will return after container
                # terminates, either successfully or killed by one of the interrupt handlers above.
                if extensions_preview_enabled():
                    container.wait_for_result(name=function_config.name,
                                              event=event,
                                              stdout=stdout,
                                              stderr=stderr)
                else:
                    container.wait_for_logs(stdout=stdout, stderr=stderr)

            except KeyboardInterrupt:
                # When user presses Ctrl+C, we receive a Keyboard Interrupt. This is especially very common when
                # container is in debugging mode. We have special handling of Ctrl+C. So handle KeyboardInterrupt
                # and swallow the exception. The ``finally`` block will also take care of cleaning it up.
                LOG.debug("Ctrl+C was pressed. Aborting Lambda execution")

            finally:
                # We will be done with execution, if either the execution completed or an interrupt was fired
                # Any case, cleanup the timer and container.
                #
                # If we are in debugging mode, timer would not be created. So skip cleanup of the timer
                if timer:
                    timer.cancel()
                self._container_manager.stop(container)
Example #6
0
    def __init__(
        self,  # pylint: disable=R0914
        runtime,
        handler,
        code_dir,
        layers,
        image_builder,
        memory_mb=128,
        env_vars=None,
        debug_options=None,
    ):
        """
        Initializes the class

        Parameters
        ----------
        runtime str
            Name of the Lambda runtime
        handler str
            Handler of the function to run
        code_dir str
            Directory where the Lambda function code is present. This directory will be mounted
            to the container to execute
        layers list(str)
            List of layers
        image_builder samcli.local.docker.lambda_image.LambdaImage
            LambdaImage that can be used to build the image needed for starting the container
        memory_mb int
            Optional. Max limit of memory in MegaBytes this Lambda function can use.
        env_vars dict
            Optional. Dictionary containing environment variables passed to container
        debug_options DebugContext
            Optional. Contains container debugging info (port, debugger path)
        """

        if not Runtime.has_value(runtime):
            raise ValueError("Unsupported Lambda runtime {}".format(runtime))

        image = LambdaContainer._get_image(image_builder, runtime, layers,
                                           debug_options)
        ports = LambdaContainer._get_exposed_ports(debug_options)
        entry, debug_env_vars = LambdaContainer._get_debug_settings(
            runtime, debug_options)
        additional_options = LambdaContainer._get_additional_options(
            runtime, debug_options)
        additional_volumes = LambdaContainer._get_additional_volumes(
            runtime, debug_options)
        cmd = []
        if not extensions_preview_enabled():
            cmd = [handler]

        if not env_vars:
            env_vars = {}

        env_vars = {**env_vars, **debug_env_vars}

        super().__init__(
            image,
            cmd,
            self._WORKING_DIR,
            code_dir,
            memory_limit_mb=memory_mb,
            exposed_ports=ports,
            entrypoint=entry,
            env_vars=env_vars,
            container_opts=additional_options,
            additional_volumes=additional_volumes,
        )