golden hour
/opt/saltstack/salt/lib/python3.10/site-packages/salt/states
⬆️ Go Up
Upload
File/Folder
Size
Actions
__init__.py
25 B
Del
OK
__pycache__
-
Del
OK
acme.py
5.08 KB
Del
OK
alias.py
2.49 KB
Del
OK
alternatives.py
6.75 KB
Del
OK
ansiblegate.py
7.93 KB
Del
OK
apache.py
3.95 KB
Del
OK
apache_conf.py
2.72 KB
Del
OK
apache_module.py
2.73 KB
Del
OK
apache_site.py
2.66 KB
Del
OK
aptpkg.py
1.42 KB
Del
OK
archive.py
68.24 KB
Del
OK
artifactory.py
6.84 KB
Del
OK
at.py
7.48 KB
Del
OK
augeas.py
10.57 KB
Del
OK
aws_sqs.py
2.59 KB
Del
OK
azurearm_compute.py
11.78 KB
Del
OK
azurearm_dns.py
26.05 KB
Del
OK
azurearm_network.py
89.12 KB
Del
OK
azurearm_resource.py
28.23 KB
Del
OK
beacon.py
7.58 KB
Del
OK
bigip.py
96.63 KB
Del
OK
blockdev.py
5.13 KB
Del
OK
boto3_elasticache.py
48.01 KB
Del
OK
boto3_elasticsearch.py
32.58 KB
Del
OK
boto3_route53.py
37.54 KB
Del
OK
boto3_sns.py
12.69 KB
Del
OK
boto_apigateway.py
82.83 KB
Del
OK
boto_asg.py
31.93 KB
Del
OK
boto_cfn.py
11.53 KB
Del
OK
boto_cloudfront.py
6.01 KB
Del
OK
boto_cloudtrail.py
13.18 KB
Del
OK
boto_cloudwatch_alarm.py
6.4 KB
Del
OK
boto_cloudwatch_event.py
12.33 KB
Del
OK
boto_cognitoidentity.py
13.69 KB
Del
OK
boto_datapipeline.py
18.5 KB
Del
OK
boto_dynamodb.py
29.32 KB
Del
OK
boto_ec2.py
71.98 KB
Del
OK
boto_elasticache.py
16.75 KB
Del
OK
boto_elasticsearch_domain.py
12.27 KB
Del
OK
boto_elb.py
55.1 KB
Del
OK
boto_elbv2.py
12.19 KB
Del
OK
boto_iam.py
69.16 KB
Del
OK
boto_iam_role.py
27.12 KB
Del
OK
boto_iot.py
25.33 KB
Del
OK
boto_kinesis.py
16.69 KB
Del
OK
boto_kms.py
12.11 KB
Del
OK
boto_lambda.py
35.52 KB
Del
OK
boto_lc.py
11.04 KB
Del
OK
boto_rds.py
26 KB
Del
OK
boto_route53.py
19.49 KB
Del
OK
boto_s3.py
9.32 KB
Del
OK
boto_s3_bucket.py
24.67 KB
Del
OK
boto_secgroup.py
32.62 KB
Del
OK
boto_sns.py
8.92 KB
Del
OK
boto_sqs.py
7.97 KB
Del
OK
boto_vpc.py
62.23 KB
Del
OK
bower.py
8.26 KB
Del
OK
btrfs.py
10.34 KB
Del
OK
cabal.py
5.73 KB
Del
OK
ceph.py
1.9 KB
Del
OK
chef.py
3.76 KB
Del
OK
chocolatey.py
16.15 KB
Del
OK
chronos_job.py
4.6 KB
Del
OK
cimc.py
14.32 KB
Del
OK
cisconso.py
3.14 KB
Del
OK
cloud.py
14.4 KB
Del
OK
cmd.py
40.92 KB
Del
OK
composer.py
8.38 KB
Del
OK
consul.py
5.4 KB
Del
OK
cron.py
23.39 KB
Del
OK
cryptdev.py
6.17 KB
Del
OK
csf.py
9.98 KB
Del
OK
cyg.py
7.05 KB
Del
OK
ddns.py
4.2 KB
Del
OK
debconfmod.py
6.33 KB
Del
OK
dellchassis.py
24.49 KB
Del
OK
disk.py
6.49 KB
Del
OK
docker_container.py
85.27 KB
Del
OK
docker_image.py
16.7 KB
Del
OK
docker_network.py
36.78 KB
Del
OK
docker_volume.py
6.72 KB
Del
OK
drac.py
4.17 KB
Del
OK
dvs.py
26.29 KB
Del
OK
elasticsearch.py
20.38 KB
Del
OK
elasticsearch_index.py
3.25 KB
Del
OK
elasticsearch_index_template.py
3.67 KB
Del
OK
environ.py
5.81 KB
Del
OK
eselect.py
2.27 KB
Del
OK
esxcluster.py
22.4 KB
Del
OK
esxdatacenter.py
4.44 KB
Del
OK
esxi.py
63.07 KB
Del
OK
esxvm.py
20.11 KB
Del
OK
etcd_mod.py
11 KB
Del
OK
ethtool.py
9.88 KB
Del
OK
event.py
2.48 KB
Del
OK
file.py
316.7 KB
Del
OK
firewall.py
1.33 KB
Del
OK
firewalld.py
26.08 KB
Del
OK
gem.py
7.13 KB
Del
OK
git.py
123.85 KB
Del
OK
github.py
27.25 KB
Del
OK
glance_image.py
2.26 KB
Del
OK
glassfish.py
21.47 KB
Del
OK
glusterfs.py
12.21 KB
Del
OK
gnomedesktop.py
7.47 KB
Del
OK
gpg.py
5.28 KB
Del
OK
grafana.py
12.11 KB
Del
OK
grafana4_dashboard.py
17.31 KB
Del
OK
grafana4_datasource.py
6.15 KB
Del
OK
grafana4_org.py
7.73 KB
Del
OK
grafana4_user.py
5.52 KB
Del
OK
grafana_dashboard.py
17.74 KB
Del
OK
grafana_datasource.py
5.31 KB
Del
OK
grains.py
15.57 KB
Del
OK
group.py
9.84 KB
Del
OK
heat.py
9.69 KB
Del
OK
helm.py
10.39 KB
Del
OK
hg.py
6.33 KB
Del
OK
highstate_doc.py
1.41 KB
Del
OK
host.py
8.64 KB
Del
OK
http.py
7.46 KB
Del
OK
icinga2.py
9.07 KB
Del
OK
idem.py
3.91 KB
Del
OK
ifttt.py
2.12 KB
Del
OK
incron.py
5.71 KB
Del
OK
influxdb08_database.py
2.85 KB
Del
OK
influxdb08_user.py
3.39 KB
Del
OK
influxdb_continuous_query.py
2.83 KB
Del
OK
influxdb_database.py
2.11 KB
Del
OK
influxdb_retention_policy.py
4.82 KB
Del
OK
influxdb_user.py
4.84 KB
Del
OK
infoblox_a.py
4.24 KB
Del
OK
infoblox_cname.py
4.19 KB
Del
OK
infoblox_host_record.py
6.59 KB
Del
OK
infoblox_range.py
6.85 KB
Del
OK
ini_manage.py
12.67 KB
Del
OK
ipmi.py
8.42 KB
Del
OK
ipset.py
9.66 KB
Del
OK
iptables.py
27.65 KB
Del
OK
jboss7.py
23.95 KB
Del
OK
jenkins.py
3.36 KB
Del
OK
junos.py
17.78 KB
Del
OK
kapacitor.py
6.46 KB
Del
OK
kernelpkg.py
6.42 KB
Del
OK
keyboard.py
2.01 KB
Del
OK
keystone.py
27.12 KB
Del
OK
keystone_domain.py
2.81 KB
Del
OK
keystone_endpoint.py
4.69 KB
Del
OK
keystone_group.py
3.25 KB
Del
OK
keystone_project.py
3.36 KB
Del
OK
keystone_role.py
2.33 KB
Del
OK
keystone_role_grant.py
4.08 KB
Del
OK
keystone_service.py
2.89 KB
Del
OK
keystone_user.py
3.47 KB
Del
OK
keystore.py
5.67 KB
Del
OK
kmod.py
8.59 KB
Del
OK
kubernetes.py
24.87 KB
Del
OK
layman.py
2.44 KB
Del
OK
ldap.py
19.78 KB
Del
OK
libcloud_dns.py
5.7 KB
Del
OK
libcloud_loadbalancer.py
5.66 KB
Del
OK
libcloud_storage.py
5.13 KB
Del
OK
linux_acl.py
24.42 KB
Del
OK
locale.py
2.52 KB
Del
OK
logadm.py
4.67 KB
Del
OK
logrotate.py
3.86 KB
Del
OK
loop.py
7.74 KB
Del
OK
lvm.py
13.33 KB
Del
OK
lvs_server.py
6.28 KB
Del
OK
lvs_service.py
4.38 KB
Del
OK
lxc.py
22.17 KB
Del
OK
lxd.py
7.88 KB
Del
OK
lxd_container.py
22.25 KB
Del
OK
lxd_image.py
10.59 KB
Del
OK
lxd_profile.py
7.11 KB
Del
OK
mac_assistive.py
1.55 KB
Del
OK
mac_keychain.py
5.59 KB
Del
OK
mac_xattr.py
3.15 KB
Del
OK
macdefaults.py
2.65 KB
Del
OK
macpackage.py
6.76 KB
Del
OK
makeconf.py
6.87 KB
Del
OK
marathon_app.py
4.45 KB
Del
OK
mdadm_raid.py
6.41 KB
Del
OK
memcached.py
3.95 KB
Del
OK
modjk.py
2.84 KB
Del
OK
modjk_worker.py
6.49 KB
Del
OK
module.py
18.64 KB
Del
OK
mongodb_database.py
1.65 KB
Del
OK
mongodb_user.py
6.26 KB
Del
OK
monit.py
2.68 KB
Del
OK
mount.py
50.32 KB
Del
OK
mssql_database.py
3 KB
Del
OK
mssql_login.py
3.64 KB
Del
OK
mssql_role.py
2.37 KB
Del
OK
mssql_user.py
3.51 KB
Del
OK
msteams.py
2.53 KB
Del
OK
mysql_database.py
6.05 KB
Del
OK
mysql_grants.py
8.49 KB
Del
OK
mysql_query.py
13.07 KB
Del
OK
mysql_user.py
9.51 KB
Del
OK
net_napalm_yang.py
9.15 KB
Del
OK
netacl.py
31.92 KB
Del
OK
netconfig.py
33.42 KB
Del
OK
netntp.py
12.51 KB
Del
OK
netsnmp.py
11.33 KB
Del
OK
netusers.py
16.1 KB
Del
OK
network.py
23.97 KB
Del
OK
neutron_network.py
3.96 KB
Del
OK
neutron_secgroup.py
4 KB
Del
OK
neutron_secgroup_rule.py
4.75 KB
Del
OK
neutron_subnet.py
4.29 KB
Del
OK
nexus.py
4.97 KB
Del
OK
nfs_export.py
4.92 KB
Del
OK
nftables.py
19.5 KB
Del
OK
npm.py
11.21 KB
Del
OK
ntp.py
2.12 KB
Del
OK
nxos.py
10.37 KB
Del
OK
nxos_upgrade.py
3.5 KB
Del
OK
openstack_config.py
3.26 KB
Del
OK
openvswitch_bridge.py
4.36 KB
Del
OK
openvswitch_db.py
2.24 KB
Del
OK
openvswitch_port.py
17.24 KB
Del
OK
opsgenie.py
4.07 KB
Del
OK
pagerduty.py
1.89 KB
Del
OK
pagerduty_escalation_policy.py
5.42 KB
Del
OK
pagerduty_schedule.py
6.09 KB
Del
OK
pagerduty_service.py
3.93 KB
Del
OK
pagerduty_user.py
1.18 KB
Del
OK
panos.py
48.13 KB
Del
OK
pbm.py
20.46 KB
Del
OK
pcs.py
36.46 KB
Del
OK
pdbedit.py
3.43 KB
Del
OK
pecl.py
3.65 KB
Del
OK
pip_state.py
38.55 KB
Del
OK
pkg.py
138.08 KB
Del
OK
pkgbuild.py
11.37 KB
Del
OK
pkgng.py
685 B
Del
OK
pkgrepo.py
27.53 KB
Del
OK
portage_config.py
5.01 KB
Del
OK
ports.py
5.65 KB
Del
OK
postgres_cluster.py
4.19 KB
Del
OK
postgres_database.py
6.08 KB
Del
OK
postgres_extension.py
5.68 KB
Del
OK
postgres_group.py
8.52 KB
Del
OK
postgres_initdb.py
2.84 KB
Del
OK
postgres_language.py
3.94 KB
Del
OK
postgres_privileges.py
7.86 KB
Del
OK
postgres_schema.py
4.34 KB
Del
OK
postgres_tablespace.py
6.62 KB
Del
OK
postgres_user.py
9.49 KB
Del
OK
powerpath.py
2.34 KB
Del
OK
probes.py
15.06 KB
Del
OK
process.py
1.32 KB
Del
OK
proxy.py
4.94 KB
Del
OK
pushover.py
3.13 KB
Del
OK
pyenv.py
6.07 KB
Del
OK
pyrax_queues.py
2.97 KB
Del
OK
quota.py
1.4 KB
Del
OK
rabbitmq_cluster.py
1.84 KB
Del
OK
rabbitmq_plugin.py
2.77 KB
Del
OK
rabbitmq_policy.py
4.59 KB
Del
OK
rabbitmq_upstream.py
7.9 KB
Del
OK
rabbitmq_user.py
8.89 KB
Del
OK
rabbitmq_vhost.py
3.04 KB
Del
OK
rbac_solaris.py
6.67 KB
Del
OK
rbenv.py
7.36 KB
Del
OK
rdp.py
1.28 KB
Del
OK
redismod.py
4.76 KB
Del
OK
reg.py
19.22 KB
Del
OK
restconf.py
6.41 KB
Del
OK
rsync.py
4.45 KB
Del
OK
rvm.py
6.56 KB
Del
OK
salt_proxy.py
1.34 KB
Del
OK
saltmod.py
33.12 KB
Del
OK
saltutil.py
8.91 KB
Del
OK
schedule.py
12.47 KB
Del
OK
selinux.py
18.61 KB
Del
OK
serverdensity_device.py
6.41 KB
Del
OK
service.py
37.89 KB
Del
OK
slack.py
4.98 KB
Del
OK
smartos.py
44.83 KB
Del
OK
smtp.py
2.3 KB
Del
OK
snapper.py
7.24 KB
Del
OK
solrcloud.py
4.48 KB
Del
OK
splunk.py
4.32 KB
Del
OK
splunk_search.py
3.17 KB
Del
OK
sqlite3.py
14.7 KB
Del
OK
ssh_auth.py
19.57 KB
Del
OK
ssh_known_hosts.py
7.92 KB
Del
OK
stateconf.py
494 B
Del
OK
status.py
2.21 KB
Del
OK
statuspage.py
17.29 KB
Del
OK
supervisord.py
10.48 KB
Del
OK
svn.py
8.14 KB
Del
OK
sysctl.py
4.11 KB
Del
OK
sysfs.py
2.13 KB
Del
OK
syslog_ng.py
2.97 KB
Del
OK
sysrc.py
2.82 KB
Del
OK
telemetry_alert.py
7.04 KB
Del
OK
test.py
13.09 KB
Del
OK
testinframod.py
1.35 KB
Del
OK
timezone.py
3.42 KB
Del
OK
tls.py
1.81 KB
Del
OK
tomcat.py
9.72 KB
Del
OK
trafficserver.py
8.82 KB
Del
OK
tuned.py
3.32 KB
Del
OK
uptime.py
1.87 KB
Del
OK
user.py
38.63 KB
Del
OK
vagrant.py
11.4 KB
Del
OK
vault.py
3.28 KB
Del
OK
vbox_guest.py
4.05 KB
Del
OK
victorops.py
3.32 KB
Del
OK
virt.py
80.41 KB
Del
OK
virtualenv_mod.py
11.21 KB
Del
OK
webutil.py
3.89 KB
Del
OK
win_certutil.py
4.8 KB
Del
OK
win_dacl.py
7.96 KB
Del
OK
win_dism.py
14.97 KB
Del
OK
win_dns_client.py
8.32 KB
Del
OK
win_firewall.py
6.87 KB
Del
OK
win_iis.py
31.56 KB
Del
OK
win_lgpo.py
24.99 KB
Del
OK
win_lgpo_reg.py
10.96 KB
Del
OK
win_license.py
1.6 KB
Del
OK
win_network.py
14.18 KB
Del
OK
win_path.py
6.39 KB
Del
OK
win_pki.py
5.56 KB
Del
OK
win_powercfg.py
3.79 KB
Del
OK
win_servermanager.py
10.4 KB
Del
OK
win_shortcut.py
7.81 KB
Del
OK
win_smtp_server.py
10.01 KB
Del
OK
win_snmp.py
6.64 KB
Del
OK
win_system.py
13.78 KB
Del
OK
win_wua.py
16.27 KB
Del
OK
win_wusa.py
3.53 KB
Del
OK
winrepo.py
2.74 KB
Del
OK
wordpress.py
4.82 KB
Del
OK
x509.py
27.86 KB
Del
OK
x509_v2.py
64.78 KB
Del
OK
xml.py
1.75 KB
Del
OK
xmpp.py
2.61 KB
Del
OK
zabbix_action.py
9.35 KB
Del
OK
zabbix_host.py
27.25 KB
Del
OK
zabbix_hostgroup.py
5.64 KB
Del
OK
zabbix_mediatype.py
16.89 KB
Del
OK
zabbix_template.py
35.14 KB
Del
OK
zabbix_user.py
17.6 KB
Del
OK
zabbix_usergroup.py
9.64 KB
Del
OK
zabbix_usermacro.py
9.69 KB
Del
OK
zabbix_valuemap.py
8.11 KB
Del
OK
zcbuildout.py
5.16 KB
Del
OK
zenoss.py
2.89 KB
Del
OK
zfs.py
34.48 KB
Del
OK
zk_concurrency.py
5.81 KB
Del
OK
zone.py
46.48 KB
Del
OK
zookeeper.py
11.55 KB
Del
OK
zpool.py
13.4 KB
Del
OK
Edit: boto_ec2.py
""" Manage EC2 .. versionadded:: 2015.8.0 This module provides an interface to the Elastic Compute Cloud (EC2) service from AWS. The below code creates a key pair: .. code-block:: yaml create-key-pair: boto_ec2.key_present: - name: mykeypair - save_private: /root/ - region: eu-west-1 - keyid: GKTADJGHEIQSXMKKRBJ08H - key: askdjghsdfjkghWupUjasdflkdfklgjsdfjajkghs .. code-block:: yaml import-key-pair: boto_ec2.key_present: - name: mykeypair - upload_public: 'ssh-rsa AAAA' - keyid: GKTADJGHEIQSXMKKRBJ08H - key: askdjghsdfjkghWupUjasdflkdfklgjsdfjajkghs You can also use salt:// in order to define the public key. .. code-block:: yaml import-key-pair: boto_ec2.key_present: - name: mykeypair - upload_public: salt://mybase/public_key.pub - keyid: GKTADJGHEIQSXMKKRBJ08H - key: askdjghsdfjkghWupUjasdflkdfklgjsdfjajkghs The below code deletes a key pair: .. code-block:: yaml delete-key-pair: boto_ec2.key_absent: - name: mykeypair - region: eu-west-1 - keyid: GKTADJGHEIQSXMKKRBJ08H - key: askdjghsdfjkghWupUjasdflkdfklgjsdfjajkghs """ import logging from time import sleep, time import salt.utils.data import salt.utils.dictupdate as dictupdate from salt.exceptions import CommandExecutionError, SaltInvocationError log = logging.getLogger(__name__) def __virtual__(): """ Only load if boto is available. """ if "boto_ec2.get_key" in __salt__: return "boto_ec2" return (False, "boto_ec2 module could not be loaded") def key_present( name, save_private=None, upload_public=None, region=None, key=None, keyid=None, profile=None, ): """ Ensure key pair is present. """ ret = {"name": name, "result": True, "comment": "", "changes": {}} exists = __salt__["boto_ec2.get_key"](name, region, key, keyid, profile) log.debug("exists is %s", exists) if upload_public is not None and "salt://" in upload_public: try: upload_public = __salt__["cp.get_file_str"](upload_public) except OSError as e: log.debug(e) ret["comment"] = "File {} not found.".format(upload_public) ret["result"] = False return ret if not exists: if __opts__["test"]: ret["comment"] = "The key {} is set to be created.".format(name) ret["result"] = None return ret if save_private and not upload_public: created = __salt__["boto_ec2.create_key"]( name, save_private, region, key, keyid, profile ) if created: ret["result"] = True ret["comment"] = "The key {} is created.".format(name) ret["changes"]["new"] = created else: ret["result"] = False ret["comment"] = "Could not create key {} ".format(name) elif not save_private and upload_public: imported = __salt__["boto_ec2.import_key"]( name, upload_public, region, key, keyid, profile ) if imported: ret["result"] = True ret["comment"] = "The key {} is created.".format(name) ret["changes"]["old"] = None ret["changes"]["new"] = imported else: ret["result"] = False ret["comment"] = "Could not create key {} ".format(name) else: ret["result"] = False ret["comment"] = "You can either upload or download a private key " else: ret["result"] = True ret["comment"] = "The key name {} already exists".format(name) return ret def key_absent(name, region=None, key=None, keyid=None, profile=None): """ Deletes a key pair """ ret = {"name": name, "result": True, "comment": "", "changes": {}} exists = __salt__["boto_ec2.get_key"](name, region, key, keyid, profile) if exists: if __opts__["test"]: ret["comment"] = "The key {} is set to be deleted.".format(name) ret["result"] = None return ret deleted = __salt__["boto_ec2.delete_key"](name, region, key, keyid, profile) log.debug("exists is %s", deleted) if deleted: ret["result"] = True ret["comment"] = "The key {} is deleted.".format(name) ret["changes"]["old"] = name else: ret["result"] = False ret["comment"] = "Could not delete key {} ".format(name) else: ret["result"] = True ret["comment"] = "The key name {} does not exist".format(name) return ret def eni_present( name, subnet_id=None, subnet_name=None, private_ip_address=None, description=None, groups=None, source_dest_check=True, allocate_eip=None, arecords=None, region=None, key=None, keyid=None, profile=None, ): """ Ensure the EC2 ENI exists. .. versionadded:: 2016.3.0 name Name tag associated with the ENI. subnet_id The VPC subnet ID the ENI will exist within. subnet_name The VPC subnet name the ENI will exist within. private_ip_address The private ip address to use for this ENI. If this is not specified AWS will automatically assign a private IP address to the ENI. Must be specified at creation time; will be ignored afterward. description Description of the key. groups A list of security groups to apply to the ENI. source_dest_check Boolean specifying whether source/destination checking is enabled on the ENI. allocate_eip allocate and associate an EIP to the ENI. Could be 'standard' to allocate Elastic IP to EC2 region or 'vpc' to get it for a particular VPC .. versionchanged:: 2016.11.0 arecords A list of arecord dicts with attributes needed for the DNS add_record state. By default the boto_route53.add_record state will be used, which requires: name, zone, ttl, and identifier. See the boto_route53 state for information about these attributes. Other DNS modules can be called by specifying the provider keyword. By default, the private ENI IP address will be used, set 'public: True' in the arecord dict to use the ENI's public IP address .. versionadded:: 2016.3.0 region Region to connect to. key Secret key to be used. keyid Access key to be used. profile A dict with region, key and keyid, or a pillar key (string) that contains a dict with region, key and keyid. """ if not salt.utils.data.exactly_one((subnet_id, subnet_name)): raise SaltInvocationError( "One (but not both) of subnet_id or subnet_name must be provided." ) if not groups: raise SaltInvocationError("groups is a required argument.") if not isinstance(groups, list): raise SaltInvocationError("groups must be a list.") if not isinstance(source_dest_check, bool): raise SaltInvocationError("source_dest_check must be a bool.") ret = {"name": name, "result": True, "comment": "", "changes": {}} r = __salt__["boto_ec2.get_network_interface"]( name=name, region=region, key=key, keyid=keyid, profile=profile ) if "error" in r: ret["result"] = False ret["comment"] = "Error when attempting to find eni: {}.".format( r["error"]["message"] ) return ret if not r["result"]: if __opts__["test"]: ret["comment"] = "ENI is set to be created." if allocate_eip: ret["comment"] = " ".join( [ ret["comment"], "An EIP is set to be allocated/assocaited to the ENI.", ] ) if arecords: ret["comment"] = " ".join( [ret["comment"], "A records are set to be created."] ) ret["result"] = None return ret result_create = __salt__["boto_ec2.create_network_interface"]( name, subnet_id=subnet_id, subnet_name=subnet_name, private_ip_address=private_ip_address, description=description, groups=groups, region=region, key=key, keyid=keyid, profile=profile, ) if "error" in result_create: ret["result"] = False ret["comment"] = "Failed to create ENI: {}".format( result_create["error"]["message"] ) return ret r["result"] = result_create["result"] ret["comment"] = "Created ENI {}".format(name) ret["changes"]["id"] = r["result"]["id"] else: _ret = _eni_attribute( r["result"], "description", description, region, key, keyid, profile ) ret["changes"] = dictupdate.update(ret["changes"], _ret["changes"]) ret["comment"] = _ret["comment"] if not _ret["result"]: ret["result"] = _ret["result"] if ret["result"] is False: return ret _ret = _eni_groups(r["result"], groups, region, key, keyid, profile) ret["changes"] = dictupdate.update(ret["changes"], _ret["changes"]) ret["comment"] = " ".join([ret["comment"], _ret["comment"]]) if not _ret["result"]: ret["result"] = _ret["result"] if ret["result"] is False: return ret # Actions that need to occur whether creating or updating _ret = _eni_attribute( r["result"], "source_dest_check", source_dest_check, region, key, keyid, profile ) ret["changes"] = dictupdate.update(ret["changes"], _ret["changes"]) ret["comment"] = " ".join([ret["comment"], _ret["comment"]]) if not _ret["result"]: ret["result"] = _ret["result"] return ret if allocate_eip: if "allocationId" not in r["result"]: if __opts__["test"]: ret["comment"] = " ".join( [ ret["comment"], "An EIP is set to be allocated and assocaited to the ENI.", ] ) else: domain = "vpc" if allocate_eip == "vpc" else None eip_alloc = __salt__["boto_ec2.allocate_eip_address"]( domain=domain, region=region, key=key, keyid=keyid, profile=profile ) if eip_alloc: _ret = __salt__["boto_ec2.associate_eip_address"]( instance_id=None, instance_name=None, public_ip=None, allocation_id=eip_alloc["allocation_id"], network_interface_id=r["result"]["id"], private_ip_address=None, allow_reassociation=False, region=region, key=key, keyid=keyid, profile=profile, ) if not _ret: _ret = __salt__["boto_ec2.release_eip_address"]( public_ip=None, allocation_id=eip_alloc["allocation_id"], region=region, key=key, keyid=keyid, profile=profile, ) ret["result"] = False msg = ( "Failed to assocaite the allocated EIP address with the" " ENI. The EIP {}".format( "was successfully released." if _ret else "was NOT RELEASED." ) ) ret["comment"] = " ".join([ret["comment"], msg]) return ret else: ret["result"] = False ret["comment"] = " ".join( [ret["comment"], "Failed to allocate an EIP address"] ) return ret else: ret["comment"] = " ".join( [ret["comment"], "An EIP is already allocated/assocaited to the ENI"] ) if arecords: for arecord in arecords: if "name" not in arecord: msg = 'The arecord must contain a "name" property.' raise SaltInvocationError(msg) log.debug("processing arecord %s", arecord) _ret = None dns_provider = "boto_route53" arecord["record_type"] = "A" public_ip_arecord = False if "public" in arecord: public_ip_arecord = arecord.pop("public") if public_ip_arecord: if "publicIp" in r["result"]: arecord["value"] = r["result"]["publicIp"] elif "public_ip" in eip_alloc: arecord["value"] = eip_alloc["public_ip"] else: msg = ( "Unable to add an A record for the public IP address, a public" " IP address does not seem to be allocated to this ENI." ) raise CommandExecutionError(msg) else: arecord["value"] = r["result"]["private_ip_address"] if "provider" in arecord: dns_provider = arecord.pop("provider") if dns_provider == "boto_route53": if "profile" not in arecord: arecord["profile"] = profile if "key" not in arecord: arecord["key"] = key if "keyid" not in arecord: arecord["keyid"] = keyid if "region" not in arecord: arecord["region"] = region _ret = __states__[".".join([dns_provider, "present"])](**arecord) log.debug("ret from dns_provider.present = %s", _ret) ret["changes"] = dictupdate.update(ret["changes"], _ret["changes"]) ret["comment"] = " ".join([ret["comment"], _ret["comment"]]) if not _ret["result"]: ret["result"] = _ret["result"] if ret["result"] is False: return ret return ret def _eni_attribute(metadata, attr, value, region, key, keyid, profile): ret = {"result": True, "comment": "", "changes": {}} if metadata[attr] == value: return ret if __opts__["test"]: ret["comment"] = "ENI set to have {} updated.".format(attr) ret["result"] = None return ret result_update = __salt__["boto_ec2.modify_network_interface_attribute"]( network_interface_id=metadata["id"], attr=attr, value=value, region=region, key=key, keyid=keyid, profile=profile, ) if "error" in result_update: msg = "Failed to update ENI {0}: {1}." ret["result"] = False ret["comment"] = msg.format(attr, result_update["error"]["message"]) else: ret["comment"] = "Updated ENI {}.".format(attr) ret["changes"][attr] = {"old": metadata[attr], "new": value} return ret def _eni_groups(metadata, groups, region, key, keyid, profile): ret = {"result": True, "comment": "", "changes": {}} group_ids = [g["id"] for g in metadata["groups"]] group_ids.sort() _groups = __salt__["boto_secgroup.convert_to_group_ids"]( groups, vpc_id=metadata["vpc_id"], region=region, key=key, keyid=keyid, profile=profile, ) if not _groups: ret["comment"] = "Could not find secgroup ids for provided groups." ret["result"] = False _groups.sort() if group_ids == _groups: return ret if __opts__["test"]: ret["comment"] = "ENI set to have groups updated." ret["result"] = None return ret result_update = __salt__["boto_ec2.modify_network_interface_attribute"]( network_interface_id=metadata["id"], attr="groups", value=_groups, region=region, key=key, keyid=keyid, profile=profile, ) if "error" in result_update: msg = "Failed to update ENI groups: {1}." ret["result"] = False ret["comment"] = msg.format(result_update["error"]["message"]) else: ret["comment"] = "Updated ENI groups." ret["changes"]["groups"] = {"old": group_ids, "new": _groups} return ret def eni_absent( name, release_eip=False, region=None, key=None, keyid=None, profile=None ): """ Ensure the EC2 ENI is absent. .. versionadded:: 2016.3.0 name Name tag associated with the ENI. release_eip True/False - release any EIP associated with the ENI region Region to connect to. key Secret key to be used. keyid Access key to be used. profile A dict with region, key and keyid, or a pillar key (string) that contains a dict with region, key and keyid. """ ret = {"name": name, "result": True, "comment": "", "changes": {}} r = __salt__["boto_ec2.get_network_interface"]( name=name, region=region, key=key, keyid=keyid, profile=profile ) if "error" in r: ret["result"] = False ret["comment"] = "Error when attempting to find eni: {}.".format( r["error"]["message"] ) return ret if not r["result"]: if __opts__["test"]: ret["comment"] = "ENI is set to be deleted." ret["result"] = None return ret else: if __opts__["test"]: ret["comment"] = "ENI is set to be deleted." if release_eip and "allocationId" in r["result"]: ret["comment"] = " ".join( [ret["comment"], "Allocated/associated EIP is set to be released"] ) ret["result"] = None return ret if "id" in r["result"]["attachment"]: result_detach = __salt__["boto_ec2.detach_network_interface"]( name=name, force=True, region=region, key=key, keyid=keyid, profile=profile, ) if "error" in result_detach: ret["result"] = False ret["comment"] = "Failed to detach ENI: {}".format( result_detach["error"]["message"] ) return ret # TODO: Ensure the detach occurs before continuing result_delete = __salt__["boto_ec2.delete_network_interface"]( name=name, region=region, key=key, keyid=keyid, profile=profile ) if "error" in result_delete: ret["result"] = False ret["comment"] = "Failed to delete ENI: {}".format( result_delete["error"]["message"] ) return ret ret["comment"] = "Deleted ENI {}".format(name) ret["changes"]["id"] = None if release_eip and "allocationId" in r["result"]: _ret = __salt__["boto_ec2.release_eip_address"]( public_ip=None, allocation_id=r["result"]["allocationId"], region=region, key=key, keyid=keyid, profile=profile, ) if not _ret: ret["comment"] = " ".join( [ret["comment"], "Failed to release EIP allocated to the ENI."] ) ret["result"] = False return ret else: ret["comment"] = " ".join([ret["comment"], "EIP released."]) ret["changes"]["eip released"] = True return ret def snapshot_created( name, ami_name, instance_name, wait_until_available=True, wait_timeout_seconds=300, **kwargs ): """ Create a snapshot from the given instance .. versionadded:: 2016.3.0 """ ret = {"name": name, "result": True, "comment": "", "changes": {}} if not __salt__["boto_ec2.create_image"]( ami_name=ami_name, instance_name=instance_name, **kwargs ): ret["comment"] = "Failed to create new AMI {ami_name}".format(ami_name=ami_name) ret["result"] = False return ret ret["comment"] = "Created new AMI {ami_name}".format(ami_name=ami_name) ret["changes"]["new"] = {ami_name: ami_name} if not wait_until_available: return ret starttime = time() while True: images = __salt__["boto_ec2.find_images"]( ami_name=ami_name, return_objs=True, **kwargs ) if images and images[0].state == "available": break if time() - starttime > wait_timeout_seconds: if images: ret["comment"] = "AMI still in state {state} after timeout".format( state=images[0].state ) else: ret[ "comment" ] = "AMI with name {ami_name} not found after timeout.".format( ami_name=ami_name ) ret["result"] = False return ret sleep(5) return ret def instance_present( name, instance_name=None, instance_id=None, image_id=None, image_name=None, tags=None, key_name=None, security_groups=None, user_data=None, instance_type=None, placement=None, kernel_id=None, ramdisk_id=None, vpc_id=None, vpc_name=None, monitoring_enabled=None, subnet_id=None, subnet_name=None, private_ip_address=None, block_device_map=None, disable_api_termination=None, instance_initiated_shutdown_behavior=None, placement_group=None, client_token=None, security_group_ids=None, security_group_names=None, additional_info=None, tenancy=None, instance_profile_arn=None, instance_profile_name=None, ebs_optimized=None, network_interfaces=None, network_interface_name=None, network_interface_id=None, attributes=None, target_state=None, public_ip=None, allocation_id=None, allocate_eip=False, region=None, key=None, keyid=None, profile=None, ): ### TODO - implement 'target_state={running, stopped}' """ Ensure an EC2 instance is running with the given attributes and state. name (string) - The name of the state definition. Recommended that this match the instance_name attribute (generally the FQDN of the instance). instance_name (string) - The name of the instance, generally its FQDN. Exclusive with 'instance_id'. instance_id (string) - The ID of the instance (if known). Exclusive with 'instance_name'. image_id (string) – The ID of the AMI image to run. image_name (string) – The name of the AMI image to run. tags (dict) - Tags to apply to the instance. key_name (string) – The name of the key pair with which to launch instances. security_groups (list of strings) – The names of the EC2 classic security groups with which to associate instances user_data (string) – The Base64-encoded MIME user data to be made available to the instance(s) in this reservation. instance_type (string) – The EC2 instance size/type. Note that only certain types are compatible with HVM based AMIs. placement (string) – The Availability Zone to launch the instance into. kernel_id (string) – The ID of the kernel with which to launch the instances. ramdisk_id (string) – The ID of the RAM disk with which to launch the instances. vpc_id (string) - The ID of a VPC to attach the instance to. vpc_name (string) - The name of a VPC to attach the instance to. monitoring_enabled (bool) – Enable detailed CloudWatch monitoring on the instance. subnet_id (string) – The ID of the subnet within which to launch the instances for VPC. subnet_name (string) – The name of the subnet within which to launch the instances for VPC. private_ip_address (string) – If you’re using VPC, you can optionally use this parameter to assign the instance a specific available IP address from the subnet (e.g., 10.0.0.25). block_device_map (boto.ec2.blockdevicemapping.BlockDeviceMapping) – A BlockDeviceMapping data structure describing the EBS volumes associated with the Image. disable_api_termination (bool) – If True, the instances will be locked and will not be able to be terminated via the API. instance_initiated_shutdown_behavior (string) – Specifies whether the instance stops or terminates on instance-initiated shutdown. Valid values are: - 'stop' - 'terminate' placement_group (string) – If specified, this is the name of the placement group in which the instance(s) will be launched. client_token (string) – Unique, case-sensitive identifier you provide to ensure idempotency of the request. Maximum 64 ASCII characters. security_group_ids (list of strings) – The IDs of the VPC security groups with which to associate instances. security_group_names (list of strings) – The names of the VPC security groups with which to associate instances. additional_info (string) – Specifies additional information to make available to the instance(s). tenancy (string) – The tenancy of the instance you want to launch. An instance with a tenancy of ‘dedicated’ runs on single-tenant hardware and can only be launched into a VPC. Valid values are:”default” or “dedicated”. NOTE: To use dedicated tenancy you MUST specify a VPC subnet-ID as well. instance_profile_arn (string) – The Amazon resource name (ARN) of the IAM Instance Profile (IIP) to associate with the instances. instance_profile_name (string) – The name of the IAM Instance Profile (IIP) to associate with the instances. ebs_optimized (bool) – Whether the instance is optimized for EBS I/O. This optimization provides dedicated throughput to Amazon EBS and a tuned configuration stack to provide optimal EBS I/O performance. This optimization isn’t available with all instance types. network_interfaces (boto.ec2.networkinterface.NetworkInterfaceCollection) – A NetworkInterfaceCollection data structure containing the ENI specifications for the instance. network_interface_name (string) - The name of Elastic Network Interface to attach .. versionadded:: 2016.11.0 network_interface_id (string) - The id of Elastic Network Interface to attach .. versionadded:: 2016.11.0 attributes (dict) - Instance attributes and value to be applied to the instance. Available options are: - instanceType - A valid instance type (m1.small) - kernel - Kernel ID (None) - ramdisk - Ramdisk ID (None) - userData - Base64 encoded String (None) - disableApiTermination - Boolean (true) - instanceInitiatedShutdownBehavior - stop|terminate - blockDeviceMapping - List of strings - ie: [‘/dev/sda=false’] - sourceDestCheck - Boolean (true) - groupSet - Set of Security Groups or IDs - ebsOptimized - Boolean (false) - sriovNetSupport - String - ie: ‘simple’ target_state (string) - The desired target state of the instance. Available options are: - running - stopped Note that this option is currently UNIMPLEMENTED. public_ip: (string) - The IP of a previously allocated EIP address, which will be attached to the instance. EC2 Classic instances ONLY - for VCP pass in an allocation_id instead. allocation_id: (string) - The ID of a previously allocated EIP address, which will be attached to the instance. VPC instances ONLY - for Classic pass in a public_ip instead. allocate_eip: (bool) - Allocate and attach an EIP on-the-fly for this instance. Note you'll want to release this address when terminating the instance, either manually or via the 'release_eip' flag to 'instance_absent'. region (string) - Region to connect to. key (string) - Secret key to be used. keyid (string) - Access key to be used. profile (variable) - A dict with region, key and keyid, or a pillar key (string) that contains a dict with region, key and keyid. .. versionadded:: 2016.3.0 """ ret = {"name": name, "result": True, "comment": "", "changes": {}} _create = False running_states = ("pending", "rebooting", "running", "stopping", "stopped") changed_attrs = {} if not salt.utils.data.exactly_one((image_id, image_name)): raise SaltInvocationError( "Exactly one of image_id OR image_name must be provided." ) if (public_ip or allocation_id or allocate_eip) and not salt.utils.data.exactly_one( (public_ip, allocation_id, allocate_eip) ): raise SaltInvocationError( "At most one of public_ip, allocation_id OR allocate_eip may be provided." ) if instance_id: exists = __salt__["boto_ec2.exists"]( instance_id=instance_id, region=region, key=key, keyid=keyid, profile=profile, in_states=running_states, ) if not exists: _create = True else: instances = __salt__["boto_ec2.find_instances"]( name=instance_name if instance_name else name, region=region, key=key, keyid=keyid, profile=profile, in_states=running_states, ) if not instances: _create = True elif len(instances) > 1: log.debug( "Multiple instances matching criteria found - cannot determine a" " singular instance-id" ) instance_id = None # No way to know, we'll just have to bail later.... else: instance_id = instances[0] if _create: if __opts__["test"]: ret["comment"] = "The instance {} is set to be created.".format(name) ret["result"] = None return ret if image_name: args = { "ami_name": image_name, "region": region, "key": key, "keyid": keyid, "profile": profile, } image_ids = __salt__["boto_ec2.find_images"](**args) if image_ids: image_id = image_ids[0] else: image_id = image_name r = __salt__["boto_ec2.run"]( image_id, instance_name if instance_name else name, tags=tags, key_name=key_name, security_groups=security_groups, user_data=user_data, instance_type=instance_type, placement=placement, kernel_id=kernel_id, ramdisk_id=ramdisk_id, vpc_id=vpc_id, vpc_name=vpc_name, monitoring_enabled=monitoring_enabled, subnet_id=subnet_id, subnet_name=subnet_name, private_ip_address=private_ip_address, block_device_map=block_device_map, disable_api_termination=disable_api_termination, instance_initiated_shutdown_behavior=instance_initiated_shutdown_behavior, placement_group=placement_group, client_token=client_token, security_group_ids=security_group_ids, security_group_names=security_group_names, additional_info=additional_info, tenancy=tenancy, instance_profile_arn=instance_profile_arn, instance_profile_name=instance_profile_name, ebs_optimized=ebs_optimized, network_interfaces=network_interfaces, network_interface_name=network_interface_name, network_interface_id=network_interface_id, region=region, key=key, keyid=keyid, profile=profile, ) if not r or "instance_id" not in r: ret["result"] = False ret["comment"] = "Failed to create instance {}.".format( instance_name if instance_name else name ) return ret instance_id = r["instance_id"] ret["changes"] = {"old": {}, "new": {}} ret["changes"]["old"]["instance_id"] = None ret["changes"]["new"]["instance_id"] = instance_id # To avoid issues we only allocate new EIPs at instance creation. # This might miss situations where an instance is initially created # created without and one is added later, but the alternative is the # risk of EIPs allocated at every state run. if allocate_eip: if __opts__["test"]: ret["comment"] = "New EIP would be allocated." ret["result"] = None return ret domain = "vpc" if vpc_id or vpc_name else None r = __salt__["boto_ec2.allocate_eip_address"]( domain=domain, region=region, key=key, keyid=keyid, profile=profile ) if not r: ret["result"] = False ret["comment"] = "Failed to allocate new EIP." return ret allocation_id = r["allocation_id"] log.info("New EIP with address %s allocated.", r["public_ip"]) else: log.info("EIP not requested.") if public_ip or allocation_id: # This can take a bit to show up, give it a chance to... tries = 10 secs = 3 for t in range(tries): r = __salt__["boto_ec2.get_eip_address_info"]( addresses=public_ip, allocation_ids=allocation_id, region=region, key=key, keyid=keyid, profile=profile, ) if r: break else: log.info( "Waiting up to %s secs for new EIP %s to become available", tries * secs, public_ip or allocation_id, ) time.sleep(secs) if not r: ret["result"] = False ret["comment"] = "Failed to lookup EIP {}.".format( public_ip or allocation_id ) return ret ip = r[0]["public_ip"] if r[0].get("instance_id"): if r[0]["instance_id"] != instance_id: ret["result"] = False ret[ "comment" ] = "EIP {} is already associated with instance {}.".format( public_ip if public_ip else allocation_id, r[0]["instance_id"] ) return ret else: if __opts__["test"]: ret["comment"] = "Instance {} to be updated.".format(name) ret["result"] = None return ret r = __salt__["boto_ec2.associate_eip_address"]( instance_id=instance_id, public_ip=public_ip, allocation_id=allocation_id, region=region, key=key, keyid=keyid, profile=profile, ) if r: if "new" not in ret["changes"]: ret["changes"]["new"] = {} ret["changes"]["new"]["public_ip"] = ip else: ret["result"] = False ret["comment"] = "Failed to attach EIP to instance {}.".format( instance_name if instance_name else name ) return ret if attributes: for k, v in attributes.items(): curr = __salt__["boto_ec2.get_attribute"]( k, instance_id=instance_id, region=region, key=key, keyid=keyid, profile=profile, ) curr = {} if not isinstance(curr, dict) else curr if curr.get(k) == v: continue else: if __opts__["test"]: changed_attrs[k] = ( "The instance attribute {} is set to be changed from '{}' to" " '{}'.".format(k, curr.get(k), v) ) continue try: r = __salt__["boto_ec2.set_attribute"]( attribute=k, attribute_value=v, instance_id=instance_id, region=region, key=key, keyid=keyid, profile=profile, ) except SaltInvocationError as e: ret["result"] = False ret[ "comment" ] = "Failed to set attribute {} to {} on instance {}.".format( k, v, instance_name ) return ret ret["changes"] = ( ret["changes"] if ret["changes"] else {"old": {}, "new": {}} ) ret["changes"]["old"][k] = curr.get(k) ret["changes"]["new"][k] = v if __opts__["test"]: if changed_attrs: ret["changes"]["new"] = changed_attrs ret["result"] = None else: ret["comment"] = "Instance {} is in the correct state".format( instance_name if instance_name else name ) ret["result"] = True if tags and instance_id is not None: tags = dict(tags) curr_tags = dict( __salt__["boto_ec2.get_all_tags"]( filters={"resource-id": instance_id}, region=region, key=key, keyid=keyid, profile=profile, ).get(instance_id, {}) ) current = set(curr_tags.keys()) desired = set(tags.keys()) remove = list( current - desired ) # Boto explicitly requires a list here and can't cope with a set... add = {t: tags[t] for t in desired - current} replace = {t: tags[t] for t in tags if tags.get(t) != curr_tags.get(t)} # Tag keys are unique despite the bizarre semantics uses which make it LOOK like they could be duplicative. add.update(replace) if add or remove: if __opts__["test"]: ret["changes"]["old"] = ( ret["changes"]["old"] if "old" in ret["changes"] else {} ) ret["changes"]["new"] = ( ret["changes"]["new"] if "new" in ret["changes"] else {} ) ret["changes"]["old"]["tags"] = curr_tags ret["changes"]["new"]["tags"] = tags ret["comment"] += " Tags would be updated on instance {}.".format( instance_name if instance_name else name ) else: if remove: if not __salt__["boto_ec2.delete_tags"]( resource_ids=instance_id, tags=remove, region=region, key=key, keyid=keyid, profile=profile, ): msg = "Error while deleting tags on instance {}".format( instance_name if instance_name else name ) log.error(msg) ret["comment"] += " " + msg ret["result"] = False return ret if add: if not __salt__["boto_ec2.create_tags"]( resource_ids=instance_id, tags=add, region=region, key=key, keyid=keyid, profile=profile, ): msg = "Error while creating tags on instance {}".format( instance_name if instance_name else name ) log.error(msg) ret["comment"] += " " + msg ret["result"] = False return ret ret["changes"]["old"] = ( ret["changes"]["old"] if "old" in ret["changes"] else {} ) ret["changes"]["new"] = ( ret["changes"]["new"] if "new" in ret["changes"] else {} ) ret["changes"]["old"]["tags"] = curr_tags ret["changes"]["new"]["tags"] = tags return ret def instance_absent( name, instance_name=None, instance_id=None, release_eip=False, region=None, key=None, keyid=None, profile=None, filters=None, ): """ Ensure an EC2 instance does not exist (is stopped and removed). .. versionchanged:: 2016.11.0 name (string) - The name of the state definition. instance_name (string) - The name of the instance. instance_id (string) - The ID of the instance. release_eip (bool) - Release any associated EIPs during termination. region (string) - Region to connect to. key (string) - Secret key to be used. keyid (string) - Access key to be used. profile (variable) - A dict with region, key and keyid, or a pillar key (string) that contains a dict with region, key and keyid. filters (dict) - A dict of additional filters to use in matching the instance to delete. YAML example fragment: .. code-block:: yaml - filters: vpc-id: vpc-abcdef12 """ ### TODO - Implement 'force' option?? Would automagically turn off ### 'disableApiTermination', as needed, before trying to delete. ret = {"name": name, "result": True, "comment": "", "changes": {}} running_states = ("pending", "rebooting", "running", "stopping", "stopped") if not instance_id: try: instance_id = __salt__["boto_ec2.get_id"]( name=instance_name if instance_name else name, region=region, key=key, keyid=keyid, profile=profile, in_states=running_states, filters=filters, ) except CommandExecutionError as e: ret["result"] = None ret["comment"] = "Couldn't determine current status of instance {}.".format( instance_name or name ) return ret instances = __salt__["boto_ec2.find_instances"]( instance_id=instance_id, region=region, key=key, keyid=keyid, profile=profile, return_objs=True, filters=filters, ) if not instances: ret["result"] = True ret["comment"] = "Instance {} is already gone.".format(instance_id) return ret instance = instances[0] ### Honor 'disableApiTermination' - if you want to override it, first use set_attribute() to turn it off no_can_do = __salt__["boto_ec2.get_attribute"]( "disableApiTermination", instance_id=instance_id, region=region, key=key, keyid=keyid, profile=profile, ) if no_can_do.get("disableApiTermination") is True: ret["result"] = False ret["comment"] = "Termination of instance {} via the API is disabled.".format( instance_id ) return ret if __opts__["test"]: ret["comment"] = "The instance {} is set to be deleted.".format(name) ret["result"] = None return ret r = __salt__["boto_ec2.terminate"]( instance_id=instance_id, name=instance_name, region=region, key=key, keyid=keyid, profile=profile, ) if not r: ret["result"] = False ret["comment"] = "Failed to terminate instance {}.".format(instance_id) return ret ret["changes"]["old"] = {"instance_id": instance_id} ret["changes"]["new"] = None if release_eip: ip = getattr(instance, "ip_address", None) if ip: base_args = { "region": region, "key": key, "keyid": keyid, "profile": profile, } public_ip = None alloc_id = None assoc_id = None if getattr(instance, "vpc_id", None): r = __salt__["boto_ec2.get_eip_address_info"](addresses=ip, **base_args) if r and "allocation_id" in r[0]: alloc_id = r[0]["allocation_id"] assoc_id = r[0].get("association_id") else: # I /believe/ this situation is impossible but let's hedge our bets... ret["result"] = False ret[ "comment" ] = "Can't determine AllocationId for address {}.".format(ip) return ret else: public_ip = instance.ip_address if assoc_id: # Race here - sometimes the terminate above will already have dropped this if not __salt__["boto_ec2.disassociate_eip_address"]( association_id=assoc_id, **base_args ): log.warning("Failed to disassociate EIP %s.", ip) if __salt__["boto_ec2.release_eip_address"]( allocation_id=alloc_id, public_ip=public_ip, **base_args ): log.info("Released EIP address %s", public_ip or r[0]["public_ip"]) ret["changes"]["old"]["public_ip"] = public_ip or r[0]["public_ip"] else: ret["result"] = False ret["comment"] = "Failed to release EIP {}.".format(ip) return ret return ret def volume_absent( name, volume_name=None, volume_id=None, instance_name=None, instance_id=None, device=None, region=None, key=None, keyid=None, profile=None, ): """ Ensure the EC2 volume is detached and absent. .. versionadded:: 2016.11.0 name State definition name. volume_name Name tag associated with the volume. For safety, if this matches more than one volume, the state will refuse to apply. volume_id Resource ID of the volume. instance_name Only remove volume if it is attached to instance with this Name tag. Exclusive with 'instance_id'. Requires 'device'. instance_id Only remove volume if it is attached to this instance. Exclusive with 'instance_name'. Requires 'device'. device Match by device rather than ID. Requires one of 'instance_name' or 'instance_id'. region Region to connect to. key Secret key to be used. keyid Access key to be used. profile A dict with region, key and keyid, or a pillar key (string) that contains a dict with region, key and keyid. """ ret = {"name": name, "result": True, "comment": "", "changes": {}} filters = {} running_states = ("pending", "rebooting", "running", "stopping", "stopped") if not salt.utils.data.exactly_one( (volume_name, volume_id, instance_name, instance_id) ): raise SaltInvocationError( "Exactly one of 'volume_name', 'volume_id', " "'instance_name', or 'instance_id' must be provided." ) if (instance_name or instance_id) and not device: raise SaltInvocationError( "Parameter 'device' is required when either " "'instance_name' or 'instance_id' is specified." ) if volume_id: filters.update({"volume-id": volume_id}) if volume_name: filters.update({"tag:Name": volume_name}) if instance_name: instance_id = __salt__["boto_ec2.get_id"]( name=instance_name, region=region, key=key, keyid=keyid, profile=profile, in_states=running_states, ) if not instance_id: ret["comment"] = ( "Instance with Name {} not found. Assuming " "associated volumes gone.".format(instance_name) ) return ret if instance_id: filters.update({"attachment.instance-id": instance_id}) if device: filters.update({"attachment.device": device}) args = {"region": region, "key": key, "keyid": keyid, "profile": profile} vols = __salt__["boto_ec2.get_all_volumes"](filters=filters, **args) if len(vols) < 1: ret["comment"] = "Volume matching criteria not found, assuming already absent" return ret if len(vols) > 1: msg = ( "More than one volume matched criteria, can't continue in state {}".format( name ) ) log.error(msg) ret["comment"] = msg ret["result"] = False return ret vol = vols[0] log.info("Matched Volume ID %s", vol) if __opts__["test"]: ret["comment"] = "The volume {} is set to be deleted.".format(vol) ret["result"] = None return ret if __salt__["boto_ec2.delete_volume"](volume_id=vol, force=True, **args): ret["comment"] = "Volume {} deleted.".format(vol) ret["changes"] = {"old": {"volume_id": vol}, "new": {"volume_id": None}} else: ret["comment"] = "Error deleting volume {}.".format(vol) ret["result"] = False return ret def volumes_tagged( name, tag_maps, authoritative=False, region=None, key=None, keyid=None, profile=None ): """ Ensure EC2 volume(s) matching the given filters have the defined tags. .. versionadded:: 2016.11.0 name State definition name. tag_maps List of dicts of filters and tags, where 'filters' is a dict suitable for passing to the 'filters' argument of boto_ec2.get_all_volumes(), and 'tags' is a dict of tags to be set on volumes as matched by the given filters. The filter syntax is extended to permit passing either a list of volume_ids or an instance_name (with instance_name being the Name tag of the instance to which the desired volumes are mapped). Each mapping in the list is applied separately, so multiple sets of volumes can be all tagged differently with one call to this function. YAML example fragment: .. code-block:: yaml - filters: attachment.instance_id: i-abcdef12 tags: Name: dev-int-abcdef12.aws-foo.com - filters: attachment.device: /dev/sdf tags: ManagedSnapshots: true BillingGroup: bubba.hotep@aws-foo.com - filters: instance_name: prd-foo-01.aws-foo.com tags: Name: prd-foo-01.aws-foo.com BillingGroup: infra-team@aws-foo.com - filters: volume_ids: [ vol-12345689, vol-abcdef12 ] tags: BillingGroup: infra-team@aws-foo.com authoritative Should un-declared tags currently set on matched volumes be deleted? Boolean. region Region to connect to. key Secret key to be used. keyid Access key to be used. profile A dict with region, key and keyid, or a pillar key (string) that contains a dict with region, key and keyid. """ ret = {"name": name, "result": True, "comment": "", "changes": {}} args = { "tag_maps": tag_maps, "authoritative": authoritative, "region": region, "key": key, "keyid": keyid, "profile": profile, } if __opts__["test"]: args["dry_run"] = True r = __salt__["boto_ec2.set_volumes_tags"](**args) if r["success"]: if r.get("changes"): ret["comment"] = "Tags would be updated." ret["changes"] = r["changes"] ret["result"] = None else: ret["comment"] = "Error validating requested volume tags." ret["result"] = False return ret r = __salt__["boto_ec2.set_volumes_tags"](**args) if r["success"]: if r.get("changes"): ret["comment"] = "Tags applied." ret["changes"] = r["changes"] else: ret["comment"] = "Error updating requested volume tags." ret["result"] = False return ret def volume_present( name, volume_name=None, volume_id=None, instance_name=None, instance_id=None, device=None, size=None, snapshot_id=None, volume_type=None, iops=None, encrypted=False, kms_key_id=None, region=None, key=None, keyid=None, profile=None, ): """ Ensure the EC2 volume is present and attached. .. name State definition name. volume_name The Name tag value for the volume. If no volume with that matching name tag is found, a new volume will be created. If multiple volumes are matched, the state will fail. volume_id Resource ID of the volume. Exclusive with 'volume_name'. instance_name Attach volume to instance with this Name tag. Exclusive with 'instance_id'. instance_id Attach volume to instance with this ID. Exclusive with 'instance_name'. device The device on the instance through which the volume is exposed (e.g. /dev/sdh) size The size of the new volume, in GiB. If you're creating the volume from a snapshot and don't specify a volume size, the default is the snapshot size. Optionally specified at volume creation time; will be ignored afterward. Requires 'volume_name'. snapshot_id The snapshot ID from which the new Volume will be created. Optionally specified at volume creation time; will be ignored afterward. Requires 'volume_name'. volume_type The type of the volume. Optionally specified at volume creation time; will be ignored afterward. Requires 'volume_name'. Valid volume types for AWS can be found here: http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSVolumeTypes.html iops The provisioned IOPS you want to associate with this volume. Optionally specified at volume creation time; will be ignored afterward. Requires 'volume_name'. encrypted Specifies whether the volume should be encrypted. Optionally specified at volume creation time; will be ignored afterward. Requires 'volume_name'. kms_key_id If encrypted is True, this KMS Key ID may be specified to encrypt volume with this key. Optionally specified at volume creation time; will be ignored afterward. Requires 'volume_name'. e.g.: arn:aws:kms:us-east-1:012345678910:key/abcd1234-a123-456a-a12b-a123b4cd56ef region Region to connect to. key Secret key to be used. keyid Access key to be used. profile A dict with region, key and keyid, or a pillar key (string) that contains a dict with region, key and keyid. """ ret = {"name": name, "result": True, "comment": "", "changes": {}} old_dict = {} new_dict = {} running_states = ("running", "stopped") if not salt.utils.data.exactly_one((volume_name, volume_id)): raise SaltInvocationError( "Exactly one of 'volume_name', 'volume_id', must be provided." ) if not salt.utils.data.exactly_one((instance_name, instance_id)): raise SaltInvocationError( "Exactly one of 'instance_name', or 'instance_id' must be provided." ) if device is None: raise SaltInvocationError("Parameter 'device' is required.") args = {"region": region, "key": key, "keyid": keyid, "profile": profile} if instance_name: instance_id = __salt__["boto_ec2.get_id"]( name=instance_name, in_states=running_states, **args ) if not instance_id: raise SaltInvocationError( "Instance with Name {} not found.".format(instance_name) ) instances = __salt__["boto_ec2.find_instances"]( instance_id=instance_id, return_objs=True, **args ) instance = instances[0] if volume_name: filters = {} filters.update({"tag:Name": volume_name}) vols = __salt__["boto_ec2.get_all_volumes"](filters=filters, **args) if len(vols) > 1: msg = ( "More than one volume matched volume name {}, can't continue in" " state {}".format(volume_name, name) ) raise SaltInvocationError(msg) if len(vols) < 1: if __opts__["test"]: ret["comment"] = ( "The volume with name {} is set to be created and attached" " on {}({}).".format(volume_name, instance_id, device) ) ret["result"] = None return ret _rt = __salt__["boto_ec2.create_volume"]( zone_name=instance.placement, size=size, snapshot_id=snapshot_id, volume_type=volume_type, iops=iops, encrypted=encrypted, kms_key_id=kms_key_id, wait_for_creation=True, **args ) if "result" in _rt: volume_id = _rt["result"] else: raise SaltInvocationError( "Error creating volume with name {}.".format(volume_name) ) _rt = __salt__["boto_ec2.set_volumes_tags"]( tag_maps=[ { "filters": {"volume_ids": [volume_id]}, "tags": {"Name": volume_name}, } ], **args ) if _rt["success"] is False: raise SaltInvocationError( "Error updating requested volume {} with name {}. {}".format( volume_id, volume_name, _rt["comment"] ) ) old_dict["volume_id"] = None new_dict["volume_id"] = volume_id else: volume_id = vols[0] vols = __salt__["boto_ec2.get_all_volumes"]( volume_ids=[volume_id], return_objs=True, **args ) if len(vols) < 1: raise SaltInvocationError("Volume {} do not exist".format(volume_id)) vol = vols[0] if vol.zone != instance.placement: raise SaltInvocationError( "Volume {} in {} cannot attach to instance {} in {}.".format( volume_id, vol.zone, instance_id, instance.placement ) ) attach_data = vol.attach_data if attach_data is not None and attach_data.instance_id is not None: if instance_id == attach_data.instance_id and device == attach_data.device: ret["comment"] = "The volume {} is attached on {}({}).".format( volume_id, instance_id, device ) return ret else: if __opts__["test"]: ret[ "comment" ] = "The volume {} is set to be detached from {}({} and attached on {}({}).".format( attach_data.instance_id, attach_data.devic, volume_id, instance_id, device, ) ret["result"] = None return ret if __salt__["boto_ec2.detach_volume"]( volume_id=volume_id, wait_for_detachement=True, **args ): ret["comment"] = "Volume {} is detached from {}({}).".format( volume_id, attach_data.instance_id, attach_data.device ) old_dict["instance_id"] = attach_data.instance_id old_dict["device"] = attach_data.device else: raise SaltInvocationError( "The volume {} is already attached on instance {}({})." " Failed to detach".format( volume_id, attach_data.instance_id, attach_data.device ) ) else: old_dict["instance_id"] = instance_id old_dict["device"] = None if __opts__["test"]: ret["comment"] = "The volume {} is set to be attached on {}({}).".format( volume_id, instance_id, device ) ret["result"] = None return ret if __salt__["boto_ec2.attach_volume"]( volume_id=volume_id, instance_id=instance_id, device=device, **args ): ret["comment"] = " ".join( [ ret["comment"], "Volume {} is attached on {}({}).".format( volume_id, instance_id, device ), ] ) new_dict["instance_id"] = instance_id new_dict["device"] = device ret["changes"] = {"old": old_dict, "new": new_dict} else: ret["comment"] = "Error attaching volume {} to instance {}({}).".format( volume_id, instance_id, device ) ret["result"] = False return ret def private_ips_present( name, network_interface_name=None, network_interface_id=None, private_ip_addresses=None, allow_reassignment=False, region=None, key=None, keyid=None, profile=None, ): """ Ensure an ENI has secondary private ip addresses associated with it name (String) - State definition name network_interface_id (String) - The EC2 network interface id, example eni-123456789 private_ip_addresses (List or String) - The secondary private ip address(es) that should be present on the ENI. allow_reassignment (Boolean) - If true, will reassign a secondary private ip address associated with another ENI. If false, state will fail if the secondary private ip address is associated with another ENI. region (string) - Region to connect to. key (string) - Secret key to be used. keyid (string) - Access key to be used. profile (variable) - A dict with region, key and keyid, or a pillar key (string) that contains a dict with region, key and keyid. """ if not salt.utils.data.exactly_one((network_interface_name, network_interface_id)): raise SaltInvocationError( "Exactly one of 'network_interface_name', " "'network_interface_id' must be provided" ) if not private_ip_addresses: raise SaltInvocationError( "You must provide the private_ip_addresses to associate with the ENI" ) ret = { "name": name, "result": True, "comment": "", "changes": {"old": [], "new": []}, } get_eni_args = { "name": network_interface_name, "network_interface_id": network_interface_id, "region": region, "key": key, "keyid": keyid, "profile": profile, } eni = __salt__["boto_ec2.get_network_interface"](**get_eni_args) # Check if there are any new secondary private ips to add to the eni if eni and eni.get("result", {}).get("private_ip_addresses"): for eni_pip in eni["result"]["private_ip_addresses"]: ret["changes"]["old"].append(eni_pip["private_ip_address"]) ips_to_add = [] for private_ip in private_ip_addresses: if private_ip not in ret["changes"]["old"]: ips_to_add.append(private_ip) if ips_to_add: if not __opts__["test"]: # Assign secondary private ips to ENI assign_ips_args = { "network_interface_id": network_interface_id, "private_ip_addresses": ips_to_add, "allow_reassignment": allow_reassignment, "region": region, "key": key, "keyid": keyid, "profile": profile, } __salt__["boto_ec2.assign_private_ip_addresses"](**assign_ips_args) # Verify secondary private ips were properly assigned to ENI eni = __salt__["boto_ec2.get_network_interface"](**get_eni_args) if eni and eni.get("result", {}).get("private_ip_addresses", None): for eni_pip in eni["result"]["private_ip_addresses"]: ret["changes"]["new"].append(eni_pip["private_ip_address"]) ips_not_added = [] for private_ip in private_ip_addresses: if private_ip not in ret["changes"]["new"]: ips_not_added.append(private_ip) # Display results if ips_not_added: ret["result"] = False ret["comment"] = ( "ips on eni: {}\n" "attempted to add: {}\n" "could not add the following ips: {}\n".format( "\n\t- " + "\n\t- ".join(ret["changes"]["new"]), "\n\t- " + "\n\t- ".join(ips_to_add), "\n\t- " + "\n\t- ".join(ips_not_added), ) ) else: ret["comment"] = "added ips: {}".format( "\n\t- " + "\n\t- ".join(ips_to_add) ) # Verify there were changes if ret["changes"]["old"] == ret["changes"]["new"]: ret["changes"] = {} else: # Testing mode, show that there were ips to add ret["comment"] = "ips on eni: {}\nips that would be added: {}\n".format( "\n\t- " + "\n\t- ".join(ret["changes"]["old"]), "\n\t- " + "\n\t- ".join(ips_to_add), ) ret["changes"] = {} ret["result"] = None else: ret["comment"] = "ips on eni: {}".format( "\n\t- " + "\n\t- ".join(ret["changes"]["old"]) ) # there were no changes since we did not attempt to remove ips ret["changes"] = {} return ret def private_ips_absent( name, network_interface_name=None, network_interface_id=None, private_ip_addresses=None, region=None, key=None, keyid=None, profile=None, ): """ Ensure an ENI does not have secondary private ip addresses associated with it name (String) - State definition name network_interface_id (String) - The EC2 network interface id, example eni-123456789 private_ip_addresses (List or String) - The secondary private ip address(es) that should be absent on the ENI. region (string) - Region to connect to. key (string) - Secret key to be used. keyid (string) - Access key to be used. profile (variable) - A dict with region, key and keyid, or a pillar key (string) that contains a dict with region, key and keyid. """ if not salt.utils.data.exactly_one((network_interface_name, network_interface_id)): raise SaltInvocationError( "Exactly one of 'network_interface_name', " "'network_interface_id' must be provided" ) if not private_ip_addresses: raise SaltInvocationError( "You must provide the private_ip_addresses to unassociate with the ENI" ) if not isinstance(private_ip_addresses, list): private_ip_addresses = [private_ip_addresses] ret = { "name": name, "result": True, "comment": "", "changes": {"new": [], "old": []}, } get_eni_args = { "name": network_interface_name, "network_interface_id": network_interface_id, "region": region, "key": key, "keyid": keyid, "profile": profile, } eni = __salt__["boto_ec2.get_network_interface"](**get_eni_args) # Check if there are any old private ips to remove from the eni primary_private_ip = None if eni and eni.get("result", {}).get("private_ip_addresses"): for eni_pip in eni["result"]["private_ip_addresses"]: ret["changes"]["old"].append(eni_pip["private_ip_address"]) if eni_pip["primary"]: primary_private_ip = eni_pip["private_ip_address"] ips_to_remove = [] for private_ip in private_ip_addresses: if private_ip in ret["changes"]["old"]: ips_to_remove.append(private_ip) if private_ip == primary_private_ip: ret["result"] = False ret["comment"] = ( "You cannot unassign the primary private ip address ({}) on an " "eni\n" "ips on eni: {}\n" "attempted to remove: {}\n".format( primary_private_ip, "\n\t- " + "\n\t- ".join(ret["changes"]["old"]), "\n\t- " + "\n\t- ".join(private_ip_addresses), ) ) ret["changes"] = {} return ret if ips_to_remove: if not __opts__["test"]: # Unassign secondary private ips to ENI assign_ips_args = { "network_interface_id": network_interface_id, "private_ip_addresses": ips_to_remove, "region": region, "key": key, "keyid": keyid, "profile": profile, } __salt__["boto_ec2.unassign_private_ip_addresses"](**assign_ips_args) # Verify secondary private ips were properly unassigned from ENI eni = __salt__["boto_ec2.get_network_interface"](**get_eni_args) if eni and eni.get("result", {}).get("private_ip_addresses", None): for eni_pip in eni["result"]["private_ip_addresses"]: ret["changes"]["new"].append(eni_pip["private_ip_address"]) ips_not_removed = [] for private_ip in private_ip_addresses: if private_ip in ret["changes"]["new"]: ips_not_removed.append(private_ip) if ips_not_removed: ret["result"] = False ret["comment"] = ( "ips on eni: {}\n" "attempted to remove: {}\n" "could not remove the following ips: {}\n".format( "\n\t- " + "\n\t- ".join(ret["changes"]["new"]), "\n\t- " + "\n\t- ".join(ips_to_remove), "\n\t- " + "\n\t- ".join(ips_not_removed), ) ) else: ret["comment"] = "removed ips: {}".format( "\n\t- " + "\n\t- ".join(ips_to_remove) ) # Verify there were changes if ret["changes"]["old"] == ret["changes"]["new"]: ret["changes"] = {} else: # Testing mode, show that there were ips to remove ret["comment"] = "ips on eni: {}\nips that would be removed: {}\n".format( "\n\t- " + "\n\t- ".join(ret["changes"]["old"]), "\n\t- " + "\n\t- ".join(ips_to_remove), ) ret["changes"] = {} ret["result"] = None else: ret["comment"] = "ips on network interface: {}".format( "\n\t- " + "\n\t- ".join(ret["changes"]["old"]) ) # there were no changes since we did not attempt to remove ips ret["changes"] = {} return ret
Save