From a5a1dd01f1af02991fe24dbd9d265b1d90d8a544 Mon Sep 17 00:00:00 2001 From: Wayne Starr Date: Fri, 19 Apr 2024 14:34:53 -0600 Subject: [PATCH 01/18] feat: add a keycloak service entry for internal cluster traffic --- .../chart/templates/secret-admin-password.yaml | 2 +- .../chart/templates/secret-postgresql.yaml | 2 +- src/keycloak/chart/templates/service-entry.yaml | 16 ++++++++++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 src/keycloak/chart/templates/service-entry.yaml diff --git a/src/keycloak/chart/templates/secret-admin-password.yaml b/src/keycloak/chart/templates/secret-admin-password.yaml index 04e42d5a8..59307eb32 100644 --- a/src/keycloak/chart/templates/secret-admin-password.yaml +++ b/src/keycloak/chart/templates/secret-admin-password.yaml @@ -13,7 +13,7 @@ apiVersion: v1 kind: Secret metadata: name: {{ $secretName }} - namespace: {{ .Release.Namespace }} + namespace: {{ .Release.Namespace }} labels: {{- include "keycloak.labels" . | nindent 4 }} type: Opaque diff --git a/src/keycloak/chart/templates/secret-postgresql.yaml b/src/keycloak/chart/templates/secret-postgresql.yaml index 771ad3a16..e0af8d089 100644 --- a/src/keycloak/chart/templates/secret-postgresql.yaml +++ b/src/keycloak/chart/templates/secret-postgresql.yaml @@ -3,7 +3,7 @@ apiVersion: v1 kind: Secret metadata: name: {{ include "keycloak.fullname" . }}-postgresql - namespace: {{ .Release.Namespace }} + namespace: {{ .Release.Namespace }} labels: {{- include "keycloak.labels" . | nindent 4 }} type: Opaque diff --git a/src/keycloak/chart/templates/service-entry.yaml b/src/keycloak/chart/templates/service-entry.yaml new file mode 100644 index 000000000..d0066eef3 --- /dev/null +++ b/src/keycloak/chart/templates/service-entry.yaml @@ -0,0 +1,16 @@ +apiVersion: networking.istio.io/v1beta1 +kind: ServiceEntry +metadata: + name: {{ include "keycloak.fullname" . }}-internal-service-entry + namespace: {{ .Release.Namespace }} +spec: + hosts: + - sso.{{ .Values.domain }} + location: MESH_INTERNAL + ports: + - number: 443 + name: https + protocol: HTTPS + resolution: DNS + endpoints: + - address: tenant-ingressgateway.istio-tenant-gateway.svc.cluster.local From 7b21a9460938d0de5ff7d3b07fc4775ea5f523c5 Mon Sep 17 00:00:00 2001 From: Wayne Starr Date: Fri, 19 Apr 2024 14:55:29 -0600 Subject: [PATCH 02/18] add cap check --- .../templates/{service-entry.yaml => istio-service-entry.yaml} | 2 ++ 1 file changed, 2 insertions(+) rename src/keycloak/chart/templates/{service-entry.yaml => istio-service-entry.yaml} (83%) diff --git a/src/keycloak/chart/templates/service-entry.yaml b/src/keycloak/chart/templates/istio-service-entry.yaml similarity index 83% rename from src/keycloak/chart/templates/service-entry.yaml rename to src/keycloak/chart/templates/istio-service-entry.yaml index d0066eef3..efcde2fa9 100644 --- a/src/keycloak/chart/templates/service-entry.yaml +++ b/src/keycloak/chart/templates/istio-service-entry.yaml @@ -1,3 +1,4 @@ +{{- if .Capabilities.APIVersions.Has "networking.istio.io/v1beta1" }} apiVersion: networking.istio.io/v1beta1 kind: ServiceEntry metadata: @@ -14,3 +15,4 @@ spec: resolution: DNS endpoints: - address: tenant-ingressgateway.istio-tenant-gateway.svc.cluster.local +{{- end }} From fde7b14a949f15a23e67e3894062e9aa96d2787e Mon Sep 17 00:00:00 2001 From: Wayne Starr Date: Mon, 6 May 2024 14:52:42 -0600 Subject: [PATCH 03/18] this should work :fingers-crossed: --- .../controllers/istio/service-entry.ts | 107 +++++++++++++ .../controllers/istio/virtual-service.ts | 16 +- .../generated/istio/serviceentry-v1beta1.ts | 141 ++++++++++++++++++ src/pepr/operator/crd/index.ts | 15 +- .../reconcilers/package-reconciler.ts | 4 + 5 files changed, 274 insertions(+), 9 deletions(-) create mode 100644 src/pepr/operator/controllers/istio/service-entry.ts create mode 100644 src/pepr/operator/crd/generated/istio/serviceentry-v1beta1.ts diff --git a/src/pepr/operator/controllers/istio/service-entry.ts b/src/pepr/operator/controllers/istio/service-entry.ts new file mode 100644 index 000000000..53b079e03 --- /dev/null +++ b/src/pepr/operator/controllers/istio/service-entry.ts @@ -0,0 +1,107 @@ +import { K8s, Log } from "pepr"; + +import { UDSConfig } from "../../../config"; +import { Expose, Gateway, IstioServiceEntry, IstioLocation, IstioResolution, IstioPort, IstioEndpoint, UDSPackage } from "../../crd"; +import { getOwnerRef, sanitizeResourceName } from "../utils"; + +/** + * Creates a ServiceEntry for each exposed service in the package + * + * @param pkg + * @param namespace + */ +export async function serviceEntry(pkg: UDSPackage, namespace: string) { + const pkgName = pkg.metadata!.name!; + const generation = (pkg.metadata?.generation ?? 0).toString(); + + // Get the list of exposed services + const exposeList = pkg.spec?.network?.expose ?? []; + + // Track which ServiceEntries we've created + const serviceEntryNames: Map = new Map(); + + // Iterate over each exposed service + for (const expose of exposeList) { + const { gateway = Gateway.Tenant, host } = expose; + + const name = generateSEName(pkg, expose); + + // If we have already made a ServiceEntry with this name, skip (i.e. if advancedHTTP was used) + if (serviceEntryNames.get(name)) { + continue + } + + // For the admin gateway, we need to add the path prefix + const domain = (gateway === Gateway.Admin ? "admin." : "") + UDSConfig.domain; + + // Append the domain to the host + const fqdn = `${host}.${domain}`; + + const serviceEntryPort: IstioPort = { + name: "https", + number: 443, + protocol: "HTTPS", + } + + const serviceEntryEndpoint: IstioEndpoint = { + // Map the gateway (admin, passthrough or tenant) to the ServiceEntry + address: `${gateway}-ingressgateway.istio-${gateway}-gateway.svc.cluster.local` + } + + const payload: IstioServiceEntry = { + metadata: { + name, + namespace, + labels: { + "uds/package": pkgName, + "uds/generation": generation, + }, + // Use the CR as the owner ref for each ServiceEntry + ownerReferences: getOwnerRef(pkg), + }, + spec: { + // Append the UDS Domain to the host + hosts: [fqdn], + location: IstioLocation.MeshInternal, + resolution: IstioResolution.DNS, + ports: [serviceEntryPort], + endpoints: [serviceEntryEndpoint], + }, + }; + + + Log.debug(payload, `Applying ServiceEntry ${payload.metadata?.name}`); + + // Apply the ServiceEntry and force overwrite any existing policy + await K8s(IstioServiceEntry).Apply(payload, { force: true }); + + serviceEntryNames.set(name, true) + } + + // Get all related ServiceEntries in the namespace + const serviceEntries = await K8s(IstioServiceEntry) + .InNamespace(namespace) + .WithLabel("uds/package", pkgName) + .Get(); + + // Find any orphaned ServiceEntries (not matching the current generation) + const orphanedSE = serviceEntries.items.filter( + vs => vs.metadata?.labels?.["uds/generation"] !== generation, + ); + + // Delete any orphaned ServiceEntries + for (const vs of orphanedSE) { + Log.debug(vs, `Deleting orphaned ServiceEntry ${vs.metadata!.name}`); + await K8s(IstioServiceEntry).Delete(vs); + } +} + +export function generateSEName(pkg: UDSPackage, expose: Expose) { + const { gateway = Gateway.Tenant, host, port, service, description } = expose; + + // Ensure the resource name is valid + const nameSuffix = description || `${host}-${port}-${service}`; + const name = sanitizeResourceName(`${pkg.metadata!.name}-${gateway}-${nameSuffix}`); + + return name; +} diff --git a/src/pepr/operator/controllers/istio/virtual-service.ts b/src/pepr/operator/controllers/istio/virtual-service.ts index 913625699..7a619868b 100644 --- a/src/pepr/operator/controllers/istio/virtual-service.ts +++ b/src/pepr/operator/controllers/istio/virtual-service.ts @@ -1,7 +1,7 @@ import { K8s, Log } from "pepr"; import { UDSConfig } from "../../../config"; -import { Expose, Gateway, Istio, UDSPackage } from "../../crd"; +import { Expose, Gateway, IstioVirtualService, IstioHTTP, IstioHTTPRoute, UDSPackage } from "../../crd"; import { getOwnerRef, sanitizeResourceName } from "../utils"; /** @@ -18,7 +18,7 @@ export async function virtualService(pkg: UDSPackage, namespace: string) { const exposeList = pkg.spec?.network?.expose ?? []; // Create a list of generated VirtualServices - const payloads: Istio.VirtualService[] = []; + const payloads: IstioVirtualService[] = []; // Iterate over each exposed service for (const expose of exposeList) { @@ -32,10 +32,10 @@ export async function virtualService(pkg: UDSPackage, namespace: string) { // Append the domain to the host const fqdn = `${host}.${domain}`; - const http: Istio.HTTP = { ...advancedHTTP }; + const http: IstioHTTP = { ...advancedHTTP }; // Create the route to the service - const route: Istio.HTTPRoute[] = [ + const route: IstioHTTPRoute[] = [ { destination: { // Use the service name as the host @@ -51,7 +51,7 @@ export async function virtualService(pkg: UDSPackage, namespace: string) { http.route = route; } - const payload: Istio.VirtualService = { + const payload: IstioVirtualService = { metadata: { name, namespace, @@ -85,13 +85,13 @@ export async function virtualService(pkg: UDSPackage, namespace: string) { Log.debug(payload, `Applying VirtualService ${name}`); // Apply the VirtualService and force overwrite any existing policy - await K8s(Istio.VirtualService).Apply(payload, { force: true }); + await K8s(IstioVirtualService).Apply(payload, { force: true }); payloads.push(payload); } // Get all related VirtualServices in the namespace - const virtualServices = await K8s(Istio.VirtualService) + const virtualServices = await K8s(IstioVirtualService) .InNamespace(namespace) .WithLabel("uds/package", pkgName) .Get(); @@ -104,7 +104,7 @@ export async function virtualService(pkg: UDSPackage, namespace: string) { // Delete any orphaned VirtualServices for (const vs of orphanedVS) { Log.debug(vs, `Deleting orphaned VirtualService ${vs.metadata!.name}`); - await K8s(Istio.VirtualService).Delete(vs); + await K8s(IstioVirtualService).Delete(vs); } // Return the list of unique hostnames diff --git a/src/pepr/operator/crd/generated/istio/serviceentry-v1beta1.ts b/src/pepr/operator/crd/generated/istio/serviceentry-v1beta1.ts new file mode 100644 index 000000000..854633580 --- /dev/null +++ b/src/pepr/operator/crd/generated/istio/serviceentry-v1beta1.ts @@ -0,0 +1,141 @@ +// This file is auto-generated by kubernetes-fluent-client, do not edit manually + +import { GenericKind, RegisterKind } from "kubernetes-fluent-client"; + +export class ServiceEntry extends GenericKind { + /** + * Configuration affecting service registry. See more details at: + * https://istio.io/docs/reference/config/networking/service-entry.html + */ + spec?: Spec; + status?: { [key: string]: any }; +} + +/** + * Configuration affecting service registry. See more details at: + * https://istio.io/docs/reference/config/networking/service-entry.html + */ +export interface Spec { + /** + * The virtual IP addresses associated with the service. + */ + addresses?: string[]; + /** + * One or more endpoints associated with the service. + */ + endpoints?: Endpoint[]; + /** + * A list of namespaces to which this service is exported. + */ + exportTo?: string[]; + /** + * The hosts associated with the ServiceEntry. + */ + hosts: string[]; + /** + * Specify whether the service should be considered external to the mesh or part of the mesh. + */ + location?: Location; + /** + * The ports associated with the external service. + */ + ports?: Port[]; + /** + * Service resolution mode for the hosts. + */ + resolution?: Resolution; + /** + * If specified, the proxy will verify that the server certificate's subject alternate name + * matches one of the specified values. + */ + subjectAltNames?: string[]; + /** + * Applicable only for MESH_INTERNAL services. + */ + workloadSelector?: WorkloadSelector; +} + +export interface Endpoint { + /** + * Address associated with the network endpoint without the port. + */ + address?: string; + /** + * One or more labels associated with the endpoint. + */ + labels?: { [key: string]: string }; + /** + * The locality associated with the endpoint. + */ + locality?: string; + /** + * Network enables Istio to group endpoints resident in the same L3 domain/network. + */ + network?: string; + /** + * Set of ports associated with the endpoint. + */ + ports?: { [key: string]: number }; + /** + * The service account associated with the workload if a sidecar is present in the workload. + */ + serviceAccount?: string; + /** + * The load balancing weight associated with the endpoint. + */ + weight?: number; +} + +/** + * Specify whether the service should be considered external to the mesh or part of the mesh. + */ +export enum Location { + MeshExternal = "MESH_EXTERNAL", + MeshInternal = "MESH_INTERNAL", +} + +export interface Port { + /** + * Label assigned to the port. + */ + name: string; + /** + * A valid non-negative integer port number. + */ + number: number; + /** + * The protocol exposed on the port. + */ + protocol?: string; + /** + * The port number on the endpoint where the traffic will be received. + */ + targetPort?: number; +} + +/** + * Service resolution mode for the hosts. + */ +export enum Resolution { + DNS = "DNS", + DNSRoundRobin = "DNS_ROUND_ROBIN", + None = "NONE", + Static = "STATIC", +} + +/** + * Applicable only for MESH_INTERNAL services. + */ +export interface WorkloadSelector { + /** + * One or more labels that indicate a specific set of pods/VMs on which the configuration + * should be applied. + */ + labels?: { [key: string]: string }; +} + +RegisterKind(ServiceEntry, { + group: "networking.istio.io", + version: "v1beta1", + kind: "ServiceEntry", +}); \ No newline at end of file diff --git a/src/pepr/operator/crd/index.ts b/src/pepr/operator/crd/index.ts index 11982ead1..1968931b5 100644 --- a/src/pepr/operator/crd/index.ts +++ b/src/pepr/operator/crd/index.ts @@ -20,5 +20,18 @@ export { Exemption as UDSExemption, } from "./generated/exemption-v1alpha1"; -export * as Istio from "./generated/istio/virtualservice-v1beta1"; +export { + VirtualService as IstioVirtualService, + HTTPRoute as IstioHTTPRoute, + HTTP as IstioHTTP, +} from "./generated/istio/virtualservice-v1beta1"; + +export { + ServiceEntry as IstioServiceEntry, + Location as IstioLocation, + Resolution as IstioResolution, + Endpoint as IstioEndpoint, + Port as IstioPort, +} from "./generated/istio/serviceentry-v1beta1"; + export * as Prometheus from "./generated/prometheus/servicemonitor-v1"; diff --git a/src/pepr/operator/reconcilers/package-reconciler.ts b/src/pepr/operator/reconcilers/package-reconciler.ts index 2c500301d..a111afa1e 100644 --- a/src/pepr/operator/reconcilers/package-reconciler.ts +++ b/src/pepr/operator/reconcilers/package-reconciler.ts @@ -4,6 +4,7 @@ import { handleFailure, shouldSkip, updateStatus } from "."; import { UDSConfig } from "../../config"; import { enableInjection } from "../controllers/istio/injection"; import { virtualService } from "../controllers/istio/virtual-service"; +import { serviceEntry } from "../controllers/istio/service-entry"; import { keycloak } from "../controllers/keycloak/client-sync"; import { serviceMonitor } from "../controllers/monitoring/service-monitor"; import { networkPolicies } from "../controllers/network/policies"; @@ -43,6 +44,9 @@ export async function packageReconciler(pkg: UDSPackage) { // Create the VirtualService for each exposed service endpoints = await virtualService(pkg, namespace!); + // Create the ServiceEntry for each exposed service + await serviceEntry(pkg, namespace!); + // Only configure the ServiceMonitors if not running in single test mode let monitors: string[] = []; if (!UDSConfig.isSingleTest) { From 759b141b3d52bd35b1a4a04280c9aa8a5b5b499a Mon Sep 17 00:00:00 2001 From: Wayne Starr Date: Mon, 6 May 2024 14:53:36 -0600 Subject: [PATCH 04/18] remove service entry --- .../chart/templates/istio-service-entry.yaml | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 src/keycloak/chart/templates/istio-service-entry.yaml diff --git a/src/keycloak/chart/templates/istio-service-entry.yaml b/src/keycloak/chart/templates/istio-service-entry.yaml deleted file mode 100644 index efcde2fa9..000000000 --- a/src/keycloak/chart/templates/istio-service-entry.yaml +++ /dev/null @@ -1,18 +0,0 @@ -{{- if .Capabilities.APIVersions.Has "networking.istio.io/v1beta1" }} -apiVersion: networking.istio.io/v1beta1 -kind: ServiceEntry -metadata: - name: {{ include "keycloak.fullname" . }}-internal-service-entry - namespace: {{ .Release.Namespace }} -spec: - hosts: - - sso.{{ .Values.domain }} - location: MESH_INTERNAL - ports: - - number: 443 - name: https - protocol: HTTPS - resolution: DNS - endpoints: - - address: tenant-ingressgateway.istio-tenant-gateway.svc.cluster.local -{{- end }} From 2ebbc864f237c6d3f093e1688101d63cfb27eaa1 Mon Sep 17 00:00:00 2001 From: Wayne Starr Date: Mon, 6 May 2024 15:28:06 -0600 Subject: [PATCH 05/18] fix linting --- .eslintrc.json | 10 +- .../controllers/istio/service-entry.ts | 22 +- .../controllers/istio/virtual-service.ts | 9 +- .../generated/istio/serviceentry-v1beta1.ts | 198 +++++++++--------- 4 files changed, 131 insertions(+), 108 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 85f4b187a..dcbc9ca6c 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -14,5 +14,13 @@ "root": true, "rules": { "@typescript-eslint/no-floating-promises": ["error"] - } + }, + "overrides": [ + { + "files": [ "src/pepr/operator/crd/generated/**/*.ts", "src/pepr/operator/crd/generated/*.ts" ], + "rules": { + "@typescript-eslint/no-explicit-any": "off" + } + } + ] } diff --git a/src/pepr/operator/controllers/istio/service-entry.ts b/src/pepr/operator/controllers/istio/service-entry.ts index 53b079e03..d613bc8e7 100644 --- a/src/pepr/operator/controllers/istio/service-entry.ts +++ b/src/pepr/operator/controllers/istio/service-entry.ts @@ -1,7 +1,16 @@ import { K8s, Log } from "pepr"; import { UDSConfig } from "../../../config"; -import { Expose, Gateway, IstioServiceEntry, IstioLocation, IstioResolution, IstioPort, IstioEndpoint, UDSPackage } from "../../crd"; +import { + Expose, + Gateway, + IstioServiceEntry, + IstioLocation, + IstioResolution, + IstioPort, + IstioEndpoint, + UDSPackage, +} from "../../crd"; import { getOwnerRef, sanitizeResourceName } from "../utils"; /** @@ -28,7 +37,7 @@ export async function serviceEntry(pkg: UDSPackage, namespace: string) { // If we have already made a ServiceEntry with this name, skip (i.e. if advancedHTTP was used) if (serviceEntryNames.get(name)) { - continue + continue; } // For the admin gateway, we need to add the path prefix @@ -41,12 +50,12 @@ export async function serviceEntry(pkg: UDSPackage, namespace: string) { name: "https", number: 443, protocol: "HTTPS", - } + }; const serviceEntryEndpoint: IstioEndpoint = { // Map the gateway (admin, passthrough or tenant) to the ServiceEntry - address: `${gateway}-ingressgateway.istio-${gateway}-gateway.svc.cluster.local` - } + address: `${gateway}-ingressgateway.istio-${gateway}-gateway.svc.cluster.local`, + }; const payload: IstioServiceEntry = { metadata: { @@ -69,13 +78,12 @@ export async function serviceEntry(pkg: UDSPackage, namespace: string) { }, }; - Log.debug(payload, `Applying ServiceEntry ${payload.metadata?.name}`); // Apply the ServiceEntry and force overwrite any existing policy await K8s(IstioServiceEntry).Apply(payload, { force: true }); - serviceEntryNames.set(name, true) + serviceEntryNames.set(name, true); } // Get all related ServiceEntries in the namespace diff --git a/src/pepr/operator/controllers/istio/virtual-service.ts b/src/pepr/operator/controllers/istio/virtual-service.ts index 7a619868b..9bcfd9c3e 100644 --- a/src/pepr/operator/controllers/istio/virtual-service.ts +++ b/src/pepr/operator/controllers/istio/virtual-service.ts @@ -1,7 +1,14 @@ import { K8s, Log } from "pepr"; import { UDSConfig } from "../../../config"; -import { Expose, Gateway, IstioVirtualService, IstioHTTP, IstioHTTPRoute, UDSPackage } from "../../crd"; +import { + Expose, + Gateway, + IstioVirtualService, + IstioHTTP, + IstioHTTPRoute, + UDSPackage, +} from "../../crd"; import { getOwnerRef, sanitizeResourceName } from "../utils"; /** diff --git a/src/pepr/operator/crd/generated/istio/serviceentry-v1beta1.ts b/src/pepr/operator/crd/generated/istio/serviceentry-v1beta1.ts index 854633580..136ab288a 100644 --- a/src/pepr/operator/crd/generated/istio/serviceentry-v1beta1.ts +++ b/src/pepr/operator/crd/generated/istio/serviceentry-v1beta1.ts @@ -3,12 +3,12 @@ import { GenericKind, RegisterKind } from "kubernetes-fluent-client"; export class ServiceEntry extends GenericKind { - /** - * Configuration affecting service registry. See more details at: - * https://istio.io/docs/reference/config/networking/service-entry.html - */ - spec?: Spec; - status?: { [key: string]: any }; + /** + * Configuration affecting service registry. See more details at: + * https://istio.io/docs/reference/config/networking/service-entry.html + */ + spec?: Spec; + status?: { [key: string]: any }; } /** @@ -16,126 +16,126 @@ export class ServiceEntry extends GenericKind { * https://istio.io/docs/reference/config/networking/service-entry.html */ export interface Spec { - /** - * The virtual IP addresses associated with the service. - */ - addresses?: string[]; - /** - * One or more endpoints associated with the service. - */ - endpoints?: Endpoint[]; - /** - * A list of namespaces to which this service is exported. - */ - exportTo?: string[]; - /** - * The hosts associated with the ServiceEntry. - */ - hosts: string[]; - /** - * Specify whether the service should be considered external to the mesh or part of the mesh. - */ - location?: Location; - /** - * The ports associated with the external service. - */ - ports?: Port[]; - /** - * Service resolution mode for the hosts. - */ - resolution?: Resolution; - /** - * If specified, the proxy will verify that the server certificate's subject alternate name - * matches one of the specified values. - */ - subjectAltNames?: string[]; - /** - * Applicable only for MESH_INTERNAL services. - */ - workloadSelector?: WorkloadSelector; + /** + * The virtual IP addresses associated with the service. + */ + addresses?: string[]; + /** + * One or more endpoints associated with the service. + */ + endpoints?: Endpoint[]; + /** + * A list of namespaces to which this service is exported. + */ + exportTo?: string[]; + /** + * The hosts associated with the ServiceEntry. + */ + hosts: string[]; + /** + * Specify whether the service should be considered external to the mesh or part of the mesh. + */ + location?: Location; + /** + * The ports associated with the external service. + */ + ports?: Port[]; + /** + * Service resolution mode for the hosts. + */ + resolution?: Resolution; + /** + * If specified, the proxy will verify that the server certificate's subject alternate name + * matches one of the specified values. + */ + subjectAltNames?: string[]; + /** + * Applicable only for MESH_INTERNAL services. + */ + workloadSelector?: WorkloadSelector; } export interface Endpoint { - /** - * Address associated with the network endpoint without the port. - */ - address?: string; - /** - * One or more labels associated with the endpoint. - */ - labels?: { [key: string]: string }; - /** - * The locality associated with the endpoint. - */ - locality?: string; - /** - * Network enables Istio to group endpoints resident in the same L3 domain/network. - */ - network?: string; - /** - * Set of ports associated with the endpoint. - */ - ports?: { [key: string]: number }; - /** - * The service account associated with the workload if a sidecar is present in the workload. - */ - serviceAccount?: string; - /** - * The load balancing weight associated with the endpoint. - */ - weight?: number; + /** + * Address associated with the network endpoint without the port. + */ + address?: string; + /** + * One or more labels associated with the endpoint. + */ + labels?: { [key: string]: string }; + /** + * The locality associated with the endpoint. + */ + locality?: string; + /** + * Network enables Istio to group endpoints resident in the same L3 domain/network. + */ + network?: string; + /** + * Set of ports associated with the endpoint. + */ + ports?: { [key: string]: number }; + /** + * The service account associated with the workload if a sidecar is present in the workload. + */ + serviceAccount?: string; + /** + * The load balancing weight associated with the endpoint. + */ + weight?: number; } /** * Specify whether the service should be considered external to the mesh or part of the mesh. */ export enum Location { - MeshExternal = "MESH_EXTERNAL", - MeshInternal = "MESH_INTERNAL", + MeshExternal = "MESH_EXTERNAL", + MeshInternal = "MESH_INTERNAL", } export interface Port { - /** - * Label assigned to the port. - */ - name: string; - /** - * A valid non-negative integer port number. - */ - number: number; - /** - * The protocol exposed on the port. - */ - protocol?: string; - /** - * The port number on the endpoint where the traffic will be received. - */ - targetPort?: number; + /** + * Label assigned to the port. + */ + name: string; + /** + * A valid non-negative integer port number. + */ + number: number; + /** + * The protocol exposed on the port. + */ + protocol?: string; + /** + * The port number on the endpoint where the traffic will be received. + */ + targetPort?: number; } /** * Service resolution mode for the hosts. */ export enum Resolution { - DNS = "DNS", - DNSRoundRobin = "DNS_ROUND_ROBIN", - None = "NONE", - Static = "STATIC", + DNS = "DNS", + DNSRoundRobin = "DNS_ROUND_ROBIN", + None = "NONE", + Static = "STATIC", } /** * Applicable only for MESH_INTERNAL services. */ export interface WorkloadSelector { - /** - * One or more labels that indicate a specific set of pods/VMs on which the configuration - * should be applied. - */ - labels?: { [key: string]: string }; + /** + * One or more labels that indicate a specific set of pods/VMs on which the configuration + * should be applied. + */ + labels?: { [key: string]: string }; } RegisterKind(ServiceEntry, { group: "networking.istio.io", version: "v1beta1", kind: "ServiceEntry", -}); \ No newline at end of file +}); From 948c259b941e83ed8780681055a11efa052962fc Mon Sep 17 00:00:00 2001 From: Wayne Starr Date: Tue, 7 May 2024 12:30:07 -0600 Subject: [PATCH 06/18] fix plural and names --- src/pepr/operator/controllers/istio/service-entry.ts | 5 ++--- .../operator/crd/generated/istio/serviceentry-v1beta1.ts | 1 + 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pepr/operator/controllers/istio/service-entry.ts b/src/pepr/operator/controllers/istio/service-entry.ts index d613bc8e7..d204a53b9 100644 --- a/src/pepr/operator/controllers/istio/service-entry.ts +++ b/src/pepr/operator/controllers/istio/service-entry.ts @@ -105,11 +105,10 @@ export async function serviceEntry(pkg: UDSPackage, namespace: string) { } export function generateSEName(pkg: UDSPackage, expose: Expose) { - const { gateway = Gateway.Tenant, host, port, service, description } = expose; + const { gateway = Gateway.Tenant, host } = expose; // Ensure the resource name is valid - const nameSuffix = description || `${host}-${port}-${service}`; - const name = sanitizeResourceName(`${pkg.metadata!.name}-${gateway}-${nameSuffix}`); + const name = sanitizeResourceName(`${pkg.metadata!.name}-${gateway}-${host}`); return name; } diff --git a/src/pepr/operator/crd/generated/istio/serviceentry-v1beta1.ts b/src/pepr/operator/crd/generated/istio/serviceentry-v1beta1.ts index 136ab288a..3ab93402f 100644 --- a/src/pepr/operator/crd/generated/istio/serviceentry-v1beta1.ts +++ b/src/pepr/operator/crd/generated/istio/serviceentry-v1beta1.ts @@ -138,4 +138,5 @@ RegisterKind(ServiceEntry, { group: "networking.istio.io", version: "v1beta1", kind: "ServiceEntry", + plural: "serviceentries", }); From 67c9cd1b8631cc5ad64153c573ec6904a612ad55 Mon Sep 17 00:00:00 2001 From: Wayne Starr Date: Tue, 7 May 2024 15:24:09 -0600 Subject: [PATCH 07/18] fix dev --- tasks.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tasks.yaml b/tasks.yaml index fd5f367b7..0899f5f03 100644 --- a/tasks.yaml +++ b/tasks.yaml @@ -30,6 +30,9 @@ tasks: - description: "Deploy the Istio source package with Zarf Dev" cmd: "uds zarf dev deploy src/istio --flavor ${FLAVOR}" + - description: "Deploy the Prometheus-Stack source package with Zarf Dev to only install the CRDs" + cmd: "uds zarf dev deploy src/prometheus-stack --flavor crds-only" + - description: "Dev instructions" cmd: | echo "Next steps:" From 7d89156962254978a316c212bd4e3f38c6d0bf07 Mon Sep 17 00:00:00 2001 From: Wayne Starr Date: Tue, 7 May 2024 16:25:45 -0600 Subject: [PATCH 08/18] remove vs --- src/pepr/operator/controllers/istio/service-entry.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pepr/operator/controllers/istio/service-entry.ts b/src/pepr/operator/controllers/istio/service-entry.ts index d204a53b9..73956d65a 100644 --- a/src/pepr/operator/controllers/istio/service-entry.ts +++ b/src/pepr/operator/controllers/istio/service-entry.ts @@ -94,13 +94,13 @@ export async function serviceEntry(pkg: UDSPackage, namespace: string) { // Find any orphaned ServiceEntries (not matching the current generation) const orphanedSE = serviceEntries.items.filter( - vs => vs.metadata?.labels?.["uds/generation"] !== generation, + se => se.metadata?.labels?.["uds/generation"] !== generation, ); // Delete any orphaned ServiceEntries - for (const vs of orphanedSE) { - Log.debug(vs, `Deleting orphaned ServiceEntry ${vs.metadata!.name}`); - await K8s(IstioServiceEntry).Delete(vs); + for (const se of orphanedSE) { + Log.debug(se, `Deleting orphaned ServiceEntry ${se.metadata!.name}`); + await K8s(IstioServiceEntry).Delete(se); } } From 21729e066b9b6f4b32e7228056e6ac4ccbaeb83b Mon Sep 17 00:00:00 2001 From: Wayne Starr Date: Thu, 9 May 2024 12:36:14 -0600 Subject: [PATCH 09/18] fix the feedback - refactor the codes --- .../controllers/istio/istio-resources.ts | 92 ++++++++++ .../controllers/istio/service-entry.ts | 129 +++++-------- .../controllers/istio/virtual-service.ts | 172 +++++++----------- .../monitoring/service-monitor.spec.ts | 12 +- .../controllers/monitoring/service-monitor.ts | 14 +- .../reconcilers/package-reconciler.ts | 10 +- 6 files changed, 222 insertions(+), 207 deletions(-) create mode 100644 src/pepr/operator/controllers/istio/istio-resources.ts diff --git a/src/pepr/operator/controllers/istio/istio-resources.ts b/src/pepr/operator/controllers/istio/istio-resources.ts new file mode 100644 index 000000000..c21876b16 --- /dev/null +++ b/src/pepr/operator/controllers/istio/istio-resources.ts @@ -0,0 +1,92 @@ +import { K8s, Log } from "pepr"; + +import { IstioVirtualService, IstioServiceEntry, UDSPackage } from "../../crd"; +import { getOwnerRef } from "../utils"; +import { generateVirtualService } from "./virtual-service"; +import { generateServiceEntry } from "./service-entry"; + +/** + * Creates a VirtualService for each exposed service in the package + * + * @param pkg + * @param namespace + */ +export async function istioResources(pkg: UDSPackage, namespace: string) { + const pkgName = pkg.metadata!.name!; + const generation = (pkg.metadata?.generation ?? 0).toString(); + const ownerRefs = getOwnerRef(pkg); + + // Get the list of exposed services + const exposeList = pkg.spec?.network?.expose ?? []; + + // Create a Set of processed hosts (to maintain uniqueness) + const hosts = new Set(); + + // Track which ServiceEntries we've created + const serviceEntryNames: Map = new Map(); + + // Iterate over each exposed service + for (const expose of exposeList) { + // Generate a VirtualService for this `expose` entry + const vsPayload = generateVirtualService(expose, namespace, pkgName, generation, ownerRefs); + + Log.debug(vsPayload, `Applying VirtualService ${vsPayload.metadata?.name}`); + + // Apply the VirtualService and force overwrite any existing policy + await K8s(IstioVirtualService).Apply(vsPayload, { force: true }); + + vsPayload.spec!.hosts!.forEach(h => hosts.add(h)); + + // Generate a ServiceEntry for this `expose` entry + const sePayload = generateServiceEntry(expose, namespace, pkgName, generation, ownerRefs); + + // If we have already made a ServiceEntry with this name, skip (i.e. if advancedHTTP was used) + if (serviceEntryNames.get(sePayload.metadata!.name!)) { + continue; + } + + Log.debug(sePayload, `Applying ServiceEntry ${sePayload.metadata?.name}`); + + // Apply the ServiceEntry and force overwrite any existing policy + await K8s(IstioServiceEntry).Apply(sePayload, { force: true }); + + serviceEntryNames.set(sePayload.metadata!.name!, true); + } + + // Get all related VirtualServices in the namespace + const virtualServices = await K8s(IstioVirtualService) + .InNamespace(namespace) + .WithLabel("uds/package", pkgName) + .Get(); + + // Find any orphaned VirtualServices (not matching the current generation) + const orphanedVS = virtualServices.items.filter( + vs => vs.metadata?.labels?.["uds/generation"] !== generation, + ); + + // Delete any orphaned VirtualServices + for (const vs of orphanedVS) { + Log.debug(vs, `Deleting orphaned VirtualService ${vs.metadata!.name}`); + await K8s(IstioVirtualService).Delete(vs); + } + + // Get all related ServiceEntries in the namespace + const serviceEntries = await K8s(IstioServiceEntry) + .InNamespace(namespace) + .WithLabel("uds/package", pkgName) + .Get(); + + // Find any orphaned ServiceEntries (not matching the current generation) + const orphanedSE = serviceEntries.items.filter( + se => se.metadata?.labels?.["uds/generation"] !== generation, + ); + + // Delete any orphaned ServiceEntries + for (const se of orphanedSE) { + Log.debug(se, `Deleting orphaned ServiceEntry ${se.metadata!.name}`); + await K8s(IstioServiceEntry).Delete(se); + } + + // Return the list of unique hostnames + return [...hosts]; +} diff --git a/src/pepr/operator/controllers/istio/service-entry.ts b/src/pepr/operator/controllers/istio/service-entry.ts index 73956d65a..b326a99d0 100644 --- a/src/pepr/operator/controllers/istio/service-entry.ts +++ b/src/pepr/operator/controllers/istio/service-entry.ts @@ -1,6 +1,5 @@ -import { K8s, Log } from "pepr"; - import { UDSConfig } from "../../../config"; +import { V1OwnerReference } from "@kubernetes/client-node"; import { Expose, Gateway, @@ -9,9 +8,8 @@ import { IstioResolution, IstioPort, IstioEndpoint, - UDSPackage, } from "../../crd"; -import { getOwnerRef, sanitizeResourceName } from "../utils"; +import { sanitizeResourceName } from "../utils"; /** * Creates a ServiceEntry for each exposed service in the package @@ -19,96 +17,63 @@ import { getOwnerRef, sanitizeResourceName } from "../utils"; * @param pkg * @param namespace */ -export async function serviceEntry(pkg: UDSPackage, namespace: string) { - const pkgName = pkg.metadata!.name!; - const generation = (pkg.metadata?.generation ?? 0).toString(); - - // Get the list of exposed services - const exposeList = pkg.spec?.network?.expose ?? []; - - // Track which ServiceEntries we've created - const serviceEntryNames: Map = new Map(); - - // Iterate over each exposed service - for (const expose of exposeList) { - const { gateway = Gateway.Tenant, host } = expose; - - const name = generateSEName(pkg, expose); +export function generateServiceEntry( + expose: Expose, + namespace: string, + pkgName: string, + generation: string, + ownerRefs: V1OwnerReference[], +) { + const { gateway = Gateway.Tenant, host } = expose; - // If we have already made a ServiceEntry with this name, skip (i.e. if advancedHTTP was used) - if (serviceEntryNames.get(name)) { - continue; - } + const name = generateSEName(pkgName, expose); - // For the admin gateway, we need to add the path prefix - const domain = (gateway === Gateway.Admin ? "admin." : "") + UDSConfig.domain; + // For the admin gateway, we need to add the path prefix + const domain = (gateway === Gateway.Admin ? "admin." : "") + UDSConfig.domain; - // Append the domain to the host - const fqdn = `${host}.${domain}`; + // Append the domain to the host + const fqdn = `${host}.${domain}`; - const serviceEntryPort: IstioPort = { - name: "https", - number: 443, - protocol: "HTTPS", - }; + const serviceEntryPort: IstioPort = { + name: "https", + number: 443, + protocol: "HTTPS", + }; - const serviceEntryEndpoint: IstioEndpoint = { - // Map the gateway (admin, passthrough or tenant) to the ServiceEntry - address: `${gateway}-ingressgateway.istio-${gateway}-gateway.svc.cluster.local`, - }; + const serviceEntryEndpoint: IstioEndpoint = { + // Map the gateway (admin, passthrough or tenant) to the ServiceEntry + address: `${gateway}-ingressgateway.istio-${gateway}-gateway.svc.cluster.local`, + }; - const payload: IstioServiceEntry = { - metadata: { - name, - namespace, - labels: { - "uds/package": pkgName, - "uds/generation": generation, - }, - // Use the CR as the owner ref for each ServiceEntry - ownerReferences: getOwnerRef(pkg), - }, - spec: { - // Append the UDS Domain to the host - hosts: [fqdn], - location: IstioLocation.MeshInternal, - resolution: IstioResolution.DNS, - ports: [serviceEntryPort], - endpoints: [serviceEntryEndpoint], + const payload: IstioServiceEntry = { + metadata: { + name, + namespace, + labels: { + "uds/package": pkgName, + "uds/generation": generation, }, - }; - - Log.debug(payload, `Applying ServiceEntry ${payload.metadata?.name}`); - - // Apply the ServiceEntry and force overwrite any existing policy - await K8s(IstioServiceEntry).Apply(payload, { force: true }); - - serviceEntryNames.set(name, true); - } - - // Get all related ServiceEntries in the namespace - const serviceEntries = await K8s(IstioServiceEntry) - .InNamespace(namespace) - .WithLabel("uds/package", pkgName) - .Get(); - - // Find any orphaned ServiceEntries (not matching the current generation) - const orphanedSE = serviceEntries.items.filter( - se => se.metadata?.labels?.["uds/generation"] !== generation, - ); - - // Delete any orphaned ServiceEntries - for (const se of orphanedSE) { - Log.debug(se, `Deleting orphaned ServiceEntry ${se.metadata!.name}`); - await K8s(IstioServiceEntry).Delete(se); - } + // Use the CR as the owner ref for each ServiceEntry + ownerReferences: ownerRefs, + }, + spec: { + // Append the UDS Domain to the host + hosts: [fqdn], + location: IstioLocation.MeshInternal, + resolution: IstioResolution.DNS, + ports: [serviceEntryPort], + endpoints: [serviceEntryEndpoint], + }, + }; + + return payload; } -export function generateSEName(pkg: UDSPackage, expose: Expose) { +export function generateSEName(pkgName: string, expose: Expose) { const { gateway = Gateway.Tenant, host } = expose; // Ensure the resource name is valid - const name = sanitizeResourceName(`${pkg.metadata!.name}-${gateway}-${host}`); + const name = sanitizeResourceName(`${pkgName}-${gateway}-${host}`); return name; } diff --git a/src/pepr/operator/controllers/istio/virtual-service.ts b/src/pepr/operator/controllers/istio/virtual-service.ts index 9bcfd9c3e..591fa691f 100644 --- a/src/pepr/operator/controllers/istio/virtual-service.ts +++ b/src/pepr/operator/controllers/istio/virtual-service.ts @@ -1,15 +1,7 @@ -import { K8s, Log } from "pepr"; - import { UDSConfig } from "../../../config"; -import { - Expose, - Gateway, - IstioVirtualService, - IstioHTTP, - IstioHTTPRoute, - UDSPackage, -} from "../../crd"; -import { getOwnerRef, sanitizeResourceName } from "../utils"; +import { V1OwnerReference } from "@kubernetes/client-node"; +import { Expose, Gateway, IstioVirtualService, IstioHTTP, IstioHTTPRoute } from "../../crd"; +import { sanitizeResourceName } from "../utils"; /** * Creates a VirtualService for each exposed service in the package @@ -17,114 +9,82 @@ import { getOwnerRef, sanitizeResourceName } from "../utils"; * @param pkg * @param namespace */ -export async function virtualService(pkg: UDSPackage, namespace: string) { - const pkgName = pkg.metadata!.name!; - const generation = (pkg.metadata?.generation ?? 0).toString(); - - // Get the list of exposed services - const exposeList = pkg.spec?.network?.expose ?? []; - - // Create a list of generated VirtualServices - const payloads: IstioVirtualService[] = []; - - // Iterate over each exposed service - for (const expose of exposeList) { - const { gateway = Gateway.Tenant, host, port, service, advancedHTTP = {} } = expose; - - const name = generateVSName(pkg, expose); - - // For the admin gateway, we need to add the path prefix - const domain = (gateway === Gateway.Admin ? "admin." : "") + UDSConfig.domain; - - // Append the domain to the host - const fqdn = `${host}.${domain}`; - - const http: IstioHTTP = { ...advancedHTTP }; - - // Create the route to the service - const route: IstioHTTPRoute[] = [ - { - destination: { - // Use the service name as the host - host: `${service}.${namespace}.svc.cluster.local`, - // The CRD only uses numeric ports - port: { number: port }, - }, +export function generateVirtualService( + expose: Expose, + namespace: string, + pkgName: string, + generation: string, + ownerRefs: V1OwnerReference[], +) { + const { gateway = Gateway.Tenant, host, port, service, advancedHTTP = {} } = expose; + + const name = generateVSName(pkgName, expose); + + // For the admin gateway, we need to add the path prefix + const domain = (gateway === Gateway.Admin ? "admin." : "") + UDSConfig.domain; + + // Append the domain to the host + const fqdn = `${host}.${domain}`; + + const http: IstioHTTP = { ...advancedHTTP }; + + // Create the route to the service + const route: IstioHTTPRoute[] = [ + { + destination: { + // Use the service name as the host + host: `${service}.${namespace}.svc.cluster.local`, + // The CRD only uses numeric ports + port: { number: port }, }, - ]; + }, + ]; - if (!advancedHTTP.directResponse) { - // Create the route to the service if not using advancedHTTP.directResponse - http.route = route; - } + if (!advancedHTTP.directResponse) { + // Create the route to the service if not using advancedHTTP.directResponse + http.route = route; + } - const payload: IstioVirtualService = { - metadata: { - name, - namespace, - labels: { - "uds/package": pkgName, - "uds/generation": generation, - }, - // Use the CR as the owner ref for each VirtualService - ownerReferences: getOwnerRef(pkg), + const payload: IstioVirtualService = { + metadata: { + name, + namespace, + labels: { + "uds/package": pkgName, + "uds/generation": generation, }, - spec: { - // Append the UDS Domain to the host - hosts: [fqdn], - // Map the gateway (admin, passthrough or tenant) to the VirtualService - gateways: [`istio-${gateway}-gateway/${gateway}-gateway`], - // Apply the route to the VirtualService - http: [http], + // Use the CR as the owner ref for each VirtualService + ownerReferences: ownerRefs, + }, + spec: { + // Append the UDS Domain to the host + hosts: [fqdn], + // Map the gateway (admin, passthrough or tenant) to the VirtualService + gateways: [`istio-${gateway}-gateway/${gateway}-gateway`], + // Apply the route to the VirtualService + http: [http], + }, + }; + + // If the gateway is the passthrough gateway, apply the TLS match + if (gateway === Gateway.Passthrough) { + payload.spec!.tls = [ + { + match: [{ port: 443, sniHosts: [fqdn] }], + route, }, - }; - - // If the gateway is the passthrough gateway, apply the TLS match - if (gateway === Gateway.Passthrough) { - payload.spec!.tls = [ - { - match: [{ port: 443, sniHosts: [fqdn] }], - route, - }, - ]; - } - - Log.debug(payload, `Applying VirtualService ${name}`); - - // Apply the VirtualService and force overwrite any existing policy - await K8s(IstioVirtualService).Apply(payload, { force: true }); - - payloads.push(payload); - } - - // Get all related VirtualServices in the namespace - const virtualServices = await K8s(IstioVirtualService) - .InNamespace(namespace) - .WithLabel("uds/package", pkgName) - .Get(); - - // Find any orphaned VirtualServices (not matching the current generation) - const orphanedVS = virtualServices.items.filter( - vs => vs.metadata?.labels?.["uds/generation"] !== generation, - ); - - // Delete any orphaned VirtualServices - for (const vs of orphanedVS) { - Log.debug(vs, `Deleting orphaned VirtualService ${vs.metadata!.name}`); - await K8s(IstioVirtualService).Delete(vs); + ]; } - - // Return the list of unique hostnames - return [...new Set(payloads.map(v => v.spec!.hosts!).flat())]; + return payload; } -export function generateVSName(pkg: UDSPackage, expose: Expose) { +export function generateVSName(pkgName: string, expose: Expose) { const { gateway = Gateway.Tenant, host, port, service, description, advancedHTTP } = expose; // Ensure the resource name is valid const matchHash = advancedHTTP?.match?.flatMap(m => m.name).join("-") || ""; const nameSuffix = description || `${host}-${port}-${service}-${matchHash}`; - const name = sanitizeResourceName(`${pkg.metadata!.name}-${gateway}-${nameSuffix}`); + const name = sanitizeResourceName(`${pkgName}-${gateway}-${nameSuffix}`); return name; } diff --git a/src/pepr/operator/controllers/monitoring/service-monitor.spec.ts b/src/pepr/operator/controllers/monitoring/service-monitor.spec.ts index e4cb5cfc7..bbba94d6f 100644 --- a/src/pepr/operator/controllers/monitoring/service-monitor.spec.ts +++ b/src/pepr/operator/controllers/monitoring/service-monitor.spec.ts @@ -3,14 +3,14 @@ import { generateServiceMonitor } from "./service-monitor"; describe("test generate service monitor", () => { it("should return a valid Service Monitor object", () => { - const pkg = { - apiVersion: "uds.dev/v1alpha1", - kind: "Package", - metadata: { + const ownerRefs = [ + { + apiVersion: "uds.dev/v1alpha1", + kind: "Package", name: "test", uid: "f50120aa-2713-4502-9496-566b102b1174", }, - }; + ]; const portName = "http-metrics"; const metricsPath = "/test"; const selectorApp = "test"; @@ -25,7 +25,7 @@ describe("test generate service monitor", () => { const namespace = "test"; const pkgName = "test"; const generation = "1"; - const payload = generateServiceMonitor(pkg, monitor, namespace, pkgName, generation); + const payload = generateServiceMonitor(monitor, namespace, pkgName, generation, ownerRefs); expect(payload).toBeDefined(); expect(payload.metadata?.name).toEqual(`${pkgName}-${selectorApp}-${portName}`); diff --git a/src/pepr/operator/controllers/monitoring/service-monitor.ts b/src/pepr/operator/controllers/monitoring/service-monitor.ts index 7756142a6..ec427f71c 100644 --- a/src/pepr/operator/controllers/monitoring/service-monitor.ts +++ b/src/pepr/operator/controllers/monitoring/service-monitor.ts @@ -1,5 +1,6 @@ import { K8s, Log } from "pepr"; +import { V1OwnerReference } from "@kubernetes/client-node"; import { Prometheus, UDSPackage } from "../../crd"; import { Monitor } from "../../crd/generated/package-v1alpha1"; import { getOwnerRef, sanitizeResourceName } from "../utils"; @@ -13,6 +14,7 @@ import { getOwnerRef, sanitizeResourceName } from "../utils"; export async function serviceMonitor(pkg: UDSPackage, namespace: string) { const pkgName = pkg.metadata!.name!; const generation = (pkg.metadata?.generation ?? 0).toString(); + const ownerRefs = getOwnerRef(pkg); Log.debug(`Reconciling ServiceMonitors for ${pkgName}`); @@ -24,7 +26,7 @@ export async function serviceMonitor(pkg: UDSPackage, namespace: string) { try { for (const monitor of monitorList) { - const payload = generateServiceMonitor(pkg, monitor, namespace, pkgName, generation); + const payload = generateServiceMonitor(monitor, namespace, pkgName, generation, ownerRefs); Log.debug(payload, `Applying ServiceMonitor ${payload.metadata?.name}`); @@ -60,25 +62,25 @@ export async function serviceMonitor(pkg: UDSPackage, namespace: string) { return [...payloads.map(sm => sm.metadata!.name!)]; } -export function generateSMName(pkg: UDSPackage, monitor: Monitor) { +export function generateSMName(pkgName: string, monitor: Monitor) { const { selector, portName, description } = monitor; // Ensure the resource name is valid const nameSuffix = description || `${Object.values(selector)}-${portName}`; - const name = sanitizeResourceName(`${pkg.metadata!.name}-${nameSuffix}`); + const name = sanitizeResourceName(`${pkgName}-${nameSuffix}`); return name; } export function generateServiceMonitor( - pkg: UDSPackage, monitor: Monitor, namespace: string, pkgName: string, generation: string, + ownerRefs: V1OwnerReference[], ) { const { selector, portName } = monitor; - const name = generateSMName(pkg, monitor); + const name = generateSMName(pkgName, monitor); const payload: Prometheus.ServiceMonitor = { metadata: { name, @@ -87,7 +89,7 @@ export function generateServiceMonitor( "uds/package": pkgName, "uds/generation": generation, }, - ownerReferences: getOwnerRef(pkg), + ownerReferences: ownerRefs, }, spec: { endpoints: [ diff --git a/src/pepr/operator/reconcilers/package-reconciler.ts b/src/pepr/operator/reconcilers/package-reconciler.ts index a111afa1e..4e522a14c 100644 --- a/src/pepr/operator/reconcilers/package-reconciler.ts +++ b/src/pepr/operator/reconcilers/package-reconciler.ts @@ -3,8 +3,7 @@ import { Log } from "pepr"; import { handleFailure, shouldSkip, updateStatus } from "."; import { UDSConfig } from "../../config"; import { enableInjection } from "../controllers/istio/injection"; -import { virtualService } from "../controllers/istio/virtual-service"; -import { serviceEntry } from "../controllers/istio/service-entry"; +import { istioResources } from "../controllers/istio/istio-resources"; import { keycloak } from "../controllers/keycloak/client-sync"; import { serviceMonitor } from "../controllers/monitoring/service-monitor"; import { networkPolicies } from "../controllers/network/policies"; @@ -41,11 +40,8 @@ export async function packageReconciler(pkg: UDSPackage) { // Update the namespace to ensure the istio-injection label is set await enableInjection(pkg); - // Create the VirtualService for each exposed service - endpoints = await virtualService(pkg, namespace!); - - // Create the ServiceEntry for each exposed service - await serviceEntry(pkg, namespace!); + // Create the VirtualService and ServiceEntry for each exposed service + endpoints = await istioResources(pkg, namespace!); // Only configure the ServiceMonitors if not running in single test mode let monitors: string[] = []; From 23c39810f38535db6c4346cfbfea10be7f14fce9 Mon Sep 17 00:00:00 2001 From: Wayne Starr Date: Thu, 9 May 2024 12:38:04 -0600 Subject: [PATCH 10/18] add a comment --- tasks.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/tasks.yaml b/tasks.yaml index 0899f5f03..9ff6b4971 100644 --- a/tasks.yaml +++ b/tasks.yaml @@ -30,6 +30,7 @@ tasks: - description: "Deploy the Istio source package with Zarf Dev" cmd: "uds zarf dev deploy src/istio --flavor ${FLAVOR}" + # Note, this abuses the --flavor flag to only install the CRDs from this package - the "crds-only" flavor is not an explicit flavor of the package - description: "Deploy the Prometheus-Stack source package with Zarf Dev to only install the CRDs" cmd: "uds zarf dev deploy src/prometheus-stack --flavor crds-only" From 0531a3c3bd3eadf25c803cbec371585ebe951b3b Mon Sep 17 00:00:00 2001 From: Wayne Starr Date: Thu, 9 May 2024 12:39:15 -0600 Subject: [PATCH 11/18] add a comment2 --- src/pepr/operator/controllers/istio/istio-resources.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pepr/operator/controllers/istio/istio-resources.ts b/src/pepr/operator/controllers/istio/istio-resources.ts index c21876b16..bb10c9bda 100644 --- a/src/pepr/operator/controllers/istio/istio-resources.ts +++ b/src/pepr/operator/controllers/istio/istio-resources.ts @@ -41,6 +41,7 @@ export async function istioResources(pkg: UDSPackage, namespace: string) { const sePayload = generateServiceEntry(expose, namespace, pkgName, generation, ownerRefs); // If we have already made a ServiceEntry with this name, skip (i.e. if advancedHTTP was used) + // TODO: (@MJNAGEL) - want to validate that skipping this here doesn't miss any edge cases if (serviceEntryNames.get(sePayload.metadata!.name!)) { continue; } From a8f2838eb994f466dd6f14aa9448a2ce3a6290ab Mon Sep 17 00:00:00 2001 From: Wayne Starr Date: Thu, 9 May 2024 16:20:47 -0600 Subject: [PATCH 12/18] add tests --- .../controllers/istio/service-entry.spec.ts | 51 +++++++++ .../controllers/istio/virtual-service.spec.ts | 104 ++++++++++++++++++ .../monitoring/service-monitor.spec.ts | 3 +- .../controllers/monitoring/service-monitor.ts | 3 +- src/pepr/operator/crd/index.ts | 1 + 5 files changed, 159 insertions(+), 3 deletions(-) create mode 100644 src/pepr/operator/controllers/istio/service-entry.spec.ts create mode 100644 src/pepr/operator/controllers/istio/virtual-service.spec.ts diff --git a/src/pepr/operator/controllers/istio/service-entry.spec.ts b/src/pepr/operator/controllers/istio/service-entry.spec.ts new file mode 100644 index 000000000..06e79e5c3 --- /dev/null +++ b/src/pepr/operator/controllers/istio/service-entry.spec.ts @@ -0,0 +1,51 @@ +import { describe, expect, it } from "@jest/globals"; +import { UDSConfig } from "../../../config"; +import { generateServiceEntry } from "./service-entry"; +import { Expose, Gateway, IstioLocation, IstioResolution } from "../../crd"; + +describe("test generate service entry", () => { + const ownerRefs = [ + { + apiVersion: "uds.dev/v1alpha1", + kind: "Package", + name: "test", + uid: "f50120aa-2713-4502-9496-566b102b1174", + }, + ]; + + const host = "test"; + const port = 8080; + const service = "test-service" + + const namespace = "test"; + const pkgName = "test"; + const generation = "1"; + + it("should create a simple ServiceEntry object", () => { + const expose: Expose = { + host, + port, + service, + }; + + const payload = generateServiceEntry(expose, namespace, pkgName, generation, ownerRefs); + + expect(payload).toBeDefined(); + expect(payload.metadata?.name).toEqual(`${pkgName}-${Gateway.Tenant}-${host}`); + expect(payload.metadata?.namespace).toEqual(namespace); + + expect(payload.spec?.hosts).toBeDefined(); + expect(payload.spec!.hosts![0]).toEqual(`${host}.${UDSConfig.domain}`); + + expect(payload.spec!.location).toEqual(IstioLocation.MeshInternal); + expect(payload.spec!.resolution).toEqual(IstioResolution.DNS); + + expect(payload.spec?.ports).toBeDefined(); + expect(payload.spec!.ports![0].name).toEqual("https"); + expect(payload.spec!.ports![0].number).toEqual(443); + expect(payload.spec!.ports![0].protocol).toEqual("HTTPS"); + + expect(payload.spec?.endpoints).toBeDefined(); + expect(payload.spec!.endpoints![0].address).toEqual(`${Gateway.Tenant}-ingressgateway.istio-${Gateway.Tenant}-gateway.svc.cluster.local`); + }); +}); diff --git a/src/pepr/operator/controllers/istio/virtual-service.spec.ts b/src/pepr/operator/controllers/istio/virtual-service.spec.ts new file mode 100644 index 000000000..5e5df3d49 --- /dev/null +++ b/src/pepr/operator/controllers/istio/virtual-service.spec.ts @@ -0,0 +1,104 @@ +import { describe, expect, it } from "@jest/globals"; +import { UDSConfig } from "../../../config"; +import { generateVirtualService } from "./virtual-service"; +import { Expose, Gateway } from "../../crd"; + +describe("test generate virtual service", () => { + const ownerRefs = [ + { + apiVersion: "uds.dev/v1alpha1", + kind: "Package", + name: "test", + uid: "f50120aa-2713-4502-9496-566b102b1174", + }, + ]; + + const host = "test"; + const port = 8080; + const service = "test-service" + + const namespace = "test"; + const pkgName = "test"; + const generation = "1"; + + it("should create a simple VirtualService object", () => { + const expose: Expose = { + host, + port, + service, + }; + + const payload = generateVirtualService(expose, namespace, pkgName, generation, ownerRefs); + + expect(payload).toBeDefined(); + expect(payload.metadata?.name).toEqual(`${pkgName}-${Gateway.Tenant}-${host}-${port}-${service}`); + expect(payload.metadata?.namespace).toEqual(namespace); + + expect(payload.spec?.hosts).toBeDefined(); + expect(payload.spec!.hosts![0]).toEqual(`${host}.${UDSConfig.domain}`); + + expect(payload.spec?.http).toBeDefined(); + expect(payload.spec!.http![0].route).toBeDefined(); + expect(payload.spec!.http![0].route![0].destination?.host).toEqual(`${service}.${namespace}.svc.cluster.local`); + expect(payload.spec!.http![0].route![0].destination?.port?.number).toEqual(port); + + expect(payload.spec?.gateways).toBeDefined(); + expect(payload.spec!.gateways![0]).toEqual(`istio-${Gateway.Tenant}-gateway/${Gateway.Tenant}-gateway`); + }); + + it("should create an admin VirtualService object", () => { + const gateway = Gateway.Admin; + const expose: Expose = { + gateway, + host, + port, + service, + }; + + const payload = generateVirtualService(expose, namespace, pkgName, generation, ownerRefs); + + expect(payload).toBeDefined(); + expect(payload.spec?.hosts).toBeDefined(); + expect(payload.spec!.hosts![0]).toEqual(`${host}.admin.${UDSConfig.domain}`); + }); + + it("should create an advancedHttp VirtualService object", () => { + const advancedHTTP = { + directResponse: { status: 404 } + } + const expose: Expose = { + host, + port, + service, + advancedHTTP, + }; + + const payload = generateVirtualService(expose, namespace, pkgName, generation, ownerRefs); + + expect(payload).toBeDefined(); + expect(payload.spec?.http).toBeDefined(); + expect(payload.spec!.http![0].route).not.toBeDefined(); + expect(payload.spec!.http![0].directResponse?.status).toEqual(404); + }); + + it("should create a passthrough VirtualService object", () => { + const gateway = Gateway.Passthrough; + const expose: Expose = { + gateway, + host, + port, + service, + }; + + const payload = generateVirtualService(expose, namespace, pkgName, generation, ownerRefs); + + expect(payload).toBeDefined(); + expect(payload.spec?.tls).toBeDefined(); + expect(payload.spec!.tls![0].match).toBeDefined(); + expect(payload.spec!.tls![0].match![0].port).toEqual(443); + expect(payload.spec!.tls![0].match![0].sniHosts![0]).toEqual(`${host}.${UDSConfig.domain}`); + expect(payload.spec!.tls![0].route).toBeDefined(); + expect(payload.spec!.http![0].route![0].destination?.host).toEqual(`${service}.${namespace}.svc.cluster.local`); + expect(payload.spec!.http![0].route![0].destination?.port?.number).toEqual(port); + }); +}); diff --git a/src/pepr/operator/controllers/monitoring/service-monitor.spec.ts b/src/pepr/operator/controllers/monitoring/service-monitor.spec.ts index bbba94d6f..83d4fa03e 100644 --- a/src/pepr/operator/controllers/monitoring/service-monitor.spec.ts +++ b/src/pepr/operator/controllers/monitoring/service-monitor.spec.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from "@jest/globals"; import { generateServiceMonitor } from "./service-monitor"; +import { Monitor } from "../../crd"; describe("test generate service monitor", () => { it("should return a valid Service Monitor object", () => { @@ -14,7 +15,7 @@ describe("test generate service monitor", () => { const portName = "http-metrics"; const metricsPath = "/test"; const selectorApp = "test"; - const monitor = { + const monitor: Monitor = { portName: portName, path: metricsPath, targetPort: 1234, diff --git a/src/pepr/operator/controllers/monitoring/service-monitor.ts b/src/pepr/operator/controllers/monitoring/service-monitor.ts index ec427f71c..ff2ba0713 100644 --- a/src/pepr/operator/controllers/monitoring/service-monitor.ts +++ b/src/pepr/operator/controllers/monitoring/service-monitor.ts @@ -1,8 +1,7 @@ import { K8s, Log } from "pepr"; import { V1OwnerReference } from "@kubernetes/client-node"; -import { Prometheus, UDSPackage } from "../../crd"; -import { Monitor } from "../../crd/generated/package-v1alpha1"; +import { Prometheus, UDSPackage, Monitor } from "../../crd"; import { getOwnerRef, sanitizeResourceName } from "../utils"; /** diff --git a/src/pepr/operator/crd/index.ts b/src/pepr/operator/crd/index.ts index 1968931b5..959ae0029 100644 --- a/src/pepr/operator/crd/index.ts +++ b/src/pepr/operator/crd/index.ts @@ -2,6 +2,7 @@ export { Allow, Direction, Expose, + Monitor, Gateway, Phase, Status as PkgStatus, From 10a0c7e3bff67bda4bc60a58874cd300a50f1a8d Mon Sep 17 00:00:00 2001 From: Wayne Starr Date: Thu, 9 May 2024 16:22:27 -0600 Subject: [PATCH 13/18] fix lint --- .../controllers/istio/service-entry.spec.ts | 6 +++-- .../controllers/istio/virtual-service.spec.ts | 22 +++++++++++++------ 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/pepr/operator/controllers/istio/service-entry.spec.ts b/src/pepr/operator/controllers/istio/service-entry.spec.ts index 06e79e5c3..caeb1be1c 100644 --- a/src/pepr/operator/controllers/istio/service-entry.spec.ts +++ b/src/pepr/operator/controllers/istio/service-entry.spec.ts @@ -15,7 +15,7 @@ describe("test generate service entry", () => { const host = "test"; const port = 8080; - const service = "test-service" + const service = "test-service"; const namespace = "test"; const pkgName = "test"; @@ -46,6 +46,8 @@ describe("test generate service entry", () => { expect(payload.spec!.ports![0].protocol).toEqual("HTTPS"); expect(payload.spec?.endpoints).toBeDefined(); - expect(payload.spec!.endpoints![0].address).toEqual(`${Gateway.Tenant}-ingressgateway.istio-${Gateway.Tenant}-gateway.svc.cluster.local`); + expect(payload.spec!.endpoints![0].address).toEqual( + `${Gateway.Tenant}-ingressgateway.istio-${Gateway.Tenant}-gateway.svc.cluster.local`, + ); }); }); diff --git a/src/pepr/operator/controllers/istio/virtual-service.spec.ts b/src/pepr/operator/controllers/istio/virtual-service.spec.ts index 5e5df3d49..890d0c12b 100644 --- a/src/pepr/operator/controllers/istio/virtual-service.spec.ts +++ b/src/pepr/operator/controllers/istio/virtual-service.spec.ts @@ -15,7 +15,7 @@ describe("test generate virtual service", () => { const host = "test"; const port = 8080; - const service = "test-service" + const service = "test-service"; const namespace = "test"; const pkgName = "test"; @@ -31,7 +31,9 @@ describe("test generate virtual service", () => { const payload = generateVirtualService(expose, namespace, pkgName, generation, ownerRefs); expect(payload).toBeDefined(); - expect(payload.metadata?.name).toEqual(`${pkgName}-${Gateway.Tenant}-${host}-${port}-${service}`); + expect(payload.metadata?.name).toEqual( + `${pkgName}-${Gateway.Tenant}-${host}-${port}-${service}`, + ); expect(payload.metadata?.namespace).toEqual(namespace); expect(payload.spec?.hosts).toBeDefined(); @@ -39,11 +41,15 @@ describe("test generate virtual service", () => { expect(payload.spec?.http).toBeDefined(); expect(payload.spec!.http![0].route).toBeDefined(); - expect(payload.spec!.http![0].route![0].destination?.host).toEqual(`${service}.${namespace}.svc.cluster.local`); + expect(payload.spec!.http![0].route![0].destination?.host).toEqual( + `${service}.${namespace}.svc.cluster.local`, + ); expect(payload.spec!.http![0].route![0].destination?.port?.number).toEqual(port); expect(payload.spec?.gateways).toBeDefined(); - expect(payload.spec!.gateways![0]).toEqual(`istio-${Gateway.Tenant}-gateway/${Gateway.Tenant}-gateway`); + expect(payload.spec!.gateways![0]).toEqual( + `istio-${Gateway.Tenant}-gateway/${Gateway.Tenant}-gateway`, + ); }); it("should create an admin VirtualService object", () => { @@ -64,8 +70,8 @@ describe("test generate virtual service", () => { it("should create an advancedHttp VirtualService object", () => { const advancedHTTP = { - directResponse: { status: 404 } - } + directResponse: { status: 404 }, + }; const expose: Expose = { host, port, @@ -98,7 +104,9 @@ describe("test generate virtual service", () => { expect(payload.spec!.tls![0].match![0].port).toEqual(443); expect(payload.spec!.tls![0].match![0].sniHosts![0]).toEqual(`${host}.${UDSConfig.domain}`); expect(payload.spec!.tls![0].route).toBeDefined(); - expect(payload.spec!.http![0].route![0].destination?.host).toEqual(`${service}.${namespace}.svc.cluster.local`); + expect(payload.spec!.http![0].route![0].destination?.host).toEqual( + `${service}.${namespace}.svc.cluster.local`, + ); expect(payload.spec!.http![0].route![0].destination?.port?.number).toEqual(port); }); }); From 4393b6237ef1fec834938d08e15c16d94b892e38 Mon Sep 17 00:00:00 2001 From: Wayne Starr Date: Thu, 9 May 2024 16:29:57 -0600 Subject: [PATCH 14/18] fix other pkg name --- src/pepr/operator/crd/validators/package-validator.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pepr/operator/crd/validators/package-validator.ts b/src/pepr/operator/crd/validators/package-validator.ts index 3cbe19251..8155bf0e5 100644 --- a/src/pepr/operator/crd/validators/package-validator.ts +++ b/src/pepr/operator/crd/validators/package-validator.ts @@ -11,6 +11,7 @@ const invalidNamespaces = ["kube-system", "kube-public", "_unknown_", "pepr-syst export async function validator(req: PeprValidateRequest) { const pkg = migrate(req.Raw); + const pkgName = pkg.metadata?.name ?? "_unknown_"; const ns = pkg.metadata?.namespace ?? "_unknown_"; if (invalidNamespaces.includes(ns)) { @@ -38,7 +39,7 @@ export async function validator(req: PeprValidateRequest) { } // Ensure the service name is unique - const name = generateVSName(req.Raw, expose); + const name = generateVSName(pkgName, expose); if (virtualServiceNames.has(name)) { return req.Deny( `The combination of characteristics of this expose entry would create a duplicate VirtualService. ` + From 7721df318da4ae67ae3a5d74a9e0fd05b000d79e Mon Sep 17 00:00:00 2001 From: Wayne Starr Date: Fri, 10 May 2024 08:37:07 -0600 Subject: [PATCH 15/18] update docs --- src/pepr/operator/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pepr/operator/README.md b/src/pepr/operator/README.md index fe152f391..a98a7c36b 100644 --- a/src/pepr/operator/README.md +++ b/src/pepr/operator/README.md @@ -8,7 +8,7 @@ The UDS Operator manages the lifecycle of UDS Package CRs and their correspondin - establishing default-deny ingress/egress network policies - creating a layered allow-list based approach on top of the default deny network policies including some basic defaults such as Istio requirements and DNS egress - providing targeted remote endpoints network policies such as `KubeAPI` and `CloudMetadata` to make policies more DRY and provide dynamic bindings where a static definition is not possible -- creating Istio Virtual Services & related ingress gateway network policies +- creating Istio Virtual Services, Service Entries & related ingress gateway network policies #### Exemption @@ -25,7 +25,7 @@ metadata: namespace: grafana spec: network: - # Expose rules generate Istio VirtualServices and related network policies + # Expose rules generate Istio VirtualServices, ServiceEntries and related network policies expose: - service: grafana selector: From 3a1798127feef3ef573b41411c50d55d3a63f5e3 Mon Sep 17 00:00:00 2001 From: Wayne Starr Date: Fri, 10 May 2024 08:42:35 -0600 Subject: [PATCH 16/18] update docs --- src/pepr/operator/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pepr/operator/README.md b/src/pepr/operator/README.md index a98a7c36b..737828f60 100644 --- a/src/pepr/operator/README.md +++ b/src/pepr/operator/README.md @@ -196,8 +196,8 @@ graph TD G -->|Yes| H["Log: Skipping pkg"] G -->|No| I["Update pkg status to Phase.Pending"] I --> J{"Check if Istio is installed"} - J -->|Yes| K["Add injection label, process expose CRs for Virtual Services"] - J -->|No| L["Skip Virtual Service Creation"] + J -->|Yes| K["Add injection label, process expose CRs for Istio Resources"] + J -->|No| L["Skip Istio Resource Creation"] K --> M["Create default network policies in namespace"] L --> M M --> N["Process allow CRs for network policies"] From f16521920a4d0a5a2bfa9d4f7a991b41f285799d Mon Sep 17 00:00:00 2001 From: Wayne Starr Date: Tue, 14 May 2024 11:28:05 -0600 Subject: [PATCH 17/18] Update src/pepr/operator/controllers/istio/istio-resources.ts Co-authored-by: Micah Nagel --- src/pepr/operator/controllers/istio/istio-resources.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pepr/operator/controllers/istio/istio-resources.ts b/src/pepr/operator/controllers/istio/istio-resources.ts index bb10c9bda..49a750600 100644 --- a/src/pepr/operator/controllers/istio/istio-resources.ts +++ b/src/pepr/operator/controllers/istio/istio-resources.ts @@ -6,7 +6,7 @@ import { generateVirtualService } from "./virtual-service"; import { generateServiceEntry } from "./service-entry"; /** - * Creates a VirtualService for each exposed service in the package + * Creates a VirtualService and ServiceEntry for each exposed service in the package * * @param pkg * @param namespace From 87127571aab4eae405707e43c449716636c5c93d Mon Sep 17 00:00:00 2001 From: Wayne Starr Date: Tue, 14 May 2024 11:28:26 -0600 Subject: [PATCH 18/18] Update src/pepr/operator/controllers/istio/istio-resources.ts Co-authored-by: Micah Nagel --- src/pepr/operator/controllers/istio/istio-resources.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pepr/operator/controllers/istio/istio-resources.ts b/src/pepr/operator/controllers/istio/istio-resources.ts index 49a750600..84406067a 100644 --- a/src/pepr/operator/controllers/istio/istio-resources.ts +++ b/src/pepr/operator/controllers/istio/istio-resources.ts @@ -41,7 +41,6 @@ export async function istioResources(pkg: UDSPackage, namespace: string) { const sePayload = generateServiceEntry(expose, namespace, pkgName, generation, ownerRefs); // If we have already made a ServiceEntry with this name, skip (i.e. if advancedHTTP was used) - // TODO: (@MJNAGEL) - want to validate that skipping this here doesn't miss any edge cases if (serviceEntryNames.get(sePayload.metadata!.name!)) { continue; }