diff --git a/README.md b/README.md index 6167f726b..8ef95903b 100644 --- a/README.md +++ b/README.md @@ -244,6 +244,13 @@ - [Creating New Evidence Service Manager](#creating-new-evidence-service-manager) - [Using Evidence Services](#using-evidence-services) - [Upload Evidence](#upload-evidence) + - [Metadata APIs](#metadata-apis) + - [Creating Metadata Service Manager](#creating-metadata-service-manager) + - [Creating Metadata Details](#creating-metadata-details) + - [Creating Metadata Service Config](#creating-metadata-service-config) + - [Creating New Metadata Service Manager](#creating-new-metadata-service-manager) + - [Using Metadata Services](#using-metadata-services) + - [Graphql query](#graphql-query) ## General @@ -2964,3 +2971,48 @@ evidenceDetails := evidenceService.EvidenceDetails{ } body, err = evideceManager.UploadEvidence(evidenceDetails) ``` +## Metadata APIs + +### Creating Metadata Service Manager + +#### Creating Metadata Details + +```go +mdDetails := auth.NewMetadataDetails() +mdDetails.SetUrl("http://localhost:8081/metadata") +mdDetails.SetAccessToken("access-token") +// if client certificates are required +mdDetails.SetClientCertPath("path/to/.cer") +mdDetails.SetClientCertKeyPath("path/to/.key") +``` + +#### Creating Metadata Service Config + +```go +serviceConfig, err := config.NewConfigBuilder(). + SetServiceDetails(mdDetails). + SetCertificatesPath(mdDetails.GetClientCertPath()). + // Optionally overwrite the default HTTP retries, which is set to 3. + SetHttpRetries(3). + Build() +``` + +#### Creating New Metadata Service Manager + +```go +metadataManager, err := metadata.NewManager(serviceConfig) +``` + +### Using Metadata Services + +#### Graphql query + +```go +queryBytes := []byte(`{"query":"someGraphqlQuery"}`) + +queryDetails := metadataService.QueryDetails{ + Body: queryBytes, +} + +body, err = metadataManager.GraphqlQuery(queryDetails) +``` \ No newline at end of file diff --git a/metadata/auth/metadatadetails.go b/metadata/auth/metadatadetails.go new file mode 100644 index 000000000..fe8504d19 --- /dev/null +++ b/metadata/auth/metadatadetails.go @@ -0,0 +1,17 @@ +package auth + +import ( + "github.com/jfrog/jfrog-client-go/auth" +) + +type metadataDetails struct { + auth.CommonConfigFields +} + +func NewMetadataDetails() auth.ServiceDetails { + return &metadataDetails{} +} + +func (rt *metadataDetails) GetVersion() (string, error) { + panic("Failed: Method is not implemented") +} diff --git a/metadata/manager.go b/metadata/manager.go new file mode 100644 index 000000000..9b6e3b071 --- /dev/null +++ b/metadata/manager.go @@ -0,0 +1,45 @@ +package metadata + +import ( + "github.com/jfrog/jfrog-client-go/config" + "github.com/jfrog/jfrog-client-go/http/jfroghttpclient" + "github.com/jfrog/jfrog-client-go/metadata/services" +) + +type Manager interface { + GraphqlQuery(query []byte) ([]byte, error) +} + +type metadataManager struct { + client *jfroghttpclient.JfrogHttpClient + config config.Config +} + +func NewManager(config config.Config) (Manager, error) { + details := config.GetServiceDetails() + var err error + manager := &metadataManager{config: config} + manager.client, err = jfroghttpclient.JfrogClientBuilder(). + SetCertificatesPath(config.GetCertificatesPath()). + SetInsecureTls(config.IsInsecureTls()). + SetClientCertPath(details.GetClientCertPath()). + SetClientCertKeyPath(details.GetClientCertKeyPath()). + AppendPreRequestInterceptor(details.RunPreRequestFunctions). + SetContext(config.GetContext()). + SetDialTimeout(config.GetDialTimeout()). + SetOverallRequestTimeout(config.GetOverallRequestTimeout()). + SetRetries(config.GetHttpRetries()). + SetRetryWaitMilliSecs(config.GetHttpRetryWaitMilliSecs()). + Build() + + return manager, err +} + +func (mm *metadataManager) Client() *jfroghttpclient.JfrogHttpClient { + return mm.client +} + +func (mm *metadataManager) GraphqlQuery(query []byte) ([]byte, error) { + metadataService := services.NewMetadataService(mm.config.GetServiceDetails(), mm.client) + return metadataService.Query(query) +} diff --git a/metadata/services/graphql.go b/metadata/services/graphql.go new file mode 100644 index 000000000..1ccde656f --- /dev/null +++ b/metadata/services/graphql.go @@ -0,0 +1,45 @@ +package services + +import ( + "fmt" + rtUtils "github.com/jfrog/jfrog-client-go/artifactory/services/utils" + "github.com/jfrog/jfrog-client-go/auth" + "github.com/jfrog/jfrog-client-go/http/jfroghttpclient" + "github.com/jfrog/jfrog-client-go/utils/errorutils" + "net/http" + "net/url" +) + +const queryUrl = "api/v1/query" + +type Service interface { + Query(query []byte) ([]byte, error) +} + +type metadataService struct { + client *jfroghttpclient.JfrogHttpClient + serviceDetails *auth.ServiceDetails +} + +func NewMetadataService(serviceDetails auth.ServiceDetails, client *jfroghttpclient.JfrogHttpClient) Service { + return &metadataService{serviceDetails: &serviceDetails, client: client} +} + +func (m *metadataService) GetMetadataDetails() auth.ServiceDetails { + return *m.serviceDetails +} + +func (m *metadataService) Query(query []byte) ([]byte, error) { + graphqlUrl, err := url.Parse(m.GetMetadataDetails().GetUrl() + queryUrl) + if err != nil { + return nil, fmt.Errorf("failed to parse URL: %w", err) + } + httpClientDetails := m.GetMetadataDetails().CreateHttpClientDetails() + rtUtils.SetContentType("application/json", &httpClientDetails.Headers) + + resp, body, err := m.client.SendPost(graphqlUrl.String(), query, &httpClientDetails) + if err != nil { + return []byte{}, err + } + return body, errorutils.CheckResponseStatusWithBody(resp, body, http.StatusOK) +} diff --git a/metadata/services/graphql_test.go b/metadata/services/graphql_test.go new file mode 100644 index 000000000..b15bc5a65 --- /dev/null +++ b/metadata/services/graphql_test.go @@ -0,0 +1,55 @@ +package services + +import ( + "encoding/json" + "github.com/jfrog/jfrog-client-go/artifactory/auth" + "github.com/jfrog/jfrog-client-go/http/jfroghttpclient" + "github.com/stretchr/testify/assert" + "net/http" + "net/http/httptest" + "testing" +) + +const queryData = `{"query":"someGraphqlQuery"}` + +func TestMetadataService_Query(t *testing.T) { + handlerFunc, requestNum := createMetadataHandlerFunc(t) + + mockServer, metadataService := createMockMetadataServer(t, handlerFunc) + defer mockServer.Close() + + _, err := metadataService.Query([]byte(queryData)) + assert.NoError(t, err) + assert.Equal(t, 1, *requestNum) +} + +func createMockMetadataServer(t *testing.T, testHandler http.HandlerFunc) (*httptest.Server, *metadataService) { + testServer := httptest.NewServer(testHandler) + + serviceDetails := auth.NewArtifactoryDetails() + serviceDetails.SetUrl(testServer.URL + "/") + + client, err := jfroghttpclient.JfrogClientBuilder().Build() + assert.NoError(t, err) + return testServer, &metadataService{serviceDetails: &serviceDetails, client: client} +} + +func createMetadataHandlerFunc(t *testing.T) (http.HandlerFunc, *int) { + requestNum := 0 + return func(w http.ResponseWriter, r *http.Request) { + if r.RequestURI == "/api/v1/query" { + assert.Equal(t, "application/json", r.Header.Get("Content-Type")) + + w.WriteHeader(http.StatusOK) + requestNum++ + writeMockMetadataResponse(t, w, []byte(queryData)) + } + }, &requestNum +} + +func writeMockMetadataResponse(t *testing.T, w http.ResponseWriter, payload []byte) { + content, err := json.Marshal(payload) + assert.NoError(t, err) + _, err = w.Write(content) + assert.NoError(t, err) +} diff --git a/tests/timeout_test.go b/tests/timeout_test.go index ea7304654..6dc284bae 100644 --- a/tests/timeout_test.go +++ b/tests/timeout_test.go @@ -3,6 +3,7 @@ package tests import ( "github.com/jfrog/jfrog-client-go/evidence" "github.com/jfrog/jfrog-client-go/evidence/services" + "github.com/jfrog/jfrog-client-go/metadata" "net/http" "net/http/httptest" "testing" @@ -23,6 +24,7 @@ import ( evidenceAuth "github.com/jfrog/jfrog-client-go/evidence/auth" lifecycleAuth "github.com/jfrog/jfrog-client-go/lifecycle/auth" lifecycleServices "github.com/jfrog/jfrog-client-go/lifecycle/services" + metadataAuth "github.com/jfrog/jfrog-client-go/metadata/auth" pipelinesAuth "github.com/jfrog/jfrog-client-go/pipelines/auth" "github.com/jfrog/jfrog-client-go/utils/log" "github.com/jfrog/jfrog-client-go/utils/tests" @@ -48,6 +50,7 @@ func TestTimeout(t *testing.T) { t.Run("testPipelinesTimeout", testPipelinesTimeout) t.Run("testXrayTimeout", testXrayTimeout) t.Run("testEvidenceTimeout", testEvidenceTimeout) + t.Run("testMetadataTimeout", testMetadataTimeout) } func testAccessTimeout(t *testing.T) { @@ -136,6 +139,23 @@ func testEvidenceTimeout(t *testing.T) { assert.ErrorContains(t, err, "context deadline exceeded") } +func testMetadataTimeout(t *testing.T) { + // Create mock server + url, cleanup := createSleepyRequestServer() + defer cleanup() + + // Create services manager configuring to work with the mock server + details := metadataAuth.NewMetadataDetails() + details.SetUrl(url) + servicesManager, err := metadata.NewManager(createServiceConfigWithTimeout(t, details)) + assert.NoError(t, err) + + query := []byte("query body") + // Expect timeout + _, err = servicesManager.GraphqlQuery(query) + assert.ErrorContains(t, err, "context deadline exceeded") +} + func testPipelinesTimeout(t *testing.T) { // Create mock server url, cleanup := createSleepyRequestServer()