Linux ip-172-26-7-228 5.4.0-1103-aws #111~18.04.1-Ubuntu SMP Tue May 23 20:04:10 UTC 2023 x86_64
Your IP : 18.117.91.116
#!/usr/bin/python3
#
# Query and display EC2 metadata related to the AMI instance
# Copyright (c) 2009 Canonical Ltd. (Canonical Contributor Agreement 2.5)
#
# Author: Alon Swartz <alon@turnkeylinux.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
import time
import getopt
import socket
import os
try:
from urllib import request as urllib_request
from urllib import error as urllib_error
from urllib import parse as urllib_parse
except ImportError:
# python2
import urllib2 as urllib_request
import urllib2 as urllib_error
import urlparse as urllib_parse
instdata_host = "169.254.169.254"
instdata_ver = "2009-04-04"
instdata_url = "http://%s/%s" % (instdata_host, instdata_ver)
TOKEN_TTL_SECONDS = 21600
TOKEN_HEADER = "X-aws-ec2-metadata-token"
TOKEN_HEADER_TTL = "X-aws-ec2-metadata-token-ttl-seconds"
session_token_url = "http://%s/%s/%s" % (instdata_host, 'latest', 'api/token')
__doc__ = """
Query and display EC2 metadata.
If no options are provided, all options will be displayed
Options:
-h --help show this help
--kernel-id display the kernel id
--ramdisk-id display the ramdisk id
--reservation-id display the reservation id
--ami-id display the ami id
--ami-launch-index display the ami launch index
--ami-manifest-path display the ami manifest path
--ancestor-ami-ids display the ami ancestor id
--product-codes display the ami associated product codes
--availability-zone display the ami placement zone name
--availability-zone-id display the ami placement zone id
--region display the ami placement region name
--group-name display the ami placement group name
--host-id display the dedicated host id
--partition-number display the partition instance was launched from
--instance-id display the instance id
--instance-type display the instance type
--local-hostname display the local hostname
--public-hostname display the public hostname
--local-ipv4 display the local ipv4 ip address
--public-ipv4 display the public ipv4 ip address
--block-device-mapping display the block device id
--security-groups display the security groups
--mac display the instance mac address
--profile display the instance profile
--instance-action display the instance-action
--public-keys display the openssh public keys
--user-data display the user data (not actually metadata)
-u | --url URL use URL (default: %s)
""" % instdata_url
METAOPTS = ['ami-id', 'ami-launch-index', 'ami-manifest-path',
'ancestor-ami-ids', 'availability-zone', 'block-device-mapping',
'instance-action', 'instance-id', 'instance-type',
'local-hostname', 'local-ipv4', 'kernel-id', 'mac',
'profile', 'product-codes', 'public-hostname', 'public-ipv4',
'public-keys', 'ramdisk-id', 'reservation-id', 'security-groups',
'user-data', 'availability-zone-id', 'region', 'host-id',
'group-name', 'partition-number']
binstdout = os.fdopen(sys.stdout.fileno(), 'wb')
def print_binary(data):
if not isinstance(data, bytes):
data = data.encode()
binstdout.write(data)
binstdout.flush()
class Error(Exception):
pass
class EC2Metadata: # pylint: disable=R0903
"""Class for querying metadata from EC2"""
def __init__(self, burl=instdata_url):
self.burl = burl
s = urllib_parse.urlsplit(burl)
addr = s.netloc.split(":")[0]
port = s.port
if s.port is None:
port = 80
if not self._test_connectivity(addr, port):
raise Error("could not establish connection to: %s:%s" %
(addr, port))
self._imdsv2_ensure_token()
@staticmethod
def _test_connectivity(addr, port):
for _ in range(6):
s = socket.socket()
try:
s.connect((addr, port))
s.close()
return True
except socket.error:
time.sleep(1)
return False
def _imdsv2_ensure_token(self):
# Get IMDSv2 session token
request = urllib_request.Request(
session_token_url,
method='PUT',
headers={TOKEN_HEADER_TTL: TOKEN_TTL_SECONDS})
resp = urllib_request.urlopen(request)
self.session_token = resp.read()
def _get(self, uri, decode=True):
url = "%s/%s" % (self.burl, uri)
try:
resp = urllib_request.urlopen(
urllib_request.Request(
url,
headers={TOKEN_HEADER: self.session_token}))
value = resp.read()
if decode:
value = value.decode()
except urllib_error.HTTPError as e:
if e.code == 404:
return None
# Eucalyptus may raise a 500 (Internal Server Error)
if e.code == 500:
return None
raise
return value
def get(self, metaopt):
"""return value of metaopt"""
if metaopt not in METAOPTS:
raise Error('unknown metaopt', metaopt, METAOPTS)
if metaopt in [
'availability-zone',
'availability-zone-id',
'region',
'host-id',
'group-name',
'partition-number',
]:
return self._get('meta-data/placement/' + metaopt)
if metaopt == 'public-keys':
data = self._get('meta-data/public-keys')
if data is None:
return None
keyids = [line.split('=')[0] for line in data.splitlines()]
public_keys = []
for keyid in keyids:
uri = 'meta-data/public-keys/%d/openssh-key' % int(keyid)
public_keys.append(self._get(uri).rstrip())
return public_keys
if metaopt == 'user-data':
return self._get('user-data', decode=False)
return self._get('meta-data/' + metaopt)
def get(metaopt):
"""primitive: return value of metaopt"""
m = EC2Metadata()
return m.get(metaopt)
def display(metaopts, burl, prefix=False):
"""primitive: display metaopts (list) values with optional prefix"""
m = EC2Metadata(burl)
for metaopt in metaopts:
value = m.get(metaopt)
if not value:
value = "unavailable"
if prefix:
print("%s: %s" % (metaopt, value))
elif metaopt == "user-data":
# We want to avoid binary blob corruption while printing as string
print_binary(value)
else:
print(value)
def usage(s=None):
"""display usage and exit"""
msg = ""
if s:
msg = "Error: %s\n" % s
msg += "Syntax: %s [options]\n" % sys.argv[0]
msg += __doc__
sys.stderr.write(msg + "\n")
sys.exit(1)
def main():
"""handle cli options"""
try:
getopt_metaopts = METAOPTS[:]
getopt_metaopts.append('help')
getopt_metaopts.append('url=')
opts, _ = getopt.gnu_getopt(sys.argv[1:], "hu:", getopt_metaopts)
except getopt.GetoptError as e:
usage(e)
burl = instdata_url
metaopts = []
prefix = False
for opt, val in opts:
if opt in ('-h', '--help'):
usage()
if opt in ('-u', '--url'):
burl = val
continue
metaopts.append(opt.replace('--', ''))
if len(metaopts) == 0:
prefix = True
metaopts = METAOPTS
display(metaopts, burl, prefix)
if __name__ == "__main__":
main()
# vi: ts=4 expandtab
|