Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: deploy to existing projects in selected metro #93

Merged
merged 2 commits into from
Sep 24, 2024

Conversation

displague
Copy link
Contributor

@displague displague commented Sep 6, 2024

While attempting to deploy, I noticed a few things that have been addressed in this PR.
I would like someone more familiar with the project to review these changes and ensure that deployment is still working as expected. With these changes, I did get a successful terraform apply, and after a few minutes, I was able to access the Harvester portal.

  • existing projects can be used when referenced by new project_id variable
  • spot market pricing lookups were occurring and blocking under conditions where spot_instance was false. These pricing lookups will be skipped when not requested. Additionally, the use_cheapest_metro value was overriding the user supplied metro value when spot_instance was false. This has been corrected.
  • organization_id variable added as it may be required when creating projects and multiple Equinix Metal organizations are available
  • harvester_os_password sensitive output variable added for chaining this module to additional modules where harvester credentials may be needed (also helpful for logging into the UI without looking through terraform state)
  • harvester_cluster_secret sensitive output variable added for chaining this module to additional modules where harvester credentials may be needed
  • added descriptions for all variables that lacked descriptions (looks like other PRs addressed most of this. I've added some details.)

@displague displague force-pushed the sans_spot branch 3 times, most recently from 9b91104 to 463a768 Compare September 6, 2024 21:09
Copy link
Collaborator

@dnoland1 dnoland1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contribution. The PR failed formatting checks which we use to keep the code consistently indented. Can you have a look - https://github.com/rancherlabs/terraform-harvester-equinix/actions/runs/10745278454/job/29809400512?pr=93

@displague
Copy link
Contributor Author

@dnoland1 -- updated with terraform fmt

Copy link
Collaborator

@dnoland1 dnoland1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@displague
Copy link
Contributor Author

displague commented Sep 7, 2024

@dnoland1

The equinix_metal_project datasource will only take name or project_id, mutually exclusive, so the missing argument has to be sent as null instead of their default values of "".

I updated the logic and added precondition checks (required bumping to min TF 1.2). I verified that the datasource worked when I looked up by project name or id. I also noticed that datasource was not needed when metal_create_project was set, so we count=0 it now to further simplify the logic.

@displague
Copy link
Contributor Author

displague commented Sep 9, 2024

CI is currently failing in because TF_VAR_project_name is not defined in the contributor PR pipeline. I believe this would succeed in the e2e pipeline. The requirement for either a project_id or project_name is real and it makes sense that the validation fails in the current state. We could add a TF_VAR_project_name to this workflow or change the defaults: set a default name "harvester_something" and create_project=true, these will make organization_id a stronger requirement.

Copy link
Collaborator

@glovecchi0 glovecchi0 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @displague,
I tried to contribute to your Fork, but I received a "permission denied".

Anyway, I'll share the changes I made below:

data.tf

data "equinix_metal_project" "project" {
  count = var.metal_create_project ? 0 : 1

  name       = var.project_name == "" ? null : var.project_name
  project_id = var.project_id == "" ? null : var.project_id

  lifecycle {
    precondition {
      condition     = !(var.project_name != "" && var.project_id != "")
      error_message = "Only one of project_name or project_id can be set"
    }
    precondition {
      condition     = var.project_name != "" || var.project_id != ""
      error_message = "One of project_name or project_id must be set when metal_create_project is false"
    }
  }
}

data "equinix_metal_ip_block_ranges" "address_block" {
  project_id = local.project_id
  metro      = (var.spot_instance && var.use_cheapest_metro && local.cheapest_metro_price != null) ? local.cheapest_metro_price.metro : var.metro
}


data "equinix_metal_spot_market_request" "seed_req" {
  count      = var.spot_instance ? 1 : 0
  request_id = equinix_metal_spot_market_request.seed_spot_request.0.id
}


data "equinix_metal_spot_market_request" "join_req" {
  count      = var.spot_instance ? var.node_count - 1 : 0
  request_id = equinix_metal_spot_market_request.join_spot_request[count.index].id
}


data "equinix_metal_device" "seed_device" {
  device_id = var.spot_instance ? data.equinix_metal_spot_market_request.seed_req.0.device_ids[0] : equinix_metal_device.seed.0.id
}

data "equinix_metal_device" "join_devices" {
  count     = var.node_count - 1
  device_id = var.spot_instance ? data.equinix_metal_spot_market_request.join_req[count.index].device_ids[0] : equinix_metal_device.join[count.index].id
}

data "http" "prices" {
  count  = var.spot_instance && var.use_cheapest_metro ? 1 : 0
  url    = "https://api.equinix.com/metal/v1/market/spot/prices/metros"
  method = "GET"
  request_headers = {
    "X-Auth-Token" = var.api_key
  }
}

Here I fixed the "local.cheapest_metro_price is null" issue during CI plan

variables.tf

variable "harvester_version" {
  description = "Harvester version to be installed (Must be a valid version tag from https://github.com/rancherlabs/terraform-harvester-equinix/tree/main/ipxe)"
  type        = string
  default     = "v1.3.1"
}

variable "node_count" {
  description = "Number of nodes to deploy Harvester cluster"
  type        = number
  default     = 3
}

variable "metal_create_project" {
  description = "Create a Metal Project if this is 'true'. Else use provided 'project_name'"
  type        = bool
  default     = false
}

variable "project_name" {
  description = "Name of the Equinix Metal project to deploy into, when not looking up by project_id"
  type        = string
  default     = "Harvester Labs"
}

variable "organization_id" {
  description = "Equinix Metal organization ID to create or find a project in"
  type        = string
  default     = ""
}

variable "project_id" {
  description = "Equinix Metal project ID to deploy into, if not creating a new project or looking up by name"
  type        = string
  default     = ""
}

variable "plan" {
  description = "Size of the servers to be deployed on Equinix metal (https://deploy.equinix.com/developers/docs/metal/hardware/standard-servers/)"
  type        = string
  default     = "m3.small.x86"
}

variable "billing_cycle" {
  description = "Equinix metal billing/invoice generation schedule (hourly/daily/monthly/yearly)"
  type        = string
  default     = "hourly"
}

variable "metro" {
  description = "Equinix metal data center location (https://deploy.equinix.com/developers/docs/metal/locations/metros/). Examples: SG,SV,AM,MA,Ny,LA,etc."
  type        = string
  default     = "SG"
}

variable "ipxe_script" {
  description = "URL to the iPXE script to use for booting the server (harvester_version will be appended to this without the 'v' prefix)"
  type        = string
  default     = "https://raw.githubusercontent.com/rancherlabs/terraform-harvester-equinix/main/ipxe/ipxe-"
}

variable "hostname_prefix" {
  description = "Prefix for resources to be created in equinix metal"
  type        = string
  default     = "harvester-pxe"
}

variable "spot_instance" {
  description = "Set to true to use spot instance instead of on demand. Also set your max bid price if true."
  type        = bool
  default     = true
}

variable "max_bid_price" {
  description = "Maximum bid price for spot request"
  type        = string
  default     = "0.75"
}

variable "use_cheapest_metro" {
  description = "A boolean variable to control cheapest metro selection"
  type        = bool
  default     = true
}

variable "ssh_key" {
  description = "Your ssh key, examples: 'github: myghid' or 'ssh-rsa AAAAblahblah== keyname'"
  type        = string
  default     = ""
}

variable "num_of_vlans" {
  description = "Number of VLANs to be created"
  type        = number
  default     = 2
}

variable "rancher_api_url" {
  description = "Rancher API endpoint to manager your Harvester cluster"
  type        = string
  default     = ""
}

variable "rancher_access_key" {
  description = "Rancher access key"
  type        = string
  default     = ""
}

variable "rancher_secret_key" {
  description = "Rancher secret key"
  type        = string
  default     = ""
}

variable "rancher_insecure" {
  description = "Allow insecure connections to the Rancher API"
  type        = bool
  default     = false
}

variable "api_key" {
  description = "Equinix Metal authentication token. Required when using Spot Instances for HTTP pricing lookups. METAL_AUTH_TOKEN should always be set as an environment variable"
  type        = string
  default     = ""
}

Here I wrote the variables all with the same style (description, type, default) and assigned a default value to "project_name".

main.tf

locals {
  project_id = var.metal_create_project ? equinix_metal_project.new_project[0].id : data.equinix_metal_project.project[0].project_id
  metro      = (var.spot_instance && var.use_cheapest_metro && local.cheapest_metro_price != null) ? local.cheapest_metro_price.metro : lower(var.metro)
}

// IP attachment to be added to seed node, and this is subsequently assigned as Harvester vip
// in the config.yaml
resource "random_password" "password" {
  length  = 16
  special = false
}

resource "random_password" "token" {
  length  = 16
  special = false
}

resource "equinix_metal_project" "new_project" {
  count           = var.metal_create_project ? 1 : 0
  name            = var.project_name
  organization_id = var.organization_id == "" ? null : var.organization_id
}

locals {
  machine_size = var.plan
  pricing_data = (var.spot_instance && var.use_cheapest_metro) ? try(jsondecode(data.http.prices[0].response_body), null) : null
  least_bid_price_metro = can(local.pricing_data) && can(local.pricing_data.spot_market_prices) ? flatten([for metro, machines in local.pricing_data.spot_market_prices : [
    for machine, details in machines : {
      metro   = metro
      machine = machine
      price   = details.price
    } if machine == local.machine_size
  ]]) : []
  cheapest_metro_price = length(local.least_bid_price_metro) > 0 ? {
    price = min([for price in local.least_bid_price_metro : price.price]...),
    metro = [for price in local.least_bid_price_metro : price.metro if price.price == min([for price in local.least_bid_price_metro : price.price]...)][0]
  } : null
}

## Keeping it commented for debugging purposes. Will remove once verified.
#output "http_response" {
#  value = data.http.prices.response_body
#}

resource "equinix_metal_reserved_ip_block" "harvester_vip" {
  project_id = local.project_id
  metro      = local.metro
  type       = "public_ipv4"
  quantity   = 1
}

resource "equinix_metal_device" "seed" {
  hostname         = "${var.hostname_prefix}-1"
  count            = var.node_count >= 1 && !var.spot_instance ? 1 : 0
  plan             = var.plan
  metro            = local.metro
  operating_system = "custom_ipxe"
  billing_cycle    = var.billing_cycle
  project_id       = local.project_id
  ipxe_script_url  = "${var.ipxe_script}${element(split("v", var.harvester_version), 1)}"
  always_pxe       = "false"
  user_data        = templatefile("${path.module}/create.tpl", { version = var.harvester_version, password = random_password.password.result, token = random_password.token.result, vip = equinix_metal_reserved_ip_block.harvester_vip.network, hostname_prefix = var.hostname_prefix, ssh_key = var.ssh_key, count = "1", cluster_registration_url = var.rancher_api_url != "" ? rancher2_cluster.rancher_cluster[0].cluster_registration_token[0].manifest_url : "" })
}

resource "equinix_metal_spot_market_request" "seed_spot_request" {
  count            = var.node_count >= 1 && var.spot_instance ? 1 : 0
  project_id       = local.project_id
  max_bid_price    = (var.use_cheapest_metro && local.cheapest_metro_price != null) ? local.cheapest_metro_price.price : var.max_bid_price
  metro            = local.metro
  devices_min      = 1
  devices_max      = 1
  wait_for_devices = true

  instance_parameters {
    hostname         = "${var.hostname_prefix}-1"
    billing_cycle    = "hourly"
    operating_system = "custom_ipxe"
    ipxe_script_url  = "${var.ipxe_script}${element(split("v", var.harvester_version), 1)}"
    plan             = var.plan
    userdata         = templatefile("${path.module}/create.tpl", { version = var.harvester_version, password = random_password.password.result, token = random_password.token.result, vip = equinix_metal_reserved_ip_block.harvester_vip.network, hostname_prefix = var.hostname_prefix, ssh_key = var.ssh_key, count = "1", cluster_registration_url = var.rancher_api_url != "" ? rancher2_cluster.rancher_cluster[0].cluster_registration_token[0].manifest_url : "" })
  }
}


resource "equinix_metal_ip_attachment" "first_address_assignment" {
  device_id     = var.spot_instance ? data.equinix_metal_spot_market_request.seed_req.0.device_ids[0] : equinix_metal_device.seed.0.id
  cidr_notation = join("/", [cidrhost(equinix_metal_reserved_ip_block.harvester_vip.cidr_notation, 0), "32"])
}

resource "equinix_metal_device" "join" {
  hostname         = "${var.hostname_prefix}-${count.index + 2}"
  count            = var.spot_instance ? 0 : var.node_count - 1
  plan             = var.plan
  metro            = local.metro
  operating_system = "custom_ipxe"
  billing_cycle    = var.billing_cycle
  project_id       = local.project_id
  ipxe_script_url  = "${var.ipxe_script}${element(split("v", var.harvester_version), 1)}"
  always_pxe       = "false"
  user_data        = templatefile("${path.module}/join.tpl", { version = var.harvester_version, password = random_password.password.result, token = random_password.token.result, seed = equinix_metal_reserved_ip_block.harvester_vip.network, hostname_prefix = var.hostname_prefix, ssh_key = var.ssh_key, count = "${count.index + 2}" })
}

resource "equinix_metal_spot_market_request" "join_spot_request" {
  count            = var.spot_instance ? var.node_count - 1 : 0
  project_id       = local.project_id
  max_bid_price    = (var.use_cheapest_metro && local.cheapest_metro_price != null) ? local.cheapest_metro_price.price : var.max_bid_price
  metro            = local.metro
  devices_min      = 1
  devices_max      = 1
  wait_for_devices = true

  instance_parameters {
    hostname         = "${var.hostname_prefix}-${count.index + 2}"
    billing_cycle    = "hourly"
    operating_system = "custom_ipxe"
    ipxe_script_url  = "${var.ipxe_script}${element(split("v", var.harvester_version), 1)}"
    plan             = var.plan
    userdata         = templatefile("${path.module}/join.tpl", { version = var.harvester_version, password = random_password.password.result, token = random_password.token.result, seed = equinix_metal_reserved_ip_block.harvester_vip.network, hostname_prefix = var.hostname_prefix, ssh_key = var.ssh_key, count = "${count.index + 2}" })
  }
}

resource "equinix_metal_vlan" "vlans" {
  count       = var.num_of_vlans
  description = "VLAN for ${var.hostname_prefix}"
  project_id  = local.project_id
  metro       = local.metro
}

resource "equinix_metal_port_vlan_attachment" "vlan_attach_seed" {

  count     = var.num_of_vlans
  device_id = data.equinix_metal_device.seed_device.id
  vlan_vnid = equinix_metal_vlan.vlans[count.index].vxlan
  port_name = "bond0"
}

resource "equinix_metal_port_vlan_attachment" "vlan_attach_join" {

  count     = var.num_of_vlans * (var.node_count - 1)
  device_id = data.equinix_metal_device.join_devices[count.index % (var.node_count - 1)].id
  vlan_vnid = equinix_metal_vlan.vlans[floor(count.index / (var.node_count - 1))].vxlan
  port_name = "bond0"
}

resource "rancher2_cluster" "rancher_cluster" {
  name        = var.hostname_prefix
  count       = var.rancher_api_url != "" ? 1 : 0
  description = "${var.hostname_prefix} created by Terraform"
}

resource "local_file" "harvester_kubeconfig" {
  count    = var.rancher_api_url != "" ? 1 : 0
  content  = rancher2_cluster.rancher_cluster[0].kube_config
  filename = "${var.hostname_prefix}-kubeconfig.yaml"
}

Here I fixed the "local.cheapest_metro_price is null" issue during CI plan.

terraform.tfvars.example

## -- Harvester version to be installed (Must be a valid version tag from https://github.com/rancherlabs/terraform-harvester-equinix/tree/main/ipxe)
harvester_version = "v1.3.1"

## -- Number of nodes to deploy Harvester cluster
node_count = 3

## -- Create a Metal Project if this is 'true'. Else use provided 'project_name'
metal_create_project = false

## -- Name of the Equinix Metal project to deploy into, when not looking up by project_id
project_name = "Harvester Labs"

## -- Equinix Metal organization ID to create or find a project in
organization_id = ""

## -- Equinix Metal project ID to deploy into, if not creating a new project or looking up by nam
project_id = ""

## -- Size of the servers to be deployed on Equinix metal (https://deploy.equinix.com/developers/docs/metal/hardware/standard-servers/)
plan = "m3.small.x86"

## -- Equinix metal billing/invoice generation schedule (hourly/daily/monthly/yearly)
billing_cycle = "hourly"

## -- Equinix metal data center location (https://deploy.equinix.com/developers/docs/metal/locations/metros/). Examples: SG,SV,AM,MA,Ny,LA,etc.
metro = "SG"

## -- URL to the iPXE script to use for booting the server (harvester_version will be appended to this without the 'v' prefix)
ipxe_script = "https://raw.githubusercontent.com/rancherlabs/terraform-harvester-equinix/main/ipxe/ipxe-"

## -- Prefix for resources to be created in equinix metal
hostname_prefix = "harvester-pxe"

## -- Set to true to use spot instance instead of on demand. Also set your max bid price if true.
spot_instance = true

## -- Maximum bid price for spot request
max_bid_price = "0.75"

## -- A boolean variable to control cheapest metro selection
use_cheapest_metro = true

## -- Your ssh key, examples: 'github: myghid' or 'ssh-rsa AAAAblahblah== keyname'
ssh_key = ""

## -- Number of VLANs to be created
num_of_vlans = 2

## -- Rancher API endpoint to manager your Harvester cluster
rancher_api_url = ""

## -- Rancher access key
rancher_access_key = ""

## -- Rancher secret key
rancher_secret_key = ""

## -- Allow insecure connections to the Rancher API
rancher_insecure = false

## -- Equinix Metal authentication token. Required when using Spot Instances for HTTP pricing lookups. METAL_AUTH_TOKEN should always be set as an environment variable
api_key = ""

Rewritten.

README.md

# Harvester on Equinix

Simple example using the terraform equinix provider to create a multi node harvester cluster.

## How to create resources

- Copy `./terraform.tfvars.example` to `./terraform.tfvars`
- Edit `./terraform.tfvars`
  - Update the required variables:
    -  `project_name` to identify project in your Equinix account <- **otherwise you can export the `TF_VAR_project_name` or `TF_VAR_project_id` variable**
    -  `hostname_prefix` to give the resources an identifiable name (eg, your initials or first name)
    -  `ssh_key` to access the nodes
    -  `api_key` to access your Equinix account <- **otherwise you can export the `METAL_AUTH_TOKEN` or `TF_VAR_api_key` variable**

#### Optionally the user can also provide:

`TF_VAR_metal_create_project` Terraform variable to create a project of name `TF_VAR_project_name` if it does not exist.

#### Terraform Apply
\`\`\`bash
terraform init -upgrade && terraform apply -auto-approve
\`\`\`

#### Terraform Destroy
- Destroy the resources when finished
\`\`\`bash
terraform destroy -auto-approve
\`\`\`

The Harvester console can be accessed using an Elastic IP created by the sample.

A random token and password will be generated for your example.

\`\`\`sh
terraform output -raw harvester_os_password
terraform output -raw harvester_cluster_secret
\`\`\`

If you provide a Rancher API URL and keys, your Harvester environment can be managed by Rancher and a kubeconfig file will be saved locally.

Rewritten. I added the backticks for the terraform commands because otherwise the copy/paste here won't work; obviously they must be removed.

docs.md

## Requirements

| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.2 |
| <a name="requirement_equinix"></a> [equinix](#requirement\_equinix) | 2.3.2 |
| <a name="requirement_rancher2"></a> [rancher2](#requirement\_rancher2) | 5.0.0 |

## Providers

| Name | Version |
|------|---------|
| <a name="provider_equinix"></a> [equinix](#provider\_equinix) | 2.3.2 |
| <a name="provider_http"></a> [http](#provider\_http) | n/a |
| <a name="provider_local"></a> [local](#provider\_local) | n/a |
| <a name="provider_rancher2"></a> [rancher2](#provider\_rancher2) | 5.0.0 |
| <a name="provider_random"></a> [random](#provider\_random) | n/a |

## Modules

No modules.

## Resources

| Name | Type |
|------|------|
| [equinix_metal_device.join](https://registry.terraform.io/providers/equinix/equinix/2.3.2/docs/resources/metal_device) | resource |
| [equinix_metal_device.seed](https://registry.terraform.io/providers/equinix/equinix/2.3.2/docs/resources/metal_device) | resource |
| [equinix_metal_ip_attachment.first_address_assignment](https://registry.terraform.io/providers/equinix/equinix/2.3.2/docs/resources/metal_ip_attachment) | resource |
| [equinix_metal_port_vlan_attachment.vlan_attach_join](https://registry.terraform.io/providers/equinix/equinix/2.3.2/docs/resources/metal_port_vlan_attachment) | resource |
| [equinix_metal_port_vlan_attachment.vlan_attach_seed](https://registry.terraform.io/providers/equinix/equinix/2.3.2/docs/resources/metal_port_vlan_attachment) | resource |
| [equinix_metal_project.new_project](https://registry.terraform.io/providers/equinix/equinix/2.3.2/docs/resources/metal_project) | resource |
| [equinix_metal_reserved_ip_block.harvester_vip](https://registry.terraform.io/providers/equinix/equinix/2.3.2/docs/resources/metal_reserved_ip_block) | resource |
| [equinix_metal_spot_market_request.join_spot_request](https://registry.terraform.io/providers/equinix/equinix/2.3.2/docs/resources/metal_spot_market_request) | resource |
| [equinix_metal_spot_market_request.seed_spot_request](https://registry.terraform.io/providers/equinix/equinix/2.3.2/docs/resources/metal_spot_market_request) | resource |
| [equinix_metal_vlan.vlans](https://registry.terraform.io/providers/equinix/equinix/2.3.2/docs/resources/metal_vlan) | resource |
| [local_file.harvester_kubeconfig](https://registry.terraform.io/providers/hashicorp/local/latest/docs/resources/file) | resource |
| [rancher2_cluster.rancher_cluster](https://registry.terraform.io/providers/rancher/rancher2/5.0.0/docs/resources/cluster) | resource |
| [random_password.password](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource |
| [random_password.token](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource |
| [equinix_metal_device.join_devices](https://registry.terraform.io/providers/equinix/equinix/2.3.2/docs/data-sources/metal_device) | data source |
| [equinix_metal_device.seed_device](https://registry.terraform.io/providers/equinix/equinix/2.3.2/docs/data-sources/metal_device) | data source |
| [equinix_metal_ip_block_ranges.address_block](https://registry.terraform.io/providers/equinix/equinix/2.3.2/docs/data-sources/metal_ip_block_ranges) | data source |
| [equinix_metal_project.project](https://registry.terraform.io/providers/equinix/equinix/2.3.2/docs/data-sources/metal_project) | data source |
| [equinix_metal_spot_market_request.join_req](https://registry.terraform.io/providers/equinix/equinix/2.3.2/docs/data-sources/metal_spot_market_request) | data source |
| [equinix_metal_spot_market_request.seed_req](https://registry.terraform.io/providers/equinix/equinix/2.3.2/docs/data-sources/metal_spot_market_request) | data source |
| [http_http.prices](https://registry.terraform.io/providers/hashicorp/http/latest/docs/data-sources/http) | data source |

## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_api_key"></a> [api\_key](#input\_api\_key) | Equinix Metal authentication token. Required when using Spot Instances for HTTP pricing lookups. METAL\_AUTH\_TOKEN should always be set as an environment variable | `string` | `""` | no |
| <a name="input_billing_cycle"></a> [billing\_cycle](#input\_billing\_cycle) | Equinix metal billing/invoice generation schedule (hourly/daily/monthly/yearly) | `string` | `"hourly"` | no |
| <a name="input_harvester_version"></a> [harvester\_version](#input\_harvester\_version) | Harvester version to be installed (Must be a valid version tag from https://github.com/rancherlabs/terraform-harvester-equinix/tree/main/ipxe) | `string` | `"v1.3.1"` | no |
| <a name="input_hostname_prefix"></a> [hostname\_prefix](#input\_hostname\_prefix) | Prefix for resources to be created in equinix metal | `string` | `"harvester-pxe"` | no |
| <a name="input_ipxe_script"></a> [ipxe\_script](#input\_ipxe\_script) | URL to the iPXE script to use for booting the server (harvester\_version will be appended to this without the 'v' prefix) | `string` | `"https://raw.githubusercontent.com/rancherlabs/terraform-harvester-equinix/main/ipxe/ipxe-"` | no |
| <a name="input_max_bid_price"></a> [max\_bid\_price](#input\_max\_bid\_price) | Maximum bid price for spot request | `string` | `"0.75"` | no |
| <a name="input_metal_create_project"></a> [metal\_create\_project](#input\_metal\_create\_project) | Create a Metal Project if this is 'true'. Else use provided 'project\_name' | `bool` | `false` | no |
| <a name="input_metro"></a> [metro](#input\_metro) | Equinix metal data center location (https://deploy.equinix.com/developers/docs/metal/locations/metros/). Examples: SG,SV,AM,MA,Ny,LA,etc. | `string` | `"SG"` | no |
| <a name="input_node_count"></a> [node\_count](#input\_node\_count) | Number of nodes to deploy Harvester cluster | `number` | `3` | no |
| <a name="input_num_of_vlans"></a> [num\_of\_vlans](#input\_num\_of\_vlans) | Number of VLANs to be created | `number` | `2` | no |
| <a name="input_organization_id"></a> [organization\_id](#input\_organization\_id) | Equinix Metal organization ID to create or find a project in | `string` | `""` | no |
| <a name="input_plan"></a> [plan](#input\_plan) | Size of the servers to be deployed on Equinix metal (https://deploy.equinix.com/developers/docs/metal/hardware/standard-servers/) | `string` | `"m3.small.x86"` | no |
| <a name="input_project_id"></a> [project\_id](#input\_project\_id) | Equinix Metal project ID to deploy into, if not creating a new project or looking up by name | `string` | `""` | no |
| <a name="input_project_name"></a> [project\_name](#input\_project\_name) | Name of the Equinix Metal project to deploy into, when not looking up by project\_id | `string` | `"Harvester Labs"` | no |
| <a name="input_rancher_access_key"></a> [rancher\_access\_key](#input\_rancher\_access\_key) | Rancher access key | `string` | `""` | no |
| <a name="input_rancher_api_url"></a> [rancher\_api\_url](#input\_rancher\_api\_url) | Rancher API endpoint to manager your Harvester cluster | `string` | `""` | no |
| <a name="input_rancher_insecure"></a> [rancher\_insecure](#input\_rancher\_insecure) | Allow insecure connections to the Rancher API | `bool` | `false` | no |
| <a name="input_rancher_secret_key"></a> [rancher\_secret\_key](#input\_rancher\_secret\_key) | Rancher secret key | `string` | `""` | no |
| <a name="input_spot_instance"></a> [spot\_instance](#input\_spot\_instance) | Set to true to use spot instance instead of on demand. Also set your max bid price if true. | `bool` | `true` | no |
| <a name="input_ssh_key"></a> [ssh\_key](#input\_ssh\_key) | Your ssh key, examples: 'github: myghid' or 'ssh-rsa AAAAblahblah== keyname' | `string` | `""` | no |
| <a name="input_use_cheapest_metro"></a> [use\_cheapest\_metro](#input\_use\_cheapest\_metro) | A boolean variable to control cheapest metro selection | `bool` | `true` | no |

## Outputs

| Name | Description |
|------|-------------|
| <a name="output_harvester_cluster_secret"></a> [harvester\_cluster\_secret](#output\_harvester\_cluster\_secret) | The cluster secret for joining nodes to the cluster (https://docs.harvesterhci.io/v1.3/install/harvester-configuration/#token) |
| <a name="output_harvester_os_password"></a> [harvester\_os\_password](#output\_harvester\_os\_password) | The password for the default OS user, 'rancher' (https://docs.harvesterhci.io/v1.3/install/harvester-configuration/#ospassword) |
| <a name="output_harvester_url"></a> [harvester\_url](#output\_harvester\_url) | The URL to reach the Harvester user interface |
| <a name="output_out_of_band_hostnames"></a> [out\_of\_band\_hostnames](#output\_out\_of\_band\_hostnames) | Out of band hostnames for SSH access to the console |
| <a name="output_public_ips"></a> [public\_ips](#output\_public\_ips) | The public IP addresses of all Harvester nodes |

New file.

Everything worked fine using this terraform.tfvars file:

 glovecchio  ~  suse  displague  terraform-harvester-equinix   fix/sans_spot  cat terraform.tfvars
project_name    = "Harvester Labs"
metro           = "AM"
hostname_prefix = "glovecchio-harv"
ssh_key         = "ssh-rsa MYKEY="
api_key         = "MYKEY"
 glovecchio  ~  suse  displague  terraform-harvester-equinix   fix/sans_spot 

If you give me a chance to contribute in the "[email protected]:displague/terraform-harvester-equinix.git" repo, I'll send you everything, otherwise please take a look at the above and tell me what you think.

Thanks again for the help!

@displague
Copy link
Contributor Author

@glovecchi0 I applied your changes in the latest commit (with minor formatting tweaks and --author you).
I didn't apply the README.md and DOCS.md changes because #75 covers that, automating and persisting it going forward.

@glovecchi0 glovecchi0 self-requested a review September 13, 2024 09:46
Copy link
Collaborator

@glovecchi0 glovecchi0 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The plan works, so I don't think there are any CI issues right now.

image image

With the tfvars below, the deployment also works (PS: I exported the token via environment variable).

hostname_prefix = "glovecchio"
project_name    = "Harvester Labs"
metro           = "PA"
image image

Well done!, thank you!

@displague
Copy link
Contributor Author

@glovecchi0 do you have the option to flip the workflow approval switch so CI will run checks?

Copy link
Collaborator

@dnoland1 dnoland1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The terraform plan check is still failing and needs to be fixed before merging. See https://github.com/rancherlabs/terraform-harvester-equinix/actions/runs/10831569903/job/30155965468?pr=93

@glovecchi0
Copy link
Collaborator

The terraform plan check is still failing and needs to be fixed before merging. See https://github.com/rancherlabs/terraform-harvester-equinix/actions/runs/10831569903/job/30155965468?pr=93

Hey @dnoland1,
Is there a reason why all those variables are configured in the "Terraform Plan" section of the CI?

          METAL_AUTH_TOKEN: ${{ secrets.metal_auth_token }}
          TF_VAR_project_name: ${{ secrets.project_name }}
          TF_VAR_api_key: ${{ secrets.metal_auth_token }}
          TF_VAR_node_count: 3
          TF_VAR_plan: m3.small.x86
          TF_VAR_spot_instance: true
          TF_VAR_num_of_vlans: 10 

I don't encounter any errors and the only thing I do is export the METAL_AUTH_TOKEN variable.
image

image

PS: the api_key variable cannot work, as it is not initialized in the provider.tf file.
Once this PR is resolved, I'll add a few fixes.

@displague
Copy link
Contributor Author

The api_key is used for the spot market pricing requests issued by the http provider (a feature not present in the Equinix TF provider).

@displague
Copy link
Contributor Author

displague commented Sep 16, 2024

@dnoland1 the CI failed because my PR is from a fork, it does not have access to the project's secrets.

The error, as follows, is sensible under root org executions where project_name is defined, https://github.com/rancherlabs/terraform-harvester-equinix/blob/main/.github/workflows/ghactions-ci.yml#L33. (as @glovecchi0 pointed out).

It may improve the contributor experience if the non-sensitive variables were moved to an environment variable (not secret) and made part of a CI build that only executes terraform validate. terraform plan requires secrets. Those changes can not be introduced in this PR (they could, but they wouldn't pass CI for the same reason).

│ Error: Resource precondition failed
│ 
│   on data.tf line 13, in data "equinix_metal_project" "project":
│   13:       condition     = var.project_name != "" || var.project_id != ""
│     ├────────────────
│     │ var.project_id is ""
│     │ var.project_name is ""
│ 
│ One of project_name or project_id must be set when metal_create_project is
│ false

@dnoland1
Copy link
Collaborator

I put in a PR to hard code the project name in the GH action instead of using a secret.

@dnoland1
Copy link
Collaborator

PR is now merged. @displague if you want to update your PR/branch with the latest from main we can see if it passes the GH action checks.

@dnoland1
Copy link
Collaborator

Tests still aren't passing because it cannot retrieve the project id using the project name because the Equinix API key is not available. I'll merge and will need to make sure future internal PRs pass the tests.

@dnoland1 dnoland1 merged commit edd5a93 into rancherlabs:main Sep 24, 2024
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants