diff --git a/Dockerfile.debug b/Dockerfile.debug new file mode 100644 index 000000000..4b99a727d --- /dev/null +++ b/Dockerfile.debug @@ -0,0 +1,37 @@ +FROM --platform=${BUILDPLATFORM} mcr.microsoft.com/oss/go/microsoft/golang:1.22@sha256:9e5243001d0f43d6e1c556a0ce8eedc1bc80eb37b5210036b289d2c601bc08b6 AS go +FROM scratch AS dlv-ctx + +FROM go AS frontend-build +ARG HOSTDIR +WORKDIR ${HOSTDIR} +COPY . . +ENV CGO_ENABLED=0 +ARG TARGETARCH TARGETOS GOFLAGS=-trimpath +ENV GOOS=${TARGETOS} GOARCH=${TARGETARCH} GOFLAGS=${GOFLAGS} +RUN \ + --mount=type=cache,target=/go/pkg/mod \ + --mount=type=cache,target=/root/.cache/go-build \ + go build -tags=debug -gcflags="all=-N -l" -o /frontend ./cmd/frontend && \ + go build -o /dalec-redirectio ./cmd/dalec-redirectio + +FROM go AS dlv-build +ARG HOSTDIR +WORKDIR /dlv +ADD https://github.com/go-delve/delve.git#v1.23.1 . +ENV CGO_ENABLED=0 +ARG TARGETARCH TARGETOS GOFLAGS=-trimpath +ENV GOOS=${TARGETOS} GOARCH=${TARGETARCH} GOFLAGS=${GOFLAGS} +RUN \ + --mount=type=cache,target=/go/pkg/mod \ + --mount=type=cache,target=/root/.cache/go-build \ + make build + +FROM mcr.microsoft.com/azurelinux/distroless/base:3.0 AS frontend +ARG HOSTDIR +COPY . ${HOSTDIR} +COPY --from=frontend-build /frontend /frontend +COPY --from=frontend-build /dalec-redirectio /dalec-redirectio +COPY --from=dlv-build /dlv/dlv /usr/local/bin/dlv +LABEL moby.buildkit.frontend.network.none="true" +LABEL moby.buildkit.frontend.caps="moby.buildkit.frontend.inputs,moby.buildkit.frontend.subrequests,moby.buildkit.frontend.contexts" +ENTRYPOINT ["/frontend"] diff --git a/cmd/frontend/debug.go b/cmd/frontend/debug.go new file mode 100644 index 000000000..399a72294 --- /dev/null +++ b/cmd/frontend/debug.go @@ -0,0 +1,64 @@ +//go:build debug + +package main + +import ( + "bufio" + "context" + "fmt" + "io" + "os" + "os/exec" + "strings" + "syscall" +) + +func waitForDebug(ctx context.Context) error { + pid := fmt.Sprintf("%d", syscall.Getpid()) + cmd := exec.Command( + "dlv", + "attach", + "--api-version=2", + "--headless", + "--listen=unix:/dlv.sock", + "--allow-non-terminal-interactive", + pid, + ) + cmd.Stdout = io.Discard + cmd.Stderr = io.Discard + + if err := cmd.Start(); err != nil { + return err + } + + go func() { + if err := cmd.Wait(); err != nil { + panic(err) + } + }() + + tracerPid := "0" + for tracerPid == "0" { + b, err := os.Open("/proc/self/status") + if err != nil { + return err + } + + s := bufio.NewScanner(b) + for s.Scan() { + start, end, ok := strings.Cut(s.Text(), ":\t") + if !ok { + continue + } + + if start != "TracerPid" { + continue + } + + tracerPid = end + break + } + } + + return nil +} diff --git a/cmd/frontend/main.go b/cmd/frontend/main.go index 6940ea153..6731fb04c 100644 --- a/cmd/frontend/main.go +++ b/cmd/frontend/main.go @@ -1,6 +1,7 @@ package main import ( + "context" _ "embed" "os" @@ -10,6 +11,7 @@ import ( "github.com/Azure/dalec/frontend/debug" "github.com/Azure/dalec/frontend/ubuntu" "github.com/Azure/dalec/frontend/windows" + gwclient "github.com/moby/buildkit/frontend/gateway/client" "github.com/moby/buildkit/frontend/gateway/grpcclient" "github.com/moby/buildkit/util/appcontext" "github.com/moby/buildkit/util/bklog" @@ -31,16 +33,27 @@ func main() { mux.Add(debug.DebugRoute, debug.Handle, nil) - if err := grpcclient.RunFromEnvironment(ctx, mux.Handler( - // copy/paster's beware: [frontend.WithTargetForwardingHandler] should not be set except for the root dalec frontend. - frontend.WithBuiltinHandler(azlinux.Mariner2TargetKey, azlinux.NewMariner2Handler()), - frontend.WithBuiltinHandler(azlinux.AzLinux3TargetKey, azlinux.NewAzlinux3Handler()), - frontend.WithBuiltinHandler(windows.DefaultTargetKey, windows.Handle), - ubuntu.Handlers, - debian.Handlers, - frontend.WithTargetForwardingHandler, - )); err != nil { + f := func(ctx context.Context, client gwclient.Client) (*gwclient.Result, error) { + if err := waitForDebug(ctx); err != nil { + return nil, err + } + + handlerFunc := mux.Handler( + // copy/paster's beware: [frontend.WithTargetForwardingHandler] should not be set except for the root dalec frontend. + frontend.WithBuiltinHandler(azlinux.Mariner2TargetKey, azlinux.NewMariner2Handler()), + frontend.WithBuiltinHandler(azlinux.AzLinux3TargetKey, azlinux.NewAzlinux3Handler()), + frontend.WithBuiltinHandler(windows.DefaultTargetKey, windows.Handle), + ubuntu.Handlers, + debian.Handlers, + frontend.WithTargetForwardingHandler, + ) + + return handlerFunc(ctx, client) + } + + if err := grpcclient.RunFromEnvironment(ctx, f); err != nil { bklog.L.WithError(err).Fatal("error running frontend") os.Exit(137) } + } diff --git a/cmd/frontend/no_debug.go b/cmd/frontend/no_debug.go new file mode 100644 index 000000000..4b5591125 --- /dev/null +++ b/cmd/frontend/no_debug.go @@ -0,0 +1,9 @@ +//go:build !debug + +package main + +import "context" + +func waitForDebug(ctx context.Context) error { + return nil +} diff --git a/debug/debug.sh b/debug/debug.sh new file mode 100755 index 000000000..dca45a244 --- /dev/null +++ b/debug/debug.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +set -eux + +if [ -z "$(command -v socat)" ]; then + echo you must have "'socat'" installed + exit 1 +fi + +if [ -z "$(command -v pgrep)" ]; then + echo you must have "'pgrep'" installed + exit 1 +fi + +PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "${PROJECT_DIR}" + +# Build frontend with debugging setup Note the host path for the dalec source +# and the in-container build path must be the same +REF="local/dalec/frontend:tmp" +docker build \ + -f Dockerfile.debug \ + -t "${REF}" \ + --build-arg=HOSTDIR="${PROJECT_DIR}" \ + . + +# Wait for frontend process to start, and forward the socket connection when the process has started +( + set +x + pid="" + while [ -z "$pid" ]; do + sleep 0.5 + pid="$(pgrep frontend)" + done + set -x + + socat_logfile="$(mktemp)" + socat -v UNIX:"/proc/${pid}/root/dlv.sock" TCP-LISTEN:30157,reuseaddr,fork 2>"$socat_logfile" + socat_pid="$!" + trap "kill -9 ${socat_pid}" EXIT +) & + +# Run the build +exec docker build --build-arg=BUILDKIT_SYNTAX="${REF}" "$@"