From bc39594f5207c7d56733f443bb99316dc86e456b Mon Sep 17 00:00:00 2001 From: Daniel Simmons Date: Wed, 2 Oct 2019 17:32:23 +0200 Subject: [PATCH] Add version command (#12) * Add version command `astro version` will print the version of astro at the command line. Version information will be set using ldflags at release time. For more information, see: https://stackoverflow.com/questions/11354518/golang-application-auto-build-versioning Automated releasing is implemented in a separate PR. --- CHANGELOG.md | 1 + astro/cli/astro/cmd/cmd.go | 23 +++++++---- astro/cli/astro/cmd/version.go | 62 ++++++++++++++++++++++++++++++ astro/tests/integration_test.go | 67 +++++++++++++++++++++++++++++++-- 4 files changed, 142 insertions(+), 11 deletions(-) create mode 100644 astro/cli/astro/cmd/version.go diff --git a/CHANGELOG.md b/CHANGELOG.md index f9be3bc..e672fe9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## 0.5.0 (UNRELEASED, 2019) +* Add `version` command (#12) * Add binaries via goreleaser (#14) * Adopt options pattern for `astro.NewProject` constructor (#26) * Refactor and improve integration tests to invoke them directly using cli diff --git a/astro/cli/astro/cmd/cmd.go b/astro/cli/astro/cmd/cmd.go index 44a286b..4dc093c 100644 --- a/astro/cli/astro/cmd/cmd.go +++ b/astro/cli/astro/cmd/cmd.go @@ -63,9 +63,10 @@ type AstroCLI struct { } commands struct { - root *cobra.Command - plan *cobra.Command - apply *cobra.Command + root *cobra.Command + plan *cobra.Command + apply *cobra.Command + version *cobra.Command } } @@ -85,10 +86,12 @@ func NewAstroCLI(opts ...Option) (*AstroCLI, error) { cli.createRootCommand() cli.createPlanCmd() cli.createApplyCmd() + cli.createVersionCmd() cli.commands.root.AddCommand( cli.commands.plan, cli.commands.apply, + cli.commands.version, ) // Set trace. Note, this will turn tracing on for all instances of astro @@ -159,11 +162,10 @@ func (cli *AstroCLI) configureDynamicUserFlags() { func (cli *AstroCLI) createRootCommand() { rootCmd := &cobra.Command{ - Use: "astro", - Short: "A tool for managing multiple Terraform modules.", - SilenceUsage: true, - SilenceErrors: true, - PersistentPreRunE: cli.preRun, + Use: "astro", + Short: "A tool for managing multiple Terraform modules.", + SilenceUsage: true, + SilenceErrors: true, } rootCmd.PersistentFlags().BoolVarP(&cli.flags.verbose, "verbose", "v", false, "verbose output") @@ -178,6 +180,7 @@ func (cli *AstroCLI) createApplyCmd() { Use: "apply [flags] [-- [Terraform argument]...]", DisableFlagsInUseLine: true, Short: "Run Terraform apply on all modules", + PersistentPreRunE: cli.preRun, RunE: cli.runApply, } @@ -191,6 +194,7 @@ func (cli *AstroCLI) createPlanCmd() { Use: "plan [flags] [-- [Terraform argument]...]", DisableFlagsInUseLine: true, Short: "Generate execution plans for modules", + PersistentPreRunE: cli.preRun, RunE: cli.runPlan, } @@ -203,6 +207,9 @@ func (cli *AstroCLI) createPlanCmd() { func (cli *AstroCLI) preRun(cmd *cobra.Command, args []string) error { logger.Trace.Println("cli: in preRun") + if cli.config == nil { + return fmt.Errorf("unable to find config file") + } // Load astro from config project, err := astro.NewProject(astro.WithConfig(*cli.config)) if err != nil { diff --git a/astro/cli/astro/cmd/version.go b/astro/cli/astro/cmd/version.go new file mode 100644 index 0000000..183b981 --- /dev/null +++ b/astro/cli/astro/cmd/version.go @@ -0,0 +1,62 @@ +/* + * 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 contains the source for the `astro` command line tool +// that operators use to interact with the project. + +package cmd + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" +) + +var ( + // When a release happens, the value of this variable will be overwritten + // by the linker to match the release version. + version = "dev" + commit = "" + date = "" +) + +func (cli *AstroCLI) createVersionCmd() { + versionCmd := &cobra.Command{ + Use: "version", + DisableFlagsInUseLine: true, + Short: "Print astro version", + RunE: func(cmd *cobra.Command, args []string) error { + versionString := []string{ + "astro version", + version, + } + + if commit != "" { + versionString = append(versionString, fmt.Sprintf("(%s)", commit)) + } + + if date != "" { + versionString = append(versionString, fmt.Sprintf("built %s", date)) + } + + fmt.Fprintln(cli.stdout, strings.Join(versionString, " ")) + + return nil + }, + } + cli.commands.version = versionCmd +} diff --git a/astro/tests/integration_test.go b/astro/tests/integration_test.go index 2e9ab4b..eaf847f 100644 --- a/astro/tests/integration_test.go +++ b/astro/tests/integration_test.go @@ -71,10 +71,17 @@ func stringVersionMatches(v string, versionConstraint string) bool { } // compiles the astro binary and returns the path to it. -func compileAstro(dir string) (string, error) { +func compileAstro(dir string, buildFlags []string) (string, error) { astroPath := filepath.Join(dir, "astro") packageName := "github.com/uber/astro/astro/cli/astro" - out, err := exec.Command("go", "build", "-o", astroPath, packageName).CombinedOutput() + compileArgs := []string{"build"} + if len(buildFlags) > 0 { + for _, flag := range buildFlags { + compileArgs = append(compileArgs, flag) + } + } + compileArgs = append(compileArgs, "-o", astroPath, packageName) + out, err := exec.Command("go", compileArgs...).CombinedOutput() if err != nil { return "", errors.New(string(out)) } @@ -95,7 +102,7 @@ func TestPlanInterrupted(t *testing.T) { defer os.RemoveAll(tmpdir) require.NoError(t, err) - astroBinary, err := compileAstro(tmpdir) + astroBinary, err := compileAstro(tmpdir, []string{}) require.NoError(t, err) command := exec.Command(astroBinary, "plan") @@ -219,3 +226,57 @@ func TestProjectPlanDetachSuccess(t *testing.T) { }) } } + +func TestVersionDev(t *testing.T) { + dir, err := ioutil.TempDir("/tmp", "astro-tests") + defer os.RemoveAll(dir) + require.NoError(t, err) + result := RunTest(t, []string{"version"}, "/tmp/astro-tests", "") + assert.Equal(t, "", result.Stderr.String()) + assert.Equal(t, "astro version dev\n", result.Stdout.String()) + assert.Equal(t, 0, result.ExitCode) +} + +func TestVersionWithLdflags(t *testing.T) { + dir, err := ioutil.TempDir("/tmp", "astro-tests") + defer os.RemoveAll(dir) + require.NoError(t, err) + + stdoutBytes := &bytes.Buffer{} + stderrBytes := &bytes.Buffer{} + + astroBinary, err := compileAstro(dir, []string{ + "-ldflags", + "-X github.com/uber/astro/astro/cli/astro/cmd.version=1.2.3 " + + "-X github.com/uber/astro/astro/cli/astro/cmd.commit=ab123 " + + "-X github.com/uber/astro/astro/cli/astro/cmd.date=2019-01-01T10:00:00", + }) + require.NoError(t, err) + command := exec.Command(astroBinary, "version") + command.Dir = dir + + command.Stdout = stdoutBytes + command.Stderr = stderrBytes + + err = command.Run() + + require.NoError(t, err) + + assert.Equal(t, "", stderrBytes.String()) + assert.Equal(t, "astro version 1.2.3 (ab123) built 2019-01-01T10:00:00\n", stdoutBytes.String()) +} + +func TestAstroErrorsWithoutConfig(t *testing.T) { + dir, err := ioutil.TempDir("/tmp", "astro-tests") + defer os.RemoveAll(dir) + require.NoError(t, err) + + commands := []string{"plan", "apply"} + + for _, cmd := range commands { + result := RunTest(t, []string{cmd}, "/tmp/astro-tests", "") + assert.Equal(t, "unable to find config file\n", result.Stderr.String()) + assert.Equal(t, "", result.Stdout.String()) + assert.Equal(t, 1, result.ExitCode) + } +}