Skip to content

Commit 6852086

Browse files
CopilotfrjcompCopilot
authored
Fix misleading output when project has no .gitlab-ci.yml file (#395)
* Initial plan * Fix misleading output when project has no .gitlab-ci.yml file Co-authored-by: frjcomp <107982661+frjcomp@users.noreply.github.com> * fix error detection, when no gitlab ci yml exists * fmt * Fix e2e test for updated FetchCICDYml behavior Update TestGLCicdYaml_NoCiCdYaml to match new behavior: - Mock API now returns error in errors array instead of empty merged_yaml - Test expects 'most certainly not have a .gitlab-ci.yml file' message - Matches actual GitLab API behavior when project has no CI/CD config * Update src/pipeleak/pkg/gitlab/util/util.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: frjcomp <107982661+frjcomp@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent fed34e5 commit 6852086

13 files changed

Lines changed: 314 additions & 166 deletions

File tree

src/pipeleak/cmd/gitea/enum/enum.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ func NewEnumCmd() *cobra.Command {
1515
Run: Enum,
1616
}
1717

18-
1918
return enumCmd
2019
}
2120

src/pipeleak/cmd/gitea/secrets/secrets.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,5 @@ func NewSecretsCommand() *cobra.Command {
2626
},
2727
}
2828

29-
3029
return cmd
3130
}

src/pipeleak/cmd/gitea/variables/variables.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,5 @@ func NewVariablesCommand() *cobra.Command {
2626
},
2727
}
2828

29-
3029
return cmd
3130
}

src/pipeleak/pkg/bitbucket/url/builder_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package url
22

33
import (
4-
"testing"
54
"strings"
5+
"testing"
66

77
"github.com/stretchr/testify/assert"
88
)
Lines changed: 128 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -1,159 +1,159 @@
11
package docs
22

33
import (
4-
"os"
5-
"path/filepath"
6-
"testing"
4+
"os"
5+
"path/filepath"
6+
"testing"
77

8-
"github.com/spf13/cobra"
9-
"github.com/stretchr/testify/assert"
10-
"gopkg.in/yaml.v3"
8+
"github.com/spf13/cobra"
9+
"github.com/stretchr/testify/assert"
10+
"gopkg.in/yaml.v3"
1111
)
1212

1313
// Test getFileName logic for level handling and GroupID usage.
1414
func TestGetFileName(t *testing.T) {
15-
cmdNoGroup := &cobra.Command{Use: "scan", Short: "scan"}
16-
cmdGroup := &cobra.Command{Use: "enum", GroupID: "enumeration"}
15+
cmdNoGroup := &cobra.Command{Use: "scan", Short: "scan"}
16+
cmdGroup := &cobra.Command{Use: "enum", GroupID: "enumeration"}
1717

18-
assert.Equal(t, "scan.md", getFileName(cmdNoGroup, 1))
19-
assert.Equal(t, "enumeration.md", getFileName(cmdGroup, 1))
20-
assert.Equal(t, "scan.md", getFileName(cmdNoGroup, 2)) // level >1 ignores GroupID logic
18+
assert.Equal(t, "scan.md", getFileName(cmdNoGroup, 1))
19+
assert.Equal(t, "enumeration.md", getFileName(cmdGroup, 1))
20+
assert.Equal(t, "scan.md", getFileName(cmdNoGroup, 2)) // level >1 ignores GroupID logic
2121
}
2222

