[pkg-eucalyptus-commits] [SCM] managing cloud instances for Eucalyptus branch, master, updated. 3.0.0-alpha3-257-g1da8e3a
Garrett Holmstrom
gholms at fedoraproject.org
Sun Jun 16 02:30:41 UTC 2013
The following commit has been merged in the master branch:
commit 7cdceb98df0e34a6f614c4f3f9dd56770f4ae800
Author: Garrett Holmstrom <gholms at fedoraproject.org>
Date: Sun Mar 17 01:52:14 2013 -0700
Bump to EC2 API 2013-02-01
diff --git a/euca2ools/commands/argtypes.py b/euca2ools/commands/argtypes.py
index 40ee8dc..6674334 100644
--- a/euca2ools/commands/argtypes.py
+++ b/euca2ools/commands/argtypes.py
@@ -33,6 +33,7 @@ import base64
from requestbuilder import EMPTY
import sys
+
def ec2_block_device_mapping(map_as_str):
'''
Parse a block device mapping from an image registration command line.
@@ -41,8 +42,8 @@ def ec2_block_device_mapping(map_as_str):
(device, mapping) = map_as_str.split('=')
except ValueError:
raise argparse.ArgumentTypeError(
- 'block device mapping "{0}" must have form '
- 'DEVICE=MAPPED'.format(map_as_str))
+ 'block device mapping "{0}" must have form DEVICE=MAPPED'
+ .format(map_as_str))
map_dict = {'DeviceName': device}
if mapping.lower() == 'none':
map_dict['NoDevice'] = 'none'
@@ -51,14 +52,13 @@ def ec2_block_device_mapping(map_as_str):
elif (mapping.startswith('snap-') or mapping.startswith('vol-') or
mapping.startswith(':')):
map_bits = mapping.split(':')
- if len(map_bits) == 1:
- map_bits.append(None)
- if len(map_bits) == 2:
+ while len(map_bits) < 5:
map_bits.append(None)
- if len(map_bits) != 3:
+ if len(map_bits) != 5:
raise argparse.ArgumentTypeError(
- 'EBS block device mapping "{0}" must have form '
- 'DEVICE=[SNAP-ID]:[SIZE]:[true|false]'.format(map_as_str))
+ 'EBS block device mapping "{0}" must have form '
+ 'DEVICE=[SNAP-ID]:[SIZE]:[true|false]:[standard|TYPE[:IOPS]]'
+ .format(map_as_str))
map_dict['Ebs'] = {}
if map_bits[0]:
@@ -68,29 +68,138 @@ def ec2_block_device_mapping(map_as_str):
map_dict['Ebs']['VolumeSize'] = int(map_bits[1])
except ValueError:
raise argparse.ArgumentTypeError(
- 'second element of EBS block device mapping "{0}" '
- 'must be an integer'.format(map_as_str))
+ 'second element of EBS block device mapping "{0}" must be '
+ 'an integer'.format(map_as_str))
if map_bits[2]:
if map_bits[2].lower() not in ('true', 'false'):
raise argparse.ArgumentTypeError(
- 'third element of EBS block device mapping "{0}" must '
- 'be "true" or "false"'.format(map_as_str))
+ 'third element of EBS block device mapping "{0}" must be '
+ '"true" or "false"'.format(map_as_str))
map_dict['Ebs']['DeleteOnTermination'] = map_bits[2].lower()
+ if map_bits[3]:
+ map_dict['Ebs']['VolumeType'] = map_bits[3]
+ if map_bits[4]:
+ if map_bits[3] == 'standard':
+ raise argparse.ArgumentTypeError(
+ 'fifth element of EBS block device mapping "{0}" is not '
+ 'allowed with volume type "standard"'.format(map_as_str))
+ map_dict['Ebs']['Iops'] = map_bits[4]
if not map_dict['Ebs']:
raise argparse.ArgumentTypeError(
- 'EBS block device mapping "{0}" must specify at least one '
- 'element. Use "{1}=none" to specify that no device '
- 'should be mapped.'.format(map_as_str, device))
+ 'EBS block device mapping "{0}" must specify at least one '
+ 'element. Use "{1}=none" to suppress an existing mapping.'
+ .format(map_as_str, device))
elif not mapping:
raise argparse.ArgumentTypeError(
- 'invalid block device mapping "{0}". Use "{1}=none" to '
- 'specify that no device should be mapped.'.format(map_as_str,
- device))
+ 'invalid block device mapping "{0}". Use "{1}=none" to suppress '
+ 'an existing mapping.'.format(map_as_str, device))
else:
raise argparse.ArgumentTypeError(
- 'invalid block device mapping "{0}"'.format(map_as_str))
+ 'invalid block device mapping "{0}"'.format(map_as_str))
return map_dict
+
+def vpc_interface(iface_as_str):
+ '''
+ Nine-part VPC network interface definition:
+ [INTERFACE]:INDEX:[SUBNET]:[DESCRIPTION]:[PRIV_IP]:[GROUP1,GROUP2,...]:
+ [true|false]:[SEC_IP_COUNT|:SEC_IP1,SEC_IP2,...]
+ '''
+
+ if len(iface_as_str) == 0:
+ raise argparse.ArgumentTypeError(
+ 'network interface definitions must be non-empty'.format(
+ iface_as_str))
+
+ bits = iface_as_str.split(':')
+ iface = {}
+
+ if len(bits) < 2:
+ raise argparse.ArgumentTypeError(
+ 'network interface definition "{0}" must consist of at least 2 '
+ 'elements ({1} provided)'.format(iface_as_str, len(bits)))
+ elif len(bits) > 9:
+ raise argparse.ArgumentTypeError(
+ 'network interface definition "{0}" must consist of at most 9 '
+ 'elements ({1} provided)'.format(iface_as_str, len(bits)))
+ while len(bits) < 9:
+ bits.append(None)
+
+ if bits[0]:
+ # Preexisting NetworkInterfaceId
+ if bits[0].startswith('eni-') and len(bits[0]) == 12:
+ iface['NetworkInterfaceId'] = bits[0]
+ else:
+ raise argparse.ArgumentTypeError(
+ 'first element of network interface definition "{0}" must be '
+ 'a network interface ID'.format(iface_as_str))
+ if bits[1]:
+ # DeviceIndex
+ try:
+ iface['DeviceIndex'] = int(bits[1])
+ except ValueError:
+ raise argparse.ArgumentTypeError(
+ 'second element of network interface definition "{0}" must be '
+ 'an integer'.format(iface_as_str))
+ else:
+ raise argparse.ArgumentTypeError(
+ 'second element of network interface definition "{0}" must be '
+ 'non-empty'.format(iface_as_str))
+ if bits[2]:
+ # SubnetId
+ if bits[2].startswith('subnet-'):
+ iface['SubnetId'] = bits[2]
+ else:
+ raise argparse.ArgumentTypeError(
+ 'third element of network interface definition "{0}" must be '
+ 'a subnet ID'.format(iface_as_str))
+ if bits[3]:
+ # Description
+ iface['Description'] = bits[3]
+ if bits[4]:
+ # PrivateIpAddresses.n.PrivateIpAddress
+ # PrivateIpAddresses.n.Primary
+ iface.setdefault('PrivateIpAddresses', [])
+ iface['PrivateIpAddresses'].append({'PrivateIpAddress': bits[4],
+ 'Primary': 'true'})
+ if bits[5]:
+ # SecurityGroupId.n
+ groups = filter(None, bits[5].split(','))
+ if not all(group.startswith('sg-') for group in groups):
+ raise argparse.ArgumentTypeError(
+ 'sixth element of network interface definition "{0}" must '
+ 'refer to security groups by IDs, not names'
+ .format(iface_as_str))
+ iface['SecurityGroupId'] = groups
+ if bits[6]:
+ # DeleteOnTermination
+ if bits[6] in ('true', 'false'):
+ iface['DeleteOnTermination'] = bits[6]
+ else:
+ raise argparse.ArgumentTypeError(
+ 'seventh element of network interface definition "{0}" '
+ 'must be "true" or "false"'.format(iface_as_str))
+ if bits[7]:
+ # SecondaryPrivateIpAddressCount
+ if bits[8]:
+ raise argparse.ArgumentTypeError(
+ 'eighth and ninth elements of network interface definition '
+ '"{0}" must not both be non-empty'.format(iface_as_str))
+ try:
+ iface['SecondaryPrivateIpAddressCount'] = int(bits[7])
+ except ValueError:
+ raise argparse.ArgumentTypeError(
+ 'eighth element of network interface definition "{0}" must be '
+ 'an integer'.format(iface_as_str))
+ if bits[8]:
+ # PrivateIpAddresses.n.PrivateIpAddress
+ sec_ips = [{'PrivateIpAddress': addr} for addr in
+ bits[8].split(',') if addr]
+ iface.setdefault('PrivateIpAddresses', [])
+ iface['PrivateIpAddresses'].extend(sec_ips)
+ return iface
+
+
def file_contents(filename):
if filename == '-':
return sys.stdin.read()
@@ -98,6 +207,7 @@ def file_contents(filename):
with open(filename) as arg_file:
return arg_file.read()
+
def b64encoded_file_contents(filename):
if filename == '-':
return base64.b64encode(sys.stdin.read())
@@ -105,6 +215,7 @@ def b64encoded_file_contents(filename):
with open(filename) as arg_file:
return base64.b64encode(arg_file.read())
+
def binary_tag_def(tag_str):
'''
Parse a tag definition from the command line. Return a dict that depends
@@ -120,6 +231,7 @@ def binary_tag_def(tag_str):
else:
return {'Key': tag_str, 'Value': EMPTY}
+
def ternary_tag_def(tag_str):
'''
Parse a tag definition from the command line. Return a dict that depends
@@ -135,6 +247,7 @@ def ternary_tag_def(tag_str):
else:
return {'Key': tag_str}
+
def delimited_list(delimiter):
def _concrete_delimited_list(list_as_str):
if isinstance(list_as_str, str) and len(list_as_str) > 0:
diff --git a/euca2ools/commands/euca/__init__.py b/euca2ools/commands/euca/__init__.py
index cd2e02f..9f8df66 100644
--- a/euca2ools/commands/euca/__init__.py
+++ b/euca2ools/commands/euca/__init__.py
@@ -29,6 +29,7 @@
# POSSIBILITY OF SUCH DAMAGE.
import argparse
+from euca2ools.commands import Euca2ools
from euca2ools.exceptions import AWSError
from operator import itemgetter
import os.path
@@ -42,7 +43,7 @@ import requests
import shlex
from string import Template
import sys
-from .. import Euca2ools
+
class EC2CompatibleQuerySigV2Auth(QuerySigV2Auth):
# -a and -s are deprecated; remove them in 3.2
@@ -133,7 +134,7 @@ class EC2CompatibleQuerySigV2Auth(QuerySigV2Auth):
class Eucalyptus(requestbuilder.service.BaseService):
NAME = 'ec2'
DESCRIPTION = 'Eucalyptus compute cloud service'
- API_VERSION = '2009-11-30'
+ API_VERSION = '2013-02-01'
AUTH_CLASS = EC2CompatibleQuerySigV2Auth
URL_ENVVAR = 'EC2_URL'
@@ -241,7 +242,7 @@ class EucalyptusRequest(requestbuilder.request.AWSQueryRequest,
instance_line.append(instance.get('placement', {}).get('availabilityZone'))
instance_line.append(instance.get('kernelId'))
instance_line.append(instance.get('ramdiskId'))
- instance_line.append(None) # What is this?
+ instance_line.append(instance.get('platform'))
if instance.get('monitoring'):
instance_line.append('monitoring-' +
instance['monitoring'].get('state'))
@@ -252,35 +253,76 @@ class EucalyptusRequest(requestbuilder.request.AWSQueryRequest,
instance_line.append(instance.get('vpcId'))
instance_line.append(instance.get('subnetId'))
instance_line.append(instance.get('rootDeviceType'))
- instance_line.append(None) # What is this?
- instance_line.append(None) # What is this?
- instance_line.append(None) # What is this?
- instance_line.append(None) # What is this?
+ instance_line.append(instance.get('instanceLifecycle'))
+ instance_line.append(instance.get('showInstanceRequestId'))
+ instance_line.append(None) # Should be the license, but where is it?
+ instance_line.append(instance.get('placement', {}).get('groupName'))
instance_line.append(instance.get('virtualizationType'))
instance_line.append(instance.get('hypervisor'))
- instance_line.append(None) # What is this?
- instance_line.append(instance.get('placement', {}).get('groupName'))
+ instance_line.append(instance.get('clientToken'))
instance_line.append(','.join([group['groupId'] for group in
instance.get('groupSet', [])]))
instance_line.append(instance.get('placement', {}).get('tenancy'))
+ instance_line.append(instance.get('ebsOptimized'))
+ instance_line.append(instance.get('iamInstanceProfile', {}).get('arn'))
print self.tabify(instance_line)
for blockdev in instance.get('blockDeviceMapping', []):
self.print_blockdevice(blockdev)
+ for nic in instance.get('networkInterfaceSet', []):
+ self.print_interface(nic)
+
for tag in instance.get('tagSet', []):
self.print_resource_tag(tag, instance.get('instanceId'))
def print_blockdevice(self, blockdev):
- print self.tabify(['BLOCKDEVICE', blockdev.get('deviceName'),
+ print self.tabify(('BLOCKDEVICE', blockdev.get('deviceName'),
blockdev.get('ebs', {}).get('volumeId'),
blockdev.get('ebs', {}).get('attachTime'),
- blockdev.get('ebs', {}).get('deleteOnTermination')])
+ blockdev.get('ebs', {}).get('deleteOnTermination'),
+ blockdev.get('ebs', {}).get('volumeType'),
+ blockdev.get('ebs', {}).get('iops')))
+
+ def print_interface(self, nic):
+ nic_info = [nic.get(attr) for attr in ('networkInterfaceId',
+ 'subnetId', 'vpcId', 'ownerId', 'status', 'privateIpAddress',
+ 'privateDnsName', 'sourceDestCheck')]
+ print self.tabify(['NIC'] + nic_info)
+ for attachment in nic.get('attachment', []):
+ attachment_info = [attachment.get(attr) for attr in (
+ 'attachmentID', 'deviceIndex', 'status', 'attachTime',
+ 'deleteOnTermination')]
+ print self.tabify(['NICATTACHMENT'] + attachment_info)
+ privaddresses = nic.get('privateIpAddressesSet', [])
+ for association in nic.get('association', []):
+ # The EC2 tools apparently print private IP info in the
+ # association even though that info doesn't appear there
+ # in the response, so we have to look it up elsewhere.
+ for privaddress in privaddresses:
+ if (privaddress.get('association', {}).get('publicIp') ==
+ association.get('publicIp')):
+ # Found a match
+ break
+ else:
+ privaddress = None
+ print self.tabify(('NICASSOCIATION', association.get('publicIp'),
+ association.get('ipOwnerId'), privaddress))
+ for group in nic.get('groupSet', []):
+ print self.tabify(('GROUP', group.get('groupId'),
+ group.get('groupName')))
+ for privaddress in privaddresses:
+ print self.tabify(('PRIVATEIPADDRESS',
+ privaddress.get('privateIpAddress')))
def print_volume(self, volume):
- print self.tabify(['VOLUME'] + [volume.get(attr) for attr in
- ('volumeId', 'size', 'snapshotId', 'availabilityZone',
- 'status', 'createTime')])
+ vol_bits = ['VOLUME']
+ for attr in ('volumeId', 'size', 'snapshotId', 'availabilityZone',
+ 'status', 'createTime'):
+ vol_bits.append(volume.get(attr))
+ vol_bits.append(volume.get('volumeType') or 'standard')
+ vol_bits.append(volume.get('iops'))
+ print self.tabify(vol_bits)
for attachment in volume.get('attachmentSet', []):
self.print_attachment(attachment)
for tag in volume.get('tagSet', []):
diff --git a/euca2ools/commands/euca/allocateaddress.py b/euca2ools/commands/euca/allocateaddress.py
index cd7a837..ff474e4 100644
--- a/euca2ools/commands/euca/allocateaddress.py
+++ b/euca2ools/commands/euca/allocateaddress.py
@@ -28,11 +28,17 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
-import euca2ools.commands.euca
+from euca2ools.commands.euca import EucalyptusRequest
+from requestbuilder import Arg
-class AllocateAddress(euca2ools.commands.euca.EucalyptusRequest):
+
+class AllocateAddress(EucalyptusRequest):
DESCRIPTION = 'Allocate a public IP address'
+ ARGS = [Arg('-d', '--domain', dest='Domain', metavar='vpc',
+ choices=('vpc',), help='''[VPC only] "vpc" to allocate the
+ address for use in a VPC''')]
def print_result(self, result):
print self.tabify(('ADDRESS', result.get('publicIp'),
- result.get('domain'), result.get('allocationId')))
+ result.get('domain', 'standard'),
+ result.get('allocationId')))
diff --git a/euca2ools/commands/euca/associateaddress.py b/euca2ools/commands/euca/associateaddress.py
index 3aae1f2..02d91d9 100644
--- a/euca2ools/commands/euca/associateaddress.py
+++ b/euca2ools/commands/euca/associateaddress.py
@@ -28,16 +28,54 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
-from requestbuilder import Arg
-from . import EucalyptusRequest
+from euca2ools.commands.euca import EucalyptusRequest
+from requestbuilder import Arg, MutuallyExclusiveArgList
+from requestbuilder.exceptions import ArgumentError
+
class AssociateAddress(EucalyptusRequest):
DESCRIPTION = 'Associate an elastic IP address with a running instance'
- ARGS = [Arg('-i', '--instance', dest='InstanceId', metavar='INSTANCE',
- required=True, help='instance to associate the address with'),
- Arg('PublicIp', metavar='ADDRESS', help='IP address to associate')]
+ ARGS = [MutuallyExclusiveArgList(True,
+ Arg('-i', '--instance-id', dest='InstanceId',
+ metavar='INSTANCE', help='''ID of the instance to associate
+ the address with'''),
+ Arg('-n', '--network-interface', dest='NetworkInterfaceId',
+ metavar='INTERFACE', help='''[VPC only] network interface
+ to associate the address with''')),
+ Arg('PublicIp', metavar='ADDRESS', nargs='?', help='''[Non-VPC
+ only] IP address to associate (required)'''),
+ Arg('-a', '--allocation-id', dest='AllocationId', metavar='ALLOC',
+ help='[VPC only] VPC allocation ID (required)'),
+ Arg('-p', '--private-ip-address', dest='PrivateIpAddress',
+ metavar='ADDRESS', help='''[VPC only] the private address to
+ associate with the address being associated in the VPC
+ (default: primary private IP)'''),
+ Arg('--allow-reassociation', dest='AllowReassociation',
+ action='store_const', const='true',
+ help='''[VPC only] allow the address to be associated even if
+ it is already associated with another interface''')]
+
+ def configure(self):
+ EucalyptusRequest.configure(self)
+ if (self.args.get('PublicIp') is not None and
+ self.args.get('AllocationId') is not None):
+ # Can't be both EC2 and VPC
+ raise ArgumentError(
+ 'argument -a/--allocation-id: not allowed with an IP address')
+ if (self.args.get('PublicIp') is None and
+ self.args.get('AllocationId') is None):
+ # ...but we still have to be one of them
+ raise ArgumentError(
+ 'argument -a/--allocation-id or an IP address is required')
def print_result(self, result):
- print self.tabify(('ADDRESS', self.args['PublicIp'],
- self.args['InstanceId'],
- result.get('associationId')))
+ if self.args.get('AllocationId'):
+ # VPC
+ print self.tabify(('ADDRESS', self.args.get('InstanceId'),
+ self.args.get('AllocationId'),
+ response.get('associationId'),
+ self.args.get('PrivateIpAddress')))
+ else:
+ # EC2
+ print self.tabify(('ADDRESS', self.args.get('PublicIp'),
+ self.args.get('InstanceId')))
diff --git a/euca2ools/commands/euca/attachvolume.py b/euca2ools/commands/euca/attachvolume.py
index c28644c..fe4f9a3 100644
--- a/euca2ools/commands/euca/attachvolume.py
+++ b/euca2ools/commands/euca/attachvolume.py
@@ -28,16 +28,19 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
+from euca2ools.commands.euca import EucalyptusRequest
from requestbuilder import Arg
-from . import EucalyptusRequest
+
class AttachVolume(EucalyptusRequest):
DESCRIPTION = 'Attach an EBS volume to an instance'
ARGS = [Arg('-i', '--instance', dest='InstanceId', metavar='INSTANCE',
- required=True, help='instance to attach the folume to'),
+ required=True,
+ help='instance to attach the volume to (required)'),
Arg('-d', '--device', dest='Device', required=True,
- help='device name exposed to the instance'),
- Arg('VolumeId', metavar='VOLUME', help='volume to attach')]
+ help='device name exposed to the instance (required)'),
+ Arg('VolumeId', metavar='VOLUME',
+ help='ID of the volume to attach (required)')]
def print_result(self, result):
self.print_attachment(result)
diff --git a/euca2ools/commands/euca/authorize.py b/euca2ools/commands/euca/authorize.py
index d4405cf..a5bcf56 100644
--- a/euca2ools/commands/euca/authorize.py
+++ b/euca2ools/commands/euca/authorize.py
@@ -28,8 +28,15 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
-from .modgroup import ModifySecurityGroupRequest
+from euca2ools.commands.euca.modgroup import ModifySecurityGroupRequest
+
class Authorize(ModifySecurityGroupRequest):
- NAME = 'AuthorizeSecurityGroupIngress'
- DESCRIPTION = 'Authorize a rule for a security group'
+ DESCRIPTION = 'Add a rule to a security group that allows traffic to pass'
+
+ @property
+ def action(self):
+ if self.args['egress']:
+ return 'AuthorizeSecurityGroupEgress'
+ else:
+ return 'AuthorizeSecurityGroupIngress'
diff --git a/euca2ools/commands/euca/bundleinstance.py b/euca2ools/commands/euca/bundleinstance.py
index 596ded0..1f13927 100644
--- a/euca2ools/commands/euca/bundleinstance.py
+++ b/euca2ools/commands/euca/bundleinstance.py
@@ -30,34 +30,37 @@
import base64
from datetime import datetime, timedelta
+from euca2ools.commands.euca import EucalyptusRequest
import hashlib
import hmac
import json
from requestbuilder import Arg
+from requestbuilder.exceptions import ArgumentError
import textwrap
-from . import EucalyptusRequest
+
class BundleInstance(EucalyptusRequest):
DESCRIPTION = 'Bundle an S3-backed Windows instance'
- ARGS = [Arg('InstanceId', metavar='INSTANCE', help='instance to bundle'),
+ ARGS = [Arg('InstanceId', metavar='INSTANCE',
+ help='ID of the instance to bundle (required)'),
Arg('-b', '--bucket', dest='Storage.S3.Bucket', metavar='BUCKET',
- required=True,
- help='bucket in which to store the new machine image'),
+ required=True, help='''bucket in which to store the new machine
+ image (required)'''),
Arg('-p', '--prefix', dest='Storage.S3.Prefix', metavar='PREFIX',
required=True,
- help='beginning of the machine image bundle name'),
+ help='beginning of the machine image bundle name (required)'),
Arg('-o', '--owner-akid', '--user-access-key', metavar='KEY-ID',
dest='Storage.S3.AWSAccessKeyId', required=True,
- help="bucket owner's access key ID"),
+ help="bucket owner's access key ID (required)"),
Arg('-c', '--policy', metavar='POLICY',
dest='Storage.S3.UploadPolicy',
help='''Base64-encoded upload policy that allows the server
to upload a bundle on your behalf. If unused, -w is
- required'''),
+ required.'''),
Arg('-s', '--policy-signature', metavar='SIGNATURE',
dest='Storage.S3.UploadPolicySignature',
help='''signature of the Base64-encoded upload policy. If
- unused, -w is required'''),
+ unused, -w is required.'''),
Arg('-w', '--owner-sak', '--user-secret-key', metavar='KEY',
route_to=None,
help="""bucket owner's secret access key, used to sign upload
@@ -90,12 +93,12 @@ class BundleInstance(EucalyptusRequest):
EucalyptusRequest.configure(self)
if not self.args.get('Storage.S3.UploadPolicy'):
if not self.args.get('owner_sak'):
- self._cli_parser.error('argument -w/--owner-sak is required '
- 'when -c/--policy is not used')
+ raise ArgumentError('argument -w/--owner-sak is required when '
+ '-c/--policy is not used')
elif not self.args.get('Storage.S3.UploadPolicySignature'):
if not self.args.get('owner_sak'):
- self._cli_parser.error('argument -w/--owner-sak is required '
- 'when -c/--policy is not used')
+ raise ArgumentError('argument -w/--owner-sak is required when '
+ '-s/--policy-signature is not used')
def preprocess(self):
if not self.args.get('Storage.S3.UploadPolicy'):
diff --git a/euca2ools/commands/euca/cancelbundletask.py b/euca2ools/commands/euca/cancelbundletask.py
index caf5d85..c42c0ff 100644
--- a/euca2ools/commands/euca/cancelbundletask.py
+++ b/euca2ools/commands/euca/cancelbundletask.py
@@ -28,13 +28,14 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
+from euca2ools.commands.euca import EucalyptusRequest
from requestbuilder import Arg
-from . import EucalyptusRequest
+
class CancelBundleTask(EucalyptusRequest):
DESCRIPTION = 'Cancel an instance bundling operation'
ARGS = [Arg('BundleId', metavar='TASK-ID',
- help='ID of the bundle task to cancel')]
+ help='ID of the bundle task to cancel (required)')]
def print_result(self, result):
self.print_bundle_task(result.get('bundleInstanceTask'))
diff --git a/euca2ools/commands/euca/confirmproductinstance.py b/euca2ools/commands/euca/confirmproductinstance.py
index 68a8feb..fa2b61b 100644
--- a/euca2ools/commands/euca/confirmproductinstance.py
+++ b/euca2ools/commands/euca/confirmproductinstance.py
@@ -28,15 +28,18 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
+from euca2ools.commands.euca import EucalyptusRequest
from requestbuilder import Arg
-from . import EucalyptusRequest
+
class ConfirmProductInstance(EucalyptusRequest):
DESCRIPTION = 'Verify if a product code is associated with an instance'
- ARGS = [Arg('ProductCode', metavar='CODE', help='product code to confirm'),
+ ARGS = [Arg('ProductCode', metavar='CODE',
+ help='product code to confirm (required)'),
Arg('-i', '--instance', dest='InstanceId', metavar='INSTANCE',
- required=True, help='instance to confirm')]
+ required=True,
+ help='ID of the instance to confirm (required)')]
def print_result(self, result):
- print self.tabify(self.args['ProductCode'], self.args['InstanceId'],
- result.get('return'), result.get('ownerId'))
+ print self.tabify((self.args['ProductCode'], self.args['InstanceId'],
+ result.get('return'), result.get('ownerId')))
diff --git a/euca2ools/commands/euca/createimage.py b/euca2ools/commands/euca/createimage.py
index 034b903..45a4620 100644
--- a/euca2ools/commands/euca/createimage.py
+++ b/euca2ools/commands/euca/createimage.py
@@ -28,21 +28,29 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
+from euca2ools.commands.argtypes import ec2_block_device_mapping
+from euca2ools.commands.euca import EucalyptusRequest
from requestbuilder import Arg
-from . import EucalyptusRequest
+
class CreateImage(EucalyptusRequest):
DESCRIPTION = 'Create an EBS image from a running or stopped EBS instance'
ARGS = [Arg('InstanceId', metavar='INSTANCE',
- help='instance from which to create the image'),
+ help='instance from which to create the image (required)'),
Arg('-n', '--name', dest='Name', required=True,
help='name for the new image (required)'),
Arg('-d', '--description', dest='Description', metavar='DESC',
help='description for the new image'),
Arg('--no-reboot', dest='NoReboot', action='store_const',
- const='true',
- help='''do not shut down the instance before creating the
- image. Image integrity may be affected.''')]
+ const='true', help='''do not shut down the instance before
+ creating the image. Image integrity may be affected.'''),
+ Arg('-b', '--block-device-mapping', metavar='DEVICE=MAPPED',
+ dest='BlockDeviceMapping', action='append',
+ type=ec2_block_device_mapping, default=[],
+ help='''define a block device mapping for the image, in the
+ form DEVICE=MAPPED, where "MAPPED" is "none", "ephemeral(0-3)",
+ or
+ "[SNAP_ID]:[SIZE]:[true|false]:[standard|VOLTYPE[:IOPS]]"''')]
def print_result(self, result):
print self.tabify(('IMAGE', result.get('imageId')))
diff --git a/euca2ools/commands/euca/createkeypair.py b/euca2ools/commands/euca/createkeypair.py
index 03b2172..04fbce1 100644
--- a/euca2ools/commands/euca/createkeypair.py
+++ b/euca2ools/commands/euca/createkeypair.py
@@ -28,13 +28,15 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
+from euca2ools.commands.euca import EucalyptusRequest
import os
from requestbuilder import Arg
-from . import EucalyptusRequest
+
class CreateKeyPair(EucalyptusRequest):
DESCRIPTION = 'Create a new SSH key pair for use with instances'
- ARGS = [Arg('KeyName', metavar='KEYPAIR', help='name of the new key pair'),
+ ARGS = [Arg('KeyName', metavar='KEYPAIR',
+ help='name of the new key pair (required)'),
Arg('-f', '--filename', metavar='FILE', route_to=None,
help='file name to save the private key to')]
diff --git a/euca2ools/commands/euca/createsecuritygroup.py b/euca2ools/commands/euca/createsecuritygroup.py
index cce7565..df4c73f 100644
--- a/euca2ools/commands/euca/createsecuritygroup.py
+++ b/euca2ools/commands/euca/createsecuritygroup.py
@@ -28,14 +28,18 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
+from euca2ools.commands.euca import EucalyptusRequest
from requestbuilder import Arg
-from . import EucalyptusRequest
+
class CreateSecurityGroup(EucalyptusRequest):
DESCRIPTION = 'Create a new security group'
- ARGS = [Arg('-d', '--description', dest='GroupDescription', metavar='DESC',
- required=True),
- Arg('GroupName', metavar='GROUP', help='name of the new group')]
+ ARGS = [Arg('GroupName', metavar='GROUP',
+ help='name of the new group (required)'),
+ Arg('-d', '--description', dest='GroupDescription', metavar='DESC',
+ required=True, help='description of the new group (required)'),
+ Arg('-c', '--vpc', dest='VpcId', metavar='VPC',
+ help='[VPC only] ID of the VPC to create the group in')]
def print_result(self, result):
print self.tabify(('GROUP', result.get('groupId'),
diff --git a/euca2ools/commands/euca/createsnapshot.py b/euca2ools/commands/euca/createsnapshot.py
index b40d0fc..b418743 100644
--- a/euca2ools/commands/euca/createsnapshot.py
+++ b/euca2ools/commands/euca/createsnapshot.py
@@ -28,18 +28,20 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
+from euca2ools.commands.euca import EucalyptusRequest
from requestbuilder import Arg
-from . import EucalyptusRequest
+
class CreateSnapshot(EucalyptusRequest):
DESCRIPTION = 'Create a snapshot of a volume'
- ARGS = [Arg('VolumeId', metavar='VOLUME', help='volume to snapshot'),
+ ARGS = [Arg('VolumeId', metavar='VOLUME',
+ help='volume to create a snapshot of (required)'),
Arg('-d', '--description', metavar='DESC', dest='Description',
help='snapshot description')]
def print_result(self, result):
- print self.tabify(['SNAPSHOT', result.get('snapshotId'),
- result.get('volumeId'), result.get('status'),
+ print self.tabify(('SNAPSHOT', result.get('snapshotId'),
+ result.get('volumeId'), result.get('status'),
result.get('startTime'), result.get('ownerId'),
result.get('volumeSize'),
- result.get('description')])
+ result.get('description')))
diff --git a/euca2ools/commands/euca/createtags.py b/euca2ools/commands/euca/createtags.py
index 83d4681..7f28788 100644
--- a/euca2ools/commands/euca/createtags.py
+++ b/euca2ools/commands/euca/createtags.py
@@ -28,20 +28,20 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
+from euca2ools.commands.argtypes import binary_tag_def
+from euca2ools.commands.euca import EucalyptusRequest
from requestbuilder import Arg
-from . import EucalyptusRequest
-from ..argtypes import binary_tag_def
+
class CreateTags(EucalyptusRequest):
- API_VERSION = '2010-08-31'
DESCRIPTION = 'Add or overwrite tags for one or more resources'
ARGS = [Arg('ResourceId', metavar='RESOURCE', nargs='+',
- help='IDs of the resource(s) to tag'),
+ help='ID(s) of the resource(s) to tag (at least 1 required)'),
Arg('--tag', dest='Tag', metavar='KEY[=VALUE]',
type=binary_tag_def, action='append', required=True,
help='''key and optional value of the tag to create, separated
- by an "=" character. If no value is given the tag's
- value is set to an empty string.''')]
+ by an "=" character. If no value is given the tag's value is
+ set to an empty string. (at least 1 required)''')]
def print_result(self, result):
for resource_id in self.args['ResourceId']:
diff --git a/euca2ools/commands/euca/createvolume.py b/euca2ools/commands/euca/createvolume.py
index e1ec9da..3f39fdf 100644
--- a/euca2ools/commands/euca/createvolume.py
+++ b/euca2ools/commands/euca/createvolume.py
@@ -28,28 +28,37 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
+from euca2ools.commands.euca import EucalyptusRequest
from requestbuilder import Arg
-from . import EucalyptusRequest
+from requestbuilder.exceptions import ArgumentError
+
class CreateVolume(EucalyptusRequest):
DESCRIPTION = 'Create a new volume'
- ARGS = [Arg('-s', '--size', dest='Size', type=int,
- help='''size of the new volume in GiB. Required unless
- --snapshot is used'''),
+ ARGS = [Arg('-z', '--zone', dest='AvailabilityZone', metavar='ZONE',
+ required=True, help='''availability zone in which to create the
+ new volume (required)'''),
+ Arg('-s', '--size', dest='Size', type=int, help='''size of the new
+ volume in GiB (required unless --snapshot is used)'''),
Arg('--snapshot', dest='SnapshotId', metavar='SNAPSHOT',
help='snapshot from which to create the new volume'),
- Arg('-z', '--zone', dest='AvailabilityZone', metavar='ZONE',
- required=True,
- help='availability zone in which to create the new volume')]
+ Arg('-t', '--type', dest='VolumeType', metavar='VOLTYPE',
+ help='volume type'),
+ Arg('-i', '--iops', dest='Iops', type=int,
+ help='number of I/O operations per second')]
def configure(self):
EucalyptusRequest.configure(self)
if not self.args.get('Size') and not self.args.get('SnapshotId'):
- self._cli_parser.error('at least one of -s/--size and --snapshot '
- 'must be specified')
+ raise ArgumentError('-s/--size or --snapshot must be specified')
+ if self.args.get('Iops') and not self.args.get('VolumeType'):
+ raise ArgumentError('argument -i/--iops: -t/--type is required')
+ if self.args.get('Iops') and self.args.get('VolumeType') == 'standard':
+ raise ArgumentError(
+ 'argument -i/--iops: not allowed with volume type "standard"')
def print_result(self, result):
- print self.tabify(['VOLUME', result.get('volumeId'),
+ print self.tabify(('VOLUME', result.get('volumeId'),
result.get('size'), result.get('snapshotId'),
result.get('availabilityZone'),
- result.get('status'), result.get('createTime')])
+ result.get('status'), result.get('createTime')))
diff --git a/euca2ools/commands/euca/deletekeypair.py b/euca2ools/commands/euca/deletekeypair.py
index 4517bbf..dd9fac0 100644
--- a/euca2ools/commands/euca/deletekeypair.py
+++ b/euca2ools/commands/euca/deletekeypair.py
@@ -28,13 +28,14 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
+from euca2ools.commands.euca import EucalyptusRequest
from requestbuilder import Arg
-from . import EucalyptusRequest
+
class DeleteKeyPair(EucalyptusRequest):
- DESCRIPTION = 'Delete an existing keypair'
+ DESCRIPTION = 'Delete a key pair'
ARGS = [Arg('KeyName', metavar='KEYPAIR',
- help='name of the keypair to delete')]
+ help='name of the key pair to delete (required)')]
def print_result(self, result):
- print self.tabify(['KEYPAIR', self.args['KeyName']])
+ print self.tabify(('KEYPAIR', self.args['KeyName']))
diff --git a/euca2ools/commands/euca/deletesecuritygroup.py b/euca2ools/commands/euca/deletesecuritygroup.py
index 9ce27aa..300c54d 100644
--- a/euca2ools/commands/euca/deletesecuritygroup.py
+++ b/euca2ools/commands/euca/deletesecuritygroup.py
@@ -28,12 +28,20 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
+from euca2ools.commands.euca import EucalyptusRequest
from requestbuilder import Arg
-from . import EucalyptusRequest
+
class DeleteSecurityGroup(EucalyptusRequest):
DESCRIPTION = 'Delete a security group'
- ARGS = [Arg('GroupName', metavar='GROUP')]
+ ARGS = [Arg('group', metavar='GROUP', route_to=None,
+ help='name or ID of the security group to delete (required)')]
+
+ def preprocess(self):
+ if self.args['group'].startswith('sg-'):
+ self.params['GroupId'] = self.args['group']
+ else:
+ self.params['GroupName'] = self.args['group']
def print_result(self, result):
print self.tabify(('RETURN', result.get('return')))
diff --git a/euca2ools/commands/euca/deletesnapshot.py b/euca2ools/commands/euca/deletesnapshot.py
index 99d0b2f..f0cf73a 100644
--- a/euca2ools/commands/euca/deletesnapshot.py
+++ b/euca2ools/commands/euca/deletesnapshot.py
@@ -28,12 +28,14 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
+from euca2ools.commands.euca import EucalyptusRequest
from requestbuilder import Arg
-from . import EucalyptusRequest
+
class DeleteSnapshot(EucalyptusRequest):
DESCRIPTION = 'Delete a snapshot'
- ARGS = [Arg('SnapshotId', metavar='SNAPSHOT', help='snapshot to delete')]
+ ARGS = [Arg('SnapshotId', metavar='SNAPSHOT',
+ help='ID of the snapshot to delete (required)')]
def print_result(self, result):
- print self.tabify(['SNAPSHOT', self.args['SnapshotId']])
+ print self.tabify(('SNAPSHOT', self.args['SnapshotId']))
diff --git a/euca2ools/commands/euca/deletetags.py b/euca2ools/commands/euca/deletetags.py
index e2bcf00..34452f4 100644
--- a/euca2ools/commands/euca/deletetags.py
+++ b/euca2ools/commands/euca/deletetags.py
@@ -28,23 +28,22 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
+from euca2ools.commands.argtypes import ternary_tag_def
+from euca2ools.commands.euca import EucalyptusRequest
from requestbuilder import Arg
-from . import EucalyptusRequest
-from ..argtypes import ternary_tag_def
+
class DeleteTags(EucalyptusRequest):
- API_VERSION = '2010-08-31'
DESCRIPTION = 'Delete tags from one or more resources'
- ARGS = [Arg('ResourceId', metavar='RESOURCE', nargs='+',
- help='IDs of the resource(s) to un-tag'),
+ ARGS = [Arg('ResourceId', metavar='RESOURCE', nargs='+', help='''ID(s) of
+ the resource(s) to un-tag (at least 1 required)'''),
Arg('--tag', dest='Tag', metavar='KEY[=[VALUE]]',
type=ternary_tag_def, action='append', required=True,
help='''key and optional value of the tag to delete, separated
- by an "=" character. If no value is given, but a "="
- character is, then the tag is deleted if its value is
- not an empty string. If neither a value nor a "="
- character is given then the tag with that key is
- deleted regardless of its value.''')]
-
- def print_result(self, result):
- pass
+ by an "=" character. If you specify a value then the tag is
+ deleted only if its value matches the one you specified. If
+ you specify the empty string as the value (e.g. "--tag foo=")
+ then the tag is deleted only if its value is the empty
+ string. If you do not specify a value (e.g. "--tag foo") then
+ the tag is deleted regardless of its value. (at least 1
+ required)''')]
diff --git a/euca2ools/commands/euca/deletevolume.py b/euca2ools/commands/euca/deletevolume.py
index 2b7cbe1..c0a58c3 100644
--- a/euca2ools/commands/euca/deletevolume.py
+++ b/euca2ools/commands/euca/deletevolume.py
@@ -28,12 +28,14 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
+from euca2ools.commands.euca import EucalyptusRequest
from requestbuilder import Arg
-from . import EucalyptusRequest
+
class DeleteVolume(EucalyptusRequest):
DESCRIPTION = 'Delete a volume'
- ARGS = [Arg('VolumeId', metavar='VOLUME', help='volume to delete')]
+ ARGS = [Arg('VolumeId', metavar='VOLUME',
+ help='ID of the volume to delete (required)')]
def print_result(self, result):
- print self.tabify(['VOLUME', self.args['VolumeId']])
+ print self.tabify(('VOLUME', self.args['VolumeId']))
diff --git a/euca2ools/commands/euca/deregisterimage.py b/euca2ools/commands/euca/deregisterimage.py
index c52a08e..a640e73 100644
--- a/euca2ools/commands/euca/deregisterimage.py
+++ b/euca2ools/commands/euca/deregisterimage.py
@@ -28,12 +28,17 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
+from euca2ools.commands.euca import EucalyptusRequest
from requestbuilder import Arg
-from . import EucalyptusRequest
+
class DeregisterImage(EucalyptusRequest):
- DESCRIPTION = 'De-register an image'
- ARGS = [Arg('ImageId', metavar='IMAGE', help='image to de-register')]
+ DESCRIPTION = ('De-register an image. After you de-register an image it '
+ 'cannot be used to launch new instances.\n\nNote that in '
+ 'Eucalyptus 3 you may need to run this twice to completely '
+ "remove an image's registration from the system.")
+ ARGS = [Arg('ImageId', metavar='IMAGE',
+ help='ID of the image to de-register (required)')]
def print_result(self, result):
- print self.tabify(['IMAGE', self.args['ImageId']])
+ print self.tabify(('IMAGE', self.args['ImageId']))
diff --git a/euca2ools/commands/euca/describeaddresses.py b/euca2ools/commands/euca/describeaddresses.py
index b556dd9..5350884 100644
--- a/euca2ools/commands/euca/describeaddresses.py
+++ b/euca2ools/commands/euca/describeaddresses.py
@@ -28,21 +28,27 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
+from euca2ools.commands.euca import EucalyptusRequest
from requestbuilder import Arg, Filter
-from . import EucalyptusRequest
+
class DescribeAddresses(EucalyptusRequest):
- API_VERSION = '2011-01-01'
DESCRIPTION = 'Show information about elastic IP addresses'
ARGS = [Arg('address', nargs='*', route_to=None,
- help='''limit results to one or more elastic IP addresses or
- allocation IDs''')]
- FILTERS = [Filter('allocation-id', help='allocation ID (VPC only)'),
- Filter('association-id', help='association ID (VPC only)'),
- Filter('domain', choices=['standard', 'vpc'],
+ help='''limit results to specific elastic IP addresses or
+ VPC allocation IDs''')]
+ FILTERS = [Filter('allocation-id', help='[VPC only] allocation ID'),
+ Filter('association-id', help='[VPC only] association ID'),
+ Filter('domain', choices=('standard', 'vpc'),
help='whether the address is a standard or VPC address'),
Filter('instance-id',
help='instance the address is associated with'),
+ Filter('network-interface-id', help='''[VPC only] network
+ interface the address is associated with'''),
+ Filter('network-interface-owner-id', help='''[VPC only] ID of
+ the network interface's owner'''),
+ Filter('private-ip-address', help='''[VPC only] private address
+ associated with the public address'''),
Filter('public-ip', help='the elastic IP address')]
LIST_TAGS = ['addressesSet']
@@ -52,14 +58,16 @@ class DescribeAddresses(EucalyptusRequest):
public_ips = set(self.args.get('address', [])) - alloc_ids
self.params = {}
if alloc_ids:
- self.params['AllocationId'] = list(alloc_ids)
+ self.params['AllocationId'] = list(sorted(alloc_ids))
if public_ips:
- self.params['PublicIp'] = list(public_ips)
+ self.params['PublicIp'] = list(sorted(public_ips))
def print_result(self, result):
for addr in result.get('addressesSet', []):
- print self.tabify(['ADDRESS', addr.get('publicIp'),
+ print self.tabify(('ADDRESS', addr.get('publicIp'),
addr.get('instanceId'),
addr.get('domain', 'standard'),
addr.get('allocationId'),
- addr.get('associationId')])
+ addr.get('associationId'),
+ addr.get('networkInterfaceId'),
+ addr.get('privateIpAddress')))
diff --git a/euca2ools/commands/euca/describeavailabilityzones.py b/euca2ools/commands/euca/describeavailabilityzones.py
index e699b8c..710a74c 100644
--- a/euca2ools/commands/euca/describeavailabilityzones.py
+++ b/euca2ools/commands/euca/describeavailabilityzones.py
@@ -28,17 +28,17 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
+from euca2ools.commands.euca import EucalyptusRequest
import euca2ools.utils
from requestbuilder import Arg, Filter
-from . import EucalyptusRequest
+
class DescribeAvailabilityZones(EucalyptusRequest):
- DESCRIPTION = 'Display availability zones within the active region'
- API_VERSION = '2010-08-31'
+ DESCRIPTION = 'Display availability zones within the current region'
ARGS = [Arg('ZoneName', metavar='ZONE', nargs='*',
- help='limit results to one or more availability zones')]
- FILTERS = [Filter('message', help=('message giving information about the'
- 'availability zone')),
+ help='limit results to specific availability zones')]
+ FILTERS = [Filter('message', help='''message giving information about the
+ 'availability zone'''),
Filter('region-name',
help='region the availability zone is in'),
Filter('state', help='state of the availability zone'),
diff --git a/euca2ools/commands/euca/describebundletasks.py b/euca2ools/commands/euca/describebundletasks.py
index e75cee0..ab67607 100644
--- a/euca2ools/commands/euca/describebundletasks.py
+++ b/euca2ools/commands/euca/describebundletasks.py
@@ -28,14 +28,14 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
+from euca2ools.commands.euca import EucalyptusRequest
from requestbuilder import Arg, Filter
-from . import EucalyptusRequest
+
class DescribeBundleTasks(EucalyptusRequest):
DESCRIPTION = 'Describe current instance-bundling tasks'
- API_VERSION = '2010-08-31'
ARGS = [Arg('BundleId', metavar='BUNDLE', nargs='*',
- help='limit results to one or more bundle tasks')]
+ help='limit results to specific bundle tasks')]
FILTERS = [Filter('bundle-id', help='bundle task ID'),
Filter('error-code',
help='if the task failed, the error code returned'),
diff --git a/euca2ools/commands/euca/describeimageattribute.py b/euca2ools/commands/euca/describeimageattribute.py
index dae3b68..02469ad 100644
--- a/euca2ools/commands/euca/describeimageattribute.py
+++ b/euca2ools/commands/euca/describeimageattribute.py
@@ -28,8 +28,9 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
+from euca2ools.commands.euca import EucalyptusRequest
from requestbuilder import Arg, MutuallyExclusiveArgList
-from . import EucalyptusRequest
+
class DescribeImageAttribute(EucalyptusRequest):
DESCRIPTION = 'Show information about an attribute of an image'
diff --git a/euca2ools/commands/euca/describeimages.py b/euca2ools/commands/euca/describeimages.py
index e0db1be..e1debd3 100644
--- a/euca2ools/commands/euca/describeimages.py
+++ b/euca2ools/commands/euca/describeimages.py
@@ -28,19 +28,17 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
+from euca2ools.commands.euca import EucalyptusRequest
from requestbuilder import Arg, Filter, GenericTagFilter
-from . import EucalyptusRequest
+from requestbuilder.exceptions import ArgumentError
-class DescribeImages(EucalyptusRequest):
- DESCRIPTION = '''\
- Show information about images
-
- By default, only images the caller owns and images for which the caller
- has explicit launch permissions are shown.'''
- API_VERSION = '2010-08-31'
+class DescribeImages(EucalyptusRequest):
+ DESCRIPTION = ('Show information about images\n\nBy default, only images '
+ 'your account owns and images for which your account has '
+ 'explicit launch permissions are shown.')
ARGS = [Arg('ImageId', metavar='IMAGE', nargs='*',
- help='limit results to one or more images'),
+ help='limit results to specific images'),
Arg('-a', '--all', action='store_true', route_to=None,
help='describe all images'),
Arg('-o', '--owner', dest='Owner', metavar='ACCOUNT',
@@ -48,20 +46,23 @@ class DescribeImages(EucalyptusRequest):
help='describe images owned by the specified owner'),
Arg('-x', '--executable-by', dest='ExecutableBy',
metavar='ACCOUNT', action='append',
- help='''describe images for which the specified entity has
- explicit launch permissions''')]
+ help='''describe images for which the specified account has
+ explicit launch permissions''')]
FILTERS = [Filter('architecture', choices=('i386', 'x86_64', 'armhf'),
- help='image architecture'),
+ help='CPU architecture'),
Filter('block-device-mapping.delete-on-termination',
help='''whether a volume is deleted upon instance
- termination'''),
+ termination'''),
Filter('block-device-mapping.device-name',
help='device name for a volume mapped to the image'),
Filter('block-device-mapping.snapshot-id',
help='snapshot ID for a volume mapped to the image'),
Filter('block-device-mapping.volume-size',
help='volume size for a volume mapped to the image'),
+ Filter('block-device-mapping.volume-type',
+ help='volume type for a volume mapped to the image'),
Filter('description', help='image description'),
+ Filter('hypervisor', help='image\'s hypervisor type'),
Filter('image-id'),
Filter('image-type', choices=('machine', 'kernel', 'ramdisk'),
help='image type ("machine", "kernel", or "ramdisk")'),
@@ -82,7 +83,7 @@ class DescribeImages(EucalyptusRequest):
help='root device type ("ebs" or "instance-store")'),
Filter('state', choices=('available', 'pending', 'failed'),
help='''image state ("available", "pending", or
- "failed")'''),
+ "failed")'''),
Filter('state-reason-code',
help='reason code for the most recent state change'),
Filter('state-reason-message',
@@ -93,34 +94,33 @@ class DescribeImages(EucalyptusRequest):
GenericTagFilter('tag:KEY',
help='specific tag key/value combination'),
Filter('virtualization-type', choices=('paravirtual', 'hvm'),
- help='virtualization type ("paravirtual" or "hvm")'),
- Filter('hypervisor', choices=('ovm', 'xen'),
- help='image\'s hypervisor type ("ovm" or "xen")')]
- LIST_TAGS = ['imagesSet', 'blockDeviceMapping', 'tagSet']
+ help='virtualization type ("paravirtual" or "hvm")')]
+ LIST_TAGS = ['imagesSet', 'productCodes', 'blockDeviceMapping', 'tagSet']
def configure(self):
EucalyptusRequest.configure(self)
if self.args['all']:
if self.args.get('ImageId'):
- self._cli_parser.error('argument -a/--all: not allowed with '
- 'a list of images')
+ raise ArgumentError('argument -a/--all: not allowed with '
+ 'a list of images')
if self.args.get('ExecutableBy'):
- self._cli_parser.error('argument -a/--all: not allowed with '
- 'argument -x/--executable-by')
+ raise ArgumentError('argument -a/--all: not allowed with '
+ 'argument -x/--executable-by')
if self.args.get('Owner'):
- self._cli_parser.error('argument -a/--all: not allowed with '
- 'argument -o/--owner')
+ raise ArgumentError('argument -a/--all: not allowed with '
+ 'argument -o/--owner')
def main(self):
if not any(self.args.get(item) for item in ('all', 'ImageId',
'ExecutableBy', 'Owner')):
# Default to owned images and images with explicit launch perms
- self.params = {'Owner': 'self'}
+ self.params['Owner'] = ['self']
owned = self.send()
- self.params = {'ExecutableBy': 'self'}
+ del self.params['Owner']
+ self.params['ExecutableBy'] = ['self']
executable = self.send()
- self.params = None
- owned['imagesSet'] = (owned.get( 'imagesSet', []) +
+ del self.params['ExecutableBy']
+ owned['imagesSet'] = (owned.get('imagesSet', []) +
executable.get('imagesSet', []))
return owned
else:
diff --git a/euca2ools/commands/euca/describeinstances.py b/euca2ools/commands/euca/describeinstances.py
index 52c4a1e..97311d3 100644
--- a/euca2ools/commands/euca/describeinstances.py
+++ b/euca2ools/commands/euca/describeinstances.py
@@ -28,22 +28,33 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
+from euca2ools.commands.euca import EucalyptusRequest
from requestbuilder import Arg, Filter, GenericTagFilter
-from . import EucalyptusRequest
+
class DescribeInstances(EucalyptusRequest):
- API_VERSION = '2010-08-31'
DESCRIPTION = 'Show information about instances'
ARGS = [Arg('InstanceId', metavar='INSTANCE', nargs='*',
- help='Limit results to one or more instances')]
+ help='limit results to specific instances')]
FILTERS = [Filter('architecture', choices=('i386', 'x86_64', 'armhf'),
help='CPU architecture'),
+ Filter('association.allocation-id',
+ help='''[VPC only] allocation ID bound to a network
+ interface's elastic IP address'''),
+ Filter('association.association-id', help='''[VPC only]
+ association ID returned when an elastic IP was associated
+ with a network interface'''),
+ Filter('association.ip-owner-id',
+ help='''[VPC only] ID of the owner of the elastic IP
+ address associated with a network interface'''),
+ Filter('association.public-ip', help='''[VPC only] address of
+ the elastic IP address bound to a network interface'''),
Filter('availability-zone'),
Filter('block-device-mapping.attach-time',
help='volume attachment time'),
Filter('block-device-mapping.delete-on-termination', type=bool,
help='''whether a volume is deleted upon instance
- termination'''),
+ termination'''),
Filter('block-device-mapping.device-name',
help='volume device name (e.g. /dev/sdf)'),
Filter('block-device-mapping.status', help='volume status'),
@@ -51,56 +62,133 @@ class DescribeInstances(EucalyptusRequest):
Filter('client-token',
help='idempotency token provided at instance run time'),
Filter('dns-name', help='public DNS name'),
- Filter('group-id', help='security group membership'),
+ # EC2's documentation for "group-id" refers VPC users to
+ # "instance.group-id", while their documentation for the latter
+ # refers them to the former. Consequently, I'm not going to
+ # document a difference for either. They both seem to work for
+ # non-VPC instances.
+ Filter('group-id', help='security group ID'),
+ Filter('group-name', help='security group name'),
Filter('hypervisor', help='hypervisor type'),
Filter('image-id', help='machine image ID'),
+ Filter('instance.group-id', help='security group ID'),
+ Filter('instance.group-name', help='security group name'),
Filter('instance-id'),
- Filter('instance-lifecycle', choices=['spot'],
+ Filter('instance-lifecycle', choices=('spot',),
help='whether this is a spot instance'),
Filter('instance-state-code', type=int,
help='numeric code identifying instance state'),
Filter('instance-state-name', help='instance state'),
- Filter('instance-type',),
+ Filter('instance-type'),
Filter('ip-address', help='public IP address'),
Filter('kernel-id', help='kernel image ID'),
Filter('key-name',
help='key pair name provided at instance launch time'),
Filter('launch-index', help='launch index within a reservation'),
Filter('launch-time', help='instance launch time'),
- Filter('monitoring-state', help='whether monitoring is enabled'),
- Filter('owner-id', help='instance owner\'s account ID'),
+ Filter('monitoring-state', choices=('enabled', 'disabled'),
+ help='monitoring state ("enabled" or "disabled")'),
+ Filter('network-interface.addresses.association.ip-owner-id',
+ help='''[VPC only] ID of the owner of the private IP
+ address associated with a network interface'''),
+ Filter('network-interface.addresses.association.public-ip',
+ help='''[VPC only] ID of the association of an elastic IP
+ address with a network interface'''),
+ Filter('network-interface.addresses.primary',
+ choices=('true', 'false'),
+ help='''[VPC only] whether the IP address of the VPC
+ network interface is the primary private IP address'''),
+ Filter('network-interface.addresses.private-ip-address',
+ help='''[VPC only] network interface's private IP
+ address'''),
+ Filter('network-interface.attachment.device-index', type=int,
+ help='''[VPC only] device index to which a network
+ interface is attached'''),
+ Filter('network-interface.attachment.attach-time',
+ help='''[VPC only] time a network interface was attached
+ to an instance'''),
+ Filter('network-interface.attachment.attachment-id',
+ help='''[VPC only] ID of a network interface's
+ attachment'''),
+ Filter('network-interface.attachment.delete-on-termination',
+ choices=('true', 'false'),
+ help='''[VPC only] whether a network interface attachment
+ is deleted when an instance is terminated'''),
+ Filter('network-interface.attachment.instance-owner-id',
+ help='''[VPC only] ID of the instance to which a network
+ interface is attached'''),
+ Filter('network-interface.attachment.status',
+ choices=('attaching', 'attached', 'detaching',
+ 'detached'),
+ help="[VPC only] network interface's attachment status"),
+ Filter('network-interface.availability-zone',
+ help="[VPC only] network interface's availability zone"),
+ Filter('network-interface.description',
+ help='[VPC only] description of a network interface'),
+ Filter('network-interface.group-id',
+ help="[VPC only] network interface's security group ID"),
+ Filter('network-interface.group-name', help='''[VPC only]
+ network interface's security group name'''),
+ Filter('network-interface.mac-address',
+ help="[VPC only] network interface's hardware address"),
+ Filter('network-interface.network-interface.id',
+ help='[VPC only] ID of a network interface'),
+ Filter('network-interface.owner-id',
+ help="[VPC only] ID of a network interface's owner"),
+ Filter('network-interface.private-dns-name',
+ help="[VPC only] network interface's private DNS name"),
+ Filter('network-interface.requester-id',
+ help="[VPC only] network interface's requester ID"),
+ Filter('network-interface.requester-managed',
+ help='''[VPC only] whether the network interface is
+ managed by the service'''),
+ Filter('network-interface.source-destination-check',
+ choices=('true', 'false'),
+ help='''[VPC only] whether source/destination checking is
+ enabled for a network interface'''),
+ Filter('network-interface.status',
+ help="[VPC only] network interface's status"),
+ Filter('network-interface.subnet-id',
+ help="[VPC only] ID of a network interface's subnet"),
+ Filter('network-interface.vpc-id',
+ help="[VPC only] ID of a network interface's VPC"),
+ Filter('owner-id', help="instance owner's account ID"),
Filter('placement-group-name'),
- Filter('platform', choices=['windows'],
- help='whether this is a Windows instance'),
+ Filter('platform', help='"windows" for Windows instances'),
Filter('private-dns-name'),
Filter('private-ip-address'),
Filter('product-code'),
+ Filter('product-code.type', choices=('devpay', 'marketplace'),
+ help='type of product code ("devpay" or "marketplace")'),
Filter('ramdisk-id', help='ramdisk image ID'),
- Filter('reason', help='reason for the more recent state change'),
+ Filter('reason',
+ help="reason for the instance's current state"),
Filter('requestor-id',
help='ID of the entity that launched an instance'),
Filter('reservation-id'),
Filter('root-device-name',
help='root device name (e.g. /dev/sda1)'),
- Filter('root-device-type', choices=['ebs', 'instance-store'],
- help='root device type (ebs or instance-store)'),
+ Filter('root-device-type', choices=('ebs', 'instance-store'),
+ help='root device type ("ebs" or "instance-store")'),
Filter('spot-instance-request-id'),
Filter('state-reason-code',
help='reason code for the most recent state change'),
Filter('state-reason-message',
- help='message for the most recent state change'),
+ help='message describing the most recent state change'),
Filter('subnet-id',
- help='ID of the VPC subnet the instance is in'),
+ help='[VPC only] ID of the subnet the instance is in'),
Filter('tag-key',
help='name of any tag assigned to the instance'),
Filter('tag-value',
help='value of any tag assigned to the instance'),
GenericTagFilter('tag:KEY',
help='specific tag key/value combination'),
- Filter('virtualization-type', choices=['paravirtual', 'hvm']),
- Filter('vpc-id', help='ID of the VPC the instance is in')]
+ Filter('virtualization-type', choices=('paravirtual', 'hvm')),
+ Filter('vpc-id',
+ help='[VPC only] ID of the VPC the instance is in')]
LIST_TAGS = ['reservationSet', 'instancesSet', 'groupSet', 'tagSet',
- 'blockDeviceMapping', 'productCodes']
+ 'blockDeviceMapping', 'productCodes', 'networkInterfaceSet',
+ 'attachment', 'association', 'privateIpAddressesSet']
def print_result(self, result):
for reservation in result.get('reservationSet'):
diff --git a/euca2ools/commands/euca/describekeypairs.py b/euca2ools/commands/euca/describekeypairs.py
index 35e93a5..47fc683 100644
--- a/euca2ools/commands/euca/describekeypairs.py
+++ b/euca2ools/commands/euca/describekeypairs.py
@@ -28,11 +28,11 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
+from euca2ools.commands.euca import EucalyptusRequest
from requestbuilder import Arg, Filter
-from . import EucalyptusRequest
+
class DescribeKeyPairs(EucalyptusRequest):
- API_VERSION = '2010-08-31'
DESCRIPTION = 'Display information about available key pairs'
ARGS = [Arg('KeyName', nargs='*', metavar='KEYPAIR',
help='limit results to specific key pairs')]
diff --git a/euca2ools/commands/euca/describeregions.py b/euca2ools/commands/euca/describeregions.py
index e5ddaf9..cd1b73b 100644
--- a/euca2ools/commands/euca/describeregions.py
+++ b/euca2ools/commands/euca/describeregions.py
@@ -28,11 +28,11 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
+from euca2ools.commands.euca import EucalyptusRequest
from requestbuilder import Arg, Filter
-from . import EucalyptusRequest
+
class DescribeRegions(EucalyptusRequest):
- API_VERSION = '2010-08-31'
DESCRIPTION = 'Display information about regions'
ARGS = [Arg('RegionName', nargs='*', metavar='REGION',
help='limit results to specific regions')]
diff --git a/euca2ools/commands/euca/describesecuritygroups.py b/euca2ools/commands/euca/describesecuritygroups.py
index d2251dc..fa554ff 100644
--- a/euca2ools/commands/euca/describesecuritygroups.py
+++ b/euca2ools/commands/euca/describesecuritygroups.py
@@ -28,20 +28,17 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
+from euca2ools.commands.euca import EucalyptusRequest
from requestbuilder import Arg, Filter
-from . import EucalyptusRequest
-class DescribeSecurityGroups(EucalyptusRequest):
- DESCRIPTION = '''\
- Show information about security groups
-
- Note that filters are matched on literal strings only, so
- "--filter ip-permission.from-port=22" will *not* match a group with a
- port range of 20 to 30.'''
- API_VERSION = '2012-12-01'
- ARGS = [Arg('group', metavar='GROUP', nargs='*', route_to=None, default=[],
- help='limit results to one or more security groups')]
+class DescribeSecurityGroups(EucalyptusRequest):
+ DESCRIPTION = ('Show information about security groups\n\nNote that '
+ 'filters are matched on literal strings only, so '
+ '"--filter ip-permission.from-port=22" will *not* match a '
+ 'group with a port range of 20 to 30.')
+ ARGS = [Arg('group', metavar='GROUP', nargs='*', route_to=None,
+ default=[], help='limit results to specific security groups')]
FILTERS = [Filter('description', help='group description'),
Filter('group-id'),
Filter('group-name'),
@@ -61,9 +58,11 @@ class DescribeSecurityGroups(EucalyptusRequest):
Filter('owner-id', help=="account ID of the group's owner"),
Filter('tag-key', help='key of a tag assigned to the group'),
Filter('tag-value',
- help='value of a tag assigned to the group')]
+ help='value of a tag assigned to the group'),
+ Filter('vpc-id',
+ help='[VPC only] ID of a VPC the group belongs to')]
LIST_TAGS = ['securityGroupInfo', 'ipPermissions', 'ipPermissionsEgress',
- 'groups', 'ipRanges']
+ 'groups', 'ipRanges', 'tagSet']
def preprocess(self):
for group in self.args['group']:
@@ -81,12 +80,13 @@ class DescribeSecurityGroups(EucalyptusRequest):
def print_group(self, group):
print self.tabify(('GROUP', group.get('groupId'), group.get('ownerId'),
group.get('groupName'),
- group.get('groupDescription')))
+ group.get('groupDescription'),
+ group.get('vpcId')))
for perm in group.get('ipPermissions', []):
perm_base = ['PERMISSION', group.get('ownerId'),
- group.get('groupName'), 'ALLOWS']
- perm_base.extend([perm.get('ipProtocol'), perm.get('fromPort'),
- perm.get('toPort')])
+ group.get('groupName'), 'ALLOWS',
+ perm.get('ipProtocol'), perm.get('fromPort'),
+ perm.get('toPort')]
for cidr_range in perm.get('ipRanges', []):
perm_item = ['FROM', 'CIDR', cidr_range.get('cidrIp'),
'ingress']
@@ -101,9 +101,9 @@ class DescribeSecurityGroups(EucalyptusRequest):
print self.tabify(perm_base + perm_item)
for perm in group.get('ipPermissionsEgress', []):
perm_base = ['PERMISSION', group.get('ownerId'),
- group.get('groupName'), 'ALLOWS']
- perm_base.extend([perm.get('ipProtocol'), perm.get('fromPort'),
- perm.get('toPort')])
+ group.get('groupName'), 'ALLOWS',
+ perm.get('ipProtocol'), perm.get('fromPort'),
+ perm.get('toPort')]
for cidr_range in perm.get('ipRanges', []):
perm_item = ['TO', 'CIDR', cidr_range.get('cidrIp'), 'egress']
print self.tabify(perm_base + perm_item)
@@ -115,3 +115,6 @@ class DescribeSecurityGroups(EucalyptusRequest):
perm_item.extend(['GRPNAME', othergroup['groupName']])
perm_item.append('egress')
print self.tabify(perm_base + perm_item)
+ for tag in group.get('tagSet', []):
+ self.print_resource_tag(tag, (group.get('groupId') or
+ group.get('groupName')))
diff --git a/euca2ools/commands/euca/describesnapshots.py b/euca2ools/commands/euca/describesnapshots.py
index b06da4c..0eb978c 100644
--- a/euca2ools/commands/euca/describesnapshots.py
+++ b/euca2ools/commands/euca/describesnapshots.py
@@ -29,16 +29,15 @@
# POSSIBILITY OF SUCH DAMAGE.
from argparse import SUPPRESS
+from euca2ools.commands.euca import EucalyptusRequest
from requestbuilder import Arg, Filter, GenericTagFilter
-from . import EucalyptusRequest
+from requestbuilder.exceptions import ArgumentError
-class DescribeSnapshots(EucalyptusRequest):
- API_VERSION = '2010-08-31'
- DESCRIPTION = '''\
- Show information about snapshots
- By default, only snapshots explicitly restorable by the caller are
- shown.'''
+class DescribeSnapshots(EucalyptusRequest):
+ DESCRIPTION = ('Show information about snapshots\n\nBy default, only '
+ 'snapshots your account owns and snapshots for which your '
+ 'account has explicit restore permissions are shown.')
ARGS = [Arg('SnapshotId', nargs='*', metavar='SNAPSHOT',
help='limit results to specific snapshots'),
Arg('-a', '--all', action='store_true', route_to=None,
@@ -67,17 +66,29 @@ class DescribeSnapshots(EucalyptusRequest):
def configure(self):
EucalyptusRequest.configure(self)
- if not any(self.args.get(item) for item in ('all', 'Owner',
- 'RestorableBy')):
- # Default to restorable snapshots
- self.args['RestorableBy'] = ['self']
- elif self.args.get('all'):
+ if self.args.get('all'):
if self.args.get('Owner'):
- self._cli_parser.error('argument -a/--all: not allowed with '
- 'argument -o/--owner')
+ raise ArgumentError('argument -a/--all: not allowed with '
+ 'argument -o/--owner')
if self.args.get('RestorableBy'):
- self._cli_parser.error('argument -a/--all: not allowed with '
- 'argument -r/--restorable-by')
+ raise ArgumentError('argument -a/--all: not allowed with '
+ 'argument -r/--restorable-by')
+
+ def main(self):
+ if not any(self.args.get(item) for item in ('all', 'Owner',
+ 'RestorableBy')):
+ # Default to owned snapshots and those with explicit restore perms
+ self.params['Owner'] = ['self']
+ owned = self.send()
+ del self.params['Owner']
+ self.params['RestorableBy'] = ['self']
+ restorable = self.send()
+ del self.params['RestorableBy']
+ owned['snapshotSet'] = (owned.get('snapshotSet', []) +
+ restorable.get('snapshotSet', []))
+ return owned
+ else:
+ return self.send()
def print_result(self, result):
for snapshot in result.get('snapshotSet', []):
diff --git a/euca2ools/commands/euca/describetags.py b/euca2ools/commands/euca/describetags.py
index a104dd9..ab2ab6d 100644
--- a/euca2ools/commands/euca/describetags.py
+++ b/euca2ools/commands/euca/describetags.py
@@ -28,12 +28,12 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
+from euca2ools.commands.euca import EucalyptusRequest, RESOURCE_TYPE_MAP
from requestbuilder import Filter
-from . import EucalyptusRequest, RESOURCE_TYPE_MAP
+
class DescribeTags(EucalyptusRequest):
- API_VERSION = '2010-08-31'
- DESCRIPTION = 'List tags associated with your account'
+ DESCRIPTION = "List tags associated with your account's resources"
FILTERS = [Filter('key'),
Filter('resource-id'),
Filter('resource-type',
diff --git a/euca2ools/commands/euca/describevolumes.py b/euca2ools/commands/euca/describevolumes.py
index 52de269..1094121 100644
--- a/euca2ools/commands/euca/describevolumes.py
+++ b/euca2ools/commands/euca/describevolumes.py
@@ -28,14 +28,14 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
+from euca2ools.commands.euca import EucalyptusRequest
from requestbuilder import Arg, Filter, GenericTagFilter
-from . import EucalyptusRequest
+
class DescribeVolumes(EucalyptusRequest):
DESCRIPTION = 'Display information about volumes'
- API_VERSION = '2010-08-31'
ARGS = [Arg('VolumeId', metavar='VOLUME', nargs='*',
- help='volume(s) to describe (default: all volumes)')]
+ help='limit results to specific volumes')]
FILTERS = [Filter('attachment.attach-time', help='attachment start time'),
Filter('attachment.delete-on-termination', help='''whether the
volume will be deleted upon instance termination'''),
@@ -44,21 +44,22 @@ class DescribeVolumes(EucalyptusRequest):
Filter('attachment.instance-id',
help='ID of the instance the volume is attached to'),
Filter('attachment.status', help='attachment state',
- choices=['attaching', 'attached', 'detaching',
- 'detached']),
+ choices=('attaching', 'attached', 'detaching',
+ 'detached')),
Filter('availability-zone'),
Filter('create-time', help='creation time'),
Filter('size', type=int, help='size in GiB'),
Filter('snapshot-id',
help='snapshot from which the volume was created'),
- Filter('status', choices=['creating', 'available', 'in-use',
- 'deleting', 'deleted', 'error']),
+ Filter('status', choices=('creating', 'available', 'in-use',
+ 'deleting', 'deleted', 'error')),
Filter('tag-key', help='key of a tag assigned to the volume'),
Filter('tag-value',
help='value of a tag assigned to the volume'),
GenericTagFilter('tag:KEY',
help='specific tag key/value combination'),
- Filter(name='volume-id')]
+ Filter(name='volume-id'),
+ Filter(name='volume-type')]
LIST_TAGS = ['volumeSet', 'attachmentSet', 'tagSet']
def print_result(self, result):
diff --git a/euca2ools/commands/euca/detachvolume.py b/euca2ools/commands/euca/detachvolume.py
index 909b6b8..8ed2a19 100644
--- a/euca2ools/commands/euca/detachvolume.py
+++ b/euca2ools/commands/euca/detachvolume.py
@@ -28,18 +28,20 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
+from euca2ools.commands.euca import EucalyptusRequest
from requestbuilder import Arg
-from . import EucalyptusRequest
+
class DetachVolume(EucalyptusRequest):
DESCRIPTION = 'Detach a volume from an instance'
- ARGS = [Arg('VolumeId', metavar='VOLUME', help='volume to detach'),
+ ARGS = [Arg('VolumeId', metavar='VOLUME',
+ help='ID of the volume to detach (required)'),
Arg('-i', '--instance', dest='InstanceID', metavar='INSTANCE',
help='instance to detach from'),
Arg('-d', '--device', dest='Device', help='device name'),
Arg('-f', '--force', action='store_const', const='true',
help='''detach without waiting for the instance. Data may be
- lost''')]
+ lost.''')]
def print_result(self, result):
self.print_attachment(result)
diff --git a/euca2ools/commands/euca/disassociateaddress.py b/euca2ools/commands/euca/disassociateaddress.py
index ff65c10..88290a2 100644
--- a/euca2ools/commands/euca/disassociateaddress.py
+++ b/euca2ools/commands/euca/disassociateaddress.py
@@ -28,19 +28,32 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
+from euca2ools.commands.euca import EucalyptusRequest
from requestbuilder import Arg
-from . import EucalyptusRequest
+from requestbuilder.exceptions import ArgumentError
+
class DisassociateAddress(EucalyptusRequest):
DESCRIPTION = 'Disassociate an elastic IP address from an instance'
- ARGS = [Arg('address', route_to=None,
- help='elastic IP address or association ID to disassociate')]
+ ARGS = [Arg('PublicIp', metavar='ADDRESS', nargs='?', help='''[Non-VPC
+ only] elastic IP address to disassociate (required)'''),
+ Arg('-a', '--association-id', dest='AssociationId',
+ metavar='ASSOC',
+ help="[VPC only] address's association ID (required)")]
- def preprocess(self):
- if self.args['address'].startswith('eipassoc'):
- self.params = {'AssociationId': self.args['address']}
- else:
- self.params = {'PublicIp': self.args['address']}
+ def configure(self):
+ EucalyptusRequest.configure(self)
+ if self.args.get('PublicIp'):
+ if self.args.get('AssociationId'):
+ raise ArgumentError('argument -a/--association-id: not '
+ 'allowed with an IP address')
+ elif self.args['PublicIp'].startswith('eipassoc'):
+ raise ArgumentError('VPC elastic IP association IDs must be '
+ 'be specified with -a/--association-id')
+ elif not self.args.get('AssociationId'):
+ raise ArgumentError(
+ 'argument -a/--association-id or an IP address is required')
def print_result(self, result):
- print self.tabify(['ADDRESS', self.args['address']])
+ target = self.args.get('PublicIp') or self.args.get('AssociationId')
+ print self.tabify(('ADDRESS', target))
diff --git a/euca2ools/commands/euca/getconsoleoutput.py b/euca2ools/commands/euca/getconsoleoutput.py
index 541be6b..9c34e77 100644
--- a/euca2ools/commands/euca/getconsoleoutput.py
+++ b/euca2ools/commands/euca/getconsoleoutput.py
@@ -29,8 +29,9 @@
# POSSIBILITY OF SUCH DAMAGE.
import base64
+from euca2ools.commands.euca import EucalyptusRequest
from requestbuilder import Arg
-from . import EucalyptusRequest
+
CHAR_ESCAPES = {
u'\x00': u'^@', u'\x0c': u'^L', u'\x17': u'^W',
@@ -45,11 +46,13 @@ CHAR_ESCAPES = {
u'\x0b': u'^K', u'\x16': u'^V', u'\x7f': u'^?',
}
+
class GetConsoleOutput(EucalyptusRequest):
DESCRIPTION = 'Retrieve console output for the specified instance'
- ARGS = [Arg('InstanceId', metavar='INSTANCE',
- help='instance to obtain console output from'),
- Arg('--raw', action='store_true', route_to=None,
+ ARGS = [Arg('InstanceId', metavar='INSTANCE', help='''ID of the instance to
+ obtain console output from (required)'''),
+ Arg('-r', '--raw-console-output', action='store_true',
+ route_to=None,
help='Display raw output without escaping control characters')]
def print_result(self, result):
@@ -57,7 +60,7 @@ class GetConsoleOutput(EucalyptusRequest):
print result.get('timestamp', '')
output = base64.b64decode(result.get('output', ''))
output = output.decode()
- if not self.args['raw']:
+ if not self.args['raw_console_output']:
# Escape control characters
for char, escape in CHAR_ESCAPES.iteritems():
output = output.replace(char, escape)
diff --git a/euca2ools/commands/euca/getpassword.py b/euca2ools/commands/euca/getpassword.py
index 4e863ef..7171cec 100644
--- a/euca2ools/commands/euca/getpassword.py
+++ b/euca2ools/commands/euca/getpassword.py
@@ -29,19 +29,19 @@
# POSSIBILITY OF SUCH DAMAGE.
import base64
+from euca2ools.commands.argtypes import file_contents
+from euca2ools.commands.euca.getpassworddata import GetPasswordData
from M2Crypto import RSA
from requestbuilder import Arg
-from ..argtypes import file_contents
-from .getpassworddata import GetPasswordData
+
class GetPassword(GetPasswordData):
- ACTION = 'GetPasswordData'
- DESCRIPTION = '''Retrieve the administrator password for an instance
- running Windows'''
- ARGS = [Arg('-k', '--priv-launch-key', metavar='PRIVKEY',
+ DESCRIPTION = ('Retrieve the administrator password for an instance '
+ 'running Windows')
+ ARGS = [Arg('-k', '--priv-launch-key', metavar='FILE',
type=file_contents, required=True, route_to=None,
help='''file containing the private key corresponding to the
- key pair supplied at instance launch time''')]
+ key pair supplied at instance launch time (required)''')]
def print_result(self, result):
try:
diff --git a/euca2ools/commands/euca/getpassworddata.py b/euca2ools/commands/euca/getpassworddata.py
index ae60438..b847171 100644
--- a/euca2ools/commands/euca/getpassworddata.py
+++ b/euca2ools/commands/euca/getpassworddata.py
@@ -28,14 +28,17 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
+from euca2ools.commands.euca import EucalyptusRequest
from requestbuilder import Arg
-from . import EucalyptusRequest
+
class GetPasswordData(EucalyptusRequest):
- DESCRIPTION = '''Retrieve the encrypted administrator password for an
- instance running Windows'''
- ARGS = [Arg('InstanceId', metavar='INSTANCE',
- help='instance to obtain the initial password for')]
+ DESCRIPTION = ('Retrieve the encrypted administrator password for an '
+ 'instance running Windows. The encrypted password may be '
+ 'decrypted using the private key of the key pair given '
+ 'when launching the instance.')
+ ARGS = [Arg('InstanceId', metavar='INSTANCE', help='''ID of the instance to
+ obtain the initial password for (required)''')]
def print_result(self, result):
if result.get('passwordData'):
diff --git a/euca2ools/commands/euca/importkeypair.py b/euca2ools/commands/euca/importkeypair.py
index faa5299..09a1dd5 100644
--- a/euca2ools/commands/euca/importkeypair.py
+++ b/euca2ools/commands/euca/importkeypair.py
@@ -28,23 +28,19 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
-import base64
+from euca2ools.commands.argtypes import b64encoded_file_contents
+from euca2ools.commands.euca import EucalyptusRequest
from requestbuilder import Arg
-from . import EucalyptusRequest
-from ..argtypes import file_contents
+
class ImportKeyPair(EucalyptusRequest):
- API_VERSION = '2010-08-31'
- DESCRIPTION = 'Import a public RSA key'
+ DESCRIPTION = 'Import a public RSA key as a new key pair'
ARGS = [Arg('KeyName', metavar='KEYPAIR',
- help='name for the new key pair'),
- Arg('-f', '--public-key-file', dest='pubkey', metavar='PUBKEY',
- type=file_contents, required=True, route_to=None,
- help='file name of the public key to import')]
-
- def preprocess(self):
- self.params = {'PublicKeyMaterial':
- base64.b64encode(self.args['pubkey'])}
+ help='name for the new key pair (required)'),
+ Arg('-f', '--public-key-file', dest='PublicKeyMaterial',
+ metavar='FILE', type=b64encoded_file_contents, required=True,
+ help='''name of a file containing the public key to import
+ (required)''')]
def print_result(self, result):
print self.tabify(['KEYPAIR', result.get('keyName'),
diff --git a/euca2ools/commands/euca/modgroup.py b/euca2ools/commands/euca/modgroup.py
index 438f187..4d25d88 100644
--- a/euca2ools/commands/euca/modgroup.py
+++ b/euca2ools/commands/euca/modgroup.py
@@ -1,6 +1,6 @@
# Software License Agreement (BSD License)
#
-# Copyright (c) 2012, Eucalyptus Systems, Inc.
+# Copyright (c) 2012-2013, Eucalyptus Systems, Inc.
# All rights reserved.
#
# Redistribution and use of this software in source and binary forms, with or
@@ -28,76 +28,108 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
+from euca2ools.commands.euca import EucalyptusRequest
from requestbuilder import Arg, MutuallyExclusiveArgList
+from requestbuilder.exceptions import ArgumentError
import sys
-from . import EucalyptusRequest
+
class ModifySecurityGroupRequest(EucalyptusRequest):
'''
The basis for security group-editing commands
'''
- ARGS = [Arg('GroupName', metavar='GROUP',
- help='name of the security group to modify'),
+ ARGS = [Arg('group', metavar='GROUP', route_to=None,
+ help='name or ID of the security group to modify (required)'),
+ Arg('--egress', action='store_true', route_to=None,
+ help='''[VPC only] manage an egress rule, which controls
+ traffic leaving the group'''),
Arg('-P', '--protocol', dest='IpPermissions.1.IpProtocol',
choices=['tcp', 'udp', 'icmp', '6', '17', '1'], default='tcp',
help='protocol to affect (default: tcp)'),
- Arg('-p', '--port-range', dest='port_range', route_to=None,
- help='''range of ports (specified as "from-to") or a single
- port'''),
+ Arg('-p', '--port-range', dest='port_range', metavar='RANGE',
+ route_to=None, help='''range of ports (specified as "from-to")
+ or a single port number (required for tcp and udp)'''),
# ^ required for tcp and udp
Arg('-t', '--icmp-type-code', dest='icmp_type_code',
metavar='TYPE:CODE', route_to=None,
- help='ICMP type and code (specified as "type:code")'),
+ help='''ICMP type and code (specified as "type:code") (required
+ for icmp)'''),
# ^ required for icmp
MutuallyExclusiveArgList(
Arg('-s', '--cidr', metavar='CIDR',
dest='IpPermissions.1.IpRanges.1.CidrIp',
help='''IP range (default: 0.0.0.0/0)'''),
# ^ default is added by main()
- Arg('-o', metavar='GROUP',
- dest='IpPermissions.1.Groups.1.GroupName',
- help='''name of a security group with which to authorize
- network communication''')),
- Arg('-u', metavar='GROUP_USER',
+ Arg('-o', dest='target_group', metavar='GROUP', route_to=None,
+ help='''[Non-VPC only] name of a security group with which
+ to affect network communication''')),
+ Arg('-u', metavar='ACCOUNT',
dest='IpPermissions.1.Groups.1.UserId',
help='''ID of the account that owns the security group
- specified with -o''')]
- # ^ required if -o is used
+ specified with -o''')]
def configure(self):
EucalyptusRequest.configure(self)
+ if (self.args['group'].startswith('sg-') and
+ len(self.args['group']) == 11):
+ # The check could probably be a little better, but meh. Fix if
+ # needed.
+ self.params['GroupId'] = self.args['group']
+ else:
+ if self.args['egress']:
+ raise ArgumentError('egress rules must use group IDs, not '
+ 'names')
+ self.params['GroupName'] = self.args['group']
+
+ target_group = self.args.get('target_group')
+ if (target_group is not None and target_group.startswith('sg-') and
+ len(target_group) == 11):
+ # Same note as above
+ self.params['IpPermissions.1.Groups.1.GroupId'] = target_group
+ else:
+ if self.args['egress']:
+ raise ArgumentError('argument -o: egress rules must use group '
+ 'IDs, not names')
+ self.params['IpPermissions.1.Groups.1.GroupName'] = target_group
+
from_port = None
to_port = None
protocol = self.args.get('IpPermissions.1.IpProtocol')
if protocol in ['icmp', '1']:
+ if self.args.get('port_range'):
+ raise ArgumentError('argument -p/--port-range: not compatible '
+ 'with protocol ' + protocol)
if not self.args.get('icmp_type_code'):
- self._cli_parser.error('argument -t/--icmp-type-code is '
- 'required for ICMP')
+ raise ArgumentError('argument -t/--icmp-type-code is required '
+ 'for protocol ' + protocol)
types = self.args['icmp_type_code'].split(':')
if len(types) == 2:
try:
from_port = int(types[0])
to_port = int(types[1])
except ValueError:
- self._cli_parser.error('argument -t/--icmp-type-code: '
- 'value must have format "1:2"')
+ raise ArgumentError('argument -t/--icmp-type-code: value '
+ 'must have format "1:2"')
else:
- self._cli_parser.error('argument -t/--icmp-type-code: value '
- 'must have format "1:2"')
+ raise ArgumentError('argument -t/--icmp-type-code: value must '
+ 'have format "1:2"')
if from_port < -1 or to_port < -1:
- self._cli_parser.error('argument -t/--icmp-type-code: type, '
- 'code must be at least -1')
+ raise ArgumentError('argument -t/--icmp-type-code: type, code '
+ 'must be at least -1')
elif protocol in ['tcp', 'udp', '6', '17']:
+ if self.args.get('icmp_type_code'):
+ raise ArgumentError('argument -t/--icmp-type-code: not '
+ 'compatible with protocol ' + protocol)
if not self.args.get('port_range'):
- self._cli_parser.error('argument -p/--port-range is required '
- 'for protocol ' + protocol)
+ raise ArgumentError('argument -p/--port-range is required for '
+ 'protocol ' + protocol)
if ':' in self.args['port_range']:
# Be extra helpful in the event of this common typo
- self._cli_parser.error('argument -p/--port-range: multi-port '
- 'range must be separated by "-", not ":"')
+ raise ArgumentError('argument -p/--port-range: multi-port '
+ 'range must be separated by "-", not ":"')
if self.args['port_range'].startswith('-'):
ports = self.args['port_range'][1:].split('-')
ports[0] = '-' + ports[0]
@@ -108,20 +140,20 @@ class ModifySecurityGroupRequest(EucalyptusRequest):
from_port = int(ports[0])
to_port = int(ports[1])
except ValueError:
- self._cli_parser.error('argument -p/--port-range: '
- 'multi-port value must be comprised of integers')
+ raise ArgumentError('argument -p/--port-range: multi-port '
+ 'value must be comprised of integers')
elif len(ports) == 1:
try:
from_port = to_port = int(ports[0])
except ValueError:
- self._cli_parser.error('argument -p/--port-range: single '
- 'port value must be an integer')
+ raise ArgumentError('argument -p/--port-range: single '
+ 'port value must be an integer')
else:
- self._cli_parser.error('argument -p/--port-range: value must '
- 'have format "1" or "1-2"')
+ raise ArgumentError('argument -p/--port-range: value must '
+ 'have format "1" or "1-2"')
if from_port < -1 or to_port < -1:
- self._cli_parser.error('argument -p/--port-range: port '
- 'number(s) must be at least -1')
+ raise ArgumentError('argument -p/--port-range: port number(s) '
+ 'must be at least -1')
else:
# Shouldn't get here since argparse should only allow the values we
# handle
@@ -133,27 +165,32 @@ class ModifySecurityGroupRequest(EucalyptusRequest):
if not self.args.get('IpPermissions.1.IpRanges.1.GroupName'):
self.args.setdefault('IpPermissions.1.IpRanges.1.CidrIp',
'0.0.0.0/0')
- if (self.args.get('IpPermissions.1.Groups.1.GroupName') and
+ if (self.params.get('IpPermissions.1.Groups.1.GroupName') and
not self.args.get('IpPermissions.1.Groups.1.UserId')):
- self._cli_parser.error('argument -u is required when -o is '
- 'specified')
+ raise ArgumentError('argument -u is required when -o names a '
+ 'security group by name')
def print_result(self, result):
- print self.tabify(['GROUP', self.args.get('GroupName')])
- perm_str = ['PERMISSION', self.args.get('GroupName'), 'ALLOWS',
- self.args.get('IpPermissions.1.IpProtocol'),
- self.args.get('IpPermissions.1.FromPort'),
- self.args.get('IpPermissions.1.ToPort')]
- if self.args.get('IpPermissions.1.Groups.1.UserId'):
+ print self.tabify(['GROUP', self.args.get('group')])
+ perm_str = ['PERMISSION', self.args.get('group'), 'ALLOWS',
+ self.params.get('IpPermissions.1.IpProtocol'),
+ self.params.get('IpPermissions.1.FromPort'),
+ self.params.get('IpPermissions.1.ToPort')]
+ if self.params.get('IpPermissions.1.Groups.1.UserId'):
perm_str.append('USER')
- perm_str.append(self.args.get('IpPermissions.1.Groups.1.UserId'))
- if self.args.get('IpPermissions.1.Groups.1.GroupName'):
+ perm_str.append(self.params.get('IpPermissions.1.Groups.1.UserId'))
+ if self.params.get('IpPermissions.1.Groups.1.GroupId'):
+ perm_str.append('GRPID')
+ perm_str.append(self.params.get(
+ 'IpPermissions.1.Groups.1.GroupId'))
+ elif self.params.get('IpPermissions.1.Groups.1.GroupName'):
perm_str.append('GRPNAME')
- perm_str.append(self.args.get(
- 'IpPermissions.1.Groups.1.GroupName'))
- if self.args.get('IpPermissions.1.IpRanges.1.CidrIp'):
+ perm_str.append(self.params.get(
+ 'IpPermissions.1.Groups.1.GroupName'))
+ if self.params.get('IpPermissions.1.IpRanges.1.CidrIp'):
perm_str.extend(['FROM', 'CIDR'])
- perm_str.append(self.args.get('IpPermissions.1.IpRanges.1.CidrIp'))
+ perm_str.append(self.params.get(
+ 'IpPermissions.1.IpRanges.1.CidrIp'))
print self.tabify(perm_str)
def process_cli_args(self):
diff --git a/euca2ools/commands/euca/modifyimageattribute.py b/euca2ools/commands/euca/modifyimageattribute.py
index a6265ab..6dcb8ce 100644
--- a/euca2ools/commands/euca/modifyimageattribute.py
+++ b/euca2ools/commands/euca/modifyimageattribute.py
@@ -28,8 +28,10 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
+from euca2ools.commands.euca import EucalyptusRequest
from requestbuilder import Arg, MutuallyExclusiveArgList
-from . import EucalyptusRequest
+from requestbuilder.exceptions import ArgumentError
+
class ModifyImageAttribute(EucalyptusRequest):
DESCRIPTION = 'Modify an attribute of an image'
@@ -48,7 +50,7 @@ class ModifyImageAttribute(EucalyptusRequest):
"all" for all accounts'''),
Arg('-r', '--remove', metavar='ENTITY', action='append',
default=[], route_to=None, help='''account to remove launch
- permission from , or "all" for all accounts''')]
+ permission from, or "all" for all accounts''')]
def preprocess(self):
if self.args.get('launch_permission'):
@@ -66,16 +68,16 @@ class ModifyImageAttribute(EucalyptusRequest):
else:
lp['Remove'].append({'UserId': entity})
if not lp:
- self._cli_parser.error('at least one entity must be specified '
- 'with -a/--add or -r/--remove')
+ raise ArgumentError('at least one entity must be specified '
+ 'with -a/--add or -r/--remove')
self.params['LaunchPermission'] = lp
else:
if self.args.get('add'):
- self._cli_parser.error('argument -a/--add may only be used '
- 'with -l/--launch-permission')
+ raise ArgumentError('argument -a/--add may only be used '
+ 'with -l/--launch-permission')
if self.args.get('remove'):
- self._cli_parser.error('argument -r/--remove may only be used '
- 'with -l/--launch-permission')
+ raise ArgumentError('argument -r/--remove may only be used '
+ 'with -l/--launch-permission')
def print_result(self, result):
if self.args.get('Description.Value'):
diff --git a/euca2ools/commands/euca/monitorinstances.py b/euca2ools/commands/euca/monitorinstances.py
index d459548..e3b741c 100644
--- a/euca2ools/commands/euca/monitorinstances.py
+++ b/euca2ools/commands/euca/monitorinstances.py
@@ -28,16 +28,17 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
+from euca2ools.commands.euca import EucalyptusRequest
from requestbuilder import Arg
-from . import EucalyptusRequest
+
class MonitorInstances(EucalyptusRequest):
DESCRIPTION = 'Enable monitoring for one or more instances'
- ARGS = [Arg('InstanceId', metavar='INSTANCE', nargs='+',
- help='instance(s) to monitor')]
+ ARGS = [Arg('InstanceId', metavar='INSTANCE', nargs='+', help='''ID(s) of
+ the instance(s) to begin monitoring (at least 1 required)''')]
LIST_TAGS = ['instancesSet']
def print_result(self, result):
for instance in result.get('instancesSet', []):
print self.tabify((instance.get('instanceId'), 'monitoring-' +
- instance.get('monitoring', {}).get('state')))
+ instance.get('monitoring', {}).get('state')))
diff --git a/euca2ools/commands/euca/rebootinstances.py b/euca2ools/commands/euca/rebootinstances.py
index 56e1b46..5e3f8e2 100644
--- a/euca2ools/commands/euca/rebootinstances.py
+++ b/euca2ools/commands/euca/rebootinstances.py
@@ -28,10 +28,11 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
+from euca2ools.commands.euca import EucalyptusRequest
from requestbuilder import Arg
-from . import EucalyptusRequest
+
class RebootInstances(EucalyptusRequest):
DESCRIPTION = 'Reboot one or more instances'
- ARGS = [Arg('InstanceId', metavar='INSTANCE', nargs='+',
- help='instance(s) to reboot')]
+ ARGS = [Arg('InstanceId', metavar='INSTANCE', nargs='+', help='''ID(s) of
+ the instance(s) to reboot (at least 1 required)''')]
diff --git a/euca2ools/commands/euca/registerimage.py b/euca2ools/commands/euca/registerimage.py
index 8f4fa50..f785a98 100644
--- a/euca2ools/commands/euca/registerimage.py
+++ b/euca2ools/commands/euca/registerimage.py
@@ -28,9 +28,11 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
+from euca2ools.commands.argtypes import ec2_block_device_mapping
+from euca2ools.commands.euca import EucalyptusRequest
from requestbuilder import Arg
-from . import EucalyptusRequest
-from ..argtypes import ec2_block_device_mapping
+from requestbuilder.exceptions import ArgumentError
+
class RegisterImage(EucalyptusRequest):
DESCRIPTION = 'Register a new image'
@@ -45,30 +47,31 @@ class RegisterImage(EucalyptusRequest):
choices=('i386', 'x86_64', 'armhf'),
help='CPU architecture of the new image'),
Arg('--kernel', dest='KernelId', metavar='KERNEL',
- help='kernel to associate with the new image'),
+ help='ID of the kernel to associate with the new image'),
Arg('--ramdisk', dest='RamdiskId', metavar='RAMDISK',
- help='ramdisk to associate with the new image'),
+ help='ID of the ramdisk to associate with the new image'),
Arg('--root-device-name', dest='RootDeviceName', metavar='DEVICE',
help='root device name (default: /dev/sda1)'),
# ^ default is added by main()
- Arg('--snapshot', route_to=None,
+ Arg('-s', '--snapshot', route_to=None,
help='snapshot to use for the root device'),
Arg('-b', '--block-device-mapping', metavar='DEVICE=MAPPED',
dest='BlockDeviceMapping', action='append',
- type=block_device_mapping, default=[],
+ type=ec2_block_device_mapping, default=[],
help='''define a block device mapping for the image, in the
form DEVICE=MAPPED, where "MAPPED" is "none", "ephemeral(0-3)",
- or "[SNAP-ID]:[SIZE]:[true|false]"''')]
+ or
+ "[SNAP-ID]:[SIZE]:[true|false]:[standard|VOLTYPE[:IOPS]]"''')]
def preprocess(self):
if self.args.get('ImageLocation'):
# instance-store image
if self.args.get('RootDeviceName'):
- self._cli_parser.error('argument --root-device-name: not '
- 'allowed with argument MANIFEST')
+ raise ArgumentError('argument --root-device-name: not allowed '
+ 'with argument MANIFEST')
if self.args.get('snapshot'):
- self._cli_parser.error('argument --snapshot: not allowed '
- 'with argument MANIFEST')
+ raise ArgumentError('argument --snapshot: not allowed with '
+ 'argument MANIFEST')
else:
# Try for an EBS image
if not self.args.get('RootDeviceName'):
@@ -80,10 +83,9 @@ class RegisterImage(EucalyptusRequest):
if (snapshot and
snapshot != mapping.get('Ebs', {}).get('SnapshotId')):
# The mapping's snapshot differs or doesn't exist
- self._cli_parser.error('snapshot ID supplied with '
- '--snapshot conflicts with block device '
- 'mapping for root device ' +
- mapping['DeviceName'])
+ raise ArgumentError('snapshot ID supplied with '
+ '--snapshot conflicts with block device mapping '
+ 'for root device ' + mapping['DeviceName'])
else:
# No need to apply --snapshot since the mapping is
# already there
@@ -92,10 +94,10 @@ class RegisterImage(EucalyptusRequest):
if snapshot:
self.args['BlockDeviceMapping'].append(
{'DeviceName': self.args['RootDeviceName'],
- 'Ebs': {'SnapshotId': snapshot}})
+ 'Ebs': {'SnapshotId': snapshot}})
else:
- self._cli_parser.error('either a manifest location or a '
- 'root device snapshot mapping must be specified')
+ raise ArgumentError('either a manifest location or a root '
+ 'device snapshot mapping must be specified')
def print_result(self, result):
print self.tabify(('IMAGE', result.get('imageId')))
diff --git a/euca2ools/commands/euca/releaseaddress.py b/euca2ools/commands/euca/releaseaddress.py
index 646f099..a9a8c45 100644
--- a/euca2ools/commands/euca/releaseaddress.py
+++ b/euca2ools/commands/euca/releaseaddress.py
@@ -28,12 +28,30 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
+from euca2ools.commands.euca import EucalyptusRequest
from requestbuilder import Arg
-from . import EucalyptusRequest
+from requestbuilder.exceptions import ArgumentError
+
class ReleaseAddress(EucalyptusRequest):
DESCRIPTION = 'Release an elastic IP address'
- ARGS = [Arg('PublicIp', metavar='IP', help='elastic IP to release')]
+ ARGS = [Arg('PublicIp', metavar='ADDRESS', nargs='?',
+ help='[Non-VPC only] address to release (required)'),
+ Arg('-a', '--allocation-id', dest='AllocationId', metavar='ALLOC',
+ help='''[VPC only] allocation ID for the address to release
+ (required)''')]
+
+ def configure(self):
+ if (self.args.get('PublicIp') is not None and
+ self.args.get('AllocationId') is not None):
+ # Can't be both EC2 and VPC
+ raise ArgumentError(
+ 'argument -a/--allocation-id: not allowed with an IP address')
+ if (self.args.get('PublicIp') is None and
+ self.args.get('AllocationId') is None):
+ # ...but we still have to be one of them
+ raise ArgumentError(
+ 'argument -a/--allocation-id or an IP address is required')
def print_result(self, result):
print self.tabify(('ADDRESS', self.args.get('PublicIp'),
diff --git a/euca2ools/commands/euca/resetimageattribute.py b/euca2ools/commands/euca/resetimageattribute.py
index 474b300..0abd935 100644
--- a/euca2ools/commands/euca/resetimageattribute.py
+++ b/euca2ools/commands/euca/resetimageattribute.py
@@ -29,12 +29,13 @@
# POSSIBILITY OF SUCH DAMAGE.
from requestbuilder import Arg
-from . import EucalyptusRequest
+from euca2ools.commands.euca import EucalyptusRequest
+
class ResetImageAttribute(EucalyptusRequest):
DESCRIPTION = 'Reset an attribute of an image to its default value'
ARGS = [Arg('ImageId', metavar='IMAGE',
- help='image whose attribute should be reset'),
+ help='ID of the image whose attribute should be reset (required)'),
Arg('-l', '--launch-permission', dest='Attribute',
action='store_const', const='launchPermission', required=True,
help='reset launch permissions')]
diff --git a/euca2ools/commands/euca/revoke.py b/euca2ools/commands/euca/revoke.py
index c1630c6..0841ad6 100644
--- a/euca2ools/commands/euca/revoke.py
+++ b/euca2ools/commands/euca/revoke.py
@@ -28,8 +28,15 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
-from .modgroup import ModifySecurityGroupRequest
+from euca2ools.commands.euca.modgroup import ModifySecurityGroupRequest
+
class Revoke(ModifySecurityGroupRequest):
- NAME = 'RevokeSecurityGroupIngress'
- DESCRIPTION = 'Revoke an existing rule from a security group'
+ DESCRIPTION = 'Remove a rule from a security group'
+
+ @property
+ def action(self):
+ if self.args['egress']:
+ return 'RevokeSecurityGroupEgress'
+ else:
+ return 'RevokeSecurityGroupIngress'
diff --git a/euca2ools/commands/euca/runinstances.py b/euca2ools/commands/euca/runinstances.py
index b74fdf9..5c1a066 100644
--- a/euca2ools/commands/euca/runinstances.py
+++ b/euca2ools/commands/euca/runinstances.py
@@ -30,15 +30,19 @@
import argparse
import base64
+from euca2ools.commands.argtypes import (b64encoded_file_contents,
+ ec2_block_device_mapping, vpc_interface)
+from euca2ools.commands.euca import EucalyptusRequest
import os.path
from requestbuilder import Arg, MutuallyExclusiveArgList
+from requestbuilder.exceptions import ArgumentError
import sys
-from . import EucalyptusRequest
-from ..argtypes import b64encoded_file_contents, ec2_block_device_mapping
+
class RunInstances(EucalyptusRequest):
DESCRIPTION = 'Launch instances of a machine image'
- ARGS = [Arg('ImageId', metavar='IMAGE', help='image to instantiate'),
+ ARGS = [Arg('ImageId', metavar='IMAGE',
+ help='ID of the image to instantiate (required)'),
Arg('-n', '--instance-count', dest='count', metavar='MIN[-MAX]',
default='1', route_to=None,
help='''number of instances to launch. If this number of
@@ -63,36 +67,84 @@ class RunInstances(EucalyptusRequest):
help='''file containing user data to make available to the
instances in this reservation''')),
Arg('--addressing', dest='AddressingType',
- choices=('public', 'private'),
- help=('addressing scheme to launch the instance with. Use '
- '"private" to run an instance with no public address.')),
+ choices=('public', 'private'), help='''[Eucalyptus only]
+ addressing scheme to launch the instance with. Use "private"
+ to run an instance with no public address.'''),
Arg('-t', '--instance-type', dest='InstanceType',
help='type of instance to launch'),
+ Arg('-z', '--availability-zone', metavar='ZONE',
+ dest='Placement.AvailabilityZone'),
Arg('--kernel', dest='KernelId', metavar='KERNEL',
- help='kernel to launch the instance(s) with'),
+ help='ID of the kernel to launch the instance(s) with'),
Arg('--ramdisk', dest='RamdiskId', metavar='RAMDISK',
- help='ramdisk to launch the instance(s) with'),
+ help='ID of the ramdisk to launch the instance(s) with'),
Arg('-b', '--block-device-mapping', metavar='DEVICE=MAPPED',
dest='BlockDeviceMapping', action='append',
- type=block_device_mapping, default=[],
+ type=ec2_block_device_mapping, default=[],
help='''define a block device mapping for the instances, in the
- form DEVICE=MAPPED, where "MAPPED" is "none",
- "ephemeral(0-3)", or
- "[SNAP-ID]:[SIZE]:[true|false]"'''),
+ form DEVICE=MAPPED, where "MAPPED" is "none", "ephemeral(0-3)",
+ or
+ "[SNAP-ID]:[SIZE]:[true|false]:[standard|VOLTYPE[:IOPS]]"'''),
Arg('-m', '--monitor', dest='Monitoring.Enabled',
action='store_const', const='true',
help='enable detailed monitoring for the instance(s)'),
- Arg('--subnet', dest='SubnetId', metavar='SUBNET',
- help='VPC subnet in which to launch the instance(s)'),
- Arg('-z', '--availability-zone', metavar='ZONE',
- dest='Placement.AvailabilityZone'),
+ Arg('--disable-api-termination', dest='DisableApiTermination',
+ action='store_const', const='true',
+ help='prevent API users from terminating the instance(s)'),
Arg('--instance-initiated-shutdown-behavior',
dest='InstanceInitiatedShutdownBehavior',
choices=('stop', 'terminate'),
help=('whether to "stop" (default) or terminate EBS instances '
- 'when they shut down'))]
+ 'when they shut down')),
+ Arg('--placement-group', dest='Placement.GroupName',
+ metavar='PLGROUP', help='''name of a placement group to launch
+ into'''),
+ Arg('--tenancy', dest='Placement.Tenancy',
+ choices=('default', 'dedicated'), help='''[VPC only]
+ "dedicated" to run on single-tenant hardware'''),
+ Arg('--client-token', dest='ClientToken', metavar='TOKEN',
+ help='unique identifier to ensure request idempotency'),
+ Arg('-s', '--subnet', metavar='SUBNET', route_to=None,
+ help='''[VPC only] subnet to create the instance's network
+ interface in'''),
+ Arg('--private-ip-address', metavar='ADDRESS', route_to=None,
+ help='''[VPC only] assign a specific primary private IP address
+ to an instance's interface'''),
+ MutuallyExclusiveArgList(
+ Arg('--secondary-private-ip-address', metavar='ADDRESS',
+ action='append', route_to=None, help='''[VPC only]
+ assign a specific secondary private IP address to an
+ instance's network interface. Use this option multiple
+ times to add additional addresses.'''),
+ Arg('--secondary-private-ip-address-count', metavar='COUNT',
+ type=int, route_to=None, help='''[VPC only]
+ automatically assign a specific number of secondary private
+ IP addresses to an instance's network interface''')),
+ Arg('-a', '--network-interface', dest='NetworkInterface',
+ metavar='INTERFACE', action='append', type=vpc_interface,
+ help='''[VPC only] add a network interface to the new
+ instance. If the interface already exists, supply its ID and
+ a numeric index for it, separated by ":", in the form
+ "eni-XXXXXXXX:INDEX". To create a new interface, supply a
+ numeric index and subnet ID for it, along with (in order) an
+ optional description, a primary private IP address, a list of
+ security group IDs to associate with the interface, whether to
+ delete the interface upon instance termination ("true" or
+ "false"), a number of secondary private IP addresses to create
+ automatically, and a list of secondary private IP addresses to
+ assign to the interface, separated by ":", in the form
+ ":INDEX:SUBNET:[DESCRIPTION]:[PRIV_IP]:[GROUP1,GROUP2,...]:[true|false]:[SEC_IP_COUNT|:SEC_IP1,SEC_IP2,...]". You cannot specify both of the
+ latter two. This option may be used multiple times. Each adds
+ another network interface.'''),
+ Arg('-p', '--iam-profile', metavar='IPROFILE', route_to=None,
+ help='''name or ARN of the IAM instance profile to associate
+ with the new instance(s)'''),
+ Arg('--ebs-optimized', dest='EbsOptimized', action='store_const',
+ const='true', help='optimize the new instance(s) for EBS I/O')]
+
LIST_TAGS = ['reservationSet', 'instancesSet', 'groupSet', 'tagSet',
- 'blockDeviceMapping', 'productCodes']
+ 'blockDeviceMapping', 'productCodes', 'networkInterfaceSet',
+ 'attachment', 'association', 'privateIpAddressesSet']
def preprocess(self):
counts = self.args['count'].split('-')
@@ -101,22 +153,22 @@ class RunInstances(EucalyptusRequest):
self.params['MinCount'] = int(counts[0])
self.params['MaxCount'] = int(counts[0])
except ValueError:
- self._cli_parser.error('argument -n/--instance-count: '
- 'instance count must be an integer')
+ raise ArgumentError('argument -n/--instance-count: instance '
+ 'count must be an integer')
elif len(counts) == 2:
try:
self.params['MinCount'] = int(counts[0])
self.params['MaxCount'] = int(counts[1])
except ValueError:
- self._cli_parser.error('argument -n/--instance-count: '
- 'instance count range must be must be comprised of '
- 'integers')
+ raise ArgumentError('argument -n/--instance-count: instance '
+ 'count range must be must be comprised of '
+ 'integers')
else:
- self._cli_parser.error('argument -n/--instance-count: value must '
- 'have format "1" or "1-2"')
+ raise ArgumentError('argument -n/--instance-count: value must '
+ 'have format "1" or "1-2"')
if self.params['MinCount'] < 1 or self.params['MaxCount'] < 1:
- self._cli_parser.error('argument -n/--instance-count: instance '
- 'count must be positive')
+ raise ArgumentError('argument -n/--instance-count: instance count '
+ 'must be positive')
if self.params['MinCount'] > self.params['MaxCount']:
self.log.debug('MinCount > MaxCount; swapping')
self.params.update({'MinCount': self.params['MaxCount'],
@@ -130,5 +182,33 @@ class RunInstances(EucalyptusRequest):
self.params.setdefault('SecurityGroup', [])
self.params['SecurityGroup'].append(group)
+ iprofile = self.args.get('iam_profile')
+ if iprofile:
+ if iprofile.startswith('arn:'):
+ self.params['IamInstanceProfile.Arn'] = iprofile
+ else:
+ self.params['IamInstanceProfile.Name'] = iprofile
+
+ # Assemble an interface out of the "friendly" split interface options
+ cli_iface = {}
+ if self.args.get('private_ip_address'):
+ cli_iface['PrivateIpAddresses'] = [
+ {'PrivateIpAddress': self.args['private_ip_address'],
+ 'Primary': 'true'}]
+ if self.args.get('secondary_private_ip_address'):
+ sec_ips = [{'PrivateIpAddress': addr} for addr in
+ self.args['secondary_private_ip_address']]
+ cli_iface.setdefault('PrivateIpAddresses', [])
+ cli_iface['PrivateIpAddresses'].extend(sec_ips)
+ if self.args.get('secondary_private_ip_address_count'):
+ sec_ip_count = self.args['secondary_private_ip_address_count']
+ cli_iface['SecondaryPrivateIpAddressCount'] = sec_ip_count
+ if self.args.get('subnet'):
+ cli_iface['SubnetId'] = self.args['subnet']
+ if cli_iface:
+ cli_iface['DeviceIndex'] = 0
+ self.params.setdefault('NetworkInterface', [])
+ self.params['NetworkInterface'].append(cli_iface)
+
def print_result(self, result):
self.print_reservation(result)
diff --git a/euca2ools/commands/euca/startinstances.py b/euca2ools/commands/euca/startinstances.py
index f4d0f1d..e14168d 100644
--- a/euca2ools/commands/euca/startinstances.py
+++ b/euca2ools/commands/euca/startinstances.py
@@ -28,13 +28,14 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
+from euca2ools.commands.euca import EucalyptusRequest
from requestbuilder import Arg
-from . import EucalyptusRequest
+
class StartInstances(EucalyptusRequest):
DESCRIPTION = 'Start one or more stopped instances'
ARGS = [Arg('InstanceId', metavar='INSTANCE', nargs='+',
- help='instance(s) to start')]
+ help='ID(s) of the instance(s) to start')]
LIST_TAGS = ['instancesSet']
def print_result(self, result):
diff --git a/euca2ools/commands/euca/stopinstances.py b/euca2ools/commands/euca/stopinstances.py
index 32426d9..c677a20 100644
--- a/euca2ools/commands/euca/stopinstances.py
+++ b/euca2ools/commands/euca/stopinstances.py
@@ -28,16 +28,17 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
+from euca2ools.commands.euca import EucalyptusRequest
from requestbuilder import Arg
-from . import EucalyptusRequest
+
class StopInstances(EucalyptusRequest):
DESCRIPTION = 'Stop one or more running instances'
ARGS = [Arg('InstanceId', metavar='INSTANCE', nargs='+',
- help='instance(s) to stop'),
+ help='ID(s) of the instance(s) to stop'),
Arg('-f', '--force', dest='Force', action='store_const',
const='true',
- help='immediately stop the instance. Data may be lost')]
+ help='immediately stop the instance(s). Data may be lost')]
LIST_TAGS = ['instancesSet']
def print_result(self, result):
diff --git a/euca2ools/commands/euca/terminateinstances.py b/euca2ools/commands/euca/terminateinstances.py
index 9021aa1..97fd42b 100644
--- a/euca2ools/commands/euca/terminateinstances.py
+++ b/euca2ools/commands/euca/terminateinstances.py
@@ -28,13 +28,14 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
+from euca2ools.commands.euca import EucalyptusRequest
from requestbuilder import Arg
-from . import EucalyptusRequest
+
class TerminateInstances(EucalyptusRequest):
DESCRIPTION = 'Terminate one or more instances'
ARGS = [Arg('InstanceId', metavar='INSTANCE', nargs='+',
- help='instance(s) to terminate')]
+ help='ID(s) of the instance(s) to terminate')]
LIST_TAGS = ['instancesSet']
def print_result(self, result):
diff --git a/euca2ools/commands/euca/unmonitorinstances.py b/euca2ools/commands/euca/unmonitorinstances.py
index bcb3ce0..a3a458e 100644
--- a/euca2ools/commands/euca/unmonitorinstances.py
+++ b/euca2ools/commands/euca/unmonitorinstances.py
@@ -28,13 +28,14 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
+from euca2ools.commands.euca import EucalyptusRequest
from requestbuilder import Arg
-from . import EucalyptusRequest
+
class UnmonitorInstances(EucalyptusRequest):
DESCRIPTION = 'Disable monitoring for one or more instances'
- ARGS = [Arg('InstanceId', metavar='INSTANCE', nargs='+',
- help='instance(s) to un-monitor')]
+ ARGS = [Arg('InstanceId', metavar='INSTANCE', nargs='+', help='''ID(s) ofthe
+ the instance(s) to stop monitoring (at least 1 required)''')]
LIST_TAGS = ['instancesSet']
def print_result(self, result):
--
managing cloud instances for Eucalyptus
More information about the pkg-eucalyptus-commits
mailing list