feat: add execute task, rework Dockerfile to allow podman run rootless

This commit is contained in:
Stefan Reimer 2022-06-27 20:51:03 +02:00
parent c7b0daab22
commit b32de905a4
9 changed files with 309 additions and 190 deletions

View File

@ -39,9 +39,11 @@ RUN pip install . --no-deps
RUN cd /root/.pulumi/bin && rm -f *dotnet *nodejs *go *java && strip pulumi* || true RUN cd /root/.pulumi/bin && rm -f *dotnet *nodejs *go *java && strip pulumi* || true
# Now build the final runtime # Now build the final runtime, incl. running rootless containers
FROM python:${RUNTIME_VERSION}-alpine${DISTRO_VERSION} FROM python:${RUNTIME_VERSION}-alpine${DISTRO_VERSION}
ARG USER=cloudbender
#cd /etc/apk/keys && \ #cd /etc/apk/keys && \
#echo "@testing http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories && \ #echo "@testing http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories && \
#cfssl@testing \ #cfssl@testing \
@ -52,20 +54,46 @@ RUN apk upgrade -U --available --no-cache && \
libc6-compat \ libc6-compat \
ca-certificates \ ca-certificates \
aws-cli \ aws-cli \
podman fuse-overlayfs \
podman \
buildah \
strace
COPY --from=builder /venv /venv COPY --from=builder /venv /venv
COPY --from=builder /root/.pulumi/bin /usr/local/bin COPY --from=builder /root/.pulumi/bin /usr/local/bin
RUN mkdir /workspace && \
# Dont run as root by default
RUN addgroup $USER && adduser $USER -G $USER -D && \
mkdir -p /home/$USER/.local/share/containers && \
chown $USER:$USER -R /home/$USER
# Rootless podman
# https://github.com/containers/podman/blob/main/contrib/podmanimage/stable/Containerfile
ADD conf/containers.conf conf/registries.conf conf/storage.conf /etc/containers/
ADD --chown=$USER:$USER conf/podman-containers.conf /home/$USER/.config/containers/containers.conf
RUN mkdir -p /var/lib/shared/overlay-images /var/lib/shared/overlay-layers \
/var/lib/shared/vfs-images /var/lib/shared/vfs-layers && \
touch /var/lib/shared/overlay-images/images.lock /var/lib/shared/overlay-layers/layers.lock \
/var/lib/shared/vfs-images/images.lock /var/lib/shared/vfs-layers/layers.lock && \
mkdir /tmp/podman-run-1000 && chown $USER:$USER /tmp/podman-run-1000 && chmod 700 /tmp/podman-run-1000 && \
echo -e "$USER:1:999\n$USER:1001:64535" > /etc/subuid && \
echo -e "$USER:1:999\n$USER:1001:64535" > /etc/subgid && \
mkdir /workspace && \
cd /usr/bin && ln -s podman docker cd /usr/bin && ln -s podman docker
WORKDIR /workspace WORKDIR /workspace
ENV XDG_RUNTIME_DIR=/tmp/podman-run-1000
ENV _CONTAINERS_USERNS_CONFIGURED=""
ENV BUILDAH_ISOLATION=chroot
ENV VIRTUAL_ENV=/venv ENV VIRTUAL_ENV=/venv
ENV PATH="$VIRTUAL_ENV/bin:$PATH" ENV PATH="$VIRTUAL_ENV/bin:$PATH"
# Dont run as root by default USER $USER
RUN addgroup cloudbender && adduser cloudbender -G cloudbender -D
USER cloudbender # Allow container layers to be stored in PVCs
VOLUME /home/$USER/.local/share/containers
CMD ["cloudbender"] CMD ["cloudbender"]

View File

