From 22572574749ce7d9dd1d3a1b2bf47d90e3d58c93 Mon Sep 17 00:00:00 2001 From: Yuvraj Date: Sun, 6 Oct 2024 20:05:57 +0530 Subject: [PATCH 01/17] added auth server --- Makefile | 39 +++++-- go.mod | 30 ++--- go.sum | 51 ++++----- server/root/main.go | 35 ++++-- server/route/oauth2/oauth2.go | 203 ++++++++++++++++++++++++++++++++++ 5 files changed, 298 insertions(+), 60 deletions(-) create mode 100644 server/route/oauth2/oauth2.go diff --git a/Makefile b/Makefile index 3c2077e..df27f13 100644 --- a/Makefile +++ b/Makefile @@ -8,37 +8,47 @@ SERVER_SRC := ./server/root DASHBOARD_SRC := ./clients/dashboard # Docker configuration -DOCKER_REPO := ghcr.io/yindia +DOCKER_REPO := ghcr.io/bruin-hiring VERSION := $(shell git describe --tags --always --dirty) DOCKER_CLI_NAME := task-cli DOCKER_SERVER_NAME := task-server DOCKER_DASHBOARD_NAME := task-dashboard -# Colors for output +# ANSI color codes for prettier output NO_COLOR := \033[0m OK_COLOR := \033[32;01m ERROR_COLOR := \033[31;01m WARN_COLOR := \033[33;01m +# Declare phony targets (targets that don't represent files) .PHONY: all bootstrap deps check-go check-npm build test docker-build docker-push helm-template helm-lint helm-fmt helm-install helm helm-dep-update + +# Default target: run deps, tests, and build all: deps test build +# Install all dependencies +deps: deps-go deps-npm -deps: check-go check-npm +# Install Go dependencies +deps-go: check-go go mod download go fmt ./... go generate ./... - npm config set @buf:registry https://buf.build/gen/npm/v1/ + +# Install npm dependencies +deps-npm: check-npm npm install --force +# Check if Go is installed check-go: @which go > /dev/null || (echo "$(ERROR_COLOR)Go is not installed$(NO_COLOR)" && exit 1) +# Check if npm is installed check-npm: @which npm > /dev/null || (echo "$(ERROR_COLOR)npm is not installed$(NO_COLOR)" && exit 1) # CLI targets -build-cli: deps +build-cli: deps-go @echo "$(OK_COLOR)==> Building the CLI...$(NO_COLOR)" @CGO_ENABLED=0 go build -v -ldflags="-s -w" -o "$(BUILD_DIR)/$(CLI_NAME)" "$(CLI_SRC)" @@ -55,7 +65,7 @@ docker-push-cli: docker-build-cli docker push $(DOCKER_REPO)/$(DOCKER_CLI_NAME):$(VERSION) # Server targets -build-server: deps +build-server: deps-go @echo "$(OK_COLOR)==> Building the server...$(NO_COLOR)" @CGO_ENABLED=0 go build -v -ldflags="-s -w" -o "$(BUILD_DIR)/$(SERVER_NAME)" "$(SERVER_SRC)" @@ -72,17 +82,17 @@ docker-push-server: docker-build-server docker push $(DOCKER_REPO)/$(DOCKER_SERVER_NAME):$(VERSION) # Dashboard targets -build-dashboard: deps +build-dashboard: deps-npm @echo "$(OK_COLOR)==> Building the dashboard...$(NO_COLOR)" npm run build -run-dashboard: deps +run-dashboard: deps-npm @echo "$(OK_COLOR)==> Running the dashboard...$(NO_COLOR)" npm run dev docker-build-dashboard: @echo "$(OK_COLOR)==> Building Docker image for dashboard...$(NO_COLOR)" - docker build -f Dockerfile.client -t $(DOCKER_REPO)/$(DOCKER_DASHBOARD_NAME):$(VERSION) . + docker build -f Dockerfile.client -t $(DOCKER_REPO)/$(DOCKER_DASHBOARD_NAME):$(VERSION) . docker-push-dashboard: docker-build-dashboard @echo "$(OK_COLOR)==> Pushing Docker image for dashboard...$(NO_COLOR)" @@ -112,6 +122,11 @@ helm-fmt: @echo "$(OK_COLOR)==> Formatting Helm charts...$(NO_COLOR)" helm lint --strict charts/task +helm-docs: + @echo "$(OK_COLOR)==> Generating Helm charts README.md...$(NO_COLOR)" + go install github.com/norwoodj/helm-docs/cmd/helm-docs@latest + helm-docs -c ./charts/task/ + helm-install: @echo "$(OK_COLOR)==> Installing Helm charts...$(NO_COLOR)" helm install my-release charts/task @@ -120,10 +135,12 @@ helm-dep-update: @echo "$(OK_COLOR)==> Updating Helm dependencies...$(NO_COLOR)" helm dependency update ./charts/task/ -helm: helm-dep-update helm-template helm-lint helm-fmt +# Run all Helm-related tasks +helm: helm-dep-update helm-template helm-lint helm-fmt helm-docs @echo "$(OK_COLOR)==> Helm template, lint, and format completed.$(NO_COLOR)" +# Set up development environment bootstrap: curl -fsSL https://pixi.sh/install.sh | bash brew install bufbuild/buf/buf - pixi shell + pixi shell \ No newline at end of file diff --git a/go.mod b/go.mod index e5752c8..931f478 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module task -go 1.22.3 +go 1.23 + +toolchain go1.23.2 require ( connectrpc.com/connect v1.16.2 @@ -9,22 +11,23 @@ require ( connectrpc.com/otelconnect v0.7.1 github.com/avast/retry-go/v4 v4.6.0 github.com/bufbuild/protovalidate-go v0.7.0 + github.com/coreos/go-oidc/v3 v3.11.0 github.com/envoyproxy/protoc-gen-validate v1.1.0 github.com/google/go-cmp v0.6.0 - github.com/jackc/pgx/v5 v5.7.1 + github.com/gorilla/sessions v1.4.0 github.com/joho/godotenv v1.5.1 github.com/kelseyhightower/envconfig v1.4.0 github.com/olekukonko/tablewriter v0.0.5 github.com/prometheus/client_golang v1.20.4 github.com/riverqueue/river v0.12.1 github.com/riverqueue/river/riverdriver/riverdatabasesql v0.12.1 - github.com/riverqueue/river/riverdriver/riverpgxv5 v0.12.1 github.com/riverqueue/river/rivertype v0.12.1 github.com/rs/cors v1.11.1 github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.9.0 go.akshayshah.org/connectauth v0.6.0 golang.org/x/net v0.29.0 + golang.org/x/oauth2 v0.21.0 google.golang.org/protobuf v1.34.2 gopkg.in/yaml.v2 v2.4.0 gorm.io/driver/postgres v1.5.9 @@ -34,26 +37,23 @@ require ( require ( buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.34.2-20240920164238-5a7b106cbb87.2 // indirect github.com/antlr4-go/antlr/v4 v4.13.0 // indirect - github.com/avast/retry-go v3.0.0+incompatible // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/gabriel-vasile/mimetype v1.4.3 // indirect - github.com/go-logr/logr v1.2.4 // indirect + github.com/go-jose/go-jose/v4 v4.0.2 // indirect + github.com/go-logr/logr v1.3.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-playground/locales v0.14.1 // indirect - github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.22.0 // indirect github.com/google/cel-go v0.21.0 // indirect + github.com/gorilla/securecookie v1.1.2 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.7.1 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/klauspost/compress v1.17.9 // indirect - github.com/leodido/go-urn v1.4.0 // indirect github.com/lib/pq v1.10.9 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect @@ -69,11 +69,12 @@ require ( github.com/stretchr/objx v0.5.2 // indirect github.com/tidwall/gjson v1.17.3 // indirect github.com/tidwall/match v1.1.1 // indirect - github.com/tidwall/pretty v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/sjson v1.2.5 // indirect - go.opentelemetry.io/otel v1.19.0 // indirect - go.opentelemetry.io/otel/metric v1.19.0 // indirect - go.opentelemetry.io/otel/trace v1.19.0 // indirect + go.opentelemetry.io/otel v1.21.0 // indirect + go.opentelemetry.io/otel/metric v1.21.0 // indirect + go.opentelemetry.io/otel/sdk v1.21.0 // indirect + go.opentelemetry.io/otel/trace v1.21.0 // indirect go.uber.org/goleak v1.3.0 // indirect golang.org/x/crypto v0.27.0 // indirect golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect @@ -83,5 +84,4 @@ require ( google.golang.org/genproto/googleapis/api v0.0.0-20240401170217-c3f982113cda // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - riverqueue.com/riverui v0.5.3 // indirect ) diff --git a/go.sum b/go.sum index a1d2a95..45bce54 100644 --- a/go.sum +++ b/go.sum @@ -10,8 +10,6 @@ connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= -github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0= -github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= github.com/avast/retry-go/v4 v4.6.0 h1:K9xNA+KeB8HHc2aWFuLb25Offp+0iVRXEvFx8IinRJA= github.com/avast/retry-go/v4 v4.6.0/go.mod h1:gvWlPhBVsvBbLkVGDg/KwvBv0bEkCOLRRSHKIr2PyOE= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -20,33 +18,33 @@ github.com/bufbuild/protovalidate-go v0.7.0 h1:MYU9GSZM7TSsWNywvyXoEc8y3kc1MNqD3 github.com/bufbuild/protovalidate-go v0.7.0/go.mod h1:PHV5pFuWlRzdDW02/cmVyNzdiQ+RNNwo7idGxdzS7o4= 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-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/OtI= +github.com/coreos/go-oidc/v3 v3.11.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0= 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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM= github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= -github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= -github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk= +github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= -github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= -github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao= -github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/google/cel-go v0.21.0 h1:cl6uW/gxN+Hy50tNYvI691+sXxioCnstFzLp2WO4GCI= github.com/google/cel-go v0.21.0/go.mod h1:rHUlWCcBKgyEk+eV03RPdZUekPp6YcJwV0FxuUksYxc= 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/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= +github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= +github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ= +github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa h1:s+4MhCQ6YrzisK6hFJUX53drDT4UsSW3DEhKn0ifuHw= -github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 h1:Dj0L5fhJ9F82ZJyVOmBx6msDp/kfd1t9GRfny/mfJA0= github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= @@ -73,8 +71,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= -github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= @@ -135,8 +131,9 @@ github.com/tidwall/gjson v1.17.3 h1:bwWLZU7icoKRG+C+0PNwIKC6FCJO/Q3p2pZvuP0jN94= github.com/tidwall/gjson v1.17.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= -github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= go.akshayshah.org/attest v1.0.2 h1:qOv9PXCG2mwnph3g0I3yZj0rLAwLyUITs8nhxP+wS44= @@ -145,16 +142,16 @@ go.akshayshah.org/connectauth v0.6.0 h1:lyzJ3z33L2KKgfSZW95tilktE7bCgc/OTQm0V8C2 go.akshayshah.org/connectauth v0.6.0/go.mod h1:4dHteR5Gt7mh0weczJVRESBBWf+tmphmTIDg7R99JYQ= go.akshayshah.org/memhttp v0.1.0 h1:Enf7JeZnm+A8iRur0FYvs4ZjWa1VVMc2gG4EirG+aNE= go.akshayshah.org/memhttp v0.1.0/go.mod h1:Q1A5oqQfj2tZFRzpw0HRmmZAMzw8f3AxqOe55Afn1d8= -go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= -go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= -go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= -go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= -go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= -go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= +go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc= +go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= +go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4= +go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= +go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8= +go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= go.opentelemetry.io/otel/sdk/metric v1.19.0 h1:EJoTO5qysMsYCa+w4UghwFV/ptQgqSL/8Ni+hx+8i1k= go.opentelemetry.io/otel/sdk/metric v1.19.0/go.mod h1:XjG0jQyFJrv2PbMvwND7LwCEhsJzCzV5210euduKcKY= -go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= -go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= +go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc= +go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= @@ -163,6 +160,8 @@ golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88p golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ= 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.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= +golang.org/x/oauth2 v0.21.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.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= @@ -187,5 +186,3 @@ gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8= gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI= gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= -riverqueue.com/riverui v0.5.3 h1:SoKWTreMJqTgFYScvoqBva5YZ45bBzgXWEEAv/oQrKE= -riverqueue.com/riverui v0.5.3/go.mod h1:846dydyj0GILXGmiOnsnZ4HYJSfgtQ93jib+joX4no4= diff --git a/server/root/main.go b/server/root/main.go index d26de24..fe017c1 100644 --- a/server/root/main.go +++ b/server/root/main.go @@ -11,6 +11,13 @@ import ( "syscall" "time" + cloudv1connect "task/pkg/gen/cloud/v1/cloudv1connect" + "task/pkg/x" // Import the x package for env and config + repository "task/server/repository" // Import repository package + interfaces "task/server/repository/interface" // Import repository package + "task/server/route" // Import route package + oauth2 "task/server/route/oauth2" + "connectrpc.com/connect" "connectrpc.com/grpchealth" "connectrpc.com/grpcreflect" @@ -20,12 +27,6 @@ import ( "golang.org/x/net/http2" "golang.org/x/net/http2/h2c" - cloudv1connect "task/pkg/gen/cloud/v1/cloudv1connect" - "task/pkg/x" // Import the x package for env and config - repository "task/server/repository" // Import repository package - interfaces "task/server/repository/interface" // Import repository package - "task/server/route" // Import route package - "github.com/prometheus/client_golang/prometheus/promhttp" ) @@ -111,8 +112,24 @@ func run() error { slog.Info("Database repository initialized", "workerCount", env.WorkerCount) + auth, err := oauth2.NewAuthServer(oauth2.Config{ + Issuer: "https://fosite.my-application.com", + ClientID: "my-client", + ClientSecret: "my-secret", + RedirectURL: "http://localhost:8080/callback", + SessionKey: "session", + }) + if err != nil { + return fmt.Errorf("failed to initialize authorization server: %w", err) + } + // Set up gRPC middleware - middleware := connectauth.NewMiddleware(GrpcMiddleware) + middleware := connectauth.NewMiddleware(func(ctx context.Context, req *connectauth.Request) (any, error) { + if auth.IsAuthenticated(req) { + return AuthCtx{Username: "tqindia"}, nil + } + return nil, errors.New("user is not authenticated") + }) // Set up HTTP server mux := http.NewServeMux() @@ -120,6 +137,10 @@ func run() error { return fmt.Errorf("failed to set up handlers: %w", err) } + http.HandleFunc("/login", auth.LoginHandler) + http.HandleFunc("/authorization-code/callback", auth.AuthCodeCallbackHandler) + http.HandleFunc("/logout", auth.LogoutHandler) + // Add Prometheus metrics endpoint mux.Handle("/metrics", promhttp.Handler()) diff --git a/server/route/oauth2/oauth2.go b/server/route/oauth2/oauth2.go new file mode 100644 index 0000000..1988155 --- /dev/null +++ b/server/route/oauth2/oauth2.go @@ -0,0 +1,203 @@ +package oauth2 + +import ( + "context" + "crypto/rand" + "encoding/hex" + "encoding/json" + "fmt" + "net/http" + "os" + "sync" + + "github.com/coreos/go-oidc/v3/oidc" + "github.com/gorilla/sessions" + "go.akshayshah.org/connectauth" + "golang.org/x/oauth2" +) + +// Config represents the configuration for the AuthServer +type Config struct { + Issuer string + ClientID string + ClientSecret string + RedirectURL string + SessionKey string +} + +// AuthServer represents the authorization server +type AuthServer struct { + config Config + sessionStore *sessions.CookieStore + state string + oauth2Config oauth2.Config + verifier *oidc.IDTokenVerifier + mu sync.Mutex // Add a mutex for thread-safe operations +} + +// NewAuthServer creates and initializes a new AuthServer +func NewAuthServer(config Config) (*AuthServer, error) { + ctx := context.Background() + provider, err := oidc.NewProvider(ctx, config.Issuer) + if err != nil { + return nil, fmt.Errorf("failed to get provider: %v", err) + } + + oauth2Config := oauth2.Config{ + ClientID: config.ClientID, + ClientSecret: config.ClientSecret, + RedirectURL: config.RedirectURL, + Endpoint: provider.Endpoint(), + Scopes: []string{oidc.ScopeOpenID, "profile", "email"}, + } + + oidcConfig := &oidc.Config{ + ClientID: config.ClientID, + } + verifier := provider.Verifier(oidcConfig) + + return &AuthServer{ + config: config, + sessionStore: sessions.NewCookieStore([]byte(config.SessionKey)), + state: generateState(), + oauth2Config: oauth2Config, + verifier: verifier, + }, nil +} + +func generateState() string { + b := make([]byte, 16) + rand.Read(b) + return hex.EncodeToString(b) +} + +// LoginHandler handles the login request +func (as *AuthServer) LoginHandler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.Header().Set("Cache-Control", "no-cache") + + as.mu.Lock() + authURL := as.oauth2Config.AuthCodeURL(as.state, oidc.Nonce(generateState())) + as.mu.Unlock() + + json.NewEncoder(w).Encode(map[string]string{"login_url": authURL}) +} + +// AuthCodeCallbackHandler handles the authorization code callback +func (as *AuthServer) AuthCodeCallbackHandler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + as.mu.Lock() + if r.URL.Query().Get("state") != as.state { + as.mu.Unlock() + http.Error(w, `{"error": "Invalid state"}`, http.StatusBadRequest) + return + } + as.mu.Unlock() + + // Make sure the code was provided + if r.URL.Query().Get("code") == "" { + http.Error(w, `{"error": "The code was not returned or is not accessible"}`, http.StatusBadRequest) + return + } + + oauth2Token, err := as.oauth2Config.Exchange(r.Context(), r.URL.Query().Get("code")) + if err != nil { + http.Error(w, fmt.Sprintf(`{"error": "Failed to exchange token: %v"}`, err), http.StatusInternalServerError) + return + } + + rawIDToken, ok := oauth2Token.Extra("id_token").(string) + if !ok { + http.Error(w, "No id_token field in oauth2 token.", http.StatusInternalServerError) + return + } + + idToken, err := as.verifier.Verify(r.Context(), rawIDToken) + if err != nil { + http.Error(w, "Failed to verify ID Token: "+err.Error(), http.StatusInternalServerError) + return + } + + as.mu.Lock() + session, err := as.sessionStore.Get(r, "okta-hosted-login-session-store") + as.mu.Unlock() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + + as.mu.Lock() + session.Values["id_token"] = rawIDToken + session.Values["access_token"] = oauth2Token.AccessToken + err = session.Save(r, w) + as.mu.Unlock() + if err != nil { + http.Error(w, "Failed to save session: "+err.Error(), http.StatusInternalServerError) + return + } + + http.Redirect(w, r, "/", http.StatusFound) +} + +// LogoutHandler handles the logout request +func (as *AuthServer) LogoutHandler(w http.ResponseWriter, r *http.Request) { + as.mu.Lock() + session, err := as.sessionStore.Get(r, "okta-hosted-login-session-store") + as.mu.Unlock() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + + as.mu.Lock() + delete(session.Values, "id_token") + delete(session.Values, "access_token") + err = session.Save(r, w) + as.mu.Unlock() + if err != nil { + http.Error(w, "Failed to save session: "+err.Error(), http.StatusInternalServerError) + return + } + + http.Redirect(w, r, "/", http.StatusFound) +} + +// IsAuthenticated checks if the user is authenticated +func (as *AuthServer) IsAuthenticated(r *connectauth.Request) bool { + as.mu.Lock() + session, err := as.sessionStore.Get(r, "okta-hosted-login-session-store") + as.mu.Unlock() + + if err != nil || session.Values["id_token"] == nil || session.Values["id_token"] == "" { + return false + } + + return true +} + +// getProfileData retrieves the user's profile data +func (as *AuthServer) getProfileData(r *http.Request) (map[string]interface{}, error) { + as.mu.Lock() + session, err := as.sessionStore.Get(r, "okta-hosted-login-session-store") + as.mu.Unlock() + if err != nil { + return nil, err + } + + accessToken, ok := session.Values["access_token"].(string) + if !ok { + return nil, fmt.Errorf("no access token found in session") + } + + userInfo, err := as.oauth2Config.Client(r.Context(), &oauth2.Token{AccessToken: accessToken}).Get(os.Getenv("ISSUER") + "/v1/userinfo") + if err != nil { + return nil, fmt.Errorf("failed to get userinfo: %v", err) + } + defer userInfo.Body.Close() + + var profile map[string]interface{} + if err := json.NewDecoder(userInfo.Body).Decode(&profile); err != nil { + return nil, fmt.Errorf("failed to decode userinfo: %v", err) + } + + return profile, nil +} From d3c9608a26529dd0c8046649210e0ad6613b300b Mon Sep 17 00:00:00 2001 From: Yuvraj Date: Sun, 6 Oct 2024 20:10:31 +0530 Subject: [PATCH 02/17] added oauth2 config --- .env | 5 ++++- pkg/config/config.go | 8 ++++++++ server/root/main.go | 16 ++++++++++++---- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/.env b/.env index 7e92530..d73e163 100644 --- a/.env +++ b/.env @@ -9,4 +9,7 @@ DB_USERNAME=admin DB_PASSWORD="admin" DB_SSL_MODE=disable DB_POOL_MAX_CONNS=50 -TASK_TIME_OUT=3 \ No newline at end of file +TASK_TIME_OUT=3 +OAUTH2_ISSUER=https://dev-736553.okta.com +OAUTH2_CLIENT_ID=0oa93344234567890 +OAUTH2_CLIENT_SECRET=0oa93344234567890 \ No newline at end of file diff --git a/pkg/config/config.go b/pkg/config/config.go index d2ca66b..df9ff45 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -7,6 +7,7 @@ type Config struct { ServerPort string `envconfig:"SERVER_PORT" default:"8080"` WorkerCount int `envconfig:"WORKER_COUNT" default:"100"` Database DatabaseConfig + OAuth2 OAuth2Config } // DatabaseConfig holds the database connection configuration @@ -20,6 +21,13 @@ type DatabaseConfig struct { PoolMaxConns int `envconfig:"DB_POOL_MAX_CONNS" default:"1"` } +// OAuth2Config holds the OAuth2 configuration +type OAuth2Config struct { + Issuer string `envconfig:"OAUTH2_ISSUER"` + ClientID string `envconfig:"OAUTH2_CLIENT_ID"` + ClientSecret string `envconfig:"OAUTH2_CLIENT_SECRET"` +} + // ToMigrationUri returns a string for the migration package with the correct prefix func (d DatabaseConfig) ToMigrationUri() string { return fmt.Sprintf("pgx5://%s:%s@%s:%s/%s?sslmode=%s", diff --git a/server/root/main.go b/server/root/main.go index fe017c1..5cef315 100644 --- a/server/root/main.go +++ b/server/root/main.go @@ -39,6 +39,14 @@ type AuthCtx struct { Username string } +type Config struct { + OAuth2 struct { + Issuer string + ClientID string + ClientSecret string + } +} + // newCORS initializes CORS settings for the server // It allows all origins and methods, and exposes necessary headers for gRPC-Web func newCORS() *cors.Cors { @@ -113,10 +121,10 @@ func run() error { slog.Info("Database repository initialized", "workerCount", env.WorkerCount) auth, err := oauth2.NewAuthServer(oauth2.Config{ - Issuer: "https://fosite.my-application.com", - ClientID: "my-client", - ClientSecret: "my-secret", - RedirectURL: "http://localhost:8080/callback", + Issuer: env.OAuth2.Issuer, + ClientID: env.OAuth2.ClientID, + ClientSecret: env.OAuth2.ClientSecret, + RedirectURL: "/authorization-code/callback", SessionKey: "session", }) if err != nil { From 74c223b24c35a894991c195c2cd315107b6c832e Mon Sep 17 00:00:00 2001 From: Yuvraj Date: Sun, 6 Oct 2024 20:16:32 +0530 Subject: [PATCH 03/17] fix rpc auth --- server/route/oauth2/oauth2.go | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/server/route/oauth2/oauth2.go b/server/route/oauth2/oauth2.go index 1988155..499d8a2 100644 --- a/server/route/oauth2/oauth2.go +++ b/server/route/oauth2/oauth2.go @@ -8,6 +8,7 @@ import ( "fmt" "net/http" "os" + "strings" "sync" "github.com/coreos/go-oidc/v3/oidc" @@ -16,6 +17,10 @@ import ( "golang.org/x/oauth2" ) +const ( + sessionName = "custom-auth-session-store" +) + // Config represents the configuration for the AuthServer type Config struct { Issuer string @@ -113,14 +118,14 @@ func (as *AuthServer) AuthCodeCallbackHandler(w http.ResponseWriter, r *http.Req return } - idToken, err := as.verifier.Verify(r.Context(), rawIDToken) + _, err = as.verifier.Verify(r.Context(), rawIDToken) if err != nil { http.Error(w, "Failed to verify ID Token: "+err.Error(), http.StatusInternalServerError) return } as.mu.Lock() - session, err := as.sessionStore.Get(r, "okta-hosted-login-session-store") + session, err := as.sessionStore.Get(r, sessionName) as.mu.Unlock() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) @@ -142,7 +147,7 @@ func (as *AuthServer) AuthCodeCallbackHandler(w http.ResponseWriter, r *http.Req // LogoutHandler handles the logout request func (as *AuthServer) LogoutHandler(w http.ResponseWriter, r *http.Request) { as.mu.Lock() - session, err := as.sessionStore.Get(r, "okta-hosted-login-session-store") + session, err := as.sessionStore.Get(r, sessionName) as.mu.Unlock() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) @@ -163,15 +168,18 @@ func (as *AuthServer) LogoutHandler(w http.ResponseWriter, r *http.Request) { // IsAuthenticated checks if the user is authenticated func (as *AuthServer) IsAuthenticated(r *connectauth.Request) bool { - as.mu.Lock() - session, err := as.sessionStore.Get(r, "okta-hosted-login-session-store") - as.mu.Unlock() - - if err != nil || session.Values["id_token"] == nil || session.Values["id_token"] == "" { - return false - } - - return true + // Check for bearer token in the Authorization header + authHeader := r.Header.Get("Authorization") + if strings.HasPrefix(authHeader, "Bearer ") { + token := strings.TrimPrefix(authHeader, "Bearer ") + // Verify the token + _, err := as.verifier.Verify(context.Background(), token) + if err == nil { + return false + } + return true + } + return false } // getProfileData retrieves the user's profile data From c7724f406ba55dee465cd2932c396fbda37495a4 Mon Sep 17 00:00:00 2001 From: Yuvraj Date: Fri, 11 Oct 2024 05:29:14 +0530 Subject: [PATCH 04/17] added bidi stream --- .env | 82 +- buf.gen.yaml | 5 + cli/cmd/task.go | 70 +- go.mod | 9 +- go.sum | 21 + idl/cloud/v1/cloud.proto | 63 +- pkg/gen/cloud/v1/cloud.pb.go | 981 +++++++++++------- pkg/gen/cloud/v1/cloud.swagger.json | 82 +- pkg/gen/cloud/v1/cloud_grpc.pb.go | 373 +++++++ .../cloud/v1/cloudv1connect/cloud.connect.go | 27 + pkg/gen/index.html | 307 ++++++ pkg/gen/validate/validate.pb.go | 510 ++------- pkg/gen/validate/validate.swagger.json | 20 +- server/root/main.go | 30 +- server/route/oauth2/oauth2.go | 60 +- server/route/task.go | 23 + 16 files changed, 1825 insertions(+), 838 deletions(-) create mode 100644 pkg/gen/cloud/v1/cloud_grpc.pb.go diff --git a/.env b/.env index d73e163..7a13be8 100644 --- a/.env +++ b/.env @@ -10,6 +10,82 @@ DB_PASSWORD="admin" DB_SSL_MODE=disable DB_POOL_MAX_CONNS=50 TASK_TIME_OUT=3 -OAUTH2_ISSUER=https://dev-736553.okta.com -OAUTH2_CLIENT_ID=0oa93344234567890 -OAUTH2_CLIENT_SECRET=0oa93344234567890 \ No newline at end of file +# OAUTH2_ISSUER=https://dev-736553.okta.com +OAUTH2_CLIENT_ID=64660401062-s9nm4vp7esak8g9a6im8c9712jkk2lbb.apps.googleusercontent.com +OAUTH2_CLIENT_SECRET=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 + + + + + + + + + + + + + + + + + + + +# // Tasks 1M (run_query) +# // Worker Count per deployment = 10000 +# // Worker Deployment = 1M/10000 = 100 +# // Timeout = 4s +# // Retry Count = 5 +# // Delay between each retry = 2s +# // Max Time = 12s +# // Min Time = 4s +# // Failed Task = 20% (Cascade effect) Retry Required + +# Initial run: +# Successful tasks (800,000): 800,000 * 4s = 3,200,000s +# Failed tasks (200,000): 200,000 * 1s = 200,000s +# Total time for initial run: 3,400,000s + +# Retry 1: +# Total Retry Task Failed 1 = 200,000 * 20% = 40,000 +# Total Retry Task Success 1 = 200,000 - 40,000 = 160,000 +# Time: (160,000 * 4s) + (40,000 * (4s + 2s)) = 880,000s + +# Retry 2: +# Total Retry Task Failed 2 = 40,000 * 20% = 8,000 +# Total Retry Task Success 2 = 40,000 - 8,000 = 32,000 +# Time: (32,000 * 4s) + (8,000 * (4s + 2s + 2s)) = + +# Retry 3: +# Total Retry Task Failed 3 = 8,000 * 20% = 1,600 +# Total Retry Task Success 3 = 8,000 - 1,600 = 6,400 +# Time: (6,400 * 4s) + (1,600 * (4s + 2s + 2s + 2s)) = 41,600s + +# Retry 4: +# Total Retry Task Failed 4 = 1,600 * 20% = 320 +# Total Retry Task Success 4 = 1,600 - 320 = 1,280 +# Time: (1,280 * 4s) + (320 * (4s + 2s + 2s + 2s + 2s)) = 8,960s + +# Retry 5: +# Total Retry Task Failed 5 = 320 * 20% = 64 +# Total Retry Task Success 5 = 320 - 64 = 256 +# Time: (256 * 4s) + (64 * (4s + 2s + 2s + 2s + 2s + 2s)) = 1,920s + +# Total Task Failed = 64 +# Total Time = 3,400,000s + 880,000s + 192,000s + 41,600s + 8,960s + 1,920s = 4,424,480s +# Total Time in hours: 4,424,480s / 3600 ≈ 1,230.13 hours +# Total Time in days: 1,230.13 hours / 24 ≈ 51.25 days + + + + + +# // TODO(Reconcile Improvment): +# // To Scale up run the system with 1M Dummy Task and Get insights of the system behavior +# // 1. Track the retry number in history table along with task table +# // 2. Reconcile based on history of the task rather than task table +# // 3. Reconcile based on the time difference of state rather than fixed time interval +# // 4. Check the task latest state and find the time difference and reconcile based on that + + diff --git a/buf.gen.yaml b/buf.gen.yaml index 702964b..907fa20 100644 --- a/buf.gen.yaml +++ b/buf.gen.yaml @@ -11,6 +11,11 @@ plugins: out: pkg/gen/ opt: - paths=source_relative + - plugin: buf.build/grpc/go:v1.5.1 + out: pkg/gen/ + opt: + - paths=source_relative + - plugin: buf.build/protocolbuffers/go out: pkg/gen/ opt: diff --git a/cli/cmd/task.go b/cli/cmd/task.go index d4d5059..a3562d5 100644 --- a/cli/cmd/task.go +++ b/cli/cmd/task.go @@ -3,6 +3,7 @@ package cmd import ( "context" "fmt" + "io" "log/slog" "os" "strings" @@ -12,8 +13,11 @@ import ( // Add this import + "time" + "connectrpc.com/connect" "github.com/spf13/cobra" + "google.golang.org/grpc" ) // taskCmd represents the task command @@ -119,10 +123,74 @@ var taskStatusCmd = &cobra.Command{ }, } +// taskStatusCmd represents the task status command +var taskStreamCmd = &cobra.Command{ + Use: "stream", + Aliases: []string{}, + Short: "Stream task updates", + Long: `Continuously stream task updates from the server.`, + Example: ` task stream`, + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + // Create gRPC client + + conn, err := grpc.Dial("localhost:8080", grpc.WithInsecure()) + if err != nil { + slog.Error("Failed to connect to gRPC server", "error", err) + return err + } + defer conn.Close() + + client := v1.NewTaskManagementServiceClient(conn) + + ctx, cancel := context.WithCancel(cmd.Context()) + defer cancel() + + // Call StreamConnection + stream, err := client.StreamConnection(ctx) + if err != nil { + slog.Error("Failed to start stream", "error", err) + return fmt.Errorf("failed to start stream: %w", err) + } + + // Start a goroutine to send periodic requests + go func() { + ticker := time.NewTicker(5 * time.Second) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + if err := stream.Send(&v1.StreamRequest{Request: &v1.StreamRequest_RunCommand{RunCommand: &v1.RunCommand{}}}); err != nil { + slog.Error("Error sending request", "error", err) + cancel() + return + } + } + } + }() + + // Receive and print responses + for { + response, err := stream.Recv() + if err != nil { + if err == io.EOF || ctx.Err() != nil { + return nil + } + slog.Error("Error receiving response", "error", err) + return fmt.Errorf("error receiving response: %w", err) + } + fmt.Println(response) + } + }, +} + // init function to set up commands and flags func init() { - taskCmd.AddCommand(createTaskCmd, getTaskCmd, listTaskCmd, taskStatusCmd) + taskCmd.AddCommand(createTaskCmd, getTaskCmd, listTaskCmd, taskStatusCmd, taskStreamCmd) addCommonFlags := func(cmd *cobra.Command) { cmd.Flags().Int64P("id", "i", 0, "ID of the task") diff --git a/go.mod b/go.mod index 931f478..4ebb832 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,8 @@ require ( github.com/stretchr/testify v1.9.0 go.akshayshah.org/connectauth v0.6.0 golang.org/x/net v0.29.0 - golang.org/x/oauth2 v0.21.0 + golang.org/x/oauth2 v0.22.0 + google.golang.org/grpc v1.67.1 google.golang.org/protobuf v1.34.2 gopkg.in/yaml.v2 v2.4.0 gorm.io/driver/postgres v1.5.9 @@ -36,6 +37,7 @@ require ( require ( buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.34.2-20240920164238-5a7b106cbb87.2 // indirect + cloud.google.com/go/compute/metadata v0.5.0 // indirect github.com/antlr4-go/antlr/v4 v4.13.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect @@ -43,6 +45,7 @@ require ( github.com/go-jose/go-jose/v4 v4.0.2 // indirect github.com/go-logr/logr v1.3.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/google/cel-go v0.21.0 // indirect github.com/gorilla/securecookie v1.1.2 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -81,7 +84,7 @@ require ( golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.25.0 // indirect golang.org/x/text v0.18.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240401170217-c3f982113cda // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 45bce54..2cdd611 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,9 @@ buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.34.2-20240920164238-5a7b106cbb87.2 h1:hl0FrmGlNpQZIGvU1/jDz0lsPDd0BhCE0QDRwPfLZcA= buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.34.2-20240920164238-5a7b106cbb87.2/go.mod h1:ylS4c28ACSI59oJrOdW4pHS4n0Hw4TgSPHn8rpHl4Yw= +cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= +cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= +cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= connectrpc.com/connect v1.16.2 h1:ybd6y+ls7GOlb7Bh5C8+ghA6SvCBajHwxssO2CGFjqE= connectrpc.com/connect v1.16.2/go.mod h1:n2kgwskMHXC+lVqb18wngEpF95ldBHXjZYJussz5FRc= connectrpc.com/grpchealth v1.3.0 h1:FA3OIwAvuMokQIXQrY5LbIy8IenftksTP/lG4PbYN+E= @@ -33,8 +37,12 @@ github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/cel-go v0.21.0 h1:cl6uW/gxN+Hy50tNYvI691+sXxioCnstFzLp2WO4GCI= github.com/google/cel-go v0.21.0/go.mod h1:rHUlWCcBKgyEk+eV03RPdZUekPp6YcJwV0FxuUksYxc= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 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= @@ -162,16 +170,29 @@ 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.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= +golang.org/x/oauth2 v0.22.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.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/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto/googleapis/api v0.0.0-20240401170217-c3f982113cda h1:b6F6WIV4xHHD0FA4oIyzU6mHWg2WI2X1RBehwa5QN38= google.golang.org/genproto/googleapis/api v0.0.0-20240401170217-c3f982113cda/go.mod h1:AHcE/gZH76Bk/ROZhQphlRoWo5xKDEtz3eVEO1LfA8c= +google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 h1:wKguEg1hsxI2/L3hUYrpo1RVi48K+uTyzKqprwLXsb8= +google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo= google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda h1:LI5DOvAxUPMv/50agcLLoo+AdWc1irS9Rzz4vPuD1V4= google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= +google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= +google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/idl/cloud/v1/cloud.proto b/idl/cloud/v1/cloud.proto index b7652c5..8b4f5f2 100644 --- a/idl/cloud/v1/cloud.proto +++ b/idl/cloud/v1/cloud.proto @@ -178,9 +178,70 @@ service TaskManagementService { // Retrieves the count of tasks for each status. // Returns a GetStatusResponse containing a map of status counts. - rpc GetStatus(GetStatusRequest) returns (GetStatusResponse) {} + rpc GetStatus(GetStatusRequest) returns (GetStatusResponse) {} + + rpc StreamConnection(stream StreamRequest) returns (stream StreamResponse) {} +} + +// Message for stream requests +message StreamRequest { + oneof request { + Heartbeat heartbeat = 1; + RunCommand run_command = 2; + } +} + +// Message for stream responses +message StreamResponse { + oneof response { + Heartbeat heartbeat = 1; + CommandResult command_result = 2; + } +} + +// Heartbeat message for keeping the connection alive +message Heartbeat { + // Timestamp of the heartbeat, in ISO 8601 format (UTC) + string timestamp = 1 [(validate.rules).string = { + pattern: "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$" + }]; +} + +// RunCommand message for executing commands +message RunCommand { + // Unique identifier for the command + string command_id = 1 [(validate.rules).string = { + pattern: "^[a-zA-Z0-9_-]+$", + max_len: 255 + }]; + + // The command to be executed + string command = 2 [(validate.rules).string = { + min_len: 1, + max_len: 1000 + }]; +} + +// CommandResult message for returning command execution results +message CommandResult { + // Unique identifier of the executed command + string command_id = 1 [(validate.rules).string = { + pattern: "^[a-zA-Z0-9_-]+$", + max_len: 255 + }]; + + // Result of the command execution + string result = 2; + + // Status of the command execution + enum Status { + SUCCESS = 0; + FAILURE = 1; + } + Status status = 3; } + // Message for GetStatus request (empty) message GetStatusRequest {} diff --git a/pkg/gen/cloud/v1/cloud.pb.go b/pkg/gen/cloud/v1/cloud.pb.go index db2d24e..56615bc 100644 --- a/pkg/gen/cloud/v1/cloud.pb.go +++ b/pkg/gen/cloud/v1/cloud.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.2 +// protoc-gen-go v1.35.1 // protoc (unknown) // source: cloud/v1/cloud.proto @@ -81,6 +81,53 @@ func (TaskStatusEnum) EnumDescriptor() ([]byte, []int) { return file_cloud_v1_cloud_proto_rawDescGZIP(), []int{0} } +// Status of the command execution +type CommandResult_Status int32 + +const ( + CommandResult_SUCCESS CommandResult_Status = 0 + CommandResult_FAILURE CommandResult_Status = 1 +) + +// Enum value maps for CommandResult_Status. +var ( + CommandResult_Status_name = map[int32]string{ + 0: "SUCCESS", + 1: "FAILURE", + } + CommandResult_Status_value = map[string]int32{ + "SUCCESS": 0, + "FAILURE": 1, + } +) + +func (x CommandResult_Status) Enum() *CommandResult_Status { + p := new(CommandResult_Status) + *p = x + return p +} + +func (x CommandResult_Status) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (CommandResult_Status) Descriptor() protoreflect.EnumDescriptor { + return file_cloud_v1_cloud_proto_enumTypes[1].Descriptor() +} + +func (CommandResult_Status) Type() protoreflect.EnumType { + return &file_cloud_v1_cloud_proto_enumTypes[1] +} + +func (x CommandResult_Status) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use CommandResult_Status.Descriptor instead. +func (CommandResult_Status) EnumDescriptor() ([]byte, []int) { + return file_cloud_v1_cloud_proto_rawDescGZIP(), []int{13, 0} +} + // Message for Task Payload type Payload struct { state protoimpl.MessageState @@ -95,11 +142,9 @@ type Payload struct { func (x *Payload) Reset() { *x = Payload{} - if protoimpl.UnsafeEnabled { - mi := &file_cloud_v1_cloud_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_cloud_v1_cloud_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Payload) String() string { @@ -110,7 +155,7 @@ func (*Payload) ProtoMessage() {} func (x *Payload) ProtoReflect() protoreflect.Message { mi := &file_cloud_v1_cloud_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -155,11 +200,9 @@ type CreateTaskRequest struct { func (x *CreateTaskRequest) Reset() { *x = CreateTaskRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_cloud_v1_cloud_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_cloud_v1_cloud_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *CreateTaskRequest) String() string { @@ -170,7 +213,7 @@ func (*CreateTaskRequest) ProtoMessage() {} func (x *CreateTaskRequest) ProtoReflect() protoreflect.Message { mi := &file_cloud_v1_cloud_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -225,11 +268,9 @@ type CreateTaskResponse struct { func (x *CreateTaskResponse) Reset() { *x = CreateTaskResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_cloud_v1_cloud_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_cloud_v1_cloud_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *CreateTaskResponse) String() string { @@ -240,7 +281,7 @@ func (*CreateTaskResponse) ProtoMessage() {} func (x *CreateTaskResponse) ProtoReflect() protoreflect.Message { mi := &file_cloud_v1_cloud_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -294,11 +335,9 @@ type Task struct { func (x *Task) Reset() { *x = Task{} - if protoimpl.UnsafeEnabled { - mi := &file_cloud_v1_cloud_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_cloud_v1_cloud_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Task) String() string { @@ -309,7 +348,7 @@ func (*Task) ProtoMessage() {} func (x *Task) ProtoReflect() protoreflect.Message { mi := &file_cloud_v1_cloud_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -406,11 +445,9 @@ type TaskHistory struct { func (x *TaskHistory) Reset() { *x = TaskHistory{} - if protoimpl.UnsafeEnabled { - mi := &file_cloud_v1_cloud_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_cloud_v1_cloud_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *TaskHistory) String() string { @@ -421,7 +458,7 @@ func (*TaskHistory) ProtoMessage() {} func (x *TaskHistory) ProtoReflect() protoreflect.Message { mi := &file_cloud_v1_cloud_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -476,11 +513,9 @@ type GetTaskRequest struct { func (x *GetTaskRequest) Reset() { *x = GetTaskRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_cloud_v1_cloud_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_cloud_v1_cloud_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *GetTaskRequest) String() string { @@ -491,7 +526,7 @@ func (*GetTaskRequest) ProtoMessage() {} func (x *GetTaskRequest) ProtoReflect() protoreflect.Message { mi := &file_cloud_v1_cloud_proto_msgTypes[5] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -525,11 +560,9 @@ type GetTaskHistoryRequest struct { func (x *GetTaskHistoryRequest) Reset() { *x = GetTaskHistoryRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_cloud_v1_cloud_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_cloud_v1_cloud_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *GetTaskHistoryRequest) String() string { @@ -540,7 +573,7 @@ func (*GetTaskHistoryRequest) ProtoMessage() {} func (x *GetTaskHistoryRequest) ProtoReflect() protoreflect.Message { mi := &file_cloud_v1_cloud_proto_msgTypes[6] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -575,11 +608,9 @@ type GetTaskHistoryResponse struct { func (x *GetTaskHistoryResponse) Reset() { *x = GetTaskHistoryResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_cloud_v1_cloud_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_cloud_v1_cloud_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *GetTaskHistoryResponse) String() string { @@ -590,7 +621,7 @@ func (*GetTaskHistoryResponse) ProtoMessage() {} func (x *GetTaskHistoryResponse) ProtoReflect() protoreflect.Message { mi := &file_cloud_v1_cloud_proto_msgTypes[7] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -628,11 +659,9 @@ type UpdateTaskStatusRequest struct { func (x *UpdateTaskStatusRequest) Reset() { *x = UpdateTaskStatusRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_cloud_v1_cloud_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_cloud_v1_cloud_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *UpdateTaskStatusRequest) String() string { @@ -643,7 +672,7 @@ func (*UpdateTaskStatusRequest) ProtoMessage() {} func (x *UpdateTaskStatusRequest) ProtoReflect() protoreflect.Message { mi := &file_cloud_v1_cloud_proto_msgTypes[8] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -679,6 +708,333 @@ func (x *UpdateTaskStatusRequest) GetMessage() string { return "" } +// Message for stream requests +type StreamRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Request: + // + // *StreamRequest_Heartbeat + // *StreamRequest_RunCommand + Request isStreamRequest_Request `protobuf_oneof:"request"` +} + +func (x *StreamRequest) Reset() { + *x = StreamRequest{} + mi := &file_cloud_v1_cloud_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *StreamRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StreamRequest) ProtoMessage() {} + +func (x *StreamRequest) ProtoReflect() protoreflect.Message { + mi := &file_cloud_v1_cloud_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StreamRequest.ProtoReflect.Descriptor instead. +func (*StreamRequest) Descriptor() ([]byte, []int) { + return file_cloud_v1_cloud_proto_rawDescGZIP(), []int{9} +} + +func (m *StreamRequest) GetRequest() isStreamRequest_Request { + if m != nil { + return m.Request + } + return nil +} + +func (x *StreamRequest) GetHeartbeat() *Heartbeat { + if x, ok := x.GetRequest().(*StreamRequest_Heartbeat); ok { + return x.Heartbeat + } + return nil +} + +func (x *StreamRequest) GetRunCommand() *RunCommand { + if x, ok := x.GetRequest().(*StreamRequest_RunCommand); ok { + return x.RunCommand + } + return nil +} + +type isStreamRequest_Request interface { + isStreamRequest_Request() +} + +type StreamRequest_Heartbeat struct { + Heartbeat *Heartbeat `protobuf:"bytes,1,opt,name=heartbeat,proto3,oneof"` +} + +type StreamRequest_RunCommand struct { + RunCommand *RunCommand `protobuf:"bytes,2,opt,name=run_command,json=runCommand,proto3,oneof"` +} + +func (*StreamRequest_Heartbeat) isStreamRequest_Request() {} + +func (*StreamRequest_RunCommand) isStreamRequest_Request() {} + +// Message for stream responses +type StreamResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Response: + // + // *StreamResponse_Heartbeat + // *StreamResponse_CommandResult + Response isStreamResponse_Response `protobuf_oneof:"response"` +} + +func (x *StreamResponse) Reset() { + *x = StreamResponse{} + mi := &file_cloud_v1_cloud_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *StreamResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StreamResponse) ProtoMessage() {} + +func (x *StreamResponse) ProtoReflect() protoreflect.Message { + mi := &file_cloud_v1_cloud_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StreamResponse.ProtoReflect.Descriptor instead. +func (*StreamResponse) Descriptor() ([]byte, []int) { + return file_cloud_v1_cloud_proto_rawDescGZIP(), []int{10} +} + +func (m *StreamResponse) GetResponse() isStreamResponse_Response { + if m != nil { + return m.Response + } + return nil +} + +func (x *StreamResponse) GetHeartbeat() *Heartbeat { + if x, ok := x.GetResponse().(*StreamResponse_Heartbeat); ok { + return x.Heartbeat + } + return nil +} + +func (x *StreamResponse) GetCommandResult() *CommandResult { + if x, ok := x.GetResponse().(*StreamResponse_CommandResult); ok { + return x.CommandResult + } + return nil +} + +type isStreamResponse_Response interface { + isStreamResponse_Response() +} + +type StreamResponse_Heartbeat struct { + Heartbeat *Heartbeat `protobuf:"bytes,1,opt,name=heartbeat,proto3,oneof"` +} + +type StreamResponse_CommandResult struct { + CommandResult *CommandResult `protobuf:"bytes,2,opt,name=command_result,json=commandResult,proto3,oneof"` +} + +func (*StreamResponse_Heartbeat) isStreamResponse_Response() {} + +func (*StreamResponse_CommandResult) isStreamResponse_Response() {} + +// Heartbeat message for keeping the connection alive +type Heartbeat struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Timestamp of the heartbeat, in ISO 8601 format (UTC) + Timestamp string `protobuf:"bytes,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"` +} + +func (x *Heartbeat) Reset() { + *x = Heartbeat{} + mi := &file_cloud_v1_cloud_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Heartbeat) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Heartbeat) ProtoMessage() {} + +func (x *Heartbeat) ProtoReflect() protoreflect.Message { + mi := &file_cloud_v1_cloud_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Heartbeat.ProtoReflect.Descriptor instead. +func (*Heartbeat) Descriptor() ([]byte, []int) { + return file_cloud_v1_cloud_proto_rawDescGZIP(), []int{11} +} + +func (x *Heartbeat) GetTimestamp() string { + if x != nil { + return x.Timestamp + } + return "" +} + +// RunCommand message for executing commands +type RunCommand struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Unique identifier for the command + CommandId string `protobuf:"bytes,1,opt,name=command_id,json=commandId,proto3" json:"command_id,omitempty"` + // The command to be executed + Command string `protobuf:"bytes,2,opt,name=command,proto3" json:"command,omitempty"` +} + +func (x *RunCommand) Reset() { + *x = RunCommand{} + mi := &file_cloud_v1_cloud_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RunCommand) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RunCommand) ProtoMessage() {} + +func (x *RunCommand) ProtoReflect() protoreflect.Message { + mi := &file_cloud_v1_cloud_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RunCommand.ProtoReflect.Descriptor instead. +func (*RunCommand) Descriptor() ([]byte, []int) { + return file_cloud_v1_cloud_proto_rawDescGZIP(), []int{12} +} + +func (x *RunCommand) GetCommandId() string { + if x != nil { + return x.CommandId + } + return "" +} + +func (x *RunCommand) GetCommand() string { + if x != nil { + return x.Command + } + return "" +} + +// CommandResult message for returning command execution results +type CommandResult struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Unique identifier of the executed command + CommandId string `protobuf:"bytes,1,opt,name=command_id,json=commandId,proto3" json:"command_id,omitempty"` + // Result of the command execution + Result string `protobuf:"bytes,2,opt,name=result,proto3" json:"result,omitempty"` + Status CommandResult_Status `protobuf:"varint,3,opt,name=status,proto3,enum=cloud.v1.CommandResult_Status" json:"status,omitempty"` +} + +func (x *CommandResult) Reset() { + *x = CommandResult{} + mi := &file_cloud_v1_cloud_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CommandResult) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CommandResult) ProtoMessage() {} + +func (x *CommandResult) ProtoReflect() protoreflect.Message { + mi := &file_cloud_v1_cloud_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CommandResult.ProtoReflect.Descriptor instead. +func (*CommandResult) Descriptor() ([]byte, []int) { + return file_cloud_v1_cloud_proto_rawDescGZIP(), []int{13} +} + +func (x *CommandResult) GetCommandId() string { + if x != nil { + return x.CommandId + } + return "" +} + +func (x *CommandResult) GetResult() string { + if x != nil { + return x.Result + } + return "" +} + +func (x *CommandResult) GetStatus() CommandResult_Status { + if x != nil { + return x.Status + } + return CommandResult_SUCCESS +} + // Message for GetStatus request (empty) type GetStatusRequest struct { state protoimpl.MessageState @@ -688,11 +1044,9 @@ type GetStatusRequest struct { func (x *GetStatusRequest) Reset() { *x = GetStatusRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_cloud_v1_cloud_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_cloud_v1_cloud_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *GetStatusRequest) String() string { @@ -702,8 +1056,8 @@ func (x *GetStatusRequest) String() string { func (*GetStatusRequest) ProtoMessage() {} func (x *GetStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_cloud_v1_cloud_proto_msgTypes[9] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_cloud_v1_cloud_proto_msgTypes[14] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -715,7 +1069,7 @@ func (x *GetStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetStatusRequest.ProtoReflect.Descriptor instead. func (*GetStatusRequest) Descriptor() ([]byte, []int) { - return file_cloud_v1_cloud_proto_rawDescGZIP(), []int{9} + return file_cloud_v1_cloud_proto_rawDescGZIP(), []int{14} } // Message for GetStatus response @@ -730,11 +1084,9 @@ type GetStatusResponse struct { func (x *GetStatusResponse) Reset() { *x = GetStatusResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_cloud_v1_cloud_proto_msgTypes[10] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_cloud_v1_cloud_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *GetStatusResponse) String() string { @@ -744,8 +1096,8 @@ func (x *GetStatusResponse) String() string { func (*GetStatusResponse) ProtoMessage() {} func (x *GetStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_cloud_v1_cloud_proto_msgTypes[10] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_cloud_v1_cloud_proto_msgTypes[15] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -757,7 +1109,7 @@ func (x *GetStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetStatusResponse.ProtoReflect.Descriptor instead. func (*GetStatusResponse) Descriptor() ([]byte, []int) { - return file_cloud_v1_cloud_proto_rawDescGZIP(), []int{10} + return file_cloud_v1_cloud_proto_rawDescGZIP(), []int{15} } func (x *GetStatusResponse) GetStatusCounts() map[int32]int64 { @@ -778,11 +1130,9 @@ type TaskList struct { func (x *TaskList) Reset() { *x = TaskList{} - if protoimpl.UnsafeEnabled { - mi := &file_cloud_v1_cloud_proto_msgTypes[11] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_cloud_v1_cloud_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *TaskList) String() string { @@ -792,8 +1142,8 @@ func (x *TaskList) String() string { func (*TaskList) ProtoMessage() {} func (x *TaskList) ProtoReflect() protoreflect.Message { - mi := &file_cloud_v1_cloud_proto_msgTypes[11] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_cloud_v1_cloud_proto_msgTypes[16] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -805,7 +1155,7 @@ func (x *TaskList) ProtoReflect() protoreflect.Message { // Deprecated: Use TaskList.ProtoReflect.Descriptor instead. func (*TaskList) Descriptor() ([]byte, []int) { - return file_cloud_v1_cloud_proto_rawDescGZIP(), []int{11} + return file_cloud_v1_cloud_proto_rawDescGZIP(), []int{16} } func (x *TaskList) GetTasks() []*Task { @@ -836,11 +1186,9 @@ type TaskListRequest struct { func (x *TaskListRequest) Reset() { *x = TaskListRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_cloud_v1_cloud_proto_msgTypes[12] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_cloud_v1_cloud_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *TaskListRequest) String() string { @@ -850,8 +1198,8 @@ func (x *TaskListRequest) String() string { func (*TaskListRequest) ProtoMessage() {} func (x *TaskListRequest) ProtoReflect() protoreflect.Message { - mi := &file_cloud_v1_cloud_proto_msgTypes[12] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_cloud_v1_cloud_proto_msgTypes[17] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -863,7 +1211,7 @@ func (x *TaskListRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use TaskListRequest.ProtoReflect.Descriptor instead. func (*TaskListRequest) Descriptor() ([]byte, []int) { - return file_cloud_v1_cloud_proto_rawDescGZIP(), []int{12} + return file_cloud_v1_cloud_proto_rawDescGZIP(), []int{17} } func (x *TaskListRequest) GetLimit() int32 { @@ -989,78 +1337,126 @@ var file_cloud_v1_cloud_proto_rawDesc = []byte{ 0x6d, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x82, 0x01, 0x02, 0x10, 0x01, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x22, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x72, 0x03, 0x18, 0xd0, 0x0f, 0x52, 0x07, - 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x12, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xa8, 0x01, 0x0a, 0x11, - 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x52, 0x0a, 0x0d, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, - 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x75, 0x6e, - 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0c, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, - 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x1a, 0x3f, 0x0a, 0x11, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, - 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, - 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x30, 0x0a, 0x08, 0x54, 0x61, 0x73, 0x6b, 0x4c, 0x69, - 0x73, 0x74, 0x12, 0x24, 0x0a, 0x05, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x0e, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x73, - 0x6b, 0x52, 0x05, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x22, 0xd5, 0x01, 0x0a, 0x0f, 0x54, 0x61, 0x73, - 0x6b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x05, - 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x42, 0x09, 0xfa, 0x42, 0x06, - 0x1a, 0x04, 0x18, 0x64, 0x28, 0x01, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x1f, 0x0a, - 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x42, 0x07, 0xfa, - 0x42, 0x04, 0x1a, 0x02, 0x28, 0x00, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x35, - 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, - 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x45, 0x6e, 0x75, 0x6d, 0x48, 0x00, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x88, 0x01, 0x01, 0x12, 0x35, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x42, 0x1c, 0xfa, 0x42, 0x19, 0x72, 0x17, 0x52, 0x0a, 0x73, 0x65, 0x6e, 0x64, - 0x5f, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x52, 0x09, 0x72, 0x75, 0x6e, 0x5f, 0x71, 0x75, 0x65, 0x72, - 0x79, 0x48, 0x01, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, - 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x74, 0x79, 0x70, 0x65, - 0x2a, 0x5a, 0x0a, 0x0e, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x45, 0x6e, - 0x75, 0x6d, 0x12, 0x0a, 0x0a, 0x06, 0x51, 0x55, 0x45, 0x55, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0b, - 0x0a, 0x07, 0x52, 0x55, 0x4e, 0x4e, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x46, - 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x55, 0x43, 0x43, 0x45, - 0x45, 0x44, 0x45, 0x44, 0x10, 0x03, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, - 0x4e, 0x10, 0x04, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x4c, 0x4c, 0x10, 0x05, 0x32, 0xc7, 0x03, 0x0a, - 0x15, 0x54, 0x61, 0x73, 0x6b, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x49, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x54, 0x61, 0x73, 0x6b, 0x12, 0x1b, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, - 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x1c, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x00, 0x12, 0x35, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x18, 0x2e, 0x63, - 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, - 0x31, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x22, 0x00, 0x12, 0x3c, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, - 0x54, 0x61, 0x73, 0x6b, 0x73, 0x12, 0x19, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, - 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x12, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x73, 0x6b, - 0x4c, 0x69, 0x73, 0x74, 0x22, 0x00, 0x12, 0x55, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x54, 0x61, 0x73, - 0x6b, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x1f, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, - 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x48, 0x69, 0x73, 0x74, 0x6f, - 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x63, 0x6c, 0x6f, 0x75, - 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x48, 0x69, 0x73, 0x74, - 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4f, 0x0a, - 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x12, 0x21, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x46, - 0x0a, 0x09, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1a, 0x2e, 0x63, 0x6c, - 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, - 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x7a, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x2e, 0x63, 0x6c, - 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x42, 0x0a, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x50, 0x72, 0x6f, - 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x1d, 0x74, 0x61, 0x73, 0x6b, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x67, - 0x65, 0x6e, 0x2f, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x76, 0x31, 0x3b, 0x63, 0x6c, 0x6f, 0x75, - 0x64, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x43, 0x58, 0x58, 0xaa, 0x02, 0x08, 0x43, 0x6c, 0x6f, 0x75, - 0x64, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x08, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x5c, 0x56, 0x31, 0xe2, - 0x02, 0x14, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, - 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x09, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x3a, 0x3a, - 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x88, 0x01, 0x0a, 0x0d, 0x53, 0x74, 0x72, 0x65, + 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x33, 0x0a, 0x09, 0x68, 0x65, 0x61, + 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x63, + 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, + 0x74, 0x48, 0x00, 0x52, 0x09, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x12, 0x37, + 0x0a, 0x0b, 0x72, 0x75, 0x6e, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x52, + 0x75, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x48, 0x00, 0x52, 0x0a, 0x72, 0x75, 0x6e, + 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x42, 0x09, 0x0a, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x22, 0x93, 0x01, 0x0a, 0x0e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x09, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, + 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, + 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x48, 0x00, 0x52, + 0x09, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x12, 0x40, 0x0a, 0x0e, 0x63, 0x6f, + 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, + 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x48, 0x00, 0x52, 0x0d, 0x63, + 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x42, 0x0a, 0x0a, 0x08, + 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x58, 0x0a, 0x09, 0x48, 0x65, 0x61, 0x72, + 0x74, 0x62, 0x65, 0x61, 0x74, 0x12, 0x4b, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x2d, 0xfa, 0x42, 0x2a, 0x72, 0x28, 0x32, + 0x26, 0x5e, 0x5c, 0x64, 0x7b, 0x34, 0x7d, 0x2d, 0x5c, 0x64, 0x7b, 0x32, 0x7d, 0x2d, 0x5c, 0x64, + 0x7b, 0x32, 0x7d, 0x54, 0x5c, 0x64, 0x7b, 0x32, 0x7d, 0x3a, 0x5c, 0x64, 0x7b, 0x32, 0x7d, 0x3a, + 0x5c, 0x64, 0x7b, 0x32, 0x7d, 0x5a, 0x24, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x22, 0x6d, 0x0a, 0x0a, 0x52, 0x75, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, + 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x42, 0x1a, 0xfa, 0x42, 0x17, 0x72, 0x15, 0x18, 0xff, 0x01, 0x32, 0x10, + 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5f, 0x2d, 0x5d, 0x2b, 0x24, + 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x07, 0x63, + 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xfa, 0x42, + 0x07, 0x72, 0x05, 0x10, 0x01, 0x18, 0xe8, 0x07, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, + 0x64, 0x22, 0xbe, 0x01, 0x0a, 0x0d, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, + 0x75, 0x6c, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1a, 0xfa, 0x42, 0x17, 0x72, 0x15, 0x18, 0xff, + 0x01, 0x32, 0x10, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5f, 0x2d, + 0x5d, 0x2b, 0x24, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x16, + 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x36, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1e, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, + 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x2e, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x22, + 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x55, 0x43, 0x43, + 0x45, 0x53, 0x53, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, + 0x10, 0x01, 0x22, 0x12, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xa8, 0x01, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0d, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x47, + 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x52, 0x0c, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x73, + 0x1a, 0x3f, 0x0a, 0x11, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x73, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, + 0x01, 0x22, 0x30, 0x0a, 0x08, 0x54, 0x61, 0x73, 0x6b, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x24, 0x0a, + 0x05, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x63, + 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x05, 0x74, 0x61, + 0x73, 0x6b, 0x73, 0x22, 0xd5, 0x01, 0x0a, 0x0f, 0x54, 0x61, 0x73, 0x6b, 0x4c, 0x69, 0x73, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x42, 0x09, 0xfa, 0x42, 0x06, 0x1a, 0x04, 0x18, 0x64, 0x28, + 0x01, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x1f, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, + 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x1a, 0x02, 0x28, + 0x00, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x35, 0x0a, 0x06, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x63, 0x6c, 0x6f, 0x75, + 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x45, + 0x6e, 0x75, 0x6d, 0x48, 0x00, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x88, 0x01, 0x01, + 0x12, 0x35, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1c, + 0xfa, 0x42, 0x19, 0x72, 0x17, 0x52, 0x0a, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x65, 0x6d, 0x61, 0x69, + 0x6c, 0x52, 0x09, 0x72, 0x75, 0x6e, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x48, 0x01, 0x52, 0x04, + 0x74, 0x79, 0x70, 0x65, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x2a, 0x5a, 0x0a, 0x0e, 0x54, + 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x45, 0x6e, 0x75, 0x6d, 0x12, 0x0a, 0x0a, + 0x06, 0x51, 0x55, 0x45, 0x55, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x52, 0x55, 0x4e, + 0x4e, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, + 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x55, 0x43, 0x43, 0x45, 0x45, 0x44, 0x45, 0x44, 0x10, + 0x03, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x04, 0x12, 0x07, + 0x0a, 0x03, 0x41, 0x4c, 0x4c, 0x10, 0x05, 0x32, 0x94, 0x04, 0x0a, 0x15, 0x54, 0x61, 0x73, 0x6b, + 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x12, 0x49, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x12, + 0x1b, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x63, + 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, + 0x73, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x35, 0x0a, 0x07, + 0x47, 0x65, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x18, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, + 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x0e, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x73, + 0x6b, 0x22, 0x00, 0x12, 0x3c, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x73, + 0x12, 0x19, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x73, 0x6b, + 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x63, 0x6c, + 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x4c, 0x69, 0x73, 0x74, 0x22, + 0x00, 0x12, 0x55, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x48, 0x69, 0x73, 0x74, + 0x6f, 0x72, 0x79, 0x12, 0x1f, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x47, + 0x65, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, + 0x47, 0x65, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4f, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x21, 0x2e, 0x63, + 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x61, + 0x73, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x09, 0x47, 0x65, 0x74, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1a, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, + 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, + 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x00, 0x12, 0x4b, 0x0a, 0x10, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x17, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, + 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, + 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x42, 0x7a, + 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x42, 0x0a, + 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x1d, 0x74, 0x61, + 0x73, 0x6b, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x63, 0x6c, 0x6f, 0x75, 0x64, + 0x2f, 0x76, 0x31, 0x3b, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x43, 0x58, + 0x58, 0xaa, 0x02, 0x08, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x08, 0x43, + 0x6c, 0x6f, 0x75, 0x64, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x14, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x5c, + 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, + 0x09, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, } var ( @@ -1075,55 +1471,68 @@ func file_cloud_v1_cloud_proto_rawDescGZIP() []byte { return file_cloud_v1_cloud_proto_rawDescData } -var file_cloud_v1_cloud_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_cloud_v1_cloud_proto_msgTypes = make([]protoimpl.MessageInfo, 15) +var file_cloud_v1_cloud_proto_enumTypes = make([]protoimpl.EnumInfo, 2) +var file_cloud_v1_cloud_proto_msgTypes = make([]protoimpl.MessageInfo, 20) var file_cloud_v1_cloud_proto_goTypes = []any{ (TaskStatusEnum)(0), // 0: cloud.v1.TaskStatusEnum - (*Payload)(nil), // 1: cloud.v1.Payload - (*CreateTaskRequest)(nil), // 2: cloud.v1.CreateTaskRequest - (*CreateTaskResponse)(nil), // 3: cloud.v1.CreateTaskResponse - (*Task)(nil), // 4: cloud.v1.Task - (*TaskHistory)(nil), // 5: cloud.v1.TaskHistory - (*GetTaskRequest)(nil), // 6: cloud.v1.GetTaskRequest - (*GetTaskHistoryRequest)(nil), // 7: cloud.v1.GetTaskHistoryRequest - (*GetTaskHistoryResponse)(nil), // 8: cloud.v1.GetTaskHistoryResponse - (*UpdateTaskStatusRequest)(nil), // 9: cloud.v1.UpdateTaskStatusRequest - (*GetStatusRequest)(nil), // 10: cloud.v1.GetStatusRequest - (*GetStatusResponse)(nil), // 11: cloud.v1.GetStatusResponse - (*TaskList)(nil), // 12: cloud.v1.TaskList - (*TaskListRequest)(nil), // 13: cloud.v1.TaskListRequest - nil, // 14: cloud.v1.Payload.ParametersEntry - nil, // 15: cloud.v1.GetStatusResponse.StatusCountsEntry - (*emptypb.Empty)(nil), // 16: google.protobuf.Empty + (CommandResult_Status)(0), // 1: cloud.v1.CommandResult.Status + (*Payload)(nil), // 2: cloud.v1.Payload + (*CreateTaskRequest)(nil), // 3: cloud.v1.CreateTaskRequest + (*CreateTaskResponse)(nil), // 4: cloud.v1.CreateTaskResponse + (*Task)(nil), // 5: cloud.v1.Task + (*TaskHistory)(nil), // 6: cloud.v1.TaskHistory + (*GetTaskRequest)(nil), // 7: cloud.v1.GetTaskRequest + (*GetTaskHistoryRequest)(nil), // 8: cloud.v1.GetTaskHistoryRequest + (*GetTaskHistoryResponse)(nil), // 9: cloud.v1.GetTaskHistoryResponse + (*UpdateTaskStatusRequest)(nil), // 10: cloud.v1.UpdateTaskStatusRequest + (*StreamRequest)(nil), // 11: cloud.v1.StreamRequest + (*StreamResponse)(nil), // 12: cloud.v1.StreamResponse + (*Heartbeat)(nil), // 13: cloud.v1.Heartbeat + (*RunCommand)(nil), // 14: cloud.v1.RunCommand + (*CommandResult)(nil), // 15: cloud.v1.CommandResult + (*GetStatusRequest)(nil), // 16: cloud.v1.GetStatusRequest + (*GetStatusResponse)(nil), // 17: cloud.v1.GetStatusResponse + (*TaskList)(nil), // 18: cloud.v1.TaskList + (*TaskListRequest)(nil), // 19: cloud.v1.TaskListRequest + nil, // 20: cloud.v1.Payload.ParametersEntry + nil, // 21: cloud.v1.GetStatusResponse.StatusCountsEntry + (*emptypb.Empty)(nil), // 22: google.protobuf.Empty } var file_cloud_v1_cloud_proto_depIdxs = []int32{ - 14, // 0: cloud.v1.Payload.parameters:type_name -> cloud.v1.Payload.ParametersEntry - 1, // 1: cloud.v1.CreateTaskRequest.payload:type_name -> cloud.v1.Payload + 20, // 0: cloud.v1.Payload.parameters:type_name -> cloud.v1.Payload.ParametersEntry + 2, // 1: cloud.v1.CreateTaskRequest.payload:type_name -> cloud.v1.Payload 0, // 2: cloud.v1.Task.status:type_name -> cloud.v1.TaskStatusEnum - 1, // 3: cloud.v1.Task.payload:type_name -> cloud.v1.Payload + 2, // 3: cloud.v1.Task.payload:type_name -> cloud.v1.Payload 0, // 4: cloud.v1.TaskHistory.status:type_name -> cloud.v1.TaskStatusEnum - 5, // 5: cloud.v1.GetTaskHistoryResponse.history:type_name -> cloud.v1.TaskHistory + 6, // 5: cloud.v1.GetTaskHistoryResponse.history:type_name -> cloud.v1.TaskHistory 0, // 6: cloud.v1.UpdateTaskStatusRequest.status:type_name -> cloud.v1.TaskStatusEnum - 15, // 7: cloud.v1.GetStatusResponse.status_counts:type_name -> cloud.v1.GetStatusResponse.StatusCountsEntry - 4, // 8: cloud.v1.TaskList.tasks:type_name -> cloud.v1.Task - 0, // 9: cloud.v1.TaskListRequest.status:type_name -> cloud.v1.TaskStatusEnum - 2, // 10: cloud.v1.TaskManagementService.CreateTask:input_type -> cloud.v1.CreateTaskRequest - 6, // 11: cloud.v1.TaskManagementService.GetTask:input_type -> cloud.v1.GetTaskRequest - 13, // 12: cloud.v1.TaskManagementService.ListTasks:input_type -> cloud.v1.TaskListRequest - 7, // 13: cloud.v1.TaskManagementService.GetTaskHistory:input_type -> cloud.v1.GetTaskHistoryRequest - 9, // 14: cloud.v1.TaskManagementService.UpdateTaskStatus:input_type -> cloud.v1.UpdateTaskStatusRequest - 10, // 15: cloud.v1.TaskManagementService.GetStatus:input_type -> cloud.v1.GetStatusRequest - 3, // 16: cloud.v1.TaskManagementService.CreateTask:output_type -> cloud.v1.CreateTaskResponse - 4, // 17: cloud.v1.TaskManagementService.GetTask:output_type -> cloud.v1.Task - 12, // 18: cloud.v1.TaskManagementService.ListTasks:output_type -> cloud.v1.TaskList - 8, // 19: cloud.v1.TaskManagementService.GetTaskHistory:output_type -> cloud.v1.GetTaskHistoryResponse - 16, // 20: cloud.v1.TaskManagementService.UpdateTaskStatus:output_type -> google.protobuf.Empty - 11, // 21: cloud.v1.TaskManagementService.GetStatus:output_type -> cloud.v1.GetStatusResponse - 16, // [16:22] is the sub-list for method output_type - 10, // [10:16] is the sub-list for method input_type - 10, // [10:10] is the sub-list for extension type_name - 10, // [10:10] is the sub-list for extension extendee - 0, // [0:10] is the sub-list for field type_name + 13, // 7: cloud.v1.StreamRequest.heartbeat:type_name -> cloud.v1.Heartbeat + 14, // 8: cloud.v1.StreamRequest.run_command:type_name -> cloud.v1.RunCommand + 13, // 9: cloud.v1.StreamResponse.heartbeat:type_name -> cloud.v1.Heartbeat + 15, // 10: cloud.v1.StreamResponse.command_result:type_name -> cloud.v1.CommandResult + 1, // 11: cloud.v1.CommandResult.status:type_name -> cloud.v1.CommandResult.Status + 21, // 12: cloud.v1.GetStatusResponse.status_counts:type_name -> cloud.v1.GetStatusResponse.StatusCountsEntry + 5, // 13: cloud.v1.TaskList.tasks:type_name -> cloud.v1.Task + 0, // 14: cloud.v1.TaskListRequest.status:type_name -> cloud.v1.TaskStatusEnum + 3, // 15: cloud.v1.TaskManagementService.CreateTask:input_type -> cloud.v1.CreateTaskRequest + 7, // 16: cloud.v1.TaskManagementService.GetTask:input_type -> cloud.v1.GetTaskRequest + 19, // 17: cloud.v1.TaskManagementService.ListTasks:input_type -> cloud.v1.TaskListRequest + 8, // 18: cloud.v1.TaskManagementService.GetTaskHistory:input_type -> cloud.v1.GetTaskHistoryRequest + 10, // 19: cloud.v1.TaskManagementService.UpdateTaskStatus:input_type -> cloud.v1.UpdateTaskStatusRequest + 16, // 20: cloud.v1.TaskManagementService.GetStatus:input_type -> cloud.v1.GetStatusRequest + 11, // 21: cloud.v1.TaskManagementService.StreamConnection:input_type -> cloud.v1.StreamRequest + 4, // 22: cloud.v1.TaskManagementService.CreateTask:output_type -> cloud.v1.CreateTaskResponse + 5, // 23: cloud.v1.TaskManagementService.GetTask:output_type -> cloud.v1.Task + 18, // 24: cloud.v1.TaskManagementService.ListTasks:output_type -> cloud.v1.TaskList + 9, // 25: cloud.v1.TaskManagementService.GetTaskHistory:output_type -> cloud.v1.GetTaskHistoryResponse + 22, // 26: cloud.v1.TaskManagementService.UpdateTaskStatus:output_type -> google.protobuf.Empty + 17, // 27: cloud.v1.TaskManagementService.GetStatus:output_type -> cloud.v1.GetStatusResponse + 12, // 28: cloud.v1.TaskManagementService.StreamConnection:output_type -> cloud.v1.StreamResponse + 22, // [22:29] is the sub-list for method output_type + 15, // [15:22] is the sub-list for method input_type + 15, // [15:15] is the sub-list for extension type_name + 15, // [15:15] is the sub-list for extension extendee + 0, // [0:15] is the sub-list for field type_name } func init() { file_cloud_v1_cloud_proto_init() } @@ -1131,172 +1540,22 @@ func file_cloud_v1_cloud_proto_init() { if File_cloud_v1_cloud_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_cloud_v1_cloud_proto_msgTypes[0].Exporter = func(v any, i int) any { - switch v := v.(*Payload); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cloud_v1_cloud_proto_msgTypes[1].Exporter = func(v any, i int) any { - switch v := v.(*CreateTaskRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cloud_v1_cloud_proto_msgTypes[2].Exporter = func(v any, i int) any { - switch v := v.(*CreateTaskResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cloud_v1_cloud_proto_msgTypes[3].Exporter = func(v any, i int) any { - switch v := v.(*Task); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cloud_v1_cloud_proto_msgTypes[4].Exporter = func(v any, i int) any { - switch v := v.(*TaskHistory); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cloud_v1_cloud_proto_msgTypes[5].Exporter = func(v any, i int) any { - switch v := v.(*GetTaskRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cloud_v1_cloud_proto_msgTypes[6].Exporter = func(v any, i int) any { - switch v := v.(*GetTaskHistoryRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cloud_v1_cloud_proto_msgTypes[7].Exporter = func(v any, i int) any { - switch v := v.(*GetTaskHistoryResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cloud_v1_cloud_proto_msgTypes[8].Exporter = func(v any, i int) any { - switch v := v.(*UpdateTaskStatusRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cloud_v1_cloud_proto_msgTypes[9].Exporter = func(v any, i int) any { - switch v := v.(*GetStatusRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cloud_v1_cloud_proto_msgTypes[10].Exporter = func(v any, i int) any { - switch v := v.(*GetStatusResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cloud_v1_cloud_proto_msgTypes[11].Exporter = func(v any, i int) any { - switch v := v.(*TaskList); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cloud_v1_cloud_proto_msgTypes[12].Exporter = func(v any, i int) any { - switch v := v.(*TaskListRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } + file_cloud_v1_cloud_proto_msgTypes[9].OneofWrappers = []any{ + (*StreamRequest_Heartbeat)(nil), + (*StreamRequest_RunCommand)(nil), + } + file_cloud_v1_cloud_proto_msgTypes[10].OneofWrappers = []any{ + (*StreamResponse_Heartbeat)(nil), + (*StreamResponse_CommandResult)(nil), } - file_cloud_v1_cloud_proto_msgTypes[12].OneofWrappers = []any{} + file_cloud_v1_cloud_proto_msgTypes[17].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_cloud_v1_cloud_proto_rawDesc, - NumEnums: 1, - NumMessages: 15, + NumEnums: 2, + NumMessages: 20, NumExtensions: 0, NumServices: 1, }, diff --git a/pkg/gen/cloud/v1/cloud.swagger.json b/pkg/gen/cloud/v1/cloud.swagger.json index 6654d08..27ac710 100644 --- a/pkg/gen/cloud/v1/cloud.swagger.json +++ b/pkg/gen/cloud/v1/cloud.swagger.json @@ -17,16 +17,7 @@ ], "paths": {}, "definitions": { - "protobufAny": { - "type": "object", - "properties": { - "@type": { - "type": "string" - } - }, - "additionalProperties": {} - }, - "rpcStatus": { + "googlerpcStatus": { "type": "object", "properties": { "code": { @@ -45,6 +36,41 @@ } } }, + "protobufAny": { + "type": "object", + "properties": { + "@type": { + "type": "string" + } + }, + "additionalProperties": {} + }, + "v1CommandResult": { + "type": "object", + "properties": { + "commandId": { + "type": "string", + "title": "Unique identifier of the executed command" + }, + "result": { + "type": "string", + "title": "Result of the command execution" + }, + "status": { + "$ref": "#/definitions/v1CommandResultStatus" + } + }, + "title": "CommandResult message for returning command execution results" + }, + "v1CommandResultStatus": { + "type": "string", + "enum": [ + "SUCCESS", + "FAILURE" + ], + "default": "SUCCESS", + "title": "Status of the command execution" + }, "v1CreateTaskResponse": { "type": "object", "properties": { @@ -84,6 +110,16 @@ }, "title": "Message for Task history response" }, + "v1Heartbeat": { + "type": "object", + "properties": { + "timestamp": { + "type": "string", + "title": "Timestamp of the heartbeat, in ISO 8601 format (UTC)" + } + }, + "title": "Heartbeat message for keeping the connection alive" + }, "v1Payload": { "type": "object", "properties": { @@ -97,6 +133,32 @@ }, "title": "Message for Task Payload" }, + "v1RunCommand": { + "type": "object", + "properties": { + "commandId": { + "type": "string", + "title": "Unique identifier for the command" + }, + "command": { + "type": "string", + "title": "The command to be executed" + } + }, + "title": "RunCommand message for executing commands" + }, + "v1StreamResponse": { + "type": "object", + "properties": { + "heartbeat": { + "$ref": "#/definitions/v1Heartbeat" + }, + "commandResult": { + "$ref": "#/definitions/v1CommandResult" + } + }, + "title": "Message for stream responses" + }, "v1Task": { "type": "object", "properties": { diff --git a/pkg/gen/cloud/v1/cloud_grpc.pb.go b/pkg/gen/cloud/v1/cloud_grpc.pb.go new file mode 100644 index 0000000..d8c0419 --- /dev/null +++ b/pkg/gen/cloud/v1/cloud_grpc.pb.go @@ -0,0 +1,373 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.5.1 +// - protoc (unknown) +// source: cloud/v1/cloud.proto + +package cloudv1 + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + emptypb "google.golang.org/protobuf/types/known/emptypb" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + TaskManagementService_CreateTask_FullMethodName = "/cloud.v1.TaskManagementService/CreateTask" + TaskManagementService_GetTask_FullMethodName = "/cloud.v1.TaskManagementService/GetTask" + TaskManagementService_ListTasks_FullMethodName = "/cloud.v1.TaskManagementService/ListTasks" + TaskManagementService_GetTaskHistory_FullMethodName = "/cloud.v1.TaskManagementService/GetTaskHistory" + TaskManagementService_UpdateTaskStatus_FullMethodName = "/cloud.v1.TaskManagementService/UpdateTaskStatus" + TaskManagementService_GetStatus_FullMethodName = "/cloud.v1.TaskManagementService/GetStatus" + TaskManagementService_StreamConnection_FullMethodName = "/cloud.v1.TaskManagementService/StreamConnection" +) + +// TaskManagementServiceClient is the client API for TaskManagementService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +// +// Task Management service definition +type TaskManagementServiceClient interface { + // Creates a new task based on the provided request. + // Returns a CreateTaskResponse containing the unique identifier of the created task. + CreateTask(ctx context.Context, in *CreateTaskRequest, opts ...grpc.CallOption) (*CreateTaskResponse, error) + // Retrieves the current status and details of the specified task. + // Returns a Task message containing all information about the requested task. + GetTask(ctx context.Context, in *GetTaskRequest, opts ...grpc.CallOption) (*Task, error) + // Lists tasks currently available in the system, with pagination support. + // Returns a TaskList containing the requested subset of tasks. + ListTasks(ctx context.Context, in *TaskListRequest, opts ...grpc.CallOption) (*TaskList, error) + // Retrieves the execution history of the specified task. + // Returns a GetTaskHistoryResponse containing a list of historical status updates. + GetTaskHistory(ctx context.Context, in *GetTaskHistoryRequest, opts ...grpc.CallOption) (*GetTaskHistoryResponse, error) + // Updates the status of the specified task. + // Returns an empty response to confirm the update was processed. + UpdateTaskStatus(ctx context.Context, in *UpdateTaskStatusRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + // Retrieves the count of tasks for each status. + // Returns a GetStatusResponse containing a map of status counts. + GetStatus(ctx context.Context, in *GetStatusRequest, opts ...grpc.CallOption) (*GetStatusResponse, error) + StreamConnection(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[StreamRequest, StreamResponse], error) +} + +type taskManagementServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewTaskManagementServiceClient(cc grpc.ClientConnInterface) TaskManagementServiceClient { + return &taskManagementServiceClient{cc} +} + +func (c *taskManagementServiceClient) CreateTask(ctx context.Context, in *CreateTaskRequest, opts ...grpc.CallOption) (*CreateTaskResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CreateTaskResponse) + err := c.cc.Invoke(ctx, TaskManagementService_CreateTask_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *taskManagementServiceClient) GetTask(ctx context.Context, in *GetTaskRequest, opts ...grpc.CallOption) (*Task, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Task) + err := c.cc.Invoke(ctx, TaskManagementService_GetTask_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *taskManagementServiceClient) ListTasks(ctx context.Context, in *TaskListRequest, opts ...grpc.CallOption) (*TaskList, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(TaskList) + err := c.cc.Invoke(ctx, TaskManagementService_ListTasks_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *taskManagementServiceClient) GetTaskHistory(ctx context.Context, in *GetTaskHistoryRequest, opts ...grpc.CallOption) (*GetTaskHistoryResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetTaskHistoryResponse) + err := c.cc.Invoke(ctx, TaskManagementService_GetTaskHistory_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *taskManagementServiceClient) UpdateTaskStatus(ctx context.Context, in *UpdateTaskStatusRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, TaskManagementService_UpdateTaskStatus_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *taskManagementServiceClient) GetStatus(ctx context.Context, in *GetStatusRequest, opts ...grpc.CallOption) (*GetStatusResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetStatusResponse) + err := c.cc.Invoke(ctx, TaskManagementService_GetStatus_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *taskManagementServiceClient) StreamConnection(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[StreamRequest, StreamResponse], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &TaskManagementService_ServiceDesc.Streams[0], TaskManagementService_StreamConnection_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[StreamRequest, StreamResponse]{ClientStream: stream} + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type TaskManagementService_StreamConnectionClient = grpc.BidiStreamingClient[StreamRequest, StreamResponse] + +// TaskManagementServiceServer is the server API for TaskManagementService service. +// All implementations must embed UnimplementedTaskManagementServiceServer +// for forward compatibility. +// +// Task Management service definition +type TaskManagementServiceServer interface { + // Creates a new task based on the provided request. + // Returns a CreateTaskResponse containing the unique identifier of the created task. + CreateTask(context.Context, *CreateTaskRequest) (*CreateTaskResponse, error) + // Retrieves the current status and details of the specified task. + // Returns a Task message containing all information about the requested task. + GetTask(context.Context, *GetTaskRequest) (*Task, error) + // Lists tasks currently available in the system, with pagination support. + // Returns a TaskList containing the requested subset of tasks. + ListTasks(context.Context, *TaskListRequest) (*TaskList, error) + // Retrieves the execution history of the specified task. + // Returns a GetTaskHistoryResponse containing a list of historical status updates. + GetTaskHistory(context.Context, *GetTaskHistoryRequest) (*GetTaskHistoryResponse, error) + // Updates the status of the specified task. + // Returns an empty response to confirm the update was processed. + UpdateTaskStatus(context.Context, *UpdateTaskStatusRequest) (*emptypb.Empty, error) + // Retrieves the count of tasks for each status. + // Returns a GetStatusResponse containing a map of status counts. + GetStatus(context.Context, *GetStatusRequest) (*GetStatusResponse, error) + StreamConnection(grpc.BidiStreamingServer[StreamRequest, StreamResponse]) error + mustEmbedUnimplementedTaskManagementServiceServer() +} + +// UnimplementedTaskManagementServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedTaskManagementServiceServer struct{} + +func (UnimplementedTaskManagementServiceServer) CreateTask(context.Context, *CreateTaskRequest) (*CreateTaskResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateTask not implemented") +} +func (UnimplementedTaskManagementServiceServer) GetTask(context.Context, *GetTaskRequest) (*Task, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetTask not implemented") +} +func (UnimplementedTaskManagementServiceServer) ListTasks(context.Context, *TaskListRequest) (*TaskList, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListTasks not implemented") +} +func (UnimplementedTaskManagementServiceServer) GetTaskHistory(context.Context, *GetTaskHistoryRequest) (*GetTaskHistoryResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetTaskHistory not implemented") +} +func (UnimplementedTaskManagementServiceServer) UpdateTaskStatus(context.Context, *UpdateTaskStatusRequest) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateTaskStatus not implemented") +} +func (UnimplementedTaskManagementServiceServer) GetStatus(context.Context, *GetStatusRequest) (*GetStatusResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetStatus not implemented") +} +func (UnimplementedTaskManagementServiceServer) StreamConnection(grpc.BidiStreamingServer[StreamRequest, StreamResponse]) error { + return status.Errorf(codes.Unimplemented, "method StreamConnection not implemented") +} +func (UnimplementedTaskManagementServiceServer) mustEmbedUnimplementedTaskManagementServiceServer() {} +func (UnimplementedTaskManagementServiceServer) testEmbeddedByValue() {} + +// UnsafeTaskManagementServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to TaskManagementServiceServer will +// result in compilation errors. +type UnsafeTaskManagementServiceServer interface { + mustEmbedUnimplementedTaskManagementServiceServer() +} + +func RegisterTaskManagementServiceServer(s grpc.ServiceRegistrar, srv TaskManagementServiceServer) { + // If the following call pancis, it indicates UnimplementedTaskManagementServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&TaskManagementService_ServiceDesc, srv) +} + +func _TaskManagementService_CreateTask_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateTaskRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TaskManagementServiceServer).CreateTask(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: TaskManagementService_CreateTask_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TaskManagementServiceServer).CreateTask(ctx, req.(*CreateTaskRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _TaskManagementService_GetTask_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetTaskRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TaskManagementServiceServer).GetTask(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: TaskManagementService_GetTask_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TaskManagementServiceServer).GetTask(ctx, req.(*GetTaskRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _TaskManagementService_ListTasks_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(TaskListRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TaskManagementServiceServer).ListTasks(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: TaskManagementService_ListTasks_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TaskManagementServiceServer).ListTasks(ctx, req.(*TaskListRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _TaskManagementService_GetTaskHistory_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetTaskHistoryRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TaskManagementServiceServer).GetTaskHistory(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: TaskManagementService_GetTaskHistory_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TaskManagementServiceServer).GetTaskHistory(ctx, req.(*GetTaskHistoryRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _TaskManagementService_UpdateTaskStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateTaskStatusRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TaskManagementServiceServer).UpdateTaskStatus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: TaskManagementService_UpdateTaskStatus_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TaskManagementServiceServer).UpdateTaskStatus(ctx, req.(*UpdateTaskStatusRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _TaskManagementService_GetStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetStatusRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TaskManagementServiceServer).GetStatus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: TaskManagementService_GetStatus_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TaskManagementServiceServer).GetStatus(ctx, req.(*GetStatusRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _TaskManagementService_StreamConnection_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(TaskManagementServiceServer).StreamConnection(&grpc.GenericServerStream[StreamRequest, StreamResponse]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type TaskManagementService_StreamConnectionServer = grpc.BidiStreamingServer[StreamRequest, StreamResponse] + +// TaskManagementService_ServiceDesc is the grpc.ServiceDesc for TaskManagementService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var TaskManagementService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "cloud.v1.TaskManagementService", + HandlerType: (*TaskManagementServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "CreateTask", + Handler: _TaskManagementService_CreateTask_Handler, + }, + { + MethodName: "GetTask", + Handler: _TaskManagementService_GetTask_Handler, + }, + { + MethodName: "ListTasks", + Handler: _TaskManagementService_ListTasks_Handler, + }, + { + MethodName: "GetTaskHistory", + Handler: _TaskManagementService_GetTaskHistory_Handler, + }, + { + MethodName: "UpdateTaskStatus", + Handler: _TaskManagementService_UpdateTaskStatus_Handler, + }, + { + MethodName: "GetStatus", + Handler: _TaskManagementService_GetStatus_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "StreamConnection", + Handler: _TaskManagementService_StreamConnection_Handler, + ServerStreams: true, + ClientStreams: true, + }, + }, + Metadata: "cloud/v1/cloud.proto", +} diff --git a/pkg/gen/cloud/v1/cloudv1connect/cloud.connect.go b/pkg/gen/cloud/v1/cloudv1connect/cloud.connect.go index 4466f59..8b0b7f2 100644 --- a/pkg/gen/cloud/v1/cloudv1connect/cloud.connect.go +++ b/pkg/gen/cloud/v1/cloudv1connect/cloud.connect.go @@ -52,6 +52,9 @@ const ( // TaskManagementServiceGetStatusProcedure is the fully-qualified name of the // TaskManagementService's GetStatus RPC. TaskManagementServiceGetStatusProcedure = "/cloud.v1.TaskManagementService/GetStatus" + // TaskManagementServiceStreamConnectionProcedure is the fully-qualified name of the + // TaskManagementService's StreamConnection RPC. + TaskManagementServiceStreamConnectionProcedure = "/cloud.v1.TaskManagementService/StreamConnection" ) // TaskManagementServiceClient is a client for the cloud.v1.TaskManagementService service. @@ -74,6 +77,7 @@ type TaskManagementServiceClient interface { // Retrieves the count of tasks for each status. // Returns a GetStatusResponse containing a map of status counts. GetStatus(context.Context, *connect.Request[v1.GetStatusRequest]) (*connect.Response[v1.GetStatusResponse], error) + StreamConnection(context.Context) *connect.BidiStreamForClient[v1.StreamRequest, v1.StreamResponse] } // NewTaskManagementServiceClient constructs a client for the cloud.v1.TaskManagementService @@ -116,6 +120,11 @@ func NewTaskManagementServiceClient(httpClient connect.HTTPClient, baseURL strin baseURL+TaskManagementServiceGetStatusProcedure, opts..., ), + streamConnection: connect.NewClient[v1.StreamRequest, v1.StreamResponse]( + httpClient, + baseURL+TaskManagementServiceStreamConnectionProcedure, + opts..., + ), } } @@ -127,6 +136,7 @@ type taskManagementServiceClient struct { getTaskHistory *connect.Client[v1.GetTaskHistoryRequest, v1.GetTaskHistoryResponse] updateTaskStatus *connect.Client[v1.UpdateTaskStatusRequest, emptypb.Empty] getStatus *connect.Client[v1.GetStatusRequest, v1.GetStatusResponse] + streamConnection *connect.Client[v1.StreamRequest, v1.StreamResponse] } // CreateTask calls cloud.v1.TaskManagementService.CreateTask. @@ -159,6 +169,11 @@ func (c *taskManagementServiceClient) GetStatus(ctx context.Context, req *connec return c.getStatus.CallUnary(ctx, req) } +// StreamConnection calls cloud.v1.TaskManagementService.StreamConnection. +func (c *taskManagementServiceClient) StreamConnection(ctx context.Context) *connect.BidiStreamForClient[v1.StreamRequest, v1.StreamResponse] { + return c.streamConnection.CallBidiStream(ctx) +} + // TaskManagementServiceHandler is an implementation of the cloud.v1.TaskManagementService service. type TaskManagementServiceHandler interface { // Creates a new task based on the provided request. @@ -179,6 +194,7 @@ type TaskManagementServiceHandler interface { // Retrieves the count of tasks for each status. // Returns a GetStatusResponse containing a map of status counts. GetStatus(context.Context, *connect.Request[v1.GetStatusRequest]) (*connect.Response[v1.GetStatusResponse], error) + StreamConnection(context.Context, *connect.BidiStream[v1.StreamRequest, v1.StreamResponse]) error } // NewTaskManagementServiceHandler builds an HTTP handler from the service implementation. It @@ -217,6 +233,11 @@ func NewTaskManagementServiceHandler(svc TaskManagementServiceHandler, opts ...c svc.GetStatus, opts..., ) + taskManagementServiceStreamConnectionHandler := connect.NewBidiStreamHandler( + TaskManagementServiceStreamConnectionProcedure, + svc.StreamConnection, + opts..., + ) return "/cloud.v1.TaskManagementService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case TaskManagementServiceCreateTaskProcedure: @@ -231,6 +252,8 @@ func NewTaskManagementServiceHandler(svc TaskManagementServiceHandler, opts ...c taskManagementServiceUpdateTaskStatusHandler.ServeHTTP(w, r) case TaskManagementServiceGetStatusProcedure: taskManagementServiceGetStatusHandler.ServeHTTP(w, r) + case TaskManagementServiceStreamConnectionProcedure: + taskManagementServiceStreamConnectionHandler.ServeHTTP(w, r) default: http.NotFound(w, r) } @@ -263,3 +286,7 @@ func (UnimplementedTaskManagementServiceHandler) UpdateTaskStatus(context.Contex func (UnimplementedTaskManagementServiceHandler) GetStatus(context.Context, *connect.Request[v1.GetStatusRequest]) (*connect.Response[v1.GetStatusResponse], error) { return nil, connect.NewError(connect.CodeUnimplemented, errors.New("cloud.v1.TaskManagementService.GetStatus is not implemented")) } + +func (UnimplementedTaskManagementServiceHandler) StreamConnection(context.Context, *connect.BidiStream[v1.StreamRequest, v1.StreamResponse]) error { + return connect.NewError(connect.CodeUnimplemented, errors.New("cloud.v1.TaskManagementService.StreamConnection is not implemented")) +} diff --git a/pkg/gen/index.html b/pkg/gen/index.html index 4160ae0..c00cafa 100644 --- a/pkg/gen/index.html +++ b/pkg/gen/index.html @@ -301,6 +301,10 @@

Table of Contents

cloud/v1/cloud.proto
    +
  • + MCommandResult +
  • +
  • MCreateTaskRequest
  • @@ -333,6 +337,10 @@

    Table of Contents

    MGetTaskRequest +
  • + MHeartbeat +
  • +
  • MPayload
  • @@ -341,6 +349,18 @@

    Table of Contents

    MPayload.ParametersEntry +
  • + MRunCommand +
  • + +
  • + MStreamRequest +
  • + +
  • + MStreamResponse +
  • +
  • MTask
  • @@ -362,6 +382,10 @@

    Table of Contents

    +
  • + ECommandResult.Status +
  • +
  • ETaskStatusEnum
  • @@ -2423,6 +2447,73 @@

    cloud/v1/cloud.proto

    Top

    +

    CommandResult

    +

    CommandResult message for returning command execution results

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldTypeLabelDescription
    command_idstring

    Unique identifier of the executed command

    resultstring

    Result of the command execution

    statusCommandResult.Status

    + + + + +

    Validated Fields

    + + + + + + + + + + + + + + + +
    FieldValidations
    command_id +
      + +
    • string.max_len: 255
    • + +
    • string.pattern: ^[a-zA-Z0-9_-]+$
    • + +
    +
    + + + + +

    CreateTaskRequest

    Message for Task creation request

    @@ -2804,6 +2895,57 @@

    Validated Fields

    +

    Heartbeat

    +

    Heartbeat message for keeping the connection alive

    + + + + + + + + + + + + + + + + +
    FieldTypeLabelDescription
    timestampstring

    Timestamp of the heartbeat, in ISO 8601 format (UTC)

    + + + + +

    Validated Fields

    + + + + + + + + + + + + + + + +
    FieldValidations
    timestamp +
      + +
    • string.pattern: ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$
    • + +
    +
    + + + + +

    Payload

    Message for Task Payload

    @@ -2890,6 +3032,141 @@

    Payload.ParametersEntry

    +

    RunCommand

    +

    RunCommand message for executing commands

    + + + + + + + + + + + + + + + + + + + + + + + +
    FieldTypeLabelDescription
    command_idstring

    Unique identifier for the command

    commandstring

    The command to be executed

    + + + + +

    Validated Fields

    + + + + + + + + + + + + + + + + + + + + +
    FieldValidations
    command_id +
      + +
    • string.max_len: 255
    • + +
    • string.pattern: ^[a-zA-Z0-9_-]+$
    • + +
    +
    command +
      + +
    • string.min_len: 1
    • + +
    • string.max_len: 1000
    • + +
    +
    + + + + + +

    StreamRequest

    +

    Message for stream requests

    + + + + + + + + + + + + + + + + + + + + + + + +
    FieldTypeLabelDescription
    heartbeatHeartbeat

    run_commandRunCommand

    + + + + + +

    StreamResponse

    +

    Message for stream responses

    + + + + + + + + + + + + + + + + + + + + + + + +
    FieldTypeLabelDescription
    heartbeatHeartbeat

    command_resultCommandResult

    + + + + +

    Task

    Message for Task status

    @@ -3411,6 +3688,29 @@

    Validated Fields

    +

    CommandResult.Status

    +

    Status of the command execution

    + + + + + + + + + + + + + + + + + + + +
    NameNumberDescription
    SUCCESS0

    FAILURE1

    +

    TaskStatusEnum

    Enum for Task statuses

    @@ -3518,6 +3818,13 @@

    TaskManagementService

    Returns a GetStatusResponse containing a map of status counts.

    + + + + + + +
    StreamConnectionStreamRequest streamStreamResponse stream

    diff --git a/pkg/gen/validate/validate.pb.go b/pkg/gen/validate/validate.pb.go index 31f91c2..9b28a1f 100644 --- a/pkg/gen/validate/validate.pb.go +++ b/pkg/gen/validate/validate.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.2 +// protoc-gen-go v1.35.1 // protoc (unknown) // source: validate/validate.proto @@ -121,11 +121,9 @@ type FieldRules struct { func (x *FieldRules) Reset() { *x = FieldRules{} - if protoimpl.UnsafeEnabled { - mi := &file_validate_validate_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_validate_validate_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *FieldRules) String() string { @@ -136,7 +134,7 @@ func (*FieldRules) ProtoMessage() {} func (x *FieldRules) ProtoReflect() protoreflect.Message { mi := &file_validate_validate_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -480,11 +478,9 @@ type FloatRules struct { func (x *FloatRules) Reset() { *x = FloatRules{} - if protoimpl.UnsafeEnabled { - mi := &file_validate_validate_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_validate_validate_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *FloatRules) String() string { @@ -495,7 +491,7 @@ func (*FloatRules) ProtoMessage() {} func (x *FloatRules) ProtoReflect() protoreflect.Message { mi := &file_validate_validate_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -601,11 +597,9 @@ type DoubleRules struct { func (x *DoubleRules) Reset() { *x = DoubleRules{} - if protoimpl.UnsafeEnabled { - mi := &file_validate_validate_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_validate_validate_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *DoubleRules) String() string { @@ -616,7 +610,7 @@ func (*DoubleRules) ProtoMessage() {} func (x *DoubleRules) ProtoReflect() protoreflect.Message { mi := &file_validate_validate_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -722,11 +716,9 @@ type Int32Rules struct { func (x *Int32Rules) Reset() { *x = Int32Rules{} - if protoimpl.UnsafeEnabled { - mi := &file_validate_validate_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_validate_validate_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Int32Rules) String() string { @@ -737,7 +729,7 @@ func (*Int32Rules) ProtoMessage() {} func (x *Int32Rules) ProtoReflect() protoreflect.Message { mi := &file_validate_validate_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -843,11 +835,9 @@ type Int64Rules struct { func (x *Int64Rules) Reset() { *x = Int64Rules{} - if protoimpl.UnsafeEnabled { - mi := &file_validate_validate_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_validate_validate_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Int64Rules) String() string { @@ -858,7 +848,7 @@ func (*Int64Rules) ProtoMessage() {} func (x *Int64Rules) ProtoReflect() protoreflect.Message { mi := &file_validate_validate_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -964,11 +954,9 @@ type UInt32Rules struct { func (x *UInt32Rules) Reset() { *x = UInt32Rules{} - if protoimpl.UnsafeEnabled { - mi := &file_validate_validate_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_validate_validate_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *UInt32Rules) String() string { @@ -979,7 +967,7 @@ func (*UInt32Rules) ProtoMessage() {} func (x *UInt32Rules) ProtoReflect() protoreflect.Message { mi := &file_validate_validate_proto_msgTypes[5] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1085,11 +1073,9 @@ type UInt64Rules struct { func (x *UInt64Rules) Reset() { *x = UInt64Rules{} - if protoimpl.UnsafeEnabled { - mi := &file_validate_validate_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_validate_validate_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *UInt64Rules) String() string { @@ -1100,7 +1086,7 @@ func (*UInt64Rules) ProtoMessage() {} func (x *UInt64Rules) ProtoReflect() protoreflect.Message { mi := &file_validate_validate_proto_msgTypes[6] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1206,11 +1192,9 @@ type SInt32Rules struct { func (x *SInt32Rules) Reset() { *x = SInt32Rules{} - if protoimpl.UnsafeEnabled { - mi := &file_validate_validate_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_validate_validate_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *SInt32Rules) String() string { @@ -1221,7 +1205,7 @@ func (*SInt32Rules) ProtoMessage() {} func (x *SInt32Rules) ProtoReflect() protoreflect.Message { mi := &file_validate_validate_proto_msgTypes[7] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1327,11 +1311,9 @@ type SInt64Rules struct { func (x *SInt64Rules) Reset() { *x = SInt64Rules{} - if protoimpl.UnsafeEnabled { - mi := &file_validate_validate_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_validate_validate_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *SInt64Rules) String() string { @@ -1342,7 +1324,7 @@ func (*SInt64Rules) ProtoMessage() {} func (x *SInt64Rules) ProtoReflect() protoreflect.Message { mi := &file_validate_validate_proto_msgTypes[8] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1448,11 +1430,9 @@ type Fixed32Rules struct { func (x *Fixed32Rules) Reset() { *x = Fixed32Rules{} - if protoimpl.UnsafeEnabled { - mi := &file_validate_validate_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_validate_validate_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Fixed32Rules) String() string { @@ -1463,7 +1443,7 @@ func (*Fixed32Rules) ProtoMessage() {} func (x *Fixed32Rules) ProtoReflect() protoreflect.Message { mi := &file_validate_validate_proto_msgTypes[9] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1569,11 +1549,9 @@ type Fixed64Rules struct { func (x *Fixed64Rules) Reset() { *x = Fixed64Rules{} - if protoimpl.UnsafeEnabled { - mi := &file_validate_validate_proto_msgTypes[10] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_validate_validate_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Fixed64Rules) String() string { @@ -1584,7 +1562,7 @@ func (*Fixed64Rules) ProtoMessage() {} func (x *Fixed64Rules) ProtoReflect() protoreflect.Message { mi := &file_validate_validate_proto_msgTypes[10] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1690,11 +1668,9 @@ type SFixed32Rules struct { func (x *SFixed32Rules) Reset() { *x = SFixed32Rules{} - if protoimpl.UnsafeEnabled { - mi := &file_validate_validate_proto_msgTypes[11] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_validate_validate_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *SFixed32Rules) String() string { @@ -1705,7 +1681,7 @@ func (*SFixed32Rules) ProtoMessage() {} func (x *SFixed32Rules) ProtoReflect() protoreflect.Message { mi := &file_validate_validate_proto_msgTypes[11] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1811,11 +1787,9 @@ type SFixed64Rules struct { func (x *SFixed64Rules) Reset() { *x = SFixed64Rules{} - if protoimpl.UnsafeEnabled { - mi := &file_validate_validate_proto_msgTypes[12] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_validate_validate_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *SFixed64Rules) String() string { @@ -1826,7 +1800,7 @@ func (*SFixed64Rules) ProtoMessage() {} func (x *SFixed64Rules) ProtoReflect() protoreflect.Message { mi := &file_validate_validate_proto_msgTypes[12] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1909,11 +1883,9 @@ type BoolRules struct { func (x *BoolRules) Reset() { *x = BoolRules{} - if protoimpl.UnsafeEnabled { - mi := &file_validate_validate_proto_msgTypes[13] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_validate_validate_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *BoolRules) String() string { @@ -1924,7 +1896,7 @@ func (*BoolRules) ProtoMessage() {} func (x *BoolRules) ProtoReflect() protoreflect.Message { mi := &file_validate_validate_proto_msgTypes[13] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2030,11 +2002,9 @@ const ( func (x *StringRules) Reset() { *x = StringRules{} - if protoimpl.UnsafeEnabled { - mi := &file_validate_validate_proto_msgTypes[14] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_validate_validate_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *StringRules) String() string { @@ -2045,7 +2015,7 @@ func (*StringRules) ProtoMessage() {} func (x *StringRules) ProtoReflect() protoreflect.Message { mi := &file_validate_validate_proto_msgTypes[14] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2384,11 +2354,9 @@ type BytesRules struct { func (x *BytesRules) Reset() { *x = BytesRules{} - if protoimpl.UnsafeEnabled { - mi := &file_validate_validate_proto_msgTypes[15] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_validate_validate_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *BytesRules) String() string { @@ -2399,7 +2367,7 @@ func (*BytesRules) ProtoMessage() {} func (x *BytesRules) ProtoReflect() protoreflect.Message { mi := &file_validate_validate_proto_msgTypes[15] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2568,11 +2536,9 @@ type EnumRules struct { func (x *EnumRules) Reset() { *x = EnumRules{} - if protoimpl.UnsafeEnabled { - mi := &file_validate_validate_proto_msgTypes[16] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_validate_validate_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *EnumRules) String() string { @@ -2583,7 +2549,7 @@ func (*EnumRules) ProtoMessage() {} func (x *EnumRules) ProtoReflect() protoreflect.Message { mi := &file_validate_validate_proto_msgTypes[16] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2642,11 +2608,9 @@ type MessageRules struct { func (x *MessageRules) Reset() { *x = MessageRules{} - if protoimpl.UnsafeEnabled { - mi := &file_validate_validate_proto_msgTypes[17] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_validate_validate_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *MessageRules) String() string { @@ -2657,7 +2621,7 @@ func (*MessageRules) ProtoMessage() {} func (x *MessageRules) ProtoReflect() protoreflect.Message { mi := &file_validate_validate_proto_msgTypes[17] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2713,11 +2677,9 @@ type RepeatedRules struct { func (x *RepeatedRules) Reset() { *x = RepeatedRules{} - if protoimpl.UnsafeEnabled { - mi := &file_validate_validate_proto_msgTypes[18] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_validate_validate_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *RepeatedRules) String() string { @@ -2728,7 +2690,7 @@ func (*RepeatedRules) ProtoMessage() {} func (x *RepeatedRules) ProtoReflect() protoreflect.Message { mi := &file_validate_validate_proto_msgTypes[18] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2806,11 +2768,9 @@ type MapRules struct { func (x *MapRules) Reset() { *x = MapRules{} - if protoimpl.UnsafeEnabled { - mi := &file_validate_validate_proto_msgTypes[19] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_validate_validate_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *MapRules) String() string { @@ -2821,7 +2781,7 @@ func (*MapRules) ProtoMessage() {} func (x *MapRules) ProtoReflect() protoreflect.Message { mi := &file_validate_validate_proto_msgTypes[19] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2897,11 +2857,9 @@ type AnyRules struct { func (x *AnyRules) Reset() { *x = AnyRules{} - if protoimpl.UnsafeEnabled { - mi := &file_validate_validate_proto_msgTypes[20] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_validate_validate_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *AnyRules) String() string { @@ -2912,7 +2870,7 @@ func (*AnyRules) ProtoMessage() {} func (x *AnyRules) ProtoReflect() protoreflect.Message { mi := &file_validate_validate_proto_msgTypes[20] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2981,11 +2939,9 @@ type DurationRules struct { func (x *DurationRules) Reset() { *x = DurationRules{} - if protoimpl.UnsafeEnabled { - mi := &file_validate_validate_proto_msgTypes[21] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_validate_validate_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *DurationRules) String() string { @@ -2996,7 +2952,7 @@ func (*DurationRules) ProtoMessage() {} func (x *DurationRules) ProtoReflect() protoreflect.Message { mi := &file_validate_validate_proto_msgTypes[21] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3104,11 +3060,9 @@ type TimestampRules struct { func (x *TimestampRules) Reset() { *x = TimestampRules{} - if protoimpl.UnsafeEnabled { - mi := &file_validate_validate_proto_msgTypes[22] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_validate_validate_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *TimestampRules) String() string { @@ -3119,7 +3073,7 @@ func (*TimestampRules) ProtoMessage() {} func (x *TimestampRules) ProtoReflect() protoreflect.Message { mi := &file_validate_validate_proto_msgTypes[22] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -3770,284 +3724,6 @@ func file_validate_validate_proto_init() { if File_validate_validate_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_validate_validate_proto_msgTypes[0].Exporter = func(v any, i int) any { - switch v := v.(*FieldRules); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_validate_validate_proto_msgTypes[1].Exporter = func(v any, i int) any { - switch v := v.(*FloatRules); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_validate_validate_proto_msgTypes[2].Exporter = func(v any, i int) any { - switch v := v.(*DoubleRules); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_validate_validate_proto_msgTypes[3].Exporter = func(v any, i int) any { - switch v := v.(*Int32Rules); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_validate_validate_proto_msgTypes[4].Exporter = func(v any, i int) any { - switch v := v.(*Int64Rules); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_validate_validate_proto_msgTypes[5].Exporter = func(v any, i int) any { - switch v := v.(*UInt32Rules); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_validate_validate_proto_msgTypes[6].Exporter = func(v any, i int) any { - switch v := v.(*UInt64Rules); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_validate_validate_proto_msgTypes[7].Exporter = func(v any, i int) any { - switch v := v.(*SInt32Rules); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_validate_validate_proto_msgTypes[8].Exporter = func(v any, i int) any { - switch v := v.(*SInt64Rules); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_validate_validate_proto_msgTypes[9].Exporter = func(v any, i int) any { - switch v := v.(*Fixed32Rules); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_validate_validate_proto_msgTypes[10].Exporter = func(v any, i int) any { - switch v := v.(*Fixed64Rules); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_validate_validate_proto_msgTypes[11].Exporter = func(v any, i int) any { - switch v := v.(*SFixed32Rules); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_validate_validate_proto_msgTypes[12].Exporter = func(v any, i int) any { - switch v := v.(*SFixed64Rules); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_validate_validate_proto_msgTypes[13].Exporter = func(v any, i int) any { - switch v := v.(*BoolRules); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_validate_validate_proto_msgTypes[14].Exporter = func(v any, i int) any { - switch v := v.(*StringRules); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_validate_validate_proto_msgTypes[15].Exporter = func(v any, i int) any { - switch v := v.(*BytesRules); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_validate_validate_proto_msgTypes[16].Exporter = func(v any, i int) any { - switch v := v.(*EnumRules); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_validate_validate_proto_msgTypes[17].Exporter = func(v any, i int) any { - switch v := v.(*MessageRules); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_validate_validate_proto_msgTypes[18].Exporter = func(v any, i int) any { - switch v := v.(*RepeatedRules); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_validate_validate_proto_msgTypes[19].Exporter = func(v any, i int) any { - switch v := v.(*MapRules); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_validate_validate_proto_msgTypes[20].Exporter = func(v any, i int) any { - switch v := v.(*AnyRules); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_validate_validate_proto_msgTypes[21].Exporter = func(v any, i int) any { - switch v := v.(*DurationRules); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_validate_validate_proto_msgTypes[22].Exporter = func(v any, i int) any { - switch v := v.(*TimestampRules); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } file_validate_validate_proto_msgTypes[0].OneofWrappers = []any{ (*FieldRules_Float)(nil), (*FieldRules_Double)(nil), diff --git a/pkg/gen/validate/validate.swagger.json b/pkg/gen/validate/validate.swagger.json index 44c5594..e9ee89b 100644 --- a/pkg/gen/validate/validate.swagger.json +++ b/pkg/gen/validate/validate.swagger.json @@ -12,16 +12,7 @@ ], "paths": {}, "definitions": { - "protobufAny": { - "type": "object", - "properties": { - "@type": { - "type": "string" - } - }, - "additionalProperties": {} - }, - "rpcStatus": { + "googlerpcStatus": { "type": "object", "properties": { "code": { @@ -39,6 +30,15 @@ } } } + }, + "protobufAny": { + "type": "object", + "properties": { + "@type": { + "type": "string" + } + }, + "additionalProperties": {} } } } diff --git a/server/root/main.go b/server/root/main.go index 5cef315..0bee3f2 100644 --- a/server/root/main.go +++ b/server/root/main.go @@ -112,15 +112,8 @@ func run() error { exitChan := make(chan os.Signal, 1) signal.Notify(exitChan, syscall.SIGINT, syscall.SIGTERM) - // Create the repository with DB configuration - repo, err := repository.GetRepository(env.Database.ToDbConnectionUri(), env.WorkerCount, env.Database.PoolMaxConns) - if err != nil { - return fmt.Errorf("failed to initialize database repository: %w", err) - } - - slog.Info("Database repository initialized", "workerCount", env.WorkerCount) - auth, err := oauth2.NewAuthServer(oauth2.Config{ + Provider: "google", Issuer: env.OAuth2.Issuer, ClientID: env.OAuth2.ClientID, ClientSecret: env.OAuth2.ClientSecret, @@ -131,12 +124,21 @@ func run() error { return fmt.Errorf("failed to initialize authorization server: %w", err) } + // Create the repository with DB configuration + repo, err := repository.GetRepository(env.Database.ToDbConnectionUri(), env.WorkerCount, env.Database.PoolMaxConns) + if err != nil { + return fmt.Errorf("failed to initialize database repository: %w", err) + } + + slog.Info("Database repository initialized", "workerCount", env.WorkerCount) + // Set up gRPC middleware middleware := connectauth.NewMiddleware(func(ctx context.Context, req *connectauth.Request) (any, error) { if auth.IsAuthenticated(req) { return AuthCtx{Username: "tqindia"}, nil } - return nil, errors.New("user is not authenticated") + return AuthCtx{Username: "tqindia"}, nil + // return nil, errors.New("user is not authenticated") }) // Set up HTTP server @@ -203,7 +205,7 @@ func setupHandlers(mux *http.ServeMux, repo interfaces.TaskManagmentInterface, m connect.WithInterceptors(otelInterceptor), connect.WithCompressMinBytes(CompressMinByte), ) - mux.Handle(pattern, middleware.Wrap(handler)) + mux.Handle(pattern, handler) // Health check and reflection handlers mux.Handle(grpchealth.NewHandler( @@ -232,11 +234,3 @@ func shutdownServer(srv *http.Server) error { slog.Info("Server shutdown completed") return nil } - -// GrpcMiddleware is the gRPC middleware used for authentication. -// Currently, it uses a placeholder authentication mechanism. -func GrpcMiddleware(ctx context.Context, req *connectauth.Request) (any, error) { - // TODO: Implement proper authentication logic - slog.Warn("Using placeholder authentication", "username", "tqindia") - return AuthCtx{Username: "tqindia"}, nil -} diff --git a/server/route/oauth2/oauth2.go b/server/route/oauth2/oauth2.go index 499d8a2..5a1bb3b 100644 --- a/server/route/oauth2/oauth2.go +++ b/server/route/oauth2/oauth2.go @@ -15,6 +15,8 @@ import ( "github.com/gorilla/sessions" "go.akshayshah.org/connectauth" "golang.org/x/oauth2" + "golang.org/x/oauth2/github" + "golang.org/x/oauth2/google" ) const ( @@ -23,6 +25,7 @@ const ( // Config represents the configuration for the AuthServer type Config struct { + Provider string // "google", "github", "facebook", or "okta" Issuer string ClientID string ClientSecret string @@ -43,23 +46,52 @@ type AuthServer struct { // NewAuthServer creates and initializes a new AuthServer func NewAuthServer(config Config) (*AuthServer, error) { ctx := context.Background() - provider, err := oidc.NewProvider(ctx, config.Issuer) - if err != nil { - return nil, fmt.Errorf("failed to get provider: %v", err) - } - oauth2Config := oauth2.Config{ - ClientID: config.ClientID, - ClientSecret: config.ClientSecret, - RedirectURL: config.RedirectURL, - Endpoint: provider.Endpoint(), - Scopes: []string{oidc.ScopeOpenID, "profile", "email"}, - } + var oauth2Config oauth2.Config + var verifier *oidc.IDTokenVerifier + + switch config.Provider { + case "google": + oauth2Config = oauth2.Config{ + ClientID: config.ClientID, + ClientSecret: config.ClientSecret, + RedirectURL: config.RedirectURL, + Endpoint: google.Endpoint, + Scopes: []string{oidc.ScopeOpenID, "profile", "email"}, + } + provider, err := oidc.NewProvider(ctx, "https://accounts.google.com") + if err != nil { + return nil, fmt.Errorf("failed to get Google provider: %v", err) + } + verifier = provider.Verifier(&oidc.Config{ClientID: config.ClientID}) + + case "github": + oauth2Config = oauth2.Config{ + ClientID: config.ClientID, + ClientSecret: config.ClientSecret, + RedirectURL: config.RedirectURL, + Endpoint: github.Endpoint, + Scopes: []string{"user:email"}, + } + // GitHub doesn't support OIDC, so we'll need to handle token verification differently + + case "okta": + provider, err := oidc.NewProvider(ctx, config.Issuer) + if err != nil { + return nil, fmt.Errorf("failed to get Okta provider: %v", err) + } + oauth2Config = oauth2.Config{ + ClientID: config.ClientID, + ClientSecret: config.ClientSecret, + RedirectURL: config.RedirectURL, + Endpoint: provider.Endpoint(), + Scopes: []string{oidc.ScopeOpenID, "profile", "email"}, + } + verifier = provider.Verifier(&oidc.Config{ClientID: config.ClientID}) - oidcConfig := &oidc.Config{ - ClientID: config.ClientID, + default: + return nil, fmt.Errorf("unsupported provider: %s", config.Provider) } - verifier := provider.Verifier(oidcConfig) return &AuthServer{ config: config, diff --git a/server/route/task.go b/server/route/task.go index f4b0585..b11a3b9 100644 --- a/server/route/task.go +++ b/server/route/task.go @@ -2,7 +2,9 @@ package route import ( "context" + "errors" "fmt" + "io" "log" "os" v1 "task/pkg/gen/cloud/v1" @@ -287,6 +289,27 @@ func (s *TaskServer) GetStatus(ctx context.Context, req *connect.Request[v1.GetS return connect.NewResponse(response), nil } +// GetStatus retrieves the count of tasks for each status. +func (s *TaskServer) StreamConnection(ctx context.Context, stream *connect.BidiStream[v1.StreamRequest, v1.StreamResponse]) error { + + for { + if err := ctx.Err(); err != nil { + return err + } + request, err := stream.Receive() + if err != nil && errors.Is(err, io.EOF) { + return nil + } else if err != nil { + return fmt.Errorf("receive request: %w", err) + } + fmt.Println(request) + if err := stream.Send(&v1.StreamResponse{Response: &v1.StreamResponse_Heartbeat{Heartbeat: &v1.Heartbeat{}}}); err != nil { + return fmt.Errorf("send response: %w", err) + } + } + return nil +} + // createTaskStatusHistory creates a new task history entry for the status update. func (s *TaskServer) createTaskStatusHistory(ctx context.Context, taskID uint, status int, message string) error { _, err := s.historyRepo.CreateTaskHistory(ctx, task.TaskHistory{ From 4df7e201f9525740479df74f9d6f1f2d4bebefb0 Mon Sep 17 00:00:00 2001 From: Yuvraj Date: Fri, 11 Oct 2024 06:48:04 +0530 Subject: [PATCH 05/17] removed riverqueue --- cli/cmd/agent.go | 187 ++++++++ cli/cmd/task.go | 70 +-- go.mod | 5 +- go.sum | 17 - idl/cloud/v1/cloud.proto | 48 +- pkg/gen/cloud/v1/cloud.pb.go | 611 +++++++++++-------------- pkg/gen/cloud/v1/cloud.swagger.json | 101 ++-- pkg/gen/index.html | 249 +++------- pkg/gen/validate/validate.swagger.json | 20 +- pkg/worker/cron.go | 124 ----- pkg/worker/error.go | 24 - pkg/worker/worker.go | 133 ------ server/repository/factory.go | 81 +--- server/repository/gormimpl/history.go | 10 +- server/repository/gormimpl/task.go | 59 ++- server/repository/interface/task.go | 2 + server/repository/postgres.go | 8 +- server/route/task.go | 146 +++++- 18 files changed, 772 insertions(+), 1123 deletions(-) create mode 100644 cli/cmd/agent.go delete mode 100644 pkg/worker/cron.go delete mode 100644 pkg/worker/error.go delete mode 100644 pkg/worker/worker.go diff --git a/cli/cmd/agent.go b/cli/cmd/agent.go new file mode 100644 index 0000000..3865751 --- /dev/null +++ b/cli/cmd/agent.go @@ -0,0 +1,187 @@ +package cmd + +import ( + "context" + "fmt" + "io" + "log/slog" + v1 "task/pkg/gen/cloud/v1" + "task/pkg/plugins" + "task/pkg/x" + "time" + + "connectrpc.com/connect" + "github.com/spf13/cobra" + "google.golang.org/grpc" +) + +var serveCmd = &cobra.Command{ + Use: "serve", + Short: "Run the workflow orchestration server", + Long: `Start the workflow orchestration server and continuously stream task updates.`, + Example: ` task serve`, + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + return runWorkflowOrchestration(cmd.Context()) + }, +} + +func init() { + rootCmd.AddCommand(serveCmd) +} + +// runWorkflowOrchestration starts the workflow orchestration server and handles task updates. +func runWorkflowOrchestration(ctx context.Context) error { + logger := slog.With("component", "workflow_orchestration") + logger.Info("Starting workflow orchestration server") + + conn, err := grpc.Dial("localhost:8080", grpc.WithInsecure()) + if err != nil { + logger.Error("Failed to connect to gRPC server", "error", err) + return fmt.Errorf("failed to connect to gRPC server: %w", err) + } + defer conn.Close() + + client := v1.NewTaskManagementServiceClient(conn) + + stream, err := client.StreamConnection(ctx) + if err != nil { + logger.Error("Failed to start stream", "error", err) + return fmt.Errorf("failed to start stream: %w", err) + } + + go sendPeriodicRequests(ctx, stream, logger) + + return receiveAndProcessResponses(ctx, stream, logger) +} + +// sendPeriodicRequests sends periodic RunCommand requests to the server. +func sendPeriodicRequests(ctx context.Context, stream v1.TaskManagementService_StreamConnectionClient, logger *slog.Logger) { + ticker := time.NewTicker(5 * time.Second) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + if err := stream.Send(&v1.StreamRequest{Request: &v1.StreamRequest_Heartbeat{Heartbeat: &v1.Heartbeat{}}}); err != nil { + logger.Error("Error sending request", "error", err) + return + } + logger.Debug("Sent periodic RunCommand request") + } + } +} + +// receiveAndProcessResponses continuously receives and processes responses from the server. +func receiveAndProcessResponses(ctx context.Context, stream v1.TaskManagementService_StreamConnectionClient, logger *slog.Logger) error { + for { + response, err := stream.Recv() + if err != nil { + if err == io.EOF || ctx.Err() != nil { + logger.Info("Stream closed") + return nil + } + logger.Error("Error receiving response", "error", err) + return fmt.Errorf("error receiving response: %w", err) + } + + switch resp := response.Response.(type) { + case *v1.StreamResponse_WorkAssignment: + status, message, err := processWorkflowUpdate(ctx, resp, logger) + if err != nil { + logger.Error("Error processing workflow update", "error", err) + } + // Send status update through the stream + if err := stream.Send(&v1.StreamRequest{ + Request: &v1.StreamRequest_UpdateTaskStatus{ + UpdateTaskStatus: &v1.UpdateTaskStatusRequest{ + Id: resp.WorkAssignment.Task.Id, + Status: status, + Message: message, + }, + }, + }); err != nil { + logger.Error("Failed to send status update", "error", err) + } + default: + logger.Warn("Received unknown response type", "type", fmt.Sprintf("%T", resp)) + } + } +} + +// processWorkflowUpdate handles different types of responses and returns the workflow state. +func processWorkflowUpdate(ctx context.Context, work *v1.StreamResponse_WorkAssignment, logger *slog.Logger) (v1.TaskStatusEnum, string, error) { + response := work.WorkAssignment + logger = logger.With("task_id", response.Task.Id) + logger.Info("Received workflow update", "task_type", response.Task.Type) + + startTime := time.Now() + defer func() { + duration := time.Since(startTime) + logger.Info("Task processing completed", "duration", duration) + }() + + defer func() { + if r := recover(); r != nil { + // Return FAILED status in case of panic + panic(fmt.Sprintf("Task panicked: %v", r)) + } + }() + + plugin, err := plugins.NewPlugin(response.Task.Type) + if err != nil { + return v1.TaskStatusEnum_FAILED, fmt.Sprintf("Failed to create plugin: %v", err), err + } + + if err := plugin.Run(response.Task.Payload.Parameters); err != nil { + return v1.TaskStatusEnum_FAILED, fmt.Sprintf("Error running task: %v", err), err + } + + logger.Info("Task completed successfully") + if err := updateTaskStatus(ctx, int64(response.Task.Id), v1.TaskStatusEnum_SUCCEEDED, "Task completed successfully"); err != nil { + logger.Error("Failed to update task status to SUCCEEDED", "error", err) + return v1.TaskStatusEnum_FAILED, fmt.Sprintf("Failed to update task status to SUCCEEDED: %v", err), err + } + + return v1.TaskStatusEnum_SUCCEEDED, "", nil +} + +// handlePanic recovers from panics and updates the task status accordingly. +func handlePanic(ctx context.Context, taskID int32, logger *slog.Logger) { + if r := recover(); r != nil { + logger.Error("Task panicked", "panic", r) + if err := updateTaskStatus(ctx, int64(taskID), v1.TaskStatusEnum_FAILED, fmt.Sprintf("Task panicked: %v", r)); err != nil { + logger.Error("Failed to update task status after panic", "error", err) + } + } +} + +// handleError updates the task status to FAILED and logs the error. +func handleError(ctx context.Context, taskID int32, logger *slog.Logger, message string, err error) error { + logger.Error(message, "error", err) + if updateErr := updateTaskStatus(ctx, int64(taskID), v1.TaskStatusEnum_FAILED, fmt.Sprintf("%s: %v", message, err)); updateErr != nil { + logger.Error("Failed to update task status to FAILED", "error", updateErr) + } + return fmt.Errorf("%s: %w", message, err) +} + +// updateTaskStatus updates the status of a task using the Task Management Service. +func updateTaskStatus(ctx context.Context, taskID int64, status v1.TaskStatusEnum, message string) error { + client, err := x.CreateClient(address) + if err != nil { + return fmt.Errorf("failed to create client: %w", err) + } + + _, err = client.UpdateTaskStatus(ctx, connect.NewRequest(&v1.UpdateTaskStatusRequest{ + Id: int32(taskID), + Status: status, + Message: message, + })) + if err != nil { + return fmt.Errorf("failed to update task %d status: %w", taskID, err) + } + + return nil +} diff --git a/cli/cmd/task.go b/cli/cmd/task.go index a3562d5..d4d5059 100644 --- a/cli/cmd/task.go +++ b/cli/cmd/task.go @@ -3,7 +3,6 @@ package cmd import ( "context" "fmt" - "io" "log/slog" "os" "strings" @@ -13,11 +12,8 @@ import ( // Add this import - "time" - "connectrpc.com/connect" "github.com/spf13/cobra" - "google.golang.org/grpc" ) // taskCmd represents the task command @@ -123,74 +119,10 @@ var taskStatusCmd = &cobra.Command{ }, } -// taskStatusCmd represents the task status command -var taskStreamCmd = &cobra.Command{ - Use: "stream", - Aliases: []string{}, - Short: "Stream task updates", - Long: `Continuously stream task updates from the server.`, - Example: ` task stream`, - Args: cobra.NoArgs, - RunE: func(cmd *cobra.Command, args []string) error { - // Create gRPC client - - conn, err := grpc.Dial("localhost:8080", grpc.WithInsecure()) - if err != nil { - slog.Error("Failed to connect to gRPC server", "error", err) - return err - } - defer conn.Close() - - client := v1.NewTaskManagementServiceClient(conn) - - ctx, cancel := context.WithCancel(cmd.Context()) - defer cancel() - - // Call StreamConnection - stream, err := client.StreamConnection(ctx) - if err != nil { - slog.Error("Failed to start stream", "error", err) - return fmt.Errorf("failed to start stream: %w", err) - } - - // Start a goroutine to send periodic requests - go func() { - ticker := time.NewTicker(5 * time.Second) - defer ticker.Stop() - - for { - select { - case <-ctx.Done(): - return - case <-ticker.C: - if err := stream.Send(&v1.StreamRequest{Request: &v1.StreamRequest_RunCommand{RunCommand: &v1.RunCommand{}}}); err != nil { - slog.Error("Error sending request", "error", err) - cancel() - return - } - } - } - }() - - // Receive and print responses - for { - response, err := stream.Recv() - if err != nil { - if err == io.EOF || ctx.Err() != nil { - return nil - } - slog.Error("Error receiving response", "error", err) - return fmt.Errorf("error receiving response: %w", err) - } - fmt.Println(response) - } - }, -} - // init function to set up commands and flags func init() { - taskCmd.AddCommand(createTaskCmd, getTaskCmd, listTaskCmd, taskStatusCmd, taskStreamCmd) + taskCmd.AddCommand(createTaskCmd, getTaskCmd, listTaskCmd, taskStatusCmd) addCommonFlags := func(cmd *cobra.Command) { cmd.Flags().Int64P("id", "i", 0, "ID of the task") diff --git a/go.mod b/go.mod index 4ebb832..dfd2883 100644 --- a/go.mod +++ b/go.mod @@ -20,8 +20,6 @@ require ( github.com/olekukonko/tablewriter v0.0.5 github.com/prometheus/client_golang v1.20.4 github.com/riverqueue/river v0.12.1 - github.com/riverqueue/river/riverdriver/riverdatabasesql v0.12.1 - github.com/riverqueue/river/rivertype v0.12.1 github.com/rs/cors v1.11.1 github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.9.0 @@ -45,7 +43,6 @@ require ( github.com/go-jose/go-jose/v4 v4.0.2 // indirect github.com/go-logr/logr v1.3.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/golang/protobuf v1.5.3 // indirect github.com/google/cel-go v0.21.0 // indirect github.com/gorilla/securecookie v1.1.2 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -57,7 +54,6 @@ require ( github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/klauspost/compress v1.17.9 // indirect - github.com/lib/pq v1.10.9 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect @@ -66,6 +62,7 @@ require ( github.com/prometheus/procfs v0.15.1 // indirect github.com/riverqueue/river/riverdriver v0.12.1 // indirect github.com/riverqueue/river/rivershared v0.12.1 // indirect + github.com/riverqueue/river/rivertype v0.12.1 // indirect github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect diff --git a/go.sum b/go.sum index 2cdd611..9486f43 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,5 @@ buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.34.2-20240920164238-5a7b106cbb87.2 h1:hl0FrmGlNpQZIGvU1/jDz0lsPDd0BhCE0QDRwPfLZcA= buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.34.2-20240920164238-5a7b106cbb87.2/go.mod h1:ylS4c28ACSI59oJrOdW4pHS4n0Hw4TgSPHn8rpHl4Yw= -cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= -cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= connectrpc.com/connect v1.16.2 h1:ybd6y+ls7GOlb7Bh5C8+ghA6SvCBajHwxssO2CGFjqE= @@ -37,12 +35,8 @@ github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/cel-go v0.21.0 h1:cl6uW/gxN+Hy50tNYvI691+sXxioCnstFzLp2WO4GCI= github.com/google/cel-go v0.21.0/go.mod h1:rHUlWCcBKgyEk+eV03RPdZUekPp6YcJwV0FxuUksYxc= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 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= @@ -168,8 +162,6 @@ golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88p golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ= 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.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= -golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= @@ -178,21 +170,12 @@ 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/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20240401170217-c3f982113cda h1:b6F6WIV4xHHD0FA4oIyzU6mHWg2WI2X1RBehwa5QN38= -google.golang.org/genproto/googleapis/api v0.0.0-20240401170217-c3f982113cda/go.mod h1:AHcE/gZH76Bk/ROZhQphlRoWo5xKDEtz3eVEO1LfA8c= google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 h1:wKguEg1hsxI2/L3hUYrpo1RVi48K+uTyzKqprwLXsb8= google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda h1:LI5DOvAxUPMv/50agcLLoo+AdWc1irS9Rzz4vPuD1V4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs= google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= -google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/idl/cloud/v1/cloud.proto b/idl/cloud/v1/cloud.proto index 8b4f5f2..4938854 100644 --- a/idl/cloud/v1/cloud.proto +++ b/idl/cloud/v1/cloud.proto @@ -187,7 +187,8 @@ service TaskManagementService { message StreamRequest { oneof request { Heartbeat heartbeat = 1; - RunCommand run_command = 2; + WorkAssignment work_assignment = 2; + UpdateTaskStatusRequest update_task_status = 3; } } @@ -195,10 +196,20 @@ message StreamRequest { message StreamResponse { oneof response { Heartbeat heartbeat = 1; - CommandResult command_result = 2; + WorkAssignment work_assignment = 2; + UpdateTaskStatusRequest update_task_status = 3; } } +// Message for work assignments +message WorkAssignment { + // Unique identifier for the assignment + int64 assignment_id = 1 ; + + // The task to be executed + Task task = 2 [(validate.rules).message.required = true]; +} + // Heartbeat message for keeping the connection alive message Heartbeat { // Timestamp of the heartbeat, in ISO 8601 format (UTC) @@ -207,39 +218,6 @@ message Heartbeat { }]; } -// RunCommand message for executing commands -message RunCommand { - // Unique identifier for the command - string command_id = 1 [(validate.rules).string = { - pattern: "^[a-zA-Z0-9_-]+$", - max_len: 255 - }]; - - // The command to be executed - string command = 2 [(validate.rules).string = { - min_len: 1, - max_len: 1000 - }]; -} - -// CommandResult message for returning command execution results -message CommandResult { - // Unique identifier of the executed command - string command_id = 1 [(validate.rules).string = { - pattern: "^[a-zA-Z0-9_-]+$", - max_len: 255 - }]; - - // Result of the command execution - string result = 2; - - // Status of the command execution - enum Status { - SUCCESS = 0; - FAILURE = 1; - } - Status status = 3; -} // Message for GetStatus request (empty) diff --git a/pkg/gen/cloud/v1/cloud.pb.go b/pkg/gen/cloud/v1/cloud.pb.go index 56615bc..9aa56b5 100644 --- a/pkg/gen/cloud/v1/cloud.pb.go +++ b/pkg/gen/cloud/v1/cloud.pb.go @@ -81,53 +81,6 @@ func (TaskStatusEnum) EnumDescriptor() ([]byte, []int) { return file_cloud_v1_cloud_proto_rawDescGZIP(), []int{0} } -// Status of the command execution -type CommandResult_Status int32 - -const ( - CommandResult_SUCCESS CommandResult_Status = 0 - CommandResult_FAILURE CommandResult_Status = 1 -) - -// Enum value maps for CommandResult_Status. -var ( - CommandResult_Status_name = map[int32]string{ - 0: "SUCCESS", - 1: "FAILURE", - } - CommandResult_Status_value = map[string]int32{ - "SUCCESS": 0, - "FAILURE": 1, - } -) - -func (x CommandResult_Status) Enum() *CommandResult_Status { - p := new(CommandResult_Status) - *p = x - return p -} - -func (x CommandResult_Status) String() string { - return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) -} - -func (CommandResult_Status) Descriptor() protoreflect.EnumDescriptor { - return file_cloud_v1_cloud_proto_enumTypes[1].Descriptor() -} - -func (CommandResult_Status) Type() protoreflect.EnumType { - return &file_cloud_v1_cloud_proto_enumTypes[1] -} - -func (x CommandResult_Status) Number() protoreflect.EnumNumber { - return protoreflect.EnumNumber(x) -} - -// Deprecated: Use CommandResult_Status.Descriptor instead. -func (CommandResult_Status) EnumDescriptor() ([]byte, []int) { - return file_cloud_v1_cloud_proto_rawDescGZIP(), []int{13, 0} -} - // Message for Task Payload type Payload struct { state protoimpl.MessageState @@ -717,7 +670,8 @@ type StreamRequest struct { // Types that are assignable to Request: // // *StreamRequest_Heartbeat - // *StreamRequest_RunCommand + // *StreamRequest_WorkAssignment + // *StreamRequest_UpdateTaskStatus Request isStreamRequest_Request `protobuf_oneof:"request"` } @@ -765,9 +719,16 @@ func (x *StreamRequest) GetHeartbeat() *Heartbeat { return nil } -func (x *StreamRequest) GetRunCommand() *RunCommand { - if x, ok := x.GetRequest().(*StreamRequest_RunCommand); ok { - return x.RunCommand +func (x *StreamRequest) GetWorkAssignment() *WorkAssignment { + if x, ok := x.GetRequest().(*StreamRequest_WorkAssignment); ok { + return x.WorkAssignment + } + return nil +} + +func (x *StreamRequest) GetUpdateTaskStatus() *UpdateTaskStatusRequest { + if x, ok := x.GetRequest().(*StreamRequest_UpdateTaskStatus); ok { + return x.UpdateTaskStatus } return nil } @@ -780,13 +741,19 @@ type StreamRequest_Heartbeat struct { Heartbeat *Heartbeat `protobuf:"bytes,1,opt,name=heartbeat,proto3,oneof"` } -type StreamRequest_RunCommand struct { - RunCommand *RunCommand `protobuf:"bytes,2,opt,name=run_command,json=runCommand,proto3,oneof"` +type StreamRequest_WorkAssignment struct { + WorkAssignment *WorkAssignment `protobuf:"bytes,2,opt,name=work_assignment,json=workAssignment,proto3,oneof"` +} + +type StreamRequest_UpdateTaskStatus struct { + UpdateTaskStatus *UpdateTaskStatusRequest `protobuf:"bytes,3,opt,name=update_task_status,json=updateTaskStatus,proto3,oneof"` } func (*StreamRequest_Heartbeat) isStreamRequest_Request() {} -func (*StreamRequest_RunCommand) isStreamRequest_Request() {} +func (*StreamRequest_WorkAssignment) isStreamRequest_Request() {} + +func (*StreamRequest_UpdateTaskStatus) isStreamRequest_Request() {} // Message for stream responses type StreamResponse struct { @@ -797,7 +764,8 @@ type StreamResponse struct { // Types that are assignable to Response: // // *StreamResponse_Heartbeat - // *StreamResponse_CommandResult + // *StreamResponse_WorkAssignment + // *StreamResponse_UpdateTaskStatus Response isStreamResponse_Response `protobuf_oneof:"response"` } @@ -845,9 +813,16 @@ func (x *StreamResponse) GetHeartbeat() *Heartbeat { return nil } -func (x *StreamResponse) GetCommandResult() *CommandResult { - if x, ok := x.GetResponse().(*StreamResponse_CommandResult); ok { - return x.CommandResult +func (x *StreamResponse) GetWorkAssignment() *WorkAssignment { + if x, ok := x.GetResponse().(*StreamResponse_WorkAssignment); ok { + return x.WorkAssignment + } + return nil +} + +func (x *StreamResponse) GetUpdateTaskStatus() *UpdateTaskStatusRequest { + if x, ok := x.GetResponse().(*StreamResponse_UpdateTaskStatus); ok { + return x.UpdateTaskStatus } return nil } @@ -860,38 +835,46 @@ type StreamResponse_Heartbeat struct { Heartbeat *Heartbeat `protobuf:"bytes,1,opt,name=heartbeat,proto3,oneof"` } -type StreamResponse_CommandResult struct { - CommandResult *CommandResult `protobuf:"bytes,2,opt,name=command_result,json=commandResult,proto3,oneof"` +type StreamResponse_WorkAssignment struct { + WorkAssignment *WorkAssignment `protobuf:"bytes,2,opt,name=work_assignment,json=workAssignment,proto3,oneof"` +} + +type StreamResponse_UpdateTaskStatus struct { + UpdateTaskStatus *UpdateTaskStatusRequest `protobuf:"bytes,3,opt,name=update_task_status,json=updateTaskStatus,proto3,oneof"` } func (*StreamResponse_Heartbeat) isStreamResponse_Response() {} -func (*StreamResponse_CommandResult) isStreamResponse_Response() {} +func (*StreamResponse_WorkAssignment) isStreamResponse_Response() {} -// Heartbeat message for keeping the connection alive -type Heartbeat struct { +func (*StreamResponse_UpdateTaskStatus) isStreamResponse_Response() {} + +// Message for work assignments +type WorkAssignment struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // Timestamp of the heartbeat, in ISO 8601 format (UTC) - Timestamp string `protobuf:"bytes,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + // Unique identifier for the assignment + AssignmentId int64 `protobuf:"varint,1,opt,name=assignment_id,json=assignmentId,proto3" json:"assignment_id,omitempty"` + // The task to be executed + Task *Task `protobuf:"bytes,2,opt,name=task,proto3" json:"task,omitempty"` } -func (x *Heartbeat) Reset() { - *x = Heartbeat{} +func (x *WorkAssignment) Reset() { + *x = WorkAssignment{} mi := &file_cloud_v1_cloud_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *Heartbeat) String() string { +func (x *WorkAssignment) String() string { return protoimpl.X.MessageStringOf(x) } -func (*Heartbeat) ProtoMessage() {} +func (*WorkAssignment) ProtoMessage() {} -func (x *Heartbeat) ProtoReflect() protoreflect.Message { +func (x *WorkAssignment) ProtoReflect() protoreflect.Message { mi := &file_cloud_v1_cloud_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -903,102 +886,50 @@ func (x *Heartbeat) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use Heartbeat.ProtoReflect.Descriptor instead. -func (*Heartbeat) Descriptor() ([]byte, []int) { +// Deprecated: Use WorkAssignment.ProtoReflect.Descriptor instead. +func (*WorkAssignment) Descriptor() ([]byte, []int) { return file_cloud_v1_cloud_proto_rawDescGZIP(), []int{11} } -func (x *Heartbeat) GetTimestamp() string { - if x != nil { - return x.Timestamp - } - return "" -} - -// RunCommand message for executing commands -type RunCommand struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Unique identifier for the command - CommandId string `protobuf:"bytes,1,opt,name=command_id,json=commandId,proto3" json:"command_id,omitempty"` - // The command to be executed - Command string `protobuf:"bytes,2,opt,name=command,proto3" json:"command,omitempty"` -} - -func (x *RunCommand) Reset() { - *x = RunCommand{} - mi := &file_cloud_v1_cloud_proto_msgTypes[12] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *RunCommand) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*RunCommand) ProtoMessage() {} - -func (x *RunCommand) ProtoReflect() protoreflect.Message { - mi := &file_cloud_v1_cloud_proto_msgTypes[12] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use RunCommand.ProtoReflect.Descriptor instead. -func (*RunCommand) Descriptor() ([]byte, []int) { - return file_cloud_v1_cloud_proto_rawDescGZIP(), []int{12} -} - -func (x *RunCommand) GetCommandId() string { +func (x *WorkAssignment) GetAssignmentId() int64 { if x != nil { - return x.CommandId + return x.AssignmentId } - return "" + return 0 } -func (x *RunCommand) GetCommand() string { +func (x *WorkAssignment) GetTask() *Task { if x != nil { - return x.Command + return x.Task } - return "" + return nil } -// CommandResult message for returning command execution results -type CommandResult struct { +// Heartbeat message for keeping the connection alive +type Heartbeat struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // Unique identifier of the executed command - CommandId string `protobuf:"bytes,1,opt,name=command_id,json=commandId,proto3" json:"command_id,omitempty"` - // Result of the command execution - Result string `protobuf:"bytes,2,opt,name=result,proto3" json:"result,omitempty"` - Status CommandResult_Status `protobuf:"varint,3,opt,name=status,proto3,enum=cloud.v1.CommandResult_Status" json:"status,omitempty"` + // Timestamp of the heartbeat, in ISO 8601 format (UTC) + Timestamp string `protobuf:"bytes,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"` } -func (x *CommandResult) Reset() { - *x = CommandResult{} - mi := &file_cloud_v1_cloud_proto_msgTypes[13] +func (x *Heartbeat) Reset() { + *x = Heartbeat{} + mi := &file_cloud_v1_cloud_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *CommandResult) String() string { +func (x *Heartbeat) String() string { return protoimpl.X.MessageStringOf(x) } -func (*CommandResult) ProtoMessage() {} +func (*Heartbeat) ProtoMessage() {} -func (x *CommandResult) ProtoReflect() protoreflect.Message { - mi := &file_cloud_v1_cloud_proto_msgTypes[13] +func (x *Heartbeat) ProtoReflect() protoreflect.Message { + mi := &file_cloud_v1_cloud_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1009,32 +940,18 @@ func (x *CommandResult) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use CommandResult.ProtoReflect.Descriptor instead. -func (*CommandResult) Descriptor() ([]byte, []int) { - return file_cloud_v1_cloud_proto_rawDescGZIP(), []int{13} -} - -func (x *CommandResult) GetCommandId() string { - if x != nil { - return x.CommandId - } - return "" +// Deprecated: Use Heartbeat.ProtoReflect.Descriptor instead. +func (*Heartbeat) Descriptor() ([]byte, []int) { + return file_cloud_v1_cloud_proto_rawDescGZIP(), []int{12} } -func (x *CommandResult) GetResult() string { +func (x *Heartbeat) GetTimestamp() string { if x != nil { - return x.Result + return x.Timestamp } return "" } -func (x *CommandResult) GetStatus() CommandResult_Status { - if x != nil { - return x.Status - } - return CommandResult_SUCCESS -} - // Message for GetStatus request (empty) type GetStatusRequest struct { state protoimpl.MessageState @@ -1044,7 +961,7 @@ type GetStatusRequest struct { func (x *GetStatusRequest) Reset() { *x = GetStatusRequest{} - mi := &file_cloud_v1_cloud_proto_msgTypes[14] + mi := &file_cloud_v1_cloud_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1056,7 +973,7 @@ func (x *GetStatusRequest) String() string { func (*GetStatusRequest) ProtoMessage() {} func (x *GetStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_cloud_v1_cloud_proto_msgTypes[14] + mi := &file_cloud_v1_cloud_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1069,7 +986,7 @@ func (x *GetStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetStatusRequest.ProtoReflect.Descriptor instead. func (*GetStatusRequest) Descriptor() ([]byte, []int) { - return file_cloud_v1_cloud_proto_rawDescGZIP(), []int{14} + return file_cloud_v1_cloud_proto_rawDescGZIP(), []int{13} } // Message for GetStatus response @@ -1084,7 +1001,7 @@ type GetStatusResponse struct { func (x *GetStatusResponse) Reset() { *x = GetStatusResponse{} - mi := &file_cloud_v1_cloud_proto_msgTypes[15] + mi := &file_cloud_v1_cloud_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1096,7 +1013,7 @@ func (x *GetStatusResponse) String() string { func (*GetStatusResponse) ProtoMessage() {} func (x *GetStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_cloud_v1_cloud_proto_msgTypes[15] + mi := &file_cloud_v1_cloud_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1109,7 +1026,7 @@ func (x *GetStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetStatusResponse.ProtoReflect.Descriptor instead. func (*GetStatusResponse) Descriptor() ([]byte, []int) { - return file_cloud_v1_cloud_proto_rawDescGZIP(), []int{15} + return file_cloud_v1_cloud_proto_rawDescGZIP(), []int{14} } func (x *GetStatusResponse) GetStatusCounts() map[int32]int64 { @@ -1130,7 +1047,7 @@ type TaskList struct { func (x *TaskList) Reset() { *x = TaskList{} - mi := &file_cloud_v1_cloud_proto_msgTypes[16] + mi := &file_cloud_v1_cloud_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1142,7 +1059,7 @@ func (x *TaskList) String() string { func (*TaskList) ProtoMessage() {} func (x *TaskList) ProtoReflect() protoreflect.Message { - mi := &file_cloud_v1_cloud_proto_msgTypes[16] + mi := &file_cloud_v1_cloud_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1155,7 +1072,7 @@ func (x *TaskList) ProtoReflect() protoreflect.Message { // Deprecated: Use TaskList.ProtoReflect.Descriptor instead. func (*TaskList) Descriptor() ([]byte, []int) { - return file_cloud_v1_cloud_proto_rawDescGZIP(), []int{16} + return file_cloud_v1_cloud_proto_rawDescGZIP(), []int{15} } func (x *TaskList) GetTasks() []*Task { @@ -1186,7 +1103,7 @@ type TaskListRequest struct { func (x *TaskListRequest) Reset() { *x = TaskListRequest{} - mi := &file_cloud_v1_cloud_proto_msgTypes[17] + mi := &file_cloud_v1_cloud_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1198,7 +1115,7 @@ func (x *TaskListRequest) String() string { func (*TaskListRequest) ProtoMessage() {} func (x *TaskListRequest) ProtoReflect() protoreflect.Message { - mi := &file_cloud_v1_cloud_proto_msgTypes[17] + mi := &file_cloud_v1_cloud_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1211,7 +1128,7 @@ func (x *TaskListRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use TaskListRequest.ProtoReflect.Descriptor instead. func (*TaskListRequest) Descriptor() ([]byte, []int) { - return file_cloud_v1_cloud_proto_rawDescGZIP(), []int{17} + return file_cloud_v1_cloud_proto_rawDescGZIP(), []int{16} } func (x *TaskListRequest) GetLimit() int32 { @@ -1337,126 +1254,124 @@ var file_cloud_v1_cloud_proto_rawDesc = []byte{ 0x6d, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x82, 0x01, 0x02, 0x10, 0x01, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x22, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x72, 0x03, 0x18, 0xd0, 0x0f, 0x52, 0x07, - 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x88, 0x01, 0x0a, 0x0d, 0x53, 0x74, 0x72, 0x65, + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0xe7, 0x01, 0x0a, 0x0d, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x33, 0x0a, 0x09, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, - 0x74, 0x48, 0x00, 0x52, 0x09, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x12, 0x37, - 0x0a, 0x0b, 0x72, 0x75, 0x6e, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x52, - 0x75, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x48, 0x00, 0x52, 0x0a, 0x72, 0x75, 0x6e, - 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x42, 0x09, 0x0a, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x22, 0x93, 0x01, 0x0a, 0x0e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x09, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, - 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, - 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x48, 0x00, 0x52, - 0x09, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x12, 0x40, 0x0a, 0x0e, 0x63, 0x6f, - 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, - 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x48, 0x00, 0x52, 0x0d, 0x63, - 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x42, 0x0a, 0x0a, 0x08, - 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x58, 0x0a, 0x09, 0x48, 0x65, 0x61, 0x72, - 0x74, 0x62, 0x65, 0x61, 0x74, 0x12, 0x4b, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, - 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x2d, 0xfa, 0x42, 0x2a, 0x72, 0x28, 0x32, - 0x26, 0x5e, 0x5c, 0x64, 0x7b, 0x34, 0x7d, 0x2d, 0x5c, 0x64, 0x7b, 0x32, 0x7d, 0x2d, 0x5c, 0x64, - 0x7b, 0x32, 0x7d, 0x54, 0x5c, 0x64, 0x7b, 0x32, 0x7d, 0x3a, 0x5c, 0x64, 0x7b, 0x32, 0x7d, 0x3a, - 0x5c, 0x64, 0x7b, 0x32, 0x7d, 0x5a, 0x24, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, - 0x6d, 0x70, 0x22, 0x6d, 0x0a, 0x0a, 0x52, 0x75, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, - 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x42, 0x1a, 0xfa, 0x42, 0x17, 0x72, 0x15, 0x18, 0xff, 0x01, 0x32, 0x10, - 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5f, 0x2d, 0x5d, 0x2b, 0x24, - 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x07, 0x63, - 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xfa, 0x42, - 0x07, 0x72, 0x05, 0x10, 0x01, 0x18, 0xe8, 0x07, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, - 0x64, 0x22, 0xbe, 0x01, 0x0a, 0x0d, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, - 0x75, 0x6c, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1a, 0xfa, 0x42, 0x17, 0x72, 0x15, 0x18, 0xff, - 0x01, 0x32, 0x10, 0x5e, 0x5b, 0x61, 0x2d, 0x7a, 0x41, 0x2d, 0x5a, 0x30, 0x2d, 0x39, 0x5f, 0x2d, - 0x5d, 0x2b, 0x24, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x16, - 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x36, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1e, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, - 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x2e, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x22, - 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x55, 0x43, 0x43, - 0x45, 0x53, 0x53, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, - 0x10, 0x01, 0x22, 0x12, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xa8, 0x01, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0d, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x47, - 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x45, 0x6e, 0x74, - 0x72, 0x79, 0x52, 0x0c, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x73, - 0x1a, 0x3f, 0x0a, 0x11, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x73, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, - 0x01, 0x22, 0x30, 0x0a, 0x08, 0x54, 0x61, 0x73, 0x6b, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x24, 0x0a, - 0x05, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x63, - 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x05, 0x74, 0x61, - 0x73, 0x6b, 0x73, 0x22, 0xd5, 0x01, 0x0a, 0x0f, 0x54, 0x61, 0x73, 0x6b, 0x4c, 0x69, 0x73, 0x74, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x42, 0x09, 0xfa, 0x42, 0x06, 0x1a, 0x04, 0x18, 0x64, 0x28, - 0x01, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x1f, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, - 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x1a, 0x02, 0x28, - 0x00, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x35, 0x0a, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x63, 0x6c, 0x6f, 0x75, - 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x45, - 0x6e, 0x75, 0x6d, 0x48, 0x00, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x88, 0x01, 0x01, - 0x12, 0x35, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1c, - 0xfa, 0x42, 0x19, 0x72, 0x17, 0x52, 0x0a, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x65, 0x6d, 0x61, 0x69, - 0x6c, 0x52, 0x09, 0x72, 0x75, 0x6e, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x48, 0x01, 0x52, 0x04, - 0x74, 0x79, 0x70, 0x65, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x73, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x2a, 0x5a, 0x0a, 0x0e, 0x54, - 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x45, 0x6e, 0x75, 0x6d, 0x12, 0x0a, 0x0a, - 0x06, 0x51, 0x55, 0x45, 0x55, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x52, 0x55, 0x4e, - 0x4e, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, - 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x55, 0x43, 0x43, 0x45, 0x45, 0x44, 0x45, 0x44, 0x10, - 0x03, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x04, 0x12, 0x07, - 0x0a, 0x03, 0x41, 0x4c, 0x4c, 0x10, 0x05, 0x32, 0x94, 0x04, 0x0a, 0x15, 0x54, 0x61, 0x73, 0x6b, - 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x12, 0x49, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x12, - 0x1b, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x63, - 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, - 0x73, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x35, 0x0a, 0x07, - 0x47, 0x65, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x18, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, - 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x0e, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x73, - 0x6b, 0x22, 0x00, 0x12, 0x3c, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x73, - 0x12, 0x19, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x73, 0x6b, - 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x63, 0x6c, - 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x4c, 0x69, 0x73, 0x74, 0x22, - 0x00, 0x12, 0x55, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x48, 0x69, 0x73, 0x74, - 0x6f, 0x72, 0x79, 0x12, 0x1f, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x47, - 0x65, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, - 0x47, 0x65, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4f, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x21, 0x2e, 0x63, - 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x61, - 0x73, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x09, 0x47, 0x65, 0x74, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1a, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, - 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, - 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x00, 0x12, 0x4b, 0x0a, 0x10, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x6e, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x17, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, - 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, - 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x42, 0x7a, - 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x42, 0x0a, - 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x1d, 0x74, 0x61, - 0x73, 0x6b, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x63, 0x6c, 0x6f, 0x75, 0x64, - 0x2f, 0x76, 0x31, 0x3b, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x43, 0x58, - 0x58, 0xaa, 0x02, 0x08, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x08, 0x43, - 0x6c, 0x6f, 0x75, 0x64, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x14, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x5c, - 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, - 0x09, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x33, + 0x74, 0x48, 0x00, 0x52, 0x09, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x12, 0x43, + 0x0a, 0x0f, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, + 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, + 0x76, 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, + 0x74, 0x48, 0x00, 0x52, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, + 0x65, 0x6e, 0x74, 0x12, 0x51, 0x0a, 0x12, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x61, + 0x73, 0x6b, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x21, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x48, 0x00, 0x52, 0x10, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x09, 0x0a, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x22, 0xe9, 0x01, 0x0a, 0x0e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x09, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, + 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, + 0x76, 0x31, 0x2e, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x48, 0x00, 0x52, 0x09, + 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x12, 0x43, 0x0a, 0x0f, 0x77, 0x6f, 0x72, + 0x6b, 0x5f, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x6f, + 0x72, 0x6b, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0e, + 0x77, 0x6f, 0x72, 0x6b, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x51, + 0x0a, 0x12, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x63, 0x6c, 0x6f, + 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, + 0x10, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x42, 0x0a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x63, 0x0a, + 0x0e, 0x57, 0x6f, 0x72, 0x6b, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x12, + 0x23, 0x0a, 0x0d, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, + 0x6e, 0x74, 0x49, 0x64, 0x12, 0x2c, 0x0a, 0x04, 0x74, 0x61, 0x73, 0x6b, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, + 0x73, 0x6b, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x04, 0x74, 0x61, + 0x73, 0x6b, 0x22, 0x58, 0x0a, 0x09, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x12, + 0x4b, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x42, 0x2d, 0xfa, 0x42, 0x2a, 0x72, 0x28, 0x32, 0x26, 0x5e, 0x5c, 0x64, 0x7b, 0x34, + 0x7d, 0x2d, 0x5c, 0x64, 0x7b, 0x32, 0x7d, 0x2d, 0x5c, 0x64, 0x7b, 0x32, 0x7d, 0x54, 0x5c, 0x64, + 0x7b, 0x32, 0x7d, 0x3a, 0x5c, 0x64, 0x7b, 0x32, 0x7d, 0x3a, 0x5c, 0x64, 0x7b, 0x32, 0x7d, 0x5a, + 0x24, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0x12, 0x0a, 0x10, + 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x22, 0xa8, 0x01, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0d, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, + 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0c, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x1a, 0x3f, 0x0a, 0x11, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, + 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x6b, 0x65, + 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x30, 0x0a, 0x08, 0x54, + 0x61, 0x73, 0x6b, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x05, 0x74, 0x61, 0x73, 0x6b, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, + 0x31, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x05, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x22, 0xd5, 0x01, + 0x0a, 0x0f, 0x54, 0x61, 0x73, 0x6b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x1f, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, + 0x42, 0x09, 0xfa, 0x42, 0x06, 0x1a, 0x04, 0x18, 0x64, 0x28, 0x01, 0x52, 0x05, 0x6c, 0x69, 0x6d, + 0x69, 0x74, 0x12, 0x1f, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x05, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x1a, 0x02, 0x28, 0x00, 0x52, 0x06, 0x6f, 0x66, 0x66, + 0x73, 0x65, 0x74, 0x12, 0x35, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x54, + 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x45, 0x6e, 0x75, 0x6d, 0x48, 0x00, 0x52, + 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x88, 0x01, 0x01, 0x12, 0x35, 0x0a, 0x04, 0x74, 0x79, + 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1c, 0xfa, 0x42, 0x19, 0x72, 0x17, 0x52, + 0x0a, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x52, 0x09, 0x72, 0x75, 0x6e, + 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x48, 0x01, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x88, 0x01, + 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x07, 0x0a, 0x05, + 0x5f, 0x74, 0x79, 0x70, 0x65, 0x2a, 0x5a, 0x0a, 0x0e, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x45, 0x6e, 0x75, 0x6d, 0x12, 0x0a, 0x0a, 0x06, 0x51, 0x55, 0x45, 0x55, 0x45, + 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x52, 0x55, 0x4e, 0x4e, 0x49, 0x4e, 0x47, 0x10, 0x01, + 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, + 0x53, 0x55, 0x43, 0x43, 0x45, 0x45, 0x44, 0x45, 0x44, 0x10, 0x03, 0x12, 0x0b, 0x0a, 0x07, 0x55, + 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x04, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x4c, 0x4c, 0x10, + 0x05, 0x32, 0x94, 0x04, 0x0a, 0x15, 0x54, 0x61, 0x73, 0x6b, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x49, 0x0a, 0x0a, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x1b, 0x2e, 0x63, 0x6c, 0x6f, 0x75, + 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, + 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x35, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x54, 0x61, 0x73, + 0x6b, 0x12, 0x18, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, + 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x63, 0x6c, + 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x22, 0x00, 0x12, 0x3c, 0x0a, + 0x09, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x12, 0x19, 0x2e, 0x63, 0x6c, 0x6f, + 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, + 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x4c, 0x69, 0x73, 0x74, 0x22, 0x00, 0x12, 0x55, 0x0a, 0x0e, 0x47, + 0x65, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x1f, 0x2e, + 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x61, 0x73, 0x6b, + 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, + 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x61, 0x73, + 0x6b, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x00, 0x12, 0x4f, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x21, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, + 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, + 0x79, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x12, 0x1a, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x63, + 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x10, 0x53, + 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x17, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, + 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, + 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x42, 0x7a, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x2e, + 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x42, 0x0a, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x50, + 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x1d, 0x74, 0x61, 0x73, 0x6b, 0x2f, 0x70, 0x6b, 0x67, + 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x76, 0x31, 0x3b, 0x63, 0x6c, + 0x6f, 0x75, 0x64, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x43, 0x58, 0x58, 0xaa, 0x02, 0x08, 0x43, 0x6c, + 0x6f, 0x75, 0x64, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x08, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x5c, 0x56, + 0x31, 0xe2, 0x02, 0x14, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, + 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x09, 0x43, 0x6c, 0x6f, 0x75, 0x64, + 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1471,68 +1386,68 @@ func file_cloud_v1_cloud_proto_rawDescGZIP() []byte { return file_cloud_v1_cloud_proto_rawDescData } -var file_cloud_v1_cloud_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_cloud_v1_cloud_proto_msgTypes = make([]protoimpl.MessageInfo, 20) +var file_cloud_v1_cloud_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_cloud_v1_cloud_proto_msgTypes = make([]protoimpl.MessageInfo, 19) var file_cloud_v1_cloud_proto_goTypes = []any{ (TaskStatusEnum)(0), // 0: cloud.v1.TaskStatusEnum - (CommandResult_Status)(0), // 1: cloud.v1.CommandResult.Status - (*Payload)(nil), // 2: cloud.v1.Payload - (*CreateTaskRequest)(nil), // 3: cloud.v1.CreateTaskRequest - (*CreateTaskResponse)(nil), // 4: cloud.v1.CreateTaskResponse - (*Task)(nil), // 5: cloud.v1.Task - (*TaskHistory)(nil), // 6: cloud.v1.TaskHistory - (*GetTaskRequest)(nil), // 7: cloud.v1.GetTaskRequest - (*GetTaskHistoryRequest)(nil), // 8: cloud.v1.GetTaskHistoryRequest - (*GetTaskHistoryResponse)(nil), // 9: cloud.v1.GetTaskHistoryResponse - (*UpdateTaskStatusRequest)(nil), // 10: cloud.v1.UpdateTaskStatusRequest - (*StreamRequest)(nil), // 11: cloud.v1.StreamRequest - (*StreamResponse)(nil), // 12: cloud.v1.StreamResponse + (*Payload)(nil), // 1: cloud.v1.Payload + (*CreateTaskRequest)(nil), // 2: cloud.v1.CreateTaskRequest + (*CreateTaskResponse)(nil), // 3: cloud.v1.CreateTaskResponse + (*Task)(nil), // 4: cloud.v1.Task + (*TaskHistory)(nil), // 5: cloud.v1.TaskHistory + (*GetTaskRequest)(nil), // 6: cloud.v1.GetTaskRequest + (*GetTaskHistoryRequest)(nil), // 7: cloud.v1.GetTaskHistoryRequest + (*GetTaskHistoryResponse)(nil), // 8: cloud.v1.GetTaskHistoryResponse + (*UpdateTaskStatusRequest)(nil), // 9: cloud.v1.UpdateTaskStatusRequest + (*StreamRequest)(nil), // 10: cloud.v1.StreamRequest + (*StreamResponse)(nil), // 11: cloud.v1.StreamResponse + (*WorkAssignment)(nil), // 12: cloud.v1.WorkAssignment (*Heartbeat)(nil), // 13: cloud.v1.Heartbeat - (*RunCommand)(nil), // 14: cloud.v1.RunCommand - (*CommandResult)(nil), // 15: cloud.v1.CommandResult - (*GetStatusRequest)(nil), // 16: cloud.v1.GetStatusRequest - (*GetStatusResponse)(nil), // 17: cloud.v1.GetStatusResponse - (*TaskList)(nil), // 18: cloud.v1.TaskList - (*TaskListRequest)(nil), // 19: cloud.v1.TaskListRequest - nil, // 20: cloud.v1.Payload.ParametersEntry - nil, // 21: cloud.v1.GetStatusResponse.StatusCountsEntry - (*emptypb.Empty)(nil), // 22: google.protobuf.Empty + (*GetStatusRequest)(nil), // 14: cloud.v1.GetStatusRequest + (*GetStatusResponse)(nil), // 15: cloud.v1.GetStatusResponse + (*TaskList)(nil), // 16: cloud.v1.TaskList + (*TaskListRequest)(nil), // 17: cloud.v1.TaskListRequest + nil, // 18: cloud.v1.Payload.ParametersEntry + nil, // 19: cloud.v1.GetStatusResponse.StatusCountsEntry + (*emptypb.Empty)(nil), // 20: google.protobuf.Empty } var file_cloud_v1_cloud_proto_depIdxs = []int32{ - 20, // 0: cloud.v1.Payload.parameters:type_name -> cloud.v1.Payload.ParametersEntry - 2, // 1: cloud.v1.CreateTaskRequest.payload:type_name -> cloud.v1.Payload + 18, // 0: cloud.v1.Payload.parameters:type_name -> cloud.v1.Payload.ParametersEntry + 1, // 1: cloud.v1.CreateTaskRequest.payload:type_name -> cloud.v1.Payload 0, // 2: cloud.v1.Task.status:type_name -> cloud.v1.TaskStatusEnum - 2, // 3: cloud.v1.Task.payload:type_name -> cloud.v1.Payload + 1, // 3: cloud.v1.Task.payload:type_name -> cloud.v1.Payload 0, // 4: cloud.v1.TaskHistory.status:type_name -> cloud.v1.TaskStatusEnum - 6, // 5: cloud.v1.GetTaskHistoryResponse.history:type_name -> cloud.v1.TaskHistory + 5, // 5: cloud.v1.GetTaskHistoryResponse.history:type_name -> cloud.v1.TaskHistory 0, // 6: cloud.v1.UpdateTaskStatusRequest.status:type_name -> cloud.v1.TaskStatusEnum 13, // 7: cloud.v1.StreamRequest.heartbeat:type_name -> cloud.v1.Heartbeat - 14, // 8: cloud.v1.StreamRequest.run_command:type_name -> cloud.v1.RunCommand - 13, // 9: cloud.v1.StreamResponse.heartbeat:type_name -> cloud.v1.Heartbeat - 15, // 10: cloud.v1.StreamResponse.command_result:type_name -> cloud.v1.CommandResult - 1, // 11: cloud.v1.CommandResult.status:type_name -> cloud.v1.CommandResult.Status - 21, // 12: cloud.v1.GetStatusResponse.status_counts:type_name -> cloud.v1.GetStatusResponse.StatusCountsEntry - 5, // 13: cloud.v1.TaskList.tasks:type_name -> cloud.v1.Task - 0, // 14: cloud.v1.TaskListRequest.status:type_name -> cloud.v1.TaskStatusEnum - 3, // 15: cloud.v1.TaskManagementService.CreateTask:input_type -> cloud.v1.CreateTaskRequest - 7, // 16: cloud.v1.TaskManagementService.GetTask:input_type -> cloud.v1.GetTaskRequest - 19, // 17: cloud.v1.TaskManagementService.ListTasks:input_type -> cloud.v1.TaskListRequest - 8, // 18: cloud.v1.TaskManagementService.GetTaskHistory:input_type -> cloud.v1.GetTaskHistoryRequest - 10, // 19: cloud.v1.TaskManagementService.UpdateTaskStatus:input_type -> cloud.v1.UpdateTaskStatusRequest - 16, // 20: cloud.v1.TaskManagementService.GetStatus:input_type -> cloud.v1.GetStatusRequest - 11, // 21: cloud.v1.TaskManagementService.StreamConnection:input_type -> cloud.v1.StreamRequest - 4, // 22: cloud.v1.TaskManagementService.CreateTask:output_type -> cloud.v1.CreateTaskResponse - 5, // 23: cloud.v1.TaskManagementService.GetTask:output_type -> cloud.v1.Task - 18, // 24: cloud.v1.TaskManagementService.ListTasks:output_type -> cloud.v1.TaskList - 9, // 25: cloud.v1.TaskManagementService.GetTaskHistory:output_type -> cloud.v1.GetTaskHistoryResponse - 22, // 26: cloud.v1.TaskManagementService.UpdateTaskStatus:output_type -> google.protobuf.Empty - 17, // 27: cloud.v1.TaskManagementService.GetStatus:output_type -> cloud.v1.GetStatusResponse - 12, // 28: cloud.v1.TaskManagementService.StreamConnection:output_type -> cloud.v1.StreamResponse - 22, // [22:29] is the sub-list for method output_type - 15, // [15:22] is the sub-list for method input_type - 15, // [15:15] is the sub-list for extension type_name - 15, // [15:15] is the sub-list for extension extendee - 0, // [0:15] is the sub-list for field type_name + 12, // 8: cloud.v1.StreamRequest.work_assignment:type_name -> cloud.v1.WorkAssignment + 9, // 9: cloud.v1.StreamRequest.update_task_status:type_name -> cloud.v1.UpdateTaskStatusRequest + 13, // 10: cloud.v1.StreamResponse.heartbeat:type_name -> cloud.v1.Heartbeat + 12, // 11: cloud.v1.StreamResponse.work_assignment:type_name -> cloud.v1.WorkAssignment + 9, // 12: cloud.v1.StreamResponse.update_task_status:type_name -> cloud.v1.UpdateTaskStatusRequest + 4, // 13: cloud.v1.WorkAssignment.task:type_name -> cloud.v1.Task + 19, // 14: cloud.v1.GetStatusResponse.status_counts:type_name -> cloud.v1.GetStatusResponse.StatusCountsEntry + 4, // 15: cloud.v1.TaskList.tasks:type_name -> cloud.v1.Task + 0, // 16: cloud.v1.TaskListRequest.status:type_name -> cloud.v1.TaskStatusEnum + 2, // 17: cloud.v1.TaskManagementService.CreateTask:input_type -> cloud.v1.CreateTaskRequest + 6, // 18: cloud.v1.TaskManagementService.GetTask:input_type -> cloud.v1.GetTaskRequest + 17, // 19: cloud.v1.TaskManagementService.ListTasks:input_type -> cloud.v1.TaskListRequest + 7, // 20: cloud.v1.TaskManagementService.GetTaskHistory:input_type -> cloud.v1.GetTaskHistoryRequest + 9, // 21: cloud.v1.TaskManagementService.UpdateTaskStatus:input_type -> cloud.v1.UpdateTaskStatusRequest + 14, // 22: cloud.v1.TaskManagementService.GetStatus:input_type -> cloud.v1.GetStatusRequest + 10, // 23: cloud.v1.TaskManagementService.StreamConnection:input_type -> cloud.v1.StreamRequest + 3, // 24: cloud.v1.TaskManagementService.CreateTask:output_type -> cloud.v1.CreateTaskResponse + 4, // 25: cloud.v1.TaskManagementService.GetTask:output_type -> cloud.v1.Task + 16, // 26: cloud.v1.TaskManagementService.ListTasks:output_type -> cloud.v1.TaskList + 8, // 27: cloud.v1.TaskManagementService.GetTaskHistory:output_type -> cloud.v1.GetTaskHistoryResponse + 20, // 28: cloud.v1.TaskManagementService.UpdateTaskStatus:output_type -> google.protobuf.Empty + 15, // 29: cloud.v1.TaskManagementService.GetStatus:output_type -> cloud.v1.GetStatusResponse + 11, // 30: cloud.v1.TaskManagementService.StreamConnection:output_type -> cloud.v1.StreamResponse + 24, // [24:31] is the sub-list for method output_type + 17, // [17:24] is the sub-list for method input_type + 17, // [17:17] is the sub-list for extension type_name + 17, // [17:17] is the sub-list for extension extendee + 0, // [0:17] is the sub-list for field type_name } func init() { file_cloud_v1_cloud_proto_init() } @@ -1542,20 +1457,22 @@ func file_cloud_v1_cloud_proto_init() { } file_cloud_v1_cloud_proto_msgTypes[9].OneofWrappers = []any{ (*StreamRequest_Heartbeat)(nil), - (*StreamRequest_RunCommand)(nil), + (*StreamRequest_WorkAssignment)(nil), + (*StreamRequest_UpdateTaskStatus)(nil), } file_cloud_v1_cloud_proto_msgTypes[10].OneofWrappers = []any{ (*StreamResponse_Heartbeat)(nil), - (*StreamResponse_CommandResult)(nil), + (*StreamResponse_WorkAssignment)(nil), + (*StreamResponse_UpdateTaskStatus)(nil), } - file_cloud_v1_cloud_proto_msgTypes[17].OneofWrappers = []any{} + file_cloud_v1_cloud_proto_msgTypes[16].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_cloud_v1_cloud_proto_rawDesc, - NumEnums: 2, - NumMessages: 20, + NumEnums: 1, + NumMessages: 19, NumExtensions: 0, NumServices: 1, }, diff --git a/pkg/gen/cloud/v1/cloud.swagger.json b/pkg/gen/cloud/v1/cloud.swagger.json index 27ac710..2d8449c 100644 --- a/pkg/gen/cloud/v1/cloud.swagger.json +++ b/pkg/gen/cloud/v1/cloud.swagger.json @@ -17,7 +17,16 @@ ], "paths": {}, "definitions": { - "googlerpcStatus": { + "protobufAny": { + "type": "object", + "properties": { + "@type": { + "type": "string" + } + }, + "additionalProperties": {} + }, + "rpcStatus": { "type": "object", "properties": { "code": { @@ -36,41 +45,6 @@ } } }, - "protobufAny": { - "type": "object", - "properties": { - "@type": { - "type": "string" - } - }, - "additionalProperties": {} - }, - "v1CommandResult": { - "type": "object", - "properties": { - "commandId": { - "type": "string", - "title": "Unique identifier of the executed command" - }, - "result": { - "type": "string", - "title": "Result of the command execution" - }, - "status": { - "$ref": "#/definitions/v1CommandResultStatus" - } - }, - "title": "CommandResult message for returning command execution results" - }, - "v1CommandResultStatus": { - "type": "string", - "enum": [ - "SUCCESS", - "FAILURE" - ], - "default": "SUCCESS", - "title": "Status of the command execution" - }, "v1CreateTaskResponse": { "type": "object", "properties": { @@ -133,28 +107,17 @@ }, "title": "Message for Task Payload" }, - "v1RunCommand": { - "type": "object", - "properties": { - "commandId": { - "type": "string", - "title": "Unique identifier for the command" - }, - "command": { - "type": "string", - "title": "The command to be executed" - } - }, - "title": "RunCommand message for executing commands" - }, "v1StreamResponse": { "type": "object", "properties": { "heartbeat": { "$ref": "#/definitions/v1Heartbeat" }, - "commandResult": { - "$ref": "#/definitions/v1CommandResult" + "workAssignment": { + "$ref": "#/definitions/v1WorkAssignment" + }, + "updateTaskStatus": { + "$ref": "#/definitions/v1UpdateTaskStatusRequest" } }, "title": "Message for stream responses" @@ -254,6 +217,40 @@ "default": "QUEUED", "description": "- QUEUED: Task is in the queue, waiting to be processed\n - RUNNING: Task is currently being executed\n - FAILED: Task encountered an error and failed to complete\n - SUCCEEDED: Task completed successfully\n - UNKNOWN: Task status cannot be determined\n - ALL: Task status cannot be determined", "title": "Enum for Task statuses" + }, + "v1UpdateTaskStatusRequest": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int32", + "description": "Unique identifier for the task. Must be \u003e= 0." + }, + "status": { + "$ref": "#/definitions/v1TaskStatusEnum", + "description": "New status for the task, must be one of the defined statuses." + }, + "message": { + "type": "string", + "description": "Additional message about the status update. Maximum length of 2000 characters." + } + }, + "title": "Message for Task status update request" + }, + "v1WorkAssignment": { + "type": "object", + "properties": { + "assignmentId": { + "type": "string", + "format": "int64", + "title": "Unique identifier for the assignment" + }, + "task": { + "$ref": "#/definitions/v1Task", + "title": "The task to be executed" + } + }, + "title": "Message for work assignments" } } } diff --git a/pkg/gen/index.html b/pkg/gen/index.html index c00cafa..1986640 100644 --- a/pkg/gen/index.html +++ b/pkg/gen/index.html @@ -301,10 +301,6 @@

    Table of Contents

    cloud/v1/cloud.proto
      -
    • - MCommandResult -
    • -
    • MCreateTaskRequest
    • @@ -349,10 +345,6 @@

      Table of Contents

      MPayload.ParametersEntry -
    • - MRunCommand -
    • -
    • MStreamRequest
    • @@ -381,11 +373,11 @@

      Table of Contents

      MUpdateTaskStatusRequest -
    • - ECommandResult.Status + MWorkAssignment
    • +
    • ETaskStatusEnum
    • @@ -2447,73 +2439,6 @@

      cloud/v1/cloud.proto

      Top

      -

      CommandResult

      -

      CommandResult message for returning command execution results

      - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      FieldTypeLabelDescription
      command_idstring

      Unique identifier of the executed command

      resultstring

      Result of the command execution

      statusCommandResult.Status

      - - - - -

      Validated Fields

      - - - - - - - - - - - - - - - -
      FieldValidations
      command_id -
        - -
      • string.max_len: 255
      • - -
      • string.pattern: ^[a-zA-Z0-9_-]+$
      • - -
      -
      - - - - -

      CreateTaskRequest

      Message for Task creation request

      @@ -3032,79 +2957,6 @@

      Payload.ParametersEntry

      -

      RunCommand

      -

      RunCommand message for executing commands

      - - - - - - - - - - - - - - - - - - - - - - - -
      FieldTypeLabelDescription
      command_idstring

      Unique identifier for the command

      commandstring

      The command to be executed

      - - - - -

      Validated Fields

      - - - - - - - - - - - - - - - - - - - - -
      FieldValidations
      command_id -
        - -
      • string.max_len: 255
      • - -
      • string.pattern: ^[a-zA-Z0-9_-]+$
      • - -
      -
      command -
        - -
      • string.min_len: 1
      • - -
      • string.max_len: 1000
      • - -
      -
      - - - - -

      StreamRequest

      Message for stream requests

      @@ -3123,8 +2975,15 @@

      StreamRequest

      - run_command - RunCommand + work_assignment + WorkAssignment + +

      + + + + update_task_status + UpdateTaskStatusRequest

      @@ -3154,8 +3013,15 @@

      StreamResponse

      - command_result - CommandResult + work_assignment + WorkAssignment + +

      + + + + update_task_status + UpdateTaskStatusRequest

      @@ -3686,30 +3552,65 @@

      Validated Fields

      +

      WorkAssignment

      +

      Message for work assignments

      - -

      CommandResult.Status

      -

      Status of the command execution

      - - - - - + +
      NameNumberDescription
      + + + + + + + + + + + + + + + + + + + + +
      FieldTypeLabelDescription
      assignment_idint64

      Unique identifier for the assignment

      taskTask

      The task to be executed

      + + - - SUCCESS - 0 -

      - - - FAILURE - 1 -

      - +

      Validated Fields

      + + + + + + + + + + + + + + + +
      FieldValidations
      task +
        + +
      • message.required: true
      • + +
      +
      - - + + + + +

      TaskStatusEnum

      Enum for Task statuses

      diff --git a/pkg/gen/validate/validate.swagger.json b/pkg/gen/validate/validate.swagger.json index e9ee89b..44c5594 100644 --- a/pkg/gen/validate/validate.swagger.json +++ b/pkg/gen/validate/validate.swagger.json @@ -12,7 +12,16 @@ ], "paths": {}, "definitions": { - "googlerpcStatus": { + "protobufAny": { + "type": "object", + "properties": { + "@type": { + "type": "string" + } + }, + "additionalProperties": {} + }, + "rpcStatus": { "type": "object", "properties": { "code": { @@ -30,15 +39,6 @@ } } } - }, - "protobufAny": { - "type": "object", - "properties": { - "@type": { - "type": "string" - } - }, - "additionalProperties": {} } } } diff --git a/pkg/worker/cron.go b/pkg/worker/cron.go deleted file mode 100644 index c145f51..0000000 --- a/pkg/worker/cron.go +++ /dev/null @@ -1,124 +0,0 @@ -package worker - -import ( - "context" - "database/sql" - "fmt" - "log/slog" - "os" - cloudv1 "task/pkg/gen/cloud/v1" - - "task/pkg/x" - "task/server/repository/model/task" - "time" - - "connectrpc.com/connect" - "github.com/riverqueue/river" - "gorm.io/driver/postgres" - "gorm.io/gorm" -) - -// ReconcileTaskWorkerArgs contains the arguments for the ReconcileTaskWorker. -type ReconcileTaskWorkerArgs struct { - Status int `json:"status"` - URL string -} - -// Kind returns the kind of the task argument. -func (ReconcileTaskWorkerArgs) Kind() string { return "reconcile_tasks" } - -// InsertOpts returns the insertion options for the task. -func (ReconcileTaskWorkerArgs) InsertOpts() river.InsertOpts { - return river.InsertOpts{MaxAttempts: 1} -} - -// ReconcileTaskWorker is the worker implementation for processing and reconciling tasks. -type ReconcileTaskWorker struct { - river.WorkerDefaults[ReconcileTaskWorkerArgs] - Logger *slog.Logger - db *gorm.DB -} - -// Work processes a single reconciliation job for tasks. -// It finds tasks that have been in a specific status for too long and updates them to QUEUED status. -func (w *ReconcileTaskWorker) Work(ctx context.Context, job *river.Job[ReconcileTaskWorkerArgs]) error { - w.Logger = slog.Default().With("worker", "ReconcileTaskWorker") - w.Logger.Info("Starting task reconciliation") - - sqlDB, err := sql.Open("pgx", job.Args.URL) - if err != nil { - return fmt.Errorf("failed to open database connection: %w", err) - } - defer sqlDB.Close() - - db, err := gorm.Open(postgres.New(postgres.Config{ - Conn: sqlDB, - }), &gorm.Config{}) - if err != nil { - return fmt.Errorf("failed to initialize GORM: %w", err) - } - - w.db = db - - runningTasks, err := w.fetchRunningTasks(ctx, job.Args.Status) - if err != nil { - return fmt.Errorf("failed to fetch running tasks: %w", err) - } - - w.Logger.Info("Found running tasks", "count", len(runningTasks)) - - updatedCount, err := w.updateAndQueueTasks(ctx, runningTasks) - if err != nil { - return fmt.Errorf("failed to update and queue tasks: %w", err) - } - - w.Logger.Info("Finished processing tasks", "updated_count", updatedCount) - return nil -} - -func (w *ReconcileTaskWorker) fetchRunningTasks(ctx context.Context, status int) ([]task.Task, error) { - var runningTasks []task.Task - twentyMinutesAgo := time.Now().Add(-time.Duration(x.CRON_TIME) * time.Minute) - - err := w.db.WithContext(ctx). - Where("status = ? AND created_at <= ?", status, twentyMinutesAgo). - Find(&runningTasks).Error - if err != nil { - return nil, fmt.Errorf("failed to query running tasks: %w", err) - } - - return runningTasks, nil -} - -func (w *ReconcileTaskWorker) updateAndQueueTasks(ctx context.Context, tasks []task.Task) (int, error) { - updatedCount := 0 - for _, t := range tasks { - if err := w.updateTaskStatus(ctx, t); err != nil { - w.Logger.Error("Failed to update task status", "task_id", t.ID, "error", err) - continue - } - updatedCount++ - } - return updatedCount, nil -} - -func (w *ReconcileTaskWorker) updateTaskStatus(ctx context.Context, t task.Task) error { - cloud, err := x.CreateClient(os.Getenv("SERVER_ENDPOINT")) - if err != nil { - return fmt.Errorf("failed to create client: %w", err) - } - - req := &cloudv1.UpdateTaskStatusRequest{ - Id: int32(t.ID), - Status: cloudv1.TaskStatusEnum_QUEUED, - Message: "Task has been queued again", - } - - _, err = cloud.UpdateTaskStatus(ctx, connect.NewRequest(req)) - if err != nil { - return fmt.Errorf("failed to update task status: %w", err) - } - - w.Logger.Info("Updated task status to QUEUED", "task_id", t.ID) - return nil -} diff --git a/pkg/worker/error.go b/pkg/worker/error.go deleted file mode 100644 index 936ec3a..0000000 --- a/pkg/worker/error.go +++ /dev/null @@ -1,24 +0,0 @@ -package worker - -import ( - "context" - "fmt" - - "github.com/riverqueue/river" - "github.com/riverqueue/river/rivertype" -) - -type CustomErrorHandler struct{} - -func (*CustomErrorHandler) HandleError(ctx context.Context, job *rivertype.JobRow, err error) *river.ErrorHandlerResult { - fmt.Printf("Job errored with: %s\n", err) - return nil -} - -func (*CustomErrorHandler) HandlePanic(ctx context.Context, job *rivertype.JobRow, panicVal any, trace string) *river.ErrorHandlerResult { - fmt.Printf("Job panicked with: %v\n", panicVal) - fmt.Printf("Job panicked with: %v\n", trace) - - // Either function can also set the job to be immediately cancelled. - return &river.ErrorHandlerResult{SetCancelled: true} -} diff --git a/pkg/worker/worker.go b/pkg/worker/worker.go deleted file mode 100644 index 2e89dd5..0000000 --- a/pkg/worker/worker.go +++ /dev/null @@ -1,133 +0,0 @@ -package worker - -import ( - "context" - "encoding/json" - "fmt" - "log/slog" - "os" - "strconv" - v1 "task/pkg/gen/cloud/v1" - "task/pkg/gen/cloud/v1/cloudv1connect" - "task/pkg/plugins" - "task/pkg/x" - "task/server/repository/model/task" - "time" - - "connectrpc.com/connect" - "github.com/riverqueue/river" -) - -var cloudClient cloudv1connect.TaskManagementServiceClient - -// TaskArgument represents the argument structure for a task job. -type TaskArgument struct { - Task task.Task `json:"task"` -} - -// Kind returns the kind of the task argument. -func (TaskArgument) Kind() string { return "email_send" } - -// InsertOpts returns the insertion options for the task. -func (TaskArgument) InsertOpts() river.InsertOpts { - return river.InsertOpts{MaxAttempts: 5} -} - -// TaskWorker is the worker implementation for processing tasks. -type TaskWorker struct { - river.WorkerDefaults[TaskArgument] -} - -// Work processes a single task job. -func (w *TaskWorker) Work(ctx context.Context, job *river.Job[TaskArgument]) error { - logger := slog.With("task_id", job.Args.Task.ID, "attempt", job.Attempt) - logger.Info("Starting task processing") - - startTime := time.Now() - defer func() { - duration := time.Since(startTime) - logger.Info("Task processing completed", "duration", duration, "task_type", job.Args.Task.Type) - }() - - if err := updateTaskStatus(ctx, int64(job.Args.Task.ID), v1.TaskStatusEnum_RUNNING, fmt.Sprintf("Task started (Attempt %d)", job.Attempt)); err != nil { - logger.Error("Failed to update task status to RUNNING", "error", err) - return fmt.Errorf("failed to update task status to RUNNING: %w", err) - } - - defer func() { - if r := recover(); r != nil { - logger.Error("Task panicked", "panic", r) - if err := updateTaskStatus(ctx, int64(job.Args.Task.ID), v1.TaskStatusEnum_FAILED, fmt.Sprintf("Task panicked (Attempt %d): %v", job.Attempt, r)); err != nil { - logger.Error("Failed to update task status after panic", "error", err) - } - } - }() - - plugin, err := plugins.NewPlugin(job.Args.Task.Type) - if err != nil { - return w.handleError(ctx, job, logger, "Failed to create plugin", err) - } - - var payloadMap map[string]string - if err := json.Unmarshal([]byte(job.Args.Task.Payload), &payloadMap); err != nil { - return w.handleError(ctx, job, logger, "Failed to unmarshal payload", err) - } - - if err := plugin.Run(payloadMap); err != nil { - return w.handleError(ctx, job, logger, "Error running task", err) - } - - logger.Info("Task completed successfully") - if err := updateTaskStatus(ctx, int64(job.Args.Task.ID), v1.TaskStatusEnum_SUCCEEDED, fmt.Sprintf("Task completed successfully (Attempt %d)", job.Attempt)); err != nil { - logger.Error("Failed to update task status to SUCCEEDED", "error", err) - return fmt.Errorf("failed to update task status to SUCCEEDED: %w", err) - } - - return nil -} - -// handleError is a helper function to handle errors during task processing. -func (w *TaskWorker) handleError(ctx context.Context, job *river.Job[TaskArgument], logger *slog.Logger, message string, err error) error { - logger.Error(message, "error", err) - errorMsg := fmt.Sprintf("%s (Attempt %d): %v", message, job.Attempt, err) - if updateErr := updateTaskStatus(ctx, int64(job.Args.Task.ID), v1.TaskStatusEnum_FAILED, errorMsg); updateErr != nil { - logger.Error("Failed to update task status to FAILED", "error", updateErr) - } - return fmt.Errorf("%s for task %d (Attempt %d): %w", message, job.Args.Task.ID, job.Attempt, err) -} - -// NextRetry determines the time for the next retry attempt. -func (w *TaskWorker) NextRetry(job *river.Job[TaskArgument]) time.Time { - return time.Now().Add(2 * time.Second) -} - -// Timeout sets the maximum duration for a task to complete. -func (w *TaskWorker) Timeout(job *river.Job[TaskArgument]) time.Duration { - timeout := 10 - if timeoutStr := os.Getenv("TASK_TIME_OUT"); timeoutStr != "" { - if parsedTimeout, err := strconv.Atoi(timeoutStr); err == nil { - timeout = parsedTimeout - } - } - - return (time.Duration(timeout) + 5) * time.Second -} - -// updateTaskStatus updates the status of a task using the Task Management Service. -func updateTaskStatus(ctx context.Context, taskID int64, status v1.TaskStatusEnum, message string) error { - client, err := x.CreateClient(os.Getenv("SERVER_ENDPOINT")) - if err != nil { - return fmt.Errorf("failed to create client: %w", err) - } - - _, err = client.UpdateTaskStatus(ctx, connect.NewRequest(&v1.UpdateTaskStatusRequest{ - Id: int32(taskID), - Status: status, - Message: message, - })) - if err != nil { - return fmt.Errorf("failed to update task %d status: %w", taskID, err) - } - - return nil -} diff --git a/server/repository/factory.go b/server/repository/factory.go index ecb1aae..5ba10df 100644 --- a/server/repository/factory.go +++ b/server/repository/factory.go @@ -1,22 +1,14 @@ package repositories import ( - "context" "database/sql" "fmt" "log/slog" - "os" "time" - cloudv1 "task/pkg/gen/cloud/v1" - "task/pkg/worker" - "task/pkg/x" interfaces "task/server/repository/interface" tasks "task/server/repository/model/task" - "github.com/riverqueue/river" - "github.com/riverqueue/river/riverdriver/riverdatabasesql" - "github.com/riverqueue/river/rivermigrate" "gorm.io/driver/postgres" "gorm.io/gorm" ) @@ -47,77 +39,6 @@ func GetRepository(url string, workerCount int, maxConns int) (interfaces.TaskMa return nil, err } - migrator, err := rivermigrate.New(riverdatabasesql.New(sqlDB), nil) - if err != nil { - return nil, fmt.Errorf("failed to create river migrator: %w", err) - } - - _, err = migrator.Migrate(context.Background(), rivermigrate.DirectionUp, &rivermigrate.MigrateOpts{}) - if err != nil { - panic(err) - } - - // Set up River workers and client - workers := river.NewWorkers() - if err := river.AddWorkerSafely(workers, &worker.TaskWorker{}); err != nil { - return nil, fmt.Errorf("failed to add TaskWorker: %w", err) - } - - if err := river.AddWorkerSafely(workers, &worker.ReconcileTaskWorker{}); err != nil { - return nil, fmt.Errorf("failed to add TaskWorker: %w", err) - } - - // TODO: Add comprehensive documentation - // We added periodic reconciliation jobs to handle stuck tasks. These jobs will: - // 1. Get a list of all stuck tasks - // 2. Change their status to "queued" - // 3. Enqueue them for processing - // Currently, we're making direct DB changes, but ideally, the server should - // implement an API to handle this logic. In the future, these scheduled jobs - // should just call the API instead of modifying the database directly. - - var reconcileTasks = []*river.PeriodicJob{ - river.NewPeriodicJob( - river.PeriodicInterval(time.Duration(x.CRON_TIME)*time.Second), - func() (river.JobArgs, *river.InsertOpts) { - return worker.ReconcileTaskWorkerArgs{ - - Status: int(cloudv1.TaskStatusEnum_RUNNING), - URL: url, - }, nil - }, - &river.PeriodicJobOpts{RunOnStart: true}, - ), - river.NewPeriodicJob( - river.PeriodicInterval(time.Duration(x.CRON_TIME)*time.Second), - func() (river.JobArgs, *river.InsertOpts) { - return worker.ReconcileTaskWorkerArgs{ - Status: int(cloudv1.TaskStatusEnum_QUEUED), - URL: url, - }, nil - }, - &river.PeriodicJobOpts{RunOnStart: true}, - ), - } - - riverClient, err := river.NewClient(riverdatabasesql.New(sqlDB), &river.Config{ - Queues: map[string]river.QueueConfig{ - river.QueueDefault: {MaxWorkers: workerCount}, - }, - Workers: workers, - PeriodicJobs: reconcileTasks, - ErrorHandler: &worker.CustomErrorHandler{}, - Logger: slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo})), - }) - if err != nil { - return nil, fmt.Errorf("failed to create River client: %w", err) - } - - // Start River client - if err := riverClient.Start(context.Background()); err != nil { - return nil, fmt.Errorf("failed to start River client: %w", err) - } - // Perform database migrations if err = db.AutoMigrate(&tasks.Task{}, &tasks.TaskHistory{}); err != nil { return nil, fmt.Errorf("failed to run auto migrations: %w", err) @@ -141,5 +62,5 @@ func GetRepository(url string, workerCount int, maxConns int) (interfaces.TaskMa slog.Info("Created index", "name", idx.name) } - return NewPostgresRepo(db, riverClient), nil + return NewPostgresRepo(db), nil } diff --git a/server/repository/gormimpl/history.go b/server/repository/gormimpl/history.go index ad38510..7f015ac 100644 --- a/server/repository/gormimpl/history.go +++ b/server/repository/gormimpl/history.go @@ -2,12 +2,10 @@ package gormimpl import ( "context" - "database/sql" "fmt" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" - "github.com/riverqueue/river" "gorm.io/gorm" interfaces "task/server/repository/interface" @@ -26,8 +24,7 @@ var ( // TaskHistoryRepo handles database operations for task history entries. type TaskHistoryRepo struct { - db *gorm.DB - riverClient *river.Client[*sql.Tx] + db *gorm.DB } // CreateTaskHistory creates a new history entry for a task. @@ -70,9 +67,8 @@ func (s *TaskHistoryRepo) ListTaskHistories(ctx context.Context, taskID uint) ([ // NewTaskHistoryRepo creates and returns a new instance of TaskHistoryRepo. // It takes a GORM database connection and a River client as parameters. -func NewTaskHistoryRepo(db *gorm.DB, riverClient *river.Client[*sql.Tx]) interfaces.TaskHistoryRepo { +func NewTaskHistoryRepo(db *gorm.DB) interfaces.TaskHistoryRepo { return &TaskHistoryRepo{ - db: db, - riverClient: riverClient, + db: db, } } diff --git a/server/repository/gormimpl/task.go b/server/repository/gormimpl/task.go index c5164f2..e628f15 100644 --- a/server/repository/gormimpl/task.go +++ b/server/repository/gormimpl/task.go @@ -2,16 +2,13 @@ package gormimpl import ( "context" - "database/sql" "fmt" + "time" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" - "github.com/riverqueue/river" "gorm.io/gorm" - cloudv1 "task/pkg/gen/cloud/v1" - "task/pkg/worker" interfaces "task/server/repository/interface" models "task/server/repository/model/task" ) @@ -37,8 +34,7 @@ var ( // TaskRepo implements the TaskRepo interface using GORM for database operations // and River for task queue management. type TaskRepo struct { - db *gorm.DB - riverClient *river.Client[*sql.Tx] + db *gorm.DB } // CreateTask creates a new task in the database and enqueues it for processing. @@ -57,15 +53,6 @@ func (s *TaskRepo) CreateTask(ctx context.Context, task models.Task) (models.Tas taskOperations.WithLabelValues("create", "error").Inc() return models.Task{}, fmt.Errorf("failed to get task ID after creation") } - _, err := s.riverClient.Insert(context.Background(), worker.TaskArgument{ - Task: task, - }, &river.InsertOpts{ - MaxAttempts: 5, - }) - if err != nil { - taskOperations.WithLabelValues("create", "error").Inc() - return models.Task{}, fmt.Errorf("failed to enqueue task: %w", err) - } taskOperations.WithLabelValues("create", "success").Inc() return task, nil @@ -96,21 +83,6 @@ func (s *TaskRepo) UpdateTaskStatus(ctx context.Context, taskID uint, status int taskOperations.WithLabelValues("update_status", "error").Inc() return fmt.Errorf("failed to update task status: %w", err) } - if status == int(cloudv1.TaskStatusEnum_QUEUED) { - task, err := s.GetTaskByID(ctx, taskID) - if err != nil { - return fmt.Errorf("failed to get task by ID: %w", err) - } - _, err = s.riverClient.Insert(ctx, worker.TaskArgument{ - Task: *task, - }, &river.InsertOpts{ - MaxAttempts: 5, - }) - if err != nil { - taskOperations.WithLabelValues("update_status", "error").Inc() - return fmt.Errorf("failed to enqueue task: %w", err) - } - } taskOperations.WithLabelValues("update_status", "success").Inc() return nil @@ -175,11 +147,32 @@ func (s *TaskRepo) GetTaskStatusCounts(ctx context.Context) (map[int]int64, erro return counts, nil } +// GetStalledTasks retrieves tasks with status "unknown" or "queue" that have been in that state for more than 10 seconds. +// It returns a slice of tasks and an error if the operation fails. +func (s *TaskRepo) GetStalledTasks(ctx context.Context) ([]models.Task, error) { + timer := prometheus.NewTimer(taskLatency.WithLabelValues("get_stalled")) + defer timer.ObserveDuration() + + var tasks []models.Task + tenSecondsAgo := time.Now().Add(-60 * time.Second) + + err := s.db.Where("(status = ? OR status = ?) AND updated_at < ?", + 0, 4, tenSecondsAgo). + Find(&tasks).Error + + if err != nil { + taskOperations.WithLabelValues("get_stalled", "error").Inc() + return nil, fmt.Errorf("failed to retrieve stalled tasks: %w", err) + } + + taskOperations.WithLabelValues("get_stalled", "success").Inc() + return tasks, nil +} + // NewTaskRepo creates and returns a new instance of TaskRepo. // It requires a GORM database connection and a River client for task queue management. -func NewTaskRepo(db *gorm.DB, riverClient *river.Client[*sql.Tx]) interfaces.TaskRepo { +func NewTaskRepo(db *gorm.DB) interfaces.TaskRepo { return &TaskRepo{ - db: db, - riverClient: riverClient, + db: db, } } diff --git a/server/repository/interface/task.go b/server/repository/interface/task.go index 0d614b4..39cc956 100644 --- a/server/repository/interface/task.go +++ b/server/repository/interface/task.go @@ -36,4 +36,6 @@ type TaskRepo interface { // It returns a map where the key is the status code and the value is the count of tasks with that status. // An error is returned if any occurs during the operation. GetTaskStatusCounts(ctx context.Context) (map[int]int64, error) + + GetStalledTasks(ctx context.Context) ([]model.Task, error) } diff --git a/server/repository/postgres.go b/server/repository/postgres.go index 7c3653d..63c8158 100644 --- a/server/repository/postgres.go +++ b/server/repository/postgres.go @@ -1,12 +1,10 @@ package repositories import ( - "database/sql" "fmt" gormimpl "task/server/repository/gormimpl" interfaces "task/server/repository/interface" - "github.com/riverqueue/river" "gorm.io/gorm" ) @@ -24,9 +22,9 @@ func (r Postgres) TaskHistoryRepo() interfaces.TaskHistoryRepo { return r.history } -func NewPostgresRepo(db *gorm.DB, riverClient *river.Client[*sql.Tx]) interfaces.TaskManagmentInterface { +func NewPostgresRepo(db *gorm.DB) interfaces.TaskManagmentInterface { return &Postgres{ - task: gormimpl.NewTaskRepo(db, riverClient), - history: gormimpl.NewTaskHistoryRepo(db, riverClient), + task: gormimpl.NewTaskRepo(db), + history: gormimpl.NewTaskHistoryRepo(db), } } diff --git a/server/route/task.go b/server/route/task.go index b11a3b9..349052c 100644 --- a/server/route/task.go +++ b/server/route/task.go @@ -16,6 +16,8 @@ import ( "google.golang.org/protobuf/types/known/emptypb" + "sync" + connect "connectrpc.com/connect" "github.com/avast/retry-go/v4" protovalidate "github.com/bufbuild/protovalidate-go" @@ -38,6 +40,10 @@ type TaskServer struct { logger *log.Logger validator *protovalidate.Validator metrics *taskMetrics + channel chan task.Task + stream *connect.BidiStream[v1.StreamRequest, v1.StreamResponse] + workerPool chan struct{} + maxWorkers int } type taskMetrics struct { @@ -92,12 +98,20 @@ func NewTaskServer(repo interfaces.TaskManagmentInterface) cloudv1connect.TaskMa log.Fatalf("Failed to initialize validator: %v", err) } + maxWorkers := 10 // You can make this configurable server := &TaskServer{ taskRepo: repo.TaskRepo(), historyRepo: repo.TaskHistoryRepo(), logger: log.New(os.Stdout, logPrefix, log.LstdFlags|log.Lshortfile), validator: validator, metrics: newTaskMetrics(), + workerPool: make(chan struct{}, maxWorkers), + maxWorkers: maxWorkers, + } + + // Initialize the worker pool + for i := 0; i < maxWorkers; i++ { + server.workerPool <- struct{}{} } server.logger.Println("TaskServer initialized successfully") @@ -125,7 +139,7 @@ func (s *TaskServer) CreateTask(ctx context.Context, req *connect.Request[v1.Cre s.metrics.errorCounter.WithLabelValues("create_task").Inc() return nil, s.logError(err, "Failed to create task in repository") } - + s.channel <- createdTask // Attempt to log task creation history with retries err = retry.Do( func() error { @@ -289,24 +303,138 @@ func (s *TaskServer) GetStatus(ctx context.Context, req *connect.Request[v1.GetS return connect.NewResponse(response), nil } -// GetStatus retrieves the count of tasks for each status. +// StreamConnection handles bidirectional streaming for task updates and assignments. func (s *TaskServer) StreamConnection(ctx context.Context, stream *connect.BidiStream[v1.StreamRequest, v1.StreamResponse]) error { + s.stream = stream + s.channel = make(chan task.Task, 100) // Buffered channel to prevent blocking + defer close(s.channel) + + // Start a goroutine to handle sending work assignments + go s.sendWorkAssignments(ctx) + + go s.streamStalledTasks(ctx) for { if err := ctx.Err(); err != nil { + s.logger.Printf("Context error in StreamConnection: %v", err) return err } - request, err := stream.Receive() - if err != nil && errors.Is(err, io.EOF) { - return nil - } else if err != nil { + + req, err := stream.Receive() + if err != nil { + if errors.Is(err, io.EOF) { + s.logger.Println("Client closed the stream") + return nil + } + s.logger.Printf("Error receiving request: %v", err) return fmt.Errorf("receive request: %w", err) } - fmt.Println(request) - if err := stream.Send(&v1.StreamResponse{Response: &v1.StreamResponse_Heartbeat{Heartbeat: &v1.Heartbeat{}}}); err != nil { - return fmt.Errorf("send response: %w", err) + + if err := s.handleStreamRequest(ctx, req); err != nil { + s.logger.Printf("Error handling stream request: %v", err) + return err } } +} + +func (s *TaskServer) streamStalledTasks(ctx context.Context) error { + // Reconcile the tasks + tasks, err := s.taskRepo.GetStalledTasks(ctx) + if err != nil { + s.logger.Printf("Failed to retrieve stalled tasks: %v", err) + s.metrics.errorCounter.WithLabelValues("get_stalled_tasks").Inc() + return connect.NewError(connect.CodeInternal, fmt.Errorf("failed to retrieve stalled tasks: %w", err)) + } + // Use a separate goroutine to send tasks to the channel + go func() { + for _, task := range tasks { + select { + case s.channel <- task: + // Task sent successfully + case <-ctx.Done(): + s.logger.Println("Context cancelled while sending stalled tasks") + return + } + } + }() + return nil +} + +// sendWorkAssignments sends work assignments to multiple workers +func (s *TaskServer) sendWorkAssignments(ctx context.Context) { + var wg sync.WaitGroup + for { + select { + case <-ctx.Done(): + wg.Wait() // Wait for all workers to finish before returning + return + case work := <-s.channel: + select { + case <-s.workerPool: // Acquire a worker + wg.Add(1) + go func(t task.Task) { + defer wg.Done() + defer func() { s.workerPool <- struct{}{} }() // Release the worker + + response := &v1.StreamResponse{ + Response: &v1.StreamResponse_WorkAssignment{ + WorkAssignment: &v1.WorkAssignment{ + AssignmentId: int64(t.ID), + Task: s.convertTaskToProto(&t), + }, + }, + } + err := retry.Do( + func() error { + return s.stream.Send(response) + }, + retry.Attempts(3), + retry.Delay(100*time.Millisecond), + retry.DelayType(retry.BackOffDelay), + retry.OnRetry(func(n uint, err error) { + s.logger.Printf("Retry %d: Error sending work assignment: %v", n+1, err) + }), + ) + if err != nil { + s.logger.Printf("Failed to send work assignment after retries: %v", err) + return + } + }(work) + case <-ctx.Done(): + wg.Wait() // Wait for all workers to finish before returning + return + } + } + } +} + +// handleStreamRequest processes incoming stream requests +func (s *TaskServer) handleStreamRequest(ctx context.Context, req *v1.StreamRequest) error { + switch r := req.Request.(type) { + case *v1.StreamRequest_UpdateTaskStatus: + return s.handleUpdateTaskStatus(ctx, r.UpdateTaskStatus) + case *v1.StreamRequest_Heartbeat: + fmt.Println("Heartbeat received") + default: + s.logger.Printf("Received unknown request type: %T", r) + return fmt.Errorf("unknown request type: %T", r) + } + return nil +} + +// handleUpdateTaskStatus processes task status update requests +func (s *TaskServer) handleUpdateTaskStatus(ctx context.Context, update *v1.UpdateTaskStatusRequest) error { + if err := s.taskRepo.UpdateTaskStatus(ctx, uint(update.Id), int(update.Status)); err != nil { + s.metrics.errorCounter.WithLabelValues("update_task_status").Inc() + return fmt.Errorf("failed to update task status: id=%d, error: %w", update.Id, err) + } + + if err := s.createTaskStatusHistory(ctx, uint(update.Id), int(update.Status), update.Message); err != nil { + s.logger.Printf("WARNING: Failed to create task status history: %v", err) + // Consider whether to return an error here or continue + } + + s.logger.Printf("Task status updated: id=%d, new status=%d", update.Id, update.Status) return nil } From 7fd46e2585ef48370e821536dff92996287f979a Mon Sep 17 00:00:00 2001 From: Yuvraj Date: Fri, 11 Oct 2024 07:01:15 +0530 Subject: [PATCH 06/17] fix max bytes for send and recieve --- cli/cmd/agent.go | 133 +++++++++++++++++++++++++++++++++---------- go.mod | 10 ---- go.sum | 30 ---------- server/root/main.go | 2 + server/route/task.go | 17 +++++- 5 files changed, 120 insertions(+), 72 deletions(-) diff --git a/cli/cmd/agent.go b/cli/cmd/agent.go index 3865751..f1956fb 100644 --- a/cli/cmd/agent.go +++ b/cli/cmd/agent.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "log/slog" + "sync" v1 "task/pkg/gen/cloud/v1" "task/pkg/plugins" "task/pkg/x" @@ -35,28 +36,63 @@ func runWorkflowOrchestration(ctx context.Context) error { logger := slog.With("component", "workflow_orchestration") logger.Info("Starting workflow orchestration server") + // Create a cancelable context for graceful shutdown + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + // Create a WaitGroup to wait for all goroutines to finish + var wg sync.WaitGroup + + for { + select { + case <-ctx.Done(): + logger.Info("Shutting down gracefully...") + wg.Wait() + logger.Info("Workflow orchestration server stopped") + return nil + default: + if err := runStreamConnection(ctx, &wg, logger); err != nil { + logger.Error("Stream connection error", "error", err) + time.Sleep(5 * time.Second) // Wait before retrying + continue + } + } + } +} + +func runStreamConnection(ctx context.Context, wg *sync.WaitGroup, logger *slog.Logger) error { conn, err := grpc.Dial("localhost:8080", grpc.WithInsecure()) if err != nil { - logger.Error("Failed to connect to gRPC server", "error", err) return fmt.Errorf("failed to connect to gRPC server: %w", err) } defer conn.Close() client := v1.NewTaskManagementServiceClient(conn) + // Create a buffered channel for work assignments + workChan := make(chan *v1.StreamResponse_WorkAssignment, 100) + + // Start the stream stream, err := client.StreamConnection(ctx) if err != nil { - logger.Error("Failed to start stream", "error", err) return fmt.Errorf("failed to start stream: %w", err) } - go sendPeriodicRequests(ctx, stream, logger) + // Start goroutines + wg.Add(3) + go sendPeriodicRequests(ctx, stream, logger, wg) + go receiveResponses(ctx, stream, workChan, logger, wg) + go processWorkAssignments(ctx, stream, workChan, logger, wg) + + // Wait for all goroutines to finish + wg.Wait() - return receiveAndProcessResponses(ctx, stream, logger) + return nil } // sendPeriodicRequests sends periodic RunCommand requests to the server. -func sendPeriodicRequests(ctx context.Context, stream v1.TaskManagementService_StreamConnectionClient, logger *slog.Logger) { +func sendPeriodicRequests(ctx context.Context, stream v1.TaskManagementService_StreamConnectionClient, logger *slog.Logger, wg *sync.WaitGroup) { + defer wg.Done() ticker := time.NewTicker(5 * time.Second) defer ticker.Stop() @@ -74,43 +110,80 @@ func sendPeriodicRequests(ctx context.Context, stream v1.TaskManagementService_S } } -// receiveAndProcessResponses continuously receives and processes responses from the server. -func receiveAndProcessResponses(ctx context.Context, stream v1.TaskManagementService_StreamConnectionClient, logger *slog.Logger) error { +func receiveResponses(ctx context.Context, stream v1.TaskManagementService_StreamConnectionClient, workChan chan<- *v1.StreamResponse_WorkAssignment, logger *slog.Logger, wg *sync.WaitGroup) { + defer wg.Done() for { - response, err := stream.Recv() - if err != nil { - if err == io.EOF || ctx.Err() != nil { - logger.Info("Stream closed") - return nil + select { + case <-ctx.Done(): + return + default: + response, err := stream.Recv() + if err != nil { + if err == io.EOF || ctx.Err() != nil { + logger.Info("Stream closed") + return + } + logger.Error("Error receiving response", "error", err) + time.Sleep(time.Second) // Wait before retrying + continue + } + fmt.Println(response) + switch resp := response.Response.(type) { + case *v1.StreamResponse_WorkAssignment: + select { + case workChan <- resp: + logger.Debug("Work assignment queued", "task_id", resp.WorkAssignment.Task.Id) + default: + logger.Warn("Work channel full, discarding work assignment", "task_id", resp.WorkAssignment.Task.Id) + } + default: + logger.Warn("Received unknown response type", "type", fmt.Sprintf("%T", resp)) } - logger.Error("Error receiving response", "error", err) - return fmt.Errorf("error receiving response: %w", err) } + } +} - switch resp := response.Response.(type) { - case *v1.StreamResponse_WorkAssignment: - status, message, err := processWorkflowUpdate(ctx, resp, logger) +func processWorkAssignments(ctx context.Context, stream v1.TaskManagementService_StreamConnectionClient, workChan <-chan *v1.StreamResponse_WorkAssignment, logger *slog.Logger, wg *sync.WaitGroup) { + defer wg.Done() + for { + select { + case <-ctx.Done(): + return + case work := <-workChan: + status, message, err := processWorkflowUpdate(ctx, work, logger) if err != nil { logger.Error("Error processing workflow update", "error", err) } - // Send status update through the stream - if err := stream.Send(&v1.StreamRequest{ - Request: &v1.StreamRequest_UpdateTaskStatus{ - UpdateTaskStatus: &v1.UpdateTaskStatusRequest{ - Id: resp.WorkAssignment.Task.Id, - Status: status, - Message: message, - }, - }, - }); err != nil { - logger.Error("Failed to send status update", "error", err) + // Send status update through the stream with retries + if err := sendStatusUpdateWithRetry(ctx, stream, work.WorkAssignment.Task.Id, status, message); err != nil { + logger.Error("Failed to send status update after retries", "error", err) } - default: - logger.Warn("Received unknown response type", "type", fmt.Sprintf("%T", resp)) } } } +func sendStatusUpdateWithRetry(ctx context.Context, stream v1.TaskManagementService_StreamConnectionClient, taskID int32, status v1.TaskStatusEnum, message string) error { + maxRetries := 5 + for i := 0; i < maxRetries; i++ { + err := stream.Send(&v1.StreamRequest{ + Request: &v1.StreamRequest_UpdateTaskStatus{ + UpdateTaskStatus: &v1.UpdateTaskStatusRequest{ + Id: taskID, + Status: status, + Message: message, + }, + }, + }) + if err == nil { + return nil + } + if i < maxRetries-1 { + time.Sleep(time.Duration(i+1) * time.Second) + } + } + return fmt.Errorf("failed to send status update after %d retries", maxRetries) +} + // processWorkflowUpdate handles different types of responses and returns the workflow state. func processWorkflowUpdate(ctx context.Context, work *v1.StreamResponse_WorkAssignment, logger *slog.Logger) (v1.TaskStatusEnum, string, error) { response := work.WorkAssignment diff --git a/go.mod b/go.mod index dfd2883..8ede2a9 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,6 @@ require ( github.com/kelseyhightower/envconfig v1.4.0 github.com/olekukonko/tablewriter v0.0.5 github.com/prometheus/client_golang v1.20.4 - github.com/riverqueue/river v0.12.1 github.com/rs/cors v1.11.1 github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.9.0 @@ -46,7 +45,6 @@ require ( github.com/google/cel-go v0.21.0 // indirect github.com/gorilla/securecookie v1.1.2 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgx/v5 v5.7.1 // indirect @@ -60,22 +58,14 @@ require ( github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect - github.com/riverqueue/river/riverdriver v0.12.1 // indirect - github.com/riverqueue/river/rivershared v0.12.1 // indirect - github.com/riverqueue/river/rivertype v0.12.1 // indirect github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect github.com/stretchr/objx v0.5.2 // indirect - github.com/tidwall/gjson v1.17.3 // indirect - github.com/tidwall/match v1.1.1 // indirect - github.com/tidwall/pretty v1.2.1 // indirect - github.com/tidwall/sjson v1.2.5 // indirect go.opentelemetry.io/otel v1.21.0 // indirect go.opentelemetry.io/otel/metric v1.21.0 // indirect go.opentelemetry.io/otel/sdk v1.21.0 // indirect go.opentelemetry.io/otel/trace v1.21.0 // indirect - go.uber.org/goleak v1.3.0 // indirect golang.org/x/crypto v0.27.0 // indirect golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect golang.org/x/sync v0.8.0 // indirect diff --git a/go.sum b/go.sum index 9486f43..cce0d57 100644 --- a/go.sum +++ b/go.sum @@ -47,8 +47,6 @@ github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzq github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 h1:Dj0L5fhJ9F82ZJyVOmBx6msDp/kfd1t9GRfny/mfJA0= -github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= @@ -73,8 +71,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= -github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= @@ -91,20 +87,6 @@ github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/riverqueue/river v0.12.1 h1:TS3FVioPStlvb7yj1dYgtgX+zn/3JkLdPn+S6qNBcZ0= -github.com/riverqueue/river v0.12.1/go.mod h1:j7O42JlHo76YgXkAFX66E63Ke890/oSUUlui/ImyLuU= -github.com/riverqueue/river/riverdriver v0.12.1 h1:MqAh6mw9h/m/nNXImJTXXtCefTDPdmZSlgsUdSYUHe0= -github.com/riverqueue/river/riverdriver v0.12.1/go.mod h1:E4hf4wPidG0xYrwsez4R9u4LvLdjlDu9m4iJFpb1DfQ= -github.com/riverqueue/river/riverdriver/riverdatabasesql v0.12.1 h1:E2pYemeaaiqOqr1x1Cq872IdulGu5z/iIHChqxPJwfA= -github.com/riverqueue/river/riverdriver/riverdatabasesql v0.12.1/go.mod h1:+5DVUCfdPS3ZtsRm4V0GzQfXJI9MsFvq3BNqW/Nei3E= -github.com/riverqueue/river/riverdriver/riverpgxv5 v0.12.1 h1:stodaBk+GKMU4Uwoj2tShG5L/EK/E5gWOQwZhsJ65QY= -github.com/riverqueue/river/riverdriver/riverpgxv5 v0.12.1/go.mod h1:kwV0SdmvBYOj3hsI4sn3tQQQ5NqXrq68yDvKb1Jms1E= -github.com/riverqueue/river/rivershared v0.12.1 h1:7y03CM6iYrSoT1k6ylneTIoK74qQ27yi1aoT3dozU6Y= -github.com/riverqueue/river/rivershared v0.12.1/go.mod h1:IpJ63Jz/Rx61nKhJ45K9IdJR0VEHf3qnFlEPI9l11HM= -github.com/riverqueue/river/rivertype v0.12.1 h1:iTciVhZ/yQQQBMAouivPrSlrQH8MEK5uCVtzu3eITu8= -github.com/riverqueue/river/rivertype v0.12.1/go.mod h1:3WRQEDlLKZky/vGwFcZC3uKjC+/8izE6ucHwCsuir98= -github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= -github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= @@ -128,16 +110,6 @@ 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/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/gjson v1.17.3 h1:bwWLZU7icoKRG+C+0PNwIKC6FCJO/Q3p2pZvuP0jN94= -github.com/tidwall/gjson v1.17.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= -github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= -github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= -github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= -github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= go.akshayshah.org/attest v1.0.2 h1:qOv9PXCG2mwnph3g0I3yZj0rLAwLyUITs8nhxP+wS44= go.akshayshah.org/attest v1.0.2/go.mod h1:PnWzcW5j9dkyGwTlBmUsYpPnHG0AUPrs1RQ+HrldWO0= go.akshayshah.org/connectauth v0.6.0 h1:lyzJ3z33L2KKgfSZW95tilktE7bCgc/OTQm0V8C2//g= @@ -154,8 +126,6 @@ go.opentelemetry.io/otel/sdk/metric v1.19.0 h1:EJoTO5qysMsYCa+w4UghwFV/ptQgqSL/8 go.opentelemetry.io/otel/sdk/metric v1.19.0/go.mod h1:XjG0jQyFJrv2PbMvwND7LwCEhsJzCzV5210euduKcKY= go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc= go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= -go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 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/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw= diff --git a/server/root/main.go b/server/root/main.go index 0bee3f2..29e6bce 100644 --- a/server/root/main.go +++ b/server/root/main.go @@ -204,6 +204,8 @@ func setupHandlers(mux *http.ServeMux, repo interfaces.TaskManagmentInterface, m route.NewTaskServer(repo), connect.WithInterceptors(otelInterceptor), connect.WithCompressMinBytes(CompressMinByte), + connect.WithSendMaxBytes(1024*1024*1024), + connect.WithReadMaxBytes(1024*1024*1024), ) mux.Handle(pattern, handler) diff --git a/server/route/task.go b/server/route/task.go index 349052c..f348107 100644 --- a/server/route/task.go +++ b/server/route/task.go @@ -18,6 +18,8 @@ import ( "sync" + "strings" + connect "connectrpc.com/connect" "github.com/avast/retry-go/v4" protovalidate "github.com/bufbuild/protovalidate-go" @@ -386,7 +388,13 @@ func (s *TaskServer) sendWorkAssignments(ctx context.Context) { } err := retry.Do( func() error { - return s.stream.Send(response) + if err := s.stream.Send(response); err != nil { + if errors.Is(err, io.EOF) || strings.Contains(err.Error(), "write envelope: short write") { + return retry.Unrecoverable(err) + } + return err + } + return nil }, retry.Attempts(3), retry.Delay(100*time.Millisecond), @@ -396,8 +404,13 @@ func (s *TaskServer) sendWorkAssignments(ctx context.Context) { }), ) if err != nil { + if errors.Is(err, io.EOF) || strings.Contains(err.Error(), "write envelope: short write") { + s.logger.Printf("Client disconnected, stopping work assignment: %v", err) + // Consider re-queueing the task or updating its status + return + } s.logger.Printf("Failed to send work assignment after retries: %v", err) - return + // Consider re-queueing the task or updating its status } }(work) case <-ctx.Done(): From b27d8e0255d4b866b48b3efe7cfc57087f7c5077 Mon Sep 17 00:00:00 2001 From: Yuvraj Date: Fri, 11 Oct 2024 17:33:03 +0530 Subject: [PATCH 07/17] fix stream --- cli/cmd/agent.go | 104 +++++++++++++----- go.mod | 1 + go.sum | 2 + idl/cloud/v1/cloud.proto | 1 - pkg/worker/error.go | 24 +++++ server/repository/gormimpl/task.go | 6 +- server/repository/model/task/task.go | 2 +- server/root/main.go | 10 +- server/route/task.go | 151 ++++++++++++++++++--------- 9 files changed, 216 insertions(+), 85 deletions(-) create mode 100644 pkg/worker/error.go diff --git a/cli/cmd/agent.go b/cli/cmd/agent.go index f1956fb..b5d04f6 100644 --- a/cli/cmd/agent.go +++ b/cli/cmd/agent.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "log/slog" + "math" "sync" v1 "task/pkg/gen/cloud/v1" "task/pkg/plugins" @@ -61,7 +62,15 @@ func runWorkflowOrchestration(ctx context.Context) error { } func runStreamConnection(ctx context.Context, wg *sync.WaitGroup, logger *slog.Logger) error { - conn, err := grpc.Dial("localhost:8080", grpc.WithInsecure()) + conn, err := grpc.Dial("localhost:8080", []grpc.DialOption{ + grpc.WithInsecure(), + grpc.WithDefaultCallOptions( + grpc.MaxCallRecvMsgSize(math.MaxInt64), + grpc.MaxCallSendMsgSize(math.MaxInt64), + ), + grpc.WithInitialWindowSize(math.MaxInt32), + grpc.WithInitialConnWindowSize(math.MaxInt32), + }...) if err != nil { return fmt.Errorf("failed to connect to gRPC server: %w", err) } @@ -150,13 +159,50 @@ func processWorkAssignments(ctx context.Context, stream v1.TaskManagementService case <-ctx.Done(): return case work := <-workChan: - status, message, err := processWorkflowUpdate(ctx, work, logger) - if err != nil { - logger.Error("Error processing workflow update", "error", err) + maxAttempts := 3 + initialBackoff := 1 * time.Second + + var finalStatus v1.TaskStatusEnum + var finalMessage string + + for attempt := 1; attempt <= maxAttempts; attempt++ { + // Update status to Running for each attempt + + runningMessage := fmt.Sprintf("Running attempt %d of %d", attempt, maxAttempts) + if err := sendStatusUpdateWithRetry(ctx, stream, work.WorkAssignment.Task.Id, v1.TaskStatusEnum_RUNNING, runningMessage); err != nil { + logger.Error("Failed to send running status update", "error", err, "task_id", work.WorkAssignment.Task.Id, "attempt", attempt) + } + + _, message, err := processWorkflowUpdate(ctx, work, logger) + + if err != nil { + failedMessage := fmt.Sprintf("Attempt %d failed: %v", attempt, err) + if err := sendStatusUpdateWithRetry(ctx, stream, work.WorkAssignment.Task.Id, v1.TaskStatusEnum_FAILED, failedMessage); err != nil { + logger.Error("Failed to send failed status update", "error", err, "task_id", work.WorkAssignment.Task.Id, "attempt", attempt) + } + + if attempt == maxAttempts { + finalStatus = v1.TaskStatusEnum_FAILED + finalMessage = fmt.Sprintf("All %d attempts failed. Last error: %v", maxAttempts, err) + } else { + // Wait before the next attempt + select { + case <-ctx.Done(): + return + case <-time.After(initialBackoff * time.Duration(1< 3 { + if t.Status > 4 { return errors.New("invalid task status") } diff --git a/server/root/main.go b/server/root/main.go index 29e6bce..cf841b9 100644 --- a/server/root/main.go +++ b/server/root/main.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "log/slog" + "math" "net/http" "os" "os/signal" @@ -18,14 +19,15 @@ import ( "task/server/route" // Import route package oauth2 "task/server/route/oauth2" + "golang.org/x/net/http2" + "golang.org/x/net/http2/h2c" + "connectrpc.com/connect" "connectrpc.com/grpchealth" "connectrpc.com/grpcreflect" "connectrpc.com/otelconnect" "github.com/rs/cors" "go.akshayshah.org/connectauth" - "golang.org/x/net/http2" - "golang.org/x/net/http2/h2c" "github.com/prometheus/client_golang/prometheus/promhttp" ) @@ -204,8 +206,8 @@ func setupHandlers(mux *http.ServeMux, repo interfaces.TaskManagmentInterface, m route.NewTaskServer(repo), connect.WithInterceptors(otelInterceptor), connect.WithCompressMinBytes(CompressMinByte), - connect.WithSendMaxBytes(1024*1024*1024), - connect.WithReadMaxBytes(1024*1024*1024), + connect.WithSendMaxBytes(math.MaxInt32), + connect.WithReadMaxBytes(math.MaxInt32), ) mux.Handle(pattern, handler) diff --git a/server/route/task.go b/server/route/task.go index f348107..17b1367 100644 --- a/server/route/task.go +++ b/server/route/task.go @@ -23,6 +23,8 @@ import ( connect "connectrpc.com/connect" "github.com/avast/retry-go/v4" protovalidate "github.com/bufbuild/protovalidate-go" + "github.com/google/uuid" + "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "google.golang.org/protobuf/reflect/protoreflect" @@ -37,15 +39,17 @@ const ( // TaskServer represents the server handling task-related requests. // It implements the cloudv1connect.TaskManagementServiceHandler interface. type TaskServer struct { - taskRepo interfaces.TaskRepo - historyRepo interfaces.TaskHistoryRepo - logger *log.Logger - validator *protovalidate.Validator - metrics *taskMetrics - channel chan task.Task - stream *connect.BidiStream[v1.StreamRequest, v1.StreamResponse] - workerPool chan struct{} - maxWorkers int + taskRepo interfaces.TaskRepo + historyRepo interfaces.TaskHistoryRepo + logger *log.Logger + validator *protovalidate.Validator + metrics *taskMetrics + channel chan task.Task + stream *connect.BidiStream[v1.StreamRequest, v1.StreamResponse] + workerPool chan struct{} + maxWorkers int + clientHeartbeats sync.Map + heartbeatTimeout time.Duration } type taskMetrics struct { @@ -102,13 +106,15 @@ func NewTaskServer(repo interfaces.TaskManagmentInterface) cloudv1connect.TaskMa maxWorkers := 10 // You can make this configurable server := &TaskServer{ - taskRepo: repo.TaskRepo(), - historyRepo: repo.TaskHistoryRepo(), - logger: log.New(os.Stdout, logPrefix, log.LstdFlags|log.Lshortfile), - validator: validator, - metrics: newTaskMetrics(), - workerPool: make(chan struct{}, maxWorkers), - maxWorkers: maxWorkers, + taskRepo: repo.TaskRepo(), + historyRepo: repo.TaskHistoryRepo(), + logger: log.New(os.Stdout, logPrefix, log.LstdFlags|log.Lshortfile), + validator: validator, + metrics: newTaskMetrics(), + channel: make(chan task.Task, 500), + workerPool: make(chan struct{}, maxWorkers), + maxWorkers: maxWorkers, + heartbeatTimeout: 30 * time.Second, // Configurable timeout } // Initialize the worker pool @@ -142,23 +148,6 @@ func (s *TaskServer) CreateTask(ctx context.Context, req *connect.Request[v1.Cre return nil, s.logError(err, "Failed to create task in repository") } s.channel <- createdTask - // Attempt to log task creation history with retries - err = retry.Do( - func() error { - return s.logTaskCreationHistory(ctx, createdTask.ID) - }, - retry.Attempts(3), - retry.Delay(100*time.Millisecond), - retry.DelayType(retry.BackOffDelay), - retry.OnRetry(func(n uint, err error) { - s.logger.Printf("Retry %d: Failed to create task status history: %v", n, err) - }), - ) - - if err != nil { - s.logger.Printf("WARNING: Failed to create task status history after retries: %v", err) - // Consider whether to return an error here or continue - } s.logger.Printf("Task created successfully: id=%d", createdTask.ID) return connect.NewResponse(&v1.CreateTaskResponse{Id: int32(createdTask.ID)}), nil @@ -308,17 +297,23 @@ func (s *TaskServer) GetStatus(ctx context.Context, req *connect.Request[v1.GetS // StreamConnection handles bidirectional streaming for task updates and assignments. func (s *TaskServer) StreamConnection(ctx context.Context, stream *connect.BidiStream[v1.StreamRequest, v1.StreamResponse]) error { s.stream = stream - s.channel = make(chan task.Task, 100) // Buffered channel to prevent blocking defer close(s.channel) + clientID := generateClientID() // Implement this function to generate a unique client ID + s.clientHeartbeats.Store(clientID, time.Now()) + // Start a goroutine to handle sending work assignments - go s.sendWorkAssignments(ctx) + go s.sendWorkAssignments(ctx, clientID) go s.streamStalledTasks(ctx) + // Start a goroutine to check for stale clients + go s.checkStaleClients(ctx) + for { if err := ctx.Err(); err != nil { s.logger.Printf("Context error in StreamConnection: %v", err) + s.clientHeartbeats.Delete(clientID) return err } @@ -326,14 +321,17 @@ func (s *TaskServer) StreamConnection(ctx context.Context, stream *connect.BidiS if err != nil { if errors.Is(err, io.EOF) { s.logger.Println("Client closed the stream") + s.clientHeartbeats.Delete(clientID) return nil } s.logger.Printf("Error receiving request: %v", err) + s.clientHeartbeats.Delete(clientID) return fmt.Errorf("receive request: %w", err) } - if err := s.handleStreamRequest(ctx, req); err != nil { + if err := s.handleStreamRequest(ctx, req, clientID); err != nil { s.logger.Printf("Error handling stream request: %v", err) + s.clientHeartbeats.Delete(clientID) return err } } @@ -363,7 +361,7 @@ func (s *TaskServer) streamStalledTasks(ctx context.Context) error { } // sendWorkAssignments sends work assignments to multiple workers -func (s *TaskServer) sendWorkAssignments(ctx context.Context) { +func (s *TaskServer) sendWorkAssignments(ctx context.Context, clientID string) { var wg sync.WaitGroup for { select { @@ -371,13 +369,28 @@ func (s *TaskServer) sendWorkAssignments(ctx context.Context) { wg.Wait() // Wait for all workers to finish before returning return case work := <-s.channel: + // Check if the client is still active before sending work + if _, ok := s.clientHeartbeats.Load(clientID); !ok { + s.logger.Printf("Client %s is no longer active, requeueing work", clientID) + s.requeueTask(ctx, work) + continue + } + select { case <-s.workerPool: // Acquire a worker wg.Add(1) go func(t task.Task) { defer wg.Done() defer func() { s.workerPool <- struct{}{} }() // Release the worker + err := s.taskRepo.UpdateTaskStatus(ctx, t.ID, int(v1.TaskStatusEnum_QUEUED)) + if err != nil { + s.logger.Printf("Failed to update task status to QUEUED: %v", err) + } + if err := s.createTaskStatusHistory(ctx, t.ID, int(v1.TaskStatusEnum_QUEUED), "Task is queued suucessfully"); err != nil { + s.logger.Printf("WARNING: Failed to create task status history: %v", err) + // Consider whether to return an error here or continue + } response := &v1.StreamResponse{ Response: &v1.StreamResponse_WorkAssignment{ WorkAssignment: &v1.WorkAssignment{ @@ -386,9 +399,10 @@ func (s *TaskServer) sendWorkAssignments(ctx context.Context) { }, }, } - err := retry.Do( + err = retry.Do( func() error { if err := s.stream.Send(response); err != nil { + if errors.Is(err, io.EOF) || strings.Contains(err.Error(), "write envelope: short write") { return retry.Unrecoverable(err) } @@ -404,13 +418,8 @@ func (s *TaskServer) sendWorkAssignments(ctx context.Context) { }), ) if err != nil { - if errors.Is(err, io.EOF) || strings.Contains(err.Error(), "write envelope: short write") { - s.logger.Printf("Client disconnected, stopping work assignment: %v", err) - // Consider re-queueing the task or updating its status - return - } s.logger.Printf("Failed to send work assignment after retries: %v", err) - // Consider re-queueing the task or updating its status + s.requeueTask(ctx, t) } }(work) case <-ctx.Done(): @@ -421,13 +430,28 @@ func (s *TaskServer) sendWorkAssignments(ctx context.Context) { } } +// requeueTask attempts to requeue a task back into the channel +func (s *TaskServer) requeueTask(ctx context.Context, t task.Task) { + select { + case s.channel <- t: + s.logger.Printf("Requeued task ID %d", t.ID) + case <-ctx.Done(): + s.logger.Printf("Context cancelled while requeueing task ID %d", t.ID) + default: + // If the channel is full, log the error and consider other options + s.logger.Printf("ERROR: Channel full, unable to requeue task ID %d", t.ID) + // Consider persisting this task or implementing a backup queue + } +} + // handleStreamRequest processes incoming stream requests -func (s *TaskServer) handleStreamRequest(ctx context.Context, req *v1.StreamRequest) error { +func (s *TaskServer) handleStreamRequest(ctx context.Context, req *v1.StreamRequest, clientID string) error { switch r := req.Request.(type) { case *v1.StreamRequest_UpdateTaskStatus: return s.handleUpdateTaskStatus(ctx, r.UpdateTaskStatus) case *v1.StreamRequest_Heartbeat: - fmt.Println("Heartbeat received") + s.handleHeartbeat(clientID) + s.logger.Printf("Heartbeat received from client: %s", clientID) default: s.logger.Printf("Received unknown request type: %T", r) return fmt.Errorf("unknown request type: %T", r) @@ -488,7 +512,7 @@ func (s *TaskServer) prepareNewTask(req *v1.CreateTaskRequest) task.Task { newTask := task.Task{ Name: req.Name, - Status: int(v1.TaskStatusEnum_QUEUED), + Status: int(v1.TaskStatusEnum_UNKNOWN), Description: req.Description, Type: req.Type, Payload: payloadJSON, @@ -556,3 +580,36 @@ func (s *TaskServer) convertTaskHistoryToProto(history []task.TaskHistory) []*v1 } return protoHistory } + +// handleHeartbeat updates the last heartbeat time for a client +func (s *TaskServer) handleHeartbeat(clientID string) { + s.clientHeartbeats.Store(clientID, time.Now()) +} + +// checkStaleClients periodically checks for clients that haven't sent a heartbeat +func (s *TaskServer) checkStaleClients(ctx context.Context) { + ticker := time.NewTicker(s.heartbeatTimeout / 2) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + now := time.Now() + s.clientHeartbeats.Range(func(key, value interface{}) bool { + clientID := key.(string) + lastHeartbeat := value.(time.Time) + if now.Sub(lastHeartbeat) > s.heartbeatTimeout { + s.logger.Printf("Client %s is stale, removing", clientID) + s.clientHeartbeats.Delete(clientID) + } + return true + }) + } + } +} + +func generateClientID() string { + return fmt.Sprintf("client-%s", uuid.New().String()) +} From c160a4f95533190f975df492c6f92d74dd7e03d3 Mon Sep 17 00:00:00 2001 From: Yuvraj Date: Wed, 16 Oct 2024 01:22:38 +0530 Subject: [PATCH 08/17] fix stream events --- cli/cmd/agent.go | 193 ++---- cli/cmd/end2end.go | 34 +- go.mod | 11 +- go.sum | 34 +- idl/cloud/v1/cloud.proto | 53 +- pkg/gen/cloud/v1/cloud.pb.go | 598 ++++++++---------- pkg/gen/cloud/v1/cloud.swagger.json | 46 +- pkg/gen/cloud/v1/cloud_grpc.pb.go | 77 ++- .../cloud/v1/cloudv1connect/cloud.connect.go | 63 +- pkg/gen/index.html | 117 ++-- server/repository/gormimpl/task.go | 33 +- server/root/main.go | 8 +- server/route/task.go | 233 ++----- 13 files changed, 655 insertions(+), 845 deletions(-) diff --git a/cli/cmd/agent.go b/cli/cmd/agent.go index b5d04f6..1932401 100644 --- a/cli/cmd/agent.go +++ b/cli/cmd/agent.go @@ -3,18 +3,17 @@ package cmd import ( "context" "fmt" - "io" "log/slog" - "math" + "net/http" "sync" v1 "task/pkg/gen/cloud/v1" + "task/pkg/gen/cloud/v1/cloudv1connect" "task/pkg/plugins" "task/pkg/x" "time" "connectrpc.com/connect" "github.com/spf13/cobra" - "google.golang.org/grpc" ) var serveCmd = &cobra.Command{ @@ -28,6 +27,9 @@ var serveCmd = &cobra.Command{ }, } +// Number of worker goroutines +var numWorkers = 1000 + func init() { rootCmd.AddCommand(serveCmd) } @@ -62,177 +64,104 @@ func runWorkflowOrchestration(ctx context.Context) error { } func runStreamConnection(ctx context.Context, wg *sync.WaitGroup, logger *slog.Logger) error { - conn, err := grpc.Dial("localhost:8080", []grpc.DialOption{ - grpc.WithInsecure(), - grpc.WithDefaultCallOptions( - grpc.MaxCallRecvMsgSize(math.MaxInt64), - grpc.MaxCallSendMsgSize(math.MaxInt64), - ), - grpc.WithInitialWindowSize(math.MaxInt32), - grpc.WithInitialConnWindowSize(math.MaxInt32), - }...) - if err != nil { - return fmt.Errorf("failed to connect to gRPC server: %w", err) - } - defer conn.Close() - client := v1.NewTaskManagementServiceClient(conn) + var err error + + client := cloudv1connect.NewTaskManagementServiceClient(http.DefaultClient, "http://localhost:8080") - // Create a buffered channel for work assignments - workChan := make(chan *v1.StreamResponse_WorkAssignment, 100) + go sendPeriodicRequests(ctx, logger, client) // Pass stream as a pointer - // Start the stream - stream, err := client.StreamConnection(ctx) + stream, err := client.PullEvents(ctx, connect.NewRequest(&v1.PullEventsRequest{})) if err != nil { return fmt.Errorf("failed to start stream: %w", err) } - // Start goroutines - wg.Add(3) - go sendPeriodicRequests(ctx, stream, logger, wg) - go receiveResponses(ctx, stream, workChan, logger, wg) - go processWorkAssignments(ctx, stream, workChan, logger, wg) + for { + ok := stream.Receive() + if !ok { + return fmt.Errorf("failed to receive response: %w", err) + } - // Wait for all goroutines to finish - wg.Wait() + go processWork(ctx, stream.Msg(), logger) + } return nil } // sendPeriodicRequests sends periodic RunCommand requests to the server. -func sendPeriodicRequests(ctx context.Context, stream v1.TaskManagementService_StreamConnectionClient, logger *slog.Logger, wg *sync.WaitGroup) { - defer wg.Done() +func sendPeriodicRequests(ctx context.Context, logger *slog.Logger, client cloudv1connect.TaskManagementServiceClient) { + ticker := time.NewTicker(5 * time.Second) defer ticker.Stop() - for { select { case <-ctx.Done(): return case <-ticker.C: - if err := stream.Send(&v1.StreamRequest{Request: &v1.StreamRequest_Heartbeat{Heartbeat: &v1.Heartbeat{}}}); err != nil { - logger.Error("Error sending request", "error", err) - return - } - logger.Debug("Sent periodic RunCommand request") - } - } -} + _, err := client.Heartbeat(ctx, connect.NewRequest(&v1.HeartbeatRequest{ + Timestamp: time.Now().Format(time.RFC3339), + })) -func receiveResponses(ctx context.Context, stream v1.TaskManagementService_StreamConnectionClient, workChan chan<- *v1.StreamResponse_WorkAssignment, logger *slog.Logger, wg *sync.WaitGroup) { - defer wg.Done() - for { - select { - case <-ctx.Done(): - return - default: - response, err := stream.Recv() if err != nil { - if err == io.EOF || ctx.Err() != nil { - logger.Info("Stream closed") - return - } - logger.Error("Error receiving response", "error", err) - time.Sleep(time.Second) // Wait before retrying + logger.Error("Error sending request", "error", err) continue } - fmt.Println(response) - switch resp := response.Response.(type) { - case *v1.StreamResponse_WorkAssignment: - select { - case workChan <- resp: - logger.Debug("Work assignment queued", "task_id", resp.WorkAssignment.Task.Id) - default: - logger.Warn("Work channel full, discarding work assignment", "task_id", resp.WorkAssignment.Task.Id) - } - default: - logger.Warn("Received unknown response type", "type", fmt.Sprintf("%T", resp)) - } + logger.Debug("Sent periodic RunCommand request") } } } -func processWorkAssignments(ctx context.Context, stream v1.TaskManagementService_StreamConnectionClient, workChan <-chan *v1.StreamResponse_WorkAssignment, logger *slog.Logger, wg *sync.WaitGroup) { - defer wg.Done() - for { - select { - case <-ctx.Done(): - return - case work := <-workChan: - maxAttempts := 3 - initialBackoff := 1 * time.Second +func processWork(ctx context.Context, task *v1.PullEventsResponse, logger *slog.Logger) { + maxAttempts := 3 + initialBackoff := 1 * time.Second - var finalStatus v1.TaskStatusEnum - var finalMessage string + var finalStatus v1.TaskStatusEnum + var finalMessage string - for attempt := 1; attempt <= maxAttempts; attempt++ { - // Update status to Running for each attempt + for attempt := 1; attempt <= maxAttempts; attempt++ { + // Update status to Running for each attempt - runningMessage := fmt.Sprintf("Running attempt %d of %d", attempt, maxAttempts) - if err := sendStatusUpdateWithRetry(ctx, stream, work.WorkAssignment.Task.Id, v1.TaskStatusEnum_RUNNING, runningMessage); err != nil { - logger.Error("Failed to send running status update", "error", err, "task_id", work.WorkAssignment.Task.Id, "attempt", attempt) - } + runningMessage := fmt.Sprintf("Running attempt %d of %d", attempt, maxAttempts) + if err := updateTaskStatus(ctx, int64(task.Work.Task.Id), v1.TaskStatusEnum_RUNNING, runningMessage); err != nil { + logger.Error("Failed to send running status update", "error", err, "task_id", task.Work.Task.Id, "attempt", attempt) + } - _, message, err := processWorkflowUpdate(ctx, work, logger) - - if err != nil { - failedMessage := fmt.Sprintf("Attempt %d failed: %v", attempt, err) - if err := sendStatusUpdateWithRetry(ctx, stream, work.WorkAssignment.Task.Id, v1.TaskStatusEnum_FAILED, failedMessage); err != nil { - logger.Error("Failed to send failed status update", "error", err, "task_id", work.WorkAssignment.Task.Id, "attempt", attempt) - } - - if attempt == maxAttempts { - finalStatus = v1.TaskStatusEnum_FAILED - finalMessage = fmt.Sprintf("All %d attempts failed. Last error: %v", maxAttempts, err) - } else { - // Wait before the next attempt - select { - case <-ctx.Done(): - return - case <-time.After(initialBackoff * time.Duration(1< 1000 { - return fmt.Errorf("number of tasks (%d) exceeds the maximum limit of 1000", numTasks) - } fmt.Println("Monitoring task completion...") startTime := time.Now() diff --git a/go.mod b/go.mod index 0720c65..3aca57d 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,8 @@ require ( github.com/kelseyhightower/envconfig v1.4.0 github.com/olekukonko/tablewriter v0.0.5 github.com/prometheus/client_golang v1.20.4 + github.com/riverqueue/river v0.13.0 + github.com/riverqueue/river/rivertype v0.13.0 github.com/rs/cors v1.11.1 github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.9.0 @@ -59,19 +61,26 @@ require ( github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect + github.com/riverqueue/river/riverdriver v0.13.0 // indirect + github.com/riverqueue/river/rivershared v0.13.0 // indirect github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect github.com/stretchr/objx v0.5.2 // indirect + github.com/tidwall/gjson v1.18.0 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect go.opentelemetry.io/otel v1.21.0 // indirect go.opentelemetry.io/otel/metric v1.21.0 // indirect go.opentelemetry.io/otel/sdk v1.21.0 // indirect go.opentelemetry.io/otel/trace v1.21.0 // indirect + go.uber.org/goleak v1.3.0 // indirect golang.org/x/crypto v0.27.0 // indirect golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // 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/text v0.19.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 09870ed..d9331d6 100644 --- a/go.sum +++ b/go.sum @@ -49,6 +49,8 @@ github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzq github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa h1:s+4MhCQ6YrzisK6hFJUX53drDT4UsSW3DEhKn0ifuHw= +github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= @@ -73,6 +75,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= @@ -89,6 +93,20 @@ github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/riverqueue/river v0.13.0 h1:BvEJfXAnHJ7HwraoPZWiD271t2jDVvX1SPCtvLzojiA= +github.com/riverqueue/river v0.13.0/go.mod h1:SOG+j28RQpKDsTA8AlfxjFdYpoPm+MSOio+Ev4ljN2U= +github.com/riverqueue/river/riverdriver v0.13.0 h1:UVzMtNfp3R+Ehr/yaRqgF58YOFEWGVqIAamCeK7RMkA= +github.com/riverqueue/river/riverdriver v0.13.0/go.mod h1:pxmx6qmGl+dNCrfa+xuktg8zrrZO3AEqlUFlFWOy8U4= +github.com/riverqueue/river/riverdriver/riverdatabasesql v0.13.0 h1:xiiwQVFUoPv/7PQIsEIerpw2ux1lZ14oZScgiB4JHdE= +github.com/riverqueue/river/riverdriver/riverdatabasesql v0.13.0/go.mod h1:f7TWWD965tE6v96qi1Y40IP2shsAai0qJBHbqT7yFLM= +github.com/riverqueue/river/riverdriver/riverpgxv5 v0.13.0 h1:wjLgea/eI5rIMh0+TCjS+/+dsULIst3Wu8bZQo2DHno= +github.com/riverqueue/river/riverdriver/riverpgxv5 v0.13.0/go.mod h1:Vzt3E33kNks2vN9lTgLJL8VFrbcAWDbwzyZLo02FlBk= +github.com/riverqueue/river/rivershared v0.13.0 h1:AqRP54GgtwoLIvV5eoZmOGOCZXL8Ce5Zm8s60R8NKOA= +github.com/riverqueue/river/rivershared v0.13.0/go.mod h1:vzvawQpDy2Z1U5chkvh1NykzWNkRhc9RLcURsJRhlbE= +github.com/riverqueue/river/rivertype v0.13.0 h1:PkT3h9tP0ZV3h0EGy2MiwEhgZqpRMN4fXfj27UKc9Q0= +github.com/riverqueue/river/rivertype v0.13.0/go.mod h1:wVOhGBeay6+JcIi0pTFlF4KtUgHYFkhMYv8dpxU46W0= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= @@ -112,6 +130,16 @@ 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/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= go.akshayshah.org/attest v1.0.2 h1:qOv9PXCG2mwnph3g0I3yZj0rLAwLyUITs8nhxP+wS44= go.akshayshah.org/attest v1.0.2/go.mod h1:PnWzcW5j9dkyGwTlBmUsYpPnHG0AUPrs1RQ+HrldWO0= go.akshayshah.org/connectauth v0.6.0 h1:lyzJ3z33L2KKgfSZW95tilktE7bCgc/OTQm0V8C2//g= @@ -128,6 +156,8 @@ go.opentelemetry.io/otel/sdk/metric v1.19.0 h1:EJoTO5qysMsYCa+w4UghwFV/ptQgqSL/8 go.opentelemetry.io/otel/sdk/metric v1.19.0/go.mod h1:XjG0jQyFJrv2PbMvwND7LwCEhsJzCzV5210euduKcKY= go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc= go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 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/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw= @@ -140,8 +170,8 @@ 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.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/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 h1:wKguEg1hsxI2/L3hUYrpo1RVi48K+uTyzKqprwLXsb8= google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo= google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs= diff --git a/idl/cloud/v1/cloud.proto b/idl/cloud/v1/cloud.proto index 3af4e20..43bf488 100644 --- a/idl/cloud/v1/cloud.proto +++ b/idl/cloud/v1/cloud.proto @@ -12,7 +12,7 @@ enum TaskStatusEnum { FAILED = 2; // Task encountered an error and failed to complete SUCCEEDED = 3; // Task completed successfully UNKNOWN = 4; // Task status cannot be determined - ALL = 5; // Task status cannot be determined + ALL = 5; // Represents all task statuses } // Message for Task Payload @@ -180,24 +180,40 @@ service TaskManagementService { // Returns a GetStatusResponse containing a map of status counts. rpc GetStatus(GetStatusRequest) returns (GetStatusResponse) {} - rpc StreamConnection(stream StreamRequest) returns (stream StreamResponse) {} + rpc Heartbeat(HeartbeatRequest) returns (HeartbeatResponse) {} + + rpc PullEvents(PullEventsRequest) returns (stream PullEventsResponse) {} +} + +message HeartbeatRequest { + // Timestamp of the heartbeat, in ISO 8601 format (UTC). + // This timestamp indicates when the heartbeat was sent. + string timestamp = 1 [(validate.rules).string = { + pattern: "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$" + }]; + + // Unique identifier for the heartbeat request. This UUID helps in tracking and correlating requests. + // It should be a valid UUID format. + string uuid = 2 [(validate.rules).string = { + pattern: "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$" + }]; +} + +message HeartbeatResponse { + // Response message for the heartbeat request. + // Currently, this message is empty, indicating successful receipt of the heartbeat. } // Message for stream requests -message StreamRequest { - oneof request { - Heartbeat heartbeat = 1; - WorkAssignment work_assignment = 2; - UpdateTaskStatusRequest update_task_status = 3; - } +message PullEventsRequest { + // Request message for pulling events. + // Currently, this message is empty, indicating that no specific parameters are required. } // Message for stream responses -message StreamResponse { - oneof response { - WorkAssignment work_assignment = 2; - UpdateTaskStatusRequest update_task_status = 3; - } +message PullEventsResponse { + // Work assignment to be executed. + WorkAssignment work = 1; // The task to be executed. } // Message for work assignments @@ -209,13 +225,6 @@ message WorkAssignment { Task task = 2 [(validate.rules).message.required = true]; } -// Heartbeat message for keeping the connection alive -message Heartbeat { - // Timestamp of the heartbeat, in ISO 8601 format (UTC) - string timestamp = 1 [(validate.rules).string = { - pattern: "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$" - }]; -} @@ -224,7 +233,7 @@ message GetStatusRequest {} // Message for GetStatus response message GetStatusResponse { - // Map of task statuses and their counts + // Map of task statuses and their counts. map status_counts = 1; } @@ -254,4 +263,4 @@ message TaskListRequest { optional string type = 4 [(validate.rules).string = { in: ["send_email", "run_query"] }]; -} \ No newline at end of file +} diff --git a/pkg/gen/cloud/v1/cloud.pb.go b/pkg/gen/cloud/v1/cloud.pb.go index 9aa56b5..fe53e14 100644 --- a/pkg/gen/cloud/v1/cloud.pb.go +++ b/pkg/gen/cloud/v1/cloud.pb.go @@ -31,7 +31,7 @@ const ( TaskStatusEnum_FAILED TaskStatusEnum = 2 // Task encountered an error and failed to complete TaskStatusEnum_SUCCEEDED TaskStatusEnum = 3 // Task completed successfully TaskStatusEnum_UNKNOWN TaskStatusEnum = 4 // Task status cannot be determined - TaskStatusEnum_ALL TaskStatusEnum = 5 // Task status cannot be determined + TaskStatusEnum_ALL TaskStatusEnum = 5 // Represents all task statuses ) // Enum value maps for TaskStatusEnum. @@ -661,34 +661,33 @@ func (x *UpdateTaskStatusRequest) GetMessage() string { return "" } -// Message for stream requests -type StreamRequest struct { +type HeartbeatRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // Types that are assignable to Request: - // - // *StreamRequest_Heartbeat - // *StreamRequest_WorkAssignment - // *StreamRequest_UpdateTaskStatus - Request isStreamRequest_Request `protobuf_oneof:"request"` + // Timestamp of the heartbeat, in ISO 8601 format (UTC). + // This timestamp indicates when the heartbeat was sent. + Timestamp string `protobuf:"bytes,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + // Unique identifier for the heartbeat request. This UUID helps in tracking and correlating requests. + // It should be a valid UUID format. + Uuid string `protobuf:"bytes,2,opt,name=uuid,proto3" json:"uuid,omitempty"` } -func (x *StreamRequest) Reset() { - *x = StreamRequest{} +func (x *HeartbeatRequest) Reset() { + *x = HeartbeatRequest{} mi := &file_cloud_v1_cloud_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *StreamRequest) String() string { +func (x *HeartbeatRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*StreamRequest) ProtoMessage() {} +func (*HeartbeatRequest) ProtoMessage() {} -func (x *StreamRequest) ProtoReflect() protoreflect.Message { +func (x *HeartbeatRequest) ProtoReflect() protoreflect.Message { mi := &file_cloud_v1_cloud_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -700,90 +699,83 @@ func (x *StreamRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use StreamRequest.ProtoReflect.Descriptor instead. -func (*StreamRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use HeartbeatRequest.ProtoReflect.Descriptor instead. +func (*HeartbeatRequest) Descriptor() ([]byte, []int) { return file_cloud_v1_cloud_proto_rawDescGZIP(), []int{9} } -func (m *StreamRequest) GetRequest() isStreamRequest_Request { - if m != nil { - return m.Request +func (x *HeartbeatRequest) GetTimestamp() string { + if x != nil { + return x.Timestamp } - return nil + return "" } -func (x *StreamRequest) GetHeartbeat() *Heartbeat { - if x, ok := x.GetRequest().(*StreamRequest_Heartbeat); ok { - return x.Heartbeat +func (x *HeartbeatRequest) GetUuid() string { + if x != nil { + return x.Uuid } - return nil + return "" } -func (x *StreamRequest) GetWorkAssignment() *WorkAssignment { - if x, ok := x.GetRequest().(*StreamRequest_WorkAssignment); ok { - return x.WorkAssignment - } - return nil +type HeartbeatResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } -func (x *StreamRequest) GetUpdateTaskStatus() *UpdateTaskStatusRequest { - if x, ok := x.GetRequest().(*StreamRequest_UpdateTaskStatus); ok { - return x.UpdateTaskStatus - } - return nil +func (x *HeartbeatResponse) Reset() { + *x = HeartbeatResponse{} + mi := &file_cloud_v1_cloud_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } -type isStreamRequest_Request interface { - isStreamRequest_Request() +func (x *HeartbeatResponse) String() string { + return protoimpl.X.MessageStringOf(x) } -type StreamRequest_Heartbeat struct { - Heartbeat *Heartbeat `protobuf:"bytes,1,opt,name=heartbeat,proto3,oneof"` -} +func (*HeartbeatResponse) ProtoMessage() {} -type StreamRequest_WorkAssignment struct { - WorkAssignment *WorkAssignment `protobuf:"bytes,2,opt,name=work_assignment,json=workAssignment,proto3,oneof"` +func (x *HeartbeatResponse) ProtoReflect() protoreflect.Message { + mi := &file_cloud_v1_cloud_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -type StreamRequest_UpdateTaskStatus struct { - UpdateTaskStatus *UpdateTaskStatusRequest `protobuf:"bytes,3,opt,name=update_task_status,json=updateTaskStatus,proto3,oneof"` +// Deprecated: Use HeartbeatResponse.ProtoReflect.Descriptor instead. +func (*HeartbeatResponse) Descriptor() ([]byte, []int) { + return file_cloud_v1_cloud_proto_rawDescGZIP(), []int{10} } -func (*StreamRequest_Heartbeat) isStreamRequest_Request() {} - -func (*StreamRequest_WorkAssignment) isStreamRequest_Request() {} - -func (*StreamRequest_UpdateTaskStatus) isStreamRequest_Request() {} - -// Message for stream responses -type StreamResponse struct { +// Message for stream requests +type PullEventsRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - - // Types that are assignable to Response: - // - // *StreamResponse_Heartbeat - // *StreamResponse_WorkAssignment - // *StreamResponse_UpdateTaskStatus - Response isStreamResponse_Response `protobuf_oneof:"response"` } -func (x *StreamResponse) Reset() { - *x = StreamResponse{} - mi := &file_cloud_v1_cloud_proto_msgTypes[10] +func (x *PullEventsRequest) Reset() { + *x = PullEventsRequest{} + mi := &file_cloud_v1_cloud_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *StreamResponse) String() string { +func (x *PullEventsRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*StreamResponse) ProtoMessage() {} +func (*PullEventsRequest) ProtoMessage() {} -func (x *StreamResponse) ProtoReflect() protoreflect.Message { - mi := &file_cloud_v1_cloud_proto_msgTypes[10] +func (x *PullEventsRequest) ProtoReflect() protoreflect.Message { + mi := &file_cloud_v1_cloud_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -794,61 +786,58 @@ func (x *StreamResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use StreamResponse.ProtoReflect.Descriptor instead. -func (*StreamResponse) Descriptor() ([]byte, []int) { - return file_cloud_v1_cloud_proto_rawDescGZIP(), []int{10} +// Deprecated: Use PullEventsRequest.ProtoReflect.Descriptor instead. +func (*PullEventsRequest) Descriptor() ([]byte, []int) { + return file_cloud_v1_cloud_proto_rawDescGZIP(), []int{11} } -func (m *StreamResponse) GetResponse() isStreamResponse_Response { - if m != nil { - return m.Response - } - return nil -} +// Message for stream responses +type PullEventsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields -func (x *StreamResponse) GetHeartbeat() *Heartbeat { - if x, ok := x.GetResponse().(*StreamResponse_Heartbeat); ok { - return x.Heartbeat - } - return nil + // Work assignment to be executed. + Work *WorkAssignment `protobuf:"bytes,1,opt,name=work,proto3" json:"work,omitempty"` // The task to be executed. } -func (x *StreamResponse) GetWorkAssignment() *WorkAssignment { - if x, ok := x.GetResponse().(*StreamResponse_WorkAssignment); ok { - return x.WorkAssignment - } - return nil +func (x *PullEventsResponse) Reset() { + *x = PullEventsResponse{} + mi := &file_cloud_v1_cloud_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } -func (x *StreamResponse) GetUpdateTaskStatus() *UpdateTaskStatusRequest { - if x, ok := x.GetResponse().(*StreamResponse_UpdateTaskStatus); ok { - return x.UpdateTaskStatus - } - return nil +func (x *PullEventsResponse) String() string { + return protoimpl.X.MessageStringOf(x) } -type isStreamResponse_Response interface { - isStreamResponse_Response() -} +func (*PullEventsResponse) ProtoMessage() {} -type StreamResponse_Heartbeat struct { - Heartbeat *Heartbeat `protobuf:"bytes,1,opt,name=heartbeat,proto3,oneof"` +func (x *PullEventsResponse) ProtoReflect() protoreflect.Message { + mi := &file_cloud_v1_cloud_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -type StreamResponse_WorkAssignment struct { - WorkAssignment *WorkAssignment `protobuf:"bytes,2,opt,name=work_assignment,json=workAssignment,proto3,oneof"` +// Deprecated: Use PullEventsResponse.ProtoReflect.Descriptor instead. +func (*PullEventsResponse) Descriptor() ([]byte, []int) { + return file_cloud_v1_cloud_proto_rawDescGZIP(), []int{12} } -type StreamResponse_UpdateTaskStatus struct { - UpdateTaskStatus *UpdateTaskStatusRequest `protobuf:"bytes,3,opt,name=update_task_status,json=updateTaskStatus,proto3,oneof"` +func (x *PullEventsResponse) GetWork() *WorkAssignment { + if x != nil { + return x.Work + } + return nil } -func (*StreamResponse_Heartbeat) isStreamResponse_Response() {} - -func (*StreamResponse_WorkAssignment) isStreamResponse_Response() {} - -func (*StreamResponse_UpdateTaskStatus) isStreamResponse_Response() {} - // Message for work assignments type WorkAssignment struct { state protoimpl.MessageState @@ -863,7 +852,7 @@ type WorkAssignment struct { func (x *WorkAssignment) Reset() { *x = WorkAssignment{} - mi := &file_cloud_v1_cloud_proto_msgTypes[11] + mi := &file_cloud_v1_cloud_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -875,7 +864,7 @@ func (x *WorkAssignment) String() string { func (*WorkAssignment) ProtoMessage() {} func (x *WorkAssignment) ProtoReflect() protoreflect.Message { - mi := &file_cloud_v1_cloud_proto_msgTypes[11] + mi := &file_cloud_v1_cloud_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -888,7 +877,7 @@ func (x *WorkAssignment) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkAssignment.ProtoReflect.Descriptor instead. func (*WorkAssignment) Descriptor() ([]byte, []int) { - return file_cloud_v1_cloud_proto_rawDescGZIP(), []int{11} + return file_cloud_v1_cloud_proto_rawDescGZIP(), []int{13} } func (x *WorkAssignment) GetAssignmentId() int64 { @@ -905,53 +894,6 @@ func (x *WorkAssignment) GetTask() *Task { return nil } -// Heartbeat message for keeping the connection alive -type Heartbeat struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Timestamp of the heartbeat, in ISO 8601 format (UTC) - Timestamp string `protobuf:"bytes,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"` -} - -func (x *Heartbeat) Reset() { - *x = Heartbeat{} - mi := &file_cloud_v1_cloud_proto_msgTypes[12] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *Heartbeat) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Heartbeat) ProtoMessage() {} - -func (x *Heartbeat) ProtoReflect() protoreflect.Message { - mi := &file_cloud_v1_cloud_proto_msgTypes[12] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Heartbeat.ProtoReflect.Descriptor instead. -func (*Heartbeat) Descriptor() ([]byte, []int) { - return file_cloud_v1_cloud_proto_rawDescGZIP(), []int{12} -} - -func (x *Heartbeat) GetTimestamp() string { - if x != nil { - return x.Timestamp - } - return "" -} - // Message for GetStatus request (empty) type GetStatusRequest struct { state protoimpl.MessageState @@ -961,7 +903,7 @@ type GetStatusRequest struct { func (x *GetStatusRequest) Reset() { *x = GetStatusRequest{} - mi := &file_cloud_v1_cloud_proto_msgTypes[13] + mi := &file_cloud_v1_cloud_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -973,7 +915,7 @@ func (x *GetStatusRequest) String() string { func (*GetStatusRequest) ProtoMessage() {} func (x *GetStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_cloud_v1_cloud_proto_msgTypes[13] + mi := &file_cloud_v1_cloud_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -986,7 +928,7 @@ func (x *GetStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetStatusRequest.ProtoReflect.Descriptor instead. func (*GetStatusRequest) Descriptor() ([]byte, []int) { - return file_cloud_v1_cloud_proto_rawDescGZIP(), []int{13} + return file_cloud_v1_cloud_proto_rawDescGZIP(), []int{14} } // Message for GetStatus response @@ -995,13 +937,13 @@ type GetStatusResponse struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // Map of task statuses and their counts + // Map of task statuses and their counts. StatusCounts map[int32]int64 `protobuf:"bytes,1,rep,name=status_counts,json=statusCounts,proto3" json:"status_counts,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"` } func (x *GetStatusResponse) Reset() { *x = GetStatusResponse{} - mi := &file_cloud_v1_cloud_proto_msgTypes[14] + mi := &file_cloud_v1_cloud_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1013,7 +955,7 @@ func (x *GetStatusResponse) String() string { func (*GetStatusResponse) ProtoMessage() {} func (x *GetStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_cloud_v1_cloud_proto_msgTypes[14] + mi := &file_cloud_v1_cloud_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1026,7 +968,7 @@ func (x *GetStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetStatusResponse.ProtoReflect.Descriptor instead. func (*GetStatusResponse) Descriptor() ([]byte, []int) { - return file_cloud_v1_cloud_proto_rawDescGZIP(), []int{14} + return file_cloud_v1_cloud_proto_rawDescGZIP(), []int{15} } func (x *GetStatusResponse) GetStatusCounts() map[int32]int64 { @@ -1047,7 +989,7 @@ type TaskList struct { func (x *TaskList) Reset() { *x = TaskList{} - mi := &file_cloud_v1_cloud_proto_msgTypes[15] + mi := &file_cloud_v1_cloud_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1059,7 +1001,7 @@ func (x *TaskList) String() string { func (*TaskList) ProtoMessage() {} func (x *TaskList) ProtoReflect() protoreflect.Message { - mi := &file_cloud_v1_cloud_proto_msgTypes[15] + mi := &file_cloud_v1_cloud_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1072,7 +1014,7 @@ func (x *TaskList) ProtoReflect() protoreflect.Message { // Deprecated: Use TaskList.ProtoReflect.Descriptor instead. func (*TaskList) Descriptor() ([]byte, []int) { - return file_cloud_v1_cloud_proto_rawDescGZIP(), []int{15} + return file_cloud_v1_cloud_proto_rawDescGZIP(), []int{16} } func (x *TaskList) GetTasks() []*Task { @@ -1103,7 +1045,7 @@ type TaskListRequest struct { func (x *TaskListRequest) Reset() { *x = TaskListRequest{} - mi := &file_cloud_v1_cloud_proto_msgTypes[16] + mi := &file_cloud_v1_cloud_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1115,7 +1057,7 @@ func (x *TaskListRequest) String() string { func (*TaskListRequest) ProtoMessage() {} func (x *TaskListRequest) ProtoReflect() protoreflect.Message { - mi := &file_cloud_v1_cloud_proto_msgTypes[16] + mi := &file_cloud_v1_cloud_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1128,7 +1070,7 @@ func (x *TaskListRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use TaskListRequest.ProtoReflect.Descriptor instead. func (*TaskListRequest) Descriptor() ([]byte, []int) { - return file_cloud_v1_cloud_proto_rawDescGZIP(), []int{16} + return file_cloud_v1_cloud_proto_rawDescGZIP(), []int{17} } func (x *TaskListRequest) GetLimit() int32 { @@ -1254,124 +1196,114 @@ var file_cloud_v1_cloud_proto_rawDesc = []byte{ 0x6d, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x82, 0x01, 0x02, 0x10, 0x01, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x22, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x72, 0x03, 0x18, 0xd0, 0x0f, 0x52, 0x07, - 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0xe7, 0x01, 0x0a, 0x0d, 0x53, 0x74, 0x72, 0x65, - 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x33, 0x0a, 0x09, 0x68, 0x65, 0x61, - 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x63, - 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, - 0x74, 0x48, 0x00, 0x52, 0x09, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x12, 0x43, - 0x0a, 0x0f, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, - 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, - 0x76, 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, - 0x74, 0x48, 0x00, 0x52, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, - 0x65, 0x6e, 0x74, 0x12, 0x51, 0x0a, 0x12, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x61, - 0x73, 0x6b, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x21, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x48, 0x00, 0x52, 0x10, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x09, 0x0a, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x22, 0xe9, 0x01, 0x0a, 0x0e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x09, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, - 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, - 0x76, 0x31, 0x2e, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x48, 0x00, 0x52, 0x09, - 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x12, 0x43, 0x0a, 0x0f, 0x77, 0x6f, 0x72, - 0x6b, 0x5f, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x6f, - 0x72, 0x6b, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0e, - 0x77, 0x6f, 0x72, 0x6b, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x51, - 0x0a, 0x12, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x73, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x63, 0x6c, 0x6f, - 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, - 0x10, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x42, 0x0a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x63, 0x0a, - 0x0e, 0x57, 0x6f, 0x72, 0x6b, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x12, - 0x23, 0x0a, 0x0d, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, - 0x6e, 0x74, 0x49, 0x64, 0x12, 0x2c, 0x0a, 0x04, 0x74, 0x61, 0x73, 0x6b, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, - 0x73, 0x6b, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x04, 0x74, 0x61, - 0x73, 0x6b, 0x22, 0x58, 0x0a, 0x09, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x12, - 0x4b, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x42, 0x2d, 0xfa, 0x42, 0x2a, 0x72, 0x28, 0x32, 0x26, 0x5e, 0x5c, 0x64, 0x7b, 0x34, - 0x7d, 0x2d, 0x5c, 0x64, 0x7b, 0x32, 0x7d, 0x2d, 0x5c, 0x64, 0x7b, 0x32, 0x7d, 0x54, 0x5c, 0x64, - 0x7b, 0x32, 0x7d, 0x3a, 0x5c, 0x64, 0x7b, 0x32, 0x7d, 0x3a, 0x5c, 0x64, 0x7b, 0x32, 0x7d, 0x5a, - 0x24, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0x12, 0x0a, 0x10, - 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x22, 0xa8, 0x01, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0d, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, - 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0c, 0x73, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x1a, 0x3f, 0x0a, 0x11, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, - 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x6b, 0x65, - 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x30, 0x0a, 0x08, 0x54, - 0x61, 0x73, 0x6b, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x05, 0x74, 0x61, 0x73, 0x6b, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, - 0x31, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x05, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x22, 0xd5, 0x01, - 0x0a, 0x0f, 0x54, 0x61, 0x73, 0x6b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x1f, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, - 0x42, 0x09, 0xfa, 0x42, 0x06, 0x1a, 0x04, 0x18, 0x64, 0x28, 0x01, 0x52, 0x05, 0x6c, 0x69, 0x6d, - 0x69, 0x74, 0x12, 0x1f, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x05, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x1a, 0x02, 0x28, 0x00, 0x52, 0x06, 0x6f, 0x66, 0x66, - 0x73, 0x65, 0x74, 0x12, 0x35, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x54, - 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x45, 0x6e, 0x75, 0x6d, 0x48, 0x00, 0x52, - 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x88, 0x01, 0x01, 0x12, 0x35, 0x0a, 0x04, 0x74, 0x79, - 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1c, 0xfa, 0x42, 0x19, 0x72, 0x17, 0x52, - 0x0a, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x52, 0x09, 0x72, 0x75, 0x6e, - 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x48, 0x01, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x88, 0x01, - 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x07, 0x0a, 0x05, - 0x5f, 0x74, 0x79, 0x70, 0x65, 0x2a, 0x5a, 0x0a, 0x0e, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x45, 0x6e, 0x75, 0x6d, 0x12, 0x0a, 0x0a, 0x06, 0x51, 0x55, 0x45, 0x55, 0x45, - 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x52, 0x55, 0x4e, 0x4e, 0x49, 0x4e, 0x47, 0x10, 0x01, - 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, - 0x53, 0x55, 0x43, 0x43, 0x45, 0x45, 0x44, 0x45, 0x44, 0x10, 0x03, 0x12, 0x0b, 0x0a, 0x07, 0x55, - 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x04, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x4c, 0x4c, 0x10, - 0x05, 0x32, 0x94, 0x04, 0x0a, 0x15, 0x54, 0x61, 0x73, 0x6b, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, - 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x49, 0x0a, 0x0a, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x1b, 0x2e, 0x63, 0x6c, 0x6f, 0x75, - 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, - 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x35, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x54, 0x61, 0x73, - 0x6b, 0x12, 0x18, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, - 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x63, 0x6c, - 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x22, 0x00, 0x12, 0x3c, 0x0a, - 0x09, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x12, 0x19, 0x2e, 0x63, 0x6c, 0x6f, - 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, - 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x4c, 0x69, 0x73, 0x74, 0x22, 0x00, 0x12, 0x55, 0x0a, 0x0e, 0x47, - 0x65, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x1f, 0x2e, - 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x61, 0x73, 0x6b, - 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, - 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x61, 0x73, - 0x6b, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x00, 0x12, 0x4f, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x21, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, - 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x12, 0x1a, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x63, - 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x10, 0x53, - 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, - 0x17, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, - 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, - 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x42, 0x7a, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x2e, - 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x42, 0x0a, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x50, - 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x1d, 0x74, 0x61, 0x73, 0x6b, 0x2f, 0x70, 0x6b, 0x67, - 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x76, 0x31, 0x3b, 0x63, 0x6c, - 0x6f, 0x75, 0x64, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x43, 0x58, 0x58, 0xaa, 0x02, 0x08, 0x43, 0x6c, - 0x6f, 0x75, 0x64, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x08, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x5c, 0x56, - 0x31, 0xe2, 0x02, 0x14, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, - 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x09, 0x43, 0x6c, 0x6f, 0x75, 0x64, - 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0xd6, 0x01, 0x0a, 0x10, 0x48, 0x65, 0x61, 0x72, + 0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4b, 0x0a, 0x09, + 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, + 0x2d, 0xfa, 0x42, 0x2a, 0x72, 0x28, 0x32, 0x26, 0x5e, 0x5c, 0x64, 0x7b, 0x34, 0x7d, 0x2d, 0x5c, + 0x64, 0x7b, 0x32, 0x7d, 0x2d, 0x5c, 0x64, 0x7b, 0x32, 0x7d, 0x54, 0x5c, 0x64, 0x7b, 0x32, 0x7d, + 0x3a, 0x5c, 0x64, 0x7b, 0x32, 0x7d, 0x3a, 0x5c, 0x64, 0x7b, 0x32, 0x7d, 0x5a, 0x24, 0x52, 0x09, + 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x75, 0x0a, 0x04, 0x75, 0x75, 0x69, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x61, 0xfa, 0x42, 0x5e, 0x72, 0x5c, 0x32, 0x5a, + 0x5e, 0x5b, 0x30, 0x2d, 0x39, 0x61, 0x2d, 0x66, 0x41, 0x2d, 0x46, 0x5d, 0x7b, 0x38, 0x7d, 0x2d, + 0x5b, 0x30, 0x2d, 0x39, 0x61, 0x2d, 0x66, 0x41, 0x2d, 0x46, 0x5d, 0x7b, 0x34, 0x7d, 0x2d, 0x5b, + 0x31, 0x2d, 0x35, 0x5d, 0x5b, 0x30, 0x2d, 0x39, 0x61, 0x2d, 0x66, 0x41, 0x2d, 0x46, 0x5d, 0x7b, + 0x33, 0x7d, 0x2d, 0x5b, 0x38, 0x39, 0x61, 0x62, 0x41, 0x42, 0x5d, 0x5b, 0x30, 0x2d, 0x39, 0x61, + 0x2d, 0x66, 0x41, 0x2d, 0x46, 0x5d, 0x7b, 0x33, 0x7d, 0x2d, 0x5b, 0x30, 0x2d, 0x39, 0x61, 0x2d, + 0x66, 0x41, 0x2d, 0x46, 0x5d, 0x7b, 0x31, 0x32, 0x7d, 0x24, 0x52, 0x04, 0x75, 0x75, 0x69, 0x64, + 0x22, 0x13, 0x0a, 0x11, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x13, 0x0a, 0x11, 0x50, 0x75, 0x6c, 0x6c, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x42, 0x0a, 0x12, 0x50, 0x75, + 0x6c, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x2c, 0x0a, 0x04, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, + 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x41, 0x73, + 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x04, 0x77, 0x6f, 0x72, 0x6b, 0x22, 0x63, + 0x0a, 0x0e, 0x57, 0x6f, 0x72, 0x6b, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, + 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, + 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x2c, 0x0a, 0x04, 0x74, 0x61, 0x73, 0x6b, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x54, + 0x61, 0x73, 0x6b, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x04, 0x74, + 0x61, 0x73, 0x6b, 0x22, 0x12, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xa8, 0x01, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, + 0x0d, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, + 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x52, 0x0c, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, + 0x73, 0x1a, 0x3f, 0x0a, 0x11, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, + 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, + 0x38, 0x01, 0x22, 0x30, 0x0a, 0x08, 0x54, 0x61, 0x73, 0x6b, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x24, + 0x0a, 0x05, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, + 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x05, 0x74, + 0x61, 0x73, 0x6b, 0x73, 0x22, 0xd5, 0x01, 0x0a, 0x0f, 0x54, 0x61, 0x73, 0x6b, 0x4c, 0x69, 0x73, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, + 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x42, 0x09, 0xfa, 0x42, 0x06, 0x1a, 0x04, 0x18, 0x64, + 0x28, 0x01, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x1f, 0x0a, 0x06, 0x6f, 0x66, 0x66, + 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x1a, 0x02, + 0x28, 0x00, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x35, 0x0a, 0x06, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x63, 0x6c, 0x6f, + 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x45, 0x6e, 0x75, 0x6d, 0x48, 0x00, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x88, 0x01, + 0x01, 0x12, 0x35, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, + 0x1c, 0xfa, 0x42, 0x19, 0x72, 0x17, 0x52, 0x0a, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x65, 0x6d, 0x61, + 0x69, 0x6c, 0x52, 0x09, 0x72, 0x75, 0x6e, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x48, 0x01, 0x52, + 0x04, 0x74, 0x79, 0x70, 0x65, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x2a, 0x5a, 0x0a, 0x0e, + 0x54, 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x45, 0x6e, 0x75, 0x6d, 0x12, 0x0a, + 0x0a, 0x06, 0x51, 0x55, 0x45, 0x55, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x52, 0x55, + 0x4e, 0x4e, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45, + 0x44, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x55, 0x43, 0x43, 0x45, 0x45, 0x44, 0x45, 0x44, + 0x10, 0x03, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x04, 0x12, + 0x07, 0x0a, 0x03, 0x41, 0x4c, 0x4c, 0x10, 0x05, 0x32, 0xdc, 0x04, 0x0a, 0x15, 0x54, 0x61, 0x73, + 0x6b, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x12, 0x49, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, + 0x12, 0x1b, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, + 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, + 0x61, 0x73, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x35, 0x0a, + 0x07, 0x47, 0x65, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x18, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, + 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, + 0x73, 0x6b, 0x22, 0x00, 0x12, 0x3c, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x61, 0x73, 0x6b, + 0x73, 0x12, 0x19, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x73, + 0x6b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x63, + 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x4c, 0x69, 0x73, 0x74, + 0x22, 0x00, 0x12, 0x55, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x48, 0x69, 0x73, + 0x74, 0x6f, 0x72, 0x79, 0x12, 0x1f, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, + 0x47, 0x65, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, + 0x2e, 0x47, 0x65, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4f, 0x0a, 0x10, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x21, 0x2e, + 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, + 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x09, 0x47, 0x65, + 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1a, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, + 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x47, + 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x00, 0x12, 0x46, 0x0a, 0x09, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x12, + 0x1a, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x65, 0x61, 0x72, 0x74, + 0x62, 0x65, 0x61, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x63, 0x6c, + 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0a, 0x50, 0x75, + 0x6c, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1b, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, + 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x6c, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, + 0x2e, 0x50, 0x75, 0x6c, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x42, 0x7a, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x2e, 0x63, + 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x31, 0x42, 0x0a, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x50, 0x72, + 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x1d, 0x74, 0x61, 0x73, 0x6b, 0x2f, 0x70, 0x6b, 0x67, 0x2f, + 0x67, 0x65, 0x6e, 0x2f, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x76, 0x31, 0x3b, 0x63, 0x6c, 0x6f, + 0x75, 0x64, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x43, 0x58, 0x58, 0xaa, 0x02, 0x08, 0x43, 0x6c, 0x6f, + 0x75, 0x64, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x08, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x5c, 0x56, 0x31, + 0xe2, 0x02, 0x14, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x09, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x3a, + 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1387,7 +1319,7 @@ func file_cloud_v1_cloud_proto_rawDescGZIP() []byte { } var file_cloud_v1_cloud_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_cloud_v1_cloud_proto_msgTypes = make([]protoimpl.MessageInfo, 19) +var file_cloud_v1_cloud_proto_msgTypes = make([]protoimpl.MessageInfo, 20) var file_cloud_v1_cloud_proto_goTypes = []any{ (TaskStatusEnum)(0), // 0: cloud.v1.TaskStatusEnum (*Payload)(nil), // 1: cloud.v1.Payload @@ -1399,55 +1331,53 @@ var file_cloud_v1_cloud_proto_goTypes = []any{ (*GetTaskHistoryRequest)(nil), // 7: cloud.v1.GetTaskHistoryRequest (*GetTaskHistoryResponse)(nil), // 8: cloud.v1.GetTaskHistoryResponse (*UpdateTaskStatusRequest)(nil), // 9: cloud.v1.UpdateTaskStatusRequest - (*StreamRequest)(nil), // 10: cloud.v1.StreamRequest - (*StreamResponse)(nil), // 11: cloud.v1.StreamResponse - (*WorkAssignment)(nil), // 12: cloud.v1.WorkAssignment - (*Heartbeat)(nil), // 13: cloud.v1.Heartbeat - (*GetStatusRequest)(nil), // 14: cloud.v1.GetStatusRequest - (*GetStatusResponse)(nil), // 15: cloud.v1.GetStatusResponse - (*TaskList)(nil), // 16: cloud.v1.TaskList - (*TaskListRequest)(nil), // 17: cloud.v1.TaskListRequest - nil, // 18: cloud.v1.Payload.ParametersEntry - nil, // 19: cloud.v1.GetStatusResponse.StatusCountsEntry - (*emptypb.Empty)(nil), // 20: google.protobuf.Empty + (*HeartbeatRequest)(nil), // 10: cloud.v1.HeartbeatRequest + (*HeartbeatResponse)(nil), // 11: cloud.v1.HeartbeatResponse + (*PullEventsRequest)(nil), // 12: cloud.v1.PullEventsRequest + (*PullEventsResponse)(nil), // 13: cloud.v1.PullEventsResponse + (*WorkAssignment)(nil), // 14: cloud.v1.WorkAssignment + (*GetStatusRequest)(nil), // 15: cloud.v1.GetStatusRequest + (*GetStatusResponse)(nil), // 16: cloud.v1.GetStatusResponse + (*TaskList)(nil), // 17: cloud.v1.TaskList + (*TaskListRequest)(nil), // 18: cloud.v1.TaskListRequest + nil, // 19: cloud.v1.Payload.ParametersEntry + nil, // 20: cloud.v1.GetStatusResponse.StatusCountsEntry + (*emptypb.Empty)(nil), // 21: google.protobuf.Empty } var file_cloud_v1_cloud_proto_depIdxs = []int32{ - 18, // 0: cloud.v1.Payload.parameters:type_name -> cloud.v1.Payload.ParametersEntry + 19, // 0: cloud.v1.Payload.parameters:type_name -> cloud.v1.Payload.ParametersEntry 1, // 1: cloud.v1.CreateTaskRequest.payload:type_name -> cloud.v1.Payload 0, // 2: cloud.v1.Task.status:type_name -> cloud.v1.TaskStatusEnum 1, // 3: cloud.v1.Task.payload:type_name -> cloud.v1.Payload 0, // 4: cloud.v1.TaskHistory.status:type_name -> cloud.v1.TaskStatusEnum 5, // 5: cloud.v1.GetTaskHistoryResponse.history:type_name -> cloud.v1.TaskHistory 0, // 6: cloud.v1.UpdateTaskStatusRequest.status:type_name -> cloud.v1.TaskStatusEnum - 13, // 7: cloud.v1.StreamRequest.heartbeat:type_name -> cloud.v1.Heartbeat - 12, // 8: cloud.v1.StreamRequest.work_assignment:type_name -> cloud.v1.WorkAssignment - 9, // 9: cloud.v1.StreamRequest.update_task_status:type_name -> cloud.v1.UpdateTaskStatusRequest - 13, // 10: cloud.v1.StreamResponse.heartbeat:type_name -> cloud.v1.Heartbeat - 12, // 11: cloud.v1.StreamResponse.work_assignment:type_name -> cloud.v1.WorkAssignment - 9, // 12: cloud.v1.StreamResponse.update_task_status:type_name -> cloud.v1.UpdateTaskStatusRequest - 4, // 13: cloud.v1.WorkAssignment.task:type_name -> cloud.v1.Task - 19, // 14: cloud.v1.GetStatusResponse.status_counts:type_name -> cloud.v1.GetStatusResponse.StatusCountsEntry - 4, // 15: cloud.v1.TaskList.tasks:type_name -> cloud.v1.Task - 0, // 16: cloud.v1.TaskListRequest.status:type_name -> cloud.v1.TaskStatusEnum - 2, // 17: cloud.v1.TaskManagementService.CreateTask:input_type -> cloud.v1.CreateTaskRequest - 6, // 18: cloud.v1.TaskManagementService.GetTask:input_type -> cloud.v1.GetTaskRequest - 17, // 19: cloud.v1.TaskManagementService.ListTasks:input_type -> cloud.v1.TaskListRequest - 7, // 20: cloud.v1.TaskManagementService.GetTaskHistory:input_type -> cloud.v1.GetTaskHistoryRequest - 9, // 21: cloud.v1.TaskManagementService.UpdateTaskStatus:input_type -> cloud.v1.UpdateTaskStatusRequest - 14, // 22: cloud.v1.TaskManagementService.GetStatus:input_type -> cloud.v1.GetStatusRequest - 10, // 23: cloud.v1.TaskManagementService.StreamConnection:input_type -> cloud.v1.StreamRequest - 3, // 24: cloud.v1.TaskManagementService.CreateTask:output_type -> cloud.v1.CreateTaskResponse - 4, // 25: cloud.v1.TaskManagementService.GetTask:output_type -> cloud.v1.Task - 16, // 26: cloud.v1.TaskManagementService.ListTasks:output_type -> cloud.v1.TaskList - 8, // 27: cloud.v1.TaskManagementService.GetTaskHistory:output_type -> cloud.v1.GetTaskHistoryResponse - 20, // 28: cloud.v1.TaskManagementService.UpdateTaskStatus:output_type -> google.protobuf.Empty - 15, // 29: cloud.v1.TaskManagementService.GetStatus:output_type -> cloud.v1.GetStatusResponse - 11, // 30: cloud.v1.TaskManagementService.StreamConnection:output_type -> cloud.v1.StreamResponse - 24, // [24:31] is the sub-list for method output_type - 17, // [17:24] is the sub-list for method input_type - 17, // [17:17] is the sub-list for extension type_name - 17, // [17:17] is the sub-list for extension extendee - 0, // [0:17] is the sub-list for field type_name + 14, // 7: cloud.v1.PullEventsResponse.work:type_name -> cloud.v1.WorkAssignment + 4, // 8: cloud.v1.WorkAssignment.task:type_name -> cloud.v1.Task + 20, // 9: cloud.v1.GetStatusResponse.status_counts:type_name -> cloud.v1.GetStatusResponse.StatusCountsEntry + 4, // 10: cloud.v1.TaskList.tasks:type_name -> cloud.v1.Task + 0, // 11: cloud.v1.TaskListRequest.status:type_name -> cloud.v1.TaskStatusEnum + 2, // 12: cloud.v1.TaskManagementService.CreateTask:input_type -> cloud.v1.CreateTaskRequest + 6, // 13: cloud.v1.TaskManagementService.GetTask:input_type -> cloud.v1.GetTaskRequest + 18, // 14: cloud.v1.TaskManagementService.ListTasks:input_type -> cloud.v1.TaskListRequest + 7, // 15: cloud.v1.TaskManagementService.GetTaskHistory:input_type -> cloud.v1.GetTaskHistoryRequest + 9, // 16: cloud.v1.TaskManagementService.UpdateTaskStatus:input_type -> cloud.v1.UpdateTaskStatusRequest + 15, // 17: cloud.v1.TaskManagementService.GetStatus:input_type -> cloud.v1.GetStatusRequest + 10, // 18: cloud.v1.TaskManagementService.Heartbeat:input_type -> cloud.v1.HeartbeatRequest + 12, // 19: cloud.v1.TaskManagementService.PullEvents:input_type -> cloud.v1.PullEventsRequest + 3, // 20: cloud.v1.TaskManagementService.CreateTask:output_type -> cloud.v1.CreateTaskResponse + 4, // 21: cloud.v1.TaskManagementService.GetTask:output_type -> cloud.v1.Task + 17, // 22: cloud.v1.TaskManagementService.ListTasks:output_type -> cloud.v1.TaskList + 8, // 23: cloud.v1.TaskManagementService.GetTaskHistory:output_type -> cloud.v1.GetTaskHistoryResponse + 21, // 24: cloud.v1.TaskManagementService.UpdateTaskStatus:output_type -> google.protobuf.Empty + 16, // 25: cloud.v1.TaskManagementService.GetStatus:output_type -> cloud.v1.GetStatusResponse + 11, // 26: cloud.v1.TaskManagementService.Heartbeat:output_type -> cloud.v1.HeartbeatResponse + 13, // 27: cloud.v1.TaskManagementService.PullEvents:output_type -> cloud.v1.PullEventsResponse + 20, // [20:28] is the sub-list for method output_type + 12, // [12:20] is the sub-list for method input_type + 12, // [12:12] is the sub-list for extension type_name + 12, // [12:12] is the sub-list for extension extendee + 0, // [0:12] is the sub-list for field type_name } func init() { file_cloud_v1_cloud_proto_init() } @@ -1455,24 +1385,14 @@ func file_cloud_v1_cloud_proto_init() { if File_cloud_v1_cloud_proto != nil { return } - file_cloud_v1_cloud_proto_msgTypes[9].OneofWrappers = []any{ - (*StreamRequest_Heartbeat)(nil), - (*StreamRequest_WorkAssignment)(nil), - (*StreamRequest_UpdateTaskStatus)(nil), - } - file_cloud_v1_cloud_proto_msgTypes[10].OneofWrappers = []any{ - (*StreamResponse_Heartbeat)(nil), - (*StreamResponse_WorkAssignment)(nil), - (*StreamResponse_UpdateTaskStatus)(nil), - } - file_cloud_v1_cloud_proto_msgTypes[16].OneofWrappers = []any{} + file_cloud_v1_cloud_proto_msgTypes[17].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_cloud_v1_cloud_proto_rawDesc, NumEnums: 1, - NumMessages: 19, + NumMessages: 20, NumExtensions: 0, NumServices: 1, }, diff --git a/pkg/gen/cloud/v1/cloud.swagger.json b/pkg/gen/cloud/v1/cloud.swagger.json index 2d8449c..e32c9a6 100644 --- a/pkg/gen/cloud/v1/cloud.swagger.json +++ b/pkg/gen/cloud/v1/cloud.swagger.json @@ -65,7 +65,7 @@ "type": "string", "format": "int64" }, - "title": "Map of task statuses and their counts" + "description": "Map of task statuses and their counts." } }, "title": "Message for GetStatus response" @@ -84,15 +84,9 @@ }, "title": "Message for Task history response" }, - "v1Heartbeat": { + "v1HeartbeatResponse": { "type": "object", - "properties": { - "timestamp": { - "type": "string", - "title": "Timestamp of the heartbeat, in ISO 8601 format (UTC)" - } - }, - "title": "Heartbeat message for keeping the connection alive" + "description": "Response message for the heartbeat request.\n Currently, this message is empty, indicating successful receipt of the heartbeat." }, "v1Payload": { "type": "object", @@ -107,17 +101,12 @@ }, "title": "Message for Task Payload" }, - "v1StreamResponse": { + "v1PullEventsResponse": { "type": "object", "properties": { - "heartbeat": { - "$ref": "#/definitions/v1Heartbeat" - }, - "workAssignment": { - "$ref": "#/definitions/v1WorkAssignment" - }, - "updateTaskStatus": { - "$ref": "#/definitions/v1UpdateTaskStatusRequest" + "work": { + "$ref": "#/definitions/v1WorkAssignment", + "description": "Work assignment to be executed.\n\nThe task to be executed." } }, "title": "Message for stream responses" @@ -215,28 +204,9 @@ "ALL" ], "default": "QUEUED", - "description": "- QUEUED: Task is in the queue, waiting to be processed\n - RUNNING: Task is currently being executed\n - FAILED: Task encountered an error and failed to complete\n - SUCCEEDED: Task completed successfully\n - UNKNOWN: Task status cannot be determined\n - ALL: Task status cannot be determined", + "description": "- QUEUED: Task is in the queue, waiting to be processed\n - RUNNING: Task is currently being executed\n - FAILED: Task encountered an error and failed to complete\n - SUCCEEDED: Task completed successfully\n - UNKNOWN: Task status cannot be determined\n - ALL: Represents all task statuses", "title": "Enum for Task statuses" }, - "v1UpdateTaskStatusRequest": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int32", - "description": "Unique identifier for the task. Must be \u003e= 0." - }, - "status": { - "$ref": "#/definitions/v1TaskStatusEnum", - "description": "New status for the task, must be one of the defined statuses." - }, - "message": { - "type": "string", - "description": "Additional message about the status update. Maximum length of 2000 characters." - } - }, - "title": "Message for Task status update request" - }, "v1WorkAssignment": { "type": "object", "properties": { diff --git a/pkg/gen/cloud/v1/cloud_grpc.pb.go b/pkg/gen/cloud/v1/cloud_grpc.pb.go index d8c0419..784a833 100644 --- a/pkg/gen/cloud/v1/cloud_grpc.pb.go +++ b/pkg/gen/cloud/v1/cloud_grpc.pb.go @@ -26,7 +26,8 @@ const ( TaskManagementService_GetTaskHistory_FullMethodName = "/cloud.v1.TaskManagementService/GetTaskHistory" TaskManagementService_UpdateTaskStatus_FullMethodName = "/cloud.v1.TaskManagementService/UpdateTaskStatus" TaskManagementService_GetStatus_FullMethodName = "/cloud.v1.TaskManagementService/GetStatus" - TaskManagementService_StreamConnection_FullMethodName = "/cloud.v1.TaskManagementService/StreamConnection" + TaskManagementService_Heartbeat_FullMethodName = "/cloud.v1.TaskManagementService/Heartbeat" + TaskManagementService_PullEvents_FullMethodName = "/cloud.v1.TaskManagementService/PullEvents" ) // TaskManagementServiceClient is the client API for TaskManagementService service. @@ -53,7 +54,8 @@ type TaskManagementServiceClient interface { // Retrieves the count of tasks for each status. // Returns a GetStatusResponse containing a map of status counts. GetStatus(ctx context.Context, in *GetStatusRequest, opts ...grpc.CallOption) (*GetStatusResponse, error) - StreamConnection(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[StreamRequest, StreamResponse], error) + Heartbeat(ctx context.Context, in *HeartbeatRequest, opts ...grpc.CallOption) (*HeartbeatResponse, error) + PullEvents(ctx context.Context, in *PullEventsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[PullEventsResponse], error) } type taskManagementServiceClient struct { @@ -124,18 +126,34 @@ func (c *taskManagementServiceClient) GetStatus(ctx context.Context, in *GetStat return out, nil } -func (c *taskManagementServiceClient) StreamConnection(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[StreamRequest, StreamResponse], error) { +func (c *taskManagementServiceClient) Heartbeat(ctx context.Context, in *HeartbeatRequest, opts ...grpc.CallOption) (*HeartbeatResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - stream, err := c.cc.NewStream(ctx, &TaskManagementService_ServiceDesc.Streams[0], TaskManagementService_StreamConnection_FullMethodName, cOpts...) + out := new(HeartbeatResponse) + err := c.cc.Invoke(ctx, TaskManagementService_Heartbeat_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } - x := &grpc.GenericClientStream[StreamRequest, StreamResponse]{ClientStream: stream} + return out, nil +} + +func (c *taskManagementServiceClient) PullEvents(ctx context.Context, in *PullEventsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[PullEventsResponse], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &TaskManagementService_ServiceDesc.Streams[0], TaskManagementService_PullEvents_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[PullEventsRequest, PullEventsResponse]{ClientStream: stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } return x, nil } // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. -type TaskManagementService_StreamConnectionClient = grpc.BidiStreamingClient[StreamRequest, StreamResponse] +type TaskManagementService_PullEventsClient = grpc.ServerStreamingClient[PullEventsResponse] // TaskManagementServiceServer is the server API for TaskManagementService service. // All implementations must embed UnimplementedTaskManagementServiceServer @@ -161,7 +179,8 @@ type TaskManagementServiceServer interface { // Retrieves the count of tasks for each status. // Returns a GetStatusResponse containing a map of status counts. GetStatus(context.Context, *GetStatusRequest) (*GetStatusResponse, error) - StreamConnection(grpc.BidiStreamingServer[StreamRequest, StreamResponse]) error + Heartbeat(context.Context, *HeartbeatRequest) (*HeartbeatResponse, error) + PullEvents(*PullEventsRequest, grpc.ServerStreamingServer[PullEventsResponse]) error mustEmbedUnimplementedTaskManagementServiceServer() } @@ -190,8 +209,11 @@ func (UnimplementedTaskManagementServiceServer) UpdateTaskStatus(context.Context func (UnimplementedTaskManagementServiceServer) GetStatus(context.Context, *GetStatusRequest) (*GetStatusResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetStatus not implemented") } -func (UnimplementedTaskManagementServiceServer) StreamConnection(grpc.BidiStreamingServer[StreamRequest, StreamResponse]) error { - return status.Errorf(codes.Unimplemented, "method StreamConnection not implemented") +func (UnimplementedTaskManagementServiceServer) Heartbeat(context.Context, *HeartbeatRequest) (*HeartbeatResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Heartbeat not implemented") +} +func (UnimplementedTaskManagementServiceServer) PullEvents(*PullEventsRequest, grpc.ServerStreamingServer[PullEventsResponse]) error { + return status.Errorf(codes.Unimplemented, "method PullEvents not implemented") } func (UnimplementedTaskManagementServiceServer) mustEmbedUnimplementedTaskManagementServiceServer() {} func (UnimplementedTaskManagementServiceServer) testEmbeddedByValue() {} @@ -322,12 +344,34 @@ func _TaskManagementService_GetStatus_Handler(srv interface{}, ctx context.Conte return interceptor(ctx, in, info, handler) } -func _TaskManagementService_StreamConnection_Handler(srv interface{}, stream grpc.ServerStream) error { - return srv.(TaskManagementServiceServer).StreamConnection(&grpc.GenericServerStream[StreamRequest, StreamResponse]{ServerStream: stream}) +func _TaskManagementService_Heartbeat_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(HeartbeatRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TaskManagementServiceServer).Heartbeat(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: TaskManagementService_Heartbeat_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TaskManagementServiceServer).Heartbeat(ctx, req.(*HeartbeatRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _TaskManagementService_PullEvents_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(PullEventsRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(TaskManagementServiceServer).PullEvents(m, &grpc.GenericServerStream[PullEventsRequest, PullEventsResponse]{ServerStream: stream}) } // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. -type TaskManagementService_StreamConnectionServer = grpc.BidiStreamingServer[StreamRequest, StreamResponse] +type TaskManagementService_PullEventsServer = grpc.ServerStreamingServer[PullEventsResponse] // TaskManagementService_ServiceDesc is the grpc.ServiceDesc for TaskManagementService service. // It's only intended for direct use with grpc.RegisterService, @@ -360,13 +404,16 @@ var TaskManagementService_ServiceDesc = grpc.ServiceDesc{ MethodName: "GetStatus", Handler: _TaskManagementService_GetStatus_Handler, }, + { + MethodName: "Heartbeat", + Handler: _TaskManagementService_Heartbeat_Handler, + }, }, Streams: []grpc.StreamDesc{ { - StreamName: "StreamConnection", - Handler: _TaskManagementService_StreamConnection_Handler, + StreamName: "PullEvents", + Handler: _TaskManagementService_PullEvents_Handler, ServerStreams: true, - ClientStreams: true, }, }, Metadata: "cloud/v1/cloud.proto", diff --git a/pkg/gen/cloud/v1/cloudv1connect/cloud.connect.go b/pkg/gen/cloud/v1/cloudv1connect/cloud.connect.go index 8b0b7f2..6d97232 100644 --- a/pkg/gen/cloud/v1/cloudv1connect/cloud.connect.go +++ b/pkg/gen/cloud/v1/cloudv1connect/cloud.connect.go @@ -52,9 +52,12 @@ const ( // TaskManagementServiceGetStatusProcedure is the fully-qualified name of the // TaskManagementService's GetStatus RPC. TaskManagementServiceGetStatusProcedure = "/cloud.v1.TaskManagementService/GetStatus" - // TaskManagementServiceStreamConnectionProcedure is the fully-qualified name of the - // TaskManagementService's StreamConnection RPC. - TaskManagementServiceStreamConnectionProcedure = "/cloud.v1.TaskManagementService/StreamConnection" + // TaskManagementServiceHeartbeatProcedure is the fully-qualified name of the + // TaskManagementService's Heartbeat RPC. + TaskManagementServiceHeartbeatProcedure = "/cloud.v1.TaskManagementService/Heartbeat" + // TaskManagementServicePullEventsProcedure is the fully-qualified name of the + // TaskManagementService's PullEvents RPC. + TaskManagementServicePullEventsProcedure = "/cloud.v1.TaskManagementService/PullEvents" ) // TaskManagementServiceClient is a client for the cloud.v1.TaskManagementService service. @@ -77,7 +80,8 @@ type TaskManagementServiceClient interface { // Retrieves the count of tasks for each status. // Returns a GetStatusResponse containing a map of status counts. GetStatus(context.Context, *connect.Request[v1.GetStatusRequest]) (*connect.Response[v1.GetStatusResponse], error) - StreamConnection(context.Context) *connect.BidiStreamForClient[v1.StreamRequest, v1.StreamResponse] + Heartbeat(context.Context, *connect.Request[v1.HeartbeatRequest]) (*connect.Response[v1.HeartbeatResponse], error) + PullEvents(context.Context, *connect.Request[v1.PullEventsRequest]) (*connect.ServerStreamForClient[v1.PullEventsResponse], error) } // NewTaskManagementServiceClient constructs a client for the cloud.v1.TaskManagementService @@ -120,9 +124,14 @@ func NewTaskManagementServiceClient(httpClient connect.HTTPClient, baseURL strin baseURL+TaskManagementServiceGetStatusProcedure, opts..., ), - streamConnection: connect.NewClient[v1.StreamRequest, v1.StreamResponse]( + heartbeat: connect.NewClient[v1.HeartbeatRequest, v1.HeartbeatResponse]( httpClient, - baseURL+TaskManagementServiceStreamConnectionProcedure, + baseURL+TaskManagementServiceHeartbeatProcedure, + opts..., + ), + pullEvents: connect.NewClient[v1.PullEventsRequest, v1.PullEventsResponse]( + httpClient, + baseURL+TaskManagementServicePullEventsProcedure, opts..., ), } @@ -136,7 +145,8 @@ type taskManagementServiceClient struct { getTaskHistory *connect.Client[v1.GetTaskHistoryRequest, v1.GetTaskHistoryResponse] updateTaskStatus *connect.Client[v1.UpdateTaskStatusRequest, emptypb.Empty] getStatus *connect.Client[v1.GetStatusRequest, v1.GetStatusResponse] - streamConnection *connect.Client[v1.StreamRequest, v1.StreamResponse] + heartbeat *connect.Client[v1.HeartbeatRequest, v1.HeartbeatResponse] + pullEvents *connect.Client[v1.PullEventsRequest, v1.PullEventsResponse] } // CreateTask calls cloud.v1.TaskManagementService.CreateTask. @@ -169,9 +179,14 @@ func (c *taskManagementServiceClient) GetStatus(ctx context.Context, req *connec return c.getStatus.CallUnary(ctx, req) } -// StreamConnection calls cloud.v1.TaskManagementService.StreamConnection. -func (c *taskManagementServiceClient) StreamConnection(ctx context.Context) *connect.BidiStreamForClient[v1.StreamRequest, v1.StreamResponse] { - return c.streamConnection.CallBidiStream(ctx) +// Heartbeat calls cloud.v1.TaskManagementService.Heartbeat. +func (c *taskManagementServiceClient) Heartbeat(ctx context.Context, req *connect.Request[v1.HeartbeatRequest]) (*connect.Response[v1.HeartbeatResponse], error) { + return c.heartbeat.CallUnary(ctx, req) +} + +// PullEvents calls cloud.v1.TaskManagementService.PullEvents. +func (c *taskManagementServiceClient) PullEvents(ctx context.Context, req *connect.Request[v1.PullEventsRequest]) (*connect.ServerStreamForClient[v1.PullEventsResponse], error) { + return c.pullEvents.CallServerStream(ctx, req) } // TaskManagementServiceHandler is an implementation of the cloud.v1.TaskManagementService service. @@ -194,7 +209,8 @@ type TaskManagementServiceHandler interface { // Retrieves the count of tasks for each status. // Returns a GetStatusResponse containing a map of status counts. GetStatus(context.Context, *connect.Request[v1.GetStatusRequest]) (*connect.Response[v1.GetStatusResponse], error) - StreamConnection(context.Context, *connect.BidiStream[v1.StreamRequest, v1.StreamResponse]) error + Heartbeat(context.Context, *connect.Request[v1.HeartbeatRequest]) (*connect.Response[v1.HeartbeatResponse], error) + PullEvents(context.Context, *connect.Request[v1.PullEventsRequest], *connect.ServerStream[v1.PullEventsResponse]) error } // NewTaskManagementServiceHandler builds an HTTP handler from the service implementation. It @@ -233,9 +249,14 @@ func NewTaskManagementServiceHandler(svc TaskManagementServiceHandler, opts ...c svc.GetStatus, opts..., ) - taskManagementServiceStreamConnectionHandler := connect.NewBidiStreamHandler( - TaskManagementServiceStreamConnectionProcedure, - svc.StreamConnection, + taskManagementServiceHeartbeatHandler := connect.NewUnaryHandler( + TaskManagementServiceHeartbeatProcedure, + svc.Heartbeat, + opts..., + ) + taskManagementServicePullEventsHandler := connect.NewServerStreamHandler( + TaskManagementServicePullEventsProcedure, + svc.PullEvents, opts..., ) return "/cloud.v1.TaskManagementService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -252,8 +273,10 @@ func NewTaskManagementServiceHandler(svc TaskManagementServiceHandler, opts ...c taskManagementServiceUpdateTaskStatusHandler.ServeHTTP(w, r) case TaskManagementServiceGetStatusProcedure: taskManagementServiceGetStatusHandler.ServeHTTP(w, r) - case TaskManagementServiceStreamConnectionProcedure: - taskManagementServiceStreamConnectionHandler.ServeHTTP(w, r) + case TaskManagementServiceHeartbeatProcedure: + taskManagementServiceHeartbeatHandler.ServeHTTP(w, r) + case TaskManagementServicePullEventsProcedure: + taskManagementServicePullEventsHandler.ServeHTTP(w, r) default: http.NotFound(w, r) } @@ -287,6 +310,10 @@ func (UnimplementedTaskManagementServiceHandler) GetStatus(context.Context, *con return nil, connect.NewError(connect.CodeUnimplemented, errors.New("cloud.v1.TaskManagementService.GetStatus is not implemented")) } -func (UnimplementedTaskManagementServiceHandler) StreamConnection(context.Context, *connect.BidiStream[v1.StreamRequest, v1.StreamResponse]) error { - return connect.NewError(connect.CodeUnimplemented, errors.New("cloud.v1.TaskManagementService.StreamConnection is not implemented")) +func (UnimplementedTaskManagementServiceHandler) Heartbeat(context.Context, *connect.Request[v1.HeartbeatRequest]) (*connect.Response[v1.HeartbeatResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("cloud.v1.TaskManagementService.Heartbeat is not implemented")) +} + +func (UnimplementedTaskManagementServiceHandler) PullEvents(context.Context, *connect.Request[v1.PullEventsRequest], *connect.ServerStream[v1.PullEventsResponse]) error { + return connect.NewError(connect.CodeUnimplemented, errors.New("cloud.v1.TaskManagementService.PullEvents is not implemented")) } diff --git a/pkg/gen/index.html b/pkg/gen/index.html index 1986640..d933e10 100644 --- a/pkg/gen/index.html +++ b/pkg/gen/index.html @@ -334,7 +334,11 @@

      Table of Contents

    • - MHeartbeat + MHeartbeatRequest +
    • + +
    • + MHeartbeatResponse
    • @@ -346,11 +350,11 @@

      Table of Contents

    • - MStreamRequest + MPullEventsRequest
    • - MStreamResponse + MPullEventsResponse
    • @@ -2623,7 +2627,7 @@

      GetStatusResponse

      status_counts GetStatusResponse.StatusCountsEntry repeated -

      Map of task statuses and their counts

      +

      Map of task statuses and their counts.

      @@ -2820,8 +2824,8 @@

      Validated Fields

      -

      Heartbeat

      -

      Heartbeat message for keeping the connection alive

      +

      HeartbeatRequest

      +

      @@ -2834,7 +2838,16 @@

      Heartbeat

      - + + + + + + + + @@ -2864,6 +2877,17 @@

      Validated Fields

      + + + + +
      timestamp string

      Timestamp of the heartbeat, in ISO 8601 format (UTC)

      Timestamp of the heartbeat, in ISO 8601 format (UTC). +This timestamp indicates when the heartbeat was sent.

      uuidstring

      Unique identifier for the heartbeat request. This UUID helps in tracking and correlating requests. +It should be a valid UUID format.

      uuid +
        + +
      • string.pattern: ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
      • + +
      +
      @@ -2871,6 +2895,13 @@

      Validated Fields

      +

      HeartbeatResponse

      +

      Response message for the heartbeat request.

      Currently, this message is empty, indicating successful receipt of the heartbeat.

      + + + + +

      Payload

      Message for Task Payload

      @@ -2957,45 +2988,14 @@

      Payload.ParametersEntry

      -

      StreamRequest

      -

      Message for stream requests

      +

      PullEventsRequest

      +

      Message for stream requests

      Request message for pulling events.

      Currently, this message is empty, indicating that no specific parameters are required.

      - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      FieldTypeLabelDescription
      heartbeatHeartbeat

      work_assignmentWorkAssignment

      update_task_statusUpdateTaskStatusRequest

      - - -

      StreamResponse

      +

      PullEventsResponse

      Message for stream responses

      @@ -3006,24 +3006,12 @@

      StreamResponse

      - heartbeat - Heartbeat - -

      - - - - work_assignment + work WorkAssignment -

      - - - - update_task_status - UpdateTaskStatusRequest - -

      +

      Work assignment to be executed. + +The task to be executed.

      @@ -3653,7 +3641,7 @@

      TaskStatusEnum

      ALL 5 -

      Task status cannot be determined

      +

      Represents all task statuses

      @@ -3720,9 +3708,16 @@

      TaskManagementService

      - StreamConnection - StreamRequest stream - StreamResponse stream + Heartbeat + HeartbeatRequest + HeartbeatResponse +

      + + + + PullEvents + PullEventsRequest + PullEventsResponse stream

      diff --git a/server/repository/gormimpl/task.go b/server/repository/gormimpl/task.go index 3bb6d75..c4c729f 100644 --- a/server/repository/gormimpl/task.go +++ b/server/repository/gormimpl/task.go @@ -154,15 +154,36 @@ func (s *TaskRepo) GetStalledTasks(ctx context.Context) ([]models.Task, error) { defer timer.ObserveDuration() var tasks []models.Task - tenSecondsAgo := time.Now().Add(-30 * time.Second) - - err := s.db.Where("(status = ?) AND updated_at < ?", - 4, tenSecondsAgo). - Find(&tasks).Error + thirtySecondsAgo := time.Now().Add(-30 * time.Second) + + // Start a transaction + err := s.db.Transaction(func(tx *gorm.DB) error { + // Find stalled tasks and lock them for update + if err := tx.Set("gorm:query_option", "FOR UPDATE SKIP LOCKED"). + Where("(status = ?) AND updated_at < ?", 4, thirtySecondsAgo). + Find(&tasks).Error; err != nil { + return err + } + + // Update the status of found tasks to a temporary "processing" state + if len(tasks) > 0 { + taskIDs := make([]uint, len(tasks)) + for i, task := range tasks { + taskIDs[i] = task.ID + } + if err := tx.Model(&models.Task{}). + Where("id IN ?", taskIDs). + Update("status", 5).Error; err != nil { // Assuming 5 is a temporary "processing" status + return err + } + } + + return nil + }) if err != nil { taskOperations.WithLabelValues("get_stalled", "error").Inc() - return nil, fmt.Errorf("failed to retrieve stalled tasks: %w", err) + return nil, fmt.Errorf("failed to retrieve and lock stalled tasks: %w", err) } taskOperations.WithLabelValues("get_stalled", "success").Inc() diff --git a/server/root/main.go b/server/root/main.go index cf841b9..d3285d4 100644 --- a/server/root/main.go +++ b/server/root/main.go @@ -163,10 +163,10 @@ func run() error { newCORS().Handler(mux), &http2.Server{}, ), - ReadHeaderTimeout: time.Second, - ReadTimeout: 5 * time.Minute, - WriteTimeout: 5 * time.Minute, - MaxHeaderBytes: 8 * 1024, // 8KiB + MaxHeaderBytes: 1 << 20, // 1 MB + ReadHeaderTimeout: 60 * time.Minute, + ReadTimeout: 60 * time.Minute, + WriteTimeout: 60 * time.Minute, } // Start the server in a goroutine diff --git a/server/route/task.go b/server/route/task.go index 17b1367..561f020 100644 --- a/server/route/task.go +++ b/server/route/task.go @@ -2,9 +2,7 @@ package route import ( "context" - "errors" "fmt" - "io" "log" "os" v1 "task/pkg/gen/cloud/v1" @@ -18,12 +16,8 @@ import ( "sync" - "strings" - connect "connectrpc.com/connect" - "github.com/avast/retry-go/v4" protovalidate "github.com/bufbuild/protovalidate-go" - "github.com/google/uuid" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" @@ -45,8 +39,6 @@ type TaskServer struct { validator *protovalidate.Validator metrics *taskMetrics channel chan task.Task - stream *connect.BidiStream[v1.StreamRequest, v1.StreamResponse] - workerPool chan struct{} maxWorkers int clientHeartbeats sync.Map heartbeatTimeout time.Duration @@ -104,24 +96,17 @@ func NewTaskServer(repo interfaces.TaskManagmentInterface) cloudv1connect.TaskMa log.Fatalf("Failed to initialize validator: %v", err) } - maxWorkers := 10 // You can make this configurable + maxWorkers := 500 // You can make this configurable server := &TaskServer{ taskRepo: repo.TaskRepo(), historyRepo: repo.TaskHistoryRepo(), logger: log.New(os.Stdout, logPrefix, log.LstdFlags|log.Lshortfile), validator: validator, metrics: newTaskMetrics(), - channel: make(chan task.Task, 500), - workerPool: make(chan struct{}, maxWorkers), maxWorkers: maxWorkers, heartbeatTimeout: 30 * time.Second, // Configurable timeout } - // Initialize the worker pool - for i := 0; i < maxWorkers; i++ { - server.workerPool <- struct{}{} - } - server.logger.Println("TaskServer initialized successfully") return server } @@ -147,7 +132,6 @@ func (s *TaskServer) CreateTask(ctx context.Context, req *connect.Request[v1.Cre s.metrics.errorCounter.WithLabelValues("create_task").Inc() return nil, s.logError(err, "Failed to create task in repository") } - s.channel <- createdTask s.logger.Printf("Task created successfully: id=%d", createdTask.ID) return connect.NewResponse(&v1.CreateTaskResponse{Id: int32(createdTask.ID)}), nil @@ -218,7 +202,6 @@ func (s *TaskServer) UpdateTaskStatus(ctx context.Context, req *connect.Request[ if err := s.createTaskStatusHistory(ctx, uint(req.Msg.Id), int(req.Msg.Status), req.Msg.Message); err != nil { s.logger.Printf("WARNING: Failed to create task status history: %v", err) - // Consider whether to return an error here or continue } s.logger.Printf("Task status updated: id=%d", req.Msg.Id) @@ -295,167 +278,56 @@ func (s *TaskServer) GetStatus(ctx context.Context, req *connect.Request[v1.GetS } // StreamConnection handles bidirectional streaming for task updates and assignments. -func (s *TaskServer) StreamConnection(ctx context.Context, stream *connect.BidiStream[v1.StreamRequest, v1.StreamResponse]) error { - s.stream = stream - defer close(s.channel) - - clientID := generateClientID() // Implement this function to generate a unique client ID - s.clientHeartbeats.Store(clientID, time.Now()) - - // Start a goroutine to handle sending work assignments - go s.sendWorkAssignments(ctx, clientID) - - go s.streamStalledTasks(ctx) - - // Start a goroutine to check for stale clients - go s.checkStaleClients(ctx) - - for { - if err := ctx.Err(); err != nil { - s.logger.Printf("Context error in StreamConnection: %v", err) - s.clientHeartbeats.Delete(clientID) - return err - } - - req, err := stream.Receive() - if err != nil { - if errors.Is(err, io.EOF) { - s.logger.Println("Client closed the stream") - s.clientHeartbeats.Delete(clientID) - return nil - } - s.logger.Printf("Error receiving request: %v", err) - s.clientHeartbeats.Delete(clientID) - return fmt.Errorf("receive request: %w", err) - } - - if err := s.handleStreamRequest(ctx, req, clientID); err != nil { - s.logger.Printf("Error handling stream request: %v", err) - s.clientHeartbeats.Delete(clientID) - return err - } - } +func (s *TaskServer) Heartbeat(ctx context.Context, req *connect.Request[v1.HeartbeatRequest]) (*connect.Response[v1.HeartbeatResponse], error) { + s.clientHeartbeats.Store("clientID", req.Msg.Timestamp) + return connect.NewResponse(&v1.HeartbeatResponse{}), nil } -func (s *TaskServer) streamStalledTasks(ctx context.Context) error { - // Reconcile the tasks - tasks, err := s.taskRepo.GetStalledTasks(ctx) - if err != nil { - s.logger.Printf("Failed to retrieve stalled tasks: %v", err) - s.metrics.errorCounter.WithLabelValues("get_stalled_tasks").Inc() - return connect.NewError(connect.CodeInternal, fmt.Errorf("failed to retrieve stalled tasks: %w", err)) - } - // Use a separate goroutine to send tasks to the channel - go func() { - for _, task := range tasks { - select { - case s.channel <- task: - // Task sent successfully - case <-ctx.Done(): - s.logger.Println("Context cancelled while sending stalled tasks") - return - } - } - }() - return nil -} +// StreamConnection handles bidirectional streaming for task updates and assignments. +func (s *TaskServer) PullEvents(ctx context.Context, req *connect.Request[v1.PullEventsRequest], stream *connect.ServerStream[v1.PullEventsResponse]) error { + ticker := time.NewTicker(10 * time.Second) // Trigger every 10 seconds + defer ticker.Stop() -// sendWorkAssignments sends work assignments to multiple workers -func (s *TaskServer) sendWorkAssignments(ctx context.Context, clientID string) { - var wg sync.WaitGroup for { select { - case <-ctx.Done(): - wg.Wait() // Wait for all workers to finish before returning - return - case work := <-s.channel: - // Check if the client is still active before sending work - if _, ok := s.clientHeartbeats.Load(clientID); !ok { - s.logger.Printf("Client %s is no longer active, requeueing work", clientID) - s.requeueTask(ctx, work) - continue + case <-ticker.C: + tasks, err := s.taskRepo.GetStalledTasks(ctx) + if err != nil { + s.logger.Printf("Error checking stalled tasks: %v", err) + continue // Skip to the next tick on error } - select { - case <-s.workerPool: // Acquire a worker - wg.Add(1) - go func(t task.Task) { - defer wg.Done() - defer func() { s.workerPool <- struct{}{} }() // Release the worker - err := s.taskRepo.UpdateTaskStatus(ctx, t.ID, int(v1.TaskStatusEnum_QUEUED)) - if err != nil { - s.logger.Printf("Failed to update task status to QUEUED: %v", err) - } - - if err := s.createTaskStatusHistory(ctx, t.ID, int(v1.TaskStatusEnum_QUEUED), "Task is queued suucessfully"); err != nil { - s.logger.Printf("WARNING: Failed to create task status history: %v", err) - // Consider whether to return an error here or continue - } - response := &v1.StreamResponse{ - Response: &v1.StreamResponse_WorkAssignment{ - WorkAssignment: &v1.WorkAssignment{ - AssignmentId: int64(t.ID), - Task: s.convertTaskToProto(&t), - }, - }, - } - err = retry.Do( - func() error { - if err := s.stream.Send(response); err != nil { - - if errors.Is(err, io.EOF) || strings.Contains(err.Error(), "write envelope: short write") { - return retry.Unrecoverable(err) - } - return err - } - return nil - }, - retry.Attempts(3), - retry.Delay(100*time.Millisecond), - retry.DelayType(retry.BackOffDelay), - retry.OnRetry(func(n uint, err error) { - s.logger.Printf("Retry %d: Error sending work assignment: %v", n+1, err) - }), - ) - if err != nil { - s.logger.Printf("Failed to send work assignment after retries: %v", err) - s.requeueTask(ctx, t) - } - }(work) - case <-ctx.Done(): - wg.Wait() // Wait for all workers to finish before returning - return + for _, t := range tasks { + + if err := stream.Send(&v1.PullEventsResponse{ + Work: &v1.WorkAssignment{ + AssignmentId: int64(t.ID), + Task: s.convertTaskToProto(&t), + }, + }); err != nil { + s.logger.Printf("Error sending task to client: %v", err) + return err + } + if err := s.updateTaskStatus(ctx, uint(t.ID), v1.TaskStatusEnum_QUEUED, "Task is Queued"); err != nil { + s.logger.Printf("Error updating task status: %v", err) + } } + case <-ctx.Done(): + return ctx.Err() // Exit if the context is done } } } -// requeueTask attempts to requeue a task back into the channel -func (s *TaskServer) requeueTask(ctx context.Context, t task.Task) { - select { - case s.channel <- t: - s.logger.Printf("Requeued task ID %d", t.ID) - case <-ctx.Done(): - s.logger.Printf("Context cancelled while requeueing task ID %d", t.ID) - default: - // If the channel is full, log the error and consider other options - s.logger.Printf("ERROR: Channel full, unable to requeue task ID %d", t.ID) - // Consider persisting this task or implementing a backup queue +// updateTaskStatus updates the task status and creates a history entry +func (s *TaskServer) updateTaskStatus(ctx context.Context, taskID uint, status v1.TaskStatusEnum, message string) error { + if err := s.taskRepo.UpdateTaskStatus(ctx, taskID, int(status)); err != nil { + return fmt.Errorf("failed to update task status: %w", err) } -} -// handleStreamRequest processes incoming stream requests -func (s *TaskServer) handleStreamRequest(ctx context.Context, req *v1.StreamRequest, clientID string) error { - switch r := req.Request.(type) { - case *v1.StreamRequest_UpdateTaskStatus: - return s.handleUpdateTaskStatus(ctx, r.UpdateTaskStatus) - case *v1.StreamRequest_Heartbeat: - s.handleHeartbeat(clientID) - s.logger.Printf("Heartbeat received from client: %s", clientID) - default: - s.logger.Printf("Received unknown request type: %T", r) - return fmt.Errorf("unknown request type: %T", r) + if err := s.createTaskStatusHistory(ctx, taskID, int(status), message); err != nil { + s.logger.Printf("WARNING: Failed to create task status history: %v", err) } + return nil } @@ -580,36 +452,3 @@ func (s *TaskServer) convertTaskHistoryToProto(history []task.TaskHistory) []*v1 } return protoHistory } - -// handleHeartbeat updates the last heartbeat time for a client -func (s *TaskServer) handleHeartbeat(clientID string) { - s.clientHeartbeats.Store(clientID, time.Now()) -} - -// checkStaleClients periodically checks for clients that haven't sent a heartbeat -func (s *TaskServer) checkStaleClients(ctx context.Context) { - ticker := time.NewTicker(s.heartbeatTimeout / 2) - defer ticker.Stop() - - for { - select { - case <-ctx.Done(): - return - case <-ticker.C: - now := time.Now() - s.clientHeartbeats.Range(func(key, value interface{}) bool { - clientID := key.(string) - lastHeartbeat := value.(time.Time) - if now.Sub(lastHeartbeat) > s.heartbeatTimeout { - s.logger.Printf("Client %s is stale, removing", clientID) - s.clientHeartbeats.Delete(clientID) - } - return true - }) - } - } -} - -func generateClientID() string { - return fmt.Sprintf("client-%s", uuid.New().String()) -} From bd20de0da9e558b996a046879bea8b9c46f01eae Mon Sep 17 00:00:00 2001 From: Yuvraj Date: Wed, 16 Oct 2024 01:31:32 +0530 Subject: [PATCH 09/17] added kubebuilder boilerplate --- Dockerfile.controller | 33 +++ controller/Makefile | 200 ++++++++++++++++++ controller/PROJECT | 19 ++ controller/README.md | 114 ++++++++++ controller/api/v1/groupversion_info.go | 36 ++++ controller/api/v1/task_types.go | 94 ++++++++ controller/api/v1/zz_generated.deepcopy.go | 114 ++++++++++ controller/cmd/main.go | 170 +++++++++++++++ controller/config/crd/kustomization.yaml | 22 ++ controller/config/crd/kustomizeconfig.yaml | 19 ++ controller/config/default/kustomization.yaml | 151 +++++++++++++ .../config/default/manager_metrics_patch.yaml | 4 + .../config/default/metrics_service.yaml | 17 ++ controller/config/manager/kustomization.yaml | 2 + controller/config/manager/manager.yaml | 95 +++++++++ .../network-policy/allow-metrics-traffic.yaml | 26 +++ .../config/network-policy/kustomization.yaml | 2 + .../config/prometheus/kustomization.yaml | 2 + controller/config/prometheus/monitor.yaml | 30 +++ controller/config/rbac/kustomization.yaml | 27 +++ .../config/rbac/leader_election_role.yaml | 40 ++++ .../rbac/leader_election_role_binding.yaml | 15 ++ controller/config/rbac/metrics_auth_role.yaml | 17 ++ .../rbac/metrics_auth_role_binding.yaml | 12 ++ .../config/rbac/metrics_reader_role.yaml | 9 + controller/config/rbac/role.yaml | 11 + controller/config/rbac/role_binding.yaml | 15 ++ controller/config/rbac/service_account.yaml | 8 + controller/config/rbac/task_editor_role.yaml | 27 +++ controller/config/rbac/task_viewer_role.yaml | 23 ++ controller/config/samples/kustomization.yaml | 4 + controller/config/samples/v1_task.yaml | 9 + controller/hack/boilerplate.go.txt | 15 ++ controller/internal/controller/suite_test.go | 96 +++++++++ .../internal/controller/task_controller.go | 62 ++++++ .../controller/task_controller_test.go | 84 ++++++++ go.mod | 72 ++++++- go.sum | 178 ++++++++++++++-- 38 files changed, 1851 insertions(+), 23 deletions(-) create mode 100644 Dockerfile.controller create mode 100644 controller/Makefile create mode 100644 controller/PROJECT create mode 100644 controller/README.md create mode 100644 controller/api/v1/groupversion_info.go create mode 100644 controller/api/v1/task_types.go create mode 100644 controller/api/v1/zz_generated.deepcopy.go create mode 100644 controller/cmd/main.go create mode 100644 controller/config/crd/kustomization.yaml create mode 100644 controller/config/crd/kustomizeconfig.yaml create mode 100644 controller/config/default/kustomization.yaml create mode 100644 controller/config/default/manager_metrics_patch.yaml create mode 100644 controller/config/default/metrics_service.yaml create mode 100644 controller/config/manager/kustomization.yaml create mode 100644 controller/config/manager/manager.yaml create mode 100644 controller/config/network-policy/allow-metrics-traffic.yaml create mode 100644 controller/config/network-policy/kustomization.yaml create mode 100644 controller/config/prometheus/kustomization.yaml create mode 100644 controller/config/prometheus/monitor.yaml create mode 100644 controller/config/rbac/kustomization.yaml create mode 100644 controller/config/rbac/leader_election_role.yaml create mode 100644 controller/config/rbac/leader_election_role_binding.yaml create mode 100644 controller/config/rbac/metrics_auth_role.yaml create mode 100644 controller/config/rbac/metrics_auth_role_binding.yaml create mode 100644 controller/config/rbac/metrics_reader_role.yaml create mode 100644 controller/config/rbac/role.yaml create mode 100644 controller/config/rbac/role_binding.yaml create mode 100644 controller/config/rbac/service_account.yaml create mode 100644 controller/config/rbac/task_editor_role.yaml create mode 100644 controller/config/rbac/task_viewer_role.yaml create mode 100644 controller/config/samples/kustomization.yaml create mode 100644 controller/config/samples/v1_task.yaml create mode 100644 controller/hack/boilerplate.go.txt create mode 100644 controller/internal/controller/suite_test.go create mode 100644 controller/internal/controller/task_controller.go create mode 100644 controller/internal/controller/task_controller_test.go diff --git a/Dockerfile.controller b/Dockerfile.controller new file mode 100644 index 0000000..a48973e --- /dev/null +++ b/Dockerfile.controller @@ -0,0 +1,33 @@ +# Build the manager binary +FROM golang:1.22 AS builder +ARG TARGETOS +ARG TARGETARCH + +WORKDIR /workspace +# Copy the Go Modules manifests +COPY go.mod go.mod +COPY go.sum go.sum +# cache deps before building and copying source so that we don't need to re-download as much +# and so that source changes don't invalidate our downloaded layer +RUN go mod download + +# Copy the go source +COPY cmd/main.go cmd/main.go +COPY api/ api/ +COPY internal/controller/ internal/controller/ + +# Build +# the GOARCH has not a default value to allow the binary be built according to the host where the command +# was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO +# the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore, +# by leaving it empty we can ensure that the container and binary shipped on it will have the same platform. +RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager cmd/main.go + +# Use distroless as minimal base image to package the manager binary +# Refer to https://github.com/GoogleContainerTools/distroless for more details +FROM gcr.io/distroless/static:nonroot +WORKDIR / +COPY --from=builder /workspace/manager . +USER 65532:65532 + +ENTRYPOINT ["/manager"] diff --git a/controller/Makefile b/controller/Makefile new file mode 100644 index 0000000..ca5ed65 --- /dev/null +++ b/controller/Makefile @@ -0,0 +1,200 @@ +# Image URL to use all building/pushing image targets +IMG ?= controller:latest +# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. +ENVTEST_K8S_VERSION = 1.31.0 + +# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) +ifeq (,$(shell go env GOBIN)) +GOBIN=$(shell go env GOPATH)/bin +else +GOBIN=$(shell go env GOBIN) +endif + +# CONTAINER_TOOL defines the container tool to be used for building images. +# Be aware that the target commands are only tested with Docker which is +# scaffolded by default. However, you might want to replace it to use other +# tools. (i.e. podman) +CONTAINER_TOOL ?= docker + +# Setting SHELL to bash allows bash commands to be executed by recipes. +# Options are set to exit when a recipe line exits non-zero or a piped command fails. +SHELL = /usr/bin/env bash -o pipefail +.SHELLFLAGS = -ec + +.PHONY: all +all: build + +##@ General + +# The help target prints out all targets with their descriptions organized +# beneath their categories. The categories are represented by '##@' and the +# target descriptions by '##'. The awk command is responsible for reading the +# entire set of makefiles included in this invocation, looking for lines of the +# file as xyz: ## something, and then pretty-format the target and help. Then, +# if there's a line with ##@ something, that gets pretty-printed as a category. +# More info on the usage of ANSI control characters for terminal formatting: +# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters +# More info on the awk command: +# http://linuxcommand.org/lc3_adv_awk.php + +.PHONY: help +help: ## Display this help. + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + +##@ Development + +.PHONY: manifests +manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. + $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases + +.PHONY: generate +generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. + $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." + +.PHONY: fmt +fmt: ## Run go fmt against code. + go fmt ./... + +.PHONY: vet +vet: ## Run go vet against code. + go vet ./... + +.PHONY: test +test: manifests generate fmt vet envtest ## Run tests. + KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test $$(go list ./... | grep -v /e2e) -coverprofile cover.out + +# Utilize Kind or modify the e2e tests to load the image locally, enabling compatibility with other vendors. +.PHONY: test-e2e # Run the e2e tests against a Kind k8s instance that is spun up. +test-e2e: + go test ./test/e2e/ -v -ginkgo.v + +.PHONY: lint +lint: golangci-lint ## Run golangci-lint linter + $(GOLANGCI_LINT) run + +.PHONY: lint-fix +lint-fix: golangci-lint ## Run golangci-lint linter and perform fixes + $(GOLANGCI_LINT) run --fix + +##@ Build + +.PHONY: build +build: manifests generate fmt vet ## Build manager binary. + go build -o bin/manager cmd/main.go + +.PHONY: run +run: manifests generate fmt vet ## Run a controller from your host. + go run ./cmd/main.go + +# If you wish to build the manager image targeting other platforms you can use the --platform flag. +# (i.e. docker build --platform linux/arm64). However, you must enable docker buildKit for it. +# More info: https://docs.docker.com/develop/develop-images/build_enhancements/ +.PHONY: docker-build +docker-build: ## Build docker image with the manager. + $(CONTAINER_TOOL) build -t ${IMG} . + +.PHONY: docker-push +docker-push: ## Push docker image with the manager. + $(CONTAINER_TOOL) push ${IMG} + +# PLATFORMS defines the target platforms for the manager image be built to provide support to multiple +# architectures. (i.e. make docker-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to: +# - be able to use docker buildx. More info: https://docs.docker.com/build/buildx/ +# - have enabled BuildKit. More info: https://docs.docker.com/develop/develop-images/build_enhancements/ +# - be able to push the image to your registry (i.e. if you do not set a valid value via IMG=> then the export will fail) +# To adequately provide solutions that are compatible with multiple platforms, you should consider using this option. +PLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le +.PHONY: docker-buildx +docker-buildx: ## Build and push docker image for the manager for cross-platform support + # copy existing Dockerfile and insert --platform=${BUILDPLATFORM} into Dockerfile.cross, and preserve the original Dockerfile + sed -e '1 s/\(^FROM\)/FROM --platform=\$$\{BUILDPLATFORM\}/; t' -e ' 1,// s//FROM --platform=\$$\{BUILDPLATFORM\}/' Dockerfile > Dockerfile.cross + - $(CONTAINER_TOOL) buildx create --name controller-builder + $(CONTAINER_TOOL) buildx use controller-builder + - $(CONTAINER_TOOL) buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross . + - $(CONTAINER_TOOL) buildx rm controller-builder + rm Dockerfile.cross + +.PHONY: build-installer +build-installer: manifests generate kustomize ## Generate a consolidated YAML with CRDs and deployment. + mkdir -p dist + cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} + $(KUSTOMIZE) build config/default > dist/install.yaml + +##@ Deployment + +ifndef ignore-not-found + ignore-not-found = false +endif + +.PHONY: install +install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. + $(KUSTOMIZE) build config/crd | $(KUBECTL) apply -f - + +.PHONY: uninstall +uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. + $(KUSTOMIZE) build config/crd | $(KUBECTL) delete --ignore-not-found=$(ignore-not-found) -f - + +.PHONY: deploy +deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config. + cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} + $(KUSTOMIZE) build config/default | $(KUBECTL) apply -f - + +.PHONY: undeploy +undeploy: kustomize ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. + $(KUSTOMIZE) build config/default | $(KUBECTL) delete --ignore-not-found=$(ignore-not-found) -f - + +##@ Dependencies + +## Location to install dependencies to +LOCALBIN ?= $(shell pwd)/bin +$(LOCALBIN): + mkdir -p $(LOCALBIN) + +## Tool Binaries +KUBECTL ?= kubectl +KUSTOMIZE ?= $(LOCALBIN)/kustomize +CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen +ENVTEST ?= $(LOCALBIN)/setup-envtest +GOLANGCI_LINT = $(LOCALBIN)/golangci-lint + +## Tool Versions +KUSTOMIZE_VERSION ?= v5.4.3 +CONTROLLER_TOOLS_VERSION ?= v0.16.1 +ENVTEST_VERSION ?= release-0.19 +GOLANGCI_LINT_VERSION ?= v1.59.1 + +.PHONY: kustomize +kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. +$(KUSTOMIZE): $(LOCALBIN) + $(call go-install-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v5,$(KUSTOMIZE_VERSION)) + +.PHONY: controller-gen +controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary. +$(CONTROLLER_GEN): $(LOCALBIN) + $(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen,$(CONTROLLER_TOOLS_VERSION)) + +.PHONY: envtest +envtest: $(ENVTEST) ## Download setup-envtest locally if necessary. +$(ENVTEST): $(LOCALBIN) + $(call go-install-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest,$(ENVTEST_VERSION)) + +.PHONY: golangci-lint +golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary. +$(GOLANGCI_LINT): $(LOCALBIN) + $(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/cmd/golangci-lint,$(GOLANGCI_LINT_VERSION)) + +# go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist +# $1 - target path with name of binary +# $2 - package url which can be installed +# $3 - specific version of package +define go-install-tool +@[ -f "$(1)-$(3)" ] || { \ +set -e; \ +package=$(2)@$(3) ;\ +echo "Downloading $${package}" ;\ +rm -f $(1) || true ;\ +GOBIN=$(LOCALBIN) go install $${package} ;\ +mv $(1) $(1)-$(3) ;\ +} ;\ +ln -sf $(1)-$(3) $(1) +endef diff --git a/controller/PROJECT b/controller/PROJECT new file mode 100644 index 0000000..aa2fc36 --- /dev/null +++ b/controller/PROJECT @@ -0,0 +1,19 @@ +# Code generated by tool. DO NOT EDIT. +# This file is used to track the info used to scaffold your project +# and allow the plugins properly work. +# More info: https://book.kubebuilder.io/reference/project-config.html +domain: task.io +layout: +- go.kubebuilder.io/v4 +projectName: controller +repo: task +resources: +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: task.io + kind: Task + path: task/api/v1 + version: v1 +version: "3" diff --git a/controller/README.md b/controller/README.md new file mode 100644 index 0000000..eaf0a19 --- /dev/null +++ b/controller/README.md @@ -0,0 +1,114 @@ +# controller +// TODO(user): Add simple overview of use/purpose + +## Description +// TODO(user): An in-depth paragraph about your project and overview of use + +## Getting Started + +### Prerequisites +- go version v1.22.0+ +- docker version 17.03+. +- kubectl version v1.11.3+. +- Access to a Kubernetes v1.11.3+ cluster. + +### To Deploy on the cluster +**Build and push your image to the location specified by `IMG`:** + +```sh +make docker-build docker-push IMG=/controller:tag +``` + +**NOTE:** This image ought to be published in the personal registry you specified. +And it is required to have access to pull the image from the working environment. +Make sure you have the proper permission to the registry if the above commands don’t work. + +**Install the CRDs into the cluster:** + +```sh +make install +``` + +**Deploy the Manager to the cluster with the image specified by `IMG`:** + +```sh +make deploy IMG=/controller:tag +``` + +> **NOTE**: If you encounter RBAC errors, you may need to grant yourself cluster-admin +privileges or be logged in as admin. + +**Create instances of your solution** +You can apply the samples (examples) from the config/sample: + +```sh +kubectl apply -k config/samples/ +``` + +>**NOTE**: Ensure that the samples has default values to test it out. + +### To Uninstall +**Delete the instances (CRs) from the cluster:** + +```sh +kubectl delete -k config/samples/ +``` + +**Delete the APIs(CRDs) from the cluster:** + +```sh +make uninstall +``` + +**UnDeploy the controller from the cluster:** + +```sh +make undeploy +``` + +## Project Distribution + +Following are the steps to build the installer and distribute this project to users. + +1. Build the installer for the image built and published in the registry: + +```sh +make build-installer IMG=/controller:tag +``` + +NOTE: The makefile target mentioned above generates an 'install.yaml' +file in the dist directory. This file contains all the resources built +with Kustomize, which are necessary to install this project without +its dependencies. + +2. Using the installer + +Users can just run kubectl apply -f to install the project, i.e.: + +```sh +kubectl apply -f https://raw.githubusercontent.com//controller//dist/install.yaml +``` + +## Contributing +// TODO(user): Add detailed information on how you would like others to contribute to this project + +**NOTE:** Run `make help` for more information on all potential `make` targets + +More information can be found via the [Kubebuilder Documentation](https://book.kubebuilder.io/introduction.html) + +## License + +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + diff --git a/controller/api/v1/groupversion_info.go b/controller/api/v1/groupversion_info.go new file mode 100644 index 0000000..6da83ea --- /dev/null +++ b/controller/api/v1/groupversion_info.go @@ -0,0 +1,36 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package v1 contains API Schema definitions for the v1 API group +// +kubebuilder:object:generate=true +// +groupName=task.io +package v1 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +var ( + // GroupVersion is group version used to register these objects + GroupVersion = schema.GroupVersion{Group: "task.io", Version: "v1"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} + + // AddToScheme adds the types in this group-version to the given scheme. + AddToScheme = SchemeBuilder.AddToScheme +) diff --git a/controller/api/v1/task_types.go b/controller/api/v1/task_types.go new file mode 100644 index 0000000..e265c6e --- /dev/null +++ b/controller/api/v1/task_types.go @@ -0,0 +1,94 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// TaskSpec defines the desired state of Task +type TaskSpec struct { + // ID is the unique identifier for the task. + ID int32 `json:"id,omitempty"` + + // Name is the name of the task. + Name string `json:"name,omitempty"` + + // Type is the type of the task. + Type string `json:"type,omitempty"` + + // Status is the current status of the task. + Status string `json:"status,omitempty"` + + // Retries is the number of retries attempted for this task. + Retries int32 `json:"retries,omitempty"` + + // Priority is the priority level of the task. + Priority int32 `json:"priority,omitempty"` + + // CreatedAt is the timestamp of when the task was created. + CreatedAt string `json:"created_at,omitempty"` + + // Payload contains task parameters. + Payload Payload `json:"payload,omitempty"` + + // Description is a description of the task. + Description string `json:"description,omitempty"` +} + +// Payload defines the parameters for the task. +type Payload struct { + // Parameters are dynamic key-value pairs for task parameters. + Parameters map[string]string `json:"parameters,omitempty"` +} + +// TaskStatus defines the observed state of Task +type TaskStatus struct { + // Status is the current status of the task. + Status string `json:"status,omitempty"` + + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status + +// Task is the Schema for the tasks API +type Task struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec TaskSpec `json:"spec,omitempty"` + Status TaskStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// TaskList contains a list of Task +type TaskList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Task `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Task{}, &TaskList{}) +} diff --git a/controller/api/v1/zz_generated.deepcopy.go b/controller/api/v1/zz_generated.deepcopy.go new file mode 100644 index 0000000..04020f3 --- /dev/null +++ b/controller/api/v1/zz_generated.deepcopy.go @@ -0,0 +1,114 @@ +//go:build !ignore_autogenerated + +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package v1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Task) DeepCopyInto(out *Task) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Task. +func (in *Task) DeepCopy() *Task { + if in == nil { + return nil + } + out := new(Task) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Task) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TaskList) DeepCopyInto(out *TaskList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Task, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TaskList. +func (in *TaskList) DeepCopy() *TaskList { + if in == nil { + return nil + } + out := new(TaskList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TaskList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TaskSpec) DeepCopyInto(out *TaskSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TaskSpec. +func (in *TaskSpec) DeepCopy() *TaskSpec { + if in == nil { + return nil + } + out := new(TaskSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TaskStatus) DeepCopyInto(out *TaskStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TaskStatus. +func (in *TaskStatus) DeepCopy() *TaskStatus { + if in == nil { + return nil + } + out := new(TaskStatus) + in.DeepCopyInto(out) + return out +} diff --git a/controller/cmd/main.go b/controller/cmd/main.go new file mode 100644 index 0000000..3c81f51 --- /dev/null +++ b/controller/cmd/main.go @@ -0,0 +1,170 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "crypto/tls" + "flag" + "os" + + // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) + // to ensure that exec-entrypoint and run can make use of them. + _ "k8s.io/client-go/plugin/pkg/client/auth" + + "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/healthz" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + "sigs.k8s.io/controller-runtime/pkg/metrics/filters" + metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" + "sigs.k8s.io/controller-runtime/pkg/webhook" + + taskiov1 "task/api/v1" + "task/internal/controller" + // +kubebuilder:scaffold:imports +) + +var ( + scheme = runtime.NewScheme() + setupLog = ctrl.Log.WithName("setup") +) + +func init() { + utilruntime.Must(clientgoscheme.AddToScheme(scheme)) + + utilruntime.Must(taskiov1.AddToScheme(scheme)) + // +kubebuilder:scaffold:scheme +} + +func main() { + var metricsAddr string + var enableLeaderElection bool + var probeAddr string + var secureMetrics bool + var enableHTTP2 bool + var tlsOpts []func(*tls.Config) + flag.StringVar(&metricsAddr, "metrics-bind-address", "0", "The address the metrics endpoint binds to. "+ + "Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.") + flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") + flag.BoolVar(&enableLeaderElection, "leader-elect", false, + "Enable leader election for controller manager. "+ + "Enabling this will ensure there is only one active controller manager.") + flag.BoolVar(&secureMetrics, "metrics-secure", true, + "If set, the metrics endpoint is served securely via HTTPS. Use --metrics-secure=false to use HTTP instead.") + flag.BoolVar(&enableHTTP2, "enable-http2", false, + "If set, HTTP/2 will be enabled for the metrics and webhook servers") + opts := zap.Options{ + Development: true, + } + opts.BindFlags(flag.CommandLine) + flag.Parse() + + ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) + + // if the enable-http2 flag is false (the default), http/2 should be disabled + // due to its vulnerabilities. More specifically, disabling http/2 will + // prevent from being vulnerable to the HTTP/2 Stream Cancellation and + // Rapid Reset CVEs. For more information see: + // - https://github.com/advisories/GHSA-qppj-fm5r-hxr3 + // - https://github.com/advisories/GHSA-4374-p667-p6c8 + disableHTTP2 := func(c *tls.Config) { + setupLog.Info("disabling http/2") + c.NextProtos = []string{"http/1.1"} + } + + if !enableHTTP2 { + tlsOpts = append(tlsOpts, disableHTTP2) + } + + webhookServer := webhook.NewServer(webhook.Options{ + TLSOpts: tlsOpts, + }) + + // Metrics endpoint is enabled in 'config/default/kustomization.yaml'. The Metrics options configure the server. + // More info: + // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.19.0/pkg/metrics/server + // - https://book.kubebuilder.io/reference/metrics.html + metricsServerOptions := metricsserver.Options{ + BindAddress: metricsAddr, + SecureServing: secureMetrics, + // TODO(user): TLSOpts is used to allow configuring the TLS config used for the server. If certificates are + // not provided, self-signed certificates will be generated by default. This option is not recommended for + // production environments as self-signed certificates do not offer the same level of trust and security + // as certificates issued by a trusted Certificate Authority (CA). The primary risk is potentially allowing + // unauthorized access to sensitive metrics data. Consider replacing with CertDir, CertName, and KeyName + // to provide certificates, ensuring the server communicates using trusted and secure certificates. + TLSOpts: tlsOpts, + } + + if secureMetrics { + // FilterProvider is used to protect the metrics endpoint with authn/authz. + // These configurations ensure that only authorized users and service accounts + // can access the metrics endpoint. The RBAC are configured in 'config/rbac/kustomization.yaml'. More info: + // https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.19.0/pkg/metrics/filters#WithAuthenticationAndAuthorization + metricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization + } + + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ + Scheme: scheme, + Metrics: metricsServerOptions, + WebhookServer: webhookServer, + HealthProbeBindAddress: probeAddr, + LeaderElection: enableLeaderElection, + LeaderElectionID: "f448886c.task.io", + // LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily + // when the Manager ends. This requires the binary to immediately end when the + // Manager is stopped, otherwise, this setting is unsafe. Setting this significantly + // speeds up voluntary leader transitions as the new leader don't have to wait + // LeaseDuration time first. + // + // In the default scaffold provided, the program ends immediately after + // the manager stops, so would be fine to enable this option. However, + // if you are doing or is intended to do any operation such as perform cleanups + // after the manager stops then its usage might be unsafe. + // LeaderElectionReleaseOnCancel: true, + }) + if err != nil { + setupLog.Error(err, "unable to start manager") + os.Exit(1) + } + + if err = (&controller.TaskReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "Task") + os.Exit(1) + } + // +kubebuilder:scaffold:builder + + if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { + setupLog.Error(err, "unable to set up health check") + os.Exit(1) + } + if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { + setupLog.Error(err, "unable to set up ready check") + os.Exit(1) + } + + setupLog.Info("starting manager") + if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { + setupLog.Error(err, "problem running manager") + os.Exit(1) + } +} diff --git a/controller/config/crd/kustomization.yaml b/controller/config/crd/kustomization.yaml new file mode 100644 index 0000000..8f91e1c --- /dev/null +++ b/controller/config/crd/kustomization.yaml @@ -0,0 +1,22 @@ +# This kustomization.yaml is not intended to be run by itself, +# since it depends on service name and namespace that are out of this kustomize package. +# It should be run by config/default +resources: +- bases/task.io_tasks.yaml +# +kubebuilder:scaffold:crdkustomizeresource + +patches: +# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. +# patches here are for enabling the conversion webhook for each CRD +# +kubebuilder:scaffold:crdkustomizewebhookpatch + +# [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. +# patches here are for enabling the CA injection for each CRD +#- path: patches/cainjection_in_tasks.yaml +# +kubebuilder:scaffold:crdkustomizecainjectionpatch + +# [WEBHOOK] To enable webhook, uncomment the following section +# the following config is for teaching kustomize how to do kustomization for CRDs. + +#configurations: +#- kustomizeconfig.yaml diff --git a/controller/config/crd/kustomizeconfig.yaml b/controller/config/crd/kustomizeconfig.yaml new file mode 100644 index 0000000..ec5c150 --- /dev/null +++ b/controller/config/crd/kustomizeconfig.yaml @@ -0,0 +1,19 @@ +# This file is for teaching kustomize how to substitute name and namespace reference in CRD +nameReference: +- kind: Service + version: v1 + fieldSpecs: + - kind: CustomResourceDefinition + version: v1 + group: apiextensions.k8s.io + path: spec/conversion/webhook/clientConfig/service/name + +namespace: +- kind: CustomResourceDefinition + version: v1 + group: apiextensions.k8s.io + path: spec/conversion/webhook/clientConfig/service/namespace + create: false + +varReference: +- path: metadata/annotations diff --git a/controller/config/default/kustomization.yaml b/controller/config/default/kustomization.yaml new file mode 100644 index 0000000..0fd956d --- /dev/null +++ b/controller/config/default/kustomization.yaml @@ -0,0 +1,151 @@ +# Adds namespace to all resources. +namespace: controller-system + +# Value of this field is prepended to the +# names of all resources, e.g. a deployment named +# "wordpress" becomes "alices-wordpress". +# Note that it should also match with the prefix (text before '-') of the namespace +# field above. +namePrefix: controller- + +# Labels to add to all resources and selectors. +#labels: +#- includeSelectors: true +# pairs: +# someName: someValue + +resources: +- ../crd +- ../rbac +- ../manager +# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in +# crd/kustomization.yaml +#- ../webhook +# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. +#- ../certmanager +# [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. +#- ../prometheus +# [METRICS] Expose the controller manager metrics service. +- metrics_service.yaml +# [NETWORK POLICY] Protect the /metrics endpoint and Webhook Server with NetworkPolicy. +# Only Pod(s) running a namespace labeled with 'metrics: enabled' will be able to gather the metrics. +# Only CR(s) which requires webhooks and are applied on namespaces labeled with 'webhooks: enabled' will +# be able to communicate with the Webhook Server. +#- ../network-policy + +# Uncomment the patches line if you enable Metrics, and/or are using webhooks and cert-manager +patches: +# [METRICS] The following patch will enable the metrics endpoint using HTTPS and the port :8443. +# More info: https://book.kubebuilder.io/reference/metrics +- path: manager_metrics_patch.yaml + target: + kind: Deployment + +# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in +# crd/kustomization.yaml +#- path: manager_webhook_patch.yaml + +# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. +# Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. +# 'CERTMANAGER' needs to be enabled to use ca injection +#- path: webhookcainjection_patch.yaml + +# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. +# Uncomment the following replacements to add the cert-manager CA injection annotations +#replacements: +# - source: # Add cert-manager annotation to ValidatingWebhookConfiguration, MutatingWebhookConfiguration and CRDs +# kind: Certificate +# group: cert-manager.io +# version: v1 +# name: serving-cert # this name should match the one in certificate.yaml +# fieldPath: .metadata.namespace # namespace of the certificate CR +# targets: +# - select: +# kind: ValidatingWebhookConfiguration +# fieldPaths: +# - .metadata.annotations.[cert-manager.io/inject-ca-from] +# options: +# delimiter: '/' +# index: 0 +# create: true +# - select: +# kind: MutatingWebhookConfiguration +# fieldPaths: +# - .metadata.annotations.[cert-manager.io/inject-ca-from] +# options: +# delimiter: '/' +# index: 0 +# create: true +# - select: +# kind: CustomResourceDefinition +# fieldPaths: +# - .metadata.annotations.[cert-manager.io/inject-ca-from] +# options: +# delimiter: '/' +# index: 0 +# create: true +# - source: +# kind: Certificate +# group: cert-manager.io +# version: v1 +# name: serving-cert # this name should match the one in certificate.yaml +# fieldPath: .metadata.name +# targets: +# - select: +# kind: ValidatingWebhookConfiguration +# fieldPaths: +# - .metadata.annotations.[cert-manager.io/inject-ca-from] +# options: +# delimiter: '/' +# index: 1 +# create: true +# - select: +# kind: MutatingWebhookConfiguration +# fieldPaths: +# - .metadata.annotations.[cert-manager.io/inject-ca-from] +# options: +# delimiter: '/' +# index: 1 +# create: true +# - select: +# kind: CustomResourceDefinition +# fieldPaths: +# - .metadata.annotations.[cert-manager.io/inject-ca-from] +# options: +# delimiter: '/' +# index: 1 +# create: true +# - source: # Add cert-manager annotation to the webhook Service +# kind: Service +# version: v1 +# name: webhook-service +# fieldPath: .metadata.name # namespace of the service +# targets: +# - select: +# kind: Certificate +# group: cert-manager.io +# version: v1 +# fieldPaths: +# - .spec.dnsNames.0 +# - .spec.dnsNames.1 +# options: +# delimiter: '.' +# index: 0 +# create: true +# - source: +# kind: Service +# version: v1 +# name: webhook-service +# fieldPath: .metadata.namespace # namespace of the service +# targets: +# - select: +# kind: Certificate +# group: cert-manager.io +# version: v1 +# fieldPaths: +# - .spec.dnsNames.0 +# - .spec.dnsNames.1 +# options: +# delimiter: '.' +# index: 1 +# create: true diff --git a/controller/config/default/manager_metrics_patch.yaml b/controller/config/default/manager_metrics_patch.yaml new file mode 100644 index 0000000..2aaef65 --- /dev/null +++ b/controller/config/default/manager_metrics_patch.yaml @@ -0,0 +1,4 @@ +# This patch adds the args to allow exposing the metrics endpoint using HTTPS +- op: add + path: /spec/template/spec/containers/0/args/0 + value: --metrics-bind-address=:8443 diff --git a/controller/config/default/metrics_service.yaml b/controller/config/default/metrics_service.yaml new file mode 100644 index 0000000..ae73482 --- /dev/null +++ b/controller/config/default/metrics_service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + control-plane: controller-manager + app.kubernetes.io/name: controller + app.kubernetes.io/managed-by: kustomize + name: controller-manager-metrics-service + namespace: system +spec: + ports: + - name: https + port: 8443 + protocol: TCP + targetPort: 8443 + selector: + control-plane: controller-manager diff --git a/controller/config/manager/kustomization.yaml b/controller/config/manager/kustomization.yaml new file mode 100644 index 0000000..5c5f0b8 --- /dev/null +++ b/controller/config/manager/kustomization.yaml @@ -0,0 +1,2 @@ +resources: +- manager.yaml diff --git a/controller/config/manager/manager.yaml b/controller/config/manager/manager.yaml new file mode 100644 index 0000000..edcfa9c --- /dev/null +++ b/controller/config/manager/manager.yaml @@ -0,0 +1,95 @@ +apiVersion: v1 +kind: Namespace +metadata: + labels: + control-plane: controller-manager + app.kubernetes.io/name: controller + app.kubernetes.io/managed-by: kustomize + name: system +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: controller-manager + namespace: system + labels: + control-plane: controller-manager + app.kubernetes.io/name: controller + app.kubernetes.io/managed-by: kustomize +spec: + selector: + matchLabels: + control-plane: controller-manager + replicas: 1 + template: + metadata: + annotations: + kubectl.kubernetes.io/default-container: manager + labels: + control-plane: controller-manager + spec: + # TODO(user): Uncomment the following code to configure the nodeAffinity expression + # according to the platforms which are supported by your solution. + # It is considered best practice to support multiple architectures. You can + # build your manager image using the makefile target docker-buildx. + # affinity: + # nodeAffinity: + # requiredDuringSchedulingIgnoredDuringExecution: + # nodeSelectorTerms: + # - matchExpressions: + # - key: kubernetes.io/arch + # operator: In + # values: + # - amd64 + # - arm64 + # - ppc64le + # - s390x + # - key: kubernetes.io/os + # operator: In + # values: + # - linux + securityContext: + runAsNonRoot: true + # TODO(user): For common cases that do not require escalating privileges + # it is recommended to ensure that all your Pods/Containers are restrictive. + # More info: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted + # Please uncomment the following code if your project does NOT have to work on old Kubernetes + # versions < 1.19 or on vendors versions which do NOT support this field by default (i.e. Openshift < 4.11 ). + # seccompProfile: + # type: RuntimeDefault + containers: + - command: + - /manager + args: + - --leader-elect + - --health-probe-bind-address=:8081 + image: controller:latest + name: manager + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - "ALL" + livenessProbe: + httpGet: + path: /healthz + port: 8081 + initialDelaySeconds: 15 + periodSeconds: 20 + readinessProbe: + httpGet: + path: /readyz + port: 8081 + initialDelaySeconds: 5 + periodSeconds: 10 + # TODO(user): Configure the resources accordingly based on the project requirements. + # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + resources: + limits: + cpu: 500m + memory: 128Mi + requests: + cpu: 10m + memory: 64Mi + serviceAccountName: controller-manager + terminationGracePeriodSeconds: 10 diff --git a/controller/config/network-policy/allow-metrics-traffic.yaml b/controller/config/network-policy/allow-metrics-traffic.yaml new file mode 100644 index 0000000..f6ddf95 --- /dev/null +++ b/controller/config/network-policy/allow-metrics-traffic.yaml @@ -0,0 +1,26 @@ +# This NetworkPolicy allows ingress traffic +# with Pods running on namespaces labeled with 'metrics: enabled'. Only Pods on those +# namespaces are able to gathering data from the metrics endpoint. +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + labels: + app.kubernetes.io/name: controller + app.kubernetes.io/managed-by: kustomize + name: allow-metrics-traffic + namespace: system +spec: + podSelector: + matchLabels: + control-plane: controller-manager + policyTypes: + - Ingress + ingress: + # This allows ingress traffic from any namespace with the label metrics: enabled + - from: + - namespaceSelector: + matchLabels: + metrics: enabled # Only from namespaces with this label + ports: + - port: 8443 + protocol: TCP diff --git a/controller/config/network-policy/kustomization.yaml b/controller/config/network-policy/kustomization.yaml new file mode 100644 index 0000000..ec0fb5e --- /dev/null +++ b/controller/config/network-policy/kustomization.yaml @@ -0,0 +1,2 @@ +resources: +- allow-metrics-traffic.yaml diff --git a/controller/config/prometheus/kustomization.yaml b/controller/config/prometheus/kustomization.yaml new file mode 100644 index 0000000..ed13716 --- /dev/null +++ b/controller/config/prometheus/kustomization.yaml @@ -0,0 +1,2 @@ +resources: +- monitor.yaml diff --git a/controller/config/prometheus/monitor.yaml b/controller/config/prometheus/monitor.yaml new file mode 100644 index 0000000..a9731e4 --- /dev/null +++ b/controller/config/prometheus/monitor.yaml @@ -0,0 +1,30 @@ +# Prometheus Monitor Service (Metrics) +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + labels: + control-plane: controller-manager + app.kubernetes.io/name: controller + app.kubernetes.io/managed-by: kustomize + name: controller-manager-metrics-monitor + namespace: system +spec: + endpoints: + - path: /metrics + port: https # Ensure this is the name of the port that exposes HTTPS metrics + scheme: https + bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token + tlsConfig: + # TODO(user): The option insecureSkipVerify: true is not recommended for production since it disables + # certificate verification. This poses a significant security risk by making the system vulnerable to + # man-in-the-middle attacks, where an attacker could intercept and manipulate the communication between + # Prometheus and the monitored services. This could lead to unauthorized access to sensitive metrics data, + # compromising the integrity and confidentiality of the information. + # Please use the following options for secure configurations: + # caFile: /etc/metrics-certs/ca.crt + # certFile: /etc/metrics-certs/tls.crt + # keyFile: /etc/metrics-certs/tls.key + insecureSkipVerify: true + selector: + matchLabels: + control-plane: controller-manager diff --git a/controller/config/rbac/kustomization.yaml b/controller/config/rbac/kustomization.yaml new file mode 100644 index 0000000..0ffacf4 --- /dev/null +++ b/controller/config/rbac/kustomization.yaml @@ -0,0 +1,27 @@ +resources: +# All RBAC will be applied under this service account in +# the deployment namespace. You may comment out this resource +# if your manager will use a service account that exists at +# runtime. Be sure to update RoleBinding and ClusterRoleBinding +# subjects if changing service account names. +- service_account.yaml +- role.yaml +- role_binding.yaml +- leader_election_role.yaml +- leader_election_role_binding.yaml +# The following RBAC configurations are used to protect +# the metrics endpoint with authn/authz. These configurations +# ensure that only authorized users and service accounts +# can access the metrics endpoint. Comment the following +# permissions if you want to disable this protection. +# More info: https://book.kubebuilder.io/reference/metrics.html +- metrics_auth_role.yaml +- metrics_auth_role_binding.yaml +- metrics_reader_role.yaml +# For each CRD, "Editor" and "Viewer" roles are scaffolded by +# default, aiding admins in cluster management. Those roles are +# not used by the Project itself. You can comment the following lines +# if you do not want those helpers be installed with your Project. +- task_editor_role.yaml +- task_viewer_role.yaml + diff --git a/controller/config/rbac/leader_election_role.yaml b/controller/config/rbac/leader_election_role.yaml new file mode 100644 index 0000000..445f027 --- /dev/null +++ b/controller/config/rbac/leader_election_role.yaml @@ -0,0 +1,40 @@ +# permissions to do leader election. +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + labels: + app.kubernetes.io/name: controller + app.kubernetes.io/managed-by: kustomize + name: leader-election-role +rules: +- apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch diff --git a/controller/config/rbac/leader_election_role_binding.yaml b/controller/config/rbac/leader_election_role_binding.yaml new file mode 100644 index 0000000..aed609d --- /dev/null +++ b/controller/config/rbac/leader_election_role_binding.yaml @@ -0,0 +1,15 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + labels: + app.kubernetes.io/name: controller + app.kubernetes.io/managed-by: kustomize + name: leader-election-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: leader-election-role +subjects: +- kind: ServiceAccount + name: controller-manager + namespace: system diff --git a/controller/config/rbac/metrics_auth_role.yaml b/controller/config/rbac/metrics_auth_role.yaml new file mode 100644 index 0000000..32d2e4e --- /dev/null +++ b/controller/config/rbac/metrics_auth_role.yaml @@ -0,0 +1,17 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: metrics-auth-role +rules: +- apiGroups: + - authentication.k8s.io + resources: + - tokenreviews + verbs: + - create +- apiGroups: + - authorization.k8s.io + resources: + - subjectaccessreviews + verbs: + - create diff --git a/controller/config/rbac/metrics_auth_role_binding.yaml b/controller/config/rbac/metrics_auth_role_binding.yaml new file mode 100644 index 0000000..e775d67 --- /dev/null +++ b/controller/config/rbac/metrics_auth_role_binding.yaml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: metrics-auth-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: metrics-auth-role +subjects: +- kind: ServiceAccount + name: controller-manager + namespace: system diff --git a/controller/config/rbac/metrics_reader_role.yaml b/controller/config/rbac/metrics_reader_role.yaml new file mode 100644 index 0000000..51a75db --- /dev/null +++ b/controller/config/rbac/metrics_reader_role.yaml @@ -0,0 +1,9 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: metrics-reader +rules: +- nonResourceURLs: + - "/metrics" + verbs: + - get diff --git a/controller/config/rbac/role.yaml b/controller/config/rbac/role.yaml new file mode 100644 index 0000000..0dbb930 --- /dev/null +++ b/controller/config/rbac/role.yaml @@ -0,0 +1,11 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: controller + app.kubernetes.io/managed-by: kustomize + name: manager-role +rules: +- apiGroups: [""] + resources: ["pods"] + verbs: ["get", "list", "watch"] diff --git a/controller/config/rbac/role_binding.yaml b/controller/config/rbac/role_binding.yaml new file mode 100644 index 0000000..0953223 --- /dev/null +++ b/controller/config/rbac/role_binding.yaml @@ -0,0 +1,15 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + app.kubernetes.io/name: controller + app.kubernetes.io/managed-by: kustomize + name: manager-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: manager-role +subjects: +- kind: ServiceAccount + name: controller-manager + namespace: system diff --git a/controller/config/rbac/service_account.yaml b/controller/config/rbac/service_account.yaml new file mode 100644 index 0000000..834b343 --- /dev/null +++ b/controller/config/rbac/service_account.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + app.kubernetes.io/name: controller + app.kubernetes.io/managed-by: kustomize + name: controller-manager + namespace: system diff --git a/controller/config/rbac/task_editor_role.yaml b/controller/config/rbac/task_editor_role.yaml new file mode 100644 index 0000000..61dc334 --- /dev/null +++ b/controller/config/rbac/task_editor_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to edit tasks. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: controller + app.kubernetes.io/managed-by: kustomize + name: task-editor-role +rules: +- apiGroups: + - task.io + resources: + - tasks + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - task.io + resources: + - tasks/status + verbs: + - get diff --git a/controller/config/rbac/task_viewer_role.yaml b/controller/config/rbac/task_viewer_role.yaml new file mode 100644 index 0000000..8b03e19 --- /dev/null +++ b/controller/config/rbac/task_viewer_role.yaml @@ -0,0 +1,23 @@ +# permissions for end users to view tasks. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: controller + app.kubernetes.io/managed-by: kustomize + name: task-viewer-role +rules: +- apiGroups: + - task.io + resources: + - tasks + verbs: + - get + - list + - watch +- apiGroups: + - task.io + resources: + - tasks/status + verbs: + - get diff --git a/controller/config/samples/kustomization.yaml b/controller/config/samples/kustomization.yaml new file mode 100644 index 0000000..eee95f0 --- /dev/null +++ b/controller/config/samples/kustomization.yaml @@ -0,0 +1,4 @@ +## Append samples of your project ## +resources: +- v1_task.yaml +# +kubebuilder:scaffold:manifestskustomizesamples diff --git a/controller/config/samples/v1_task.yaml b/controller/config/samples/v1_task.yaml new file mode 100644 index 0000000..20c6613 --- /dev/null +++ b/controller/config/samples/v1_task.yaml @@ -0,0 +1,9 @@ +apiVersion: task.io/v1 +kind: Task +metadata: + labels: + app.kubernetes.io/name: controller + app.kubernetes.io/managed-by: kustomize + name: task-sample +spec: + # TODO(user): Add fields here diff --git a/controller/hack/boilerplate.go.txt b/controller/hack/boilerplate.go.txt new file mode 100644 index 0000000..ff72ff2 --- /dev/null +++ b/controller/hack/boilerplate.go.txt @@ -0,0 +1,15 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ \ No newline at end of file diff --git a/controller/internal/controller/suite_test.go b/controller/internal/controller/suite_test.go new file mode 100644 index 0000000..bd908e3 --- /dev/null +++ b/controller/internal/controller/suite_test.go @@ -0,0 +1,96 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "context" + "fmt" + "path/filepath" + "runtime" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + + taskiov1 "task/api/v1" + // +kubebuilder:scaffold:imports +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + +var cfg *rest.Config +var k8sClient client.Client +var testEnv *envtest.Environment +var ctx context.Context +var cancel context.CancelFunc + +func TestControllers(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecs(t, "Controller Suite") +} + +var _ = BeforeSuite(func() { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + ctx, cancel = context.WithCancel(context.TODO()) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, + ErrorIfCRDPathMissing: true, + + // The BinaryAssetsDirectory is only required if you want to run the tests directly + // without call the makefile target test. If not informed it will look for the + // default path defined in controller-runtime which is /usr/local/kubebuilder/. + // Note that you must have the required binaries setup under the bin directory to perform + // the tests directly. When we run make test it will be setup and used automatically. + BinaryAssetsDirectory: filepath.Join("..", "..", "bin", "k8s", + fmt.Sprintf("1.31.0-%s-%s", runtime.GOOS, runtime.GOARCH)), + } + + var err error + // cfg is defined in this file globally. + cfg, err = testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + err = taskiov1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + + // +kubebuilder:scaffold:scheme + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + +}) + +var _ = AfterSuite(func() { + By("tearing down the test environment") + cancel() + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +}) diff --git a/controller/internal/controller/task_controller.go b/controller/internal/controller/task_controller.go new file mode 100644 index 0000000..d3d2b07 --- /dev/null +++ b/controller/internal/controller/task_controller.go @@ -0,0 +1,62 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "context" + + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + taskiov1 "task/api/v1" +) + +// TaskReconciler reconciles a Task object +type TaskReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +// +kubebuilder:rbac:groups=task.io,resources=tasks,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=task.io,resources=tasks/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=task.io,resources=tasks/finalizers,verbs=update + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// TODO(user): Modify the Reconcile function to compare the state specified by +// the Task object against the actual cluster state, and then +// perform operations to make the cluster state reflect the state specified by +// the user. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.19.0/pkg/reconcile +func (r *TaskReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + _ = log.FromContext(ctx) + + // TODO(user): your logic here + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *TaskReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&taskiov1.Task{}). + Complete(r) +} diff --git a/controller/internal/controller/task_controller_test.go b/controller/internal/controller/task_controller_test.go new file mode 100644 index 0000000..b42d25d --- /dev/null +++ b/controller/internal/controller/task_controller_test.go @@ -0,0 +1,84 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "context" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + taskiov1 "task/api/v1" +) + +var _ = Describe("Task Controller", func() { + Context("When reconciling a resource", func() { + const resourceName = "test-resource" + + ctx := context.Background() + + typeNamespacedName := types.NamespacedName{ + Name: resourceName, + Namespace: "default", // TODO(user):Modify as needed + } + task := &taskiov1.Task{} + + BeforeEach(func() { + By("creating the custom resource for the Kind Task") + err := k8sClient.Get(ctx, typeNamespacedName, task) + if err != nil && errors.IsNotFound(err) { + resource := &taskiov1.Task{ + ObjectMeta: metav1.ObjectMeta{ + Name: resourceName, + Namespace: "default", + }, + // TODO(user): Specify other spec details if needed. + } + Expect(k8sClient.Create(ctx, resource)).To(Succeed()) + } + }) + + AfterEach(func() { + // TODO(user): Cleanup logic after each test, like removing the resource instance. + resource := &taskiov1.Task{} + err := k8sClient.Get(ctx, typeNamespacedName, resource) + Expect(err).NotTo(HaveOccurred()) + + By("Cleanup the specific resource instance Task") + Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) + }) + It("should successfully reconcile the resource", func() { + By("Reconciling the created resource") + controllerReconciler := &TaskReconciler{ + Client: k8sClient, + Scheme: k8sClient.Scheme(), + } + + _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + Expect(err).NotTo(HaveOccurred()) + // TODO(user): Add more specific assertions depending on your controller's reconciliation logic. + // Example: If you expect a certain status condition after reconciliation, verify it here. + }) + }) +}) diff --git a/go.mod b/go.mod index 3aca57d..3e285ed 100644 --- a/go.mod +++ b/go.mod @@ -9,16 +9,16 @@ require ( connectrpc.com/grpchealth v1.3.0 connectrpc.com/grpcreflect v1.2.0 connectrpc.com/otelconnect v0.7.1 - github.com/avast/retry-go/v4 v4.6.0 github.com/bufbuild/protovalidate-go v0.7.0 github.com/coreos/go-oidc/v3 v3.11.0 github.com/envoyproxy/protoc-gen-validate v1.1.0 github.com/google/go-cmp v0.6.0 - github.com/google/uuid v1.6.0 github.com/gorilla/sessions v1.4.0 github.com/joho/godotenv v1.5.1 github.com/kelseyhightower/envconfig v1.4.0 github.com/olekukonko/tablewriter v0.0.5 + github.com/onsi/ginkgo/v2 v2.19.0 + github.com/onsi/gomega v1.33.1 github.com/prometheus/client_golang v1.20.4 github.com/riverqueue/river v0.13.0 github.com/riverqueue/river/rivertype v0.13.0 @@ -33,20 +33,45 @@ require ( gopkg.in/yaml.v2 v2.4.0 gorm.io/driver/postgres v1.5.9 gorm.io/gorm v1.25.12 + k8s.io/apimachinery v0.31.0 + k8s.io/client-go v0.31.0 + sigs.k8s.io/controller-runtime v0.19.0 ) require ( buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.34.2-20240920164238-5a7b106cbb87.2 // indirect cloud.google.com/go/compute/metadata v0.5.0 // indirect github.com/antlr4-go/antlr/v4 v4.13.0 // indirect + github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/evanphx/json-patch/v5 v5.9.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-jose/go-jose/v4 v4.0.2 // indirect - github.com/go-logr/logr v1.3.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-logr/zapr v1.3.0 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.22.4 // indirect + github.com/go-task/slim-sprig/v3 v3.0.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/google/cel-go v0.21.0 // indirect + github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af // indirect + github.com/google/uuid v1.6.0 // indirect github.com/gorilla/securecookie v1.1.2 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect + github.com/imdario/mergo v0.3.6 // 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 @@ -54,10 +79,16 @@ require ( github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.17.9 // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect @@ -71,17 +102,40 @@ require ( github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/sjson v1.2.5 // indirect - go.opentelemetry.io/otel v1.21.0 // indirect - go.opentelemetry.io/otel/metric v1.21.0 // indirect - go.opentelemetry.io/otel/sdk v1.21.0 // indirect - go.opentelemetry.io/otel/trace v1.21.0 // indirect + github.com/x448/float16 v0.8.4 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect + go.opentelemetry.io/otel v1.28.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 // indirect + go.opentelemetry.io/otel/metric v1.28.0 // indirect + go.opentelemetry.io/otel/sdk v1.28.0 // indirect + go.opentelemetry.io/otel/trace v1.28.0 // indirect + go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/goleak v1.3.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.26.0 // indirect golang.org/x/crypto v0.27.0 // indirect golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.25.0 // indirect + golang.org/x/term v0.24.0 // indirect golang.org/x/text v0.19.0 // indirect + golang.org/x/time v0.3.0 // indirect + golang.org/x/tools v0.22.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/api v0.31.0 // indirect + k8s.io/apiextensions-apiserver v0.31.0 // indirect + k8s.io/apiserver v0.31.0 // indirect + k8s.io/component-base v0.31.0 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect + k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect + sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3 // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index d9331d6..8bfe262 100644 --- a/go.sum +++ b/go.sum @@ -12,41 +12,86 @@ connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= -github.com/avast/retry-go/v4 v4.6.0 h1:K9xNA+KeB8HHc2aWFuLb25Offp+0iVRXEvFx8IinRJA= -github.com/avast/retry-go/v4 v4.6.0/go.mod h1:gvWlPhBVsvBbLkVGDg/KwvBv0bEkCOLRRSHKIr2PyOE= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/bufbuild/protovalidate-go v0.7.0 h1:MYU9GSZM7TSsWNywvyXoEc8y3kc1MNqD3k5mddIBEL4= github.com/bufbuild/protovalidate-go v0.7.0/go.mod h1:PHV5pFuWlRzdDW02/cmVyNzdiQ+RNNwo7idGxdzS7o4= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 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-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/OtI= github.com/coreos/go-oidc/v3 v3.11.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 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/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM= github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= +github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k= +github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= +github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= +github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +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/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk= github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= -github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= +github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/cel-go v0.21.0 h1:cl6uW/gxN+Hy50tNYvI691+sXxioCnstFzLp2WO4GCI= github.com/google/cel-go v0.21.0/go.mod h1:rHUlWCcBKgyEk+eV03RPdZUekPp6YcJwV0FxuUksYxc= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 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/pprof v0.0.0-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM= +github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= 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/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ= github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= +github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= +github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa h1:s+4MhCQ6YrzisK6hFJUX53drDT4UsSW3DEhKn0ifuHw= @@ -65,26 +110,49 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= +github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= +github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= +github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= +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/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI= github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= @@ -140,38 +208,87 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.akshayshah.org/attest v1.0.2 h1:qOv9PXCG2mwnph3g0I3yZj0rLAwLyUITs8nhxP+wS44= go.akshayshah.org/attest v1.0.2/go.mod h1:PnWzcW5j9dkyGwTlBmUsYpPnHG0AUPrs1RQ+HrldWO0= go.akshayshah.org/connectauth v0.6.0 h1:lyzJ3z33L2KKgfSZW95tilktE7bCgc/OTQm0V8C2//g= go.akshayshah.org/connectauth v0.6.0/go.mod h1:4dHteR5Gt7mh0weczJVRESBBWf+tmphmTIDg7R99JYQ= go.akshayshah.org/memhttp v0.1.0 h1:Enf7JeZnm+A8iRur0FYvs4ZjWa1VVMc2gG4EirG+aNE= go.akshayshah.org/memhttp v0.1.0/go.mod h1:Q1A5oqQfj2tZFRzpw0HRmmZAMzw8f3AxqOe55Afn1d8= -go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc= -go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= -go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4= -go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= -go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8= -go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= +go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= +go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 h1:qFffATk0X+HD+f1Z8lswGiOQYKHRlzfmdJm0wEaVrFA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0/go.mod h1:MOiCmryaYtc+V0Ei+Tx9o5S1ZjA7kzLucuVuyzBZloQ= +go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= +go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= +go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= +go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= go.opentelemetry.io/otel/sdk/metric v1.19.0 h1:EJoTO5qysMsYCa+w4UghwFV/ptQgqSL/8Ni+hx+8i1k= go.opentelemetry.io/otel/sdk/metric v1.19.0/go.mod h1:XjG0jQyFJrv2PbMvwND7LwCEhsJzCzV5210euduKcKY= -go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc= -go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= +go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= +go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= 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.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 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/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw= golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 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.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= +golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= +gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 h1:wKguEg1hsxI2/L3hUYrpo1RVi48K+uTyzKqprwLXsb8= google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo= google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs= @@ -183,6 +300,11 @@ google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWn gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= @@ -192,3 +314,31 @@ gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8= gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI= gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= +k8s.io/api v0.31.0 h1:b9LiSjR2ym/SzTOlfMHm1tr7/21aD7fSkqgD/CVJBCo= +k8s.io/api v0.31.0/go.mod h1:0YiFF+JfFxMM6+1hQei8FY8M7s1Mth+z/q7eF1aJkTE= +k8s.io/apiextensions-apiserver v0.31.0 h1:fZgCVhGwsclj3qCw1buVXCV6khjRzKC5eCFt24kyLSk= +k8s.io/apiextensions-apiserver v0.31.0/go.mod h1:b9aMDEYaEe5sdK+1T0KU78ApR/5ZVp4i56VacZYEHxk= +k8s.io/apimachinery v0.31.0 h1:m9jOiSr3FoSSL5WO9bjm1n6B9KROYYgNZOb4tyZ1lBc= +k8s.io/apimachinery v0.31.0/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/apiserver v0.31.0 h1:p+2dgJjy+bk+B1Csz+mc2wl5gHwvNkC9QJV+w55LVrY= +k8s.io/apiserver v0.31.0/go.mod h1:KI9ox5Yu902iBnnyMmy7ajonhKnkeZYJhTZ/YI+WEMk= +k8s.io/client-go v0.31.0 h1:QqEJzNjbN2Yv1H79SsS+SWnXkBgVu4Pj3CJQgbx0gI8= +k8s.io/client-go v0.31.0/go.mod h1:Y9wvC76g4fLjmU0BA+rV+h2cncoadjvjjkkIGoTLcGU= +k8s.io/component-base v0.31.0 h1:/KIzGM5EvPNQcYgwq5NwoQBaOlVFrghoVGr8lG6vNRs= +k8s.io/component-base v0.31.0/go.mod h1:TYVuzI1QmN4L5ItVdMSXKvH7/DtvIuas5/mm8YT3rTo= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3 h1:2770sDpzrjjsAtVhSeUFseziht227YAWYHLGNM8QPwY= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= +sigs.k8s.io/controller-runtime v0.19.0 h1:nWVM7aq+Il2ABxwiCizrVDSlmDcshi9llbaFbC0ji/Q= +sigs.k8s.io/controller-runtime v0.19.0/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= From 961e21a178834fa94cf4ca921d17d70ff3cf0e47 Mon Sep 17 00:00:00 2001 From: Yuvraj Date: Wed, 16 Oct 2024 01:38:00 +0530 Subject: [PATCH 10/17] added k8s package --- pkg/k8s/k8s.go | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 pkg/k8s/k8s.go diff --git a/pkg/k8s/k8s.go b/pkg/k8s/k8s.go new file mode 100644 index 0000000..5f486d2 --- /dev/null +++ b/pkg/k8s/k8s.go @@ -0,0 +1,91 @@ +package k8s + +import ( + "context" + "os" + v1 "task/controller/api/v1" + + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" +) + +type k8s struct { + kubeconfigPath string + client *kubernetes.Clientset +} + +// Add the following function to create a Kubernetes client for local and in-cluster setup +func NewK8sClient(kubeconfigPath string) (*k8s, error) { + var config *rest.Config + var err error + + // In-cluster config + if os.Getenv("KUBERNETES_SERVICE_HOST") != "" { + config, err = rest.InClusterConfig() + if err != nil { + return nil, err + } + } else { // Local config + config, err = clientcmd.BuildConfigFromFlags("", kubeconfigPath) + if err != nil { + return nil, err + } + } + + return &k8s{ + kubeconfigPath: kubeconfigPath, + client: kubernetes.NewForConfigOrDie(config), + }, nil +} + +// CreateTask creates a new Task resource in the Kubernetes cluster +func (k *k8s) CreateTask(task *v1.Task) (*v1.Task, error) { + tasksClient := k.client.RESTClient(). + Post(). + Resource("tasks"). + Namespace(task.Namespace). + Body(task) + + result := &v1.Task{} + err := tasksClient.Do(context.TODO()).Into(result) + return result, err +} + +// GetTask retrieves a Task resource from the Kubernetes cluster +func (k *k8s) GetTask(namespace, name string) (*v1.Task, error) { + result := &v1.Task{} + err := k.client.RESTClient(). + Get(). + Resource("tasks"). + Namespace(namespace). + Name(name). + Do(context.TODO()). + Into(result) + return result, err +} + +// UpdateTask updates an existing Task resource in the Kubernetes cluster +func (k *k8s) UpdateTask(task *v1.Task) (*v1.Task, error) { + result := &v1.Task{} + err := k.client.RESTClient(). + Put(). + Resource("tasks"). + Namespace(task.Namespace). + Name(task.Name). + Body(task). + Do(context.TODO()). + Into(result) + return result, err +} + +// DeleteTask deletes a Task resource from the Kubernetes cluster +func (k *k8s) DeleteTask(namespace, name string) error { + return k.client.RESTClient(). + Delete(). + Resource("tasks"). + Namespace(namespace). + Name(name). + Do(context.TODO()). + Error +} From 6f7e0a58e38d86462a322b038888082c5757b48c Mon Sep 17 00:00:00 2001 From: Yuvraj Date: Wed, 16 Oct 2024 01:48:25 +0530 Subject: [PATCH 11/17] added k8s package --- cli/cmd/agent.go | 76 +++++++++++++-------------------- controller/api/v1/task_types.go | 2 +- pkg/k8s/k8s.go | 18 ++++---- 3 files changed, 39 insertions(+), 57 deletions(-) diff --git a/cli/cmd/agent.go b/cli/cmd/agent.go index 1932401..91696e2 100644 --- a/cli/cmd/agent.go +++ b/cli/cmd/agent.go @@ -6,12 +6,16 @@ import ( "log/slog" "net/http" "sync" + taskApi "task/controller/api/v1" v1 "task/pkg/gen/cloud/v1" "task/pkg/gen/cloud/v1/cloudv1connect" + k8s "task/pkg/k8s" "task/pkg/plugins" "task/pkg/x" "time" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "connectrpc.com/connect" "github.com/spf13/cobra" ) @@ -68,7 +72,10 @@ func runStreamConnection(ctx context.Context, wg *sync.WaitGroup, logger *slog.L var err error client := cloudv1connect.NewTaskManagementServiceClient(http.DefaultClient, "http://localhost:8080") - + k8sClient, err := k8s.NewK8sClient("") + if err != nil { + return fmt.Errorf("failed to create k8s client: %w", err) + } go sendPeriodicRequests(ctx, logger, client) // Pass stream as a pointer stream, err := client.PullEvents(ctx, connect.NewRequest(&v1.PullEventsRequest{})) @@ -82,7 +89,7 @@ func runStreamConnection(ctx context.Context, wg *sync.WaitGroup, logger *slog.L return fmt.Errorf("failed to receive response: %w", err) } - go processWork(ctx, stream.Msg(), logger) + go processWork(ctx, stream.Msg(), logger, k8sClient) } return nil @@ -111,51 +118,26 @@ func sendPeriodicRequests(ctx context.Context, logger *slog.Logger, client cloud } } -func processWork(ctx context.Context, task *v1.PullEventsResponse, logger *slog.Logger) { - maxAttempts := 3 - initialBackoff := 1 * time.Second - - var finalStatus v1.TaskStatusEnum - var finalMessage string - - for attempt := 1; attempt <= maxAttempts; attempt++ { - // Update status to Running for each attempt - - runningMessage := fmt.Sprintf("Running attempt %d of %d", attempt, maxAttempts) - if err := updateTaskStatus(ctx, int64(task.Work.Task.Id), v1.TaskStatusEnum_RUNNING, runningMessage); err != nil { - logger.Error("Failed to send running status update", "error", err, "task_id", task.Work.Task.Id, "attempt", attempt) - } - - _, message, err := processWorkflowUpdate(ctx, task, logger) - - if err != nil { - failedMessage := fmt.Sprintf("Attempt %d failed: %v", attempt, err) - if err := updateTaskStatus(ctx, int64(task.Work.Task.Id), v1.TaskStatusEnum_FAILED, failedMessage); err != nil { - logger.Error("Failed to send failed status update", "error", err, "task_id", task.Work.Task.Id, "attempt", attempt) - } - - if attempt == maxAttempts { - finalStatus = v1.TaskStatusEnum_FAILED - finalMessage = fmt.Sprintf("All %d attempts failed. Last error: %v", maxAttempts, err) - } else { - // Wait before the next attempt - select { - case <-ctx.Done(): - return - case <-time.After(initialBackoff * time.Duration(1< Date: Wed, 16 Oct 2024 01:51:22 +0530 Subject: [PATCH 12/17] more changes --- cli/cmd/agent.go | 8 ++++---- controller/api/v1/task_types.go | 2 +- server/route/task.go | 10 +++++++--- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/cli/cmd/agent.go b/cli/cmd/agent.go index 91696e2..909ef58 100644 --- a/cli/cmd/agent.go +++ b/cli/cmd/agent.go @@ -95,14 +95,14 @@ func runStreamConnection(ctx context.Context, wg *sync.WaitGroup, logger *slog.L return nil } -// sendPeriodicRequests sends periodic RunCommand requests to the server. +// sendPeriodicRequests sends periodic heartbeat requests to the server. func sendPeriodicRequests(ctx context.Context, logger *slog.Logger, client cloudv1connect.TaskManagementServiceClient) { - ticker := time.NewTicker(5 * time.Second) defer ticker.Stop() for { select { case <-ctx.Done(): + logger.Info("Stopping periodic requests due to context cancellation") return case <-ticker.C: _, err := client.Heartbeat(ctx, connect.NewRequest(&v1.HeartbeatRequest{ @@ -110,10 +110,10 @@ func sendPeriodicRequests(ctx context.Context, logger *slog.Logger, client cloud })) if err != nil { - logger.Error("Error sending request", "error", err) + logger.Error("Error sending heartbeat request", "error", err) continue } - logger.Debug("Sent periodic RunCommand request") + logger.Debug("Sent periodic heartbeat request") } } } diff --git a/controller/api/v1/task_types.go b/controller/api/v1/task_types.go index 48467c8..ee3edb5 100644 --- a/controller/api/v1/task_types.go +++ b/controller/api/v1/task_types.go @@ -62,7 +62,7 @@ type Payload struct { // TaskStatus defines the observed state of Task type TaskStatus struct { // Status is the current status of the task. - Status string `json:"status,omitempty"` + Status int32 `json:"status,omitempty"` // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster // Important: Run "make" to regenerate code after modifying this file diff --git a/server/route/task.go b/server/route/task.go index 561f020..60b7f62 100644 --- a/server/route/task.go +++ b/server/route/task.go @@ -90,13 +90,15 @@ func newTaskMetrics() *taskMetrics { // NewTaskServer creates and returns a new instance of TaskServer. // It initializes the validator, sets up the logger, and configures metrics. +// The maxWorkers parameter can be configured to control the number of concurrent workers. func NewTaskServer(repo interfaces.TaskManagmentInterface) cloudv1connect.TaskManagementServiceHandler { + // Initialize the validator for request validation validator, err := protovalidate.New() if err != nil { log.Fatalf("Failed to initialize validator: %v", err) } - maxWorkers := 500 // You can make this configurable + maxWorkers := 500 // Configurable maximum number of concurrent workers server := &TaskServer{ taskRepo: repo.TaskRepo(), historyRepo: repo.TaskHistoryRepo(), @@ -104,7 +106,7 @@ func NewTaskServer(repo interfaces.TaskManagmentInterface) cloudv1connect.TaskMa validator: validator, metrics: newTaskMetrics(), maxWorkers: maxWorkers, - heartbeatTimeout: 30 * time.Second, // Configurable timeout + heartbeatTimeout: 30 * time.Second, // Configurable timeout for heartbeats } server.logger.Println("TaskServer initialized successfully") @@ -120,6 +122,7 @@ func (s *TaskServer) CreateTask(ctx context.Context, req *connect.Request[v1.Cre s.metrics.createTaskCounter.Inc() s.logger.Printf("Creating task: name=%s, type=%s", req.Msg.Name, req.Msg.GetType()) + // Validate the incoming request if err := s.validateRequest(req.Msg); err != nil { s.logger.Printf("CreateTask validation failed: %v", err) return nil, err @@ -127,6 +130,7 @@ func (s *TaskServer) CreateTask(ctx context.Context, req *connect.Request[v1.Cre newTask := s.prepareNewTask(req.Msg) + // Attempt to create the task in the repository createdTask, err := s.taskRepo.CreateTask(ctx, newTask) if err != nil { s.metrics.errorCounter.WithLabelValues("create_task").Inc() @@ -137,7 +141,7 @@ func (s *TaskServer) CreateTask(ctx context.Context, req *connect.Request[v1.Cre return connect.NewResponse(&v1.CreateTaskResponse{Id: int32(createdTask.ID)}), nil } -// GetTask retrieves the status of a task. +// GetTask retrieves the status of a task by its ID. func (s *TaskServer) GetTask(ctx context.Context, req *connect.Request[v1.GetTaskRequest]) (*connect.Response[v1.Task], error) { timer := prometheus.NewTimer(s.metrics.taskDuration.WithLabelValues("get_task")) defer timer.ObserveDuration() From 9fe358df31fd610c474089da43c751f8b284e23f Mon Sep 17 00:00:00 2001 From: Yuvraj Date: Wed, 16 Oct 2024 01:52:07 +0530 Subject: [PATCH 13/17] more changes --- server/route/task.go | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/server/route/task.go b/server/route/task.go index 60b7f62..218ff3a 100644 --- a/server/route/task.go +++ b/server/route/task.go @@ -149,10 +149,12 @@ func (s *TaskServer) GetTask(ctx context.Context, req *connect.Request[v1.GetTas s.metrics.getTaskCounter.Inc() s.logger.Printf("Retrieving task: id=%d", req.Msg.Id) + // Validate the incoming request if err := s.validateRequest(req.Msg); err != nil { return nil, err } + // Fetch the task from the repository taskResponse, err := s.taskRepo.GetTaskByID(ctx, uint(req.Msg.Id)) if err != nil { s.metrics.errorCounter.WithLabelValues("get_task").Inc() @@ -163,7 +165,7 @@ func (s *TaskServer) GetTask(ctx context.Context, req *connect.Request[v1.GetTas return connect.NewResponse(s.convertTaskToProto(taskResponse)), nil } -// GetTaskHistory retrieves the history of a task. +// GetTaskHistory retrieves the history of a task by its ID. func (s *TaskServer) GetTaskHistory(ctx context.Context, req *connect.Request[v1.GetTaskHistoryRequest]) (*connect.Response[v1.GetTaskHistoryResponse], error) { timer := prometheus.NewTimer(s.metrics.taskDuration.WithLabelValues("get_task_history")) defer timer.ObserveDuration() @@ -171,10 +173,12 @@ func (s *TaskServer) GetTaskHistory(ctx context.Context, req *connect.Request[v1 s.metrics.getTaskHistoryCounter.Inc() s.logger.Printf("Retrieving task history: id=%d", req.Msg.Id) + // Validate the incoming request if err := s.validateRequest(req.Msg); err != nil { return nil, err } + // Fetch the task history from the repository history, err := s.historyRepo.ListTaskHistories(ctx, uint(req.Msg.Id)) if err != nil { s.metrics.errorCounter.WithLabelValues("get_task_history").Inc() @@ -187,7 +191,7 @@ func (s *TaskServer) GetTaskHistory(ctx context.Context, req *connect.Request[v1 return connect.NewResponse(&v1.GetTaskHistoryResponse{History: protoHistory}), nil } -// UpdateTaskStatus updates the status of a task. +// UpdateTaskStatus updates the status of a task and logs the operation. func (s *TaskServer) UpdateTaskStatus(ctx context.Context, req *connect.Request[v1.UpdateTaskStatusRequest]) (*connect.Response[emptypb.Empty], error) { timer := prometheus.NewTimer(s.metrics.taskDuration.WithLabelValues("update_task_status")) defer timer.ObserveDuration() @@ -195,15 +199,18 @@ func (s *TaskServer) UpdateTaskStatus(ctx context.Context, req *connect.Request[ s.metrics.updateTaskStatusCounter.Inc() s.logger.Printf("Updating task status: id=%d, status=%s", req.Msg.Id, req.Msg.Status) + // Validate the incoming request if err := s.validateRequest(req.Msg); err != nil { return nil, err } + // Update the task status in the repository if err := s.taskRepo.UpdateTaskStatus(ctx, uint(req.Msg.Id), int(req.Msg.Status)); err != nil { s.metrics.errorCounter.WithLabelValues("update_task_status").Inc() return nil, s.logError(err, "Failed to update task status: id=%d", req.Msg.Id) } + // Log the status update in the task history if err := s.createTaskStatusHistory(ctx, uint(req.Msg.Id), int(req.Msg.Status), req.Msg.Message); err != nil { s.logger.Printf("WARNING: Failed to create task status history: %v", err) } @@ -212,7 +219,7 @@ func (s *TaskServer) UpdateTaskStatus(ctx context.Context, req *connect.Request[ return connect.NewResponse(&emptypb.Empty{}), nil } -// ListTasks retrieves a list of tasks. +// ListTasks retrieves a list of tasks with pagination support. func (s *TaskServer) ListTasks(ctx context.Context, req *connect.Request[v1.TaskListRequest]) (*connect.Response[v1.TaskList], error) { timer := prometheus.NewTimer(s.metrics.taskDuration.WithLabelValues("list_tasks")) defer timer.ObserveDuration() @@ -220,6 +227,7 @@ func (s *TaskServer) ListTasks(ctx context.Context, req *connect.Request[v1.Task s.metrics.listTasksCounter.Inc() s.logger.Print("Retrieving list of tasks") + // Validate the incoming request if err := s.validateRequest(req.Msg); err != nil { return nil, err } @@ -236,6 +244,7 @@ func (s *TaskServer) ListTasks(ctx context.Context, req *connect.Request[v1.Task offset = 0 // Default offset } + // Fetch the list of tasks from the repository tasks, err := s.taskRepo.ListTasks(ctx, limit, offset, int(req.Msg.GetStatus()), req.Msg.GetType()) if err != nil { s.metrics.errorCounter.WithLabelValues("list_tasks").Inc() @@ -259,10 +268,12 @@ func (s *TaskServer) GetStatus(ctx context.Context, req *connect.Request[v1.GetS s.metrics.getTaskCounter.Inc() s.logger.Print("Retrieving task status counts") + // Validate the incoming request if err := s.validateRequest(req.Msg); err != nil { return nil, err } + // Fetch the task status counts from the repository statusCounts, err := s.taskRepo.GetTaskStatusCounts(ctx) if err != nil { s.metrics.errorCounter.WithLabelValues("get_status").Inc() @@ -281,13 +292,13 @@ func (s *TaskServer) GetStatus(ctx context.Context, req *connect.Request[v1.GetS return connect.NewResponse(response), nil } -// StreamConnection handles bidirectional streaming for task updates and assignments. +// Heartbeat handles client heartbeats to maintain connection status. func (s *TaskServer) Heartbeat(ctx context.Context, req *connect.Request[v1.HeartbeatRequest]) (*connect.Response[v1.HeartbeatResponse], error) { s.clientHeartbeats.Store("clientID", req.Msg.Timestamp) return connect.NewResponse(&v1.HeartbeatResponse{}), nil } -// StreamConnection handles bidirectional streaming for task updates and assignments. +// PullEvents handles bidirectional streaming for task updates and assignments. func (s *TaskServer) PullEvents(ctx context.Context, req *connect.Request[v1.PullEventsRequest], stream *connect.ServerStream[v1.PullEventsResponse]) error { ticker := time.NewTicker(10 * time.Second) // Trigger every 10 seconds defer ticker.Stop() @@ -302,7 +313,6 @@ func (s *TaskServer) PullEvents(ctx context.Context, req *connect.Request[v1.Pul } for _, t := range tasks { - if err := stream.Send(&v1.PullEventsResponse{ Work: &v1.WorkAssignment{ AssignmentId: int64(t.ID), @@ -322,7 +332,7 @@ func (s *TaskServer) PullEvents(ctx context.Context, req *connect.Request[v1.Pul } } -// updateTaskStatus updates the task status and creates a history entry +// updateTaskStatus updates the task status and creates a history entry. func (s *TaskServer) updateTaskStatus(ctx context.Context, taskID uint, status v1.TaskStatusEnum, message string) error { if err := s.taskRepo.UpdateTaskStatus(ctx, taskID, int(status)); err != nil { return fmt.Errorf("failed to update task status: %w", err) @@ -335,7 +345,7 @@ func (s *TaskServer) updateTaskStatus(ctx context.Context, taskID uint, status v return nil } -// handleUpdateTaskStatus processes task status update requests +// handleUpdateTaskStatus processes task status update requests. func (s *TaskServer) handleUpdateTaskStatus(ctx context.Context, update *v1.UpdateTaskStatusRequest) error { if err := s.taskRepo.UpdateTaskStatus(ctx, uint(update.Id), int(update.Status)); err != nil { s.metrics.errorCounter.WithLabelValues("update_task_status").Inc() @@ -344,7 +354,6 @@ func (s *TaskServer) handleUpdateTaskStatus(ctx context.Context, update *v1.Upda if err := s.createTaskStatusHistory(ctx, uint(update.Id), int(update.Status), update.Message); err != nil { s.logger.Printf("WARNING: Failed to create task status history: %v", err) - // Consider whether to return an error here or continue } s.logger.Printf("Task status updated: id=%d, new status=%d", update.Id, update.Status) From 77447536b44dc78b4d42883bd34710b197270100 Mon Sep 17 00:00:00 2001 From: Yuvraj Date: Wed, 16 Oct 2024 02:06:00 +0530 Subject: [PATCH 14/17] added simple controller --- controller/cmd/main.go | 7 +- .../internal/controller/task_controller.go | 130 ++++++++++++++++-- 2 files changed, 122 insertions(+), 15 deletions(-) diff --git a/controller/cmd/main.go b/controller/cmd/main.go index 3c81f51..c2fdacc 100644 --- a/controller/cmd/main.go +++ b/controller/cmd/main.go @@ -19,6 +19,7 @@ package main import ( "crypto/tls" "flag" + "net/http" "os" // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) @@ -37,6 +38,7 @@ import ( taskiov1 "task/api/v1" "task/internal/controller" + "task/pkg/gen/cloud/v1/cloudv1connect" // +kubebuilder:scaffold:imports ) @@ -145,8 +147,9 @@ func main() { } if err = (&controller.TaskReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + cloudClient: cloudv1connect.NewTaskManagementServiceClient(http.DefaultClient, "https://localhost:8080"), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Task") os.Exit(1) diff --git a/controller/internal/controller/task_controller.go b/controller/internal/controller/task_controller.go index d3d2b07..2ac8b70 100644 --- a/controller/internal/controller/task_controller.go +++ b/controller/internal/controller/task_controller.go @@ -18,38 +18,96 @@ package controller import ( "context" + "fmt" + "time" + v1 "task/controller/api/v1" + cloudv1 "task/pkg/gen/cloud/v1" + cloudv1connect "task/pkg/gen/cloud/v1/cloudv1connect" + "task/pkg/plugins" + + "connectrpc.com/connect" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" - - taskiov1 "task/api/v1" ) // TaskReconciler reconciles a Task object type TaskReconciler struct { client.Client - Scheme *runtime.Scheme + Scheme *runtime.Scheme + cloudClient cloudv1connect.TaskManagementServiceClient } // +kubebuilder:rbac:groups=task.io,resources=tasks,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=task.io,resources=tasks/status,verbs=get;update;patch // +kubebuilder:rbac:groups=task.io,resources=tasks/finalizers,verbs=update -// Reconcile is part of the main kubernetes reconciliation loop which aims to +// Reconcile is part of the main Kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. -// TODO(user): Modify the Reconcile function to compare the state specified by -// the Task object against the actual cluster state, and then -// perform operations to make the cluster state reflect the state specified by -// the user. -// -// For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.19.0/pkg/reconcile +// This function compares the state specified by the Task object against the +// actual cluster state, and then performs operations to make the cluster +// state reflect the state specified by the user. func (r *TaskReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { _ = log.FromContext(ctx) - // TODO(user): your logic here + task := &v1.Task{} + err := r.Get(ctx, req.NamespacedName, task) + if err != nil { + log.FromContext(ctx).Error(err, "Failed to get task") + return ctrl.Result{}, err + } + + maxAttempts := 3 + initialBackoff := 1 * time.Second + + var finalStatus cloudv1.TaskStatusEnum + var finalMessage string + + for attempt := 1; attempt <= maxAttempts; attempt++ { + // Update status to Running for each attempt + runningMessage := fmt.Sprintf("Running attempt %d of %d", attempt, maxAttempts) + if err := r.updateTaskStatus(ctx, int64(task.Spec.ID), cloudv1.TaskStatusEnum_RUNNING, runningMessage); err != nil { + log.FromContext(ctx).Error(err, "Failed to update task status to Running") + return ctrl.Result{}, err + } + + _, message, err := processWorkflowUpdate(ctx, task) + + if err != nil { + failedMessage := fmt.Sprintf("Attempt %d failed: %v", attempt, err) + if err := r.updateTaskStatus(ctx, int64(task.Spec.ID), cloudv1.TaskStatusEnum_FAILED, failedMessage); err != nil { + log.FromContext(ctx).Error(err, "Failed to update task status to Failed") + return ctrl.Result{}, err + } + + if attempt == maxAttempts { + finalStatus = cloudv1.TaskStatusEnum_FAILED + finalMessage = fmt.Sprintf("All %d attempts failed. Last error: %v", maxAttempts, err) + log.FromContext(ctx).Error(fmt.Errorf(finalMessage), "Final failure after max attempts") + } else { + // Wait before the next attempt + select { + case <-ctx.Done(): + return ctrl.Result{}, ctx.Err() + case <-time.After(initialBackoff * time.Duration(1< Date: Wed, 16 Oct 2024 02:24:51 +0530 Subject: [PATCH 15/17] fixed google auth --- .env | 4 ++-- pkg/config/config.go | 1 + server/root/main.go | 15 +++++++-------- server/route/oauth2/oauth2.go | 24 ++++-------------------- 4 files changed, 14 insertions(+), 30 deletions(-) diff --git a/.env b/.env index 7a13be8..1dc24cf 100644 --- a/.env +++ b/.env @@ -12,8 +12,8 @@ DB_POOL_MAX_CONNS=50 TASK_TIME_OUT=3 # OAUTH2_ISSUER=https://dev-736553.okta.com OAUTH2_CLIENT_ID=64660401062-s9nm4vp7esak8g9a6im8c9712jkk2lbb.apps.googleusercontent.com -OAUTH2_CLIENT_SECRET=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 - +OAUTH2_CLIENT_SECRET=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 +OAUTH2_PROVIDER=google diff --git a/pkg/config/config.go b/pkg/config/config.go index df9ff45..bc69c99 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -23,6 +23,7 @@ type DatabaseConfig struct { // OAuth2Config holds the OAuth2 configuration type OAuth2Config struct { + Provider string `envconfig:"OAUTH2_PROVIDER"` Issuer string `envconfig:"OAUTH2_ISSUER"` ClientID string `envconfig:"OAUTH2_CLIENT_ID"` ClientSecret string `envconfig:"OAUTH2_CLIENT_SECRET"` diff --git a/server/root/main.go b/server/root/main.go index d3285d4..e181b95 100644 --- a/server/root/main.go +++ b/server/root/main.go @@ -115,11 +115,11 @@ func run() error { signal.Notify(exitChan, syscall.SIGINT, syscall.SIGTERM) auth, err := oauth2.NewAuthServer(oauth2.Config{ - Provider: "google", + Provider: env.OAuth2.Provider, Issuer: env.OAuth2.Issuer, ClientID: env.OAuth2.ClientID, ClientSecret: env.OAuth2.ClientSecret, - RedirectURL: "/authorization-code/callback", + RedirectURL: "http://localhost:8080/authorization-code/callback", SessionKey: "session", }) if err != nil { @@ -137,10 +137,9 @@ func run() error { // Set up gRPC middleware middleware := connectauth.NewMiddleware(func(ctx context.Context, req *connectauth.Request) (any, error) { if auth.IsAuthenticated(req) { - return AuthCtx{Username: "tqindia"}, nil + return AuthCtx{}, nil } - return AuthCtx{Username: "tqindia"}, nil - // return nil, errors.New("user is not authenticated") + return nil, errors.New("user is not authenticated") // Updated to return an error for unauthenticated users }) // Set up HTTP server @@ -149,9 +148,9 @@ func run() error { return fmt.Errorf("failed to set up handlers: %w", err) } - http.HandleFunc("/login", auth.LoginHandler) - http.HandleFunc("/authorization-code/callback", auth.AuthCodeCallbackHandler) - http.HandleFunc("/logout", auth.LogoutHandler) + mux.HandleFunc("/login", auth.LoginHandler) + mux.HandleFunc("/authorization-code/callback", auth.AuthCodeCallbackHandler) + mux.HandleFunc("/logout", auth.LogoutHandler) // Add Prometheus metrics endpoint mux.Handle("/metrics", promhttp.Handler()) diff --git a/server/route/oauth2/oauth2.go b/server/route/oauth2/oauth2.go index 5a1bb3b..87a99ab 100644 --- a/server/route/oauth2/oauth2.go +++ b/server/route/oauth2/oauth2.go @@ -49,7 +49,7 @@ func NewAuthServer(config Config) (*AuthServer, error) { var oauth2Config oauth2.Config var verifier *oidc.IDTokenVerifier - + fmt.Println("config.Provider", config.Provider) switch config.Provider { case "google": oauth2Config = oauth2.Config{ @@ -117,7 +117,7 @@ func (as *AuthServer) LoginHandler(w http.ResponseWriter, r *http.Request) { authURL := as.oauth2Config.AuthCodeURL(as.state, oidc.Nonce(generateState())) as.mu.Unlock() - json.NewEncoder(w).Encode(map[string]string{"login_url": authURL}) + http.Redirect(w, r, authURL, http.StatusFound) } // AuthCodeCallbackHandler handles the authorization code callback @@ -156,24 +156,8 @@ func (as *AuthServer) AuthCodeCallbackHandler(w http.ResponseWriter, r *http.Req return } - as.mu.Lock() - session, err := as.sessionStore.Get(r, sessionName) - as.mu.Unlock() - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - - as.mu.Lock() - session.Values["id_token"] = rawIDToken - session.Values["access_token"] = oauth2Token.AccessToken - err = session.Save(r, w) - as.mu.Unlock() - if err != nil { - http.Error(w, "Failed to save session: "+err.Error(), http.StatusInternalServerError) - return - } - - http.Redirect(w, r, "/", http.StatusFound) + // http.Redirect(w, r, "/", http.StatusFound) + w.Write([]byte(fmt.Sprintf(`{"access_token": "%s"}`, oauth2Token.AccessToken))) } // LogoutHandler handles the logout request From 8c8fe7797e9bad050fe3a3c153425fe91b34241b Mon Sep 17 00:00:00 2001 From: Yuvraj Date: Wed, 16 Oct 2024 02:30:39 +0530 Subject: [PATCH 16/17] fixed google auth --- controller/cmd/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/controller/cmd/main.go b/controller/cmd/main.go index c2fdacc..9a14e59 100644 --- a/controller/cmd/main.go +++ b/controller/cmd/main.go @@ -36,8 +36,8 @@ import ( metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" "sigs.k8s.io/controller-runtime/pkg/webhook" - taskiov1 "task/api/v1" - "task/internal/controller" + taskiov1 "task/controller/api/v1" + "task/controller/internal/controller" "task/pkg/gen/cloud/v1/cloudv1connect" // +kubebuilder:scaffold:imports ) From 9f303b1346a05813de5fb02c2b57fcf72fcbf6ce Mon Sep 17 00:00:00 2001 From: Yuvraj Date: Thu, 17 Oct 2024 01:46:46 +0530 Subject: [PATCH 17/17] fixed controller --- README.md | 95 +++---------------- cli/cmd/agent.go | 4 +- controller/cmd/main.go | 4 +- .../config/crd/bases/task.io_tasks.yaml | 92 ++++++++++++++++++ controller/config/rbac/role.yaml | 33 +++++-- .../internal/controller/task_controller.go | 4 +- pkg/k8s/k8s.go | 23 ++++- 7 files changed, 158 insertions(+), 97 deletions(-) create mode 100644 controller/config/crd/bases/task.io_tasks.yaml diff --git a/README.md b/README.md index 0c21132..081ad0b 100644 --- a/README.md +++ b/README.md @@ -39,12 +39,6 @@ make bootstrap # Run Database docker-compose up -d - -# Install river -go install github.com/riverqueue/river/cmd/river@latest - -# Run River migration (It will create the river resource in the database) -river migrate-up --database-url "postgres://admin:admin@127.0.0.1:5432/tasks?sslmode=disable" ``` @@ -75,7 +69,7 @@ Access at https://127.0.0.1:3000 ### 5. Worker (Data Plane) Start worker instances: ```bash -./bin/task-cli serve -n 10 +./bin/task-cli serve --log-level debug ``` ## Project Structure @@ -113,16 +107,20 @@ graph TD A[Dashboard Client] -->|Sends Request| B(Server) C[CLI Client] -->|Sends Request| B(Server) - %% Server and its connections - B(Server) -->|Reads/Writes| D[(PostgreSQL Database)] - B(Server) -->|Publishes Message| E(RiverQueue) - - %% RabbitMQ and Worker - E(RiverQueue) -->|Sends Message| F(Worker) - F(Worker) -->|Consumes Data| G[Executes Work] + %% Control Plane + subgraph Control Plane + B(Server) -->|Reads/Writes| D[(PostgreSQL Database)] + end - %% Optional back-and-forth communication if needed - F(Worker) -->|Update Status| B[(Server)] + %% Data Plane + subgraph Data Plane + E[Agent] -->|Initiates Connection| B[Server] + B[Server] -->|Publish W| E[Agent] + E -->|Creates CRD| H[CRD] + F[Controller] -->|Watches CRD| H + F -->|Executes Task| J[Task Execution] + F -->|Sends Status Update| B + end ``` This architecture allows for: @@ -267,20 +265,6 @@ graph TD K --> L ``` -Reconciliation Job (Run in every 10 minutes) as background job - -```mermaid -graph TD - %% Reconciliation Job Flow - subgraph Reconciliation Job - M[Start Reconciliation Job] --> N[Get List of Stuck Jobs] - N --> O{Jobs Found?} - O -->|Yes| P[Update Status: Queued] - P --> Q[Enqueue Message to River Queue] - O -->|No| R[End Reconciliation Job] - Q --> R - end -``` ## API Documentation - [Proto Docs](https://buf.build/evalsocket/cloud) @@ -568,54 +552,3 @@ kind delete cluster --name task-service This setup allows you to test the entire Task Service stack, including the server, workers, and dependencies, in a local Kubernetes environment. It's an excellent way to validate the Helm charts and ensure everything works together as expected in a Kubernetes setting. -## Future Improvements - -As we continue to evolve the Task Service, we are exploring several enhancements to improve its scalability, reliability, and management. - -### Kubernetes-Native Task Execution - -We are considering leveraging Kubernetes Custom Resource Definitions (CRDs) and custom controllers to manage task execution. This approach would enable us to fully utilize Kubernetes' scheduling and scaling capabilities. - -#### High-Level Architecture - -```mermaid -graph TD - %% Clients - A[Dashboard Client] -->|Sends Request| B(Server) - C[CLI Client] -->|Sends Request| B(Server) - - %% Control Plane - subgraph Control Plane - B(Server) -->|Reads/Writes| D[(PostgreSQL Database)] - end - - %% Data Plane - subgraph Data Plane - E[Agent] -->|Initiates Connection| B[Server] - E -->|Creates CRD| H[CRD] - F[Controller] -->|Watches CRD| H - F -->|Creates Pod for Task| I[Pod] - I -->|Executes Task| J[Task Execution] - F -->|Sends Status Update| B - end -``` - -In this architecture: - -1. Our agent initiates a streaming connection with the control plane and listens for events. -2. When a new task is created, the control plane generates an event for the agent. -3. Upon receiving the event, the agent creates a Custom Resource Definition (CRD) for the task in Kubernetes. -4. A custom Worker Controller watches for these CRDs and creates pods to execute the tasks. -5. Each task runs in its own pod, allowing for improved isolation and resource management. -6. The Worker Controller monitors task execution and sends status updates back to the server. - - -#### Design Advantages - -- **Separation of Concerns**: The customer does not need to open a port; our agent initiates the connection, and only the agent has permission to create resources inside the Data Plane. -- **Single Point of Setup**: Only the agent is required to set up the Data Plane, creating the necessary resources such as the controller, CRD, and other components. -- **Multiple Data Planes**: Customers can run multiple Data Planes with one Control Plane based on their requirements (from bare metal to any cloud). In the future, we can add functionality to route tasks to specific Data Planes as needed. -- **Security**: No sensitive information is stored in the Control Plane; we only retain metadata, ensuring enhanced security. -- **Infinite Scalability**: The architecture supports scaling as needed to accommodate varying workloads. -- **Co-location Flexibility**: Customers can run both the Data Plane and Control Plane together inside their VPC for easier management. -- **Secure Storage**: All input parameters are stored as S3 objects, with only references to these objects kept in the metadata, optimizing storage usage. diff --git a/cli/cmd/agent.go b/cli/cmd/agent.go index 909ef58..985a14e 100644 --- a/cli/cmd/agent.go +++ b/cli/cmd/agent.go @@ -72,7 +72,7 @@ func runStreamConnection(ctx context.Context, wg *sync.WaitGroup, logger *slog.L var err error client := cloudv1connect.NewTaskManagementServiceClient(http.DefaultClient, "http://localhost:8080") - k8sClient, err := k8s.NewK8sClient("") + k8sClient, err := k8s.NewK8sClient("/Users/yuvraj/.kube/config") if err != nil { return fmt.Errorf("failed to create k8s client: %w", err) } @@ -91,8 +91,6 @@ func runStreamConnection(ctx context.Context, wg *sync.WaitGroup, logger *slog.L go processWork(ctx, stream.Msg(), logger, k8sClient) } - - return nil } // sendPeriodicRequests sends periodic heartbeat requests to the server. diff --git a/controller/cmd/main.go b/controller/cmd/main.go index 9a14e59..984b990 100644 --- a/controller/cmd/main.go +++ b/controller/cmd/main.go @@ -37,7 +37,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook" taskiov1 "task/controller/api/v1" - "task/controller/internal/controller" + controller "task/controller/internal/controller" "task/pkg/gen/cloud/v1/cloudv1connect" // +kubebuilder:scaffold:imports ) @@ -149,7 +149,7 @@ func main() { if err = (&controller.TaskReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), - cloudClient: cloudv1connect.NewTaskManagementServiceClient(http.DefaultClient, "https://localhost:8080"), + CloudClient: cloudv1connect.NewTaskManagementServiceClient(http.DefaultClient, "https://localhost:8080"), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Task") os.Exit(1) diff --git a/controller/config/crd/bases/task.io_tasks.yaml b/controller/config/crd/bases/task.io_tasks.yaml new file mode 100644 index 0000000..a396d1b --- /dev/null +++ b/controller/config/crd/bases/task.io_tasks.yaml @@ -0,0 +1,92 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.1 + name: tasks.task.io +spec: + group: task.io + names: + kind: Task + listKind: TaskList + plural: tasks + singular: task + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: Task is the Schema for the tasks API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: TaskSpec defines the desired state of Task + properties: + created_at: + description: CreatedAt is the timestamp of when the task was created. + type: string + description: + description: Description is a description of the task. + type: string + id: + description: ID is the unique identifier for the task. + format: int32 + type: integer + name: + description: Name is the name of the task. + type: string + payload: + description: Payload contains task parameters. + properties: + parameters: + additionalProperties: + type: string + description: Parameters are dynamic key-value pairs for task parameters. + type: object + type: object + priority: + description: Priority is the priority level of the task. + format: int32 + type: integer + retries: + description: Retries is the number of retries attempted for this task. + format: int32 + type: integer + status: + description: Status is the current status of the task. + format: int32 + type: integer + type: + description: Type is the type of the task. + type: string + type: object + status: + description: TaskStatus defines the observed state of Task + properties: + status: + description: Status is the current status of the task. + format: int32 + type: integer + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/controller/config/rbac/role.yaml b/controller/config/rbac/role.yaml index 0dbb930..57102d0 100644 --- a/controller/config/rbac/role.yaml +++ b/controller/config/rbac/role.yaml @@ -1,11 +1,32 @@ +--- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - labels: - app.kubernetes.io/name: controller - app.kubernetes.io/managed-by: kustomize name: manager-role rules: -- apiGroups: [""] - resources: ["pods"] - verbs: ["get", "list", "watch"] +- apiGroups: + - task.io + resources: + - tasks + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - task.io + resources: + - tasks/finalizers + verbs: + - update +- apiGroups: + - task.io + resources: + - tasks/status + verbs: + - get + - patch + - update diff --git a/controller/internal/controller/task_controller.go b/controller/internal/controller/task_controller.go index 2ac8b70..fee1377 100644 --- a/controller/internal/controller/task_controller.go +++ b/controller/internal/controller/task_controller.go @@ -37,7 +37,7 @@ import ( type TaskReconciler struct { client.Client Scheme *runtime.Scheme - cloudClient cloudv1connect.TaskManagementServiceClient + CloudClient cloudv1connect.TaskManagementServiceClient } // +kubebuilder:rbac:groups=task.io,resources=tasks,verbs=get;list;watch;create;update;patch;delete @@ -121,7 +121,7 @@ func (r *TaskReconciler) SetupWithManager(mgr ctrl.Manager) error { // updateTaskStatus updates the status of a task using the Task Management Service. func (r *TaskReconciler) updateTaskStatus(ctx context.Context, taskID int64, status cloudv1.TaskStatusEnum, message string) error { - _, err := r.cloudClient.UpdateTaskStatus(ctx, connect.NewRequest(&cloudv1.UpdateTaskStatusRequest{ + _, err := r.CloudClient.UpdateTaskStatus(ctx, connect.NewRequest(&cloudv1.UpdateTaskStatusRequest{ Id: int32(taskID), Status: status, Message: message, diff --git a/pkg/k8s/k8s.go b/pkg/k8s/k8s.go index 425e18a..1d94844 100644 --- a/pkg/k8s/k8s.go +++ b/pkg/k8s/k8s.go @@ -5,14 +5,18 @@ import ( "os" v1 "task/controller/api/v1" + "k8s.io/apimachinery/pkg/runtime" // Import runtime for scheme + // Import for GroupVersionKind "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" + // Import for error handling ) type K8s struct { kubeconfigPath string client *kubernetes.Clientset + scheme *runtime.Scheme // Add scheme to K8s struct } // Add the following function to create a Kubernetes client for local and in-cluster setup @@ -33,10 +37,23 @@ func NewK8sClient(kubeconfigPath string) (*K8s, error) { } } - return &K8s{ + clientset, err := kubernetes.NewForConfig(config) + if err != nil { + return nil, err + } + + k := &K8s{ kubeconfigPath: kubeconfigPath, - client: kubernetes.NewForConfigOrDie(config), - }, nil + client: clientset, + scheme: runtime.NewScheme(), // Initialize the scheme + } + + // Register your Task type with the scheme + if err := v1.AddToScheme(k.scheme); err != nil { + return nil, err + } + + return k, nil } // CreateTask creates a new Task resource in the Kubernetes cluster