diff --git a/README.md b/README.md index 13a35e6f..dbeb0522 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,11 @@ cloudrunner PORT int cloudrunner K_SERVICE string cloudrunner K_REVISION string cloudrunner K_CONFIGURATION string +cloudrunner CLOUD_RUN_JOB string +cloudrunner CLOUD_RUN_EXECUTION string +cloudrunner CLOUD_RUN_TASK_INDEX int +cloudrunner CLOUD_RUN_TASK_ATTEMPT int +cloudrunner CLOUD_RUN_TASK_COUNT int cloudrunner GOOGLE_CLOUD_PROJECT string cloudrunner RUNTIME_SERVICEACCOUNT string cloudrunner SERVICE_VERSION string diff --git a/cloudruntime/config.go b/cloudruntime/config.go index 5489181f..99c9b717 100644 --- a/cloudruntime/config.go +++ b/cloudruntime/config.go @@ -25,6 +25,16 @@ type Config struct { Revision string `env:"K_REVISION"` // Configuration of the service, as assigned by a Knative runtime. Configuration string `env:"K_CONFIGURATION"` + // Job name, if running as a Cloud Run job. + Job string `env:"CLOUD_RUN_JOB"` + // Execution name, if running as a Cloud Run job. + Execution string `env:"CLOUD_RUN_EXECUTION"` + // TaskIndex of the current task, if running as a Cloud Run job. + TaskIndex int `env:"CLOUD_RUN_TASK_INDEX"` + // TaskAttempt of the current task, if running as a Cloud Run job. + TaskAttempt int `env:"CLOUD_RUN_TASK_ATTEMPT"` + // TaskCount of the job, if running as a Cloud Run job. + TaskCount int `env:"CLOUD_RUN_TASK_COUNT"` // ProjectID is the GCP project ID the service is running in. // In production, defaults to the project where the service is deployed. ProjectID string `env:"GOOGLE_CLOUD_PROJECT"` @@ -55,5 +65,20 @@ func (c *Config) Autodetect() error { if configuration, ok := Configuration(); ok { c.Configuration = configuration } + if job, ok := Job(); ok { + c.Job = job + } + if execution, ok := Execution(); ok { + c.Execution = execution + } + if taskIndex, ok := TaskIndex(); ok { + c.TaskIndex = taskIndex + } + if taskAttempt, ok := TaskAttempt(); ok { + c.TaskAttempt = taskAttempt + } + if taskCount, ok := TaskCount(); ok { + c.TaskCount = taskCount + } return nil } diff --git a/cloudruntime/env.go b/cloudruntime/env.go index 200169ad..b6ec589f 100644 --- a/cloudruntime/env.go +++ b/cloudruntime/env.go @@ -30,3 +30,35 @@ func Port() (int, bool) { port, err := strconv.Atoi(os.Getenv("PORT")) return port, err == nil } + +// Job returns the name of the Cloud Run job being run. +func Job() (string, bool) { + return os.LookupEnv("CLOUD_RUN_JOB") +} + +// Execution returns the name of the Cloud Run job execution being run. +func Execution() (string, bool) { + return os.LookupEnv("CLOUD_RUN_EXECUTION") +} + +// TaskIndex returns the index of the Cloud Run job task being run. +// Starts at 0 for the first task and increments by 1 for every successive task, +// up to the maximum number of tasks minus 1. +func TaskIndex() (int, bool) { + taskIndex, err := strconv.Atoi(os.Getenv("CLOUD_RUN_TASK_INDEX")) + return taskIndex, err == nil +} + +// TaskAttempt returns the number of time this Cloud Run job task tas been retried. +// Starts at 0 for the first attempt and increments by 1 for every successive retry, +// up to the maximum retries value. +func TaskAttempt() (int, bool) { + taskIndex, err := strconv.Atoi(os.Getenv("CLOUD_RUN_TASK_ATTEMPT")) + return taskIndex, err == nil +} + +// TaskCount returns the number of tasks in the current Cloud Run job. +func TaskCount() (int, bool) { + taskIndex, err := strconv.Atoi(os.Getenv("CLOUD_RUN_TASK_COUNT")) + return taskIndex, err == nil +} diff --git a/cloudruntime/env_test.go b/cloudruntime/env_test.go index 42b34c08..bb424a1d 100644 --- a/cloudruntime/env_test.go +++ b/cloudruntime/env_test.go @@ -79,6 +79,107 @@ func TestPort(t *testing.T) { }) } +func TestJob(t *testing.T) { + t.Run("from env", func(t *testing.T) { + const expected = "foo" + setEnv(t, "CLOUD_RUN_JOB", expected) + actual, ok := Job() + assert.Assert(t, ok) + assert.Equal(t, expected, actual) + }) + + t.Run("undefined", func(t *testing.T) { + actual, ok := Job() + assert.Assert(t, !ok) + assert.Equal(t, "", actual) + }) +} + +func TestExecution(t *testing.T) { + t.Run("from env", func(t *testing.T) { + const expected = "foo" + setEnv(t, "CLOUD_RUN_EXECUTION", expected) + actual, ok := Execution() + assert.Assert(t, ok) + assert.Equal(t, expected, actual) + }) + + t.Run("undefined", func(t *testing.T) { + actual, ok := Execution() + assert.Assert(t, !ok) + assert.Equal(t, "", actual) + }) +} + +func TestTaskIndex(t *testing.T) { + t.Run("from env", func(t *testing.T) { + const expected = 42 + setEnv(t, "CLOUD_RUN_TASK_INDEX", strconv.Itoa(expected)) + actual, ok := TaskIndex() + assert.Assert(t, ok) + assert.Equal(t, expected, actual) + }) + + t.Run("invalid", func(t *testing.T) { + setEnv(t, "CLOUD_RUN_TASK_INDEX", "invalid") + actual, ok := TaskIndex() + assert.Assert(t, !ok) + assert.Equal(t, 0, actual) + }) + + t.Run("undefined", func(t *testing.T) { + actual, ok := TaskIndex() + assert.Assert(t, !ok) + assert.Equal(t, 0, actual) + }) +} + +func TestTaskAttempt(t *testing.T) { + t.Run("from env", func(t *testing.T) { + const expected = 42 + setEnv(t, "CLOUD_RUN_TASK_ATTEMPT", strconv.Itoa(expected)) + actual, ok := TaskAttempt() + assert.Assert(t, ok) + assert.Equal(t, expected, actual) + }) + + t.Run("invalid", func(t *testing.T) { + setEnv(t, "CLOUD_RUN_TASK_ATTEMPT", "invalid") + actual, ok := TaskAttempt() + assert.Assert(t, !ok) + assert.Equal(t, 0, actual) + }) + + t.Run("undefined", func(t *testing.T) { + actual, ok := TaskAttempt() + assert.Assert(t, !ok) + assert.Equal(t, 0, actual) + }) +} + +func TestTaskCount(t *testing.T) { + t.Run("from env", func(t *testing.T) { + const expected = 42 + setEnv(t, "CLOUD_RUN_TASK_COUNT", strconv.Itoa(expected)) + actual, ok := TaskCount() + assert.Assert(t, ok) + assert.Equal(t, expected, actual) + }) + + t.Run("invalid", func(t *testing.T) { + setEnv(t, "CLOUD_RUN_TASK_COUNT", "invalid") + actual, ok := TaskCount() + assert.Assert(t, !ok) + assert.Equal(t, 0, actual) + }) + + t.Run("undefined", func(t *testing.T) { + actual, ok := TaskCount() + assert.Assert(t, !ok) + assert.Equal(t, 0, actual) + }) +} + // setEnv will be available in the standard library from Go 1.17 as t.SetEnv. func setEnv(t *testing.T, key, value string) { prevValue, ok := os.LookupEnv(key)