def run(self):
     self.process = run(self.cmd, asynchronous=True, shell=False)
def start_infra_in_docker():
    container_name = config.MAIN_CONTAINER_NAME

    if DOCKER_CLIENT.is_container_running(container_name):
        raise Exception('LocalStack container named "%s" is already running' %
                        container_name)
    if config.TMP_FOLDER != config.HOST_TMP_FOLDER and not config.LAMBDA_REMOTE_DOCKER:
        print(
            f"WARNING: The detected temp folder for localstack ({config.TMP_FOLDER}) is not equal to the "
            f"HOST_TMP_FOLDER environment variable set ({config.HOST_TMP_FOLDER})."
        )  # Logger is not initialized at this point, so the warning is displayed via print

    os.environ[ENV_SCRIPT_STARTING_DOCKER] = "1"

    # load plugins before starting the docker container
    plugin_configs = load_plugins()

    # prepare APIs
    canonicalize_api_names()

    entrypoint = os.environ.get("ENTRYPOINT", "")
    cmd = os.environ.get("CMD", "")
    user_flags = config.DOCKER_FLAGS
    image_name = get_docker_image_to_start()
    service_ports = config.SERVICE_PORTS
    force_noninteractive = os.environ.get("FORCE_NONINTERACTIVE", "")

    # get run params
    plugin_run_params = " ".join([
        entry.get("docker", {}).get("run_flags", "")
        for entry in plugin_configs
    ])

    # container for port mappings
    port_mappings = PortMappings(bind_host=config.EDGE_BIND_HOST)

    # get port ranges defined via DOCKER_FLAGS (if any)
    user_flags = extract_port_flags(user_flags, port_mappings)
    plugin_run_params = extract_port_flags(plugin_run_params, port_mappings)

    # construct default port mappings
    if service_ports.get("edge") == 0:
        service_ports.pop("edge")
    for port in service_ports.values():
        if port:
            port_mappings.add(port)

    env_vars = {}
    for env_var in config.CONFIG_ENV_VARS:
        value = os.environ.get(env_var, None)
        if value is not None:
            env_vars[env_var] = value

    bind_mounts = []
    data_dir = os.environ.get("DATA_DIR", None)
    if data_dir is not None:
        container_data_dir = "/tmp/localstack_data"
        bind_mounts.append((data_dir, container_data_dir))
        env_vars["DATA_DIR"] = container_data_dir
    bind_mounts.append((config.TMP_FOLDER, "/tmp/localstack"))
    bind_mounts.append((config.DOCKER_SOCK, config.DOCKER_SOCK))
    env_vars["DOCKER_HOST"] = f"unix://{config.DOCKER_SOCK}"
    env_vars["HOST_TMP_FOLDER"] = config.HOST_TMP_FOLDER

    if config.DEVELOP:
        port_mappings.add(config.DEVELOP_PORT)

    docker_cmd = [config.DOCKER_CMD, "run"]
    if not force_noninteractive and not in_ci():
        docker_cmd.append("-it")
    if entrypoint:
        docker_cmd += shlex.split(entrypoint)
    if env_vars:
        docker_cmd += [
            item for k, v in env_vars.items()
            for item in ["-e", "{}={}".format(k, v)]
        ]
    if user_flags:
        docker_cmd += shlex.split(user_flags)
    if plugin_run_params:
        docker_cmd += shlex.split(plugin_run_params)
    docker_cmd += ["--rm", "--privileged"]
    docker_cmd += ["--name", container_name]
    docker_cmd += port_mappings.to_list()
    docker_cmd += [
        volume for host_path, docker_path in bind_mounts
        for volume in ["-v", f"{host_path}:{docker_path}"]
    ]
    docker_cmd.append(image_name)
    docker_cmd += shlex.split(cmd)

    mkdir(config.TMP_FOLDER)
    try:
        run(["chmod", "-R", "777", config.TMP_FOLDER],
            print_error=False,
            shell=False)
    except Exception:
        pass

    class ShellRunnerThread(threading.Thread):
        def __init__(self, cmd):
            threading.Thread.__init__(self)
            self.daemon = True
            self.cmd = cmd

        def run(self):
            self.process = run(self.cmd, asynchronous=True, shell=False)

    # keep this print output here for debugging purposes
    print(docker_cmd)
    t = ShellRunnerThread(docker_cmd)
    t.start()
    time.sleep(2)

    if DO_CHMOD_DOCKER_SOCK:
        # fix permissions on /var/run/docker.sock
        for i in range(0, 100):
            if DOCKER_CLIENT.is_container_running(container_name):
                break
            time.sleep(2)
        DOCKER_CLIENT.exec_in_container(
            container_name,
            command=["chmod", "777", "/var/run/docker.sock"],
            user="******")

    t.process.wait()
    sys.exit(t.process.returncode)
