From 853ce15aa2e19951afd47086bcde61f7d6966256 Mon Sep 17 00:00:00 2001 From: Andre Azzolini Date: Thu, 20 Mar 2025 11:00:28 -0600 Subject: [PATCH 1/3] Add periodic liveness ping during doppler run --- pkg/cmd/run.go | 21 +++++++++++++++++++++ pkg/controllers/configs.go | 11 +++++++++++ pkg/http/api.go | 18 ++++++++++++++++++ 3 files changed, 50 insertions(+) diff --git a/pkg/cmd/run.go b/pkg/cmd/run.go index 40c2e0b4..e86cd5ef 100644 --- a/pkg/cmd/run.go +++ b/pkg/cmd/run.go @@ -41,6 +41,7 @@ import ( var defaultFallbackDir string const defaultFallbackFileMaxAge = 14 * 24 * time.Hour // 14 days +const defaultLivenessPingIntervalSeconds = 60 * 5 * time.Second var secretsToInclude []string @@ -223,6 +224,25 @@ doppler run --mount secrets.json -- cat secrets.json`, // this variable has the potential to be racey, but is made safe by our use of the mutex terminatedByWatch := false + startLivenessPing := func() { + ticker := time.NewTicker(defaultLivenessPingIntervalSeconds) + + go func() { + for { + select { + case <-ticker.C: + _, err := controllers.LivenessPing(localConfig) + if !err.IsNil() { + // If we fail the liveness ping, we'll just log it for debugging, but it's likely an intermittent + // connectivity error. We'll allow the ticker to continue. + utils.LogDebug(fmt.Sprintf("Error pinging for liveness \"%s\"", err)) + } + } + } + }() + + } + startProcess := func() { // ensure we can fetch the new secrets before restarting the process secrets := controllers.FetchSecrets(localConfig, enableCache, fallbackOpts, metadataPath, nameTransformer, dynamicSecretsTTL, format, secretsToInclude) @@ -365,6 +385,7 @@ doppler run --mount secrets.json -- cat secrets.json`, } startProcess() + startLivenessPing() // initiate watch logic after starting the process so that failing to watch just degrades to normal 'run' behavior if watch { diff --git a/pkg/controllers/configs.go b/pkg/controllers/configs.go index 4dffb885..08f29b57 100644 --- a/pkg/controllers/configs.go +++ b/pkg/controllers/configs.go @@ -89,3 +89,14 @@ func GetEnvironmentIDs(config models.ScopedOptions) ([]string, Error) { } return ids, Error{} } + +func LivenessPing(config models.ScopedOptions) (bool, Error) { + utils.RequireValue("token", config.Token.Value) + + _, err := http.LivenessPing(config.APIHost.Value, utils.GetBool(config.VerifyTLS.Value, true), config.Token.Value, config.EnclaveProject.Value, config.EnclaveConfig.Value) + if !err.IsNil() { + return false, Error{Err: err.Unwrap(), Message: err.Message} + } + + return true, Error{} +} diff --git a/pkg/http/api.go b/pkg/http/api.go index c2d5af41..06cb4bf3 100644 --- a/pkg/http/api.go +++ b/pkg/http/api.go @@ -911,6 +911,24 @@ func GetConfig(host string, verifyTLS bool, apiKey string, project string, confi return info, Error{} } +func LivenessPing(host string, verifyTLS bool, apiKey string, project string, config string) (bool, Error) { + var params []queryParam + params = append(params, queryParam{Key: "project", Value: project}) + params = append(params, queryParam{Key: "config", Value: config}) + + url, err := generateURL(host, "/v3/configs/config/ping", params) + if err != nil { + return false, Error{Err: err, Message: "Unable to generate url"} + } + + statusCode, _, _, err := GetRequest(url, verifyTLS, apiKeyHeader(apiKey)) + if err != nil { + return false, Error{Err: err, Message: "Unable to liveness ping", Code: statusCode} + } + + return true, Error{} +} + // CreateConfig create a config func CreateConfig(host string, verifyTLS bool, apiKey string, project string, name string, environment string) (models.ConfigInfo, Error) { postBody := map[string]interface{}{"name": name, "environment": environment} From e55299cb18330e5c4ed1bf5151fa943e4b83025b Mon Sep 17 00:00:00 2001 From: Andre Azzolini Date: Thu, 20 Mar 2025 11:00:36 -0600 Subject: [PATCH 2/3] chore: Fix formatting --- pkg/http/api.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/pkg/http/api.go b/pkg/http/api.go index 06cb4bf3..4ce4aaa7 100644 --- a/pkg/http/api.go +++ b/pkg/http/api.go @@ -154,12 +154,11 @@ func RevokeAuthToken(host string, verifyTLS bool, token string) (map[string]inte return result, Error{} } - // GetOIDCAuthToken get a short lived service account identity auth token from an OIDC token func GetOIDCAuthToken(host string, verifyTLS bool, identityId string, oidcJWT string) (map[string]interface{}, Error) { reqBody := map[string]interface{}{} - reqBody["identity"] = identityId - reqBody["token"] = oidcJWT + reqBody["identity"] = identityId + reqBody["token"] = oidcJWT body, err := json.Marshal(reqBody) if err != nil { return nil, Error{Err: err, Message: "Invalid OIDC auth token"} @@ -184,9 +183,8 @@ func GetOIDCAuthToken(host string, verifyTLS bool, identityId string, oidcJWT st return result, Error{} } - // RevokeIdentityAuthToken revoke a short lived service account identity auth token -func RevokeIdentityAuthToken(host string, verifyTLS bool, token string) (Error) { +func RevokeIdentityAuthToken(host string, verifyTLS bool, token string) Error { reqBody := map[string]interface{}{} reqBody["token"] = token body, err := json.Marshal(reqBody) From 39e270025bcbe665af3403ff57f2d0915a61e9a3 Mon Sep 17 00:00:00 2001 From: Andre Azzolini Date: Thu, 20 Mar 2025 11:01:12 -0600 Subject: [PATCH 3/3] Add --no-liveness-ping flag to disable periodic ping --- pkg/cmd/run.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pkg/cmd/run.go b/pkg/cmd/run.go index e86cd5ef..a82a9cc5 100644 --- a/pkg/cmd/run.go +++ b/pkg/cmd/run.go @@ -81,6 +81,7 @@ doppler run --mount secrets.json -- cat secrets.json`, exitOnWriteFailure := !utils.GetBoolFlag(cmd, "no-exit-on-write-failure") preserveEnv := cmd.Flag("preserve-env").Value.String() forwardSignals := utils.GetBoolFlag(cmd, "forward-signals") + enableLivenessPing := !fallbackOnly && !utils.GetBoolFlag(cmd, "no-liveness-ping") localConfig := configuration.LocalConfig(cmd) dynamicSecretsTTL := utils.GetDurationFlag(cmd, "dynamic-ttl") exitOnMissingIncludedSecrets := !cmd.Flags().Changed("no-exit-on-missing-only-secrets") @@ -385,7 +386,10 @@ doppler run --mount secrets.json -- cat secrets.json`, } startProcess() - startLivenessPing() + + if enableLivenessPing { + startLivenessPing() + } // initiate watch logic after starting the process so that failing to watch just degrades to normal 'run' behavior if watch { @@ -614,9 +618,10 @@ func init() { runCmd.Flags().Bool("no-cache", false, "disable using the fallback file to speed up fetches. the fallback file is only used when the API indicates that it's still current.") runCmd.Flags().Bool("no-fallback", false, "disable reading and writing the fallback file (implies --no-cache)") runCmd.Flags().Bool("fallback-readonly", false, "disable modifying the fallback file. secrets can still be read from the file.") - runCmd.Flags().Bool("fallback-only", false, "read all secrets directly from the fallback file, without contacting Doppler. secrets will not be updated. (implies --fallback-readonly)") + runCmd.Flags().Bool("fallback-only", false, "read all secrets directly from the fallback file, without contacting Doppler. secrets will not be updated. (implies --fallback-readonly and --no-liveness-ping)") runCmd.Flags().Bool("no-exit-on-write-failure", false, "do not exit if unable to write the fallback file") runCmd.Flags().Bool("forward-signals", forwardSignals, "forward signals to the child process (defaults to false when STDOUT is a TTY)") + runCmd.Flags().Bool("no-liveness-ping", false, "disable the periodic liveness ping") // secrets mount flags runCmd.Flags().String("mount", "", "write secrets to an ephemeral file, accessible at DOPPLER_CLI_SECRETS_PATH. when enabled, secrets are NOT injected into the environment") runCmd.Flags().String("mount-format", "json", fmt.Sprintf("file format to use. if not specified, will be auto-detected from mount name. one of %v", models.SecretsMountFormats))