Skip to content

Commit f3bd6c3

Browse files
source service API and service package for all 99% of TGC resources (#14045) (#22962)
[upstream:8ae4f86ec321d26274135376ffe449c5f1e18b40] Signed-off-by: Modular Magician <[email protected]>
1 parent ec23f5f commit f3bd6c3

File tree

3 files changed

+363
-32
lines changed

3 files changed

+363
-32
lines changed
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
// ----------------------------------------------------------------------------
4+
//
5+
// *** AUTO GENERATED CODE *** Type: Handwritten ***
6+
//
7+
// ----------------------------------------------------------------------------
8+
//
9+
// This code is generated by Magic Modules using the following:
10+
//
11+
// Source file: https://github.com/GoogleCloudPlatform/magic-modules/tree/main/mmv1/third_party/terraform/acctest/resource_inventory_reader.go
12+
//
13+
// DO NOT EDIT this file directly. Any changes made to this file will be
14+
// overwritten during the next generation cycle.
15+
//
16+
// ----------------------------------------------------------------------------
17+
package acctest
18+
19+
import (
20+
"fmt"
21+
"os"
22+
"path/filepath"
23+
"strings"
24+
"sync"
25+
26+
"gopkg.in/yaml.v2"
27+
)
28+
29+
// ResourceYAMLMetadata represents the structure of the metadata files
30+
type ResourceYAMLMetadata struct {
31+
Resource string `yaml:"resource"`
32+
ApiServiceName string `yaml:"api_service_name"`
33+
SourceFile string `yaml:"source_file"`
34+
}
35+
36+
// Cache structures to avoid repeated file system operations
37+
var (
38+
// Cache for API service names (resourceName -> apiServiceName)
39+
apiServiceNameCache = make(map[string]string)
40+
// Cache for service packages (resourceType -> servicePackage)
41+
servicePackageCache = make(map[string]string)
42+
// Flag to track if cache has been populated
43+
cachePopulated = false
44+
// Mutex to protect cache access
45+
cacheMutex sync.RWMutex
46+
)
47+
48+
// PopulateMetadataCache walks through all metadata files once and populates
49+
// both the API service name and service package caches for improved performance
50+
func PopulateMetadataCache() error {
51+
cacheMutex.Lock()
52+
defer cacheMutex.Unlock()
53+
54+
// If cache is already populated, we can skip
55+
if cachePopulated {
56+
return nil
57+
}
58+
59+
baseDir, err := getServicesDir()
60+
if err != nil {
61+
return fmt.Errorf("failed to find services directory: %v", err)
62+
}
63+
64+
// Count for statistics
65+
apiNameCount := 0
66+
servicePkgCount := 0
67+
68+
// Walk through all service directories once
69+
err = filepath.Walk(baseDir, func(path string, info os.FileInfo, err error) error {
70+
if err != nil {
71+
return nil // Skip files with errors but continue walking
72+
}
73+
74+
// Look for metadata files
75+
if !info.IsDir() && strings.HasPrefix(info.Name(), "resource_") && strings.HasSuffix(info.Name(), "_meta.yaml") {
76+
// Read the file
77+
content, err := os.ReadFile(path)
78+
if err != nil {
79+
return nil // Continue to next file
80+
}
81+
82+
// Parse YAML
83+
var metadata ResourceYAMLMetadata
84+
if err := yaml.Unmarshal(content, &metadata); err != nil {
85+
return nil // Continue to next file
86+
}
87+
88+
// Skip if resource is empty
89+
if metadata.Resource == "" {
90+
return nil
91+
}
92+
93+
// Store API service name in cache
94+
if metadata.ApiServiceName != "" {
95+
apiServiceNameCache[metadata.Resource] = metadata.ApiServiceName
96+
apiNameCount++
97+
}
98+
99+
// Extract and store service package in cache
100+
pathParts := strings.Split(path, string(os.PathSeparator))
101+
servicesIndex := -1
102+
for i, part := range pathParts {
103+
if part == "services" {
104+
servicesIndex = i
105+
break
106+
}
107+
}
108+
109+
if servicesIndex >= 0 && len(pathParts) > servicesIndex+1 {
110+
servicePackage := pathParts[servicesIndex+1] // The part after "services"
111+
servicePackageCache[metadata.Resource] = servicePackage
112+
servicePkgCount++
113+
}
114+
}
115+
return nil
116+
})
117+
118+
if err != nil {
119+
return fmt.Errorf("error walking directory: %v", err)
120+
}
121+
122+
// Mark cache as populated
123+
cachePopulated = true
124+
125+
return nil
126+
}
127+
128+
// GetAPIServiceNameForResource finds the api_service_name for a given resource name
129+
// If projectRoot is empty, it will attempt to find the project root automatically
130+
func GetAPIServiceNameForResource(resourceName string) string {
131+
// Make sure cache is populated
132+
if !cachePopulated {
133+
if err := PopulateMetadataCache(); err != nil {
134+
return "failed_to_populate_metadata_cache"
135+
}
136+
}
137+
138+
// Check cache
139+
cacheMutex.RLock()
140+
apiServiceName, found := apiServiceNameCache[resourceName]
141+
cacheMutex.RUnlock()
142+
143+
if !found {
144+
return "unknown"
145+
}
146+
147+
return apiServiceName
148+
}
149+
150+
// GetServicePackageForResourceType finds the service package for a given resource type
151+
// If projectRoot is empty, it will attempt to find the project root automatically
152+
func GetServicePackageForResourceType(resourceType string) string {
153+
// Make sure cache is populated
154+
if !cachePopulated {
155+
if err := PopulateMetadataCache(); err != nil {
156+
return "failed_to_populate_metadata_cache"
157+
}
158+
}
159+
160+
// Check cache
161+
cacheMutex.RLock()
162+
servicePackage, found := servicePackageCache[resourceType]
163+
cacheMutex.RUnlock()
164+
165+
if !found {
166+
return "unknown"
167+
}
168+
169+
return servicePackage
170+
}
171+
172+
// getServicesDir returns the path to the services directory
173+
// It will attempt to find the project root relative to cwd
174+
func getServicesDir() (string, error) {
175+
// Try to find project root
176+
root, err := findProjectRoot()
177+
if err == nil {
178+
servicesDir := filepath.Join(root, "google-beta", "services")
179+
if _, err := os.Stat(servicesDir); err == nil {
180+
return servicesDir, nil
181+
}
182+
}
183+
184+
// Last resort: try relative to current directory
185+
currentDir, err := os.Getwd()
186+
if err != nil {
187+
return "", fmt.Errorf("failed to determine current directory: %v", err)
188+
}
189+
190+
// Try a few common relative paths
191+
potentialPaths := []string{
192+
filepath.Join(currentDir, "google-beta", "services"),
193+
filepath.Join(currentDir, "..", "google-beta", "services"),
194+
filepath.Join(currentDir, "..", "..", "google-beta", "services"),
195+
}
196+
197+
for _, path := range potentialPaths {
198+
if _, err := os.Stat(path); err == nil {
199+
return path, nil
200+
}
201+
}
202+
203+
return "", fmt.Errorf("unable to locate services directory, please provide explicit project root path")
204+
}
205+
206+
// findProjectRoot walks up from the current directory to find the project root
207+
// by looking for the go.mod file
208+
func findProjectRoot() (string, error) {
209+
dir, err := os.Getwd()
210+
if err != nil {
211+
return "", err
212+
}
213+
214+
for {
215+
// Check if go.mod exists in the current directory
216+
if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {
217+
return dir, nil
218+
}
219+
220+
// Move up to the parent directory
221+
parentDir := filepath.Dir(dir)
222+
if parentDir == dir {
223+
// Reached the filesystem root without finding go.mod
224+
return "", fmt.Errorf("could not find go.mod file in any parent directory")
225+
}
226+
dir = parentDir
227+
}
228+
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
// ----------------------------------------------------------------------------
4+
//
5+
// *** AUTO GENERATED CODE *** Type: Handwritten ***
6+
//
7+
// ----------------------------------------------------------------------------
8+
//
9+
// This code is generated by Magic Modules using the following:
10+
//
11+
// Source file: https://github.com/GoogleCloudPlatform/magic-modules/tree/main/mmv1/third_party/terraform/acctest/resource_inventory_test.go
12+
//
13+
// DO NOT EDIT this file directly. Any changes made to this file will be
14+
// overwritten during the next generation cycle.
15+
//
16+
// ----------------------------------------------------------------------------
17+
package acctest_test
18+
19+
import (
20+
"strings"
21+
"testing"
22+
23+
"github.com/hashicorp/terraform-provider-google/google/acctest"
24+
"github.com/hashicorp/terraform-provider-google/google/provider"
25+
)
26+
27+
func TestResourceInventoryMetadataFound(t *testing.T) {
28+
resources := provider.ResourceMap()
29+
30+
// Track statistics
31+
var (
32+
totalResources = 0
33+
missingServicePkg = 0
34+
missingServiceName = 0
35+
)
36+
37+
// Create a map to store missing resources for summary report
38+
missingServicePkgResources := make(map[string]bool)
39+
missingServiceNameResources := make(map[string]bool)
40+
41+
for resourceType := range resources {
42+
if strings.HasSuffix(resourceType, "_iam_member") ||
43+
strings.HasSuffix(resourceType, "_iam_policy") ||
44+
strings.HasSuffix(resourceType, "_iam_binding") {
45+
continue
46+
}
47+
totalResources++
48+
49+
// Log each resource being checked
50+
// t.Logf("Checking metadata for resource: %s", resourceType)
51+
52+
// Check for service package
53+
servicePackage := acctest.GetServicePackageForResourceType(resourceType)
54+
if servicePackage == "unknown" {
55+
// t.Logf("WARNING: Could not find service package for resource %s: %v", resourceType)
56+
missingServicePkg++
57+
missingServicePkgResources[resourceType] = true
58+
}
59+
60+
apiServiceName := acctest.GetAPIServiceNameForResource(resourceType)
61+
// Check for API service name
62+
if apiServiceName == "unknown" {
63+
// t.Logf("WARNING: Could not find API service name for resource %s: %v", resourceType)
64+
missingServiceName++
65+
missingServiceNameResources[resourceType] = true
66+
}
67+
t.Logf(" %s servicePackage: %s apiServiceName: %s", resourceType, servicePackage, apiServiceName)
68+
69+
}
70+
71+
// Generate a summary report
72+
t.Logf("\n--- RESOURCE METADATA TEST SUMMARY ---")
73+
t.Logf("Total resources checked: %d", totalResources)
74+
t.Logf("Resources missing service package: %d (%.1f%%)",
75+
missingServicePkg,
76+
float64(missingServicePkg)/float64(totalResources)*100)
77+
t.Logf("Resources missing API service name: %d (%.1f%%)",
78+
missingServiceName,
79+
float64(missingServiceName)/float64(totalResources)*100)
80+
81+
// List resources missing metadata (limited to first 10 for readability)
82+
if len(missingServicePkgResources) > 0 {
83+
t.Log("\nResources missing service package (first 10):")
84+
count := 0
85+
for res := range missingServicePkgResources {
86+
t.Logf(" - %s", res)
87+
count++
88+
if count >= 10 {
89+
remaining := len(missingServicePkgResources) - 10
90+
if remaining > 0 {
91+
t.Logf(" ... and %d more", remaining)
92+
}
93+
break
94+
}
95+
}
96+
}
97+
98+
if len(missingServiceNameResources) > 0 {
99+
t.Log("\nResources missing API service name (first 10):")
100+
count := 0
101+
for res := range missingServiceNameResources {
102+
t.Logf(" - %s", res)
103+
count++
104+
if count >= 10 {
105+
remaining := len(missingServiceNameResources) - 10
106+
if remaining > 0 {
107+
t.Logf(" ... and %d more", remaining)
108+
}
109+
break
110+
}
111+
}
112+
}
113+
114+
// Decide whether to fail the test based on coverage percentage
115+
const requiredCoveragePercent = 90.0
116+
117+
servicePkgCoverage := (float64(totalResources-missingServicePkg) / float64(totalResources)) * 100
118+
serviceNameCoverage := (float64(totalResources-missingServiceName) / float64(totalResources)) * 100
119+
120+
if servicePkgCoverage < requiredCoveragePercent {
121+
t.Errorf("Service package metadata coverage (%.1f%%) is below required threshold (%.1f%%)",
122+
servicePkgCoverage, requiredCoveragePercent)
123+
}
124+
125+
if serviceNameCoverage < requiredCoveragePercent {
126+
t.Errorf("API service name metadata coverage (%.1f%%) is below required threshold (%.1f%%)",
127+
serviceNameCoverage, requiredCoveragePercent)
128+
}
129+
}

0 commit comments

Comments
 (0)