@ -152,8 +152,30 @@ def refresh(cb, stack_name):
@click.command() @click.command()
@click.argument("stack_name") @click.argument("stack_name")
@click.argument("function", default="")
@click.argument('args', nargs=-1)
@click.option( @click.option(
"-r", "--remove-pending-operations", "--listall",
is_flag=True,
help="List all available execute functions for this stack",
)
@click.pass_obj
def execute(cb, stack_name, function, args, listall=False):
"""Executes custom Python function within an existing stack context"""
stacks = _find_stacks(cb, [stack_name])
for s in stacks:
if s.mode == "pulumi":
s.execute(function, args, listall)
else:
logger.info("{} uses Cloudformation, no exec feature available.".format(s.stackname))
@click.command()
@click.argument("stack_name")
@click.option(
"-r",
"--remove-pending-operations",
is_flag=True, is_flag=True,
help="All pending stack operations are removed and the stack will be re-imported", help="All pending stack operations are removed and the stack will be re-imported",
) )
@ -408,6 +430,7 @@ cli.add_command(set_config)
cli.add_command(get_config) cli.add_command(get_config)
cli.add_command(export) cli.add_command(export)
cli.add_command(assimilate) cli.add_command(assimilate)
cli.add_command(execute)
if __name__ == "__main__": if __name__ == "__main__":
cli(obj={}) cli(obj={})

View File

@ -1,8 +1,5 @@
import os
import sys import sys
import subprocess import subprocess
import tempfile
import shutil
from functools import wraps from functools import wraps
from .exceptions import InvalidHook from .exceptions import InvalidHook
@ -37,25 +34,6 @@ def exec_hooks(func):
return decorated return decorated
def pulumi_ws(func):
@wraps(func)
def decorated(self, *args, **kwargs):
# setup temp workspace
self.work_dir = tempfile.mkdtemp(
dir=tempfile.gettempdir(), prefix="cloudbender-"
)
response = func(self, *args, **kwargs)
# Cleanup temp workspace
if os.path.exists(self.work_dir):
shutil.rmtree(self.work_dir)
return response
return decorated
# Various hooks # Various hooks
def cmd(stack, arguments): def cmd(stack, arguments):
""" """

View File

