package cloud import ( "encoding/hex" "encoding/json" "fmt" "io" "net/http" "sort" "strings" "sync" "time" ) // HuaweiProvider 通过华为云 AK/SK 签名 API 同步项目区域与 ECS 实例。 // Logan.Li type HuaweiProvider struct { accessKey, secretKey string regionMu sync.RWMutex regionByProject map[string]string } // NewHuaweiProvider 创建华为云 Provider。 // Logan.Li func NewHuaweiProvider(accessKey, secretKey string) *HuaweiProvider { return &HuaweiProvider{accessKey: accessKey, secretKey: secretKey, regionByProject: make(map[string]string)} } // Name 返回华为云厂商标识。 // Logan.Li func (p *HuaweiProvider) Name() string { return "huawei" } // signedGet 使用 SDK-HMAC-SHA256 调用华为云 GET API。 // Logan.Li func (p *HuaweiProvider) signedGet(rawURL, _ string) ([]byte, error) { u, err := http.NewRequest(http.MethodGet, rawURL, nil) if err != nil { return nil, fmt.Errorf("创建华为云请求失败: %w", err) } now := time.Now().UTC().Format("20060102T150405Z") host := u.URL.Host canonicalHeaders := "host:" + host + "\n" + "x-sdk-date:" + now + "\n" signedHeaders := "host;x-sdk-date" canonicalURI := u.URL.EscapedPath() if !strings.HasSuffix(canonicalURI, "/") { canonicalURI += "/" } canonicalRequest := "GET\n" + canonicalURI + "\n" + u.URL.Query().Encode() + "\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + sha256Hex("") stringToSign := "SDK-HMAC-SHA256\n" + now + "\n" + sha256Hex(canonicalRequest) signature := hex.EncodeToString(hmacSHA256([]byte(p.secretKey), stringToSign)) u.Header.Set("X-Sdk-Date", now) u.Header.Set("Authorization", "SDK-HMAC-SHA256 Access="+p.accessKey+", SignedHeaders="+signedHeaders+", Signature="+signature) resp, err := (&http.Client{Timeout: 30 * time.Second}).Do(u) if err != nil { return nil, fmt.Errorf("请求华为云 API 失败: %w", err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("读取华为云响应失败: %w", err) } if resp.StatusCode < 200 || resp.StatusCode >= 300 { return nil, fmt.Errorf("华为云 API 返回 HTTP %d: %s", resp.StatusCode, string(body)) } return body, nil } // ListRegions 获取当前账号的项目列表,项目 ID 作为可用于 ECS 请求的区域代码。 // Logan.Li func (p *HuaweiProvider) ListRegions() ([]CloudRegion, error) { body, err := p.signedGet("https://iam.myhuaweicloud.com/v3/projects", "iam") if err != nil { return nil, err } var result struct { Projects []struct { ID string `json:"id"` Name string `json:"name"` } `json:"projects"` } if err := json.Unmarshal(body, &result); err != nil { return nil, fmt.Errorf("解析华为云区域响应失败: %w", err) } regions := []CloudRegion{} regionByProject := make(map[string]string, len(result.Projects)) for _, project := range result.Projects { if project.ID == "" || project.Name == "" { continue } regions = append(regions, CloudRegion{RegionID: project.ID, LocalName: project.Name, Endpoint: "https://ecs." + project.Name + ".myhuaweicloud.com", Status: "active"}) regionByProject[project.ID] = project.Name } p.regionMu.Lock() p.regionByProject = regionByProject p.regionMu.Unlock() sort.Slice(regions, func(i, j int) bool { return regions[i].LocalName < regions[j].LocalName }) return regions, nil } // ListInstances 获取指定华为云项目中的 ECS 实例。 // Logan.Li func (p *HuaweiProvider) ListInstances(projectID string) ([]CloudInstance, error) { p.regionMu.RLock() regionName := p.regionByProject[projectID] p.regionMu.RUnlock() if regionName == "" { if _, err := p.ListRegions(); err != nil { return nil, err } p.regionMu.RLock() regionName = p.regionByProject[projectID] p.regionMu.RUnlock() } if regionName == "" { return nil, fmt.Errorf("未找到华为云项目 %s", projectID) } body, err := p.signedGet("https://ecs."+regionName+".myhuaweicloud.com/v2.1/"+projectID+"/servers/detail", "ecs") if err != nil { return nil, err } var result struct { Servers []struct { ID string `json:"id"` Name string `json:"name"` Status string `json:"status"` Flavor struct { ID string `json:"id"` } `json:"flavor"` Created string `json:"created"` Metadata map[string]string `json:"metadata"` Addresses map[string][]struct { Addr string `json:"addr"` Type string `json:"OS-EXT-IPS:type"` } `json:"addresses"` Zone string `json:"OS-EXT-AZ:availability_zone"` } `json:"servers"` } if err := json.Unmarshal(body, &result); err != nil { return nil, fmt.Errorf("解析华为云实例响应失败: %w", err) } instances := []CloudInstance{} for _, server := range result.Servers { privateIP, publicIP := "", "" for _, addresses := range server.Addresses { for _, address := range addresses { if address.Type == "floating" { publicIP = address.Addr } else if privateIP == "" { privateIP = address.Addr } } } osType := server.Metadata["os_type"] if osType == "" { osType = server.Metadata["__os_type"] } instances = append(instances, CloudInstance{InstanceID: server.ID, InstanceName: server.Name, Hostname: server.Name, OSType: osType, Status: huaweiState(server.Status), RegionID: projectID, ZoneID: server.Zone, PrivateIP: privateIP, PublicIP: publicIP, InstanceType: server.Flavor.ID, CreationTime: server.Created}) } return instances, nil } // huaweiState 统一华为云实例状态,便于复用导入逻辑。 // Logan.Li func huaweiState(state string) string { if strings.EqualFold(state, "ACTIVE") { return "Running" } return state }