Skip to content

Commit

Permalink
Merge pull request #5 from gubernator-io/thrawn/optional-config-file
Browse files Browse the repository at this point in the history
Fixed regression, gubernator should start without a config file
  • Loading branch information
thrawn01 authored Mar 26, 2024
2 parents 49d3f3c + 8c97b9e commit b5893f5
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 28 deletions.
3 changes: 3 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ ARG TARGETPLATFORM
ENV BUILDPLATFORM=${BUILDPLATFORM:-linux/amd64}
ENV TARGETPLATFORM=${TARGETPLATFORM:-linux/amd64}

LABEL org.opencontainers.image.source = "https://github.com/gubernator-io/gubernator"

WORKDIR /go/src

# This should create cached layer of our dependencies for subsequent builds to use
Expand Down Expand Up @@ -38,6 +40,7 @@ COPY --from=build /healthcheck /healthcheck
# Healtcheck
HEALTHCHECK --interval=3s --timeout=1s --start-period=2s --retries=2 CMD [ "/healthcheck" ]


# Run the server
ENTRYPOINT ["/gubernator"]

Expand Down
68 changes: 40 additions & 28 deletions cmd/gubernator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,15 @@ package main
import (
"context"
"flag"
"fmt"
"io"
"os"
"os/signal"
"runtime"
"strings"
"syscall"

"github.com/gubernator-io/gubernator/v2"
"github.com/mailgun/holster/v4/clock"
"github.com/mailgun/holster/v4/tracing"
"github.com/sirupsen/logrus"
"go.opentelemetry.io/otel/sdk/resource"
Expand All @@ -39,13 +40,26 @@ var Version = "dev-build"
var tracerCloser io.Closer

func main() {
err := Main(context.Background())
if err != nil {
log.Error(err.Error())
os.Exit(1)
}
}

func Main(ctx context.Context) error {
var configFile string

logrus.Infof("Gubernator %s (%s/%s)", Version, runtime.GOARCH, runtime.GOOS)
flags := flag.NewFlagSet("gubernator", flag.ContinueOnError)
flags.SetOutput(io.Discard)
flags.StringVar(&configFile, "config", "", "environment config file")
flags.BoolVar(&gubernator.DebugEnabled, "debug", false, "enable debug")
checkErr(flags.Parse(os.Args[1:]), "while parsing flags")
if err := flags.Parse(os.Args[1:]); err != nil {
if !strings.Contains(err.Error(), "flag provided but not defined") {
return fmt.Errorf("while parsing flags: %w", err)
}
}

// in order to prevent logging to /tmp by k8s.io/client-go
// and other kubernetes related dependencies which are using
Expand All @@ -61,9 +75,13 @@ func main() {
if err != nil {
log.WithError(err).Fatal("during tracing.NewResource()")
}
defer func() {
if tracerCloser != nil {
_ = tracerCloser.Close()
}
}()

// Initialize tracing.
ctx := context.Background()
err = tracing.InitTracing(ctx,
"github.com/gubernator-io/gubernator/v2",
tracing.WithLevel(gubernator.GetTracingLevel()),
Expand All @@ -73,42 +91,36 @@ func main() {
log.WithError(err).Fatal("during tracing.InitTracing()")
}

var configFileReader io.Reader
// Read our config from the environment or optional environment config file
configFileReader, err := os.Open(configFile)
if err != nil {
log.WithError(err).Fatal("while opening config file")
if configFile != "" {
configFileReader, err = os.Open(configFile)
if err != nil {
log.WithError(err).Fatal("while opening config file")
}
}
conf, err := gubernator.SetupDaemonConfig(logrus.StandardLogger(), configFileReader)
checkErr(err, "while getting config")

ctx, cancel := context.WithTimeout(ctx, clock.Second*10)
conf, err := gubernator.SetupDaemonConfig(logrus.StandardLogger(), configFileReader)
if err != nil {
return fmt.Errorf("while collecting daemon config: %w", err)
}

// Start the daemon
daemon, err := gubernator.SpawnDaemon(ctx, conf)
checkErr(err, "while spawning daemon")
cancel()
if err != nil {
return fmt.Errorf("while spawning daemon: %w", err)
}

// Wait here for signals to clean up our mess
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
for range c {
signal.Notify(c, os.Interrupt, syscall.SIGTERM, syscall.SIGINT)
select {
case <-c:
log.Info("caught signal; shutting down")
daemon.Close()
_ = tracing.CloseTracing(context.Background())
exit(0)
}
}

func checkErr(err error, msg string) {
if err != nil {
log.WithError(err).Error(msg)
exit(1)
}
}

func exit(code int) {
if tracerCloser != nil {
tracerCloser.Close()
return nil
case <-ctx.Done():
return ctx.Err()
}
os.Exit(code)
}
117 changes: 117 additions & 0 deletions cmd/gubernator/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package main_test

import (
"bytes"
"context"
"crypto/tls"
"errors"
"flag"
"fmt"
"net"
"os"
"os/exec"
"strings"
"syscall"
"testing"
"time"

cli "github.com/gubernator-io/gubernator/v2/cmd/gubernator"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/net/proxy"
)

var cliRunning = flag.Bool("test_cli_running", false, "True if running as a child process; used by TestCLI")

func TestCLI(t *testing.T) {
if *cliRunning {
if err := cli.Main(context.Background()); err != nil {
//if !strings.Contains(err.Error(), "context deadline exceeded") {
// log.Print(err.Error())
// os.Exit(1)
//}
fmt.Print(err.Error())
os.Exit(1)
}
os.Exit(0)
}

tests := []struct {
args []string
env []string
name string
contains string
}{
{
name: "Should start with no config provided",
env: []string{
"GUBER_GRPC_ADDRESS=localhost:8080",
"GUBER_HTTP_ADDRESS=localhost:8081",
},
args: []string{},
contains: "HTTP Gateway Listening on",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := exec.Command(os.Args[0], append([]string{"--test.run=TestCLI", "--test_cli_running"}, tt.args...)...)
var out bytes.Buffer
c.Stdout = &out
c.Stderr = &out
c.Env = tt.env

if err := c.Start(); err != nil {
t.Fatal("failed to start child process: ", err)
}

ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()

waitCh := make(chan struct{})
go func() {
_ = c.Wait()
close(waitCh)
}()

err := waitForConnect(ctx, "localhost:8080", nil)
assert.NoError(t, err)
time.Sleep(time.Second * 1)

err = c.Process.Signal(syscall.SIGTERM)
require.NoError(t, err)

<-waitCh
assert.Contains(t, out.String(), tt.contains)
})
}
}

// waitForConnect waits until the passed address is accepting connections.
// It will continue to attempt a connection until context is canceled.
func waitForConnect(ctx context.Context, address string, cfg *tls.Config) error {
if address == "" {
return fmt.Errorf("waitForConnect() requires a valid address")
}

var errs []string
for {
var d proxy.ContextDialer
if cfg != nil {
d = &tls.Dialer{Config: cfg}
} else {
d = &net.Dialer{}
}
conn, err := d.DialContext(ctx, "tcp", address)
if err == nil {
_ = conn.Close()
return nil
}
errs = append(errs, err.Error())
if ctx.Err() != nil {
errs = append(errs, ctx.Err().Error())
return errors.New(strings.Join(errs, "\n"))
}
time.Sleep(time.Millisecond * 100)
continue
}
}

0 comments on commit b5893f5

Please sign in to comment.