dodevops / terraform-provider-ldap

Terraform provider to manage an LDAP directory
https://registry.terraform.io/providers/dodevops/ldap/latest
MIT License
3 stars 4 forks source link

`ignore_changes` list behaviour #37

Closed sst-yde closed 3 months ago

sst-yde commented 1 year ago

Current behaviour:

When you want to change a single attribute with the Terraform LDAP provider, it always tries to import the whole entry into the state and update all attributes.

Terraform example: ```tf # See http://wiki.stoney-cloud.org/wiki/stoney_cloud:_OpenLDAP_directory_data_organisation#Domain_.28reseller.29_example resource "ldap_object" "ldap_write" { dn = "uid=4000000,ou=domains,ou=openstack,ou=services,dc=stoney-cloud,dc=org" object_classes = [ "top", "sstOpenStackDomain", "sstProvisioning", "sstRelationship", ] attributes = { sstOpenStackId = [module.stepping_stone_ag_test.identity_project_v3_id] } # Ignore everything else: ignore_changes = [ "description", "sstBelongsToCustomerUID", "sstBelongsToDomainID", "sstBelongsToProjectID", "sstBelongsToResellerUID", "sstBusinessLogicRoleName", "sstCancellationDate", "sstDisplayName", "sstEnvironment", "sstIsActive", "sstIsIaaSDomain", "sstIsIaaSProject", "sstNetworkDomainName", "sstNetworkHostname", "sstOpenStackName", "sstOperatingSystem", "sstProvisioningExecutionDate", "sstProvisioningMode", "sstProvisioningState", "sstRegion", ] lifecycle { prevent_destroy = true } } ```
Generated execution plan: ```tf Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # ldap_object.ldap_write will be created + resource "ldap_object" "ldap_write" { + attributes = { + "sstOpenStackId" = [ + "***REDACTED***", ] } + dn = "uid=4000000,ou=domains,ou=openstack,ou=services,dc=stoney-cloud,dc=org" + id = (known after apply) + ignore_changes = [ + "description", + "sstBelongsToCustomerUID", + "sstBelongsToDomainID", + "sstBelongsToProjectID", + "sstBelongsToResellerUID", + "sstBusinessLogicRoleName", + "sstCancellationDate", + "sstDisplayName", + "sstEnvironment", + "sstIsActive", + "sstIsIaaSDomain", + "sstIsIaaSProject", + "sstNetworkDomainName", + "sstNetworkHostname", + "sstOpenStackName", + "sstOperatingSystem", + "sstProvisioningExecutionDate", + "sstProvisioningMode", + "sstProvisioningState", + "sstRegion", ] + object_classes = [ + "top", + "sstOpenStackDomain", + "sstProvisioning", + "sstRelationship", ] } Plan: 1 to add, 0 to change, 0 to destroy. ```
Deployment error: ```tf ldap_object.ldap_write: Creating... ╷ │ Error: Can not add resource │ │ with ldap_object.ldap_write, │ on main.tf line 17, in resource "ldap_object" "ldap_write": │ 17: resource "ldap_object" "ldap_write" { │ │ LDAP server reported: LDAP Result Code 65 "Object Class Violation": object │ class 'sstOpenStackDomain' requires attribute 'sstIsActive' ╵ ```

We tried to work around this problem by importing the existing LDAP entry to the state. The following lines have been appended to the Terraform example above (requires Terraform >= 1.5.0):

Import state: ```tf import { to = ldap_object.ldap_write id = "uid=4000000,ou=domains,ou=openstack,ou=services,dc=stoney-cloud,dc=org" } ```

But for some reason Terraform wants to remove all attributes then in the build plan and will fail because it cannot remove attributes like uid due the restricted permission.

Deployment error: ```tf Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: ~ update in-place Terraform will perform the following actions: # ldap_object.ldap_write will be updated in-place # (imported from "uid=4000000,ou=domains,ou=openstack,ou=services,dc=stoney-cloud,dc=org") ~ resource "ldap_object" "ldap_write" { ~ attributes = { - "description" = [ - "***REDACTED***", ] -> null - "sstBelongsToCustomerUID" = [ - "***REDACTED***", ] -> null - "sstBelongsToResellerUID" = [ - "***REDACTED***", ] -> null - "sstIsActive" = [ - "***REDACTED***", ] -> null - "sstIsIaaSDomain" = [ - "***REDACTED***", ] -> null "sstOpenStackId" = [ "***REDACTED***", ] - "sstOpenStackName" = [ - "***REDACTED***", ] -> null - "sstProvisioningExecutionDate" = [ - "***REDACTED***", ] -> null - "sstProvisioningMode" = [ - "***REDACTED***", ] -> null - "sstProvisioningState" = [ - "***REDACTED***", ] -> null - "uid" = [ - "4000000", ] -> null } dn = "uid=4000000,ou=domains,ou=openstack,ou=services,dc=stoney-cloud,dc=org" id = "uid=4000000,ou=domains,ou=openstack,ou=services,dc=stoney-cloud,dc=org" + ignore_changes = [ + "description", + "sstBelongsToCustomerUID", + "sstBelongsToDomainID", + "sstBelongsToProjectID", + "sstBelongsToResellerUID", + "sstBusinessLogicRoleName", + "sstCancellationDate", + "sstDisplayName", + "sstEnvironment", + "sstIsActive", + "sstIsIaaSDomain", + "sstIsIaaSProject", + "sstNetworkDomainName", + "sstNetworkHostname", + "sstOpenStackName", + "sstOperatingSystem", + "sstProvisioningExecutionDate", + "sstProvisioningMode", + "sstProvisioningState", + "sstRegion", ] object_classes = [ "top", "sstOpenStackDomain", "sstProvisioning", "sstRelationship", ] } Plan: 1 to import, 0 to add, 1 to change, 0 to destroy. ```

