From 0fb42f87524ab87ed4bb0382f2b4ec9bb33d95d2 Mon Sep 17 00:00:00 2001 From: arisnguyenit97 Date: Sun, 16 Feb 2025 21:51:59 +0700 Subject: [PATCH] :tada: initial source: add project #1 --- .github/workflows/ci.yml | 80 +++++++++++++++ .github/workflows/ci_notify.yml | 113 +++++++++++++++++++++ .gitignore | 42 ++++++++ Makefile | 45 +++++++++ builder.go | 168 ++++++++++++++++++++++++++++++++ const.go | 1 + docs/RELEASE.md | 53 ++++++++++ go.mod | 3 + redisc.go | 51 ++++++++++ sh/git_changelog.sh | 10 ++ sh/go_deps.sh | 28 ++++++ types.go | 96 ++++++++++++++++++ 12 files changed, 690 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/ci_notify.yml create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 builder.go create mode 100644 const.go create mode 100644 docs/RELEASE.md create mode 100644 go.mod create mode 100644 redisc.go create mode 100644 sh/git_changelog.sh create mode 100644 sh/go_deps.sh create mode 100644 types.go diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..a85da73 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,80 @@ +# This workflow will build a golang project +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go + +name: Go + +on: + push: + branches: ["master"] + tags: + - "v*" + pull_request: + branches: ["master"] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + # Add Go versions as needed + go: ["1.21.x", "1.23.x"] # previous version "1.19", "1.20.x" + steps: + - uses: actions/checkout@v3 + + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: ${{ matrix.go }} + + - name: Build + run: go build -v ./... + + - name: Test + run: go test -v ./... + + create-release: + runs-on: ubuntu-latest + # Only run this job when a valid tag is pushed + if: startsWith(github.ref, 'refs/tags/v') + steps: + - name: Check if tag exists + id: check_tag + run: | + if [ -n "$GITHUB_REF" ]; then + TAG=${GITHUB_REF#refs/tags/} + # echo "::set-output name=tag::$TAG" + echo "TAG=${TAG}" >> $GITHUB_ENV + else + # echo "::set-output name=tag::" + echo "TAG=" >> $GITHUB_ENV + fi + shell: bash + + - name: Checkout repository + uses: actions/checkout@v3 + with: + # Ensure all history is fetched + fetch-depth: 0 + + - name: Apply changelog + run: chmod +x ./sh/git_changelog.sh + + - name: Generate changelog + id: changelog + run: | + CHANGELOG=$(./sh/git_changelog.sh) + echo "CHANGELOG=$CHANGELOG" >> $GITHUB_ENV + + - name: Create GitHub Release + id: create_release + uses: softprops/action-gh-release@v1 + with: + tag_name: ${{ env.TAG }} + body: | + :gem: released new version ${{ env.TAG }} + Changelog: + ${{ env.CHANGELOG }} + draft: false + prerelease: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/ci_notify.yml b/.github/workflows/ci_notify.yml new file mode 100644 index 0000000..24bf24b --- /dev/null +++ b/.github/workflows/ci_notify.yml @@ -0,0 +1,113 @@ +name: Notify + +on: + push: + branches: ["master"] + tags: + - "v*" + pull_request: + branches: ["master"] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v3 + + notify: + runs-on: ubuntu-latest + needs: build + steps: + - name: Check Secrets + id: check + run: | + if [[ -z "${{ secrets.TELEGRAM_CHAT_ID }}" || -z "${{ secrets.TELEGRAM_BOT_TOKEN }}" ]]; then + echo "::set-output name=skip::true" + else + echo "::set-output name=skip::false" + fi + - name: Send Telegram Notification + if: steps.check.outputs.skip == 'false' + uses: appleboy/telegram-action@master + with: + to: ${{ secrets.TELEGRAM_CHAT_ID }} + token: ${{ secrets.TELEGRAM_BOT_TOKEN }} + format: markdown + message: | + №Ÿš€ *AI Workflow Administrator (AWA)* + - *username*: `${{ github.actor }}` + - *message*: `${{ github.event.commits[0].message }}` + - *hash*: `${{ github.sha }}` + - *repository*: `${{ github.repository }}` + - *head*: `${{ github.event.head_commit.message }}` + №Ÿ€ *See changes*: `https://github.com/${{ github.repository }}/commit/${{github.sha}}` + + deploy: + runs-on: ubuntu-latest + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') + needs: build + steps: + - name: Checkout repository + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Check if tag exists + id: check_tag + run: | + if [ -n "$GITHUB_REF" ]; then + TAG=${GITHUB_REF#refs/tags/} + # echo "::set-output name=tag::$TAG" + echo "TAG=${TAG}" >> $GITHUB_ENV + else + # echo "::set-output name=tag::" + echo "TAG=" >> $GITHUB_ENV + fi + shell: bash + + - name: Check Secrets + id: check + run: | + if [[ -z "${{ secrets.TELEGRAM_CHAT_ID }}" || -z "${{ secrets.TELEGRAM_BOT_TOKEN }}" ]]; then + echo "::set-output name=skip::true" + else + echo "::set-output name=skip::false" + fi + + - name: Generate Changelog + id: changelog + run: | + # Generate your changelog here and set it as an output variable + CHANGELOG=$(git log --pretty=format:"%h - %s" -n 10) + echo "::set-output name=changelog::$CHANGELOG" + + - name: Create GitHub Release + id: create_release + uses: softprops/action-gh-release@v1 + with: + tag_name: ${{ env.TAG }} + body: | + :gem: released new version ${{ env.TAG }} + draft: false + prerelease: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Send Telegram Notification + if: steps.check.outputs.skip == 'false' + uses: appleboy/telegram-action@master + with: + to: ${{ secrets.TELEGRAM_CHAT_ID }} + token: ${{ secrets.TELEGRAM_BOT_TOKEN }} + format: markdown + message: | + №Ÿš€ *AI Workflow Administrator (AWA)* + - *latest tag*: *${{ env.TAG }}* + - *username*: `${{ github.actor }}` + - *hash*: `${{ github.sha }}` + - *repository*: `${{ github.repository }}` + - *head*: `${{ github.event.head_commit.message }}` + №Ÿ€ *See changes*: `https://github.com/${{ github.repository }}/releases/tag/${{ env.TAG }}` + №Ÿ“œ *Changelog*: + `${{ steps.changelog.outputs.changelog }}` diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8ebc926 --- /dev/null +++ b/.gitignore @@ -0,0 +1,42 @@ +# 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 +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test +*log + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +vendor/ + +# Go workspace file +go.work + +# vscode +.vscode/settings.json +.vscode/extensions.json + +# GoLand +.idea + +# MacOS +.DS_Store + +# Config +.env +.env.test +.env.dev +logs/ +upload/ +dir/ +config/conf-local.yaml +main/ \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..fa0b2ce --- /dev/null +++ b/Makefile @@ -0,0 +1,45 @@ +.PHONY: run build test tidy deps-upgrade deps-clean-cache + +# ============================================================================== +# Running the main application +# Executes the main.go file, useful for development and quick testing +run: + go run main/main.go + +# Building the application +# Compiles the main.go file into an executable, for production deployment +build: + go build main/main.go + +# ============================================================================== +# Module support and testing +# Runs tests across all packages in the project, showing code coverage +test: + go test -cover ./... + +# Cleaning and maintaining dependencies +# Cleans up the module by removing unused dependencies +# Copies all dependencies into the vendor directory, ensuring reproducibility +tidy: + go mod tidy + go mod vendor + +# Upgrading dependencies +# Updates all dependencies to their latest minor or patch versions +# Cleans up the module after upgrade +# Re-vendors dependencies after upgrade +deps-upgrade: + # go get $(go list -f '{{if not (or .Main .Indirect)}}{{.Path}}{{end}}' -m all) + go get -u -t -d -v ./... + go mod tidy + go mod vendor + +# Cleaning up the module cache +# Removes all items from the Go module cache +deps-clean-cache: + go clean -modcache + +# Running code coverage +# Generates code coverage report and logs the results +coverage: + sh ./sh/go_deps.sh diff --git a/builder.go b/builder.go new file mode 100644 index 0000000..f8bc527 --- /dev/null +++ b/builder.go @@ -0,0 +1,168 @@ +package redisc + +import "time" + +//т€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€О +// Getter settings +//_______________________________________________________________________ + +// IsEnabled returns true if the configuration is enabled, indicating that +// a connection to Redis should be attempted. +func (c *settings) IsEnabled() bool { + return c.enabled +} + +// IsDebugging returns true if debugging is enabled in the configuration, +// which may allow more verbose logging. +func (c *settings) IsDebugging() bool { + return c.debugging +} + +func (c *settings) Conn() *connectionSettings { + return c.conn +} + +func (c *settings) Retry() *retrySettings { + return c.retry +} + +func (c *settings) Timeout() *timeoutSettings { + return c.timeout +} + +func (c *settings) Pool() *poolSettings { + return c.pool +} + +//т€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€О +// Setter settings +//_______________________________________________________________________ + +// SetEnable sets the enabled flag in the configuration and returns the updated RConf, +// allowing for method chaining. +func (c *settings) SetEnable(value bool) *settings { + c.enabled = value + return c +} + +// SetDebug sets the debugging flag in the configuration and returns the updated RConf. +func (c *settings) SetDebug(value bool) *settings { + c.debugging = value + return c +} + +func (c *settings) SetConn(value *connectionSettings) *settings { + c.conn = value + return c +} + +func (c *settings) SetRetry(value *retrySettings) *settings { + c.retry = value + return c +} + +func (c *settings) SetTimeout(value *timeoutSettings) *settings { + c.timeout = value + return c +} + +func (c *settings) SetPool(value *poolSettings) *settings { + c.pool = value + return c +} + +//т€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€О +// Setter connectionSettings +//_______________________________________________________________________ + +func (c *connectionSettings) SetNetwork(value string) *connectionSettings { + c.network = value + return c +} + +func (c *connectionSettings) SetConnectionStrings(value string) *connectionSettings { + c.connectionStrings = value + return c +} + +func (c *connectionSettings) SetPassword(value string) *connectionSettings { + c.password = value + return c +} + +func (c *connectionSettings) SetDatabase(value int) *connectionSettings { + c.database = value + return c +} + +//т€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€О +// Setter retrySettings +//_______________________________________________________________________ + +func (r *retrySettings) SetMaxRetries(value int) *retrySettings { + r.maxRetries = value + return r +} + +func (r *retrySettings) SetMinRetryBackoff(value time.Duration) *retrySettings { + r.minRetryBackoff = value + return r +} + +func (r *retrySettings) SetMaxRetryBackoff(value time.Duration) *retrySettings { + r.maxRetryBackoff = value + return r +} + +//т€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€О +// Setter timeoutSettings +//_______________________________________________________________________ + +func (t *timeoutSettings) SetConnTimeout(value time.Duration) *timeoutSettings { + t.connTimeout = value + return t +} + +func (t *timeoutSettings) SetReadTimeout(value time.Duration) *timeoutSettings { + t.readTimeout = value + return t +} + +func (t *timeoutSettings) SetWriteTimeout(value time.Duration) *timeoutSettings { + t.writeTimeout = value + return t +} + +//т€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€От€О +// Setter poolSettings +//_______________________________________________________________________ + +func (p *poolSettings) SetPoolSize(value int) *poolSettings { + p.poolSize = value + return p +} + +func (p *poolSettings) SetMinIdleConn(value int) *poolSettings { + p.minIdleConn = value + return p +} + +func (p *poolSettings) SetMaxConnAge(value time.Duration) *poolSettings { + p.maxConnAge = value + return p +} + +func (p *poolSettings) SetPoolTimeout(value time.Duration) *poolSettings { + p.poolTimeout = value + return p +} + +func (p *poolSettings) SetIdleTimeout(value time.Duration) *poolSettings { + p.idleTimeout = value + return p +} + +func (p *poolSettings) SetIdleCheckFrequency(value time.Duration) *poolSettings { + p.idleCheckFrequency = value + return p +} diff --git a/const.go b/const.go new file mode 100644 index 0000000..e44b37f --- /dev/null +++ b/const.go @@ -0,0 +1 @@ +package redisc diff --git a/docs/RELEASE.md b/docs/RELEASE.md new file mode 100644 index 0000000..cc8a6b9 --- /dev/null +++ b/docs/RELEASE.md @@ -0,0 +1,53 @@ +# Version Releases + +## Semantic Versioning (SemVer): `vMAJOR.MINOR.PATCH` + +Example: + +- v1.0.0 +- v1.0.1 +- v1.1.1 + +## Pre-release versions: `vMAJOR.MINOR.PATCH-.` + +Example: + +- v1.0.0-beta.1 +- v1.0.0-beta.2 +- v1.2.3-rc1 +- v1.2.3-SNAPSHOT + +## Post-release versions: `vMAJOR.MINOR.PATCH-POST.` + +Example: + +- v1.2.3-post.1 +- v1.2.3-post.2 + +## Local versions: `vMAJOR.MINOR.PATCH+LOCAL` + +Example: + +- v1.0.0+local +- v1.1.0+local + +## Caret range versions: `^MAJOR.MINOR.PATCH` + +Example: + +- ^1.2.3 (similar `>=1.2.3 < 2.0.0`) + +## Tilde range versions: `~MAJOR.MINOR.PATCH` + +Example: + +- ~1.2.3 (similar `>=1.2.3 <1.3.0`) + +--- + +Notes: + +- `MAJOR`: major version. +- `MINOR`: Minor version, often adding new features. +- `PATCH`: Patch version, typically fixing bugs. +- `SNAPSHOT`: Indicates a version under development or in progress. It is often used to represent the latest state of the codebase and may include ongoing changes and features that are not yet finalized. This allows developers to work with the most recent developments in a project. diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..eab37c9 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/sivaosorg/redisc + +go 1.23.1 diff --git a/redisc.go b/redisc.go new file mode 100644 index 0000000..c3a67fd --- /dev/null +++ b/redisc.go @@ -0,0 +1,51 @@ +package redisc + +import "time" + +func NewSettings() *settings { + s := &settings{} + s. + SetRetry(newRetrySettings()). + SetTimeout(newTimeoutSettings()). + SetPool(newPoolSettings()). + SetConn(newConnSettings()) + return s +} + +func newConnSettings() *connectionSettings { + c := &connectionSettings{ + network: "tcp", // Use TCP for most connections. Use "unix" if you prefer a Unix domain socket. + database: 0, // Connects to the first logical database. Change if your application needs a different DB. + } + return c +} + +func newRetrySettings() *retrySettings { + r := &retrySettings{ + maxRetries: 3, // Three retry attempts for commands, balancing resilience and responsiveness. + minRetryBackoff: 8 * time.Millisecond, // Provides a short initial delay between retries. + maxRetryBackoff: 512 * time.Millisecond, // Caps the maximum delay between retries to prevent long waits. + } + return r +} + +func newTimeoutSettings() *timeoutSettings { + t := &timeoutSettings{ + connTimeout: 5 * time.Second, // Allows a moderate wait time when establishing a connection. + readTimeout: 3 * time.Second, // Sufficient for most environments to avoid long hangs during operations. + writeTimeout: 3 * time.Second, // Sufficient for most environments to avoid long hangs during operations. + } + return t +} + +func newPoolSettings() *poolSettings { + p := &poolSettings{ + poolSize: 10, // Supports moderate concurrency. Increase if your application has a high number of simultaneous requests. + minIdleConn: 2, // Keeps a couple of connections always ready to reduce latency. + maxConnAge: 0, // Connections are recycled indefinitely. Set a non-zero value to force periodic connection renewal. + poolTimeout: 4 * time.Second, // Wait up to 4 seconds for a free connection from the pool. + idleTimeout: 5 * time.Minute, // Closes idle connections after 5 minutes, freeing up resources. + idleCheckFrequency: 1 * time.Minute, // Checks every minute to clear out idle connections. + } + return p +} diff --git a/sh/git_changelog.sh b/sh/git_changelog.sh new file mode 100644 index 0000000..814922e --- /dev/null +++ b/sh/git_changelog.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +# Get the hash of the last tag +tag_hash_latest=$(git rev-list --tags --max-count=1) +# Get the tag name associated with that hash +tag_latest=$(git describe --tags "$tag_hash_latest") +# Get the commit messages from that tag to the current commit +CHANGELOG=$(git log "$tag_latest"..HEAD --pretty=format:"- %h %s") +# Print the changelog +echo "$CHANGELOG" diff --git a/sh/go_deps.sh b/sh/go_deps.sh new file mode 100644 index 0000000..de39da4 --- /dev/null +++ b/sh/go_deps.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + +# Set up directories and files +LOG_DIR="logs" +COVERAGE_FILE="$LOG_DIR/coverage.txt" +PROFILE_FILE="profile.out" + +# Ensure the logs directory exists +mkdir -p "$LOG_DIR" + +# Exit immediately if a command exits with a non-zero status +set -e + +# Initialize the coverage file +>"$COVERAGE_FILE" + +# Run tests with race detection and coverage for each package +for package in $(go list ./... | grep -v vendor); do + go test -race -coverprofile="$PROFILE_FILE" -covermode=atomic "$package" + + # Append profile output to the coverage file if it exists + if [ -f "$PROFILE_FILE" ]; then + cat "$PROFILE_FILE" >>"$COVERAGE_FILE" + rm "$PROFILE_FILE" + fi +done + +echo "№ŸŸЂ Coverage results written to $COVERAGE_FILE" diff --git a/types.go b/types.go new file mode 100644 index 0000000..c0e18be --- /dev/null +++ b/types.go @@ -0,0 +1,96 @@ +package redisc + +import "time" + +type settings struct { + enabled bool + debugging bool + conn *connectionSettings + retry *retrySettings + timeout *timeoutSettings + pool *poolSettings +} + +type connectionSettings struct { + // Specifies the network protocol ("tcp" or "unix"). + // When you need a standard TCP connection or a Unix domain socket. + // Default network is "tcp". Use "unix" for Unix domain sockets. + network string + + // The address of your Redis server in "host:port" format. + // Always required to locate your Redis server. + // Default address for a local Redis instance. + connectionStrings string + + // The password for authentication with the Redis server. + // Required when your Redis instance is protected by a password. + // Default is no password (set if your Redis requires authentication). + password string + + // The Redis logical database number to select (default is 0). + // Change this if your application uses a specific logical database. + // Default DB index is 0. + database int +} + +type retrySettings struct { + // Maximum number of retry attempts for a command if errors occur. + // Increase in environments with intermittent connectivity issues. + // When you want to automatically retry failed commands (e.g., due to temporary network issues). + // Try up to n times before failing a command. + maxRetries int + + // The minimum wait time between retries. + // Adjust to control the pace of retry attempts. + minRetryBackoff time.Duration + + // The maximum wait time between retry attempts. + // Prevents the backoff duration from growing excessively. + maxRetryBackoff time.Duration +} + +type timeoutSettings struct { + // Maximum duration to wait for a new connection to be established. + // Helps ensure your application fails fast if the Redis server is unreachable. + // Maximum time to wait for a connection to be established. + connTimeout time.Duration + + // Maximum duration to wait for a command response from the Redis server. + // Set according to your network conditions to avoid long blocking calls. + // Maximum time to wait for a response (per command). + readTimeout time.Duration + + // Maximum duration to wait when sending a command to the server. + // Ensures that write operations do not hang indefinitely. + // Maximum time to wait for sending a command. + writeTimeout time.Duration +} + +type poolSettings struct { + // Maximum number of connections maintained in the pool. + // Increase for high-concurrency applications. + poolSize int + + // Minimum number of idle connections to keep open. + // Helps reduce latency by always having ready-to-use connections. + // Keep at least n idle connections ready. + minIdleConn int + + // Maximum lifetime of a connection before it is closed. + // Set to recycle connections periodically (0 means no limit). + // Default is 0 means connections are reused indefinitely. + maxConnAge time.Duration + + // Maximum duration to wait for a connection if the pool is exhausted. + // Adjust to control blocking behavior under heavy load. + // Maximum time to wait for a connection if all are busy. + poolTimeout time.Duration + + // Duration after which an idle connection is closed. + // Frees up resources by closing unused connections. + idleTimeout time.Duration + + // How often the pool checks for idle connections to close. + // Balance between timely cleanup and the overhead of checks. + idleCheckFrequency time.Duration +}