]>
git.proxmox.com Git - mirror_lxc.git/blob - src/lxc/lxc-start-ephemeral.in
3 # lxc-start-ephemeral: Start a copy of a container using an overlay
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)
8 # (C) Copyright Canonical Ltd. 2012
11 # Stéphane Graber <stgraber@ubuntu.com>
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.
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.
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
38 gettext
.textdomain("lxc-start-ephemeral")
45 mac
= [0x00, 0x16, 0x3e,
46 random
.randint(0x00, 0x7f),
47 random
.randint(0x00, 0xff),
48 random
.randint(0x00, 0xff)]
49 return ':'.join(map(lambda x
: "%02x" % x
, mac
))
54 return "@RUNTIME_PATH@"
56 if "XDG_RUNTIME_DIR" in os
.environ
:
57 return os
.environ
["XDG_RUNTIME_DIR"]
59 if "HOME" in os
.environ
:
60 return "%s/.cache/lxc/run/" % os
.environ
["HOME"]
62 raise Exception("Unable to find a runtime directory")
65 # Begin parsing the command line
66 parser
= argparse
.ArgumentParser(description
=_(
67 "LXC: Start an ephemeral container"),
68 formatter_class
=argparse
.RawTextHelpFormatter
,
69 epilog
=_("If a COMMAND is given, then the "
70 """container will run only as long
72 If no COMMAND is given, this command will attach to tty1 and stop the
73 container when exiting (with ctrl-a-q).
75 If no COMMAND is given and -d is used, the name and IP addresses of the
76 container will be printed to the console."""))
78 parser
.add_argument("--lxcpath", "-P", dest
="lxcpath", metavar
="PATH",
79 help=_("Use specified container path"), default
=None)
81 parser
.add_argument("--orig", "-o", type=str, required
=True,
82 help=_("name of the original container"))
84 parser
.add_argument("--name", "-n", type=str,
85 help=_("name of the target container"))
87 parser
.add_argument("--bdir", "-b", type=str, action
="append", default
=[],
88 help=_("directory to bind mount into container, "
89 "either --bdir=/src-path or --bdir=/src-path:/dst-path"))
91 parser
.add_argument("--cdir", "-c", type=str, action
="append", default
=[],
92 help=_("directory to cow mount into container"))
94 parser
.add_argument("--user", "-u", type=str,
95 help=_("the user to run the command as"))
97 parser
.add_argument("--key", "-S", type=str,
98 help=_("the path to the key to use to connect "
101 parser
.add_argument("--daemon", "-d", action
="store_true",
102 help=_("run in the background"))
104 parser
.add_argument("--storage-type", "-s", type=str, default
=None,
105 choices
=("tmpfs", "dir"),
106 help=("type of storage use by the container"))
108 parser
.add_argument("--union-type", "-U", type=str, default
="overlayfs",
109 choices
=("overlayfs", "aufs"),
110 help=_("type of union (overlayfs or aufs), "
111 "defaults to overlayfs."))
113 parser
.add_argument("--keep-data", "-k", action
="store_true",
114 help=_("don't wipe everything clean at the end"))
116 parser
.add_argument("command", metavar
='CMD', type=str, nargs
="*",
117 help=_("Run specific command in container "
118 "(command as argument)"))
120 parser
.add_argument("--version", action
="version", version
=lxc
.version
)
122 args
= parser
.parse_args()
124 ## Check that -d and CMD aren't used at the same time
125 if args
.command
and args
.daemon
:
126 parser
.error(_("You can't use -d and a command at the same time."))
128 ## Check that -k isn't used with -s tmpfs
129 if not args
.storage_type
:
131 args
.storage_type
= "dir"
133 args
.storage_type
= "tmpfs"
135 if args
.keep_data
and args
.storage_type
== "tmpfs":
136 parser
.error(_("You can't use -k with the tmpfs storage type."))
138 # Load the orig container
139 orig
= lxc
.Container(args
.orig
, args
.lxcpath
)
141 parser
.error(_("Source container '%s' doesn't exist." % args
.orig
))
143 # Create the new container paths
145 lxc_path
= lxc
.default_config_path
147 lxc_path
= args
.lxcpath
150 if os
.path
.exists("%s/%s" % (lxc_path
, args
.name
)):
151 parser
.error(_("A container named '%s' already exists." % args
.name
))
152 dest_path
= "%s/%s" % (lxc_path
, args
.name
)
155 dest_path
= tempfile
.mkdtemp(prefix
="%s-" % args
.orig
, dir=lxc_path
)
156 os
.mkdir(os
.path
.join(dest_path
, "rootfs"))
157 os
.chmod(dest_path
, 0o770)
159 # Setup the new container's configuration
160 dest
= lxc
.Container(os
.path
.basename(dest_path
), args
.lxcpath
)
161 dest
.load_config(orig
.config_file_name
)
162 dest
.set_config_item("lxc.utsname", dest
.name
)
163 dest
.set_config_item("lxc.rootfs", os
.path
.join(dest_path
, "rootfs"))
164 print("setting rootfs to .%s.", os
.path
.join(dest_path
, "rootfs"))
165 for nic
in dest
.network
:
166 if hasattr(nic
, 'hwaddr'):
167 nic
.hwaddr
= randomMAC()
169 overlay_dirs
= [(orig
.get_config_item("lxc.rootfs"), "%s/rootfs/" % dest_path
)]
171 # Generate a new fstab
172 if orig
.get_config_item("lxc.mount"):
173 dest
.set_config_item("lxc.mount", os
.path
.join(dest_path
, "fstab"))
174 with
open(orig
.get_config_item("lxc.mount"), "r") as orig_fd
:
175 with
open(dest
.get_config_item("lxc.mount"), "w+") as dest_fd
:
176 for line
in orig_fd
.read().split("\n"):
177 # Start by replacing any reference to the container rootfs
178 line
.replace(orig
.get_config_item("lxc.rootfs"),
179 dest
.get_config_item("lxc.rootfs"))
181 fields
= line
.split()
183 # Skip invalid entries
187 # Non-bind mounts are kept as-is
188 if "bind" not in fields
[3]:
189 dest_fd
.write("%s\n" % line
)
192 # Bind mounts of virtual filesystems are also kept as-is
193 src_path
= fields
[0].split("/")
194 if len(src_path
) > 1 and src_path
[1] in ("proc", "sys"):
195 dest_fd
.write("%s\n" % line
)
198 # Skip invalid mount points
199 dest_mount
= os
.path
.abspath(os
.path
.join("%s/rootfs/" % (
200 dest_path
), fields
[1]))
202 if "%s/rootfs/" % dest_path
not in dest_mount
:
203 print(_("Skipping mount entry '%s' as it's outside "
204 "of the container rootfs.") % line
)
206 # Setup an overlay for anything remaining
207 overlay_dirs
+= [(fields
[0], dest_mount
)]
209 for entry
in args
.cdir
:
210 if not os
.path
.exists(entry
):
211 print(_("Path '%s' doesn't exist, won't be cow-mounted.") %
214 src_path
= os
.path
.abspath(entry
)
215 dst_path
= "%s/rootfs/%s" % (dest_path
, src_path
)
216 overlay_dirs
+= [(src_path
, dst_path
)]
218 # do we have the new overlay fs which requires workdir, or the older
219 # overlayfs which does not?
220 have_new_overlay
= False
221 with
open("/proc/filesystems", "r") as fd
:
223 if line
== "nodev\toverlay\n":
224 have_new_overlay
= True
226 # Generate pre-mount script
227 with
open(os
.path
.join(dest_path
, "pre-mount"), "w+") as fd
:
228 os
.fchmod(fd
.fileno(), 0o755)
229 fd
.write("""#!/bin/sh
233 """ % (dest_path
, orig
.name
, dest
.name
))
236 for entry
in overlay_dirs
:
237 tmpdir
= "%s/tmpfs" % dest_path
238 fd
.write("mkdir -p %s\n" % (tmpdir
))
239 if args
.storage_type
== "tmpfs":
240 fd
.write("mount -n -t tmpfs -o mode=0755 none %s\n" % (tmpdir
))
241 deltdir
= "%s/delta%s" % (tmpdir
, count
)
242 workdir
= "%s/work%s" % (tmpdir
, count
)
243 fd
.write("mkdir -p %s %s\n" % (deltdir
, entry
[1]))
245 fd
.write("mkdir -p %s\n" % workdir
)
247 fd
.write("getfacl -a %s | setfacl --set-file=- %s || true\n" %
249 fd
.write("getfacl -a %s | setfacl --set-file=- %s || true\n" %
250 (entry
[0], entry
[1]))
252 if args
.union_type
== "overlayfs":
254 fd
.write("mount -n -t overlay"
255 " -oupperdir=%s,lowerdir=%s,workdir=%s none %s\n" % (
261 fd
.write("mount -n -t overlayfs"
262 " -oupperdir=%s,lowerdir=%s none %s\n" % (
266 elif args
.union_type
== "aufs":
267 xino_path
= "/dev/shm/aufs.xino"
268 if not os
.path
.exists(os
.path
.basename(xino_path
)):
269 os
.makedirs(os
.path
.basename(xino_path
))
271 fd
.write("mount -n -t aufs "
272 "-o br=%s=rw:%s=ro,noplink,xino=%s none %s\n" % (
279 for entry
in args
.bdir
:
281 src_path
, dst_path
= entry
.split(":")
284 dst_path
= os
.path
.abspath(entry
)
286 if not os
.path
.exists(src_path
):
287 print(_("Path '%s' doesn't exist, won't be bind-mounted.") %
290 src_path
= os
.path
.abspath(src_path
)
291 dst_path
= "%s/rootfs/%s" % (dest_path
, dst_path
)
292 fd
.write("mkdir -p %s\nmount -n --bind %s %s\n" % (
293 dst_path
, src_path
, dst_path
))
296 [ -e $LXC_DIR/configured ] && exit 0
297 for file in $LXC_DIR/rootfs/etc/hostname \\
298 $LXC_DIR/rootfs/etc/hosts \\
299 $LXC_DIR/rootfs/etc/sysconfig/network \\
300 $LXC_DIR/rootfs/etc/sysconfig/network-scripts/ifcfg-eth0; do
301 [ -f "$file" ] && sed -i -e "s/$LXC_BASE/$LXC_NAME/" $file
303 touch $LXC_DIR/configured
306 dest
.set_config_item("lxc.hook.pre-mount",
307 os
.path
.join(dest_path
, "pre-mount"))
309 # Generate post-stop script
310 if not args
.keep_data
:
311 with
open(os
.path
.join(dest_path
, "post-stop"), "w+") as fd
:
312 os
.fchmod(fd
.fileno(), 0o755)
313 fd
.write("""#!/bin/sh
314 [ -d "%s" ] && rm -Rf "%s"
315 """ % (dest_path
, dest_path
))
317 dest
.set_config_item("lxc.hook.post-stop",
318 os
.path
.join(dest_path
, "post-stop"))
322 # Start the container
323 if not dest
.start() or not dest
.wait("RUNNING", timeout
=5):
324 print(_("The container '%s' failed to start.") % dest
.name
)
330 # Deal with the case where we just attach to the container's console
331 if not args
.command
and not args
.daemon
:
333 if not dest
.shutdown(timeout
=5):
337 # Try to get the IP addresses
338 ips
= dest
.get_ips(timeout
=10)
340 # Deal with the case where we just print info about the container
342 print(_("""The ephemeral container is now started.
344 You can enter it from the command line with: lxc-console -n %s
345 The following IP addresses have be found in the container:
347 "\n".join([" - %s" % entry
for entry
in ips
]
348 or [" - %s" % _("No address could be found")])))
351 # Now deal with the case where we want to run a command in the container
353 print(_("Failed to get an IP for container '%s'.") % dest
.name
)
359 if os
.path
.exists("/proc/self/ns/pid"):
360 def attach_as_user(command
):
366 user
= pwd
.getpwnam(username
)
367 os
.setgid(user
.pw_gid
)
368 os
.initgroups(user
.pw_name
, user
.pw_gid
)
369 os
.setuid(user
.pw_uid
)
370 os
.chdir(user
.pw_dir
)
371 os
.environ
['HOME'] = user
.pw_dir
373 print(_("Unable to switch to user: %s" % username
))
376 return lxc
.attach_run_command(command
)
378 retval
= dest
.attach_wait(attach_as_user
, args
.command
,
379 env_policy
=lxc
.LXC_ATTACH_CLEAR_ENV
)
383 "-o", "StrictHostKeyChecking=no",
384 "-o", "UserKnownHostsFile=/dev/null"]
387 cmd
+= ["-l", args
.user
]
390 cmd
+= ["-i", args
.key
]
393 ssh_cmd
= cmd
+ [ip
] + args
.command
394 retval
= subprocess
.call(ssh_cmd
, universal_newlines
=True)
396 print(_("SSH failed to connect, trying next IP address."))
400 print(_("Command returned with non-zero return code: %s") % retval
)
403 # Shutdown the container
404 if not dest
.shutdown(timeout
=5):