diff --git a/astro/tvm/cli/tvm/cmd/install.go b/astro/tvm/cli/tvm/cmd/install.go index ca2d29f..b34adaa 100644 --- a/astro/tvm/cli/tvm/cmd/install.go +++ b/astro/tvm/cli/tvm/cmd/install.go @@ -20,11 +20,10 @@ import ( "fmt" "log" + "github.com/spf13/cobra" "github.com/spf13/viper" "github.com/uber/astro/astro/tvm" - - "github.com/spf13/cobra" ) // defaultInstallPath is the path that the Terraform binary will be diff --git a/astro/tvm/cli/tvm/cmd/list.go b/astro/tvm/cli/tvm/cmd/list.go new file mode 100644 index 0000000..8d49d92 --- /dev/null +++ b/astro/tvm/cli/tvm/cmd/list.go @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2019 Uber Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cmd + +import ( + "fmt" + "log" + "os" + "os/exec" + "sort" + + version "github.com/burl/go-version" + "github.com/spf13/cobra" + + "github.com/uber/astro/astro/tvm" +) + +var listCmd = &cobra.Command{ + Use: "ls", + Short: "List locally downloaded versions of Terraform", + Run: func(cmd *cobra.Command, args []string) { + tvm, err := tvm.NewVersionRepoForCurrentSystem(repoPath) + if err != nil { + log.Fatal(err) + } + + // Get list of downloaded versions and path to binaries + versionsPaths, err := tvm.List() + if err != nil { + log.Fatal(err) + } + + // Extract just the version strings + versions := []string{} + for v := range versionsPaths { + versions = append(versions, v) + } + + // Get the path to the current Terraform binary, according to $PATH + terraformPath, _ := exec.LookPath("terraform") + + // Get path that Terraform binary links to + terraformLinkPath, _ := os.Readlink(terraformPath) + + // List the versions + for _, v := range sortedVersions(versions) { + s := v.String() + print(s) + + // If the current Terraform binary is linked to a particular + // path from tvm, then it's active and has been installed by tvm + if versionsPaths[s] == terraformLinkPath { + fmt.Printf(" (current, installed at: %s)", terraformPath) + } + + println() + } + }, +} + +// sortedVersions takes a list of version strings and returns them sorted in +// reverse order. +func sortedVersions(versions []string) (sortedVersions version.Collection) { + for _, v := range versions { + semver, err := version.NewVersion(v) + if err != nil { + log.Println(err) + continue + } + + sortedVersions = append(sortedVersions, semver) + } + + sort.Sort(sort.Reverse(sortedVersions)) + + return +} + +func init() { + rootCmd.AddCommand(listCmd) +} diff --git a/astro/tvm/versionrepo.go b/astro/tvm/versionrepo.go index 2a5cbba..eb31241 100644 --- a/astro/tvm/versionrepo.go +++ b/astro/tvm/versionrepo.go @@ -25,6 +25,7 @@ import ( "os" "path" "path/filepath" + "regexp" "runtime" "sync" @@ -40,6 +41,10 @@ const terraformBinaryFile = "terraform" // files from the Hashicorp website. var terraformZipFileDownloadURL = "https://releases.hashicorp.com/terraform/%s/terraform_%s_%s_%s.zip" +// versionDirectoryFormat is a regexp that matches Terraform semver, +// e.g. "1.2.30" +var versionDirectoryFormat = regexp.MustCompile(`\d+\.\d+\.\d+`) + // VersionRepo is a directory on the filesystem that keeps // Terraform binaries. type VersionRepo struct { @@ -191,6 +196,32 @@ func (r *VersionRepo) Link(version string, targetPath string, overwrite bool) er return os.Symlink(terraformPath, targetPath) } +// List returns all locally downloaded Terraform versions and their paths. +func (r *VersionRepo) List() (map[string]string, error) { + dirs := map[string]string{} + + repoBaseDir := r.dir("") + f, err := os.Open(repoBaseDir) + defer f.Close() + if err != nil { + return nil, err + } + + files, err := f.Readdir(-1) + if err != nil { + return nil, err + } + + for _, file := range files { + terraformVersion := file.Name() + if file.IsDir() && versionDirectoryFormat.MatchString(terraformVersion) { + dirs[terraformVersion] = r.terraformPath(terraformVersion) + } + } + + return dirs, nil +} + // terraformPath returns the path to the Terraform binary file with the // specified version. func (r *VersionRepo) terraformPath(version string) string {