diff --git a/README.md b/README.md index cdf7bfc7..39605c0e 100644 --- a/README.md +++ b/README.md @@ -271,6 +271,18 @@ buildctl build --help The images can be also built locally using `./hack/dockerfiles/test.Dockerfile` (or `./hack/dockerfiles/test.buildkit.Dockerfile` if you already have BuildKit). Run `make images` to build the images as `moby/buildkit:local` and `moby/buildkit:local-rootless`. +#### Connection helpers + +If you are running `moby/buildkit:master` or `moby/buildkit:master-rootless` as a Docker/Kubernetes container, you can use special `BUILDKIT_HOST` URL for connecting to the BuildKit daemon in the container: + +``` +export BUILDKIT_HOST=docker:// +``` + +``` +export BUILDKIT_HOST=kube-pod:// +``` + ### Opentracing support BuildKit supports opentracing for buildkitd gRPC API and buildctl commands. To capture the trace to [Jaeger](https://github.com/jaegertracing/jaeger), set `JAEGER_TRACE` environment variable to the collection address. diff --git a/client/connhelper/kubepod/kubepod.go b/client/connhelper/kubepod/kubepod.go new file mode 100644 index 00000000..ab0289c4 --- /dev/null +++ b/client/connhelper/kubepod/kubepod.go @@ -0,0 +1,77 @@ +// Package kubepod provides connhelper for kube-pod:// +package kubepod + +import ( + "context" + "net" + "net/url" + "regexp" + + "github.com/docker/cli/cli/connhelper/commandconn" + "github.com/moby/buildkit/client/connhelper" + "github.com/pkg/errors" +) + +func init() { + connhelper.Register("kube-pod", Helper) +} + +// Helper returns helper for connecting to a Kubernetes pod. +// Requires BuildKit v0.5.0 or later in the pod. +func Helper(u *url.URL) (*connhelper.ConnectionHelper, error) { + sp, err := SpecFromURL(u) + if err != nil { + return nil, err + } + return &connhelper.ConnectionHelper{ + ContextDialer: func(ctx context.Context, addr string) (net.Conn, error) { + return commandconn.New(ctx, "kubectl", "--context="+sp.Context, "--namespace="+sp.Namespace, + "exec", "--container="+sp.Container, "-i", sp.Pod, "--", "buildctl", "dial-stdio") + }, + }, nil +} + +// Spec +type Spec struct { + Context string + Namespace string + Pod string + Container string +} + +// SpecFromURL creates Spec from URL. +// URL is like kube-pod://?context=&namespace=&container= . +// Only part is mandatory. +func SpecFromURL(u *url.URL) (*Spec, error) { + q := u.Query() + sp := Spec{ + Context: q.Get("context"), + Namespace: q.Get("namespace"), + Pod: u.Hostname(), + Container: q.Get("container"), + } + if sp.Context != "" && !validKubeIdentifier(sp.Context) { + return nil, errors.Errorf("unsupported context name: %q", sp.Context) + } + if sp.Namespace != "" && !validKubeIdentifier(sp.Namespace) { + return nil, errors.Errorf("unsupported namespace name: %q", sp.Namespace) + } + if sp.Pod == "" { + return nil, errors.New("url lacks pod name") + } + if !validKubeIdentifier(sp.Pod) { + return nil, errors.Errorf("unsupported pod name: %q", sp.Pod) + } + if sp.Container != "" && !validKubeIdentifier(sp.Container) { + return nil, errors.Errorf("unsupported container name: %q", sp.Container) + } + return &sp, nil +} + +var kubeIdentifierRegexp = regexp.MustCompile(`^[-a-z0-9.]+$`) + +// validKubeIdentifier: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names +// The length is not checked because future version of Kube may support longer identifiers. +func validKubeIdentifier(s string) bool { + return kubeIdentifierRegexp.MatchString(s) +} diff --git a/client/connhelper/kubepod/kubepod_test.go b/client/connhelper/kubepod/kubepod_test.go new file mode 100644 index 00000000..e0e1c11a --- /dev/null +++ b/client/connhelper/kubepod/kubepod_test.go @@ -0,0 +1,34 @@ +package kubepod + +import ( + "net/url" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestSpecFromURL(t *testing.T) { + cases := map[string]*Spec{ + "kube-pod://podname": { + Pod: "podname", + }, + "kube-pod://podname?container=containername&namespace=nsname&context=ctxname": { + Context: "ctxname", Namespace: "nsname", Pod: "podname", Container: "containername", + }, + "kube-pod://": nil, + "kube-pod://unsupported_pod_name": nil, + } + for s, expected := range cases { + u, err := url.Parse(s) + if err != nil { + t.Fatal(err) + } + got, err := SpecFromURL(u) + if expected != nil { + require.NoError(t, err) + require.EqualValues(t, expected, got, s) + } else { + require.Error(t, err, s) + } + } +} diff --git a/cmd/buildctl/main.go b/cmd/buildctl/main.go index 5d2b99df..ebcba61b 100644 --- a/cmd/buildctl/main.go +++ b/cmd/buildctl/main.go @@ -5,6 +5,7 @@ import ( "os" _ "github.com/moby/buildkit/client/connhelper/dockercontainer" + _ "github.com/moby/buildkit/client/connhelper/kubepod" bccommon "github.com/moby/buildkit/cmd/buildctl/common" "github.com/moby/buildkit/util/apicaps" "github.com/moby/buildkit/util/appdefaults"