From 1be5c6e9fcfec6ab364294646653a2a9d0f9b69f Mon Sep 17 00:00:00 2001 From: Francois Ferrand Date: Sun, 20 Sep 2026 21:06:21 +0200 Subject: [PATCH 1/4] Prepare Arsenal dependencies for Node 24 Update CI coverage and refresh the Scality dependency set for the Node 24 transition while retaining Node 22 compatibility. Synchronize the lockfile and stabilize the health probe setup on the newer runtime. Issue: ARSN-642 --- .github/workflows/lint.yaml | 2 +- .github/workflows/tests-kmip.yaml | 2 +- .github/workflows/tests.yaml | 11 +- package.json | 14 +- .../network/probe/HealthProbeServer.spec.js | 6 +- yarn.lock | 452 +++++++++++++++--- 6 files changed, 407 insertions(+), 80 deletions(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 0447bce75..3700a5280 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -19,7 +19,7 @@ jobs: fetch-depth: 0 - uses: actions/setup-node@v4 with: - node-version: '22' + node-version: '24' cache: yarn - name: install dependencies run: yarn install --frozen-lockfile --network-concurrency 1 diff --git a/.github/workflows/tests-kmip.yaml b/.github/workflows/tests-kmip.yaml index c93f36285..d27ff0f60 100644 --- a/.github/workflows/tests-kmip.yaml +++ b/.github/workflows/tests-kmip.yaml @@ -31,7 +31,7 @@ jobs: uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: '22' + node-version: '24' cache: 'yarn' - name: Install socat (tunnel to help create disconnect and reconnect to pykmip) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index f41c15200..682cb3f3d 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -9,6 +9,12 @@ on: jobs: test: runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + # 24 is the target runtime; 22 is what engines.node still allows and + # what cloudserver/backbeat/scuba run until they move over. + node-version: ['22', '24'] services: # Label used to access the service container redis: @@ -28,7 +34,7 @@ jobs: uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: '22' + node-version: ${{ matrix.node-version }} cache: 'yarn' - name: install dependencies run: yarn install --frozen-lockfile --prefer-offline --network-concurrency 1 @@ -46,6 +52,7 @@ jobs: - name: run functional tests run: yarn ft_test - uses: codecov/codecov-action@v4 + if: matrix.node-version == '24' with: token: ${{ secrets.CODECOV_TOKEN }} - name: run executables tests @@ -62,7 +69,7 @@ jobs: - name: Install NodeJS uses: actions/setup-node@v4 with: - node-version: '22' + node-version: '24' cache: yarn - name: Install dependencies run: yarn install --frozen-lockfile --prefer-offline diff --git a/package.json b/package.json index bf218fc6f..b0b719562 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@scality/arsenal", "engines": { - "node": ">=20" + "node": ">=22" }, "version": "8.5.17", "config": { @@ -52,8 +52,8 @@ "base-x": "3.0.8", "base62": "^2.0.2", "debug": "^4.4.3", - "fcntl": "github:scality/node-fcntl#0.3.0", - "httpagent": "scality/httpagent#1.1.0", + "fcntl": "github:scality/node-fcntl#0.3.1", + "httpagent": "scality/httpagent#1.1.1", "https-proxy-agent": "^7.0.6", "ioredis": "^5.8.1", "ipaddr.js": "^2.2.0", @@ -66,10 +66,10 @@ "simple-glob": "^0.2.0", "socket.io": "^4.8.0", "socket.io-client": "^4.8.0", - "sproxydclient": "github:scality/sproxydclient#8.2.2", + "sproxydclient": "github:scality/sproxydclient#8.2.3", "utf8": "^3.0.0", "uuid": "^10.0.0", - "werelogs": "scality/werelogs#8.2.4", + "werelogs": "scality/werelogs#8.2.5", "xml2js": "^0.6.2" }, "optionalDependencies": { @@ -82,7 +82,7 @@ "@eslint/plugin-kit": "^0.4.0", "@opentelemetry/context-async-hooks": "^2.8.0", "@opentelemetry/core": "^2.8.0", - "@scality/eslint-config-scality": "scality/Guidelines#8.3.2", + "@scality/eslint-config-scality": "scality/Guidelines#8.3.3", "@sinonjs/fake-timers": "^15.0.0", "@types/async": "^3.2.24", "@types/ioredis": "^5.0.0", @@ -146,7 +146,7 @@ }, "resolutions": { "tar": "^7.5.6", - "werelogs": "scality/werelogs#8.2.4" + "werelogs": "scality/werelogs#8.2.5" }, "nyc": { "tempDirectory": "coverage", diff --git a/tests/unit/network/probe/HealthProbeServer.spec.js b/tests/unit/network/probe/HealthProbeServer.spec.js index f234f7b58..9a520765d 100644 --- a/tests/unit/network/probe/HealthProbeServer.spec.js +++ b/tests/unit/network/probe/HealthProbeServer.spec.js @@ -69,8 +69,8 @@ describe('network.probe.HealthProbeServer', () => { livenessCheck: falseStub, readinessCheck: falseStub, }); + server._cbOnListening = done; server.start(); - done(); } beforeAll(done => { @@ -105,8 +105,8 @@ describe('network.probe.HealthProbeServer', () => { server = new HealthProbeServer({ port: 4042, }); + server._cbOnListening = done; server.start(); - done(); } beforeAll(done => { @@ -140,8 +140,8 @@ describe('network.probe.HealthProbeServer', () => { server = new HealthProbeServer({ port: 4042, }); + server._cbOnListening = done; server.start(); - done(); } beforeAll(done => { diff --git a/yarn.lock b/yarn.lock index 2153c1bee..d20c5d6b8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2835,12 +2835,12 @@ resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.1.tgz#eaee5900122c110a3dbcb728c0597014a2621774" integrity sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg== -"@scality/eslint-config-scality@scality/Guidelines#8.3.2": - version "8.3.2" - resolved "https://codeload.github.com/scality/Guidelines/tar.gz/8ae4ae7142af24c28e2d92f463f2a375d5130e0f" +"@scality/eslint-config-scality@scality/Guidelines#8.3.3": + version "8.3.3" + resolved "https://codeload.github.com/scality/Guidelines/tar.gz/4f3f39e7def07df51264bb013e0978f02d110414" dependencies: - commander "11.1.0" - markdownlint "0.31.1" + globals "^14.0.0" + markdownlint "0.38.0" "@scality/hdclient@^1.3.2": version "1.3.2" @@ -3524,6 +3524,13 @@ dependencies: "@types/node" "*" +"@types/debug@^4.0.0": + version "4.1.13" + resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.13.tgz#22d1cc9d542d3593caea764f974306ab36286ee7" + integrity sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw== + dependencies: + "@types/ms" "*" + "@types/estree@^1.0.6": version "1.0.8" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e" @@ -3575,6 +3582,16 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== +"@types/katex@^0.16.0": + version "0.16.8" + resolved "https://registry.yarnpkg.com/@types/katex/-/katex-0.16.8.tgz#80bf3e0814d09a846412a0b0f140946b79c36c3e" + integrity sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg== + +"@types/ms@*": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@types/ms/-/ms-2.1.0.tgz#052aa67a48eccc4309d7f0191b7e41434b90bb78" + integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA== + "@types/node@*", "@types/node@>=10.0.0": version "24.7.0" resolved "https://registry.yarnpkg.com/@types/node/-/node-24.7.0.tgz#a34c9f0d3401db396782e440317dd5d8373c286f" @@ -3601,6 +3618,11 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== +"@types/unist@^2.0.0": + version "2.0.11" + resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.11.tgz#11af57b127e32487774841f7a4e54eab166d03c4" + integrity sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA== + "@types/utf8@^3.0.3": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/utf8/-/utf8-3.0.3.tgz#e153ada15157477a9e0d2bcc638c34284a1d82ea" @@ -4399,6 +4421,21 @@ char-regex@^1.0.2: resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== +character-entities-legacy@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz#76bc83a90738901d7bc223a9e93759fdd560125b" + integrity sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ== + +character-entities@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-2.0.2.tgz#2d09c2e72cd9523076ccb21157dff66ad43fcc22" + integrity sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ== + +character-reference-invalid@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz#85c66b041e43b47210faf401278abf808ac45cb9" + integrity sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw== + chokidar@^3.5.2: version "3.6.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" @@ -4484,10 +4521,10 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -commander@11.1.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-11.1.0.tgz#62fdce76006a68e5c1ab3314dc92e800eb83d906" - integrity sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ== +commander@^8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" + integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== commondir@^1.0.1: version "1.0.1" @@ -4588,7 +4625,7 @@ data-view-byte-offset@^1.0.1: es-errors "^1.3.0" is-data-view "^1.0.1" -debug@4, debug@^4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.5, debug@^4.4.1, debug@^4.4.3: +debug@4, debug@^4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.5, debug@^4.4.1, debug@^4.4.3: version "4.4.3" resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== @@ -4607,6 +4644,13 @@ decamelize@^1.2.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== +decode-named-character-reference@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz#3e40603760874c2e5867691b599d73a7da25b53f" + integrity sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q== + dependencies: + character-entities "^2.0.0" + dedent@^1.0.0: version "1.7.0" resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.7.0.tgz#c1f9445335f0175a96587be245a282ff451446ca" @@ -4685,11 +4729,23 @@ denque@^2.1.0: resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1" integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw== +dequal@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" + integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== + detect-newline@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== +devlop@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/devlop/-/devlop-1.1.0.tgz#4db7c2ca4dc6e0e834c30be70c94bbc976dc7018" + integrity sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA== + dependencies: + dequal "^2.0.0" + diff-sequences@^29.6.3: version "29.6.3" resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" @@ -4801,11 +4857,6 @@ engine.io@~6.6.0: engine.io-parser "~5.2.1" ws "~8.17.1" -entities@~3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/entities/-/entities-3.0.1.tgz#2b887ca62585e96db3903482d336c1006c3001d4" - integrity sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q== - env-paths@^2.2.0: version "2.2.1" resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" @@ -5213,9 +5264,9 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" -"fcntl@github:scality/node-fcntl#0.3.0": - version "0.3.0" - resolved "https://codeload.github.com/scality/node-fcntl/tar.gz/397840a8842e6d8067b6be8c22f1a2b81486f123" +"fcntl@github:scality/node-fcntl#0.3.1": + version "0.3.1" + resolved "https://codeload.github.com/scality/node-fcntl/tar.gz/bbeb7109bcdaac277d742d19d5ddbcafa633ffdb" dependencies: bindings "^1.1.1" node-addon-api "^8.3.0" @@ -5570,12 +5621,19 @@ http-proxy-agent@^7.0.0: agent-base "^7.1.0" debug "^4.3.4" -"httpagent@github:scality/httpagent#1.1.0", httpagent@scality/httpagent#1.1.0: +"httpagent@github:scality/httpagent#1.1.0": version "1.1.0" resolved "https://codeload.github.com/scality/httpagent/tar.gz/8f9958eb9cde086db7819a86582fba640a5f8876" dependencies: agentkeepalive "^4.5.0" +"httpagent@github:scality/httpagent#1.1.1", httpagent@scality/httpagent#1.1.1: + version "1.1.1" + uid "7c6ef8be1cd89b56651f4a922ea5c92d7673ee2e" + resolved "https://codeload.github.com/scality/httpagent/tar.gz/7c6ef8be1cd89b56651f4a922ea5c92d7673ee2e" + dependencies: + agentkeepalive "^4.6.0" + https-proxy-agent@^7.0.0, https-proxy-agent@^7.0.1, https-proxy-agent@^7.0.6: version "7.0.6" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz#da8dfeac7da130b05c2ba4b59c9b6cd66611a6b9" @@ -5724,6 +5782,19 @@ ipaddr.js@^2.2.0: resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.2.0.tgz#d33fa7bac284f4de7af949638c9d68157c6b92e8" integrity sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA== +is-alphabetical@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-2.0.1.tgz#01072053ea7c1036df3c7d19a6daaec7f19e789b" + integrity sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ== + +is-alphanumerical@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz#7c03fbe96e3e931113e57f964b0a368cc2dfd875" + integrity sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw== + dependencies: + is-alphabetical "^2.0.0" + is-decimal "^2.0.0" + is-array-buffer@^3.0.4, is-array-buffer@^3.0.5: version "3.0.5" resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.5.tgz#65742e1e687bd2cc666253068fd8707fe4d44280" @@ -5800,6 +5871,11 @@ is-date-object@^1.0.5, is-date-object@^1.1.0: call-bound "^1.0.2" has-tostringtag "^1.0.2" +is-decimal@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-2.0.1.tgz#9469d2dc190d0214fd87d78b78caecc0cc14eef7" + integrity sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A== + is-docker@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-3.0.0.tgz#90093aa3106277d8a77a5910dbae71747e15a200" @@ -5845,6 +5921,11 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" +is-hexadecimal@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz#86b5bf668fca307498d319dfc03289d781a90027" + integrity sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg== + is-inside-container@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-inside-container/-/is-inside-container-1.0.0.tgz#e81fba699662eb31dbdaf26766a61d4814717ea4" @@ -6545,6 +6626,13 @@ jws@^3.2.2: jwa "^1.4.1" safe-buffer "^5.0.1" +katex@^0.16.0: + version "0.16.47" + resolved "https://registry.yarnpkg.com/katex/-/katex-0.16.47.tgz#0a13a42c2deb4f74e61f162d440b9165a548030f" + integrity sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg== + dependencies: + commander "^8.3.0" + keyv@^4.5.4: version "4.5.4" resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" @@ -6693,13 +6781,6 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== -linkify-it@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-4.0.1.tgz#01f1d5e508190d06669982ba31a7d9f56a5751ec" - integrity sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw== - dependencies: - uc.micro "^1.0.1" - locate-path@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" @@ -6891,40 +6972,25 @@ makeerror@1.0.12: dependencies: tmpl "1.0.5" -markdown-it@13.0.1: - version "13.0.1" - resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-13.0.1.tgz#c6ecc431cacf1a5da531423fc6a42807814af430" - integrity sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q== +markdownlint@0.38.0: + version "0.38.0" + resolved "https://registry.yarnpkg.com/markdownlint/-/markdownlint-0.38.0.tgz#862ca9d08f3a28f4149bd388ac369bb95865534e" + integrity sha512-xaSxkaU7wY/0852zGApM8LdlIfGCW8ETZ0Rr62IQtAnUMlMuifsg09vWJcNYeL4f0anvr8Vo4ZQar8jGpV0btQ== dependencies: - argparse "^2.0.1" - entities "~3.0.1" - linkify-it "^4.0.1" - mdurl "^1.0.1" - uc.micro "^1.0.5" - -markdownlint-micromark@0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/markdownlint-micromark/-/markdownlint-micromark-0.1.7.tgz#c465091b30d61a56027ccbfb981c80c96448c165" - integrity sha512-BbRPTC72fl5vlSKv37v/xIENSRDYL/7X/XoFzZ740FGEbs9vZerLrIkFRY0rv7slQKxDczToYuMmqQFN61fi4Q== - -markdownlint@0.31.1: - version "0.31.1" - resolved "https://registry.yarnpkg.com/markdownlint/-/markdownlint-0.31.1.tgz#f014ed2d3614c5dbc351b7f65641ccc0a5facdb7" - integrity sha512-CKMR2hgcIBrYlIUccDCOvi966PZ0kJExDrUi1R+oF9PvqQmCrTqjOsgIvf2403OmJ+CWomuzDoylr6KbuMyvHA== - dependencies: - markdown-it "13.0.1" - markdownlint-micromark "0.1.7" + micromark "4.0.2" + micromark-core-commonmark "2.0.3" + micromark-extension-directive "4.0.0" + micromark-extension-gfm-autolink-literal "2.1.0" + micromark-extension-gfm-footnote "2.1.0" + micromark-extension-gfm-table "2.1.1" + micromark-extension-math "3.1.0" + micromark-util-types "2.0.2" math-intrinsics@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== -mdurl@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" - integrity sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g== - memory-pager@^1.0.2: version "1.5.0" resolved "https://registry.yarnpkg.com/memory-pager/-/memory-pager-1.5.0.tgz#d8751655d22d384682741c972f2c3d6dfa3e66b5" @@ -6940,6 +7006,251 @@ merge2@^1.3.0: resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== +micromark-core-commonmark@2.0.3, micromark-core-commonmark@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz#c691630e485021a68cf28dbc2b2ca27ebf678cd4" + integrity sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg== + dependencies: + decode-named-character-reference "^1.0.0" + devlop "^1.0.0" + micromark-factory-destination "^2.0.0" + micromark-factory-label "^2.0.0" + micromark-factory-space "^2.0.0" + micromark-factory-title "^2.0.0" + micromark-factory-whitespace "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-classify-character "^2.0.0" + micromark-util-html-tag-name "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-resolve-all "^2.0.0" + micromark-util-subtokenize "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-directive@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-directive/-/micromark-extension-directive-4.0.0.tgz#af389e33fe0654c15f8466b73a0f5af598d00368" + integrity sha512-/C2nqVmXXmiseSSuCdItCMho7ybwwop6RrrRPk0KbOHW21JKoCldC+8rFOaundDoRBUWBnJJcxeA/Kvi34WQXg== + dependencies: + devlop "^1.0.0" + micromark-factory-space "^2.0.0" + micromark-factory-whitespace "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + parse-entities "^4.0.0" + +micromark-extension-gfm-autolink-literal@2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz#6286aee9686c4462c1e3552a9d505feddceeb935" + integrity sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-sanitize-uri "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-gfm-footnote@2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz#4dab56d4e398b9853f6fe4efac4fc9361f3e0750" + integrity sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw== + dependencies: + devlop "^1.0.0" + micromark-core-commonmark "^2.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-sanitize-uri "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-gfm-table@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz#fac70bcbf51fe65f5f44033118d39be8a9b5940b" + integrity sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg== + dependencies: + devlop "^1.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-math@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz#c42ee3b1dd5a9a03584e83dd8f08e3de510212c1" + integrity sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg== + dependencies: + "@types/katex" "^0.16.0" + devlop "^1.0.0" + katex "^0.16.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-destination@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz#8fef8e0f7081f0474fbdd92deb50c990a0264639" + integrity sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-label@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz#5267efa97f1e5254efc7f20b459a38cb21058ba1" + integrity sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg== + dependencies: + devlop "^1.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-space@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz#36d0212e962b2b3121f8525fc7a3c7c029f334fc" + integrity sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-title@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz#237e4aa5d58a95863f01032d9ee9b090f1de6e94" + integrity sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw== + dependencies: + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-whitespace@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz#06b26b2983c4d27bfcc657b33e25134d4868b0b1" + integrity sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ== + dependencies: + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-character@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz#2f987831a40d4c510ac261e89852c4e9703ccda6" + integrity sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q== + dependencies: + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-chunked@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz#47fbcd93471a3fccab86cff03847fc3552db1051" + integrity sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA== + dependencies: + micromark-util-symbol "^2.0.0" + +micromark-util-classify-character@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz#d399faf9c45ca14c8b4be98b1ea481bced87b629" + integrity sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-combine-extensions@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz#2a0f490ab08bff5cc2fd5eec6dd0ca04f89b30a9" + integrity sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg== + dependencies: + micromark-util-chunked "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-decode-numeric-character-reference@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz#fcf15b660979388e6f118cdb6bf7d79d73d26fe5" + integrity sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw== + dependencies: + micromark-util-symbol "^2.0.0" + +micromark-util-encode@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz#0d51d1c095551cfaac368326963cf55f15f540b8" + integrity sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw== + +micromark-util-html-tag-name@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz#e40403096481986b41c106627f98f72d4d10b825" + integrity sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA== + +micromark-util-normalize-identifier@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz#c30d77b2e832acf6526f8bf1aa47bc9c9438c16d" + integrity sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q== + dependencies: + micromark-util-symbol "^2.0.0" + +micromark-util-resolve-all@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz#e1a2d62cdd237230a2ae11839027b19381e31e8b" + integrity sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg== + dependencies: + micromark-util-types "^2.0.0" + +micromark-util-sanitize-uri@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz#ab89789b818a58752b73d6b55238621b7faa8fd7" + integrity sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-encode "^2.0.0" + micromark-util-symbol "^2.0.0" + +micromark-util-subtokenize@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz#d8ade5ba0f3197a1cf6a2999fbbfe6357a1a19ee" + integrity sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA== + dependencies: + devlop "^1.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-symbol@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz#e5da494e8eb2b071a0d08fb34f6cefec6c0a19b8" + integrity sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q== + +micromark-util-types@2.0.2, micromark-util-types@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/micromark-util-types/-/micromark-util-types-2.0.2.tgz#f00225f5f5a0ebc3254f96c36b6605c4b393908e" + integrity sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA== + +micromark@4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/micromark/-/micromark-4.0.2.tgz#91395a3e1884a198e62116e33c9c568e39936fdb" + integrity sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA== + dependencies: + "@types/debug" "^4.0.0" + debug "^4.0.0" + decode-named-character-reference "^1.0.0" + devlop "^1.0.0" + micromark-core-commonmark "^2.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-combine-extensions "^2.0.0" + micromark-util-decode-numeric-character-reference "^2.0.0" + micromark-util-encode "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-resolve-all "^2.0.0" + micromark-util-sanitize-uri "^2.0.0" + micromark-util-subtokenize "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + micromatch@^4.0.4, micromatch@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" @@ -7429,6 +7740,19 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" +parse-entities@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-4.0.2.tgz#61d46f5ed28e4ee62e9ddc43d6b010188443f159" + integrity sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw== + dependencies: + "@types/unist" "^2.0.0" + character-entities-legacy "^3.0.0" + character-reference-invalid "^2.0.0" + decode-named-character-reference "^1.0.0" + is-alphanumerical "^2.0.0" + is-decimal "^2.0.0" + is-hexadecimal "^2.0.0" + parse-json@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" @@ -8172,13 +8496,13 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== -"sproxydclient@github:scality/sproxydclient#8.2.2": - version "8.2.2" - resolved "https://codeload.github.com/scality/sproxydclient/tar.gz/62d3aac1c844beda2286f33a1a7c7f2a31937579" +"sproxydclient@github:scality/sproxydclient#8.2.3": + version "8.2.3" + resolved "https://codeload.github.com/scality/sproxydclient/tar.gz/31cdab50c1c17104d8f009be87a2863df6a697a6" dependencies: async "^3.2.6" - httpagent "github:scality/httpagent#1.1.0" - werelogs scality/werelogs#8.2.0 + httpagent "github:scality/httpagent#1.1.1" + werelogs scality/werelogs#8.2.5 ssri@^12.0.0: version "12.0.0" @@ -8653,11 +8977,6 @@ typewiselite@~1.0.0: resolved "https://registry.yarnpkg.com/typewiselite/-/typewiselite-1.0.0.tgz#c8882fa1bb1092c06005a97f34ef5c8508e3664e" integrity sha512-J9alhjVHupW3Wfz6qFRGgQw0N3gr8hOkw6zm7FZ6UR1Cse/oD9/JVok7DNE9TT9IbciDHX2Ex9+ksE6cRmtymw== -uc.micro@^1.0.1, uc.micro@^1.0.5: - version "1.0.6" - resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" - integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== - uglify-js@^3.1.4: version "3.19.3" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.19.3.tgz#82315e9bbc6f2b25888858acd1fff8441035b77f" @@ -8796,9 +9115,10 @@ webidl-conversions@^7.0.0: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== -"werelogs@github:scality/werelogs#8.2.2", werelogs@scality/werelogs#8.2.0, werelogs@scality/werelogs#8.2.4: - version "8.2.4" - resolved "https://codeload.github.com/scality/werelogs/tar.gz/a7bbb5917a08b035d3763b24b070d517483d6982" +"werelogs@github:scality/werelogs#8.2.2", werelogs@scality/werelogs#8.2.5: + version "8.2.5" + uid "41188826d661227f98ebaaeda7df5e5862809bc7" + resolved "https://codeload.github.com/scality/werelogs/tar.gz/41188826d661227f98ebaaeda7df5e5862809bc7" dependencies: fast-safe-stringify "^2.1.1" safe-json-stringify "^1.2.0" From aec7c39b0243a0911259d13036e1bec2c53db64a Mon Sep 17 00:00:00 2001 From: Francois Ferrand Date: Sun, 20 Sep 2026 21:06:25 +0200 Subject: [PATCH 2/4] Preserve request targets without url.parse Replace deprecated URL parsing while preserving raw S3 path semantics for routing, metadata, and v2 signature canonicalization. Export the shared parser for consumers that handle client-supplied request targets. Issue: ARSN-642 --- index.ts | 1 + lib/auth/v2/getCanonicalizedResource.ts | 4 +- lib/network/rest/RESTServer.ts | 4 +- lib/network/rest/utils.ts | 5 +- lib/s3routes/routesUtils.ts | 9 +- lib/storage/data/LocationConstraintParser.js | 30 ++++- lib/storage/metadata/proxy/utils.js | 4 +- lib/utils/requestUrl.ts | 110 +++++++++++++++++++ tests/unit/auth/v2/canonicalization.spec.js | 34 ++++++ tests/unit/utils/requestUrl.spec.js | 92 ++++++++++++++++ 10 files changed, 278 insertions(+), 15 deletions(-) create mode 100644 lib/utils/requestUrl.ts create mode 100644 tests/unit/utils/requestUrl.spec.js diff --git a/index.ts b/index.ts index 6ae4db40f..0c1632399 100644 --- a/index.ts +++ b/index.ts @@ -40,6 +40,7 @@ export * as s3routes from './lib/s3routes'; export * as versioning from './lib/versioning'; export * as stream from './lib/stream'; export * as jsutil from './lib/jsutil'; +export * as requestUrl from './lib/utils/requestUrl'; export { default as stringHash } from './lib/stringHash'; export * as db from './lib/db'; export * as errorUtils from './lib/errorUtils'; diff --git a/lib/auth/v2/getCanonicalizedResource.ts b/lib/auth/v2/getCanonicalizedResource.ts index 0d951df9b..cbde01a4d 100644 --- a/lib/auth/v2/getCanonicalizedResource.ts +++ b/lib/auth/v2/getCanonicalizedResource.ts @@ -1,4 +1,4 @@ -import * as url from 'url'; +import { parseRequestTarget } from '../../utils/requestUrl'; import { type ArsenalRequest } from '../../types/ArsenalRequest'; const gcpSubresources = new Set([ @@ -50,7 +50,7 @@ export default function getCanonicalizedResource(request: ArsenalRequest, client // If bucket specified in hostname, add to resourceString let resourceString = request.gotBucketNameFromHost ? `/${request.bucketName}` : ''; // Add the path to the resourceString - resourceString += url.parse(request.url || '').pathname || ''; + resourceString += parseRequestTarget(request.url || '').pathname; /* If request includes a specified subresource, diff --git a/lib/network/rest/RESTServer.ts b/lib/network/rest/RESTServer.ts index 15f442050..6572c107b 100644 --- a/lib/network/rest/RESTServer.ts +++ b/lib/network/rest/RESTServer.ts @@ -1,10 +1,10 @@ import assert from 'assert'; -import * as url from 'url'; import * as werelogs from 'werelogs'; import * as http from 'http'; import httpServer from '../http/server'; import * as constants from '../../constants'; import { parseURL } from './utils'; +import { parseRequestTarget } from '../../utils/requestUrl'; import * as httpUtils from '../http/utils'; import errors, { ArsenalError, errorInstances } from '../../errors'; @@ -191,7 +191,7 @@ export default class RESTServer extends httpServer { // Get request on the toplevel endpoint with ?action if (req.url?.startsWith(`${constants.dataFileURL}?`)) { - const queryParam = url.parse(req.url).query; + const queryParam = parseRequestTarget(req.url).query; if (queryParam === 'diskUsage') { return this.dataStore.getDiskUsage((err, result) => { if (err) { diff --git a/lib/network/rest/utils.ts b/lib/network/rest/utils.ts index 616ffc1a4..1ec17d8cc 100644 --- a/lib/network/rest/utils.ts +++ b/lib/network/rest/utils.ts @@ -1,6 +1,6 @@ import { errorInstances } from '../../errors'; import * as constants from '../../constants'; -import * as url from 'url'; +import { parseRequestTarget } from '../../utils/requestUrl'; const passthroughPrefixLength = constants.passthroughFileURL.length; export function explodePath(path: string) { @@ -35,8 +35,7 @@ export function explodePath(path: string) { * - pathInfo.key {String} - The requested key */ export function parseURL(urlStr: string, expectKey: boolean) { - const urlObj = url.parse(urlStr); - const pathInfo = explodePath(decodeURI(urlObj.path!)); + const pathInfo = explodePath(decodeURI(parseRequestTarget(urlStr).path)); if ((pathInfo.service !== constants.dataFileURL) && (pathInfo.service !== constants.passthroughFileURL)) { throw errorInstances.InvalidAction.customizeDescription( diff --git a/lib/s3routes/routesUtils.ts b/lib/s3routes/routesUtils.ts index 60da18f57..eeb992f84 100644 --- a/lib/s3routes/routesUtils.ts +++ b/lib/s3routes/routesUtils.ts @@ -1,4 +1,5 @@ -import * as url from 'url'; +import * as querystring from 'querystring'; +import { parseRequestTarget } from '../utils/requestUrl'; import * as http from 'http'; import { eachSeries } from 'async'; @@ -1119,12 +1120,12 @@ export function getBucketNameFromHost(request: http.IncomingMessage, validHosts: * @return request object with additional attributes */ export function normalizeRequest(request: ArsenalRequest, validHosts: string[]) { - const parsedUrl = url.parse(request.url!, true); - request.query = parsedUrl.query as Record; + const parsedUrl = parseRequestTarget(request.url!); + request.query = querystring.parse(parsedUrl.query ?? '') as Record; // TODO: make the namespace come from a config variable. request.namespace = 'default'; // Parse bucket and/or object names from request - const resources = getResourceNames(request, parsedUrl.pathname!, validHosts); + const resources = getResourceNames(request, parsedUrl.pathname, validHosts); request.gotBucketNameFromHost = resources.gotBucketNameFromHost ?? false; request.bucketName = resources.bucket; request.objectKey = resources.object; diff --git a/lib/storage/data/LocationConstraintParser.js b/lib/storage/data/LocationConstraintParser.js index 6a15eef6b..2748c0fdc 100644 --- a/lib/storage/data/LocationConstraintParser.js +++ b/lib/storage/data/LocationConstraintParser.js @@ -1,5 +1,4 @@ const { http, https } = require('httpagent'); -const url = require('url'); const { NodeHttpHandler } = require('@smithy/node-http-handler'); const { fromIni } = require('@aws-sdk/credential-providers'); const Sproxy = require('sproxydclient'); @@ -15,6 +14,33 @@ const GcpClient = require('./external/GcpClient'); const PfsClient = require('./external/PfsClient'); const backendUtils = require('./external/utils'); +/** + * Describe a proxy URL as a mutable plain object, so the caller can merge cert + * options into it before handing it to the agent. + * @param {string} proxyUrl - absolute URL of the outbound proxy + * @return {object} agent options describing the proxy + */ +function proxyAgentOptions(proxyUrl) { + const parsed = new URL(proxyUrl); + const auth = parsed.username + ? `${parsed.username}${parsed.password ? `:${parsed.password}` : ''}` + : null; + return { + protocol: parsed.protocol, + slashes: true, + auth, + host: parsed.host, + port: parsed.port || null, + hostname: parsed.hostname, + hash: parsed.hash || null, + search: parsed.search || null, + query: parsed.search ? parsed.search.slice(1) : null, + pathname: parsed.pathname, + path: `${parsed.pathname}${parsed.search}`, + href: parsed.href, + }; +} + function parseLC(config, vault) { const clients = {}; @@ -66,7 +92,7 @@ function parseLC(config, vault) { } const proxyMatch = backendUtils.proxyCompareUrl(endpoint); if (config.outboundProxy.url && !proxyMatch) { - const options = url.parse(config.outboundProxy.url); + const options = proxyAgentOptions(config.outboundProxy.url); if (config.outboundProxy.certs) { Object.assign(options, config.outboundProxy.certs); options.secureProxy = true; diff --git a/lib/storage/metadata/proxy/utils.js b/lib/storage/metadata/proxy/utils.js index 19e13e3b5..a11b699a3 100644 --- a/lib/storage/metadata/proxy/utils.js +++ b/lib/storage/metadata/proxy/utils.js @@ -1,4 +1,4 @@ -const url = require('url'); +const { parseRequestTarget } = require('../../../utils/requestUrl'); const querystring = require('querystring'); const errors = require('../../../errors').default; const errorInstances = require('../../../errors').errorInstances; @@ -22,7 +22,7 @@ function getURIComponents(uri, logger) { return {}; } - const { pathname, query } = url.parse(uri); + const { pathname, query } = parseRequestTarget(uri); const options = query ? querystring.parse(query) : {}; const typeIndex = pathname.indexOf('/', 1); const bucketIndex = pathname.indexOf('/', typeIndex + 1); diff --git a/lib/utils/requestUrl.ts b/lib/utils/requestUrl.ts new file mode 100644 index 000000000..af65dcdb4 --- /dev/null +++ b/lib/utils/requestUrl.ts @@ -0,0 +1,110 @@ +/** + * An S3 object key is an opaque byte string, and almost every character is + * valid in one. Request targets are therefore split without being normalized: + * the key a request addresses has to come out exactly as the client wrote it. + * + * This is why `new URL()` is not used. WHATWG parsing collapses `.` and `..` + * segments, reads a leading `//` as an authority and percent-encodes + * non-ASCII, each of which names a different object than the request did: + * `//bucket/key` loses its bucket, `/bucket/a/../b` becomes `/bucket/b`. On + * the v2 auth path it also changes the string being signed, so a request + * would be verified against a resource it never addressed. + * + * The input is always read as a path, never as an absolute URL: no scheme or + * host is resolved. Callers handling client-supplied references such as + * `x-amz-copy-source` therefore see `http://host/x` as a whole key, and can + * reject it rather than silently reading it as `/x`. + */ + +/** Whitespace and zero-width characters stripped from either end of a target. */ +function isTrimmable(code: number): boolean { + return code < 33 || code === 0xa0 || code === 0xfeff; +} + +/** + * Targets matching this are taken verbatim; anything else is percent-escaped. + * A leading `///`, a `#`, or a `@` ahead of the query also force escaping. + */ +const VERBATIM = /^(\/\/?(?!\/)[^?\s]*)(\?[^\s]*)?$/; + +const ESCAPES: Record = { + '\t': '%09', + '\n': '%0A', + '\r': '%0D', + ' ': '%20', + '"': '%22', + "'": '%27', + '<': '%3C', + '>': '%3E', + '\\': '%5C', + '^': '%5E', + '`': '%60', + '{': '%7B', + '|': '%7C', + '}': '%7D', +}; + +function escapeDelimiters(part: string): string { + return part.replace(/[\t\n\r "'<>\\^`{|}]/g, ch => ESCAPES[ch]); +} + +export interface ParsedRequestTarget { + /** Query and fragment removed, and `\` folded to `/`. */ + pathname: string; + /** Query without its leading `?`, or null when the target carries none. */ + query: string | null; + /** `pathname` followed by the query, ready to route on. */ + path: string; +} + +/** + * Splits a request target into its path and query, without normalizing either. + * + * @param requestUrl - origin-form request target, e.g. `/bucket/key?acl` + * @returns the path and query, with delimiters escaped where the target is not + * already a plain path + * + * @example + * parseRequestTarget('/bucket/key?acl') + * // => { pathname: '/bucket/key', query: 'acl', path: '/bucket/key?acl' } + * @example + * parseRequestTarget('/bucket/my key') + * // => { pathname: '/bucket/my%20key', query: null, path: '/bucket/my%20key' } + * @example + * parseRequestTarget('/bucket/a/../b') + * // dot segments are kept as-is, unlike `new URL()`: + * // => { pathname: '/bucket/a/../b', query: null, path: '/bucket/a/../b' } + */ +export function parseRequestTarget(requestUrl: string): ParsedRequestTarget { + let start = 0; + let end = requestUrl.length; + while (start < end && isTrimmable(requestUrl.charCodeAt(start))) { + start += 1; + } + while (end > start && isTrimmable(requestUrl.charCodeAt(end - 1))) { + end -= 1; + } + const target = requestUrl.slice(start, end); + + // A '?' sitting after a '#' belongs to the fragment, so cut the fragment first. + const hashIndex = target.indexOf('#'); + const beforeHash = hashIndex === -1 ? target : target.slice(0, hashIndex); + const queryIndex = beforeHash.indexOf('?'); + + // Backslashes count as separators in the path, but stay literal in the query. + const rawPath = (queryIndex === -1 ? beforeHash : beforeHash.slice(0, queryIndex)).replace(/\\/g, '/'); + const rawQuery = queryIndex === -1 ? null : beforeHash.slice(queryIndex + 1); + + const candidate = rawQuery === null ? rawPath : `${rawPath}?${rawQuery}`; + const verbatim = hashIndex === -1 && !rawPath.includes('@') && VERBATIM.test(candidate); + const encode = verbatim ? (part: string) => part : escapeDelimiters; + + const pathname = encode(rawPath); + const query = rawQuery === null ? null : encode(rawQuery); + + return { + pathname, + query, + path: query === null ? pathname : `${pathname}?${query}`, + }; +} diff --git a/tests/unit/auth/v2/canonicalization.spec.js b/tests/unit/auth/v2/canonicalization.spec.js index c25fe2879..87f51bb38 100644 --- a/tests/unit/auth/v2/canonicalization.spec.js +++ b/tests/unit/auth/v2/canonicalization.spec.js @@ -224,4 +224,38 @@ describe('canonicalization', () => { 'x-amz-meta-meta:something very meta\n' + 'x-amz-spaced-header:value1 , value2\n', ); }); + + /* + * The signed resource has to be the path exactly as the client sent it: + * rewriting it here would check the signature against a different object + * than the one the request addresses. + */ + describe('canonicalized resource path', () => { + const resourceFor = url => getCanonicalizedResource({ headers: {}, url, query: {} }); + + it('should keep dot segments in the key', () => { + assert.strictEqual(resourceFor('/bucket/a/../b'), '/bucket/a/../b'); + assert.strictEqual(resourceFor('/bucket/a/./b'), '/bucket/a/./b'); + }); + + it('should keep a key that itself starts with a slash', () => { + assert.strictEqual(resourceFor('//bucket/key'), '//bucket/key'); + }); + + it('should leave a non-ASCII key unencoded', () => { + assert.strictEqual(resourceFor('/bucket/ключ'), '/bucket/ключ'); + }); + + it('should escape a space, matching what the signing client sends', () => { + assert.strictEqual(resourceFor('/bucket/my file.txt'), '/bucket/my%20file.txt'); + }); + + it('should keep percent-encoding untouched', () => { + assert.strictEqual(resourceFor('/bucket/key%2Fnested'), '/bucket/key%2Fnested'); + }); + + it('should drop the query string', () => { + assert.strictEqual(resourceFor('/bucket/key?uploads=1'), '/bucket/key'); + }); + }); }); diff --git a/tests/unit/utils/requestUrl.spec.js b/tests/unit/utils/requestUrl.spec.js new file mode 100644 index 000000000..2d066edfe --- /dev/null +++ b/tests/unit/utils/requestUrl.spec.js @@ -0,0 +1,92 @@ +'use strict'; + +const assert = require('assert'); + +const { parseRequestTarget } = require('../../../lib/utils/requestUrl'); + +/* + * These values pin the behaviour the v2 signature path and the key + * derivation in routesUtils rely on: any change here would change what + * gets parsed as the bucket/key or what gets signed. + */ +const cases = [ + // plain targets + ['/bucket/key', '/bucket/key', null], + ['/bucket/key?acl', '/bucket/key', 'acl'], + ['/bucket/key?versionId=abc&acl', '/bucket/key', 'versionId=abc&acl'], + ['/', '/', null], + ['', '', null], + ['/bucket/', '/bucket/', null], + ['/bucket//double//slash', '/bucket//double//slash', null], + + // delimiters + ['/bucket/key?', '/bucket/key', ''], + ['/bucket/key?x=1#frag', '/bucket/key', 'x=1'], + ['/bucket/key#frag?x=1', '/bucket/key', null], + ['/bucket/key#hash', '/bucket/key', null], + ['?onlyquery', '', 'onlyquery'], + + // percent-encoding is left exactly as the client sent it + ['/bucket/my%20key', '/bucket/my%20key', null], + ['/bucket/key%2Fencoded', '/bucket/key%2Fencoded', null], + ['/bucket/key%2fencoded', '/bucket/key%2fencoded', null], + ['/bucket/key%23hash', '/bucket/key%23hash', null], + + // whitespace and backslashes + ['/bucket/my key', '/bucket/my%20key', null], + ['/bucket/tab\tkey', '/bucket/tab%09key', null], + ['/bucket/back\\slash', '/bucket/back/slash', null], + [' /bucket/key ', '/bucket/key', null], + + // a '@' in the key drops the parser out of its verbatim path + ['/bucket/key"quote', '/bucket/key"quote', null], + ['/bucket/user@host/key"quote', '/bucket/user@host/key%22quote', null], + ['/bucket/user@host/key{brace}', '/bucket/user@host/key%7Bbrace%7D', null], + + // backslashes separate in the path but stay literal in the query + ['/bucket/key?a\\b', '/bucket/key', 'a\\b'], +]; + +describe('parseRequestTarget', () => { + cases.forEach(([input, pathname, query]) => { + it(`should parse ${JSON.stringify(input)}`, () => { + const parsed = parseRequestTarget(input); + assert.strictEqual(parsed.pathname, pathname); + assert.strictEqual(parsed.query, query); + assert.strictEqual(parsed.path, query === null ? pathname : `${pathname}?${query}`); + }); + }); + + /* + * These are the cases where `new URL()` would rewrite the target. Each one + * would change the object a request resolves to, and with it the string the + * v2 signature is checked against. + */ + describe('keys that WHATWG parsing would rewrite', () => { + it('should not collapse dot segments', () => { + assert.strictEqual(parseRequestTarget('/bucket/a/../b').pathname, '/bucket/a/../b'); + assert.strictEqual(parseRequestTarget('/bucket/a/./b').pathname, '/bucket/a/./b'); + assert.strictEqual(parseRequestTarget('/bucket/..').pathname, '/bucket/..'); + }); + + it('should keep a leading // rather than read it as a host', () => { + assert.strictEqual(parseRequestTarget('//bucket/key').pathname, '//bucket/key'); + assert.strictEqual(parseRequestTarget('///bucket/key').pathname, '///bucket/key'); + }); + + it('should leave non-ASCII keys unencoded', () => { + assert.strictEqual(parseRequestTarget('/bucket/ключ').pathname, '/bucket/ключ'); + assert.strictEqual(parseRequestTarget('/bucket/ünïcode').pathname, '/bucket/ünïcode'); + }); + + it('should not drop control characters from a key', () => { + assert.strictEqual(parseRequestTarget('/bucket/a\tb').pathname, '/bucket/a%09b'); + }); + }); + + it('should never throw on input the legacy parser rejected', () => { + ['//', ':', '\u0000', 'http://x', '/b/k?a=%'].forEach(input => { + assert.doesNotThrow(() => parseRequestTarget(input)); + }); + }); +}); From ca5c4b7eda4d024f9794793b99b09508f4c40dee Mon Sep 17 00:00:00 2001 From: Francois Ferrand Date: Mon, 21 Sep 2026 22:11:57 +0200 Subject: [PATCH 3/4] Format files checked by prettier Issue: ARSN-642 --- lib/network/rest/RESTServer.ts | 56 +++++-------------- lib/network/rest/utils.ts | 15 ++--- lib/storage/data/LocationConstraintParser.js | 36 ++++-------- lib/storage/metadata/proxy/utils.js | 46 +++++++-------- tests/unit/auth/v2/canonicalization.spec.js | 56 +++++++++---------- .../network/probe/HealthProbeServer.spec.js | 40 ++++++------- 6 files changed, 99 insertions(+), 150 deletions(-) diff --git a/lib/network/rest/RESTServer.ts b/lib/network/rest/RESTServer.ts index 6572c107b..4efe3e1cb 100644 --- a/lib/network/rest/RESTServer.ts +++ b/lib/network/rest/RESTServer.ts @@ -19,23 +19,14 @@ function setContentRange( ) { const [start, end] = byteRange; assert(start !== undefined && end !== undefined); - response.setHeader('Content-Range', - `bytes ${start}-${end}/${objectSize}`); + response.setHeader('Content-Range', `bytes ${start}-${end}/${objectSize}`); } -function sendError( - res: http.ServerResponse, - log: werelogs.RequestLogger, - error: ArsenalError, - optMessage?: string, -) { +function sendError(res: http.ServerResponse, log: werelogs.RequestLogger, error: ArsenalError, optMessage?: string) { res.writeHead(error.code); const message = optMessage ?? error.description ?? ''; - log.debug('sending back error response', { httpCode: error.code, - errorType: error.message, - error: message }); - res.end(`${JSON.stringify({ errorType: error.message, - errorMessage: message })}\n`); + log.debug('sending back error response', { httpCode: error.code, errorType: error.message, error: message }); + res.end(`${JSON.stringify({ errorType: error.message, errorMessage: message })}\n`); } /** @@ -64,7 +55,7 @@ export default class RESTServer extends httpServer { port: number; dataStore: any; bindAddress?: string; - log: { logLevel: any; dumpLevel: any; }; + log: { logLevel: any; dumpLevel: any }; }) { assert(params.port); @@ -105,9 +96,7 @@ export default class RESTServer extends httpServer { * @return new request logger */ createLogger(reqUids?: string) { - return reqUids ? - this.logging.newRequestLoggerFromSerializedUids(reqUids) : - this.logging.newRequestLogger(); + return reqUids ? this.logging.newRequestLoggerFromSerializedUids(reqUids) : this.logging.newRequestLogger(); } /** @@ -120,8 +109,7 @@ export default class RESTServer extends httpServer { _onRequest(req: http.IncomingMessage, res: http.ServerResponse) { const reqUids = req.headers['x-scal-request-uids']; const log = this.createLogger(reqUids as string); - log.debug('request received', { method: req.method, - url: req.url }); + log.debug('request received', { method: req.method, url: req.url }); const method = req.method ?? ''; if (method in this.reqMethods) { this.reqMethods[method](req, res, log); @@ -138,11 +126,7 @@ export default class RESTServer extends httpServer { * @param res - HTTP response object * @param log - logger object */ - _onPut( - req: http.IncomingMessage, - res: http.ServerResponse, - log: werelogs.RequestLogger, - ) { + _onPut(req: http.IncomingMessage, res: http.ServerResponse, log: werelogs.RequestLogger) { let size: number; try { parseURL(req.url ?? '', false); @@ -152,8 +136,7 @@ export default class RESTServer extends httpServer { } size = Number.parseInt(contentLength, 10); if (Number.isNaN(size)) { - throw errorInstances.InvalidInput.customizeDescription( - 'bad Content-Length'); + throw errorInstances.InvalidInput.customizeDescription('bad Content-Length'); } } catch (err: any) { return sendError(res, log, err); @@ -180,14 +163,9 @@ export default class RESTServer extends httpServer { * @param res - HTTP response object * @param log - logger object */ - _onGet( - req: http.IncomingMessage, - res: http.ServerResponse, - log: werelogs.RequestLogger, - ) { + _onGet(req: http.IncomingMessage, res: http.ServerResponse, log: werelogs.RequestLogger) { let pathInfo: ReturnType; - let rangeSpec: ReturnType | undefined = - undefined; + let rangeSpec: ReturnType | undefined = undefined; // Get request on the toplevel endpoint with ?action if (req.url?.startsWith(`${constants.dataFileURL}?`)) { @@ -237,8 +215,7 @@ export default class RESTServer extends httpServer { if (err) { return sendError(res, log, err); } - log.debug('sending back 200/206 response with contents', - { key: pathInfo.key }); + log.debug('sending back 200/206 response with contents', { key: pathInfo.key }); setContentLength(res, contentLength); res.setHeader('Accept-Ranges', 'bytes'); if (byteRange) { @@ -263,11 +240,7 @@ export default class RESTServer extends httpServer { * @param res - HTTP response object * @param log - logger object */ - _onDelete( - req: http.IncomingMessage, - res: http.ServerResponse, - log: werelogs.RequestLogger, - ) { + _onDelete(req: http.IncomingMessage, res: http.ServerResponse, log: werelogs.RequestLogger) { let pathInfo: ReturnType; try { pathInfo = parseURL(req.url ?? '', true); @@ -278,8 +251,7 @@ export default class RESTServer extends httpServer { if (err) { return sendError(res, log, err); } - log.debug('sending back 204 response to DELETE', - { key: pathInfo.key }); + log.debug('sending back 204 response to DELETE', { key: pathInfo.key }); res.writeHead(204); return res.end(() => { log.debug('DELETE response sent', { key: pathInfo.key }); diff --git a/lib/network/rest/utils.ts b/lib/network/rest/utils.ts index 1ec17d8cc..94def686e 100644 --- a/lib/network/rest/utils.ts +++ b/lib/network/rest/utils.ts @@ -15,8 +15,7 @@ export function explodePath(path: string) { if (pathMatch) { return { service: pathMatch[1], - key: (pathMatch[3] !== undefined && pathMatch[3].length > 0 ? - pathMatch[3] : undefined), + key: pathMatch[3] !== undefined && pathMatch[3].length > 0 ? pathMatch[3] : undefined, }; } throw errorInstances.InvalidURI.customizeDescription('malformed URI'); @@ -36,14 +35,11 @@ export function explodePath(path: string) { */ export function parseURL(urlStr: string, expectKey: boolean) { const pathInfo = explodePath(decodeURI(parseRequestTarget(urlStr).path)); - if ((pathInfo.service !== constants.dataFileURL) - && (pathInfo.service !== constants.passthroughFileURL)) { - throw errorInstances.InvalidAction.customizeDescription( - `unsupported service '${pathInfo.service}'`); + if (pathInfo.service !== constants.dataFileURL && pathInfo.service !== constants.passthroughFileURL) { + throw errorInstances.InvalidAction.customizeDescription(`unsupported service '${pathInfo.service}'`); } if (expectKey && pathInfo.key === undefined) { - throw errorInstances.MissingParameter.customizeDescription( - 'URL is missing key'); + throw errorInstances.MissingParameter.customizeDescription('URL is missing key'); } if (!expectKey && pathInfo.key !== undefined) { // note: we may implement rewrite functionality by allowing a @@ -52,8 +48,7 @@ export function parseURL(urlStr: string, expectKey: boolean) { // atomicity of the update (we would just remove the old // object when the new one has been written entirely in this // case, saving a request over an equivalent PUT + DELETE). - throw errorInstances.InvalidURI.customizeDescription( - 'PUT url cannot contain a key'); + throw errorInstances.InvalidURI.customizeDescription('PUT url cannot contain a key'); } return pathInfo; } diff --git a/lib/storage/data/LocationConstraintParser.js b/lib/storage/data/LocationConstraintParser.js index 2748c0fdc..84066d9d1 100644 --- a/lib/storage/data/LocationConstraintParser.js +++ b/lib/storage/data/LocationConstraintParser.js @@ -22,9 +22,7 @@ const backendUtils = require('./external/utils'); */ function proxyAgentOptions(proxyUrl) { const parsed = new URL(proxyUrl); - const auth = parsed.username - ? `${parsed.username}${parsed.password ? `:${parsed.password}` : ''}` - : null; + const auth = parsed.username ? `${parsed.username}${parsed.password ? `:${parsed.password}` : ''}` : null; return { protocol: parsed.protocol, slashes: true, @@ -55,8 +53,7 @@ function parseLC(config, vault) { if (locationObj.type === 'scality') { if (locationObj.details.connector.sproxyd) { clients[location] = new Sproxy({ - bootstrap: locationObj.details.connector - .sproxyd.bootstrap, + bootstrap: locationObj.details.connector.sproxyd.bootstrap, // Might be undefined which is ok since there is a default // set in sproxydclient if chordCos is undefined chordCos: locationObj.details.connector.sproxyd.chordCos, @@ -68,24 +65,21 @@ function parseLC(config, vault) { }); clients[location].clientType = 'scality'; } else if (locationObj.details.connector.hdclient) { - clients[location] = new Hyperdrive.hdcontroller.HDProxydClient( - locationObj.details.connector.hdclient); + clients[location] = new Hyperdrive.hdcontroller.HDProxydClient(locationObj.details.connector.hdclient); clients[location].clientType = 'scality'; } } if (locationObj.type === 'aws_s3' || locationObj.type === 'gcp') { let connectionAgent; - const endpoint = locationObj.type === 'gcp' ? - locationObj.details.gcpEndpoint : - locationObj.details.awsEndpoint; + const endpoint = + locationObj.type === 'gcp' ? locationObj.details.gcpEndpoint : locationObj.details.awsEndpoint; const region = locationObj.details.region || 'us-east-1'; const protocol = locationObj.details.https ? 'https' : 'http'; const sslEnabled = locationObj.details.https === true; const signatureVersion = !sslEnabled ? 'v2' : 'v4'; const pathStyle = locationObj.details.pathStyle; // keepalive config - const httpAgentConfig = - config.externalBackends[locationObj.type].httpAgent; + const httpAgentConfig = config.externalBackends[locationObj.type].httpAgent; // max sockets is infinity by default and expressed as null if (httpAgentConfig.maxSockets === null) { httpAgentConfig.maxSockets = undefined; @@ -104,9 +98,7 @@ function parseLC(config, vault) { } else { const agentOptions = { ...httpAgentConfig, maxSockets: false }; - connectionAgent = sslEnabled - ? new https.Agent(agentOptions) - : new http.Agent(agentOptions); + connectionAgent = sslEnabled ? new https.Agent(agentOptions) : new http.Agent(agentOptions); } const httpOptions = { agent: connectionAgent, timeout: 0 }; const s3Params = { @@ -114,9 +106,7 @@ function parseLC(config, vault) { region, requestHandler: new NodeHttpHandler({ requestTimeout: 0, - ...(sslEnabled - ? { httpsAgent: connectionAgent } - : { httpAgent: connectionAgent }), + ...(sslEnabled ? { httpsAgent: connectionAgent } : { httpAgent: connectionAgent }), }), forcePathStyle: pathStyle, customUserAgent: constants.productName, @@ -144,19 +134,15 @@ function parseLC(config, vault) { if (locationObj.type === 'gcp') { clientConfig.mpuBucket = locationObj.details.mpuBucketName; } - clients[location] = locationObj.type === 'gcp' ? - new GcpClient(clientConfig) : new AwsClient(clientConfig); + clients[location] = locationObj.type === 'gcp' ? new GcpClient(clientConfig) : new AwsClient(clientConfig); if (locationObj.type === 'aws_s3') { clients[location].setup(() => {}); } } if (locationObj.type === 'azure') { const azureStorageEndpoint = config.getAzureEndpoint(location); - const proxyParams = - backendUtils.proxyCompareUrl(azureStorageEndpoint) ? - {} : config.outboundProxy; - const azureStorageCredentials = - config.getAzureStorageCredentials(location); + const proxyParams = backendUtils.proxyCompareUrl(azureStorageEndpoint) ? {} : config.outboundProxy; + const azureStorageCredentials = config.getAzureStorageCredentials(location); clients[location] = new AzureClient({ azureStorageEndpoint, azureStorageCredentials, diff --git a/lib/storage/metadata/proxy/utils.js b/lib/storage/metadata/proxy/utils.js index a11b699a3..9e52c237c 100644 --- a/lib/storage/metadata/proxy/utils.js +++ b/lib/storage/metadata/proxy/utils.js @@ -67,9 +67,7 @@ function getURIComponents(uri, logger) { options, }; } catch (ex) { - logger.error('Invalid URI: failed to parse', - { uri, error: ex, errorStack: ex.stack, - message: ex.message }); + logger.error('Invalid URI: failed to parse', { uri, error: ex, errorStack: ex.stack, message: ex.message }); return null; } } @@ -87,18 +85,22 @@ function getURIComponents(uri, logger) { function getRequestBody(logger, request, cb) { const body = []; let bodyLen = 0; - request.on('data', data => { - body.push(data); - bodyLen += data.length; - }).on('error', cb).on('end', () => { - cb(null, Buffer.concat(body, bodyLen).toString()); - }).on('close', () => { - logger.error('Connection closed by remote peer'); - /* Execution will eventually reach the sendResponse code which will - * trigger the proper cleanup as the remote peer already hung up and - * nobody is on the line to get the message */ - cb(errors.BadRequest); - }); + request + .on('data', data => { + body.push(data); + bodyLen += data.length; + }) + .on('error', cb) + .on('end', () => { + cb(null, Buffer.concat(body, bodyLen).toString()); + }) + .on('close', () => { + logger.error('Connection closed by remote peer'); + /* Execution will eventually reach the sendResponse code which will + * trigger the proper cleanup as the remote peer already hung up and + * nobody is on the line to get the message */ + cb(errors.BadRequest); + }); } /** @@ -156,15 +158,15 @@ function sendResponse(req, res, log, err, data) { resData = resData.toString(); } /* - * Encoding data to binary provides a hot path to write data - * directly to the socket, without node.js trying to encode the data - * over and over again. - */ + * Encoding data to binary provides a hot path to write data + * directly to the socket, without node.js trying to encode the data + * over and over again. + */ const rawData = Buffer.from(resData, 'utf8'); /* - * Using Buffer.bytelength is not required here because data is binary - * encoded, data.length would give us the exact byte length - */ + * Using Buffer.bytelength is not required here because data is binary + * encoded, data.length would give us the exact byte length + */ res.writeHead(statusCode, statusMessage, { 'content-length': rawData.length, }); diff --git a/tests/unit/auth/v2/canonicalization.spec.js b/tests/unit/auth/v2/canonicalization.spec.js index 87f51bb38..e033a6065 100644 --- a/tests/unit/auth/v2/canonicalization.spec.js +++ b/tests/unit/auth/v2/canonicalization.spec.js @@ -11,15 +11,15 @@ const gcpCanonicalizedResource = request => getCanonicalizedResource(request, 'G describe('canonicalization', () => { it('should construct a canonicalized header in the correct order for AWS', () => { const headers = { - date: 'Mon, 21 Sep 2015 22:29:27 GMT', + 'date': 'Mon, 21 Sep 2015 22:29:27 GMT', 'x-amz-request-payer': 'requester', 'x-amz-meta-meta': 'something very meta', 'x-amz-meta-bits': '0', 'x-amz-meta-blksize': '2097152', 'x-amz-meta-compress': '0', - authorization: 'AWS accessKey1:V8g5UJUFmMzruMqUHVT6ZwvUw+M=', - host: 's3.amazonaws.com:80', - connection: 'Keep-Alive', + 'authorization': 'AWS accessKey1:V8g5UJUFmMzruMqUHVT6ZwvUw+M=', + 'host': 's3.amazonaws.com:80', + 'connection': 'Keep-Alive', 'user-agent': 'Cyberduck/4.7.2.18004 (Mac OS X/10.10.5) (x86_64)', }; const canonicalizedHeader = getCanonicalizedAmzHeaders(headers); @@ -35,10 +35,10 @@ describe('canonicalization', () => { it('should return an empty string as the canonicalized ' + 'header if no amz headers', () => { const headers = { - date: 'Mon, 21 Sep 2015 22:29:27 GMT', - authorization: 'AWS accessKey1:V8g5UJUFmMzruMqUHVT6ZwvUw+M=', - host: 's3.amazonaws.com:80', - connection: 'Keep-Alive', + 'date': 'Mon, 21 Sep 2015 22:29:27 GMT', + 'authorization': 'AWS accessKey1:V8g5UJUFmMzruMqUHVT6ZwvUw+M=', + 'host': 's3.amazonaws.com:80', + 'connection': 'Keep-Alive', 'user-agent': 'Cyberduck/4.7.2.18004 (Mac OS X/10.10.5) (x86_64)', }; const canonicalizedHeader = getCanonicalizedAmzHeaders(headers); @@ -89,15 +89,15 @@ describe('canonicalization', () => { it('should construct a canonicalized header in the correct order for GCP', () => { const headers = { - date: 'Mon, 21 Sep 2015 22:29:27 GMT', + 'date': 'Mon, 21 Sep 2015 22:29:27 GMT', 'x-goog-request-payer': 'requester', 'x-goog-meta-meta': 'something very meta', 'x-goog-meta-bits': '0', 'x-goog-meta-blksize': '2097152', 'x-goog-meta-compress': '0', - authorization: 'GOOG1 accessKey1:V8g5UJUFmMzruMqUHVT6ZwvUw+M=', - host: 's3.amazonaws.com:80', - connection: 'Keep-Alive', + 'authorization': 'GOOG1 accessKey1:V8g5UJUFmMzruMqUHVT6ZwvUw+M=', + 'host': 's3.amazonaws.com:80', + 'connection': 'Keep-Alive', 'user-agent': 'Cyberduck/4.7.2.18004 (Mac OS X/10.10.5) (x86_64)', }; const canonicalizedHeader = getCanonicalizedGcpHeaders(headers); @@ -113,10 +113,10 @@ describe('canonicalization', () => { it('should return an empty string as the canonicalized ' + 'header if no goog headers', () => { const headers = { - date: 'Mon, 21 Sep 2015 22:29:27 GMT', - authorization: 'GOOG1 accessKey1:V8g5UJUFmMzruMqUHVT6ZwvUw+M=', - host: 'storage.googleapis.com:80', - connection: 'Keep-Alive', + 'date': 'Mon, 21 Sep 2015 22:29:27 GMT', + 'authorization': 'GOOG1 accessKey1:V8g5UJUFmMzruMqUHVT6ZwvUw+M=', + 'host': 'storage.googleapis.com:80', + 'connection': 'Keep-Alive', 'user-agent': 'Cyberduck/4.7.2.18004 (Mac OS X/10.10.5) (x86_64)', }; const canonicalizedHeader = getCanonicalizedGcpHeaders(headers); @@ -167,11 +167,11 @@ describe('canonicalization', () => { it('should handle header values that are arrays (AWS SDK v3 compatibility) for AWS', () => { const headers = { - date: 'Mon, 21 Sep 2015 22:29:27 GMT', + 'date': 'Mon, 21 Sep 2015 22:29:27 GMT', 'x-amz-array-header': ['value1', 'value2', 'value3'], 'x-amz-meta-meta': 'something very meta', - authorization: 'AWS accessKey1:V8g5UJUFmMzruMqUHVT6ZwvUw+M=', - host: 's3.amazonaws.com:80', + 'authorization': 'AWS accessKey1:V8g5UJUFmMzruMqUHVT6ZwvUw+M=', + 'host': 's3.amazonaws.com:80', }; const canonicalizedHeader = getCanonicalizedAmzHeaders(headers); assert.strictEqual( @@ -182,11 +182,11 @@ describe('canonicalization', () => { it('should handle header values that are numbers for AWS', () => { const headers = { - date: 'Mon, 21 Sep 2015 22:29:27 GMT', + 'date': 'Mon, 21 Sep 2015 22:29:27 GMT', 'x-amz-number-header': 12345, 'x-amz-meta-meta': 'something very meta', - authorization: 'AWS accessKey1:V8g5UJUFmMzruMqUHVT6ZwvUw+M=', - host: 's3.amazonaws.com:80', + 'authorization': 'AWS accessKey1:V8g5UJUFmMzruMqUHVT6ZwvUw+M=', + 'host': 's3.amazonaws.com:80', }; const canonicalizedHeader = getCanonicalizedAmzHeaders(headers); assert.strictEqual( @@ -197,11 +197,11 @@ describe('canonicalization', () => { it('should handle header values that are arrays for GCP', () => { const headers = { - date: 'Mon, 21 Sep 2015 22:29:27 GMT', + 'date': 'Mon, 21 Sep 2015 22:29:27 GMT', 'x-goog-array-header': ['gcp-val1', 'gcp-val2'], 'x-goog-meta-meta': 'something very meta', - authorization: 'GOOG1 accessKey1:V8g5UJUFmMzruMqUHVT6ZwvUw+M=', - host: 'storage.googleapis.com:80', + 'authorization': 'GOOG1 accessKey1:V8g5UJUFmMzruMqUHVT6ZwvUw+M=', + 'host': 'storage.googleapis.com:80', }; const canonicalizedHeader = getCanonicalizedGcpHeaders(headers); assert.strictEqual( @@ -212,11 +212,11 @@ describe('canonicalization', () => { it('should handle header values with extra whitespace in arrays', () => { const headers = { - date: 'Mon, 21 Sep 2015 22:29:27 GMT', + 'date': 'Mon, 21 Sep 2015 22:29:27 GMT', 'x-amz-spaced-header': [' value1 ', ' value2 '], 'x-amz-meta-meta': 'something very meta', - authorization: 'AWS accessKey1:V8g5UJUFmMzruMqUHVT6ZwvUw+M=', - host: 's3.amazonaws.com:80', + 'authorization': 'AWS accessKey1:V8g5UJUFmMzruMqUHVT6ZwvUw+M=', + 'host': 's3.amazonaws.com:80', }; const canonicalizedHeader = getCanonicalizedAmzHeaders(headers); assert.strictEqual( diff --git a/tests/unit/network/probe/HealthProbeServer.spec.js b/tests/unit/network/probe/HealthProbeServer.spec.js index 9a520765d..30056ce17 100644 --- a/tests/unit/network/probe/HealthProbeServer.spec.js +++ b/tests/unit/network/probe/HealthProbeServer.spec.js @@ -1,6 +1,5 @@ const assert = require('assert'); -const HealthProbeServer = - require('../../../../lib/network/probe/HealthProbeServer').default; +const HealthProbeServer = require('../../../../lib/network/probe/HealthProbeServer').default; const http = require('http'); function makeRequest(meth, uri) { @@ -15,15 +14,9 @@ function makeRequest(meth, uri) { return req; } -const healthcheckEndpoints = [ - '/_/health/liveness', - '/_/health/readiness', -]; +const healthcheckEndpoints = ['/_/health/liveness', '/_/health/readiness']; -const badHealthcheckEndpoints = [ - '/_/health/liveness_thisiswrong', - '/_/health/readiness_thisiswrong', -]; +const badHealthcheckEndpoints = ['/_/health/liveness_thisiswrong', '/_/health/readiness_thisiswrong']; describe('network.probe.HealthProbeServer', () => { describe('service is "up"', () => { @@ -43,8 +36,7 @@ describe('network.probe.HealthProbeServer', () => { done(); }); healthcheckEndpoints.forEach(ep => { - it('should perform a GET and ' + - 'return 200 OK', done => { + it('should perform a GET and ' + 'return 200 OK', done => { makeRequest('GET', ep) .on('response', res => { assert(res.statusCode === 200); @@ -53,7 +45,8 @@ describe('network.probe.HealthProbeServer', () => { .on('error', err => { assert.ifError(err); done(); - }).end(); + }) + .end(); }); }); }); @@ -83,8 +76,7 @@ describe('network.probe.HealthProbeServer', () => { }); healthcheckEndpoints.forEach(ep => { - it('should perform a GET and ' + - 'return 503 ServiceUnavailable', done => { + it('should perform a GET and ' + 'return 503 ServiceUnavailable', done => { makeRequest('GET', ep) .on('response', res => { assert(res.statusCode === 503); @@ -93,7 +85,8 @@ describe('network.probe.HealthProbeServer', () => { .on('error', err => { assert.ifError(err); done(); - }).end(); + }) + .end(); }); }); }); @@ -119,8 +112,7 @@ describe('network.probe.HealthProbeServer', () => { }); healthcheckEndpoints.forEach(ep => { - it('should perform a POST and ' + - 'return 405 MethodNotAllowed', done => { + it('should perform a POST and ' + 'return 405 MethodNotAllowed', done => { makeRequest('POST', ep) .on('response', res => { assert(res.statusCode === 405); @@ -129,7 +121,8 @@ describe('network.probe.HealthProbeServer', () => { .on('error', err => { assert.ifError(err); done(); - }).end(); + }) + .end(); }); }); }); @@ -154,8 +147,7 @@ describe('network.probe.HealthProbeServer', () => { }); badHealthcheckEndpoints.forEach(ep => { - it('should perform a GET and ' + - 'return 400 InvalidURI', done => { + it('should perform a GET and ' + 'return 400 InvalidURI', done => { makeRequest('GET', ep) .on('response', res => { assert(res.statusCode === 400); @@ -164,7 +156,8 @@ describe('network.probe.HealthProbeServer', () => { .on('error', err => { assert.ifError(err); done(); - }).end(); + }) + .end(); }); }); }); @@ -206,7 +199,8 @@ describe('network.probe.HealthProbeServer', () => { .on('error', err => { assert.ifError(err); done(); - }).end(); + }) + .end(); }); }); }); From 431c7e5120e99c52e4f1d8de91fb3c43a29dcc12 Mon Sep 17 00:00:00 2001 From: Francois Ferrand Date: Tue, 22 Sep 2026 00:14:33 +0200 Subject: [PATCH 4/4] Fix markdown table and code block formatting in BucketInfo doc Malformed table row was missing 3 of its 4 cells, tripping markdownlint's MD056 and blocking the test job before any tests ran. Also cleaned up code fence spacing and missing semicolons/commas in the JS examples, which prettier was flagging now that this file is part of the diff. The 'Unified at v17' row was never really a 4-column table row (that's part of why it was malformed): it's a single summary line. Moving it out to a plain sentence below the table removes it as the widest cell in the first column, which is what was pushing prettier's table padding past markdownlint's 80-char line limit. Table data and field names are otherwise untouched, just repadded to prettier's formatting. Issue: ARSN-642 --- documentation/BucketInfoModelVersion.md | 49 +++++++++++++------------ 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/documentation/BucketInfoModelVersion.md b/documentation/BucketInfoModelVersion.md index c2ab8d311..9d8065b26 100644 --- a/documentation/BucketInfoModelVersion.md +++ b/documentation/BucketInfoModelVersion.md @@ -4,30 +4,31 @@ - v7 to v11 can mean different features between S3C & Zenko. - Avoid using model version to check features - - used by model < v2 (for splitter) - - used in some tests (v5 and v10) - -| Version | S3C (dev/7.X) | Zenko (dev/8.X) | Status | -|---------|-------------------------|--------------------------|-------------| -| v2 - v6 | ✅ | ✅ | ✅ Common | -| v7 | ObjectLock | UID | ❌ Collision| -| v8 | BucketNotification | ReadLocationConstraints | ❌ Collision| -| v9 | SSEConfiguredMasterKey | isNFS | ❌ Collision| -| v10 | UID | Ingestion | ❌ Collision| -| v11 | Tags | AzureInfo | ❌ Collision| -| v12 | - | ObjectLock | ↪️ Forward | -| v13 | - | BucketNotification | ↪️ Forward | -| v14 | - | SSEConfiguredMasterKey | ↪️ Forward | -| v15 | - | Tags | ↪️ Forward | -| v16 | - | Capabilities(VeeamSOSApi)| ✅ New | -| v17 | - | QuotaMax | ✅ New | -| **Unified at v17** | + - used by model < v2 (for splitter) + - used in some tests (v5 and v10) + +| Version | S3C (dev/7.X) | Zenko (dev/8.X) | Status | +| ------- | ---------------------- | ------------------------- | ------------ | +| v2 - v6 | ✅ | ✅ | ✅ Common | +| v7 | ObjectLock | UID | ❌ Collision | +| v8 | BucketNotification | ReadLocationConstraints | ❌ Collision | +| v9 | SSEConfiguredMasterKey | isNFS | ❌ Collision | +| v10 | UID | Ingestion | ❌ Collision | +| v11 | Tags | AzureInfo | ❌ Collision | +| v12 | - | ObjectLock | ↪️ Forward | +| v13 | - | BucketNotification | ↪️ Forward | +| v14 | - | SSEConfiguredMasterKey | ↪️ Forward | +| v15 | - | Tags | ↪️ Forward | +| v16 | - | Capabilities(VeeamSOSApi) | ✅ New | +| v17 | - | QuotaMax | ✅ New | + +Model versions are unified across S3C and Zenko starting at v17. ## Model Version 0/1 ### Properties -``` javascript +```javascript this._acl = aclInstance; this._name = name; this._owner = owner; @@ -44,8 +45,8 @@ property not added until Model Version 2 ### Properties Added -``` javascript -this._mdBucketModelVersion = mdBucketModelVersion || 0 +```javascript +this._mdBucketModelVersion = mdBucketModelVersion || 0; this._transient = transient || false; this._deleted = deleted || false; ``` @@ -216,9 +217,9 @@ The Tag Set of a bucket is an array of objects with Key and Value: [ { Key: 'something', - Value: 'some_data' - } -] + Value: 'some_data', + }, +]; ``` ### Usage