Skip to content

Commit

Permalink
Add golock program
Browse files Browse the repository at this point in the history
Create a `golock` program this is based on the following:
  https://github.com/kvz/cronlock

This is because the above hasn't been maintained for many years, and I
need something that does the equivalent but to be able to talk to Redis
servers that enforce TLS encryption.

Add `util.GenEnv()`, `util.GetEnvBool()`, and `util.GetEnvInt()`
functions to get values from the environment variables and return them
in a certain type. If the environment variables are not set, then the
default value to be returned is passed to these functions.

Add a `util.Run()` function to execute a command with an optional
timeout so that it will be killed if it exceeds that runtime.
  • Loading branch information
jim-barber-he committed Sep 21, 2024
1 parent 13f2ddc commit 050a873
Show file tree
Hide file tree
Showing 13 changed files with 995 additions and 7 deletions.
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* @jim-barber-he
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@

# My Go repository

## Introduction

This repository was created for me to use as I'm learning to write Go.
I decided that the best way for me to learn it was to write some utilities that will be useful for me in my line of work.

## Utilities

The utilities in this repository are:

- [golock](golock/) Creates locks in a [Redis](https://redis.io/) server to coordinate running a command on distributed servers.
- [kubectl-n](kubectl-plugins/kubectl-n) A [kubectl](https://kubernetes.io/docs/reference/kubectl/) plugin that is an alternate
version of `kubectl get nodes`.
- [kubectl-p](kubectl-plugins/kubectl-p) A [kubectl](https://kubernetes.io/docs/reference/kubectl/) plugin that is an alternate
version of `kubectl get pods`.
- [ssm](ssm/) A tool for managing AWS SSM parameters.

## Go Libraries

The following libraries in this repository were written to implement the utilities above.

- [aws](aws/) Implements functions to interact with Amazon Web Services.
- [k8s](k8s/) Implements functions to interact with Kubernetes clusters.
- [texttable](texttable/) Implements functions for handling outputting a text based table.
- [util](util/) Implements various utility functions.
8 changes: 3 additions & 5 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
# Things to do

* Add README.md
* Make repo public.
* Add JSON output for the `get` and `list` commands for `ssm`.
* Improve tests and add tests for the AWS stuff once I work out how to mock them.
* `ssm` command to handle advanced parameters; policies; intelligent tiering.
* Add `--no-value` parameter to `ssm list` to return just the names of parameters.
* `ssm` command to handle advanced parameters; policies; and intelligent tiering.
* Implement `ssm copy` command.
* Command line completion for the --namespace option in k8s commands.
* Improve / add tests and add tests for the AWS stuff once I work out how to mock them.
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/jim-barber-he/go

go 1.23.0
go 1.23.1

require (
github.com/MakeNowJust/heredoc/v2 v2.0.1
Expand All @@ -11,6 +11,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.2
github.com/aws/aws-sdk-go-v2/service/sts v1.30.1
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
github.com/redis/go-redis/v9 v9.6.1
github.com/spf13/cobra v1.8.1
github.com/spf13/pflag v1.0.5
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
Expand All @@ -29,7 +30,9 @@ require (
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.15 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.22.1 // indirect
github.com/aws/smithy-go v1.20.3 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
github.com/go-logr/logr v1.4.1 // indirect
Expand Down
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,19 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.30.1 h1:+woJ607dllHJQtsnJLi52ycuqHMw
github.com/aws/aws-sdk-go-v2/service/sts v1.30.1/go.mod h1:jiNR3JqT15Dm+QWq2SRgh0x0bCNSRP2L25+CqPNpJlQ=
github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE=
github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/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/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84=
Expand Down Expand Up @@ -104,6 +112,8 @@ 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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4=
github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
Expand Down
29 changes: 29 additions & 0 deletions golock/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
ARCH := $(shell uname -s | tr '[A-Z]' '[a-z]')
BINARY_NAME=golock

.PHONY: all build clean install lint run vet

all: vet lint build install

build:
GOARCH=amd64 GOOS=darwin go build -o ${BINARY_NAME}-darwin -race main.go
GOARCH=amd64 GOOS=linux go build -o ${BINARY_NAME}-linux -race main.go

clean:
go clean
rm -f ${BINARY_NAME}-darwin ${BINARY_NAME}-linux

install:
go install -ldflags='-s'

lint:
golangci-lint run

lintall:
golangci-lint run --enable-all

run: build
./${BINARY_NAME}-${ARCH}

vet:
go vet
100 changes: 100 additions & 0 deletions golock/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@

# golock

## Introduction

golock uses a central Redis server to globally coordinate the running of a job across a distributed system.
This is useful where you only want a job to run once, but want to schedule it on multiple servers for redundancy.

Also by only allowing one job to run at a time, it can stop jobs that are scheduled to frequently run, from overlapping
themselves.

It is a cronlock replacement, based on https://github.com/kvz/cronlock
It creates locks with the same names as cronlock does, and uses many of the same environment variables.

The purpose of writing golock is because cronlock does not support Redis servers where TLS is enforced.
And also it is an exercise in me learning Golang.

## Options.

- `CRONLOCK_HOST` the Redis hostname. default: `localhost`
- `CRONLOCK_PORT` the Redis port. default: `6379`
- `CRONLOCK_AUTH` the Redis auth password. default: Not present
- `CRONLOCK_DB` the Redis database. default: `0`
- `CRONLOCK_REDIS_TIMEOUT` the length of time to wait for a response from Redis before considering it in an errored state.
This ensures that if the Redis connection goes away that we don't wait forever waiting for a response. default: `30`
- `CRONLOCK_GRACE` determines how many seconds a lock should at least persist.
Prevents fast running jobs scheduled on multiple servers with some clock drift, from executing multiple times.
- `CRONLOCK_RELEASE` determines how long a lock can persist at most.
Acts as a failsafe so there can be no locks that persist forever in case of failure. default is a day: `86400`
- `CRONLOCK_RECONNECT_ATTEMPTS` the number of times to try to reconnect before erroring.
If the Redis connection is closed, attempt to reconnect upto this amount of times. default: `5`
- `CRONLOCK_RECONNECT_BACKOFF` the length of time to increase the wait between reconnects.
Acts as a failsafe to allow Redis to be started before trying to reconnect.
Set to 0 to retry the connection immediately. default: `5`
- `CRONLOCK_KEY` a unique key for this command in the global Redis server. default: an md5 hash of golock's arguments.
- `CRONLOCK_PREFIX` Redis key prefix used by all keys. default: `cronlock`
- `CRONLOCK_VERBOSE` set to `yes` to print debug messages. default: `no`
- `CRONLOCK_TIMEOUT` how long the command can run before it gets issued a `kill -9`. default: `0`; no timeout
- `CRONLOCK_TLS` use TLS to connect to Redis. default `false`
- `CRONLOCK_TLS_SKIP_VERIFY` donot verify TLS certificates when using TLS connections. default `false`;
certificates are verified.
- `CRONLOCK_RESET` removes the lock and exits immediately. Needs to golock arguments passed in order to remove the right lock.

## Exit Codes

- = `200` Success (delete succeeded or lock not acquired, but normal execution)
- = `201` Failure (cronlock error)
- = `202` Failure (cronlock timeout)
- < `200` Success (acquired lock, executed your command), passes the exit code of your command

## Examples

### Single server

```
* * * * * golock command.sh
```
The above crontab entry launches `command.sh` every minute.
If the previous `command.sh` has not finished yet, another is not started.
This works on one server since the default `CRONLOCK_HOST` of `localhost` is used.

### Distributed

```
00 08 * * * CRONLOCK_HOST=redis.example.com golock command.sh
```
In this configuration, a central Redis server is used to track the locking for `command.sh`.
If this crontab entry was on many servers, just one instance of `command.sh` would be run each morning.

### Lock commands that have different arguments

By default golock uses your command and its arguments to make a unique identifier by which the global lock is acquired.
However if you want to run `ls -al` or `ls -a`, but just one instance of either, you'll need to provide your own key:
```
# One of two will be executed because they share the same KEY
* * * * * CRONLOCK_KEY="ls" golock ls -al
* * * * * CRONLOCK_KEY="ls" golock ls -a
```

### Per application

If you use the same command and Redis server for multiple applications and you need them to run without impacting each other,
use the `CRONLOCK_PREFIX`:
```
# Crontab for app1
* * * * * CRONLOCK_PREFIX="lock.app1." cronlock command.sh
```
```
# Crontab for app2
* * * * * CRONLOCK_PREFIX="lock.app2." cronlock command.sh
```
Now `command.sh` will be able to run for each application at the same time, because even though `command.sh` will produce the same
md5 hash for both, the key's prefix makes it unique.

## Local Redis for testing

A quick way of getting a Redis server locally if you have Docker available is to run:
```shell
docker run --rm -it -p 6379:6379 redis:latest
```
Loading

0 comments on commit 050a873

Please sign in to comment.