From bfecec36435a6e6ca86dc4317f50b17079bb30ea Mon Sep 17 00:00:00 2001 From: Bowen Sun Date: Fri, 18 Apr 2025 16:36:18 -0700 Subject: [PATCH 01/10] add HumioGroup CRUD to operator --- .gitignore | 3 +- api/v1alpha1/humiogroup_types.go | 68 +++ api/v1alpha1/zz_generated.deepcopy.go | 94 ++++ .../crds/core.humio.com_humiogroups.yaml | 85 ++++ cmd/main.go | 14 + .../crd/bases/core.humio.com_humiogroups.yaml | 85 ++++ config/crd/kustomization.yaml | 3 + .../patches/cainjection_in_humiogroups.yaml | 8 + .../crd/patches/webhook_in_humiogroups.yaml | 17 + config/rbac/humiogroup_admin_role.yaml | 27 ++ config/rbac/humiogroup_editor_role.yaml | 24 + config/rbac/humiogroup_viewer_role.yaml | 20 + config/rbac/role.yaml | 3 + config/samples/core_v1alpha1_humiogroup.yaml | 8 + internal/api/error.go | 8 + internal/api/humiographql/genqlient.yaml | 1 + .../api/humiographql/graphql/groups.graphql | 61 +++ internal/api/humiographql/humiographql.go | 421 ++++++++++++++++++ internal/controller/humiogroup_controller.go | 188 ++++++++ internal/humio/client.go | 97 ++++ internal/humio/client_mock.go | 73 +++ 21 files changed, 1307 insertions(+), 1 deletion(-) create mode 100644 api/v1alpha1/humiogroup_types.go create mode 100644 charts/humio-operator/crds/core.humio.com_humiogroups.yaml create mode 100644 config/crd/bases/core.humio.com_humiogroups.yaml create mode 100644 config/crd/patches/cainjection_in_humiogroups.yaml create mode 100644 config/crd/patches/webhook_in_humiogroups.yaml create mode 100644 config/rbac/humiogroup_admin_role.yaml create mode 100644 config/rbac/humiogroup_editor_role.yaml create mode 100644 config/rbac/humiogroup_viewer_role.yaml create mode 100644 config/samples/core_v1alpha1_humiogroup.yaml create mode 100644 internal/api/humiographql/graphql/groups.graphql create mode 100644 internal/controller/humiogroup_controller.go diff --git a/.gitignore b/.gitignore index 0d80e1790..6259c65b6 100644 --- a/.gitignore +++ b/.gitignore @@ -84,4 +84,5 @@ bin/ testbin/ *-junit.xml .envrc -tmp/** \ No newline at end of file +tmp/** +humio-operator.iml diff --git a/api/v1alpha1/humiogroup_types.go b/api/v1alpha1/humiogroup_types.go new file mode 100644 index 000000000..1ff4b1db8 --- /dev/null +++ b/api/v1alpha1/humiogroup_types.go @@ -0,0 +1,68 @@ +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + // HumioGroupStateUnknown is the Unknown state of the group + HumioGroupStateUnknown = "Unknown" + // HumioGroupStateExists is the Exists state of the group + HumioGroupStateExists = "Exists" + // HumioGroupStateNotFound is the NotFound state of the group + HumioGroupStateNotFound = "NotFound" + // HumioGroupStateConfigError is the state of the group when user-provided specification results in configuration error, such as non-existent humio cluster + HumioGroupStateConfigError = "ConfigError" +) + +// HumioGroupSpec defines the desired state of HumioGroup. +type HumioGroupSpec struct { + // ManagedClusterName refers to an object of type HumioCluster that is managed by the operator where the Humio + // resources should be created. + // This conflicts with ExternalClusterName. + ManagedClusterName string `json:"managedClusterName,omitempty"` + // ExternalClusterName refers to an object of type HumioExternalCluster where the Humio resources should be created. + // This conflicts with ManagedClusterName. + ExternalClusterName string `json:"externalClusterName,omitempty"` + // DisplayName is the display name of the HumioGroup + // +kubebuilder:validation:MinLength=1 + // +required + DisplayName string `json:"displayName"` + // LookupName is the lookup name of the HumioGroup + // +optional + LookupName *string `json:"lookupName,omitempty"` +} + +// HumioGroupStatus defines the observed state of HumioGroup. +type HumioGroupStatus struct { + // State reflects the current state of the HumioGroup + State string `json:"state,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:resource:path=humiogroups,scope=Namespaced +// +kubebuilder:printcolumn:name="State",type="string",JSONPath=".status.state",description="The state of the group" +// +operator-sdk:gen-csv:customresourcedefinitions.displayName="Humio Group" + +// HumioGroup is the Schema for the humiogroups API +type HumioGroup struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec HumioGroupSpec `json:"spec,omitempty"` + Status HumioGroupStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// HumioGroupList contains a list of HumioGroup +type HumioGroupList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []HumioGroup `json:"items"` +} + +func init() { + SchemeBuilder.Register(&HumioGroup{}, &HumioGroupList{}) +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 4801f4ac8..7c1ff7f65 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -1171,6 +1171,100 @@ func (in *HumioFilterAlertStatus) DeepCopy() *HumioFilterAlertStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HumioGroup) DeepCopyInto(out *HumioGroup) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HumioGroup. +func (in *HumioGroup) DeepCopy() *HumioGroup { + if in == nil { + return nil + } + out := new(HumioGroup) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *HumioGroup) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HumioGroupList) DeepCopyInto(out *HumioGroupList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]HumioGroup, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HumioGroupList. +func (in *HumioGroupList) DeepCopy() *HumioGroupList { + if in == nil { + return nil + } + out := new(HumioGroupList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *HumioGroupList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HumioGroupSpec) DeepCopyInto(out *HumioGroupSpec) { + *out = *in + if in.LookupName != nil { + in, out := &in.LookupName, &out.LookupName + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HumioGroupSpec. +func (in *HumioGroupSpec) DeepCopy() *HumioGroupSpec { + if in == nil { + return nil + } + out := new(HumioGroupSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HumioGroupStatus) DeepCopyInto(out *HumioGroupStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HumioGroupStatus. +func (in *HumioGroupStatus) DeepCopy() *HumioGroupStatus { + if in == nil { + return nil + } + out := new(HumioGroupStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HumioHashedTokenSecretSpec) DeepCopyInto(out *HumioHashedTokenSecretSpec) { *out = *in diff --git a/charts/humio-operator/crds/core.humio.com_humiogroups.yaml b/charts/humio-operator/crds/core.humio.com_humiogroups.yaml new file mode 100644 index 000000000..5c3a38a33 --- /dev/null +++ b/charts/humio-operator/crds/core.humio.com_humiogroups.yaml @@ -0,0 +1,85 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.0 + name: humiogroups.core.humio.com + labels: + app: 'humio-operator' + app.kubernetes.io/name: 'humio-operator' + app.kubernetes.io/instance: 'humio-operator' + app.kubernetes.io/managed-by: 'Helm' + helm.sh/chart: 'humio-operator-0.28.2' +spec: + group: core.humio.com + names: + kind: HumioGroup + listKind: HumioGroupList + plural: humiogroups + singular: humiogroup + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The state of the group + jsonPath: .status.state + name: State + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: HumioGroup is the Schema for the humiogroups API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: HumioGroupSpec defines the desired state of HumioGroup. + properties: + displayName: + description: DisplayName is the display name of the HumioGroup + minLength: 1 + type: string + externalClusterName: + description: |- + ExternalClusterName refers to an object of type HumioExternalCluster where the Humio resources should be created. + This conflicts with ManagedClusterName. + type: string + lookupName: + description: LookupName is the lookup name of the HumioGroup + type: string + managedClusterName: + description: |- + ManagedClusterName refers to an object of type HumioCluster that is managed by the operator where the Humio + resources should be created. + This conflicts with ExternalClusterName. + type: string + required: + - displayName + type: object + status: + description: HumioGroupStatus defines the observed state of HumioGroup. + properties: + state: + description: State reflects the current state of the HumioGroup + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/cmd/main.go b/cmd/main.go index 31649e033..04df47926 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -32,7 +32,10 @@ import ( "github.com/go-logr/zapr" uberzap "go.uber.org/zap" + "github.com/humio/humio-operator/internal/controller" "github.com/humio/humio-operator/internal/helpers" + "github.com/humio/humio-operator/internal/humio" + // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) // to ensure that exec-entrypoint and run can make use of them. _ "k8s.io/client-go/plugin/pkg/client/auth" @@ -418,4 +421,15 @@ func setupControllers(mgr ctrl.Manager, log logr.Logger, requeuePeriod time.Dura ctrl.Log.Error(err, "unable to create controller", "controller", "HumioUser") os.Exit(1) } + if err = (&controller.HumioGroupReconciler{ + Client: mgr.GetClient(), + CommonConfig: controller.CommonConfig{ + RequeuePeriod: requeuePeriod, + }, + HumioClient: humio.NewClient(log, userAgent), + BaseLogger: log, + }).SetupWithManager(mgr); err != nil { + ctrl.Log.Error(err, "unable to create controller", "controller", "HumioGroup") + os.Exit(1) + } } diff --git a/config/crd/bases/core.humio.com_humiogroups.yaml b/config/crd/bases/core.humio.com_humiogroups.yaml new file mode 100644 index 000000000..5c3a38a33 --- /dev/null +++ b/config/crd/bases/core.humio.com_humiogroups.yaml @@ -0,0 +1,85 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.0 + name: humiogroups.core.humio.com + labels: + app: 'humio-operator' + app.kubernetes.io/name: 'humio-operator' + app.kubernetes.io/instance: 'humio-operator' + app.kubernetes.io/managed-by: 'Helm' + helm.sh/chart: 'humio-operator-0.28.2' +spec: + group: core.humio.com + names: + kind: HumioGroup + listKind: HumioGroupList + plural: humiogroups + singular: humiogroup + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The state of the group + jsonPath: .status.state + name: State + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: HumioGroup is the Schema for the humiogroups API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: HumioGroupSpec defines the desired state of HumioGroup. + properties: + displayName: + description: DisplayName is the display name of the HumioGroup + minLength: 1 + type: string + externalClusterName: + description: |- + ExternalClusterName refers to an object of type HumioExternalCluster where the Humio resources should be created. + This conflicts with ManagedClusterName. + type: string + lookupName: + description: LookupName is the lookup name of the HumioGroup + type: string + managedClusterName: + description: |- + ManagedClusterName refers to an object of type HumioCluster that is managed by the operator where the Humio + resources should be created. + This conflicts with ExternalClusterName. + type: string + required: + - displayName + type: object + status: + description: HumioGroupStatus defines the observed state of HumioGroup. + properties: + state: + description: State reflects the current state of the HumioGroup + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 6537b3168..604fa39ed 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -8,6 +8,7 @@ resources: - bases/core.humio.com_humioparsers.yaml - bases/core.humio.com_humiorepositories.yaml - bases/core.humio.com_humioviews.yaml +- bases/core.humio.com_humiogroups.yaml - bases/core.humio.com_humioactions.yaml - bases/core.humio.com_humioalerts.yaml - bases/core.humio.com_humiofeatureflags.yaml @@ -27,6 +28,7 @@ patchesStrategicMerge: #- patches/webhook_in_humioparsers.yaml #- patches/webhook_in_humiorepositories.yaml #- patches/webhook_in_humioviews.yaml +#- patches/webhook_in_humiogroups.yaml #- patches/webhook_in_humioactions.yaml #- patches/webhook_in_humioalerts.yaml #- patches/webhook_in_humiofilteralerts.yaml @@ -42,6 +44,7 @@ patchesStrategicMerge: #- patches/cainjection_in_humioparsers.yaml #- patches/cainjection_in_humiorepositories.yaml #- patches/cainjection_in_humioviews.yaml +#- patches/cainjection_in_humiogroups.yaml #- patches/cainjection_in_humioactions.yaml #- patches/cainjection_in_humioalerts.yaml #- patches/cainjection_in_humiofilteralerts.yaml diff --git a/config/crd/patches/cainjection_in_humiogroups.yaml b/config/crd/patches/cainjection_in_humiogroups.yaml new file mode 100644 index 000000000..1d26d6340 --- /dev/null +++ b/config/crd/patches/cainjection_in_humiogroups.yaml @@ -0,0 +1,8 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +# CRD conversion requires k8s 1.13 or later. +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: humiogroups.core.humio.com diff --git a/config/crd/patches/webhook_in_humiogroups.yaml b/config/crd/patches/webhook_in_humiogroups.yaml new file mode 100644 index 000000000..478fdd04c --- /dev/null +++ b/config/crd/patches/webhook_in_humiogroups.yaml @@ -0,0 +1,17 @@ +# The following patch enables conversion webhook for CRD +# CRD conversion requires k8s 1.13 or later. +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: humiogroups.core.humio.com +spec: + conversion: + strategy: Webhook + webhookClientConfig: + # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank, + # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager) + caBundle: Cg== + service: + namespace: system + name: webhook-service + path: /convert diff --git a/config/rbac/humiogroup_admin_role.yaml b/config/rbac/humiogroup_admin_role.yaml new file mode 100644 index 000000000..c467cefb0 --- /dev/null +++ b/config/rbac/humiogroup_admin_role.yaml @@ -0,0 +1,27 @@ +# This rule is not used by the project humio-operator itself. +# It is provided to allow the cluster admin to help manage permissions for users. +# +# Grants full permissions ('*') over core.humio.com. +# This role is intended for users authorized to modify roles and bindings within the cluster, +# enabling them to delegate specific permissions to other users or groups as needed. + +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: humio-operator + app.kubernetes.io/managed-by: kustomize + name: humiogroup-admin-role +rules: +- apiGroups: + - core.humio.com + resources: + - humiogroups + verbs: + - '*' +- apiGroups: + - core.humio.com + resources: + - humiogroups/status + verbs: + - get diff --git a/config/rbac/humiogroup_editor_role.yaml b/config/rbac/humiogroup_editor_role.yaml new file mode 100644 index 000000000..8855dda50 --- /dev/null +++ b/config/rbac/humiogroup_editor_role.yaml @@ -0,0 +1,24 @@ +# permissions for end users to edit humiogroups. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: humiogroup-editor-role +rules: +- apiGroups: + - core.humio.com + resources: + - humiogroups + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - core.humio.com + resources: + - humiogroups/status + verbs: + - get diff --git a/config/rbac/humiogroup_viewer_role.yaml b/config/rbac/humiogroup_viewer_role.yaml new file mode 100644 index 000000000..0955e73e7 --- /dev/null +++ b/config/rbac/humiogroup_viewer_role.yaml @@ -0,0 +1,20 @@ +# permissions for end users to view humiogroups. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: humiogroup-viewer-role +rules: +- apiGroups: + - core.humio.com + resources: + - humiogroups + verbs: + - get + - list + - watch +- apiGroups: + - core.humio.com + resources: + - humiogroups/status + verbs: + - get diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 58316209e..247c6573e 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -35,6 +35,7 @@ rules: - humioexternalclusters - humiofeatureflags - humiofilteralerts + - humiogroups - humioingesttokens - humioparsers - humiorepositories @@ -60,6 +61,7 @@ rules: - humioexternalclusters/finalizers - humiofeatureflags/finalizers - humiofilteralerts/finalizers + - humiogroups/finalizers - humioingesttokens/finalizers - humioparsers/finalizers - humiorepositories/finalizers @@ -79,6 +81,7 @@ rules: - humioexternalclusters/status - humiofeatureflags/status - humiofilteralerts/status + - humiogroups/status - humioingesttokens/status - humioparsers/status - humiorepositories/status diff --git a/config/samples/core_v1alpha1_humiogroup.yaml b/config/samples/core_v1alpha1_humiogroup.yaml new file mode 100644 index 000000000..4e1e64e00 --- /dev/null +++ b/config/samples/core_v1alpha1_humiogroup.yaml @@ -0,0 +1,8 @@ +apiVersion: core.humio.com/v1alpha1 +kind: HumioGroup +metadata: + name: example-humiogroup-managed +spec: + managedClusterName: example-humiocluster + displayName: "example-group" + lookupName: "example-group-lookup-name" diff --git a/internal/api/error.go b/internal/api/error.go index 7af4756b2..b725bda23 100644 --- a/internal/api/error.go +++ b/internal/api/error.go @@ -10,6 +10,7 @@ const ( entityTypeSearchDomain entityType = "search-domain" entityTypeRepository entityType = "repository" entityTypeView entityType = "view" + entityTypeGroup entityType = "group" entityTypeIngestToken entityType = "ingest-token" entityTypeParser entityType = "parser" entityTypeAction entityType = "action" @@ -63,6 +64,13 @@ func ViewNotFound(name string) error { } } +func GroupNotFound(name string) error { + return EntityNotFound{ + entityType: entityTypeGroup, + key: name, + } +} + func IngestTokenNotFound(name string) error { return EntityNotFound{ entityType: entityTypeIngestToken, diff --git a/internal/api/humiographql/genqlient.yaml b/internal/api/humiographql/genqlient.yaml index 07b2c323a..b2aeb5ce9 100644 --- a/internal/api/humiographql/genqlient.yaml +++ b/internal/api/humiographql/genqlient.yaml @@ -7,6 +7,7 @@ operations: - graphql/feature-flags.graphql - graphql/filter-alerts.graphql - graphql/fragments.graphql + - graphql/groups.graphql - graphql/ingest-tokens.graphql - graphql/license.graphql - graphql/parsers.graphql diff --git a/internal/api/humiographql/graphql/groups.graphql b/internal/api/humiographql/graphql/groups.graphql new file mode 100644 index 000000000..cb9e7170a --- /dev/null +++ b/internal/api/humiographql/graphql/groups.graphql @@ -0,0 +1,61 @@ +fragment GroupDetails on Group { + id + displayName + lookupName +} + +query GetGroupByDisplayName( + $DisplayName: String! +) { + groupByDisplayName( + displayName: $DisplayName + ) { + ...GroupDetails + } +} + +mutation CreateGroup( + $DisplayName: String! + $LookupName: String +) { + addGroup( + displayName: $DisplayName + lookupName: $LookupName + ) { + group { + id + } + } +} + +mutation UpdateGroup( + $GroupId: String! + $DisplayName: String + $LookupName: String +) { + updateGroup( + input: { + groupId: $GroupId + displayName: $DisplayName + lookupName: $LookupName + } + ) { + group { + id + } + } +} + +mutation DeleteGroup( + $GroupId: String! +) { + removeGroup( + groupId: $GroupId + ) { + group { + displayName + userCount + searchDomainCount + } + } +} \ No newline at end of file diff --git a/internal/api/humiographql/humiographql.go b/internal/api/humiographql/humiographql.go index d034a1a11..89f7a4bcd 100644 --- a/internal/api/humiographql/humiographql.go +++ b/internal/api/humiographql/humiographql.go @@ -2123,6 +2123,39 @@ func (v *CreateFilterAlertResponse) GetCreateFilterAlert() CreateFilterAlertCrea return v.CreateFilterAlert } +// CreateGroupAddGroupAddGroupMutation includes the requested fields of the GraphQL type AddGroupMutation. +type CreateGroupAddGroupAddGroupMutation struct { + // Stability: Long-term + Group CreateGroupAddGroupAddGroupMutationGroup `json:"group"` +} + +// GetGroup returns CreateGroupAddGroupAddGroupMutation.Group, and is useful for accessing the field via an interface. +func (v *CreateGroupAddGroupAddGroupMutation) GetGroup() CreateGroupAddGroupAddGroupMutationGroup { + return v.Group +} + +// CreateGroupAddGroupAddGroupMutationGroup includes the requested fields of the GraphQL type Group. +// The GraphQL type's documentation follows. +// +// A group. +type CreateGroupAddGroupAddGroupMutationGroup struct { + // Stability: Long-term + Id string `json:"id"` +} + +// GetId returns CreateGroupAddGroupAddGroupMutationGroup.Id, and is useful for accessing the field via an interface. +func (v *CreateGroupAddGroupAddGroupMutationGroup) GetId() string { return v.Id } + +// CreateGroupResponse is returned by CreateGroup on success. +type CreateGroupResponse struct { + // Creates a new group. + // Stability: Long-term + AddGroup CreateGroupAddGroupAddGroupMutation `json:"addGroup"` +} + +// GetAddGroup returns CreateGroupResponse.AddGroup, and is useful for accessing the field via an interface. +func (v *CreateGroupResponse) GetAddGroup() CreateGroupAddGroupAddGroupMutation { return v.AddGroup } + // CreateHumioRepoActionCreateHumioRepoAction includes the requested fields of the GraphQL type HumioRepoAction. // The GraphQL type's documentation follows. // @@ -2923,6 +2956,55 @@ type DeleteFilterAlertResponse struct { // GetDeleteFilterAlert returns DeleteFilterAlertResponse.DeleteFilterAlert, and is useful for accessing the field via an interface. func (v *DeleteFilterAlertResponse) GetDeleteFilterAlert() bool { return v.DeleteFilterAlert } +// DeleteGroupRemoveGroupRemoveGroupMutation includes the requested fields of the GraphQL type RemoveGroupMutation. +type DeleteGroupRemoveGroupRemoveGroupMutation struct { + // Stability: Long-term + Group DeleteGroupRemoveGroupRemoveGroupMutationGroup `json:"group"` +} + +// GetGroup returns DeleteGroupRemoveGroupRemoveGroupMutation.Group, and is useful for accessing the field via an interface. +func (v *DeleteGroupRemoveGroupRemoveGroupMutation) GetGroup() DeleteGroupRemoveGroupRemoveGroupMutationGroup { + return v.Group +} + +// DeleteGroupRemoveGroupRemoveGroupMutationGroup includes the requested fields of the GraphQL type Group. +// The GraphQL type's documentation follows. +// +// A group. +type DeleteGroupRemoveGroupRemoveGroupMutationGroup struct { + // Stability: Long-term + DisplayName string `json:"displayName"` + // Stability: Long-term + UserCount int `json:"userCount"` + // Stability: Long-term + SearchDomainCount int `json:"searchDomainCount"` +} + +// GetDisplayName returns DeleteGroupRemoveGroupRemoveGroupMutationGroup.DisplayName, and is useful for accessing the field via an interface. +func (v *DeleteGroupRemoveGroupRemoveGroupMutationGroup) GetDisplayName() string { + return v.DisplayName +} + +// GetUserCount returns DeleteGroupRemoveGroupRemoveGroupMutationGroup.UserCount, and is useful for accessing the field via an interface. +func (v *DeleteGroupRemoveGroupRemoveGroupMutationGroup) GetUserCount() int { return v.UserCount } + +// GetSearchDomainCount returns DeleteGroupRemoveGroupRemoveGroupMutationGroup.SearchDomainCount, and is useful for accessing the field via an interface. +func (v *DeleteGroupRemoveGroupRemoveGroupMutationGroup) GetSearchDomainCount() int { + return v.SearchDomainCount +} + +// DeleteGroupResponse is returned by DeleteGroup on success. +type DeleteGroupResponse struct { + // Removes a group. Only usable if roles are not managed externally, e.g. in LDAP. + // Stability: Long-term + RemoveGroup DeleteGroupRemoveGroupRemoveGroupMutation `json:"removeGroup"` +} + +// GetRemoveGroup returns DeleteGroupResponse.RemoveGroup, and is useful for accessing the field via an interface. +func (v *DeleteGroupResponse) GetRemoveGroup() DeleteGroupRemoveGroupRemoveGroupMutation { + return v.RemoveGroup +} + // DeleteParserByIDDeleteParserBooleanResultType includes the requested fields of the GraphQL type BooleanResultType. type DeleteParserByIDDeleteParserBooleanResultType struct { Typename *string `json:"__typename"` @@ -5775,6 +5857,89 @@ func (v *GetFilterAlertByIDSearchDomainView) GetFilterAlert() GetFilterAlertByID return v.FilterAlert } +// GetGroupByDisplayNameGroupByDisplayNameGroup includes the requested fields of the GraphQL type Group. +// The GraphQL type's documentation follows. +// +// A group. +type GetGroupByDisplayNameGroupByDisplayNameGroup struct { + GroupDetails `json:"-"` +} + +// GetId returns GetGroupByDisplayNameGroupByDisplayNameGroup.Id, and is useful for accessing the field via an interface. +func (v *GetGroupByDisplayNameGroupByDisplayNameGroup) GetId() string { return v.GroupDetails.Id } + +// GetDisplayName returns GetGroupByDisplayNameGroupByDisplayNameGroup.DisplayName, and is useful for accessing the field via an interface. +func (v *GetGroupByDisplayNameGroupByDisplayNameGroup) GetDisplayName() string { + return v.GroupDetails.DisplayName +} + +// GetLookupName returns GetGroupByDisplayNameGroupByDisplayNameGroup.LookupName, and is useful for accessing the field via an interface. +func (v *GetGroupByDisplayNameGroupByDisplayNameGroup) GetLookupName() *string { + return v.GroupDetails.LookupName +} + +func (v *GetGroupByDisplayNameGroupByDisplayNameGroup) UnmarshalJSON(b []byte) error { + + if string(b) == "null" { + return nil + } + + var firstPass struct { + *GetGroupByDisplayNameGroupByDisplayNameGroup + graphql.NoUnmarshalJSON + } + firstPass.GetGroupByDisplayNameGroupByDisplayNameGroup = v + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + err = json.Unmarshal( + b, &v.GroupDetails) + if err != nil { + return err + } + return nil +} + +type __premarshalGetGroupByDisplayNameGroupByDisplayNameGroup struct { + Id string `json:"id"` + + DisplayName string `json:"displayName"` + + LookupName *string `json:"lookupName"` +} + +func (v *GetGroupByDisplayNameGroupByDisplayNameGroup) MarshalJSON() ([]byte, error) { + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err + } + return json.Marshal(premarshaled) +} + +func (v *GetGroupByDisplayNameGroupByDisplayNameGroup) __premarshalJSON() (*__premarshalGetGroupByDisplayNameGroupByDisplayNameGroup, error) { + var retval __premarshalGetGroupByDisplayNameGroupByDisplayNameGroup + + retval.Id = v.GroupDetails.Id + retval.DisplayName = v.GroupDetails.DisplayName + retval.LookupName = v.GroupDetails.LookupName + return &retval, nil +} + +// GetGroupByDisplayNameResponse is returned by GetGroupByDisplayName on success. +type GetGroupByDisplayNameResponse struct { + // Used to get information on groups by a given display name. + // Stability: Long-term + GroupByDisplayName GetGroupByDisplayNameGroupByDisplayNameGroup `json:"groupByDisplayName"` +} + +// GetGroupByDisplayName returns GetGroupByDisplayNameResponse.GroupByDisplayName, and is useful for accessing the field via an interface. +func (v *GetGroupByDisplayNameResponse) GetGroupByDisplayName() GetGroupByDisplayNameGroupByDisplayNameGroup { + return v.GroupByDisplayName +} + // GetLicenseInstalledLicense includes the requested fields of the GraphQL interface License. // // GetLicenseInstalledLicense is implemented by the following types: @@ -6938,6 +7103,28 @@ func (v *GetUsersByUsernameUsersUser) __premarshalJSON() (*__premarshalGetUsersB return &retval, nil } +// GroupDetails includes the GraphQL fields of Group requested by the fragment GroupDetails. +// The GraphQL type's documentation follows. +// +// A group. +type GroupDetails struct { + // Stability: Long-term + Id string `json:"id"` + // Stability: Long-term + DisplayName string `json:"displayName"` + // Stability: Long-term + LookupName *string `json:"lookupName"` +} + +// GetId returns GroupDetails.Id, and is useful for accessing the field via an interface. +func (v *GroupDetails) GetId() string { return v.Id } + +// GetDisplayName returns GroupDetails.DisplayName, and is useful for accessing the field via an interface. +func (v *GroupDetails) GetDisplayName() string { return v.DisplayName } + +// GetLookupName returns GroupDetails.LookupName, and is useful for accessing the field via an interface. +func (v *GroupDetails) GetLookupName() *string { return v.LookupName } + // Http(s) Header entry. type HttpHeaderEntryInput struct { // Http(s) Header entry. @@ -12729,6 +12916,41 @@ func (v *UpdateFilterAlertUpdateFilterAlert) __premarshalJSON() (*__premarshalUp return &retval, nil } +// UpdateGroupResponse is returned by UpdateGroup on success. +type UpdateGroupResponse struct { + // Updates the group. + // Stability: Long-term + UpdateGroup UpdateGroupUpdateGroupUpdateGroupMutation `json:"updateGroup"` +} + +// GetUpdateGroup returns UpdateGroupResponse.UpdateGroup, and is useful for accessing the field via an interface. +func (v *UpdateGroupResponse) GetUpdateGroup() UpdateGroupUpdateGroupUpdateGroupMutation { + return v.UpdateGroup +} + +// UpdateGroupUpdateGroupUpdateGroupMutation includes the requested fields of the GraphQL type UpdateGroupMutation. +type UpdateGroupUpdateGroupUpdateGroupMutation struct { + // Stability: Long-term + Group UpdateGroupUpdateGroupUpdateGroupMutationGroup `json:"group"` +} + +// GetGroup returns UpdateGroupUpdateGroupUpdateGroupMutation.Group, and is useful for accessing the field via an interface. +func (v *UpdateGroupUpdateGroupUpdateGroupMutation) GetGroup() UpdateGroupUpdateGroupUpdateGroupMutationGroup { + return v.Group +} + +// UpdateGroupUpdateGroupUpdateGroupMutationGroup includes the requested fields of the GraphQL type Group. +// The GraphQL type's documentation follows. +// +// A group. +type UpdateGroupUpdateGroupUpdateGroupMutationGroup struct { + // Stability: Long-term + Id string `json:"id"` +} + +// GetId returns UpdateGroupUpdateGroupUpdateGroupMutationGroup.Id, and is useful for accessing the field via an interface. +func (v *UpdateGroupUpdateGroupUpdateGroupMutationGroup) GetId() string { return v.Id } + // UpdateHumioRepoActionResponse is returned by UpdateHumioRepoAction on success. type UpdateHumioRepoActionResponse struct { // Update a LogScale repository action. @@ -13731,6 +13953,18 @@ func (v *__CreateFilterAlertInput) GetQueryOwnershipType() QueryOwnershipType { return v.QueryOwnershipType } +// __CreateGroupInput is used internally by genqlient +type __CreateGroupInput struct { + DisplayName string `json:"DisplayName"` + LookupName *string `json:"LookupName"` +} + +// GetDisplayName returns __CreateGroupInput.DisplayName, and is useful for accessing the field via an interface. +func (v *__CreateGroupInput) GetDisplayName() string { return v.DisplayName } + +// GetLookupName returns __CreateGroupInput.LookupName, and is useful for accessing the field via an interface. +func (v *__CreateGroupInput) GetLookupName() *string { return v.LookupName } + // __CreateHumioRepoActionInput is used internally by genqlient type __CreateHumioRepoActionInput struct { SearchDomainName string `json:"SearchDomainName"` @@ -14099,6 +14333,14 @@ func (v *__DeleteFilterAlertInput) GetSearchDomainName() string { return v.Searc // GetFilterAlertID returns __DeleteFilterAlertInput.FilterAlertID, and is useful for accessing the field via an interface. func (v *__DeleteFilterAlertInput) GetFilterAlertID() string { return v.FilterAlertID } +// __DeleteGroupInput is used internally by genqlient +type __DeleteGroupInput struct { + GroupId string `json:"GroupId"` +} + +// GetGroupId returns __DeleteGroupInput.GroupId, and is useful for accessing the field via an interface. +func (v *__DeleteGroupInput) GetGroupId() string { return v.GroupId } + // __DeleteParserByIDInput is used internally by genqlient type __DeleteParserByIDInput struct { RepositoryName string `json:"RepositoryName"` @@ -14203,6 +14445,14 @@ func (v *__GetFilterAlertByIDInput) GetSearchDomainName() string { return v.Sear // GetFilterAlertID returns __GetFilterAlertByIDInput.FilterAlertID, and is useful for accessing the field via an interface. func (v *__GetFilterAlertByIDInput) GetFilterAlertID() string { return v.FilterAlertID } +// __GetGroupByDisplayNameInput is used internally by genqlient +type __GetGroupByDisplayNameInput struct { + DisplayName string `json:"DisplayName"` +} + +// GetDisplayName returns __GetGroupByDisplayNameInput.DisplayName, and is useful for accessing the field via an interface. +func (v *__GetGroupByDisplayNameInput) GetDisplayName() string { return v.DisplayName } + // __GetParserByIDInput is used internally by genqlient type __GetParserByIDInput struct { RepositoryName string `json:"RepositoryName"` @@ -14613,6 +14863,22 @@ func (v *__UpdateFilterAlertInput) GetQueryOwnershipType() QueryOwnershipType { return v.QueryOwnershipType } +// __UpdateGroupInput is used internally by genqlient +type __UpdateGroupInput struct { + GroupId string `json:"GroupId"` + DisplayName *string `json:"DisplayName"` + LookupName *string `json:"LookupName"` +} + +// GetGroupId returns __UpdateGroupInput.GroupId, and is useful for accessing the field via an interface. +func (v *__UpdateGroupInput) GetGroupId() string { return v.GroupId } + +// GetDisplayName returns __UpdateGroupInput.DisplayName, and is useful for accessing the field via an interface. +func (v *__UpdateGroupInput) GetDisplayName() *string { return v.DisplayName } + +// GetLookupName returns __UpdateGroupInput.LookupName, and is useful for accessing the field via an interface. +func (v *__UpdateGroupInput) GetLookupName() *string { return v.LookupName } + // __UpdateHumioRepoActionInput is used internally by genqlient type __UpdateHumioRepoActionInput struct { SearchDomainName string `json:"SearchDomainName"` @@ -15378,6 +15644,44 @@ func CreateFilterAlert( return data_, err_ } +// The mutation executed by CreateGroup. +const CreateGroup_Operation = ` +mutation CreateGroup ($DisplayName: String!, $LookupName: String) { + addGroup(displayName: $DisplayName, lookupName: $LookupName) { + group { + id + } + } +} +` + +func CreateGroup( + ctx_ context.Context, + client_ graphql.Client, + DisplayName string, + LookupName *string, +) (data_ *CreateGroupResponse, err_ error) { + req_ := &graphql.Request{ + OpName: "CreateGroup", + Query: CreateGroup_Operation, + Variables: &__CreateGroupInput{ + DisplayName: DisplayName, + LookupName: LookupName, + }, + } + + data_ = &CreateGroupResponse{} + resp_ := &graphql.Response{Data: data_} + + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, + ) + + return data_, err_ +} + // The mutation executed by CreateHumioRepoAction. const CreateHumioRepoAction_Operation = ` mutation CreateHumioRepoAction ($SearchDomainName: String!, $ActionName: String!, $IngestToken: String!) { @@ -16105,6 +16409,44 @@ func DeleteFilterAlert( return data_, err_ } +// The mutation executed by DeleteGroup. +const DeleteGroup_Operation = ` +mutation DeleteGroup ($GroupId: String!) { + removeGroup(groupId: $GroupId) { + group { + displayName + userCount + searchDomainCount + } + } +} +` + +func DeleteGroup( + ctx_ context.Context, + client_ graphql.Client, + GroupId string, +) (data_ *DeleteGroupResponse, err_ error) { + req_ := &graphql.Request{ + OpName: "DeleteGroup", + Query: DeleteGroup_Operation, + Variables: &__DeleteGroupInput{ + GroupId: GroupId, + }, + } + + data_ = &DeleteGroupResponse{} + resp_ := &graphql.Response{Data: data_} + + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, + ) + + return data_, err_ +} + // The mutation executed by DeleteParserByID. const DeleteParserByID_Operation = ` mutation DeleteParserByID ($RepositoryName: RepoOrViewName!, $ParserID: String!) { @@ -16672,6 +17014,45 @@ func GetFilterAlertByID( return data_, err_ } +// The query executed by GetGroupByDisplayName. +const GetGroupByDisplayName_Operation = ` +query GetGroupByDisplayName ($DisplayName: String!) { + groupByDisplayName(displayName: $DisplayName) { + ... GroupDetails + } +} +fragment GroupDetails on Group { + id + displayName + lookupName +} +` + +func GetGroupByDisplayName( + ctx_ context.Context, + client_ graphql.Client, + DisplayName string, +) (data_ *GetGroupByDisplayNameResponse, err_ error) { + req_ := &graphql.Request{ + OpName: "GetGroupByDisplayName", + Query: GetGroupByDisplayName_Operation, + Variables: &__GetGroupByDisplayNameInput{ + DisplayName: DisplayName, + }, + } + + data_ = &GetGroupByDisplayNameResponse{} + resp_ := &graphql.Response{Data: data_} + + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, + ) + + return data_, err_ +} + // The query executed by GetLicense. const GetLicense_Operation = ` query GetLicense { @@ -18134,6 +18515,46 @@ func UpdateFilterAlert( return data_, err_ } +// The mutation executed by UpdateGroup. +const UpdateGroup_Operation = ` +mutation UpdateGroup ($GroupId: String!, $DisplayName: String, $LookupName: String) { + updateGroup(input: {groupId:$GroupId,displayName:$DisplayName,lookupName:$LookupName}) { + group { + id + } + } +} +` + +func UpdateGroup( + ctx_ context.Context, + client_ graphql.Client, + GroupId string, + DisplayName *string, + LookupName *string, +) (data_ *UpdateGroupResponse, err_ error) { + req_ := &graphql.Request{ + OpName: "UpdateGroup", + Query: UpdateGroup_Operation, + Variables: &__UpdateGroupInput{ + GroupId: GroupId, + DisplayName: DisplayName, + LookupName: LookupName, + }, + } + + data_ = &UpdateGroupResponse{} + resp_ := &graphql.Response{Data: data_} + + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, + ) + + return data_, err_ +} + // The mutation executed by UpdateHumioRepoAction. const UpdateHumioRepoAction_Operation = ` mutation UpdateHumioRepoAction ($SearchDomainName: String!, $ActionID: String!, $ActionName: String!, $IngestToken: String!) { diff --git a/internal/controller/humiogroup_controller.go b/internal/controller/humiogroup_controller.go new file mode 100644 index 000000000..e2c5f919b --- /dev/null +++ b/internal/controller/humiogroup_controller.go @@ -0,0 +1,188 @@ +package controller + +import ( + "context" + "errors" + "fmt" + "github.com/go-logr/logr" + "github.com/google/go-cmp/cmp" + humiov1alpha1 "github.com/humio/humio-operator/api/v1alpha1" + humioapi "github.com/humio/humio-operator/internal/api" + "github.com/humio/humio-operator/internal/api/humiographql" + "github.com/humio/humio-operator/internal/helpers" + "github.com/humio/humio-operator/internal/humio" + "github.com/humio/humio-operator/internal/kubernetes" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "time" +) + +// HumioGroupReconciler reconciles a HumioGroup object +type HumioGroupReconciler struct { + client.Client + BaseLogger logr.Logger + Log logr.Logger + HumioClient humio.Client + Namespace string +} + +// +kubebuilder:rbac:groups=core.humio.com,resources=humiogroups,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=core.humio.com,resources=humiogroups/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=core.humio.com,resources=humiogroups/finalizers,verbs=update + +func (r *HumioGroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + if r.Namespace != "" { + if r.Namespace != req.Namespace { + return reconcile.Result{}, nil + } + } + + r.Log = r.BaseLogger.WithValues("Request.Namespace", req.Namespace, "Request.Name", req.Name, "Request.Type", helpers.GetTypeName(r), "Reconcile.ID", kubernetes.RandomString()) + r.Log.Info("Reconciling HumioGroup") + + // Fetch the HumioGroup instance + hg := &humiov1alpha1.HumioGroup{} + err := r.Get(ctx, req.NamespacedName, hg) + if err != nil { + if k8serrors.IsNotFound(err) { + // Request object not found, could have been deleted after reconcile request. + // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. + // Return and don't requeue + return reconcile.Result{}, nil + } + // Error reading the object - requeue the request. + return reconcile.Result{}, err + } + + r.Log = r.Log.WithValues("Request.UID", hg.UID) + + cluster, err := helpers.NewCluster(ctx, r, hg.Spec.ManagedClusterName, hg.Spec.ExternalClusterName, hg.Namespace, helpers.UseCertManager(), true, false) + if err != nil || cluster == nil || cluster.Config() == nil { + setStateErr := r.setState(ctx, humiov1alpha1.HumioParserStateConfigError, hg) + if setStateErr != nil { + return reconcile.Result{}, r.logErrorAndReturn(setStateErr, "unable to set cluster state") + } + return reconcile.Result{RequeueAfter: 5 * time.Second}, r.logErrorAndReturn(err, "unable to obtain humio client config") + } + humioHttpClient := r.HumioClient.GetHumioHttpClient(cluster.Config(), req) + + // delete + r.Log.Info("checking if group is marked to be deleted") + isMarkedForDeletion := hg.GetDeletionTimestamp() != nil + if isMarkedForDeletion { + r.Log.Info("group marked to be deleted") + if helpers.ContainsElement(hg.GetFinalizers(), humioFinalizer) { + _, err := r.HumioClient.GetGroup(ctx, humioHttpClient, req, hg) + if errors.As(err, &humioapi.EntityNotFound{}) { + hg.SetFinalizers(helpers.RemoveElement(hg.GetFinalizers(), humioFinalizer)) + err := r.Update(ctx, hg) + if err != nil { + return reconcile.Result{}, err + } + r.Log.Info("Finalizer removed successfully") + return reconcile.Result{Requeue: true}, nil + } + + // Run finalization logic for humioFinalizer. If the + // finalization logic fails, don't remove the finalizer so + // that we can retry during the next reconciliation. + r.Log.Info("Deleting Group") + if err := r.HumioClient.DeleteGroup(ctx, humioHttpClient, req, hg); err != nil { + return reconcile.Result{}, r.logErrorAndReturn(err, "Delete group returned error") + } + } + return reconcile.Result{}, nil + } + + // Add finalizer for this CR + if !helpers.ContainsElement(hg.GetFinalizers(), humioFinalizer) { + r.Log.Info("Finalizer not present, adding finalizer to group") + hg.SetFinalizers(append(hg.GetFinalizers(), humioFinalizer)) + err := r.Update(ctx, hg) + if err != nil { + return reconcile.Result{}, err + } + } + defer func(ctx context.Context, hg *humiov1alpha1.HumioGroup) { + _, err := r.HumioClient.GetGroup(ctx, humioHttpClient, req, hg) + if errors.As(err, &humioapi.EntityNotFound{}) { + _ = r.setState(ctx, humiov1alpha1.HumioGroupStateNotFound, hg) + return + } + if err != nil { + _ = r.setState(ctx, humiov1alpha1.HumioGroupStateUnknown, hg) + return + } + _ = r.setState(ctx, humiov1alpha1.HumioGroupStateExists, hg) + }(ctx, hg) + + r.Log.Info("get current group") + curGroup, err := r.HumioClient.GetGroup(ctx, humioHttpClient, req, hg) + if err != nil { + if errors.As(err, &humioapi.EntityNotFound{}) { + r.Log.Info("Group doesn't exist. Now adding group") + addErr := r.HumioClient.AddGroup(ctx, humioHttpClient, req, hg) + if addErr != nil { + return reconcile.Result{}, r.logErrorAndReturn(addErr, "could not create group") + } + r.Log.Info("created group", "GroupName", hg.Spec.DisplayName) + return reconcile.Result{Requeue: true}, nil + } + return reconcile.Result{}, r.logErrorAndReturn(err, "could not check if group exists") + } + + if asExpected, diffKeysAndValues := groupAlreadyAsExpected(hg, curGroup); !asExpected { + r.Log.Info("information differs, triggering update", + "diff", diffKeysAndValues, + ) + updateErr := r.HumioClient.UpdateGroup(ctx, humioHttpClient, req, hg) + if updateErr != nil { + return reconcile.Result{}, r.logErrorAndReturn(updateErr, "could not update group") + } + } + + r.Log.Info("done reconciling, will requeue after 15 seconds") + return reconcile.Result{RequeueAfter: time.Second * 15}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *HumioGroupReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&humiov1alpha1.HumioGroup{}). + Named("humiogroup"). + Complete(r) +} + +func (r *HumioGroupReconciler) setState(ctx context.Context, state string, hg *humiov1alpha1.HumioGroup) error { + if hg.Status.State == state { + return nil + } + r.Log.Info(fmt.Sprintf("setting group state to %s", state)) + hg.Status.State = state + return r.Status().Update(ctx, hg) +} + +func (r *HumioGroupReconciler) logErrorAndReturn(err error, msg string) error { + r.Log.Error(err, msg) + return fmt.Errorf("%s: %w", msg, err) +} + +// groupAlreadyAsExpected compares the group from the custom resource with the group from the GraphQL API. +// It returns a boolean indicating if the details from GraphQL already matches what is in the desired state of the custom resource. +// If they do not match, a map is returned with details on what the diff is. +func groupAlreadyAsExpected(fromKubernetesCustomResource *humiov1alpha1.HumioGroup, fromGraphQL *humiographql.GroupDetails) (bool, map[string]string) { + keyValues := map[string]string{} + + if diff := cmp.Diff(fromGraphQL.GetDisplayName(), &fromKubernetesCustomResource.Spec.DisplayName); diff != "" { + keyValues["displayName"] = diff + } + if diff := cmp.Diff(fromGraphQL.GetLookupName(), &fromKubernetesCustomResource.Spec.LookupName); diff != "" { + keyValues["lookupName"] = diff + } + + // TODO: handle Group / Role / SearchDomain mappings here + + return len(keyValues) == 0, keyValues +} diff --git a/internal/humio/client.go b/internal/humio/client.go index 94b47de24..5e4ec214e 100644 --- a/internal/humio/client.go +++ b/internal/humio/client.go @@ -43,6 +43,7 @@ type Client interface { ParsersClient RepositoriesClient ViewsClient + GroupsClient LicenseClient ActionsClient AlertsClient @@ -93,6 +94,13 @@ type ViewsClient interface { DeleteView(context.Context, *humioapi.Client, reconcile.Request, *humiov1alpha1.HumioView) error } +type GroupsClient interface { + AddGroup(context.Context, *humioapi.Client, reconcile.Request, *humiov1alpha1.HumioGroup) error + GetGroup(context.Context, *humioapi.Client, reconcile.Request, *humiov1alpha1.HumioGroup) (*humiographql.GroupDetails, error) + UpdateGroup(context.Context, *humioapi.Client, reconcile.Request, *humiov1alpha1.HumioGroup) error + DeleteGroup(context.Context, *humioapi.Client, reconcile.Request, *humiov1alpha1.HumioGroup) error +} + type ActionsClient interface { AddAction(context.Context, *humioapi.Client, reconcile.Request, *humiov1alpha1.HumioAction) error GetAction(context.Context, *humioapi.Client, reconcile.Request, *humiov1alpha1.HumioAction) (humiographql.ActionDetails, error) @@ -800,6 +808,95 @@ func validateSearchDomain(ctx context.Context, client *humioapi.Client, searchDo return humioapi.SearchDomainNotFound(searchDomainName) } +func (h *ClientConfig) AddGroup(ctx context.Context, client *humioapi.Client, _ reconcile.Request, hg *humiov1alpha1.HumioGroup) error { + var groupLookupName *string + if hg.Spec.LookupName != nil { + groupLookupName = hg.Spec.LookupName + } else { + // if no lookup name is provided, use the display name + groupLookupName = &hg.Spec.DisplayName + } + _, err := humiographql.CreateGroup( + ctx, + client, + hg.Spec.DisplayName, + groupLookupName, + ) + return err +} + +func (h *ClientConfig) GetGroup(ctx context.Context, client *humioapi.Client, _ reconcile.Request, hg *humiov1alpha1.HumioGroup) (*humiographql.GroupDetails, error) { + getGroupResp, err := humiographql.GetGroupByDisplayName( + ctx, + client, + hg.Spec.DisplayName, + ) + if err != nil { + return nil, humioapi.GroupNotFound(hg.Spec.DisplayName) + } + + group := getGroupResp.GetGroupByDisplayName() + return &humiographql.GroupDetails{ + Id: group.GetId(), + DisplayName: group.GetDisplayName(), + LookupName: group.GetLookupName(), + }, nil +} + +func (h *ClientConfig) UpdateGroup(ctx context.Context, client *humioapi.Client, request reconcile.Request, hg *humiov1alpha1.HumioGroup) error { + group, err := h.GetGroup(ctx, client, request, hg) + if err != nil { + return err + } + + if cmp.Diff(group.DisplayName, hg.Spec.DisplayName) != "" { + _, err = humiographql.UpdateGroup( + ctx, + client, + group.Id, + &hg.Spec.DisplayName, + group.LookupName, + ) + if err != nil { + return err + } + } + + if cmp.Diff(group.LookupName, hg.Spec.LookupName) != "" { + _, err = humiographql.UpdateGroup( + ctx, + client, + group.Id, + &group.DisplayName, + hg.Spec.LookupName, + ) + if err != nil { + return err + } + } + + // TODO: handle Group / Role / SearchDomain mappings here + + return nil +} + +func (h *ClientConfig) DeleteGroup(ctx context.Context, client *humioapi.Client, request reconcile.Request, hg *humiov1alpha1.HumioGroup) error { + group, err := h.GetGroup(ctx, client, request, hg) + if err != nil { + if errors.As(err, &humioapi.EntityNotFound{}) { + return nil + } + return err + } + + _, err = humiographql.DeleteGroup( + ctx, + client, + group.Id, + ) + return err +} + func (h *ClientConfig) GetAction(ctx context.Context, client *humioapi.Client, _ reconcile.Request, ha *humiov1alpha1.HumioAction) (humiographql.ActionDetails, error) { err := validateSearchDomain(ctx, client, ha.Spec.ViewName) if err != nil { diff --git a/internal/humio/client_mock.go b/internal/humio/client_mock.go index dec1beedf..31da0bf8d 100644 --- a/internal/humio/client_mock.go +++ b/internal/humio/client_mock.go @@ -49,6 +49,7 @@ type ClientMock struct { LicenseUID map[resourceKey]string Repository map[resourceKey]humiographql.RepositoryDetails View map[resourceKey]humiographql.GetSearchDomainSearchDomainView + Group map[resourceKey]humiographql.GroupDetails IngestToken map[resourceKey]humiographql.IngestTokenDetails Parser map[resourceKey]humiographql.ParserDetails Action map[resourceKey]humiographql.ActionDetails @@ -71,6 +72,7 @@ func NewMockClient() *MockClientConfig { LicenseUID: make(map[resourceKey]string), Repository: make(map[resourceKey]humiographql.RepositoryDetails), View: make(map[resourceKey]humiographql.GetSearchDomainSearchDomainView), + Group: make(map[resourceKey]humiographql.GroupDetails), IngestToken: make(map[resourceKey]humiographql.IngestTokenDetails), Parser: make(map[resourceKey]humiographql.ParserDetails), Action: make(map[resourceKey]humiographql.ActionDetails), @@ -534,6 +536,77 @@ func (h *MockClientConfig) DeleteView(_ context.Context, _ *humioapi.Client, _ r return nil } +func (h *MockClientConfig) AddGroup(ctx context.Context, client *humioapi.Client, request reconcile.Request, group *humiov1alpha1.HumioGroup) error { + humioClientMu.Lock() + defer humioClientMu.Unlock() + + clusterName := fmt.Sprintf("%s%s", group.Spec.ManagedClusterName, group.Spec.ExternalClusterName) + key := resourceKey{ + clusterName: clusterName, + resourceName: group.Spec.DisplayName, + } + if _, found := h.apiClient.Group[key]; found { + return fmt.Errorf("group already exists with name %s", group.Spec.DisplayName) + } + + value := &humiographql.GroupDetails{ + Id: kubernetes.RandomString(), + DisplayName: group.Spec.DisplayName, + LookupName: group.Spec.LookupName, + } + + h.apiClient.Group[key] = *value + return nil +} + +func (h *MockClientConfig) GetGroup(ctx context.Context, client *humioapi.Client, request reconcile.Request, group *humiov1alpha1.HumioGroup) (*humiographql.GroupDetails, error) { + humioClientMu.Lock() + defer humioClientMu.Unlock() + + key := resourceKey{ + clusterName: fmt.Sprintf("%s%s", group.Spec.ManagedClusterName, group.Spec.ExternalClusterName), + resourceName: group.Spec.DisplayName, + } + if value, found := h.apiClient.Group[key]; found { + return &value, nil + } + return nil, fmt.Errorf("could not find group with name %s, err=%w", group.Spec.DisplayName, humioapi.EntityNotFound{}) +} + +func (h *MockClientConfig) UpdateGroup(ctx context.Context, client *humioapi.Client, request reconcile.Request, group *humiov1alpha1.HumioGroup) error { + humioClientMu.Lock() + defer humioClientMu.Unlock() + + key := resourceKey{ + clusterName: fmt.Sprintf("%s%s", group.Spec.ManagedClusterName, group.Spec.ExternalClusterName), + resourceName: group.Spec.DisplayName, + } + currentGroup, found := h.apiClient.Group[key] + + if !found { + return fmt.Errorf("group not found with name %s, err=%w", group.Spec.DisplayName, humioapi.EntityNotFound{}) + } + value := &humiographql.GroupDetails{ + Id: currentGroup.GetId(), + DisplayName: group.Spec.DisplayName, + LookupName: group.Spec.LookupName, + } + h.apiClient.Group[key] = *value + return nil +} + +func (h *MockClientConfig) DeleteGroup(ctx context.Context, client *humioapi.Client, request reconcile.Request, group *humiov1alpha1.HumioGroup) error { + humioClientMu.Lock() + defer humioClientMu.Unlock() + + key := resourceKey{ + clusterName: fmt.Sprintf("%s%s", group.Spec.ManagedClusterName, group.Spec.ExternalClusterName), + resourceName: group.Spec.DisplayName, + } + delete(h.apiClient.Group, key) + return nil +} + func (h *MockClientConfig) GetLicenseUIDAndExpiry(_ context.Context, _ *humioapi.Client, req reconcile.Request) (string, time.Time, error) { humioClientMu.Lock() defer humioClientMu.Unlock() From 3e47594157163e1d893710b9dc883fe2a4c24eb9 Mon Sep 17 00:00:00 2001 From: Bowen Sun Date: Tue, 22 Apr 2025 15:09:17 -0700 Subject: [PATCH 02/10] handle role assignments for HumioGroups --- api/v1alpha1/humiogroup_types.go | 15 + api/v1alpha1/zz_generated.deepcopy.go | 20 + .../crds/core.humio.com_humiogroups.yaml | 21 + .../crd/bases/core.humio.com_humiogroups.yaml | 21 + config/samples/core_v1alpha1_humiogroup.yaml | 3 + internal/api/humiographql/genqlient.yaml | 1 + .../api/humiographql/graphql/groups.graphql | 56 ++ .../api/humiographql/graphql/roles.graphql | 7 + internal/api/humiographql/humiographql.go | 755 ++++++++++++++++++ internal/controller/humiogroup_controller.go | 22 +- internal/humio/client.go | 161 +++- internal/humio/client_mock.go | 34 + 12 files changed, 1106 insertions(+), 10 deletions(-) create mode 100644 internal/api/humiographql/graphql/roles.graphql diff --git a/api/v1alpha1/humiogroup_types.go b/api/v1alpha1/humiogroup_types.go index 1ff4b1db8..47791bf63 100644 --- a/api/v1alpha1/humiogroup_types.go +++ b/api/v1alpha1/humiogroup_types.go @@ -15,6 +15,18 @@ const ( HumioGroupStateConfigError = "ConfigError" ) +// HumioGroupRoleAssignment represents a role assignment for a group +type HumioGroupRoleAssignment struct { + // RoleName contains the name of the role to assign + // +kubebuilder:validation:MinLength=1 + // +required + RoleName string `json:"roleName"` + // ViewName contains the name of the view to associate the group with + // +kubebuilder:validation:MinLength=1 + // +required + ViewName string `json:"viewName"` +} + // HumioGroupSpec defines the desired state of HumioGroup. type HumioGroupSpec struct { // ManagedClusterName refers to an object of type HumioCluster that is managed by the operator where the Humio @@ -31,6 +43,9 @@ type HumioGroupSpec struct { // LookupName is the lookup name of the HumioGroup // +optional LookupName *string `json:"lookupName,omitempty"` + // Assignments contains the list of role assignments for the group + // +optional + Assignments []HumioGroupRoleAssignment `json:"assignments,omitempty"` } // HumioGroupStatus defines the observed state of HumioGroup. diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 7c1ff7f65..34e5970bb 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -1230,6 +1230,21 @@ func (in *HumioGroupList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HumioGroupRoleAssignment) DeepCopyInto(out *HumioGroupRoleAssignment) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HumioGroupRoleAssignment. +func (in *HumioGroupRoleAssignment) DeepCopy() *HumioGroupRoleAssignment { + if in == nil { + return nil + } + out := new(HumioGroupRoleAssignment) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HumioGroupSpec) DeepCopyInto(out *HumioGroupSpec) { *out = *in @@ -1238,6 +1253,11 @@ func (in *HumioGroupSpec) DeepCopyInto(out *HumioGroupSpec) { *out = new(string) **out = **in } + if in.Assignments != nil { + in, out := &in.Assignments, &out.Assignments + *out = make([]HumioGroupRoleAssignment, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HumioGroupSpec. diff --git a/charts/humio-operator/crds/core.humio.com_humiogroups.yaml b/charts/humio-operator/crds/core.humio.com_humiogroups.yaml index 5c3a38a33..b20e2c277 100644 --- a/charts/humio-operator/crds/core.humio.com_humiogroups.yaml +++ b/charts/humio-operator/crds/core.humio.com_humiogroups.yaml @@ -50,6 +50,27 @@ spec: spec: description: HumioGroupSpec defines the desired state of HumioGroup. properties: + assignments: + description: Assignments contains the list of role assignments for + the group + items: + description: HumioGroupRoleAssignment represents a role assignment + for a group + properties: + roleName: + description: RoleName contains the name of the role to assign + minLength: 1 + type: string + viewName: + description: ViewName contains the name of the view to associate + the group with + minLength: 1 + type: string + required: + - roleName + - viewName + type: object + type: array displayName: description: DisplayName is the display name of the HumioGroup minLength: 1 diff --git a/config/crd/bases/core.humio.com_humiogroups.yaml b/config/crd/bases/core.humio.com_humiogroups.yaml index 5c3a38a33..b20e2c277 100644 --- a/config/crd/bases/core.humio.com_humiogroups.yaml +++ b/config/crd/bases/core.humio.com_humiogroups.yaml @@ -50,6 +50,27 @@ spec: spec: description: HumioGroupSpec defines the desired state of HumioGroup. properties: + assignments: + description: Assignments contains the list of role assignments for + the group + items: + description: HumioGroupRoleAssignment represents a role assignment + for a group + properties: + roleName: + description: RoleName contains the name of the role to assign + minLength: 1 + type: string + viewName: + description: ViewName contains the name of the view to associate + the group with + minLength: 1 + type: string + required: + - roleName + - viewName + type: object + type: array displayName: description: DisplayName is the display name of the HumioGroup minLength: 1 diff --git a/config/samples/core_v1alpha1_humiogroup.yaml b/config/samples/core_v1alpha1_humiogroup.yaml index 4e1e64e00..80cdc8724 100644 --- a/config/samples/core_v1alpha1_humiogroup.yaml +++ b/config/samples/core_v1alpha1_humiogroup.yaml @@ -6,3 +6,6 @@ spec: managedClusterName: example-humiocluster displayName: "example-group" lookupName: "example-group-lookup-name" + assignments: + - roleName: "example-role" + viewName: "example-view" diff --git a/internal/api/humiographql/genqlient.yaml b/internal/api/humiographql/genqlient.yaml index b2aeb5ce9..2a607bb8b 100644 --- a/internal/api/humiographql/genqlient.yaml +++ b/internal/api/humiographql/genqlient.yaml @@ -12,6 +12,7 @@ operations: - graphql/license.graphql - graphql/parsers.graphql - graphql/repositories.graphql + - graphql/roles.graphql - graphql/scheduled-search.graphql - graphql/searchdomains.graphql - graphql/token.graphql diff --git a/internal/api/humiographql/graphql/groups.graphql b/internal/api/humiographql/graphql/groups.graphql index cb9e7170a..088ad0adc 100644 --- a/internal/api/humiographql/graphql/groups.graphql +++ b/internal/api/humiographql/graphql/groups.graphql @@ -2,6 +2,16 @@ fragment GroupDetails on Group { id displayName lookupName + roles { + role { + id + displayName + } + searchDomain { + id + name + } + } } query GetGroupByDisplayName( @@ -58,4 +68,50 @@ mutation DeleteGroup( searchDomainCount } } +} + +mutation AssignRoleToGroup( + $GroupId: String! + $ViewId: String! + $RoleId: String! +) { + assignRoleToGroup( + input: { + groupId: $GroupId + viewId: $ViewId + roleId: $RoleId + } + ) { + group { + role { + id + displayName + } + searchDomain { + id + name + } + } + } +} + +mutation UnassignRoleFromGroup( + $GroupId: String! + $ViewId: String! + $RoleId: String! +) { + unassignRoleFromGroup( + input: { + groupId: $GroupId + viewId: $ViewId + roleId: $RoleId + } + ) { + group { + id + displayName + userCount + searchDomainCount + } + } } \ No newline at end of file diff --git a/internal/api/humiographql/graphql/roles.graphql b/internal/api/humiographql/graphql/roles.graphql new file mode 100644 index 000000000..1c3404f06 --- /dev/null +++ b/internal/api/humiographql/graphql/roles.graphql @@ -0,0 +1,7 @@ +query ListRoles +{ + roles { + id + displayName + } +} \ No newline at end of file diff --git a/internal/api/humiographql/humiographql.go b/internal/api/humiographql/humiographql.go index 89f7a4bcd..c842cf909 100644 --- a/internal/api/humiographql/humiographql.go +++ b/internal/api/humiographql/humiographql.go @@ -1589,6 +1589,274 @@ func (v *AssignParserToIngestTokenResponse) GetAssignParserToIngestTokenV2() Ass return v.AssignParserToIngestTokenV2 } +// AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutation includes the requested fields of the GraphQL type AssignRoleToGroupMutation. +type AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutation struct { + // Stability: Long-term + Group AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRole `json:"group"` +} + +// GetGroup returns AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutation.Group, and is useful for accessing the field via an interface. +func (v *AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutation) GetGroup() AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRole { + return v.Group +} + +// AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRole includes the requested fields of the GraphQL type SearchDomainRole. +// The GraphQL type's documentation follows. +// +// The role assigned in a searchDomain. +type AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRole struct { + // Stability: Long-term + Role AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleRole `json:"role"` + // Stability: Long-term + SearchDomain AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomain `json:"-"` +} + +// GetRole returns AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRole.Role, and is useful for accessing the field via an interface. +func (v *AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRole) GetRole() AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleRole { + return v.Role +} + +// GetSearchDomain returns AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRole.SearchDomain, and is useful for accessing the field via an interface. +func (v *AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRole) GetSearchDomain() AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomain { + return v.SearchDomain +} + +func (v *AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRole) UnmarshalJSON(b []byte) error { + + if string(b) == "null" { + return nil + } + + var firstPass struct { + *AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRole + SearchDomain json.RawMessage `json:"searchDomain"` + graphql.NoUnmarshalJSON + } + firstPass.AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRole = v + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + { + dst := &v.SearchDomain + src := firstPass.SearchDomain + if len(src) != 0 && string(src) != "null" { + err = __unmarshalAssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomain( + src, dst) + if err != nil { + return fmt.Errorf( + "unable to unmarshal AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRole.SearchDomain: %w", err) + } + } + } + return nil +} + +type __premarshalAssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRole struct { + Role AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleRole `json:"role"` + + SearchDomain json.RawMessage `json:"searchDomain"` +} + +func (v *AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRole) MarshalJSON() ([]byte, error) { + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err + } + return json.Marshal(premarshaled) +} + +func (v *AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRole) __premarshalJSON() (*__premarshalAssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRole, error) { + var retval __premarshalAssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRole + + retval.Role = v.Role + { + + dst := &retval.SearchDomain + src := v.SearchDomain + var err error + *dst, err = __marshalAssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomain( + &src) + if err != nil { + return nil, fmt.Errorf( + "unable to marshal AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRole.SearchDomain: %w", err) + } + } + return &retval, nil +} + +// AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleRole includes the requested fields of the GraphQL type Role. +type AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleRole struct { + // Stability: Long-term + Id string `json:"id"` + // Stability: Long-term + DisplayName string `json:"displayName"` +} + +// GetId returns AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleRole.Id, and is useful for accessing the field via an interface. +func (v *AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleRole) GetId() string { + return v.Id +} + +// GetDisplayName returns AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleRole.DisplayName, and is useful for accessing the field via an interface. +func (v *AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleRole) GetDisplayName() string { + return v.DisplayName +} + +// AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomain includes the requested fields of the GraphQL interface SearchDomain. +// +// AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomain is implemented by the following types: +// AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainRepository +// AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainView +// The GraphQL type's documentation follows. +// +// Common interface for Repositories and Views. +type AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomain interface { + implementsGraphQLInterfaceAssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomain() + // GetTypename returns the receiver's concrete GraphQL type-name (see interface doc for possible values). + GetTypename() *string + // GetId returns the interface-field "id" from its implementation. + // The GraphQL interface field's documentation follows. + // + // Common interface for Repositories and Views. + GetId() string + // GetName returns the interface-field "name" from its implementation. + // The GraphQL interface field's documentation follows. + // + // Common interface for Repositories and Views. + GetName() string +} + +func (v *AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainRepository) implementsGraphQLInterfaceAssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomain() { +} +func (v *AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainView) implementsGraphQLInterfaceAssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomain() { +} + +func __unmarshalAssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomain(b []byte, v *AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomain) error { + if string(b) == "null" { + return nil + } + + var tn struct { + TypeName string `json:"__typename"` + } + err := json.Unmarshal(b, &tn) + if err != nil { + return err + } + + switch tn.TypeName { + case "Repository": + *v = new(AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainRepository) + return json.Unmarshal(b, *v) + case "View": + *v = new(AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainView) + return json.Unmarshal(b, *v) + case "": + return fmt.Errorf( + "response was missing SearchDomain.__typename") + default: + return fmt.Errorf( + `unexpected concrete type for AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomain: "%v"`, tn.TypeName) + } +} + +func __marshalAssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomain(v *AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomain) ([]byte, error) { + + var typename string + switch v := (*v).(type) { + case *AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainRepository: + typename = "Repository" + + result := struct { + TypeName string `json:"__typename"` + *AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainRepository + }{typename, v} + return json.Marshal(result) + case *AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainView: + typename = "View" + + result := struct { + TypeName string `json:"__typename"` + *AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainView + }{typename, v} + return json.Marshal(result) + case nil: + return []byte("null"), nil + default: + return nil, fmt.Errorf( + `unexpected concrete type for AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomain: "%T"`, v) + } +} + +// AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainRepository includes the requested fields of the GraphQL type Repository. +// The GraphQL type's documentation follows. +// +// A repository stores ingested data, configures parsers and data retention policies. +type AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainRepository struct { + Typename *string `json:"__typename"` + // Common interface for Repositories and Views. + Id string `json:"id"` + // Common interface for Repositories and Views. + Name string `json:"name"` +} + +// GetTypename returns AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainRepository.Typename, and is useful for accessing the field via an interface. +func (v *AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainRepository) GetTypename() *string { + return v.Typename +} + +// GetId returns AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainRepository.Id, and is useful for accessing the field via an interface. +func (v *AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainRepository) GetId() string { + return v.Id +} + +// GetName returns AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainRepository.Name, and is useful for accessing the field via an interface. +func (v *AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainRepository) GetName() string { + return v.Name +} + +// AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainView includes the requested fields of the GraphQL type View. +// The GraphQL type's documentation follows. +// +// Represents information about a view, pulling data from one or several repositories. +type AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainView struct { + Typename *string `json:"__typename"` + // Common interface for Repositories and Views. + Id string `json:"id"` + // Common interface for Repositories and Views. + Name string `json:"name"` +} + +// GetTypename returns AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainView.Typename, and is useful for accessing the field via an interface. +func (v *AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainView) GetTypename() *string { + return v.Typename +} + +// GetId returns AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainView.Id, and is useful for accessing the field via an interface. +func (v *AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainView) GetId() string { + return v.Id +} + +// GetName returns AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainView.Name, and is useful for accessing the field via an interface. +func (v *AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainView) GetName() string { + return v.Name +} + +// AssignRoleToGroupResponse is returned by AssignRoleToGroup on success. +type AssignRoleToGroupResponse struct { + // Assigns a role to a group for a given view. If called with overrideExistingAssignmentsForView=false, this mutation can assign multiple roles for the same view. Calling with overrideExistingAssignmentsForView=false is thus only available if the MultipleViewRoleBindings feature is enabled. + // Stability: Long-term + AssignRoleToGroup AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutation `json:"assignRoleToGroup"` +} + +// GetAssignRoleToGroup returns AssignRoleToGroupResponse.AssignRoleToGroup, and is useful for accessing the field via an interface. +func (v *AssignRoleToGroupResponse) GetAssignRoleToGroup() AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutation { + return v.AssignRoleToGroup +} + // CreateAggregateAlertCreateAggregateAlert includes the requested fields of the GraphQL type AggregateAlert. // The GraphQL type's documentation follows. // @@ -5878,6 +6146,11 @@ func (v *GetGroupByDisplayNameGroupByDisplayNameGroup) GetLookupName() *string { return v.GroupDetails.LookupName } +// GetRoles returns GetGroupByDisplayNameGroupByDisplayNameGroup.Roles, and is useful for accessing the field via an interface. +func (v *GetGroupByDisplayNameGroupByDisplayNameGroup) GetRoles() []GroupDetailsRolesSearchDomainRole { + return v.GroupDetails.Roles +} + func (v *GetGroupByDisplayNameGroupByDisplayNameGroup) UnmarshalJSON(b []byte) error { if string(b) == "null" { @@ -5909,6 +6182,8 @@ type __premarshalGetGroupByDisplayNameGroupByDisplayNameGroup struct { DisplayName string `json:"displayName"` LookupName *string `json:"lookupName"` + + Roles []GroupDetailsRolesSearchDomainRole `json:"roles"` } func (v *GetGroupByDisplayNameGroupByDisplayNameGroup) MarshalJSON() ([]byte, error) { @@ -5925,6 +6200,7 @@ func (v *GetGroupByDisplayNameGroupByDisplayNameGroup) __premarshalJSON() (*__pr retval.Id = v.GroupDetails.Id retval.DisplayName = v.GroupDetails.DisplayName retval.LookupName = v.GroupDetails.LookupName + retval.Roles = v.GroupDetails.Roles return &retval, nil } @@ -7114,6 +7390,8 @@ type GroupDetails struct { DisplayName string `json:"displayName"` // Stability: Long-term LookupName *string `json:"lookupName"` + // Stability: Long-term + Roles []GroupDetailsRolesSearchDomainRole `json:"roles"` } // GetId returns GroupDetails.Id, and is useful for accessing the field via an interface. @@ -7125,6 +7403,240 @@ func (v *GroupDetails) GetDisplayName() string { return v.DisplayName } // GetLookupName returns GroupDetails.LookupName, and is useful for accessing the field via an interface. func (v *GroupDetails) GetLookupName() *string { return v.LookupName } +// GetRoles returns GroupDetails.Roles, and is useful for accessing the field via an interface. +func (v *GroupDetails) GetRoles() []GroupDetailsRolesSearchDomainRole { return v.Roles } + +// GroupDetailsRolesSearchDomainRole includes the requested fields of the GraphQL type SearchDomainRole. +// The GraphQL type's documentation follows. +// +// The role assigned in a searchDomain. +type GroupDetailsRolesSearchDomainRole struct { + // Stability: Long-term + Role GroupDetailsRolesSearchDomainRoleRole `json:"role"` + // Stability: Long-term + SearchDomain GroupDetailsRolesSearchDomainRoleSearchDomain `json:"-"` +} + +// GetRole returns GroupDetailsRolesSearchDomainRole.Role, and is useful for accessing the field via an interface. +func (v *GroupDetailsRolesSearchDomainRole) GetRole() GroupDetailsRolesSearchDomainRoleRole { + return v.Role +} + +// GetSearchDomain returns GroupDetailsRolesSearchDomainRole.SearchDomain, and is useful for accessing the field via an interface. +func (v *GroupDetailsRolesSearchDomainRole) GetSearchDomain() GroupDetailsRolesSearchDomainRoleSearchDomain { + return v.SearchDomain +} + +func (v *GroupDetailsRolesSearchDomainRole) UnmarshalJSON(b []byte) error { + + if string(b) == "null" { + return nil + } + + var firstPass struct { + *GroupDetailsRolesSearchDomainRole + SearchDomain json.RawMessage `json:"searchDomain"` + graphql.NoUnmarshalJSON + } + firstPass.GroupDetailsRolesSearchDomainRole = v + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + { + dst := &v.SearchDomain + src := firstPass.SearchDomain + if len(src) != 0 && string(src) != "null" { + err = __unmarshalGroupDetailsRolesSearchDomainRoleSearchDomain( + src, dst) + if err != nil { + return fmt.Errorf( + "unable to unmarshal GroupDetailsRolesSearchDomainRole.SearchDomain: %w", err) + } + } + } + return nil +} + +type __premarshalGroupDetailsRolesSearchDomainRole struct { + Role GroupDetailsRolesSearchDomainRoleRole `json:"role"` + + SearchDomain json.RawMessage `json:"searchDomain"` +} + +func (v *GroupDetailsRolesSearchDomainRole) MarshalJSON() ([]byte, error) { + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err + } + return json.Marshal(premarshaled) +} + +func (v *GroupDetailsRolesSearchDomainRole) __premarshalJSON() (*__premarshalGroupDetailsRolesSearchDomainRole, error) { + var retval __premarshalGroupDetailsRolesSearchDomainRole + + retval.Role = v.Role + { + + dst := &retval.SearchDomain + src := v.SearchDomain + var err error + *dst, err = __marshalGroupDetailsRolesSearchDomainRoleSearchDomain( + &src) + if err != nil { + return nil, fmt.Errorf( + "unable to marshal GroupDetailsRolesSearchDomainRole.SearchDomain: %w", err) + } + } + return &retval, nil +} + +// GroupDetailsRolesSearchDomainRoleRole includes the requested fields of the GraphQL type Role. +type GroupDetailsRolesSearchDomainRoleRole struct { + // Stability: Long-term + Id string `json:"id"` + // Stability: Long-term + DisplayName string `json:"displayName"` +} + +// GetId returns GroupDetailsRolesSearchDomainRoleRole.Id, and is useful for accessing the field via an interface. +func (v *GroupDetailsRolesSearchDomainRoleRole) GetId() string { return v.Id } + +// GetDisplayName returns GroupDetailsRolesSearchDomainRoleRole.DisplayName, and is useful for accessing the field via an interface. +func (v *GroupDetailsRolesSearchDomainRoleRole) GetDisplayName() string { return v.DisplayName } + +// GroupDetailsRolesSearchDomainRoleSearchDomain includes the requested fields of the GraphQL interface SearchDomain. +// +// GroupDetailsRolesSearchDomainRoleSearchDomain is implemented by the following types: +// GroupDetailsRolesSearchDomainRoleSearchDomainRepository +// GroupDetailsRolesSearchDomainRoleSearchDomainView +// The GraphQL type's documentation follows. +// +// Common interface for Repositories and Views. +type GroupDetailsRolesSearchDomainRoleSearchDomain interface { + implementsGraphQLInterfaceGroupDetailsRolesSearchDomainRoleSearchDomain() + // GetTypename returns the receiver's concrete GraphQL type-name (see interface doc for possible values). + GetTypename() *string + // GetId returns the interface-field "id" from its implementation. + // The GraphQL interface field's documentation follows. + // + // Common interface for Repositories and Views. + GetId() string + // GetName returns the interface-field "name" from its implementation. + // The GraphQL interface field's documentation follows. + // + // Common interface for Repositories and Views. + GetName() string +} + +func (v *GroupDetailsRolesSearchDomainRoleSearchDomainRepository) implementsGraphQLInterfaceGroupDetailsRolesSearchDomainRoleSearchDomain() { +} +func (v *GroupDetailsRolesSearchDomainRoleSearchDomainView) implementsGraphQLInterfaceGroupDetailsRolesSearchDomainRoleSearchDomain() { +} + +func __unmarshalGroupDetailsRolesSearchDomainRoleSearchDomain(b []byte, v *GroupDetailsRolesSearchDomainRoleSearchDomain) error { + if string(b) == "null" { + return nil + } + + var tn struct { + TypeName string `json:"__typename"` + } + err := json.Unmarshal(b, &tn) + if err != nil { + return err + } + + switch tn.TypeName { + case "Repository": + *v = new(GroupDetailsRolesSearchDomainRoleSearchDomainRepository) + return json.Unmarshal(b, *v) + case "View": + *v = new(GroupDetailsRolesSearchDomainRoleSearchDomainView) + return json.Unmarshal(b, *v) + case "": + return fmt.Errorf( + "response was missing SearchDomain.__typename") + default: + return fmt.Errorf( + `unexpected concrete type for GroupDetailsRolesSearchDomainRoleSearchDomain: "%v"`, tn.TypeName) + } +} + +func __marshalGroupDetailsRolesSearchDomainRoleSearchDomain(v *GroupDetailsRolesSearchDomainRoleSearchDomain) ([]byte, error) { + + var typename string + switch v := (*v).(type) { + case *GroupDetailsRolesSearchDomainRoleSearchDomainRepository: + typename = "Repository" + + result := struct { + TypeName string `json:"__typename"` + *GroupDetailsRolesSearchDomainRoleSearchDomainRepository + }{typename, v} + return json.Marshal(result) + case *GroupDetailsRolesSearchDomainRoleSearchDomainView: + typename = "View" + + result := struct { + TypeName string `json:"__typename"` + *GroupDetailsRolesSearchDomainRoleSearchDomainView + }{typename, v} + return json.Marshal(result) + case nil: + return []byte("null"), nil + default: + return nil, fmt.Errorf( + `unexpected concrete type for GroupDetailsRolesSearchDomainRoleSearchDomain: "%T"`, v) + } +} + +// GroupDetailsRolesSearchDomainRoleSearchDomainRepository includes the requested fields of the GraphQL type Repository. +// The GraphQL type's documentation follows. +// +// A repository stores ingested data, configures parsers and data retention policies. +type GroupDetailsRolesSearchDomainRoleSearchDomainRepository struct { + Typename *string `json:"__typename"` + // Common interface for Repositories and Views. + Id string `json:"id"` + // Common interface for Repositories and Views. + Name string `json:"name"` +} + +// GetTypename returns GroupDetailsRolesSearchDomainRoleSearchDomainRepository.Typename, and is useful for accessing the field via an interface. +func (v *GroupDetailsRolesSearchDomainRoleSearchDomainRepository) GetTypename() *string { + return v.Typename +} + +// GetId returns GroupDetailsRolesSearchDomainRoleSearchDomainRepository.Id, and is useful for accessing the field via an interface. +func (v *GroupDetailsRolesSearchDomainRoleSearchDomainRepository) GetId() string { return v.Id } + +// GetName returns GroupDetailsRolesSearchDomainRoleSearchDomainRepository.Name, and is useful for accessing the field via an interface. +func (v *GroupDetailsRolesSearchDomainRoleSearchDomainRepository) GetName() string { return v.Name } + +// GroupDetailsRolesSearchDomainRoleSearchDomainView includes the requested fields of the GraphQL type View. +// The GraphQL type's documentation follows. +// +// Represents information about a view, pulling data from one or several repositories. +type GroupDetailsRolesSearchDomainRoleSearchDomainView struct { + Typename *string `json:"__typename"` + // Common interface for Repositories and Views. + Id string `json:"id"` + // Common interface for Repositories and Views. + Name string `json:"name"` +} + +// GetTypename returns GroupDetailsRolesSearchDomainRoleSearchDomainView.Typename, and is useful for accessing the field via an interface. +func (v *GroupDetailsRolesSearchDomainRoleSearchDomainView) GetTypename() *string { return v.Typename } + +// GetId returns GroupDetailsRolesSearchDomainRoleSearchDomainView.Id, and is useful for accessing the field via an interface. +func (v *GroupDetailsRolesSearchDomainRoleSearchDomainView) GetId() string { return v.Id } + +// GetName returns GroupDetailsRolesSearchDomainRoleSearchDomainView.Name, and is useful for accessing the field via an interface. +func (v *GroupDetailsRolesSearchDomainRoleSearchDomainView) GetName() string { return v.Name } + // Http(s) Header entry. type HttpHeaderEntryInput struct { // Http(s) Header entry. @@ -9882,6 +10394,30 @@ func (v *ListRepositoriesResponse) GetRepositories() []ListRepositoriesRepositor return v.Repositories } +// ListRolesResponse is returned by ListRoles on success. +type ListRolesResponse struct { + // All defined roles. + // Stability: Long-term + Roles []ListRolesRolesRole `json:"roles"` +} + +// GetRoles returns ListRolesResponse.Roles, and is useful for accessing the field via an interface. +func (v *ListRolesResponse) GetRoles() []ListRolesRolesRole { return v.Roles } + +// ListRolesRolesRole includes the requested fields of the GraphQL type Role. +type ListRolesRolesRole struct { + // Stability: Long-term + Id string `json:"id"` + // Stability: Long-term + DisplayName string `json:"displayName"` +} + +// GetId returns ListRolesRolesRole.Id, and is useful for accessing the field via an interface. +func (v *ListRolesRolesRole) GetId() string { return v.Id } + +// GetDisplayName returns ListRolesRolesRole.DisplayName, and is useful for accessing the field via an interface. +func (v *ListRolesRolesRole) GetDisplayName() string { return v.DisplayName } + // ListScheduledSearchesResponse is returned by ListScheduledSearches on success. type ListScheduledSearchesResponse struct { // Stability: Long-term @@ -12310,6 +12846,60 @@ func (v *UnassignParserToIngestTokenUnassignIngestTokenUnassignIngestTokenMutati return v.Typename } +// UnassignRoleFromGroupResponse is returned by UnassignRoleFromGroup on success. +type UnassignRoleFromGroupResponse struct { + // Removes the role assigned to the group for a given view. + // Stability: Long-term + UnassignRoleFromGroup UnassignRoleFromGroupUnassignRoleFromGroup `json:"unassignRoleFromGroup"` +} + +// GetUnassignRoleFromGroup returns UnassignRoleFromGroupResponse.UnassignRoleFromGroup, and is useful for accessing the field via an interface. +func (v *UnassignRoleFromGroupResponse) GetUnassignRoleFromGroup() UnassignRoleFromGroupUnassignRoleFromGroup { + return v.UnassignRoleFromGroup +} + +// UnassignRoleFromGroupUnassignRoleFromGroup includes the requested fields of the GraphQL type UnassignRoleFromGroup. +type UnassignRoleFromGroupUnassignRoleFromGroup struct { + // Stability: Long-term + Group UnassignRoleFromGroupUnassignRoleFromGroupGroup `json:"group"` +} + +// GetGroup returns UnassignRoleFromGroupUnassignRoleFromGroup.Group, and is useful for accessing the field via an interface. +func (v *UnassignRoleFromGroupUnassignRoleFromGroup) GetGroup() UnassignRoleFromGroupUnassignRoleFromGroupGroup { + return v.Group +} + +// UnassignRoleFromGroupUnassignRoleFromGroupGroup includes the requested fields of the GraphQL type Group. +// The GraphQL type's documentation follows. +// +// A group. +type UnassignRoleFromGroupUnassignRoleFromGroupGroup struct { + // Stability: Long-term + Id string `json:"id"` + // Stability: Long-term + DisplayName string `json:"displayName"` + // Stability: Long-term + UserCount int `json:"userCount"` + // Stability: Long-term + SearchDomainCount int `json:"searchDomainCount"` +} + +// GetId returns UnassignRoleFromGroupUnassignRoleFromGroupGroup.Id, and is useful for accessing the field via an interface. +func (v *UnassignRoleFromGroupUnassignRoleFromGroupGroup) GetId() string { return v.Id } + +// GetDisplayName returns UnassignRoleFromGroupUnassignRoleFromGroupGroup.DisplayName, and is useful for accessing the field via an interface. +func (v *UnassignRoleFromGroupUnassignRoleFromGroupGroup) GetDisplayName() string { + return v.DisplayName +} + +// GetUserCount returns UnassignRoleFromGroupUnassignRoleFromGroupGroup.UserCount, and is useful for accessing the field via an interface. +func (v *UnassignRoleFromGroupUnassignRoleFromGroupGroup) GetUserCount() int { return v.UserCount } + +// GetSearchDomainCount returns UnassignRoleFromGroupUnassignRoleFromGroupGroup.SearchDomainCount, and is useful for accessing the field via an interface. +func (v *UnassignRoleFromGroupUnassignRoleFromGroupGroup) GetSearchDomainCount() int { + return v.SearchDomainCount +} + // UnregisterClusterNodeClusterUnregisterNodeUnregisterNodeMutation includes the requested fields of the GraphQL type UnregisterNodeMutation. type UnregisterClusterNodeClusterUnregisterNodeUnregisterNodeMutation struct { // Stability: Long-term @@ -13769,6 +14359,22 @@ func (v *__AssignParserToIngestTokenInput) GetIngestTokenName() string { return // GetParserName returns __AssignParserToIngestTokenInput.ParserName, and is useful for accessing the field via an interface. func (v *__AssignParserToIngestTokenInput) GetParserName() string { return v.ParserName } +// __AssignRoleToGroupInput is used internally by genqlient +type __AssignRoleToGroupInput struct { + GroupId string `json:"GroupId"` + ViewId string `json:"ViewId"` + RoleId string `json:"RoleId"` +} + +// GetGroupId returns __AssignRoleToGroupInput.GroupId, and is useful for accessing the field via an interface. +func (v *__AssignRoleToGroupInput) GetGroupId() string { return v.GroupId } + +// GetViewId returns __AssignRoleToGroupInput.ViewId, and is useful for accessing the field via an interface. +func (v *__AssignRoleToGroupInput) GetViewId() string { return v.ViewId } + +// GetRoleId returns __AssignRoleToGroupInput.RoleId, and is useful for accessing the field via an interface. +func (v *__AssignRoleToGroupInput) GetRoleId() string { return v.RoleId } + // __CreateAggregateAlertInput is used internally by genqlient type __CreateAggregateAlertInput struct { SearchDomainName string `json:"SearchDomainName"` @@ -14637,6 +15243,22 @@ func (v *__UnassignParserToIngestTokenInput) GetRepositoryName() string { return // GetIngestTokenName returns __UnassignParserToIngestTokenInput.IngestTokenName, and is useful for accessing the field via an interface. func (v *__UnassignParserToIngestTokenInput) GetIngestTokenName() string { return v.IngestTokenName } +// __UnassignRoleFromGroupInput is used internally by genqlient +type __UnassignRoleFromGroupInput struct { + GroupId string `json:"GroupId"` + ViewId string `json:"ViewId"` + RoleId string `json:"RoleId"` +} + +// GetGroupId returns __UnassignRoleFromGroupInput.GroupId, and is useful for accessing the field via an interface. +func (v *__UnassignRoleFromGroupInput) GetGroupId() string { return v.GroupId } + +// GetViewId returns __UnassignRoleFromGroupInput.ViewId, and is useful for accessing the field via an interface. +func (v *__UnassignRoleFromGroupInput) GetViewId() string { return v.ViewId } + +// GetRoleId returns __UnassignRoleFromGroupInput.RoleId, and is useful for accessing the field via an interface. +func (v *__UnassignRoleFromGroupInput) GetRoleId() string { return v.RoleId } + // __UnregisterClusterNodeInput is used internally by genqlient type __UnregisterClusterNodeInput struct { NodeId int `json:"NodeId"` @@ -15360,6 +15982,54 @@ func AssignParserToIngestToken( return data_, err_ } +// The mutation executed by AssignRoleToGroup. +const AssignRoleToGroup_Operation = ` +mutation AssignRoleToGroup ($GroupId: String!, $ViewId: String!, $RoleId: String!) { + assignRoleToGroup(input: {groupId:$GroupId,viewId:$ViewId,roleId:$RoleId}) { + group { + role { + id + displayName + } + searchDomain { + __typename + id + name + } + } + } +} +` + +func AssignRoleToGroup( + ctx_ context.Context, + client_ graphql.Client, + GroupId string, + ViewId string, + RoleId string, +) (data_ *AssignRoleToGroupResponse, err_ error) { + req_ := &graphql.Request{ + OpName: "AssignRoleToGroup", + Query: AssignRoleToGroup_Operation, + Variables: &__AssignRoleToGroupInput{ + GroupId: GroupId, + ViewId: ViewId, + RoleId: RoleId, + }, + } + + data_ = &AssignRoleToGroupResponse{} + resp_ := &graphql.Response{Data: data_} + + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, + ) + + return data_, err_ +} + // The mutation executed by CreateAggregateAlert. const CreateAggregateAlert_Operation = ` mutation CreateAggregateAlert ($SearchDomainName: RepoOrViewName!, $Name: String!, $Description: String, $QueryString: String!, $SearchIntervalSeconds: Long!, $ActionIdsOrNames: [String!]!, $Labels: [String!]!, $Enabled: Boolean!, $ThrottleField: String, $ThrottleTimeSeconds: Long!, $TriggerMode: TriggerMode!, $QueryTimestampMode: QueryTimestampType!, $QueryOwnershipType: QueryOwnershipType!) { @@ -17025,6 +17695,17 @@ fragment GroupDetails on Group { id displayName lookupName + roles { + role { + id + displayName + } + searchDomain { + __typename + id + name + } + } } ` @@ -17797,6 +18478,37 @@ func ListRepositories( return data_, err_ } +// The query executed by ListRoles. +const ListRoles_Operation = ` +query ListRoles { + roles { + id + displayName + } +} +` + +func ListRoles( + ctx_ context.Context, + client_ graphql.Client, +) (data_ *ListRolesResponse, err_ error) { + req_ := &graphql.Request{ + OpName: "ListRoles", + Query: ListRoles_Operation, + } + + data_ = &ListRolesResponse{} + resp_ := &graphql.Response{Data: data_} + + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, + ) + + return data_, err_ +} + // The query executed by ListScheduledSearches. const ListScheduledSearches_Operation = ` query ListScheduledSearches ($SearchDomainName: String!) { @@ -18147,6 +18859,49 @@ func UnassignParserToIngestToken( return data_, err_ } +// The mutation executed by UnassignRoleFromGroup. +const UnassignRoleFromGroup_Operation = ` +mutation UnassignRoleFromGroup ($GroupId: String!, $ViewId: String!, $RoleId: String!) { + unassignRoleFromGroup(input: {groupId:$GroupId,viewId:$ViewId,roleId:$RoleId}) { + group { + id + displayName + userCount + searchDomainCount + } + } +} +` + +func UnassignRoleFromGroup( + ctx_ context.Context, + client_ graphql.Client, + GroupId string, + ViewId string, + RoleId string, +) (data_ *UnassignRoleFromGroupResponse, err_ error) { + req_ := &graphql.Request{ + OpName: "UnassignRoleFromGroup", + Query: UnassignRoleFromGroup_Operation, + Variables: &__UnassignRoleFromGroupInput{ + GroupId: GroupId, + ViewId: ViewId, + RoleId: RoleId, + }, + } + + data_ = &UnassignRoleFromGroupResponse{} + resp_ := &graphql.Response{Data: data_} + + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, + ) + + return data_, err_ +} + // The mutation executed by UnregisterClusterNode. const UnregisterClusterNode_Operation = ` mutation UnregisterClusterNode ($NodeId: Int!, $Force: Boolean!) { diff --git a/internal/controller/humiogroup_controller.go b/internal/controller/humiogroup_controller.go index e2c5f919b..8e90b0d61 100644 --- a/internal/controller/humiogroup_controller.go +++ b/internal/controller/humiogroup_controller.go @@ -16,6 +16,7 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sort" "time" ) @@ -182,7 +183,26 @@ func groupAlreadyAsExpected(fromKubernetesCustomResource *humiov1alpha1.HumioGro keyValues["lookupName"] = diff } - // TODO: handle Group / Role / SearchDomain mappings here + roleAssignmentsFromGraphQL := make([]string, len(fromGraphQL.GetRoles())) + for _, assignment := range fromGraphQL.GetRoles() { + role := assignment.GetRole() + view := assignment.GetSearchDomain() + roleAssignmentsFromGraphQL = append(roleAssignmentsFromGraphQL, formatRoleAssignmentString(role.GetDisplayName(), view.GetName())) + } + roleAssignmentsFromKubernetes := make([]string, len(fromKubernetesCustomResource.Spec.Assignments)) + for _, assignment := range fromKubernetesCustomResource.Spec.Assignments { + roleAssignmentsFromKubernetes = append(roleAssignmentsFromKubernetes, formatRoleAssignmentString(assignment.RoleName, assignment.ViewName)) + } + // sort the two lists for comparison + sort.Strings(roleAssignmentsFromGraphQL) + sort.Strings(roleAssignmentsFromKubernetes) + if diff := cmp.Diff(roleAssignmentsFromGraphQL, roleAssignmentsFromKubernetes); diff != "" { + keyValues["roleAssignments"] = diff + } return len(keyValues) == 0, keyValues } + +func formatRoleAssignmentString(roleName string, viewName string) string { + return fmt.Sprintf("%s:%s", roleName, viewName) +} diff --git a/internal/humio/client.go b/internal/humio/client.go index 5e4ec214e..42a43aec0 100644 --- a/internal/humio/client.go +++ b/internal/humio/client.go @@ -21,6 +21,7 @@ import ( "errors" "fmt" "net/http" + "slices" "sync" "time" @@ -808,6 +809,40 @@ func validateSearchDomain(ctx context.Context, client *humioapi.Client, searchDo return humioapi.SearchDomainNotFound(searchDomainName) } +func getAllRolesNameToId(ctx context.Context, client *humioapi.Client) (map[string]string, error) { + resp, err := humiographql.ListRoles( + ctx, + client, + ) + if err != nil { + return nil, fmt.Errorf("got error fetching all roles: %w", err) + } + + roleList := resp.GetRoles() + roleNameToId := make(map[string]string) + for _, role := range roleList { + roleNameToId[role.GetDisplayName()] = role.GetId() + } + return roleNameToId, nil +} + +func getViewIdByName(ctx context.Context, client *humioapi.Client, viewName string) (string, error) { + resp, err := humiographql.GetSearchDomain( + ctx, + client, + viewName, + ) + if err != nil { + return "", fmt.Errorf("got error fetching searchdomain: %w", err) + } + if resp == nil { + return "", humioapi.SearchDomainNotFound(viewName) + } + + searchDomain := resp.GetSearchDomain() + return searchDomain.GetId(), nil +} + func (h *ClientConfig) AddGroup(ctx context.Context, client *humioapi.Client, _ reconcile.Request, hg *humiov1alpha1.HumioGroup) error { var groupLookupName *string if hg.Spec.LookupName != nil { @@ -816,12 +851,49 @@ func (h *ClientConfig) AddGroup(ctx context.Context, client *humioapi.Client, _ // if no lookup name is provided, use the display name groupLookupName = &hg.Spec.DisplayName } - _, err := humiographql.CreateGroup( + createGroupResp, err := humiographql.CreateGroup( ctx, client, hg.Spec.DisplayName, groupLookupName, ) + if err != nil { + return fmt.Errorf("got error creating group: %w", err) + } + + // get the newly created Group's ID from the response + groupWrapper := createGroupResp.GetAddGroup() + createdGroup := groupWrapper.GetGroup() + groupId := createdGroup.GetId() + + roleNameToId, err := getAllRolesNameToId(ctx, client) + if err != nil { + return fmt.Errorf("got error fetching all roles: %w", err) + } + + for _, roleAssignment := range hg.Spec.Assignments { + roleName := roleAssignment.RoleName + viewName := roleAssignment.ViewName + roleId, ok := roleNameToId[roleName] + if !ok { + return fmt.Errorf("specified role doesn't exist: %s", roleName) + } + viewId, err := getViewIdByName(ctx, client, viewName) + if err != nil { + return fmt.Errorf("got error fetching viewId for %s: %w", viewName, err) + } + _, err = humiographql.AssignRoleToGroup( + ctx, + client, + groupId, + viewId, + roleId, + ) + if err != nil { + return fmt.Errorf("got error assigning role %s to group %s for view %s: %w", roleName, hg.Spec.DisplayName, viewName, err) + } + } + return err } @@ -840,34 +912,35 @@ func (h *ClientConfig) GetGroup(ctx context.Context, client *humioapi.Client, _ Id: group.GetId(), DisplayName: group.GetDisplayName(), LookupName: group.GetLookupName(), + Roles: group.GetRoles(), }, nil } func (h *ClientConfig) UpdateGroup(ctx context.Context, client *humioapi.Client, request reconcile.Request, hg *humiov1alpha1.HumioGroup) error { - group, err := h.GetGroup(ctx, client, request, hg) + curGroup, err := h.GetGroup(ctx, client, request, hg) if err != nil { return err } - if cmp.Diff(group.DisplayName, hg.Spec.DisplayName) != "" { + if cmp.Diff(curGroup.DisplayName, hg.Spec.DisplayName) != "" { _, err = humiographql.UpdateGroup( ctx, client, - group.Id, + curGroup.Id, &hg.Spec.DisplayName, - group.LookupName, + curGroup.LookupName, ) if err != nil { return err } } - if cmp.Diff(group.LookupName, hg.Spec.LookupName) != "" { + if cmp.Diff(curGroup.LookupName, hg.Spec.LookupName) != "" { _, err = humiographql.UpdateGroup( ctx, client, - group.Id, - &group.DisplayName, + curGroup.Id, + &curGroup.DisplayName, hg.Spec.LookupName, ) if err != nil { @@ -875,7 +948,77 @@ func (h *ClientConfig) UpdateGroup(ctx context.Context, client *humioapi.Client, } } - // TODO: handle Group / Role / SearchDomain mappings here + curGroupViewToRoles := make(map[string][]string) + for _, searchDomainRole := range curGroup.Roles { + view := searchDomainRole.GetSearchDomain() + role := searchDomainRole.GetRole() + curGroupViewToRoles[view.GetName()] = append(curGroupViewToRoles[view.GetName()], role.GetDisplayName()) + } + + desiredGroupViewToRoles := make(map[string][]string) + for _, roleAssignment := range hg.Spec.Assignments { + desiredGroupViewToRoles[roleAssignment.ViewName] = append(desiredGroupViewToRoles[roleAssignment.ViewName], roleAssignment.RoleName) + } + + roleNameToId, err := getAllRolesNameToId(ctx, client) + if err != nil { + return fmt.Errorf("got error fetching all roles: %w", err) + } + // reconcile - delete existing roles that are not in the desired state + for viewName, roleNames := range curGroupViewToRoles { + for _, roleName := range roleNames { + desiredGroupViewToRolesList, exists := desiredGroupViewToRoles[viewName] + if !exists || !slices.Contains(desiredGroupViewToRolesList, roleName) { + // this role is not in the desired state, delete it + roleId, ok := roleNameToId[roleName] + if !ok { + return fmt.Errorf("trying to unassigned a role that doesn't exist: %s", roleName) + } + viewId, err := getViewIdByName(ctx, client, viewName) + if err != nil { + return fmt.Errorf("got error fetching viewId for %s: %w", viewName, err) + } + _, err = humiographql.UnassignRoleFromGroup( + ctx, + client, + curGroup.Id, + viewId, + roleId, + ) + if err != nil { + return fmt.Errorf("got error unassigning role %s from group %s for view %s: %w", roleName, hg.Spec.DisplayName, viewName, err) + } + } + } + } + + // reconcile - add new roles that are in the desired state but not in the existing state + for viewName, roleNames := range desiredGroupViewToRoles { + for _, roleName := range roleNames { + curGroupViewToRolesList, exists := curGroupViewToRoles[viewName] + if !exists || !slices.Contains(curGroupViewToRolesList, roleName) { + // this role is not in the existing state, add it + roleId, ok := roleNameToId[roleName] + if !ok { + return fmt.Errorf("trying to assign a role that doesn't exist: %s", roleName) + } + viewId, err := getViewIdByName(ctx, client, viewName) + if err != nil { + return fmt.Errorf("got error fetching viewId for %s: %w", viewName, err) + } + _, err = humiographql.AssignRoleToGroup( + ctx, + client, + curGroup.Id, + viewId, + roleId, + ) + if err != nil { + return fmt.Errorf("got error assigning role %s to group %s for view %s: %w", roleName, hg.Spec.DisplayName, viewName, err) + } + } + } + } return nil } diff --git a/internal/humio/client_mock.go b/internal/humio/client_mock.go index 31da0bf8d..1be41b882 100644 --- a/internal/humio/client_mock.go +++ b/internal/humio/client_mock.go @@ -50,6 +50,7 @@ type ClientMock struct { Repository map[resourceKey]humiographql.RepositoryDetails View map[resourceKey]humiographql.GetSearchDomainSearchDomainView Group map[resourceKey]humiographql.GroupDetails + Role map[resourceKey]humiographql.ListRolesRolesRole IngestToken map[resourceKey]humiographql.IngestTokenDetails Parser map[resourceKey]humiographql.ParserDetails Action map[resourceKey]humiographql.ActionDetails @@ -73,6 +74,7 @@ func NewMockClient() *MockClientConfig { Repository: make(map[resourceKey]humiographql.RepositoryDetails), View: make(map[resourceKey]humiographql.GetSearchDomainSearchDomainView), Group: make(map[resourceKey]humiographql.GroupDetails), + Role: make(map[resourceKey]humiographql.ListRolesRolesRole), IngestToken: make(map[resourceKey]humiographql.IngestTokenDetails), Parser: make(map[resourceKey]humiographql.ParserDetails), Action: make(map[resourceKey]humiographql.ActionDetails), @@ -99,6 +101,8 @@ func (h *MockClientConfig) ClearHumioClientConnections(repoNameToKeep string) { } } h.apiClient.View = make(map[resourceKey]humiographql.GetSearchDomainSearchDomainView) + h.apiClient.Group = make(map[resourceKey]humiographql.GroupDetails) + h.apiClient.Role = make(map[resourceKey]humiographql.ListRolesRolesRole) h.apiClient.IngestToken = make(map[resourceKey]humiographql.IngestTokenDetails) h.apiClient.Parser = make(map[resourceKey]humiographql.ParserDetails) h.apiClient.Action = make(map[resourceKey]humiographql.ActionDetails) @@ -549,10 +553,24 @@ func (h *MockClientConfig) AddGroup(ctx context.Context, client *humioapi.Client return fmt.Errorf("group already exists with name %s", group.Spec.DisplayName) } + roles := make([]humiographql.GroupDetailsRolesSearchDomainRole, 0) + for _, role := range group.Spec.Assignments { + roles = append(roles, humiographql.GroupDetailsRolesSearchDomainRole{ + Role: humiographql.GroupDetailsRolesSearchDomainRoleRole{ + Id: role.RoleName, + DisplayName: role.RoleName, + }, + SearchDomain: &humiographql.GroupDetailsRolesSearchDomainRoleSearchDomainView{ + Id: role.ViewName, + Name: role.ViewName, + }, + }) + } value := &humiographql.GroupDetails{ Id: kubernetes.RandomString(), DisplayName: group.Spec.DisplayName, LookupName: group.Spec.LookupName, + Roles: roles, } h.apiClient.Group[key] = *value @@ -586,11 +604,27 @@ func (h *MockClientConfig) UpdateGroup(ctx context.Context, client *humioapi.Cli if !found { return fmt.Errorf("group not found with name %s, err=%w", group.Spec.DisplayName, humioapi.EntityNotFound{}) } + + roles := make([]humiographql.GroupDetailsRolesSearchDomainRole, 0) + for _, role := range group.Spec.Assignments { + roles = append(roles, humiographql.GroupDetailsRolesSearchDomainRole{ + Role: humiographql.GroupDetailsRolesSearchDomainRoleRole{ + Id: role.RoleName, + DisplayName: role.RoleName, + }, + SearchDomain: &humiographql.GroupDetailsRolesSearchDomainRoleSearchDomainView{ + Id: role.ViewName, + Name: role.ViewName, + }, + }) + } value := &humiographql.GroupDetails{ Id: currentGroup.GetId(), DisplayName: group.Spec.DisplayName, LookupName: group.Spec.LookupName, + Roles: roles, } + h.apiClient.Group[key] = *value return nil } From f9b59b6c2f0ab12fd43f509929f8bc11e5b981d3 Mon Sep 17 00:00:00 2001 From: Bowen Sun Date: Wed, 23 Apr 2025 13:36:19 -0700 Subject: [PATCH 03/10] bump version number --- VERSION | 2 +- charts/humio-operator/Chart.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/VERSION b/VERSION index a37255a85..b79f04f44 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.28.2 +0.28.3 diff --git a/charts/humio-operator/Chart.yaml b/charts/humio-operator/Chart.yaml index c9639f4d1..c17f40fac 100644 --- a/charts/humio-operator/Chart.yaml +++ b/charts/humio-operator/Chart.yaml @@ -1,7 +1,7 @@ apiVersion: v1 name: humio-operator -version: 0.28.2 -appVersion: 0.28.2 +version: 0.28.3 +appVersion: 0.28.3 home: https://github.com/humio/humio-operator description: | Kubernetes Operator for running Humio on top of Kubernetes From 2e61f9d8586a2c2e8917f291762fd5253d022e09 Mon Sep 17 00:00:00 2001 From: Bowen Sun Date: Thu, 24 Apr 2025 09:14:50 -0700 Subject: [PATCH 04/10] fix a state usage --- internal/controller/humiogroup_controller.go | 2 +- internal/controller/humioview_controller.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/controller/humiogroup_controller.go b/internal/controller/humiogroup_controller.go index 8e90b0d61..3a4fa89de 100644 --- a/internal/controller/humiogroup_controller.go +++ b/internal/controller/humiogroup_controller.go @@ -61,7 +61,7 @@ func (r *HumioGroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) cluster, err := helpers.NewCluster(ctx, r, hg.Spec.ManagedClusterName, hg.Spec.ExternalClusterName, hg.Namespace, helpers.UseCertManager(), true, false) if err != nil || cluster == nil || cluster.Config() == nil { - setStateErr := r.setState(ctx, humiov1alpha1.HumioParserStateConfigError, hg) + setStateErr := r.setState(ctx, humiov1alpha1.HumioGroupStateConfigError, hg) if setStateErr != nil { return reconcile.Result{}, r.logErrorAndReturn(setStateErr, "unable to set cluster state") } diff --git a/internal/controller/humioview_controller.go b/internal/controller/humioview_controller.go index 1f7af68eb..77144f34b 100644 --- a/internal/controller/humioview_controller.go +++ b/internal/controller/humioview_controller.go @@ -79,7 +79,7 @@ func (r *HumioViewReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( cluster, err := helpers.NewCluster(ctx, r, hv.Spec.ManagedClusterName, hv.Spec.ExternalClusterName, hv.Namespace, helpers.UseCertManager(), true, false) if err != nil || cluster == nil || cluster.Config() == nil { - setStateErr := r.setState(ctx, humiov1alpha1.HumioParserStateConfigError, hv) + setStateErr := r.setState(ctx, humiov1alpha1.HumioViewStateConfigError, hv) if setStateErr != nil { return reconcile.Result{}, r.logErrorAndReturn(setStateErr, "unable to set cluster state") } From 775eeffd479608f1a42d05a748fed391727174d4 Mon Sep 17 00:00:00 2001 From: Mike Rostermund Date: Thu, 15 May 2025 09:22:22 +0200 Subject: [PATCH 05/10] Keep version as-is. We will update it later when we're ready to push a new release --- VERSION | 2 +- cmd/main.go | 4 -- docs/api.md | 174 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 175 insertions(+), 5 deletions(-) diff --git a/VERSION b/VERSION index b79f04f44..a37255a85 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.28.3 +0.28.2 diff --git a/cmd/main.go b/cmd/main.go index 04df47926..69eba3fa3 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -24,9 +24,6 @@ import ( "path/filepath" "time" - "github.com/humio/humio-operator/internal/controller" - "github.com/humio/humio-operator/internal/humio" - cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" "github.com/go-logr/logr" "github.com/go-logr/zapr" @@ -35,7 +32,6 @@ import ( "github.com/humio/humio-operator/internal/controller" "github.com/humio/humio-operator/internal/helpers" "github.com/humio/humio-operator/internal/humio" - // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) // to ensure that exec-entrypoint and run can make use of them. _ "k8s.io/client-go/plugin/pkg/client/auth" diff --git a/docs/api.md b/docs/api.md index a7b57e216..589934d91 100644 --- a/docs/api.md +++ b/docs/api.md @@ -24,6 +24,8 @@ Resource Types: - [HumioFilterAlert](#humiofilteralert) +- [HumioGroup](#humiogroup) + - [HumioIngestToken](#humioingesttoken) - [HumioParser](#humioparser) @@ -36807,6 +36809,178 @@ HumioFilterAlertStatus defines the observed state of HumioFilterAlert. +## HumioGroup +[↩ Parent](#corehumiocomv1alpha1 ) + + + + + + +HumioGroup is the Schema for the humiogroups API + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
apiVersionstringcore.humio.com/v1alpha1true
kindstringHumioGrouptrue
metadataobjectRefer to the Kubernetes API documentation for the fields of the `metadata` field.true
specobject + HumioGroupSpec defines the desired state of HumioGroup.
+
false
statusobject + HumioGroupStatus defines the observed state of HumioGroup.
+
false
+ + +### HumioGroup.spec +[↩ Parent](#humiogroup) + + + +HumioGroupSpec defines the desired state of HumioGroup. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
displayNamestring + DisplayName is the display name of the HumioGroup
+
true
assignments[]object + Assignments contains the list of role assignments for the group
+
false
externalClusterNamestring + ExternalClusterName refers to an object of type HumioExternalCluster where the Humio resources should be created. +This conflicts with ManagedClusterName.
+
false
lookupNamestring + LookupName is the lookup name of the HumioGroup
+
false
managedClusterNamestring + ManagedClusterName refers to an object of type HumioCluster that is managed by the operator where the Humio +resources should be created. +This conflicts with ExternalClusterName.
+
false
+ + +### HumioGroup.spec.assignments[index] +[↩ Parent](#humiogroupspec) + + + +HumioGroupRoleAssignment represents a role assignment for a group + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
roleNamestring + RoleName contains the name of the role to assign
+
true
viewNamestring + ViewName contains the name of the view to associate the group with
+
true
+ + +### HumioGroup.status +[↩ Parent](#humiogroup) + + + +HumioGroupStatus defines the observed state of HumioGroup. + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
statestring + State reflects the current state of the HumioGroup
+
false
+ ## HumioIngestToken [↩ Parent](#corehumiocomv1alpha1 ) From 2dcbdb570e5ad4ba2d178f614de4f8a37272cc75 Mon Sep 17 00:00:00 2001 From: Mike Rostermund Date: Thu, 15 May 2025 09:25:39 +0200 Subject: [PATCH 06/10] HumioGroup: Allow configurable RequeueAfter as other CRD's --- internal/controller/humiogroup_controller.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/internal/controller/humiogroup_controller.go b/internal/controller/humiogroup_controller.go index 3a4fa89de..407ed9b57 100644 --- a/internal/controller/humiogroup_controller.go +++ b/internal/controller/humiogroup_controller.go @@ -4,6 +4,9 @@ import ( "context" "errors" "fmt" + "sort" + "time" + "github.com/go-logr/logr" "github.com/google/go-cmp/cmp" humiov1alpha1 "github.com/humio/humio-operator/api/v1alpha1" @@ -16,13 +19,12 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" - "sort" - "time" ) // HumioGroupReconciler reconciles a HumioGroup object type HumioGroupReconciler struct { client.Client + CommonConfig BaseLogger logr.Logger Log logr.Logger HumioClient humio.Client @@ -144,8 +146,8 @@ func (r *HumioGroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) } } - r.Log.Info("done reconciling, will requeue after 15 seconds") - return reconcile.Result{RequeueAfter: time.Second * 15}, nil + r.Log.Info("done reconciling, will requeue", "requeuePeriod", r.RequeuePeriod.String()) + return reconcile.Result{RequeueAfter: r.RequeuePeriod}, nil } // SetupWithManager sets up the controller with the Manager. From 072bdaafad75bccdd029b8fd341f582e5f2f6b4b Mon Sep 17 00:00:00 2001 From: Mike Rostermund Date: Thu, 15 May 2025 11:02:34 +0200 Subject: [PATCH 07/10] Remove view group assignment from HumioGroup This will be introduced as part of the role CRD's instead, which will follow later. --- api/v1alpha1/humiogroup_types.go | 31 +- api/v1alpha1/zz_generated.deepcopy.go | 24 +- .../crds/core.humio.com_humiogroups.yaml | 46 +- .../templates/operator-rbac.yaml | 3 + cmd/main.go | 1 + .../crd/bases/core.humio.com_humiogroups.yaml | 46 +- docs/api.md | 55 +- .../api/humiographql/graphql/groups.graphql | 64 +- internal/api/humiographql/humiographql.go | 996 +++++------------- internal/controller/humiogroup_controller.go | 31 +- .../humioresources_controller_test.go | 107 +- .../controller/suite/resources/suite_test.go | 11 + internal/humio/client.go | 199 +--- internal/humio/client_mock.go | 62 +- 14 files changed, 457 insertions(+), 1219 deletions(-) diff --git a/api/v1alpha1/humiogroup_types.go b/api/v1alpha1/humiogroup_types.go index 47791bf63..e0fcfc12d 100644 --- a/api/v1alpha1/humiogroup_types.go +++ b/api/v1alpha1/humiogroup_types.go @@ -15,19 +15,8 @@ const ( HumioGroupStateConfigError = "ConfigError" ) -// HumioGroupRoleAssignment represents a role assignment for a group -type HumioGroupRoleAssignment struct { - // RoleName contains the name of the role to assign - // +kubebuilder:validation:MinLength=1 - // +required - RoleName string `json:"roleName"` - // ViewName contains the name of the view to associate the group with - // +kubebuilder:validation:MinLength=1 - // +required - ViewName string `json:"viewName"` -} - // HumioGroupSpec defines the desired state of HumioGroup. +// +kubebuilder:validation:XValidation:rule="(has(self.managedClusterName) && self.managedClusterName != \"\") != (has(self.externalClusterName) && self.externalClusterName != \"\")",message="Must specify exactly one of managedClusterName or externalClusterName" type HumioGroupSpec struct { // ManagedClusterName refers to an object of type HumioCluster that is managed by the operator where the Humio // resources should be created. @@ -36,16 +25,15 @@ type HumioGroupSpec struct { // ExternalClusterName refers to an object of type HumioExternalCluster where the Humio resources should be created. // This conflicts with ManagedClusterName. ExternalClusterName string `json:"externalClusterName,omitempty"` - // DisplayName is the display name of the HumioGroup + // Name is the display name of the HumioGroup // +kubebuilder:validation:MinLength=1 - // +required - DisplayName string `json:"displayName"` - // LookupName is the lookup name of the HumioGroup - // +optional - LookupName *string `json:"lookupName,omitempty"` - // Assignments contains the list of role assignments for the group - // +optional - Assignments []HumioGroupRoleAssignment `json:"assignments,omitempty"` + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable" + // +kubebuilder:validation:Required + Name string `json:"name"` + // ExternalMappingName is the mapping name from the external provider that will assign the user to this HumioGroup + // +kubebuilder:validation:MinLength=2 + // +kubebuilder:validation:Optional + ExternalMappingName *string `json:"externalMappingName,omitempty"` } // HumioGroupStatus defines the observed state of HumioGroup. @@ -65,6 +53,7 @@ type HumioGroup struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` + // +kubebuilder:validation:Required Spec HumioGroupSpec `json:"spec,omitempty"` Status HumioGroupStatus `json:"status,omitempty"` } diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 34e5970bb..20fd6a2ce 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -1230,34 +1230,14 @@ func (in *HumioGroupList) DeepCopyObject() runtime.Object { return nil } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *HumioGroupRoleAssignment) DeepCopyInto(out *HumioGroupRoleAssignment) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HumioGroupRoleAssignment. -func (in *HumioGroupRoleAssignment) DeepCopy() *HumioGroupRoleAssignment { - if in == nil { - return nil - } - out := new(HumioGroupRoleAssignment) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HumioGroupSpec) DeepCopyInto(out *HumioGroupSpec) { *out = *in - if in.LookupName != nil { - in, out := &in.LookupName, &out.LookupName + if in.ExternalMappingName != nil { + in, out := &in.ExternalMappingName, &out.ExternalMappingName *out = new(string) **out = **in } - if in.Assignments != nil { - in, out := &in.Assignments, &out.Assignments - *out = make([]HumioGroupRoleAssignment, len(*in)) - copy(*out, *in) - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HumioGroupSpec. diff --git a/charts/humio-operator/crds/core.humio.com_humiogroups.yaml b/charts/humio-operator/crds/core.humio.com_humiogroups.yaml index b20e2c277..c3f217dfb 100644 --- a/charts/humio-operator/crds/core.humio.com_humiogroups.yaml +++ b/charts/humio-operator/crds/core.humio.com_humiogroups.yaml @@ -50,38 +50,15 @@ spec: spec: description: HumioGroupSpec defines the desired state of HumioGroup. properties: - assignments: - description: Assignments contains the list of role assignments for - the group - items: - description: HumioGroupRoleAssignment represents a role assignment - for a group - properties: - roleName: - description: RoleName contains the name of the role to assign - minLength: 1 - type: string - viewName: - description: ViewName contains the name of the view to associate - the group with - minLength: 1 - type: string - required: - - roleName - - viewName - type: object - type: array - displayName: - description: DisplayName is the display name of the HumioGroup - minLength: 1 - type: string externalClusterName: description: |- ExternalClusterName refers to an object of type HumioExternalCluster where the Humio resources should be created. This conflicts with ManagedClusterName. type: string - lookupName: - description: LookupName is the lookup name of the HumioGroup + externalMappingName: + description: ExternalMappingName is the mapping name from the external + provider that will assign the user to this HumioGroup + minLength: 2 type: string managedClusterName: description: |- @@ -89,9 +66,20 @@ spec: resources should be created. This conflicts with ExternalClusterName. type: string + name: + description: Name is the display name of the HumioGroup + minLength: 1 + type: string + x-kubernetes-validations: + - message: Value is immutable + rule: self == oldSelf required: - - displayName + - name type: object + x-kubernetes-validations: + - message: Must specify exactly one of managedClusterName or externalClusterName + rule: (has(self.managedClusterName) && self.managedClusterName != "") + != (has(self.externalClusterName) && self.externalClusterName != "") status: description: HumioGroupStatus defines the observed state of HumioGroup. properties: @@ -99,6 +87,8 @@ spec: description: State reflects the current state of the HumioGroup type: string type: object + required: + - spec type: object served: true storage: true diff --git a/charts/humio-operator/templates/operator-rbac.yaml b/charts/humio-operator/templates/operator-rbac.yaml index 2081b40e2..e8c570871 100644 --- a/charts/humio-operator/templates/operator-rbac.yaml +++ b/charts/humio-operator/templates/operator-rbac.yaml @@ -103,6 +103,9 @@ rules: - humiofilteralerts - humiofilteralerts/finalizers - humiofilteralerts/status + - humiogroups + - humiogroups/finalizers + - humiogroups/status - humiousers - humiousers/finalizers - humiousers/status diff --git a/cmd/main.go b/cmd/main.go index 69eba3fa3..4dc2a0e09 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -32,6 +32,7 @@ import ( "github.com/humio/humio-operator/internal/controller" "github.com/humio/humio-operator/internal/helpers" "github.com/humio/humio-operator/internal/humio" + // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) // to ensure that exec-entrypoint and run can make use of them. _ "k8s.io/client-go/plugin/pkg/client/auth" diff --git a/config/crd/bases/core.humio.com_humiogroups.yaml b/config/crd/bases/core.humio.com_humiogroups.yaml index b20e2c277..c3f217dfb 100644 --- a/config/crd/bases/core.humio.com_humiogroups.yaml +++ b/config/crd/bases/core.humio.com_humiogroups.yaml @@ -50,38 +50,15 @@ spec: spec: description: HumioGroupSpec defines the desired state of HumioGroup. properties: - assignments: - description: Assignments contains the list of role assignments for - the group - items: - description: HumioGroupRoleAssignment represents a role assignment - for a group - properties: - roleName: - description: RoleName contains the name of the role to assign - minLength: 1 - type: string - viewName: - description: ViewName contains the name of the view to associate - the group with - minLength: 1 - type: string - required: - - roleName - - viewName - type: object - type: array - displayName: - description: DisplayName is the display name of the HumioGroup - minLength: 1 - type: string externalClusterName: description: |- ExternalClusterName refers to an object of type HumioExternalCluster where the Humio resources should be created. This conflicts with ManagedClusterName. type: string - lookupName: - description: LookupName is the lookup name of the HumioGroup + externalMappingName: + description: ExternalMappingName is the mapping name from the external + provider that will assign the user to this HumioGroup + minLength: 2 type: string managedClusterName: description: |- @@ -89,9 +66,20 @@ spec: resources should be created. This conflicts with ExternalClusterName. type: string + name: + description: Name is the display name of the HumioGroup + minLength: 1 + type: string + x-kubernetes-validations: + - message: Value is immutable + rule: self == oldSelf required: - - displayName + - name type: object + x-kubernetes-validations: + - message: Must specify exactly one of managedClusterName or externalClusterName + rule: (has(self.managedClusterName) && self.managedClusterName != "") + != (has(self.externalClusterName) && self.externalClusterName != "") status: description: HumioGroupStatus defines the observed state of HumioGroup. properties: @@ -99,6 +87,8 @@ spec: description: State reflects the current state of the HumioGroup type: string type: object + required: + - spec type: object served: true storage: true diff --git a/docs/api.md b/docs/api.md index 589934d91..d21e07d13 100644 --- a/docs/api.md +++ b/docs/api.md @@ -36850,8 +36850,10 @@ HumioGroup is the Schema for the humiogroups API object HumioGroupSpec defines the desired state of HumioGroup.
+
+ Validations:
  • (has(self.managedClusterName) && self.managedClusterName != "") != (has(self.externalClusterName) && self.externalClusterName != ""): Must specify exactly one of managedClusterName or externalClusterName
  • - false + true status object @@ -36880,19 +36882,14 @@ HumioGroupSpec defines the desired state of HumioGroup. - displayName + name string - DisplayName is the display name of the HumioGroup
    + Name is the display name of the HumioGroup
    +
    + Validations:
  • self == oldSelf: Value is immutable
  • true - - assignments - []object - - Assignments contains the list of role assignments for the group
    - - false externalClusterName string @@ -36902,10 +36899,10 @@ This conflicts with ManagedClusterName.
    false - lookupName + externalMappingName string - LookupName is the lookup name of the HumioGroup
    + ExternalMappingName is the mapping name from the external provider that will assign the user to this HumioGroup
    false @@ -36921,40 +36918,6 @@ This conflicts with ExternalClusterName.
    -### HumioGroup.spec.assignments[index] -[↩ Parent](#humiogroupspec) - - - -HumioGroupRoleAssignment represents a role assignment for a group - - - - - - - - - - - - - - - - - - - - - -
    NameTypeDescriptionRequired
    roleNamestring - RoleName contains the name of the role to assign
    -
    true
    viewNamestring - ViewName contains the name of the view to associate the group with
    -
    true
    - - ### HumioGroup.status [↩ Parent](#humiogroup) diff --git a/internal/api/humiographql/graphql/groups.graphql b/internal/api/humiographql/graphql/groups.graphql index 088ad0adc..4f5abeafb 100644 --- a/internal/api/humiographql/graphql/groups.graphql +++ b/internal/api/humiographql/graphql/groups.graphql @@ -2,16 +2,6 @@ fragment GroupDetails on Group { id displayName lookupName - roles { - role { - id - displayName - } - searchDomain { - id - name - } - } } query GetGroupByDisplayName( @@ -33,7 +23,7 @@ mutation CreateGroup( lookupName: $LookupName ) { group { - id + ...GroupDetails } } } @@ -51,7 +41,7 @@ mutation UpdateGroup( } ) { group { - id + ...GroupDetails } } } @@ -63,55 +53,7 @@ mutation DeleteGroup( groupId: $GroupId ) { group { - displayName - userCount - searchDomainCount - } - } -} - -mutation AssignRoleToGroup( - $GroupId: String! - $ViewId: String! - $RoleId: String! -) { - assignRoleToGroup( - input: { - groupId: $GroupId - viewId: $ViewId - roleId: $RoleId - } - ) { - group { - role { - id - displayName - } - searchDomain { - id - name - } - } - } -} - -mutation UnassignRoleFromGroup( - $GroupId: String! - $ViewId: String! - $RoleId: String! -) { - unassignRoleFromGroup( - input: { - groupId: $GroupId - viewId: $ViewId - roleId: $RoleId - } - ) { - group { - id - displayName - userCount - searchDomainCount + ...GroupDetails } } } \ No newline at end of file diff --git a/internal/api/humiographql/humiographql.go b/internal/api/humiographql/humiographql.go index c842cf909..8b553e51f 100644 --- a/internal/api/humiographql/humiographql.go +++ b/internal/api/humiographql/humiographql.go @@ -1589,274 +1589,6 @@ func (v *AssignParserToIngestTokenResponse) GetAssignParserToIngestTokenV2() Ass return v.AssignParserToIngestTokenV2 } -// AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutation includes the requested fields of the GraphQL type AssignRoleToGroupMutation. -type AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutation struct { - // Stability: Long-term - Group AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRole `json:"group"` -} - -// GetGroup returns AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutation.Group, and is useful for accessing the field via an interface. -func (v *AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutation) GetGroup() AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRole { - return v.Group -} - -// AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRole includes the requested fields of the GraphQL type SearchDomainRole. -// The GraphQL type's documentation follows. -// -// The role assigned in a searchDomain. -type AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRole struct { - // Stability: Long-term - Role AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleRole `json:"role"` - // Stability: Long-term - SearchDomain AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomain `json:"-"` -} - -// GetRole returns AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRole.Role, and is useful for accessing the field via an interface. -func (v *AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRole) GetRole() AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleRole { - return v.Role -} - -// GetSearchDomain returns AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRole.SearchDomain, and is useful for accessing the field via an interface. -func (v *AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRole) GetSearchDomain() AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomain { - return v.SearchDomain -} - -func (v *AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRole) UnmarshalJSON(b []byte) error { - - if string(b) == "null" { - return nil - } - - var firstPass struct { - *AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRole - SearchDomain json.RawMessage `json:"searchDomain"` - graphql.NoUnmarshalJSON - } - firstPass.AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRole = v - - err := json.Unmarshal(b, &firstPass) - if err != nil { - return err - } - - { - dst := &v.SearchDomain - src := firstPass.SearchDomain - if len(src) != 0 && string(src) != "null" { - err = __unmarshalAssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomain( - src, dst) - if err != nil { - return fmt.Errorf( - "unable to unmarshal AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRole.SearchDomain: %w", err) - } - } - } - return nil -} - -type __premarshalAssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRole struct { - Role AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleRole `json:"role"` - - SearchDomain json.RawMessage `json:"searchDomain"` -} - -func (v *AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRole) MarshalJSON() ([]byte, error) { - premarshaled, err := v.__premarshalJSON() - if err != nil { - return nil, err - } - return json.Marshal(premarshaled) -} - -func (v *AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRole) __premarshalJSON() (*__premarshalAssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRole, error) { - var retval __premarshalAssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRole - - retval.Role = v.Role - { - - dst := &retval.SearchDomain - src := v.SearchDomain - var err error - *dst, err = __marshalAssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomain( - &src) - if err != nil { - return nil, fmt.Errorf( - "unable to marshal AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRole.SearchDomain: %w", err) - } - } - return &retval, nil -} - -// AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleRole includes the requested fields of the GraphQL type Role. -type AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleRole struct { - // Stability: Long-term - Id string `json:"id"` - // Stability: Long-term - DisplayName string `json:"displayName"` -} - -// GetId returns AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleRole.Id, and is useful for accessing the field via an interface. -func (v *AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleRole) GetId() string { - return v.Id -} - -// GetDisplayName returns AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleRole.DisplayName, and is useful for accessing the field via an interface. -func (v *AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleRole) GetDisplayName() string { - return v.DisplayName -} - -// AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomain includes the requested fields of the GraphQL interface SearchDomain. -// -// AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomain is implemented by the following types: -// AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainRepository -// AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainView -// The GraphQL type's documentation follows. -// -// Common interface for Repositories and Views. -type AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomain interface { - implementsGraphQLInterfaceAssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomain() - // GetTypename returns the receiver's concrete GraphQL type-name (see interface doc for possible values). - GetTypename() *string - // GetId returns the interface-field "id" from its implementation. - // The GraphQL interface field's documentation follows. - // - // Common interface for Repositories and Views. - GetId() string - // GetName returns the interface-field "name" from its implementation. - // The GraphQL interface field's documentation follows. - // - // Common interface for Repositories and Views. - GetName() string -} - -func (v *AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainRepository) implementsGraphQLInterfaceAssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomain() { -} -func (v *AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainView) implementsGraphQLInterfaceAssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomain() { -} - -func __unmarshalAssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomain(b []byte, v *AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomain) error { - if string(b) == "null" { - return nil - } - - var tn struct { - TypeName string `json:"__typename"` - } - err := json.Unmarshal(b, &tn) - if err != nil { - return err - } - - switch tn.TypeName { - case "Repository": - *v = new(AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainRepository) - return json.Unmarshal(b, *v) - case "View": - *v = new(AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainView) - return json.Unmarshal(b, *v) - case "": - return fmt.Errorf( - "response was missing SearchDomain.__typename") - default: - return fmt.Errorf( - `unexpected concrete type for AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomain: "%v"`, tn.TypeName) - } -} - -func __marshalAssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomain(v *AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomain) ([]byte, error) { - - var typename string - switch v := (*v).(type) { - case *AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainRepository: - typename = "Repository" - - result := struct { - TypeName string `json:"__typename"` - *AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainRepository - }{typename, v} - return json.Marshal(result) - case *AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainView: - typename = "View" - - result := struct { - TypeName string `json:"__typename"` - *AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainView - }{typename, v} - return json.Marshal(result) - case nil: - return []byte("null"), nil - default: - return nil, fmt.Errorf( - `unexpected concrete type for AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomain: "%T"`, v) - } -} - -// AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainRepository includes the requested fields of the GraphQL type Repository. -// The GraphQL type's documentation follows. -// -// A repository stores ingested data, configures parsers and data retention policies. -type AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainRepository struct { - Typename *string `json:"__typename"` - // Common interface for Repositories and Views. - Id string `json:"id"` - // Common interface for Repositories and Views. - Name string `json:"name"` -} - -// GetTypename returns AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainRepository.Typename, and is useful for accessing the field via an interface. -func (v *AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainRepository) GetTypename() *string { - return v.Typename -} - -// GetId returns AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainRepository.Id, and is useful for accessing the field via an interface. -func (v *AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainRepository) GetId() string { - return v.Id -} - -// GetName returns AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainRepository.Name, and is useful for accessing the field via an interface. -func (v *AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainRepository) GetName() string { - return v.Name -} - -// AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainView includes the requested fields of the GraphQL type View. -// The GraphQL type's documentation follows. -// -// Represents information about a view, pulling data from one or several repositories. -type AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainView struct { - Typename *string `json:"__typename"` - // Common interface for Repositories and Views. - Id string `json:"id"` - // Common interface for Repositories and Views. - Name string `json:"name"` -} - -// GetTypename returns AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainView.Typename, and is useful for accessing the field via an interface. -func (v *AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainView) GetTypename() *string { - return v.Typename -} - -// GetId returns AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainView.Id, and is useful for accessing the field via an interface. -func (v *AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainView) GetId() string { - return v.Id -} - -// GetName returns AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainView.Name, and is useful for accessing the field via an interface. -func (v *AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutationGroupSearchDomainRoleSearchDomainView) GetName() string { - return v.Name -} - -// AssignRoleToGroupResponse is returned by AssignRoleToGroup on success. -type AssignRoleToGroupResponse struct { - // Assigns a role to a group for a given view. If called with overrideExistingAssignmentsForView=false, this mutation can assign multiple roles for the same view. Calling with overrideExistingAssignmentsForView=false is thus only available if the MultipleViewRoleBindings feature is enabled. - // Stability: Long-term - AssignRoleToGroup AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutation `json:"assignRoleToGroup"` -} - -// GetAssignRoleToGroup returns AssignRoleToGroupResponse.AssignRoleToGroup, and is useful for accessing the field via an interface. -func (v *AssignRoleToGroupResponse) GetAssignRoleToGroup() AssignRoleToGroupAssignRoleToGroupAssignRoleToGroupMutation { - return v.AssignRoleToGroup -} - // CreateAggregateAlertCreateAggregateAlert includes the requested fields of the GraphQL type AggregateAlert. // The GraphQL type's documentation follows. // @@ -2407,12 +2139,71 @@ func (v *CreateGroupAddGroupAddGroupMutation) GetGroup() CreateGroupAddGroupAddG // // A group. type CreateGroupAddGroupAddGroupMutationGroup struct { - // Stability: Long-term - Id string `json:"id"` + GroupDetails `json:"-"` } // GetId returns CreateGroupAddGroupAddGroupMutationGroup.Id, and is useful for accessing the field via an interface. -func (v *CreateGroupAddGroupAddGroupMutationGroup) GetId() string { return v.Id } +func (v *CreateGroupAddGroupAddGroupMutationGroup) GetId() string { return v.GroupDetails.Id } + +// GetDisplayName returns CreateGroupAddGroupAddGroupMutationGroup.DisplayName, and is useful for accessing the field via an interface. +func (v *CreateGroupAddGroupAddGroupMutationGroup) GetDisplayName() string { + return v.GroupDetails.DisplayName +} + +// GetLookupName returns CreateGroupAddGroupAddGroupMutationGroup.LookupName, and is useful for accessing the field via an interface. +func (v *CreateGroupAddGroupAddGroupMutationGroup) GetLookupName() *string { + return v.GroupDetails.LookupName +} + +func (v *CreateGroupAddGroupAddGroupMutationGroup) UnmarshalJSON(b []byte) error { + + if string(b) == "null" { + return nil + } + + var firstPass struct { + *CreateGroupAddGroupAddGroupMutationGroup + graphql.NoUnmarshalJSON + } + firstPass.CreateGroupAddGroupAddGroupMutationGroup = v + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + err = json.Unmarshal( + b, &v.GroupDetails) + if err != nil { + return err + } + return nil +} + +type __premarshalCreateGroupAddGroupAddGroupMutationGroup struct { + Id string `json:"id"` + + DisplayName string `json:"displayName"` + + LookupName *string `json:"lookupName"` +} + +func (v *CreateGroupAddGroupAddGroupMutationGroup) MarshalJSON() ([]byte, error) { + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err + } + return json.Marshal(premarshaled) +} + +func (v *CreateGroupAddGroupAddGroupMutationGroup) __premarshalJSON() (*__premarshalCreateGroupAddGroupAddGroupMutationGroup, error) { + var retval __premarshalCreateGroupAddGroupAddGroupMutationGroup + + retval.Id = v.GroupDetails.Id + retval.DisplayName = v.GroupDetails.DisplayName + retval.LookupName = v.GroupDetails.LookupName + return &retval, nil +} // CreateGroupResponse is returned by CreateGroup on success. type CreateGroupResponse struct { @@ -3240,25 +3031,70 @@ func (v *DeleteGroupRemoveGroupRemoveGroupMutation) GetGroup() DeleteGroupRemove // // A group. type DeleteGroupRemoveGroupRemoveGroupMutationGroup struct { - // Stability: Long-term - DisplayName string `json:"displayName"` - // Stability: Long-term - UserCount int `json:"userCount"` - // Stability: Long-term - SearchDomainCount int `json:"searchDomainCount"` + GroupDetails `json:"-"` } +// GetId returns DeleteGroupRemoveGroupRemoveGroupMutationGroup.Id, and is useful for accessing the field via an interface. +func (v *DeleteGroupRemoveGroupRemoveGroupMutationGroup) GetId() string { return v.GroupDetails.Id } + // GetDisplayName returns DeleteGroupRemoveGroupRemoveGroupMutationGroup.DisplayName, and is useful for accessing the field via an interface. func (v *DeleteGroupRemoveGroupRemoveGroupMutationGroup) GetDisplayName() string { - return v.DisplayName + return v.GroupDetails.DisplayName +} + +// GetLookupName returns DeleteGroupRemoveGroupRemoveGroupMutationGroup.LookupName, and is useful for accessing the field via an interface. +func (v *DeleteGroupRemoveGroupRemoveGroupMutationGroup) GetLookupName() *string { + return v.GroupDetails.LookupName +} + +func (v *DeleteGroupRemoveGroupRemoveGroupMutationGroup) UnmarshalJSON(b []byte) error { + + if string(b) == "null" { + return nil + } + + var firstPass struct { + *DeleteGroupRemoveGroupRemoveGroupMutationGroup + graphql.NoUnmarshalJSON + } + firstPass.DeleteGroupRemoveGroupRemoveGroupMutationGroup = v + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + err = json.Unmarshal( + b, &v.GroupDetails) + if err != nil { + return err + } + return nil +} + +type __premarshalDeleteGroupRemoveGroupRemoveGroupMutationGroup struct { + Id string `json:"id"` + + DisplayName string `json:"displayName"` + + LookupName *string `json:"lookupName"` +} + +func (v *DeleteGroupRemoveGroupRemoveGroupMutationGroup) MarshalJSON() ([]byte, error) { + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err + } + return json.Marshal(premarshaled) } -// GetUserCount returns DeleteGroupRemoveGroupRemoveGroupMutationGroup.UserCount, and is useful for accessing the field via an interface. -func (v *DeleteGroupRemoveGroupRemoveGroupMutationGroup) GetUserCount() int { return v.UserCount } +func (v *DeleteGroupRemoveGroupRemoveGroupMutationGroup) __premarshalJSON() (*__premarshalDeleteGroupRemoveGroupRemoveGroupMutationGroup, error) { + var retval __premarshalDeleteGroupRemoveGroupRemoveGroupMutationGroup -// GetSearchDomainCount returns DeleteGroupRemoveGroupRemoveGroupMutationGroup.SearchDomainCount, and is useful for accessing the field via an interface. -func (v *DeleteGroupRemoveGroupRemoveGroupMutationGroup) GetSearchDomainCount() int { - return v.SearchDomainCount + retval.Id = v.GroupDetails.Id + retval.DisplayName = v.GroupDetails.DisplayName + retval.LookupName = v.GroupDetails.LookupName + return &retval, nil } // DeleteGroupResponse is returned by DeleteGroup on success. @@ -6146,11 +5982,6 @@ func (v *GetGroupByDisplayNameGroupByDisplayNameGroup) GetLookupName() *string { return v.GroupDetails.LookupName } -// GetRoles returns GetGroupByDisplayNameGroupByDisplayNameGroup.Roles, and is useful for accessing the field via an interface. -func (v *GetGroupByDisplayNameGroupByDisplayNameGroup) GetRoles() []GroupDetailsRolesSearchDomainRole { - return v.GroupDetails.Roles -} - func (v *GetGroupByDisplayNameGroupByDisplayNameGroup) UnmarshalJSON(b []byte) error { if string(b) == "null" { @@ -6182,8 +6013,6 @@ type __premarshalGetGroupByDisplayNameGroupByDisplayNameGroup struct { DisplayName string `json:"displayName"` LookupName *string `json:"lookupName"` - - Roles []GroupDetailsRolesSearchDomainRole `json:"roles"` } func (v *GetGroupByDisplayNameGroupByDisplayNameGroup) MarshalJSON() ([]byte, error) { @@ -6200,7 +6029,6 @@ func (v *GetGroupByDisplayNameGroupByDisplayNameGroup) __premarshalJSON() (*__pr retval.Id = v.GroupDetails.Id retval.DisplayName = v.GroupDetails.DisplayName retval.LookupName = v.GroupDetails.LookupName - retval.Roles = v.GroupDetails.Roles return &retval, nil } @@ -7339,303 +7167,67 @@ func (v *GetUsersByUsernameUsersUser) UnmarshalJSON(b []byte) error { *GetUsersByUsernameUsersUser graphql.NoUnmarshalJSON } - firstPass.GetUsersByUsernameUsersUser = v - - err := json.Unmarshal(b, &firstPass) - if err != nil { - return err - } - - err = json.Unmarshal( - b, &v.UserDetails) - if err != nil { - return err - } - return nil -} - -type __premarshalGetUsersByUsernameUsersUser struct { - Id string `json:"id"` - - Username string `json:"username"` - - IsRoot bool `json:"isRoot"` -} - -func (v *GetUsersByUsernameUsersUser) MarshalJSON() ([]byte, error) { - premarshaled, err := v.__premarshalJSON() - if err != nil { - return nil, err - } - return json.Marshal(premarshaled) -} - -func (v *GetUsersByUsernameUsersUser) __premarshalJSON() (*__premarshalGetUsersByUsernameUsersUser, error) { - var retval __premarshalGetUsersByUsernameUsersUser - - retval.Id = v.UserDetails.Id - retval.Username = v.UserDetails.Username - retval.IsRoot = v.UserDetails.IsRoot - return &retval, nil -} - -// GroupDetails includes the GraphQL fields of Group requested by the fragment GroupDetails. -// The GraphQL type's documentation follows. -// -// A group. -type GroupDetails struct { - // Stability: Long-term - Id string `json:"id"` - // Stability: Long-term - DisplayName string `json:"displayName"` - // Stability: Long-term - LookupName *string `json:"lookupName"` - // Stability: Long-term - Roles []GroupDetailsRolesSearchDomainRole `json:"roles"` -} - -// GetId returns GroupDetails.Id, and is useful for accessing the field via an interface. -func (v *GroupDetails) GetId() string { return v.Id } - -// GetDisplayName returns GroupDetails.DisplayName, and is useful for accessing the field via an interface. -func (v *GroupDetails) GetDisplayName() string { return v.DisplayName } - -// GetLookupName returns GroupDetails.LookupName, and is useful for accessing the field via an interface. -func (v *GroupDetails) GetLookupName() *string { return v.LookupName } - -// GetRoles returns GroupDetails.Roles, and is useful for accessing the field via an interface. -func (v *GroupDetails) GetRoles() []GroupDetailsRolesSearchDomainRole { return v.Roles } - -// GroupDetailsRolesSearchDomainRole includes the requested fields of the GraphQL type SearchDomainRole. -// The GraphQL type's documentation follows. -// -// The role assigned in a searchDomain. -type GroupDetailsRolesSearchDomainRole struct { - // Stability: Long-term - Role GroupDetailsRolesSearchDomainRoleRole `json:"role"` - // Stability: Long-term - SearchDomain GroupDetailsRolesSearchDomainRoleSearchDomain `json:"-"` -} - -// GetRole returns GroupDetailsRolesSearchDomainRole.Role, and is useful for accessing the field via an interface. -func (v *GroupDetailsRolesSearchDomainRole) GetRole() GroupDetailsRolesSearchDomainRoleRole { - return v.Role -} - -// GetSearchDomain returns GroupDetailsRolesSearchDomainRole.SearchDomain, and is useful for accessing the field via an interface. -func (v *GroupDetailsRolesSearchDomainRole) GetSearchDomain() GroupDetailsRolesSearchDomainRoleSearchDomain { - return v.SearchDomain -} - -func (v *GroupDetailsRolesSearchDomainRole) UnmarshalJSON(b []byte) error { - - if string(b) == "null" { - return nil - } - - var firstPass struct { - *GroupDetailsRolesSearchDomainRole - SearchDomain json.RawMessage `json:"searchDomain"` - graphql.NoUnmarshalJSON - } - firstPass.GroupDetailsRolesSearchDomainRole = v - - err := json.Unmarshal(b, &firstPass) - if err != nil { - return err - } - - { - dst := &v.SearchDomain - src := firstPass.SearchDomain - if len(src) != 0 && string(src) != "null" { - err = __unmarshalGroupDetailsRolesSearchDomainRoleSearchDomain( - src, dst) - if err != nil { - return fmt.Errorf( - "unable to unmarshal GroupDetailsRolesSearchDomainRole.SearchDomain: %w", err) - } - } - } - return nil -} - -type __premarshalGroupDetailsRolesSearchDomainRole struct { - Role GroupDetailsRolesSearchDomainRoleRole `json:"role"` - - SearchDomain json.RawMessage `json:"searchDomain"` -} - -func (v *GroupDetailsRolesSearchDomainRole) MarshalJSON() ([]byte, error) { - premarshaled, err := v.__premarshalJSON() - if err != nil { - return nil, err - } - return json.Marshal(premarshaled) -} - -func (v *GroupDetailsRolesSearchDomainRole) __premarshalJSON() (*__premarshalGroupDetailsRolesSearchDomainRole, error) { - var retval __premarshalGroupDetailsRolesSearchDomainRole - - retval.Role = v.Role - { - - dst := &retval.SearchDomain - src := v.SearchDomain - var err error - *dst, err = __marshalGroupDetailsRolesSearchDomainRoleSearchDomain( - &src) - if err != nil { - return nil, fmt.Errorf( - "unable to marshal GroupDetailsRolesSearchDomainRole.SearchDomain: %w", err) - } - } - return &retval, nil -} - -// GroupDetailsRolesSearchDomainRoleRole includes the requested fields of the GraphQL type Role. -type GroupDetailsRolesSearchDomainRoleRole struct { - // Stability: Long-term - Id string `json:"id"` - // Stability: Long-term - DisplayName string `json:"displayName"` -} - -// GetId returns GroupDetailsRolesSearchDomainRoleRole.Id, and is useful for accessing the field via an interface. -func (v *GroupDetailsRolesSearchDomainRoleRole) GetId() string { return v.Id } - -// GetDisplayName returns GroupDetailsRolesSearchDomainRoleRole.DisplayName, and is useful for accessing the field via an interface. -func (v *GroupDetailsRolesSearchDomainRoleRole) GetDisplayName() string { return v.DisplayName } - -// GroupDetailsRolesSearchDomainRoleSearchDomain includes the requested fields of the GraphQL interface SearchDomain. -// -// GroupDetailsRolesSearchDomainRoleSearchDomain is implemented by the following types: -// GroupDetailsRolesSearchDomainRoleSearchDomainRepository -// GroupDetailsRolesSearchDomainRoleSearchDomainView -// The GraphQL type's documentation follows. -// -// Common interface for Repositories and Views. -type GroupDetailsRolesSearchDomainRoleSearchDomain interface { - implementsGraphQLInterfaceGroupDetailsRolesSearchDomainRoleSearchDomain() - // GetTypename returns the receiver's concrete GraphQL type-name (see interface doc for possible values). - GetTypename() *string - // GetId returns the interface-field "id" from its implementation. - // The GraphQL interface field's documentation follows. - // - // Common interface for Repositories and Views. - GetId() string - // GetName returns the interface-field "name" from its implementation. - // The GraphQL interface field's documentation follows. - // - // Common interface for Repositories and Views. - GetName() string -} - -func (v *GroupDetailsRolesSearchDomainRoleSearchDomainRepository) implementsGraphQLInterfaceGroupDetailsRolesSearchDomainRoleSearchDomain() { -} -func (v *GroupDetailsRolesSearchDomainRoleSearchDomainView) implementsGraphQLInterfaceGroupDetailsRolesSearchDomainRoleSearchDomain() { -} - -func __unmarshalGroupDetailsRolesSearchDomainRoleSearchDomain(b []byte, v *GroupDetailsRolesSearchDomainRoleSearchDomain) error { - if string(b) == "null" { - return nil - } + firstPass.GetUsersByUsernameUsersUser = v - var tn struct { - TypeName string `json:"__typename"` - } - err := json.Unmarshal(b, &tn) + err := json.Unmarshal(b, &firstPass) if err != nil { return err } - switch tn.TypeName { - case "Repository": - *v = new(GroupDetailsRolesSearchDomainRoleSearchDomainRepository) - return json.Unmarshal(b, *v) - case "View": - *v = new(GroupDetailsRolesSearchDomainRoleSearchDomainView) - return json.Unmarshal(b, *v) - case "": - return fmt.Errorf( - "response was missing SearchDomain.__typename") - default: - return fmt.Errorf( - `unexpected concrete type for GroupDetailsRolesSearchDomainRoleSearchDomain: "%v"`, tn.TypeName) + err = json.Unmarshal( + b, &v.UserDetails) + if err != nil { + return err } + return nil } -func __marshalGroupDetailsRolesSearchDomainRoleSearchDomain(v *GroupDetailsRolesSearchDomainRoleSearchDomain) ([]byte, error) { +type __premarshalGetUsersByUsernameUsersUser struct { + Id string `json:"id"` - var typename string - switch v := (*v).(type) { - case *GroupDetailsRolesSearchDomainRoleSearchDomainRepository: - typename = "Repository" + Username string `json:"username"` - result := struct { - TypeName string `json:"__typename"` - *GroupDetailsRolesSearchDomainRoleSearchDomainRepository - }{typename, v} - return json.Marshal(result) - case *GroupDetailsRolesSearchDomainRoleSearchDomainView: - typename = "View" + IsRoot bool `json:"isRoot"` +} - result := struct { - TypeName string `json:"__typename"` - *GroupDetailsRolesSearchDomainRoleSearchDomainView - }{typename, v} - return json.Marshal(result) - case nil: - return []byte("null"), nil - default: - return nil, fmt.Errorf( - `unexpected concrete type for GroupDetailsRolesSearchDomainRoleSearchDomain: "%T"`, v) +func (v *GetUsersByUsernameUsersUser) MarshalJSON() ([]byte, error) { + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err } + return json.Marshal(premarshaled) } -// GroupDetailsRolesSearchDomainRoleSearchDomainRepository includes the requested fields of the GraphQL type Repository. -// The GraphQL type's documentation follows. -// -// A repository stores ingested data, configures parsers and data retention policies. -type GroupDetailsRolesSearchDomainRoleSearchDomainRepository struct { - Typename *string `json:"__typename"` - // Common interface for Repositories and Views. - Id string `json:"id"` - // Common interface for Repositories and Views. - Name string `json:"name"` -} +func (v *GetUsersByUsernameUsersUser) __premarshalJSON() (*__premarshalGetUsersByUsernameUsersUser, error) { + var retval __premarshalGetUsersByUsernameUsersUser -// GetTypename returns GroupDetailsRolesSearchDomainRoleSearchDomainRepository.Typename, and is useful for accessing the field via an interface. -func (v *GroupDetailsRolesSearchDomainRoleSearchDomainRepository) GetTypename() *string { - return v.Typename + retval.Id = v.UserDetails.Id + retval.Username = v.UserDetails.Username + retval.IsRoot = v.UserDetails.IsRoot + return &retval, nil } -// GetId returns GroupDetailsRolesSearchDomainRoleSearchDomainRepository.Id, and is useful for accessing the field via an interface. -func (v *GroupDetailsRolesSearchDomainRoleSearchDomainRepository) GetId() string { return v.Id } - -// GetName returns GroupDetailsRolesSearchDomainRoleSearchDomainRepository.Name, and is useful for accessing the field via an interface. -func (v *GroupDetailsRolesSearchDomainRoleSearchDomainRepository) GetName() string { return v.Name } - -// GroupDetailsRolesSearchDomainRoleSearchDomainView includes the requested fields of the GraphQL type View. +// GroupDetails includes the GraphQL fields of Group requested by the fragment GroupDetails. // The GraphQL type's documentation follows. // -// Represents information about a view, pulling data from one or several repositories. -type GroupDetailsRolesSearchDomainRoleSearchDomainView struct { - Typename *string `json:"__typename"` - // Common interface for Repositories and Views. +// A group. +type GroupDetails struct { + // Stability: Long-term Id string `json:"id"` - // Common interface for Repositories and Views. - Name string `json:"name"` + // Stability: Long-term + DisplayName string `json:"displayName"` + // Stability: Long-term + LookupName *string `json:"lookupName"` } -// GetTypename returns GroupDetailsRolesSearchDomainRoleSearchDomainView.Typename, and is useful for accessing the field via an interface. -func (v *GroupDetailsRolesSearchDomainRoleSearchDomainView) GetTypename() *string { return v.Typename } +// GetId returns GroupDetails.Id, and is useful for accessing the field via an interface. +func (v *GroupDetails) GetId() string { return v.Id } -// GetId returns GroupDetailsRolesSearchDomainRoleSearchDomainView.Id, and is useful for accessing the field via an interface. -func (v *GroupDetailsRolesSearchDomainRoleSearchDomainView) GetId() string { return v.Id } +// GetDisplayName returns GroupDetails.DisplayName, and is useful for accessing the field via an interface. +func (v *GroupDetails) GetDisplayName() string { return v.DisplayName } -// GetName returns GroupDetailsRolesSearchDomainRoleSearchDomainView.Name, and is useful for accessing the field via an interface. -func (v *GroupDetailsRolesSearchDomainRoleSearchDomainView) GetName() string { return v.Name } +// GetLookupName returns GroupDetails.LookupName, and is useful for accessing the field via an interface. +func (v *GroupDetails) GetLookupName() *string { return v.LookupName } // Http(s) Header entry. type HttpHeaderEntryInput struct { @@ -12846,60 +12438,6 @@ func (v *UnassignParserToIngestTokenUnassignIngestTokenUnassignIngestTokenMutati return v.Typename } -// UnassignRoleFromGroupResponse is returned by UnassignRoleFromGroup on success. -type UnassignRoleFromGroupResponse struct { - // Removes the role assigned to the group for a given view. - // Stability: Long-term - UnassignRoleFromGroup UnassignRoleFromGroupUnassignRoleFromGroup `json:"unassignRoleFromGroup"` -} - -// GetUnassignRoleFromGroup returns UnassignRoleFromGroupResponse.UnassignRoleFromGroup, and is useful for accessing the field via an interface. -func (v *UnassignRoleFromGroupResponse) GetUnassignRoleFromGroup() UnassignRoleFromGroupUnassignRoleFromGroup { - return v.UnassignRoleFromGroup -} - -// UnassignRoleFromGroupUnassignRoleFromGroup includes the requested fields of the GraphQL type UnassignRoleFromGroup. -type UnassignRoleFromGroupUnassignRoleFromGroup struct { - // Stability: Long-term - Group UnassignRoleFromGroupUnassignRoleFromGroupGroup `json:"group"` -} - -// GetGroup returns UnassignRoleFromGroupUnassignRoleFromGroup.Group, and is useful for accessing the field via an interface. -func (v *UnassignRoleFromGroupUnassignRoleFromGroup) GetGroup() UnassignRoleFromGroupUnassignRoleFromGroupGroup { - return v.Group -} - -// UnassignRoleFromGroupUnassignRoleFromGroupGroup includes the requested fields of the GraphQL type Group. -// The GraphQL type's documentation follows. -// -// A group. -type UnassignRoleFromGroupUnassignRoleFromGroupGroup struct { - // Stability: Long-term - Id string `json:"id"` - // Stability: Long-term - DisplayName string `json:"displayName"` - // Stability: Long-term - UserCount int `json:"userCount"` - // Stability: Long-term - SearchDomainCount int `json:"searchDomainCount"` -} - -// GetId returns UnassignRoleFromGroupUnassignRoleFromGroupGroup.Id, and is useful for accessing the field via an interface. -func (v *UnassignRoleFromGroupUnassignRoleFromGroupGroup) GetId() string { return v.Id } - -// GetDisplayName returns UnassignRoleFromGroupUnassignRoleFromGroupGroup.DisplayName, and is useful for accessing the field via an interface. -func (v *UnassignRoleFromGroupUnassignRoleFromGroupGroup) GetDisplayName() string { - return v.DisplayName -} - -// GetUserCount returns UnassignRoleFromGroupUnassignRoleFromGroupGroup.UserCount, and is useful for accessing the field via an interface. -func (v *UnassignRoleFromGroupUnassignRoleFromGroupGroup) GetUserCount() int { return v.UserCount } - -// GetSearchDomainCount returns UnassignRoleFromGroupUnassignRoleFromGroupGroup.SearchDomainCount, and is useful for accessing the field via an interface. -func (v *UnassignRoleFromGroupUnassignRoleFromGroupGroup) GetSearchDomainCount() int { - return v.SearchDomainCount -} - // UnregisterClusterNodeClusterUnregisterNodeUnregisterNodeMutation includes the requested fields of the GraphQL type UnregisterNodeMutation. type UnregisterClusterNodeClusterUnregisterNodeUnregisterNodeMutation struct { // Stability: Long-term @@ -13534,12 +13072,71 @@ func (v *UpdateGroupUpdateGroupUpdateGroupMutation) GetGroup() UpdateGroupUpdate // // A group. type UpdateGroupUpdateGroupUpdateGroupMutationGroup struct { - // Stability: Long-term - Id string `json:"id"` + GroupDetails `json:"-"` } // GetId returns UpdateGroupUpdateGroupUpdateGroupMutationGroup.Id, and is useful for accessing the field via an interface. -func (v *UpdateGroupUpdateGroupUpdateGroupMutationGroup) GetId() string { return v.Id } +func (v *UpdateGroupUpdateGroupUpdateGroupMutationGroup) GetId() string { return v.GroupDetails.Id } + +// GetDisplayName returns UpdateGroupUpdateGroupUpdateGroupMutationGroup.DisplayName, and is useful for accessing the field via an interface. +func (v *UpdateGroupUpdateGroupUpdateGroupMutationGroup) GetDisplayName() string { + return v.GroupDetails.DisplayName +} + +// GetLookupName returns UpdateGroupUpdateGroupUpdateGroupMutationGroup.LookupName, and is useful for accessing the field via an interface. +func (v *UpdateGroupUpdateGroupUpdateGroupMutationGroup) GetLookupName() *string { + return v.GroupDetails.LookupName +} + +func (v *UpdateGroupUpdateGroupUpdateGroupMutationGroup) UnmarshalJSON(b []byte) error { + + if string(b) == "null" { + return nil + } + + var firstPass struct { + *UpdateGroupUpdateGroupUpdateGroupMutationGroup + graphql.NoUnmarshalJSON + } + firstPass.UpdateGroupUpdateGroupUpdateGroupMutationGroup = v + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + err = json.Unmarshal( + b, &v.GroupDetails) + if err != nil { + return err + } + return nil +} + +type __premarshalUpdateGroupUpdateGroupUpdateGroupMutationGroup struct { + Id string `json:"id"` + + DisplayName string `json:"displayName"` + + LookupName *string `json:"lookupName"` +} + +func (v *UpdateGroupUpdateGroupUpdateGroupMutationGroup) MarshalJSON() ([]byte, error) { + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err + } + return json.Marshal(premarshaled) +} + +func (v *UpdateGroupUpdateGroupUpdateGroupMutationGroup) __premarshalJSON() (*__premarshalUpdateGroupUpdateGroupUpdateGroupMutationGroup, error) { + var retval __premarshalUpdateGroupUpdateGroupUpdateGroupMutationGroup + + retval.Id = v.GroupDetails.Id + retval.DisplayName = v.GroupDetails.DisplayName + retval.LookupName = v.GroupDetails.LookupName + return &retval, nil +} // UpdateHumioRepoActionResponse is returned by UpdateHumioRepoAction on success. type UpdateHumioRepoActionResponse struct { @@ -14359,22 +13956,6 @@ func (v *__AssignParserToIngestTokenInput) GetIngestTokenName() string { return // GetParserName returns __AssignParserToIngestTokenInput.ParserName, and is useful for accessing the field via an interface. func (v *__AssignParserToIngestTokenInput) GetParserName() string { return v.ParserName } -// __AssignRoleToGroupInput is used internally by genqlient -type __AssignRoleToGroupInput struct { - GroupId string `json:"GroupId"` - ViewId string `json:"ViewId"` - RoleId string `json:"RoleId"` -} - -// GetGroupId returns __AssignRoleToGroupInput.GroupId, and is useful for accessing the field via an interface. -func (v *__AssignRoleToGroupInput) GetGroupId() string { return v.GroupId } - -// GetViewId returns __AssignRoleToGroupInput.ViewId, and is useful for accessing the field via an interface. -func (v *__AssignRoleToGroupInput) GetViewId() string { return v.ViewId } - -// GetRoleId returns __AssignRoleToGroupInput.RoleId, and is useful for accessing the field via an interface. -func (v *__AssignRoleToGroupInput) GetRoleId() string { return v.RoleId } - // __CreateAggregateAlertInput is used internally by genqlient type __CreateAggregateAlertInput struct { SearchDomainName string `json:"SearchDomainName"` @@ -15243,22 +14824,6 @@ func (v *__UnassignParserToIngestTokenInput) GetRepositoryName() string { return // GetIngestTokenName returns __UnassignParserToIngestTokenInput.IngestTokenName, and is useful for accessing the field via an interface. func (v *__UnassignParserToIngestTokenInput) GetIngestTokenName() string { return v.IngestTokenName } -// __UnassignRoleFromGroupInput is used internally by genqlient -type __UnassignRoleFromGroupInput struct { - GroupId string `json:"GroupId"` - ViewId string `json:"ViewId"` - RoleId string `json:"RoleId"` -} - -// GetGroupId returns __UnassignRoleFromGroupInput.GroupId, and is useful for accessing the field via an interface. -func (v *__UnassignRoleFromGroupInput) GetGroupId() string { return v.GroupId } - -// GetViewId returns __UnassignRoleFromGroupInput.ViewId, and is useful for accessing the field via an interface. -func (v *__UnassignRoleFromGroupInput) GetViewId() string { return v.ViewId } - -// GetRoleId returns __UnassignRoleFromGroupInput.RoleId, and is useful for accessing the field via an interface. -func (v *__UnassignRoleFromGroupInput) GetRoleId() string { return v.RoleId } - // __UnregisterClusterNodeInput is used internally by genqlient type __UnregisterClusterNodeInput struct { NodeId int `json:"NodeId"` @@ -15982,54 +15547,6 @@ func AssignParserToIngestToken( return data_, err_ } -// The mutation executed by AssignRoleToGroup. -const AssignRoleToGroup_Operation = ` -mutation AssignRoleToGroup ($GroupId: String!, $ViewId: String!, $RoleId: String!) { - assignRoleToGroup(input: {groupId:$GroupId,viewId:$ViewId,roleId:$RoleId}) { - group { - role { - id - displayName - } - searchDomain { - __typename - id - name - } - } - } -} -` - -func AssignRoleToGroup( - ctx_ context.Context, - client_ graphql.Client, - GroupId string, - ViewId string, - RoleId string, -) (data_ *AssignRoleToGroupResponse, err_ error) { - req_ := &graphql.Request{ - OpName: "AssignRoleToGroup", - Query: AssignRoleToGroup_Operation, - Variables: &__AssignRoleToGroupInput{ - GroupId: GroupId, - ViewId: ViewId, - RoleId: RoleId, - }, - } - - data_ = &AssignRoleToGroupResponse{} - resp_ := &graphql.Response{Data: data_} - - err_ = client_.MakeRequest( - ctx_, - req_, - resp_, - ) - - return data_, err_ -} - // The mutation executed by CreateAggregateAlert. const CreateAggregateAlert_Operation = ` mutation CreateAggregateAlert ($SearchDomainName: RepoOrViewName!, $Name: String!, $Description: String, $QueryString: String!, $SearchIntervalSeconds: Long!, $ActionIdsOrNames: [String!]!, $Labels: [String!]!, $Enabled: Boolean!, $ThrottleField: String, $ThrottleTimeSeconds: Long!, $TriggerMode: TriggerMode!, $QueryTimestampMode: QueryTimestampType!, $QueryOwnershipType: QueryOwnershipType!) { @@ -16319,10 +15836,15 @@ const CreateGroup_Operation = ` mutation CreateGroup ($DisplayName: String!, $LookupName: String) { addGroup(displayName: $DisplayName, lookupName: $LookupName) { group { - id + ... GroupDetails } } } +fragment GroupDetails on Group { + id + displayName + lookupName +} ` func CreateGroup( @@ -17084,12 +16606,15 @@ const DeleteGroup_Operation = ` mutation DeleteGroup ($GroupId: String!) { removeGroup(groupId: $GroupId) { group { - displayName - userCount - searchDomainCount + ... GroupDetails } } } +fragment GroupDetails on Group { + id + displayName + lookupName +} ` func DeleteGroup( @@ -17695,17 +17220,6 @@ fragment GroupDetails on Group { id displayName lookupName - roles { - role { - id - displayName - } - searchDomain { - __typename - id - name - } - } } ` @@ -18859,49 +18373,6 @@ func UnassignParserToIngestToken( return data_, err_ } -// The mutation executed by UnassignRoleFromGroup. -const UnassignRoleFromGroup_Operation = ` -mutation UnassignRoleFromGroup ($GroupId: String!, $ViewId: String!, $RoleId: String!) { - unassignRoleFromGroup(input: {groupId:$GroupId,viewId:$ViewId,roleId:$RoleId}) { - group { - id - displayName - userCount - searchDomainCount - } - } -} -` - -func UnassignRoleFromGroup( - ctx_ context.Context, - client_ graphql.Client, - GroupId string, - ViewId string, - RoleId string, -) (data_ *UnassignRoleFromGroupResponse, err_ error) { - req_ := &graphql.Request{ - OpName: "UnassignRoleFromGroup", - Query: UnassignRoleFromGroup_Operation, - Variables: &__UnassignRoleFromGroupInput{ - GroupId: GroupId, - ViewId: ViewId, - RoleId: RoleId, - }, - } - - data_ = &UnassignRoleFromGroupResponse{} - resp_ := &graphql.Response{Data: data_} - - err_ = client_.MakeRequest( - ctx_, - req_, - resp_, - ) - - return data_, err_ -} - // The mutation executed by UnregisterClusterNode. const UnregisterClusterNode_Operation = ` mutation UnregisterClusterNode ($NodeId: Int!, $Force: Boolean!) { @@ -19275,10 +18746,15 @@ const UpdateGroup_Operation = ` mutation UpdateGroup ($GroupId: String!, $DisplayName: String, $LookupName: String) { updateGroup(input: {groupId:$GroupId,displayName:$DisplayName,lookupName:$LookupName}) { group { - id + ... GroupDetails } } } +fragment GroupDetails on Group { + id + displayName + lookupName +} ` func UpdateGroup( diff --git a/internal/controller/humiogroup_controller.go b/internal/controller/humiogroup_controller.go index 407ed9b57..9656a4db2 100644 --- a/internal/controller/humiogroup_controller.go +++ b/internal/controller/humiogroup_controller.go @@ -4,7 +4,6 @@ import ( "context" "errors" "fmt" - "sort" "time" "github.com/go-logr/logr" @@ -130,7 +129,7 @@ func (r *HumioGroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) if addErr != nil { return reconcile.Result{}, r.logErrorAndReturn(addErr, "could not create group") } - r.Log.Info("created group", "GroupName", hg.Spec.DisplayName) + r.Log.Info("created group", "GroupName", hg.Spec.Name) return reconcile.Result{Requeue: true}, nil } return reconcile.Result{}, r.logErrorAndReturn(err, "could not check if group exists") @@ -178,33 +177,9 @@ func (r *HumioGroupReconciler) logErrorAndReturn(err error, msg string) error { func groupAlreadyAsExpected(fromKubernetesCustomResource *humiov1alpha1.HumioGroup, fromGraphQL *humiographql.GroupDetails) (bool, map[string]string) { keyValues := map[string]string{} - if diff := cmp.Diff(fromGraphQL.GetDisplayName(), &fromKubernetesCustomResource.Spec.DisplayName); diff != "" { - keyValues["displayName"] = diff - } - if diff := cmp.Diff(fromGraphQL.GetLookupName(), &fromKubernetesCustomResource.Spec.LookupName); diff != "" { - keyValues["lookupName"] = diff - } - - roleAssignmentsFromGraphQL := make([]string, len(fromGraphQL.GetRoles())) - for _, assignment := range fromGraphQL.GetRoles() { - role := assignment.GetRole() - view := assignment.GetSearchDomain() - roleAssignmentsFromGraphQL = append(roleAssignmentsFromGraphQL, formatRoleAssignmentString(role.GetDisplayName(), view.GetName())) - } - roleAssignmentsFromKubernetes := make([]string, len(fromKubernetesCustomResource.Spec.Assignments)) - for _, assignment := range fromKubernetesCustomResource.Spec.Assignments { - roleAssignmentsFromKubernetes = append(roleAssignmentsFromKubernetes, formatRoleAssignmentString(assignment.RoleName, assignment.ViewName)) - } - // sort the two lists for comparison - sort.Strings(roleAssignmentsFromGraphQL) - sort.Strings(roleAssignmentsFromKubernetes) - if diff := cmp.Diff(roleAssignmentsFromGraphQL, roleAssignmentsFromKubernetes); diff != "" { - keyValues["roleAssignments"] = diff + if diff := cmp.Diff(fromGraphQL.GetLookupName(), fromKubernetesCustomResource.Spec.ExternalMappingName); diff != "" { + keyValues["externalMappingName"] = diff } return len(keyValues) == 0, keyValues } - -func formatRoleAssignmentString(roleName string, viewName string) string { - return fmt.Sprintf("%s:%s", roleName, viewName) -} diff --git a/internal/controller/suite/resources/humioresources_controller_test.go b/internal/controller/suite/resources/humioresources_controller_test.go index 68f7cda61..f96e6e755 100644 --- a/internal/controller/suite/resources/humioresources_controller_test.go +++ b/internal/controller/suite/resources/humioresources_controller_test.go @@ -3886,6 +3886,111 @@ var _ = Describe("Humio Resources Controllers", func() { }) + Context("HumioGroup", Label("envtest", "dummy", "real"), func() { + It("Should successfully create, update and delete group with valid configuration", func() { + ctx := context.Background() + key := types.NamespacedName{ + Name: "humio-group", + Namespace: clusterKey.Namespace, + } + toCreateGroup := &humiov1alpha1.HumioGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: key.Name, + Namespace: key.Namespace, + }, + Spec: humiov1alpha1.HumioGroupSpec{ + ManagedClusterName: clusterKey.Name, + Name: "example-group", + ExternalMappingName: nil, // default, empty value + }, + } + humioHttpClient := humioClient.GetHumioHttpClient(sharedCluster.Config(), reconcile.Request{NamespacedName: clusterKey}) + + suite.UsingClusterBy(clusterKey.Name, "Confirming the group does not exist in LogScale before we start") + Eventually(func() error { + _, err := humioClient.GetGroup(ctx, humioHttpClient, reconcile.Request{NamespacedName: clusterKey}, toCreateGroup) + return err + }, testTimeout, suite.TestInterval).ShouldNot(Succeed()) + + suite.UsingClusterBy(clusterKey.Name, "Creating the group custom resource") + Expect(k8sClient.Create(ctx, toCreateGroup)).Should(Succeed()) + + suite.UsingClusterBy(clusterKey.Name, "Custom resource for group should be marked with Exists") + Eventually(func() string { + updatedHumioGroup := humiov1alpha1.HumioGroup{} + err = k8sClient.Get(ctx, key, &updatedHumioGroup) + if err != nil { + return err.Error() + } + return updatedHumioGroup.Status.State + }, testTimeout, suite.TestInterval).Should(Equal(humiov1alpha1.HumioGroupStateExists)) + + suite.UsingClusterBy(clusterKey.Name, "Confirming the group does exist in LogScale after custom resource indicates that it does") + var fetchedGroupDetails *humiographql.GroupDetails + Eventually(func() error { + fetchedGroupDetails, err = humioClient.GetGroup(ctx, humioHttpClient, reconcile.Request{NamespacedName: clusterKey}, toCreateGroup) + return err + }, testTimeout, suite.TestInterval).Should(Succeed()) + Expect(fetchedGroupDetails.LookupName).Should(Equal(toCreateGroup.Spec.ExternalMappingName)) + + suite.UsingClusterBy(clusterKey.Name, "Set lookup name to custom resource using k8sClient") + newExternalMappingName := "some-ad-group" + Eventually(func() error { + updatedHumioGroup := humiov1alpha1.HumioGroup{} + err = k8sClient.Get(ctx, key, &updatedHumioGroup) + if err != nil { + return err + } + updatedHumioGroup.Spec.ExternalMappingName = &newExternalMappingName + return k8sClient.Update(ctx, &updatedHumioGroup) + }, testTimeout, suite.TestInterval).Should(Succeed()) + + suite.UsingClusterBy(clusterKey.Name, "verify it was updated according to humioClient") + Eventually(func() (*string, error) { + fetchedGroupDetails, err = humioClient.GetGroup(ctx, humioHttpClient, reconcile.Request{NamespacedName: clusterKey}, toCreateGroup) + if err != nil { + return nil, err + } + Expect(fetchedGroupDetails).ToNot(BeNil()) + return fetchedGroupDetails.LookupName, err + }, testTimeout, suite.TestInterval).Should(BeEquivalentTo(&newExternalMappingName)) + + suite.UsingClusterBy(clusterKey.Name, "Remove lookup name to custom resource using k8sClient") + Eventually(func() error { + updatedHumioGroup := humiov1alpha1.HumioGroup{} + err = k8sClient.Get(ctx, key, &updatedHumioGroup) + if err != nil { + return err + } + updatedHumioGroup.Spec.ExternalMappingName = nil + return k8sClient.Update(ctx, &updatedHumioGroup) + }, testTimeout, suite.TestInterval).Should(Succeed()) + + suite.UsingClusterBy(clusterKey.Name, "verify it was updated according to humioClient") + Eventually(func() (*string, error) { + fetchedGroupDetails, err = humioClient.GetGroup(ctx, humioHttpClient, reconcile.Request{NamespacedName: clusterKey}, toCreateGroup) + if err != nil { + return nil, err + } + Expect(fetchedGroupDetails).ToNot(BeNil()) + return fetchedGroupDetails.LookupName, err + }, testTimeout, suite.TestInterval).Should(BeNil()) + + suite.UsingClusterBy(clusterKey.Name, "Delete custom resource using k8sClient") + Expect(k8sClient.Delete(ctx, toCreateGroup)).To(Succeed()) + Eventually(func() bool { + err := k8sClient.Get(ctx, key, toCreateGroup) + return k8serrors.IsNotFound(err) + }, testTimeout, suite.TestInterval).Should(BeTrue()) + + suite.UsingClusterBy(clusterKey.Name, "Verify group was removed using humioClient") + Eventually(func() string { + fetchedGroupDetails, err = humioClient.GetGroup(ctx, humioHttpClient, reconcile.Request{NamespacedName: clusterKey}, toCreateGroup) + return err.Error() + }, testTimeout, suite.TestInterval).Should(BeEquivalentTo(humioapi.GroupNotFound(toCreateGroup.Spec.Name).Error())) + }) + }) + Context("Humio User", Label("envtest", "dummy", "real"), func() { It("HumioUser: Should handle user correctly", func() { ctx := context.Background() @@ -4029,7 +4134,7 @@ var _ = Describe("Humio Resources Controllers", func() { } // Verify we validate this for all our CRD's - Expect(resources).To(HaveLen(14)) // Bump this as we introduce new CRD's + Expect(resources).To(HaveLen(15)) // Bump this as we introduce new CRD's for i := range resources { // Get the GVK information diff --git a/internal/controller/suite/resources/suite_test.go b/internal/controller/suite/resources/suite_test.go index a48efeaaf..5b69cc6a8 100644 --- a/internal/controller/suite/resources/suite_test.go +++ b/internal/controller/suite/resources/suite_test.go @@ -304,6 +304,17 @@ var _ = BeforeSuite(func() { }).SetupWithManager(k8sManager) Expect(err).NotTo(HaveOccurred()) + err = (&controller.HumioGroupReconciler{ + Client: k8sManager.GetClient(), + CommonConfig: controller.CommonConfig{ + RequeuePeriod: requeuePeriod, + }, + HumioClient: humioClient, + BaseLogger: log, + Namespace: clusterKey.Namespace, + }).SetupWithManager(k8sManager) + Expect(err).NotTo(HaveOccurred()) + ctx, cancel = context.WithCancel(context.TODO()) go func() { diff --git a/internal/humio/client.go b/internal/humio/client.go index 42a43aec0..e56300d95 100644 --- a/internal/humio/client.go +++ b/internal/humio/client.go @@ -21,7 +21,6 @@ import ( "errors" "fmt" "net/http" - "slices" "sync" "time" @@ -809,91 +808,13 @@ func validateSearchDomain(ctx context.Context, client *humioapi.Client, searchDo return humioapi.SearchDomainNotFound(searchDomainName) } -func getAllRolesNameToId(ctx context.Context, client *humioapi.Client) (map[string]string, error) { - resp, err := humiographql.ListRoles( - ctx, - client, - ) - if err != nil { - return nil, fmt.Errorf("got error fetching all roles: %w", err) - } - - roleList := resp.GetRoles() - roleNameToId := make(map[string]string) - for _, role := range roleList { - roleNameToId[role.GetDisplayName()] = role.GetId() - } - return roleNameToId, nil -} - -func getViewIdByName(ctx context.Context, client *humioapi.Client, viewName string) (string, error) { - resp, err := humiographql.GetSearchDomain( - ctx, - client, - viewName, - ) - if err != nil { - return "", fmt.Errorf("got error fetching searchdomain: %w", err) - } - if resp == nil { - return "", humioapi.SearchDomainNotFound(viewName) - } - - searchDomain := resp.GetSearchDomain() - return searchDomain.GetId(), nil -} - func (h *ClientConfig) AddGroup(ctx context.Context, client *humioapi.Client, _ reconcile.Request, hg *humiov1alpha1.HumioGroup) error { - var groupLookupName *string - if hg.Spec.LookupName != nil { - groupLookupName = hg.Spec.LookupName - } else { - // if no lookup name is provided, use the display name - groupLookupName = &hg.Spec.DisplayName - } - createGroupResp, err := humiographql.CreateGroup( + _, err := humiographql.CreateGroup( ctx, client, - hg.Spec.DisplayName, - groupLookupName, + hg.Spec.Name, + hg.Spec.ExternalMappingName, ) - if err != nil { - return fmt.Errorf("got error creating group: %w", err) - } - - // get the newly created Group's ID from the response - groupWrapper := createGroupResp.GetAddGroup() - createdGroup := groupWrapper.GetGroup() - groupId := createdGroup.GetId() - - roleNameToId, err := getAllRolesNameToId(ctx, client) - if err != nil { - return fmt.Errorf("got error fetching all roles: %w", err) - } - - for _, roleAssignment := range hg.Spec.Assignments { - roleName := roleAssignment.RoleName - viewName := roleAssignment.ViewName - roleId, ok := roleNameToId[roleName] - if !ok { - return fmt.Errorf("specified role doesn't exist: %s", roleName) - } - viewId, err := getViewIdByName(ctx, client, viewName) - if err != nil { - return fmt.Errorf("got error fetching viewId for %s: %w", viewName, err) - } - _, err = humiographql.AssignRoleToGroup( - ctx, - client, - groupId, - viewId, - roleId, - ) - if err != nil { - return fmt.Errorf("got error assigning role %s to group %s for view %s: %w", roleName, hg.Spec.DisplayName, viewName, err) - } - } - return err } @@ -901,10 +822,10 @@ func (h *ClientConfig) GetGroup(ctx context.Context, client *humioapi.Client, _ getGroupResp, err := humiographql.GetGroupByDisplayName( ctx, client, - hg.Spec.DisplayName, + hg.Spec.Name, ) if err != nil { - return nil, humioapi.GroupNotFound(hg.Spec.DisplayName) + return nil, humioapi.GroupNotFound(hg.Spec.Name) } group := getGroupResp.GetGroupByDisplayName() @@ -912,7 +833,6 @@ func (h *ClientConfig) GetGroup(ctx context.Context, client *humioapi.Client, _ Id: group.GetId(), DisplayName: group.GetDisplayName(), LookupName: group.GetLookupName(), - Roles: group.GetRoles(), }, nil } @@ -922,105 +842,20 @@ func (h *ClientConfig) UpdateGroup(ctx context.Context, client *humioapi.Client, return err } - if cmp.Diff(curGroup.DisplayName, hg.Spec.DisplayName) != "" { - _, err = humiographql.UpdateGroup( - ctx, - client, - curGroup.Id, - &hg.Spec.DisplayName, - curGroup.LookupName, - ) - if err != nil { - return err - } - } - - if cmp.Diff(curGroup.LookupName, hg.Spec.LookupName) != "" { - _, err = humiographql.UpdateGroup( - ctx, - client, - curGroup.Id, - &curGroup.DisplayName, - hg.Spec.LookupName, - ) - if err != nil { - return err - } - } - - curGroupViewToRoles := make(map[string][]string) - for _, searchDomainRole := range curGroup.Roles { - view := searchDomainRole.GetSearchDomain() - role := searchDomainRole.GetRole() - curGroupViewToRoles[view.GetName()] = append(curGroupViewToRoles[view.GetName()], role.GetDisplayName()) - } - - desiredGroupViewToRoles := make(map[string][]string) - for _, roleAssignment := range hg.Spec.Assignments { - desiredGroupViewToRoles[roleAssignment.ViewName] = append(desiredGroupViewToRoles[roleAssignment.ViewName], roleAssignment.RoleName) - } - - roleNameToId, err := getAllRolesNameToId(ctx, client) - if err != nil { - return fmt.Errorf("got error fetching all roles: %w", err) - } - // reconcile - delete existing roles that are not in the desired state - for viewName, roleNames := range curGroupViewToRoles { - for _, roleName := range roleNames { - desiredGroupViewToRolesList, exists := desiredGroupViewToRoles[viewName] - if !exists || !slices.Contains(desiredGroupViewToRolesList, roleName) { - // this role is not in the desired state, delete it - roleId, ok := roleNameToId[roleName] - if !ok { - return fmt.Errorf("trying to unassigned a role that doesn't exist: %s", roleName) - } - viewId, err := getViewIdByName(ctx, client, viewName) - if err != nil { - return fmt.Errorf("got error fetching viewId for %s: %w", viewName, err) - } - _, err = humiographql.UnassignRoleFromGroup( - ctx, - client, - curGroup.Id, - viewId, - roleId, - ) - if err != nil { - return fmt.Errorf("got error unassigning role %s from group %s for view %s: %w", roleName, hg.Spec.DisplayName, viewName, err) - } - } - } + newLookupName := hg.Spec.ExternalMappingName + if hg.Spec.ExternalMappingName != nil && *hg.Spec.ExternalMappingName == "" { + // LogScale returns null from graphql when lookup name is updated to empty string + newLookupName = nil } - // reconcile - add new roles that are in the desired state but not in the existing state - for viewName, roleNames := range desiredGroupViewToRoles { - for _, roleName := range roleNames { - curGroupViewToRolesList, exists := curGroupViewToRoles[viewName] - if !exists || !slices.Contains(curGroupViewToRolesList, roleName) { - // this role is not in the existing state, add it - roleId, ok := roleNameToId[roleName] - if !ok { - return fmt.Errorf("trying to assign a role that doesn't exist: %s", roleName) - } - viewId, err := getViewIdByName(ctx, client, viewName) - if err != nil { - return fmt.Errorf("got error fetching viewId for %s: %w", viewName, err) - } - _, err = humiographql.AssignRoleToGroup( - ctx, - client, - curGroup.Id, - viewId, - roleId, - ) - if err != nil { - return fmt.Errorf("got error assigning role %s to group %s for view %s: %w", roleName, hg.Spec.DisplayName, viewName, err) - } - } - } - } - - return nil + _, err = humiographql.UpdateGroup( + ctx, + client, + curGroup.GetId(), + &hg.Spec.Name, + newLookupName, + ) + return err } func (h *ClientConfig) DeleteGroup(ctx context.Context, client *humioapi.Client, request reconcile.Request, hg *humiov1alpha1.HumioGroup) error { diff --git a/internal/humio/client_mock.go b/internal/humio/client_mock.go index 1be41b882..4acc9c02a 100644 --- a/internal/humio/client_mock.go +++ b/internal/humio/client_mock.go @@ -540,102 +540,80 @@ func (h *MockClientConfig) DeleteView(_ context.Context, _ *humioapi.Client, _ r return nil } -func (h *MockClientConfig) AddGroup(ctx context.Context, client *humioapi.Client, request reconcile.Request, group *humiov1alpha1.HumioGroup) error { +func (h *MockClientConfig) AddGroup(_ context.Context, _ *humioapi.Client, _ reconcile.Request, group *humiov1alpha1.HumioGroup) error { humioClientMu.Lock() defer humioClientMu.Unlock() clusterName := fmt.Sprintf("%s%s", group.Spec.ManagedClusterName, group.Spec.ExternalClusterName) key := resourceKey{ clusterName: clusterName, - resourceName: group.Spec.DisplayName, + resourceName: group.Spec.Name, } if _, found := h.apiClient.Group[key]; found { - return fmt.Errorf("group already exists with name %s", group.Spec.DisplayName) + return fmt.Errorf("group already exists with name %s", group.Spec.Name) } - roles := make([]humiographql.GroupDetailsRolesSearchDomainRole, 0) - for _, role := range group.Spec.Assignments { - roles = append(roles, humiographql.GroupDetailsRolesSearchDomainRole{ - Role: humiographql.GroupDetailsRolesSearchDomainRoleRole{ - Id: role.RoleName, - DisplayName: role.RoleName, - }, - SearchDomain: &humiographql.GroupDetailsRolesSearchDomainRoleSearchDomainView{ - Id: role.ViewName, - Name: role.ViewName, - }, - }) - } value := &humiographql.GroupDetails{ Id: kubernetes.RandomString(), - DisplayName: group.Spec.DisplayName, - LookupName: group.Spec.LookupName, - Roles: roles, + DisplayName: group.Spec.Name, + LookupName: group.Spec.ExternalMappingName, } h.apiClient.Group[key] = *value return nil } -func (h *MockClientConfig) GetGroup(ctx context.Context, client *humioapi.Client, request reconcile.Request, group *humiov1alpha1.HumioGroup) (*humiographql.GroupDetails, error) { +func (h *MockClientConfig) GetGroup(_ context.Context, _ *humioapi.Client, _ reconcile.Request, group *humiov1alpha1.HumioGroup) (*humiographql.GroupDetails, error) { humioClientMu.Lock() defer humioClientMu.Unlock() key := resourceKey{ clusterName: fmt.Sprintf("%s%s", group.Spec.ManagedClusterName, group.Spec.ExternalClusterName), - resourceName: group.Spec.DisplayName, + resourceName: group.Spec.Name, } if value, found := h.apiClient.Group[key]; found { return &value, nil } - return nil, fmt.Errorf("could not find group with name %s, err=%w", group.Spec.DisplayName, humioapi.EntityNotFound{}) + return nil, humioapi.GroupNotFound(group.Spec.Name) } -func (h *MockClientConfig) UpdateGroup(ctx context.Context, client *humioapi.Client, request reconcile.Request, group *humiov1alpha1.HumioGroup) error { +func (h *MockClientConfig) UpdateGroup(_ context.Context, _ *humioapi.Client, _ reconcile.Request, group *humiov1alpha1.HumioGroup) error { humioClientMu.Lock() defer humioClientMu.Unlock() key := resourceKey{ clusterName: fmt.Sprintf("%s%s", group.Spec.ManagedClusterName, group.Spec.ExternalClusterName), - resourceName: group.Spec.DisplayName, + resourceName: group.Spec.Name, } currentGroup, found := h.apiClient.Group[key] if !found { - return fmt.Errorf("group not found with name %s, err=%w", group.Spec.DisplayName, humioapi.EntityNotFound{}) + return humioapi.GroupNotFound(group.Spec.Name) } - roles := make([]humiographql.GroupDetailsRolesSearchDomainRole, 0) - for _, role := range group.Spec.Assignments { - roles = append(roles, humiographql.GroupDetailsRolesSearchDomainRole{ - Role: humiographql.GroupDetailsRolesSearchDomainRoleRole{ - Id: role.RoleName, - DisplayName: role.RoleName, - }, - SearchDomain: &humiographql.GroupDetailsRolesSearchDomainRoleSearchDomainView{ - Id: role.ViewName, - Name: role.ViewName, - }, - }) + newLookupName := group.Spec.ExternalMappingName + if group.Spec.ExternalMappingName != nil && *group.Spec.ExternalMappingName == "" { + // LogScale returns null from graphql when lookup name is updated to empty string + newLookupName = nil } + value := &humiographql.GroupDetails{ Id: currentGroup.GetId(), - DisplayName: group.Spec.DisplayName, - LookupName: group.Spec.LookupName, - Roles: roles, + DisplayName: group.Spec.Name, + LookupName: newLookupName, } h.apiClient.Group[key] = *value return nil } -func (h *MockClientConfig) DeleteGroup(ctx context.Context, client *humioapi.Client, request reconcile.Request, group *humiov1alpha1.HumioGroup) error { +func (h *MockClientConfig) DeleteGroup(_ context.Context, _ *humioapi.Client, _ reconcile.Request, group *humiov1alpha1.HumioGroup) error { humioClientMu.Lock() defer humioClientMu.Unlock() key := resourceKey{ clusterName: fmt.Sprintf("%s%s", group.Spec.ManagedClusterName, group.Spec.ExternalClusterName), - resourceName: group.Spec.DisplayName, + resourceName: group.Spec.Name, } delete(h.apiClient.Group, key) return nil From 076d5146e83f471759979e12ae6b7b9e8d31aadf Mon Sep 17 00:00:00 2001 From: Mike Rostermund Date: Thu, 15 May 2025 14:24:59 +0200 Subject: [PATCH 08/10] Remove unused reconcile.Request parameter from Group functions --- internal/controller/humiogroup_controller.go | 12 +++++------ .../humioresources_controller_test.go | 10 +++++----- internal/humio/client.go | 20 +++++++++---------- internal/humio/client_mock.go | 8 ++++---- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/internal/controller/humiogroup_controller.go b/internal/controller/humiogroup_controller.go index 9656a4db2..960c5d048 100644 --- a/internal/controller/humiogroup_controller.go +++ b/internal/controller/humiogroup_controller.go @@ -76,7 +76,7 @@ func (r *HumioGroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) if isMarkedForDeletion { r.Log.Info("group marked to be deleted") if helpers.ContainsElement(hg.GetFinalizers(), humioFinalizer) { - _, err := r.HumioClient.GetGroup(ctx, humioHttpClient, req, hg) + _, err := r.HumioClient.GetGroup(ctx, humioHttpClient, hg) if errors.As(err, &humioapi.EntityNotFound{}) { hg.SetFinalizers(helpers.RemoveElement(hg.GetFinalizers(), humioFinalizer)) err := r.Update(ctx, hg) @@ -91,7 +91,7 @@ func (r *HumioGroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) // finalization logic fails, don't remove the finalizer so // that we can retry during the next reconciliation. r.Log.Info("Deleting Group") - if err := r.HumioClient.DeleteGroup(ctx, humioHttpClient, req, hg); err != nil { + if err := r.HumioClient.DeleteGroup(ctx, humioHttpClient, hg); err != nil { return reconcile.Result{}, r.logErrorAndReturn(err, "Delete group returned error") } } @@ -108,7 +108,7 @@ func (r *HumioGroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) } } defer func(ctx context.Context, hg *humiov1alpha1.HumioGroup) { - _, err := r.HumioClient.GetGroup(ctx, humioHttpClient, req, hg) + _, err := r.HumioClient.GetGroup(ctx, humioHttpClient, hg) if errors.As(err, &humioapi.EntityNotFound{}) { _ = r.setState(ctx, humiov1alpha1.HumioGroupStateNotFound, hg) return @@ -121,11 +121,11 @@ func (r *HumioGroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) }(ctx, hg) r.Log.Info("get current group") - curGroup, err := r.HumioClient.GetGroup(ctx, humioHttpClient, req, hg) + curGroup, err := r.HumioClient.GetGroup(ctx, humioHttpClient, hg) if err != nil { if errors.As(err, &humioapi.EntityNotFound{}) { r.Log.Info("Group doesn't exist. Now adding group") - addErr := r.HumioClient.AddGroup(ctx, humioHttpClient, req, hg) + addErr := r.HumioClient.AddGroup(ctx, humioHttpClient, hg) if addErr != nil { return reconcile.Result{}, r.logErrorAndReturn(addErr, "could not create group") } @@ -139,7 +139,7 @@ func (r *HumioGroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) r.Log.Info("information differs, triggering update", "diff", diffKeysAndValues, ) - updateErr := r.HumioClient.UpdateGroup(ctx, humioHttpClient, req, hg) + updateErr := r.HumioClient.UpdateGroup(ctx, humioHttpClient, hg) if updateErr != nil { return reconcile.Result{}, r.logErrorAndReturn(updateErr, "could not update group") } diff --git a/internal/controller/suite/resources/humioresources_controller_test.go b/internal/controller/suite/resources/humioresources_controller_test.go index f96e6e755..8ab14d6e6 100644 --- a/internal/controller/suite/resources/humioresources_controller_test.go +++ b/internal/controller/suite/resources/humioresources_controller_test.go @@ -3908,7 +3908,7 @@ var _ = Describe("Humio Resources Controllers", func() { suite.UsingClusterBy(clusterKey.Name, "Confirming the group does not exist in LogScale before we start") Eventually(func() error { - _, err := humioClient.GetGroup(ctx, humioHttpClient, reconcile.Request{NamespacedName: clusterKey}, toCreateGroup) + _, err := humioClient.GetGroup(ctx, humioHttpClient, toCreateGroup) return err }, testTimeout, suite.TestInterval).ShouldNot(Succeed()) @@ -3928,7 +3928,7 @@ var _ = Describe("Humio Resources Controllers", func() { suite.UsingClusterBy(clusterKey.Name, "Confirming the group does exist in LogScale after custom resource indicates that it does") var fetchedGroupDetails *humiographql.GroupDetails Eventually(func() error { - fetchedGroupDetails, err = humioClient.GetGroup(ctx, humioHttpClient, reconcile.Request{NamespacedName: clusterKey}, toCreateGroup) + fetchedGroupDetails, err = humioClient.GetGroup(ctx, humioHttpClient, toCreateGroup) return err }, testTimeout, suite.TestInterval).Should(Succeed()) Expect(fetchedGroupDetails.LookupName).Should(Equal(toCreateGroup.Spec.ExternalMappingName)) @@ -3947,7 +3947,7 @@ var _ = Describe("Humio Resources Controllers", func() { suite.UsingClusterBy(clusterKey.Name, "verify it was updated according to humioClient") Eventually(func() (*string, error) { - fetchedGroupDetails, err = humioClient.GetGroup(ctx, humioHttpClient, reconcile.Request{NamespacedName: clusterKey}, toCreateGroup) + fetchedGroupDetails, err = humioClient.GetGroup(ctx, humioHttpClient, toCreateGroup) if err != nil { return nil, err } @@ -3968,7 +3968,7 @@ var _ = Describe("Humio Resources Controllers", func() { suite.UsingClusterBy(clusterKey.Name, "verify it was updated according to humioClient") Eventually(func() (*string, error) { - fetchedGroupDetails, err = humioClient.GetGroup(ctx, humioHttpClient, reconcile.Request{NamespacedName: clusterKey}, toCreateGroup) + fetchedGroupDetails, err = humioClient.GetGroup(ctx, humioHttpClient, toCreateGroup) if err != nil { return nil, err } @@ -3985,7 +3985,7 @@ var _ = Describe("Humio Resources Controllers", func() { suite.UsingClusterBy(clusterKey.Name, "Verify group was removed using humioClient") Eventually(func() string { - fetchedGroupDetails, err = humioClient.GetGroup(ctx, humioHttpClient, reconcile.Request{NamespacedName: clusterKey}, toCreateGroup) + fetchedGroupDetails, err = humioClient.GetGroup(ctx, humioHttpClient, toCreateGroup) return err.Error() }, testTimeout, suite.TestInterval).Should(BeEquivalentTo(humioapi.GroupNotFound(toCreateGroup.Spec.Name).Error())) }) diff --git a/internal/humio/client.go b/internal/humio/client.go index e56300d95..4371a8682 100644 --- a/internal/humio/client.go +++ b/internal/humio/client.go @@ -95,10 +95,10 @@ type ViewsClient interface { } type GroupsClient interface { - AddGroup(context.Context, *humioapi.Client, reconcile.Request, *humiov1alpha1.HumioGroup) error - GetGroup(context.Context, *humioapi.Client, reconcile.Request, *humiov1alpha1.HumioGroup) (*humiographql.GroupDetails, error) - UpdateGroup(context.Context, *humioapi.Client, reconcile.Request, *humiov1alpha1.HumioGroup) error - DeleteGroup(context.Context, *humioapi.Client, reconcile.Request, *humiov1alpha1.HumioGroup) error + AddGroup(context.Context, *humioapi.Client, *humiov1alpha1.HumioGroup) error + GetGroup(context.Context, *humioapi.Client, *humiov1alpha1.HumioGroup) (*humiographql.GroupDetails, error) + UpdateGroup(context.Context, *humioapi.Client, *humiov1alpha1.HumioGroup) error + DeleteGroup(context.Context, *humioapi.Client, *humiov1alpha1.HumioGroup) error } type ActionsClient interface { @@ -808,7 +808,7 @@ func validateSearchDomain(ctx context.Context, client *humioapi.Client, searchDo return humioapi.SearchDomainNotFound(searchDomainName) } -func (h *ClientConfig) AddGroup(ctx context.Context, client *humioapi.Client, _ reconcile.Request, hg *humiov1alpha1.HumioGroup) error { +func (h *ClientConfig) AddGroup(ctx context.Context, client *humioapi.Client, hg *humiov1alpha1.HumioGroup) error { _, err := humiographql.CreateGroup( ctx, client, @@ -818,7 +818,7 @@ func (h *ClientConfig) AddGroup(ctx context.Context, client *humioapi.Client, _ return err } -func (h *ClientConfig) GetGroup(ctx context.Context, client *humioapi.Client, _ reconcile.Request, hg *humiov1alpha1.HumioGroup) (*humiographql.GroupDetails, error) { +func (h *ClientConfig) GetGroup(ctx context.Context, client *humioapi.Client, hg *humiov1alpha1.HumioGroup) (*humiographql.GroupDetails, error) { getGroupResp, err := humiographql.GetGroupByDisplayName( ctx, client, @@ -836,8 +836,8 @@ func (h *ClientConfig) GetGroup(ctx context.Context, client *humioapi.Client, _ }, nil } -func (h *ClientConfig) UpdateGroup(ctx context.Context, client *humioapi.Client, request reconcile.Request, hg *humiov1alpha1.HumioGroup) error { - curGroup, err := h.GetGroup(ctx, client, request, hg) +func (h *ClientConfig) UpdateGroup(ctx context.Context, client *humioapi.Client, hg *humiov1alpha1.HumioGroup) error { + curGroup, err := h.GetGroup(ctx, client, hg) if err != nil { return err } @@ -858,8 +858,8 @@ func (h *ClientConfig) UpdateGroup(ctx context.Context, client *humioapi.Client, return err } -func (h *ClientConfig) DeleteGroup(ctx context.Context, client *humioapi.Client, request reconcile.Request, hg *humiov1alpha1.HumioGroup) error { - group, err := h.GetGroup(ctx, client, request, hg) +func (h *ClientConfig) DeleteGroup(ctx context.Context, client *humioapi.Client, hg *humiov1alpha1.HumioGroup) error { + group, err := h.GetGroup(ctx, client, hg) if err != nil { if errors.As(err, &humioapi.EntityNotFound{}) { return nil diff --git a/internal/humio/client_mock.go b/internal/humio/client_mock.go index 4acc9c02a..55676a02a 100644 --- a/internal/humio/client_mock.go +++ b/internal/humio/client_mock.go @@ -540,7 +540,7 @@ func (h *MockClientConfig) DeleteView(_ context.Context, _ *humioapi.Client, _ r return nil } -func (h *MockClientConfig) AddGroup(_ context.Context, _ *humioapi.Client, _ reconcile.Request, group *humiov1alpha1.HumioGroup) error { +func (h *MockClientConfig) AddGroup(_ context.Context, _ *humioapi.Client, group *humiov1alpha1.HumioGroup) error { humioClientMu.Lock() defer humioClientMu.Unlock() @@ -563,7 +563,7 @@ func (h *MockClientConfig) AddGroup(_ context.Context, _ *humioapi.Client, _ rec return nil } -func (h *MockClientConfig) GetGroup(_ context.Context, _ *humioapi.Client, _ reconcile.Request, group *humiov1alpha1.HumioGroup) (*humiographql.GroupDetails, error) { +func (h *MockClientConfig) GetGroup(_ context.Context, _ *humioapi.Client, group *humiov1alpha1.HumioGroup) (*humiographql.GroupDetails, error) { humioClientMu.Lock() defer humioClientMu.Unlock() @@ -577,7 +577,7 @@ func (h *MockClientConfig) GetGroup(_ context.Context, _ *humioapi.Client, _ rec return nil, humioapi.GroupNotFound(group.Spec.Name) } -func (h *MockClientConfig) UpdateGroup(_ context.Context, _ *humioapi.Client, _ reconcile.Request, group *humiov1alpha1.HumioGroup) error { +func (h *MockClientConfig) UpdateGroup(_ context.Context, _ *humioapi.Client, group *humiov1alpha1.HumioGroup) error { humioClientMu.Lock() defer humioClientMu.Unlock() @@ -607,7 +607,7 @@ func (h *MockClientConfig) UpdateGroup(_ context.Context, _ *humioapi.Client, _ return nil } -func (h *MockClientConfig) DeleteGroup(_ context.Context, _ *humioapi.Client, _ reconcile.Request, group *humiov1alpha1.HumioGroup) error { +func (h *MockClientConfig) DeleteGroup(_ context.Context, _ *humioapi.Client, group *humiov1alpha1.HumioGroup) error { humioClientMu.Lock() defer humioClientMu.Unlock() From 3439412be438ef4b6d848c277763c90f90f0585c Mon Sep 17 00:00:00 2001 From: Mike Rostermund Date: Thu, 15 May 2025 14:44:44 +0200 Subject: [PATCH 09/10] Keep version 0.28.2 for now. We will release later --- charts/humio-operator/Chart.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/humio-operator/Chart.yaml b/charts/humio-operator/Chart.yaml index c17f40fac..c9639f4d1 100644 --- a/charts/humio-operator/Chart.yaml +++ b/charts/humio-operator/Chart.yaml @@ -1,7 +1,7 @@ apiVersion: v1 name: humio-operator -version: 0.28.3 -appVersion: 0.28.3 +version: 0.28.2 +appVersion: 0.28.2 home: https://github.com/humio/humio-operator description: | Kubernetes Operator for running Humio on top of Kubernetes From d239a014818fa6605cceef9bbc98d0d7f33ce4d8 Mon Sep 17 00:00:00 2001 From: Mike Rostermund Date: Mon, 19 May 2025 10:23:29 +0200 Subject: [PATCH 10/10] Fix UpdateGroup() The lookup name / external mapping name is an optional through graphql API's and return null if not set. To unset, it is necessary to explicitly set it to the empty string as passing null to the graphql mutation for updating the group will simply skip/ignore updates to the external mapping name. --- internal/humio/client.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/humio/client.go b/internal/humio/client.go index 1f2e71a69..97ceb7073 100644 --- a/internal/humio/client.go +++ b/internal/humio/client.go @@ -866,9 +866,9 @@ func (h *ClientConfig) UpdateGroup(ctx context.Context, client *humioapi.Client, } newLookupName := hg.Spec.ExternalMappingName - if hg.Spec.ExternalMappingName != nil && *hg.Spec.ExternalMappingName == "" { + if hg.Spec.ExternalMappingName == nil { // LogScale returns null from graphql when lookup name is updated to empty string - newLookupName = nil + newLookupName = helpers.StringPtr("") } _, err = humiographql.UpdateGroup(