replace inaccessible external lib

This commit is contained in:
Christoph Haas 2023-10-26 10:31:29 +02:00
parent b3a5f2ac60
commit dd86d0ff49
5 changed files with 360 additions and 4 deletions

View File

@ -9,7 +9,7 @@ import (
"syscall"
"time"
"git.prolicht.digital/golib/healthcheck"
"github.com/h44z/wg-portal/internal/common/healthcheck"
"github.com/h44z/wg-portal/internal/server"
"github.com/sirupsen/logrus"
)

1
go.mod
View File

@ -3,7 +3,6 @@ module github.com/h44z/wg-portal
go 1.18
require (
git.prolicht.digital/golib/healthcheck v1.1.1
github.com/evanphx/json-patch v5.6.0+incompatible
github.com/gin-contrib/sessions v0.0.5
github.com/gin-gonic/gin v1.8.2

2
go.sum
View File

@ -1,5 +1,3 @@
git.prolicht.digital/golib/healthcheck v1.1.1 h1:bdx0MuGqAq0PCooPpiuPXsr4/Ok+yfJwq8P9ITq2eLI=
git.prolicht.digital/golib/healthcheck v1.1.1/go.mod h1:wEqVrqHJ8NsSx5qlFGUlw74wJ/wDSKaA34QoyvsEkdc=
github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=

View File

@ -0,0 +1,116 @@
// source taken from https://git.prolicht.digital/golib/healthcheck/-/blob/master/healthcheck.go
package healthcheck
import (
"context"
"fmt"
"net/http"
"os"
"time"
)
type service struct {
listenAddress string
checkFunc func() int
}
type Option func(svc *service)
// New creates a new healthcheck instance that can be started with either Start() or StartWithContext().
func New(opts ...Option) *service {
svc := &service{
listenAddress: ":11223",
checkFunc: func() int {
return http.StatusOK
},
}
for _, opt := range opts {
opt(svc)
}
return svc
}
// Start starts a background goroutine with the healthcheck webserver. This goroutine is only stopped
// if the whole program is shut down.
func (s *service) Start() {
s.StartWithContext(context.Background())
}
// StartForeground starts a goroutine with the healthcheck webserver. This function will block until the context
// gets canceled or the healthcheck server crashes.
func (s *service) StartForeground(ctx context.Context) {
router := http.NewServeMux()
router.Handle("/health", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(s.checkFunc())
}))
srv := &http.Server{
Addr: s.listenAddress,
Handler: router,
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 15 * time.Second,
}
srvContext, cancelFn := context.WithCancel(ctx)
go func() {
if err := srv.ListenAndServe(); err != nil {
fmt.Printf("[HEALTHCHECK] web service on %s exited: %v\n", s.listenAddress, err)
cancelFn()
}
}()
// Wait for the main context to end, this call blocks
<-srvContext.Done()
// 1-second grace period
shutdownCtx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
srv.SetKeepAlivesEnabled(false) // disable keep-alive kills idle connections
_ = srv.Shutdown(shutdownCtx)
fmt.Println("[HEALTHCHECK] web service stopped")
}
// StartWithContext starts a background goroutine with the healthcheck webserver. The goroutine will be
// stopped if the context gets canceled or the healthcheck server crashes.
func (s *service) StartWithContext(ctx context.Context) {
go s.StartForeground(ctx)
}
// ListenOn allows to change the default listening address of ":11223".
func ListenOn(addr string) Option {
return func(svc *service) {
svc.listenAddress = addr
}
}
// WithCustomCheck allows to use a custom check function. The integer return value of the check
// function is used as HTTP status code.
func WithCustomCheck(fnc func() int) Option {
return func(svc *service) {
if fnc != nil {
svc.checkFunc = fnc
}
}
}
// ListenOnFromEnv sets the listening address to a value retrieved from the environment variable
// HC_LISTEN_ADDR.
// If the argument list is not empty, the listening address value will be loaded from an
// environment variable with the name of the first list entry.
// If the environment variable was empty, the listening address will not be overridden.
func ListenOnFromEnv(envName ...string) Option {
return func(svc *service) {
varName := "HC_LISTEN_ADDR"
if len(envName) > 0 {
varName = envName[0]
}
listenAddr := os.Getenv(varName)
if listenAddr != "" {
svc.listenAddress = listenAddr
}
}
}

View File

