From d291b5aad3fbd40407548dcd57af357175c72b54 Mon Sep 17 00:00:00 2001 From: Mike Danese Date: Wed, 9 Sep 2020 12:13:19 -0700 Subject: [PATCH] bazel: derive exec path of go binaries from action graph Output paths are not a stable API and all our conditional path construction here is fragile. This replaces the old path construction with queries to the action graph. Part of unblocking: kubernetes/kubernetes#94449 --- .../nodeimage/internal/kube/builder_bazel.go | 109 ++++++++++++------ 1 file changed, 75 insertions(+), 34 deletions(-) diff --git a/pkg/build/nodeimage/internal/kube/builder_bazel.go b/pkg/build/nodeimage/internal/kube/builder_bazel.go index c6ae72f982..1abe0bd1f8 100644 --- a/pkg/build/nodeimage/internal/kube/builder_bazel.go +++ b/pkg/build/nodeimage/internal/kube/builder_bazel.go @@ -17,10 +17,12 @@ limitations under the License. package kube import ( + "encoding/json" "fmt" "os" "path/filepath" + "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/exec" "sigs.k8s.io/kind/pkg/log" ) @@ -81,42 +83,27 @@ func (b *bazelBuilder) Build() (Bits, error) { return nil, err } - // https://docs.bazel.build/versions/master/output_directories.html - binDir := filepath.Join(b.kubeRoot, "bazel-bin") - buildDir := filepath.Join(binDir, "build") - bazelGoosGoarch := fmt.Sprintf("linux_%s", b.arch) - - // helpers to get the binary paths which may or may not be "pure" (no cgo) - // Except for kubelet, these are pure in Kubernetes 1.14+ - // kubelet is at bazel-bin/cmd/kubelet/kubelet since 1.14+ - // TODO: do we care about building 1.13 from source once we add support - // for building from release binaries? - // https://github.com/kubernetes/kubernetes/pull/73930 - strippedCommandPath := func(command string) string { - return filepath.Join( - binDir, "cmd", command, - fmt.Sprintf("%s_stripped", bazelGoosGoarch), command, - ) + // This is a manual dereference of the alias created here: + // + // https://github.com/kubernetes/kubernetes/blob/b56d0acaf5bead31bd17d3b88d4a167fcbac7866/build/go.bzl#L45 + kubeletPath, err := findGoBinary("//cmd/kubelet:_kubelet-cgo") + if err != nil { + return nil, errors.Wrap(err, "could not find kubelet") } - commandPathPureOrNot := func(command string) string { - strippedPath := strippedCommandPath(command) - pureStrippedPath := filepath.Join( - binDir, "cmd", command, - fmt.Sprintf("%s_pure_stripped", bazelGoosGoarch), command, - ) - // if the new path doesn't exist, do the old path - if _, err := os.Stat(pureStrippedPath); os.IsNotExist(err) { - return strippedPath - } - return pureStrippedPath + + kubeadmPath, err := findGoBinary("//cmd/kubeadm:kubeadm") + if err != nil { + return nil, errors.Wrap(err, "could not find kubeadm") } - // - kubeletPath := filepath.Join(binDir, "cmd", "kubelet", "kubelet") - oldKubeletPath := strippedCommandPath("kubelet") - if _, err := os.Stat(kubeletPath); os.IsNotExist(err) { - kubeletPath = oldKubeletPath + + kubectlPath, err := findGoBinary("//cmd/kubectl:kubectl") + if err != nil { + return nil, errors.Wrap(err, "could not find kubectl") } + // https://docs.bazel.build/versions/master/output_directories.html + buildDir := filepath.Join(b.kubeRoot, "bazel-bin/build") + // return the paths return &bits{ imagePaths: []string{ @@ -127,9 +114,63 @@ func (b *bazelBuilder) Build() (Bits, error) { }, binaryPaths: []string{ kubeletPath, - commandPathPureOrNot("kubeadm"), - commandPathPureOrNot("kubectl"), + kubeadmPath, + kubectlPath, }, version: version, }, nil } + +func findGoBinary(label string) (string, error) { + // This output of bazel aquery --output=jsonproto is an ActionGraphContainer + // as defined in: + // + // https://cs.opensource.google/bazel/bazel/+/master:src/main/protobuf/analysis.proto + type ( + Action struct { + Mnemonic string + OutputIds []string + } + Artifact struct { + Id string + ExecPath string + } + ActionGraphContainer struct { + Artifacts []Artifact + Actions []Action + } + ) + + cmd := exec.Command("bazel", "aquery", "--output=jsonproto", label) + actionBytes, err := exec.Output(cmd) + if err != nil { + return "", errors.Wrap(err, "failed to query action graph") + } + var agc ActionGraphContainer + if err := json.Unmarshal(actionBytes, &agc); err != nil { + return "", errors.Wrap(err, "failed to unpack action graph container") + } + + var linkActions []Action + for _, action := range agc.Actions { + if action.Mnemonic == "GoLink" { + linkActions = append(linkActions, action) + } + } + if len(linkActions) != 1 { + return "", fmt.Errorf("unexpected number of link actions %d, wanted 1", len(linkActions)) + } + linkAction := linkActions[0] + if len(linkAction.OutputIds) != 1 { + return "", fmt.Errorf("unexpected number of link action outputs %d, wanted 1", len(linkAction.OutputIds)) + } + outputID := linkAction.OutputIds[0] + + for _, artifact := range agc.Artifacts { + if artifact.Id == outputID { + return artifact.ExecPath, nil + } + } + // We really should never get here + return "", fmt.Errorf("could not find artifact corresponding to output id %q", outputID) +}