Repository: cdk8s-team/cdk8s-plus (ts)

Issue:
Add IngressClassName to Ingress
### Description of the feature or enhancement:
We would like to be able to configure the `IngressClassName` on `Ingress` objects. 


### Use Case:
The IngressClassName is part of the officiall documentation and was added within Kubernetes version 1.18.

"used to reference the name of the Ingress controller that should implement the Ingress, the field is a reference to an IngressClass resource that contains additional Ingress configuration, including the name of the Ingress controller" (https://kubernetes.io/docs/concepts/services-networking/ingress)


### Proposed Solution:
Currently we have implemented it as an extension like follows:
```
import { JsonPatch } from "cdk8s";
import { Ingress, IngressProps } from "cdk8s-plus-25";
import { Construct } from "constructs";

export class IngressWithClassName extends Ingress {
    constructor(scope: Construct, id: string, props: IngressProps & { ingressClassName: string }) {
        super(scope, id, props);

        this.apiObject.addJsonPatch(
            JsonPatch.add(
                '/spec/ingressClassName',
                props.ingressClassName
            )
        );
    }
}
```



---

This is a :rocket: Feature Request

Pull request description:
fix(ingress): add `ingressClassName` to `IngressProps`
Fixes #2856 

Currently, `IngressProps` does not have a property allowing users to pass in an `ingressClassName`. This PR fixes this and adds a unit test to verify the change.

Patch:
diff --git a/src/ingress.ts b/src/ingress.ts
index 09bfceb7..983ddbbf 100644
--- a/src/ingress.ts
+++ b/src/ingress.ts
@@ -40,6 +40,14 @@ export interface IngressProps extends base.ResourceProps {
    * extension, if the ingress controller fulfilling the ingress supports SNI.
    */
   readonly tls?: IngressTls[];
+
+  /**
+   * Class Name for this ingress.
+   *
+   * This field is a reference to an IngressClass resource that contains
+   * additional Ingress configuration, including the name of the Ingress controller.
+   */
+  readonly className?: string;
 }
 
 /**
@@ -90,6 +98,7 @@ export class Ingress extends base.Resource {
       metadata: props.metadata,
       spec: {
         defaultBackend: Lazy.any({ produce: () => this._defaultBackend?._toKube() }),
+        ingressClassName: props.className,
         rules: Lazy.any({ produce: () => this.synthRules() }),
         tls: Lazy.any({ produce: () => this.tlsConfig() }),
       },


Test patch:
diff --git a/test/ingress.test.ts b/test/ingress.test.ts
index 8f79dc36..65b63503 100644
--- a/test/ingress.test.ts
+++ b/test/ingress.test.ts
@@ -12,6 +12,36 @@ test('defaultChild', () => {
 
 });
 
+test('IngressClassName can be set', () => {
+  // GIVEN
+  const chart = Testing.chart();
+  const service = new Service(chart, 'my-service', { ports: [{ port: 80 }] } );
+
+  // WHEN
+  new Ingress(chart, 'my-ingress', {
+    defaultBackend: IngressBackend.fromService(service),
+    className: 'myIngressClassName',
+  });
+
+  // THEN
+  expect(Testing.synth(chart).filter(x => x.kind === 'Ingress')).toStrictEqual([
+    {
+      apiVersion: 'networking.k8s.io/v1',
+      kind: 'Ingress',
+      metadata: { name: 'test-my-ingress-c8135042' },
+      spec: {
+        defaultBackend: {
+          service: {
+            name: 'test-my-service-c8493104',
+            port: { number: 80 },
+          },
+        },
+        ingressClassName: 'myIngressClassName',
+      },
+    },
+  ]);
+});
+
 describe('IngressBackend', () => {
   describe('fromService', () => {
     test('if the service exposes a port, it will be used by the ingress', () => {