github.com/IBM-Blockchain/fabric-operator@v1.0.4/pkg/k8s/controllerclient/client.go (about) 1 /* 2 * Copyright contributors to the Hyperledger Fabric Operator project 3 * 4 * SPDX-License-Identifier: Apache-2.0 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at: 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 package controllerclient 20 21 import ( 22 "context" 23 "time" 24 25 "github.com/IBM-Blockchain/fabric-operator/pkg/util" 26 "github.com/pkg/errors" 27 28 k8serrors "k8s.io/apimachinery/pkg/api/errors" 29 v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/apimachinery/pkg/runtime" 31 "k8s.io/apimachinery/pkg/types" 32 k8sclient "sigs.k8s.io/controller-runtime/pkg/client" 33 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 34 ) 35 36 //go:generate counterfeiter -o ../../controller/mocks/client.go -fake-name Client . Client 37 38 type Client interface { 39 Get(ctx context.Context, key k8sclient.ObjectKey, obj k8sclient.Object) error 40 List(ctx context.Context, list k8sclient.ObjectList, opts ...k8sclient.ListOption) error 41 Create(ctx context.Context, obj k8sclient.Object, opts ...CreateOption) error 42 CreateOrUpdate(ctx context.Context, obj k8sclient.Object, opts ...CreateOrUpdateOption) error 43 Delete(ctx context.Context, obj k8sclient.Object, opts ...k8sclient.DeleteOption) error 44 Patch(ctx context.Context, obj k8sclient.Object, patch k8sclient.Patch, opts ...PatchOption) error 45 PatchStatus(ctx context.Context, obj k8sclient.Object, patch k8sclient.Patch, opts ...PatchOption) error 46 Update(ctx context.Context, obj k8sclient.Object, opts ...UpdateOption) error 47 UpdateStatus(ctx context.Context, obj k8sclient.Object, opts ...k8sclient.UpdateOption) error 48 } 49 50 // GlobalConfig applies the global configuration defined in operator's config to appropriate 51 // kubernetes resources 52 type GlobalConfig interface { 53 Apply(runtime.Object) 54 } 55 56 type ClientImpl struct { 57 k8sClient k8sclient.Client 58 GlobalConfig GlobalConfig 59 } 60 61 func New(c k8sclient.Client, gc GlobalConfig) *ClientImpl { 62 return &ClientImpl{ 63 k8sClient: c, 64 GlobalConfig: gc, 65 } 66 } 67 68 func (c *ClientImpl) Get(ctx context.Context, key k8sclient.ObjectKey, obj k8sclient.Object) error { 69 err := c.k8sClient.Get(ctx, key, obj) 70 if err != nil { 71 return err 72 } 73 return nil 74 } 75 76 func (c *ClientImpl) List(ctx context.Context, list k8sclient.ObjectList, opts ...k8sclient.ListOption) error { 77 err := c.k8sClient.List(ctx, list, opts...) 78 if err != nil { 79 return err 80 } 81 return nil 82 } 83 84 func (c *ClientImpl) Create(ctx context.Context, obj k8sclient.Object, opts ...CreateOption) error { 85 var createOpts []k8sclient.CreateOption 86 87 c.GlobalConfig.Apply(obj) 88 89 if opts != nil && len(opts) > 0 { 90 if err := setControllerReference(opts[0].Owner, obj, opts[0].Scheme); err != nil { 91 return err 92 } 93 createOpts = opts[0].Opts 94 } 95 96 err := c.k8sClient.Create(ctx, obj, createOpts...) 97 if err != nil { 98 return util.IgnoreAlreadyExistError(err) 99 } 100 return nil 101 } 102 103 func (c *ClientImpl) Patch(ctx context.Context, obj k8sclient.Object, patch k8sclient.Patch, opts ...PatchOption) error { 104 var patchOpts []k8sclient.PatchOption 105 106 c.GlobalConfig.Apply(obj) 107 108 if opts != nil && len(opts) > 0 { 109 if opts[0].Resilient != nil { 110 return c.ResilientPatch(ctx, obj, opts[0].Resilient, opts[0].Opts...) 111 } 112 113 patchOpts = opts[0].Opts 114 } 115 116 err := c.k8sClient.Patch(ctx, obj, patch, patchOpts...) 117 if err != nil { 118 return err 119 } 120 return nil 121 } 122 123 func (c *ClientImpl) ResilientPatch(ctx context.Context, obj k8sclient.Object, resilient *ResilientPatch, opts ...k8sclient.PatchOption) error { 124 retry := resilient.Retry 125 into := resilient.Into 126 strategy := resilient.Strategy 127 128 c.GlobalConfig.Apply(obj) 129 130 for i := 0; i < retry; i++ { 131 err := c.resilientPatch(ctx, obj, strategy, into, opts...) 132 if err != nil { 133 if i == retry { 134 return err 135 } 136 if k8serrors.IsConflict(err) { 137 time.Sleep(2 * time.Second) 138 continue 139 } 140 return err 141 } 142 } 143 144 return nil 145 } 146 147 func (c *ClientImpl) resilientPatch(ctx context.Context, obj k8sclient.Object, strategy func(k8sclient.Object) k8sclient.Patch, into k8sclient.Object, opts ...k8sclient.PatchOption) error { 148 key := types.NamespacedName{ 149 Name: obj.GetName(), 150 Namespace: obj.GetNamespace(), 151 } 152 153 err := c.Get(ctx, key, into) 154 if err != nil { 155 return err 156 } 157 158 err = c.k8sClient.Patch(ctx, obj, strategy(into), opts...) 159 if err != nil { 160 return err 161 } 162 return nil 163 } 164 165 // If utilizing resilient option, nil can be passed for patch parameter 166 func (c *ClientImpl) PatchStatus(ctx context.Context, obj k8sclient.Object, patch k8sclient.Patch, opts ...PatchOption) error { 167 var patchOpts []k8sclient.PatchOption 168 169 if opts != nil && len(opts) > 0 { 170 if opts[0].Resilient != nil { 171 return c.ResilientPatchStatus(ctx, obj, opts[0].Resilient, opts[0].Opts...) 172 } 173 174 patchOpts = opts[0].Opts 175 } 176 177 err := c.k8sClient.Status().Patch(ctx, obj, patch, patchOpts...) 178 if err != nil { 179 return err 180 } 181 return nil 182 } 183 184 func (c *ClientImpl) ResilientPatchStatus(ctx context.Context, obj k8sclient.Object, resilient *ResilientPatch, opts ...k8sclient.PatchOption) error { 185 retry := resilient.Retry 186 into := resilient.Into 187 strategy := resilient.Strategy 188 189 for i := 0; i < retry; i++ { 190 err := c.resilientPatchStatus(ctx, obj, strategy, into, opts...) 191 if err != nil { 192 if i == retry { 193 return err 194 } 195 if k8serrors.IsConflict(err) { 196 time.Sleep(2 * time.Second) 197 continue 198 } 199 return err 200 } 201 } 202 203 return nil 204 } 205 206 func (c *ClientImpl) resilientPatchStatus(ctx context.Context, obj k8sclient.Object, strategy func(k8sclient.Object) k8sclient.Patch, into k8sclient.Object, opts ...k8sclient.PatchOption) error { 207 key := types.NamespacedName{ 208 Name: obj.GetName(), 209 Namespace: obj.GetNamespace(), 210 } 211 212 err := c.Get(ctx, key, into) 213 if err != nil { 214 return err 215 } 216 217 err = c.k8sClient.Status().Patch(ctx, obj, strategy(into), opts...) 218 if err != nil { 219 return err 220 } 221 return nil 222 } 223 224 // NOTE: Currently, Resilient Update is not supported as it requires more specific 225 // implementation based on scenario. When possible, should utilize resilient Patch. 226 func (c *ClientImpl) Update(ctx context.Context, obj k8sclient.Object, opts ...UpdateOption) error { 227 var updateOpts []k8sclient.UpdateOption 228 229 c.GlobalConfig.Apply(obj) 230 231 if opts != nil && len(opts) > 0 { 232 if err := setControllerReference(opts[0].Owner, obj, opts[0].Scheme); err != nil { 233 return err 234 } 235 updateOpts = opts[0].Opts 236 } 237 238 err := c.k8sClient.Update(ctx, obj, updateOpts...) 239 if err != nil { 240 return err 241 } 242 return nil 243 } 244 245 // NOTE: Currently, Resilient UpdateStatus is not supported as it requires more specific 246 // implementation based on scenario. When possible, should utilize resilient PatchStatus. 247 func (c *ClientImpl) UpdateStatus(ctx context.Context, obj k8sclient.Object, opts ...k8sclient.UpdateOption) error { 248 err := c.k8sClient.Status().Update(ctx, obj, opts...) 249 if err != nil { 250 return err 251 } 252 return nil 253 } 254 255 func (c *ClientImpl) Delete(ctx context.Context, obj k8sclient.Object, opts ...k8sclient.DeleteOption) error { 256 err := c.k8sClient.Delete(ctx, obj, opts...) 257 if err != nil { 258 return err 259 } 260 return nil 261 } 262 263 // CreateOrUpdate does not support k8sclient.CreateOption or k8sclient.UpdateOption being passed as variadic parameters, 264 // if want to use opts use Create or Update methods 265 func (c *ClientImpl) CreateOrUpdate(ctx context.Context, obj k8sclient.Object, opts ...CreateOrUpdateOption) error { 266 if opts != nil && len(opts) > 0 { 267 if err := setControllerReference(opts[0].Owner, obj, opts[0].Scheme); err != nil { 268 return err 269 } 270 } 271 272 c.GlobalConfig.Apply(obj) 273 274 err := c.k8sClient.Create(ctx, obj) 275 if err != nil { 276 if k8serrors.IsAlreadyExists(err) { 277 return c.k8sClient.Update(ctx, obj) 278 } 279 return err 280 } 281 return nil 282 } 283 284 func setControllerReference(owner v1.Object, obj v1.Object, scheme *runtime.Scheme) error { 285 if owner != nil && obj != nil && scheme != nil { 286 err := controllerutil.SetControllerReference(owner, obj, scheme) 287 if err != nil { 288 if _, ok := err.(*controllerutil.AlreadyOwnedError); ok { 289 return nil 290 } 291 return errors.Wrap(err, "controller reference error") 292 } 293 } 294 295 return nil 296 }