*: 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 <informatic@hackerspace.pl>
diff --git a/tools/BUILD b/tools/BUILD
index 64faf53..daf6c12 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 0000000..ac455b3
--- /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 0000000..1ef2b33
--- /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 0000000..c45b050
--- /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 0000000..35f00f3
--- /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 6151a38..04b4f79 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 "$@"