diff --git a/Makefile b/Makefile index 1430f9c2..81081306 100644 --- a/Makefile +++ b/Makefile @@ -150,6 +150,7 @@ dev-setup: --set 'crds.install=true' \ --set 'crds.exclusive=true'\ --set 'crds.createConfig=true'\ + --set "tls.enableController=false"\ --set "webhooks.exclusive=true"\ --set "webhooks.hooks.nodes.enabled=true"\ --set "webhooks.service.url=$${WEBHOOK_URL}" \ @@ -415,7 +416,7 @@ nwa: $(call go-install-tool,$(NWA),github.com/$(NWA_LOOKUP)@$(NWA_VERSION)) GOLANGCI_LINT := $(LOCALBIN)/golangci-lint -GOLANGCI_LINT_VERSION := v2.5.0 +GOLANGCI_LINT_VERSION := v2.7.2 GOLANGCI_LINT_LOOKUP := golangci/golangci-lint golangci-lint: ## Download golangci-lint locally if necessary. @test -s $(GOLANGCI_LINT) && $(GOLANGCI_LINT) -h | grep -q $(GOLANGCI_LINT_VERSION) || \ diff --git a/api/v1beta1/tenant_types.go b/api/v1beta1/tenant_types.go index 1f53d0d9..a33bfa8c 100644 --- a/api/v1beta1/tenant_types.go +++ b/api/v1beta1/tenant_types.go @@ -20,17 +20,21 @@ type TenantSpec struct { // Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures that all PersistentVolumeClaim resources created in the Tenant can use only one of the allowed StorageClasses. Optional. StorageClasses *api.AllowedListSpec `json:"storageClasses,omitempty"` // Specifies options for the Ingress resources, such as allowed hostnames and IngressClass. Optional. - IngressOptions IngressOptions `json:"ingressOptions,omitempty"` + // +optional + IngressOptions IngressOptions `json:"ingressOptions,omitzero"` // Specifies the trusted Image Registries assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed trusted registries. Optional. ContainerRegistries *api.AllowedListSpec `json:"containerRegistries,omitempty"` // Specifies the label to control the placement of pods on a given pool of worker nodes. All namespaces created within the Tenant will have the node selector annotation. This annotation tells the Kubernetes scheduler to place pods on the nodes having the selector label. Optional. NodeSelector map[string]string `json:"nodeSelector,omitempty"` // Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional. - NetworkPolicies api.NetworkPolicySpec `json:"networkPolicies,omitempty"` + // +optional + NetworkPolicies api.NetworkPolicySpec `json:"networkPolicies,omitzero"` // Specifies the resource min/max usage restrictions to the Tenant. The assigned values are inherited by any namespace created in the Tenant. Optional. - LimitRanges api.LimitRangesSpec `json:"limitRanges,omitempty"` + // +optional + LimitRanges api.LimitRangesSpec `json:"limitRanges,omitzero"` // Specifies a list of ResourceQuota resources assigned to the Tenant. The assigned values are inherited by any namespace created in the Tenant. The Capsule operator aggregates ResourceQuota at Tenant level, so that the hard quota is never crossed for the given Tenant. This permits the Tenant owner to consume resources in the Tenant regardless of the namespace. Optional. - ResourceQuota api.ResourceQuotaSpec `json:"resourceQuotas,omitempty"` + // +optional + ResourceQuota api.ResourceQuotaSpec `json:"resourceQuotas,omitzero"` // Specifies additional RoleBindings assigned to the Tenant. Capsule will ensure that all namespaces in the Tenant always contain the RoleBinding for the given ClusterRole. Optional. AdditionalRoleBindings []api.AdditionalRoleBindingsSpec `json:"additionalRoleBindings,omitempty"` // Specify the allowed values for the imagePullPolicies option in Pod resources. Capsule assures that all Pod resources created in the Tenant can use only one of the allowed policy. Optional. @@ -50,11 +54,13 @@ type TenantSpec struct { // Tenant is the Schema for the tenants API. type Tenant struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` + metav1.TypeMeta `json:",inline"` + // +optional + metav1.ObjectMeta `json:"metadata,omitzero"` - Spec TenantSpec `json:"spec,omitempty"` - Status TenantStatus `json:"status,omitempty"` + Spec TenantSpec `json:"spec"` + // +optional + Status TenantStatus `json:"status,omitzero"` } func (in *Tenant) Hub() {} @@ -64,7 +70,8 @@ func (in *Tenant) Hub() {} // TenantList contains a list of Tenant. type TenantList struct { metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` + // +optional + metav1.ListMeta `json:"metadata,omitzero"` Items []Tenant `json:"items"` } diff --git a/api/v1beta2/capsuleconfiguration_types.go b/api/v1beta2/capsuleconfiguration_types.go index 894f0fb1..f8d35fd7 100644 --- a/api/v1beta2/capsuleconfiguration_types.go +++ b/api/v1beta2/capsuleconfiguration_types.go @@ -40,13 +40,14 @@ type CapsuleConfigurationSpec struct { // Allows to set different name rather than the canonical one for the Capsule configuration objects, // such as webhook secret or configurations. // +kubebuilder:default={TLSSecretName:"capsule-tls",mutatingWebhookConfigurationName:"capsule-mutating-webhook-configuration",validatingWebhookConfigurationName:"capsule-validating-webhook-configuration"} - CapsuleResources CapsuleResources `json:"overrides,omitempty"` + // +optional + CapsuleResources CapsuleResources `json:"overrides,omitzero"` // Allows to set the forbidden metadata for the worker nodes that could be patched by a Tenant. // This applies only if the Tenant has an active NodeSelector, and the Owner have right to patch their nodes. NodeMetadata *NodeMetadata `json:"nodeMetadata,omitempty"` // Toggles the TLS reconciler, the controller that is able to generate CA and certificates for the webhooks // when not using an already provided CA and certificate, or when these are managed externally with Vault, or cert-manager. - // +kubebuilder:default=true + // +kubebuilder:default=false EnableTLSReconciler bool `json:"enableTLSReconciler"` //nolint:tagliatelle // Define entities which can act as Administrators in the capsule construct // These entities are automatically owners for all existing tenants. Meaning they can add namespaces to any tenant. However they must be specific by using the capsule label @@ -57,9 +58,11 @@ type CapsuleConfigurationSpec struct { type NodeMetadata struct { // Define the labels that a Tenant Owner cannot set for their nodes. - ForbiddenLabels api.ForbiddenListSpec `json:"forbiddenLabels"` + // +optional + ForbiddenLabels api.ForbiddenListSpec `json:"forbiddenLabels,omitzero"` // Define the annotations that a Tenant Owner cannot set for their nodes. - ForbiddenAnnotations api.ForbiddenListSpec `json:"forbiddenAnnotations"` + // +optional + ForbiddenAnnotations api.ForbiddenListSpec `json:"forbiddenAnnotations,omitzero"` } type CapsuleResources struct { @@ -81,10 +84,12 @@ type CapsuleResources struct { // CapsuleConfiguration is the Schema for the Capsule configuration API. type CapsuleConfiguration struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` + metav1.TypeMeta `json:",inline"` - Spec CapsuleConfigurationSpec `json:"spec,omitempty"` + // +optional + metav1.ObjectMeta `json:"metadata,omitzero"` + + Spec CapsuleConfigurationSpec `json:"spec"` } // +kubebuilder:object:root=true @@ -92,7 +97,7 @@ type CapsuleConfiguration struct { // CapsuleConfigurationList contains a list of CapsuleConfiguration. type CapsuleConfigurationList struct { metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` + metav1.ListMeta `json:"metadata,omitzero"` Items []CapsuleConfiguration `json:"items"` } diff --git a/api/v1beta2/namespace_options.go b/api/v1beta2/namespace_options.go index cdb19dcb..2321ff2a 100644 --- a/api/v1beta2/namespace_options.go +++ b/api/v1beta2/namespace_options.go @@ -18,9 +18,11 @@ type NamespaceOptions struct { // Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant via a list. Optional. AdditionalMetadataList []api.AdditionalMetadataSelectorSpec `json:"additionalMetadataList,omitempty"` // Define the labels that a Tenant Owner cannot set for their Namespace resources. - ForbiddenLabels api.ForbiddenListSpec `json:"forbiddenLabels,omitempty"` + // +optional + ForbiddenLabels api.ForbiddenListSpec `json:"forbiddenLabels,omitzero"` // Define the annotations that a Tenant Owner cannot set for their Namespace resources. - ForbiddenAnnotations api.ForbiddenListSpec `json:"forbiddenAnnotations,omitempty"` + // +optional + ForbiddenAnnotations api.ForbiddenListSpec `json:"forbiddenAnnotations,omitzero"` // If enabled only metadata from additionalMetadata is reconciled to the namespaces. //+kubebuilder:default:=false ManagedMetadataOnly bool `json:"managedMetadataOnly,omitempty"` diff --git a/api/v1beta2/resourcepool_status.go b/api/v1beta2/resourcepool_status.go index f093ee5f..4c87180c 100644 --- a/api/v1beta2/resourcepool_status.go +++ b/api/v1beta2/resourcepool_status.go @@ -21,9 +21,11 @@ type ResourcePoolStatus struct { // Namespaces which are considered for claims Namespaces []string `json:"namespaces,omitempty"` // Tracks the quotas for the Resource. - Claims ResourcePoolNamespaceClaimsStatus `json:"claims,omitempty"` + // +optional + Claims ResourcePoolNamespaceClaimsStatus `json:"claims,omitzero"` // Tracks the Usage from Claimed against what has been granted from the pool - Allocation ResourcePoolQuotaStatus `json:"allocation,omitempty"` + // +optional + Allocation ResourcePoolQuotaStatus `json:"allocation,omitzero"` // Exhaustions from claims associated with the pool Exhaustions map[string]api.PoolExhaustionResource `json:"exhaustions,omitempty"` } diff --git a/api/v1beta2/resourcepool_types.go b/api/v1beta2/resourcepool_types.go index bace7a6b..007a102f 100644 --- a/api/v1beta2/resourcepool_types.go +++ b/api/v1beta2/resourcepool_types.go @@ -18,10 +18,12 @@ type ResourcePoolSpec struct { Quota corev1.ResourceQuotaSpec `json:"quota"` // The Defaults given for each namespace, the default is not counted towards the total allocation // When you use claims it's recommended to provision Defaults as the prevent the scheduling of any resources - Defaults corev1.ResourceList `json:"defaults,omitempty"` + // +optional + Defaults corev1.ResourceList `json:"defaults,omitzero"` // Additional Configuration //+kubebuilder:default:={} - Config ResourcePoolSpecConfiguration `json:"config,omitempty"` + // +optional + Config ResourcePoolSpecConfiguration `json:"config,omitzero"` } type ResourcePoolSpecConfiguration struct { @@ -55,11 +57,15 @@ type ResourcePoolSpecConfiguration struct { // it's up the group of users within these namespaces, to manage the resources they consume per namespace. Each Resourcepool provisions a ResourceQuotainto all the selected namespaces. Then essentially the ResourcePoolClaims, when they can be assigned to the ResourcePool stack resources on top of that // ResourceQuota based on the namspace, where the ResourcePoolClaim was made from. type ResourcePool struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` + metav1.TypeMeta `json:",inline"` - Spec ResourcePoolSpec `json:"spec,omitempty"` - Status ResourcePoolStatus `json:"status,omitempty"` + // +optional + metav1.ObjectMeta `json:"metadata,omitzero"` + + Spec ResourcePoolSpec `json:"spec"` + + // +optional + Status ResourcePoolStatus `json:"status,omitzero"` } // +kubebuilder:object:root=true @@ -67,7 +73,9 @@ type ResourcePool struct { // ResourcePoolList contains a list of ResourcePool. type ResourcePoolList struct { metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` + + // +optional + metav1.ListMeta `json:"metadata,omitzero"` Items []ResourcePool `json:"items"` } diff --git a/api/v1beta2/resourcepoolclaim_types.go b/api/v1beta2/resourcepoolclaim_types.go index 85a096f2..bd2542ba 100644 --- a/api/v1beta2/resourcepoolclaim_types.go +++ b/api/v1beta2/resourcepoolclaim_types.go @@ -22,9 +22,11 @@ type ResourcePoolClaimSpec struct { // ResourceQuotaClaimStatus defines the observed state of ResourceQuotaClaim. type ResourcePoolClaimStatus struct { // Reference to the GlobalQuota being claimed from - Pool api.StatusNameUID `json:"pool,omitempty"` + // +optional + Pool api.StatusNameUID `json:"pool,omitzero"` // Condtion for this resource claim - Condition metav1.Condition `json:"condition,omitempty"` + // +optional + Condition metav1.Condition `json:"condition,omitzero"` } // +kubebuilder:object:root=true @@ -37,11 +39,15 @@ type ResourcePoolClaimStatus struct { // ResourcePoolClaim is the Schema for the resourcepoolclaims API. type ResourcePoolClaim struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` + metav1.TypeMeta `json:",inline"` - Spec ResourcePoolClaimSpec `json:"spec,omitempty"` - Status ResourcePoolClaimStatus `json:"status,omitempty"` + // +optional + metav1.ObjectMeta `json:"metadata,omitzero"` + + Spec ResourcePoolClaimSpec `json:"spec"` + + // +optional + Status ResourcePoolClaimStatus `json:"status,omitzero"` } // +kubebuilder:object:root=true @@ -49,7 +55,9 @@ type ResourcePoolClaim struct { // ResourceQuotaClaimList contains a list of ResourceQuotaClaim. type ResourcePoolClaimList struct { metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` + + // +optional + metav1.ListMeta `json:"metadata,omitzero"` Items []ResourcePoolClaim `json:"items"` } diff --git a/api/v1beta2/tenant_status.go b/api/v1beta2/tenant_status.go index 848d22ce..57602668 100644 --- a/api/v1beta2/tenant_status.go +++ b/api/v1beta2/tenant_status.go @@ -58,7 +58,8 @@ type TenantStatusNamespaceMetadata struct { type TenantAvailableStatus struct { // Available Class Types within Tenant - Classes TenantAvailableClassesStatus `json:"classes,omitempty"` + // +optional + Classes TenantAvailableClassesStatus `json:"classes,omitzero"` } type TenantAvailableClassesStatus struct { diff --git a/api/v1beta2/tenant_types.go b/api/v1beta2/tenant_types.go index b43482f6..68958631 100644 --- a/api/v1beta2/tenant_types.go +++ b/api/v1beta2/tenant_types.go @@ -16,7 +16,8 @@ import ( // TenantSpec defines the desired state of Tenant. type TenantSpec struct { // Specify Permissions for the Tenant. - Permissions Permissions `json:"permissions,omitempty"` + // +optional + Permissions Permissions `json:"permissions,omitzero"` // Specifies the owners of the Tenant. // Optional Owners api.OwnerListSpec `json:"owners,omitempty"` @@ -32,7 +33,8 @@ type TenantSpec struct { // Optional. StorageClasses *api.DefaultAllowedListSpec `json:"storageClasses,omitempty"` // Specifies options for the Ingress resources, such as allowed hostnames and IngressClass. Optional. - IngressOptions IngressOptions `json:"ingressOptions,omitempty"` + // +optional + IngressOptions IngressOptions `json:"ingressOptions,omitzero"` // Specifies the trusted Image Registries assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed trusted registries. Optional. ContainerRegistries *api.AllowedListSpec `json:"containerRegistries,omitempty"` // Specifies the label to control the placement of pods on a given pool of worker nodes. All namespaces created within the Tenant will have the node selector annotation. This annotation tells the Kubernetes scheduler to place pods on the nodes having the selector label. Optional. @@ -40,13 +42,16 @@ type TenantSpec struct { // Deprecated: Use Tenant Replications instead (https://projectcapsule.dev/docs/replications/) // // Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional. - NetworkPolicies api.NetworkPolicySpec `json:"networkPolicies,omitempty"` + // +optional + NetworkPolicies api.NetworkPolicySpec `json:"networkPolicies,omitzero"` // Deprecated: Use Tenant Replications instead (https://projectcapsule.dev/docs/replications/) // // Specifies the resource min/max usage restrictions to the Tenant. The assigned values are inherited by any namespace created in the Tenant. Optional. - LimitRanges api.LimitRangesSpec `json:"limitRanges,omitempty"` + // +optional + LimitRanges api.LimitRangesSpec `json:"limitRanges,omitzero"` // Specifies a list of ResourceQuota resources assigned to the Tenant. The assigned values are inherited by any namespace created in the Tenant. The Capsule operator aggregates ResourceQuota at Tenant level, so that the hard quota is never crossed for the given Tenant. This permits the Tenant owner to consume resources in the Tenant regardless of the namespace. Optional. - ResourceQuota api.ResourceQuotaSpec `json:"resourceQuotas,omitempty"` + // +optional + ResourceQuota api.ResourceQuotaSpec `json:"resourceQuotas,omitzero"` // Specifies additional RoleBindings assigned to the Tenant. Capsule will ensure that all namespaces in the Tenant always contain the RoleBinding for the given ClusterRole. Optional. AdditionalRoleBindings []api.AdditionalRoleBindingsSpec `json:"additionalRoleBindings,omitempty"` // Specify the allowed values for the imagePullPolicies option in Pod resources. Capsule assures that all Pod resources created in the Tenant can use only one of the allowed policy. Optional. @@ -63,7 +68,8 @@ type TenantSpec struct { // Specifies options for the DeviceClass resources. DeviceClasses *api.SelectorAllowedListSpec `json:"deviceClasses,omitempty"` // Specifies options for the GatewayClass resources. - GatewayOptions GatewayOptions `json:"gatewayOptions,omitempty"` + // +optional + GatewayOptions GatewayOptions `json:"gatewayOptions,omitzero"` // Toggling the Tenant resources cordoning, when enable resources cannot be deleted. //+kubebuilder:default:=false Cordoned bool `json:"cordoned,omitempty"` @@ -110,11 +116,15 @@ func (p *Permissions) ListMatchingOwners( // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="Age" // Tenant is the Schema for the tenants API. type Tenant struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` + metav1.TypeMeta `json:",inline"` - Spec TenantSpec `json:"spec,omitempty"` - Status TenantStatus `json:"status,omitempty"` + // +optional + metav1.ObjectMeta `json:"metadata,omitzero"` + + Spec TenantSpec `json:"spec"` + + // +optional + Status TenantStatus `json:"status,omitzero"` } func (in *Tenant) GetNamespaces() (res []string) { @@ -130,7 +140,7 @@ func (in *Tenant) GetNamespaces() (res []string) { // TenantList contains a list of Tenant. type TenantList struct { metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` + metav1.ListMeta `json:"metadata,omitzero"` Items []Tenant `json:"items"` } diff --git a/api/v1beta2/tenantresource_global.go b/api/v1beta2/tenantresource_global.go index 163a9d75..feb98675 100644 --- a/api/v1beta2/tenantresource_global.go +++ b/api/v1beta2/tenantresource_global.go @@ -13,7 +13,8 @@ type GlobalTenantResourceSpec struct { TenantResourceSpec `json:",inline"` // Defines the Tenant selector used target the tenants on which resources must be propagated. - TenantSelector metav1.LabelSelector `json:"tenantSelector,omitempty"` + // +optional + TenantSelector metav1.LabelSelector `json:"tenantSelector,omitzero"` } // GlobalTenantResourceStatus defines the observed state of GlobalTenantResource. @@ -21,7 +22,7 @@ type GlobalTenantResourceStatus struct { // List of Tenants addressed by the GlobalTenantResource. SelectedTenants []string `json:"selectedTenants"` // List of the replicated resources for the given TenantResource. - ProcessedItems ProcessedItems `json:"processedItems"` + ProcessedItems ProcessedItems `json:"processedItems,omitzero"` } type ProcessedItems []ObjectReferenceStatus @@ -42,11 +43,15 @@ func (p *ProcessedItems) AsSet() sets.Set[string] { // GlobalTenantResource allows to propagate resource replications to a specific subset of Tenant resources. type GlobalTenantResource struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` + metav1.TypeMeta `json:",inline"` - Spec GlobalTenantResourceSpec `json:"spec,omitempty"` - Status GlobalTenantResourceStatus `json:"status,omitempty"` + // +optional + metav1.ObjectMeta `json:"metadata,omitzero"` + + Spec GlobalTenantResourceSpec `json:"spec"` + + // +optional + Status GlobalTenantResourceStatus `json:"status,omitzero"` } // +kubebuilder:object:root=true @@ -54,7 +59,7 @@ type GlobalTenantResource struct { // GlobalTenantResourceList contains a list of GlobalTenantResource. type GlobalTenantResourceList struct { metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` + metav1.ListMeta `json:"metadata,omitzero"` Items []GlobalTenantResource `json:"items"` } diff --git a/api/v1beta2/tenantresource_namespaced.go b/api/v1beta2/tenantresource_namespaced.go index e3a22858..3151c928 100644 --- a/api/v1beta2/tenantresource_namespaced.go +++ b/api/v1beta2/tenantresource_namespaced.go @@ -56,11 +56,15 @@ type TenantResourceStatus struct { // The object must be deployed in a Tenant Namespace, and cannot reference object living in non-Tenant namespaces. // For such cases, the GlobalTenantResource must be used. type TenantResource struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` + metav1.TypeMeta `json:",inline"` - Spec TenantResourceSpec `json:"spec,omitempty"` - Status TenantResourceStatus `json:"status,omitempty"` + // +optional + metav1.ObjectMeta `json:"metadata,omitzero"` + + Spec TenantResourceSpec `json:"spec"` + + // +optional + Status TenantResourceStatus `json:"status,omitzero"` } // +kubebuilder:object:root=true @@ -68,7 +72,7 @@ type TenantResource struct { // TenantResourceList contains a list of TenantResource. type TenantResourceList struct { metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` + metav1.ListMeta `json:"metadata,omitzero"` Items []TenantResource `json:"items"` } diff --git a/charts/capsule/README.md b/charts/capsule/README.md index 3270f28c..72cbb533 100644 --- a/charts/capsule/README.md +++ b/charts/capsule/README.md @@ -122,7 +122,7 @@ The following Values have changed key or Value: | manager.options.generateCertificates | bool | `true` | Specifies whether capsule webhooks certificates should be generated by capsule operator | | manager.options.ignoreUserWithGroups | list | `[]` | Define groups which when found in the request of a user will be ignored by the Capsule this might be useful if you have one group where all the users are in, but you want to separate administrators from normal users with additional groups. | | manager.options.labels | object | `{}` | Additional labels to add to the CapsuleConfiguration resource | -| manager.options.logLevel | string | `"3"` | Set the log verbosity of the capsule with a value from 1 to 5 | +| manager.options.logLevel | string | `"info"` | Set the log verbosity of the capsule with a value from 1 to 5 | | manager.options.nodeMetadata | object | `{"forbiddenAnnotations":{"denied":[],"deniedRegex":""},"forbiddenLabels":{"denied":[],"deniedRegex":""}}` | Allows to set the forbidden metadata for the worker nodes that could be patched by a Tenant | | manager.options.protectedNamespaceRegex | string | `""` | If specified, disallows creation of namespaces matching the passed regexp | | manager.options.userNames | list | `[]` | DEPRECATED: use users properties. Names of the users considered as Capsule users. | diff --git a/charts/capsule/crds/capsule.clastix.io_capsuleconfigurations.yaml b/charts/capsule/crds/capsule.clastix.io_capsuleconfigurations.yaml index 4030bc0d..2ea5fa11 100644 --- a/charts/capsule/crds/capsule.clastix.io_capsuleconfigurations.yaml +++ b/charts/capsule/crds/capsule.clastix.io_capsuleconfigurations.yaml @@ -72,7 +72,7 @@ spec: However ServiceAccounts which have been promoted to owner can not promote further serviceAccounts. type: boolean enableTLSReconciler: - default: true + default: false description: |- Toggles the TLS reconciler, the controller that is able to generate CA and certificates for the webhooks when not using an already provided CA and certificate, or when these are managed externally with Vault, or cert-manager. @@ -117,9 +117,6 @@ spec: deniedRegex: type: string type: object - required: - - forbiddenAnnotations - - forbiddenLabels type: object overrides: default: @@ -198,6 +195,8 @@ spec: required: - enableTLSReconciler type: object + required: + - spec type: object served: true storage: true diff --git a/charts/capsule/crds/capsule.clastix.io_globaltenantresources.yaml b/charts/capsule/crds/capsule.clastix.io_globaltenantresources.yaml index d74c9f00..136259a8 100644 --- a/charts/capsule/crds/capsule.clastix.io_globaltenantresources.yaml +++ b/charts/capsule/crds/capsule.clastix.io_globaltenantresources.yaml @@ -291,6 +291,8 @@ spec: - processedItems - selectedTenants type: object + required: + - spec type: object served: true storage: true diff --git a/charts/capsule/crds/capsule.clastix.io_resourcepoolclaims.yaml b/charts/capsule/crds/capsule.clastix.io_resourcepoolclaims.yaml index dbfef905..7dfc2f6f 100644 --- a/charts/capsule/crds/capsule.clastix.io_resourcepoolclaims.yaml +++ b/charts/capsule/crds/capsule.clastix.io_resourcepoolclaims.yaml @@ -151,6 +151,8 @@ spec: type: string type: object type: object + required: + - spec type: object served: true storage: true diff --git a/charts/capsule/crds/capsule.clastix.io_resourcepools.yaml b/charts/capsule/crds/capsule.clastix.io_resourcepools.yaml index 65368c02..80aef6f5 100644 --- a/charts/capsule/crds/capsule.clastix.io_resourcepools.yaml +++ b/charts/capsule/crds/capsule.clastix.io_resourcepools.yaml @@ -321,6 +321,8 @@ spec: type: string type: array type: object + required: + - spec type: object served: true storage: true diff --git a/charts/capsule/crds/capsule.clastix.io_tenantresources.yaml b/charts/capsule/crds/capsule.clastix.io_tenantresources.yaml index 51009469..ccddb4a2 100644 --- a/charts/capsule/crds/capsule.clastix.io_tenantresources.yaml +++ b/charts/capsule/crds/capsule.clastix.io_tenantresources.yaml @@ -239,6 +239,8 @@ spec: required: - processedItems type: object + required: + - spec type: object served: true storage: true diff --git a/charts/capsule/crds/capsule.clastix.io_tenants.yaml b/charts/capsule/crds/capsule.clastix.io_tenants.yaml index 5319d1f8..17271817 100644 --- a/charts/capsule/crds/capsule.clastix.io_tenants.yaml +++ b/charts/capsule/crds/capsule.clastix.io_tenants.yaml @@ -1074,6 +1074,8 @@ spec: - size - state type: object + required: + - spec type: object served: true storage: false @@ -2890,6 +2892,8 @@ spec: - size - state type: object + required: + - spec type: object served: true storage: true diff --git a/charts/capsule/values.yaml b/charts/capsule/values.yaml index e863f02e..ce22c5a7 100644 --- a/charts/capsule/values.yaml +++ b/charts/capsule/values.yaml @@ -177,7 +177,7 @@ manager: # -- Workers (MaxConcurrentReconciles) is the maximum number of concurrent Reconciles which can be run (ALPHA). workers: 1 # -- Set the log verbosity of the capsule with a value from 1 to 5 - logLevel: '3' + logLevel: "info" # -- Define entities which are considered part of the Capsule construct. # Users not mentioned here will be ignored by Capsule users: diff --git a/e2e/namespace_status_test.go b/e2e/namespace_status_test.go index b8b3b02d..a3db8d9a 100644 --- a/e2e/namespace_status_test.go +++ b/e2e/namespace_status_test.go @@ -91,15 +91,26 @@ var _ = Describe("creating namespace with status lifecycle", Label("namespace", }) By("removing first namespace", func() { - Expect(k8sClient.Delete(context.TODO(), ns1)).Should(Succeed()) + cs := impersonationClient(tnt.Spec.Owners[0].UserSpec.Name, withDefaultGroups(nil)) + Expect(cs.Delete(context.TODO(), ns1)).Should(Succeed()) - t := &capsulev1beta2.Tenant{} - Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tnt.GetName()}, t)).Should(Succeed()) + Eventually(func(g Gomega) { + t := &capsulev1beta2.Tenant{} - Expect(t.Status.Size).To(Equal(uint(1))) + err := k8sClient.Get( + context.TODO(), + types.NamespacedName{Name: tnt.GetName()}, + t, + ) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(t.Status.Size).To(Equal(uint(1))) - instance := t.Status.GetInstance(&capsulev1beta2.TenantStatusNamespaceItem{Name: ns1.GetName(), UID: ns1.GetUID()}) - Expect(instance).To(BeNil(), "Namespace instance should be nil") + instance := t.Status.GetInstance(&capsulev1beta2.TenantStatusNamespaceItem{ + Name: ns1.GetName(), + UID: ns1.GetUID(), + }) + g.Expect(instance).To(BeNil(), "Namespace instance should be nil") + }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) }) By("removing second namespace", func() { diff --git a/e2e/sa_owner_promotion_test.go b/e2e/sa_owner_promotion_test.go index 6cfbe637..09a4d49b 100644 --- a/e2e/sa_owner_promotion_test.go +++ b/e2e/sa_owner_promotion_test.go @@ -104,9 +104,9 @@ var _ = Describe("Promoting ServiceAccounts to Owners", Label("config"), Label(" client client.Client matcher otypes.GomegaMatcher }{ - "owner": {client: impersonationClient(tnt.Spec.Owners[0].Name, withDefaultGroups(make([]string, 0)), k8sClient.Scheme()), matcher: Not(Succeed())}, - "rb-user": {client: impersonationClient("bob", withDefaultGroups(make([]string, 0)), k8sClient.Scheme()), matcher: Not(Succeed())}, - "rb-sa": {client: impersonationClient("system:serviceaccount:"+sa.GetNamespace()+":default", withDefaultGroups(make([]string, 0)), k8sClient.Scheme()), matcher: Not(Succeed())}, + "owner": {client: impersonationClient(tnt.Spec.Owners[0].Name, withDefaultGroups(make([]string, 0))), matcher: Not(Succeed())}, + "rb-user": {client: impersonationClient("bob", withDefaultGroups(make([]string, 0))), matcher: Not(Succeed())}, + "rb-sa": {client: impersonationClient("system:serviceaccount:"+sa.GetNamespace()+":default", withDefaultGroups(make([]string, 0))), matcher: Not(Succeed())}, } for name, tc := range personas { @@ -197,9 +197,9 @@ var _ = Describe("Promoting ServiceAccounts to Owners", Label("config"), Label(" client client.Client matcher otypes.GomegaMatcher }{ - "rb-user": {client: impersonationClient("bob", withDefaultGroups(make([]string, 0)), k8sClient.Scheme()), matcher: Not(Succeed())}, - "rb-sa": {client: impersonationClient("system:serviceaccount:"+sa.GetNamespace()+":default", withDefaultGroups(make([]string, 0)), k8sClient.Scheme()), matcher: Not(Succeed())}, - "owner": {client: impersonationClient(tnt.Spec.Owners[0].Name, withDefaultGroups(make([]string, 0)), k8sClient.Scheme()), matcher: Succeed()}, + "rb-user": {client: impersonationClient("bob", withDefaultGroups(make([]string, 0))), matcher: Not(Succeed())}, + "rb-sa": {client: impersonationClient("system:serviceaccount:"+sa.GetNamespace()+":default", withDefaultGroups(make([]string, 0))), matcher: Not(Succeed())}, + "owner": {client: impersonationClient(tnt.Spec.Owners[0].Name, withDefaultGroups(make([]string, 0))), matcher: Succeed()}, } for name, tc := range personas { @@ -258,7 +258,7 @@ var _ = Describe("Promoting ServiceAccounts to Owners", Label("config"), Label(" client client.Client matcher otypes.GomegaMatcher }{ - "owner": {client: impersonationClient(tnt.Spec.Owners[0].Name, withDefaultGroups(make([]string, 0)), k8sClient.Scheme()), matcher: Succeed()}, + "owner": {client: impersonationClient(tnt.Spec.Owners[0].Name, withDefaultGroups(make([]string, 0))), matcher: Succeed()}, } for name, tc := range personas { @@ -294,14 +294,21 @@ var _ = Describe("Promoting ServiceAccounts to Owners", Label("config"), Label(" saClient := impersonationClient( fmt.Sprintf("system:serviceaccount:%s:%s", ns.Name, sa.Name), nil, - k8sClient.Scheme(), ) - newNs := NewNamespace("") - Expect(saClient.Create(context.TODO(), newNs)).To(Succeed()) - TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElements(ns.GetName())) + By("preventing the service account from deleting the namespace", func() { + newNs := NewNamespace("") + Expect(saClient.Create(context.TODO(), newNs)).To(Succeed()) - Expect(saClient.Delete(context.TODO(), newNs)).To(Not(Succeed())) + TenantNamespaceList(tnt, defaultTimeoutInterval). + Should(ContainElements(ns.GetName(), newNs.GetName())) + + Eventually(func(g Gomega) { + // Deletion should eventually be forbidden / fail + g.Expect(saClient.Delete(context.TODO(), newNs)). + ToNot(Succeed()) + }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) + }) for name, tc := range personas { By(fmt.Sprintf("trying to promote SA as %s", name)) diff --git a/e2e/suite_test.go b/e2e/suite_test.go index 02c81614..77250708 100644 --- a/e2e/suite_test.go +++ b/e2e/suite_test.go @@ -12,7 +12,6 @@ import ( . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" @@ -115,16 +114,17 @@ func ownerClient(owner api.UserSpec) (cs kubernetes.Interface) { return cs } -func impersonationClient(user string, groups []string, scheme *runtime.Scheme) client.Client { - c, err := config.GetConfig() - Expect(err).ToNot(HaveOccurred()) - c.Impersonate = rest.ImpersonationConfig{ +func impersonationClient(user string, groups []string) client.Client { + impersonatedCfg := rest.CopyConfig(cfg) + impersonatedCfg.Impersonate = rest.ImpersonationConfig{ UserName: user, Groups: groups, } - cl, err := client.New(c, client.Options{Scheme: scheme}) + + c, err := client.New(impersonatedCfg, client.Options{Scheme: k8sClient.Scheme()}) Expect(err).ToNot(HaveOccurred()) - return cl + + return c } func withDefaultGroups(groups []string) []string { diff --git a/internal/controllers/resources/processor.go b/internal/controllers/resources/processor.go index cd00a0e3..ab804fde 100644 --- a/internal/controllers/resources/processor.go +++ b/internal/controllers/resources/processor.go @@ -7,6 +7,7 @@ import ( "context" "errors" "fmt" + "maps" "sync" "github.com/valyala/fasttemplate" @@ -40,13 +41,8 @@ func prepareAdditionalMetadata(m map[string]string) map[string]string { return make(map[string]string) } - // we need to create a new map to avoid modifying the original one - copied := make(map[string]string, len(m)) - for k, v := range m { - copied[k] = v - } - - return copied + // clone without mutating the original + return maps.Clone(m) } func (r *Processor) HandlePruning(ctx context.Context, current, desired sets.Set[string]) (updateStatus bool) { @@ -249,12 +245,12 @@ func (r *Processor) HandleSection(ctx context.Context, tnt capsulev1beta2.Tenant t := fasttemplate.New(template, "{{ ", " }}") - tmplString := t.ExecuteString(map[string]interface{}{ + tmplString := t.ExecuteString(map[string]any{ "tenant.name": tnt.Name, "namespace": ns.Name, }) - obj, keysAndValues := unstructured.Unstructured{}, []interface{}{"index", rawIndex} + obj, keysAndValues := unstructured.Unstructured{}, []any{"index", rawIndex} if _, _, decodeErr := codecFactory.UniversalDeserializer().Decode([]byte(tmplString), nil, &obj); decodeErr != nil { log.Error(decodeErr, "unable to deserialize rawItem", keysAndValues...) @@ -304,27 +300,18 @@ func (r *Processor) createOrUpdate(ctx context.Context, obj *unstructured.Unstru rv := actual.GetResourceVersion() actual.SetUnstructuredContent(desired.Object) - combinedLabels := obj.GetLabels() - if combinedLabels == nil { - combinedLabels = make(map[string]string) - } - - for key, value := range labels { - combinedLabels[key] = value - } + combinedLabels := map[string]string{} + maps.Copy(combinedLabels, obj.GetLabels()) + maps.Copy(combinedLabels, labels) actual.SetLabels(combinedLabels) - combinedAnnotations := obj.GetAnnotations() - if combinedAnnotations == nil { - combinedAnnotations = make(map[string]string) - } - - for key, value := range annotations { - combinedAnnotations[key] = value - } + combinedAnnotations := map[string]string{} + maps.Copy(combinedAnnotations, obj.GetAnnotations()) + maps.Copy(combinedAnnotations, annotations) actual.SetAnnotations(combinedAnnotations) + actual.SetResourceVersion(rv) actual.SetUID(UID) diff --git a/internal/controllers/tenant/manager.go b/internal/controllers/tenant/manager.go index 064a06f9..167a9df6 100644 --- a/internal/controllers/tenant/manager.go +++ b/internal/controllers/tenant/manager.go @@ -6,6 +6,7 @@ package tenant import ( "context" "fmt" + "slices" "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" @@ -155,13 +156,7 @@ func (r *Manager) SetupWithManager(mgr ctrl.Manager, ctrlConfig utils.Controller q workqueue.TypedRateLimitingInterface[reconcile.Request], ) { r.enqueueForTenantsWithCondition(ctx, e.Object, q, func(tnt *capsulev1beta2.Tenant, c client.Object) bool { - for _, n := range tnt.Status.Namespaces { - if n == c.GetNamespace() { - return true - } - } - - return false + return slices.Contains(tnt.Status.Namespaces, c.GetNamespace()) }) }, UpdateFunc: func( @@ -170,13 +165,7 @@ func (r *Manager) SetupWithManager(mgr ctrl.Manager, ctrlConfig utils.Controller q workqueue.TypedRateLimitingInterface[reconcile.Request], ) { r.enqueueForTenantsWithCondition(ctx, e.ObjectNew, q, func(tnt *capsulev1beta2.Tenant, c client.Object) bool { - for _, n := range tnt.Status.Namespaces { - if n == c.GetNamespace() { - return true - } - } - - return false + return slices.Contains(tnt.Status.Namespaces, c.GetNamespace()) }) }, DeleteFunc: func( @@ -241,6 +230,7 @@ func (r *Manager) SetupWithManager(mgr ctrl.Manager, ctrlConfig utils.Controller func (r Manager) Reconcile(ctx context.Context, request ctrl.Request) (result ctrl.Result, err error) { r.Log = r.Log.WithValues("Request.Name", request.Name) + // Fetch the Tenant instance instance := &capsulev1beta2.Tenant{} if err = r.Get(ctx, request.NamespacedName, instance); err != nil { diff --git a/internal/controllers/tenant/namespaces.go b/internal/controllers/tenant/namespaces.go index 6285c204..814470ce 100644 --- a/internal/controllers/tenant/namespaces.go +++ b/internal/controllers/tenant/namespaces.go @@ -7,6 +7,7 @@ import ( "context" "fmt" "maps" + "slices" "golang.org/x/sync/errgroup" corev1 "k8s.io/api/core/v1" @@ -213,7 +214,12 @@ func (r *Manager) collectNamespaces(ctx context.Context, tenant *capsulev1beta2. return err } - tenant.AssignNamespaces(list.Items) + // Drop namespaces that are currently being deleted (DeletionTimestamp != nil) + activeNamespaces := slices.DeleteFunc(list.Items, func(ns corev1.Namespace) bool { + return ns.DeletionTimestamp != nil + }) + + tenant.AssignNamespaces(activeNamespaces) return err } diff --git a/internal/controllers/utils/predicates.go b/internal/controllers/utils/predicates.go index df70310a..a58d6092 100644 --- a/internal/controllers/utils/predicates.go +++ b/internal/controllers/utils/predicates.go @@ -4,6 +4,8 @@ package utils import ( + "slices" + "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/event" @@ -98,12 +100,6 @@ func LabelsChanged(keys []string, oldLabels, newLabels map[string]string) bool { func NamesMatchingPredicate(names ...string) builder.Predicates { return builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool { - for _, name := range names { - if object.GetName() == name { - return true - } - } - - return false + return slices.Contains(names, object.GetName()) })) } diff --git a/internal/webhook/namespace/mutation/handler.go b/internal/webhook/namespace/mutation/handler.go index 42ac7776..fc964f7f 100644 --- a/internal/webhook/namespace/mutation/handler.go +++ b/internal/webhook/namespace/mutation/handler.go @@ -30,7 +30,6 @@ type handler struct { handlers []webhook.TypedHandler[*corev1.Namespace] } -//nolint:dupl func (h *handler) OnCreate(c client.Client, decoder admission.Decoder, recorder record.EventRecorder) webhook.Func { return func(ctx context.Context, req admission.Request) *admission.Response { userIsAdmin := users.IsAdminUser(req, h.cfg.Administrators()) @@ -63,35 +62,8 @@ func (h *handler) OnCreate(c client.Client, decoder admission.Decoder, recorder } } -//nolint:dupl func (h *handler) OnDelete(c client.Client, decoder admission.Decoder, recorder record.EventRecorder) webhook.Func { - return func(ctx context.Context, req admission.Request) *admission.Response { - userIsAdmin := users.IsAdminUser(req, h.cfg.Administrators()) - - if !userIsAdmin && !users.IsCapsuleUser(ctx, c, h.cfg, req.UserInfo.Username, req.UserInfo.Groups) { - return nil - } - - ns := &corev1.Namespace{} - if err := decoder.Decode(req, ns); err != nil { - return utils.ErroredResponse(err) - } - - tnt, err := tenant.GetTenantByLabels(ctx, c, ns) - if err != nil { - return utils.ErroredResponse(err) - } - - if tnt == nil && userIsAdmin { - return nil - } - - for _, hndl := range h.handlers { - if response := hndl.OnDelete(c, ns, decoder, recorder)(ctx, req); response != nil { - return response - } - } - + return func(context.Context, admission.Request) *admission.Response { return nil } } diff --git a/internal/webhook/namespace/mutation/metadata.go b/internal/webhook/namespace/mutation/metadata.go index f786eb31..28b41ccb 100644 --- a/internal/webhook/namespace/mutation/metadata.go +++ b/internal/webhook/namespace/mutation/metadata.go @@ -6,6 +6,7 @@ package mutation import ( "context" "encoding/json" + "maps" "net/http" corev1 "k8s.io/api/core/v1" @@ -134,12 +135,10 @@ func mergeStringMap(dst, src map[string]string) map[string]string { } if dst == nil { - dst = make(map[string]string, len(src)) + return maps.Clone(src) } - for k, v := range src { - dst[k] = v - } + maps.Copy(dst, src) return dst } diff --git a/internal/webhook/namespace/validation/handler.go b/internal/webhook/namespace/validation/handler.go index 41de7fb8..94789893 100644 --- a/internal/webhook/namespace/validation/handler.go +++ b/internal/webhook/namespace/validation/handler.go @@ -33,7 +33,6 @@ type handler struct { handlers []webhook.TypedHandlerWithTenant[*corev1.Namespace] } -//nolint:dupl func (h *handler) OnCreate(c client.Client, decoder admission.Decoder, recorder record.EventRecorder) webhook.Func { return func(ctx context.Context, req admission.Request) *admission.Response { userIsAdmin := users.IsAdminUser(req, h.cfg.Administrators()) @@ -66,7 +65,6 @@ func (h *handler) OnCreate(c client.Client, decoder admission.Decoder, recorder } } -//nolint:dupl func (h *handler) OnDelete(c client.Client, decoder admission.Decoder, recorder record.EventRecorder) webhook.Func { return func(ctx context.Context, req admission.Request) *admission.Response { userIsAdmin := users.IsAdminUser(req, h.cfg.Administrators()) @@ -76,7 +74,7 @@ func (h *handler) OnDelete(c client.Client, decoder admission.Decoder, recorder } ns := &corev1.Namespace{} - if err := decoder.Decode(req, ns); err != nil { + if err := decoder.DecodeRaw(req.OldObject, ns); err != nil { return utils.ErroredResponse(err) } diff --git a/pkg/api/exhaustion.go b/pkg/api/exhaustion.go index 1ec59053..eb149873 100644 --- a/pkg/api/exhaustion.go +++ b/pkg/api/exhaustion.go @@ -10,7 +10,9 @@ import ( // +kubebuilder:object:generate=true type PoolExhaustionResource struct { // Available Resources to be claimed - Available resource.Quantity `json:"available,omitempty"` + // +optional + Available resource.Quantity `json:"available,omitzero"` // Requesting Resources - Requesting resource.Quantity `json:"requesting,omitempty"` + // +optional + Requesting resource.Quantity `json:"requesting,omitzero"` } diff --git a/pkg/api/owner_list.go b/pkg/api/owner_list.go index 8c801abc..dcb3123a 100644 --- a/pkg/api/owner_list.go +++ b/pkg/api/owner_list.go @@ -4,6 +4,7 @@ package api import ( + "slices" "sort" ) @@ -19,10 +20,8 @@ func (o OwnerListSpec) IsOwner(name string, groups []string) bool { return true } case GroupOwner: - for _, group := range groups { - if group == owner.Name { - return true - } + if slices.Contains(groups, owner.Name) { + return true } } } diff --git a/pkg/api/service_options.go b/pkg/api/service_options.go index 68f52b98..03a4ddf1 100644 --- a/pkg/api/service_options.go +++ b/pkg/api/service_options.go @@ -13,7 +13,9 @@ type ServiceOptions struct { // Specifies the external IPs that can be used in Services with type ClusterIP. An empty list means no IPs are allowed. Optional. ExternalServiceIPs *ExternalServiceIPsSpec `json:"externalIPs,omitempty"` // Define the labels that a Tenant Owner cannot set for their Service resources. - ForbiddenLabels ForbiddenListSpec `json:"forbiddenLabels,omitempty"` + // +optional + ForbiddenLabels ForbiddenListSpec `json:"forbiddenLabels,omitzero"` // Define the annotations that a Tenant Owner cannot set for their Service resources. - ForbiddenAnnotations ForbiddenListSpec `json:"forbiddenAnnotations,omitempty"` + // +optional + ForbiddenAnnotations ForbiddenListSpec `json:"forbiddenAnnotations,omitzero"` } diff --git a/pkg/template/fast.go b/pkg/template/fast.go index 8528246d..ee6b8e97 100644 --- a/pkg/template/fast.go +++ b/pkg/template/fast.go @@ -20,7 +20,7 @@ func TemplateForTenantAndNamespace(m map[string]string, tnt *capsulev1beta2.Tena } t := fasttemplate.New(v, "{{ ", " }}") - tmplString := t.ExecuteString(map[string]interface{}{ + tmplString := t.ExecuteString(map[string]any{ "tenant.name": tnt.Name, "namespace": ns.Name, }) diff --git a/pkg/utils/users/is_capsule_user.go b/pkg/utils/users/is_capsule_user.go index 1799c21a..40d2c8b1 100644 --- a/pkg/utils/users/is_capsule_user.go +++ b/pkg/utils/users/is_capsule_user.go @@ -50,6 +50,7 @@ func IsCapsuleUser( } } + //nolint:modernize for _, group := range cfg.UserGroups() { if groupList.Find(group) { if len(cfg.IgnoreUserWithGroups()) > 0 {