mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2026-07-24 01:31:20 +03:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0aaf6f8a33 | ||
|
|
00a26b9d32 | ||
|
|
43d4cc61dc | ||
|
|
e85d61a98c | ||
|
|
588e996bd5 | ||
|
|
edb702e326 | ||
|
|
84c2d86a00 | ||
|
|
2da774b8ad | ||
|
|
650cf31f32 | ||
|
|
3dfb3336f9 | ||
|
|
344d3e1911 | ||
|
|
8b94ca0e30 | ||
|
|
a9ae7e1da0 | ||
|
|
ba59d78f10 | ||
|
|
1c774f9216 | ||
|
|
7ea9bfa8d6 | ||
|
|
2b95c11095 | ||
|
|
655ed9e8d1 | ||
|
|
4f676b4012 | ||
|
|
dce9514c65 | ||
|
|
25598da441 | ||
|
|
6250255c4f |
6
.github/workflows/codeql-analysis-go.yml
vendored
6
.github/workflows/codeql-analysis-go.yml
vendored
@@ -54,14 +54,14 @@ jobs:
|
||||
restore-keys: go-artifacts-${{ runner.os }}-codeql-analyze-${{ steps.go.outputs.go-version }}-
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
|
||||
uses: github/codeql-action/init@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
|
||||
with:
|
||||
languages: go
|
||||
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
|
||||
uses: github/codeql-action/autobuild@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
|
||||
uses: github/codeql-action/analyze@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
|
||||
with:
|
||||
category: 'language:go'
|
||||
|
||||
8
.github/workflows/vmui.yml
vendored
8
.github/workflows/vmui.yml
vendored
@@ -26,9 +26,7 @@ jobs:
|
||||
vmui-checks:
|
||||
name: VMUI Checks (lint, test, typecheck)
|
||||
permissions:
|
||||
checks: write
|
||||
contents: read
|
||||
pull-requests: read
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Code checkout
|
||||
@@ -70,12 +68,6 @@ jobs:
|
||||
env:
|
||||
VMUI_SKIP_INSTALL: true
|
||||
|
||||
- name: Annotate Code Linting Results
|
||||
uses: ataylorme/eslint-annotate-action@d57a1193d4c59cbfbf3f86c271f42612f9dbd9e9 # 3.0.0
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
report-json: app/vmui/packages/vmui/vmui-lint-report.json
|
||||
|
||||
- name: Check overall status
|
||||
run: |
|
||||
echo "Lint status: ${{ steps.lint.outcome }}"
|
||||
|
||||
@@ -151,17 +151,23 @@ func newHTTPClient(argIdx int, remoteWriteURL, sanitizedURL string, fq *persiste
|
||||
}
|
||||
tr.Proxy = http.ProxyURL(pu)
|
||||
}
|
||||
|
||||
hc := &http.Client{
|
||||
Transport: authCfg.NewRoundTripper(tr),
|
||||
Timeout: sendTimeout.GetOptionalArg(argIdx),
|
||||
}
|
||||
rwURL, err := url.Parse(remoteWriteURL)
|
||||
if err != nil {
|
||||
logger.Fatalf("BUG: cannot parse already parsed -remoteWrite.url=%q: %s", remoteWriteURL, err)
|
||||
}
|
||||
hc.Transport, rwURL = httputil.NewLoadBalancerTransport(hc.Transport, rwURL)
|
||||
retryMaxIntervalFlag := retryMaxTime
|
||||
if retryMaxInterval.String() != "" {
|
||||
retryMaxIntervalFlag = retryMaxInterval
|
||||
}
|
||||
c := &client{
|
||||
sanitizedURL: sanitizedURL,
|
||||
remoteWriteURL: remoteWriteURL,
|
||||
remoteWriteURL: rwURL.String(),
|
||||
authCfg: authCfg,
|
||||
awsCfg: awsCfg,
|
||||
fq: fq,
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/vmalertutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/flagutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httputil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
|
||||
)
|
||||
|
||||
@@ -94,6 +95,12 @@ func Init(extraParams url.Values) (QuerierBuilder, error) {
|
||||
tr.MaxIdleConns = tr.MaxIdleConnsPerHost
|
||||
}
|
||||
tr.IdleConnTimeout = *idleConnectionTimeout
|
||||
hc := &http.Client{Transport: tr}
|
||||
datasourceURL, err := url.Parse(*addr)
|
||||
if err != nil {
|
||||
logger.Fatalf("BUG: cannot parse already parsed -datasource.url=%q: %s", *addr, err)
|
||||
}
|
||||
hc.Transport, datasourceURL = httputil.NewLoadBalancerTransport(tr, datasourceURL)
|
||||
|
||||
if extraParams == nil {
|
||||
extraParams = url.Values{}
|
||||
@@ -120,9 +127,9 @@ func Init(extraParams url.Values) (QuerierBuilder, error) {
|
||||
}
|
||||
|
||||
return &Client{
|
||||
c: &http.Client{Transport: tr},
|
||||
c: hc,
|
||||
authCfg: authCfg,
|
||||
datasourceURL: strings.TrimSuffix(*addr, "/"),
|
||||
datasourceURL: strings.TrimSuffix(datasourceURL.String(), "/"),
|
||||
appendTypePrefix: *appendTypePrefix,
|
||||
queryStep: *queryStep,
|
||||
extraParams: extraParams,
|
||||
|
||||
@@ -4,12 +4,14 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/datasource"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/vmalertutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/flagutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httputil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
|
||||
)
|
||||
|
||||
@@ -76,7 +78,13 @@ func Init() (datasource.QuerierBuilder, error) {
|
||||
return nil, fmt.Errorf("failed to create transport for -remoteRead.url=%q: %w", *addr, err)
|
||||
}
|
||||
tr.IdleConnTimeout = *idleConnectionTimeout
|
||||
c := &http.Client{Transport: tr}
|
||||
rrURL, err := url.Parse(*addr)
|
||||
if err != nil {
|
||||
logger.Fatalf("BUG: cannot parse already parsed -remoteRead.url=%q: %s", *addr, err)
|
||||
}
|
||||
|
||||
c.Transport, rrURL = httputil.NewLoadBalancerTransport(tr, rrURL)
|
||||
endpointParams, err := flagutil.ParseJSONMap(*oauth2EndpointParams)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse JSON for -remoteRead.oauth2.endpointParams=%s: %w", *oauth2EndpointParams, err)
|
||||
@@ -89,6 +97,5 @@ func Init() (datasource.QuerierBuilder, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to configure auth: %w", err)
|
||||
}
|
||||
c := &http.Client{Transport: tr}
|
||||
return datasource.NewPrometheusClient(*addr, authCfg, false, c), nil
|
||||
return datasource.NewPrometheusClient(rrURL.String(), authCfg, false, c), nil
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -111,12 +112,18 @@ func NewClient(ctx context.Context, cfg Config) (*Client, error) {
|
||||
if cfg.Concurrency > 0 {
|
||||
cc = cfg.Concurrency
|
||||
}
|
||||
hc := &http.Client{
|
||||
Timeout: *sendTimeout,
|
||||
Transport: cfg.Transport,
|
||||
}
|
||||
rwURL, err := url.Parse(cfg.Addr)
|
||||
if err != nil {
|
||||
logger.Fatalf("cannot parse already parsed -remoteWrite.url=%q: %s", cfg.Addr, err)
|
||||
}
|
||||
hc.Transport, rwURL = httputil.NewLoadBalancerTransport(hc.Transport, rwURL)
|
||||
c := &Client{
|
||||
c: &http.Client{
|
||||
Timeout: *sendTimeout,
|
||||
Transport: cfg.Transport,
|
||||
},
|
||||
addr: strings.TrimSuffix(cfg.Addr, "/"),
|
||||
c: hc,
|
||||
addr: strings.TrimSuffix(rwURL.String(), "/"),
|
||||
authCfg: cfg.AuthCfg,
|
||||
flushInterval: cfg.FlushInterval,
|
||||
maxBatchSize: cfg.MaxBatchSize,
|
||||
|
||||
@@ -75,10 +75,13 @@ type GroupAlerts struct {
|
||||
// ApiRule represents a Rule for web view
|
||||
// see https://github.com/prometheus/compliance/blob/main/alert_generator/specification.md#get-apiv1rules
|
||||
type ApiRule struct {
|
||||
// State must be one of these under following scenarios
|
||||
// "pending": at least 1 alert in the rule in pending state and no other alert in firing ruleState.
|
||||
// "firing": at least 1 alert in the rule in firing state.
|
||||
// "inactive": no alert in the rule in firing or pending state.
|
||||
// Rule state must be one of these under following scenarios:
|
||||
// "pending": at least 1 alert in the rule in pending state and no other alert in firing state. (only for alerting rules)
|
||||
// "firing": at least 1 alert in the rule in firing state. (only for alerting rules)
|
||||
// "inactive": rule's last evaluation was successful but no alert in the rule in firing or pending state. (only for alerting rules)
|
||||
// "unhealthy": rule's last evaluation was failed with error. (for both alerting and recording rules)
|
||||
// "nomatch": rule's last evaluation was successful but no time series matched the rule's expression. (for both alerting and recording rules)
|
||||
// "ok": the recording rule's last evaluation was successful. (only for recording rules)
|
||||
State string `json:"state"`
|
||||
Name string `json:"name"`
|
||||
// Query represents Rule's `expression` field
|
||||
@@ -237,6 +240,7 @@ func NewAlertAPI(ar *AlertingRule, a *notifier.Alert) *ApiAlert {
|
||||
}
|
||||
|
||||
func (r *ApiRule) ExtendState() {
|
||||
// if alerting rule already has alerts, then state is already set to either "pending" or "firing" and we don't need to change it
|
||||
if len(r.Alerts) > 0 {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ func (rh *requestHandler) handler(w http.ResponseWriter, r *http.Request) bool {
|
||||
}
|
||||
WriteRule(w, r, rule)
|
||||
return true
|
||||
// current used by old vmalert UI and Grafana Alerts
|
||||
// used by old vmalert UI
|
||||
case "/vmalert/groups", "/rules":
|
||||
rf, err := newRulesFilter(r)
|
||||
if err != nil {
|
||||
@@ -128,6 +128,8 @@ func (rh *requestHandler) handler(w http.ResponseWriter, r *http.Request) bool {
|
||||
state = rf.states[0]
|
||||
rf.states = rf.states[:1]
|
||||
}
|
||||
// enable extendedStates by default for vmalert UI
|
||||
rf.extendedStates = true
|
||||
lr := rh.groups(rf)
|
||||
WriteListGroups(w, r, lr.Data.Groups, state)
|
||||
return true
|
||||
@@ -543,6 +545,8 @@ func (rh *requestHandler) groups(rf *rulesFilter) *listGroupsResponse {
|
||||
if !groupFound && !strings.Contains(strings.ToLower(rule.Name), rf.search) {
|
||||
continue
|
||||
}
|
||||
// extendedStates is used by the vmalert UI to extend the rule state with values such as "nomatch" and "unhealthy".
|
||||
// those states are not supported by Grafana and not part of the Prometheus API spec yet
|
||||
if rf.extendedStates {
|
||||
rule.ExtendState()
|
||||
}
|
||||
|
||||
@@ -130,9 +130,12 @@
|
||||
data-bs-target="#item-{%s g.ID %}"
|
||||
>
|
||||
<span class="d-flex gap-2">
|
||||
{% if g.States["unhealthy"] > 0 %}<span class="badge bg-danger" title="Number of rules with status Error">{%d g.States["unhealthy"] %}</span> {% endif %}
|
||||
{% if g.States["nomatch"] > 0 %}<span class="badge bg-warning" title="Number of rules with status NoMatch">{%d g.States["nomatch"] %}</span> {% endif %}
|
||||
<span class="badge bg-success" title="Number of rules with status Ok">{%d g.States["ok"] %}</span>
|
||||
{% if g.States["inactive"] > 0 %}<span class="badge bg-light text-success border border-success" title="None of the alert instances is in a pending or firing state">{%d g.States["inactive"] %} inactive</span> {% endif %}
|
||||
{% if g.States["pending"] > 0 %}<span class="badge bg-warning text-dark" title="At least one alert instance is pending">{%d g.States["pending"] %} pending</span> {% endif %}
|
||||
{% if g.States["firing"] > 0 %}<span class="badge bg-danger" title="At least one alert instance is firing">{%d g.States["firing"] %} firing</span> {% endif %}
|
||||
{% if g.States["ok"] > 0 %}<span class="badge bg-success" title="Recording rule last evaluation succeeded">{%d g.States["ok"] %} ok</span>{% endif %}
|
||||
{% if g.States["unhealthy"] > 0 %}<span class="badge bg-danger" title="Last evaluation failed with an error">{%d g.States["unhealthy"] %} unhealthy</span> {% endif %}
|
||||
{% if g.States["nomatch"] > 0 %}<span class="badge bg-warning" title="Rule expression matched no time series">{%d g.States["nomatch"] %} nomatch</span> {% endif %}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
@@ -169,7 +172,7 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" class="w-60">Rule</th>
|
||||
<th scope="col" class="w-20" class="text-center" title="How many series were produced by the rule">Series</th>
|
||||
<th scope="col" class="w-20" class="text-center" title="How many series were produced by the rule">Series Returned</th>
|
||||
<th scope="col" class="w-20" class="text-center" title="How many seconds ago rule was executed">Updated</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -188,8 +191,21 @@
|
||||
{% else %}
|
||||
<b>record:</b> {%s r.Name %}
|
||||
{% endif %}
|
||||
|
|
||||
{% if r.State == "inactive" %}
|
||||
<span><a class="badge bg-success">{%s r.State %}</a></span>
|
||||
{% elseif r.State == "pending" %}
|
||||
<span><a class="badge bg-warning">{%s r.State %}</a></span>
|
||||
{% elseif r.State == "firing" %}
|
||||
<span><a class="badge bg-danger">{%s r.State %}</a></span>
|
||||
{% elseif r.State == "ok" %}
|
||||
<span><a class="badge bg-success">{%s r.State %}</a></span>
|
||||
{% elseif r.State == "unhealthy" %}
|
||||
<span><a class="badge bg-danger">{%s r.State %}</a></span>
|
||||
{% elseif r.State == "nomatch" %}
|
||||
<span><a class="badge bg-warning">{%s r.State %}</a></span>
|
||||
{% endif %}
|
||||
{%= seriesFetchedWarn(prefix, &r) %}
|
||||
|
|
||||
<span><a target="_blank" href="{%s prefix+r.WebLink() %}">Details</a></span>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -35,7 +35,10 @@ var (
|
||||
"By default, or when set to 0, -maxBackfillAge equals to -retentionPeriod, e.g. it is unlimited within the configured retention. "+
|
||||
"This can be useful for limiting ingestion of historical samples, for example, when older data has been moved to another storage tier. "+
|
||||
"See https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#retention")
|
||||
vmselectAddr = flag.String("vmselectAddr", "", "TCP address to accept connections from vmselect services")
|
||||
vmselectAddr = flag.String("vmselectAddr", "", "TCP address to listen for incoming connections from vmselect. "+
|
||||
"When set, the node will be able to accept cluster-native vmselect RPC requests as if it were vmstorage. "+
|
||||
"The tenant ID assigned to this node's data is controlled by -accountID and -projectID flags. "+
|
||||
"See https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#multi-tenancy")
|
||||
vmselectDisableRPCCompression = flag.Bool("rpc.disableCompression", false, "Whether to disable compression of the data sent from vmstorage to vmselect. "+
|
||||
"This reduces CPU usage at the cost of higher network bandwidth usage")
|
||||
snapshotAuthKey = flagutil.NewPassword("snapshotAuthKey", "authKey, which must be passed in query string to /snapshot* pages. It overrides -httpAuth.*")
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"start": "vite",
|
||||
"start:playground": "cross-env PLAYGROUND=true npm run start",
|
||||
"build": "vite build",
|
||||
"lint": "eslint --output-file vmui-lint-report.json --format json 'src/**/*.{ts,tsx}'",
|
||||
"lint": "eslint --format stylish 'src/**/*.{ts,tsx}'",
|
||||
"lint:local": "eslint --ext .ts,.tsx -f stylish src",
|
||||
"lint:fix": "eslint 'src/**/*.{ts,tsx}' --fix",
|
||||
"copy-metricsql-docs": "cp ../../../../docs/MetricsQL.md src/assets/MetricsQL.md || true",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FC, useEffect, useRef, useState } from "preact/compat";
|
||||
import { FC, useEffect, useRef } from "preact/compat";
|
||||
import { useTimeDispatch } from "../../../../state/time/TimeStateContext";
|
||||
import { getAppModeEnable } from "../../../../utils/app-mode";
|
||||
import Button from "../../../Main/Button/Button";
|
||||
@@ -9,27 +9,38 @@ import classNames from "classnames";
|
||||
import Tooltip from "../../../Main/Tooltip/Tooltip";
|
||||
import useDeviceDetect from "../../../../hooks/useDeviceDetect";
|
||||
import useBoolean from "../../../../hooks/useBoolean";
|
||||
import { getMillisecondsFromDuration } from "../../../../utils/time";
|
||||
import { useSearchParams } from "react-router";
|
||||
|
||||
interface AutoRefreshOption {
|
||||
seconds: number
|
||||
title: string
|
||||
}
|
||||
|
||||
const delayOptions: AutoRefreshOption[] = [
|
||||
{ seconds: 0, title: "Off" },
|
||||
{ seconds: 1, title: "1s" },
|
||||
{ seconds: 2, title: "2s" },
|
||||
{ seconds: 5, title: "5s" },
|
||||
{ seconds: 10, title: "10s" },
|
||||
{ seconds: 30, title: "30s" },
|
||||
{ seconds: 60, title: "1m" },
|
||||
{ seconds: 300, title: "5m" },
|
||||
{ seconds: 900, title: "15m" },
|
||||
{ seconds: 1800, title: "30m" },
|
||||
{ seconds: 3600, title: "1h" },
|
||||
{ seconds: 7200, title: "2h" }
|
||||
const delayOptions = [
|
||||
"Off",
|
||||
"1s",
|
||||
"2s",
|
||||
"5s",
|
||||
"10s",
|
||||
"30s",
|
||||
"1m",
|
||||
"5m",
|
||||
"15m",
|
||||
"30m",
|
||||
"1h",
|
||||
"2h"
|
||||
];
|
||||
|
||||
const DEFAULT_OPTION = delayOptions[0];
|
||||
|
||||
const MIN_REFRESH_MS = 1000;
|
||||
const MAX_REFRESH_MS = getMillisecondsFromDuration(delayOptions[delayOptions.length - 1]);
|
||||
const REFRESH_URL_PARAM = "refresh";
|
||||
|
||||
const durationToMs = (dur: string | null) => {
|
||||
return dur ? getMillisecondsFromDuration(dur) : 0;
|
||||
};
|
||||
|
||||
const isValidDelay = (ms: number) => {
|
||||
return ms >= MIN_REFRESH_MS && ms <= MAX_REFRESH_MS;
|
||||
};
|
||||
|
||||
interface ExecutionControlsProps {
|
||||
tooltip: string;
|
||||
useAutorefresh?: boolean;
|
||||
@@ -38,12 +49,14 @@ interface ExecutionControlsProps {
|
||||
|
||||
export const ExecutionControls: FC<ExecutionControlsProps> = ({ tooltip, useAutorefresh, closeModal }) => {
|
||||
const { isMobile } = useDeviceDetect();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const dispatch = useTimeDispatch();
|
||||
const appModeEnable = getAppModeEnable();
|
||||
const [autoRefresh, setAutoRefresh] = useState(false);
|
||||
|
||||
const [selectedDelay, setSelectedDelay] = useState<AutoRefreshOption>(delayOptions[0]);
|
||||
const rawDelay = searchParams.get(REFRESH_URL_PARAM);
|
||||
const msDelay = durationToMs(rawDelay);
|
||||
const selectedDelay = isValidDelay(msDelay) ? rawDelay : DEFAULT_OPTION;
|
||||
|
||||
const {
|
||||
value: openOptions,
|
||||
@@ -52,11 +65,20 @@ export const ExecutionControls: FC<ExecutionControlsProps> = ({ tooltip, useAuto
|
||||
} = useBoolean(false);
|
||||
const optionsButtonRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const handleChange = (d: AutoRefreshOption) => {
|
||||
if ((autoRefresh && !d.seconds) || (!autoRefresh && d.seconds)) {
|
||||
setAutoRefresh(prev => !prev);
|
||||
}
|
||||
setSelectedDelay(d);
|
||||
const handleChange = (dur: string) => () => {
|
||||
setSearchParams(prev => {
|
||||
const nextParams = new URLSearchParams(prev);
|
||||
const ms = durationToMs(dur);
|
||||
|
||||
if (ms) {
|
||||
nextParams.set(REFRESH_URL_PARAM, `${dur}`);
|
||||
} else {
|
||||
nextParams.delete(REFRESH_URL_PARAM);
|
||||
}
|
||||
|
||||
return nextParams;
|
||||
});
|
||||
|
||||
handleCloseOptions();
|
||||
};
|
||||
|
||||
@@ -68,23 +90,19 @@ export const ExecutionControls: FC<ExecutionControlsProps> = ({ tooltip, useAuto
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const delay = selectedDelay.seconds;
|
||||
const ms = durationToMs(selectedDelay);
|
||||
let timer: number;
|
||||
if (autoRefresh) {
|
||||
|
||||
if (useAutorefresh && isValidDelay(ms)) {
|
||||
timer = setInterval(() => {
|
||||
dispatch({ type: "RUN_QUERY" });
|
||||
}, delay * 1000) as unknown as number;
|
||||
} else {
|
||||
setSelectedDelay(delayOptions[0]);
|
||||
}, ms) as unknown as number;
|
||||
}
|
||||
return () => {
|
||||
timer && clearInterval(timer);
|
||||
};
|
||||
}, [selectedDelay, autoRefresh]);
|
||||
|
||||
const createHandlerChange = (d: AutoRefreshOption) => () => {
|
||||
handleChange(d);
|
||||
};
|
||||
return () => {
|
||||
clearInterval(timer);
|
||||
};
|
||||
}, [selectedDelay, useAutorefresh]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -106,7 +124,7 @@ export const ExecutionControls: FC<ExecutionControlsProps> = ({ tooltip, useAuto
|
||||
<span className="vm-mobile-option__icon"><RestartIcon/></span>
|
||||
<div className="vm-mobile-option-text">
|
||||
<span className="vm-mobile-option-text__label">Auto-refresh</span>
|
||||
<span className="vm-mobile-option-text__value">{selectedDelay.title}</span>
|
||||
<span className="vm-mobile-option-text__value">{selectedDelay}</span>
|
||||
</div>
|
||||
<span className="vm-mobile-option__arrow"><ArrowDownIcon/></span>
|
||||
</div>
|
||||
@@ -139,7 +157,7 @@ export const ExecutionControls: FC<ExecutionControlsProps> = ({ tooltip, useAuto
|
||||
)}
|
||||
onClick={toggleOpenOptions}
|
||||
>
|
||||
{selectedDelay.title}
|
||||
{selectedDelay}
|
||||
</Button>
|
||||
</div>
|
||||
</Tooltip>
|
||||
@@ -187,12 +205,12 @@ export const ExecutionControls: FC<ExecutionControlsProps> = ({ tooltip, useAuto
|
||||
className={classNames({
|
||||
"vm-list-item": true,
|
||||
"vm-list-item_mobile": isMobile,
|
||||
"vm-list-item_active": d.seconds === selectedDelay.seconds
|
||||
"vm-list-item_active": d === selectedDelay
|
||||
})}
|
||||
key={d.seconds}
|
||||
onClick={createHandlerChange(d)}
|
||||
key={d}
|
||||
onClick={handleChange(d)}
|
||||
>
|
||||
{d.title}
|
||||
{d}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import dayjs from "dayjs";
|
||||
import { getNanoTimestamp, parseSupportedDuration } from "./time";
|
||||
import { getMillisecondsFromDuration, getNanoTimestamp, parseSupportedDuration } from "./time";
|
||||
|
||||
describe("Time utils", () => {
|
||||
describe("getNanoTimestamp", () => {
|
||||
@@ -82,4 +82,19 @@ describe("Time utils", () => {
|
||||
expect(parseSupportedDuration(" ")).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("getMillisecondsFromDuration", () => {
|
||||
it("should convert valid durations to milliseconds", () => {
|
||||
expect(getMillisecondsFromDuration("1s")).toBe(1000);
|
||||
expect(getMillisecondsFromDuration("1.5s")).toBe(1500);
|
||||
expect(getMillisecondsFromDuration("1m30s")).toBe(90000);
|
||||
expect(getMillisecondsFromDuration("1h 30m")).toBe(5400000);
|
||||
expect(getMillisecondsFromDuration("500ms")).toBe(500);
|
||||
});
|
||||
|
||||
it("should return zero when duration cannot be parsed", () => {
|
||||
expect(getMillisecondsFromDuration("garbage")).toBe(0);
|
||||
expect(getMillisecondsFromDuration("")).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -100,6 +100,10 @@ export const getSecondsFromDuration = (dur: string) => {
|
||||
return dayjs.duration(durObject).asSeconds();
|
||||
};
|
||||
|
||||
export const getMillisecondsFromDuration = (dur: string): number => {
|
||||
return getSecondsFromDuration(dur) * 1000;
|
||||
};
|
||||
|
||||
const instantQueryViews = [DisplayType.table, DisplayType.code];
|
||||
export const getStepFromDuration = (dur: number, histogram?: boolean, displayType?: DisplayType): string => {
|
||||
if (displayType && instantQueryViews.includes(displayType)) return roundStep(dur);
|
||||
@@ -276,4 +280,3 @@ export const getNanoTimestamp = (dateStr: string): bigint => {
|
||||
// Return the full timestamp in nanoseconds as a BigInt
|
||||
return BigInt(baseMs) * 1000000n + BigInt(extraNano);
|
||||
};
|
||||
|
||||
|
||||
@@ -5136,6 +5136,110 @@
|
||||
],
|
||||
"title": "Major page faults rate ($instance)",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "$ds"
|
||||
},
|
||||
"description": "Average duration of fsync system calls. Spikes or high latency indicates that storage I/O cannot keep up with the write rate, which may slow down data ingestion.\n\nIf this value is elevated:\n- Check disk-related metrics.\n- If using cloud storage, check IOPS and throughput limits.\n- If using NFS, check network performance, server resource usage, and any errors on the NFS server side.\n- Check for known bugs in the filesystem or kernel version being used.",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"axisSoftMin": 0,
|
||||
"barAlignment": 0,
|
||||
"barWidthFactor": 0.6,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 0,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"insertNulls": false,
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "never",
|
||||
"showValues": false,
|
||||
"spanNulls": false,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
},
|
||||
"thresholdsStyle": {
|
||||
"mode": "off"
|
||||
}
|
||||
},
|
||||
"links": [],
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "s"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 305
|
||||
},
|
||||
"id": 230,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [
|
||||
"lastNotNull",
|
||||
"max"
|
||||
],
|
||||
"displayMode": "table",
|
||||
"placement": "bottom",
|
||||
"showLegend": true,
|
||||
"sortBy": "Last *",
|
||||
"sortDesc": true
|
||||
},
|
||||
"tooltip": {
|
||||
"hideZeros": true,
|
||||
"mode": "multi",
|
||||
"sort": "desc"
|
||||
}
|
||||
},
|
||||
"pluginVersion": "12.2.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "$ds"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "rate(vm_filestream_fsync_duration_seconds_total{job=~\"$job_storage\", instance=~\"$instance\"}[$__rate_interval]) / rate(vm_filestream_fsync_calls_total{job=~\"$job_storage\", instance=~\"$instance\"}[$__rate_interval])",
|
||||
"format": "time_series",
|
||||
"instant": false,
|
||||
"legendFormat": "{{instance}}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Fsync avg duration ($instance)",
|
||||
"type": "timeseries"
|
||||
}
|
||||
],
|
||||
"title": "Troubleshooting",
|
||||
|
||||
@@ -5181,6 +5181,110 @@
|
||||
],
|
||||
"title": "Major page faults rate",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "$ds"
|
||||
},
|
||||
"description": "Average duration of fsync system calls. Spikes or high latency indicates that storage I/O cannot keep up with the write rate, which may slow down data ingestion.\n\nIf this value is elevated:\n- Check disk-related metrics.\n- If using cloud storage, check IOPS and throughput limits.\n- If using NFS, check network performance, server resource usage, and any errors on the NFS server side.\n- Check for known bugs in the filesystem or kernel version being used.",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"axisSoftMin": 0,
|
||||
"barAlignment": 0,
|
||||
"barWidthFactor": 0.6,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 0,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"insertNulls": false,
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "never",
|
||||
"showValues": false,
|
||||
"spanNulls": false,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
},
|
||||
"thresholdsStyle": {
|
||||
"mode": "off"
|
||||
}
|
||||
},
|
||||
"links": [],
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "s"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 81
|
||||
},
|
||||
"id": 159,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [
|
||||
"lastNotNull",
|
||||
"max"
|
||||
],
|
||||
"displayMode": "table",
|
||||
"placement": "bottom",
|
||||
"showLegend": true,
|
||||
"sortBy": "Last *",
|
||||
"sortDesc": true
|
||||
},
|
||||
"tooltip": {
|
||||
"hideZeros": true,
|
||||
"mode": "multi",
|
||||
"sort": "desc"
|
||||
}
|
||||
},
|
||||
"pluginVersion": "12.2.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "$ds"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "rate(vm_filestream_fsync_duration_seconds_total{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval]) / rate(vm_filestream_fsync_calls_total{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval])",
|
||||
"format": "time_series",
|
||||
"instant": false,
|
||||
"legendFormat": "{{instance}}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Fsync avg duration ($instance)",
|
||||
"type": "timeseries"
|
||||
}
|
||||
],
|
||||
"title": "Troubleshooting",
|
||||
@@ -8742,4 +8846,4 @@
|
||||
"uid": "wNf0q_kZk",
|
||||
"version": 1,
|
||||
"weekStart": ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5137,6 +5137,110 @@
|
||||
],
|
||||
"title": "Major page faults rate ($instance)",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "victoriametrics-metrics-datasource",
|
||||
"uid": "$ds"
|
||||
},
|
||||
"description": "Average duration of fsync system calls. Spikes or high latency indicates that storage I/O cannot keep up with the write rate, which may slow down data ingestion.\n\nIf this value is elevated:\n- Check disk-related metrics.\n- If using cloud storage, check IOPS and throughput limits.\n- If using NFS, check network performance, server resource usage, and any errors on the NFS server side.\n- Check for known bugs in the filesystem or kernel version being used.",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"axisSoftMin": 0,
|
||||
"barAlignment": 0,
|
||||
"barWidthFactor": 0.6,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 0,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"insertNulls": false,
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "never",
|
||||
"showValues": false,
|
||||
"spanNulls": false,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
},
|
||||
"thresholdsStyle": {
|
||||
"mode": "off"
|
||||
}
|
||||
},
|
||||
"links": [],
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "s"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 305
|
||||
},
|
||||
"id": 230,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [
|
||||
"lastNotNull",
|
||||
"max"
|
||||
],
|
||||
"displayMode": "table",
|
||||
"placement": "bottom",
|
||||
"showLegend": true,
|
||||
"sortBy": "Last *",
|
||||
"sortDesc": true
|
||||
},
|
||||
"tooltip": {
|
||||
"hideZeros": true,
|
||||
"mode": "multi",
|
||||
"sort": "desc"
|
||||
}
|
||||
},
|
||||
"pluginVersion": "12.2.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "victoriametrics-metrics-datasource",
|
||||
"uid": "$ds"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "rate(vm_filestream_fsync_duration_seconds_total{job=~\"$job_storage\", instance=~\"$instance\"}[$__rate_interval]) / rate(vm_filestream_fsync_calls_total{job=~\"$job_storage\", instance=~\"$instance\"}[$__rate_interval])",
|
||||
"format": "time_series",
|
||||
"instant": false,
|
||||
"legendFormat": "{{instance}}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Fsync avg duration ($instance)",
|
||||
"type": "timeseries"
|
||||
}
|
||||
],
|
||||
"title": "Troubleshooting",
|
||||
|
||||
@@ -5182,6 +5182,110 @@
|
||||
],
|
||||
"title": "Major page faults rate",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "victoriametrics-metrics-datasource",
|
||||
"uid": "$ds"
|
||||
},
|
||||
"description": "Average duration of fsync system calls. Spikes or high latency indicates that storage I/O cannot keep up with the write rate, which may slow down data ingestion.\n\nIf this value is elevated:\n- Check disk-related metrics.\n- If using cloud storage, check IOPS and throughput limits.\n- If using NFS, check network performance, server resource usage, and any errors on the NFS server side.\n- Check for known bugs in the filesystem or kernel version being used.",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"axisSoftMin": 0,
|
||||
"barAlignment": 0,
|
||||
"barWidthFactor": 0.6,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 0,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"insertNulls": false,
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "never",
|
||||
"showValues": false,
|
||||
"spanNulls": false,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
},
|
||||
"thresholdsStyle": {
|
||||
"mode": "off"
|
||||
}
|
||||
},
|
||||
"links": [],
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "s"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 81
|
||||
},
|
||||
"id": 159,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [
|
||||
"lastNotNull",
|
||||
"max"
|
||||
],
|
||||
"displayMode": "table",
|
||||
"placement": "bottom",
|
||||
"showLegend": true,
|
||||
"sortBy": "Last *",
|
||||
"sortDesc": true
|
||||
},
|
||||
"tooltip": {
|
||||
"hideZeros": true,
|
||||
"mode": "multi",
|
||||
"sort": "desc"
|
||||
}
|
||||
},
|
||||
"pluginVersion": "12.2.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "victoriametrics-metrics-datasource",
|
||||
"uid": "$ds"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "rate(vm_filestream_fsync_duration_seconds_total{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval]) / rate(vm_filestream_fsync_calls_total{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval])",
|
||||
"format": "time_series",
|
||||
"instant": false,
|
||||
"legendFormat": "{{instance}}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Fsync avg duration ($instance)",
|
||||
"type": "timeseries"
|
||||
}
|
||||
],
|
||||
"title": "Troubleshooting",
|
||||
@@ -8743,4 +8847,4 @@
|
||||
"uid": "wNf0q_kZk_vm",
|
||||
"version": 1,
|
||||
"weekStart": ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4581,6 +4581,110 @@
|
||||
],
|
||||
"title": "Rows ignored for last 1h ($instance)",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "victoriametrics-metrics-datasource",
|
||||
"uid": "$ds"
|
||||
},
|
||||
"description": "Average duration of fsync system calls. Spikes or high latency indicates that storage I/O cannot keep up with the write rate, which may slow down data ingestion.\n\nIf this value is elevated:\n- Check disk-related metrics.\n- If using cloud storage, check IOPS and throughput limits.\n- If using NFS, check network performance, server resource usage, and any errors on the NFS server side.\n- Check for known bugs in the filesystem or kernel version being used.",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"axisSoftMin": 0,
|
||||
"barAlignment": 0,
|
||||
"barWidthFactor": 0.6,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 0,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"insertNulls": false,
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "never",
|
||||
"showValues": false,
|
||||
"spanNulls": false,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
},
|
||||
"thresholdsStyle": {
|
||||
"mode": "off"
|
||||
}
|
||||
},
|
||||
"links": [],
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "s"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 189
|
||||
},
|
||||
"id": 171,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [
|
||||
"lastNotNull",
|
||||
"max"
|
||||
],
|
||||
"displayMode": "table",
|
||||
"placement": "bottom",
|
||||
"showLegend": true,
|
||||
"sortBy": "Last *",
|
||||
"sortDesc": true
|
||||
},
|
||||
"tooltip": {
|
||||
"hideZeros": true,
|
||||
"mode": "multi",
|
||||
"sort": "desc"
|
||||
}
|
||||
},
|
||||
"pluginVersion": "12.2.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "victoriametrics-metrics-datasource",
|
||||
"uid": "$ds"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "rate(vm_filestream_fsync_duration_seconds_total{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval]) / rate(vm_filestream_fsync_calls_total{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval])",
|
||||
"format": "time_series",
|
||||
"instant": false,
|
||||
"legendFormat": "{{instance}}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Fsync avg duration ($instance)",
|
||||
"type": "timeseries"
|
||||
}
|
||||
],
|
||||
"title": "Troubleshooting",
|
||||
|
||||
@@ -4580,6 +4580,110 @@
|
||||
],
|
||||
"title": "Rows ignored for last 1h ($instance)",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "$ds"
|
||||
},
|
||||
"description": "Average duration of fsync system calls. Spikes or high latency indicates that storage I/O cannot keep up with the write rate, which may slow down data ingestion.\n\nIf this value is elevated:\n- Check disk-related metrics.\n- If using cloud storage, check IOPS and throughput limits.\n- If using NFS, check network performance, server resource usage, and any errors on the NFS server side.\n- Check for known bugs in the filesystem or kernel version being used.",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"axisSoftMin": 0,
|
||||
"barAlignment": 0,
|
||||
"barWidthFactor": 0.6,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 0,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"insertNulls": false,
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "never",
|
||||
"showValues": false,
|
||||
"spanNulls": false,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
},
|
||||
"thresholdsStyle": {
|
||||
"mode": "off"
|
||||
}
|
||||
},
|
||||
"links": [],
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "s"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 189
|
||||
},
|
||||
"id": 171,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [
|
||||
"lastNotNull",
|
||||
"max"
|
||||
],
|
||||
"displayMode": "table",
|
||||
"placement": "bottom",
|
||||
"showLegend": true,
|
||||
"sortBy": "Last *",
|
||||
"sortDesc": true
|
||||
},
|
||||
"tooltip": {
|
||||
"hideZeros": true,
|
||||
"mode": "multi",
|
||||
"sort": "desc"
|
||||
}
|
||||
},
|
||||
"pluginVersion": "12.2.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "$ds"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "rate(vm_filestream_fsync_duration_seconds_total{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval]) / rate(vm_filestream_fsync_calls_total{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval])",
|
||||
"format": "time_series",
|
||||
"instant": false,
|
||||
"legendFormat": "{{instance}}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Fsync avg duration ($instance)",
|
||||
"type": "timeseries"
|
||||
}
|
||||
],
|
||||
"title": "Troubleshooting",
|
||||
|
||||
@@ -163,6 +163,8 @@ The list of alerting rules is the following:
|
||||
alerting rules related to [vmauth](https://docs.victoriametrics.com/victoriametrics/vmauth/) component;
|
||||
* [alerts-vmanomaly.yml](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/deployment/docker/rules/alerts-vmanomaly.yml):
|
||||
alerting rules related to [VictoriaMetrics Anomaly Detection](https://docs.victoriametrics.com/anomaly-detection/);
|
||||
* [alerts-vmbackupmanager.yml](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/deployment/docker/rules/alerts-vmbackupmanager.yml):
|
||||
alerting rules related to [vmbackupmanager](https://docs.victoriametrics.com/victoriametrics/vmbackupmanager/) component;
|
||||
|
||||
Please, also see [how to monitor VictoriaMetrics installations](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#monitoring).
|
||||
## Troubleshooting
|
||||
|
||||
@@ -3,7 +3,7 @@ services:
|
||||
# It scrapes targets defined in --promscrape.config
|
||||
# And forward them to --remoteWrite.url
|
||||
vmagent:
|
||||
image: victoriametrics/vmagent:v1.147.0
|
||||
image: victoriametrics/vmagent:v1.148.0
|
||||
depends_on:
|
||||
- "vmauth"
|
||||
ports:
|
||||
@@ -42,14 +42,14 @@ services:
|
||||
# vmstorage shards. Each shard receives 1/N of all metrics sent to vminserts,
|
||||
# where N is number of vmstorages (2 in this case).
|
||||
vmstorage-1:
|
||||
image: victoriametrics/vmstorage:v1.147.0-cluster
|
||||
image: victoriametrics/vmstorage:v1.148.0-cluster
|
||||
volumes:
|
||||
- strgdata-1:/storage
|
||||
command:
|
||||
- "--storageDataPath=/storage"
|
||||
restart: always
|
||||
vmstorage-2:
|
||||
image: victoriametrics/vmstorage:v1.147.0-cluster
|
||||
image: victoriametrics/vmstorage:v1.148.0-cluster
|
||||
volumes:
|
||||
- strgdata-2:/storage
|
||||
command:
|
||||
@@ -59,7 +59,7 @@ services:
|
||||
# vminsert is ingestion frontend. It receives metrics pushed by vmagent,
|
||||
# pre-process them and distributes across configured vmstorage shards.
|
||||
vminsert-1:
|
||||
image: victoriametrics/vminsert:v1.147.0-cluster
|
||||
image: victoriametrics/vminsert:v1.148.0-cluster
|
||||
depends_on:
|
||||
- "vmstorage-1"
|
||||
- "vmstorage-2"
|
||||
@@ -68,7 +68,7 @@ services:
|
||||
- "--storageNode=vmstorage-2:8400"
|
||||
restart: always
|
||||
vminsert-2:
|
||||
image: victoriametrics/vminsert:v1.147.0-cluster
|
||||
image: victoriametrics/vminsert:v1.148.0-cluster
|
||||
depends_on:
|
||||
- "vmstorage-1"
|
||||
- "vmstorage-2"
|
||||
@@ -80,7 +80,7 @@ services:
|
||||
# vmselect is a query fronted. It serves read queries in MetricsQL or PromQL.
|
||||
# vmselect collects results from configured `--storageNode` shards.
|
||||
vmselect-1:
|
||||
image: victoriametrics/vmselect:v1.147.0-cluster
|
||||
image: victoriametrics/vmselect:v1.148.0-cluster
|
||||
depends_on:
|
||||
- "vmstorage-1"
|
||||
- "vmstorage-2"
|
||||
@@ -90,7 +90,7 @@ services:
|
||||
- "--vmalert.proxyURL=http://vmalert:8880"
|
||||
restart: always
|
||||
vmselect-2:
|
||||
image: victoriametrics/vmselect:v1.147.0-cluster
|
||||
image: victoriametrics/vmselect:v1.148.0-cluster
|
||||
depends_on:
|
||||
- "vmstorage-1"
|
||||
- "vmstorage-2"
|
||||
@@ -105,7 +105,7 @@ services:
|
||||
# read requests from Grafana, vmui, vmalert among vmselects.
|
||||
# It can be used as an authentication proxy.
|
||||
vmauth:
|
||||
image: victoriametrics/vmauth:v1.147.0
|
||||
image: victoriametrics/vmauth:v1.148.0
|
||||
depends_on:
|
||||
- "vmselect-1"
|
||||
- "vmselect-2"
|
||||
@@ -119,7 +119,7 @@ services:
|
||||
|
||||
# vmalert executes alerting and recording rules
|
||||
vmalert:
|
||||
image: victoriametrics/vmalert:v1.147.0
|
||||
image: victoriametrics/vmalert:v1.148.0
|
||||
depends_on:
|
||||
- "vmauth"
|
||||
ports:
|
||||
@@ -129,6 +129,7 @@ services:
|
||||
- ./rules/alerts-health.yml:/etc/alerts/alerts-health.yml
|
||||
- ./rules/alerts-vmagent.yml:/etc/alerts/alerts-vmagent.yml
|
||||
- ./rules/alerts-vmalert.yml:/etc/alerts/alerts-vmalert.yml
|
||||
- ./rules/alerts-vmbackupmanager.yml:/etc/alerts/alerts-vmbackupmanager.yml
|
||||
command:
|
||||
- "--datasource.url=http://vmauth:8427/select/0/prometheus"
|
||||
- "--datasource.basicAuth.username=foo"
|
||||
|
||||
@@ -3,7 +3,7 @@ services:
|
||||
# It scrapes targets defined in --promscrape.config
|
||||
# And forward them to --remoteWrite.url
|
||||
vmagent:
|
||||
image: victoriametrics/vmagent:v1.147.0
|
||||
image: victoriametrics/vmagent:v1.148.0
|
||||
depends_on:
|
||||
- "victoriametrics"
|
||||
ports:
|
||||
@@ -18,7 +18,7 @@ services:
|
||||
# VictoriaMetrics instance, a single process responsible for
|
||||
# storing metrics and serve read requests.
|
||||
victoriametrics:
|
||||
image: victoriametrics/victoria-metrics:v1.147.0
|
||||
image: victoriametrics/victoria-metrics:v1.148.0
|
||||
ports:
|
||||
- 8428:8428
|
||||
- 8089:8089
|
||||
@@ -59,7 +59,7 @@ services:
|
||||
|
||||
# vmalert executes alerting and recording rules
|
||||
vmalert:
|
||||
image: victoriametrics/vmalert:v1.147.0
|
||||
image: victoriametrics/vmalert:v1.148.0
|
||||
depends_on:
|
||||
- "victoriametrics"
|
||||
- "alertmanager"
|
||||
@@ -70,6 +70,7 @@ services:
|
||||
- ./rules/alerts-health.yml:/etc/alerts/alerts-health.yml
|
||||
- ./rules/alerts-vmagent.yml:/etc/alerts/alerts-vmagent.yml
|
||||
- ./rules/alerts-vmalert.yml:/etc/alerts/alerts-vmalert.yml
|
||||
- ./rules/alerts-vmbackupmanager.yml:/etc/alerts/alerts-vmbackupmanager.yml
|
||||
command:
|
||||
- "--datasource.url=http://victoriametrics:8428/"
|
||||
- "--remoteRead.url=http://victoriametrics:8428/"
|
||||
|
||||
@@ -10,7 +10,7 @@ groups:
|
||||
concurrency: 2
|
||||
rules:
|
||||
- alert: PersistentQueueIsDroppingData
|
||||
expr: sum(increase(vm_persistentqueue_bytes_dropped_total[5m])) without (path) > 0
|
||||
expr: sum(increase(vm_persistentqueue_bytes_dropped_total[5m])) without (path,name) > 0
|
||||
for: 10m
|
||||
labels:
|
||||
severity: critical
|
||||
@@ -201,4 +201,4 @@ groups:
|
||||
annotations:
|
||||
summary: "Persistent Queue (url {{ $labels.url }}) of {{ $labels.instance }} (job:{{ $labels.job }}) will run out of space in 4 hours."
|
||||
description: "RemoteWrite destination ({{ $labels.url }}) is unavailable or unable to receive data in a timely manner, so the persistent queue size is growing.
|
||||
Once the available space is exhausted, some samples will be discarded and cause incident. Please check the health of remoteWrite destination ({{ $labels.url }})."
|
||||
Once the available space is exhausted, some samples will be discarded and cause incident. Please check the health of remoteWrite destination ({{ $labels.url }})."
|
||||
|
||||
110
deployment/docker/rules/alerts-vmbackupmanager.yml
Normal file
110
deployment/docker/rules/alerts-vmbackupmanager.yml
Normal file
@@ -0,0 +1,110 @@
|
||||
# File contains default list of alerts for vmbackupmanager.
|
||||
# The alerts below are just recommendations and may require some updates
|
||||
# and threshold calibration according to every specific setup.
|
||||
groups:
|
||||
- name: vmbackupmanager
|
||||
rules:
|
||||
- alert: NoLatestBackupWithinLastDay
|
||||
expr: |
|
||||
(
|
||||
vm_backup_last_success_at{type="latest"} == 0
|
||||
and on(job, instance)
|
||||
vm_app_uptime_seconds > 24 * 3600
|
||||
)
|
||||
or
|
||||
(
|
||||
vm_backup_last_success_at{type="latest"} > 0
|
||||
and time() - vm_backup_last_success_at{type="latest"} > 24 * 3600
|
||||
)
|
||||
for: 1m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "latest backup hasn't completed successfully for more than 1 day on \"{{ $labels.job }}\"(\"{{ $labels.instance }}\")"
|
||||
description: >
|
||||
vmbackupmanager on "{{ $labels.job }}"("{{ $labels.instance }}") hasn't completed the latest backup for more than 1 day.
|
||||
Check error logs on vmbackupmanager for root cause.
|
||||
|
||||
- alert: NoHourlyBackupWithinLastDay
|
||||
expr: |
|
||||
(
|
||||
vm_backup_last_success_at{type="hourly"} == 0
|
||||
and on(job, instance)
|
||||
vm_app_uptime_seconds > 24 * 3600
|
||||
)
|
||||
or
|
||||
(
|
||||
vm_backup_last_success_at{type="hourly"} > 0
|
||||
and time() - vm_backup_last_success_at{type="hourly"} > 24 * 3600
|
||||
)
|
||||
for: 1m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "hourly backup hasn't completed successfully for more than 1 day on \"{{ $labels.job }}\"(\"{{ $labels.instance }}\")"
|
||||
description: >
|
||||
vmbackupmanager on "{{ $labels.job }}"("{{ $labels.instance }}") hasn't completed an hourly backup for more than 1 day.
|
||||
Check error logs on vmbackupmanager for root cause.
|
||||
|
||||
- alert: NoDailyBackupWithinLast3Days
|
||||
expr: |
|
||||
(
|
||||
vm_backup_last_success_at{type="daily"} == 0
|
||||
and on(job, instance)
|
||||
vm_app_uptime_seconds > 3 * 24 * 3600
|
||||
)
|
||||
or
|
||||
(
|
||||
vm_backup_last_success_at{type="daily"} > 0
|
||||
and time() - vm_backup_last_success_at{type="daily"} > 3 * 24 * 3600
|
||||
)
|
||||
for: 1m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "daily backup hasn't completed successfully for more than 3 days on \"{{ $labels.job }}\"(\"{{ $labels.instance }}\")"
|
||||
description: >
|
||||
vmbackupmanager on "{{ $labels.job }}"("{{ $labels.instance }}") hasn't completed a daily backup for more than 3 days.
|
||||
Check error logs on vmbackupmanager for root cause.
|
||||
|
||||
- alert: NoWeeklyBackupWithinLast14Days
|
||||
expr: |
|
||||
(
|
||||
vm_backup_last_success_at{type="weekly"} == 0
|
||||
and on(job, instance)
|
||||
vm_app_uptime_seconds > 14 * 24 * 3600
|
||||
)
|
||||
or
|
||||
(
|
||||
vm_backup_last_success_at{type="weekly"} > 0
|
||||
and time() - vm_backup_last_success_at{type="weekly"} > 14 * 24 * 3600
|
||||
)
|
||||
for: 1m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "weekly backup hasn't completed successfully for more than 14 days on \"{{ $labels.job }}\"(\"{{ $labels.instance }}\")"
|
||||
description: >
|
||||
vmbackupmanager on "{{ $labels.job }}"("{{ $labels.instance }}") hasn't completed a weekly backup for more than 14 days.
|
||||
Check error logs on vmbackupmanager for root cause.
|
||||
|
||||
- alert: NoMonthlyBackupWithinLast62Days
|
||||
expr: |
|
||||
(
|
||||
vm_backup_last_success_at{type="monthly"} == 0
|
||||
and on(job, instance)
|
||||
vm_app_uptime_seconds > 62 * 24 * 3600
|
||||
)
|
||||
or
|
||||
(
|
||||
vm_backup_last_success_at{type="monthly"} > 0
|
||||
and time() - vm_backup_last_success_at{type="monthly"} > 62 * 24 * 3600
|
||||
)
|
||||
for: 1m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "monthly backup hasn't completed successfully for more than 62 days on \"{{ $labels.job }}\"(\"{{ $labels.instance }}\")"
|
||||
description: >
|
||||
vmbackupmanager on "{{ $labels.job }}"("{{ $labels.instance }}") hasn't completed a monthly backup for more than 62 days.
|
||||
Check error logs on vmbackupmanager for root cause.
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
vmagent:
|
||||
image: victoriametrics/vmagent:v1.147.0
|
||||
image: victoriametrics/vmagent:v1.148.0
|
||||
depends_on:
|
||||
- "victoriametrics"
|
||||
ports:
|
||||
@@ -14,7 +14,7 @@ services:
|
||||
restart: always
|
||||
|
||||
victoriametrics:
|
||||
image: victoriametrics/victoria-metrics:v1.147.0
|
||||
image: victoriametrics/victoria-metrics:v1.148.0
|
||||
ports:
|
||||
- 8428:8428
|
||||
volumes:
|
||||
@@ -40,7 +40,7 @@ services:
|
||||
restart: always
|
||||
|
||||
vmalert:
|
||||
image: victoriametrics/vmalert:v1.147.0
|
||||
image: victoriametrics/vmalert:v1.148.0
|
||||
depends_on:
|
||||
- "victoriametrics"
|
||||
ports:
|
||||
|
||||
@@ -10,9 +10,9 @@ sitemap:
|
||||
|
||||
- To use *vmanomaly*, part of the enterprise package, a license key is required. Obtain your key [here](https://victoriametrics.com/products/enterprise/trial/) for this tutorial or for enterprise use.
|
||||
- In the tutorial, we'll be using the following VictoriaMetrics components:
|
||||
- [VictoriaMetrics Single-Node](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/) (v1.147.0)
|
||||
- [vmalert](https://docs.victoriametrics.com/victoriametrics/vmalert/) (v1.147.0)
|
||||
- [vmagent](https://docs.victoriametrics.com/victoriametrics/vmagent/) (v1.147.0)
|
||||
- [VictoriaMetrics Single-Node](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/) (v1.148.0)
|
||||
- [vmalert](https://docs.victoriametrics.com/victoriametrics/vmalert/) (v1.148.0)
|
||||
- [vmagent](https://docs.victoriametrics.com/victoriametrics/vmagent/) (v1.148.0)
|
||||
- [Grafana](https://grafana.com/) (v12.2.0)
|
||||
- [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/)
|
||||
- [Node exporter](https://github.com/prometheus/node_exporter#node-exporter) (v1.9.1) and [Alertmanager](https://prometheus.io/docs/alerting/latest/alertmanager/) (v0.28.1)
|
||||
@@ -323,7 +323,7 @@ Let's wrap it all up together into the `docker-compose.yml` file.
|
||||
services:
|
||||
vmagent:
|
||||
container_name: vmagent
|
||||
image: victoriametrics/vmagent:v1.147.0
|
||||
image: victoriametrics/vmagent:v1.148.0
|
||||
depends_on:
|
||||
- "victoriametrics"
|
||||
ports:
|
||||
@@ -340,7 +340,7 @@ services:
|
||||
|
||||
victoriametrics:
|
||||
container_name: victoriametrics
|
||||
image: victoriametrics/victoria-metrics:v1.147.0
|
||||
image: victoriametrics/victoria-metrics:v1.148.0
|
||||
ports:
|
||||
- 8428:8428
|
||||
volumes:
|
||||
@@ -373,7 +373,7 @@ services:
|
||||
|
||||
vmalert:
|
||||
container_name: vmalert
|
||||
image: victoriametrics/vmalert:v1.147.0
|
||||
image: victoriametrics/vmalert:v1.148.0
|
||||
depends_on:
|
||||
- "victoriametrics"
|
||||
ports:
|
||||
|
||||
426
docs/guides/connecting-vm-components-to-cloud-storage/README.md
Normal file
426
docs/guides/connecting-vm-components-to-cloud-storage/README.md
Normal file
@@ -0,0 +1,426 @@
|
||||
Several VictoriaMetrics components can connect to cloud storage to read or write object data.
|
||||
|
||||
The following table shows the supported types of storage for each component:
|
||||
|
||||
| Component | AWS S3 and S3-compatible | Google Cloud Storage | Azure Blob Storage |
|
||||
|-----------|----|----------------------|--------------------|
|
||||
| [vmbackup](https://docs.victoriametrics.com/victoriametrics/vmbackup/) | ✅ | ✅ | ✅ |
|
||||
| [vmrestore](https://docs.victoriametrics.com/victoriametrics/vmrestore/) | ✅ | ✅ | ✅ |
|
||||
| [vmbackupmanager](https://docs.victoriametrics.com/victoriametrics/vmbackupmanager/) | ✅ | ✅ | ✅ |
|
||||
| [vmalert](https://docs.victoriametrics.com/victoriametrics/vmalert/) | ✅ | ✅ | ❌ |
|
||||
|
||||
All these components use the same underlying libraries, so the authentication setup is largely the same. The main difference is in the command-line flags:
|
||||
|
||||
- vmalert uses `-s3.*` prefixed flags (e.g., `-s3.credsFilePath`)
|
||||
- backup and restore tools use unprefixed flags (e.g., `-credsFilePath`)
|
||||
|
||||
See the [component reference](https://docs.victoriametrics.com/guides/connecting-vm-components-to-cloud-storage/#per-component-flag-reference) for details.
|
||||
|
||||
## Obtaining credentials
|
||||
|
||||
You need to supply credentials so the component can connect to the cloud storage. The setup differs by provider; the sections below cover AWS S3, S3‑compatible systems, GCS, and Azure Blob Storage.
|
||||
|
||||
### AWS S3
|
||||
|
||||
1. In AWS, [create an IAM user](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users_create.html) or role with the minimum permissions for the target bucket (see table below).
|
||||
1. [Create an access key](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html) for that IAM identity.
|
||||
1. Copy the **Access key ID** and **Secret access key** values. You will use them in the credentials file or environment variables.
|
||||
|
||||
| Component | S3 usage | Minimum S3 permissions |
|
||||
|-----------------|-------------------------------------------------------------|------------------------|
|
||||
| vmalert (Enterprise) | Reads alerting/recording rules from bucket. | `s3:GetObject`, `s3:ListBucket` on the rules bucket/prefix.|
|
||||
| vmrestore | Restores backups from S3 buckets | `s3:GetObject`, `s3:ListBucket` on the backup bucket/prefix.|
|
||||
| vmbackup | Uploads backups to S3 and may delete old backup objects during maintenance.| `s3:PutObject`, `s3:GetObject`, `s3:ListBucket`, `s3:DeleteObject` on the backup bucket/prefix.|
|
||||
| vmbackupmanager (Enterprise) | Automates backups using vmbackup behavior. | Same as vmbackup. |
|
||||
|
||||
|
||||
### S3-compatible storage (MinIO, Ceph)
|
||||
|
||||
Generate access keys using your storage system's admin interface or CLI. The credentials follow the same format as AWS S3.
|
||||
|
||||
### Google Cloud Storage
|
||||
|
||||
1. Open the Google Cloud Console and go to **IAM & Admin > Service Accounts**.
|
||||
1. Click **Create service account**, enter a name, and assign a Storage role (for example, Storage Object Admin).
|
||||
1. Open the service account, go to **Keys**, then click **Add key > Create new key**.
|
||||
1. Choose **JSON** as the key type and click **Create**.
|
||||
1. Store the JSON file on the machine running the VictoriaMetrics component.
|
||||
|
||||
### Azure Blob Storage
|
||||
|
||||
> Azure does not use credential files.
|
||||
|
||||
1. Log in to the Azure Portal.
|
||||
1. Search for and select **Storage accounts**.
|
||||
1. Click on your specific storage account name. (This is your `AZURE_STORAGE_ACCOUNT_NAME`).
|
||||
1. In the left menu under **Security + networking**, click **Access keys**.
|
||||
1. Copy the key value from either **key1** or **key2** (this is your `AZURE_STORAGE_ACCOUNT_KEY`).
|
||||
1. Define the access keys as environment variables.
|
||||
```sh
|
||||
export AZURE_STORAGE_ACCOUNT_NAME=mystorageaccount
|
||||
export AZURE_STORAGE_ACCOUNT_KEY=myaccountkey
|
||||
```
|
||||
|
||||
## Authenticating with the cloud provider
|
||||
|
||||
Provide the credentials as a file or with environment variables, along with the path to the cloud storage bucket. The syntax for the bucket name depends on the cloud provider:
|
||||
|
||||
- `s3://`: for AWS S3 and self-hosted S3-compatible storage (MinIO, Ceph)
|
||||
- `gs://`: Google Cloud Storage
|
||||
- `azblob://`: Azure Blob Storage
|
||||
|
||||
### vmbackup and vmrestore
|
||||
|
||||
The following example backups to an AWS S3 bucket using a credentials file:
|
||||
|
||||
```sh
|
||||
vmbackup \
|
||||
-storageDataPath=/data \
|
||||
-snapshot.createURL=http://localhost:8428/snapshot/create \
|
||||
-dst=s3://victoriametrics-backup/backup01 \
|
||||
-credsFilePath=/etc/credentials
|
||||
```
|
||||
|
||||
In order to restore from the same backup from AWS S3:
|
||||
|
||||
```sh
|
||||
vmrestore \
|
||||
-src=s3://victoriametrics-backup/backup01 \
|
||||
-storageDataPath=/data \
|
||||
-credsFilePath=/etc/credentials
|
||||
|
||||
```
|
||||
|
||||
> To use non-AWS S3 buckets such as MinIO or Ceph, you must [supply the `-customS3Endpoint` argument](https://docs.victoriametrics.com/guides/connecting-vm-components-to-cloud-storage/#s3-compatible).
|
||||
|
||||
Alternatively, you can set the access keys as environment variables instead of using a credential file:
|
||||
|
||||
```sh
|
||||
export AWS_ACCESS_KEY_ID=YOUR_AWS_ACCESS_KEY
|
||||
export AWS_SECRET_ACCESS_KEY=YOUR_SECRET_AWS_ACCESS_KEY
|
||||
|
||||
vmbackup \
|
||||
-storageDataPath=/data \
|
||||
-snapshot.createURL=http://localhost:8428/snapshot/create \
|
||||
-dst=s3://victoriametrics-backup/backup01
|
||||
|
||||
vmrestore \
|
||||
-src=s3://victoriametrics-backup/backup01 \
|
||||
-storageDataPath=/data
|
||||
```
|
||||
|
||||
|
||||
Backups on Google Cloud Storage use the `gs://` prefix in the destination:
|
||||
|
||||
```sh
|
||||
vmbackup \
|
||||
-storageDataPath=/data \
|
||||
-snapshot.createURL=http://localhost:8428/snapshot/create \
|
||||
-dst=gs://victoriametrics-backup/backup01 \
|
||||
-credsFilePath=/etc/credentials
|
||||
```
|
||||
|
||||
You can restore this backup with:
|
||||
|
||||
```sh
|
||||
vmrestore \
|
||||
-src=gs://victoriametrics-backup/backup01 \
|
||||
-storageDataPath=/data \
|
||||
-credsFilePath=/etc/credentials
|
||||
```
|
||||
|
||||
On Google Cloud, you can define the path to the JSON credential file with `GOOGLE_APPLICATION_CREDENTIALS`. For example:
|
||||
|
||||
```sh
|
||||
export GOOGLE_APPLICATION_CREDENTIALS=/etc/credentials
|
||||
|
||||
vmbackup \
|
||||
-storageDataPath=/data \
|
||||
-snapshot.createURL=http://localhost:8428/snapshot/create \
|
||||
-dst=gs://victoriametrics-backup/backup01
|
||||
|
||||
vmrestore \
|
||||
-src=gs://victoriametrics-backup/backup01 \
|
||||
-storageDataPath=/data
|
||||
```
|
||||
|
||||
For Azure Blob Storage, use the `azblob://` prefix and rely on environment variables instead of `-credsFilePath`.
|
||||
|
||||
```sh
|
||||
export AZURE_STORAGE_ACCOUNT_NAME=myaccount
|
||||
export AZURE_STORAGE_ACCOUNT_KEY=mykey
|
||||
|
||||
vmbackup \
|
||||
-storageDataPath=/data \
|
||||
-snapshot.createURL=http://localhost:8428/snapshot/create \
|
||||
-dst=azblob://victoriametrics-backup/backup01
|
||||
|
||||
vmrestore \
|
||||
-src=azblob://victoriametrics-backup/backup01 \
|
||||
-storageDataPath=/data
|
||||
```
|
||||
|
||||
### vmbackupmanager
|
||||
|
||||
> vmbackupmanager only works in the [Enterprise](https://docs.victoriametrics.com/victoriametrics/enterprise/) edition.
|
||||
|
||||
To manage backups with vmbackupmanager on AWS S3, add the credentials with the `-credsFilePath` flag:
|
||||
|
||||
```sh
|
||||
vmbackupmanager \
|
||||
-dst=s3://vmstorage-data/backups \
|
||||
-credsFilePath=/etc/credentials \
|
||||
-storageDataPath=/vmstorage-data \
|
||||
-snapshot.createURL=http://vmstorage:8482/snapshot/create \
|
||||
-licenseFile=/etc/vm-license
|
||||
```
|
||||
|
||||
Or define the access keys using environment variables:
|
||||
|
||||
```sh
|
||||
export AWS_ACCESS_KEY_ID=YOUR_AWS_ACCESS_KEY
|
||||
export AWS_SECRET_ACCESS_KEY=YOUR_SECRET_AWS_ACCESS_KEY
|
||||
|
||||
vmbackupmanager \
|
||||
-dst=s3://vmstorage-data/backups \
|
||||
-storageDataPath=/vmstorage-data \
|
||||
-snapshot.createURL=http://vmstorage:8482/snapshot/create \
|
||||
-licenseFile=/etc/vm-license
|
||||
```
|
||||
|
||||
> To use non-AWS S3 buckets, you must [supply the `-customS3Endpoint` argument](#s3-compatible).
|
||||
|
||||
Automated backups on Google Cloud Storage take the following form:
|
||||
|
||||
```sh
|
||||
vmbackupmanager \
|
||||
-dst=gs://vmstorage-data/backups \
|
||||
-credsFilePath=/etc/credentials \
|
||||
-storageDataPath=/vmstorage-data \
|
||||
-snapshot.createURL=http://vmstorage:8482/snapshot/create \
|
||||
-licenseFile=/etc/vm-license
|
||||
```
|
||||
|
||||
As with vmbackup and vmrestore, you can also define the path to the JSON credential file with `GOOGLE_APPLICATION_CREDENTIALS`:
|
||||
|
||||
```sh
|
||||
export GOOGLE_APPLICATION_CREDENTIALS=/etc/credentials
|
||||
|
||||
vmbackupmanager \
|
||||
-dst=gs://vmstorage-data/backups \
|
||||
-storageDataPath=/vmstorage-data \
|
||||
-snapshot.createURL=http://vmstorage:8482/snapshot/create \
|
||||
-licenseFile=/etc/vm-license
|
||||
```
|
||||
|
||||
vmbackupmanager can also use Azure Blob Storage by defining environment variables:
|
||||
|
||||
```sh
|
||||
export AZURE_STORAGE_ACCOUNT_NAME=mystorageaccount
|
||||
export AZURE_STORAGE_ACCOUNT_KEY=myaccountkey
|
||||
|
||||
vmbackupmanager \
|
||||
-dst=azblob://vmstorage-data/backups \
|
||||
-storageDataPath=/vmstorage-data \
|
||||
-snapshot.createURL=http://vmstorage:8482/snapshot/create \
|
||||
-licenseFile=/etc/vm-license
|
||||
```
|
||||
|
||||
### vmalert
|
||||
|
||||
> - vmalert cloud storage command line flags are prefixed with `-s3.` for S3 buckets *and* Google Cloud Storage.
|
||||
> - Cloud storage only works in the [Enterprise](https://docs.victoriametrics.com/victoriametrics/enterprise/) edition.
|
||||
|
||||
Read alerting rules from an S3 bucket. The `-rule` flag accepts a prefix, so it matches all files starting with `alerts_` in the `rules` folder:
|
||||
|
||||
```sh
|
||||
vmalert \
|
||||
-rule=s3://my-alert-bucket/rules/alerts_ \
|
||||
-s3.credsFilePath=/etc/vmalert/aws-credentials \
|
||||
-datasource.url=http://vmselect:8481/select/0/prometheus \
|
||||
-notifier.url=http://alertmanager:9093 \
|
||||
-licenseFile=/etc/vm-license
|
||||
```
|
||||
|
||||
Instead of a credential file, you can supply the access keys using environment variables:
|
||||
|
||||
```sh
|
||||
export AWS_ACCESS_KEY_ID=YOUR_AWS_ACCESS_KEY
|
||||
export AWS_SECRET_ACCESS_KEY=YOUR_SECRET_AWS_ACCESS_KEY
|
||||
|
||||
vmalert \
|
||||
-rule=s3://my-alert-bucket/rules/alerts_ \
|
||||
-datasource.url=http://vmselect:8481/select/0/prometheus \
|
||||
-notifier.url=http://alertmanager:9093 \
|
||||
-licenseFile=/etc/vm-license
|
||||
```
|
||||
|
||||
> To use non-AWS S3 buckets, you must [supply the `-s3.customEndpoint` argument](#s3-compatible).
|
||||
|
||||
To read rules from Google Cloud Storage:
|
||||
|
||||
```sh
|
||||
vmalert \
|
||||
-rule=gs://my-alert-bucket/rules/alerts_ \
|
||||
-s3.credsFilePath=/etc/vmalert/gcp-service-account.json \
|
||||
-datasource.url=http://vmselect:8481/select/0/prometheus \
|
||||
-notifier.url=http://alertmanager:9093 \
|
||||
-licenseFile=/etc/vm-license
|
||||
```
|
||||
|
||||
If you prefer, you can supply the path to the JSON credential file with the `GOOGLE_APPLICATION_CREDENTIALS` environment variable:
|
||||
|
||||
```sh
|
||||
export GOOGLE_APPLICATION_CREDENTIALS=/etc/credentials
|
||||
|
||||
vmalert \
|
||||
-rule=gs://my-alert-bucket/rules/alerts_ \
|
||||
-datasource.url=http://vmselect:8481/select/0/prometheus \
|
||||
-notifier.url=http://alertmanager:9093 \
|
||||
-licenseFile=/etc/vm-license
|
||||
```
|
||||
|
||||
## Credentials files format
|
||||
|
||||
The file format depends on the storage provider.
|
||||
|
||||
### S3 credentials
|
||||
|
||||
The file uses the standard AWS shared credentials format used by the [AWS CLI](https://docs.aws.amazon.com/cli/v1/userguide/cli-configure-files.html) and [AWS SDKs](https://docs.aws.amazon.com/sdkref/latest/guide/file-format.html):
|
||||
|
||||
```ini
|
||||
[default]
|
||||
aws_access_key_id=YOUR_AWS_ACCESS_KEY
|
||||
aws_secret_access_key=YOUR_AWS_SECRET_ACCESS_KEY
|
||||
```
|
||||
|
||||
You can define multiple profiles in a single file:
|
||||
|
||||
```ini
|
||||
[default]
|
||||
aws_access_key_id=DEFAULT_ACCESS_KEY
|
||||
aws_secret_access_key=DEFAULT_SECRET_KEY
|
||||
|
||||
[monitoring]
|
||||
aws_access_key_id=MONITORING_ACCESS_KEY
|
||||
aws_secret_access_key=MONITORING_SECRET_KEY
|
||||
|
||||
[alerts]
|
||||
aws_access_key_id=ALERTS_ACCESS_KEY
|
||||
aws_secret_access_key=ALERTS_SECRET_KEY
|
||||
```
|
||||
|
||||
Use the `-configProfile` flag (or `-s3.configProfile` in vmalert) to select a non-default profile:
|
||||
|
||||
```sh
|
||||
-configProfile=alerts
|
||||
```
|
||||
|
||||
You can separate credentials from other configuration settings. Put credentials in one file:
|
||||
|
||||
```ini
|
||||
[default]
|
||||
aws_access_key_id=DEFAULT_ACCESS_KEY
|
||||
aws_secret_access_key=DEFAULT_SECRET_KEY
|
||||
```
|
||||
|
||||
And non-sensitive settings in another:
|
||||
|
||||
```ini
|
||||
[default]
|
||||
region=us-east-1
|
||||
```
|
||||
|
||||
Then pass both files:
|
||||
|
||||
```sh
|
||||
-configFilePath=/etc/aws-config \
|
||||
-credsFilePath=/etc/credentials
|
||||
```
|
||||
|
||||
### GCS credentials file format
|
||||
|
||||
The file is the JSON key downloaded from Google Cloud Console. Its content looks like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "service_account",
|
||||
"project_id": "project-id",
|
||||
"private_key_id": "key-id",
|
||||
"private_key": "-----BEGIN PRIVATE KEY-----\nprivate-key\n-----END PRIVATE KEY-----\n",
|
||||
"client_email": "service-account-email",
|
||||
"client_id": "client-id",
|
||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||
"token_uri": "https://accounts.google.com/o/oauth2/token",
|
||||
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
||||
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/service-account-email"
|
||||
}
|
||||
```
|
||||
|
||||
This is the standard service account key format defined by [Google Cloud IAM](https://developers.google.com/workspace/guides/create-credentials).
|
||||
|
||||
### Azure Blob Storage
|
||||
|
||||
Azure does not support credentials via file. Use environment variables instead.
|
||||
|
||||
```sh
|
||||
export AZURE_STORAGE_ACCOUNT_NAME=mystorageaccount
|
||||
export AZURE_STORAGE_ACCOUNT_KEY=myaccountkey
|
||||
```
|
||||
|
||||
## Self-hosted and S3-compatible endpoints
|
||||
|
||||
For S3-compatible storage such as MinIO or Ceph, set a custom endpoint with the `-customS3Endpoint` flag for vmbackup, vmrestore, and vmbackupmanager. For example:
|
||||
|
||||
```sh
|
||||
vmbackup \
|
||||
-storageDataPath=/data \
|
||||
-snapshot.createURL=http://localhost:8428/snapshot/create \
|
||||
-dst=s3://victoriametrics-backup/backup01 \
|
||||
-customS3Endpoint=http://minio.example.local:9000
|
||||
```
|
||||
|
||||
On vmalert, use the `-s3.customEndpoint` flag instead:
|
||||
|
||||
```sh
|
||||
vmalert \
|
||||
-rule=s3://my-alert-bucket/rules/alerts_ \
|
||||
-s3.customEndpoint=http://minio.example.local:9000 \
|
||||
-s3.credsFilePath=/etc/vmalert/aws-credentials \
|
||||
-datasource.url=http://vmselect:8481/select/0/prometheus \
|
||||
-notifier.url=http://alertmanager:9093 \
|
||||
-licenseFile=/etc/vm-license
|
||||
```
|
||||
|
||||
### Addressing S3-compatible buckets {#s3-compatible}
|
||||
|
||||
When connecting to non-AWS S3-compatible buckets, there is an additional flag you might need to configure:
|
||||
|
||||
- `-s3ForcePathStyle`: on vmbackupmanager, vmbackup, and vmrestore.
|
||||
- `-s3.forcePathStyle`: on vmalert.
|
||||
|
||||
The flag changes the expected URL pattern for a bucket.
|
||||
|
||||
| Flag value | Address-style | Example | Use with |
|
||||
|------------|---------------|---------|----------|
|
||||
| `true` (default) | Path-style | `https://endpoint/bucket/key` | MinIO, Ceph, most S3-compatible storages |
|
||||
| `false` | Virtual host-style | `https://bucket.endpoint/key` | [Aliyun OSS](https://www.aliyun.com/product/oss) and other endpoints that require it |
|
||||
|
||||
> The flag only takes effect when you use a custom endpoint (`-customS3Endpoint` or `-s3.customEndpoint` on vmalert). When connecting to real AWS S3, the SDK handles addressing automatically.
|
||||
|
||||
## Per-component flag reference
|
||||
|
||||
The table below shows how the same concept maps to different flag names across components.
|
||||
|
||||
| Concept | vmalert | vmbackup, vmrestore, and vmbackupmanager |
|
||||
|---|---|---|---|---|
|
||||
| Credentials file | `-s3.credsFilePath` | `-credsFilePath` |
|
||||
| Config file | `-s3.configFilePath` | `-configFilePath` |
|
||||
| Profile selection | `-s3.configProfile` | `-configProfile` |
|
||||
| Custom endpoint | `-s3.customEndpoint` | `-customS3Endpoint` |
|
||||
| Force path style | `-s3.forcePathStyle` | `-s3ForcePathStyle` |
|
||||
| TLS insecure | N/A | `-s3TLSInsecureSkipVerify` |
|
||||
| Storage class | N/A | `-s3StorageClass` |
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
weight: 5
|
||||
title: Connecting VictoriaMetrics components to cloud storage
|
||||
menu:
|
||||
docs:
|
||||
parent: guides
|
||||
weight: 5
|
||||
tags:
|
||||
- metrics
|
||||
- guide
|
||||
---
|
||||
{{% content "README.md" %}}
|
||||
@@ -240,23 +240,23 @@ vmagent will write data into VictoriaMetrics single-node and cluster (with tenan
|
||||
# compose.yaml
|
||||
services:
|
||||
vmsingle:
|
||||
image: victoriametrics/victoria-metrics:v1.147.0
|
||||
image: victoriametrics/victoria-metrics:v1.148.0
|
||||
|
||||
vmstorage:
|
||||
image: victoriametrics/vmstorage:v1.147.0-cluster
|
||||
image: victoriametrics/vmstorage:v1.148.0-cluster
|
||||
|
||||
vminsert:
|
||||
image: victoriametrics/vminsert:v1.147.0-cluster
|
||||
image: victoriametrics/vminsert:v1.148.0-cluster
|
||||
command:
|
||||
- -storageNode=vmstorage:8400
|
||||
|
||||
vmselect:
|
||||
image: victoriametrics/vmselect:v1.147.0-cluster
|
||||
image: victoriametrics/vmselect:v1.148.0-cluster
|
||||
command:
|
||||
- -storageNode=vmstorage:8401
|
||||
|
||||
vmagent:
|
||||
image: victoriametrics/vmagent:v1.147.0
|
||||
image: victoriametrics/vmagent:v1.148.0
|
||||
volumes:
|
||||
- ./scrape.yaml:/etc/vmagent/config.yaml
|
||||
command:
|
||||
@@ -308,7 +308,7 @@ Now add the vmauth service to `compose.yaml`:
|
||||
# compose.yaml
|
||||
services:
|
||||
vmauth:
|
||||
image: docker.io/victoriametrics/vmauth:v1.147.0
|
||||
image: docker.io/victoriametrics/vmauth:v1.148.0
|
||||
ports:
|
||||
- 8427:8427
|
||||
volumes:
|
||||
|
||||
@@ -155,15 +155,15 @@ These services will store and query the metrics scraped by vmagent.
|
||||
# compose.yaml
|
||||
services:
|
||||
vmstorage:
|
||||
image: victoriametrics/vmstorage:v1.147.0-cluster
|
||||
image: victoriametrics/vmstorage:v1.148.0-cluster
|
||||
|
||||
vminsert:
|
||||
image: victoriametrics/vminsert:v1.147.0-cluster
|
||||
image: victoriametrics/vminsert:v1.148.0-cluster
|
||||
command:
|
||||
- -storageNode=vmstorage:8400
|
||||
|
||||
vmselect:
|
||||
image: victoriametrics/vmselect:v1.147.0-cluster
|
||||
image: victoriametrics/vmselect:v1.148.0-cluster
|
||||
command:
|
||||
- -storageNode=vmstorage:8401
|
||||
ports:
|
||||
@@ -196,7 +196,7 @@ Add the vmauth service to `compose.yaml`:
|
||||
# compose.yaml
|
||||
services:
|
||||
vmauth:
|
||||
image: victoriametrics/vmauth:v1.147.0-enterprise
|
||||
image: victoriametrics/vmauth:v1.148.0-enterprise
|
||||
ports:
|
||||
- 8427:8427
|
||||
volumes:
|
||||
@@ -251,7 +251,7 @@ Add the vmagent service to `compose.yaml` with OAuth2 configuration:
|
||||
# compose.yaml
|
||||
services:
|
||||
vmagent:
|
||||
image: victoriametrics/vmagent:v1.147.0
|
||||
image: victoriametrics/vmagent:v1.148.0
|
||||
volumes:
|
||||
- ./scrape.yaml:/etc/vmagent/config.yaml
|
||||
command:
|
||||
|
||||
@@ -107,7 +107,7 @@ The final piece is the Docker Compose file. This ties all the services together
|
||||
# compose.yml
|
||||
services:
|
||||
victoriametrics:
|
||||
image: victoriametrics/victoria-metrics:v1.147.0
|
||||
image: victoriametrics/victoria-metrics:v1.148.0
|
||||
command:
|
||||
- "--storageDataPath=/victoria-metrics-data"
|
||||
- "--selfScrapeInterval=10s"
|
||||
@@ -128,7 +128,7 @@ services:
|
||||
- ./alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro
|
||||
|
||||
vmalert:
|
||||
image: victoriametrics/vmalert:v1.147.0
|
||||
image: victoriametrics/vmalert:v1.148.0
|
||||
depends_on:
|
||||
- victoriametrics
|
||||
- alertmanager
|
||||
|
||||
@@ -829,12 +829,9 @@ See also [minimum downtime strategy](#minimum-downtime-strategy).
|
||||
|
||||
## Slowness-based re-routing
|
||||
|
||||
By default, `vminsert` nodes limit the cluster's overall ingestion rate to the throughput of the slowest `vmstorage` node.
|
||||
This ensures that incoming metrics are evenly distributed across all `vmstorage` nodes.
|
||||
The downside is that a single slow vmstorage node can throttle the entire cluster.
|
||||
|
||||
When `-disableRerouting=false` is enabled on `vminsert`,
|
||||
the cluster will automatically re-route writes away from the slowest vmstorage node to preserve maximum ingestion throughput.
|
||||
By default{{% available_from "#" %}}, `vminsert` automatically re-routes writes away from the slowest `vmstorage` node
|
||||
to preserve maximum ingestion throughput. This prevents a single slow `vmstorage` node
|
||||
from throttling the entire cluster.
|
||||
|
||||
Re-routing occurs only when all of the following conditions hold:
|
||||
- the storage send buffer is full.
|
||||
@@ -842,11 +839,15 @@ Re-routing occurs only when all of the following conditions hold:
|
||||
- the vmstorage cluster have much lower saturation overall.
|
||||
- the vmstorage cluster has at least three ready nodes.
|
||||
|
||||
Enable slowness-based re-routing when peak write throughput matters more
|
||||
than minimizing the number of [active time series](https://docs.victoriametrics.com/victoriametrics/faq/#what-is-an-active-time-series)
|
||||
or keeping metrics perfectly balanced across nodes.
|
||||
Disable slowness-based re-routing with `-disableRerouting=true` when keeping metrics
|
||||
perfectly balanced across nodes or minimizing the number of [active time series](https://docs.victoriametrics.com/victoriametrics/faq/#what-is-an-active-time-series)
|
||||
matters more than peak write throughput.
|
||||
|
||||
The rerouting and node saturation could be seen at [VictoriaMetrics - cluster](https://grafana.com/grafana/dashboards/11176) dashboard.
|
||||
Slowness-based re-routing is automatically disabled{{% available_from "#" %}} when `-replicationFactor` is greater than `1`,
|
||||
because rerouting does not guarantee that replicated copies land on distinct storage nodes,
|
||||
which violates the replication contract.
|
||||
|
||||
The rerouting and node saturation could be seen at [VictoriaMetrics - cluster](https://grafana.com/grafana/dashboards/11176) dashboard.
|
||||
|
||||
## Capacity planning
|
||||
|
||||
|
||||
@@ -27,5 +27,5 @@ to [the latest available releases](https://docs.victoriametrics.com/victoriametr
|
||||
|
||||
## Currently supported LTS release lines
|
||||
|
||||
- v1.136.x - the latest one is [v1.136.4 LTS release](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.136.4)
|
||||
- v1.122.x - the latest one is [v1.122.19 LTS release](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.122.19)
|
||||
- v1.148.x - the latest one is [v1.148.0 LTS release](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.148.0)
|
||||
- v1.136.x - the latest one is [v1.136.14 LTS release](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.136.14)
|
||||
|
||||
@@ -53,8 +53,8 @@ and unpack it. It contains a single `victoria-metrics-prod` binary.
|
||||
For example, on Linux with `amd64` architecture:
|
||||
|
||||
```sh
|
||||
wget https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.147.0/victoria-metrics-linux-amd64-v1.147.0.tar.gz
|
||||
tar xzf victoria-metrics-linux-amd64-v1.147.0.tar.gz
|
||||
wget https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.148.0/victoria-metrics-linux-amd64-v1.148.0.tar.gz
|
||||
tar xzf victoria-metrics-linux-amd64-v1.148.0.tar.gz
|
||||
```
|
||||
|
||||
The binary is self-contained and requires no installation - it is ready to run as is.
|
||||
@@ -229,9 +229,9 @@ Download the newest available [VictoriaMetrics release](https://docs.victoriamet
|
||||
from [DockerHub](https://hub.docker.com/r/victoriametrics/victoria-metrics) or [Quay](https://quay.io/repository/victoriametrics/victoria-metrics?tab=tags):
|
||||
|
||||
```sh
|
||||
docker pull victoriametrics/victoria-metrics:v1.147.0
|
||||
docker pull victoriametrics/victoria-metrics:v1.148.0
|
||||
docker run -it --rm -v `pwd`/victoria-metrics-data:/victoria-metrics-data -p 8428:8428 \
|
||||
victoriametrics/victoria-metrics:v1.147.0 --selfScrapeInterval=5s -storageDataPath=victoria-metrics-data
|
||||
victoriametrics/victoria-metrics:v1.148.0 --selfScrapeInterval=5s -storageDataPath=victoria-metrics-data
|
||||
```
|
||||
|
||||
_For Enterprise images, see [this link](https://docs.victoriametrics.com/victoriametrics/enterprise/#docker-images)._
|
||||
|
||||
@@ -26,9 +26,20 @@ See also [LTS releases](https://docs.victoriametrics.com/victoriametrics/lts-rel
|
||||
|
||||
## tip
|
||||
|
||||
**Update Note 1:** `vminsert` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/): the default value of `-disableRerouting` flag has changed from `true` to `false`, enabling [slowness-based re-routing](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/#slowness-based-re-routing) by default. Slowness re-routing is automatically disabled when `-replicationFactor` is greater than 1. If you rely on the old behavior, pass `-disableRerouting` command-line flag to `vminsert`. See [#11287](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11287).
|
||||
|
||||
* FEATURE: [vmagent](https://docs.victoriametrics.com/victoriametrics/vmagent/): add `name` label identifying the corresponding `-remoteWrite.url` target to the `vm_persistentqueue_*` metrics exposed by persistent queue. See [#7944](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7944). Thanks to @tIGO for contribution.
|
||||
* FEATURE: `vminsert` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/): enable [slowness-based re-routing](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/#slowness-based-re-routing) by default. Previously, `-disableRerouting` defaulted to `true`, which limited ingestion throughput to the slowest `vmstorage` node. Now `-disableRerouting` defaults to `false`, so `vminsert` automatically routes data away from the slowest `vmstorage` node, improving overall ingestion performance. Slowness re-routing is automatically disabled when `-replicationFactor` is greater than 1. See [#11287](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11287).
|
||||
* FEATURE: [vmui](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#vmui): persist the selected auto-refresh interval in the URL. See [VictoriaLogs#1310](https://github.com/VictoriaMetrics/VictoriaLogs/issues/1310).
|
||||
* FEATURE: [dashboards](https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/dashboards): add `Fsync avg duration` panel to the Troubleshooting section of the single-node, cluster, and vmagent dashboards. This panel surfaces degradation of IO operation for faster incident triage. See [#10432](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/10432).
|
||||
* FEATURE: [vmbackupmanager](https://docs.victoriametrics.com/victoriametrics/vmbackupmanager/) and [alerts](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/deployment/docker/rules): introduce `vm_backup_last_success_at` metric to track the last successful backup by type. Add [alerting rules](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/deployment/docker/rules/alerts-vmbackupmanager.yml) `NoLatestBackupWithinLastDay`, `NoHourlyBackupWithinLastDay`, `NoDailyBackupWithinLast3Days`, `NoWeeklyBackupWithinLast14Days` and `NoMonthlyBackupWithinLast62Days` to remind users about the missing backups. See [#11217](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11217).
|
||||
|
||||
* BUGFIX: [vmalert](https://docs.victoriametrics.com/victoriametrics/vmalert/): fixed the display of rule state badges on the `Groups` page in the web UI. See [#11160](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11160).
|
||||
* BUGFIX: [vmbackupmanager](https://docs.victoriametrics.com/victoriametrics/vmbackupmanager/): previously, `vmbackupmanager` was crashing on startup when it failed to restore backup state from remote storage, causing a crash loop. Now it logs the error and continues running, retrying the state restore before each scheduled backup. Added `vm_backup_errors_total{type="restoreState"}` metric to track backup state restore failures. See [#11217](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11217).
|
||||
|
||||
## [v1.148.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.148.0)
|
||||
|
||||
Release candidate
|
||||
Released at 2026-07-20
|
||||
|
||||
* SECURITY: upgrade Go builder from Go1.26.4 to Go1.26.5. See [the list of issues addressed in Go1.26.5](https://github.com/golang/go/issues?q=milestone%3AGo1.26.5%20label%3ACherryPickApproved).
|
||||
|
||||
@@ -36,6 +47,7 @@ Release candidate
|
||||
* FEATURE: [vmagent](https://docs.victoriametrics.com/victoriametrics/vmagent/) and [vmsingle](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/): support scraping metrics over Unix domain sockets. The socket path can be configured via the `__unix_socket__` target label. See [#11156](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11156). Thanks to @vinyas-bharadwaj for contribution.
|
||||
* FEATURE: [vmagent](https://docs.victoriametrics.com/victoriametrics/vmagent/) and [vmsingle](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/): Improve background discovery performance for [http_sd](https://docs.victoriametrics.com/victoriametrics/sd_configs/#http_sd_configs) discovery. See [#8838](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8838).
|
||||
* FEATURE: [vmagent](https://docs.victoriametrics.com/victoriametrics/vmagent/) and [vmsingle](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/): allow overriding `max_scrape_size` on a per-target basis via the `__max_scrape_size__` label during target relabeling. See [#11188](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11188).
|
||||
* FEATURE: [vmagent](https://docs.victoriametrics.com/victoriametrics/vmagent/) and [vmalert](https://docs.victoriametrics.com/victoriametrics/vmalert/): add client side least-loaded load-balancing with `DNS` discovery. See [#2388](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2388) and these [vmagent DNS URLs](https://docs.victoriametrics.com/victoriametrics/vmagent/#dns-urls), [vmalert DNS URLs](https://docs.victoriametrics.com/victoriametrics/vmalert/#dns-urls).
|
||||
* FEATURE: [vmstorage](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/) and [vmsingle](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/): add `-maxBackfillAge` command-line flag for limiting ingestion of samples with historical timestamps, for example, when older data has been moved between storage tiers (nvme/hdd, hot/cold). See [#11199](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11199). Thanks to @AshwinRamaniPsg for contribution.
|
||||
* FEATURE: [vmagent](https://docs.victoriametrics.com/victoriametrics/vmagent/): automatically preload relabeling rules configured via `-remoteWrite.relabelConfig` and `-remoteWrite.urlRelabelConfig` in the [metrics relabel debug UI](https://docs.victoriametrics.com/victoriametrics/relabeling/#relabel-debugging). See [#9918](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/9918).
|
||||
|
||||
@@ -50,6 +62,7 @@ Release candidate
|
||||
* BUGFIX: [vmui](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#vmui): hide `Total metric names` stats on [Cardinality Explorer](https://docs.victoriametrics.com/victoriametrics/#cardinality-explorer) page when user selects a specific metric or label to focus. See [#11154](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11154) for details. Thanks to @lghuy05 for the contribution.
|
||||
* BUGFIX: [vmui](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#vmui): keep only one header navigation dropdown (`Explore`, `Tools`) open at a time. Previously, hovering across two dropdowns could briefly leave both open due to the close delay. See [#11224](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11224). Thanks to @antedotee for contribution.
|
||||
* BUGFIX: [vmauth](https://docs.victoriametrics.com/victoriametrics/vmauth/): return `408 Request Timeout` instead of `400 Bad Request` when the request body isn't received within `-maxQueueDuration`. This prevents `vmagent` from incorrectly downgrading the remote write protocol and dropping data when `vmauth` is used as a proxy for a remote write endpoint. See [#11272](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11272).
|
||||
|
||||
## [v1.147.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.147.0)
|
||||
|
||||
Released at 2026-07-06
|
||||
@@ -109,7 +122,6 @@ Released at 2026-06-22
|
||||
* BUGFIX: [vmsingle](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/) and `vmselect` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/): properly escape `metricFamilyName` at metrics metadata response. See [#11129](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11129). Thanks for @fxrlv for the contribution.
|
||||
* BUGFIX: [vmsingle](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/) and `vmstorage` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/): prevent more cases of panic during directory deletion on `NFS`-based mounts. See [#11060](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11060).
|
||||
|
||||
|
||||
## [v1.145.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.145.0)
|
||||
|
||||
Released at 2026-06-08
|
||||
|
||||
@@ -121,7 +121,7 @@ It is allowed to run Enterprise components in [cases listed here](https://docs.v
|
||||
Binary releases of Enterprise components are available at [the releases page for VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/latest),
|
||||
[the releases page for VictoriaLogs](https://github.com/VictoriaMetrics/VictoriaLogs/releases/latest)
|
||||
and [the releases page for VictoriaTraces](https://github.com/VictoriaMetrics/VictoriaTraces/releases/latest).
|
||||
Enterprise binaries and packages have `enterprise` suffix in their names. For example, `victoria-metrics-linux-amd64-v1.147.0-enterprise.tar.gz`.
|
||||
Enterprise binaries and packages have `enterprise` suffix in their names. For example, `victoria-metrics-linux-amd64-v1.148.0-enterprise.tar.gz`.
|
||||
|
||||
In order to run binary release of Enterprise component, please download the `*-enterprise.tar.gz` archive for your OS and architecture
|
||||
from the corresponding releases page and unpack it. Then run the unpacked binary.
|
||||
@@ -139,8 +139,8 @@ For example, the following command runs VictoriaMetrics Enterprise binary with t
|
||||
obtained at [this page](https://victoriametrics.com/products/enterprise/trial/):
|
||||
|
||||
```sh
|
||||
wget https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.147.0/victoria-metrics-linux-amd64-v1.147.0-enterprise.tar.gz
|
||||
tar -xzf victoria-metrics-linux-amd64-v1.147.0-enterprise.tar.gz
|
||||
wget https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.148.0/victoria-metrics-linux-amd64-v1.148.0-enterprise.tar.gz
|
||||
tar -xzf victoria-metrics-linux-amd64-v1.148.0-enterprise.tar.gz
|
||||
./victoria-metrics-prod -license=BASE64_ENCODED_LICENSE_KEY
|
||||
```
|
||||
|
||||
@@ -155,7 +155,7 @@ Alternatively, VictoriaMetrics Enterprise license can be stored in the file and
|
||||
It is allowed to run Enterprise components in [cases listed here](https://docs.victoriametrics.com/victoriametrics/enterprise/#valid-cases-for-victoriametrics-enterprise).
|
||||
|
||||
Docker images for Enterprise components are available at [VictoriaMetrics Docker Hub](https://hub.docker.com/u/victoriametrics) and [VictoriaMetrics Quay](https://quay.io/organization/victoriametrics).
|
||||
Enterprise docker images have `enterprise` suffix in their names. For example, `victoriametrics/victoria-metrics:v1.147.0-enterprise`.
|
||||
Enterprise docker images have `enterprise` suffix in their names. For example, `victoriametrics/victoria-metrics:v1.148.0-enterprise`.
|
||||
|
||||
In order to run Docker image of VictoriaMetrics Enterprise component, it is required to provide the license key via the command-line
|
||||
flag as described in the [binary-releases](https://docs.victoriametrics.com/victoriametrics/enterprise/#binary-releases) section.
|
||||
@@ -165,13 +165,13 @@ Enterprise license key can be obtained at [this page](https://victoriametrics.co
|
||||
For example, the following command runs VictoriaMetrics Enterprise Docker image with the specified license key:
|
||||
|
||||
```sh
|
||||
docker run --name=victoria-metrics victoriametrics/victoria-metrics:v1.147.0-enterprise -license=BASE64_ENCODED_LICENSE_KEY
|
||||
docker run --name=victoria-metrics victoriametrics/victoria-metrics:v1.148.0-enterprise -license=BASE64_ENCODED_LICENSE_KEY
|
||||
```
|
||||
|
||||
Alternatively, the license code can be stored in the file and then referred via `-licenseFile` command-line flag:
|
||||
|
||||
```sh
|
||||
docker run --name=victoria-metrics -v /vm-license:/vm-license victoriametrics/victoria-metrics:v1.147.0-enterprise -licenseFile=/path/to/vm-license
|
||||
docker run --name=victoria-metrics -v /vm-license:/vm-license victoriametrics/victoria-metrics:v1.148.0-enterprise -licenseFile=/path/to/vm-license
|
||||
```
|
||||
|
||||
Example docker-compose configuration:
|
||||
@@ -181,7 +181,7 @@ version: "3.5"
|
||||
services:
|
||||
victoriametrics:
|
||||
container_name: victoriametrics
|
||||
image: victoriametrics/victoria-metrics:v1.147.0
|
||||
image: victoriametrics/victoria-metrics:v1.148.0
|
||||
ports:
|
||||
- 8428:8428
|
||||
volumes:
|
||||
@@ -213,7 +213,7 @@ is used to provide the license key in plain-text:
|
||||
```yaml
|
||||
server:
|
||||
image:
|
||||
tag: v1.147.0-enterprise
|
||||
tag: v1.148.0-enterprise
|
||||
|
||||
license:
|
||||
key: {BASE64_ENCODED_LICENSE_KEY}
|
||||
@@ -224,7 +224,7 @@ In order to provide the license key via existing secret, the following values fi
|
||||
```yaml
|
||||
server:
|
||||
image:
|
||||
tag: v1.147.0-enterprise
|
||||
tag: v1.148.0-enterprise
|
||||
|
||||
license:
|
||||
secret:
|
||||
@@ -274,7 +274,7 @@ spec:
|
||||
license:
|
||||
key: {BASE64_ENCODED_LICENSE_KEY}
|
||||
image:
|
||||
tag: v1.147.0-enterprise
|
||||
tag: v1.148.0-enterprise
|
||||
```
|
||||
|
||||
In order to provide the license key via an existing secret, the following custom resource is used:
|
||||
@@ -291,7 +291,7 @@ spec:
|
||||
name: vm-license
|
||||
key: license
|
||||
image:
|
||||
tag: v1.147.0-enterprise
|
||||
tag: v1.148.0-enterprise
|
||||
```
|
||||
|
||||
Example secret with license key:
|
||||
@@ -342,7 +342,7 @@ Builds are available for amd64 and arm64 architectures.
|
||||
|
||||
Example archive:
|
||||
|
||||
`victoria-metrics-linux-amd64-v1.147.0-enterprise.tar.gz`
|
||||
`victoria-metrics-linux-amd64-v1.148.0-enterprise.tar.gz`
|
||||
|
||||
Includes:
|
||||
|
||||
@@ -351,7 +351,7 @@ Includes:
|
||||
|
||||
Example Docker image:
|
||||
|
||||
`victoriametrics/victoria-metrics:v1.147.0-enterprise-fips` – uses the FIPS-compatible binary and based on `scratch` image.
|
||||
`victoriametrics/victoria-metrics:v1.148.0-enterprise-fips` – uses the FIPS-compatible binary and based on `scratch` image.
|
||||
|
||||
## What Happens to Licensed Components When a License Expires
|
||||
|
||||
|
||||
@@ -35,8 +35,8 @@ scrape_configs:
|
||||
After you created the `scrape.yaml` file, download and unpack [single-node VictoriaMetrics](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/) to the same directory:
|
||||
|
||||
```sh
|
||||
wget https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.147.0/victoria-metrics-linux-amd64-v1.147.0.tar.gz
|
||||
tar xzf victoria-metrics-linux-amd64-v1.147.0.tar.gz
|
||||
wget https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.148.0/victoria-metrics-linux-amd64-v1.148.0.tar.gz
|
||||
tar xzf victoria-metrics-linux-amd64-v1.148.0.tar.gz
|
||||
```
|
||||
|
||||
Then start VictoriaMetrics and instruct it to scrape targets defined in `scrape.yaml` and save scraped metrics
|
||||
@@ -150,8 +150,8 @@ Then start [single-node VictoriaMetrics](https://docs.victoriametrics.com/victor
|
||||
|
||||
```yaml
|
||||
# Download and unpack single-node VictoriaMetrics
|
||||
wget https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.147.0/victoria-metrics-linux-amd64-v1.147.0.tar.gz
|
||||
tar xzf victoria-metrics-linux-amd64-v1.147.0.tar.gz
|
||||
wget https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.148.0/victoria-metrics-linux-amd64-v1.148.0.tar.gz
|
||||
tar xzf victoria-metrics-linux-amd64-v1.148.0.tar.gz
|
||||
|
||||
# Run single-node VictoriaMetrics with the given scrape.yaml
|
||||
./victoria-metrics-prod -promscrape.config=scrape.yaml
|
||||
|
||||
@@ -188,6 +188,9 @@ See the docs at https://docs.victoriametrics.com/victoriametrics/
|
||||
Timezone to use for timestamps in logs. Timezone must be a valid IANA Time Zone. For example: America/New_York, Europe/Berlin, Etc/GMT+3 or Local (default "UTC")
|
||||
-loggerWarnsPerSecondLimit int
|
||||
Per-second limit on the number of WARN messages. If more than the given number of warns are emitted per second, then the remaining warns are suppressed. Zero values disable the rate limit
|
||||
-maxBackfillAge value
|
||||
The maximum allowed age for the ingested samples with historical timestamps. Samples with timestamps older than now-maxBackfillAge are rejected during data ingestion. By default, or when set to 0, -maxBackfillAge equals to -retentionPeriod, e.g. it is unlimited within the configured retention. This can be useful for limiting ingestion of historical samples, for example, when older data has been moved to another storage tier. See https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#retention
|
||||
The following optional suffixes are supported: s (second), h (hour), d (day), w (week), M (month), y (year). If suffix isn't set, then the duration is counted in months (default 0)
|
||||
-maxConcurrentInserts int
|
||||
The maximum number of concurrent insert requests. Set higher value when clients send data over slow networks. Default value depends on the number of available CPU cores. It should work fine in most cases since it minimizes resource usage. See also -insert.maxQueueDuration (default 2*cgroup.AvailableCPUs())
|
||||
-maxIngestionRate int
|
||||
|
||||
@@ -466,6 +466,43 @@ and `-remoteWrite.streamAggr.config`:
|
||||
|
||||
There is also the `-promscrape.configCheckInterval` command-line flag, which can be used to automatically reload configs from the updated `-promscrape.config` file.
|
||||
|
||||
## DNS URLs
|
||||
|
||||
If `vmagent` encounters URLs with the `dns+` prefix in the hostname (such as `http://dns+some-addr:8428/some/path`), it resolves `some-addr` into IP addresses
|
||||
via [DNS A records](https://datatracker.ietf.org/doc/html/rfc1035#section-3.4.1). The port from the original URL is appended to each discovered IP address.
|
||||
Each discovered IP address is used for least-loaded balancing of write requests.
|
||||
|
||||
DNS URLs are supported in the following places:
|
||||
|
||||
* In `-remoteWrite.url` command-line flag. For example, if `victoria-metrics` [DNS A Record](https://datatracker.ietf.org/doc/html/rfc1035#section-3.4.1) record contains
|
||||
`192.168.1.15` IP address, then `-remoteWrite.url=http://dns+victoria-metrics:8428/api/v1/write` is automatically resolved into
|
||||
`-remoteWrite.url=http://192.168.1.15:8428/api/v1/write`.
|
||||
|
||||
DNS URLs are useful when client-side HTTP load balancing is needed. A good example
|
||||
is a [Kubernetes headless Service](https://kubernetes.io/docs/concepts/services-networking/service/#headless-services),
|
||||
which returns multiple IP addresses for a single hostname.
|
||||
|
||||
### DNS URLs and HTTPS
|
||||
|
||||
When a `dns+` URL uses the `https` scheme, `vmagent` connects to the discovered
|
||||
IP addresses directly. This affects [TLS](https://en.wikipedia.org/wiki/Transport_Layer_Security)
|
||||
in two ways:
|
||||
|
||||
* No [SNI](https://en.wikipedia.org/wiki/Server_Name_Indication) is sent in the TLS handshake,
|
||||
since the connection target is an IP address rather than a hostname.
|
||||
* The server certificate is verified against the IP address, so the verification fails
|
||||
unless the certificate contains the corresponding
|
||||
[IP SAN](https://en.wikipedia.org/wiki/Subject_Alternative_Name) entries.
|
||||
|
||||
To use `dns+` URLs with HTTPS, pass the original hostname via the `-remoteWrite.tlsServerName`
|
||||
command-line flag. It is used both as SNI and as the name the server certificate
|
||||
is verified against:
|
||||
|
||||
```sh
|
||||
-remoteWrite.url=https://dns+victoria-metrics:8428/api/v1/write
|
||||
-remoteWrite.tlsServerName=victoria-metrics
|
||||
```
|
||||
|
||||
## SRV URLs
|
||||
|
||||
If `vmagent` encounters URLs with `srv+` prefix in hostname (such as `http://srv+some-addr/some/path`), then it resolves `some-addr` [DNS SRV](https://en.wikipedia.org/wiki/SRV_record)
|
||||
@@ -476,7 +513,7 @@ SRV URLs are supported in the following places:
|
||||
* In `-remoteWrite.url` command-line flag. For example, if `victoria-metrics` [DNS SRV](https://en.wikipedia.org/wiki/SRV_record) record contains
|
||||
`victoria-metrics-host:8428` TCP address, then `-remoteWrite.url=http://srv+victoria-metrics/api/v1/write` is automatically resolved into
|
||||
`-remoteWrite.url=http://victoria-metrics-host:8428/api/v1/write`. If the DNS SRV record is resolved into multiple TCP addresses, then `vmagent`
|
||||
uses a randomly chosen address for each connection it establishes to the remote storage.
|
||||
performs per request least-loaded load-balancing.
|
||||
|
||||
* In scrape target addresses aka `__address__` label. See [these docs](https://docs.victoriametrics.com/victoriametrics/relabeling/#how-to-modify-scrape-urls-in-targets) for details.
|
||||
|
||||
|
||||
@@ -546,7 +546,7 @@ tags at [Docker Hub](https://hub.docker.com/r/victoriametrics/vmalert/tags) and
|
||||
|
||||
## Reading rules from object storage
|
||||
|
||||
[Enterprise version](https://docs.victoriametrics.com/victoriametrics/enterprise/) of `vmalert` may read alerting and recording rules
|
||||
The [Enterprise version](https://docs.victoriametrics.com/victoriametrics/enterprise/) of `vmalert` may read alerting and recording rules
|
||||
from object storage:
|
||||
|
||||
* `./bin/vmalert -rule=s3://bucket/dir/alert.rules` would read rules from the given path at S3 bucket
|
||||
@@ -563,6 +563,48 @@ The following [command-line flags](#flags) can be used for fine-tuning access to
|
||||
* `-s3.customEndpoint` - custom S3 endpoint for use with S3-compatible storages (e.g. MinIO). S3 is used if not set.
|
||||
* `-s3.forcePathStyle` - prefixing endpoint with bucket name when set false, true by default.
|
||||
|
||||
### S3 (AWS and S3-compatible)
|
||||
|
||||
The following example reads rules from an S3 bucket:
|
||||
|
||||
```sh
|
||||
./bin/vmalert \
|
||||
-rule=s3://my-alert-bucket/rules/alerts_ \
|
||||
-s3.credsFilePath=/etc/vmalert/aws-credentials \
|
||||
-datasource.url=http://vmselect:8481/select/0/prometheus \
|
||||
-notifier.url=http://alertmanager:9093 \
|
||||
-licenseFile=/etc/vm-license
|
||||
```
|
||||
|
||||
For S3-compatible backends such as [MinIO](https://www.min.io/) or [Ceph](https://ceph.io/), add `-s3.customEndpoint`:
|
||||
|
||||
```sh
|
||||
./bin/vmalert \
|
||||
-rule=s3://victoriametrics-alert-rules/rules_ \
|
||||
-s3.credsFilePath=/etc/vmalert/aws-credentials \
|
||||
-s3.customEndpoint=http://minio.example.local:9000 \
|
||||
-datasource.url=http://vmselect:8481/select/0/prometheus \
|
||||
-notifier.url=http://alertmanager:9093 \
|
||||
-licenseFile=/etc/vm-license
|
||||
```
|
||||
|
||||
See [Connecting VM components to cloud storage](https://docs.victoriametrics.com/guides/connecting-vm-components-to-cloud-storage/) for details on creating IAM users, credentials file format, environment variables, and IAM roles.
|
||||
|
||||
### Google Cloud Storage (GCS)
|
||||
|
||||
The following example reads rules from a GCS bucket:
|
||||
|
||||
```sh
|
||||
./bin/vmalert \
|
||||
-rule=gs://my-alert-bucket/rules/alerts_ \
|
||||
-s3.credsFilePath=/etc/vmalert/gcp-service-account.json \
|
||||
-datasource.url=http://vmselect:8481/select/0/prometheus \
|
||||
-notifier.url=http://alertmanager:9093 \
|
||||
-licenseFile=/etc/vm-license
|
||||
```
|
||||
|
||||
See [Connecting VM components to cloud storage](https://docs.victoriametrics.com/guides/connecting-vm-components-to-cloud-storage/) for details on creating service accounts and downloading JSON keys.
|
||||
|
||||
## Topology examples
|
||||
|
||||
The following sections are showing how `vmalert` may be used and configured
|
||||
@@ -1470,6 +1512,59 @@ alert_relabel_configs:
|
||||
|
||||
The configuration file can be [hot-reloaded](#hot-config-reload).
|
||||
|
||||
## DNS URLs
|
||||
|
||||
If `vmalert` encounters URLs with the `dns+` prefix in the hostname (such as `http://dns+some-addr:8428/some/path`), it resolves `some-addr` into IP addresses via DNS A/AAAA records.
|
||||
The port from the original URL is appended to each discovered IP address.
|
||||
Each discovered IP address is used for least-loaded balancing of write requests.
|
||||
|
||||
DNS URLs are supported in the following places:
|
||||
|
||||
* In `-remoteWrite.url`, `-remoteRead.url` and `-datasource.url` command-line flags. For example, if `victoria-metrics` [DNS A Record](https://datatracker.ietf.org/doc/html/rfc1035#section-3.4.1) record contains
|
||||
`192.168.1.15` IP address, then `-remoteWrite.url=http://dns+victoria-metrics:8428` is automatically resolved into
|
||||
`-remoteWrite.url=http://192.168.1.15:8428`.
|
||||
|
||||
DNS URLs are useful when client-side HTTP load balancing is needed. A good example
|
||||
is a [Kubernetes headless Service](https://kubernetes.io/docs/concepts/services-networking/service/#headless-services),
|
||||
which returns multiple IP addresses for a single hostname.
|
||||
|
||||
### DNS URLs and HTTPS
|
||||
|
||||
When a `dns+` URL uses the `https` scheme, `vmalert` connects to the discovered
|
||||
IP addresses directly. No [SNI](https://en.wikipedia.org/wiki/Server_Name_Indication)
|
||||
is sent in the TLS handshake, and the server certificate is verified against the IP address,
|
||||
which fails unless the certificate contains the corresponding
|
||||
[IP SAN](https://en.wikipedia.org/wiki/Subject_Alternative_Name) entries.
|
||||
|
||||
To use `dns+` URLs with HTTPS, pass the original hostname via the corresponding
|
||||
`tlsServerName` command-line flag - `-datasource.tlsServerName`, `-remoteRead.tlsServerName`
|
||||
or `-remoteWrite.tlsServerName`. It is used both as SNI and as the name the server
|
||||
certificate is verified against:
|
||||
|
||||
```sh
|
||||
-datasource.url=https://dns+victoria-metrics:8428
|
||||
-datasource.tlsServerName=victoria-metrics
|
||||
```
|
||||
|
||||
Alternatively, issue server certificates with IP SAN entries for every backend IP address.
|
||||
Avoid `tlsInsecureSkipVerify` flags for working around this, since they disable
|
||||
server certificate verification completely.
|
||||
|
||||
## SRV URLs
|
||||
|
||||
If `vmalert` encounters URLs with `srv+` prefix in hostname (such as `http://srv+some-addr/some/path`), then it resolves `some-addr` [DNS SRV](https://en.wikipedia.org/wiki/SRV_record)
|
||||
record into TCP address with hostname and TCP port, and then use the resulting URL when it needs to connect to it.
|
||||
|
||||
SRV URLs are supported in the following places:
|
||||
|
||||
* In `-remoteWrite.url`, `-remoteRead.url` and `-datasource.url` command-line flags. For example, if `victoria-metrics` [DNS SRV](https://en.wikipedia.org/wiki/SRV_record) record contains
|
||||
`victoria-metrics-host:8085`, then `-remoteWrite.url=http://srv+victoria-metrics:8428` is automatically resolved into
|
||||
`-remoteWrite.url=http://victoria-metrics-host:8085`. If the DNS SRV record is resolved into multiple TCP addresses, then `vmalert`
|
||||
performs per request round-robin load-balancing.
|
||||
|
||||
SRV URLs are useful when HTTP services run on different TCP ports or when their TCP ports can change over time (for instance, after a restart).
|
||||
|
||||
|
||||
## Contributing
|
||||
|
||||
`vmalert` is mostly designed and built by VictoriaMetrics community.
|
||||
|
||||
@@ -204,61 +204,45 @@ See [this article](https://medium.com/@valyala/speeding-up-backups-for-big-time-
|
||||
|
||||
### Providing credentials as a file
|
||||
|
||||
Obtaining credentials from a file.
|
||||
See [Connecting VM components to cloud storage](https://docs.victoriametrics.com/guides/connecting-vm-components-to-cloud-storage/) for instructions on obtaining credentials and providing them via credential files, environment variables, cloud provider metadata service, or Kubernetes secrets and IAM roles.
|
||||
|
||||
Add flag `-credsFilePath=/etc/credentials` with the following content:
|
||||
The following examples show the most common authentication patterns for each storage provider.
|
||||
|
||||
* for S3 (AWS, MinIO or other S3 compatible storages):
|
||||
#### S3 (AWS and S3-compatible)
|
||||
|
||||
```sh
|
||||
[default]
|
||||
aws_access_key_id=theaccesskey
|
||||
aws_secret_access_key=thesecretaccesskeyvalue
|
||||
```
|
||||
```sh
|
||||
vmbackup \
|
||||
-storageDataPath=/data \
|
||||
-snapshot.createURL=http://localhost:8428/snapshot/create \
|
||||
-dst=s3://victoriametrics-backup/backup01 \
|
||||
-credsFilePath=/etc/credentials
|
||||
```
|
||||
|
||||
* for GCP cloud storage:
|
||||
#### Google Cloud Storage (GCS)
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "service_account",
|
||||
"project_id": "project-id",
|
||||
"private_key_id": "key-id",
|
||||
"private_key": "-----BEGIN PRIVATE KEY-----\nprivate-key\n-----END PRIVATE KEY-----\n",
|
||||
"client_email": "service-account-email",
|
||||
"client_id": "client-id",
|
||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||
"token_uri": "https://accounts.google.com/o/oauth2/token",
|
||||
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
||||
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/service-account-email"
|
||||
}
|
||||
```
|
||||
```sh
|
||||
vmbackup \
|
||||
-storageDataPath=/data \
|
||||
-snapshot.createURL=http://localhost:8428/snapshot/create \
|
||||
-dst=gs://victoriametrics-backup/backup01 \
|
||||
-credsFilePath=/etc/credentials
|
||||
```
|
||||
|
||||
### Providing credentials via env variables
|
||||
#### Azure Blob Storage
|
||||
|
||||
Obtaining credentials from env variables.
|
||||
```sh
|
||||
export AZURE_STORAGE_ACCOUNT_NAME=mystorageaccount
|
||||
export AZURE_STORAGE_ACCOUNT_KEY=myaccountkey
|
||||
|
||||
* For AWS S3 compatible storages set env variable `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`.
|
||||
Also you can set env variable `AWS_SHARED_CREDENTIALS_FILE` with path to credentials file.
|
||||
* For GCE cloud storage set env variable `GOOGLE_APPLICATION_CREDENTIALS` with path to credentials file.
|
||||
* For Azure storage use one of these env variables:
|
||||
* `AZURE_STORAGE_ACCOUNT_CONNECTION_STRING`: use a connection string (must be either SAS Token or Account/Key)
|
||||
* `AZURE_STORAGE_ACCOUNT_NAME` and `AZURE_STORAGE_ACCOUNT_KEY`: use a specific account name and key (either primary or secondary)
|
||||
* `AZURE_USE_DEFAULT_CREDENTIAL` and `AZURE_STORAGE_ACCOUNT_NAME`: use the `DefaultAzureCredential` to allow the Azure library
|
||||
to search for multiple options (for example, managed identity related variables). Note that if multiple credentials are available,
|
||||
it is required to specify the `AZURE_CLIENT_ID` to select specific credentials.
|
||||
|
||||
The `AZURE_STORAGE_DOMAIN` can be used for optionally overriding the default domain for the Azure storage service.
|
||||
|
||||
Please, note that `vmbackup` will use credentials provided by cloud providers metadata service [when applicable](https://docs.victoriametrics.com/victoriametrics/vmbackup/#using-cloud-providers-metadata-service).
|
||||
|
||||
### Using cloud providers metadata service
|
||||
|
||||
`vmbackup` and `vmbackupmanager` will automatically use cloud providers metadata service in order to obtain credentials if they are running in cloud environment and credentials are not explicitly provided via flags or env variables.
|
||||
vmbackup \
|
||||
-storageDataPath=/data \
|
||||
-snapshot.createURL=http://localhost:8428/snapshot/create \
|
||||
-dst=azblob://victoriametrics-backup/backup01
|
||||
```
|
||||
|
||||
### Providing credentials in Kubernetes
|
||||
|
||||
The simplest way to provide credentials in Kubernetes is to use [Secrets](https://kubernetes.io/docs/concepts/configuration/secret/)
|
||||
and inject them into the pod as environment variables. For example, the following secret can be used for AWS S3 credentials:
|
||||
The simplest way to provide credentials in Kubernetes is to use [Secrets](https://kubernetes.io/docs/concepts/configuration/secret/) and inject them into the pod as environment variables. For example, the following secret can be used for AWS S3 credentials:
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
@@ -266,14 +250,13 @@ kind: Secret
|
||||
metadata:
|
||||
name: vmbackup-credentials
|
||||
data:
|
||||
access_key: key
|
||||
secret_key: secret
|
||||
access_key: <base64-encoded-key>
|
||||
secret_key: <base64-encoded-secret>
|
||||
```
|
||||
|
||||
And then it can be injected into the pod as environment variables:
|
||||
|
||||
```yaml
|
||||
...
|
||||
env:
|
||||
- name: AWS_ACCESS_KEY_ID
|
||||
valueFrom:
|
||||
@@ -285,7 +268,6 @@ env:
|
||||
secretKeyRef:
|
||||
key: secret_key
|
||||
name: vmbackup-credentials
|
||||
...
|
||||
```
|
||||
|
||||
A more secure way is to use IAM roles to provide tokens for pods instead of managing credentials manually.
|
||||
|
||||
@@ -88,34 +88,21 @@ There are two flags which could help with performance tuning:
|
||||
|
||||
### Example of Usage
|
||||
|
||||
GCS and cluster version. You need to have a credentials file in json format with following structure:
|
||||
|
||||
credentials.json
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "service_account",
|
||||
"project_id": "<project>",
|
||||
"private_key_id": "",
|
||||
"private_key": "-----BEGIN PRIVATE KEY-----\-----END PRIVATE KEY-----\n",
|
||||
"client_email": "test@<project>.iam.gserviceaccount.com",
|
||||
"client_id": "",
|
||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||
"token_uri": "https://oauth2.googleapis.com/token",
|
||||
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
||||
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/test%40<project>.iam.gserviceaccount.com"
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Backup manager launched with the following configuration:
|
||||
The following command backs up data to a GCS bucket:
|
||||
|
||||
```sh
|
||||
export NODE_IP=192.168.0.10
|
||||
export VMSTORAGE_ENDPOINT=http://127.0.0.1:8428
|
||||
./vmbackupmanager -dst=gs://vmstorage-data/$NODE_IP -credsFilePath=credentials.json -storageDataPath=/vmstorage-data -snapshot.createURL=$VMSTORAGE_ENDPOINT/snapshot/create -licenseFile=/path/to/vm-license
|
||||
./vmbackupmanager \
|
||||
-dst=gs://vmstorage-data/$NODE_IP \
|
||||
-credsFilePath=credentials.json \
|
||||
-storageDataPath=/vmstorage-data \
|
||||
-snapshot.createURL=$VMSTORAGE_ENDPOINT/snapshot/create \
|
||||
-licenseFile=/path/to/vm-license
|
||||
```
|
||||
|
||||
See [Connecting VM components to cloud storage](https://docs.victoriametrics.com/guides/connecting-vm-components-to-cloud-storage/) for instructions on obtaining credentials and configuring access to S3, GCS, and Azure Blob Storage.
|
||||
|
||||
Expected logs in vmbackupmanager:
|
||||
|
||||
```sh
|
||||
@@ -147,8 +134,7 @@ objects. Typical object storage systems implement server-side copy by creating n
|
||||
This is very fast and efficient. Unfortunately there are systems such as [S3 Glacier](https://aws.amazon.com/s3/storage-classes/glacier/),
|
||||
which perform full object copy during server-side copying. This may be slow and expensive.
|
||||
|
||||
Please, see [vmbackup docs](https://docs.victoriametrics.com/victoriametrics/vmbackup/#advanced-usage) for more examples of authentication with different
|
||||
storage types.
|
||||
See [Connecting VM components to cloud storage](https://docs.victoriametrics.com/guides/connecting-vm-components-to-cloud-storage/) for more examples of authentication with different storage types.
|
||||
|
||||
### Backup Retention Policy
|
||||
|
||||
@@ -492,6 +478,12 @@ Clusters here are referred to as `source` and `destination`.
|
||||
|
||||
`vmbackupmanager` exports various metrics in Prometheus exposition format at `http://vmbackupmanager:8300/metrics` page. It is recommended to set up regular scraping of this page either via [vmagent](https://docs.victoriametrics.com/victoriametrics/vmagent/) or via Prometheus, so the exported metrics could be analyzed later.
|
||||
|
||||
To verify that `vmbackupmanager` is executing backup tasks normally, the following metrics can help:
|
||||
|
||||
* `vm_backup_last_success_at{type="<backup_type>"}` - unix timestamp of the last successful backup{{% available_from "#" %}}. Remains `0` if no backup has completed successfully since startup. Check error logs and verify remote storage accessibility if this persists.
|
||||
* `vm_backup_last_run_failed{type="<backup_type>"}` - whether the last backup task for the given backup type failed. The value `1` means the last task failed. Check the error logs of `vmbackupmanager` for the root cause
|
||||
* `vm_backup_errors_total{type="<backup_type>"}` - total number of backup errors for the given backup type.
|
||||
|
||||
Use the official [Grafana dashboard](https://grafana.com/grafana/dashboards/17798) for `vmbackupmanager` overview.
|
||||
Graphs on this dashboard contain useful hints - hover the `i` icon in the top left corner of each graph in order to read it.
|
||||
If you have suggestions for improvements or have found a bug - please open an issue on github or add a review to the dashboard.
|
||||
|
||||
@@ -34,9 +34,9 @@ vmctl command-line tool is available as:
|
||||
|
||||
Download and unpack vmctl:
|
||||
```sh
|
||||
wget https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.147.0/vmutils-darwin-arm64-v1.147.0.tar.gz
|
||||
wget https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.148.0/vmutils-darwin-arm64-v1.148.0.tar.gz
|
||||
|
||||
tar xzf vmutils-darwin-arm64-v1.147.0.tar.gz
|
||||
tar xzf vmutils-darwin-arm64-v1.148.0.tar.gz
|
||||
```
|
||||
|
||||
Once binary is unpacked, see the full list of supported modes by running the following command:
|
||||
|
||||
@@ -47,13 +47,13 @@ i.e. the end result would be similar to [rsync --delete](https://askubuntu.com/q
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
* See [how to setup credentials via environment variables](https://docs.victoriametrics.com/victoriametrics/vmbackup/#providing-credentials-via-env-variables).
|
||||
* See [Connecting VM components to cloud storage](https://docs.victoriametrics.com/guides/connecting-vm-components-to-cloud-storage/) for instructions on configuring credentials.
|
||||
* If `vmrestore` consumes all the network bandwidth, then set `-maxBytesPerSecond` to the desired value.
|
||||
* If `vmrestore` has been interrupted due to temporary error, then just restart it with the same args. It will resume the restore process.
|
||||
|
||||
## Advanced usage
|
||||
|
||||
Please, see [vmbackup docs](https://docs.victoriametrics.com/victoriametrics/vmbackup/#advanced-usage) for examples of authentication
|
||||
See [Connecting VM components to cloud storage](https://docs.victoriametrics.com/guides/connecting-vm-components-to-cloud-storage/) for examples of authentication
|
||||
with different storage types.
|
||||
|
||||
Run `vmrestore -help` in order to see all the available options:
|
||||
|
||||
@@ -41,6 +41,8 @@ See the docs at https://docs.victoriametrics.com/victoriametrics/cluster-victori
|
||||
Flag value can be read from the given http/https url when using -deleteAuthKey=http://host/path or -deleteAuthKey=https://host/path
|
||||
-denyQueryTracing
|
||||
Whether to disable the ability to trace queries. See https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#query-tracing
|
||||
-enableMetadata
|
||||
Whether to enable metadata processing for metrics scraped from targets, received via VictoriaMetrics remote write, Prometheus remote write v1 or OpenTelemetry protocol. See also remoteWrite.maxMetadataPerBlock (default true)
|
||||
-enableMultitenancyViaHeaders
|
||||
Enables multitenancy via HTTP headers. See https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/#multitenancy-via-headers
|
||||
-enableTCP6
|
||||
@@ -103,6 +105,8 @@ See the docs at https://docs.victoriametrics.com/victoriametrics/cluster-victori
|
||||
Whether to use proxy protocol for connections accepted at the given -httpListenAddr . See https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt . With enabled proxy protocol http server cannot serve regular /metrics endpoint. Use -pushmetrics.url for metrics pushing
|
||||
Supports array of values separated by comma or specified via multiple flags.
|
||||
Empty values are set to false.
|
||||
-insert.maxQueueDuration duration
|
||||
The maximum duration to wait in the queue when -maxConcurrentInserts concurrent insert requests are executed (default 1m0s)
|
||||
-internStringCacheExpireDuration duration
|
||||
The expiry duration for caches for interned strings. See https://en.wikipedia.org/wiki/String_interning . See also -internStringMaxLen and -internStringDisableCache (default 6m0s)
|
||||
-internStringDisableCache
|
||||
@@ -127,6 +131,8 @@ See the docs at https://docs.victoriametrics.com/victoriametrics/cluster-victori
|
||||
Timezone to use for timestamps in logs. Timezone must be a valid IANA Time Zone. For example: America/New_York, Europe/Berlin, Etc/GMT+3 or Local (default "UTC")
|
||||
-loggerWarnsPerSecondLimit int
|
||||
Per-second limit on the number of WARN messages. If more than the given number of warns are emitted per second, then the remaining warns are suppressed. Zero values disable the rate limit
|
||||
-maxConcurrentInserts int
|
||||
The maximum number of concurrent insert requests. Set higher value when clients send data over slow networks. Default value depends on the number of available CPU cores. It should work fine in most cases since it minimizes resource usage. See also -insert.maxQueueDuration (default 24)
|
||||
-memory.allowedBytes size
|
||||
Allowed size of system memory VictoriaMetrics caches may occupy. This option overrides -memory.allowedPercent if set to a non-zero value. Too low a value may increase the cache miss rate usually resulting in higher CPU and disk IO usage. Too high a value may evict too much data from the OS page cache resulting in higher disk IO usage. The process may behave unexpectedly if this flag is set too small (e.g., 1 byte).
|
||||
Supports the following optional suffixes for size values: KB, MB, GB, TB, KiB, MiB, GiB, TiB (default 0)
|
||||
@@ -148,6 +154,119 @@ See the docs at https://docs.victoriametrics.com/victoriametrics/cluster-victori
|
||||
Flag value can be read from the given http/https url when using -pprofAuthKey=http://host/path or -pprofAuthKey=https://host/path
|
||||
-prevCacheRemovalPercent float
|
||||
Items in the previous caches are removed when the percent of requests it serves becomes lower than this value. Higher values reduce memory usage at the cost of higher CPU usage. See also -cacheExpireDuration (default 0.1)
|
||||
-promscrape.azureSDCheckInterval duration
|
||||
Interval for checking for changes in Azure. This works only if azure_sd_configs is configured in '-promscrape.config' file. See https://docs.victoriametrics.com/victoriametrics/sd_configs/#azure_sd_configs for details (default 1m0s)
|
||||
-promscrape.cluster.memberLabel string
|
||||
If non-empty, then the label with this name and the -promscrape.cluster.memberNum value is added to all the scraped metrics. See https://docs.victoriametrics.com/victoriametrics/vmagent/#scraping-big-number-of-targets for more info
|
||||
-promscrape.cluster.memberNum string
|
||||
The number of vmagent instance in the cluster of scrapers. It must be a unique value in the range 0 ... promscrape.cluster.membersCount-1 across scrapers in the cluster. Can be specified as pod name of Kubernetes StatefulSet - pod-name-Num, where Num is a numeric part of pod name. See also -promscrape.cluster.memberLabel . See https://docs.victoriametrics.com/victoriametrics/vmagent/#scraping-big-number-of-targets for more info (default "0")
|
||||
-promscrape.cluster.memberURLTemplate string
|
||||
An optional template for URL to access vmagent instance with the given -promscrape.cluster.memberNum value. Every %d occurrence in the template is substituted with -promscrape.cluster.memberNum at urls to vmagent instances responsible for scraping the given target at /service-discovery page. For example -promscrape.cluster.memberURLTemplate='http://vmagent-%d:8429/targets'. See https://docs.victoriametrics.com/victoriametrics/vmagent/#scraping-big-number-of-targets for more details
|
||||
-promscrape.cluster.membersCount int
|
||||
The number of members in a cluster of scrapers. Each member must have a unique -promscrape.cluster.memberNum in the range 0 ... promscrape.cluster.membersCount-1 . Each member then scrapes roughly 1/N of all the targets. By default, cluster scraping is disabled, i.e. a single scraper scrapes all the targets. See https://docs.victoriametrics.com/victoriametrics/vmagent/#scraping-big-number-of-targets for more info (default 1)
|
||||
-promscrape.cluster.name string
|
||||
Optional name of the cluster. If multiple vmagent clusters scrape the same targets, then each cluster must have unique name in order to properly de-duplicate samples received from these clusters. See https://docs.victoriametrics.com/victoriametrics/vmagent/#scraping-big-number-of-targets for more info
|
||||
-promscrape.cluster.replicationFactor int
|
||||
The number of members in the cluster, which scrape the same targets. If the replication factor is greater than 1, then the deduplication must be enabled at remote storage side. See https://docs.victoriametrics.com/victoriametrics/vmagent/#scraping-big-number-of-targets for more info (default 1)
|
||||
-promscrape.cluster.shardByLabels array
|
||||
Optional list of target labels, which will be used for sharding targets among cluster members if -promscrape.cluster.membersCount is greater than 1. If none of the specified labels are found in a target, then all the target labels will be used for sharding. See https://docs.victoriametrics.com/victoriametrics/vmagent/#scraping-big-number-of-targets for more info
|
||||
Supports an array of values separated by comma or specified via multiple flags.
|
||||
Each array item can contain comma inside single-quoted or double-quoted string, {}, [] and () braces.
|
||||
-promscrape.config string
|
||||
Optional path to Prometheus config file with 'scrape_configs' section containing targets to scrape. The path can point to local file and to http url. See https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#how-to-scrape-prometheus-exporters-such-as-node-exporter for details
|
||||
-promscrape.config.dryRun
|
||||
Checks -promscrape.config file for errors and unsupported fields and then exits. Returns non-zero exit code on parsing errors and emits these errors to stderr. See also -promscrape.config.strictParse command-line flag. Pass -loggerLevel=ERROR if you don't need to see info messages in the output.
|
||||
-promscrape.config.strictParse
|
||||
Whether to deny unsupported fields in -promscrape.config . Set to false in order to silently skip unsupported fields (default true)
|
||||
-promscrape.configCheckInterval duration
|
||||
Interval for checking for changes in -promscrape.config file. By default, the checking is disabled. See how to reload -promscrape.config file at https://docs.victoriametrics.com/victoriametrics/vmagent/#configuration-update
|
||||
-promscrape.consul.waitTime duration
|
||||
Wait time used by Consul service discovery. Default value is used if not set
|
||||
-promscrape.consulSDCheckInterval duration
|
||||
Interval for checking for changes in Consul. This works only if consul_sd_configs is configured in '-promscrape.config' file. See https://docs.victoriametrics.com/victoriametrics/sd_configs/#consul_sd_configs for details (default 30s)
|
||||
-promscrape.consulagentSDCheckInterval duration
|
||||
Interval for checking for changes in Consul Agent. This works only if consulagent_sd_configs is configured in '-promscrape.config' file. See https://docs.victoriametrics.com/victoriametrics/sd_configs/#consulagent_sd_configs for details (default 30s)
|
||||
-promscrape.digitaloceanSDCheckInterval duration
|
||||
Interval for checking for changes in digital ocean. This works only if digitalocean_sd_configs is configured in '-promscrape.config' file. See https://docs.victoriametrics.com/victoriametrics/sd_configs/#digitalocean_sd_configs for details (default 1m0s)
|
||||
-promscrape.disableCompression
|
||||
Whether to disable sending 'Accept-Encoding: gzip' request headers to all the scrape targets. This may reduce CPU usage on scrape targets at the cost of higher network bandwidth utilization. It is possible to set 'disable_compression: true' individually per each 'scrape_config' section in '-promscrape.config' for fine-grained control
|
||||
-promscrape.disableKeepAlive
|
||||
Whether to disable HTTP keep-alive connections when scraping all the targets. This may be useful when targets has no support for HTTP keep-alive connection. It is possible to set 'disable_keepalive: true' individually per each 'scrape_config' section in '-promscrape.config' for fine-grained control. Note that disabling HTTP keep-alive may increase load on both vmagent and scrape targets
|
||||
-promscrape.discovery.concurrency int
|
||||
The maximum number of concurrent requests to Prometheus autodiscovery API (Consul, Kubernetes, etc.) (default 100)
|
||||
-promscrape.discovery.concurrentWaitTime duration
|
||||
The maximum duration for waiting to perform API requests if more than -promscrape.discovery.concurrency requests are simultaneously performed (default 1m0s)
|
||||
-promscrape.dnsSDCheckInterval duration
|
||||
Interval for checking for changes in dns. This works only if dns_sd_configs is configured in '-promscrape.config' file. See https://docs.victoriametrics.com/victoriametrics/sd_configs/#dns_sd_configs for details (default 30s)
|
||||
-promscrape.dockerSDCheckInterval duration
|
||||
Interval for checking for changes in docker. This works only if docker_sd_configs is configured in '-promscrape.config' file. See https://docs.victoriametrics.com/victoriametrics/sd_configs/#docker_sd_configs for details (default 30s)
|
||||
-promscrape.dockerswarmSDCheckInterval duration
|
||||
Interval for checking for changes in dockerswarm. This works only if dockerswarm_sd_configs is configured in '-promscrape.config' file. See https://docs.victoriametrics.com/victoriametrics/sd_configs/#dockerswarm_sd_configs for details (default 30s)
|
||||
-promscrape.dropOriginalLabels
|
||||
Whether to drop original labels for scrape targets at /targets and /api/v1/targets pages. This may be needed for reducing memory usage when original labels for big number of scrape targets occupy big amounts of memory. Note that this reduces debuggability for improper per-target relabeling configs
|
||||
-promscrape.ec2SDCheckInterval duration
|
||||
Interval for checking for changes in ec2. This works only if ec2_sd_configs is configured in '-promscrape.config' file. See https://docs.victoriametrics.com/victoriametrics/sd_configs/#ec2_sd_configs for details (default 1m0s)
|
||||
-promscrape.eurekaSDCheckInterval duration
|
||||
Interval for checking for changes in eureka. This works only if eureka_sd_configs is configured in '-promscrape.config' file. See https://docs.victoriametrics.com/victoriametrics/sd_configs/#eureka_sd_configs for details (default 30s)
|
||||
-promscrape.fileSDCheckInterval duration
|
||||
Interval for checking for changes in 'file_sd_config'. See https://docs.victoriametrics.com/victoriametrics/sd_configs/#file_sd_configs for details (default 1m0s)
|
||||
-promscrape.gceSDCheckInterval duration
|
||||
Interval for checking for changes in gce. This works only if gce_sd_configs is configured in '-promscrape.config' file. See https://docs.victoriametrics.com/victoriametrics/sd_configs/#gce_sd_configs for details (default 1m0s)
|
||||
-promscrape.hetznerSDCheckInterval duration
|
||||
Interval for checking for changes in Hetzner API. This works only if hetzner_sd_configs is configured in '-promscrape.config' file. See https://docs.victoriametrics.com/victoriametrics/sd_configs/#hetzner_sd_configs for details (default 1m0s)
|
||||
-promscrape.httpSDCheckInterval duration
|
||||
Interval for checking for changes in http endpoint service discovery. This works only if http_sd_configs is configured in '-promscrape.config' file. See https://docs.victoriametrics.com/victoriametrics/sd_configs/#http_sd_configs for details (default 1m0s)
|
||||
-promscrape.kubernetes.apiServerTimeout duration
|
||||
How frequently to reload the full state from Kubernetes API server (default 30m0s)
|
||||
-promscrape.kubernetes.attachNamespaceMetadataAll
|
||||
Whether to set attach_metadata.namespace=true for all the kubernetes_sd_configs at -promscrape.config . It is possible to set attach_metadata.namespace=false individually per each kubernetes_sd_configs . See https://docs.victoriametrics.com/victoriametrics/sd_configs/#kubernetes_sd_configs
|
||||
-promscrape.kubernetes.attachNodeMetadataAll
|
||||
Whether to set attach_metadata.node=true for all the kubernetes_sd_configs at -promscrape.config . It is possible to set attach_metadata.node=false individually per each kubernetes_sd_configs . See https://docs.victoriametrics.com/victoriametrics/sd_configs/#kubernetes_sd_configs
|
||||
-promscrape.kubernetes.useHTTP2Client
|
||||
Whether to use HTTP/2 client for connection to Kubernetes API server. This may reduce amount of concurrent connections to API server when watching for a big number of Kubernetes objects.
|
||||
-promscrape.kubernetesSDCheckInterval duration
|
||||
Interval for checking for changes in Kubernetes API server. This works only if kubernetes_sd_configs is configured in '-promscrape.config' file. See https://docs.victoriametrics.com/victoriametrics/sd_configs/#kubernetes_sd_configs for details (default 30s)
|
||||
-promscrape.kumaSDCheckInterval duration
|
||||
Interval for checking for changes in kuma service discovery. This works only if kuma_sd_configs is configured in '-promscrape.config' file. See https://docs.victoriametrics.com/victoriametrics/sd_configs/#kuma_sd_configs for details (default 30s)
|
||||
-promscrape.marathonSDCheckInterval duration
|
||||
Interval for checking for changes in Marathon REST API. This works only if marathon_sd_configs is configured in '-promscrape.config' file. See https://docs.victoriametrics.com/victoriametrics/sd_configs/#marathon_sd_configs for details (default 30s)
|
||||
-promscrape.maxDroppedTargets int
|
||||
The maximum number of droppedTargets to show at /api/v1/targets page. Increase this value if your setup drops more scrape targets during relabeling and you need investigating labels for all the dropped targets. Note that the increased number of tracked dropped targets may result in increased memory usage (default 10000)
|
||||
-promscrape.maxResponseHeadersSize size
|
||||
The maximum size of http response headers from Prometheus scrape targets
|
||||
Supports the following optional suffixes for size values: KB, MB, GB, TB, KiB, MiB, GiB, TiB (default 4096)
|
||||
-promscrape.maxScrapeSize size
|
||||
The maximum size of uncompressed scrape response in bytes to process from Prometheus targets. Bigger uncompressed responses are rejected. See also max_scrape_size option at https://docs.victoriametrics.com/victoriametrics/sd_configs/#scrape_configs
|
||||
Supports the following optional suffixes for size values: KB, MB, GB, TB, KiB, MiB, GiB, TiB (default 16777216)
|
||||
-promscrape.minResponseSizeForStreamParse size
|
||||
The minimum target response size for automatic switching to stream parsing mode, which can reduce memory usage. See https://docs.victoriametrics.com/victoriametrics/vmagent/#stream-parsing-mode
|
||||
Supports the following optional suffixes for size values: KB, MB, GB, TB, KiB, MiB, GiB, TiB (default 1000000)
|
||||
-promscrape.noStaleMarkers
|
||||
Whether to disable sending Prometheus stale markers for metrics when scrape target disappears. This option may reduce memory usage if stale markers aren't needed for your setup. This option also disables populating the scrape_series_added metric. See https://prometheus.io/docs/concepts/jobs_instances/#automatically-generated-labels-and-time-series
|
||||
-promscrape.nomad.waitTime duration
|
||||
Wait time used by Nomad service discovery. Default value is used if not set
|
||||
-promscrape.nomadSDCheckInterval duration
|
||||
Interval for checking for changes in Nomad. This works only if nomad_sd_configs is configured in '-promscrape.config' file. See https://docs.victoriametrics.com/victoriametrics/sd_configs/#nomad_sd_configs for details (default 30s)
|
||||
-promscrape.openstackSDCheckInterval duration
|
||||
Interval for checking for changes in openstack API server. This works only if openstack_sd_configs is configured in '-promscrape.config' file. See https://docs.victoriametrics.com/victoriametrics/sd_configs/#openstack_sd_configs for details (default 30s)
|
||||
-promscrape.ovhcloudSDCheckInterval duration
|
||||
Interval for checking for changes in OVH Cloud API. This works only if ovhcloud_sd_configs is configured in '-promscrape.config' file. See https://docs.victoriametrics.com/victoriametrics/sd_configs/#ovhcloud_sd_configs for details (default 30s)
|
||||
-promscrape.puppetdbSDCheckInterval duration
|
||||
Interval for checking for changes in PuppetDB API. This works only if puppetdb_sd_configs is configured in '-promscrape.config' file. See https://docs.victoriametrics.com/victoriametrics/sd_configs/#puppetdb_sd_configs for details (default 30s)
|
||||
-promscrape.seriesLimitPerTarget int
|
||||
Optional limit on the number of unique time series a single scrape target can expose. See https://docs.victoriametrics.com/victoriametrics/vmagent/#cardinality-limiter for more info
|
||||
-promscrape.streamParse
|
||||
Whether to enable stream parsing for metrics obtained from scrape targets. This may be useful for reducing memory usage when millions of metrics are exposed per each scrape target. It is possible to set 'stream_parse: true' individually per each 'scrape_config' section in '-promscrape.config' for fine-grained control
|
||||
-promscrape.suppressDuplicateScrapeTargetErrors
|
||||
Whether to suppress 'duplicate scrape target' errors; see https://docs.victoriametrics.com/victoriametrics/vmagent/#troubleshooting for details
|
||||
-promscrape.suppressScrapeErrors
|
||||
Whether to suppress scrape errors logging. The last error for each target is always available at '/targets' page even if scrape errors logging is suppressed. See also -promscrape.suppressScrapeErrorsDelay
|
||||
-promscrape.suppressScrapeErrorsDelay duration
|
||||
The delay for suppressing repeated scrape errors logging per each scrape targets. This may be used for reducing the number of log lines related to scrape errors. See also -promscrape.suppressScrapeErrors
|
||||
-promscrape.vultrSDCheckInterval duration
|
||||
Interval for checking for changes in Vultr. This works only if vultr_sd_configs is configured in '-promscrape.config' file. See https://docs.victoriametrics.com/victoriametrics/sd_configs/#vultr_sd_configs for details (default 30s)
|
||||
-promscrape.yandexcloudSDCheckInterval duration
|
||||
Interval for checking for changes in Yandex Cloud API. This works only if yandexcloud_sd_configs is configured in '-promscrape.config' file. See https://docs.victoriametrics.com/victoriametrics/sd_configs/#yandexcloud_sd_configs for details (default 30s)
|
||||
-pushmetrics.disableCompression
|
||||
Whether to disable request body compression when pushing metrics to every -pushmetrics.url
|
||||
-pushmetrics.extraLabel array
|
||||
|
||||
@@ -130,6 +130,9 @@ See the docs at https://docs.victoriametrics.com/victoriametrics/cluster-victori
|
||||
Timezone to use for timestamps in logs. Timezone must be a valid IANA Time Zone. For example: America/New_York, Europe/Berlin, Etc/GMT+3 or Local (default "UTC")
|
||||
-loggerWarnsPerSecondLimit int
|
||||
Per-second limit on the number of WARN messages. If more than the given number of warns are emitted per second, then the remaining warns are suppressed. Zero values disable the rate limit
|
||||
-maxBackfillAge value
|
||||
The maximum allowed age for the ingested samples with historical timestamps. Samples with timestamps older than now-maxBackfillAge are rejected during data ingestion. By default, or when set to 0, -maxBackfillAge equals to -retentionPeriod, e.g. it is unlimited within the configured retention. This can be useful for limiting ingestion of historical samples, for example, when older data has been moved to another storage tier. See https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#retention
|
||||
The following optional suffixes are supported: s (second), h (hour), d (day), w (week), M (month), y (year). If suffix isn't set, then the duration is counted in months (default 0)
|
||||
-maxConcurrentInserts int
|
||||
The maximum number of concurrent insert requests. Set higher value when clients send data over slow networks. Default value depends on the number of available CPU cores. It should work fine in most cases since it minimizes resource usage. See also -insert.maxQueueDuration (default 2*cgroup.AvailableCPUs())
|
||||
-memory.allowedBytes size
|
||||
|
||||
134
go.mod
134
go.mod
@@ -3,7 +3,7 @@ module github.com/VictoriaMetrics/VictoriaMetrics
|
||||
go 1.26.5
|
||||
|
||||
require (
|
||||
cloud.google.com/go/storage v1.62.3
|
||||
cloud.google.com/go/storage v1.64.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.22.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.8.0
|
||||
@@ -12,65 +12,65 @@ require (
|
||||
github.com/VictoriaMetrics/fastcache v1.13.3
|
||||
github.com/VictoriaMetrics/metrics v1.44.0
|
||||
github.com/VictoriaMetrics/metricsql v0.87.3
|
||||
github.com/aws/aws-sdk-go-v2 v1.42.0
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.25
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.27
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.103.3
|
||||
github.com/aws/aws-sdk-go-v2 v1.43.0
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.31
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.35
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.106.0
|
||||
github.com/bmatcuk/doublestar/v4 v4.10.0
|
||||
github.com/cespare/xxhash/v2 v2.3.0
|
||||
github.com/cheggaaa/pb/v3 v3.1.7
|
||||
github.com/cheggaaa/pb/v3 v3.2.0
|
||||
github.com/gogo/protobuf v1.3.2
|
||||
github.com/golang/snappy v1.0.0
|
||||
github.com/google/go-cmp v0.7.0
|
||||
github.com/googleapis/gax-go/v2 v2.22.0
|
||||
github.com/googleapis/gax-go/v2 v2.23.0
|
||||
github.com/influxdata/influxdb v1.12.4
|
||||
github.com/klauspost/compress v1.18.6
|
||||
github.com/klauspost/compress v1.19.1
|
||||
github.com/oklog/ulid/v2 v2.1.1
|
||||
github.com/prometheus/prometheus v0.312.0
|
||||
github.com/prometheus/prometheus v0.313.1
|
||||
github.com/urfave/cli/v2 v2.27.7
|
||||
github.com/valyala/fastjson v1.6.10
|
||||
github.com/valyala/fastrand v1.1.0
|
||||
github.com/valyala/fasttemplate v1.2.2
|
||||
github.com/valyala/gozstd v1.24.0
|
||||
github.com/valyala/gozstd v1.25.0
|
||||
github.com/valyala/histogram v1.2.0
|
||||
github.com/valyala/quicktemplate v1.8.0
|
||||
golang.org/x/net v0.56.0
|
||||
golang.org/x/net v0.57.0
|
||||
golang.org/x/oauth2 v0.36.0
|
||||
golang.org/x/sys v0.47.0
|
||||
google.golang.org/api v0.284.0
|
||||
google.golang.org/api v0.290.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
require (
|
||||
cel.dev/expr v0.25.2 // indirect
|
||||
cloud.google.com/go v0.123.0 // indirect
|
||||
cloud.google.com/go/auth v0.20.0 // indirect
|
||||
cloud.google.com/go/auth v0.22.0 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
||||
cloud.google.com/go/iam v1.11.0 // indirect
|
||||
cloud.google.com/go/monitoring v1.29.0 // indirect
|
||||
cloud.google.com/go/iam v1.12.0 // indirect
|
||||
cloud.google.com/go/monitoring v1.30.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.7.2 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.33.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.57.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.57.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.34.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.58.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.58.0 // indirect
|
||||
github.com/VividCortex/ewma v1.2.0 // indirect
|
||||
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.24 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.29 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.29 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.29 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.30 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.12 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.22 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.29 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.29 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.2.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.31.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.43.3 // indirect
|
||||
github.com/aws/smithy-go v1.27.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.14 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.30 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.31 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.31 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.31 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.32 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.24 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.31 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.32 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.5.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.33.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.38.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.45.0 // indirect
|
||||
github.com/aws/smithy-go v1.27.4 // indirect
|
||||
github.com/bboreham/go-loser v0.0.0-20230920113527-fcc2c21820a3 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||
@@ -85,14 +85,14 @@ require (
|
||||
github.com/felixge/httpsnoop v1.1.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.9.2 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/logr v1.4.4 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
||||
github.com/gobwas/glob v0.2.3 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
|
||||
github.com/google/s2a-go v0.1.9 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.16 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.18 // indirect
|
||||
github.com/grafana/regexp v0.0.0-20250905093917-f7b3be9d1853 // indirect
|
||||
github.com/hashicorp/go-version v1.9.0 // indirect
|
||||
github.com/jpillora/backoff v1.0.0 // indirect
|
||||
@@ -102,7 +102,7 @@ require (
|
||||
github.com/knadh/koanf/v2 v2.3.5 // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.15 // indirect
|
||||
github.com/mattn/go-isatty v0.0.22 // indirect
|
||||
github.com/mattn/go-isatty v0.0.23 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.24 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
@@ -110,36 +110,36 @@ require (
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
|
||||
github.com/open-telemetry/opentelemetry-collector-contrib/internal/exp/metrics v0.154.0 // indirect
|
||||
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil v0.154.0 // indirect
|
||||
github.com/open-telemetry/opentelemetry-collector-contrib/processor/deltatocumulativeprocessor v0.154.0 // indirect
|
||||
github.com/open-telemetry/opentelemetry-collector-contrib/internal/exp/metrics v0.157.0 // indirect
|
||||
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil v0.157.0 // indirect
|
||||
github.com/open-telemetry/opentelemetry-collector-contrib/processor/deltatocumulativeprocessor v0.157.0 // indirect
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_golang v1.23.2 // indirect
|
||||
github.com/prometheus/client_golang/exp v0.0.0-20260602051030-3537b20ac86b // indirect
|
||||
github.com/prometheus/client_golang v1.24.0 // indirect
|
||||
github.com/prometheus/client_golang/exp v0.0.0-20260723095000-436bf2e0fb60 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.68.1 // indirect
|
||||
github.com/prometheus/common v0.70.1 // indirect
|
||||
github.com/prometheus/otlptranslator v1.0.0 // indirect
|
||||
github.com/prometheus/procfs v0.20.1 // indirect
|
||||
github.com/prometheus/procfs v0.21.1 // indirect
|
||||
github.com/prometheus/sigv4 v0.4.1 // indirect
|
||||
github.com/puzpuzpuz/xsync/v4 v4.5.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/spiffe/go-spiffe/v2 v2.7.0 // indirect
|
||||
github.com/spiffe/go-spiffe/v2 v2.8.1 // indirect
|
||||
github.com/stretchr/testify v1.11.1 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/collector/component v1.60.0 // indirect
|
||||
go.opentelemetry.io/collector/confmap v1.60.0 // indirect
|
||||
go.opentelemetry.io/collector/confmap/xconfmap v0.154.0 // indirect
|
||||
go.opentelemetry.io/collector/consumer v1.60.0 // indirect
|
||||
go.opentelemetry.io/collector/featuregate v1.60.0 // indirect
|
||||
go.opentelemetry.io/collector/internal/componentalias v0.154.0 // indirect
|
||||
go.opentelemetry.io/collector/pdata v1.60.0 // indirect
|
||||
go.opentelemetry.io/collector/pipeline v1.60.0 // indirect
|
||||
go.opentelemetry.io/collector/processor v1.60.0 // indirect
|
||||
go.opentelemetry.io/collector/component v1.63.0 // indirect
|
||||
go.opentelemetry.io/collector/confmap v1.63.0 // indirect
|
||||
go.opentelemetry.io/collector/confmap/xconfmap v0.157.0 // indirect
|
||||
go.opentelemetry.io/collector/consumer v1.63.0 // indirect
|
||||
go.opentelemetry.io/collector/featuregate v1.63.0 // indirect
|
||||
go.opentelemetry.io/collector/internal/componentalias v0.157.0 // indirect
|
||||
go.opentelemetry.io/collector/pdata v1.63.0 // indirect
|
||||
go.opentelemetry.io/collector/pipeline v1.63.0 // indirect
|
||||
go.opentelemetry.io/collector/processor v1.63.0 // indirect
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.44.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.69.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.69.0 // indirect
|
||||
@@ -155,26 +155,26 @@ require (
|
||||
go.uber.org/zap v1.28.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.4 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/crypto v0.53.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20260611194520-c48552f49976 // indirect
|
||||
golang.org/x/sync v0.21.0 // indirect
|
||||
golang.org/x/term v0.44.0 // indirect
|
||||
golang.org/x/text v0.38.0 // indirect
|
||||
golang.org/x/crypto v0.54.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20260718201538-764159d718ef // indirect
|
||||
golang.org/x/sync v0.22.0 // indirect
|
||||
golang.org/x/term v0.45.0 // indirect
|
||||
golang.org/x/text v0.40.0 // indirect
|
||||
golang.org/x/time v0.15.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20260615183401-62b3387ff324 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260615183401-62b3387ff324 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260615183401-62b3387ff324 // indirect
|
||||
google.golang.org/grpc v1.81.1 // indirect
|
||||
google.golang.org/genproto v0.0.0-20260720211330-0afa2a65878a // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260720211330-0afa2a65878a // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260720211330-0afa2a65878a // indirect
|
||||
google.golang.org/grpc v1.82.1 // indirect
|
||||
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/apimachinery v0.36.2 // indirect
|
||||
k8s.io/client-go v0.36.2 // indirect
|
||||
k8s.io/apimachinery v0.36.3 // indirect
|
||||
k8s.io/client-go v0.36.3 // indirect
|
||||
k8s.io/klog/v2 v2.140.0 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20260603220949-865597e52e25 // indirect
|
||||
k8s.io/utils v0.0.0-20260507154919-ff6756f316d2 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad // indirect
|
||||
k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
|
||||
sigs.k8s.io/randfill v1.0.0 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.4.0 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.4.2 // indirect
|
||||
sigs.k8s.io/yaml v1.6.0 // indirect
|
||||
)
|
||||
|
||||
432
go.sum
432
go.sum
@@ -2,22 +2,22 @@ cel.dev/expr v0.25.2 h1:K6j46C81hXtZQfuX60cVWQFBJahKSE2gfRbNuvr5bFs=
|
||||
cel.dev/expr v0.25.2/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
|
||||
cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=
|
||||
cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
|
||||
cloud.google.com/go/auth v0.20.0 h1:kXTssoVb4azsVDoUiF8KvxAqrsQcQtB53DcSgta74CA=
|
||||
cloud.google.com/go/auth v0.20.0/go.mod h1:942/yi/itH1SsmpyrbnTMDgGfdy2BUqIKyd0cyYLc5Q=
|
||||
cloud.google.com/go/auth v0.22.0 h1:Xp9wAKkLoeaYb5pYZZoQGz4E9sdPxIbzS3gywZE3ciQ=
|
||||
cloud.google.com/go/auth v0.22.0/go.mod h1:M9o2Oz+YI2jAfxewJgb1vyI3vceHF+eohmxyzmrl+9s=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
|
||||
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
|
||||
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
|
||||
cloud.google.com/go/iam v1.11.0 h1:KieQ9Pb+LLPak1O3Rv3GgCxhnmkYf7Xyh0P5HfF1jFM=
|
||||
cloud.google.com/go/iam v1.11.0/go.mod h1:KP+nKGugNJW4LcLx1uEZcq1ok5sQHFaQehQNl4QDgV4=
|
||||
cloud.google.com/go/logging v1.18.0 h1:KhzZq+1cSkPH9YUaKLLhLtQxIHitVayBmk0sGfoM9+k=
|
||||
cloud.google.com/go/logging v1.18.0/go.mod h1:ZGKnpBaURITh+g/uom2VhbiFoFWvejcrHPDhxFtU/gI=
|
||||
cloud.google.com/go/longrunning v1.0.0 h1:lwzWEYD8+NkYV7dhexOz6kmlvajZA70+bW/xMhRVVdY=
|
||||
cloud.google.com/go/longrunning v1.0.0/go.mod h1:8nqFBPOO1U/XkhWl0I19AMZEphrHi73VNABIpKYaTwM=
|
||||
cloud.google.com/go/monitoring v1.29.0 h1:AHhDsFaSax1/4k+qlIDX/SDGe6hggnfXJ9dkgD9qBPY=
|
||||
cloud.google.com/go/monitoring v1.29.0/go.mod h1:72NOVjJXHY/HBfoLT0+qlCZBT059+9VXLeAnL2PeeVM=
|
||||
cloud.google.com/go/storage v1.62.3 h1:SZq1t23NCI+e96dH77Dg3PEfsNNEjqO8zE5AnD8gVD0=
|
||||
cloud.google.com/go/storage v1.62.3/go.mod h1:cpYz/kRVZ+UQAF1uHeea10/9ewcRbxGoGNKsS9daSXA=
|
||||
cloud.google.com/go/iam v1.12.0 h1:Aki3bX9aHUDKPHfnRJfDcTdVedvy6quGBQcTqx3DRXk=
|
||||
cloud.google.com/go/iam v1.12.0/go.mod h1:FEZ4lXpADAC2AIpQY7LANNjjwyQ2jK439CI2VaD+sLY=
|
||||
cloud.google.com/go/logging v1.19.0 h1:NCqhdVUg3wQ8Cobdf16FDSuTGi3+6+hdSBHrY5TsR6Q=
|
||||
cloud.google.com/go/logging v1.19.0/go.mod h1:i40NZCHC9Gqvod4yE+yQfDWwlgwW/SrshkkGibCHxcA=
|
||||
cloud.google.com/go/longrunning v1.2.0 h1:WjYH3YHBGCxGJP9M4dWGHBfXr/cFIjMkNgWcJj7/iMM=
|
||||
cloud.google.com/go/longrunning v1.2.0/go.mod h1:5KMQALFGOCtFoi2xSOA1u3H7WKlhmckgiyFw7+LGQp0=
|
||||
cloud.google.com/go/monitoring v1.30.0 h1:r/d+JUbyKmJ8b07iznuKfzVzrIXTWxHQ3lBRm3x2LlY=
|
||||
cloud.google.com/go/monitoring v1.30.0/go.mod h1:htlUR0QWVMrjFzZmN4LGnMAve9xB/eduwjmINxVZ8RM=
|
||||
cloud.google.com/go/storage v1.64.0 h1:KLpxI/oX9LxeRsNqn877d2WyeT3ryiEwnGt8pwcSPZg=
|
||||
cloud.google.com/go/storage v1.64.0/go.mod h1:lWyAtwvDZHdL3k68WVKbESP6bmWaV23ZJJ/JEVw/ZaQ=
|
||||
cloud.google.com/go/trace v1.16.0 h1:GmQovzFc5F0CNfl0VLgL64aoTtu7xsM0YajW2GlG9+E=
|
||||
cloud.google.com/go/trace v1.16.0/go.mod h1:r+bdAn16dKLSV1G2D5v3e58IlQlizfxWrUfjx7kM7X0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.22.0 h1:aokoqcHvaGjiM3VpjKDfMMnF/8epJ+Q1HLJ7CudztqE=
|
||||
@@ -42,14 +42,14 @@ github.com/AzureAD/microsoft-authentication-library-for-go v1.7.2 h1:RHK7bS+HQMs
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.7.2/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk=
|
||||
github.com/Code-Hex/go-generics-cache v1.5.1 h1:6vhZGc5M7Y/YD8cIUcY8kcuQLB4cHR7U+0KMqAA0KcU=
|
||||
github.com/Code-Hex/go-generics-cache v1.5.1/go.mod h1:qxcC9kRVrct9rHeiYpFWSoW1vxyillCVzX13KZG8dl4=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.33.0 h1:l7+6kwRMJNwdCvYdDl7Eax+wzEYHSnNY7zrrfbhDdTA=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.33.0/go.mod h1:pJTkW8hEUIIi3Pf65lPZOnn4Y81yCllX6IWk2jNXdkM=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.57.0 h1:jLdiS1vO+XJFyDSWRHBx56r4s/NNtcl5J6KyCcWUX/w=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.57.0/go.mod h1:8lmpHY+1VRoteiOwyrQMDt1YGXOrFKCz+1wJW7n3ODY=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.57.0 h1:cSjUzZ7KU8hicTgzaSv9NmSyM9fTVK3y5lsBUl3wOis=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.57.0/go.mod h1:dzcEjy1WJ0Q4u9twNR3LcLhNoYMRCrMCMafpxa0TjPQ=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.57.0 h1:RoO5+d7uCmDqovLrHCr2/BuViUXvdcrNxyNM1pN9dDQ=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.57.0/go.mod h1:YqwkQPrWSC7+byyc1VlKbWLBF5JsW5IoL6xUkemYSXk=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.34.0 h1:yzIYdwuro811Z27D3T80Wkd3rqZzb0K43nner7Eh1yE=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.34.0/go.mod h1:pJTkW8hEUIIi3Pf65lPZOnn4Y81yCllX6IWk2jNXdkM=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.58.0 h1:ZYGajzJNcirVZpT1rltgf9iM+j9zZ4v8V9DrF+xKRJ8=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.58.0/go.mod h1:PDQyYBOzGtQgvshQI//UiXyzuMHCz0ndyu+4W8X82vM=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.58.0 h1:IBF8BbhKJkMsON/eY+LMu3aF3XMiotCb9KvkUmEkOJo=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.58.0/go.mod h1:dzcEjy1WJ0Q4u9twNR3LcLhNoYMRCrMCMafpxa0TjPQ=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.58.0 h1:SBZzZCiPmDrUV7NSCWY54OnKikO/oTydPCvyEyYaDDE=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.58.0/go.mod h1:YqwkQPrWSC7+byyc1VlKbWLBF5JsW5IoL6xUkemYSXk=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/VictoriaMetrics/VictoriaLogs v1.51.1-0.20260624061259-dc94972a8708 h1:D9/Jzlm3B8PBnrWxg4ft8KYZdG607dV3lpBfPCoiJD8=
|
||||
@@ -60,8 +60,6 @@ github.com/VictoriaMetrics/fastcache v1.13.3 h1:rBabE0iIxcqKEMCwUmwHZ9dgEqXerg8F
|
||||
github.com/VictoriaMetrics/fastcache v1.13.3/go.mod h1:hHXhl4DA2fTL2HTZDJFXWgW0LNjo6B+4aj2Wmng3TjU=
|
||||
github.com/VictoriaMetrics/metrics v1.44.0 h1:Fr8yqQSV+ZfYaDD/anqk1E8e9YPgfleSleJmAI0M0Tw=
|
||||
github.com/VictoriaMetrics/metrics v1.44.0/go.mod h1:xDM82ULLYCYdFRgQ2JBxi8Uf1+8En1So9YUwlGTOqTc=
|
||||
github.com/VictoriaMetrics/metricsql v0.87.2 h1:7OsrcDBWREWKqqpnFyIUEOM4FNv2qHvCoww2GYz3Tc0=
|
||||
github.com/VictoriaMetrics/metricsql v0.87.2/go.mod h1:d4EisFO6ONP/HIGDYTAtwrejJBBeKGQYiRl095bS4QQ=
|
||||
github.com/VictoriaMetrics/metricsql v0.87.3 h1:JU4JnVKSC5Vp3b4AvogXyOAjkz1iFF9n1KBMphS4WO8=
|
||||
github.com/VictoriaMetrics/metricsql v0.87.3/go.mod h1:d4EisFO6ONP/HIGDYTAtwrejJBBeKGQYiRl095bS4QQ=
|
||||
github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
|
||||
@@ -74,56 +72,56 @@ github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJ
|
||||
github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4=
|
||||
github.com/aws/aws-sdk-go v1.55.8 h1:JRmEUbU52aJQZ2AjX4q4Wu7t4uZjOu71uyNmaWlUkJQ=
|
||||
github.com/aws/aws-sdk-go v1.55.8/go.mod h1:ZkViS9AqA6otK+JBBNH2++sx1sgxrPKcSzPPvQkUtXk=
|
||||
github.com/aws/aws-sdk-go-v2 v1.42.0 h1:XvXMJTkFQtpBKIWZnmr9ZEOc2InWM2yldjXEJ/bymhA=
|
||||
github.com/aws/aws-sdk-go-v2 v1.42.0/go.mod h1:27+ACypSLljLAEKsCYOmrjKh83vuTRkuAe9Uv/3A4bg=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.13 h1:p1BBrg/Hhp6uK7zpejeI8QFXHJeC/mynzi04Sl03k9g=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.13/go.mod h1:8cIfkE9MDhkRZGpQ22aV6/lkYeYSozpz16Smrs5x4Ls=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.25 h1:ACCejvStYoilgwrfegSt5ZntCbPrk52qfwyNcnl3omM=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.25/go.mod h1:LJyU8sDRbXUxFn8xMJIGP+v9QYYwveNLI8a/giAOiAs=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.24 h1:2hQqYCV9yqyePQ9o6dCrZc/zO8U3TwPr9mIKlZnPu/I=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.24/go.mod h1:IDwpACtwqHLISdzfwUUNq4P9DsB/h5BLg4FwJPNfqFY=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.29 h1:r6qZHbT+wxgWO/e9vYNUEtg7lv5+UN3pRqKhLXvnArg=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.29/go.mod h1:QRnaRcTVGKPGRy8w78HMQtKUGRYcnMZAANATkeVA6Mo=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.27 h1:gb+HtIZdwcIoLxv/xwGumQr1DmGmGGCQnjKKVVSMYsU=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.27/go.mod h1:2b/8jZl/qwUMBZpSAcxX+IdM3zj6RUyfnB2IdLt9I+I=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.29 h1:f3vKqSo13fhTYb+JEcXwXefZQE26I1FB5eTSniU67ko=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.29/go.mod h1:MzoLFUArKGpGD+ukmPiTPG1X5x4o6M2kq4v2dr1FiEc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.29 h1:RdwIf/CuUsvJX3RgJagbOyotl/cxoLY4xviKuE7p2GY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.29/go.mod h1:71wt8W2EgswdZy9Mf9KNnzxZ3TiZlv4caKghPktDOkA=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.30 h1:VTGy885W5DKBxWRUJbym9hytNaYzsyaPkCHGRRMAOhU=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.30/go.mod h1:AS0HycUvJRFvTt613AYDOgO2jzw+00cVSMny8XB3yMY=
|
||||
github.com/aws/aws-sdk-go-v2/service/ec2 v1.304.0 h1:wZthLlYdKxBo7NpWLbl0A/8DB/QNDB+8RJa9WboK9Q0=
|
||||
github.com/aws/aws-sdk-go-v2/service/ec2 v1.304.0/go.mod h1:Y95W0Hm6FYLPa6o0hbnJ+sWgmdc4ifcLFjGkdobWVhY=
|
||||
github.com/aws/aws-sdk-go-v2/service/ecs v1.81.0 h1:2Sp9EwK7giQpJnQ54k0zdUh6aykmmbpEurEEygr104c=
|
||||
github.com/aws/aws-sdk-go-v2/service/ecs v1.81.0/go.mod h1:TIKZ9zIFS6W2k9FeW+r5sGVnlxp+aUt9oQ/St3Suj1o=
|
||||
github.com/aws/aws-sdk-go-v2/service/elasticache v1.52.2 h1:5wbCUfyxXcjIqesyVfJBBJs0bDMyejthtHyy48mfZCI=
|
||||
github.com/aws/aws-sdk-go-v2/service/elasticache v1.52.2/go.mod h1:o4vQxDt6oteknUjkXIEskp0ccy+93NRTPKXw3HlVMFE=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.12 h1:ZD2+BSw9vFsNlKYIasSNt3uDbjqqXIBcM13UJv/Lx2k=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.12/go.mod h1:Ms4zlcVBbXbiP7EVLhl+lgjvA/a7YphqQ3Ih3174EmI=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.22 h1:V51LGlOq/1VsDsHUdoklAQi7rMmx4qQubvFYAlP2254=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.22/go.mod h1:4Pzhyz8hJOm2bepgl+NjvRx8vlUFAIIvJnZ/MkcNPpU=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.29 h1:DRebniUGZ2MqiiIVmQJ04vIXr918hubdHMnarSLEWyU=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.29/go.mod h1:LfRkPCD8YHDM2E5eTkos2UpwYeZnBcVarTa8L59bJHA=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.29 h1:hiME6pBzC7OTl9LMtlyTWBuEl1f4QBcUmFDKC7MLXtc=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.29/go.mod h1:G7RP+uhagpKtKhd1BM9N6JQqjCcGEU47K5lBVZQyRQw=
|
||||
github.com/aws/aws-sdk-go-v2/service/kafka v1.52.0 h1:jalIJqKvZMvJRvs6ABLX+FhHz8E9pjU03Pyml4D9r3k=
|
||||
github.com/aws/aws-sdk-go-v2/service/kafka v1.52.0/go.mod h1:pW4pYNuVeScl13yqwsjLY0F/7g2YD8E0AvR6SOQsJZE=
|
||||
github.com/aws/aws-sdk-go-v2/service/lightsail v1.54.0 h1:07DKnL5eKSel3XEM2UxlD/z9zUZZ6XMHLGDXkAdY4u8=
|
||||
github.com/aws/aws-sdk-go-v2/service/lightsail v1.54.0/go.mod h1:Etcg8xorq1b0g0V2KMNgFjubYITZseJv08qtX/3szko=
|
||||
github.com/aws/aws-sdk-go-v2/service/rds v1.118.2 h1:pkEeQneYFpTAnGhyqSbyp/DlCPPJTGt0GkWahlLYzMA=
|
||||
github.com/aws/aws-sdk-go-v2/service/rds v1.118.2/go.mod h1:7gS+cGrKF0mH253QHFlStmx79ws+DlNk+04ZRfmw3U0=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.103.3 h1:JRseEu/vIDMaWis4bSw0QbXL+cvIGc1XnX076H5ZXLE=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.103.3/go.mod h1:77ZAgynvx1txMvDG8gGWoWkO1augYDxkp9JElWFgjQU=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.2.0 h1:3nXpRcFwRCW8n7HgO2QGy0Dc20eQNfBuUemGQhpF8m8=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.2.0/go.mod h1:LxYujSTLPRlp2vTtcUO/+1ilrew8ytt6SvQyOgejzFQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.31.3 h1:ey1XLTYXb9PcLt4535632o5kCGXNXEhNb620Dqwuylo=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.31.3/go.mod h1:Lk7PlmoTYryQmyBG0EXqj5BcUbj3whXdU2s3yGI3EAc=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.6 h1:yLr03zQE/5Eu5l3QU0Si+xMbLMbSDF2YXsigqXngs6g=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.6/go.mod h1:Q5N6icH+KJZDLh+ESNwzdv6cZ6vLFF/egy3IOxWhmz4=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.43.3 h1:VrIhKRCSK1umelSgB9RghvA9RTUYeQffyAS5ApXehNI=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.43.3/go.mod h1:r8wkDOuLaaMFqFiYAb8dGY2A3gJCOujMc6CFOVC4Zhc=
|
||||
github.com/aws/smithy-go v1.27.2 h1:y9NPmSE6am6LjEFPfqHqG/jJk7AauQvhCJONKh7kpzk=
|
||||
github.com/aws/smithy-go v1.27.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
|
||||
github.com/aws/aws-sdk-go-v2 v1.43.0 h1:fharf/WhbRAVZ1du0QL7roNFxZ6T/sWr+4Ni617bwSI=
|
||||
github.com/aws/aws-sdk-go-v2 v1.43.0/go.mod h1:5pKeft2eJj+gElQ38Jqg4ibCqh+/AK33/0X3hip7IjM=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.14 h1:3IZY0XAJquT3aHzbkHfPzy4ACPcEjVG0x87KOwtpqGY=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.14/go.mod h1:zwM6veDkhGgQFqkBy+uT28AAYpLu+uFMlPl+rCg/73E=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.31 h1:n4nY9O3QKoHIkL85EX+V8RcMFtOhlpTFhGArg915PXk=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.31/go.mod h1:PN0NYDCCoOpGGsZ2+elDUidmHfQBPyYzN2GCgl8HEBs=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.30 h1:TTCvvzFU6gXa4iJecNG/0F/B0oYTiazoRECr2XyLHrY=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.30/go.mod h1:jKxAp2AEncnliinzpgOSZDFv6+VjvWhjw/AtbfsWT9U=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.31 h1:kfVL5wAunCJycL6MOQ6aNh6PlAYEymflcjuKmrWUA0o=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.31/go.mod h1:nWfRNDAppujCQgOUd43lKT4yeLv9z3nJ3bw1G3BgQKo=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.35 h1:TwCjUC1rnFKTtfqEpQY9ClYPFpGpUaouODrdGPB5b3Y=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.35/go.mod h1:V0zqtP3iJk9zu86GxuQGN09RYyQB+3mjcPiyfLC6wlg=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.31 h1:Z8F3hfCY33IGpJjFAnv0wvtv1FIKj1GHmRDEYqy64tw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.31/go.mod h1:aVyUoytEyOViR6jhq6jula0xkc5NfBE2hgeF6BvOrao=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.31 h1:hyOxUyXdh3AyjE93gBgsfziJag9ACwcs+ZpDBLzi8mw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.31/go.mod h1:OERqI9k0draSLB8O8woxY3q25ZWTELRK4RRoLMuMZFo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.32 h1:0MrUL35H/Y4kdFfItoR5jCgtDQ4Z/8LudAoIHRfA4hE=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.32/go.mod h1:2tNZkuWz54arj8mHVf+8Y7cKkcD8Wr/fBpENgEXpjLc=
|
||||
github.com/aws/aws-sdk-go-v2/service/ec2 v1.307.0 h1:ZQMhFWDFhwJbq3xCggO0gh3AW+yu65QtcT9F5HfdZhY=
|
||||
github.com/aws/aws-sdk-go-v2/service/ec2 v1.307.0/go.mod h1:8mrDF7OtbuL0QpwP4YCvLuoOE4/5lL7D33MXgp069/Y=
|
||||
github.com/aws/aws-sdk-go-v2/service/ecs v1.83.0 h1:LQKIHuVHqdbU9LUt5c2G9f+CcQAzolxQmAch3RTORMc=
|
||||
github.com/aws/aws-sdk-go-v2/service/ecs v1.83.0/go.mod h1:0vahPCh3slyORHbSuAP8YDyJKLEUQAMX7+bzYGxEnVI=
|
||||
github.com/aws/aws-sdk-go-v2/service/elasticache v1.54.3 h1:KZDlMf8V5riU8xBCMJLWhfa+RP/MIagz2qJFwRg/b1g=
|
||||
github.com/aws/aws-sdk-go-v2/service/elasticache v1.54.3/go.mod h1:nsMdHtF/ned4F5GCAfoerJaa/Q6cx+G+WYNsb/TFN7Q=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.13 h1:mbRIur/BiHK6SKPjoBIXSE/hJ6g6JGRLuxQy1jGjlN4=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.13/go.mod h1:ITg9em2KbJx1s0y4aqRX5OYWG6HBZ5TVR//OdpEZ2CQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.24 h1:mdPwDQPqxlw9Sc62Nt15yjEcARaDbPXkjRYtXsUripo=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.24/go.mod h1:ls5ytnwLTcQaUu32fMYXFI3MjpKuTwL840PAm9iqyEg=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.31 h1:w2SIhW92DZPFrSL4ksVCr8IYff5OZwIcxg8+95tzvAI=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.31/go.mod h1:wAhpCQbkov+IcvjozJbd2xRCoZybUEHNkcFunssNACg=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.32 h1:jWXtZdCnhXa9sGFixRaU2AxT4DIVse9HS4E2f+/KwV0=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.32/go.mod h1:9JS1UpfVvyD/ZPX8GsKb/Pq8scEM+7GP5fqh9SwH7po=
|
||||
github.com/aws/aws-sdk-go-v2/service/kafka v1.52.6 h1:1Cn7pNj5Knye9dx2KFY0UmSdXM+DZdzQaeBx72QHgSQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/kafka v1.52.6/go.mod h1:5SCWP3gW59x0gRYHuwzXoj/ZuxEoa+j9/OeynrJd/sk=
|
||||
github.com/aws/aws-sdk-go-v2/service/lightsail v1.56.1 h1:bbOZEcMgnUQocfDoaaU2f148Te/MpUk6FkOGtJyfwlg=
|
||||
github.com/aws/aws-sdk-go-v2/service/lightsail v1.56.1/go.mod h1:428ttHou5n2J4/oQAQS9EmOU6LrBv48F2bGk+Ta7EF4=
|
||||
github.com/aws/aws-sdk-go-v2/service/rds v1.119.3 h1:SIGdk+wA+xGXgN+L7Jr3Ot83Mjh3jpjyJIwZd3DqAnU=
|
||||
github.com/aws/aws-sdk-go-v2/service/rds v1.119.3/go.mod h1:zCRPUdp05FEZG3OO7LmJq9xkSDjMEhkiVrZV0oJs2a0=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.106.0 h1:7QZWVJZWzHivHWIa+5TELLaBBkbuoj0GPwQtMlJ0sqk=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.106.0/go.mod h1:fcvq5L7dK+5cQFicEJwpI6e6Wn8NY2i6yT5wRLYVc7s=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.5.0 h1:OHH5iTQvVGmfHjX/5Q+vFuA/Rf2x6/95aJ/75QCQSm4=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.5.0/go.mod h1:mCF3AK9PpL49oOrhniUXWAfhVBVQ/XbytoE5eccZUIs=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.33.0 h1:CaJyYhxBE0M/HJX/YvSaSmQlsI91VHB0lKU8LtLxL3A=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.33.0/go.mod h1:+e6BMRMPjBQoCw/WovYR9GLy2IU0z4Q77smOB1DraSg=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.38.0 h1:tC323YV77QdafeBr6LUhLDTsboyuyHLNRwAyCP44kGU=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.38.0/go.mod h1:SfLK1sgviHmbI+MozR9iDwDjL4cdCVZtahsjoR+z7wg=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.45.0 h1:Pd6PNlp4t8PTXxqzstICl52Wsy78vpjFZ7PRUj44mJc=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.45.0/go.mod h1:rmQ0TnHzuLPmabgjPcsywhsSOmaBDgzR4zvDxSPsGdg=
|
||||
github.com/aws/smithy-go v1.27.4 h1:JQcphmBN4f0q/sPqXqROIItRNV/hy10cgu7CsFy616M=
|
||||
github.com/aws/smithy-go v1.27.4/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
|
||||
github.com/bboreham/go-loser v0.0.0-20230920113527-fcc2c21820a3 h1:6df1vn4bBlDDo4tARvBm7l6KA9iVMnE3NWizDeWSrps=
|
||||
github.com/bboreham/go-loser v0.0.0-20230920113527-fcc2c21820a3/go.mod h1:CIWtjkly68+yqLPbvwwR/fjNJA/idrtULjZWh2v1ys0=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
@@ -134,8 +132,8 @@ github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1x
|
||||
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cheggaaa/pb/v3 v3.1.7 h1:2FsIW307kt7A/rz/ZI2lvPO+v3wKazzE4K/0LtTWsOI=
|
||||
github.com/cheggaaa/pb/v3 v3.1.7/go.mod h1:/Ji89zfVPeC/u5j8ukD0MBPHt2bzTYp74lQ7KlgFWTQ=
|
||||
github.com/cheggaaa/pb/v3 v3.2.0 h1:ziC7JV5/Ge2iZNa9ckxdXBxHHyPgC+p/QGzhWRoPlHU=
|
||||
github.com/cheggaaa/pb/v3 v3.2.0/go.mod h1:KtXGzgipYGqY3avGtFmlQTgiT88AEFvc1LvPk7oA9fM=
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
|
||||
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik=
|
||||
@@ -152,8 +150,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dennwc/varint v1.0.0 h1:kGNFFSSw8ToIy3obO/kKr8U9GZYUAxQEVuix4zfDWzE=
|
||||
github.com/dennwc/varint v1.0.0/go.mod h1:hnItb35rvZvJrbTALZtY/iQfDs48JKRG1RPpgziApxA=
|
||||
github.com/digitalocean/godo v1.193.0 h1:CSbbUl5LufT75KPNvex3vDnBYjY2RfJWs7T3Ac7dHpA=
|
||||
github.com/digitalocean/godo v1.193.0/go.mod h1:xQsWpVCCbkDrWisHA72hPzPlnC+4W5w/McZY5ij9uvU=
|
||||
github.com/digitalocean/godo v1.196.0 h1:32bkla5iESoGaCHmXD2+fUXAepR23wWwbzPjwenIhik=
|
||||
github.com/digitalocean/godo v1.196.0/go.mod h1:xQsWpVCCbkDrWisHA72hPzPlnC+4W5w/McZY5ij9uvU=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf0mN39c=
|
||||
@@ -185,38 +183,38 @@ github.com/fxamacker/cbor/v2 v2.9.2/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj2
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8=
|
||||
github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-openapi/jsonpointer v0.23.1 h1:1HBACs7XIwR2RcmItfdSFlALhGbe6S92p0ry4d1GWg4=
|
||||
github.com/go-openapi/jsonpointer v0.23.1/go.mod h1:iWRmZTrGn7XwYhtPt/fvdSFj1OfNBngqRT2UG3BxSqY=
|
||||
github.com/go-openapi/jsonreference v0.21.5 h1:6uCGVXU/aNF13AQNggxfysJ+5ZcU4nEAe+pJyVWRdiE=
|
||||
github.com/go-openapi/jsonreference v0.21.5/go.mod h1:u25Bw85sX4E2jzFodh1FOKMTZLcfifd1Q+iKKOUxExw=
|
||||
github.com/go-openapi/swag v0.25.5 h1:pNkwbUEeGwMtcgxDr+2GBPAk4kT+kJ+AaB+TMKAg+TU=
|
||||
github.com/go-openapi/swag v0.25.5/go.mod h1:B3RT6l8q7X803JRxa2e59tHOiZlX1t8viplOcs9CwTA=
|
||||
github.com/go-openapi/swag/cmdutils v0.25.5 h1:yh5hHrpgsw4NwM9KAEtaDTXILYzdXh/I8Whhx9hKj7c=
|
||||
github.com/go-openapi/swag/cmdutils v0.25.5/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0=
|
||||
github.com/go-openapi/swag/conv v0.25.5 h1:wAXBYEXJjoKwE5+vc9YHhpQOFj2JYBMF2DUi+tGu97g=
|
||||
github.com/go-openapi/swag/conv v0.25.5/go.mod h1:CuJ1eWvh1c4ORKx7unQnFGyvBbNlRKbnRyAvDvzWA4k=
|
||||
github.com/go-openapi/swag/fileutils v0.25.5 h1:B6JTdOcs2c0dBIs9HnkyTW+5gC+8NIhVBUwERkFhMWk=
|
||||
github.com/go-openapi/swag/fileutils v0.25.5/go.mod h1:V3cT9UdMQIaH4WiTrUc9EPtVA4txS0TOmRURmhGF4kc=
|
||||
github.com/go-openapi/swag/jsonname v0.26.0 h1:gV1NFX9M8avo0YSpmWogqfQISigCmpaiNci8cGECU5w=
|
||||
github.com/go-openapi/swag/jsonname v0.26.0/go.mod h1:urBBR8bZNoDYGr653ynhIx+gTeIz0ARZxHkAPktJK2M=
|
||||
github.com/go-openapi/swag/jsonutils v0.25.5 h1:XUZF8awQr75MXeC+/iaw5usY/iM7nXPDwdG3Jbl9vYo=
|
||||
github.com/go-openapi/swag/jsonutils v0.25.5/go.mod h1:48FXUaz8YsDAA9s5AnaUvAmry1UcLcNVWUjY42XkrN4=
|
||||
github.com/go-openapi/swag/loading v0.25.5 h1:odQ/umlIZ1ZVRteI6ckSrvP6e2w9UTF5qgNdemJHjuU=
|
||||
github.com/go-openapi/swag/loading v0.25.5/go.mod h1:I8A8RaaQ4DApxhPSWLNYWh9NvmX2YKMoB9nwvv6oW6g=
|
||||
github.com/go-openapi/swag/mangling v0.25.5 h1:hyrnvbQRS7vKePQPHHDso+k6CGn5ZBs5232UqWZmJZw=
|
||||
github.com/go-openapi/swag/mangling v0.25.5/go.mod h1:6hadXM/o312N/h98RwByLg088U61TPGiltQn71Iw0NY=
|
||||
github.com/go-openapi/swag/netutils v0.25.5 h1:LZq2Xc2QI8+7838elRAaPCeqJnHODfSyOa7ZGfxDKlU=
|
||||
github.com/go-openapi/swag/netutils v0.25.5/go.mod h1:lHbtmj4m57APG/8H7ZcMMSWzNqIQcu0RFiXrPUara14=
|
||||
github.com/go-openapi/swag/stringutils v0.25.5 h1:NVkoDOA8YBgtAR/zvCx5rhJKtZF3IzXcDdwOsYzrB6M=
|
||||
github.com/go-openapi/swag/stringutils v0.25.5/go.mod h1:PKK8EZdu4QJq8iezt17HM8RXnLAzY7gW0O1KKarrZII=
|
||||
github.com/go-openapi/swag/typeutils v0.25.5 h1:EFJ+PCga2HfHGdo8s8VJXEVbeXRCYwzzr9u4rJk7L7E=
|
||||
github.com/go-openapi/swag/typeutils v0.25.5/go.mod h1:itmFmScAYE1bSD8C4rS0W+0InZUBrB2xSPbWt6DLGuc=
|
||||
github.com/go-openapi/swag/yamlutils v0.25.5 h1:kASCIS+oIeoc55j28T4o8KwlV2S4ZLPT6G0iq2SSbVQ=
|
||||
github.com/go-openapi/swag/yamlutils v0.25.5/go.mod h1:Gek1/SjjfbYvM+Iq4QGwa/2lEXde9n2j4a3wI3pNuOQ=
|
||||
github.com/go-openapi/jsonpointer v1.0.0 h1:kR9tHqY0CtZaOPVFm622dPVNhrvYpwr4uCxgL3h1H8s=
|
||||
github.com/go-openapi/jsonpointer v1.0.0/go.mod h1:Z3rw7dWu1p9IgitXCFamSlA5lmDiklEB6vkaxcNZW5Y=
|
||||
github.com/go-openapi/jsonreference v1.0.0 h1:jlmTr6torcd1YgDQvSfNmRtKzYDO4FGBkrAdlAVWnpY=
|
||||
github.com/go-openapi/jsonreference v1.0.0/go.mod h1:jtwdyGbJk0Xhe5Y+rwtglQP6Sb1WZST4rT32LWB+sv0=
|
||||
github.com/go-openapi/swag v0.27.1 h1:VotvOLWW8q/EAxB0YdsBBGC8XYyeL1YwBj2ungAGPNg=
|
||||
github.com/go-openapi/swag v0.27.1/go.mod h1:GTkJPwHfhJp6MWr4/rCh64HVI3Ofu+tcsbfjfHmTxpE=
|
||||
github.com/go-openapi/swag/cmdutils v0.27.1 h1:I7sYqaWVl5mq0NEmNQkAmFDyNin9ufvMX/p2zwtQaOE=
|
||||
github.com/go-openapi/swag/cmdutils v0.27.1/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM=
|
||||
github.com/go-openapi/swag/conv v0.27.1 h1:8wi9ZG+olmY1wXphl93EWniPtbSPkXM/feH7FgjsvrU=
|
||||
github.com/go-openapi/swag/conv v0.27.1/go.mod h1:QbqMivkpKhC3g1B1GGGOJ6ANewI3S62dbzYu3Duowqs=
|
||||
github.com/go-openapi/swag/fileutils v0.27.1 h1:QQqBSoi5mW4XpU85nS0mLcA+zAE6vLzrb0QkmLKf9oM=
|
||||
github.com/go-openapi/swag/fileutils v0.27.1/go.mod h1:VvJFZLTZS0AI854gEQz5tk7dBESdLjiNUMSZ/th2ry8=
|
||||
github.com/go-openapi/swag/jsonutils v0.27.1 h1:SVgK3i4USzCU5mibOOS/l4ea2h9UQXy7J7RNLTjuXjU=
|
||||
github.com/go-openapi/swag/jsonutils v0.27.1/go.mod h1:tdlEpZqdcQ17uj6J4YdK9vd8It5qWMwjWXOs0tjpRlk=
|
||||
github.com/go-openapi/swag/loading v0.27.1 h1:/DxUgDXKbBX4bcn7r9uEXfJyzN5XpiJmZplzQTjrRCY=
|
||||
github.com/go-openapi/swag/loading v0.27.1/go.mod h1:jvGh3iA2+zyUUycB5fgJWzeHnhrpvGnJJM0RVE9ZShE=
|
||||
github.com/go-openapi/swag/mangling v0.27.1 h1:yC9D0HyUE8gbP+BfmGx9+AA89ikwZTMjESK3OnnoaqA=
|
||||
github.com/go-openapi/swag/mangling v0.27.1/go.mod h1:jtBE2+V+3pILxOR7Vgce+Cwp6A2PgZbvVqfNntbVs0w=
|
||||
github.com/go-openapi/swag/netutils v0.27.1 h1:mICMFoS82F5TZ4Zy3cqmcQk+BFeCp3Uyq3Np7GI0/qU=
|
||||
github.com/go-openapi/swag/netutils v0.27.1/go.mod h1:J+WYyFMLtvtCGqa6jLv+YNUmIKI3ZRQRrvfNDMoQoEQ=
|
||||
github.com/go-openapi/swag/pools v0.27.1 h1:9LeadcMyb2GJCbXX5hVQDbZ2Lq9TL4dCs/nx1j5DO0E=
|
||||
github.com/go-openapi/swag/pools v0.27.1/go.mod h1:kVQefhSK5RWuRe7BXsL8htgBPAMpN7HDGpGEknqugeE=
|
||||
github.com/go-openapi/swag/stringutils v0.27.1 h1:ZXePZ0r2p1qSjo8tD3Un4vFj8+FqlCkczxDrJIhYUp8=
|
||||
github.com/go-openapi/swag/stringutils v0.27.1/go.mod h1:lzRN95CxXmA03XcDWHLOb6nOMcxCqR5rGY0lOgsfRoM=
|
||||
github.com/go-openapi/swag/typeutils v0.27.1 h1:KSTdFlfnse4r6dP9IrEnwMldjE+zs71UeEB3//PtVXc=
|
||||
github.com/go-openapi/swag/typeutils v0.27.1/go.mod h1:Srm0xFNRZ1Y+vCxJclo5qzx8aj+1pAKda/YfFPrG0dQ=
|
||||
github.com/go-openapi/swag/yamlutils v0.27.1 h1:ftxv6xvXb1E3zohUc+okZ9nSqNb9StQX/FXnKZ98sQA=
|
||||
github.com/go-openapi/swag/yamlutils v0.27.1/go.mod h1:bnxFIB1qewGRiZHypXGZ3fNgf13/0HfRgnS/iZBDrOo=
|
||||
github.com/go-resty/resty/v2 v2.17.2 h1:FQW5oHYcIlkCNrMD2lloGScxcHJ0gkjshV3qcQAyHQk=
|
||||
github.com/go-resty/resty/v2 v2.17.2/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
|
||||
@@ -246,10 +244,10 @@ github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
|
||||
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.16 h1:F/VPrx0YPBdksZJQdCAp0WUsqnNmZpUZszzfYt0M5Dw=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.16/go.mod h1:9Yb0eAkH/Xqhvv3zbeKf/+wMJqCeocWc6KIhDvEAuYE=
|
||||
github.com/googleapis/gax-go/v2 v2.22.0 h1:PjIWBpgGIVKGoCXuiCoP64altEJCj3/Ei+kSU5vlZD4=
|
||||
github.com/googleapis/gax-go/v2 v2.22.0/go.mod h1:irWBbALSr0Sk3qlqb9SyJ1h68WjgeFuiOzI4Rqw5+aY=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.18 h1:hvVi34VucdrV1IIsiWuqYM8kutw/92MxNEFxCJZEh0k=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.18/go.mod h1:rSEsBUemEBZEexP2y6jPp16LUmUbjmSbcPMQizR0o4k=
|
||||
github.com/googleapis/gax-go/v2 v2.23.0 h1:Tchl7qkvE7Ip3y+ztvNufYFvkfqTe7NfLTYGIdJRLuE=
|
||||
github.com/googleapis/gax-go/v2 v2.23.0/go.mod h1:rBQKOVJCdb8IFEzg+FCwlt1LP/xMDGuqUXhUG+XMXEg=
|
||||
github.com/gophercloud/gophercloud/v2 v2.12.0 h1:Gxmc/Bog1UDKkxTcQW7MSPTDviJXpLeEgVeN5KrxoCo=
|
||||
github.com/gophercloud/gophercloud/v2 v2.12.0/go.mod h1:H7TTOxbLy8RIaHSNhI2GCrWIzw4Xpw8Xn2mBhCUT5kA=
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
|
||||
@@ -278,16 +276,16 @@ github.com/hashicorp/go-version v1.9.0 h1:CeOIz6k+LoN3qX9Z0tyQrPtiB1DFYRPfCIBtaX
|
||||
github.com/hashicorp/go-version v1.9.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/golang-lru v0.6.0 h1:uL2shRDx7RTrOrTCUZEGP/wJUFiUI8QT6E7z5o8jga4=
|
||||
github.com/hashicorp/golang-lru v0.6.0/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/hashicorp/nomad/api v0.0.0-20260528135333-5b027732945f h1:sdf4a6FF3tC1/c0buuizLAwZa/xLu4gWD87qWrzQLvo=
|
||||
github.com/hashicorp/nomad/api v0.0.0-20260528135333-5b027732945f/go.mod h1:Kr8imJwigbQ/50BqVae2+JL+AyX+FnzbnuCoIFb6iYg=
|
||||
github.com/hashicorp/nomad/api v0.0.0-20260616181215-ea1ca2d932bf h1:pU9wD+K2z1mY8ypEmMlfnuxPURG6Vf/OCZsyuWP/3AE=
|
||||
github.com/hashicorp/nomad/api v0.0.0-20260616181215-ea1ca2d932bf/go.mod h1:Kr8imJwigbQ/50BqVae2+JL+AyX+FnzbnuCoIFb6iYg=
|
||||
github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY=
|
||||
github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4=
|
||||
github.com/hetznercloud/hcloud-go/v2 v2.41.2 h1:fO5zsMgp5oejrtnFj8mYuqlp+iMuirpaKv4b5FYNRdQ=
|
||||
github.com/hetznercloud/hcloud-go/v2 v2.41.2/go.mod h1:9OGvC//jbHE4sv2Oyo0bQ2vEWuUMKYoNMyj9Qxz2qcc=
|
||||
github.com/hetznercloud/hcloud-go/v2 v2.43.0 h1:soqEUxJJqbf8UICQmDXfUwY/khfROAk0fi1s0bnBtd8=
|
||||
github.com/hetznercloud/hcloud-go/v2 v2.43.0/go.mod h1:d0s2WLe7jSoStamv3eHoWgBSOxc/K17tYSXsqUkbse0=
|
||||
github.com/influxdata/influxdb v1.12.4 h1:vn/1rvFYkYpg9efRw79+PUPPnMX7HwyJV+hDIB9IrOQ=
|
||||
github.com/influxdata/influxdb v1.12.4/go.mod h1:czsGl4TCm2kWtzEHsGh74Nye77o/KgmKsLtF4/L9QVc=
|
||||
github.com/ionos-cloud/sdk-go/v6 v6.3.7 h1:t773JkC/asnyVqeQ+OvN9WCRZuosSoPtJfyM82EFCWY=
|
||||
github.com/ionos-cloud/sdk-go/v6 v6.3.7/go.mod h1:nUGHP4kZHAZngCVr4v6C8nuargFrtvt7GrzH/hqn7c4=
|
||||
github.com/ionos-cloud/sdk-go/v6 v6.3.8 h1:CUZzrNciLM2IlmZtnclIznjST29tAYQbtQ8epiX5RUo=
|
||||
github.com/ionos-cloud/sdk-go/v6 v6.3.8/go.mod h1:nUGHP4kZHAZngCVr4v6C8nuargFrtvt7GrzH/hqn7c4=
|
||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
|
||||
@@ -298,8 +296,8 @@ github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRt
|
||||
github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao=
|
||||
github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/compress v1.19.1 h1:VsB4HPswih7mmZ8WleSFQ75c/Ui1M4trX5oAsJnhSlk=
|
||||
github.com/klauspost/compress v1.19.1/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
|
||||
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
|
||||
github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=
|
||||
@@ -318,8 +316,8 @@ github.com/linode/linodego v1.69.1 h1:f45N2MHR/oece2/ktTTCYmrlfse4//k3NgwcF5zbGZ
|
||||
github.com/linode/linodego v1.69.1/go.mod h1:Fha0NYsQSx5VZK1HQNJY/z/dIxxkFp+vb5veawbmAUw=
|
||||
github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY=
|
||||
github.com/mattn/go-colorable v0.1.15/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
|
||||
github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
|
||||
github.com/mattn/go-isatty v0.0.23 h1:cYwCQTQf3HB6xUC+BtyCLZNr7IzbOmoZbmssVNzSyiQ=
|
||||
github.com/mattn/go-isatty v0.0.23/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A=
|
||||
github.com/mattn/go-runewidth v0.0.24 h1:cpokDiIn0MGnhdHwuWnJBITySJ20QyNGnY2kR/ay2DU=
|
||||
github.com/mattn/go-runewidth v0.0.24/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
|
||||
@@ -350,12 +348,12 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s=
|
||||
github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
|
||||
github.com/open-telemetry/opentelemetry-collector-contrib/internal/exp/metrics v0.154.0 h1:WS8HkUa6p8iVJ2v0mmGEK1a9R2b+Uro6tSG+4IfX6rk=
|
||||
github.com/open-telemetry/opentelemetry-collector-contrib/internal/exp/metrics v0.154.0/go.mod h1:9QPTx+XgZE7ktvh5jT5TvSisIkh2Fwc7mrfuf6+j2/U=
|
||||
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil v0.154.0 h1:Kda+8F8o5QATBLP5K2MKmI2t7ddr7sBaV0EhZpjlvB0=
|
||||
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil v0.154.0/go.mod h1:iVnoGSVXYhnyuQ6TQNhBIHqtu7h0LTXbSyWy584eBjg=
|
||||
github.com/open-telemetry/opentelemetry-collector-contrib/processor/deltatocumulativeprocessor v0.154.0 h1:U/MRkEeVwZ3zl8hOlUBP/Q/RMgLfMbTHQoATlLXhI4I=
|
||||
github.com/open-telemetry/opentelemetry-collector-contrib/processor/deltatocumulativeprocessor v0.154.0/go.mod h1:dFTV2c6rjph2ZMtkq9xHN5QuYbUSQ+o/25UQfIY3QUQ=
|
||||
github.com/open-telemetry/opentelemetry-collector-contrib/internal/exp/metrics v0.157.0 h1:WzyLbYQ5GswNa/3Jma/bYYbn2ziLoQct2VEoN5u1j74=
|
||||
github.com/open-telemetry/opentelemetry-collector-contrib/internal/exp/metrics v0.157.0/go.mod h1:TmipmukIEZQwvWXuFMaSnDBlaNzgsyiHcv54luc76pk=
|
||||
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil v0.157.0 h1:oQWc2BKFkPqo14Zh+qDEWZItxJacKSubLjtFirojFnk=
|
||||
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil v0.157.0/go.mod h1:fMeXcxEg6tHlCPRjf2jQ6KavnvDrwoGvJ8q8ihDk3BE=
|
||||
github.com/open-telemetry/opentelemetry-collector-contrib/processor/deltatocumulativeprocessor v0.157.0 h1:y/Uf4K+H3WOWIFEbqymmBcw6s5QQe/hxTy23UERke7w=
|
||||
github.com/open-telemetry/opentelemetry-collector-contrib/processor/deltatocumulativeprocessor v0.157.0/go.mod h1:2J1/XjEfj6pQkRGOjE81TjDFUxH+v0JnMtELt0VhOtA=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||
@@ -372,20 +370,20 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
||||
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
||||
github.com/prometheus/client_golang/exp v0.0.0-20260602051030-3537b20ac86b h1:633sracZPrB7O7T6r5skFtwqXDOrXlQkE9Wr5DnYVJE=
|
||||
github.com/prometheus/client_golang/exp v0.0.0-20260602051030-3537b20ac86b/go.mod h1:7hAEIbflIgnK0HubVroVy6UgJYYKryF6p3mP/dcyay8=
|
||||
github.com/prometheus/client_golang v1.24.0 h1:5XStIklKuAtJSNpdD3s8XJj/Yv78IQmE1kbNk87JrAI=
|
||||
github.com/prometheus/client_golang v1.24.0/go.mod h1:QcsNdotprC2nS4BTM2ucbcqxd2CeXTEa9jW7zHO9iDE=
|
||||
github.com/prometheus/client_golang/exp v0.0.0-20260723095000-436bf2e0fb60 h1:n7SAxoEJEzwf8mrZLQzxCAs3agUbBU3g7hEffGZP7v0=
|
||||
github.com/prometheus/client_golang/exp v0.0.0-20260723095000-436bf2e0fb60/go.mod h1:CoLfLGxCH1vzpdmZ+p2uaUGH43j+99HYmnK1Wak6rS4=
|
||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.68.1 h1:omjRRl4QP4komogpXuhfeOiisQg7xdy8VM1UY+pStaY=
|
||||
github.com/prometheus/common v0.68.1/go.mod h1:ZzL3f6u94qUxh9p+tJTrF+FvBS1XXbbRAZCQkytAL0Y=
|
||||
github.com/prometheus/common v0.70.1 h1:1HvjP4D5oL3t8RsPlwxA9onvvStjtIHYE5XuuwOi/PY=
|
||||
github.com/prometheus/common v0.70.1/go.mod h1:VdFUQDMZK3VLkurFUVhia6uys/0suUp86TJz5qbJRhc=
|
||||
github.com/prometheus/otlptranslator v1.0.0 h1:s0LJW/iN9dkIH+EnhiD3BlkkP5QVIUVEoIwkU+A6qos=
|
||||
github.com/prometheus/otlptranslator v1.0.0/go.mod h1:vRYWnXvI6aWGpsdY/mOT/cbeVRBlPWtBNDb7kGR3uKM=
|
||||
github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=
|
||||
github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=
|
||||
github.com/prometheus/prometheus v0.312.0 h1:f9jdv2fQhQ1fks9a9YwlGZrKr4hih0rRP/rh0mu3Q18=
|
||||
github.com/prometheus/prometheus v0.312.0/go.mod h1:8oAYd2XPgHXLP4fFKam594R/ZLlPicrrBkVdaWt74Sw=
|
||||
github.com/prometheus/procfs v0.21.1 h1:GljZCt+zSTS+NZq88cyQ1LjZ+RCHp3uVuabBWA5+OJI=
|
||||
github.com/prometheus/procfs v0.21.1/go.mod h1:aB55Cww9pdSJVHk0hUf0inxWyyjPogFIjmHKYgMKmtY=
|
||||
github.com/prometheus/prometheus v0.313.1 h1:BOyoPuxCL+58NpuZ0ovKuKo8ALmVLTCTIM7r8znt6z8=
|
||||
github.com/prometheus/prometheus v0.313.1/go.mod h1:Kq9A+EPun2WyVusbQxO7Tx1RxKqLKFclfiBGJA1mFkk=
|
||||
github.com/prometheus/sigv4 v0.4.1 h1:EIc3j+8NBea9u1iV6O5ZAN8uvPq2xOIUPcqCTivHuXs=
|
||||
github.com/prometheus/sigv4 v0.4.1/go.mod h1:eu+ZbRvsc5TPiHwqh77OWuCnWK73IdkETYY46P4dXOU=
|
||||
github.com/puzpuzpuz/xsync/v4 v4.5.0 h1:vOSWu6b57/emh+L/Cw0BeQfvxa/cogFywXHeGUxQxAg=
|
||||
@@ -398,8 +396,8 @@ github.com/scaleway/scaleway-sdk-go v1.0.0-beta.36 h1:ObX9hZmK+VmijreZO/8x9pQ8/P
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.36/go.mod h1:LEsDu4BubxK7/cWhtlQWfuxwL4rf/2UEpxXz1o1EMtM=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spiffe/go-spiffe/v2 v2.7.0 h1:uXe1MflJoHw58wAUvxVlcM7WpKtijWG7I1UidcGh6g4=
|
||||
github.com/spiffe/go-spiffe/v2 v2.7.0/go.mod h1:47Q0Q9/AqGha8QLHp+kxpH4Wca7X7EnOtlIJy3mxZ3U=
|
||||
github.com/spiffe/go-spiffe/v2 v2.8.1 h1:eXZMLsu+3MLEPJyGJkolqtVrteZfQdUpOWj6LTiDl/E=
|
||||
github.com/spiffe/go-spiffe/v2 v2.8.1/go.mod h1:47Q0Q9/AqGha8QLHp+kxpH4Wca7X7EnOtlIJy3mxZ3U=
|
||||
github.com/stackitcloud/stackit-sdk-go/core v0.26.0 h1:jQEb9gkehfp6VCP6TcYk7BI10cz4l0KM2L6hqYBH2QA=
|
||||
github.com/stackitcloud/stackit-sdk-go/core v0.26.0/go.mod h1:WU1hhxnjXw2EV7CYa1nlEvNpMiRY6CvmIOaHuL3pOaA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
@@ -424,8 +422,8 @@ github.com/valyala/fastrand v1.1.0 h1:f+5HkLW4rsgzdNoleUOB69hyT9IlD2ZQh9GyDMfb5G
|
||||
github.com/valyala/fastrand v1.1.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ=
|
||||
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
||||
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
github.com/valyala/gozstd v1.24.0 h1:M/9L3h7bVwbj2gZwrmuoaxzwVrmBUvos2jG9cZtuhlc=
|
||||
github.com/valyala/gozstd v1.24.0/go.mod h1:y5Ew47GLlP37EkTB+B4s7r6A5rdaeB7ftbl9zoYiIPQ=
|
||||
github.com/valyala/gozstd v1.25.0 h1:7gS7+5zwidZT1BFQqGPAPII8ekZ3tvYTp5IvOEWC34Y=
|
||||
github.com/valyala/gozstd v1.25.0/go.mod h1:y5Ew47GLlP37EkTB+B4s7r6A5rdaeB7ftbl9zoYiIPQ=
|
||||
github.com/valyala/histogram v1.2.0 h1:wyYGAZZt3CpwUiIb9AU/Zbllg1llXyrtApRS815OLoQ=
|
||||
github.com/valyala/histogram v1.2.0/go.mod h1:Hb4kBwb4UxsaNbbbh+RRz8ZR6pdodR57tzWUS3BUzXY=
|
||||
github.com/valyala/quicktemplate v1.8.0 h1:zU0tjbIqTRgKQzFY1L42zq0qR3eh4WoQQdIdqCysW5k=
|
||||
@@ -440,42 +438,42 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/collector/component v1.60.0 h1:LpIjHMn7OOjUsFR84ROc2kqPbP1xnKyDCGi7ZVqEaKU=
|
||||
go.opentelemetry.io/collector/component v1.60.0/go.mod h1:Rag+NNgiGIkcGYlcTfJtMh2l0T5XS1KNv9Wjw9yofAk=
|
||||
go.opentelemetry.io/collector/component/componentstatus v0.154.0 h1:4ifSCy2Y332iZ5AldHt9ujVjY6XKxhVe/hND4TSDarg=
|
||||
go.opentelemetry.io/collector/component/componentstatus v0.154.0/go.mod h1:ZsBIax7tvvODn0XqTyhTfKZjm96zVKnLUKvlN8SHFjo=
|
||||
go.opentelemetry.io/collector/component/componenttest v0.154.0 h1:uH06tUatG4S45A/f3sFENMMAMzWURmgxKK3MAbVZAUI=
|
||||
go.opentelemetry.io/collector/component/componenttest v0.154.0/go.mod h1:SQ1JRosjFAZ7kN2yNHNcNakOliqrP0QxglKcYyUrUpQ=
|
||||
go.opentelemetry.io/collector/confmap v1.60.0 h1:TEBi/N3kac/JI4VTEq9LjqRCFdF2JS2MHOCEiHq8GSM=
|
||||
go.opentelemetry.io/collector/confmap v1.60.0/go.mod h1:Z693ETewV4n8JsOO2jp/iLe1PGGpFCIzuNsF1xLeiSY=
|
||||
go.opentelemetry.io/collector/confmap/xconfmap v0.154.0 h1:tarvY9S02jkYNYW/4+yD02RRatwJAojMD430Bs4JD/4=
|
||||
go.opentelemetry.io/collector/confmap/xconfmap v0.154.0/go.mod h1:zcVRrY1gS8qVwBrTrhzVI67tMAUu5BONTsIXzjXu1Ho=
|
||||
go.opentelemetry.io/collector/consumer v1.60.0 h1:SWP/0HvDnWiiy/4S366CiatAZ4gFl410UmggrZEcWVg=
|
||||
go.opentelemetry.io/collector/consumer v1.60.0/go.mod h1:nkp1NBtKQzme7WFF7fkgRgDlQLs49VIMOn8rO0jfmYU=
|
||||
go.opentelemetry.io/collector/consumer/consumertest v0.154.0 h1:G9gFP86ZsglC3mTLA6cqOrW5lvdcEBJrVgHtThE+Sc4=
|
||||
go.opentelemetry.io/collector/consumer/consumertest v0.154.0/go.mod h1:FRLGgy8gFYjm3A+yby1bctz5ZIAn6EUOpuV49KnKbFY=
|
||||
go.opentelemetry.io/collector/consumer/xconsumer v0.154.0 h1:I3rB+S5ORE1XLzqopFXvP6UmYrsj5n1tFlcEAPg96Zw=
|
||||
go.opentelemetry.io/collector/consumer/xconsumer v0.154.0/go.mod h1:WNT9BoyLE/nE5N6WEL4c1GXcfGcRUmSTCSr6e/tyfO4=
|
||||
go.opentelemetry.io/collector/featuregate v1.60.0 h1:/HxHB8hq4N5Fhq5N0C8G6xbXTHxnGcWIryyJzmP7pdc=
|
||||
go.opentelemetry.io/collector/featuregate v1.60.0/go.mod h1:4ga1QBMPEejXXmpyJS8lmaRpknJ3Lb9Bvk6e420bUFU=
|
||||
go.opentelemetry.io/collector/internal/componentalias v0.154.0 h1:g0y8F/qez9cbsgF5+/uU6YC6l5oXVkccIhsXVHmF3xQ=
|
||||
go.opentelemetry.io/collector/internal/componentalias v0.154.0/go.mod h1:F2tudJ/Zcm8w8b768sU65nZc4q2rgY1MhfX5FxDeUgA=
|
||||
go.opentelemetry.io/collector/internal/testutil v0.154.0 h1:iUYHOM8+wONW01A4jFnzauanOYGVBGchKWWtm51is6c=
|
||||
go.opentelemetry.io/collector/internal/testutil v0.154.0/go.mod h1:Jkjs6rkqs973LqgZ0Fe3zrokQRKULYXPIf4HuqStiEE=
|
||||
go.opentelemetry.io/collector/pdata v1.60.0 h1:YcGMHzeJucHen41AoR4mxHro8reUr9SVqt7P0KacKzQ=
|
||||
go.opentelemetry.io/collector/pdata v1.60.0/go.mod h1:Ca8VgZX2wOr6wW4nihPWaCpkJVvzeo6Txa7BJ7/WO90=
|
||||
go.opentelemetry.io/collector/pdata/pprofile v0.154.0 h1:dWrHnKBzzMhkZXfKmSuFpGVAApSUcrQ+mBFzAsO6/8s=
|
||||
go.opentelemetry.io/collector/pdata/pprofile v0.154.0/go.mod h1:BE9oOmAEHVqE+yHRe5Z3qz7co+2SU249DIxVGPRsYf8=
|
||||
go.opentelemetry.io/collector/pdata/testdata v0.154.0 h1:PSc3gogHpJoVHenvMhcxkOPTnEKpaykURxtSNyVXYK4=
|
||||
go.opentelemetry.io/collector/pdata/testdata v0.154.0/go.mod h1:zIT+sag/xmSM6VAMhv2tnEzlQF9n266OcQm4V6roWdU=
|
||||
go.opentelemetry.io/collector/pipeline v1.60.0 h1:ZLk/8K/Xzz+JRBWLmqLlVMwEWVnQvmly6nWeKs+lh6s=
|
||||
go.opentelemetry.io/collector/pipeline v1.60.0/go.mod h1:RD90NG3Jbk965Xaqym3JyHkuol4uZJjQVUkD9ddXJIs=
|
||||
go.opentelemetry.io/collector/processor v1.60.0 h1:B3YgiKa+4tMuJ6v4bSaKUtTCwNRzugbEDei8j7jiPpI=
|
||||
go.opentelemetry.io/collector/processor v1.60.0/go.mod h1:ZRNUW8FHZ+0CW+HoIG0/h+fQq8aYjMz9ccy2w2jguag=
|
||||
go.opentelemetry.io/collector/processor/processortest v0.154.0 h1:2Lu7JGqH3fzg9BE0rmzBwCQB7oRWzM8fs+X5SSZO/4M=
|
||||
go.opentelemetry.io/collector/processor/processortest v0.154.0/go.mod h1:E813PIbkBcwgoDnZ9cjuw70MUNmqxAHIvmDC8gOZiP8=
|
||||
go.opentelemetry.io/collector/processor/xprocessor v0.154.0 h1:ert+SRk5DPSqIxqpOEnywrwVLYSvqEvXwy60F94VtFE=
|
||||
go.opentelemetry.io/collector/processor/xprocessor v0.154.0/go.mod h1:93XyfiqPYokF1i8NQvWsKggt5Si5qZvOcZ2P0l+uxII=
|
||||
go.opentelemetry.io/collector/component v1.63.0 h1:l98ZCxfCTt/O6dYB0JVKKtewaFLe/a6N2qQe61Tbf2o=
|
||||
go.opentelemetry.io/collector/component v1.63.0/go.mod h1:yLGMmT7jUiqvuGvkqlfR1CBi0dRkSV67tq22I08ZMPk=
|
||||
go.opentelemetry.io/collector/component/componentstatus v0.157.0 h1:6aARK84axselDP/YGM1cKVPvg6eIyN7Bg9x8x5GzJb4=
|
||||
go.opentelemetry.io/collector/component/componentstatus v0.157.0/go.mod h1:R1nsiV3JluCaffVfjDmglZ0cU3jJUsiaMXgsAZfc670=
|
||||
go.opentelemetry.io/collector/component/componenttest v0.157.0 h1:kTMapOVJ3YMKf5Lu2j/FdcKipn90KkIrnHkKca8uNfk=
|
||||
go.opentelemetry.io/collector/component/componenttest v0.157.0/go.mod h1:AUzvlwDat8AHaNRDm+dzQ59uaEXQO1qTWccjkRjqq00=
|
||||
go.opentelemetry.io/collector/confmap v1.63.0 h1:1THBabHoQc8t/9r6ztMsghiO1OxDPZpYtn0cuwwsxYI=
|
||||
go.opentelemetry.io/collector/confmap v1.63.0/go.mod h1:ksJNAmLTiMkBjMYwXFW1MRRfXYnRsHXA0fW+ZGwb/1U=
|
||||
go.opentelemetry.io/collector/confmap/xconfmap v0.157.0 h1:jZV8p2wMQhq0ktl1/LAFGOXMCzzfMq56r80r41kKoEs=
|
||||
go.opentelemetry.io/collector/confmap/xconfmap v0.157.0/go.mod h1:W1wZk6YJ3+IyvIZEmkXH/ejwkUtBV2zCThY6ewZYd6E=
|
||||
go.opentelemetry.io/collector/consumer v1.63.0 h1:0eOZh0qmDg6HCJaiUqI9FAb4BD4fKJNNO+ygMV/WW0w=
|
||||
go.opentelemetry.io/collector/consumer v1.63.0/go.mod h1:IVhjv4d+PmSf4Ttz/guJFbWJtRM3Ld3nRcZ12gxy6PA=
|
||||
go.opentelemetry.io/collector/consumer/consumertest v0.157.0 h1:zehAVaLn67AWLhhi/LrUY0Tv06XWx5LYCvvmu7xHXHU=
|
||||
go.opentelemetry.io/collector/consumer/consumertest v0.157.0/go.mod h1:HRxCIepTczx1uWnm3VPPNbt0k+N9fG/ENZDF1dGrkQc=
|
||||
go.opentelemetry.io/collector/consumer/xconsumer v0.157.0 h1:DPbDCHDwbwVa0OoikTktkbXAobN7EgC4XbYaNJIyooI=
|
||||
go.opentelemetry.io/collector/consumer/xconsumer v0.157.0/go.mod h1:IepWZ5ZNBUuS5OiCPQaD/Q1buqriiItxaaMYD7nqYdc=
|
||||
go.opentelemetry.io/collector/featuregate v1.63.0 h1:6EWX1C5AtmIh8hFH97DwK6R7R8Jk3fTLxAUfZPXGutY=
|
||||
go.opentelemetry.io/collector/featuregate v1.63.0/go.mod h1:4ga1QBMPEejXXmpyJS8lmaRpknJ3Lb9Bvk6e420bUFU=
|
||||
go.opentelemetry.io/collector/internal/componentalias v0.157.0 h1:bvVANHVkXRhoRBc92rDsbiC0OHlu9/EbRqPROD9qrPc=
|
||||
go.opentelemetry.io/collector/internal/componentalias v0.157.0/go.mod h1:PCLANRXGlMhf9NmU+JPFHtZuY4WpMOsccXlDHTDTu1w=
|
||||
go.opentelemetry.io/collector/internal/testutil v0.157.0 h1:plojUQwFC5l1ex9KUDaLmCFY/mTxEmf3zrlP7M23IEw=
|
||||
go.opentelemetry.io/collector/internal/testutil v0.157.0/go.mod h1:Jkjs6rkqs973LqgZ0Fe3zrokQRKULYXPIf4HuqStiEE=
|
||||
go.opentelemetry.io/collector/pdata v1.63.0 h1:fY2xSG2MnyoBwA4GUhzoogGZMuNS0qHpCoODqaKwiVQ=
|
||||
go.opentelemetry.io/collector/pdata v1.63.0/go.mod h1:jzozYYhQEkTQ/CCbCBNC+hYUeju9S2J8HIqIDHdxZWk=
|
||||
go.opentelemetry.io/collector/pdata/pprofile v0.157.0 h1:YRTPhwWzdG0pfJmb8p/qpQm1EdX+JfV20qzwG3ypDqI=
|
||||
go.opentelemetry.io/collector/pdata/pprofile v0.157.0/go.mod h1:kwy/ufNUBkw8PsFPQnAqCvD12OpGU8h9A1cz5S7xS6g=
|
||||
go.opentelemetry.io/collector/pdata/testdata v0.157.0 h1:TEee4jvYe8b0nPPKQnOnLz3LaA/XRtv3ja8J56DWIZc=
|
||||
go.opentelemetry.io/collector/pdata/testdata v0.157.0/go.mod h1:ht50rAkY2bonM0eL+IvGDcAKWPNKdFE+8LjdaFjcOAA=
|
||||
go.opentelemetry.io/collector/pipeline v1.63.0 h1:D97Nb1Jsgp8Ks+74jXs24kBZVd4pPdhuKe9dVogjcEo=
|
||||
go.opentelemetry.io/collector/pipeline v1.63.0/go.mod h1:RD90NG3Jbk965Xaqym3JyHkuol4uZJjQVUkD9ddXJIs=
|
||||
go.opentelemetry.io/collector/processor v1.63.0 h1:Aj1MLNiG9TJKOMywmojjbVFe1qtTLLMjRqmViEpPHeM=
|
||||
go.opentelemetry.io/collector/processor v1.63.0/go.mod h1:dUyL11sxKBZn1XSqsiDyg76k+jZtYlRL7CkOuFWwVLI=
|
||||
go.opentelemetry.io/collector/processor/processortest v0.157.0 h1:dtpyoIvbB3VLqZ+cV96EAsyCCUVX3mBFg9KMPRRyhlo=
|
||||
go.opentelemetry.io/collector/processor/processortest v0.157.0/go.mod h1:gkRzdmNYfKJAmGCk/x8qDsIavVbNI0wTs+uhN7rY7B0=
|
||||
go.opentelemetry.io/collector/processor/xprocessor v0.157.0 h1:+WyLGrHwcPk3TK+qt7T8CH+oADEMY4r+U5fIMTBvrAc=
|
||||
go.opentelemetry.io/collector/processor/xprocessor v0.157.0/go.mod h1:DPmoWlks+CihSRjTGKvPKK6a8bl9mHk8hxthrJ+lvBI=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.44.0 h1:NmLfL734pJhM0JKaYd2Y28+nY9dPRWYAAbxhRCrKXPw=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.44.0/go.mod h1:tNAsgd8avTGke1+MndXlU5Cru4PQ9Ai/cCNWQv/ZJ/s=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.69.0 h1:2yEATaop1/a1I4psnSLgWVPLWwCzkqWakgJy7xTDVy0=
|
||||
@@ -486,8 +484,8 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 h1:8tvICD4
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0/go.mod h1:z9+yiacE0IHRqM4qFfkbt/JYlmYXgss8GY/jXoNuPJI=
|
||||
go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
|
||||
go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0 h1:TC+BewnDpeiAmcscXbGMfxkO+mwYUwE/VySwvw88PfA=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0/go.mod h1:J/ZyF4vfPwsSr9xJSPyQ4LqtcTPULFR64KwTikGLe+A=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.44.0 h1:hqxVTu/GtBF+vJ8d1fzW7fRxZFvgoDjWcxwwCaFDYpU=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.44.0/go.mod h1:z5fVEF4X5v0ESvlJqBrrFlBVoj5EQuefZpzsu7R+x5Q=
|
||||
go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
|
||||
go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
|
||||
go.opentelemetry.io/otel/metric/x v0.66.0 h1:YkCrx1zLOChi9ZcZ6euupOcsgzbVlec7D/xoEU1+cTA=
|
||||
@@ -519,65 +517,63 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
|
||||
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
|
||||
golang.org/x/exp v0.0.0-20260611194520-c48552f49976 h1:X8Hz2ImujgbmetVuW+w2YkyZChE3cBpZi2P158rTG9M=
|
||||
golang.org/x/exp v0.0.0-20260611194520-c48552f49976/go.mod h1:vnf4pv9iKZXY58sQE1L86zmNWJ4159e1RkcWiLCkeEY=
|
||||
golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
|
||||
golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
|
||||
golang.org/x/exp v0.0.0-20260718201538-764159d718ef h1:LkZ48HFgy/TvhTI0bcWkjgFkgLyKUwcTbDjS0DUjw+A=
|
||||
golang.org/x/exp v0.0.0-20260718201538-764159d718ef/go.mod h1:EdfpwwqSu+0Li0mzskwHU6FWDV3t9Q+RZDo3QMUtL3Q=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
|
||||
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
|
||||
golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk=
|
||||
golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
|
||||
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
|
||||
golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
|
||||
golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
|
||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
|
||||
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
|
||||
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
|
||||
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
|
||||
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
|
||||
golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
|
||||
golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
|
||||
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
|
||||
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
|
||||
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
|
||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.46.0 h1:7jTurBkPZu4moS/Uy4OQT1M+QBlsj3wejyZwsT8Z7rk=
|
||||
golang.org/x/tools v0.46.0/go.mod h1:FrD85F8l+NWL+9XWBSyVSHO6Ne4jutsfIFba7AWQ5Ys=
|
||||
golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE=
|
||||
golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
||||
google.golang.org/api v0.284.0 h1:i+cKTgeQRcRySkP7QTl5PDO7/pAm8EcMFIUMlNbk4Vc=
|
||||
google.golang.org/api v0.284.0/go.mod h1:AU44fU+XVZOCcd8uLaBIa/ZgzgPf/0qqY3+m7lQaado=
|
||||
google.golang.org/genproto v0.0.0-20260615183401-62b3387ff324 h1:r7/+bt4yKglJiN8eUY8enbRjglCvFm1eh8ezYdYoKTM=
|
||||
google.golang.org/genproto v0.0.0-20260615183401-62b3387ff324/go.mod h1:V5M1lxGXNUICs0aOqAMsK6HtmLnCyuzY031uOQS9rJE=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260615183401-62b3387ff324 h1:g0RAkxK/smSu/iRwC/KIX1mwUoVJtk2OjbgaeS4DmUM=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260615183401-62b3387ff324/go.mod h1:Z4WJ5pJOYWFWcHEQUelD5QaZDknIQkpIL/+fyJOT9+A=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260615183401-62b3387ff324 h1:9HZDLIdYBJXAnaFOr9WHrKVycfpY+75s9HGadC0305A=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260615183401-62b3387ff324/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ=
|
||||
google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I=
|
||||
google.golang.org/api v0.290.0 h1:eMw0Xo+IfbbMlKmW7aHvpyQRv9RCXuWx/vs8AD+0x9A=
|
||||
google.golang.org/api v0.290.0/go.mod h1:weJZ3lldHFYI0DBFNKpJelUDNnusTt5YaOEgxvt8ci8=
|
||||
google.golang.org/genproto v0.0.0-20260720211330-0afa2a65878a h1:MVNwR9RFj7qfpMtNK71pq97FgrLG0lVHZh+VbM2LZeI=
|
||||
google.golang.org/genproto v0.0.0-20260720211330-0afa2a65878a/go.mod h1:0qnvndM9dUhat9AtF1jqYN6WZ+tMxEAFImo3WNvUX7w=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260720211330-0afa2a65878a h1:97PfJ4tCxY5C7NzzgGqQEMZmXbISdvSArNNEOoUGKBg=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260720211330-0afa2a65878a/go.mod h1:1brfde68Npq6+WA75c1EHWPijZEG1kMus61ygPZfn4A=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260720211330-0afa2a65878a h1:qI/YMH1ep2qQtqcp00gMQyoU7mjvbhg88GJKCvfoLj0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260720211330-0afa2a65878a/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE=
|
||||
google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA=
|
||||
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI=
|
||||
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -594,23 +590,23 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
k8s.io/api v0.36.2 h1:TF6YDLIzKfccK7cq9YpTcGX8TJmEkHVRv78DM51fRYY=
|
||||
k8s.io/api v0.36.2/go.mod h1:F4LbMO4brjZYh7yFkXWhynSvtB7YauxV4c+HHkNRGNg=
|
||||
k8s.io/apimachinery v0.36.2 h1:0PE/W/WNy1UX61NLbXY5TMbJ6UwLL6E6lAPkYrKFxbQ=
|
||||
k8s.io/apimachinery v0.36.2/go.mod h1:fvf/HOLXq9RId0rnDIbN1OEBvHXdQbLMM8nu0LcBUf4=
|
||||
k8s.io/client-go v0.36.2 h1:bfgxmFKc9CgqsgX4xKLAAdmTQlWee7Ob/HlDOrJ5TBI=
|
||||
k8s.io/client-go v0.36.2/go.mod h1:1vgO4OAlfPnoLcb+Rze2GF5rAr14w8qjrYMoyXJzQj0=
|
||||
k8s.io/api v0.36.3 h1:NxB+05W2UGqXWFXcLO0RB5cnqnUPP5v5sVlaOH0Iz4w=
|
||||
k8s.io/api v0.36.3/go.mod h1:JzLQKqRHC5+I8RVj/lS3lCg0mg6nWI9Fo/Sk3ElxHzg=
|
||||
k8s.io/apimachinery v0.36.3 h1:PkzMRBRG8joFD8EhCuQAtNPvJlxb82FwplP26HIzvAM=
|
||||
k8s.io/apimachinery v0.36.3/go.mod h1:cTSjBWgPe/6CQyBKzY/hDIRWCQQQeK0mfLbml0UYFHE=
|
||||
k8s.io/client-go v0.36.3 h1:M4JdVzXxYcZk4fGpfDdYnxSwhLKWCFoQsHW6t+z8Hfg=
|
||||
k8s.io/client-go v0.36.3/go.mod h1:gcPwr0c87vjjG6HB6pWEqOeuYVoXSsREjzux2j6GF30=
|
||||
k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=
|
||||
k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=
|
||||
k8s.io/kube-openapi v0.0.0-20260603220949-865597e52e25 h1:mPMaPMpBij2V1Wv/fR+HW124vVGXXvOSS9ver/9yjWs=
|
||||
k8s.io/kube-openapi v0.0.0-20260603220949-865597e52e25/go.mod h1:V/QaCUYDa+0QpcHhVVc5l99Uz56wEMEXBSj9oCDkNDY=
|
||||
k8s.io/utils v0.0.0-20260507154919-ff6756f316d2 h1:wU4tMEhLGgIbLvXQb1cfN+EcM0wf7zC6CPF+C79jroc=
|
||||
k8s.io/utils v0.0.0-20260507154919-ff6756f316d2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
|
||||
k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad h1:oXImqH8mQNk7PmvzKhmN3ddJoY6OnyM225MXwGHPm0A=
|
||||
k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad/go.mod h1:0/mqHCVhlumdJ3BhCfnjSZQE037nAhNodh1/hK0T8/I=
|
||||
k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3 h1:jVkFFVfXdXP74B/zbO3hM3hpSFD0xvhQ5U686DPurkE=
|
||||
k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3/go.mod h1:M2s5JB1lIYP3jzZdorPLHXIPJzt9vv2muW5a6L9DtNM=
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
|
||||
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
|
||||
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.4.0 h1:qmp2e3ZfFi1/jJbDGpD4mt3wyp6PE1NfKHCYLqgNQJo=
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.4.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.4.2 h1:qdOxHwrl2Kaag1aQEarlYcOA9vSyGCp3CIki3aW8c4Q=
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.4.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
|
||||
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
|
||||
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
|
||||
|
||||
334
lib/httputil/transport_lb.go
Normal file
334
lib/httputil/transport_lb.go
Normal file
@@ -0,0 +1,334 @@
|
||||
package httputil
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/netutil"
|
||||
)
|
||||
|
||||
const (
|
||||
brokenBackendTimeout = 5 * time.Second
|
||||
backendDiscoveryInterval = 10 * time.Second
|
||||
backendDiscoveryTimeout = 10 * time.Second
|
||||
)
|
||||
|
||||
// NewLoadBalancerTransport returns new RoundTripper that performs least-loaded HTTP requests loadbalancing
|
||||
// based on discovered backends for the given url host
|
||||
// and update url with load-balancing prefix
|
||||
//
|
||||
// It returns origin transport and url if load-balancing is not needed for given url
|
||||
func NewLoadBalancerTransport(origin http.RoundTripper, originURL *url.URL) (http.RoundTripper, *url.URL) {
|
||||
|
||||
modifiedURL := *originURL
|
||||
var discoverFunc func(context.Context, string, string) ([]*backend, error)
|
||||
switch {
|
||||
case strings.HasPrefix(originURL.Host, "dns+"):
|
||||
modifiedURL.Host = modifiedURL.Host[4:]
|
||||
discoverFunc = discoverDNSBackends
|
||||
case strings.HasPrefix(originURL.Host, "srv+"):
|
||||
modifiedURL.Host = modifiedURL.Host[4:]
|
||||
discoverFunc = discoverSRVBackends
|
||||
default:
|
||||
return origin, originURL
|
||||
}
|
||||
host, port, err := net.SplitHostPort(modifiedURL.Host)
|
||||
if err != nil {
|
||||
host = modifiedURL.Host
|
||||
port = "80"
|
||||
if modifiedURL.Scheme == "https" {
|
||||
port = "443"
|
||||
}
|
||||
}
|
||||
t := &loadbalancerTransport{
|
||||
tr: origin,
|
||||
host: host,
|
||||
port: port,
|
||||
discoverFunc: discoverFunc,
|
||||
}
|
||||
t.discoverBackends()
|
||||
return t, &modifiedURL
|
||||
}
|
||||
|
||||
type loadbalancerTransport struct {
|
||||
tr http.RoundTripper
|
||||
host string
|
||||
port string
|
||||
|
||||
discoverFunc func(context.Context, string, string) ([]*backend, error)
|
||||
|
||||
nextDiscoveryDeadline atomic.Uint64
|
||||
discovering atomic.Bool
|
||||
dbs atomic.Pointer[discoveredBackends]
|
||||
}
|
||||
|
||||
type discoveredBackends struct {
|
||||
backends []*backend
|
||||
// n is an atomic counter, which is used for balancing load among available backends.
|
||||
n atomic.Uint32
|
||||
}
|
||||
|
||||
// getLeastLoadedBackend returns least loaded backend
|
||||
// caller must release backend with backend.put() method
|
||||
func (dbs *discoveredBackends) getLeastLoadedBackend() *backend {
|
||||
firstB := dbs.backends[0]
|
||||
if len(dbs.backends) == 1 {
|
||||
firstB.get()
|
||||
return firstB
|
||||
}
|
||||
|
||||
// Slow path - select other backends.
|
||||
n := dbs.n.Add(1) - 1
|
||||
for i := range uint32(len(dbs.backends)) {
|
||||
idx := (n + i) % uint32(len(dbs.backends))
|
||||
bu := dbs.backends[idx]
|
||||
if bu.isBroken() {
|
||||
continue
|
||||
}
|
||||
|
||||
// The Load() in front of CompareAndSwap() avoids CAS overhead for items with values bigger than 0.
|
||||
if bu.concurrentRequests.Load() == 0 && bu.concurrentRequests.CompareAndSwap(0, 1) {
|
||||
dbs.n.CompareAndSwap(n+1, idx+1)
|
||||
// There is no need in the call b.get(), because we already incremented b.concurrentRequests above.
|
||||
return bu
|
||||
}
|
||||
}
|
||||
|
||||
// Slow path - return the backend with the minimum number of concurrently executed requests.
|
||||
bMinIdx := n % uint32(len(dbs.backends))
|
||||
minRequests := dbs.backends[bMinIdx].concurrentRequests.Load()
|
||||
for i := uint32(1); i < uint32(len(dbs.backends)); i++ {
|
||||
idx := (n + i) % uint32(len(dbs.backends))
|
||||
bu := dbs.backends[idx]
|
||||
if bu.isBroken() {
|
||||
continue
|
||||
}
|
||||
|
||||
reqs := bu.concurrentRequests.Load()
|
||||
if reqs < minRequests || dbs.backends[bMinIdx].isBroken() {
|
||||
bMinIdx = idx
|
||||
minRequests = reqs
|
||||
}
|
||||
}
|
||||
bMin := dbs.backends[bMinIdx]
|
||||
if bMin.isBroken() {
|
||||
// If all backends are broken, then returns the first backend.
|
||||
firstB.get()
|
||||
return firstB
|
||||
}
|
||||
bMin.get()
|
||||
dbs.n.CompareAndSwap(n+1, bMinIdx+1)
|
||||
return bMin
|
||||
}
|
||||
|
||||
type backend struct {
|
||||
addr string
|
||||
concurrentRequests atomic.Int32
|
||||
brokenDeadline atomic.Uint64
|
||||
}
|
||||
|
||||
func (b *backend) get() {
|
||||
b.concurrentRequests.Add(1)
|
||||
}
|
||||
|
||||
func (b *backend) put() {
|
||||
b.concurrentRequests.Add(-1)
|
||||
}
|
||||
|
||||
func (b *backend) isBroken() bool {
|
||||
bd := b.brokenDeadline.Load()
|
||||
if bd == 0 {
|
||||
return false
|
||||
}
|
||||
ct := fasttime.UnixTimestamp()
|
||||
return ct < bd
|
||||
}
|
||||
|
||||
// RoundTrip implements http.RoundTripper interface
|
||||
func (lb *loadbalancerTransport) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
dbs := lb.getBackends()
|
||||
if dbs == nil || len(dbs.backends) == 0 {
|
||||
return nil, fmt.Errorf("no backends found for hostname=%q", lb.host)
|
||||
}
|
||||
|
||||
maxRetries := len(dbs.backends)
|
||||
var lastErr error
|
||||
for range maxRetries {
|
||||
b := dbs.getLeastLoadedBackend()
|
||||
resp, err := lb.doRequest(r, b)
|
||||
if err != nil {
|
||||
ct := fasttime.UnixTimestamp()
|
||||
brokenDeadline := ct + uint64(brokenBackendTimeout.Seconds())
|
||||
b.brokenDeadline.Store(brokenDeadline)
|
||||
if !netutil.IsTrivialNetworkError(err) {
|
||||
return nil, err
|
||||
}
|
||||
// perform the same check for retry as http.Request.isReplayable does
|
||||
canRetry := r.Body == nil || r.Body == http.NoBody || r.GetBody != nil
|
||||
if !canRetry {
|
||||
return nil, err
|
||||
}
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
return nil, fmt.Errorf("all backends are unavailable: %w", lastErr)
|
||||
}
|
||||
|
||||
func (lb *loadbalancerTransport) doRequest(r *http.Request, b *backend) (*http.Response, error) {
|
||||
r2 := r.Clone(r.Context())
|
||||
if r.GetBody != nil {
|
||||
body, err := r.GetBody()
|
||||
if err != nil {
|
||||
b.put()
|
||||
return nil, err
|
||||
}
|
||||
r2.Body = body
|
||||
}
|
||||
r2.URL.Host = b.addr
|
||||
if r2.Host == "" {
|
||||
r2.Host = r.URL.Host
|
||||
}
|
||||
resp, err := lb.tr.RoundTrip(r2)
|
||||
if err != nil {
|
||||
b.put()
|
||||
return nil, err
|
||||
}
|
||||
// wrap response body with readCloser that releases backend after Close call
|
||||
// it's needed to properly account loaded backends at getLeastLoadedBackends
|
||||
resp.Body = newReleaseReadCloser(resp.Body, b)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (lb *loadbalancerTransport) getBackends() *discoveredBackends {
|
||||
ct := fasttime.UnixTimestamp()
|
||||
deadline := lb.nextDiscoveryDeadline.Load()
|
||||
if ct < deadline || !lb.discovering.CompareAndSwap(false, true) {
|
||||
return lb.dbs.Load()
|
||||
}
|
||||
lb.discoverBackends()
|
||||
return lb.dbs.Load()
|
||||
}
|
||||
|
||||
func (lb *loadbalancerTransport) discoverBackends() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), backendDiscoveryTimeout)
|
||||
defer func() {
|
||||
cancel()
|
||||
ct := fasttime.UnixTimestamp()
|
||||
nextDeadline := ct + uint64(backendDiscoveryInterval.Seconds())
|
||||
lb.nextDiscoveryDeadline.Store(nextDeadline)
|
||||
lb.discovering.Store(false)
|
||||
}()
|
||||
backends, err := lb.discoverFunc(ctx, lb.host, lb.port)
|
||||
if err != nil {
|
||||
logger.Errorf("cannot discover backends: %s, retry in %s", err, backendDiscoveryInterval)
|
||||
return
|
||||
}
|
||||
dbs := &discoveredBackends{
|
||||
backends: backends,
|
||||
}
|
||||
sort.Slice(dbs.backends, func(i, j int) bool {
|
||||
return dbs.backends[i].addr < dbs.backends[j].addr
|
||||
})
|
||||
|
||||
prevBackends := lb.dbs.Load()
|
||||
if areBackendsEqual(prevBackends, dbs) {
|
||||
return
|
||||
}
|
||||
|
||||
lb.dbs.Store(dbs)
|
||||
}
|
||||
|
||||
func discoverDNSBackends(ctx context.Context, host, port string) ([]*backend, error) {
|
||||
addrs, err := netutil.Resolver.LookupIPAddr(ctx, host)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to lookupIPAddr for host: %q: %w", host, err)
|
||||
}
|
||||
backends := make([]*backend, 0, len(addrs))
|
||||
for _, addr := range addrs {
|
||||
if !netutil.TCP6Enabled() {
|
||||
ip, ok := netip.AddrFromSlice(addr.IP)
|
||||
if !ok {
|
||||
logger.Panicf("BUG: cannot build netip Addr from slice addr: %q", addr.IP.String())
|
||||
}
|
||||
if !ip.Unmap().Is4() {
|
||||
continue
|
||||
}
|
||||
}
|
||||
ip := addr.IP.String()
|
||||
if len(port) > 0 {
|
||||
ip = net.JoinHostPort(ip, port)
|
||||
}
|
||||
backends = append(backends, &backend{addr: ip})
|
||||
}
|
||||
return backends, nil
|
||||
}
|
||||
|
||||
func discoverSRVBackends(ctx context.Context, host, port string) ([]*backend, error) {
|
||||
_, addrs, err := netutil.Resolver.LookupSRV(ctx, "", "", host)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to LookupSRV records for host: %q: %w", host, err)
|
||||
}
|
||||
backends := make([]*backend, 0, len(addrs))
|
||||
for _, addr := range addrs {
|
||||
hostPort := port
|
||||
if addr.Port > 0 {
|
||||
hostPort = strconv.FormatUint(uint64(addr.Port), 10)
|
||||
}
|
||||
hostAddr := net.JoinHostPort(addr.Target, hostPort)
|
||||
backends = append(backends, &backend{addr: hostAddr})
|
||||
}
|
||||
return backends, nil
|
||||
}
|
||||
|
||||
func newReleaseReadCloser(responseBody io.ReadCloser, b *backend) io.ReadCloser {
|
||||
return &releaseReadCloser{
|
||||
ReadCloser: responseBody,
|
||||
b: b,
|
||||
}
|
||||
}
|
||||
|
||||
type releaseReadCloser struct {
|
||||
io.ReadCloser
|
||||
b *backend
|
||||
released atomic.Bool
|
||||
}
|
||||
|
||||
func (rrc *releaseReadCloser) Close() error {
|
||||
if rrc.released.CompareAndSwap(false, true) {
|
||||
// Close method could be called multiple times
|
||||
// and it must produce idempotent result
|
||||
rrc.b.put()
|
||||
}
|
||||
return rrc.ReadCloser.Close()
|
||||
}
|
||||
|
||||
func areBackendsEqual(left, right *discoveredBackends) bool {
|
||||
if left == nil || right == nil {
|
||||
return left == right
|
||||
}
|
||||
if len(left.backends) != len(right.backends) {
|
||||
return false
|
||||
}
|
||||
for idx, leftB := range left.backends {
|
||||
rightB := right.backends[idx]
|
||||
if leftB.addr != rightB.addr {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
127
lib/httputil/transport_lb_test.go
Normal file
127
lib/httputil/transport_lb_test.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package httputil
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/netutil"
|
||||
)
|
||||
|
||||
type testRemoteServer struct {
|
||||
mu sync.Mutex
|
||||
requestsPerHost map[string]int
|
||||
|
||||
totalRequests int
|
||||
firstError error
|
||||
}
|
||||
|
||||
func (trs *testRemoteServer) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
trs.mu.Lock()
|
||||
if trs.firstError != nil && trs.totalRequests == 0 {
|
||||
err := trs.firstError
|
||||
trs.firstError = nil
|
||||
trs.totalRequests++
|
||||
trs.mu.Unlock()
|
||||
return nil, err
|
||||
}
|
||||
trs.totalRequests++
|
||||
|
||||
if trs.requestsPerHost == nil {
|
||||
trs.requestsPerHost = make(map[string]int)
|
||||
}
|
||||
trs.requestsPerHost[r.URL.Host]++
|
||||
trs.mu.Unlock()
|
||||
|
||||
return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody}, nil
|
||||
}
|
||||
|
||||
type testDNSResolver struct {
|
||||
ips []net.IPAddr
|
||||
}
|
||||
|
||||
func (tdr *testDNSResolver) LookupSRV(_ context.Context, _, _, name string) (cname string, addrs []*net.SRV, err error) {
|
||||
return "", nil, fmt.Errorf("unexpected LookupMX call for name=%q", name)
|
||||
}
|
||||
func (tdr *testDNSResolver) LookupIPAddr(ctx context.Context, host string) ([]net.IPAddr, error) {
|
||||
return tdr.ips, nil
|
||||
}
|
||||
|
||||
func (tdr *testDNSResolver) LookupMX(_ context.Context, name string) ([]*net.MX, error) {
|
||||
return nil, fmt.Errorf("unexpected LookupMX call for name=%q", name)
|
||||
}
|
||||
|
||||
func TestLoadbalancerTransport(t *testing.T) {
|
||||
f := func(discoveredIPs []string, trs *testRemoteServer) {
|
||||
t.Helper()
|
||||
|
||||
parsedIPs := make([]net.IPAddr, 0, len(discoveredIPs))
|
||||
for _, dIP := range discoveredIPs {
|
||||
pIP, err := netip.ParseAddr(dIP)
|
||||
if err != nil {
|
||||
t.Fatalf("cannot parse IP=%q: %s", dIP, err)
|
||||
}
|
||||
parsedIPs = append(parsedIPs, net.IPAddr{IP: pIP.AsSlice()})
|
||||
}
|
||||
tdr := &testDNSResolver{ips: parsedIPs}
|
||||
originResolver := netutil.Resolver
|
||||
defer func() { netutil.Resolver = originResolver }()
|
||||
|
||||
netutil.Resolver = tdr
|
||||
requestURL, err := url.Parse("http://dns+vmsingle.example.com:8429/api/v1/write")
|
||||
if err != nil {
|
||||
t.Fatalf("cannot parse url: %s", err)
|
||||
}
|
||||
lbt, requestURL := NewLoadBalancerTransport(trs, requestURL)
|
||||
if len(discoveredIPs) == 0 {
|
||||
r, err := http.NewRequest(http.MethodGet, requestURL.String(), nil)
|
||||
if err != nil {
|
||||
t.Fatalf("cannot create http request: %s", err)
|
||||
}
|
||||
_, err = lbt.RoundTrip(r)
|
||||
if err == nil {
|
||||
t.Fatalf("expected no backends found error")
|
||||
}
|
||||
return
|
||||
}
|
||||
expectedRequestsPerHost := 2
|
||||
for range len(discoveredIPs) * expectedRequestsPerHost {
|
||||
r, err := http.NewRequest(http.MethodGet, requestURL.String(), nil)
|
||||
if err != nil {
|
||||
t.Fatalf("cannot create http request: %s", err)
|
||||
}
|
||||
resp, err := lbt.RoundTrip(r)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
}
|
||||
requestsPerHost := trs.requestsPerHost
|
||||
|
||||
for _, dIP := range discoveredIPs {
|
||||
expectedHostPort := net.JoinHostPort(dIP, "8429")
|
||||
gotRequestsPerHost, ok := requestsPerHost[expectedHostPort]
|
||||
if !ok {
|
||||
t.Fatalf("not found expected backend request for: %q", expectedHostPort)
|
||||
}
|
||||
if gotRequestsPerHost != expectedRequestsPerHost {
|
||||
t.Fatalf("unexpected requests per host:%q %d:%d (-;+)", expectedHostPort, expectedRequestsPerHost, gotRequestsPerHost)
|
||||
}
|
||||
}
|
||||
}
|
||||
trs := testRemoteServer{}
|
||||
f([]string{"1.1.1.1"}, &trs)
|
||||
|
||||
trs = testRemoteServer{}
|
||||
f([]string{"1.1.1.1", "2.2.2.2", "5.5.5.5"}, &trs)
|
||||
|
||||
// empty backends, expecting error
|
||||
trs = testRemoteServer{}
|
||||
f([]string{}, &trs)
|
||||
|
||||
}
|
||||
@@ -95,14 +95,14 @@ func mustOpenFastQueue(path, name string, opts OpenFastQueueOpts) *FastQueue {
|
||||
}
|
||||
fq.cond.L = &fq.mu
|
||||
fq.lastInmemoryBlockReadTime = fasttime.UnixTimestamp()
|
||||
_ = metrics.GetOrCreateGauge(fmt.Sprintf(`vm_persistentqueue_bytes_pending{path=%q}`, path), func() float64 {
|
||||
_ = metrics.GetOrCreateGauge(fmt.Sprintf(`vm_persistentqueue_bytes_pending{path=%q, name=%q}`, path, name), func() float64 {
|
||||
fq.mu.Lock()
|
||||
n := fq.pq.GetPendingBytes()
|
||||
fq.mu.Unlock()
|
||||
return float64(n)
|
||||
})
|
||||
|
||||
_ = metrics.GetOrCreateGauge(fmt.Sprintf(`vm_persistentqueue_free_disk_space_bytes{path=%q}`, path), func() float64 {
|
||||
_ = metrics.GetOrCreateGauge(fmt.Sprintf(`vm_persistentqueue_free_disk_space_bytes{path=%q, name=%q}`, path, name), func() float64 {
|
||||
freeSpaceBytes := fs.MustGetFreeSpace(path)
|
||||
// Limited by disk space if remoteWrite.maxDiskUsagePerURL wasn't set
|
||||
if maxPendingBytes == 0 {
|
||||
|
||||
@@ -156,12 +156,12 @@ func tryOpeningQueue(path, name string, chunkFileSize, maxBlockSize, maxPendingB
|
||||
q.dir = path
|
||||
q.name = name
|
||||
|
||||
q.blocksDropped = metrics.GetOrCreateCounter(fmt.Sprintf(`vm_persistentqueue_blocks_dropped_total{path=%q}`, path))
|
||||
q.bytesDropped = metrics.GetOrCreateCounter(fmt.Sprintf(`vm_persistentqueue_bytes_dropped_total{path=%q}`, path))
|
||||
q.blocksWritten = metrics.GetOrCreateCounter(fmt.Sprintf(`vm_persistentqueue_blocks_written_total{path=%q}`, path))
|
||||
q.bytesWritten = metrics.GetOrCreateCounter(fmt.Sprintf(`vm_persistentqueue_bytes_written_total{path=%q}`, path))
|
||||
q.blocksRead = metrics.GetOrCreateCounter(fmt.Sprintf(`vm_persistentqueue_blocks_read_total{path=%q}`, path))
|
||||
q.bytesRead = metrics.GetOrCreateCounter(fmt.Sprintf(`vm_persistentqueue_bytes_read_total{path=%q}`, path))
|
||||
q.blocksDropped = metrics.GetOrCreateCounter(fmt.Sprintf(`vm_persistentqueue_blocks_dropped_total{path=%q, name=%q}`, path, name))
|
||||
q.bytesDropped = metrics.GetOrCreateCounter(fmt.Sprintf(`vm_persistentqueue_bytes_dropped_total{path=%q, name=%q}`, path, name))
|
||||
q.blocksWritten = metrics.GetOrCreateCounter(fmt.Sprintf(`vm_persistentqueue_blocks_written_total{path=%q, name=%q}`, path, name))
|
||||
q.bytesWritten = metrics.GetOrCreateCounter(fmt.Sprintf(`vm_persistentqueue_bytes_written_total{path=%q, name=%q}`, path, name))
|
||||
q.blocksRead = metrics.GetOrCreateCounter(fmt.Sprintf(`vm_persistentqueue_blocks_read_total{path=%q, name=%q}`, path, name))
|
||||
q.bytesRead = metrics.GetOrCreateCounter(fmt.Sprintf(`vm_persistentqueue_bytes_read_total{path=%q, name=%q}`, path, name))
|
||||
|
||||
cleanOnError := func() {
|
||||
if q.reader != nil {
|
||||
|
||||
@@ -485,78 +485,65 @@ func TestIndexDBOpenClose(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIndexDB(t *testing.T) {
|
||||
const metricGroups = 10
|
||||
timestamp := time.Now().UnixMilli()
|
||||
defer testRemoveAll(t)
|
||||
|
||||
t.Run("serial", func(t *testing.T) {
|
||||
const path = "TestIndexDB-serial"
|
||||
s := MustOpenStorage(path, OpenOptions{})
|
||||
ptw := s.tb.MustGetPartition(timestamp)
|
||||
db := ptw.pt.idb
|
||||
mns, tsids, err := testIndexDBGetOrCreateTSIDByName(db, metricGroups, timestamp)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
if err := testIndexDBCheckTSIDByName(db, mns, tsids, timestamp, false); err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
// Re-open the storage and verify it works as expected.
|
||||
s.tb.PutPartition(ptw)
|
||||
s.MustClose()
|
||||
s = MustOpenStorage(path, OpenOptions{})
|
||||
|
||||
ptw = s.tb.MustGetPartition(timestamp)
|
||||
db = ptw.pt.idb
|
||||
if err := testIndexDBCheckTSIDByName(db, mns, tsids, timestamp, false); err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
s.tb.PutPartition(ptw)
|
||||
s.MustClose()
|
||||
fs.MustRemoveDir(path)
|
||||
})
|
||||
|
||||
t.Run("concurrent", func(t *testing.T) {
|
||||
const path = "TestIndexDB-concurrent"
|
||||
s := MustOpenStorage(path, OpenOptions{})
|
||||
f := func(t *testing.T, concurrency int, disablePerDayIndex bool) {
|
||||
const metricGroups = 10
|
||||
now := time.Now().UTC()
|
||||
timestamp := now.UnixMilli()
|
||||
date := uint64(timestamp / msecPerDay)
|
||||
searchTR := TimeRange{
|
||||
MinTimestamp: time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC).UnixMilli(),
|
||||
MaxTimestamp: time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 999_999_999, time.UTC).UnixMilli(),
|
||||
}
|
||||
if disablePerDayIndex {
|
||||
searchTR = globalIndexTimeRange
|
||||
}
|
||||
|
||||
s := MustOpenStorage(t.Name(), OpenOptions{
|
||||
DisablePerDayIndex: disablePerDayIndex,
|
||||
})
|
||||
defer s.MustClose()
|
||||
ptw := s.tb.MustGetPartition(timestamp)
|
||||
defer s.tb.PutPartition(ptw)
|
||||
db := ptw.pt.idb
|
||||
|
||||
ch := make(chan error, 3)
|
||||
for range cap(ch) {
|
||||
go func() {
|
||||
mns, tsid, err := testIndexDBGetOrCreateTSIDByName(db, metricGroups, timestamp)
|
||||
var wg sync.WaitGroup
|
||||
errs := make([]error, concurrency)
|
||||
isConcurrent := concurrency > 1
|
||||
for i := range concurrency {
|
||||
wg.Go(func() {
|
||||
mns, tsids, err := testIndexDBGetOrCreateTSIDByName(db, metricGroups, date)
|
||||
if err != nil {
|
||||
ch <- err
|
||||
errs[i] = fmt.Errorf("testIndexDBGetOrCreateTSIDByName failed unexpectedly: %w", err)
|
||||
return
|
||||
}
|
||||
if err := testIndexDBCheckTSIDByName(db, mns, tsid, timestamp, true); err != nil {
|
||||
ch <- err
|
||||
return
|
||||
if err := testIndexDBCheckTSIDByName(db, mns, tsids, date, searchTR, isConcurrent); err != nil {
|
||||
errs[i] = fmt.Errorf("testIndexDBCheckTSIDByName failed unexpectedly: %w", err)
|
||||
}
|
||||
ch <- nil
|
||||
}()
|
||||
})
|
||||
}
|
||||
deadlineCh := time.After(30 * time.Second)
|
||||
for range cap(ch) {
|
||||
select {
|
||||
case err := <-ch:
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
case <-deadlineCh:
|
||||
t.Fatalf("timeout")
|
||||
wg.Wait()
|
||||
for i, err := range errs {
|
||||
if err != nil {
|
||||
t.Errorf("[worker %d] %s", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s.tb.PutPartition(ptw)
|
||||
s.MustClose()
|
||||
fs.MustRemoveDir(path)
|
||||
})
|
||||
for _, concurrency := range []int{1, 4} {
|
||||
for _, disablePerDayIndex := range []bool{false, true} {
|
||||
name := fmt.Sprintf("concurrency=%d/disablePerDayIndex=%t", concurrency, disablePerDayIndex)
|
||||
t.Run(name, func(t *testing.T) {
|
||||
f(t, concurrency, disablePerDayIndex)
|
||||
// Repeat the same test on non-empty reopened storage.
|
||||
f(t, concurrency, disablePerDayIndex)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testIndexDBGetOrCreateTSIDByName(db *indexDB, metricGroups int, timestamp int64) ([]MetricName, []TSID, error) {
|
||||
func testIndexDBGetOrCreateTSIDByName(db *indexDB, metricGroups int, date uint64) ([]MetricName, []TSID, error) {
|
||||
r := rand.New(rand.NewSource(1))
|
||||
// Create tsids.
|
||||
var mns []MetricName
|
||||
@@ -564,8 +551,6 @@ func testIndexDBGetOrCreateTSIDByName(db *indexDB, metricGroups int, timestamp i
|
||||
|
||||
is := db.getIndexSearch(noDeadline)
|
||||
|
||||
date := uint64(timestamp) / msecPerDay
|
||||
|
||||
var metricNameBuf []byte
|
||||
for i := range 401 {
|
||||
var mn MetricName
|
||||
@@ -602,7 +587,7 @@ func testIndexDBGetOrCreateTSIDByName(db *indexDB, metricGroups int, timestamp i
|
||||
return mns, tsids, nil
|
||||
}
|
||||
|
||||
func testIndexDBCheckTSIDByName(db *indexDB, mns []MetricName, tsids []TSID, timestamp int64, isConcurrent bool) error {
|
||||
func testIndexDBCheckTSIDByName(db *indexDB, mns []MetricName, tsids []TSID, date uint64, tr TimeRange, isConcurrent bool) error {
|
||||
timeseriesCounters := make(map[uint64]bool)
|
||||
var tsidLocal TSID
|
||||
var metricNameCopy []byte
|
||||
@@ -618,7 +603,7 @@ func testIndexDBCheckTSIDByName(db *indexDB, mns []MetricName, tsids []TSID, tim
|
||||
metricName := mn.Marshal(nil)
|
||||
|
||||
is := db.getIndexSearch(noDeadline)
|
||||
if !is.getTSIDByMetricName(&tsidLocal, metricName, uint64(timestamp)/msecPerDay) {
|
||||
if !is.getTSIDByMetricName(&tsidLocal, metricName, date) {
|
||||
return fmt.Errorf("cannot obtain tsid #%d for mn %s", i, mn)
|
||||
}
|
||||
db.putIndexSearch(is)
|
||||
@@ -652,7 +637,7 @@ func testIndexDBCheckTSIDByName(db *indexDB, mns []MetricName, tsids []TSID, tim
|
||||
}
|
||||
|
||||
// Test SearchLabelValues
|
||||
lvs, err := db.SearchLabelValues(nil, "__name__", nil, TimeRange{}, 1e5, 1e9, noDeadline)
|
||||
lvs, err := db.SearchLabelValues(nil, "__name__", nil, tr, 1e5, 1e9, noDeadline)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error in SearchLabelValues(labelName=%q): %w", "__name__", err)
|
||||
}
|
||||
@@ -661,7 +646,7 @@ func testIndexDBCheckTSIDByName(db *indexDB, mns []MetricName, tsids []TSID, tim
|
||||
}
|
||||
for i := range mn.Tags {
|
||||
tag := &mn.Tags[i]
|
||||
lvs, err := db.SearchLabelValues(nil, string(tag.Key), nil, TimeRange{}, 1e5, 1e9, noDeadline)
|
||||
lvs, err := db.SearchLabelValues(nil, string(tag.Key), nil, tr, 1e5, 1e9, noDeadline)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error in SearchLabelValues(labelName=%q): %w", tag.Key, err)
|
||||
}
|
||||
@@ -672,10 +657,10 @@ func testIndexDBCheckTSIDByName(db *indexDB, mns []MetricName, tsids []TSID, tim
|
||||
}
|
||||
}
|
||||
|
||||
// Test SearchLabelNames (empty filters, global time range)
|
||||
lns, err := db.SearchLabelNames(nil, nil, TimeRange{}, 1e5, 1e9, noDeadline)
|
||||
// Test SearchLabelNames (empty filter)
|
||||
lns, err := db.SearchLabelNames(nil, nil, tr, 1e5, 1e9, noDeadline)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error in SearchLabelNames(empty filter, global time range): %w", err)
|
||||
return fmt.Errorf("error in SearchLabelNames(empty filter): %w", err)
|
||||
}
|
||||
if _, ok := lns["__name__"]; !ok {
|
||||
return fmt.Errorf("cannot find __name__ in %q", lns)
|
||||
@@ -700,10 +685,6 @@ func testIndexDBCheckTSIDByName(db *indexDB, mns []MetricName, tsids []TSID, tim
|
||||
}
|
||||
|
||||
// Try tag filters.
|
||||
tr := TimeRange{
|
||||
MinTimestamp: timestamp - msecPerDay,
|
||||
MaxTimestamp: timestamp + msecPerDay,
|
||||
}
|
||||
for i := range mns {
|
||||
mn := &mns[i]
|
||||
tsid := &tsids[i]
|
||||
|
||||
20
vendor/cloud.google.com/go/auth/CHANGES.md
generated
vendored
20
vendor/cloud.google.com/go/auth/CHANGES.md
generated
vendored
@@ -1,5 +1,25 @@
|
||||
# Changes
|
||||
|
||||
## [0.22.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.21.0...auth/v0.22.0) (2026-07-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** Populate http.response.status_code in http transport ([#20053](https://github.com/googleapis/google-cloud-go/issues/20053)) ([bff5d7c](https://github.com/googleapis/google-cloud-go/commit/bff5d7c9bc7f8ea8c0059f7d8e2d4294ba92c5bc))
|
||||
|
||||
## [0.21.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.20.0...auth/v0.21.0) (2026-07-07)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** Implement updated design for regional access boundary ([#13417](https://github.com/googleapis/google-cloud-go/issues/13417)) ([fadb6c7](https://github.com/googleapis/google-cloud-go/commit/fadb6c764dbe869fca736d9f0446b5010f21d2f2))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** Avoid double impersonation in idtoken and clarify docs ([#14474](https://github.com/googleapis/google-cloud-go/issues/14474)) ([995bfc3](https://github.com/googleapis/google-cloud-go/commit/995bfc36199ba6ae1c88803c71f39a0b19fc8c0f)), closes [#11105](https://github.com/googleapis/google-cloud-go/issues/11105)
|
||||
* **auth:** Correct go min version ([#20094](https://github.com/googleapis/google-cloud-go/issues/20094)) ([6bb4358](https://github.com/googleapis/google-cloud-go/commit/6bb4358dca69b803e52eb2af882fa5afc24d54e2))
|
||||
|
||||
## [0.20.0](https://github.com/googleapis/google-cloud-go/releases/tag/auth%2Fv0.20.0) (2026-04-06)
|
||||
|
||||
## [0.19.0](https://github.com/googleapis/google-cloud-go/releases/tag/auth%2Fv0.19.0) (2026-03-23)
|
||||
|
||||
16
vendor/cloud.google.com/go/auth/credentials/detect.go
generated
vendored
16
vendor/cloud.google.com/go/auth/credentials/detect.go
generated
vendored
@@ -27,7 +27,7 @@ import (
|
||||
"cloud.google.com/go/auth"
|
||||
"cloud.google.com/go/auth/internal"
|
||||
"cloud.google.com/go/auth/internal/credsfile"
|
||||
"cloud.google.com/go/auth/internal/trustboundary"
|
||||
"cloud.google.com/go/auth/internal/regionalaccessboundary"
|
||||
"cloud.google.com/go/compute/metadata"
|
||||
"github.com/googleapis/gax-go/v2/internallog"
|
||||
)
|
||||
@@ -138,11 +138,15 @@ func OnGCE() bool {
|
||||
// Google APIs can compromise the security of your systems and data. For
|
||||
// more information, refer to [Validate credential configurations from
|
||||
// external sources](https://cloud.google.com/docs/authentication/external/externally-sourced-credentials).
|
||||
//
|
||||
// Note: If the detected credential configuration file contains a
|
||||
// `service_account_impersonation_url` field, the returned credentials will
|
||||
// yield tokens that are already impersonated to that target service account.
|
||||
func DetectDefault(opts *DetectOptions) (*auth.Credentials, error) {
|
||||
if err := opts.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
trustBoundaryEnabled, err := trustboundary.IsEnabled()
|
||||
regionalAccessBoundaryEnabled, err := regionalaccessboundary.IsEnabled()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -175,12 +179,12 @@ func DetectDefault(opts *DetectOptions) (*auth.Credentials, error) {
|
||||
}
|
||||
|
||||
tp := computeTokenProvider(opts, metadataClient)
|
||||
if trustBoundaryEnabled {
|
||||
gceConfigProvider := trustboundary.NewGCEConfigProvider(gceUniverseDomainProvider)
|
||||
if regionalAccessBoundaryEnabled {
|
||||
gceConfigProvider := regionalaccessboundary.NewGCEConfigProvider(gceUniverseDomainProvider)
|
||||
var err error
|
||||
tp, err = trustboundary.NewProvider(opts.client(), gceConfigProvider, opts.logger(), tp)
|
||||
tp, err = regionalaccessboundary.NewProvider(opts.client(), gceConfigProvider, opts.logger(), tp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("credentials: failed to initialize GCE trust boundary provider: %w", err)
|
||||
return nil, fmt.Errorf("credentials: failed to initialize GCE Regional Access Boundary provider: %w", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
46
vendor/cloud.google.com/go/auth/credentials/filetypes.go
generated
vendored
46
vendor/cloud.google.com/go/auth/credentials/filetypes.go
generated
vendored
@@ -25,7 +25,7 @@ import (
|
||||
"cloud.google.com/go/auth/credentials/internal/impersonate"
|
||||
internalauth "cloud.google.com/go/auth/internal"
|
||||
"cloud.google.com/go/auth/internal/credsfile"
|
||||
"cloud.google.com/go/auth/internal/trustboundary"
|
||||
"cloud.google.com/go/auth/internal/regionalaccessboundary"
|
||||
)
|
||||
|
||||
const cloudPlatformScope = "https://www.googleapis.com/auth/cloud-platform"
|
||||
@@ -159,15 +159,15 @@ func handleServiceAccount(f *credsfile.ServiceAccountFile, opts *DetectOptions)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
trustBoundaryEnabled, err := trustboundary.IsEnabled()
|
||||
regionalAccessBoundaryEnabled, err := regionalaccessboundary.IsEnabled()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !trustBoundaryEnabled {
|
||||
if !regionalAccessBoundaryEnabled {
|
||||
return tp, nil
|
||||
}
|
||||
saConfig := trustboundary.NewServiceAccountConfigProvider(opts2LO.Email, opts2LO.UniverseDomain)
|
||||
return trustboundary.NewProvider(opts.client(), saConfig, opts.logger(), tp)
|
||||
saConfig := regionalaccessboundary.NewServiceAccountConfigProvider(opts2LO.Email, opts2LO.UniverseDomain)
|
||||
return regionalaccessboundary.NewProvider(opts.client(), saConfig, opts.logger(), tp)
|
||||
}
|
||||
|
||||
func handleUserCredential(f *credsfile.UserCredentialsFile, opts *DetectOptions) (auth.TokenProvider, error) {
|
||||
@@ -210,35 +210,35 @@ func handleExternalAccount(f *credsfile.ExternalAccountFile, opts *DetectOptions
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
trustBoundaryEnabled, err := trustboundary.IsEnabled()
|
||||
regionalAccessBoundaryEnabled, err := regionalaccessboundary.IsEnabled()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !trustBoundaryEnabled {
|
||||
if !regionalAccessBoundaryEnabled {
|
||||
return tp, nil
|
||||
}
|
||||
|
||||
ud := resolveUniverseDomain(opts.UniverseDomain, f.UniverseDomain)
|
||||
var configProvider trustboundary.ConfigProvider
|
||||
var configProvider regionalaccessboundary.ConfigProvider
|
||||
|
||||
if f.ServiceAccountImpersonationURL == "" {
|
||||
// No impersonation, this is a direct external account credential.
|
||||
// The trust boundary is based on the workload/workforce pool.
|
||||
// The Regional Access Boundary is based on the workload/workforce pool.
|
||||
var err error
|
||||
configProvider, err = trustboundary.NewExternalAccountConfigProvider(f.Audience, ud)
|
||||
configProvider, err = regionalaccessboundary.NewExternalAccountConfigProvider(f.Audience, ud)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// Impersonation is used. The trust boundary is based on the target service account.
|
||||
// Impersonation is used. The Regional Access Boundary is based on the target service account.
|
||||
targetSAEmail, err := impersonate.ExtractServiceAccountEmail(f.ServiceAccountImpersonationURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("credentials: could not extract target service account email for trust boundary: %w", err)
|
||||
return nil, fmt.Errorf("credentials: could not extract target service account email for Regional Access Boundary: %w", err)
|
||||
}
|
||||
configProvider = trustboundary.NewServiceAccountConfigProvider(targetSAEmail, ud)
|
||||
configProvider = regionalaccessboundary.NewServiceAccountConfigProvider(targetSAEmail, ud)
|
||||
}
|
||||
|
||||
return trustboundary.NewProvider(opts.client(), configProvider, opts.logger(), tp)
|
||||
return regionalaccessboundary.NewProvider(opts.client(), configProvider, opts.logger(), tp)
|
||||
}
|
||||
|
||||
func handleExternalAccountAuthorizedUser(f *credsfile.ExternalAccountAuthorizedUserFile, opts *DetectOptions) (auth.TokenProvider, error) {
|
||||
@@ -257,20 +257,20 @@ func handleExternalAccountAuthorizedUser(f *credsfile.ExternalAccountAuthorizedU
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
trustBoundaryEnabled, err := trustboundary.IsEnabled()
|
||||
regionalAccessBoundaryEnabled, err := regionalaccessboundary.IsEnabled()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !trustBoundaryEnabled {
|
||||
if !regionalAccessBoundaryEnabled {
|
||||
return tp, nil
|
||||
}
|
||||
|
||||
ud := resolveUniverseDomain(opts.UniverseDomain, f.UniverseDomain)
|
||||
configProvider, err := trustboundary.NewExternalAccountConfigProvider(f.Audience, ud)
|
||||
configProvider, err := regionalaccessboundary.NewExternalAccountConfigProvider(f.Audience, ud)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return trustboundary.NewProvider(opts.client(), configProvider, opts.logger(), tp)
|
||||
return regionalaccessboundary.NewProvider(opts.client(), configProvider, opts.logger(), tp)
|
||||
}
|
||||
|
||||
func handleImpersonatedServiceAccount(f *credsfile.ImpersonatedServiceAccountFile, opts *DetectOptions) (auth.TokenProvider, error) {
|
||||
@@ -306,19 +306,19 @@ func handleImpersonatedServiceAccount(f *credsfile.ImpersonatedServiceAccountFil
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
trustBoundaryEnabled, err := trustboundary.IsEnabled()
|
||||
regionalAccessBoundaryEnabled, err := regionalaccessboundary.IsEnabled()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !trustBoundaryEnabled {
|
||||
if !regionalAccessBoundaryEnabled {
|
||||
return tp, nil
|
||||
}
|
||||
targetSAEmail, err := impersonate.ExtractServiceAccountEmail(f.ServiceAccountImpersonationURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("credentials: could not extract target service account email for trust boundary: %w", err)
|
||||
return nil, fmt.Errorf("credentials: could not extract target service account email for Regional Access Boundary: %w", err)
|
||||
}
|
||||
targetSAConfig := trustboundary.NewServiceAccountConfigProvider(targetSAEmail, ud)
|
||||
return trustboundary.NewProvider(opts.client(), targetSAConfig, opts.logger(), tp)
|
||||
targetSAConfig := regionalaccessboundary.NewServiceAccountConfigProvider(targetSAEmail, ud)
|
||||
return regionalaccessboundary.NewProvider(opts.client(), targetSAConfig, opts.logger(), tp)
|
||||
}
|
||||
func handleGDCHServiceAccount(f *credsfile.GDCHServiceAccountFile, opts *DetectOptions) (auth.TokenProvider, error) {
|
||||
return gdch.NewTokenProvider(f, &gdch.Options{
|
||||
|
||||
4
vendor/cloud.google.com/go/auth/grpctransport/directpath.go
generated
vendored
4
vendor/cloud.google.com/go/auth/grpctransport/directpath.go
generated
vendored
@@ -120,7 +120,7 @@ func configureDirectPath(grpcOpts []grpc.DialOption, opts *Options, endpoint str
|
||||
})
|
||||
if isDirectPathEnabled(endpoint, opts) && compute.OnComputeEngine() && isTokenProviderDirectPathCompatible(creds, opts) {
|
||||
// Overwrite all of the previously specific DialOptions, DirectPath uses its own set of credentials and certificates.
|
||||
defaultCredetialsOptions := grpcgoogle.DefaultCredentialsOptions{PerRPCCreds: &grpcCredentialsProvider{creds: creds}}
|
||||
defaultCredetialsOptions := grpcgoogle.DefaultCredentialsOptions{PerRPCCreds: &grpcCredentialsProvider{creds: creds, endpoint: endpoint}}
|
||||
if isDirectPathBoundTokenEnabled(opts.InternalOptions) && isTokenProviderComputeEngine(creds) {
|
||||
optsClone := opts.resolveDetectOptions()
|
||||
optsClone.TokenBindingType = credentials.ALTSHardBinding
|
||||
@@ -128,7 +128,7 @@ func configureDirectPath(grpcOpts []grpc.DialOption, opts *Options, endpoint str
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
defaultCredetialsOptions.ALTSPerRPCCreds = &grpcCredentialsProvider{creds: altsCreds}
|
||||
defaultCredetialsOptions.ALTSPerRPCCreds = &grpcCredentialsProvider{creds: altsCreds, endpoint: endpoint}
|
||||
}
|
||||
grpcOpts = []grpc.DialOption{
|
||||
grpc.WithCredentialsBundle(grpcgoogle.NewDefaultCredentialsWithOptions(defaultCredetialsOptions))}
|
||||
|
||||
4
vendor/cloud.google.com/go/auth/grpctransport/grpctransport.go
generated
vendored
4
vendor/cloud.google.com/go/auth/grpctransport/grpctransport.go
generated
vendored
@@ -358,6 +358,7 @@ func dial(ctx context.Context, secure bool, opts *Options) (*grpc.ClientConn, er
|
||||
creds: creds,
|
||||
metadata: metadata,
|
||||
clientUniverseDomain: opts.UniverseDomain,
|
||||
endpoint: transportCreds.Endpoint,
|
||||
}),
|
||||
)
|
||||
// Attempt Direct Path
|
||||
@@ -405,6 +406,7 @@ type grpcCredentialsProvider struct {
|
||||
// Additional metadata attached as headers.
|
||||
metadata map[string]string
|
||||
clientUniverseDomain string
|
||||
endpoint string
|
||||
}
|
||||
|
||||
// getClientUniverseDomain returns the default service domain for a given Cloud
|
||||
@@ -447,7 +449,7 @@ func (c *grpcCredentialsProvider) GetRequestMetadata(ctx context.Context, uri ..
|
||||
}
|
||||
}
|
||||
metadata := make(map[string]string, len(c.metadata)+1)
|
||||
headers.SetAuthMetadata(token, metadata)
|
||||
headers.SetAuthMetadata(ctx, token, c.endpoint, metadata)
|
||||
for k, v := range c.metadata {
|
||||
metadata[k] = v
|
||||
}
|
||||
|
||||
5
vendor/cloud.google.com/go/auth/httptransport/transport.go
generated
vendored
5
vendor/cloud.google.com/go/auth/httptransport/transport.go
generated
vendored
@@ -284,6 +284,11 @@ func (t *otelAttributeTransport) RoundTrip(req *http.Request) (*http.Response, e
|
||||
}
|
||||
|
||||
resp, err := t.base.RoundTrip(req)
|
||||
if gax.IsFeatureEnabled("METRICS") {
|
||||
if data != nil && resp != nil {
|
||||
data.SetHTTPStatusCode(resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
var logger *slog.Logger
|
||||
if gax.IsFeatureEnabled("LOGGING") {
|
||||
|
||||
47
vendor/cloud.google.com/go/auth/internal/internal.go
generated
vendored
47
vendor/cloud.google.com/go/auth/internal/internal.go
generated
vendored
@@ -48,11 +48,8 @@ const (
|
||||
// Universe domain is the default service domain for a given Cloud universe.
|
||||
DefaultUniverseDomain = "googleapis.com"
|
||||
|
||||
// TrustBoundaryNoOp is a constant indicating no trust boundary is enforced.
|
||||
TrustBoundaryNoOp = "0x0"
|
||||
|
||||
// TrustBoundaryDataKey is the key used to store trust boundary data in a token's metadata.
|
||||
TrustBoundaryDataKey = "google.auth.trust_boundary_data"
|
||||
// RegionalAccessBoundaryDataKey is the key used to store regional access boundary data in a token's metadata.
|
||||
RegionalAccessBoundaryDataKey = "google.auth.regional_access_boundary_data"
|
||||
)
|
||||
|
||||
type clonableTransport interface {
|
||||
@@ -231,55 +228,35 @@ func FormatIAMServiceAccountResource(name string) string {
|
||||
return fmt.Sprintf("projects/-/serviceAccounts/%s", name)
|
||||
}
|
||||
|
||||
// TrustBoundaryData represents the trust boundary data associated with a token.
|
||||
// RegionalAccessBoundaryData represents the regional access boundary data associated with a token.
|
||||
// It contains information about the regions or environments where the token is valid.
|
||||
type TrustBoundaryData struct {
|
||||
type RegionalAccessBoundaryData struct {
|
||||
// Locations is the list of locations that the token is allowed to be used in.
|
||||
Locations []string
|
||||
// EncodedLocations represents the locations in an encoded format.
|
||||
EncodedLocations string
|
||||
}
|
||||
|
||||
// NewTrustBoundaryData returns a new TrustBoundaryData with the specified locations and encoded locations.
|
||||
func NewTrustBoundaryData(locations []string, encodedLocations string) *TrustBoundaryData {
|
||||
// NewRegionalAccessBoundaryData returns a new RegionalAccessBoundaryData with the specified locations and encoded locations.
|
||||
func NewRegionalAccessBoundaryData(locations []string, encodedLocations string) *RegionalAccessBoundaryData {
|
||||
// Ensure consistency by treating a nil slice as an empty slice.
|
||||
if locations == nil {
|
||||
locations = []string{}
|
||||
}
|
||||
locationsCopy := make([]string, len(locations))
|
||||
copy(locationsCopy, locations)
|
||||
return &TrustBoundaryData{
|
||||
return &RegionalAccessBoundaryData{
|
||||
Locations: locationsCopy,
|
||||
EncodedLocations: encodedLocations,
|
||||
}
|
||||
}
|
||||
|
||||
// NewNoOpTrustBoundaryData returns a new TrustBoundaryData with no restrictions.
|
||||
func NewNoOpTrustBoundaryData() *TrustBoundaryData {
|
||||
return &TrustBoundaryData{
|
||||
Locations: []string{},
|
||||
EncodedLocations: TrustBoundaryNoOp,
|
||||
}
|
||||
}
|
||||
|
||||
// TrustBoundaryHeader returns the value for the x-allowed-locations header and a bool
|
||||
// indicating if the header should be set. The return values are structured to
|
||||
// handle three distinct states required by the backend:
|
||||
// 1. Header not set: (value="", present=false) -> data is empty.
|
||||
// 2. Header set to an empty string: (value="", present=true) -> data is a no-op.
|
||||
// 3. Header set to a value: (value="...", present=true) -> data has locations.
|
||||
func (t TrustBoundaryData) TrustBoundaryHeader() (value string, present bool) {
|
||||
// RegionalAccessBoundaryHeader returns the value for the x-allowed-locations header and a bool
|
||||
// indicating if the header should be set. If EncodedLocations is empty, the header
|
||||
// should not be present. Otherwise, it should be present with the value of EncodedLocations.
|
||||
func (t RegionalAccessBoundaryData) RegionalAccessBoundaryHeader() (value string, present bool) {
|
||||
if t.EncodedLocations == "" {
|
||||
// If the data is empty, the header should not be present.
|
||||
return "", false
|
||||
}
|
||||
|
||||
// If data is not empty, the header should always be present.
|
||||
present = true
|
||||
value = ""
|
||||
if t.EncodedLocations != TrustBoundaryNoOp {
|
||||
value = t.EncodedLocations
|
||||
}
|
||||
// For a no-op, the backend requires an empty string.
|
||||
return value, present
|
||||
return t.EncodedLocations, true
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package trustboundary
|
||||
package regionalaccessboundary
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -21,13 +21,13 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
workloadAllowedLocationsEndpoint = "https://iamcredentials.%s/v1/projects/%s/locations/global/workloadIdentityPools/%s/allowedLocations"
|
||||
workforceAllowedLocationsEndpoint = "https://iamcredentials.%s/v1/locations/global/workforcePools/%s/allowedLocations"
|
||||
workloadAllowedLocationsEndpoint = "https://iamcredentials.googleapis.com/v1/projects/%s/locations/global/workloadIdentityPools/%s/allowedLocations"
|
||||
workforceAllowedLocationsEndpoint = "https://iamcredentials.googleapis.com/v1/locations/global/workforcePools/%s/allowedLocations"
|
||||
)
|
||||
|
||||
var (
|
||||
workforceAudiencePattern = regexp.MustCompile(`//iam\.([^/]+)/locations/global/workforcePools/([^/]+)`)
|
||||
workloadAudiencePattern = regexp.MustCompile(`//iam\.([^/]+)/projects/([^/]+)/locations/global/workloadIdentityPools/([^/]+)`)
|
||||
workforceAudiencePattern = regexp.MustCompile(`^//iam\.([^/]+)/locations/([^/]+)/workforcePools/([^/]+)/providers/[^/]+$`)
|
||||
workloadAudiencePattern = regexp.MustCompile(`^//iam\.([^/]+)/projects/([^/]+)/locations/([^/]+)/workloadIdentityPools/([^/]+)/providers/[^/]+$`)
|
||||
)
|
||||
|
||||
// NewExternalAccountConfigProvider creates a new ConfigProvider for external accounts.
|
||||
@@ -36,19 +36,19 @@ func NewExternalAccountConfigProvider(audience, inputUniverseDomain string) (Con
|
||||
var isWorkload bool
|
||||
|
||||
matches := workloadAudiencePattern.FindStringSubmatch(audience)
|
||||
if len(matches) == 4 { // Expecting full match, domain, projectNumber, poolID
|
||||
if len(matches) == 5 { // Expecting full match, domain, projectNumber, location, poolID
|
||||
audienceDomain = matches[1]
|
||||
projectNumber = matches[2]
|
||||
poolID = matches[3]
|
||||
poolID = matches[4]
|
||||
isWorkload = true
|
||||
} else {
|
||||
matches = workforceAudiencePattern.FindStringSubmatch(audience)
|
||||
if len(matches) == 3 { // Expecting full match, domain, poolID
|
||||
if len(matches) == 4 { // Expecting full match, domain, location, poolID
|
||||
audienceDomain = matches[1]
|
||||
poolID = matches[2]
|
||||
poolID = matches[3]
|
||||
isWorkload = false
|
||||
} else {
|
||||
return nil, fmt.Errorf("trustboundary: unknown audience format: %q", audience)
|
||||
return nil, fmt.Errorf("regionalaccessboundary: unknown audience format: %q", audience)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ func NewExternalAccountConfigProvider(audience, inputUniverseDomain string) (Con
|
||||
if effectiveUniverseDomain == "" {
|
||||
effectiveUniverseDomain = audienceDomain
|
||||
} else if audienceDomain != "" && effectiveUniverseDomain != audienceDomain {
|
||||
return nil, fmt.Errorf("trustboundary: provided universe domain (%q) does not match domain in audience (%q)", inputUniverseDomain, audienceDomain)
|
||||
return nil, fmt.Errorf("regionalaccessboundary: provided universe domain (%q) does not match domain in audience (%q)", inputUniverseDomain, audienceDomain)
|
||||
}
|
||||
|
||||
if isWorkload {
|
||||
@@ -77,8 +77,8 @@ type workforcePoolConfigProvider struct {
|
||||
universeDomain string
|
||||
}
|
||||
|
||||
func (p *workforcePoolConfigProvider) GetTrustBoundaryEndpoint(ctx context.Context) (string, error) {
|
||||
return fmt.Sprintf(workforceAllowedLocationsEndpoint, p.universeDomain, p.poolID), nil
|
||||
func (p *workforcePoolConfigProvider) GetRegionalAccessBoundaryEndpoint(ctx context.Context) (string, error) {
|
||||
return fmt.Sprintf(workforceAllowedLocationsEndpoint, p.poolID), nil
|
||||
}
|
||||
|
||||
func (p *workforcePoolConfigProvider) GetUniverseDomain(ctx context.Context) (string, error) {
|
||||
@@ -91,8 +91,8 @@ type workloadIdentityPoolConfigProvider struct {
|
||||
universeDomain string
|
||||
}
|
||||
|
||||
func (p *workloadIdentityPoolConfigProvider) GetTrustBoundaryEndpoint(ctx context.Context) (string, error) {
|
||||
return fmt.Sprintf(workloadAllowedLocationsEndpoint, p.universeDomain, p.projectNumber, p.poolID), nil
|
||||
func (p *workloadIdentityPoolConfigProvider) GetRegionalAccessBoundaryEndpoint(ctx context.Context) (string, error) {
|
||||
return fmt.Sprintf(workloadAllowedLocationsEndpoint, p.projectNumber, p.poolID), nil
|
||||
}
|
||||
|
||||
func (p *workloadIdentityPoolConfigProvider) GetUniverseDomain(ctx context.Context) (string, error) {
|
||||
503
vendor/cloud.google.com/go/auth/internal/regionalaccessboundary/regional_access_boundary.go
generated
vendored
Normal file
503
vendor/cloud.google.com/go/auth/internal/regionalaccessboundary/regional_access_boundary.go
generated
vendored
Normal file
@@ -0,0 +1,503 @@
|
||||
// Copyright 2026 Google LLC
|
||||
//
|
||||
// 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 regionalaccessboundary
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"maps"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/auth"
|
||||
"cloud.google.com/go/auth/internal"
|
||||
"cloud.google.com/go/auth/internal/retry"
|
||||
"github.com/googleapis/gax-go/v2/internallog"
|
||||
)
|
||||
|
||||
// ProviderKey is the key to fetch the DataProvider from Token Metadata.
|
||||
const ProviderKey = "regionalaccessboundary.ProviderKey"
|
||||
|
||||
const (
|
||||
// serviceAccountAllowedLocationsEndpoint is the URL for fetching allowed locations for a given service account email.
|
||||
serviceAccountAllowedLocationsEndpoint = "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/%s/allowedLocations"
|
||||
|
||||
// cacheTTL is the duration cached RAB data remains valid before hard expiry.
|
||||
cacheTTL = 6 * time.Hour
|
||||
// cacheSoftExpiry is the threshold before hard expiry where a background refresh is triggered.
|
||||
cacheSoftExpiry = 1 * time.Hour
|
||||
// baseCooldownDuration is the initial delay after a failed background fetch.
|
||||
baseCooldownDuration = 15 * time.Minute
|
||||
)
|
||||
|
||||
var (
|
||||
// retryOptions configures the retry behavior for Regional Access Boundary lookups.
|
||||
retryOptions = &retry.Options{
|
||||
Initial: 1 * time.Second,
|
||||
Max: 60 * time.Second,
|
||||
Multiplier: 2.0,
|
||||
MaxAttempts: 6,
|
||||
}
|
||||
)
|
||||
|
||||
// isEnabled wraps isRegionalAccessBoundaryEnabled with sync.OnceValues to ensure it's
|
||||
// called only once.
|
||||
var isEnabled = sync.OnceValues(isRegionalAccessBoundaryEnabled)
|
||||
|
||||
// IsEnabled returns if the Regional Access Boundary feature is enabled and an error if
|
||||
// the configuration is invalid. The underlying check is performed only once.
|
||||
func IsEnabled() (bool, error) {
|
||||
return isEnabled()
|
||||
}
|
||||
|
||||
// isRegionalAccessBoundaryEnabled checks if the Regional Access Boundary feature
|
||||
// is enabled via the GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED environment variable.
|
||||
//
|
||||
// If the environment variable is not set or empty, it is considered false.
|
||||
//
|
||||
// The environment variable is interpreted as a boolean with the following
|
||||
// (case-insensitive) rules:
|
||||
// - "true", "1" are considered true.
|
||||
// - All other values (including "false", "0", or invalid strings) are considered false.
|
||||
func isRegionalAccessBoundaryEnabled() (bool, error) {
|
||||
val := strings.ToLower(os.Getenv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED"))
|
||||
return val == "true" || val == "1", nil
|
||||
}
|
||||
|
||||
// ConfigProvider provides specific configuration for Regional Access Boundary lookups.
|
||||
type ConfigProvider interface {
|
||||
// GetRegionalAccessBoundaryEndpoint returns the endpoint URL for the Regional Access Boundary lookup.
|
||||
GetRegionalAccessBoundaryEndpoint(ctx context.Context) (url string, err error)
|
||||
// GetUniverseDomain returns the universe domain associated with the credential.
|
||||
// It may return an error if the universe domain cannot be determined.
|
||||
GetUniverseDomain(ctx context.Context) (string, error)
|
||||
}
|
||||
|
||||
// AllowedLocationsResponse is the structure of the response from the Regional Access Boundary API.
|
||||
type AllowedLocationsResponse struct {
|
||||
// Locations is the list of allowed locations.
|
||||
Locations []string `json:"locations"`
|
||||
// EncodedLocations is the encoded representation of the allowed locations.
|
||||
EncodedLocations string `json:"encodedLocations"`
|
||||
}
|
||||
|
||||
// fetchRegionalAccessBoundaryData fetches the Regional Access Boundary data from the API.
|
||||
func fetchRegionalAccessBoundaryData(ctx context.Context, client *http.Client, url string, token *auth.Token, logger *slog.Logger) (*internal.RegionalAccessBoundaryData, error) {
|
||||
if logger == nil {
|
||||
logger = slog.New(slog.NewTextHandler(io.Discard, nil))
|
||||
}
|
||||
if client == nil {
|
||||
return nil, errors.New("regionalaccessboundary: HTTP client is required")
|
||||
}
|
||||
|
||||
if url == "" {
|
||||
return nil, errors.New("regionalaccessboundary: URL cannot be empty")
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("regionalaccessboundary: failed to create Regional Access Boundary request: %w", err)
|
||||
}
|
||||
|
||||
if token == nil || token.Value == "" {
|
||||
return nil, errors.New("regionalaccessboundary: access token required for lookup API authentication")
|
||||
}
|
||||
typ := token.Type
|
||||
if typ == "" {
|
||||
typ = internal.TokenTypeBearer
|
||||
}
|
||||
req.Header.Set("Authorization", typ+" "+token.Value)
|
||||
logger.DebugContext(ctx, "Regional Access Boundary request", "request", internallog.HTTPRequest(req, nil))
|
||||
|
||||
retryer := retry.NewWithOptions(retryOptions)
|
||||
startTime := time.Now()
|
||||
var response *http.Response
|
||||
for {
|
||||
response, err = client.Do(req)
|
||||
|
||||
var statusCode int
|
||||
if response != nil {
|
||||
statusCode = response.StatusCode
|
||||
}
|
||||
pause, shouldRetry := retryer.Retry(statusCode, err)
|
||||
|
||||
// Enforce a maximum 1 minute retry window for specific server errors.
|
||||
if shouldRetry && (statusCode == 500 || statusCode == 502 || statusCode == 503 || statusCode == 504) {
|
||||
if time.Since(startTime)+pause > 1*time.Minute {
|
||||
shouldRetry = false
|
||||
}
|
||||
}
|
||||
|
||||
if !shouldRetry {
|
||||
break
|
||||
}
|
||||
|
||||
if response != nil {
|
||||
// Drain and close the body to reuse the connection
|
||||
io.Copy(io.Discard, response.Body)
|
||||
response.Body.Close()
|
||||
}
|
||||
|
||||
if err := retry.Sleep(ctx, pause); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("regionalaccessboundary: failed to fetch Regional Access Boundary: %w", err)
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("regionalaccessboundary: failed to read Regional Access Boundary response: %w", err)
|
||||
}
|
||||
|
||||
logger.DebugContext(ctx, "Regional Access Boundary response", "response", internallog.HTTPResponse(response, body))
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("regionalaccessboundary: Regional Access Boundary request failed with status: %s, body: %s", response.Status, string(body))
|
||||
}
|
||||
|
||||
apiResponse := AllowedLocationsResponse{}
|
||||
if err := json.Unmarshal(body, &apiResponse); err != nil {
|
||||
return nil, fmt.Errorf("regionalaccessboundary: failed to unmarshal Regional Access Boundary response: %w", err)
|
||||
}
|
||||
|
||||
if apiResponse.EncodedLocations == "" {
|
||||
return nil, errors.New("regionalaccessboundary: invalid API response: encodedLocations is empty")
|
||||
}
|
||||
|
||||
return internal.NewRegionalAccessBoundaryData(apiResponse.Locations, apiResponse.EncodedLocations), nil
|
||||
}
|
||||
|
||||
// DataProvider fetches and caches Regional Access Boundary Data.
|
||||
// It implements the auth.TokenProvider interface and uses a ConfigProvider
|
||||
// to get type-specific details for the lookup.
|
||||
type DataProvider struct {
|
||||
client *http.Client
|
||||
configProvider ConfigProvider
|
||||
logger *slog.Logger
|
||||
base auth.TokenProvider
|
||||
|
||||
mu sync.RWMutex
|
||||
data *internal.RegionalAccessBoundaryData
|
||||
dataExpiry time.Time
|
||||
isFetching bool
|
||||
cooldownExpiry time.Time
|
||||
cooldownDuration time.Duration // tracks the current cooldown duration for exponential backoff
|
||||
}
|
||||
|
||||
// NewProvider wraps the provided base [auth.TokenProvider] and returns a new
|
||||
// provider that fetches and caches the Regional Access Boundary data. It uses
|
||||
// the provided HTTP client and configProvider.
|
||||
func NewProvider(client *http.Client, configProvider ConfigProvider, logger *slog.Logger, base auth.TokenProvider) (*DataProvider, error) {
|
||||
if client == nil {
|
||||
return nil, errors.New("regionalaccessboundary: HTTP client cannot be nil for DataProvider")
|
||||
}
|
||||
if configProvider == nil {
|
||||
return nil, errors.New("regionalaccessboundary: ConfigProvider cannot be nil for DataProvider")
|
||||
}
|
||||
p := &DataProvider{
|
||||
client: client,
|
||||
configProvider: configProvider,
|
||||
logger: internallog.New(logger),
|
||||
base: base,
|
||||
cooldownDuration: 15 * time.Minute,
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// Token retrieves a token from the base provider and injects the DataProvider
|
||||
// instance into its metadata.
|
||||
func (p *DataProvider) Token(ctx context.Context) (*auth.Token, error) {
|
||||
token, err := p.base.Token(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Clone the token and its metadata to avoid mutating shared/cached state.
|
||||
newToken := *token
|
||||
newToken.Metadata = maps.Clone(token.Metadata)
|
||||
if newToken.Metadata == nil {
|
||||
newToken.Metadata = make(map[string]interface{})
|
||||
}
|
||||
newToken.Metadata[ProviderKey] = p
|
||||
|
||||
return &newToken, nil
|
||||
}
|
||||
|
||||
// GetHeaderValue immediately returns a valid header if it's cached, or kicks off a background fetch
|
||||
// if it is unpopulated or expired.
|
||||
func (p *DataProvider) GetHeaderValue(ctx context.Context, reqURL string, accessToken *auth.Token) string {
|
||||
if !strings.Contains(reqURL, "://") {
|
||||
reqURL = "https://" + reqURL
|
||||
}
|
||||
if u, err := url.Parse(reqURL); err == nil {
|
||||
host := u.Host
|
||||
if host == "" && strings.HasPrefix(u.Path, "/") {
|
||||
host = strings.TrimPrefix(u.Path, "/")
|
||||
}
|
||||
if h, _, err := net.SplitHostPort(host); err == nil {
|
||||
host = h
|
||||
}
|
||||
// Skip lookup for regional endpoints.
|
||||
if host == "rep.googleapis.com" || strings.HasSuffix(host, ".rep.googleapis.com") ||
|
||||
host == "rep.sandbox.googleapis.com" || strings.HasSuffix(host, ".rep.sandbox.googleapis.com") {
|
||||
return ""
|
||||
}
|
||||
// Skip lookup for IAM and STS endpoints as they do not require RAB headers.
|
||||
if host == "iam.googleapis.com" || host == "iamcredentials.googleapis.com" ||
|
||||
host == "sts.googleapis.com" {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// Skip lookup for non-default universe domains.
|
||||
uniDomain, err := p.configProvider.GetUniverseDomain(ctx)
|
||||
if err != nil {
|
||||
p.logger.WarnContext(ctx, "regionalaccessboundary: error getting universe domain", "error", err)
|
||||
return ""
|
||||
}
|
||||
if uniDomain != "" && uniDomain != internal.DefaultUniverseDomain {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Return the cached data if present and not expired.
|
||||
p.mu.RLock()
|
||||
data := p.data
|
||||
dataExpiry := p.dataExpiry
|
||||
p.mu.RUnlock()
|
||||
|
||||
now := time.Now()
|
||||
if data != nil && now.Before(dataExpiry) {
|
||||
val, _ := data.RegionalAccessBoundaryHeader()
|
||||
|
||||
// Soft Expiry: if the cached data is within the soft expiration window,
|
||||
// initiate a non-blocking background refresh to proactively fetch new data
|
||||
// while continuing to serve the current valid cache block.
|
||||
if now.After(dataExpiry.Add(-cacheSoftExpiry)) {
|
||||
p.mu.Lock()
|
||||
if !p.isFetching && now.After(p.cooldownExpiry) {
|
||||
p.isFetching = true
|
||||
go p.fetchAsync(context.Background(), accessToken)
|
||||
}
|
||||
p.mu.Unlock()
|
||||
}
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
// Skip lookup if in cooldown or another process is already fetching.
|
||||
if p.isFetching || time.Now().Before(p.cooldownExpiry) {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Start async RAB lookup and return empty header.
|
||||
p.isFetching = true
|
||||
go p.fetchAsync(context.Background(), accessToken)
|
||||
return ""
|
||||
}
|
||||
|
||||
// fetchAsync performs the background lookup for Regional Access Boundary data.
|
||||
// It updates the provider's state based on the result (success or failure).
|
||||
func (p *DataProvider) fetchAsync(ctx context.Context, accessToken *auth.Token) {
|
||||
defer func() {
|
||||
p.mu.Lock()
|
||||
p.isFetching = false
|
||||
p.mu.Unlock()
|
||||
}()
|
||||
|
||||
url, err := p.configProvider.GetRegionalAccessBoundaryEndpoint(ctx)
|
||||
if err != nil {
|
||||
p.logger.WarnContext(ctx, "regionalaccessboundary: error getting the lookup endpoint", "error", err)
|
||||
p.handleFetchFailure(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
newData, fetchErr := fetchRegionalAccessBoundaryData(ctx, p.client, url, accessToken, p.logger)
|
||||
|
||||
if fetchErr != nil {
|
||||
p.logger.WarnContext(ctx, "regionalaccessboundary: async fetch failed", "error", fetchErr)
|
||||
p.handleFetchFailure(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
p.handleFetchSuccess(newData)
|
||||
}
|
||||
|
||||
// handleFetchSuccess updates the cache with new data and clears any existing cooldown.
|
||||
func (p *DataProvider) handleFetchSuccess(newData *internal.RegionalAccessBoundaryData) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
p.data = newData
|
||||
p.dataExpiry = time.Now().Add(cacheTTL)
|
||||
p.cooldownExpiry = time.Time{}
|
||||
p.cooldownDuration = baseCooldownDuration
|
||||
}
|
||||
|
||||
// handleFetchFailure triggers the cooldown period using exponential backoff.
|
||||
func (p *DataProvider) handleFetchFailure(ctx context.Context) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
// Add random bounded jitter (between half of the base and the full base) to prevent thundering herds
|
||||
jitter := p.cooldownDuration/2 + time.Duration(rand.Int63n(int64(p.cooldownDuration/2)))
|
||||
p.cooldownExpiry = time.Now().Add(jitter)
|
||||
|
||||
// Exponential backoff for the NEXT attempt, up to cacheTTL max (6 hours)
|
||||
nextCooldown := p.cooldownDuration * 2
|
||||
if nextCooldown > cacheTTL {
|
||||
nextCooldown = cacheTTL
|
||||
}
|
||||
p.cooldownDuration = nextCooldown
|
||||
}
|
||||
|
||||
// serviceAccountConfig holds configuration for SA Regional Access Boundary lookups.
|
||||
// It implements the ConfigProvider interface.
|
||||
type serviceAccountConfig struct {
|
||||
ServiceAccountEmail string
|
||||
UniverseDomain string
|
||||
}
|
||||
|
||||
// NewServiceAccountConfigProvider creates a new config for service accounts.
|
||||
func NewServiceAccountConfigProvider(saEmail, universeDomain string) ConfigProvider {
|
||||
return &serviceAccountConfig{
|
||||
ServiceAccountEmail: saEmail,
|
||||
UniverseDomain: universeDomain,
|
||||
}
|
||||
}
|
||||
|
||||
// GetRegionalAccessBoundaryEndpoint returns the formatted URL for fetching allowed locations
|
||||
// for the configured service account.
|
||||
func (sac *serviceAccountConfig) GetRegionalAccessBoundaryEndpoint(ctx context.Context) (url string, err error) {
|
||||
if sac.ServiceAccountEmail == "" {
|
||||
return "", errors.New("regionalaccessboundary: service account email cannot be empty for config")
|
||||
}
|
||||
return fmt.Sprintf(serviceAccountAllowedLocationsEndpoint, sac.ServiceAccountEmail), nil
|
||||
}
|
||||
|
||||
// GetUniverseDomain returns the configured universe domain, defaulting to
|
||||
// [internal.DefaultUniverseDomain] if not explicitly set.
|
||||
func (sac *serviceAccountConfig) GetUniverseDomain(ctx context.Context) (string, error) {
|
||||
if sac.UniverseDomain == "" {
|
||||
return internal.DefaultUniverseDomain, nil
|
||||
}
|
||||
return sac.UniverseDomain, nil
|
||||
}
|
||||
|
||||
// GCEConfigProvider implements ConfigProvider for GCE environments.
|
||||
// It lazily fetches and caches the necessary metadata (service account email, universe domain)
|
||||
type GCEConfigProvider struct {
|
||||
// universeDomainProvider provides the universe domain and underlying metadata client.
|
||||
universeDomainProvider *internal.ComputeUniverseDomainProvider
|
||||
|
||||
// Caching for service account email
|
||||
saMu sync.Mutex
|
||||
saEmail string
|
||||
|
||||
// Caching for universe domain
|
||||
udOnce sync.Once
|
||||
ud string
|
||||
udErr error
|
||||
}
|
||||
|
||||
// NewGCEConfigProvider creates a new GCEConfigProvider
|
||||
// which uses the provided gceUDP to interact with the GCE metadata server.
|
||||
func NewGCEConfigProvider(gceUDP *internal.ComputeUniverseDomainProvider) *GCEConfigProvider {
|
||||
// The validity of gceUDP and its internal MetadataClient will be checked
|
||||
// within the GetRegionalAccessBoundaryEndpoint and GetUniverseDomain methods.
|
||||
return &GCEConfigProvider{
|
||||
universeDomainProvider: gceUDP,
|
||||
}
|
||||
}
|
||||
|
||||
func (g *GCEConfigProvider) fetchSA(ctx context.Context) (string, error) {
|
||||
if g.universeDomainProvider == nil || g.universeDomainProvider.MetadataClient == nil {
|
||||
return "", errors.New("regionalaccessboundary: GCEConfigProvider not properly initialized (missing ComputeUniverseDomainProvider or MetadataClient)")
|
||||
}
|
||||
mdClient := g.universeDomainProvider.MetadataClient
|
||||
saEmail, err := mdClient.EmailWithContext(ctx, "default")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("regionalaccessboundary: GCE config: failed to get service account email: %w", err)
|
||||
}
|
||||
return saEmail, nil
|
||||
}
|
||||
|
||||
func (g *GCEConfigProvider) fetchUD(ctx context.Context) {
|
||||
if g.universeDomainProvider == nil || g.universeDomainProvider.MetadataClient == nil {
|
||||
g.udErr = errors.New("regionalaccessboundary: GCEConfigProvider not properly initialized (missing ComputeUniverseDomainProvider or MetadataClient)")
|
||||
return
|
||||
}
|
||||
ud, err := g.universeDomainProvider.GetProperty(ctx)
|
||||
if err != nil {
|
||||
g.udErr = fmt.Errorf("regionalaccessboundary: GCE config: failed to get universe domain: %w", err)
|
||||
return
|
||||
}
|
||||
if ud == "" {
|
||||
ud = internal.DefaultUniverseDomain
|
||||
}
|
||||
g.ud = ud
|
||||
}
|
||||
|
||||
// GetRegionalAccessBoundaryEndpoint constructs the Regional Access Boundary lookup URL for a GCE environment.
|
||||
// It uses cached service account email after the first call.
|
||||
func (g *GCEConfigProvider) GetRegionalAccessBoundaryEndpoint(ctx context.Context) (string, error) {
|
||||
// Check if we already have a cached service account email.
|
||||
g.saMu.Lock()
|
||||
if g.saEmail != "" {
|
||||
email := g.saEmail
|
||||
g.saMu.Unlock()
|
||||
return fmt.Sprintf(serviceAccountAllowedLocationsEndpoint, email), nil
|
||||
}
|
||||
g.saMu.Unlock()
|
||||
|
||||
// Fetch the email from the metadata server. We do not hold the lock
|
||||
// during this I/O operation to avoid blocking other goroutines.
|
||||
email, err := g.fetchSA(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Cache the successful result.
|
||||
g.saMu.Lock()
|
||||
g.saEmail = email
|
||||
g.saMu.Unlock()
|
||||
|
||||
return fmt.Sprintf(serviceAccountAllowedLocationsEndpoint, email), nil
|
||||
}
|
||||
|
||||
// GetUniverseDomain retrieves the universe domain from the GCE metadata server.
|
||||
// It uses a cached value after the first call.
|
||||
func (g *GCEConfigProvider) GetUniverseDomain(ctx context.Context) (string, error) {
|
||||
g.udOnce.Do(func() { g.fetchUD(ctx) })
|
||||
if g.udErr != nil {
|
||||
return "", g.udErr
|
||||
}
|
||||
return g.ud, nil
|
||||
}
|
||||
70
vendor/cloud.google.com/go/auth/internal/retry/retry.go
generated
vendored
70
vendor/cloud.google.com/go/auth/internal/retry/retry.go
generated
vendored
@@ -22,10 +22,6 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
maxRetryAttempts = 5
|
||||
)
|
||||
|
||||
var (
|
||||
syscallRetryable = func(error) bool { return false }
|
||||
)
|
||||
@@ -61,21 +57,69 @@ func Sleep(ctx context.Context, d time.Duration) error {
|
||||
|
||||
// New returns a new Retryer with the default backoff strategy.
|
||||
func New() *Retryer {
|
||||
return &Retryer{bo: &defaultBackoff{
|
||||
cur: 100 * time.Millisecond,
|
||||
max: 30 * time.Second,
|
||||
mul: 2,
|
||||
}}
|
||||
return NewWithOptions(&Options{
|
||||
Initial: 100 * time.Millisecond,
|
||||
Max: 30 * time.Second,
|
||||
Multiplier: 2,
|
||||
MaxAttempts: 5,
|
||||
})
|
||||
}
|
||||
|
||||
// Options defines the configuration for the Retryer.
|
||||
type Options struct {
|
||||
// Initial is the initial backoff duration.
|
||||
Initial time.Duration
|
||||
// Max is the maximum backoff duration for a single retry attempt.
|
||||
// It does not limit the total time of all retries.
|
||||
Max time.Duration
|
||||
// Multiplier is the factor by which the backoff duration is multiplied after each attempt.
|
||||
Multiplier float64
|
||||
// MaxAttempts is the maximum number of attempts before giving up.
|
||||
MaxAttempts int
|
||||
}
|
||||
|
||||
// NewWithOptions returns a new Retryer with the specified backoff strategy.
|
||||
// If any option is not set (zero value), it defaults to the values used in New().
|
||||
func NewWithOptions(opts *Options) *Retryer {
|
||||
initial := opts.Initial
|
||||
if initial <= 0 {
|
||||
initial = 100 * time.Millisecond
|
||||
}
|
||||
|
||||
max := opts.Max
|
||||
if max <= 0 {
|
||||
max = 30 * time.Second
|
||||
}
|
||||
|
||||
multiplier := opts.Multiplier
|
||||
if multiplier < 1.0 {
|
||||
multiplier = 2.0
|
||||
}
|
||||
|
||||
maxAttempts := opts.MaxAttempts
|
||||
if maxAttempts <= 0 {
|
||||
maxAttempts = 5
|
||||
}
|
||||
|
||||
return &Retryer{
|
||||
bo: &defaultBackoff{
|
||||
cur: initial,
|
||||
max: max,
|
||||
mul: multiplier,
|
||||
},
|
||||
maxAttempts: maxAttempts,
|
||||
}
|
||||
}
|
||||
|
||||
type backoff interface {
|
||||
Pause() time.Duration
|
||||
}
|
||||
|
||||
// Retryer is a retryer for HTTP requests.
|
||||
// Retryer handles retry logic for HTTP requests using a configurable backoff strategy.
|
||||
type Retryer struct {
|
||||
bo backoff
|
||||
attempts int
|
||||
bo backoff
|
||||
attempts int
|
||||
maxAttempts int
|
||||
}
|
||||
|
||||
// Retry determines if a request should be retried.
|
||||
@@ -87,7 +131,7 @@ func (r *Retryer) Retry(status int, err error) (time.Duration, bool) {
|
||||
if !retryOk {
|
||||
return 0, false
|
||||
}
|
||||
if r.attempts == maxRetryAttempts {
|
||||
if r.attempts == r.maxAttempts {
|
||||
return 0, false
|
||||
}
|
||||
r.attempts++
|
||||
|
||||
31
vendor/cloud.google.com/go/auth/internal/retry/retry_linux.go
generated
vendored
Normal file
31
vendor/cloud.google.com/go/auth/internal/retry/retry_linux.go
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright 2026 Google LLC
|
||||
//
|
||||
// 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.
|
||||
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package retry
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Initialize syscallRetryable to return true on transient socket-level
|
||||
// errors. These errors are specific to Linux.
|
||||
syscallRetryable = func(err error) bool {
|
||||
return errors.Is(err, syscall.ECONNRESET) || errors.Is(err, syscall.ECONNREFUSED)
|
||||
}
|
||||
}
|
||||
35
vendor/cloud.google.com/go/auth/internal/transport/headers/headers.go
generated
vendored
35
vendor/cloud.google.com/go/auth/internal/transport/headers/headers.go
generated
vendored
@@ -15,14 +15,20 @@
|
||||
package headers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"cloud.google.com/go/auth"
|
||||
"cloud.google.com/go/auth/internal"
|
||||
"cloud.google.com/go/auth/internal/regionalaccessboundary"
|
||||
)
|
||||
|
||||
// SetAuthHeader uses the provided token to set the Authorization and trust
|
||||
// boundary headers on a request. If the token.Type is empty, the type is
|
||||
type regionalAccessBoundaryProvider interface {
|
||||
GetHeaderValue(ctx context.Context, reqURL string, token *auth.Token) string
|
||||
}
|
||||
|
||||
// SetAuthHeader uses the provided token to set the Authorization and regional
|
||||
// access boundary headers on a request. If the token.Type is empty, the type is
|
||||
// assumed to be Bearer.
|
||||
func SetAuthHeader(token *auth.Token, req *http.Request) {
|
||||
typ := token.Type
|
||||
@@ -31,31 +37,26 @@ func SetAuthHeader(token *auth.Token, req *http.Request) {
|
||||
}
|
||||
req.Header.Set("Authorization", typ+" "+token.Value)
|
||||
|
||||
if headerVal, setHeader := getTrustBoundaryHeader(token); setHeader {
|
||||
req.Header.Set("x-allowed-locations", headerVal)
|
||||
if provider, ok := token.Metadata[regionalaccessboundary.ProviderKey].(regionalAccessBoundaryProvider); ok {
|
||||
if headerVal := provider.GetHeaderValue(req.Context(), req.URL.String(), token); headerVal != "" {
|
||||
req.Header.Set("x-allowed-locations", headerVal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SetAuthMetadata uses the provided token to set the Authorization and trust
|
||||
// boundary metadata. If the token.Type is empty, the type is assumed to be
|
||||
// SetAuthMetadata uses the provided token to set the Authorization and regional
|
||||
// access boundary metadata. If the token.Type is empty, the type is assumed to be
|
||||
// Bearer.
|
||||
func SetAuthMetadata(token *auth.Token, m map[string]string) {
|
||||
func SetAuthMetadata(ctx context.Context, token *auth.Token, reqURL string, m map[string]string) {
|
||||
typ := token.Type
|
||||
if typ == "" {
|
||||
typ = internal.TokenTypeBearer
|
||||
}
|
||||
m["authorization"] = typ + " " + token.Value
|
||||
|
||||
if headerVal, setHeader := getTrustBoundaryHeader(token); setHeader {
|
||||
m["x-allowed-locations"] = headerVal
|
||||
}
|
||||
}
|
||||
|
||||
func getTrustBoundaryHeader(token *auth.Token) (val string, present bool) {
|
||||
if data, ok := token.Metadata[internal.TrustBoundaryDataKey]; ok {
|
||||
if tbd, ok := data.(internal.TrustBoundaryData); ok {
|
||||
return tbd.TrustBoundaryHeader()
|
||||
if provider, ok := token.Metadata[regionalaccessboundary.ProviderKey].(regionalAccessBoundaryProvider); ok {
|
||||
if headerVal := provider.GetHeaderValue(ctx, reqURL, token); headerVal != "" {
|
||||
m["x-allowed-locations"] = headerVal
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
392
vendor/cloud.google.com/go/auth/internal/trustboundary/trust_boundary.go
generated
vendored
392
vendor/cloud.google.com/go/auth/internal/trustboundary/trust_boundary.go
generated
vendored
@@ -1,392 +0,0 @@
|
||||
// Copyright 2025 Google LLC
|
||||
//
|
||||
// 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 trustboundary
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"cloud.google.com/go/auth"
|
||||
"cloud.google.com/go/auth/internal"
|
||||
"cloud.google.com/go/auth/internal/retry"
|
||||
"cloud.google.com/go/auth/internal/transport/headers"
|
||||
"github.com/googleapis/gax-go/v2/internallog"
|
||||
)
|
||||
|
||||
const (
|
||||
// serviceAccountAllowedLocationsEndpoint is the URL for fetching allowed locations for a given service account email.
|
||||
serviceAccountAllowedLocationsEndpoint = "https://iamcredentials.%s/v1/projects/-/serviceAccounts/%s/allowedLocations"
|
||||
)
|
||||
|
||||
// isEnabled wraps isTrustBoundaryEnabled with sync.OnceValues to ensure it's
|
||||
// called only once.
|
||||
var isEnabled = sync.OnceValues(isTrustBoundaryEnabled)
|
||||
|
||||
// IsEnabled returns if the trust boundary feature is enabled and an error if
|
||||
// the configuration is invalid. The underlying check is performed only once.
|
||||
func IsEnabled() (bool, error) {
|
||||
return isEnabled()
|
||||
}
|
||||
|
||||
// isTrustBoundaryEnabled checks if the trust boundary feature is enabled via
|
||||
// GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED environment variable.
|
||||
//
|
||||
// If the environment variable is not set, it is considered false.
|
||||
//
|
||||
// The environment variable is interpreted as a boolean with the following
|
||||
// (case-insensitive) rules:
|
||||
// - "true", "1" are considered true.
|
||||
// - "false", "0" are considered false.
|
||||
//
|
||||
// Any other values will return an error.
|
||||
func isTrustBoundaryEnabled() (bool, error) {
|
||||
const envVar = "GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED"
|
||||
val, ok := os.LookupEnv(envVar)
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
val = strings.ToLower(val)
|
||||
switch val {
|
||||
case "true", "1":
|
||||
return true, nil
|
||||
case "false", "0":
|
||||
return false, nil
|
||||
default:
|
||||
return false, fmt.Errorf(`invalid value for %s: %q. Must be one of "true", "false", "1", or "0"`, envVar, val)
|
||||
}
|
||||
}
|
||||
|
||||
// ConfigProvider provides specific configuration for trust boundary lookups.
|
||||
type ConfigProvider interface {
|
||||
// GetTrustBoundaryEndpoint returns the endpoint URL for the trust boundary lookup.
|
||||
GetTrustBoundaryEndpoint(ctx context.Context) (url string, err error)
|
||||
// GetUniverseDomain returns the universe domain associated with the credential.
|
||||
// It may return an error if the universe domain cannot be determined.
|
||||
GetUniverseDomain(ctx context.Context) (string, error)
|
||||
}
|
||||
|
||||
// AllowedLocationsResponse is the structure of the response from the Trust Boundary API.
|
||||
type AllowedLocationsResponse struct {
|
||||
// Locations is the list of allowed locations.
|
||||
Locations []string `json:"locations"`
|
||||
// EncodedLocations is the encoded representation of the allowed locations.
|
||||
EncodedLocations string `json:"encodedLocations"`
|
||||
}
|
||||
|
||||
// fetchTrustBoundaryData fetches the trust boundary data from the API.
|
||||
func fetchTrustBoundaryData(ctx context.Context, client *http.Client, url string, token *auth.Token, logger *slog.Logger) (*internal.TrustBoundaryData, error) {
|
||||
if logger == nil {
|
||||
logger = slog.New(slog.NewTextHandler(io.Discard, nil))
|
||||
}
|
||||
if client == nil {
|
||||
return nil, errors.New("trustboundary: HTTP client is required")
|
||||
}
|
||||
|
||||
if url == "" {
|
||||
return nil, errors.New("trustboundary: URL cannot be empty")
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("trustboundary: failed to create trust boundary request: %w", err)
|
||||
}
|
||||
|
||||
if token == nil || token.Value == "" {
|
||||
return nil, errors.New("trustboundary: access token required for lookup API authentication")
|
||||
}
|
||||
headers.SetAuthHeader(token, req)
|
||||
logger.DebugContext(ctx, "trust boundary request", "request", internallog.HTTPRequest(req, nil))
|
||||
|
||||
retryer := retry.New()
|
||||
var response *http.Response
|
||||
for {
|
||||
response, err = client.Do(req)
|
||||
|
||||
var statusCode int
|
||||
if response != nil {
|
||||
statusCode = response.StatusCode
|
||||
}
|
||||
pause, shouldRetry := retryer.Retry(statusCode, err)
|
||||
|
||||
if !shouldRetry {
|
||||
break
|
||||
}
|
||||
|
||||
if response != nil {
|
||||
// Drain and close the body to reuse the connection
|
||||
io.Copy(io.Discard, response.Body)
|
||||
response.Body.Close()
|
||||
}
|
||||
|
||||
if err := retry.Sleep(ctx, pause); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("trustboundary: failed to fetch trust boundary: %w", err)
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("trustboundary: failed to read trust boundary response: %w", err)
|
||||
}
|
||||
|
||||
logger.DebugContext(ctx, "trust boundary response", "response", internallog.HTTPResponse(response, body))
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("trustboundary: trust boundary request failed with status: %s, body: %s", response.Status, string(body))
|
||||
}
|
||||
|
||||
apiResponse := AllowedLocationsResponse{}
|
||||
if err := json.Unmarshal(body, &apiResponse); err != nil {
|
||||
return nil, fmt.Errorf("trustboundary: failed to unmarshal trust boundary response: %w", err)
|
||||
}
|
||||
|
||||
if apiResponse.EncodedLocations == "" {
|
||||
return nil, errors.New("trustboundary: invalid API response: encodedLocations is empty")
|
||||
}
|
||||
|
||||
return internal.NewTrustBoundaryData(apiResponse.Locations, apiResponse.EncodedLocations), nil
|
||||
}
|
||||
|
||||
// serviceAccountConfig holds configuration for SA trust boundary lookups.
|
||||
// It implements the ConfigProvider interface.
|
||||
type serviceAccountConfig struct {
|
||||
ServiceAccountEmail string
|
||||
UniverseDomain string
|
||||
}
|
||||
|
||||
// NewServiceAccountConfigProvider creates a new config for service accounts.
|
||||
func NewServiceAccountConfigProvider(saEmail, universeDomain string) ConfigProvider {
|
||||
return &serviceAccountConfig{
|
||||
ServiceAccountEmail: saEmail,
|
||||
UniverseDomain: universeDomain,
|
||||
}
|
||||
}
|
||||
|
||||
// GetTrustBoundaryEndpoint returns the formatted URL for fetching allowed locations
|
||||
// for the configured service account and universe domain.
|
||||
func (sac *serviceAccountConfig) GetTrustBoundaryEndpoint(ctx context.Context) (url string, err error) {
|
||||
if sac.ServiceAccountEmail == "" {
|
||||
return "", errors.New("trustboundary: service account email cannot be empty for config")
|
||||
}
|
||||
ud := sac.UniverseDomain
|
||||
if ud == "" {
|
||||
ud = internal.DefaultUniverseDomain
|
||||
}
|
||||
return fmt.Sprintf(serviceAccountAllowedLocationsEndpoint, ud, sac.ServiceAccountEmail), nil
|
||||
}
|
||||
|
||||
// GetUniverseDomain returns the configured universe domain, defaulting to
|
||||
// [internal.DefaultUniverseDomain] if not explicitly set.
|
||||
func (sac *serviceAccountConfig) GetUniverseDomain(ctx context.Context) (string, error) {
|
||||
if sac.UniverseDomain == "" {
|
||||
return internal.DefaultUniverseDomain, nil
|
||||
}
|
||||
return sac.UniverseDomain, nil
|
||||
}
|
||||
|
||||
// DataProvider fetches and caches trust boundary Data.
|
||||
// It implements the DataProvider interface and uses a ConfigProvider
|
||||
// to get type-specific details for the lookup.
|
||||
type DataProvider struct {
|
||||
client *http.Client
|
||||
configProvider ConfigProvider
|
||||
data *internal.TrustBoundaryData
|
||||
logger *slog.Logger
|
||||
base auth.TokenProvider
|
||||
}
|
||||
|
||||
// NewProvider wraps the provided base [auth.TokenProvider] to create a new
|
||||
// provider that injects tokens with trust boundary data. It uses the provided
|
||||
// HTTP client and configProvider to fetch the data and attach it to the token's
|
||||
// metadata.
|
||||
func NewProvider(client *http.Client, configProvider ConfigProvider, logger *slog.Logger, base auth.TokenProvider) (*DataProvider, error) {
|
||||
if client == nil {
|
||||
return nil, errors.New("trustboundary: HTTP client cannot be nil for DataProvider")
|
||||
}
|
||||
if configProvider == nil {
|
||||
return nil, errors.New("trustboundary: ConfigProvider cannot be nil for DataProvider")
|
||||
}
|
||||
p := &DataProvider{
|
||||
client: client,
|
||||
configProvider: configProvider,
|
||||
logger: internallog.New(logger),
|
||||
base: base,
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// Token retrieves a token from the base provider and injects it with trust
|
||||
// boundary data.
|
||||
func (p *DataProvider) Token(ctx context.Context) (*auth.Token, error) {
|
||||
// Get the original token.
|
||||
token, err := p.base.Token(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tbData, err := p.GetTrustBoundaryData(ctx, token)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("trustboundary: error fetching the trust boundary data: %w", err)
|
||||
}
|
||||
if tbData != nil {
|
||||
if token.Metadata == nil {
|
||||
token.Metadata = make(map[string]interface{})
|
||||
}
|
||||
token.Metadata[internal.TrustBoundaryDataKey] = *tbData
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// GetTrustBoundaryData retrieves the trust boundary data.
|
||||
// It first checks the universe domain: if it's non-default, a NoOp is returned.
|
||||
// Otherwise, it checks a local cache. If the data is not cached as NoOp,
|
||||
// it fetches new data from the endpoint provided by its ConfigProvider,
|
||||
// using the given accessToken for authentication. Results are cached.
|
||||
// If fetching fails, it returns previously cached data if available, otherwise the fetch error.
|
||||
func (p *DataProvider) GetTrustBoundaryData(ctx context.Context, token *auth.Token) (*internal.TrustBoundaryData, error) {
|
||||
// Check the universe domain.
|
||||
uniDomain, err := p.configProvider.GetUniverseDomain(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("trustboundary: error getting universe domain: %w", err)
|
||||
}
|
||||
if uniDomain != "" && uniDomain != internal.DefaultUniverseDomain {
|
||||
if p.data == nil || p.data.EncodedLocations != internal.TrustBoundaryNoOp {
|
||||
p.data = internal.NewNoOpTrustBoundaryData()
|
||||
}
|
||||
return p.data, nil
|
||||
}
|
||||
|
||||
// Check cache for a no-op result from a previous API call.
|
||||
cachedData := p.data
|
||||
if cachedData != nil && cachedData.EncodedLocations == internal.TrustBoundaryNoOp {
|
||||
return cachedData, nil
|
||||
}
|
||||
|
||||
// Get the endpoint
|
||||
url, err := p.configProvider.GetTrustBoundaryEndpoint(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("trustboundary: error getting the lookup endpoint: %w", err)
|
||||
}
|
||||
|
||||
// Proceed to fetch new data.
|
||||
newData, fetchErr := fetchTrustBoundaryData(ctx, p.client, url, token, p.logger)
|
||||
|
||||
if fetchErr != nil {
|
||||
// Fetch failed. Fallback to cachedData if available.
|
||||
if cachedData != nil {
|
||||
return cachedData, nil // Successful fallback
|
||||
}
|
||||
// No cache to fallback to.
|
||||
return nil, fmt.Errorf("trustboundary: failed to fetch trust boundary data for endpoint %s and no cache available: %w", url, fetchErr)
|
||||
}
|
||||
|
||||
// Fetch successful. Update cache.
|
||||
p.data = newData
|
||||
return newData, nil
|
||||
}
|
||||
|
||||
// GCEConfigProvider implements ConfigProvider for GCE environments.
|
||||
// It lazily fetches and caches the necessary metadata (service account email, universe domain)
|
||||
// from the GCE metadata server.
|
||||
type GCEConfigProvider struct {
|
||||
// universeDomainProvider provides the universe domain and underlying metadata client.
|
||||
universeDomainProvider *internal.ComputeUniverseDomainProvider
|
||||
|
||||
// Caching for service account email
|
||||
saOnce sync.Once
|
||||
saEmail string
|
||||
saEmailErr error
|
||||
|
||||
// Caching for universe domain
|
||||
udOnce sync.Once
|
||||
ud string
|
||||
udErr error
|
||||
}
|
||||
|
||||
// NewGCEConfigProvider creates a new GCEConfigProvider
|
||||
// which uses the provided gceUDP to interact with the GCE metadata server.
|
||||
func NewGCEConfigProvider(gceUDP *internal.ComputeUniverseDomainProvider) *GCEConfigProvider {
|
||||
// The validity of gceUDP and its internal MetadataClient will be checked
|
||||
// within the GetTrustBoundaryEndpoint and GetUniverseDomain methods.
|
||||
return &GCEConfigProvider{
|
||||
universeDomainProvider: gceUDP,
|
||||
}
|
||||
}
|
||||
|
||||
func (g *GCEConfigProvider) fetchSA(ctx context.Context) {
|
||||
if g.universeDomainProvider == nil || g.universeDomainProvider.MetadataClient == nil {
|
||||
g.saEmailErr = errors.New("trustboundary: GCEConfigProvider not properly initialized (missing ComputeUniverseDomainProvider or MetadataClient)")
|
||||
return
|
||||
}
|
||||
mdClient := g.universeDomainProvider.MetadataClient
|
||||
saEmail, err := mdClient.EmailWithContext(ctx, "default")
|
||||
if err != nil {
|
||||
g.saEmailErr = fmt.Errorf("trustboundary: GCE config: failed to get service account email: %w", err)
|
||||
return
|
||||
}
|
||||
g.saEmail = saEmail
|
||||
}
|
||||
|
||||
func (g *GCEConfigProvider) fetchUD(ctx context.Context) {
|
||||
if g.universeDomainProvider == nil || g.universeDomainProvider.MetadataClient == nil {
|
||||
g.udErr = errors.New("trustboundary: GCEConfigProvider not properly initialized (missing ComputeUniverseDomainProvider or MetadataClient)")
|
||||
return
|
||||
}
|
||||
ud, err := g.universeDomainProvider.GetProperty(ctx)
|
||||
if err != nil {
|
||||
g.udErr = fmt.Errorf("trustboundary: GCE config: failed to get universe domain: %w", err)
|
||||
return
|
||||
}
|
||||
if ud == "" {
|
||||
ud = internal.DefaultUniverseDomain
|
||||
}
|
||||
g.ud = ud
|
||||
}
|
||||
|
||||
// GetTrustBoundaryEndpoint constructs the trust boundary lookup URL for a GCE environment.
|
||||
// It uses cached metadata (service account email, universe domain) after the first call.
|
||||
func (g *GCEConfigProvider) GetTrustBoundaryEndpoint(ctx context.Context) (string, error) {
|
||||
g.saOnce.Do(func() { g.fetchSA(ctx) })
|
||||
if g.saEmailErr != nil {
|
||||
return "", g.saEmailErr
|
||||
}
|
||||
g.udOnce.Do(func() { g.fetchUD(ctx) })
|
||||
if g.udErr != nil {
|
||||
return "", g.udErr
|
||||
}
|
||||
return fmt.Sprintf(serviceAccountAllowedLocationsEndpoint, g.ud, g.saEmail), nil
|
||||
}
|
||||
|
||||
// GetUniverseDomain retrieves the universe domain from the GCE metadata server.
|
||||
// It uses a cached value after the first call.
|
||||
func (g *GCEConfigProvider) GetUniverseDomain(ctx context.Context) (string, error) {
|
||||
g.udOnce.Do(func() { g.fetchUD(ctx) })
|
||||
if g.udErr != nil {
|
||||
return "", g.udErr
|
||||
}
|
||||
return g.ud, nil
|
||||
}
|
||||
2
vendor/cloud.google.com/go/auth/internal/version.go
generated
vendored
2
vendor/cloud.google.com/go/auth/internal/version.go
generated
vendored
@@ -17,4 +17,4 @@
|
||||
package internal
|
||||
|
||||
// Version is the current tagged release of the library.
|
||||
const Version = "0.20.0"
|
||||
const Version = "0.22.0"
|
||||
|
||||
7
vendor/cloud.google.com/go/iam/CHANGES.md
generated
vendored
7
vendor/cloud.google.com/go/iam/CHANGES.md
generated
vendored
@@ -1,6 +1,13 @@
|
||||
# Changes
|
||||
|
||||
|
||||
## [1.12.0](https://github.com/googleapis/google-cloud-go/compare/iam/v1.11.0...iam/v1.12.0) (2026-07-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **o11y:** Regenerate clients for LRO tracing ([#20107](https://github.com/googleapis/google-cloud-go/issues/20107)) ([779074e](https://github.com/googleapis/google-cloud-go/commit/779074edd267a26520bae459307660953129eb07))
|
||||
|
||||
## [1.11.0](https://github.com/googleapis/google-cloud-go/releases/tag/iam%2Fv1.11.0) (2026-05-07)
|
||||
|
||||
## [1.10.0](https://github.com/googleapis/google-cloud-go/releases/tag/iam%2Fv1.10.0) (2026-04-30)
|
||||
|
||||
4
vendor/cloud.google.com/go/monitoring/apiv3/v2/alert_policy_client.go
generated
vendored
4
vendor/cloud.google.com/go/monitoring/apiv3/v2/alert_policy_client.go
generated
vendored
@@ -144,7 +144,7 @@ type AlertPolicyClient struct {
|
||||
|
||||
// Wrapper methods routed to the internal client.
|
||||
|
||||
// Close closes the connection to the API service. The user should invoke this when
|
||||
// Close closes the connection to the API service. **Always** call Close() when
|
||||
// the client is no longer required.
|
||||
func (c *AlertPolicyClient) Close() error {
|
||||
return c.internalClient.Close()
|
||||
@@ -312,7 +312,7 @@ func (c *alertPolicyGRPCClient) setGoogleClientInfo(keyval ...string) {
|
||||
}
|
||||
}
|
||||
|
||||
// Close closes the connection to the API service. The user should invoke this when
|
||||
// Close closes the connection to the API service. **Always** call Close() when
|
||||
// the client is no longer required.
|
||||
func (c *alertPolicyGRPCClient) Close() error {
|
||||
return c.connPool.Close()
|
||||
|
||||
4
vendor/cloud.google.com/go/monitoring/apiv3/v2/group_client.go
generated
vendored
4
vendor/cloud.google.com/go/monitoring/apiv3/v2/group_client.go
generated
vendored
@@ -171,7 +171,7 @@ type GroupClient struct {
|
||||
|
||||
// Wrapper methods routed to the internal client.
|
||||
|
||||
// Close closes the connection to the API service. The user should invoke this when
|
||||
// Close closes the connection to the API service. **Always** call Close() when
|
||||
// the client is no longer required.
|
||||
func (c *GroupClient) Close() error {
|
||||
return c.internalClient.Close()
|
||||
@@ -334,7 +334,7 @@ func (c *groupGRPCClient) setGoogleClientInfo(keyval ...string) {
|
||||
}
|
||||
}
|
||||
|
||||
// Close closes the connection to the API service. The user should invoke this when
|
||||
// Close closes the connection to the API service. **Always** call Close() when
|
||||
// the client is no longer required.
|
||||
func (c *groupGRPCClient) Close() error {
|
||||
return c.connPool.Close()
|
||||
|
||||
4
vendor/cloud.google.com/go/monitoring/apiv3/v2/metric_client.go
generated
vendored
4
vendor/cloud.google.com/go/monitoring/apiv3/v2/metric_client.go
generated
vendored
@@ -184,7 +184,7 @@ type MetricClient struct {
|
||||
|
||||
// Wrapper methods routed to the internal client.
|
||||
|
||||
// Close closes the connection to the API service. The user should invoke this when
|
||||
// Close closes the connection to the API service. **Always** call Close() when
|
||||
// the client is no longer required.
|
||||
func (c *MetricClient) Close() error {
|
||||
return c.internalClient.Close()
|
||||
@@ -375,7 +375,7 @@ func (c *metricGRPCClient) setGoogleClientInfo(keyval ...string) {
|
||||
}
|
||||
}
|
||||
|
||||
// Close closes the connection to the API service. The user should invoke this when
|
||||
// Close closes the connection to the API service. **Always** call Close() when
|
||||
// the client is no longer required.
|
||||
func (c *metricGRPCClient) Close() error {
|
||||
return c.connPool.Close()
|
||||
|
||||
4
vendor/cloud.google.com/go/monitoring/apiv3/v2/notification_channel_client.go
generated
vendored
4
vendor/cloud.google.com/go/monitoring/apiv3/v2/notification_channel_client.go
generated
vendored
@@ -198,7 +198,7 @@ type NotificationChannelClient struct {
|
||||
|
||||
// Wrapper methods routed to the internal client.
|
||||
|
||||
// Close closes the connection to the API service. The user should invoke this when
|
||||
// Close closes the connection to the API service. **Always** call Close() when
|
||||
// the client is no longer required.
|
||||
func (c *NotificationChannelClient) Close() error {
|
||||
return c.internalClient.Close()
|
||||
@@ -422,7 +422,7 @@ func (c *notificationChannelGRPCClient) setGoogleClientInfo(keyval ...string) {
|
||||
}
|
||||
}
|
||||
|
||||
// Close closes the connection to the API service. The user should invoke this when
|
||||
// Close closes the connection to the API service. **Always** call Close() when
|
||||
// the client is no longer required.
|
||||
func (c *notificationChannelGRPCClient) Close() error {
|
||||
return c.connPool.Close()
|
||||
|
||||
4
vendor/cloud.google.com/go/monitoring/apiv3/v2/query_client.go
generated
vendored
4
vendor/cloud.google.com/go/monitoring/apiv3/v2/query_client.go
generated
vendored
@@ -87,7 +87,7 @@ type QueryClient struct {
|
||||
|
||||
// Wrapper methods routed to the internal client.
|
||||
|
||||
// Close closes the connection to the API service. The user should invoke this when
|
||||
// Close closes the connection to the API service. **Always** call Close() when
|
||||
// the client is no longer required.
|
||||
func (c *QueryClient) Close() error {
|
||||
return c.internalClient.Close()
|
||||
@@ -215,7 +215,7 @@ func (c *queryGRPCClient) setGoogleClientInfo(keyval ...string) {
|
||||
}
|
||||
}
|
||||
|
||||
// Close closes the connection to the API service. The user should invoke this when
|
||||
// Close closes the connection to the API service. **Always** call Close() when
|
||||
// the client is no longer required.
|
||||
func (c *queryGRPCClient) Close() error {
|
||||
return c.connPool.Close()
|
||||
|
||||
4
vendor/cloud.google.com/go/monitoring/apiv3/v2/service_monitoring_client.go
generated
vendored
4
vendor/cloud.google.com/go/monitoring/apiv3/v2/service_monitoring_client.go
generated
vendored
@@ -191,7 +191,7 @@ type ServiceMonitoringClient struct {
|
||||
|
||||
// Wrapper methods routed to the internal client.
|
||||
|
||||
// Close closes the connection to the API service. The user should invoke this when
|
||||
// Close closes the connection to the API service. **Always** call Close() when
|
||||
// the client is no longer required.
|
||||
func (c *ServiceMonitoringClient) Close() error {
|
||||
return c.internalClient.Close()
|
||||
@@ -369,7 +369,7 @@ func (c *serviceMonitoringGRPCClient) setGoogleClientInfo(keyval ...string) {
|
||||
}
|
||||
}
|
||||
|
||||
// Close closes the connection to the API service. The user should invoke this when
|
||||
// Close closes the connection to the API service. **Always** call Close() when
|
||||
// the client is no longer required.
|
||||
func (c *serviceMonitoringGRPCClient) Close() error {
|
||||
return c.connPool.Close()
|
||||
|
||||
4
vendor/cloud.google.com/go/monitoring/apiv3/v2/snooze_client.go
generated
vendored
4
vendor/cloud.google.com/go/monitoring/apiv3/v2/snooze_client.go
generated
vendored
@@ -124,7 +124,7 @@ type SnoozeClient struct {
|
||||
|
||||
// Wrapper methods routed to the internal client.
|
||||
|
||||
// Close closes the connection to the API service. The user should invoke this when
|
||||
// Close closes the connection to the API service. **Always** call Close() when
|
||||
// the client is no longer required.
|
||||
func (c *SnoozeClient) Close() error {
|
||||
return c.internalClient.Close()
|
||||
@@ -269,7 +269,7 @@ func (c *snoozeGRPCClient) setGoogleClientInfo(keyval ...string) {
|
||||
}
|
||||
}
|
||||
|
||||
// Close closes the connection to the API service. The user should invoke this when
|
||||
// Close closes the connection to the API service. **Always** call Close() when
|
||||
// the client is no longer required.
|
||||
func (c *snoozeGRPCClient) Close() error {
|
||||
return c.connPool.Close()
|
||||
|
||||
4
vendor/cloud.google.com/go/monitoring/apiv3/v2/uptime_check_client.go
generated
vendored
4
vendor/cloud.google.com/go/monitoring/apiv3/v2/uptime_check_client.go
generated
vendored
@@ -157,7 +157,7 @@ type UptimeCheckClient struct {
|
||||
|
||||
// Wrapper methods routed to the internal client.
|
||||
|
||||
// Close closes the connection to the API service. The user should invoke this when
|
||||
// Close closes the connection to the API service. **Always** call Close() when
|
||||
// the client is no longer required.
|
||||
func (c *UptimeCheckClient) Close() error {
|
||||
return c.internalClient.Close()
|
||||
@@ -321,7 +321,7 @@ func (c *uptimeCheckGRPCClient) setGoogleClientInfo(keyval ...string) {
|
||||
}
|
||||
}
|
||||
|
||||
// Close closes the connection to the API service. The user should invoke this when
|
||||
// Close closes the connection to the API service. **Always** call Close() when
|
||||
// the client is no longer required.
|
||||
func (c *uptimeCheckGRPCClient) Close() error {
|
||||
return c.connPool.Close()
|
||||
|
||||
2
vendor/cloud.google.com/go/monitoring/internal/version.go
generated
vendored
2
vendor/cloud.google.com/go/monitoring/internal/version.go
generated
vendored
@@ -17,4 +17,4 @@
|
||||
package internal
|
||||
|
||||
// Version is the current tagged release of the library.
|
||||
const Version = "1.29.0"
|
||||
const Version = "1.30.0"
|
||||
|
||||
47
vendor/cloud.google.com/go/storage/CHANGES.md
generated
vendored
47
vendor/cloud.google.com/go/storage/CHANGES.md
generated
vendored
@@ -1,12 +1,43 @@
|
||||
# Changes
|
||||
|
||||
|
||||
## [1.64.0](https://github.com/googleapis/google-cloud-go/compare/storage/v1.63.1...storage/v1.64.0) (2026-07-21)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **storage:** Accept CRC32C for appendable objects ([#20104](https://github.com/googleapis/google-cloud-go/issues/20104)) ([1b2f5af](https://github.com/googleapis/google-cloud-go/commit/1b2f5afa0fe0f159edbc7184467928ebbd8720e2))
|
||||
|
||||
## [1.63.1](https://github.com/googleapis/google-cloud-go/compare/storage/v1.63.0...storage/v1.63.1) (2026-07-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **o11y:** Regenerate clients for LRO tracing ([#20107](https://github.com/googleapis/google-cloud-go/issues/20107)) ([779074e](https://github.com/googleapis/google-cloud-go/commit/779074edd267a26520bae459307660953129eb07))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **storage:** Minor documentation fix ([#20139](https://github.com/googleapis/google-cloud-go/issues/20139)) ([37f1745](https://github.com/googleapis/google-cloud-go/commit/37f17453d73ad7abbc424f51b51ff82f8004c5b2))
|
||||
|
||||
## [1.63.0](https://github.com/googleapis/google-cloud-go/compare/storage/v1.62.2...storage/v1.63.0) (2026-06-25)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **go:** Add full object checksum for negative offsets > size ([#20026](https://github.com/googleapis/google-cloud-go/issues/20026)) ([a04d980](https://github.com/googleapis/google-cloud-go/commit/a04d9809f8897195d796f4323d36e6880c0e02e8))
|
||||
* **storage:** Add client feature tracking in HTTP client ([#14691](https://github.com/googleapis/google-cloud-go/issues/14691)) ([319cc4c](https://github.com/googleapis/google-cloud-go/commit/319cc4c868f17196249d51faaecf91e58876ddba))
|
||||
* **storage:** App Centric Observability ([#14685](https://github.com/googleapis/google-cloud-go/issues/14685)) ([c3273bb](https://github.com/googleapis/google-cloud-go/commit/c3273bbf15b0d093dbb4e0bc62d22d26f273dbe5))
|
||||
* **storage:** Read checksums in gRPC partial reads ([#14586](https://github.com/googleapis/google-cloud-go/issues/14586)) ([d29f68a](https://github.com/googleapis/google-cloud-go/commit/d29f68afa17d1b874c374c97642afaaf0958a929))
|
||||
* **storage:** Support deleteSourceObjects option in object compose ([#14704](https://github.com/googleapis/google-cloud-go/issues/14704)) ([0d2d680](https://github.com/googleapis/google-cloud-go/commit/0d2d68046cae33909028c0c116c4743f72cc00f5))
|
||||
* Update API sources and regenerate ([#14701](https://github.com/googleapis/google-cloud-go/issues/14701)) ([a9b7921](https://github.com/googleapis/google-cloud-go/commit/a9b7921551e9c1535496731da53e880e9e364efa))
|
||||
|
||||
## [1.62.3](https://github.com/googleapis/google-cloud-go/releases/tag/storage%2Fv1.62.3) (2026-06-03)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add server closed idle connection to retriable errors (#14594) ([20b37d6](https://github.com/googleapis/google-cloud-go/commit/20b37d65d56d4b5e7b8d43b1f6b2ddefcd8944be))
|
||||
* fix race condition during retries in gRPC writer (#14649) ([04b6c63](https://github.com/googleapis/google-cloud-go/commit/04b6c635c09de1772fcefd8a7fd5e4ffdd370b79))
|
||||
* **storage:** Add server closed idle connection to retriable errors ([#14594](https://github.com/googleapis/google-cloud-go/issues/14594)) ([a6bd392](https://github.com/googleapis/google-cloud-go/commit/a6bd39257d261f74680c909c24102ca6f69989b9))
|
||||
* **storage:** Fix race condition during retries in gRPC writer ([#14649](https://github.com/googleapis/google-cloud-go/issues/14649)) ([c781a75](https://github.com/googleapis/google-cloud-go/commit/c781a7535f87377bb7d0b47fc82ad0bb330faf29))
|
||||
|
||||
## [1.62.2](https://github.com/googleapis/google-cloud-go/releases/tag/storage%2Fv1.62.2) (2026-05-18)
|
||||
|
||||
@@ -42,18 +73,6 @@
|
||||
|
||||
* Update `EnableParallelUpload` documentation in `writer.go` (#14328) ([22d0749](http://github.com/googleapis/google-cloud-go/commit/22d0749f8fdbeaa34f2a836b63510bd0c3def990))
|
||||
|
||||
## [1.61.5](https://github.com/googleapis/google-cloud-go/releases/tag/storage%2Fv1.61.5) (2026-06-01)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix race condition during retries in gRPC writer (#14649) ([e87213b](https://github.com/googleapis/google-cloud-go/commit/e87213b78affff3aafb6ee0ecd542d65719e5c5c))
|
||||
|
||||
## [1.61.4](https://github.com/googleapis/google-cloud-go/releases/tag/storage%2Fv1.61.4) (2026-05-21)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add server closed idle connection to retriable errors (#14594) ([37580e7](https://github.com/googleapis/google-cloud-go/commit/37580e7eb530bbcf54951425947060c51cb0b30a))
|
||||
|
||||
## [1.61.3](https://github.com/googleapis/google-cloud-go/releases/tag/storage%2Fv1.61.3) (2026-03-13)
|
||||
|
||||
### Documentation
|
||||
|
||||
6
vendor/cloud.google.com/go/storage/acl.go
generated
vendored
6
vendor/cloud.google.com/go/storage/acl.go
generated
vendored
@@ -76,7 +76,7 @@ type ACLHandle struct {
|
||||
|
||||
// Delete permanently deletes the ACL entry for the given entity.
|
||||
func (a *ACLHandle) Delete(ctx context.Context, entity ACLEntity) (err error) {
|
||||
ctx, _ = startSpan(ctx, "ACL.Delete")
|
||||
ctx, _ = startSpanWithBucket(ctx, a.c, a.bucket, "ACL.Delete")
|
||||
defer func() { endSpan(ctx, err) }()
|
||||
|
||||
if a.object != "" {
|
||||
@@ -90,7 +90,7 @@ func (a *ACLHandle) Delete(ctx context.Context, entity ACLEntity) (err error) {
|
||||
|
||||
// Set sets the role for the given entity.
|
||||
func (a *ACLHandle) Set(ctx context.Context, entity ACLEntity, role ACLRole) (err error) {
|
||||
ctx, _ = startSpan(ctx, "ACL.Set")
|
||||
ctx, _ = startSpanWithBucket(ctx, a.c, a.bucket, "ACL.Set")
|
||||
defer func() { endSpan(ctx, err) }()
|
||||
|
||||
if a.object != "" {
|
||||
@@ -104,7 +104,7 @@ func (a *ACLHandle) Set(ctx context.Context, entity ACLEntity, role ACLRole) (er
|
||||
|
||||
// List retrieves ACL entries.
|
||||
func (a *ACLHandle) List(ctx context.Context) (rules []ACLRule, err error) {
|
||||
ctx, _ = startSpan(ctx, "ACL.List")
|
||||
ctx, _ = startSpanWithBucket(ctx, a.c, a.bucket, "ACL.List")
|
||||
defer func() { endSpan(ctx, err) }()
|
||||
|
||||
if a.object != "" {
|
||||
|
||||
16
vendor/cloud.google.com/go/storage/bucket.go
generated
vendored
16
vendor/cloud.google.com/go/storage/bucket.go
generated
vendored
@@ -21,6 +21,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -81,7 +82,7 @@ func (c *Client) Bucket(name string) *BucketHandle {
|
||||
// Create creates the Bucket in the project.
|
||||
// If attrs is nil the API defaults will be used.
|
||||
func (b *BucketHandle) Create(ctx context.Context, projectID string, attrs *BucketAttrs) (err error) {
|
||||
ctx, _ = startSpan(ctx, "Bucket.Create")
|
||||
ctx, _ = startSpanWithBucket(ctx, b.c, b.name, "Bucket.Create")
|
||||
defer func() { endSpan(ctx, err) }()
|
||||
|
||||
o := makeStorageOpts(true, b.retry, b.userProject)
|
||||
@@ -94,7 +95,7 @@ func (b *BucketHandle) Create(ctx context.Context, projectID string, attrs *Buck
|
||||
|
||||
// Delete deletes the Bucket.
|
||||
func (b *BucketHandle) Delete(ctx context.Context) (err error) {
|
||||
ctx, _ = startSpan(ctx, "Bucket.Delete")
|
||||
ctx, _ = startSpanWithBucket(ctx, b.c, b.name, "Bucket.Delete")
|
||||
defer func() { endSpan(ctx, err) }()
|
||||
|
||||
o := makeStorageOpts(true, b.retry, b.userProject)
|
||||
@@ -149,16 +150,21 @@ func (b *BucketHandle) Object(name string) *ObjectHandle {
|
||||
|
||||
// Attrs returns the metadata for the bucket.
|
||||
func (b *BucketHandle) Attrs(ctx context.Context) (attrs *BucketAttrs, err error) {
|
||||
ctx, _ = startSpan(ctx, "Bucket.Attrs")
|
||||
ctx, _ = startSpanWithBucket(ctx, b.c, b.name, "Bucket.Attrs")
|
||||
defer func() { endSpan(ctx, err) }()
|
||||
|
||||
o := makeStorageOpts(true, b.retry, b.userProject)
|
||||
return b.c.tc.GetBucket(ctx, b.name, b.conds, o...)
|
||||
attrs, err = b.c.tc.GetBucket(ctx, b.name, b.conds, o...)
|
||||
if err == nil && b.c != nil && b.c.bucketMetadataCache != nil {
|
||||
resource, location := getMetadataFromAttrs(attrs.Location, attrs.LocationType, strconv.FormatUint(attrs.ProjectNumber, 10), b.name)
|
||||
b.c.bucketMetadataCache.put(b.name, bucketMetadata{resource: resource, location: location})
|
||||
}
|
||||
return attrs, err
|
||||
}
|
||||
|
||||
// Update updates a bucket's attributes.
|
||||
func (b *BucketHandle) Update(ctx context.Context, uattrs BucketAttrsToUpdate) (attrs *BucketAttrs, err error) {
|
||||
ctx, _ = startSpan(ctx, "Bucket.Update")
|
||||
ctx, _ = startSpanWithBucket(ctx, b.c, b.name, "Bucket.Update")
|
||||
defer func() { endSpan(ctx, err) }()
|
||||
|
||||
isIdempotent := b.conds != nil && b.conds.MetagenerationMatch != 0
|
||||
|
||||
161
vendor/cloud.google.com/go/storage/bucket_metadata_cache.go
generated
vendored
Normal file
161
vendor/cloud.google.com/go/storage/bucket_metadata_cache.go
generated
vendored
Normal file
@@ -0,0 +1,161 @@
|
||||
// Copyright 2026 Google LLC
|
||||
//
|
||||
// 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 storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sync/singleflight"
|
||||
"google.golang.org/grpc/codes"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultBucketMetadataCacheLimit = 10000
|
||||
fetchBackgroundTimeout = 10 * time.Second
|
||||
)
|
||||
|
||||
type bucketMetadataFetcher interface {
|
||||
fetchBucketMetadata(ctx context.Context, bucket string) (resource string, location string, err error)
|
||||
}
|
||||
|
||||
type bucketMetadata struct {
|
||||
resource string
|
||||
location string
|
||||
placeholder bool
|
||||
}
|
||||
|
||||
type bucketMetadataCache struct {
|
||||
mu sync.Mutex
|
||||
muSF singleflight.Group
|
||||
lru *lruCache[string, bucketMetadata]
|
||||
fetcher bucketMetadataFetcher
|
||||
// fetchDone is a hook channel used to signal completion of fetchBackground in tests.
|
||||
fetchDone chan struct{}
|
||||
}
|
||||
|
||||
func newBucketMetadataCache(limit int, fetcher bucketMetadataFetcher) *bucketMetadataCache {
|
||||
return &bucketMetadataCache{
|
||||
lru: newLRUCache[string, bucketMetadata](limit),
|
||||
fetcher: fetcher,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *bucketMetadataCache) get(bucket string) (bucketMetadata, bool) {
|
||||
if c == nil {
|
||||
return bucketMetadata{}, false
|
||||
}
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
return c.lru.get(bucket)
|
||||
}
|
||||
|
||||
func (c *bucketMetadataCache) put(bucket string, entry bucketMetadata) {
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
// Don't let a placeholder overwrite valid metadata.
|
||||
if entry.placeholder {
|
||||
if curr, hit := c.lru.get(bucket); hit && !curr.placeholder {
|
||||
return
|
||||
}
|
||||
}
|
||||
c.lru.put(bucket, entry)
|
||||
}
|
||||
|
||||
func (c *bucketMetadataCache) evict(bucket string) {
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.lru.evict(bucket)
|
||||
}
|
||||
|
||||
func (c *bucketMetadataCache) fetchBackground(bucket string) {
|
||||
if c == nil || c.fetcher == nil {
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
if c.fetchDone != nil {
|
||||
select {
|
||||
case c.fetchDone <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
c.muSF.Do(bucket, func() (interface{}, error) {
|
||||
// Perform the call with context.Background and a timeout so it runs outside request context lifetime but is bounded.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), fetchBackgroundTimeout)
|
||||
defer cancel()
|
||||
resource, location, err := c.fetcher.fetchBucketMetadata(ctx, bucket)
|
||||
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
curr, hit := c.lru.get(bucket)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrBucketNotExist) || isError(err, http.StatusNotFound, codes.NotFound) {
|
||||
c.lru.evict(bucket)
|
||||
} else if ShouldRetry(err) {
|
||||
if curr.placeholder {
|
||||
c.lru.evict(bucket)
|
||||
}
|
||||
} else {
|
||||
if !hit {
|
||||
c.lru.put(bucket, bucketMetadata{
|
||||
resource: fmt.Sprintf("projects/_/buckets/%s", bucket),
|
||||
location: "global",
|
||||
placeholder: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
entry := bucketMetadata{
|
||||
resource: resource,
|
||||
location: location,
|
||||
}
|
||||
c.lru.put(bucket, entry)
|
||||
return entry, nil
|
||||
})
|
||||
}()
|
||||
}
|
||||
|
||||
func getMetadataFromAttrs(location, locationType, project, bucket string) (string, string) {
|
||||
finalLocation := "global"
|
||||
if locationType == "zone" || locationType == "region" {
|
||||
finalLocation = strings.ToLower(location)
|
||||
}
|
||||
if strings.HasPrefix(project, "projects/") {
|
||||
return project + "/buckets/" + bucket, finalLocation
|
||||
}
|
||||
finalProject := "_"
|
||||
if project != "0" && project != "" {
|
||||
finalProject = project
|
||||
}
|
||||
return fmt.Sprintf("projects/%s/buckets/%s", finalProject, bucket), finalLocation
|
||||
}
|
||||
45
vendor/cloud.google.com/go/storage/client.go
generated
vendored
45
vendor/cloud.google.com/go/storage/client.go
generated
vendored
@@ -109,6 +109,8 @@ type storageClient interface {
|
||||
DeleteNotification(ctx context.Context, bucket string, id string, opts ...storageOption) error
|
||||
|
||||
NewMultiRangeDownloader(ctx context.Context, params *newMultiRangeDownloaderParams, opts ...storageOption) (*MultiRangeDownloader, error)
|
||||
|
||||
fetchBucketMetadata(ctx context.Context, bucket string) (resource string, location string, err error)
|
||||
}
|
||||
|
||||
// settings contains transport-agnostic configuration for API calls made via
|
||||
@@ -297,12 +299,13 @@ type openWriterParams struct {
|
||||
}
|
||||
|
||||
type newMultiRangeDownloaderParams struct {
|
||||
bucket string
|
||||
conds *Conditions
|
||||
encryptionKey []byte
|
||||
gen int64
|
||||
object string
|
||||
handle *ReadHandle
|
||||
bucket string
|
||||
conds *Conditions
|
||||
disableMRDReadChecksum bool
|
||||
encryptionKey []byte
|
||||
gen int64
|
||||
handle *ReadHandle
|
||||
object string
|
||||
|
||||
// Multistream settings.
|
||||
minConnections int
|
||||
@@ -312,15 +315,16 @@ type newMultiRangeDownloaderParams struct {
|
||||
}
|
||||
|
||||
type newRangeReaderParams struct {
|
||||
bucket string
|
||||
conds *Conditions
|
||||
encryptionKey []byte
|
||||
gen int64
|
||||
length int64
|
||||
object string
|
||||
offset int64
|
||||
readCompressed bool // Use accept-encoding: gzip. Only works for HTTP currently.
|
||||
handle *ReadHandle
|
||||
bucket string
|
||||
conds *Conditions
|
||||
encryptionKey []byte
|
||||
gen int64
|
||||
length int64
|
||||
object string
|
||||
offset int64
|
||||
readCompressed bool // Use accept-encoding: gzip. Only works for HTTP currently.
|
||||
handle *ReadHandle
|
||||
disableCRCCheck bool
|
||||
}
|
||||
|
||||
type getObjectParams struct {
|
||||
@@ -356,11 +360,12 @@ type moveObjectParams struct {
|
||||
}
|
||||
|
||||
type composeObjectRequest struct {
|
||||
dstBucket string
|
||||
dstObject destinationObject
|
||||
srcs []sourceObject
|
||||
predefinedACL string
|
||||
sendCRC32C bool
|
||||
dstBucket string
|
||||
dstObject destinationObject
|
||||
srcs []sourceObject
|
||||
predefinedACL string
|
||||
sendCRC32C bool
|
||||
deleteSourceObjects bool
|
||||
}
|
||||
|
||||
type sourceObject struct {
|
||||
|
||||
15
vendor/cloud.google.com/go/storage/copy.go
generated
vendored
15
vendor/cloud.google.com/go/storage/copy.go
generated
vendored
@@ -80,7 +80,7 @@ type Copier struct {
|
||||
|
||||
// Run performs the copy.
|
||||
func (c *Copier) Run(ctx context.Context) (attrs *ObjectAttrs, err error) {
|
||||
ctx, _ = startSpan(ctx, "Copier.Run")
|
||||
ctx, _ = startSpanWithBucket(ctx, c.dst.c, c.dst.bucket, "Copier.Run")
|
||||
defer func() { endSpan(ctx, err) }()
|
||||
|
||||
if err := c.src.validate(); err != nil {
|
||||
@@ -172,13 +172,17 @@ type Composer struct {
|
||||
// the checksum, the compose will be rejected.
|
||||
SendCRC32C bool
|
||||
|
||||
// DeleteSourceObjects specifies whether to delete the source objects after a
|
||||
// successful composition.
|
||||
DeleteSourceObjects bool
|
||||
|
||||
dst *ObjectHandle
|
||||
srcs []*ObjectHandle
|
||||
}
|
||||
|
||||
// Run performs the compose operation.
|
||||
func (c *Composer) Run(ctx context.Context) (attrs *ObjectAttrs, err error) {
|
||||
ctx, _ = startSpan(ctx, "Composer.Run")
|
||||
ctx, _ = startSpanWithBucket(ctx, c.dst.c, c.dst.bucket, "Composer.Run")
|
||||
defer func() { endSpan(ctx, err) }()
|
||||
|
||||
if err := c.dst.validate(); err != nil {
|
||||
@@ -204,9 +208,10 @@ func (c *Composer) Run(ctx context.Context) (attrs *ObjectAttrs, err error) {
|
||||
}
|
||||
|
||||
req := &composeObjectRequest{
|
||||
dstBucket: c.dst.bucket,
|
||||
predefinedACL: c.PredefinedACL,
|
||||
sendCRC32C: c.SendCRC32C,
|
||||
dstBucket: c.dst.bucket,
|
||||
predefinedACL: c.PredefinedACL,
|
||||
sendCRC32C: c.SendCRC32C,
|
||||
deleteSourceObjects: c.DeleteSourceObjects,
|
||||
}
|
||||
req.dstObject = destinationObject{
|
||||
name: c.dst.object,
|
||||
|
||||
16
vendor/cloud.google.com/go/storage/doc.go
generated
vendored
16
vendor/cloud.google.com/go/storage/doc.go
generated
vendored
@@ -407,6 +407,9 @@ roles which must be enabled in order to do the export successfully. To
|
||||
disable this export, you can use the [WithDisabledClientMetrics] client
|
||||
option.
|
||||
|
||||
To disable OpenTelemetry bucket metadata in traces, you can set the
|
||||
environment variable GO_OTEL_BUCKETMETADATA_DISABLED=true.
|
||||
|
||||
The client automatically computes and sends CRC32C checksums for uploads using [Writer],
|
||||
providing an additional layer of data integrity validation with a slight CPU overhead.
|
||||
|
||||
@@ -416,6 +419,19 @@ apply to single-shot uploads when user-provided checksum is provided.
|
||||
|
||||
Automatic checksumming can be disabled using [Writer.DisableAutoChecksum].
|
||||
|
||||
# Read checksumming
|
||||
|
||||
By default, the client automatically computes and validates CRC32C checksums for reads
|
||||
when downloading an entire object, providing an additional layer of data integrity
|
||||
validation with a slight CPU overhead.
|
||||
|
||||
For gRPC clients, read checksumming is also performed for partial reads (range requests)
|
||||
by validating the checksum of each individual data chunk returned by the server.
|
||||
|
||||
Automatic read checksumming can be disabled using the [WithDisableReaderChecksum] option
|
||||
for a normal reader (both gRPC and JSON), or the [WithDisableMRDReadChecksum] option
|
||||
for the multi-range downloader (gRPC only).
|
||||
|
||||
# Parallel Uploads
|
||||
|
||||
The parallel upload feature splits a large object into multiple parts and uploads them
|
||||
|
||||
7
vendor/cloud.google.com/go/storage/experimental/experimental.go
generated
vendored
7
vendor/cloud.google.com/go/storage/experimental/experimental.go
generated
vendored
@@ -115,3 +115,10 @@ func WithZonalBucketAPIs() option.ClientOption {
|
||||
func WithDirectConnectivityEnforced() option.ClientOption {
|
||||
return internal.WithDirectConnectivityEnforced.(func() option.ClientOption)()
|
||||
}
|
||||
|
||||
// WithOtelMetrics provides an [option.ClientOption] that may be passed to
|
||||
// [cloud.google.com/go/storage.NewClient] or [cloud.google.com/go/storage.NewGRPCClient].
|
||||
// It enables client-side OpenTelemetry metrics.
|
||||
func WithOtelMetrics() option.ClientOption {
|
||||
return internal.WithOtelMetrics.(func() option.ClientOption)()
|
||||
}
|
||||
|
||||
280
vendor/cloud.google.com/go/storage/grpc_client.go
generated
vendored
280
vendor/cloud.google.com/go/storage/grpc_client.go
generated
vendored
@@ -120,10 +120,12 @@ func defaultGRPCOptions() []option.ClientOption {
|
||||
// grpcStorageClient is the gRPC API implementation of the transport-agnostic
|
||||
// storageClient interface.
|
||||
type grpcStorageClient struct {
|
||||
raw *gapic.Client
|
||||
settings *settings
|
||||
config *storageConfig
|
||||
dpDiag string
|
||||
raw *gapic.Client
|
||||
settings *settings
|
||||
config *storageConfig
|
||||
dpDiag string
|
||||
metrics *clientMetrics
|
||||
metricsCleanup func()
|
||||
|
||||
// configFeatureAttributes tracks client-level features that are enabled for this
|
||||
// client instance.
|
||||
@@ -152,7 +154,7 @@ func enableClientMetrics(ctx context.Context, s *settings, config storageConfig)
|
||||
|
||||
// newGRPCStorageClient initializes a new storageClient that uses the gRPC
|
||||
// Storage API.
|
||||
func newGRPCStorageClient(ctx context.Context, opts ...storageOption) (*grpcStorageClient, error) {
|
||||
func newGRPCStorageClient(ctx context.Context, opts ...storageOption) (client *grpcStorageClient, err error) {
|
||||
s := initSettings(opts...)
|
||||
s.clientOption = append(defaultGRPCOptions(), s.clientOption...)
|
||||
// Disable all gax-level retries in favor of retry logic in the veneer client.
|
||||
@@ -172,9 +174,37 @@ func newGRPCStorageClient(ctx context.Context, opts ...storageOption) (*grpcStor
|
||||
log.Printf("Failed to enable client metrics: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
var clientMetrics *clientMetrics
|
||||
var metricsCleanup func()
|
||||
if isOtelMetricsEnabled(&config) {
|
||||
var project string
|
||||
if c, err := transport.Creds(ctx, s.clientOption...); err == nil {
|
||||
project = c.ProjectID
|
||||
}
|
||||
clientMetrics, metricsCleanup = initClientMetrics(ctx, project, &config)
|
||||
if clientMetrics != nil {
|
||||
unaryInt, streamInt := metricsInterceptors(clientMetrics)
|
||||
s.clientOption = append(s.clientOption,
|
||||
option.WithGRPCDialOption(grpc.WithChainUnaryInterceptor(unaryInt)),
|
||||
option.WithGRPCDialOption(grpc.WithChainStreamInterceptor(streamInt)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if metricsCleanup != nil {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
metricsCleanup()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
c := &grpcStorageClient{
|
||||
settings: s,
|
||||
config: &config,
|
||||
settings: s,
|
||||
config: &config,
|
||||
metrics: clientMetrics,
|
||||
metricsCleanup: metricsCleanup,
|
||||
}
|
||||
// Add routing interceptors to inject headers.
|
||||
ui, si := c.routingInterceptors()
|
||||
@@ -275,6 +305,9 @@ func (c *grpcStorageClient) Close() error {
|
||||
if c.settings.metricsContext != nil {
|
||||
c.settings.metricsContext.close()
|
||||
}
|
||||
if c.metricsCleanup != nil {
|
||||
c.metricsCleanup()
|
||||
}
|
||||
return c.raw.Close()
|
||||
}
|
||||
|
||||
@@ -331,10 +364,12 @@ func (c *grpcStorageClient) ListBuckets(ctx context.Context, project string, opt
|
||||
|
||||
var gitr *gapic.BucketIterator
|
||||
fetch := func(pageSize int, pageToken string) (token string, err error) {
|
||||
ctx, record := startMetricsOp(it.ctx, "ListBuckets", false)
|
||||
defer func() { record(err) }()
|
||||
|
||||
var buckets []*storagepb.Bucket
|
||||
var next string
|
||||
err = run(it.ctx, func(ctx context.Context) error {
|
||||
err = run(ctx, func(ctx context.Context) error {
|
||||
// Initialize GAPIC-based iterator when pageToken is empty, which
|
||||
// indicates that this fetch call is attempting to get the first page.
|
||||
//
|
||||
@@ -573,16 +608,18 @@ func (c *grpcStorageClient) ListObjects(ctx context.Context, bucket string, q *Q
|
||||
Filter: it.query.Filter,
|
||||
}
|
||||
fetch := func(pageSize int, pageToken string) (token string, err error) {
|
||||
ctx, record := startMetricsOp(it.ctx, "ListObjects", false)
|
||||
defer func() { record(err) }()
|
||||
// Add trace span around List API call within the fetch.
|
||||
ctx, _ = startSpan(ctx, "grpcStorageClient.ObjectsListCall")
|
||||
defer func() { endSpan(ctx, err) }()
|
||||
var objects []*storagepb.Object
|
||||
var gitr *gapic.ObjectIterator
|
||||
err = run(it.ctx, func(ctx context.Context) error {
|
||||
err = run(ctx, func(ctx context.Context) error {
|
||||
gitr = c.raw.ListObjects(ctx, req, s.gax...)
|
||||
objects, token, err = gitr.InternalFetch(pageSize, pageToken)
|
||||
return err
|
||||
}, s.retry, s.idempotent)
|
||||
}, s.retry, s.idempotent, withOperation("ListObjects"), withBucket(bucket), withObject(it.query.Prefix))
|
||||
if err != nil {
|
||||
return "", formatBucketError(err)
|
||||
}
|
||||
@@ -1055,6 +1092,9 @@ func (c *grpcStorageClient) ComposeObject(ctx context.Context, req *composeObjec
|
||||
Destination: dstObjPb,
|
||||
SourceObjects: srcs,
|
||||
}
|
||||
if req.deleteSourceObjects {
|
||||
rawReq.DeleteSourceObjects = proto.Bool(true)
|
||||
}
|
||||
if err := applyCondsProto("ComposeObject destination", defaultGen, req.dstObject.conds, rawReq); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1328,7 +1368,7 @@ func (c *grpcStorageClient) NewRangeReader(ctx context.Context, params *newRange
|
||||
}
|
||||
err = decoder.readFullObjectResponse()
|
||||
return err
|
||||
}, s.retry, s.idempotent)
|
||||
}, s.retry, s.idempotent, withOperation("ReadObject"), withBucket(params.bucket), withObject(params.object))
|
||||
if err != nil {
|
||||
// Close the stream context we just created to ensure we don't leak
|
||||
// resources.
|
||||
@@ -1369,18 +1409,6 @@ func (c *grpcStorageClient) NewRangeReader(ctx context.Context, params *newRange
|
||||
params.length = obj.Size - params.offset
|
||||
}
|
||||
|
||||
// Only support checksums when reading an entire object, not a range.
|
||||
var (
|
||||
wantCRC uint32
|
||||
checkCRC bool
|
||||
)
|
||||
if checksums := obj.GetChecksums(); checksums != nil && checksums.Crc32C != nil {
|
||||
if params.offset == 0 && params.length < 0 {
|
||||
checkCRC = true
|
||||
}
|
||||
wantCRC = checksums.GetCrc32C()
|
||||
}
|
||||
|
||||
startOffset := params.offset
|
||||
if params.offset < 0 {
|
||||
startOffset = size + params.offset
|
||||
@@ -1390,6 +1418,20 @@ func (c *grpcStorageClient) NewRangeReader(ctx context.Context, params *newRange
|
||||
if startOffset < 0 {
|
||||
startOffset = 0
|
||||
}
|
||||
// Only support checksums when reading an entire object, not a range.
|
||||
var (
|
||||
wantCRC uint32
|
||||
checkCRC bool
|
||||
)
|
||||
if checksums := obj.GetChecksums(); checksums != nil && checksums.Crc32C != nil {
|
||||
if !params.disableCRCCheck &&
|
||||
startOffset == 0 &&
|
||||
(params.length < 0 ||
|
||||
finalized && params.length >= size) {
|
||||
checkCRC = true
|
||||
}
|
||||
wantCRC = checksums.GetCrc32C()
|
||||
}
|
||||
|
||||
// The remaining bytes are the lesser of the requested range and all bytes
|
||||
// after params.offset.
|
||||
@@ -1401,6 +1443,15 @@ func (c *grpcStorageClient) NewRangeReader(ctx context.Context, params *newRange
|
||||
}
|
||||
remain := length - startOffset
|
||||
|
||||
var chunkCRC uint32
|
||||
var chunkCRCPresent bool
|
||||
if ranges := msg.GetObjectDataRanges(); len(ranges) > 0 {
|
||||
if cs := ranges[0].GetChecksummedData(); cs != nil && cs.Crc32C != nil && !params.disableCRCCheck {
|
||||
chunkCRCPresent = true
|
||||
chunkCRC = *cs.Crc32C
|
||||
}
|
||||
}
|
||||
|
||||
metadata := obj.GetMetadata()
|
||||
r = &Reader{
|
||||
Attrs: ReaderObjectAttrs{
|
||||
@@ -1421,18 +1472,23 @@ func (c *grpcStorageClient) NewRangeReader(ctx context.Context, params *newRange
|
||||
cancel: cancel,
|
||||
size: size,
|
||||
// Preserve the decoder to read out object data when Read/WriteTo is called.
|
||||
currMsg: res.decoder,
|
||||
settings: s,
|
||||
zeroRange: params.length == 0,
|
||||
wantCRC: wantCRC,
|
||||
checkCRC: checkCRC,
|
||||
finalized: finalized,
|
||||
negativeOffset: negativeOffset,
|
||||
currMsg: res.decoder,
|
||||
wantChunkCRC: chunkCRC,
|
||||
chunkCRCPresent: chunkCRCPresent,
|
||||
settings: s,
|
||||
zeroRange: params.length == 0,
|
||||
wantCRC: wantCRC,
|
||||
checkCRC: checkCRC,
|
||||
disableCRCCheck: params.disableCRCCheck,
|
||||
finalized: finalized,
|
||||
negativeOffset: negativeOffset,
|
||||
},
|
||||
checkCRC: checkCRC,
|
||||
handle: &handle,
|
||||
remain: remain,
|
||||
unfinalized: !finalized,
|
||||
bucket: params.bucket,
|
||||
object: params.object,
|
||||
}
|
||||
|
||||
// For a zero-length request, explicitly close the stream and set remaining
|
||||
@@ -1570,19 +1626,23 @@ type bidiReadStreamResponse struct {
|
||||
|
||||
// gRPCReader is used by storage.Reader if the experimental option WithGRPCBidiReads is passed.
|
||||
type gRPCReader struct {
|
||||
seen, size int64
|
||||
zeroRange bool
|
||||
finalized bool // if we are reading from a finalized object; in this case, remain and size may be inaccurate
|
||||
negativeOffset bool
|
||||
stream storagepb.Storage_BidiReadObjectClient
|
||||
reopen func(seen int64) (*readStreamResponse, context.CancelFunc, error)
|
||||
leftovers []byte
|
||||
currMsg *readResponseDecoder // decoder for the current message
|
||||
cancel context.CancelFunc
|
||||
settings *settings
|
||||
checkCRC bool // should we check the CRC?
|
||||
wantCRC uint32 // the CRC32c value the server sent in the header
|
||||
gotCRC uint32 // running crc
|
||||
seen, size int64
|
||||
zeroRange bool
|
||||
finalized bool // if we are reading from a finalized object; in this case, remain and size may be inaccurate
|
||||
negativeOffset bool
|
||||
stream storagepb.Storage_BidiReadObjectClient
|
||||
reopen func(seen int64) (*readStreamResponse, context.CancelFunc, error)
|
||||
leftovers []byte
|
||||
currMsg *readResponseDecoder // decoder for the current message
|
||||
wantChunkCRC uint32
|
||||
chunkCRCPresent bool
|
||||
cancel context.CancelFunc
|
||||
settings *settings
|
||||
checkCRC bool // should we check the CRC?
|
||||
wantCRC uint32 // the CRC32c value the server sent in the header
|
||||
gotCRC uint32 // running crc
|
||||
gotChunkCRC uint32 // running crc32c of chunk
|
||||
disableCRCCheck bool
|
||||
}
|
||||
|
||||
// Update the running CRC with the data in the slice, if CRC checking was enabled.
|
||||
@@ -1590,6 +1650,9 @@ func (r *gRPCReader) updateCRC(b []byte) {
|
||||
if r.checkCRC {
|
||||
r.gotCRC = crc32.Update(r.gotCRC, crc32cTable, b)
|
||||
}
|
||||
if r.chunkCRCPresent {
|
||||
r.gotChunkCRC = crc32.Update(r.gotChunkCRC, crc32cTable, b)
|
||||
}
|
||||
}
|
||||
|
||||
// Checks whether the CRC matches at the conclusion of a read, if CRC checking was enabled.
|
||||
@@ -1600,6 +1663,17 @@ func (r *gRPCReader) runCRCCheck() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkAndResetChunkCRC verifies the chunk CRC if present, and resets the chunk CRC state.
|
||||
func (r *gRPCReader) checkAndResetChunkCRC() error {
|
||||
if r.chunkCRCPresent && r.gotChunkCRC != r.wantChunkCRC {
|
||||
return fmt.Errorf("storage: bad CRC on chunk read: got %d, want %d", r.gotChunkCRC, r.wantChunkCRC)
|
||||
}
|
||||
r.gotChunkCRC = 0
|
||||
r.chunkCRCPresent = false
|
||||
r.wantChunkCRC = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read reads bytes into the user's buffer from an open gRPC stream.
|
||||
func (r *gRPCReader) Read(p []byte) (int, error) {
|
||||
// The entire object has been read by this reader, check the checksum if
|
||||
@@ -1622,29 +1696,53 @@ func (r *gRPCReader) Read(p []byte) (int, error) {
|
||||
for {
|
||||
// If there is data remaining in the current message, try to read from it.
|
||||
if r.currMsg != nil && !r.currMsg.done {
|
||||
n, found := r.currMsg.readAndUpdateCRC(p, 1, func(b []byte) {
|
||||
n, found := r.currMsg.readAndUpdateCRC(p, defaultReadID, func(b []byte) {
|
||||
r.updateCRC(b)
|
||||
})
|
||||
// If we are done reading the current msg, free buffers.
|
||||
if r.currMsg.done {
|
||||
r.currMsg.databufs.Free()
|
||||
}
|
||||
|
||||
// If data for our readID was found, we can update `seen` and return.
|
||||
if found {
|
||||
r.seen += int64(n)
|
||||
}
|
||||
// If we are done reading the current msg, validate chunk checksum and free buffers.
|
||||
if r.currMsg.done {
|
||||
r.currMsg.databufs.Free()
|
||||
if err := r.checkAndResetChunkCRC(); err != nil {
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
|
||||
// If data for our readID was found, we can return.
|
||||
if found {
|
||||
return n, nil
|
||||
}
|
||||
// If not found, this message is exhausted for our purposes.
|
||||
// Fall through to recv() to get a new one.
|
||||
} else if r.currMsg != nil {
|
||||
if err := r.checkAndResetChunkCRC(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
// Get the next message from the stream.
|
||||
err := r.recv()
|
||||
if err == io.EOF {
|
||||
if err := r.runCRCCheck(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return 0, io.EOF
|
||||
}
|
||||
if err != nil {
|
||||
// This correctly handles io.EOF, context canceled, and other terminal errors.
|
||||
return 0, err
|
||||
}
|
||||
msg := r.currMsg.msg
|
||||
if !r.disableCRCCheck &&
|
||||
len(msg.GetObjectDataRanges()) > 0 &&
|
||||
msg.GetObjectDataRanges()[0].GetChecksummedData() != nil &&
|
||||
msg.GetObjectDataRanges()[0].GetChecksummedData().Crc32C != nil {
|
||||
r.gotChunkCRC = 0
|
||||
r.wantChunkCRC = *msg.GetObjectDataRanges()[0].GetChecksummedData().Crc32C
|
||||
r.chunkCRCPresent = true
|
||||
}
|
||||
// The loop will now restart and try to read from the new r.currMsg.
|
||||
}
|
||||
}
|
||||
@@ -1686,8 +1784,19 @@ func (r *gRPCReader) WriteTo(w io.Writer) (int64, error) {
|
||||
if err != nil {
|
||||
return r.seen - alreadySeen, err
|
||||
}
|
||||
if r.currMsg.done {
|
||||
if err := r.checkAndResetChunkCRC(); err != nil {
|
||||
return r.seen - alreadySeen, err
|
||||
}
|
||||
}
|
||||
// If no data was found, we still need to fetch the next message.
|
||||
// If data was found, we also need the next message. So we always fall through.
|
||||
r.currMsg = nil
|
||||
|
||||
} else if r.currMsg != nil {
|
||||
if err := r.checkAndResetChunkCRC(); err != nil {
|
||||
return r.seen - alreadySeen, err
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to receive the next message on the stream.
|
||||
@@ -1699,6 +1808,15 @@ func (r *gRPCReader) WriteTo(w io.Writer) (int64, error) {
|
||||
}
|
||||
return r.seen - alreadySeen, err
|
||||
}
|
||||
msg := r.currMsg.msg
|
||||
if !r.disableCRCCheck &&
|
||||
len(msg.GetObjectDataRanges()) > 0 &&
|
||||
msg.GetObjectDataRanges()[0].GetChecksummedData() != nil &&
|
||||
msg.GetObjectDataRanges()[0].GetChecksummedData().Crc32C != nil {
|
||||
r.gotChunkCRC = 0
|
||||
r.wantChunkCRC = *msg.GetObjectDataRanges()[0].GetChecksummedData().Crc32C
|
||||
r.chunkCRCPresent = true
|
||||
}
|
||||
// Continue loop to process the new message.
|
||||
}
|
||||
// Propagate any checksum error.
|
||||
@@ -1781,6 +1899,7 @@ type readResponseDecoder struct {
|
||||
msg *storagepb.BidiReadObjectResponse // processed response message with all fields other than object data populated
|
||||
dataOffsets map[int64]bufferSliceOffsets // Map ReadId to the offsets of the object data for that ID in the message.
|
||||
done bool // true if the data has been completely read.
|
||||
crcErrs map[int64]error // Map ReadId to the CRC validation error if it failed.
|
||||
}
|
||||
|
||||
type bufferSliceOffsets struct {
|
||||
@@ -1908,6 +2027,51 @@ func (d *readResponseDecoder) readAndUpdateCRC(p []byte, readID int64, updateCRC
|
||||
return n, true
|
||||
}
|
||||
|
||||
func (d *readResponseDecoder) verifyChecksums() {
|
||||
if d.msg == nil {
|
||||
return
|
||||
}
|
||||
for _, dataRange := range d.msg.GetObjectDataRanges() {
|
||||
checksummedData := dataRange.GetChecksummedData()
|
||||
if checksummedData == nil || checksummedData.Crc32C == nil {
|
||||
continue
|
||||
}
|
||||
readID := dataRange.GetReadRange().GetReadId()
|
||||
offsets, ok := d.dataOffsets[readID]
|
||||
wantCRC := *checksummedData.Crc32C
|
||||
var gotCRC uint32
|
||||
|
||||
if ok {
|
||||
for i := offsets.startBuf; i <= offsets.endBuf; i++ {
|
||||
if i < 0 || i >= len(d.databufs) {
|
||||
continue
|
||||
}
|
||||
databuf := d.databufs[i]
|
||||
var start uint64
|
||||
if i == offsets.startBuf {
|
||||
start = min(offsets.startOff, uint64(databuf.Len()))
|
||||
}
|
||||
end := uint64(databuf.Len())
|
||||
if i == offsets.endBuf {
|
||||
end = min(offsets.endOff, end)
|
||||
}
|
||||
if start >= end {
|
||||
continue
|
||||
}
|
||||
dataSlice := databuf.ReadOnlyData()[start:end]
|
||||
gotCRC = crc32.Update(gotCRC, crc32cTable, dataSlice)
|
||||
}
|
||||
}
|
||||
|
||||
if gotCRC != wantCRC {
|
||||
if d.crcErrs == nil {
|
||||
d.crcErrs = make(map[int64]error)
|
||||
}
|
||||
d.crcErrs[readID] = fmt.Errorf("storage: bad CRC on chunk read: got %d, want %d", gotCRC, wantCRC)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *readResponseDecoder) writeToAndUpdateCRC(w io.Writer, readID int64, updateCRC func([]byte)) (totalWritten int64, found bool, err error) {
|
||||
// For a completely empty message, just return 0
|
||||
if len(d.databufs) == 0 {
|
||||
@@ -1923,6 +2087,9 @@ func (d *readResponseDecoder) writeToAndUpdateCRC(w io.Writer, readID int64, upd
|
||||
|
||||
// Loop from the current buffer to the ending buffer for this specific data range.
|
||||
for i := offsets.currBuf; i <= offsets.endBuf; i++ {
|
||||
if i < 0 || i >= len(d.databufs) {
|
||||
continue
|
||||
}
|
||||
databuf := d.databufs[i]
|
||||
|
||||
// Determine the start and end of the data slice for the current buffer.
|
||||
@@ -2240,3 +2407,16 @@ func (r *gRPCReader) reopenStream() error {
|
||||
r.cancel = cancel
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *grpcStorageClient) fetchBucketMetadata(ctx context.Context, bucket string) (string, string, error) {
|
||||
req := &storagepb.GetBucketRequest{
|
||||
Name: bucketResourceName(globalProjectAlias, bucket),
|
||||
ReadMask: &fieldmaskpb.FieldMask{Paths: []string{"name", "project", "location", "location_type"}},
|
||||
}
|
||||
resp, err := c.raw.GetBucket(ctx, req)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
resource, location := getMetadataFromAttrs(resp.GetLocation(), resp.GetLocationType(), resp.GetProject(), bucket)
|
||||
return resource, location, nil
|
||||
}
|
||||
|
||||
8
vendor/cloud.google.com/go/storage/grpc_metrics.go
generated
vendored
8
vendor/cloud.google.com/go/storage/grpc_metrics.go
generated
vendored
@@ -53,7 +53,9 @@ type storageMonitoredResource struct {
|
||||
func (smr *storageMonitoredResource) exporter() (metric.Exporter, error) {
|
||||
exporter, err := mexporter.New(
|
||||
mexporter.WithProjectID(smr.project),
|
||||
mexporter.WithMetricDescriptorTypeFormatter(metricFormatter),
|
||||
mexporter.WithMetricDescriptorTypeFormatter(func(m metricdata.Metrics) string {
|
||||
return formatMetricWithPrefix(m, metricPrefix)
|
||||
}),
|
||||
mexporter.WithCreateServiceTimeSeries(),
|
||||
mexporter.WithMonitoredResourceDescription(monitoredResourceName, []string{"project_id", "location", "cloud_platform", "host_id", "instance_id", "api"}),
|
||||
)
|
||||
@@ -281,7 +283,3 @@ func createHistogramView(name string, boundaries []float64) metric.View {
|
||||
Aggregation: metric.AggregationExplicitBucketHistogram{Boundaries: boundaries},
|
||||
})
|
||||
}
|
||||
|
||||
func metricFormatter(m metricdata.Metrics) string {
|
||||
return metricPrefix + strings.ReplaceAll(string(m.Name), ".", "/")
|
||||
}
|
||||
|
||||
133
vendor/cloud.google.com/go/storage/grpc_reader.go
generated
vendored
133
vendor/cloud.google.com/go/storage/grpc_reader.go
generated
vendored
@@ -183,14 +183,32 @@ func (c *grpcStorageClient) NewRangeReaderReadObject(ctx context.Context, params
|
||||
obj := msg.GetMetadata()
|
||||
// This is the size of the entire object, even if only a range was requested.
|
||||
size := obj.GetSize()
|
||||
var chunkCRC uint32
|
||||
chunkCRCPresent := false
|
||||
if !params.disableCRCCheck &&
|
||||
msg.GetChecksummedData() != nil &&
|
||||
msg.GetChecksummedData().Crc32C != nil {
|
||||
chunkCRCPresent = true
|
||||
chunkCRC = *msg.GetChecksummedData().Crc32C
|
||||
}
|
||||
startOffset := params.offset
|
||||
if params.offset < 0 {
|
||||
startOffset = size + params.offset
|
||||
}
|
||||
// If caller has specified a negative start offset that's larger than the
|
||||
// reported size, start at the beginning of the object.
|
||||
if startOffset < 0 {
|
||||
startOffset = 0
|
||||
}
|
||||
|
||||
// Only support checksums when reading an entire object, not a range.
|
||||
var (
|
||||
wantCRC uint32
|
||||
checkCRC bool
|
||||
)
|
||||
if checksums := msg.GetObjectChecksums(); checksums != nil && checksums.Crc32C != nil {
|
||||
if params.offset == 0 && params.length < 0 {
|
||||
if !params.disableCRCCheck &&
|
||||
startOffset == 0 &&
|
||||
(params.length < 0 || (obj != nil && params.length >= size)) {
|
||||
checkCRC = true
|
||||
}
|
||||
wantCRC = checksums.GetCrc32C()
|
||||
@@ -215,13 +233,18 @@ func (c *grpcStorageClient) NewRangeReaderReadObject(ctx context.Context, params
|
||||
cancel: cancel,
|
||||
size: size,
|
||||
// Preserve the decoder to read out object data when Read/WriteTo is called.
|
||||
currMsg: res.decoder,
|
||||
settings: s,
|
||||
zeroRange: params.length == 0,
|
||||
wantCRC: wantCRC,
|
||||
checkCRC: checkCRC,
|
||||
currMsg: res.decoder,
|
||||
wantChunkCRC: chunkCRC,
|
||||
chunkCRCPresent: chunkCRCPresent,
|
||||
settings: s,
|
||||
zeroRange: params.length == 0,
|
||||
wantCRC: wantCRC,
|
||||
checkCRC: checkCRC,
|
||||
disableCRCCheck: params.disableCRCCheck,
|
||||
},
|
||||
checkCRC: checkCRC,
|
||||
bucket: params.bucket,
|
||||
object: params.object,
|
||||
}
|
||||
|
||||
cr := msg.GetContentRange()
|
||||
@@ -248,17 +271,21 @@ type readStreamResponseReadObject struct {
|
||||
}
|
||||
|
||||
type gRPCReadObjectReader struct {
|
||||
seen, size int64
|
||||
zeroRange bool
|
||||
stream storagepb.Storage_ReadObjectClient
|
||||
reopen func(seen int64) (*readStreamResponseReadObject, context.CancelFunc, error)
|
||||
leftovers []byte
|
||||
currMsg *readObjectResponseDecoder // decoder for the current message
|
||||
cancel context.CancelFunc
|
||||
settings *settings
|
||||
checkCRC bool // should we check the CRC?
|
||||
wantCRC uint32 // the CRC32c value the server sent in the header
|
||||
gotCRC uint32 // running crc
|
||||
seen, size int64
|
||||
zeroRange bool
|
||||
stream storagepb.Storage_ReadObjectClient
|
||||
reopen func(seen int64) (*readStreamResponseReadObject, context.CancelFunc, error)
|
||||
leftovers []byte
|
||||
currMsg *readObjectResponseDecoder // decoder for the current message
|
||||
wantChunkCRC uint32
|
||||
chunkCRCPresent bool
|
||||
cancel context.CancelFunc
|
||||
settings *settings
|
||||
checkCRC bool // should we check the CRC?
|
||||
wantCRC uint32 // the CRC32c value the server sent in the header
|
||||
gotCRC uint32 // running crc
|
||||
gotChunkCRC uint32 // running crc32c of chunk
|
||||
disableCRCCheck bool
|
||||
}
|
||||
|
||||
// Update the running CRC with the data in the slice, if CRC checking was enabled.
|
||||
@@ -266,6 +293,9 @@ func (r *gRPCReadObjectReader) updateCRC(b []byte) {
|
||||
if r.checkCRC {
|
||||
r.gotCRC = crc32.Update(r.gotCRC, crc32cTable, b)
|
||||
}
|
||||
if r.chunkCRCPresent {
|
||||
r.gotChunkCRC = crc32.Update(r.gotChunkCRC, crc32cTable, b)
|
||||
}
|
||||
}
|
||||
|
||||
// Checks whether the CRC matches at the conclusion of a read, if CRC checking was enabled.
|
||||
@@ -276,6 +306,17 @@ func (r *gRPCReadObjectReader) runCRCCheck() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkAndResetChunkCRC verifies the chunk CRC if present, and resets the chunk CRC state.
|
||||
func (r *gRPCReadObjectReader) checkAndResetChunkCRC() error {
|
||||
if r.chunkCRCPresent && r.gotChunkCRC != r.wantChunkCRC {
|
||||
return fmt.Errorf("storage: bad CRC on chunk read: got %d, want %d", r.gotChunkCRC, r.wantChunkCRC)
|
||||
}
|
||||
r.gotChunkCRC = 0
|
||||
r.chunkCRCPresent = false
|
||||
r.wantChunkCRC = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read reads bytes into the user's buffer from an open gRPC stream.
|
||||
func (r *gRPCReadObjectReader) Read(p []byte) (int, error) {
|
||||
// The entire object has been read by this reader, check the checksum if
|
||||
@@ -305,16 +346,38 @@ func (r *gRPCReadObjectReader) Read(p []byte) (int, error) {
|
||||
r.updateCRC(b)
|
||||
})
|
||||
r.seen += int64(n)
|
||||
if r.currMsg.done {
|
||||
if err := r.checkAndResetChunkCRC(); err != nil {
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
return n, nil
|
||||
} else if err := r.checkAndResetChunkCRC(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Attempt to Recv the next message on the stream.
|
||||
// This will update r.currMsg with the decoder for the new message.
|
||||
err := r.recv()
|
||||
if err == io.EOF {
|
||||
if err := r.runCRCCheck(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return 0, io.EOF
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
msg := r.currMsg.msg
|
||||
if !r.disableCRCCheck &&
|
||||
msg.GetChecksummedData() != nil &&
|
||||
msg.GetChecksummedData().Crc32C != nil {
|
||||
r.wantChunkCRC = *msg.GetChecksummedData().Crc32C
|
||||
r.chunkCRCPresent = true
|
||||
r.gotChunkCRC = 0
|
||||
}
|
||||
|
||||
// TODO: Determine if we need to capture incremental CRC32C for this
|
||||
// chunk. The Object CRC32C checksum is captured when directed to read
|
||||
// the entire Object. If directed to read a range, we may need to
|
||||
@@ -327,6 +390,11 @@ func (r *gRPCReadObjectReader) Read(p []byte) (int, error) {
|
||||
r.updateCRC(b)
|
||||
})
|
||||
r.seen += int64(n)
|
||||
if r.currMsg.done {
|
||||
if err := r.checkAndResetChunkCRC(); err != nil {
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
@@ -360,8 +428,19 @@ func (r *gRPCReadObjectReader) WriteTo(w io.Writer) (int64, error) {
|
||||
r.updateCRC(b)
|
||||
})
|
||||
r.seen += int64(written)
|
||||
r.currMsg = nil
|
||||
if err != nil {
|
||||
r.currMsg = nil
|
||||
return r.seen - alreadySeen, err
|
||||
}
|
||||
if r.currMsg.done {
|
||||
if err := r.checkAndResetChunkCRC(); err != nil {
|
||||
r.currMsg = nil
|
||||
return r.seen - alreadySeen, err
|
||||
}
|
||||
}
|
||||
r.currMsg = nil
|
||||
} else if r.currMsg != nil {
|
||||
if err := r.checkAndResetChunkCRC(); err != nil {
|
||||
return r.seen - alreadySeen, err
|
||||
}
|
||||
}
|
||||
@@ -379,7 +458,14 @@ func (r *gRPCReadObjectReader) WriteTo(w io.Writer) (int64, error) {
|
||||
}
|
||||
return r.seen - alreadySeen, err
|
||||
}
|
||||
|
||||
msg := r.currMsg.msg
|
||||
if !r.disableCRCCheck &&
|
||||
msg.ChecksummedData != nil &&
|
||||
msg.ChecksummedData.Crc32C != nil {
|
||||
r.gotChunkCRC = 0
|
||||
r.wantChunkCRC = *msg.ChecksummedData.Crc32C
|
||||
r.chunkCRCPresent = true
|
||||
}
|
||||
// TODO: Determine if we need to capture incremental CRC32C for this
|
||||
// chunk. The Object CRC32C checksum is captured when directed to read
|
||||
// the entire Object. If directed to read a range, we may need to
|
||||
@@ -394,6 +480,11 @@ func (r *gRPCReadObjectReader) WriteTo(w io.Writer) (int64, error) {
|
||||
if err != nil {
|
||||
return r.seen - alreadySeen, err
|
||||
}
|
||||
if r.currMsg != nil && r.currMsg.done {
|
||||
if err := r.checkAndResetChunkCRC(); err != nil {
|
||||
return r.seen - alreadySeen, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -596,7 +687,7 @@ func (d *readObjectResponseDecoder) writeToAndUpdateCRC(w io.Writer, updateCRC f
|
||||
// Write all remaining data from the current buffer
|
||||
n, err := w.Write(b)
|
||||
written += int64(n)
|
||||
updateCRC(b)
|
||||
updateCRC(b[:n])
|
||||
if err != nil {
|
||||
return written, err
|
||||
}
|
||||
|
||||
24
vendor/cloud.google.com/go/storage/grpc_reader_multi_range.go
generated
vendored
24
vendor/cloud.google.com/go/storage/grpc_reader_multi_range.go
generated
vendored
@@ -20,6 +20,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"sync"
|
||||
|
||||
"cloud.google.com/go/storage/internal/apiv2/storagepb"
|
||||
@@ -769,7 +770,7 @@ func (m *multiRangeDownloaderManager) createNewSession(id int, readSpec *storage
|
||||
newSession = session
|
||||
firstResult = result
|
||||
return nil
|
||||
}, retry, true)
|
||||
}, retry, true, withOperation("ReadObject"), withBucket(m.params.bucket), withObject(m.params.object))
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -1010,6 +1011,10 @@ func (m *multiRangeDownloaderManager) processSessionResult(result mrdSessionResu
|
||||
m.handleStreamEnd(result, m.streams[result.id])
|
||||
return
|
||||
}
|
||||
// Safety check but this should not happen.
|
||||
if result.decoder == nil {
|
||||
return
|
||||
}
|
||||
|
||||
resp := result.decoder.msg
|
||||
if handle := resp.GetReadHandle().GetHandle(); len(handle) > 0 {
|
||||
@@ -1057,6 +1062,7 @@ func (m *multiRangeDownloaderManager) processDataRanges(result mrdSessionResult,
|
||||
if !exists || req.completed {
|
||||
continue
|
||||
}
|
||||
|
||||
written, _, err := result.decoder.writeToAndUpdateCRC(req.output, readID, nil)
|
||||
req.bytesWritten += written
|
||||
mrdStream.updateCapacity(m, 0, -written)
|
||||
@@ -1065,10 +1071,18 @@ func (m *multiRangeDownloaderManager) processDataRanges(result mrdSessionResult,
|
||||
continue
|
||||
}
|
||||
|
||||
if result.decoder.crcErrs != nil && result.decoder.crcErrs[readID] != nil {
|
||||
m.failRange(mrdStream, req, result.decoder.crcErrs[readID])
|
||||
continue
|
||||
}
|
||||
|
||||
if dataRange.GetRangeEnd() {
|
||||
req.completed = true
|
||||
delete(mrdStream.pendingRanges, req.readID)
|
||||
delete(mrdStream.pendingRanges, readID)
|
||||
mrdStream.updateCapacity(m, -1, 0)
|
||||
if req.length >= 0 && req.bytesWritten > req.length {
|
||||
log.Printf("storage: received %d more bytes than requested from GCS for bucket %q, object %q", req.bytesWritten-req.length, m.params.bucket, m.params.object)
|
||||
}
|
||||
m.runCallback(req.origOffset, req.bytesWritten, nil, req.callback)
|
||||
}
|
||||
}
|
||||
@@ -1140,6 +1154,9 @@ func (m *multiRangeDownloaderManager) failRange(mrdStream *mrdStream, req *range
|
||||
mrdStream.updateCapacity(m, -1, -(req.length - req.bytesWritten))
|
||||
}
|
||||
}
|
||||
if req.length >= 0 && req.bytesWritten > req.length {
|
||||
log.Printf("storage: received %d more bytes than requested from GCS for bucket %q, object %q", req.bytesWritten-req.length, m.params.bucket, m.params.object)
|
||||
}
|
||||
m.runCallback(req.origOffset, req.bytesWritten, err, req.callback)
|
||||
}
|
||||
|
||||
@@ -1331,6 +1348,9 @@ func (s *bidiReadStreamSession) receiveLoop() {
|
||||
databufs: databufs,
|
||||
}
|
||||
err = decoder.readFullObjectResponse()
|
||||
if err == nil && !s.params.disableMRDReadChecksum {
|
||||
decoder.verifyChecksums()
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
||||
74
vendor/cloud.google.com/go/storage/grpc_writer.go
generated
vendored
74
vendor/cloud.google.com/go/storage/grpc_writer.go
generated
vendored
@@ -58,7 +58,8 @@ func (w *gRPCWriter) Write(p []byte) (n int, err error) {
|
||||
// Skip checksum calculation if user configures MD5 or CRC32C themselves.
|
||||
if !w.disableAutoChecksum &&
|
||||
!w.sendCRC32C &&
|
||||
!md5Provided {
|
||||
!md5Provided &&
|
||||
!w.append {
|
||||
w.fullObjectChecksum = crc32.Update(w.fullObjectChecksum, crc32cTable, p)
|
||||
}
|
||||
// write command successfully delivered to sender. We no longer own cmd.
|
||||
@@ -109,6 +110,11 @@ func (w *gRPCWriter) CloseWithError(err error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *gRPCWriter) setAppendFinalCRC32C(sendAppendFinalCRC32C bool, c uint32) {
|
||||
w.sendAppendFinalCRC32C = sendAppendFinalCRC32C
|
||||
w.appendFinalCRC32C = c
|
||||
}
|
||||
|
||||
func (c *grpcStorageClient) OpenWriter(params *openWriterParams, opts ...storageOption) (internalWriter, error) {
|
||||
if params.attrs.Retention != nil {
|
||||
// TO-DO: remove once ObjectRetention is available - see b/308194853
|
||||
@@ -259,7 +265,9 @@ type gRPCWriter struct {
|
||||
setSize func(int64)
|
||||
setTakeoverOffset func(int64)
|
||||
|
||||
fullObjectChecksum uint32
|
||||
fullObjectChecksum uint32
|
||||
appendFinalCRC32C uint32
|
||||
sendAppendFinalCRC32C bool
|
||||
|
||||
flushSupported bool
|
||||
sendCRC32C bool
|
||||
@@ -948,9 +956,9 @@ type getObjectChecksumsParams struct {
|
||||
sendCRC32C bool
|
||||
disableAutoChecksum bool
|
||||
objectAttrs *ObjectAttrs
|
||||
fullObjectChecksum func() uint32
|
||||
fullObjectChecksum func() *uint32
|
||||
finishWrite bool
|
||||
takeoverWriter bool
|
||||
append bool
|
||||
}
|
||||
|
||||
// getObjectChecksums determines what checksum information to include in the final
|
||||
@@ -965,20 +973,30 @@ func getObjectChecksums(params *getObjectChecksumsParams) *storagepb.ObjectCheck
|
||||
return nil
|
||||
}
|
||||
|
||||
// For append operations, send user's final append checksum on last write op if available.
|
||||
// Auto checksum is not supported for appendable writes.
|
||||
var crc32c *uint32
|
||||
if params.fullObjectChecksum != nil {
|
||||
crc32c = params.fullObjectChecksum()
|
||||
}
|
||||
|
||||
if params.append && crc32c != nil {
|
||||
return &storagepb.ObjectChecksums{Crc32C: crc32c}
|
||||
}
|
||||
|
||||
// send user's checksum on last write op if available
|
||||
if params.sendCRC32C || (params.objectAttrs != nil && params.objectAttrs.MD5 != nil) {
|
||||
return toProtoChecksums(params.sendCRC32C, params.objectAttrs)
|
||||
}
|
||||
// TODO(b/461982277): Enable checksum validation for appendable takeover writer gRPC
|
||||
if params.disableAutoChecksum || params.takeoverWriter {
|
||||
|
||||
if params.append || params.disableAutoChecksum || params.fullObjectChecksum == nil {
|
||||
return nil
|
||||
}
|
||||
if params.fullObjectChecksum == nil {
|
||||
return nil
|
||||
}
|
||||
return &storagepb.ObjectChecksums{
|
||||
Crc32C: proto.Uint32(params.fullObjectChecksum()),
|
||||
|
||||
if crc32c != nil {
|
||||
return &storagepb.ObjectChecksums{Crc32C: crc32c}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type gRPCBidiWriteBufferSender interface {
|
||||
@@ -1014,7 +1032,7 @@ type gRPCOneshotBidiWriteBufferSender struct {
|
||||
sendCRC32C bool
|
||||
disableAutoChecksum bool
|
||||
objectAttrs *ObjectAttrs
|
||||
fullObjectChecksum func() uint32
|
||||
fullObjectChecksum func() *uint32
|
||||
}
|
||||
|
||||
func (w *gRPCWriter) newGRPCOneshotBidiWriteBufferSender() *gRPCOneshotBidiWriteBufferSender {
|
||||
@@ -1031,8 +1049,9 @@ func (w *gRPCWriter) newGRPCOneshotBidiWriteBufferSender() *gRPCOneshotBidiWrite
|
||||
sendCRC32C: w.sendCRC32C,
|
||||
disableAutoChecksum: w.disableAutoChecksum,
|
||||
objectAttrs: w.attrs,
|
||||
fullObjectChecksum: func() uint32 {
|
||||
return w.fullObjectChecksum
|
||||
fullObjectChecksum: func() *uint32 {
|
||||
checksum := w.fullObjectChecksum
|
||||
return &checksum
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1151,7 +1170,7 @@ type gRPCResumableBidiWriteBufferSender struct {
|
||||
sendCRC32C bool
|
||||
disableAutoChecksum bool
|
||||
objectAttrs *ObjectAttrs
|
||||
fullObjectChecksum func() uint32
|
||||
fullObjectChecksum func() *uint32
|
||||
|
||||
streamErr error
|
||||
}
|
||||
@@ -1168,8 +1187,9 @@ func (w *gRPCWriter) newGRPCResumableBidiWriteBufferSender() *gRPCResumableBidiW
|
||||
sendCRC32C: w.sendCRC32C,
|
||||
disableAutoChecksum: w.disableAutoChecksum,
|
||||
objectAttrs: w.attrs,
|
||||
fullObjectChecksum: func() uint32 {
|
||||
return w.fullObjectChecksum
|
||||
fullObjectChecksum: func() *uint32 {
|
||||
checksum := w.fullObjectChecksum
|
||||
return &checksum
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1299,7 +1319,7 @@ type gRPCAppendBidiWriteBufferSender struct {
|
||||
sendCRC32C bool
|
||||
disableAutoChecksum bool
|
||||
objectAttrs *ObjectAttrs
|
||||
fullObjectChecksum func() uint32
|
||||
fullObjectChecksum func() *uint32
|
||||
|
||||
takeoverWriter bool
|
||||
|
||||
@@ -1323,8 +1343,12 @@ func (w *gRPCWriter) newGRPCAppendableObjectBufferSender() *gRPCAppendBidiWriteB
|
||||
sendCRC32C: w.sendCRC32C,
|
||||
disableAutoChecksum: w.disableAutoChecksum,
|
||||
objectAttrs: w.attrs,
|
||||
fullObjectChecksum: func() uint32 {
|
||||
return w.fullObjectChecksum
|
||||
fullObjectChecksum: func() *uint32 {
|
||||
if !w.sendAppendFinalCRC32C {
|
||||
return nil
|
||||
}
|
||||
checksum := w.appendFinalCRC32C
|
||||
return &checksum
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1439,8 +1463,12 @@ func (w *gRPCWriter) newGRPCAppendTakeoverWriteBufferSender() *gRPCAppendTakeove
|
||||
sendCRC32C: w.sendCRC32C,
|
||||
disableAutoChecksum: w.disableAutoChecksum,
|
||||
objectAttrs: w.attrs,
|
||||
fullObjectChecksum: func() uint32 {
|
||||
return w.fullObjectChecksum
|
||||
fullObjectChecksum: func() *uint32 {
|
||||
if !w.sendAppendFinalCRC32C {
|
||||
return nil
|
||||
}
|
||||
checksum := w.appendFinalCRC32C
|
||||
return &checksum
|
||||
},
|
||||
},
|
||||
takeoverReported: false,
|
||||
@@ -1582,7 +1610,7 @@ func (s *gRPCAppendBidiWriteBufferSender) send(stream storagepb.Storage_BidiWrit
|
||||
fullObjectChecksum: s.fullObjectChecksum,
|
||||
disableAutoChecksum: s.disableAutoChecksum,
|
||||
finishWrite: finalizeObject,
|
||||
takeoverWriter: s.takeoverWriter,
|
||||
append: true,
|
||||
})
|
||||
req := bidiWriteObjectRequest(r, bufChecksum, objectChecksums)
|
||||
if sendFirstMessage {
|
||||
|
||||
153
vendor/cloud.google.com/go/storage/http_client.go
generated
vendored
153
vendor/cloud.google.com/go/storage/http_client.go
generated
vendored
@@ -45,8 +45,6 @@ import (
|
||||
|
||||
// httpStorageClient is the HTTP-JSON API implementation of the transport-agnostic
|
||||
// storageClient interface.
|
||||
//
|
||||
// TODO(b/498422946): Add client feature tracker in HTTP client.
|
||||
type httpStorageClient struct {
|
||||
creds *auth.Credentials
|
||||
hc *http.Client
|
||||
@@ -56,11 +54,17 @@ type httpStorageClient struct {
|
||||
settings *settings
|
||||
config *storageConfig
|
||||
dynamicReadReqStallTimeout *bucketDelayManager
|
||||
metrics *clientMetrics
|
||||
metricsCleanup func()
|
||||
|
||||
// configFeatureAttributes tracks client-level features that are enabled for this
|
||||
// client instance.
|
||||
configFeatureAttributes uint32
|
||||
}
|
||||
|
||||
// newHTTPStorageClient initializes a new storageClient that uses the HTTP-JSON
|
||||
// Storage API.
|
||||
func newHTTPStorageClient(ctx context.Context, opts ...storageOption) (storageClient, error) {
|
||||
func newHTTPStorageClient(ctx context.Context, opts ...storageOption) (client storageClient, err error) {
|
||||
s := initSettings(opts...)
|
||||
o := s.clientOption
|
||||
config := newStorageConfig(o...)
|
||||
@@ -119,8 +123,50 @@ func newHTTPStorageClient(ctx context.Context, opts ...storageOption) (storageCl
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("dialing: %w", err)
|
||||
}
|
||||
|
||||
var clientMetrics *clientMetrics
|
||||
var metricsCleanup func()
|
||||
if isOtelMetricsEnabled(&config) {
|
||||
var project string
|
||||
if creds != nil {
|
||||
project, _ = creds.ProjectID(ctx)
|
||||
}
|
||||
clientMetrics, metricsCleanup = initClientMetrics(ctx, project, &config)
|
||||
}
|
||||
if metricsCleanup != nil {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
metricsCleanup()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Clone the http.Client to avoid modifying the original one if it was provided by the user.
|
||||
hcClone := *hc
|
||||
c := &httpStorageClient{
|
||||
creds: creds,
|
||||
hc: &hcClone,
|
||||
settings: s,
|
||||
config: &config,
|
||||
metrics: clientMetrics,
|
||||
metricsCleanup: metricsCleanup,
|
||||
}
|
||||
|
||||
// Wrap transport to inject tracking headers and metrics.
|
||||
transport := hc.Transport
|
||||
if clientMetrics != nil {
|
||||
transport = &metricsRoundTripper{
|
||||
base: transport,
|
||||
metrics: clientMetrics,
|
||||
}
|
||||
}
|
||||
hcClone.Transport = &trackingTransport{
|
||||
base: transport,
|
||||
features: c.configFeatureAttributes,
|
||||
}
|
||||
|
||||
// RawService should be created with the chosen endpoint to take account of user override.
|
||||
rawService, err := raw.NewService(ctx, option.WithEndpoint(ep), option.WithHTTPClient(hc))
|
||||
rawService, err := raw.NewService(ctx, option.WithEndpoint(ep), option.WithHTTPClient(c.hc))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("storage client: %w", err)
|
||||
}
|
||||
@@ -144,23 +190,59 @@ func newHTTPStorageClient(ctx context.Context, opts ...storageOption) (storageCl
|
||||
}
|
||||
}
|
||||
|
||||
return &httpStorageClient{
|
||||
creds: creds,
|
||||
hc: hc,
|
||||
xmlHost: u.Host,
|
||||
raw: rawService,
|
||||
scheme: u.Scheme,
|
||||
settings: s,
|
||||
config: &config,
|
||||
dynamicReadReqStallTimeout: bd,
|
||||
}, nil
|
||||
c.xmlHost = u.Host
|
||||
c.raw = rawService
|
||||
c.scheme = u.Scheme
|
||||
c.dynamicReadReqStallTimeout = bd
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *httpStorageClient) Close() error {
|
||||
c.hc.CloseIdleConnections()
|
||||
if c.metricsCleanup != nil {
|
||||
c.metricsCleanup()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// trackingTransport wraps an http.RoundTripper to inject feature tracking headers.
|
||||
type trackingTransport struct {
|
||||
base http.RoundTripper
|
||||
features uint32
|
||||
}
|
||||
|
||||
func (t *trackingTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
baseRT := t.base
|
||||
if baseRT == nil {
|
||||
baseRT = http.DefaultTransport
|
||||
}
|
||||
features := t.features | featureAttributes(req.Context())
|
||||
// Merge all existing headers for this key.
|
||||
features |= mergeFeatureAttributes(req.Header.Values(featureTrackerHeaderName))
|
||||
if features > 0 {
|
||||
// Clone the request to avoid modifying the original one.
|
||||
clonedReq := req.Clone(req.Context())
|
||||
clonedReq.Header.Set(featureTrackerHeaderName, encodeUint32(features))
|
||||
return baseRT.RoundTrip(clonedReq)
|
||||
}
|
||||
|
||||
return baseRT.RoundTrip(req)
|
||||
}
|
||||
|
||||
func (t *trackingTransport) CloseIdleConnections() {
|
||||
type closeIdler interface {
|
||||
CloseIdleConnections()
|
||||
}
|
||||
base := t.base
|
||||
if base == nil {
|
||||
base = http.DefaultTransport
|
||||
}
|
||||
if tr, ok := base.(closeIdler); ok {
|
||||
tr.CloseIdleConnections()
|
||||
}
|
||||
}
|
||||
|
||||
// Top-level methods.
|
||||
|
||||
func (c *httpStorageClient) GetServiceAccount(ctx context.Context, project string, opts ...storageOption) (string, error) {
|
||||
@@ -222,6 +304,8 @@ func (c *httpStorageClient) ListBuckets(ctx context.Context, project string, opt
|
||||
}
|
||||
|
||||
fetch := func(pageSize int, pageToken string) (token string, err error) {
|
||||
ctx, record := startMetricsOp(it.ctx, "ListBuckets", true)
|
||||
defer func() { record(err) }()
|
||||
req := c.raw.Buckets.List(it.projectID)
|
||||
req.Projection("full")
|
||||
req.Prefix(it.Prefix)
|
||||
@@ -231,15 +315,16 @@ func (c *httpStorageClient) ListBuckets(ctx context.Context, project string, opt
|
||||
req.MaxResults(int64(pageSize))
|
||||
}
|
||||
var resp *raw.Buckets
|
||||
err = run(it.ctx, func(ctx context.Context) error {
|
||||
err = run(ctx, func(ctx context.Context) error {
|
||||
resp, err = req.Context(ctx).Do()
|
||||
return err
|
||||
}, s.retry, s.idempotent)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var b *BucketAttrs
|
||||
for _, item := range resp.Items {
|
||||
b, err := newBucket(item)
|
||||
b, err = newBucket(item)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -348,6 +433,8 @@ func (c *httpStorageClient) ListObjects(ctx context.Context, bucket string, q *Q
|
||||
}
|
||||
fetch := func(pageSize int, pageToken string) (string, error) {
|
||||
var err error
|
||||
ctx, record := startMetricsOp(it.ctx, "ListObjects", true)
|
||||
defer func() { record(err) }()
|
||||
// Add trace span around List API call within the fetch.
|
||||
ctx, _ = startSpan(ctx, "httpStorageClient.ObjectsListCall")
|
||||
defer func() { endSpan(ctx, err) }()
|
||||
@@ -385,7 +472,7 @@ func (c *httpStorageClient) ListObjects(ctx context.Context, bucket string, q *Q
|
||||
req.MaxResults(int64(pageSize))
|
||||
}
|
||||
var resp *raw.Objects
|
||||
err = run(it.ctx, func(ctx context.Context) error {
|
||||
err = run(ctx, func(ctx context.Context) error {
|
||||
resp, err = req.Context(ctx).Do()
|
||||
return err
|
||||
}, s.retry, s.idempotent, withOperation("ListObjects"), withObject(it.query.Prefix), withBucket(bucket))
|
||||
@@ -767,7 +854,9 @@ func (c *httpStorageClient) UpdateObjectACL(ctx context.Context, bucket, object
|
||||
|
||||
func (c *httpStorageClient) ComposeObject(ctx context.Context, req *composeObjectRequest, opts ...storageOption) (*ObjectAttrs, error) {
|
||||
s := callSettings(c.settings, opts...)
|
||||
rawReq := &raw.ComposeRequest{}
|
||||
rawReq := &raw.ComposeRequest{
|
||||
DeleteSourceObjects: req.deleteSourceObjects,
|
||||
}
|
||||
// Compose requires a non-empty Destination, so we always set it,
|
||||
// even if the caller-provided ObjectAttrs is the zero value.
|
||||
rawReq.Destination = req.dstObject.attrs.toRawObject(req.dstBucket)
|
||||
@@ -1031,6 +1120,9 @@ func (hiw *httpInternalWriter) Flush() (int64, error) {
|
||||
return 0, errors.New("Writer.Flush is only supported for gRPC-based clients")
|
||||
}
|
||||
|
||||
// Not supported on HTTP Client as this is for setting CRC on appendable objects.
|
||||
func (hiw *httpInternalWriter) setAppendFinalCRC32C(sendAppendFinalCRC32C bool, c uint32) {}
|
||||
|
||||
func (c *httpStorageClient) OpenWriter(params *openWriterParams, opts ...storageOption) (internalWriter, error) {
|
||||
if params.append {
|
||||
return nil, errors.New("storage: append not supported on HTTP Client; use gRPC")
|
||||
@@ -1235,6 +1327,8 @@ func (c *httpStorageClient) ListHMACKeys(ctx context.Context, project, serviceAc
|
||||
retry: s.retry,
|
||||
}
|
||||
fetch := func(pageSize int, pageToken string) (token string, err error) {
|
||||
ctx, record := startMetricsOp(it.ctx, "ListHMACKeys", true)
|
||||
defer func() { record(err) }()
|
||||
call := c.raw.Projects.HmacKeys.List(project)
|
||||
if pageToken != "" {
|
||||
call = call.PageToken(pageToken)
|
||||
@@ -1253,7 +1347,7 @@ func (c *httpStorageClient) ListHMACKeys(ctx context.Context, project, serviceAc
|
||||
}
|
||||
|
||||
var resp *raw.HmacKeysMetadata
|
||||
err = run(it.ctx, func(ctx context.Context) error {
|
||||
err = run(ctx, func(ctx context.Context) error {
|
||||
resp, err = call.Context(ctx).Do()
|
||||
return err
|
||||
}, s.retry, s.idempotent)
|
||||
@@ -1261,11 +1355,12 @@ func (c *httpStorageClient) ListHMACKeys(ctx context.Context, project, serviceAc
|
||||
return "", err
|
||||
}
|
||||
|
||||
var hkey *HMACKey
|
||||
for _, metadata := range resp.Items {
|
||||
hk := &raw.HmacKey{
|
||||
Metadata: metadata,
|
||||
}
|
||||
hkey, err := toHMACKeyFromRaw(hk, true)
|
||||
hkey, err = toHMACKeyFromRaw(hk, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -1406,7 +1501,7 @@ func (r *httpReader) Read(p []byte) (int, error) {
|
||||
n += m
|
||||
r.seen += int64(m)
|
||||
if r.checkCRC {
|
||||
r.gotCRC = crc32.Update(r.gotCRC, crc32cTable, p[:n])
|
||||
r.gotCRC = crc32.Update(r.gotCRC, crc32cTable, p[n-m:n])
|
||||
}
|
||||
if err == nil {
|
||||
return n, nil
|
||||
@@ -1535,7 +1630,7 @@ func readerReopen(ctx context.Context, header http.Header, params *newRangeReade
|
||||
params.gen = gen64
|
||||
}
|
||||
return nil
|
||||
}, s.retry, s.idempotent)
|
||||
}, s.retry, s.idempotent, withOperation("ReadObject"), withBucket(params.bucket), withObject(params.object))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1582,6 +1677,7 @@ func parseReadResponse(res *http.Response, params *newRangeReaderParams, reopen
|
||||
}
|
||||
|
||||
// Check the CRC iff all of the following hold:
|
||||
// - The user did not disable CRC check.
|
||||
// - We asked for content (length != 0).
|
||||
// - We got all the content (status != PartialContent).
|
||||
// - The server sent a CRC header.
|
||||
@@ -1591,7 +1687,7 @@ func parseReadResponse(res *http.Response, params *newRangeReaderParams, reopen
|
||||
// computes it on the compressed contents, but we compute it on the
|
||||
// uncompressed contents.
|
||||
crc, checkCRC = parseCRC32c(res)
|
||||
if params.length == 0 || res.StatusCode == http.StatusPartialContent || res.Uncompressed || uncompressedByServer(res) {
|
||||
if params.disableCRCCheck || params.length == 0 || res.StatusCode == http.StatusPartialContent || res.Uncompressed || uncompressedByServer(res) {
|
||||
checkCRC = false
|
||||
}
|
||||
|
||||
@@ -1652,6 +1748,8 @@ func parseReadResponse(res *http.Response, params *newRangeReaderParams, reopen
|
||||
wantCRC: crc,
|
||||
checkCRC: checkCRC,
|
||||
},
|
||||
bucket: params.bucket,
|
||||
object: params.object,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -1681,3 +1779,12 @@ func setHeadersFromCtx(ctx context.Context, header http.Header) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *httpStorageClient) fetchBucketMetadata(ctx context.Context, bucket string) (string, string, error) {
|
||||
resp, err := c.raw.Buckets.Get(bucket).Projection("noAcl").Context(ctx).Do()
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
resource, location := getMetadataFromAttrs(resp.Location, resp.LocationType, strconv.FormatUint(resp.ProjectNumber, 10), bucket)
|
||||
return resource, location, nil
|
||||
}
|
||||
|
||||
8
vendor/cloud.google.com/go/storage/iam.go
generated
vendored
8
vendor/cloud.google.com/go/storage/iam.go
generated
vendored
@@ -29,6 +29,7 @@ func (b *BucketHandle) IAM() *iam.Handle {
|
||||
userProject: b.userProject,
|
||||
retry: b.retry,
|
||||
client: b.c,
|
||||
bucket: b.name,
|
||||
}, b.name)
|
||||
}
|
||||
|
||||
@@ -37,6 +38,7 @@ type iamClient struct {
|
||||
userProject string
|
||||
retry *retryConfig
|
||||
client *Client
|
||||
bucket string
|
||||
}
|
||||
|
||||
func (c *iamClient) Get(ctx context.Context, resource string) (p *iampb.Policy, err error) {
|
||||
@@ -44,7 +46,7 @@ func (c *iamClient) Get(ctx context.Context, resource string) (p *iampb.Policy,
|
||||
}
|
||||
|
||||
func (c *iamClient) GetWithVersion(ctx context.Context, resource string, requestedPolicyVersion int32) (p *iampb.Policy, err error) {
|
||||
ctx, _ = startSpan(ctx, "storage.IAM.Get")
|
||||
ctx, _ = startSpanWithBucket(ctx, c.client, c.bucket, "storage.IAM.Get")
|
||||
defer func() { endSpan(ctx, err) }()
|
||||
|
||||
o := makeStorageOpts(true, c.retry, c.userProject)
|
||||
@@ -52,7 +54,7 @@ func (c *iamClient) GetWithVersion(ctx context.Context, resource string, request
|
||||
}
|
||||
|
||||
func (c *iamClient) Set(ctx context.Context, resource string, p *iampb.Policy) (err error) {
|
||||
ctx, _ = startSpan(ctx, "storage.IAM.Set")
|
||||
ctx, _ = startSpanWithBucket(ctx, c.client, c.bucket, "storage.IAM.Set")
|
||||
defer func() { endSpan(ctx, err) }()
|
||||
|
||||
isIdempotent := len(p.Etag) > 0
|
||||
@@ -61,7 +63,7 @@ func (c *iamClient) Set(ctx context.Context, resource string, p *iampb.Policy) (
|
||||
}
|
||||
|
||||
func (c *iamClient) Test(ctx context.Context, resource string, perms []string) (permissions []string, err error) {
|
||||
ctx, _ = startSpan(ctx, "storage.IAM.Test")
|
||||
ctx, _ = startSpanWithBucket(ctx, c.client, c.bucket, "storage.IAM.Test")
|
||||
defer func() { endSpan(ctx, err) }()
|
||||
|
||||
o := makeStorageOpts(true, c.retry, c.userProject)
|
||||
|
||||
4
vendor/cloud.google.com/go/storage/internal/apiv2/storage_client.go
generated
vendored
4
vendor/cloud.google.com/go/storage/internal/apiv2/storage_client.go
generated
vendored
@@ -460,7 +460,7 @@ type Client struct {
|
||||
|
||||
// Wrapper methods routed to the internal client.
|
||||
|
||||
// Close closes the connection to the API service. The user should invoke this when
|
||||
// Close closes the connection to the API service. **Always** call Close() when
|
||||
// the client is no longer required.
|
||||
func (c *Client) Close() error {
|
||||
return c.internalClient.Close()
|
||||
@@ -1079,7 +1079,7 @@ func (c *gRPCClient) setGoogleClientInfo(keyval ...string) {
|
||||
}
|
||||
}
|
||||
|
||||
// Close closes the connection to the API service. The user should invoke this when
|
||||
// Close closes the connection to the API service. **Always** call Close() when
|
||||
// the client is no longer required.
|
||||
func (c *gRPCClient) Close() error {
|
||||
return c.connPool.Close()
|
||||
|
||||
4
vendor/cloud.google.com/go/storage/internal/experimental.go
generated
vendored
4
vendor/cloud.google.com/go/storage/internal/experimental.go
generated
vendored
@@ -47,4 +47,8 @@ var (
|
||||
// It sets the gRPC client to use direct path connectivity for all requests and may fail
|
||||
// if direct path connectivity cannot be established for a request.
|
||||
WithDirectConnectivityEnforced any // func() option.ClientOption
|
||||
|
||||
// WithOtelMetrics is a function which is implemented by the storage package.
|
||||
// It enables client-side OpenTelemetry metrics.
|
||||
WithOtelMetrics any // func() option.ClientOption
|
||||
)
|
||||
|
||||
2
vendor/cloud.google.com/go/storage/internal/version.go
generated
vendored
2
vendor/cloud.google.com/go/storage/internal/version.go
generated
vendored
@@ -17,4 +17,4 @@
|
||||
package internal
|
||||
|
||||
// Version is the current tagged release of the library.
|
||||
const Version = "1.62.3"
|
||||
const Version = "1.64.0"
|
||||
|
||||
15
vendor/cloud.google.com/go/storage/invoke.go
generated
vendored
15
vendor/cloud.google.com/go/storage/invoke.go
generated
vendored
@@ -246,3 +246,18 @@ func ShouldRetry(err error) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isError(err error, httpErrorCode int, grpcErrorCode codes.Code) bool {
|
||||
var e *googleapi.Error
|
||||
if errors.As(err, &e) {
|
||||
if e.Code == httpErrorCode {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if s, ok := status.FromError(err); ok {
|
||||
if s.Code() == grpcErrorCode {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
98
vendor/cloud.google.com/go/storage/lru.go
generated
vendored
Normal file
98
vendor/cloud.google.com/go/storage/lru.go
generated
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
// Copyright 2026 Google LLC
|
||||
//
|
||||
// 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 storage
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
)
|
||||
|
||||
// lruCache is a generic Least Recently Used cache. It is not thread-safe.
|
||||
type lruCache[K comparable, V any] struct {
|
||||
entries map[K]*list.Element
|
||||
evictList *list.List
|
||||
limit int
|
||||
}
|
||||
|
||||
type cacheEntry[K comparable, V any] struct {
|
||||
key K
|
||||
value V
|
||||
}
|
||||
|
||||
func newLRUCache[K comparable, V any](limit int) *lruCache[K, V] {
|
||||
return &lruCache[K, V]{
|
||||
entries: make(map[K]*list.Element),
|
||||
evictList: list.New(),
|
||||
limit: limit,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *lruCache[K, V]) get(key K) (V, bool) {
|
||||
if c == nil {
|
||||
var zero V
|
||||
return zero, false
|
||||
}
|
||||
|
||||
if elem, ok := c.entries[key]; ok {
|
||||
c.evictList.MoveToFront(elem)
|
||||
return elem.Value.(*cacheEntry[K, V]).value, true
|
||||
}
|
||||
var zero V
|
||||
return zero, false
|
||||
}
|
||||
|
||||
func (c *lruCache[K, V]) put(key K, val V) {
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// If element already exists, update the value and promote it.
|
||||
if elem, ok := c.entries[key]; ok {
|
||||
c.evictList.MoveToFront(elem)
|
||||
elem.Value.(*cacheEntry[K, V]).value = val
|
||||
return
|
||||
}
|
||||
|
||||
// Add new element to front
|
||||
elem := c.evictList.PushFront(&cacheEntry[K, V]{key: key, value: val})
|
||||
c.entries[key] = elem
|
||||
|
||||
// Evict oldest element if limit exceeded.
|
||||
if c.evictList.Len() > c.limit {
|
||||
c.removeOldest()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *lruCache[K, V]) evict(key K) {
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if elem, ok := c.entries[key]; ok {
|
||||
c.removeElement(elem)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *lruCache[K, V]) removeOldest() {
|
||||
elem := c.evictList.Back()
|
||||
if elem != nil {
|
||||
c.removeElement(elem)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *lruCache[K, V]) removeElement(elem *list.Element) {
|
||||
c.evictList.Remove(elem)
|
||||
kv := elem.Value.(*cacheEntry[K, V])
|
||||
delete(c.entries, kv.key)
|
||||
}
|
||||
1141
vendor/cloud.google.com/go/storage/metrics.go
generated
vendored
Normal file
1141
vendor/cloud.google.com/go/storage/metrics.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user