Skip to content

Commit fa5bca1

Browse files
committed
style: maintainability tech debt resorbtion
split big functions into smaller atomic parts removed sugar logger added docstrings and comments in most places that required it
1 parent 9196cc8 commit fa5bca1

File tree

9 files changed

+546
-388
lines changed

9 files changed

+546
-388
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,5 @@ Desktop.ini
5757
mirroring.json
5858
go.sum
5959
sonar-project.properties
60+
unit-tests.xml
61+
coverage.out

cmd/main.go

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,18 +36,20 @@ func main() {
3636
zap.L().Debug("Parsing command line arguments")
3737

3838
// Obtain the retry count
39-
if args.Retry == -1 {
39+
switch args.Retry {
40+
case -1:
4041
args.Retry = 10000
41-
} else if args.Retry == 0 {
42+
case 0:
4243
zap.L().Fatal("retry count must be -1 (no limit) or strictly greater than 0")
4344
}
4445

4546
// Set the timeout for GitLab API requests
46-
if timeout == -1 {
47+
switch timeout {
48+
case -1:
4749
args.Timeout = time.Duration(10000 * time.Second)
48-
} else if timeout == 0 {
50+
case 0:
4951
zap.L().Fatal("timeout must be -1 (no limit) or strictly greater than 0")
50-
} else {
52+
default:
5153
args.Timeout = time.Duration(timeout) * time.Second
5254
}
5355

@@ -67,7 +69,7 @@ func main() {
6769
zap.L().Debug("Parsing mirror mapping file")
6870
mapping, err := utils.OpenMirrorMapping(mirrorMappingPath)
6971
if err != nil {
70-
zap.L().Sugar().Fatalf("Error opening mirror mapping file: %s", err)
72+
zap.L().Fatal("Error opening mirror mapping file", zap.Error(err))
7173
}
7274
zap.L().Debug("Mirror mapping file parsed successfully")
7375
args.MirrorMapping = mapping
@@ -97,18 +99,27 @@ func main() {
9799
}
98100
}
99101

102+
// promptForInput prompts the user for input and returns the trimmed response.
103+
// It handles errors and prints a message if the input is empty.
104+
// If the input is empty, it will return an empty string.
100105
func promptForInput(prompt string) string {
101106
var input string
102107
fmt.Printf("%s: ", prompt)
103-
fmt.Scanln(&input)
104-
return input
108+
_, err := fmt.Scanln(&input)
109+
if err != nil {
110+
zap.L().Fatal("Error reading input", zap.Error(err))
111+
}
112+
return strings.TrimSpace(input)
105113
}
106114

115+
// promptForMandatoryInput prompts the user for mandatory input and returns the trimmed response.
116+
// If the input is empty, it will log a fatal error message and exit the program.
117+
// It also logs the input value if hideOutput is false.
107118
func promptForMandatoryInput(defaultValue string, prompt string, errorMsg string, loggerMsg string, promptsDisabled bool, hideOutput bool) string {
108119
input := strings.TrimSpace(defaultValue)
109120
if input == "" {
110121
if !promptsDisabled {
111-
input = strings.TrimSpace(promptForInput(prompt))
122+
input = promptForInput(prompt)
112123
if input == "" {
113124
zap.L().Fatal(errorMsg)
114125
}
@@ -118,12 +129,15 @@ func promptForMandatoryInput(defaultValue string, prompt string, errorMsg string
118129
zap.L().Debug(loggerMsg)
119130
}
120131
} else {
121-
zap.L().Sugar().Fatal("Prompting is disabled, %s", errorMsg)
132+
zap.L().Fatal("Prompting is disabled")
122133
}
123134
}
124135
return input
125136
}
126137

138+
// setupZapLogger sets up the Zap logger with the specified verbosity level.
139+
// It configures the logger to use ISO8601 time format and capitalizes the log levels.
140+
// The logger is set to production mode by default, but can be configured for debug mode if verbose is true.
127141
func setupZapLogger(verbose bool) {
128142
// Set up the logger configuration
129143
config := zap.NewProductionConfig()

internal/mirroring/get.go

Lines changed: 123 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -12,89 +12,96 @@ import (
1212
"go.uber.org/zap"
1313
)
1414

15+
// fetchProjects retrieves all projects that match the filters from the GitLab instance and stores them in the instance cache.
16+
// It also updates the mirror mapping with the corresponding group creation options.
17+
//
18+
// The function is run in a goroutine for each project, and a wait group is used to wait for all goroutines to finish.
1519
func (g *GitlabInstance) fetchProjects(projectFilters *map[string]bool, groupFilters *map[string]bool, mirrorMapping *utils.MirrorMapping, isSource bool) error {
1620
sourceString := "source"
1721
if !isSource {
1822
sourceString = "destination"
1923
}
20-
zap.L().Sugar().Debugf("Fetching all projects from %s GitLab instance", sourceString)
24+
zap.L().Debug("Fetching all projects from GitLab instance", zap.String("role", sourceString))
2125
projects, _, err := g.Gitlab.Projects.ListProjects(nil)
2226
if err != nil {
2327
return err
2428
}
2529

26-
zap.L().Sugar().Debugf("Processing %d projects from %s GitLab instance", len(projects), sourceString)
30+
zap.L().Debug("Processing projects from GitLab instance", zap.String("role", sourceString), zap.Int("projects", len(projects)))
2731

2832
// Create a wait group to wait for all goroutines to finish
2933
var wg sync.WaitGroup
3034

3135
for _, project := range projects {
3236
wg.Add(1)
33-
// Acquire a token from the semaphore
3437

3538
go func(project *gitlab.Project) {
3639
defer wg.Done()
3740

38-
// Check if the project matches the filters:
39-
// - either is in the projects map
40-
// - or path starts with any of the groups in the groups map
41-
if _, ok := (*projectFilters)[project.PathWithNamespace]; ok {
42-
g.addProject(project.PathWithNamespace, project)
43-
} else {
44-
for group := range *groupFilters {
45-
if strings.HasPrefix(project.PathWithNamespace, group) {
46-
// Add the project to the gitlab instance projects cache
47-
g.addProject(project.PathWithNamespace, project)
48-
49-
if isSource {
50-
// Retrieve the corresponding group creation options from the mirror mapping
51-
groupCreationOptions, ok := mirrorMapping.Groups[group]
52-
if !ok {
53-
zap.L().Sugar().Errorf("Group %s not found in mirror mapping (internal error, please review script)", group)
54-
}
55-
56-
// Calculate the relative path between the project and the group
57-
relativePath, err := filepath.Rel(group, project.PathWithNamespace)
58-
if err != nil {
59-
zap.L().Sugar().Errorf("Failed to calculate relative path for project %s: %s", project.PathWithNamespace, err)
60-
}
61-
62-
// Add the project to the mirror mapping
63-
mirrorMapping.AddProject(project.PathWithNamespace, &utils.MirroringOptions{
64-
DestinationPath: filepath.Join(groupCreationOptions.DestinationPath, relativePath),
65-
CI_CD_Catalog: groupCreationOptions.CI_CD_Catalog,
66-
Issues: groupCreationOptions.Issues,
67-
MirrorTriggerBuilds: groupCreationOptions.MirrorTriggerBuilds,
68-
Visibility: groupCreationOptions.Visibility,
69-
MirrorReleases: groupCreationOptions.MirrorReleases,
70-
})
71-
}
72-
break
73-
}
74-
}
41+
group, matches := g.checkPathMatchesFilters(project.PathWithNamespace, projectFilters, groupFilters)
42+
if matches {
43+
g.storeProject(project, group, mirrorMapping, isSource)
7544
}
45+
7646
}(project)
7747
}
7848

7949
wg.Wait()
8050

81-
zap.L().Sugar().Debugf("Found %d projects to mirror in the %s GitLab instance", len(g.Projects), sourceString)
82-
51+
zap.L().Debug("Found matching projects in the GitLab instance", zap.String("role", sourceString), zap.Int("projects", len(g.Projects)))
8352
return nil
8453
}
8554

55+
// storeProject stores the project in the Gitlab instance projects cache
56+
// and updates the mirror mapping with the corresponding group creation options.
57+
func (g *GitlabInstance) storeProject(project *gitlab.Project, parentGroupPath string, mirrorMapping *utils.MirrorMapping, isSource bool) {
58+
// Add the project to the gitlab instance projects cache
59+
g.addProject(project.PathWithNamespace, project)
60+
61+
if isSource {
62+
zap.L().Debug("Storing project in mirror mapping", zap.String("project", project.HTTPURLToRepo), zap.String("group", parentGroupPath))
63+
// Retrieve the corresponding group creation options from the mirror mapping
64+
groupCreationOptions, ok := mirrorMapping.GetGroup(parentGroupPath)
65+
if !ok {
66+
zap.L().Error("Group not found in mirror mapping", zap.String("group", parentGroupPath))
67+
return
68+
}
69+
70+
// Calculate the relative path between the project and the group
71+
relativePath, err := filepath.Rel(parentGroupPath, project.PathWithNamespace)
72+
if err != nil {
73+
zap.L().Error("Failed to calculate relative path for project", zap.String("project", project.HTTPURLToRepo), zap.String("group", parentGroupPath), zap.Error(err))
74+
return
75+
}
76+
77+
// Add the project to the mirror mapping with the corresponding group creation options
78+
mirrorMapping.AddProject(project.PathWithNamespace, &utils.MirroringOptions{
79+
DestinationPath: filepath.Join(groupCreationOptions.DestinationPath, relativePath),
80+
CI_CD_Catalog: groupCreationOptions.CI_CD_Catalog,
81+
Issues: groupCreationOptions.Issues,
82+
MirrorTriggerBuilds: groupCreationOptions.MirrorTriggerBuilds,
83+
Visibility: groupCreationOptions.Visibility,
84+
MirrorReleases: groupCreationOptions.MirrorReleases,
85+
})
86+
}
87+
}
88+
89+
// fetchGroups retrieves all groups that match the filters from the GitLab instance and stores them in the instance cache.
90+
// It also updates the mirror mapping with the corresponding group creation options.
91+
//
92+
// The function is run in a goroutine for each group, and a wait group is used to wait for all goroutines to finish.
8693
func (g *GitlabInstance) fetchGroups(groupFilters *map[string]bool, mirrorMapping *utils.MirrorMapping, isSource bool) error {
8794
sourceString := "source"
8895
if !isSource {
8996
sourceString = "destination"
9097
}
91-
zap.L().Sugar().Debugf("Fetching all groups from %s GitLab instance", sourceString)
98+
zap.L().Debug("Fetching all groups from GitLab instance", zap.String("role", sourceString))
9299
groups, _, err := g.Gitlab.Groups.ListGroups(nil)
93100
if err != nil {
94101
return err
95102
}
96103

97-
zap.L().Sugar().Debugf("Processing %d groups from %s GitLab instance", len(groups), sourceString)
104+
zap.L().Debug("Processing groups from GitLab instance", zap.String("role", sourceString), zap.Int("groups", len(groups)))
98105

99106
// Create a wait group to wait for all goroutines to finish
100107
var wg sync.WaitGroup
@@ -105,55 +112,84 @@ func (g *GitlabInstance) fetchGroups(groupFilters *map[string]bool, mirrorMappin
105112
go func(group *gitlab.Group) {
106113
defer wg.Done()
107114

108-
// Check if the group matches the filters:
109-
// - either is in the groups map
110-
// - or path starts with any of the groups in the groups map
111-
// - or is a subgroup of any of the groups in the groups map
112-
if _, ok := (*groupFilters)[group.FullPath]; ok {
113-
g.addGroup(group.FullPath, group)
114-
} else {
115-
for groupPath := range *groupFilters {
116-
if strings.HasPrefix(group.FullPath, groupPath) {
117-
// Add the group to the gitlab instance groups cache
118-
g.addGroup(group.FullPath, group)
119-
120-
if isSource {
121-
// Retrieve the corresponding group creation options from the mirror mapping
122-
groupCreationOptions, ok := mirrorMapping.Groups[groupPath]
123-
if !ok {
124-
zap.L().Sugar().Fatalf("Group %s not found in mirror mapping (internal error, please review script)", groupPath)
125-
}
126-
127-
// Calculate the relative path between the group and the groupPath
128-
relativePath, err := filepath.Rel(groupPath, group.FullPath)
129-
if err != nil {
130-
zap.L().Sugar().Fatalf("Failed to calculate relative path for group %s: %s", group.FullPath, err)
131-
}
132-
133-
// Add the group to the mirror mapping
134-
mirrorMapping.AddGroup(group.FullPath, &utils.MirroringOptions{
135-
DestinationPath: filepath.Join(groupCreationOptions.DestinationPath, relativePath),
136-
CI_CD_Catalog: groupCreationOptions.CI_CD_Catalog,
137-
Issues: groupCreationOptions.Issues,
138-
MirrorTriggerBuilds: groupCreationOptions.MirrorTriggerBuilds,
139-
Visibility: groupCreationOptions.Visibility,
140-
MirrorReleases: groupCreationOptions.MirrorReleases,
141-
})
142-
}
143-
break
144-
}
145-
}
115+
groupPath, matches := g.checkPathMatchesFilters(group.FullPath, nil, groupFilters)
116+
if matches {
117+
g.storeGroup(group, groupPath, mirrorMapping, isSource)
146118
}
147119
}(group)
148120
}
149121

150122
wg.Wait()
151123

152-
zap.L().Sugar().Debugf("Found %d matching groups in %s GitLab instance", len(g.Groups), sourceString)
124+
zap.L().Debug("Found matching groups in the GitLab instance", zap.String("role", sourceString), zap.Int("groups", len(g.Groups)))
153125

154126
return nil
155127
}
156128

129+
// storeGroup stores the group in the Gitlab instance groups cache
130+
// and updates the mirror mapping with the corresponding group creation options.
131+
func (g *GitlabInstance) storeGroup(group *gitlab.Group, parentGroupPath string, mirrorMapping *utils.MirrorMapping, isSource bool) {
132+
if group != nil {
133+
// Add the group to the gitlab instance groups cache
134+
g.addGroup(group.FullPath, group)
135+
136+
if isSource {
137+
zap.L().Debug("Storing group in mirror mapping", zap.String("group", group.FullPath), zap.String("parentGroup", parentGroupPath))
138+
// Retrieve the corresponding group creation options from the mirror mapping
139+
groupCreationOptions, ok := mirrorMapping.Groups[parentGroupPath]
140+
if !ok {
141+
zap.L().Error("Group not found in mirror mapping", zap.String("group", parentGroupPath))
142+
return
143+
}
144+
145+
// Calculate the relative path between the group and the parent group
146+
relativePath, err := filepath.Rel(parentGroupPath, group.FullPath)
147+
if err != nil {
148+
zap.L().Error("Failed to calculate relative path for group", zap.String("group", group.FullPath), zap.String("parentGroup", parentGroupPath), zap.Error(err))
149+
return
150+
}
151+
152+
// Add the group to the mirror mapping
153+
mirrorMapping.AddGroup(group.FullPath, &utils.MirroringOptions{
154+
DestinationPath: filepath.Join(groupCreationOptions.DestinationPath, relativePath),
155+
CI_CD_Catalog: groupCreationOptions.CI_CD_Catalog,
156+
Issues: groupCreationOptions.Issues,
157+
MirrorTriggerBuilds: groupCreationOptions.MirrorTriggerBuilds,
158+
Visibility: groupCreationOptions.Visibility,
159+
MirrorReleases: groupCreationOptions.MirrorReleases,
160+
})
161+
}
162+
} else {
163+
zap.L().Error("Failed to store group in mirror mapping: nil group")
164+
}
165+
}
166+
167+
// checkPathMatchesFilters checks if the resources matches the filters
168+
// - either is in the projects map
169+
// - or path starts with any of the groups in the groups map
170+
//
171+
// In the case of a match with a group, it returns the group path
172+
func (g *GitlabInstance) checkPathMatchesFilters(resourcePath string, projectFilters *map[string]bool, groupFilters *map[string]bool) (string, bool) {
173+
zap.L().Debug("Checking if path matches filters", zap.String("path", resourcePath))
174+
if projectFilters != nil {
175+
if _, ok := (*projectFilters)[resourcePath]; ok {
176+
zap.L().Debug("Resource path matches project filter", zap.String("project", resourcePath))
177+
return "", true
178+
}
179+
}
180+
if groupFilters != nil {
181+
for groupPath := range *groupFilters {
182+
if strings.HasPrefix(resourcePath, groupPath) {
183+
zap.L().Debug("Resource path matches group filter", zap.String("resource", resourcePath), zap.String("group", groupPath))
184+
return groupPath, true
185+
}
186+
}
187+
}
188+
return "", false
189+
}
190+
191+
// fetchAll retrieves all projects and groups from the GitLab instance
192+
// that match the filters and stores them in the instance cache.
157193
func fetchAll(gitlabInstance *GitlabInstance, projectFilters map[string]bool, groupFilters map[string]bool, mirrorMapping *utils.MirrorMapping, isSource bool) error {
158194
wg := sync.WaitGroup{}
159195
errCh := make(chan error, 2)
@@ -176,6 +212,10 @@ func fetchAll(gitlabInstance *GitlabInstance, projectFilters map[string]bool, gr
176212
return utils.MergeErrors(errCh, 2)
177213
}
178214

215+
// getParentNamespaceID retrieves the parent namespace ID for a given project or group path.
216+
// It checks if the parent path is already in the instance groups cache.
217+
//
218+
// If not, it returns an error indicating that the parent group was not found.
179219
func (g *GitlabInstance) getParentNamespaceID(projectOrGroupPath string) (int, error) {
180220
parentGroupID := -1
181221
parentPath := filepath.Dir(projectOrGroupPath)

0 commit comments

Comments
 (0)