Commits

Ian Bicking committed 1f4c1ae

Add config command, that works on XML

  • Participants
  • Parent commits 63972ab

Comments (0)

Files changed (5)

File silverhudson/command.py

     '--help-command', action='store_true',
     help="Describe command")
 
+parser_config = subcommands.add_parser(
+    'config',
+    help="Configure a running Hudson instance")
+
+parser_config.add_argument(
+    '--info', action='store_true',
+    help="Show information about the Hudson installation")
+
+parser_config.add_argument(
+    '--show-set', action='store_true',
+    help="Show what the set would do (but don't do it)")
+
+parser_config.add_argument(
+    '--no-restart', action='store_true',
+    help="Don't restart the server after setting variables")
+
+parser_config.add_argument(
+    'var_set', metavar='VAR=VALUE',
+    nargs='*',
+    help="Variable to set")
 
 for subparser in [parser] + subcommands._name_parser_map.values():
     subparser.add_argument(

File silverhudson/command_config.py

+import os
+from silversupport.shell import ssh
+
+
+def command_config(args):
+    ext_args = []
+    if args.info:
+        ext_args.append('--info')
+    if args.show_set:
+        ext_args.append('--show-set')
+    if args.no_restart:
+        ext_args.append('--no-restart')
+    ext_args.extend(args.var_set)
+    fp = open(os.path.join(os.path.dirname(__file__), 'hudson-config-script.py'))
+    content = fp.read()
+    fp.close()
+    ssh('root', args.config.host, ['python', '-c', content] + ext_args)

File silverhudson/config.py

 import os
+import urlparse
 from cmdutils import CommandError
 from ConfigParser import ConfigParser
 
     def site(self):
         return self.settings['hudson']['site']
 
+    @property
+    def host(self):
+        return urlparse.urlsplit(self.settings['hudson']['site']).netloc
+
     def get_api(self, args):
         from silverhudson.request import Hudson
         return Hudson(self.site, self.username, self.password, args.logger,

File silverhudson/hudson-config-script.py

+#!/usr/bin/env python
+
+import os
+import sys
+import optparse
+import difflib
+import subprocess
+from xml.etree import ElementTree as ET
+
+parser = optparse.OptionParser(
+    usage='%prog [OPTIONS] VAR=VALUE')
+
+parser.add_option(
+    '--info', action='store_true',
+    help="Show information about the Hudson installation")
+
+parser.add_option(
+    '--show-set', action='store_true',
+    help="Show what the set would do (but don't do it)")
+
+parser.add_option(
+    '--no-restart', action='store_true',
+    help="Don't restart the server after setting variables")
+
+
+def hudson_env(settings='/etc/default/hudson'):
+    proc = subprocess.Popen(['bash', '-c', """\
+set -a
+python -c 'import os; print dict(os.environ); print "----xx----"'
+. %s
+python -c 'import os; print dict(os.environ)'
+""" % settings],
+        stdout=subprocess.PIPE)
+    stdout, stderr = proc.communicate()
+    dicts = stdout.split('----xx----')
+    start = eval(dicts[0])
+    end = eval(dicts[1])
+    for name, value in end.items():
+        if start.get(name) == value:
+            del end[name]
+    return end
+
+
+def main():
+    env = hudson_env()
+    options, args = parser.parse_args()
+    if options.info:
+        return info(env)
+    elif not args:
+        parser.print_help()
+    need_restart = False
+    vars = []
+    for arg in args:
+        if '=' not in arg:
+            print 'Not a variable assignment: %s' % arg
+            return
+        vars.append(arg.split('=', 1))
+    for name, value in vars:
+        fn, rest = get_var_filename(env['HUDSON_HOME'], name)
+        print 'Setting %s=%s (in %s)' % (name, value, fn)
+        doc = ET.parse(fn).getroot()
+        start = ET.tostring(doc)
+        set_var_xml(doc, rest, value)
+        end = ET.tostring(doc)
+        if options.show_set:
+            diff = list(difflib.unified_diff(start.splitlines(True), end.splitlines(True),
+                                             fn, '(after set)'))
+            if not diff:
+                print 'No change'
+            else:
+                print ''.join('  %s' % l for l in diff)
+        elif start == end:
+            print '  No change'
+        else:
+            need_restart = True
+            print '  Writing to %s' % fn
+            fp = open(fn, 'wb')
+            fp.write(ET.tostring(doc))
+            fp.close()
+    if need_restart and not options.no_restart:
+        print 'Restarting Hudson'
+        proc = subprocess.Popen(['/etc/init.d/hudson', 'restart'])
+        proc.communicate()
+
+
+def config_files(env):
+    config_dir = env['HUDSON_HOME']
+    yield os.path.join(config_dir, 'config.xml')
+    for fn in os.listdir(config_dir):
+        if fn.startswith('hudson.plugins.') and fn.endswith('.xml'):
+            yield os.path.join(config_dir, fn)
+
+
+def config_vars(fn):
+    doc = ET.parse(fn).getroot()
+    if fn.endswith('config.xml'):
+        name = 'hudson'
+    else:
+        name = os.path.basename(os.path.splitext(fn)[0])
+        name = name.split('.')[-1]
+    return xml_to_vars(doc, name)
+
+
+def info(env):
+    print 'Process variables:'
+    for name, value in sorted(env.items()):
+        print '  %s: %s' % (name, value)
+    for fn in config_files(env):
+        print 'Config %s:' % fn
+        for name, value in config_vars(fn):
+            print '  %s: %s' % (name, value)
+
+
+def xml_to_vars(doc, name):
+    # The parent element is always boring
+    result = []
+    replacements = {}
+    if isinstance(name, basestring):
+        name = (name,)
+    for child in doc:
+        _xml_to_vars(child, result, name, replacements)
+    return result
+
+
+def _xml_to_vars(el, result, name, replacements):
+    name = name + (el.tag,)
+    name = '/'.join(name)
+    if name in replacements:
+        replacements[name] += 1
+        name = '%s+%s' % (name, replacements[name])
+    else:
+        parts = name.split('/')
+        total = []
+        for part in parts:
+            total.append(part)
+            name_chunk = '/'.join(total)
+            if replacements.get(name_chunk):
+                name_chunk = '%s+%s' + (name_chunk, replacements[name_chunk])
+                total = name_chunk.split('/')
+        name = '/'.join(total)
+        replacements[name] = 0
+    if not len(el):
+        # This is a genuine node
+        if el.text is None:
+            value = '+'
+        else:
+            value = (el.text or '').strip()
+        result.append((name, value))
+    else:
+        name = tuple(name.split('/'))
+        for child in el:
+            _xml_to_vars(child, result, name, replacements)
+
+
+def get_var_filename(home, name):
+    name, rest = name.split('/', 1)
+    if name == 'hudson':
+        return os.path.join(home, 'config.xml'), rest
+    else:
+        for fn in os.listdir(home):
+            if (fn.startswith('hudson.plugins.')
+                and fn.endswith('.' + name + '.xml')):
+                return os.path.join(home, fn), rest
+    return None, None
+
+
+def set_var_xml(doc, name, value):
+    if isinstance(name, basestring):
+        name = name.split('/')
+    chunk, index = _get_index(name[0])
+    for child in doc:
+        if child.tag == chunk:
+            if index == 0:
+                if len(name) == 1:
+                    if value == '+':
+                        # Everything is cool
+                        pass
+                    elif value == '-':
+                        # Better get rid of this
+                        doc.remove(child)
+                    else:
+                        child.text = value
+                else:
+                    set_var_xml(child, name[1:], value)
+                break
+            else:
+                index -= 1
+    else:
+        if len(name) == 1:
+            # Fell through without finding element, need to add
+            if value != '-':
+                el = ET.Element(name[0])
+                if value != '+':
+                    el.text = value
+                doc.append(el)
+        else:
+            if name[0].endswith('+'):
+                el = ET.Element(name[0][:-1])
+                doc.append(el)
+                set_var_xml(el, name[1:], value)
+            else:
+                raise Exception('No element matching %s' % '/'.join(name))
+
+
+def _get_index(name):
+    if '+' in name:
+        if name.endswith('+'):
+            return name[:-1], -1
+        else:
+            name, index = name.split('+', 1)
+            return name, int(index)
+    else:
+        return name, 0
+
+
+if __name__ == '__main__':
+    sys.exit(main())

File silverhudson/request.py

         resp = form.submit('Submit')
         print 'ok.'
 
+    def load_config(self):
+        page = self.req_app.get(self.url('configure'))
+        form = page.forms[1]
+        fields = form.submit_fields()
+        json = self.fill_json_schema(self.configure_json_schema, dict(fields))
+        return json
+
+    _config2_javascript = (
+        "(function () {"
+        "buildFormTree(document.forms[1]);"
+        "div=document.createElement('div');"
+        "div.innerHTML = document.forms[1].elements.json.value;"
+        "document.body.appendChild(div);"
+        "})()")
+
+    def command_config2(self, **kw):
+        if 'json' in kw or not kw:
+            print 'To see JSON, go to %s and paste:' % self.url('configure')
+            print 'javascript:' + self._config2_javascript
+        data = self.load_config()
+        for name, value in kw.items():
+            name_parts = name.split('/')
+            place = data
+            for name_part in name_parts[:-1]:
+                place = place.setdefault(name_part, {})
+            place[name_parts[-1]] = value
+        import pprint
+        pprint.pprint(data)
+        self.req_app.post(self.url('configSubmit'), params={'json': json.dumps(data)})
+        print 'ok.'
+
     def command_create_user(self, username, password, fullname='', email=''):
         """
         Create a user.  Fields: