OSUpgrade: Version Planning
This commit is contained in:
80
clitools/pkg/controller/osupgrade/handler.go
Normal file
80
clitools/pkg/controller/osupgrade/handler.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package osupgrade
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
monov1alpha1 "example.com/monok8s/pkg/apis/monok8s/v1alpha1"
|
||||
"example.com/monok8s/pkg/buildinfo"
|
||||
"example.com/monok8s/pkg/catalog"
|
||||
"example.com/monok8s/pkg/kube"
|
||||
)
|
||||
|
||||
func HandleOSUpgrade(
|
||||
ctx context.Context,
|
||||
clients *kube.Clients,
|
||||
namespace string,
|
||||
nodeName string,
|
||||
osu *monov1alpha1.OSUpgrade,
|
||||
) error {
|
||||
osup, err := ensureProgressHeartbeat(ctx, clients, namespace, nodeName, osu)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
klog.InfoS("handling osupgrade",
|
||||
"name", osu.Name,
|
||||
"node", nodeName,
|
||||
"desiredVersion", osu.Spec.DesiredVersion,
|
||||
)
|
||||
|
||||
kata, err := catalog.ResolveCatalog(ctx, clients.Kubernetes, namespace, osu.Spec.Catalog)
|
||||
if err != nil {
|
||||
return failProgress(ctx, clients, osup, "resolve catalog", err)
|
||||
}
|
||||
|
||||
plan, err := PlanUpgrade(buildinfo.KubeVersion, osu, kata)
|
||||
if err != nil {
|
||||
return failProgress(ctx, clients, osup, "plan upgrade", err)
|
||||
}
|
||||
|
||||
if len(plan.Path) == 0 {
|
||||
osup.Status.CurrentVersion = buildinfo.KubeVersion
|
||||
osup.Status.TargetVersion = buildinfo.KubeVersion
|
||||
if err := markProgressCompleted(ctx, clients, osup, "already at target version"); err != nil {
|
||||
return err
|
||||
}
|
||||
klog.InfoS("osupgrade already satisfied",
|
||||
"name", osu.Name,
|
||||
"node", nodeName,
|
||||
"target", plan.ResolvedTarget,
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
first := plan.Path[0]
|
||||
|
||||
osup.Status.TargetVersion = plan.ResolvedTarget
|
||||
osup.Status.Phase = monov1alpha1.OSUpgradeProgressPhaseDownloading
|
||||
osup.Status.Message = fmt.Sprintf("downloading image: %s", first.URL)
|
||||
now := metav1.Now()
|
||||
osup.Status.LastUpdatedAt = &now
|
||||
|
||||
if _, err := updateProgressStatus(ctx, clients, osup_gvr, osup); err != nil {
|
||||
return fmt.Errorf("update progress status: %w", err)
|
||||
}
|
||||
|
||||
klog.InfoS("planned osupgrade",
|
||||
"name", osu.Name,
|
||||
"node", nodeName,
|
||||
"resolvedTarget", plan.ResolvedTarget,
|
||||
"steps", len(plan.Path),
|
||||
"firstVersion", first.Version,
|
||||
"firstURL", first.URL,
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
327
clitools/pkg/controller/osupgrade/planner.go
Normal file
327
clitools/pkg/controller/osupgrade/planner.go
Normal file
@@ -0,0 +1,327 @@
|
||||
package osupgrade
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
monov1alpha1 "example.com/monok8s/pkg/apis/monok8s/v1alpha1"
|
||||
"example.com/monok8s/pkg/catalog"
|
||||
)
|
||||
|
||||
type Version struct {
|
||||
Major int
|
||||
Minor int
|
||||
Patch int
|
||||
Raw string
|
||||
}
|
||||
|
||||
func ParseVersion(s string) (Version, error) {
|
||||
raw := strings.TrimSpace(s)
|
||||
if raw == "" {
|
||||
return Version{}, fmt.Errorf("empty version")
|
||||
}
|
||||
|
||||
raw = strings.TrimPrefix(raw, "v")
|
||||
parts := strings.Split(raw, ".")
|
||||
if len(parts) != 3 {
|
||||
return Version{}, fmt.Errorf("invalid version %q: expected vMAJOR.MINOR.PATCH", s)
|
||||
}
|
||||
|
||||
maj, err := strconv.Atoi(parts[0])
|
||||
if err != nil {
|
||||
return Version{}, fmt.Errorf("parse major from %q: %w", s, err)
|
||||
}
|
||||
min, err := strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
return Version{}, fmt.Errorf("parse minor from %q: %w", s, err)
|
||||
}
|
||||
patch, err := strconv.Atoi(parts[2])
|
||||
if err != nil {
|
||||
return Version{}, fmt.Errorf("parse patch from %q: %w", s, err)
|
||||
}
|
||||
|
||||
return Version{
|
||||
Major: maj,
|
||||
Minor: min,
|
||||
Patch: patch,
|
||||
Raw: fmt.Sprintf("v%d.%d.%d", maj, min, patch),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (v Version) String() string {
|
||||
return v.Raw
|
||||
}
|
||||
|
||||
func (v Version) Compare(o Version) int {
|
||||
if v.Major != o.Major {
|
||||
if v.Major < o.Major {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
}
|
||||
if v.Minor != o.Minor {
|
||||
if v.Minor < o.Minor {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
}
|
||||
if v.Patch != o.Patch {
|
||||
if v.Patch < o.Patch {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (v Version) SameMinor(o Version) bool {
|
||||
return v.Major == o.Major && v.Minor == o.Minor
|
||||
}
|
||||
|
||||
type Plan struct {
|
||||
ResolvedTarget string
|
||||
Path []catalog.CatalogImage
|
||||
}
|
||||
|
||||
func PlanUpgrade(
|
||||
current string,
|
||||
osu *monov1alpha1.OSUpgrade,
|
||||
cat *catalog.VersionCatalog,
|
||||
) (*Plan, error) {
|
||||
target, err := resolveTarget(osu.Spec.DesiredVersion, cat)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if isBlocked(target, cat.Blocked) {
|
||||
return nil, fmt.Errorf("target %s is blocked", target)
|
||||
}
|
||||
|
||||
imagesByVersion := make(map[string]catalog.CatalogImage, len(cat.Images))
|
||||
installable := make([]string, 0, len(cat.Images))
|
||||
|
||||
for _, img := range cat.Images {
|
||||
if img.Version == "" {
|
||||
continue
|
||||
}
|
||||
if isBlocked(img.Version, cat.Blocked) {
|
||||
continue
|
||||
}
|
||||
if _, exists := imagesByVersion[img.Version]; exists {
|
||||
return nil, fmt.Errorf("duplicate image entry for version %s", img.Version)
|
||||
}
|
||||
imagesByVersion[img.Version] = img
|
||||
installable = append(installable, img.Version)
|
||||
}
|
||||
|
||||
versionPath, err := calculatePath(current, target, installable)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
path := make([]catalog.CatalogImage, 0, len(versionPath))
|
||||
for _, v := range versionPath {
|
||||
img, ok := imagesByVersion[v]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("internal error: no image for planned version %s", v)
|
||||
}
|
||||
path = append(path, img)
|
||||
}
|
||||
|
||||
return &Plan{
|
||||
ResolvedTarget: target,
|
||||
Path: path,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func installableVersions(cat *catalog.VersionCatalog) []string {
|
||||
out := make([]string, 0, len(cat.Images))
|
||||
for _, img := range cat.Images {
|
||||
if img.Version == "" {
|
||||
continue
|
||||
}
|
||||
if isBlocked(img.Version, cat.Blocked) {
|
||||
continue
|
||||
}
|
||||
out = append(out, img.Version)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func resolveTarget(desired string, cat *catalog.VersionCatalog) (string, error) {
|
||||
if desired == "stable" {
|
||||
if cat.Stable == "" {
|
||||
return "", fmt.Errorf("catalog missing stable")
|
||||
}
|
||||
return cat.Stable, nil
|
||||
}
|
||||
|
||||
for _, img := range cat.Images {
|
||||
if img.Version == desired {
|
||||
return desired, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("desired version %s not in catalog", desired)
|
||||
}
|
||||
|
||||
func calculatePath(current, target string, available []string) ([]string, error) {
|
||||
cur, err := ParseVersion(current)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse current version: %w", err)
|
||||
}
|
||||
|
||||
tgt, err := ParseVersion(target)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse target version: %w", err)
|
||||
}
|
||||
|
||||
if cur.Compare(tgt) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if cur.Compare(tgt) > 0 {
|
||||
return nil, fmt.Errorf("downgrade not supported: current=%s target=%s", cur, tgt)
|
||||
}
|
||||
|
||||
if cur.Major != tgt.Major {
|
||||
return nil, fmt.Errorf("cross-major upgrade not supported: %s -> %s", cur, tgt)
|
||||
}
|
||||
|
||||
versions, err := parseAndSortVersions(available)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !containsVersion(versions, tgt) {
|
||||
return nil, fmt.Errorf("target version %s not found in available versions", tgt)
|
||||
}
|
||||
|
||||
var path []Version
|
||||
seen := map[string]struct{}{}
|
||||
|
||||
add := func(v Version) {
|
||||
if v.Compare(cur) <= 0 {
|
||||
return
|
||||
}
|
||||
if _, ok := seen[v.String()]; ok {
|
||||
return
|
||||
}
|
||||
seen[v.String()] = struct{}{}
|
||||
path = append(path, v)
|
||||
}
|
||||
|
||||
// Same minor: jump directly to target patch.
|
||||
if cur.SameMinor(tgt) {
|
||||
add(tgt)
|
||||
return versionsToStrings(path), nil
|
||||
}
|
||||
|
||||
// Step 1: finish current minor by moving to the latest patch available there.
|
||||
if latestCurMinor, ok := latestPatchInMinor(versions, cur.Major, cur.Minor, cur); ok {
|
||||
add(latestCurMinor)
|
||||
}
|
||||
|
||||
// Step 2: walk each intermediate minor using the lowest available patch in that minor.
|
||||
for minor := cur.Minor + 1; minor < tgt.Minor; minor++ {
|
||||
bridge, ok := lowestPatchInMinor(versions, cur.Major, minor)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no available bridge version for v%d.%d.x", cur.Major, minor)
|
||||
}
|
||||
add(bridge)
|
||||
}
|
||||
|
||||
// Step 3: final target.
|
||||
add(tgt)
|
||||
|
||||
return versionsToStrings(path), nil
|
||||
}
|
||||
|
||||
func parseAndSortVersions(raw []string) ([]Version, error) {
|
||||
out := make([]Version, 0, len(raw))
|
||||
seen := map[string]struct{}{}
|
||||
|
||||
for _, s := range raw {
|
||||
v, err := ParseVersion(s)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse catalog version %q: %w", s, err)
|
||||
}
|
||||
if _, ok := seen[v.String()]; ok {
|
||||
continue
|
||||
}
|
||||
seen[v.String()] = struct{}{}
|
||||
out = append(out, v)
|
||||
}
|
||||
|
||||
sort.Slice(out, func(i, j int) bool {
|
||||
return out[i].Compare(out[j]) < 0
|
||||
})
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func containsRawVersion(versions []string, want string) bool {
|
||||
for _, v := range versions {
|
||||
if strings.TrimSpace(v) == strings.TrimSpace(want) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func containsVersion(versions []Version, want Version) bool {
|
||||
for _, v := range versions {
|
||||
if v.Compare(want) == 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isBlocked(version string, blocked []string) bool {
|
||||
for _, v := range blocked {
|
||||
if strings.TrimSpace(v) == strings.TrimSpace(version) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func latestPatchInMinor(versions []Version, major, minor int, gt Version) (Version, bool) {
|
||||
var found Version
|
||||
ok := false
|
||||
|
||||
for _, v := range versions {
|
||||
if v.Major != major || v.Minor != minor {
|
||||
continue
|
||||
}
|
||||
if v.Compare(gt) <= 0 {
|
||||
continue
|
||||
}
|
||||
if !ok || found.Compare(v) < 0 {
|
||||
found = v
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
|
||||
return found, ok
|
||||
}
|
||||
|
||||
func lowestPatchInMinor(versions []Version, major, minor int) (Version, bool) {
|
||||
for _, v := range versions {
|
||||
if v.Major == major && v.Minor == minor {
|
||||
return v, true
|
||||
}
|
||||
}
|
||||
return Version{}, false
|
||||
}
|
||||
|
||||
func versionsToStrings(vs []Version) []string {
|
||||
out := make([]string, 0, len(vs))
|
||||
for _, v := range vs {
|
||||
out = append(out, v.String())
|
||||
}
|
||||
return out
|
||||
}
|
||||
265
clitools/pkg/controller/osupgrade/progress.go
Normal file
265
clitools/pkg/controller/osupgrade/progress.go
Normal file
@@ -0,0 +1,265 @@
|
||||
package osupgrade
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
monov1alpha1 "example.com/monok8s/pkg/apis/monok8s/v1alpha1"
|
||||
"example.com/monok8s/pkg/buildinfo"
|
||||
"example.com/monok8s/pkg/kube"
|
||||
)
|
||||
|
||||
var (
|
||||
unstructuredConverter = runtime.DefaultUnstructuredConverter
|
||||
|
||||
osup_gvr = schema.GroupVersionResource{
|
||||
Group: monov1alpha1.Group,
|
||||
Version: monov1alpha1.Version,
|
||||
Resource: "osupgradeprogresses",
|
||||
}
|
||||
)
|
||||
|
||||
func ensureProgressHeartbeat(
|
||||
ctx context.Context,
|
||||
clients *kube.Clients,
|
||||
namespace string,
|
||||
nodeName string,
|
||||
osu *monov1alpha1.OSUpgrade,
|
||||
) (*monov1alpha1.OSUpgradeProgress, error) {
|
||||
|
||||
name := fmt.Sprintf("%s-%s", osu.Name, nodeName)
|
||||
now := metav1.Now()
|
||||
|
||||
currentVersion := buildinfo.KubeVersion
|
||||
targetVersion := ""
|
||||
|
||||
if osu.Status != nil {
|
||||
targetVersion = osu.Status.ResolvedVersion
|
||||
}
|
||||
|
||||
progress := &monov1alpha1.OSUpgradeProgress{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: monov1alpha1.APIVersion,
|
||||
Kind: "OSUpgradeProgress",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: monov1alpha1.OSUpgradeProgressSpec{
|
||||
NodeName: nodeName,
|
||||
SourceRef: monov1alpha1.OSUpgradeSourceRef{
|
||||
Name: osu.Name,
|
||||
},
|
||||
},
|
||||
Status: &monov1alpha1.OSUpgradeProgressStatus{
|
||||
CurrentVersion: currentVersion,
|
||||
TargetVersion: targetVersion,
|
||||
Phase: monov1alpha1.OSUpgradeProgressPhasePending,
|
||||
LastUpdatedAt: &now,
|
||||
Message: "acknowledged",
|
||||
},
|
||||
}
|
||||
|
||||
created, err := createProgress(ctx, clients, osup_gvr, progress)
|
||||
if err == nil {
|
||||
klog.InfoS("created osupgradeprogress", "name", created.Name, "namespace", created.Namespace)
|
||||
return created, nil
|
||||
}
|
||||
if !apierrors.IsAlreadyExists(err) {
|
||||
return nil, fmt.Errorf("create OSUpgradeProgress %s/%s: %w", namespace, name, err)
|
||||
}
|
||||
|
||||
existing, err := getProgress(ctx, clients, osup_gvr, namespace, name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get existing OSUpgradeProgress %s/%s: %w", namespace, name, err)
|
||||
}
|
||||
|
||||
// Spec should remain aligned with the source and node.
|
||||
existing.Spec.NodeName = nodeName
|
||||
existing.Spec.SourceRef.Name = osu.Name
|
||||
|
||||
if existing, err = updateProgressSpec(ctx, clients, osup_gvr, existing); err != nil {
|
||||
return nil, fmt.Errorf("update OSUpgradeProgress spec %s/%s: %w", namespace, name, err)
|
||||
}
|
||||
|
||||
if existing.Status == nil {
|
||||
existing.Status = &monov1alpha1.OSUpgradeProgressStatus{}
|
||||
}
|
||||
|
||||
existing.Status.CurrentVersion = currentVersion
|
||||
existing.Status.TargetVersion = targetVersion
|
||||
existing.Status.LastUpdatedAt = &now
|
||||
|
||||
// Only set phase/message if they are still empty, so later real state machine
|
||||
// updates are not clobbered by the heartbeat.
|
||||
if existing.Status.Phase == "" {
|
||||
existing.Status.Phase = monov1alpha1.OSUpgradeProgressPhasePending
|
||||
}
|
||||
|
||||
if existing, err = updateProgressStatus(ctx, clients, osup_gvr, existing); err != nil {
|
||||
return nil, fmt.Errorf("update OSUpgradeProgress status %s/%s: %w", namespace, name, err)
|
||||
}
|
||||
|
||||
klog.InfoS("updated osupgradeprogress", "name", existing.Name, "namespace", existing.Namespace)
|
||||
return existing, nil
|
||||
}
|
||||
|
||||
func createProgress(
|
||||
ctx context.Context,
|
||||
clients *kube.Clients,
|
||||
gvr schema.GroupVersionResource,
|
||||
progress *monov1alpha1.OSUpgradeProgress,
|
||||
) (*monov1alpha1.OSUpgradeProgress, error) {
|
||||
obj, err := toUnstructured(progress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
created, err := clients.Dynamic.
|
||||
Resource(gvr).
|
||||
Namespace(progress.Namespace).
|
||||
Create(ctx, obj, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return fromUnstructuredProgress(created)
|
||||
}
|
||||
|
||||
func getProgress(
|
||||
ctx context.Context,
|
||||
clients *kube.Clients,
|
||||
gvr schema.GroupVersionResource,
|
||||
namespace, name string,
|
||||
) (*monov1alpha1.OSUpgradeProgress, error) {
|
||||
got, err := clients.Dynamic.
|
||||
Resource(gvr).
|
||||
Namespace(namespace).
|
||||
Get(ctx, name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return fromUnstructuredProgress(got)
|
||||
}
|
||||
|
||||
func updateProgressSpec(
|
||||
ctx context.Context,
|
||||
clients *kube.Clients,
|
||||
gvr schema.GroupVersionResource,
|
||||
progress *monov1alpha1.OSUpgradeProgress,
|
||||
) (*monov1alpha1.OSUpgradeProgress, error) {
|
||||
obj, err := toUnstructured(progress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
updated, err := clients.Dynamic.
|
||||
Resource(gvr).
|
||||
Namespace(progress.Namespace).
|
||||
Update(ctx, obj, metav1.UpdateOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return fromUnstructuredProgress(updated)
|
||||
}
|
||||
|
||||
func updateProgressStatus(
|
||||
ctx context.Context,
|
||||
clients *kube.Clients,
|
||||
gvr schema.GroupVersionResource,
|
||||
progress *monov1alpha1.OSUpgradeProgress,
|
||||
) (*monov1alpha1.OSUpgradeProgress, error) {
|
||||
obj, err := toUnstructured(progress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
updated, err := clients.Dynamic.
|
||||
Resource(gvr).
|
||||
Namespace(progress.Namespace).
|
||||
UpdateStatus(ctx, obj, metav1.UpdateOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return fromUnstructuredProgress(updated)
|
||||
}
|
||||
|
||||
func failProgress(
|
||||
ctx context.Context,
|
||||
clients *kube.Clients,
|
||||
osup *monov1alpha1.OSUpgradeProgress,
|
||||
action string,
|
||||
cause error,
|
||||
) error {
|
||||
now := metav1.Now()
|
||||
|
||||
if osup.Status == nil {
|
||||
osup.Status = &monov1alpha1.OSUpgradeProgressStatus{}
|
||||
}
|
||||
|
||||
osup.Status.LastUpdatedAt = &now
|
||||
osup.Status.Message = fmt.Sprintf("%s: %v", action, cause)
|
||||
osup.Status.Phase = monov1alpha1.OSUpgradeProgressPhaseFailed
|
||||
|
||||
if _, err := updateProgressStatus(ctx, clients, osup_gvr, osup); err != nil {
|
||||
klog.ErrorS(err, "failed to update osupgradeprogress status after error",
|
||||
"action", action,
|
||||
"name", osup.Name,
|
||||
"namespace", osup.Namespace,
|
||||
)
|
||||
}
|
||||
|
||||
return fmt.Errorf("%s: %w", action, cause)
|
||||
}
|
||||
|
||||
func markProgressCompleted(
|
||||
ctx context.Context,
|
||||
clients *kube.Clients,
|
||||
osup *monov1alpha1.OSUpgradeProgress,
|
||||
message string,
|
||||
) error {
|
||||
now := metav1.Now()
|
||||
|
||||
if osup.Status == nil {
|
||||
osup.Status = &monov1alpha1.OSUpgradeProgressStatus{}
|
||||
}
|
||||
|
||||
osup.Status.Phase = monov1alpha1.OSUpgradeProgressPhaseCompleted
|
||||
osup.Status.Message = message
|
||||
osup.Status.LastUpdatedAt = &now
|
||||
osup.Status.CompletedAt = &now
|
||||
|
||||
_, err := updateProgressStatus(ctx, clients, osup_gvr, osup)
|
||||
if err != nil {
|
||||
return fmt.Errorf("mark progress completed: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func toUnstructured(progress *monov1alpha1.OSUpgradeProgress) (*unstructured.Unstructured, error) {
|
||||
m, err := unstructuredConverter.ToUnstructured(progress)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("convert OSUpgradeProgress to unstructured: %w", err)
|
||||
}
|
||||
return &unstructured.Unstructured{Object: m}, nil
|
||||
}
|
||||
|
||||
func fromUnstructuredProgress(obj *unstructured.Unstructured) (*monov1alpha1.OSUpgradeProgress, error) {
|
||||
var progress monov1alpha1.OSUpgradeProgress
|
||||
if err := unstructuredConverter.FromUnstructured(obj.Object, &progress); err != nil {
|
||||
return nil, fmt.Errorf("convert unstructured to OSUpgradeProgress: %w", err)
|
||||
}
|
||||
return &progress, nil
|
||||
}
|
||||
Reference in New Issue
Block a user