def validate_localstack_config(name):
    # TODO: separate functionality from CLI output
    #  (use exceptions to communicate errors, and return list of warnings)
    from subprocess import CalledProcessError

    from localstack.cli import console

    dirname = os.getcwd()
    compose_file_name = name if os.path.isabs(name) else os.path.join(
        dirname, name)
    warns = []

    # validating docker-compose file
    cmd = ["docker-compose", "-f", compose_file_name, "config"]
    try:
        run(cmd, shell=False, print_error=False)
    except CalledProcessError as e:
        msg = f"{e}\n{to_str(e.output)}".strip()
        raise ValueError(msg)

    # validating docker-compose variable
    import yaml  # keep import here to avoid issues in test Lambdas

    with open(compose_file_name) as file:
        compose_content = yaml.full_load(file)
    services_config = compose_content.get("services", {})
    ls_service_name = [
        name for name, svc in services_config.items()
        if "localstack" in svc.get("image", "")
    ]
    if not ls_service_name:
        raise Exception(
            'No LocalStack service found in config (looking for image names containing "localstack")'
        )
    if len(ls_service_name) > 1:
        warns.append(
            f"Multiple candidates found for LocalStack service: {ls_service_name}"
        )
    ls_service_name = ls_service_name[0]
    ls_service_details = services_config[ls_service_name]
    image_name = ls_service_details.get("image", "")
    if image_name.split(":")[0] not in constants.OFFICIAL_IMAGES:
        warns.append(
            'Using custom image "%s", we recommend using an official image: %s'
            % (image_name, constants.OFFICIAL_IMAGES))

    # prepare config options
    network_mode = ls_service_details.get("network_mode")
    image_name = ls_service_details.get("image")
    container_name = ls_service_details.get("container_name") or ""
    docker_ports = (port.split(":")[-2]
                    for port in ls_service_details.get("ports", []))
    docker_env = dict((env.split("=")[0], env.split("=")[1])
                      for env in ls_service_details.get("environment", {}))
    edge_port = str(docker_env.get("EDGE_PORT") or config.EDGE_PORT)
    main_container = config.MAIN_CONTAINER_NAME

    # docker-compose file validation cases

    if (docker_env.get("PORT_WEB_UI") not in ["${PORT_WEB_UI- }", None, ""]
            and image_name == "localstack/localstack"):
        warns.append(
            '"PORT_WEB_UI" Web UI is now deprecated, '
            'and requires to use the "localstack/localstack-full" image.')

    if not docker_env.get("HOST_TMP_FOLDER"):
        warns.append(
            'Please configure the "HOST_TMP_FOLDER" environment variable to point to the '
            +
            "absolute path of a temp folder on your host system (e.g., HOST_TMP_FOLDER=${TMPDIR})"
        )

    if (main_container not in container_name
        ) and not docker_env.get("MAIN_CONTAINER_NAME"):
        warns.append(
            'Please use "container_name: %s" or add "MAIN_CONTAINER_NAME" in "environment".'
            % main_container)

    def port_exposed(port):
        for exposed in docker_ports:
            if re.match(r"^([0-9]+-)?%s(-[0-9]+)?$" % port, exposed):
                return True

    if not port_exposed(edge_port):
        warns.append(
            ("Edge port %s is not exposed. You may have to add the entry "
             'to the "ports" section of the docker-compose file.') % edge_port)

    if network_mode != "bridge" and not docker_env.get(
            "LAMBDA_DOCKER_NETWORK"):
        warns.append(
            'Network mode is not set to "bridge" which may cause networking issues in Lambda containers. '
            'Consider adding "network_mode: bridge" to your docker-compose file, or configure '
            "LAMBDA_DOCKER_NETWORK with the name of the Docker network of your compose stack."
        )

    # print warning/info messages
    for warning in warns:
        console.print("[yellow]:warning:[/yellow]", warning)
    if not warns:
        return True
    return False
