Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ require (
github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.0
github.com/openshift-eng/openshift-tests-extension v0.0.0-20250804142706-7b3ab438a292
github.com/openshift/api v0.0.0-20251111013132-5c461e21bdb7
github.com/openshift/api v0.0.0-20260109135506-3920bba77f16
github.com/openshift/build-machinery-go v0.0.0-20251020112516-49aa9f5db6d8
github.com/openshift/client-go v0.0.0-20251015124057-db0dee36e235
github.com/openshift/client-go v0.0.0-20260108185524-48f4ccfc4e13
github.com/openshift/library-go v0.0.0-20251222131241-289839b3ffe8
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
github.com/prometheus/client_golang v1.23.2
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -385,12 +385,12 @@ github.com/opencontainers/selinux v1.13.0 h1:Zza88GWezyT7RLql12URvoxsbLfjFx988+L
github.com/opencontainers/selinux v1.13.0/go.mod h1:XxWTed+A/s5NNq4GmYScVy+9jzXhGBVEOAyucdRUY8s=
github.com/openshift-eng/openshift-tests-extension v0.0.0-20250804142706-7b3ab438a292 h1:3athg6KQ+TaNfW4BWZDlGFt1ImSZEJWgzXtPC1VPITI=
github.com/openshift-eng/openshift-tests-extension v0.0.0-20250804142706-7b3ab438a292/go.mod h1:6gkP5f2HL0meusT0Aim8icAspcD1cG055xxBZ9yC68M=
github.com/openshift/api v0.0.0-20251111013132-5c461e21bdb7 h1:fdvcDJySvjVJctbPbdLPoMiMk+bls34+eq6tWOqdFZg=
github.com/openshift/api v0.0.0-20251111013132-5c461e21bdb7/go.mod h1:d5uzF0YN2nQQFA0jIEWzzOZ+edmo6wzlGLvx5Fhz4uY=
github.com/openshift/api v0.0.0-20260109135506-3920bba77f16 h1:EfTfmlNBtG/xauH9gcnq64J08nYTBKyilbl/EUbxGno=
github.com/openshift/api v0.0.0-20260109135506-3920bba77f16/go.mod h1:d5uzF0YN2nQQFA0jIEWzzOZ+edmo6wzlGLvx5Fhz4uY=
github.com/openshift/build-machinery-go v0.0.0-20251020112516-49aa9f5db6d8 h1:2sktNP3CNpDb5F9rIg1qcBYU4lFxsOfBsUSP32LwAPo=
github.com/openshift/build-machinery-go v0.0.0-20251020112516-49aa9f5db6d8/go.mod h1:8jcm8UPtg2mCAsxfqKil1xrmRMI3a+XU2TZ9fF8A7TE=
github.com/openshift/client-go v0.0.0-20251015124057-db0dee36e235 h1:9JBeIXmnHlpXTQPi7LPmu1jdxznBhAE7bb1K+3D8gxY=
github.com/openshift/client-go v0.0.0-20251015124057-db0dee36e235/go.mod h1:L49W6pfrZkfOE5iC1PqEkuLkXG4W0BX4w8b+L2Bv7fM=
github.com/openshift/client-go v0.0.0-20260108185524-48f4ccfc4e13 h1:6rd4zSo2UaWQcAPZfHK9yzKVqH0BnMv1hqMzqXZyTds=
github.com/openshift/client-go v0.0.0-20260108185524-48f4ccfc4e13/go.mod h1:YvOmPmV7wcJxpfhTDuFqqs2Xpb3M3ovsM6Qs/i2ptq4=
github.com/openshift/gssapi v0.0.0-20161010215902-5fb4217df13b h1:it0YPE/evO6/m8t8wxis9KFI2F/aleOKsI6d9uz0cEk=
github.com/openshift/gssapi v0.0.0-20161010215902-5fb4217df13b/go.mod h1:tNrEB5k8SI+g5kOlsCmL2ELASfpqEofI0+FLBgBdN08=
github.com/openshift/library-go v0.0.0-20251222131241-289839b3ffe8 h1:TWqbSjaYbZGgB6EmnEN6Hc8lQYYCgju2qORBX7Ix1LI=
Expand Down
217 changes: 217 additions & 0 deletions pkg/cli/admin/upgrade/accept/accept.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
package accept

import (
"context"
"fmt"
"github.com/google/go-cmp/cmp"
"sort"
"strings"

configv1 "github.com/openshift/api/config/v1"
configv1client "github.com/openshift/client-go/config/clientset/versioned"
"github.com/spf13/cobra"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/cli-runtime/pkg/genericiooptions"
kcmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/util/templates"
)

func newOptions(streams genericiooptions.IOStreams) *options {
return &options{
IOStreams: streams,
}
}

var (
acceptExample = templates.Examples(`
# Accept RiskA and RiskB and stop accepting RiskC if accepted
oc adm upgrade accept RiskA,RiskB,-RiskC

# Accept RiskA and RiskB and nothing else
oc adm upgrade accept --replace RiskA,RiskB

# Accept no risks
oc adm upgrade accept --clear
`)

acceptLong = templates.LongDesc(`
Accept risks exposed to conditional updates.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: "Manage update risk acceptance." or something that gives enough room for both acceptance and -RiskC / --clear rejection.


Multiple risks are concatenated with comma. Append the provided accepted risks into the existing
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe "Append ..." -> "By default, the command appends..." or something that makes the actor and context for that result more clear?

list. If --replace is specified, the existing accepted risks will be replaced with the provided
ones instead of appending by default. Placing "-" as prefix to an accepted risk will lead to
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I'd drop "by default" here. That's context I'd rather cover in the previous sentance, where appending is the main focus, and if we cover it there, folks should still remember that appending was the default by the time they get to the end of this --replace sentence.

removal if it exists and no-ops otherwise. If --replace is specified, the prefix "-" on the risks
is not allowed.

The existing accepted risks can be removed by passing --clear.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Maybe "Passing --clear removes all existing excepted risks." to be extra precise on the completeness of the removal?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hah, and the wording I floated matches what you have in the --clear help text a few lines down, so that's another vote in favor ;)

`)
)

func New(f kcmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
o := newOptions(streams)
cmd := &cobra.Command{
Use: "accept",
Hidden: true,
Short: "Accept risks exposed to conditional updates.",
Long: acceptLong,
Example: acceptExample,
Run: func(cmd *cobra.Command, args []string) {
kcmdutil.CheckErr(o.Complete(f, cmd, args))
kcmdutil.CheckErr(o.Run(cmd.Context()))
},
}

flags := cmd.Flags()
flags.BoolVar(&o.replace, "replace", false, "Replace existing accepted risks with new ones")
flags.BoolVar(&o.clear, "clear", false, "Remove all existing accepted risks")
return cmd
}

// clusterVersionInterface is the subset of configv1client.ClusterVersionInterface
// that we need, for easier mocking in unit tests.
type clusterVersionInterface interface {
Get(ctx context.Context, name string, opts metav1.GetOptions) (*configv1.ClusterVersion, error)
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *configv1.ClusterVersion, err error)
}

type options struct {
genericiooptions.IOStreams

Client configv1client.Interface
replace bool
clear bool
plus sets.Set[string]
minus sets.Set[string]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: minus matches your command line syntax, but add and remove might be more recognizable property names by the time we get down into sets here.

}

func (o *options) Complete(f kcmdutil.Factory, cmd *cobra.Command, args []string) error {
if o.clear && o.replace {
return kcmdutil.UsageErrorf(cmd, "--clear and --replace are mutually exclusive")
}

if o.clear {
kcmdutil.RequireNoArguments(cmd, args)
} else if len(args) == 0 {
return kcmdutil.UsageErrorf(cmd, "no positional arguments given")
}

if len(args) > 1 {
return kcmdutil.UsageErrorf(cmd, "multiple positional arguments given")
} else if len(args) == 1 {
o.plus = sets.New[string]()
o.minus = sets.New[string]()
for _, s := range strings.Split(args[0], ",") {
trimmed := strings.TrimSpace(s)
if trimmed == "-" || trimmed == "" {
return kcmdutil.UsageErrorf(cmd, "illegal risk %q", trimmed)
}
if strings.HasPrefix(trimmed, "-") {
o.minus.Insert(trimmed[1:])
} else {
o.plus.Insert(trimmed)
}
}
}

if conflict := o.plus.Intersection(o.minus); conflict.Len() > 0 {
return kcmdutil.UsageErrorf(cmd, "found conflicting risks: %s", strings.Join(sets.List(conflict), ","))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: maybe spell out the conflict more explicitly, e.g. requested risk acceptance and rejection or requested risks with both Risk and -Risk or some such.

}

if o.replace && o.minus.Len() > 0 {
return kcmdutil.UsageErrorf(cmd, "The prefix '-' on risks is not allowed if --replace is specified")
}

cfg, err := f.ToRESTConfig()
if err != nil {
return err
}
client, err := configv1client.NewForConfig(cfg)
if err != nil {
return err
}
o.Client = client
return nil
}

