github.com/wmuizelaar/kpt@v0.0.0-20221018115725-bd564717b2ed/pkg/status/configconnector.go (about) 1 // Copyright 2021 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package status 16 17 import ( 18 "context" 19 "errors" 20 "fmt" 21 "strings" 22 23 corev1 "k8s.io/api/core/v1" 24 apierrors "k8s.io/apimachinery/pkg/api/errors" 25 "k8s.io/apimachinery/pkg/api/meta" 26 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 27 "k8s.io/apimachinery/pkg/runtime/schema" 28 "k8s.io/apimachinery/pkg/types" 29 "sigs.k8s.io/cli-utils/pkg/kstatus/polling/engine" 30 "sigs.k8s.io/cli-utils/pkg/kstatus/polling/event" 31 "sigs.k8s.io/cli-utils/pkg/kstatus/status" 32 "sigs.k8s.io/cli-utils/pkg/object" 33 ) 34 35 const ( 36 ConditionReady = "Ready" 37 ) 38 39 // ConfigConnectorStatusReader can compute reconcile status for Config Connector 40 // resources. It leverages information in the `Reason` field of the `Ready` condition. 41 // TODO(mortent): Make more of the convencience functions and types from cli-utils 42 // exported so we can simplify this. 43 type ConfigConnectorStatusReader struct { 44 Mapper meta.RESTMapper 45 } 46 47 func NewConfigConnectorStatusReader(mapper meta.RESTMapper) engine.StatusReader { 48 return &ConfigConnectorStatusReader{ 49 Mapper: mapper, 50 } 51 } 52 53 var _ engine.StatusReader = &ConfigConnectorStatusReader{} 54 55 // Supports returns true for all Config Connector resources. 56 func (c *ConfigConnectorStatusReader) Supports(gk schema.GroupKind) bool { 57 return strings.HasSuffix(gk.Group, "cnrm.cloud.google.com") 58 } 59 60 func (c *ConfigConnectorStatusReader) ReadStatus(ctx context.Context, reader engine.ClusterReader, id object.ObjMetadata) (*event.ResourceStatus, error) { 61 gvk, err := toGVK(id.GroupKind, c.Mapper) 62 if err != nil { 63 return newUnknownResourceStatus(id, nil, err), nil 64 } 65 66 key := types.NamespacedName{ 67 Name: id.Name, 68 Namespace: id.Namespace, 69 } 70 71 var u unstructured.Unstructured 72 u.SetGroupVersionKind(gvk) 73 err = reader.Get(ctx, key, &u) 74 if err != nil { 75 if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { 76 return nil, err 77 } 78 if apierrors.IsNotFound(err) { 79 return newResourceStatus(id, status.NotFoundStatus, &u, "Resource not found"), nil 80 } 81 return newUnknownResourceStatus(id, nil, err), nil 82 } 83 84 return c.ReadStatusForObject(ctx, reader, &u) 85 } 86 87 func (c *ConfigConnectorStatusReader) ReadStatusForObject(_ context.Context, _ engine.ClusterReader, u *unstructured.Unstructured) (*event.ResourceStatus, error) { 88 id := object.UnstructuredToObjMetadata(u) 89 90 // First check if the resource is in the process of being deleted. 91 deletionTimestamp, found, err := unstructured.NestedString(u.Object, "metadata", "deletionTimestamp") 92 if err != nil { 93 return newUnknownResourceStatus(id, u, err), nil 94 } 95 if found && deletionTimestamp != "" { 96 return newResourceStatus(id, status.TerminatingStatus, u, "Resource scheduled for deletion"), nil 97 } 98 99 res, err := c.Compute(u) 100 101 if err != nil { 102 return newUnknownResourceStatus(id, u, err), nil 103 } 104 return newResourceStatus(id, res.Status, u, res.Message), nil 105 } 106 107 func (c *ConfigConnectorStatusReader) Compute(u *unstructured.Unstructured) (*status.Result, error) { 108 if u.GroupVersionKind().Kind == "ConfigConnectorContext" { 109 return computeStatusForConfigConnectorContext(u) 110 } 111 112 return computeStatusForConfigConnector(u) 113 } 114 115 func computeStatusForConfigConnectorContext(u *unstructured.Unstructured) (*status.Result, error) { 116 healthy, found, err := unstructured.NestedBool(u.Object, "status", "healthy") 117 if err != nil { 118 e := fmt.Errorf("looking up status.healthy from resource: %w", err) 119 return nil, e 120 } 121 if !found { 122 msg := "status.healthy property not set" 123 return &status.Result{ 124 Status: status.InProgressStatus, 125 Message: msg, 126 Conditions: []status.Condition{ 127 { 128 Type: status.ConditionReconciling, 129 Status: corev1.ConditionTrue, 130 Reason: "NotReady", 131 Message: msg, 132 }, 133 }, 134 }, nil 135 } 136 if !healthy { 137 msg := "status.healthy is false" 138 return &status.Result{ 139 Status: status.InProgressStatus, 140 Message: msg, 141 Conditions: []status.Condition{ 142 { 143 Type: status.ConditionReconciling, 144 Status: corev1.ConditionTrue, 145 Reason: "NotReady", 146 Message: msg, 147 }, 148 }, 149 }, nil 150 } 151 return &status.Result{ 152 Status: status.CurrentStatus, 153 Message: "status.healthy is true", 154 }, nil 155 } 156 157 func computeStatusForConfigConnector(u *unstructured.Unstructured) (*status.Result, error) { 158 // ensure that the meta generation is observed 159 generation, found, err := unstructured.NestedInt64(u.Object, "metadata", "generation") 160 if err != nil { 161 e := fmt.Errorf("looking up metadata.generation from resource: %w", err) 162 return nil, e 163 } 164 if !found { 165 e := fmt.Errorf("metadata.generation not found") 166 return nil, e 167 } 168 169 observedGeneration, found, err := unstructured.NestedInt64(u.Object, "status", "observedGeneration") 170 if err != nil { 171 e := fmt.Errorf("looking up status.observedGeneration from resource: %w", err) 172 return nil, e 173 } 174 if !found { 175 // We know that Config Connector resources uses the ObservedGeneration pattern, so consider it 176 // an error if it is not found. 177 e := fmt.Errorf("status.ObservedGeneration not found") 178 return nil, e 179 } 180 if generation != observedGeneration { 181 msg := fmt.Sprintf("%s generation is %d, but latest observed generation is %d", u.GetKind(), generation, observedGeneration) 182 return &status.Result{ 183 Status: status.InProgressStatus, 184 Message: msg, 185 Conditions: []status.Condition{ 186 { 187 Type: status.ConditionReconciling, 188 Status: corev1.ConditionTrue, 189 Reason: "LatestGenerationNotObserved", 190 Message: msg, 191 }, 192 }, 193 }, nil 194 } 195 196 obj, err := status.GetObjectWithConditions(u.Object) 197 if err != nil { 198 return nil, err 199 } 200 201 var readyCond status.BasicCondition 202 foundCond := false 203 for i := range obj.Status.Conditions { 204 if obj.Status.Conditions[i].Type == ConditionReady { 205 readyCond = obj.Status.Conditions[i] 206 foundCond = true 207 } 208 } 209 210 if !foundCond { 211 msg := "Ready condition not set" 212 return &status.Result{ 213 Status: status.InProgressStatus, 214 Message: msg, 215 Conditions: []status.Condition{ 216 { 217 Type: status.ConditionReconciling, 218 Status: corev1.ConditionTrue, 219 Reason: "NoReadyCondition", 220 Message: msg, 221 }, 222 }, 223 }, nil 224 } 225 226 if readyCond.Status == corev1.ConditionTrue { 227 return &status.Result{ 228 Status: status.CurrentStatus, 229 Message: "Resource is Current", 230 }, nil 231 } 232 233 switch readyCond.Reason { 234 case "ManagementConflict", "UpdateFailed", "DeleteFailed", "DependencyInvalid": 235 return &status.Result{ 236 Status: status.FailedStatus, 237 Message: readyCond.Message, 238 Conditions: []status.Condition{ 239 { 240 Type: status.ConditionStalled, 241 Status: corev1.ConditionTrue, 242 Reason: readyCond.Reason, 243 Message: readyCond.Message, 244 }, 245 }, 246 }, nil 247 } 248 249 return &status.Result{ 250 Status: status.InProgressStatus, 251 Message: readyCond.Message, 252 Conditions: []status.Condition{ 253 { 254 Type: status.ConditionReconciling, 255 Status: corev1.ConditionTrue, 256 Reason: readyCond.Reason, 257 Message: readyCond.Message, 258 }, 259 }, 260 }, nil 261 } 262 263 func toGVK(gk schema.GroupKind, mapper meta.RESTMapper) (schema.GroupVersionKind, error) { 264 mapping, err := mapper.RESTMapping(gk) 265 if err != nil { 266 return schema.GroupVersionKind{}, err 267 } 268 return mapping.GroupVersionKind, nil 269 } 270 271 func newResourceStatus(id object.ObjMetadata, s status.Status, u *unstructured.Unstructured, msg string) *event.ResourceStatus { 272 return &event.ResourceStatus{ 273 Identifier: id, 274 Status: s, 275 Resource: u, 276 Message: msg, 277 } 278 } 279 280 func newUnknownResourceStatus(id object.ObjMetadata, u *unstructured.Unstructured, err error) *event.ResourceStatus { 281 return &event.ResourceStatus{ 282 Identifier: id, 283 Status: status.UnknownStatus, 284 Error: err, 285 Resource: u, 286 } 287 }