github.com/IBM-Blockchain/fabric-operator@v1.0.4/pkg/manager/resources/ingress/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 ingress
    20  
    21  import (
    22  	"context"
    23  	"fmt"
    24  	"os"
    25  	"time"
    26  
    27  	"github.com/IBM-Blockchain/fabric-operator/pkg/manager/resources"
    28  
    29  	k8sclient "github.com/IBM-Blockchain/fabric-operator/pkg/k8s/controllerclient"
    30  	"github.com/IBM-Blockchain/fabric-operator/pkg/util"
    31  	"github.com/pkg/errors"
    32  	networkingv1 "k8s.io/api/networking/v1"
    33  	k8serrors "k8s.io/apimachinery/pkg/api/errors"
    34  	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    35  	"k8s.io/apimachinery/pkg/runtime"
    36  	"k8s.io/apimachinery/pkg/types"
    37  	"k8s.io/apimachinery/pkg/util/wait"
    38  	"sigs.k8s.io/controller-runtime/pkg/client"
    39  	logf "sigs.k8s.io/controller-runtime/pkg/log"
    40  )
    41  
    42  var log = logf.Log.WithName("ingress_manager")
    43  
    44  type Manager struct {
    45  	Client      k8sclient.Client
    46  	Scheme      *runtime.Scheme
    47  	IngressFile string
    48  	Suffix      string
    49  
    50  	LabelsFunc   func(v1.Object) map[string]string
    51  	OverrideFunc func(v1.Object, *networkingv1.Ingress, resources.Action) error
    52  
    53  	routeName string
    54  	Name      string
    55  }
    56  
    57  func (m *Manager) Reconcile(instance v1.Object, update bool) error {
    58  	name := instance.GetName()
    59  	if m.Suffix != "" {
    60  		name = fmt.Sprintf("%s-%s", instance.GetName(), m.Suffix)
    61  	}
    62  	ingress := &networkingv1.Ingress{}
    63  	err := m.Client.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: instance.GetNamespace()}, ingress)
    64  	if err != nil {
    65  		if k8serrors.IsNotFound(err) {
    66  			log.Info(fmt.Sprintf("Creating ingress '%s'", name))
    67  			ingress, err := m.GetIngressBasedOnCRFromFile(instance)
    68  			if err != nil {
    69  				return err
    70  			}
    71  			err = m.Client.Create(context.TODO(), ingress, k8sclient.CreateOption{Owner: instance, Scheme: m.Scheme})
    72  			if err != nil {
    73  				return err
    74  			}
    75  
    76  			err = m.UpdateIngressClassName(name, instance)
    77  			if err != nil {
    78  				log.Error(err, "Error updating ingress class name")
    79  				return err
    80  			}
    81  
    82  			return nil
    83  		}
    84  		return err
    85  	}
    86  
    87  	if update {
    88  		if m.OverrideFunc != nil {
    89  			log.Info(fmt.Sprintf("Updating ingress '%s'", name))
    90  			err := m.OverrideFunc(instance, ingress, resources.Update)
    91  			if err != nil {
    92  				return err
    93  			}
    94  
    95  			err = m.Client.Update(context.TODO(), ingress, k8sclient.UpdateOption{Owner: instance, Scheme: m.Scheme})
    96  			if err != nil {
    97  				return err
    98  			}
    99  
   100  			err = m.UpdateIngressClassName(name, instance)
   101  			if err != nil {
   102  				log.Error(err, "Error updating ingress class name")
   103  				return err
   104  			}
   105  
   106  			return nil
   107  		}
   108  	}
   109  
   110  	// TODO: If needed, update logic for servie goes here
   111  
   112  	return nil
   113  }
   114  
   115  func (m *Manager) Exists(instance v1.Object) bool {
   116  	if instance == nil {
   117  		return false // Instance has not been reconciled yet
   118  	}
   119  
   120  	name := instance.GetName()
   121  	if m.Suffix != "" {
   122  		name = fmt.Sprintf("%s-%s", instance.GetName(), m.Suffix)
   123  	}
   124  
   125  	ingress := &networkingv1.Ingress{}
   126  	err := m.Client.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: instance.GetNamespace()}, ingress)
   127  	if err != nil {
   128  		return false
   129  	}
   130  
   131  	return true
   132  }
   133  
   134  func (m *Manager) Delete(instance v1.Object) error {
   135  	ingress, err := m.Get(instance)
   136  	if err != nil {
   137  		if !k8serrors.IsNotFound(err) {
   138  			return err
   139  		}
   140  	}
   141  
   142  	if ingress == nil {
   143  		return nil
   144  	}
   145  
   146  	err = m.Client.Delete(context.TODO(), ingress)
   147  	if err != nil {
   148  		if !k8serrors.IsNotFound(err) {
   149  			return err
   150  		}
   151  	}
   152  
   153  	return nil
   154  }
   155  
   156  func (m *Manager) Get(instance v1.Object) (client.Object, error) {
   157  	if instance == nil {
   158  		return nil, nil // Instance has not been reconciled yet
   159  	}
   160  
   161  	name := m.GetName(instance)
   162  	ingress := &networkingv1.Ingress{}
   163  	err := m.Client.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: instance.GetNamespace()}, ingress)
   164  	if err != nil {
   165  		return nil, err
   166  	}
   167  
   168  	return ingress, nil
   169  }
   170  
   171  func (m *Manager) GetName(instance v1.Object) string {
   172  	if m.Name != "" {
   173  		return fmt.Sprintf("%s-%s", instance.GetName(), m.Name)
   174  	}
   175  	return instance.GetName()
   176  }
   177  
   178  func (m *Manager) GetIngressBasedOnCRFromFile(instance v1.Object) (*networkingv1.Ingress, error) {
   179  	ingress, err := util.GetIngressFromFile(m.IngressFile)
   180  	if err != nil {
   181  		log.Error(err, fmt.Sprintf("Error reading ingress configuration file: %s", m.IngressFile))
   182  		return nil, err
   183  	}
   184  
   185  	return m.BasedOnCR(instance, ingress)
   186  }
   187  
   188  func (m *Manager) BasedOnCR(instance v1.Object, ingress *networkingv1.Ingress) (*networkingv1.Ingress, error) {
   189  	if m.OverrideFunc != nil {
   190  		err := m.OverrideFunc(instance, ingress, resources.Create)
   191  		if err != nil {
   192  			return nil, errors.Wrap(err, "failed during ingress override")
   193  		}
   194  	}
   195  
   196  	ingress.Name = instance.GetName()
   197  	if m.Suffix != "" {
   198  		ingress.Name = fmt.Sprintf("%s-%s", instance.GetName(), m.Suffix)
   199  	}
   200  
   201  	ingress.Namespace = instance.GetNamespace()
   202  	ingress.Labels = m.LabelsFunc(instance)
   203  
   204  	return ingress, nil
   205  }
   206  
   207  func (m *Manager) CheckState(instance v1.Object) error {
   208  	// NO-OP
   209  	return nil
   210  }
   211  
   212  func (m *Manager) RestoreState(instance v1.Object) error {
   213  	// NO-OP
   214  	return nil
   215  }
   216  
   217  func (m *Manager) SetCustomName(name string) {
   218  	// NO-OP
   219  }
   220  
   221  func (m *Manager) UpdateIngressClassName(name string, instance v1.Object) error {
   222  	ingress := &networkingv1.Ingress{}
   223  
   224  	// We have to wait for ingress to be available
   225  	// as it fails if this function is called immediately after creation
   226  	log.Info("Waiting for ingress resource to be ready", "ingress", name)
   227  
   228  	ingressPollTimeout := 10 * time.Second
   229  
   230  	if pollTime := os.Getenv("INGRESS_RESOURCE_POLL_TIMEOUT"); pollTime != "" {
   231  		d, err := time.ParseDuration(pollTime)
   232  		if err != nil {
   233  			return err
   234  		}
   235  
   236  		ingressPollTimeout = d
   237  	}
   238  
   239  	var errGet error
   240  	err := wait.Poll(500*time.Millisecond, ingressPollTimeout, func() (bool, error) {
   241  		errGet = m.Client.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: instance.GetNamespace()}, ingress)
   242  		if errGet != nil {
   243  			return false, nil
   244  		}
   245  		return true, nil
   246  	})
   247  
   248  	if err != nil {
   249  		return err
   250  	}
   251  
   252  	ingressClass := ingress.ObjectMeta.Annotations["kubernetes.io/ingress.class"]
   253  	if ingressClass != "" {
   254  		ingress.Spec.IngressClassName = &ingressClass
   255  	}
   256  
   257  	log.Info("Updating ingress classname in the ingress resource spec", "ingress", name, "ingressClassName", ingressClass)
   258  	err = m.Client.Update(context.TODO(), ingress, k8sclient.UpdateOption{Owner: instance, Scheme: m.Scheme})
   259  	if err != nil {
   260  		return err
   261  	}
   262  
   263  	return nil
   264  }