Compare commits

..

11 Commits

Author SHA1 Message Date
Gabriele Fedi
511bb045cb
Merge 2cffcf6fb1 into 1e4a731e14 2026-04-28 08:17:24 +00:00
Armando Ruocco
2cffcf6fb1 fix: rework managed RBAC reconciliation
Adds the safety and robustness improvements identified during
review of the recommended-labels feature, all on the managed
Role and RoleBinding paths.

Labels: per-key overwrite. Keys the plugin manages overwrite
existing values; unrelated keys (anything outside the desired
set) are left alone. The plugin owns these objects exclusively
— OwnerReference points to the Cluster CR, the Pre hook
reconciles them on every call, and there is no realistic path
for an external manager (Helm, Kustomize, ArgoCD, …) to ever
touch these per-Cluster runtime-generated objects, so per-key
overwrite is the correct policy and version labels follow
plugin upgrades naturally.

Subjects: additive. The plugin guarantees its own ServiceAccount
Subject is bound, but never removes Subjects added by other
actors. A Subject is a grant of access; silently revoking
access an admin granted (e.g. a debugging ServiceAccount) is
the wrong default. This is intentionally asymmetric to the
label policy.

RoleRef: immutable in Kubernetes; a Patch cannot fix it.
Divergence at the canonical name is corruption regardless of
who wrote the existing object — fail loudly so the operator
notices and deletes the RoleBinding, and the next Pre call
recreates it correctly.

AlreadyExists tolerance: EnsureRoleBinding called c.Create
directly when Get returned NotFound and propagated whatever
Create returned. That's noisy on plugin pod startup: the
controller-runtime client is cache-backed by default, and during
the warm-up window the informer cache can return NotFound for a
RoleBinding that is actually present on the API server. The
follow-up Create then hits AlreadyExists and the whole Pre hook
returned an error to the cnpg-i framework. Mirror the pattern
used by ensureRoleExists: split the exists-or-create concern
into getOrCreateRoleBinding, swallow AlreadyExists, and re-Get
to return the racing winner so the caller can fall through to
reconcile.

Optimistic locking on patches: patchRole already wrapped its
Get+Patch in retry.RetryOnConflict, but used client.MergeFrom
which doesn't include resourceVersion — so the API server never
returns 409 and the retry never fires. Switch both Patch sites
to client.MergeFromWithOptions(WithOptimisticLock{}) so the
retry actually does what its caller assumes it does.
EnsureRoleBinding gets the same retry wrapper for parity.

Single-Get steady state: the previous structure did one Get to
check existence and a second Get inside the retry loop, even
when the object was already present and matched desired.
reconcileRoleBinding now receives the existing object as input
and uses it on the first attempt, only Get-ing again on actual
conflict-driven retries.

Helpers: extract mergeLabels (per-key overwrite), mergeSubjects
(additive append), containsSubject (semantic deep-equal), and
keep labelsNeedUpdate / roleBindingNeedsUpdate as no-Patch
short-circuits whose result mirrors the merge helpers exactly.

