Skip to content

Commit

Permalink
command line interface (#60)
Browse files Browse the repository at this point in the history
* implement command line interface

Co-authored-by: Seth Hoenig <seth.a.hoenig@gmail.com>

* cli: make `hc-install install` a subcommand

* Apply suggestions from code review

Co-authored-by: Radek Simko <radek.simko@gmail.com>

* run go mod tidy

* switch to use Install method

* create the product on the fly

* remove unused functions, return nicer success message

* return installed path directly from Install method

* fix bad return code

Co-authored-by: kmoe <5575356+kmoe@users.noreply.github.com>

* go fmt ./...

Co-authored-by: Seth Hoenig <seth.a.hoenig@gmail.com>
Co-authored-by: Radek Simko <radek.simko@gmail.com>
Co-authored-by: kmoe <5575356+kmoe@users.noreply.github.com>
  • Loading branch information
4 people committed Jun 30, 2022
1 parent eb7dc86 commit c241e27
Show file tree
Hide file tree
Showing 4 changed files with 221 additions and 131 deletions.
120 changes: 120 additions & 0 deletions cmd/hc-install/cmd_install.go
@@ -0,0 +1,120 @@
package main

import (
"context"
"flag"
"fmt"
"os"
"runtime"
"strings"

"github.com/hashicorp/go-version"
"github.com/mitchellh/cli"

hci "github.com/hashicorp/hc-install"
"github.com/hashicorp/hc-install/product"
"github.com/hashicorp/hc-install/releases"
"github.com/hashicorp/hc-install/src"
)

type InstallCommand struct {
Ui cli.Ui
}

func (c *InstallCommand) Name() string { return "install" }

func (c *InstallCommand) Synopsis() string {
return "Install a HashiCorp product"
}

func (c *InstallCommand) Help() string {
helpText := `
Usage: hc-install install [options] -version <version> <product>
This command installs a HashiCorp product.
Options:
-version [REQUIRED] Version of product to install.
-path Path to directory where the product will be installed. Defaults
to current working directory.
`
return strings.TrimSpace(helpText)
}

func (c *InstallCommand) Run(args []string) int {
var (
version string
installDirPath string
)

fs := flag.NewFlagSet("install", flag.ExitOnError)
fs.Usage = func() { c.Ui.Output(c.Help()) }
fs.StringVar(&version, "version", "", "version of product to install")
fs.StringVar(&installDirPath, "path", "", "path to directory where production will be installed")

if err := fs.Parse(args); err != nil {
return 1
}

// golang's arg parser is Posix-compliant but doesn't match the
// common GNU flag parsing argument, so force an error rather than
// silently dropping the options
args = fs.Args()
if len(args) != 1 {
c.Ui.Error(`This command requires one positional argument: <product>
Option flags must be provided before the positional argument`)
return 1
}
product := fs.Args()[0]

if version == "" {
c.Ui.Error("-version flag is required")
return 1
}

if installDirPath == "" {
cwd, err := os.Getwd()
if err != nil {
c.Ui.Error(fmt.Sprintf("Could not get current working directory for default installation path: %v", err))
return 1
}
installDirPath = cwd
}

installedPath, err := c.install(product, version, installDirPath)
if err != nil {
msg := fmt.Sprintf("failed to install %s@%s: %v", product, version, err)
c.Ui.Error(msg)
return 1
}

c.Ui.Info(fmt.Sprintf("installed %s@%s to %s", product, version, installedPath))
return 0
}

func (c *InstallCommand) install(project, tag, installDirPath string) (string, error) {
msg := fmt.Sprintf("hc-install: will install %s@%s", project, tag)
c.Ui.Info(msg)

v, err := version.NewVersion(tag)
if err != nil {
return "", fmt.Errorf("invalid version: %w", err)
}
i := hci.NewInstaller()

source := &releases.ExactVersion{
Product: product.Product{
Name: project,
BinaryName: func() string {
if runtime.GOOS == "windows" {
return fmt.Sprintf("%s.exe", project)
}
return project
},
},
Version: v,
InstallDir: installDirPath,
}

ctx := context.Background()
return i.Install(ctx, []src.Installable{source})
}
174 changes: 45 additions & 129 deletions cmd/hc-install/main.go
@@ -1,131 +1,47 @@
package main

// import (
// "context"
// "flag"
// "io/ioutil"
// "log"
// "os"
// "strings"

// "github.com/hashicorp/logutils"
// "github.com/mitchellh/cli"

// "github.com/hashicorp/hcinstall"
// )

// // TODO: add versioning to this?
// const userAgentAppend = "hcinstall-cli"

// func main() {
// filter := &logutils.LevelFilter{
// Levels: []logutils.LogLevel{"DEBUG", "WARN", "ERROR"},
// MinLevel: logutils.LogLevel("WARN"),
// Writer: os.Stderr,
// }
// log.SetOutput(filter)

// ui := &cli.ColoredUi{
// ErrorColor: cli.UiColorRed,
// WarnColor: cli.UiColorYellow,
// Ui: &cli.BasicUi{
// Reader: os.Stdin,
// Writer: os.Stdout,
// ErrorWriter: os.Stderr,
// },
// }

// exitStatus := run(ui, os.Args[1:])

// os.Exit(exitStatus)
// }

// func help() string {
// return `Usage: hcinstall [--dir=DIR] VERSION-OR-REF

// Downloads, verifies, and installs a official releases of a binary
// from releases.hashicorp.com or downloads, compiles, and installs a version of
// the the binary from the GitHub repository.

// To download an official release, pass "latest" or a valid semantic versioning
// version string.

// To download and compile a version of the binary from the GitHub
// repository pass a ref in the form "refs/...", some examples are shown below.

// If a binary is successfully installed, its path will be printed to stdout.

// Unless --dir is given, the default system temporary directory will be used.

// Options:
// --dir Directory into which to install the terraform binary. The
// directory must exist.

// Examples:
// hcinstall terraform 0.12.28
// hcinstall consul latest
// hcinstall terraform 0.13.0-beta3
// hcinstall --dir=/home/kmoe/bin 0.12.28
// hcinstall refs/heads/master
// hcinstall refs/tags/v0.12.29
// hcinstall refs/pull/25633/head
// `
// }

// func run(ui cli.Ui, args []string) int {
// ctx := context.Background()

// args = os.Args[1:]
// flags := flag.NewFlagSet("", flag.ExitOnError)
// var tfDir string
// flags.StringVar(&tfDir, "dir", "", "Local directory into which to install terraform")

// err := flags.Parse(args)
// if err != nil {
// ui.Error(err.Error())
// return 1
// }

// if flags.NArg() != 1 {
// ui.Error("Please specify VERSION-OR-REF")
// ui.Output(help())
// return 127
// }

// tfVersion := flags.Args()[0]

// if tfDir == "" {
// tfDir, err = ioutil.TempDir("", "hcinstall")
// if err != nil {
// ui.Error(err.Error())
// return 1
// }
// }

// var findArgs []hcinstall.ExecPathFinder

// switch {
// case tfVersion == "latest":
// finder := hcinstall.LatestVersion(tfDir, false)
// finder.UserAgent = userAgentAppend
// findArgs = append(findArgs, finder)
// case strings.HasPrefix(tfVersion, "refs/"):
// findArgs = append(findArgs, gitref.Install(tfVersion, "", tfDir))
// default:
// if strings.HasPrefix(tfVersion, "v") {
// tfVersion = tfVersion[1:]
// }
// finder := hcinstall.ExactVersion(tfVersion, tfDir)
// finder.UserAgent = userAgentAppend
// findArgs = append(findArgs, finder)
// }

// tfPath, err := hcinstall.Find(ctx, findArgs...)
// if err != nil {
// ui.Error(err.Error())
// return 1
// }

// ui.Output(tfPath)
// return 0
// }
import (
"log"
"os"

"github.com/hashicorp/hc-install/internal/version"

"github.com/hashicorp/logutils"
"github.com/mitchellh/cli"
)

func main() {
filter := &logutils.LevelFilter{
Levels: []logutils.LogLevel{"DEBUG", "WARN", "ERROR"},
MinLevel: logutils.LogLevel("WARN"),
Writer: os.Stderr,
}
log.SetOutput(filter)

ui := &cli.ColoredUi{
ErrorColor: cli.UiColorRed,
WarnColor: cli.UiColorYellow,
Ui: &cli.BasicUi{
Reader: os.Stdin,
Writer: os.Stdout,
ErrorWriter: os.Stderr,
},
}

c := cli.NewCLI("hc-install", version.ModuleVersion())
c.Args = os.Args[1:]
c.Commands = map[string]cli.CommandFactory{
"install": func() (cli.Command, error) {
return &InstallCommand{
Ui: ui,
}, nil
},
}

exitStatus, err := c.Run()
if err != nil {
ui.Error(err.Error())
}

os.Exit(exitStatus)
}
4 changes: 4 additions & 0 deletions go.mod
Expand Up @@ -9,5 +9,9 @@ require (
github.com/hashicorp/go-cleanhttp v0.5.2
github.com/hashicorp/go-multierror v1.1.1
github.com/hashicorp/go-version v1.6.0
github.com/hashicorp/logutils v1.0.0
github.com/mitchellh/cli v1.1.3
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
)

0 comments on commit c241e27

Please sign in to comment.