mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-10 04:27:54 +00:00
Merge pull request #117688 from liggitt/dynamic-version
Allow override of prerelease/buildID portions of version at runtime
This commit is contained in:
commit
d96993fc1c
77
staging/src/k8s.io/component-base/version/dynamic.go
Normal file
77
staging/src/k8s.io/component-base/version/dynamic.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 The Kubernetes Authors.
|
||||||
|
|
||||||
|
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 version
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
utilversion "k8s.io/apimachinery/pkg/util/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
var dynamicGitVersion atomic.Value
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// initialize to static gitVersion
|
||||||
|
dynamicGitVersion.Store(gitVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDynamicVersion overrides the version returned as the GitVersion from Get().
|
||||||
|
// The specified version must be non-empty, a valid semantic version, and must
|
||||||
|
// match the major/minor/patch version of the default gitVersion.
|
||||||
|
func SetDynamicVersion(dynamicVersion string) error {
|
||||||
|
if err := ValidateDynamicVersion(dynamicVersion); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dynamicGitVersion.Store(dynamicVersion)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateDynamicVersion ensures the given version is non-empty, a valid semantic version,
|
||||||
|
// and matched the major/minor/patch version of the default gitVersion.
|
||||||
|
func ValidateDynamicVersion(dynamicVersion string) error {
|
||||||
|
return validateDynamicVersion(dynamicVersion, gitVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateDynamicVersion(dynamicVersion, defaultVersion string) error {
|
||||||
|
if len(dynamicVersion) == 0 {
|
||||||
|
return fmt.Errorf("version must not be empty")
|
||||||
|
}
|
||||||
|
if dynamicVersion == defaultVersion {
|
||||||
|
// allow no-op
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
vRuntime, err := utilversion.ParseSemantic(dynamicVersion)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// must match major/minor/patch of default version
|
||||||
|
var vDefault *utilversion.Version
|
||||||
|
if defaultVersion == "v0.0.0-master+$Format:%H$" {
|
||||||
|
// special-case the placeholder value which doesn't parse as a semantic version
|
||||||
|
vDefault, err = utilversion.ParseSemantic("v0.0.0-master")
|
||||||
|
} else {
|
||||||
|
vDefault, err = utilversion.ParseSemantic(defaultVersion)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if vRuntime.Major() != vDefault.Major() || vRuntime.Minor() != vDefault.Minor() || vRuntime.Patch() != vDefault.Patch() {
|
||||||
|
return fmt.Errorf("version %q must match major/minor/patch of default version %q", dynamicVersion, defaultVersion)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -20,20 +20,22 @@ package verflag
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
flag "github.com/spf13/pflag"
|
flag "github.com/spf13/pflag"
|
||||||
|
|
||||||
"k8s.io/component-base/version"
|
"k8s.io/component-base/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
type versionValue int
|
type versionValue string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
VersionFalse versionValue = 0
|
VersionFalse versionValue = "false"
|
||||||
VersionTrue versionValue = 1
|
VersionTrue versionValue = "true"
|
||||||
VersionRaw versionValue = 2
|
VersionRaw versionValue = "raw"
|
||||||
)
|
)
|
||||||
|
|
||||||
const strRawVersion string = "raw"
|
const strRawVersion string = "raw"
|
||||||
@ -51,20 +53,28 @@ func (v *versionValue) Set(s string) error {
|
|||||||
*v = VersionRaw
|
*v = VersionRaw
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(s, "v") {
|
||||||
|
err := version.SetDynamicVersion(s)
|
||||||
|
if err == nil {
|
||||||
|
*v = versionValue(s)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
boolVal, err := strconv.ParseBool(s)
|
boolVal, err := strconv.ParseBool(s)
|
||||||
if boolVal {
|
if err == nil {
|
||||||
*v = VersionTrue
|
if boolVal {
|
||||||
} else {
|
*v = VersionTrue
|
||||||
*v = VersionFalse
|
} else {
|
||||||
|
*v = VersionFalse
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *versionValue) String() string {
|
func (v *versionValue) String() string {
|
||||||
if *v == VersionRaw {
|
return string(*v)
|
||||||
return strRawVersion
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%v", bool(*v == VersionTrue))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The type of the flag as required by the pflag.Value interface
|
// The type of the flag as required by the pflag.Value interface
|
||||||
@ -88,7 +98,7 @@ func Version(name string, value versionValue, usage string) *versionValue {
|
|||||||
const versionFlagName = "version"
|
const versionFlagName = "version"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
versionFlag = Version(versionFlagName, VersionFalse, "Print version information and quit")
|
versionFlag = Version(versionFlagName, VersionFalse, "--version, --version=raw prints version information and quits; --version=vX.Y.Z... sets the reported version")
|
||||||
programName = "Kubernetes"
|
programName = "Kubernetes"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -98,14 +108,20 @@ func AddFlags(fs *flag.FlagSet) {
|
|||||||
fs.AddFlag(flag.Lookup(versionFlagName))
|
fs.AddFlag(flag.Lookup(versionFlagName))
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrintAndExitIfRequested will check if the -version flag was passed
|
// variables for unit testing PrintAndExitIfRequested
|
||||||
|
var (
|
||||||
|
output = io.Writer(os.Stdout)
|
||||||
|
exit = os.Exit
|
||||||
|
)
|
||||||
|
|
||||||
|
// PrintAndExitIfRequested will check if --version or --version=raw was passed
|
||||||
// and, if so, print the version and exit.
|
// and, if so, print the version and exit.
|
||||||
func PrintAndExitIfRequested() {
|
func PrintAndExitIfRequested() {
|
||||||
if *versionFlag == VersionRaw {
|
if *versionFlag == VersionRaw {
|
||||||
fmt.Printf("%#v\n", version.Get())
|
fmt.Fprintf(output, "%#v\n", version.Get())
|
||||||
os.Exit(0)
|
exit(0)
|
||||||
} else if *versionFlag == VersionTrue {
|
} else if *versionFlag == VersionTrue {
|
||||||
fmt.Printf("%s %s\n", programName, version.Get())
|
fmt.Fprintf(output, "%s %s\n", programName, version.Get())
|
||||||
os.Exit(0)
|
exit(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,166 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 The Kubernetes Authors.
|
||||||
|
|
||||||
|
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 verflag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
|
||||||
|
"k8s.io/component-base/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestVersionFlag(t *testing.T) {
|
||||||
|
initialFlagValue := string(*versionFlag)
|
||||||
|
initialVersion := version.Get()
|
||||||
|
|
||||||
|
testcases := []struct {
|
||||||
|
name string
|
||||||
|
flags []string
|
||||||
|
expectError string
|
||||||
|
expectExit bool
|
||||||
|
expectPrintVersion string
|
||||||
|
expectGitVersion string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no flag",
|
||||||
|
flags: []string{},
|
||||||
|
expectGitVersion: initialVersion.GitVersion,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "false",
|
||||||
|
flags: []string{"--version=false"},
|
||||||
|
expectGitVersion: initialVersion.GitVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "valueless",
|
||||||
|
flags: []string{"--version"},
|
||||||
|
expectGitVersion: initialVersion.GitVersion,
|
||||||
|
expectExit: true,
|
||||||
|
expectPrintVersion: "Kubernetes " + initialVersion.GitVersion,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "true",
|
||||||
|
flags: []string{"--version=true"},
|
||||||
|
expectGitVersion: initialVersion.GitVersion,
|
||||||
|
expectExit: true,
|
||||||
|
expectPrintVersion: "Kubernetes " + initialVersion.GitVersion,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "raw",
|
||||||
|
flags: []string{"--version=raw"},
|
||||||
|
expectGitVersion: initialVersion.GitVersion,
|
||||||
|
expectExit: true,
|
||||||
|
expectPrintVersion: fmt.Sprintf("%#v", initialVersion),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "truthy",
|
||||||
|
flags: []string{"--version=T"},
|
||||||
|
expectGitVersion: initialVersion.GitVersion,
|
||||||
|
expectExit: true,
|
||||||
|
expectPrintVersion: "Kubernetes " + initialVersion.GitVersion,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "override",
|
||||||
|
flags: []string{"--version=v0.0.0-custom"},
|
||||||
|
expectGitVersion: "v0.0.0-custom",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid override semver",
|
||||||
|
flags: []string{"--version=vX"},
|
||||||
|
expectError: `could not parse "vX"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid override major",
|
||||||
|
flags: []string{"--version=v1.0.0"},
|
||||||
|
expectError: `must match major/minor/patch`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid override minor",
|
||||||
|
flags: []string{"--version=v0.1.0"},
|
||||||
|
expectError: `must match major/minor/patch`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid override patch",
|
||||||
|
flags: []string{"--version=v0.0.1"},
|
||||||
|
expectError: `must match major/minor/patch`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "override and exit",
|
||||||
|
flags: []string{"--version=v0.0.0-custom", "--version"},
|
||||||
|
expectGitVersion: "v0.0.0-custom",
|
||||||
|
expectExit: true,
|
||||||
|
expectPrintVersion: "Kubernetes v0.0.0-custom",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testcases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
|
||||||
|
originalOutput := output
|
||||||
|
originalExit := exit
|
||||||
|
|
||||||
|
outputBuffer := &bytes.Buffer{}
|
||||||
|
output = outputBuffer
|
||||||
|
exitCalled := false
|
||||||
|
exit = func(code int) { exitCalled = true }
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
output = originalOutput
|
||||||
|
exit = originalExit
|
||||||
|
*versionFlag = versionValue(initialFlagValue)
|
||||||
|
err := version.SetDynamicVersion(initialVersion.GitVersion)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
fs := pflag.NewFlagSet("test", pflag.ContinueOnError)
|
||||||
|
AddFlags(fs)
|
||||||
|
err := fs.Parse(tc.flags)
|
||||||
|
if tc.expectError != "" {
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error, got none")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), tc.expectError) {
|
||||||
|
t.Fatalf("expected error containing %q, got %q", tc.expectError, err.Error())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
} else if err != nil {
|
||||||
|
t.Fatalf("unexpected parse error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if e, a := tc.expectGitVersion, version.Get().GitVersion; e != a {
|
||||||
|
t.Fatalf("gitversion: expected %v, got %v", e, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
PrintAndExitIfRequested()
|
||||||
|
if e, a := tc.expectExit, exitCalled; e != a {
|
||||||
|
t.Fatalf("exit(): expected %v, got %v", e, a)
|
||||||
|
}
|
||||||
|
if e, a := tc.expectPrintVersion, strings.TrimSpace(outputBuffer.String()); e != a {
|
||||||
|
t.Fatalf("print version: expected %v, got %v", e, a)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -31,7 +31,7 @@ func Get() apimachineryversion.Info {
|
|||||||
return apimachineryversion.Info{
|
return apimachineryversion.Info{
|
||||||
Major: gitMajor,
|
Major: gitMajor,
|
||||||
Minor: gitMinor,
|
Minor: gitMinor,
|
||||||
GitVersion: gitVersion,
|
GitVersion: dynamicGitVersion.Load().(string),
|
||||||
GitCommit: gitCommit,
|
GitCommit: gitCommit,
|
||||||
GitTreeState: gitTreeState,
|
GitTreeState: gitTreeState,
|
||||||
BuildDate: buildDate,
|
BuildDate: buildDate,
|
||||||
|
@ -30,15 +30,13 @@ import (
|
|||||||
"k8s.io/apiserver/pkg/endpoints/discovery/aggregated"
|
"k8s.io/apiserver/pkg/endpoints/discovery/aggregated"
|
||||||
genericfeatures "k8s.io/apiserver/pkg/features"
|
genericfeatures "k8s.io/apiserver/pkg/features"
|
||||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||||
|
"k8s.io/apiserver/pkg/server/dynamiccertificates"
|
||||||
"k8s.io/apiserver/pkg/server/egressselector"
|
"k8s.io/apiserver/pkg/server/egressselector"
|
||||||
serverstorage "k8s.io/apiserver/pkg/server/storage"
|
serverstorage "k8s.io/apiserver/pkg/server/storage"
|
||||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
"k8s.io/client-go/pkg/version"
|
|
||||||
"k8s.io/client-go/transport"
|
"k8s.io/client-go/transport"
|
||||||
openapicommon "k8s.io/kube-openapi/pkg/common"
|
"k8s.io/component-base/version"
|
||||||
|
|
||||||
"k8s.io/apiserver/pkg/server/dynamiccertificates"
|
|
||||||
v1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
|
v1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
|
||||||
v1helper "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1/helper"
|
v1helper "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1/helper"
|
||||||
"k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1"
|
"k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1"
|
||||||
@ -52,6 +50,7 @@ import (
|
|||||||
openapiv3aggregator "k8s.io/kube-aggregator/pkg/controllers/openapiv3/aggregator"
|
openapiv3aggregator "k8s.io/kube-aggregator/pkg/controllers/openapiv3/aggregator"
|
||||||
statuscontrollers "k8s.io/kube-aggregator/pkg/controllers/status"
|
statuscontrollers "k8s.io/kube-aggregator/pkg/controllers/status"
|
||||||
apiservicerest "k8s.io/kube-aggregator/pkg/registry/apiservice/rest"
|
apiservicerest "k8s.io/kube-aggregator/pkg/registry/apiservice/rest"
|
||||||
|
openapicommon "k8s.io/kube-openapi/pkg/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
Loading…
Reference in New Issue
Block a user