202 lines
6.0 KiB
Go
202 lines
6.0 KiB
Go
package channelserver
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/blang/semver"
|
|
"github.com/rancher/channelserver/pkg/config"
|
|
"github.com/rancher/channelserver/pkg/model"
|
|
"github.com/rancher/channelserver/pkg/server"
|
|
"github.com/rancher/rancher/pkg/catalog/utils"
|
|
"github.com/rancher/rancher/pkg/settings"
|
|
"github.com/rancher/wrangler/v3/pkg/data"
|
|
"github.com/rancher/wrangler/v3/pkg/schemas"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
var (
|
|
configs map[string]*config.Config
|
|
configsInit sync.Once
|
|
action chan string
|
|
)
|
|
|
|
func GetURLAndInterval() (string, time.Duration) {
|
|
val := map[string]interface{}{}
|
|
if err := json.Unmarshal([]byte(settings.RkeMetadataConfig.Get()), &val); err != nil {
|
|
logrus.Errorf("failed to parse %s value: %v", settings.RkeMetadataConfig.Name, err)
|
|
return "", 0
|
|
}
|
|
url := data.Object(val).String("url")
|
|
minutes, _ := strconv.Atoi(data.Object(val).String("refresh-interval-minutes"))
|
|
if minutes <= 0 {
|
|
minutes = 1440
|
|
}
|
|
|
|
return url, time.Duration(minutes) * time.Minute
|
|
}
|
|
|
|
// getChannelServerArg will return with an argument to pass to channel server
|
|
// to indicate the server version that is running. If the current version is
|
|
// not a proper release version, the argument will be set to the dev version.
|
|
func getChannelServerArg() string {
|
|
serverVersion := settings.ServerVersion.Get()
|
|
if !utils.ReleaseServerVersion(serverVersion) {
|
|
return settings.RancherVersionDev
|
|
}
|
|
return serverVersion
|
|
}
|
|
|
|
type DynamicInterval struct {
|
|
subKey string
|
|
}
|
|
|
|
func (d *DynamicInterval) Wait(ctx context.Context) bool {
|
|
start := time.Now()
|
|
for {
|
|
select {
|
|
case <-time.After(time.Second):
|
|
_, duration := GetURLAndInterval()
|
|
if start.Add(duration).Before(time.Now()) {
|
|
return true
|
|
}
|
|
continue
|
|
case msg := <-action:
|
|
if msg == d.subKey {
|
|
logrus.Infof("getReleaseConfig: reloading config for %s", d.subKey)
|
|
return true
|
|
}
|
|
action <- msg
|
|
case <-ctx.Done():
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
func Refresh() {
|
|
action <- "k3s"
|
|
action <- "rke2"
|
|
}
|
|
|
|
type DynamicSource struct{}
|
|
|
|
func (d *DynamicSource) URL() string {
|
|
url, _ := GetURLAndInterval()
|
|
return url
|
|
}
|
|
|
|
func GetReleaseConfigByRuntimeAndVersion(ctx context.Context, runtime, kubernetesVersion string) model.Release {
|
|
fallBack := model.Release{
|
|
AgentArgs: map[string]schemas.Field{},
|
|
ServerArgs: map[string]schemas.Field{},
|
|
}
|
|
for _, releaseData := range GetReleaseConfigByRuntime(ctx, runtime).ReleasesConfig().Releases {
|
|
if releaseData.Version == kubernetesVersion {
|
|
return releaseData
|
|
}
|
|
for k, v := range releaseData.ServerArgs {
|
|
fallBack.ServerArgs[k] = v
|
|
}
|
|
for k, v := range releaseData.AgentArgs {
|
|
fallBack.AgentArgs[k] = v
|
|
}
|
|
}
|
|
return fallBack
|
|
}
|
|
|
|
func GetReleaseConfigByRuntime(ctx context.Context, runtime string) *config.Config {
|
|
configsInit.Do(func() {
|
|
urls := []config.Source{
|
|
&DynamicSource{},
|
|
config.StringSource("/var/lib/rancher-data/driver-metadata/data.json"),
|
|
}
|
|
configs = map[string]*config.Config{
|
|
"k3s": config.NewConfig(ctx, "k3s", &DynamicInterval{"k3s"}, getChannelServerArg(), "rancher", urls),
|
|
"rke2": config.NewConfig(ctx, "rke2", &DynamicInterval{"rke2"}, getChannelServerArg(), "rancher", urls),
|
|
}
|
|
})
|
|
return configs[runtime]
|
|
}
|
|
|
|
func NewHandler(ctx context.Context) http.Handler {
|
|
action = make(chan string, 2)
|
|
return server.NewHandler(map[string]*config.Config{
|
|
"v1-k3s-release": GetReleaseConfigByRuntime(ctx, "k3s"),
|
|
"v1-rke2-release": GetReleaseConfigByRuntime(ctx, "rke2"),
|
|
})
|
|
}
|
|
|
|
func GetDefaultByRuntimeAndServerVersion(ctx context.Context, runtime, serverVersion string) string {
|
|
version, err := getDefaultFromAppDefaultsByRuntimeAndServerVersion(ctx, runtime, serverVersion)
|
|
if err != nil {
|
|
logrus.Debugf("[channelserver] fallback to use the default channel due to: %v", err)
|
|
version = getDefaultFromChannel(ctx, runtime, "default")
|
|
}
|
|
return version
|
|
}
|
|
|
|
func getDefaultFromAppDefaultsByRuntimeAndServerVersion(ctx context.Context, runtime, serverVersion string) (string, error) {
|
|
var defaultVersionRange string
|
|
serverVersionParsed, err := semver.ParseTolerant(serverVersion)
|
|
if err != nil {
|
|
return "", fmt.Errorf("fails to parse the server version: %v", err)
|
|
}
|
|
config := GetReleaseConfigByRuntime(ctx, runtime)
|
|
appDefaults := config.AppDefaultsConfig().AppDefaults
|
|
if len(appDefaults) == 0 {
|
|
return "", fmt.Errorf("no %s appDefaults is found for %s", runtime, serverVersion)
|
|
}
|
|
// We use the first entry from the list. We do not expect the list contains more than one entry.
|
|
for _, appDefault := range appDefaults[0].Defaults {
|
|
avrParsed, err := semver.ParseRange(appDefault.AppVersion)
|
|
if err != nil {
|
|
return "", fmt.Errorf("faild to parse %s appVersionRange for %s: %v", runtime, serverVersion, err)
|
|
}
|
|
if avrParsed(serverVersionParsed) {
|
|
defaultVersionRange = appDefault.DefaultVersion
|
|
continue
|
|
}
|
|
}
|
|
if defaultVersionRange == "" {
|
|
return "", fmt.Errorf("no matching %s defaultVersionRange is found for %s", runtime, serverVersion)
|
|
}
|
|
dvrParsed, err := semver.ParseRange(defaultVersionRange)
|
|
if err != nil {
|
|
return "", fmt.Errorf("faild to parse %s defaultVersionRange for %s: %v", runtime, serverVersion, err)
|
|
}
|
|
|
|
var candidate []semver.Version
|
|
for _, release := range config.ReleasesConfig().Releases {
|
|
version, err := semver.ParseTolerant(release.Version)
|
|
if err != nil {
|
|
logrus.Debugf("fails to parse the release version %s: %v", release.Version, err)
|
|
continue
|
|
}
|
|
if dvrParsed(version) {
|
|
candidate = append(candidate, version)
|
|
}
|
|
}
|
|
if len(candidate) == 0 {
|
|
return "", fmt.Errorf("no %s version is found for %s", runtime, serverVersion)
|
|
}
|
|
// the build metadata parts are ignored when sorting versions for now;
|
|
// ideally they should be since k3s/RKE2 releases use it to establish order of precedence
|
|
semver.Sort(candidate)
|
|
return candidate[len(candidate)-1].String(), nil
|
|
}
|
|
|
|
func getDefaultFromChannel(ctx context.Context, runtime, channelName string) string {
|
|
config := GetReleaseConfigByRuntime(ctx, runtime)
|
|
for _, c := range config.ChannelsConfig().Channels {
|
|
if c.Name == channelName {
|
|
return c.Latest
|
|
}
|
|
}
|
|
return ""
|
|
}
|