Exemple #4
0
def add_file_to_jar(class_file, class_url, target_jar, base_dir=None):
    base_dir = base_dir or os.path.dirname(target_jar)
    patch_class_file = os.path.join(base_dir, class_file)
    if not os.path.exists(patch_class_file):
        download(class_url, patch_class_file)
        run(["zip", target_jar, class_file], cwd=base_dir)
Exemple #5
0
def install_stepfunctions_local():
    if not os.path.exists(INSTALL_PATH_STEPFUNCTIONS_JAR):
        # pull the JAR file from the Docker image, which is more up-to-date than the downloadable JAR file
        if not DOCKER_CLIENT.has_docker():
            # TODO: works only when a docker socket is available -> add a fallback if running without Docker?
            LOG.warning(
                "Docker not available - skipping installation of StepFunctions dependency"
            )
            return
        log_install_msg("Step Functions")
        mkdir(INSTALL_DIR_STEPFUNCTIONS)
        DOCKER_CLIENT.pull_image(IMAGE_NAME_SFN_LOCAL)
        docker_name = "tmp-ls-sfn"
        DOCKER_CLIENT.run_container(
            IMAGE_NAME_SFN_LOCAL,
            remove=True,
            entrypoint="",
            name=docker_name,
            detach=True,
            command=["sleep", "15"],
        )
        time.sleep(5)
        DOCKER_CLIENT.copy_from_container(
            docker_name,
            local_path=dirs.static_libs,
            container_path="/home/stepfunctionslocal/")

        path = Path(f"{dirs.static_libs}/stepfunctionslocal/")
        for file in path.glob("*.jar"):
            file.rename(Path(INSTALL_DIR_STEPFUNCTIONS) / file.name)
        rm_rf(str(path))

    classes = [
        SFN_PATCH_CLASS1,
        SFN_PATCH_CLASS2,
        SFN_PATCH_CLASS_REGION,
        SFN_PATCH_CLASS_STARTER,
        SFN_PATCH_CLASS_ASYNC2SERVICEAPI,
        SFN_PATCH_CLASS_DESCRIBEEXECUTIONPARSED,
        SFN_PATCH_FILE_METAINF,
    ]
    for patch_class in classes:
        patch_url = f"{SFN_PATCH_URL_PREFIX}/{patch_class}"
        add_file_to_jar(patch_class,
                        patch_url,
                        target_jar=INSTALL_PATH_STEPFUNCTIONS_JAR)

    # special case for Manifest file - extract first, replace content, then update in JAR file
    manifest_file = os.path.join(INSTALL_DIR_STEPFUNCTIONS, "META-INF",
                                 "MANIFEST.MF")
    if not os.path.exists(manifest_file):
        content = run([
            "unzip", "-p", INSTALL_PATH_STEPFUNCTIONS_JAR,
            "META-INF/MANIFEST.MF"
        ])
        content = re.sub("Main-Class: .+",
                         "Main-Class: cloud.localstack.StepFunctionsStarter",
                         content)
        classpath = " ".join([os.path.basename(jar) for jar in JAR_URLS])
        content = re.sub(r"Class-Path: \. ", f"Class-Path: {classpath} . ",
                         content)
        save_file(manifest_file, content)
        run(
            ["zip", INSTALL_PATH_STEPFUNCTIONS_JAR, "META-INF/MANIFEST.MF"],
            cwd=INSTALL_DIR_STEPFUNCTIONS,
        )

    # download additional jar libs
    for jar_url in JAR_URLS:
        target = os.path.join(INSTALL_DIR_STEPFUNCTIONS,
                              os.path.basename(jar_url))
        if not file_exists_not_empty(target):
            download(jar_url, target)

    # download aws-sdk lambda handler
    target = os.path.join(INSTALL_DIR_STEPFUNCTIONS,
                          "localstack-internal-awssdk", "awssdk.zip")
    if not file_exists_not_empty(target):
        download(SFN_AWS_SDK_LAMBDA_ZIP_FILE, target)
Exemple #6
0
 def try_install():
     output = run([plugin_binary, "install", "-b", plugin])
     LOG.debug("Plugin installation output: %s", output)