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  }