Thanks to visit codestin.com
Credit goes to github.com

Skip to content

add Directories config object #5011

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions bin/docker-entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ shopt -s nullglob

if [[ ! $INIT_SCRIPTS_PATH ]]
then
# FIXME: move
INIT_SCRIPTS_PATH=/docker-entrypoint-initaws.d
fi
if [[ ! $EDGE_PORT ]]
Expand Down
134 changes: 122 additions & 12 deletions localstack/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import subprocess
import tempfile
import time
from os.path import expanduser
from typing import Dict, List, Mapping

import six
Expand All @@ -21,6 +20,7 @@
DEFAULT_PORT_EDGE,
DEFAULT_SERVICE_PORTS,
FALSE_STRINGS,
INSTALL_DIR_INFRA,
LOCALHOST,
LOCALHOST_IP,
LOG_LEVELS,
Expand All @@ -32,6 +32,118 @@
load_start_time = time.time()


class Directories:
"""
Holds the different directories available to localstack. Some directories are shared between the host and the
localstack container, some live only on the host and some only in the container.

Attributes:
static_libs: container only; binaries and libraries statically packaged with the image
var_libs: shared; binaries and libraries+data computed at runtime: lazy-loaded binaries, ssl cert, ...
cache: shared; ephemeral data that has to persist across localstack runs and reboots
tmp: shared; ephemeral data that has to persist across localstack runs but not reboots
functions: shared; volume to communicate between host<->lambda containers
data: shared; holds localstack state, pods, ...
config: host only; pre-defined configuration values, cached credentials, machine id, ...
init: shared; user-defined provisioning scripts executed in the container when it starts
logs: shared; log files produced by localstack
"""

static_libs: str
var_libs: str
cache: str
tmp: str
functions: str
data: str
config: str
init: str
logs: str

# these are the folders mounted into the container by default when the CLI is used
default_bind_mounts = ["var_libs", "cache", "tmp", "data", "init", "logs"]

def __init__(
self,
static_libs: str = None,
var_libs: str = None,
cache: str = None,
tmp: str = None,
functions: str = None,
data: str = None,
config: str = None,
init: str = None,
logs: str = None,
) -> None:
super().__init__()
self.static_libs = static_libs
self.var_libs = var_libs
self.cache = cache
self.tmp = tmp
self.functions = functions
self.data = data
self.config = config
self.init = init
self.logs = logs

@staticmethod
def from_config():
"""Returns Localstack directory paths from the config/environment variables defined by the config."""
return Directories(
static_libs=INSTALL_DIR_INFRA,
var_libs=TMP_FOLDER, # TODO: add variable
cache=TMP_FOLDER, # TODO: add variable
tmp=TMP_FOLDER, # TODO: should inherit from root value for /var/lib/localstack (e.g., MOUNT_ROOT)
functions=HOST_TMP_FOLDER, # TODO: rename variable/consider a volume
data=DATA_DIR,
config=None, # TODO: will be introduced once .localstack config file has been refactored
init=None, # TODO: introduce environment variable
logs=TMP_FOLDER, # TODO: add variable
)

@staticmethod
def for_container() -> "Directories":
"""
/var/lib/localstack. Returns Localstack directory paths as they are defined within the container. Everything
shared and writable lives in /var/lib/localstack or /tmp/localstack.
Comment on lines +106 to +107
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The directory structure with /var and /etc would be problematic when we consider moving to a more permissive user configuration (i.e. using a specific lower-priviledged / non-root user), since localstack itself would not be allowed to write there anymore. Should we move this to the home directory (of the user running localstack in host-mode, or of the localstack user in the container)? On the other hand, considering the nature of LocalStack with all its low-level services (DNS server,...) it might be really challenging to restrict its permissions while supporting the full feature-set.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i may have done a poor job communicating there: /var/ and /etc will only be used inside the container. the local mount would be, e.g. ~/.cache/localstack -> /var/lib/localstack ~/.localstack -> /etc/localstack (ro)

Copy link
Member

@alexrashed alexrashed Nov 26, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I know, I was thinking about restricting the permissions of localstack in the container itself. For example, OpenShift clusters do not allow any root uid usage (without an explicit security context constraints configured). So in the future it might be necessary to restrict localstack (also within the container) to not run as root.


:returns: Directories object
"""
return Directories(
static_libs=INSTALL_DIR_INFRA,
var_libs="/var/lib/localstack/var_libs",
cache="/var/lib/localstack/cache",
tmp=TMP_FOLDER, # TODO: move to /var/lib/localstack/tmp
functions=HOST_TMP_FOLDER, # TODO: move to /var/lib/localstack/tmp
data=DATA_DIR, # TODO: move to /var/lib/localstack/data
config="/etc/localstack", # TODO: will be introduced once .localstack config file has been refactored
logs="/var/lib/localstack/logs",
init="/docker-entrypoint-initaws.d",
)

