Skip to content

Commit f88ce7e

Browse files
yogesh-chauhanHarness
authored andcommitted
feat: [ML-1358]: add list_connectors tool (#142)
* fix test * feat: add enum values for connector filter parameters in API schema * add e2e * feat: add list_connectors tool with human-readable timestamps and filtering options
1 parent 168368b commit f88ce7e

File tree

6 files changed

+865
-2
lines changed

6 files changed

+865
-2
lines changed

client/connectors.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
const (
1212
listConnectorCataloguePath = "/connectors/catalogue"
1313
getConnectorPath = "/connectors/%s"
14+
listConnectorsPath = "/connectors/listV2"
1415
)
1516

1617
type ConnectorService struct {
@@ -90,3 +91,44 @@ func (c *ConnectorService) GetConnector(ctx context.Context, scope dto.Scope, co
9091

9192
return &response.Data, nil
9293
}
94+
95+
// ListConnectors retrieves a list of connectors with filtering options
96+
// https://apidocs.harness.io/tag/Connectors#operation/getConnectorListV2
97+
func (c *ConnectorService) ListConnectors(ctx context.Context, scope dto.Scope, connectorNames, connectorIdentifiers, types, categories, connectivityStatuses, connectorConnectivityModes []string, description string, inheritingCredentialsFromDelegate *bool, tags map[string]string) (*pkgDTO.ConnectorListData, error) {
98+
if scope.AccountID == "" {
99+
return nil, fmt.Errorf("accountIdentifier cannot be null")
100+
}
101+
102+
params := make(map[string]string)
103+
addScope(scope, params)
104+
105+
// Create request body with specified fields
106+
requestBody := pkgDTO.ConnectorListRequestBody{
107+
ConnectorNames: connectorNames,
108+
ConnectorIdentifiers: connectorIdentifiers,
109+
Description: description,
110+
Types: types,
111+
Categories: categories,
112+
ConnectivityStatuses: connectivityStatuses,
113+
InheritingCredentialsFromDelegate: inheritingCredentialsFromDelegate,
114+
ConnectorConnectivityModes: connectorConnectivityModes,
115+
Tags: tags,
116+
FilterType: "Connector", // Fixed value
117+
}
118+
119+
// Define a struct to match the actual API response structure
120+
type listConnectorsResponse struct {
121+
Status string `json:"status"`
122+
Data pkgDTO.ConnectorListData `json:"data"`
123+
MetaData interface{} `json:"metaData"`
124+
CorrelationID string `json:"correlationId"`
125+
}
126+
127+
var response listConnectorsResponse
128+
err := c.Client.Post(ctx, listConnectorsPath, params, requestBody, nil, &response)
129+
if err != nil {
130+
return nil, fmt.Errorf("failed to list connectors: %w", err)
131+
}
132+
133+
return &response.Data, nil
134+
}

client/dto/connectors.go

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
package dto
2+
3+
import (
4+
"time"
5+
)
6+
7+
// ConnectorCatalogueItem represents an item in the connector catalogue.
8+
// Based on https://apidocs.harness.io/tag/Connectors#operation/getConnectorCatalogue
9+
type ConnectorCatalogueItem struct {
10+
Category string `json:"category,omitempty"`
11+
Type string `json:"type,omitempty"`
12+
Name string `json:"name,omitempty"`
13+
Description string `json:"description,omitempty"`
14+
LogoURL string `json:"logoURL,omitempty"`
15+
Tags []string `json:"tags,omitempty"`
16+
HarnessManaged bool `json:"harnessManaged,omitempty"`
17+
Beta bool `json:"beta,omitempty"`
18+
ComingSoon bool `json:"comingSoon,omitempty"`
19+
ComingSoonDate string `json:"comingSoonDate,omitempty"`
20+
ComingSoonDescription string `json:"comingSoonDescription,omitempty"`
21+
IsNew bool `json:"isNew,omitempty"`
22+
NewUntil *time.Time `json:"newUntil,omitempty"`
23+
SupportedDelegateTypes []string `json:"supportedDelegateTypes,omitempty"`
24+
DelegateSelectors []string `json:"delegateSelectors,omitempty"`
25+
DelegateRequiresConnectivityMode bool `json:"delegateRequiresConnectivityMode,omitempty"`
26+
ConnectivityModes []string `json:"connectivityModes,omitempty"`
27+
DocumentationLink string `json:"documentationLink,omitempty"`
28+
IsSSCA bool `json:"isSSCA,omitempty"`
29+
SSCADescription string `json:"sscaDescription,omitempty"`
30+
SSCADocumentationLink string `json:"sscaDocumentationLink,omitempty"`
31+
SSCAType string `json:"sscaType,omitempty"`
32+
SSCASupported bool `json:"sscaSupported,omitempty"`
33+
}
34+
35+
// formatUnixMillisToRFC3339 converts Unix timestamp in milliseconds to RFC3339 format
36+
func formatUnixMillisToRFC3339(ms int64) string {
37+
if ms <= 0 {
38+
return ""
39+
}
40+
sec := ms / 1000
41+
nsec := (ms % 1000) * 1000000
42+
t := time.Unix(sec, nsec)
43+
return t.Format(time.RFC3339)
44+
}
45+
46+
// ConnectorDetail represents the detailed information of a connector.
47+
// Based on https://apidocs.harness.io/tag/Connectors#operation/getConnector
48+
type ConnectorDetail struct {
49+
Connector Connector `json:"connector"`
50+
CreatedAt int64 `json:"createdAt"`
51+
LastModifiedAt int64 `json:"lastModifiedAt"`
52+
Status ConnectorStatus `json:"status"`
53+
ActivityDetails ActivityDetails `json:"activityDetails"`
54+
HarnessManaged bool `json:"harnessManaged"`
55+
GitDetails ConnectorGitDetails `json:"gitDetails"`
56+
EntityValidityDetails ConnectorEntityValidityDetails `json:"entityValidityDetails"`
57+
GovernanceMetadata interface{} `json:"governanceMetadata,omitempty"`
58+
IsFavorite bool `json:"isFavorite"`
59+
}
60+
61+
// ConnectorDetailResponse extends ConnectorDetail with human-readable timestamp fields
62+
type ConnectorDetailResponse struct {
63+
// Core connector fields
64+
Connector Connector `json:"connector"`
65+
// Human-readable timestamps in RFC3339 instead of Unix timestamps
66+
CreatedAtTime string `json:"created_at_time"`
67+
LastModifiedAtTime string `json:"last_modified_at_time"`
68+
// Nested structures with human-readable fields
69+
Status ConnectorStatusResponse `json:"status"`
70+
ActivityDetails ActivityDetailsResponse `json:"activityDetails"`
71+
// Other fields from ConnectorDetail
72+
HarnessManaged bool `json:"harnessManaged"`
73+
GitDetails ConnectorGitDetails `json:"gitDetails"`
74+
EntityValidityDetails ConnectorEntityValidityDetails `json:"entityValidityDetails"`
75+
GovernanceMetadata interface{} `json:"governanceMetadata,omitempty"`
76+
IsFavorite bool `json:"isFavorite"`
77+
}
78+
79+
// Connector represents the core connector information.
80+
type Connector struct {
81+
Name string `json:"name"`
82+
Identifier string `json:"identifier"`
83+
Description string `json:"description"`
84+
AccountIdentifier string `json:"accountIdentifier"`
85+
OrgIdentifier string `json:"orgIdentifier"`
86+
ProjectIdentifier string `json:"projectIdentifier"`
87+
Tags map[string]string `json:"tags"`
88+
Type string `json:"type"`
89+
Spec map[string]interface{} `json:"spec"`
90+
}
91+
92+
// ConnectorEntityValidityDetails represents the validity information of a connector.
93+
type ConnectorEntityValidityDetails struct {
94+
Valid bool `json:"valid"`
95+
InvalidYaml string `json:"invalidYaml"`
96+
}
97+
98+
// ConnectorStatus represents the status information of a connector.
99+
type ConnectorStatus struct {
100+
Status string `json:"status"`
101+
ErrorSummary string `json:"errorSummary"`
102+
Errors []ConnectorError `json:"errors"`
103+
TestedAt int64 `json:"testedAt"`
104+
LastTestedAt int64 `json:"lastTestedAt"`
105+
LastConnectedAt int64 `json:"lastConnectedAt"`
106+
LastAlertSent int64 `json:"lastAlertSent"`
107+
}
108+
109+
// ConnectorStatusResponse extends ConnectorStatus with human-readable timestamp fields
110+
type ConnectorStatusResponse struct {
111+
// Original fields from ConnectorStatus
112+
Status string `json:"status"`
113+
ErrorSummary string `json:"errorSummary"`
114+
Errors []ConnectorError `json:"errors"`
115+
// Human-readable timestamps instead of Unix timestamps
116+
TestedAtTime string `json:"tested_at_time"`
117+
LastTestedAtTime string `json:"last_tested_at_time"`
118+
LastConnectedAtTime string `json:"last_connected_at_time"`
119+
LastAlertSentTime string `json:"last_alert_sent_time"`
120+
}
121+
122+
// ConnectorError represents an error in connector status.
123+
type ConnectorError struct {
124+
Reason string `json:"reason"`
125+
Message string `json:"message"`
126+
Code int `json:"code"`
127+
}
128+
129+
// ActivityDetails represents the activity information of a connector.
130+
type ActivityDetails struct {
131+
LastActivityTime int64 `json:"lastActivityTime"`
132+
}
133+
134+
// ActivityDetailsResponse extends ActivityDetails with human-readable timestamp fields
135+
type ActivityDetailsResponse struct {
136+
// Human-readable timestamp instead of Unix timestamp
137+
LastActivityTimeStr string `json:"last_activity_time"`
138+
}
139+
140+
// ConnectorGitDetails represents git-related information of a connector.
141+
type ConnectorGitDetails struct {
142+
Valid bool `json:"valid"`
143+
InvalidYaml string `json:"invalidYaml"`
144+
}
145+
146+
// ConnectorListRequestBody represents the request body for listing connectors.
147+
// Based on https://apidocs.harness.io/tag/Connectors#operation/getConnectorListV2
148+
type ConnectorListRequestBody struct {
149+
ConnectorNames []string `json:"connectorNames,omitempty"`
150+
ConnectorIdentifiers []string `json:"connectorIdentifiers,omitempty"`
151+
Description string `json:"description,omitempty"`
152+
Types []string `json:"types,omitempty"`
153+
Categories []string `json:"categories,omitempty"`
154+
ConnectivityStatuses []string `json:"connectivityStatuses,omitempty"`
155+
InheritingCredentialsFromDelegate *bool `json:"inheritingCredentialsFromDelegate,omitempty"`
156+
ConnectorConnectivityModes []string `json:"connectorConnectivityModes,omitempty"`
157+
Tags map[string]string `json:"tags,omitempty"`
158+
FilterType string `json:"filterType,omitempty"`
159+
}
160+
161+
// ConnectorListQueryParams represents query parameters for listing connectors.
162+
type ConnectorListQueryParams struct {
163+
SearchTerm string `json:"searchTerm,omitempty"`
164+
FilterIdentifier string `json:"filterIdentifier,omitempty"`
165+
IncludeAllConnectorsAvailableAtScope *bool `json:"includeAllConnectorsAvailableAtScope,omitempty"`
166+
Branch string `json:"branch,omitempty"`
167+
RepoIdentifier string `json:"repoIdentifier,omitempty"`
168+
GetDefaultFromOtherRepo *bool `json:"getDefaultFromOtherRepo,omitempty"`
169+
GetDistinctFromBranches *bool `json:"getDistinctFromBranches,omitempty"`
170+
Version string `json:"version,omitempty"`
171+
OnlyFavorites *bool `json:"onlyFavorites,omitempty"`
172+
PageIndex *int `json:"pageIndex,omitempty"`
173+
PageSize *int `json:"pageSize,omitempty"`
174+
SortOrders string `json:"sortOrders,omitempty"`
175+
PageToken string `json:"pageToken,omitempty"`
176+
}
177+
178+
// ConnectorListResponse represents the response from listing connectors.
179+
type ConnectorListResponse struct {
180+
Status string `json:"status"`
181+
Data ConnectorListData `json:"data"`
182+
MetaData interface{} `json:"metaData"`
183+
CorrelationID string `json:"correlationId"`
184+
}
185+
186+
// ConnectorListData represents the data section of connector list response.
187+
type ConnectorListData struct {
188+
Content []ConnectorDetail `json:"content"`
189+
PageInfo PageInfo `json:"pageInfo"`
190+
Empty bool `json:"empty"`
191+
TotalElements int `json:"totalElements"`
192+
TotalPages int `json:"totalPages"`
193+
}
194+
195+
// ConnectorListDataResponse extends ConnectorListData with human-readable timestamp fields in content
196+
type ConnectorListDataResponse struct {
197+
Content []ConnectorDetailResponse `json:"content"`
198+
PageInfo PageInfo `json:"pageInfo"`
199+
Empty bool `json:"empty"`
200+
TotalElements int `json:"totalElements"`
201+
TotalPages int `json:"totalPages"`
202+
}
203+
204+
// PageInfo represents pagination information.
205+
type PageInfo struct {
206+
Page int `json:"page"`
207+
Size int `json:"size"`
208+
HasNext bool `json:"hasNext"`
209+
HasPrev bool `json:"hasPrev"`
210+
}
211+
212+
// ToConnectorStatus converts ConnectorStatus adding RFC3339 time strings
213+
func ToConnectorStatus(s ConnectorStatus) ConnectorStatusResponse {
214+
return ConnectorStatusResponse{
215+
Status: s.Status,
216+
ErrorSummary: s.ErrorSummary,
217+
Errors: s.Errors,
218+
TestedAtTime: formatUnixMillisToRFC3339(s.TestedAt),
219+
LastTestedAtTime: formatUnixMillisToRFC3339(s.LastTestedAt),
220+
LastConnectedAtTime: formatUnixMillisToRFC3339(s.LastConnectedAt),
221+
LastAlertSentTime: formatUnixMillisToRFC3339(s.LastAlertSent),
222+
}
223+
}
224+
225+
// ToActivityDetails converts ActivityDetails adding RFC3339 time string
226+
func ToActivityDetails(a ActivityDetails) ActivityDetailsResponse {
227+
return ActivityDetailsResponse{
228+
LastActivityTimeStr: formatUnixMillisToRFC3339(a.LastActivityTime),
229+
}
230+
}
231+
232+
// ToConnectorDetail converts ConnectorDetail adding RFC3339 time strings and nested conversions
233+
func ToConnectorDetail(d ConnectorDetail) ConnectorDetailResponse {
234+
return ConnectorDetailResponse{
235+
Connector: d.Connector,
236+
CreatedAtTime: formatUnixMillisToRFC3339(d.CreatedAt),
237+
LastModifiedAtTime: formatUnixMillisToRFC3339(d.LastModifiedAt),
238+
Status: ToConnectorStatus(d.Status),
239+
ActivityDetails: ToActivityDetails(d.ActivityDetails),
240+
HarnessManaged: d.HarnessManaged,
241+
GitDetails: d.GitDetails,
242+
EntityValidityDetails: d.EntityValidityDetails,
243+
GovernanceMetadata: d.GovernanceMetadata,
244+
IsFavorite: d.IsFavorite,
245+
}
246+
}
247+
248+
// ToConnectorListData converts ConnectorListData content to include human-readable times
249+
func ToConnectorListData(data ConnectorListData) ConnectorListDataResponse {
250+
out := ConnectorListDataResponse{
251+
PageInfo: data.PageInfo,
252+
Empty: data.Empty,
253+
TotalElements: data.TotalElements,
254+
TotalPages: data.TotalPages,
255+
}
256+
if len(data.Content) > 0 {
257+
out.Content = make([]ConnectorDetailResponse, len(data.Content))
258+
for i, item := range data.Content {
259+
out.Content[i] = ToConnectorDetail(item)
260+
}
261+
}
262+
return out
263+
}

0 commit comments

Comments
 (0)