Tests: cover fresh creation, steady-state no-op (via Patch
interceptor counter, more reliable than the previous
ResourceVersion comparison which depended on fake-client RV
semantics that aren't part of the controller-runtime contract),
unrelated-label preservation, stale-label refresh, additive
subjects with user-added entries, AlreadyExists race during
pod startup (deterministic via interceptor), divergent
RoleRef error path, and the upgrade scenario for objects that
predate the recommended-labels feature.

Signed-off-by: Armando Ruocco <armando.ruocco@enterprisedb.com>
2026-04-28 10:10:33 +02:00
Armando Ruocco
1e8a61f7fe fix: align label values with issue #545 spec
The original PR set the recommended labels to:
  app.kubernetes.io/name="postgresql"  (utils.AppName, the cnpg
                                         operator's identity)
  app.kubernetes.io/version=<cluster PG major>

Per issue #545 the plugin's RBAC objects should advertise the
plugin's own identity:
  app.kubernetes.io/name="barman-cloud-plugin"
  app.kubernetes.io/version=<plugin semver>

Use metadata.Data.Version for the version value (always set,
never conditional on cluster image), drop the cnpg
GetPostgresqlMajorVersion lookup (irrelevant to plugin RBAC and
fragile against the cnpg PostgresImageName default), and extract
the plugin-local AppLabelValue and ManagedByLabelValue constants
so test and production share a single source of truth. The
labels_test.go helper now also asserts the version's value, not
just the key.

Document on metadata.ClusterLabelName that it's a discovery
contract for objectstore_controller.go (which selects on the
key), so the dual-labeling scheme (the legacy cluster label
alongside the K8s recommended labels) is explicit and the
legacy key cannot be silently removed by a later cleanup.

Signed-off-by: Armando Ruocco <armando.ruocco@enterprisedb.com>
2026-04-28 10:09:48 +02:00
Armando Ruocco
4ce06b89fe refactor: rename label builder to BuildLabels, move into labels.go
The package is already named specs, the file content is label
construction, and a separate metadata package exists at
internal/cnpgi/metadata; labels.go describes the file directly
without the package-name collision. BuildLabels also matches
the verb convention already used elsewhere in the package
(BuildRole, BuildRoleBinding, BuildRoleRules).

No behavior change.

Signed-off-by: Armando Ruocco <armando.ruocco@enterprisedb.com>
2026-04-28 10:09:00 +02:00
Gabriele Fedi
2bdc1caee4 chore: lint fix
Signed-off-by: Gabriele Fedi <gabriele.fedi@enterprisedb.com>
2026-04-27 15:05:17 +02:00
Gabriele Fedi
3439acbcec test: unit tests
Signed-off-by: Gabriele Fedi <gabriele.fedi@enterprisedb.com>
2026-04-27 15:05:17 +02:00
Gabriele Fedi
ea4b00b66b fix: merge labels instead of replacing
Signed-off-by: Gabriele Fedi <gabriele.fedi@enterprisedb.com>
2026-04-27 15:05:17 +02:00
Gabriele Fedi
7fcd5dfdcd feat: configure k8s recommended labels on subresources
Signed-off-by: Gabriele Fedi <gabriele.fedi@enterprisedb.com>
2026-04-27 15:05:17 +02:00
renovate[bot]
1e4a731e14
chore(deps): update all cloudnative-pg daggerverse dependencies to f84ac0c (#875)
Some checks failed
release-please / release-please (push) Failing after 10s
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| commitlint | digest | `eda3ea3` → `f84ac0c` |
| controller-gen | digest | `eda3ea3` → `f84ac0c` |
| crd-gen-refs | digest | `eda3ea3` → `f84ac0c` |
| spellcheck | digest | `eda3ea3` → `f84ac0c` |
| uncommitted | digest | `eda3ea3` → `f84ac0c` |

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Never, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these
updates again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/cloudnative-pg/plugin-barman-cloud).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xNDEuMyIsInVwZGF0ZWRJblZlciI6IjQzLjE0MS4zIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJhdXRvbWF0ZWQiLCJuby1pc3N1ZSJdfQ==-->

Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Leonardo Cecchi <leonardo.cecchi@enterprisedb.com>
2026-04-27 12:05:00 +02:00
renovate[bot]
7bf416a2bc
chore(deps): refresh pip-compile outputs (#873)
Some checks failed
Deploy Docusaurus to GitHub Pages / build (push) Failing after 4s
Deploy Docusaurus to GitHub Pages / deploy (push) Has been skipped
release-please / release-please (push) Failing after 2s
This PR contains the following updates:

| Update | Change |
|---|---|
| lockFileMaintenance | All locks refreshed |

🔧 This Pull Request updates lock files to use the latest dependency
versions.

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - "before 4am on monday"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Never, or you tick the rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config
help](https://redirect.github.com/renovatebot/renovate/discussions) if
that's undesired.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/cloudnative-pg/plugin-barman-cloud).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xNDEuMyIsInVwZGF0ZWRJblZlciI6IjQzLjE0MS4zIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJhdXRvbWF0ZWQiLCJuby1pc3N1ZSJdfQ==-->

Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Leonardo Cecchi <leonardo.cecchi@enterprisedb.com>
2026-04-27 10:49:58 +02:00
renovate[bot]
2f86c342c4
chore(deps): lock file maintenance (#872)
This PR contains the following updates:

| Update | Change |
|---|---|
| lockFileMaintenance | All locks refreshed |

🔧 This Pull Request updates lock files to use the latest dependency
versions.

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - "before 4am on monday"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Never, or you tick the rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config
help](https://redirect.github.com/renovatebot/renovate/discussions) if
that's undesired.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/cloudnative-pg/plugin-barman-cloud).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xNDEuMyIsInVwZGF0ZWRJblZlciI6IjQzLjE0MS4zIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJhdXRvbWF0ZWQiLCJuby1pc3N1ZSJdfQ==-->

Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Leonardo Cecchi <leonardo.cecchi@enterprisedb.com>
2026-04-27 09:59:58 +02:00
9 changed files with 707 additions and 362 deletions

View File

@ -46,7 +46,7 @@ tasks:
- wordlist-ordered - wordlist-ordered
env: env:
# renovate: datasource=git-refs depName=spellcheck lookupName=https://github.com/cloudnative-pg/daggerverse currentValue=main # renovate: datasource=git-refs depName=spellcheck lookupName=https://github.com/cloudnative-pg/daggerverse currentValue=main
DAGGER_SPELLCHECK_SHA: eda3ea39e88ce2cb4be786ae74127a380adbfa38 DAGGER_SPELLCHECK_SHA: f84ac0c681fcfff4d6f138bf53d44d023f615b48
cmds: cmds:
- > - >
GITHUB_REF= dagger -s call -m github.com/cloudnative-pg/daggerverse/spellcheck@${DAGGER_SPELLCHECK_SHA} GITHUB_REF= dagger -s call -m github.com/cloudnative-pg/daggerverse/spellcheck@${DAGGER_SPELLCHECK_SHA}
@ -60,7 +60,7 @@ tasks:
desc: Check for conventional commits desc: Check for conventional commits
env: env:
# renovate: datasource=git-refs depName=commitlint lookupName=https://github.com/cloudnative-pg/daggerverse currentValue=main # renovate: datasource=git-refs depName=commitlint lookupName=https://github.com/cloudnative-pg/daggerverse currentValue=main
DAGGER_COMMITLINT_SHA: eda3ea39e88ce2cb4be786ae74127a380adbfa38 DAGGER_COMMITLINT_SHA: f84ac0c681fcfff4d6f138bf53d44d023f615b48
cmds: cmds:
- > - >
GITHUB_REF= dagger -s call -m github.com/cloudnative-pg/daggerverse/commitlint@${DAGGER_COMMITLINT_SHA} GITHUB_REF= dagger -s call -m github.com/cloudnative-pg/daggerverse/commitlint@${DAGGER_COMMITLINT_SHA}
@ -74,7 +74,7 @@ tasks:
- wordlist-ordered - wordlist-ordered
env: env:
# renovate: datasource=git-refs depName=uncommitted lookupName=https://github.com/cloudnative-pg/daggerverse currentValue=main # renovate: datasource=git-refs depName=uncommitted lookupName=https://github.com/cloudnative-pg/daggerverse currentValue=main
DAGGER_UNCOMMITTED_SHA: eda3ea39e88ce2cb4be786ae74127a380adbfa38 DAGGER_UNCOMMITTED_SHA: f84ac0c681fcfff4d6f138bf53d44d023f615b48
cmds: cmds:
- GITHUB_REF= dagger -s call -m github.com/cloudnative-pg/daggerverse/uncommitted@${DAGGER_UNCOMMITTED_SHA} check-uncommitted --source . stdout - GITHUB_REF= dagger -s call -m github.com/cloudnative-pg/daggerverse/uncommitted@${DAGGER_UNCOMMITTED_SHA} check-uncommitted --source . stdout
sources: sources:
@ -86,7 +86,7 @@ tasks:
- controller-gen - controller-gen
env: env:
# renovate: datasource=git-refs depName=crd-gen-refs lookupName=https://github.com/cloudnative-pg/daggerverse currentValue=main # renovate: datasource=git-refs depName=crd-gen-refs lookupName=https://github.com/cloudnative-pg/daggerverse currentValue=main
DAGGER_CRDGENREF_SHA: eda3ea39e88ce2cb4be786ae74127a380adbfa38 DAGGER_CRDGENREF_SHA: f84ac0c681fcfff4d6f138bf53d44d023f615b48
# renovate: datasource=go depName=github.com/elastic/crd-ref-docs # renovate: datasource=go depName=github.com/elastic/crd-ref-docs
CRDREFDOCS_VERSION: v0.3.0 CRDREFDOCS_VERSION: v0.3.0
cmds: cmds:
@ -381,7 +381,7 @@ tasks:
run: once run: once
env: env:
# renovate: datasource=git-refs depName=controller-gen lookupName=https://github.com/cloudnative-pg/daggerverse currentValue=main # renovate: datasource=git-refs depName=controller-gen lookupName=https://github.com/cloudnative-pg/daggerverse currentValue=main
DAGGER_CONTROLLER_GEN_SHA: eda3ea39e88ce2cb4be786ae74127a380adbfa38 DAGGER_CONTROLLER_GEN_SHA: f84ac0c681fcfff4d6f138bf53d44d023f615b48
cmds: cmds:
- > - >
GITHUB_REF= dagger -s call -m github.com/cloudnative-pg/daggerverse/controller-gen@${DAGGER_CONTROLLER_GEN_SHA} GITHUB_REF= dagger -s call -m github.com/cloudnative-pg/daggerverse/controller-gen@${DAGGER_CONTROLLER_GEN_SHA}

View File

@ -22,19 +22,19 @@ barman==3.18.0 \
--hash=sha256:8e752ac93d2f3a61e86b8374185209cae477a638ece7e6f540070f36d28d6997 \ --hash=sha256:8e752ac93d2f3a61e86b8374185209cae477a638ece7e6f540070f36d28d6997 \
--hash=sha256:ff90c44dafa4107b7574142771cdc2611c4cf1af818d93d3e67440a0c81164b9 --hash=sha256:ff90c44dafa4107b7574142771cdc2611c4cf1af818d93d3e67440a0c81164b9
# via -r sidecar-requirements.in # via -r sidecar-requirements.in
boto3==1.42.91 \ boto3==1.42.96 \
--hash=sha256:03d70532b17f7f84df37ca7e8c21553280454dea53ae12b15d1cfef9b16fcb8a \ --hash=sha256:2f4566da2c209a98bdbfc874d813ef231c84ad24e4f815e9bc91de5f63351a24 \
--hash=sha256:04e72071cde022951ce7f81bd9933c90095ab8923e8ced61c8dacfe9edac0f5c --hash=sha256:b38a9e4a3fbbee9017252576f1379780d0a5814768676c08df2f539d31fcdd68
# via barman # via barman
botocore==1.42.91 \ botocore==1.42.96 \
--hash=sha256:7a28c3cc6bfab5724ad18899d52402b776a0de7d87fa20c3c5270bcaaf199ce8 \ --hash=sha256:75b3b841ffacaa944f645196655a21ca777591dd8911e732bfb6614545af0250 \
--hash=sha256:d252e27bc454afdbf5ed3dc617aa423f2c855c081e98b7963093399483ecc698 --hash=sha256:db2c3e2006628be6fde81a24124a6563c363d6982fb92728837cf174bad9d98a
# via # via
# boto3 # boto3
# s3transfer # s3transfer
certifi==2026.2.25 \ certifi==2026.4.22 \
--hash=sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa \ --hash=sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a \
--hash=sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7 --hash=sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580
# via requests # via requests
cffi==2.0.0 \ cffi==2.0.0 \
--hash=sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb \ --hash=sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb \
@ -390,56 +390,56 @@ cramjam==2.11.0 \
# via # via
# barman # barman
# python-snappy # python-snappy
cryptography==46.0.7 \ cryptography==47.0.0 \
--hash=sha256:04959522f938493042d595a736e7dbdff6eb6cc2339c11465b3ff89343b65f65 \ --hash=sha256:0024b87d47ae2399165a6bfb20d24888881eeab83ae2566d62467c5ff0030ce7 \
--hash=sha256:128c5edfe5e5938b86b03941e94fac9ee793a94452ad1365c9fc3f4f62216832 \ --hash=sha256:07efe86201817e7d3c18781ca9770bc0db04e1e48c994be384e4602bc38f8f27 \
--hash=sha256:1d25aee46d0c6f1a501adcddb2d2fee4b979381346a78558ed13e50aa8a59067 \ --hash=sha256:09f6d7bf6724f8db8b32f11eccf23efc8e759924bc5603800335cf8859a3ddbd \
--hash=sha256:24402210aa54baae71d99441d15bb5a1919c195398a87b563df84468160a65de \ --hash=sha256:11438c7518132d95f354fa01a4aa2f806d172a061a7bed18cf18cbdacdb204d7 \
--hash=sha256:258514877e15963bd43b558917bc9f54cf7cf866c38aa576ebf47a77ddbc43a4 \ --hash=sha256:11dbb9f50a0f1bb9757b3d8c27c1101780efb8f0bdecfb12439c22a74d64c001 \
--hash=sha256:35719dc79d4730d30f1c2b6474bd6acda36ae2dfae1e3c16f2051f215df33ce0 \ --hash=sha256:14432c8a9bcb37009784f9594a62fae211a2ae9543e96c92b2a8e4c3cd5cd0c4 \
--hash=sha256:397655da831414d165029da9bc483bed2fe0e75dde6a1523ec2fe63f3c46046b \ --hash=sha256:1581aef4219f7ca2849d0250edaa3866212fb74bf5667284f46aa92f9e65c1ca \
--hash=sha256:3986ac1dee6def53797289999eabe84798ad7817f3e97779b5061a95b0ee4968 \ --hash=sha256:160ad728f128972d362e714054f6ba0067cab7fb350c5202a9ae8ae4ce3ef1a0 \
--hash=sha256:420b1e4109cc95f0e5700eed79908cef9268265c773d3a66f7af1eef53d409ef \ --hash=sha256:1a405c08857258c11016777e11c02bacbe7ef596faf259305d282272a3a05cbe \
--hash=sha256:42a1e5f98abb6391717978baf9f90dc28a743b7d9be7f0751a6f56a75d14065b \ --hash=sha256:1e47422b5557bb82d3fff997e8d92cff4e28b9789576984f08c248d2b3535d93 \
--hash=sha256:462ad5cb1c148a22b2e3bcc5ad52504dff325d17daf5df8d88c17dda1f75f2a4 \ --hash=sha256:20fdbe3e38fb67c385d233c89371fa27f9909f6ebca1cecc20c13518dae65475 \
--hash=sha256:506c4ff91eff4f82bdac7633318a526b1d1309fc07ca76a3ad182cb5b686d6d3 \ --hash=sha256:2207a498b03275d0051589e326b79d4cf59985c99031b05bb292ac52631c37fe \
--hash=sha256:5ad9ef796328c5e3c4ceed237a183f5d41d21150f972455a9d926593a1dcb308 \ --hash=sha256:256d07c78a04d6b276f5df935a9923275f53bd1522f214447fdf365494e2d515 \
--hash=sha256:5d1c02a14ceb9148cc7816249f64f623fbfee39e8c03b3650d842ad3f34d637e \ --hash=sha256:2b45761c6ec22b7c726d6a829558777e32d0f1c8be7c3f3480f9c912d5ee8a10 \
--hash=sha256:5e51be372b26ef4ba3de3c167cd3d1022934bc838ae9eaad7e644986d2a3d163 \ --hash=sha256:2ebd84adf0728c039a3be2700289378e1c164afc6748df1a5ed456767bef9ba7 \
--hash=sha256:60627cf07e0d9274338521205899337c5d18249db56865f943cbe753aa96f40f \ --hash=sha256:34b4358b925a5ea3e14384ca781a2c0ef7ac219b57bb9eacc4457078e2b19f92 \
--hash=sha256:65814c60f8cc400c63131584e3e1fad01235edba2614b61fbfbfa954082db0ee \ --hash=sha256:3fb8fa48075fad7193f2e5496135c6a76ac4b2aa5a38433df0a539296b377829 \
--hash=sha256:73510b83623e080a2c35c62c15298096e2a5dc8d51c3b4e1740211839d0dea77 \ --hash=sha256:4e1de79e047e25d6e9f8cea71c86b4a53aced64134f0f003bbcbf3655fd172c8 \
--hash=sha256:7bbc6ccf49d05ac8f7d7b5e2e2c33830d4fe2061def88210a126d130d7f71a85 \ --hash=sha256:4f7722c97826770bab8ae92959a2e7b20a5e9e9bf4deae68fd86c3ca457bab52 \
--hash=sha256:80406c3065e2c55d7f49a9550fe0c49b3f12e5bfff5dedb727e319e1afb9bf99 \ --hash=sha256:51c9313e90bd1690ec5a75ed047c27c0b8e6c570029712943d6116ef9a90620b \
--hash=sha256:84d4cced91f0f159a7ddacad249cc077e63195c36aac40b4150e7a57e84fffe7 \ --hash=sha256:5d0e362ff51041b0c0d219cc7d6924d7b8996f57ce5712bdcef71eb3c65a59cc \
--hash=sha256:8a469028a86f12eb7d2fe97162d0634026d92a21f3ae0ac87ed1c4a447886c83 \ --hash=sha256:6651d32eff255423503aa276739da98c30f26c40cbeffcc6048e0d54ef704c0c \
--hash=sha256:91bbcb08347344f810cbe49065914fe048949648f6bd5c2519f34619142bbe85 \ --hash=sha256:6eebcaf0df1d21ce1f90605c9b432dd2c4f4ab665ac29a40d5e3fc68f51b5e63 \
--hash=sha256:935ce7e3cfdb53e3536119a542b839bb94ec1ad081013e9ab9b7cfd478b05006 \ --hash=sha256:6f29f36582e6151d9686235e586dd35bb67491f024767d10b842e520dc6a07ac \
--hash=sha256:9694078c5d44c157ef3162e3bf3946510b857df5a3955458381d1c7cfc143ddb \ --hash=sha256:7a02675e2fabd0c0fc04c868b8781863cbf1967691543c22f5470500ff840b31 \
--hash=sha256:a1529d614f44b863a7b480c6d000fe93b59acee9c82ffa027cfadc77521a9f5e \ --hash=sha256:7f1207974a904e005f762869996cf620e9bf79ecb4622f148550bb48e0eb35a7 \
--hash=sha256:abad9dac36cbf55de6eb49badd4016806b3165d396f64925bf2999bcb67837ba \ --hash=sha256:7f68d6fbc7fbbcfb0939fea72c3b96a9f9a6edfc0e1b1d29778a2066030418b1 \
--hash=sha256:b36a4695e29fe69215d75960b22577197aca3f7a25b9cf9d165dcfe9d80bc325 \ --hash=sha256:7fda2f02c9015db3f42bb8a22324a454516ed10a8c29ca6ece6cdbb5efe2a203 \
--hash=sha256:b7b412817be92117ec5ed95f880defe9cf18a832e8cafacf0a22337dc1981b4d \ --hash=sha256:80887c5cbd1774683cb126f0ab4184567f080071d5acf62205acb354b4b753b7 \
--hash=sha256:c5b1ccd1239f48b7151a65bc6dd54bcfcc15e028c8ac126d3fada09db0e07ef1 \ --hash=sha256:835d2d7f47cdc53b3224e90810fb1d36ca94ea29cc1801fb4c1bc43876735769 \
--hash=sha256:cbd5fb06b62bd0721e1170273d3f4d5a277044c47ca27ee257025146c34cbdd1 \ --hash=sha256:8c1a736bbb3288005796c3f7ccb9453360d7fed483b13b9f468aea5171432923 \
--hash=sha256:cdf1a610ef82abb396451862739e3fc93b071c844399e15b90726ef7470eeaf2 \ --hash=sha256:9af828c0d5a65c70ec729cd7495a4bf1a67ecb66417b8f02ff125ab8a6326a74 \
--hash=sha256:cdfbe22376065ffcf8be74dc9a909f032df19bc58a699456a21712d6e5eabfd0 \ --hash=sha256:9c59ab0e0fa3a180a5a9c59f3a5abe3ef90d474bc56d7fadfbe80359491b615b \
--hash=sha256:d02c738dacda7dc2a74d1b2b3177042009d5cab7c7079db74afc19e56ca1b455 \ --hash=sha256:9f8e55fe4e63613a5e1cc5819030f27b97742d720203a087802ce4ce9ceb52bb \
--hash=sha256:d151173275e1728cf7839aaa80c34fe550c04ddb27b34f48c232193df8db5842 \ --hash=sha256:9fe6b7c64926c765f9dff301f9c1b867febcda5768868ca084e18589113732ab \
--hash=sha256:d23c8ca48e44ee015cd0a54aeccdf9f09004eba9fc96f38c911011d9ff1bd457 \ --hash=sha256:a49a3eb5341b9503fa3000a9a0db033161db90d47285291f53c2a9d2cd1b7f76 \
--hash=sha256:d3b99c535a9de0adced13d159c5a9cf65c325601aa30f4be08afd680643e9c15 \ --hash=sha256:a9b761f012a943b7de0e828843c5688d0de94a0578d44d6c85a1bae32f87791f \
--hash=sha256:d5f7520159cd9c2154eb61eb67548ca05c5774d39e9c2c4339fd793fe7d097b2 \ --hash=sha256:b1c76fca783aa7698eb21eb14f9c4aa09452248ee54a627d125025a43f83e7a7 \
--hash=sha256:db0f493b9181c7820c8134437eb8b0b4792085d37dbb24da050476ccb664e59c \ --hash=sha256:b9a8943e359b7615db1a3ba587994618e094ff3d6fa5a390c73d079ce18b3973 \
--hash=sha256:e06acf3c99be55aa3b516397fe42f5855597f430add9c17fa46bf2e0fb34c9bb \ --hash=sha256:be12cb6a204f77ed968bcefe68086eb061695b540a3dd05edac507a3111b25f0 \
--hash=sha256:e4cfd68c5f3e0bfdad0d38e023239b96a2fe84146481852dffbcca442c245aa5 \ --hash=sha256:cffbba3392df0fa8629bb7f43454ee2925059ee158e23c54620b9063912b86c8 \
--hash=sha256:ea42cbe97209df307fdc3b155f1b6fa2577c0defa8f1f7d3be7d31d189108ad4 \ --hash=sha256:ed67ea4e0cfb5faa5bc7ecb6e2b8838f3807a03758eec239d6c21c8769355310 \
--hash=sha256:ebd6daf519b9f189f85c479427bbd6e9c9037862cf8fe89ee35503bd209ed902 \ --hash=sha256:edd4da498015da5b9f26d38d3bfc2e90257bfa9cbed1f6767c282a0025ae649b \
--hash=sha256:f247c8c1a1fb45e12586afbb436ef21ff1e80670b2861a90353d9b025583d246 \ --hash=sha256:ef6b3634087f18d2155b1e8ce264e5345a753da2c5fa9815e7d41315c90f8318 \
--hash=sha256:fbfd0e5f273877695cb93baf14b185f4878128b250cc9f8e617ea0c025dfb022 \ --hash=sha256:f1557695e5c2b86e204f6ce9470497848634100787935ab7adc5397c54abd7ab \
--hash=sha256:fc9ab8856ae6cf7c9358430e49b368f3108f050031442eaeb6b9d87e4dcf4e4f \ --hash=sha256:f5c15764f261394b22aef6b00252f5195f46f2ca300bec57149474e2538b31f8 \
--hash=sha256:fcd8eac50d9138c1d7fc53a653ba60a2bee81a505f9f8850b6b2888555a45d0e \ --hash=sha256:f5c3296dab66202f1b18a91fa266be93d6aa0c2806ea3d67762c69f60adc71aa \
--hash=sha256:fdd1736fed309b4300346f88f74cd120c27c56852c3838cab416e7a166f67298 \ --hash=sha256:f7db373287273d8af1414cf95dc4118b13ffdc62be521997b0f2b270771fef50 \
--hash=sha256:ffca7aa1d00cf7d6469b988c581598f2259e46215e0140af408966a24cf086ce --hash=sha256:f9a034b642b960767fb343766ae5ba6ad653f2e890ddd82955aef288ffea8736
# via # via
# azure-identity # azure-identity
# azure-storage-blob # azure-storage-blob
@ -512,9 +512,9 @@ googleapis-common-protos==1.74.0 \
--hash=sha256:57971e4eeeba6aad1163c1f0fc88543f965bb49129b8bb55b2b7b26ecab084f1 \ --hash=sha256:57971e4eeeba6aad1163c1f0fc88543f965bb49129b8bb55b2b7b26ecab084f1 \
--hash=sha256:702216f78610bb510e3f12ac3cafd281b7ac45cc5d86e90ad87e4d301a3426b5 --hash=sha256:702216f78610bb510e3f12ac3cafd281b7ac45cc5d86e90ad87e4d301a3426b5
# via google-api-core # via google-api-core
idna==3.11 \ idna==3.13 \
--hash=sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea \ --hash=sha256:585ea8fe5d69b9181ec1afba340451fba6ba764af97026f92a91d4eef164a242 \
--hash=sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902 --hash=sha256:892ea0cde124a99ce773decba204c5552b69c3c67ffd5f232eb7696135bc8bb3
# via requests # via requests
isodate==0.7.2 \ isodate==0.7.2 \
--hash=sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15 \ --hash=sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15 \
@ -612,14 +612,14 @@ protobuf==7.34.1 \
# google-api-core # google-api-core
# googleapis-common-protos # googleapis-common-protos
# proto-plus # proto-plus
psycopg2==2.9.11 \ psycopg2==2.9.12 \
--hash=sha256:103e857f46bb76908768ead4e2d0ba1d1a130e7b8ed77d3ae91e8b33481813e8 \ --hash=sha256:09826a6b89714626a662275d03f21639f1c68d183e2dcc9ba134d463a3da753e \
--hash=sha256:210daed32e18f35e3140a1ebe059ac29209dd96468f2f7559aa59f75ee82a5cb \ --hash=sha256:1dedb1c7a1d8552c4a6044c6b1c41a52e6a8e2d144af83eccac758076b1b7c15 \
--hash=sha256:6ecddcf573777536bddfefaea8079ce959287798c8f5804bee6933635d538924 \ --hash=sha256:2532c0cdc6ad18c9c35cd935cc3159712e14f05276a6d29a6435c52d24b840c1 \
--hash=sha256:8dc379166b5b7d5ea66dcebf433011dfc51a7bb8a5fc12367fa05668e5fc53c8 \ --hash=sha256:3d23e684927d37b95cee9a943f6927b04ae2fdcd056fd0e2a30929ee89fee5a9 \
--hash=sha256:964d31caf728e217c697ff77ea69c2ba0865fa41ec20bb00f0977e62fdcc52e3 \ --hash=sha256:83d48e66e18c301d832e93c984a7bcbc0f4ac3bb79e2137e3bc335978c756dc0 \
--hash=sha256:e03e4a6dbe87ff81540b434f2e5dc2bddad10296db5eea7bdc995bf5f4162938 \ --hash=sha256:a73d5513bfe929c56555006c7a9cc7ae6e4276aa99dd2b1e2544eb8bb54f8b23 \
--hash=sha256:f10a48acba5fe6e312b891f290b4d2ca595fc9a06850fe53320beac353575578 --hash=sha256:d5fbe092315fb007c03544704e6d1e678a6c0378139d01cea433dc59edf041b4
# via barman # via barman
pyasn1==0.6.3 \ pyasn1==0.6.3 \
--hash=sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf \ --hash=sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf \
@ -657,9 +657,9 @@ requests==2.33.1 \
# google-api-core # google-api-core
# google-cloud-storage # google-cloud-storage
# msal # msal
s3transfer==0.16.0 \ s3transfer==0.16.1 \
--hash=sha256:18e25d66fed509e3868dc1572b3f427ff947dd2c56f844a5bf09481ad3f3b2fe \ --hash=sha256:61bcd00ccb83b21a0fe7e91a553fff9729d46c83b4e0106e7c314a733891f7c2 \
--hash=sha256:8e990f13268025792229cd52fa10cb7163744bf56e719e0b9cb925ab79abf920 --hash=sha256:8e424355754b9ccb32467bdc568edf55be82692ef2002d934b1311dbb3b9e524
# via boto3 # via boto3
six==1.17.0 \ six==1.17.0 \
--hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \ --hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \

View File

@ -28,8 +28,24 @@ const PluginName = "barman-cloud.cloudnative-pg.io"
const ( const (
// ClusterLabelName is the label applied to RBAC resources created // ClusterLabelName is the label applied to RBAC resources created
// by this plugin. Its value is the name of the owning Cluster. // by this plugin. Its value is the name of the owning Cluster.
//
// Discovery contract: internal/controller/objectstore_controller.go
// selects Roles by this key when an ObjectStore is reconciled.
// Renaming or removing the label would break that controller; new
// recommended-label keys are added alongside it, never in place
// of it.
ClusterLabelName = "barmancloud.cnpg.io/cluster" ClusterLabelName = "barmancloud.cnpg.io/cluster"
// AppLabelValue is the value applied to app.kubernetes.io/name on
// every plugin-managed object. It identifies the application as
// the Barman Cloud plugin (see issue #545).
AppLabelValue = "barman-cloud-plugin"
// ManagedByLabelValue is the value applied to app.kubernetes.io/managed-by
// on every plugin-managed object. It identifies this plugin as
// the controller responsible for the object.
ManagedByLabelValue = "plugin-barman-cloud"
// CheckEmptyWalArchiveFile is the name of the file in the PGDATA that, // CheckEmptyWalArchiveFile is the name of the file in the PGDATA that,
// if present, requires the WAL archiver to check that the backup object // if present, requires the WAL archiver to check that the backup object
// store is empty. // store is empty.

View File

@ -21,6 +21,7 @@ package rbac
import ( import (
"context" "context"
"fmt"
cnpgv1 "github.com/cloudnative-pg/cloudnative-pg/api/v1" cnpgv1 "github.com/cloudnative-pg/cloudnative-pg/api/v1"
"github.com/cloudnative-pg/machinery/pkg/log" "github.com/cloudnative-pg/machinery/pkg/log"
@ -61,7 +62,7 @@ func EnsureRole(
return err return err
} }
return patchRole(ctx, c, roleKey, newRole.Rules, specs.GetRequiredLabels(cluster)) return patchRole(ctx, c, roleKey, newRole.Rules, specs.BuildLabels(cluster))
} }
// EnsureRoleRules updates the rules of an existing Role to match // EnsureRoleRules updates the rules of an existing Role to match
@ -87,51 +88,141 @@ func EnsureRoleRules(
return err return err
} }
// EnsureRoleBinding ensures the RoleBinding for the given Cluster matches // EnsureRoleBinding ensures the RoleBinding for the given Cluster
// the desired state. // is present and carries the recommended labels.
// //
// This function is called from the Pre hook (gRPC). It creates the RoleBinding // This function is called from the Pre hook (gRPC). It creates the
// if it does not exist, otherwise it patches Subjects and labels to match // RoleBinding if it does not exist, then reconciles labels and
// the desired state. // Subjects:
// - Labels are written per-key. Keys the plugin manages overwrite
// existing values; unrelated keys (anything outside the desired
// set) are left alone.
// - Subjects are additive. The plugin guarantees its own Subject
// is bound, but never removes Subjects added by other actors —
// a Subject is a grant of access, and silently revoking access
// someone else granted is the wrong default.
//
// RoleRef is immutable in Kubernetes. If the existing RoleBinding
// points to a different Role, the plugin fails loudly so the
// operator notices and recreates the object.
func EnsureRoleBinding(ctx context.Context, c client.Client, cluster *cnpgv1.Cluster) error { func EnsureRoleBinding(ctx context.Context, c client.Client, cluster *cnpgv1.Cluster) error {
contextLogger := log.FromContext(ctx)
desiredRoleBinding := specs.BuildRoleBinding(cluster) desiredRoleBinding := specs.BuildRoleBinding(cluster)
if err := specs.SetControllerReference(cluster, desiredRoleBinding); err != nil { if err := specs.SetControllerReference(cluster, desiredRoleBinding); err != nil {
return err return err
} }
roleBinding := &rbacv1.RoleBinding{} roleBinding, err := getOrCreateRoleBinding(ctx, c, desiredRoleBinding)
if err != nil || roleBinding == nil {
if err := c.Get(ctx, client.ObjectKey{ // Either an error, or we just created the object with the
Namespace: cluster.Namespace, // desired state — nothing to patch.
Name: specs.GetRBACName(cluster.Name),
}, roleBinding); err != nil {
if apierrs.IsNotFound(err) {
contextLogger.Info("Creating RoleBinding", "name", desiredRoleBinding.Name,
"namespace", desiredRoleBinding.Namespace)
return c.Create(ctx, desiredRoleBinding)
}
return err return err
} }
if !roleBindingNeedsUpdate(roleBinding, desiredRoleBinding) { return reconcileRoleBinding(ctx, c, roleBinding, desiredRoleBinding)
return nil }
// getOrCreateRoleBinding returns the existing RoleBinding when it
// is already present on the API server, or nil after a successful
// Create when the just-created object already carries the desired
// state (so the caller can skip the patch path).
//
// On a stale-informer-cache race during plugin pod startup, where
// Get returns NotFound but Create returns AlreadyExists, the
// function re-Gets to return the racing winner — the caller then
// falls through to reconciliation against that real object.
func getOrCreateRoleBinding(
ctx context.Context,
c client.Client,
desired *rbacv1.RoleBinding,
) (*rbacv1.RoleBinding, error) {
contextLogger := log.FromContext(ctx)
rb := &rbacv1.RoleBinding{}
err := c.Get(ctx, client.ObjectKeyFromObject(desired), rb)
if err == nil {
return rb, nil
}
if !apierrs.IsNotFound(err) {
return nil, err
} }
contextLogger.Info("Patching role binding", createErr := c.Create(ctx, desired)
"name", roleBinding.Name, "namespace", roleBinding.Namespace) switch {
case createErr == nil:
oldRoleBinding := roleBinding.DeepCopy() contextLogger.Info("Created RoleBinding",
if roleBinding.Labels == nil { "name", desired.Name, "namespace", desired.Namespace)
roleBinding.Labels = make(map[string]string, len(desiredRoleBinding.Labels)) // Just-created with the desired state — caller skips patch.
return nil, nil
case apierrs.IsAlreadyExists(createErr):
contextLogger.Debug(
"RoleBinding already exists, likely a stale informer cache; re-fetching",
"name", desired.Name, "namespace", desired.Namespace)
// Re-Get to return the racing winner so the caller can
// reconcile against the real existing object.
fetched := &rbacv1.RoleBinding{}
if err := c.Get(ctx, client.ObjectKeyFromObject(desired), fetched); err != nil {
return nil, err
}
return fetched, nil
default:
return nil, createErr
} }
for k, v := range desiredRoleBinding.Labels { }
roleBinding.Labels[k] = v
}
roleBinding.Subjects = desiredRoleBinding.Subjects
return c.Patch(ctx, roleBinding, client.MergeFrom(oldRoleBinding)) // reconcileRoleBinding brings an existing RoleBinding's labels and
// Subjects into alignment with desired. It re-Gets the object on
// conflict-retry so each attempt observes fresh server state, and
// uses optimistic locking so a competing writer's patch is rejected
// with 409 instead of silently last-write-winning.
//
// On the first attempt the function uses the existing object passed
// in by the caller, avoiding a second Get on the steady-state path.
func reconcileRoleBinding(
ctx context.Context,
c client.Client,
existing, desired *rbacv1.RoleBinding,
) error {
contextLogger := log.FromContext(ctx)
first := true
return retry.RetryOnConflict(retry.DefaultBackoff, func() error {
var roleBinding *rbacv1.RoleBinding
if first {
roleBinding = existing
first = false
} else {
roleBinding = &rbacv1.RoleBinding{}
if err := c.Get(ctx, client.ObjectKeyFromObject(desired), roleBinding); err != nil {
return err
}
}
// RoleRef is immutable in Kubernetes; we cannot patch it.
// Divergence at the canonical name is corruption regardless
// of who wrote the existing object — fail loudly so the
// operator notices and deletes the RoleBinding, and the
// next Pre call recreates it correctly.
if !equality.Semantic.DeepEqual(roleBinding.RoleRef, desired.RoleRef) {
return fmt.Errorf(
"RoleBinding %s/%s has divergent immutable RoleRef "+
"(existing=%+v, desired=%+v); delete the RoleBinding to allow recreation",
roleBinding.Namespace, roleBinding.Name,
roleBinding.RoleRef, desired.RoleRef)
}
if !roleBindingNeedsUpdate(roleBinding, desired) {
return nil
}
contextLogger.Info("Patching role binding",
"name", roleBinding.Name, "namespace", roleBinding.Namespace)
oldRoleBinding := roleBinding.DeepCopy()
roleBinding.Labels = mergeLabels(roleBinding.Labels, desired.Labels)
roleBinding.Subjects = mergeSubjects(roleBinding.Subjects, desired.Subjects)
return c.Patch(ctx, roleBinding,
client.MergeFromWithOptions(oldRoleBinding, client.MergeFromWithOptimisticLock{}))
})
} }
// ensureRoleExists creates the Role if it does not exist. Returns // ensureRoleExists creates the Role if it does not exist. Returns
@ -199,22 +290,32 @@ func patchRole(
oldRole := role.DeepCopy() oldRole := role.DeepCopy()
role.Rules = desiredRules role.Rules = desiredRules
role.Labels = mergeLabels(role.Labels, desiredLabels)
if desiredLabels != nil { return c.Patch(ctx, &role,
if role.Labels == nil { client.MergeFromWithOptions(oldRole, client.MergeFromWithOptimisticLock{}))
role.Labels = make(map[string]string, len(desiredLabels))
}
for k, v := range desiredLabels {
role.Labels[k] = v
}
}
return c.Patch(ctx, &role, client.MergeFrom(oldRole))
}) })
} }
// labelsNeedUpdate returns true if any key in desired is missing // mergeLabels writes the desired labels onto existing per-key.
// or has a different value in existing. // Keys in desired overwrite the existing value; keys not in desired
// (any unrelated label a user may have set) are left alone.
func mergeLabels(existing, desired map[string]string) map[string]string {
if len(desired) == 0 {
return existing
}
if existing == nil {
existing = make(map[string]string, len(desired))
}
for k, v := range desired {
existing[k] = v
}
return existing
}
// labelsNeedUpdate returns true if a Patch is required to bring
// existing labels into the state mergeLabels would produce, i.e.
// any desired key is missing or has a different value in existing.
func labelsNeedUpdate(existing, desired map[string]string) bool { func labelsNeedUpdate(existing, desired map[string]string) bool {
for k, v := range desired { for k, v := range desired {
if existing[k] != v { if existing[k] != v {
@ -224,11 +325,44 @@ func labelsNeedUpdate(existing, desired map[string]string) bool {
return false return false
} }
// roleBindingNeedsUpdate returns true if the existing RoleBinding's // containsSubject reports whether subjects contains an element that
// Subjects differ from the desired or if labels need update. // is semantically equal to subject.
func containsSubject(subjects []rbacv1.Subject, subject rbacv1.Subject) bool {
for _, s := range subjects {
if equality.Semantic.DeepEqual(s, subject) {
return true
}
}
return false
}
// mergeSubjects appends desired Subjects that are not already
// present in existing.
//
// This is intentionally asymmetric to mergeLabels: labels are
// metadata, so replacing stale plugin-set values is safe. A
// Subject is a grant of access, so removing a Subject silently
// revokes permissions an external operator chose to grant. The
// plugin only requires that ITS Subject is present, not that it
// is exclusive.
func mergeSubjects(existing, desired []rbacv1.Subject) []rbacv1.Subject {
for _, d := range desired {
if !containsSubject(existing, d) {
existing = append(existing, d)
}
}
return existing
}
// roleBindingNeedsUpdate returns true if a Patch is required to
// bring existing into alignment with desired — any desired Subject
// missing (see mergeSubjects), or any desired label key missing
// or holding a stale value (see mergeLabels).
func roleBindingNeedsUpdate(existing, desired *rbacv1.RoleBinding) bool { func roleBindingNeedsUpdate(existing, desired *rbacv1.RoleBinding) bool {
if !equality.Semantic.DeepEqual(existing.Subjects, desired.Subjects) { for _, s := range desired.Subjects {
return true if !containsSubject(existing.Subjects, s) {
return true
}
} }
if labelsNeedUpdate(existing.Labels, desired.Labels) { if labelsNeedUpdate(existing.Labels, desired.Labels) {

View File

@ -45,11 +45,38 @@ import (
func expectRequiredLabels(labels map[string]string, clusterName string) { func expectRequiredLabels(labels map[string]string, clusterName string) {
ExpectWithOffset(1, labels).To(HaveKeyWithValue(metadata.ClusterLabelName, clusterName)) ExpectWithOffset(1, labels).To(HaveKeyWithValue(metadata.ClusterLabelName, clusterName))
ExpectWithOffset(1, labels).To(HaveKeyWithValue(utils.KubernetesAppLabelName, utils.AppName)) ExpectWithOffset(1, labels).To(HaveKeyWithValue(utils.KubernetesAppLabelName, metadata.AppLabelValue))
ExpectWithOffset(1, labels).To(HaveKeyWithValue(utils.KubernetesAppInstanceLabelName, clusterName)) ExpectWithOffset(1, labels).To(HaveKeyWithValue(utils.KubernetesAppInstanceLabelName, clusterName))
ExpectWithOffset(1, labels).To(HaveKeyWithValue(utils.KubernetesAppManagedByLabelName, "plugin-barman-cloud")) ExpectWithOffset(1, labels).To(HaveKeyWithValue(utils.KubernetesAppManagedByLabelName, metadata.ManagedByLabelValue))
ExpectWithOffset(1, labels).To(HaveKeyWithValue(utils.KubernetesAppComponentLabelName, utils.DatabaseComponentName)) ExpectWithOffset(1, labels).To(HaveKeyWithValue(utils.KubernetesAppComponentLabelName, utils.DatabaseComponentName))
ExpectWithOffset(1, labels).To(HaveKey(utils.KubernetesAppVersionLabelName)) ExpectWithOffset(1, labels).To(HaveKeyWithValue(utils.KubernetesAppVersionLabelName, metadata.Data.Version))
}
// newPatchCountingClient returns a fake client plus a pointer to a
// counter incremented on every Patch call. Useful for asserting
// that a "no-op" reconcile path issues no Patch — more reliable
// than comparing ResourceVersion across reads, which depends on
// fake-client RV-bumping semantics that are not part of the
// controller-runtime contract.
func newPatchCountingClient(initObjs ...client.Object) (client.Client, *int) {
count := 0
c := fake.NewClientBuilder().
WithScheme(newScheme()).
WithObjects(initObjs...).
WithInterceptorFuncs(interceptor.Funcs{
Patch: func(
ctx context.Context,
client client.WithWatch,
obj client.Object,
patch client.Patch,
opts ...client.PatchOption,
) error {
count++
return client.Patch(ctx, obj, patch, opts...)
},
}).
Build()
return c, &count
} }
func newScheme() *runtime.Scheme { func newScheme() *runtime.Scheme {
@ -139,28 +166,18 @@ var _ = Describe("EnsureRole", func() {
}) })
Context("when the Role exists with matching rules", func() { Context("when the Role exists with matching rules", func() {
var patchCount *int
BeforeEach(func() { BeforeEach(func() {
fakeClient = fake.NewClientBuilder().WithScheme(newScheme()).Build() fakeClient, patchCount = newPatchCountingClient()
Expect(rbac.EnsureRole(ctx, fakeClient, cluster, objects)).To(Succeed()) Expect(rbac.EnsureRole(ctx, fakeClient, cluster, objects)).To(Succeed())
*patchCount = 0
}) })
It("should not patch the Role", func() { It("should not patch the Role", func() {
var before rbacv1.Role
Expect(fakeClient.Get(ctx, client.ObjectKey{
Namespace: "default",
Name: "test-cluster-barman-cloud",
}, &before)).To(Succeed())
err := rbac.EnsureRole(ctx, fakeClient, cluster, objects) err := rbac.EnsureRole(ctx, fakeClient, cluster, objects)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(*patchCount).To(BeZero())
var after rbacv1.Role
Expect(fakeClient.Get(ctx, client.ObjectKey{
Namespace: "default",
Name: "test-cluster-barman-cloud",
}, &after)).To(Succeed())
Expect(after.ResourceVersion).To(Equal(before.ResourceVersion))
}) })
}) })
@ -242,6 +259,38 @@ var _ = Describe("EnsureRole", func() {
}) })
}) })
Context("when the Role exists with a stale label value", func() {
BeforeEach(func() {
fakeClient = fake.NewClientBuilder().WithScheme(newScheme()).Build()
existing := &rbacv1.Role{
ObjectMeta: metav1.ObjectMeta{
Name: "test-cluster-barman-cloud",
Namespace: "default",
Labels: map[string]string{
// Stale value as if written by an older plugin
// version.
utils.KubernetesAppVersionLabelName: "0.0.0-stale",
},
},
}
Expect(fakeClient.Create(ctx, existing)).To(Succeed())
})
It("should overwrite the stale value with the current plugin's value", func() {
err := rbac.EnsureRole(ctx, fakeClient, cluster, objects)
Expect(err).NotTo(HaveOccurred())
var role rbacv1.Role
Expect(fakeClient.Get(ctx, client.ObjectKey{
Namespace: "default",
Name: "test-cluster-barman-cloud",
}, &role)).To(Succeed())
Expect(role.Labels).To(HaveKeyWithValue(
utils.KubernetesAppVersionLabelName, metadata.Data.Version))
})
})
Context("when the Role exists without the cluster label (upgrade scenario)", func() { Context("when the Role exists without the cluster label (upgrade scenario)", func() {
BeforeEach(func() { BeforeEach(func() {
fakeClient = fake.NewClientBuilder().WithScheme(newScheme()).Build() fakeClient = fake.NewClientBuilder().WithScheme(newScheme()).Build()
@ -316,32 +365,22 @@ var _ = Describe("EnsureRoleBinding", func() {
}) })
Context("when the RoleBinding exists with matching state", func() { Context("when the RoleBinding exists with matching state", func() {
var patchCount *int
BeforeEach(func() { BeforeEach(func() {
fakeClient = fake.NewClientBuilder().WithScheme(newScheme()).Build() fakeClient, patchCount = newPatchCountingClient()
Expect(rbac.EnsureRoleBinding(ctx, fakeClient, cluster)).To(Succeed()) Expect(rbac.EnsureRoleBinding(ctx, fakeClient, cluster)).To(Succeed())
*patchCount = 0
}) })
It("should not patch the RoleBinding", func() { It("should not patch the RoleBinding", func() {
var before rbacv1.RoleBinding
Expect(fakeClient.Get(ctx, client.ObjectKey{
Namespace: "default",
Name: "test-cluster-barman-cloud",
}, &before)).To(Succeed())
err := rbac.EnsureRoleBinding(ctx, fakeClient, cluster) err := rbac.EnsureRoleBinding(ctx, fakeClient, cluster)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(*patchCount).To(BeZero())
var after rbacv1.RoleBinding
Expect(fakeClient.Get(ctx, client.ObjectKey{
Namespace: "default",
Name: "test-cluster-barman-cloud",
}, &after)).To(Succeed())
Expect(after.ResourceVersion).To(Equal(before.ResourceVersion))
}) })
}) })
Context("when the RoleBinding exists with different subjects", func() { Context("when the RoleBinding exists with extra user-added subjects", func() {
BeforeEach(func() { BeforeEach(func() {
fakeClient = fake.NewClientBuilder().WithScheme(newScheme()).Build() fakeClient = fake.NewClientBuilder().WithScheme(newScheme()).Build()
existing := &rbacv1.RoleBinding{ existing := &rbacv1.RoleBinding{
@ -352,7 +391,7 @@ var _ = Describe("EnsureRoleBinding", func() {
Subjects: []rbacv1.Subject{ Subjects: []rbacv1.Subject{
{ {
Kind: "ServiceAccount", Kind: "ServiceAccount",
Name: "old-sa", Name: "user-debug-sa",
APIGroup: "", APIGroup: "",
}, },
}, },
@ -365,7 +404,7 @@ var _ = Describe("EnsureRoleBinding", func() {
Expect(fakeClient.Create(ctx, existing)).To(Succeed()) Expect(fakeClient.Create(ctx, existing)).To(Succeed())
}) })
It("should patch the subjects to match the desired state", func() { It("should add the plugin's subject without removing user-added ones", func() {
err := rbac.EnsureRoleBinding(ctx, fakeClient, cluster) err := rbac.EnsureRoleBinding(ctx, fakeClient, cluster)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
@ -375,8 +414,62 @@ var _ = Describe("EnsureRoleBinding", func() {
Name: "test-cluster-barman-cloud", Name: "test-cluster-barman-cloud",
}, &rb)).To(Succeed()) }, &rb)).To(Succeed())
Expect(rb.Subjects).To(HaveLen(1)) // Additive policy: the user-added subject must remain.
Expect(rb.Subjects[0].Name).To(Equal("test-cluster")) Expect(rb.Subjects).To(ContainElement(rbacv1.Subject{
Kind: "ServiceAccount",
Name: "user-debug-sa",
APIGroup: "",
}))
// The plugin's required subject must be present.
Expect(rb.Subjects).To(ContainElement(rbacv1.Subject{
Kind: "ServiceAccount",
Name: "test-cluster",
Namespace: "default",
APIGroup: "",
}))
})
})
Context("when the RoleBinding exists with a stale label value", func() {
BeforeEach(func() {
fakeClient = fake.NewClientBuilder().WithScheme(newScheme()).Build()
existing := &rbacv1.RoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "test-cluster-barman-cloud",
Namespace: "default",
Labels: map[string]string{
utils.KubernetesAppVersionLabelName: "0.0.0-stale",
},
},
Subjects: []rbacv1.Subject{
{
Kind: "ServiceAccount",
Name: "test-cluster",
Namespace: "default",
APIGroup: "",
},
},
RoleRef: rbacv1.RoleRef{
APIGroup: "rbac.authorization.k8s.io",
Kind: "Role",
Name: "test-cluster-barman-cloud",
},
}
Expect(fakeClient.Create(ctx, existing)).To(Succeed())
})
It("should overwrite the stale value with the current plugin's value", func() {
err := rbac.EnsureRoleBinding(ctx, fakeClient, cluster)
Expect(err).NotTo(HaveOccurred())
var rb rbacv1.RoleBinding
Expect(fakeClient.Get(ctx, client.ObjectKey{
Namespace: "default",
Name: "test-cluster-barman-cloud",
}, &rb)).To(Succeed())
Expect(rb.Labels).To(HaveKeyWithValue(
utils.KubernetesAppVersionLabelName, metadata.Data.Version))
}) })
}) })
@ -423,6 +516,108 @@ var _ = Describe("EnsureRoleBinding", func() {
}) })
}) })
Context("when the RoleBinding has a divergent RoleRef", func() {
BeforeEach(func() {
fakeClient = fake.NewClientBuilder().WithScheme(newScheme()).Build()
existing := &rbacv1.RoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "test-cluster-barman-cloud",
Namespace: "default",
},
Subjects: []rbacv1.Subject{
{
Kind: "ServiceAccount",
Name: "test-cluster",
Namespace: "default",
APIGroup: "",
},
},
RoleRef: rbacv1.RoleRef{
APIGroup: "rbac.authorization.k8s.io",
Kind: "Role",
Name: "wrong-role",
},
}
Expect(fakeClient.Create(ctx, existing)).To(Succeed())
})
It("should return a descriptive error since RoleRef is immutable", func() {
err := rbac.EnsureRoleBinding(ctx, fakeClient, cluster)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("RoleRef"))
Expect(err.Error()).To(ContainSubstring("wrong-role"))
})
})
Context("when an AlreadyExists race happens during a stale-cache create (plugin pod startup)", func() {
var preExisting *rbacv1.RoleBinding
BeforeEach(func() {
preExisting = &rbacv1.RoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "test-cluster-barman-cloud",
Namespace: "default",
},
Subjects: []rbacv1.Subject{
{
Kind: "ServiceAccount",
Name: "test-cluster",
Namespace: "default",
APIGroup: "",
},
},
RoleRef: rbacv1.RoleRef{
APIGroup: "rbac.authorization.k8s.io",
Kind: "Role",
Name: "test-cluster-barman-cloud",
},
}
// First Get returns NotFound (simulates cold informer
// cache after plugin pod restart). Subsequent Gets
// fall through to real fake-client behavior.
gets := 0
fakeClient = fake.NewClientBuilder().
WithScheme(newScheme()).
WithObjects(preExisting).
WithInterceptorFuncs(interceptor.Funcs{
Get: func(
ctx context.Context,
c client.WithWatch,
key client.ObjectKey,
obj client.Object,
opts ...client.GetOption,
) error {
gets++
if gets == 1 {
return apierrs.NewNotFound(
rbacv1.Resource("rolebindings"), key.Name)
}
return c.Get(ctx, key, obj, opts...)
},
}).
Build()
})
It("should tolerate the AlreadyExists and reconcile from the existing object", func() {
err := rbac.EnsureRoleBinding(ctx, fakeClient, cluster)
Expect(err).NotTo(HaveOccurred())
var rb rbacv1.RoleBinding
Expect(fakeClient.Get(ctx, client.ObjectKey{
Namespace: "default",
Name: "test-cluster-barman-cloud",
}, &rb)).To(Succeed())
Expect(rb.Subjects).To(ContainElement(rbacv1.Subject{
Kind: "ServiceAccount",
Name: "test-cluster",
Namespace: "default",
APIGroup: "",
}))
})
})
Context("when the RoleBinding exists without labels (upgrade scenario)", func() { Context("when the RoleBinding exists without labels (upgrade scenario)", func() {
BeforeEach(func() { BeforeEach(func() {
fakeClient = fake.NewClientBuilder().WithScheme(newScheme()).Build() fakeClient = fake.NewClientBuilder().WithScheme(newScheme()).Build()
@ -506,23 +701,21 @@ var _ = Describe("EnsureRoleRules", func() {
}) })
It("should not patch when rules already match", func() { It("should not patch when rules already match", func() {
// Seed with the same objects so rules match // Replace the seeded client with a counting one,
// then re-seed via EnsureRole so the desired rules
// are already in place when EnsureRoleRules runs.
var patchCount *int
fakeClient, patchCount = newPatchCountingClient()
cluster := newCluster("test-cluster", "default") cluster := newCluster("test-cluster", "default")
Expect(rbac.EnsureRole(ctx, fakeClient, cluster, objects)).To(Succeed()) Expect(rbac.EnsureRole(ctx, fakeClient, cluster, objects)).To(Succeed())
*patchCount = 0
roleKey := client.ObjectKey{ roleKey := client.ObjectKey{
Namespace: "default", Namespace: "default",
Name: "test-cluster-barman-cloud", Name: "test-cluster-barman-cloud",
} }
var before rbacv1.Role
Expect(fakeClient.Get(ctx, roleKey, &before)).To(Succeed())
Expect(rbac.EnsureRoleRules(ctx, fakeClient, roleKey, objects)).To(Succeed()) Expect(rbac.EnsureRoleRules(ctx, fakeClient, roleKey, objects)).To(Succeed())
Expect(*patchCount).To(BeZero())
var after rbacv1.Role
Expect(fakeClient.Get(ctx, roleKey, &after)).To(Succeed())
Expect(after.ResourceVersion).To(Equal(before.ResourceVersion))
}) })
It("should not modify labels", func() { It("should not modify labels", func() {

View File

@ -20,28 +20,22 @@ SPDX-License-Identifier: Apache-2.0
package specs package specs
import ( import (
"fmt"
cnpgv1 "github.com/cloudnative-pg/cloudnative-pg/api/v1" cnpgv1 "github.com/cloudnative-pg/cloudnative-pg/api/v1"
"github.com/cloudnative-pg/cloudnative-pg/pkg/utils" "github.com/cloudnative-pg/cloudnative-pg/pkg/utils"
"github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/metadata" "github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/metadata"
) )
// GetRequiredLabels returns the labels that must be present on all plugin-managed objects for a given Cluster. // BuildLabels returns the Kubernetes recommended labels applied to
func GetRequiredLabels(cluster *cnpgv1.Cluster) map[string]string { // every object managed by this plugin for the given Cluster. See
requiredLabels := map[string]string{ // https://github.com/cloudnative-pg/plugin-barman-cloud/issues/545.
metadata.ClusterLabelName: cluster.Name, func BuildLabels(cluster *cnpgv1.Cluster) map[string]string {
// Kubernetes recommended labels return map[string]string{
utils.KubernetesAppLabelName: utils.AppName, metadata.ClusterLabelName: cluster.Name,
utils.KubernetesAppLabelName: metadata.AppLabelValue,
utils.KubernetesAppInstanceLabelName: cluster.Name, utils.KubernetesAppInstanceLabelName: cluster.Name,
utils.KubernetesAppManagedByLabelName: "plugin-barman-cloud", utils.KubernetesAppVersionLabelName: metadata.Data.Version,
utils.KubernetesAppComponentLabelName: utils.DatabaseComponentName, utils.KubernetesAppComponentLabelName: utils.DatabaseComponentName,
utils.KubernetesAppManagedByLabelName: metadata.ManagedByLabelValue,
} }
if version, err := cluster.GetPostgresqlMajorVersion(); err == nil && version != 0 {
requiredLabels[utils.KubernetesAppVersionLabelName] = fmt.Sprint(version)
}
return requiredLabels
} }

View File

@ -29,25 +29,26 @@ import (
"github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/metadata" "github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/metadata"
) )
var _ = Describe("GetRequiredLabels", func() { var _ = Describe("BuildLabels", func() {
It("should return all expected labels for a cluster without an image", func() { It("should return the recommended labels with plugin identity", func() {
cluster := &cnpgv1.Cluster{ cluster := &cnpgv1.Cluster{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "my-cluster", Name: "my-cluster",
Namespace: "default", Namespace: "default",
}, },
} }
labels := GetRequiredLabels(cluster) labels := BuildLabels(cluster)
Expect(labels).To(HaveKeyWithValue(metadata.ClusterLabelName, "my-cluster")) Expect(labels).To(HaveKeyWithValue(metadata.ClusterLabelName, "my-cluster"))
Expect(labels).To(HaveKeyWithValue(utils.KubernetesAppLabelName, utils.AppName)) Expect(labels).To(HaveKeyWithValue(utils.KubernetesAppLabelName, metadata.AppLabelValue))
Expect(labels).To(HaveKeyWithValue(utils.KubernetesAppInstanceLabelName, "my-cluster")) Expect(labels).To(HaveKeyWithValue(utils.KubernetesAppInstanceLabelName, "my-cluster"))
Expect(labels).To(HaveKeyWithValue(utils.KubernetesAppManagedByLabelName, "plugin-barman-cloud")) Expect(labels).To(HaveKeyWithValue(utils.KubernetesAppVersionLabelName, metadata.Data.Version))
Expect(labels).To(HaveKeyWithValue(utils.KubernetesAppComponentLabelName, utils.DatabaseComponentName)) Expect(labels).To(HaveKeyWithValue(utils.KubernetesAppComponentLabelName, utils.DatabaseComponentName))
Expect(labels).To(HaveKeyWithValue(utils.KubernetesAppManagedByLabelName, metadata.ManagedByLabelValue))
Expect(labels).To(HaveLen(6)) Expect(labels).To(HaveLen(6))
}) })
It("should use the major version from the image catalog ref", func() { It("should report the plugin version regardless of the cluster's Postgres image", func() {
cluster := &cnpgv1.Cluster{ cluster := &cnpgv1.Cluster{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "pg16-cluster", Name: "pg16-cluster",
@ -59,9 +60,9 @@ var _ = Describe("GetRequiredLabels", func() {
}, },
}, },
} }
labels := GetRequiredLabels(cluster) labels := BuildLabels(cluster)
Expect(labels).To(HaveKeyWithValue(utils.KubernetesAppVersionLabelName, "16")) Expect(labels).To(HaveKeyWithValue(utils.KubernetesAppVersionLabelName, metadata.Data.Version))
Expect(labels).To(HaveKeyWithValue(utils.KubernetesAppInstanceLabelName, "pg16-cluster")) Expect(labels).To(HaveKeyWithValue(utils.KubernetesAppInstanceLabelName, "pg16-cluster"))
}) })
}) })

View File

@ -40,7 +40,7 @@ func BuildRole(
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Namespace: cluster.Namespace, Namespace: cluster.Namespace,
Name: GetRBACName(cluster.Name), Name: GetRBACName(cluster.Name),
Labels: GetRequiredLabels(cluster), Labels: BuildLabels(cluster),
}, },
Rules: BuildRoleRules(barmanObjects), Rules: BuildRoleRules(barmanObjects),
} }
@ -128,7 +128,7 @@ func BuildRoleBinding(
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Namespace: cluster.Namespace, Namespace: cluster.Namespace,
Name: GetRBACName(cluster.Name), Name: GetRBACName(cluster.Name),
Labels: GetRequiredLabels(cluster), Labels: BuildLabels(cluster),
}, },
Subjects: []rbacv1.Subject{ Subjects: []rbacv1.Subject{
{ {

View File

@ -2,15 +2,15 @@
# yarn lockfile v1 # yarn lockfile v1
"@algolia/abtesting@1.16.2": "@algolia/abtesting@1.17.0":
version "1.16.2" version "1.17.0"
resolved "https://registry.yarnpkg.com/@algolia/abtesting/-/abtesting-1.16.2.tgz#f0f0db8a070c4dc0a08c35e851f97651e29f160a" resolved "https://registry.yarnpkg.com/@algolia/abtesting/-/abtesting-1.17.0.tgz#d1c5cb798852b7d61225935ecca3efcef042cf20"
integrity sha512-n9s6bEV6imdtIEd+BGP7WkA4pEZ5YTdgQ05JQhHwWawHg3hyjpNwC0TShGz6zWhv+jfLDGA/6FFNbySFS0P9cw== integrity sha512-nuhHZdTiCtRzJEe9VSNzyqE9cOQMt01UWBzymFnjbgwrxxZpbGHQde6Oa/y9zyspTCjbUtb7Q5HQek1CLiLyeg==
dependencies: dependencies:
"@algolia/client-common" "5.50.2" "@algolia/client-common" "5.51.0"
"@algolia/requester-browser-xhr" "5.50.2" "@algolia/requester-browser-xhr" "5.51.0"
"@algolia/requester-fetch" "5.50.2" "@algolia/requester-fetch" "5.51.0"
"@algolia/requester-node-http" "5.50.2" "@algolia/requester-node-http" "5.51.0"
"@algolia/autocomplete-core@1.19.2": "@algolia/autocomplete-core@1.19.2":
version "1.19.2" version "1.19.2"
@ -52,126 +52,126 @@
resolved "https://registry.yarnpkg.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.19.8.tgz#5d723d8bdb448efbb1b0e1c7ff94cc18e5b1dc0e" resolved "https://registry.yarnpkg.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.19.8.tgz#5d723d8bdb448efbb1b0e1c7ff94cc18e5b1dc0e"
integrity sha512-h5hf2t8ejF6vlOgvLaZzQbWs5SyH2z4PAWygNAvvD/2RI29hdQ54ldUGwqVuj9Srs+n8XUKTPUqb7fvhBhQrnQ== integrity sha512-h5hf2t8ejF6vlOgvLaZzQbWs5SyH2z4PAWygNAvvD/2RI29hdQ54ldUGwqVuj9Srs+n8XUKTPUqb7fvhBhQrnQ==
"@algolia/client-abtesting@5.50.2": "@algolia/client-abtesting@5.51.0":
version "5.50.2" version "5.51.0"
resolved "https://registry.yarnpkg.com/@algolia/client-abtesting/-/client-abtesting-5.50.2.tgz#0fa6442c8792221d77bbfd42308d7f60aafe4edd" resolved "https://registry.yarnpkg.com/@algolia/client-abtesting/-/client-abtesting-5.51.0.tgz#20e30e3c0c302faefeef5d6d85e96acef3ecd5a4"
integrity sha512-52iq0vHy1sphgnwoZyx5PmbEt8hsh+m7jD123LmBs6qy4GK7LbYZIeKd+nSnSipN2zvKRZ2zScS6h9PW3J7SXg== integrity sha512-PKrKlIla1U2J7mFcIQn6N3pWP4oySmkxShnbbDsj/H7818gKbET5KsUwsVoNjWIxHKTJMCTcQ7ekAJ8Ea23NMg==
dependencies: dependencies:
"@algolia/client-common" "5.50.2" "@algolia/client-common" "5.51.0"
"@algolia/requester-browser-xhr" "5.50.2" "@algolia/requester-browser-xhr" "5.51.0"
"@algolia/requester-fetch" "5.50.2" "@algolia/requester-fetch" "5.51.0"
"@algolia/requester-node-http" "5.50.2" "@algolia/requester-node-http" "5.51.0"
"@algolia/client-analytics@5.50.2": "@algolia/client-analytics@5.51.0":
version "5.50.2" version "5.51.0"
resolved "https://registry.yarnpkg.com/@algolia/client-analytics/-/client-analytics-5.50.2.tgz#458d2c4119ff543dfd2c0ebd685ac6c3c279bdec" resolved "https://registry.yarnpkg.com/@algolia/client-analytics/-/client-analytics-5.51.0.tgz#91d79812b26c4462bdd50911a8824c308c13a921"
integrity sha512-WpPIUg+cSG2aPUG0gS8Ko9DwRgbRPUZxJkolhL2aCsmSlcEEZT65dILrfg5ovcxtx0Kvr+xtBVsTMtsQWRtPDQ== integrity sha512-U+HCY1K16Km91pIRL1kN6bW6BbGFAF/WhkRSCx4wyl1aFpbrlhSFQs/dAwWbmyBiHWwVWhl7stWHQ1pum5EfMw==
dependencies: dependencies:
"@algolia/client-common" "5.50.2" "@algolia/client-common" "5.51.0"
"@algolia/requester-browser-xhr" "5.50.2" "@algolia/requester-browser-xhr" "5.51.0"
"@algolia/requester-fetch" "5.50.2" "@algolia/requester-fetch" "5.51.0"
"@algolia/requester-node-http" "5.50.2" "@algolia/requester-node-http" "5.51.0"
"@algolia/client-common@5.50.2": "@algolia/client-common@5.51.0":
version "5.50.2" version "5.51.0"
resolved "https://registry.yarnpkg.com/@algolia/client-common/-/client-common-5.50.2.tgz#059ff282b52106f592d77a59b3993d01e51d2918" resolved "https://registry.yarnpkg.com/@algolia/client-common/-/client-common-5.51.0.tgz#094ded739d8e7ef3eca05fe9a9ae49d7620fde50"
integrity sha512-Gj2MgtArGcsr82kIqRlo6/dCAFjrs2gLByEqyRENuT7ugrSMFuqg1vDzeBjRL1t3EJEJCFtT0PLX3gB8A6Hq4Q== integrity sha512-YPJ3dEuZLCRp846Az94t6Z2gwSNRazP+SmBco7p6SCa4fYrtIE820PDXYZshbNrj2Z8Qfbmv7BQ1Lecl5L3G/w==
"@algolia/client-insights@5.50.2": "@algolia/client-insights@5.51.0":
version "5.50.2" version "5.51.0"
resolved "https://registry.yarnpkg.com/@algolia/client-insights/-/client-insights-5.50.2.tgz#4606f3b9a7c3dee42ff8fbb665f189ad6f1630c8" resolved "https://registry.yarnpkg.com/@algolia/client-insights/-/client-insights-5.51.0.tgz#26b23d63a883575c16445cc4a101e0eb170966b8"
integrity sha512-CUqoid5jDpmrc0oK3/xuZXFt6kwT0P9Lw7/nsM14YTr6puvmi+OUKmURpmebQF22S2vCG8L1DAoXXujxQUi/ug== integrity sha512-/gEwLlR7fQ7YjOW+ADRZ0NxLDtpTC61FSzlZ01Gdl1kTJfU0Rq3Y/TYqwxGxlQGcUiXtGzrpjxXWh3Y0TZD6NA==
dependencies: dependencies:
"@algolia/client-common" "5.50.2" "@algolia/client-common" "5.51.0"
"@algolia/requester-browser-xhr" "5.50.2" "@algolia/requester-browser-xhr" "5.51.0"
"@algolia/requester-fetch" "5.50.2" "@algolia/requester-fetch" "5.51.0"
"@algolia/requester-node-http" "5.50.2" "@algolia/requester-node-http" "5.51.0"
"@algolia/client-personalization@5.50.2": "@algolia/client-personalization@5.51.0":
version "5.50.2" version "5.51.0"
resolved "https://registry.yarnpkg.com/@algolia/client-personalization/-/client-personalization-5.50.2.tgz#a3b536106afe5940fb0ad4c864ada2c8c8a11f04" resolved "https://registry.yarnpkg.com/@algolia/client-personalization/-/client-personalization-5.51.0.tgz#44bd17e79503ba32e199484c5b42683384d2805c"
integrity sha512-AndZWFoc0gbP5901OeQJ73BazgGgSGiBEba4ohdoJuZwHTO2Gio8Q4L1VLmytMBYcviVigB0iICToMvEJxI4ug== integrity sha512-nRwUN1Y2cKyOAFZyIBagkEfZSIhP05nWhT4Rjwl84lcjECssYggftrAODrZ4leakXxSGjhxs/AdaAFEIBqwVFA==
dependencies: dependencies:
"@algolia/client-common" "5.50.2" "@algolia/client-common" "5.51.0"
"@algolia/requester-browser-xhr" "5.50.2" "@algolia/requester-browser-xhr" "5.51.0"
"@algolia/requester-fetch" "5.50.2" "@algolia/requester-fetch" "5.51.0"
"@algolia/requester-node-http" "5.50.2" "@algolia/requester-node-http" "5.51.0"
"@algolia/client-query-suggestions@5.50.2": "@algolia/client-query-suggestions@5.51.0":
version "5.50.2" version "5.51.0"
resolved "https://registry.yarnpkg.com/@algolia/client-query-suggestions/-/client-query-suggestions-5.50.2.tgz#44180b6a7381c2070fe7c006c673f0b5d4b56c4f" resolved "https://registry.yarnpkg.com/@algolia/client-query-suggestions/-/client-query-suggestions-5.51.0.tgz#ca22ef16ffdd6a5a889161b309fa53ad2e2a3ab1"
integrity sha512-NWoL+psEkz5dIzweaByVXuEB45wS8/rk0E0AhMMnaVJdVs7TcACPH2/OURm+N0xRDITkTHqCna823rd6Uqntdg== integrity sha512-pybzYCG7VoQKppo+z5iZOKpW8XqtFxhsAIRgEaNboCnfypKukiBHJAwB+pmr7vMZXBsOHwklGYWwCG82e8qshA==
dependencies: dependencies:
"@algolia/client-common" "5.50.2" "@algolia/client-common" "5.51.0"
"@algolia/requester-browser-xhr" "5.50.2" "@algolia/requester-browser-xhr" "5.51.0"
"@algolia/requester-fetch" "5.50.2" "@algolia/requester-fetch" "5.51.0"
"@algolia/requester-node-http" "5.50.2" "@algolia/requester-node-http" "5.51.0"
"@algolia/client-search@5.50.2": "@algolia/client-search@5.51.0":
version "5.50.2" version "5.51.0"
resolved "https://registry.yarnpkg.com/@algolia/client-search/-/client-search-5.50.2.tgz#27501c5c0a457891c5246d6c23e3c4b81bba8e8e" resolved "https://registry.yarnpkg.com/@algolia/client-search/-/client-search-5.51.0.tgz#6ae39eb6e4a316ba457c9777f6ea1b2a15d19f0e"
integrity sha512-ypSboUJ3XJoQz5DeDo82hCnrRuwq3q9ZdFhVKAik9TnZh1DvLqoQsrbBjXg7C7zQOtV/Qbge/HmyoV6V5L7MhQ== integrity sha512-DWVIlj6RqcvdhwP0gBU9OpOQPnHdcAk9jlT+z8rsNb2+liWv4eUlfQZ7saGBraFsnygEHD3PtdppIHvqwBAb5w==
dependencies: dependencies:
"@algolia/client-common" "5.50.2" "@algolia/client-common" "5.51.0"
"@algolia/requester-browser-xhr" "5.50.2" "@algolia/requester-browser-xhr" "5.51.0"
"@algolia/requester-fetch" "5.50.2" "@algolia/requester-fetch" "5.51.0"
"@algolia/requester-node-http" "5.50.2" "@algolia/requester-node-http" "5.51.0"
"@algolia/events@^4.0.1": "@algolia/events@^4.0.1":
version "4.0.1" version "4.0.1"
resolved "https://registry.yarnpkg.com/@algolia/events/-/events-4.0.1.tgz#fd39e7477e7bc703d7f893b556f676c032af3950" resolved "https://registry.yarnpkg.com/@algolia/events/-/events-4.0.1.tgz#fd39e7477e7bc703d7f893b556f676c032af3950"
integrity sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ== integrity sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==
"@algolia/ingestion@1.50.2": "@algolia/ingestion@1.51.0":
version "1.50.2" version "1.51.0"
resolved "https://registry.yarnpkg.com/@algolia/ingestion/-/ingestion-1.50.2.tgz#ea485239264d5c8296387d3a4563fbe7b025cdf4" resolved "https://registry.yarnpkg.com/@algolia/ingestion/-/ingestion-1.51.0.tgz#b8344f2bf7012e1fe72fd8e6e78384d85e6690ad"
integrity sha512-VlR2FRXLw2bCB94SQo6zxg/Qi+547aOji6Pb+dKE7h1DMCCY317St+OpjpmgzE+bT2O9ALIc0V4nVIBOd7Gy+Q== integrity sha512-bA25s12iUDJi/X8M7tWlPRT8GeOhls/yDbdoUqinz27lNqsOlcM1UrAxIKdIZ6lm3sXit+ewPIz1pS2x6rXu8g==
dependencies: dependencies:
"@algolia/client-common" "5.50.2" "@algolia/client-common" "5.51.0"
"@algolia/requester-browser-xhr" "5.50.2" "@algolia/requester-browser-xhr" "5.51.0"
"@algolia/requester-fetch" "5.50.2" "@algolia/requester-fetch" "5.51.0"
"@algolia/requester-node-http" "5.50.2" "@algolia/requester-node-http" "5.51.0"
"@algolia/monitoring@1.50.2": "@algolia/monitoring@1.51.0":
version "1.50.2" version "1.51.0"
resolved "https://registry.yarnpkg.com/@algolia/monitoring/-/monitoring-1.50.2.tgz#2a346a7445bb965b2930bdb6303a7f9361da3320" resolved "https://registry.yarnpkg.com/@algolia/monitoring/-/monitoring-1.51.0.tgz#5518fd6f9c0205dac3655639262f3b395dbf3dc8"
integrity sha512-Cmvfp2+qopzQt8OilU97rhLhosq7ZrB6uieok3EwFUqG/aalPg6DgfCmu0yJMrYe+KMC1qRVt1MTRAUwLknUMQ== integrity sha512-zj+RcE5e0NE0/ew6oEOTgOplPHry+w2oi7h0Y87lhdq4E0d7xLS31KVB8kKfCGkrG7AYtZvrcyvLOKS5d0no4Q==
dependencies: dependencies:
"@algolia/client-common" "5.50.2" "@algolia/client-common" "5.51.0"
"@algolia/requester-browser-xhr" "5.50.2" "@algolia/requester-browser-xhr" "5.51.0"
"@algolia/requester-fetch" "5.50.2" "@algolia/requester-fetch" "5.51.0"
"@algolia/requester-node-http" "5.50.2" "@algolia/requester-node-http" "5.51.0"
"@algolia/recommend@5.50.2": "@algolia/recommend@5.51.0":
version "5.50.2" version "5.51.0"
resolved "https://registry.yarnpkg.com/@algolia/recommend/-/recommend-5.50.2.tgz#8f0264ffbbfe183198e3ee8dbe9d621bfa0d248c" resolved "https://registry.yarnpkg.com/@algolia/recommend/-/recommend-5.51.0.tgz#d987d3baad9a420fcf896912610f78f8ab0daf3c"
integrity sha512-jrkuyKoOM7dFWQ/6Y4hQAse2SC3L/RldG6GnPjMvAj65h+7Ubb51S0pKk4ofSStF0xm4LCNe0C4T6XX4nOFDiQ== integrity sha512-/HDgccfye1Rq3bPxaSCsvSEBHzSMmtpM9ZRGRtAuC62Cv+ql/76IWnxjGTDXtqIJ+/j7ZlFYAzq9fkp95wF2SQ==
dependencies: dependencies:
"@algolia/client-common" "5.50.2" "@algolia/client-common" "5.51.0"
"@algolia/requester-browser-xhr" "5.50.2" "@algolia/requester-browser-xhr" "5.51.0"
"@algolia/requester-fetch" "5.50.2" "@algolia/requester-fetch" "5.51.0"
"@algolia/requester-node-http" "5.50.2" "@algolia/requester-node-http" "5.51.0"
"@algolia/requester-browser-xhr@5.50.2": "@algolia/requester-browser-xhr@5.51.0":
version "5.50.2" version "5.51.0"
resolved "https://registry.yarnpkg.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.50.2.tgz#8285f922d2ac4ff5663cb0b22f28e3f32727b9cf" resolved "https://registry.yarnpkg.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.51.0.tgz#eb693cc8c6944966dc9d336c47b39fb738de22ad"
integrity sha512-4107YLJqCudPiBUlwnk6oTSUVwU7ab+qL1SfQGEDYI8DZH5gsf1ekPt9JykXRKYXf2IfouFL5GiCY/PHTFIjYw== integrity sha512-nJdW+WBwGlXWMJbxxB7/AJPvNq0lLJSudXmIQCJbmH8jsOXQhRpAtoCD4ceLyJKv3ze9JbQu4Gqu5JDLckuFcw==
dependencies: dependencies:
"@algolia/client-common" "5.50.2" "@algolia/client-common" "5.51.0"
"@algolia/requester-fetch@5.50.2": "@algolia/requester-fetch@5.51.0":
version "5.50.2" version "5.51.0"
resolved "https://registry.yarnpkg.com/@algolia/requester-fetch/-/requester-fetch-5.50.2.tgz#a0c33f399be213d6f678d2b7ca91fc5e9435a619" resolved "https://registry.yarnpkg.com/@algolia/requester-fetch/-/requester-fetch-5.51.0.tgz#d667a6d235e3e6f2274213298d28751bd0bbc3cc"
integrity sha512-vOrd3MQpLgmf6wXAueTuZ/cA0W4uRwIHHaxNy3h+a6YcNn6bCV/gFdZuv3F13v593zRU2k5R75NmvRWLenvMrw== integrity sha512-bsBgRI/1h1mjS3eCyfGau78yGZVmiDLmT1aU6dMnk75/T0SgKqnSKNpQ53xKoDYVChGDcNnpHXWpoUSo8MH1+w==
dependencies: dependencies:
"@algolia/client-common" "5.50.2" "@algolia/client-common" "5.51.0"
"@algolia/requester-node-http@5.50.2": "@algolia/requester-node-http@5.51.0":
version "5.50.2" version "5.51.0"
resolved "https://registry.yarnpkg.com/@algolia/requester-node-http/-/requester-node-http-5.50.2.tgz#c5e3a3cf2863393c7d9f012a455973c48fd140a4" resolved "https://registry.yarnpkg.com/@algolia/requester-node-http/-/requester-node-http-5.51.0.tgz#981b9241e0e34f62166cf5f81df3b10bd3529141"
integrity sha512-Mu9BFtgzGqDUy5Bcs2nMyoILIFSN13GKQaklKAFIsd0K3/9CpNyfeBc+/+Qs6mFZLlxG9qzullO7h+bjcTBuGQ== integrity sha512-zPrIDVPpmKWgrjmWOqpqrhqAhNjvVkjoj+mqw2NBPxEOuKNzP0H+Qz5NJLLTOepBVj1UFedFaF3AUgxLsB9ukQ==
dependencies: dependencies:
"@algolia/client-common" "5.50.2" "@algolia/client-common" "5.51.0"
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.28.6", "@babel/code-frame@^7.29.0": "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.28.6", "@babel/code-frame@^7.29.0":
version "7.29.0" version "7.29.0"
@ -3268,9 +3268,9 @@ ajv-keywords@^5.1.0:
fast-deep-equal "^3.1.3" fast-deep-equal "^3.1.3"
ajv@^6.12.5: ajv@^6.12.5:
version "6.14.0" version "6.15.0"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.14.0.tgz#fd067713e228210636ebb08c60bd3765d6dbe73a" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.15.0.tgz#07e982c74626167aa7a2495c53817892d7139492"
integrity sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw== integrity sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==
dependencies: dependencies:
fast-deep-equal "^3.1.1" fast-deep-equal "^3.1.1"
fast-json-stable-stringify "^2.0.0" fast-json-stable-stringify "^2.0.0"
@ -3278,9 +3278,9 @@ ajv@^6.12.5:
uri-js "^4.2.2" uri-js "^4.2.2"
ajv@^8.0.0, ajv@^8.9.0: ajv@^8.0.0, ajv@^8.9.0:
version "8.18.0" version "8.20.0"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.18.0.tgz#8864186b6738d003eb3a933172bb3833e10cefbc" resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.20.0.tgz#304b3636add88ba7d936760dd50ece006dea95f9"
integrity sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A== integrity sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==
dependencies: dependencies:
fast-deep-equal "^3.1.3" fast-deep-equal "^3.1.3"
fast-uri "^3.0.1" fast-uri "^3.0.1"
@ -3295,24 +3295,24 @@ algoliasearch-helper@^3.26.0:
"@algolia/events" "^4.0.1" "@algolia/events" "^4.0.1"
algoliasearch@^5.37.0: algoliasearch@^5.37.0:
version "5.50.2" version "5.51.0"
resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-5.50.2.tgz#0ce5bc3ca15c7854e4845b9cbbe29fc2854d9627" resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-5.51.0.tgz#aeb693d28de6aa45100e128e7beb3e000b8e4081"
integrity sha512-Tfp26yoNWurUjfgK4GOrVJQhSNXu9tJtHfFFNosgT2YClG+vPyUjX/gbC8rG39qLncnZg8Fj34iarQWpMkqefw== integrity sha512-u3XS8HaTzt5YN90KPsOXMRjYJUMVD1dtr6yi4NXQluMbZ5IjQNBu1MEabdAxFhYtEuexqomPMSmRIhQJUd3QSg==
dependencies: dependencies:
"@algolia/abtesting" "1.16.2" "@algolia/abtesting" "1.17.0"
"@algolia/client-abtesting" "5.50.2" "@algolia/client-abtesting" "5.51.0"
"@algolia/client-analytics" "5.50.2" "@algolia/client-analytics" "5.51.0"
"@algolia/client-common" "5.50.2" "@algolia/client-common" "5.51.0"
"@algolia/client-insights" "5.50.2" "@algolia/client-insights" "5.51.0"
"@algolia/client-personalization" "5.50.2" "@algolia/client-personalization" "5.51.0"
"@algolia/client-query-suggestions" "5.50.2" "@algolia/client-query-suggestions" "5.51.0"
"@algolia/client-search" "5.50.2" "@algolia/client-search" "5.51.0"
"@algolia/ingestion" "1.50.2" "@algolia/ingestion" "1.51.0"
"@algolia/monitoring" "1.50.2" "@algolia/monitoring" "1.51.0"
"@algolia/recommend" "5.50.2" "@algolia/recommend" "5.51.0"
"@algolia/requester-browser-xhr" "5.50.2" "@algolia/requester-browser-xhr" "5.51.0"
"@algolia/requester-fetch" "5.50.2" "@algolia/requester-fetch" "5.51.0"
"@algolia/requester-node-http" "5.50.2" "@algolia/requester-node-http" "5.51.0"
ansi-align@^3.0.1: ansi-align@^3.0.1:
version "3.0.1" version "3.0.1"
@ -3471,9 +3471,9 @@ balanced-match@^1.0.0:
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
baseline-browser-mapping@^2.10.12: baseline-browser-mapping@^2.10.12:
version "2.10.20" version "2.10.23"
resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.20.tgz#7c99b86d43ae9be3810cac515f4675802e1f6b87" resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.23.tgz#3a1a55d1a691a8c8d74688af7f1fd17eac23c184"
integrity sha512-1AaXxEPfXT+GvTBJFuy4yXVHWJBXa4OdbIebGN/wX5DlsIkU0+wzGnd2lOzokSk51d5LUmqjgBLRLlypLUqInQ== integrity sha512-xwVXGqevyKPsiuQdLj+dZMVjidjJV508TBqexND5HrF89cGdCYCJFB3qhcxRHSeMctdCfbR1jrxBajhDy7o29g==
batch@0.6.1: batch@0.6.1:
version "0.6.1" version "0.6.1"
@ -3491,9 +3491,9 @@ binary-extensions@^2.0.0:
integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==
body-parser@~1.20.3: body-parser@~1.20.3:
version "1.20.4" version "1.20.5"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.4.tgz#f8e20f4d06ca8a50a71ed329c15dccad1cdc547f" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.5.tgz#303c8c34423d1d6fa799bc764e93c1e4dc6ebf64"
integrity sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA== integrity sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==
dependencies: dependencies:
bytes "~3.1.2" bytes "~3.1.2"
content-type "~1.0.5" content-type "~1.0.5"
@ -3503,7 +3503,7 @@ body-parser@~1.20.3:
http-errors "~2.0.1" http-errors "~2.0.1"
iconv-lite "~0.4.24" iconv-lite "~0.4.24"
on-finished "~2.4.1" on-finished "~2.4.1"
qs "~6.14.0" qs "~6.15.1"
raw-body "~2.5.3" raw-body "~2.5.3"
type-is "~1.6.18" type-is "~1.6.18"
unpipe "~1.0.0" unpipe "~1.0.0"
@ -3680,9 +3680,9 @@ caniuse-api@^3.0.0:
lodash.uniq "^4.5.0" lodash.uniq "^4.5.0"
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001782, caniuse-lite@^1.0.30001787: caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001782, caniuse-lite@^1.0.30001787:
version "1.0.30001788" version "1.0.30001791"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001788.tgz#31e97d1bfec332b3f2d7eea7781460c97629b3bf" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001791.tgz#dfb93d85c40ad380c57123e72e10f3c575786b51"
integrity sha512-6q8HFp+lOQtcf7wBK+uEenxymVWkGKkjFpCvw5W25cmMwEDU45p1xQFBQv8JDlMMry7eNxyBaR+qxgmTUZkIRQ== integrity sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==
ccount@^2.0.0: ccount@^2.0.0:
version "2.0.1" version "2.0.1"
@ -4476,9 +4476,9 @@ ee-first@1.1.1:
integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
electron-to-chromium@^1.5.328: electron-to-chromium@^1.5.328:
version "1.5.340" version "1.5.344"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.340.tgz#fe3f76e8d9b9541c123fb7edbc3381688272f79a" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.344.tgz#6437cc08a7d9b914a98120e182f37793c9eaffd4"
integrity sha512-908qahOGocRMinT2nM3ajCEM99H4iPdv84eagPP3FfZy/1ZGeOy2CZYzjhms81ckOPCXPlW7LkY4XpxD8r1DrA== integrity sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg==
emoji-regex@^8.0.0: emoji-regex@^8.0.0:
version "8.0.0" version "8.0.0"
@ -4519,12 +4519,12 @@ encoding-sniffer@^0.2.1:
whatwg-encoding "^3.1.1" whatwg-encoding "^3.1.1"
enhanced-resolve@^5.20.0: enhanced-resolve@^5.20.0:
version "5.20.1" version "5.21.0"
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz#eeeb3966bea62c348c40a0cc9e7912e2557d0be0" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.21.0.tgz#bb8e6fabaf74930de70e61397798750429e5b1ae"
integrity sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA== integrity sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA==
dependencies: dependencies:
graceful-fs "^4.2.4" graceful-fs "^4.2.4"
tapable "^2.3.0" tapable "^2.3.3"
entities@^2.0.0: entities@^2.0.0:
version "2.2.0" version "2.2.0"
@ -4564,9 +4564,9 @@ es-errors@^1.3.0:
integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
es-module-lexer@^2.0.0: es-module-lexer@^2.0.0:
version "2.0.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-2.0.0.tgz#f657cd7a9448dcdda9c070a3cb75e5dc1e85f5b1" resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-2.1.0.tgz#1dfcbb5ea3bbfb63f28e1fc3676c3676d1c9624c"
integrity sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw== integrity sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==
es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: es-object-atoms@^1.0.0, es-object-atoms@^1.1.1:
version "1.1.1" version "1.1.1"
@ -5912,9 +5912,9 @@ lines-and-columns@^1.1.6:
integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
loader-runner@^4.3.1: loader-runner@^4.3.1:
version "4.3.1" version "4.3.2"
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.1.tgz#6c76ed29b0ccce9af379208299f07f876de737e3" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.2.tgz#9913d3a15971f8f635915e601fb5c9d495d918e9"
integrity sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q== integrity sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w==
loader-utils@^2.0.0: loader-utils@^2.0.0:
version "2.0.4" version "2.0.4"
@ -6853,9 +6853,9 @@ node-emoji@^2.1.0:
skin-tone "^2.0.0" skin-tone "^2.0.0"
node-releases@^2.0.36: node-releases@^2.0.36:
version "2.0.37" version "2.0.38"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.37.tgz#9bd4f10b77ba39c2b9402d4e8399c482a797f671" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.38.tgz#791569b9e4424a044e12c3abfad418ed83ce9947"
integrity sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg== integrity sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==
normalize-path@^3.0.0, normalize-path@~3.0.0: normalize-path@^3.0.0, normalize-path@~3.0.0:
version "3.0.0" version "3.0.0"
@ -7739,9 +7739,9 @@ postcss-zindex@^6.0.2:
integrity sha512-5BxW9l1evPB/4ZIc+2GobEBoKC+h8gPGCMi+jxsYvd2x0mjq7wazk6DrP71pStqxE9Foxh5TVnonbWpFZzXaYg== integrity sha512-5BxW9l1evPB/4ZIc+2GobEBoKC+h8gPGCMi+jxsYvd2x0mjq7wazk6DrP71pStqxE9Foxh5TVnonbWpFZzXaYg==
postcss@^8.4.21, postcss@^8.4.24, postcss@^8.4.33, postcss@^8.5.4: postcss@^8.4.21, postcss@^8.4.24, postcss@^8.4.33, postcss@^8.5.4:
version "8.5.10" version "8.5.12"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.10.tgz#8992d8c30acf3f12169e7c09514a12fed7e48356" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.12.tgz#cd0c0f667f7cb0521e2313234ea6e707a9ec1ddb"
integrity sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ== integrity sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==
dependencies: dependencies:
nanoid "^3.3.11" nanoid "^3.3.11"
picocolors "^1.1.1" picocolors "^1.1.1"
@ -7844,6 +7844,13 @@ qs@~6.14.0:
dependencies: dependencies:
side-channel "^1.1.0" side-channel "^1.1.0"
qs@~6.15.1:
version "6.15.1"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.15.1.tgz#bdb55aed06bfac257a90c44a446a73fba5575c8f"
integrity sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==
dependencies:
side-channel "^1.1.0"
queue-microtask@^1.2.2: queue-microtask@^1.2.2:
version "1.2.3" version "1.2.3"
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
@ -8808,15 +8815,15 @@ svgo@^3.0.2, svgo@^3.2.0:
picocolors "^1.0.0" picocolors "^1.0.0"
sax "^1.5.0" sax "^1.5.0"
tapable@^2.0.0, tapable@^2.2.1, tapable@^2.3.0: tapable@^2.0.0, tapable@^2.2.1, tapable@^2.3.0, tapable@^2.3.3:
version "2.3.2" version "2.3.3"
resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.3.2.tgz#86755feabad08d82a26b891db044808c6ad00f15" resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.3.3.tgz#5da7c9992c46038221267985ab28421a8879f160"
integrity sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA== integrity sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==
terser-webpack-plugin@^5.3.17, terser-webpack-plugin@^5.3.9: terser-webpack-plugin@^5.3.17, terser-webpack-plugin@^5.3.9:
version "5.4.0" version "5.5.0"
resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.4.0.tgz#95fc4cf4437e587be11ecf37d08636089174d76b" resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.5.0.tgz#d92b8e2c892dd09c683c38120394267e8d8660ef"
integrity sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g== integrity sha512-UYhptBwhWvfIjKd/UuFo6D8uq9xpGLDK+z8EDsj/zWhrTaH34cKEbrkMKfV5YWqGBvAYA3tlzZbs2R+qYrbQJA==
dependencies: dependencies:
"@jridgewell/trace-mapping" "^0.3.25" "@jridgewell/trace-mapping" "^0.3.25"
jest-worker "^27.4.5" jest-worker "^27.4.5"
@ -8824,9 +8831,9 @@ terser-webpack-plugin@^5.3.17, terser-webpack-plugin@^5.3.9:
terser "^5.31.1" terser "^5.31.1"
terser@^5.10.0, terser@^5.15.1, terser@^5.31.1: terser@^5.10.0, terser@^5.15.1, terser@^5.31.1:
version "5.46.1" version "5.46.2"
resolved "https://registry.yarnpkg.com/terser/-/terser-5.46.1.tgz#40e4b1e35d5f13130f82793a8b3eeb7ec3a92eee" resolved "https://registry.yarnpkg.com/terser/-/terser-5.46.2.tgz#b9529672d5b0024c7959571c83b82f65077b2a4f"
integrity sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ== integrity sha512-uxfo9fPcSgLDYob/w1FuL0c99MWiJDnv+5qXSQc5+Ki5NjVNsYi66INnMFBjf6uFz6OnX12piJQPF4IpjJTNTw==
dependencies: dependencies:
"@jridgewell/source-map" "^0.3.3" "@jridgewell/source-map" "^0.3.3"
acorn "^8.15.0" acorn "^8.15.0"
@ -9256,9 +9263,9 @@ webpack-merge@^6.0.1:
wildcard "^2.0.1" wildcard "^2.0.1"
webpack-sources@^3.3.4: webpack-sources@^3.3.4:
version "3.3.4" version "3.4.0"
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.3.4.tgz#a338b95eb484ecc75fbb196cbe8a2890618b4891" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.4.0.tgz#67cdfdff349ff1e3e7ca5c1ed6a2802b84cf6cf5"
integrity sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q== integrity sha512-gHwIe1cgBvvfLeu1Yz/dcFpmHfKDVxxyqI+kzqmuxZED81z2ChxpyqPaWcNqigPywhaEke7AjSGga+kxY55gjQ==
webpack@^5.88.1, webpack@^5.95.0: webpack@^5.88.1, webpack@^5.95.0:
version "5.106.2" version "5.106.2"