|
1 | 1 | package client |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "bufio" |
4 | 5 | "bytes" |
5 | 6 | "context" |
6 | 7 | "crypto/tls" |
@@ -1011,6 +1012,170 @@ func (c *Client) GatewayClusters() (*GatewayClustersResponse, error) { |
1011 | 1012 | return &out, nil |
1012 | 1013 | } |
1013 | 1014 |
|
| 1015 | +func (c *Client) MeterListWorkloads(workloadType string) (map[string]any, error) { |
| 1016 | + if c.cfg.Transport != "http" { |
| 1017 | + return nil, fmt.Errorf("meter API is available only with http transport") |
| 1018 | + } |
| 1019 | + path := "/meter/v1/workloads" |
| 1020 | + q := url.Values{} |
| 1021 | + if trimmed := strings.TrimSpace(workloadType); trimmed != "" { |
| 1022 | + q.Set("workload_type", trimmed) |
| 1023 | + } |
| 1024 | + if encoded := q.Encode(); encoded != "" { |
| 1025 | + path += "?" + encoded |
| 1026 | + } |
| 1027 | + var out map[string]any |
| 1028 | + if err := c.httpJSONRequest("GET", path, nil, &out); err != nil { |
| 1029 | + return nil, err |
| 1030 | + } |
| 1031 | + return out, nil |
| 1032 | +} |
| 1033 | + |
| 1034 | +func (c *Client) MeterGetWorkload(workloadID string) (map[string]any, error) { |
| 1035 | + if c.cfg.Transport != "http" { |
| 1036 | + return nil, fmt.Errorf("meter API is available only with http transport") |
| 1037 | + } |
| 1038 | + path := "/meter/v1/workloads/" + url.PathEscape(strings.TrimSpace(workloadID)) |
| 1039 | + var out map[string]any |
| 1040 | + if err := c.httpJSONRequest("GET", path, nil, &out); err != nil { |
| 1041 | + return nil, err |
| 1042 | + } |
| 1043 | + return out, nil |
| 1044 | +} |
| 1045 | + |
| 1046 | +func (c *Client) MeterHistory(workloadID string, from, to time.Time, limit int) (map[string]any, error) { |
| 1047 | + if c.cfg.Transport != "http" { |
| 1048 | + return nil, fmt.Errorf("meter API is available only with http transport") |
| 1049 | + } |
| 1050 | + path := "/meter/v1/workloads/" + url.PathEscape(strings.TrimSpace(workloadID)) + "/history" |
| 1051 | + q := url.Values{} |
| 1052 | + if !from.IsZero() { |
| 1053 | + q.Set("from", from.UTC().Format(time.RFC3339)) |
| 1054 | + } |
| 1055 | + if !to.IsZero() { |
| 1056 | + q.Set("to", to.UTC().Format(time.RFC3339)) |
| 1057 | + } |
| 1058 | + if limit > 0 { |
| 1059 | + q.Set("limit", strconv.Itoa(limit)) |
| 1060 | + } |
| 1061 | + if encoded := q.Encode(); encoded != "" { |
| 1062 | + path += "?" + encoded |
| 1063 | + } |
| 1064 | + var out map[string]any |
| 1065 | + if err := c.httpJSONRequest("GET", path, nil, &out); err != nil { |
| 1066 | + return nil, err |
| 1067 | + } |
| 1068 | + return out, nil |
| 1069 | +} |
| 1070 | + |
| 1071 | +func (c *Client) MeterSummary(workloadID string, from, to time.Time) (map[string]any, error) { |
| 1072 | + if c.cfg.Transport != "http" { |
| 1073 | + return nil, fmt.Errorf("meter API is available only with http transport") |
| 1074 | + } |
| 1075 | + path := "/meter/v1/workloads/" + url.PathEscape(strings.TrimSpace(workloadID)) + "/summary" |
| 1076 | + q := url.Values{} |
| 1077 | + if !from.IsZero() { |
| 1078 | + q.Set("from", from.UTC().Format(time.RFC3339)) |
| 1079 | + } |
| 1080 | + if !to.IsZero() { |
| 1081 | + q.Set("to", to.UTC().Format(time.RFC3339)) |
| 1082 | + } |
| 1083 | + if encoded := q.Encode(); encoded != "" { |
| 1084 | + path += "?" + encoded |
| 1085 | + } |
| 1086 | + var out map[string]any |
| 1087 | + if err := c.httpJSONRequest("GET", path, nil, &out); err != nil { |
| 1088 | + return nil, err |
| 1089 | + } |
| 1090 | + return out, nil |
| 1091 | +} |
| 1092 | + |
| 1093 | +func (c *Client) WatchGatewayEvents(eventType, workloadID, nodeID string, limit int) ([]map[string]any, error) { |
| 1094 | + if c.cfg.Transport != "http" { |
| 1095 | + return nil, fmt.Errorf("gateway event stream is available only with http transport") |
| 1096 | + } |
| 1097 | + if limit <= 0 { |
| 1098 | + limit = 20 |
| 1099 | + } |
| 1100 | + |
| 1101 | + path := "/events/watch" |
| 1102 | + q := url.Values{} |
| 1103 | + if trimmed := strings.TrimSpace(eventType); trimmed != "" { |
| 1104 | + q.Set("type", trimmed) |
| 1105 | + } |
| 1106 | + if trimmed := strings.TrimSpace(workloadID); trimmed != "" { |
| 1107 | + q.Set("workload_id", trimmed) |
| 1108 | + } |
| 1109 | + if trimmed := strings.TrimSpace(nodeID); trimmed != "" { |
| 1110 | + q.Set("node_id", trimmed) |
| 1111 | + } |
| 1112 | + if encoded := q.Encode(); encoded != "" { |
| 1113 | + path += "?" + encoded |
| 1114 | + } |
| 1115 | + |
| 1116 | + timeout := time.Duration(c.cfg.RPCTimeoutSeconds) * time.Second |
| 1117 | + if timeout <= 0 { |
| 1118 | + timeout = 15 * time.Second |
| 1119 | + } |
| 1120 | + ctx, cancel := context.WithTimeout(context.Background(), timeout) |
| 1121 | + defer cancel() |
| 1122 | + |
| 1123 | + req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.cfg.APIEndpoint+path, nil) |
| 1124 | + if err != nil { |
| 1125 | + return nil, fmt.Errorf("failed to create request: %w", err) |
| 1126 | + } |
| 1127 | + req.Header.Set("Accept", "text/event-stream") |
| 1128 | + resp, err := c.httpClient.Do(req) |
| 1129 | + if err != nil { |
| 1130 | + if ctx.Err() == context.DeadlineExceeded { |
| 1131 | + return nil, nil |
| 1132 | + } |
| 1133 | + return nil, fmt.Errorf("failed to send request: %w", err) |
| 1134 | + } |
| 1135 | + defer resp.Body.Close() |
| 1136 | + if resp.StatusCode < 200 || resp.StatusCode >= 300 { |
| 1137 | + body, _ := io.ReadAll(resp.Body) |
| 1138 | + return nil, fmt.Errorf("API returned status %d: %s", resp.StatusCode, string(body)) |
| 1139 | + } |
| 1140 | + |
| 1141 | + scanner := bufio.NewScanner(resp.Body) |
| 1142 | + scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024) |
| 1143 | + var ( |
| 1144 | + events []map[string]any |
| 1145 | + dataLine string |
| 1146 | + ) |
| 1147 | + for scanner.Scan() { |
| 1148 | + line := strings.TrimSpace(scanner.Text()) |
| 1149 | + if strings.HasPrefix(line, "event:") { |
| 1150 | + continue |
| 1151 | + } |
| 1152 | + if strings.HasPrefix(line, "data:") { |
| 1153 | + dataLine = strings.TrimSpace(strings.TrimPrefix(line, "data:")) |
| 1154 | + continue |
| 1155 | + } |
| 1156 | + if line == "" && dataLine != "" { |
| 1157 | + var event map[string]any |
| 1158 | + if err := json.Unmarshal([]byte(dataLine), &event); err == nil { |
| 1159 | + events = append(events, event) |
| 1160 | + if len(events) >= limit { |
| 1161 | + return events, nil |
| 1162 | + } |
| 1163 | + } |
| 1164 | + dataLine = "" |
| 1165 | + } |
| 1166 | + if ctx.Err() != nil { |
| 1167 | + return events, nil |
| 1168 | + } |
| 1169 | + } |
| 1170 | + if err := scanner.Err(); err != nil { |
| 1171 | + if ctx.Err() == context.DeadlineExceeded { |
| 1172 | + return events, nil |
| 1173 | + } |
| 1174 | + return nil, fmt.Errorf("failed to read event stream: %w", err) |
| 1175 | + } |
| 1176 | + return events, nil |
| 1177 | +} |
| 1178 | + |
1014 | 1179 | func (c *Client) TriggerForgeryBuild(req ForgeryBuildTriggerRequest) (map[string]interface{}, error) { |
1015 | 1180 | if c.cfg.Transport != "http" { |
1016 | 1181 | return nil, fmt.Errorf("forgery trigger-build is available only with http transport") |
|
0 commit comments