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/VERSION b/VERSION index a37255a85..b79f04f44 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.28.2 +0.28.3 diff --git a/api/v1alpha1/humiogroup_types.go b/api/v1alpha1/humiogroup_types.go new file mode 100644 index 000000000..47791bf63 --- /dev/null +++ b/api/v1alpha1/humiogroup_types.go @@ -0,0 +1,83 @@ +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" +) + +// 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 + // 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"` + // Assignments contains the list of role assignments for the group + // +optional + Assignments []HumioGroupRoleAssignment `json:"assignments,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 bef66a0d8..1997d7d65 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -1097,6 +1097,120 @@ 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 *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 + *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. +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/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 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..b20e2c277 --- /dev/null +++ b/charts/humio-operator/crds/core.humio.com_humiogroups.yaml @@ -0,0 +1,106 @@ +--- +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: + 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 + 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 8334f5445..f49f252c2 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -360,6 +360,14 @@ func main() { ctrl.Log.Error(err, "unable to create controller", "controller", "HumioView") os.Exit(1) } + if err = (&controller.HumioGroupReconciler{ + Client: mgr.GetClient(), + HumioClient: humio.NewClient(log, userAgent), + BaseLogger: log, + }).SetupWithManager(mgr); err != nil { + ctrl.Log.Error(err, "unable to create controller", "controller", "HumioGroup") + os.Exit(1) + } // +kubebuilder:scaffold:builder if metricsCertWatcher != nil { 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..b20e2c277 --- /dev/null +++ b/config/crd/bases/core.humio.com_humiogroups.yaml @@ -0,0 +1,106 @@ +--- +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: + 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 + 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 fd131bb46..6945d8cf6 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_humiofilteralerts.yaml @@ -25,6 +26,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 @@ -40,6 +42,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 6a4c428dc..75465564e 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -34,6 +34,7 @@ rules: - humioclusters - humioexternalclusters - humiofilteralerts + - humiogroups - humioingesttokens - humioparsers - humiorepositories @@ -57,6 +58,7 @@ rules: - humioclusters/finalizers - humioexternalclusters/finalizers - humiofilteralerts/finalizers + - humiogroups/finalizers - humioingesttokens/finalizers - humioparsers/finalizers - humiorepositories/finalizers @@ -74,6 +76,7 @@ rules: - humioclusters/status - humioexternalclusters/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..80cdc8724 --- /dev/null +++ b/config/samples/core_v1alpha1_humiogroup.yaml @@ -0,0 +1,11 @@ +apiVersion: core.humio.com/v1alpha1 +kind: HumioGroup +metadata: + name: example-humiogroup-managed +spec: + managedClusterName: example-humiocluster + displayName: "example-group" + lookupName: "example-group-lookup-name" + assignments: + - roleName: "example-role" + viewName: "example-view" diff --git a/internal/api/error.go b/internal/api/error.go index 27100d442..8d783fbb5 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" @@ -62,6 +63,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 c6d3655fe..2621cf8bf 100644 --- a/internal/api/humiographql/genqlient.yaml +++ b/internal/api/humiographql/genqlient.yaml @@ -6,10 +6,12 @@ operations: - graphql/cluster.graphql - graphql/filter-alerts.graphql - graphql/fragments.graphql + - graphql/groups.graphql - graphql/ingest-tokens.graphql - 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 new file mode 100644 index 000000000..088ad0adc --- /dev/null +++ b/internal/api/humiographql/graphql/groups.graphql @@ -0,0 +1,117 @@ +fragment GroupDetails on Group { + id + displayName + lookupName + roles { + role { + id + displayName + } + searchDomain { + id + name + } + } +} + +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 + } + } +} + +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 01a03ab6e..623012a11 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. // @@ -2123,6 +2391,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. // @@ -2779,6 +3080,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"` @@ -5339,6 +5689,97 @@ 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 +} + +// 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" { + 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"` + + Roles []GroupDetailsRolesSearchDomainRole `json:"roles"` +} + +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 + retval.Roles = v.GroupDetails.Roles + 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: @@ -6493,14 +6934,272 @@ func (v *GetUsersByUsernameUsersUser) MarshalJSON() ([]byte, error) { return json.Marshal(premarshaled) } -func (v *GetUsersByUsernameUsersUser) __premarshalJSON() (*__premarshalGetUsersByUsernameUsersUser, error) { - var retval __premarshalGetUsersByUsernameUsersUser +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 + } + + 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 } - retval.Id = v.UserDetails.Id - retval.Username = v.UserDetails.Username - retval.IsRoot = v.UserDetails.IsRoot - return &retval, nil -} +// 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 { @@ -9232,6 +9931,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 @@ -11568,6 +12291,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 @@ -12174,6 +12951,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. @@ -12900,6 +13712,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"` @@ -13084,6 +13912,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"` @@ -13426,6 +14266,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"` @@ -13514,6 +14362,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"` @@ -13682,6 +14538,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"` @@ -13908,6 +14780,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"` @@ -14377,6 +15265,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!) { @@ -14661,6 +15597,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!) { @@ -15330,6 +16304,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!) { @@ -15803,6 +16815,56 @@ 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 + roles { + role { + id + displayName + } + searchDomain { + __typename + id + name + } + } +} +` + +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 { @@ -16513,6 +17575,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!) { @@ -16822,6 +17915,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!) { @@ -17190,6 +18326,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..3a4fa89de --- /dev/null +++ b/internal/controller/humiogroup_controller.go @@ -0,0 +1,208 @@ +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" + "sort" + "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.HumioGroupStateConfigError, 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 + } + + 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/controller/humioview_controller.go b/internal/controller/humioview_controller.go index 23331ced9..6ea152057 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") } diff --git a/internal/humio/client.go b/internal/humio/client.go index 650fffc62..8cb202cb0 100644 --- a/internal/humio/client.go +++ b/internal/humio/client.go @@ -21,6 +21,7 @@ import ( "errors" "fmt" "net/http" + "slices" "sync" "time" @@ -43,6 +44,7 @@ type Client interface { ParsersClient RepositoriesClient ViewsClient + GroupsClient LicenseClient ActionsClient AlertsClient @@ -92,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) @@ -757,6 +766,237 @@ 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( + 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 +} + +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(), + Roles: group.GetRoles(), + }, 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) + if err != nil { + 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) + } + } + } + } + + // 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 +} + +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 c60ef8842..adc06d7bc 100644 --- a/internal/humio/client_mock.go +++ b/internal/humio/client_mock.go @@ -49,6 +49,8 @@ type ClientMock struct { LicenseUID map[resourceKey]string 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 @@ -69,6 +71,8 @@ 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), + Role: make(map[resourceKey]humiographql.ListRolesRolesRole), IngestToken: make(map[resourceKey]humiographql.IngestTokenDetails), Parser: make(map[resourceKey]humiographql.ParserDetails), Action: make(map[resourceKey]humiographql.ActionDetails), @@ -93,6 +97,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) @@ -528,6 +534,107 @@ 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) + } + + 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 + 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{}) + } + + 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 +} + +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()