]> git.proxmox.com Git - mirror_lxc.git/blame - src/lxc/lxc-start-ephemeral.in
Fix previous commit, removing hardcoded /var/lib/lxc from lxc-start-ephemeral
[mirror_lxc.git] / src / lxc / lxc-start-ephemeral.in
CommitLineData
d7415aea
SG
1#!/usr/bin/python3
2#
3# lxc-start-ephemeral: Start a copy of a container using an overlay
4#
5# This python implementation is based on the work done in the original
6# shell implementation done by Serge Hallyn in Ubuntu (and other contributors)
7#
8# (C) Copyright Canonical Ltd. 2012
9#
10# Authors:
11# Stéphane Graber <stgraber@ubuntu.com>
12#
13# This library is free software; you can redistribute it and/or
14# modify it under the terms of the GNU Lesser General Public
15# License as published by the Free Software Foundation; either
16# version 2.1 of the License, or (at your option) any later version.
17#
18# This library is distributed in the hope that it will be useful,
19# but WITHOUT ANY WARRANTY; without even the implied warranty of
20# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21# Lesser General Public License for more details.
22#
23# You should have received a copy of the GNU Lesser General Public
24# License along with this library; if not, write to the Free Software
25# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
26#
27
28# NOTE: To remove once the API is stabilized
29import warnings
30warnings.filterwarnings("ignore", "The python-lxc API isn't yet stable")
31
32import argparse
33import gettext
34import lxc
35import os
36import sys
37import subprocess
38import tempfile
39
40_ = gettext.gettext
41gettext.textdomain("lxc-start-ephemeral")
42
43
44# Other functions
45def randomMAC():
46 import random
47
48 mac = [0x00, 0x16, 0x3e,
49 random.randint(0x00, 0x7f),
50 random.randint(0x00, 0xff),
51 random.randint(0x00, 0xff)]
52 return ':'.join(map(lambda x: "%02x" % x, mac))
53
54# Begin parsing the command line
55parser = argparse.ArgumentParser(
56 description=_("LXC: Start an ephemeral container"),
57 formatter_class=argparse.RawTextHelpFormatter, epilog=_(
58"""If a COMMAND is given, then the container will run only as long
59as the command runs.
60If no COMMAND is given, this command will attach to tty1 and stop the
61container when exiting (with ctrl-a-q).
62
63If no COMMAND is given and -d is used, the name and IP addresses of the
64container will be printed to the console."""))
65
66parser.add_argument("--orig", "-o", type=str, required=True,
67 help=_("name of the original container"))
68
69parser.add_argument("--bdir", "-b", type=str,
70 help=_("directory to bind mount into container"))
71
72parser.add_argument("--user", "-u", type=str,
73 help=_("the user to connect to the container as"))
74
75parser.add_argument("--key", "-S", type=str,
76 help=_("the path to the SSH key to use to connect"))
77
78parser.add_argument("--daemon", "-d", action="store_true",
79 help=_("run in the background"))
80
81parser.add_argument("--union-type", "-U", type=str, default="overlayfs",
82 choices=("overlayfs", "aufs"),
83 help=_("type of union (overlayfs or aufs), defaults to overlayfs."))
84
85parser.add_argument("--keep-data", "-k", action="store_true",
86 help=_("Use a persistent backend instead of tmpfs."))
87
88parser.add_argument("command", metavar='CMD', type=str, nargs="*",
89 help=_("Run specific command in container (command as argument)"))
90
91args = parser.parse_args()
92
93# Basic requirements check
94## Check that -d and CMD aren't used at the same time
95if args.command and args.daemon:
96 print(_("You can't use -d and a command at the same time."))
97 sys.exit(1)
98
99## The user needs to be uid 0
100if not os.geteuid() == 0:
101 print(_("You must be root to run this script. Try running: sudo %s" %
102 (sys.argv[0])))
103 sys.exit(1)
104
105# Load the orig container
106orig = lxc.Container(args.orig)
107if not orig.defined:
108 print(_("Source container '%s' doesn't exist." % args.orig))
109 sys.exit(1)
110
111# Create the new container paths
95a717e9 112dest_path = tempfile.mkdtemp(prefix="%s-" % args.orig, dir="@LXCPATH@")
d7415aea
SG
113os.mkdir(os.path.join(dest_path, "rootfs"))
114
115# Setup the new container's configuration
116dest = lxc.Container(os.path.basename(dest_path))
117dest.load_config(orig.config_file_name)
118dest.set_config_item("lxc.utsname", dest.name)
119dest.set_config_item("lxc.rootfs", os.path.join(dest_path, "rootfs"))
120dest.set_config_item("lxc.network.hwaddr", randomMAC())
121
122overlay_dirs = [(orig.get_config_item("lxc.rootfs"), "%s/rootfs/" % dest_path)]
123
124# Generate a new fstab
125if orig.get_config_item("lxc.mount"):
126 dest.set_config_item("lxc.mount", os.path.join(dest_path, "fstab"))
127 with open(orig.get_config_item("lxc.mount"), "r") as orig_fd:
128 with open(dest.get_config_item("lxc.mount"), "w+") as dest_fd:
129 for line in orig_fd.read().split("\n"):
130 # Start by replacing any reference to the container rootfs
131 line.replace(orig.get_config_item("lxc.rootfs"),
132 dest.get_config_item("lxc.rootfs"))
133
134 # Skip any line that's not a bind mount
135 fields = line.split()
136 if len(fields) < 4:
137 dest_fd.write("%s\n" % line)
138 continue
139
140 if fields[2] != "bind" and "bind" not in fields[3]:
141 dest_fd.write("%s\n" % line)
142 continue
143
144 # Process any remaining line
145 dest_mount = os.path.abspath(os.path.join("%s/rootfs/" % (
146 dest_path), fields[1]))
147
148 if dest_mount == os.path.abspath("%s/rootfs/%s" % (
149 dest_path, args.bdir)):
150
151 dest_fd.write("%s\n" % line)
152 continue
153
154 if "%s/rootfs/" % dest_path not in dest_mount:
155 print(_(
156"Skipping mount entry '%s' as it's outside of the container rootfs.") % line)
157
158 overlay_dirs += [(fields[0], dest_mount)]
159
160# Generate pre-mount script
161with open(os.path.join(dest_path, "pre-mount"), "w+") as fd:
162 os.fchmod(fd.fileno(), 0o755)
163 fd.write("""#!/bin/sh
164LXC_DIR="%s"
165LXC_BASE="%s"
166LXC_NAME="%s"
167""" % (dest_path, orig.name, dest.name))
168
169 count = 0
170 for entry in overlay_dirs:
171 target = "%s/delta%s" % (dest_path, count)
172 fd.write("mkdir -p %s %s\n" % (target, entry[1]))
173
174 if not args.keep_data:
175 fd.write("mount -n -t tmpfs none %s\n" % (target))
176
177 if args.union_type == "overlayfs":
178 fd.write("mount -n -t overlayfs"
179 " -oupperdir=%s,lowerdir=%s none %s\n" % (
180 target,
181 entry[0],
182 entry[1]))
183 elif args.union_type == "aufs":
184 fd.write("mount -n -t aufs "
185 "-o br=${upper}=rw:${lower}=ro,noplink none %s\n" % (
186 target,
187 entry[0],
188 entry[1]))
189 count += 1
190
191 if args.bdir:
192 if not os.path.exists(args.bdir):
193 print(_("Path '%s' doesn't exist, won't be bind-mounted.") %
194 args.bdir)
195 else:
196 src_path = os.path.abspath(args.bdir)
197 dst_path = "%s/rootfs/%s" % (dest_path, os.path.abspath(args.bdir))
198 fd.write("mkdir -p %s\nmount -n --bind %s %s\n" % (
199 dst_path, src_path, dst_path))
200
201 fd.write("""
202[ -e $LXC_DIR/configured ] && exit 0
203for file in $LXC_DIR/rootfs/etc/hostname \\
204 $LXC_DIR/rootfs/etc/hosts \\
205 $LXC_DIR/rootfs/etc/sysconfig/network \\
206 $LXC_DIR/rootfs/etc/sysconfig/network-scripts/ifcfg-eth0; do
207 [ -f "$file" ] && sed -i -e "s/$LXC_BASE/$LXC_NAME/" $file
208done
209touch $LXC_DIR/configured
210""")
211
212dest.set_config_item("lxc.hook.pre-mount",
213 os.path.join(dest_path, "pre-mount"))
214
215# Generate post-stop script
216if not args.keep_data:
217 with open(os.path.join(dest_path, "post-stop"), "w+") as fd:
218 os.fchmod(fd.fileno(), 0o755)
219 fd.write("""#!/bin/sh
95a717e9
SG
220[ -d "%s" ] && rm -Rf "%s"
221""" % (dest.get_config_item("lxc.rootfs"), dest.get_config_item("lxc.rootfs"))
d7415aea
SG
222
223 dest.set_config_item("lxc.hook.post-stop",
224 os.path.join(dest_path, "post-stop"))
225
226dest.save_config()
227
228# Start the container
229if not dest.start() or not dest.wait("RUNNING", timeout=5):
230 print(_("The container '%s' failed to start.") % dest.name)
231 dest.stop()
232 if dest.defined:
233 dest.destroy()
234 sys.exit(1)
235
236# Try to get the IP addresses
237ips = dest.get_ips(timeout=5)
238
239# Deal with the case where we don't start a command in the container
240if not args.command:
241 if args.daemon:
242 print(_("""The ephemeral container is now started.
243
244You can enter it from the command line with: lxc-console -n %s
245The following IP addresses have be found in the container:
246%s""") % (
247 dest.name,
248 "\n".join([" - %s" % entry for entry in ips]
249 or [" - %s" % _("No address could be found")])
250 ))
251 sys.exit(0)
252 else:
253 dest.console(tty=1)
254 if not dest.shutdown(timeout=5):
255 dest.stop()
256 sys.exit(0)
257
258# Now deal with the case where we want to run a command in the container
259if not ips:
260 print(_("Failed to get an IP for container '%s'.") % dest.name)
261 dest.stop()
262 if dest.defined:
263 dest.destroy()
264 sys.exit(1)
265
266# NOTE: To replace by .attach() once the kernel supports it
267cmd = ["ssh",
268 "-o", "StrictHostKeyChecking=no",
269 "-o", "UserKnownHostsFile=/dev/null"]
270
271if args.user:
272 cmd += ["-l", args.user]
273
274if args.key:
275 cmd += ["-k", args.key]
276
277for ip in ips:
278 ssh_cmd = cmd + [ip] + args.command
279 retval = subprocess.call(ssh_cmd, universal_newlines=True)
280 if retval == 255:
281 print(_("SSH failed to connect, trying next IP address."))
282 continue
283
284 if retval != 0:
285 print(_("Command returned with non-zero return code: %s") % retval)
286 break
287
288# Shutdown the container
289if not dest.shutdown(timeout=5):
290 dest.stop()