diff --git a/blueprints/aws_subnet/aws_subnet.json b/blueprints/aws_subnet/aws_subnet.json new file mode 100644 index 00000000..a1531e00 --- /dev/null +++ b/blueprints/aws_subnet/aws_subnet.json @@ -0,0 +1,45 @@ +{ + "any-group-can-deploy": true, + "auto-historical-resources": false, + "build-items": [ + { + "action-name": "Build Subnet", + "continue-on-failure": false, + "deploy-seq": 1, + "description": null, + "execute-in-parallel": false, + "name": "Build Subnet", + "run-on-scale-up": true, + "show-on-order-form": true, + "type": "plugin" + } + ], + "description": "Create a subnet in a VPC associated with an existing CloudBolt Environment.", + "favorited": false, + "icon": "aws_subnet_1mCgJw9.png", + "is-orderable": true, + "management-actions": [], + "name": "AWS Subnet", + "resource-name-template": "AWS Subnet 000X", + "resource-type": { + "icon": "", + "label": "AWS Subnet", + "lifecycle": "ACTIVE", + "list-view-columns": [], + "name": "aws_subnet", + "plural-label": "AWS Subnets" + }, + "sequence": 0, + "show-recipient-field-on-order-form": false, + "teardown-items": [ + { + "action-name": "Delete Subnet", + "continue-on-failure": false, + "deploy-seq": -1, + "description": null, + "execute-in-parallel": false, + "name": "Delete Subnet", + "type": "teardown_plugin" + } + ] +} \ No newline at end of file diff --git a/blueprints/aws_subnet/aws_subnet_1mCgJw9.png b/blueprints/aws_subnet/aws_subnet_1mCgJw9.png new file mode 100644 index 00000000..ef0dc6bb Binary files /dev/null and b/blueprints/aws_subnet/aws_subnet_1mCgJw9.png differ diff --git a/blueprints/aws_subnet/build_subnet/build_subnet.json b/blueprints/aws_subnet/build_subnet/build_subnet.json new file mode 100644 index 00000000..467c15a4 --- /dev/null +++ b/blueprints/aws_subnet/build_subnet/build_subnet.json @@ -0,0 +1,135 @@ +{ + "action-inputs": [ + { + "available-all-servers": false, + "description": "The Availability Zone or Local Zone for the subnet.\r\n\r\nDefault: AWS selects one for you. If you create more than one subnet in your VPC, we do not necessarily select a different zone for each subnet.", + "field-dependency-controlling-set": [ + { + "controlling-field": { + "name": "AWS_ENV_ID_a192" + }, + "custom-field-options": [], + "dependency-type": "REGENOPTIONS", + "dependent-field": { + "name": "AWS_AVAIL_ZONE_a192" + }, + "maximum": null, + "minimum": null, + "regex": "" + } + ], + "field-dependency-dependent-set": [ + { + "controlling-field": { + "name": "AWS_AVAIL_ZONE_a192" + }, + "custom-field-options": [], + "dependency-type": "REGENOPTIONS", + "dependent-field": { + "name": "AWS_VPC_CIDR_a192" + }, + "maximum": null, + "minimum": null, + "regex": "" + } + ], + "global-options": [], + "hide-if-default-value": false, + "label": "Availability Zone", + "name": "AWS_AVAIL_ZONE", + "relevant-osfamilies": [], + "required": true, + "show-as-attribute": false, + "show-on-servers": false, + "type": "STR" + }, + { + "available-all-servers": false, + "description": "", + "field-dependency-controlling-set": [], + "field-dependency-dependent-set": [ + { + "controlling-field": { + "name": "AWS_ENV_ID_a192" + }, + "custom-field-options": [], + "dependency-type": "REGENOPTIONS", + "dependent-field": { + "name": "AWS_AVAIL_ZONE_a192" + }, + "maximum": null, + "minimum": null, + "regex": "" + } + ], + "global-options": [], + "hide-if-default-value": false, + "label": "AWS Environment", + "name": "AWS_ENV_ID", + "relevant-osfamilies": [], + "required": true, + "show-as-attribute": false, + "show-on-servers": false, + "type": "STR" + }, + { + "available-all-servers": false, + "description": "The IPv4 network range for the subnet, in CIDR notation. For example, 10.0.0.0/24 . We modify the specified CIDR block to its canonical form; for example, if you specify 100.68.0.18/18 , we modify it to 100.68.0.0/18 .", + "field-dependency-controlling-set": [], + "field-dependency-dependent-set": [], + "global-options": [], + "hide-if-default-value": false, + "label": "Subnet IPv4 CIDR Address", + "name": "AWS_SUBNET_CIDR", + "regex-constraint": "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(3[0-2]|[1-2][0-9]|[0-9]))$", + "relevant-osfamilies": [], + "required": true, + "show-as-attribute": false, + "show-on-servers": false, + "type": "STR" + }, + { + "available-all-servers": false, + "description": "This is the CIDR address associated with the selected VPC, and is for informational purposes only.", + "field-dependency-controlling-set": [ + { + "controlling-field": { + "name": "AWS_AVAIL_ZONE_a192" + }, + "custom-field-options": [], + "dependency-type": "REGENOPTIONS", + "dependent-field": { + "name": "AWS_VPC_CIDR_a192" + }, + "maximum": null, + "minimum": null, + "regex": "" + } + ], + "field-dependency-dependent-set": [], + "global-options": [], + "hide-if-default-value": false, + "label": "VPC IPv4 CIDR Address", + "name": "AWS_VPC_CIDR", + "relevant-osfamilies": [], + "required": true, + "show-as-attribute": false, + "show-on-servers": false, + "type": "STR" + } + ], + "action-inputs-sequence": [ + "AWS_ENV_ID", + "AWS_VPC_CIDR", + "AWS_AVAIL_ZONE", + "AWS_SUBNET_CIDR" + ], + "description": "", + "max-retries": 0, + "name": "Build Subnet", + "resource-technologies": [], + "script-filename": "cb_plugin_1605664239999382_ocUIYRM_Rtj6rXs.py", + "shared": "False", + "target-os-families": [], + "type": "CloudBolt Plug-in" +} \ No newline at end of file diff --git a/blueprints/aws_subnet/build_subnet/cb_plugin_1605664239999382_ocUIYRM_Rtj6rXs.py b/blueprints/aws_subnet/build_subnet/cb_plugin_1605664239999382_ocUIYRM_Rtj6rXs.py new file mode 100644 index 00000000..bbfd1249 --- /dev/null +++ b/blueprints/aws_subnet/build_subnet/cb_plugin_1605664239999382_ocUIYRM_Rtj6rXs.py @@ -0,0 +1,183 @@ +from common.methods import set_progress +from infrastructure.models import CustomField, Environment +from resourcehandlers.aws.models import AWSHandler +from utilities.logger import ThreadLogger + +logger = ThreadLogger(__name__) + +AWS_ENVIRONMENT_ID = int('{{AWS_ENV_ID}}') +AWS_SUBNET_CIDR = str('{{AWS_SUBNET_CIDR}}') +AWS_AVAILABILITY_ZONE = str('{{AWS_AVAIL_ZONE}}') +AWS_VPC_CIDR = str('{{AWS_VPC_CIDR}}') + + +def generate_options_for_AWS_ENV_ID( + profile, + **kwargs): + envs = Environment.objects_for_profile(profile) + aws_envs = filter( + lambda e: e.resource_handler and e.resource_handler.type_slug == 'aws', + envs) + + options = [] + for env in list(aws_envs): + options.append( + (env.id, env.name), + ) + + return options + + +def generate_options_for_AWS_VPC_CIDR( + control_field=None, + form_data=None, form_prefix=None, + **kwargs): + options = [] + + if not control_field: + return options + + _, env_id = get_value_from_form_data( + form_data=form_data, + form_prefix=form_prefix, + field_name='AWS_ENV_ID') + + try: + env = Environment.objects.get(id=env_id) + ec2_client = get_boto3_for_env(env) + response = ec2_client.describe_vpcs(VpcIds=[env.vpc_id]) + if 'Vpcs' in response and len(response['Vpcs']) > 0: + cidr = response['Vpcs'][0]['CidrBlock'] + options.append((cidr, cidr), ) + except Exception as ex: + logger.error(ex) + options.append(('not available', 'not available'), ) + + return options + + +def get_value_from_form_data( + form_data=None, + form_prefix=None, + field_name=None): + field_key = None + field_value = None + if 'form_prefix' in form_data and not form_prefix: + form_prefix = form_data.get('form_prefix', None) + if not form_prefix: + # Nothing can be found + return field_key, field_value + for key_name in form_data: + if form_prefix + "-" + field_name + "_a" in key_name: + field_key = key_name + field_value = form_data[key_name] if \ + isinstance(form_data[key_name], str) else \ + form_data[key_name][0] + break + return field_key, field_value + + +def generate_options_for_AWS_AVAIL_ZONE( + control_field=None, + control_value=None, + **kwargs): + if not control_field: + return [] + + availability_zones = [ + ('default', 'Default'), + ] + + try: + env = Environment.objects.get(id=control_value) + region = env.aws_region + rh = AWSHandler.objects.get(id=env.resource_handler.id) + ec2_client = rh.get_boto3_client(region_name=region) + + response = ec2_client.describe_availability_zones() + if response.get('AvailabilityZones'): + for zone in response['AvailabilityZones']: + availability_zones.append( + (zone['ZoneName'], zone['ZoneName'],), + ) + except Exception as ex: + logger.error(ex) + + return availability_zones + + +def run(resource, profile, **kwargs): + cf_subnet_id, _ = CustomField.objects.get_or_create( + name="bp_aws_subnet_id", + label="Subnet ID", + type="STR", + show_as_attribute=True + ) + cf_subnet_cidr, _ = CustomField.objects.get_or_create( + name="bp_aws_subnet_cidr", + label="IPv4 CIDR Address", + type="STR", + show_as_attribute=True + ) + cf_env_id, _ = CustomField.objects.get_or_create( + name="bp_aws_subnet_env_id", + label="CB Environment ID", + type="INT", + show_as_attribute=False + ) + + env = Environment.objects.get(id=AWS_ENVIRONMENT_ID) + vpc_id = env.vpc_id + region = env.aws_region + rh = env.resource_handler.cast() + ec2_client = rh.get_boto3_client(region_name=region) + + try: + response = ec2_client.create_subnet( + AvailabilityZone='' if AWS_AVAILABILITY_ZONE.lower() == 'default' + else AWS_AVAILABILITY_ZONE, + CidrBlock=AWS_SUBNET_CIDR, + VpcId=vpc_id, + ) + subnet_id = response['Subnet']['SubnetId'] + + response = ec2_client.create_tags( + Resources=[subnet_id], + Tags=[ + { + "Key": "Name", + "Value": f"{resource.name}" + }, + { + "Key": "cb_resource_id", + "Value": f"{resource.id}" + }, + { + "Key": "cb_env_id", + "Value": f"{AWS_ENVIRONMENT_ID}" + }, + { + "Key": "cb_rh_id", + "Value": f"{rh.id}" + }, + ] + ) + + resource.update_cf_value(cf_subnet_id, subnet_id, profile) + resource.update_cf_value(cf_subnet_cidr, AWS_SUBNET_CIDR, profile) + resource.update_cf_value(cf_env_id, env.id, profile) + + set_progress(f"Subnet ID: {subnet_id}") + set_progress(f"Subnet IPv4 CIDR: {AWS_SUBNET_CIDR}") + set_progress(f"CB Environment ID: {env.id}") + except Exception as ex: + logger.error(ex) + return "FAILURE", "", str(ex) + + return "SUCCESS", "", "" + + +def get_boto3_for_env(env): + region = env.aws_region + rh = env.resource_handler.cast() + return rh.get_boto3_client(region_name=region) \ No newline at end of file diff --git a/blueprints/aws_subnet/delete_subnet/cb_plugin_1605666168750486_WIucDJx_HgkL5JW.py b/blueprints/aws_subnet/delete_subnet/cb_plugin_1605666168750486_WIucDJx_HgkL5JW.py new file mode 100644 index 00000000..a57becdb --- /dev/null +++ b/blueprints/aws_subnet/delete_subnet/cb_plugin_1605666168750486_WIucDJx_HgkL5JW.py @@ -0,0 +1,25 @@ +from common.methods import set_progress +from infrastructure.models import Environment +from resourcehandlers.aws.models import AWSHandler +from utilities.logger import ThreadLogger + +logger = ThreadLogger(__name__) + + +def run(job, resource, profile, **kwargs): + set_progress("Delete Customer Subnet") + + env_id = resource.bp_aws_subnet_env_id + subnet_id = resource.bp_aws_subnet_id + + try: + env = Environment.objects.get(id=env_id) + rh = env.resource_handler.cast() + ec2_client = rh.get_boto3_client(region_name=env.aws_region) + response = ec2_client.delete_subnet(SubnetId=subnet_id) + except Exception as ex: + logger.error(str(ex)) + return "WARNING", "", f"Unable to remove subnet: {subnet_id}. " \ + f"Manual clean-up in AWS may be necessary." + + return "SUCCESS", "", "" \ No newline at end of file diff --git a/blueprints/aws_subnet/delete_subnet/delete_subnet.json b/blueprints/aws_subnet/delete_subnet/delete_subnet.json new file mode 100644 index 00000000..b309ceb8 --- /dev/null +++ b/blueprints/aws_subnet/delete_subnet/delete_subnet.json @@ -0,0 +1,10 @@ +{ + "description": "", + "max-retries": 0, + "name": "Delete Subnet", + "resource-technologies": [], + "script-filename": "cb_plugin_1605666168750486_WIucDJx_HgkL5JW.py", + "shared": "False", + "target-os-families": [], + "type": "CloudBolt Plug-in" +} \ No newline at end of file