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
23 changes: 16 additions & 7 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,19 +46,28 @@ jobs:
GOARCH: ${{ matrix.goarch }}
CGO_ENABLED: "0"
run: |
if [[ "$GITHUB_REF" == refs/tags/* ]]; then
version="${GITHUB_REF_NAME#v}"
else
version="dev-${GITHUB_SHA::7}"
fi
out="dist/certstrap-${GOOS}-${GOARCH}"
go build -trimpath -ldflags "-X main.release=${version}" -o "$out" .
go build -trimpath -o "$out" .

# Fail if the binary wasn't built for the requested platform.
go version -m "$out" | grep -Eq "^[[:space:]]+build[[:space:]]+GOOS=${GOOS}$"
go version -m "$out" | grep -Eq "^[[:space:]]+build[[:space:]]+GOARCH=${GOARCH}$"

# --version prints the module version that Go stamps from git. A tag
# build must report exactly that tag, and no build may be dirty or
# missing git metadata.
version=$(go version -m "$out" | awk '$1 == "mod" { print $3 }')
echo "Module version: ${version}"
if [[ "$GITHUB_REF" == refs/tags/* && "$version" != "$GITHUB_REF_NAME" ]]; then
echo "::error::binary reports ${version}, want tag ${GITHUB_REF_NAME}"
exit 1
fi
if [[ "$version" == "(devel)" || "$version" == *+dirty ]]; then
echo "::error::binary reports ${version}, want a version from a clean git checkout"
exit 1
fi
if [[ "$GOOS" == linux && "$GOARCH" == amd64 ]]; then
"./$out" --version | grep -F "$version"
test "$("./$out" --version)" = "certstrap version ${version#v}"
fi

- name: Upload artifact
Expand Down
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ certstrap
# Nice to have in .gitignore
.idea/
# .DS_Store file sometimes generated by Mac computers
.DS_Store
.DS_Store
# Release build output
dist/
4 changes: 1 addition & 3 deletions certstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,10 @@ import (
"github.com/urfave/cli"
)

var release = "1.3.0"

func main() {
app := cli.NewApp()
app.Name = "certstrap"
app.Version = release
app.Version = appVersion()
app.Usage = "A simple certificate manager written in Go, to bootstrap your own certificate authority and public key infrastructure."
app.Flags = []cli.Flag{
cli.StringFlag{
Expand Down
50 changes: 50 additions & 0 deletions version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*-
* Copyright 2026 Square 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 main

import (
"runtime/debug"
"strings"
)

// release overrides what --version prints. Leave it empty to print the
// module version that Go stamps into the binary at build time. Builds
// without git metadata, such as from a source tarball, can set it with
// -ldflags "-X main.release=1.2.3".
var release string

// appVersion returns the version for --version, without the leading "v"
// of a Go module version.
func appVersion() string {
var buildVersion string
if info, ok := debug.ReadBuildInfo(); ok {
buildVersion = info.Main.Version
}
return resolveVersion(release, buildVersion)
}

// resolveVersion returns override if it is set, then buildVersion without
// its leading "v", and "(devel)" if neither is set.
func resolveVersion(override, buildVersion string) string {
if override != "" {
return override
}
if buildVersion != "" {
return strings.TrimPrefix(buildVersion, "v")
}
return "(devel)"
}
56 changes: 56 additions & 0 deletions version_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*-
* Copyright 2026 Square 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 main

import "testing"

func TestResolveVersion(t *testing.T) {
tests := []struct {
name string
override string
buildVersion string
want string
}{
{name: "override wins over build info", override: "1.2.3", buildVersion: "v1.4.0", want: "1.2.3"},
{name: "tagged build", buildVersion: "v1.4.0", want: "1.4.0"},
{name: "prerelease tag", buildVersion: "v1.4.0-rc.1", want: "1.4.0-rc.1"},
{name: "untagged commit", buildVersion: "v1.4.1-0.20261005184410-5abe9dab23cc", want: "1.4.1-0.20261005184410-5abe9dab23cc"},
{name: "uncommitted changes", buildVersion: "v1.4.0+dirty", want: "1.4.0+dirty"},
{name: "no git metadata", buildVersion: "(devel)", want: "(devel)"},
{name: "no build info", want: "(devel)"},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
if got := resolveVersion(tc.override, tc.buildVersion); got != tc.want {
t.Fatalf("resolveVersion(%q, %q) = %q, want %q", tc.override, tc.buildVersion, got, tc.want)
}
})
}
}

func TestAppVersion(t *testing.T) {
if appVersion() == "" {
t.Fatal("appVersion() is empty")
}

defer func(v string) { release = v }(release)
release = "1.2.3"
if got := appVersion(); got != "1.2.3" {
t.Fatalf("appVersion() = %q, want the -X override %q", got, "1.2.3")
}
}
Loading