def mkdirs(self):
for folder in [
self.static_libs,
self.var_libs,
self.cache,
self.tmp,
self.functions,
Copy link
Member

@whummer whummer Nov 27, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@thrau I'm still not 100% clear on the naming of the functions config. Currently (prior to us moving to a Docker volume), this still corresponds to HOST_TMP_FOLDER - i.e., we should probably remove it here from this list (should not get created inside the container). (Note that this config could also be something like C:\temp under Windows.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it bad that this is created in the localstack container? i'm not too familiar with the way lambdas and in particular the code mounting works, but it seems to me to make sense that the localstack container can also access the shared lambda volume?

self.data,
self.config,
self.init,
self.logs,
]:
if folder and not os.path.exists(folder):
try:
os.makedirs(folder)
except Exception:
# this can happen due to a race condition when starting
# multiple processes in parallel. Should be safe to ignore
pass

def __str__(self):
return str(self.__dict__)


def eval_log_type(env_var_name):
"""get the log type from environment variable"""
ls_log = os.environ.get(env_var_name, "").lower().strip()
Expand Down Expand Up @@ -133,16 +245,6 @@ def is_env_not_false(env_var_name):
# folder for temporary files and data
TMP_FOLDER = os.path.join(tempfile.gettempdir(), "localstack")

# create folders
for folder in [DATA_DIR, TMP_FOLDER]:
if folder and not os.path.exists(folder):
try:
os.makedirs(folder)
except Exception:
# this can happen due to a race condition when starting
# multiple processes in parallel. Should be safe to ignore
pass

# fix for Mac OS, to be able to mount /var/folders in Docker
if TMP_FOLDER.startswith("/var/folders/") and os.path.exists("/private%s" % TMP_FOLDER):
TMP_FOLDER = "/private%s" % TMP_FOLDER
Expand Down Expand Up @@ -478,7 +580,7 @@ def in_docker():
# local config file path in home directory
CONFIG_FILE_PATH = os.path.join(TMP_FOLDER, ".localstack")
if not is_in_docker:
CONFIG_FILE_PATH = os.path.join(expanduser("~"), ".localstack")
CONFIG_FILE_PATH = os.path.join(os.path.expanduser("~"), ".localstack")

# set variables no_proxy, i.e., run internal service calls directly
no_proxy = ",".join(set((LOCALSTACK_HOSTNAME, LOCALHOST, LOCALHOST_IP, "[::1]")))
Expand Down Expand Up @@ -673,3 +775,11 @@ def __iter__(self):
LOG.debug(
"Initializing the configuration took %s ms" % int((load_end_time - load_start_time) * 1000)
)

# initialize directories
if is_in_docker:
dirs = Directories.for_container()
else:
dirs = Directories.from_config()

dirs.mkdirs()
4 changes: 3 additions & 1 deletion localstack/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@
MODULE_MAIN_PATH = os.path.dirname(os.path.realpath(__file__))
# TODO rename to "ROOT_FOLDER"!
LOCALSTACK_ROOT_FOLDER = os.path.realpath(os.path.join(MODULE_MAIN_PATH, ".."))
INSTALL_DIR_INFRA = os.path.join(MODULE_MAIN_PATH, "infra")
INSTALL_DIR_INFRA = os.path.join(
MODULE_MAIN_PATH, "infra"
) # FIXME: deprecated, use config.dirs.infra

# virtualenv folder
LOCALSTACK_VENV_FOLDER = os.environ.get("VIRTUAL_ENV")
Expand Down
10 changes: 5 additions & 5 deletions localstack/contrib/thundra.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ def _init_java_agent_configs():
"https://repo.thundra.io/service/local/artifact/maven/redirect?"
+ "r=thundra-releases&g=io.thundra.agent&a=thundra-agent-lambda-bootstrap&v={v}"
).format(v=version)
THUNDRA_JAVA_AGENT_LOCAL_PATH = "%s/%s" % (config.TMP_FOLDER, jar_name)
THUNDRA_JAVA_AGENT_LOCAL_PATH = "%s/%s" % (config.dirs.tmp, jar_name)


def _install_java_agent():
Expand Down Expand Up @@ -217,11 +217,11 @@ def _init_node_agent_configs() -> bool:

