@robinmordasiewicz/f5xc-terraform-mcp 3.8.27 → 3.8.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -44,6 +44,12 @@ export async function handleMetadata(input) {
44
44
  return handleSummary(response_format);
45
45
  case 'syntax':
46
46
  return handleSyntax(input, response_format);
47
+ case 'validate':
48
+ return handleValidate(input, response_format);
49
+ case 'example':
50
+ return handleExample(input, response_format);
51
+ case 'mistakes':
52
+ return handleMistakes(input, response_format);
47
53
  default:
48
54
  throw new Error(`Unknown operation: ${operation}`);
49
55
  }
@@ -698,6 +704,836 @@ function handleSyntax(input, format) {
698
704
  // Return the generated markdown guide
699
705
  return guide.syntaxGuide;
700
706
  }
707
+ /**
708
+ * Validates a Terraform config snippet for common syntax errors
709
+ * Phase 2: Now detects unsupported arguments/blocks, not just boolean assignment
710
+ */
711
+ function handleValidate(input, format) {
712
+ const { resource, config } = input;
713
+ if (!resource) {
714
+ return formatError('Missing parameter', 'resource parameter is required for validate operation', format);
715
+ }
716
+ if (!config) {
717
+ return formatError('Missing parameter', 'config parameter is required for validate operation. Provide a Terraform config snippet to validate.', format);
718
+ }
719
+ const resourceMeta = getResourceMetadata(resource);
720
+ if (!resourceMeta) {
721
+ return formatError('Resource not found', `Resource '${resource}' not found in metadata`, format);
722
+ }
723
+ const errors = [];
724
+ const validFields = Object.keys(resourceMeta.attributes);
725
+ // Phase 1: Check for boolean assignment errors (existing)
726
+ const booleanAssignmentPattern = /^\s*(\w+)\s*=\s*(true|false)\s*$/gm;
727
+ let match;
728
+ while ((match = booleanAssignmentPattern.exec(config)) !== null) {
729
+ const fieldName = match[1];
730
+ const value = match[2];
731
+ const attrMeta = resourceMeta.attributes[fieldName];
732
+ if (attrMeta && attrMeta.is_block && attrMeta.type === 'object') {
733
+ errors.push({
734
+ type: 'boolean_assignment',
735
+ field: fieldName,
736
+ found: `${fieldName} = ${value}`,
737
+ expected: `${fieldName} {}`,
738
+ reason: attrMeta.oneof_group
739
+ ? `This is a OneOf choice field in group '${attrMeta.oneof_group}', not a boolean`
740
+ : 'This is a block-type attribute, not a boolean',
741
+ });
742
+ }
743
+ }
744
+ // Phase 2: Parse HCL and check for unsupported fields
745
+ const parsed = parseHCLForFields(config);
746
+ // Check each parsed field against the schema
747
+ for (const field of parsed.fields) {
748
+ // Skip standard fields that are always valid at root level
749
+ if (!field.parent && ['name', 'namespace', 'description', 'labels', 'annotations'].includes(field.name)) {
750
+ continue;
751
+ }
752
+ // Check if field exists in resource schema
753
+ const parentMeta = field.parent ? resourceMeta.attributes[field.parent] : null;
754
+ // Strategy 1: Check if parent is a KNOWN reference block (handles nested cases like pool inside origin_pools_weights)
755
+ // This catches cases where the parent block name is in KNOWN_REFERENCE_BLOCKS even if
756
+ // we don't have schema metadata for it (e.g., nested blocks)
757
+ if (field.parent && KNOWN_REFERENCE_BLOCKS.has(field.parent)) {
758
+ const allowedFields = ['name', 'namespace', 'tenant'];
759
+ if (!allowedFields.includes(field.name)) {
760
+ const refResource = getReferenceResourceName(field.parent);
761
+ errors.push({
762
+ type: field.type === 'block' ? 'unsupported_block' : 'reference_inline',
763
+ field: field.name,
764
+ line: field.line,
765
+ location: `Inside \`${field.parent}\` block`,
766
+ found: field.type === 'block' ? `${field.name} {}` : `${field.name} = ${field.value || '...'}`,
767
+ expected: `Only name, namespace, tenant are valid inside ${field.parent}`,
768
+ reason: `\`${field.parent}\` is a reference block - it references a separate resource, not inline configuration`,
769
+ suggestions: [`Create a separate ${refResource} resource with these parameters`],
770
+ referenceResource: refResource,
771
+ });
772
+ }
773
+ continue; // Skip further checks for this field
774
+ }
775
+ // Strategy 2: Check parent using schema metadata (handles top-level blocks)
776
+ if (field.parent && parentMeta) {
777
+ // Field is inside a parent block - check if parent is a reference block via schema
778
+ if (isReferenceBlock(parentMeta, field.parent)) {
779
+ const allowedFields = ['name', 'namespace', 'tenant'];
780
+ if (!allowedFields.includes(field.name)) {
781
+ const refResource = getReferenceResourceName(field.parent);
782
+ errors.push({
783
+ type: field.type === 'block' ? 'unsupported_block' : 'reference_inline',
784
+ field: field.name,
785
+ line: field.line,
786
+ location: `Inside \`${field.parent}\` block`,
787
+ found: field.type === 'block' ? `${field.name} {}` : `${field.name} = ${field.value || '...'}`,
788
+ expected: `Only name, namespace, tenant are valid inside ${field.parent}`,
789
+ reason: `\`${field.parent}\` is a reference block - it references a separate resource, not inline configuration`,
790
+ suggestions: [`Create a separate ${refResource} resource with these parameters`],
791
+ referenceResource: refResource,
792
+ });
793
+ }
794
+ }
795
+ // Note: For non-reference blocks without nested_attributes in schema,
796
+ // we can't validate nested fields - skip validation for these cases
797
+ }
798
+ else if (!field.parent) {
799
+ // Field is at root level - check against resource schema
800
+ if (!resourceMeta.attributes[field.name]) {
801
+ const similar = findSimilarFieldNames(field.name, validFields);
802
+ errors.push({
803
+ type: field.type === 'block' ? 'unsupported_block' : 'unsupported_argument',
804
+ field: field.name,
805
+ line: field.line,
806
+ found: field.type === 'block' ? `${field.name} {}` : `${field.name} = ${field.value || '...'}`,
807
+ expected: `Valid attributes for ${resource}`,
808
+ reason: `An ${field.type === 'block' ? 'block' : 'argument'} named "${field.name}" is not expected here`,
809
+ suggestions: similar.length > 0 ? [`Did you mean: ${similar.join(', ')}?`] : undefined,
810
+ });
811
+ }
812
+ }
813
+ }
814
+ // Build response
815
+ if (errors.length === 0) {
816
+ if (format === ResponseFormat.JSON) {
817
+ return JSON.stringify({
818
+ operation: 'validate',
819
+ resource,
820
+ status: 'valid',
821
+ errors: [],
822
+ message: 'No syntax errors detected in the provided configuration.',
823
+ }, null, 2);
824
+ }
825
+ return [
826
+ `# Validation Results: ${resource}`,
827
+ '',
828
+ '✅ **No syntax errors detected**',
829
+ '',
830
+ 'The provided configuration appears to use correct syntax.',
831
+ '',
832
+ '*Note: This validates common syntax patterns only. Run `terraform validate` for complete validation.*',
833
+ ].join('\n');
834
+ }
835
+ // Errors found
836
+ if (format === ResponseFormat.JSON) {
837
+ return JSON.stringify({
838
+ operation: 'validate',
839
+ resource,
840
+ status: 'invalid',
841
+ errors,
842
+ error_count: errors.length,
843
+ }, null, 2);
844
+ }
845
+ const lines = [
846
+ `# Validation Results: ${errors.length} Error(s) Found`,
847
+ '',
848
+ ];
849
+ for (let i = 0; i < errors.length; i++) {
850
+ const err = errors[i];
851
+ const errorTitle = getErrorTitle(err.type);
852
+ lines.push(`## Error ${i + 1}: ${errorTitle}`);
853
+ lines.push('');
854
+ lines.push(`- **Field**: \`${err.field}\``);
855
+ if (err.line) {
856
+ lines.push(`- **Line**: ${err.line}`);
857
+ }
858
+ if (err.location) {
859
+ lines.push(`- **Location**: ${err.location}`);
860
+ }
861
+ lines.push(`- **Found**: \`${err.found}\``);
862
+ lines.push(`- **Expected**: ${err.expected}`);
863
+ lines.push(`- **Reason**: ${err.reason}`);
864
+ if (err.suggestions && err.suggestions.length > 0) {
865
+ lines.push(`- **Suggestion**: ${err.suggestions.join('; ')}`);
866
+ }
867
+ lines.push('');
868
+ }
869
+ // Add corrected pattern for reference block errors
870
+ const refErrors = errors.filter(e => e.type === 'reference_inline' || (e.type === 'unsupported_block' && e.referenceResource));
871
+ if (refErrors.length > 0) {
872
+ const refErr = refErrors[0];
873
+ const parentBlock = refErr.location?.match(/`(\w+)`/)?.[1] || 'unknown';
874
+ const refResource = refErr.referenceResource || `f5xc_${parentBlock}`;
875
+ lines.push('## Correct Pattern');
876
+ lines.push('');
877
+ lines.push('```hcl');
878
+ lines.push(`# Create the ${parentBlock} resource separately`);
879
+ lines.push(`resource "${refResource}" "example" {`);
880
+ lines.push(` name = "my-${parentBlock}"`);
881
+ lines.push(' namespace = "default"');
882
+ lines.push('');
883
+ lines.push(' # Add your configuration here');
884
+ lines.push(' # ...');
885
+ lines.push('}');
886
+ lines.push('');
887
+ lines.push(`# Reference it in ${resource}`);
888
+ lines.push(`resource "f5xc_${resource}" "example" {`);
889
+ lines.push(` ${parentBlock} {`);
890
+ lines.push(` name = ${refResource}.example.name`);
891
+ lines.push(' namespace = "default"');
892
+ lines.push(' }');
893
+ lines.push('}');
894
+ lines.push('```');
895
+ }
896
+ else {
897
+ // For non-reference errors, show corrected config
898
+ const boolErrors = errors.filter(e => e.type === 'boolean_assignment');
899
+ if (boolErrors.length > 0) {
900
+ lines.push('## Corrected Configuration');
901
+ lines.push('');
902
+ lines.push('```hcl');
903
+ lines.push(generateCorrectedConfig(config, boolErrors));
904
+ lines.push('```');
905
+ }
906
+ }
907
+ return lines.join('\n');
908
+ }
909
+ /**
910
+ * Get human-readable error title
911
+ */
912
+ function getErrorTitle(type) {
913
+ switch (type) {
914
+ case 'boolean_assignment':
915
+ return 'Invalid boolean assignment';
916
+ case 'unsupported_argument':
917
+ return 'Unsupported argument';
918
+ case 'unsupported_block':
919
+ return 'Unsupported block type';
920
+ case 'reference_inline':
921
+ return 'Inline configuration in reference block';
922
+ default:
923
+ return 'Validation error';
924
+ }
925
+ }
926
+ /**
927
+ * Generates corrected config by replacing boolean assignments with empty blocks
928
+ */
929
+ function generateCorrectedConfig(config, errors) {
930
+ let corrected = config;
931
+ for (const err of errors) {
932
+ if (err.type === 'boolean_assignment') {
933
+ // Replace "field = true" or "field = false" with "field {}"
934
+ const pattern = new RegExp(`\\b${err.field}\\s*=\\s*(true|false)`, 'g');
935
+ corrected = corrected.replace(pattern, `${err.field} {}`);
936
+ }
937
+ }
938
+ return corrected;
939
+ }
940
+ // =============================================================================
941
+ // EXAMPLE OPERATION HANDLER
942
+ // =============================================================================
943
+ /**
944
+ * Generates complete, syntactically correct Terraform examples
945
+ */
946
+ function handleExample(input, format) {
947
+ const { resource, pattern = 'basic' } = input;
948
+ if (!resource) {
949
+ return formatError('Missing parameter', 'resource parameter is required for example operation', format);
950
+ }
951
+ // Get example based on resource type and pattern
952
+ const example = getExampleForResource(resource, pattern);
953
+ if (!example) {
954
+ return formatError('Example not found', `No example available for resource '${resource}' with pattern '${pattern}'. Available patterns: basic, with_waf, with_bot_defense, with_rate_limiting, full`, format);
955
+ }
956
+ if (format === ResponseFormat.JSON) {
957
+ return JSON.stringify({
958
+ operation: 'example',
959
+ resource,
960
+ pattern,
961
+ ...example,
962
+ }, null, 2);
963
+ }
964
+ return example.markdown;
965
+ }
966
+ /**
967
+ * Returns example configuration for a resource
968
+ */
969
+ function getExampleForResource(resource, pattern) {
970
+ // HTTP Load Balancer examples
971
+ if (resource === 'http_loadbalancer') {
972
+ return getHttpLoadbalancerExample(pattern);
973
+ }
974
+ // Origin Pool examples
975
+ if (resource === 'origin_pool') {
976
+ return getOriginPoolExample(pattern);
977
+ }
978
+ // Namespace example
979
+ if (resource === 'namespace') {
980
+ return {
981
+ description: 'Basic namespace configuration',
982
+ terraform: `resource "f5xc_namespace" "example" {
983
+ name = "my-namespace"
984
+ }`,
985
+ markdown: [
986
+ '# Complete Example: Namespace (Basic Pattern)',
987
+ '',
988
+ 'Creates a namespace in F5 Distributed Cloud.',
989
+ '',
990
+ '```hcl',
991
+ 'resource "f5xc_namespace" "example" {',
992
+ ' name = "my-namespace"',
993
+ '}',
994
+ '```',
995
+ '',
996
+ '## Key Syntax Notes',
997
+ '',
998
+ '- Namespace is the simplest resource - just needs a name',
999
+ '- Most other resources require a namespace reference',
1000
+ ].join('\n'),
1001
+ };
1002
+ }
1003
+ // App Firewall example
1004
+ if (resource === 'app_firewall') {
1005
+ return getAppFirewallExample(pattern);
1006
+ }
1007
+ // Generic fallback - generate from syntax guide
1008
+ const syntaxGuide = generateTerraformSyntaxGuide(resource);
1009
+ if (syntaxGuide) {
1010
+ const blocks = syntaxGuide.blocks.slice(0, 5);
1011
+ const attrs = syntaxGuide.attributes.slice(0, 5);
1012
+ const exampleLines = [
1013
+ `resource "f5xc_${resource}" "example" {`,
1014
+ ' name = "example-' + resource.replace(/_/g, '-') + '"',
1015
+ ' namespace = "default"',
1016
+ '',
1017
+ ];
1018
+ // Add required OneOf selections
1019
+ for (const [groupName, fields] of Object.entries(syntaxGuide.oneOfGroups)) {
1020
+ if (fields.length > 0) {
1021
+ exampleLines.push(` # OneOf group: ${groupName} - choose one`);
1022
+ exampleLines.push(` ${fields[0]} {}`);
1023
+ exampleLines.push('');
1024
+ }
1025
+ }
1026
+ exampleLines.push('}');
1027
+ return {
1028
+ description: `Auto-generated example for ${resource}`,
1029
+ terraform: exampleLines.join('\n'),
1030
+ markdown: [
1031
+ `# Complete Example: ${resource} (Auto-Generated)`,
1032
+ '',
1033
+ '⚠️ **Note**: This is an auto-generated example. Verify required fields in documentation.',
1034
+ '',
1035
+ '```hcl',
1036
+ exampleLines.join('\n'),
1037
+ '```',
1038
+ '',
1039
+ '## Block-Type Attributes (use empty block syntax)',
1040
+ '',
1041
+ blocks.map(b => `- \`${b} {}\``).join('\n'),
1042
+ '',
1043
+ '## Simple Attributes',
1044
+ '',
1045
+ attrs.map(a => `- \`${a} = <value>\``).join('\n'),
1046
+ ].join('\n'),
1047
+ };
1048
+ }
1049
+ return null;
1050
+ }
1051
+ function getHttpLoadbalancerExample(pattern) {
1052
+ const baseExample = `# 1. Create the origin pool
1053
+ resource "f5xc_origin_pool" "example" {
1054
+ name = "httpbin-pool"
1055
+ namespace = "default"
1056
+
1057
+ # TLS choice - use empty block syntax
1058
+ no_tls {}
1059
+
1060
+ # Port choice - use empty block syntax
1061
+ automatic_port {}
1062
+
1063
+ origin_servers {
1064
+ public_name {
1065
+ dns_name = "httpbin.org"
1066
+ }
1067
+ }
1068
+
1069
+ loadbalancer_algorithm = "ROUND_ROBIN"
1070
+ }
1071
+
1072
+ # 2. Create the HTTP load balancer
1073
+ resource "f5xc_http_loadbalancer" "example" {
1074
+ name = "httpbin-lb"
1075
+ namespace = "default"
1076
+ domains = ["httpbin.example.com"]
1077
+
1078
+ # Load balancer type - use empty block syntax
1079
+ https_auto_cert {
1080
+ http_redirect = true
1081
+ add_hsts = false
1082
+ }
1083
+
1084
+ # Advertising - use empty block syntax
1085
+ advertise_on_public_default_vip {}
1086
+
1087
+ # Origin pool reference
1088
+ default_route_pools {
1089
+ pool {
1090
+ name = f5xc_origin_pool.example.name
1091
+ namespace = "default"
1092
+ }
1093
+ }
1094
+
1095
+ # Security options - use empty block syntax
1096
+ disable_waf {}
1097
+ no_challenge {}
1098
+ disable_rate_limit {}
1099
+ no_service_policies {}
1100
+ disable_bot_defense {}
1101
+ disable_api_definition {}
1102
+ disable_api_discovery {}
1103
+
1104
+ # Load balancing algorithm - use empty block syntax
1105
+ round_robin {}
1106
+ }`;
1107
+ if (pattern === 'basic') {
1108
+ return {
1109
+ description: 'Basic HTTP Load Balancer with origin pool pointing to httpbin.org',
1110
+ terraform: baseExample,
1111
+ markdown: [
1112
+ '# Complete Example: HTTP Load Balancer (Basic Pattern)',
1113
+ '',
1114
+ 'Creates an HTTP load balancer with an origin pool pointing to httpbin.org.',
1115
+ '',
1116
+ '## Required Resources',
1117
+ '',
1118
+ '```hcl',
1119
+ baseExample,
1120
+ '```',
1121
+ '',
1122
+ '## Key Syntax Notes',
1123
+ '',
1124
+ '1. **Empty blocks for OneOf choices**: `no_tls {}`, `advertise_on_public_default_vip {}`',
1125
+ '2. **Nested blocks with values**: `https_auto_cert { http_redirect = true }`',
1126
+ '3. **Reference syntax**: `f5xc_origin_pool.example.name`',
1127
+ '',
1128
+ '## Common Mistakes to Avoid',
1129
+ '',
1130
+ '```hcl',
1131
+ '# WRONG - Do not use boolean assignment for these fields!',
1132
+ 'no_tls = true # ERROR: use no_tls {}',
1133
+ 'advertise_on_public_default_vip = true # ERROR: use advertise_on_public_default_vip {}',
1134
+ 'round_robin = true # ERROR: use round_robin {}',
1135
+ '```',
1136
+ ].join('\n'),
1137
+ };
1138
+ }
1139
+ if (pattern === 'with_waf') {
1140
+ const wafExample = baseExample.replace(' disable_waf {}', ` app_firewall {
1141
+ name = f5xc_app_firewall.example.name
1142
+ namespace = "default"
1143
+ }`);
1144
+ const fullExample = `# 0. Create App Firewall first
1145
+ resource "f5xc_app_firewall" "example" {
1146
+ name = "my-waf"
1147
+ namespace = "default"
1148
+
1149
+ # Use blocking mode (not monitoring)
1150
+ blocking {}
1151
+ }
1152
+
1153
+ ${wafExample}`;
1154
+ return {
1155
+ description: 'HTTP Load Balancer with WAF/App Firewall protection',
1156
+ terraform: fullExample,
1157
+ markdown: [
1158
+ '# Complete Example: HTTP Load Balancer with WAF',
1159
+ '',
1160
+ 'Creates an HTTP load balancer with Web Application Firewall protection.',
1161
+ '',
1162
+ '```hcl',
1163
+ fullExample,
1164
+ '```',
1165
+ '',
1166
+ '## WAF Configuration Notes',
1167
+ '',
1168
+ '- Create `f5xc_app_firewall` resource first',
1169
+ '- Reference it in `http_loadbalancer.app_firewall` block',
1170
+ '- Use `blocking {}` for enforcement, `monitoring {}` for detection-only',
1171
+ ].join('\n'),
1172
+ };
1173
+ }
1174
+ // Default to basic
1175
+ return getHttpLoadbalancerExample('basic');
1176
+ }
1177
+ function getOriginPoolExample(pattern) {
1178
+ const example = `# Optional: Create a healthcheck resource first
1179
+ # (healthcheck is a REFERENCE BLOCK - only name/namespace/tenant are valid)
1180
+ resource "f5xc_healthcheck" "example" {
1181
+ name = "my-healthcheck"
1182
+ namespace = "default"
1183
+
1184
+ # Health check type - select ONE
1185
+ http_health_check {
1186
+ use_origin_server_name {}
1187
+ path = "/health"
1188
+ }
1189
+ # OR: tcp_health_check {}
1190
+ # OR: grpc_health_check { ... }
1191
+
1192
+ healthy_threshold = 2
1193
+ unhealthy_threshold = 3
1194
+ interval = 15
1195
+ timeout = 5
1196
+ }
1197
+
1198
+ resource "f5xc_origin_pool" "example" {
1199
+ name = "my-origin-pool"
1200
+ namespace = "default"
1201
+
1202
+ # TLS Choice - select ONE (use empty block syntax)
1203
+ # Option 1: No TLS (plain HTTP to origin)
1204
+ no_tls {}
1205
+
1206
+ # Option 2: Use TLS (uncomment and configure)
1207
+ # use_tls {
1208
+ # # Nested TLS configuration
1209
+ # skip_server_verification {}
1210
+ # # OR
1211
+ # # use_server_verification {}
1212
+ # }
1213
+
1214
+ # Port Choice - select ONE
1215
+ # Option 1: Automatic port (80 for HTTP, 443 for HTTPS)
1216
+ automatic_port {}
1217
+
1218
+ # Option 2: Same as endpoint port
1219
+ # same_as_endpoint_port {}
1220
+
1221
+ # Option 3: Explicit port number (use attribute, not block)
1222
+ # port = 8080
1223
+
1224
+ # Origin servers (at least one required)
1225
+ origin_servers {
1226
+ # Public DNS name
1227
+ public_name {
1228
+ dns_name = "api.example.com"
1229
+ }
1230
+ }
1231
+
1232
+ # Healthcheck - REFERENCE BLOCK (only name/namespace/tenant!)
1233
+ # This references the separate f5xc_healthcheck resource above
1234
+ # DO NOT put configuration parameters here (interval, timeout, etc.)
1235
+ healthcheck {
1236
+ name = f5xc_healthcheck.example.name
1237
+ namespace = "default"
1238
+ }
1239
+
1240
+ # Load balancing algorithm (attribute, not block)
1241
+ loadbalancer_algorithm = "ROUND_ROBIN"
1242
+ }`;
1243
+ return {
1244
+ description: 'Origin Pool with healthcheck reference and common configuration patterns',
1245
+ terraform: example,
1246
+ markdown: [
1247
+ '# Complete Example: Origin Pool with Healthcheck',
1248
+ '',
1249
+ 'Creates an origin pool with healthcheck reference and multiple configuration options.',
1250
+ '',
1251
+ '```hcl',
1252
+ example,
1253
+ '```',
1254
+ '',
1255
+ '## Reference Blocks vs Inline Blocks',
1256
+ '',
1257
+ '**IMPORTANT**: The `healthcheck` field is a **reference block**, NOT an inline configuration block.',
1258
+ '',
1259
+ '### Reference Blocks (only accept name/namespace/tenant)',
1260
+ '- `healthcheck {}` - References a separate `f5xc_healthcheck` resource',
1261
+ '- Configuration parameters (interval, timeout, path, etc.) go in the separate resource',
1262
+ '',
1263
+ '### Common Error (WRONG):',
1264
+ '```hcl',
1265
+ '# DO NOT DO THIS - healthcheck is a reference, not inline config',
1266
+ 'healthcheck {',
1267
+ ' interval_seconds = 30 # ERROR!',
1268
+ ' http_request {} # ERROR!',
1269
+ '}',
1270
+ '```',
1271
+ '',
1272
+ '## OneOf Groups Explained',
1273
+ '',
1274
+ '### tls_choice',
1275
+ '- `no_tls {}` - Plain HTTP to origin (use empty block)',
1276
+ '- `use_tls { ... }` - TLS to origin (use block with nested config)',
1277
+ '',
1278
+ '### port_choice',
1279
+ '- `automatic_port {}` - Auto-select based on TLS (use empty block)',
1280
+ '- `same_as_endpoint_port {}` - Use endpoint\'s port (use empty block)',
1281
+ '- `port = 8080` - Explicit port (use attribute assignment)',
1282
+ '',
1283
+ '## Key Syntax Pattern',
1284
+ '',
1285
+ 'Block-type fields like `no_tls`, `automatic_port` use `{}` syntax.',
1286
+ 'Attribute fields like `port` use `= value` syntax.',
1287
+ 'Reference fields like `healthcheck` only accept `name`/`namespace`/`tenant`.',
1288
+ ].join('\n'),
1289
+ };
1290
+ }
1291
+ function getAppFirewallExample(pattern) {
1292
+ const example = `resource "f5xc_app_firewall" "example" {
1293
+ name = "my-waf-policy"
1294
+ namespace = "default"
1295
+
1296
+ # Enforcement mode - select ONE (use empty block syntax)
1297
+ # Option 1: Blocking mode (actively blocks attacks)
1298
+ blocking {}
1299
+
1300
+ # Option 2: Monitoring mode (logs only, no blocking)
1301
+ # monitoring {}
1302
+
1303
+ # Anonymization setting - select ONE
1304
+ default_anonymization {}
1305
+ # OR: custom_anonymization { ... }
1306
+ # OR: disable_anonymization {}
1307
+
1308
+ # Bot protection settings - select ONE
1309
+ default_bot_setting {}
1310
+ # OR: custom_bot_protection_setting { ... }
1311
+
1312
+ # Detection settings - select ONE
1313
+ default_detection_settings {}
1314
+ # OR: detection_settings { ... }
1315
+ }`;
1316
+ return {
1317
+ description: 'App Firewall (WAF) with common configuration patterns',
1318
+ terraform: example,
1319
+ markdown: [
1320
+ '# Complete Example: App Firewall (WAF)',
1321
+ '',
1322
+ 'Creates a Web Application Firewall policy.',
1323
+ '',
1324
+ '```hcl',
1325
+ example,
1326
+ '```',
1327
+ '',
1328
+ '## Key OneOf Groups',
1329
+ '',
1330
+ '### enforcement_mode_choice',
1331
+ '- `blocking {}` - Actively block attacks',
1332
+ '- `monitoring {}` - Detection only, no blocking',
1333
+ '',
1334
+ '### anonymization_setting',
1335
+ '- `default_anonymization {}` - Use default settings',
1336
+ '- `custom_anonymization { ... }` - Custom configuration',
1337
+ '- `disable_anonymization {}` - No anonymization',
1338
+ '',
1339
+ '## Important',
1340
+ '',
1341
+ 'All OneOf options use **empty block syntax** `{}`, not boolean assignment.',
1342
+ ].join('\n'),
1343
+ };
1344
+ }
1345
+ // =============================================================================
1346
+ // MISTAKES OPERATION HANDLER
1347
+ // =============================================================================
1348
+ /**
1349
+ * Returns common syntax mistakes and how to fix them
1350
+ */
1351
+ function handleMistakes(input, format) {
1352
+ const { resource } = input;
1353
+ const mistakes = getCommonMistakes(resource);
1354
+ if (format === ResponseFormat.JSON) {
1355
+ return JSON.stringify({
1356
+ operation: 'mistakes',
1357
+ resource: resource || 'all',
1358
+ mistakes,
1359
+ total: mistakes.length,
1360
+ }, null, 2);
1361
+ }
1362
+ const lines = [
1363
+ '# Common Terraform Syntax Mistakes for F5XC Provider',
1364
+ '',
1365
+ ];
1366
+ if (resource) {
1367
+ lines.push(`**Resource**: ${resource}`);
1368
+ lines.push('');
1369
+ }
1370
+ for (let i = 0; i < mistakes.length; i++) {
1371
+ const m = mistakes[i];
1372
+ lines.push(`## Mistake #${i + 1}: ${m.title}`);
1373
+ lines.push('');
1374
+ lines.push(`**Severity**: ${m.severity}`);
1375
+ lines.push('');
1376
+ lines.push('**Wrong**:');
1377
+ lines.push('```hcl');
1378
+ lines.push(m.wrong);
1379
+ lines.push('```');
1380
+ lines.push('');
1381
+ lines.push('**Correct**:');
1382
+ lines.push('```hcl');
1383
+ lines.push(m.correct);
1384
+ lines.push('```');
1385
+ lines.push('');
1386
+ lines.push(`**Explanation**: ${m.explanation}`);
1387
+ lines.push('');
1388
+ if (m.detection) {
1389
+ lines.push(`**How to detect**: ${m.detection}`);
1390
+ lines.push('');
1391
+ }
1392
+ }
1393
+ return lines.join('\n');
1394
+ }
1395
+ function getCommonMistakes(resource) {
1396
+ const allMistakes = [
1397
+ {
1398
+ title: 'Boolean Assignment Instead of Empty Block',
1399
+ severity: 'Critical',
1400
+ wrong: `no_tls = true
1401
+ advertise_on_public_default_vip = true
1402
+ round_robin = true
1403
+ disable_api_definition = true
1404
+ no_challenge = true`,
1405
+ correct: `no_tls {}
1406
+ advertise_on_public_default_vip {}
1407
+ round_robin {}
1408
+ disable_api_definition {}
1409
+ no_challenge {}`,
1410
+ explanation: 'OneOf choice fields in F5XC provider are modeled as blocks, not booleans. Use empty block syntax {} to select an option.',
1411
+ detection: 'Query f5xc_terraform_metadata(operation="syntax", resource="...") and check "Block-Type Attributes" section.',
1412
+ resources: ['http_loadbalancer', 'origin_pool', 'tcp_loadbalancer', 'app_firewall'],
1413
+ },
1414
+ {
1415
+ title: 'Missing Required OneOf Selection',
1416
+ severity: 'Critical',
1417
+ wrong: `resource "f5xc_http_loadbalancer" "example" {
1418
+ name = "test"
1419
+ namespace = "default"
1420
+ domains = ["example.com"]
1421
+ # Missing loadbalancer_type selection!
1422
+ }`,
1423
+ correct: `resource "f5xc_http_loadbalancer" "example" {
1424
+ name = "test"
1425
+ namespace = "default"
1426
+ domains = ["example.com"]
1427
+
1428
+ # Required: one of http/https/https_auto_cert
1429
+ https_auto_cert {
1430
+ http_redirect = true
1431
+ }
1432
+ }`,
1433
+ explanation: 'Some OneOf groups have a required selection. For http_loadbalancer, you must specify http {}, https {}, or https_auto_cert {}.',
1434
+ detection: 'Query f5xc_terraform_metadata(operation="oneof", resource="...") to see required OneOf groups.',
1435
+ resources: ['http_loadbalancer'],
1436
+ },
1437
+ {
1438
+ title: 'Confusing Block Attribute with Simple Attribute',
1439
+ severity: 'High',
1440
+ wrong: `# Using 'port' thinking it's part of port_choice OneOf
1441
+ port {} # ERROR: port is a simple attribute
1442
+
1443
+ # Using loadbalancer_algorithm as a block
1444
+ round_robin {} # This is for hash_policy_choice, not algorithm`,
1445
+ correct: `# port is a simple integer attribute
1446
+ port = 8080
1447
+
1448
+ # loadbalancer_algorithm is a string attribute
1449
+ loadbalancer_algorithm = "ROUND_ROBIN"
1450
+
1451
+ # hash_policy_choice uses blocks for stickiness
1452
+ round_robin {} # OR source_ip_stickiness {} etc.`,
1453
+ explanation: 'Not all similarly-named fields work the same way. Some are simple attributes (use =), others are blocks (use {}).',
1454
+ detection: 'Query f5xc_terraform_metadata(operation="attribute", resource="...", attribute="...") to check the type.',
1455
+ resources: ['origin_pool', 'http_loadbalancer'],
1456
+ },
1457
+ {
1458
+ title: 'Nested Block Syntax Errors',
1459
+ severity: 'Medium',
1460
+ wrong: `use_tls {
1461
+ no_mtls = true # ERROR
1462
+ volterra_trusted_ca = true # ERROR
1463
+ }`,
1464
+ correct: `use_tls {
1465
+ no_mtls {} # Empty block syntax
1466
+ volterra_trusted_ca {} # Empty block syntax
1467
+
1468
+ tls_config {
1469
+ default_security {} # Also empty block
1470
+ }
1471
+ }`,
1472
+ explanation: 'Empty block syntax applies to nested fields too, not just top-level attributes.',
1473
+ detection: 'Recursively check nested fields using the syntax operation.',
1474
+ resources: ['origin_pool', 'http_loadbalancer'],
1475
+ },
1476
+ {
1477
+ title: 'Inline Configuration in Reference Block',
1478
+ severity: 'Critical',
1479
+ wrong: `# WRONG - trying to configure healthcheck inline
1480
+ resource "f5xc_origin_pool" "example" {
1481
+ name = "my-pool"
1482
+ namespace = "default"
1483
+
1484
+ healthcheck {
1485
+ interval_seconds = 30 # ERROR: not a valid attribute
1486
+ timeout_seconds = 5 # ERROR: not a valid attribute
1487
+ healthy_threshold = 2 # ERROR: not a valid attribute
1488
+ unhealthy_threshold = 3 # ERROR: not a valid attribute
1489
+
1490
+ http_request { # ERROR: not a valid block
1491
+ path = "/health"
1492
+ }
1493
+ }
1494
+ }`,
1495
+ correct: `# CORRECT - create healthcheck as separate resource, then reference it
1496
+ # Step 1: Create the healthcheck resource
1497
+ resource "f5xc_healthcheck" "example" {
1498
+ name = "my-healthcheck"
1499
+ namespace = "default"
1500
+
1501
+ http_health_check {
1502
+ use_origin_server_name {}
1503
+ path = "/health"
1504
+ }
1505
+
1506
+ healthy_threshold = 2
1507
+ unhealthy_threshold = 3
1508
+ interval = 30
1509
+ timeout = 5
1510
+ }
1511
+
1512
+ # Step 2: Reference it by name in origin_pool
1513
+ resource "f5xc_origin_pool" "example" {
1514
+ name = "my-pool"
1515
+ namespace = "default"
1516
+
1517
+ healthcheck {
1518
+ name = f5xc_healthcheck.example.name
1519
+ namespace = "default"
1520
+ }
1521
+ }`,
1522
+ explanation: `Some fields like "healthcheck" are REFERENCE BLOCKS - they reference separate resources, not inline configuration.
1523
+ Reference blocks ONLY accept "name", "namespace", and optionally "tenant" - NOT configuration parameters.
1524
+ The actual configuration must be done in a separate resource (e.g., f5xc_healthcheck).`,
1525
+ detection: `Query f5xc_terraform_metadata(operation="attribute", resource="origin_pool", attribute="healthcheck")
1526
+ and check if it's a reference block (only has name/namespace/tenant nested attributes).
1527
+ Or use f5xc_terraform_metadata(operation="validate", resource="origin_pool", config="...") to detect this error.`,
1528
+ resources: ['origin_pool', 'http_loadbalancer', 'tcp_loadbalancer'],
1529
+ },
1530
+ ];
1531
+ // Filter by resource if specified
1532
+ if (resource) {
1533
+ return allMistakes.filter(m => !m.resources || m.resources.includes(resource));
1534
+ }
1535
+ return allMistakes;
1536
+ }
701
1537
  // =============================================================================
