From d81ac463e05cfbf09dede5982716b46bc0b13229 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emre=20=C3=96nal?= Date: Thu, 16 Nov 2023 20:17:08 +0300 Subject: [PATCH] chore: improve code --- README.md | 97 ++++++++++++--------------- cmd/main.go | 14 ++-- examples/.gitlab-ci.yml | 1 - examples/functions/advanced.js | 7 ++ examples/functions/advanced.json | 85 +++++++++++++++++++++++ examples/functions/basic.json | 2 +- examples/functions/withenv.js | 7 ++ examples/functions/withenv.prod.json | 18 +++++ examples/functions/withenv.stage.json | 18 +++++ examples/functions/withvariable.js | 7 ++ examples/functions/withvariable.json | 18 +++++ internal/action/upsert.go | 2 +- internal/logger/logger.go | 2 +- internal/model/environment.go | 35 ++-------- 14 files changed, 220 insertions(+), 93 deletions(-) create mode 100644 examples/functions/advanced.js create mode 100644 examples/functions/advanced.json create mode 100644 examples/functions/withenv.js create mode 100644 examples/functions/withenv.prod.json create mode 100644 examples/functions/withenv.stage.json create mode 100644 examples/functions/withvariable.js create mode 100644 examples/functions/withvariable.json diff --git a/README.md b/README.md index 000ff07..afe68b2 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ Update function settings ```bash nano ./examples/functions/basic.json ``` +> Note: JavaScript eventing code file is found automatically. Update function file ```bash @@ -58,21 +59,11 @@ Set the environment variable `CONFIG_FILE` to the path of the `basic.json` confi export CONFIG_FILE=./examples/functions/basic.json ``` -Set the environment variable `CI_COMMIT_SHORT_SHA` to the value `foo`, representing a short SHA identifier -```bash -export CI_COMMIT_SHORT_SHA=foo -``` - Set the environment variable `CI_COMMIT_AUTHOR` to the value `foo`, representing the author of the commit ```bash export CI_COMMIT_AUTHOR=foo ``` -Set the environment variable `EXECUTION_TIMEOUT` to `3m`, representing a timeout of 3 minutes -```bash -export EXECUTION_TIMEOUT=3m -``` - Run the Go program located in the `cmd` directory ```bash go run ./cmd @@ -93,46 +84,46 @@ docker run --platform=linux/amd64 ghcr.io/trendyol/cbef:latest-amd64 ## Function Configurations > Configurations that can be used in files with `.json` extension located in the functions folder -| Field | Description | Value Type | Required | Options | -|------------------------------------------|--------------------------------------------------------------|:------------:|:--------:|------------------------------------| -| cluster | Couchbase connection configurations | object | ✅ | | -| cluster.connection_string | Couchbase cluster connection string | string | ✅ | | -| cluster.user | Username for cluster authentication | string | ✅ | | -| cluster.pass | Password for cluster authentication | string | ✅ | | -| name | Function name | string | ✅ | | -| metadata_keyspace | Metadata keyspace configurations | object | ✅ | | -| metadata_keyspace.bucket | Metadata bucket name | string | ✅ | | -| metadata_keyspace.scope | Metadata scope name | string | ✅ | | -| metadata_keyspace.collection | Metadata collection name | string | ✅ | | -| source_keyspace | Source keyspace configurations | object | ✅ | | -| source_keyspace.bucket | Source bucket name | string | ✅ | | -| source_keyspace.scope | Source scope name | string | ✅ | | -| source_keyspace.collection | Source collection name | string | ✅ | | -| bucket_bindings | Bucket bindings configurations | object array | ❌ | | -| bucket_bindings.alias | Alias for bucket binding | string | ❌ | | -| bucket_bindings.bucket | Target bucket name for binding | string | ❌ | | -| bucket_bindings.scope | Target scope name for binding | string | ❌ | | -| bucket_bindings.collection | Target collection name for binding | string | ❌ | | -| bucket_bindings.access | Access level for binding | string | ❌ | r (read-only), rw (read-write) | -| url_bindings | URL bindings configurations | object array | ❌ | | -| bucket_bindings.hostname | Hostname for URL binding | string | ❌ | | -| bucket_bindings.alias | Alias for URL binding | string | ❌ | | -| bucket_bindings.allow_cookies | Allow cookies for URL binding | bool | ❌ | | -| bucket_bindings.validate_ssl_certificate | Validate SSL certificate for URL binding | bool | ❌ | | -| bucket_bindings.auth | Authentication configurations for URL binding | object | ❌ | | -| bucket_bindings.auth.type | Authentication type for URL binding | string | ❌ | basic, digest, bearer | -| bucket_bindings.auth.user | Username for URL binding authentication | string | ❌ | | -| bucket_bindings.auth.pass | Password for URL binding authentication | string | ❌ | | -| bucket_bindings.auth.token | Token for URL binding authentication | string | ❌ | | -| constant_bindings | Constant bindings configurations | object array | ❌ | | -| constant_bindings.alias | Alias for constant binding | string | ❌ | | -| constant_bindings.literal | Literal value for constant binding | string | ❌ | | -| settings | Function configurations | object | ❌ | | -| settings.dcp_stream_boundary | The preferred deployment time feed boundary for the function | string | ❌ | everything, from_now | -| settings.description | Description for function | string | ❌ | | -| settings.log_level | Granularity of system events being captured in the log | string | ❌ | INFO, ERROR, WARNING, DEBUG, TRACE | -| settings.query_consistency | Consistency level of N1QL statements in the function | uint | ❌ | 1 (NotBounded), 2 (RequestPlus) | -| settings.worker_count | Number of workers per node to process the events | uint | ❌ | | -| settings.language_compatibility | Language compatibility of the function | string | ❌ | 6.0.0, 6.5.0, 6.6.2 | -| settings.execution_timeout | Time after which the function's execution will be timed out | uint | ❌ | | -| settings.timer_context_size | Maximum allowed value of the timer context size in bytes | uint | ❌ | | +| Field | Description | Value Type | Required | Options | +|------------------------------------------|--------------------------------------------------------------|:--------------:|:--------:|------------------------------------| +| cluster | Couchbase connection configurations | object | ✅ | | +| cluster.connection_string | Couchbase cluster connection string | string | ✅ | env supported with {{ENV}} | +| cluster.user | Username for cluster authentication | string | ✅ | env supported with {{ENV}} | +| cluster.pass | Password for cluster authentication | string | ✅ | env supported with {{ENV}} | +| name | Function name | string | ✅ | | +| metadata_keyspace | Metadata keyspace configurations | object | ✅ | | +| metadata_keyspace.bucket | Metadata bucket name | string | ✅ | | +| metadata_keyspace.scope | Metadata scope name | string | ✅ | | +| metadata_keyspace.collection | Metadata collection name | string | ✅ | | +| source_keyspace | Source keyspace configurations | object | ✅ | | +| source_keyspace.bucket | Source bucket name | string | ✅ | | +| source_keyspace.scope | Source scope name | string | ✅ | | +| source_keyspace.collection | Source collection name | string | ✅ | | +| bucket_bindings | Bucket bindings configurations | object array | ❌ | | +| bucket_bindings.alias | Alias for bucket binding | string | ❌ | | +| bucket_bindings.bucket | Target bucket name for binding | string | ❌ | | +| bucket_bindings.scope | Target scope name for binding | string | ❌ | | +| bucket_bindings.collection | Target collection name for binding | string | ❌ | | +| bucket_bindings.access | Access level for binding | string | ❌ | r (read-only), rw (read-write) | +| url_bindings | URL bindings configurations | object array | ❌ | | +| bucket_bindings.hostname | Hostname for URL binding | string | ❌ | | +| bucket_bindings.alias | Alias for URL binding | string | ❌ | | +| bucket_bindings.allow_cookies | Allow cookies for URL binding | bool | ❌ | | +| bucket_bindings.validate_ssl_certificate | Validate SSL certificate for URL binding | bool | ❌ | | +| bucket_bindings.auth | Authentication configurations for URL binding | object | ❌ | | +| bucket_bindings.auth.type | Authentication type for URL binding | string | ❌ | basic, digest, bearer | +| bucket_bindings.auth.user | Username for URL binding authentication | string | ❌ | | +| bucket_bindings.auth.pass | Password for URL binding authentication | string | ❌ | | +| bucket_bindings.auth.token | Token for URL binding authentication | string | ❌ | | +| constant_bindings | Constant bindings configurations | object array | ❌ | | +| constant_bindings.alias | Alias for constant binding | string | ❌ | | +| constant_bindings.literal | Literal value for constant binding | string | ❌ | | +| settings | Function configurations | object | ❌ | | +| settings.dcp_stream_boundary | The preferred deployment time feed boundary for the function | string | ❌ | everything, from_now | +| settings.description | Description for function | string | ❌ | | +| settings.log_level | Granularity of system events being captured in the log | string | ❌ | INFO, ERROR, WARNING, DEBUG, TRACE | +| settings.query_consistency | Consistency level of N1QL statements in the function | uint | ❌ | 1 (NotBounded), 2 (RequestPlus) | +| settings.worker_count | Number of workers per node to process the events | uint | ❌ | | +| settings.language_compatibility | Language compatibility of the function | string | ❌ | 6.0.0, 6.5.0, 6.6.2 | +| settings.execution_timeout | Time after which the function's execution will be timed out | uint (seconds) | ❌ | | +| settings.timer_context_size | Maximum allowed value of the timer context size in bytes | uint | ❌ | | diff --git a/cmd/main.go b/cmd/main.go index 2fae12a..9bd12e0 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -15,7 +15,7 @@ import ( ) const ( - connectTimeout = 5 * time.Second + executionTimeout = 3 * time.Minute processTimeout = 10 * time.Second codeAuditComment = "// Created by %s on %s via github.com/trendyol/cbef.\n\n%s" ) @@ -25,20 +25,20 @@ func main() { env, err := model.NewEnvironment() if err != nil { - log.Fatal(err.Error()) + log.Fatal("failed to load environment", "error", err.Error()) } f, err := config.Parse(env.ConfigFile) if err != nil { - log.Fatal(err.Error()) + log.Fatal("failed to parse config file", "error", err.Error()) } - ctx, cancel := context.WithTimeout(context.Background(), env.ExecutionTimeout) + ctx, cancel := context.WithTimeout(context.Background(), executionTimeout) defer cancel() - cluster, err := couchbase.Connect(ctx, connectTimeout, f.Cluster) + cluster, err := couchbase.Connect(ctx, processTimeout, f.Cluster) if err != nil { - log.Fatal(err.Error()) //nolint:gocritic + log.Fatal("failed to connect couchbase", "error", err.Error()) //nolint:gocritic } code, err := os.ReadFile(filepath.Join(filepath.Dir(env.ConfigFile), f.Name+".js")) @@ -62,6 +62,6 @@ func main() { } if err = act.Upsert(ctx, f); err != nil { - log.Fatal("failed to upsert function", "error", err.Error(), "function", f.Name) + log.Fatal("failed to upsert function", "error", err.Error()) } } diff --git a/examples/.gitlab-ci.yml b/examples/.gitlab-ci.yml index 6730ba0..7d5c2dd 100644 --- a/examples/.gitlab-ci.yml +++ b/examples/.gitlab-ci.yml @@ -25,7 +25,6 @@ Prepare: script: - ln -s /cbef \${CI_PROJECT_DIR}/cbef - export CONFIG_FILE=./functions/\${FUNCTION}.json - - export EXECUTION_TIMEOUT=3m - ./cbef EOF diff --git a/examples/functions/advanced.js b/examples/functions/advanced.js new file mode 100644 index 0000000..e42861c --- /dev/null +++ b/examples/functions/advanced.js @@ -0,0 +1,7 @@ +function OnUpdate(doc, meta) { + log("Doc created/updated", meta.id); +} + +function OnDelete(meta, options) { + log("Doc deleted/expired", meta.id); +} diff --git a/examples/functions/advanced.json b/examples/functions/advanced.json new file mode 100644 index 0000000..ddb8210 --- /dev/null +++ b/examples/functions/advanced.json @@ -0,0 +1,85 @@ +{ + "name": "advanced", + "cluster": { + "connection_string": "localhost", + "user": "foo", + "pass": "bar" + }, + "source_keyspace": { + "bucket": "foo", + "scope": "_default", + "collection": "_default" + }, + "metadata_keyspace": { + "bucket": "bar", + "scope": "_default", + "collection": "_default" + }, + "bucket_bindings": [ + { + "alias": "foobucket", + "bucket": "foobucket", + "scope": "fooscope", + "collection": "foocollection", + "access": "rw" + }, + { + "alias": "barbucket", + "bucket": "barbucket", + "scope": "barscope", + "collection": "barcollection", + "access": "r" + } + ], + "url_bindings": [ + { + "hostname": "https://www.trendyol.com", + "alias": "trendyol", + "allow_cookies": true, + "validate_ssl_certificate": true, + "auth": { + "type": "basic", + "user": "foo", + "pass": "bar" + } + }, + { + "hostname": "https://github.com", + "alias": "github", + "allow_cookies": true, + "validate_ssl_certificate": true, + "auth": { + "type": "digest", + "user": "foo", + "pass": "bar" + } + }, + { + "hostname": "https://www.google.com", + "alias": "google", + "allow_cookies": true, + "validate_ssl_certificate": true, + "auth": { + "type": "bearer", + "token": "foo", + "pass": "bar" + } + } + ], + "constant_bindings": [ + { + "alias": "foo", + "literal": "bar" + } + ], + "settings": { + "dcp_stream_boundary": "from_now", + "description": "foo", + "log_level": "TRACE", + "query_consistency": 2, + "worker_count": 5, + "language_compatibility": "6.6.2", + "execution_timeout": 60, + "timer_context_size": 1024 + } +} diff --git a/examples/functions/basic.json b/examples/functions/basic.json index 539b12a..f160bcd 100644 --- a/examples/functions/basic.json +++ b/examples/functions/basic.json @@ -1,7 +1,7 @@ { "name": "basic", "cluster": { - "connection_string": "couchbase://localhost", + "connection_string": "localhost", "user": "foo", "pass": "bar" }, diff --git a/examples/functions/withenv.js b/examples/functions/withenv.js new file mode 100644 index 0000000..e42861c --- /dev/null +++ b/examples/functions/withenv.js @@ -0,0 +1,7 @@ +function OnUpdate(doc, meta) { + log("Doc created/updated", meta.id); +} + +function OnDelete(meta, options) { + log("Doc deleted/expired", meta.id); +} diff --git a/examples/functions/withenv.prod.json b/examples/functions/withenv.prod.json new file mode 100644 index 0000000..631b4eb --- /dev/null +++ b/examples/functions/withenv.prod.json @@ -0,0 +1,18 @@ +{ + "name": "withenv", + "cluster": { + "connection_string": "{{PROD_CB_HOST}}", + "user": "{{PROD_CB_USER}}", + "pass": "{{PROD_CB_PASS}}" + }, + "source_keyspace": { + "bucket": "foo", + "scope": "_default", + "collection": "_default" + }, + "metadata_keyspace": { + "bucket": "bar", + "scope": "_default", + "collection": "_default" + } +} diff --git a/examples/functions/withenv.stage.json b/examples/functions/withenv.stage.json new file mode 100644 index 0000000..b8303bb --- /dev/null +++ b/examples/functions/withenv.stage.json @@ -0,0 +1,18 @@ +{ + "name": "withenv", + "cluster": { + "connection_string": "{{STAGE_CB_HOST}}", + "user": "{{STAGE_CB_USER}}", + "pass": "{{STAGE_CB_PASS}}" + }, + "source_keyspace": { + "bucket": "foo", + "scope": "_default", + "collection": "_default" + }, + "metadata_keyspace": { + "bucket": "bar", + "scope": "_default", + "collection": "_default" + } +} diff --git a/examples/functions/withvariable.js b/examples/functions/withvariable.js new file mode 100644 index 0000000..e42861c --- /dev/null +++ b/examples/functions/withvariable.js @@ -0,0 +1,7 @@ +function OnUpdate(doc, meta) { + log("Doc created/updated", meta.id); +} + +function OnDelete(meta, options) { + log("Doc deleted/expired", meta.id); +} diff --git a/examples/functions/withvariable.json b/examples/functions/withvariable.json new file mode 100644 index 0000000..1827ec8 --- /dev/null +++ b/examples/functions/withvariable.json @@ -0,0 +1,18 @@ +{ + "name": "withvariable", + "cluster": { + "connection_string": "{{FOO_ENV_VAR}}", + "user": "{{BAR_ENV_VAR}}", + "pass": "{{ANY_ENV_VAR}}" + }, + "source_keyspace": { + "bucket": "foo", + "scope": "_default", + "collection": "_default" + }, + "metadata_keyspace": { + "bucket": "bar", + "scope": "_default", + "collection": "_default" + } +} diff --git a/internal/action/upsert.go b/internal/action/upsert.go index 5a4630c..e7aadec 100644 --- a/internal/action/upsert.go +++ b/internal/action/upsert.go @@ -27,10 +27,10 @@ func (a *action) Upsert(ctx context.Context, f *model.Function) error { DCPStreamBoundary: gocb.EventingFunctionDCPBoundary(f.Settings.DCPStreamBoundary), Description: f.Settings.Description, LogLevel: gocb.EventingFunctionLogLevel(f.Settings.LogLevel), + ExecutionTimeout: f.Settings.ExecutionTimeout, QueryConsistency: gocb.QueryScanConsistency(f.Settings.QueryConsistency), WorkerCount: int(f.Settings.WorkerCount), LanguageCompatibility: gocb.EventingFunctionLanguageCompatibility(f.Settings.LanguageCompatibility), - ExecutionTimeout: f.Settings.ExecutionTimeout, TimerContextSize: int(f.Settings.TimerContextSize), DeploymentStatus: gocb.EventingFunctionDeploymentStatusDeployed, ProcessingStatus: gocb.EventingFunctionProcessingStatusRunning, diff --git a/internal/logger/logger.go b/internal/logger/logger.go index 337a64f..cb37f44 100644 --- a/internal/logger/logger.go +++ b/internal/logger/logger.go @@ -22,6 +22,6 @@ func New() *Logger { // Fatal logs fatal reasons with exit. func (l *Logger) Fatal(msg string, args ...any) { - l.Error(msg, args) //nolint:all + l.Error(msg, args...) os.Exit(1) } diff --git a/internal/model/environment.go b/internal/model/environment.go index ed0deee..61bc5b8 100644 --- a/internal/model/environment.go +++ b/internal/model/environment.go @@ -5,48 +5,25 @@ import ( "fmt" "os" "strings" - "time" ) // Environment represents application configurations. type Environment struct { - ConfigFile string - ExecutionTimeout time.Duration - CommitAuthor string + ConfigFile string + CommitAuthor string } // NewEnvironment creates a Environment struct via environment vriables. func NewEnvironment() (*Environment, error) { - e := &Environment{} - if err := e.fill(); err != nil { - return nil, fmt.Errorf("fill environment variables: %s", err.Error()) - } - - if err := e.validate(); err != nil { - return nil, fmt.Errorf("validate environment variables: %s", err.Error()) - } - - return e, nil -} - -// fill fills and validates Environment struct via environment variables. -func (e *Environment) fill() error { + var e Environment e.ConfigFile = os.Getenv("CONFIG_FILE") e.CommitAuthor = os.Getenv("CI_COMMIT_AUTHOR") - timeout := os.Getenv("EXECUTION_TIMEOUT") - - if len(timeout) == 0 { - timeout = "3m" - } - t, err := time.ParseDuration(timeout) - if err != nil { - return fmt.Errorf("failed to parse execution timeout duration: %w", err) + if err := e.validate(); err != nil { + return nil, fmt.Errorf("validate environment variables: %s", err.Error()) } - e.ExecutionTimeout = t - - return nil + return &e, nil } // validate validates provided environment variable requirements.