lego/e2e/loader/loader.go
2020-02-27 19:14:45 +01:00

304 lines
6 KiB
Go

package loader
import (
"bytes"
"crypto/tls"
"errors"
"fmt"
"io/ioutil"
"net/http"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
"time"
"github.com/go-acme/lego/v3/platform/wait"
)
const (
cmdNamePebble = "pebble"
cmdNameChallSrv = "pebble-challtestsrv"
)
type CmdOption struct {
HealthCheckURL string
Args []string
Env []string
Dir string
}
type EnvLoader struct {
PebbleOptions *CmdOption
LegoOptions []string
ChallSrv *CmdOption
lego string
}
func (l *EnvLoader) MainTest(m *testing.M) int {
_, force := os.LookupEnv("LEGO_E2E_TESTS")
if _, ci := os.LookupEnv("CI"); !ci && !force {
fmt.Fprintln(os.Stderr, "skipping test: e2e tests are disabled. (no 'CI' or 'LEGO_E2E_TESTS' env var)")
fmt.Println("PASS")
return 0
}
if _, err := exec.LookPath("git"); err != nil {
fmt.Fprintln(os.Stderr, "skipping because git command not found")
fmt.Println("PASS")
return 0
}
if l.PebbleOptions != nil {
if _, err := exec.LookPath(cmdNamePebble); err != nil {
fmt.Fprintln(os.Stderr, "skipping because pebble binary not found")
fmt.Println("PASS")
return 0
}
}
if l.ChallSrv != nil {
if _, err := exec.LookPath(cmdNameChallSrv); err != nil {
fmt.Fprintln(os.Stderr, "skipping because challtestsrv binary not found")
fmt.Println("PASS")
return 0
}
}
pebbleTearDown := l.launchPebble()
defer pebbleTearDown()
challSrvTearDown := l.launchChallSrv()
defer challSrvTearDown()
legoBinary, tearDown, err := buildLego()
defer tearDown()
if err != nil {
fmt.Fprintln(os.Stderr, err)
return 1
}
l.lego = legoBinary
if l.PebbleOptions != nil && l.PebbleOptions.HealthCheckURL != "" {
pebbleHealthCheck(l.PebbleOptions)
}
return m.Run()
}
func (l *EnvLoader) RunLego(arg ...string) ([]byte, error) {
cmd := exec.Command(l.lego, arg...)
cmd.Env = l.LegoOptions
fmt.Printf("$ %s\n", strings.Join(cmd.Args, " "))
return cmd.CombinedOutput()
}
func (l *EnvLoader) launchPebble() func() {
if l.PebbleOptions == nil {
return func() {}
}
pebble, outPebble := l.cmdPebble()
go func() {
err := pebble.Run()
if err != nil {
fmt.Println(err)
}
}()
return func() {
err := pebble.Process.Kill()
if err != nil {
fmt.Println(err)
}
fmt.Println(outPebble.String())
}
}
func (l *EnvLoader) cmdPebble() (*exec.Cmd, *bytes.Buffer) {
cmd := exec.Command(cmdNamePebble, l.PebbleOptions.Args...)
cmd.Env = l.PebbleOptions.Env
dir, err := filepath.Abs(l.PebbleOptions.Dir)
if err != nil {
panic(err)
}
cmd.Dir = dir
fmt.Printf("$ %s\n", strings.Join(cmd.Args, " "))
var b bytes.Buffer
cmd.Stdout = &b
cmd.Stderr = &b
return cmd, &b
}
func pebbleHealthCheck(options *CmdOption) {
client := &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}}
err := wait.For("pebble", 10*time.Second, 500*time.Millisecond, func() (bool, error) {
resp, err := client.Get(options.HealthCheckURL)
if err != nil {
return false, err
}
if resp.StatusCode != http.StatusOK {
return false, nil
}
return true, nil
})
if err != nil {
panic(err)
}
}
func (l *EnvLoader) launchChallSrv() func() {
if l.ChallSrv == nil {
return func() {}
}
challtestsrv, outChalSrv := l.cmdChallSrv()
go func() {
err := challtestsrv.Run()
if err != nil {
fmt.Println(err)
}
}()
return func() {
err := challtestsrv.Process.Kill()
if err != nil {
fmt.Println(err)
}
fmt.Println(outChalSrv.String())
}
}
func (l *EnvLoader) cmdChallSrv() (*exec.Cmd, *bytes.Buffer) {
cmd := exec.Command(cmdNameChallSrv, l.ChallSrv.Args...)
fmt.Printf("$ %s\n", strings.Join(cmd.Args, " "))
var b bytes.Buffer
cmd.Stdout = &b
cmd.Stderr = &b
return cmd, &b
}
func buildLego() (string, func(), error) {
here, err := os.Getwd()
if err != nil {
return "", func() {}, err
}
defer func() { _ = os.Chdir(here) }()
buildPath, err := ioutil.TempDir("", "lego_test")
if err != nil {
return "", func() {}, err
}
projectRoot, err := getProjectRoot()
if err != nil {
return "", func() {}, err
}
mainFolder := filepath.Join(projectRoot, "cmd", "lego")
err = os.Chdir(mainFolder)
if err != nil {
return "", func() {}, err
}
binary := filepath.Join(buildPath, "lego")
err = build(binary)
if err != nil {
return "", func() {}, err
}
err = os.Chdir(here)
if err != nil {
return "", func() {}, err
}
return binary, func() {
_ = os.RemoveAll(buildPath)
CleanLegoFiles()
}, nil
}
func getProjectRoot() (string, error) {
git := exec.Command("git", "rev-parse", "--show-toplevel")
output, err := git.CombinedOutput()
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", output)
return "", err
}
return strings.TrimSpace(string(output)), nil
}
func build(binary string) error {
toolPath, err := goToolPath()
if err != nil {
return err
}
cmd := exec.Command(toolPath, "build", "-o", binary)
output, err := cmd.CombinedOutput()
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", output)
return err
}
return nil
}
func goToolPath() (string, error) {
// inspired by go1.11.1/src/internal/testenv/testenv.go
if os.Getenv("GO_GCFLAGS") != "" {
return "", errors.New("'go build' not compatible with setting $GO_GCFLAGS")
}
if runtime.GOOS == "darwin" && strings.HasPrefix(runtime.GOARCH, "arm") {
return "", fmt.Errorf("skipping test: 'go build' not available on %s/%s", runtime.GOOS, runtime.GOARCH)
}
return goTool()
}
func goTool() (string, error) {
var exeSuffix string
if runtime.GOOS == "windows" {
exeSuffix = ".exe"
}
path := filepath.Join(runtime.GOROOT(), "bin", "go"+exeSuffix)
if _, err := os.Stat(path); err == nil {
return path, nil
}
goBin, err := exec.LookPath("go" + exeSuffix)
if err != nil {
return "", fmt.Errorf("cannot find go tool: %w", err)
}
return goBin, nil
}
func CleanLegoFiles() {
cmd := exec.Command("rm", "-rf", ".lego")
fmt.Printf("$ %s\n", strings.Join(cmd.Args, " "))
output, err := cmd.CombinedOutput()
if err != nil {
fmt.Println(string(output))
}
}