Expected behaviour:

Ignored attributes should not be touched by Terraform. If no values are specified, it should not import them into the state (they may also contain sensitive values like in your example). Maybe attributes that are not managed by Terraform shouldn't be touched at all if you don't create the whole entry. This may also need some clarification in documentation.

The showcase in this issue is related to: #36

dploeger commented 1 year ago

To clarify this: Terraform will manage all attributes of the ldap entry except those listed in ignore_changes. You need to import an entry to manage it with Terraform.

But what you're saying is that Terraform would also manage attributes of an imported entry although they're listed in ignore_changes? Did I get that right?

sst-yde commented 1 year ago

Yes, exactly. Even though the entries are listed in ignore_changes, Terraform tries to manage these entries.

x4e-jonas commented 1 year ago

I'm working on the same issue as @sst-yde who is currently out of office. The deployment error above clearly shows in the build plan that Terraform tries to manage all attributes, even those that should be ignored.

On the server side, we also see a list of various attributes in the modify request, but in the example only sstOpenStackId is specified to be set (updated) by Terraform.

OpenLDAP log: ``` hostname-01 archive # grep 1944672 slapd.log-20230822 Aug 22 15:54:41 hostname-01 slapd[9047]: conn=1944672 fd=83 ACCEPT from IP=203.0.113.15:59734 (IP=0.0.0.0:636) Aug 22 15:54:41 hostname-01 slapd[9047]: conn=1944672 fd=83 TLS established tls_ssf=128 ssf=128 Aug 22 15:54:41 hostname-01 slapd[9047]: conn=1944672 op=0 BIND dn="cn=redacted,ou=services,ou=administration,o=stoney-cloud,c=org" method=128 Aug 22 15:54:41 hostname-01 slapd[9047]: conn=1944672 op=0 BIND dn="cn=redacted,ou=services,ou=administration,o=stoney-cloud,c=org" mech=SIMPLE ssf=0 Aug 22 15:54:41 hostname-01 slapd[9047]: conn=1944672 op=0 RESULT tag=97 err=0 text= Aug 22 15:54:41 hostname-01 slapd[9047]: conn=1944672 op=1 MOD dn="uid=4000000,ou=domains,ou=openstack,ou=services,o=stoney-cloud,c=org" Aug 22 15:54:41 hostname-01 slapd[9047]: conn=1944672 op=1 MOD attr=sstProvisioningState sstBelongsToResellerUID sstProvisioningMode uid description sstBelongsToCustomerUID sstProvisioningExecutionDate sstIsActive sstIsIaaSDomain sstOpenStackName Aug 22 15:54:41 hostname-01 slapd[9047]: conn=1944672 op=1 RESULT tag=103 err=64 text=naming attribute 'uid' is not present in entry Aug 22 15:54:41 hostname-01 slapd[9047]: conn=1944672 fd=83 closed (connection lost) ``` For example `sstIsActive` is in the `ignore_list`, but shows up in the modify request.

We tried several scenarios, with and without importing the state, swapping different attributes in the ignore_changes list and even updated the read/write permissions for this user. It always ended up with the same issue, that the attributes in the ignore_changes list are showing up in the build plan to be changed or deleted.

Our use case is that we want to only update (respectively let Terraform manage) a single attribute, leaving everything else as it is. So therefore we specified everything else in the ignore_changes list.

Please let us know if you need any additional information.

dploeger commented 1 year ago

The problem appears to only occur if you don't specify the attributes. So a current workaround could be to just add them to the "attributes" map with bogus values like "IGNORED" or something.

Working on a fix.

dploeger commented 1 year ago

I've implemented a fix (hopefully) in https://github.com/dodevops/terraform-provider-ldap/tree/feature/logging-and-replace

Can you please try it?

x4e-jonas commented 1 year ago

Thank you for the update!

Unfortunately this did not fix the issue for us. With the patech applied, the provider fails in the planning step:

Planning failed. Terraform encountered an error while generating this plan.
╷
│ Error: Provider produced invalid plan
│ 
│ Provider "registry.terraform.io/dodevops/ldap" planned an invalid value for
│ ldap_object.ldap_write_openstack.attributes: planned value
│ cty.MapVal(map[string]cty.Value{"description":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}),
│ "sstOpenStackId":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")})})
│ does not match config value
│ cty.MapVal(map[string]cty.Value{"sstOpenStackId":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")})})
│ nor prior value
│ cty.MapVal(map[string]cty.Value{"description":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}),
│ "sstBelongsToCustomerUID":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}),
│ "sstBelongsToDomainID":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}),
│ "sstBelongsToResellerUID":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}),
│ "sstBillable":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}),
│ "sstConsolidatedBill":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}),
│ "sstIsActive":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}),
│ "sstIsIaaSProject":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}),
│ "sstNetworkHostnameFormat":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}),
│ "sstOpenStackId":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}),
│ "sstOpenStackName":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}),
│ "sstProvisioningExecutionDate":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}),
│ "sstProvisioningMode":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}),
│ "sstProvisioningState":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}),
│ "sstRegion":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}),
│ "uid":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")})}).
│ 
│ This is a bug in the provider, which should be reported in the provider's
│ own issue tracker.
╵

Respectively the output below when setting everything else to IGNORED:

Expand log: ```tf Planning failed. Terraform encountered an error while generating this plan. ╷ │ Error: Provider produced invalid plan │ │ Provider "registry.terraform.io/dodevops/ldap" planned an invalid value for │ ldap_object.ldap_write_openstack.attributes: planned value │ cty.MapVal(map[string]cty.Value{"description":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}), │ "sstOpenStackId":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}), │ "sstbelongstocustomeruid":cty.NullVal(cty.List(cty.String)), │ "sstbelongstodomainid":cty.NullVal(cty.List(cty.String)), │ "sstbelongstoprojectid":cty.NullVal(cty.List(cty.String)), │ "sstbelongstoreselleruid":cty.NullVal(cty.List(cty.String)), │ "sstbillable":cty.NullVal(cty.List(cty.String)), │ "sstbusinesslogicrolename":cty.NullVal(cty.List(cty.String)), │ "sstcancellationdate":cty.NullVal(cty.List(cty.String)), │ "sstconsolidatedbill":cty.NullVal(cty.List(cty.String)), │ "sstdisplayname":cty.NullVal(cty.List(cty.String)), │ "sstenvironment":cty.NullVal(cty.List(cty.String)), │ "sstisactive":cty.NullVal(cty.List(cty.String)), │ "sstisiaasproject":cty.NullVal(cty.List(cty.String)), │ "sstnetworkdomainname":cty.NullVal(cty.List(cty.String)), │ "sstnetworkhostname":cty.NullVal(cty.List(cty.String)), │ "sstnetworkhostnameformat":cty.NullVal(cty.List(cty.String)), │ "sstnetworkhostnamenextfreenumber":cty.NullVal(cty.List(cty.String)), │ "sstopenstackname":cty.NullVal(cty.List(cty.String)), │ "sstoperatingsystem":cty.NullVal(cty.List(cty.String)), │ "sstprovisioningexecutiondate":cty.NullVal(cty.List(cty.String)), │ "sstprovisioningmode":cty.NullVal(cty.List(cty.String)), │ "sstprovisioningstate":cty.NullVal(cty.List(cty.String)), │ "sstregion":cty.NullVal(cty.List(cty.String))}) does not match config value │ cty.MapVal(map[string]cty.Value{"description":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}), │ "sstOpenStackId":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}), │ "sstbelongstocustomeruid":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}), │ "sstbelongstodomainid":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}), │ "sstbelongstoprojectid":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}), │ "sstbelongstoreselleruid":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}), │ "sstbillable":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}), │ "sstbusinesslogicrolename":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}), │ "sstcancellationdate":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}), │ "sstconsolidatedbill":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}), │ "sstdisplayname":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}), │ "sstenvironment":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}), │ "sstisactive":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}), │ "sstisiaasproject":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}), │ "sstnetworkdomainname":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}), │ "sstnetworkhostname":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}), │ "sstnetworkhostnameformat":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}), │ "sstnetworkhostnamenextfreenumber":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}), │ "sstopenstackname":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}), │ "sstoperatingsystem":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}), │ "sstprovisioningexecutiondate":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}), │ "sstprovisioningmode":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}), │ "sstprovisioningstate":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}), │ "sstregion":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")})}) nor prior │ value │ cty.MapVal(map[string]cty.Value{"description":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}), │ "sstBelongsToCustomerUID":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}), │ "sstBelongsToDomainID":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}), │ "sstBelongsToResellerUID":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}), │ "sstBillable":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}), │ "sstConsolidatedBill":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}), │ "sstIsActive":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}), │ "sstIsIaaSProject":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}), │ "sstNetworkHostnameFormat":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}), │ "sstOpenStackId":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}), │ "sstOpenStackName":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}), │ "sstProvisioningExecutionDate":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}), │ "sstProvisioningMode":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}), │ "sstProvisioningState":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}), │ "sstRegion":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")}), │ "uid":cty.ListVal([]cty.Value{cty.StringVal("REDACTED")})}). │ │ This is a bug in the provider, which should be reported in the provider's │ own issue tracker. ╵ ```
dploeger commented 9 months ago

This is really a hard one and I sadly don't have a solution currently. 😞

dploeger commented 3 months ago

I'm sorry to tell you, but we have decided to discontinue this provider as we're not using it anymore. If you like or know somebody who might like to adopt it, we're open for that.

Thank you.