1
# Software License Agreement (BSD License)
3
# Copyright (c) 2009-2011, Eucalyptus Systems, Inc.
6
# Redistribution and use of this software in source and binary forms, with or
7
# without modification, are permitted provided that the following conditions
10
# Redistributions of source code must retain the above
11
# copyright notice, this list of conditions and the
12
# following disclaimer.
14
# Redistributions in binary form must reproduce the above
15
# copyright notice, this list of conditions and the
16
# following disclaimer in the documentation and/or other
17
# materials provided with the distribution.
19
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
# POSSIBILITY OF SUCH DAMAGE.
31
# Author: Neil Soman neil@eucalyptus.com
32
# Mitch Garnaat mgarnaat@eucalyptus.com
41
import euca2ools.utils
42
import euca2ools.exceptions
43
import euca2ools.nc.auth
44
import euca2ools.nc.connection
45
from boto.ec2.regioninfo import RegionInfo
46
from boto.s3.connection import OrdinaryCallingFormat
47
from boto.ec2.blockdevicemapping import BlockDeviceMapping, BlockDeviceType
48
from boto.roboto.param import Param
50
SYSTEM_EUCARC_PATH = os.path.join('/etc', 'euca2ools', 'eucarc')
53
'us-east-1' : 'ec2.us-east-1.amazonaws.com',
54
'us-west-1' : 'ec2.us-west-1.amazonaws.com',
55
'eu-west-1' : 'ec2.eu-west-1.amazonaws.com',
56
'ap-southeast-1' : 'ec2.ap-southeast-1.amazonaws.com'}
61
import epdb as debugger
63
import pdb as debugger
65
def euca_except_hook(debugger_flag, debug_flag):
66
def excepthook(typ, value, tb):
67
if typ is bdb.BdbQuit:
69
sys.excepthook = sys.__excepthook__
71
if debugger_flag and sys.stdout.isatty() and sys.stdin.isatty():
72
if debugger.__name__ == 'epdb':
73
debugger.post_mortem(tb, typ, value)
75
debugger.post_mortem(tb)
77
print traceback.print_tb(tb)
85
class EucaCommand(object):
87
Description = 'Base class'
88
StandardOptions = [Param(name='access_key',
89
short_name='a', long_name='access-key',
90
doc="User's Access Key ID.",
92
Param(name='secret_key',
93
short_name='s', long_name='secret-key',
94
doc="User's Secret Key.",
96
Param(name='config_path',
97
short_name=None, long_name='config',
98
doc="""Read credentials and cloud settings
99
from the specified config file (defaults to
100
$HOME/.eucarc or /etc/euca2ools/eucarc).""",
102
Param(short_name=None, long_name='debug',
103
doc='Turn on debugging output.',
104
optional=True, ptype='boolean'),
105
Param(short_name=None, long_name='debugger',
106
doc='Enable interactive debugger on error',
107
optional=True, ptype='boolean'),
108
Param(short_name='h', long_name='help',
109
doc='Display this help message.',
110
optional=True, ptype='boolean'),
111
Param(name='region_name',
112
short_name=None, long_name='region',
113
doc='region to direct requests to',
115
Param(short_name='U', long_name='url',
116
doc='URL of the Cloud to connect to.',
118
Param(short_name=None, long_name='version',
119
doc='Display the version of this tool.',
120
optional=True, ptype='boolean'),
121
Param(long_name='euca-auth',
122
doc='Use NC authentication mode',
123
optional=True, ptype='boolean')]
127
APIVersion = '2009-11-30'
129
def __init__(self, is_euca=False, debug=False):
130
self.access_key_short_name = '-a'
131
self.secret_key_short_name = '-s'
132
self.ec2_user_access_key = None
133
self.ec2_user_secret_key = None
136
self.region_name = None
137
self.region = RegionInfo()
138
self.config_file_path = None
139
self.is_secure = True
141
self.service_path = '/'
142
self.is_euca = is_euca
143
self.euca_cert_path = None
144
self.euca_private_key_path = None
146
self.debugger = False
147
self.set_debug(debug)
148
self.cmd_name = os.path.basename(sys.argv[0])
150
self.check_for_conflict()
151
self.process_cli_args()
153
# logging.getLogger('boto').addHandler(h)
155
def set_debug(self, debug=False):
157
boto.set_stream_logger('euca2ools')
160
def process_cli_args(self):
161
(opts, args) = getopt.gnu_getopt(sys.argv[1:],
162
self.short_options(),
164
for (name, value) in opts:
165
if name in ('-h', '--help'):
168
elif name == '--version':
170
elif name == '--debug':
172
elif name == '--debugger':
174
elif name in (self.access_key_short_name, '--access-key'):
175
self.ec2_user_access_key = value
176
elif name in (self.secret_key_short_name, '--secret-key'):
177
self.ec2_user_secret_key = value
178
elif name in ('-U', '--url'):
180
elif name == '--region':
181
self.region_name = value
182
elif name == '--config':
183
self.config_file_path = value
184
elif name == '--euca-auth':
186
elif name == '--filter':
188
name, value = value.split('=')
190
msg = 'Filters must be of the form name=value'
191
self.display_error_and_exit(msg)
192
self.filters[name] = value
194
option = self.find_option(name)
197
value = option.convert(value)
199
msg = '%s should be of type %s' % (option.long_name,
201
self.display_error_and_exit(msg)
202
if option.cardinality in ('*', '+'):
203
if not hasattr(self, option.name):
204
setattr(self, option.name, [])
205
getattr(self, option.name).append(value)
207
setattr(self, option.name, value)
208
self.handle_defaults()
209
self.check_required_options()
211
for arg in self.Args:
212
if not arg.optional and len(args)==0:
214
msg = 'Argument (%s) was not provided' % arg.name
215
self.display_error_and_exit(msg)
216
if arg.cardinality in ('*', '+'):
217
setattr(self, arg.name, args)
218
elif arg.cardinality == 1:
219
if len(args) == 0 and arg.optional:
222
value = arg.convert(args[0])
224
msg = '%s should be of type %s' % (arg.name,
226
setattr(self, arg.name, value)
228
msg = 'Only 1 argument (%s) permitted' % arg.name
229
self.display_error_and_exit(msg)
231
sys.excepthook = euca_except_hook(self.debugger, self.debug)
234
def check_for_conflict(self):
235
for option in self.Options:
236
if option.short_name == 'a' or option.short_name == 's':
237
self.access_key_short_name = '-A'
238
self.secret_key_short_name = '-S'
239
opt = self.find_option('--access-key')
241
opt = self.find_option('--secret-key')
244
def find_option(self, op_name):
245
for option in self.StandardOptions+self.Options:
246
if option.synopsis_short_name == op_name or option.synopsis_long_name == op_name:
250
def short_options(self):
252
for option in self.StandardOptions + self.Options:
253
if option.short_name:
254
s += option.getopt_short_name
257
def long_options(self):
259
for option in self.StandardOptions+self.Options:
261
l.append(option.getopt_long_name)
267
return [ opt for opt in self.StandardOptions+self.Options if not opt.optional ]
269
def required_args(self):
270
return [ arg for arg in self.Args if not arg.optional ]
273
return [ opt for opt in self.StandardOptions+self.Options if opt.optional ]
275
def optional_args(self):
276
return [ arg for arg in self.Args if arg.optional ]
278
def handle_defaults(self):
279
for option in self.Options+self.Args:
280
if not hasattr(self, option.name):
281
value = option.default
282
if value is None and option.cardinality in ('+', '*'):
284
elif value is None and option.ptype == 'boolean':
286
elif value is None and option.ptype == 'integer':
288
setattr(self, option.name, value)
290
def check_required_options(self):
292
for option in self.required():
293
if not hasattr(self, option.name) or getattr(self, option.name) is None:
294
missing.append(option.long_name)
296
msg = 'These required options are missing: %s' % ','.join(missing)
297
self.display_error_and_exit(msg)
300
print '\tVersion: %s (BSD)' % euca2ools.__version__
303
def param_usage(self, plist, label, n=30):
310
names.append(opt.synopsis_short_name)
312
names.append(opt.synopsis_long_name)
314
names.append(opt.name)
315
doc = textwrap.dedent(opt.doc)
316
doclines = textwrap.wrap(doc, nn)
318
print ' %s%s' % (','.join(names).ljust(n), doclines[0])
319
for line in doclines[1:]:
320
print '%s%s' % (' '*(n+4), line)
322
def filter_usage(self, n=30):
325
print '\nAVAILABLE FILTERS'
326
for filter in self.Filters:
327
doc = textwrap.dedent(filter.doc)
328
doclines = textwrap.wrap(doc, nn, fix_sentence_endings=True)
329
print ' %s%s' % (filter.name.ljust(n), doclines[0])
330
for line in doclines[1:]:
331
print '%s%s' % (' '*(n+4), line)
334
def option_synopsis(self, options):
336
for option in options:
338
if option.short_name:
339
names.append(option.synopsis_short_name)
341
names.append(option.synopsis_long_name)
344
s += ', '.join(names)
345
if option.ptype != 'boolean':
358
s = '%s ' % self.cmd_name
361
t += self.option_synopsis(self.required())
362
t += self.option_synopsis(self.optional())
364
t += ' [--filter name=value]'
368
for arg in self.Args:
371
name = '[ %s ]' % name
372
arg_names.append(name)
373
t += ' '.join(arg_names)
374
lines = textwrap.wrap(t, 80-n)
376
for line in lines[1:]:
377
print '%s%s' % (' '*n, line)
380
print '%s\n' % self.Description
382
self.param_usage(self.required()+self.required_args(),
383
'REQUIRED PARAMETERS')
384
self.param_usage(self.optional()+self.optional_args(),
385
'OPTIONAL PARAMETERS')
388
def display_error_and_exit(self, exc):
390
print '%s: %s' % (exc.error_code, exc.error_message)
396
def setup_environ(self):
397
envlist = ('EC2_ACCESS_KEY', 'EC2_SECRET_KEY',
398
'S3_URL', 'EC2_URL', 'EC2_CERT', 'EC2_PRIVATE_KEY',
399
'EUCALYPTUS_CERT', 'EC2_USER_ID',
400
'EUCA_CERT', 'EUCA_PRIVATE_KEY')
403
if 'HOME' in os.environ:
404
user_eucarc = os.path.join(os.getenv('HOME'), '.eucarc')
406
if self.config_file_path \
407
and os.path.exists(self.config_file_path):
408
read_config = self.config_file_path
409
elif user_eucarc is not None and os.path.exists(user_eucarc):
410
if os.path.isdir(user_eucarc):
411
user_eucarc = os.path.join(user_eucarc, 'eucarc')
412
if os.path.isfile(user_eucarc):
413
read_config = user_eucarc
414
elif os.path.isfile(user_eucarc):
415
read_config = user_eucarc
416
elif os.path.exists(SYSTEM_EUCARC_PATH):
417
read_config = SYSTEM_EUCARC_PATH
419
euca2ools.utils.parse_config(read_config, self.environ, envlist)
422
self.environ[v] = os.getenv(v)
424
def get_environ(self, name):
425
if self.environ.has_key(name):
426
value = self.environ[name]
428
return self.environ[name]
429
msg = 'Environment variable: %s not found' % name
430
self.display_error_and_exit(msg)
432
def get_credentials(self):
434
if not self.euca_cert_path:
435
self.euca_cert_path = self.environ['EUCA_CERT']
436
if not self.euca_cert_path:
437
print 'EUCA_CERT variable must be set.'
438
raise euca2ools.exceptions.ConnectionFailed
439
if not self.euca_private_key_path:
440
self.euca_private_key_path = self.environ['EUCA_PRIVATE_KEY']
441
if not self.euca_private_key_path:
442
print 'EUCA_PRIVATE_KEY variable must be set.'
443
raise euca2ools.exceptions.ConnectionFailed
444
if not self.ec2_user_access_key:
445
self.ec2_user_access_key = self.environ['EC2_ACCESS_KEY']
446
if not self.ec2_user_access_key:
447
print 'EC2_ACCESS_KEY environment variable must be set.'
448
raise euca2ools.exceptions.ConnectionFailed
450
if not self.ec2_user_secret_key:
451
self.ec2_user_secret_key = self.environ['EC2_SECRET_KEY']
452
if not self.ec2_user_secret_key:
453
print 'EC2_SECRET_KEY environment variable must be set.'
454
raise euca2ools.exceptions.ConnectionFailed
456
def get_connection_details(self):
458
self.service_path = '/'
460
rslt = urlparse.urlparse(self.url)
461
if rslt.scheme == 'https':
462
self.is_secure = True
464
self.is_secure = False
466
self.host = rslt.netloc
467
l = self.host.split(':')
470
self.port = int(l[1])
473
self.service_path = rslt.path
475
def make_s3_connection(self):
477
self.url = self.environ['S3_URL']
480
'http://localhost:8773/services/Walrus'
481
print 'S3_URL not specified. Trying %s' \
484
self.get_connection_details()
487
return euca2ools.nc.connection.EucaConnection(
488
private_key_path=self.euca_private_key_path,
489
cert_path=self.euca_cert_path,
490
aws_access_key_id=self.ec2_user_access_key,
491
aws_secret_access_key=self.ec2_user_secret_key,
492
is_secure=self.is_secure, debug=self.debug,
495
path=self.service_path)
497
return boto.connect_s3(
498
aws_access_key_id=self.ec2_user_access_key,
499
aws_secret_access_key=self.ec2_user_secret_key,
500
is_secure=self.is_secure, debug=self.debug,
503
calling_format=OrdinaryCallingFormat(),
504
path=self.service_path)
506
def make_ec2_connection(self):
508
self.region.name = self.region_name
510
self.region.endpoint = EC2RegionData[self.region_name]
512
print 'Unknown region: %s' % self.region_name
515
self.url = self.environ['EC2_URL']
518
'http://localhost:8773/services/Eucalyptus'
519
print 'EC2_URL not specified. Trying %s' \
522
if not self.region.endpoint:
523
self.get_connection_details()
524
self.region.name = 'eucalyptus'
525
self.region.endpoint = self.host
527
return boto.connect_ec2(aws_access_key_id=self.ec2_user_access_key,
528
aws_secret_access_key=self.ec2_user_secret_key,
529
is_secure=self.is_secure,
533
path=self.service_path,
534
api_version=self.APIVersion)
536
def make_connection(self, conn_type='ec2'):
537
self.get_credentials()
538
if conn_type == 's3':
539
conn = self.make_s3_connection()
540
elif conn_type == 'ec2':
541
conn = self.make_ec2_connection()
546
def make_connection_cli(self, conn_type='ec2'):
548
This just wraps up the make_connection call with appropriate
549
try/except logic to print out an error message and exit if
550
a EucaError is encountered. This keeps the try/except logic
551
out of all the command files.
554
conn = self.make_connection(conn_type)
556
msg = 'Unknown connection type: %s' % conn_type
557
self.display_error_and_exit(msg)
559
except euca2ools.exceptions.EucaError, ex:
560
self.display_error_and_exit(ex)
562
def make_request_cli(self, connection, request_name, **params):
564
This provides a simple
565
This just wraps up the make_connection call with appropriate
566
try/except logic to print out an error message and exit if
567
a EucaError is encountered. This keeps the try/except logic
568
out of all the command files.
572
params['filters'] = self.filters
573
method = getattr(connection, request_name)
574
except AttributeError:
575
print 'Unknown request: %s' % request_name
578
return method(**params)
579
except Exception, ex:
580
self.display_error_and_exit(ex)
582
def get_relative_filename(self, filename):
583
return os.path.split(filename)[-1]
585
def get_file_path(self, filename):
586
relative_filename = self.get_relative_filename(filename)
587
file_path = os.path.dirname(filename)
588
if len(file_path) == 0:
592
def parse_block_device_args(self, block_device_maps_args):
593
block_device_map = BlockDeviceMapping()
594
for block_device_map_arg in block_device_maps_args:
595
parts = block_device_map_arg.split('=')
597
device_name = parts[0]
598
block_dev_type = BlockDeviceType()
599
value_parts = parts[1].split(':')
600
if value_parts[0].startswith('snap'):
601
block_dev_type.snapshot_id = value_parts[0]
603
if value_parts[0].startswith('ephemeral'):
604
block_dev_type.ephemeral_name = value_parts[0]
605
if len(value_parts) > 1:
607
block_dev_type.size = int(value_parts[1])
610
if len(value_parts) > 2:
611
if value_parts[2] == 'true':
612
block_dev_type.delete_on_termination = True
613
block_device_map[device_name] = block_dev_type
614
return block_device_map