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 }