Skip to content

Commit 64974ba

Browse files
committed
remove need for configuration file #3
add method to attach volume at instance launch #4 Added a configuration object allowing multiple ways to do configuration. A new argument to launch_new_instance allows attaching a volume snapshot at launch.
1 parent d417287 commit 64974ba

File tree

4 files changed

+180
-68
lines changed

4 files changed

+180
-68
lines changed

simple-aws.conf.TEMPLATE

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
[simple-aws]
22
# AWS access key identifier
3-
aws_access_key_id =
3+
aws_access_key =
44
# AWS secret access key
5-
aws_secret_access_key =
5+
aws_secret_key =
66
# default image identifier to launch
7-
image_id =
7+
aws_image_id = ami-6ac2a85a
88
# default instance type to launch
9-
instance_type =
9+
aws_instance_type = t1.micro
1010
# default region to connect to
11-
region =
11+
aws_region = us-west-2
1212
# default security group to use
13-
security_group =
13+
aws_security_group = sosecure
1414
# name of key file to use for instance filtering
15-
key_name =
15+
aws_key_name = sosecure-key
16+
# path to the key file
17+
aws_key_path = ~/.ssh/sosecure-key.pem

src/saws/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
from manager import AwsManager
1+
from manager import AwsManager, AwsManagerConfiguration

src/saws/manager.py

Lines changed: 91 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -6,29 +6,31 @@
66
from boto import ses
77
import boto.ec2
88
from boto.ec2.address import Address
9+
from boto.ec2.blockdevicemapping import BlockDeviceMapping, BlockDeviceType
910
from exc import InstanceNameNotAvailable
1011

1112

13+
SIMPLEAWS_SECTION_TITLE = 'simple-aws'
14+
15+
1216
class AwsManager(object):
1317
"""
14-
:param str conf_path: Path to the configuration file. See *simple-aws.conf.TEMPLATE* for structure.
18+
:param str conf_path: See :class:`saws.manager.AwsManagerConfiguration`.
1519
:param bool filtered: If ``True``, only consider instances attached to ``key_name`` in the configuration file.
1620
:param bool only_running: If ``True``, only consider running instances.
17-
:param str section_title: The title of the section in the configuration file to read values from.
21+
:param str section_title: See :class:`saws.manager.AwsManagerConfiguration`.
22+
:param conf: Configuration object used in place of configuration file.
23+
:type conf: :class:`saws.manager.AwsManagerConfiguration`
1824
"""
1925

20-
def __init__(self, conf_path=None, filtered=True, only_running=True, section_title='simple-aws'):
21-
if conf_path is None:
22-
msg = 'Path to configuration file required.'
23-
raise ValueError(msg)
24-
25-
self.conf_path = conf_path or os.path.expanduser(os.getenv('SIMPLEAWS_CONF_PATH'))
26+
def __init__(self, conf_path=None, filtered=True, only_running=True, section_title=SIMPLEAWS_SECTION_TITLE,
27+
conf=None):
28+
if conf is None:
29+
conf = AwsManagerConfiguration(conf_path=conf_path, section_title=section_title)
30+
self.conf = conf
2631
self.filtered = filtered
2732
self.only_running = only_running
2833
self._conn = None
29-
self._parser = SafeConfigParser()
30-
self._parser.read(self.conf_path)
31-
self._cfg = dict(self._parser.items(section_title))
3234

3335
@property
3436
def conn(self):
@@ -50,7 +52,7 @@ def _get_key_(instance):
5052
raise NotImplementedError(key)
5153
return ret
5254

53-
reservations = self.conn.get_all_reservations()
55+
reservations = self.conn.get_all_instances()
5456
instances = [i for r in reservations for i in r.instances]
5557
instances = {i.id: i for i in instances}
5658
if self.only_running:
@@ -69,18 +71,28 @@ def get_instance_by_name(self, name):
6971
else:
7072
return ret
7173

72-
def launch_new_instance(self, name, image_id=None, instance_type=None, placement=None, elastic_ip=None, wait=True):
73-
image_id = image_id or self._cfg['image_id']
74-
instance_type = instance_type or self._cfg['instance_type']
75-
security_group = self._cfg['security_group']
76-
key_name = self._cfg['key_name']
74+
def launch_new_instance(self, name, image_id=None, instance_type=None, placement=None, elastic_ip=None, wait=True,
75+
ebs_snapshot_id=None, ebs_mount_name='/dev/xvdg'):
76+
image_id = image_id or self.conf.aws_image_id
77+
instance_type = instance_type or self.conf.aws_instance_type
78+
security_group = self.conf.aws_security_group
79+
key_name = self.conf.aws_key_name
7780

7881
if not self._get_is_unique_tag_(name, 'Name'):
7982
msg = 'The assigned instance name "{0}" is not unique.'.format(name)
8083
raise ValueError(msg)
8184

85+
block_device_map = None
86+
if ebs_snapshot_id is not None:
87+
block_device_map = BlockDeviceMapping()
88+
block_dev_type = BlockDeviceType()
89+
block_dev_type.delete_on_termination = True
90+
block_dev_type.snapshot_id = ebs_snapshot_id
91+
block_device_map[ebs_mount_name] = block_dev_type
92+
8293
reservation = self.conn.run_instances(image_id, key_name=key_name, instance_type=instance_type,
83-
security_groups=[security_group], placement=placement)
94+
security_groups=[security_group], placement=placement,
95+
block_device_map=block_device_map)
8496
instance = reservation.instances[0]
8597

