#!/usr/bin/env python

# Copyright 2016-2017 Nitor Creations Oy
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

""" Utilities to bootsrap AWS accounts into use with nameless-deploy-tools
"""

import argparse
import os
import random
import re
import shutil
import stat
import subprocess
import sys
from ast import literal_eval
from collections import OrderedDict
from copy import deepcopy
from subprocess import PIPE, Popen

import argcomplete
import ipaddr
from argcomplete.completers import ChoicesCompleter
from threadlocal_aws.clients import ec2, route53

from n_utils import _to_str
from n_utils.aws_infra_util import yaml_load, yaml_save
from n_utils.ndt import find_all_includes, find_include
from n_utils.utils import has_output_selector, select_stacks
from n_utils.writer import ConfigFileWriter


def enum(**enums):
    return type("Enum", (), enums)


def type_guess(val):
    if not val:
        return None
    try:
        return literal_eval(val)
    except Exception:
        return _to_str(val)


BRANCH_MODES = enum(SINGLE_STACK="single", MULTI_STACK="multi")


def _add_default_params(parser):
    parser.add_argument("template", nargs="?").completer = ChoicesCompleter(list_templates())
    parser.add_argument(
        "-y",
        "--yes",
        action="store_true",
        help="Answer yes or use default to all questions",
    )


def create_stack():
    """Create a stack from a template"""
    parser = argparse.ArgumentParser(description=create_stack.__doc__, add_help=False)
    _add_default_params(parser)
    parser.add_argument("-h", "--help", action="store_true")
    if "_ARGCOMPLETE" in os.environ:
        ac_args = argcomplete.split_line(os.environ["COMP_LINE"], os.environ["COMP_POINT"])[3]
        if len(ac_args) >= 2:
            template = ac_args[1]
            template_yaml = load_template(template)
            if template_yaml and "ContextClass" in template_yaml:
                context = load_class(template_yaml["ContextClass"])()
                context.add_context_arguments(parser)
    argcomplete.autocomplete(parser)
    args, _ = parser.parse_known_args()
    if args.template:
        template_yaml = load_template(args.template)
        if "ContextClass" in template_yaml:
            context = load_class(template_yaml["ContextClass"])()
            template_yaml.pop("ContextClass", None)
            parser = argparse.ArgumentParser(description=context.__doc__)
            _add_default_params(parser)
            context.add_context_arguments(parser)
        else:
            parser = argparse.ArgumentParser(description=create_stack.__doc__)
            _add_default_params(parser)
    else:
        parser = argparse.ArgumentParser(description=create_stack.__doc__)
        _add_default_params(parser)
    args = parser.parse_args()
    context.resolve_parameters(args)
    context.set_template(template_yaml)
    if context.write(yes=args.yes):
        subprocess.check_call(
            [
                "ndt",
                "print-create-instructions",
                context.component_name,
                context.stack_name,
            ]
        )
    return


def load_template(template):
    file_name = find_include("creatable-templates/" + template + ".yaml")
    if file_name:
        return yaml_load(open(file_name))
    else:
        return None


def load_class(name):
    components = name.split(".")
    mod = __import__(components[0])
    for comp in components[1:]:
        mod = getattr(mod, comp)
    return mod


def list_templates():
    ret = []
    files = find_all_includes("creatable-templates/*.yaml")
    for next_file in files:
        next_name = os.path.basename(next_file)[:-5]
        if next_name not in ret:
            ret.append(next_name)
    return ret


def has_entry(prefix, name, file_name):
    if not os.path.isfile(file_name):
        return False
    with open(file_name) as config:
        for line in config.readlines():
            if "[" + prefix + name + "]" == line.strip():
                return True
    return False