func (o *options) Run(ctx context.Context) error {
cv, err := o.Client.ConfigV1().ClusterVersions().Get(ctx, "version", metav1.GetOptions{})
if err != nil {
if apierrors.IsNotFound(err) {
return fmt.Errorf("no cluster version information available - you must be connected to an OpenShift version 4 server to fetch the current version")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see you're pattern-matching vs. some sibling subcommands. Mind adding a new commit to this pull to drop the version 4 as we prep for a possible 5 (e.g. openshift/cluster-version-operator#1275)? Or would you rather I pulled that out to a separate pull request?

}
return err
}

existing := map[string]configv1.AcceptRisk{}
if cv.Spec.DesiredUpdate != nil {
for _, risk := range cv.Spec.DesiredUpdate.AcceptRisks {
existing[risk.Name] = risk
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

appending to a map is going to lose the in-cluster order. I think we should just pass an existing slice into getAcceptRisks, and then build the "already existing?" lookup map as an internal helper inside getAcceptedRisks where we manipulate the slice into the shape we want to see.

}
}
acceptRisks := getAcceptRisks(existing, o.replace, o.clear, o.plus, o.minus)

var update *configv1.Update
if cv.Spec.DesiredUpdate != nil {
update = cv.Spec.DesiredUpdate.DeepCopy()
update.AcceptRisks = acceptRisks
} else if len(acceptRisks) > 0 {
update = &configv1.Update{
Architecture: cv.Status.Desired.Architecture,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I don't think we want to set this. The status property is ImageStreamImportMode feature-gated, and there's a chance that this accept-risks feature-gate goes GA first. It wouldn't be terrible to have this line here in that situation, but leaving the spec property unset just means "coast along at the current architecture", so I don't see a value in lifting it up either.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image: cv.Status.Desired.Image,
Version: cv.Status.Desired.Version,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image and version are both optional, maybe we can avoid setting them too? If we can avoid setting them, this command can just patch the acceptedRisks property, to avoid bumping into any unrecognized properties or other folks making parallel edits.

AcceptRisks: acceptRisks,
}
}
if diff := cmp.Diff(update, cv.Spec.DesiredUpdate); diff != "" {
cv.Spec.DesiredUpdate = update
cv, err = o.Client.ConfigV1().ClusterVersions().Update(ctx, cv, metav1.UpdateOptions{})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we please use Patch instead of Update? 🥺 That one's bitten us before #1111 😅 And it's nice to clearly tell the API-server exactly which subset of spec we have opinions on, regardless of whether there are any other parallel changes in flight to other aspects of spec.

if err != nil {
return fmt.Errorf("unable to upgrade: %w", err)
}
var names []string
if cv.Spec.DesiredUpdate != nil {
for _, risk := range cv.Spec.DesiredUpdate.AcceptRisks {
names = append(names, risk.Name)
}
}
_, _ = fmt.Fprintf(o.Out, "info: Accept risks are [%s]\n", strings.Join(names, ", "))
} else {
_, _ = fmt.Fprintf(o.Out, "info: Accept risks are not changed\n")
}

return nil
}

