]>
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,
88 help=_("directory to bind mount into container"))
90 parser
.add_argument("--user", "-u", type=str,
91 help=_("the user to run the command as"))
93 parser
.add_argument("--key", "-S", type=str,
94 help=_("the path to the key to use to connect "
97 parser
.add_argument("--daemon", "-d", action
="store_true",
98 help=_("run in the background"))
100 parser
.add_argument("--storage-type", "-s", type=str, default
=None,
101 choices
=("tmpfs", "dir"),
102 help=("type of storage use by the container"))
104 parser
.add_argument("--union-type", "-U", type=str, default
="overlayfs",
105 choices
=("overlayfs", "aufs"),
106 help=_("type of union (overlayfs or aufs), "
107 "defaults to overlayfs."))
109 parser
.add_argument("--keep-data", "-k", action
="store_true",
110 help=_("don't wipe everything clean at the end"))
112 parser
.add_argument("command", metavar
='CMD', type=str, nargs
="*",
113 help=_("Run specific command in container "
114 "(command as argument)"))
116 parser
.add_argument("--version", action
="version", version
=lxc
.version
)
118 args
= parser
.parse_args()
120 # Basic requirements check
121 ## We only support privileged containers for now
122 if os
.geteuid() != 0 and args
.union_type
!= "overlayfs":
123 parser
.error(_("Unprivileged containers may only use "
124 "overlayfs at this time."))
126 ## Check that -d and CMD aren't used at the same time
127 if args
.command
and args
.daemon
:
128 parser
.error(_("You can't use -d and a command at the same time."))
130 ## Check that -k isn't used with -s tmpfs
131 if not args
.storage_type
:
133 args
.storage_type
= "dir"
135 args
.storage_type
= "tmpfs"
137 if args
.keep_data
and args
.storage_type
== "tmpfs":
138 parser
.error(_("You can't use -k with the tmpfs storage type."))
140 # Load the orig container
141 orig
= lxc
.Container(args
.orig
, args
.lxcpath
)
143 parser
.error(_("Source container '%s' doesn't exist." % args
.orig
))
145 # Create the new container paths
147 lxc_path
= lxc
.default_config_path
149 lxc_path
= args
.lxcpath
152 if os
.path
.exists("%s/%s" % (lxc_path
, args
.name
)):
153 parser
.error(_("A container named '%s' already exists." % args
.name
))
154 dest_path
= "%s/%s" % (lxc_path
, args
.name
)
157 dest_path
= tempfile
.mkdtemp(prefix
="%s-" % args
.orig
, dir=lxc_path
)
158 os
.mkdir(os
.path
.join(dest_path
, "rootfs"))
160 # Setup the new container's configuration
161 dest
= lxc
.Container(os
.path
.basename(dest_path
), args
.lxcpath
)
162 dest
.load_config(orig
.config_file_name
)
163 dest
.set_config_item("lxc.utsname", dest
.name
)
164 dest
.set_config_item("lxc.rootfs", 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 # Generate pre-mount script
210 with
open(os
.path
.join(dest_path
, "pre-mount"), "w+") as fd
:
211 os
.fchmod(fd
.fileno(), 0o755)
212 fd
.write("""#!/bin/sh
216 """ % (dest_path
, orig
.name
, dest
.name
))
219 for entry
in overlay_dirs
:
220 target
= "%s/delta%s" % (dest_path
, count
)
221 fd
.write("mkdir -p %s %s\n" % (target
, entry
[1]))
223 if args
.storage_type
== "tmpfs":
224 fd
.write("mount -n -t tmpfs -o mode=0755 none %s\n" % (target
))
226 if args
.union_type
== "overlayfs":
227 fd
.write("mount -n -t overlayfs"
228 " -oupperdir=%s,lowerdir=%s none %s\n" % (
232 elif args
.union_type
== "aufs":
233 xino_path
= "%s/lxc/aufs.xino" % get_rundir()
234 if not os
.path
.exists(os
.path
.basename(xino_path
)):
235 os
.makedirs(os
.path
.basename(xino_path
))
237 fd
.write("mount -n -t aufs "
238 "-o br=%s=rw:%s=ro,noplink,xino=%s none %s\n" % (
246 if not os
.path
.exists(args
.bdir
):
247 print(_("Path '%s' doesn't exist, won't be bind-mounted.") %
250 src_path
= os
.path
.abspath(args
.bdir
)
251 dst_path
= "%s/rootfs/%s" % (dest_path
, os
.path
.abspath(args
.bdir
))
252 fd
.write("mkdir -p %s\nmount -n --bind %s %s\n" % (
253 dst_path
, src_path
, dst_path
))
256 [ -e $LXC_DIR/configured ] && exit 0
257 for file in $LXC_DIR/rootfs/etc/hostname \\
258 $LXC_DIR/rootfs/etc/hosts \\
259 $LXC_DIR/rootfs/etc/sysconfig/network \\
260 $LXC_DIR/rootfs/etc/sysconfig/network-scripts/ifcfg-eth0; do
261 [ -f "$file" ] && sed -i -e "s/$LXC_BASE/$LXC_NAME/" $file
263 touch $LXC_DIR/configured
266 dest
.set_config_item("lxc.hook.pre-mount",
267 os
.path
.join(dest_path
, "pre-mount"))
269 # Generate post-stop script
270 if not args
.keep_data
:
271 with
open(os
.path
.join(dest_path
, "post-stop"), "w+") as fd
:
272 os
.fchmod(fd
.fileno(), 0o755)
273 fd
.write("""#!/bin/sh
274 [ -d "%s" ] && rm -Rf "%s"
275 """ % (dest_path
, dest_path
))
277 dest
.set_config_item("lxc.hook.post-stop",
278 os
.path
.join(dest_path
, "post-stop"))
282 # Start the container
283 if not dest
.start() or not dest
.wait("RUNNING", timeout
=5):
284 print(_("The container '%s' failed to start.") % dest
.name
)
290 # Deal with the case where we just attach to the container's console
291 if not args
.command
and not args
.daemon
:
293 if not dest
.shutdown(timeout
=5):
297 # Try to get the IP addresses
298 ips
= dest
.get_ips(timeout
=10)
300 # Deal with the case where we just print info about the container
302 print(_("""The ephemeral container is now started.
304 You can enter it from the command line with: lxc-console -n %s
305 The following IP addresses have be found in the container:
307 "\n".join([" - %s" % entry
for entry
in ips
]
308 or [" - %s" % _("No address could be found")])))
311 # Now deal with the case where we want to run a command in the container
313 print(_("Failed to get an IP for container '%s'.") % dest
.name
)
319 if os
.path
.exists("/proc/self/ns/pid"):
320 def attach_as_user(command
):
326 user
= pwd
.getpwnam(username
)
327 os
.setgid(user
.pw_gid
)
328 os
.initgroups(user
.pw_name
, user
.pw_gid
)
329 os
.setuid(user
.pw_uid
)
330 os
.chdir(user
.pw_dir
)
331 os
.environ
['HOME'] = user
.pw_dir
333 print(_("Unable to switch to user: %s" % username
))
336 return lxc
.attach_run_command(command
)
338 retval
= dest
.attach_wait(attach_as_user
, args
.command
,
339 env_policy
=lxc
.LXC_ATTACH_CLEAR_ENV
)
343 "-o", "StrictHostKeyChecking=no",
344 "-o", "UserKnownHostsFile=/dev/null"]
347 cmd
+= ["-l", args
.user
]
350 cmd
+= ["-i", args
.key
]
353 ssh_cmd
= cmd
+ [ip
] + args
.command
354 retval
= subprocess
.call(ssh_cmd
, universal_newlines
=True)
356 print(_("SSH failed to connect, trying next IP address."))
360 print(_("Command returned with non-zero return code: %s") % retval
)
363 # Shutdown the container
364 if not dest
.shutdown(timeout
=5):