Skip to content

Commit 06b64a2

Browse files
javrudskyHarness
authored andcommitted
feat: [fme-7168]: Added Fetch CCM Metadata Tool (FetchCcmMetadata graphQL Operation) (#44)
* [fme-7168] - Fixing tool name. fetch_ccm_metadata to get_ccm_metadata * [fme-7168] - Added Fetch CCM Metadata Tool (FetchCcmMetadata graphQL Operation) * [fme-7154] Adding master changes * [fme-7154] - Added GraphQL perspective Budget tool * [fme-7133] - Using MM/DD/YYYY format in LastPeriodCostCcmPerspective tool. Removing unused imports * [fme-7133] - Merge with master * [fme-7133] Added Get Perspective Grid with budget graphql query * [fme-5745] - Added perspective time series graphql API * [fme-4352] - Fixing group by AWS fields constants * [fme-4352] Fix for key values filter parameter config * [fme-4352] Improving description for MCP tool for ccm_perspective_grid * [fme-4352] Fixing filter field description * [fme-4352] - Adding filtering and grouping by key/value fields (Label, LabelV2 and Cost Categories) * [FME-4352] - Added Field ID filters * [fme-4352] - Using default Account ID in perspectives * [fme-4352] Added initial setup integration for Fetchperspecti
1 parent d1cd23f commit 06b64a2

File tree

6 files changed

+227
-1
lines changed

6 files changed

+227
-1
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ Toolset Name: `cloudcostmanagement`
9797
- `ccm_perspective_grid`: Query detailed cost perspective data in Harness Cloud Cost Management.
9898
- `ccm_perspective_time_series`: Query detailed cost perspective data, grouped by time in Harness Cloud Cost Management.
9999
- `ccm_perspective_summary_with_budget`: Query a summary of cost perspectives with budget information in Harness Cloud Cost Management, including detailed cost and budget data grouped by time.
100+
- `ccm_perspective_budget`: Query the budget information for a perspective in Harness Cloud Cost Management.
101+
- `get_ccm_metadata`: Retrieves metadata about available cloud connectors, cost data sources, default perspectives, and currency preferences in Harness Cloud Cost Management.
100102
- `get_ccm_commitment_coverage`: Get commitment coverage information for an account in Harness Cloud Cost Management
101103

102104
#### Chaos Engineering Toolset

client/ccmcommons/ccmgraphqlqueries.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,3 +145,50 @@ const CCMPerspectiveSummaryWithBudgetQuery = `
145145
}
146146
}
147147
`
148+
const CCMPerspectiveBudgetQuery = `
149+
query FetchPerspectiveBudget($perspectiveId: String) {
150+
budgetSummaryList(perspectiveId: $perspectiveId) {
151+
id
152+
name
153+
budgetAmount
154+
actualCost
155+
timeLeft
156+
timeUnit
157+
timeScope
158+
period
159+
folderId
160+
__typename
161+
}
162+
}
163+
`
164+
165+
const CCMMetadataQuery = `
166+
query FetchCcmMetaData {
167+
ccmMetaData {
168+
k8sClusterConnectorPresent
169+
cloudDataPresent
170+
awsConnectorsPresent
171+
gcpConnectorsPresent
172+
azureConnectorsPresent
173+
applicationDataPresent
174+
inventoryDataPresent
175+
clusterDataPresent
176+
externalDataPresent
177+
isSampleClusterPresent
178+
defaultAzurePerspectiveId
179+
defaultAwsPerspectiveId
180+
defaultGcpPerspectiveId
181+
defaultClusterPerspectiveId
182+
defaultExternalDataPerspectiveId
183+
showCostOverview
184+
currencyPreference {
185+
destinationCurrency
186+
symbol
187+
locale
188+
setupTime
189+
__typename
190+
}
191+
__typename
192+
}
193+
}
194+
`

client/ccmgraphqlperspectives.go

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,51 @@ func (r *CloudCostManagementService) PerspectiveSummaryWithBudget(ctx context.Co
115115
return result, nil
116116
}
117117

118-
//func buildFilters(options *dto.CCMPerspectiveGridOptions) ([]map[string]any) {
118+
func (r *CloudCostManagementService) PerspectiveBudget(ctx context.Context, scope dto.Scope, options *dto.CCMPerspectiveBudgetOptions) (*dto.CCMPerspectiveBudgetResponse, error) {
119+
path := fmt.Sprintf(ccmPerspectiveGraphQLPath, options.AccountId, options.AccountId)
120+
121+
gqlQuery := ccmcommons.CCMPerspectiveBudgetQuery
122+
variables := map[string]any{
123+
"perspectiveId": options.PerspectiveId,
124+
}
125+
126+
payload := map[string]any{
127+
"query": gqlQuery,
128+
"operationName": "FetchPerspectiveBudget",
129+
"variables": variables,
130+
}
131+
132+
slog.Debug("PerspectiveBudget", "Payload", payload)
133+
result := new(dto.CCMPerspectiveBudgetResponse)
134+
err := r.Client.Post(ctx, path, nil, payload, &result)
135+
if err != nil {
136+
return nil, fmt.Errorf("failed to get perspective budget: %w", err)
137+
}
138+
return result, nil
139+
}
140+
141+
func (r *CloudCostManagementService) GetCcmMetadata(ctx context.Context, scope dto.Scope, accountId string) (*dto.CCMMetadataResponse, error) {
142+
path := fmt.Sprintf(ccmPerspectiveGraphQLPath, accountId, accountId)
143+
144+
gqlQuery := ccmcommons.CCMMetadataQuery
145+
variables := map[string]any{
146+
}
147+
148+
payload := map[string]any{
149+
"query": gqlQuery,
150+
"operationName": "FetchCcmMetaData",
151+
"variables": variables,
152+
}
153+
154+
slog.Debug("FetchCcmMetadata", "Payload", payload)
155+
result := new(dto.CCMMetadataResponse)
156+
err := r.Client.Post(ctx, path, nil, payload, &result)
157+
if err != nil {
158+
return nil, fmt.Errorf("failed to get perspective budget: %w", err)
159+
}
160+
return result, nil
161+
}
162+
119163
func buildFilters(timeFilters string, idFilters dto.CCMGraphQLFilters, keyValueFilters dto.CCMGraphQLKeyValueFilters) ([]map[string]any) {
120164
filters := []map[string]any{}
121165
viewFilter := []map[string]any{

client/dto/ccmgraphqlperspectives.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,3 +152,62 @@ type CCMPerspectiveSummaryWithBudgetResponse struct {
152152
PerspectiveForecastCost CCMPerspectiveForecastCost `json:"perspectiveForecastCost"`
153153
} `json:"data"`
154154
}
155+
156+
type CCMPerspectiveBudget struct {
157+
ID string `json:"id"`
158+
Name string `json:"name"`
159+
BudgetAmount float64 `json:"budgetAmount"`
160+
ActualCost float64 `json:"actualCost"`
161+
TimeLeft int `json:"timeLeft"`
162+
TimeUnit string `json:"timeUnit"`
163+
TimeScope string `json:"timeScope"`
164+
Period string `json:"period"`
165+
FolderID string `json:"folderId"`
166+
Typename string `json:"__typename"`
167+
}
168+
169+
type CCMPerspectiveBudgetResponse struct {
170+
Data struct {
171+
BudgetSummaryList []CCMPerspectiveBudget `json:"budgetSummaryList"`
172+
} `json:"data"`
173+
}
174+
175+
type CCMPerspectiveBudgetOptions struct {
176+
AccountId string `json:"account_id"`
177+
PerspectiveId string `json:"perspective_id"`
178+
}
179+
180+
type CCMCurrencyPreference struct {
181+
DestinationCurrency string `json:"destinationCurrency"`
182+
Symbol string `json:"symbol"`
183+
Locale string `json:"locale"`
184+
SetupTime int64 `json:"setupTime"`
185+
Typename string `json:"__typename"`
186+
}
187+
188+
type CCMMetadata struct {
189+
K8sClusterConnectorPresent bool `json:"k8sClusterConnectorPresent"`
190+
CloudDataPresent bool `json:"cloudDataPresent"`
191+
AwsConnectorsPresent bool `json:"awsConnectorsPresent"`
192+
GcpConnectorsPresent bool `json:"gcpConnectorsPresent"`
193+
AzureConnectorsPresent bool `json:"azureConnectorsPresent"`
194+
ApplicationDataPresent bool `json:"applicationDataPresent"`
195+
InventoryDataPresent bool `json:"inventoryDataPresent"`
196+
ClusterDataPresent bool `json:"clusterDataPresent"`
197+
ExternalDataPresent bool `json:"externalDataPresent"`
198+
IsSampleClusterPresent bool `json:"isSampleClusterPresent"`
199+
DefaultAzurePerspectiveId string `json:"defaultAzurePerspectiveId"`
200+
DefaultAwsPerspectiveId string `json:"defaultAwsPerspectiveId"`
201+
DefaultGcpPerspectiveId string `json:"defaultGcpPerspectiveId"`
202+
DefaultClusterPerspectiveId string `json:"defaultClusterPerspectiveId"`
203+
DefaultExternalDataPerspectiveId string `json:"defaultExternalDataPerspectiveId"`
204+
ShowCostOverview bool `json:"showCostOverview"`
205+
CurrencyPreference CCMCurrencyPreference `json:"currencyPreference"`
206+
Typename string `json:"__typename"`
207+
}
208+
209+
type CCMMetadataResponse struct {
210+
Data struct {
211+
CCMMetadata CCMMetadata `json:"ccmMetaData"`
212+
} `json:"data"`
213+
}

pkg/harness/ccmgraphqlperspectives.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,78 @@ func CcmPerspectiveSummaryWithBudgetTool(config *config.Config, client *client.C
225225
}
226226
}
227227

228+
func CcmPerspectiveBudgetTool(config *config.Config, client *client.CloudCostManagementService) (tool mcp.Tool, handler server.ToolHandlerFunc) {
229+
return mcp.NewTool("ccm_perspective_budget",
230+
mcp.WithDescription("Get the budget information for a perspective in Harness Cloud Cost Management"),
231+
mcp.WithString("perspective_id",
232+
mcp.Description("Required perspective identifier."),
233+
),
234+
WithScope(config, false),
235+
),
236+
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
237+
accountId, err := getAccountID(config, request)
238+
if err != nil {
239+
return mcp.NewToolResultError(err.Error()), nil
240+
}
241+
242+
perspectiveId, err := OptionalParam[string](request, "perspective_id")
243+
if err != nil {
244+
return mcp.NewToolResultError(err.Error()), nil
245+
}
246+
247+
params := &dto.CCMPerspectiveBudgetOptions{}
248+
params.AccountId = accountId
249+
params.PerspectiveId = perspectiveId
250+
251+
scope, err := fetchScope(config, request, false)
252+
if err != nil {
253+
return mcp.NewToolResultError(err.Error()), nil
254+
}
255+
256+
data, err := client.PerspectiveBudget(ctx, scope, params)
257+
if err != nil {
258+
return nil, fmt.Errorf("failed to get CCM Perspective Budget: %w", err)
259+
}
260+
261+
r, err := json.Marshal(data)
262+
if err != nil {
263+
return nil, fmt.Errorf("failed to marshal CCM Perspective Budget: %w", err)
264+
}
265+
266+
return mcp.NewToolResultText(string(r)), nil
267+
}
268+
}
269+
270+
func CcmMetadataTool(config *config.Config, client *client.CloudCostManagementService) (tool mcp.Tool, handler server.ToolHandlerFunc) {
271+
return mcp.NewTool("get_ccm_metadata",
272+
mcp.WithDescription("Get metadata about available cloud connectors, cost data sources, default perspectives, and currency preferences in Harness Cloud Cost Management."),
273+
),
274+
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
275+
accountId, err := getAccountID(config, request)
276+
if err != nil {
277+
return mcp.NewToolResultError(err.Error()), nil
278+
}
279+
280+
scope, err := fetchScope(config, request, false)
281+
if err != nil {
282+
return mcp.NewToolResultError(err.Error()), nil
283+
}
284+
285+
data, err := client.GetCcmMetadata(ctx, scope, accountId)
286+
if err != nil {
287+
return nil, fmt.Errorf("failed to get CCM Metadata: %w", err)
288+
}
289+
290+
r, err := json.Marshal(data)
291+
if err != nil {
292+
return nil, fmt.Errorf("failed to marshal CCM Metadata: %w", err)
293+
}
294+
295+
return mcp.NewToolResultText(string(r)), nil
296+
}
297+
}
298+
299+
228300
func buildFilters(filterFields []map[string]string, request mcp.CallToolRequest) (map[string][]string, error) {
229301

230302
filters := make(map[string][]string)

pkg/harness/tools.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,8 @@ func registerCloudCostManagement(config *config.Config, tsg *toolsets.ToolsetGro
580580
toolsets.NewServerTool(CcmPerspectiveGridTool(config, ccmClient)),
581581
toolsets.NewServerTool(CcmPerspectiveTimeSeriesTool(config, ccmClient)),
582582
toolsets.NewServerTool(CcmPerspectiveSummaryWithBudgetTool(config, ccmClient)),
583+
toolsets.NewServerTool(CcmPerspectiveBudgetTool(config, ccmClient)),
584+
toolsets.NewServerTool(CcmMetadataTool(config, ccmClient)),
583585
toolsets.NewServerTool(FetchCommitmentCoverageTool(config, ccmClient)),
584586
)
585587

0 commit comments

Comments
 (0)