Skip to content

Commit 2084293

Browse files
authored
Merge pull request #132 from samsara-dev/add-soft-fail-option
feat: Add soft-fail option for non-critical cache operations
2 parents fb6e9d2 + 2adc6af commit 2084293

9 files changed

Lines changed: 358 additions & 51 deletions

File tree

README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,12 @@ The plugin includes wrappers to provide both examples and backwards-compatibilit
145145

146146
Force saving the cache even if it exists. Default: `false`.
147147

148+
### `soft-fail` (boolean)
149+
150+
When enabled, any operational failures during cache restore or save operations will emit a warning and continue without failing the build. This includes missing cache paths, network errors, permission issues, and other runtime errors. Configuration errors (missing required options, invalid cache levels, etc.) will still fail immediately.
151+
152+
This is useful when caching is an optimization and should not block the build pipeline. Default: `false`.
153+
148154
### `keep-compressed-artifacts` (boolean)
149155

150156
Remove compression artifacts after they are used. Default: `false`.
@@ -268,6 +274,28 @@ steps:
268274
269275
```
270276

277+
### Using soft-fail for non-critical caching
278+
279+
When caching is purely an optimization and should never block your build, use the `soft-fail` option. This is particularly useful for:
280+
281+
- Mission critical builds where caching is a "nice to have" but not essential
282+
- Optional build caches (ccache, sccache)
283+
- Dependency caches where network issues shouldn't fail builds
284+
- Situations where the cached path may not always exist
285+
286+
```yaml
287+
steps:
288+
- label: ':nodejs: Install dependencies'
289+
command: npm ci
290+
plugins:
291+
- cache#v1.7.0:
292+
path: node_modules
293+
manifest: package-lock.json
294+
restore: file
295+
save: file
296+
soft-fail: true # Network issues or missing paths won't fail the build
297+
```
298+
271299
## License
272300

273301
MIT (see [LICENSE](LICENSE))

compression/tgz_wrapper

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#!/bin/bash
2+
set -eo pipefail
23

34
OPERATION=${1?Operation not specified}
45
SOURCE=${2?Source not specified}

compression/zip_wrapper

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#!/bin/bash
2+
set -eo pipefail
23

34
OPERATION=${1?Operation not specified}
45
SOURCE=${2?Source not specified}

compression/zstd_wrapper

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#!/bin/bash
2+
set -eo pipefail
23

34
OPERATION=${1?Operation not specified}
45
SOURCE=${2?Source not specified}

hooks/post-command

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -45,35 +45,50 @@ if [ "${#SAVE_LEVELS[@]}" -ne "${#result[@]}" ]; then
4545
exit 1
4646
fi
4747

48-
ACTUAL_PATH="${CACHE_PATH}"
49-
48+
# Validate manifest is present for file-level caching (config validation - always fails)
5049
for LEVEL in "${SAVE_LEVELS[@]}"; do
51-
# this validates the level as well
52-
KEY=$(build_key "${LEVEL}" "${CACHE_PATH}" "${COMPRESS}")
53-
5450
if [ "${LEVEL}" = 'file' ] && [ -z "$(plugin_read_list MANIFEST)" ]; then
5551
echo "+++ 🚨 Missing manifest option in the cache plugin for file-level saving"
5652
exit 1
5753
fi
54+
done
5855

59-
if [ "$(plugin_read_config FORCE 'false')" != 'false' ] ||
60-
[ "${lower_level_updated:-false}" = 'true' ] ||
61-
! backend_exec exists "${KEY}"; then
62-
echo "Saving ${LEVEL}-level cache of ${CACHE_PATH}"
63-
if compression_active && [ "${already_compressed:-false}" = 'false' ]; then
64-
ACTUAL_PATH=$(mktemp)
65-
66-
# TERM in case it is cancelled
67-
# EXIT when the script ends
68-
# shellcheck disable=SC2064 # we want the expansion to happen now
69-
trap "cleanup_compression_tempfile '${ACTUAL_PATH}'" TERM EXIT
56+
perform_save() {
57+
# Check if cache path exists before attempting to save
58+
if [ ! -e "${CACHE_PATH}" ]; then
59+
echo "+++ 🚨 Cache path '${CACHE_PATH}' does not exist"
60+
return 1
61+
fi
7062

71-
compress "${CACHE_PATH}" "${ACTUAL_PATH}"
72-
already_compressed='true' # avoid re-compressing the files
63+
local ACTUAL_PATH="${CACHE_PATH}"
64+
local lower_level_updated='false'
65+
local already_compressed='false'
66+
67+
for LEVEL in "${SAVE_LEVELS[@]}"; do
68+
# this validates the level as well
69+
KEY=$(build_key "${LEVEL}" "${CACHE_PATH}" "${COMPRESS}")
70+
71+
if [ "$(plugin_read_config FORCE 'false')" != 'false' ] ||
72+
[ "${lower_level_updated}" = 'true' ] ||
73+
! backend_exec exists "${KEY}"; then
74+
echo "Saving ${LEVEL}-level cache of ${CACHE_PATH}"
75+
if compression_active && [ "${already_compressed}" = 'false' ]; then
76+
ACTUAL_PATH=$(mktemp)
77+
78+
# TERM in case it is cancelled
79+
# EXIT when the script ends
80+
# shellcheck disable=SC2064 # we want the expansion to happen now
81+
trap "cleanup_compression_tempfile '${ACTUAL_PATH}'" TERM EXIT
82+
83+
compress "${CACHE_PATH}" "${ACTUAL_PATH}" || return 1
84+
already_compressed='true' # avoid re-compressing the files
85+
fi
86+
backend_exec save "${KEY}" "${ACTUAL_PATH}" || return 1
87+
lower_level_updated='true' # to force saving higher levels
88+
else
89+
echo "Cache of ${LEVEL} already exists, skipping"
7390
fi
74-
backend_exec save "${KEY}" "${ACTUAL_PATH}"
75-
lower_level_updated='true' # to force saving higher levels
76-
else
77-
echo "Cache of ${LEVEL} already exists, skipping"
78-
fi
79-
done
91+
done
92+
}
93+
94+
soft_fail_exec "save" perform_save

hooks/pre-command

Lines changed: 31 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -30,32 +30,36 @@ build_key "${MAX_LEVEL}" "${RESTORE_PATH}" >/dev/null # to validate the level
3030

3131
SORTED_LEVELS=(file step branch pipeline all)
3232

33-
for CURRENT_LEVEL in "${SORTED_LEVELS[@]}"; do
34-
if [ "${CURRENT_LEVEL}" = 'file' ] && [ -z "$(plugin_read_list MANIFEST)" ]; then
35-
continue
36-
fi
37-
38-
KEY=$(build_key "${CURRENT_LEVEL}" "${RESTORE_PATH}" "${COMPRESS}")
39-
if backend_exec exists "${KEY}"; then
40-
echo "Cache hit at ${CURRENT_LEVEL} level, restoring ${RESTORE_PATH}..."
41-
42-
if compression_active; then
43-
TEMP_PATH=$(mktemp)
44-
45-
# TERM in case it is cancelled
46-
# EXIT when the script ends
47-
# shellcheck disable=SC2064 # we want the expansion to happen now
48-
trap "cleanup_compression_tempfile '${TEMP_PATH}'" TERM EXIT
49-
50-
backend_exec get "${KEY}" "${TEMP_PATH}"
51-
decompress "${TEMP_PATH}" "${RESTORE_PATH}"
52-
else
53-
backend_exec get "${KEY}" "${RESTORE_PATH}"
33+
perform_restore() {
34+
for CURRENT_LEVEL in "${SORTED_LEVELS[@]}"; do
35+
if [ "${CURRENT_LEVEL}" = 'file' ] && [ -z "$(plugin_read_list MANIFEST)" ]; then
36+
continue
5437
fi
5538

56-
exit 0
57-
elif [ "${CURRENT_LEVEL}" = "${MAX_LEVEL}" ]; then
58-
echo "Cache miss up to ${CURRENT_LEVEL}-level for ${RESTORE_PATH}, sorry"
59-
break
60-
fi
61-
done
39+
KEY=$(build_key "${CURRENT_LEVEL}" "${RESTORE_PATH}" "${COMPRESS}")
40+
if backend_exec exists "${KEY}"; then
41+
echo "Cache hit at ${CURRENT_LEVEL} level, restoring ${RESTORE_PATH}..."
42+
43+
if compression_active; then
44+
TEMP_PATH=$(mktemp)
45+
46+
# TERM in case it is cancelled
47+
# EXIT when the script ends
48+
# shellcheck disable=SC2064 # we want the expansion to happen now
49+
trap "cleanup_compression_tempfile '${TEMP_PATH}'" TERM EXIT
50+
51+
backend_exec get "${KEY}" "${TEMP_PATH}" || return 1
52+
decompress "${TEMP_PATH}" "${RESTORE_PATH}" || return 1
53+
else
54+
backend_exec get "${KEY}" "${RESTORE_PATH}" || return 1
55+
fi
56+
57+
exit 0
58+
elif [ "${CURRENT_LEVEL}" = "${MAX_LEVEL}" ]; then
59+
echo "Cache miss up to ${CURRENT_LEVEL}-level for ${RESTORE_PATH}, sorry"
60+
break
61+
fi
62+
done
63+
}
64+
65+
soft_fail_exec "restore" perform_restore

lib/shared.bash

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,30 @@ backend_exec() {
6767

6868
PATH="${PATH}:${DIR}/../backends" "cache_${BACKEND_NAME}" "$@"
6969
}
70+
71+
# Executes a command with soft-fail handling
72+
# If soft-fail is enabled and the command fails, warns and exits 0
73+
# Otherwise, propagates the failure
74+
soft_fail_exec() {
75+
local operation="$1"
76+
shift
77+
78+
local SOFT_FAIL
79+
SOFT_FAIL=$(plugin_read_config SOFT_FAIL 'false')
80+
81+
if [ "${SOFT_FAIL}" = 'true' ]; then
82+
# Disable errexit temporarily to catch errors
83+
set +e
84+
"$@"
85+
local exit_code=$?
86+
set -e
87+
88+
if [ ${exit_code} -ne 0 ]; then
89+
echo "--- ⚠️ Cache ${operation} operation failed, continuing build (soft-fail enabled)"
90+
exit 0
91+
fi
92+
else
93+
# Execute normally, let errors propagate
94+
"$@"
95+
fi
96+
}

plugin.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ configuration:
2323
type: boolean
2424
key-extra:
2525
type: string
26+
soft-fail:
27+
type: boolean
2628
manifest:
2729
oneOf:
2830
- type: string

0 commit comments

Comments
 (0)