THUNDRA_NODE_AGENT_VERSION = version.strip()
THUNDRA_NODE_AGENT_LOCAL_PATH = "%s/thundra/node/%s/" % (
config.TMP_FOLDER,
config.dirs.tmp,
THUNDRA_NODE_AGENT_VERSION,
)
THUNDRA_NODE_AGENT_LOCAL_PATH_ON_HOST = "%s/thundra/node/%s/" % (
config.HOST_TMP_FOLDER,
config.dirs.functions,
THUNDRA_NODE_AGENT_VERSION,
)

Expand Down Expand Up @@ -327,11 +327,11 @@ def _init_python_agent_configs() -> bool:

THUNDRA_PYTHON_AGENT_VERSION = version.strip()
THUNDRA_PYTHON_AGENT_LOCAL_PATH = "%s/thundra/python/%s/" % (
config.TMP_FOLDER,
config.dirs.tmp,
THUNDRA_PYTHON_AGENT_VERSION,
)
THUNDRA_PYTHON_AGENT_LOCAL_PATH_ON_HOST = "%s/thundra/python/%s/" % (
config.HOST_TMP_FOLDER,
config.dirs.functions,
THUNDRA_PYTHON_AGENT_VERSION,
)

Expand Down
6 changes: 3 additions & 3 deletions localstack/services/awslambda/lambda_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@
LAMBDA_POLICY_NAME_PATTERN = "lambda_policy_{name}_{qualifier}"
# constants
APP_NAME = "lambda_api"
ARCHIVE_FILE_PATTERN = "%s/lambda.handler.*.jar" % config.TMP_FOLDER
LAMBDA_SCRIPT_PATTERN = "%s/lambda_script_*.py" % config.TMP_FOLDER
ARCHIVE_FILE_PATTERN = "%s/lambda.handler.*.jar" % config.dirs.tmp
LAMBDA_SCRIPT_PATTERN = "%s/lambda_script_*.py" % config.dirs.tmp
LAMBDA_ZIP_FILE_NAME = "original_lambda_archive.zip"
LAMBDA_JAR_FILE_NAME = "original_lambda_archive.jar"

Expand Down Expand Up @@ -998,7 +998,7 @@ def set_archive_code(code, lambda_name, zip_file_content=None):
latest_version = lambda_details.get_version(VERSION_LATEST)
latest_version["CodeSize"] = len(zip_file_content)
latest_version["CodeSha256"] = code_sha_256.decode("utf-8")
tmp_dir = "%s/zipfile.%s" % (config.TMP_FOLDER, short_uid())
tmp_dir = "%s/zipfile.%s" % (config.dirs.tmp, short_uid())
mkdir(tmp_dir)
tmp_file = "%s/%s" % (tmp_dir, LAMBDA_ZIP_FILE_NAME)
save_file(tmp_file, zip_file_content)
Expand Down
6 changes: 3 additions & 3 deletions localstack/services/awslambda/lambda_executors.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
LAMBDA_EXECUTOR_JAR = INSTALL_PATH_LOCALSTACK_FAT_JAR
LAMBDA_EXECUTOR_CLASS = "cloud.localstack.LambdaExecutor"
LAMBDA_HANDLER_ENV_VAR_NAME = "_HANDLER"
EVENT_FILE_PATTERN = "%s/lambda.event.*.json" % config.TMP_FOLDER
EVENT_FILE_PATTERN = "%s/lambda.event.*.json" % config.dirs.tmp

LAMBDA_SERVER_UNIQUE_PORTS = 500
LAMBDA_SERVER_PORT_OFFSET = 5000
Expand Down Expand Up @@ -1484,7 +1484,7 @@ def get_java_opts(cls):

@classmethod
def get_host_path_for_path_in_docker(cls, path):
return re.sub(r"^%s/(.*)$" % config.TMP_FOLDER, r"%s/\1" % config.HOST_TMP_FOLDER, path)
return re.sub(r"^%s/(.*)$" % config.dirs.tmp, r"%s/\1" % config.dirs.functions, path)

@classmethod
def format_windows_path(cls, path):
Expand Down Expand Up @@ -1540,7 +1540,7 @@ def get_java_classpath(cls, archive):

@staticmethod
def mountable_tmp_file():
f = os.path.join(config.TMP_FOLDER, short_uid())
f = os.path.join(config.dirs.tmp, short_uid())
TMP_FILES.append(f)
return f

Expand Down
4 changes: 2 additions & 2 deletions localstack/services/cloudformation/deployment_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import os
from typing import Callable

from localstack.constants import INSTALL_DIR_INFRA
from localstack.config import dirs
from localstack.utils import common

# URL to "cfn-response" module which is required in some CF Lambdas
Expand Down Expand Up @@ -119,7 +119,7 @@ def _convert(params, **kwargs):


def get_cfn_response_mod_file():
cfn_response_tmp_file = os.path.join(INSTALL_DIR_INFRA, "lambda.cfn-response.js")
cfn_response_tmp_file = os.path.join(dirs.static_libs, "lambda.cfn-response.js")
if not os.path.exists(cfn_response_tmp_file):
common.download(CFN_RESPONSE_MODULE_URL, cfn_response_tmp_file)
return cfn_response_tmp_file
Expand Down
11 changes: 5 additions & 6 deletions localstack/services/dynamodb/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
from typing import List, Optional

from localstack import config
from localstack.config import is_env_true
from localstack.constants import MODULE_MAIN_PATH
from localstack.config import dirs, is_env_true
from localstack.services import install
from localstack.utils.common import TMP_THREADS, ShellCommandThread, get_free_tcp_port, mkdir
from localstack.utils.run import FuncThread
Expand Down Expand Up @@ -39,11 +38,11 @@ def in_memory(self):

@property
def jar_path(self) -> str:
return f"{MODULE_MAIN_PATH}/infra/dynamodb/DynamoDBLocal.jar"
return f"{dirs.static_libs}/dynamodb/DynamoDBLocal.jar"

@property
def library_path(self) -> str:
return f"{MODULE_MAIN_PATH}/infra/dynamodb/DynamoDBLocal_lib"
return f"{dirs.static_libs}/dynamodb/DynamoDBLocal_lib"

def _create_shell_command(self) -> List[str]:
cmd = [
Expand Down Expand Up @@ -96,8 +95,8 @@ def create_dynamodb_server(port=None) -> DynamodbServer:

server = DynamodbServer(port)

if config.DATA_DIR:
ddb_data_dir = "%s/dynamodb" % config.DATA_DIR
if config.dirs.data:
ddb_data_dir = "%s/dynamodb" % config.dirs.data
mkdir(ddb_data_dir)
absolute_path = os.path.abspath(ddb_data_dir)
server.db_path = absolute_path
Expand Down
8 changes: 4 additions & 4 deletions localstack/services/es/cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,10 @@ def resolve_directories(version: str, cluster_path: str, data_root: str = None)
modules_dir = os.path.join(install_dir, "modules")

if data_root is None:
if config.DATA_DIR:
data_root = config.DATA_DIR
if config.dirs.data:
data_root = config.dirs.data
else:
data_root = config.TMP_FOLDER
data_root = config.dirs.tmp

data_path = os.path.join(data_root, "elasticsearch", cluster_path)

Expand All @@ -106,7 +106,7 @@ def init_directories(dirs: Directories):
LOG.debug("initializing elasticsearch directories %s", dirs)
chmod_r(dirs.install, 0o777)

if not dirs.data.startswith(config.DATA_DIR):
if not dirs.data.startswith(config.dirs.data):
# only clear previous data if it's not in DATA_DIR
rm_rf(dirs.data)

Expand Down
2 changes: 1 addition & 1 deletion localstack/services/events/events_listener.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def _dump_events_to_files(events_with_added_uuid):


def _get_events_tmp_dir():
return os.path.join(config.TMP_FOLDER, EVENTS_TMP_DIR)
return os.path.join(config.dirs.tmp, EVENTS_TMP_DIR)


def get_scheduled_rule_func(data):
Expand Down
2 changes: 1 addition & 1 deletion localstack/services/generic_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -641,7 +641,7 @@ async def _accept_connection2(self, protocol_factory, conn, extra, sslcontext, *


def get_cert_pem_file_path():
return os.path.join(config.TMP_FOLDER, SERVER_CERT_PEM_FILE)
return os.path.join(config.dirs.tmp, SERVER_CERT_PEM_FILE)


def start_proxy_server(
Expand Down
8 changes: 2 additions & 6 deletions localstack/services/infra.py
Original file line number Diff line number Diff line change
Expand Up @@ -378,11 +378,7 @@ def start_infra(asynchronous=False, apis=None):
% config.LAMBDA_EXECUTOR
)

if (
is_in_docker
and not config.LAMBDA_REMOTE_DOCKER
and not os.environ.get("HOST_TMP_FOLDER")
):
if is_in_docker and not config.LAMBDA_REMOTE_DOCKER and not config.dirs.functions:
print(
"!WARNING! - Looks like you have configured $LAMBDA_REMOTE_DOCKER=0 - "
"please make sure to configure $HOST_TMP_FOLDER to point to your host's $TMPDIR"
Expand Down Expand Up @@ -513,7 +509,7 @@ def start_runtime_components():
thread = start_runtime_components()
preload_services()

if config.DATA_DIR:
if config.dirs.data:
persistence.save_startup_info()

print(READY_MARKER_OUTPUT)
Expand Down
Loading