Skip to content

Commit

Permalink
Prefill the wizard with data extracted by kubectl
Browse files Browse the repository at this point in the history
  • Loading branch information
riccardo-forina committed Jun 27, 2024
1 parent 0ded689 commit bdd240f
Show file tree
Hide file tree
Showing 7 changed files with 857 additions and 182 deletions.
1 change: 1 addition & 0 deletions podman-desktop-extension/packages/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
"vitest": "^1.6.0"
},
"dependencies": {
"@kubernetes/client-node": "^0.21.0",
"@types/js-yaml": "^4.0.9",
"js-yaml": "^4.1.0",
"semver": "^7.6.2"
Expand Down
221 changes: 192 additions & 29 deletions podman-desktop-extension/packages/backend/src/api-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,14 @@
***********************************************************************/

import { Messages } from '/@shared/src/messages/Messages';
import type { ConsoleCompose, ConsoleConfig, StreamshubConsoleInfo } from '/@shared/src/models/streamshub';
import type {
ConsoleCompose,
ConsoleConfig,
KubernetesCluster,
StreamshubConsoleInfo,
} from '/@shared/src/models/streamshub';
import type { StreamshubApi } from '/@shared/src/StreamshubApi';
import * as podmanDesktopApi from '@podman-desktop/api';
import yaml from 'js-yaml';
import * as fs from 'node:fs/promises';
import { telemetryLogger } from './extension';
import { createAndStartConsole, startConsole, stopAndDeleteConsole, stopConsole } from './utils';
Expand Down Expand Up @@ -79,27 +83,29 @@ export class StreamshubImpl implements StreamshubApi {
consoles[project] = c;
});

(await fs.readdir(storagePath, { withFileTypes: true }))
.filter(f => f.isDirectory())
.forEach(f => {
const project = f.name;
if (!consoles[project]) {
consoles[project] = {
project,
managed: true,
api: {
ports: [],
baseUrl: '',
status: 'exited',
},
ui: {
ports: [],
url: '',
status: 'exited',
},
};
}
});
try {
(await fs.readdir(storagePath, { withFileTypes: true }))
.filter(f => f.isDirectory())
.forEach(f => {
const project = f.name;
if (!consoles[project]) {
consoles[project] = {
project,
managed: true,
api: {
ports: [],
baseUrl: '',
status: 'exited',
},
ui: {
ports: [],
url: '',
status: 'exited',
},
};
}
});
} catch {}
console.log({ containers, consoleContainers, consoles });
} catch (err) {
await podmanDesktopApi.window.showErrorMessage(`Error listing containers: ${err}`);
Expand Down Expand Up @@ -147,12 +153,72 @@ export class StreamshubImpl implements StreamshubApi {
await stopAndDeleteConsole(storagePath, projectName);
}

async getKubernetesClusters() {
const kconfig = podmanDesktopApi.kubernetes.getKubeconfig();
const configContent = await fs.readFile(kconfig.fsPath, 'utf8');
const parsedConfig = yaml.load(configContent) as { clusters: { cluster: { server: string }; name: string }[] };
console.log('getKubernetesClusters', parsedConfig);
return parsedConfig?.clusters.map(c => ({ name: c.name, server: c.cluster.server })) ?? [];
async getKubernetesClusters(): Promise<{ context: string; server: string; clusters: KubernetesCluster[] }> {
console.log('getKubernetesClusters');
try {
const { stdout: context } = await podmanDesktopApi.process.exec('kubectl', ['config', 'current-context']);
console.log('getKubernetesClusters', { context });
const server = context === 'minikube' ? await getServerFromMinikube() : await getServerFromKubectl();
console.log('getKubernetesClusters', { server });
const { stdout: kafkaCRsText } = await podmanDesktopApi.process.exec('kubectl', [
'get',
'kafkas.kafka.strimzi.io',
'--all-namespaces',
'-o',
'json',
]);
console.log('getKubernetesClusters', { kafkaCRsText });
const kafkaCRs = JSON.parse(kafkaCRsText) as {
items: {
metadata: {
name: string;
namespace: string;
};
spec: {
kafka: {
listeners: {
authentication?: {
type: string;
};
tls: true;
name: string;
}[];
};
};
status: {
listeners: {
name: string;
bootstrapServers: string;
}[];
};
}[];
};
const clusters = await Promise.all(
kafkaCRs.items.map<Promise<KubernetesCluster>>(async i => {
const jaasConfigurations = await getNamespaceJaasConfigurations(i.metadata.namespace);
const token = await getToken(i.metadata.namespace);
return {
name: i.metadata.name,
namespace: i.metadata.namespace,
listeners: i.status.listeners.map(l => ({
...l,
...(i.spec.kafka.listeners.find(sl => l.name === sl.name) ?? {}),
})),
jaasConfigurations: jaasConfigurations[i.metadata.name],
token,
};
}),
);
console.log('getKubernetesClusters', { clusters });
return {
context,
server,
clusters,
};
} catch (e) {
console.error(e);
throw e;
}
}

async containerChanges() {
Expand All @@ -170,3 +236,100 @@ export class StreamshubImpl implements StreamshubApi {
});
}
}

