diff --git a/.envrc b/.envrc new file mode 100644 index 000000000000..4a59461f0cbe --- /dev/null +++ b/.envrc @@ -0,0 +1,8 @@ +# TODO(marun) Document usage of direnv + +# contains manually-installed tooling +PATH_add bin +# contains avalanchego and xsvm binaries +PATH_add build +# contains tool wrappers +PATH_add tools diff --git a/.gitignore b/.gitignore index c5a3cad32b13..9fd544a5af0e 100644 --- a/.gitignore +++ b/.gitignore @@ -62,3 +62,5 @@ tests/upgrade/upgrade.test vendor **/testdata + +.tool-downloads diff --git a/scripts/ginkgo.sh b/scripts/ginkgo.sh index 9beef8d73cec..02aade791234 100755 --- a/scripts/ginkgo.sh +++ b/scripts/ginkgo.sh @@ -3,4 +3,5 @@ set -euo pipefail # Run the ginkgo version from go.mod -go run github.com/onsi/ginkgo/v2/ginkgo "${@}" +AVALANCHE_PATH=$( cd "$( dirname "${BASH_SOURCE[0]}" )"; cd .. && pwd ) +${AVALANCHE_PATH}/scripts/av.sh tool ginkgo "$@" diff --git a/tests/av/cmd/main.go b/tests/av/cmd/main.go new file mode 100644 index 000000000000..f52558a97619 --- /dev/null +++ b/tests/av/cmd/main.go @@ -0,0 +1,172 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package main + +import ( + "errors" + "fmt" + "os" + "os/exec" + "path/filepath" + + "github.com/spf13/cobra" + "go.uber.org/zap" + + "github.com/ava-labs/avalanchego/tests" + "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/avalanchego/utils/perms" +) + +const ( + cmdName = "av" + kubectlVersion = "v1.30.2" +) + +func main() { + var rawLogFormat string + rootCmd := &cobra.Command{ + Use: cmdName, + Short: cmdName + " commands", + } + rootCmd.PersistentFlags().StringVar(&rawLogFormat, "log-format", logging.AutoString, logging.FormatDescription) + + toolCmd := &cobra.Command{ + Use: "tool", + Short: "configured cli tools", + RunE: func(*cobra.Command, []string) error { + return fmt.Errorf("please specify a valid tool name") + }, + } + rootCmd.AddCommand(toolCmd) + + ginkgoCmd := &cobra.Command{ + Use: "ginkgo", + Short: "cli for building and running e2e tests", + RunE: func(c *cobra.Command, args []string) error { + cmdArgs := []string{ + "run", + "github.com/onsi/ginkgo/v2/ginkgo", + } + cmdArgs = append(cmdArgs, args...) + cmd := exec.Command("go", cmdArgs...) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + c.SilenceUsage = true + c.SilenceErrors = true + // TODO(marun) Suppress the duplicated 'exit status X' + // caused by passing through the exit code from the + // `go run` subcommand + return cmd.Run() + }, + } + toolCmd.AddCommand(ginkgoCmd) + + tmpnetctlCmd := &cobra.Command{ + Use: "tmpnetctl", + Short: "cli for managing temporary networks", + RunE: func(c *cobra.Command, args []string) error { + cmdArgs := []string{ + "run", + "github.com/ava-labs/avalanchego/tests/fixture/tmpnet/cmd", + } + cmdArgs = append(cmdArgs, args...) + cmd := exec.Command("go", cmdArgs...) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + c.SilenceUsage = true + c.SilenceErrors = true + // TODO(marun) Suppress the duplicated 'exit status X' + // caused by passing through the exit code from the + // `go run` subcommand + return cmd.Run() + }, + } + toolCmd.AddCommand(tmpnetctlCmd) + + kubectlCmd := &cobra.Command{ + Use: "kubectl", + Short: "cli for interacting with a kubernetes cluster", + RunE: func(c *cobra.Command, args []string) error { + log, err := tests.LoggerForFormat("", rawLogFormat) + if err != nil { + return err + } + + downloadDir, err := filepath.Abs(".tool-downloads") + if err != nil { + return err + } + + // Ensure the download directory exists + if info, err := os.Stat(downloadDir); errors.Is(err, os.ErrNotExist) { + log.Info("creating tool download directory", + zap.String("dir", downloadDir), + ) + if err := os.MkdirAll(downloadDir, perms.ReadWriteExecute); err != nil { + return err + } + } else if err != nil { + return err + } else if !info.IsDir() { + return fmt.Errorf("download path is not a directory: %s", downloadDir) + } + + var ( + kubectlPath = downloadDir + "/kubectl-" + kubectlVersion + // TODO(marun) Make these dynamic + kubeOS = "linux" + kubeArch = "arm64" + ) + + if _, err := os.Stat(downloadDir); errors.Is(err, os.ErrNotExist) { + // TODO(marun) Maybe use a library to download the binary? + curlArgs := []string{ + "curl -L -o " + kubectlPath + " https://dl.k8s.io/release/" + kubectlVersion + "/bin/" + kubeOS + "/" + kubeArch + "/kubectl", + } + log.Info("downloading kubectl with curl", + zap.Strings("curlArgs", curlArgs), + ) + // Run in a subshell to ensure -L redirects work + curl := exec.Command("bash", append([]string{"-x", "-c"}, curlArgs...)...) + curl.Stdin = os.Stdin + curl.Stdout = os.Stdout + curl.Stderr = os.Stderr + c.SilenceUsage = true + c.SilenceErrors = true + err = curl.Run() + if err != nil { + return err + } + + if err := os.Chmod(kubectlPath, perms.ReadWriteExecute); err != nil { + return err + } + + log.Info("running kubectl for the first time", + zap.String("path", kubectlPath), + ) + } + + kubectl := exec.Command(kubectlPath, args...) + kubectl.Stdin = os.Stdin + kubectl.Stdout = os.Stdout + kubectl.Stderr = os.Stderr + c.SilenceUsage = true + c.SilenceErrors = true + return kubectl.Run() + }, + } + toolCmd.AddCommand(kubectlCmd) + + if err := rootCmd.Execute(); err != nil { + var exitCode int = 1 + if exitError, ok := err.(*exec.ExitError); ok { + exitCode = exitError.ExitCode() + } + os.Exit(exitCode) + } + os.Exit(0) +} diff --git a/tools/av b/tools/av new file mode 100755 index 000000000000..d515b0693ba1 --- /dev/null +++ b/tools/av @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +set -euo pipefail + +AVALANCHE_PATH=$( cd "$( dirname "${BASH_SOURCE[0]}" )"; cd .. && pwd ) +cd ${AVALANCHE_PATH} + +# Set the CGO flags to use the portable version of BLST +# +# We use "export" here instead of just setting a bash variable because we need +# to pass this flag to all child processes spawned by the shell. +export CGO_CFLAGS="-O2 -D__BLST_PORTABLE__" +# While CGO_ENABLED doesn't need to be explicitly set, it produces a much more +# clear error due to the default value change in go1.20. +export CGO_ENABLED=1 + +go run ./tests/av/cmd "${@}" diff --git a/tools/ginkgo b/tools/ginkgo new file mode 100755 index 000000000000..c466d2525c04 --- /dev/null +++ b/tools/ginkgo @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +set -euo pipefail + +TOOLS_DIR="$( dirname "${BASH_SOURCE[0]}" )" +${TOOLS_DIR}/av tool ginkgo "$@" diff --git a/tools/kubectl b/tools/kubectl new file mode 100755 index 000000000000..7c3c59016c88 --- /dev/null +++ b/tools/kubectl @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +set -euo pipefail + +TOOLS_DIR="$( dirname "${BASH_SOURCE[0]}" )" +${TOOLS_DIR}/av tool kubectl "$@" diff --git a/tools/tmpnetctl b/tools/tmpnetctl new file mode 100755 index 000000000000..4184dba3c81e --- /dev/null +++ b/tools/tmpnetctl @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +set -euo pipefail + +TOOLS_DIR="$( dirname "${BASH_SOURCE[0]}" )" +${TOOLS_DIR}/av tool tmpnetctl "$@"