diff --git a/pkg/build/nodeimage/internal/kube/builder_bazel.go b/pkg/build/nodeimage/internal/kube/builder_bazel.go index c6ae72f982..167ec07f67 100644 --- a/pkg/build/nodeimage/internal/kube/builder_bazel.go +++ b/pkg/build/nodeimage/internal/kube/builder_bazel.go @@ -17,6 +17,7 @@ limitations under the License. package kube import ( + "encoding/json" "fmt" "os" "path/filepath" @@ -81,42 +82,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, fmt.Errorf("could not find kubelet: %w", err) } - 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, fmt.Errorf("could not find kubeadm: %w", err) } - // - 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, fmt.Errorf("could not find kubectl: %w", err) } + // 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 +113,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 "", fmt.Errorf("failed to query action graph: %w", err) + } + var agc ActionGraphContainer + if err := json.Unmarshal(actionBytes, &agc); err != nil { + return "", fmt.Errorf("failed to unpack action graph container: %w", err) + } + + 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) +}