init
This commit is contained in:
+393
@@ -0,0 +1,393 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/markbates/pkger"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
LatestVersion = "latest"
|
||||
)
|
||||
|
||||
type Requirements struct {
|
||||
BrowserSource BrowserSource
|
||||
BrowserChannel string // "beta", "esr", "dev" and so on
|
||||
DriverVersion string
|
||||
Tags []string
|
||||
NoCache bool
|
||||
TestsDir string
|
||||
RunTests bool
|
||||
IgnoreTests bool
|
||||
PushImage bool
|
||||
}
|
||||
|
||||
type BrowserSource string
|
||||
|
||||
// Return regular file corresponding to this source and optionally download this file
|
||||
func (bs *BrowserSource) Prepare() (string, string, error) {
|
||||
src := string(*bs)
|
||||
if src == "" {
|
||||
return "", "", errors.New("empty browser source")
|
||||
}
|
||||
if _, err := os.Stat(src); err == nil {
|
||||
pkgName := filepath.Base(src)
|
||||
return src, extractVersion(pkgName), nil
|
||||
} else if u, err := url.Parse(src); strings.HasPrefix(src, "http") && err == nil {
|
||||
pkgName := path.Base(src)
|
||||
data, err := downloadFile(u.String())
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("download file: %v", err)
|
||||
}
|
||||
f, err := ioutil.TempFile("", "images")
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("temporary file: %v", err)
|
||||
}
|
||||
outputFileName := f.Name()
|
||||
err = ioutil.WriteFile(outputFileName, data, 0644)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("save downloaded file: %v", err)
|
||||
}
|
||||
return outputFileName, extractVersion(pkgName), nil
|
||||
}
|
||||
return "", src, nil
|
||||
}
|
||||
|
||||
func extractVersion(name string) string {
|
||||
pieces := strings.Split(name, "_")
|
||||
version := name
|
||||
if len(pieces) >= 2 {
|
||||
version = pieces[1]
|
||||
}
|
||||
pieces = strings.Split(version, "+")
|
||||
pieces = strings.Split(pieces[0], "-")
|
||||
pieces = strings.Split(pieces[0], "~")
|
||||
return pieces[0]
|
||||
}
|
||||
|
||||
func versionN(pkgVersion string, n int) string {
|
||||
buildVersion := pkgVersion
|
||||
pieces := strings.Split(pkgVersion, ".")
|
||||
if len(pieces) >= n {
|
||||
buildVersion = strings.Join(pieces[0:n], ".")
|
||||
}
|
||||
return buildVersion
|
||||
}
|
||||
|
||||
func majorVersion(pkgVersion string) string {
|
||||
return versionN(pkgVersion, 1)
|
||||
}
|
||||
|
||||
func majorMinorVersion(pkgVersion string) string {
|
||||
return versionN(pkgVersion, 2)
|
||||
}
|
||||
|
||||
func buildVersion(pkgVersion string) string {
|
||||
return versionN(pkgVersion, 3)
|
||||
}
|
||||
|
||||
type Image struct {
|
||||
Dir string
|
||||
BuildArgs []string
|
||||
Labels []string
|
||||
FileServer bool
|
||||
Requirements
|
||||
}
|
||||
|
||||
func NewImage(srcDir string, destDir string, req Requirements) (*Image, error) {
|
||||
if !requireCommand("docker") {
|
||||
return nil, fmt.Errorf("docker is not installed")
|
||||
}
|
||||
|
||||
dir, err := copySourceFiles(srcDir, destDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("copy source files: %v", err)
|
||||
}
|
||||
|
||||
if len(req.Tags) == 0 {
|
||||
return nil, errors.New("image tag is required")
|
||||
}
|
||||
return &Image{Dir: dir, Requirements: req}, nil
|
||||
}
|
||||
|
||||
func requireCommand(cmd string) bool {
|
||||
_, err := exec.LookPath(cmd)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func tmpDir() (string, error) {
|
||||
dir, err := ioutil.TempDir("", "images")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("create temporary dir: %v", err)
|
||||
}
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
func copySourceFiles(srcDir string, destDir string) (string, error) {
|
||||
|
||||
const prefix = "/static"
|
||||
walkDir := filepath.Join(prefix, srcDir)
|
||||
err := pkger.Walk(walkDir, func(path string, info os.FileInfo, err error) error {
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
regex := regexp.MustCompile(`^.+:/static(.+)$`)
|
||||
relativePath := regex.FindStringSubmatch(path)[1]
|
||||
outputPath := filepath.Join(destDir, relativePath)
|
||||
if info.IsDir() {
|
||||
return os.MkdirAll(outputPath, info.Mode())
|
||||
}
|
||||
|
||||
fileDir := filepath.Join(destDir, filepath.Dir(relativePath))
|
||||
if !fileExists(fileDir) {
|
||||
log.Printf("mkdir dir %s", fileDir)
|
||||
return os.MkdirAll(fileDir, info.Mode())
|
||||
}
|
||||
|
||||
src, err := pkger.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
dest, err := os.Create(outputPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dest.Close()
|
||||
|
||||
_, err = io.Copy(dest, src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = dest.Sync()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = os.Chmod(outputPath, info.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return filepath.Join(destDir, srcDir), nil
|
||||
}
|
||||
|
||||
func (i *Image) Build() error {
|
||||
|
||||
args := []string{"build"}
|
||||
for _, tag := range i.Tags {
|
||||
args = append(args, "-t", tag)
|
||||
}
|
||||
if len(i.BuildArgs) > 0 {
|
||||
for _, arg := range i.BuildArgs {
|
||||
args = append(args, "--build-arg", arg)
|
||||
}
|
||||
}
|
||||
if httpProxy := os.Getenv("HTTP_PROXY"); httpProxy != "" {
|
||||
args = append(args, "--build-arg", fmt.Sprintf("http_proxy=%s", httpProxy))
|
||||
}
|
||||
if httpsProxy := os.Getenv("HTTPS_PROXY"); httpsProxy != "" {
|
||||
args = append(args, "--build-arg", fmt.Sprintf("https_proxy=%s", httpsProxy))
|
||||
}
|
||||
if len(i.Labels) > 0 {
|
||||
for _, label := range i.Labels {
|
||||
args = append(args, "--label", label)
|
||||
}
|
||||
}
|
||||
|
||||
if i.NoCache {
|
||||
args = append(args, "--no-cache")
|
||||
}
|
||||
|
||||
if i.FileServer {
|
||||
server := &http.Server{
|
||||
Handler: http.FileServer(http.Dir(i.Dir)),
|
||||
}
|
||||
ln, err := net.Listen("tcp", ":8080")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to allocate free port: %v", err)
|
||||
}
|
||||
|
||||
e := make(chan error)
|
||||
go func() {
|
||||
e <- server.Serve(ln)
|
||||
}()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
defer server.Shutdown(ctx)
|
||||
if runtime.GOOS == "linux" {
|
||||
ip, err := dockerHostIP()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to detect host machine IP: %v", err)
|
||||
}
|
||||
args = append(args, "--add-host", fmt.Sprintf("host.docker.internal:%s", ip))
|
||||
}
|
||||
}
|
||||
|
||||
args = append(args, i.Dir)
|
||||
log.Printf("running command: docker %s", strings.Join(args, " "))
|
||||
cmd := exec.Command("docker", args...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to start command: %v", err)
|
||||
}
|
||||
err = cmd.Wait()
|
||||
if err != nil {
|
||||
return fmt.Errorf("command execution error: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func dockerHostIP() (string, error) {
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, iface := range ifaces {
|
||||
if iface.Name != "docker0" {
|
||||
continue
|
||||
}
|
||||
if iface.Flags&net.FlagUp == 0 {
|
||||
continue // interface down
|
||||
}
|
||||
if iface.Flags&net.FlagLoopback != 0 {
|
||||
continue // loopback interface
|
||||
}
|
||||
addrs, err := iface.Addrs()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, addr := range addrs {
|
||||
var ip net.IP
|
||||
switch v := addr.(type) {
|
||||
case *net.IPNet:
|
||||
ip = v.IP
|
||||
case *net.IPAddr:
|
||||
ip = v.IP
|
||||
}
|
||||
if ip == nil || ip.IsLoopback() {
|
||||
continue
|
||||
}
|
||||
ip = ip.To4()
|
||||
if ip == nil {
|
||||
continue // not an ipv4 address
|
||||
}
|
||||
return ip.String(), nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("no live network interfaces detected")
|
||||
}
|
||||
|
||||
func (i *Image) Test(testsDir string, browserName string, browserVersion string) error {
|
||||
if !i.RunTests {
|
||||
log.Println("not running tests")
|
||||
return nil
|
||||
}
|
||||
ref := i.Tags[0]
|
||||
err := doTest(ref, testsDir, browserName, browserVersion)
|
||||
if err != nil {
|
||||
if i.IgnoreTests {
|
||||
log.Printf("ignoring tests: %v", err)
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("tests error: %v", err)
|
||||
}
|
||||
log.Println("tests passed")
|
||||
return nil
|
||||
}
|
||||
|
||||
func doTest(ref string, testsDir string, browserName string, browserVersion string) error {
|
||||
if !fileExists(testsDir) {
|
||||
return fmt.Errorf("tests directory %s does not exist", testsDir)
|
||||
}
|
||||
|
||||
seleniumUrl := "http://localhost:4445/"
|
||||
if browserName == "firefox" || (browserName == "opera" && browserVersion == "12.16") {
|
||||
seleniumUrl = "http://localhost:4445/wd/hub"
|
||||
}
|
||||
|
||||
exec.Command("docker", "rm", "-f", "selenium").Output()
|
||||
defer func() {
|
||||
exec.Command("docker", "rm", "-f", "selenium").Output()
|
||||
}()
|
||||
|
||||
output, err := exec.Command("docker", "run", "-d", "--name", "selenium", "--privileged", "-p", "4445:4444", ref).Output()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to start docker image: %s %v", string(output), err)
|
||||
}
|
||||
|
||||
if !requireCommand("mvn") {
|
||||
return fmt.Errorf("maven is not installed")
|
||||
}
|
||||
|
||||
mvnCmd := exec.Command("mvn", "clean", "test",
|
||||
fmt.Sprintf("-Dgrid.connection.url=%s", seleniumUrl),
|
||||
fmt.Sprintf("-Dgrid.browser.name=%s", browserName),
|
||||
fmt.Sprintf("-Dgrid.browser.version=%s", browserVersion),
|
||||
)
|
||||
mvnCmd.Dir = testsDir
|
||||
mvnCmd.Stdout = os.Stdout
|
||||
mvnCmd.Stderr = os.Stderr
|
||||
err = mvnCmd.Start()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to start tests: %v", err)
|
||||
}
|
||||
err = mvnCmd.Wait()
|
||||
if err != nil {
|
||||
return fmt.Errorf("tests finished with error: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func fileExists(p string) bool {
|
||||
_, err := os.Stat(p)
|
||||
return !os.IsNotExist(err)
|
||||
}
|
||||
|
||||
func (i *Image) Push() error {
|
||||
if i.PushImage {
|
||||
for _, tag := range i.Tags {
|
||||
cmd := exec.Command("docker", "push", tag)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid docker push %s: %v", tag, err)
|
||||
}
|
||||
err = cmd.Wait()
|
||||
if err != nil {
|
||||
return fmt.Errorf("pushing failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user