diff --git a/.github/workflows/test-exact-match.yaml b/.github/workflows/test-exact-match.yaml new file mode 100644 index 0000000..93dcf2b --- /dev/null +++ b/.github/workflows/test-exact-match.yaml @@ -0,0 +1,73 @@ +name: test-exact-match + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + # Save a "decoy" cache whose key is a prefix-extension of the exact key. + # e.g. key = "test-exact-Linux-12345-decoy" which shares the prefix + # "test-exact-Linux-12345" with the key we'll try to restore later. + test-save-decoy: + strategy: + matrix: + os: [ubuntu-latest] + fail-fast: false + runs-on: ${{ matrix.os }} + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Generate decoy cache files + shell: bash + run: src/create-cache-files.sh ${{ runner.os }} test-cache + - name: Save decoy cache + uses: ./ + with: + endpoint: ${{ secrets.ENDPOINT }} + accessKey: ${{ secrets.ACCESS_KEY }} + secretKey: ${{ secrets.SECRET_KEY }} + bucket: ${{ secrets.BUCKET }} + use-fallback: false + key: test-exact-${{ runner.os }}-${{ github.run_id }}-decoy + path: test-cache + + # Try to restore with the shorter key that is a prefix of the decoy key. + # With correct exact matching this must NOT match the decoy. + test-exact-no-false-positive: + needs: test-save-decoy + strategy: + matrix: + os: [ubuntu-latest] + fail-fast: false + runs-on: ${{ matrix.os }} + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Restore cache (should miss) + id: restore + uses: ./ + with: + endpoint: ${{ secrets.ENDPOINT }} + accessKey: ${{ secrets.ACCESS_KEY }} + secretKey: ${{ secrets.SECRET_KEY }} + bucket: ${{ secrets.BUCKET }} + use-fallback: false + key: test-exact-${{ runner.os }}-${{ github.run_id }} + path: test-cache + - name: Verify cache was NOT restored + shell: bash + env: + CACHE_HIT: ${{ steps.restore.outputs.cache-hit }} + run: | + echo "cache-hit=$CACHE_HIT" + if [ "$CACHE_HIT" = "true" ]; then + echo "FAIL: cache-hit should not be true — decoy was incorrectly matched" + exit 1 + fi + if [ -e test-cache/test-file.txt ]; then + echo "FAIL: test-file.txt should not exist — decoy files were restored" + exit 1 + fi + echo "PASS: exact match correctly rejected the decoy" diff --git a/dist/restore/index.js b/dist/restore/index.js index 549ff9b..5f72acb 100644 --- a/dist/restore/index.js +++ b/dist/restore/index.js @@ -86300,14 +86300,18 @@ function findObject(mc, bucket, key, restoreKeys, compressionMethod) { return __awaiter(this, void 0, void 0, function* () { core.debug("Key: " + JSON.stringify(key)); core.debug("Restore keys: " + JSON.stringify(restoreKeys)); - core.debug(`Finding exact macth for: ${key}`); - const exactMatch = yield listObjects(mc, bucket, key); - core.debug(`Found ${JSON.stringify(exactMatch, null, 2)}`); - if (exactMatch.length) { - const result = { item: exactMatch[0], matchingKey: key }; - core.debug(`Using ${JSON.stringify(result)}`); - return result; + core.debug(`Finding exact match for: ${key}`); + const keyMatches = yield listObjects(mc, bucket, key); + core.debug(`Found ${JSON.stringify(keyMatches, null, 2)}`); + if (keyMatches.length > 0) { + const exactMatch = keyMatches.find((obj) => { var _a; return (_a = obj.name) === null || _a === void 0 ? void 0 : _a.startsWith(key + path_1.default.sep); }); + if (exactMatch) { + const result = { item: exactMatch, matchingKey: key }; + core.debug(`Found an exact match; using ${JSON.stringify(result)}`); + return result; + } } + core.debug(`Didn't find an exact match`); for (const restoreKey of restoreKeys) { const fn = utils.getCacheFileName(compressionMethod); core.debug(`Finding object with prefix: ${restoreKey}`); diff --git a/dist/save/index.js b/dist/save/index.js index 4dc03f9..7af964c 100644 --- a/dist/save/index.js +++ b/dist/save/index.js @@ -86215,14 +86215,18 @@ function findObject(mc, bucket, key, restoreKeys, compressionMethod) { return __awaiter(this, void 0, void 0, function* () { core.debug("Key: " + JSON.stringify(key)); core.debug("Restore keys: " + JSON.stringify(restoreKeys)); - core.debug(`Finding exact macth for: ${key}`); - const exactMatch = yield listObjects(mc, bucket, key); - core.debug(`Found ${JSON.stringify(exactMatch, null, 2)}`); - if (exactMatch.length) { - const result = { item: exactMatch[0], matchingKey: key }; - core.debug(`Using ${JSON.stringify(result)}`); - return result; + core.debug(`Finding exact match for: ${key}`); + const keyMatches = yield listObjects(mc, bucket, key); + core.debug(`Found ${JSON.stringify(keyMatches, null, 2)}`); + if (keyMatches.length > 0) { + const exactMatch = keyMatches.find((obj) => { var _a; return (_a = obj.name) === null || _a === void 0 ? void 0 : _a.startsWith(key + path_1.default.sep); }); + if (exactMatch) { + const result = { item: exactMatch, matchingKey: key }; + core.debug(`Found an exact match; using ${JSON.stringify(result)}`); + return result; + } } + core.debug(`Didn't find an exact match`); for (const restoreKey of restoreKeys) { const fn = utils.getCacheFileName(compressionMethod); core.debug(`Finding object with prefix: ${restoreKey}`); diff --git a/dist/saveOnly/index.js b/dist/saveOnly/index.js index e480d95..d6edfed 100644 --- a/dist/saveOnly/index.js +++ b/dist/saveOnly/index.js @@ -86215,14 +86215,18 @@ function findObject(mc, bucket, key, restoreKeys, compressionMethod) { return __awaiter(this, void 0, void 0, function* () { core.debug("Key: " + JSON.stringify(key)); core.debug("Restore keys: " + JSON.stringify(restoreKeys)); - core.debug(`Finding exact macth for: ${key}`); - const exactMatch = yield listObjects(mc, bucket, key); - core.debug(`Found ${JSON.stringify(exactMatch, null, 2)}`); - if (exactMatch.length) { - const result = { item: exactMatch[0], matchingKey: key }; - core.debug(`Using ${JSON.stringify(result)}`); - return result; + core.debug(`Finding exact match for: ${key}`); + const keyMatches = yield listObjects(mc, bucket, key); + core.debug(`Found ${JSON.stringify(keyMatches, null, 2)}`); + if (keyMatches.length > 0) { + const exactMatch = keyMatches.find((obj) => { var _a; return (_a = obj.name) === null || _a === void 0 ? void 0 : _a.startsWith(key + path_1.default.sep); }); + if (exactMatch) { + const result = { item: exactMatch, matchingKey: key }; + core.debug(`Found an exact match; using ${JSON.stringify(result)}`); + return result; + } } + core.debug(`Didn't find an exact match`); for (const restoreKey of restoreKeys) { const fn = utils.getCacheFileName(compressionMethod); core.debug(`Finding object with prefix: ${restoreKey}`); diff --git a/src/utils.ts b/src/utils.ts index a4d8d75..b7e2173 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -128,14 +128,18 @@ export async function findObject( core.debug("Key: " + JSON.stringify(key)); core.debug("Restore keys: " + JSON.stringify(restoreKeys)); - core.debug(`Finding exact macth for: ${key}`); - const exactMatch = await listObjects(mc, bucket, key); - core.debug(`Found ${JSON.stringify(exactMatch, null, 2)}`); - if (exactMatch.length) { - const result = { item: exactMatch[0], matchingKey: key }; - core.debug(`Using ${JSON.stringify(result)}`); - return result; + core.debug(`Finding exact match for: ${key}`); + const keyMatches = await listObjects(mc, bucket, key); + core.debug(`Found ${JSON.stringify(keyMatches, null, 2)}`); + if (keyMatches.length > 0) { + const exactMatch = keyMatches.find((obj) => obj.name?.startsWith(key + path.sep)); + if (exactMatch) { + const result = { item: exactMatch, matchingKey: key }; + core.debug(`Found an exact match; using ${JSON.stringify(result)}`); + return result; + } } + core.debug(`Didn't find an exact match`); for (const restoreKey of restoreKeys) { const fn = utils.getCacheFileName(compressionMethod);