Skip to content
This repository was archived by the owner on Jul 18, 2025. It is now read-only.

Commit e1d4364

Browse files
committed
Add relative path rule
Signed-off-by: Djordje Lukic <[email protected]>
1 parent 4f5d946 commit e1d4364

File tree

2 files changed

+162
-0
lines changed

2 files changed

+162
-0
lines changed
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package rules
2+
3+
import (
4+
"fmt"
5+
"path/filepath"
6+
"regexp"
7+
"strings"
8+
)
9+
10+
type relativePathRule struct {
11+
volumes map[string]interface{}
12+
service string
13+
}
14+
15+
func NewRelativePathRule() Rule {
16+
return &relativePathRule{
17+
volumes: map[string]interface{}{},
18+
}
19+
}
20+
21+
func (s *relativePathRule) Collect(parent string, key string, value interface{}) {
22+
if parent == "volumes" {
23+
s.volumes[key] = value
24+
}
25+
}
26+
27+
func (s *relativePathRule) Accept(parent string, key string) bool {
28+
if parent == "services" {
29+
s.service = key
30+
}
31+
return regexp.MustCompile("services.(.*).volumes").MatchString(parent + "." + key)
32+
}
33+
34+
func (s *relativePathRule) Validate(value interface{}) []error {
35+
if m, ok := value.(map[string]interface{}); ok {
36+
src, ok := m["source"]
37+
if !ok {
38+
return []error{fmt.Errorf("invalid volume in service %q", s.service)}
39+
}
40+
_, volumeExists := s.volumes[src.(string)]
41+
if !filepath.IsAbs(src.(string)) && !volumeExists {
42+
return []error{fmt.Errorf("1 can't use relative path as volume source (%q) in service %q", src, s.service)}
43+
}
44+
}
45+
46+
if m, ok := value.([]interface{}); ok {
47+
errs := []error{}
48+
for _, p := range m {
49+
str, ok := p.(string)
50+
if !ok {
51+
errs = append(errs, fmt.Errorf("invalid volume in service %q", s.service))
52+
continue
53+
}
54+
55+
parts := strings.Split(str, ":")
56+
if len(parts) != 2 {
57+
errs = append(errs, fmt.Errorf("invalid volume definition (%q) in service %q", str, s.service))
58+
continue
59+
}
60+
61+
volumeName := parts[0]
62+
_, volumeExists := s.volumes[volumeName]
63+
if !filepath.IsAbs(volumeName) && !volumeExists {
64+
errs = append(errs, fmt.Errorf("can't use relative path as volume source (%q) in service %q", str, s.service))
65+
continue
66+
}
67+
}
68+
69+
if len(errs) > 0 {
70+
return errs
71+
}
72+
}
73+
return nil
74+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package rules
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"gotest.tools/assert"
8+
)
9+
10+
func TestRelativePathRule(t *testing.T) {
11+
s := NewRelativePathRule()
12+
13+
t.Run("should accept only volume paths", func(t *testing.T) {
14+
assert.Equal(t, s.Accept("services", "test"), false)
15+
assert.Equal(t, s.Accept("services.test.volumes", "my_volume"), true)
16+
assert.Equal(t, s.Accept("services.test", "volumes"), true)
17+
})
18+
19+
t.Run("should validate named volume paths", func(t *testing.T) {
20+
input := map[string]string{
21+
"toto": "tata",
22+
}
23+
errs := s.Validate(input)
24+
assert.Equal(t, len(errs), 0)
25+
})
26+
27+
t.Run("should return error if short syntax volume path is relative", func(t *testing.T) {
28+
input := []interface{}{
29+
"./foo:/data",
30+
}
31+
errs := s.Validate(input)
32+
assert.Equal(t, len(errs), 1)
33+
34+
assert.ErrorContains(t, errs[0], `can't use relative path as volume source ("./foo:/data") in service "test"`)
35+
})
36+
37+
t.Run("should return error if the volume definition is invalid", func(t *testing.T) {
38+
input := []interface{}{
39+
"foo",
40+
}
41+
errs := s.Validate(input)
42+
assert.Equal(t, len(errs), 1)
43+
44+
assert.ErrorContains(t, errs[0], `invalid volume definition ("foo") in service "test"`)
45+
})
46+
47+
t.Run("should return all volume errors", func(t *testing.T) {
48+
input := []interface{}{
49+
"./foo:/data1",
50+
"./bar:/data2",
51+
}
52+
errs := s.Validate(input)
53+
assert.Equal(t, len(errs), 2)
54+
55+
assert.ErrorContains(t, errs[0], `can't use relative path as volume source ("./foo:/data1") in service "test"`)
56+
assert.ErrorContains(t, errs[1], `can't use relative path as volume source ("./bar:/data2") in service "test"`)
57+
})
58+
59+
// When a volume is in short syntax, the list of volumes must be strings
60+
t.Run("shoud return error if volume list is invalid", func(t *testing.T) {
61+
input := []interface{}{
62+
1,
63+
}
64+
errs := s.Validate(input)
65+
fmt.Println(errs)
66+
assert.Equal(t, len(errs), 1)
67+
68+
assert.ErrorContains(t, errs[0], `invalid volume in service "test"`)
69+
})
70+
71+
t.Run("should return error if long syntax volume path is relative", func(t *testing.T) {
72+
input := map[string]interface{}{
73+
"source": "./foo",
74+
}
75+
errs := s.Validate(input)
76+
assert.Equal(t, len(errs), 1)
77+
78+
assert.ErrorContains(t, errs[0], `can't use relative path as volume source ("./foo") in service "test"`)
79+
})
80+
81+
t.Run("shoud return error if volume map is invalid", func(t *testing.T) {
82+
input := map[string]interface{}{}
83+
errs := s.Validate(input)
84+
assert.Equal(t, len(errs), 1)
85+
86+
assert.ErrorContains(t, errs[0], `invalid volume in service "test"`)
87+
})
88+
}

0 commit comments

Comments
 (0)