@ -0,0 +1,243 @@
// source taken from https://git.prolicht.digital/golib/healthcheck/-/blob/master/healthcheck_test.go
package healthcheck
import (
"context"
"net/http"
"os"
"reflect"
"testing"
"time"
)
func TestNew(t *testing.T) {
type args struct {
opts []Option
}
tests := []struct {
name string
args args
want *service
}{
{
name: "Test Plain",
args: args{},
want: &service{
listenAddress: ":11223",
checkFunc: nil, // we cannot compare the check function
},
},
{
name: "Test With Empty Options",
args: args{
opts: []Option{},
},
want: &service{
listenAddress: ":11223",
checkFunc: nil, // we cannot compare the check function
},
},
{
name: "Test With Options",
args: args{
opts: []Option{ListenOn(":123456")},
},
want: &service{
listenAddress: ":123456",
checkFunc: nil, // we cannot compare the check function
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := New(tt.args.opts...)
got.checkFunc = nil
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("New() = %v, want %v", got, tt.want)
}
})
}
}
func TestListenOn(t *testing.T) {
type args struct {
addr string
}
tests := []struct {
name string
args args
want *service
}{
{
name: "Test Port Only",
args: args{
addr: ":8080",
},
want: &service{
listenAddress: ":8080",
checkFunc: nil, // cannot deeply compare check func,
},
},
{
name: "Test Addr:Port Only",
args: args{
addr: "localhost:8080",
},
want: &service{
listenAddress: "localhost:8080",
checkFunc: nil, // cannot deeply compare check func,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := New(ListenOn(tt.args.addr))
got.checkFunc = nil
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("ListenOn() = %v, want %v", got, tt.want)
}
})
}
}
func TestListenOnEnv(t *testing.T) {
_ = os.Setenv("HC_LISTEN_ADDR", "")
hc := New(ListenOnFromEnv())
if hc.listenAddress != New().listenAddress {
t.Errorf("ListenOnFromEnv() = %v, want %v", hc.listenAddress, New().listenAddress)
}
want := ":1337"
_ = os.Setenv("HC_LISTEN_ADDR", want)
hc = New(ListenOnFromEnv())
if hc.listenAddress != want {
t.Errorf("ListenOnFromEnv() = %v, want %v", hc.listenAddress, want)
}
hc = New() // check that the env var has no effect
if hc.listenAddress != New().listenAddress {
t.Errorf("ListenOnFromEnv() = %v, want %v", hc.listenAddress, New().listenAddress)
}
want = ":1338"
_ = os.Setenv("SOME_RANDOM_ENV_VAR", want)
hc = New(ListenOnFromEnv("SOME_RANDOM_ENV_VAR"))
if hc.listenAddress != want {
t.Errorf("ListenOnFromEnv() = %v, want %v", hc.listenAddress, want)
}
hc = New(ListenOnFromEnv("SOME_RANDOM_ENV_VAR", "ignored", "ignored 2"))
if hc.listenAddress != want {
t.Errorf("ListenOnFromEnv() = %v, want %v", hc.listenAddress, want)
}
}
func TestWithCustomCheck(t *testing.T) {
customFnc := func() int { return 123 }
type args struct {
fnc func() int
}
tests := []struct {
name string
args args
want *service
wantFnc func() int
}{
{
name: "Test Custom Function",
args: args{
fnc: customFnc,
},
want: &service{
listenAddress: New().listenAddress,
checkFunc: nil, // cannot deeply compare check func,
},
wantFnc: customFnc,
},
{
name: "Test Nil Function",
args: args{
fnc: nil,
},
want: &service{
listenAddress: New().listenAddress,
checkFunc: nil, // cannot deeply compare check func,
},
wantFnc: New().checkFunc,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := New(WithCustomCheck(tt.args.fnc))
gotFnc := got.checkFunc
got.checkFunc = nil
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("WithContext() = %v, want %v", got, tt.want)
}
if reflect.ValueOf(gotFnc).Pointer() != reflect.ValueOf(tt.wantFnc).Pointer() {
t.Error("WithContext() function mismatch")
}
})
}
}
func Test_service_StartForeground(t *testing.T) {
runTime := 550 * time.Millisecond
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
defer cancel()
hc := New()
start := time.Now()
hc.StartForeground(ctx)
elapsed := time.Since(start)
// check if execution time is within +-10% of the runTime
if elapsed > (runTime+(runTime/10)) || elapsed < (runTime-(runTime/10)) {
t.Errorf("StartForeground() invalid execution time = %v, want %v", elapsed, runTime)
}
}
func Test_service_HTTPResponse(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
defer cancel()
hc := New()
hc.StartWithContext(ctx)
time.Sleep(200 * time.Millisecond) // ensure that web server is up and running
cl := http.Client{Timeout: time.Millisecond * 200}
req, _ := http.NewRequest("GET", "http://localhost:11223/health", nil)
resp, err := cl.Do(req)
if err != nil {
t.Errorf("http request failed: %v", err)
return
}
if resp.StatusCode != http.StatusOK {
t.Errorf("http request with wrong response code: %v, want %v", err, http.StatusOK)
}
<-ctx.Done() // wait for clean shutdown
}
func Test_service_CustomCheckResponse(t *testing.T) {
want := http.StatusExpectationFailed
hc := New(WithCustomCheck(func() int {
return want
}))
hc.Start()
time.Sleep(200 * time.Millisecond) // ensure that web server is up and running
cl := http.Client{Timeout: time.Millisecond * 200}
req, _ := http.NewRequest("GET", "http://localhost:11223/health", nil)
resp, err := cl.Do(req)
if err != nil {
t.Errorf("http request failed: %v", err)
return
}
if resp.StatusCode != want {
t.Errorf("http request with wrong response code: %v, want %v", err, want)
}
}