bump reva 2.28

This commit is contained in:
Viktor Scharf
2025-03-17 14:49:17 +01:00
parent 69c0da8715
commit e15989e993
29 changed files with 718 additions and 189 deletions
@@ -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))
@@ -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
}
@@ -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 == "" {
-5
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
File diff suppressed because one or more lines are too long
+44 -8
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 }
@@ -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
@@ -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()) {
@@ -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
+1 -1
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
+1 -1
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
+2 -2
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 [""]
+1 -1
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)
+59
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 }}
+15
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.
+101 -41
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)
}
+123
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
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))
}
+1 -1
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 {
+2 -2
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)
}
-16
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 ./...
+13 -5
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
-------
+5 -8
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 {