github.com/GoogleCloudPlatform/terraformer@v0.8.18/providers/keycloak/generator.go (about)

     1  // Copyright 2018 The Terraformer Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package keycloak
    16  
    17  import (
    18  	"errors"
    19  	"fmt"
    20  	"sort"
    21  	"strings"
    22  
    23  	"github.com/GoogleCloudPlatform/terraformer/terraformutils"
    24  	"github.com/mrparkers/terraform-provider-keycloak/keycloak"
    25  )
    26  
    27  type RealmGenerator struct {
    28  	KeycloakService
    29  }
    30  
    31  func (g *RealmGenerator) InitResources() error {
    32  	var realms []*keycloak.Realm
    33  	var realmsGroups []*keycloak.Group
    34  
    35  	// Connect to keycloak instance
    36  	kck, err := keycloak.NewKeycloakClient(g.GetArgs()["url"].(string), g.GetArgs()["client_id"].(string), g.GetArgs()["client_secret"].(string), g.GetArgs()["realm"].(string), "", "", true, g.GetArgs()["client_timeout"].(int), g.GetArgs()["root_ca_certificate"].(string), g.GetArgs()["tls_insecure_skip_verify"].(bool))
    37  	if err != nil {
    38  		return errors.New("keycloak: could not connect to Keycloak")
    39  	}
    40  
    41  	// Get realm resources
    42  	target := g.GetArgs()["target"].(string)
    43  	if target == "" {
    44  		realms, err = kck.GetRealms()
    45  		if err != nil {
    46  			return errors.New("keycloak: could not get realms attributes in Keycloak")
    47  		}
    48  	} else {
    49  		realm, err := kck.GetRealm(target)
    50  		if err != nil {
    51  			return errors.New("keycloak: could not get " + target + " realm attributes in Keycloak")
    52  		}
    53  		realms = append(realms, realm)
    54  	}
    55  	g.Resources = append(g.Resources, g.createRealmResources(realms)...)
    56  
    57  	// For each realm, get resources
    58  	for _, realm := range realms {
    59  		// Get required actions resources
    60  		requiredActions, err := kck.GetRequiredActions(realm.Id)
    61  		if err != nil {
    62  			return fmt.Errorf("keycloak: could not get required actions of realm %s in Keycloak. err: %w", realm.Id, err)
    63  		}
    64  		g.Resources = append(g.Resources, g.createRequiredActionResources(requiredActions)...)
    65  
    66  		// Get top-level authentication flows resources
    67  		authenticationFlows, err := kck.ListAuthenticationFlows(realm.Id)
    68  		if err != nil {
    69  			return fmt.Errorf("keycloak: could not get authentication flows of realm %s in Keycloak. err: %w", realm.Id, err)
    70  		}
    71  		g.Resources = append(g.Resources, g.createAuthenticationFlowResources(authenticationFlows)...)
    72  
    73  		// For each authentication flow, get subFlow, execution and execution config resources
    74  		for _, topLevelAuthenticationFlow := range authenticationFlows {
    75  			authenticationSubFlowOrExecutions, err := kck.ListAuthenticationExecutions(realm.Id, topLevelAuthenticationFlow.Alias)
    76  			if err != nil {
    77  				return fmt.Errorf("keycloak: could not get authentication executions of authentication flow %s of realm %s in Keycloak. err: %w",
    78  					topLevelAuthenticationFlow.Alias, realm.Id, err)
    79  			}
    80  
    81  			var stack []*keycloak.AuthenticationExecutionInfo
    82  			parentFlowAlias := topLevelAuthenticationFlow.Alias
    83  
    84  			for _, authenticationSubFlowOrExecution := range authenticationSubFlowOrExecutions {
    85  
    86  				// Find the parent flow alias
    87  				if len(stack) > 0 {
    88  					previous := stack[len(stack)-1]
    89  					if authenticationSubFlowOrExecution.Level < previous.Level {
    90  						// Find the last sub flow/execution for the current level
    91  						stack = stack[:authenticationSubFlowOrExecution.Level+1]
    92  						previous = stack[len(stack)-1]
    93  					}
    94  					if authenticationSubFlowOrExecution.Level == previous.Level {
    95  						// Same level sub flow/execution, it means that the sub flow/execution has same parent flow of the last sub flow/execution
    96  						parentFlowAlias = previous.ParentFlowAlias
    97  
    98  					} else if authenticationSubFlowOrExecution.Level > previous.Level {
    99  						// Deep level sub flow/execution, it means that the parent flow is the last sub flow/execution
   100  						if previous.AuthenticationFlow {
   101  							parentFlowAlias = previous.Alias
   102  						} else {
   103  							return errors.New("keycloak: invalid parent sub flow, it should be a sub flow but it's an execution")
   104  						}
   105  					}
   106  				}
   107  
   108  				var resource terraformutils.Resource
   109  
   110  				switch authenticationSubFlowOrExecution.AuthenticationFlow {
   111  				case true:
   112  					authenticationSubFlow, err := kck.GetAuthenticationSubFlow(realm.Id, parentFlowAlias, authenticationSubFlowOrExecution.FlowId)
   113  					if err != nil {
   114  						return fmt.Errorf("keycloak: could not get authentication subflow %s of realm %s in Keycloak. err: %w",
   115  							authenticationSubFlowOrExecution.FlowId, realm.Id, err)
   116  					}
   117  
   118  					// Need to store the alias and parent flow alias
   119  					authenticationSubFlowOrExecution.Alias = authenticationSubFlow.Alias
   120  					authenticationSubFlowOrExecution.ParentFlowAlias = parentFlowAlias
   121  
   122  					resource = g.createAuthenticationSubFlowResource(authenticationSubFlow)
   123  					g.Resources = append(g.Resources, resource)
   124  
   125  				case false:
   126  					authenticationExecution, err := kck.GetAuthenticationExecution(realm.Id, parentFlowAlias, authenticationSubFlowOrExecution.Id)
   127  					if err != nil {
   128  						return fmt.Errorf("keycloak: could not get authentication execution %s of realm %s in Keycloak. err: %w",
   129  							authenticationSubFlowOrExecution.Id, realm.Id, err)
   130  					}
   131  
   132  					// Need to store the parent flow alias
   133  					authenticationSubFlowOrExecution.ParentFlowAlias = parentFlowAlias
   134  
   135  					resource = g.createAuthenticationExecutionResource(authenticationExecution)
   136  					g.Resources = append(g.Resources, resource)
   137  
   138  					if authenticationSubFlowOrExecution.AuthenticationConfig != "" {
   139  						authenticationExecutionConfig := &keycloak.AuthenticationExecutionConfig{
   140  							RealmId:     realm.Id,
   141  							Id:          authenticationSubFlowOrExecution.AuthenticationConfig,
   142  							ExecutionId: authenticationSubFlowOrExecution.Id,
   143  						}
   144  						err := kck.GetAuthenticationExecutionConfig(authenticationExecutionConfig)
   145  						if err != nil {
   146  							return fmt.Errorf("keycloak: could not get authentication execution config %s of realm %s in Keycloak. err: %w",
   147  								authenticationExecutionConfig.Id, realm.Id, err)
   148  						}
   149  
   150  						g.Resources = append(g.Resources, g.createAuthenticationExecutionConfigResource(authenticationExecutionConfig))
   151  					}
   152  				}
   153  
   154  				if len(stack) > 0 && authenticationSubFlowOrExecution.Index > 0 {
   155  					previous := stack[len(stack)-1]
   156  					var resouceType string
   157  					var resouceName string
   158  					if previous.AuthenticationFlow {
   159  						resouceType = "keycloak_authentication_subflow"
   160  						resouceName = "authentication_subflow_" +
   161  							normalizeResourceName(realm.Id) + "_" + normalizeResourceName(previous.FlowId)
   162  					} else {
   163  						resouceType = "keycloak_authentication_execution"
   164  						resouceName = "authentication_execution_" +
   165  							normalizeResourceName(realm.Id) + "_" + normalizeResourceName(previous.Id)
   166  					}
   167  					resource.AdditionalFields["depends_on"] = []string{resouceType + "." + terraformutils.TfSanitize(resouceName)}
   168  				}
   169  
   170  				// Stack the current sub flow/execution
   171  				if len(stack) > 0 && stack[len(stack)-1].Level == authenticationSubFlowOrExecution.Level {
   172  					// Replace it if it's same level
   173  					stack[len(stack)-1] = authenticationSubFlowOrExecution
   174  				} else {
   175  					stack = append(stack, authenticationSubFlowOrExecution)
   176  				}
   177  			}
   178  		}
   179  
   180  		// Get custom federations resources
   181  		// TODO: support kerberos user federation
   182  		customUserFederations, err := kck.GetCustomUserFederations(realm.Id)
   183  		if err != nil {
   184  			return errors.New("keycloak: could not get custom user federations of realm " + realm.Id + " in Keycloak")
   185  		}
   186  		g.Resources = append(g.Resources, g.createCustomUserFederationResources(customUserFederations)...)
   187  
   188  		// For each custom federation, get mappers resources
   189  		for _, customUserFederation := range *customUserFederations {
   190  			if customUserFederation.ProviderId == "ldap" {
   191  				mappers, err := kck.GetLdapUserFederationMappers(realm.Id, customUserFederation.Id)
   192  				if err != nil {
   193  					return errors.New("keycloak: could not get mappers of ldap user federation " + customUserFederation.Name + " of realm " + realm.Id + " in Keycloak")
   194  				}
   195  				g.Resources = append(g.Resources, g.createLdapMapperResources(realm.Id, customUserFederation.Name, mappers)...)
   196  			}
   197  		}
   198  
   199  		// Get groups tree and default groups resources
   200  		realmGroups, err := kck.GetGroups(realm.Id)
   201  		if err != nil {
   202  			return errors.New("keycloak: could not get groups of realm " + realm.Id + " in Keycloak")
   203  		}
   204  		realmsGroups = append(realmsGroups, realmGroups...)
   205  		g.Resources = append(g.Resources, g.createDefaultGroupResource(realm.Id))
   206  
   207  		// Get users resources
   208  		realmUsers, err := kck.GetUsers(realm.Id)
   209  		if err != nil {
   210  			return errors.New("keycloak: could not get users of realm " + realm.Id + " in Keycloak")
   211  		}
   212  		g.Resources = append(g.Resources, g.createUserResources(realmUsers)...)
   213  
   214  		// Get realm open id client scopes resources
   215  		realmScopes, err := kck.ListOpenidClientScopesWithFilter(realm.Id, func(scope *keycloak.OpenidClientScope) bool { return true })
   216  		if err != nil {
   217  			return errors.New("keycloak: could not get realm scopes of realm " + realm.Id + " in Keycloak")
   218  		}
   219  		g.Resources = append(g.Resources, g.createScopeResources(realm.Id, realmScopes)...)
   220  
   221  		// Get open id clients
   222  		realmClients, err := kck.GetOpenidClients(realm.Id, true)
   223  		if err != nil {
   224  			return errors.New("keycloak: could not get open id clients of realm " + realm.Id + " in Keycloak")
   225  		}
   226  		g.Resources = append(g.Resources, g.createOpenIDClientResources(realmClients)...)
   227  
   228  		// For earch open id client, get resources
   229  		mapServiceAccountIds := map[string]map[string]string{}
   230  		mapContainerIDs := map[string]string{}
   231  		mapClientIDs := map[string]string{}
   232  		for _, client := range realmClients {
   233  			mapClientIDs[client.Id] = client.ClientId
   234  			mapContainerIDs[client.Id] = "_" + client.ClientId
   235  
   236  			// Get open id client protocol mappers resources
   237  			clientMappers, err := kck.GetGenericClientProtocolMappers(realm.Id, client.Id)
   238  			if err != nil {
   239  				return errors.New("keycloak: could not get protocol mappers of open id client " + client.ClientId + " of realm " + realm.Id + " in Keycloak")
   240  			}
   241  			g.Resources = append(g.Resources, g.createOpenIDProtocolMapperResources(client.ClientId, clientMappers)...)
   242  
   243  			// Get open id client default scopes resources
   244  			clientScopes, err := kck.GetOpenidDefaultClientScopes(realm.Id, client.Id)
   245  			if err != nil {
   246  				return errors.New("keycloak: could not get default client scopes of open id client " + client.ClientId + " of realm " + realm.Id + " in Keycloak")
   247  			}
   248  			if len(*clientScopes) > 0 {
   249  				g.Resources = append(g.Resources, g.createOpenidClientScopesResources(realm.Id, client.Id, client.ClientId, "default", clientScopes))
   250  			}
   251  
   252  			// Get open id client optional scopes resources
   253  			clientScopes, err = kck.GetOpenidOptionalClientScopes(realm.Id, client.Id)
   254  			if err != nil {
   255  				return errors.New("keycloak: could not get optional client scopes of open id client " + client.ClientId + " of realm " + realm.Id + " in Keycloak")
   256  			}
   257  			if len(*clientScopes) > 0 {
   258  				g.Resources = append(g.Resources, g.createOpenidClientScopesResources(realm.Id, client.Id, client.ClientId, "optional", clientScopes))
   259  			}
   260  
   261  			// Prepare a slice to be able to link roles associated to service account roles to be associated to the open id client, only if service accounts are enabled
   262  			if !client.ServiceAccountsEnabled {
   263  				continue
   264  			}
   265  			serviceAccountUser, err := kck.GetOpenidClientServiceAccountUserId(realm.Id, client.Id)
   266  			if err != nil {
   267  				return errors.New("keycloak: could not get service account user associated to open id client " + client.ClientId + " of realm " + realm.Id + " in Keycloak")
   268  			}
   269  			mapServiceAccountIds[serviceAccountUser.Id] = map[string]string{}
   270  			mapServiceAccountIds[serviceAccountUser.Id]["Id"] = client.Id
   271  			mapServiceAccountIds[serviceAccountUser.Id]["ClientId"] = client.ClientId
   272  		}
   273  
   274  		// Get open id client roles
   275  		clientRoles, err := kck.GetClientRoles(realm.Id, realmClients)
   276  		if err != nil {
   277  			return errors.New("keycloak: could not get open id clients roles of realm " + realm.Id + " in Keycloak")
   278  		}
   279  
   280  		// Get roles
   281  		realmRoles, err := kck.GetRealmRoles(realm.Id)
   282  		if err != nil {
   283  			return errors.New("keycloak: could not get realm roles of realm " + realm.Id + " in Keycloak")
   284  		}
   285  
   286  		// Set ContainerId of the roles, for realm = "", for open id clients = "_" + client.ClientId
   287  		// and get roles resources
   288  		mapContainerIDs[realm.Id] = ""
   289  		roles := append(clientRoles, realmRoles...)
   290  		for _, role := range roles {
   291  			role.ContainerId = mapContainerIDs[role.ContainerId]
   292  		}
   293  		g.Resources = append(g.Resources, g.createRoleResources(roles)...)
   294  
   295  		// Get service account roles resources
   296  		usersInRole, err := kck.GetClientRoleUsers(realm.Id, clientRoles)
   297  		if err != nil {
   298  			return errors.New("keycloak: could not get users roles of realm " + realm.Id + " in Keycloak")
   299  		}
   300  		g.Resources = append(g.Resources, g.createServiceAccountClientRolesResources(realm.Id, clientRoles, *usersInRole, mapServiceAccountIds, mapClientIDs)...)
   301  	}
   302  
   303  	// Parse the groups trees, and get all the groups
   304  	// Get groups resources
   305  	groups := g.flattenGroups(realmsGroups, "")
   306  	g.Resources = append(g.Resources, g.createGroupResources(groups)...)
   307  
   308  	// For each group, get group memberships and roles resources
   309  	for _, group := range groups {
   310  		// Get group members resources
   311  		members, err := kck.GetGroupMembers(group.RealmId, group.Id)
   312  		if err != nil {
   313  			return errors.New("keycloak: could not get group members of group " + group.Name + " in Keycloak")
   314  		}
   315  		if len(members) > 0 {
   316  			groupMembers := make([]string, len(members))
   317  			for k, member := range members {
   318  				groupMembers[k] = member.Username
   319  			}
   320  			g.Resources = append(g.Resources, g.createGroupMembershipsResource(group.RealmId, group.Id, group.Name, groupMembers))
   321  		}
   322  
   323  		// Get group roles resources
   324  		// For realm roles and open id clients roles
   325  		groupDetails, err := kck.GetGroup(group.RealmId, group.Id)
   326  		if err != nil {
   327  			return errors.New("keycloak: could not get details about group " + group.Name + " in Keycloak")
   328  		}
   329  		groupRoles := []string{}
   330  		if len(groupDetails.RealmRoles) > 0 {
   331  			groupRoles = append(groupRoles, groupDetails.RealmRoles...)
   332  		}
   333  		if len(groupDetails.ClientRoles) > 0 {
   334  			for _, clientRoles := range groupDetails.ClientRoles {
   335  				groupRoles = append(groupRoles, clientRoles...)
   336  			}
   337  		}
   338  		if len(groupRoles) > 0 {
   339  			g.Resources = append(g.Resources, g.createGroupRolesResource(group.RealmId, group.Id, group.Name, groupRoles))
   340  		}
   341  	}
   342  
   343  	return nil
   344  }
   345  
   346  func (g *RealmGenerator) PostConvertHook() error {
   347  	mapRealmIDs := map[string]string{}
   348  	mapUserFederationIDs := map[string]string{}
   349  	mapGroupIDs := map[string]string{}
   350  	mapClientIDs := map[string]string{}
   351  	mapClientNames := map[string]string{}
   352  	mapClientClientIDs := map[string]string{}
   353  	mapClientClientNames := map[string]string{}
   354  	mapServiceAccountUserIDs := map[string]string{}
   355  	mapRoleIDs := map[string]string{}
   356  	mapClientRoleNames := map[string]string{}
   357  	mapClientRoleShortNames := map[string]string{}
   358  	mapScopeNames := map[string]string{}
   359  	mapUserNames := map[string]string{}
   360  	mapGroupNames := map[string]string{}
   361  	mapAuthenticationFlowAliases := map[string]string{}
   362  	mapAuthenticationExecutionIDs := map[string]string{}
   363  
   364  	// Set slices to be able to map IDs with Terraform variables
   365  	for _, r := range g.Resources {
   366  		if r.InstanceInfo.Type != "keycloak_realm" &&
   367  			r.InstanceInfo.Type != "keycloak_ldap_user_federation" &&
   368  			r.InstanceInfo.Type != "keycloak_group" &&
   369  			r.InstanceInfo.Type != "keycloak_openid_client" &&
   370  			r.InstanceInfo.Type != "keycloak_role" &&
   371  			r.InstanceInfo.Type != "keycloak_openid_client_scope" &&
   372  			r.InstanceInfo.Type != "keycloak_user" &&
   373  			r.InstanceInfo.Type != "keycloak_authentication_flow" &&
   374  			r.InstanceInfo.Type != "keycloak_authentication_subflow" &&
   375  			r.InstanceInfo.Type != "keycloak_authentication_execution" {
   376  			continue
   377  		}
   378  		if r.InstanceInfo.Type == "keycloak_realm" {
   379  			mapRealmIDs[r.InstanceState.ID] = "${" + r.InstanceInfo.Type + "." + r.ResourceName + ".id}"
   380  		}
   381  		if r.InstanceInfo.Type == "keycloak_ldap_user_federation" {
   382  			mapUserFederationIDs[r.Item["realm_id"].(string)+"_"+r.InstanceState.ID] = "${" + r.InstanceInfo.Type + "." + r.ResourceName + ".id}"
   383  		}
   384  		if r.InstanceInfo.Type == "keycloak_group" {
   385  			mapGroupIDs[r.Item["realm_id"].(string)+"_"+r.InstanceState.ID] = "${" + r.InstanceInfo.Type + "." + r.ResourceName + ".id}"
   386  			mapGroupNames[r.Item["realm_id"].(string)+"_"+r.Item["name"].(string)] = "${" + r.InstanceInfo.Type + "." + r.ResourceName + ".name}"
   387  		}
   388  		if r.InstanceInfo.Type == "keycloak_openid_client" {
   389  			mapClientIDs[r.Item["realm_id"].(string)+"_"+r.InstanceState.ID] = "${" + r.InstanceInfo.Type + "." + r.ResourceName + ".id}"
   390  			mapClientNames[r.Item["realm_id"].(string)+"_"+r.InstanceState.ID] = r.Item["client_id"].(string)
   391  			mapClientClientNames[r.Item["realm_id"].(string)+"_"+r.InstanceState.ID] = "${" + r.InstanceInfo.Type + "." + r.ResourceName + ".client_id}"
   392  			mapClientClientIDs[r.Item["realm_id"].(string)+"_"+r.InstanceState.Attributes["client_id"]] = "${" + r.InstanceInfo.Type + "." + r.ResourceName + ".client_id}"
   393  			if _, exist := r.InstanceState.Attributes["service_account_user_id"]; exist {
   394  				mapServiceAccountUserIDs[r.Item["realm_id"].(string)+"_"+r.InstanceState.Attributes["service_account_user_id"]] = "${" + r.InstanceInfo.Type + "." + r.ResourceName + ".service_account_user_id}"
   395  			}
   396  		}
   397  		if r.InstanceInfo.Type == "keycloak_role" {
   398  			mapRoleIDs[r.Item["realm_id"].(string)+"_"+r.InstanceState.ID] = "${" + r.InstanceInfo.Type + "." + r.ResourceName + ".id}"
   399  			if _, exist := r.Item["client_id"]; exist {
   400  				mapClientRoleNames[r.Item["realm_id"].(string)+"_"+mapClientNames[r.Item["realm_id"].(string)+"_"+r.Item["client_id"].(string)]+"."+r.Item["name"].(string)] = mapClientClientNames[r.Item["realm_id"].(string)+"_"+r.Item["client_id"].(string)] + ".${" + r.InstanceInfo.Type + "." + r.ResourceName + ".name}"
   401  				mapClientRoleShortNames[r.Item["realm_id"].(string)+"_"+mapClientNames[r.Item["realm_id"].(string)+"_"+r.Item["client_id"].(string)]+"."+r.Item["name"].(string)] = "${" + r.InstanceInfo.Type + "." + r.ResourceName + ".name}"
   402  			} else {
   403  				mapClientRoleNames[r.Item["realm_id"].(string)+"_"+r.Item["name"].(string)] = "${" + r.InstanceInfo.Type + "." + r.ResourceName + ".name}"
   404  			}
   405  		}
   406  		if r.InstanceInfo.Type == "keycloak_openid_client_scope" {
   407  			mapScopeNames[r.Item["realm_id"].(string)+"_"+r.Item["name"].(string)] = "${" + r.InstanceInfo.Type + "." + r.ResourceName + ".name}"
   408  		}
   409  		if r.InstanceInfo.Type == "keycloak_user" {
   410  			mapUserNames[r.Item["realm_id"].(string)+"_"+r.Item["username"].(string)] = "${" + r.InstanceInfo.Type + "." + r.ResourceName + ".username}"
   411  		}
   412  		if r.InstanceInfo.Type == "keycloak_authentication_flow" || r.InstanceInfo.Type == "keycloak_authentication_subflow" {
   413  			mapAuthenticationFlowAliases[r.Item["realm_id"].(string)+"_"+r.Item["alias"].(string)] = "${" + r.InstanceInfo.Type + "." + r.ResourceName + ".alias}"
   414  		}
   415  		if r.InstanceInfo.Type == "keycloak_authentication_execution" {
   416  			mapAuthenticationExecutionIDs[r.Item["realm_id"].(string)+"_"+r.InstanceState.ID] = "${" + r.InstanceInfo.Type + "." + r.ResourceName + ".id}"
   417  		}
   418  	}
   419  
   420  	// For each resources, modify import if needed...
   421  	for i, r := range g.Resources {
   422  		// Escape keycloak text inputs not to get unpredictable results or errors when Terraform will try to interpret variables ($ vs $$)
   423  		// TODO: ensure that we escape all existing fields
   424  		if strings.Contains(r.InstanceState.Attributes["consent_screen_text"], "$") {
   425  			g.Resources[i].Item["consent_screen_text"] = strings.ReplaceAll(r.InstanceState.Attributes["consent_screen_text"], "$", "$$")
   426  		}
   427  		if strings.Contains(r.InstanceState.Attributes["name"], "$") {
   428  			g.Resources[i].Item["name"] = strings.ReplaceAll(r.InstanceState.Attributes["name"], "$", "$$")
   429  		}
   430  		if strings.Contains(r.InstanceState.Attributes["description"], "$") {
   431  			g.Resources[i].Item["description"] = strings.ReplaceAll(r.InstanceState.Attributes["description"], "$", "$$")
   432  		}
   433  		if strings.Contains(r.InstanceState.Attributes["root_url"], "$") {
   434  			g.Resources[i].Item["root_url"] = strings.ReplaceAll(r.InstanceState.Attributes["root_url"], "$", "$$")
   435  		}
   436  
   437  		// Sort supported_locales to get reproducible results for keycloak_realm resources
   438  		if r.InstanceInfo.Type == "keycloak_realm" {
   439  			if _, exist := r.Item["internationalization"]; exist {
   440  				for _, v := range r.Item["internationalization"].([]interface{}) {
   441  					sortedSupportedLocales := make([]string, len(v.(map[string]interface{})["supported_locales"].([]interface{})))
   442  					for k, vv := range v.(map[string]interface{})["supported_locales"].([]interface{}) {
   443  						sortedSupportedLocales[k] = vv.(string)
   444  					}
   445  					sort.Strings(sortedSupportedLocales)
   446  					v.(map[string]interface{})["supported_locales"] = sortedSupportedLocales
   447  				}
   448  			}
   449  		}
   450  
   451  		// Sort group_ids to get reproducible results for keycloak_default_groups resources
   452  		// Set an empty string slice if the attribute doesn't exist as it is mandatory
   453  		if r.InstanceInfo.Type == "keycloak_default_groups" {
   454  			if _, exist := r.Item["group_ids"]; exist {
   455  				renamedGroupIDs := make([]string, len(r.Item["group_ids"].([]interface{})))
   456  				for k, v := range r.Item["group_ids"].([]interface{}) {
   457  					renamedGroupIDs[k] = mapGroupIDs[r.Item["realm_id"].(string)+"_"+v.(string)]
   458  				}
   459  				sort.Strings(renamedGroupIDs)
   460  				g.Resources[i].Item["group_ids"] = renamedGroupIDs
   461  			} else {
   462  				g.Resources[i].Item["group_ids"] = []string{}
   463  			}
   464  		}
   465  
   466  		// Sort valid_redirect_uris and web_origins to get reproducible results for keycloak_openid_client resources
   467  		if r.InstanceInfo.Type == "keycloak_openid_client" {
   468  			if _, exist := r.Item["valid_redirect_uris"]; exist {
   469  				sortedValidRedirectUris := make([]string, len(r.Item["valid_redirect_uris"].([]interface{})))
   470  				for k, v := range r.Item["valid_redirect_uris"].([]interface{}) {
   471  					sortedValidRedirectUris[k] = v.(string)
   472  				}
   473  				sort.Strings(sortedValidRedirectUris)
   474  				g.Resources[i].Item["valid_redirect_uris"] = sortedValidRedirectUris
   475  			}
   476  
   477  			if _, exist := r.Item["web_origins"]; exist {
   478  				sortedWebOrigins := make([]string, len(r.Item["web_origins"].([]interface{})))
   479  				for k, v := range r.Item["web_origins"].([]interface{}) {
   480  					sortedWebOrigins[k] = v.(string)
   481  				}
   482  				sort.Strings(sortedWebOrigins)
   483  				g.Resources[i].Item["web_origins"] = sortedWebOrigins
   484  			}
   485  		}
   486  
   487  		// Sort composite_roles to get reproducible results for keycloak_role resources
   488  		if _, exist := r.Item["composite_roles"]; exist && r.InstanceInfo.Type == "keycloak_role" {
   489  			renamedCompositeRoles := make([]string, len(r.Item["composite_roles"].([]interface{})))
   490  			for k, v := range r.Item["composite_roles"].([]interface{}) {
   491  				renamedCompositeRoles[k] = mapRoleIDs[r.Item["realm_id"].(string)+"_"+v.(string)]
   492  			}
   493  			sort.Strings(renamedCompositeRoles)
   494  			g.Resources[i].Item["composite_roles"] = renamedCompositeRoles
   495  		}
   496  
   497  		// Sort default_scopes to get reproducible results for keycloak_openid_client_default_scopes resources
   498  		if _, exist := r.Item["default_scopes"]; exist && r.InstanceInfo.Type == "keycloak_openid_client_default_scopes" {
   499  			renamedScopes := make([]string, len(r.Item["default_scopes"].([]interface{})))
   500  			for k, v := range r.Item["default_scopes"].([]interface{}) {
   501  				renamedScopes[k] = mapScopeNames[r.Item["realm_id"].(string)+"_"+v.(string)]
   502  			}
   503  			sort.Strings(renamedScopes)
   504  			g.Resources[i].Item["default_scopes"] = renamedScopes
   505  		}
   506  
   507  		// Sort optional_scopes to get reproducible results for keycloak_openid_client_optional_scopes resources
   508  		if _, exist := r.Item["optional_scopes"]; exist && r.InstanceInfo.Type == "keycloak_openid_client_optional_scopes" {
   509  			renamedScopes := make([]string, len(r.Item["optional_scopes"].([]interface{})))
   510  			for k, v := range r.Item["optional_scopes"].([]interface{}) {
   511  				renamedScopes[k] = mapScopeNames[r.Item["realm_id"].(string)+"_"+v.(string)]
   512  			}
   513  			sort.Strings(renamedScopes)
   514  			g.Resources[i].Item["optional_scopes"] = renamedScopes
   515  		}
   516  
   517  		// Sort role_ids to get reproducible results for keycloak_group_roles resources
   518  		if r.InstanceInfo.Type == "keycloak_group_roles" {
   519  			sortedRoles := make([]string, len(r.Item["role_ids"].([]interface{})))
   520  			for k, v := range r.Item["role_ids"].([]interface{}) {
   521  				sortedRoles[k] = mapRoleIDs[r.Item["realm_id"].(string)+"_"+v.(string)]
   522  			}
   523  			sort.Strings(sortedRoles)
   524  			g.Resources[i].Item["role_ids"] = sortedRoles
   525  		}
   526  
   527  		// Sort members to get reproducible results for keycloak_group_memberships resources
   528  		// Map members to keycloak_user.foo.username Terraform variables
   529  		if r.InstanceInfo.Type == "keycloak_group_memberships" {
   530  			sortedMembers := make([]string, len(r.Item["members"].([]interface{})))
   531  			for k, v := range r.Item["members"].([]interface{}) {
   532  				if mapUserNames[r.Item["realm_id"].(string)+"_"+v.(string)] != "" {
   533  					sortedMembers[k] = mapUserNames[r.Item["realm_id"].(string)+"_"+v.(string)]
   534  				} else {
   535  					sortedMembers[k] = v.(string)
   536  				}
   537  			}
   538  			sort.Strings(sortedMembers)
   539  			g.Resources[i].Item["members"] = sortedMembers
   540  		}
   541  
   542  		// Map ldap_user_federation_id attributes to keycloak_ldap_user_federation.foo.id Terraform variables for ldap mappers resources
   543  		if r.InstanceInfo.Type == "keycloak_ldap_full_name_mapper" ||
   544  			r.InstanceInfo.Type == "keycloak_ldap_group_mapper" ||
   545  			r.InstanceInfo.Type == "keycloak_ldap_role_mapper" ||
   546  			r.InstanceInfo.Type == "keycloak_ldap_hardcoded_group_mapper" ||
   547  			r.InstanceInfo.Type == "keycloak_ldap_hardcoded_role_mapper" ||
   548  			r.InstanceInfo.Type == "keycloak_ldap_msad_lds_user_account_control_mapper" ||
   549  			r.InstanceInfo.Type == "keycloak_ldap_msad_user_account_control_mapper" ||
   550  			r.InstanceInfo.Type == "keycloak_ldap_user_attribute_mapper" {
   551  			g.Resources[i].Item["ldap_user_federation_id"] = mapUserFederationIDs[r.Item["realm_id"].(string)+"_"+g.Resources[i].Item["ldap_user_federation_id"].(string)]
   552  		}
   553  
   554  		// Map group to keycloak_group.foo.name Terraform variables for ldap hardcoded group mapper resources
   555  		if r.InstanceInfo.Type == "keycloak_ldap_hardcoded_group_mapper" {
   556  			g.Resources[i].Item["group"] = mapGroupNames[r.Item["realm_id"].(string)+"_"+r.Item["group"].(string)]
   557  		}
   558  
   559  		// Map role to Terraform variables for ldap hardcoded role mapper resources
   560  		if r.InstanceInfo.Type == "keycloak_ldap_hardcoded_role_mapper" {
   561  			g.Resources[i].Item["role"] = mapClientRoleNames[r.Item["realm_id"].(string)+"_"+r.Item["role"].(string)]
   562  		}
   563  
   564  		// Map parent_id to keycloak_group.foo.id Terraform variables for keycloak_group resources
   565  		if _, exist := r.Item["parent_id"]; exist && r.InstanceInfo.Type == "keycloak_group" {
   566  			g.Resources[i].Item["parent_id"] = mapGroupIDs[r.Item["realm_id"].(string)+"_"+r.Item["parent_id"].(string)]
   567  		}
   568  
   569  		// Map group_id to keycloak_group.foo.id Terraform variables for keycloak_group_memberships and keycloak_group_roles resources
   570  		if r.InstanceInfo.Type == "keycloak_group_memberships" || r.InstanceInfo.Type == "keycloak_group_roles" {
   571  			g.Resources[i].Item["group_id"] = mapGroupIDs[r.Item["realm_id"].(string)+"_"+r.Item["group_id"].(string)]
   572  		}
   573  
   574  		// Map service_account_user_id to keycloak_openid_client.foo.service_account_user_id Terraform variables for service account role resources
   575  		if r.InstanceInfo.Type == "keycloak_openid_client_service_account_role" {
   576  			g.Resources[i].Item["service_account_user_id"] = mapServiceAccountUserIDs[r.Item["realm_id"].(string)+"_"+r.Item["service_account_user_id"].(string)]
   577  			g.Resources[i].Item["role"] = mapClientRoleShortNames[r.Item["realm_id"].(string)+"_"+mapClientNames[r.Item["realm_id"].(string)+"_"+r.Item["client_id"].(string)]+"."+r.Item["role"].(string)]
   578  		}
   579  
   580  		// Map client_id attributes to keycloak_openid_client.foo.id Terraform variables for open id mappers resources
   581  		if _, exist := r.Item["client_id"]; exist && (r.InstanceInfo.Type == "keycloak_openid_client_service_account_role" ||
   582  			r.InstanceInfo.Type == "keycloak_openid_audience_protocol_mapper" ||
   583  			r.InstanceInfo.Type == "keycloak_openid_full_name_protocol_mapper" ||
   584  			r.InstanceInfo.Type == "keycloak_openid_group_membership_protocol_mapper" ||
   585  			r.InstanceInfo.Type == "keycloak_openid_hardcoded_claim_protocol_mapper" ||
   586  			r.InstanceInfo.Type == "keycloak_openid_hardcoded_group_protocol_mapper" ||
   587  			r.InstanceInfo.Type == "keycloak_openid_hardcoded_role_protocol_mapper" ||
   588  			r.InstanceInfo.Type == "keycloak_openid_user_attribute_protocol_mapper" ||
   589  			r.InstanceInfo.Type == "keycloak_openid_user_property_protocol_mapper" ||
   590  			r.InstanceInfo.Type == "keycloak_openid_user_realm_role_protocol_mapper" ||
   591  			r.InstanceInfo.Type == "keycloak_openid_client_default_scopes" ||
   592  			r.InstanceInfo.Type == "keycloak_openid_client_optional_scopes" ||
   593  			r.InstanceInfo.Type == "keycloak_role") {
   594  			g.Resources[i].Item["client_id"] = mapClientIDs[r.Item["realm_id"].(string)+"_"+r.Item["client_id"].(string)]
   595  		}
   596  
   597  		// Map included_client_audience to keycloak_openid_client.foo.client_id Terraform variables for open id audience mapper resources
   598  		if _, exist := r.Item["included_client_audience"]; exist && r.InstanceInfo.Type == "keycloak_openid_audience_protocol_mapper" {
   599  			g.Resources[i].Item["included_client_audience"] = mapClientClientIDs[r.Item["realm_id"].(string)+"_"+r.Item["included_client_audience"].(string)]
   600  		}
   601  
   602  		// Map parent_flow_alias attributes to keycloak_authentication_(sub)flow.foo.alias Terraform variables for authentication subflow and execution resources
   603  		if r.InstanceInfo.Type == "keycloak_authentication_subflow" || r.InstanceInfo.Type == "keycloak_authentication_execution" {
   604  			g.Resources[i].Item["parent_flow_alias"] = mapAuthenticationFlowAliases[r.Item["realm_id"].(string)+"_"+r.Item["parent_flow_alias"].(string)]
   605  		}
   606  
   607  		// Map execution_id attributes to keycloak_authentication_execution_config.foo.execution_id Terraform variables for authentication execution config resources
   608  		if r.InstanceInfo.Type == "keycloak_authentication_execution_config" {
   609  			g.Resources[i].Item["execution_id"] = mapAuthenticationExecutionIDs[r.Item["realm_id"].(string)+"_"+r.Item["execution_id"].(string)]
   610  		}
   611  
   612  		// Map realm_id attributes to keycloak_realm.foo.id Terraform variables for all the resources (almost all resources have this attribute)
   613  		if _, exist := r.Item["realm_id"]; exist {
   614  			g.Resources[i].Item["realm_id"] = mapRealmIDs[r.Item["realm_id"].(string)]
   615  		}
   616  	}
   617  	return nil
   618  }