Skip to content

Commit 0685271

Browse files
authored
Follow symlinks and ignore target directories. (#3137)
* Follow symlinks and ignore target directories. Signed-off-by: Peter Štibraný <[email protected]> * CHANGELOG.md Signed-off-by: Peter Štibraný <[email protected]>
1 parent 195bac9 commit 0685271

File tree

3 files changed

+58
-16
lines changed

3 files changed

+58
-16
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
* [ENHANCEMENT] Add `DELETE api/v1/rules/{namespace}` to the Ruler. It allows all the rule groups of a namespace to be deleted. #3120
5454
* [ENHANCEMENT] Experimental Delete Series: Retry processing of Delete requests during failures. #2926
5555
* [ENHANCEMENT] Modules included in "All" target are now visible in output of `-modules` CLI flag. #3155
56+
* [BUGFIX] Ruler: when loading rules from "local" storage, check for directory after resolving symlink. #3137
5657
* [BUGFIX] Query-frontend: Fixed rounding for incoming query timestamps, to be 100% Prometheus compatible. #2990
5758
* [BUGFIX] Querier: Merge results from chunks and blocks ingesters when using streaming of results. #3013
5859
* [BUGFIX] Querier: query /series from ingesters regardless the `-querier.query-ingesters-within` setting. #3035

pkg/ruler/rules/local/local.go

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"flag"
66
"io/ioutil"
7+
"os"
78
"path/filepath"
89

910
"github.com/pkg/errors"
@@ -48,16 +49,27 @@ func (l *Client) ListAllRuleGroups(ctx context.Context) (map[string]rules.RuleGr
4849
}
4950

5051
for _, info := range infos {
52+
// After resolving link, info.Name() may be different than user, so keep original name.
53+
user := info.Name()
54+
55+
if info.Mode()&os.ModeSymlink != 0 {
56+
// ioutil.ReadDir only returns result of LStat. Calling Stat resolves symlink.
57+
info, err = os.Stat(filepath.Join(root, info.Name()))
58+
if err != nil {
59+
return nil, err
60+
}
61+
}
62+
5163
if !info.IsDir() {
5264
continue
5365
}
5466

55-
list, err := l.listAllRulesGroupsForUser(ctx, info.Name())
67+
list, err := l.listAllRulesGroupsForUser(ctx, user)
5668
if err != nil {
57-
return nil, errors.Wrapf(err, "failed to list rule groups for user %s", info.Name())
69+
return nil, errors.Wrapf(err, "failed to list rule groups for user %s", user)
5870
}
5971

60-
lists[info.Name()] = list
72+
lists[user] = list
6173
}
6274

6375
return lists, nil
@@ -102,13 +114,24 @@ func (l *Client) listAllRulesGroupsForUser(ctx context.Context, userID string) (
102114
}
103115

104116
for _, info := range infos {
117+
// After resolving link, info.Name() may be different than namespace, so keep original name.
118+
namespace := info.Name()
119+
120+
if info.Mode()&os.ModeSymlink != 0 {
121+
// ioutil.ReadDir only returns result of LStat. Calling Stat resolves symlink.
122+
info, err = os.Stat(filepath.Join(root, info.Name()))
123+
if err != nil {
124+
return nil, err
125+
}
126+
}
127+
105128
if info.IsDir() {
106129
continue
107130
}
108131

109-
list, err := l.listAllRulesGroupsForUserAndNamespace(ctx, userID, info.Name())
132+
list, err := l.listAllRulesGroupsForUserAndNamespace(ctx, userID, namespace)
110133
if err != nil {
111-
return nil, errors.Wrapf(err, "failed to list rule group for user %s and namespace %s", userID, info.Name())
134+
return nil, errors.Wrapf(err, "failed to list rule group for user %s and namespace %s", userID, namespace)
112135
}
113136

114137
allLists = append(allLists, list...)

pkg/ruler/rules/local/local_test.go

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@ import (
1717
)
1818

1919
func TestClient_ListAllRuleGroups(t *testing.T) {
20-
user := "user"
21-
namespace := "ns"
20+
user1 := "user"
21+
user2 := "second-user"
22+
23+
namespace1 := "ns"
24+
namespace2 := "z-another" // This test relies on the fact that ioutil.ReadDir() returns files sorted by name.
2225

2326
dir, err := ioutil.TempDir("", "")
2427
require.NoError(t, err)
@@ -42,10 +45,25 @@ func TestClient_ListAllRuleGroups(t *testing.T) {
4245
b, err := yaml.Marshal(ruleGroups)
4346
require.NoError(t, err)
4447

45-
err = os.MkdirAll(path.Join(dir, user), 0777)
48+
err = os.MkdirAll(path.Join(dir, user1), 0777)
49+
require.NoError(t, err)
50+
51+
// Link second user to first.
52+
err = os.Symlink(user1, path.Join(dir, user2))
53+
require.NoError(t, err)
54+
55+
err = ioutil.WriteFile(path.Join(dir, user1, namespace1), b, 0777)
4656
require.NoError(t, err)
4757

48-
err = ioutil.WriteFile(path.Join(dir, user, namespace), b, 0777)
58+
const ignoredDir = "ignored-dir"
59+
err = os.Mkdir(path.Join(dir, user1, ignoredDir), os.ModeDir|0644)
60+
require.NoError(t, err)
61+
62+
err = os.Symlink(ignoredDir, path.Join(dir, user1, "link-to-dir"))
63+
require.NoError(t, err)
64+
65+
// Link second namespace to first.
66+
err = os.Symlink(namespace1, path.Join(dir, user1, namespace2))
4967
require.NoError(t, err)
5068

5169
client, err := NewLocalRulesClient(Config{
@@ -57,13 +75,13 @@ func TestClient_ListAllRuleGroups(t *testing.T) {
5775
userMap, err := client.ListAllRuleGroups(ctx)
5876
require.NoError(t, err)
5977

60-
actual, found := userMap[user]
61-
require.True(t, found)
62-
63-
require.Equal(t, len(ruleGroups.Groups), len(actual))
64-
for i, actualGroup := range actual {
65-
expected := rules.ToProto(user, namespace, ruleGroups.Groups[i])
78+
for _, u := range []string{user1, user2} {
79+
actual, found := userMap[u]
80+
require.True(t, found)
6681

67-
require.Equal(t, expected, actualGroup)
82+
require.Equal(t, 2, len(actual))
83+
// We rely on the fact that files are parsed in alphabetical order, and our namespace1 < namespace2.
84+
require.Equal(t, rules.ToProto(u, namespace1, ruleGroups.Groups[0]), actual[0])
85+
require.Equal(t, rules.ToProto(u, namespace2, ruleGroups.Groups[0]), actual[1])
6886
}
6987
}

0 commit comments

Comments
 (0)