Skip to content

Commit 494570b

Browse files
committed
feat: use sdk for graphql requests as well
update the gitlab golang sdk to a version that now supports the graphql client requests allows for proper retry strategy add unit test on graphql based functions remove all linked graphql classes that are now irrelevant
1 parent d17ff94 commit 494570b

File tree

10 files changed

+71
-133
lines changed

10 files changed

+71
-133
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ If mandatory arguments are not provided, the program will prompt for them.
6464
| `--destination-token` | `DESTINATION_GITLAB_TOKEN` | Yes | Access token for the destination GitLab instance |
6565
| `--destination-big` | `DESTINATION_GITLAB_BIG` | No | Specify if the destination GitLab instance is a big instance (default: false) |
6666
| `--mirror-mapping` | `MIRROR_MAPPING` | Yes | Path to a JSON file containing the mirror mapping |
67-
| `--retry` or `-r` | N/A | No | Number of retries for failed GitLab API requests (does not apply to GraphQL requests) (default: 3) |
67+
| `--retry` or `-r` | N/A | No | Number of retries for failed GitLab API requests (default: 3) |
6868

6969
## Example
7070

go.mod

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,18 @@ module gitlab-sync
33
go 1.24.2
44

55
require (
6+
github.com/hashicorp/go-retryablehttp v0.7.7
67
github.com/spf13/cobra v1.9.1
7-
gitlab.com/gitlab-org/api/client-go v0.126.0
8+
gitlab.com/gitlab-org/api/client-go v0.128.0
9+
go.uber.org/zap v1.27.0
810
)
911

1012
require (
1113
github.com/google/go-querystring v1.1.0 // indirect
1214
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
13-
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
1415
github.com/inconshreveable/mousetrap v1.1.0 // indirect
1516
github.com/spf13/pflag v1.0.6 // indirect
1617
go.uber.org/multierr v1.11.0 // indirect
17-
go.uber.org/zap v1.27.0 // indirect
18-
golang.org/x/oauth2 v0.25.0 // indirect
19-
golang.org/x/time v0.10.0 // indirect
18+
golang.org/x/oauth2 v0.30.0 // indirect
19+
golang.org/x/time v0.11.0 // indirect
2020
)

internal/mirroring/helper_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,9 @@ func setupTestServer(t *testing.T, role string, instanceSize string) (*http.Serv
292292
setupTestProjects(mux)
293293
setupTestGroups(mux)
294294

295+
// Add test handlers for the GraphQL endpoint.
296+
setupTestGraphQL(mux)
297+
295298
// Catch-all handler for undefined routes
296299
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
297300
http.Error(w, fmt.Sprintf("Undefined route accessed: %s %s", r.Method, r.URL.Path), http.StatusNotFound)
@@ -369,6 +372,29 @@ func setupTestGroup(mux *http.ServeMux, group *gitlab.Group, stringResponse stri
369372
})
370373
}
371374

375+
func setupTestGraphQL(mux *http.ServeMux) {
376+
// Setup the GraphQL endpoint to return a mock response.
377+
mux.HandleFunc("/api/graphql", func(w http.ResponseWriter, r *http.Request) {
378+
switch r.Method {
379+
case http.MethodPost:
380+
w.Header().Set(HEADER_CONTENT_TYPE, HEADER_ACCEPT)
381+
// Set response status to 200 OK
382+
w.WriteHeader(http.StatusOK)
383+
fmt.Fprint(w, `{
384+
"data": {
385+
"catalogResourcesCreate": {
386+
"errors": []
387+
}
388+
},
389+
"correlationId": "4a5a7b18e94ae6770b3933913989ef40"
390+
}`)
391+
default:
392+
// Set response status to 405 Method Not Allowed
393+
w.WriteHeader(http.StatusMethodNotAllowed)
394+
}
395+
})
396+
}
397+
372398
// setupTestProjects sets up the test HTTP server with handlers for project-related
373399
func setupTestProjects(mux *http.ServeMux) {
374400
// Setup the get projects endpoint to return a list of projects.

internal/mirroring/instance.go

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ package mirroring
33
import (
44
"sync"
55

6-
"gitlab-sync/internal/utils"
7-
86
"github.com/hashicorp/go-retryablehttp"
97
gitlab "gitlab.com/gitlab-org/api/client-go"
108
)
@@ -24,10 +22,6 @@ type GitlabInstance struct {
2422
// muGroups is a mutex used to synchronize access to the Groups map
2523
// It ensures that only one goroutine can read or write to the Groups map at a time
2624
muGroups sync.RWMutex
27-
// GraphQLClient is the GraphQL client used to interact with the GitLab GraphQL API
28-
// It is used to perform GraphQL queries and mutations
29-
// It is initialized with the GitLab token and URL
30-
GraphQLClient *utils.GraphQLClient
3125
// Role is the role of the GitLab instance, it can be either "source" or "destination"
3226
// It is used to determine the behavior of the mirroring process
3327
Role string
@@ -60,12 +54,11 @@ func newGitlabInstance(initArgs *GitlabInstanceOpts) (*GitlabInstance, error) {
6054
}
6155

6256
gitlabInstance := &GitlabInstance{
63-
Gitlab: gitlabClient,
64-
Projects: make(map[string]*gitlab.Project),
65-
Groups: make(map[string]*gitlab.Group),
66-
GraphQLClient: utils.NewGitlabGraphQLClient(initArgs.GitlabToken, initArgs.GitlabURL),
67-
Role: initArgs.Role,
68-
InstanceSize: initArgs.InstanceSize,
57+
Gitlab: gitlabClient,
58+
Projects: make(map[string]*gitlab.Project),
59+
Groups: make(map[string]*gitlab.Group),
60+
Role: initArgs.Role,
61+
InstanceSize: initArgs.InstanceSize,
6962
}
7063

7164
return gitlabInstance, nil

internal/mirroring/instance_test.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,6 @@ func TestNewGitlabInstance(t *testing.T) {
3232
if instance.Groups == nil {
3333
t.Error("expected Groups map to be initialized")
3434
}
35-
36-
if instance.GraphQLClient == nil {
37-
t.Error("expected GraphQLClient to be initialized")
38-
}
3935
}
4036

4137
func TestAddProject(t *testing.T) {

internal/mirroring/post.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package mirroring
22

33
import (
4+
"context"
45
"fmt"
56
"sync"
67

@@ -277,3 +278,26 @@ func (destinationGitlab *GitlabInstance) mirrorReleases(sourceGitlab *GitlabInst
277278
zap.L().Info("Releases mirroring completed", zap.String(ROLE_SOURCE, sourceProject.HTTPURLToRepo), zap.String(ROLE_DESTINATION, destinationProject.HTTPURLToRepo))
278279
return utils.MergeErrors(errorChan, 2)
279280
}
281+
282+
// addProjectToCICDCatalog adds a project to the CI/CD catalog in the destination GitLab instance.
283+
// It uses a GraphQL mutation to create the catalog resource for the project.
284+
func (g *GitlabInstance) addProjectToCICDCatalog(project *gitlab.Project) error {
285+
zap.L().Debug("Adding project to CI/CD catalog", zap.String("project", project.HTTPURLToRepo))
286+
mutation := `
287+
mutation {
288+
catalogResourcesCreate(input: { projectPath: "%s" }) {
289+
errors
290+
}
291+
}`
292+
query := fmt.Sprintf(mutation, project.PathWithNamespace)
293+
var response struct {
294+
Data struct {
295+
CatalogResourcesCreate struct {
296+
Errors []string `json:"errors"`
297+
} `json:"catalogResourcesCreate"`
298+
} `json:"data"`
299+
}
300+
301+
_, err := g.Gitlab.GraphQL.Do(context.Background(), gitlab.GraphQLQuery{Query: query}, &response)
302+
return err
303+
}

internal/mirroring/post_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,3 +219,13 @@ func TestCreateProjects(t *testing.T) {
219219
}
220220
})
221221
}
222+
223+
func TestAddProjectToCICDCatalog(t *testing.T) {
224+
_, gitlabInstance := setupTestServer(t, ROLE_DESTINATION, INSTANCE_SIZE_SMALL)
225+
t.Run("Add Project to CI/CD Catalog", func(t *testing.T) {
226+
err := gitlabInstance.addProjectToCICDCatalog(TEST_PROJECT)
227+
if err != nil {
228+
t.Errorf("Unexpected error when adding project to CI/CD catalog: %v", err)
229+
}
230+
})
231+
}

internal/mirroring/put.go

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -25,25 +25,6 @@ func (g *GitlabInstance) enableProjectMirrorPull(sourceProject *gitlab.Project,
2525
return err
2626
}
2727

28-
// addProjectToCICDCatalog adds a project to the CI/CD catalog in the destination GitLab instance.
29-
// It uses a GraphQL mutation to create the catalog resource for the project.
30-
//
31-
// NOTE: This function needs to be changed as soon as the gitlab SDK supports the GraphQL API.
32-
func (g *GitlabInstance) addProjectToCICDCatalog(project *gitlab.Project) error {
33-
zap.L().Debug("Adding project to CI/CD catalog", zap.String("project", project.HTTPURLToRepo))
34-
mutation := `
35-
mutation {
36-
catalogResourcesCreate(input: { projectPath: "%s" }) {
37-
errors
38-
}
39-
}`
40-
query := fmt.Sprintf(mutation, project.PathWithNamespace)
41-
_, err := g.GraphQLClient.SendRequest(&utils.GraphQLRequest{
42-
Query: query,
43-
}, "POST")
44-
return err
45-
}
46-
4728
// copyProjectAvatar copies the avatar from the source project to the destination project.
4829
// It first checks if the destination project already has an avatar set. If not, it downloads the avatar from the source project
4930
// and uploads it to the destination project.

internal/utils/types.go

Lines changed: 0 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,9 @@ Utility types definitions
44
package utils
55

66
import (
7-
"bytes"
8-
"context"
97
"encoding/json"
108
"errors"
119
"fmt"
12-
"net/http"
1310
"os"
1411
"path/filepath"
1512
"strings"
@@ -237,58 +234,3 @@ func checkVisibility(visibility string) bool {
237234
}
238235
return valid
239236
}
240-
241-
// GraphQLClient is a client for sending GraphQL requests to GitLab
242-
type GraphQLClient struct {
243-
token string
244-
URL string
245-
}
246-
247-
// GraphQLRequest is a struct that represents a GraphQL request
248-
// It contains the query and the variables
249-
type GraphQLRequest struct {
250-
Query string `json:"query"`
251-
Variables string `json:"variables,omitempty"`
252-
}
253-
254-
// NewGitlabGraphQLClient creates a new GraphQL client for GitLab
255-
// It takes the token and the GitLab URL as arguments
256-
// It returns a pointer to the GraphQLClient struct
257-
func NewGitlabGraphQLClient(token, gitlabUrl string) *GraphQLClient {
258-
return &GraphQLClient{
259-
token: token,
260-
URL: strings.TrimSuffix(gitlabUrl, "/") + "/api/graphql",
261-
}
262-
}
263-
264-
// SendRequest sends a GraphQL request to GitLab
265-
// It takes a GraphQLRequest struct and the HTTP method as arguments
266-
// It returns the response body as a string and an error if any
267-
func (g *GraphQLClient) SendRequest(request *GraphQLRequest, method string) (string, error) {
268-
requestBody, err := json.Marshal(request)
269-
if err != nil {
270-
return "", err
271-
}
272-
req, err := http.NewRequestWithContext(context.Background(), method, g.URL, bytes.NewBuffer(requestBody))
273-
if err != nil {
274-
return "", err
275-
}
276-
277-
req.Header.Set("Content-Type", "application/json")
278-
req.Header.Set("Authorization", "Bearer "+g.token)
279-
280-
client := &http.Client{}
281-
resp, err := client.Do(req)
282-
if err != nil {
283-
return "", err
284-
}
285-
defer resp.Body.Close()
286-
if resp.StatusCode != http.StatusOK {
287-
return "", fmt.Errorf("GraphQL request failed with status: %s", resp.Status)
288-
}
289-
var responseBody bytes.Buffer
290-
if _, err := responseBody.ReadFrom(resp.Body); err != nil {
291-
return "", err
292-
}
293-
return responseBody.String(), nil
294-
}

internal/utils/types_test.go

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ package utils
22

33
import (
44
"fmt"
5-
"net/http"
6-
"net/http/httptest"
75
"os"
86
"reflect"
97
"testing"
@@ -228,35 +226,3 @@ func TestCheck(t *testing.T) {
228226
})
229227
}
230228
}
231-
232-
// TestSendRequest tests sending a GraphQL request to GitLab
233-
func TestSendRequest(t *testing.T) {
234-
client := NewGitlabGraphQLClient("test-token", "http://example.com")
235-
236-
request := &GraphQLRequest{
237-
Query: "query { test }",
238-
Variables: "",
239-
}
240-
241-
server := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
242-
w.WriteHeader(http.StatusOK)
243-
_, err := w.Write([]byte(`{"data": "response"}`))
244-
if err != nil {
245-
t.Fatalf("failed to write response: %v", err)
246-
}
247-
})
248-
testServer := httptest.NewServer(server)
249-
defer testServer.Close()
250-
251-
client.URL = testServer.URL
252-
253-
response, err := client.SendRequest(request, http.MethodPost)
254-
if err != nil {
255-
t.Fatalf("SendRequest() error = %v", err)
256-
}
257-
258-
expectedResponse := `{"data": "response"}`
259-
if response != expectedResponse {
260-
t.Errorf("expected response %v, got %v", expectedResponse, response)
261-
}
262-
}

0 commit comments

Comments
 (0)