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

feat: Output terraform format #147

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open

feat: Output terraform format #147

wants to merge 6 commits into from

Conversation

displague
Copy link
Member

@displague displague commented Jul 23, 2021

This is a demonstration of how the Metal CLI can offer a Terraform output format for each supported resource type.
In this PR, since devices and device lists are translated into the most primitive Terraform HCL snippet, accompanied by the terraform import command needed to fetch the state of the configured resource.

The same pattern could be used to offer Crossplane and Ansible resources, among others.

An elaborate use-case to consider is fetching a project, but rather than getting the single project resource, getting all objects contained within that project as HCL. The --output argument in that case may need sub-options (--output=tf,deep)

The hinted argument, deep, would walk all descendent resources. metal project get -i $PROJECT_ID -o tf,deep, for example, would generate TF config for the project, devices, IPs, VLANs, etc.


All devices in the project

$ metal devices get -o tf
# terraform import metal_device.ch-x1-small-x86-01 8cda1f07-378a-43f7-9c94-3ab2373a9660
resource "metal_device" "ch-x1-small-x86-01" {
  plan = "baremetal_1e"
  hostname = "ch-x1-small-x86-01"
  billing_cycle = "hourly"
  metro = "ch"
  operating_system = "ubuntu_20_10"
  project_id = ""

  tags = []
}

# terraform import metal_device.mj-test b925f22f-edc9-4d58-8f8b-55d99c5b7cae
resource "metal_device" "mj-test" {
  plan = "t1.small.x86"
  hostname = "mj-test"
  billing_cycle = "hourly"
  metro = "sv"
  operating_system = "ubuntu_21_04"
  project_id = ""

  tags = []
}

A single devices in the project

$ metal devices get --id b925f22f-edc9-4d58-8f8b-55d99c5b7cae -o tf
# terraform import metal_device.mj-test b925f22f-edc9-4d58-8f8b-55d99c5b7cae
resource "metal_device" "mj-test" {
  plan = "t1.small.x86"
  hostname = "mj-test"
  billing_cycle = "hourly"
  metro = "sv"
  operating_system = "ubuntu_21_04"
  project_id = ""

  tags = []
}

Crossplane Format

The same single and multiple listing support can be used to emit Crossplane configuration. With Crossplane, the controller will synchronize the EM API settings, so only the UUID (as external-name) must be set in the resource.

See https://github.com/crossplane-contrib/provider-equinix-metal#install-the-equinix-metal-provider for more details about the EM Crossplane provider and https://crossplane.io/docs/v1.3/concepts/managed-resources.html for details on using Managed resources in Crossplane.

The utility this support would bring to the metal-cli has similar gains to the proposal for a crossplane import tool: crossplane/crossplane-cli#66.

$ metal devices get  -o crossplane
---
apiVersion: server.metal.equinix.com/v1alpha2
kind: Device
metadata:
  name: ch-x1-small-x86-01
  annotations:
    crossplane.io/external-name: 8cda1f07-378a-43f7-9c94-3ab2373a9660
spec:
  ## Late-Initialization will provide the current values
  ## so we don't specify them here.
  forProvider:
    hostname: ch-x1-small-x86-01
    plan: baremetal_1e
    metro: ch
    operatingSystem: ubuntu_20_10
    billingCycle: hourly
    locked: false
    tags: []

  ## The "default" provider will be used unless named here.
  # providerConfigRef:
  #  name: equinix-metal-provider

  ## EM devices do not persist passwords in the API long. This
  ## optional secret will not get the root pass for devices > 24h
  # writeConnectionSecretToRef:
  #  name: crossplane-example
  #  namespace: crossplane-system

  ## Do not delete devices that have been imported.
  # reclaimPolicy: Retain

---
apiVersion: server.metal.equinix.com/v1alpha2
kind: Device
metadata:
  name: mj-test
  annotations:
    crossplane.io/external-name: b925f22f-edc9-4d58-8f8b-55d99c5b7cae
