mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
chore: kube-api lint fixes
Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
parent
84dd0de35e
commit
2792985191
@ -11,20 +11,24 @@ import { DaemonSet } from "@k8slens/kube-object";
|
|||||||
export class DaemonSetApi extends KubeApi<DaemonSet> {
|
export class DaemonSetApi extends KubeApi<DaemonSet> {
|
||||||
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
||||||
super(deps, {
|
super(deps, {
|
||||||
...opts ?? {},
|
...(opts ?? {}),
|
||||||
objectConstructor: DaemonSet,
|
objectConstructor: DaemonSet,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
restart(params: NamespacedResourceDescriptor) {
|
restart(params: NamespacedResourceDescriptor) {
|
||||||
return this.patch(params, {
|
return this.patch(
|
||||||
spec: {
|
params,
|
||||||
template: {
|
{
|
||||||
metadata: {
|
spec: {
|
||||||
annotations: { "kubectl.kubernetes.io/restartedAt" : moment.utc().format() },
|
template: {
|
||||||
|
metadata: {
|
||||||
|
annotations: { "kubectl.kubernetes.io/restartedAt": moment.utc().format() },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, "strategic");
|
"strategic",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import { Deployment } from "@k8slens/kube-object";
|
|||||||
export class DeploymentApi extends KubeApi<Deployment> {
|
export class DeploymentApi extends KubeApi<Deployment> {
|
||||||
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
||||||
super(deps, {
|
super(deps, {
|
||||||
...opts ?? {},
|
...(opts ?? {}),
|
||||||
objectConstructor: Deployment,
|
objectConstructor: Deployment,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -24,18 +24,22 @@ export class DeploymentApi extends KubeApi<Deployment> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
scale(params: NamespacedResourceDescriptor, replicas: number) {
|
scale(params: NamespacedResourceDescriptor, replicas: number) {
|
||||||
return this.scaleResource(params, { spec: { replicas }});
|
return this.scaleResource(params, { spec: { replicas } });
|
||||||
}
|
}
|
||||||
|
|
||||||
restart(params: NamespacedResourceDescriptor) {
|
restart(params: NamespacedResourceDescriptor) {
|
||||||
return this.patch(params, {
|
return this.patch(
|
||||||
spec: {
|
params,
|
||||||
template: {
|
{
|
||||||
metadata: {
|
spec: {
|
||||||
annotations: { "kubectl.kubernetes.io/restartedAt" : moment.utc().format() },
|
template: {
|
||||||
|
metadata: {
|
||||||
|
annotations: { "kubectl.kubernetes.io/restartedAt": moment.utc().format() },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, "strategic");
|
"strategic",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,14 +11,9 @@ export class HorizontalPodAutoscalerApi extends KubeApi<HorizontalPodAutoscaler>
|
|||||||
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
||||||
super(deps, {
|
super(deps, {
|
||||||
allowedUsableVersions: {
|
allowedUsableVersions: {
|
||||||
autoscaling: [
|
autoscaling: ["v2", "v2beta2", "v2beta1", "v1"],
|
||||||
"v2",
|
|
||||||
"v2beta2",
|
|
||||||
"v2beta1",
|
|
||||||
"v1",
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
...opts ?? {},
|
...(opts ?? {}),
|
||||||
objectConstructor: HorizontalPodAutoscaler,
|
objectConstructor: HorizontalPodAutoscaler,
|
||||||
checkPreferredVersion: true,
|
checkPreferredVersion: true,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -17,12 +17,16 @@ export class IngressClassApi extends KubeApi<IngressClass> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setAsDefault({ name }: ResourceDescriptor, isDefault = true) {
|
setAsDefault({ name }: ResourceDescriptor, isDefault = true) {
|
||||||
return this.patch({ name }, {
|
return this.patch(
|
||||||
metadata: {
|
{ name },
|
||||||
annotations: {
|
{
|
||||||
[IngressClass.ANNOTATION_IS_DEFAULT]: String(isDefault),
|
metadata: {
|
||||||
|
annotations: {
|
||||||
|
[IngressClass.ANNOTATION_IS_DEFAULT]: String(isDefault),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, "strategic");
|
"strategic",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import { KubeApi } from "../kube-api";
|
|||||||
export class IngressApi extends KubeApi<Ingress> {
|
export class IngressApi extends KubeApi<Ingress> {
|
||||||
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
||||||
super(deps, {
|
super(deps, {
|
||||||
...opts ?? {},
|
...(opts ?? {}),
|
||||||
objectConstructor: Ingress,
|
objectConstructor: Ingress,
|
||||||
// Add fallback for Kubernetes <1.19
|
// Add fallback for Kubernetes <1.19
|
||||||
checkPreferredVersion: true,
|
checkPreferredVersion: true,
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import { Job } from "@k8slens/kube-object";
|
|||||||
export class JobApi extends KubeApi<Job> {
|
export class JobApi extends KubeApi<Job> {
|
||||||
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
||||||
super(deps, {
|
super(deps, {
|
||||||
...opts ?? {},
|
...(opts ?? {}),
|
||||||
objectConstructor: Job,
|
objectConstructor: Job,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import { Lease } from "@k8slens/kube-object";
|
|||||||
export class LeaseApi extends KubeApi<Lease> {
|
export class LeaseApi extends KubeApi<Lease> {
|
||||||
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
||||||
super(deps, {
|
super(deps, {
|
||||||
...opts ?? {},
|
...(opts ?? {}),
|
||||||
objectConstructor: Lease,
|
objectConstructor: Lease,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,12 +7,11 @@ import { LimitRange } from "@k8slens/kube-object";
|
|||||||
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
|
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
|
||||||
import { KubeApi } from "../kube-api";
|
import { KubeApi } from "../kube-api";
|
||||||
|
|
||||||
|
|
||||||
export class LimitRangeApi extends KubeApi<LimitRange> {
|
export class LimitRangeApi extends KubeApi<LimitRange> {
|
||||||
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
||||||
super(deps, {
|
super(deps, {
|
||||||
objectConstructor: LimitRange,
|
objectConstructor: LimitRange,
|
||||||
...opts ?? {},
|
...(opts ?? {}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import { KubeApi } from "../kube-api";
|
|||||||
export class MutatingWebhookConfigurationApi extends KubeApi<MutatingWebhookConfiguration> {
|
export class MutatingWebhookConfigurationApi extends KubeApi<MutatingWebhookConfiguration> {
|
||||||
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
||||||
super(deps, {
|
super(deps, {
|
||||||
...opts ?? {},
|
...(opts ?? {}),
|
||||||
objectConstructor: MutatingWebhookConfiguration,
|
objectConstructor: MutatingWebhookConfiguration,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import { Namespace } from "@k8slens/kube-object";
|
|||||||
export class NamespaceApi extends KubeApi<Namespace> {
|
export class NamespaceApi extends KubeApi<Namespace> {
|
||||||
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
||||||
super(deps, {
|
super(deps, {
|
||||||
...opts ?? {},
|
...(opts ?? {}),
|
||||||
objectConstructor: Namespace,
|
objectConstructor: Namespace,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import { KubeApi } from "../kube-api";
|
|||||||
export class NodeApi extends KubeApi<Node> {
|
export class NodeApi extends KubeApi<Node> {
|
||||||
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
||||||
super(deps, {
|
super(deps, {
|
||||||
...opts ?? {},
|
...(opts ?? {}),
|
||||||
objectConstructor: Node,
|
objectConstructor: Node,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,9 +10,8 @@ import { PersistentVolumeClaim } from "@k8slens/kube-object";
|
|||||||
export class PersistentVolumeClaimApi extends KubeApi<PersistentVolumeClaim> {
|
export class PersistentVolumeClaimApi extends KubeApi<PersistentVolumeClaim> {
|
||||||
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
||||||
super(deps, {
|
super(deps, {
|
||||||
...opts ?? {},
|
...(opts ?? {}),
|
||||||
objectConstructor: PersistentVolumeClaim,
|
objectConstructor: PersistentVolumeClaim,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -12,7 +12,6 @@ export class PodSecurityPolicyApi extends KubeApi<PodSecurityPolicy> {
|
|||||||
super(deps, {
|
super(deps, {
|
||||||
...opts,
|
...opts,
|
||||||
objectConstructor: PodSecurityPolicy,
|
objectConstructor: PodSecurityPolicy,
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,11 +13,10 @@ import { KubeApi } from "../kube-api";
|
|||||||
import type { KubeStatusData, PodLogsQuery } from "@k8slens/kube-object";
|
import type { KubeStatusData, PodLogsQuery } from "@k8slens/kube-object";
|
||||||
import { isKubeStatusData, KubeStatus, Pod } from "@k8slens/kube-object";
|
import { isKubeStatusData, KubeStatus, Pod } from "@k8slens/kube-object";
|
||||||
|
|
||||||
|
|
||||||
export class PodApi extends KubeApi<Pod> {
|
export class PodApi extends KubeApi<Pod> {
|
||||||
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
||||||
super(deps, {
|
super(deps, {
|
||||||
...opts ?? {},
|
...(opts ?? {}),
|
||||||
objectConstructor: Pod,
|
objectConstructor: Pod,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -47,6 +46,7 @@ export class PodApi extends KubeApi<Pod> {
|
|||||||
if (status.code >= 200 && status.code < 300) {
|
if (status.code >= 200 && status.code < 300) {
|
||||||
return status.getExplanation();
|
return status.getExplanation();
|
||||||
} else {
|
} else {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-throw-literal
|
||||||
throw status.getExplanation();
|
throw status.getExplanation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import { KubeApi } from "../kube-api";
|
|||||||
import type { PriorityClassData } from "@k8slens/kube-object";
|
import type { PriorityClassData } from "@k8slens/kube-object";
|
||||||
import { PriorityClass } from "@k8slens/kube-object";
|
import { PriorityClass } from "@k8slens/kube-object";
|
||||||
|
|
||||||
|
|
||||||
export class PriorityClassApi extends KubeApi<PriorityClass, PriorityClassData> {
|
export class PriorityClassApi extends KubeApi<PriorityClass, PriorityClassData> {
|
||||||
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
|
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
|
||||||
super(deps, {
|
super(deps, {
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import { ReplicaSet } from "@k8slens/kube-object";
|
|||||||
export class ReplicaSetApi extends KubeApi<ReplicaSet> {
|
export class ReplicaSetApi extends KubeApi<ReplicaSet> {
|
||||||
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
||||||
super(deps, {
|
super(deps, {
|
||||||
...opts ?? {},
|
...(opts ?? {}),
|
||||||
objectConstructor: ReplicaSet,
|
objectConstructor: ReplicaSet,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -22,6 +22,6 @@ export class ReplicaSetApi extends KubeApi<ReplicaSet> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
scale(params: NamespacedResourceDescriptor, replicas: number) {
|
scale(params: NamespacedResourceDescriptor, replicas: number) {
|
||||||
return this.scaleResource(params, { spec: { replicas }});
|
return this.scaleResource(params, { spec: { replicas } });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import type { Scale } from "@k8slens/kube-object";
|
|||||||
export class ReplicationControllerApi extends KubeApi<ReplicationController> {
|
export class ReplicationControllerApi extends KubeApi<ReplicationController> {
|
||||||
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
||||||
super(deps, {
|
super(deps, {
|
||||||
...opts ?? {},
|
...(opts ?? {}),
|
||||||
objectConstructor: ReplicationController,
|
objectConstructor: ReplicationController,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -25,6 +25,6 @@ export class ReplicationControllerApi extends KubeApi<ReplicationController> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
scale(params: NamespacedResourceDescriptor, replicas: number): Promise<Scale> {
|
scale(params: NamespacedResourceDescriptor, replicas: number): Promise<Scale> {
|
||||||
return this.scaleResource(params, { spec: { replicas }});
|
return this.scaleResource(params, { spec: { replicas } });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import { RoleBinding } from "@k8slens/kube-object";
|
|||||||
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
|
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
|
||||||
import { KubeApi } from "../kube-api";
|
import { KubeApi } from "../kube-api";
|
||||||
|
|
||||||
|
|
||||||
export class RoleBindingApi extends KubeApi<RoleBinding, RoleBindingData> {
|
export class RoleBindingApi extends KubeApi<RoleBinding, RoleBindingData> {
|
||||||
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
|
constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) {
|
||||||
super(deps, {
|
super(deps, {
|
||||||
|
|||||||
@ -10,16 +10,19 @@ import { KubeApi } from "../kube-api";
|
|||||||
export class SelfSubjectRulesReviewApi extends KubeApi<SelfSubjectRulesReview> {
|
export class SelfSubjectRulesReviewApi extends KubeApi<SelfSubjectRulesReview> {
|
||||||
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
||||||
super(deps, {
|
super(deps, {
|
||||||
...opts ?? {},
|
...(opts ?? {}),
|
||||||
objectConstructor: SelfSubjectRulesReview,
|
objectConstructor: SelfSubjectRulesReview,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
create({ namespace = "default" }) {
|
create({ namespace = "default" }) {
|
||||||
return super.create({}, {
|
return super.create(
|
||||||
spec: {
|
{},
|
||||||
namespace,
|
{
|
||||||
|
spec: {
|
||||||
|
namespace,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import { StatefulSet } from "@k8slens/kube-object";
|
|||||||
export class StatefulSetApi extends KubeApi<StatefulSet> {
|
export class StatefulSetApi extends KubeApi<StatefulSet> {
|
||||||
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
||||||
super(deps, {
|
super(deps, {
|
||||||
...opts ?? {},
|
...(opts ?? {}),
|
||||||
objectConstructor: StatefulSet,
|
objectConstructor: StatefulSet,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -24,18 +24,22 @@ export class StatefulSetApi extends KubeApi<StatefulSet> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
scale(params: NamespacedResourceDescriptor, replicas: number) {
|
scale(params: NamespacedResourceDescriptor, replicas: number) {
|
||||||
return this.scaleResource(params, { spec: { replicas }});
|
return this.scaleResource(params, { spec: { replicas } });
|
||||||
}
|
}
|
||||||
|
|
||||||
restart(params: NamespacedResourceDescriptor) {
|
restart(params: NamespacedResourceDescriptor) {
|
||||||
return this.patch(params, {
|
return this.patch(
|
||||||
spec: {
|
params,
|
||||||
template: {
|
{
|
||||||
metadata: {
|
spec: {
|
||||||
annotations: { "kubectl.kubernetes.io/restartedAt" : moment.utc().format() },
|
template: {
|
||||||
|
metadata: {
|
||||||
|
annotations: { "kubectl.kubernetes.io/restartedAt": moment.utc().format() },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, "strategic");
|
"strategic",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import { KubeApi } from "../kube-api";
|
|||||||
export class ValidatingWebhookConfigurationApi extends KubeApi<ValidatingWebhookConfiguration> {
|
export class ValidatingWebhookConfigurationApi extends KubeApi<ValidatingWebhookConfiguration> {
|
||||||
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
||||||
super(deps, {
|
super(deps, {
|
||||||
...opts ?? {},
|
...(opts ?? {}),
|
||||||
objectConstructor: ValidatingWebhookConfiguration,
|
objectConstructor: ValidatingWebhookConfiguration,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,11 +7,10 @@ import { VerticalPodAutoscaler } from "@k8slens/kube-object";
|
|||||||
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
|
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
|
||||||
import { KubeApi } from "../kube-api";
|
import { KubeApi } from "../kube-api";
|
||||||
|
|
||||||
|
|
||||||
export class VerticalPodAutoscalerApi extends KubeApi<VerticalPodAutoscaler> {
|
export class VerticalPodAutoscalerApi extends KubeApi<VerticalPodAutoscaler> {
|
||||||
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
|
||||||
super(deps, {
|
super(deps, {
|
||||||
...opts ?? {},
|
...(opts ?? {}),
|
||||||
objectConstructor: VerticalPodAutoscaler,
|
objectConstructor: VerticalPodAutoscaler,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -543,7 +543,7 @@ export class KubeApi<
|
|||||||
// custom apis might return array for list response, e.g. users, groups, etc.
|
// custom apis might return array for list response, e.g. users, groups, etc.
|
||||||
return data
|
return data
|
||||||
.filter(isJsonApiData)
|
.filter(isJsonApiData)
|
||||||
.map((data) => (this.ensureMetadataSelfLink(data.metadata), data))
|
.map((data) => (this.ensureMetadataSelfLink(data.metadata), data)) // eslint-disable-line no-sequences
|
||||||
.map((data) => new KubeObjectConstructor(data as Data));
|
.map((data) => new KubeObjectConstructor(data as Data));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user