OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2009 Chris Moyer http://coredumped.org/ |
| 3 # |
| 4 # Permission is hereby granted, free of charge, to any person obtaining a |
| 5 # copy of this software and associated documentation files (the |
| 6 # "Software"), to deal in the Software without restriction, including |
| 7 # without limitation the rights to use, copy, modify, merge, publish, dis- |
| 8 # tribute, sublicense, and/or sell copies of the Software, and to permit |
| 9 # persons to whom the Software is furnished to do so, subject to the fol- |
| 10 # lowing conditions: |
| 11 # |
| 12 # The above copyright notice and this permission notice shall be included |
| 13 # in all copies or substantial portions of the Software. |
| 14 # |
| 15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
| 16 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- |
| 17 # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT |
| 18 # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |
| 19 # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 20 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
| 21 |
| 22 # |
| 23 # Utility to launch an EC2 Instance |
| 24 # |
| 25 VERSION="0.2" |
| 26 |
| 27 |
| 28 CLOUD_INIT_SCRIPT = """#!/usr/bin/env python |
| 29 f = open("/etc/boto.cfg", "w") |
| 30 f.write(\"\"\"%s\"\"\") |
| 31 f.close() |
| 32 """ |
| 33 import boto.pyami.config |
| 34 import boto.utils |
| 35 import re, os |
| 36 import ConfigParser |
| 37 |
| 38 class Config(boto.pyami.config.Config): |
| 39 """A special config class that also adds import abilities |
| 40 Directly in the config file. To have a config file import |
| 41 another config file, simply use "#import <path>" where <path> |
| 42 is either a relative path or a full URL to another config |
| 43 """ |
| 44 |
| 45 def __init__(self): |
| 46 ConfigParser.SafeConfigParser.__init__(self, {'working_dir' : '/mnt/pyam
i', 'debug' : '0'}) |
| 47 |
| 48 def add_config(self, file_url): |
| 49 """Add a config file to this configuration |
| 50 :param file_url: URL for the file to add, or a local path |
| 51 :type file_url: str |
| 52 """ |
| 53 if not re.match("^([a-zA-Z0-9]*:\/\/)(.*)", file_url): |
| 54 if not file_url.startswith("/"): |
| 55 file_url = os.path.join(os.getcwd(), file_url) |
| 56 file_url = "file://%s" % file_url |
| 57 (base_url, file_name) = file_url.rsplit("/", 1) |
| 58 base_config = boto.utils.fetch_file(file_url) |
| 59 base_config.seek(0) |
| 60 for line in base_config.readlines(): |
| 61 match = re.match("^#import[\s\t]*([^\s^\t]*)[\s\t]*$", line) |
| 62 if match: |
| 63 self.add_config("%s/%s" % (base_url, match.group(1))) |
| 64 base_config.seek(0) |
| 65 self.readfp(base_config) |
| 66 |
| 67 def add_creds(self, ec2): |
| 68 """Add the credentials to this config if they don't already exist""" |
| 69 if not self.has_section('Credentials'): |
| 70 self.add_section('Credentials') |
| 71 self.set('Credentials', 'aws_access_key_id', ec2.aws_access_key_id) |
| 72 self.set('Credentials', 'aws_secret_access_key', ec2.aws_secret_acce
ss_key) |
| 73 |
| 74 |
| 75 def __str__(self): |
| 76 """Get config as string""" |
| 77 from StringIO import StringIO |
| 78 s = StringIO() |
| 79 self.write(s) |
| 80 return s.getvalue() |
| 81 |
| 82 SCRIPTS = [] |
| 83 |
| 84 def scripts_callback(option, opt, value, parser): |
| 85 arg = value.split(',') |
| 86 if len(arg) == 1: |
| 87 SCRIPTS.append(arg[0]) |
| 88 else: |
| 89 SCRIPTS.extend(arg) |
| 90 setattr(parser.values, option.dest, SCRIPTS) |
| 91 |
| 92 def add_script(scr_url): |
| 93 """Read a script and any scripts that are added using #import""" |
| 94 base_url = '/'.join(scr_url.split('/')[:-1]) + '/' |
| 95 script_raw = boto.utils.fetch_file(scr_url) |
| 96 script_content = '' |
| 97 for line in script_raw.readlines(): |
| 98 match = re.match("^#import[\s\t]*([^\s^\t]*)[\s\t]*$", line) |
| 99 #if there is an import |
| 100 if match: |
| 101 #Read the other script and put it in that spot |
| 102 script_content += add_script("%s/%s" % (base_url, match.group(1))) |
| 103 else: |
| 104 #Otherwise, add the line and move on |
| 105 script_content += line |
| 106 return script_content |
| 107 |
| 108 if __name__ == "__main__": |
| 109 try: |
| 110 import readline |
| 111 except ImportError: |
| 112 pass |
| 113 import sys |
| 114 import time |
| 115 import boto |
| 116 from boto.ec2 import regions |
| 117 from optparse import OptionParser |
| 118 from boto.mashups.iobject import IObject |
| 119 parser = OptionParser(version=VERSION, usage="%prog [options] config_url") |
| 120 parser.add_option("-c", "--max-count", help="Maximum number of this type of
instance to launch", dest="max_count", default="1") |
| 121 parser.add_option("--min-count", help="Minimum number of this type of instan
ce to launch", dest="min_count", default="1") |
| 122 parser.add_option("--cloud-init", help="Indicates that this is an instance t
hat uses 'CloudInit', Ubuntu's cloud bootstrap process. This wraps the config in
a shell script command instead of just passing it in directly", dest="cloud_ini
t", default=False, action="store_true") |
| 123 parser.add_option("-g", "--groups", help="Security Groups to add this instan
ce to", action="append", dest="groups") |
| 124 parser.add_option("-a", "--ami", help="AMI to launch", dest="ami_id") |
| 125 parser.add_option("-t", "--type", help="Type of Instance (default m1.small)"
, dest="type", default="m1.small") |
| 126 parser.add_option("-k", "--key", help="Keypair", dest="key_name") |
| 127 parser.add_option("-z", "--zone", help="Zone (default us-east-1a)", dest="zo
ne", default="us-east-1a") |
| 128 parser.add_option("-r", "--region", help="Region (default us-east-1)", dest=
"region", default="us-east-1") |
| 129 parser.add_option("-i", "--ip", help="Elastic IP", dest="elastic_ip") |
| 130 parser.add_option("-n", "--no-add-cred", help="Don't add a credentials secti
on", default=False, action="store_true", dest="nocred") |
| 131 parser.add_option("--save-ebs", help="Save the EBS volume on shutdown, inste
ad of deleting it", default=False, action="store_true", dest="save_ebs") |
| 132 parser.add_option("-w", "--wait", help="Wait until instance is running", def
ault=False, action="store_true", dest="wait") |
| 133 parser.add_option("-d", "--dns", help="Returns public and private DNS (impli
cates --wait)", default=False, action="store_true", dest="dns") |
| 134 parser.add_option("-T", "--tag", help="Set tag", default=None, action="appen
d", dest="tags", metavar="key:value") |
| 135 parser.add_option("-s", "--scripts", help="Pass in a script or a folder cont
aining scripts to be run when the instance starts up, assumes cloud-init. Specif
y scripts in a list specified by commas. If multiple scripts are specified, they
are run lexically (A good way to ensure they run in the order is to prefix file
names with numbers)", type='string', action="callback", callback=scripts_callbac
k) |
| 136 parser.add_option("--role", help="IAM Role to use, this implies --no-add-cre
d", dest="role") |
| 137 |
| 138 (options, args) = parser.parse_args() |
| 139 |
| 140 if len(args) < 1: |
| 141 parser.print_help() |
| 142 sys.exit(1) |
| 143 file_url = os.path.expanduser(args[0]) |
| 144 |
| 145 cfg = Config() |
| 146 cfg.add_config(file_url) |
| 147 |
| 148 for r in regions(): |
| 149 if r.name == options.region: |
| 150 region = r |
| 151 break |
| 152 else: |
| 153 print "Region %s not found." % options.region |
| 154 sys.exit(1) |
| 155 ec2 = boto.connect_ec2(region=region) |
| 156 if not options.nocred and not options.role: |
| 157 cfg.add_creds(ec2) |
| 158 |
| 159 iobj = IObject() |
| 160 if options.ami_id: |
| 161 ami = ec2.get_image(options.ami_id) |
| 162 else: |
| 163 ami_id = options.ami_id |
| 164 l = [(a, a.id, a.location) for a in ec2.get_all_images()] |
| 165 ami = iobj.choose_from_list(l, prompt='Choose AMI') |
| 166 |
| 167 if options.key_name: |
| 168 key_name = options.key_name |
| 169 else: |
| 170 l = [(k, k.name, '') for k in ec2.get_all_key_pairs()] |
| 171 key_name = iobj.choose_from_list(l, prompt='Choose Keypair').name |
| 172 |
| 173 if options.groups: |
| 174 groups = options.groups |
| 175 else: |
| 176 groups = [] |
| 177 l = [(g, g.name, g.description) for g in ec2.get_all_security_groups()] |
| 178 g = iobj.choose_from_list(l, prompt='Choose Primary Security Group') |
| 179 while g != None: |
| 180 groups.append(g) |
| 181 l.remove((g, g.name, g.description)) |
| 182 g = iobj.choose_from_list(l, prompt='Choose Additional Security Grou
p (0 to quit)') |
| 183 |
| 184 user_data = str(cfg) |
| 185 # If it's a cloud init AMI, |
| 186 # then we need to wrap the config in our |
| 187 # little wrapper shell script |
| 188 |
| 189 if options.cloud_init: |
| 190 user_data = CLOUD_INIT_SCRIPT % user_data |
| 191 scriptuples = [] |
| 192 if options.scripts: |
| 193 scripts = options.scripts |
| 194 scriptuples.append(('user_data', user_data)) |
| 195 for scr in scripts: |
| 196 scr_url = scr |
| 197 if not re.match("^([a-zA-Z0-9]*:\/\/)(.*)", scr_url): |
| 198 if not scr_url.startswith("/"): |
| 199 scr_url = os.path.join(os.getcwd(), scr_url) |
| 200 try: |
| 201 newfiles = os.listdir(scr_url) |
| 202 for f in newfiles: |
| 203 #put the scripts in the folder in the array such tha
t they run in the correct order |
| 204 scripts.insert(scripts.index(scr) + 1, scr.split("/"
)[-1] + "/" + f) |
| 205 except OSError: |
| 206 scr_url = "file://%s" % scr_url |
| 207 try: |
| 208 scriptuples.append((scr, add_script(scr_url))) |
| 209 except Exception, e: |
| 210 pass |
| 211 |
| 212 user_data = boto.utils.write_mime_multipart(scriptuples, compress=Tr
ue) |
| 213 |
| 214 shutdown_proc = "terminate" |
| 215 if options.save_ebs: |
| 216 shutdown_proc = "save" |
| 217 |
| 218 instance_profile_name = None |
| 219 if options.role: |
| 220 instance_profile_name = options.role |
| 221 |
| 222 r = ami.run(min_count=int(options.min_count), max_count=int(options.max_coun
t), |
| 223 key_name=key_name, user_data=user_data, |
| 224 security_groups=groups, instance_type=options.type, |
| 225 placement=options.zone, instance_initiated_shutdown_behavior=shutdow
n_proc, |
| 226 instance_profile_name=instance_profile_name) |
| 227 |
| 228 instance = r.instances[0] |
| 229 |
| 230 if options.tags: |
| 231 for tag_pair in options.tags: |
| 232 name = tag_pair |
| 233 value = '' |
| 234 if ':' in tag_pair: |
| 235 name, value = tag_pair.split(':', 1) |
| 236 instance.add_tag(name, value) |
| 237 |
| 238 if options.dns: |
| 239 options.wait = True |
| 240 |
| 241 if not options.wait: |
| 242 sys.exit(0) |
| 243 |
| 244 while True: |
| 245 instance.update() |
| 246 if instance.state == 'running': |
| 247 break |
| 248 time.sleep(3) |
| 249 |
| 250 if options.dns: |
| 251 print "Public DNS name: %s" % instance.public_dns_name |
| 252 print "Private DNS name: %s" % instance.private_dns_name |
OLD | NEW |