Compare commits

...

10 Commits

Author SHA1 Message Date
JAYICE
0aaf6f8a33 deployment/docker/rules: introduce "no backup within time" alerts (#1068)
The commit introduces the `vm_backup_last_success_at` metric and alerts based on it:
- NoLatestBackupWithinLastDay
- NoHourlyBackupWithinLastDay
- NoDailyBackupWithinLast3Days
- NoWeeklyBackupWithinLast14Days
- NoMonthlyBackupWithinLast62Days

PR https://github.com/VictoriaMetrics/VictoriaMetrics-enterprise/pull/1068

---------

Co-authored-by: Max Kotliar <mkotlyar@victoriametrics.com>
2026-07-23 17:15:41 +03:00
JAYICE
00a26b9d32 app/vmbackupmanager: don't exit when restoring backup state fails on startup (#1064)
Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11217

---------

Co-authored-by: Max Kotliar <mkotlyar@victoriametrics.com>
2026-07-23 17:07:29 +03:00
dependabot[bot]
43d4cc61dc build(deps): bump github/codeql-action/init from 4.36.2 to 4.36.3 (#11291)
Bumps
[github/codeql-action/init](https://github.com/github/codeql-action)
from 4.36.2 to 4.36.3.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/releases">github/codeql-action/init's
releases</a>.</em></p>
<blockquote>
<h2>v4.36.3</h2>
<p>No user facing changes.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action/init's
changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>[UNRELEASED]</h2>
<p>No user facing changes.</p>
<h2>4.37.3 - 22 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.2 - 21 Jul 2026</h2>
<ul>
<li>The new address format for the <code>config-file</code> input that
was introduced in CodeQL Action 4.37.0 is now enabled by default. In
addition to the format described there, the <code>remote=</code> prefix
can now be used to explicitly indicate that the input refers to a remote
file. All previous input formats continue to be accepted as well. <a
href="https://redirect.github.com/github/codeql-action/pull/4023">#4023</a></li>
<li>The CodeQL Action can now make use of <a
href="https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries">configured
private registries</a> in Default Setup to retrieve CodeQL configuration
files from remote repositories that require authentication. This will
allow customers to store their CodeQL configuration in a single
repository that can then be referenced by Default Setup workflows in
other repositories. We expect to roll this and other, related changes
out to everyone in July. <a
href="https://redirect.github.com/github/codeql-action/pull/4007">#4007</a></li>
</ul>
<h2>4.37.1 - 16 Jul 2026</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.20.6 and earlier. These versions of
CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise
Server 3.16, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3956">#3956</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.1">2.26.1</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4019">#4019</a></li>
</ul>
<h2>4.37.0 - 08 Jul 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.0">2.26.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3995">#3995</a></li>
<li>In addition to the existing input format, the
<code>config-file</code> input for the <code>codeql-action/init</code>
step will soon support a new <code>[owner/]repo[@ref][:path]</code>
format. All components except the repository name are optional. If
omitted, <code>owner</code> defaults to the same owner as the repository
the analysis is running for, <code>ref</code> to <code>main</code>, and
<code>path</code> to <code>.github/codeql-action.yaml</code>. Support
for this format ships in this version of the CodeQL Action, but will
only be enabled over the coming weeks. <a
href="https://redirect.github.com/github/codeql-action/pull/3973">#3973</a></li>
</ul>
<h2>4.36.3 - 01 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.36.2 - 04 Jun 2026</h2>
<ul>
<li>Cache CodeQL CLI version information across Actions steps. <a
href="https://redirect.github.com/github/codeql-action/pull/3943">#3943</a></li>
<li>Reduce requests while waiting for analysis processing by using
exponential backoff when polling SARIF processing status. <a
href="https://redirect.github.com/github/codeql-action/pull/3937">#3937</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.6">2.25.6</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3948">#3948</a></li>
</ul>
<h2>4.36.1 - 02 Jun 2026</h2>
<p>No user facing changes.</p>
<h2>4.36.0 - 22 May 2026</h2>
<ul>
<li><em>Breaking change</em>: Bump the minimum required CodeQL bundle
version to 2.19.4. <a
href="https://redirect.github.com/github/codeql-action/pull/3894">#3894</a></li>
<li>Add support for SHA-256 Git object IDs. <a
href="https://redirect.github.com/github/codeql-action/pull/3893">#3893</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.5">2.25.5</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3926">#3926</a></li>
</ul>
<h2>4.35.5 - 15 May 2026</h2>
<ul>
<li>We have improved how the JavaScript bundles for the CodeQL Action
are generated to avoid duplication across bundles and reduce the size of
the repository by around 70%. This should have no effect on the runtime
behaviour of the CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3899">#3899</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="54f647b7e1"><code>54f647b</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3984">#3984</a>
from github/update-v4.36.3-1f34ec164</li>
<li><a
href="e78819e055"><code>e78819e</code></a>
Trigger checks</li>
<li><a
href="2c9d3d63eb"><code>2c9d3d6</code></a>
Update changelog for v4.36.3</li>
<li><a
href="1f34ec1643"><code>1f34ec1</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3983">#3983</a>
from github/mbg/repo-props/ff-for-config-file-prop</li>
<li><a
href="d5f0145480"><code>d5f0145</code></a>
Log when repository property has a value but is ignored</li>
<li><a
href="f27f56386a"><code>f27f563</code></a>
Add test for when the FF is off</li>
<li><a
href="0025d0f2b5"><code>0025d0f</code></a>
Use FF</li>
<li><a
href="f7fa18f05d"><code>f7fa18f</code></a>
Add FF for config file repo property</li>
<li><a
href="628fc3f124"><code>628fc3f</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3979">#3979</a>
from github/henrymercer/overlay-db-cleanup-size-tele...</li>
<li><a
href="9cfb67bab9"><code>9cfb67b</code></a>
Add clarifying comments</li>
<li>Additional commits viewable in <a
href="8aad20d150...54f647b7e1">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github/codeql-action/init&package-manager=github_actions&previous-version=4.36.2&new-version=4.36.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-23 16:52:34 +03:00
dependabot[bot]
e85d61a98c build(deps): bump github/codeql-action/analyze from 4.36.2 to 4.36.3 (#11290)
Bumps
[github/codeql-action/analyze](https://github.com/github/codeql-action)
from 4.36.2 to 4.36.3.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/releases">github/codeql-action/analyze's
releases</a>.</em></p>
<blockquote>
<h2>v4.36.3</h2>
<p>No user facing changes.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action/analyze's
changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>[UNRELEASED]</h2>
<p>No user facing changes.</p>
<h2>4.37.3 - 22 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.2 - 21 Jul 2026</h2>
<ul>
<li>The new address format for the <code>config-file</code> input that
was introduced in CodeQL Action 4.37.0 is now enabled by default. In
addition to the format described there, the <code>remote=</code> prefix
can now be used to explicitly indicate that the input refers to a remote
file. All previous input formats continue to be accepted as well. <a
href="https://redirect.github.com/github/codeql-action/pull/4023">#4023</a></li>
<li>The CodeQL Action can now make use of <a
href="https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries">configured
private registries</a> in Default Setup to retrieve CodeQL configuration
files from remote repositories that require authentication. This will
allow customers to store their CodeQL configuration in a single
repository that can then be referenced by Default Setup workflows in
other repositories. We expect to roll this and other, related changes
out to everyone in July. <a
href="https://redirect.github.com/github/codeql-action/pull/4007">#4007</a></li>
</ul>
<h2>4.37.1 - 16 Jul 2026</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.20.6 and earlier. These versions of
CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise
Server 3.16, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3956">#3956</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.1">2.26.1</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4019">#4019</a></li>
</ul>
<h2>4.37.0 - 08 Jul 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.0">2.26.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3995">#3995</a></li>
<li>In addition to the existing input format, the
<code>config-file</code> input for the <code>codeql-action/init</code>
step will soon support a new <code>[owner/]repo[@ref][:path]</code>
format. All components except the repository name are optional. If
omitted, <code>owner</code> defaults to the same owner as the repository
the analysis is running for, <code>ref</code> to <code>main</code>, and
<code>path</code> to <code>.github/codeql-action.yaml</code>. Support
for this format ships in this version of the CodeQL Action, but will
only be enabled over the coming weeks. <a
href="https://redirect.github.com/github/codeql-action/pull/3973">#3973</a></li>
</ul>
<h2>4.36.3 - 01 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.36.2 - 04 Jun 2026</h2>
<ul>
<li>Cache CodeQL CLI version information across Actions steps. <a
href="https://redirect.github.com/github/codeql-action/pull/3943">#3943</a></li>
<li>Reduce requests while waiting for analysis processing by using
exponential backoff when polling SARIF processing status. <a
href="https://redirect.github.com/github/codeql-action/pull/3937">#3937</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.6">2.25.6</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3948">#3948</a></li>
</ul>
<h2>4.36.1 - 02 Jun 2026</h2>
<p>No user facing changes.</p>
<h2>4.36.0 - 22 May 2026</h2>
<ul>
<li><em>Breaking change</em>: Bump the minimum required CodeQL bundle
version to 2.19.4. <a
href="https://redirect.github.com/github/codeql-action/pull/3894">#3894</a></li>
<li>Add support for SHA-256 Git object IDs. <a
href="https://redirect.github.com/github/codeql-action/pull/3893">#3893</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.5">2.25.5</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3926">#3926</a></li>
</ul>
<h2>4.35.5 - 15 May 2026</h2>
<ul>
<li>We have improved how the JavaScript bundles for the CodeQL Action
are generated to avoid duplication across bundles and reduce the size of
the repository by around 70%. This should have no effect on the runtime
behaviour of the CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3899">#3899</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="54f647b7e1"><code>54f647b</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3984">#3984</a>
from github/update-v4.36.3-1f34ec164</li>
<li><a
href="e78819e055"><code>e78819e</code></a>
Trigger checks</li>
<li><a
href="2c9d3d63eb"><code>2c9d3d6</code></a>
Update changelog for v4.36.3</li>
<li><a
href="1f34ec1643"><code>1f34ec1</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3983">#3983</a>
from github/mbg/repo-props/ff-for-config-file-prop</li>
<li><a
href="d5f0145480"><code>d5f0145</code></a>
Log when repository property has a value but is ignored</li>
<li><a
href="f27f56386a"><code>f27f563</code></a>
Add test for when the FF is off</li>
<li><a
href="0025d0f2b5"><code>0025d0f</code></a>
Use FF</li>
<li><a
href="f7fa18f05d"><code>f7fa18f</code></a>
Add FF for config file repo property</li>
<li><a
href="628fc3f124"><code>628fc3f</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3979">#3979</a>
from github/henrymercer/overlay-db-cleanup-size-tele...</li>
<li><a
href="9cfb67bab9"><code>9cfb67b</code></a>
Add clarifying comments</li>
<li>Additional commits viewable in <a
href="8aad20d150...54f647b7e1">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github/codeql-action/analyze&package-manager=github_actions&previous-version=4.36.2&new-version=4.36.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-23 16:52:05 +03:00
dependabot[bot]
588e996bd5 build(deps): bump github/codeql-action/autobuild from 4.36.2 to 4.36.3 (#11289)
Bumps
[github/codeql-action/autobuild](https://github.com/github/codeql-action)
from 4.36.2 to 4.36.3.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/releases">github/codeql-action/autobuild's
releases</a>.</em></p>
<blockquote>
<h2>v4.36.3</h2>
<p>No user facing changes.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action/autobuild's
changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>[UNRELEASED]</h2>
<p>No user facing changes.</p>
<h2>4.37.3 - 22 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.2 - 21 Jul 2026</h2>
<ul>
<li>The new address format for the <code>config-file</code> input that
was introduced in CodeQL Action 4.37.0 is now enabled by default. In
addition to the format described there, the <code>remote=</code> prefix
can now be used to explicitly indicate that the input refers to a remote
file. All previous input formats continue to be accepted as well. <a
href="https://redirect.github.com/github/codeql-action/pull/4023">#4023</a></li>
<li>The CodeQL Action can now make use of <a
href="https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries">configured
private registries</a> in Default Setup to retrieve CodeQL configuration
files from remote repositories that require authentication. This will
allow customers to store their CodeQL configuration in a single
repository that can then be referenced by Default Setup workflows in
other repositories. We expect to roll this and other, related changes
out to everyone in July. <a
href="https://redirect.github.com/github/codeql-action/pull/4007">#4007</a></li>
</ul>
<h2>4.37.1 - 16 Jul 2026</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.20.6 and earlier. These versions of
CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise
Server 3.16, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3956">#3956</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.1">2.26.1</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4019">#4019</a></li>
</ul>
<h2>4.37.0 - 08 Jul 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.0">2.26.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3995">#3995</a></li>
<li>In addition to the existing input format, the
<code>config-file</code> input for the <code>codeql-action/init</code>
step will soon support a new <code>[owner/]repo[@ref][:path]</code>
format. All components except the repository name are optional. If
omitted, <code>owner</code> defaults to the same owner as the repository
the analysis is running for, <code>ref</code> to <code>main</code>, and
<code>path</code> to <code>.github/codeql-action.yaml</code>. Support
for this format ships in this version of the CodeQL Action, but will
only be enabled over the coming weeks. <a
href="https://redirect.github.com/github/codeql-action/pull/3973">#3973</a></li>
</ul>
<h2>4.36.3 - 01 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.36.2 - 04 Jun 2026</h2>
<ul>
<li>Cache CodeQL CLI version information across Actions steps. <a
href="https://redirect.github.com/github/codeql-action/pull/3943">#3943</a></li>
<li>Reduce requests while waiting for analysis processing by using
exponential backoff when polling SARIF processing status. <a
href="https://redirect.github.com/github/codeql-action/pull/3937">#3937</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.6">2.25.6</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3948">#3948</a></li>
</ul>
<h2>4.36.1 - 02 Jun 2026</h2>
<p>No user facing changes.</p>
<h2>4.36.0 - 22 May 2026</h2>
<ul>
<li><em>Breaking change</em>: Bump the minimum required CodeQL bundle
version to 2.19.4. <a
href="https://redirect.github.com/github/codeql-action/pull/3894">#3894</a></li>
<li>Add support for SHA-256 Git object IDs. <a
href="https://redirect.github.com/github/codeql-action/pull/3893">#3893</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.5">2.25.5</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3926">#3926</a></li>
</ul>
<h2>4.35.5 - 15 May 2026</h2>
<ul>
<li>We have improved how the JavaScript bundles for the CodeQL Action
are generated to avoid duplication across bundles and reduce the size of
the repository by around 70%. This should have no effect on the runtime
behaviour of the CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3899">#3899</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="54f647b7e1"><code>54f647b</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3984">#3984</a>
from github/update-v4.36.3-1f34ec164</li>
<li><a
href="e78819e055"><code>e78819e</code></a>
Trigger checks</li>
<li><a
href="2c9d3d63eb"><code>2c9d3d6</code></a>
Update changelog for v4.36.3</li>
<li><a
href="1f34ec1643"><code>1f34ec1</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3983">#3983</a>
from github/mbg/repo-props/ff-for-config-file-prop</li>
<li><a
href="d5f0145480"><code>d5f0145</code></a>
Log when repository property has a value but is ignored</li>
<li><a
href="f27f56386a"><code>f27f563</code></a>
Add test for when the FF is off</li>
<li><a
href="0025d0f2b5"><code>0025d0f</code></a>
Use FF</li>
<li><a
href="f7fa18f05d"><code>f7fa18f</code></a>
Add FF for config file repo property</li>
<li><a
href="628fc3f124"><code>628fc3f</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3979">#3979</a>
from github/henrymercer/overlay-db-cleanup-size-tele...</li>
<li><a
href="9cfb67bab9"><code>9cfb67b</code></a>
Add clarifying comments</li>
<li>Additional commits viewable in <a
href="8aad20d150...54f647b7e1">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github/codeql-action/autobuild&package-manager=github_actions&previous-version=4.36.2&new-version=4.36.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-23 16:51:26 +03:00
Max Kotliar
edb702e326 go.mod: run make vendor-update 2026-07-23 16:32:18 +03:00
Hui Wang
84c2d86a00 app/vmalert: fix rule state badges display on the Groups page (#11230)
Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11160,
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11101

Show each state independently on vmalert UI, to be consistent with the
VMUI Alerting page.
<img width="1512" height="819" alt="image"
src="https://github.com/user-attachments/assets/b3b14151-f85e-4579-8347-3f60629235ef"
/>


We enable `extendedStates` for our own UI (both VMUI Alerting and the
built-in vmalert UI) by default in order to show extended states such as
`nomatch` and `unhealthy`, which are not supported by Grafana and are
not part of the Prometheus API specification(in Prometheus, only
alerting rules have a state, while recording rules do not, code
[here](6d53e9f5cc/rules/alerting.go (L56))).

PR https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11230

---------

Co-authored-by: Max Kotliar <mkotlyar@victoriametrics.com>
2026-07-23 15:54:56 +03:00
Dominic
2da774b8ad dashboards: Add Fsync avg duration panel to Troubleshooting section (#11079)
The commit
dd2d6807e4
added the following metrics to VictoriaMetrics:
- vm_filestream_fsync_duration_seconds_total - it measures the summary
duration for
[fsync()](https://man7.org/linux/man-pages/man2/fsync.2.html) calls
during background merges at VictoriaMetrics.
- vm_filestream_fsync_calls_total - it counts the number of fsync()
calls.

This commit adds a `Fsync avg duration ($instance)` panel to the
`Troubleshooting` section of the `VictoriaMetrics - single-node`,
`VictoriaMetrics - cluster`, and `vmagent` dashboards.

PR https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11079

---------

Co-authored-by: Dominic Polizzi <dpolizzi@victoriametrics.com>
Co-authored-by: Max Kotliar <mkotlyar@victoriametrics.com>
2026-07-23 15:43:05 +03:00
Yury Moladau
650cf31f32 app/vmui: persist auto-refresh interval in URL (#11284)
Persist the selected auto-refresh interval in the URL, allowing shared links to restore auto-refresh automatically.

Based on https://github.com/VictoriaMetrics/VictoriaLogs/pull/1595.
Related issue: https://github.com/VictoriaMetrics/VictoriaLogs/issues/1310

Changes:
- store the selected auto-refresh interval in the `refresh` URL
parameter
- restore auto-refresh from the URL when VMUI opens
- support custom duration values between one second and two hours
- treat missing, invalid, or out-of-range intervals as `Off`

---------

Signed-off-by: Yury Molodov <yurymolodov@gmail.com>
Co-authored-by: Max Kotliar <mkotlyar@victoriametrics.com>
2026-07-23 15:29:12 +03:00
Max Kotliar
3dfb3336f9 app/vminsert: enable slowness rerouting by default; disable it for replicationFactor > 1 (#11287)
Slowness rerouting was disabled by default in
a1b298a842
due to rerouting storm issues.

PRs https://github.com/VictoriaMetrics/VictoriaMetrics/pull/9945 and
https://github.com/VictoriaMetrics/VictoriaMetrics/pull/10901 fixed the
storm: now only the single slowest storage node
is rerouted away from. With these fixes in place, most users benefit
from
slowness rerouting being enabled by default.

Slowness rerouting is forcefully disabled when replicationFactor > 1
because:
- rerouting does not guarantee that replicated copies land on distinct
storage nodes,
  which violates the replication contract
- rerouting under replication creates significant additional load on the
next node
2026-07-23 15:12:49 +03:00
575 changed files with 21238 additions and 10812 deletions

View File

@@ -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'

View File

@@ -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
}

View File

@@ -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()
}

View File

@@ -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

View File

@@ -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>

View File

@@ -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);
});
});
});

View File

@@ -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);
};

View File

@@ -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",

View File

@@ -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": ""
}
}

