@@ -16,6 +16,196 @@ import (
16
16
"github.com/zclconf/go-cty/cty"
17
17
)
18
18
19
+ // knownReferences is a map of all resource fields that can be referenced from another resource.
20
+ // For example, the `folder` field of a `grafana_dashboard` resource can be a `grafana_folder` reference.
21
+ //
22
+ //go:generate go run ./genreferences --file=$GOFILE --walk-dir=../..
23
+ var knownReferences = []string {
24
+ "grafana_annotation.dashboard_uid=grafana_dashboard.uid" ,
25
+ "grafana_annotation.org_id=grafana_organization.id" ,
26
+ "grafana_api_key.auth=grafana_api_key.key" ,
27
+ "grafana_cloud_access_policy.identifier=grafana_cloud_stack.id" ,
28
+ "grafana_cloud_access_policy_token.access_policy_id=grafana_cloud_access_policy.policy_id" ,
29
+ "grafana_cloud_plugin_installation.stack_slug=grafana_cloud_stack.slug" ,
30
+ "grafana_cloud_stack_service_account.stack_slug=grafana_cloud_stack.slug" ,
31
+ "grafana_cloud_stack_service_account_token.auth=grafana_cloud_stack_service_account_token.key" ,
32
+ "grafana_cloud_stack_service_account_token.service_account_id=grafana_cloud_stack_service_account.id" ,
33
+ "grafana_cloud_stack_service_account_token.stack_slug=grafana_cloud_stack.slug" ,
34
+ "grafana_cloud_stack_service_account_token.url=grafana_cloud_stack.url" ,
35
+ "grafana_contact_point.org_id=grafana_organization.id" ,
36
+ "grafana_dashboard.folder=grafana_folder.id" ,
37
+ "grafana_dashboard.folder=grafana_folder.uid" ,
38
+ "grafana_dashboard.name=grafana_library_panel.name" ,
39
+ "grafana_dashboard.org_id=grafana_organization.id" ,
40
+ "grafana_dashboard.org_id=grafana_organization.org_id" ,
41
+ "grafana_dashboard.uid=grafana_library_panel.uid" ,
42
+ "grafana_dashboard_permission.dashboard_uid=grafana_dashboard.uid" ,
43
+ "grafana_dashboard_permission.team_id=grafana_team.id" ,
44
+ "grafana_dashboard_permission.user_id=grafana_user.id" ,
45
+ "grafana_dashboard_permission_item.dashboard_uid=grafana_dashboard.uid" ,
46
+ "grafana_dashboard_permission_item.team=grafana_team.id" ,
47
+ "grafana_dashboard_permission_item.user=grafana_service_account.id" ,
48
+ "grafana_dashboard_permission_item.user=grafana_user.id" ,
49
+ "grafana_dashboard_public.dashboard_uid=grafana_dashboard.uid" ,
50
+ "grafana_dashboard_public.org_id=grafana_organization.org_id" ,
51
+ "grafana_data_source.datasourceUid=grafana_data_source.uid" ,
52
+ "grafana_data_source.org_id=grafana_organization.id" ,
53
+ "grafana_data_source_config.datasourceUid=grafana_data_source.uid" ,
54
+ "grafana_data_source_config.uid=grafana_data_source.uid" ,
55
+ "grafana_data_source_permission.datasource_uid=grafana_data_source.uid" ,
56
+ "grafana_data_source_permission.team_id=grafana_team.id" ,
57
+ "grafana_data_source_permission.user_id=grafana_service_account.id" ,
58
+ "grafana_data_source_permission.user_id=grafana_user.id" ,
59
+ "grafana_data_source_permission_item.datasource_uid=grafana_data_source.uid" ,
60
+ "grafana_data_source_permission_item.team=grafana_team.id" ,
61
+ "grafana_data_source_permission_item.user=grafana_service_account.id" ,
62
+ "grafana_data_source_permission_item.user=grafana_user.id" ,
63
+ "grafana_folder.org_id=grafana_organization.id" ,
64
+ "grafana_folder.org_id=grafana_organization.org_id" ,
65
+ "grafana_folder.parent_folder_uid=grafana_folder.uid" ,
66
+ "grafana_folder_permission.folder_uid=grafana_folder.uid" ,
67
+ "grafana_folder_permission.team_id=grafana_team.id" ,
68
+ "grafana_folder_permission.user_id=grafana_service_account.id" ,
69
+ "grafana_folder_permission.user_id=grafana_user.id" ,
70
+ "grafana_folder_permission_item.folder_uid=grafana_folder.uid" ,
71
+ "grafana_folder_permission_item.team=grafana_team.id" ,
72
+ "grafana_folder_permission_item.user=grafana_service_account.id" ,
73
+ "grafana_folder_permission_item.user=grafana_user.id" ,
74
+ "grafana_library_panel.folder_uid=grafana_folder.uid" ,
75
+ "grafana_library_panel.org_id=grafana_organization.id" ,
76
+ "grafana_machine_learning_job.datasource_uid=grafana_data_source.uid" ,
77
+ "grafana_message_template.org_id=grafana_organization.id" ,
78
+ "grafana_notification_policy.contact_point=grafana_contact_point.name" ,
79
+ "grafana_notification_policy.mute_timings=grafana_mute_timing.name" ,
80
+ "grafana_notification_policy.org_id=grafana_organization.id" ,
81
+ "grafana_oncall_escalation.escalation_chain_id=grafana_oncall_escalation_chain.id" ,
82
+ "grafana_oncall_integration.escalation_chain_id=grafana_oncall_escalation_chain.id" ,
83
+ "grafana_oncall_route.escalation_chain_id=grafana_oncall_escalation_chain.id" ,
84
+ "grafana_oncall_route.integration_id=grafana_oncall_integration.id" ,
85
+ "grafana_organization.org_id=grafana_organization.id" ,
86
+ "grafana_organization_preferences.home_dashboard_uid=grafana_dashboard.uid" ,
87
+ "grafana_organization_preferences.org_id=grafana_organization.id" ,
88
+ "grafana_playlist.org_id=grafana_organization.id" ,
89
+ "grafana_report.dashboard_id=grafana_dashboard.dashboard_id" ,
90
+ "grafana_report.org_id=grafana_organization.id" ,
91
+ "grafana_report.uid=grafana_dashboard.uid" ,
92
+ "grafana_role.org_id=grafana_organization.id" ,
93
+ "grafana_role_assignment.auth=grafana_cloud_stack_service_account_token.key" ,
94
+ "grafana_role_assignment.org_id=grafana_organization.id" ,
95
+ "grafana_role_assignment.role_uid=grafana_role.uid" ,
96
+ "grafana_role_assignment.service_accounts=grafana_cloud_stack_service_account.id" ,
97
+ "grafana_role_assignment.service_accounts=grafana_service_account.id" ,
98
+ "grafana_role_assignment.teams=grafana_team.id" ,
99
+ "grafana_role_assignment.url=grafana_cloud_stack.url" ,
100
+ "grafana_role_assignment.users=grafana_user.id" ,
101
+ "grafana_role_assignment_item.role_uid=grafana_role.uid" ,
102
+ "grafana_role_assignment_item.service_account_id=grafana_service_account.id" ,
103
+ "grafana_role_assignment_item.team_id=grafana_team.id" ,
104
+ "grafana_role_assignment_item.user_id=grafana_user.id" ,
105
+ "grafana_rule_group.folder_uid=grafana_folder.uid" ,
106
+ "grafana_rule_group.org_id=grafana_organization.id" ,
107
+ "grafana_service_account.org_id=grafana_organization.id" ,
108
+ "grafana_service_account.role_uid=grafana_role.uid" ,
109
+ "grafana_service_account.service_account_id=grafana_service_account.id" ,
110
+ "grafana_service_account.team_id=grafana_team.id" ,
111
+ "grafana_service_account.user_id=grafana_user.id" ,
112
+ "grafana_service_account_permission.org_id=grafana_organization.id" ,
113
+ "grafana_service_account_permission.service_account_id=grafana_cloud_stack_service_account.id" ,
114
+ "grafana_service_account_permission.service_account_id=grafana_service_account.id" ,
115
+ "grafana_service_account_permission.team_id=grafana_team.id" ,
116
+ "grafana_service_account_permission.user_id=grafana_user.id" ,
117
+ "grafana_service_account_permission_item.auth=grafana_cloud_stack_service_account_token.key" ,
118
+ "grafana_service_account_permission_item.org_id=grafana_organization.id" ,
119
+ "grafana_service_account_permission_item.service_account_id=grafana_cloud_stack_service_account.id" ,
120
+ "grafana_service_account_permission_item.service_account_id=grafana_service_account.id" ,
121
+ "grafana_service_account_permission_item.team=grafana_team.id" ,
122
+ "grafana_service_account_permission_item.url=grafana_cloud_stack.url" ,
123
+ "grafana_service_account_permission_item.user=grafana_user.id" ,
124
+ "grafana_service_account_token.auth=grafana_service_account_token.key" ,
125
+ "grafana_service_account_token.service_account_id=grafana_service_account.id" ,
126
+ "grafana_slo.folder_uid=grafana_folder.uid" ,
127
+ "grafana_synthetic_monitoring_installation.logs_instance_id=grafana_cloud_stack.logs_user_id" ,
128
+ "grafana_synthetic_monitoring_installation.metrics_instance_id=grafana_cloud_stack.prometheus_user_id" ,
129
+ "grafana_synthetic_monitoring_installation.metrics_publisher_key=grafana_cloud_access_policy_token.token" ,
130
+ "grafana_synthetic_monitoring_installation.metrics_publisher_key=grafana_cloud_api_key.key" ,
131
+ "grafana_synthetic_monitoring_installation.sm_access_token=grafana_synthetic_monitoring_installation.sm_access_token" ,
132
+ "grafana_synthetic_monitoring_installation.sm_url=grafana_synthetic_monitoring_installation.stack_sm_api_url" ,
133
+ "grafana_synthetic_monitoring_installation.stack_id=grafana_cloud_stack.id" ,
134
+ "grafana_team.home_dashboard_uid=grafana_dashboard.uid" ,
135
+ "grafana_team.org_id=grafana_organization.id" ,
136
+ "grafana_team_external_group.team_id=grafana_team.id" ,
137
+ "grafana_team_preferences.home_dashboard_uid=grafana_dashboard.uid" ,
138
+ "grafana_team_preferences.team_id=grafana_team.id" ,
139
+ }
140
+
141
+ // TODO: Also find references from the state (computed fields, like ID)
142
+ func replaceReferences (fpath string , extraKnownReferences []string ) error {
143
+ file , err := readHCLFile (fpath )
144
+ if err != nil {
145
+ return err
146
+ }
147
+
148
+ hasChanges := false
149
+
150
+ knownReferences := knownReferences
151
+ knownReferences = append (knownReferences , extraKnownReferences ... )
152
+ // Find all resources. This map will be used to search for references
153
+ resourcesBlocks := map [string ]* hclwrite.Block {}
154
+ for _ , block := range file .Body ().Blocks () {
155
+ if block .Type () == "resource" {
156
+ resourcesBlocks [block .Labels ()[0 ]+ "." + block .Labels ()[1 ]] = block
157
+ }
158
+ }
159
+
160
+ for _ , block := range file .Body ().Blocks () {
161
+ for attrName , attr := range block .Body ().Attributes () {
162
+ attrValue := string (attr .Expr ().BuildTokens (nil ).Bytes ())
163
+ attrReplaced := false
164
+
165
+ // Check the field name. If it has a possible reference, we have to search for it in the resources
166
+ for _ , ref := range knownReferences {
167
+ if attrReplaced {
168
+ break
169
+ }
170
+
171
+ refFrom := strings .Split (ref , "=" )[0 ]
172
+ refTo := strings .Split (ref , "=" )[1 ]
173
+ hasPossibleReference := refFrom == fmt .Sprintf ("%s.%s" , block .Labels ()[0 ], attrName ) || (strings .HasPrefix (refFrom , "*." ) && strings .HasSuffix (refFrom , fmt .Sprintf (".%s" , attrName )))
174
+ if ! hasPossibleReference {
175
+ continue
176
+ }
177
+
178
+ refToResource := strings .Split (refTo , "." )[0 ]
179
+ refToAttr := strings .Split (refTo , "." )[1 ]
180
+
181
+ for possibleResourceRefName , possibleResourceRef := range resourcesBlocks {
182
+ if strings .HasPrefix (possibleResourceRefName , refToResource + "." ) {
183
+ valueFromRef := ""
184
+ if possibleResourceRef .Body ().GetAttribute (refToAttr ) != nil {
185
+ valueFromRef = string (possibleResourceRef .Body ().GetAttribute (refToAttr ).Expr ().BuildTokens (nil ).Bytes ())
186
+ }
187
+ // If the value from the first block matches the value from the second block, we have a reference
188
+ if attrValue == valueFromRef {
189
+ // Replace the value with the reference
190
+ block .Body ().SetAttributeTraversal (attrName , traversal (possibleResourceRefName , refToAttr ))
191
+ hasChanges = true
192
+ attrReplaced = true
193
+ break
194
+ }
195
+ }
196
+ }
197
+ }
198
+ }
199
+ }
200
+
201
+ if hasChanges {
202
+ log .Printf ("Updating file: %s\n " , fpath )
203
+ return os .WriteFile (fpath , file .Bytes (), 0600 )
204
+ }
205
+
206
+ return nil
207
+ }
208
+
19
209
func stripDefaults (fpath string , extraFieldsToRemove map [string ]any ) error {
20
210
file , err := readHCLFile (fpath )
21
211
if err != nil {
0 commit comments