terraform-provider-aci icon indicating copy to clipboard operation
terraform-provider-aci copied to clipboard

Ability to associated contract with ESG without going into the resource for creating the ESG.

Open JockeW-DvL opened this issue 1 year ago • 18 comments

Community Note

  • Please vote on this issue by adding a 👍 reaction to the original issue to help the community and maintainers prioritize this request
  • Please do not leave "+1" or other comments that do not add relevant new information or questions, they generate extra noise for issue followers and do not help prioritize the request
  • If you are interested in working on this issue or have submitted a pull request, please leave a comment

Description

I'm in need of a resource for associating contracts and ESG's the same way we can do with EPG's, i.e. the same as aci_epg_to_contract.

The reason behind this request is that i need a way of associating contracts to ESG's without beeing forced to rebuild/change the ESG for every run of terraform apply.

New or Affected Resource(s) + ACI Class(es):

  • aci_XXXXX + fv:XXXX

APIC version and APIC Platform

  • V x.x.x and on-prem/cloud-aws/cloud-azure/all.

Potential Terraform Configuration

# Copy-paste your Terraform configurations here - for large Terraform configs,
# please use a service like Dropbox and share a link to the ZIP file. For
# security, you can also encrypt the files using our GPG public key.

References

  • #0000

JockeW-DvL avatar Feb 15 '24 14:02 JockeW-DvL

Hi @JockeW-DvL , thanks for raising this issue. I have added it to the todo items.

akinross avatar Feb 15 '24 15:02 akinross

Classes that should be configured:

  • fvRsCons
  • fvRsConsIf
  • fvRsProv
  • fvRsIntraEpg -> https://github.com/CiscoDevNet/terraform-provider-aci/issues/936

akinross avatar Feb 21 '24 09:02 akinross

@lhercot, I similarities with https://github.com/CiscoDevNet/terraform-provider-aci/issues/1023. Would this also apply for ESG?

akinross avatar Feb 21 '24 09:02 akinross

Hi @JockeW-DvL,

The aci_epg_to_contract resource would actually work for esg also. You should be able to do the following:

resource "aci_epg_to_contract" "example" {
  application_epg_dn = <ESG_DN>
  contract_dn        = aci_contract.example.id
  contract_type      = "provider"
  annotation         = "terraform"
  match_t            = "AtleastOne"
  prio               = "unspecified"
}

Please let me know if you face any issues.

akinross avatar Mar 04 '24 16:03 akinross

It works the first apply run, but if i do a second apply run without changing any config all the contracts will be removed from the ESGs. if i do a third apply run the contracts will then be reapplyd again.

The output from Terraform on the second run is:

# module.esg.aci_endpoint_security_group.terraform_esg["ESG1503"] will be updated in-place
  ~ resource "aci_endpoint_security_group" "terraform_esg" {
        id                     = "uni/tn-prod/ap-prod-isolated/esg-prod-isolated_app-101123_dataparc-usmen"
        name                   = "prod-isolated_app-101123_dataparc-usmen"
        # (8 unchanged attributes hidden)

      - relation_fv_rs_prov {
          - match_t   = "AtleastOne" -> null
          - prio      = "unspecified" -> null
          - target_dn = "uni/tn-prod/brc-Allow_All" -> null
        }
      - relation_fv_rs_prov {
          - match_t   = "AtleastOne" -> null
          - prio      = "unspecified" -> null
          - target_dn = "uni/tn-prod/brc-LB_to_Dataparc_Usmen" -> null
        }
    }

Just one example of the contracts beeing removed.

JockeW-DvL avatar Mar 05 '24 10:03 JockeW-DvL

Hi @JockeW-DvL, just to get a better understanding, could you provide your configuration also? I assume you do not define any contract relationships in aci_endpoint_security_group.terraform_esg configuration.

akinross avatar Mar 05 '24 10:03 akinross

Of course Akini, Here are the config i am using:

for building the ESGs:

