github.com/wmuizelaar/kpt@v0.0.0-20221018115725-bd564717b2ed/pkg/status/rollout.go (about) 1 // Copyright 2022 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 "strconv" 22 23 apierrors "k8s.io/apimachinery/pkg/api/errors" 24 "k8s.io/apimachinery/pkg/api/meta" 25 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 26 "k8s.io/apimachinery/pkg/runtime/schema" 27 "k8s.io/apimachinery/pkg/types" 28 "sigs.k8s.io/cli-utils/pkg/kstatus/polling/engine" 29 "sigs.k8s.io/cli-utils/pkg/kstatus/polling/event" 30 "sigs.k8s.io/cli-utils/pkg/kstatus/status" 31 "sigs.k8s.io/cli-utils/pkg/object" 32 "sigs.k8s.io/yaml" 33 ) 34 35 const ( 36 ArgoGroup = "argoproj.io" 37 Rollout = "Rollout" 38 Degraded = "Degraded" 39 Failed = "Failed" 40 Healthy = "Healthy" 41 Paused = "Paused" 42 Progressing = "Progressing" 43 ) 44 45 type RolloutStatusReader struct { 46 Mapper meta.RESTMapper 47 } 48 49 func NewRolloutStatusReader(mapper meta.RESTMapper) engine.StatusReader { 50 return &RolloutStatusReader{ 51 Mapper: mapper, 52 } 53 } 54 55 var _ engine.StatusReader = &RolloutStatusReader{} 56 57 // Supports returns true for all rollout resources. 58 func (r *RolloutStatusReader) Supports(gk schema.GroupKind) bool { 59 return gk.Group == ArgoGroup && gk.Kind == Rollout 60 } 61 62 func (r *RolloutStatusReader) Compute(u *unstructured.Unstructured) (*status.Result, error) { 63 result := status.Result{ 64 Status: status.UnknownStatus, 65 Message: status.GetStringField(u.Object, ".status.message", ""), 66 Conditions: make([]status.Condition, 0), 67 } 68 // ensure that the meta generation is observed 69 generation, found, err := unstructured.NestedInt64(u.Object, "metadata", "generation") 70 if err != nil { 71 return &result, fmt.Errorf("looking up metadata.generation from resource: %w", err) 72 } 73 if !found { 74 return &result, fmt.Errorf("metadata.generation not found") 75 } 76 77 // Argo Rollouts defines the observedGeneration field in the Rollout object as a string 78 // so read it as a string here 79 observedGenerationString, found, err := unstructured.NestedString(u.Object, "status", "observedGeneration") 80 if err != nil { 81 return &result, fmt.Errorf("looking up status.observedGeneration from resource: %w", err) 82 } 83 if !found { 84 // We know that Rollout resources uses the ObservedGeneration pattern, so consider it 85 // an error if it is not found. 86 return &result, fmt.Errorf("status.ObservedGeneration not found") 87 } 88 // If no errors detected and the field is found 89 // Parse it to become an integer 90 observedGeneration, err := strconv.ParseInt(observedGenerationString, 10, 64) 91 if err != nil { 92 return &result, fmt.Errorf("looking up status.observedGeneration from resource: %w", err) 93 } 94 95 if generation != observedGeneration { 96 msg := fmt.Sprintf("%s generation is %d, but latest observed generation is %d", u.GetKind(), generation, observedGeneration) 97 result.Status = status.InProgressStatus 98 result.Message = msg 99 return &result, nil 100 } 101 102 phase, phaseFound, err := unstructured.NestedString(u.Object, "status", "phase") 103 if err != nil { 104 return &result, fmt.Errorf("looking up status.phase from resource: %w", err) 105 } 106 if !phaseFound { 107 // We know that Rollout resources uses the phase pattern, so consider it 108 // an error if it is not found. 109 return &result, fmt.Errorf("status.phase not found") 110 } 111 112 conditions, condFound, err := unstructured.NestedSlice(u.Object, "status", "conditions") 113 if err != nil { 114 return &result, fmt.Errorf("looking up status.conditions from resource: %w", err) 115 } 116 if condFound { 117 data, err := yaml.Marshal(conditions) 118 if err != nil { 119 return &result, fmt.Errorf("failed to marshal conditions for %s/%s", u.GetNamespace(), u.GetName()) 120 } 121 err = yaml.Unmarshal(data, &result.Conditions) 122 if err != nil { 123 return &result, fmt.Errorf("failed to unmarshal conditions for %s/%s", u.GetNamespace(), u.GetName()) 124 } 125 } 126 127 specReplicas := status.GetIntField(u.Object, ".spec.replicas", 1) // Controller uses 1 as default if not specified. 128 statusReplicas := status.GetIntField(u.Object, ".status.replicas", 0) 129 updatedReplicas := status.GetIntField(u.Object, ".status.updatedReplicas", 0) 130 readyReplicas := status.GetIntField(u.Object, ".status.readyReplicas", 0) 131 availableReplicas := status.GetIntField(u.Object, ".status.availableReplicas", 0) 132 133 if specReplicas > statusReplicas { 134 message := fmt.Sprintf("replicas: %d/%d", statusReplicas, specReplicas) 135 result.Status = status.InProgressStatus 136 result.Message = message 137 138 return &result, nil 139 } 140 141 if statusReplicas > specReplicas { 142 message := fmt.Sprintf("Pending termination: %d", statusReplicas-specReplicas) 143 result.Status = status.InProgressStatus 144 result.Message = message 145 return &result, nil 146 } 147 148 if updatedReplicas > availableReplicas { 149 message := fmt.Sprintf("Available: %d/%d", availableReplicas, updatedReplicas) 150 result.Status = status.InProgressStatus 151 result.Message = message 152 return &result, nil 153 } 154 155 if specReplicas > readyReplicas { 156 message := fmt.Sprintf("Ready: %d/%d", readyReplicas, specReplicas) 157 result.Status = status.InProgressStatus 158 result.Message = message 159 return &result, nil 160 } 161 162 message := status.GetStringField(u.Object, ".status.message", "") 163 if message != "" { 164 message += " " 165 } 166 message += fmt.Sprintf("Ready Replicas: %d, Updated Replicas: %d", readyReplicas, updatedReplicas) 167 result.Message = message 168 169 switch phase { 170 case Degraded, Failed: 171 result.Status = status.FailedStatus 172 case Healthy: 173 result.Status = status.CurrentStatus 174 case Paused, Progressing: 175 result.Status = status.InProgressStatus 176 default: 177 // Undefined status 178 result.Status = status.UnknownStatus 179 } 180 return &result, nil 181 } 182 183 func (r *RolloutStatusReader) ReadStatus(ctx context.Context, reader engine.ClusterReader, id object.ObjMetadata) ( 184 *event.ResourceStatus, error) { 185 gvk, err := toGVK(id.GroupKind, r.Mapper) 186 if err != nil { 187 return newUnknownResourceStatus(id, nil, err), nil 188 } 189 190 key := types.NamespacedName{ 191 Name: id.Name, 192 Namespace: id.Namespace, 193 } 194 195 var u unstructured.Unstructured 196 u.SetGroupVersionKind(gvk) 197 err = reader.Get(ctx, key, &u) 198 if err != nil { 199 if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { 200 return nil, err 201 } 202 if apierrors.IsNotFound(err) { 203 return newResourceStatus(id, status.NotFoundStatus, &u, "Resource not found"), nil 204 } 205 return newUnknownResourceStatus(id, nil, err), nil 206 } 207 208 return r.ReadStatusForObject(ctx, reader, &u) 209 } 210 211 func (r *RolloutStatusReader) ReadStatusForObject(_ context.Context, _ engine.ClusterReader, u *unstructured.Unstructured) ( 212 *event.ResourceStatus, error) { 213 id := object.UnstructuredToObjMetadata(u) 214 215 // First check if the resource is in the process of being deleted. 216 deletionTimestamp, found, err := unstructured.NestedString(u.Object, "metadata", "deletionTimestamp") 217 if err != nil { 218 return newUnknownResourceStatus(id, u, err), nil 219 } 220 if found && deletionTimestamp != "" { 221 return newResourceStatus(id, status.TerminatingStatus, u, "Resource scheduled for deletion"), nil 222 } 223 224 res, err := r.Compute(u) 225 if err != nil { 226 return newUnknownResourceStatus(id, u, err), nil 227 } 228 229 return newResourceStatus(id, res.Status, u, res.Message), nil 230 }