diff options
-rwxr-xr-x | tools/mock-meta.py | 78 |
1 files changed, 70 insertions, 8 deletions
diff --git a/tools/mock-meta.py b/tools/mock-meta.py index 5f421c98..7c38ec48 100755 --- a/tools/mock-meta.py +++ b/tools/mock-meta.py @@ -4,6 +4,19 @@ # # http://docs.amazonwebservices.com/AWSEC2/2007-08-29/DeveloperGuide/AESDG-chapter-instancedata.html +""" +To use this to mimic the EC2 metadata service entirely, run it like: + # Where 'eth0' is *some* interface. + sudo ifconfig eth0:0 169.254.169.254 netmask 255.255.255.255 + + sudo ./mock-meta -a 169.254.169.254 -p 80 + +Then: + wget -q http://169.254.169.254/latest/meta-data/instance-id -O -; echo + curl --silent http://169.254.169.254/latest/meta-data/instance-id ; echo + ec2metadata --instance-id +""" + import functools import httplib import json @@ -20,7 +33,6 @@ from BaseHTTPServer import (HTTPServer, BaseHTTPRequestHandler) log = logging.getLogger('meta-server') -# Constants EC2_VERSIONS = [ '1.0', '2007-01-19', @@ -69,6 +81,14 @@ META_CAPABILITIES = [ 'security-groups' ] +PUB_KEYS = { + 'brickies': [ + 'ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA3I7VUf2l5gSn5uavROsc5HRDpZdQueUq5ozemNSj8T7enqKHOEaFoU2VoPgGEWC9RyzSQVeyD6s7APMcE82EtmW4skVEgEGSbDc1pvxzxtchBj78hJP6Cf5TCMFSXw+Fz5rF1dR23QDbN1mkHs7adr8GW4kSWqU7Q7NDwfIrJJtO7Hi42GyXtvEONHbiRPOe8stqUly7MvUoN+5kfjBM8Qqpfl2+FNhTYWpMfYdPUnE7u536WqzFmsaqJctz3gBxH9Ex7dFtrxR4qiqEr9Qtlu3xGn7Bw07/+i1D+ey3ONkZLN+LQ714cgj8fRS4Hj29SCmXp5Kt5/82cD/VN3NtHw== brickies', + '', + ], +} + + INSTANCE_TYPES = [ 'm1.small', 'm1.medium', @@ -136,6 +156,8 @@ class MetaDataHandler(object): def get_data(self, params, who, **kwargs): if not params: + # Show the root level capabilities when + # no params are passed... caps = sorted(META_CAPABILITIES) return "\n".join(caps) action = params[0] @@ -172,6 +194,43 @@ class MetaDataHandler(object): return "r-%s" % (id_generator(lower=True)) elif action == 'product-codes': return "%s" % (id_generator(size=8)) + elif action == 'public-keys': + nparams = params[1:] + # public-keys is messed up. a list of /latest/meta-data/public-keys/ + # shows something like: '0=brickies' + # but a GET to /latest/meta-data/public-keys/0=brickies will fail + # you have to know to get '/latest/meta-data/public-keys/0', then + # from there you get a 'openssh-key', which you can get. + # this hunk of code just re-works the object for that. + key_ids = sorted(list(PUB_KEYS.keys())) + if nparams: + mybe_key = nparams[0] + try: + key_id = int(mybe_key) + key_name = key_ids[key_id] + except: + raise WebException(httplib.BAD_REQUEST, "Unknown key id %r" % mybe_key) + # Extract the possible sub-params + key_info = { + "openssh-key": "\n".join(PUB_KEYS[key_name]), + } + result = dict(key_info) + for k in nparams[1:]: + try: + result = result.get(k) + except (AttributeError, TypeError): + result = None + break + if isinstance(result, (dict)): + result = json.dumps(result) + if result is None: + result = '' + return str(result) + else: + contents = [] + for (i, key_id) in enumerate(key_ids): + contents.append("%s=%s" % (i, key_id)) + return "\n".join(contents) elif action == 'placement': nparams = params[1:] if not nparams: @@ -198,10 +257,8 @@ class UserDataHandler(object): def _get_user_blob(self, **kwargs): blob = None - if self.opts['user_data_file']: - with open(opts['user_data_file'], 'rb') as fh: - blob = fh.read() - blob = blob.strip() + if self.opts['user_data_file'] is not None: + blob = self.opts['user_data_file'] if not blob: blob_mp = { 'hostname': kwargs.get('who', 'localhost'), @@ -312,6 +369,8 @@ def extract_opts(): parser = OptionParser() parser.add_option("-p", "--port", dest="port", action="store", type=int, default=80, help="port from which to serve traffic (default: %default)", metavar="PORT") + parser.add_option("-a", "--addr", dest="address", action="store", type=str, default='0.0.0.0', + help="address from which to serve traffic (default: %default)", metavar="ADDRESS") parser.add_option("-f", '--user-data-file', dest='user_data_file', action='store', help="user data filename to serve back to incoming requests", metavar='FILE') (options, args) = parser.parse_args() @@ -319,10 +378,12 @@ def extract_opts(): out['extra'] = args out['port'] = options.port out['user_data_file'] = None + out['address'] = options.address if options.user_data_file: if not os.path.isfile(options.user_data_file): parser.error("Option -f specified a non-existent file") - out['user_data_file'] = options.user_data_file + with open(options.user_data_file, 'rb') as fh: + out['user_data_file'] = fh.read() return out @@ -340,9 +401,10 @@ def run_server(): setup_logging(logging.DEBUG) setup_fetchers(opts) log.info("CLI opts: %s", opts) - server = HTTPServer(('0.0.0.0', opts['port']), Ec2Handler) + server_address = (opts['address'], opts['port']) + server = HTTPServer(server_address, Ec2Handler) sa = server.socket.getsockname() - log.info("Serving server on %s using port %s ...", sa[0], sa[1]) + log.info("Serving ec2 metadata on %s using port %s ...", sa[0], sa[1]) server.serve_forever() |