Skip to content

Instantly share code, notes, and snippets.

@hemebond
Last active March 24, 2019 20:31
Show Gist options
  • Save hemebond/57cb7b18815f452cee8c7285b47bf18f to your computer and use it in GitHub Desktop.
Save hemebond/57cb7b18815f452cee8c7285b47bf18f to your computer and use it in GitHub Desktop.
A passive AWS EC2 wrapper for the salt-cloud profile function
#!/usr/bin/env python
# 2019-03-22 Updated to be compatible with Salt 2018.3.3
# Import python libs
import logging
import os.path
import time
import boto3
import salt.config
import salt.wheel
import salt.cloud
import salt.runners.cloud
import salt.utils.aws as aws
from salt.exceptions import SaltInvocationError
# Get logging started
log = logging.getLogger(__name__)
def autosign(name, output=True):
'''
Create a file in minions_autosign to pre-approve a minion
'''
ret = {}
autosign_key = os.path.join(__opts__['pki_dir'], 'minions_autosign', name)
open(autosign_key, 'a').close()
ret['key'] = autosign_key
return ret
def profile(profile_name, minion_id, **kwargs):
'''
Pass is a profile to create a new EC2 instance with the name provided.
profile_name is the name of the Salt Cloud profile to use in creating the EC2 instance.
minion_id is the name of the instance
'''
# __opts__ = salt.config.client_config('/etc/salt/master')
wheel_client = salt.wheel.WheelClient(__opts__)
cloud_client = salt.cloud.CloudClient(
os.path.join(os.path.dirname(__opts__['conf_file']), 'cloud')
)
if not profile_name in cloud_client.opts['profiles']:
raise SaltInvocationError('Profile %s not found' % profile_name)
profile_details = cloud_client.opts['profiles'][profile_name]
log.debug('profile_details: %s' % profile_details)
alias, driver = profile_details['provider'].split(':')
provider_details = cloud_client.opts['providers'][alias][driver].copy()
vm_ = salt.cloud.Cloud.vm_config(minion_id, cloud_client.opts, provider_details, profile_details, {})
# This is a passive provisioner so we don't want salt-cloud trying
# to SSH onto the instance and installing salt-minion
if salt.config.get_cloud_config_value('deploy', vm_, cloud_client.opts, default=True):
raise SaltCloudConfigError('Deploy can not be true')
# Generate the minion key and upload to S3
aws_access_id = salt.config.get_cloud_config_value('id', vm_, cloud_client.opts)
aws_secret_key = salt.config.get_cloud_config_value('key', vm_, cloud_client.opts)
if not (aws_access_id and aws_secret_key):
raise SaltInvocationError('Need S3 credentials')
s3_bucket = salt.config.get_cloud_config_value('s3_bucket', vm_, cloud_client.opts)
s3_path = salt.config.get_cloud_config_value('s3_path', vm_, cloud_client.opts)
if not (s3_bucket and s3_path):
raise SaltInvocationError('No S3 bucket and path provided.')
# Generate a key for the new minion
keys = wheel_client.cmd('key.gen_accept', [minion_id])
if keys == {}:
# If the key has already been accepted `keys` will be an empty dict
print('Minion key for %s already exists. No key will be created' % minion_id)
else:
# Upload the new minion keys to S3
s3 = boto3.client('s3', aws_access_key_id=aws_access_id,
aws_secret_access_key=aws_secret_key)
s3.put_object(Key='%s/%s.pem' % (s3_path, minion_id), Bucket=s3_bucket, Body=keys['priv'])
s3.put_object(Key='%s/%s.pub' % (s3_path, minion_id), Bucket=s3_bucket, Body=keys['pub'])
info = cloud_client.profile(profile_name, [minion_id,])
log.debug("New instance info: %s", info)
eip_id = salt.config.get_cloud_config_value('associate_eip', vm_, cloud_client.opts, default=False)
if eip_id:
# Associate an Elastic IP with the instance
# Can take a while for the instance to be available
# so we have to try multiple times
attempts = 5
while attempts >= 0:
# Just a little delay between attempts...
time.sleep(10)
result = aws.query(
{
'Action': 'AssociateAddress',
'InstanceId': info[minion_id]['instanceId'],
'AllocationId': eip_id
},
return_root=True,
location=salt.config.get_cloud_config_value('location', vm_, cloud_client.opts, default='us-east-1'),
provider=(salt.config.get_cloud_config_value('provider', vm_, cloud_client.opts)).split(':')[0],
opts=cloud_client.opts,
sigver='4'
)
if 'error' in result:
log.warning('Failed to associate EIP. Remaining attempts %s', attempts)
attempts -= 1
continue
else:
break
ret = {
'instance': info,
'keys': {
'priv': 's3://%s/%s/%s.pem' % (s3_bucket, s3_path, minion_id),
'pub': 's3://%s/%s/%s.pub' % (s3_bucket, s3_path, minion_id),
}
}
return ret
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment