Skip to content

Commit 999ccc3

Browse files
committed
build: concurrency upgrade
Fix the improper initial logic Add concurrency Add verbosity
1 parent 6767246 commit 999ccc3

File tree

7 files changed

+551
-104
lines changed

7 files changed

+551
-104
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
# Compile output
1717
dist/
1818
build/
19+
bin/
1920

2021
# Node.js
2122
node_modules/
@@ -52,3 +53,6 @@ Desktop.ini
5253
*.cer
5354
*.jks
5455
*.pub
56+
57+
mirroring.json
58+
go.sum

cmd/main.go

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ import (
44
"fmt"
55
"log"
66
"os"
7-
"path"
7+
"path/filepath"
88
"strings"
99

10+
"github.com/boxboxjason/gitlab-sync/mirroring"
1011
"github.com/boxboxjason/gitlab-sync/utils"
1112
"github.com/spf13/cobra"
1213
)
@@ -46,15 +47,21 @@ func main() {
4647

4748
// Check if the Mirror Mapping file path is provided
4849
mirrorMappingPath = promptForMandatoryInput(mirrorMappingPath, "Input Mirror Mapping file path (MANDATORY)", "Mirror Mapping file path is mandatory", "Mirror Mapping file path set", args.NoPrompt, false)
49-
utils.LogVerbose("Mirror Mapping file resolved path: " + path.Clean(mirrorMappingPath))
50+
utils.LogVerbose("Mirror Mapping file resolved path: " + filepath.Clean(mirrorMappingPath))
5051

5152
utils.LogVerbose("Parsing mirror mapping file")
5253
mapping, err := utils.OpenMirrorMapping(mirrorMappingPath)
5354
if err != nil {
54-
log.Fatalf("Error opening mirror mapping file: %v", err)
55+
log.Fatalf("Error opening mirror mapping file: %s", err)
5556
}
56-
args.MirrorMapping = *mapping
5757
utils.LogVerbose("Mirror mapping file parsed successfully")
58+
59+
err = mirroring.MirrorGitlabs(args.SourceGitlabURL, args.SourceGitlabToken, args.DestinationGitlabURL, args.DestinationGitlabToken, mapping)
60+
if err != nil {
61+
fmt.Println("Error during mirroring process:")
62+
fmt.Println(err)
63+
}
64+
log.Println("Mirroring completed")
5865
},
5966
}
6067

@@ -70,7 +77,6 @@ func main() {
7077
fmt.Println(err)
7178
os.Exit(1)
7279
}
73-
7480
}
7581

