diff --git a/nix/aws-ec2-launch-template.nix b/nix/aws-ec2-launch-template.nix
new file mode 100644
index 00000000..6c6f6116
--- /dev/null
+++ b/nix/aws-ec2-launch-template.nix
@@ -0,0 +1,100 @@
+{ config, lib, uuid, name, ... }:
+
+with import ./lib.nix lib;
+with lib;
+
+{
+ imports = [ ./common-ec2-auth-options.nix ];
+
+ options = {
+
+ templateName = mkOption {
+ default = "nixops-${uuid}-${name}";
+ type = types.str;
+ description = "Name of the launch template.";
+ };
+
+ templateId = mkOption {
+ default = "";
+ type = types.str;
+ description = "ec2 launch template ID (set by NixOps)";
+ };
+
+ versionDescription = mkOption {
+ default = "";
+ type = types.str;
+ description = "A description for the version of the launch template";
+ };
+
+
+ # we might want to make this in a way similar to ec2.nix
+ ebsOptimized = mkOption {
+ default = true;
+ description = ''
+ Whether the EC2 instance should be created as an EBS Optimized instance.
+ '';
+ type = types.bool;
+ };
+
+ userData = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ The user data to make available to the instance.
+ It should be valid nix expressions.
+ '';
+ };
+
+ # add support for ec2 then move to common
+ disableApiTermination = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ If set to true , you can't terminate the instance
+ using the Amazon EC2 console, CLI, or API.
+ '';
+ };
+
+ # add support for ec2 then move to common
+ instanceInitiatedShutdownBehavior = mkOption {
+ default = "terminate";
+ type = types.enum ["stop" "terminate"];
+ description = ''
+ Indicates whether an instance stops or terminates
+ when you initiate shutdown from the instance (using
+ the operating system command for system shutdown).
+ '';
+ };
+ # add support for ec2 then move to common
+ networkInterfaceId = mkOption {
+ default = "";
+ # must get the id fro mthe name
+ type = with types; either str (resource "vpc-network-interface");
+ apply = x: if builtins.isString x then x else "res-" + x._name "." + x._type;
+ description = ''
+ The ID of the network interface.
+ '';
+ };
+
+ privateIpAddresses = mkOption {
+ default = null;
+ type = with types; (nullOr (listOf str));
+ description = ''
+ One or more secondary private IPv4 addresses.
+ '';
+ };
+ secondaryPrivateIpAddressCount = mkOption {
+ default = null;
+ type = types.nullOr types.int;
+ description = ''
+ The number of secondary private IPv4 addresses to assign to a network interface.
+ When you specify a number of secondary IPv4 addresses, Amazon EC2 selects these
+ IP addresses within the subnet's IPv4 CIDR range.
+ You can't specify this option and specify privateIpAddresses in the same time.
+ '';
+ };
+
+ }// (import ./common-ec2-options.nix { inherit lib; }) // (import ./common-ec2-instance-options.nix { inherit lib; });
+
+ config._type = "aws-ec2-launch-template";
+}
\ No newline at end of file
diff --git a/nix/common-ec2-instance-options.nix b/nix/common-ec2-instance-options.nix
new file mode 100644
index 00000000..a4e85976
--- /dev/null
+++ b/nix/common-ec2-instance-options.nix
@@ -0,0 +1,168 @@
+# Options shared between an ec2 resource type and the
+# launch template resource in EC2
+# instances.
+
+{ lib }:
+
+with lib;
+with import ./lib.nix lib;
+{
+
+ zone = mkOption {
+ default = "";
+ example = "us-east-1c";
+ type = types.str;
+ description = ''
+ The EC2 availability zone in which the instance should be
+ created. If not specified, a zone is selected automatically.
+ '';
+ };
+
+ # add support for ec2
+ monitoring = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ if set to true, detailed monitoring is enabled.
+ Otherwise, basic monitoring is enabled.
+ '';
+ };
+
+ tenancy = mkOption {
+ default = "default";
+ type = types.enum [ "default" "dedicated" "host" ];
+ description = ''
+ The tenancy of the instance (if the instance is running in a VPC).
+ An instance with a tenancy of dedicated runs on single-tenant hardware.
+ An instance with host tenancy runs on a Dedicated Host, which is an
+ isolated server with configurations that you can control.
+ '';
+ };
+
+ ebsInitialRootDiskSize = mkOption {
+ default = 0;
+ type = types.int;
+ description = ''
+ Preferred size (G) of the root disk of the EBS-backed instance. By
+ default, EBS-backed images have a size determined by the
+ AMI. Only supported on creation of the instance.
+ '';
+ };
+
+ ami = mkOption {
+ example = "ami-00000000";
+ type = types.str;
+ description = ''
+ EC2 identifier of the AMI disk image used in the virtual
+ machine. This must be a NixOS image providing SSH access.
+ '';
+ };
+
+ instanceType = mkOption {
+ default = "m1.small";
+ example = "m1.large";
+ type = types.str;
+ description = ''
+ EC2 instance type. See for a
+ list of valid Amazon EC2 instance types.
+ '';
+ };
+
+ instanceProfile = mkOption {
+ default = "";
+ example = "rolename";
+ type = types.str;
+ description = ''
+ The name of the IAM Instance Profile (IIP) to associate with
+ the instances.
+ '';
+ };
+
+ keyPair = mkOption {
+ example = "my-keypair";
+ type = types.either types.str (resource "ec2-keypair");
+ apply = x: if builtins.isString x then x else x.name;
+ description = ''
+ Name of the SSH key pair to be used to communicate securely
+ with the instance. Key pairs can be created using the
+ ec2-add-keypair command.
+ '';
+ };
+
+ securityGroupIds = mkOption {
+ default = [ "default" ];
+ type = types.listOf types.str;
+ description = ''
+ Security Group IDs for the instance. Necessary if starting
+ an instance inside a VPC/subnet. In the non-default VPC, security
+ groups needs to be specified by ID and not name.
+ '';
+ };
+
+ subnetId = mkOption {
+ default = "";
+ example = "subnet-00000000";
+ type = types.either types.str (resource "vpc-subnet");
+ apply = x: if builtins.isString x then x else "res-" + x._name + "." + x._type;
+ description = ''
+ The subnet inside a VPC to launch the instance in.
+ '';
+ };
+
+ associatePublicIpAddress = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ If instance in a subnet/VPC, whether to associate a public
+ IP address with the instance.
+ '';
+ };
+
+ placementGroup = mkOption {
+ default = "";
+ example = "my-cluster";
+ type = types.either types.str (resource "ec2-placement-group");
+ apply = x: if builtins.isString x then x else x.name;
+ description = ''
+ Placement group for the instance.
+ '';
+ };
+
+ spotInstancePrice = mkOption {
+ default = 0;
+ type = types.int;
+ description = ''
+ Price (in dollar cents per hour) to use for spot instances request for the machine.
+ If the value is equal to 0 (default), then spot instances are not used.
+ '';
+ };
+
+ spotInstanceRequestType = mkOption {
+ default = "one-time";
+ type = types.enum [ "one-time" "persistent" ];
+ description = ''
+ The type of the spot instance request. It can be either "one-time" or "persistent".
+ '';
+ };
+
+ spotInstanceInterruptionBehavior = mkOption {
+ default = "terminate";
+ type = types.enum [ "terminate" "stop" "hibernate" ];
+ description = ''
+ Whether to terminate, stop or hibernate the instance when it gets interrupted.
+ For stop, spotInstanceRequestType must be set to "persistent".
+ '';
+ };
+
+ spotInstanceTimeout = mkOption {
+ default = 0;
+ type = types.int;
+ description = ''
+ The duration (in seconds) that the spot instance request is
+ valid. If the request cannot be satisfied in this amount of
+ time, the request will be cancelled automatically, and NixOps
+ will fail with an error message. The default (0) is no timeout.
+ '';
+ };
+}
\ No newline at end of file
diff --git a/nix/default.nix b/nix/default.nix
index fd71ca4f..d3547d71 100644
--- a/nix/default.nix
+++ b/nix/default.nix
@@ -44,6 +44,7 @@
awsVPNGateways = evalResources ./aws-vpn-gateway.nix (zipAttrs resourcesByType.awsVPNGateways or []);
awsVPNConnections = evalResources ./aws-vpn-connection.nix (zipAttrs resourcesByType.awsVPNConnections or []);
awsVPNConnectionRoutes = evalResources ./aws-vpn-connection-route.nix (zipAttrs resourcesByType.awsVPNConnectionRoutes or []);
+ awsEc2LaunchTemplate = evalResources ./aws-ec2-launch-template.nix (zipAttrs resourcesByType.awsEc2LaunchTemplate or []);
};
}
diff --git a/nix/ec2.nix b/nix/ec2.nix
index b78de953..6efe6a2e 100644
--- a/nix/ec2.nix
+++ b/nix/ec2.nix
@@ -173,9 +173,9 @@ in
###### interface
- options = {
+ options.deployment.ec2 = {
- deployment.ec2.accessKeyId = mkOption {
+ accessKeyId = mkOption {
default = "";
example = "AKIABOGUSACCESSKEY";
type = types.str;
@@ -197,7 +197,7 @@ in
'';
};
- deployment.ec2.region = mkOption {
+ region = mkOption {
default = "";
example = "us-east-1";
type = types.str;
@@ -208,28 +208,7 @@ in
'';
};
- deployment.ec2.zone = mkOption {
- default = "";
- example = "us-east-1c";
- type = types.str;
- description = ''
- The EC2 availability zone in which the instance should be
- created. If not specified, a zone is selected automatically.
- '';
- };
-
- deployment.ec2.tenancy = mkOption {
- default = "default";
- type = types.enum [ "default" "dedicated" "host" ];
- description = ''
- The tenancy of the instance (if the instance is running in a VPC).
- An instance with a tenancy of dedicated runs on single-tenant hardware.
- An instance with host tenancy runs on a Dedicated Host, which is an
- isolated server with configurations that you can control.
- '';
- };
-
- deployment.ec2.ebsBoot = mkOption {
+ ebsBoot = mkOption {
default = true;
type = types.bool;
description = ''
@@ -241,37 +220,7 @@ in
'';
};
- deployment.ec2.ebsInitialRootDiskSize = mkOption {
- default = 0;
- type = types.int;
- description = ''
- Preferred size (G) of the root disk of the EBS-backed instance. By
- default, EBS-backed images have a size determined by the
- AMI. Only supported on creation of the instance.
- '';
- };
-
- deployment.ec2.ami = mkOption {
- example = "ami-00000000";
- type = types.str;
- description = ''
- EC2 identifier of the AMI disk image used in the virtual
- machine. This must be a NixOS image providing SSH access.
- '';
- };
-
- deployment.ec2.instanceType = mkOption {
- default = "m1.small";
- example = "m1.large";
- type = types.str;
- description = ''
- EC2 instance type. See for a
- list of valid Amazon EC2 instance types.
- '';
- };
-
- deployment.ec2.instanceId = mkOption {
+ instanceId = mkOption {
default = "";
type = types.str;
description = ''
@@ -279,28 +228,7 @@ in
'';
};
- deployment.ec2.instanceProfile = mkOption {
- default = "";
- example = "rolename";
- type = types.str;
- description = ''
- The name of the IAM Instance Profile (IIP) to associate with
- the instances.
- '';
- };
-
- deployment.ec2.keyPair = mkOption {
- example = "my-keypair";
- type = types.either types.str (resource "ec2-keypair");
- apply = x: if builtins.isString x then x else x.name;
- description = ''
- Name of the SSH key pair to be used to communicate securely
- with the instance. Key pairs can be created using the
- ec2-add-keypair command.
- '';
- };
-
- deployment.ec2.privateKey = mkOption {
+ privateKey = mkOption {
default = "";
example = "/home/alice/.ssh/id_rsa-my-keypair";
type = types.str;
@@ -314,7 +242,7 @@ in
'';
};
- deployment.ec2.securityGroups = mkOption {
+ securityGroups = mkOption {
default = [ "default" ];
example = [ "my-group" "my-other-group" ];
type = types.listOf (types.either types.str (resource "ec2-security-group"));
@@ -325,36 +253,7 @@ in
'';
};
- deployment.ec2.securityGroupIds = mkOption {
- default = [ "default" ];
- type = types.listOf types.str;
- description = ''
- Security Group IDs for the instance. Necessary if starting
- an instance inside a VPC/subnet. In the non-default VPC, security
- groups needs to be specified by ID and not name.
- '';
- };
-
- deployment.ec2.subnetId = mkOption {
- default = "";
- example = "subnet-00000000";
- type = types.either types.str (resource "vpc-subnet");
- apply = x: if builtins.isString x then x else "res-" + x._name + "." + x._type;
- description = ''
- The subnet inside a VPC to launch the instance in.
- '';
- };
-
- deployment.ec2.associatePublicIpAddress = mkOption {
- default = false;
- type = types.bool;
- description = ''
- If instance in a subnet/VPC, whether to associate a public
- IP address with the instance.
- '';
- };
-
- deployment.ec2.usePrivateIpAddress = mkOption {
+ usePrivateIpAddress = mkOption {
default = defaultUsePrivateIpAddress;
type = types.bool;
description = ''
@@ -365,7 +264,7 @@ in
'';
};
- deployment.ec2.sourceDestCheck = mkOption {
+ sourceDestCheck = mkOption {
default = true;
type = types.bool;
description = ''
@@ -374,19 +273,9 @@ in
'';
};
- deployment.ec2.placementGroup = mkOption {
- default = "";
- example = "my-cluster";
- type = types.either types.str (resource "ec2-placement-group");
- apply = x: if builtins.isString x then x else x.name;
- description = ''
- Placement group for the instance.
- '';
- };
-
- deployment.ec2.tags = commonEC2Options.tags;
+ tags = commonEC2Options.tags;
- deployment.ec2.blockDeviceMapping = mkOption {
+ blockDeviceMapping = mkOption {
default = { };
example = { "/dev/xvdb".disk = "ephemeral0"; "/dev/xvdg".disk = "vol-00000000"; };
type = with types; attrsOf (submodule ec2DiskOptions);
@@ -408,7 +297,7 @@ in
'';
};
- deployment.ec2.elasticIPv4 = mkOption {
+ elasticIPv4 = mkOption {
default = "";
example = "123.1.123.123";
type = types.either types.str (resource "elastic-ip");
@@ -418,7 +307,7 @@ in
'';
};
- deployment.ec2.physicalProperties = mkOption {
+ physicalProperties = mkOption {
default = {};
example = { cores = 4; memory = 14985; };
description = ''
@@ -427,44 +316,7 @@ in
'';
};
- deployment.ec2.spotInstancePrice = mkOption {
- default = 0;
- type = types.int;
- description = ''
- Price (in dollar cents per hour) to use for spot instances request for the machine.
- If the value is equal to 0 (default), then spot instances are not used.
- '';
- };
-
- deployment.ec2.spotInstanceRequestType = mkOption {
- default = "one-time";
- type = types.enum [ "one-time" "persistent" ];
- description = ''
- The type of the spot instance request. It can be either "one-time" or "persistent".
- '';
- };
-
- deployment.ec2.spotInstanceInterruptionBehavior = mkOption {
- default = "terminate";
- type = types.enum [ "terminate" "stop" "hibernate" ];
- description = ''
- Whether to terminate, stop or hibernate the instance when it gets interrupted.
- For stop, spotInstanceRequestType must be set to "persistent".
- '';
- };
-
- deployment.ec2.spotInstanceTimeout = mkOption {
- default = 0;
- type = types.int;
- description = ''
- The duration (in seconds) that the spot instance request is
- valid. If the request cannot be satisfied in this amount of
- time, the request will be cancelled automatically, and NixOps
- will fail with an error message. The default (0) is no timeout.
- '';
- };
-
- deployment.ec2.ebsOptimized = mkOption {
+ ebsOptimized = mkOption {
default = defaultEbsOptimized;
type = types.bool;
description = ''
@@ -472,10 +324,9 @@ in
'';
};
- fileSystems = mkOption {
+ } // import ./common-ec2-instance-options.nix { inherit lib; };
+ options.fileSystems = mkOption {
type = with types; loaOf (submodule fileSystemsOptions);
- };
-
};
diff --git a/nixopsaws/resources/__init__.py b/nixopsaws/resources/__init__.py
index 20773100..d578a47d 100644
--- a/nixopsaws/resources/__init__.py
+++ b/nixopsaws/resources/__init__.py
@@ -36,3 +36,4 @@
import vpc_route_table
import vpc_route_table_association
import vpc_subnet
+import aws_ec2_launch_template
\ No newline at end of file
diff --git a/nixopsaws/resources/aws_ec2_launch_template.py b/nixopsaws/resources/aws_ec2_launch_template.py
new file mode 100644
index 00000000..e6877233
--- /dev/null
+++ b/nixopsaws/resources/aws_ec2_launch_template.py
@@ -0,0 +1,278 @@
+# -*- coding: utf-8 -*-
+import ast
+import sys
+import base64
+import nixops.util
+import nixopsaws.ec2_utils
+import nixops.resources
+import botocore.exceptions
+from ec2_common import EC2CommonState
+
+class awsEc2LaunchTemplateDefinition(nixops.resources.ResourceDefinition):
+ """Definition of an ec2 launch template"""
+
+ @classmethod
+ def get_type(cls):
+ return "aws-ec2-launch-template"
+
+ @classmethod
+ def get_resource_type(cls):
+ return "awsEc2LaunchTemplate"
+
+ def show_type(self):
+ return "{0}".format(self.get_type())
+
+class awsEc2LaunchTemplateState(nixops.resources.ResourceState, EC2CommonState):
+ """State of an ec2 launch template"""
+
+ state = nixops.util.attr_property("state", nixops.resources.ResourceState.MISSING, int)
+ access_key_id = nixops.util.attr_property("accessKeyId", None)
+ region = nixops.util.attr_property("region", None)
+ templateName = nixops.util.attr_property("templateName", None)
+ templateId = nixops.util.attr_property("templateId", None)
+ templateVersion = nixops.util.attr_property("templateVersion", None)
+ versionDescription = nixops.util.attr_property("versionDescription", None)
+ ebsOptimized = nixops.util.attr_property("ebsOptimized", True, type=bool)
+ instanceProfile = nixops.util.attr_property("instanceProfile", None)
+ ami = nixops.util.attr_property("ami", None)
+ instanceType = nixops.util.attr_property("instanceType", None)
+ keyPair = nixops.util.attr_property("keyPair", None)
+ userData = nixops.util.attr_property("userData", None)
+ securityGroupIds = nixops.util.attr_property("securityGroupIds", None, 'json')
+ disableApiTermination = nixops.util.attr_property("disableApiTermination", False, type=bool)
+ instanceInitiatedShutdownBehavior = nixops.util.attr_property("instanceInitiatedShutdownBehavior", None)
+ placementGroup = nixops.util.attr_property("placementGroup", None)
+ zone = nixops.util.attr_property("zone", None)
+ tenancy = nixops.util.attr_property("tenancy", None)
+ associatePublicIpAddress = nixops.util.attr_property("associatePublicIpAddress", True, type=bool)
+ networkInterfaceId = nixops.util.attr_property("networkInterfaceId", None)
+ subnetId = nixops.util.attr_property("subnetId", None)
+ privateIpAddresses = nixops.util.attr_property("privateIpAddresses", {}, 'json')
+ secondaryPrivateIpAddressCount = nixops.util.attr_property("secondaryPrivateIpAddressCount", None)
+ monitoring = nixops.util.attr_property("LTMonitoring", False, type=bool)
+ spotInstancePrice = nixops.util.attr_property("ec2.spotInstancePrice", None)
+ spotInstanceRequestType = nixops.util.attr_property("spotInstanceRequestType", None)
+ spotInstanceInterruptionBehavior = nixops.util.attr_property("spotInstanceInterruptionBehavior", None)
+ spotInstanceTimeout = nixops.util.attr_property("spotInstanceTimeout", None)
+ clientToken = nixops.util.attr_property("clientToken", None)
+ ebsInitialRootDiskSize = nixops.util.attr_property("ebsInitialRootDiskSize", None)
+
+ @classmethod
+ def get_type(cls):
+ return "aws-ec2-launch-template"
+
+ def __init__(self, depl, name, id):
+ nixops.resources.ResourceState.__init__(self, depl, name, id)
+ self._conn_boto3 = None
+ self._conn_vpc = None
+
+ def _exists(self):
+ return self.state != self.MISSING
+
+ def show_type(self):
+ s = super(awsEc2LaunchTemplateState, self).show_type()
+ return s
+
+ @property
+ def resource_id(self):
+ return self.templateId
+
+ def connect_boto3(self, region):
+ if self._conn_boto3: return self._conn_boto3
+ self._conn_boto3 = nixopsaws.ec2_utils.connect_ec2_boto3(region, self.access_key_id)
+ return self._conn_boto3
+
+ def connect_vpc(self):
+ if self._conn_vpc:
+ return self._conn_vpc
+ self._conn_vpc = nixopsaws.ec2_utils.connect_vpc(self.region, self.access_key_id)
+ return self._conn_vpc
+
+ def _update_tag(self, defn):
+ self.connect_boto3(self.region)
+ tags = defn.config['tags']
+ tags.update(self.get_common_tags())
+ self._conn_boto3.create_tags(Resources=[self.templateId], Tags=[{"Key": k, "Value": tags[k]} for k in tags])
+
+ # TODO: Work on how to update the template (create a new version and update default version to use or what)
+ # i think this is done automatically so i think i need to remove it right ?
+ def create_after(self, resources, defn):
+ # EC2 launch templates can require key pairs, IAM roles, security
+ # groups and placement groups
+ return {r for r in resources if
+ isinstance(r, nixopsaws.resources.ec2_keypair.EC2KeyPairState) or
+ isinstance(r, nixopsaws.resources.iam_role.IAMRoleState) or
+ isinstance(r, nixopsaws.resources.ec2_security_group.EC2SecurityGroupState) or
+ isinstance(r, nixopsaws.resources.ec2_placement_group.EC2PlacementGroupState) or
+ isinstance(r, nixopsaws.resources.vpc_subnet.VPCSubnetState)}
+
+ # fix security group stuff later
+ def security_groups_to_ids(self, subnetId, groups):
+ sg_names = filter(lambda g: not g.startswith('sg-'), groups)
+ if sg_names != [ ] and subnetId != "":
+ self.connect_vpc()
+ vpc_id = self._conn_vpc.get_all_subnets([subnetId])[0].vpc_id
+
+ #Note: we can use ec2_utils.name_to_security_group but it only works with boto2
+ group_ids = []
+ for i in groups:
+ if i.startswith('sg-'):
+ group_ids.append(i)
+ else:
+ try:
+ group_ids.append(self._conn_boto3.describe_security_groups(Filters=[{'Name': 'group-name',
+ 'Values': [i]}]
+ )['SecurityGroups'][0]['GroupId'])
+ except botocore.exceptions.ClientError as error:
+ raise error
+ return group_ids
+ else:
+ return groups
+
+ def create(self, defn, check, allow_reboot, allow_recreate):
+
+ if self.region is None:
+ self.region = defn.config['region']
+ elif self.region != defn.config['region']:
+ self.warn("cannot change region of a running instance (from ‘{}‘ to ‘{}‘)"
+ .format(self.region, defn.config['region']))
+
+ self.access_key_id = defn.config['accessKeyId']
+ self.connect_boto3(self.region)
+ if self.state != self.UP:
+ tags = defn.config['tags']
+ tags.update(self.get_common_tags())
+ args = dict()
+ args['LaunchTemplateName'] = defn.config['templateName']
+ args['VersionDescription'] = defn.config['versionDescription']
+ args['LaunchTemplateData'] = dict(
+ EbsOptimized=defn.config['ebsOptimized'],
+ ImageId=defn.config['ami'],
+ Placement=dict(Tenancy=defn.config['tenancy']),
+ Monitoring=dict(Enabled=defn.config['monitoring']),
+ DisableApiTermination=defn.config['disableApiTermination'],
+ InstanceInitiatedShutdownBehavior=defn.config['instanceInitiatedShutdownBehavior'],
+ TagSpecifications=[dict(
+ ResourceType='instance',
+ Tags=[{"Key": k, "Value": tags[k]} for k in tags]
+ ),
+ dict(
+ ResourceType='volume',
+ Tags=[{"Key": k, "Value": tags[k]} for k in tags]
+ ) ]
+ )
+ if defn.config['instanceProfile'] != "":
+ args['LaunchTemplateData']['IamInstanceProfile'] = dict(
+ Name=defn.config['instanceProfile']
+ )
+ if defn.config['userData']:
+ args['LaunchTemplateData']['UserData'] = base64.b64encode(defn.config['userData'])
+
+ if defn.config['instanceType']:
+ args['LaunchTemplateData']['InstanceType'] = defn.config['instanceType']
+ if defn.config['placementGroup'] != "":
+ args['LaunchTemplateData']['Placement']['GroupName'] = defn.config['placementGroup']
+ if defn.config['zone']:
+ args['LaunchTemplateData']['Placement']['AvailabilityZone'] = defn.config['zone']
+
+ if defn.config['spotInstancePrice'] != 0:
+ args['LaunchTemplateData']['InstanceMarketOptions'] = dict(
+ MarketType="spot",
+ SpotOptions=dict(
+ MaxPrice=str(defn.config['spotInstancePrice']/100.0),
+ SpotInstanceType=defn.config['spotInstanceRequestType'],
+ ValidUntil=(datetime.datetime.utcnow() +
+ datetime.timedelta(0, defn.config['spotInstanceTimeout'])).isoformat(),
+ InstanceInterruptionBehavior=defn.config['spotInstanceInterruptionBehavior']
+ )
+ )
+ if defn.config['networkInterfaceId'] != "" or defn.config['subnetId'] != "":
+
+ args['LaunchTemplateData']['NetworkInterfaces'] = [dict(
+ DeviceIndex=0,
+ AssociatePublicIpAddress=defn.config['associatePublicIpAddress']
+ )]
+ if defn.config['securityGroupIds']!=[]:
+ args['LaunchTemplateData']['NetworkInterfaces'][0]['Groups'] = self.security_groups_to_ids(defn.config['subnetId'], defn.config['securityGroupIds'])
+ if defn.config['networkInterfaceId'] != "":
+ if defn.config['networkInterfaceId'].startswith("res-"):
+ res = self.depl.get_typed_resource(defn.config['networkInterfaceId'][4:].split(".")[0], "vpc-network-interface")
+ defn.config['networkInterfaceId'] = res._state['networkInterfaceId']
+ args['LaunchTemplateData']['NetworkInterfaces'][0]['networkInterfaceId']=defn.config['networkInterfaceId']
+ if defn.config['subnetId'] != "":
+ if defn.config['subnetId'].startswith("res-"):
+ res = self.depl.get_typed_resource(defn.config['subnetId'][4:].split(".")[0], "vpc-subnet")
+ defn.config['subnetId'] = res._state['subnetId']
+ args['LaunchTemplateData']['NetworkInterfaces'][0]['SubnetId']=defn.config['subnetId']
+ if defn.config['secondaryPrivateIpAddressCount']:
+ args['LaunchTemplateData']['NetworkInterfaces'][0]['SecondaryPrivateIpAddressCount']=defn.config['secondaryPrivateIpAddressCount']
+ if defn.config['privateIpAddresses']:
+ args['LaunchTemplateData']['NetworkInterfaces'][0]['PrivateIpAddresses']=defn.config['privateIpAddresses']
+ if defn.config['keyPair'] != "":
+ args['LaunchTemplateData']['KeyName']=defn.config['keyPair']
+
+ ami = self._conn_boto3.describe_images(ImageIds=[defn.config['ami']])['Images'][0]
+
+ # TODO: BlockDeviceMappings for non root volumes
+ args['LaunchTemplateData']['BlockDeviceMappings'] = [dict(
+ DeviceName="/dev/sda1",
+ Ebs=dict(
+ DeleteOnTermination=True,
+ VolumeSize=defn.config['ebsInitialRootDiskSize'],
+ VolumeType=ami['BlockDeviceMappings'][0]['Ebs']['VolumeType']
+ )
+ )]
+ # Use a client token to ensure that the template creation is
+ # idempotent; i.e., if we get interrupted before recording
+ # the fleet ID, we'll get the same fleet ID on the
+ # next run.
+ if not self.clientToken:
+ with self.depl._db:
+ self.clientToken = nixops.util.generate_random_string(length=48) # = 64 ASCII chars
+ self.state = self.STARTING
+
+ args['ClientToken'] = self.clientToken
+ self.log("creating launch template {} ...".format(defn.config['templateName']))
+ try:
+ launch_template = self._conn_boto3.create_launch_template(**args)
+ except botocore.exceptions.ClientError as error:
+ raise error
+ # Not sure whether to use lambda retry or keep it like this
+ with self.depl._db:
+ self.templateId = launch_template['LaunchTemplate']['LaunchTemplateId']
+ self.templateName = defn.config['templateName']
+ self.templateVersion = launch_template['LaunchTemplate']['LatestVersionNumber']
+ self.versionDescription = defn.config['versionDescription']
+ self.state = self.UP
+ # these are the tags for the template
+ self._update_tag(defn)
+
+ def check(self):
+
+ self.connect_boto3(self.region)
+ launch_template = self._conn_boto3.describe_launch_templates(
+ LaunchTemplateIds=[self.templateId]
+ )['LaunchTemplates']
+ if launch_template is None:
+ self.state = self.MISSING
+ return
+ if str(launch_template[0]['DefaultVersionNumber']) != self.templateVersion:
+ self.warn("default version on the launch template is different then nixops managed version...")
+
+ def _destroy(self):
+
+ self.connect_boto3(self.region)
+ self.log("deleting ec2 launch template `{}`... ".format(self.templateName))
+ try:
+ self._conn_boto3.delete_launch_template(LaunchTemplateId=self.templateId)
+ except botocore.exceptions.ClientError as error:
+ if error.response['Error']['Code'] == "InvalidLaunchTemplateId.NotFound":
+ self.warn("Template `{}` already deleted...".format(self.templateName))
+ else:
+ raise error
+
+ def destroy(self, wipe=False):
+ if not self._exists(): return True
+
+ self._destroy()
+ return True