Using Terraform to create custom linux image on OCI

I’m trying to build a custom linux image with Terraform OCI provider and launch a VM.Standard.E2.1.Micro amd64 instance with it. The terraform plan & terraform apply seem to run to completion without errors but when I try to ssh into the instance I only get connection timed out:

$ ssh -o StrictHostKeyChecking=no fedora@XXX.XXX.XXX.XXX
ssh: connect to host XXX.XXX.XXX.XXX port 22: Connection timed out

I’m following the OCI docs about bring your own image, and importing custom linux images.

Skipping parts irrelevat to custom image (creating compartment, vcn etc etc), my process follows the outlined steps:

  1. Download Fedora Cloud Base 41 to the current directory.
  2. Get the object storage namespace
  3. Get the schema for compute capabilities.
  4. Create object storage bucked in earlier retrived namespace.
  5. Use a pre-authenticated request and null_resource to upload Fedora image to the bucket.
  6. Import the image, define its capabilities.
  7. Create a compute with the imported image.

The relevant parts of Terraform files:

locals {
  fedora_image            = "Fedora-Cloud-Base-Generic-41-1.4.x86_64.qcow2"
  object_storage_endpoint = format("https://objectstorage.%s.oraclecloud.com", var.region_ocid)
  ssh_key                 = file(var.ssh_public_key_path)
}

# compartment

resource "oci_identity_compartment" "snafu" {
  compartment_id = var.tenancy_ocid
  name           = var.compartment_name
  description    = "experimentation und mischief"

  enable_delete = true
}

# networking

resource "oci_core_vcn" "snafu_vcn" {
  compartment_id = oci_identity_compartment.snafu.id
  cidr_blocks    = var.vcn_cidr_block

  display_name = var.vcn_display_name
}

resource "oci_core_subnet" "snafu_public_subnet" {
  compartment_id = oci_identity_compartment.snafu.id
  vcn_id         = oci_core_vcn.snafu_vcn.id
  cidr_block     = var.snet_pub_cidr_block

  display_name = var.snet_pub_display_name
}

resource "oci_core_internet_gateway" "snafu_internet_gateway" {
  compartment_id = oci_identity_compartment.snafu.id
  vcn_id         = oci_core_vcn.snafu_vcn.id

  display_name = var.inet_gateway_display_name
}

resource "oci_core_default_route_table" "snafu_route_table" {
  compartment_id             = oci_identity_compartment.snafu.id
  manage_default_resource_id = oci_core_vcn.snafu_vcn.default_route_table_id

  display_name = var.snafu_route_table_display_name
  dynamic "route_rules" {
    for_each = [true]
    content {
      destination       = "0.0.0.0/0"
      network_entity_id = oci_core_internet_gateway.snafu_internet_gateway.id
    }
  }
}

# object storage / image

data "oci_objectstorage_namespace" "ns" {
  compartment_id = var.tenancy_ocid
}

data "oci_core_compute_global_image_capability_schemas_versions" "compute_global_image_capability_schemas_versions" {
  compute_global_image_capability_schema_id = data.oci_core_compute_global_image_capability_schema.compute_global_image_capability_schema.id
}

data "oci_core_compute_global_image_capability_schema" "compute_global_image_capability_schema" {
  compute_global_image_capability_schema_id = data.oci_core_compute_global_image_capability_schemas.compute_global_image_capability_schemas.compute_global_image_capability_schemas[0].id
}

data "oci_core_compute_global_image_capability_schemas" "compute_global_image_capability_schemas" {}

resource "oci_objectstorage_bucket" "fedora_bucket" {
  compartment_id = oci_identity_compartment.snafu.id
  name           = var.bucket_name
  namespace      = data.oci_objectstorage_namespace.ns.namespace

  access_type = "ObjectRead"
}

resource "oci_objectstorage_preauthrequest" "fedora_upload_par" {
  namespace    = data.oci_objectstorage_namespace.ns.namespace
  bucket       = oci_objectstorage_bucket.fedora_bucket.name
  name         = "fedora-upload-par"
  access_type  = "ObjectWrite"
  object_name  = local.fedora_image
  time_expires = timeadd(timestamp(), "24h")
}

resource "null_resource" "upload_fedora" {
  depends_on = [
    oci_objectstorage_bucket.fedora_bucket,
    oci_objectstorage_preauthrequest.fedora_upload_par
  ]

  triggers = {
    file_hash = filemd5(local.fedora_image)
  }

  provisioner "local-exec" {
    command     = <<-EOT
      FULL_URL="${local.object_storage_endpoint}${oci_objectstorage_preauthrequest.fedora_upload_par.access_uri}"
      curl \
        -H "Content-Type: application/octet-stream" \
        --fail \
        --show-error \
        --upload-file "${local.fedora_image}" \
        "$FULL_URL"
    EOT
    interpreter = ["/bin/bash", "-c"]
  }
}

resource "oci_objectstorage_object" "fedora_image" {
  bucket       = oci_objectstorage_bucket.fedora_bucket.name
  namespace    = oci_objectstorage_bucket.fedora_bucket.namespace
  object       = local.fedora_image
  content_type = "application/octet-stream"

  delete_all_object_versions = true

  depends_on = [
    null_resource.upload_fedora
  ]
}

resource "oci_core_image" "fedora_custom_image" {
  compartment_id = oci_identity_compartment.snafu.id
  display_name   = "Fedora Linux"

  image_source_details {
    source_type    = "objectStorageTuple"
    namespace_name = oci_objectstorage_bucket.fedora_bucket.namespace
    bucket_name    = oci_objectstorage_bucket.fedora_bucket.name
    object_name    = "Fedora-Cloud-Base-Generic-41-1.4.x86_64.qcow2"

    operating_system         = "Fedora Linux"
    operating_system_version = "41"
    source_image_type        = "QCOW2"
  }

  timeouts {
    create = "30m"
  }
  depends_on = [
    null_resource.upload_fedora,
    oci_objectstorage_object.fedora_image,
  ]
}

