diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a8b4350..6366df1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,8 +5,8 @@ repos: rev: v4.6.0 hooks: - id: trailing-whitespace + exclude: configgen/api-docs.md - id: detect-private-key - exclude: providers/gmail/gmail_test.go - repo: https://github.com/google/yamlfmt rev: v0.13.0 hooks: diff --git a/config/.env.example b/config/.env.example index 716200d..a75c67a 100644 --- a/config/.env.example +++ b/config/.env.example @@ -1,3 +1,7 @@ RIVERBOAT_REFRESHINTERVAL="10m" RIVERBOAT_JOBQUEUE_DATABASEHOST="postgres://postgres:password@0.0.0.0:5432/jobs?sslmode=disable" RIVERBOAT_JOBQUEUE_QUEUES="" +RIVERBOAT_JOBQUEUE_WORKERS_DATABASEWORKER_CONFIG_ENABLED="true" +RIVERBOAT_JOBQUEUE_WORKERS_DATABASEWORKER_CONFIG_BASEURL="http://localhost:1337" +RIVERBOAT_JOBQUEUE_WORKERS_DATABASEWORKER_CONFIG_ENDPOINT="query" +RIVERBOAT_JOBQUEUE_WORKERS_DATABASEWORKER_CONFIG_DEBUG="false" diff --git a/config/config.example.yaml b/config/config.example.yaml index 65cb312..e19d6de 100644 --- a/config/config.example.yaml +++ b/config/config.example.yaml @@ -2,6 +2,12 @@ jobQueue: databaseHost: postgres://postgres:password@0.0.0.0:5432/jobs?sslmode=disable queues: null workers: + databaseWorker: + config: + baseUrl: http://localhost:1337 + debug: false + enabled: true + endpoint: query emailWorker: devMode: true fromEmail: no-reply@example.com diff --git a/config/configmap.yaml b/config/configmap.yaml index 068d1b7..d994e44 100644 --- a/config/configmap.yaml +++ b/config/configmap.yaml @@ -13,3 +13,7 @@ data: RIVERBOAT_REFRESHINTERVAL: {{ .Values.riverboat.refreshInterval | default "10m" }} RIVERBOAT_JOBQUEUE_DATABASEHOST: {{ .Values.riverboat.jobQueue.databaseHost | default "postgres://postgres:password@0.0.0.0:5432/jobs?sslmode=disable" }} RIVERBOAT_JOBQUEUE_QUEUES: {{ .Values.riverboat.jobQueue.queues }} + RIVERBOAT_JOBQUEUE_WORKERS_DATABASEWORKER_CONFIG_ENABLED: {{ .Values.riverboat.jobQueue.workers.databaseWorker.config.enabled | default true }} + RIVERBOAT_JOBQUEUE_WORKERS_DATABASEWORKER_CONFIG_BASEURL: {{ .Values.riverboat.jobQueue.workers.databaseWorker.config.baseUrl | default "http://localhost:1337" }} + RIVERBOAT_JOBQUEUE_WORKERS_DATABASEWORKER_CONFIG_ENDPOINT: {{ .Values.riverboat.jobQueue.workers.databaseWorker.config.endpoint | default "query" }} + RIVERBOAT_JOBQUEUE_WORKERS_DATABASEWORKER_CONFIG_DEBUG: {{ .Values.riverboat.jobQueue.workers.databaseWorker.config.debug | default false }} diff --git a/configgen/api-docs.md b/configgen/api-docs.md index 5234dfd..30f2413 100644 --- a/configgen/api-docs.md +++ b/configgen/api-docs.md @@ -20,7 +20,7 @@ Config is the configuration for the river server |----|----|-----------|--------| |**databaseHost**|`string`|DatabaseHost for connecting to the postgres database
|| |[**queues**](#jobqueuequeues)|`array`||| -|[**workers**](#jobqueueworkers)|`object`||| +|[**workers**](#jobqueueworkers)|`object`|Workers that will be enabled on the server
|| **Additional Properties:** not allowed @@ -31,11 +31,15 @@ Config is the configuration for the river server ### jobQueue\.workers: object +Workers that will be enabled on the server + + **Properties** |Name|Type|Description|Required| |----|----|-----------|--------| |[**emailWorker**](#jobqueueworkersemailworker)|`object`||| +|[**databaseWorker**](#jobqueueworkersdatabaseworker)|`object`||| **Additional Properties:** not allowed @@ -51,4 +55,27 @@ Config is the configuration for the river server |**fromEmail**|`string`||| **Additional Properties:** not allowed + +#### jobQueue\.workers\.databaseWorker: object + +**Properties** + +|Name|Type|Description|Required| +|----|----|-----------|--------| +|[**config**](#jobqueueworkersdatabaseworkerconfig)|`object`||| + +**Additional Properties:** not allowed + +##### jobQueue\.workers\.databaseWorker\.config: object + +**Properties** + +|Name|Type|Description|Required| +|----|----|-----------|--------| +|**enabled**|`boolean`|Enable the dbx client
|| +|**baseUrl**|`string`|Base URL for the dbx service
|| +|**endpoint**|`string`|Endpoint for the graphql api
|| +|**debug**|`boolean`|Enable debug mode
|| + +**Additional Properties:** not allowed diff --git a/configgen/riverboat.config.json b/configgen/riverboat.config.json index e153f1f..850da5e 100644 --- a/configgen/riverboat.config.json +++ b/configgen/riverboat.config.json @@ -8,6 +8,38 @@ }, "type": "array" }, + "dbxclient.Config": { + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable the dbx client" + }, + "baseUrl": { + "type": "string", + "description": "Base URL for the dbx service" + }, + "endpoint": { + "type": "string", + "description": "Endpoint for the graphql api" + }, + "debug": { + "type": "boolean", + "description": "Enable debug mode" + } + }, + "additionalProperties": false, + "type": "object" + }, + "jobs.DatabaseWorker": { + "properties": { + "config": { + "$ref": "#/$defs/dbxclient.Config", + "description": "the database configuration" + } + }, + "additionalProperties": false, + "type": "object" + }, "jobs.EmailWorker": { "properties": { "devMode": { @@ -66,11 +98,17 @@ "river.Workers": { "properties": { "emailWorker": { - "$ref": "#/$defs/jobs.EmailWorker" + "$ref": "#/$defs/jobs.EmailWorker", + "description": "EmailWorker configuration for sending emails" + }, + "databaseWorker": { + "$ref": "#/$defs/jobs.DatabaseWorker", + "description": "DatabaseWorker configuration for creating databases using openlane/dbx" } }, "additionalProperties": false, - "type": "object" + "type": "object", + "description": "Workers that will be enabled on the server" } }, "properties": { diff --git a/go.mod b/go.mod index 4bba6cf..c31fb87 100644 --- a/go.mod +++ b/go.mod @@ -17,18 +17,25 @@ require ( github.com/rs/zerolog v1.33.0 github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.9.0 + github.com/theopenlane/dbx v0.1.2 github.com/theopenlane/newman v0.1.1 github.com/theopenlane/utils v0.2.1 ) require ( + github.com/99designs/gqlgen v0.17.49 // indirect + github.com/Yamashou/gqlgenc v0.24.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/buger/jsonparser v1.1.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-viper/mapstructure/v2 v2.1.0 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/gorilla/css v1.0.1 // indirect + github.com/gorilla/securecookie v1.1.2 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect @@ -40,19 +47,32 @@ require ( github.com/microcosm-cc/bluemonday v1.0.27 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/oklog/ulid/v2 v2.1.0 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/redis/go-redis/v9 v9.6.1 // indirect github.com/resend/resend-go/v2 v2.11.0 // indirect github.com/riverqueue/river/riverdriver v0.11.4 // indirect github.com/riverqueue/river/rivershared v0.11.4 // indirect github.com/riverqueue/river/rivertype v0.11.4 // indirect + github.com/sosodev/duration v1.3.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect + github.com/theopenlane/echox v0.2.0 // indirect + github.com/theopenlane/iam v0.1.6 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect + github.com/vektah/gqlparser/v2 v2.5.16 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect go.uber.org/goleak v1.3.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.0 // indirect golang.org/x/crypto v0.27.0 // indirect golang.org/x/net v0.29.0 // indirect + golang.org/x/oauth2 v0.23.0 // indirect golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.25.0 // indirect golang.org/x/text v0.18.0 // indirect + golang.org/x/time v0.6.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 6258503..2dee70d 100644 --- a/go.sum +++ b/go.sum @@ -1,22 +1,48 @@ +github.com/99designs/gqlgen v0.17.49 h1:b3hNGexHd33fBSAd4NDT/c3NCcQzcAVkknhN9ym36YQ= +github.com/99designs/gqlgen v0.17.49/go.mod h1:tC8YFVZMed81x7UJ7ORUwXF4Kn6SXuucFqQBhN8+BU0= +github.com/Yamashou/gqlgenc v0.24.0 h1:Aeufjb2zF0XxkeSTAVQ+DfiHL+ney/M2ovShZozBmHw= +github.com/Yamashou/gqlgenc v0.24.0/go.mod h1:3QQD8ZoeEyVXuzqcMDsl8OfCCCTk+ulaxkvFFQDupIA= +github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 h1:uvdUDbHQHO85qeSydJtItA4T55Pw6BtAejd0APRJOCE= +github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= +github.com/alicebob/miniredis/v2 v2.33.0 h1:uvTF0EDeu9RLnUEG27Db5I68ESoIxTiXbNUiji6lZrA= +github.com/alicebob/miniredis/v2 v2.33.0/go.mod h1:MhP4a3EU7aENRi9aO+tHfTBZicLqQevyi/DJpoj6mi0= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/go-viper/mapstructure/v2 v2.1.0 h1:gHnMa2Y/pIxElCH2GlZZ1lZSsn6XMtufpGyP1XxdC/w= github.com/go-viper/mapstructure/v2 v2.1.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= +github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= +github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI= @@ -71,10 +97,16 @@ github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HK github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU= +github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= +github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4= +github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA= github.com/resend/resend-go/v2 v2.11.0 h1:Ja5eXizUCbvyLgbiP8sFsJW/UN1b7d6IEUqi80IlgiU= github.com/resend/resend-go/v2 v2.11.0/go.mod h1:ihnxc7wPpSgans8RV8d8dIF4hYWVsqMK5KxXAr9LIos= github.com/riverqueue/river v0.11.4 h1:NMRsODhRgFztf080RMCjI377jldLXsx41E2r7+c0lPE= @@ -97,6 +129,10 @@ github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4= +github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -113,18 +149,38 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/theopenlane/dbx v0.1.2 h1:h2+okfFg3oh4ou8z99QTqCxBTDeaQ2Ek2AEsh3tw/KQ= +github.com/theopenlane/dbx v0.1.2/go.mod h1:SRBOZyQaGQLM4OFxOEtJW5JbR0aTXFUjY8GYm2WOr64= +github.com/theopenlane/echox v0.2.0 h1:s9DJJrsLOSPsXVfgmQxgXmSVtxzztBnSmcVX4ax7tIM= +github.com/theopenlane/echox v0.2.0/go.mod h1:nfxwQpwvqYYI/pFHJKDs3/HLvjYKEGCih4XDgLSma64= +github.com/theopenlane/iam v0.1.6 h1:ps6xLXHpnGy687uLPRZiD7034DRVqaWEfJLCJVMx95o= +github.com/theopenlane/iam v0.1.6/go.mod h1:mOtYjuqUD7SX4EkwXFAYwf8+mwPDsRvTsLhAngqVIxM= github.com/theopenlane/newman v0.1.1 h1:pxGPRcy8kXQplfv4Sp1N3XUkWmx/scZvp7oj+y2l8wI= github.com/theopenlane/newman v0.1.1/go.mod h1:A37pInKEYsdvUmjQzTDv7x5T4KhMxoFW105DA3XvH4Y= github.com/theopenlane/utils v0.2.1 h1:T6VfvOQDcAXBa1NFVL4QCsCbHvVQkp6Tl4hGJVd7TwQ= github.com/theopenlane/utils v0.2.1/go.mod h1:ydEtwhmEvkVt3KKmNqiQiSY5b3rKH7U4umZ3QbFDsxU= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= +github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/vektah/gqlparser/v2 v2.5.16 h1:1gcmLTvs3JLKXckwCwlUagVn/IlV2bwqle0vJ0vy5p8= +github.com/vektah/gqlparser/v2 v2.5.16/go.mod h1:1lz1OeCqgQbQepsGxPVywrjdBHW2T08PUS3pJqepRww= github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= +github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= +github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -134,6 +190,8 @@ golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= +golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= diff --git a/internal/river/config.go b/internal/river/config.go index 5cd5251..6844fce 100644 --- a/internal/river/config.go +++ b/internal/river/config.go @@ -31,8 +31,13 @@ type Logger struct { Pretty bool `koanf:"-" json:"-"` } +// Workers that will be enabled on the server type Workers struct { + // EmailWorker configuration for sending emails EmailWorker jobs.EmailWorker `koanf:"emailWorker" json:"emailWorker"` + // DatabaseWorker configuration for creating databases using openlane/dbx + DatabaseWorker jobs.DatabaseWorker `koanf:"databaseWorker" json:"databaseWorker"` + // add more workers here } diff --git a/internal/river/workers.go b/internal/river/workers.go index 974a185..93ea50c 100644 --- a/internal/river/workers.go +++ b/internal/river/workers.go @@ -2,6 +2,7 @@ package river import ( "github.com/riverqueue/river" + "github.com/theopenlane/riverboat/pkg/jobs" ) @@ -17,6 +18,13 @@ func createWorkers(c Workers) (*river.Workers, error) { return nil, err } + if err := river.AddWorkerSafely(workers, &jobs.DatabaseWorker{ + Config: c.DatabaseWorker.Config, + }, + ); err != nil { + return nil, err + } + // add more workers here return workers, nil diff --git a/pkg/jobs/database.go b/pkg/jobs/database.go new file mode 100644 index 0000000..041f0d9 --- /dev/null +++ b/pkg/jobs/database.go @@ -0,0 +1,74 @@ +package jobs + +import ( + "context" + + dbx "github.com/theopenlane/dbx/pkg/dbxclient" + + "github.com/riverqueue/river" + "github.com/rs/zerolog/log" +) + +// DatabaseArgs are the arguments for the database worker +type DatabaseArgs struct { + // OrganizationID is the organization id to create the database for + OrganizationID string `json:"organization_id"` + // Location is the location to create the database in, e.g. AMER + Location string `json:"location"` +} + +// Kind satisfies the river.Args interface for the database worker +func (DatabaseArgs) Kind() string { return "dedicated_database" } + +// DatabaseWorker is a worker to create a dedicated database for an organization +type DatabaseWorker struct { + river.WorkerDefaults[DatabaseArgs] + + Config dbx.Config `koanf:"config" json:"config" jsonschema:"description=the database configuration"` +} + +// validateDatabaseInput validates the input for the database worker +func validateDatabaseInput(job *river.Job[DatabaseArgs]) error { + if job.Args.OrganizationID == "" { + return newMissingRequiredArg("organization_id", DatabaseArgs{}.Kind()) + } + + if job.Args.Location == "" { + return newMissingRequiredArg("location", DatabaseArgs{}.Kind()) + } + + return nil +} + +// Work satisfies the river.Worker interface for the database worker +// it creates a dedicated database using the dbx client for the organization +func (w *DatabaseWorker) Work(ctx context.Context, job *river.Job[DatabaseArgs]) error { + // if its not enabled, return early + if !w.Config.Enabled { + return nil + } + + if err := validateDatabaseInput(job); err != nil { + return err + } + + input := dbx.CreateDatabaseInput{ + OrganizationID: job.Args.OrganizationID, + Geo: &job.Args.Location, + } + + log.Debug(). + Str("org", input.OrganizationID). + Str("geo", *input.Geo). + Msg("creating database") + + client := w.Config.NewDefaultClient() + + if _, err := client.CreateDatabase(ctx, input); err != nil { + log.Error().Err(err).Msg("failed to create database") + + return err + } + + return nil +} diff --git a/pkg/jobs/database_test.go b/pkg/jobs/database_test.go new file mode 100644 index 0000000..e703a81 --- /dev/null +++ b/pkg/jobs/database_test.go @@ -0,0 +1,56 @@ +package jobs_test + +import ( + "context" + "testing" + + "github.com/riverqueue/river" + "github.com/stretchr/testify/require" + "github.com/theopenlane/dbx/pkg/dbxclient" + "github.com/theopenlane/utils/ulids" + + "github.com/theopenlane/riverboat/pkg/jobs" +) + +// TODO :this currently does not test the actual database creation because the dbx client is not mocked +// so the only thing we can test is that the worker returns early when disabled +func (suite *TestSuite) TestDatabaseWorker() { + t := suite.T() + + testCases := []struct { + name string + worker *jobs.DatabaseWorker + args jobs.DatabaseArgs + expectedError string + }{ + { + name: "happy path, skip while disabled", + worker: &jobs.DatabaseWorker{ + Config: dbxclient.Config{ + Enabled: false, + }, + }, + args: jobs.DatabaseArgs{ + Location: "AMER", + OrganizationID: ulids.New().String(), + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + + err := (tc.worker).Work(ctx, &river.Job[jobs.DatabaseArgs]{Args: tc.args}) + + if tc.expectedError != "" { + require.Error(t, err) + require.Contains(t, err.Error(), tc.expectedError) + + return + } + + require.NoError(t, err) + }) + } +} diff --git a/pkg/jobs/email.go b/pkg/jobs/email.go index 22d85b1..887a027 100644 --- a/pkg/jobs/email.go +++ b/pkg/jobs/email.go @@ -42,11 +42,11 @@ type EmailConfig struct { // validateEmailConfig validates the email configuration settings func (w *EmailWorker) validateEmailConfig() error { if w.DevMode && w.TestDir == "" { - return ErrMissingTestDir + return newMissingRequiredArg("test directory", EmailArgs{}.Kind()) } if !w.DevMode && w.Token == "" { - return ErrMissingToken + return newMissingRequiredArg("token", EmailArgs{}.Kind()) } return nil diff --git a/pkg/jobs/email_test.go b/pkg/jobs/email_test.go index 6c1cf1e..f7ee014 100644 --- a/pkg/jobs/email_test.go +++ b/pkg/jobs/email_test.go @@ -49,7 +49,7 @@ func (suite *TestSuite) TestEmailWorker() { }, }, msg: emailWithoutFrom, - expectedError: jobs.ErrMissingTestDir.Error(), + expectedError: "test directory is required for the email job", }, { name: "missing from email", @@ -81,7 +81,7 @@ func (suite *TestSuite) TestEmailWorker() { }, }, msg: emailWithoutFrom, - expectedError: jobs.ErrMissingToken.Error(), + expectedError: "token is required for the email job", }, } diff --git a/pkg/jobs/errors.go b/pkg/jobs/errors.go index fd5b2f0..3c7593d 100644 --- a/pkg/jobs/errors.go +++ b/pkg/jobs/errors.go @@ -1,6 +1,9 @@ package jobs -import "errors" +import ( + "errors" + "fmt" +) var ( // ErrMissingTestDir is the error for missing test directory @@ -8,3 +11,24 @@ var ( // ErrMissingToken is the error for missing token ErrMissingToken = errors.New("missing resend api token, set token or use dev mode") ) + +// MissingRequiredArg is returned when a required argument was not provided to a job +type MissingRequiredArgError struct { + // Arg is the required argument that was not provided + Arg string `json:"arg"` + // Job is the job that requires the argument + Job string `json:"job"` +} + +// Error returns the MissingRequiredArgError in string format +func (e *MissingRequiredArgError) Error() string { + return fmt.Sprintf("%s is required for the %s job", e.Arg, e.Job) +} + +// newMissingRequiredArg returns an error for a missing required argument +func newMissingRequiredArg(field, job string) *MissingRequiredArgError { + return &MissingRequiredArgError{ + Arg: field, + Job: job, + } +} diff --git a/test/database/main.go b/test/database/main.go new file mode 100644 index 0000000..603a320 --- /dev/null +++ b/test/database/main.go @@ -0,0 +1,27 @@ +package main + +import ( + "context" + + "github.com/rs/zerolog/log" + + "github.com/theopenlane/riverboat/test/common" + + "github.com/theopenlane/riverboat/pkg/jobs" +) + +// the main function here will insert an database job into the river +// this will be picked up by the river server and processed +func main() { + client := common.NewInsertOnlyRiverClient() + + _, err := client.Insert(context.Background(), jobs.DatabaseArgs{ + OrganizationID: "100100100010001000", + Location: "AMER", + }, nil) + if err != nil { + log.Fatal().Err(err).Msg("error inserting database job") + } + + log.Info().Msg("database job successfully inserted") +}