| |
@@ -0,0 +1,244 @@
|
| |
+ #!/usr/bin/python3
|
| |
+
|
| |
+ # --- BEGIN COPYRIGHT BLOCK ---
|
| |
+ # Copyright (C) 2019 William Brown <william@blackhats.net.au>
|
| |
+ # All rights reserved.
|
| |
+ #
|
| |
+ # License: GPL (version 3 or any later version).
|
| |
+ # See LICENSE for details.
|
| |
+ # --- END COPYRIGHT BLOCK ---
|
| |
+
|
| |
+ # Why does this exist, and what does it do?
|
| |
+ ###########################################
|
| |
+ #
|
| |
+ # This entry point exists because it's hard to make 389 really "stateless"
|
| |
+ # in the way a container environment expects, and especially with systems
|
| |
+ # like kubernetes with volume setup etc.
|
| |
+ #
|
| |
+ # This script will detect if an instance exists in the volume locations
|
| |
+ # and if one does not (new, or ephemeral) we create a container-optimised
|
| |
+ # instance of 389-ds.
|
| |
+ #
|
| |
+ # If an instance *does* exist, we will start it up, and let it run. Simple
|
| |
+ # as that!
|
| |
+ #
|
| |
+
|
| |
+ import grp
|
| |
+ import pwd
|
| |
+ import atexit
|
| |
+ import os
|
| |
+ import signal
|
| |
+ import sys
|
| |
+ import subprocess
|
| |
+ import argparse, argcomplete
|
| |
+ from argparse import RawTextHelpFormatter
|
| |
+
|
| |
+
|
| |
+ from lib389 import DirSrv
|
| |
+ from lib389.cli_base import setup_script_logger
|
| |
+ from lib389.instance.setup import SetupDs
|
| |
+ from lib389.instance.options import General2Base, Slapd2Base
|
| |
+ from lib389.passwd import password_generate
|
| |
+ from lib389.paths import Paths
|
| |
+
|
| |
+ # We setup the logger in verbose mode to make sure debug info
|
| |
+ # is always available!
|
| |
+ log = setup_script_logger("container-init", True)
|
| |
+
|
| |
+ def begin_magic():
|
| |
+ log.info("The 389 Directory Server Container Bootstrap")
|
| |
+ # Leave this comment here: UofA let me take this code with me provided
|
| |
+ # I gave attribution. -- wibrown
|
| |
+ log.info("Inspired by works of: ITS, The University of Adelaide")
|
| |
+
|
| |
+ # Setup our ds_paths ...
|
| |
+ # Notice we pre-populate the instance id, which allows the start up to work correctly
|
| |
+ # to find the correct configuration path?
|
| |
+ #
|
| |
+ # We wouldn't need this *except* for testing containers that build to /opt/dirsrv
|
| |
+ paths = Paths(serverid='localhost')
|
| |
+
|
| |
+ # Make sure that /data/config, /data/ssca and /data/config exist, because
|
| |
+ # k8s may not template them out.
|
| |
+ #
|
| |
+ # Big note for those at home: This means you need your dockerfile to run
|
| |
+ # something like:
|
| |
+ # EXPOSE 3389 3636
|
| |
+ # RUN mkdir -p /data/config && \
|
| |
+ # mkdir -p /data/ssca && \
|
| |
+ # ln -s /data/config /etc/dirsrv/slapd-localhost && \
|
| |
+ # ln -s /data/ssca /etc/dirsrv/ssca && \
|
| |
+ # # Temporal volumes for each instance
|
| |
+ # VOLUME /data
|
| |
+ #
|
| |
+ # When I said this was a container tool, I really really meant it!
|
| |
+ #
|
| |
+ # Q: "William, why do you symlink in these locations?"
|
| |
+ # A: Docker lets you mount in volumes. The *simpler* we can make this for a user
|
| |
+ # the absolute beter. This means any downstream container can simply use:
|
| |
+ # docker run -v 389_data:/data ... 389-ds:latest
|
| |
+ # If we were to use the "normal paths", we would require MORE volume mounts, with
|
| |
+ # cryptic paths and complexity. Not friendly at all.
|
| |
+ #
|
| |
+ # Q: "William, why not change the paths in the config?"
|
| |
+ # A: Despite the fact that ds alleges support for moving content and paths, this
|
| |
+ # is not possible for the /etc/dirsrv content unless at COMPILE time. Additionally
|
| |
+ # some parts of the code base make assumptions. Instead of fighting legacy, we want
|
| |
+ # results now! So we mask our limitations with symlinks.
|
| |
+ #
|
| |
+ for d in [
|
| |
+ '/data/config',
|
| |
+ '/data/ssca',
|
| |
+ '/data/db',
|
| |
+ '/data/bak',
|
| |
+ '/data/ldif',
|
| |
+ '/data/run',
|
| |
+ '/data/run/lock',
|
| |
+ '/data/logs'
|
| |
+ ]:
|
| |
+ if not os.path.exists(d):
|
| |
+ os.makedirs(d, mode=0o770)
|
| |
+
|
| |
+ # Do we have correct permissions to our volumes? With the power of thoughts and
|
| |
+ # prayers, we continue blindy and ... well hope.
|
| |
+
|
| |
+ # Do we have an instance? We can only tell by the /data/config/container.inf
|
| |
+ # marker file
|
| |
+ if not os.path.exists('/data/config/container.inf'):
|
| |
+ # Nope? Make one ...
|
| |
+ log.info("Initialising 389-ds-container due to empty volume ...")
|
| |
+ rpw = password_generate()
|
| |
+
|
| |
+ g2b = General2Base(log)
|
| |
+ s2b = Slapd2Base(log)
|
| |
+ # Fill in container defaults?
|
| |
+
|
| |
+ g2b.set('strict_host_checking', False)
|
| |
+ g2b.set('selinux', False)
|
| |
+ g2b.set('systemd', False)
|
| |
+ g2b.set('start', False)
|
| |
+
|
| |
+ s2b.set('instance_name', 'localhost')
|
| |
+
|
| |
+ # We use our user/group from the current user, begause in envs like kubernetes
|
| |
+ # it WILL NOT be dirsrv
|
| |
+ user_name = pwd.getpwuid(os.getuid())[0]
|
| |
+ group_name = grp.getgrgid(os.getgid())[0]
|
| |
+
|
| |
+ s2b.set('user', user_name)
|
| |
+ s2b.set('group', group_name)
|
| |
+ s2b.set('root_password', rpw)
|
| |
+ s2b.set('port', 3389)
|
| |
+ s2b.set('secure_port', 3636)
|
| |
+
|
| |
+ s2b.set('local_state_dir', '/data')
|
| |
+ s2b.set('inst_dir', '/data')
|
| |
+ s2b.set('db_dir', '/data/db')
|
| |
+ # Why is this bak? Some dsctl commands use INST_DIR/bak, not "backup_dir"
|
| |
+ # due to some legacy handling of paths in lib389's population of instances.
|
| |
+ s2b.set('backup_dir', '/data/bak')
|
| |
+ s2b.set('ldif_dir', '/data/ldif')
|
| |
+ s2b.set('run_dir', '/data/run')
|
| |
+ s2b.set('lock_dir', '/data/run/lock')
|
| |
+ s2b.set('ldapi', '/data/run/slapd.socket')
|
| |
+
|
| |
+ s2b.set('log_dir', '/data/logs')
|
| |
+ s2b.set('access_log', '/data/logs/access')
|
| |
+ s2b.set('error_log', '/data/logs/error')
|
| |
+ s2b.set('audit_log', '/data/logs/audit')
|
| |
+
|
| |
+ # Now collect and submit for creation.
|
| |
+ sds = SetupDs(verbose=True, dryrun=False, log=log, containerised=True)
|
| |
+
|
| |
+ if not sds.create_from_args(g2b.collect(), s2b.collect()):
|
| |
+ log.error("Failed to create instance")
|
| |
+ sys.exit(1)
|
| |
+
|
| |
+ log.info("IMPORTANT: Set cn=Directory Manager password to \"%s\"" % rpw)
|
| |
+
|
| |
+ # Create the marker to say we exist. This is also a good writable permissions
|
| |
+ # test for the volume.
|
| |
+ with open('/data/config/container.inf', 'w'):
|
| |
+ pass
|
| |
+
|
| |
+ # TODO: All of this is contingent on the server starting *and*
|
| |
+ # ldapi working ... Perhaps these are better inside ns-slapd core
|
| |
+ # and we just proxy/filter the env through?
|
| |
+ # TODO: Should we reset cn=Directory Manager from env?
|
| |
+ # TODO: Should we set replica id from env?
|
| |
+ # TODO: Should we set replication agreements from env?
|
| |
+ # TODO: Should we allow re-indexing at startup from env?
|
| |
+
|
| |
+ # Yep! Run it ...
|
| |
+ # Now unlike a normal lib389 start, we use subprocess and don't fork!
|
| |
+ # TODO: Should we pass in a loglevel from env?
|
| |
+ log.info("Starting 389-ds-container ...")
|
| |
+
|
| |
+ global ds_proc
|
| |
+ ds_proc = subprocess.Popen([
|
| |
+ "%s/ns-slapd" % paths.sbin_dir,
|
| |
+ "-D", paths.config_dir,
|
| |
+ # See /ldap/servers/slapd/slap.h SLAPD_DEFAULT_ERRORLOG_LEVEL
|
| |
+ "-d", "266354688",
|
| |
+ ], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
| |
+
|
| |
+ # To make sure we really do shutdown, we actually re-block on the proc
|
| |
+ # again here to be sure it's done.
|
| |
+ def kill_ds():
|
| |
+ if ds_proc is None:
|
| |
+ pass
|
| |
+ else:
|
| |
+ try:
|
| |
+ os.kill(ds_proc.pid, signal.SIGTERM)
|
| |
+ except ProcessLookupError:
|
| |
+ # It's already gone ...
|
| |
+ pass
|
| |
+ log.info("STOPPING: Shutting down 389-ds-container ...")
|
| |
+ ds_proc.wait()
|
| |
+
|
| |
+ atexit.register(kill_ds)
|
| |
+
|
| |
+ # Now wait ...
|
| |
+ try:
|
| |
+ ds_proc.wait()
|
| |
+ except KeyboardInterrupt:
|
| |
+ pass
|
| |
+ # THE LETTER OF THE DAY IS C AND THE NUMBER IS 10
|
| |
+
|
| |
+ if __name__ == '__main__':
|
| |
+ parser = argparse.ArgumentParser(allow_abbrev=True, description="""
|
| |
+ dscontainer - this is a container entry point that will run a stateless
|
| |
+ instance of 389-ds. You should not use this unless you are developing or
|
| |
+ building a container image of some nature. As a result, this tool is
|
| |
+ *extremely* opinionated, and you will need your container build file to
|
| |
+ have certain settings to work correctly.
|
| |
+
|
| |
+ \tEXPOSE 3389 3636
|
| |
+ \tRUN mkdir -p /data/config && \\
|
| |
+ \t mkdir -p /data/ssca && \\
|
| |
+ \t ln -s /data/config /etc/dirsrv/slapd-localhost && \\
|
| |
+ \t ln -s /data/ssca /etc/dirsrv/ssca && \\
|
| |
+ \tVOLUME /data
|
| |
+
|
| |
+ This is an example of the minimal required configuration. The 389
|
| |
+ instance will be created with ports 3389 and 3636. *All* of the data will
|
| |
+ be installed under /data. This means that to "reset" an instance you only
|
| |
+ need to remove the content of /data. In the case there is no instance
|
| |
+ one will be created.
|
| |
+
|
| |
+ No backends or suffixes are created by default, as we can not assume your
|
| |
+ domain component. The cn=Directory Manager password is randomised on
|
| |
+ install, and can be viewed in the setup log, or can be accessed via ldapi
|
| |
+ - the ldapi socket is placed into /data so you can access it from the
|
| |
+ container host.
|
| |
+ """, formatter_class=RawTextHelpFormatter)
|
| |
+ parser.add_argument('-r', '--runit',
|
| |
+ help="Actually run the instance! You understand what that means ...",
|
| |
+ action='store_true', default=False, dest='runit')
|
| |
+ argcomplete.autocomplete(parser)
|
| |
+
|
| |
+ args = parser.parse_args()
|
| |
+
|
| |
+ if args.runit:
|
| |
+ begin_magic()
|
| |
+
|
| |
Bug Description: It's important that 389 Directory Server
has a functional, correct, and high quality container integration
system. After years of work on the server core and lib389, this is
nearly possible.
Importantly, containers have certain requirements we must understand.
All state must be in external-filesystem volumes. We can not assume
that we have an instance installed, so must create one on launch.
If one exists, we need to expose it. We don't have the ability to
ask questions, so we need to use environment, or work with no
input at all. We can't make assumptions about backends. Finally,
we need to assume that we could be a new version of the server -
we don't know about anything else.
Fix Description: This adds a dscontainer wrapper tool that is
intended for operation inside of containers. It handles and binds
many of the existing parts of lib389 for container support. I have
cleaned up past container support realising how it was done wasn't
as elegant as this.
The dscontainer tool is intended to be the entry point from a
dockerfile, IE the CMD directive.
There are still some avenues to explore. For example, we could
attempt to override the storage paths for logs and db rather than
relying on dockerfile system links. (this may break apparmor though).
https://pagure.io/389-ds-base/issue/50197
Author: William Brown william@blackhats.net.au
Review by: ???