1# Copyright (c) 2006-2010 Mitch Garnaat http://garnaat.org/
2# Copyright (c) 2010, Eucalyptus Systems, Inc.
3#
4# Permission is hereby granted, free of charge, to any person obtaining a
5# copy of this software and associated documentation files (the
6# "Software"), to deal in the Software without restriction, including
7# without limitation the rights to use, copy, modify, merge, publish, dis-
8# tribute, sublicense, and/or sell copies of the Software, and to permit
9# persons to whom the Software is furnished to do so, subject to the fol-
10# lowing conditions:
11#
12# The above copyright notice and this permission notice shall be included
13# in all copies or substantial portions of the Software.
14#
15# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
17# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
18# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
19# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21# IN THE SOFTWARE.
22
23from boto.ec2.ec2object import EC2Object, TaggedEC2Object
24from boto.ec2.blockdevicemapping import BlockDeviceMapping
25
26
27class ProductCodes(list):
28    def startElement(self, name, attrs, connection):
29        pass
30
31    def endElement(self, name, value, connection):
32        if name == 'productCode':
33            self.append(value)
34
35
36class BillingProducts(list):
37    def startElement(self, name, attrs, connection):
38        pass
39
40    def endElement(self, name, value, connection):
41        if name == 'billingProduct':
42            self.append(value)
43
44
45class Image(TaggedEC2Object):
46    """
47    Represents an EC2 Image
48    """
49
50    def __init__(self, connection=None):
51        super(Image, self).__init__(connection)
52        self.id = None
53        self.location = None
54        self.state = None
55        self.ownerId = None  # for backwards compatibility
56        self.owner_id = None
57        self.owner_alias = None
58        self.is_public = False
59        self.architecture = None
60        self.platform = None
61        self.type = None
62        self.kernel_id = None
63        self.ramdisk_id = None
64        self.name = None
65        self.description = None
66        self.product_codes = ProductCodes()
67        self.billing_products = BillingProducts()
68        self.block_device_mapping = None
69        self.root_device_type = None
70        self.root_device_name = None
71        self.virtualization_type = None
72        self.hypervisor = None
73        self.instance_lifecycle = None
74        self.sriov_net_support = None
75
76    def __repr__(self):
77        return 'Image:%s' % self.id
78
79    def startElement(self, name, attrs, connection):
80        retval = super(Image, self).startElement(name, attrs, connection)
81        if retval is not None:
82            return retval
83        if name == 'blockDeviceMapping':
84            self.block_device_mapping = BlockDeviceMapping()
85            return self.block_device_mapping
86        elif name == 'productCodes':
87            return self.product_codes
88        elif name == 'billingProducts':
89            return self.billing_products
90        else:
91            return None
92
93    def endElement(self, name, value, connection):
94        if name == 'imageId':
95            self.id = value
96        elif name == 'imageLocation':
97            self.location = value
98        elif name == 'imageState':
99            self.state = value
100        elif name == 'imageOwnerId':
101            self.ownerId = value # for backwards compatibility
102            self.owner_id = value
103        elif name == 'isPublic':
104            if value == 'false':
105                self.is_public = False
106            elif value == 'true':
107                self.is_public = True
108            else:
109                raise Exception(
110                    'Unexpected value of isPublic %s for image %s' % (
111                        value,
112                        self.id
113                    )
114                )
115        elif name == 'architecture':
116            self.architecture = value
117        elif name == 'imageType':
118            self.type = value
119        elif name == 'kernelId':
120            self.kernel_id = value
121        elif name == 'ramdiskId':
122            self.ramdisk_id = value
123        elif name == 'imageOwnerAlias':
124            self.owner_alias = value
125        elif name == 'platform':
126            self.platform = value
127        elif name == 'name':
128            self.name = value
129        elif name == 'description':
130            self.description = value
131        elif name == 'rootDeviceType':
132            self.root_device_type = value
133        elif name == 'rootDeviceName':
134            self.root_device_name = value
135        elif name == 'virtualizationType':
136            self.virtualization_type = value
137        elif name == 'hypervisor':
138            self.hypervisor = value
139        elif name == 'instanceLifecycle':
140            self.instance_lifecycle = value
141        elif name == 'sriovNetSupport':
142            self.sriov_net_support = value
143        else:
144            setattr(self, name, value)
145
146    def _update(self, updated):
147        self.__dict__.update(updated.__dict__)
148
149    def update(self, validate=False, dry_run=False):
150        """
151        Update the image's state information by making a call to fetch
152        the current image attributes from the service.
153
154        :type validate: bool
155        :param validate: By default, if EC2 returns no data about the
156                         image the update method returns quietly.  If
157                         the validate param is True, however, it will
158                         raise a ValueError exception if no data is
159                         returned from EC2.
160        """
161        rs = self.connection.get_all_images([self.id], dry_run=dry_run)
162        if len(rs) > 0:
163            img = rs[0]
164            if img.id == self.id:
165                self._update(img)
166        elif validate:
167            raise ValueError('%s is not a valid Image ID' % self.id)
168        return self.state
169
170    def run(self, min_count=1, max_count=1, key_name=None,
171            security_groups=None, user_data=None,
172            addressing_type=None, instance_type='m1.small', placement=None,
173            kernel_id=None, ramdisk_id=None,
174            monitoring_enabled=False, subnet_id=None,
175            block_device_map=None,
176            disable_api_termination=False,
177            instance_initiated_shutdown_behavior=None,
178            private_ip_address=None,
179            placement_group=None, security_group_ids=None,
180            additional_info=None, instance_profile_name=None,
181            instance_profile_arn=None, tenancy=None, dry_run=False):
182
183        """
184        Runs this instance.
185
186        :type min_count: int
187        :param min_count: The minimum number of instances to start
188
189        :type max_count: int
190        :param max_count: The maximum number of instances to start
191
192        :type key_name: string
193        :param key_name: The name of the key pair with which to
194            launch instances.
195
196        :type security_groups: list of strings
197        :param security_groups: The names of the security groups with which to
198            associate instances.
199
200        :type user_data: string
201        :param user_data: The Base64-encoded MIME user data to be made
202            available to the instance(s) in this reservation.
203
204        :type instance_type: string
205        :param instance_type: The type of instance to run:
206
207            * t1.micro
208            * m1.small
209            * m1.medium
210            * m1.large
211            * m1.xlarge
212            * m3.medium
213            * m3.large
214            * m3.xlarge
215            * m3.2xlarge
216            * c1.medium
217            * c1.xlarge
218            * m2.xlarge
219            * m2.2xlarge
220            * m2.4xlarge
221            * cr1.8xlarge
222            * hi1.4xlarge
223            * hs1.8xlarge
224            * cc1.4xlarge
225            * cg1.4xlarge
226            * cc2.8xlarge
227            * g2.2xlarge
228            * c3.large
229            * c3.xlarge
230            * c3.2xlarge
231            * c3.4xlarge
232            * c3.8xlarge
233            * i2.xlarge
234            * i2.2xlarge
235            * i2.4xlarge
236            * i2.8xlarge
237            * t2.micro
238            * t2.small
239            * t2.medium
240
241        :type placement: string
242        :param placement: The Availability Zone to launch the instance into.
243
244        :type kernel_id: string
245        :param kernel_id: The ID of the kernel with which to launch the
246            instances.
247
248        :type ramdisk_id: string
249        :param ramdisk_id: The ID of the RAM disk with which to launch the
250            instances.
251
252        :type monitoring_enabled: bool
253        :param monitoring_enabled: Enable CloudWatch monitoring on
254            the instance.
255
256         :type subnet_id: string
257        :param subnet_id: The subnet ID within which to launch the instances
258            for VPC.
259
260        :type private_ip_address: string
261        :param private_ip_address: If you're using VPC, you can
262            optionally use this parameter to assign the instance a
263            specific available IP address from the subnet (e.g.,
264            10.0.0.25).
265
266        :type block_device_map: :class:`boto.ec2.blockdevicemapping.BlockDeviceMapping`
267        :param block_device_map: A BlockDeviceMapping data structure
268            describing the EBS volumes associated with the Image.
269
270        :type disable_api_termination: bool
271        :param disable_api_termination: If True, the instances will be locked
272            and will not be able to be terminated via the API.
273
274        :type instance_initiated_shutdown_behavior: string
275        :param instance_initiated_shutdown_behavior: Specifies whether the
276            instance stops or terminates on instance-initiated shutdown.
277            Valid values are:
278
279            * stop
280            * terminate
281
282        :type placement_group: string
283        :param placement_group: If specified, this is the name of the placement
284            group in which the instance(s) will be launched.
285
286        :type additional_info: string
287        :param additional_info: Specifies additional information to make
288            available to the instance(s).
289
290        :type security_group_ids: list of strings
291        :param security_group_ids: The ID of the VPC security groups with
292            which to associate instances.
293
294        :type instance_profile_name: string
295        :param instance_profile_name: The name of
296            the IAM Instance Profile (IIP) to associate with the instances.
297
298        :type instance_profile_arn: string
299        :param instance_profile_arn: The Amazon resource name (ARN) of
300            the IAM Instance Profile (IIP) to associate with the instances.
301
302        :type tenancy: string
303        :param tenancy: The tenancy of the instance you want to
304            launch. An instance with a tenancy of 'dedicated' runs on
305            single-tenant hardware and can only be launched into a
306            VPC. Valid values are:"default" or "dedicated".
307            NOTE: To use dedicated tenancy you MUST specify a VPC
308            subnet-ID as well.
309
310        :rtype: Reservation
311        :return: The :class:`boto.ec2.instance.Reservation` associated with
312                 the request for machines
313
314        """
315
316        return self.connection.run_instances(self.id, min_count, max_count,
317                                             key_name, security_groups,
318                                             user_data, addressing_type,
319                                             instance_type, placement,
320                                             kernel_id, ramdisk_id,
321                                             monitoring_enabled, subnet_id,
322                                             block_device_map, disable_api_termination,
323                                             instance_initiated_shutdown_behavior,
324                                             private_ip_address, placement_group,
325                                             security_group_ids=security_group_ids,
326                                             additional_info=additional_info,
327                                             instance_profile_name=instance_profile_name,
328                                             instance_profile_arn=instance_profile_arn,
329                                             tenancy=tenancy, dry_run=dry_run)
330
331    def deregister(self, delete_snapshot=False, dry_run=False):
332        return self.connection.deregister_image(
333            self.id,
334            delete_snapshot,
335            dry_run=dry_run
336        )
337
338    def get_launch_permissions(self, dry_run=False):
339        img_attrs = self.connection.get_image_attribute(
340            self.id,
341            'launchPermission',
342            dry_run=dry_run
343        )
344        return img_attrs.attrs
345
346    def set_launch_permissions(self, user_ids=None, group_names=None,
347                               dry_run=False):
348        return self.connection.modify_image_attribute(self.id,
349                                                      'launchPermission',
350                                                      'add',
351                                                      user_ids,
352                                                      group_names,
353                                                      dry_run=dry_run)
354
355    def remove_launch_permissions(self, user_ids=None, group_names=None,
356                                  dry_run=False):
357        return self.connection.modify_image_attribute(self.id,
358                                                      'launchPermission',
359                                                      'remove',
360                                                      user_ids,
361                                                      group_names,
362                                                      dry_run=dry_run)
363
364    def reset_launch_attributes(self, dry_run=False):
365        return self.connection.reset_image_attribute(
366            self.id,
367            'launchPermission',
368            dry_run=dry_run
369        )
370
371    def get_kernel(self, dry_run=False):
372        img_attrs = self.connection.get_image_attribute(
373            self.id,
374            'kernel',
375            dry_run=dry_run
376        )
377        return img_attrs.kernel
378
379    def get_ramdisk(self, dry_run=False):
380        img_attrs = self.connection.get_image_attribute(
381            self.id,
382            'ramdisk',
383            dry_run=dry_run
384        )
385        return img_attrs.ramdisk
386
387
388class ImageAttribute(object):
389    def __init__(self, parent=None):
390        self.name = None
391        self.kernel = None
392        self.ramdisk = None
393        self.attrs = {}
394
395    def startElement(self, name, attrs, connection):
396        if name == 'blockDeviceMapping':
397            self.attrs['block_device_mapping'] = BlockDeviceMapping()
398            return self.attrs['block_device_mapping']
399        else:
400            return None
401
402    def endElement(self, name, value, connection):
403        if name == 'launchPermission':
404            self.name = 'launch_permission'
405        elif name == 'group':
406            if 'groups' in self.attrs:
407                self.attrs['groups'].append(value)
408            else:
409                self.attrs['groups'] = [value]
410        elif name == 'userId':
411            if 'user_ids' in self.attrs:
412                self.attrs['user_ids'].append(value)
413            else:
414                self.attrs['user_ids'] = [value]
415        elif name == 'productCode':
416            if 'product_codes' in self.attrs:
417                self.attrs['product_codes'].append(value)
418            else:
419                self.attrs['product_codes'] = [value]
420        elif name == 'imageId':
421            self.image_id = value
422        elif name == 'kernel':
423            self.kernel = value
424        elif name == 'ramdisk':
425            self.ramdisk = value
426        else:
427            setattr(self, name, value)
428
429
430class CopyImage(object):
431    def __init__(self, parent=None):
432        self._parent = parent
433        self.image_id = None
434
435    def startElement(self, name, attrs, connection):
436        pass
437
438    def endElement(self, name, value, connection):
439        if name == 'imageId':
440            self.image_id = value
441