@@ -14,17 +14,97 @@ type ProjectNotFoundError struct {
1414}
1515
1616func (e * ProjectNotFoundError ) Error () string {
17- if e == nil {
18- return "project not found"
19- }
20- return fmt .Sprintf ("project not found for region %s" , strings .TrimSpace (e .Region ))
17+ return "project not found"
2118}
2219
2320func IsProjectNotFound (err error ) bool {
2421 var target * ProjectNotFoundError
2522 return errors .As (err , & target )
2623}
2724
25+ type ProjectCatalog struct {
26+ byRegion map [string ]string
27+ }
28+
29+ // NewProjectCatalog indexes IAM projects by region name, preferring projects
30+ // in domainID when duplicate names are present.
31+ func NewProjectCatalog (projects []IAMProject , domainID string ) * ProjectCatalog {
32+ domainID = strings .TrimSpace (domainID )
33+ matched := make (map [string ]string , len (projects ))
34+ fallback := make (map [string ]string )
35+ for _ , project := range projects {
36+ region := strings .TrimSpace (project .Name )
37+ projectID := strings .TrimSpace (project .ID )
38+ if region == "" || projectID == "" {
39+ continue
40+ }
41+ if domainID == "" || strings .TrimSpace (project .DomainID ) == domainID {
42+ if matched [region ] == "" {
43+ matched [region ] = projectID
44+ }
45+ continue
46+ }
47+ if fallback [region ] == "" {
48+ fallback [region ] = projectID
49+ }
50+ }
51+ for region , projectID := range fallback {
52+ if matched [region ] == "" {
53+ matched [region ] = projectID
54+ }
55+ }
56+ return & ProjectCatalog {byRegion : matched }
57+ }
58+
59+ // ProjectID returns the project ID for region when the catalog contains it.
60+ func (c * ProjectCatalog ) ProjectID (region string ) (string , bool ) {
61+ if c == nil {
62+ return "" , false
63+ }
64+ projectID := strings .TrimSpace (c .byRegion [strings .TrimSpace (region )])
65+ return projectID , projectID != ""
66+ }
67+
68+ // FilterRegions keeps only regions that exist in the catalog.
69+ func (c * ProjectCatalog ) FilterRegions (regions []string ) []string {
70+ if c == nil {
71+ return append ([]string (nil ), regions ... )
72+ }
73+ filtered := make ([]string , 0 , len (regions ))
74+ for _ , region := range regions {
75+ if _ , ok := c .ProjectID (region ); ok {
76+ filtered = append (filtered , region )
77+ }
78+ }
79+ return filtered
80+ }
81+
82+ // ListAccessibleProjectCatalog fetches the project list visible to the current
83+ // credential and indexes it by region name.
84+ func ListAccessibleProjectCatalog (ctx context.Context , client * Client , domainID string ) (* ProjectCatalog , error ) {
85+ if client == nil {
86+ return nil , fmt .Errorf ("huawei project catalog: nil client" )
87+ }
88+
89+ controlPlaneRegion := strings .TrimSpace (client .credential .Region )
90+ if controlPlaneRegion == "" || controlPlaneRegion == "all" {
91+ return nil , fmt .Errorf ("huawei project catalog: unresolved control plane region %q" , controlPlaneRegion )
92+ }
93+
94+ var resp ListProjectsResponse
95+ if err := client .DoJSON (ctx , Request {
96+ Service : "iam" ,
97+ Region : controlPlaneRegion ,
98+ Intl : client .credential .Intl ,
99+ Method : http .MethodGet ,
100+ Path : "/v3/auth/projects" ,
101+ Idempotent : true ,
102+ }, & resp ); err != nil {
103+ return nil , err
104+ }
105+ return NewProjectCatalog (resp .Projects , domainID ), nil
106+ }
107+
28108// ResolveProjectID maps a region name to its project ID via the IAM control
29109// plane. The client must be configured with a concrete control-plane region.
30110func ResolveProjectID (ctx context.Context , client * Client , domainID , targetRegion string ) (string , error ) {
0 commit comments