2323
// Test displayName title casing and GroupID preference.
2424
func TestDisplayName(t *testing.T) {
25-
cmdNoGroup := &cobra.Command{Use: "list"}
26-
cmdGroup := &cobra.Command{Use: "enum", GroupID: "gitlab pentest"}
25+
cmdNoGroup := &cobra.Command{Use: "list"}
26+
cmdGroup := &cobra.Command{Use: "enum", GroupID: "gitlab pentest"}
2727

28-
assert.Equal(t, "List", displayName(cmdNoGroup, 1))
29-
assert.Equal(t, "Gitlab Pentest", displayName(cmdGroup, 1)) // Title case applied
30-
assert.Equal(t, "Enum", displayName(cmdGroup, 2)) // deeper level uses Name
28+
assert.Equal(t, "List", displayName(cmdNoGroup, 1))
29+
assert.Equal(t, "Gitlab Pentest", displayName(cmdGroup, 1)) // Title case applied
30+
assert.Equal(t, "Enum", displayName(cmdGroup, 2)) // deeper level uses Name
3131
}
3232

3333
// buildNav should create index.md for commands with children and .md file for leaves.
3434
func TestBuildNav(t *testing.T) {
35-
root := &cobra.Command{Use: "pipeleak"}
36-
parent := &cobra.Command{Use: "alpha"}
37-
leaf := &cobra.Command{Use: "scan", Run: func(cmd *cobra.Command, args []string) {}}
38-
parent.AddCommand(leaf)
39-
root.AddCommand(parent)
40-
41-
entry := buildNav(root, 0, "")
42-
assert.Equal(t, "Pipeleak", entry.Label)
43-
assert.Len(t, entry.Children, 1)
44-
child := entry.Children[0]
45-
assert.Equal(t, "Alpha", child.Label)
46-
assert.Equal(t, filepath.ToSlash("pipeleak/alpha/index.md"), child.FilePath)
47-
assert.Len(t, child.Children, 1)
48-
grand := child.Children[0]
49-
assert.Equal(t, filepath.ToSlash("pipeleak/alpha/scan.md"), grand.FilePath)
35+
root := &cobra.Command{Use: "pipeleak"}
36+
parent := &cobra.Command{Use: "alpha"}
37+
leaf := &cobra.Command{Use: "scan", Run: func(cmd *cobra.Command, args []string) {}}
38+
parent.AddCommand(leaf)
39+
root.AddCommand(parent)
40+
41+
entry := buildNav(root, 0, "")
42+
assert.Equal(t, "Pipeleak", entry.Label)
43+
assert.Len(t, entry.Children, 1)
44+
child := entry.Children[0]
45+
assert.Equal(t, "Alpha", child.Label)
46+
assert.Equal(t, filepath.ToSlash("pipeleak/alpha/index.md"), child.FilePath)
47+
assert.Len(t, child.Children, 1)
48+
grand := child.Children[0]
49+
assert.Equal(t, filepath.ToSlash("pipeleak/alpha/scan.md"), grand.FilePath)
5050
}
5151

5252
// convertNavToYaml should trim pipeleak/ prefix and .md suffix.
5353
func TestConvertNavToYaml(t *testing.T) {
54-
entries := []*NavEntry{
55-
{Label: "Alpha", FilePath: filepath.ToSlash("pipeleak/alpha/index.md"), Children: []*NavEntry{}},
56-
{Label: "Beta", FilePath: filepath.ToSlash("pipeleak/beta/leaf.md"), Children: []*NavEntry{}},
57-
}
58-
yamlList := convertNavToYaml(entries)
59-
// Entries become maps with label key
60-
assert.Len(t, yamlList, 2)
61-
// Validate trimming and removal of extension
62-
alphaMap := yamlList[0]
63-
betaMap := yamlList[1]
64-
assert.Equal(t, "alpha/index", alphaMap["Alpha"])
65-
assert.Equal(t, "beta/leaf", betaMap["Beta"])
54+
entries := []*NavEntry{
55+
{Label: "Alpha", FilePath: filepath.ToSlash("pipeleak/alpha/index.md"), Children: []*NavEntry{}},
56+
{Label: "Beta", FilePath: filepath.ToSlash("pipeleak/beta/leaf.md"), Children: []*NavEntry{}},
57+
}
58+
yamlList := convertNavToYaml(entries)
59+
// Entries become maps with label key
60+
assert.Len(t, yamlList, 2)
61+
// Validate trimming and removal of extension
62+
alphaMap := yamlList[0]
63+
betaMap := yamlList[1]
64+
assert.Equal(t, "alpha/index", alphaMap["Alpha"])
65+
assert.Equal(t, "beta/leaf", betaMap["Beta"])
6666
}
6767

