-
Notifications
You must be signed in to change notification settings - Fork 22
Expand file tree
/
Copy pathversion.go
More file actions
175 lines (149 loc) · 4.23 KB
/
version.go
File metadata and controls
175 lines (149 loc) · 4.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
package app
import (
"fmt"
"runtime/debug"
"strconv"
"strings"
"time"
"encoding/json"
"io"
"net/http"
"github.com/urfave/cli/v2"
"golang.org/x/mod/semver"
)
const (
// MinSupportedVersion is the minimum tcld version supported by our APIs.
// This string must be updated when we deprecate older versions, but should be
// done carefully as this will likely break user's current usage of tcld.
MinSupportedVersion = "v0.22.0"
// DefaultVersionString is the version which is sent over if no version was available.
// This can happen if a user builds the latest main branch, as the version string provided
// to us from Go tooling is `(devel)`.
DefaultVersionString = MinSupportedVersion + "+no-version-available"
pseudoVersionMinLen = len("vX.0.0-yyyymmddhhmmss-abcdefabcdef")
pseudoVersionCommitInfoLen = len("yyyymmddhhmmss-abcdefabcdef")
)
var (
date string
commit string
version string
)
type (
BuildInfo struct {
Date string // build time or commit time.
Commit string
Version string
}
Release struct {
TagName string `json:"tag_name"`
}
)
func NewVersionCommand() (CommandOut, error) {
return CommandOut{Command: &cli.Command{
Name: "version",
Usage: "Version information",
Aliases: []string{"v"},
Action: versionAction,
}}, nil
}
func versionAction(c *cli.Context) error {
buildInfo := NewBuildInfo()
err := PrintObj(buildInfo)
if err != nil {
return err
}
return checkForUpdate(&buildInfo)
}
// NewBuildInfo will populate build info, to make debugging API errors easier,
// in the three scenarios a user can install tcld:
// 1. Installed via the makefile or via brew.
// 2. Installed via `go install`.
// 3. Compiled on a branch via `go build ./cmd/tcld`
func NewBuildInfo() BuildInfo {
if len(version) > 0 {
// Used when built with make or installed with brew.
return BuildInfo{
Date: date,
Commit: commit,
Version: version,
}
}
di, ok := debug.ReadBuildInfo()
if !ok {
fmt.Printf("Failed to read debug info\n")
return BuildInfo{}
}
info := BuildInfo{
Version: di.Main.Version,
}
if !semver.IsValid(di.Main.Version) {
info.Version = DefaultVersionString
}
if len(di.Main.Version) >= pseudoVersionMinLen {
// Used when compiled with `go install`.
// See https://go.dev/ref/mod#pseudo-versions for more info on the expected string format
commitInfoStart := len(di.Main.Version) - pseudoVersionCommitInfoLen
split := strings.Split(di.Main.Version[commitInfoStart:], "-")
// Make the time human readable.
at, err := time.Parse("20060102150405", split[0])
if err == nil {
info.Date = at.UTC().Format("2006-01-02T15:04:05.000Z")
}
info.Commit = split[1]
} else {
// Used when built directly from a branch with `go build`.
var hash, modified string
for _, setting := range di.Settings {
switch setting.Key {
case "vcs.revision":
hash = setting.Value
case "vcs.modified":
if v, err := strconv.ParseBool(setting.Value); err == nil && v {
modified = "-modified"
}
case "vcs.time":
info.Date = setting.Value
}
}
info.Commit = fmt.Sprintf("%s%s", hash, modified)
}
return info
}
func checkForUpdate(buildInfo *BuildInfo) error {
latestInfo, err := fetchLatestVersion()
if err != nil {
return fmt.Errorf("failed to fetch latest version: %w", err)
}
if strings.Contains(buildInfo.Version, "no-version-available") {
fmt.Println("no-version-available detected, unable to determine if current build is latest version")
} else if buildInfo.Version == latestInfo.TagName {
fmt.Println("You are running the latest version")
} else {
fmt.Printf("A newer version is available: %s\n", latestInfo.TagName)
}
return nil
}
func fetchLatestVersion() (Release, error) {
url := "https://api.github.com/repos/temporalio/tcld/releases/latest"
resp, err := http.Get(url)
if err != nil {
return Release{}, err
}
defer func() {
if err := resp.Body.Close(); err != nil {
fmt.Printf("Failed to close response body: %v\n", err)
}
}()
if resp.StatusCode != 200 {
return Release{}, fmt.Errorf("GitHub API error: %s", resp.Status)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return Release{}, err
}
var r Release
if err := json.Unmarshal(body, &r); err != nil {
return Release{}, err
}
return r, nil
}