@@ -5,9 +5,10 @@ import (
5
5
"fmt"
6
6
"io"
7
7
8
+ kerrs "k8s.io/apimachinery/pkg/api/errors"
8
9
"k8s.io/apimachinery/pkg/apis/meta/v1"
9
- "k8s.io/apimachinery/pkg/runtime "
10
- utilerrors "k8s.io/apimachinery /pkg/util/errors "
10
+ "k8s.io/client-go/util/flowcontrol "
11
+ "k8s.io/kubernetes /pkg/apis/rbac/v1beta1 "
11
12
rbacinternalversion "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion"
12
13
kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
13
14
"k8s.io/kubernetes/pkg/kubectl/resource"
39
40
40
41
No resources are mutated.` )
41
42
42
- errOutOfSync = errors .New ("is not in sync with RBAC" )
43
+ // errOutOfSync is retriable since it could be caused by the controller lagging behind
44
+ errOutOfSync = migrate.ErrRetriable {errors .New ("is not in sync with RBAC" )}
43
45
)
44
46
45
47
type MigrateAuthorizationOptions struct {
@@ -54,6 +56,7 @@ func NewCmdMigrateAuthorization(name, fullName string, f *clientcmd.Factory, in
54
56
Out : out ,
55
57
ErrOut : errout ,
56
58
AllNamespaces : true ,
59
+ Confirm : true , // force our save function to always run (it is read only)
57
60
Include : []string {
58
61
"clusterroles.authorization.openshift.io" ,
59
62
"roles.authorization.openshift.io" ,
@@ -80,20 +83,40 @@ func (o *MigrateAuthorizationOptions) Complete(name string, f *clientcmd.Factory
80
83
return fmt .Errorf ("%s takes no positional arguments" , name )
81
84
}
82
85
86
+ o .ResourceOptions .SaveFn = o .checkParity
83
87
if err := o .ResourceOptions .Complete (f , c ); err != nil {
84
88
return err
85
89
}
86
90
87
- _ , kclient , err := f .Clients ()
91
+ discovery , err := f .DiscoveryClient ()
88
92
if err != nil {
89
93
return err
90
94
}
91
95
92
- if err := clientcmd .LegacyPolicyResourceGate (kclient . Discovery () ); err != nil {
96
+ if err := clientcmd .LegacyPolicyResourceGate (discovery ); err != nil {
93
97
return err
94
98
}
95
99
96
- o .rbac = kclient .Rbac ()
100
+ config , err := f .ClientConfig ()
101
+ if err != nil {
102
+ return err
103
+ }
104
+
105
+ // do not rate limit this client because it has to scan all RBAC data across the cluster
106
+ // this is safe because only a cluster admin will have the ability to read these objects
107
+ configShallowCopy := * config
108
+ configShallowCopy .RateLimiter = flowcontrol .NewFakeAlwaysRateLimiter ()
109
+
110
+ // This command is only compatible with a 3.6 server, which only supported RBAC v1beta1
111
+ // Thus we must force that GV otherwise the client will default to v1
112
+ gv := v1beta1 .SchemeGroupVersion
113
+ configShallowCopy .GroupVersion = & gv
114
+
115
+ rbac , err := rbacinternalversion .NewForConfig (& configShallowCopy )
116
+ if err != nil {
117
+ return err
118
+ }
119
+ o .rbac = rbac
97
120
98
121
return nil
99
122
}
@@ -103,130 +126,159 @@ func (o MigrateAuthorizationOptions) Validate() error {
103
126
}
104
127
105
128
func (o MigrateAuthorizationOptions ) Run () error {
106
- return o .ResourceOptions .Visitor ().Visit (func (info * resource.Info ) (migrate.Reporter , error ) {
107
- return o .checkParity (info .Object )
108
- })
129
+ // we lie and say this object has changed so our save function will run
130
+ return o .ResourceOptions .Visitor ().Visit (migrate .AlwaysRequiresMigration )
109
131
}
110
132
111
133
// checkParity confirms that Openshift authorization objects are in sync with Kubernetes RBAC
112
134
// and returns an error if they are out of sync or if it encounters a conversion error
113
- func (o * MigrateAuthorizationOptions ) checkParity (obj runtime.Object ) (migrate.Reporter , error ) {
114
- var errlist []error
115
- switch t := obj .(type ) {
135
+ func (o * MigrateAuthorizationOptions ) checkParity (info * resource.Info , _ migrate.Reporter ) error {
136
+ var err migrate.TemporaryError
137
+
138
+ switch t := info .Object .(type ) {
116
139
case * authorizationapi.ClusterRole :
117
- errlist = append ( errlist , o .checkClusterRole (t ) ... )
140
+ err = o .checkClusterRole (t )
118
141
case * authorizationapi.Role :
119
- errlist = append ( errlist , o .checkRole (t ) ... )
142
+ err = o .checkRole (t )
120
143
case * authorizationapi.ClusterRoleBinding :
121
- errlist = append ( errlist , o .checkClusterRoleBinding (t ) ... )
144
+ err = o .checkClusterRoleBinding (t )
122
145
case * authorizationapi.RoleBinding :
123
- errlist = append ( errlist , o .checkRoleBinding (t ) ... )
146
+ err = o .checkRoleBinding (t )
124
147
default :
125
- return nil , nil // indicate that we ignored the object
148
+ // this should never happen unless o.Include or the server is broken
149
+ return fmt .Errorf ("impossible type %T for checkParity info=%#v object=%#v" , t , info , t )
150
+ }
151
+
152
+ // We encountered no error, so this object is in sync.
153
+ if err == nil {
154
+ // we only perform read operations so we return this error to signal that we did not change anything
155
+ return migrate .ErrUnchanged
156
+ }
157
+
158
+ // At this point we know that we have some non-nil TemporaryError.
159
+ // If it has the possibility of being transient, we need to sync ourselves with the current state of the object.
160
+ if err .Temporary () {
161
+ // The most likely cause is that an authorization object was deleted after we initially fetched it,
162
+ // and the controller deleted the associated RBAC object, which caused our RBAC GET to fail.
163
+ // We can confirm this by refetching the authorization object.
164
+ refreshErr := info .Get ()
165
+ if refreshErr != nil {
166
+ // Our refresh GET for this authorization object failed.
167
+ // The default logic for migration errors is appropriate in this case (refreshErr is most likely a NotFound).
168
+ return migrate .DefaultRetriable (info , refreshErr )
169
+ }
170
+ // We had no refreshErr, but encountered some other possibly transient error.
171
+ // No special handling is required in this case, we just pass it through below.
126
172
}
127
- return migrate .NotChanged , utilerrors .NewAggregate (errlist ) // we only perform read operations
173
+
174
+ // All of the check* funcs return an appropriate TemporaryError based on the failure,
175
+ // so we can pass that through to the default migration logic which will retry as needed.
176
+ return err
128
177
}
129
178
130
- func (o * MigrateAuthorizationOptions ) checkClusterRole (originClusterRole * authorizationapi.ClusterRole ) []error {
131
- var errlist []error
179
+ // handleRBACGetError signals for a retry on NotFound (handles deletion and sync lag)
180
+ // and ServerTimeout (handles heavy load against the server).
181
+ func handleRBACGetError (err error ) migrate.TemporaryError {
182
+ switch {
183
+ case kerrs .IsNotFound (err ), kerrs .IsServerTimeout (err ):
184
+ return migrate.ErrRetriable {err }
185
+ default :
186
+ return migrate.ErrNotRetriable {err }
187
+ }
188
+ }
132
189
190
+ func (o * MigrateAuthorizationOptions ) checkClusterRole (originClusterRole * authorizationapi.ClusterRole ) migrate.TemporaryError {
133
191
// convert the origin role to a rbac role
134
192
convertedClusterRole , err := authorizationsync .ConvertToRBACClusterRole (originClusterRole )
135
193
if err != nil {
136
- errlist = append (errlist , err )
194
+ // conversion errors should basically never happen, so we do not attempt to retry on those
195
+ return migrate.ErrNotRetriable {err }
137
196
}
138
197
139
198
// try to get the equivalent rbac role from the api
140
199
rbacClusterRole , err := o .rbac .ClusterRoles ().Get (originClusterRole .Name , v1.GetOptions {})
141
200
if err != nil {
142
- errlist = append (errlist , err )
201
+ // it is possible that the controller has not synced this yet
202
+ return handleRBACGetError (err )
143
203
}
144
204
145
- // compare the results if there have been no errors so far
146
- if len (errlist ) == 0 {
147
- // if they are not equal, something has gone wrong and the two objects are not in sync
148
- if authorizationsync .PrepareForUpdateClusterRole (convertedClusterRole , rbacClusterRole ) {
149
- errlist = append (errlist , errOutOfSync )
150
- }
205
+ // if they are not equal, something has gone wrong and the two objects are not in sync
206
+ if authorizationsync .PrepareForUpdateClusterRole (convertedClusterRole , rbacClusterRole ) {
207
+ // we retry on this since it could be caused by the controller lagging behind
208
+ return errOutOfSync
151
209
}
152
210
153
- return errlist
211
+ return nil
154
212
}
155
213
156
- func (o * MigrateAuthorizationOptions ) checkRole (originRole * authorizationapi.Role ) []error {
157
- var errlist []error
158
-
214
+ func (o * MigrateAuthorizationOptions ) checkRole (originRole * authorizationapi.Role ) migrate.TemporaryError {
159
215
// convert the origin role to a rbac role
160
216
convertedRole , err := authorizationsync .ConvertToRBACRole (originRole )
161
217
if err != nil {
162
- errlist = append (errlist , err )
218
+ // conversion errors should basically never happen, so we do not attempt to retry on those
219
+ return migrate.ErrNotRetriable {err }
163
220
}
164
221
165
222
// try to get the equivalent rbac role from the api
166
223
rbacRole , err := o .rbac .Roles (originRole .Namespace ).Get (originRole .Name , v1.GetOptions {})
167
224
if err != nil {
168
- errlist = append (errlist , err )
225
+ // it is possible that the controller has not synced this yet
226
+ return handleRBACGetError (err )
169
227
}
170
228
171
- // compare the results if there have been no errors so far
172
- if len (errlist ) == 0 {
173
- // if they are not equal, something has gone wrong and the two objects are not in sync
174
- if authorizationsync .PrepareForUpdateRole (convertedRole , rbacRole ) {
175
- errlist = append (errlist , errOutOfSync )
176
- }
229
+ // if they are not equal, something has gone wrong and the two objects are not in sync
230
+ if authorizationsync .PrepareForUpdateRole (convertedRole , rbacRole ) {
231
+ // we retry on this since it could be caused by the controller lagging behind
232
+ return errOutOfSync
177
233
}
178
234
179
- return errlist
235
+ return nil
180
236
}
181
237
182
- func (o * MigrateAuthorizationOptions ) checkClusterRoleBinding (originRoleBinding * authorizationapi.ClusterRoleBinding ) []error {
183
- var errlist []error
184
-
238
+ func (o * MigrateAuthorizationOptions ) checkClusterRoleBinding (originRoleBinding * authorizationapi.ClusterRoleBinding ) migrate.TemporaryError {
185
239
// convert the origin role binding to a rbac role binding
186
240
convertedRoleBinding , err := authorizationsync .ConvertToRBACClusterRoleBinding (originRoleBinding )
187
241
if err != nil {
188
- errlist = append (errlist , err )
242
+ // conversion errors should basically never happen, so we do not attempt to retry on those
243
+ return migrate.ErrNotRetriable {err }
189
244
}
190
245
191
246
// try to get the equivalent rbac role binding from the api
192
247
rbacRoleBinding , err := o .rbac .ClusterRoleBindings ().Get (originRoleBinding .Name , v1.GetOptions {})
193
248
if err != nil {
194
- errlist = append (errlist , err )
249
+ // it is possible that the controller has not synced this yet
250
+ return handleRBACGetError (err )
195
251
}
196
252
197
- // compare the results if there have been no errors so far
198
- if len (errlist ) == 0 {
199
- // if they are not equal, something has gone wrong and the two objects are not in sync
200
- if authorizationsync .PrepareForUpdateClusterRoleBinding (convertedRoleBinding , rbacRoleBinding ) {
201
- errlist = append (errlist , errOutOfSync )
202
- }
253
+ // if they are not equal, something has gone wrong and the two objects are not in sync
254
+ if authorizationsync .PrepareForUpdateClusterRoleBinding (convertedRoleBinding , rbacRoleBinding ) {
255
+ // we retry on this since it could be caused by the controller lagging behind
256
+ return errOutOfSync
203
257
}
204
258
205
- return errlist
259
+ return nil
206
260
}
207
261
208
- func (o * MigrateAuthorizationOptions ) checkRoleBinding (originRoleBinding * authorizationapi.RoleBinding ) []error {
209
- var errlist []error
210
-
262
+ func (o * MigrateAuthorizationOptions ) checkRoleBinding (originRoleBinding * authorizationapi.RoleBinding ) migrate.TemporaryError {
211
263
// convert the origin role binding to a rbac role binding
212
264
convertedRoleBinding , err := authorizationsync .ConvertToRBACRoleBinding (originRoleBinding )
213
265
if err != nil {
214
- errlist = append (errlist , err )
266
+ // conversion errors should basically never happen, so we do not attempt to retry on those
267
+ return migrate.ErrNotRetriable {err }
215
268
}
216
269
217
270
// try to get the equivalent rbac role binding from the api
218
271
rbacRoleBinding , err := o .rbac .RoleBindings (originRoleBinding .Namespace ).Get (originRoleBinding .Name , v1.GetOptions {})
219
272
if err != nil {
220
- errlist = append (errlist , err )
273
+ // it is possible that the controller has not synced this yet
274
+ return handleRBACGetError (err )
221
275
}
222
276
223
- // compare the results if there have been no errors so far
224
- if len (errlist ) == 0 {
225
- // if they are not equal, something has gone wrong and the two objects are not in sync
226
- if authorizationsync .PrepareForUpdateRoleBinding (convertedRoleBinding , rbacRoleBinding ) {
227
- errlist = append (errlist , errOutOfSync )
228
- }
277
+ // if they are not equal, something has gone wrong and the two objects are not in sync
278
+ if authorizationsync .PrepareForUpdateRoleBinding (convertedRoleBinding , rbacRoleBinding ) {
279
+ // we retry on this since it could be caused by the controller lagging behind
280
+ return errOutOfSync
229
281
}
230
282
231
- return errlist
283
+ return nil
232
284
}
0 commit comments