Skip to content

Commit 0eabf86

Browse files
vistaarjunejaHarness
authored andcommitted
feat: [ML-1106]: Abstract out authentication and separate out clients (#7)
* add account ID in internal mode * update how clients are created and passed around * use auth provider intfc in client * add internal command * move client creation out of main.go * add interface for authentication and implementations for api-key/jwt
1 parent b297f8e commit 0eabf86

24 files changed

+901
-182
lines changed

client/client.go

Lines changed: 16 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,21 @@ import (
55
"context"
66
"encoding/json"
77
"fmt"
8-
"github.com/harness/harness-mcp/client/ar"
98
"io"
109
"log/slog"
1110
"net/http"
1211
"net/url"
1312
"strings"
1413
"time"
1514

15+
"github.com/harness/harness-mcp/pkg/harness/auth"
16+
1617
"github.com/cenkalti/backoff/v4"
1718
"github.com/harness/harness-mcp/client/dto"
1819
"github.com/rs/zerolog/log"
1920
)
2021

2122
var (
22-
defaultBaseURL = "https://app.harness.io/"
23-
2423
// these can be moved to a level above if we want to make this a generic
2524
// client, keeping these here to ensure we don't end up returning too much info
2625
// when different tools get added.
@@ -43,17 +42,8 @@ type Client struct {
4342
// set to a domain endpoint to use with custom Harness installations
4443
BaseURL *url.URL
4544

46-
// API key for authentication
47-
// TODO: We can abstract out the auth provider
48-
APIKey string
49-
50-
// Services used for talking to different Harness entities
51-
Connectors *ConnectorService
52-
PullRequests *PullRequestService
53-
Pipelines *PipelineService
54-
Repositories *RepositoryService
55-
Logs *LogService
56-
Registry *ar.ClientWithResponses
45+
// AuthProvider used for authentication
46+
AuthProvider auth.Provider
5747
}
5848

5949
type service struct {
@@ -67,49 +57,19 @@ func defaultHTTPClient() *http.Client {
6757
}
6858

6959
// NewWithToken creates a new client with the specified base URL and API token
70-
func NewWithToken(uri, apiKey string) (*Client, error) {
60+
func NewWithAuthProvider(uri string, authProvider auth.Provider) (*Client, error) {
7161
parsedURL, err := url.Parse(uri)
7262
if err != nil {
7363
return nil, err
7464
}
7565
c := &Client{
76-
client: defaultHTTPClient(),
77-
BaseURL: parsedURL,
78-
APIKey: apiKey,
66+
client: defaultHTTPClient(),
67+
BaseURL: parsedURL,
68+
AuthProvider: authProvider,
7969
}
80-
c.initialize()
8170
return c, nil
8271
}
8372

84-
func (c *Client) initialize() error {
85-
if c.client == nil {
86-
c.client = defaultHTTPClient()
87-
}
88-
if c.BaseURL == nil {
89-
baseURL, err := url.Parse(defaultBaseURL)
90-
if err != nil {
91-
return err
92-
}
93-
c.BaseURL = baseURL
94-
}
95-
96-
c.Connectors = &ConnectorService{client: c}
97-
c.PullRequests = &PullRequestService{client: c}
98-
c.Pipelines = &PipelineService{client: c}
99-
c.Repositories = &RepositoryService{client: c}
100-
c.Logs = &LogService{client: c}
101-
102-
// TODO: Replace it with harness-go-sdk
103-
arClient, err := ar.NewClientWithResponses(c.BaseURL.String()+"/har/api/v1", ar.WithHTTPClient(c.client),
104-
ar.WithRequestEditorFn(getEditor(c.APIKey)))
105-
if err != nil {
106-
return err
107-
}
108-
c.Registry = arClient
109-
110-
return nil
111-
}
112-
11373
// Get is a simple helper that builds up the request URL, adding the path and parameters.
11474
// The response from the request is unmarshalled into the data parameter.
11575
func (c *Client) Get(
@@ -262,7 +222,14 @@ func (c *Client) PostRaw(
262222
// Do is a wrapper of http.Client.Do that injects the auth header in the request.
263223
func (c *Client) Do(r *http.Request) (*http.Response, error) {
264224
slog.Debug("Request", "method", r.Method, "url", r.URL.String())
265-
r.Header.Add(apiKeyHeader, c.APIKey)
225+
226+
// set the auth header
227+
ctx := r.Context()
228+
k, v, err := c.AuthProvider.GetHeader(ctx)
229+
if err != nil {
230+
return nil, err
231+
}
232+
r.Header.Set(k, v)
266233

267234
return c.client.Do(r)
268235
}
@@ -367,11 +334,3 @@ func setDefaultPagination(opts *dto.PaginationOptions) {
367334
}
368335
}
369336
}
370-
371-
// TODO: Remove once we have Client service or we integrate with go-sdk
372-
func getEditor(token string) func(ctx context.Context, req *http.Request) error {
373-
return func(ctx context.Context, req *http.Request) error {
374-
req.Header.Set("x-api-key", token)
375-
return nil
376-
}
377-
}

client/connectors.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
package client
22

33
type ConnectorService struct {
4-
client *Client
4+
Client *Client
55
}

client/logs.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ const (
1313

1414
// LogService handles operations related to pipeline logs
1515
type LogService struct {
16-
client *Client
16+
Client *Client
1717
}
1818

1919
// DownloadLogs fetches a download URL for pipeline execution logs
2020
func (l *LogService) DownloadLogs(ctx context.Context, scope dto.Scope, planExecutionID string) (string, error) {
2121
// First, get the pipeline execution details to determine the prefix format
22-
pipelineService := &PipelineService{client: l.client}
22+
pipelineService := &PipelineService{Client: l.Client} // TODO: needs to be changed for internal case, we should move this above
2323
execution, err := pipelineService.GetExecution(ctx, scope, planExecutionID)
2424
if err != nil {
2525
return "", fmt.Errorf("failed to get execution details: %w", err)
@@ -53,7 +53,7 @@ func (l *LogService) DownloadLogs(ctx context.Context, scope dto.Scope, planExec
5353
response := &dto.LogDownloadResponse{}
5454

5555
// Make the POST request
56-
err = l.client.Post(ctx, logDownloadPath, params, nil, response)
56+
err = l.Client.Post(ctx, logDownloadPath, params, nil, response)
5757
if err != nil {
5858
return "", fmt.Errorf("failed to fetch log download URL: %w", err)
5959
}

client/pipelines.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package client
33
import (
44
"context"
55
"fmt"
6+
67
"github.com/harness/harness-mcp/client/dto"
78
)
89

@@ -15,7 +16,7 @@ const (
1516
)
1617

1718
type PipelineService struct {
18-
client *Client
19+
Client *Client
1920
}
2021

2122
func (p *PipelineService) Get(ctx context.Context, scope dto.Scope, pipelineID string) (
@@ -32,7 +33,7 @@ func (p *PipelineService) Get(ctx context.Context, scope dto.Scope, pipelineID s
3233
response := &dto.Entity[dto.PipelineData]{}
3334

3435
// Make the GET request
35-
err := p.client.Get(ctx, path, params, map[string]string{}, response)
36+
err := p.Client.Get(ctx, path, params, map[string]string{}, response)
3637
if err != nil {
3738
return nil, err
3839
}
@@ -75,7 +76,7 @@ func (p *PipelineService) List(
7576
response := &dto.ListOutput[dto.PipelineListItem]{}
7677

7778
// Make the POST request
78-
err := p.client.Post(ctx, pipelineListPath, params, requestBody, response)
79+
err := p.Client.Post(ctx, pipelineListPath, params, requestBody, response)
7980
if err != nil {
8081
return nil, err
8182
}
@@ -131,7 +132,7 @@ func (p *PipelineService) ListExecutions(
131132
response := &dto.ListOutput[dto.PipelineExecution]{}
132133

133134
// Make the POST request
134-
err := p.client.Post(ctx, pipelineExecutionSummaryPath, params, requestBody, response)
135+
err := p.Client.Post(ctx, pipelineExecutionSummaryPath, params, requestBody, response)
135136
if err != nil {
136137
return nil, fmt.Errorf("failed to list pipeline executions: %w", err)
137138
}
@@ -155,7 +156,7 @@ func (p *PipelineService) GetExecution(
155156
response := &dto.Entity[dto.PipelineExecutionResponse]{}
156157

157158
// Make the GET request
158-
err := p.client.Get(ctx, path, params, map[string]string{}, response)
159+
err := p.Client.Get(ctx, path, params, map[string]string{}, response)
159160
if err != nil {
160161
return nil, fmt.Errorf("failed to get execution details: %w", err)
161162
}
@@ -186,7 +187,7 @@ func (p *PipelineService) FetchExecutionURL(
186187
urlResponse := &dto.Entity[string]{}
187188

188189
// Make the POST request
189-
err := p.client.Post(ctx, path, params, nil, urlResponse)
190+
err := p.Client.Post(ctx, path, params, nil, urlResponse)
190191
if err != nil {
191192
return "", fmt.Errorf("failed to fetch execution URL: %w", err)
192193
}

client/pullrequest.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const (
1717
)
1818

1919
type PullRequestService struct {
20-
client *Client
20+
Client *Client
2121
}
2222

2323
func (p *PullRequestService) Get(ctx context.Context, scope dto.Scope, repoID string, prNumber int) (*dto.PullRequest, error) {
@@ -26,7 +26,7 @@ func (p *PullRequestService) Get(ctx context.Context, scope dto.Scope, repoID st
2626
addScope(scope, params)
2727

2828
pr := new(dto.PullRequest)
29-
err := p.client.Get(ctx, path, params, nil, pr)
29+
err := p.Client.Get(ctx, path, params, nil, pr)
3030
if err != nil {
3131
return nil, fmt.Errorf("failed to get pull request: %w", err)
3232
}
@@ -115,7 +115,7 @@ func (p *PullRequestService) List(ctx context.Context, scope dto.Scope, repoID s
115115
}
116116

117117
var prs []*dto.PullRequest
118-
err := p.client.Get(ctx, path, params, nil, &prs)
118+
err := p.Client.Get(ctx, path, params, nil, &prs)
119119
if err != nil {
120120
return nil, fmt.Errorf("failed to list pull requests: %w", err)
121121
}
@@ -134,7 +134,7 @@ func (p *PullRequestService) Create(ctx context.Context, scope dto.Scope, repoID
134134
}
135135

136136
pr := new(dto.PullRequest)
137-
err := p.client.Post(ctx, path, params, createPR, pr)
137+
err := p.Client.Post(ctx, path, params, createPR, pr)
138138
if err != nil {
139139
return nil, fmt.Errorf("failed to create pull request: %w", err)
140140
}
@@ -149,7 +149,7 @@ func (p *PullRequestService) GetChecks(ctx context.Context, scope dto.Scope, rep
149149
addScope(scope, params)
150150

151151
checks := new(dto.PullRequestChecksResponse)
152-
err := p.client.Get(ctx, path, params, nil, checks)
152+
err := p.Client.Get(ctx, path, params, nil, checks)
153153
if err != nil {
154154
return nil, fmt.Errorf("failed to get pull request checks: %w", err)
155155
}

client/repositories.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const (
1414
)
1515

1616
type RepositoryService struct {
17-
client *Client
17+
Client *Client
1818
}
1919

2020
func (r *RepositoryService) Get(ctx context.Context, scope dto.Scope, repoIdentifier string) (*dto.Repository, error) {
@@ -23,7 +23,7 @@ func (r *RepositoryService) Get(ctx context.Context, scope dto.Scope, repoIdenti
2323
addScope(scope, params)
2424

2525
repo := new(dto.Repository)
26-
err := r.client.Get(ctx, path, params, nil, repo)
26+
err := r.Client.Get(ctx, path, params, nil, repo)
2727
if err != nil {
2828
return nil, fmt.Errorf("failed to get repository: %w", err)
2929
}
@@ -73,7 +73,7 @@ func (r *RepositoryService) List(ctx context.Context, scope dto.Scope, opts *dto
7373
}
7474

7575
var repos []*dto.Repository
76-
err := r.client.Get(ctx, path, params, nil, &repos)
76+
err := r.Client.Get(ctx, path, params, nil, &repos)
7777
if err != nil {
7878
return nil, fmt.Errorf("failed to list repositories: %w", err)
7979
}
Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,27 @@
11
package config
22

33
type Config struct {
4-
Version string
4+
// Common fields for both modes
5+
Version string
6+
ReadOnly bool
7+
Toolsets []string
8+
LogFilePath string
9+
Debug bool
10+
11+
Internal bool
12+
13+
// Only used for external mode
514
BaseURL string
615
AccountID string
716
DefaultOrgID string
817
DefaultProjectID string
918
APIKey string
10-
ReadOnly bool
11-
Toolsets []string
12-
LogFilePath string
13-
Debug bool
19+
20+
// Only used for internal mode
21+
BearerToken string
22+
PipelineSvcBaseURL string
23+
PipelineSvcSecret string
24+
NgManagerBaseURL string
25+
NgManagerSecret string
26+
McpSvcSecret string
1427
}

0 commit comments

Comments
 (0)