github.com/IBM-Blockchain/fabric-operator@v1.0.4/pkg/manager/resources/orderernode/manager.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 orderernode
    20  
    21  import (
    22  	"context"
    23  	"encoding/json"
    24  	"fmt"
    25  	"io/ioutil"
    26  	"path/filepath"
    27  	"regexp"
    28  
    29  	current "github.com/IBM-Blockchain/fabric-operator/api/v1beta1"
    30  	k8sclient "github.com/IBM-Blockchain/fabric-operator/pkg/k8s/controllerclient"
    31  	"github.com/IBM-Blockchain/fabric-operator/pkg/manager/resources"
    32  	"github.com/IBM-Blockchain/fabric-operator/pkg/operatorerrors"
    33  	"github.com/go-test/deep"
    34  	"github.com/pkg/errors"
    35  	k8serrors "k8s.io/apimachinery/pkg/api/errors"
    36  	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    37  	"k8s.io/apimachinery/pkg/runtime"
    38  	"k8s.io/apimachinery/pkg/types"
    39  	"k8s.io/apimachinery/pkg/util/yaml"
    40  	"sigs.k8s.io/controller-runtime/pkg/client"
    41  	logf "sigs.k8s.io/controller-runtime/pkg/log"
    42  )
    43  
    44  var log = logf.Log.WithName("orderernode_manager")
    45  
    46  type Manager struct {
    47  	Client            k8sclient.Client
    48  	Scheme            *runtime.Scheme
    49  	OrdererNodeFile   string
    50  	IgnoreDifferences []string
    51  	Name              string
    52  
    53  	LabelsFunc   func(v1.Object) map[string]string
    54  	OverrideFunc func(v1.Object, *current.IBPOrderer, resources.Action) error
    55  }
    56  
    57  func (m *Manager) GetName(instance v1.Object) string {
    58  	name := instance.GetName()
    59  	switch instance.(type) {
    60  	case *current.IBPOrderer:
    61  		ordererspec := instance.(*current.IBPOrderer)
    62  		if ordererspec.Spec.NodeNumber != nil {
    63  			name = fmt.Sprintf("%snode%d", instance.GetName(), *ordererspec.Spec.NodeNumber)
    64  		}
    65  	}
    66  	return GetName(name)
    67  }
    68  
    69  func (m *Manager) Reconcile(instance v1.Object, update bool) error {
    70  	name := m.GetName(instance)
    71  
    72  	orderernode := &current.IBPOrderer{}
    73  	err := m.Client.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: instance.GetNamespace()}, orderernode)
    74  	if err != nil {
    75  		if k8serrors.IsNotFound(err) {
    76  			log.Info(fmt.Sprintf("Creating orderernode '%s'", name))
    77  			orderernode, err = m.GetOrdererNodeBasedOnCRFromFile(instance)
    78  			if err != nil {
    79  				return err
    80  			}
    81  
    82  			log.Info(fmt.Sprintf("Setting controller reference instance name: %s, orderernode name: %s", instance.GetName(), orderernode.GetName()))
    83  			err = m.Client.Create(context.TODO(), orderernode, k8sclient.CreateOption{Owner: instance, Scheme: m.Scheme})
    84  			if err != nil {
    85  				return err
    86  			}
    87  			return nil
    88  		}
    89  		return err
    90  	}
    91  
    92  	if update {
    93  		log.Info(fmt.Sprintf("Updating orderer node is not allowed programmatically '%s'", name))
    94  		return operatorerrors.New(operatorerrors.InvalidOrdererNodeUpdateRequest, "Updating orderer node is not allowed programmatically")
    95  	}
    96  
    97  	return nil
    98  }
    99  
   100  func (m *Manager) GetOrdererNodeBasedOnCRFromFile(instance v1.Object) (*current.IBPOrderer, error) {
   101  	orderernode, err := GetOrderernodeFromFile(m.OrdererNodeFile)
   102  	if err != nil {
   103  		log.Error(err, fmt.Sprintf("Error reading deployment configuration file: %s", m.OrdererNodeFile))
   104  		return nil, err
   105  	}
   106  
   107  	return m.BasedOnCR(instance, orderernode)
   108  }
   109  
   110  func (m *Manager) BasedOnCR(instance v1.Object, orderernode *current.IBPOrderer) (*current.IBPOrderer, error) {
   111  	if m.OverrideFunc != nil {
   112  		err := m.OverrideFunc(instance, orderernode, resources.Create)
   113  		if err != nil {
   114  			return nil, operatorerrors.New(operatorerrors.InvalidOrdererNodeCreateRequest, err.Error())
   115  		}
   116  	}
   117  
   118  	orderernode.Name = m.GetName(instance)
   119  	orderernode.Namespace = instance.GetNamespace()
   120  	orderernode.ObjectMeta.Name = m.GetName(instance)
   121  	orderernode.ObjectMeta.Namespace = instance.GetNamespace()
   122  
   123  	orderernode.Labels = m.LabelsFunc(instance)
   124  
   125  	return orderernode, nil
   126  }
   127  
   128  func (m *Manager) CheckState(instance v1.Object) error {
   129  	if instance == nil {
   130  		return nil // Instance has not been reconciled yet
   131  	}
   132  
   133  	name := m.GetName(instance)
   134  
   135  	// Get the latest version of the instance
   136  	orderernode := &current.IBPOrderer{}
   137  	err := m.Client.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: instance.GetNamespace()}, orderernode)
   138  	if err != nil {
   139  		return nil
   140  	}
   141  
   142  	copy := orderernode.DeepCopy()
   143  	expectedOrderernode, err := m.BasedOnCR(instance, copy)
   144  	if err != nil {
   145  		return err
   146  	}
   147  
   148  	deep.MaxDepth = 20
   149  	deep.MaxDiff = 30
   150  	deep.CompareUnexportedFields = true
   151  	deep.LogErrors = true
   152  
   153  	diff := deep.Equal(orderernode.Spec, expectedOrderernode.Spec)
   154  	if diff != nil {
   155  		err := m.ignoreDifferences(diff)
   156  		if err != nil {
   157  			return errors.Wrap(err, "orderernode has been edited manually, and does not match what is expected based on the CR")
   158  		}
   159  	}
   160  
   161  	return nil
   162  }
   163  
   164  func (m *Manager) RestoreState(instance v1.Object) error {
   165  	if instance == nil {
   166  		return nil // Instance has not been reconciled yet
   167  	}
   168  
   169  	name := m.GetName(instance)
   170  	orderernode := &current.IBPOrderer{}
   171  	err := m.Client.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: instance.GetNamespace()}, orderernode)
   172  	if err != nil {
   173  		return nil
   174  	}
   175  
   176  	orderernode, err = m.BasedOnCR(instance, orderernode)
   177  	if err != nil {
   178  		return err
   179  	}
   180  
   181  	err = m.Client.Update(context.TODO(), orderernode)
   182  	if err != nil {
   183  		return err
   184  	}
   185  
   186  	return nil
   187  }
   188  
   189  func (m *Manager) Get(instance v1.Object) (client.Object, error) {
   190  	if instance == nil {
   191  		return nil, nil // Instance has not been reconciled yet
   192  	}
   193  
   194  	name := m.GetName(instance)
   195  	orderernode := &current.IBPOrderer{}
   196  	err := m.Client.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: instance.GetNamespace()}, orderernode)
   197  	if err != nil {
   198  		return nil, err
   199  	}
   200  
   201  	return orderernode, nil
   202  }
   203  
   204  func (m *Manager) Exists(instance v1.Object) bool {
   205  	_, err := m.Get(instance)
   206  	if err != nil {
   207  		return false
   208  	}
   209  
   210  	return true
   211  }
   212  
   213  func (m *Manager) Delete(instance v1.Object) error {
   214  	on, err := m.Get(instance)
   215  	if err != nil {
   216  		if !k8serrors.IsNotFound(err) {
   217  			return err
   218  		}
   219  	}
   220  
   221  	if on == nil {
   222  		return nil
   223  	}
   224  
   225  	err = m.Client.Delete(context.TODO(), on)
   226  	if err != nil {
   227  		if !k8serrors.IsNotFound(err) {
   228  			return err
   229  		}
   230  	}
   231  
   232  	return nil
   233  }
   234  
   235  func (m *Manager) getSelectorLabels(instance v1.Object) map[string]string {
   236  	return map[string]string{
   237  		"app": instance.GetName(),
   238  	}
   239  }
   240  
   241  func (m *Manager) ignoreDifferences(diff []string) error {
   242  	diffs := []string{}
   243  	for _, d := range diff {
   244  		found := false
   245  		for _, i := range m.differenceToIgnore() {
   246  			regex := regexp.MustCompile(i)
   247  			found = regex.MatchString(d)
   248  			if found {
   249  				break
   250  			}
   251  		}
   252  		if !found {
   253  			diffs = append(diffs, d)
   254  			return fmt.Errorf("unexpected mismatch: %s", d)
   255  		}
   256  	}
   257  	return nil
   258  }
   259  
   260  func (m *Manager) differenceToIgnore() []string {
   261  	d := []string{
   262  		"TypeMeta", "ObjectMeta",
   263  	}
   264  	d = append(d, m.IgnoreDifferences...)
   265  	return d
   266  }
   267  
   268  func (m *Manager) SetCustomName(name string) {
   269  	// NO-OP
   270  }
   271  
   272  func GetName(instanceName string, suffix ...string) string {
   273  	if len(suffix) != 0 {
   274  		if suffix[0] != "" {
   275  			return fmt.Sprintf("%s-%s", instanceName, suffix[0])
   276  		}
   277  	}
   278  	return fmt.Sprintf("%s", instanceName)
   279  }
   280  
   281  func GetOrderernodeFromFile(file string) (*current.IBPOrderer, error) {
   282  	jsonBytes, err := ConvertYamlFileToJson(file)
   283  	if err != nil {
   284  		return nil, err
   285  	}
   286  
   287  	on := &current.IBPOrderer{}
   288  	err = json.Unmarshal(jsonBytes, &on)
   289  	if err != nil {
   290  		return nil, err
   291  	}
   292  
   293  	return on, nil
   294  }
   295  
   296  func ConvertYamlFileToJson(file string) ([]byte, error) {
   297  	absfilepath, err := filepath.Abs(file)
   298  	if err != nil {
   299  		return nil, err
   300  	}
   301  	bytes, err := ioutil.ReadFile(filepath.Clean(absfilepath))
   302  	if err != nil {
   303  		return nil, err
   304  	}
   305  
   306  	return yaml.ToJSON(bytes)
   307  }