2
\$\begingroup\$

Like the title suggests, I wrote a script in Python that uses the python3-lxc library to automate the creation of Linux LXC containers. I realised that containers can be great when I'm writing (and even installing other) apps and setting up FreeBSD-ish* jail environments to organise my workspaces for whatever project that I'm up to.

Setting up LXC containers can be a pain in the you-know-what, so I decided to automate the task with Python and came up with this:

# Generic Python3 development environment
# - Automated LXC container setup script

# By Aleksey <[email protected]>
# - GitHub: https://github.com/Alekseyyy

import lxc
import sys
import argparse

parser = argparse.ArgumentParser(description="Use LXC to setup a Python3 development environment")
parser.add_argument("--name", action="store", type=str, default="python3-dev", help="the name of the container")
parser.add_argument("--user", action="store", type=str, default="dev", help="the user's ssh username")
args = parser.parse_args()

container = lxc.Container(args.name)

# Check to see if container already exists
if container.defined:
    print("Container of the name %s already exists. Exiting..." % name)
    sys.exit(1)
    
# Create and start the container
if not container.create("download", lxc.LXC_CREATE_QUIET, {"dist":"ubuntu",
                            "release":"bionic",
                            "arch":"amd64"}):
    print("Failed to create the Python3 development container. Exiting... ")
    sys.exit(1)
print("Created the Python3 development container!")
if not container.start():
    print("Failed to start the Python3 development container")
    sys.exit(1)
print("Started the Python3 development container!")

# Setup the container
print("Setting up the container...")
container.get_ips(timeout=30) #wait a bit :P

setup_commands = [
    ["apt", "update"],
    ["apt", "dist-upgrade"],
    
    ["apt", "install", "python3"],
    ["apt", "install", "python3-pip"],
    ["apt", "install",  "ssh"],
    
    ["useradd", args.user],
    ["mkdir", "/home/%s" % args.user]
]

for k in setup_commands:
    container.attach_wait(lxc.attach_run_command, k)

print("\nFinished setting up! (don't forget to set a password for %s" % args.user)
print("Container state: %s" % container.state)
print("Container PID: %s\n" % container.init_pid)

The script works like:

  1. Importing the necessary libraries
  2. Parsing arguments to be used in creating the image
  3. Creating and starting up the container
  4. "Attaching" to the container to set up any prerequisites
  5. Finally printing out the process ID and state of the container

Because I'm lazy, this sets up a privileged container that requires the sudo command to run (which is insecure for production systems if I'm not mistaken, but should be fine for users who just want a container to work in).

  • Can you recommend a better way to deploy LXC containers?
  • Can you recommend ways coding practices to improve my script?
  • I noticed that I can't automatically set a password for the lower-privileged user that I created and that some images can't download (possibly because of some GPG keyserver error). I should ask this in StackOverflow, but do you have any advice to solve this?
  • Do you have any other advice regarding LXC-based containers, containers in general or Python programming?

Also, you can check out my other Python-automated LXC scripts if you're interested here: https://github.com/Alekseyyy/InfoSec/tree/master/lxc

*I'm sure that there are technical differences between FreeBSD jails and LXC containers, plz go easy on me :P

\$\endgroup\$
4
  • 1
    \$\begingroup\$ Why not use the lxc-create command to create the container? It looks like you just wrote a Python wrapper around an already existing command line tool. Note that for some distros, the lxc-create commands also allows you to specify a list of packages you want added onto the base image. \$\endgroup\$
    – G. Sliepen
    Commented Aug 27, 2021 at 18:47
  • \$\begingroup\$ Yeah but this is sort of like a Dockerfile (but for LXC) defining how the container should be setup. Mostly cos' it would take forever to setup (some) containers (I have more complex setups besides this) and would like to just automate all the things :P Also, I'm gonna forget what packages that I need to install, so it's nice to keep them in Python script ;-) \$\endgroup\$
    – Aleksey
    Commented Aug 27, 2021 at 18:58
  • 1
    \$\begingroup\$ Fair enough. I would recommend switching to shell scripts though, as they require less dependencies to be installed. That said, once complexity goes up it might be better to stick with Python. \$\endgroup\$
    – G. Sliepen
    Commented Aug 27, 2021 at 19:02
  • \$\begingroup\$ Yeah, but I'm very bad at shell scripting @G. Sliepen, thanks for advices tho :D \$\endgroup\$
    – Aleksey
    Commented Aug 27, 2021 at 19:03

1 Answer 1

2
\$\begingroup\$

I don't blame you for favouring Python over Bash, which I find spooky and awkward. This is mostly due to my inexperience, but the syntax seems to me inconsistent and unintuitive.

I find Exiting... to be redundant - the user will notice that the script has exited when it has exited.

Particularly since this is being run as a script from the shell, you should prefix your script with a shebang:

#!/usr/bin/env python3

It's good that you're using argparse. Keep doing that.

For your error message print calls, consider including file=stderr.

Interpolated "f-strings" are more syntactically sugary than percent notation:

f'Container named {name} already exists'

I find

if not container.create("download", lxc.LXC_CREATE_QUIET, {"dist":"ubuntu",
                            "release":"bionic",
                            "arch":"amd64"}):

to be easier to read when formatted as

    if not container.create(
        "download", lxc.LXC_CREATE_QUIET, 
        {
            "dist": "ubuntu",
            "release": "bionic",
            "arch": "amd64",
        },
    ):

Is "the user's SSH username" as used in the path /home/{args.user} able to be implied by ~? That would be preferable.

container.get_ips(timeout=30) #wait a bit :P

is icky. What if it takes far less (bad) or more (worse) than 30 seconds? Instead write a polling loop that is able to detect when the container is set up.

\$\endgroup\$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.