resource "aci_endpoint_security_group" "terraform_esg" {
  for_each                             = var.esgs 

  application_profile_dn               = var.apps[each.value.network].id
  name                                 = join("-", [trimprefix(var.tenants[each.value.tenant].name,"tnt-"), lower(each.value.application)])
  description                          = join(" ", ["Endpoint Security Group for", each.value.application, "in Tenant", var.tenants[each.value.tenant].name])
  pc_enf_pref                          = "unenforced"
  pref_gr_memb                         = each.value.prefgr_member == "yes" ? "include" : "exclude"
  relation_fv_rs_scope                 = var.vrfs[each.value.vrf].id
  name_alias                           = each.key
}

And the indata for the above resource:

prod_isolated_esg            = {
  ESG1500                      = {
    tenant                       = "TEN2"
    vrf                          = "VRF7"
    network                      = "NETW9"
    tag_key                      = ""
    application                  = "isolated_default"
    epg                          = "NETW9"
    prefgr_member                = "no"
    infra_service                = ""
  }
  ESG1501                       = {
    tenant                       = "TEN2"
    vrf                          = "VRF7"
    network                      = "NETW9"
    tag_key                      = "TAG2"
    application                  = "isolated_APP-XXXXXX_tims"
    epg                          = ""
    prefgr_member                = "no"
    infra_service                = ""
  }
  ESG1502                       = {
    tenant                       = "TEN2"
    vrf                          = "VRF7"
    network                      = "NETW9"
    tag_key                      = "TAG2"
    application                  = "isolated_app-100557_clscl-msoft-lms"
    epg                          = ""
    prefgr_member                = "no"
    infra_service                = ""
  }
  ESG1503                       = {
    tenant                       = "TEN2"
    vrf                          = "VRF7"
    network                      = "NETW9"
    tag_key                      = "TAG2"
    application                  = "isolated_app-101123_dataparc-usmen"
    epg                          = ""
    prefgr_member                = "no"
    infra_service                = ""
  }
}

and the configuration for doing the mapping between ESG and contract:

resource "aci_epg_to_contract" "example" {
  for_each                    = var.esg_ctr_map
  
  application_epg_dn          = var.esgs[each.value.esg].id
  contract_dn                 = aci_contract.terraform_contract[each.value.contract].id
  contract_type               = each.value.ctr_type
}

And the indata for the above resourse:

esg_ctr_map	= {
  MAP1			= {
    esg 			= "ESG1500"
    contract		= "CTR1000"
    ctr_type		= "provider" 											
  }	
  MAP2                         = {
    esg                          = "ESG1500"
    contract                     = "CTR1001"
    ctr_type                     = "consumer"                       
  } 
  MAP3                         = {
    esg                          = "ESG1500"
    contract                     = "CTR1002"
    ctr_type                     = "consumer"                       
  } 
  MAP4                         = {
    esg                          = "ESG1501"
    contract                     = "CTR1000"
    ctr_type                     = "provider"                       
  } 
  MAP5                         = {
    esg                          = "ESG1502"
    contract                     = "CTR1000"
    ctr_type                     = "provider"                       
  } 
  MAP6                         = {
    esg                          = "ESG1502"
    contract                     = "CTR1002"
    ctr_type                     = "provider"                       
  } 
  MAP7                         = {
    esg                          = "ESG1503"
    contract                     = "CTR1000"
    ctr_type                     = "provider"                       
  } 
  MAP8                         = {
    esg                          = "ESG1503"
    contract                     = "CTR1001"
    ctr_type                     = "provider"                       
  } 
}

JockeW-DvL avatar Mar 05 '24 11:03 JockeW-DvL

Hi @JockeW-DvL,

I had a quick look today to see if I could find a quick solution to specify it in the ESG resource. Is there a reason that you are specifying the objects outside the resource, because I think this might solve your problems until we make changes. See example below and let me know if that could help you change your TF configuration logic a bit.

resource "aci_tenant" "test" {
  name = "abr_contract"
}

resource "aci_vrf" "test" {
  name      = "abr_contract_vrf"
  tenant_dn = aci_tenant.test.id
}