702
1538
  // HELPERS
703
1539
  // =============================================================================
@@ -713,11 +1549,336 @@ function formatNoData(title, message, format) {
713
1549
  }
714
1550
  return `# ${title}\n\n${message}`;
715
1551
  }
1552
+ /**
1553
+ * Parse HCL config to extract all field/block names for validation
1554
+ */
1555
+ function parseHCLForFields(hcl) {
1556
+ const fields = [];
1557
+ const blocks = [];
1558
+ const lines = hcl.split('\n');
1559
+ // Track block nesting
1560
+ const blockStack = [];
1561
+ let braceCount = 0;
1562
+ for (let lineNum = 0; lineNum < lines.length; lineNum++) {
1563
+ const line = lines[lineNum];
1564
+ const trimmed = line.trim();
1565
+ // Skip comments and empty lines
1566
+ if (trimmed.startsWith('#') || trimmed.startsWith('//') || trimmed === '') {
1567
+ continue;
1568
+ }
1569
+ // Count braces to track nesting
1570
+ const openBraces = (line.match(/\{/g) || []).length;
1571
+ const closeBraces = (line.match(/\}/g) || []).length;
1572
+ // Check for block definition: name { or name "label" {
1573
+ const blockMatch = trimmed.match(/^(\w+)\s*(?:"[^"]*"\s*)?\{(.*)$/);
1574
+ if (blockMatch) {
1575
+ const blockName = blockMatch[1];
1576
+ const inlineContent = blockMatch[2] || '';
1577
+ // Skip resource/data/provider/variable declarations
1578
+ if (!['resource', 'data', 'provider', 'variable', 'output', 'locals', 'terraform', 'module'].includes(blockName)) {
1579
+ const parent = blockStack.length > 0 ? blockStack[blockStack.length - 1].name : undefined;
1580
+ blocks.push({
1581
+ name: blockName,
1582
+ line: lineNum + 1,
1583
+ parent,
1584
+ children: [],
1585
+ });
1586
+ fields.push({
1587
+ name: blockName,
1588
+ type: 'block',
1589
+ line: lineNum + 1,
1590
+ parent,
1591
+ });
1592
+ }
1593
+ blockStack.push({ name: blockMatch[1], line: lineNum + 1 });
1594
+ // Handle inline content after opening brace (e.g., "block { attr = value }")
1595
+ if (inlineContent.trim() && !inlineContent.trim().startsWith('}')) {
1596
+ const currentBlockName = blockStack[blockStack.length - 1].name;
1597
+ // Extract attributes from inline content
1598
+ const inlineAttrMatch = inlineContent.match(/(\w+)\s*=\s*([^}]+)/);
1599
+ if (inlineAttrMatch) {
1600
+ const inlineAttrName = inlineAttrMatch[1];
1601
+ const inlineAttrValue = inlineAttrMatch[2].trim();
1602
+ fields.push({
1603
+ name: inlineAttrName,
1604
+ type: 'attribute',
1605
+ line: lineNum + 1,
1606
+ parent: currentBlockName,
1607
+ value: inlineAttrValue,
1608
+ });
1609
+ }
1610
+ // Check for nested blocks in inline content
1611
+ const inlineBlockMatch = inlineContent.match(/(\w+)\s*\{/);
1612
+ if (inlineBlockMatch && !inlineAttrMatch) {
1613
+ fields.push({
1614
+ name: inlineBlockMatch[1],
1615
+ type: 'block',
1616
+ line: lineNum + 1,
1617
+ parent: currentBlockName,
1618
+ });
1619
+ }
1620
+ }
1621
+ }
1622
+ // Check for attribute assignment: name = value (only if not part of a block definition)
1623
+ const attrMatch = trimmed.match(/^(\w+)\s*=\s*(.+?)$/);
1624
+ if (attrMatch && !blockMatch) {
1625
+ const attrName = attrMatch[1];
1626
+ const attrValue = attrMatch[2];
1627
+ const parent = blockStack.length > 0 ? blockStack[blockStack.length - 1].name : undefined;
1628
+ // Skip top-level resource attributes like 'name', 'namespace' at resource level
1629
+ if (blockStack.length > 0 || !['resource', 'data'].includes(blockStack[0]?.name || '')) {
1630
+ fields.push({
1631
+ name: attrName,
1632
+ type: 'attribute',
1633
+ line: lineNum + 1,
1634
+ parent: parent !== 'resource' ? parent : undefined,
1635
+ value: attrValue,
1636
+ });
1637
+ }
1638
+ }
1639
+ // Track brace closing
1640
+ braceCount += openBraces - closeBraces;
1641
+ if (closeBraces > 0 && blockStack.length > 0) {
1642
+ for (let i = 0; i < closeBraces; i++) {
1643
+ if (blockStack.length > 0) {
1644
+ blockStack.pop();
1645
+ }
1646
+ }
1647
+ }
1648
+ }
1649
+ return { fields, blocks };
1650
+ }
1651
+ /**
1652
+ * Known reference block field names in F5XC provider.
1653
+ * These blocks only accept name/namespace/tenant, not inline configuration.
1654
+ *
1655
+ * COMPREHENSIVE LIST - derived from analysis of all 98 resources in the provider.
1656
+ * This list is used for deterministic detection when description patterns are unclear.
1657
+ */
1658
+ const KNOWN_REFERENCE_BLOCKS = new Set([
1659
+ // Security references
1660
+ 'app_firewall',
1661
+ 'bot_defense_policy',
1662
+ 'csrf_policy',
1663
+ 'data_guard_rules',
1664
+ 'dos_protection',
1665
+ 'graphql_rules',
1666
+ 'ip_reputation',
1667
+ 'jwt_validation',
1668
+ 'malicious_user_mitigation',
1669
+ 'protected_cookies',
1670
+ 'slow_ddos_mitigation',
1671
+ 'trusted_clients',
1672
+ 'usb_policy',
1673
+ 'waf_exclusion_rules',
1674
+ // Load balancer and pool references
1675
+ 'healthcheck',
1676
+ 'origin_pool',
1677
+ 'pool',
1678
+ 'cluster',
1679
+ // Policy references
1680
+ 'rate_limiter',
1681
+ 'rate_limiter_allowed_prefixes',
1682
+ 'service_policy',
1683
+ 'policer',
1684
+ // Identity references
1685
+ 'user_identification',
1686
+ 'authentication',
1687
+ 'ad_client',
1688
+ // API references
1689
+ 'api_definition',
1690
+ 'api_definitions',
1691
+ // Certificate references
1692
+ 'certificate',
1693
+ 'certificates',
1694
+ 'crl',
1695
+ 'trusted_ca',
1696
+ 'trusted_ca_list',
1697
+ // Infrastructure references
1698
+ 'site',
1699
+ 'virtual_site',
1700
+ 'virtual_network',
1701
+ 'segment',
1702
+ 'network_connector',
1703
+ 'cloud_credentials',
1704
+ 'aws_cred',
1705
+ 'azure_cred',
1706
+ 'gcp_cred',
1707
+ 'k8s_cluster',
1708
+ 'log_receiver',
1709
+ // Site group references
1710
+ 'dc_cluster_group',
1711
+ 'dc_cluster_group_inside',
1712
+ 'dc_cluster_group_sli',
1713
+ 'dc_cluster_group_slo',
1714
+ 'ce_site_reference',
1715
+ // Selector references (name/namespace pattern)
1716
+ 'client_selector',
1717
+ 'server_selector',
1718
+ 'site_selector',
1719
+ 'prefix_selector',
1720
+ 'proxy_label_selector',
1721
+ 'ip_prefix_set',
1722
+ // Advertise location references
1723
+ 'public_ip',
1724
+ 'where',
1725
+ // DR references
1726
+ 'sli_to_global_dr',
1727
+ 'slo_to_global_dr',
1728
+ // Nested reference blocks (commonly found inside other blocks)
1729
+ 'policies', // inside active_service_policies
1730
+ ]);
1731
+ /**
1732
+ * Description patterns that indicate a reference block.
1733
+ * These are exact patterns from F5XC API documentation.
1734
+ */
1735
+ const REFERENCE_DESCRIPTION_PATTERNS = [
1736
+ // Exact F5XC pattern for object references
1737
+ 'type establishes a direct reference from one object',
1738
+ 'establishes a direct reference',
1739
+ 'such a reference is in form of tenant/namespace/name',
1740
+ 'reference is in form of tenant/namespace/name',
1741
+ // Simpler reference patterns
1742
+ 'reference to',
1743
+ 'references to',
1744
+ 'reference object',
1745
+ 'refers to another',
1746
+ // Configuration object reference patterns
1747
+ 'configuration object(e.g. virtual_host) refers to another',
1748
+ 'when a configuration object',
1749
+ ];
1750
+ /**
1751
+ * Check if an attribute is a reference block (only accepts name/namespace/tenant)
1752
+ *
1753
+ * Detection strategy (in order of precedence):
1754
+ * 1. Known reference block names (deterministic, hardcoded list)
1755
+ * 2. Description pattern matching (F5XC documentation patterns)
1756
+ * 3. Type + block flag analysis
1757
+ *
1758
+ * This function is critical for preventing AI trial-and-error by correctly
1759
+ * identifying blocks that ONLY accept name/namespace/tenant.
1760
+ */
1761
+ function isReferenceBlock(attrMeta, fieldName) {
1762
+ // Strategy 1: Check if it's a known reference block (most reliable)
1763
+ if (fieldName && KNOWN_REFERENCE_BLOCKS.has(fieldName)) {
1764
+ return true;
1765
+ }
1766
+ // Strategy 2: Must be a block type for description-based detection
1767
+ if (!attrMeta.is_block && attrMeta.type !== 'object' && attrMeta.type !== 'list') {
1768
+ return false;
1769
+ }
1770
+ // Strategy 3: Check description for F5XC reference patterns
1771
+ const desc = (attrMeta.description || '').toLowerCase();
1772
+ // Check against all known reference patterns
1773
+ for (const pattern of REFERENCE_DESCRIPTION_PATTERNS) {
1774
+ if (desc.includes(pattern.toLowerCase())) {
1775
+ return true;
1776
+ }
1777
+ }
1778
+ return false;
1779
+ }
1780
+ /**
1781
+ * Calculate Levenshtein distance between two strings
1782
+ */
1783
+ function levenshteinDistance(str1, str2) {
1784
+ const m = str1.length;
1785
+ const n = str2.length;
1786
+ // Create distance matrix
1787
+ const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));
1788
+ // Initialize base cases
1789
+ for (let i = 0; i <= m; i++)
1790
+ dp[i][0] = i;
1791
+ for (let j = 0; j <= n; j++)
1792
+ dp[0][j] = j;
1793
+ // Fill in the rest of the matrix
1794
+ for (let i = 1; i <= m; i++) {
1795
+ for (let j = 1; j <= n; j++) {
1796
+ if (str1[i - 1] === str2[j - 1]) {
1797
+ dp[i][j] = dp[i - 1][j - 1];
1798
+ }
1799
+ else {
1800
+ dp[i][j] = 1 + Math.min(dp[i - 1][j], // deletion
1801
+ dp[i][j - 1], // insertion
1802
+ dp[i - 1][j - 1]);
1803
+ }
1804
+ }
1805
+ }
1806
+ return dp[m][n];
1807
+ }
1808
+ /**
1809
+ * Find similar field names for "did you mean?" suggestions
1810
+ */
1811
+ function findSimilarFieldNames(typo, validFields, maxDistance = 3) {
1812
+ return validFields
1813
+ .map(f => ({ field: f, distance: levenshteinDistance(typo.toLowerCase(), f.toLowerCase()) }))
1814
+ .filter(x => x.distance <= maxDistance && x.distance > 0)
1815
+ .sort((a, b) => a.distance - b.distance)
1816
+ .slice(0, 3)
1817
+ .map(x => x.field);
1818
+ }
1819
+ /**
1820
+ * Get the resource that a reference block points to.
1821
+ * Comprehensive mapping for all known reference blocks.
1822
+ */
1823
+ function getReferenceResourceName(fieldName) {
1824
+ // Comprehensive reference field mappings
1825
+ const mappings = {
1826
+ // Security resources
1827
+ 'app_firewall': 'f5xc_app_firewall',
1828
+ 'waf': 'f5xc_app_firewall',
1829
+ 'bot_defense_policy': 'f5xc_bot_defense_policy',
1830
+ 'malicious_user_mitigation': 'f5xc_malicious_user_mitigation',
1831
+ 'usb_policy': 'f5xc_usb_policy',
1832
+ // Load balancer and pool resources
1833
+ 'healthcheck': 'f5xc_healthcheck',
1834
+ 'origin_pool': 'f5xc_origin_pool',
1835
+ 'pool': 'f5xc_origin_pool',
1836
+ 'cluster': 'f5xc_cluster',
1837
+ // Policy resources
1838
+ 'service_policy': 'f5xc_service_policy',
1839
+ 'policies': 'f5xc_service_policy',
1840
+ 'rate_limiter': 'f5xc_rate_limiter',
1841
+ 'policer': 'f5xc_policer',
1842
+ // Identity resources
1843
+ 'user_identification': 'f5xc_user_identification',
1844
+ 'authentication': 'f5xc_authentication',
1845
+ // API resources
1846
+ 'api_definition': 'f5xc_api_definition',
1847
+ 'api_definitions': 'f5xc_api_definition',
1848
+ // Certificate resources
1849
+ 'certificate': 'f5xc_certificate',
1850
+ 'certificates': 'f5xc_certificate',
1851
+ 'crl': 'f5xc_crl',
1852
+ 'trusted_ca': 'f5xc_trusted_ca_list',
1853
+ 'trusted_ca_list': 'f5xc_trusted_ca_list',
1854
+ // Infrastructure resources
1855
+ 'site': 'f5xc_site',
1856
+ 'virtual_site': 'f5xc_virtual_site',
1857
+ 'virtual_network': 'f5xc_virtual_network',
1858
+ 'segment': 'f5xc_segment',
1859
+ 'network_connector': 'f5xc_network_connector',
1860
+ 'cloud_credentials': 'f5xc_cloud_credentials',
1861
+ 'aws_cred': 'f5xc_cloud_credentials',
1862
+ 'azure_cred': 'f5xc_cloud_credentials',
1863
+ 'gcp_cred': 'f5xc_cloud_credentials',
1864
+ 'k8s_cluster': 'f5xc_cluster',
1865
+ 'log_receiver': 'f5xc_log_receiver',
1866
+ // Site group resources
1867
+ 'dc_cluster_group': 'f5xc_dc_cluster_group',
1868
+ 'dc_cluster_group_inside': 'f5xc_dc_cluster_group',
1869
+ 'dc_cluster_group_sli': 'f5xc_dc_cluster_group',
1870
+ 'dc_cluster_group_slo': 'f5xc_dc_cluster_group',
1871
+ // IP/Prefix resources
1872
+ 'ip_prefix_set': 'f5xc_ip_prefix_set',
1873
+ 'public_ip': 'f5xc_cloud_elastic_ip',
1874
+ };
1875
+ return mappings[fieldName] || `f5xc_${fieldName}`;
1876
+ }
716
1877
  // =============================================================================
717
1878
  // TOOL DEFINITION
718
1879
  // =============================================================================
719
1880
  export const METADATA_TOOL_DEFINITION = {
720
1881
  name: 'f5xc_terraform_metadata',
721
- description: 'Query resource metadata for deterministic Terraform configuration generation. Operations: oneof (mutually exclusive fields), validation (regex/range patterns), defaults, enums, attribute (full metadata), requires_replace, tier (subscription requirements), dependencies (creation order), troubleshoot (error remediation), syntax (correct Terraform block vs attribute syntax), summary.',
1882
+ description: 'Query resource metadata for deterministic Terraform configuration generation. Operations: oneof (mutually exclusive fields), validation (regex/range patterns), defaults, enums, attribute (full metadata), requires_replace, tier (subscription requirements), dependencies (creation order), troubleshoot (error remediation), syntax (correct Terraform block vs attribute syntax), validate (check config for syntax errors), example (generate complete working examples), mistakes (common errors and fixes), summary.',
722
1883
  };
723
1884
  //# sourceMappingURL=metadata.js.map