Skip to content

Commit

Permalink
this should work :fingers-crossed:
Browse files Browse the repository at this point in the history
  • Loading branch information
Racer159 committed May 6, 2024
1 parent 6ffa236 commit fde7b14
Show file tree
Hide file tree
Showing 5 changed files with 274 additions and 9 deletions.
107 changes: 107 additions & 0 deletions src/pepr/operator/controllers/istio/service-entry.ts
Original file line number Diff line number Diff line change
@@ -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<string, boolean> = 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;
}
16 changes: 8 additions & 8 deletions src/pepr/operator/controllers/istio/virtual-service.ts
Original file line number Diff line number Diff line change
@@ -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";

/**
Expand All @@ -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) {
Expand All @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -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();
Expand All @@ -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
Expand Down
141 changes: 141 additions & 0 deletions src/pepr/operator/crd/generated/istio/serviceentry-v1beta1.ts
Original file line number Diff line number Diff line change
@@ -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 };

Check failure on line 11 in src/pepr/operator/crd/generated/istio/serviceentry-v1beta1.ts

View workflow job for this annotation

GitHub Actions / lint-check

Unexpected any. Specify a different type
}

/**
* 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",
});
15 changes: 14 additions & 1 deletion src/pepr/operator/crd/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
4 changes: 4 additions & 0 deletions src/pepr/operator/reconcilers/package-reconciler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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) {
Expand Down

0 comments on commit fde7b14

Please sign in to comment.