@@ -14,6 +14,7 @@ import (
14
14
v1extensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
15
15
"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
16
16
apierr "k8s.io/apimachinery/pkg/api/errors"
17
+ "k8s.io/apimachinery/pkg/api/validation"
17
18
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
18
19
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
19
20
"k8s.io/apimachinery/pkg/runtime/schema"
@@ -23,6 +24,7 @@ import (
23
24
"k8s.io/client-go/rest"
24
25
"k8s.io/client-go/util/retry"
25
26
"k8s.io/klog/v2/textlogger"
27
+ "k8s.io/kubectl/pkg/cmd/util"
26
28
cmdutil "k8s.io/kubectl/pkg/cmd/util"
27
29
"k8s.io/kubectl/pkg/util/openapi"
28
30
@@ -123,7 +125,15 @@ func WithPruneConfirmed(confirmed bool) SyncOpt {
123
125
}
124
126
}
125
127
128
+ // WithDryRun sets dry run setting
129
+ func WithDryRun (dryRun bool ) SyncOpt {
130
+ return func (ctx * syncContext ) {
131
+ ctx .dryRun = dryRun
132
+ }
133
+ }
134
+
126
135
// WithOperationSettings allows to set sync operation settings
136
+ // Deprecated, use individual setters
127
137
func WithOperationSettings (dryRun bool , prune bool , force bool , skipHooks bool ) SyncOpt {
128
138
return func (ctx * syncContext ) {
129
139
ctx .prune = prune
@@ -183,12 +193,30 @@ func WithSyncWaveHook(syncWaveHook common.SyncWaveHook) SyncOpt {
183
193
}
184
194
}
185
195
196
+ // WithReplace sets a replace to a given value
197
+ // Deprecated, prefer using WithReplaceOption
186
198
func WithReplace (replace bool ) SyncOpt {
187
199
return func (ctx * syncContext ) {
188
200
ctx .replace = replace
189
201
}
190
202
}
191
203
204
+ // WithReplaceOptions sets replace options
205
+ func WithReplaceOptions (replaceOption string , replaceRequested bool ) SyncOpt {
206
+ return func (ctx * syncContext ) {
207
+ ctx .replaceOption = replaceOption
208
+ ctx .replaceRequested = replaceRequested
209
+ }
210
+ }
211
+
212
+ // WithReplaceOption sets force options
213
+ func WithForceOptions (forceOption string , forceRequested bool ) SyncOpt {
214
+ return func (ctx * syncContext ) {
215
+ ctx .forceOption = forceOption
216
+ ctx .forceRequested = forceRequested
217
+ }
218
+ }
219
+
192
220
func WithServerSideApply (serverSideApply bool ) SyncOpt {
193
221
return func (ctx * syncContext ) {
194
222
ctx .serverSideApply = serverSideApply
@@ -337,11 +365,15 @@ type syncContext struct {
337
365
338
366
dryRun bool
339
367
force bool
368
+ forceOption string
369
+ forceRequested bool
340
370
validate bool
341
371
skipHooks bool
342
372
resourcesFilter func (key kube.ResourceKey , target * unstructured.Unstructured , live * unstructured.Unstructured ) bool
343
373
prune bool
344
374
replace bool
375
+ replaceOption string
376
+ replaceRequested bool
345
377
serverSideApply bool
346
378
serverSideApplyManager string
347
379
pruneLast bool
@@ -965,6 +997,64 @@ func (sc *syncContext) shouldUseServerSideApply(targetObj *unstructured.Unstruct
965
997
return sc .serverSideApply || resourceutil .HasAnnotationOption (targetObj , common .AnnotationSyncOptions , common .SyncOptionServerSideApply )
966
998
}
967
999
1000
+ func (sc * syncContext ) replaceObject (t * syncTask , dryRunStrategy util.DryRunStrategy , force bool , validate bool ) (message string , err error ) {
1001
+ if t .liveObj != nil {
1002
+ // Avoid using `kubectl replace` for CRDs since 'replace' might recreate resource and so delete all CRD instances.
1003
+ // The same thing applies for namespaces, which would delete the namespace as well as everything within it,
1004
+ // so we want to avoid using `kubectl replace` in that case as well.
1005
+ if kube .IsCRD (t .targetObj ) || t .targetObj .GetKind () == kubeutil .NamespaceKind {
1006
+ update := t .targetObj .DeepCopy ()
1007
+ update .SetResourceVersion (t .liveObj .GetResourceVersion ())
1008
+ _ , err = sc .resourceOps .UpdateResource (context .TODO (), update , dryRunStrategy )
1009
+ if err == nil {
1010
+ message = fmt .Sprintf ("%s/%s updated" , t .targetObj .GetKind (), t .targetObj .GetName ())
1011
+ } else {
1012
+ message = fmt .Sprintf ("error when updating: %v" , err .Error ())
1013
+ }
1014
+ } else {
1015
+ message , err = sc .resourceOps .ReplaceResource (context .TODO (), t .targetObj , dryRunStrategy , force )
1016
+ }
1017
+ } else {
1018
+ message , err = sc .resourceOps .CreateResource (context .TODO (), t .targetObj , dryRunStrategy , validate )
1019
+ }
1020
+ return message , err
1021
+ }
1022
+
1023
+ func (sc * syncContext ) shouldReplaceByDefault (t * syncTask ) bool {
1024
+ return (sc .replace ||
1025
+ resourceutil .HasAnnotationOption (t .targetObj , common .AnnotationSyncOptions , common .SyncOptionReplace ) ||
1026
+ sc .replaceOption == common .SyncOptionReplace ||
1027
+ sc .replaceOption == common .SyncOptionReplaceAlways ||
1028
+ resourceutil .HasAnnotationOption (t .targetObj , common .AnnotationSyncOptions , common .SyncOptionReplaceAlways ) ||
1029
+ sc .replaceRequested && sc .replaceOption == common .SyncOptionReplaceIfRequested ||
1030
+ sc .replaceRequested && resourceutil .HasAnnotationOption (t .targetObj , common .AnnotationSyncOptions , common .SyncOptionReplaceIfRequested )) &&
1031
+ ! (sc .replaceOption == common .SyncOptionReplaceNever || resourceutil .HasAnnotationOption (t .targetObj , common .AnnotationSyncOptions , common .SyncOptionReplaceNever ))
1032
+ }
1033
+
1034
+ func (sc * syncContext ) shouldForceByDefault (t * syncTask ) bool {
1035
+ return (sc .force ||
1036
+ resourceutil .HasAnnotationOption (t .targetObj , common .AnnotationSyncOptions , common .SyncOptionForce ) ||
1037
+ sc .forceOption == common .SyncOptionForce ||
1038
+ sc .forceOption == common .SyncOptionForceAlways ||
1039
+ resourceutil .HasAnnotationOption (t .targetObj , common .AnnotationSyncOptions , common .SyncOptionForceAlways ) ||
1040
+ sc .forceRequested && sc .replaceOption == common .SyncOptionForceIfRequested ||
1041
+ sc .forceRequested && resourceutil .HasAnnotationOption (t .targetObj , common .AnnotationSyncOptions , common .SyncOptionForceIfRequested )) &&
1042
+ ! (sc .forceOption == common .SyncOptionForceNever || resourceutil .HasAnnotationOption (t .targetObj , common .AnnotationSyncOptions , common .SyncOptionForceNever ))
1043
+ }
1044
+
1045
+ func (sc * syncContext ) shouldRetryWithReplace (t * syncTask , err error ) bool {
1046
+ return strings .Contains (err .Error (), validation .FieldImmutableErrorMsg ) &&
1047
+ (sc .replaceOption == common .SyncOptionReplaceIfImmutableFieldsUpdated || resourceutil .HasAnnotationOption (t .targetObj , common .AnnotationSyncOptions , common .SyncOptionReplaceIfImmutableFieldsUpdated )) ||
1048
+ (sc .replaceOption == common .SyncOptionReplaceIfApplyFailed || resourceutil .HasAnnotationOption (t .targetObj , common .AnnotationSyncOptions , common .SyncOptionReplaceIfApplyFailed ))
1049
+ }
1050
+
1051
+ func (sc * syncContext ) shouldForceWhenRetrying (t * syncTask , err error , forceByDefault bool ) bool {
1052
+ return forceByDefault ||
1053
+ (strings .Contains (err .Error (), validation .FieldImmutableErrorMsg ) &&
1054
+ (sc .forceOption == common .SyncOptionForceIfImmutableFieldsUpdated || resourceutil .HasAnnotationOption (t .targetObj , common .AnnotationSyncOptions , common .SyncOptionForceIfImmutableFieldsUpdated )) ||
1055
+ (sc .forceOption == common .SyncOptionForceIfApplyFailed || resourceutil .HasAnnotationOption (t .targetObj , common .AnnotationSyncOptions , common .SyncOptionForceIfApplyFailed )))
1056
+ }
1057
+
968
1058
func (sc * syncContext ) applyObject (t * syncTask , dryRun , validate bool ) (common.ResultCode , string ) {
969
1059
dryRunStrategy := cmdutil .DryRunNone
970
1060
if dryRun {
@@ -978,31 +1068,18 @@ func (sc *syncContext) applyObject(t *syncTask, dryRun, validate bool) (common.R
978
1068
979
1069
var err error
980
1070
var message string
981
- shouldReplace := sc .replace || resourceutil . HasAnnotationOption ( t . targetObj , common . AnnotationSyncOptions , common . SyncOptionReplace )
982
- force := sc .force || resourceutil . HasAnnotationOption ( t . targetObj , common . AnnotationSyncOptions , common . SyncOptionForce )
1071
+ replaceByDefault := sc .shouldReplaceByDefault ( t )
1072
+ forceByDefault := sc .shouldForceByDefault ( t )
983
1073
serverSideApply := sc .shouldUseServerSideApply (t .targetObj )
984
- if shouldReplace {
985
- if t .liveObj != nil {
986
- // Avoid using `kubectl replace` for CRDs since 'replace' might recreate resource and so delete all CRD instances.
987
- // The same thing applies for namespaces, which would delete the namespace as well as everything within it,
988
- // so we want to avoid using `kubectl replace` in that case as well.
989
- if kube .IsCRD (t .targetObj ) || t .targetObj .GetKind () == kubeutil .NamespaceKind {
990
- update := t .targetObj .DeepCopy ()
991
- update .SetResourceVersion (t .liveObj .GetResourceVersion ())
992
- _ , err = sc .resourceOps .UpdateResource (context .TODO (), update , dryRunStrategy )
993
- if err == nil {
994
- message = fmt .Sprintf ("%s/%s updated" , t .targetObj .GetKind (), t .targetObj .GetName ())
995
- } else {
996
- message = fmt .Sprintf ("error when updating: %v" , err .Error ())
997
- }
998
- } else {
999
- message , err = sc .resourceOps .ReplaceResource (context .TODO (), t .targetObj , dryRunStrategy , force )
1074
+ if replaceByDefault {
1075
+ message , err = sc .replaceObject (t , dryRunStrategy , forceByDefault , validate )
1076
+ } else {
1077
+ message , err = sc .resourceOps .ApplyResource (context .TODO (), t .targetObj , dryRunStrategy , forceByDefault , validate , serverSideApply , sc .serverSideApplyManager , false )
1078
+ if err != nil {
1079
+ if sc .shouldRetryWithReplace (t , err ) {
1080
+ message , err = sc .replaceObject (t , dryRunStrategy , sc .shouldForceWhenRetrying (t , err , forceByDefault ), validate )
1000
1081
}
1001
- } else {
1002
- message , err = sc .resourceOps .CreateResource (context .TODO (), t .targetObj , dryRunStrategy , validate )
1003
1082
}
1004
- } else {
1005
- message , err = sc .resourceOps .ApplyResource (context .TODO (), t .targetObj , dryRunStrategy , force , validate , serverSideApply , sc .serverSideApplyManager , false )
1006
1083
}
1007
1084
if err != nil {
1008
1085
return common .ResultCodeSyncFailed , err .Error ()
0 commit comments