Skip to content

Commit de75da2

Browse files
committed
add get executions and list executions
1 parent e92134d commit de75da2

File tree

6 files changed

+294
-33
lines changed

6 files changed

+294
-33
lines changed

client/client.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"encoding/json"
77
"fmt"
88
"io"
9+
"log/slog"
910
"net/http"
1011
"net/url"
1112
"strings"
@@ -20,6 +21,7 @@ var (
2021
defaultBaseURL = "https://app.harness.io/"
2122

2223
defaultPageSize = 5
24+
maxPageSize = 20
2325

2426
apiKeyHeader = "x-api-key"
2527
)
@@ -248,6 +250,7 @@ func (c *Client) PostRaw(
248250

249251
// Do is a wrapper of http.Client.Do that injects the auth header in the request.
250252
func (c *Client) Do(r *http.Request) (*http.Response, error) {
253+
slog.Debug("Request", "method", r.Method, "url", r.URL.String())
251254
r.Header.Add(apiKeyHeader, c.APIKey)
252255

253256
return c.client.Do(r)
@@ -334,6 +337,13 @@ func addScope(scope dto.Scope, params map[string]string) {
334337

335338
func setDefaultPagination(opts *dto.PaginationOptions) {
336339
if opts != nil {
337-
opts.Size = defaultPageSize
340+
if opts.Size == 0 {
341+
opts.Size = defaultPageSize
342+
}
343+
// too many entries will lower the quality of responses
344+
// can be tweaked later based on feedback
345+
if opts.Size > maxPageSize {
346+
opts.Size = maxPageSize
347+
}
338348
}
339349
}

client/dto/pipeline.go

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -110,41 +110,53 @@ type PageableInfo struct {
110110
PageNumber int `json:"pageNumber,omitempty"`
111111
}
112112

113+
// PipelineTag represents a key-value tag for filtering pipelines
114+
type PipelineTag struct {
115+
Key string `json:"key,omitempty"`
116+
Value string `json:"value,omitempty"`
117+
}
118+
113119
// PipelineExecutionOptions represents the options for listing pipeline executions
114120
type PipelineExecutionOptions struct {
115121
PaginationOptions
116-
Status string `json:"status,omitempty"`
117-
MyDeployments bool `json:"myDeployments,omitempty"`
118-
Branch string `json:"branch,omitempty"`
119-
SearchTerm string `json:"searchTerm,omitempty"`
120-
PipelineIdentifier string `json:"pipelineIdentifier,omitempty"`
122+
Status string `json:"status,omitempty"`
123+
MyDeployments bool `json:"myDeployments,omitempty"`
124+
Branch string `json:"branch,omitempty"`
125+
SearchTerm string `json:"searchTerm,omitempty"`
126+
PipelineIdentifier string `json:"pipelineIdentifier,omitempty"`
127+
PipelineTags []PipelineTag `json:"pipelineTags,omitempty"`
128+
}
129+
130+
// PipelineExecutionResponse represents the full response structure for pipeline execution details
131+
type PipelineExecutionResponse struct {
132+
PipelineExecutionSummary PipelineExecution `json:"pipelineExecutionSummary,omitempty"`
121133
}
122134

123135
// PipelineExecution represents a pipeline execution
124136
type PipelineExecution struct {
125-
PipelineIdentifier string `json:"pipelineIdentifier,omitempty"`
126-
ProjectIdentifier string `json:"projectIdentifier,omitempty"`
127-
OrgIdentifier string `json:"orgIdentifier,omitempty"`
128-
PlanExecutionId string `json:"planExecutionId,omitempty"`
129-
Name string `json:"name,omitempty"`
130-
Status string `json:"status,omitempty"`
131-
FailureInfo ExecutionFailureInfo `json:"failureInfo,omitempty"`
132-
StartTs int64 `json:"startTs,omitempty"`
133-
EndTs int64 `json:"endTs,omitempty"`
134-
CreatedAt int64 `json:"createdAt,omitempty"`
135-
ConnectorRef string `json:"connectorRef,omitempty"`
136-
SuccessfulStagesCount int `json:"successfulStagesCount,omitempty"`
137-
FailedStagesCount int `json:"failedStagesCount,omitempty"`
138-
RunningStagesCount int `json:"runningStagesCount,omitempty"`
139-
TotalStagesRunningCount int `json:"totalStagesRunningCount,omitempty"`
140-
StagesExecuted []string `json:"stagesExecuted,omitempty"`
141-
AbortedBy User `json:"abortedBy,omitempty"`
142-
QueuedType string `json:"queuedType,omitempty"`
143-
RunSequence int32 `json:"runSequence,omitempty"`
137+
PipelineIdentifier string `json:"pipelineIdentifier,omitempty"`
138+
ProjectIdentifier string `json:"projectIdentifier,omitempty"`
139+
OrgIdentifier string `json:"orgIdentifier,omitempty"`
140+
PlanExecutionId string `json:"planExecutionId,omitempty"`
141+
Name string `json:"name,omitempty"`
142+
Status string `json:"status,omitempty"`
143+
FailureInfo ExecutionFailureInfo `json:"failureInfo,omitempty"`
144+
StartTs int64 `json:"startTs,omitempty"`
145+
EndTs int64 `json:"endTs,omitempty"`
146+
CreatedAt int64 `json:"createdAt,omitempty"`
147+
ConnectorRef string `json:"connectorRef,omitempty"`
148+
SuccessfulStagesCount int `json:"successfulStagesCount,omitempty"`
149+
FailedStagesCount int `json:"failedStagesCount,omitempty"`
150+
RunningStagesCount int `json:"runningStagesCount,omitempty"`
151+
TotalStagesRunningCount int `json:"totalStagesRunningCount,omitempty"`
152+
StagesExecuted []string `json:"stagesExecuted,omitempty"`
153+
AbortedBy User `json:"abortedBy,omitempty"`
154+
QueuedType string `json:"queuedType,omitempty"`
155+
RunSequence int32 `json:"runSequence,omitempty"`
156+
ShouldUseSimplifiedBaseKey bool `json:"shouldUseSimplifiedBaseKey,omitempty"`
144157
}
145158

146159
// ExecutionFailureInfo represents the failure information of a pipeline execution
147-
148160
type ExecutionFailureInfo struct {
149161
FailureTypeList []string `json:"failureTypeList,omitempty"`
150162
ResponseMessages []ExecutionResponseMessage `json:"responseMessages,omitempty"`

client/pipelines.go

Lines changed: 76 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ import (
88
)
99

1010
const (
11-
pipelinePath = "pipeline/api/pipelines/%s"
12-
pipelineListPath = "pipeline/api/pipelines/list"
13-
pipelineExecutionPath = "pipeline/api/pipelines/execution/url"
11+
pipelinePath = "pipeline/api/pipelines/%s"
12+
pipelineListPath = "pipeline/api/pipelines/list"
13+
pipelineExecutionPath = "pipeline/api/pipelines/execution/url"
14+
pipelineExecutionGetPath = "pipeline/api/pipelines/execution/v2/%s"
15+
pipelineExecutionSummaryPath = "pipeline/api/pipelines/execution/summary"
1416
)
1517

1618
type PipelineService struct {
@@ -71,8 +73,76 @@ func (p *PipelineService) List(ctx context.Context, scope dto.Scope, opts *dto.P
7173
}
7274

7375
func (p *PipelineService) ListExecutions(ctx context.Context, scope dto.Scope, opts *dto.PipelineExecutionOptions) (*dto.ListOutput[dto.PipelineExecution], error) {
76+
// Set default pagination
7477
setDefaultPagination(&opts.PaginationOptions)
75-
return nil, nil
78+
79+
// Prepare query parameters
80+
params := make(map[string]string)
81+
addScope(scope, params)
82+
83+
// Add pagination parameters
84+
params["page"] = fmt.Sprintf("%d", opts.Page)
85+
params["size"] = fmt.Sprintf("%d", opts.Size)
86+
87+
// Add optional parameters if provided
88+
if opts.SearchTerm != "" {
89+
params["searchTerm"] = opts.SearchTerm
90+
}
91+
if opts.PipelineIdentifier != "" {
92+
params["pipelineIdentifier"] = opts.PipelineIdentifier
93+
}
94+
if opts.Status != "" {
95+
params["status"] = opts.Status
96+
}
97+
if opts.Branch != "" {
98+
params["branch"] = opts.Branch
99+
}
100+
if opts.MyDeployments {
101+
params["myDeployments"] = "true"
102+
} else {
103+
params["showAllExecutions"] = "false"
104+
}
105+
106+
// Create request body with filter type and tags if needed
107+
requestBody := map[string]interface{}{
108+
"filterType": "PipelineExecution",
109+
}
110+
// Initialize the response object
111+
response := &dto.ListOutput[dto.PipelineExecution]{}
112+
113+
// Make the POST request
114+
err := p.client.Post(ctx, pipelineExecutionSummaryPath, params, requestBody, response)
115+
if err != nil {
116+
return nil, fmt.Errorf("failed to list pipeline executions: %w", err)
117+
}
118+
119+
return response, nil
120+
}
121+
122+
// GetExecution retrieves details of a specific pipeline execution
123+
func (p *PipelineService) GetExecution(ctx context.Context, scope dto.Scope, planExecutionID string) (*dto.Entity[dto.PipelineExecution], error) {
124+
path := fmt.Sprintf(pipelineExecutionGetPath, planExecutionID)
125+
126+
// Prepare query parameters
127+
params := make(map[string]string)
128+
addScope(scope, params)
129+
130+
// Initialize the response object with the new structure that matches the API response
131+
response := &dto.Entity[dto.PipelineExecutionResponse]{}
132+
133+
// Make the GET request
134+
err := p.client.Get(ctx, path, params, map[string]string{}, response)
135+
if err != nil {
136+
return nil, fmt.Errorf("failed to get execution details: %w", err)
137+
}
138+
139+
// Extract the execution details from the nested structure
140+
result := &dto.Entity[dto.PipelineExecution]{
141+
Status: response.Status,
142+
Data: response.Data.PipelineExecutionSummary,
143+
}
144+
145+
return result, nil
76146
}
77147

78148
func (p *PipelineService) FetchExecutionURL(ctx context.Context, scope dto.Scope, pipelineID, planExecutionID string) (string, error) {
@@ -87,8 +157,8 @@ func (p *PipelineService) FetchExecutionURL(ctx context.Context, scope dto.Scope
87157
// Initialize the response object
88158
urlResponse := &dto.Entity[string]{}
89159

90-
// Make the GET request
91-
err := p.client.Get(ctx, path, params, nil, urlResponse)
160+
// Make the POST request
161+
err := p.client.Post(ctx, path, params, nil, urlResponse)
92162
if err != nil {
93163
return "", fmt.Errorf("failed to fetch execution URL: %w", err)
94164
}

pkg/harness/pagination.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ func WithPagination() mcp.ToolOption {
1616

1717
// fetchPagination fetches pagination parameters from the MCP request.
1818
func fetchPagination(request mcp.CallToolRequest) (page, size int, err error) {
19-
pageVal, err := OptionalIntParamWithDefault(request, "page", 1)
19+
pageVal, err := OptionalIntParamWithDefault(request, "page", 0)
2020
if err != nil {
2121
return 0, 0, err
2222
}

0 commit comments

Comments
 (0)