@ -2,25 +2,33 @@ import sys
import os import os
import re import re
import shutil import shutil
import tempfile
import importlib import importlib
import pkg_resources
import pulumi import pulumi
from functools import wraps
import logging import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def pulumi_init(stack, create=False):
# Fail early if pulumi binaries are not available # Fail early if pulumi binaries are not available
if not shutil.which("pulumi"): if not shutil.which("pulumi"):
raise FileNotFoundError( raise FileNotFoundError(
"Cannot find pulumi binary, see https://www.pulumi.com/docs/get-started/install/" "Cannot find pulumi binary, see https://www.pulumi.com/docs/get-started/install/"
) )
def pulumi_ws(func):
@wraps(func)
def decorated(self, *args, **kwargs):
# setup temp workspace
if self.mode == "pulumi":
self.work_dir = tempfile.mkdtemp(
dir=tempfile.gettempdir(), prefix="cloudbender-"
)
# add all artifact_paths/pulumi to the search path for easier imports in the pulumi code # add all artifact_paths/pulumi to the search path for easier imports in the pulumi code
for artifacts_path in stack.ctx["artifact_paths"]: for artifacts_path in self.ctx["artifact_paths"]:
_path = "{}/pulumi".format(artifacts_path.resolve()) _path = "{}/pulumi".format(artifacts_path.resolve())
sys.path.append(_path) sys.path.append(_path)
@ -28,16 +36,16 @@ def pulumi_init(stack, create=False):
_found = False _found = False
try: try:
_stack = importlib.import_module( _stack = importlib.import_module(
"config.{}.{}".format(stack.rel_path, stack.template).replace("/", ".") "config.{}.{}".format(self.rel_path, self.template).replace("/", ".")
) )
_found = True _found = True
except ImportError: except ImportError:
for artifacts_path in stack.ctx["artifact_paths"]: for artifacts_path in self.ctx["artifact_paths"]:
try: try:
spec = importlib.util.spec_from_file_location( spec = importlib.util.spec_from_file_location(
"_stack", "_stack",
"{}/pulumi/{}.py".format(artifacts_path.resolve(), stack.template), "{}/pulumi/{}.py".format(artifacts_path.resolve(), self.template),
) )
_stack = importlib.util.module_from_spec(spec) _stack = importlib.util.module_from_spec(spec)
spec.loader.exec_module(_stack) spec.loader.exec_module(_stack)
@ -48,54 +56,57 @@ def pulumi_init(stack, create=False):
if not _found: if not _found:
raise FileNotFoundError( raise FileNotFoundError(
"Cannot find Pulumi implementation for {}".format(stack.stackname) "Cannot find Pulumi implementation for {}".format(self.stackname)
) )
# Store internal pulumi code reference # Store internal pulumi code reference
stack._pulumi_code = _stack self._pulumi_code = _stack
project_name = stack.parameters["Conglomerate"]
# Use legacy Conglomerate as Pulumi project_name
project_name = self.parameters["Conglomerate"]
# Remove stacknameprefix if equals Conglomerate as Pulumi implicitly prefixes project_name # Remove stacknameprefix if equals Conglomerate as Pulumi implicitly prefixes project_name
pulumi_stackname = re.sub(r"^" + project_name + "-?", "", stack.stackname) self.pulumi_stackname = re.sub(r"^" + project_name + "-?", "", self.stackname)
try: try:
pulumi_backend = "{}/{}/{}".format( pulumi_backend = "{}/{}/{}".format(
stack.pulumi["backend"], project_name, stack.region self.pulumi["backend"], project_name, self.region
) )
except KeyError: except KeyError:
raise KeyError("Missing pulumi.backend setting !") raise KeyError("Missing pulumi.backend setting !")
account_id = stack.connection_manager.call( account_id = self.connection_manager.call(
"sts", "get_caller_identity", profile=stack.profile, region=stack.region "sts", "get_caller_identity", profile=self.profile, region=self.region
)["Account"] )["Account"]
# Ugly hack as Pulumi currently doesnt support MFA_TOKENs during role assumptions # Ugly hack as Pulumi currently doesnt support MFA_TOKENs during role assumptions
# Do NOT set them via 'aws:secretKey' as they end up in the stack.json in plain text !!! # Do NOT set them via 'aws:secretKey' as they end up in the self.json in plain text !!!
if ( if (
stack.connection_manager._sessions[(stack.profile, stack.region)] self.connection_manager._sessions[(self.profile, self.region)]
.get_credentials() .get_credentials()
.token .token
): ):
os.environ["AWS_SESSION_TOKEN"] = ( os.environ["AWS_SESSION_TOKEN"] = (
stack.connection_manager._sessions[(stack.profile, stack.region)] self.connection_manager._sessions[(self.profile, self.region)]
.get_credentials() .get_credentials()
.token .token
) )
os.environ["AWS_ACCESS_KEY_ID"] = ( os.environ["AWS_ACCESS_KEY_ID"] = (
stack.connection_manager._sessions[(stack.profile, stack.region)] self.connection_manager._sessions[(self.profile, self.region)]
.get_credentials() .get_credentials()
.access_key .access_key
) )
os.environ["AWS_SECRET_ACCESS_KEY"] = ( os.environ["AWS_SECRET_ACCESS_KEY"] = (
stack.connection_manager._sessions[(stack.profile, stack.region)] self.connection_manager._sessions[(self.profile, self.region)]
.get_credentials() .get_credentials()
.secret_key .secret_key
) )
os.environ["AWS_DEFAULT_REGION"] = stack.region os.environ["AWS_DEFAULT_REGION"] = self.region
# Secrets provider # Secrets provider
if "secretsProvider" in stack.pulumi: if "secretsProvider" in self.pulumi:
secrets_provider = stack.pulumi["secretsProvider"] secrets_provider = self.pulumi["secretsProvider"]
if ( if (
secrets_provider == "passphrase" secrets_provider == "passphrase"
and "PULUMI_CONFIG_PASSPHRASE" not in os.environ and "PULUMI_CONFIG_PASSPHRASE" not in os.environ
@ -104,7 +115,7 @@ def pulumi_init(stack, create=False):
else: else:
try: try:
if stack._pulumi_code.IKNOWHATIDO: if self._pulumi_code.IKNOWHATIDO:
logger.warning( logger.warning(
"Missing pulumi.secretsProvider setting, IKNOWHATIDO enabled ... " "Missing pulumi.secretsProvider setting, IKNOWHATIDO enabled ... "
) )
@ -115,66 +126,55 @@ def pulumi_init(stack, create=False):
# Set tag for stack file name and version # Set tag for stack file name and version
_tags = {} _tags = {}
try: try:
_version = stack._pulumi_code.VERSION _version = self._pulumi_code.VERSION
except AttributeError: except AttributeError:
_version = "undefined" _version = "undefined"
# Tag all resources with our metadata, allowing "prune" eventually # Tag all resources with our metadata, allowing "prune" eventually
_tags["zdt:cloudbender.source"] = "{}:{}".format( _tags["zdt:cloudbender.source"] = "{}:{}".format(
os.path.basename(stack._pulumi_code.__file__), _version os.path.basename(self._pulumi_code.__file__), _version
) )
_tags["zdt:cloudbender.owner"] = f"{project_name}.{pulumi_stackname}" _tags["zdt:cloudbender.owner"] = f"{project_name}.{self.pulumi_stackname}"
_config = { self.pulumi_config.update({
"aws:region": stack.region, "aws:region": self.region,
"aws:defaultTags": {"tags": _tags}, "aws:defaultTags": {"tags": _tags},
"zdt:region": stack.region, "zdt:region": self.region,
"zdt:awsAccountId": account_id, "zdt:awsAccountId": account_id,
"zdt:projectName": project_name, "zdt:projectName": project_name,
"zdt:stackName": pulumi_stackname "zdt:stackName": self.pulumi_stackname
} })
# inject all parameters as config in the <Conglomerate> namespace # inject all parameters as config in the <Conglomerate> namespace
for p in stack.parameters: for p in self.parameters:
_config["{}:{}".format(stack.parameters["Conglomerate"], p)] = stack.parameters[ self.pulumi_config["{}:{}".format(self.parameters["Conglomerate"], p)] = self.parameters[
p p
] ]
stack_settings = pulumi.automation.StackSettings( stack_settings = pulumi.automation.StackSettings(
config=_config, config=self.pulumi_config,
secrets_provider=secrets_provider, secrets_provider=secrets_provider,
encryption_salt=stack.pulumi.get("encryptionsalt", None), encryption_salt=self.pulumi.get("encryptionsalt", None),
encrypted_key=stack.pulumi.get("encryptedkey", None), encrypted_key=self.pulumi.get("encryptedkey", None),
) )
project_settings = pulumi.automation.ProjectSettings( project_settings = pulumi.automation.ProjectSettings(
name=project_name, runtime="python", backend={"url": pulumi_backend} name=project_name, runtime="python", backend={"url": pulumi_backend}
) )
ws_opts = pulumi.automation.LocalWorkspaceOptions( self.pulumi_ws_opts = pulumi.automation.LocalWorkspaceOptions(
work_dir=stack.work_dir, work_dir=self.work_dir,
project_settings=project_settings, project_settings=project_settings,
stack_settings={pulumi_stackname: stack_settings}, stack_settings={self.pulumi_stackname: stack_settings},
secrets_provider=secrets_provider, secrets_provider=secrets_provider,
) )
if create: response = func(self, *args, **kwargs)
pulumi_stack = pulumi.automation.create_or_select_stack(
stack_name=pulumi_stackname,
project_name=project_name,
program=stack._pulumi_code.pulumi_program,
opts=ws_opts,
)
pulumi_stack.workspace.install_plugin(
"aws", pkg_resources.get_distribution("pulumi_aws").version
)
else: # Cleanup temp workspace
pulumi_stack = pulumi.automation.select_stack( if self.work_dir and os.path.exists(self.work_dir):
stack_name=pulumi_stackname, shutil.rmtree(self.work_dir)
project_name=project_name,
program=stack._pulumi_code.pulumi_program,
opts=ws_opts,
)
return pulumi_stack return response
return decorated

View File

@ -7,6 +7,7 @@ import time
import pathlib import pathlib
import pprint import pprint
import pulumi import pulumi
import pkg_resources
from datetime import datetime, timedelta from datetime import datetime, timedelta
from dateutil.tz import tzutc from dateutil.tz import tzutc
@ -18,14 +19,13 @@ from .connection import BotoConnection
from .jinja import JinjaEnv, read_config_file from .jinja import JinjaEnv, read_config_file
from . import __version__ from . import __version__
from .exceptions import ParameterNotFound, ParameterIllegalValue, ChecksumError from .exceptions import ParameterNotFound, ParameterIllegalValue, ChecksumError
from .hooks import exec_hooks, pulumi_ws from .hooks import exec_hooks
from .pulumi import pulumi_init from .pulumi import pulumi_ws
import cfnlint.core import cfnlint.core
import cfnlint.template import cfnlint.template
import cfnlint.graph import cfnlint.graph
import importlib.resources as pkg_resources
from . import templates from . import templates
import logging import logging
@ -81,9 +81,13 @@ class Stack(object):
self.default_lock = None self.default_lock = None
self.multi_delete = True self.multi_delete = True
self.template_bucket_url = None self.template_bucket_url = None
self.work_dir = None self.work_dir = None
self.pulumi = {} self.pulumi = {}
self._pulumi_stack = None self._pulumi_stack = None
self.pulumi_stackname = ""
self.pulumi_config = {}
self.pulumi_ws_opts = None
def dump_config(self): def dump_config(self):
logger.debug("<Stack {}: {}>".format(self.id, pprint.pformat(vars(self)))) logger.debug("<Stack {}: {}>".format(self.id, pprint.pformat(vars(self))))
@ -484,7 +488,7 @@ class Stack(object):
"""gets outputs of the stack""" """gets outputs of the stack"""
if self.mode == "pulumi": if self.mode == "pulumi":
self.outputs = pulumi_init(self).outputs() self.outputs = self._get_pulumi_stack().outputs()
else: else:
self.read_template_file() self.read_template_file()
@ -724,7 +728,7 @@ class Stack(object):
if self.mode == "pulumi": if self.mode == "pulumi":
kwargs = self._set_pulumi_args() kwargs = self._set_pulumi_args()
pulumi_init(self, create=True).up(**kwargs) self._get_pulumi_stack(create=True).up(**kwargs)
else: else:
# Prepare parameters # Prepare parameters
@ -822,7 +826,7 @@ class Stack(object):
logger.info("Deleting {0} {1}".format(self.region, self.stackname)) logger.info("Deleting {0} {1}".format(self.region, self.stackname))
if self.mode == "pulumi": if self.mode == "pulumi":
pulumi_stack = pulumi_init(self) pulumi_stack = self._get_pulumi_stack()
pulumi_stack.destroy(on_output=self._log_pulumi) pulumi_stack.destroy(on_output=self._log_pulumi)
pulumi_stack.workspace.remove_stack(pulumi_stack.name) pulumi_stack.workspace.remove_stack(pulumi_stack.name)
@ -843,7 +847,7 @@ class Stack(object):
def refresh(self): def refresh(self):
"""Refreshes a Pulumi stack""" """Refreshes a Pulumi stack"""
pulumi_init(self).refresh(on_output=self._log_pulumi) self._get_pulumi_stack().refresh(on_output=self._log_pulumi)
return return
@ -852,15 +856,44 @@ class Stack(object):
"""Preview a Pulumi stack up operation""" """Preview a Pulumi stack up operation"""
kwargs = self._set_pulumi_args() kwargs = self._set_pulumi_args()
pulumi_init(self, create=True).preview(**kwargs) self._get_pulumi_stack(create=True).preview(**kwargs)
return return
@pulumi_ws
def execute(self, function, args, listall=False):
"""Executes custom Python function within a Pulumi stack"""
# call all available functions and output built in help
if listall:
for k in vars(self._pulumi_code).keys():
if k.startswith("_execute_"):
docstring = vars(self._pulumi_code)[k](docstring=True)
print("{}: {}".format(k.lstrip("_execute_"), docstring))
return
else:
if not function:
logger.error("No function specified !")
return
exec_function = f"_execute_{function}"
if exec_function in vars(self._pulumi_code):
pulumi_stack = self._get_pulumi_stack()
vars(self._pulumi_code)[exec_function](
config=pulumi_stack.get_all_config(), outputs=pulumi_stack.outputs(), args=args
)
else:
logger.error(
"{} is not defined in {}".format(function, self._pulumi_code)
)
@pulumi_ws @pulumi_ws
def assimilate(self): def assimilate(self):
"""Import resources into Pulumi stack""" """Import resources into Pulumi stack"""
pulumi_stack = pulumi_init(self, create=True) pulumi_stack = self._get_pulumi_stack(create=True)
# now lets import each defined resource # now lets import each defined resource
for r in self._pulumi_code.RESOURCES: for r in self._pulumi_code.RESOURCES:
@ -881,7 +914,7 @@ class Stack(object):
def export(self, remove_pending_operations): def export(self, remove_pending_operations):
"""Exports a Pulumi stack""" """Exports a Pulumi stack"""
pulumi_stack = pulumi_init(self) pulumi_stack = self._get_pulumi_stack()
deployment = pulumi_stack.export_stack() deployment = pulumi_stack.export_stack()
if remove_pending_operations: if remove_pending_operations:
@ -897,7 +930,7 @@ class Stack(object):
def set_config(self, key, value, secret): def set_config(self, key, value, secret):
"""Set a config or secret""" """Set a config or secret"""
pulumi_stack = pulumi_init(self, create=True) pulumi_stack = self._get_pulumi_stack(create=True)
pulumi_stack.set_config(key, pulumi.automation.ConfigValue(value, secret)) pulumi_stack.set_config(key, pulumi.automation.ConfigValue(value, secret))
# Store salt or key and encrypted value in CloudBender stack config # Store salt or key and encrypted value in CloudBender stack config
@ -932,7 +965,7 @@ class Stack(object):
def get_config(self, key): def get_config(self, key):
"""Get a config or secret""" """Get a config or secret"""
print(pulumi_init(self).get_config(key).value) print(self._get_pulumi_stack().get_config(key).value)
def create_change_set(self, change_set_name): def create_change_set(self, change_set_name):
"""Creates a Change Set with the name ``change_set_name``.""" """Creates a Change Set with the name ``change_set_name``."""
@ -1153,6 +1186,29 @@ class Stack(object):
if text and not text.isspace(): if text and not text.isspace():
logger.info(" ".join([self.region, self.stackname, text])) logger.info(" ".join([self.region, self.stackname, text]))
def _get_pulumi_stack(self, create=False):
if create:
pulumi_stack = pulumi.automation.create_or_select_stack(
stack_name=self.pulumi_stackname,
project_name=self.parameters["Conglomerate"],
program=self._pulumi_code.pulumi_program,
opts=self.pulumi_ws_opts,
)
pulumi_stack.workspace.install_plugin(
"aws", pkg_resources.get_distribution("pulumi_aws").version
)
else:
pulumi_stack = pulumi.automation.select_stack(
stack_name=self.pulumi_stackname,
project_name=self.parameters["Conglomerate"],
program=self._pulumi_code.pulumi_program,
opts=self.pulumi_ws_opts,
)
return pulumi_stack
def _set_pulumi_args(self, kwargs={}): def _set_pulumi_args(self, kwargs={}):
kwargs["on_output"] = self._log_pulumi kwargs["on_output"] = self._log_pulumi
kwargs["policy_packs"] = [] kwargs["policy_packs"] = []
@ -1163,7 +1219,9 @@ class Stack(object):
for policy in self.pulumi["policies"]: for policy in self.pulumi["policies"]:
found = False found = False
for artifacts_path in self.ctx["artifact_paths"]: for artifacts_path in self.ctx["artifact_paths"]:
path = "{}/pulumi/policies/{}".format(artifacts_path.resolve(), policy) path = "{}/pulumi/policies/{}".format(
artifacts_path.resolve(), policy
)
if os.path.exists(path): if os.path.exists(path):
kwargs["policy_packs"].append(path) kwargs["policy_packs"].append(path)
found = True found = True

12
conf/containers.conf Normal file
View File

@ -0,0 +1,12 @@
[containers]
netns="host"
userns="host"
ipcns="host"
utsns="host"
cgroupns="host"
cgroups="disabled"
log_driver = "k8s-file"
[engine]
cgroup_manager = "cgroupfs"
events_logger="file"
runtime="crun"

View File

@ -0,0 +1,4 @@
[containers]
volumes = [
"/proc:/proc",
]

2
conf/registries.conf Normal file
View File

@ -0,0 +1,2 @@
# Note that changing the order here may break lazy devs Dockerfile
unqualified-search-registries = [ "gcr.io", "quay.io", "docker.io", "registry.fedoraproject.org"]

14
conf/storage.conf Normal file
View File

@ -0,0 +1,14 @@
[storage]
driver = "overlay"
runroot = "/run/containers/storage"
graphroot = "/var/lib/containers/storage"
[storage.options]
additionalimagestores = [
"/var/lib/shared",
]
[storage.options.overlay]
mount_program = "/usr/bin/fuse-overlayfs"
mountopt = "nodev,fsync=0"
[storage.options.thinpool]