Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 50 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ jobs:
goarch: ${{ matrix.goarch }}
goversion: go.mod
binary_name: "terrakube"
ldflags: -s -w -X terrakube/cmd.Version=${{ github.event.release.tag_name }} -X terrakube/cmd.Commit=${{ github.sha }} -X terrakube/cmd.Date=${{ github.event.release.published_at || github.event.release.created_at }}

docker:
name: Build and Push Docker Image
Expand Down Expand Up @@ -63,4 +64,52 @@ jobs:
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
VERSION=${{ github.event.release.tag_name }}
COMMIT=${{ github.sha }}
DATE=${{ github.event.release.published_at || github.event.release.created_at }}

snap:
name: Build and Publish Snap Package
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Determine Snapcraft Channel and Version
id: channel
run: |
TAG="${{ github.event.release.tag_name || github.ref_name }}"
VERSION="${TAG#v}"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"

if [[ "$TAG" =~ -rc ]]; then
CHANNEL="latest/candidate"
elif [[ "$TAG" =~ -beta ]]; then
CHANNEL="latest/beta"
elif [[ "$TAG" =~ -alpha ]] || [[ "$TAG" =~ - ]]; then
CHANNEL="latest/edge"
else
CHANNEL="latest/stable"
fi

echo "channel=${CHANNEL}" >> "$GITHUB_OUTPUT"
echo "Release tag: $TAG -> Version: $VERSION -> Snap Channel: $CHANNEL"

- name: Build Snap Package
id: build
uses: canonical/action-build@v1
with:
path: .
env:
RELEASE_VERSION: ${{ steps.channel.outputs.version }}

- name: Publish Snap Package to Snap Store
uses: canonical/action-publish@v1
with:
store_login: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}
snap: ${{ steps.build.outputs.snap }}
channel: ${{ steps.channel.outputs.channel }}
5 changes: 4 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /terrakube .
ARG VERSION=dev
ARG COMMIT=unknown
ARG DATE=unknown
RUN CGO_ENABLED=0 go build -ldflags="-s -w -X terrakube/cmd.Version=${VERSION} -X terrakube/cmd.Commit=${COMMIT} -X terrakube/cmd.Date=${DATE}" -o /terrakube .

FROM dhi.io/alpine-base:3.23
# hadolint ignore=DL3018
Expand Down
3 changes: 3 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ func Execute() {
func init() {
cobra.OnInitialize(initConfig)

rootCmd.Version = FormatVersion()
rootCmd.SetVersionTemplate("{{.Version}}\n")

rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.terrakube-cli.yaml)")
rootCmd.PersistentFlags().StringVar(&output, "output", "json", "Output format: json, yaml, table, tsv, or none")
rootCmd.PersistentFlags().BoolVar(&hideNulls, "hide-nulls", true, "Hide null values in JSON output")
Expand Down
86 changes: 86 additions & 0 deletions cmd/version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package cmd

import (
"fmt"
"runtime"
"runtime/debug"
)

var (
// Version is populated at build time via -ldflags.
Version = "dev"
// Commit is populated at build time via -ldflags.
Commit = "none"
// Date is populated at build time via -ldflags.
Date = "unknown"
)

// BuildInfo encapsulates version and runtime build metadata.
type BuildInfo struct {
Version string
Commit string
Date string
GoVersion string
Platform string
}

// GetBuildInfo resolves build information from ldflags variables or runtime debug info.
func GetBuildInfo() BuildInfo {
info := BuildInfo{
Version: Version,
Commit: Commit,
Date: Date,
GoVersion: runtime.Version(),
Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH),
}

if bi, ok := debug.ReadBuildInfo(); ok {
if info.Version == "dev" || info.Version == "" {
if bi.Main.Version != "" && bi.Main.Version != "(devel)" {
info.Version = bi.Main.Version
}
}

var vcsRev, vcsTime string
var vcsModified bool
for _, s := range bi.Settings {
switch s.Key {
case "vcs.revision":
vcsRev = s.Value
case "vcs.time":
vcsTime = s.Value
case "vcs.modified":
vcsModified = s.Value == "true"
}
}

if (info.Commit == "none" || info.Commit == "") && vcsRev != "" {
if len(vcsRev) > 7 {
info.Commit = vcsRev[:7]
} else {
info.Commit = vcsRev
}
if vcsModified {
info.Commit += "+dirty"
}
}

if (info.Date == "unknown" || info.Date == "") && vcsTime != "" {
info.Date = vcsTime
}
}

return info
}

// FormatVersion returns the multi-line structured version output.
func FormatVersion() string {
info := GetBuildInfo()
return fmt.Sprintf("Version: %s\nGit Commit: %s\nBuilt At: %s\nGo Version: %s\nOS/Arch: %s",
info.Version,
info.Commit,
info.Date,
info.GoVersion,
info.Platform,
)
}
85 changes: 85 additions & 0 deletions cmd/version_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package cmd

import (
"runtime"
"strings"
"testing"
)

func TestVersionFlag(t *testing.T) {
resetGlobalFlags()

out, err := executeCommand("--version")
if err != nil {
t.Fatalf("unexpected error running --version: %v", err)
}

expectedFields := []string{
"Version:",
"Git Commit:",
"Built At:",
"Go Version:",
"OS/Arch:",
runtime.Version(),
runtime.GOOS + "/" + runtime.GOARCH,
}

for _, field := range expectedFields {
if !strings.Contains(out, field) {
t.Errorf("expected output to contain %q, got:\n%s", field, out)
}
}
}

func TestVersionShortFlag(t *testing.T) {
resetGlobalFlags()

out, err := executeCommand("-v")
if err != nil {
t.Fatalf("unexpected error running -v: %v", err)
}

if !strings.Contains(out, "Version:") {
t.Errorf("expected output to contain 'Version:', got:\n%s", out)
}
}

func TestGetBuildInfo(t *testing.T) {
oldVersion := Version
oldCommit := Commit
oldDate := Date
defer func() {
Version = oldVersion
Commit = oldCommit
Date = oldDate
}()

Version = "v1.2.3"
Commit = "abcdef1"
Date = "2026-08-20T16:00:00Z"

info := GetBuildInfo()
if info.Version != "v1.2.3" {
t.Errorf("expected version v1.2.3, got %s", info.Version)
}
if info.Commit != "abcdef1" {
t.Errorf("expected commit abcdef1, got %s", info.Commit)
}
if info.Date != "2026-08-20T16:00:00Z" {
t.Errorf("expected date 2026-08-20T16:00:00Z, got %s", info.Date)
}
if info.GoVersion != runtime.Version() {
t.Errorf("expected GoVersion %s, got %s", runtime.Version(), info.GoVersion)
}
if info.Platform != runtime.GOOS+"/"+runtime.GOARCH {
t.Errorf("expected Platform %s/%s, got %s", runtime.GOOS, runtime.GOARCH, info.Platform)
}

formatted := FormatVersion()
if !strings.Contains(formatted, "Version: v1.2.3") {
t.Errorf("unexpected formatted output:\n%s", formatted)
}
if !strings.Contains(formatted, "Git Commit: abcdef1") {
t.Errorf("unexpected formatted output:\n%s", formatted)
}
}
20 changes: 10 additions & 10 deletions snap/snapcraft.yaml
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
name: terrakube-cli
title: Terrakube CLI
base: core24
version: '0.5.0'
adopt-info: terrakube-cli
summary: CLI tool for Terrakube
description: |
terrakube is a CLI tool to handle remote Terraform workspaces and modules
in Terrakube organizations, managing the full execution lifecycle (plan, apply, destroy).
Tool to handle Terrakube organization configurations.
license: Apache-2.0
website: https://terrakube.io
contact: https://github.com/terrakube-io/terrakube-cli/issues
contact: mailto:snap@terrakube.io
issues: https://github.com/terrakube-io/terrakube-cli/issues
source-code: https://github.com/terrakube-io/terrakube-cli

Expand All @@ -21,11 +20,6 @@ apps:
plugs:
- home
- network
terrakube:
command: bin/terrakube
plugs:
- home
- network

parts:
terrakube-cli:
Expand All @@ -34,4 +28,10 @@ parts:
build-snaps:
- go/latest/stable
override-build: |
go build -ldflags="-s -w" -o "${CRAFT_PART_INSTALL}/bin/terrakube" .
RAW_TAG="${RELEASE_VERSION:-$(git describe --tags --always 2>/dev/null || echo '0.0.0')}"
CLEAN_VERSION="${RAW_TAG#v}"
COMMIT="$(git rev-parse --short HEAD 2>/dev/null || echo 'unknown')"
BUILD_DATE="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
craftctl set version="${CLEAN_VERSION}"
go build -ldflags="-s -w -X terrakube/cmd.Version=${CLEAN_VERSION} -X terrakube/cmd.Commit=${COMMIT} -X terrakube/cmd.Date=${BUILD_DATE}" -o "${CRAFT_PART_INSTALL}/bin/terrakube" .

Loading