async function getServerFromKubectl() {
const { stdout: serverRaw } = await podmanDesktopApi.process.exec('kubectl', [
'config',
'view',
'--minify',
'-o',
"jsonpath='{.clusters[0].cluster.server}'",
]);
return serverRaw.replaceAll("'", '');
}

async function getServerFromMinikube() {
const { stdout: server } = await podmanDesktopApi.process.exec('minikube', ['ip']);
return `https://${server}:8443`;
}

async function getToken(namespace: string): Promise<string | undefined> {
try {
const { stdout: tokenRaw } = await podmanDesktopApi.process.exec('kubectl', [
'describe',
'secret',
'default-token',
'-n',
namespace,
]);

const lines = tokenRaw.split('\n').filter(line => line.startsWith('token'));

// Step 2: cut -f2 -d':'
const fields = lines.map(line => line.split(':')[1]);

// Step 3: tr -d " "
return fields.map(field => field.replace(/\s/g, '')).join('\n');
} catch {
const { stdout: token } = await podmanDesktopApi.process.exec('kubectl', [
'create',
'token',
'console-server',
'-n',
namespace,
`--duration=${365 * 24}h`,
]);
return token;
}
}

async function getNamespaceJaasConfigurations(namespace: string): Promise<Record<string, string[]>> {
const { stdout: usersRaw } = await podmanDesktopApi.process.exec('kubectl', [
'get',
'kafkausers',
'-n',
namespace,
'-o',
'json',
]);
const users = JSON.parse(usersRaw) as {
items: {
metadata: {
labels: {
'strimzi.io/cluster': string;
};
};
status: {
username: string;
secret: string;
};
}[];
};
const usersList = users.items.map(u => u.status.username);
const { stdout: secretsRaw } = await podmanDesktopApi.process.exec('kubectl', [
'get',
'secrets',
'-n',
namespace,
'-o',
'json',
...usersList,
]);
const secrets = JSON.parse(secretsRaw) as {
items: {
data: {
'sasl.jaas.config': string;
};
metadata: {
name: string;
};
}[];
};
const jaasConfigurations: Record<string, string[]> = Object.fromEntries(
users.items.map(u => [
u.metadata.labels['strimzi.io/cluster'],
secrets.items.filter(s => s.metadata.name === u.status.secret).map(s => atob(s.data['sasl.jaas.config'])),
]),
);
return jaasConfigurations;
}
3 changes: 0 additions & 3 deletions podman-desktop-extension/packages/backend/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ export async function activate(extensionContext: ExtensionContext): Promise<void
});
extensionContext.subscriptions.push(panel);


const indexHtmlUri = extensionApi.Uri.joinPath(extensionContext.extensionUri, 'media', 'index.html');
const indexHtmlPath = indexHtmlUri.fsPath;
let indexHtml = await fs.promises.readFile(indexHtmlPath, 'utf8');
Expand Down Expand Up @@ -119,10 +118,8 @@ export async function activate(extensionContext: ExtensionContext): Promise<void
}, 1000);
}),
);

}


function checkVersion(version: string): boolean {
if (!version) {
return false;
Expand Down
Loading

0 comments on commit bdd240f

Please sign in to comment.