IBM-Cloud / terraform-provider-ibm

https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs
Mozilla Public License 2.0
341 stars 670 forks source link

Option so that apply doesn't fail if a resource already exists #3729

Open triceam opened 2 years ago

triceam commented 2 years ago

Community Note

Description

Many resources from the IBM Cloud Terraform provider will fail if a resource already exists that has the same name. Our team has created some fairly complex terraform templates that will create a resource group, activity tracker, service authorizations, plus VPCs & clusters etc... We will often target the same resource group and activity tracker instances, but this means that our automation will only work on clean accounts. You can't re-run the same automation on the same account (With different vars to create separate instances in the same resource group) b/c it will fail. We encounter failures VERY often b/c a resource group, service authorization, access group, or instance of activity tracker already exists.

Can we have a use_existing_instance boolean variable (or similar), that if set to true will just return data for the existing instance instead of causing terraform execution to fail.

We (the ecosystem team) run into this all the time, to the point that we have written a terraform module that calls the IBM Cloud APIs directly inside of a local exec provisioner, so that it handles the case where a RG, access group, service authorization, or activity tracker instance already exists. We don't use the native terraform resource for any of these anymore.

New or Affected Resource(s)

en-ree commented 2 years ago

Hi @triceam, I'm not from the Terraform Provider team but I may be able to offer a workaround for that: As far as I know Terraform is not capable of deciding this dynamically during the runtime. You have to decide prior to execution if a resource shall be created or not.

The good news is: There is a pattern for that when you provide a boolean such as you described. We solve this issue in the following way:

1) Provide an input variable, e.g. use_existing_rg (bool). 2) Create a module for the resource which looks like this:

# Config
terraform {
  required_providers {
    ibm = {
      source = "IBM-Cloud/ibm"
      version = ">= 1.18.0"
    }
  }
  required_version = ">= 0.13"
}

# Variables
variable region {
    type = string
}

variable name {
    type = string
}

variable lookup {
    type = bool
    default = false
}

variable tags {
    type = list(string)
    default = []
}

# Resources
data "ibm_resource_group" "lookup" {
    count = var.lookup ? 1 : 0
    name = var.name
}

resource "ibm_resource_group" "create" {
    count = !var.lookup ? 1 : 0
    name = var.name
    tags = var.tags
}

# Outputs
locals {
    result = one(concat(ibm_resource_group.create, data.ibm_resource_group.lookup))
}

output "group" {
    value = local.result
}

output "group_id" {
    value = local.result.id
}

I like to use a module variable called lookup. If this is set to true the module code will use a data source to look up an existing resource group while setting this to false will use the resource to create your resource group. If you set the results in a clever way the rest of the code will not recognize the difference if the resource group was created of looked up. You can simply use module.rg_module.group.id or module.rg_module.group_id if your module is called rg_module.

You would use this module like this:

module "resource_group" {
    source = "../modules/resource_group"

    region  = var.region
    name    = var.rg_name
    lookup  = var.use_existing_rg
    tags        = var.rg_tags
}

This approach works for most of the resources (as long as there is a data source). I just have a general hint: It is also common to use a unique_id or a prefix for resource names. We use this to make it possible to run the same Terraform script on the same account multiple times, each time providing a different prefix. This way you can prevent name collisions.

module "resource_group" {
    source = "../modules/resource_group"

    region  = var.region
    name    = format("%s%s", var.prefix, var.rg_name) # Use this variant if the prefix is optional so that it can be left blank). A non-empty prefix must be provided ending with a separator character: mypref-
    #name   = format("%s-%s", var.prefix, var.rg_name) # Use this variant if the prefix is not optional. The prefix is provided without the separator charater: mypref
    lookup  = var.use_existing_rg
    tags        = var.rg_tags
}

Hope this helps.

Kind regards, Enrico | IBM Cloud Solution Engineering team (ICSE)