6868
// writeMkdocsYaml should create mkdocs.yml with expected keys and nav entries.
6969
func TestWriteMkdocsYaml(t *testing.T) {
70-
root := &cobra.Command{Use: "pipeleak"}
71-
alpha := &cobra.Command{Use: "alpha", Run: func(cmd *cobra.Command, args []string) {}}
72-
deepParent := &cobra.Command{Use: "beta"}
73-
deepChild := &cobra.Command{Use: "deep", Run: func(cmd *cobra.Command, args []string) {}}
74-
deepParent.AddCommand(deepChild)
75-
root.AddCommand(alpha)
76-
root.AddCommand(deepParent)
77-
78-
tmpDir := t.TempDir()
79-
// Change working directory to module root (src/pipeleak) so relative ../../docs resolves correctly
80-
wd, _ := os.Getwd()
81-
rootDir := filepath.Clean(filepath.Join(wd, "..", ".."))
82-
old := wd
83-
if err := os.Chdir(rootDir); err != nil {
84-
t.Fatalf("failed to chdir to root: %v", err)
85-
}
86-
defer func() { _ = os.Chdir(old) }()
87-
88-
err := writeMkdocsYaml(root, tmpDir, false)
89-
assert.NoError(t, err)
90-
91-
data, err := os.ReadFile(filepath.Join(tmpDir, "mkdocs.yml"))
92-
assert.NoError(t, err)
93-
94-
var parsed map[string]interface{}
95-
err = yaml.Unmarshal(data, &parsed)
96-
assert.NoError(t, err)
97-
98-
// Basic keys exist
99-
assert.Equal(t, "Pipeleak - Pipeline Secrets Scanner", parsed["site_name"])
100-
assert.Equal(t, "pipeleak", parsed["docs_dir"])
101-
102-
// Nav structure assertions
103-
navAny, ok := parsed["nav"].([]interface{})
104-
assert.True(t, ok)
105-
assert.Equal(t, 4, len(navAny)) // intro, methodology, alpha, beta
106-
107-
// Introduction entry first
108-
introMap, ok := navAny[0].(map[string]interface{})
109-
assert.True(t, ok)
110-
assert.Contains(t, introMap, "Introduction")
111-
assert.Equal(t, "/introduction/getting_started/", introMap["Introduction"])
112-
113-
// Methodology second
114-
methMap, ok := navAny[1].(map[string]interface{})
115-
assert.True(t, ok)
116-
assert.Contains(t, methMap, "Methodology")
117-
118-
// Command entries appear after methodology
119-
foundAlpha := false
120-
foundBeta := false
121-
for _, item := range navAny[2:] {
122-
if m, ok := item.(map[string]interface{}); ok {
123-
if _, ok := m["Alpha"]; ok {
124-
foundAlpha = true
125-
}
126-
if _, ok := m["Beta"]; ok {
127-
foundBeta = true
128-
}
129-
}
130-
}
131-
assert.True(t, foundAlpha)
132-
assert.True(t, foundBeta)
70+
root := &cobra.Command{Use: "pipeleak"}
71+
alpha := &cobra.Command{Use: "alpha", Run: func(cmd *cobra.Command, args []string) {}}
72+
deepParent := &cobra.Command{Use: "beta"}
73+
deepChild := &cobra.Command{Use: "deep", Run: func(cmd *cobra.Command, args []string) {}}
74+
deepParent.AddCommand(deepChild)
75+
root.AddCommand(alpha)
76+
root.AddCommand(deepParent)
77+
78+
tmpDir := t.TempDir()
79+
// Change working directory to module root (src/pipeleak) so relative ../../docs resolves correctly
80+
wd, _ := os.Getwd()
81+
rootDir := filepath.Clean(filepath.Join(wd, "..", ".."))
82+
old := wd
83+
if err := os.Chdir(rootDir); err != nil {
84+
t.Fatalf("failed to chdir to root: %v", err)
85+
}
86+
defer func() { _ = os.Chdir(old) }()
87+
88+
err := writeMkdocsYaml(root, tmpDir, false)
89+
assert.NoError(t, err)
90+
91+
data, err := os.ReadFile(filepath.Join(tmpDir, "mkdocs.yml"))
92+
assert.NoError(t, err)
93+
94+
var parsed map[string]interface{}
95+
err = yaml.Unmarshal(data, &parsed)
96+
assert.NoError(t, err)
97+
98+
// Basic keys exist
99+
assert.Equal(t, "Pipeleak - Pipeline Secrets Scanner", parsed["site_name"])
100+
assert.Equal(t, "pipeleak", parsed["docs_dir"])
101+
102+
// Nav structure assertions
103+
navAny, ok := parsed["nav"].([]interface{})
104+
assert.True(t, ok)
105+
assert.Equal(t, 4, len(navAny)) // intro, methodology, alpha, beta
106+
107+
// Introduction entry first
108+
introMap, ok := navAny[0].(map[string]interface{})
109+
assert.True(t, ok)
110+
assert.Contains(t, introMap, "Introduction")
111+
assert.Equal(t, "/introduction/getting_started/", introMap["Introduction"])
112+
113+
// Methodology second
114+
methMap, ok := navAny[1].(map[string]interface{})
115+
assert.True(t, ok)
116+
assert.Contains(t, methMap, "Methodology")
117+
118+
// Command entries appear after methodology
119+
foundAlpha := false
120+
foundBeta := false
121+
for _, item := range navAny[2:] {
122+
if m, ok := item.(map[string]interface{}); ok {
123+
if _, ok := m["Alpha"]; ok {
124+
foundAlpha = true
125+
}
126+
if _, ok := m["Beta"]; ok {
127+
foundBeta = true
128+
}
129+
}
130+
}
131+
assert.True(t, foundAlpha)
132+
assert.True(t, foundBeta)
133133
}
134134

