Skip to content

Commit b4dc31c

Browse files
committed
OTA-1548: set up accepted risks
With OC_ENABLE_CMD_UPGRADE_ACCEPT_RISKS=true, a new command `oc adm upgrade accept` is enabled. It accepts comma-separated risks exposed to an OpenShift release [1]. The risks are stored in `clusterversion/version`'s `.specs.desiredUpdate.acceptRisks`. [1]. https://docs.redhat.com/en/documentation/openshift_container_platform/4.18/html-single/updating_clusters/index#understanding-clusterversion-conditiontypes_understanding-openshift-updates
1 parent c5c6334 commit b4dc31c

2 files changed

Lines changed: 204 additions & 0 deletions

File tree

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
package accept
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"strings"
8+
9+
configv1 "github.com/openshift/api/config/v1"
10+
configv1client "github.com/openshift/client-go/config/clientset/versioned/typed/config/v1"
11+
"github.com/spf13/cobra"
12+
apierrors "k8s.io/apimachinery/pkg/api/errors"
13+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
14+
"k8s.io/apimachinery/pkg/types"
15+
"k8s.io/apimachinery/pkg/util/sets"
16+
"k8s.io/cli-runtime/pkg/genericiooptions"
17+
kcmdutil "k8s.io/kubectl/pkg/cmd/util"
18+
"k8s.io/kubectl/pkg/util/templates"
19+
20+
"github.com/openshift/client-go/config/clientset/versioned/fake"
21+
)
22+
23+
func newOptions(streams genericiooptions.IOStreams) *options {
24+
return &options{
25+
IOStreams: streams,
26+
}
27+
}
28+
29+
var (
30+
acceptExample = templates.Examples(`
31+
# Accept RiskA and RiskB and stop accepting RiskC if accepted
32+
oc adm upgrade accept RiskA,RiskB,-RiskC
33+
34+
# Accept RiskA and RiskB and nothing else
35+
oc adm upgrade accept --replace RiskA,RiskB
36+
37+
# Accept no risks
38+
oc adm upgrade accept --clear
39+
`)
40+
41+
acceptLong = templates.LongDesc(`
42+
Accept risks exposed to conditional updates.
43+
44+
Multiple risks are concatenated with comma. Append the provided accepted risks into the existing
45+
list. If --replace is specified, the existing accepted risks will be replaced with the provided
46+
ones instead of appending by default. Placing "-" as prefix to an accepted risk will lead to
47+
removal if it exists and no-ops otherwise. If --replace is specified, the prefix "-" on the risks
48+
is not allowed.
49+
50+
The existing accepted risks can be removed by passing --clear.
51+
`)
52+
)
53+
54+
func New(f kcmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
55+
o := newOptions(streams)
56+
cmd := &cobra.Command{
57+
Use: "accept",
58+
Hidden: true,
59+
Short: "Accept risks exposed to conditional updates.",
60+
Long: acceptLong,
61+
Example: acceptExample,
62+
Run: func(cmd *cobra.Command, args []string) {
63+
kcmdutil.CheckErr(o.Complete(f, cmd, args))
64+
kcmdutil.CheckErr(o.Run(cmd.Context()))
65+
},
66+
}
67+
68+
flags := cmd.Flags()
69+
flags.BoolVar(&o.replace, "replace", false, "Replace existing accepted risks with new ones")
70+
flags.BoolVar(&o.clear, "clear", false, "Remove all existing accepted risks")
71+
return cmd
72+
}
73+
74+
// clusterVersionInterface is the subset of configv1client.ClusterVersionInterface
75+
// that we need, for easier mocking in unit tests.
76+
type clusterVersionInterface interface {
77+
Get(ctx context.Context, name string, opts metav1.GetOptions) (*configv1.ClusterVersion, error)
78+
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *configv1.ClusterVersion, err error)
79+
}
80+
81+
type options struct {
82+
genericiooptions.IOStreams
83+
84+
Client clusterVersionInterface
85+
replace bool
86+
clear bool
87+
plus sets.Set[string]
88+
minus sets.Set[string]
89+
}
90+
91+
func (o *options) Complete(f kcmdutil.Factory, cmd *cobra.Command, args []string) error {
92+
if o.clear && o.replace {
93+
return kcmdutil.UsageErrorf(cmd, "--clear and --replace are mutually exclusive")
94+
}
95+
96+
if o.clear {
97+
kcmdutil.RequireNoArguments(cmd, args)
98+
} else if len(args) == 0 {
99+
return kcmdutil.UsageErrorf(cmd, "no positional arguments given")
100+
}
101+
102+
if len(args) > 1 {
103+
return kcmdutil.UsageErrorf(cmd, "multiple positional arguments given")
104+
} else if len(args) == 1 {
105+
o.plus = sets.New[string]()
106+
o.minus = sets.New[string]()
107+
for _, s := range strings.Split(args[0], ",") {
108+
trimmed := strings.TrimSpace(s)
109+
if trimmed == "-" {
110+
return kcmdutil.UsageErrorf(cmd, "illegal risk \"-\"")
111+
}
112+
if strings.HasPrefix(trimmed, "-") {
113+
o.minus.Insert(trimmed[1:])
114+
} else {
115+
o.plus.Insert(trimmed)
116+
}
117+
}
118+
}
119+
120+
if conflict := o.plus.Intersection(o.minus); conflict.Len() > 0 {
121+
return kcmdutil.UsageErrorf(cmd, "found conflicting risks: %s", strings.Join(sets.List(conflict), ","))
122+
}
123+
124+
if o.replace && o.minus.Len() > 0 {
125+
return kcmdutil.UsageErrorf(cmd, "The prefix '-' on risks is not allowed if --replace is specified")
126+
}
127+
128+
cfg, err := f.ToRESTConfig()
129+
if err != nil {
130+
return err
131+
}
132+
client, err := configv1client.NewForConfig(cfg)
133+
if err != nil {
134+
return err
135+
}
136+
o.Client = client.ClusterVersions()
137+
138+
// TODO remove this testing code
139+
o.Client = fake.NewClientset(&configv1.ClusterVersion{
140+
ObjectMeta: metav1.ObjectMeta{
141+
Name: "version",
142+
},
143+
}).ConfigV1().ClusterVersions()
144+
return nil
145+
}
146+
147+
func (o *options) Run(ctx context.Context) error {
148+
_, err := o.Client.Get(ctx, "version", metav1.GetOptions{})
149+
if err != nil {
150+
if apierrors.IsNotFound(err) {
151+
return fmt.Errorf("no cluster version information available - you must be connected to an OpenShift version 4 server to fetch the current version")
152+
}
153+
return err
154+
}
155+
156+
// TODO: get it from the existing CV above
157+
// We need to bump o/api first
158+
risks := sets.New[string]("fakeRiskA", "fakeRiskB")
159+
newRisks := risks.Union(o.plus).Difference(o.minus)
160+
if o.replace {
161+
newRisks = o.plus
162+
}
163+
164+
added := newRisks.Difference(risks)
165+
deleted := risks.Difference(newRisks)
166+
167+
acceptedRisks := sets.List(newRisks)
168+
if err := patchDesiredUpdate(context.TODO(), acceptedRisks, o.Client, "version"); err != nil {
169+
return err
170+
}
171+
172+
if o.replace {
173+
fmt.Fprintf(o.Out, "info: Accept risks are replaced with %s\n", strings.Join(acceptedRisks, ","))
174+
} else {
175+
fmt.Fprintf(o.Out, "info: Accept risks are %s with %s added and %s deleted\n", strings.Join(acceptedRisks, ","), nothingOrJoined(added), nothingOrJoined(deleted))
176+
}
177+
178+
return nil
179+
}
180+
181+
func nothingOrJoined(s sets.Set[string]) string {
182+
if s.Len() == 0 {
183+
return "nothing"
184+
}
185+
return strings.Join(sets.List(s), ",")
186+
}
187+
188+
func patchDesiredUpdate(ctx context.Context, acceptRisks []string, client clusterVersionInterface,
189+
clusterVersionName string) error {
190+
acceptRisksJSON, err := json.Marshal(acceptRisks)
191+
if err != nil {
192+
return fmt.Errorf("marshal ClusterVersion patch: %v", err)
193+
}
194+
patch := []byte(fmt.Sprintf(`{"spec":{"desiredUpdate.acceptRisks": %s}}`, acceptRisksJSON))
195+
if _, err := client.Patch(ctx, clusterVersionName, types.MergePatchType, patch,
196+
metav1.PatchOptions{}); err != nil {
197+
return fmt.Errorf("unable to accept risks: %v", err)
198+
}
199+
return nil
200+
}

pkg/cli/admin/upgrade/upgrade.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
configv1client "github.com/openshift/client-go/config/clientset/versioned"
2626
imagereference "github.com/openshift/library-go/pkg/image/reference"
2727

28+
"github.com/openshift/oc/pkg/cli/admin/upgrade/accept"
2829
"github.com/openshift/oc/pkg/cli/admin/upgrade/channel"
2930
"github.com/openshift/oc/pkg/cli/admin/upgrade/recommend"
3031
"github.com/openshift/oc/pkg/cli/admin/upgrade/rollback"
@@ -122,6 +123,9 @@ func New(f kcmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command
122123
if kcmdutil.FeatureGate("OC_ENABLE_CMD_UPGRADE_ROLLBACK").IsEnabled() {
123124
cmd.AddCommand(rollback.New(f, streams))
124125
}
126+
if kcmdutil.FeatureGate("OC_ENABLE_CMD_UPGRADE_ACCEPT_RISKS").IsEnabled() {
127+
cmd.AddCommand(accept.New(f, streams))
128+
}
125129
cmd.AddCommand(recommend.New(f, streams))
126130

127131
return cmd

0 commit comments

Comments
 (0)