diff --git a/tools/image-builder/compute-rhel.sh b/tools/image-builder/compute-rhel.sh deleted file mode 100644 index a842db2d..00000000 --- a/tools/image-builder/compute-rhel.sh +++ /dev/null @@ -1,141 +0,0 @@ -#!/bin/bash -# LSF prerequisites -LSF_TOP="/opt/ibm/lsf" -LSF_CONF_PATH="${LSF_TOP}/conf" -LSF_PACKAGES_PATH="provide the path to where the lsf latest packges are present" -sleep 180 -LSF_PREREQS="python38 nfs-utils ed wget gcc-c++ elfutils-libelf-devel kernel-devel-$(uname -r) gcc-gfortran libgfortran libquadmath libmpc libquadmath-devel mpfr perl libnsl" -dnf install -y libnsl libnsl2 openldap-clients nss-pam-ldapd authselect sssd oddjob oddjob-mkhomedir -# shellcheck disable=SC2086 -yum install -y ${LSF_PREREQS} -useradd lsfadmin -# The IBM tool https://github.com/ibm/detect-secrets detects a potential secret keywork. -# However, the code is safe, it only allows lsfadmin to become sudoers. -echo 'lsfadmin ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers # pragma: allowlist secret -mkdir -p ${LSF_TOP} -chmod -R 755 /opt -rm -f /usr/bin/python3 -ln -s /usr/bin/python3.8 /usr/bin/python3 -curl -fsSL https://clis.cloud.ibm.com/install/linux | sh -pip3 install ibm-vpc==0.10.0 -pip3 install ibm-cloud-networking-services ibm-cloud-sdk-core selinux -ibmcloud plugin install vpc-infrastructure -ibmcloud plugin install DNS -chmod 755 -R /usr/local/lib/python3.8 -chmod 755 -R /usr/local/lib64/python3.8 -hostname lsfservers -echo 'provide the value of the entitlement check that is available on the lsf entitlement check file' > "${LSF_PACKAGES_PATH}/ls.entitlement" -echo 'provide the value of the entitlement check that is available on the lsf entitlement check file' > "${LSF_PACKAGES_PATH}/lsf.entitlement" - -# Need a appropriate LSF latest packages, using the packages the below configures the lsf core installation -cd "${LSF_PACKAGES_PATH}" || exit -zcat lsf*lsfinstall_linux_x86_64.tar.Z | tar xvf - -cd lsf*_lsfinstall || exit -sed -e '/show_copyright/ s/^#*/#/' -i lsfinstall -cat <> install.config -LSF_TOP="/opt/ibm/lsf" -LSF_ADMINS="lsfadmin" -LSF_CLUSTER_NAME="HPCCluster" -LSF_MASTER_LIST="lsfservers" -LSF_ENTITLEMENT_FILE="\${LSF_PACKAGES_PATH}/lsf.entitlement" -CONFIGURATION_TEMPLATE="DEFAULT" -ENABLE_DYNAMIC_HOSTS="Y" -ENABLE_EGO="N" -ACCEPT_LICENSE="Y" -SILENT_INSTALL="Y" -LSF_SILENT_INSTALL_TARLIST="ALL" -EOT -bash lsfinstall -f install.config -echo $? -cat Install.log -echo "====================== LSF Setup Done=====================" - -# The IBM tool https://github.com/ibm/detect-secrets detects a potential secret keywork. -# However, the code is safe, it only detected the word API_KEY but no secrets are exposed. -# Removal of API keys -sed -i "s/^VPC_APIKEY=.*/VPC_APIKEY=/g" "${LSF_CONF_PATH}/resource_connector/ibmcloudgen2/credentials" # pragma: allowlist secret -sed -i "s/^RESOURCE_RECORDS_APIKEY=.*/RESOURCE_RECORDS_APIKEY=/g" "${LSF_CONF_PATH}/resource_connector/ibmcloudgen2/credentials" # pragma: allowlist secret - -hostname lsfservers - -# LSF worker (Resource connector) installation -cd "${LSF_PACKAGES_PATH}" || exit -cd lsf*_lsfinstall || exit -cat <> server.config -LSF_TOP="/opt/ibm/lsf_worker" -LSF_ADMINS="lsfadmin" -LSF_ENTITLEMENT_FILE="\${LSF_PACKAGES_PATH}/lsf.entitlement" -LSF_SERVER_HOSTS="lsfservers" -LSF_LOCAL_RESOURCES="[resource cloudhpchost]" -ACCEPT_LICENSE="Y" -SILENT_INSTALL="Y" -EOT -bash lsfinstall -s -f server.config -echo $? -cat Install.log -echo "====================== WORKER Setup Done=====================" -rm -rf /opt/ibm/lsf_worker/10.1 -ln -s /opt/ibm/lsf/10.1 /opt/ibm/lsf_worker -rm -rf /opt/ibm/lsf_worker/10.1 -ln -s /opt/ibm/lsf/10.1 /opt/ibm/lsf_worker - - -# OpenMPI installation -cd "${LSF_PACKAGES_PATH}" || exit -wget https://download.open-mpi.org/release/open-mpi/v4.1/openmpi-4.1.0.tar.gz -tar -xvf openmpi-4.1.0.tar.gz -cd openmpi-4.1.0 || exit -ln -s /usr/lib64/libnsl.so.2.0.0 /usr/lib64/libnsl.so -export LANG=C -./configure --prefix='/usr/local/openmpi-4.1.0' --enable-mpi-thread-multiple --enable-shared --disable-static --enable-mpi-fortran=usempi --disable-libompitrace --enable-script-wrapper-compilers --enable-wrapper-rpath --enable-orterun-prefix-by-default --with-io-romio-flags=--with-file-system=nfs --with-lsf=/opt/ibm/lsf/10.1 --with-lsf-libdir=/opt/ibm/lsf/10.1/linux3.10-glibc2.17-x86_64/lib -make -j 32 -make install -find /usr/local/openmpi-4.1.0/ -type d -exec chmod 775 {} \; -echo "====================== SetUp of oneMPI completed=====================" - -# Intel One API (hpckit) installation -tee > /etc/yum.repos.d/oneAPI.repo << EOF -[oneAPI] -name=IntelĀ® oneAPI repository -baseurl=https://yum.repos.intel.com/oneapi -enabled=1 -gpgcheck=1 -repo_gpgcheck=1 -gpgkey=https://yum.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS.PUB -EOF -ls /etc/yum.repos.d -yum install -y intel-basekit intel-hpckit -rpm -qa | grep "intel-hpckit\|intel-basekit" -rm -rf /etc/yum.repos.d/oneAPI.repo -ls /etc/yum.repos.d -echo "====================== SetUp of one API completed=====================" - -# Setting up access for the appropriate path -# shellcheck disable=SC2086 -mv -f ${LSF_PACKAGES_PATH}/*.entitlement /opt/ibm/lsf/conf -chown -R lsfadmin:root ${LSF_CONF_PATH} - -# Updating security check -yum update --security -y - -# Sysdig Agent installation -echo "Installing Sysdig Agent" -curl -sL https://ibm.biz/install-sysdig-agent | sudo bash -s -- --access_key ==ACCESSKEY== --collector ==COLLECTOR== --collector_port 6443 --secure true --check_certificate false --additional_conf 'sysdig_capture_enabled: false\nremotefs: true\nfeature:\n mode: monitor_light' -systemctl stop dragent -systemctl disable dragent - -# Cleanup of all the folders and unwanted ssh keys as part of security -rm -rf "${LSF_PACKAGES_PATH}" -rm -rf /home/vpcuser/.ssh/authorized_keys -rm -rf /home/vpcuser/.ssh/known_hosts -rm -rf /home/vpcuser/.ssh/id_rsa* -rm -rf /home/lsfadmin/.ssh/authorized_keys -rm -rf /home/lsfadmin/.ssh/known_hosts -rm -rf /home/lsfadmin/.ssh/id_rsa* -rm -rf /root/.ssh/authorized_keys -rm -rf /root/.ssh/known_hosts -rm -rf /root/.ssh/id_rsa* -systemctl stop syslog -rm -rf /var/log/messages -rm -rf /root/.bash_history -history -c diff --git a/tools/image-builder/datasource.tf b/tools/image-builder/datasource.tf new file mode 100644 index 00000000..8ceb4e7e --- /dev/null +++ b/tools/image-builder/datasource.tf @@ -0,0 +1,36 @@ +data "ibm_is_vpc" "existing_vpc" { + count = var.vpc_name != null ? 1 : 0 + name = var.vpc_name +} + +data "ibm_is_vpc" "vpc" { + name = local.vpc_name + # Depends on creation of new VPC or look up of existing VPC based on value of var.vpc_name, + depends_on = [module.landing_zone, data.ibm_is_vpc.existing_vpc] +} + +data "ibm_is_subnet" "existing_subnet" { + count = (var.vpc_name != null && var.subnet_id != null) ? 1 : 0 + identifier = var.subnet_id +} + +data "ibm_resource_instance" "kms_instance" { + count = (var.key_management == "key_protect" && var.kms_instance_name != null) ? 1 : 0 + name = var.kms_instance_name + service = "kms" +} + +data "ibm_kms_key" "kms_key" { + count = (var.key_management == "key_protect" && var.kms_key_name != null) ? 1 : 0 + instance_id = data.ibm_resource_instance.kms_instance[0].id + key_name = var.kms_key_name +} + +data "ibm_is_image" "packer" { + name = "ibm-redhat-8-8-minimal-amd64-6" +} + +data "ibm_is_ssh_key" "packer" { + for_each = toset(var.ssh_keys) + name = each.key +} diff --git a/tools/image-builder/input_validation.tf b/tools/image-builder/input_validation.tf new file mode 100644 index 00000000..e4d9a370 --- /dev/null +++ b/tools/image-builder/input_validation.tf @@ -0,0 +1,68 @@ +################################################### +# Copyright (C) IBM Corp. 2023 All Rights Reserved. +# Licensed under the Apache License v2.0 +################################################### + +# This file contains the complete information on all the validations performed from the code during the generate plan process +# Validations are performed to make sure, the appropriate error messages are displayed to user in-order to provide required input parameter + +locals { + # validation for the boot volume encryption toggling. + validate_enable_customer_managed_encryption = anytrue([alltrue([var.kms_key_name != null, var.kms_instance_name != null]), (var.kms_key_name == null), (var.key_management != "key_protect")]) + validate_enable_customer_managed_encryption_msg = "Please make sure you are passing the kms_instance_name if you are passing kms_key_name." + # tflint-ignore: terraform_unused_declarations + validate_enable_customer_managed_encryption_chk = regex( + "^${local.validate_enable_customer_managed_encryption_msg}$", + (local.validate_enable_customer_managed_encryption ? local.validate_enable_customer_managed_encryption_msg : "")) + + # validation for the boot volume encryption toggling. + validate_null_customer_managed_encryption = anytrue([alltrue([var.kms_instance_name == null, var.key_management != "key_protect"]), (var.key_management == "key_protect")]) + validate_null_customer_managed_encryption_msg = "Please make sure you are setting key_management as key_protect if you are passing kms_instance_name, kms_key_name." + # tflint-ignore: terraform_unused_declarations + validate_null_customer_managed_encryption_chk = regex( + "^${local.validate_null_customer_managed_encryption_msg}$", + (local.validate_null_customer_managed_encryption ? local.validate_null_customer_managed_encryption_msg : "")) + + # Validate existing packer subnet should be the subset of vpc_name entered + validate_subnet_id_vpc_msg = "Provided packer subnet should be within the vpc entered." + validate_subnet_id_vpc = anytrue([var.subnet_id == null, var.subnet_id != null && var.vpc_name != null ? alltrue([for subnet_id in [var.subnet_id] : contains(data.ibm_is_vpc.existing_vpc[0].subnets[*].id, subnet_id)]) : false]) + # tflint-ignore: terraform_unused_declarations + validate_subnet_id_vpc_chk = regex("^${local.validate_subnet_id_vpc_msg}$", + (local.validate_subnet_id_vpc ? local.validate_subnet_id_vpc_msg : "")) + + # Validate existing packer subnet should be in the appropriate zone. + validate_subnet_id_zone_msg = "Provided packer subnet should be in appropriate zone." + validate_subnet_id_zone = anytrue([var.subnet_id == null, var.subnet_id != null && var.vpc_name != null ? alltrue([data.ibm_is_subnet.existing_subnet[0].zone == var.zones[0]]) : false]) + # tflint-ignore: terraform_unused_declarations + validate_subnet_id_zone_chk = regex("^${local.validate_subnet_id_zone_msg}$", + (local.validate_subnet_id_zone ? local.validate_subnet_id_zone_msg : "")) + + # Validate existing packer subnet public gateways + validate_subnet_name_pg_msg = "Provided existing packer subnet should have public gateway attached." + validate_subnet_name_pg = anytrue([var.subnet_id == null, var.subnet_id != null && var.vpc_name != null ? (data.ibm_is_subnet.existing_subnet[0].public_gateway != "") : false]) + # tflint-ignore: terraform_unused_declarations + validate_subnet_name_pg_chk = regex("^${local.validate_subnet_name_pg_msg}$", + (local.validate_subnet_name_pg ? local.validate_subnet_name_pg_msg : "")) + + # Validate existing vpc public gateways + validate_existing_vpc_pgw_msg = "Provided existing vpc should have the public gateways created in the provided zones." + validate_existing_vpc_pgw = anytrue([(var.vpc_name == null), alltrue([var.vpc_name != null, var.subnet_id != null]), alltrue([var.vpc_name != null, var.subnet_id == null, length(local.zone_1_pgw_ids) > 0])]) + # tflint-ignore: terraform_unused_declarations + validate_existing_vpc_pgw_chk = regex("^${local.validate_existing_vpc_pgw_msg}$", + (local.validate_existing_vpc_pgw ? local.validate_existing_vpc_pgw_msg : "")) + + # Validate the subnet_id user input value + validate_subnet_id_msg = "If the packer subnet_id is provided, the user should also provide the vpc_name." + validate_subnet_id = anytrue([var.vpc_name != null && var.subnet_id != null, var.subnet_id == null]) + # tflint-ignore: terraform_unused_declarations + validate_subnet_id_chk = regex("^${local.validate_subnet_id_msg}$", + (local.validate_subnet_id ? local.validate_subnet_id_msg : "")) + + # Validate security_group_id user input value + validate_security_group_id_msg = "If existing security_group_id is provided, the user should also specify vpc_name that has that security group ID." + validate_security_group_id = anytrue([var.vpc_name != null && var.security_group_id != "", var.security_group_id == ""]) + # tflint-ignore: terraform_unused_declarations + validate_security_group_id_chk = regex("^${local.validate_security_group_id_msg}$", + (local.validate_security_group_id ? local.validate_security_group_id_msg : "")) + +} diff --git a/tools/image-builder/locals.tf b/tools/image-builder/locals.tf new file mode 100644 index 00000000..5c9dbe7b --- /dev/null +++ b/tools/image-builder/locals.tf @@ -0,0 +1,341 @@ +locals { + # Defined values + name = "hpc-packer" + prefix = var.prefix + tags = [local.prefix, local.name] + no_addr_prefix = true + # Derived values + vpc_id = var.vpc_name == null ? module.landing_zone.vpc_data[0].vpc_id : data.ibm_is_vpc.existing_vpc[0].id + # Resource group calculation + # If user defined then use existing else create new + create_resource_group = var.resource_group == "null" ? true : false + resource_groups = var.resource_group == "null" ? [ + { + name = "${local.prefix}-service-rg", + create = local.create_resource_group, + use_prefix : false + }, + { + name = "${local.prefix}-workload-rg", + create = local.create_resource_group, + use_prefix : false + } + ] : [ + { + name = var.resource_group, + create = local.create_resource_group + } + ] + # For the variables looking for resource group names only (transit_gateway, key_management, atracker) + resource_group = var.resource_group == "null" ? "${local.prefix}-service-rg" : var.resource_group + region = join("-", slice(split("-", var.zones[0]), 0, 2)) + zones = ["zone-1", "zone-2", "zone-3"] + active_zones = [ + for zone in var.zones : + format("zone-%d", substr(zone, -1, -2)) + ] + packer_sg_variable_cidr_list = split(",", var.network_cidr) + address_prefixes = { + "zone-${element(split("-", var.zones[0]), 2)}" = [local.packer_sg_variable_cidr_list[0]] + } + + # Subnet calculation + active_subnets = { + for zone in local.zones : zone => contains(local.active_zones, zone) ? [ + zone == local.active_zones[0] ? { + name = "subnet" + acl_name = "hpc-acl" + cidr = var.packer_subnet_cidr[0] + public_gateway = true + no_addr_prefix = local.no_addr_prefix + } : null + ] : [] + } + subnets = { for zone, subnets in local.active_subnets : zone => [for each in subnets : each if each != null] } + + # Use public gateway calculation + use_public_gateways = { + for zone in local.zones : zone => contains(local.active_zones, zone) ? true : false + } + network_acl_inbound_rules = [ + { + name = "allow-all-inbound" + action = "allow" + destination = "0.0.0.0/0" + direction = "inbound" + source = "0.0.0.0/0" + } + ] + network_acl_outbound_rules = [ + { + name = "allow-all-outbound" + action = "allow" + destination = "0.0.0.0/0" + direction = "outbound" + source = "0.0.0.0/0" + } + ] + network_acl_rules = flatten([local.network_acl_inbound_rules, local.network_acl_outbound_rules]) + + use_public_gateways_existing_vpc = { + "zone-1" = false + "zone-2" = false + "zone-3" = false + } + + vpcs = [ + { + existing_vpc_id = var.vpc_name == null ? null : data.ibm_is_vpc.existing_vpc[0].id + existing_subnets = (var.vpc_name != null && var.subnet_id != null) ? [ + { + id = var.subnet_id + public_gateway = false + } + ] : null + prefix = local.name + resource_group = var.resource_group == "null" ? "${local.prefix}-workload-rg" : var.resource_group + clean_default_security_group = true + clean_default_acl = true + # flow_logs_bucket_name = var.enable_vpc_flow_logs ? "vpc-flow-logs-bucket" : null + network_acls = [ + { + name = "hpc-acl" + add_cluster_rules = false + rules = local.network_acl_rules + } + ], + subnets = (var.vpc_name != null && var.subnet_id != null) ? null : local.subnets + use_public_gateways = var.vpc_name == null ? local.use_public_gateways : local.use_public_gateways_existing_vpc + address_prefixes = var.vpc_name == null ? local.address_prefixes : null + } + ] + + # Define SSH key + ssh_keys = [ + for item in var.ssh_keys : { + name = item + } + ] + + security_groups = [] + vpc_name = var.vpc_name == null ? module.landing_zone.vpc_data[0].vpc_name : var.vpc_name + packer_node_name = format("%s-%s", local.prefix, "packer") + packer_machine_type = "bx2-2x8" + packer_image_id = data.ibm_is_image.packer.id + packer_ssh_keys = [for name in var.ssh_keys : data.ibm_is_ssh_key.packer[name].id] + kms_encryption_enabled = var.key_management == "key_protect" ? true : false + landing_zone_kms_output = var.key_management == "key_protect" ? (var.kms_key_name == null ? module.landing_zone.key_map[format("%s-vsi-key", var.prefix)] : module.landing_zone.key_map[var.kms_key_name]) : null + boot_volume_encryption_key = var.key_management == "key_protect" ? local.landing_zone_kms_output["crn"] : null + existing_kms_instance_guid = var.key_management == "key_protect" ? module.landing_zone.key_management_guid : null + landing_zone_subnet_output = [for subnet in flatten(module.landing_zone.subnet_data) : { + name = subnet["name"] + id = subnet["id"] + zone = subnet["zone"] + cidr = subnet["cidr"] + crn = subnet["crn"] + } + ] + existing_subnets = var.subnet_id != null ? [ + element(local.landing_zone_subnet_output, index([local.landing_zone_subnet_output[0].id], var.subnet_id)) + ] : [] + packer_subnets = var.subnet_id == null ? local.landing_zone_subnet_output : local.existing_subnets + security_group = { + name = "${local.prefix}-hpc-packer-sg" + rules = local.packer_security_group_rules + } + + packer_vsi_data = flatten(module.packer_vsi["list"]) + # packer_vsi_id = local.packer_vsi_data[0]["id"] + packer_vsi_name = local.packer_vsi_data[0]["name"] + packer_floating_ip = var.enable_fip ? local.packer_vsi_data[0]["floating_ip"] : null + + packer_resource_groups = { + service_rg = var.resource_group == "null" ? module.landing_zone.resource_group_data["${var.prefix}-service-rg"] : one(values(module.landing_zone.resource_group_data)) + workload_rg = var.resource_group == "null" ? module.landing_zone.resource_group_data["${var.prefix}-workload-rg"] : one(values(module.landing_zone.resource_group_data)) + } + + vsi = [] + + # Define VPN + vpn_gateways = var.enable_vpn ? [ + { + name = "packer-vpn-gw" + vpc_name = local.name + subnet_name = (var.vpc_name != null && var.subnet_id != null) ? data.ibm_is_subnet.existing_subnet[0].name : "subnet" + mode = "policy" + resource_group = var.resource_group == "null" ? "${local.prefix}-service-rg" : var.resource_group + } + ] : [] + + # Define transit gateway (to connect multiple VPC) + enable_transit_gateway = false + transit_gateway_resource_group = local.resource_group + transit_gateway_connections = [var.vpc_name] + + cos = [] + + active_keys = var.key_management == "key_protect" ? (var.kms_key_name == null ? [ + var.key_management == "key_protect" ? { + name = format("%s-vsi-key", var.prefix) + } : null + ] : [ + { + name = var.kms_key_name + existing_key_crn = data.ibm_kms_key.kms_key[0].keys[0].crn + } + ]) : null + key_management = var.key_management == "key_protect" ? { + name = var.kms_instance_name != null ? var.kms_instance_name : format("%s-kms", var.prefix) + resource_group = local.resource_group + use_hs_crypto = false + keys = [for each in local.active_keys : each if each != null] + use_data = var.kms_instance_name != null ? true : false + } : { + name = null + resource_group = null + use_hs_crypto = null + keys = [] + use_data = null + } + # Unexplored variables + + virtual_private_endpoints = [] + service_endpoints = "private" + atracker = { + resource_group = local.resource_group + receive_global_events = false + collector_bucket_name = "atracker-bucket" + add_route = false + } + secrets_manager = { + use_secrets_manager = false + } + access_groups = [] + f5_vsi = [] + #add_kms_block_storage_s2s = false + skip_kms_block_storage_s2s_auth_policy = true + clusters = [] + wait_till = "IngressReady" + teleport_vsi = [] + iam_account_settings = { + enable = false + } + teleport_config_data = { + domain = var.prefix + } + f5_template_data = { + license_type = "none" + } + appid = { + use_appid = false + } +} + +#################################################### +# The code below does some internal processing of variables and locals +# (e.g. concatenating lists). + +locals { + # (overridable) switch to enable extra outputs (debugging) + # print_extra_outputs = false + + # (overridable) switch to add the current (plan execution) IP to allowed CIDR list + # add_current_ip_to_allowed_cidr = false + + # (overridable) list of extra entries for allowed CIDR list + remote_allowed_ips_extra = [] +} + +locals { + allowed_cidr = concat(var.remote_allowed_ips, local.remote_allowed_ips_extra, []) + + rhel_subscription_cidrs = [ + "161.26.0.0/16", + "166.8.0.0/14" + ] + + packer_sg_variable_cidr = flatten([ + # local.schematics_reserved_cidrs, + local.allowed_cidr + # var.network_cidr + ]) + + packer_security_group_rules = flatten([ + { + name = "allow-all-outbound-outbound" + direction = "outbound" + source = "0.0.0.0/0" + }, + [for cidr in local.packer_sg_variable_cidr : { + name = format("allow-variable-inbound-%s", index(local.packer_sg_variable_cidr, cidr) + 1) + direction = "inbound" + source = cidr + # ssh port + tcp = { + port_min = 22 + port_max = 22 + } + }], + [for cidr in local.packer_sg_variable_cidr : { + name = format("allow-variable-outbound-%s", index(local.packer_sg_variable_cidr, cidr) + 1) + direction = "outbound" + source = cidr + }], + [for cidr in local.packer_sg_variable_cidr_list : { + name = format("allow-variable-inbound-cidr-%s", index(local.packer_sg_variable_cidr_list, cidr) + 1) + direction = "inbound" + source = cidr + tcp = { + port_min = 22 + port_max = 22 + } + }], + [for cidr in local.packer_sg_variable_cidr_list : { + name = format("allow-variable-outbound-cidr-%s", index(local.packer_sg_variable_cidr_list, cidr) + 1) + direction = "outbound" + source = cidr + }], + [for cidr in local.rhel_subscription_cidrs : { + name = format("allow-variable-outbound-cidr-rhel-%s", index(local.rhel_subscription_cidrs, cidr) + 1) + direction = "outbound" + source = cidr + }], + [for cidr in local.rhel_subscription_cidrs : { + name = format("allow-variable-inbound-cidr-rhel-%s", index(local.rhel_subscription_cidrs, cidr) + 1) + direction = "inbound" + source = cidr + }] + ]) +} + +# env variables (use to override) +locals { + env = { + resource_groups = local.resource_groups + network_cidr = var.network_cidr + vpcs = local.vpcs + vpn_gateways = local.vpn_gateways + enable_transit_gateway = local.enable_transit_gateway + transit_gateway_resource_group = local.transit_gateway_resource_group + transit_gateway_connections = local.transit_gateway_connections + vsi = local.vsi + ssh_keys = local.ssh_keys + cos = local.cos + key_management = local.key_management + atracker = local.atracker + security_groups = local.security_groups + virtual_private_endpoints = local.virtual_private_endpoints + service_endpoints = local.service_endpoints + skip_kms_block_storage_s2s_auth_policy = local.skip_kms_block_storage_s2s_auth_policy + clusters = local.clusters + wait_till = local.wait_till + iam_account_settings = local.iam_account_settings + access_groups = local.access_groups + f5_vsi = local.f5_vsi + f5_template_data = local.f5_template_data + appid = local.appid + teleport_config_data = local.teleport_config_data + teleport_vsi = local.teleport_vsi + secrets_manager = local.secrets_manager + } +} diff --git a/tools/image-builder/main.tf b/tools/image-builder/main.tf new file mode 100644 index 00000000..65396615 --- /dev/null +++ b/tools/image-builder/main.tf @@ -0,0 +1,98 @@ +module "landing_zone" { + source = "terraform-ibm-modules/landing-zone/ibm" + version = "5.27.0" + prefix = local.prefix + region = local.region + tags = local.tags + resource_groups = local.env.resource_groups + network_cidr = local.env.network_cidr + vpcs = local.env.vpcs + vpn_gateways = local.env.vpn_gateways + enable_transit_gateway = local.env.enable_transit_gateway + transit_gateway_resource_group = local.env.transit_gateway_resource_group + transit_gateway_connections = local.env.transit_gateway_connections + ssh_keys = local.env.ssh_keys + vsi = local.env.vsi + security_groups = local.env.security_groups + virtual_private_endpoints = local.env.virtual_private_endpoints + cos = local.env.cos + service_endpoints = local.env.service_endpoints + key_management = local.env.key_management + skip_kms_block_storage_s2s_auth_policy = local.env.skip_kms_block_storage_s2s_auth_policy + atracker = local.env.atracker + clusters = local.env.clusters + wait_till = local.env.wait_till + f5_vsi = local.env.f5_vsi + f5_template_data = local.env.f5_template_data + appid = local.env.appid + teleport_config_data = local.env.teleport_config_data + teleport_vsi = local.env.teleport_vsi +} + +# # Code for Public Gateway attachment for the existing vpc and new subnets scenario + +data "ibm_is_public_gateways" "public_gateways" { +} + +locals { + public_gateways_list = data.ibm_is_public_gateways.public_gateways.public_gateways + zone_1_pgw_ids = var.vpc_name != null ? [for gateway in local.public_gateways_list : gateway.id if gateway.vpc == local.vpc_id && gateway.zone == var.zones[0]] : [] +} + +resource "ibm_is_subnet_public_gateway_attachment" "zone_1_attachment" { + count = (var.vpc_name != null && var.subnet_id == null) ? 1 : 0 + subnet = module.landing_zone.subnet_data[0].id + public_gateway = length(local.zone_1_pgw_ids) > 0 ? local.zone_1_pgw_ids[0] : "" +} + +resource "null_resource" "compress_and_encode_folder" { + provisioner "local-exec" { + command = < /dev/null +} + +# Installation of S3fs packages for mounting the cos buckets +if grep -q 'ID="rhel"' /etc/os-release || grep -q 'ID="rocky"' /etc/os-release; then + sudo rpm --import https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-8 + sudo dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm + install_with_retry "sudo yum install -y s3fs-fuse" 3 + rpm -qa | grep epel-release + rpm -qa | grep s3fs-fuse + yum install -y python3.11 python3.11-pip ed wget gcc-c++ gcc-gfortran kernel-devel-"$(uname -r)" perl libnsl openldap-clients nss-pam-ldapd sssd tar + useradd lsfadmin + echo 'lsfadmin ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers #pragma: allowlist secret + rm -f /usr/bin/python3 + rm -rf /bin/pip3 + ln -s /usr/bin/python3.11 /usr/bin/python3 + ln -s /usr/bin/pip3.11 /bin/pip3 + python3 --version + pip3 -V +else + sudo add-apt-repository -y ppa:deadsnakes/ppa; + sudo apt update + disable_cnf_update_db_hook + add_sysdig_gpg_key + apt install -y s3fs + dpkg -l | grep s3fs + apt install -y python3.11 g++ libelf-dev linux-headers-"$(uname -r)" gfortran libopenmpi-dev python3-pip sssd libpam-sss libnss-sss + rm -f /usr/bin/python3 + ln -s /usr/bin/python3.11 /usr/bin/python3 + python3 --version + pip3 -V + adduser --disabled-password --gecos "" lsfadmin + echo 'lsfadmin ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers #pragma: allowlist secret + usermod -s /bin/bash lsfadmin +fi + +# LSF prerequisites packages required for configuring and installing LSF +LSF_TOP="/opt/ibm/lsf" +LSF_CONF_PATH="${LSF_TOP}/conf" +LSF_PACKAGES_PATH="/tmp/packages" +echo $LSF_PACKAGES_PATH +mkdir -p ${LSF_TOP} +chmod -R 755 /opt + +echo "======================Triggering mounting of Cos Bucket=====================" +mkdir /wes-hpc +s3fs custom-image-builder /wes-hpc -o url=https://s3.direct.us-south.cloud-object-storage.appdomain.cloud -o ro -o public_bucket=1 +mkdir -p /tmp/packages +cp -r /wes-hpc/hpcaas/base/latest/* /tmp/packages/ +ls -ltr /tmp/packages/ +echo "======================Cos Bucket mounting completed=====================" + +sleep 100 + +echo "======================Installation of IBMCloud Plugins started=====================" +curl -fsSL https://clis.cloud.ibm.com/install/linux | sh +pip3 install ibm-vpc==0.10.0 +pip3 install ibm-cloud-networking-services ibm-cloud-sdk-core selinux +ibmcloud plugin install vpc-infrastructure DNS +echo 'LS_Standard 10.1 () () () () 18b1928f13939bd17bf25e09a2dd8459f238028f' > ${LSF_PACKAGES_PATH}/ls.entitlement +echo 'LSF_Standard 10.1 () () () pa 3f08e215230ffe4608213630cd5ef1d8c9b4dfea' > ${LSF_PACKAGES_PATH}/lsf.entitlement +echo "======================Installation of IBMCloud Plugins completed=====================" + + +hostname lsfservers +# Installation of LSF base packages on compute node +cd "${LSF_PACKAGES_PATH}" || exit +zcat lsf*lsfinstall_linux_x86_64.tar.Z | tar xvf - +cd lsf*_lsfinstall || exit +sed -e '/show_copyright/ s/^#*/#/' -i lsfinstall +cat <> install.config +LSF_TOP="/opt/ibm/lsf" +LSF_ADMINS="lsfadmin" +LSF_CLUSTER_NAME="HPCCluster" +LSF_MASTER_LIST="lsfservers" +LSF_ENTITLEMENT_FILE="${LSF_PACKAGES_PATH}/lsf.entitlement" +CONFIGURATION_TEMPLATE="DEFAULT" +ENABLE_DYNAMIC_HOSTS="Y" +ENABLE_EGO="N" +ACCEPT_LICENSE="Y" +SILENT_INSTALL="Y" +LSF_SILENT_INSTALL_TARLIST="ALL" +EOT +bash lsfinstall -f install.config +echo $? +cat Install.log +echo "========================LSF 10.1 installation completed=====================" + + +hostname lsfservers +# Installation of Resource connector configuration on compute nodes +cd "${LSF_PACKAGES_PATH}" || exit +cd lsf*_lsfinstall || exit +cat <> server.config +LSF_TOP="/opt/ibm/lsf_worker" +LSF_ADMINS="lsfadmin" +LSF_ENTITLEMENT_FILE="${LSF_PACKAGES_PATH}/lsf.entitlement" +LSF_SERVER_HOSTS="lsfservers" +LSF_LOCAL_RESOURCES="[resource cloudhpchost]" +ACCEPT_LICENSE="Y" +SILENT_INSTALL="Y" +EOT +bash lsfinstall -s -f server.config +echo $? +cat Install.log +rm -rf /opt/ibm/lsf_worker/10.1 +ln -s /opt/ibm/lsf/10.1 /opt/ibm/lsf_worker +echo "==================LSF 10.1 Resource connector installation completed===============" + + +# Installation Of OpenMPI +cd "${LSF_PACKAGES_PATH}" || exit +wget https://download.open-mpi.org/release/open-mpi/v4.1/openmpi-4.1.0.tar.gz +tar -xvf openmpi-4.1.0.tar.gz +cd openmpi-4.1.0 || exit +ln -s /usr/lib64/libnsl.so.2.0.0 /usr/lib64/libnsl.so +export LANG=C +./configure --prefix='/usr/local/openmpi-4.1.0' --enable-mpi-thread-multiple --enable-shared --disable-static --enable-mpi-fortran=usempi --disable-libompitrace --enable-script-wrapper-compilers --enable-wrapper-rpath --enable-orterun-prefix-by-default --with-io-romio-flags=--with-file-system=nfs --with-lsf=/opt/ibm/lsf/10.1 --with-lsf-libdir=/opt/ibm/lsf/10.1/linux3.10-glibc2.17-x86_64/lib +make -j 32 +make install +find /usr/local/openmpi-4.1.0/ -type d -exec chmod 775 {} \; +echo "======================OneMPI installation completed=====================" + +# Intel One API (hpckit) installation based on the Operating system +if grep -q 'ID="rhel"' /etc/os-release || grep -q 'ID="rocky"' /etc/os-release; then + # For RHEL-based systems + cat << EOF | sudo tee /etc/yum.repos.d/oneAPI.repo +[oneAPI] +name=IntelĀ® oneAPI repository +baseurl=https://yum.repos.intel.com/oneapi +enabled=1 +gpgcheck=1 +repo_gpgcheck=1 +gpgkey=https://yum.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS.PUB +EOF + sudo yum install -y intel-basekit intel-hpckit + rpm -qa | grep -E "intel-hpckit|intel-basekit" + sudo rm -rf /etc/yum.repos.d/oneAPI.repo + ls /etc/yum.repos.d + # Updating security check + yum update --security -y +else + # For Ubuntu-based systems + wget -O- https://apt.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS.PUB | gpg --dearmor | sudo tee /usr/share/keyrings/oneapi-archive-keyring.gpg > /dev/null + echo "deb [signed-by=/usr/share/keyrings/oneapi-archive-keyring.gpg] https://apt.repos.intel.com/oneapi all main" | sudo tee /etc/apt/sources.list.d/oneAPI.list + sudo apt update + sudo apt install -y intel-basekit intel-hpckit + sudo rm -rf /etc/apt/sources.list.d/oneAPI.list + ls /etc/apt/sources.list.d + # Verify the installation + dpkg -l | grep -E "intel-basekit|intel-hpckit" + sudo apt update && sudo apt upgrade -y + sudo apt install -y nfs-common build-essential +fi + +# Setting up access to the appropriate path +mv -f ${LSF_PACKAGES_PATH}/*.entitlement /opt/ibm/lsf/conf +chown -R lsfadmin:root ${LSF_CONF_PATH} + +echo "${INSTALL_SYSDIG}" +if [ "${INSTALL_SYSDIG}" = true ]; then + # Installation of Sysdig Agent on compute nodes + echo "Installation of Sysdig Agent started" + curl -sL https://ibm.biz/install-sysdig-agent | sudo bash -s -- --access_key ==ACCESSKEY== --collector ==COLLECTOR== --collector_port 6443 --secure true --check_certificate false --additional_conf 'sysdig_capture_enabled: false\nremotefs: true\nfeature:\n mode: monitor_light' + systemctl stop dragent + systemctl disable dragent +else + echo "INSTALL_SYDIG is set as false and the sysdig agent is not installed on compute node image" +fi + +# Security approach to delete unwanted ssh keys and host file entries +rm -rf "${LSF_PACKAGES_PATH}" +if grep -q 'ID="rhel"' /etc/os-release || grep -q 'ID="rocky"' /etc/os-release; then + rm -rf /home/vpcuser/.ssh/authorized_keys + rm -rf /home/vpcuser/.ssh/known_hosts + rm -rf /home/vpcuser/.ssh/id_rsa* +else + rm -rf /home/ubuntu/.ssh/authorized_keys + rm -rf /home/ubuntu/.ssh/known_hosts + rm -rf /home/ubuntu/.ssh/id_rsa* +fi + rm -rf /home/lsfadmin/.ssh/authorized_keys + rm -rf /home/lsfadmin/.ssh/known_hosts + rm -rf /home/lsfadmin/.ssh/id_rsa* + rm -rf /root/.ssh/authorized_keys + rm -rf /root/.ssh/known_hosts + rm -rf /root/.ssh/id_rsa* + systemctl stop syslog + rm -rf /var/log/messages + rm -rf /root/.bash_history + history -c diff --git a/tools/image-builder/packer/hpcaas/compute/variables.pkr.hcl b/tools/image-builder/packer/hpcaas/compute/variables.pkr.hcl new file mode 100644 index 00000000..3b8650ef --- /dev/null +++ b/tools/image-builder/packer/hpcaas/compute/variables.pkr.hcl @@ -0,0 +1,48 @@ +variable "ibm_api_key" { + type = string + description = "IBM Cloud API key." +} + +variable "vpc_region" { + type = string + description = "The region where IBM Cloud operations will take place. Examples are us-east, us-south, etc." +} + +variable "resource_group_id" { + type = string + description = "The existing resource group id." +} + +variable "vpc_subnet_id" { + type = string + description = "The subnet ID to use for the instance." +} + +variable "image_name" { + type = string + default = "hpc-base-v2" + description = "The name of the resulting custom image. To make this unique, timestamp will be appended." +} + +variable "vsi_profile" { + type = string + default = "bx2d-2x8" + description = "The IBM Cloud vsi type to use while building the AMI." +} + +variable "source_image_name" { + type = string + description = "The source image name whose root volume will be copied and provisioned on the currently running instance." +} + +variable "install_sysdig" { + type = bool + default = false + description = "Set the value as true to install sysdig agent on the compute node images." +} + +variable "security_group_id" { + type = string + default = null + description = "The security group identifier to use. If not specified, IBM packer plugin creates a new temporary security group to allow SSH and WinRM access." +} diff --git a/tools/image-builder/provider.tf b/tools/image-builder/provider.tf new file mode 100644 index 00000000..73d31835 --- /dev/null +++ b/tools/image-builder/provider.tf @@ -0,0 +1,4 @@ +provider "ibm" { + ibmcloud_api_key = var.ibmcloud_api_key + region = local.region +} diff --git a/tools/image-builder/template_files.tf b/tools/image-builder/template_files.tf new file mode 100644 index 00000000..30874ad7 --- /dev/null +++ b/tools/image-builder/template_files.tf @@ -0,0 +1,23 @@ +data "template_file" "packer_user_data" { + template = file("${path.module}/templates/packer_user_data.tpl") + vars = { + ibm_api_key = var.ibmcloud_api_key + vpc_region = local.region + vpc_id = local.vpc_id + vpc_subnet_id = (var.vpc_name != null && var.subnet_id != null) ? var.subnet_id : local.landing_zone_subnet_output[0].id + resource_group_id = local.packer_resource_groups["workload_rg"] + source_image_name = var.source_image_name # Source_image_name_from_images_of_VPC + image_name = var.image_name # image_name_for_newly_created_custom_image + install_sysdig = var.install_sysdig + security_group_id = var.security_group_id + encoded_compute = data.local_file.encoded_compute_content.content + target_dir = "/var" + prefix = var.prefix + cluster_id = var.cluster_id + reservation_id = var.reservation_id + catalog_validate_ssh_key = var.ssh_keys[0] + zones = join(",", var.zones) + resource_group = var.resource_group + private_catalog_id = var.private_catalog_id + } +} diff --git a/tools/image-builder/templates/packer_user_data.tpl b/tools/image-builder/templates/packer_user_data.tpl new file mode 100644 index 00000000..9ed0385a --- /dev/null +++ b/tools/image-builder/templates/packer_user_data.tpl @@ -0,0 +1,140 @@ +#!/usr/bin/bash + +################################################### +# Copyright (C) IBM Corp. 2023 All Rights Reserved. +# Licensed under the Apache License v2.0 +################################################### + +# Install required packages + +export HOME=/root # Setting this as a path because got to see error on user data scripts, we can revisit this logic later + +sudo yum install -y wget unzip jq + +# Decode and extract the base64-encoded content + +sudo mkdir -p ${target_dir} +echo "Decoding and saving base64-encoded content [necessary to retrieve the original compressed file]" +echo "${encoded_compute}" | base64 -d > ${target_dir}/compressed_compute.tar.gz + +# Extract the tar.gz file +echo "Unpacking the contents of the compressed file" +tar -xzf ${target_dir}/compressed_compute.tar.gz -C ${target_dir} +rm -f ${target_dir}/compressed_compute.tar.gz + +echo "Packer installation started" + +# Download and unzip Packer +packer_version=$(curl -s https://checkpoint-api.hashicorp.com/v1/check/packer | jq -r .current_version) +wget https://releases.hashicorp.com/packer/"$packer_version"/packer_"$packer_version"_linux_amd64.zip +unzip packer_"$packer_version"_linux_amd64.zip + +# Move Packer to /usr/local/bin +sudo mv packer /usr/local/bin/ + +# Create a symlink to /usr/sbin +sudo ln -sf /usr/local/bin/packer /usr/sbin/packer + +install_with_retry() { + local cmd="$1" + local retries="$2" + local count=0 + + until $cmd || [ $count -eq "$retries" ]; do + echo "Installation failed. Retrying..." + sleep 5 # Adjust sleep duration between retries as needed + count=$((count + 1)) + done + + if [ $count -eq "$retries" ]; then + echo "Failed to install after $retries attempts. Exiting." + exit 1 + fi +} + +echo $'***** Installing s3fs *****\n' +sudo rpm --import https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-8 +sudo dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm +install_with_retry "sudo yum install -y s3fs-fuse" 3 +rpm -qa | grep epel-release +rpm -qa | grep s3fs-fuse + +echo "======================Cloning HPC public repo=====================" + +sudo yum install git -y +mkdir /HPCaaS +cd /HPCaaS +git clone https://github.com/terraform-ibm-modules/terraform-ibm-hpc.git +cd /HPCaaS/terraform-ibm-hpc/solutions/hpc + +echo "======================Installing terraform=====================" +git clone --depth=1 https://github.com/tfutils/tfenv.git ~/.tfenv +echo "export PATH=$PATH:$HOME/.tfenv/bin" >> ~/.bashrc +ln -s ~/.tfenv/bin/* /usr/local/bin +tfenv install latest +tfenv use latest +terraform --version + +echo "====================== Triggering mounting of Cos Bucket =====================" +mkdir /wes-hpc +s3fs custom-image-builder /wes-hpc -o url=https://s3.direct.us-south.cloud-object-storage.appdomain.cloud -o ro -o public_bucket=1 +mkdir -p /HPCaaS/terraform-ibm-hpc/tools/tests +cp -r /wes-hpc/tests/* /HPCaaS/terraform-ibm-hpc/tools/tests/ +ls -ltr /HPCaaS/terraform-ibm-hpc/tools/tests/ +echo "====================== Cos Bucket mounting completed =====================" + +cd /var/packer/hpcaas/compute + +sudo -E packer init . && sudo -E packer build \ + -var "ibm_api_key=${ibm_api_key}" \ + -var "vpc_region=${vpc_region}" \ + -var "resource_group_id=${resource_group_id}" \ + -var "vpc_subnet_id=${vpc_subnet_id}" \ + -var "source_image_name=${source_image_name}" \ + -var "install_sysdig=${install_sysdig}" \ + -var "security_group_id=${security_group_id}" \ + -var "image_name=${image_name}" . + +echo "========== Generating SSH key =========" +mkdir -p /HPCaaS/artifacts/.ssh +ssh-keygen -t rsa -N '' -f /HPCaaS/artifacts/.ssh/id_rsa <<< y + +RANDOM_SUFFIX=$(head /dev/urandom | tr -dc 'a-z' | head -c 4) +CICD_SSH_KEY="hpc-packer-$RANDOM_SUFFIX" + +PACKER_FIP=$(curl -s ifconfig.io) + +echo "========== Installing IBM cloud CLI =========" +curl -fsSL https://clis.cloud.ibm.com/install/linux | sh +ibmcloud plugin install infrastructure-service +ibmcloud login --apikey ${ibm_api_key} -r ${vpc_region} +echo "========== Uploading SSH key to IBM cloud =========" +ibmcloud is key-create $CICD_SSH_KEY @/HPCaaS/artifacts/.ssh/id_rsa.pub --resource-group-name ${resource_group} + +cd /HPCaaS/terraform-ibm-hpc/tools/tests +git submodule update --init + +sudo yum update -y + +echo "***** Installing Golang *****" + +if [ ! -d "$(pwd)/go" ]; then + wget https://go.dev/dl/go1.23.1.linux-amd64.tar.gz + tar -C $(pwd)/ -xzf go1.23.1.linux-amd64.tar.gz + echo "export PATH=\$PATH:$(pwd)/go/bin:\$HOME/go/bin" >> ~/.bashrc + echo "export GOROOT=$(pwd)/go" >> ~/.bashrc + source ~/.bashrc +fi + +echo "========== Executing Go function to validate the image through HPC deployment =========" +export TF_VAR_ibmcloud_api_key=${ibm_api_key} + +if [ "${private_catalog_id}" ]; then + PREFIX=${prefix} CLUSTER_ID=${cluster_id} RESERVATION_ID=${reservation_id} SSH_FILE_PATH="/HPCaaS/artifacts/.ssh/id_rsa" REMOTE_ALLOWED_IPS=$PACKER_FIP SSH_KEYS=$CICD_SSH_KEY CATALOG_VALIDATE_SSH_KEY=${catalog_validate_ssh_key} ZONES=${zones} RESOURCE_GROUP=${resource_group} COMPUTE_IMAGE_NAME=${image_name} PRIVATE_CATALOG_ID=${private_catalog_id} VPC_ID=${vpc_id} SUBNET_ID=${vpc_subnet_id} SOURCE_IMAGE_NAME=${source_image_name} go test -v -timeout 900m -parallel 4 -run "TestRunHpcDeploymentForCustomImageBuilder" | tee hpc_log_$(date +%d-%m-%Y-%H-%M-%S).log +else + PREFIX=${prefix} CLUSTER_ID=${cluster_id} RESERVATION_ID=${reservation_id} SSH_FILE_PATH="/HPCaaS/artifacts/.ssh/id_rsa" REMOTE_ALLOWED_IPS=$PACKER_FIP SSH_KEYS=$CICD_SSH_KEY ZONES=${zones} RESOURCE_GROUP=${resource_group} COMPUTE_IMAGE_NAME=${image_name} SOURCE_IMAGE_NAME=${source_image_name} go test -v -timeout 900m -parallel 4 -run "TestRunHpcDeploymentForCustomImageBuilder" | tee hpc_log_$(date +%d-%m-%Y-%H-%M-%S).log +fi + +echo "========== Deleting the SSH key =========" + +ibmcloud is key-delete $CICD_SSH_KEY -f diff --git a/tools/image-builder/variables.tf b/tools/image-builder/variables.tf new file mode 100644 index 00000000..b4a63fe5 --- /dev/null +++ b/tools/image-builder/variables.tf @@ -0,0 +1,216 @@ +############################################################################## +# Account Variables +############################################################################## + +variable "ibmcloud_api_key" { + description = "IBM Cloud API key for the IBM Cloud account where the IBM Cloud HPC cluster needs to be deployed. For more information on how to create an API key, see [Managing user API keys](https://cloud.ibm.com/docs/account?topic=account-userapikey)." + type = string + sensitive = true + validation { + condition = var.ibmcloud_api_key != "" + error_message = "The API key for IBM Cloud must be set." + } +} + +############################################################################## +# Resource Groups Variables +############################################################################## + +variable "resource_group" { + description = "Specify the existing resource group name from your IBM Cloud account where the VPC resources should be deployed. By default, the resource group name is set to 'Default.' Note that in some older accounts, the resource group name may be 'default,' so please validate the resource_group name before deployment. If the resource group value is set to the string \"null\", the automation will create two different resource groups named 'workload-rg' and 'service-rg.' For more information on resource groups, refer to Managing resource groups." + type = string + default = "Default" + validation { + condition = var.resource_group != null + error_message = "If you want to provide null for resource_group variable, it should be within double quotes." + } +} + +############################################################################## +# Module Level Variables +############################################################################## + +variable "prefix" { + description = "A unique identifier for resources. Must begin with a letter and end with a letter or number. This prefix will be prepended to any resources provisioned by this template. Prefixes must be 16 or fewer characters." + type = string + validation { + error_message = "Prefix must start with a lowercase letter and contain only lowercase letters, digits, and hyphens in between. Hyphens must be followed by at least one lowercase letter or digit. There are no leading, trailing, or consecutive hyphens." + condition = can(regex("^[a-z](?:[a-z0-9]*(-[a-z0-9]+)*)?$", var.prefix)) + } + validation { + condition = length(var.prefix) <= 16 + error_message = "The prefix must be 16 characters or fewer." + } +} + +variable "zones" { + description = "The IBM Cloud zone name within the selected region where the infrastructure for image creation cluster should be deployed and requires a single zone input value. Supported regions are: eu-de, us-east and us-south.[Learn more](https://cloud.ibm.com/docs/vpc?topic=vpc-creating-a-vpc-in-a-different-region#get-zones-using-the-cli)." + type = list(string) + default = ["us-east-1"] + validation { + condition = length(var.zones) == 1 + error_message = "Image builder deployment supports only a single zone. Supported regions are: eu-de, us-east and us-south." + } +} + +############################################################################## +# VPC Variables +############################################################################## + +variable "vpc_name" { + type = string + description = "Name of an existing VPC in which the cluster resources will be deployed. If no value is given, then a new VPC will be provisioned for the cluster. [Learn more](https://cloud.ibm.com/docs/vpc)" + default = null +} + +variable "subnet_id" { + type = string + default = null + description = "Existing subnet ID under the VPC, where the packer VSI will be provisioned." +} + +variable "network_cidr" { + description = "Creates the address prefix for the new VPC, when the vpc_name variable is empty. The VPC requires an address prefix for creation of subnet in a single zone." + type = string + default = "10.241.0.0/18" +} + +variable "ssh_keys" { + type = list(string) + description = "Provide the list of SSH key names configured in your IBM Cloud account to establish a connection to the IBM Cloud packer node. Ensure the SSH key is present in the same resource group and region where the cluster is being provisioned. If you do not have an SSH key in your IBM Cloud account, create one by following the provided instructions.[SSH Keys](https://cloud.ibm.com/docs/vpc?topic=vpc-ssh-keys)." +} + +############################################################################## +# Access Variables +############################################################################## + +variable "packer_subnet_cidr" { + type = list(string) + default = ["10.241.16.0/28"] + description = "Provide the CIDR block required for the creation of the packer cluster's subnet. Only one CIDR block is needed. If using a hybrid environment, modify the CIDR block to avoid conflicts with any on-premises CIDR blocks. Since the packer subnet is used only for the creation of packer instances, provide a CIDR range of /28." + validation { + condition = length(var.packer_subnet_cidr) <= 1 + error_message = "Only a single zone is supported to deploy resources. Provide a CIDR range of subnet creation." + } + validation { + condition = tonumber(regex("/(\\d+)", join(",", var.packer_subnet_cidr))[0]) <= 28 + error_message = "This subnet is used to create only packer instances. Providing a larger CIDR size will waste the usage of available IPs. A CIDR range of /28 is sufficient for the creation of the packer subnet." + } +} + +############################################################################## +# Encryption Variables +############################################################################## + +variable "key_management" { + type = string + default = "key_protect" + description = "Set the value as key_protect to enable customer managed encryption for boot volume and file share. If the key_management is set as null, IBM Cloud resources will be always be encrypted through provider managed." + validation { + condition = var.key_management == "null" || var.key_management == null || var.key_management == "key_protect" + error_message = "key_management must be either 'null' or 'key_protect'." + } +} + +variable "kms_instance_name" { + type = string + default = null + description = "Provide the name of the existing Key Protect instance associated with the Key Management Service. Note: To use existing kms_instance_name set key_management as key_protect. The name can be found under the details of the KMS, see [View key-protect ID](https://cloud.ibm.com/docs/key-protect?topic=key-protect-retrieve-instance-ID&interface=ui)." +} + +variable "kms_key_name" { + type = string + default = null + description = "Provide the existing KMS encryption key name that you want to use for the IBM Cloud HPC cluster. (for example kms_key_name: my-encryption-key)." +} + +variable "skip_iam_authorization_policy" { + type = bool + default = false + description = "Set to false if authorization policy is required for VPC block storage volumes to access kms. This can be set to true if authorization policy already exists. For more information on how to create authorization policy manually, see [creating authorization policies for block storage volume](https://cloud.ibm.com/docs/vpc?topic=vpc-block-s2s-auth&interface=ui)." +} + +variable "remote_allowed_ips" { + type = list(string) + description = "Comma-separated list of IP addresses that can access the IBM Cloud HPC cluster instance through an SSH interface. For security purposes, provide the public IP addresses assigned to the devices that are authorized to establish SSH connections (for example, [\"169.45.117.34\"]). To fetch the IP address of the device, use [https://ipv4.icanhazip.com/](https://ipv4.icanhazip.com/)." + validation { + condition = alltrue([ + for o in var.remote_allowed_ips : !contains(["0.0.0.0/0", "0.0.0.0"], o) + ]) + error_message = "For security, provide the public IP addresses assigned to the devices authorized to establish SSH connections. Use https://ipv4.icanhazip.com/ to fetch the ip address of the device." + } + validation { + condition = alltrue([ + for a in var.remote_allowed_ips : can(regex("^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(/(3[0-2]|2[0-9]|1[0-9]|[0-9]))?$", a)) + ]) + error_message = "The provided IP address format is not valid. Check if the IP address contains a comma instead of a dot, and ensure there are double quotation marks between each IP address range if using multiple IP ranges. For multiple IP address, use the format [\"169.45.117.34\",\"128.122.144.145\"]." + } +} + +variable "image_name" { + type = string + description = "Image name for newly created custom image." +} + +variable "source_image_name" { + type = string + default = "ibm-redhat-8-8-minimal-amd64-5" + description = "Provide the base stock image available in IBM Cloud that will be used as the foundation for creating a custom image." + + validation { + condition = can(regex("^(ibm-ubuntu-22-04-4-minimal-amd64-|ibm-redhat-8-8-minimal-amd64-|ibm-rocky-linux-8-10-minimal-amd64-)", var.source_image_name)) + error_message = "We provide support for the following source images: Ubuntu 22.04, RHEL 8.8, and Rocky Linux 8.10." + } +} + +variable "install_sysdig" { + type = bool + default = false + description = "Set to true to install the Sysdig agent on the created image." +} + +variable "security_group_id" { + type = string + default = "" + description = "The security group identifier to use. If not specified, IBM packer plugin creates a new temporary security group to allow SSH and WinRM access." +} + +variable "enable_vpn" { + type = bool + default = false + description = "Set the value as true to deploy a VPN gateway for VPC in the cluster." +} + +variable "enable_fip" { + type = bool + default = true + description = "If connecting to the Packer deployment via Floating IP, set this value to true." +} + +# tflint-ignore: terraform_unused_declarations +variable "cluster_id" { + type = string + description = "Ensure that you have received the cluster ID from IBM technical sales. A unique identifer for HPC cluster used by IBM Cloud HPC to differentiate different HPC clusters within the same reservations. This can be up to 39 alphanumeric characters including the underscore (_), the hyphen (-), and the period (.) characters. You cannot change the cluster ID after deployment." + validation { + condition = 0 < length(var.cluster_id) && length(var.cluster_id) < 40 && can(regex("^[a-zA-Z0-9_.-]+$", var.cluster_id)) + error_message = "The Cluster ID can be up to 39 alphanumeric characters including the underscore (_), the hyphen (-), and the period (.) characters. Other special characters and spaces are not allowed." + } +} + +# tflint-ignore: terraform_unused_declarations +variable "reservation_id" { + type = string + sensitive = true + description = "Ensure that you have received the reservation ID from IBM technical sales. Reservation ID is a unique identifier to distinguish different IBM Cloud HPC service agreements. It must start with a letter and can only contain letters, numbers, hyphens (-), or underscores (_)." + validation { + condition = can(regex("^[a-zA-Z][a-zA-Z0-9-_]*$", var.reservation_id)) + error_message = "Reservation ID must start with a letter and can only contain letters, numbers, hyphens (-), or underscores (_)." + } +} + +# tflint-ignore: terraform_unused_declarations +variable "private_catalog_id" { + type = string + default = "" + description = "Provide the private catalog ID if you wish to publish and share the created image to the CE account." +} diff --git a/tools/image-builder/version.tf b/tools/image-builder/version.tf new file mode 100644 index 00000000..c02c9667 --- /dev/null +++ b/tools/image-builder/version.tf @@ -0,0 +1,21 @@ +terraform { + required_version = ">= 1.3" + required_providers { + ibm = { + source = "IBM-Cloud/ibm" + version = "1.69.2" + } + null = { + source = "hashicorp/null" + version = "3.2.3" + } + template = { + source = "hashicorp/template" + version = "2.2.0" + } + local = { + source = "hashicorp/local" + version = "2.5.1" + } + } +}