From 60b2eafe34394ce2f13deb0ec5b91115ab096662 Mon Sep 17 00:00:00 2001 From: Carlos Matos Date: Mon, 16 Mar 2026 13:38:12 -0400 Subject: [PATCH 1/4] feat(container-pull): add version channel selector (latest, N-1, N-2, LTS, LTS-1) Allow users to pull images by release channel keyword via the existing -v/--version flag instead of requiring exact version numbers. Channel keywords are resolved from registry tag data with no new API calls. --- .../falcon-container-sensor-pull/README.md | 51 +++++++- .../falcon-container-sensor-pull.sh | 121 ++++++++++++++++-- 2 files changed, 162 insertions(+), 10 deletions(-) diff --git a/bash/containers/falcon-container-sensor-pull/README.md b/bash/containers/falcon-container-sensor-pull/README.md index 690b9d3..dba7984 100644 --- a/bash/containers/falcon-container-sensor-pull/README.md +++ b/bash/containers/falcon-container-sensor-pull/README.md @@ -101,6 +101,16 @@ Optional Flags: By default, the image name and tag are appended. Use --copy-omit-image-name and/or --copy-custom-tag to change that behavior. -v, --version Specify sensor version to retrieve from the registry + + Accepts version strings or channel keywords: + ------------------------------------------- + latest Latest sensor version (default) + N-1 Previous major.minor release + N-2 Two major.minor releases back + LTS Latest LTS release + LTS-1 Previous LTS release + 7.33 Latest build of version 7.33.x + 7.33.0-18606 Specific sensor build -p, --platform Specify sensor platform to retrieve, e.g., x86_64, aarch64 -t, --type Specify which sensor to download (Default: falcon-container) @@ -144,7 +154,7 @@ Help Options: | `-s`, `--client-secret ` | `$FALCON_CLIENT_SECRET` | `None` (Required) | CrowdStrike API Client Secret | | `-r`, `--region ` | `$FALCON_CLOUD` | `us-1` (Optional) | CrowdStrike Region.
\**Auto-discovery is only available for [`us-1, us-2, eu-1`] regions.* | | `-c`, `--copy ` | `$COPY` | `None` (Optional) | Registry you want to copy the sensor image to. Example: `myregistry.com/mynamespace`.
*\*By default, the image name and tag are appended. Use `--copy-omit-image-name` and/or `--copy-custom-tag` to change that behavior.* | -| `-v`, `--version ` | `$SENSOR_VERSION` | `None` (Optional) | Specify sensor version to retrieve from the registry | +| `-v`, `--version ` | `$SENSOR_VERSION` | `None` (Optional) | Specify sensor version to retrieve from the registry. Accepts version strings (e.g., `7.33`, `7.33.0`) or channel keywords: `latest`, `N-1`, `N-2`, `LTS`, `LTS-1` | | `-p`, `--platform ` | `$SENSOR_PLATFORM` | `None` (Optional) | Specify sensor platform to retrieve from the registry | | `-t`, `--type ` | `$SENSOR_TYPE` | `falcon-container` (Optional) | Specify which sensor to download [`falcon-container`, `falcon-sensor`, `falcon-sensor-regional`, `falcon-kac`, `falcon-snapshot`, `falcon-imageanalyzer`, `fcs`, `falcon-jobcontroller`, `falcon-registryassessmentexecutor`] ([see more details below](#sensor-types)) | | `--runtime` | `$CONTAINER_TOOL` | `docker` (Optional) | Use a different container runtime [docker, podman, skopeo]. **Default is Docker**. | @@ -183,6 +193,45 @@ The following sensor types are available to download: | `falcon-jobcontroller` | The Self Hosted Registry Assessment Jobs Controller | | `falcon-registryassessmentexecutor` | The Self Hosted Registry Assessment Executor | +### Version Channels + +The `-v, --version` flag accepts channel keywords for policy-driven deployments without needing to know exact version numbers: + +| Keyword | Description | +| :------- | :---------------------------------- | +| `latest` | Latest sensor version (default) | +| `N-1` | Previous major.minor release | +| `N-2` | Two major.minor releases back | +| `LTS` | Latest LTS release | +| `LTS-1` | Previous LTS release | + +Channel keywords are case-insensitive (`n-1`, `N-1`, `lts`, `LTS` all work). N-1/N-2 exclude LTS tags from consideration. No additional API scopes are required — channels are resolved entirely from existing registry tag data. + +```shell +# Pull latest (default behavior) +./falcon-container-sensor-pull.sh \ +--client-id \ +--client-secret + +# Pull the previous major.minor release (N-1) +./falcon-container-sensor-pull.sh \ +--client-id \ +--client-secret \ +--version N-1 + +# Pull the latest LTS release +./falcon-container-sensor-pull.sh \ +--client-id \ +--client-secret \ +--version LTS + +# Pull the previous LTS release +./falcon-container-sensor-pull.sh \ +--client-id \ +--client-secret \ +--version LTS-1 +``` + ### Examples #### Example downloading the Falcon Kubernetes Admission Controller diff --git a/bash/containers/falcon-container-sensor-pull/falcon-container-sensor-pull.sh b/bash/containers/falcon-container-sensor-pull/falcon-container-sensor-pull.sh index 9e601bb..4cc52ad 100755 --- a/bash/containers/falcon-container-sensor-pull/falcon-container-sensor-pull.sh +++ b/bash/containers/falcon-container-sensor-pull/falcon-container-sensor-pull.sh @@ -23,6 +23,17 @@ Optional Flags: By default, the image name and tag are appended. Use --copy-omit-image-name and/or --copy-custom-tag to change that behavior. -v, --version Specify sensor version to retrieve from the registry + + Accepts version strings or channel keywords: + ------------------------------------------- + latest Latest sensor version (default) + N-1 Previous major.minor release + N-2 Two major.minor releases back + LTS Latest LTS release + LTS-1 Previous LTS release + 7.33 Latest build of version 7.33.x + 7.33.0-18606 Specific sensor build + -p, --platform Specify sensor platform to retrieve, e.g., x86_64, aarch64 -t, --type Specify which sensor to download (Default: falcon-container) @@ -454,6 +465,94 @@ display_api_scopes() { esac } +# Extract clean tag strings from list_tags JSON output +extract_raw_tags() { + list_tags | awk ' + /^[[:space:]]*"[0-9]/ { + gsub(/^[[:space:]]*"/, "") + gsub(/"[[:space:]]*,?[[:space:]]*$/, "") + if (length($0) > 0) print $0 + } + ' +} + +# Resolve version channel keywords (latest, N-1, N-2, LTS, LTS-1) to version prefixes +resolve_version_channel() { + local input="$1" + local normalized all_tags major_minor_versions lts_tags lts_versions target_version + + normalized=$(echo "$input" | tr '[:upper:]' '[:lower:]') + + case "$normalized" in + latest) + echo "" + return 0 + ;; + n-1|n-2|lts|lts-1) + all_tags=$(extract_raw_tags) + if [ -z "$all_tags" ]; then + echo "Fatal error: No tags found for sensor type: ${SENSOR_TYPE}" >&2 + return 1 + fi + ;; + *) + # Not a channel keyword — pass through as-is + echo "$input" + return 0 + ;; + esac + + case "$normalized" in + n-1) + major_minor_versions=$(echo "$all_tags" | grep -v "\-LTS" | \ + awk -F'.' '{ print $1"."$2 }' | sort -u -V) + if [ "$(echo "$major_minor_versions" | wc -l)" -lt 2 ]; then + echo "Fatal error: Not enough versions available for N-1. Only $(echo "$major_minor_versions" | wc -l | tr -d ' ') major.minor version(s) found." >&2 + return 1 + fi + target_version=$(echo "$major_minor_versions" | tail -2 | head -1) + ;; + n-2) + major_minor_versions=$(echo "$all_tags" | grep -v "\-LTS" | \ + awk -F'.' '{ print $1"."$2 }' | sort -u -V) + if [ "$(echo "$major_minor_versions" | wc -l)" -lt 3 ]; then + echo "Fatal error: Not enough versions available for N-2. Only $(echo "$major_minor_versions" | wc -l | tr -d ' ') major.minor version(s) found." >&2 + return 1 + fi + target_version=$(echo "$major_minor_versions" | tail -3 | head -1) + ;; + lts) + lts_tags=$(echo "$all_tags" | grep "\-LTS") + if [ -z "$lts_tags" ]; then + echo "Fatal error: No LTS versions found for sensor type: ${SENSOR_TYPE}" >&2 + return 1 + fi + lts_versions=$(echo "$lts_tags" | awk -F'.' '{ print $1"."$2 }' | sort -u -V) + target_version=$(echo "$lts_versions" | tail -1) + ;; + lts-1) + lts_tags=$(echo "$all_tags" | grep "\-LTS") + if [ -z "$lts_tags" ]; then + echo "Fatal error: No LTS versions found for sensor type: ${SENSOR_TYPE}" >&2 + return 1 + fi + lts_versions=$(echo "$lts_tags" | awk -F'.' '{ print $1"."$2 }' | sort -u -V) + if [ "$(echo "$lts_versions" | wc -l)" -lt 2 ]; then + echo "Fatal error: Not enough LTS versions available for LTS-1. Only $(echo "$lts_versions" | wc -l | tr -d ' ') LTS version(s) found." >&2 + return 1 + fi + target_version=$(echo "$lts_versions" | tail -2 | head -1) + ;; + esac + + if [ -z "$target_version" ]; then + return 1 + fi + + echo "$target_version" + return 0 +} + # Smart version matching function match_sensor_version() { local requested_version="$1" @@ -462,14 +561,7 @@ match_sensor_version() { local version_pattern # Get all available tags by properly parsing JSON output from list_tags - all_tags=$(list_tags | awk ' - /^[[:space:]]*"[0-9]/ { - # Extract quoted tag (lines starting with version numbers), remove surrounding quotes and whitespace - gsub(/^[[:space:]]*"/, "") - gsub(/"[[:space:]]*,?[[:space:]]*$/, "") - if (length($0) > 0) print $0 - } - ') + all_tags=$(extract_raw_tags) if [ -z "$requested_version" ]; then # If no version specified, get the latest version @@ -836,8 +928,18 @@ if [ "${ERROR}" = "true" ]; then fi #Get latest sensor version +# Resolve channel keywords (latest, N-1, N-2, LTS, LTS-1) to version prefixes +set +e +RESOLVED_VERSION=$(resolve_version_channel "$SENSOR_VERSION") +CHANNEL_STATUS=$? +set -e + +if [ $CHANNEL_STATUS -ne 0 ]; then + exit 1 # error message already printed to stderr by resolve_version_channel +fi + set +e # Temporarily disable exit-on-error for version matching -LATESTSENSOR=$(match_sensor_version "$SENSOR_VERSION") +LATESTSENSOR=$(match_sensor_version "$RESOLVED_VERSION") set -e # Re-enable exit-on-error # Check if version matching was successful @@ -847,6 +949,7 @@ if [ -z "$LATESTSENSOR" ]; then Available versions can be listed with: $0 --list-tags -t ${SENSOR_TYPE} Tips for version matching: + - Use channel keywords: -v latest, -v N-1, -v N-2, -v LTS, -v LTS-1 - Use exact version: -v 7.31.0 - Use partial version: -v 7.31 (matches latest 7.31.x) - Use major version: -v 7 (matches latest 7.x.x) From 44f3517ea69ff7c5f08349565215232c5346ef2d Mon Sep 17 00:00:00 2001 From: Carlos Matos Date: Mon, 16 Mar 2026 13:44:23 -0400 Subject: [PATCH 2/4] fix(container-pull): use die() in resolve_version_channel for cleaner error handling Replace echo-to-stderr + return 1 with die() calls, and remove the set +e / CHANNEL_STATUS boilerplate in the caller. die() prints to stderr (not captured by $()) and exit 1 propagates via set -e. --- .../falcon-container-sensor-pull.sh | 32 +++++++------------ 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/bash/containers/falcon-container-sensor-pull/falcon-container-sensor-pull.sh b/bash/containers/falcon-container-sensor-pull/falcon-container-sensor-pull.sh index 4cc52ad..1694c86 100755 --- a/bash/containers/falcon-container-sensor-pull/falcon-container-sensor-pull.sh +++ b/bash/containers/falcon-container-sensor-pull/falcon-container-sensor-pull.sh @@ -491,8 +491,7 @@ resolve_version_channel() { n-1|n-2|lts|lts-1) all_tags=$(extract_raw_tags) if [ -z "$all_tags" ]; then - echo "Fatal error: No tags found for sensor type: ${SENSOR_TYPE}" >&2 - return 1 + die "No tags found for sensor type: ${SENSOR_TYPE}" fi ;; *) @@ -507,8 +506,7 @@ resolve_version_channel() { major_minor_versions=$(echo "$all_tags" | grep -v "\-LTS" | \ awk -F'.' '{ print $1"."$2 }' | sort -u -V) if [ "$(echo "$major_minor_versions" | wc -l)" -lt 2 ]; then - echo "Fatal error: Not enough versions available for N-1. Only $(echo "$major_minor_versions" | wc -l | tr -d ' ') major.minor version(s) found." >&2 - return 1 + die "Not enough versions available for N-1. Only $(echo "$major_minor_versions" | wc -l | tr -d ' ') major.minor version(s) found." fi target_version=$(echo "$major_minor_versions" | tail -2 | head -1) ;; @@ -516,16 +514,14 @@ resolve_version_channel() { major_minor_versions=$(echo "$all_tags" | grep -v "\-LTS" | \ awk -F'.' '{ print $1"."$2 }' | sort -u -V) if [ "$(echo "$major_minor_versions" | wc -l)" -lt 3 ]; then - echo "Fatal error: Not enough versions available for N-2. Only $(echo "$major_minor_versions" | wc -l | tr -d ' ') major.minor version(s) found." >&2 - return 1 + die "Not enough versions available for N-2. Only $(echo "$major_minor_versions" | wc -l | tr -d ' ') major.minor version(s) found." fi target_version=$(echo "$major_minor_versions" | tail -3 | head -1) ;; lts) lts_tags=$(echo "$all_tags" | grep "\-LTS") if [ -z "$lts_tags" ]; then - echo "Fatal error: No LTS versions found for sensor type: ${SENSOR_TYPE}" >&2 - return 1 + die "No LTS versions found for sensor type: ${SENSOR_TYPE}" fi lts_versions=$(echo "$lts_tags" | awk -F'.' '{ print $1"."$2 }' | sort -u -V) target_version=$(echo "$lts_versions" | tail -1) @@ -533,13 +529,11 @@ resolve_version_channel() { lts-1) lts_tags=$(echo "$all_tags" | grep "\-LTS") if [ -z "$lts_tags" ]; then - echo "Fatal error: No LTS versions found for sensor type: ${SENSOR_TYPE}" >&2 - return 1 + die "No LTS versions found for sensor type: ${SENSOR_TYPE}" fi lts_versions=$(echo "$lts_tags" | awk -F'.' '{ print $1"."$2 }' | sort -u -V) if [ "$(echo "$lts_versions" | wc -l)" -lt 2 ]; then - echo "Fatal error: Not enough LTS versions available for LTS-1. Only $(echo "$lts_versions" | wc -l | tr -d ' ') LTS version(s) found." >&2 - return 1 + die "Not enough LTS versions available for LTS-1. Only $(echo "$lts_versions" | wc -l | tr -d ' ') LTS version(s) found." fi target_version=$(echo "$lts_versions" | tail -2 | head -1) ;; @@ -928,17 +922,13 @@ if [ "${ERROR}" = "true" ]; then fi #Get latest sensor version -# Resolve channel keywords (latest, N-1, N-2, LTS, LTS-1) to version prefixes -set +e +# Resolve channel keywords (latest, N-1, N-2, LTS, LTS-1) to version prefixes. +# die() inside resolve_version_channel exits the subshell with code 1; +# set -e (active since line 7) propagates that to the parent script. RESOLVED_VERSION=$(resolve_version_channel "$SENSOR_VERSION") -CHANNEL_STATUS=$? -set -e -if [ $CHANNEL_STATUS -ne 0 ]; then - exit 1 # error message already printed to stderr by resolve_version_channel -fi - -set +e # Temporarily disable exit-on-error for version matching +# match_sensor_version returns 1 for "no match" — a soft failure we handle below. +set +e LATESTSENSOR=$(match_sensor_version "$RESOLVED_VERSION") set -e # Re-enable exit-on-error From bc7a09292066461ade4a814f69efd8240b3d9fac Mon Sep 17 00:00:00 2001 From: Carlos Matos Date: Mon, 16 Mar 2026 13:50:31 -0400 Subject: [PATCH 3/4] chore(container-pull): clean up version resolution comments --- .../falcon-container-sensor-pull.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bash/containers/falcon-container-sensor-pull/falcon-container-sensor-pull.sh b/bash/containers/falcon-container-sensor-pull/falcon-container-sensor-pull.sh index 1694c86..83426ce 100755 --- a/bash/containers/falcon-container-sensor-pull/falcon-container-sensor-pull.sh +++ b/bash/containers/falcon-container-sensor-pull/falcon-container-sensor-pull.sh @@ -921,12 +921,10 @@ if [ "${ERROR}" = "true" ]; then die "ERROR: ${CONTAINER_TOOL} login failed. Error message: ${error_message}" fi -#Get latest sensor version # Resolve channel keywords (latest, N-1, N-2, LTS, LTS-1) to version prefixes. -# die() inside resolve_version_channel exits the subshell with code 1; -# set -e (active since line 7) propagates that to the parent script. RESOLVED_VERSION=$(resolve_version_channel "$SENSOR_VERSION") +# Get latest sensor version # match_sensor_version returns 1 for "no match" — a soft failure we handle below. set +e LATESTSENSOR=$(match_sensor_version "$RESOLVED_VERSION") From b5f2c65e59de7f293204ff52fabd64633ce5cdba Mon Sep 17 00:00:00 2001 From: Carlos Matos Date: Mon, 16 Mar 2026 14:00:08 -0400 Subject: [PATCH 4/4] style(container-pull): apply shfmt formatting fixes --- .../falcon-container-sensor-pull.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bash/containers/falcon-container-sensor-pull/falcon-container-sensor-pull.sh b/bash/containers/falcon-container-sensor-pull/falcon-container-sensor-pull.sh index 83426ce..c9e0c41 100755 --- a/bash/containers/falcon-container-sensor-pull/falcon-container-sensor-pull.sh +++ b/bash/containers/falcon-container-sensor-pull/falcon-container-sensor-pull.sh @@ -488,7 +488,7 @@ resolve_version_channel() { echo "" return 0 ;; - n-1|n-2|lts|lts-1) + n-1 | n-2 | lts | lts-1) all_tags=$(extract_raw_tags) if [ -z "$all_tags" ]; then die "No tags found for sensor type: ${SENSOR_TYPE}" @@ -503,7 +503,7 @@ resolve_version_channel() { case "$normalized" in n-1) - major_minor_versions=$(echo "$all_tags" | grep -v "\-LTS" | \ + major_minor_versions=$(echo "$all_tags" | grep -v "\-LTS" | awk -F'.' '{ print $1"."$2 }' | sort -u -V) if [ "$(echo "$major_minor_versions" | wc -l)" -lt 2 ]; then die "Not enough versions available for N-1. Only $(echo "$major_minor_versions" | wc -l | tr -d ' ') major.minor version(s) found." @@ -511,7 +511,7 @@ resolve_version_channel() { target_version=$(echo "$major_minor_versions" | tail -2 | head -1) ;; n-2) - major_minor_versions=$(echo "$all_tags" | grep -v "\-LTS" | \ + major_minor_versions=$(echo "$all_tags" | grep -v "\-LTS" | awk -F'.' '{ print $1"."$2 }' | sort -u -V) if [ "$(echo "$major_minor_versions" | wc -l)" -lt 3 ]; then die "Not enough versions available for N-2. Only $(echo "$major_minor_versions" | wc -l | tr -d ' ') major.minor version(s) found." @@ -928,7 +928,7 @@ RESOLVED_VERSION=$(resolve_version_channel "$SENSOR_VERSION") # match_sensor_version returns 1 for "no match" — a soft failure we handle below. set +e LATESTSENSOR=$(match_sensor_version "$RESOLVED_VERSION") -set -e # Re-enable exit-on-error +set -e # Re-enable exit-on-error # Check if version matching was successful if [ -z "$LATESTSENSOR" ]; then