mirror of
https://github.com/openziti/ziti.git
synced 2026-09-09 16:25:41 +00:00
fixes openziti/ziti#4304 build flags on the version response (#4305)
- adds a package-level buildFlags string the linker sets at build time, parsed into a capped list of [A-Z0-9_] names with blanks, duplicates, and malformed tokens dropped - returns those names in the new buildFlags field on /version, separate from capabilities, and prints them under ziti version -v - bumps edge-api to v0.36.0 for the buildFlags field
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
* [DNS Upstream Query Modes](#dns-upstream-query-modes) - choose how multiple DNS upstreams are queried: parallel fan-out (default) or serial fail-through
|
||||
* [Controller Read Throughput Under Load](#controller-read-throughput-under-load) - a bbolt upgrade lifts a ceiling on concurrent read transactions that could stall a busy controller
|
||||
* [Logging Now Uses slog with an Async Handler](#logging-now-uses-slog-with-an-async-handler) - Logging moves to Go's `log/slog` behind an asynchronous sink; output is unchanged by default, with new flags to tune buffering
|
||||
* [Build Flags](#build-flags) - A build of the controller can name the build time choices it was made with, and clients can read them from `/version`
|
||||
* [Security Advisories](#security-advisories) - Eight security advisories, plus the two control-plane certificate validation fixes first released in 2.0.2
|
||||
|
||||
## Security Advisories
|
||||
@@ -496,6 +497,36 @@ most call sites are not yet migrated, per-channel overrides have limited reach
|
||||
today and expand as packages are converted. The global `ziti agent set-log-level
|
||||
<level>` still affects everything.
|
||||
|
||||
## Build Flags
|
||||
|
||||
A controller binary can now carry a list of build flags: short names for the
|
||||
build time choices it was made with. The `/version` endpoint returns them in a
|
||||
new `buildFlags` field, and `ziti version -v` prints them, so a client or an
|
||||
operator can tell what a running controller was built to do without asking the
|
||||
person who built it.
|
||||
|
||||
Flags are set with the Go linker at build time. There is one symbol, and one
|
||||
build owner composes the whole list:
|
||||
|
||||
```
|
||||
go build -ldflags "-X github.com/openziti/ziti/v2/common/build.buildFlags=ALPHA,BRAVO_MODE" ./ziti
|
||||
```
|
||||
|
||||
Names are uppercase letters, digits, and underscores. Anything else in the list
|
||||
is dropped rather than served. A second `-X` against the same symbol replaces
|
||||
the first rather than adding to it, and the linker silently ignores an `-X`
|
||||
whose symbol path it cannot resolve, so a typo in that path yields a binary with
|
||||
no flags rather than a build error.
|
||||
|
||||
Releases from this repository set no flags, so `buildFlags` is empty and `ziti
|
||||
version -v` prints no build flags line. The names themselves are defined by
|
||||
whoever produced the build; a client should ignore any it does not recognize.
|
||||
|
||||
Build flags are not capabilities. `capabilities` on `/version` describes what
|
||||
the controller offers over the API, is defined by this repository, and is
|
||||
enumerated at `/enumerated-capabilities`. Build flags describe how the binary
|
||||
was built, are open-ended, and are enumerated nowhere.
|
||||
|
||||
## Deprecated Features
|
||||
|
||||
Deprecated features still work, but are no longer recommended and will be removed
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// maxBuildFlags is the greatest number of flag names accepted from buildFlags.
|
||||
maxBuildFlags = 32
|
||||
|
||||
// maxBuildFlagNameLength is the greatest length accepted for a single flag name.
|
||||
maxBuildFlagNameLength = 64
|
||||
)
|
||||
|
||||
// buildFlagNamePattern matches a well formed build flag name.
|
||||
var buildFlagNamePattern = regexp.MustCompile(`^[A-Z0-9_]+$`)
|
||||
|
||||
// buildFlags is a comma separated list of flag names supplied at build time by the linker, empty
|
||||
// in a stock build:
|
||||
//
|
||||
// -X github.com/openziti/ziti/v2/common/build.buildFlags=ALPHA,BRAVO
|
||||
//
|
||||
// The symbol path is a contract with downstream builds. The linker silently ignores -X against a
|
||||
// symbol it cannot resolve, so renaming this variable or moving it to another package produces a
|
||||
// binary with no build flags rather than a build error. A second -X against this symbol replaces
|
||||
// the first rather than adding to it: one build owner composes the whole list.
|
||||
var buildFlags string
|
||||
|
||||
// GetBuildFlags returns the well formed flag names supplied at build time, in the order they were
|
||||
// given. It returns an empty slice for a stock build.
|
||||
func GetBuildFlags() []string {
|
||||
return parseBuildFlags(buildFlags)
|
||||
}
|
||||
|
||||
// parseBuildFlags splits raw on commas and returns the well formed names in first seen order,
|
||||
// dropping blanks, duplicates, and anything outside [A-Z0-9_]. The result is never nil, so it
|
||||
// serializes as an empty JSON array rather than null. The count of names and the length of each
|
||||
// are capped, because the result is served over an unauthenticated API.
|
||||
func parseBuildFlags(raw string) []string {
|
||||
result := []string{}
|
||||
seen := map[string]struct{}{}
|
||||
|
||||
for _, token := range strings.Split(raw, ",") {
|
||||
if len(result) == maxBuildFlags {
|
||||
break
|
||||
}
|
||||
|
||||
name := strings.TrimSpace(token)
|
||||
if len(name) > maxBuildFlagNameLength || !buildFlagNamePattern.MatchString(name) {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := seen[name]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
seen[name] = struct{}{}
|
||||
result = append(result, name)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestParseBuildFlags(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
raw string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "empty string yields an empty, non nil slice",
|
||||
raw: "",
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
name: "single name",
|
||||
raw: "ALPHA",
|
||||
expected: []string{"ALPHA"},
|
||||
},
|
||||
{
|
||||
name: "multiple names keep their order",
|
||||
raw: "ALPHA,BRAVO,CHARLIE",
|
||||
expected: []string{"ALPHA", "BRAVO", "CHARLIE"},
|
||||
},
|
||||
{
|
||||
name: "surrounding whitespace is trimmed",
|
||||
raw: " ALPHA , BRAVO\t,\nCHARLIE ",
|
||||
expected: []string{"ALPHA", "BRAVO", "CHARLIE"},
|
||||
},
|
||||
{
|
||||
name: "blank tokens are dropped",
|
||||
raw: ",ALPHA,, ,BRAVO,",
|
||||
expected: []string{"ALPHA", "BRAVO"},
|
||||
},
|
||||
{
|
||||
name: "duplicates are dropped, first occurrence wins",
|
||||
raw: "ALPHA,BRAVO,ALPHA",
|
||||
expected: []string{"ALPHA", "BRAVO"},
|
||||
},
|
||||
{
|
||||
name: "digits and underscores are accepted",
|
||||
raw: "ALPHA_2,BRAVO_MODE,3",
|
||||
expected: []string{"ALPHA_2", "BRAVO_MODE", "3"},
|
||||
},
|
||||
{
|
||||
name: "malformed tokens are dropped, well formed ones survive",
|
||||
raw: "alpha,BRAVO,Char-lie,DELTA ECHO,FOXTROT!,GOLF",
|
||||
expected: []string{"BRAVO", "GOLF"},
|
||||
},
|
||||
{
|
||||
name: "names longer than the cap are dropped",
|
||||
raw: "ALPHA," + strings.Repeat("B", maxBuildFlagNameLength+1) + ",CHARLIE",
|
||||
expected: []string{"ALPHA", "CHARLIE"},
|
||||
},
|
||||
{
|
||||
name: "names exactly at the length cap are kept",
|
||||
raw: strings.Repeat("B", maxBuildFlagNameLength),
|
||||
expected: []string{strings.Repeat("B", maxBuildFlagNameLength)},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
result := parseBuildFlags(test.raw)
|
||||
|
||||
require.Equal(t, test.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseBuildFlagsStopsAtTheCountCap(t *testing.T) {
|
||||
names := make([]string, 0, maxBuildFlags+5)
|
||||
for i := 0; i < maxBuildFlags+5; i++ {
|
||||
names = append(names, "NAME_"+strings.Repeat("X", i))
|
||||
}
|
||||
|
||||
result := parseBuildFlags(strings.Join(names, ","))
|
||||
|
||||
require.Len(t, result, maxBuildFlags, "accepted names must be capped")
|
||||
require.Equal(t, names[:maxBuildFlags], result, "the first names up to the cap are the ones kept")
|
||||
}
|
||||
@@ -105,6 +105,7 @@ func (ir *VersionRouter) buildVersions(ae *env.AppEnv) *rest_model.Version {
|
||||
Version: buildInfo.Version(),
|
||||
APIVersions: map[string]map[string]rest_model.APIVersion{},
|
||||
Capabilities: []string{},
|
||||
BuildFlags: build.GetBuildFlags(),
|
||||
}
|
||||
|
||||
for apiBinding, apiVersionToPathMap := range webapis.AllApiBindingVersions {
|
||||
|
||||
@@ -65,7 +65,7 @@ require (
|
||||
github.com/natefinch/lumberjack v2.0.0+incompatible
|
||||
github.com/openziti/channel/v5 v5.0.27
|
||||
github.com/openziti/cobra-to-md v1.0.1
|
||||
github.com/openziti/edge-api v0.35.2
|
||||
github.com/openziti/edge-api v0.36.0
|
||||
github.com/openziti/foundation/v2 v2.0.100
|
||||
github.com/openziti/identity v1.0.140
|
||||
github.com/openziti/jwks v1.0.6
|
||||
|
||||
@@ -534,6 +534,8 @@ github.com/openziti/cobra-to-md v1.0.1 h1:WRinNoIRmwWUSJm+pSNXMjOrtU48oxXDZgeCYQ
|
||||
github.com/openziti/cobra-to-md v1.0.1/go.mod h1:FjCpk/yzHF7/r28oSTNr5P57yN5VolpdAtS/g7KNi2c=
|
||||
github.com/openziti/edge-api v0.35.2 h1:+5+i0BkhzsKZkURBAr6fgVp7BnHITjaHDiRxxdW3DuE=
|
||||
github.com/openziti/edge-api v0.35.2/go.mod h1:m1oAQ6+fnkEO0NOAkDy6WpnXDxPhj2sko4eBUvIMxkE=
|
||||
github.com/openziti/edge-api v0.36.0 h1:adp0gCDbxee3r4tPBCXm7IyLTcIGhDcWmQ9QJMO+AwY=
|
||||
github.com/openziti/edge-api v0.36.0/go.mod h1:m1oAQ6+fnkEO0NOAkDy6WpnXDxPhj2sko4eBUvIMxkE=
|
||||
github.com/openziti/foundation/v2 v2.0.100 h1:Q9DzyZYp7/QF0No2CDadcmhWpp1FqOvxwhNFENk6GrU=
|
||||
github.com/openziti/foundation/v2 v2.0.100/go.mod h1:voePbn/hwFypw6Ogr2k5sl/g3ZiHWdHO202uHBNpsAQ=
|
||||
github.com/openziti/go-term-markdown v1.0.1 h1:9uzMpK4tav6OtvRxRt99WwPTzAzCh+Pj9zWU2FBp3Qg=
|
||||
|
||||
@@ -18,7 +18,9 @@ package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/openziti/ziti/v2/common/build"
|
||||
"github.com/openziti/ziti/v2/common/version"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -36,6 +38,10 @@ func NewVersionCmd() *cobra.Command {
|
||||
fmt.Printf("Build Date: %s\n", version.GetBuildDate())
|
||||
fmt.Printf("Go Version: %s\n", version.GetGoVersion())
|
||||
fmt.Printf("OS/Arch: %s/%s\n", version.GetOS(), version.GetArchitecture())
|
||||
|
||||
if buildFlags := build.GetBuildFlags(); len(buildFlags) > 0 {
|
||||
fmt.Printf("Build Flags: %s\n", strings.Join(buildFlags, ", "))
|
||||
}
|
||||
} else {
|
||||
fmt.Println(version.GetVersion())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user