Skip to content

Commit

Permalink
feat: Add ability to create an ingress
Browse files Browse the repository at this point in the history
### What does this PR do?

* Add the option to create an ingress when deploying to Kubernetes
* Compatible with 'Contour' ingress with Kind

### Screenshot/screencast of this PR

<!-- Please include a screenshot or a screencast explaining what is doing this PR -->

### What issues does this PR fix or reference?

<!-- Please include any related issue from Podman Desktop repository (or from another issue tracker).
-->

Fixes containers#1322

### How to test this PR?

1. Create a `kind` cluster with port 9090 for http
2. Run 'podman run --name hello -d -p 8080:8080 cdrage/helloworld`
3. Press "Deploy to Kubernetes"
4. Select the ingress option
5. `curl localhost:9090` or visit with the browser.

Signed-off-by: Charlie Drage <charlie@charliedrage.com>
  • Loading branch information
cdrage committed Apr 18, 2023
1 parent 30b5d12 commit 0f2a8ac
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 5 deletions.
9 changes: 8 additions & 1 deletion packages/main/src/plugin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ import { AutostartEngine } from './autostart-engine';
import { CloseBehavior } from './close-behavior';
import { TrayIconColor } from './tray-icon-color';
import { KubernetesClient } from './kubernetes-client';
import type { V1Pod, V1ConfigMap, V1NamespaceList, V1PodList, V1Service } from '@kubernetes/client-node';
import type { V1Pod, V1ConfigMap, V1NamespaceList, V1PodList, V1Service, V1Ingress } from '@kubernetes/client-node';
import type { V1Route } from './api/openshift-types';
import type { NetworkInspectInfo } from './api/network-info';
import { FilesystemMonitoring } from './filesystem-monitoring';
Expand Down Expand Up @@ -1335,6 +1335,13 @@ export class PluginSystem {
},
);

this.ipcHandle(
'kubernetes-client:createIngress',
async (_listener, namespace: string, ingress: V1Ingress): Promise<V1Ingress> => {
return kubernetesClient.createIngress(namespace, ingress);
},
);

this.ipcHandle('kubernetes-client:listPods', async (): Promise<PodInfo[]> => {
return kubernetesClient.listPods();
});
Expand Down
13 changes: 13 additions & 0 deletions packages/main/src/plugin/kubernetes-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ import type {
V1PodList,
V1NamespaceList,
V1Service,
V1Ingress,
V1ContainerState,
} from '@kubernetes/client-node';
import { NetworkingV1Api } from '@kubernetes/client-node';
import { AppsV1Api } from '@kubernetes/client-node';
import { CustomObjectsApi } from '@kubernetes/client-node';
import { CoreV1Api, KubeConfig, Log, Watch } from '@kubernetes/client-node';
Expand Down Expand Up @@ -306,6 +308,17 @@ export class KubernetesClient {
}
}

async createIngress(namespace: string, body: V1Ingress): Promise<V1Ingress> {
const k8sCoreApi = this.kubeConfig.makeApiClient(NetworkingV1Api);

try {
const createdIngressData = await k8sCoreApi.createNamespacedIngress(namespace, body);
return createdIngressData.body;
} catch (error) {
throw this.wrapK8sClientError(error);
}
}

async createOpenShiftRoute(namespace: string, body: V1Route): Promise<V1Route> {
const k8sCustomObjectsApi = this.kubeConfig.makeApiClient(CustomObjectsApi);

Expand Down
9 changes: 8 additions & 1 deletion packages/preload/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ import type {
PodCreateOptions,
ContainerCreateOptions as PodmanContainerCreateOptions,
} from '../../main/src/plugin/dockerode/libpod-dockerode';
import type { V1ConfigMap, V1NamespaceList, V1Pod, V1PodList, V1Service } from '@kubernetes/client-node';
import type { V1ConfigMap, V1Ingress, V1NamespaceList, V1Pod, V1PodList, V1Service } from '@kubernetes/client-node';
import type { Menu } from '../../main/src/plugin/menu-registry';

export type DialogResultCallback = (openDialogReturnValue: Electron.OpenDialogReturnValue) => void;
Expand Down Expand Up @@ -1118,6 +1118,13 @@ function initExposure(): void {
},
);

contextBridge.exposeInMainWorld(
'kubernetesCreateIngress',
async (namespace: string, ingress: V1Ingress): Promise<V1Ingress> => {
return ipcInvoke('kubernetes-client:createIngress', namespace, ingress);
},
);

