mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-01-21 07:55:00 +00:00
Externalized ExMachina again, partially reverting change:
f1e764f2e5728113f191456236d02fdae6e7680a
Partially revert the EM integration change, it's not solid yet. This
allows EM to grow on its own. However, I'm not reverting the whole
change because I want to make it easy to use EM from an external
repository, and most of the Plinth-specific changes are good. To use
EM in Plinth again, make sure EM and Plinth are in the same directory
before running start.sh. The directory structure should look like:
./exmachina/
./plinth/
start.sh updates Python's path correctly, so this change should be
transparent and Plinth should still run the same.
This commit is contained in:
parent
88225aa3ae
commit
fed5bd13f1
@ -1,59 +0,0 @@
|
||||
|
||||
HOWTO: Use exmachina in Plinth to Change Configuration and Restart Services
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
exmachina is the name of a small and simple privilege separation layer that
|
||||
lets Plinth (running as a web application with user/group www-data) edit
|
||||
FreedomBox system configuration (which requires root privileges). The Augeas
|
||||
library is used to provide a uniform interface to *most* configuration files in
|
||||
/etc/, and a wrapper around init.d scripts allows services to be restarted
|
||||
(which is often the way to have configuration changes go live).
|
||||
|
||||
The exmachina daemon and client library is initialized when Plinth starts, and
|
||||
is accessible in Plinth code through the "cfg" global variable:
|
||||
|
||||
import cfg
|
||||
cfg.exmachina.initd.restart("networking")
|
||||
|
||||
Existing on-disk configuration settings can be read back through the "get"
|
||||
action, and files/settings can be found with the "match" method. For example,
|
||||
the following would print out a list of all the files in /etc/ with
|
||||
augeas-editing support, and then print the system's hostname according to
|
||||
/etc/hostname (a minimal one-line configuration file):
|
||||
|
||||
for fname in c.call.augeas_match("/files/etc/*"):
|
||||
print fname
|
||||
cfg.exmachina.augeas.get("/files/etc/hostname/*")
|
||||
|
||||
The mutating Augeas actions ("set", "insert", "remove", etc) operate on an
|
||||
in-memory cache of the configuration files; to commit all changes (for all
|
||||
modified files) to disk, use the "save" action. The following would set the
|
||||
local hostname to fluffy.example.net, and then commit that change to disk.
|
||||
|
||||
cfg.exmachina.augeas.set("/files/etc/hostname/*", "fluffy.example.net")
|
||||
cfg.exmachina.augeas.save()
|
||||
|
||||
After the above, the /etc/hostname file will be updated, but running network
|
||||
services will not have picked up on the change. To have changes that have been
|
||||
written to disk actually take effect, the "initd" module allows access to
|
||||
restarting system services. In the case of hostnames, in debian there exists a
|
||||
helper script called "/etc/init.d/hostname.sh", so the following would be used
|
||||
to update all network daemons to a new hostname:
|
||||
|
||||
cfg.exmachina.initd.restart("hostname.sh")
|
||||
|
||||
Augeas makes some effort to prevent partial writes or configuration file
|
||||
corruption, and in some cases provides basic type checking. However, it does
|
||||
not do any logical error detection or correction. For example, two separate
|
||||
services could be configured to try and listen on the same port, or an invalid
|
||||
hostname could be specified (eg, "&.^$#"). Application code in Plinth is
|
||||
responsible for ensuring the logical correctness of configuration changes.
|
||||
|
||||
If there is a serious problem executing an exmachina method call (eg, tried to
|
||||
restart a non-existent service), a bjsonrpc.exceptions.ServerError exception
|
||||
will be raised, with the actual exception message (but not the traceback)
|
||||
passed through.
|
||||
|
||||
Reading:
|
||||
- Augeas API notes: http://augeas.net/docs/api.html
|
||||
- exmachina repository: https://github.com/bnewbold/exmachina
|
||||
@ -1,437 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Author: bnewbold <bnewbold@robocracy.org>
|
||||
Date: July 2012
|
||||
License: GPLv3 (see http://www.gnu.org/licenses/gpl-3.0.html)
|
||||
(two helper functions copied from web, as cited below)
|
||||
Package Requirements: python-augeas, bjsonrpc
|
||||
|
||||
This file implements both ends (privilaged daemon and unprivilaged python
|
||||
client library) of a crude system configuration message bus, intended for use
|
||||
(initially) with the Plinth web interface to the FreedomBox operating system.
|
||||
|
||||
The goal is to provide partially-untrusted processes (such as the web interface
|
||||
running as the www-data user) access to core system configuration files
|
||||
(through the Augeas library) and daemon control (through the init.d scripts).
|
||||
|
||||
The daemon process (started in the same startup script as Plinth) runs as root
|
||||
and accepts JSON-RPC method calls through a unix domain socket
|
||||
(/tmp/exmachina.sock by default). Because file access control may not be
|
||||
sufficiently flexible for access control, a somewhat-elaborate secret key
|
||||
mechanism can be used to control access to the RPC mechanism.
|
||||
|
||||
The (optional) shared secret-key mechanism requires clients to first call the
|
||||
"authenticate" RPC method before any other methods. The secret key is passed to
|
||||
the server process through stdin at startup (command line arguments could be
|
||||
snooped by unprivilaged processes), and would presumably be passed on to the
|
||||
client in the same way. The init_test.sh script demonstrates this mechanism.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import grp
|
||||
import shutil
|
||||
import argparse
|
||||
import logging
|
||||
import socket
|
||||
import subprocess
|
||||
import time
|
||||
import base64
|
||||
|
||||
import bjsonrpc
|
||||
import bjsonrpc.handlers
|
||||
import bjsonrpc.server
|
||||
import augeas
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def execute_service(servicename, action, timeout=10):
|
||||
"""This function mostly ripped from StackOverflow:
|
||||
http://stackoverflow.com/questions/1556348/python-run-a-process-with-timeout-and-capture-stdout-stderr-and-exit-status
|
||||
"""
|
||||
# ensure service name isn't tricky trick
|
||||
script = "/etc/init.d/" + os.path.split(servicename)[1]
|
||||
|
||||
if not os.path.exists(script):
|
||||
raise ValueError("so such service: %s" % servicename)
|
||||
|
||||
command_list = [script, action]
|
||||
log.info("executing: %s" % command_list)
|
||||
proc = subprocess.Popen(command_list,
|
||||
bufsize=0,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
poll_seconds = .250
|
||||
deadline = time.time() + timeout
|
||||
while time.time() < deadline and proc.poll() is None:
|
||||
time.sleep(poll_seconds)
|
||||
|
||||
if proc.poll() is None:
|
||||
if float(sys.version[:3]) >= 2.6:
|
||||
proc.terminate()
|
||||
raise Exception("execution timed out (>%d seconds): %s" %
|
||||
(timeout, command_list))
|
||||
|
||||
stdout, stderr = proc.communicate()
|
||||
# TODO: should raise exception here if proc.returncode != 0?
|
||||
return stdout, stderr, proc.returncode
|
||||
|
||||
def execute_apt(packagename, action, timeout=120, aptargs=['-q', '-y']):
|
||||
# ensure package name isn't tricky trick
|
||||
if action != "update" \
|
||||
and (packagename != packagename.strip().split()[0] \
|
||||
or packagename.startswith('-')):
|
||||
raise ValueError("Not a good apt package name: %s" % packagename)
|
||||
|
||||
if action == "update":
|
||||
command_list = ['apt-get', action]
|
||||
else:
|
||||
command_list = ['apt-get', action, packagename]
|
||||
command_list.extend(aptargs)
|
||||
log.info("executing: %s" % command_list)
|
||||
proc = subprocess.Popen(command_list,
|
||||
bufsize=0,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
poll_seconds = .250
|
||||
deadline = time.time() + timeout
|
||||
while time.time() < deadline and proc.poll() is None:
|
||||
time.sleep(poll_seconds)
|
||||
|
||||
if proc.poll() is None:
|
||||
if float(sys.version[:3]) >= 2.6:
|
||||
proc.terminate()
|
||||
raise Exception("execution timed out (>%d seconds): %s" %
|
||||
(timeout, command_list))
|
||||
|
||||
stdout, stderr = proc.communicate()
|
||||
return stdout, stderr, proc.returncode
|
||||
|
||||
|
||||
class ExMachinaHandler(bjsonrpc.handlers.BaseHandler):
|
||||
|
||||
# authentication state variable. If not None, still need to authenticate;
|
||||
# if None then authentication not require or was already successful for
|
||||
# this instantiation of the Handler. This class variable gets optionally
|
||||
# overridden on a per-process basis
|
||||
secret_key = None
|
||||
|
||||
def _setup(self):
|
||||
self.augeas = augeas.Augeas()
|
||||
|
||||
def authenticate(self, secret_key):
|
||||
if not self.secret_key:
|
||||
log.warn("Unecessary authentication attempt")
|
||||
return
|
||||
if not secret_key.strip() == self.secret_key.strip():
|
||||
# fail hard
|
||||
log.error("Authentication failed!")
|
||||
sys.exit()
|
||||
self.secret_key = None
|
||||
|
||||
# ------------- Augeas API Passthrough -----------------
|
||||
def augeas_save(self):
|
||||
if not self.secret_key:
|
||||
log.info("augeas: saving config")
|
||||
return self.augeas.save()
|
||||
|
||||
def augeas_set(self, path, value):
|
||||
if not self.secret_key:
|
||||
log.info("augeas: set %s=%s" % (path, value))
|
||||
return self.augeas.set(path.encode('utf-8'),
|
||||
value.encode('utf-8'))
|
||||
|
||||
def augeas_setm(self, base, sub, value):
|
||||
if not self.secret_key:
|
||||
log.info("augeas: setm %s %s = %s" % (base, sub, value))
|
||||
return self.augeas.setm(base.encode('utf-8'),
|
||||
sub.encode('utf-8'),
|
||||
value.encode('utf-8'))
|
||||
|
||||
def augeas_get(self, path):
|
||||
if not self.secret_key:
|
||||
# reduce verbosity
|
||||
log.debug("augeas: get %s" % path)
|
||||
return self.augeas.get(path.encode('utf-8'))
|
||||
|
||||
def augeas_match(self, path):
|
||||
if not self.secret_key:
|
||||
# reduce verbosity
|
||||
log.debug("augeas: match %s" % path)
|
||||
return self.augeas.match("%s" % path.encode('utf-8'))
|
||||
|
||||
def augeas_insert(self, path, label, before=True):
|
||||
if not self.secret_key:
|
||||
log.info("augeas: insert %s=%s" % (path, value))
|
||||
return self.augeas.insert(path.encode('utf-8'),
|
||||
label.encode('utf-8'),
|
||||
before=before)
|
||||
|
||||
def augeas_move(self, src, dst):
|
||||
if not self.secret_key:
|
||||
log.info("augeas: move %s -> %s" % (src, dst))
|
||||
return self.augeas.move(src.encode('utf-8'), dst.encode('utf-8'))
|
||||
|
||||
def augeas_remove(self, path):
|
||||
if not self.secret_key:
|
||||
log.info("augeas: remove %s" % path)
|
||||
return self.augeas.remove(path.encode('utf-8'))
|
||||
|
||||
# ------------- Misc. non-Augeas Helpers -----------------
|
||||
def set_timezone(self, tzname):
|
||||
if not self.secret_key:
|
||||
log.info("reset timezone to %s" % tzname)
|
||||
tzname = tzname.strip()
|
||||
tzpath = os.path.join("/usr/share/zoneinfo", tzname)
|
||||
try:
|
||||
os.stat(tzpath)
|
||||
except OSError:
|
||||
# file not found
|
||||
raise ValueError("timezone not valid: %s" % tzname)
|
||||
shutil.copy(
|
||||
os.path.join("/usr/share/zoneinfo", tzname),
|
||||
"/etc/localtime")
|
||||
with open("/etc/timezone", "w") as tzfile:
|
||||
tzfile.write(tzname + "\n")
|
||||
return "timezone changed to %s" % tzname
|
||||
|
||||
# ------------- init.d Service Control -----------------
|
||||
def initd_status(self, servicename):
|
||||
if not self.secret_key:
|
||||
return execute_service(servicename, "status")
|
||||
|
||||
def initd_start(self, servicename):
|
||||
if not self.secret_key:
|
||||
return execute_service(servicename, "start")
|
||||
|
||||
def initd_stop(self, servicename):
|
||||
if not self.secret_key:
|
||||
return execute_service(servicename, "stop")
|
||||
|
||||
def initd_restart(self, servicename):
|
||||
if not self.secret_key:
|
||||
return execute_service(servicename, "restart")
|
||||
|
||||
# ------------- apt-get Package Control -----------------
|
||||
def apt_install(self, packagename):
|
||||
if not self.secret_key:
|
||||
return execute_apt(packagename, "install")
|
||||
|
||||
def apt_update(self):
|
||||
if not self.secret_key:
|
||||
return execute_apt("", "update")
|
||||
|
||||
def apt_remove(self, packagename):
|
||||
if not self.secret_key:
|
||||
return execute_apt(packagename, "remove")
|
||||
|
||||
|
||||
class EmptyClass():
|
||||
# Used by ExMachinaClient below
|
||||
pass
|
||||
|
||||
|
||||
class ExMachinaClient():
|
||||
"""Simple client wrapper library to expose augeas and init.d methods.
|
||||
|
||||
In brief, use augeas.get/set/insert to modify system configuration files
|
||||
under the /files/etc/* namespace. augeas.match with a wildcard can be used
|
||||
to find variables to edit.
|
||||
|
||||
After making any changes, use augeas.save to commit to disk, then
|
||||
initd.restart to restart the appropriate system daemons. In many cases,
|
||||
this would be the 'networking' meta-daemon.
|
||||
|
||||
See test_exmachina.py for some simple examples; see the augeas docs for
|
||||
more in depth guidance.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
socket_path="/tmp/exmachina.sock",
|
||||
secret_key=None):
|
||||
self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
self.sock.connect(socket_path)
|
||||
self.conn = bjsonrpc.connection.Connection(self.sock)
|
||||
|
||||
if secret_key:
|
||||
self.conn.call.authenticate(secret_key)
|
||||
|
||||
self.augeas = EmptyClass()
|
||||
self.initd = EmptyClass()
|
||||
self.apt = EmptyClass()
|
||||
self.misc = EmptyClass()
|
||||
|
||||
self.augeas.save = self.conn.call.augeas_save
|
||||
self.augeas.set = self.conn.call.augeas_set
|
||||
self.augeas.setm = self.conn.call.augeas_setm
|
||||
self.augeas.get = self.conn.call.augeas_get
|
||||
self.augeas.match = self.conn.call.augeas_match
|
||||
self.augeas.insert = self.conn.call.augeas_insert
|
||||
self.augeas.move = self.conn.call.augeas_move
|
||||
self.augeas.remove = self.conn.call.augeas_remove
|
||||
self.initd.status = self.conn.call.initd_status
|
||||
self.initd.start = self.conn.call.initd_start
|
||||
self.initd.stop = self.conn.call.initd_stop
|
||||
self.initd.restart = self.conn.call.initd_restart
|
||||
self.apt.install = self.conn.call.apt_install
|
||||
self.apt.update = self.conn.call.apt_update
|
||||
self.apt.remove = self.conn.call.apt_remove
|
||||
self.misc.set_timezone = self.conn.call.set_timezone
|
||||
|
||||
def close(self):
|
||||
self.sock.close()
|
||||
|
||||
|
||||
def run_server(socket_path, secret_key=None, socket_group=None):
|
||||
|
||||
if not 0 == os.geteuid():
|
||||
log.warn("Expected to be running as root!")
|
||||
|
||||
if os.path.exists(socket_path):
|
||||
os.unlink(socket_path)
|
||||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
sock.bind(socket_path)
|
||||
sock.listen(1)
|
||||
|
||||
if socket_group is not None:
|
||||
socket_uid = os.stat(socket_path).st_uid
|
||||
socket_gid = grp.getgrnam(socket_group).gr_gid
|
||||
os.chmod(socket_path, 0660)
|
||||
os.chown(socket_path, socket_uid, socket_gid)
|
||||
else:
|
||||
os.chmod(socket_path, 0666)
|
||||
if secret_key:
|
||||
ExMachinaHandler.secret_key = secret_key
|
||||
|
||||
serv = bjsonrpc.server.Server(sock, handler_factory=ExMachinaHandler)
|
||||
serv.serve()
|
||||
|
||||
def daemonize(stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
|
||||
"""
|
||||
From: http://www.noah.org/wiki/Daemonize_Python
|
||||
|
||||
This forks the current process into a daemon. The stdin, stdout, and
|
||||
stderr arguments are file names that will be opened and be used to replace
|
||||
the standard file descriptors in sys.stdin, sys.stdout, and sys.stderr.
|
||||
These arguments are optional and default to /dev/null. Note that stderr is
|
||||
opened unbuffered, so if it shares a file with stdout then interleaved
|
||||
output may not appear in the order that you expect. """
|
||||
|
||||
# Do first fork.
|
||||
try:
|
||||
pid = os.fork()
|
||||
if pid > 0:
|
||||
sys.exit(0) # Exit first parent.
|
||||
except OSError, e:
|
||||
sys.stderr.write("fork #1 failed: (%d) %s\n" % (e.errno, e.strerror))
|
||||
sys.exit(1)
|
||||
|
||||
# Decouple from parent environment.
|
||||
os.chdir("/")
|
||||
os.umask(0)
|
||||
os.setsid()
|
||||
|
||||
# Do second fork.
|
||||
try:
|
||||
pid = os.fork()
|
||||
if pid > 0:
|
||||
sys.exit(0) # Exit second parent.
|
||||
except OSError, e:
|
||||
sys.stderr.write("fork #2 failed: (%d) %s\n" % (e.errno, e.strerror))
|
||||
sys.exit(1)
|
||||
|
||||
# Now I am a daemon!
|
||||
|
||||
# Redirect standard file descriptors.
|
||||
si = open(stdin, 'r')
|
||||
so = open(stdout, 'a+')
|
||||
se = open(stderr, 'a+', 0)
|
||||
os.dup2(si.fileno(), sys.stdin.fileno())
|
||||
os.dup2(so.fileno(), sys.stdout.fileno())
|
||||
os.dup2(se.fileno(), sys.stderr.fileno())
|
||||
return pid
|
||||
|
||||
# =============================================================================
|
||||
# Command line handling
|
||||
def main():
|
||||
|
||||
global log
|
||||
parser = argparse.ArgumentParser(usage=
|
||||
"usage: %prog [options]\n"
|
||||
"%prog --help for more info."
|
||||
)
|
||||
parser.add_argument("-v", "--verbose",
|
||||
default=False,
|
||||
help="Show more debugging statements",
|
||||
action="store_true")
|
||||
parser.add_argument("-q", "--quiet",
|
||||
default=False,
|
||||
help="Show fewer informational statements",
|
||||
action="store_true")
|
||||
parser.add_argument("-k", "--key",
|
||||
default=False,
|
||||
help="Wait for Secret Access Key on stdin before starting",
|
||||
action="store_true")
|
||||
parser.add_argument("--random-key",
|
||||
default=False,
|
||||
help="Just dump a random base64 key and exit",
|
||||
action="store_true")
|
||||
parser.add_argument("-s", "--socket-path",
|
||||
default="/tmp/exmachina.sock",
|
||||
help="UNIX Domain socket file path to listen on",
|
||||
metavar="FILE")
|
||||
parser.add_argument("--pidfile",
|
||||
default=None,
|
||||
help="Daemonize and write pid to this file",
|
||||
metavar="FILE")
|
||||
parser.add_argument("-g", "--group",
|
||||
default=None,
|
||||
help="chgrp socket file to this group and set 0660 permissions")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
#if len(args) != 0:
|
||||
#parser.error("Incorrect number of arguments")
|
||||
|
||||
if args.random_key:
|
||||
sys.stdout.write(base64.urlsafe_b64encode(os.urandom(128)))
|
||||
sys.exit(0)
|
||||
|
||||
log = logging.getLogger()
|
||||
hdlr = logging.StreamHandler()
|
||||
formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
|
||||
hdlr.setFormatter(formatter)
|
||||
log.addHandler(hdlr)
|
||||
|
||||
if args.verbose:
|
||||
log.setLevel(logging.DEBUG)
|
||||
elif args.quiet:
|
||||
log.setLevel(logging.ERROR)
|
||||
else:
|
||||
log.setLevel(logging.INFO)
|
||||
|
||||
secret_key = None
|
||||
if args.key:
|
||||
log.debug("Waiting for secret key on stdin...")
|
||||
secret_key = sys.stdin.readline().strip()
|
||||
log.debug("Got it!")
|
||||
|
||||
if args.pidfile:
|
||||
with open(args.pidfile, 'w') as pfile:
|
||||
# ensure file is available/writable
|
||||
pass
|
||||
os.unlink(args.pidfile)
|
||||
daemonize()
|
||||
pid = os.getpid()
|
||||
with open(args.pidfile, 'w') as pfile:
|
||||
pfile.write("%s" % pid)
|
||||
log.info("Daemonized, pid is %s" % pid)
|
||||
|
||||
run_server(secret_key=secret_key,
|
||||
socket_path=args.socket_path,
|
||||
socket_group=args.group)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@ -1,13 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Test init.d-style initialization; run this script as root (or sudo it)
|
||||
|
||||
export key=`./exmachina.py --random-key`
|
||||
|
||||
echo $key | ./exmachina.py -vk --pidfile /tmp/exmachina_test.pid -g www-data
|
||||
sleep 1
|
||||
echo $key | sudo -u www-data -g www-data ./test_exmachina.py -k
|
||||
|
||||
kill `cat /tmp/exmachina_test.pid` && rm /tmp/exmachina_test.pid
|
||||
sleep 1
|
||||
jobs
|
||||
@ -1,82 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
This file tests the "client side" of the exmachina layer.
|
||||
|
||||
To use with secret keys, do the following in seperate terminals:
|
||||
|
||||
$ echo "<key>" | sudo ./exmachina.py -vk
|
||||
$ echo "<key>" | ./test_exmachina.py -k
|
||||
|
||||
To use without, do the following in seperate terminals:
|
||||
|
||||
$ sudo ./exmachina.py -v
|
||||
$ ./test_exmachina.py
|
||||
|
||||
Use the init_test.sh script to test shared key passing and privilage seperation
|
||||
at the same time:
|
||||
|
||||
$ sudo ./init_test.sh
|
||||
"""
|
||||
|
||||
import sys
|
||||
import socket
|
||||
|
||||
import bjsonrpc
|
||||
import bjsonrpc.connection
|
||||
from bjsonrpc.exceptions import ServerError
|
||||
|
||||
from exmachina import ExMachinaClient
|
||||
|
||||
# =============================================================================
|
||||
# Command line handling
|
||||
def main():
|
||||
|
||||
socket_path = "/tmp/exmachina.sock"
|
||||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
sock.connect(socket_path)
|
||||
|
||||
secret_key = None
|
||||
if sys.argv[-1] == "-k":
|
||||
print "waiting for key on stdin..."
|
||||
secret_key = sys.stdin.readline()
|
||||
print "sent!"
|
||||
|
||||
print "========= Testing JSON-RPC connection"
|
||||
c = bjsonrpc.connection.Connection(sock)
|
||||
if secret_key:
|
||||
c.call.authenticate(secret_key)
|
||||
print "/*: %s" % c.call.augeas_match("/*")
|
||||
print "/augeas/*: %s" % c.call.augeas_match("/augeas/*")
|
||||
print "/etc/* files:"
|
||||
for name in c.call.augeas_match("/files/etc/*"):
|
||||
print "\t%s" % name
|
||||
print c.call.initd_status("bluetooth")
|
||||
print "hostname: %s" % c.call.augeas_get("/files/etc/hostname/*")
|
||||
print "localhost: %s" % c.call.augeas_get("/files/etc/hosts/1/canonical")
|
||||
sock.close()
|
||||
|
||||
print "========= Testing user client library"
|
||||
client = ExMachinaClient(secret_key=secret_key)
|
||||
print client.augeas.match("/files/etc/*")
|
||||
#print client.initd.restart("bluetooth")
|
||||
try:
|
||||
print client.initd.status("greentooth")
|
||||
print "ERROR: should have failed above!"
|
||||
except ServerError:
|
||||
print "(got expected error, good!)"
|
||||
print "(expect Error on the above line)"
|
||||
print client.initd.status("bluetooth")
|
||||
print client.apt.install("pkg_which_does_not_exist")
|
||||
print client.apt.remove("pkg_which_does_not_exist")
|
||||
#print client.apt.update() # can be slow...
|
||||
#print client.misc.set_timezone("UTC") # don't clobber system...
|
||||
try:
|
||||
print client.misc.set_timezone("whoopie") # should be an error
|
||||
print "ERROR: should have failed above!"
|
||||
except ServerError:
|
||||
print "(got expected error, good!)"
|
||||
client.close()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Loading…
x
Reference in New Issue
Block a user