Compare commits

...

5 Commits

Author SHA1 Message Date
Viktor Scharf
e362f04093 run CI with posix if posix in the title 2025-03-17 12:43:01 +01:00
Viktor Scharf
9f01bf1af3 use old expected failures file 2025-03-17 12:43:01 +01:00
Viktor Scharf
217a84b360 reva bump 2025-03-17 12:43:01 +01:00
Viktor Scharf
e40d7f4246 add storage_user_id_cache 2025-03-17 12:43:01 +01:00
Viktor Scharf
827a053ac0 run test in CI with posix 2025-03-17 12:43:00 +01:00
28 changed files with 700 additions and 170 deletions

View File

@@ -886,12 +886,16 @@ def localApiTestPipeline(ctx):
if ctx.build.event == "cron" or "full-ci" in ctx.build.title.lower():
with_remote_php.append(False)
storages = ["decomposed"]
if "posix" in ctx.build.title.lower():
storages = ["posix"]
defaults = {
"suites": {},
"skip": False,
"extraEnvironment": {},
"extraServerEnvironment": {},
"storages": ["decomposed"],
"storages": storages,
"accounts_hash_difficulty": 4,
"emailNeeded": False,
"antivirusNeeded": False,
@@ -945,7 +949,7 @@ def localApiTestPipeline(ctx):
def localApiTests(ctx, name, suites, storage = "decomposed", extra_environment = {}, with_remote_php = False):
test_dir = "%s/tests/acceptance" % dirs["base"]
expected_failures_file = "%s/expected-failures-localAPI-on-%s-storage.md" % (test_dir, storage)
expected_failures_file = "%s/expected-failures-localAPI-on-decomposed-storage.md" % (test_dir)
environment = {
"TEST_SERVER_URL": OC_URL,
@@ -1122,10 +1126,10 @@ def wopiValidatorTests(ctx, storage, wopiServerType, accounts_hash_difficulty =
],
}
def coreApiTests(ctx, part_number = 1, number_of_parts = 1, with_remote_php = False, storage = "ocis", accounts_hash_difficulty = 4):
def coreApiTests(ctx, part_number = 1, number_of_parts = 1, with_remote_php = False, storage = "posix", accounts_hash_difficulty = 4):
filterTags = "~@skipOnGraph&&~@skipOnOcis-%s-Storage" % ("OC" if storage == "owncloud" else "OCIS")
test_dir = "%s/tests/acceptance" % dirs["base"]
expected_failures_file = "%s/expected-failures-API-on-%s-storage.md" % (test_dir, storage.upper())
expected_failures_file = "%s/expected-failures-API-on-decomposed-storage.md" % (test_dir)
return {
"name": "Core-API-Tests-%s%s" % (part_number, "-withoutRemotePhp" if not with_remote_php else ""),
@@ -1186,10 +1190,13 @@ def apiTests(ctx):
"withRemotePhp": with_remote_php,
}
if "posix" in ctx.build.title.lower():
storage = "posix"
for runPart in range(1, config["apiTests"]["numberOfParts"] + 1):
for run_with_remote_php in defaults["withRemotePhp"]:
if (not debugPartsEnabled or (debugPartsEnabled and runPart in debugParts)):
pipelines.append(coreApiTests(ctx, runPart, config["apiTests"]["numberOfParts"], run_with_remote_php))
pipelines.append(coreApiTests(ctx, runPart, config["apiTests"]["numberOfParts"], run_with_remote_php, storage))
return pipelines
@@ -1337,6 +1344,10 @@ def multiServiceE2ePipeline(ctx):
if (not "full-ci" in ctx.build.title.lower() and ctx.build.event != "cron"):
return pipelines
storage = "decomposed"
if "posix" in ctx.build.title.lower():
storage = "posix"
extra_server_environment = {
"OCIS_PASSWORD_POLICY_BANNED_PASSWORDS_LIST": "%s" % dirs["bannedPasswordList"],
"OCIS_JWT_SECRET": "some-ocis-jwt-secret",
@@ -1408,7 +1419,7 @@ def multiServiceE2ePipeline(ctx):
restoreWebCache() + \
restoreWebPnpmCache() + \
tikaService() + \
opencloudServer(extra_server_environment = extra_server_environment, tika_enabled = params["tikaNeeded"]) + \
opencloudServer(storage, extra_server_environment = extra_server_environment, tika_enabled = params["tikaNeeded"]) + \
storage_users_services + \
[{
"name": "e2e-tests",
@@ -2009,6 +2020,7 @@ def opencloudServer(storage = "decomposed", accounts_hash_difficulty = 4, volume
"OC_URL": OC_URL,
"OC_CONFIG_DIR": "/root/.opencloud/config", # needed for checking config later
"STORAGE_USERS_DRIVER": "%s" % (storage),
"STORAGE_USERS_ID_CACHE_STORE": "nats-js-kv",
"PROXY_ENABLE_BASIC_AUTH": True,
"WEB_UI_CONFIG_FILE": "%s/%s" % (dirs["base"], dirs["opencloudConfig"]),
"OC_LOG_LEVEL": "error",

10
go.mod
View File

@@ -21,7 +21,7 @@ require (
github.com/egirna/icap-client v0.1.1
github.com/gabriel-vasile/mimetype v1.4.8
github.com/ggwhite/go-masker v1.1.0
github.com/go-chi/chi/v5 v5.2.0
github.com/go-chi/chi/v5 v5.2.1
github.com/go-chi/render v1.0.3
github.com/go-ldap/ldap/v3 v3.4.10
github.com/go-ldap/ldif v0.0.0-20200320164324-fd88d9b715b3
@@ -63,7 +63,7 @@ require (
github.com/onsi/ginkgo/v2 v2.23.0
github.com/onsi/gomega v1.36.2
github.com/open-policy-agent/opa v1.1.0
github.com/opencloud-eu/reva/v2 v2.27.3-0.20250312134906-766c69c5d1be
github.com/opencloud-eu/reva/v2 v2.27.3-0.20250314084055-d2fcfe6b3445
github.com/orcaman/concurrent-map v1.0.0
github.com/owncloud/libre-graph-api-go v1.0.5-0.20240829135935-80dc00d6f5ea
github.com/pkg/errors v0.9.1
@@ -274,7 +274,7 @@ require (
github.com/pjbgf/sha1cd v0.3.2 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/pquerna/cachecontrol v0.2.0 // indirect
github.com/prometheus/alertmanager v0.27.0 // indirect
github.com/prometheus/alertmanager v0.28.1 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.62.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
@@ -289,8 +289,8 @@ require (
github.com/sercand/kuberesolver/v5 v5.1.1 // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/sethvargo/go-password v0.3.1 // indirect
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect
github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 // indirect
github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect
github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92 // indirect
github.com/skeema/knownhosts v1.3.0 // indirect
github.com/spacewander/go-suffix-tree v0.0.0-20191010040751-0865e368c784 // indirect
github.com/spf13/pflag v1.0.6 // indirect

23
go.sum
View File

@@ -133,9 +133,8 @@ github.com/bbalet/stopwords v1.0.0/go.mod h1:sAWrQoDMfqARGIn4s6dp7OW7ISrshUD8IP2
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
github.com/beevik/etree v1.5.0 h1:iaQZFSDS+3kYZiGoc9uKeOkUY3nYMXOKLl6KIJxiJWs=
github.com/beevik/etree v1.5.0/go.mod h1:gPNJNaBGVZ9AwsidazFZyygnd+0pAU38N4D+WemwKNs=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=
github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
@@ -338,8 +337,8 @@ github.com/go-asn1-ber/asn1-ber v1.4.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkPro
github.com/go-asn1-ber/asn1-ber v1.5.7 h1:DTX+lbVTWaTw1hQ+PbZPlnDZPEIs0SS/GCZAl535dDk=
github.com/go-asn1-ber/asn1-ber v1.5.7/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-chi/chi/v5 v5.2.0 h1:Aj1EtB0qR2Rdo2dG4O94RIU35w2lvQSj6BRA4+qwFL0=
github.com/go-chi/chi/v5 v5.2.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s=
@@ -865,8 +864,8 @@ github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
github.com/open-policy-agent/opa v1.1.0 h1:HMz2evdEMTyNqtdLjmu3Vyx06BmhNYAx67Yz3Ll9q2s=
github.com/open-policy-agent/opa v1.1.0/go.mod h1:T1pASQ1/vwfTa+e2fYcfpLCvWgYtqtiUv+IuA/dLPQs=
github.com/opencloud-eu/reva/v2 v2.27.3-0.20250312134906-766c69c5d1be h1:dxKsVUzdKIGf1hfGdr1GH6NFfo0xuIcPma/qjHNG8mU=
github.com/opencloud-eu/reva/v2 v2.27.3-0.20250312134906-766c69c5d1be/go.mod h1:sqlExPoEnEd0KdfoSKogV8PrwEBY3l06icoa4gJnGnU=
github.com/opencloud-eu/reva/v2 v2.27.3-0.20250314084055-d2fcfe6b3445 h1:At2GtwEeNls1P60RpBa9QQridCtFQNW/pnQ5tybT8X0=
github.com/opencloud-eu/reva/v2 v2.27.3-0.20250314084055-d2fcfe6b3445/go.mod h1:yCscyJJ7FX/HA2fexM2i1OyKSZnJgdq1vnoXgXKmnn8=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
@@ -911,8 +910,8 @@ github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndr
github.com/pquerna/cachecontrol v0.2.0 h1:vBXSNuE5MYP9IJ5kjsdo8uq+w41jSPgvba2DEnkRx9k=
github.com/pquerna/cachecontrol v0.2.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI=
github.com/pquerna/otp v1.3.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/prometheus/alertmanager v0.27.0 h1:V6nTa2J5V4s8TG4C4HtrBP/WNSebCCTYGGv4qecA/+I=
github.com/prometheus/alertmanager v0.27.0/go.mod h1:8Ia/R3urPmbzJ8OsdvmZvIprDwvwmYCmUbwBL+jlPOE=
github.com/prometheus/alertmanager v0.28.1 h1:BK5pCoAtaKg01BYRUJhEDV1tqJMEtYBGzPw8QdvnnvA=
github.com/prometheus/alertmanager v0.28.1/go.mod h1:0StpPUDDHi1VXeM7p2yYfeZgLVi/PPlt39vo9LQUHxM=
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
@@ -1010,11 +1009,11 @@ github.com/shamaton/msgpack/v2 v2.2.2 h1:GOIg0c9LV04VwzOOqZSrmsv/JzjNOOMxnS/HvOH
github.com/shamaton/msgpack/v2 v2.2.2/go.mod h1:6khjYnkx73f7VQU7wjcFS9DFjs+59naVWJv1TB7qdOI=
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 h1:bUGsEnyNbVPw06Bs80sCeARAlK8lhwqGyi6UT8ymuGk=
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c h1:aqg5Vm5dwtvL+YgDpBcK1ITf3o96N/K7/wsRXQnUTEs=
github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c/go.mod h1:owqhoLW1qZoYLZzLnBw+QkPP9WZnjlSWihhxAJC1+/M=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 h1:pXY9qYc/MP5zdvqWEUH6SjNiu7VhSjuVFTFiTcphaLU=
github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92 h1:OfRzdxCzDhp+rsKWXuOO2I/quKMJ/+TQwVbIP/gltZg=
github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92/go.mod h1:7/OT02F6S6I7v6WXb+IjhMuZEYfH/RJ5RwEWnEo5BMg=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=

View File

@@ -26,13 +26,13 @@ import (
"strings"
"time"
"github.com/google/uuid"
"github.com/rs/zerolog"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
"github.com/opencloud-eu/reva/v2/pkg/storage"
"github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/lookup"
"github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/options"
@@ -145,7 +145,7 @@ func trashRootForNode(n *node.Node) string {
}
func (tb *Trashbin) MoveToTrash(ctx context.Context, n *node.Node, path string) error {
key := uuid.New().String()
key := n.ID
trashPath := trashRootForNode(n)
err := os.MkdirAll(filepath.Join(trashPath, "info"), 0755)
@@ -197,6 +197,30 @@ func (tb *Trashbin) ListRecycle(ctx context.Context, spaceID string, key, relati
return nil, err
}
originalPath = filepath.Join(originalPath, relativePath)
fi, err := os.Stat(base)
if err != nil {
return nil, err
}
item := &provider.RecycleItem{
Key: filepath.Join(key, relativePath),
Size: uint64(fi.Size()),
Ref: &provider.Reference{
ResourceId: &provider.ResourceId{
SpaceId: spaceID,
OpaqueId: spaceID,
},
Path: originalPath,
},
DeletionTime: ts,
Type: provider.ResourceType_RESOURCE_TYPE_FILE,
}
if fi.IsDir() {
item.Type = provider.ResourceType_RESOURCE_TYPE_CONTAINER
} else {
item.Type = provider.ResourceType_RESOURCE_TYPE_FILE
}
return []*provider.RecycleItem{item}, nil
}
items := []*provider.RecycleItem{}
@@ -287,6 +311,10 @@ func (tb *Trashbin) RestoreRecycleItem(ctx context.Context, spaceID string, key,
if err != nil {
return nil, err
}
if id == "" {
return nil, errtypes.NotFound("trashbin: item not found")
}
// update parent id in case it was restored to a different location
_, parentID, _, err := tb.lu.MetadataBackend().IdentifyPath(ctx, filepath.Dir(restorePath))

View File

@@ -102,6 +102,10 @@ type Session interface {
LockID() string
}
type IDCachingTree interface {
WarmupIDCache(root string, assimilate, onlyDirty bool) error
}
type SessionStore interface {
New(ctx context.Context) *upload.DecomposedFsSession
List(ctx context.Context) ([]*upload.DecomposedFsSession, error)
@@ -1304,6 +1308,11 @@ func (fs *Decomposedfs) RestoreRecycleItem(ctx context.Context, space *provider.
return err
}
sizeDiff = int64(treeSize)
// Warmup posix IDCache if restored path is a directory
if cachingTree, ok := fs.tp.(IDCachingTree); ok {
_ = cachingTree.WarmupIDCache(restoredNode.InternalPath(), false, false)
}
} else {
sizeDiff = restoredNode.Blobsize
}

View File

@@ -135,7 +135,7 @@ func New(m map[string]interface{}) (*Options, error) {
o.Root = filepath.Clean(o.Root)
if o.PersonalSpaceAliasTemplate == "" {
o.PersonalSpaceAliasTemplate = "{{.SpaceType}}/{{.User.Username}}"
o.PersonalSpaceAliasTemplate = "{{.SpaceType}}/{{.User.Username | lower}}"
}
if o.GeneralSpaceAliasTemplate == "" {

View File

@@ -11,8 +11,3 @@ Bootstrap
http://getbootstrap.com
Copyright 2011-2014 Twitter, Inc.
Licensed under the MIT License
bootstrap-datetimepicker.js
http://www.eyecon.ro/bootstrap-datepicker
Copyright 2012 Stefan Petre
Licensed under the Apache License, Version 2.0

View File

File diff suppressed because one or more lines are too long

View File

@@ -16,35 +16,41 @@ package featurecontrol
import (
"errors"
"fmt"
"log/slog"
"strings"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
)
const (
FeatureReceiverNameInMetrics = "receiver-name-in-metrics"
FeatureClassicMode = "classic-mode"
FeatureUTF8StrictMode = "utf8-strict-mode"
FeatureAutoGOMEMLIMIT = "auto-gomemlimit"
FeatureAutoGOMAXPROCS = "auto-gomaxprocs"
)
var AllowedFlags = []string{
FeatureReceiverNameInMetrics,
FeatureClassicMode,
FeatureUTF8StrictMode,
FeatureAutoGOMEMLIMIT,
FeatureAutoGOMAXPROCS,
}
type Flagger interface {
EnableReceiverNamesInMetrics() bool
ClassicMode() bool
UTF8StrictMode() bool
EnableAutoGOMEMLIMIT() bool
EnableAutoGOMAXPROCS() bool
}
type Flags struct {
logger log.Logger
logger *slog.Logger
enableReceiverNamesInMetrics bool
classicMode bool
utf8StrictMode bool
enableAutoGOMEMLIMIT bool
enableAutoGOMAXPROCS bool
}
func (f *Flags) EnableReceiverNamesInMetrics() bool {
@@ -59,6 +65,14 @@ func (f *Flags) UTF8StrictMode() bool {
return f.utf8StrictMode
}
func (f *Flags) EnableAutoGOMEMLIMIT() bool {
return f.enableAutoGOMEMLIMIT
}
func (f *Flags) EnableAutoGOMAXPROCS() bool {
return f.enableAutoGOMAXPROCS
}
type flagOption func(flags *Flags)
func enableReceiverNameInMetrics() flagOption {
@@ -79,7 +93,19 @@ func enableUTF8StrictMode() flagOption {
}
}
func NewFlags(logger log.Logger, features string) (Flagger, error) {
func enableAutoGOMEMLIMIT() flagOption {
return func(configs *Flags) {
configs.enableAutoGOMEMLIMIT = true
}
}
func enableAutoGOMAXPROCS() flagOption {
return func(configs *Flags) {
configs.enableAutoGOMAXPROCS = true
}
}
func NewFlags(logger *slog.Logger, features string) (Flagger, error) {
fc := &Flags{logger: logger}
opts := []flagOption{}
@@ -91,13 +117,19 @@ func NewFlags(logger log.Logger, features string) (Flagger, error) {
switch feature {
case FeatureReceiverNameInMetrics:
opts = append(opts, enableReceiverNameInMetrics())
level.Warn(logger).Log("msg", "Experimental receiver name in metrics enabled")
logger.Warn("Experimental receiver name in metrics enabled")
case FeatureClassicMode:
opts = append(opts, enableClassicMode())
level.Warn(logger).Log("msg", "Classic mode enabled")
logger.Warn("Classic mode enabled")
case FeatureUTF8StrictMode:
opts = append(opts, enableUTF8StrictMode())
level.Warn(logger).Log("msg", "UTF-8 strict mode enabled")
logger.Warn("UTF-8 strict mode enabled")
case FeatureAutoGOMEMLIMIT:
opts = append(opts, enableAutoGOMEMLIMIT())
logger.Warn("Automatically set GOMEMLIMIT to match the Linux container or system memory limit.")
case FeatureAutoGOMAXPROCS:
opts = append(opts, enableAutoGOMAXPROCS())
logger.Warn("Automatically set GOMAXPROCS to match Linux container CPU quota")
default:
return nil, fmt.Errorf("Unknown option '%s' for --enable-feature", feature)
}
@@ -121,3 +153,7 @@ func (n NoopFlags) EnableReceiverNamesInMetrics() bool { return false }
func (n NoopFlags) ClassicMode() bool { return false }
func (n NoopFlags) UTF8StrictMode() bool { return false }
func (n NoopFlags) EnableAutoGOMEMLIMIT() bool { return false }
func (n NoopFlags) EnableAutoGOMAXPROCS() bool { return false }

View File

@@ -15,23 +15,23 @@ package compat
import (
"fmt"
"log/slog"
"reflect"
"strings"
"unicode/utf8"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/prometheus/common/model"
"github.com/prometheus/common/promslog"
"github.com/prometheus/alertmanager/featurecontrol"
"github.com/prometheus/alertmanager/matchers/parse"
"github.com/prometheus/alertmanager/matcher/parse"
"github.com/prometheus/alertmanager/pkg/labels"
)
var (
isValidLabelName = isValidClassicLabelName(log.NewNopLogger())
parseMatcher = ClassicMatcherParser(log.NewNopLogger())
parseMatchers = ClassicMatchersParser(log.NewNopLogger())
isValidLabelName = isValidClassicLabelName(promslog.NewNopLogger())
parseMatcher = ClassicMatcherParser(promslog.NewNopLogger())
parseMatchers = ClassicMatchersParser(promslog.NewNopLogger())
)
// IsValidLabelName returns true if the string is a valid label name.
@@ -56,7 +56,7 @@ func Matchers(input, origin string) (labels.Matchers, error) {
}
// InitFromFlags initializes the compat package from the flagger.
func InitFromFlags(l log.Logger, f featurecontrol.Flagger) {
func InitFromFlags(l *slog.Logger, f featurecontrol.Flagger) {
if f.ClassicMode() {
isValidLabelName = isValidClassicLabelName(l)
parseMatcher = ClassicMatcherParser(l)
@@ -74,27 +74,27 @@ func InitFromFlags(l log.Logger, f featurecontrol.Flagger) {
// ClassicMatcherParser uses the pkg/labels parser to parse the matcher in
// the input string.
func ClassicMatcherParser(l log.Logger) ParseMatcher {
func ClassicMatcherParser(l *slog.Logger) ParseMatcher {
return func(input, origin string) (matcher *labels.Matcher, err error) {
level.Debug(l).Log("msg", "Parsing with classic matchers parser", "input", input, "origin", origin)
l.Debug("Parsing with classic matchers parser", "input", input, "origin", origin)
return labels.ParseMatcher(input)
}
}
// ClassicMatchersParser uses the pkg/labels parser to parse zero or more
// matchers in the input string. It returns an error if the input is invalid.
func ClassicMatchersParser(l log.Logger) ParseMatchers {
func ClassicMatchersParser(l *slog.Logger) ParseMatchers {
return func(input, origin string) (matchers labels.Matchers, err error) {
level.Debug(l).Log("msg", "Parsing with classic matchers parser", "input", input, "origin", origin)
l.Debug("Parsing with classic matchers parser", "input", input, "origin", origin)
return labels.ParseMatchers(input)
}
}
// UTF8MatcherParser uses the new matchers/parse parser to parse the matcher
// UTF8MatcherParser uses the new matcher/parse parser to parse the matcher
// in the input string. If this fails it does not revert to the pkg/labels parser.
func UTF8MatcherParser(l log.Logger) ParseMatcher {
func UTF8MatcherParser(l *slog.Logger) ParseMatcher {
return func(input, origin string) (matcher *labels.Matcher, err error) {
level.Debug(l).Log("msg", "Parsing with UTF-8 matchers parser", "input", input, "origin", origin)
l.Debug("Parsing with UTF-8 matchers parser", "input", input, "origin", origin)
if strings.HasPrefix(input, "{") || strings.HasSuffix(input, "}") {
return nil, fmt.Errorf("unexpected open or close brace: %s", input)
}
@@ -102,22 +102,22 @@ func UTF8MatcherParser(l log.Logger) ParseMatcher {
}
}
// UTF8MatchersParser uses the new matchers/parse parser to parse zero or more
// UTF8MatchersParser uses the new matcher/parse parser to parse zero or more
// matchers in the input string. If this fails it does not revert to the
// pkg/labels parser.
func UTF8MatchersParser(l log.Logger) ParseMatchers {
func UTF8MatchersParser(l *slog.Logger) ParseMatchers {
return func(input, origin string) (matchers labels.Matchers, err error) {
level.Debug(l).Log("msg", "Parsing with UTF-8 matchers parser", "input", input, "origin", origin)
l.Debug("Parsing with UTF-8 matchers parser", "input", input, "origin", origin)
return parse.Matchers(input)
}
}
// FallbackMatcherParser uses the new matchers/parse parser to parse zero or more
// FallbackMatcherParser uses the new matcher/parse parser to parse zero or more
// matchers in the string. If this fails it reverts to the pkg/labels parser and
// emits a warning log line.
func FallbackMatcherParser(l log.Logger) ParseMatcher {
func FallbackMatcherParser(l *slog.Logger) ParseMatcher {
return func(input, origin string) (matcher *labels.Matcher, err error) {
level.Debug(l).Log("msg", "Parsing with UTF-8 matchers parser, with fallback to classic matchers parser", "input", input, "origin", origin)
l.Debug("Parsing with UTF-8 matchers parser, with fallback to classic matchers parser", "input", input, "origin", origin)
if strings.HasPrefix(input, "{") || strings.HasSuffix(input, "}") {
return nil, fmt.Errorf("unexpected open or close brace: %s", input)
}
@@ -130,28 +130,28 @@ func FallbackMatcherParser(l log.Logger) ParseMatcher {
if cErr != nil {
return nil, cErr
}
// The input is valid in the pkg/labels parser, but not the matchers/parse
// The input is valid in the pkg/labels parser, but not the matcher/parse
// parser. This means the input is not forwards compatible.
suggestion := cMatcher.String()
level.Warn(l).Log("msg", "Alertmanager is moving to a new parser for labels and matchers, and this input is incompatible. Alertmanager has instead parsed the input using the classic matchers parser as a fallback. To make this input compatible with the UTF-8 matchers parser please make sure all regular expressions and values are double-quoted. If you are still seeing this message please open an issue.", "input", input, "origin", origin, "err", nErr, "suggestion", suggestion)
l.Warn("Alertmanager is moving to a new parser for labels and matchers, and this input is incompatible. Alertmanager has instead parsed the input using the classic matchers parser as a fallback. To make this input compatible with the UTF-8 matchers parser please make sure all regular expressions and values are double-quoted and backslashes are escaped. If you are still seeing this message please open an issue.", "input", input, "origin", origin, "err", nErr, "suggestion", suggestion)
return cMatcher, nil
}
// If the input is valid in both parsers, but produces different results,
// then there is disagreement.
if nErr == nil && cErr == nil && !reflect.DeepEqual(nMatcher, cMatcher) {
level.Warn(l).Log("msg", "Matchers input has disagreement", "input", input, "origin", origin)
l.Warn("Matchers input has disagreement", "input", input, "origin", origin)
return cMatcher, nil
}
return nMatcher, nil
}
}
// FallbackMatchersParser uses the new matchers/parse parser to parse the
// FallbackMatchersParser uses the new matcher/parse parser to parse the
// matcher in the input string. If this fails it falls back to the pkg/labels
// parser and emits a warning log line.
func FallbackMatchersParser(l log.Logger) ParseMatchers {
func FallbackMatchersParser(l *slog.Logger) ParseMatchers {
return func(input, origin string) (matchers labels.Matchers, err error) {
level.Debug(l).Log("msg", "Parsing with UTF-8 matchers parser, with fallback to classic matchers parser", "input", input, "origin", origin)
l.Debug("Parsing with UTF-8 matchers parser, with fallback to classic matchers parser", "input", input, "origin", origin)
// Parse the input in both parsers to look for disagreement and incompatible
// inputs.
nMatchers, nErr := parse.Matchers(input)
@@ -161,7 +161,7 @@ func FallbackMatchersParser(l log.Logger) ParseMatchers {
if cErr != nil {
return nil, cErr
}
// The input is valid in the pkg/labels parser, but not the matchers/parse
// The input is valid in the pkg/labels parser, but not the matcher/parse
// parser. This means the input is not forwards compatible.
var sb strings.Builder
for i, n := range cMatchers {
@@ -172,15 +172,15 @@ func FallbackMatchersParser(l log.Logger) ParseMatchers {
}
suggestion := sb.String()
// The input is valid in the pkg/labels parser, but not the
// new matchers/parse parser.
level.Warn(l).Log("msg", "Alertmanager is moving to a new parser for labels and matchers, and this input is incompatible. Alertmanager has instead parsed the input using the classic matchers parser as a fallback. To make this input compatible with the UTF-8 matchers parser please make sure all regular expressions and values are double-quoted. If you are still seeing this message please open an issue.", "input", input, "origin", origin, "err", nErr, "suggestion", suggestion)
// new matcher/parse parser.
l.Warn("Alertmanager is moving to a new parser for labels and matchers, and this input is incompatible. Alertmanager has instead parsed the input using the classic matchers parser as a fallback. To make this input compatible with the UTF-8 matchers parser please make sure all regular expressions and values are double-quoted and backslashes are escaped. If you are still seeing this message please open an issue.", "input", input, "origin", origin, "err", nErr, "suggestion", suggestion)
return cMatchers, nil
}
// If the input is valid in both parsers, but produces different results,
// then there is disagreement. We need to compare to labels.Matchers(cMatchers)
// as cMatchers is a []*labels.Matcher not labels.Matchers.
if nErr == nil && cErr == nil && !reflect.DeepEqual(nMatchers, labels.Matchers(cMatchers)) {
level.Warn(l).Log("msg", "Matchers input has disagreement", "input", input, "origin", origin)
l.Warn("Matchers input has disagreement", "input", input, "origin", origin)
return cMatchers, nil
}
return nMatchers, nil
@@ -188,14 +188,14 @@ func FallbackMatchersParser(l log.Logger) ParseMatchers {
}
// isValidClassicLabelName returns true if the string is a valid classic label name.
func isValidClassicLabelName(_ log.Logger) func(model.LabelName) bool {
func isValidClassicLabelName(_ *slog.Logger) func(model.LabelName) bool {
return func(name model.LabelName) bool {
return name.IsValid()
}
}
// isValidUTF8LabelName returns true if the string is a valid UTF-8 label name.
func isValidUTF8LabelName(_ log.Logger) func(model.LabelName) bool {
func isValidUTF8LabelName(_ *slog.Logger) func(model.LabelName) bool {
return func(name model.LabelName) bool {
if len(name) == 0 {
return false

View File

@@ -251,7 +251,7 @@ func (l *lexer) accept(valid string) bool {
}
// expect consumes the next rune if its one of the valid runes.
// it returns nil if the next rune is valid, otherwise an expectedError
// It returns nil if the next rune is valid, otherwise an expectedError
// error.
func (l *lexer) expect(valid string) error {
if strings.ContainsRune(valid, l.next()) {

View File

@@ -196,7 +196,7 @@ func (p *parser) parseEndOfMatcher(l *lexer) (parseFunc, error) {
if err != nil {
if errors.Is(err, errEOF) {
// If this is the end of input we still need to check if the optional
// open brace has a matching close brace
// open brace has a matching close brace.
return p.parseCloseBrace, nil
}
return nil, fmt.Errorf("%w: %w", err, errExpectedCommaOrCloseBrace)
@@ -220,7 +220,7 @@ func (p *parser) parseComma(l *lexer) (parseFunc, error) {
if err != nil {
if errors.Is(err, errEOF) {
// If this is the end of input we still need to check if the optional
// open brace has a matching close brace
// open brace has a matching close brace.
return p.parseCloseBrace, nil
}
return nil, fmt.Errorf("%w: %w", err, errExpectedMatcherOrCloseBrace)
@@ -242,6 +242,7 @@ func (p *parser) parseEOF(l *lexer) (parseFunc, error) {
return nil, nil
}
// nolint:godot
// accept returns true if the next token is one of the specified kinds,
// otherwise false. If the token is accepted it is consumed. tokenEOF is
// not an accepted kind and instead accept returns ErrEOF if there is no
@@ -256,6 +257,7 @@ func (p *parser) accept(l *lexer, kinds ...tokenKind) (ok bool, err error) {
return ok, err
}
// nolint:godot
// acceptPeek returns true if the next token is one of the specified kinds,
// otherwise false. However, unlike accept, acceptPeek does not consume accepted
// tokens. tokenEOF is not an accepted kind and instead accept returns ErrEOF
@@ -271,6 +273,7 @@ func (p *parser) acceptPeek(l *lexer, kinds ...tokenKind) (bool, error) {
return t.isOneOf(kinds...), nil
}
// nolint:godot
// expect returns the next token if it is one of the specified kinds, otherwise
// it returns an error. If the token is expected it is consumed. tokenEOF is not
// an accepted kind and instead expect returns ErrEOF if there is no more input.
@@ -285,6 +288,7 @@ func (p *parser) expect(l *lexer, kind ...tokenKind) (token, error) {
return t, nil
}
// nolint:godot
// expect returns the next token if it is one of the specified kinds, otherwise
// it returns an error. However, unlike expect, expectPeek does not consume tokens.
// tokenEOF is not an accepted kind and instead expect returns ErrEOF if there is no

View File

@@ -205,7 +205,7 @@ func (ms Matchers) String() string {
return buf.String()
}
// This is copied from matchers/parse/lexer.go. It will be removed when
// This is copied from matcher/parse/lexer.go. It will be removed when
// the transition window from classic matchers to UTF-8 matchers is complete,
// as then we can use double quotes when printing the label name for all
// matchers. Until then, the classic parser does not understand double quotes

View File

@@ -136,7 +136,7 @@ func ParseMatcher(s string) (_ *Matcher, err error) {
return nil, fmt.Errorf("matcher value not valid UTF-8: %s", ms[3])
}
// Unescape the rawValue:
// Unescape the rawValue.
for i, r := range rawValue {
if escaped {
escaped = false

View File

@@ -1,7 +1,7 @@
FROM node
FROM node:20-alpine
ENV NODE_PATH="/usr/local/lib/node_modules"
RUN npm install juice -g
RUN npm install juice@10.0.1 -g
ENTRYPOINT [""]

View File

@@ -1,4 +1,4 @@
DOCKER_IMG := altermanager-template
DOCKER_IMG := alertmanager-template
DOCKER_RUN_CURRENT_USER := docker run --user=$(shell id -u $(USER)):$(shell id -g $(USER))
DOCKER_CMD := $(DOCKER_RUN_CURRENT_USER) --rm -t -v $(PWD):/app -w /app $(DOCKER_IMG)

View File

@@ -123,6 +123,7 @@ Alerts Resolved:
{{ end }}
{{ end }}
{{ define "discord.default.content" }}{{ end }}
{{ define "discord.default.title" }}{{ template "__subject" . }}{{ end }}
{{ define "discord.default.message" }}
{{ if gt (len .Alerts.Firing) 0 }}
@@ -158,3 +159,61 @@ Alerts Resolved:
{{ template "__text_alert_list_markdown" .Alerts.Resolved }}
{{ end }}
{{ end }}
{{ define "msteamsv2.default.title" }}{{ template "__subject" . }}{{ end }}
{{ define "msteamsv2.default.text" }}
{{ if gt (len .Alerts.Firing) 0 }}
# Alerts Firing:
{{ template "__text_alert_list_markdown" .Alerts.Firing }}
{{ end }}
{{ if gt (len .Alerts.Resolved) 0 }}
# Alerts Resolved:
{{ template "__text_alert_list_markdown" .Alerts.Resolved }}
{{ end }}
{{ end }}
{{ define "jira.default.summary" }}{{ template "__subject" . }}{{ end }}
{{ define "jira.default.description" }}
{{ if gt (len .Alerts.Firing) 0 }}
# Alerts Firing:
{{ template "__text_alert_list_markdown" .Alerts.Firing }}
{{ end }}
{{ if gt (len .Alerts.Resolved) 0 }}
# Alerts Resolved:
{{ template "__text_alert_list_markdown" .Alerts.Resolved }}
{{ end }}
{{ end }}
{{- define "jira.default.priority" -}}
{{- $priority := "" }}
{{- range .Alerts.Firing -}}
{{- $severity := index .Labels "severity" -}}
{{- if (eq $severity "critical") -}}
{{- $priority = "High" -}}
{{- else if (and (eq $severity "warning") (ne $priority "High")) -}}
{{- $priority = "Medium" -}}
{{- else if (and (eq $severity "info") (eq $priority "")) -}}
{{- $priority = "Low" -}}
{{- end -}}
{{- end -}}
{{- if eq $priority "" -}}
{{- range .Alerts.Resolved -}}
{{- $severity := index .Labels "severity" -}}
{{- if (eq $severity "critical") -}}
{{- $priority = "High" -}}
{{- else if (and (eq $severity "warning") (ne $priority "High")) -}}
{{- $priority = "Medium" -}}
{{- else if (and (eq $severity "info") (eq $priority "")) -}}
{{- $priority = "Low" -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- $priority -}}
{{- end -}}
{{ define "rocketchat.default.title" }}{{ template "__subject" . }}{{ end }}
{{ define "rocketchat.default.alias" }}{{ template "__alertmanager" . }}{{ end }}
{{ define "rocketchat.default.titlelink" }}{{ template "__alertmanagerURL" . }}{{ end }}
{{ define "rocketchat.default.emoji" }}{{ end }}
{{ define "rocketchat.default.iconurl" }}{{ end }}
{{ define "rocketchat.default.text" }}{{ end }}

View File

@@ -26,6 +26,7 @@ import (
tmpltext "text/template"
"time"
commonTemplates "github.com/prometheus/common/helpers/templates"
"github.com/prometheus/common/model"
"golang.org/x/text/cases"
"golang.org/x/text/language"
@@ -192,6 +193,20 @@ var DefaultFuncs = FuncMap{
"stringSlice": func(s ...string) []string {
return s
},
// date returns the text representation of the time in the specified format.
"date": func(fmt string, t time.Time) string {
return t.Format(fmt)
},
// tz returns the time in the timezone.
"tz": func(name string, t time.Time) (time.Time, error) {
loc, err := time.LoadLocation(name)
if err != nil {
return time.Time{}, err
}
return t.In(loc), nil
},
"since": time.Since,
"humanizeDuration": commonTemplates.HumanizeDuration,
}
// Pair is a key/value string pair.

View File

@@ -22,7 +22,7 @@ import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/common/model"
"github.com/prometheus/alertmanager/matchers/compat"
"github.com/prometheus/alertmanager/matcher/compat"
"github.com/prometheus/alertmanager/pkg/labels"
)
@@ -52,9 +52,17 @@ type AlertStatus struct {
silencesVersion int
}
// Marker helps to mark alerts as silenced and/or inhibited.
// groupStatus stores the state of the group, and, as applicable, the names
// of all active and mute time intervals that are muting it.
type groupStatus struct {
// mutedBy contains the names of all active and mute time intervals that
// are muting it.
mutedBy []string
}
// AlertMarker helps to mark alerts as silenced and/or inhibited.
// All methods are goroutine-safe.
type Marker interface {
type AlertMarker interface {
// SetActiveOrSilenced replaces the previous SilencedBy by the provided IDs of
// active and pending silences, including the version number of the
// silences state. The set of provided IDs is supposed to represent the
@@ -92,24 +100,74 @@ type Marker interface {
Inhibited(model.Fingerprint) ([]string, bool)
}
// NewMarker returns an instance of a Marker implementation.
func NewMarker(r prometheus.Registerer) Marker {
m := &memMarker{
m: map[model.Fingerprint]*AlertStatus{},
// GroupMarker helps to mark groups as active or muted.
// All methods are goroutine-safe.
//
// TODO(grobinson): routeID is used in Muted and SetMuted because groupKey
// is not unique (see #3817). Once groupKey uniqueness is fixed routeID can
// be removed from the GroupMarker interface.
type GroupMarker interface {
// Muted returns true if the group is muted, otherwise false. If the group
// is muted then it also returns the names of the time intervals that muted
// it.
Muted(routeID, groupKey string) ([]string, bool)
// SetMuted marks the group as muted, and sets the names of the time
// intervals that mute it. If the list of names is nil or the empty slice
// then the muted marker is removed.
SetMuted(routeID, groupKey string, timeIntervalNames []string)
// DeleteByGroupKey removes all markers for the GroupKey.
DeleteByGroupKey(routeID, groupKey string)
}
// NewMarker returns an instance of a AlertMarker implementation.
func NewMarker(r prometheus.Registerer) *MemMarker {
m := &MemMarker{
alerts: map[model.Fingerprint]*AlertStatus{},
groups: map[string]*groupStatus{},
}
m.registerMetrics(r)
return m
}
type memMarker struct {
m map[model.Fingerprint]*AlertStatus
type MemMarker struct {
alerts map[model.Fingerprint]*AlertStatus
groups map[string]*groupStatus
mtx sync.RWMutex
}
func (m *memMarker) registerMetrics(r prometheus.Registerer) {
// Muted implements GroupMarker.
func (m *MemMarker) Muted(routeID, groupKey string) ([]string, bool) {
m.mtx.Lock()
defer m.mtx.Unlock()
status, ok := m.groups[routeID+groupKey]
if !ok {
return nil, false
}
return status.mutedBy, len(status.mutedBy) > 0
}
// SetMuted implements GroupMarker.
func (m *MemMarker) SetMuted(routeID, groupKey string, timeIntervalNames []string) {
m.mtx.Lock()
defer m.mtx.Unlock()
status, ok := m.groups[routeID+groupKey]
if !ok {
status = &groupStatus{}
m.groups[routeID+groupKey] = status
}
status.mutedBy = timeIntervalNames
}
func (m *MemMarker) DeleteByGroupKey(routeID, groupKey string) {
m.mtx.Lock()
defer m.mtx.Unlock()
delete(m.groups, routeID+groupKey)
}
func (m *MemMarker) registerMetrics(r prometheus.Registerer) {
newMarkedAlertMetricByState := func(st AlertState) prometheus.GaugeFunc {
return prometheus.NewGaugeFunc(
prometheus.GaugeOpts{
@@ -132,17 +190,17 @@ func (m *memMarker) registerMetrics(r prometheus.Registerer) {
r.MustRegister(alertStateUnprocessed)
}
// Count implements Marker.
func (m *memMarker) Count(states ...AlertState) int {
// Count implements AlertMarker.
func (m *MemMarker) Count(states ...AlertState) int {
m.mtx.RLock()
defer m.mtx.RUnlock()
if len(states) == 0 {
return len(m.m)
return len(m.alerts)
}
var count int
for _, status := range m.m {
for _, status := range m.alerts {
for _, state := range states {
if status.State == state {
count++
@@ -152,15 +210,15 @@ func (m *memMarker) Count(states ...AlertState) int {
return count
}
// SetActiveOrSilenced implements Marker.
func (m *memMarker) SetActiveOrSilenced(alert model.Fingerprint, version int, activeIDs, pendingIDs []string) {
// SetActiveOrSilenced implements AlertMarker.
func (m *MemMarker) SetActiveOrSilenced(alert model.Fingerprint, version int, activeIDs, pendingIDs []string) {
m.mtx.Lock()
defer m.mtx.Unlock()
s, found := m.m[alert]
s, found := m.alerts[alert]
if !found {
s = &AlertStatus{}
m.m[alert] = s
m.alerts[alert] = s
}
s.SilencedBy = activeIDs
s.pendingSilences = pendingIDs
@@ -177,15 +235,15 @@ func (m *memMarker) SetActiveOrSilenced(alert model.Fingerprint, version int, ac
s.State = AlertStateSuppressed
}
// SetInhibited implements Marker.
func (m *memMarker) SetInhibited(alert model.Fingerprint, ids ...string) {
// SetInhibited implements AlertMarker.
func (m *MemMarker) SetInhibited(alert model.Fingerprint, ids ...string) {
m.mtx.Lock()
defer m.mtx.Unlock()
s, found := m.m[alert]
s, found := m.alerts[alert]
if !found {
s = &AlertStatus{}
m.m[alert] = s
m.alerts[alert] = s
}
s.InhibitedBy = ids
@@ -200,12 +258,12 @@ func (m *memMarker) SetInhibited(alert model.Fingerprint, ids ...string) {
s.State = AlertStateSuppressed
}
// Status implements Marker.
func (m *memMarker) Status(alert model.Fingerprint) AlertStatus {
// Status implements AlertMarker.
func (m *MemMarker) Status(alert model.Fingerprint) AlertStatus {
m.mtx.RLock()
defer m.mtx.RUnlock()
if s, found := m.m[alert]; found {
if s, found := m.alerts[alert]; found {
return *s
}
return AlertStatus{
@@ -215,26 +273,26 @@ func (m *memMarker) Status(alert model.Fingerprint) AlertStatus {
}
}
// Delete implements Marker.
func (m *memMarker) Delete(alert model.Fingerprint) {
// Delete implements AlertMarker.
func (m *MemMarker) Delete(alert model.Fingerprint) {
m.mtx.Lock()
defer m.mtx.Unlock()
delete(m.m, alert)
delete(m.alerts, alert)
}
// Unprocessed implements Marker.
func (m *memMarker) Unprocessed(alert model.Fingerprint) bool {
// Unprocessed implements AlertMarker.
func (m *MemMarker) Unprocessed(alert model.Fingerprint) bool {
return m.Status(alert).State == AlertStateUnprocessed
}
// Active implements Marker.
func (m *memMarker) Active(alert model.Fingerprint) bool {
// Active implements AlertMarker.
func (m *MemMarker) Active(alert model.Fingerprint) bool {
return m.Status(alert).State == AlertStateActive
}
// Inhibited implements Marker.
func (m *memMarker) Inhibited(alert model.Fingerprint) ([]string, bool) {
// Inhibited implements AlertMarker.
func (m *MemMarker) Inhibited(alert model.Fingerprint) ([]string, bool) {
s := m.Status(alert)
return s.InhibitedBy,
s.State == AlertStateSuppressed && len(s.InhibitedBy) > 0
@@ -243,7 +301,7 @@ func (m *memMarker) Inhibited(alert model.Fingerprint) ([]string, bool) {
// Silenced returns whether the alert for the given Fingerprint is in the
// Silenced state, any associated silence IDs, and the silences state version
// the result is based on.
func (m *memMarker) Silenced(alert model.Fingerprint) (activeIDs, pendingIDs []string, version int, silenced bool) {
func (m *MemMarker) Silenced(alert model.Fingerprint) (activeIDs, pendingIDs []string, version int, silenced bool) {
s := m.Status(alert)
return s.SilencedBy, s.pendingSilences, s.silencesVersion,
s.State == AlertStateSuppressed && len(s.SilencedBy) > 0
@@ -410,15 +468,17 @@ func (a *Alert) Merge(o *Alert) *Alert {
}
// A Muter determines whether a given label set is muted. Implementers that
// maintain an underlying Marker are expected to update it during a call of
// maintain an underlying AlertMarker are expected to update it during a call of
// Mutes.
type Muter interface {
Mutes(model.LabelSet) bool
}
// TimeMuter determines if alerts should be muted based on the specified current time and active time interval on the route.
// A TimeMuter determines if the time is muted by one or more active or mute
// time intervals. If the time is muted, it returns true and the names of the
// time intervals that muted it. Otherwise, it returns false and a nil slice.
type TimeMuter interface {
Mutes(timeIntervalName []string, now time.Time) (bool, error)
Mutes(timeIntervalNames []string, now time.Time) (bool, []string, error)
}
// A MuteFunc is a function that implements the Muter interface.
@@ -458,7 +518,7 @@ type Silence struct {
}
// Expired return if the silence is expired
// meaning that both StartsAt and EndsAt are equal
// meaning that both StartsAt and EndsAt are equal.
func (s *Silence) Expired() bool {
return s.StartsAt.Equal(s.EndsAt)
}

View File

@@ -0,0 +1,123 @@
// Copyright 2024 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package templates
import (
"errors"
"fmt"
"math"
"strconv"
"time"
"github.com/prometheus/common/model"
)
var errNaNOrInf = errors.New("value is NaN or Inf")
func ConvertToFloat(i interface{}) (float64, error) {
switch v := i.(type) {
case float64:
return v, nil
case string:
return strconv.ParseFloat(v, 64)
case int:
return float64(v), nil
case uint:
return float64(v), nil
case int64:
return float64(v), nil
case uint64:
return float64(v), nil
case time.Duration:
return v.Seconds(), nil
default:
return 0, fmt.Errorf("can't convert %T to float", v)
}
}
func FloatToTime(v float64) (*time.Time, error) {
if math.IsNaN(v) || math.IsInf(v, 0) {
return nil, errNaNOrInf
}
timestamp := v * 1e9
if timestamp > math.MaxInt64 || timestamp < math.MinInt64 {
return nil, fmt.Errorf("%v cannot be represented as a nanoseconds timestamp since it overflows int64", v)
}
t := model.TimeFromUnixNano(int64(timestamp)).Time().UTC()
return &t, nil
}
func HumanizeDuration(i interface{}) (string, error) {
v, err := ConvertToFloat(i)
if err != nil {
return "", err
}
if math.IsNaN(v) || math.IsInf(v, 0) {
return fmt.Sprintf("%.4g", v), nil
}
if v == 0 {
return fmt.Sprintf("%.4gs", v), nil
}
if math.Abs(v) >= 1 {
sign := ""
if v < 0 {
sign = "-"
v = -v
}
duration := int64(v)
seconds := duration % 60
minutes := (duration / 60) % 60
hours := (duration / 60 / 60) % 24
days := duration / 60 / 60 / 24
// For days to minutes, we display seconds as an integer.
if days != 0 {
return fmt.Sprintf("%s%dd %dh %dm %ds", sign, days, hours, minutes, seconds), nil
}
if hours != 0 {
return fmt.Sprintf("%s%dh %dm %ds", sign, hours, minutes, seconds), nil
}
if minutes != 0 {
return fmt.Sprintf("%s%dm %ds", sign, minutes, seconds), nil
}
// For seconds, we display 4 significant digits.
return fmt.Sprintf("%s%.4gs", sign, v), nil
}
prefix := ""
for _, p := range []string{"m", "u", "n", "p", "f", "a", "z", "y"} {
if math.Abs(v) >= 1 {
break
}
prefix = p
v *= 1000
}
return fmt.Sprintf("%.4g%ss", v, prefix), nil
}
func HumanizeTimestamp(i interface{}) (string, error) {
v, err := ConvertToFloat(i)
if err != nil {
return "", err
}
tm, err := FloatToTime(v)
switch {
case errors.Is(err, errNaNOrInf):
return fmt.Sprintf("%.4g", v), nil
case err != nil:
return "", err
}
return fmt.Sprint(tm), nil
}

201
vendor/github.com/prometheus/common/promslog/slog.go generated vendored Normal file
View File

@@ -0,0 +1,201 @@
// Copyright 2024 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package promslog defines standardised ways to initialize the Go standard
// library's log/slog logger.
// It should typically only ever be imported by main packages.
package promslog
import (
"fmt"
"io"
"log/slog"
"os"
"path/filepath"
"strconv"
"strings"
)
type LogStyle string
const (
SlogStyle LogStyle = "slog"
GoKitStyle LogStyle = "go-kit"
)
var (
LevelFlagOptions = []string{"debug", "info", "warn", "error"}
FormatFlagOptions = []string{"logfmt", "json"}
callerAddFunc = false
defaultWriter = os.Stderr
goKitStyleReplaceAttrFunc = func(groups []string, a slog.Attr) slog.Attr {
key := a.Key
switch key {
case slog.TimeKey:
a.Key = "ts"
// This timestamp format differs from RFC3339Nano by using .000 instead
// of .999999999 which changes the timestamp from 9 variable to 3 fixed
// decimals (.130 instead of .130987456).
t := a.Value.Time()
a.Value = slog.StringValue(t.UTC().Format("2006-01-02T15:04:05.000Z07:00"))
case slog.SourceKey:
a.Key = "caller"
src, _ := a.Value.Any().(*slog.Source)
switch callerAddFunc {
case true:
a.Value = slog.StringValue(filepath.Base(src.File) + "(" + filepath.Base(src.Function) + "):" + strconv.Itoa(src.Line))
default:
a.Value = slog.StringValue(filepath.Base(src.File) + ":" + strconv.Itoa(src.Line))
}
case slog.LevelKey:
a.Value = slog.StringValue(strings.ToLower(a.Value.String()))
default:
}
return a
}
defaultReplaceAttrFunc = func(groups []string, a slog.Attr) slog.Attr {
key := a.Key
switch key {
case slog.TimeKey:
t := a.Value.Time()
a.Value = slog.TimeValue(t.UTC())
case slog.SourceKey:
src, _ := a.Value.Any().(*slog.Source)
a.Value = slog.StringValue(filepath.Base(src.File) + ":" + strconv.Itoa(src.Line))
default:
}
return a
}
)
// AllowedLevel is a settable identifier for the minimum level a log entry
// must be have.
type AllowedLevel struct {
s string
lvl *slog.LevelVar
}
func (l *AllowedLevel) UnmarshalYAML(unmarshal func(interface{}) error) error {
var s string
type plain string
if err := unmarshal((*plain)(&s)); err != nil {
return err
}
if s == "" {
return nil
}
lo := &AllowedLevel{}
if err := lo.Set(s); err != nil {
return err
}
*l = *lo
return nil
}
func (l *AllowedLevel) String() string {
return l.s
}
// Set updates the value of the allowed level.
func (l *AllowedLevel) Set(s string) error {
if l.lvl == nil {
l.lvl = &slog.LevelVar{}
}
switch strings.ToLower(s) {
case "debug":
l.lvl.Set(slog.LevelDebug)
callerAddFunc = true
case "info":
l.lvl.Set(slog.LevelInfo)
callerAddFunc = false
case "warn":
l.lvl.Set(slog.LevelWarn)
callerAddFunc = false
case "error":
l.lvl.Set(slog.LevelError)
callerAddFunc = false
default:
return fmt.Errorf("unrecognized log level %s", s)
}
l.s = s
return nil
}
// AllowedFormat is a settable identifier for the output format that the logger can have.
type AllowedFormat struct {
s string
}
func (f *AllowedFormat) String() string {
return f.s
}
// Set updates the value of the allowed format.
func (f *AllowedFormat) Set(s string) error {
switch s {
case "logfmt", "json":
f.s = s
default:
return fmt.Errorf("unrecognized log format %s", s)
}
return nil
}
// Config is a struct containing configurable settings for the logger
type Config struct {
Level *AllowedLevel
Format *AllowedFormat
Style LogStyle
Writer io.Writer
}
// New returns a new slog.Logger. Each logged line will be annotated
// with a timestamp. The output always goes to stderr.
func New(config *Config) *slog.Logger {
if config.Level == nil {
config.Level = &AllowedLevel{}
_ = config.Level.Set("info")
}
if config.Writer == nil {
config.Writer = defaultWriter
}
logHandlerOpts := &slog.HandlerOptions{
Level: config.Level.lvl,
AddSource: true,
ReplaceAttr: defaultReplaceAttrFunc,
}
if config.Style == GoKitStyle {
logHandlerOpts.ReplaceAttr = goKitStyleReplaceAttrFunc
}
if config.Format != nil && config.Format.s == "json" {
return slog.New(slog.NewJSONHandler(config.Writer, logHandlerOpts))
}
return slog.New(slog.NewTextHandler(config.Writer, logHandlerOpts))
}
// NewNopLogger is a convenience function to return an slog.Logger that writes
// to io.Discard.
func NewNopLogger() *slog.Logger {
return slog.New(slog.NewTextHandler(io.Discard, nil))
}

View File

@@ -8,7 +8,7 @@ import (
// FilesWithExtensions returns a filter func that selects files (but not directories)
// that have any of the given extensions. For example:
//
// filter.FilesWithExtensions(".go", ".html")
// filter.FilesWithExtensions(".go", ".html")
//
// Would select both .go and .html files. It would not select any directories.
func FilesWithExtensions(exts ...string) Func {

View File

@@ -2,7 +2,7 @@
package vfsutil
import (
"io/ioutil"
"io"
"net/http"
"os"
)
@@ -35,5 +35,5 @@ func ReadFile(fs http.FileSystem, path string) ([]byte, error) {
return nil, err
}
defer rc.Close()
return ioutil.ReadAll(rc)
return io.ReadAll(rc)
}

View File

@@ -1,16 +0,0 @@
sudo: false
language: go
go:
- 1.x
- master
matrix:
allow_failures:
- go: master
fast_finish: true
install:
- # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step).
script:
- go get -t -v ./...
- diff -n <(echo -n) <(gofmt -d -s .)
- go vet ./...
- go test -v -race ./...

View File

@@ -1,7 +1,7 @@
vfsgen
======
[![Build Status](https://travis-ci.org/shurcooL/vfsgen.svg?branch=master)](https://travis-ci.org/shurcooL/vfsgen) [![GoDoc](https://godoc.org/github.com/shurcooL/vfsgen?status.svg)](https://godoc.org/github.com/shurcooL/vfsgen)
[![Go Reference](https://pkg.go.dev/badge/github.com/shurcooL/vfsgen.svg)](https://pkg.go.dev/github.com/shurcooL/vfsgen)
Package vfsgen takes an http.FileSystem (likely at `go generate` time) and
generates Go code that statically implements the provided http.FileSystem.
@@ -19,8 +19,8 @@ Features:
Installation
------------
```bash
go get -u github.com/shurcooL/vfsgen
```sh
go get github.com/shurcooL/vfsgen
```
Usage
@@ -81,7 +81,7 @@ By using build tags, you can create a development mode where assets are loaded d
For example, suppose your source filesystem is defined in a package with import path "example.com/project/data" as:
```Go
// +build dev
//go:build dev
package data
@@ -96,7 +96,7 @@ When built with the "dev" build tag, accessing `data.Assets` will read from disk
A generate helper file assets_generate.go can be invoked via "//go:generate go run -tags=dev assets_generate.go" directive:
```Go
// +build ignore
//go:build ignore
package main
@@ -177,6 +177,7 @@ It strives to be the best in its class in terms of code quality and efficiency o
### Alternatives
- [`embed`](https://go.dev/pkg/embed) - Package embed provides access to files embedded in the running Go program.
- [`go-bindata`](https://github.com/jteeuwen/go-bindata) - Reads from disk, generates Go code that provides access to data via a [custom API](https://github.com/jteeuwen/go-bindata#accessing-an-asset).
- [`go-bindata-assetfs`](https://github.com/elazarl/go-bindata-assetfs) - Takes output of go-bindata and provides a wrapper that implements `http.FileSystem` interface (the same as what vfsgen outputs directly).
- [`becky`](https://github.com/tv42/becky) - Embeds assets as string literals in Go source.
@@ -195,6 +196,13 @@ Attribution
This package was originally based on the excellent work by [@jteeuwen](https://github.com/jteeuwen) on [`go-bindata`](https://github.com/jteeuwen/go-bindata) and [@elazarl](https://github.com/elazarl) on [`go-bindata-assetfs`](https://github.com/elazarl/go-bindata-assetfs).
Directories
-----------
| Path | Synopsis |
|------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------|
| [cmd/vfsgendev](https://pkg.go.dev/github.com/shurcooL/vfsgen/cmd/vfsgendev) | vfsgendev is a convenience tool for using vfsgen in a common development configuration. |
License
-------

View File

@@ -5,7 +5,6 @@ import (
"compress/gzip"
"errors"
"io"
"io/ioutil"
"net/http"
"os"
pathpkg "path"
@@ -47,7 +46,7 @@ func Generate(input http.FileSystem, opt Options) error {
}
// Write output file (all at once).
err = ioutil.WriteFile(opt.Filename, buf.Bytes(), 0644)
err = os.WriteFile(opt.Filename, buf.Bytes(), 0644)
return err
}
@@ -217,7 +216,7 @@ var t = template.Must(template.New("").Funcs(template.FuncMap{
},
}).Parse(`{{define "Header"}}// Code generated by vfsgen; DO NOT EDIT.
{{with .BuildTags}}// +build {{.}}
{{with .BuildTags}}//go:build {{.}}
{{end}}package {{.PackageName}}
@@ -226,7 +225,6 @@ import (
"compress/gzip"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
pathpkg "path"
@@ -358,7 +356,7 @@ func (f *vfsgen۰CompressedFile) Read(p []byte) (n int, err error) {
}
if f.grPos < f.seekPos {
// Fast-forward.
_, err = io.CopyN(ioutil.Discard, f.gr, f.seekPos-f.grPos)
_, err = io.CopyN(io.Discard, f.gr, f.seekPos-f.grPos)
if err != nil {
return 0, err
}
@@ -386,9 +384,8 @@ func (f *vfsgen۰CompressedFile) Close() error {
return f.gr.Close()
}
{{else}}
// We already imported "compress/gzip" and "io/ioutil", but ended up not using them. Avoid unused import error.
var _ = gzip.Reader{}
var _ = ioutil.Discard
// We already imported "compress/gzip" but ended up not using it. Avoid unused import error.
var _ *gzip.Reader
{{end}}{{if .HasFile}}
// vfsgen۰FileInfo is a static definition of an uncompressed file (because it's not worth gzip compressing).
type vfsgen۰FileInfo struct {

24
vendor/modules.txt vendored
View File

@@ -439,8 +439,8 @@ github.com/go-acme/lego/v4/challenge
# github.com/go-asn1-ber/asn1-ber v1.5.7
## explicit; go 1.13
github.com/go-asn1-ber/asn1-ber
# github.com/go-chi/chi/v5 v5.2.0
## explicit; go 1.14
# github.com/go-chi/chi/v5 v5.2.1
## explicit; go 1.20
github.com/go-chi/chi/v5
github.com/go-chi/chi/v5/middleware
# github.com/go-chi/render v1.0.3
@@ -1194,7 +1194,7 @@ github.com/open-policy-agent/opa/v1/types
github.com/open-policy-agent/opa/v1/util
github.com/open-policy-agent/opa/v1/util/decoding
github.com/open-policy-agent/opa/v1/version
# github.com/opencloud-eu/reva/v2 v2.27.3-0.20250312134906-766c69c5d1be
# github.com/opencloud-eu/reva/v2 v2.27.3-0.20250314084055-d2fcfe6b3445
## explicit; go 1.24.1
github.com/opencloud-eu/reva/v2/cmd/revad/internal/grace
github.com/opencloud-eu/reva/v2/cmd/revad/runtime
@@ -1629,12 +1629,12 @@ github.com/pmezard/go-difflib/difflib
## explicit; go 1.16
github.com/pquerna/cachecontrol
github.com/pquerna/cachecontrol/cacheobject
# github.com/prometheus/alertmanager v0.27.0
## explicit; go 1.21
# github.com/prometheus/alertmanager v0.28.1
## explicit; go 1.22.0
github.com/prometheus/alertmanager/asset
github.com/prometheus/alertmanager/featurecontrol
github.com/prometheus/alertmanager/matchers/compat
github.com/prometheus/alertmanager/matchers/parse
github.com/prometheus/alertmanager/matcher/compat
github.com/prometheus/alertmanager/matcher/parse
github.com/prometheus/alertmanager/pkg/labels
github.com/prometheus/alertmanager/template
github.com/prometheus/alertmanager/types
@@ -1652,7 +1652,9 @@ github.com/prometheus/client_model/go
# github.com/prometheus/common v0.62.0
## explicit; go 1.21
github.com/prometheus/common/expfmt
github.com/prometheus/common/helpers/templates
github.com/prometheus/common/model
github.com/prometheus/common/promslog
# github.com/prometheus/procfs v0.15.1
## explicit; go 1.20
github.com/prometheus/procfs
@@ -1778,13 +1780,13 @@ github.com/shamaton/msgpack/v2/internal/encoding
github.com/shamaton/msgpack/v2/internal/stream/decoding
github.com/shamaton/msgpack/v2/internal/stream/encoding
github.com/shamaton/msgpack/v2/time
# github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749
## explicit
# github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c
## explicit; go 1.19
github.com/shurcooL/httpfs/filter
github.com/shurcooL/httpfs/union
github.com/shurcooL/httpfs/vfsutil
# github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546
## explicit
# github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92
## explicit; go 1.19
github.com/shurcooL/vfsgen
# github.com/sirupsen/logrus v1.9.3
## explicit; go 1.13