Skip to content

Commit 62b911c

Browse files
sandyydkHarness
authored andcommitted
feat: [CCM-24080]: Add support for commitment coverage in CCM (#34)
* Address review comments * Add support for commitment coverage in CCM
1 parent cedfba4 commit 62b911c

File tree

5 files changed

+165
-5
lines changed

5 files changed

+165
-5
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ Toolset Name: `cloudcostmanagement`
6060
- `list_ccm_cost_categories`: List all cost categories names for a specified account.
6161
- `list_ccm_cost_categories_detail`: List all cost categories details for a specified account.
6262
- `get_ccm_cost_category`: Retrieve a cost category detail by Id for a specified account.
63+
- `get_ccm_commitment_coverage`: Get commitment coverage information for an account in Harness Cloud Cost Management
6364

6465
#### Chaos Engineering Toolset
6566

client/cloudcostmanagement.go

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,22 @@ import (
44
"context"
55
"fmt"
66
"log/slog"
7+
"time"
78

89
"github.com/harness/harness-mcp/client/dto"
910
"github.com/harness/harness-mcp/pkg/utils"
1011
)
1112

1213
const (
13-
ccmBasePath = "ccm/api"
14-
ccmGetOverviewPath = ccmBasePath + "/overview?accountIdentifier=%s&startTime=%d&endTime=%d&groupBy=%s"
15-
ccmCostCategoryListPath = ccmBasePath + "/business-mapping/filter-panel?accountIdentifier=%s"
16-
ccmCostCategoryDetailListPath = ccmBasePath + "/business-mapping?accountIdentifier=%s" // This endpoint lists cost categories
17-
ccmGetCostCategoryPath = ccmBasePath + "/business-mapping/%s?accountIdentifier=%s" // This endpoint lists cost categories
14+
ccmBasePath = "ccm/api"
15+
ccmCommitmentBasePath = "/lw/co/api"
16+
ccmGetOverviewPath = ccmBasePath + "/overview?accountIdentifier=%s&startTime=%d&endTime=%d&groupBy=%s"
17+
ccmCostCategoryListPath = ccmBasePath + "/business-mapping/filter-panel?accountIdentifier=%s"
18+
ccmCostCategoryDetailListPath = ccmBasePath + "/business-mapping?accountIdentifier=%s" // This endpoint lists cost categories
19+
ccmGetCostCategoryPath = ccmBasePath + "/business-mapping/%s?accountIdentifier=%s" // This endpoint lists cost categories
20+
ccmCommitmentCoverageDetailsPath = ccmCommitmentBasePath + "/accounts/%s/v1/detail/compute_coverage?accountIdentifier=%s"
21+
22+
ccmCommitmentComputeService string = "Amazon Elastic Compute Cloud - Compute"
1823
)
1924

2025
type CloudCostManagementService struct {
@@ -131,3 +136,50 @@ func setCCMPaginationDefault(opts *dto.CCMPaginationOptions) {
131136
opts.Limit = safeMaxPageSize
132137
}
133138
}
139+
140+
func (r *CloudCostManagementService) GetComputeCoverage(ctx context.Context, scope dto.Scope, opts *dto.CCMCommitmentOptions) (*dto.CCMCommitmentBaseResponse, error) {
141+
path := fmt.Sprintf(ccmCommitmentCoverageDetailsPath, scope.AccountID, scope.AccountID)
142+
params := make(map[string]string)
143+
addScope(scope, params)
144+
145+
// Handle nil options by creating default options
146+
if opts == nil {
147+
opts = &dto.CCMCommitmentOptions{}
148+
}
149+
150+
if opts.StartDate != nil && *opts.StartDate != "" {
151+
params["start_date"] = *opts.StartDate
152+
} else {
153+
// Default to last 30 days
154+
params["start_date"] = utils.FormatUnixToMMDDYYYY(time.Now().AddDate(0, 0, -30).Unix())
155+
}
156+
if opts.EndDate != nil && *opts.EndDate != "" {
157+
params["end_date"] = *opts.EndDate
158+
} else {
159+
// Default to last 30 days
160+
params["end_date"] = utils.CurrentMMDDYYYY()
161+
}
162+
163+
var requestPayload = dto.CCMCommitmentAPIFilter{
164+
Service: ccmCommitmentComputeService, // Default value
165+
166+
}
167+
168+
if opts.Service != nil && *opts.Service != "" {
169+
requestPayload.Service = *opts.Service
170+
}
171+
172+
if len(opts.CloudAccountIDs) > 0 {
173+
requestPayload.CloudAccounts = opts.CloudAccountIDs
174+
}
175+
176+
// Temporary slice to hold the strings
177+
coverageRespone := new(dto.CCMCommitmentBaseResponse)
178+
179+
err := r.Client.Post(ctx, path, params, requestPayload, coverageRespone)
180+
if err != nil {
181+
return nil, fmt.Errorf("failed to get cloud cost managment compute coverage with path %s: %w", path, err)
182+
}
183+
184+
return coverageRespone, nil
185+
}

client/dto/cloudcostmanagement.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,14 @@ type CCMListCostCategoriesOptions struct {
7878
SearchTerm string `json:"search,omitempty"`
7979
}
8080

81+
type CCMCommitmentOptions struct {
82+
AccountIdentifier *string `json:"accountIdentifier,omitempty"`
83+
CloudAccountIDs []string `json:"cloudAccountId,omitempty"`
84+
Service *string `json:"service,omitempty"`
85+
StartDate *string `json:"startDate,omitempty"`
86+
EndDate *string `json:"endDate,omitempty"`
87+
}
88+
8189
// CcmCostCategoryList represents a list of cost categories in CCM
8290
type CCMCostCategoryList struct {
8391
CCMBaseResponse
@@ -206,3 +214,15 @@ type CCMGetCostCategoryOptions struct {
206214
AccountIdentifier string `json:"accountIdentifier,omitempty"`
207215
CostCategoryId string `json:"id,omitempty"`
208216
}
217+
218+
type CCMCommitmentBaseResponse struct {
219+
Ts int64 `json:"ts"`
220+
Success bool `json:"success"`
221+
Errors []string `json:"errors"`
222+
Response any `json:"response"`
223+
}
224+
225+
type CCMCommitmentAPIFilter struct {
226+
CloudAccounts []string `json:"cloud_account_ids,omitempty"`
227+
Service string `json:"service,omitempty"`
228+
}

pkg/harness/cloudcostmanagement.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,3 +284,89 @@ func getAccountID(config *config.Config, request mcp.CallToolRequest) (string, e
284284
}
285285
return "", fmt.Errorf("Account ID is required")
286286
}
287+
288+
func FetchCommitmentCoverageTool(config *config.Config, client *client.CloudCostManagementService) (tool mcp.Tool, handler server.ToolHandlerFunc) {
289+
return mcp.NewTool("get_ccm_commitment_coverage",
290+
mcp.WithDescription("Get commitment coverage information for an account in Harness Cloud Cost Management"),
291+
mcp.WithString("start_date",
292+
mcp.Required(),
293+
mcp.Description("Start date to filter commitment coverage"),
294+
),
295+
mcp.WithString("end_date",
296+
mcp.Required(),
297+
mcp.Description("End date to filter commitment coverage"),
298+
),
299+
mcp.WithString("service",
300+
mcp.Description("Optional service to filter commitment coverage"),
301+
),
302+
mcp.WithArray("cloud_account_ids",
303+
mcp.Description("Optional cloud account IDs to filter commitment coverage"),
304+
mcp.Items(map[string]any{
305+
"type": "string",
306+
}),
307+
),
308+
WithScope(config, false),
309+
),
310+
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
311+
accountId, err := getAccountID(config, request)
312+
if err != nil {
313+
return mcp.NewToolResultError(err.Error()), nil
314+
}
315+
316+
params := &dto.CCMCommitmentOptions{}
317+
params.AccountIdentifier = &accountId
318+
319+
// Handle service parameter
320+
service, ok, err := OptionalParamOK[string](request, "service")
321+
if err != nil {
322+
return mcp.NewToolResultError(err.Error()), nil
323+
}
324+
if ok && service != "" {
325+
params.Service = &service
326+
}
327+
328+
// Handle cloud account IDs parameter
329+
cloudAccountIDs, ok, err := OptionalParamOK[[]string](request, "cloud_account_ids")
330+
if err != nil {
331+
return mcp.NewToolResultError(err.Error()), nil
332+
}
333+
if ok && len(cloudAccountIDs) > 0 {
334+
params.CloudAccountIDs = cloudAccountIDs
335+
}
336+
337+
// Handle start date parameter
338+
startDate, ok, err := OptionalParamOK[string](request, "start_date")
339+
if err != nil {
340+
return mcp.NewToolResultError(err.Error()), nil
341+
}
342+
if ok && startDate != "" {
343+
params.StartDate = &startDate
344+
}
345+
346+
// Handle end date parameter
347+
endDate, ok, err := OptionalParamOK[string](request, "end_date")
348+
if err != nil {
349+
return mcp.NewToolResultError(err.Error()), nil
350+
}
351+
if ok && endDate != "" {
352+
params.EndDate = &endDate
353+
}
354+
355+
scope, err := fetchScope(config, request, false)
356+
if err != nil {
357+
return mcp.NewToolResultError(err.Error()), nil
358+
}
359+
360+
data, err := client.GetComputeCoverage(ctx, scope, params)
361+
if err != nil {
362+
return nil, fmt.Errorf("failed to get commitment coverage: %w", err)
363+
}
364+
365+
r, err := json.Marshal(data)
366+
if err != nil {
367+
return nil, fmt.Errorf("failed to marshal commitment coverage: %w", err)
368+
}
369+
370+
return mcp.NewToolResultText(string(r)), nil
371+
}
372+
}

pkg/harness/tools.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,7 @@ func registerCloudCostManagement(config *config.Config, tsg *toolsets.ToolsetGro
479479
toolsets.NewServerTool(ListCcmCostCategoriesTool(config, ccmClient)),
480480
toolsets.NewServerTool(ListCcmCostCategoriesDetailTool(config, ccmClient)),
481481
toolsets.NewServerTool(GetCcmCostCategoryTool(config, ccmClient)),
482+
toolsets.NewServerTool(FetchCommitmentCoverageTool(config, ccmClient)),
482483
)
483484

484485
// Add toolset to the group

0 commit comments

Comments
 (0)