From 0d876941d2950ac602ce827d9593419dc7626bff Mon Sep 17 00:00:00 2001 From: Ferruh <63190600+ferruhcihan@users.noreply.github.com> Date: Thu, 20 Mar 2025 15:02:12 +0100 Subject: [PATCH 1/3] fix: create helm chart with branches or tags (#667) * test: find chart repo refs * feat: update workloadUtils and workloadUtils.test files (cherry picked from commit 1a551420b64c1c28975e9b591a9b561e22a6a856) --- src/utils/workloadUtils.test.ts | 290 +++++++++++++++++++++++++++++--- src/utils/workloadUtils.ts | 126 +++++++++----- 2 files changed, 346 insertions(+), 70 deletions(-) diff --git a/src/utils/workloadUtils.test.ts b/src/utils/workloadUtils.test.ts index a03ae64ff..859ff941b 100644 --- a/src/utils/workloadUtils.test.ts +++ b/src/utils/workloadUtils.test.ts @@ -2,12 +2,17 @@ import axios from 'axios' import * as fsExtra from 'fs-extra' import * as fsPromises from 'fs/promises' +import path from 'path' import simpleGit from 'simple-git' import YAML from 'yaml' +import * as workloadUtils from './workloadUtils' import { + chartRepo, detectGitProvider, fetchChartYaml, fetchWorkloadCatalog, + findRevision, + getBranchesAndTags, getGitCloneUrl, sparseCloneChart, updateChartIconInYaml, @@ -369,6 +374,7 @@ describe('sparseCloneChart', () => { commit: jest.fn().mockResolvedValue(undefined), pull: jest.fn().mockResolvedValue(undefined), push: jest.fn().mockResolvedValue(undefined), + listRemote: jest.fn().mockResolvedValue(''), } ;(simpleGit as jest.Mock).mockReturnValue(mockGit) @@ -387,11 +393,16 @@ describe('sparseCloneChart', () => { expect(fsExtra.mkdirSync).toHaveBeenCalledWith(localHelmChartsDir, { recursive: true }) expect(fsExtra.mkdirSync).toHaveBeenCalledWith(`${localHelmChartsDir}-newChart`, { recursive: true }) expect(mockGit.clone).toHaveBeenCalledTimes(2) // Once for catalog repo, once for chart repo + expect(mockGit.listRemote).toHaveBeenCalled() expect(mockGit.raw).toHaveBeenCalledWith(['sparse-checkout', 'init', '--cone']) - expect(mockGit.raw).toHaveBeenCalledWith(['sparse-checkout', 'set', 'bitnami/cassandra/']) - expect(mockGit.checkout).toHaveBeenCalledWith('main') + expect(mockGit.raw).toHaveBeenCalledWith(['sparse-checkout', 'set', 'main/bitnami/cassandra/']) + expect(mockGit.checkout).toHaveBeenCalled() expect(fsExtra.renameSync).toHaveBeenCalled() - expect(fsExtra.rmSync).toHaveBeenCalled() + expect(fsExtra.rmSync).toHaveBeenCalledWith(`${localHelmChartsDir}-newChart`, { recursive: true, force: true }) + expect(fsExtra.rmSync).toHaveBeenCalledWith(`${localHelmChartsDir}/${chartTargetDirName}/.git`, { + recursive: true, + force: true, + }) // Verify addConfig was called with correct user/email expect(mockGit.addConfig).toHaveBeenCalledWith('user.name', user) expect(mockGit.addConfig).toHaveBeenCalledWith('user.email', email) @@ -413,8 +424,10 @@ describe('sparseCloneChart', () => { commit: jest.fn().mockResolvedValue(undefined), pull: jest.fn().mockResolvedValue(undefined), push: jest.fn().mockResolvedValue(undefined), + listRemote: jest.fn().mockResolvedValue(''), } ;(simpleGit as jest.Mock).mockReturnValue(mockGit) + jest.spyOn(workloadUtils, 'isGiteaURL').mockImplementation(() => true) await sparseCloneChart( gitRepositoryUrl, @@ -443,6 +456,7 @@ describe('sparseCloneChart', () => { commit: jest.fn().mockResolvedValue(undefined), pull: jest.fn().mockResolvedValue(undefined), push: jest.fn().mockResolvedValue(undefined), + listRemote: jest.fn().mockResolvedValue(''), } ;(simpleGit as jest.Mock).mockReturnValue(mockGit) @@ -474,6 +488,7 @@ describe('sparseCloneChart', () => { commit: jest.fn().mockResolvedValue(undefined), pull: jest.fn().mockResolvedValue(undefined), push: jest.fn().mockResolvedValue(undefined), + listRemote: jest.fn().mockResolvedValue(''), } ;(simpleGit as jest.Mock).mockReturnValue(mockGit) ;(fsExtra.existsSync as jest.Mock).mockReturnValueOnce(false) @@ -491,6 +506,24 @@ describe('sparseCloneChart', () => { expect(fsExtra.mkdirSync).toHaveBeenCalledWith(localHelmChartsDir, { recursive: true }) }) + + test('returns false if git provider detection fails', async () => { + // Mock the detectGitProvider to return null + jest.spyOn(workloadUtils, 'detectGitProvider').mockImplementation(() => null) + + const result = await sparseCloneChart( + 'https://invalid-url.com', + localHelmChartsDir, + helmChartCatalogUrl, + user, + email, + chartTargetDirName, + chartIcon, + allowTeams, + ) + + expect(result).toBe(false) + }) }) // ---------------------------------------------------------------- @@ -516,14 +549,14 @@ describe('fetchWorkloadCatalog', () => { }, betaCharts: ['chart2'], }) - ;(fsExtra.readFile as jest.Mock).mockImplementation((path) => { - if (path.endsWith('rbac.yaml')) return Promise.resolve(rbacContent) + ;(fsExtra.readFile as jest.Mock).mockImplementation((filePath) => { + if (filePath.endsWith('rbac.yaml')) return Promise.resolve(rbacContent) - if (path.endsWith('chart1/README.md')) return Promise.resolve('# Chart 1 README') + if (filePath.endsWith('chart1/README.md')) return Promise.resolve('# Chart 1 README') - if (path.endsWith('chart1/values.yaml')) return Promise.resolve('key: value') + if (filePath.endsWith('chart1/values.yaml')) return Promise.resolve('key: value') - if (path.endsWith('chart1/Chart.yaml')) { + if (filePath.endsWith('chart1/Chart.yaml')) { return Promise.resolve( YAML.stringify({ name: 'chart1', @@ -533,11 +566,11 @@ describe('fetchWorkloadCatalog', () => { }), ) } - if (path.endsWith('chart2/README.md')) return Promise.resolve('# Chart 2 README') + if (filePath.endsWith('chart2/README.md')) return Promise.resolve('# Chart 2 README') - if (path.endsWith('chart2/values.yaml')) return Promise.resolve('key: value') + if (filePath.endsWith('chart2/values.yaml')) return Promise.resolve('key: value') - if (path.endsWith('chart2/Chart.yaml')) { + if (filePath.endsWith('chart2/Chart.yaml')) { return Promise.resolve( YAML.stringify({ name: 'chart2', @@ -547,7 +580,7 @@ describe('fetchWorkloadCatalog', () => { }), ) } - return Promise.reject(new Error(`File not found: ${path}`)) + return Promise.reject(new Error(`File not found: ${filePath}`)) }) }) @@ -627,12 +660,12 @@ describe('fetchWorkloadCatalog', () => { ;(simpleGit as jest.Mock).mockReturnValue(mockGit) // Make the README.md file read fail - ;(fsExtra.readFile as jest.Mock).mockImplementation((path) => { - if (path.endsWith('chart1/README.md')) return Promise.reject(new Error('File not found')) + ;(fsExtra.readFile as jest.Mock).mockImplementation((filePath) => { + if (filePath.endsWith('chart1/README.md')) return Promise.reject(new Error('File not found')) - if (path.endsWith('chart1/values.yaml')) return Promise.resolve('key: value') + if (filePath.endsWith('chart1/values.yaml')) return Promise.resolve('key: value') - if (path.endsWith('chart1/Chart.yaml')) { + if (filePath.endsWith('chart1/Chart.yaml')) { return Promise.resolve( YAML.stringify({ name: 'chart1', @@ -642,7 +675,7 @@ describe('fetchWorkloadCatalog', () => { }), ) } - if (path.endsWith('rbac.yaml')) { + if (filePath.endsWith('rbac.yaml')) { return Promise.resolve( YAML.stringify({ rbac: { chart1: null }, @@ -650,7 +683,7 @@ describe('fetchWorkloadCatalog', () => { }), ) } - return Promise.reject(new Error(`File not found: ${path}`)) + return Promise.reject(new Error(`File not found: ${filePath}`)) }) const result = await fetchWorkloadCatalog(url, helmChartsDir, 'admin') @@ -666,14 +699,14 @@ describe('fetchWorkloadCatalog', () => { ;(simpleGit as jest.Mock).mockReturnValue(mockGit) // Make the rbac.yaml file read fail - ;(fsExtra.readFile as jest.Mock).mockImplementation((path) => { - if (path.endsWith('rbac.yaml')) return Promise.reject(new Error('File not found')) + ;(fsExtra.readFile as jest.Mock).mockImplementation((filePath) => { + if (filePath.endsWith('rbac.yaml')) return Promise.reject(new Error('File not found')) - if (path.endsWith('chart1/README.md')) return Promise.resolve('# Chart 1 README') + if (filePath.endsWith('chart1/README.md')) return Promise.resolve('# Chart 1 README') - if (path.endsWith('chart1/values.yaml')) return Promise.resolve('key: value') + if (filePath.endsWith('chart1/values.yaml')) return Promise.resolve('key: value') - if (path.endsWith('chart1/Chart.yaml')) { + if (filePath.endsWith('chart1/Chart.yaml')) { return Promise.resolve( YAML.stringify({ name: 'chart1', @@ -683,7 +716,7 @@ describe('fetchWorkloadCatalog', () => { }), ) } - return Promise.reject(new Error(`File not found: ${path}`)) + return Promise.reject(new Error(`File not found: ${filePath}`)) }) const result = await fetchWorkloadCatalog(url, helmChartsDir, 'admin') @@ -693,3 +726,212 @@ describe('fetchWorkloadCatalog', () => { expect(result.catalog).toHaveLength(1) }) }) + +// ---------------------------------------------------------------- +// Tests for chartRepo class +describe('chartRepo', () => { + const localPath = '/tmp/repo' + const chartRepoUrl = 'https://github.com/example/repo.git' + const gitUser = 'test-user' + const gitEmail = 'test@example.com' + + let mockGit + + beforeEach(() => { + jest.clearAllMocks() + mockGit = { + clone: jest.fn().mockResolvedValue(undefined), + cwd: jest.fn().mockResolvedValue(undefined), + raw: jest.fn().mockResolvedValue(undefined), + checkout: jest.fn().mockResolvedValue(undefined), + addConfig: jest.fn().mockResolvedValue(undefined), + add: jest.fn().mockResolvedValue(undefined), + commit: jest.fn().mockResolvedValue(undefined), + pull: jest.fn().mockResolvedValue(undefined), + push: jest.fn().mockResolvedValue(undefined), + listRemote: jest.fn().mockResolvedValue(''), + } + ;(simpleGit as jest.Mock).mockReturnValue(mockGit) + }) + + test('clone method clones the repository', async () => { + const repo = new chartRepo(localPath, chartRepoUrl, gitUser, gitEmail) + await repo.clone() + + expect(mockGit.clone).toHaveBeenCalledWith(chartRepoUrl, localPath) + }) + + test('cloneSingleChart method performs sparse checkout', async () => { + const repo = new chartRepo(localPath, chartRepoUrl, gitUser, gitEmail) + const refAndPath = 'main/charts/my-chart' + const finalDestinationPath = '/tmp/final/my-chart' + + // Mock getBranchesAndTags to return 'main' as a branch + mockGit.listRemote.mockResolvedValue( + '1234567890abcdef1234567890abcdef12345678\trefs/heads/main\n' + + 'abcdef1234567890abcdef1234567890abcdef12\trefs/tags/v1.0.0', + ) + + await repo.cloneSingleChart(refAndPath, finalDestinationPath) + + expect(mockGit.listRemote).toHaveBeenCalledWith([chartRepoUrl]) + expect(mockGit.clone).toHaveBeenCalledWith(chartRepoUrl, localPath, ['--filter=blob:none', '--no-checkout']) + expect(mockGit.cwd).toHaveBeenCalledWith(localPath) + expect(mockGit.raw).toHaveBeenCalledWith(['sparse-checkout', 'init', '--cone']) + expect(mockGit.raw).toHaveBeenCalledWith(['sparse-checkout', 'set', 'charts/my-chart']) + expect(mockGit.checkout).toHaveBeenCalledWith('main') + expect(fsExtra.renameSync).toHaveBeenCalledWith(path.join(localPath, 'charts/my-chart'), finalDestinationPath) + }) + + test('addConfig method sets git config', async () => { + const repo = new chartRepo(localPath, chartRepoUrl, gitUser, gitEmail) + await repo.addConfig() + + expect(mockGit.addConfig).toHaveBeenCalledWith('user.name', gitUser) + expect(mockGit.addConfig).toHaveBeenCalledWith('user.email', gitEmail) + }) + + test('commitAndPush method commits and pushes changes', async () => { + const repo = new chartRepo(localPath, chartRepoUrl, gitUser, gitEmail) + const chartName = 'my-chart' + + await repo.commitAndPush(chartName) + + expect(mockGit.add).toHaveBeenCalledWith('.') + expect(mockGit.commit).toHaveBeenCalledWith(`Add ${chartName} helm chart`) + expect(mockGit.pull).toHaveBeenCalledWith('origin', 'main', { '--rebase': null }) + expect(mockGit.push).toHaveBeenCalledWith('origin', 'main') + }) +}) + +// ---------------------------------------------------------------- +// Tests for findRevision +describe('findRevision', () => { + test('finds branch in simple case', () => { + const branches = ['main', 'develop', 'feature/x'] + const tags = ['v1.0.0', 'v1.1.0'] + const refAndPath = 'main/charts/my-chart' + + const result = findRevision(branches, tags, refAndPath) + + expect(result).toBe('main') + }) + + test('finds tag in simple case', () => { + const branches = ['main'] + const tags = ['v1.0.0', 'v1.1.0'] + const refAndPath = 'v1.0.0/charts/my-chart' + + const result = findRevision(branches, tags, refAndPath) + + expect(result).toBe('v1.0.0') + }) + + test('finds branch with multiple segments', () => { + const branches = ['main', 'feature/x'] + const tags = [] + const refAndPath = 'feature/x/charts/my-chart' + + const result = findRevision(branches, tags, refAndPath) + + expect(result).toBe('feature/x') + }) + + test('handles complex paths correctly', () => { + const branches = ['main', 'feature/x/y'] + const tags = [] + const refAndPath = 'feature/x/y/charts/my-chart' + + const result = findRevision(branches, tags, refAndPath) + + expect(result).toBe('feature/x/y') + }) + + test('returns null if no match is found', () => { + const branches = ['main'] + const tags = ['v1.0.0'] + const refAndPath = 'develop/charts/my-chart' + + const result = findRevision(branches, tags, refAndPath) + + expect(result).toBeNull() + }) +}) + +// ---------------------------------------------------------------- +// Tests for getBranchesAndTags +describe('getBranchesAndTags', () => { + test('parses branches and tags from git remote output', () => { + const remoteResult = + '1234567890abcdef1234567890abcdef12345678\trefs/heads/main\n' + + 'abcdef1234567890abcdef1234567890abcdef12\trefs/heads/develop\n' + + '7890abcdef1234567890abcdef1234567890abcd\trefs/tags/v1.0.0\n' + + '4567890abcdef1234567890abcdef1234567890a\trefs/tags/v1.1.0' + + const result = getBranchesAndTags(remoteResult) + + expect(result).toEqual({ + branches: ['main', 'develop'], + tags: ['v1.0.0', 'v1.1.0'], + }) + }) + + test('handles empty input', () => { + const result = getBranchesAndTags('') + + expect(result).toEqual({ + branches: [], + tags: [], + }) + }) + + test('handles input with no branches or tags', () => { + const remoteResult = + '1234567890abcdef1234567890abcdef12345678\trefs/other/main\n' + + 'abcdef1234567890abcdef1234567890abcdef12\trefs/something/develop' + + const result = getBranchesAndTags(remoteResult) + + expect(result).toEqual({ + branches: [], + tags: [], + }) + }) + + test('handles input with only branches', () => { + const remoteResult = + '1234567890abcdef1234567890abcdef12345678\trefs/heads/main\n' + + 'abcdef1234567890abcdef1234567890abcdef12\trefs/heads/develop' + + const result = getBranchesAndTags(remoteResult) + + expect(result).toEqual({ + branches: ['main', 'develop'], + tags: [], + }) + }) + + test('handles input with only tags', () => { + const remoteResult = + '7890abcdef1234567890abcdef1234567890abcd\trefs/tags/v1.0.0\n' + + '4567890abcdef1234567890abcdef1234567890a\trefs/tags/v1.1.0' + + const result = getBranchesAndTags(remoteResult) + + expect(result).toEqual({ + branches: [], + tags: ['v1.0.0', 'v1.1.0'], + }) + }) + + test('handles invalid input', () => { + const remoteResult = 'This is not a valid git remote output' + + const result = getBranchesAndTags(remoteResult) + + expect(result).toEqual({ + branches: [], + tags: [], + }) + }) +}) diff --git a/src/utils/workloadUtils.ts b/src/utils/workloadUtils.ts index b743570fb..c2da9a82c 100644 --- a/src/utils/workloadUtils.ts +++ b/src/utils/workloadUtils.ts @@ -13,6 +13,34 @@ const env = cleanEnv({ GIT_PROVIDER_URL_PATTERNS, }) +export interface NewHelmChartValues { + gitRepositoryUrl: string + chartTargetDirName: string + chartIcon?: string + allowTeams: boolean +} + +function throwChartError(message: string) { + const err = { + code: 404, + message, + } + throw err +} +export function isGiteaURL(url: string) { + let hostname = '' + if (url) { + try { + hostname = new URL(url).hostname + } catch (e) { + // ignore + return false + } + } + const giteaPattern = /^gitea\..+/i + return giteaPattern.test(hostname) +} + export function detectGitProvider(url) { if (!url || typeof url !== 'string') return null @@ -90,33 +118,30 @@ export async function fetchChartYaml(url) { } } -export interface NewHelmChartValues { - gitRepositoryUrl: string - chartTargetDirName: string - chartIcon?: string - allowTeams: boolean -} +export function getBranchesAndTags(remoteResult: string) { + const lines = remoteResult.split('\n') + const branches: string[] = [] + const tags: string[] = [] + + lines.forEach((line) => { + const parts = line.split('\t') + if (parts.length === 2) { + const ref = parts[1] + if (ref.startsWith('refs/heads/')) branches.push(ref.replace('refs/heads/', '')) + else if (ref.startsWith('refs/tags/')) tags.push(ref.replace('refs/tags/', '')) + } + }) -function throwChartError(message: string) { - const err = { - code: 404, - message, - } - throw err + return { branches, tags } } -function isGiteaURL(url: string) { - let hostname = '' - if (url) { - try { - hostname = new URL(url).hostname - } catch (e) { - // ignore - return false - } - } - const giteaPattern = /^gitea\..+/i - return giteaPattern.test(hostname) + +export function findRevision(branches, tags, refAndPath) { + const parts = refAndPath.split('/') + const candidates = [parts[0], parts.slice(0, 2).join('/'), parts.slice(0, 3).join('/')] + for (const candidate of candidates) if (branches.includes(candidate) || tags.includes(candidate)) return candidate + return null } + /** * Reads the Chart.yaml file at the given path, updates (or sets) its icon field, * and writes the updated content back to disk. @@ -167,13 +192,14 @@ export async function updateRbacForNewChart(sparsePath: string, chartKey: string await writeFile(rbacFilePath, newContent, 'utf-8') debug(`Updated rbac.yaml: added ${chartKey}: ${allowTeams ? 'null' : '[]'}`) } -class chartRepo { + +export class chartRepo { localPath: string chartRepoUrl: string gitUser?: string gitEmail?: string git: SimpleGit - constructor(localPath: string, chartRepoUrl: string, gitUser: string | undefined, gitEmail: string | undefined) { + constructor(localPath: string, chartRepoUrl: string, gitUser?: string, gitEmail?: string) { this.localPath = localPath this.chartRepoUrl = chartRepoUrl this.gitUser = gitUser @@ -183,6 +209,28 @@ class chartRepo { async clone() { await this.git.clone(this.chartRepoUrl, this.localPath) } + async cloneSingleChart(refAndPath: string, finalDestinationPath: string) { + const remoteResult = await this.git.listRemote([this.chartRepoUrl]) + const { branches, tags } = getBranchesAndTags(remoteResult) + const finalRevision = findRevision(branches, tags, refAndPath) as string + const finalFilePath = refAndPath.slice(finalRevision?.length + 1) + + debug(`Cloning repository: ${this.chartRepoUrl} into ${this.localPath}`) + await this.git.clone(this.chartRepoUrl, this.localPath, ['--filter=blob:none', '--no-checkout']) + + debug(`Initializing sparse checkout in cone mode at ${this.localPath}`) + await this.git.cwd(this.localPath) + await this.git.raw(['sparse-checkout', 'init', '--cone']) + + debug(`Setting sparse checkout path to ${finalFilePath}`) + await this.git.raw(['sparse-checkout', 'set', finalFilePath]) + + debug(`Checking out the desired revision (branch or commit): ${finalRevision}`) + await this.git.checkout(finalRevision) + + // Move files from "temporaryCloneDir/chartPath/*" to "finalDestinationPath/" + renameSync(path.join(this.localPath, finalFilePath), finalDestinationPath) + } async addConfig() { await this.git.addConfig('user.name', this.gitUser!) await this.git.addConfig('user.email', this.gitEmail!) @@ -194,6 +242,7 @@ class chartRepo { await this.git.push('origin', 'main') } } + /** * Clones a repository using sparse checkout, checks out a specific revision, * and moves the contents of the desired subdirectory (sparsePath) to the root of the target folder. @@ -220,9 +269,9 @@ export async function sparseCloneChart( allowTeams?: boolean, ): Promise { const details = detectGitProvider(gitRepositoryUrl) + if (!details) return false const gitCloneUrl = getGitCloneUrl(details) as string - const chartPath = details?.filePath.replace('Chart.yaml', '') as string - const revision = details?.branch as string + const refAndPath = `${details.branch}/${details.filePath.replace('Chart.yaml', '')}` const temporaryCloneDir = `${localHelmChartsDir}-newChart` const finalDestinationPath = `${localHelmChartsDir}/${chartTargetDirName}` @@ -243,23 +292,8 @@ export async function sparseCloneChart( mkdirSync(temporaryCloneDir, { recursive: true }) } - const git = simpleGit() - - debug(`Cloning repository: ${gitCloneUrl} into ${temporaryCloneDir}`) - await git.clone(gitCloneUrl, temporaryCloneDir, ['--filter=blob:none', '--no-checkout']) - - debug(`Initializing sparse checkout in cone mode at ${temporaryCloneDir}`) - await git.cwd(temporaryCloneDir) - await git.raw(['sparse-checkout', 'init', '--cone']) - - debug(`Setting sparse checkout path to ${chartPath}`) - await git.raw(['sparse-checkout', 'set', chartPath]) - - debug(`Checking out the desired revision (branch or commit): ${revision}`) - await git.checkout(revision) - - // Move files from "temporaryCloneDir/chartPath/*" to "finalDestinationPath/" - renameSync(path.join(temporaryCloneDir, chartPath), finalDestinationPath) + const gitSingleChart = new chartRepo(temporaryCloneDir, gitCloneUrl) + await gitSingleChart.cloneSingleChart(refAndPath, finalDestinationPath) // Remove the .git directory from the final destination. rmSync(`${finalDestinationPath}/.git`, { recursive: true, force: true }) @@ -292,7 +326,7 @@ export async function fetchWorkloadCatalog(url: string, helmChartsDir: string, t const encodedPassword = encodeURIComponent(process.env.GIT_PASSWORD as string) gitUrl = `${protocol}://${encodedUser}:${encodedPassword}@${bareUrl}` } - const gitRepo = new chartRepo(helmChartsDir, gitUrl, undefined, undefined) + const gitRepo = new chartRepo(helmChartsDir, gitUrl) await gitRepo.clone() const files = await readdir(helmChartsDir, 'utf-8') From c30668c1c653fea24f85e6416b4123c7039a6b5c Mon Sep 17 00:00:00 2001 From: Ferruh Cihan <63190600+ferruhcihan@users.noreply.github.com> Date: Thu, 20 Mar 2025 15:10:33 +0100 Subject: [PATCH 2/3] fix: update changelog --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index af4556d94..6e43537d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,19 @@ All notable changes to this project will be documented in this file. See [standa * remove password from error log ([#658](https://github.com/redkubes/otomi-api/issues/658)) ([754e672](https://github.com/redkubes/otomi-api/commit/754e6727d0f5558abc58e6e64a9b194151317871)) +### [3.7.4](https://github.com/redkubes/otomi-api/compare/v3.7.3...v3.7.4) (2025-03-20) + + +### Bug Fixes + +* create helm chart with branches or tags ([#667](https://github.com/redkubes/otomi-api/issues/667)) ([1a55142](https://github.com/redkubes/otomi-api/commit/1a551420b64c1c28975e9b591a9b561e22a6a856)) + +### [3.7.3](https://github.com/redkubes/otomi-api/compare/v3.7.2...v3.7.3) (2025-03-19) + + +### Bug Fixes + +* get commit sha from session stack ([#664](https://github.com/redkubes/otomi-api/issues/664)) ([4cf4ecd](https://github.com/redkubes/otomi-api/commit/4cf4ecdf8082c2f1e94447fee9636cb6f94d0045)) ### [3.7.2](https://github.com/redkubes/otomi-api/compare/v3.7.1...v3.7.2) (2025-03-14) From 5ca022016b668f6145b84468c39856e5b4555fb1 Mon Sep 17 00:00:00 2001 From: Ferruh Cihan <63190600+ferruhcihan@users.noreply.github.com> Date: Mon, 24 Mar 2025 10:21:38 +0100 Subject: [PATCH 3/3] fix: don't create a session for createWorkloadCatalog --- src/middleware/session.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/middleware/session.ts b/src/middleware/session.ts index bcf4776c5..a33d6dec6 100644 --- a/src/middleware/session.ts +++ b/src/middleware/session.ts @@ -98,7 +98,8 @@ export function sessionMiddleware(server: http.Server): RequestHandler { if (['post', 'put', 'delete'].includes(req.method.toLowerCase())) { // in the cloudtty or workloadCatalog endpoint(s), don't need to create a session - if (req.path === '/v1/cloudtty' || req.path === '/v1/workloadCatalog') return next() + if (req.path === '/v1/cloudtty' || req.path === '/v1/workloadCatalog' || req.path === '/v1/createWorkloadCatalog') + return next() // bootstrap session stack with unique sessionId to manipulate data const sessionId = uuidv4() as string // eslint-disable-next-line no-param-reassign