4 ## THIS FILE IS UNDER PUPPET CONTROL. DON'T EDIT IT HERE.
5 ## USE: git clone git+ssh://$USER@puppet.debian.org/srv/puppet.debian.org/git/dsa-puppet.git
9 # Copyright (c) 2013 Peter Palfrader <peter@palfrader.org>
11 # Permission is hereby granted, free of charge, to any person obtaining
12 # a copy of this software and associated documentation files (the
13 # "Software"), to deal in the Software without restriction, including
14 # without limitation the rights to use, copy, modify, merge, publish,
15 # distribute, sublicense, and/or sell copies of the Software, and to
16 # permit persons to whom the Software is furnished to do so, subject to
17 # the following conditions:
19 # The above copyright notice and this permission notice shall be
20 # included in all copies or substantial portions of the Software.
22 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 # script to allow otherwise unprivileged users to do certain
31 # apt commands in schroot environments.
34 # - ownership of the schroot session is only checked at the beginning.
35 # This means that if the original user deleted it, and then somebody
36 # else comes along and creates a session of the same name, they might
37 # get some of our commands run in there.
52 SCHROOT_SUPER = 'root'
55 print >> sys.stderr, s
58 def get_session_owner(session):
59 if re.search('[^0-9a-zA-Z_-]', session):
60 die("Invalid session name.")
62 path = os.path.join('/var/lib/schroot/session', session)
63 config = ConfigParser.RawConfigParser()
67 owner.append(config.get(session, 'users'))
68 owner.append(config.get(session, 'root-users'))
69 except ConfigParser.NoSectionError:
70 die("Did not find session definition in session file.")
71 except ConfigParser.NoOptionError:
72 die("Did not find user information in session file.")
76 def ensure_ok(session):
77 if 'SUDO_USER' not in os.environ:
78 die("Cannot find SUDO_USER in environment.")
79 if not os.environ['SUDO_USER'] in get_session_owner(session):
80 die("Session owner mismatch.")
82 def os_supports_unshare():
83 if platform.uname()[0] == 'GNU/kFreeBSD':
87 class WrappedRunner():
88 def __init__(self, session, args, unshare=True):
89 self.unshare = unshare
90 if not os_supports_unshare(): self.unshare = False
91 s,r = self.run('schroot', '-c', session, '--run-session', '--', 'env', 'DEBIAN_FRONTEND=noninteractive', *args)
93 die("Command %s exited due to signal %d."%(' '.join(args), s))
95 die("Command %s exited with exit code %d."%(' '.join(args), r))
99 signal = status & 0xff
100 if signal == 0: retcode = status > 8
102 return signal, retcode
106 cmdstr = ' '.join(pipes.quote(s) for s in cmd)
107 cmd = ['unshare', '--uts', '--ipc', '--net', '--']
108 cmd += ['sh', '-c', 'ip addr add 127.0.0.1/8 dev lo && ip link set dev lo up && %s'%(cmdstr)]
110 pid, ptyfd = pty.fork()
113 fd = os.open("/dev/null", os.O_RDWR)
114 os.dup2(fd, 0) # stdin
115 os.dup2(w, 1) # stdout
116 os.dup2(w, 2) # stderr
117 os.execlp(cmd[0], *cmd)
128 os.close(ptyfd) # we don't care about that one
129 p,v = os.waitpid(pid, 0)
130 s,r = WrappedRunner.get_ret(v)
134 APT_DRY = ['apt-get', '--dry-run']
135 APT_REAL = ['apt-get', '--assume-yes', '-o', 'Dpkg::Options::=--force-confnew']
137 def __init__(self, options, args):
138 self.session = options.chroot
139 self.assume_yes = options.assume_yes
141 die("No operation given for apt.")
146 self.ensure_no_extra_args()
148 elif op == "upgrade":
149 self.ensure_no_extra_args()
151 elif op == "dist-upgrade":
152 self.ensure_no_extra_args()
153 self.apt_dist_upgrade()
154 elif op == "install":
155 self.apt_install(args)
156 elif op == "build-dep":
157 self.apt_build_dep(args)
159 die("Invalid operation %s"%(op,))
161 def ensure_no_extra_args(self):
162 if len(self.args) > 0:
163 die("superfluous arguments: %s"%(' '.join(self.args),))
165 def apt_update(self):
166 self.secure_run(AptSchroot.APT_REAL +['update'], unshare=False)
168 def apt_upgrade(self):
169 self.apt_simulate_and_ask(['upgrade'])
171 def apt_dist_upgrade(self):
172 self.apt_simulate_and_ask(['dist-upgrade'])
174 def apt_install(self, packages):
175 self.apt_simulate_and_ask(['install', '--'] + packages)
177 def apt_build_dep(self, packages):
178 self.apt_simulate_and_ask(['build-dep', '--'] + packages)
180 def apt_simulate_and_ask(self, cmd, split_download=True, run_clean=True):
181 if not self.assume_yes:
182 self.secure_run(AptSchroot.APT_DRY + cmd)
183 ans = raw_input("Do it for real [Y/n]: ")
184 if ans.lower() == 'n': sys.exit(0)
186 self.secure_run(AptSchroot.APT_REAL + ['--download-only'] + cmd, unshare=False)
187 self.secure_run(AptSchroot.APT_REAL + cmd)
189 self.secure_run(AptSchroot.APT_REAL + ['clean'])
191 def secure_run(self, args, unshare=True):
192 WrappedRunner(self.session, args, unshare)
195 parser = optparse.OptionParser()
196 parser.set_usage("""%prog [options] -c <session-chroot> [-y] -- <command>
201 apt-get install <package> ...
202 apt-get build-dep <package> ...""")
203 parser.add_option("-c", "--chroot", metavar="chroot", dest="chroot",
204 help="Which chroot to act on")
205 parser.add_option("-y", "--assume-yes", dest="assume_yes", default=False,
206 action="store_true", help="Assume yes on confirm questions.")
208 (options, args) = parser.parse_args()
210 if len(args) < 1 or options.chroot is None:
214 if os.getuid() != SCHROOT_SUPER_UID:
215 os.execlp('sudo', 'sudo', '-u', SCHROOT_SUPER, '--', *sys.argv)
217 ensure_ok(options.chroot)
219 command = args.pop(0)
220 if command == "apt-get":
221 AptSchroot(options, args)
223 die("Invalid command: %s."%(command,))