From 1ec8c45c26e3550ee47f65df726014120442210d Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Sun, 23 Nov 2025 00:33:36 +0700 Subject: [PATCH] feat: init using claude --- couchbase/.env.example | 4 +- couchbase/.gitignore | 4 - couchbase/Dockerfile | 40 ----- couchbase/README.md | 23 +-- couchbase/go.mod | 25 +-- couchbase/go.sum | 56 ++++--- couchbase/main.go | 339 +++++++++++++++++++++++++++-------------- 7 files changed, 270 insertions(+), 221 deletions(-) delete mode 100644 couchbase/Dockerfile diff --git a/couchbase/.env.example b/couchbase/.env.example index 5a27ba6..58b8a81 100644 --- a/couchbase/.env.example +++ b/couchbase/.env.example @@ -3,4 +3,6 @@ COUCHBASE_USERNAME=your_couchbase_username COUCHBASE_PASSWORD=your_couchbase_password COUCHBASE_BUCKET_NAME=couchbase-keepalive COUCHBASE_SCOPE_NAME=development -COUCHBASE_COLLECTION_NAME=keepalive + +INTERVAL_SECONDS=60 +TIMEOUT_SECONDS=86400 diff --git a/couchbase/.gitignore b/couchbase/.gitignore index 089a213..aaadf73 100644 --- a/couchbase/.gitignore +++ b/couchbase/.gitignore @@ -1,7 +1,3 @@ -.idea - - - # If you prefer the allow list template instead of the deny list, see community template: # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore # diff --git a/couchbase/Dockerfile b/couchbase/Dockerfile deleted file mode 100644 index 00895c4..0000000 --- a/couchbase/Dockerfile +++ /dev/null @@ -1,40 +0,0 @@ -ARG GO_VERSION=1.24.10 -FROM --platform=$BUILDPLATFORM golang:${GO_VERSION} AS build -WORKDIR /src - -RUN --mount=type=cache,target=/go/pkg/mod/ \ - --mount=type=bind,source=go.sum,target=go.sum \ - --mount=type=bind,source=go.mod,target=go.mod \ - go mod download -x - -ARG TARGETARCH - -RUN --mount=type=cache,target=/go/pkg/mod/ \ - --mount=type=bind,target=. \ - CGO_ENABLED=0 GOARCH=$TARGETARCH go build -o /bin/server . - -FROM alpine:latest AS final - -RUN --mount=type=cache,target=/var/cache/apk \ - apk --update add \ - ca-certificates \ - tzdata \ - && \ - update-ca-certificates - -ARG UID=10001 -RUN adduser \ - --disabled-password \ - --gecos "" \ - --home "/nonexistent" \ - --shell "/sbin/nologin" \ - --no-create-home \ - --uid "${UID}" \ - appuser -USER appuser - -COPY --from=build /bin/server /bin/ - -EXPOSE 1999 - -ENTRYPOINT [ "/bin/server" ] diff --git a/couchbase/README.md b/couchbase/README.md index f0c970c..a363e85 100644 --- a/couchbase/README.md +++ b/couchbase/README.md @@ -1,23 +1,2 @@ # couchbase-keepalive - -> [!IMPORTANT] -> **Archived — moved to [tiennm99/db-keepalive](https://github.com/tiennm99/db-keepalive).** -> -> All six `*-keepalive` repos were consolidated into a single binary with pluggable adapters. Use `DB_TYPE=couchbase` with the new image: -> -> ```bash -> docker run -d --restart unless-stopped \ -> -e DB_TYPE=couchbase \ -> -e COUCHBASE_CONNECTION_STRING='...' -e COUCHBASE_USERNAME=... # ...etc \ -> ghcr.io/tiennm99/db-keepalive:latest -> ``` -> -> The source here is retained for git history. No further changes will land on this repo. - -## Original description - -Lightweight Go utility performing periodic pings to keep Couchbase Capella free clusters active. - -## License - -Apache-2.0 — see [LICENSE](LICENSE). +A lightweight Go utility that performs periodic randomized get/set operations to prevent Couchbase Capella free clusters from entering inactive state. diff --git a/couchbase/go.mod b/couchbase/go.mod index 6b0b192..55981d5 100644 --- a/couchbase/go.mod +++ b/couchbase/go.mod @@ -1,6 +1,8 @@ module github.com/tiennm99/couchbase-keepalive -go 1.24.10 +go 1.23.12 + +toolchain go1.24.10 require ( github.com/couchbase/gocb/v2 v2.11.1 @@ -8,7 +10,6 @@ require ( ) require ( - github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/couchbase/gocbcore/v10 v10.8.1 // indirect github.com/couchbase/gocbcoreps v0.1.4 // indirect github.com/couchbase/goprotostellar v1.0.2 // indirect @@ -19,17 +20,17 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect - go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.62.0 // indirect - go.opentelemetry.io/otel v1.41.0 // indirect - go.opentelemetry.io/otel/metric v1.41.0 // indirect - go.opentelemetry.io/otel/trace v1.41.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/net v0.48.0 // indirect - golang.org/x/sys v0.39.0 // indirect - golang.org/x/text v0.32.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect - google.golang.org/grpc v1.79.3 // indirect - google.golang.org/protobuf v1.36.10 // indirect + golang.org/x/net v0.43.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/text v0.28.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a // indirect + google.golang.org/grpc v1.74.2 // indirect + google.golang.org/protobuf v1.36.7 // indirect ) diff --git a/couchbase/go.sum b/couchbase/go.sum index c8eba6e..96e95f9 100644 --- a/couchbase/go.sum +++ b/couchbase/go.sum @@ -2,8 +2,6 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -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/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/couchbase/gocb/v2 v2.11.1 h1:xWDco7Qk/XSvGUjbUWRaXi0V35nsMijJnm4vHXN/rqY= @@ -74,24 +72,24 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= -go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.62.0 h1:rbRJ8BBoVMsQShESYZ0FkvcITu8X8QNwJogcLUmDNNw= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.62.0/go.mod h1:ru6KHrNtNHxM4nD/vd6QrLVWgKhxPYgblq4VAtNawTQ= -go.opentelemetry.io/otel v1.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c= -go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE= -go.opentelemetry.io/otel/metric v1.41.0 h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ= -go.opentelemetry.io/otel/metric v1.41.0/go.mod h1:xPvCwd9pU0VN8tPZYzDZV/BMj9CM9vs00GuBjeKhJps= -go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= -go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= -go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= -go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= -go.opentelemetry.io/otel/trace v1.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0= -go.opentelemetry.io/otel/trace v1.41.0/go.mod h1:U1NU4ULCoxeDKc09yCWdWe+3QoyweJcISEVa1RBzOis= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -120,8 +118,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn 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.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= -golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -134,12 +132,12 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= -golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 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.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= -golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -153,24 +151,22 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T 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= -gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= -gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a h1:tPE/Kp+x9dMSwUm/uM0JKK0IfdiJkwAbSMSeZBXXJXc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= -google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4= +google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= +google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= +google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/couchbase/main.go b/couchbase/main.go index e91a6fb..6f72037 100644 --- a/couchbase/main.go +++ b/couchbase/main.go @@ -2,134 +2,249 @@ package main import ( "context" + "fmt" "log" "os" - "os/signal" - "syscall" + "strconv" + "sync" "time" "github.com/couchbase/gocb/v2" "github.com/joho/godotenv" ) -func main() { - // Uncomment following line to enable logging - // gocb.SetLogger(gocb.VerboseStdioLogger()) +// Global counter to track number of documents created +var documentCounter int = 0 +var counterMutex sync.Mutex - if err := godotenv.Load(); err != nil { - log.Println("Warning: .env file not found") - } - - // Update this to your cluster details - connectionString, isExist := os.LookupEnv("COUCHBASE_CONNECTION_STRING") - if !isExist { - log.Fatal("Warning: COUCHBASE_CONNECTION_STRING not set!") - return - } - username, isExist := os.LookupEnv("COUCHBASE_USERNAME") - if !isExist { - log.Fatal("Warning: COUCHBASE_USERNAME not set!") - return - } - password, isExist := os.LookupEnv("COUCHBASE_PASSWORD") - if !isExist { - log.Fatal("Warning: COUCHBASE_PASSWORD not set!") - return - } - bucketName, isExist := os.LookupEnv("COUCHBASE_BUCKET_NAME") - if !isExist { - log.Fatal("Warning: COUCHBASE_BUCKET_NAME not set!") - return - } - scopeName, isExist := os.LookupEnv("COUCHBASE_SCOPE_NAME") - if !isExist { - log.Fatal("Warning: COUCHBASE_SCOPE_NAME not set!") - return - } - collectionName, isExist := os.LookupEnv("COUCHBASE_COLLECTION_NAME") - if !isExist { - log.Fatal("Warning: COUCHBASE_COLLECTION_NAME not set!") - return - } - - options := gocb.ClusterOptions{ - Authenticator: gocb.PasswordAuthenticator{ - Username: username, - Password: password, - }, - } - - // Sets a pre-configured profile called "wan-development" to help avoid latency issues - // when accessing Capella from a different Wide Area Network - // or Availability Zone (e.g. your laptop). - if err := options.ApplyProfile(gocb.ClusterConfigProfileWanDevelopment); err != nil { - log.Fatal(err) - } - - // Initialize the Connection - cluster, err := gocb.Connect(connectionString, options) - if err != nil { - log.Fatal(err) - } - - bucket := cluster.Bucket(bucketName) - - err = bucket.WaitUntilReady(5*time.Second, nil) - if err != nil { - log.Fatal(err) - } - - // Get a reference to the default collection, required for older Couchbase server versions - // col := bucket.DefaultCollection() - - col := bucket.Scope(scopeName).Collection(collectionName) - - ctx, cancel := context.WithCancel(context.Background()) - - go func() { - ticker := time.NewTicker(time.Minute) - defer ticker.Stop() - - for { - select { - case <-ticker.C: - if err := incrementCounter(col); err != nil { - log.Printf("Keepalive increment error: %v", err) - } - case <-ctx.Done(): - return - } - } - }() - - defer func() { - cancel() - if err := cluster.Close(nil); err != nil { - log.Printf("Error closing cluster: %v", err) - } - }() - - sigCh := make(chan os.Signal, 1) - signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) - <-sigCh +// KeepaliveDocument represents the JSON document structure for keepalive operations +type KeepaliveDocument struct { + ID string `json:"id"` + Timestamp int64 `json:"timestamp"` + Value string `json:"value"` + Operation string `json:"operation"` + Cluster string `json:"cluster"` } -func incrementCounter(col *gocb.Collection) error { - counterDocId := "counter" - docOut, err := col.Get(counterDocId, &gocb.GetOptions{}) +type Config struct { + ClusterURL string + Username string + Password string + BucketName string + ScopeName string + CollectionName string + Interval time.Duration + OperationTimeout time.Duration +} + +func main() { + // Load environment variables from .env file + err := godotenv.Load() if err != nil { - return err + log.Println("No .env file found, using system environment variables") } - var current uint64 - err = docOut.Content(¤t) + + config := loadConfig() + + // Initialize random seed + rand.Seed(time.Now().UnixNano()) + + // Connect to Couchbase cluster + cluster, err := connectToCluster(config) if err != nil { - return err + log.Fatalf("Failed to connect to cluster: %v", err) } - current++ - _, err = col.Upsert(counterDocId, current, &gocb.UpsertOptions{}) + defer cluster.Close(nil) + + // Test connection to the specified scope + err = testScopeConnection(cluster, config) if err != nil { - return err + log.Fatalf("Failed to connect to scope: %v", err) } - log.Printf("Counter : %d\n", current) + + log.Println("Successfully connected to Couchbase cluster") + log.Printf("Scope: %s, Collection: %s", config.ScopeName, config.CollectionName) + log.Printf("Keeping cluster alive with operations every %v", config.Interval) + + // Start keepalive operations + keepAlive(cluster, config) +} + +func loadConfig() Config { + var config Config + + // Load from environment variables with defaults + config.ClusterURL = getEnv("COUCHBASE_CONNECTION_STRING", "localhost") + config.Username = getEnv("COUCHBASE_USERNAME", "") + config.Password = getEnv("COUCHBASE_PASSWORD", "") + config.BucketName = getEnv("COUCHBASE_BUCKET_NAME", "default") + config.ScopeName = getEnv("COUCHBASE_SCOPE_NAME", "_default") + config.CollectionName = getEnv("COUCHBASE_COLLECTION_NAME", "_default") + + // Parse interval duration from environment variable + intervalStr := getEnv("COUCHBASE_INTERVAL", "5m") + if interval, err := time.ParseDuration(intervalStr); err == nil { + config.Interval = interval + } else { + log.Printf("Invalid interval '%s', using default 5m", intervalStr) + config.Interval = 5 * time.Minute + } + + // Parse operation timeout from environment variable + timeoutStr := getEnv("COUCHBASE_OPERATION_TIMEOUT", "10s") + if timeout, err := time.ParseDuration(timeoutStr); err == nil { + config.OperationTimeout = timeout + } else { + log.Printf("Invalid operation timeout '%s', using default 10s", timeoutStr) + config.OperationTimeout = 10 * time.Second + } + + // Validate required parameters + if config.Username == "" || config.Password == "" { + log.Fatal("COUCHBASE_USERNAME and COUCHBASE_PASSWORD are required") + } + + return config +} + +func getEnv(key, defaultValue string) string { + if value := os.Getenv(key); value != "" { + return value + } + return defaultValue +} + +func connectToCluster(config Config) (*gocb.Cluster, error) { + cluster, err := gocb.Connect(config.ClusterURL, gocb.ClusterOptions{ + Username: config.Username, + Password: config.Password, + }) + if err != nil { + return nil, fmt.Errorf("failed to create cluster connection: %w", err) + } + + // Get the bucket + bucket := cluster.Bucket(config.BucketName) + + return cluster, bucket, nil +} + +func testScopeConnection(cluster *gocb.Cluster, config Config) error { + bucket := cluster.Bucket(config.BucketName) + scope := bucket.Scope(config.ScopeName) + collection := scope.Collection(config.CollectionName) + + // Test the connection to the specific scope and collection with configured timeout + err := collection.Ping(context.Background()) + if err != nil { + return fmt.Errorf("failed to ping scope '%s', collection '%s': %w", + config.ScopeName, config.CollectionName, err) + } + return nil } + +func keepAlive(cluster *gocb.Cluster, config Config) { + ticker := time.NewTicker(config.Interval) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + err := performRandomOperation(cluster, config) + if err != nil { + log.Printf("Error performing keepalive operation: %v", err) + } else { + log.Println("Successfully performed keepalive operation") + } + } + } +} + +func performRandomOperation(cluster *gocb.Cluster, config Config) error { + bucket := cluster.Bucket(config.BucketName) + scope := bucket.Scope(config.ScopeName) + collection := scope.Collection(config.CollectionName) + + // 50% chance to perform GET or SET operation + if rand.Intn(2) == 0 { + return performGetOperation(collection, config) + } else { + return performSetOperation(collection, config) + } +} + +func getCurrentHourSeconds() string { + now := time.Now() + hourSeconds := now.Hour()*3600 + now.Minute()*60 + now.Second() + return fmt.Sprintf("%d", hourSeconds) +} + +// incrementCounter increases the document counter and returns the new value +func incrementCounter() int { + counterMutex.Lock() + defer counterMutex.Unlock() + documentCounter++ + return documentCounter +} + +// getCurrentCounter returns the current document counter value +func getCurrentCounter() int { + counterMutex.Lock() + defer counterMutex.Unlock() + return documentCounter +} + +func performGetOperation(collection *gocb.Collection, config Config) error { + // Use the current counter value to get the latest document + currentCounter := getCurrentCounter() + docID := fmt.Sprintf("%d", currentCounter) + + // Create a document struct for the Get result + var doc KeepaliveDocument + + // Get the document with configured timeout + _, err := collection.Get(docID, &gocb.GetOptions{ + Timeout: config.OperationTimeout, + }) + + // It's okay if the document doesn't exist, we're just testing the operation + if err != nil && err != gocb.ErrDocumentNotFound { + return fmt.Errorf("get operation failed: %w", err) + } + + // Try to decode the document to verify JSON structure + // We don't need the actual data, just testing the operation + _ = doc + + log.Printf("Successfully retrieved document with counter: %d", currentCounter) + return nil +} + +func performSetOperation(collection *gocb.Collection, config Config) error { + // Use the incremented counter value as document ID + newCounter := incrementCounter() + docID := fmt.Sprintf("%d", newCounter) + + // Create a structured JSON document + document := KeepaliveDocument{ + ID: docID, + Timestamp: time.Now().Unix(), + Value: fmt.Sprintf("keepalive-value-%d", newCounter), + Operation: "keepalive", + Cluster: config.ClusterURL, + } + + // Set the document with a short TTL (1 hour) and configured timeout + _, err := collection.Insert(docID, document, &gocb.InsertOptions{ + Expiry: time.Hour, + Timeout: config.OperationTimeout, + }) + + if err != nil { + return fmt.Errorf("set operation failed: %w", err) + } + + log.Printf("Successfully created document with counter: %d", newCounter) + return nil +} \ No newline at end of file