OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2009 Chris Moyer http://coredumped.org/ | 2 # Copyright (c) 2009 Chris Moyer http://coredumped.org/ |
3 # | 3 # |
4 # Permission is hereby granted, free of charge, to any person obtaining a | 4 # Permission is hereby granted, free of charge, to any person obtaining a |
5 # copy of this software and associated documentation files (the | 5 # copy of this software and associated documentation files (the |
6 # "Software"), to deal in the Software without restriction, including | 6 # "Software"), to deal in the Software without restriction, including |
7 # without limitation the rights to use, copy, modify, merge, publish, dis- | 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 | 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- | 9 # persons to whom the Software is furnished to do so, subject to the fol- |
10 # lowing conditions: | 10 # lowing conditions: |
(...skipping 13 matching lines...) Expand all Loading... |
24 # | 24 # |
25 VERSION="0.2" | 25 VERSION="0.2" |
26 | 26 |
27 | 27 |
28 CLOUD_INIT_SCRIPT = """#!/usr/bin/env python | 28 CLOUD_INIT_SCRIPT = """#!/usr/bin/env python |
29 f = open("/etc/boto.cfg", "w") | 29 f = open("/etc/boto.cfg", "w") |
30 f.write(\"\"\"%s\"\"\") | 30 f.write(\"\"\"%s\"\"\") |
31 f.close() | 31 f.close() |
32 """ | 32 """ |
33 import boto.pyami.config | 33 import boto.pyami.config |
34 from boto.utils import fetch_file | 34 import boto.utils |
35 import re, os | 35 import re, os |
36 import ConfigParser | 36 import ConfigParser |
37 | 37 |
38 class Config(boto.pyami.config.Config): | 38 class Config(boto.pyami.config.Config): |
39 """A special config class that also adds import abilities | 39 """A special config class that also adds import abilities |
40 Directly in the config file. To have a config file import | 40 Directly in the config file. To have a config file import |
41 another config file, simply use "#import <path>" where <path> | 41 another config file, simply use "#import <path>" where <path> |
42 is either a relative path or a full URL to another config | 42 is either a relative path or a full URL to another config |
43 """ | 43 """ |
44 | 44 |
45 def __init__(self): | 45 def __init__(self): |
46 ConfigParser.SafeConfigParser.__init__(self, {'working_dir' : '/mnt/pyam
i', 'debug' : '0'}) | 46 ConfigParser.SafeConfigParser.__init__(self, {'working_dir' : '/mnt/pyam
i', 'debug' : '0'}) |
47 | 47 |
48 def add_config(self, file_url): | 48 def add_config(self, file_url): |
49 """Add a config file to this configuration | 49 """Add a config file to this configuration |
50 :param file_url: URL for the file to add, or a local path | 50 :param file_url: URL for the file to add, or a local path |
51 :type file_url: str | 51 :type file_url: str |
52 """ | 52 """ |
53 if not re.match("^([a-zA-Z0-9]*:\/\/)(.*)", file_url): | 53 if not re.match("^([a-zA-Z0-9]*:\/\/)(.*)", file_url): |
54 if not file_url.startswith("/"): | 54 if not file_url.startswith("/"): |
55 file_url = os.path.join(os.getcwd(), file_url) | 55 file_url = os.path.join(os.getcwd(), file_url) |
56 file_url = "file://%s" % file_url | 56 file_url = "file://%s" % file_url |
57 (base_url, file_name) = file_url.rsplit("/", 1) | 57 (base_url, file_name) = file_url.rsplit("/", 1) |
58 base_config = fetch_file(file_url) | 58 base_config = boto.utils.fetch_file(file_url) |
59 base_config.seek(0) | 59 base_config.seek(0) |
60 for line in base_config.readlines(): | 60 for line in base_config.readlines(): |
61 match = re.match("^#import[\s\t]*([^\s^\t]*)[\s\t]*$", line) | 61 match = re.match("^#import[\s\t]*([^\s^\t]*)[\s\t]*$", line) |
62 if match: | 62 if match: |
63 self.add_config("%s/%s" % (base_url, match.group(1))) | 63 self.add_config("%s/%s" % (base_url, match.group(1))) |
64 base_config.seek(0) | 64 base_config.seek(0) |
65 self.readfp(base_config) | 65 self.readfp(base_config) |
66 | 66 |
67 def add_creds(self, ec2): | 67 def add_creds(self, ec2): |
68 """Add the credentials to this config if they don't already exist""" | 68 """Add the credentials to this config if they don't already exist""" |
69 if not self.has_section('Credentials'): | 69 if not self.has_section('Credentials'): |
70 self.add_section('Credentials') | 70 self.add_section('Credentials') |
71 self.set('Credentials', 'aws_access_key_id', ec2.aws_access_key_id) | 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) | 72 self.set('Credentials', 'aws_secret_access_key', ec2.aws_secret_acce
ss_key) |
73 | 73 |
74 | 74 |
75 def __str__(self): | 75 def __str__(self): |
76 """Get config as string""" | 76 """Get config as string""" |
77 from StringIO import StringIO | 77 from StringIO import StringIO |
78 s = StringIO() | 78 s = StringIO() |
79 self.write(s) | 79 self.write(s) |
80 return s.getvalue() | 80 return s.getvalue() |
81 | 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 |
82 | 107 |
83 if __name__ == "__main__": | 108 if __name__ == "__main__": |
84 try: | 109 try: |
85 import readline | 110 import readline |
86 except ImportError: | 111 except ImportError: |
87 pass | 112 pass |
88 import sys | 113 import sys |
89 import time | 114 import time |
90 import boto | 115 import boto |
91 from boto.ec2 import regions | 116 from boto.ec2 import regions |
92 from optparse import OptionParser | 117 from optparse import OptionParser |
93 from boto.mashups.iobject import IObject | 118 from boto.mashups.iobject import IObject |
94 parser = OptionParser(version=VERSION, usage="%prog [options] config_url") | 119 parser = OptionParser(version=VERSION, usage="%prog [options] config_url") |
95 parser.add_option("-c", "--max-count", help="Maximum number of this type of
instance to launch", dest="max_count", default="1") | 120 parser.add_option("-c", "--max-count", help="Maximum number of this type of
instance to launch", dest="max_count", default="1") |
96 parser.add_option("--min-count", help="Minimum number of this type of instan
ce to launch", dest="min_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") |
97 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") | 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") |
98 parser.add_option("-g", "--groups", help="Security Groups to add this instan
ce to", action="append", dest="groups") | 123 parser.add_option("-g", "--groups", help="Security Groups to add this instan
ce to", action="append", dest="groups") |
99 parser.add_option("-a", "--ami", help="AMI to launch", dest="ami_id") | 124 parser.add_option("-a", "--ami", help="AMI to launch", dest="ami_id") |
100 parser.add_option("-t", "--type", help="Type of Instance (default m1.small)"
, dest="type", default="m1.small") | 125 parser.add_option("-t", "--type", help="Type of Instance (default m1.small)"
, dest="type", default="m1.small") |
101 parser.add_option("-k", "--key", help="Keypair", dest="key_name") | 126 parser.add_option("-k", "--key", help="Keypair", dest="key_name") |
102 parser.add_option("-z", "--zone", help="Zone (default us-east-1a)", dest="zo
ne", default="us-east-1a") | 127 parser.add_option("-z", "--zone", help="Zone (default us-east-1a)", dest="zo
ne", default="us-east-1a") |
103 parser.add_option("-r", "--region", help="Region (default us-east-1)", dest=
"region", default="us-east-1") | 128 parser.add_option("-r", "--region", help="Region (default us-east-1)", dest=
"region", default="us-east-1") |
104 parser.add_option("-i", "--ip", help="Elastic IP", dest="elastic_ip") | 129 parser.add_option("-i", "--ip", help="Elastic IP", dest="elastic_ip") |
105 parser.add_option("-n", "--no-add-cred", help="Don't add a credentials secti
on", default=False, action="store_true", dest="nocred") | 130 parser.add_option("-n", "--no-add-cred", help="Don't add a credentials secti
on", default=False, action="store_true", dest="nocred") |
106 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") | 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") |
107 parser.add_option("-w", "--wait", help="Wait until instance is running", def
ault=False, action="store_true", dest="wait") | 132 parser.add_option("-w", "--wait", help="Wait until instance is running", def
ault=False, action="store_true", dest="wait") |
108 parser.add_option("-d", "--dns", help="Returns public and private DNS (impli
cates --wait)", default=False, action="store_true", dest="dns") | 133 parser.add_option("-d", "--dns", help="Returns public and private DNS (impli
cates --wait)", default=False, action="store_true", dest="dns") |
109 parser.add_option("-T", "--tag", help="Set tag", default=None, action="appen
d", dest="tags", metavar="key:value") | 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) |
110 | 136 |
111 (options, args) = parser.parse_args() | 137 (options, args) = parser.parse_args() |
112 | 138 |
113 if len(args) < 1: | 139 if len(args) < 1: |
114 parser.print_help() | 140 parser.print_help() |
115 sys.exit(1) | 141 sys.exit(1) |
116 file_url = os.path.expanduser(args[0]) | 142 file_url = os.path.expanduser(args[0]) |
117 | 143 |
118 cfg = Config() | 144 cfg = Config() |
119 cfg.add_config(file_url) | 145 cfg.add_config(file_url) |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
151 g = iobj.choose_from_list(l, prompt='Choose Primary Security Group') | 177 g = iobj.choose_from_list(l, prompt='Choose Primary Security Group') |
152 while g != None: | 178 while g != None: |
153 groups.append(g) | 179 groups.append(g) |
154 l.remove((g, g.name, g.description)) | 180 l.remove((g, g.name, g.description)) |
155 g = iobj.choose_from_list(l, prompt='Choose Additional Security Grou
p (0 to quit)') | 181 g = iobj.choose_from_list(l, prompt='Choose Additional Security Grou
p (0 to quit)') |
156 | 182 |
157 user_data = str(cfg) | 183 user_data = str(cfg) |
158 # If it's a cloud init AMI, | 184 # If it's a cloud init AMI, |
159 # then we need to wrap the config in our | 185 # then we need to wrap the config in our |
160 # little wrapper shell script | 186 # little wrapper shell script |
| 187 |
161 if options.cloud_init: | 188 if options.cloud_init: |
162 user_data = CLOUD_INIT_SCRIPT % user_data | 189 user_data = CLOUD_INIT_SCRIPT % user_data |
| 190 scriptuples = [] |
| 191 if options.scripts: |
| 192 scripts = options.scripts |
| 193 scriptuples.append(('user_data', user_data)) |
| 194 for scr in scripts: |
| 195 scr_url = scr |
| 196 if not re.match("^([a-zA-Z0-9]*:\/\/)(.*)", scr_url): |
| 197 if not scr_url.startswith("/"): |
| 198 scr_url = os.path.join(os.getcwd(), scr_url) |
| 199 try: |
| 200 newfiles = os.listdir(scr_url) |
| 201 for f in newfiles: |
| 202 #put the scripts in the folder in the array such tha
t they run in the correct order |
| 203 scripts.insert(scripts.index(scr) + 1, scr.split("/"
)[-1] + "/" + f) |
| 204 except OSError: |
| 205 scr_url = "file://%s" % scr_url |
| 206 try: |
| 207 scriptuples.append((scr, add_script(scr_url))) |
| 208 except Exception, e: |
| 209 pass |
| 210 |
| 211 user_data = boto.utils.write_mime_multipart(scriptuples, compress=Tr
ue) |
| 212 |
163 shutdown_proc = "terminate" | 213 shutdown_proc = "terminate" |
164 if options.save_ebs: | 214 if options.save_ebs: |
165 shutdown_proc = "save" | 215 shutdown_proc = "save" |
166 | 216 |
167 r = ami.run(min_count=int(options.min_count), max_count=int(options.max_coun
t), | 217 r = ami.run(min_count=int(options.min_count), max_count=int(options.max_coun
t), |
168 key_name=key_name, user_data=user_data, | 218 key_name=key_name, user_data=user_data, |
169 security_groups=groups, instance_type=options.type, | 219 security_groups=groups, instance_type=options.type, |
170 placement=options.zone, instance_initiated_shutdown_behavior=shutdow
n_proc) | 220 placement=options.zone, instance_initiated_shutdown_behavior=shutdow
n_proc) |
171 | 221 |
172 instance = r.instances[0] | 222 instance = r.instances[0] |
(...skipping 14 matching lines...) Expand all Loading... |
187 | 237 |
188 while True: | 238 while True: |
189 instance.update() | 239 instance.update() |
190 if instance.state == 'running': | 240 if instance.state == 'running': |
191 break | 241 break |
192 time.sleep(3) | 242 time.sleep(3) |
193 | 243 |
194 if options.dns: | 244 if options.dns: |
195 print "Public DNS name: %s" % instance.public_dns_name | 245 print "Public DNS name: %s" % instance.public_dns_name |
196 print "Private DNS name: %s" % instance.private_dns_name | 246 print "Private DNS name: %s" % instance.private_dns_name |
197 | |
OLD | NEW |