resource "aci_application_profile" "test" {
  name      = "abr_contract_app"
  tenant_dn = aci_tenant.test.id
}

variable "contracts" {
  type    = list(string)
  default = ["abr_contract_contract_1", "abr_contract_contract_2", "abr_contract_contract_3"]
}

resource "aci_contract" "test" {
  for_each  = toset(var.contracts)
  name      = each.value
  tenant_dn = aci_tenant.test.id
}

variable "esg_to_contracts" {
  type = map(object({
    esg           = string
    contract      = string
    contract_type = string
  }))
  default = {
    1 = {
      esg           = "abr_contract_esg"
      contract      = "abr_contract_contract_1"
      contract_type = "provider"
    }
    2 = {
      esg           = "abr_contract_esg"
      contract      = "abr_contract_contract_1"
      contract_type = "consumer"
    }
    3 = {
      esg           = "abr_contract_esg"
      contract      = "abr_contract_contract_2"
      contract_type = "provider"
    }
    4 = {
      esg           = "abr_contract_esg_2"
      contract      = "abr_contract_contract_3"
      contract_type = "consumer"
    }
    5 = {
      esg           = "abr_contract_esg"
      contract      = "abr_contract_contract_2"
      contract_type = "consumer"
    }
  }
}

resource "aci_endpoint_security_group" "terraform_esg" {
  application_profile_dn = aci_application_profile.test.id
  name                   = "abr_contract_esg"
  relation_fv_rs_scope   = aci_vrf.test.id
  dynamic "relation_fv_rs_prov" {
    for_each = {for k, v in var.esg_to_contracts : k => v if v.esg == "abr_contract_esg" && v.contract_type == "provider"}
    content {
        match_t = "AtleastOne"
        prio = "unspecified"
        target_dn = aci_contract.test[relation_fv_rs_prov.value.contract].id
    }
  }
  dynamic "relation_fv_rs_cons" {
    for_each = {for k, v in var.esg_to_contracts : k => v if v.esg == "abr_contract_esg" && v.contract_type == "consumer"}
    content {
        prio = "unspecified"
        target_dn = aci_contract.test[relation_fv_rs_cons.value.contract].id
    }
  }
}

akinross avatar Mar 05 '24 13:03 akinross

Hello Akini,

If you look at my resourse definition i'm using for_each for the whole resource, and the reason behind this is that we have around 150-200 different ESGs that need to be created. And you can't have 2 for_each in the same resource, nested for_each as it's called.

The reason i want to have the mapping outside of the resource is 2-fold: 1: I don't want to do any changes to the ESG to add or remove a contract, just have the mapping between Contract and ESG removed 2: we will be adding multiple contracts in the near future and i want to keep the config files simple, i.e. just have a mapping between Contract and ESG change.

JockeW-DvL avatar Mar 06 '24 07:03 JockeW-DvL

Hi @JockeW-DvL,

Ok thanks for the additional information, this is currently indeed not possible. The resource assumes that you define all the relationships to the ESG whenever you manage an ESG.

As a workaround you could define the ESG in another terraform configuration. Thus have a configuration that has datasource for ESG and then the relations defined, this way the changes made in relationship are not impacting the a resource that is defined in the same configuration. The problem would still occur that when there is a apply of configuration where the ESG is defined, but would be limited. Another option would be to adjust the data structure of you input. Both solutions are not ideal I would say.

We are working on a change for this behaviour, but this would also mean changes to the behaviour of the ESG resource where we need to detect None is provided in the relationship attribute. This is only possible starting the new terraform-plugin-framework, thus will depend on the migration of our resources to this new way.

akinross avatar Mar 06 '24 07:03 akinross

I can try and have the resources for creating the ESGs and the association of the Contract and ESG in separate workspaces and report back if this works as intended.

JockeW-DvL avatar Mar 06 '24 08:03 JockeW-DvL

Hi @JockeW-DvL,

I was thinking a bit more about your problem and think the best way would probably be to define a local module. This way you bypass the restriction of the nested for_each. Also this way you would not have the situation you were describing whenever there is a apply needed in the code where ESG is specified.