7682
func promptForInput(prompt string) string {
@@ -80,10 +86,10 @@ func promptForInput(prompt string) string {
8086
return input
8187
}
8288

83-
func promptForMandatoryInput(defaultValue string, prompt string, errorMsg string, loggerMsg string, promptsEnabled bool, hideOutput bool) string {
89+
func promptForMandatoryInput(defaultValue string, prompt string, errorMsg string, loggerMsg string, promptsDisabled bool, hideOutput bool) string {
8490
input := strings.TrimSpace(defaultValue)
8591
if input == "" {
86-
if promptsEnabled {
92+
if !promptsDisabled {
8793
input = strings.TrimSpace(promptForInput(prompt))
8894
if input == "" {
8995
log.Fatal(errorMsg)

mirroring/get.go

Lines changed: 152 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,72 +2,193 @@ package mirroring
22

33
import (
44
"fmt"
5-
"path"
5+
"log"
6+
"path/filepath"
67
"strings"
8+
"sync"
79

810
"github.com/boxboxjason/gitlab-sync/utils"
11+
gitlab "gitlab.com/gitlab-org/api/client-go"
912
)
1013

11-
func (g *GitlabInstance) fetchProjects(filters *utils.MirrorMapping) error {
14+
func (g *GitlabInstance) fetchProjects(projectFilters *map[string]bool, groupFilters *map[string]bool, mirrorMapping *utils.MirrorMapping, isSource bool) error {
15+
sourceString := "source"
16+
if !isSource {
17+
sourceString = "destination"
18+
}
19+
utils.LogVerbosef("Fetching all projects from %s GitLab instance", sourceString)
1220
projects, _, err := g.Gitlab.Projects.ListProjects(nil)
1321
if err != nil {
1422
return err
1523
}
1624

25+
utils.LogVerbosef("Processing %d projects from %s GitLab instance", len(projects), sourceString)
26+
27+
// Create a wait group to wait for all goroutines to finish
28+
var wg sync.WaitGroup
29+
30+
// Create a channel to limit the number of concurrent goroutines
31+
concurrencyLimit := 10
32+
sem := make(chan struct{}, concurrencyLimit)
33+
1734
for _, project := range projects {
18-
// Check if the project matches the filters:
19-
// - either is in the projects map
20-
// - or path starts with any of the groups in the groups map
21-
if _, ok := filters.Projects[project.PathWithNamespace]; ok {
22-
g.addProject(project.PathWithNamespace, project)
23-
} else {
24-
for group := range filters.Groups {
25-
if strings.HasPrefix(project.PathWithNamespace, group) {
26-
g.addProject(project.PathWithNamespace, project)
27-
break
35+
wg.Add(1)
36+
// Acquire a token from the semaphore
37+
sem <- struct{}{}
38+
39+
go func(project *gitlab.Project) {
40+
defer wg.Done()
41+
// Release the token back to the semaphore
42+
defer func() { <-sem }()
43+
44+
// Check if the project matches the filters:
45+
// - either is in the projects map
46+
// - or path starts with any of the groups in the groups map
47+
if _, ok := (*projectFilters)[project.PathWithNamespace]; ok {
48+
g.addProject(project.PathWithNamespace, project)
49+
} else {
50+
for group := range *groupFilters {
51+
if strings.HasPrefix(project.PathWithNamespace, group) {
52+
// Add the project to the gitlab instance projects cache
53+
g.addProject(project.PathWithNamespace, project)
54+
55+
if isSource {
56+
// Retrieve the corresponding group creation options from the mirror mapping
57+
groupCreationOptions, ok := mirrorMapping.Groups[group]
58+
if !ok {
59+
log.Fatalf("Group %s not found in mirror mapping (internal error, please review script)", group)
60+
}
61+
62+
// Calculate the relative path between the project and the group
63+
relativePath, err := filepath.Rel(group, project.PathWithNamespace)
64+
if err != nil {
65+
log.Fatalf("Failed to calculate relative path for project %s: %s", project.PathWithNamespace, err)
66+
}
67+
68+
// Add the project to the mirror mapping
69+
mirrorMapping.AddProject(project.PathWithNamespace, &utils.ProjectMirroringOptions{
70+
CI_CD_Catalog: groupCreationOptions.CI_CD_Catalog,
71+
Issues: groupCreationOptions.Issues,
72+
DestinationURL: filepath.Join(groupCreationOptions.DestinationURL, relativePath),
73+
})
74+
}
75+
break
76+
}
2877
}
2978
}
30-
}
79+
}(project)
3180
}
3281

33-
utils.LogVerbosef("Found %d projects to mirror in the source GitLab instance", len(g.Projects))
82+
wg.Wait()
83+
84+
utils.LogVerbosef("Found %d projects to mirror in the %s GitLab instance", len(g.Projects), sourceString)
3485

3586
return nil
3687
}
3788

38-
func (g *GitlabInstance) fetchGroups(filters *utils.MirrorMapping) error {
89+
func (g *GitlabInstance) fetchGroups(groupFilters *map[string]bool, mirrorMapping *utils.MirrorMapping, isSource bool) error {
90+
sourceString := "source"
91+
if !isSource {
92+
sourceString = "destination"
93+
}
94+
utils.LogVerbosef("Fetching all groups from %s GitLab instance", sourceString)
3995
groups, _, err := g.Gitlab.Groups.ListGroups(nil)
4096
if err != nil {
4197
return err
4298
}
4399

100+
utils.LogVerbosef("Processing %d groups from %s GitLab instance", len(groups), sourceString)
101+
102+
// Create a wait group to wait for all goroutines to finish
103+
var wg sync.WaitGroup
104+
105+
// Create a channel to limit the number of concurrent goroutines
106+
concurrencyLimit := 10
107+
sem := make(chan struct{}, concurrencyLimit)
108+
44109
for _, group := range groups {
45-
// Check if the group matches the filters:
46-
// - either is in the groups map
47-
// - or path starts with any of the groups in the groups map
48-
// - or is a subgroup of any of the groups in the groups map
49-
if _, ok := filters.Groups[group.FullPath]; ok {
50-
g.addGroup(group.FullPath, group)
51-
} else {
52-
for groupPath := range filters.Groups {
53-
if strings.HasPrefix(group.FullPath, groupPath) {
54-
g.addGroup(group.FullPath, group)
55-
break
110+
wg.Add(1)
111+
// Acquire a token from the semaphore
112+
sem <- struct{}{}
113+
114+
go func(group *gitlab.Group) {
115+
defer wg.Done()
116+
// Release the token back to the semaphore
117+
defer func() { <-sem }()
118+
119+
// Check if the group matches the filters:
120+
// - either is in the groups map
121+
// - or path starts with any of the groups in the groups map
122+
// - or is a subgroup of any of the groups in the groups map
123+
if _, ok := (*groupFilters)[group.FullPath]; ok {
124+
g.addGroup(group.FullPath, group)
125+
} else {
126+
for groupPath := range *groupFilters {
127+
if strings.HasPrefix(group.FullPath, groupPath) {
128+
// Add the group to the gitlab instance groups cache
129+
g.addGroup(group.FullPath, group)
130+
131+
if isSource {
132+
// Retrieve the corresponding group creation options from the mirror mapping
133+
groupCreationOptions, ok := mirrorMapping.Groups[groupPath]
134+
if !ok {
135+
log.Fatalf("Group %s not found in mirror mapping (internal error, please review script)", groupPath)
136+
}
137+
138+
// Calculate the relative path between the group and the groupPath
139+
relativePath, err := filepath.Rel(groupPath, group.FullPath)
140+
if err != nil {
141+
log.Fatalf("Failed to calculate relative path for group %s: %s", group.FullPath, err)
142+
}
143+
144+
// Add the group to the mirror mapping
145+
mirrorMapping.AddGroup(group.FullPath, &utils.GroupMirroringOptions{
146+
CI_CD_Catalog: groupCreationOptions.CI_CD_Catalog,
147+
Issues: groupCreationOptions.Issues,
148+
DestinationURL: filepath.Join(groupCreationOptions.DestinationURL, relativePath),
149+
})
150+
}
151+
break
152+
}
56153
}
57154
}
58-
}
155+
}(group)
59156
}
60157

61-
utils.LogVerbosef("Found %d groups to mirror in the source GitLab instance", len(g.Groups))
158+
wg.Wait()
159+
160+
utils.LogVerbosef("Found %d matching groups in %s GitLab instance", len(g.Groups), sourceString)
62161

63162
return nil
64163
}
65164

66-
func (g *GitlabInstance) getParentGroupID(projectOrGroupPath string) (int, error) {
165+
func fetchAll(gitlabInstance *GitlabInstance, projectFilters map[string]bool, groupFilters map[string]bool, mirrorMapping *utils.MirrorMapping, isSource bool) error {
166+
wg := sync.WaitGroup{}
167+
errCh := make(chan error, 2)
168+
wg.Add(2)
169+
go func() {
170+
defer wg.Done()
171+
if err := gitlabInstance.fetchGroups(&groupFilters, mirrorMapping, isSource); err != nil {
172+
errCh <- err
173+
}
174+
}()
175+
go func() {
176+
defer wg.Done()
177+
if err := gitlabInstance.fetchProjects(&projectFilters, &groupFilters, mirrorMapping, isSource); err != nil {
178+
errCh <- err
179+
}
180+
}()
181+
wg.Wait()
182+
close(errCh)
183+
184+
return utils.MergeErrors(errCh, 2)
185+
}
186+
187+
func (g *GitlabInstance) getParentNamespaceID(projectOrGroupPath string) (int, error) {
67188
parentGroupID := -1
68-
parentPath := path.Dir(projectOrGroupPath)
189+
parentPath := filepath.Dir(projectOrGroupPath)
69190
var err error = nil
70-
if parentPath != "." {
191+
if parentPath != "." && parentPath != "/" {
71192
// Check if parent path is already in the instance groups cache
72193
if parentGroup, ok := g.Groups[parentPath]; ok {
73194
parentGroupID = parentGroup.ID

mirroring/instance.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package mirroring
2+
3+
import (
4+
"sync"
5+
6+
"github.com/boxboxjason/gitlab-sync/utils"
7+
gitlab "gitlab.com/gitlab-org/api/client-go"
8+
)
9+
10+
type GitlabInstance struct {
11+
Gitlab *gitlab.Client
12+
Projects map[string]*gitlab.Project
13+
muProjects sync.RWMutex
14+
Groups map[string]*gitlab.Group
15+
muGroups sync.RWMutex
16+
GraphQLClient *utils.GraphQLClient
17+
}
18+
19+
func newGitlabInstance(gitlabURL string, gitlabToken string) (*GitlabInstance, error) {
20+
gitlabClient, err := gitlab.NewClient(gitlabToken, gitlab.WithBaseURL(gitlabURL))
21+
if err != nil {
22+
return nil, err
23+
}
24+
25+
gitlabInstance := &GitlabInstance{
26+
Gitlab: gitlabClient,
27+
Projects: make(map[string]*gitlab.Project),
28+
Groups: make(map[string]*gitlab.Group),
29+
GraphQLClient: utils.NewGitlabGraphQLClient(gitlabToken, gitlabURL),
30+
}
31+
32+
return gitlabInstance, nil
33+
}
34+
35+
func (g *GitlabInstance) addProject(projectPath string, project *gitlab.Project) {
36+
g.muProjects.Lock()
37+
defer g.muProjects.Unlock()
38+
g.Projects[projectPath] = project
39+
}
40+
41+
func (g *GitlabInstance) getProject(projectPath string) *gitlab.Project {
42+
g.muProjects.RLock()
43+
defer g.muProjects.RUnlock()
44+
var project *gitlab.Project
45+
project, exists := g.Projects[projectPath]
46+
if !exists {
47+
project = nil
48+
}
49+
return project
50+
}
51+
52+
func (g *GitlabInstance) addGroup(groupPath string, group *gitlab.Group) {
53+
g.muGroups.Lock()
54+
defer g.muGroups.Unlock()
55+
g.Groups[groupPath] = group
56+
}
57+
58+
func (g *GitlabInstance) getGroup(groupPath string) *gitlab.Group {
59+
g.muGroups.RLock()
60+
defer g.muGroups.RUnlock()
61+
var group *gitlab.Group
62+
group, exists := g.Groups[groupPath]
63+
if !exists {
64+
group = nil
65+
}
66+
return group
67+
}

0 commit comments

Comments
 (0)