diff --git a/api/v1beta1/status.go b/api/v1beta1/status.go index c7bef7f0..d2eca4bd 100644 --- a/api/v1beta1/status.go +++ b/api/v1beta1/status.go @@ -18,8 +18,24 @@ package v1beta1 import ( corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +// FailedCluster describes an error encountered while trying to +// manage resources for a specific matching cluster. +type FailedCluster struct { + // ClusterRef is a reference to the cluster where the failure occurred. + ClusterRef corev1.ObjectReference `json:"clusterRef"` + + // FailureMessage is a human-readable explanation of why the + // ClusterSummary creation/update failed. + FailureMessage string `json:"failureMessage"` + + // LastFailureTime is the time the error occurred. + // +optional + LastFailureTime *metav1.Time `json:"lastFailureTime,omitempty"` +} + // Status defines the observed state of ClusterProfile/Profile type Status struct { // MatchingClusterRefs reference all the clusters currently matching @@ -38,6 +54,11 @@ type Status struct { // +optional UpdatedClusters Clusters `json:"updatedClusters,omitempty"` + // FailedClusters contains information about clusters for which + // a ClusterSummary could not be created or updated. + // +optional + FailedClusters []FailedCluster `json:"failedClusters,omitempty"` + // DependenciesHash is a hash representing the set of clusters where this ClusterProfile // must be deployed, based on the combined configuration of its dependencies. DependenciesHash []byte `json:"dependenciesHash,omitempty"` diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index ec78f38d..9241f874 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -675,6 +675,26 @@ func (in *DryRunReconciliationError) DeepCopy() *DryRunReconciliationError { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FailedCluster) DeepCopyInto(out *FailedCluster) { + *out = *in + out.ClusterRef = in.ClusterRef + if in.LastFailureTime != nil { + in, out := &in.LastFailureTime, &out.LastFailureTime + *out = (*in).DeepCopy() + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FailedCluster. +func (in *FailedCluster) DeepCopy() *FailedCluster { + if in == nil { + return nil + } + out := new(FailedCluster) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Feature) DeepCopyInto(out *Feature) { *out = *in @@ -1367,6 +1387,13 @@ func (in *Status) DeepCopyInto(out *Status) { } in.UpdatingClusters.DeepCopyInto(&out.UpdatingClusters) in.UpdatedClusters.DeepCopyInto(&out.UpdatedClusters) + if in.FailedClusters != nil { + in, out := &in.FailedClusters, &out.FailedClusters + *out = make([]FailedCluster, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } if in.DependenciesHash != nil { in, out := &in.DependenciesHash, &out.DependenciesHash *out = make([]byte, len(*in)) diff --git a/config/crd/bases/config.projectsveltos.io_clusterprofiles.yaml b/config/crd/bases/config.projectsveltos.io_clusterprofiles.yaml index 09725d92..b0cb5d86 100644 --- a/config/crd/bases/config.projectsveltos.io_clusterprofiles.yaml +++ b/config/crd/bases/config.projectsveltos.io_clusterprofiles.yaml @@ -1482,6 +1482,73 @@ spec: must be deployed, based on the combined configuration of its dependencies. format: byte type: string + failedClusters: + description: |- + FailedClusters contains information about clusters for which + a ClusterSummary could not be created or updated. + items: + description: |- + FailedCluster describes an error encountered while trying to + manage resources for a specific matching cluster. + properties: + clusterRef: + description: ClusterRef is a reference to the cluster where + the failure occurred. + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: |- + If referring to a piece of an object instead of an entire object, this string + should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. + For example, if the object reference is to a container within a pod, this would take on a value like: + "spec.containers{name}" (where "name" refers to the name of the container that triggered + the event) or if no container name is specified "spec.containers[2]" (container with + index 2 in this pod). This syntax is chosen only to have some well-defined way of + referencing a part of an object. + type: string + kind: + description: |- + Kind of the referent. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + namespace: + description: |- + Namespace of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ + type: string + resourceVersion: + description: |- + Specific resourceVersion to which this reference is made, if any. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency + type: string + uid: + description: |- + UID of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids + type: string + type: object + x-kubernetes-map-type: atomic + failureMessage: + description: |- + FailureMessage is a human-readable explanation of why the + ClusterSummary creation/update failed. + type: string + lastFailureTime: + description: LastFailureTime is the time the error occurred. + format: date-time + type: string + required: + - clusterRef + - failureMessage + type: object + type: array matchingClusters: description: |- MatchingClusterRefs reference all the clusters currently matching diff --git a/config/crd/bases/config.projectsveltos.io_profiles.yaml b/config/crd/bases/config.projectsveltos.io_profiles.yaml index 13fdaabf..8aed3943 100644 --- a/config/crd/bases/config.projectsveltos.io_profiles.yaml +++ b/config/crd/bases/config.projectsveltos.io_profiles.yaml @@ -1482,6 +1482,73 @@ spec: must be deployed, based on the combined configuration of its dependencies. format: byte type: string + failedClusters: + description: |- + FailedClusters contains information about clusters for which + a ClusterSummary could not be created or updated. + items: + description: |- + FailedCluster describes an error encountered while trying to + manage resources for a specific matching cluster. + properties: + clusterRef: + description: ClusterRef is a reference to the cluster where + the failure occurred. + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: |- + If referring to a piece of an object instead of an entire object, this string + should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. + For example, if the object reference is to a container within a pod, this would take on a value like: + "spec.containers{name}" (where "name" refers to the name of the container that triggered + the event) or if no container name is specified "spec.containers[2]" (container with + index 2 in this pod). This syntax is chosen only to have some well-defined way of + referencing a part of an object. + type: string + kind: + description: |- + Kind of the referent. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + namespace: + description: |- + Namespace of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ + type: string + resourceVersion: + description: |- + Specific resourceVersion to which this reference is made, if any. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency + type: string + uid: + description: |- + UID of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids + type: string + type: object + x-kubernetes-map-type: atomic + failureMessage: + description: |- + FailureMessage is a human-readable explanation of why the + ClusterSummary creation/update failed. + type: string + lastFailureTime: + description: LastFailureTime is the time the error occurred. + format: date-time + type: string + required: + - clusterRef + - failureMessage + type: object + type: array matchingClusters: description: |- MatchingClusterRefs reference all the clusters currently matching diff --git a/config/rbac/kustomization.yaml b/config/rbac/kustomization.yaml index 3f5abd4d..215d3d80 100644 --- a/config/rbac/kustomization.yaml +++ b/config/rbac/kustomization.yaml @@ -19,13 +19,14 @@ resources: # default, aiding admins in cluster management. Those roles are # not used by the Project itself. You can comment the following lines # if you do not want those helpers be installed with your Project. -- profile_editor_role.yaml -- profile_viewer_role.yaml -- clusterconfiguration_editor_role.yaml -- clusterconfiguration_viewer_role.yaml -- clustersummary_editor_role.yaml -- clustersummary_viewer_role.yaml -- clusterreport_editor_role.yaml -- clusterreport_viewer_role.yaml -- clusterprofile_editor_role.yaml -- clusterprofile_viewer_role.yaml +# - profile_editor_role.yaml +# - profile_viewer_role.yaml +# - clusterconfiguration_editor_role.yaml +# - clusterconfiguration_viewer_role.yaml +# - clustersummary_editor_role.yaml +# - clustersummary_viewer_role.yaml +# - clustersummary_viewer_role.yaml +# - clusterreport_editor_role.yaml +# - clusterreport_viewer_role.yaml +# - clusterprofile_editor_role.yaml +# - clusterprofile_viewer_role.yaml diff --git a/controllers/profile_utils.go b/controllers/profile_utils.go index b83ed086..bd1fb0af 100644 --- a/controllers/profile_utils.go +++ b/controllers/profile_utils.go @@ -752,7 +752,13 @@ func updateClusterSummaryInstanceForCluster(ctx context.Context, c client.Client // If a Cluster exists and it is a match, ClusterSummary is created (and ClusterSummary.Spec kept in sync if mode is // continuous). // ClusterSummary won't program cluster in paused state. - return false, patchClusterSummary(ctx, c, profileScope, cluster, logger) + err = patchClusterSummary(ctx, c, profileScope, cluster, logger) + if err != nil { + profileScope.SetFailedClusters(cluster, err) + } else { + profileScope.ClearFailedClusters(cluster) + } + return false, err } func patchClusterSummary(ctx context.Context, c client.Client, profileScope *scope.ProfileScope, @@ -769,6 +775,7 @@ func patchClusterSummary(ctx context.Context, c client.Client, profileScope *sco err = createClusterSummary(ctx, c, profileScope, cluster) if err != nil { logger.Error(err, "failed to create ClusterSummary") + return err } } else { logger.Error(err, "failed to get ClusterSummary") diff --git a/manifest/manifest.yaml b/manifest/manifest.yaml index 4d4e3903..4c166bcf 100644 --- a/manifest/manifest.yaml +++ b/manifest/manifest.yaml @@ -1791,6 +1791,73 @@ spec: must be deployed, based on the combined configuration of its dependencies. format: byte type: string + failedClusters: + description: |- + FailedClusters contains information about clusters for which + a ClusterSummary could not be created or updated. + items: + description: |- + FailedCluster describes an error encountered while trying to + manage resources for a specific matching cluster. + properties: + clusterRef: + description: ClusterRef is a reference to the cluster where + the failure occurred. + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: |- + If referring to a piece of an object instead of an entire object, this string + should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. + For example, if the object reference is to a container within a pod, this would take on a value like: + "spec.containers{name}" (where "name" refers to the name of the container that triggered + the event) or if no container name is specified "spec.containers[2]" (container with + index 2 in this pod). This syntax is chosen only to have some well-defined way of + referencing a part of an object. + type: string + kind: + description: |- + Kind of the referent. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + namespace: + description: |- + Namespace of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ + type: string + resourceVersion: + description: |- + Specific resourceVersion to which this reference is made, if any. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency + type: string + uid: + description: |- + UID of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids + type: string + type: object + x-kubernetes-map-type: atomic + failureMessage: + description: |- + FailureMessage is a human-readable explanation of why the + ClusterSummary creation/update failed. + type: string + lastFailureTime: + description: LastFailureTime is the time the error occurred. + format: date-time + type: string + required: + - clusterRef + - failureMessage + type: object + type: array matchingClusters: description: |- MatchingClusterRefs reference all the clusters currently matching @@ -7246,6 +7313,73 @@ spec: must be deployed, based on the combined configuration of its dependencies. format: byte type: string + failedClusters: + description: |- + FailedClusters contains information about clusters for which + a ClusterSummary could not be created or updated. + items: + description: |- + FailedCluster describes an error encountered while trying to + manage resources for a specific matching cluster. + properties: + clusterRef: + description: ClusterRef is a reference to the cluster where + the failure occurred. + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: |- + If referring to a piece of an object instead of an entire object, this string + should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. + For example, if the object reference is to a container within a pod, this would take on a value like: + "spec.containers{name}" (where "name" refers to the name of the container that triggered + the event) or if no container name is specified "spec.containers[2]" (container with + index 2 in this pod). This syntax is chosen only to have some well-defined way of + referencing a part of an object. + type: string + kind: + description: |- + Kind of the referent. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + namespace: + description: |- + Namespace of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ + type: string + resourceVersion: + description: |- + Specific resourceVersion to which this reference is made, if any. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency + type: string + uid: + description: |- + UID of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids + type: string + type: object + x-kubernetes-map-type: atomic + failureMessage: + description: |- + FailureMessage is a human-readable explanation of why the + ClusterSummary creation/update failed. + type: string + lastFailureTime: + description: LastFailureTime is the time the error occurred. + format: date-time + type: string + required: + - clusterRef + - failureMessage + type: object + type: array matchingClusters: description: |- MatchingClusterRefs reference all the clusters currently matching @@ -7461,182 +7595,6 @@ rules: --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole -metadata: - name: addon-clusterconfiguration-editor-role -rules: -- apiGroups: - - config.projectsveltos.io - resources: - - clusterconfigurations - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - config.projectsveltos.io - resources: - - clusterconfigurations/status - verbs: - - get ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: addon-clusterconfiguration-viewer-role -rules: -- apiGroups: - - config.projectsveltos.io - resources: - - clusterconfigurations - verbs: - - get - - list - - watch -- apiGroups: - - config.projectsveltos.io - resources: - - clusterconfigurations/status - verbs: - - get ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: addon-clusterprofile-editor-role -rules: -- apiGroups: - - config.projectsveltos.io - resources: - - clusterprofiles - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - config.projectsveltos.io - resources: - - clusterprofiles/status - verbs: - - get ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: addon-clusterprofile-viewer-role -rules: -- apiGroups: - - config.projectsveltos.io - resources: - - clusterprofiles - verbs: - - get - - list - - watch -- apiGroups: - - config.projectsveltos.io - resources: - - clusterprofiles/status - verbs: - - get ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: addon-clusterreport-editor-role -rules: -- apiGroups: - - config.projectsveltos.io - resources: - - clusterreports - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - config.projectsveltos.io - resources: - - clusterreports/status - verbs: - - get ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: addon-clusterreport-viewer-role -rules: -- apiGroups: - - config.projectsveltos.io - resources: - - clusterreports - verbs: - - get - - list - - watch -- apiGroups: - - config.projectsveltos.io - resources: - - clusterreports/status - verbs: - - get ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: addon-clustersummary-editor-role -rules: -- apiGroups: - - config.projectsveltos.io - resources: - - clustersummaries - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - config.projectsveltos.io - resources: - - clustersummaries/status - verbs: - - get ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: addon-clustersummary-viewer-role -rules: -- apiGroups: - - config.projectsveltos.io - resources: - - clustersummaries - verbs: - - get - - list - - watch -- apiGroups: - - config.projectsveltos.io - resources: - - clustersummaries/status - verbs: - - get ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole metadata: name: addon-controller-role rules: @@ -7876,64 +7834,6 @@ metadata: name: addon-controller-role-extra --- apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - labels: - app.kubernetes.io/component: rbac - app.kubernetes.io/created-by: addon-controller - app.kubernetes.io/instance: profile-editor-role - app.kubernetes.io/managed-by: kustomize - app.kubernetes.io/name: clusterrole - app.kubernetes.io/part-of: addon-controller - name: addon-profile-editor-role -rules: -- apiGroups: - - config.projectsveltos.io - resources: - - profiles - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - config.projectsveltos.io - resources: - - profiles/status - verbs: - - get ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - labels: - app.kubernetes.io/component: rbac - app.kubernetes.io/created-by: addon-controller - app.kubernetes.io/instance: profile-viewer-role - app.kubernetes.io/managed-by: kustomize - app.kubernetes.io/name: clusterrole - app.kubernetes.io/part-of: addon-controller - name: addon-profile-viewer-role -rules: -- apiGroups: - - config.projectsveltos.io - resources: - - profiles - verbs: - - get - - list - - watch -- apiGroups: - - config.projectsveltos.io - resources: - - profiles/status - verbs: - - get ---- -apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: addon-controller-rolebinding-extra diff --git a/pkg/scope/clusterprofile.go b/pkg/scope/clusterprofile.go index 34e55afb..dddcbbab 100644 --- a/pkg/scope/clusterprofile.go +++ b/pkg/scope/clusterprofile.go @@ -113,6 +113,49 @@ func (s *ProfileScope) GetSelector() *metav1.LabelSelector { return &spec.ClusterSelector.LabelSelector } +func (s *ProfileScope) SetFailedClusters(cluster *corev1.ObjectReference, err error) { + status := s.GetStatus() + now := metav1.Now() + + for i := range status.FailedClusters { + fc := status.FailedClusters[i] + if fc.ClusterRef.Namespace == cluster.Namespace && + fc.ClusterRef.Name == cluster.Name && + fc.ClusterRef.Kind == cluster.Kind && + fc.ClusterRef.APIVersion == cluster.APIVersion { + + status.FailedClusters[i].FailureMessage = err.Error() + status.FailedClusters[i].LastFailureTime = &now + return + } + } + + newEntry := configv1beta1.FailedCluster{ + ClusterRef: *cluster, + FailureMessage: err.Error(), + LastFailureTime: &now, // Assign the pointer + } + + status.FailedClusters = append(status.FailedClusters, newEntry) +} + +func (s *ProfileScope) ClearFailedClusters(cluster *corev1.ObjectReference) { + status := s.GetStatus() + + for i := range status.FailedClusters { + fc := status.FailedClusters[i] + if fc.ClusterRef.Namespace == cluster.Namespace && + fc.ClusterRef.Name == cluster.Name && + fc.ClusterRef.Kind == cluster.Kind && + fc.ClusterRef.APIVersion == cluster.APIVersion { + + // Remove this entry by slicing it out + status.FailedClusters = append(status.FailedClusters[:i], status.FailedClusters[i+1:]...) + return + } + } +} + // SetMatchingClusterRefs sets the feature status. func (s *ProfileScope) SetMatchingClusterRefs(matchingClusters []corev1.ObjectReference) { status := s.GetStatus()