spec:
  ## Late-Initialization will provide the current values
  ## so we don't specify them here.
  forProvider:
    hostname: mj-test
    plan: t1.small.x86
    metro: sv
    operatingSystem: ubuntu_21_04
    billingCycle: hourly
    locked: false
    tags: []

  ## The "default" provider will be used unless named here.
  # providerConfigRef:
  #  name: equinix-metal-provider

  ## EM devices do not persist passwords in the API long. This
  ## optional secret will not get the root pass for devices > 24h
  # writeConnectionSecretToRef:
  #  name: crossplane-example
  #  namespace: crossplane-system

  ## Do not delete devices that have been imported.
  # reclaimPolicy: Retain

@displague
Copy link
Member Author

Noting that Crossplane format offered could follow that presented by the Jet provider: https://github.com/crossplane-contrib/provider-tf-equinix-metal

@displague
Copy link
Member Author

I'm going to use this stub to catalog some alternatives (even if they are currently unaware of Equinix resource types):

@displague
Copy link
Member Author

@displague
Copy link
Member Author

Yes another tool that will generate Terraform from existing state:
https://github.com/GoogleCloudPlatform/terraformer/blob/master/docs/equinixmetal.md

@displague displague marked this pull request as ready for review July 6, 2022 17:34
@displague
Copy link
Member Author

This should be updated to use the equinix Terraform provider and the Terraform generated Crossplane provider.

@displague
Copy link
Member Author

displague commented Nov 8, 2023

The Terraform import lines can now be expressed as import lines: https://developer.hashicorp.com/terraform/language/import/generating-configuration#1-add-the-import-block

Perhaps the output format should allow users to take advantage of this feature in a few ways.
For example, it should be enough for Metal CLI to output the import blocks, with commented out hints to run terraform plan ---generate-config-out=resourcename.tf (instead of how it currently includes a comment to run terraform import resource id).

Metal CLI could offer this as an option, -o tf, -o tf,with-import, and -o tf,only-import could modify the output so that the resource block is templated, or the import block is templated, or both.

@cprivitere cprivitere changed the title Output terraform format doc: Output terraform format Jan 29, 2024
@cprivitere cprivitere changed the title doc: Output terraform format feat: Output terraform format Jan 29, 2024
Copy link
Contributor

@cprivitere cprivitere left a comment

Choose a reason for hiding this comment

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

This branch should probably be marked draft.

The terraform output function works (as in, they output text to the screen, didn't test for validity upon import).

The crossplane function does not work. Even after converting things to equinix-sdk-go, i still get this error:

Error: template: crossplane:8:35: executing "crossplane" at <.ID>: can't evaluate field ID in type metalv1.Device

Copy link
Contributor

@cprivitere cprivitere left a comment

Choose a reason for hiding this comment

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

This branch should probably be marked draft.

The terraform output function works (as in, outputs text to the screen, didn't test for validity upon import).

The crossplane function does not work. Even after converting things to equinix-sdk-go, i still get this error:

Error: template: crossplane:8:35: executing "crossplane" at <.ID>: can't evaluate field ID in type metalv1.Device


@displague
Copy link
Member Author

displague commented Aug 26, 2024

The import format can be improved with Terraform v1.5.0+
https://developer.hashicorp.com/terraform/language/import

import {
  to = equinix_metal_device.example
  id = "abcd1234"
}

This PR does not introduce that format. It uses a comment block today.
It could introduce both behaviors behind a toggle, if we preferred the pre-1.5 behavior: -o tf,import

This format could also handle list endpoints (again, not implemented in the PR):

locals {
  metal_devices = {
    "hostnamea" = "uuid-a"
    "hostnameb"     = "uuid-b"
    "hostnamec"    = "uuid-c"
  }
}

import {
  for_each = local.metal_devices
  to = equinix_metal_device.devices[each.key]
  id = each.value
}

resource "equinix_metal_device" "devices" {
  for_each = local.metal_devices
}

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