Skip to content

Commit 806b96c

Browse files
sandyydkHarness
authored andcommitted
feat: [CCM-24165]: Support for Commitment Utilization (#94)
* Fix typos * Apply suggestion from code review * Update error handling to return tool wrapped error * Update README * Add support to include Commitment Utilisation Tool call
1 parent d9afefb commit 806b96c

File tree

4 files changed

+149
-15
lines changed

4 files changed

+149
-15
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ Toolset Name: `ccm`
122122
- `get_ccm_recommendations_stats`: Returns overall statistics for cloud cost optimization recommendations within a given account in Harness Cloud Cost Management.
123123
- `get_ccm_commitment_coverage`: Get commitment coverage information for an account in Harness Cloud Cost Management
124124
- `get_ccm_commitment_savings`: Get commitment savings information for an account in Harness Cloud Cost Management
125+
- `get_ccm_commitment_utilisation`: Get commitment utilisation information for an account in Harness Cloud Cost Management broken down by Reserved Instances and Savings Plans in day wise granularity.
125126

126127
#### Database Operations Toolset
127128

client/ccmcosts.go

Lines changed: 57 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,16 @@ import (
1111
)
1212

1313
const (
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-
ccmCommitmentSavingsDetailsPath = ccmCommitmentBasePath + "/accounts/%s/v1/detail/savings?accountIdentifier=%s"
22-
23-
ccmCommitmentComputeService string = "Amazon Elastic Compute Cloud - Compute"
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+
ccmCommitmentSavingsDetailsPath = ccmCommitmentBasePath + "/accounts/%s/v1/detail/savings?accountIdentifier=%s"
22+
ccmCommitmentUtilisationDetailsPath = ccmCommitmentBasePath + "/accounts/%s/v1/detail/commitment_utilisation?accountIdentifier=%s"
23+
ccmCommitmentComputeService string = "Amazon Elastic Compute Cloud - Compute"
2424
)
2525

2626
type CloudCostManagementService struct {
@@ -237,3 +237,50 @@ func (r *CloudCostManagementService) GetCommitmentSavings(ctx context.Context, s
237237

238238
return savingsResponse, nil
239239
}
240+
241+
func (r *CloudCostManagementService) GetCommitmentUtilisation(ctx context.Context, scope dto.Scope, opts *dto.CCMCommitmentOptions) (*dto.CCMCommitmentBaseResponse, error) {
242+
path := fmt.Sprintf(ccmCommitmentUtilisationDetailsPath, scope.AccountID, scope.AccountID)
243+
params := make(map[string]string)
244+
addScope(scope, params)
245+
246+
// Handle nil options by creating default options
247+
if opts == nil {
248+
opts = &dto.CCMCommitmentOptions{}
249+
}
250+
251+
if opts.StartDate != nil && *opts.StartDate != "" {
252+
params["start_date"] = *opts.StartDate
253+
} else {
254+
// Default to last 30 days
255+
params["start_date"] = utils.FormatUnixToMMDDYYYY(time.Now().AddDate(0, 0, -30).Unix())
256+
}
257+
if opts.EndDate != nil && *opts.EndDate != "" {
258+
params["end_date"] = *opts.EndDate
259+
} else {
260+
// Default to last 30 days
261+
params["end_date"] = utils.CurrentMMDDYYYY()
262+
}
263+
264+
var requestPayload = dto.CCMCommitmentAPIFilter{
265+
Service: ccmCommitmentComputeService, // Default value
266+
267+
}
268+
269+
if opts.Service != nil && *opts.Service != "" {
270+
requestPayload.Service = *opts.Service
271+
}
272+
273+
if len(opts.CloudAccountIDs) > 0 {
274+
requestPayload.CloudAccounts = opts.CloudAccountIDs
275+
}
276+
277+
// Temporary slice to hold the strings
278+
utilisationResponse := new(dto.CCMCommitmentBaseResponse)
279+
280+
err := r.Client.Post(ctx, path, params, requestPayload, utilisationResponse)
281+
if err != nil {
282+
return nil, fmt.Errorf("failed to get cloud cost managment compute utilisation with path %s: %w", path, err)
283+
}
284+
285+
return utilisationResponse, nil
286+
}

pkg/harness/tools/ccmcosts.go

Lines changed: 90 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7+
"time"
8+
79
"github.com/harness/harness-mcp/client"
810
"github.com/harness/harness-mcp/client/dto"
911
"github.com/harness/harness-mcp/cmd/harness-mcp-server/config"
1012
"github.com/harness/harness-mcp/pkg/utils"
1113
"github.com/mark3labs/mcp-go/mcp"
1214
"github.com/mark3labs/mcp-go/server"
13-
"time"
1415
)
1516

1617
// GetCcmOverview creates a tool for getting a ccm overview from an account
@@ -357,12 +358,12 @@ func FetchCommitmentCoverageTool(config *config.Config, client *client.CloudCost
357358

358359
data, err := client.GetComputeCoverage(ctx, scope, params)
359360
if err != nil {
360-
return nil, fmt.Errorf("failed to get commitment coverage: %w", err)
361+
return mcp.NewToolResultError(fmt.Sprintf("failed to get commitment coverage: %s", err)), nil
361362
}
362363

363364
r, err := json.Marshal(data)
364365
if err != nil {
365-
return nil, fmt.Errorf("failed to marshal commitment coverage: %w", err)
366+
return mcp.NewToolResultError(fmt.Sprintf("failed to marshal commitment coverage: %s", err)), nil
366367
}
367368

368369
return mcp.NewToolResultText(string(r)), nil
@@ -454,12 +455,96 @@ func FetchCommitmentSavingsTool(config *config.Config, client *client.CloudCostM
454455

455456
data, err := client.GetCommitmentSavings(ctx, scope, params)
456457
if err != nil {
457-
return nil, fmt.Errorf("failed to get commitment savings: %w", err)
458+
return mcp.NewToolResultError(fmt.Sprintf("failed to get commitment savings: %s", err)), nil
459+
}
460+
461+
r, err := json.Marshal(data)
462+
if err != nil {
463+
return mcp.NewToolResultError(fmt.Sprintf("failed to marshal commitment savings: %s", err)), nil
464+
}
465+
466+
return mcp.NewToolResultText(string(r)), nil
467+
}
468+
}
469+
470+
func FetchCommitmentUtilisationTool(config *config.Config, client *client.CloudCostManagementService) (tool mcp.Tool, handler server.ToolHandlerFunc) {
471+
return mcp.NewTool("get_ccm_commitment_utilisation",
472+
mcp.WithDescription("Get commitment utilisation information for an account in Harness Cloud Cost Management broken down by Reserved Instances and Savings Plans in day wise granularity"),
473+
mcp.WithString("start_date",
474+
mcp.Description("Start date to filter commitment utilisation (optional, defaults to 30 days ago)"),
475+
),
476+
mcp.WithString("end_date",
477+
mcp.Description("End date to filter commitment utilisation (optional, defaults to current date)"),
478+
),
479+
mcp.WithString("service",
480+
mcp.Description("Optional service to filter commitment utilisation"),
481+
),
482+
mcp.WithArray("cloud_account_ids",
483+
mcp.Description("Optional cloud account IDs to filter commitment utilisation"),
484+
mcp.Items(map[string]any{
485+
"type": "string",
486+
}),
487+
),
488+
WithScope(config, false),
489+
),
490+
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
491+
accountId, err := getAccountID(config, request)
492+
if err != nil {
493+
return mcp.NewToolResultError(err.Error()), nil
494+
}
495+
496+
params := &dto.CCMCommitmentOptions{}
497+
params.AccountIdentifier = &accountId
498+
499+
// Handle service parameter
500+
service, ok, err := OptionalParamOK[string](request, "service")
501+
if err != nil {
502+
return mcp.NewToolResultError(err.Error()), nil
503+
}
504+
if ok && service != "" {
505+
params.Service = &service
506+
}
507+
508+
// Handle cloud account IDs parameter
509+
cloudAccountIDs, ok, err := OptionalParamOK[[]string](request, "cloud_account_ids")
510+
if err != nil {
511+
return mcp.NewToolResultError(err.Error()), nil
512+
}
513+
if ok && len(cloudAccountIDs) > 0 {
514+
params.CloudAccountIDs = cloudAccountIDs
515+
}
516+
517+
// Handle start date parameter
518+
startDate, ok, err := OptionalParamOK[string](request, "start_date")
519+
if err != nil {
520+
return mcp.NewToolResultError(err.Error()), nil
521+
}
522+
if ok && startDate != "" {
523+
params.StartDate = &startDate
524+
}
525+
526+
// Handle end date parameter
527+
endDate, ok, err := OptionalParamOK[string](request, "end_date")
528+
if err != nil {
529+
return mcp.NewToolResultError(err.Error()), nil
530+
}
531+
if ok && endDate != "" {
532+
params.EndDate = &endDate
533+
}
534+
535+
scope, err := FetchScope(config, request, false)
536+
if err != nil {
537+
return mcp.NewToolResultError(err.Error()), nil
538+
}
539+
540+
data, err := client.GetCommitmentUtilisation(ctx, scope, params)
541+
if err != nil {
542+
return mcp.NewToolResultError(fmt.Sprintf("failed to get commitment utilisation: %s", err)), nil
458543
}
459544

460545
r, err := json.Marshal(data)
461546
if err != nil {
462-
return nil, fmt.Errorf("failed to marshal commitment savings: %w", err)
547+
return mcp.NewToolResultError(fmt.Sprintf("failed to marshal commitment utilisation: %s", err)), nil
463548
}
464549

465550
return mcp.NewToolResultText(string(r)), nil

pkg/modules/ccm.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ func RegisterCloudCostManagement(config *config.Config, tsg *toolsets.ToolsetGro
101101
toolsets.NewServerTool(tools.GetCcmRecommendationsStatsTool(config, ccmClient)),
102102
toolsets.NewServerTool(tools.FetchCommitmentCoverageTool(config, ccmClient)),
103103
toolsets.NewServerTool(tools.FetchCommitmentSavingsTool(config, ccmClient)),
104+
toolsets.NewServerTool(tools.FetchCommitmentUtilisationTool(config, ccmClient)),
104105
)
105106

106107
// Add toolset to the group

0 commit comments

Comments
 (0)