contextBridge.exposeInMainWorld('kubernetesListPods', async (): Promise<PodInfo[]> => {
return ipcInvoke('kubernetes-client:listPods');
});
Expand Down
100 changes: 97 additions & 3 deletions packages/renderer/src/lib/pod/DeployPodToKube.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ let deployUsingRoutes = true;
let createdPod = undefined;
let bodyPod;
let createIngress = false;
let ingressServiceName = '';
let containerPortArray: string[] = [];
let createdRoutes: V1Route[] = [];
onMount(async () => {
Expand Down Expand Up @@ -65,6 +69,14 @@ onMount(async () => {
// probably not OpenShift cluster, ignoring
console.debug('Probably not an OpenShift cluster, so ignoring the error to grab console link', error);
}
// Go through bodyPod.spec.containers and create a string array of each container and port,
// have the name as: containerName-portNumber
bodyPod.spec.containers.forEach((container: any) => {
container.ports.forEach((port: any) => {
containerPortArray.push(`${container.name}-${port.containerPort}`);
});
});
});
function openOpenshiftConsole() {
Expand Down Expand Up @@ -111,6 +123,7 @@ async function deployToKube() {
createdRoutes = [];
let servicesToCreate: any[] = [];
let routesToCreate: any[] = [];
let ingressesToCreate: any[] = [];
if (bodyPod?.spec?.volumes) {
delete bodyPod.spec.volumes;
Expand All @@ -125,19 +138,20 @@ async function deployToKube() {
}
container?.ports?.forEach((port: any) => {
let portName = `${bodyPod.metadata.name}-${port.hostPort}`;
if (port.hostPort) {
// create service
const service = {
apiVersion: 'v1',
kind: 'Service',
metadata: {
name: `${bodyPod.metadata.name}-${port.hostPort}`,
name: portName,
namespace: currentNamespace,
},
spec: {
ports: [
{
name: port.name,
name: portName,
port: port.hostPort,
protocol: port.protocol || 'TCP',
targetPort: port.containerPort,
Expand Down Expand Up @@ -178,6 +192,44 @@ async function deployToKube() {
});
}
// Check if we are deploying an ingress, if so, we need to create an ingress object using ingressPath and ingressDomain.
if (createIngress) {
let serviceName = '';
// Check that there are services (servicesToCreate), if there aren't. Warn that we can't create an ingress.
if (servicesToCreate.length === 0) {
deployError = 'You need to deploy using services to create an ingress.';
return;
} else if (servicesToCreate.length == 1) {
// If there is only one service, retrieve the service name that we'll be creating an ingress for
serviceName = servicesToCreate[0].metadata.name;
} else if (servicesToCreate.length > 1) {
serviceName = ingressServiceName;
}
const ingress = {
apiVersion: 'networking.k8s.io/v1',
kind: 'Ingress',
metadata: {
name: bodyPod.metadata.name,
namespace: currentNamespace,
},
spec: {
defaultBackend: {
service: {
name: serviceName,
port: {
name: serviceName,
},
},
},
},
};
// Support for multiple ingress creation in the future
ingressesToCreate.push(ingress);
}
// https://github.com/kubernetes-client/javascript/issues/487
if (bodyPod?.metadata?.creationTimestamp) {
bodyPod.metadata.creationTimestamp = new Date(bodyPod.metadata.creationTimestamp);
Expand All @@ -186,6 +238,7 @@ async function deployToKube() {
const eventProperties = {
useServices: deployUsingServices,
useRoutes: deployUsingRoutes,
createIngress: createIngress,
};
if (openshiftConsoleURL) {
eventProperties['isOpenshift'] = true;
Expand All @@ -204,6 +257,13 @@ async function deployToKube() {
const createdRoute = await window.openshiftCreateRoute(currentNamespace, route);
createdRoutes = [...createdRoutes, createdRoute];
}
// Create ingresses
for (const ingress of ingressesToCreate) {
await window.kubernetesCreateIngress(currentNamespace, ingress);
}
// Telemetry
window.telemetryTrack('deployToKube', eventProperties);
// update status
Expand Down Expand Up @@ -246,7 +306,7 @@ function updateKubeResult() {
</div>
{/if}

<div class="pt-2 m-2">
<div class="pt-2 pb-4">
<label for="services" class="block mb-1 text-sm font-medium text-gray-300">Use Kubernetes Services:</label>
<input
type="checkbox"
Expand All @@ -260,6 +320,40 @@ function updateKubeResult() {
policy may prevent to use hostPort.</span>
</div>

<div class="pt-2 pb-4">
<label for="ingress" class="block mb-1 text-sm font-medium text-gray-300">Use Kubernetes Ingress:</label>
<input
type="checkbox"
bind:checked="{createIngress}"
name="createIngress"
id="createIngress"
class=""
required />
<span class="text-gray-300 text-sm ml-1"
>Create a Kubernetes ingress to get access to the exposed ports of this pod. An ingress controller is required
on your cluster. This ingress will be created at the default Ingress location (example Podman kind setup:
localhost:9090)</span>
</div>

{#if createIngress && containerPortArray.length > 1}
<div class="pt-2 pb-4">
<label for="ingress" class="block mb-1 text-sm font-medium text-gray-300">Ingress Service:</label>
<select
bind:value="{ingressServiceName}"
name="serviceName"
id="serviceName"
class=" cursor-default w-full p-2 outline-none text-sm bg-zinc-900 rounded-sm text-gray-400 placeholder-gray-400"
required>
{#each containerPortArray as serviceName}
<option value="{serviceName}">{serviceName}</option>
{/each}
</select>
<span class="text-gray-300 text-sm ml-1"
>There are multiple services available. Select the one you want to expose with the Ingress.
</span>
</div>
{/if}

<!-- Allow to create routes for OpenShift clusters -->
{#if openshiftConsoleURL}
<div class="pt-2 m-2">
Expand Down

0 comments on commit 0f2a8ac

Please sign in to comment.