Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name: lint-test

on:
workflow_dispatch:
push:
pull_request:

permissions:
contents: read

jobs:
golangci-lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: stable
- run: go mod tidy
- name: golangci-lint
uses: golangci/golangci-lint-action@v7
with:
version: v2.0
continue-on-error: true


gosec:
runs-on: ubuntu-latest
env:
GO111MODULE: on
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.23.7'
- run: go mod tidy
- name: Run Gosec Security Scanner
uses: securego/gosec@master
with:
args: ./...
continue-on-error: true


gotestsum:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.23.7'
- run: go mod tidy
- name: go-test
run: |
go test -v ./...
20 changes: 12 additions & 8 deletions mirroring/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,12 @@ func (g *GitlabInstance) fetchProjects(projectFilters *map[string]bool, groupFil
}

// Add the project to the mirror mapping
mirrorMapping.AddProject(project.PathWithNamespace, &utils.ProjectMirroringOptions{
CI_CD_Catalog: groupCreationOptions.CI_CD_Catalog,
Issues: groupCreationOptions.Issues,
DestinationPath: filepath.Join(groupCreationOptions.DestinationPath, relativePath),
mirrorMapping.AddProject(project.PathWithNamespace, &utils.MirroringOptions{
DestinationPath: filepath.Join(groupCreationOptions.DestinationPath, relativePath),
CI_CD_Catalog: groupCreationOptions.CI_CD_Catalog,
Issues: groupCreationOptions.Issues,
MirrorTriggerBuilds: groupCreationOptions.MirrorTriggerBuilds,
Visibility: groupCreationOptions.Visibility,
})
}
break
Expand Down Expand Up @@ -143,10 +145,12 @@ func (g *GitlabInstance) fetchGroups(groupFilters *map[string]bool, mirrorMappin
}

// Add the group to the mirror mapping
mirrorMapping.AddGroup(group.FullPath, &utils.GroupMirroringOptions{
CI_CD_Catalog: groupCreationOptions.CI_CD_Catalog,
Issues: groupCreationOptions.Issues,
DestinationPath: filepath.Join(groupCreationOptions.DestinationPath, relativePath),
mirrorMapping.AddGroup(group.FullPath, &utils.MirroringOptions{
DestinationPath: filepath.Join(groupCreationOptions.DestinationPath, relativePath),
CI_CD_Catalog: groupCreationOptions.CI_CD_Catalog,
Issues: groupCreationOptions.Issues,
MirrorTriggerBuilds: groupCreationOptions.MirrorTriggerBuilds,
Visibility: groupCreationOptions.Visibility,
})
}
break
Expand Down
12 changes: 6 additions & 6 deletions mirroring/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ func TestProcessFilters(t *testing.T) {
{
name: "EmptyMirrorMapping",
mirrorMapping: &utils.MirrorMapping{
Projects: make(map[string]*utils.ProjectMirroringOptions),
Groups: make(map[string]*utils.GroupMirroringOptions),
Projects: make(map[string]*utils.MirroringOptions),
Groups: make(map[string]*utils.MirroringOptions),
},
expectedSourceProjectFilters: map[string]bool{},
expectedSourceGroupFilters: map[string]bool{},
Expand All @@ -30,14 +30,14 @@ func TestProcessFilters(t *testing.T) {
{
name: "SingleProjectAndGroup",
mirrorMapping: &utils.MirrorMapping{
Projects: map[string]*utils.ProjectMirroringOptions{
Projects: map[string]*utils.MirroringOptions{
"sourceProject": {
DestinationPath: "destinationGroupPath/destinationProjectPath",
CI_CD_Catalog: true,
Issues: true,
},
},
Groups: map[string]*utils.GroupMirroringOptions{
Groups: map[string]*utils.MirroringOptions{
"sourceGroup": {
DestinationPath: "destinationGroupPath",
CI_CD_Catalog: true,
Expand All @@ -61,7 +61,7 @@ func TestProcessFilters(t *testing.T) {
{
name: "MultipleProjectsAndGroups",
mirrorMapping: &utils.MirrorMapping{
Projects: map[string]*utils.ProjectMirroringOptions{
Projects: map[string]*utils.MirroringOptions{
"sourceProject1": {
DestinationPath: "destinationGroupPath1/destinationProjectPath1",
CI_CD_Catalog: true,
Expand All @@ -73,7 +73,7 @@ func TestProcessFilters(t *testing.T) {
Issues: false,
},
},
Groups: map[string]*utils.GroupMirroringOptions{
Groups: map[string]*utils.MirroringOptions{
"sourceGroup1": {
DestinationPath: "destinationGroupPath3",
CI_CD_Catalog: true,
Expand Down
7 changes: 4 additions & 3 deletions mirroring/post.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@
utils.LogVerbosef("Mirroring group from source %s to destination %s", sourceGroupPath, destinationGroupPath)
sourceGroup := sourceGitlab.Groups[sourceGroupPath]
if sourceGroup == nil {
errorChan <- fmt.Errorf("Group %s not found in destination GitLab instance (internal error, please review script)", sourceGroupPath)

Check failure on line 35 in mirroring/post.go

View workflow job for this annotation

GitHub Actions / golangci-lint

ST1005: error strings should not be capitalized (staticcheck)

Check failure on line 35 in mirroring/post.go

View workflow job for this annotation

GitHub Actions / golangci-lint

ST1005: error strings should not be capitalized (staticcheck)
continue
}

// Retrieve the corresponding group creation options from the mirror mapping
groupCreationOptions, ok := mirrorMapping.Groups[sourceGroupPath]
if !ok {
errorChan <- fmt.Errorf("Source Group %s not found in mirror mapping (internal error, please review script)", sourceGroupPath)

Check failure on line 42 in mirroring/post.go

View workflow job for this annotation

GitHub Actions / golangci-lint

ST1005: error strings should not be capitalized (staticcheck)

Check failure on line 42 in mirroring/post.go

View workflow job for this annotation

GitHub Actions / golangci-lint

ST1005: error strings should not be capitalized (staticcheck)
continue
}

Expand All @@ -52,7 +52,7 @@
utils.LogVerbosef("Creating group %s in destination GitLab instance", destinationGroupPath)
destinationGroup, err = destinationGitlab.createGroupFromSource(sourceGroup, groupCreationOptions)
if err != nil {
errorChan <- fmt.Errorf("Failed to create group %s in destination GitLab instance: %s", destinationGroupPath, err)

Check failure on line 55 in mirroring/post.go

View workflow job for this annotation

GitHub Actions / golangci-lint

ST1005: error strings should not be capitalized (staticcheck)

Check failure on line 55 in mirroring/post.go

View workflow job for this annotation

GitHub Actions / golangci-lint

ST1005: error strings should not be capitalized (staticcheck)
continue
} else {
err = sourceGitlab.copyGroupAvatar(destinationGitlab, destinationGroup, sourceGroup)
Expand Down Expand Up @@ -143,15 +143,16 @@
return utils.MergeErrors(errorChan, 2)
}

func (g *GitlabInstance) createProjectFromSource(sourceProject *gitlab.Project, copyOptions *utils.ProjectMirroringOptions) (*gitlab.Project, error) {
func (g *GitlabInstance) createProjectFromSource(sourceProject *gitlab.Project, copyOptions *utils.MirroringOptions) (*gitlab.Project, error) {
projectCreationArgs := &gitlab.CreateProjectOptions{
Name: &sourceProject.Name,
Path: &sourceProject.Path,
DefaultBranch: &sourceProject.DefaultBranch,
Description: &sourceProject.Description,
MirrorTriggerBuilds: gitlab.Ptr(true),
MirrorTriggerBuilds: gitlab.Ptr(copyOptions.MirrorTriggerBuilds),
Mirror: gitlab.Ptr(true),
Topics: &sourceProject.Topics,
Visibility: gitlab.Ptr(gitlab.VisibilityValue(copyOptions.Visibility)),
}

utils.LogVerbosef("Retrieving project namespace ID for %s", copyOptions.DestinationPath)
Expand All @@ -173,7 +174,7 @@
return destinationProject, nil
}

func (g *GitlabInstance) createGroupFromSource(sourceGroup *gitlab.Group, copyOptions *utils.GroupMirroringOptions) (*gitlab.Group, error) {
func (g *GitlabInstance) createGroupFromSource(sourceGroup *gitlab.Group, copyOptions *utils.MirroringOptions) (*gitlab.Group, error) {
groupCreationArgs := &gitlab.CreateGroupOptions{
Name: &sourceGroup.Name,
Path: &sourceGroup.Path,
Expand Down
2 changes: 1 addition & 1 deletion mirroring/put.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func (g *GitlabInstance) copyGroupAvatar(destinationGitlabInstance *GitlabInstan
return nil
}

func (g *GitlabInstance) updateProjectFromSource(sourceGitlab *GitlabInstance, sourceProject *gitlab.Project, destinationProject *gitlab.Project, copyOptions *utils.ProjectMirroringOptions) error {
func (g *GitlabInstance) updateProjectFromSource(sourceGitlab *GitlabInstance, sourceProject *gitlab.Project, destinationProject *gitlab.Project, copyOptions *utils.MirroringOptions) error {
wg := sync.WaitGroup{}
maxErrors := 2
if copyOptions.CI_CD_Catalog {
Expand Down
62 changes: 40 additions & 22 deletions utils/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
"os"
"strings"
"sync"

gitlab "gitlab.com/gitlab-org/api/client-go"
)

// ParserArgs defines the command line arguments
Expand Down Expand Up @@ -42,21 +44,12 @@
// - destination_url: the URL of the destination GitLab instance
// - ci_cd_catalog: whether to add the project to the CI/CD catalog
// - issues: whether to mirror the issues
type ProjectMirroringOptions struct {
DestinationPath string `json:"destination_path"`
CI_CD_Catalog bool `json:"ci_cd_catalog"`
Issues bool `json:"issues"`
}

// GroupMirrorOptions defines how the group should be mirrored
// to the destination GitLab instance
// - destination_url: the URL of the destination GitLab instance
// - ci_cd_catalog: whether to add the group to the CI/CD catalog
// - issues: whether to mirror the issues
type GroupMirroringOptions struct {
DestinationPath string `json:"destination_path"`
CI_CD_Catalog bool `json:"ci_cd_catalog"`
Issues bool `json:"issues"`
type MirroringOptions struct {
DestinationPath string `json:"destination_path"`
CI_CD_Catalog bool `json:"ci_cd_catalog"`
Issues bool `json:"issues"`
MirrorTriggerBuilds bool `json:"mirror_trigger_builds"`
Visibility string `json:"visibility"`
}

// MirrorMapping defines the mapping of projects and groups
Expand All @@ -65,19 +58,19 @@
// - projects: a map of project names to their mirroring options
// - groups: a map of group names to their mirroring options
type MirrorMapping struct {
Projects map[string]*ProjectMirroringOptions `json:"projects"`
Groups map[string]*GroupMirroringOptions `json:"groups"`
Projects map[string]*MirroringOptions `json:"projects"`
Groups map[string]*MirroringOptions `json:"groups"`
muProjects sync.Mutex
muGroups sync.Mutex
}

func (m *MirrorMapping) AddProject(project string, options *ProjectMirroringOptions) {
func (m *MirrorMapping) AddProject(project string, options *MirroringOptions) {
m.muProjects.Lock()
defer m.muProjects.Unlock()
m.Projects[project] = options
}

func (m *MirrorMapping) AddGroup(group string, options *GroupMirroringOptions) {
func (m *MirrorMapping) AddGroup(group string, options *MirroringOptions) {
m.muGroups.Lock()
defer m.muGroups.Unlock()
m.Groups[group] = options
Expand All @@ -88,8 +81,8 @@
// It returns the mapping and an error if any
func OpenMirrorMapping(path string) (*MirrorMapping, error) {
mapping := &MirrorMapping{
Projects: make(map[string]*ProjectMirroringOptions),
Groups: make(map[string]*GroupMirroringOptions),
Projects: make(map[string]*MirroringOptions),
Groups: make(map[string]*MirroringOptions),
}

// Read the file
Expand All @@ -97,7 +90,7 @@
if err != nil {
return nil, err
}
defer file.Close()

Check failure on line 93 in utils/types.go

View workflow job for this annotation

GitHub Actions / golangci-lint

Error return value of `file.Close` is not checked (errcheck)

Check failure on line 93 in utils/types.go

View workflow job for this annotation

GitHub Actions / golangci-lint

Error return value of `file.Close` is not checked (errcheck)

// Decode the JSON
decoder := json.NewDecoder(file)
Expand All @@ -115,7 +108,7 @@
}

func (m *MirrorMapping) check() error {
errChan := make(chan error, 3*(len(m.Projects)+len(m.Groups)+1))
errChan := make(chan error, 4*(len(m.Projects)+len(m.Groups))+1)
// Check if the mapping is valid
if len(m.Projects) == 0 && len(m.Groups) == 0 {
errChan <- errors.New("no projects or groups defined in the mapping")
Expand All @@ -134,6 +127,11 @@
} else if strings.Count(options.DestinationPath, "/") < 1 {
errChan <- fmt.Errorf("invalid project destination path (must be in a namespace): %s", options.DestinationPath)
}
visibilityString := strings.TrimSpace(string(options.Visibility))
if visibilityString != "" && !checkVisibility(visibilityString) {
errChan <- fmt.Errorf("invalid project visibility: %s", string(options.Visibility))
options.Visibility = string(gitlab.PublicVisibility)
}
}

// Check if the groups are valid
Expand All @@ -147,11 +145,31 @@
if strings.HasPrefix(options.DestinationPath, "/") || strings.HasSuffix(options.DestinationPath, "/") {
errChan <- fmt.Errorf("invalid destination path (must not start or end with /): %s", options.DestinationPath)
}
visibilityString := strings.TrimSpace(string(options.Visibility))
if visibilityString != "" && !checkVisibility(visibilityString) {
errChan <- fmt.Errorf("invalid group visibility: %s", string(options.Visibility))
options.Visibility = string(gitlab.PublicVisibility)
}
}
close(errChan)
return MergeErrors(errChan, 2)
}

func checkVisibility(visibility string) bool {
var valid bool
switch visibility {
case string(gitlab.PublicVisibility):
valid = true
case string(gitlab.InternalVisibility):
valid = true
case string(gitlab.PrivateVisibility):
valid = true
default:
valid = false
}
return valid
}

// GraphQLClient is a client for sending GraphQL requests to GitLab
type GraphQLClient struct {
token string
Expand Down Expand Up @@ -197,7 +215,7 @@
if err != nil {
return "", err
}
defer resp.Body.Close()

Check failure on line 218 in utils/types.go

View workflow job for this annotation

GitHub Actions / golangci-lint

Error return value of `resp.Body.Close` is not checked (errcheck)

Check failure on line 218 in utils/types.go

View workflow job for this annotation

GitHub Actions / golangci-lint

Error return value of `resp.Body.Close` is not checked (errcheck)
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("GraphQL request failed with status: %s", resp.Status)
}
Expand Down
Loading
Loading