resource "oci_core_shape_management" "fedora_shapes" {
  compartment_id = oci_identity_compartment.snafu.id
  image_id       = oci_core_image.fedora_custom_image.id
  shape_name     = var.snafu_compute_shape
}

resource "oci_core_compute_image_capability_schema" "custom_image_capability_schema" {
  compartment_id                                      = oci_identity_compartment.snafu.id
  compute_global_image_capability_schema_version_name = data.oci_core_compute_global_image_capability_schemas_versions.compute_global_image_capability_schemas_versions.compute_global_image_capability_schema_versions[1].name
  display_name                                        = "snafu_fedora_image_capability_schema"
  image_id                                            = oci_core_image.fedora_custom_image.id

  schema_data = {
    "Compute.AMD_SecureEncryptedVirtualization" = jsonencode(
      {
        defaultValue   = false
        descriptorType = "boolean"
        source         = "IMAGE"
      }
    )
    "Compute.Firmware" = jsonencode(
      {
        defaultValue   = "BIOS"
        descriptorType = "enumstring"
        source         = "IMAGE"
        values = [
          "BIOS",
          "UEFI_64",
        ]
      }
    )
    "Compute.LaunchMode" = jsonencode(
      {
        defaultValue   = "PARAVIRTUALIZED"
        descriptorType = "enumstring"
        source         = "IMAGE"
        values = [
          "NATIVE",
          "EMULATED",
          "VDPA",
          "PARAVIRTUALIZED",
          "CUSTOM",
        ]
      }
    )
    "Compute.SecureBoot" = jsonencode(
      {
        defaultValue   = false
        descriptorType = "boolean"
        source         = "IMAGE"
      }
    )
    "Network.AttachmentType" = jsonencode(
      {
        defaultValue   = "PARAVIRTUALIZED"
        descriptorType = "enumstring"
        source         = "IMAGE"
        values = [
          "VFIO",
          "PARAVIRTUALIZED",
          "E1000",
          "VDPA",
        ]
      }
    )
    "Network.IPv6Only" = jsonencode(
      {
        defaultValue   = false
        descriptorType = "boolean"
        source         = "IMAGE"
      }
    )
    "Storage.BootVolumeType" = jsonencode(
      {
        defaultValue   = "PARAVIRTUALIZED"
        descriptorType = "enumstring"
        source         = "IMAGE"
        values = [
          "ISCSI",
          "PARAVIRTUALIZED",
          "SCSI",
          "IDE",
          "NVME",
        ]
      }
    )
    "Storage.ConsistentVolumeNaming" = jsonencode(
      {
        defaultValue   = false
        descriptorType = "boolean"
        source         = "IMAGE"
      }
    )
    "Storage.Iscsi.MultipathDeviceSupported" = jsonencode(
      {
        defaultValue   = false
        descriptorType = "boolean"
        source         = "IMAGE"
      }
    )
    "Storage.LocalDataVolumeType" = jsonencode(
      {
        defaultValue   = "PARAVIRTUALIZED"
        descriptorType = "enumstring"
        source         = "IMAGE"
        values = [
          "ISCSI",
          "PARAVIRTUALIZED",
          "SCSI",
          "IDE",
          "NVME",
        ]
      }
    )
    "Storage.ParaVirtualization.AttachmentVersion" = jsonencode(
      {
        defaultValue   = 2
        descriptorType = "enuminteger"
        source         = "IMAGE"
        values = [
          1,
          2,
        ]
      }
    )
    "Storage.ParaVirtualization.EncryptionInTransit" = jsonencode(
      {
        defaultValue   = false
        descriptorType = "boolean"
        source         = "IMAGE"
      }
    )
    "Storage.RemoteDataVolumeType" = jsonencode(
      {
        defaultValue   = "PARAVIRTUALIZED"
        descriptorType = "enumstring"
        source         = "IMAGE"
        values = [
          "ISCSI",
          "PARAVIRTUALIZED",
          "SCSI",
          "IDE",
          "NVME",
        ]
      }
    )
  }
}

# compute

data "oci_identity_availability_domain" "snafu_availability_domain" {
  compartment_id = oci_identity_compartment.snafu.id
  ad_number      = 3
}

resource "oci_core_instance" "snafu_compute" {
  compartment_id      = oci_identity_compartment.snafu.id
  shape               = var.snafu_compute_shape
  availability_domain = data.oci_identity_availability_domain.snafu_availability_domain.name
  display_name        = var.snafu_compute_display_name

  source_details {
    source_id   = oci_core_image.fedora_custom_image.id
    source_type = "image"
  }

  create_vnic_details {
    subnet_id        = oci_core_subnet.snafu_public_subnet.id
    assign_public_ip = true
  }

  metadata = {
    ssh_authorized_keys = local.ssh_key
  }

  depends_on = [ 
    oci_core_shape_management.fedora_shapes,
    oci_core_compute_image_capability_schema.custom_image_capability_schema,
   ]
}

Like I said, the plan & apply cycle completes without any errors, but I can’t connect to the compute instance. For completeness, here’s the screenshot from the OCI web console:

The instance was destroyed since taking this screenshot so the IP there is no longer valid.

Any tips or suggestions on what I got wrong are appreciated!

1 Like

The issue of being unable to SSH into your instance is likely due to a misconfiguration in your VCN setup, the image, or the instance itself. could you please recheck