feat(gcp): add parameter manager support#1808
Conversation
Signed-off-by: Mahesh Patil <maheshfinity@gmail.com>
Signed-off-by: Mahesh Patil <maheshfinity@gmail.com>
…til/java-sdk-contrib into feat/gcp-parameter-manager Signed-off-by: Mahesh Patil <maheshfinity@gmail.com>
Signed-off-by: Mahesh Patil <maheshfinity@gmail.com>
1. add location id to options as gcp params require location 2. fixed parameter manager classes to use common gcp classes post refactor Signed-off-by: Mahesh Patil <maheshfinity@gmail.com>
Signed-off-by: Mahesh Patil <maheshfinity@gmail.com>
Signed-off-by: Mahesh Patil <maheshfinity@gmail.com>
Signed-off-by: Mahesh Patil <maheshfinity@gmail.com>
Signed-off-by: Mahesh Patil <maheshfinity@gmail.com>
There was a problem hiding this comment.
Code Review
This pull request introduces support for Google Cloud Parameter Manager by refactoring the existing GCP provider into a shared AbstractGcpProvider base class and adding the new GcpParameterManagerProvider. It also updates the sample applications, setup/teardown scripts, and tests to support both Secret Manager and Parameter Manager backends. Feedback on these changes highlights several issues: locationId in GcpProviderOptions needs a default value of "global" to prevent null pointer exceptions; the Parameter Manager setup script contains broken update and versioning logic; redundant log statements should be cleaned up; synchronizing on this during blocking network calls creates a performance bottleneck; and the teardown script's deletion chaining is risky and should be decoupled.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| /** | ||
| * Optional location required for ParameterManager, ignored by SecretManager | ||
| */ | ||
| private final String locationId; |
There was a problem hiding this comment.
The locationId field is currently not defaulted or validated. If it is left null, GcpParameterManagerProvider will throw a NullPointerException when building ParameterVersionName because ParameterVersionName.of requires a non-null location. This also causes the sample application ParameterManagerSampleApp to crash out-of-the-box because it does not configure locationId when building GcpProviderOptions.
Defaulting locationId to "global" using @Builder.Default ensures robust out-of-the-box behavior for both the sample app and general usage.
| /** | |
| * Optional location required for ParameterManager, ignored by SecretManager | |
| */ | |
| private final String locationId; | |
| /** | |
| * Optional location required for ParameterManager, ignored by SecretManager | |
| */ | |
| @Builder.Default | |
| private final String locationId = "global"; |
References
- Breaking changes to default behavior can be acceptable for very recently introduced features to allow for architectural improvements before the behavior is widely adopted.
| elif [[ "$BACKEND" == "parameter-manager" ]]; then | ||
| # Create or update Parameter Manager parameter | ||
| if gcloud parametermanager parameters describe "${full_name}" --location=global --project="${PROJECT}" &>/dev/null 2>&1; then | ||
| echo " [EXISTS] ${full_name} — updating value" | ||
| gcloud parametermanager parameters update "${full_name}" \ | ||
| --location=global \ | ||
| --project="${PROJECT}" \ | ||
| --data="${value}" \ | ||
| --quiet || echo " [WARN] Could not update parameter (may require gcloud alpha components)" | ||
| else | ||
| gcloud parametermanager parameters create "${full_name}" \ | ||
| --location=global \ | ||
| --project="${PROJECT}" \ | ||
| --parameter-format=UNFORMATTED \ | ||
| --quiet || echo " [WARN] Could not create parameter (may require gcloud alpha components)" | ||
| echo " [CREATED] ${full_name}" | ||
|
|
||
| gcloud parametermanager parameters versions create VERSION_ID \ | ||
| --parameter="${full_name}" \ | ||
| --project="${PROJECT}" \ | ||
| --location=global \ | ||
| --payload-data="${value}" | ||
|
|
||
| # Add a secret version with the flag value | ||
| echo -n "${value}" | gcloud secrets versions add "${full_name}" \ | ||
| --project="${PROJECT}" \ | ||
| --data-file=- \ | ||
| --quiet | ||
| echo " [VERSION] ${full_name} → ${value}" | ||
|
|
||
| fi | ||
| echo " [SET] ${full_name} → ${value}" | ||
| fi |
There was a problem hiding this comment.
The update and creation logic for Parameter Manager is broken:
gcloud parametermanager parameters updatedoes not support a--dataflag, so the update command will fail with an error.- Parameter Manager parameters are versioned, and the provider fetches the payload from a parameter version (defaulting to
"latest"). Simply updating the parameter metadata (even if it worked) does not create a new version, so the provider would never see the updated value. - The creation logic hardcodes the version ID to
VERSION_ID. If the script is run multiple times, it will fail or fail to update.
To fix this, we should create a new version with a unique ID (e.g., using a timestamp and random suffix) whenever setting or updating a parameter, so that the "latest" alias automatically points to the newest value.
elif [[ "$BACKEND" == "parameter-manager" ]]; then
# Create Parameter Manager parameter if it doesn't exist
if ! gcloud parametermanager parameters describe "${full_name}" --location=global --project="${PROJECT}" &>/dev/null 2>&1; then
gcloud parametermanager parameters create "${full_name}" \
--location=global \
--project="${PROJECT}" \
--parameter-format=UNFORMATTED \
--quiet || echo " [WARN] Could not create parameter"
echo " [CREATED] ${full_name}"
else
echo " [EXISTS] ${full_name}"
fi
# Create a new version with a unique ID so "latest" points to the new value
local version_id="v$(date +%s)_$RANDOM"
gcloud parametermanager parameters versions create "${version_id}" \
--parameter="${full_name}" \
--project="${PROJECT}" \
--location=global \
--payload-data="${value}" \
--quiet || echo " [WARN] Could not create parameter version"
echo " [VERSION] ${full_name} → ${value} (version: ${version_id})"
fi| log.info("Fetching parameter from GCP name '{}'", parameterName); | ||
| ParameterVersionName versionName = ParameterVersionName.of( | ||
| options.getProjectId(), options.getLocationId(), parameterName, options.getVersion()); | ||
| log.info("Fetching parameter from GCP version {}", parameterName); |
There was a problem hiding this comment.
These log statements are redundant and noisy. They log "Fetching parameter from GCP name..." and then immediately log "Fetching parameter from GCP version..." (which incorrectly prints the parameter name instead of the version).
Since GcpSecretManagerProvider does not log on every fetch, these should be removed to avoid polluting production logs and maintain consistency across providers.
ParameterVersionName versionName = ParameterVersionName.of(
options.getProjectId(), options.getLocationId(), parameterName, options.getVersion());| synchronized (this) { | ||
| return cache.get(name).orElseGet(() -> { | ||
| String value = fetchFromGcp(name); | ||
| cache.put(name, value); | ||
| return value; | ||
| }); | ||
| } |
There was a problem hiding this comment.
Synchronizing on this (the entire provider instance) during a blocking network call (fetchFromGcp) is a performance bottleneck. If multiple threads evaluate different feature flags concurrently, they will all block on each other, resulting in high latency and thread starvation.
Consider using a ConcurrentHashMap of locks or a striped lock to synchronize only on a per-key basis rather than globally.
| elif [[ "$BACKEND" == "parameter-manager" ]]; then | ||
| if gcloud parametermanager parameters describe "${full_name}" --location=global --project="${PROJECT}" &>/dev/null 2>&1; then | ||
| for v in $(gcloud parametermanager parameters versions list \ | ||
| --parameter="${full_name}" --location=global \ | ||
| --format="value(name.basename())"); do | ||
| gcloud parametermanager parameters versions delete "$v" \ | ||
| --parameter="${full_name}" --location=global --quiet | ||
| done && \ | ||
|
|
||
| gcloud parametermanager parameters delete "${full_name}" \ | ||
| --location=global \ | ||
| --project="${PROJECT}" \ | ||
| --quiet || echo " [WARN] Could not delete parameter (may require gcloud alpha components)" | ||
| echo " [DELETED] ${full_name}" | ||
| else | ||
| echo " [SKIP] ${full_name} (not found)" | ||
| fi | ||
| fi |
There was a problem hiding this comment.
Chaining the loop with the parameter deletion using && \ is risky. If any version deletion fails (or if there are no versions and the loop exits with a non-zero status in some shells), the parameter itself will not be deleted. It is safer to run them as separate commands and add error tolerance. Additionally, the indentation inside the for loop should be corrected.
| elif [[ "$BACKEND" == "parameter-manager" ]]; then | |
| if gcloud parametermanager parameters describe "${full_name}" --location=global --project="${PROJECT}" &>/dev/null 2>&1; then | |
| for v in $(gcloud parametermanager parameters versions list \ | |
| --parameter="${full_name}" --location=global \ | |
| --format="value(name.basename())"); do | |
| gcloud parametermanager parameters versions delete "$v" \ | |
| --parameter="${full_name}" --location=global --quiet | |
| done && \ | |
| gcloud parametermanager parameters delete "${full_name}" \ | |
| --location=global \ | |
| --project="${PROJECT}" \ | |
| --quiet || echo " [WARN] Could not delete parameter (may require gcloud alpha components)" | |
| echo " [DELETED] ${full_name}" | |
| else | |
| echo " [SKIP] ${full_name} (not found)" | |
| fi | |
| fi | |
| elif [[ "$BACKEND" == "parameter-manager" ]]; then | |
| if gcloud parametermanager parameters describe "${full_name}" --location=global --project="${PROJECT}" &>/dev/null 2>&1; then | |
| for v in $(gcloud parametermanager parameters versions list \ | |
| --parameter="${full_name}" --location=global \ | |
| --format="value(name.basename())" 2>/dev/null || true); do | |
| gcloud parametermanager parameters versions delete "$v" \ | |
| --parameter="${full_name}" --location=global --quiet || true | |
| done | |
| gcloud parametermanager parameters delete "${full_name}" \ | |
| --location=global \ | |
| --project="${PROJECT}" \ | |
| --quiet || echo " [WARN] Could not delete parameter" | |
| echo " [DELETED] ${full_name}" | |
| else | |
| echo " [SKIP] ${full_name} (not found)" | |
| fi | |
| fi |
Signed-off-by: Mahesh Patil <maheshfinity@gmail.com>
… of creating new versoin2. locationId default to prevent null pointers3. update references to stale GcpParameterManagerOptions Signed-off-by: Mahesh Patil <maheshfinity@gmail.com>
This PR
Adds a GCP Parameter Manager support to the GCP Provider that reads feature flags from GCP Parameter Manager config.
Includes updates to sample application under samples/gcp/ with setup/teardown script
Package: providers/gcp
Class: GcpParameterManagerProvider
Supports all OpenFeature flag types via structured or plain-text secret values
Configurable poll interval and GCP project settings
Related Issues
Fixes GCP services covered under #1420
Notes
This builds on top of the previous PR that added support for GCP Secret manager.
Follow-up Tasks
How to test