-
Notifications
You must be signed in to change notification settings - Fork 1
/
versions.go
112 lines (105 loc) · 3.04 KB
/
versions.go
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
package apitools
import (
"errors"
"fmt"
"path"
"reflect"
"runtime/debug"
"sort"
"strings"
"sync"
"github.com/blang/semver/v4"
)
var (
errBuildInfoUnavailable = errors.New("build info unavailable")
deps []*debug.Module
depsAvailable bool
depsSetup sync.Once
)
// APIModuleVersions returns a map of Sensu API modules that are compiled into
// the product.
func APIModuleVersions() map[string]string {
apiModuleVersions := make(map[string]string)
typeMapMu.Lock()
defer typeMapMu.Unlock()
for groupName, group := range typeMap {
var first reflect.Type
for _, typ := range group {
first = typ.Type
break
}
groupVersion, err := versionOf(first)
if err != nil {
_, groupVersion = parseAPIVersion(groupName)
}
apiModuleVersions[groupName] = groupVersion
}
return apiModuleVersions
}
// versionOf interrogates the build environment for the version of the module
// defining a type.
func versionOf(typ reflect.Type) (string, error) {
packagePath := typ.PkgPath()
packagePathParts := strings.Split(packagePath, "/")
dependencies, ok := buildDependencies()
if !ok {
return "", errBuildInfoUnavailable
}
DEPSEARCH:
for _, mod := range dependencies {
for i, modPathPart := range strings.Split(mod.Path, "/") {
if packagePathParts[i] != modPathPart {
continue DEPSEARCH
}
}
return mod.Version, nil
}
return "", fmt.Errorf("error finding build dependency for type %s", typ)
}
func buildDependencies() ([]*debug.Module, bool) {
depsSetup.Do(func() {
buildInfo, ok := debug.ReadBuildInfo()
if !ok || buildInfo.Deps == nil {
return
}
depsAvailable = true
for _, dep := range buildInfo.Deps {
deps = append(deps, dep)
}
// sort by module path length descending so that when matching package
// names to a module we find the more specific modules first.
// e.g. github.com/sensu/sensu-go/api/core/v2 before github.com/sensu/sensu-go
sort.Slice(deps, func(i, j int) bool {
return len(deps[i].Path) > len(deps[j].Path)
})
})
return deps, depsAvailable
}
// parseAPIVersion parses an api_version that looks like the following:
//
// core/v2
// core/v2.2
// core/v2.2.1
//
// It returns the name of the apiGroup (core/v2), and the semantic version
// (v2.0.0, v2.2.0, v2.2.1). A leading 'v' is included, keeping with how Go
// modules express their versions.
//
// If ParseAPIVersion can't determine the version, for instance if it's passed
// a string that does not seem to be a versioned API group, it will return its
// input as the apiGroup, and v0.0.0 as the version.
func parseAPIVersion(apiVersion string) (apiGroup, semVer string) {
group, version := path.Split(apiVersion)
if version == "" {
// There is no version for the API group, which is fine.
return group, "v0.0.0"
}
semver, err := semver.ParseTolerant(version)
if err != nil {
// It's not the expected format
return apiVersion, "v0.0.0"
}
apiGroup = path.Join(group, fmt.Sprintf("v%d", semver.Major))
semVer = fmt.Sprintf("v%d.%d.%d", semver.Major, semver.Minor, semver.Patch)
return apiGroup, semVer
}