135135
// writeMkdocsYaml with GithubPages should prefix nav paths.
136136
func TestWriteMkdocsYaml_GithubPagesPrefix(t *testing.T) {
137-
root := &cobra.Command{Use: "pipeleak"}
138-
root.AddCommand(&cobra.Command{Use: "alpha", Run: func(cmd *cobra.Command, args []string) {}})
139-
140-
tmpDir := t.TempDir()
141-
wd, _ := os.Getwd()
142-
rootDir := filepath.Clean(filepath.Join(wd, "..", ".."))
143-
old := wd
144-
if err := os.Chdir(rootDir); err != nil {
145-
t.Fatalf("failed to chdir to root: %v", err)
146-
}
147-
defer func() { _ = os.Chdir(old) }()
148-
149-
err := writeMkdocsYaml(root, tmpDir, true)
150-
assert.NoError(t, err)
151-
data, err := os.ReadFile(filepath.Join(tmpDir, "mkdocs.yml"))
152-
assert.NoError(t, err)
153-
var parsed map[string]interface{}
154-
err = yaml.Unmarshal(data, &parsed)
155-
assert.NoError(t, err)
156-
navAny := parsed["nav"].([]interface{})
157-
introMap := navAny[0].(map[string]interface{})
158-
assert.Equal(t, "/pipeleak/introduction/getting_started/", introMap["Introduction"])
137+
root := &cobra.Command{Use: "pipeleak"}
138+
root.AddCommand(&cobra.Command{Use: "alpha", Run: func(cmd *cobra.Command, args []string) {}})
139+
140+
tmpDir := t.TempDir()
141+
wd, _ := os.Getwd()
142+
rootDir := filepath.Clean(filepath.Join(wd, "..", ".."))
143+
old := wd
144+
if err := os.Chdir(rootDir); err != nil {
145+
t.Fatalf("failed to chdir to root: %v", err)
146+
}
147+
defer func() { _ = os.Chdir(old) }()
148+
149+
err := writeMkdocsYaml(root, tmpDir, true)
150+
assert.NoError(t, err)
151+
data, err := os.ReadFile(filepath.Join(tmpDir, "mkdocs.yml"))
152+
assert.NoError(t, err)
153+
var parsed map[string]interface{}
154+
err = yaml.Unmarshal(data, &parsed)
155+
assert.NoError(t, err)
156+
navAny := parsed["nav"].([]interface{})
157+
introMap := navAny[0].(map[string]interface{})
158+
assert.Equal(t, "/pipeleak/introduction/getting_started/", introMap["Introduction"])
159159
}

src/pipeleak/pkg/gitlab/renovate/autodiscovery/autodiscovery.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package renovate
22

33
import (
4-
"github.com/CompassSecurity/pipeleak/pkg/gitlab/util"
54
"github.com/CompassSecurity/pipeleak/pkg/format"
5+
"github.com/CompassSecurity/pipeleak/pkg/gitlab/util"
66
"github.com/rs/zerolog/log"
77
gogitlab "gitlab.com/gitlab-org/api/client-go"
88
)

src/pipeleak/pkg/gitlab/renovate/enum/enum.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import (
1111
"regexp"
1212
"strings"
1313

14-
"github.com/CompassSecurity/pipeleak/pkg/gitlab/util"
1514
"github.com/CompassSecurity/pipeleak/pkg/format"
15+
"github.com/CompassSecurity/pipeleak/pkg/gitlab/util"
1616
"github.com/CompassSecurity/pipeleak/pkg/httpclient"
1717
"github.com/rs/zerolog/log"
1818
"github.com/yosuke-furukawa/json5/encoding/json5"

src/pipeleak/pkg/gitlab/runners/list/list_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -181,9 +181,9 @@ func TestFormatRunnerInfo(t *testing.T) {
181181
return
182182
}
183183

184-
require.NotNil(t, info)
185-
assert.Equal(t, int(tt.details.ID), info.ID)
186-
assert.Equal(t, tt.details.Name, info.Name)
184+
require.NotNil(t, info)
185+
assert.Equal(t, int(tt.details.ID), info.ID)
186+
assert.Equal(t, tt.details.Name, info.Name)
187187
assert.Equal(t, tt.details.Description, info.Description)
188188
assert.Equal(t, tt.details.RunnerType, info.Type)
189189
assert.Equal(t, tt.details.Paused, info.Paused)

src/pipeleak/pkg/gitlab/schedule/schedule.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ func ListPipelineSchedules(git *gitlab.Client, project *gitlab.Project) {
6565

6666
for _, schedule := range schedules {
6767
scheduleWithVars, _, err := git.PipelineSchedules.GetPipelineSchedule(project.ID, schedule.ID)
68-
if err != nil {
68+
if err != nil {
6969
log.Error().Stack().Err(err).Int64("project", project.ID).Msg("Failed fetching schedule variables")
7070
continue
7171
}

src/pipeleak/pkg/gitlab/util/util.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,15 @@ func FetchCICDYml(git *gitlab.Client, pid int64) (string, error) {
207207
return "", err
208208
}
209209

210+
for _, msg := range res.Errors {
211+
log.Debug().Str("type", msg).Msg("Validation error of gitlab-ci.yml in project")
212+
213+
// API does not distinguish between missing file and actual errors in the YAML
214+
if strings.Contains(msg, "Please provide content of") {
215+
return "", errors.New("project does most certainly not have a .gitlab-ci.yml file")
216+
}
217+
}
218+
210219
if len(res.Errors) > 0 {
211220
return "", errors.New(strings.Join(res.Errors, ", "))
212221
}

0 commit comments

Comments
 (0)