|
1 | 1 | import axios from 'axios' |
2 | 2 | import Debug from 'debug' |
3 | | -import { existsSync, lstatSync, mkdirSync, renameSync, rmSync } from 'fs' |
| 3 | +import { existsSync, lstatSync, mkdirSync, mkdtempSync, renameSync, rmSync } from 'fs' |
4 | 4 | import { readFile } from 'fs-extra' |
5 | 5 | import { readdir, writeFile } from 'fs/promises' |
6 | | -import path from 'path' |
| 6 | +import { tmpdir } from 'os' |
| 7 | +import path, { join } from 'path' |
7 | 8 | import simpleGit, { SimpleGit } from 'simple-git' |
8 | 9 | import { safeReadTextFile } from 'src/utils' |
9 | 10 | import { cleanEnv, GIT_PROVIDER_URL_PATTERNS } from 'src/validators' |
@@ -339,6 +340,40 @@ export async function sparseCloneChart( |
339 | 340 | return true |
340 | 341 | } |
341 | 342 |
|
| 343 | +export async function sparseCheckoutPath( |
| 344 | + gitCloneUrl: string, |
| 345 | + ref: string, |
| 346 | + sparsePath: string, |
| 347 | + targetBaseDir: string, |
| 348 | + targetDirName: string, |
| 349 | +): Promise<{ success: true; checkoutPath: string } | { success: false; error: string }> { |
| 350 | + if (!existsSync(targetBaseDir)) mkdirSync(targetBaseDir, { recursive: true }) |
| 351 | + |
| 352 | + const tempCloneDir = mkdtempSync(join(tmpdir(), 'sparse-checkout-')) |
| 353 | + const finalDestinationPath = join(targetBaseDir, targetDirName) |
| 354 | + |
| 355 | + try { |
| 356 | + rmSync(finalDestinationPath, { recursive: true, force: true }) |
| 357 | + |
| 358 | + const normalizedSparsePath = sparsePath.replace(/^\/+/, '').replace(/\/+$/, '') |
| 359 | + const refAndPath = `${ref}/${normalizedSparsePath}` |
| 360 | + |
| 361 | + const repo = new chartRepo(tempCloneDir, gitCloneUrl) |
| 362 | + await repo.cloneSingleChart(refAndPath, finalDestinationPath) |
| 363 | + |
| 364 | + rmSync(join(finalDestinationPath, '.git'), { recursive: true, force: true }) |
| 365 | + |
| 366 | + return { success: true, checkoutPath: finalDestinationPath } |
| 367 | + } catch (error) { |
| 368 | + return { |
| 369 | + success: false, |
| 370 | + error: error instanceof Error ? error.message : 'Unknown sparse checkout error.', |
| 371 | + } |
| 372 | + } finally { |
| 373 | + rmSync(tempCloneDir, { recursive: true, force: true }) |
| 374 | + } |
| 375 | +} |
| 376 | + |
342 | 377 | /** |
343 | 378 | * Encodes Git credentials into the URL for internal Gitea repositories |
344 | 379 | */ |
@@ -534,3 +569,66 @@ export async function fetchWorkloadCatalog( |
534 | 569 |
|
535 | 570 | return { helmCharts, catalog } |
536 | 571 | } |
| 572 | + |
| 573 | +export async function fetchWorkloadCatalogChart( |
| 574 | + url: string, |
| 575 | + helmChartsDir: string, |
| 576 | + chartName: string, |
| 577 | + branch: string = 'main', |
| 578 | + clusterDomainSuffix?: string, |
| 579 | + teamId?: string, |
| 580 | + chartsPath?: string, |
| 581 | +): Promise<any | null> { |
| 582 | + const resolvedHelmChartsDir = path.resolve(helmChartsDir) |
| 583 | + |
| 584 | + if (!existsSync(resolvedHelmChartsDir)) { |
| 585 | + mkdirSync(resolvedHelmChartsDir, { recursive: true }) |
| 586 | + } |
| 587 | + |
| 588 | + const gitUrl = encodeGitCredentials(url, clusterDomainSuffix) |
| 589 | + |
| 590 | + const sparsePath = chartsPath ? `${chartsPath}/${chartName}` : chartName |
| 591 | + const checkoutResult = await sparseCheckoutPath(gitUrl, branch, sparsePath, resolvedHelmChartsDir, chartName) |
| 592 | + |
| 593 | + if (!checkoutResult.success) { |
| 594 | + debug(`Sparse checkout failed for chart '${chartName}' from '${url}': ${checkoutResult.error}`) |
| 595 | + return null |
| 596 | + } |
| 597 | + |
| 598 | + const chartDir = checkoutResult.checkoutPath |
| 599 | + |
| 600 | + try { |
| 601 | + const values = await safeReadTextFile(chartDir, 'values.yaml') |
| 602 | + |
| 603 | + let valuesSchema = '{}' |
| 604 | + try { |
| 605 | + const schemaContent = await safeReadTextFile(chartDir, 'values.schema.json') |
| 606 | + valuesSchema = schemaContent || '{}' |
| 607 | + } catch { |
| 608 | + // optional |
| 609 | + } |
| 610 | + |
| 611 | + const chartYaml = await safeReadTextFile(chartDir, 'Chart.yaml') |
| 612 | + const chartMetadata = YAML.parse(chartYaml) |
| 613 | + |
| 614 | + let readme = 'There is no `README` for this chart.' |
| 615 | + try { |
| 616 | + readme = await safeReadTextFile(chartDir, 'README.md') |
| 617 | + } catch { |
| 618 | + // optional |
| 619 | + } |
| 620 | + |
| 621 | + return { |
| 622 | + name: chartName, |
| 623 | + values: values || '{}', |
| 624 | + valuesSchema, |
| 625 | + icon: chartMetadata?.icon, |
| 626 | + chartVersion: chartMetadata?.version, |
| 627 | + chartDescription: chartMetadata?.description, |
| 628 | + readme, |
| 629 | + } |
| 630 | + } catch (error) { |
| 631 | + debug(`Error parsing chart '${chartName}' in '${chartDir}': ${error.message}`) |
| 632 | + return null |
| 633 | + } |
| 634 | +} |
0 commit comments