Add service wait script in Go

`gitlab-runner-service` is a bash script inside of the helper image that
runs to check if a specific host/port of service is open. This is
written in bash currently, which does not run on windows. With support
for windows coming we need to make this cross platform.

Create a new command `health-check` for the `gitlab-runner-helper` CLI
which has the same logic that will be used only for windows containers
not to have any breaking changes for the current Linux containers

gitlab-org/gitlab-runner#3755
parent 40a22cf9
Pipeline #47719299 passed with stages
in 30 minutes and 2 seconds
package helpers
import (
"fmt"
"net"
"os"
"strings"
"time"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
"gitlab.com/gitlab-org/gitlab-runner/common"
)
type HealthCheckCommand struct{}
func (c *HealthCheckCommand) Execute(ctx *cli.Context) {
var addr, port string
for _, e := range os.Environ() {
parts := strings.Split(e, "=")
if len(parts) != 2 {
continue
} else if strings.HasSuffix(parts[0], "_TCP_ADDR") {
addr = parts[1]
} else if strings.HasSuffix(parts[0], "_TCP_PORT") {
port = parts[1]
}
}
if addr == "" || port == "" {
logrus.Fatalln("No HOST or PORT found")
}
fmt.Fprintf(os.Stdout, "waiting for TCP connection to %s:%s...", addr, port)
for {
conn, err := net.Dial("tcp", net.JoinHostPort(addr, port))
if err != nil {
time.Sleep(time.Second)
continue
}
conn.Close()
return
}
}
func init() {
common.RegisterCommand2("health-check", "check health for a specific address", &HealthCheckCommand{})
}
package helpers
import (
"context"
"net"
"os"
"strconv"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gitlab.com/gitlab-org/gitlab-runner/helpers"
)
func TestServiceWaiterCommand_NoEnvironmentVariables(t *testing.T) {
removeHook := helpers.MakeFatalToPanic()
defer removeHook()
// Make sure there are no env vars that match the pattern
for _, e := range os.Environ() {
if strings.Contains(e, "_TCP_") {
err := os.Unsetenv(strings.Split(e, "=")[0])
require.NoError(t, err)
}
}
cmd := HealthCheckCommand{}
assert.Panics(t, func() {
cmd.Execute(nil)
})
}
func TestServiceWaiterCommand_Execute(t *testing.T) {
cases := []struct {
name string
expectedConnect bool
}{
{
name: "Successful connect",
expectedConnect: true,
},
{
name: "Unsuccessful connect because service is down",
expectedConnect: false,
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
// Start listening to reverse addr
listener, err := net.Listen("tcp", "127.0.0.1:")
require.NoError(t, err)
err = os.Setenv("SERVICE_TCP_ADDR", "127.0.0.1")
require.NoError(t, err)
err = os.Setenv("SERVICE_TCP_PORT", strconv.Itoa(listener.Addr().(*net.TCPAddr).Port))
// If we don't expect to connect we close the listener.
if !c.expectedConnect {
listener.Close()
}
ctx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second)
defer cancelFn()
done := make(chan struct{})
go func() {
cmd := HealthCheckCommand{}
cmd.Execute(nil)
done <- struct{}{}
}()
select {
case <-ctx.Done():
if c.expectedConnect {
require.Fail(t, "Timeout waiting to start service.")
}
case <-done:
if !c.expectedConnect {
require.Fail(t, "Expected to not connect to server")
}
}
})
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment