Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed regression, gubernator should start without a config file #5

Merged
merged 1 commit into from
Mar 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}
}
Loading