in internal/resource/resource.go [127:251]
func NewResource(ctx context.Context, slice *apiv1.ResourceSlice, index int) (*Resource, error) {
logger := logr.FromContextOrDiscard(ctx)
resource := slice.Spec.Resources[index]
res := &Resource{
ManifestDeleted: resource.Deleted,
ManifestRef: ManifestRef{
Slice: types.NamespacedName{
Namespace: slice.Namespace,
Name: slice.Name,
},
Index: index,
},
}
hash := fnv.New64()
hash.Write([]byte(resource.Manifest))
res.ManifestHash = hash.Sum(nil)
parsed := &unstructured.Unstructured{}
res.parsed = parsed
err := parsed.UnmarshalJSON([]byte(resource.Manifest))
if err != nil {
return nil, fmt.Errorf("invalid json: %w", err)
}
// Prune out the status/creation time.
// This is a pragmatic choice to make Eno behave in expected ways for synthesizers written using client-go structs,
// which set metadata.creationTime=null and status={}.
if parsed.Object != nil {
delete(parsed.Object, "status")
parsed.SetCreationTimestamp(metav1.Time{})
}
gvk := parsed.GroupVersionKind()
res.GVK = gvk
res.Ref.Name = parsed.GetName()
res.Ref.Namespace = parsed.GetNamespace()
res.Ref.Group = parsed.GroupVersionKind().Group
res.Ref.Kind = parsed.GetKind()
logger = logger.WithValues("resourceKind", parsed.GetKind(), "resourceName", parsed.GetName(), "resourceNamespace", parsed.GetNamespace())
if res.Ref.Name == "" || res.Ref.Kind == "" || parsed.GetAPIVersion() == "" {
return nil, fmt.Errorf("missing name, kind, or apiVersion")
}
if res.GVK == patchGVK {
obj := struct {
Patch patchMeta `json:"patch"`
}{}
err = json.Unmarshal([]byte(resource.Manifest), &obj)
if err != nil {
return nil, fmt.Errorf("parsing patch json: %w", err)
}
gv, err := schema.ParseGroupVersion(obj.Patch.APIVersion)
if err != nil {
return nil, fmt.Errorf("parsing patch apiVersion: %w", err)
}
res.GVK.Group = gv.Group
res.GVK.Version = gv.Version
res.GVK.Kind = obj.Patch.Kind
res.Patch = obj.Patch.Ops
}
if res.GVK.Group == "apiextensions.k8s.io" && res.GVK.Kind == "CustomResourceDefinition" {
res.DefinedGroupKind = &schema.GroupKind{}
res.DefinedGroupKind.Group, _, _ = unstructured.NestedString(parsed.Object, "spec", "group")
res.DefinedGroupKind.Kind, _, _ = unstructured.NestedString(parsed.Object, "spec", "names", "kind")
}
res.Labels = maps.Clone(parsed.GetLabels())
anno := parsed.GetAnnotations()
if anno == nil {
anno = map[string]string{}
}
const reconcileIntervalKey = "eno.azure.io/reconcile-interval"
if str, ok := anno[reconcileIntervalKey]; ok {
reconcileInterval, err := time.ParseDuration(str)
if anno[reconcileIntervalKey] != "" && err != nil {
logger.V(0).Info("invalid reconcile interval - ignoring")
}
res.ReconcileInterval = &metav1.Duration{Duration: reconcileInterval}
}
const disableUpdatesKey = "eno.azure.io/disable-updates"
res.DisableUpdates = anno[disableUpdatesKey] == "true"
const replaceKey = "eno.azure.io/replace"
res.Replace = anno[replaceKey] == "true"
const readinessGroupKey = "eno.azure.io/readiness-group"
if str, ok := anno[readinessGroupKey]; ok {
rg, err := strconv.Atoi(str)
if err != nil {
logger.V(0).Info("invalid readiness group - ignoring")
} else {
res.ReadinessGroup = rg
}
}
for key, value := range anno {
if !strings.HasPrefix(key, "eno.azure.io/readiness") || key == readinessGroupKey {
continue
}
name := strings.TrimPrefix(key, "eno.azure.io/readiness-")
if name == "eno.azure.io/readiness" {
name = "default"
}
check, err := readiness.ParseCheck(value)
if err != nil {
logger.Error(err, "invalid cel expression")
continue
}
check.Name = name
res.ReadinessChecks = append(res.ReadinessChecks, check)
}
sort.Slice(res.ReadinessChecks, func(i, j int) bool { return res.ReadinessChecks[i].Name < res.ReadinessChecks[j].Name })
parsed.SetAnnotations(pruneMetadata(parsed.GetAnnotations()))
parsed.SetLabels(pruneMetadata(parsed.GetLabels()))
return res, nil
}