Skip to content

Commit

Permalink
Initial version. Added code, documentation and etc.
Browse files Browse the repository at this point in the history
  • Loading branch information
Nikita Sapogov authored and liderman committed Apr 18, 2022
1 parent 8a82b3c commit 700d4f2
Show file tree
Hide file tree
Showing 9 changed files with 356 additions and 1 deletion.
16 changes: 16 additions & 0 deletions .github/workflows/codecov.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: Test and coverage
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 2
- uses: actions/setup-go@v2
with:
go-version: '1.13'
- name: Run coverage
run: go test -race -coverprofile=coverage.txt -covermode=atomic
- name: Upload coverage to Codecov
run: bash <(curl -s https://codecov.io/bash)
22 changes: 22 additions & 0 deletions .github/workflows/golangci-lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: golangci-lint
on:
push:
pull_request:
workflow_dispatch:
jobs:
golangci:
strategy:
matrix:
go-version: [1.13, 1.14, 1.15, 1.16]
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
- run: go mod vendor
- name: golangci-lint
uses: golangci/golangci-lint-action@v2
with:
version: v1.40
25 changes: 25 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: test
on:
push:
pull_request:
workflow_dispatch:
jobs:
test:
strategy:
matrix:
go-version: [1.13, 1.14, 1.15, 1.16]
name: test
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
- name: run tests
run: go test -json ./... > test.json
- name: Annotate tests
if: always()
uses: guyarb/golang-test-annotations@v0.3.0
with:
test-results: test.json
21 changes: 21 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
run:
timeout: 5m
modules-download-mode: vendor
skip-files:
- '.*_test.go'

linters:
enable:
- prealloc
- dogsled
- exportloopref
- unconvert
- unparam
- whitespace
- bodyclose
- gosec
- asciicheck
- depguard
- errorlint
- goconst
- gocritic
119 changes: 118 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,118 @@
# organism
# Organism

![Lint](https://github.com/citilinkru/organism/actions/workflows/golangci-lint.yml/badge.svg?branch=master)
![Tests](https://github.com/citilinkru/organism/actions/workflows/test.yml/badge.svg?branch=master)
[![codecov](https://codecov.io/gh/citilinkru/organism/branch/master/graph/badge.svg)](https://codecov.io/gh/citilinkru/organism)
[![Go Report Card](https://goreportcard.com/badge/github.com/citilinkru/organism)](https://goreportcard.com/report/github.com/citilinkru/organism)
[![License](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/citilinkru/organism/blob/master/LICENSE)
[![GoDoc](https://godoc.org/github.com/citilinkru/organism?status.svg)](https://godoc.org/github.com/citilinkru/organism)
[![Release](https://img.shields.io/github/release/citilinkru/organism.svg?style=flat-square)](https://github.com/citilinkru/organism/releases/latest)

Abstraction for liveness and readiness probes of your app

# Description
We can describe each app as Organism, that consists of core and Limbs. Each Limb describes important part of your app,
without which app can't work properly. If each Limb is ready to work, then the whole organism ready to work too
(readiness probe). If at least one Limb is dead, then the whole organism is partially dead too.

# Example
Let's take simple http handlers funcs, to answer on readiness and liveness probes

```go
package main

import (
"github.com/citilinkru/organism"
"github.com/gorilla/mux"
"net/http"
"log"
)

func main() {
o := organism.New()
limb1 := o.GrowLimb()
go func() {
defer limb1.Die()
limb1.Ready()
err := DoSmtValuable()
if err != nil {
log.Println("something wrong with 1: ", err)
}
}()

limb2 := o.GrowLimb()
go func() {
defer limb2.Die()
limb2.Ready()
err := DoAnotherValuable()
if err != nil {
log.Println("something wrong with 2: ", err)
}
}()

o.Ready()
// ...

r := mux.NewRouter()
r.HandleFunc("/healty", ReadinessHandler(o))
r.HandleFunc("/healtz", LivenessHandler(o))
http.Handle("/", r)

// ...
}

func LivenessHandler(o *organism.Organism) http.HandlerFunc {
return func(writer http.ResponseWriter, request *http.Request) {
if !o.IsAlive() {
return
}

_, err := writer.Write([]byte("OK"))
if err != nil {
writer.WriteHeader(http.StatusInternalServerError)
}
}
}

func ReadinessHandler(o *organism.Organism) http.HandlerFunc {
return func(writer http.ResponseWriter, request *http.Request) {
if !o.IsReady() {
return
}

_, err := writer.Write([]byte("OK"))
if err != nil {
writer.WriteHeader(http.StatusInternalServerError)
}
}
}
```

Testing
-----------
Unit-tests:
```bash
go test -v -race ./...
```

Run linter:
```bash
go mod vendor \
&& docker run --rm -v $(pwd):/app -w /app golangci/golangci-lint:v1.40 golangci-lint run -v \
&& rm -R vendor
```

CONTRIBUTE
-----------
* write code
* run `go fmt ./...`
* run all linters and tests (see above)
* create a PR describing the changes

LICENSE
-----------
MIT

AUTHOR
-----------
Nikita Sapogov <amstaffix@gmail.com>
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/citilinkru/organism

go 1.13

require github.com/stretchr/testify v1.7.0
11 changes: 11 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
81 changes: 81 additions & 0 deletions organism.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package organism

// New create new organism
func New() *Organism {
o := &Organism{}
o.core = o.GrowLimb()
return o
}

// Organism is your application, it has core and limbs. Each Limb describes important part of your app, and if this part
// is dead, then organism is partially dead too (liveness probe). If each Limb of Organism, and the core too, are ready
// to work, then whole Organism ready too work too (readiness probe)
type Organism struct {
limbs []*Limb
core *Limb
}

// GrowLimb grow new Limb for Organism and returns it
func (o *Organism) GrowLimb() *Limb {
limb := newLimb()
o.limbs = append(o.limbs, limb)

return limb
}

func (o *Organism) IsReady() bool {
for _, l := range o.limbs {
if !l.IsReady() {
return false
}
}

return true
}

func (o *Organism) IsAlive() bool {
for _, l := range o.limbs {
if !l.IsAlive() {
return false
}
}

return true
}

// Ready marks core of Organism as ready
func (o *Organism) Ready() {
o.core.Ready()
}

// Ready marks core of Organism as dead
func (o *Organism) Die() {
o.core.Die()
}

func newLimb() *Limb {
return &Limb{isAlive: true}
}

type Limb struct {
isReady bool
isAlive bool
}

// Ready marks Limb as ready
func (l *Limb) Ready() {
l.isReady = true
}

// Die marks Limb as dead (not alive)
func (l *Limb) Die() {
l.isAlive = false
}

func (l *Limb) IsReady() bool {
return l.isReady
}

func (l *Limb) IsAlive() bool {
return l.isAlive
}
57 changes: 57 additions & 0 deletions organism_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package organism

import (
"github.com/stretchr/testify/assert"
"testing"
)

func TestNewOrganism(t *testing.T) {
o := New()
assert.True(t, o.IsAlive())
assert.False(t, o.IsReady())

o.Ready()

assert.True(t, o.IsReady())

o.Die()

assert.False(t, o.IsAlive())
assert.True(t, o.IsReady())
}

func TestOrganism_GrowLimb_One(t *testing.T) {
o := New()
limb := o.GrowLimb()

assert.True(t, limb.IsAlive())
assert.False(t, limb.IsReady())

limb.Ready()

assert.True(t, limb.IsAlive())
assert.True(t, limb.IsReady())
assert.True(t, o.IsAlive())
assert.False(t, o.IsReady())

o.Ready()

assert.True(t, limb.IsAlive())
assert.True(t, limb.IsReady())
assert.True(t, o.IsAlive())
assert.True(t, o.IsReady())

limb.Die()

assert.False(t, limb.IsAlive())
assert.True(t, limb.IsReady())
assert.False(t, o.IsAlive())
assert.True(t, o.IsReady())

o.Die()

assert.False(t, limb.IsAlive())
assert.True(t, limb.IsReady())
assert.False(t, o.IsAlive())
assert.True(t, o.IsReady())
}

0 comments on commit 700d4f2

Please sign in to comment.