auth-ui: add e2e tests
Change-Id: Ic8f2f9e032d24eed2d4fd824dcfc26c59d7d915e
diff --git a/core/auth/ui/e2e/archive.go b/core/auth/ui/e2e/archive.go
new file mode 100644
index 0000000..f9b85dd
--- /dev/null
+++ b/core/auth/ui/e2e/archive.go
@@ -0,0 +1,104 @@
+package e2e
+
+import (
+ "archive/tar"
+ "compress/gzip"
+ "context"
+ "errors"
+ "fmt"
+ "io"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+ "time"
+)
+
+func extractBinary(archivePath, destinationDir, expectedName string) (string, error) {
+ f, err := os.Open(archivePath)
+ if err != nil {
+ return "", err
+ }
+ defer f.Close()
+ gz, err := gzip.NewReader(f)
+ if err != nil {
+ return "", fmt.Errorf("open gzip archive: %w", err)
+ }
+ defer gz.Close()
+
+ if err := os.MkdirAll(destinationDir, 0o755); err != nil {
+ return "", err
+ }
+ var found []byte
+ tr := tar.NewReader(gz)
+ for {
+ h, err := tr.Next()
+ if errors.Is(err, io.EOF) {
+ break
+ }
+ if err != nil {
+ return "", fmt.Errorf("read tar archive: %w", err)
+ }
+ clean := filepath.Clean(h.Name)
+ if filepath.IsAbs(h.Name) || clean == ".." || strings.HasPrefix(clean, ".."+string(filepath.Separator)) {
+ return "", fmt.Errorf("unsafe archive path %q", h.Name)
+ }
+ if filepath.Base(clean) != expectedName {
+ continue
+ }
+ if h.Typeflag != tar.TypeReg && h.Typeflag != tar.TypeRegA {
+ return "", fmt.Errorf("expected executable %q is not a regular file", h.Name)
+ }
+ if found != nil {
+ return "", fmt.Errorf("archive contains multiple %q executables", expectedName)
+ }
+ if h.Size < 1 || h.Size > 512<<20 {
+ return "", fmt.Errorf("invalid executable size %d", h.Size)
+ }
+ found, err = io.ReadAll(io.LimitReader(tr, h.Size+1))
+ if err != nil {
+ return "", err
+ }
+ if int64(len(found)) != h.Size {
+ return "", fmt.Errorf("truncated executable %q", h.Name)
+ }
+ }
+ if found == nil {
+ return "", fmt.Errorf("archive does not contain expected executable %q", expectedName)
+ }
+ destination := filepath.Join(destinationDir, expectedName)
+ if err := os.WriteFile(destination, found, 0o755); err != nil {
+ return "", err
+ }
+ if err := os.Chmod(destination, 0o755); err != nil {
+ return "", err
+ }
+ return destination, nil
+}
+
+func verifyBinaryVersion(path, expected string) error {
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+ defer cancel()
+ out, err := exec.CommandContext(ctx, path, "version").CombinedOutput()
+ if ctx.Err() != nil {
+ return fmt.Errorf("verify %s version: %w", filepath.Base(path), ctx.Err())
+ }
+ if err != nil {
+ return fmt.Errorf("verify %s version: %w (%s)", filepath.Base(path), err, boundedText(out, 2048))
+ }
+ if !versionOutputMatches(out, expected) {
+ return fmt.Errorf("unexpected %s version: expected %s, got %s", filepath.Base(path), expected, boundedText(out, 2048))
+ }
+ return nil
+}
+
+func versionOutputMatches(output []byte, expected string) bool {
+ withoutV := strings.TrimPrefix(expected, "v")
+ for _, field := range strings.Fields(string(output)) {
+ field = strings.Trim(field, " \t\r\n,;:()[]{}")
+ if field == expected || field == withoutV {
+ return true
+ }
+ }
+ return false
+}