From 0f8e5a21324d10590e7a015bf4946f7c857f87b5 Mon Sep 17 00:00:00 2001 From: Serge Bazanski Date: Sat, 16 Oct 2021 20:53:51 +0000 Subject: [PATCH] *: do not require env.sh This removes the need to source env.{sh,fish} when working with hscloud. This is done by: 1. Implementing a Go library to reliably detect the location of the active hscloud checkout. That in turn is enabled by BUILD_WORKSPACE_DIRECTORY being now a thing in Bazel. 2. Creating a tool `hscloud`, with a command `hscloud workspace` that returns the workspace path. 3. Wrapping this tool to be accessible from Python and Bash. 4. Bumping all users of hscloud_root to use either the Go library or one of the two implemented wrappers. We also drive-by replace tools/install.sh to be a proper sh_binary, and make it yell at people if it isn't being ran as `bazel run //tools:install`. Finally, we also drive-by delete cluster/tools/nixops.sh which was never used. Change-Id: I7873714319bfc38bbb930b05baa605c5aa36470a Reviewed-on: https://gerrit.hackerspace.pl/c/hscloud/+/1169 Reviewed-by: informatic --- cluster/clustercfg/BUILD | 1 + cluster/clustercfg/clustercfg.py | 5 +- cluster/doc/user.md | 5 +- cluster/prodaccess/BUILD.bazel | 1 + cluster/prodaccess/kubernetes.go | 13 ++-- cluster/tools/BUILD | 9 ++- cluster/tools/calicoctl.sh | 33 +++++----- cluster/tools/install.sh | 17 ------ cluster/tools/nixops.sh | 22 ------- env.fish | 2 + env.sh | 13 ++-- go/workspace/BUILD.bazel | 8 +++ go/workspace/workspace.go | 101 +++++++++++++++++++++++++++++++ shell.nix | 2 +- tools/BUILD | 11 ++++ tools/hscloud/BUILD.bazel | 46 ++++++++++++++ tools/hscloud/lib.py | 45 ++++++++++++++ tools/hscloud/lib.sh | 65 ++++++++++++++++++++ tools/hscloud/main.go | 37 +++++++++++ tools/install.sh | 56 ++++++++++++++--- 20 files changed, 409 insertions(+), 83 deletions(-) delete mode 100755 cluster/tools/install.sh delete mode 100755 cluster/tools/nixops.sh create mode 100644 go/workspace/BUILD.bazel create mode 100644 go/workspace/workspace.go create mode 100644 tools/hscloud/BUILD.bazel create mode 100644 tools/hscloud/lib.py create mode 100644 tools/hscloud/lib.sh create mode 100644 tools/hscloud/main.go diff --git a/cluster/clustercfg/BUILD b/cluster/clustercfg/BUILD index 433f79ce..e08a2e30 100644 --- a/cluster/clustercfg/BUILD +++ b/cluster/clustercfg/BUILD @@ -15,5 +15,6 @@ py_binary( requirement("idna"), requirement("six"), "//tools:secretstore_lib", + "//tools/hscloud:python", ], ) diff --git a/cluster/clustercfg/clustercfg.py b/cluster/clustercfg/clustercfg.py index d852d6ac..ea15df8b 100644 --- a/cluster/clustercfg/clustercfg.py +++ b/cluster/clustercfg/clustercfg.py @@ -16,13 +16,12 @@ from cryptography.hazmat.backends import default_backend import fabric from tools import secretstore +from tools.hscloud import lib as hscloud import ca -local_root = os.getenv('hscloud_root') -if local_root is None: - raise Exception("Please source env.sh") +local_root = hscloud.workspace_location() cluster = 'k0.hswaw.net' diff --git a/cluster/doc/user.md b/cluster/doc/user.md index f04e7dbb..11ea9594 100644 --- a/cluster/doc/user.md +++ b/cluster/doc/user.md @@ -19,7 +19,10 @@ Before you can use `kubectl`, however, you will need to authenticate yourself. T Enter SSO/LDAP password for q3k@hackerspace.pl: Good evening professor. I see you have driven here in your Ferrari. -If `prodaccess` is not on your $PATH, ensure you have sourced `env.sh` from the root of hscloud and ran `tools/install.sh`. +If `prodaccess` is not on your $PATH: + + $ bazel run //tools:install + $ . env.sh By default, `prodaccess` will use your local user name to authenticate as `@hackerspce.pl`. If your Hackerspace SSO name is different, specify it using the `-u` flag to prodaccess, eg. `prodaccess -u informatic`. diff --git a/cluster/prodaccess/BUILD.bazel b/cluster/prodaccess/BUILD.bazel index 6c720823..4db48dd7 100644 --- a/cluster/prodaccess/BUILD.bazel +++ b/cluster/prodaccess/BUILD.bazel @@ -13,6 +13,7 @@ go_library( "//cluster/certs:go_default_library", "//cluster/prodvider/proto:go_default_library", "//go/pki:go_default_library", + "//go/workspace:go_default_library", "@com_github_golang_glog//:go_default_library", "@org_golang_google_grpc//:go_default_library", "@org_golang_google_grpc//credentials:go_default_library", diff --git a/cluster/prodaccess/kubernetes.go b/cluster/prodaccess/kubernetes.go index 72264236..c50cd072 100644 --- a/cluster/prodaccess/kubernetes.go +++ b/cluster/prodaccess/kubernetes.go @@ -14,17 +14,18 @@ import ( "github.com/golang/glog" pb "code.hackerspace.pl/hscloud/cluster/prodvider/proto" + "code.hackerspace.pl/hscloud/go/workspace" ) func kubernetesPaths() (string, string, string) { - localRoot := os.Getenv("hscloud_root") - if localRoot == "" { - glog.Exitf("Please source env.sh") + ws, err := workspace.Get() + if err != nil { + glog.Exitf("%v", err) } - localKey := path.Join(localRoot, ".kubectl", fmt.Sprintf("%s.key", flagUsername)) - localCert := path.Join(localRoot, ".kubectl", fmt.Sprintf("%s.crt", flagUsername)) - localCA := path.Join(localRoot, ".kubectl", fmt.Sprintf("ca.crt")) + localKey := path.Join(ws, ".kubectl", fmt.Sprintf("%s.key", flagUsername)) + localCert := path.Join(ws, ".kubectl", fmt.Sprintf("%s.crt", flagUsername)) + localCA := path.Join(ws, ".kubectl", fmt.Sprintf("ca.crt")) return localKey, localCert, localCA } diff --git a/cluster/tools/BUILD b/cluster/tools/BUILD index 2526fd74..a63245eb 100644 --- a/cluster/tools/BUILD +++ b/cluster/tools/BUILD @@ -22,7 +22,14 @@ copy_go_binary( sh_binary( name = "calicoctl", srcs = ["calicoctl.sh"], - data = [":calicoctl.bin", "//tools:secretstore"], + data = [ + ":calicoctl.bin", + "//tools:secretstore", + "//tools/hscloud", + ], + deps = [ + "//tools/hscloud:shell", + ], ) copy_go_binary( diff --git a/cluster/tools/calicoctl.sh b/cluster/tools/calicoctl.sh index 30fe6526..86e1097e 100755 --- a/cluster/tools/calicoctl.sh +++ b/cluster/tools/calicoctl.sh @@ -1,23 +1,22 @@ #!/usr/bin/env bash -# A wrapper around the real calicoctl to configure etcd access... +# A wrapper around the real calicoctl to configure etcd access. -if [ -z "$hscloud_root" ]; then - echo 2>&1 "Please source env.sh" - exit 1 -fi +source tools/hscloud/lib.sh || exit 1 -ETCD_ENDPOINTS="https://bc01n01.hswaw.net:2379,https://bc01n01.hswaw.net:2379,https://bc01n01.hswaw.net:2379" -ETCD_KEY_FILE="$hscloud_root/cluster/secrets/plain/etcd-calico.key" -ETCD_CERT_FILE="$hscloud_root/cluster/certs/etcd-calico.cert" -ETCD_CA_CERT_FILE="$hscloud_root/cluster/certs/ca-etcd.crt" +function main() { + local ws=$(hscloud::workspace_location) -if [ ! -f "$ETCD_KEY_FILE" ] ; then - secretstore decrypt "$hscloud_root/cluster/secrets/cipher/etcd-calico.key" > "$ETCD_KEY_FILE" -fi + export ETCD_ENDPOINTS="https://bc01n01.hswaw.net:2379,https://bc01n01.hswaw.net:2379,https://bc01n01.hswaw.net:2379" + export ETCD_KEY_FILE="$ws/cluster/secrets/plain/etcd-calico.key" + export ETCD_CERT_FILE="$ws/cluster/certs/etcd-calico.cert" + export ETCD_CA_CERT_FILE="$ws/cluster/certs/ca-etcd.crt" -export ETCD_ENDPOINTS -export ETCD_KEY_FILE -export ETCD_CERT_FILE -export ETCD_CA_CERT_FILE -calicoctl.bin "$@" + if [ ! -f "$ETCD_KEY_FILE" ] ; then + $(hscloud::must_rlocation hscloud/tools/secretstore) decrypt "$ws/cluster/secrets/cipher/etcd-calico.key" "$ETCD_KEY_FILE" + fi + + "$(hscloud::must_rlocation hscloud/cluster/tools/calicoctl.bin)" "$@" +} + +main "$@" diff --git a/cluster/tools/install.sh b/cluster/tools/install.sh deleted file mode 100755 index 6f32fbb3..00000000 --- a/cluster/tools/install.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env bash - -set -e -o pipefail - -if [ -z "$hscloud_root" ]; then - echo 2>&1 "Please first source env.sh" - exit 1 -fi - -cd "${hscloud_root}" - -bazel build \ - //cluster/tools:kubectl \ - //cluster/tools:kubecfg \ - //cluster/tools:calicoctl \ - //cluster/tools:cfssl - diff --git a/cluster/tools/nixops.sh b/cluster/tools/nixops.sh deleted file mode 100755 index e8109729..00000000 --- a/cluster/tools/nixops.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env bash - -# A wrapper around real nixops to decrypt GCP secret. - -if [ -z "$hscloud_root" ]; then - echo 2>&1 "Please source env.sh" - exit 1 -fi - -for f in sa.json sa.pem; do - plain="$hscloud_root/gcp/secrets/plain/$f" - cipher="$hscloud_root/gcp/secrets/cipher/$f" - if [ ! -f "$plain" ]; then - secretstore decrypt "$cipher" > "$plain" - fi -done - -export GCE_PROJECT="hscloud" -export GCE_SERVICE_ACCOUNT="nixops@hscloud.iam.gserviceaccount.com" -export ACCESS_KEYPATH="$hscloud_root/gcp/secrets/plain/sa.pem" - -./external/nixops/bin/nixops "$@" diff --git a/env.fish b/env.fish index 428c5df5..02cfa772 100644 --- a/env.fish +++ b/env.fish @@ -11,6 +11,8 @@ if not string match -q $hscloud_path $PATH set -x PATH $hscloud_path $PATH end +# Leftover junk. This should be removed, as env.fish is now optional. +# Do _not_ add more aliases! function gpg-unlock echo "test" | gpg2 --sign --batch --no-tty -o /dev/null end diff --git a/env.sh b/env.sh index e9cfd220..0723030b 100644 --- a/env.sh +++ b/env.sh @@ -1,23 +1,24 @@ -# source me to have all the nice things +# Source this file to have hscloud tools available in your PATH after running +# `bazel run //tools:install`. if [ "$0" == "$BASH_SOURCE" ]; then echo "You should be sourcing this." exit 1 fi -export hscloud_root="$( cd "$(dirname "$BASH_SOURCE")"; pwd -P )" - +hscloud_root="$( cd "$(dirname "$BASH_SOURCE")"; pwd -P )" if [ ! -f "$hscloud_root/WORKSPACE" ]; then echo "Could not find WORKSPACE" exit 1 fi - hscloud_path="$hscloud_root/bazel-bin/tools:$hscloud_root/bazel-bin/cluster/tools" - [[ ":$PATH:" != *":$hscloud_path:"* ]] && PATH="$hscloud_path:${PATH}" +unset -f hscloud_root +unset -f hscloud_path +# Leftover junk. This should be removed, as env.sh is now optional. +# Do _not_ add more aliases! alias bajzel=bazel - gpg-unlock() { echo "test" | gpg2 --sign --batch --no-tty -o /dev/null } diff --git a/go/workspace/BUILD.bazel b/go/workspace/BUILD.bazel new file mode 100644 index 00000000..34f8acc2 --- /dev/null +++ b/go/workspace/BUILD.bazel @@ -0,0 +1,8 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["workspace.go"], + importpath = "code.hackerspace.pl/hscloud/go/workspace", + visibility = ["//visibility:public"], +) diff --git a/go/workspace/workspace.go b/go/workspace/workspace.go new file mode 100644 index 00000000..f8f2ef9f --- /dev/null +++ b/go/workspace/workspace.go @@ -0,0 +1,101 @@ +package workspace + +import ( + "fmt" + "os" + "path" + "sync" + "syscall" +) + +// isWorkspace returns whether a given string is a valid path pointing to a +// Bazel workspace directory. +func isWorkspace(dir string) bool { + w := path.Join(dir, "WORKSPACE") + if _, err := os.Stat(w); err == nil { + return true + } + return false +} + +// getPathFSID returns an opaque filesystem identifier for a given path. +func getPathFSID(dir string) (uint64, error) { + st, err := os.Stat(dir) + if err != nil { + // No need to wrap err, as stat errors are already quite explicit + // (they also include the stat'd path). + return 0, err + } + switch x := st.Sys().(type) { + case *syscall.Stat_t: + return x.Dev, nil + default: + return 0, fmt.Errorf("unsupported operating system (got stat type %+v)", st.Sys()) + } +} + +// lookForWorkspace recurses up a directory until it finds a WORKSPACE, the +// root, or crosses a filesystem boundary. +func lookForWorkspace(dir string) (string, error) { + fsid, err := getPathFSID(dir) + if err != nil { + return "", fmt.Errorf("could not get initial FSID: %w", err) + } + for { + if dir == "." || dir == "/" || dir == "" { + return "", fmt.Errorf("got up to root before finding workspace") + } + + fsid2, err := getPathFSID(dir) + if err != nil { + return "", fmt.Errorf("could not get parent FWID: %w", err) + } + if fsid2 != fsid { + return "", fmt.Errorf("crossed filesystem boundaries before finding workspace") + } + + if isWorkspace(dir) { + return dir, nil + } + dir = path.Dir(dir) + } +} + +// Get returns the workspace directory from which a given +// command line tool is running. This handles the following +// cases: +// +// 1. The command line tool was invoked via `bazel run`. +// 2. The command line tool was started in the workspace directory or a +// subdirectory. +// +// If the workspace directory path cannot be inferred based on the above +// assumptions, an error is returned. +func Get() (string, error) { + workspaceOnce.Do(func() { + workspace, workspaceErr = get() + }) + return workspace, workspaceErr +} + +var ( + workspace string + workspaceErr error + workspaceOnce sync.Once +) + +func get() (string, error) { + if p := os.Getenv("BUILD_WORKSPACE_DIRECTORY"); p != "" && isWorkspace(p) { + return p, nil + } + + wd, err := os.Getwd() + if err != nil { + return "", err + } + p, err := lookForWorkspace(wd) + if err != nil { + return "", fmt.Errorf("not invoked from `bazel run` and could not find workspace root by traversing upwards: %w", err) + } + return p, nil +} diff --git a/shell.nix b/shell.nix index 080aca05..e295a367 100644 --- a/shell.nix +++ b/shell.nix @@ -10,7 +10,7 @@ in with hscloud.pkgs; let '' source /etc/profile source ${toString ./.}/env.sh - ${toString ./.}/tools/install.sh + bazel run //tools:install # Fancy colorful PS1 to make people notice easily they're in hscloud. PS1='\[\033]0;\u/hscloud:\w\007\]' diff --git a/tools/BUILD b/tools/BUILD index 64faf531..daf6c121 100644 --- a/tools/BUILD +++ b/tools/BUILD @@ -1,6 +1,17 @@ load("@bazel_tools//tools/build_defs/pkg:pkg.bzl", "pkg_tar", "pkg_deb") load("//bzl:rules.bzl", "copy_go_binary") +sh_binary( + name = "install", + srcs = [ + "install.sh", + ], + visibility = ["//visibility:public"], + deps = [ + "//tools/hscloud:shell", + ], +) + py_library( name = "secretstore_lib", srcs = ["secretstore.py"], diff --git a/tools/hscloud/BUILD.bazel b/tools/hscloud/BUILD.bazel new file mode 100644 index 00000000..ac455b3d --- /dev/null +++ b/tools/hscloud/BUILD.bazel @@ -0,0 +1,46 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "hscloud_lib", + srcs = ["main.go"], + importpath = "code.hackerspace.pl/hscloud/tools/hscloud", + visibility = ["//visibility:private"], + deps = [ + "//go/workspace:go_default_library", + "@com_github_spf13_cobra//:go_default_library", + ], +) + +go_binary( + name = "hscloud", + embed = [":hscloud_lib"], + visibility = ["//visibility:public"], +) + +sh_library( + name = "shell", + srcs = [ + "lib.sh", + ], + deps = [ + "@bazel_tools//tools/bash/runfiles", + ], + data = [ + ":hscloud", + ], + visibility = ["//visibility:public"], +) + +py_library( + name = "python", + srcs = [ + "lib.py", + ], + deps = [ + "@rules_python//python/runfiles", + ], + data = [ + ":hscloud", + ], + visibility = ["//visibility:public"], +) diff --git a/tools/hscloud/lib.py b/tools/hscloud/lib.py new file mode 100644 index 00000000..1ef2b330 --- /dev/null +++ b/tools/hscloud/lib.py @@ -0,0 +1,45 @@ +# Library to interact with the active hscloud checkout. This supersedes the +# hscloud_root environment variable once used in hscloud. +# +# Some of this could be implemented in Python instead of shelling out to a Go +# binary - but that way we have a single source of truth, even if it's janky. +# +# To use: +# +# from tools.hscloud import lib as hscloud +# +# And specify deps = [ "//tools/hscloud:python" ] in py_binary. + +import subprocess + +from rules_python.python.runfiles import runfiles + + +r = runfiles.Create() + + +def tool_location(): + """ + Return an absolute path to a built //tools/hscloud binary, ready to run. + """ + rloc = r.Rlocation("hscloud/tools/hscloud/hscloud_/hscloud") + if rloc is None: + raise Exception("Could not find location of hscloud - are you in a valid checkout?") + return rloc + + +def workspace_location(): + """Return an absolute path to the hscloud checkout.""" + return subprocess.check_output([tool_location(), "workspace"]).decode() + + +def must_rlocation(runfile): + """Return an absolute path to a runfile, eg. a data depndency in sh_binary.""" + rloc = r.Rlocation(runfile) + if rloc is None: + msg = f"Could not find runfile {runfile}" + manifest = os.environ.get("RUNFILES_MANIFEST_FILE", "") + if manifest != "": + msg += f"; manifest file: {manifest}" + raise Exception(msg) + return rloc diff --git a/tools/hscloud/lib.sh b/tools/hscloud/lib.sh new file mode 100644 index 00000000..c45b050a --- /dev/null +++ b/tools/hscloud/lib.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash + +# Top-level 'universal' shell library for hscloud. All sh_binary targets should +# depend on this and import it as follows: +# +# #!/usr/bin/env bash +# source tools/hscloud/lib.sh || exit 1 +# +# And by specifying deps = [ "//tools/hscloud:shell" ] in sh_binary. + +set -e -u -o pipefail + +function hscloud::_prepare_runfiles() { + if [[ $(type -t rlocation) == function ]]; then + return + fi + # --- begin runfiles.bash initialization v2 --- + # Mostly copy-pasted from the Bazel Bash runfiles library v2. + local f=bazel_tools/tools/bash/runfiles/runfiles.bash + source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \ + source "$0.runfiles/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + { echo>&2 "ERROR: cannot find $f - are you in a valid checkout and running the script via bazel?"; exit 1; } + # --- end runfiles.bash initialization v2 --- +} + +# Return an absolute path to a built //tools/hscloud binary, ready to run. +# +# This will fail if we're not in a hscloud checkout. +function hscloud::tool_location() { + rloc="$(hscloud::rlocation "hscloud/tools/hscloud/hscloud_/hscloud")" + if [ -z "$rloc" ]; then + echo "Could not find location of hscloud - are you in a valid checkout and running the script via bazel?" >&2 + exit 1 + fi + echo "$rloc" +} + +# Return an absolute path to the hscloud checkout. +function hscloud::workspace_location() { + $(hscloud::tool_location) workspace +} + +# Return an absolute path to a runfile, eg. a data dependency in sh_binary. +function hscloud::rlocation() { + hscloud::_prepare_runfiles + echo "$(rlocation "$1")" +} + +# Return an absolute path to a runfile, eg. a data dependency in sh_binary. +# +# This will fail if the runfile is not found. +function hscloud::must_rlocation() { + rloc="$(hscloud::rlocation $1)" + if [ -z "$rloc" ]; then + echo "Could not find runfile $1" >&2 + if [ ! -z "${RUNFILES_MANIFEST_FILE:-}" ]; then + echo "Manifest file: $RUNFILES_MANIFEST_FILE" >&2 + fi + exit 1 + fi + echo "$rloc" +} diff --git a/tools/hscloud/main.go b/tools/hscloud/main.go new file mode 100644 index 00000000..35f00f3d --- /dev/null +++ b/tools/hscloud/main.go @@ -0,0 +1,37 @@ +package main + +import ( + "fmt" + "os" + + "code.hackerspace.pl/hscloud/go/workspace" + "github.com/spf13/cobra" +) + +var rootCmd = &cobra.Command{ + Use: "hscloud", + Short: "hscloud kitchesink tool", + Long: `A single entrypoint tool to interact with a hscloud git checkout.`, +} + +var workspaceCmd = &cobra.Command{ + Use: "workspace", + Short: "Print root path of hscloud checkuot", + Long: `This returns the directory path containing WORKSPACE. It works both from 'bazel run', when invoked as a tool in bazel or when called manually. Feel free to use this in your sh_binary scripts.`, + Run: func(cmd *cobra.Command, args []string) { + wd, err := workspace.Get() + if err != nil { + fmt.Fprintf(os.Stderr, "%v", err) + os.Exit(1) + } + fmt.Println(wd) + }, +} + +func main() { + rootCmd.AddCommand(workspaceCmd) + if err := rootCmd.Execute(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} diff --git a/tools/install.sh b/tools/install.sh index 6151a382..04b4f792 100755 --- a/tools/install.sh +++ b/tools/install.sh @@ -1,14 +1,52 @@ #!/usr/bin/env bash +source tools/hscloud/lib.sh || exit 1 -set -e -o pipefail +function main() { + # If we're not running from `bazel run/buld`, complain and re-execute + # ourselves. + # + # We do the check fairly low level, as //tools/hscloud:lib.sh will just + # fail in this case. We want to be nice. + # + # This is all mostly copied from the runfiles.bash snippet in + # tools/hscloud/lib.sh. + f=bazel_tools/tools/bash/runfiles/runfiles.bash + if [ ! -e "${RUNFILES_DIR:-/dev/null}/$f" ] && \ + [ ! -e "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" ] && \ + [ ! -e "$0.runfiles/$f" ] && \ + [ ! -e "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" ] && \ + [ ! -e "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" ]; then + echo "Uh-oh, looks like you didn't run this as 'bazel run //tools:install'.">&2 + echo "Let me fix that for you in a few seconds - but work on your muscle memory, as we'll stop supporting this by some point.">&2 + sleep 2 + bazel run //tools:install -- "$@" + exit $? + fi -if [ -z "$hscloud_root" ]; then - echo 2>&1 "Please first source env.sh" - exit 1 -fi + cd $(hscloud::workspace_location) + echo "Building hscloud tools and cluster tools..." + bazel build //tools/... //cluster/tools/... -cd "${hscloud_root}" + local path_missing="" + local path="$(hscloud::workspace_location)/bazel-bin/tools" + if [[ ":$PATH:" == *":$path:"* ]]; then + path_missing="$path" + fi + local path="$(hscloud::workspace_location)/bazel-bin/cluster/tools" + if [[ ":$PATH:" == *":$path:"* ]]; then + if [ -z "$path_missing" ]; then + path_missing="$path" + else + path_missing="$path_missing:$path" + fi + fi + if [ -z "$path_missing" ]; then + echo "Tools built correctly, but your PATH should be updated to access them:">&2 + echo ' PATH="$PATH:'$path_missing'"' + echo 'Add the above line to your shell profile, or source env.sh from the root of hscloud.' + else + echo "Tools built correctly and in PATH. Happy hsclouding!" + fi +} -bazel build //tools/... - -cluster/tools/install.sh +main "$@"