I have tested and created some example code that I can share with you over email but probably best if we set up a call for this to walk through it since it is multiple files. Could you send me an email so we can set something up?

akinross avatar Mar 06 '24 16:03 akinross

Hello Akini,I came to the same conclusion earlier today.I’ve gotten half of the terraform files created and will test this in the coming days (when my schedule allows)Regards Joacim W On 6 March 2024 at 17:49:13, Akini Ross @.***) wrote: Hi @JockeW-DvL, I was thinking a bit more about your problem and think the best way would probably be to define a local module. This way you bypass the restriction of the nested for_each. Also this way you would not have the situation you were describing whenever there is a apply needed in the code where ESG is specified. I have tested and created some example code that I can share with you over email but probably best if we set up a call for this to walk through it since it is multiple files. Could you send me an email so we can set something up?

—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you were mentioned.Message ID: @.> [ { @.": "http://schema.org", @.": "EmailMessage", "potentialAction": { @.": "ViewAction", "target": "https://github.com/CiscoDevNet/terraform-provider-aci/issues/1157#issuecomment-1981328360", "url": "https://github.com/CiscoDevNet/terraform-provider-aci/issues/1157#issuecomment-1981328360", "name": "View Issue" }, "description": "View this Issue on GitHub", "publisher": { @.***": "Organization", "name": "GitHub", "url": "https://github.com" } } ]

JockeW-DvL avatar Mar 06 '24 18:03 JockeW-DvL

Hi @JockeW-DvL, I was running some additional checks and noticed that the behaviour is also not matching the behaviour as we have in the EPG resource. I have labeled it as a bug now and we will be looking into a fix. Please follow the issue for PR updates and leverage the module workaround for now in case you need an immediate fix. Apologies for the inconvenience.

akinross avatar Mar 06 '24 20:03 akinross

Hi @JockeW-DvL, so looking at the code and running several tests drew me to the following conclusion. We need to migrate the resource into plugin framework in order for this to be fixed. The difference between EPG and ESG modules is that the ESG module exposes a list of maps, where the EPG module exposes a list of strings. In SDKv2 (framework to develop resources) the list of strings is written to state as Null when not provided, however for the list of maps it is written to a empty list. This difference in attribute handling makes it impossible for us to properly detect that a attribute is not provided and thus should not be touched. So for now the only way around this is the module workaround mentioned above.

akinross avatar Mar 11 '24 08:03 akinross

body{font-family:Helvetica,Arial;font-size:13px}Hello Akini,Thanks for the update. I would like to setup a webex-meeting so that you can show me the example code you’ve used.RegardsJoacim W On 11 March 2024 at 09:29:17, Akini Ross @.***) wrote: Hi @JockeW-DvL, so looking at the code and running several tests drew me to the following conclusion. We need to migrate the resource into plugin framework in order for this to be fixed. The difference between EPG and ESG modules is that the ESG module exposes a list of maps, where the EPG module exposes a list of strings. In SDKv2 (framework to develop resources) the list of strings is written to state as Null when not provided, however for the list of maps it is written to a empty list. This difference in attribute handling makes it impossible for us to properly detect that a attribute is not provided and thus should not be touched. So for now the only way around this is the module workaround mentioned above.

—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you were mentioned.Message ID: @.***>

JockeW-DvL avatar Mar 14 '24 08:03 JockeW-DvL

I have some time now if you are available, can you send me an email directly so we can schedule something?

akinross avatar Mar 14 '24 08:03 akinross

body{font-family:Helvetica,Arial;font-size:13px}Hello Akini,I’m working with another customer all day today, but tommorow have time between 10 and 12 tomorrow.RegardsJoacim W On 14 March 2024 at 09:08:41, Akini Ross @.***) wrote: I have some time now if you are available, can you send me an email directly so we can schedule something?

—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you were mentioned.Message ID: @.***>

JockeW-DvL avatar Mar 14 '24 08:03 JockeW-DvL