View File

@@ -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",

View File

@@ -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": ""
}
}

View File

@@ -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",

View File

@@ -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",

View File

@@ -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

View File

@@ -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"

View File

@@ -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/"

View 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.

View File

@@ -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

View File

@@ -26,7 +26,16 @@ 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)
@@ -53,6 +62,7 @@ Released at 2026-07-20
* 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
@@ -112,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

View File

@@ -478,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.

130
go.mod
View File

@@ -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.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,37 +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
github.com/yuin/goldmark v1.8.4 // 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
@@ -158,31 +157,24 @@ require (
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.54.0 // indirect
golang.org/x/exp v0.0.0-20260718201538-764159d718ef // indirect
golang.org/x/mod v0.38.0 // indirect
golang.org/x/sync v0.22.0 // indirect
golang.org/x/telemetry v0.0.0-20260717140457-bdb89881bb75 // 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
golang.org/x/tools v0.48.0 // indirect
golang.org/x/tools/go/expect v0.1.1-deprecated // indirect
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated // indirect
golang.org/x/tools/godoc v0.1.0-deprecated // indirect
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // 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
)

429
go.sum
View File

@@ -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=
@@ -438,46 +436,44 @@ github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 h1:FnBeRrxr7OU4VvAz
github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.8.4 h1:oat/nd3U6NeQqFEL3xpEJq7d7c86NI+DbSNGAs4xnjA=
github.com/yuin/goldmark v1.8.4/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
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=
@@ -488,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=
@@ -521,26 +517,18 @@ 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/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-20260611194520-c48552f49976 h1:X8Hz2ImujgbmetVuW+w2YkyZChE3cBpZi2P158rTG9M=
golang.org/x/exp v0.0.0-20260611194520-c48552f49976/go.mod h1:vnf4pv9iKZXY58sQE1L86zmNWJ4159e1RkcWiLCkeEY=
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=
@@ -548,28 +536,18 @@ golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7
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/telemetry v0.0.0-20260717140457-bdb89881bb75 h1:I9ygRooEYoVHV0SRNOSr/KVjTf5EeJ52BuNkVjsP2GU=
golang.org/x/telemetry v0.0.0-20260717140457-bdb89881bb75/go.mod h1:LV7u5Oco+Z/g6XI7PqN+EUUUGGkEcmB1uj2ceI0fOVg=
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=
@@ -578,35 +556,24 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
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.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q=
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/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM=
golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM=
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8=
golang.org/x/tools/godoc v0.1.0-deprecated h1:o+aZ1BOj6Hsx/GBdJO/s815sqftjSnrZZwyYTHODvtk=
golang.org/x/tools/godoc v0.1.0-deprecated/go.mod h1:qM63CriJ961IHWmnWa9CjZnBndniPt4a3CK0PVB9bIg=
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=
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY=
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
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=
@@ -623,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=