func getAcceptRisks(existing map[string]configv1.AcceptRisk, replace, clear bool, plus sets.Set[string], minus sets.Set[string]) []configv1.AcceptRisk {
var acceptRisks []configv1.AcceptRisk

if clear {
return acceptRisks
}

for name := range plus {
if r, ok := existing[name]; ok {
acceptRisks = append(acceptRisks, r)
} else {
acceptRisks = append(acceptRisks, configv1.AcceptRisk{
Name: name,
})
}
}

if !replace {
for name, r := range existing {
if !plus.Has(name) && !minus.Has(name) {
acceptRisks = append(acceptRisks, r)
}
}
}

sort.Slice(acceptRisks, func(i, j int) bool {
return acceptRisks[i].Name < acceptRisks[j].Name
})
return acceptRisks
}
69 changes: 69 additions & 0 deletions pkg/cli/admin/upgrade/accept/accept_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package accept

import (
"testing"

"github.com/google/go-cmp/cmp"
configv1 "github.com/openshift/api/config/v1"
"k8s.io/apimachinery/pkg/util/sets"
)

func Test_getAcceptRisks(t *testing.T) {
for _, testCase := range []struct {
name string
existing map[string]configv1.AcceptRisk
replace bool
clear bool
plus sets.Set[string]
minus sets.Set[string]
expected []configv1.AcceptRisk
}{
{
name: "all zeros",
},
{
name: "riskA, riskB + riskB + riskC - riskA - riskD",
existing: map[string]configv1.AcceptRisk{
"riskA": {Name: "riskA"},
"riskB": {Name: "riskB"},
},
plus: sets.New[string]("riskB", "riskC"),
minus: sets.New[string]("riskA", "riskD"),
expected: []configv1.AcceptRisk{
{Name: "riskB"},
{Name: "riskC"},
},
},
{
name: "replace",
existing: map[string]configv1.AcceptRisk{
"riskA": {Name: "riskA"},
"riskB": {Name: "riskB"},
},
plus: sets.New[string]("riskB", "riskC"),
minus: sets.New[string]("does not matter"),
replace: true,
expected: []configv1.AcceptRisk{
{Name: "riskB"},
{Name: "riskC"},
},
},
{
name: "clear",
existing: map[string]configv1.AcceptRisk{
"riskA": {Name: "riskA"},
"riskB": {Name: "riskB"},
},
plus: sets.New[string]("not important"),
minus: sets.New[string]("does not matter"),
clear: true,
},
} {
t.Run(testCase.name, func(t *testing.T) {
actual := getAcceptRisks(testCase.existing, testCase.replace, testCase.clear, testCase.plus, testCase.minus)
if diff := cmp.Diff(actual, testCase.expected); diff != "" {
t.Errorf("getAcceptRisks() mismatch (-want +got):\n%s", diff)
}
})
}
}
4 changes: 4 additions & 0 deletions pkg/cli/admin/upgrade/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
configv1client "github.com/openshift/client-go/config/clientset/versioned"
imagereference "github.com/openshift/library-go/pkg/image/reference"

"github.com/openshift/oc/pkg/cli/admin/upgrade/accept"
"github.com/openshift/oc/pkg/cli/admin/upgrade/channel"
"github.com/openshift/oc/pkg/cli/admin/upgrade/recommend"
"github.com/openshift/oc/pkg/cli/admin/upgrade/rollback"
Expand Down Expand Up @@ -126,6 +127,9 @@ func New(f kcmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command
if kcmdutil.FeatureGate("OC_ENABLE_CMD_UPGRADE_ROLLBACK").IsEnabled() {
cmd.AddCommand(rollback.New(f, streams))
}
if kcmdutil.FeatureGate("OC_ENABLE_CMD_UPGRADE_ACCEPT_RISKS").IsEnabled() {
cmd.AddCommand(accept.New(f, streams))
}
cmd.AddCommand(recommend.New(f, streams))

return cmd
Expand Down
1 change: 1 addition & 0 deletions pkg/helpers/describe/describer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ var MissingDescriberGroupCoverageExceptions = []schema.GroupVersion{
{Group: "machine.openshift.io", Version: "v1beta1"},
{Group: "machine.openshift.io", Version: "v1"},
{Group: "sharedresource.openshift.io", Version: "v1alpha1"},
{Group: "apiextensions.openshift.io", Version: "v1alpha1"},
}

func TestDescriberCoverage(t *testing.T) {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading