@@ -413,12 +413,23 @@ func resourceSummaryKeys(resType string, cfg map[string]any) [][2]string {
413413
414414 case "infra.firewall" :
415415 add (& pairs , "name" , str ("name" ))
416- // Inbound rules summary.
417- if inbound , ok := cfg ["inbound" ]; ok {
418- add (& pairs , "inbound" , formatFirewallRules (inbound ))
416+ // Inbound rules — try both key variants.
417+ for _ , key := range []string {"inbound_rules" , "inbound" } {
418+ if rules , ok := cfg [key ]; ok {
419+ for _ , line := range formatFirewallRulesList (rules ) {
420+ add (& pairs , "allow inbound" , line )
421+ }
422+ break
423+ }
419424 }
420- if outbound , ok := cfg ["outbound" ]; ok {
421- add (& pairs , "outbound" , formatFirewallRules (outbound ))
425+ // Outbound rules.
426+ for _ , key := range []string {"outbound_rules" , "outbound" } {
427+ if rules , ok := cfg [key ]; ok {
428+ for _ , line := range formatFirewallRulesList (rules ) {
429+ add (& pairs , "allow outbound" , line )
430+ }
431+ break
432+ }
422433 }
423434
424435 case "infra.database" :
@@ -493,7 +504,68 @@ func resourceSummaryKeys(resType string, cfg map[string]any) [][2]string {
493504 return pairs
494505}
495506
496- // formatFirewallRules produces a compact summary of firewall rule config.
507+ // formatFirewallRulesList returns one summary line per firewall rule.
508+ func formatFirewallRulesList (v any ) []string {
509+ rules , ok := v .([]any )
510+ if ! ok {
511+ return nil
512+ }
513+ var lines []string
514+ for _ , r := range rules {
515+ rm , ok := r .(map [string ]any )
516+ if ! ok {
517+ continue
518+ }
519+ var parts []string
520+ if proto , ok := rm ["protocol" ].(string ); ok && proto != "" {
521+ parts = append (parts , strings .ToUpper (proto ))
522+ }
523+ if ports , ok := rm ["ports" ].(string ); ok && ports != "" {
524+ parts = append (parts , ports )
525+ }
526+ // Handle both "source"/"sources" and "destination"/"destinations".
527+ if src := extractSources (rm , "source" , "sources" ); src != "" {
528+ parts = append (parts , "from " + src )
529+ }
530+ if dst := extractSources (rm , "destination" , "destinations" ); dst != "" {
531+ parts = append (parts , "to " + dst )
532+ }
533+ if len (parts ) > 0 {
534+ lines = append (lines , strings .Join (parts , " " ))
535+ }
536+ }
537+ return lines
538+ }
539+
540+ // extractSources gets a string or []string from a map, trying both singular and plural keys.
541+ func extractSources (m map [string ]any , singular , plural string ) string {
542+ // Try plural first (array of IPs).
543+ if v , ok := m [plural ]; ok {
544+ switch sv := v .(type ) {
545+ case []any :
546+ strs := make ([]string , 0 , len (sv ))
547+ for _ , s := range sv {
548+ if str , ok := s .(string ); ok {
549+ strs = append (strs , str )
550+ }
551+ }
552+ return strings .Join (strs , "," )
553+ case []string :
554+ return strings .Join (sv , "," )
555+ case string :
556+ return sv
557+ }
558+ }
559+ // Try singular.
560+ if v , ok := m [singular ]; ok {
561+ if s , ok := v .(string ); ok {
562+ return s
563+ }
564+ }
565+ return ""
566+ }
567+
568+ // formatFirewallRules produces a compact summary of firewall rule config (legacy, single-line).
497569func formatFirewallRules (v any ) string {
498570 switch rules := v .(type ) {
499571 case []any :
0 commit comments