View File

@@ -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)

View File

@@ -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)
}
}

View File

@@ -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{

View File

@@ -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))}

View File

@@ -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
}

View File

@@ -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") {

View File

@@ -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
}

View File

@@ -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) {

View 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
}

View File

@@ -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++

View 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)
}
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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"

View File

@@ -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)

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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"

View File

@@ -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 &gt; 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

View File

@@ -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 != "" {

View File

@@ -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

View 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
}

View File

@@ -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 {

View File

@@ -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,

View File

@@ -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

View File

@@ -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)()
}

View File

@@ -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
}

View File

@@ -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), ".", "/")
}

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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()

View File

@@ -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
)

View File

@@ -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"

View File

@@ -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
View 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

File diff suppressed because it is too large Load Diff

View File

@@ -120,7 +120,7 @@ func toRawNotification(n *Notification) *raw.Notification {
// returned Notification's ID can be used to refer to it.
// Note: gRPC is not supported.
func (b *BucketHandle) AddNotification(ctx context.Context, n *Notification) (ret *Notification, err error) {
ctx, _ = startSpan(ctx, "Bucket.AddNotification")
ctx, _ = startSpanWithBucket(ctx, b.c, b.name, "Bucket.AddNotification")
defer func() { endSpan(ctx, err) }()
if n.ID != "" {
@@ -142,7 +142,7 @@ func (b *BucketHandle) AddNotification(ctx context.Context, n *Notification) (re
// indexed by notification ID.
// Note: gRPC is not supported.
func (b *BucketHandle) Notifications(ctx context.Context) (n map[string]*Notification, err error) {
ctx, _ = startSpan(ctx, "Bucket.Notifications")
ctx, _ = startSpanWithBucket(ctx, b.c, b.name, "Bucket.Notifications")
defer func() { endSpan(ctx, err) }()
opts := makeStorageOpts(true, b.retry, b.userProject)
@@ -161,7 +161,7 @@ func notificationsToMap(rns []*raw.Notification) map[string]*Notification {
// DeleteNotification deletes the notification with the given ID.
// Note: gRPC is not supported.
func (b *BucketHandle) DeleteNotification(ctx context.Context, id string) (err error) {
ctx, _ = startSpan(ctx, "Bucket.DeleteNotification")
ctx, _ = startSpanWithBucket(ctx, b.c, b.name, "Bucket.DeleteNotification")
defer func() { endSpan(ctx, err) }()
opts := makeStorageOpts(true, b.retry, b.userProject)

View File

@@ -44,6 +44,7 @@ func init() {
storageinternal.WithGRPCBidiReads = withGRPCBidiReads
storageinternal.WithZonalBucketAPIs = withZonalBucketAPIs
storageinternal.WithDirectConnectivityEnforced = withDirectConnectivityEnforced
storageinternal.WithOtelMetrics = withOtelMetrics
}
// getDynamicReadReqIncreaseRateFromEnv returns the value set in the env variable.
@@ -81,6 +82,7 @@ type storageConfig struct {
useJSONforReads bool
readAPIWasSet bool
disableClientMetrics bool
enableOtelMetrics bool
metricExporter *metric.Exporter
metricInterval time.Duration
meterProvider *metric.MeterProvider
@@ -300,3 +302,15 @@ func (w *withZonalBucketAPIsConfig) ApplyStorageOpt(config *storageConfig) {
config.grpcAppendableUploads = true
config.grpcBidiReads = true
}
func withOtelMetrics() option.ClientOption {
return &withOtelMetricsConfig{}
}
type withOtelMetricsConfig struct {
internaloption.EmbeddableAdapter
}
func (w *withOtelMetricsConfig) ApplyStorageOpt(c *storageConfig) {
c.enableOtelMetrics = true
}

View File

@@ -47,6 +47,8 @@ const (
//
// **Note:** This feature is currently experimental and its API surface may change
// in future releases. It is not yet recommended for production use.
//
// TODO(b/521239530): Add option to delete source parts after compose operation and remove cleanup logic.
type ParallelUploadConfig struct {
// PartSize is the size of each part to be uploaded in parallel.
@@ -182,7 +184,8 @@ func (w *Writer) initPCU(ctx context.Context) error {
// Track PCU operations using client feature tracking header.
ctx = addFeatureAttributes(ctx, featurePCU)
pCtx, cancel := context.WithCancel(ctx)
bgCtx := contextWithoutMetrics(ctx)
pCtx, cancel := context.WithCancel(bgCtx)
state := &pcuState{
ctx: pCtx,

View File

@@ -19,10 +19,15 @@ import (
"fmt"
"hash/crc32"
"io"
"log"
"net/http"
"strings"
"sync"
"sync/atomic"
"time"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
)
var crc32cTable = crc32.MakeTable(crc32.Castagnoli)
@@ -90,8 +95,8 @@ type ReaderObjectAttrs struct {
// when calling [NewClient]. This ensures consistency with other client
// operations, which all use JSON. JSON will become the default in a future
// release.
func (o *ObjectHandle) NewReader(ctx context.Context) (*Reader, error) {
return o.NewRangeReader(ctx, 0, -1)
func (o *ObjectHandle) NewReader(ctx context.Context, opts ...ReaderOption) (*Reader, error) {
return o.NewRangeReader(ctx, 0, -1, opts...)
}
// NewRangeReader reads part of an object, reading at most length bytes
@@ -111,10 +116,10 @@ func (o *ObjectHandle) NewReader(ctx context.Context) (*Reader, error) {
// when calling [NewClient]. This ensures consistency with other client
// operations, which all use JSON. JSON will become the default in a future
// release.
func (o *ObjectHandle) NewRangeReader(ctx context.Context, offset, length int64) (r *Reader, err error) {
func (o *ObjectHandle) NewRangeReader(ctx context.Context, offset, length int64, opts ...ReaderOption) (r *Reader, err error) {
// This span covers the life of the reader. It is closed via the context
// in Reader.Close.
ctx, _ = startSpan(ctx, "Object.Reader")
ctx, _ = startSpanWithBucket(ctx, o.c, o.bucket, "Object.Reader")
defer func() { endSpan(ctx, err) }()
if err := o.validate(); err != nil {
@@ -129,7 +134,7 @@ func (o *ObjectHandle) NewRangeReader(ctx context.Context, offset, length int64)
}
}
opts := makeStorageOpts(true, o.retry, o.userProject)
storageOpts := makeStorageOpts(true, o.retry, o.userProject)
params := &newRangeReaderParams{
bucket: o.bucket,
@@ -142,8 +147,11 @@ func (o *ObjectHandle) NewRangeReader(ctx context.Context, offset, length int64)
readCompressed: o.readCompressed,
handle: &o.readHandle,
}
for _, opt := range opts {
opt.apply(params)
}
r, err = o.c.tc.NewRangeReader(ctx, params, opts...)
r, err = o.c.tc.NewRangeReader(ctx, params, storageOpts...)
// Pass the context so that the span can be closed in Reader.Close, or close the
// span now if there is an error.
@@ -154,6 +162,11 @@ func (o *ObjectHandle) NewRangeReader(ctx context.Context, offset, length int64)
return r, err
}
// ReaderOption is an option for NewReader or NewRangeReader.
type ReaderOption interface {
apply(*newRangeReaderParams)
}
// MRDOption is an option for MultiRangeDownloader.
type MRDOption interface {
apply(*newMultiRangeDownloaderParams)
@@ -221,6 +234,28 @@ func WithTargetPendingBytes(c int) MRDOption {
return targetPendingBytes(c)
}
type disableMRDReadChecksum struct{}
func (c disableMRDReadChecksum) apply(params *newMultiRangeDownloaderParams) {
params.disableMRDReadChecksum = true
}
// WithDisableMRDReadChecksum returns an MRDOption that disables read checksum validation for the MRD range downloads.
func WithDisableMRDReadChecksum() MRDOption {
return disableMRDReadChecksum{}
}
type disableReaderChecksum struct{}
func (c disableReaderChecksum) apply(params *newRangeReaderParams) {
params.disableCRCCheck = true
}
// WithDisableReaderChecksum returns a ReaderOption that disables read checksum validation.
func WithDisableReaderChecksum() ReaderOption {
return disableReaderChecksum{}
}
// NewMultiRangeDownloader creates a multi-range reader for an object.
// Must be called on a gRPC client created using [NewGRPCClient].
//
@@ -235,7 +270,7 @@ func (o *ObjectHandle) NewMultiRangeDownloader(ctx context.Context, opts ...MRDO
// This span covers the life of the MRD. It is closed via the context
// in MultiRangeDownloader.Close.
var spanCtx context.Context
spanCtx, _ = startSpan(ctx, "Object.MultiRangeDownloader")
spanCtx, _ = startSpanWithBucket(ctx, o.c, o.bucket, "Object.MultiRangeDownloader")
defer func() {
if err != nil {
endSpan(spanCtx, err)
@@ -339,31 +374,61 @@ var emptyBody = io.NopCloser(strings.NewReader(""))
// the stored CRC, returning an error from Read if there is a mismatch. This integrity check
// is skipped if transcoding occurs. See https://cloud.google.com/storage/docs/transcoding.
type Reader struct {
// remain must be the first field in the struct to guarantee 64-bit
// alignment on 32-bit architectures for atomic operations.
remain int64
bytesRead int64 // Cumulative bytes read for response size metric.
Attrs ReaderObjectAttrs
objectMetadata *map[string]string
seen, remain, size int64
checkCRC bool // Did we check the CRC? This is now only used by tests.
seen, size int64
checkCRC bool // Did we check the CRC? This is now only used by tests.
reader io.ReadCloser
ctx context.Context
mu sync.Mutex
err error // Persistent error encountered during Read or WriteTo.
handle *ReadHandle
unfinalized bool
bucket string
object string
metricsState *metricsState
}
// Close closes the Reader. It must be called when done reading.
func (r *Reader) Close() error {
if !r.unfinalized && !r.Attrs.Decompressed {
if rem := atomic.LoadInt64(&r.remain); rem < 0 {
log.Printf("storage: received %d more bytes than requested from GCS for bucket %q, object %q", -rem, r.bucket, r.object)
}
}
err := r.reader.Close()
r.mu.Lock()
if r.err != nil {
err = r.err
}
r.mu.Unlock()
if r.metricsState != nil {
if r.metricsState.metrics != nil {
if total := atomic.SwapInt64(&r.bytesRead, 0); total > 0 {
r.metricsState.metrics.responseBodySize.Record(r.ctx, total, metric.WithAttributes(attribute.String("rpc.method", "ReadObject")))
}
}
if r.metricsState.record != nil {
r.metricsState.record(err)
}
}
endSpan(r.ctx, err)
return err
}
func (r *Reader) Read(p []byte) (int, error) {
n, err := r.reader.Read(p)
if r.remain != -1 {
r.remain -= int64(n)
}
r.recordRead(int64(n), err)
return n, err
}
@@ -373,12 +438,26 @@ func (r *Reader) WriteTo(w io.Writer) (int64, error) {
// This implicitly calls r.reader.WriteTo for gRPC only. JSON and XML don't have an
// implementation of WriteTo.
n, err := io.Copy(w, r.reader)
if r.remain != -1 {
r.remain -= int64(n)
}
r.recordRead(n, err)
return n, err
}
// recordRead updates remaining byte counts and metrics after a read operation,
// and saves any persistent error encountered during Read or WriteTo.
func (r *Reader) recordRead(n int64, err error) {
if !r.unfinalized && !r.Attrs.Decompressed {
atomic.AddInt64(&r.remain, -n)
}
if n > 0 {
atomic.AddInt64(&r.bytesRead, n)
}
if err != nil && err != io.EOF {
r.mu.Lock()
r.err = err
r.mu.Unlock()
}
}
// Size returns the size of the object in bytes.
// The returned value is always the same and is not affected by
// calls to Read or Close.
@@ -392,10 +471,14 @@ func (r *Reader) Size() int64 {
// Remain returns the number of bytes left to read, or -1 if unknown.
// Unfinalized objects will return -1.
func (r *Reader) Remain() int64 {
if r.unfinalized {
if r.unfinalized || r.Attrs.Decompressed {
return -1
}
return r.remain
rem := atomic.LoadInt64(&r.remain)
if rem < 0 {
return 0
}
return rem
}
// ContentType returns the content type of the object.
@@ -506,6 +589,9 @@ func (mrd *MultiRangeDownloader) Add(output io.Writer, offset, length int64, cal
// it could lead to a deadlock.
func (mrd *MultiRangeDownloader) Close() error {
err := mrd.impl.close(nil)
if state := metricsStateFromContext(mrd.impl.getSpanCtx()); state != nil && state.record != nil {
state.record(err)
}
endSpan(mrd.impl.getSpanCtx(), err)
return err
}

View File

@@ -127,6 +127,8 @@ type Client struct {
// Option to use gRRPC appendable upload API was set.
grpcAppendableUploads bool
bucketMetadataCache *bucketMetadataCache
}
// credsJSON returns the raw JSON of the Client's creds and true, or an empty slice
@@ -224,14 +226,27 @@ func NewClient(ctx context.Context, opts ...option.ClientOption) (*Client, error
return nil, fmt.Errorf("storage: %w", err)
}
return &Client{
var tcWrapped storageClient = tc
if httpClient, ok := tc.(*httpStorageClient); ok && httpClient.metrics != nil {
tcWrapped = &metricsStorageClient{
storageClient: tc,
metrics: httpClient.metrics,
isHTTP: true,
}
}
c := &Client{
hc: hc,
raw: rawService,
scheme: u.Scheme,
xmlHost: u.Host,
creds: creds,
tc: tc,
}, nil
tc: tcWrapped,
}
if isACOEnabled() {
c.bucketMetadataCache = newBucketMetadataCache(defaultBucketMetadataCacheLimit, c.tc)
}
return c, nil
}
// NewGRPCClient creates a new Storage client using the gRPC transport and API.
@@ -251,10 +266,22 @@ func NewGRPCClient(ctx context.Context, opts ...option.ClientOption) (*Client, e
if err != nil {
return nil, err
}
return &Client{
tc: tc,
var tcWrapped storageClient = tc
if tc.metrics != nil {
tcWrapped = &metricsStorageClient{
storageClient: tc,
metrics: tc.metrics,
isHTTP: false,
}
}
c := &Client{
tc: tcWrapped,
grpcAppendableUploads: tc.config.grpcAppendableUploads,
}, nil
}
if isACOEnabled() {
c.bucketMetadataCache = newBucketMetadataCache(defaultBucketMetadataCacheLimit, c.tc)
}
return c, nil
}
// CheckDirectConnectivitySupported checks if gRPC direct connectivity
@@ -321,6 +348,7 @@ func (c *Client) Close() error {
c.hc = nil
c.raw = nil
c.creds = nil
c.bucketMetadataCache = nil
if c.tc != nil {
return c.tc.Close()
}
@@ -1038,7 +1066,7 @@ func (o *ObjectHandle) Key(encryptionKey []byte) *ObjectHandle {
// Attrs returns meta information about the object.
// ErrObjectNotExist will be returned if the object is not found.
func (o *ObjectHandle) Attrs(ctx context.Context) (attrs *ObjectAttrs, err error) {
ctx, _ = startSpan(ctx, "Object.Attrs")
ctx, _ = startSpanWithBucket(ctx, o.c, o.bucket, "Object.Attrs")
defer func() { endSpan(ctx, err) }()
if err := o.validate(); err != nil {
@@ -1052,7 +1080,7 @@ func (o *ObjectHandle) Attrs(ctx context.Context) (attrs *ObjectAttrs, err error
// ObjectAttrsToUpdate docs for details on treatment of zero values.
// ErrObjectNotExist will be returned if the object is not found.
func (o *ObjectHandle) Update(ctx context.Context, uattrs ObjectAttrsToUpdate) (oa *ObjectAttrs, err error) {
ctx, _ = startSpan(ctx, "Object.Update")
ctx, _ = startSpanWithBucket(ctx, o.c, o.bucket, "Object.Update")
defer func() { endSpan(ctx, err) }()
if err := o.validate(); err != nil {
@@ -1130,7 +1158,7 @@ type ObjectAttrsToUpdate struct {
// Delete deletes the single specified object.
func (o *ObjectHandle) Delete(ctx context.Context) (err error) {
ctx, _ = startSpan(ctx, "Object.Delete")
ctx, _ = startSpanWithBucket(ctx, o.c, o.bucket, "Object.Delete")
defer func() { endSpan(ctx, err) }()
if err := o.validate(); err != nil {
return err
@@ -1252,7 +1280,7 @@ type MoveObjectDestination struct {
// It is the caller's responsibility to call Close when writing is done. To
// stop writing without saving the data, cancel the context.
func (o *ObjectHandle) NewWriter(ctx context.Context) *Writer {
ctx, _ = startSpan(ctx, "Object.Writer")
ctx, _ = startSpanWithBucket(ctx, o.c, o.bucket, "Object.Writer")
return &Writer{
ctx: ctx,
o: o,
@@ -1288,7 +1316,7 @@ func (o *ObjectHandle) NewWriter(ctx context.Context) *Writer {
// objects which were created append semantics and not finalized.
// This feature is in preview and is not yet available for general use.
func (o *ObjectHandle) NewWriterFromAppendableObject(ctx context.Context, opts *AppendableWriterOpts) (*Writer, int64, error) {
ctx, _ = startSpan(ctx, "Object.WriterFromAppendableObject")
ctx, _ = startSpanWithBucket(ctx, o.c, o.bucket, "Object.WriterFromAppendableObject")
if o.gen < 0 {
return nil, 0, errors.New("storage: ObjectHandle.Generation must be set to use NewWriterFromAppendableObject")
}
@@ -1342,6 +1370,13 @@ type AppendableWriterOpts struct {
ProgressFunc func(int64)
// FinalizeOnClose: See Writer.FinalizeOnClose.
FinalizeOnClose bool
// DisableAutoChecksum: See Writer.DisableAutoChecksum.
DisableAutoChecksum bool
// SendCRC32C: See Writer.SendCRC32C.
SendCRC32C bool
// CRC32C of the whole object.
// See Writer.CRC32C.
CRC32C uint32
}
func (opts *AppendableWriterOpts) apply(w *Writer) {
@@ -1352,6 +1387,9 @@ func (opts *AppendableWriterOpts) apply(w *Writer) {
w.ProgressFunc = opts.ProgressFunc
w.ChunkSize = opts.ChunkSize
w.FinalizeOnClose = opts.FinalizeOnClose
w.DisableAutoChecksum = opts.DisableAutoChecksum
w.CRC32C = opts.CRC32C
w.SendCRC32C = opts.SendCRC32C
}
func (o *ObjectHandle) validate() error {
@@ -1569,6 +1607,7 @@ type ObjectAttrs struct {
// MD5 is the MD5 hash of the object's content. This field is read-only,
// except when used from a Writer. If set on a Writer, the uploaded
// data is rejected if its MD5 hash does not match this field.
// Note: MD5 validation is not supported for appendable writes.
MD5 []byte
// CRC32C is the CRC32 checksum of the object's content using the Castagnoli93

View File

@@ -16,8 +16,11 @@ package storage
import (
"context"
"errors"
"fmt"
"net/http"
"os"
"strings"
internalTrace "cloud.google.com/go/internal/trace"
"cloud.google.com/go/storage/internal"
@@ -25,13 +28,35 @@ import (
"go.opentelemetry.io/otel/attribute"
otelcodes "go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
"google.golang.org/api/googleapi"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type traceContextKey string
const (
storageOtelTracingDevVar = "GO_STORAGE_DEV_OTEL_TRACING"
defaultTracerName = "cloud.google.com/go/storage"
gcpClientRepo = "googleapis/google-cloud-go"
gcpClientArtifact = "cloud.google.com/go/storage"
cacheContextKey traceContextKey = "bucketMetadataCache"
bucketContextKey traceContextKey = "bucketName"
isBucketContextKey traceContextKey = "isBucketOp"
traceAttributesKey traceContextKey = "traceAttributes"
)
func contextWithTraceAttributes(ctx context.Context, attrs []attribute.KeyValue) context.Context {
return context.WithValue(ctx, traceAttributesKey, attrs)
}
func traceAttributesFromContext(ctx context.Context) ([]attribute.KeyValue, bool) {
attrs, ok := ctx.Value(traceAttributesKey).([]attribute.KeyValue)
return attrs, ok
}
const (
storageOtelTracingDevVar = "GO_STORAGE_DEV_OTEL_TRACING"
defaultTracerName = "cloud.google.com/go/storage"
gcpClientRepo = "googleapis/google-cloud-go"
gcpClientArtifact = "cloud.google.com/go/storage"
storageBucketMetadataDisabledVar = "GO_OTEL_BUCKETMETADATA_DISABLED"
)
// isOTelTracingDevEnabled checks the development flag until experimental feature is launched.
@@ -40,10 +65,45 @@ func isOTelTracingDevEnabled() bool {
return os.Getenv(storageOtelTracingDevVar) == "true"
}
func isACOEnabled() bool {
return os.Getenv(storageBucketMetadataDisabledVar) != "true"
}
func tracer() trace.Tracer {
return otel.Tracer(defaultTracerName, trace.WithInstrumentationVersion(internal.Version))
}
func startSpanWithBucket(ctx context.Context, client *Client, bucket string, name string, opts ...trace.SpanStartOption) (context.Context, trace.Span) {
if !isOTelTracingDevEnabled() {
return startSpan(ctx, name, opts...)
}
if client != nil && client.bucketMetadataCache != nil && bucket != "" {
ctx = context.WithValue(ctx, cacheContextKey, client.bucketMetadataCache)
ctx = context.WithValue(ctx, bucketContextKey, bucket)
isBucket := strings.HasPrefix(name, "Bucket.") || strings.HasPrefix(name, "ACL.") || strings.HasPrefix(name, "storage.IAM.")
ctx = context.WithValue(ctx, isBucketContextKey, isBucket)
cache := client.bucketMetadataCache
meta, hit := cache.get(bucket)
if !hit {
placeholder := bucketMetadata{
resource: fmt.Sprintf("projects/_/buckets/%s", bucket),
location: "global",
placeholder: true,
}
cache.put(bucket, placeholder)
cache.fetchBackground(bucket)
meta = placeholder
}
attrs := []attribute.KeyValue{
attribute.String("gcp.resource.destination.id", meta.resource),
attribute.String("gcp.resource.destination.location", meta.location),
}
ctx = contextWithTraceAttributes(ctx, attrs)
}
return startSpan(ctx, name, opts...)
}
// startSpan creates a span and a context.Context containing the newly-created span.
// If the context.Context provided in `ctx` contains a span then the newly-created
// span will be a child of that span, otherwise it will be a root span.
@@ -55,14 +115,39 @@ func startSpan(ctx context.Context, name string, opts ...trace.SpanStartOption)
return ctx, nil
}
opts = append(opts, getCommonTraceOptions()...)
if attrs, ok := traceAttributesFromContext(ctx); ok {
opts = append(opts, trace.WithAttributes(attrs...))
}
ctx, span := tracer().Start(ctx, name, opts...)
return ctx, span
}
func isNotFoundError(err error) bool {
if errors.Is(err, ErrBucketNotExist) {
return true
}
var e *googleapi.Error
if s, ok := status.FromError(err); (ok && s.Code() == codes.NotFound) ||
(errors.As(err, &e) && e.Code == http.StatusNotFound) {
return true
}
return false
}
// endSpan retrieves the current span from ctx and completes the span.
// If an error occurs, the error is recorded as an exception span event for this span,
// and the span status is set in the form of a code and a description.
func endSpan(ctx context.Context, err error) {
if err != nil && isNotFoundError(err) {
isBucket, _ := ctx.Value(isBucketContextKey).(bool)
cache, _ := ctx.Value(cacheContextKey).(*bucketMetadataCache)
bucket, _ := ctx.Value(bucketContextKey).(string)
if isBucket && cache != nil && bucket != "" {
cache.evict(bucket)
}
}
// TODO: Remove internalTrace upon experimental launch.
if !isOTelTracingDevEnabled() {
internalTrace.EndSpan(ctx, err)

View File

@@ -21,8 +21,12 @@ import (
"io"
"log"
"sync"
"sync/atomic"
"time"
"unicode/utf8"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
)
// Interface internalWriter wraps low-level implementations which may vary
@@ -33,6 +37,9 @@ type internalWriter interface {
// CloseWithError terminates the write operation and sets its status.
// Note that CloseWithError always returns nil.
CloseWithError(error) error
// Set final object checksum for appendable objects after write
// and before finalization.
setAppendFinalCRC32C(sendAppendFinalCRC32C bool, c uint32)
}
// A Writer writes a Cloud Storage object.
@@ -200,6 +207,28 @@ type Writer struct {
// in future releases. It is not yet recommended for production use.
ParallelUploadConfig ParallelUploadConfig
// SendAppendFinalCRC32C indicates that AppendFinalCRC32C should be sent as the
// full-object checksum when finalizing an appendable object.
//
// This field is only supported for gRPC clients and appendable objects.
SendAppendFinalCRC32C bool
// AppendFinalCRC32C is the expected full-object CRC32C checksum to be validated
// by the server when finalizing an appendable object.
//
// This field must be set on the Writer, along with SendAppendFinalCRC32C = true,
// before calling Close(). It allows callers to defer providing the checksum
// until the final write is complete.
//
// If SendAppendFinalCRC32C is false, checksum validation for the full object will
// fall back to using ObjectAttrs.CRC32C if Writer.SendCRC32C is true. If both are
// configured, AppendFinalCRC32C takes precedence.
//
// This field is ignored if Writer.Append is false or if using the JSON API.
// Chunk-level validation will still be performed for all chunks during upload if
// Writer.DisableAutoChecksum is false.
AppendFinalCRC32C uint32
ctx context.Context
o *ObjectHandle
@@ -215,6 +244,9 @@ type Writer struct {
mu sync.Mutex
err error
setTakeoverOffset func(int64)
// bytesWritten is the cumulative bytes written for request size metric.
bytesWritten int64
}
func (w *Writer) wrapWriteError(n int, err error) (int, error) {
@@ -280,27 +312,36 @@ func (w *Writer) Write(p []byte) (int, error) {
return 0, fmt.Errorf("storage: Writer is closed")
}
var n int
var err error
if pcu != nil {
return w.wrapWriteError(pcu.write(p))
}
if !w.opened {
// First time initialization: freeze the configuration to either PCU or standard.
if w.EnableParallelUpload {
var err error
if pcu, err = w.getOrInitPCU(); err != nil {
return 0, err
n, err = pcu.write(p)
} else {
if !w.opened {
// First time initialization: freeze the configuration to either PCU or standard.
if w.EnableParallelUpload {
if pcu, err = w.getOrInitPCU(); err != nil {
return 0, err
}
}
if pcu != nil {
return w.wrapWriteError(pcu.write(p))
if pcu == nil {
if err = w.openWriter(); err != nil {
return 0, err
}
}
}
if err := w.openWriter(); err != nil {
return 0, err
if pcu != nil {
n, err = pcu.write(p)
} else {
n, err = w.iw.Write(p)
}
}
return w.wrapWriteError(w.iw.Write(p))
if n > 0 {
atomic.AddInt64(&w.bytesWritten, int64(n))
}
return w.wrapWriteError(n, err)
}
// Flush syncs all bytes currently in the Writer's buffer to Cloud Storage.
@@ -361,38 +402,54 @@ func (w *Writer) Close() error {
if pcu != nil || (!w.opened && w.EnableParallelUpload) {
var err error
if pcu, err = w.getOrInitPCU(); err != nil {
return err
return w.markClosed(err)
}
if pcu != nil {
err = pcu.close()
w.mu.Lock()
defer w.mu.Unlock()
w.closed = true
if w.err == nil && err != nil {
w.err = err
}
endSpan(w.ctx, w.err)
return w.err
return w.markClosed(pcu.close())
}
}
if !w.opened {
if err := w.openWriter(); err != nil {
return err
return w.markClosed(err)
}
}
if w.Append {
w.iw.setAppendFinalCRC32C(w.SendAppendFinalCRC32C, w.AppendFinalCRC32C)
}
if err := w.iw.Close(); err != nil {
return err
return w.markClosed(err)
}
<-w.donec
return w.markClosed(nil)
}
// markClosed marks the Writer as closed, records any closing error on Writer.err,
// and records request body size metrics and trace span completion.
func (w *Writer) markClosed(err error) error {
w.mu.Lock()
defer w.mu.Unlock()
w.closed = true
endSpan(w.ctx, w.err)
return w.err
if w.err == nil && err != nil {
w.err = err
}
closingErr := w.err
total := atomic.LoadInt64(&w.bytesWritten)
w.mu.Unlock()
if state := metricsStateFromContext(w.ctx); state != nil {
if state.metrics != nil && total > 0 {
state.metrics.requestBodySize.Record(w.ctx, total, metric.WithAttributes(attribute.String("rpc.method", "WriteObject")))
}
if state.record != nil {
state.record(closingErr)
}
}
endSpan(w.ctx, closingErr)
return closingErr
}
func (w *Writer) openWriter() (err error) {
@@ -440,6 +497,7 @@ func (w *Writer) openWriter() (err error) {
if err != nil {
return err
}
w.ctx = params.ctx
w.opened = true
go w.monitorCancel()

View File

@@ -17,5 +17,5 @@ package metric
// Version is the current release version of the OpenTelemetry
// Operations Metric Exporter in use.
func Version() string {
return "0.57.0"
return "0.58.0"
}

View File

@@ -164,6 +164,14 @@ type Config struct {
// the shared config profile attribute request_min_compression_size_bytes
RequestMinCompressSizeBytes int64
// DisableClockSkewCorrection turns off SDK clock skew correction. When set
// the SDK will not adjust request signing timestamps to compensate for
// drift between the client and service clocks. Set to false (enabled) by
// default. This variable is sourced from the environment variable
// AWS_DISABLE_CLOCK_SKEW_CORRECTION or the shared config profile attribute
// disable_clock_skew_correction.
DisableClockSkewCorrection bool
// Controls how a resolved AWS account ID is handled for endpoint routing.
AccountIDEndpointMode AccountIDEndpointMode

View File

@@ -3,4 +3,4 @@
package aws
// goModuleVersion is the tagged release for this module
const goModuleVersion = "1.42.0"
const goModuleVersion = "1.43.0"

View File

@@ -43,7 +43,12 @@ func (r ClientRequestID) HandleBuild(ctx context.Context, in middleware.BuildInp
}
// RecordResponseTiming records the response timing for the SDK client requests.
type RecordResponseTiming struct{}
type RecordResponseTiming struct {
// DisableClockSkewCorrection suppresses recording of clock skew observed
// from the response, per the Clock Skew Correction SEP. Response timing is
// still recorded.
DisableClockSkewCorrection bool
}
// ID is the middleware identifier
func (a *RecordResponseTiming) ID() string {
@@ -54,14 +59,17 @@ func (a *RecordResponseTiming) ID() string {
func (a RecordResponseTiming) HandleDeserialize(ctx context.Context, in middleware.DeserializeInput, next middleware.DeserializeHandler) (
out middleware.DeserializeOutput, metadata middleware.Metadata, err error,
) {
requestAt := sdk.NowTime()
out, metadata, err = next.HandleDeserialize(ctx, in)
responseAt := sdk.NowTime()
setResponseAt(&metadata, responseAt)
var serverTime time.Time
var hasAgeHeader bool
switch resp := out.RawResponse.(type) {
case *smithyhttp.Response:
hasAgeHeader = len(resp.Header.Get("Age")) > 0
respDateHeader := resp.Header.Get("Date")
if len(respDateHeader) == 0 {
break
@@ -77,14 +85,45 @@ func (a RecordResponseTiming) HandleDeserialize(ctx context.Context, in middlewa
setServerTime(&metadata, serverTime)
}
if !serverTime.IsZero() {
attemptSkew := serverTime.Sub(responseAt)
setAttemptSkew(&metadata, attemptSkew)
if !a.DisableClockSkewCorrection {
if skew, ok := computeClockSkew(serverTime, requestAt, responseAt, hasAgeHeader); ok {
setAttemptSkew(&metadata, skew)
}
}
return out, metadata, err
}
// maxTrustedRequestDuration bounds how long a request may take before the SDK
// discards the skew measurement derived from its response. A slower round trip
// could only produce a signing failure if it pushed the timestamp outside the
// SigV4 validity window. See the Clock Skew Correction SEP.
const maxTrustedRequestDuration = 15 * time.Minute
// computeClockSkew derives a clock skew candidate from a response per the Clock
// Skew Correction SEP. It returns ok=false (no candidate) when the Date header
// was absent/unparseable (serverTime zero), the round trip exceeded the maximum
// trusted request duration, or the response was served from a cache (Age
// header present). Otherwise the skew is the difference between the server's
// Date and the midpoint of the request round trip.
func computeClockSkew(serverTime, requestAt, responseAt time.Time, hasAgeHeader bool) (time.Duration, bool) {
if serverTime.IsZero() {
return 0, false
}
if hasAgeHeader {
return 0, false
}
elapsed := responseAt.Sub(requestAt)
if elapsed > maxTrustedRequestDuration {
return 0, false
}
midpoint := requestAt.Add(elapsed / 2)
return serverTime.Sub(midpoint), true
}
type responseAtKey struct{}
// GetResponseAt returns the time response was received at.

View File

@@ -1,3 +1,7 @@
# v1.7.14 (2026-07-01)
* No change notes available for this release.
# v1.7.13 (2026-06-04)
* **Dependency Update**: Update to smithy-go v1.27.1 to fix several union-related deserialization bugs in schema-serde-enabled services.

View File

@@ -3,4 +3,4 @@
package eventstream
// goModuleVersion is the tagged release for this module
const goModuleVersion = "1.7.13"
const goModuleVersion = "1.7.14"

View File

@@ -48,6 +48,12 @@ type Attempt struct {
// call.
ClientSkew *atomic.Int64
// DisableClockSkewCorrection disables clock skew correction per the Clock
// Skew Correction SEP: observed skew is not applied to the signing
// timestamp, not recorded into ClientSkew, and clock skew error codes are
// not treated as retry candidates.
DisableClockSkewCorrection bool
retryer aws.RetryerV2
requestCloner RequestCloner
}
@@ -88,7 +94,7 @@ func (r *Attempt) HandleFinalize(ctx context.Context, in smithymiddle.FinalizeIn
out smithymiddle.FinalizeOutput, metadata smithymiddle.Metadata, err error,
) {
var attemptClockSkew time.Duration
if r.ClientSkew != nil {
if !r.DisableClockSkewCorrection && r.ClientSkew != nil {
attemptClockSkew = time.Duration(r.ClientSkew.Load())
}
@@ -159,7 +165,7 @@ func (r *Attempt) HandleFinalize(ctx context.Context, in smithymiddle.FinalizeIn
// this guarantees we are staying on top of the persistent skew value
// (either to apply it or to heal it back if the clocks realign)
if r.ClientSkew != nil {
if !r.DisableClockSkewCorrection && r.ClientSkew != nil {
if resultSkew, ok := awsmiddle.GetAttemptSkew(metadata); ok {
r.ClientSkew.Store(resultSkew.Nanoseconds())
}
@@ -245,7 +251,10 @@ func (r *Attempt) handleAttempt(
return out, attemptResult, nopRelease, err
}
err = wrapAsClockSkew(ctx, err)
if !r.DisableClockSkewCorrection {
candidateSkew, hasCandidateSkew := awsmiddle.GetAttemptSkew(metadata)
err = wrapAsClockSkew(err, candidateSkew, hasCandidateSkew, retryMetadata.AttemptClockSkew)
}
//------------------------------
// Is Retryable and Should Retry
@@ -316,37 +325,66 @@ func (r *Attempt) handleAttempt(
return out, attemptResult, releaseRetryToken, err
}
// errors that, if detected when we know there's a clock skew,
// can be retried and have a high chance of success
var possibleSkewCodes = map[string]struct{}{
// clockSkewCodes are the error codes that may indicate a clock skew problem.
// Per the Clock Skew Correction SEP these are retryable only when the absolute
// skew observed from the response Date header exceeds the detection threshold.
// The SEP does not distinguish "definite" from "possible" skew errors: modern
// services overload a single code (e.g. InvalidSignatureException) for both
// skewed and genuinely malformed signatures, so every code is gated on the
// observed skew.
var clockSkewCodes = map[string]struct{}{
"InvalidSignatureException": {},
"SignatureDoesNotMatch": {},
"AuthFailure": {},
"RequestTimeTooSkewed": {},
"AccessDeniedException": {},
}
var definiteSkewCodes = map[string]struct{}{
"RequestExpired": {},
"RequestInTheFuture": {},
"RequestTimeTooSkewed": {},
}
// wrapAsClockSkew checks if this error could be related to a clock skew
// error and if so, wrap the error.
func wrapAsClockSkew(ctx context.Context, err error) error {
// wrapAsClockSkew classifies err as a retryable clock skew error when its code
// is a known clock skew code and the signing time diverges from the server
// time by more than the detection threshold.
//
// The signing time is now() + attemptSkew. The server time is now() +
// candidateSkew (derived from the response Date header). The signing error is:
//
// |attemptSkew - candidateSkew| > skewThreshold
//
// This single check covers both fresh skew detection (attemptSkew is zero on
// first attempt, so the error equals |candidateSkew|) and stale offset healing
// (attemptSkew is large but the server and client clocks have realigned, so
// candidateSkew is near zero).
//
// If no candidate was observed (the Date header was absent, unparseable, or
// discarded as untrusted), the error is not treated as clock skew.
func wrapAsClockSkew(err error, candidateSkew time.Duration, hasCandidateSkew bool, attemptSkew time.Duration) error {
var v interface{ ErrorCode() string }
if !errors.As(err, &v) {
return err
}
if _, ok := definiteSkewCodes[v.ErrorCode()]; ok {
return &retryableClockSkewError{Err: err}
}
_, isPossibleSkewCode := possibleSkewCodes[v.ErrorCode()]
if skew := internalcontext.GetAttemptSkewContext(ctx); skew > skewThreshold && isPossibleSkewCode {
if _, ok := clockSkewCodes[v.ErrorCode()]; !ok {
return err
}
if !hasCandidateSkew {
return err
}
if absDuration(attemptSkew-candidateSkew) > skewThreshold {
return &retryableClockSkewError{Err: err}
}
return err
}
func absDuration(d time.Duration) time.Duration {
if d < 0 {
return -d
}
return d
}
// MetricsHeader attaches SDK request metric header for retries to the transport
type MetricsHeader struct{}

View File

@@ -1,3 +1,28 @@
# v1.32.31 (2026-07-21)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.32.30 (2026-07-13)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.32.29 (2026-07-08.2)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.32.28 (2026-07-06)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.32.27 (2026-07-01)
* **Bug Fix**: Bump smithy-go to 1.27.3, fix JSON encorder for document.Number, endpoint host label format validation and CBOR union serialization on new serde
* **Dependency Update**: Updated to the latest SDK module versions
# v1.32.26 (2026-06-29)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.32.25 (2026-06-10)
* **Dependency Update**: Updated to the latest SDK module versions

View File

@@ -77,6 +77,8 @@ var defaultAWSConfigResolvers = []awsConfigResolver{
// Sets the DisableRequestCompression if present in env var or shared config profile
resolveDisableRequestCompression,
// Sets the DisableClockSkewCorrection if present in env var or shared config profile
resolveDisableClockSkewCorrection,
// Sets the RequestMinCompressSizeBytes if present in env var or shared config profile
resolveRequestMinCompressSizeBytes,

View File

@@ -78,6 +78,8 @@ const (
awsDisableRequestCompressionEnv = "AWS_DISABLE_REQUEST_COMPRESSION"
awsRequestMinCompressionSizeBytesEnv = "AWS_REQUEST_MIN_COMPRESSION_SIZE_BYTES"
awsDisableClockSkewCorrectionEnv = "AWS_DISABLE_CLOCK_SKEW_CORRECTION"
awsS3DisableExpressSessionAuthEnv = "AWS_S3_DISABLE_EXPRESS_SESSION_AUTH"
awsAccountIDEnv = "AWS_ACCOUNT_ID"
@@ -293,6 +295,10 @@ type EnvConfig struct {
// retrieved from env var AWS_REQUEST_MIN_COMPRESSION_SIZE_BYTES
RequestMinCompressSizeBytes *int64
// determine if clock skew correction is disabled, default to false
// retrieved from env var AWS_DISABLE_CLOCK_SKEW_CORRECTION
DisableClockSkewCorrection *bool
// Whether S3Express auth is disabled.
//
// This will NOT prevent requests from being made to S3Express buckets, it
@@ -364,6 +370,9 @@ func NewEnvConfig() (EnvConfig, error) {
if err := setInt64PtrFromEnvVal(&cfg.RequestMinCompressSizeBytes, []string{awsRequestMinCompressionSizeBytesEnv}, smithyrequestcompression.MaxRequestMinCompressSizeBytes); err != nil {
return cfg, err
}
if err := setBoolPtrFromEnvVal(&cfg.DisableClockSkewCorrection, []string{awsDisableClockSkewCorrectionEnv}); err != nil {
return cfg, err
}
if err := setEndpointDiscoveryTypeFromEnvVal(&cfg.EnableEndpointDiscovery, []string{awsEnableEndpointDiscoveryEnv}); err != nil {
return cfg, err
@@ -453,6 +462,13 @@ func (c EnvConfig) getDisableRequestCompression(context.Context) (bool, bool, er
return *c.DisableRequestCompression, true, nil
}
func (c EnvConfig) getDisableClockSkewCorrection(context.Context) (bool, bool, error) {
if c.DisableClockSkewCorrection == nil {
return false, false, nil
}
return *c.DisableClockSkewCorrection, true, nil
}
func (c EnvConfig) getRequestMinCompressSizeBytes(context.Context) (int64, bool, error) {
if c.RequestMinCompressSizeBytes == nil {
return 0, false, nil

View File

@@ -3,4 +3,4 @@
package config
// goModuleVersion is the tagged release for this module
const goModuleVersion = "1.32.25"
const goModuleVersion = "1.32.31"

View File

@@ -214,6 +214,9 @@ type LoadOptions struct {
// The inclusive min bytes of a request body that could be compressed
RequestMinCompressSizeBytes *int64
// Specifies whether SDK clock skew correction is disabled
DisableClockSkewCorrection *bool
// Whether S3 Express auth is disabled.
S3DisableExpressAuth *bool
@@ -299,6 +302,14 @@ func (o LoadOptions) getDisableRequestCompression(ctx context.Context) (bool, bo
return *o.DisableRequestCompression, true, nil
}
// getDisableClockSkewCorrection returns DisableClockSkewCorrection from config's LoadOptions
func (o LoadOptions) getDisableClockSkewCorrection(ctx context.Context) (bool, bool, error) {
if o.DisableClockSkewCorrection == nil {
return false, false, nil
}
return *o.DisableClockSkewCorrection, true, nil
}
// getRequestMinCompressSizeBytes returns RequestMinCompressSizeBytes from config's LoadOptions
func (o LoadOptions) getRequestMinCompressSizeBytes(ctx context.Context) (int64, bool, error) {
if o.RequestMinCompressSizeBytes == nil {
@@ -369,6 +380,18 @@ func WithDisableRequestCompression(DisableRequestCompression *bool) LoadOptionsF
}
}
// WithDisableClockSkewCorrection is a helper function to construct functional
// options that sets DisableClockSkewCorrection on config's LoadOptions.
func WithDisableClockSkewCorrection(DisableClockSkewCorrection *bool) LoadOptionsFunc {
return func(o *LoadOptions) error {
if DisableClockSkewCorrection == nil {
return nil
}
o.DisableClockSkewCorrection = DisableClockSkewCorrection
return nil
}
}
// WithRequestMinCompressSizeBytes is a helper function to construct functional options
// that sets RequestMinCompressSizeBytes on config's LoadOptions.
func WithRequestMinCompressSizeBytes(RequestMinCompressSizeBytes *int64) LoadOptionsFunc {

View File

@@ -208,6 +208,23 @@ func getDisableRequestCompression(ctx context.Context, configs configs) (value b
return
}
// disableClockSkewCorrectionProvider provides access to the DisableClockSkewCorrection
type disableClockSkewCorrectionProvider interface {
getDisableClockSkewCorrection(context.Context) (bool, bool, error)
}
func getDisableClockSkewCorrection(ctx context.Context, configs configs) (value bool, found bool, err error) {
for _, cfg := range configs {
if p, ok := cfg.(disableClockSkewCorrectionProvider); ok {
value, found, err = p.getDisableClockSkewCorrection(ctx)
if err != nil || found {
break
}
}
}
return
}
// requestMinCompressSizeBytesProvider provides access to the MinCompressSizeBytes
type requestMinCompressSizeBytesProvider interface {
getRequestMinCompressSizeBytes(context.Context) (int64, bool, error)

View File

@@ -151,6 +151,18 @@ func resolveDisableRequestCompression(ctx context.Context, cfg *aws.Config, conf
return nil
}
// resolveDisableClockSkewCorrection extracts the DisableClockSkewCorrection from
// the configs slice's SharedConfig or EnvConfig
func resolveDisableClockSkewCorrection(ctx context.Context, cfg *aws.Config, configs configs) error {
disable, _, err := getDisableClockSkewCorrection(ctx, configs)
if err != nil {
return err
}
cfg.DisableClockSkewCorrection = disable
return nil
}
// resolveRequestMinCompressSizeBytes extracts the RequestMinCompressSizeBytes from the configs slice's
// SharedConfig or EnvConfig
func resolveRequestMinCompressSizeBytes(ctx context.Context, cfg *aws.Config, configs configs) error {

View File

@@ -113,6 +113,8 @@ const (
disableRequestCompression = "disable_request_compression"
requestMinCompressionSizeBytes = "request_min_compression_size_bytes"
disableClockSkewCorrection = "disable_clock_skew_correction"
s3DisableExpressSessionAuthKey = "s3_disable_express_session_auth"
accountIDKey = "aws_account_id"
@@ -346,6 +348,10 @@ type SharedConfig struct {
// retrieved from config file's profile field request_min_compression_size_bytes
RequestMinCompressSizeBytes *int64
// determine if clock skew correction is disabled, default to false
// retrieved from config file's profile field disable_clock_skew_correction
DisableClockSkewCorrection *bool
// Whether S3Express auth is disabled.
//
// This will NOT prevent requests from being made to S3Express buckets, it
@@ -1149,6 +1155,9 @@ func (c *SharedConfig) setFromIniSection(profile string, section ini.Section) er
if err := updateDisableRequestCompression(&c.DisableRequestCompression, section, disableRequestCompression); err != nil {
return fmt.Errorf("failed to load %s from shared config, %w", disableRequestCompression, err)
}
if err := updateDisableRequestCompression(&c.DisableClockSkewCorrection, section, disableClockSkewCorrection); err != nil {
return fmt.Errorf("failed to load %s from shared config, %w", disableClockSkewCorrection, err)
}
if err := updateRequestMinCompressSizeBytes(&c.RequestMinCompressSizeBytes, section, requestMinCompressionSizeBytes); err != nil {
return fmt.Errorf("failed to load %s from shared config, %w", requestMinCompressionSizeBytes, err)
}
@@ -1292,6 +1301,13 @@ func (c SharedConfig) getDisableRequestCompression(ctx context.Context) (bool, b
return *c.DisableRequestCompression, true, nil
}
func (c SharedConfig) getDisableClockSkewCorrection(ctx context.Context) (bool, bool, error) {
if c.DisableClockSkewCorrection == nil {
return false, false, nil
}
return *c.DisableClockSkewCorrection, true, nil
}
func (c SharedConfig) getAccountIDEndpointMode(ctx context.Context) (aws.AccountIDEndpointMode, bool, error) {
return c.AccountIDEndpointMode, len(c.AccountIDEndpointMode) > 0, nil
}

View File

@@ -1,3 +1,28 @@
# v1.19.30 (2026-07-21)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.19.29 (2026-07-13)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.19.28 (2026-07-08.2)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.19.27 (2026-07-06)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.19.26 (2026-07-01)
* **Bug Fix**: Bump smithy-go to 1.27.3, fix JSON encorder for document.Number, endpoint host label format validation and CBOR union serialization on new serde
* **Dependency Update**: Updated to the latest SDK module versions
# v1.19.25 (2026-06-29)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.19.24 (2026-06-10)
* **Dependency Update**: Updated to the latest SDK module versions

View File

@@ -3,4 +3,4 @@
package credentials
// goModuleVersion is the tagged release for this module
const goModuleVersion = "1.19.24"
const goModuleVersion = "1.19.30"

View File

@@ -1,3 +1,11 @@
# v1.18.31 (2026-07-21)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.18.30 (2026-07-01)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.18.29 (2026-06-08)
* **Dependency Update**: Updated to the latest SDK module versions

View File

@@ -3,4 +3,4 @@
package imds
// goModuleVersion is the tagged release for this module
const goModuleVersion = "1.18.29"
const goModuleVersion = "1.18.31"

View File

@@ -1,3 +1,36 @@
# v1.22.35 (2026-07-21)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.22.34 (2026-07-16)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.22.33 (2026-07-13)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.22.32 (2026-07-08.2)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.22.31 (2026-07-06)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.22.30 (2026-07-01)
* **Bug Fix**: Bump smithy-go to 1.27.3, fix JSON encorder for document.Number, endpoint host label format validation and CBOR union serialization on new serde
* **Dependency Update**: Updated to the latest SDK module versions
# v1.22.29 (2026-06-29)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.22.28 (2026-06-16)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.22.27 (2026-06-10)
* **Dependency Update**: Updated to the latest SDK module versions

View File

@@ -3,4 +3,4 @@
package manager
// goModuleVersion is the tagged release for this module
const goModuleVersion = "1.22.27"
const goModuleVersion = "1.22.35"

View File

@@ -1,3 +1,11 @@
# v1.4.31 (2026-07-21)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.4.30 (2026-07-01)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.4.29 (2026-06-08)
* **Dependency Update**: Updated to the latest SDK module versions

View File

@@ -3,4 +3,4 @@
package configsources
// goModuleVersion is the tagged release for this module
const goModuleVersion = "1.4.29"
const goModuleVersion = "1.4.31"

View File

@@ -1,3 +1,11 @@
# v2.7.31 (2026-07-21)
* **Dependency Update**: Updated to the latest SDK module versions
# v2.7.30 (2026-07-01)
* **Dependency Update**: Updated to the latest SDK module versions
# v2.7.29 (2026-06-08)
* **Dependency Update**: Updated to the latest SDK module versions

View File

@@ -3,4 +3,4 @@
package endpoints
// goModuleVersion is the tagged release for this module
const goModuleVersion = "2.7.29"
const goModuleVersion = "2.7.31"

Some files were not shown because too many files have changed in this diff Show More