diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 83ca032..eaff415 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,14 +1,13 @@ // For format details, see https://aka.ms/devcontainer.json. { "name": "Codefresh Support Package", - "image": "denoland/deno:1.46.3", + "image": "denoland/deno:2.0.3", "onCreateCommand": "apt-get update && apt-get install git zip -y", "customizations": { "vscode": { "settings": { "deno.enable": true, - "deno.lint": true, - "deno.unstable": false + "deno.lint": true }, "extensions": [ "denoland.vscode-deno", diff --git a/CONTRIBUTIONS.md b/CONTRIBUTIONS.md index 60adb25..bb4a328 100644 --- a/CONTRIBUTIONS.md +++ b/CONTRIBUTIONS.md @@ -20,8 +20,9 @@ Pull requests are the best way to propose changes to the codebase. We actively w ## Any contributions you make will be under the MIT Software License -In short, when you submit code changes, your submissions are understood to be under the same [MIT License](http://choosealicense.com/licenses/mit/) that covers -the project. Feel free to contact the maintainers if that's a concern. +In short, when you submit code changes, your submissions are understood to be under the same +[MIT License](http://choosealicense.com/licenses/mit/) that covers the project. Feel free to contact the maintainers if +that's a concern. ## Development diff --git a/README.md b/README.md index 0951cf1..471ea1a 100644 --- a/README.md +++ b/README.md @@ -76,3 +76,10 @@ chmod +x cf-support 1. Go the the [Latest](https://github.com/codefresh-support/codefresh-support-package/releases/latest) release. 1. Download the cf-support_windows_x86_64.zip file 1. Run the `.exe` file via CMD or PowerShell + +## Exit Codes + +- 10 - Failed to get codefresh credentials. Please set the enviroment variables (CF_API_KEY and CF_BASE_URL) or make sure you have a valid codefresh config file. +- 20 - Failed to Create Demo Pipeline / Project or Failed to run Demo Pipeline. +- 30 - Failed to Delete Demo Pipeline / Project +- 40 - Invalid Runtime Type. ex: Selecting On-Prem for a SaaS Account. diff --git a/ci/codefresh.yaml b/ci/codefresh.yaml index 4bae93a..8e9128c 100644 --- a/ci/codefresh.yaml +++ b/ci/codefresh.yaml @@ -1,4 +1,4 @@ -version: "1.0" +version: '1.0' stages: - Clone @@ -20,7 +20,7 @@ steps: title: Compiling stage: Build arguments: - image: denoland/deno:alpine-1.46.3 + image: denoland/deno:alpine-2.0.3 commands: - cf_export VERSION=$(cat VERSION) - deno task compile diff --git a/deno.json b/deno.json index cf49657..e700157 100644 --- a/deno.json +++ b/deno.json @@ -1,23 +1,30 @@ { "fmt": { "indentWidth": 2, - "lineWidth": 160, + "lineWidth": 120, "singleQuote": true, "semiColons": true, - "exclude": [".devcontainer/**"] + "exclude": [ + ".devcontainer/**", + "README.md", + "ci/**" + ] }, "tasks": { "pre-compile": "rm -rf ./bin && mkdir ./bin", - "compile:linux": "deno compile --config=./deno.json --allow-env --allow-read --allow-write --allow-net --unsafely-ignore-certificate-errors --allow-run --output=./bin/cf-support_linux_x86_64 --target=x86_64-unknown-linux-gnu ./src/main.js", - "compile:windows": "deno compile --config=./deno.json --allow-env --allow-read --allow-write --allow-net --unsafely-ignore-certificate-errors --allow-run --output=./bin/cf-support_windows_x86_64 --target=x86_64-pc-windows-msvc ./src/main.js", - "compile:apple": "deno compile --config=./deno.json --allow-env --allow-read --allow-write --allow-net --unsafely-ignore-certificate-errors --allow-run --output=./bin/cf-support_darwin_x86_64 --target=x86_64-apple-darwin ./src/main.js", - "compile:apple_arm64": "deno compile --config=./deno.json --allow-env --allow-read --allow-write --allow-net --unsafely-ignore-certificate-errors --allow-run --output=./bin/cf-support_darwin_arm64 --target=aarch64-apple-darwin ./src/main.js", + "compile:linux": "deno compile --config=./deno.json --allow-env --allow-read --allow-write --allow-net --unsafely-ignore-certificate-errors --allow-run --output=./bin/cf-support_linux_x86_64 --target=x86_64-unknown-linux-gnu ./src/main.ts", + "compile:windows": "deno compile --config=./deno.json --allow-env --allow-read --allow-write --allow-net --unsafely-ignore-certificate-errors --allow-run --output=./bin/cf-support_windows_x86_64 --target=x86_64-pc-windows-msvc ./src/main.ts", + "compile:apple": "deno compile --config=./deno.json --allow-env --allow-read --allow-write --allow-net --unsafely-ignore-certificate-errors --allow-run --output=./bin/cf-support_darwin_x86_64 --target=x86_64-apple-darwin ./src/main.ts", + "compile:apple_arm64": "deno compile --config=./deno.json --allow-env --allow-read --allow-write --allow-net --unsafely-ignore-certificate-errors --allow-run --output=./bin/cf-support_darwin_arm64 --target=aarch64-apple-darwin ./src/main.ts", "compile": "deno task pre-compile && deno task compile:linux && deno task compile:windows && deno task compile:apple && deno task compile:apple_arm64" }, "imports": { - "@cloudydeno/kubernetes-apis": "jsr:@cloudydeno/kubernetes-apis@^0.5.1", + "@cliffy/table": "jsr:@cliffy/table@1.0.0-rc.7", + "@cloudydeno/kubernetes-apis": "jsr:@cloudydeno/kubernetes-apis@^0.5.2", "@cloudydeno/kubernetes-client": "jsr:@cloudydeno/kubernetes-client@^0.7.3", "@fakoua/zip-ts": "jsr:@fakoua/zip-ts@^1.3.1", - "@std/yaml": "jsr:@std/yaml@^1.0.5" + "@std/encoding": "jsr:@std/encoding@^1.0.5", + "@std/yaml": "jsr:@std/yaml@^1.0.5", + "pako": "npm:pako@^2.1.0" } -} +} \ No newline at end of file diff --git a/src/codefresh.js b/src/codefresh.js deleted file mode 100644 index 2533397..0000000 --- a/src/codefresh.js +++ /dev/null @@ -1,105 +0,0 @@ -'use strict'; -import { parse } from '@std/yaml'; - -class Codefresh { - async init() { - this.apiKey = await this.getEnvVarOrConfig('CF_API_KEY', 'token'); - this.apiURL = await this.getEnvVarOrConfig('CF_URL', 'url'); - - this.headers = { - Authorization: this.apiKey, - }; - this.baseURL = this.apiURL + `/api`; - } - - async getEnvVarOrConfig(envVar, configKey) { - if (Deno.env.has(envVar)) { - return Deno.env.get(envVar); - } else { - try { - let cfConfig; - if (Deno.build.os === 'windows') { - cfConfig = parse(await Deno.readTextFile(`${Deno.env.get('USERPROFILE')}/.cfconfig`)); - } else { - cfConfig = parse(await Deno.readTextFile(`${Deno.env.get('HOME')}/.cfconfig`)); - } - - return cfConfig.contexts[cfConfig['current-context']][configKey]; - } catch (error) { - console.error(error); - } - } - } - - async getAllRuntimes() { - try { - const response = await fetch(`${this.baseURL}/runtime-environments`, { - method: 'GET', - headers: this.headers, - }); - const runtimes = await response.json(); - this.runtimes = runtimes; - return runtimes.map((re) => re.metadata.name); - } catch (error) { - console.error(error); - } - } - - async getOnPremAccounts() { - try { - const response = await fetch(`${this.baseURL}/admin/accounts`, { - method: 'GET', - headers: this.headers, - }); - const accounts = await response.json(); - return accounts; - } catch (error) { - console.error(error); - return error; - } - } - - async getOnPremRuntimes() { - try { - const response = await fetch(`${this.baseURL}/admin/runtime-environments`, { - method: 'GET', - headers: this.headers, - }); - const onPremRuntimes = await response.json(); - return onPremRuntimes; - } catch (error) { - console.error(error); - return error; - } - } - - async getOnPremUserTotal() { - try { - const response = await fetch(`${this.baseURL}/admin/users?limit=1&page=1`, { - method: 'GET', - headers: this.headers, - }); - const users = await response.json(); - return users.total; - } catch (error) { - console.error(error); - return error; - } - } - - async getOnPremSystemFF() { - try { - const response = await fetch(`${this.baseURL}/admin/features`, { - method: 'GET', - headers: this.headers, - }); - const onPremSystemFF = await response.json(); - return onPremSystemFF; - } catch (error) { - console.error(error); - return error; - } - } -} - -export { Codefresh }; diff --git a/src/codefresh/codefresh.ts b/src/codefresh/codefresh.ts new file mode 100644 index 0000000..2eb0042 --- /dev/null +++ b/src/codefresh/codefresh.ts @@ -0,0 +1,52 @@ +import { parse } from '../deps.ts'; + +enum ContextKeys { + Token = 'token', + Url = 'url', +} + +interface Context { + [ContextKeys.Token]: string; + [ContextKeys.Url]: string; +} + +interface CodefreshConfig { + contexts: { + [key: string]: Context; + }; + 'current-context': string; +} + +async function readConfigFile() { + const configPath = Deno.build.os === 'windows' + ? `${Deno.env.get('USERPROFILE')}/.cfconfig` + : `${Deno.env.get('HOME')}/.cfconfig`; + const configFileContent = await Deno.readTextFile(configPath); + return parse(configFileContent) as CodefreshConfig; +} + +async function getCodefreshCredentials(envVar: string, configKey: ContextKeys) { + const envValue = Deno.env.get(envVar); + if (envValue) { + return envValue; + } + + try { + const cfConfig = await readConfigFile(); + return cfConfig.contexts[cfConfig['current-context']][configKey]; + } catch (error) { + console.error('Failed to get Codefresh credentials:', error); + console.error( + 'Please set the environment variables (CF_API_KEY and CF_BASE_URL) or make sure you have a valid Codefresh config file.', + ); + Deno.exit(10); + } +} + +export async function autoDetectCodefreshClient() { + const headers = { + Authorization: await getCodefreshCredentials('CF_API_KEY', ContextKeys.Token), + }; + const baseUrl = `${await getCodefreshCredentials('CF_BASE_URL', ContextKeys.Url)}/api`; + return { headers, baseUrl }; +} diff --git a/src/codefresh/gitops.ts b/src/codefresh/gitops.ts new file mode 100644 index 0000000..57aff14 --- /dev/null +++ b/src/codefresh/gitops.ts @@ -0,0 +1,13 @@ +import { fetchAndSaveData, prepareAndCleanup, RuntimeType, selectNamespace } from '../deps.ts'; + +export async function gitopsRuntime() { + try { + const namespace = await selectNamespace(); + console.log(`\nGathering data in "${namespace}" namespace for the GitOps Runtime.`); + await fetchAndSaveData(RuntimeType.gitops, namespace); + console.log('\nData Gathered Successfully.'); + await prepareAndCleanup(); + } catch (error) { + console.error(`Error gathering GitOps runtime data:`, error); + } +} diff --git a/src/codefresh/onprem.ts b/src/codefresh/onprem.ts new file mode 100644 index 0000000..3a5c8f7 --- /dev/null +++ b/src/codefresh/onprem.ts @@ -0,0 +1,64 @@ +import { fetchAndSaveData, prepareAndCleanup, RuntimeType, selectNamespace, writeCodefreshFiles } from '../deps.ts'; + +async function getAllAccounts(config: { headers: { Authorization: string }; baseUrl: string }) { + const response = await fetch(`${config.baseUrl}/admin/accounts`, { + method: 'GET', + headers: config.headers, + }); + const accounts = await response.json(); + await writeCodefreshFiles(accounts, 'onPrem-accounts'); +} + +async function getAllRuntimes(config: { headers: { Authorization: string }; baseUrl: string }) { + const response = await fetch(`${config.baseUrl}/admin/runtime-environments`, { + method: 'GET', + headers: config.headers, + }); + const onPremRuntimes = await response.json(); + await writeCodefreshFiles(onPremRuntimes, 'onPrem-runtimes'); +} + +async function getTotalUsers(config: { headers: { Authorization: string }; baseUrl: string }) { + const response = await fetch(`${config.baseUrl}/admin/user?limit=1&page=1`, { + method: 'GET', + headers: config.headers, + }); + const users = await response.json(); + await writeCodefreshFiles({ total: users.total }, 'onPrem-totalUsers'); +} + +async function getSystemFeatureFlags(config: { headers: { Authorization: string }; baseUrl: string }) { + const response = await fetch(`${config.baseUrl}/admin/features`, { + method: 'GET', + headers: config.headers, + }); + const onPremSystemFF = await response.json(); + await writeCodefreshFiles(onPremSystemFF, 'onPrem-systemFeatureFlags'); +} + +export async function onPrem(config: { headers: { Authorization: string }; baseUrl: string }) { + if (config.baseUrl === 'https://g.codefresh.io/api') { + console.error( + `\nCannot gather On-Prem data for Codefresh SaaS. Please select either ${RuntimeType.pipelines} or ${RuntimeType.gitops}.`, + ); + console.error( + 'If you need to gather data for Codefresh On-Prem, please update your ./cfconfig conext (or Envs) to point to an On-Prem instance.', + ); + Deno.exit(40); + } + try { + const namespace = await selectNamespace(); + console.log(`\nGathering data in "${namespace}" namespace for Codefresh On-Prem.`); + await fetchAndSaveData(RuntimeType.onprem, namespace); + await Promise.all([ + getAllAccounts(config), + getAllRuntimes(config), + getTotalUsers(config), + getSystemFeatureFlags(config), + ]); + console.log('\nData Gathered Successfully.'); + await prepareAndCleanup(); + } catch (error) { + console.error(`Error gathering On-Prem data:`, error); + } +} diff --git a/src/codefresh/pipelines.ts b/src/codefresh/pipelines.ts new file mode 100644 index 0000000..d51f717 --- /dev/null +++ b/src/codefresh/pipelines.ts @@ -0,0 +1,170 @@ +import { fetchAndSaveData, prepareAndCleanup, RuntimeType, writeCodefreshFiles } from '../deps.ts'; + +async function getRuntimes(config: { headers: { Authorization: string }; baseUrl: string }) { + const response = await fetch(`${config.baseUrl}/runtime-environments`, { + method: 'GET', + headers: config.headers, + }); + const runtimes = await response.json(); + return runtimes; +} + +async function runTestPipeline( + config: { headers: { Authorization: string }; baseUrl: string }, + runtimeName: string, +) { + let selection = String( + prompt( + '\nTo troubleshoot, we would like to create a Demo Pipeline and run it.\nAfter creating this pipeline we will clean up the resources\n\nWould you like to proceed with the demo pipeline? (y/n): ', + ), + ); + while (selection !== 'y' && selection !== 'n') { + console.log('Invalid selection. Please enter "y" or "n".'); + selection = String(prompt('\nWould you like to proceed with the demo pipeline? (y/n): ')); + } + if (selection === 'n') { + return; + } + + console.log(`\nCreating a demo pipeline to test the ${runtimeName} runtime.`); + + const project = JSON.stringify({ + projectName: 'codefresh-support-package', + }); + + const pipeline = JSON.stringify({ + version: '1.0', + kind: 'pipeline', + metadata: { + name: 'codefresh-support-package/TEST-PIPELINE-FOR-SUPPORT', + project: 'codefresh-support-package', + originalYamlString: + 'version: "1.0"\n\nsteps:\n\n test:\n title: Running test\n type: freestyle\n arguments:\n image: alpine\n commands:\n - echo "Hello Test"', + }, + spec: { + concurrency: 1, + runtimeEnvironment: { + name: runtimeName, + }, + }, + }); + + const createProjectResponse = await fetch(`${config.baseUrl}/projects`, { + method: 'POST', + headers: { + ...config.headers, + 'Content-Type': 'application/json', + }, + body: project, + }); + + const projectStatus = await createProjectResponse.json(); + + if (!createProjectResponse.ok) { + console.error('Error creating project:', createProjectResponse.statusText); + console.error(projectStatus); + Deno.exit(20); + } + + const createPipelineResponse = await fetch(`${config.baseUrl}/pipelines`, { + method: 'POST', + headers: { + ...config.headers, + 'Content-Type': 'application/json', + }, + body: pipeline, + }); + + const pipelineStatus = await createPipelineResponse.json(); + + if (!createPipelineResponse.ok) { + console.error('Error creating pipeline:', createPipelineResponse.statusText); + console.error(pipelineStatus); + Deno.exit(20); + } + + const runPipelineResponse = await fetch(`${config.baseUrl}/pipelines/run/${pipelineStatus.metadata.id}`, { + method: 'POST', + headers: { + ...config.headers, + 'Content-Type': 'application/json', + }, + }); + + const runPipelineStatus = await runPipelineResponse.json(); + + if (!runPipelineResponse.ok) { + console.error('Error running pipeline:', runPipelineResponse.statusText); + Deno.exit(20); + } + + console.log(`Demo pipeline created and running build with id of ${runPipelineStatus}.`); + + return { pipelineID: pipelineStatus.metadata.id, projectID: projectStatus.id }; +} + +async function deleteTestPipeline( + config: { headers: { Authorization: string }; baseUrl: string }, + pipelineID: string, + projectID: string, +) { + const deletePipelineResponse = await fetch(`${config.baseUrl}/pipelines/${pipelineID}`, { + method: 'DELETE', + headers: config.headers, + }); + + if (!deletePipelineResponse.ok) { + console.error('Error deleting pipeline:', await deletePipelineResponse.text()); + Deno.exit(30); + } + + const deleteProjectResponse = await fetch(`${config.baseUrl}/projects/${projectID}`, { + method: 'DELETE', + headers: config.headers, + }); + + if (!deleteProjectResponse.ok) { + console.error('Error deleting project:', await deleteProjectResponse.text()); + Deno.exit(30); + } + + console.log('Demo pipeline and project deleted successfully.'); +} + +export async function pipelinesRuntime(config: { headers: { Authorization: string }; baseUrl: string }) { + try { + const runtimes = await getRuntimes(config); + console.log(''); + runtimes.forEach((re: any, index: number) => { + console.log(`${index + 1}. ${re.metadata.name}`); + }); + + let selection = Number(prompt('\nWhich Pipelines Runtime Are We Working With? (Number): ')); + while (isNaN(selection) || selection < 1 || selection > runtimes.length) { + console.log('Invalid selection. Please enter a number corresponding to one of the listed runtimes.'); + selection = Number(prompt('\nWhich Pipelines Runtime Are We Working With? (Number): ')); + } + + const reSpec = runtimes[selection - 1]; + const namespace = reSpec.runtimeScheduler.cluster.namespace; + + const pipelineExecutionOutput = await runTestPipeline(config, reSpec.metadata.name); + + console.log(`\nGathering Data For ${reSpec.metadata.name} in the "${namespace}" namespace.`); + + // Wait 15 seconds to allow the pipeline to run + await new Promise((resolve) => setTimeout(resolve, 15000)); + + await fetchAndSaveData(RuntimeType.pipelines, namespace); + await writeCodefreshFiles(reSpec, 'pipelines-runtime-spec'); + console.log('Data Gathered Successfully.'); + + if (pipelineExecutionOutput) { + await deleteTestPipeline(config, pipelineExecutionOutput?.pipelineID, pipelineExecutionOutput?.projectID); + } + + await prepareAndCleanup(); + } catch (error) { + console.error(`Error gathering Pipelines Runtime data:`, error); + } +} diff --git a/src/codefresh/runtime-type.ts b/src/codefresh/runtime-type.ts new file mode 100644 index 0000000..292be98 --- /dev/null +++ b/src/codefresh/runtime-type.ts @@ -0,0 +1,19 @@ +export enum RuntimeType { + pipelines = 'Pipelines Runtime', + gitops = 'GitOps Runtime', + onprem = 'On-Prem', +} + +export function getUserRuntimeSelection(runtimes: string[]) { + runtimes.forEach((runtimeName, index) => { + console.log(`${index + 1}. ${runtimeName}`); + }); + + let selection = Number(prompt('\nWhich Type Of Runtime Are We Using? (Number):')); + while (isNaN(selection) || selection < 1 || selection > runtimes.length) { + console.log('Invalid selection. Please enter a number corresponding to one of the listed options.'); + selection = Number(prompt('\nWhich Type Of Runtime Are We Using? (Number):')); + } + + return runtimes[selection - 1]; +} diff --git a/src/deps.ts b/src/deps.ts new file mode 100644 index 0000000..8fc452d --- /dev/null +++ b/src/deps.ts @@ -0,0 +1,37 @@ +export { autoDetectClient } from '@cloudydeno/kubernetes-client'; +export { AppsV1Api } from '@cloudydeno/kubernetes-apis/apps/v1'; +export { BatchV1Api } from '@cloudydeno/kubernetes-apis/batch/v1'; +export { CoreV1Api } from '@cloudydeno/kubernetes-apis/core/v1'; +export type { + EventList, + PersistentVolumeClaimList, + PersistentVolumeList, + PodList, + SecretList, +} from '@cloudydeno/kubernetes-apis/core/v1'; +export { StorageV1Api } from '@cloudydeno/kubernetes-apis/storage.k8s.io/v1'; +export { ArgoprojIoV1alpha1Api } from '@cloudydeno/kubernetes-apis/argoproj.io/v1alpha1'; +export { ungzip } from 'pako'; +export { compress } from '@fakoua/zip-ts'; +export { parse, stringify as toYaml } from '@std/yaml'; +export { decodeBase64 } from '@std/encoding'; +export { Table } from '@cliffy/table'; + +// Internal dependencies +export { getUserRuntimeSelection, RuntimeType } from './codefresh/runtime-type.ts'; +export { autoDetectCodefreshClient } from './codefresh/codefresh.ts'; +export { + describeK8sResources, + getFormattedEvents, + getHelmReleases, + getK8sLogs, + getK8sResources, + getPodList, + getPVCList, + getPVList, + selectNamespace, +} from './kubernetes/kubernetes.ts'; +export { fetchAndSaveData, prepareAndCleanup, writeCodefreshFiles } from './utils/file-io.ts'; +export { gitopsRuntime } from './codefresh/gitops.ts'; +export { onPrem } from './codefresh/onprem.ts'; +export { pipelinesRuntime } from './codefresh/pipelines.ts'; diff --git a/src/kubernetes/kubernetes.ts b/src/kubernetes/kubernetes.ts new file mode 100644 index 0000000..7bfac5c --- /dev/null +++ b/src/kubernetes/kubernetes.ts @@ -0,0 +1,302 @@ +import type { EventList, PersistentVolumeClaimList, PersistentVolumeList, PodList, SecretList } from '../deps.ts'; + +import { + AppsV1Api, + ArgoprojIoV1alpha1Api, + autoDetectClient, + BatchV1Api, + CoreV1Api, + decodeBase64, + RuntimeType, + StorageV1Api, + Table, + ungzip, +} from '../deps.ts'; + +const kubeConfig = await autoDetectClient(); +const appsApi = new AppsV1Api(kubeConfig); +const coreApi = new CoreV1Api(kubeConfig); +const storageApi = new StorageV1Api(kubeConfig); +const batchApi = new BatchV1Api(kubeConfig); +const argoProj = new ArgoprojIoV1alpha1Api(kubeConfig); + +export async function selectNamespace() { + const namespaceList = await coreApi.getNamespaceList(); + console.log(''); + namespaceList.items.forEach((namespace, index: number) => { + console.log(`${index + 1}. ${namespace.metadata?.name}`); + }); + + let selection = Number(prompt('\nWhich Namespace Is Codefresh Installed In? (Number): ')); + while (isNaN(selection) || selection < 1 || selection > namespaceList.items.length) { + console.log('Invalid selection. Please enter a number corresponding to one of the listed namespaces.'); + selection = Number(prompt('\nWhich Namespace Is Codefresh Installed In? (Number): ')); + } + + const namespace = namespaceList.items[selection - 1].metadata?.name; + + if (!namespace) { + throw new Error('Selected namespace is invalid.'); + } + + return namespace; +} + +export function getK8sResources(runtimeType: RuntimeType, namespace: string) { + switch (runtimeType) { + case RuntimeType.pipelines: + return { + 'CronJobs': () => batchApi.namespace(namespace).getCronJobList(), + 'Jobs': () => batchApi.namespace(namespace).getJobList(), + 'Deployments': () => appsApi.namespace(namespace).getDeploymentList(), + 'Daemonsets': () => appsApi.namespace(namespace).getDaemonSetList(), + 'Nodes': () => coreApi.getNodeList(), + 'Volumes': () => coreApi.getPersistentVolumeList({ labelSelector: 'io.codefresh.accountName' }), + 'Volumeclaims': () => + coreApi.namespace(namespace).getPersistentVolumeClaimList({ labelSelector: 'io.codefresh.accountName' }), + 'Configmaps': () => + coreApi.namespace(namespace).getConfigMapList({ labelSelector: 'app.kubernetes.io/name=cf-runtime' }), + 'Services': () => coreApi.namespace(namespace).getServiceList(), + 'Pods': () => coreApi.namespace(namespace).getPodList(), + 'Storageclass': () => storageApi.getStorageClassList(), + 'Events': () => coreApi.namespace(namespace).getEventList(), + 'HelmReleases': () => coreApi.namespace(namespace).getSecretList({ labelSelector: 'owner=helm' }), + }; + case RuntimeType.gitops: + return { + 'Apps': () => argoProj.namespace(namespace).getApplicationList(), + 'AppSets': () => argoProj.namespace(namespace).getApplicationSetList(), + 'CronJobs': () => batchApi.namespace(namespace).getCronJobList(), + 'Jobs': () => batchApi.namespace(namespace).getJobList(), + 'Deployments': () => appsApi.namespace(namespace).getDeploymentList(), + 'Daemonsets': () => appsApi.namespace(namespace).getDaemonSetList(), + 'Statefulsets': () => appsApi.namespace(namespace).getStatefulSetList(), + 'Nodes': () => coreApi.getNodeList(), + 'Configmaps': () => coreApi.namespace(namespace).getConfigMapList(), + 'Services': () => coreApi.namespace(namespace).getServiceList(), + 'Pods': () => coreApi.namespace(namespace).getPodList(), + 'Events': () => coreApi.namespace(namespace).getEventList(), + 'HelmReleases': () => coreApi.namespace(namespace).getSecretList({ labelSelector: 'owner=helm' }), + }; + case RuntimeType.onprem: + return { + 'CronJobs': () => batchApi.namespace(namespace).getCronJobList(), + 'Jobs': () => batchApi.namespace(namespace).getJobList(), + 'Deployments': () => appsApi.namespace(namespace).getDeploymentList(), + 'Daemonsets': () => appsApi.namespace(namespace).getDaemonSetList(), + 'Nodes': () => coreApi.getNodeList(), + 'Volumes': () => coreApi.getPersistentVolumeList({ labelSelector: 'io.codefresh.accountName' }), + 'Volumeclaims': () => + coreApi.namespace(namespace).getPersistentVolumeClaimList({ labelSelector: 'io.codefresh.accountName' }), + 'Configmaps': () => coreApi.namespace(namespace).getConfigMapList(), + 'Services': () => coreApi.namespace(namespace).getServiceList(), + 'Pods': () => coreApi.namespace(namespace).getPodList(), + 'Storageclass': () => storageApi.getStorageClassList(), + 'Events': () => coreApi.namespace(namespace).getEventList(), + 'HelmReleases': () => coreApi.namespace(namespace).getSecretList({ labelSelector: 'owner=helm' }), + }; + default: + console.error('Invalid runtime type selected'); + return; + } +} + +function calculateAge(creationTimestamp: Date) { + const now = new Date(); + const diffMs = now.getTime() - creationTimestamp.getTime(); + const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); + const diffHours = Math.floor((diffMs % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); + const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60)); + return `${diffDays}d ${diffHours}h ${diffMinutes}m`; +} + +export function getFormattedEvents(events: EventList) { + // Sort the events by .metadata.creationTimestamp + const sortedEvents = events.items.sort((a, b) => { + const dateA = a.metadata.creationTimestamp ? new Date(a.metadata.creationTimestamp).getTime() : 0; + const dateB = b.metadata.creationTimestamp ? new Date(b.metadata.creationTimestamp).getTime() : 0; + return dateA - dateB; + }); + + const formattedEvents = sortedEvents.length > 0 + ? sortedEvents.map((event) => { + const lastSeen = event.lastTimestamp ? calculateAge(event.lastTimestamp) : 'N/A'; + const type = event.type ?? 'N/A'; + const reason = event.reason ?? 'N/A'; + const kind = event.involvedObject.kind ?? 'N/A'; + const name = event.involvedObject.name ?? 'N/A'; + const message = event.message ?? 'N/A'; + return { + lastSeen, + type, + reason, + kind, + name, + message, + }; + }) + : [{ + lastSeen: 'N/A', + type: 'N/A', + reason: 'N/A', + kind: 'N/A', + name: 'N/A', + message: 'N/A', + }]; + + const table = new Table(); + table.fromJson(formattedEvents); + return table.toString(); +} + +export function getHelmReleases(secrets: SecretList) { + const helmReleases = secrets.items.map((secret) => { + const releaseData = secret.data?.release; + if (!releaseData) { + throw new Error('Release data is undefined'); + } + const firstDecodedData = decodeBase64(releaseData); + const secondDecodedData = decodeBase64(new TextDecoder().decode(firstDecodedData)); + const extractedData = JSON.parse(ungzip(secondDecodedData, { to: 'string' })); + + const helmInfo = { + 'name': extractedData.name, + 'namespace': extractedData.namespace, + 'revision': extractedData.version, + 'updated': extractedData.info.last_deployed, + 'status': extractedData.info.status, + 'chart': `${extractedData.chart.metadata.name}-${extractedData.chart.metadata.version}`, + 'appVersion': extractedData.chart.metadata.appVersion, + }; + return helmInfo; + }); + + return helmReleases; +} + +// TODO: convert using the kubernetes sdk + +export async function describeK8sResources(resourceType: string, namespace: string, name: string) { + const describe = new Deno.Command('kubectl', { + args: ['describe', resourceType.toLowerCase(), '-n', namespace, name], + }); + + return new TextDecoder().decode((await describe.output()).stdout); +} + +export async function getK8sLogs(namespace: string, podName: string, containerName: string) { + try { + const logs = await coreApi.namespace(namespace).getPodLog(podName, { + container: containerName, + timestamps: true, + }); + return logs; + } catch (error) { + return (error as any).message; + } +} + +export function getPodList(pods: PodList) { + const podList = pods.items.length > 0 + ? pods.items.map((pod) => { + const name = pod.metadata?.name ?? 'N/A'; + const ready = `${pod.status?.containerStatuses?.filter((cs) => cs.ready).length ?? 0}/${ + pod.status?.containerStatuses?.length ?? 0 + }`; + const status = pod.status?.phase ?? 'Unknown'; + const restarts = pod.status?.containerStatuses?.reduce((acc, cur) => acc + (cur.restartCount ?? 0), 0) ?? 0; + const age = pod.metadata?.creationTimestamp ? calculateAge(pod.metadata.creationTimestamp) : 'N/A'; + return { + name, + ready, + status, + restarts, + age, + }; + }) + : [{ + name: 'N/A', + ready: 'N/A', + status: 'N/A', + restarts: 'N/A', + age: 'N/A', + }]; + const table = new Table(); + table.fromJson(podList); + return table.toString(); +} + +export function getPVCList(Volumeclaims: PersistentVolumeClaimList) { + const formattedPVC = Volumeclaims.items.length > 0 + ? Volumeclaims.items.map((pvc) => { + const name = pvc.metadata?.name ?? 'N/A'; + const status = pvc.status?.phase ?? 'Unknown'; + const volume = pvc.spec?.volumeName ?? 'N/A'; + const capacity = `${pvc.spec?.resources?.requests?.storage?.number ?? 'N/A'} ${ + pvc.spec?.resources?.requests?.storage.suffix ?? 'N/A' + }`; + const accessModes = pvc.spec?.accessModes?.join(', ') ?? 'N/A'; + const storageClass = pvc.spec?.storageClassName ?? 'N/A'; + const age = pvc.metadata?.creationTimestamp ? calculateAge(pvc.metadata.creationTimestamp) : 'N/A'; + return { + name, + status, + volume, + capacity, + accessModes, + storageClass, + age, + }; + }) + : [{ + name: 'N/A', + status: 'N/A', + volume: 'N/A', + capacity: 'N/A', + accessModes: 'N/A', + storageClass: 'N/A', + age: 'N/A', + }]; + + const table = new Table(); + table.fromJson(formattedPVC); + return table.toString(); +} + +export function getPVList(Volumes: PersistentVolumeList) { + const formattedPV = Volumes.items.length > 0 + ? Volumes.items.map((pv) => { + const name = pv.metadata?.name ?? 'N/A'; + const capacity = `${pv.spec?.capacity?.storage?.number ?? 'N/A'} ${pv.spec?.capacity?.storage.suffix ?? 'N/A'}`; + const accessModes = pv.spec?.accessModes?.join(', ') ?? 'N/A'; + const reclaimPolicy = pv.spec?.persistentVolumeReclaimPolicy ?? 'N/A'; + const status = pv.status?.phase ?? 'Unknown'; + const claim = `${pv.spec?.claimRef?.namespace ?? 'N/A'}/${pv.spec?.claimRef?.name ?? 'N/A'}`; + const storageClass = pv.spec?.storageClassName ?? 'N/A'; + const age = pv.metadata?.creationTimestamp ? calculateAge(pv.metadata.creationTimestamp) : 'N/A'; + return { + name, + capacity, + accessModes, + reclaimPolicy, + status, + claim, + storageClass, + age, + }; + }) + : [{ + name: 'N/A', + capacity: 'N/A', + accessModes: 'N/A', + reclaimPolicy: 'N/A', + status: 'N/A', + claim: 'N/A', + storageClass: 'N/A', + age: 'N/A', + }]; + + const table = new Table(); + table.fromJson(formattedPV); + return table.toString(); +} diff --git a/src/main.js b/src/main.js deleted file mode 100644 index 604f7cb..0000000 --- a/src/main.js +++ /dev/null @@ -1,299 +0,0 @@ -'use strict'; - -import { Codefresh } from './codefresh.js'; -import { autoDetectClient } from '@cloudydeno/kubernetes-client'; -import { AppsV1Api } from '@cloudydeno/kubernetes-apis/apps/v1'; -import { BatchV1Api } from '@cloudydeno/kubernetes-apis/batch/v1'; -import { CoreV1Api } from '@cloudydeno/kubernetes-apis/core/v1'; -import { StorageV1Api } from '@cloudydeno/kubernetes-apis/storage.k8s.io/v1'; -import { ArgoprojIoV1alpha1Api } from '@cloudydeno/kubernetes-apis/argoproj.io/v1alpha1'; - -import { compress } from '@fakoua/zip-ts'; -import { stringify as toYaml } from '@std/yaml'; - -console.log('Initializing \n'); -const kubeConfig = await autoDetectClient(); -const appsApi = new AppsV1Api(kubeConfig); -const coreApi = new CoreV1Api(kubeConfig); -const storageApi = new StorageV1Api(kubeConfig); -const batchApi = new BatchV1Api(kubeConfig); -const argoProj = new ArgoprojIoV1alpha1Api(kubeConfig); -const timestamp = new Date().getTime(); -const dirPath = `./codefresh-support-${timestamp}`; - -function selectRuntimeType() { - const reTypes = ['Pipelines Runtime', 'GitOps Runtime', 'On-Prem']; - reTypes.forEach((reType, index) => { - console.log(`${index + 1}. ${reType}`); - }); - - let typeSelected = Number(prompt('\nWhich Type Of Runtime Are We Using? (Number):')); - while (isNaN(typeSelected) || typeSelected < 1 || typeSelected > reTypes.length) { - console.log('Invalid selection. Please enter a number corresponding to one of the listed runtime types.'); - typeSelected = Number(prompt('\nWhich Type Of Runtime Are We Using? (Number):')); - } - - return reTypes[typeSelected - 1]; -} - -async function saveItems(resources, dir) { - try { - await Deno.mkdir(`${dirPath}/${dir}/`, { recursive: true }); - const writePromises = resources.map(async (item) => { - const filePath = `${dirPath}/${dir}/${item.metadata.name}_get.yaml`; - const fileContent = toYaml(item, { skipInvalid: true }); - await Deno.writeTextFile(filePath, fileContent); - }); - await Promise.all(writePromises); - } catch (error) { - console.error(`Error saving items to ${dir}:`, error); - } -} - -async function describeItems(dir, namespace, name) { - try { - const describe = new Deno.Command('kubectl', { args: ['describe', dir.toLowerCase(), '-n', namespace, name] }); - const output = await describe.output(); - await Deno.writeTextFile(`${dirPath}/${dir}/${name}_describe.yaml`, new TextDecoder().decode(output.stdout)); - } catch (error) { - console.error(`Failed to describe ${name}:`, error); - } -} - -async function saveEvents(namespace) { - try { - const events = new Deno.Command('kubectl', { args: ['get', 'events', '-n', namespace, '--sort-by=.metadata.creationTimestamp'] }); - const output = await events.output(); - await Deno.writeTextFile(`${dirPath}/Events.txt`, new TextDecoder().decode(output.stdout)); - } catch (error) { - console.error(`Error saving events:`, error); - } -} - -async function saveHelmReleases(type, namespace) { - try { - const helmList = new Deno.Command('helm', { args: ['list', '-n', namespace, '-o', 'json'] }); - const output = await helmList.output(); - const helmReleases = JSON.parse(new TextDecoder().decode(output.stdout)); - await Deno.writeTextFile(`${dirPath}/${type}_helmReleases.yaml`, toYaml(helmReleases, { skipInvalid: true })); - } catch (error) { - console.error(`Error saving Helm releases for ${type}:`, error); - } -} - -function dataFetchers(type, namespace) { - switch (type) { - case 'Pipelines Runtime': - return { - 'Cron': () => batchApi.namespace(namespace).getCronJobList(), - 'Jobs': () => batchApi.namespace(namespace).getJobList(), - 'Deployments': () => appsApi.namespace(namespace).getDeploymentList(), - 'Daemonsets': () => appsApi.namespace(namespace).getDaemonSetList(), - 'Nodes': () => coreApi.getNodeList(), - 'Volumes': () => coreApi.getPersistentVolumeList({ labelSelector: 'io.codefresh.accountName' }), - 'Volumeclaims': () => coreApi.namespace(namespace).getPersistentVolumeClaimList({ labelSelector: 'io.codefresh.accountName' }), - 'Configmaps': () => coreApi.namespace(namespace).getConfigMapList({ labelSelector: 'app.kubernetes.io/name=cf-runtime' }), - 'Services': () => coreApi.namespace(namespace).getServiceList(), - 'Pods': () => coreApi.namespace(namespace).getPodList(), - 'Storageclass': () => storageApi.getStorageClassList(), - }; - case 'GitOps Runtime': - return { - 'Argo-Apps': () => argoProj.namespace(namespace).getApplicationList(), - 'Argo-AppSets': () => argoProj.namespace(namespace).getApplicationSetList(), - 'Cron': () => batchApi.namespace(namespace).getCronJobList(), - 'Jobs': () => batchApi.namespace(namespace).getJobList(), - 'Deployments': () => appsApi.namespace(namespace).getDeploymentList(), - 'Daemonsets': () => appsApi.namespace(namespace).getDaemonSetList(), - 'Statefulsets': () => appsApi.namespace(namespace).getStatefulSetList(), - 'Nodes': () => coreApi.getNodeList(), - 'Configmaps': () => coreApi.namespace(namespace).getConfigMapList(), - 'Services': () => coreApi.namespace(namespace).getServiceList(), - 'Pods': () => coreApi.namespace(namespace).getPodList(), - }; - case 'On-Prem': - return { - 'Cron': () => batchApi.namespace(namespace).getCronJobList(), - 'Jobs': () => batchApi.namespace(namespace).getJobList(), - 'Deployments': () => appsApi.namespace(namespace).getDeploymentList(), - 'Daemonsets': () => appsApi.namespace(namespace).getDaemonSetList(), - 'Nodes': () => coreApi.getNodeList(), - 'Volumes': () => coreApi.getPersistentVolumeList({ labelSelector: 'io.codefresh.accountName' }), - 'Volumeclaims': () => coreApi.namespace(namespace).getPersistentVolumeClaimList({ labelSelector: 'io.codefresh.accountName' }), - 'Configmaps': () => coreApi.namespace(namespace).getConfigMapList(), - 'Services': () => coreApi.namespace(namespace).getServiceList(), - 'Pods': () => coreApi.namespace(namespace).getPodList(), - 'Storageclass': () => storageApi.getStorageClassList(), - }; - default: - console.error('Invalid runtime type selected'); - return; - } -} - -async function fetchAndSaveData(type, namespace) { - for (const [dir, fetcher] of Object.entries(dataFetchers(type, namespace))) { - const resources = await fetcher(); - - await saveItems(resources.items, dir); - - if (dir === 'Pods') { - await Promise.all(resources.items.map(async (item) => { - const podName = item.metadata.name; - const containers = item.spec.containers; - - await Promise.all(containers.map(async (container) => { - let log; - try { - log = await coreApi.namespace(namespace).getPodLog(podName, { - container: container.name, - timestamps: true, - }); - } catch (error) { - console.error(`Failed to get logs for container ${container.name} in pod ${podName}:`, error); - log = error.toString(); - } - const logFileName = `${dirPath}/${dir}/${podName}_${container.name}_log.log`; - await Deno.writeTextFile(logFileName, log); - })); - - await describeItems(dir, namespace, podName); - })); - } - - if (dir === 'Nodes') { - await Promise.all(resources.items.map(async (item) => { - await describeItems(dir, namespace, item.metadata.name); - })); - } - } - await saveHelmReleases(type, namespace); - await saveEvents(namespace); - const listPods = new Deno.Command('kubectl', { args: ['get', 'pods', '-n', namespace] }); - const output = await listPods.output(); - await Deno.writeTextFile(`${dirPath}/ListPods.txt`, new TextDecoder().decode(output.stdout)); -} - -async function gatherPipelines() { - try { - const cf = new Codefresh(); - await cf.init(); - const reNames = await cf.getAllRuntimes(); - console.log(''); - reNames.forEach((re, index) => { - console.log(`${index + 1}. ${re}`); - }); - - let selection = Number(prompt('\nWhich Pipelines Runtime Are We Working With? (Number): ')); - while (isNaN(selection) || selection < 1 || selection > reNames.length) { - console.log('Invalid selection. Please enter a number corresponding to one of the listed runtimes.'); - selection = Number(prompt('\nWhich Pipelines Runtime Are We Working With? (Number): ')); - } - - const reSpec = cf.runtimes[selection - 1]; - const namespace = reSpec.runtimeScheduler.cluster.namespace; - - console.log(`\nGathering Data For ${reSpec.metadata.name}.`); - - await fetchAndSaveData('Pipelines Runtime', namespace); - - await Deno.writeTextFile(`${dirPath}/runtimeSpec.yaml`, toYaml(reSpec, { skipInvalid: true })); - } catch (error) { - console.error(`Error gathering Pipelines Runtime data:`, error); - } -} - -async function gatherGitOps() { - try { - const namespaceList = await coreApi.getNamespaceList(); - console.log(''); - namespaceList.items.forEach((ns, index) => { - console.log(`${index + 1}. ${ns.metadata.name}`); - }); - - let selection = Number(prompt('\nWhich Namespace Is The GitOps Runtime Installed In? (Number): ')); - while (isNaN(selection) || selection < 1 || selection > namespaceList.items.length) { - console.log('Invalid selection. Please enter a number corresponding to one of the listed namespaces.'); - selection = Number(prompt('\nWhich Namespace Is The GitOps Runtime Installed In? (Number): ')); - } - - const namespace = namespaceList.items[selection - 1].metadata.name; - - console.log(`\nGathering Data In ${namespace} For The GitOps Runtime.`); - - await fetchAndSaveData('GitOps Runtime', namespace); - } catch (error) { - console.error(`Error gathering GitOps runtime data:`, error); - } -} - -async function gatherOnPrem() { - try { - const cf = new Codefresh(); - await cf.init(); - if (cf.apiURL === 'https://g.codefresh.io') { - console.error(`The API URL ( ${cf.apiURL} ) is not an On Prem instance. Please use Pipelines Runtime or GitOps Runtime.`); - Deno.exit(1); - } - const accounts = await cf.getOnPremAccounts(); - const runtimes = await cf.getOnPremRuntimes(); - const userTotal = await cf.getOnPremUserTotal(); - const systemFF = await cf.getOnPremSystemFF(); - - const namespaceList = await coreApi.getNamespaceList(); - console.log(''); - namespaceList.items.forEach((ns, index) => { - console.log(`${index + 1}. ${ns.metadata.name}`); - }); - - let selection = Number(prompt('\nWhich Namespace Is Codefresh OnPrem Installed In? (Number): ')); - while (isNaN(selection) || selection < 1 || selection > namespaceList.items.length) { - console.log('Invalid selection. Please enter a number corresponding to one of the listed namespaces.'); - selection = Number(prompt('\nWhich Namespace Is Codefresh OnPrem Installed In? (Number): ')); - } - - const namespace = namespaceList.items[selection - 1].metadata.name; - - console.log(`\nGathering Data For On Prem.`); - - await fetchAndSaveData('On-Prem', namespace); - - await Deno.writeTextFile(`${dirPath}/onPremAccounts.yaml`, toYaml(accounts, { skipInvalid: true })); - await Deno.writeTextFile(`${dirPath}/onPremRuntimes.yaml`, toYaml(runtimes, { skipInvalid: true })); - await Deno.writeTextFile(`${dirPath}/onPremUserTotal.txt`, userTotal.toString()); - await Deno.writeTextFile(`${dirPath}/onPremSystemFF.yaml`, toYaml(systemFF, { skipInvalid: true })); - } catch (error) { - console.error(`Error gathering On Prem data:`, error); - } -} - -async function main() { - try { - const runtimeType = selectRuntimeType(); - - switch (runtimeType) { - case 'Pipelines Runtime': - await gatherPipelines(); - break; - case 'GitOps Runtime': - await gatherGitOps(); - break; - case 'On-Prem': - await gatherOnPrem(); - break; - } - - console.log(`\nSaving data to ./codefresh-support-package-${timestamp}.zip`); - await compress(dirPath, `./codefresh-support-package-${timestamp}.zip`, { overwrite: true }); - - console.log('\nCleaning up temp directory'); - await Deno.remove(dirPath, { recursive: true }); - - console.log(`\nPlease attach ./codefresh-support-package-${timestamp}.zip to your support ticket.`); - console.log('Before attaching, verify the contents and remove any sensitive information.'); - } catch (error) { - console.error(`Error:`, error); - } -} - -await main(); diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..20dfa9b --- /dev/null +++ b/src/main.ts @@ -0,0 +1,32 @@ +import { + autoDetectCodefreshClient, + getUserRuntimeSelection, + gitopsRuntime, + onPrem, + pipelinesRuntime, + RuntimeType, +} from './deps.ts'; + +async function main() { + try { + const runtimeTypes = Object.values(RuntimeType); + const runtimeSelected = getUserRuntimeSelection(runtimeTypes); + const cfConfig = await autoDetectCodefreshClient(); + + switch (runtimeSelected) { + case RuntimeType.pipelines: + await pipelinesRuntime(cfConfig); + break; + case RuntimeType.gitops: + await gitopsRuntime(); + break; + case RuntimeType.onprem: + await onPrem(cfConfig); + break; + } + } catch (error) { + console.error(`Error:`, error); + } +} + +await main(); diff --git a/src/utils/file-io.ts b/src/utils/file-io.ts new file mode 100644 index 0000000..8d0b5a0 --- /dev/null +++ b/src/utils/file-io.ts @@ -0,0 +1,105 @@ +import { + compress, + describeK8sResources, + getFormattedEvents, + getHelmReleases, + getK8sLogs, + getK8sResources, + getPodList, + getPVCList, + getPVList, + RuntimeType, + toYaml, +} from '../deps.ts'; + +const timestamp = new Date().getTime(); +const dirPath = `./codefresh-support-${timestamp}`; + +async function creatDirectory(path: string) { + await Deno.mkdir(`${dirPath}/${path}/`, { recursive: true }); +} + +export async function writeCodefreshFiles(data: any, name: string) { + const filePath = `${dirPath}/${name}.yaml`; + const fileContent = toYaml(data, { skipInvalid: true }); + await Deno.writeTextFile(filePath, fileContent); +} + +async function writeGetApiCalls(resources: any, path: string) { + const writePromises = resources.map(async (item: any) => { + const filePath = `${dirPath}/${path}/${item.metadata.name}_get.yaml`; + const fileContent = toYaml(item, { skipInvalid: true }); + await Deno.writeTextFile(filePath, fileContent); + }); + await Promise.all(writePromises); +} + +export async function prepareAndCleanup() { + console.log(`Saving data to ./codefresh-support-package-${timestamp}.zip`); + await compress(dirPath, `./codefresh-support-package-${timestamp}.zip`, { overwrite: true }); + + console.log('Cleaning up temp directory'); + await Deno.remove(dirPath, { recursive: true }); + + console.log(`\nPlease attach ./codefresh-support-package-${timestamp}.zip to your support ticket.`); +} + +export async function fetchAndSaveData(type: RuntimeType, namespace: string) { + await Deno.mkdir(`${dirPath}/`, { recursive: true }); + + for (const [itemType, fetcher] of Object.entries(getK8sResources(type, namespace) || {})) { + const resources = await fetcher(); + + if (itemType === 'Events') { + const formattedEvents = getFormattedEvents(resources); + await Deno.writeTextFile(`${dirPath}/Events.txt`, formattedEvents); + continue; + } + + if (itemType === 'HelmReleases') { + const helmReleases = getHelmReleases(resources); + await writeCodefreshFiles(helmReleases, 'HelmReleases'); + continue; + } + + await creatDirectory(itemType); + + if (itemType === 'Pods') { + const podList = getPodList(resources); + await Deno.writeTextFile(`${dirPath}/PodList.txt`, podList); + + await Promise.all( + resources.items.map(async (resource: { metadata: { name: string }; spec: { containers: any } }) => { + const podName = resource.metadata.name; + const containers = resource.spec.containers; + + await Promise.all(containers.map(async (container: { name: string }) => { + const log = await getK8sLogs(namespace, podName, container.name); + const logFileName = `${dirPath}/${itemType}/${podName}_${container.name}_log.log`; + await Deno.writeTextFile(logFileName, log); + })); + }), + ); + } + + if (itemType === 'Volumeclaims') { + const pvcList = getPVCList(resources); + await Deno.writeTextFile(`${dirPath}/VolumeClaimsList.txt`, pvcList); + await writeGetApiCalls(resources.items, itemType); + continue; + } + + if (itemType === 'Volumes') { + const pvList = getPVList(resources); + await Deno.writeTextFile(`${dirPath}/VolumesList.txt`, pvList); + await writeGetApiCalls(resources.items, itemType); + continue; + } + + await Promise.all(resources.items.map(async (resource: { metadata: { name: string } }) => { + const describeOutput = await describeK8sResources(itemType, namespace, resource.metadata.name); + const describeFileName = `${dirPath}/${itemType}/${resource.metadata.name}_describe.yaml`; + await Deno.writeTextFile(describeFileName, describeOutput); + })); + } +}