]> git.proxmox.com Git - mirror_lxc.git/blame - src/lxc/lxc-start-ephemeral.in
licensing: Add missing headers and FSF address
[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
250b1eec 25# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
d7415aea
SG
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,
bde18539
SG
49 random.randint(0x00, 0x7f),
50 random.randint(0x00, 0xff),
51 random.randint(0x00, 0xff)]
d7415aea
SG
52 return ':'.join(map(lambda x: "%02x" % x, mac))
53
54# Begin parsing the command line
bde18539
SG
55parser = argparse.ArgumentParser(description=_(
56 "LXC: Start an ephemeral container"),
57 formatter_class=argparse.RawTextHelpFormatter,
58 epilog=_("If a COMMAND is given, then the "
59 """container will run only as long
d7415aea
SG
60as the command runs.
61If no COMMAND is given, this command will attach to tty1 and stop the
62container when exiting (with ctrl-a-q).
63
64If no COMMAND is given and -d is used, the name and IP addresses of the
65container will be printed to the console."""))
66
9157421a
SG
67parser.add_argument("--lxcpath", "-P", dest="lxcpath", metavar="PATH",
68 help=_("Use specified container path"), default=None)
69
d7415aea 70parser.add_argument("--orig", "-o", type=str, required=True,
bde18539 71 help=_("name of the original container"))
d7415aea 72
f63b1efd
SG
73parser.add_argument("--name", "-n", type=str,
74 help=_("name of the target container"))
75
d7415aea 76parser.add_argument("--bdir", "-b", type=str,
bde18539 77 help=_("directory to bind mount into container"))
d7415aea
SG
78
79parser.add_argument("--user", "-u", type=str,
bde18539 80 help=_("the user to connect to the container as"))
d7415aea
SG
81
82parser.add_argument("--key", "-S", type=str,
bde18539 83 help=_("the path to the SSH key to use to connect"))
d7415aea
SG
84
85parser.add_argument("--daemon", "-d", action="store_true",
bde18539 86 help=_("run in the background"))
d7415aea 87
b58e60e2
SG
88parser.add_argument("--storage-type", "-s", type=str, default=None,
89 choices=("tmpfs", "dir"),
90 help=("type of storage use by the container"))
91
d7415aea 92parser.add_argument("--union-type", "-U", type=str, default="overlayfs",
bde18539
SG
93 choices=("overlayfs", "aufs"),
94 help=_("type of union (overlayfs or aufs), "
95 "defaults to overlayfs."))
d7415aea
SG
96
97parser.add_argument("--keep-data", "-k", action="store_true",
b58e60e2 98 help=_("don't wipe everything clean at the end"))
d7415aea
SG
99
100parser.add_argument("command", metavar='CMD', type=str, nargs="*",
bde18539
SG
101 help=_("Run specific command in container "
102 "(command as argument)"))
d7415aea
SG
103
104args = parser.parse_args()
105
106# Basic requirements check
107## Check that -d and CMD aren't used at the same time
108if args.command and args.daemon:
c0b5f522 109 parser.error(_("You can't use -d and a command at the same time."))
d7415aea 110
b58e60e2
SG
111## Check that -k isn't used with -s tmpfs
112if not args.storage_type:
113 if args.keep_data:
114 args.storage_type = "dir"
115 else:
116 args.storage_type = "tmpfs"
117
118if args.keep_data and args.storage_type == "tmpfs":
119 parser.error(_("You can't use -k with the tmpfs storage type."))
120
d7415aea
SG
121## The user needs to be uid 0
122if not os.geteuid() == 0:
c0b5f522
SG
123 parser.error(_("You must be root to run this script. Try running: sudo %s"
124 % (sys.argv[0])))
d7415aea
SG
125
126# Load the orig container
9157421a 127orig = lxc.Container(args.orig, args.lxcpath)
d7415aea 128if not orig.defined:
c0b5f522 129 parser.error(_("Source container '%s' doesn't exist." % args.orig))
d7415aea
SG
130
131# Create the new container paths
9157421a
SG
132if not args.lxcpath:
133 lxc_path = lxc.default_config_path
134else:
135 lxc_path = args.lxcpath
136
f63b1efd
SG
137if args.name:
138 if os.path.exists("%s/%s" % (lxc_path, args.name)):
139 parser.error(_("A container named '%s' already exists." % args.name))
140 dest_path = "%s/%s" % (lxc_path, args.name)
141 os.mkdir(dest_path)
142else:
143 dest_path = tempfile.mkdtemp(prefix="%s-" % args.orig, dir=lxc_path)
d7415aea
SG
144os.mkdir(os.path.join(dest_path, "rootfs"))
145
146# Setup the new container's configuration
9157421a 147dest = lxc.Container(os.path.basename(dest_path), args.lxcpath)
d7415aea
SG
148dest.load_config(orig.config_file_name)
149dest.set_config_item("lxc.utsname", dest.name)
150dest.set_config_item("lxc.rootfs", os.path.join(dest_path, "rootfs"))
6c5db2af
SG
151for nic in dest.network:
152 if hasattr(nic, 'hwaddr'):
153 nic.hwaddr = randomMAC()
d7415aea
SG
154
155overlay_dirs = [(orig.get_config_item("lxc.rootfs"), "%s/rootfs/" % dest_path)]
156
157# Generate a new fstab
158if orig.get_config_item("lxc.mount"):
159 dest.set_config_item("lxc.mount", os.path.join(dest_path, "fstab"))
160 with open(orig.get_config_item("lxc.mount"), "r") as orig_fd:
161 with open(dest.get_config_item("lxc.mount"), "w+") as dest_fd:
162 for line in orig_fd.read().split("\n"):
163 # Start by replacing any reference to the container rootfs
164 line.replace(orig.get_config_item("lxc.rootfs"),
bde18539 165 dest.get_config_item("lxc.rootfs"))
d7415aea
SG
166
167 # Skip any line that's not a bind mount
168 fields = line.split()
169 if len(fields) < 4:
170 dest_fd.write("%s\n" % line)
171 continue
172
173 if fields[2] != "bind" and "bind" not in fields[3]:
174 dest_fd.write("%s\n" % line)
175 continue
176
177 # Process any remaining line
178 dest_mount = os.path.abspath(os.path.join("%s/rootfs/" % (
bde18539 179 dest_path), fields[1]))
d7415aea
SG
180
181 if dest_mount == os.path.abspath("%s/rootfs/%s" % (
bde18539 182 dest_path, args.bdir)):
d7415aea
SG
183
184 dest_fd.write("%s\n" % line)
185 continue
186
187 if "%s/rootfs/" % dest_path not in dest_mount:
bde18539
SG
188 print(_("Skipping mount entry '%s' as it's outside "
189 "of the container rootfs.") % line)
d7415aea
SG
190
191 overlay_dirs += [(fields[0], dest_mount)]
192
193# Generate pre-mount script
194with open(os.path.join(dest_path, "pre-mount"), "w+") as fd:
195 os.fchmod(fd.fileno(), 0o755)
196 fd.write("""#!/bin/sh
197LXC_DIR="%s"
198LXC_BASE="%s"
199LXC_NAME="%s"
200""" % (dest_path, orig.name, dest.name))
201
202 count = 0
203 for entry in overlay_dirs:
204 target = "%s/delta%s" % (dest_path, count)
205 fd.write("mkdir -p %s %s\n" % (target, entry[1]))
206
b58e60e2 207 if args.storage_type == "tmpfs":
d7415aea
SG
208 fd.write("mount -n -t tmpfs none %s\n" % (target))
209
210 if args.union_type == "overlayfs":
211 fd.write("mount -n -t overlayfs"
bde18539
SG
212 " -oupperdir=%s,lowerdir=%s none %s\n" % (
213 target,
214 entry[0],
215 entry[1]))
d7415aea
SG
216 elif args.union_type == "aufs":
217 fd.write("mount -n -t aufs "
bde18539
SG
218 "-o br=${upper}=rw:${lower}=ro,noplink none %s\n" % (
219 target,
220 entry[0],
221 entry[1]))
d7415aea
SG
222 count += 1
223
224 if args.bdir:
225 if not os.path.exists(args.bdir):
226 print(_("Path '%s' doesn't exist, won't be bind-mounted.") %
bde18539 227 args.bdir)
d7415aea
SG
228 else:
229 src_path = os.path.abspath(args.bdir)
230 dst_path = "%s/rootfs/%s" % (dest_path, os.path.abspath(args.bdir))
231 fd.write("mkdir -p %s\nmount -n --bind %s %s\n" % (
bde18539 232 dst_path, src_path, dst_path))
d7415aea
SG
233
234 fd.write("""
235[ -e $LXC_DIR/configured ] && exit 0
236for file in $LXC_DIR/rootfs/etc/hostname \\
237 $LXC_DIR/rootfs/etc/hosts \\
238 $LXC_DIR/rootfs/etc/sysconfig/network \\
239 $LXC_DIR/rootfs/etc/sysconfig/network-scripts/ifcfg-eth0; do
240 [ -f "$file" ] && sed -i -e "s/$LXC_BASE/$LXC_NAME/" $file
241done
242touch $LXC_DIR/configured
243""")
244
245dest.set_config_item("lxc.hook.pre-mount",
bde18539 246 os.path.join(dest_path, "pre-mount"))
d7415aea
SG
247
248# Generate post-stop script
249if not args.keep_data:
250 with open(os.path.join(dest_path, "post-stop"), "w+") as fd:
251 os.fchmod(fd.fileno(), 0o755)
252 fd.write("""#!/bin/sh
95a717e9 253[ -d "%s" ] && rm -Rf "%s"
d76db55b 254""" % (dest_path, dest_path))
d7415aea
SG
255
256 dest.set_config_item("lxc.hook.post-stop",
bde18539 257 os.path.join(dest_path, "post-stop"))
d7415aea
SG
258
259dest.save_config()
260
261# Start the container
262if not dest.start() or not dest.wait("RUNNING", timeout=5):
263 print(_("The container '%s' failed to start.") % dest.name)
264 dest.stop()
265 if dest.defined:
266 dest.destroy()
267 sys.exit(1)
268
abbe2ead
SG
269# Deal with the case where we just attach to the container's console
270if not args.command and not args.daemon:
b58e60e2 271 dest.console()
abbe2ead
SG
272 dest.shutdown(timeout=5)
273 sys.exit(0)
274
d7415aea 275# Try to get the IP addresses
819554fe 276ips = dest.get_ips(timeout=10)
d7415aea 277
abbe2ead
SG
278# Deal with the case where we just print info about the container
279if args.daemon:
280 print(_("""The ephemeral container is now started.
d7415aea
SG
281
282You can enter it from the command line with: lxc-console -n %s
283The following IP addresses have be found in the container:
bde18539
SG
284%s""") % (dest.name,
285 "\n".join([" - %s" % entry for entry in ips]
286 or [" - %s" % _("No address could be found")])))
abbe2ead 287 sys.exit(0)
d7415aea
SG
288
289# Now deal with the case where we want to run a command in the container
290if not ips:
291 print(_("Failed to get an IP for container '%s'.") % dest.name)
292 dest.stop()
293 if dest.defined:
294 dest.destroy()
295 sys.exit(1)
296
297# NOTE: To replace by .attach() once the kernel supports it
298cmd = ["ssh",
bde18539
SG
299 "-o", "StrictHostKeyChecking=no",
300 "-o", "UserKnownHostsFile=/dev/null"]
d7415aea
SG
301
302if args.user:
303 cmd += ["-l", args.user]
304
305if args.key:
33892746 306 cmd += ["-i", args.key]
d7415aea
SG
307
308for ip in ips:
309 ssh_cmd = cmd + [ip] + args.command
310 retval = subprocess.call(ssh_cmd, universal_newlines=True)
311 if retval == 255:
312 print(_("SSH failed to connect, trying next IP address."))
313 continue
314
315 if retval != 0:
316 print(_("Command returned with non-zero return code: %s") % retval)
317 break
318
319# Shutdown the container
9737a206 320dest.shutdown(timeout=5)
6506255c
SG
321
322sys.exit(retval)