def setup_cli(name=None, key_id=None, secret=None, region=None):
    if name is None:
        name = type_guess(input("Profile name: "))
    home_dir = os.path.expanduser("~")
    config_file = os.path.join(home_dir, ".aws", "config")
    credentials_file = os.path.join(home_dir, ".aws", "credentials")
    if has_entry("profile ", name, config_file) or has_entry("", name, credentials_file):
        print("Profile " + name + " already exists. Not overwriting.")
        return
    if key_id is None:
        key_id = type_guess(input("Key ID: "))
    if secret is None:
        secret = type_guess(input("Key secret: "))
    if region is None:
        region = type_guess(input("Default region: "))
    writer = ConfigFileWriter()
    config_values = {
        "__section__": "profile " + name,
        "output": "json",
        "region": region,
    }
    credentials_values = {
        "__section__": name,
        "aws_access_key_id": key_id,
        "aws_secret_access_key": secret,
    }
    writer.update_config(config_values, config_file)
    writer.update_config(credentials_values, credentials_file)
    home_bin = credentials_file = os.path.join(home_dir, "bin")
    if not os.path.isdir(home_bin):
        os.makedirs(home_bin)
    source_file = os.path.join(home_bin, name)
    with open(source_file, "w") as source_script:
        source_script.write("#!/bin/bash\n\n")
        source_script.write("export AWS_DEFAULT_REGION=")
        source_script.write(region + " AWS_PROFILE=" + name)
        source_script.write(" AWS_DEFAULT_PROFILE=" + name + "\n")
    os.chmod(source_file, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
    os.environ["AWS_PROFILE"] = name
    os.environ["AWS_DEFAULT_PROFILE"] = name
    os.environ["AWS_DEFAULT_REGION"] = region


def _append_cidr_param(public, letter, cidr, params):
    if public:
        cidr_param = "paramPub" + letter + "Cidr"
        description = "Public Subnet " + letter + " CIDR block"
    else:
        cidr_param = "paramPriv" + letter + "Cidr"
        description = "Private Subnet " + letter + " CIDR block"
    params.update(
        OrderedDict(
            [
                (
                    cidr_param,
                    OrderedDict(
                        [
                            ("Description", description),
                            ("Type", "String"),
                            ("Default", _nts(cidr)),
                        ]
                    ),
                )
            ]
        )
    )


def _append_network_resources(public, letter, resources, availability_zone):
    if public:
        subnet_resource = "resourcePubSubnetA"
        route_table_assoc = "resourcePubSubnetRouteTableAssocA"
        cidr_param = "paramPub" + letter + "Cidr"
        db_subnet = "resourcePubSubnetGroup"
    else:
        subnet_resource = "resourcePrivSubnetA"
        route_table_assoc = "resourcePrivSubnetRouteTableAssocA"
        cidr_param = "paramPriv" + letter + "Cidr"
        route_table = "resourcePrivRouteTableA"
        db_subnet = "resourcePrivSubnetGroup"

    subnet_to_create = subnet_resource[:-1] + letter

    subnet_res_dict = deepcopy(resources[subnet_resource])
    subnet_res_dict["Properties"]["CidrBlock"]["Ref"] = cidr_param
    subnet_res_dict["Properties"]["AvailabilityZone"] = availability_zone
    subnet_res_dict["Properties"]["Tags"][0]["Value"]["Fn::Join"][1][1] += availability_zone
    resources.update({subnet_to_create: subnet_res_dict})
    if not public:
        route_table_dict = deepcopy(resources[route_table])
        route_table_dict["Properties"]["Tags"][0]["Value"]["Fn::Join"][1][1] = " private route table " + letter
        resources.update(OrderedDict([(route_table[:-1] + letter, route_table_dict)]))
    route_table_assoc_dict = deepcopy(resources[route_table_assoc])
    if not public:
        route_table_assoc_dict["Properties"]["RouteTableId"]["Ref"] = route_table[:-1] + letter
    route_table_assoc_dict["Properties"]["SubnetId"]["Ref"] = subnet_to_create
    resources.update(OrderedDict([(route_table_assoc[:-1] + letter, route_table_assoc_dict)]))

    resources[db_subnet]["Properties"]["SubnetIds"].append(OrderedDict([("Ref", subnet_to_create)]))


def _nts(net):
    return str(net).lower()


def _get_network_yaml(network, vpc_cidr, subnet_prefixlen, subnet_base, network_yaml, common_yaml):
    subnet_bits = 32 - int(subnet_prefixlen)
    subnet_size = 2**subnet_bits
    last_subnet = subnet_base - subnet_size
    az_response = ec2().describe_availability_zones()
    az_names = sorted([az_data["ZoneName"] for az_data in az_response["AvailabilityZones"]])
    network_yaml["Parameters"]["paramVPCCidr"]["Default"] = _nts(vpc_cidr)
    for az_name in az_names:
        zone_letter = az_name[-1:]
        zone_upper_letter = zone_letter.upper()
        last_subnet += subnet_size
        private_subnet_addr = last_subnet + (len(az_names) * subnet_size)
        subnet = ipaddr.IPv4Network(str(last_subnet) + "/" + str(subnet_prefixlen))
        private_subnet = ipaddr.IPv4Network(str(private_subnet_addr) + "/" + str(subnet_prefixlen))
        if zone_letter == "a":
            network_yaml["Parameters"]["paramPubACidr"]["Default"] = _nts(subnet)
            network_yaml["Parameters"]["paramPrivACidr"]["Default"] = _nts(private_subnet)
            network_yaml["Resources"]["resourcePubSubnetA"]["Properties"]["AvailabilityZone"] = az_name
            network_yaml["Resources"]["resourcePrivSubnetA"]["Properties"]["AvailabilityZone"] = az_name
            common_yaml["paramNetwork"]["Default"] = _nts(network)
        else:
            _append_cidr_param(True, zone_upper_letter, _nts(subnet), network_yaml["Parameters"])
            _append_cidr_param(
                False,
                zone_upper_letter,
                _nts(private_subnet),
                network_yaml["Parameters"],
            )
            _append_network_resources(True, zone_upper_letter, network_yaml["Resources"], az_name)
            _append_network_resources(False, zone_upper_letter, network_yaml["Resources"], az_name)
            network_yaml["Outputs"]["subnet" + zone_upper_letter] = {
                "Description": "Public Subnet " + zone_upper_letter,
                "Value": {"Ref": "resourcePubSubnet" + zone_upper_letter},
                "Export": {
                    "Name": {
                        "Fn::Join": [
                            ":",
                            [
                                {"Ref": "AWS::StackName"},
                                "publicSubnet" + zone_upper_letter,
                            ],
                        ]
                    }
                },
            }
            network_yaml["Outputs"]["subnetPriv" + zone_upper_letter] = {
                "Description": "Private Subnet " + zone_upper_letter,
                "Value": {"Ref": "resourcePrivSubnet" + zone_upper_letter},
                "Export": {
                    "Name": {
                        "Fn::Join": [
                            ":",
                            [
                                {"Ref": "AWS::StackName"},
                                "privateSubnet" + zone_upper_letter,
                            ],
                        ]
                    }
                },
            }
            pub_net = deepcopy(common_yaml["paramSubnetA"])
            pub_net["Description"] = pub_net["Description"][:-1] + zone_upper_letter
            pub_net["Default"]["StackRef"]["paramName"] = (
                pub_net["Default"]["StackRef"]["paramName"][:-1] + zone_upper_letter
            )
            common_yaml["paramSubnet" + zone_upper_letter] = pub_net
            priv_net = deepcopy(common_yaml["paramSubnetPrivA"])
            priv_net["Description"] = pub_net["Description"][:-1] + zone_upper_letter
            priv_net["Default"]["StackRef"]["paramName"] = (
                priv_net["Default"]["StackRef"]["paramName"][:-1] + zone_upper_letter
            )
            common_yaml["paramSubnetPriv" + zone_upper_letter] = priv_net
    return network_yaml, common_yaml


def _set_first_parameter(root, name, value):
    if name in root:
        root[name]["Default"] = value
        return True
    for k, v in list(root.items()):
        if isinstance(v, dict) or isinstance(v, OrderedDict):
            return _set_first_parameter(v, name, value)
        elif isinstance(v, list):
            for next in v:
                if _set_first_parameter(next, name, value):
                    return True


def _map_ssh_key(context, param, value):
    key_name = None
    default_name = "ndt-" + context.__class__.__name__.lower() + "-instance"
    if value == "1":
        key_name = type_guess(input("Name for new key pair (" + default_name + "): "))
        if not key_name:
            key_name = default_name
        key_res = ec2().create_key_pair(KeyName=key_name)
        if key_res and "KeyMaterial" in key_res:
            proc = Popen(
                ["store-secret.sh", key_name + ".pem"],
                stdout=PIPE,
                stdin=PIPE,
                stderr=PIPE,
            )
            proc.communicate(input=key_res["KeyMaterial"])
    else:
        try:
            index = int(value) - 2
            key_name = context.ssh_keys[index]
        except (ValueError, IndexError):
            print("Invalid ssh key selection " + value)
            sys.exit(1)
    return key_name


def _map_elastic_ip(context, param, value):
    eip = None
    if value == "1":
        eip_res = ec2().allocate_address(Domain="vpc")
        if eip_res and "PublicIp" in eip_res:
            eip = eip_res["PublicIp"]
        ec2().create_tags(
            Resources=[eip_res["AllocationId"]],
            Tags=[{"Key": "Name", "Value": context.__class__.__name__.lower()}],
        )
    else:
        try:
            index = int(value) - 2
            eip = context.elastic_ips[index]
        except (ValueError, IndexError):
            print("Invalid elastic ip selection " + value)
            sys.exit(1)
    return eip


def _map_list(context, param, value):
    select_list = getattr(context, param + "s")
    try:
        index = int(value) - 1
        return select_list[index]
    except (ValueError, IndexError):
        print("Invalid " + param + " selection " + value)
        sys.exit(1)


class ContextClassBase:
    """Base class for template contexts. Can be directly used for
    bootstrap stacks with no parameters to ask can use it directly
    """

    ask_fields = []
    component_name = "bootstrap"
    stack_name = "Stack name ({0}): "
    branch_mode = BRANCH_MODES.SINGLE_STACK
    template = None
    value_mappers = {}
    template_transformers = []

    def __init__(self, ask_fields):
        if not ask_fields:
            ask_fields = ["stack_name"]
        elif "stack_name" not in ask_fields:
            if ask_fields[0] == "component_name":
                ask_fields.insert(1, "stack_name")
            else:
                ask_fields.insert(0, "stack_name")
        self.ask_fields = ask_fields

    def stack_name_default(self):
        return "default"

    def set_template(self, template):
        self.template = template
        for transformer in self.template_transformers:
            transformer(self)

    def format_prompt(self, parameter, default=None):
        prompt = getattr(self, parameter)
        if not default:
            default = getattr(self, parameter + "_default")()
        return prompt.format(default)

    def getattr(self, parameter):
        return getattr(self, parameter)

    def setattr(self, parameter, value):
        return setattr(self, parameter, value)

    def add_context_arguments(self, parser):
        parameters = self.getattr("ask_fields")
        shorts = ["h"]
        for param in parameters:
            if param[0] not in shorts:
                shorts.append(param[0])
            else:
                attempt = [x[0] for x in re.split("[\b_]", param)]
                if len(attempt) > 1 and attempt[1] not in shorts:
                    shorts.append(attempt[1])
                else:
                    attempt = param[1]
                    if attempt not in shorts:
                        shorts.append(attempt)
                    else:
                        shorts.append("")
        shorts.pop(0)
        for short_arg, long_arg in zip(shorts, parameters):
            if short_arg:
                parser.add_argument("-" + short_arg, "--" + long_arg, help=self.format_prompt(long_arg))
            else:
                parser.add_argument("--" + long_arg, help=self.format_prompt(long_arg))

    def resolve_parameters(self, args):
        for param in self.ask_fields:
            if getattr(args, param):
                setattr(self, param, getattr(args, param))
            elif args.yes:
                setattr(self, param, getattr(args, param + "_default")())
            else:
                default = self.getattr(param + "_default")()
                prompt = self.format_prompt(param, default=default)
                setval = type_guess(input(prompt))
                if not setval:
                    setval = default
                if param in self.value_mappers:
                    setval = self.value_mappers[param](self, param, setval)
                self.setattr(param, setval)

    def _ask_ssh_key(self):
        self.ssh_key = "Ssh key ({0}):\n1: create new\n"
        self.ssh_keys = []
        self.ssh_key_default = lambda: "1"
        if "ssh_key" not in self.ask_fields:
            self.ask_fields.append("ssh_key")
        index = 2
        keys = ec2().describe_key_pairs()
        if keys and "KeyPairs" in keys:
            for key in keys["KeyPairs"]:
                self.ssh_keys.append(key["KeyName"])
                self.ssh_key = self.ssh_key + str(index) + ": " + key["KeyName"] + "\n"
                index = index + 1
        self.value_mappers["ssh_key"] = _map_ssh_key
        self.template_transformers.append(
            lambda myself: _set_first_parameter(myself.template, "paramSshKeyName", myself.ssh_key)
        )

    def _ask_elastic_ip(self):
        self.elastic_ip = "Elastic ip ({0}):\n1: allocate new\n"
        self.elastic_ips = []
        self.elastic_ip_default = lambda: "1"
        if "elastic_ip" not in self.ask_fields:
            self.ask_fields.append("elastic_ip")
        index = 2
        eips = ec2().describe_addresses()
        if eips and "Addresses" in eips:
            for address in eips["Addresses"]:
                if "InstanceId" not in address:
                    self.elastic_ips.append(address["PublicIp"])
                    self.elastic_ip = self.elastic_ip + str(index) + ": " + address["PublicIp"] + "\n"
                    index = index + 1
        self.value_mappers["elastic_ip"] = _map_elastic_ip
        self.template_transformers.append(
            lambda myself: _set_first_parameter(myself.template, "paramEip", myself.elastic_ip)
        )

    def write(self, yes=False):
        if "Files" in self.template:
            for entry in self.template["Files"]:
                for source, dest in list(entry.items()):
                    dest = self.component_name + os.sep + dest % self.__dict__
                    dest = os.path.normpath(dest)
                    dest_dir = os.path.normpath(os.path.dirname(dest))
                    if not os.path.exists(dest_dir):
                        os.makedirs(dest_dir)
                    source_file = find_include(source)
                    shutil.copy2(source_file, dest)
            self.template.pop("Files", None)
        stack_dir = os.path.join(".", self.component_name, "stack-" + self.stack_name)
        if not os.path.exists(stack_dir):
            os.makedirs(stack_dir)
        if self.branch_mode == BRANCH_MODES.SINGLE_STACK:
            file_name = "infra.properties"
            stack_props = os.path.join(stack_dir, file_name)
            if not os.path.exists(stack_props):
                with open(stack_props, "w") as stack_props_file:
                    stack_props_file.write("STACK_NAME=$ORIG_STACK_NAME\n")
        stack_template = os.path.join(stack_dir, "template.yaml")
        if os.path.exists(stack_template) and not yes:
            answer = type_guess(input("Overwrite " + self.stack_name + " stack? (n): "))
            if not answer or not answer.lower() == "y":
                return False
        with open(stack_template, "w") as stack_file:
            stack_file.write(yaml_save(self.template))
        return True


class Network(ContextClassBase):
    """Creates a public and private subnet for every availability zone
    in the selected region and a shared common yaml (common/network.yaml)
    for easy access to parameters.
    """

    stack_name = "Network stack name ({0}): "
    vpc_cidr = "VPC CIDR ({0}) "
    subnet_prefixlen = "Subnet prefix length ({0}) "
    subnet_base = "Subnet base ({0}) "
    last_vpc_cidr = None
    common_yaml = None

    def __init__(self):
        ContextClassBase.__init__(self, ["vpc_cidr", "subnet_prefixlen", "subnet_base"])

    def stack_name_default(self):
        return "network"

    def vpc_cidr_default(self):
        self.last_vpc_cidr = "10." + str(random.randint(0, 255)) + ".0.0/16"
        return self.last_vpc_cidr

    def subnet_prefixlen_default(self):
        try:
            nw = ipaddr.IPv4Network(self.vpc_cidr)
            return nw.prefixlen + 4
        except BaseException:
            try:
                nw = ipaddr.IPv4Network(self.last_vpc_cidr)
                return nw.prefixlen + 4
            except BaseException:
                return 20

    def subnet_base_default(self):
        try:
            nw = ipaddr.IPv4Network(self.vpc_cidr)
            return nw.network
        except BaseException:
            try:
                nw = ipaddr.IPv4Network(self.last_vpc_cidr)
                return nw.network
            except BaseException:
                return "10." + str(random.randint(0, 255)) + ".0.0"

    def set_template(self, template):
        common_yaml = yaml_load(open(find_include("creatable-templates/network/common.yaml")))
        self.template, self.common_yaml = _get_network_yaml(
            self.stack_name,
            self.vpc_cidr,
            self.subnet_prefixlen,
            self.subnet_base,
            template,
            common_yaml,
        )

    def write(self, yes=False):
        ContextClassBase.write(self, yes=yes)
        if not os.path.exists("common"):
            os.makedirs("common")
        common_out = os.path.join("common", "network.yaml")
        with open(common_out, "w") as c_out:
            c_out.write(yaml_save(self.common_yaml))
        return True


class BakeryRoles(ContextClassBase):
    """Creates roles necessary for baking images and deploying stacks"""

    def __init__(self):
        ContextClassBase.__init__(self, ["network_stack", "vault_stack"])
        self._ask_network_stack()
        self._ask_vault_stack()

    def _ask_network_stack(self):
        self.network_stack = "Network stack ({0}):\n"

        def network_sel(stack):
            return has_output_selector(stack, "VPC", lambda stack: stack["StackName"])

        self.network_stacks = select_stacks(network_sel)
        if self.network_stacks:
            index = 1
            for stack_name in self.network_stacks:
                self.network_stack = self.network_stack + str(index) + ": " + stack_name + "\n"
                index = index + 1
        self.value_mappers["network_stack"] = _map_list
        self.template_transformers.append(
            lambda myself: _set_first_parameter(myself.template, "paramNetworkStack", myself.network_stack)
        )

    def _ask_vault_stack(self):
        self.vault_stack = "Vault stack ({0}):\n"

        def vault_sel(stack):
            return has_output_selector(stack, "decryptPolicy", lambda stack: stack["StackName"])

        self.vault_stacks = select_stacks(vault_sel)
        if self.vault_stacks:
            index = 1
            for stack_name in self.vault_stacks:
                self.vault_stack = self.vault_stack + str(index) + ": " + stack_name + "\n"
                index = index + 1
        self.value_mappers["vault_stack"] = _map_list
        self.template_transformers.append(
            lambda myself: _set_first_parameter(myself.template, "paramVaultStack", myself.vault_stack)
        )

    def stack_name_default(self):
        return "bakery-roles"

    def network_stack_default(self):
        return "1"

    def vault_stack_default(self):
        return "1"


class Route53(ContextClassBase):
    """Creates a common shared yaml (common/route53.yaml) for
    easy access to route53 parameters
    """

    def __init__(self):
        ContextClassBase.__init__(self, ["hosted_zone"])
        self.ask_fields.pop(0)
        self._ask_hosted_zone()

    def _ask_hosted_zone(self):
        self.hosted_zone = "Hosted zone ({0}):\n"
        self.hosted_zones = route53().list_hosted_zones()["HostedZones"]
        self.hosted_zone_default = lambda: "1"
        if self.hosted_zones:
            index = 1
            for zone in self.hosted_zones:
                priv = "Private"
                if zone["Config"]["PrivateZone"]:
                    priv = "Public"
                self.hosted_zone = (
                    self.hosted_zone
                    + str(index)
                    + ": "
                    + zone["Name"]
                    + " ("
                    + zone["Id"].split("/")[-1:][0]
                    + ") - "
                    + priv
                    + "\n"
                )
                index = index + 1
        self.value_mappers["hosted_zone"] = _map_list

    def write(self, yes=False):
        if not os.path.exists("common"):
            os.makedirs("common")
        common_out = os.path.join("common", "route53.yaml")
        common_yaml = {
            "paramHostedZoneName": {
                "Type": "String",
                "Description": "Name of hosted zone to use",
                "Default": self.hosted_zone["Name"],
            },
            "paramHostedZoneDomain": {
                "Type": "String",
                "Description": "Name of hosted zone to use",
                "Default": self.hosted_zone["Name"][:-1],
            },
            "paramHostedZoneId": {
                "Type": "String",
                "Description": "Id of hosted zone to use",
                "Default": self.hosted_zone["Id"].split("/")[-1:][0],
            },
        }
        with open(common_out, "w") as c_out:
            c_out.write(yaml_save(common_yaml))
        return False


class Jenkins(ContextClassBase):
    """Creates a jenkins stack with the default domain name jenkins.${paramHostedZoneDomain}.
    Also a Jenkins Job DSL script is created that can be run in Jenkins to create jobs
    to bake and deploy ndt infrastructure defined in this repository.
    """

    elastic_ip = "Elastic IP ({0}):\n1: allocate new\n"
    elastic_ips = []

    def __init__(self):
        ContextClassBase.__init__(self, ["component_name", "elastic_ip", "ssh_key"])
        self.component_name = "Component name ({0}): "
        self.branch_mode = BRANCH_MODES.MULTI_STACK
        self._ask_elastic_ip()
        self._ask_ssh_key()

    def component_name_default(self):
        return "jenkins"

    def stack_name_default(self):
        return "jenkins"

    def set_template(self, template):
        ContextClassBase.set_template(self, template)
        az_response = ec2().describe_availability_zones()
        az_names = sorted([az_data["ZoneName"] for az_data in az_response["AvailabilityZones"]])
        for az_name in az_names:
            self.template["Resources"]["Fn::Merge"][0]["resourceAsg"]["Properties"]["AvailabilityZones"].append(az_name)
            self.template["Resources"]["Fn::Merge"][0]["resourceAsg"]["Properties"]["VPCZoneIdentifier"].append(
                "paramSubnet" + az_name[-1:].upper()
            )