8698
if wait:
@@ -101,10 +113,10 @@ def send_email(self, fromaddr, recipient, subject, body):
101113
msg['From'] = fromaddr
102114
msg['To'] = recipient
103115

104-
aws_access_key_id = self._cfg['aws_access_key_id']
105-
aws_secret_access_key = self._cfg['aws_secret_access_key']
106-
conn = ses.connect_to_region(self._cfg['region'], aws_access_key_id=aws_access_key_id,
107-
aws_secret_access_key=aws_secret_access_key)
116+
aws_access_key = self.conf.aws_access_key
117+
aws_secret_key = self.conf.aws_secret_key
118+
conn = ses.connect_to_region(self.conf.aws_region, aws_access_key_id=aws_access_key,
119+
aws_secret_access_key=aws_secret_key)
108120
conn.send_raw_email(msg.as_string())
109121

110122
def start_instance_by_name(self, name, wait=True):
@@ -123,31 +135,25 @@ def start_instance_by_name(self, name, wait=True):
123135
self.only_running = prev_only_running
124136

125137
@staticmethod
126-
def wait_for_status(target, status_target, sleep=1, pad=None):
138+
def wait_for_status(instance, status_target, sleep=1, pad=3):
127139
time.sleep(sleep)
128-
while target.update() != status_target:
140+
while instance.update() != status_target:
129141
time.sleep(sleep)
130142
if pad is not None:
131143
time.sleep(pad)
132144

133145
def _filter_(self, instance):
134-
if instance.key_name == self._cfg['key_name']:
146+
if instance.key_name == self.conf.aws_key_name:
135147
ret = True
136148
else:
137149
ret = False
138150
return ret
139151

140152
def _get_aws_connection_(self):
141-
region = self._cfg['region']
142-
143-
aws_access_key_id = self._cfg['aws_access_key_id']
144-
aws_secret_access_key = self._cfg['aws_secret_access_key']
145-
146-
conn = boto.ec2.connect_to_region(region, aws_access_key_id=aws_access_key_id, aws_secret_access_key=aws_secret_access_key)
147-
153+
conn = boto.ec2.connect_to_region(self.conf.aws_region, aws_access_key_id=self.conf.aws_access_key,
154+
aws_secret_access_key=self.conf.aws_secret_key)
148155
if conn is None:
149156
raise RuntimeError('Unable to launch instance.')
150-
151157
return conn
152158

153159
def _get_is_unique_tag_(self, tag_value, tag_key):
@@ -157,3 +163,55 @@ def _get_is_unique_tag_(self, tag_value, tag_key):
157163
if i.tags.get(tag_key) == tag_value:
158164
ret = False
159165
return ret
166+
167+
168+
class AwsManagerConfiguration(object):
169+
"""
170+
:class:`saws.AwsManager` configuration object. Any of the keywords arguments found in
171+
:attr:`saws.AwsManagerConfiguration._kwargs` may be set in the following ways (in order of precedence):
172+
* Passed as keyword arguments to the constructor.
173+
* Set as uppercase environment variables.
174+
* Set in the configuration file.
175+
176+
:keyword str conf_path: (``=None``) Path to the configuration file. The environment variable
177+
``'SIMPLEAWS_CONF_PATH'`` may also be used.
178+
:keyword str section_title: (``='simple-aws'``) Name of the section in the configuration file to look for values.
179+
:keyword kwargs: Any keyword from :attr:`saws.AwsManagerConfiguration._kwargs`.
180+
:raises: ValueError
181+
"""
182+
183+
_kwargs = {'aws_access_key': False, 'aws_secret_key': False, 'aws_image_id': True, 'aws_instance_type': True,
184+
'aws_region': False, 'aws_security_group': True, 'aws_key_name': True, 'aws_key_path': False}
185+
186+
def __init__(self, **kwargs):
187+
self.conf_path = kwargs.pop('conf_path', None)
188+
if self.conf_path is None:
189+
self.conf_path = os.environ.get('SIMPLEAWS_CONF_PATH')
190+
self.section_title = kwargs.pop('section_title', SIMPLEAWS_SECTION_TITLE)
191+
192+
cfg_kwargs = {}
193+
for kwarg in self._kwargs.iterkeys():
194+
value = kwargs.get(kwarg)
195+
if value is None:
196+
value = os.environ.get(kwarg)
197+
if value is None:
198+
value = os.environ.get(kwarg.upper())
199+
cfg_kwargs[kwarg] = value
200+
201+
cfg_conf = {}
202+
if self.conf_path is not None:
203+
self.conf_path = os.path.expanduser(self.conf_path)
204+
parser = SafeConfigParser()
205+
parser.read(self.conf_path)
206+
cfg_conf = dict(parser.items(self.section_title))
207+
208+
for k, v in cfg_conf.iteritems():
209+
if cfg_kwargs.get(k) is None:
210+
cfg_kwargs[k] = v
211+
212+
for k, v in cfg_kwargs.iteritems():
213+
if v is None and not self._kwargs[k]:
214+
msg = 'The configuration key "{0}" may not be None.'.format(k)
215+
raise ValueError(msg)
216+
else:
217+
setattr(self, k, v)

0 commit comments

Comments
 (0)