From f8996685a5ce6275acd088d673f573052b65ab12 Mon Sep 17 00:00:00 2001 From: Nandagopal Nambiar Date: Wed, 28 May 2025 17:16:36 +0530 Subject: [PATCH 1/3] Adding worker sample for AFTER_BUILD_INFO_SAVE to set artifact property --- .../build-property-setter/README.md | 60 +++++ .../build-property-setter/manifest.json | 16 ++ .../build-property-setter/package.json | 40 +++ .../build-property-setter/tsconfig.json | 15 ++ .../build-property-setter/types.ts | 58 +++++ .../build-property-setter/worker.spec.ts | 213 ++++++++++++++++ .../build-property-setter/worker.ts | 229 ++++++++++++++++++ 7 files changed, 631 insertions(+) create mode 100644 samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/README.md create mode 100644 samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/manifest.json create mode 100644 samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/package.json create mode 100644 samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/tsconfig.json create mode 100644 samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/types.ts create mode 100644 samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/worker.spec.ts create mode 100644 samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/worker.ts diff --git a/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/README.md b/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/README.md new file mode 100644 index 0000000..066e93f --- /dev/null +++ b/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/README.md @@ -0,0 +1,60 @@ +Build Property Setter +===================================== + +This worker is triggered by the `AFTER_BUILD_INFO_SAVE` event of Artifactory. Its primary purpose is to remove the "latest=true" property from any artifacts in previous builds and set the same property on the artifacts corresponding to the current build that was saved. + +Functionality +------------- +- **Removing property:** Removes "latest=true" from artifacts in past builds. +- **Adding property:** Sets "latest=true" for artifacts in the current build. + +Worker Logic +------------ +1. **Removing latest from previous build's artifacts**: + - Find the previous build numbers for the same build name. + - Find the unique artifacts from the past builds. + - Use the delete property API to remove the property from these artifacts. +2. **Setting latest for artifacts in current build**: + - Fetch artifacts from the current build. + - Use the set property API to set `latest=true` for each of these artifacts. + +Payload +------- +The worker operates on the `AFTER_BUILD_INFO_SAVE` event payload provided by Artifactory. It uses the build name and build number to fetch the artifact details. + +Possible Responses +------------------ + +### Success +- **Structure:** + ```json + { + "data": { + "message": "Successfully set property for artifacts", + "executionStatus": 4 + }, + "executionStatus": "STATUS_SUCCESS" + } + ``` +- **Explanation:** + - Indicates that only the artifacts present in the build (if any) will now have the property `latest=true` set. + +### Failed +- **Structure:** + ```json + { + "data": { + "message": "Error occurred", + "executionStatus": 2 + }, + "executionStatus": "STATUS_FAIL" + } + ``` +- **Explanation:** + - Indicates there was an error in fetching details or setting the property. + +Recommendations +--------------- + +- **Testing**: + - Validate the worker functionality in a staging environment before deploying to production. \ No newline at end of file diff --git a/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/manifest.json b/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/manifest.json new file mode 100644 index 0000000..4093334 --- /dev/null +++ b/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/manifest.json @@ -0,0 +1,16 @@ +{ + "name": "build-property-setter", + "description": "Removes 'latest=true' from previous build artifacts and sets it for current build artifacts after build info is saved.", + "filterCriteria": { + "artifactFilterCriteria": { + "repoKeys": [ + "example-repo-local" + ] + } + }, + "secrets": {}, + "sourceCodePath": "./worker.ts", + "action": "AFTER_BUILD_INFO_SAVE", + "enabled": false, + "debug": true +} \ No newline at end of file diff --git a/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/package.json b/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/package.json new file mode 100644 index 0000000..8b0c78a --- /dev/null +++ b/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/package.json @@ -0,0 +1,40 @@ +{ + "name": "build-property-setter", + "description": "Run a script on AFTER_BUILD_INFO_SAVE", + "version": "1.0.0", + "scripts": { + "deploy": "jf worker deploy", + "undeploy": "jf worker rm \"build-property-setter\"", + "test": "jest" + }, + "license": "ISC", + "devDependencies": { + "jfrog-workers": "^0.4.0", + "@golevelup/ts-jest": "^0.4.0", + "@types/jest": "^29.5.12", + "jest": "^29.7.0", + "jest-jasmine2": "^29.7.0", + "ts-jest": "^29.1.2" + }, + "jest": { + "moduleFileExtensions": [ + "ts", + "js" + ], + "rootDir": ".", + "testEnvironment": "node", + "clearMocks": true, + "maxConcurrency": 1, + "testRegex": "\\.spec\\.ts$", + "moduleDirectories": ["node_modules"], + "collectCoverageFrom": [ + "**/*.ts" + ], + "coverageDirectory": "../coverage", + "transform": { + "^.+\\.(t|j)s$": "ts-jest" + }, + "testRunner": "jest-jasmine2", + "verbose": true + } +} diff --git a/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/tsconfig.json b/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/tsconfig.json new file mode 100644 index 0000000..21b69a5 --- /dev/null +++ b/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "module": "commonjs", + "declaration": true, + "target": "es2017", + "skipLibCheck": true, + "forceConsistentCasingInFileNames": false, + "noFallthroughCasesInSwitch": false, + "allowJs": true + }, + "include": [ + "**/*.ts", + "node_modules/@types/**/*.d.ts" + ] +} \ No newline at end of file diff --git a/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/types.ts b/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/types.ts new file mode 100644 index 0000000..9cf5668 --- /dev/null +++ b/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/types.ts @@ -0,0 +1,58 @@ +interface DetailedBuildRun { + name: string; + number: string; + started: string; + buildAgent: string; + agent: string; + durationMillis: number; + principal: string; + artifactoryPrincipal: string; + url: string; + parentName: string; + parentNumber: string; + buildRepo: string; + modules: Module[]; + releaseStatus: string; + promotionStatuses: PromotionStatus[]; +} + +interface Module { + id: string; + artifacts: Artifact[]; + dependencies: Dependency[]; +} + +interface Artifact { + name: string; + type: string; + sha1: string; + sha256: string; + md5: string; + remotePath: string; + properties: string; +} + +interface Dependency { + id: string; + scopes: string; + requestedBy: string; +} + +interface PromotionStatus { + status: string; + comment: string; + repository: string; + timestamp: string; + user: string; + ciUser: string; +} + +export interface AfterBuildInfoSaveRequest { + /** Various immutable build run details */ + build: DetailedBuildRun; +} + +export interface ArtifactPathInfo { + repo: string; + path: string; +} diff --git a/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/worker.spec.ts b/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/worker.spec.ts new file mode 100644 index 0000000..8776574 --- /dev/null +++ b/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/worker.spec.ts @@ -0,0 +1,213 @@ +import { PlatformContext, PlatformClients, PlatformHttpClient, Status } from 'jfrog-workers'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { AfterBuildInfoSaveRequest } from './types'; +import runWorker from './worker'; + +describe("build-property-setter worker", () => { + let context: DeepMocked; + let request: DeepMocked; + + beforeEach(() => { + context = createMock({ + clients: createMock({ + platformHttp: createMock({ + get: jest.fn().mockResolvedValue({ + status: 200, + data: { buildsNumbers: [], buildInfo: { modules: [] } } + }), + put: jest.fn().mockResolvedValue({ status: 204 }), + delete: jest.fn().mockResolvedValue({ status: 204 }) + }) + }) + }); + request = createMock({ + build: { + name: 'test-build', + number: '123' + } + }); + }); + + it('should handle first build scenario', async () => { + // Simulate no previous builds + (context.clients.platformHttp.get as jest.Mock).mockResolvedValueOnce({ + status: 200, + data: { buildsNumbers: [], buildInfo: { modules: [] } } + }); + + await expect(runWorker(context, request)).resolves.toEqual( + expect.objectContaining({ + message: expect.stringContaining('Successfully set property for artifacts'), + executionStatus: Status.STATUS_SUCCESS + }) + ); + }); + + it('should call delete API for past artifacts in past builds', async () => { + (context.clients.platformHttp.get as jest.Mock).mockResolvedValueOnce({ + status: 200, + data: { + buildsNumbers: [{ uri: '/12356' }], + buildInfo: { modules: [] } + } + }); + + (context.clients.platformHttp.get as jest.Mock).mockResolvedValueOnce({ + status: 200, + data: { + buildInfo: { + modules: [ + { + id: 'module1', + artifacts: [ + { name: 'artifact1.jar', sha1: 'abc', md5: 'def', type: 'jar', remotePath: 'repo/path/artifact1.jar' } + ] + } + ] + } + } + }); + + await runWorker(context, request); + + expect(context.clients.platformHttp.delete).toHaveBeenCalled(); + }); + + it('should call put API to set properties for artifacts in current build', async () => { + // Mock: No previous builds + (context.clients.platformHttp.get as jest.Mock).mockResolvedValueOnce({ + status: 200, + data: { buildsNumbers: [], buildInfo: { modules: [] } } + }); + // Mock: Current build details with one artifact + (context.clients.platformHttp.get as jest.Mock).mockResolvedValueOnce({ + status: 200, + data: { + buildInfo: { + modules: [ + { + id: 'module1', + artifacts: [ + { + name: 'artifact2.jar', + sha1: 'xyz', + md5: 'uvw', + type: 'jar', + originalDeploymentRepo: 'my-repo', + path: 'some/path/artifact2.jar' + } + ] + } + ] + } + } + }); + + await runWorker(context, request); + + expect(context.clients.platformHttp.put).toHaveBeenCalledWith( + expect.stringContaining('/artifactory/api/storage/my-repo/some/path/artifact2.jar?properties=latest=true') + ); + }); + + it('should handle past build with module having no artifacts, but log a warning', async () => { + + (context.clients.platformHttp.get as jest.Mock).mockResolvedValueOnce({ + status: 200, + data: { + buildsNumbers: [{ uri: '/12356' }], + buildInfo: { modules: [] } + } + }); + + (context.clients.platformHttp.get as jest.Mock).mockResolvedValueOnce({ + status: 200, + data: { + buildInfo: { + modules: [ + { id: 'module1' } // no artifacts property + ] + } + } + }); + + const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => { }); + await runWorker(context, request); + expect(consoleWarnSpy).toHaveBeenCalledWith("A module was skipped as no artifact array was found"); + consoleWarnSpy.mockRestore(); + await expect(runWorker(context, request)).resolves.toEqual( + expect.objectContaining({ + message: expect.stringContaining('Successfully set property for artifacts'), + executionStatus: Status.STATUS_SUCCESS + }) + ); + }); + + it('should return fail status if set property API call fails', async () => { + (context.clients.platformHttp.get as jest.Mock).mockResolvedValueOnce({ + status: 200, + data: { buildsNumbers: [], buildInfo: { modules: [] } } + }); + // Mock: Current build details with one artifact + (context.clients.platformHttp.get as jest.Mock).mockResolvedValueOnce({ + status: 200, + data: { + buildInfo: { + modules: [ + { + id: 'module1', + artifacts: [ + { + name: 'artifact2.jar', + sha1: 'xyz', + md5: 'uvw', + type: 'jar', + originalDeploymentRepo: 'my-repo', + path: 'some/path/artifact2.jar' + } + ] + } + ] + } + } + }); + // Mock: setProperty (put) fails + (context.clients.platformHttp.put as jest.Mock).mockResolvedValueOnce({ + status: 400 + }); + + const result = await runWorker(context, request); + + expect(result).toEqual( + expect.objectContaining({ + message: expect.stringContaining('Error occurred'), + executionStatus: Status.STATUS_FAIL + }) + ); + }); + + it('should return fail status if fetching artifact details for a past build fails', async () => { + + (context.clients.platformHttp.get as jest.Mock).mockResolvedValueOnce({ + status: 200, + data: { + buildsNumbers: [{ uri: '/12356' }], + buildInfo: { modules: [] } + } + }); + + (context.clients.platformHttp.get as jest.Mock).mockResolvedValueOnce({ + status: 404, + data: {} + }); + + const result = await runWorker(context, request); + + expect(result).toEqual( + expect.objectContaining({ + message: expect.stringContaining('Error occurred'), + executionStatus: Status.STATUS_FAIL + }) + ); + }); +}); \ No newline at end of file diff --git a/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/worker.ts b/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/worker.ts new file mode 100644 index 0000000..92607c2 --- /dev/null +++ b/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/worker.ts @@ -0,0 +1,229 @@ +import { PlatformContext, Status } from "jfrog-workers"; +import { AfterBuildInfoSaveRequest, ArtifactPathInfo } from "./types"; + +export default async function oldBuildCleanup( + context: PlatformContext, + data: AfterBuildInfoSaveRequest +): Promise<{ message: string; executionStatus: Status }> { + try { + const buildName = data.build.name; + const currentBuildNumber = data.build.number; + const previousBuildResponse = await context.clients.platformHttp.get( + `/artifactory/api/build/${buildName}` + ); + + const previousBuildNumbers = new Set(); + + for (const build of previousBuildResponse.data.buildsNumbers) { + const buildNumber = build.uri.replace(/^\/+/, ""); + if (buildNumber != currentBuildNumber) { + previousBuildNumbers.add(buildNumber); + } + } + + if (previousBuildNumbers.size >= 1) { + await processPropertyCleanupForPreviousBuilds( + context, + buildName, + previousBuildNumbers + ); + } + + await processCurrentBuildForSettingProperty( + context, + buildName, + currentBuildNumber + ); + } catch (error) { + console.error( + `Request failed with status code ${error.status || ""} caused by: ${error.message + }` + ); + return { + message: "Error occurred", + executionStatus: Status.STATUS_FAIL, + }; + } + return { + message: "Successfully set property for artifacts", + executionStatus: Status.STATUS_SUCCESS, + }; +} + +async function processPropertyCleanupForPreviousBuilds( + context: PlatformContext, + buildName: string, + previousBuildNumbers: Set +) { + console.info( + "Finding all artifacts with latest set from the build numbers: " + + Array.from(previousBuildNumbers) + ); + const artifactsForPropertyRemoval = new Map(); + + for (const buildNumber of previousBuildNumbers) { + await fetchAndExtractBuildDetailsForPropertyRemoval( + context, + buildName, + buildNumber, + artifactsForPropertyRemoval + ); + } + + for (const artifactInfo of artifactsForPropertyRemoval.values()) { + await deleteProperty(context, artifactInfo.repo, artifactInfo.path); + } +} + +async function processCurrentBuildForSettingProperty( + context: PlatformContext, + buildName: string, + currentBuildNumber: string +) { + console.info( + `Setting property latest to value true for artifacts in build number ${currentBuildNumber}` + ); + const artifactSet = new Set(); + + await fetchAndExtractBuildDetailsForPropertyUpdate( + context, + buildName, + currentBuildNumber, + artifactSet + ); + + for (const artifactInfo of artifactSet) { + await setProperty(context, artifactInfo.repo, artifactInfo.path); + } +} + +async function populateArtifactsForPropertyRemoval( + modules: any, + artifactsForPropertyRemoval: Map +) { + for (const module of modules) { + if (Array.isArray(module.artifacts)) { + for (const artifact of module.artifacts) { + const artifactFullPath = + artifact.originalDeploymentRepo + "/" + artifact.path; + if (!artifactsForPropertyRemoval.has(artifactFullPath)) { + const artifactPathInfo: ArtifactPathInfo = { + repo: artifact.originalDeploymentRepo, + path: artifact.path, + }; + artifactsForPropertyRemoval.set(artifactFullPath, artifactPathInfo); + } + } + } else { + console.warn("A module was skipped as no artifact array was found"); + } + } +} + +async function populateArtifactsForPropertyUpdate( + modules: any, + artifactsForPropertyUpdate: Set +) { + for (const module of modules) { + if (Array.isArray(module.artifacts)) { + for (const artifact of module.artifacts) { + const artifactPathInfo: ArtifactPathInfo = { + repo: artifact.originalDeploymentRepo, + path: artifact.path, + }; + artifactsForPropertyUpdate.add(artifactPathInfo); + } + } else { + console.warn(`A module was skipped as no artifact array was found`); + } + } +} + +async function fetchAndExtractBuildDetailsForPropertyRemoval( + context: PlatformContext, + buildName: string, + buildNumber: string, + artifactsForPropertyRemoval: Map +) { + const buildResponse = await context.clients.platformHttp.get( + `/artifactory/api/build/${buildName}/${buildNumber}` + ); + if (buildResponse.status === 200) { + const buildData = buildResponse.data; + const modules = buildData.buildInfo.modules; + if (Array.isArray(modules)) { + populateArtifactsForPropertyRemoval(modules, artifactsForPropertyRemoval); + } else { + console.warn( + "No modules found in the build data or modules is not an array" + ); + } + } else { + console.warn( + `Failed to retrieve build data, status code: ${buildResponse.status}` + ); + throw new Error(`build fetch failed for build number: ${buildNumber}`); + } +} + +async function fetchAndExtractBuildDetailsForPropertyUpdate( + context: PlatformContext, + buildName: string, + buildNumber: string, + artifactsForPropertyUpdate: Set +) { + const buildResponse = await context.clients.platformHttp.get( + `/artifactory/api/build/${buildName}/${buildNumber}` + ); + if (buildResponse.status === 200) { + const buildData = buildResponse.data; + const modules = buildData.buildInfo.modules; + if (Array.isArray(modules)) { + populateArtifactsForPropertyUpdate(modules, artifactsForPropertyUpdate); + } else { + console.warn( + "No modules found in the build data or modules is not an array" + ); + } + } else { + console.warn( + `Failed to retrieve build data, status code: ${buildResponse.status}` + ); + throw new Error(`build fetch failed for build number: ${buildNumber}`); + } +} + +async function deleteProperty( + context: PlatformContext, + repository: string, + path: string +) { + const updateResponse = await context.clients.platformHttp.delete( + `/artifactory/api/storage/${repository}/${path}?properties=latest` + ); + + if (updateResponse.status !== 204) { + console.error( + `Failed to delete properties for ${path}, status code: ${updateResponse.status}` + ); + throw new Error("failed to remove property from an artifact"); + } +} + +async function setProperty( + context: PlatformContext, + repository: string, + path: string +) { + const properties = `latest=true`; + const updateResponse = await context.clients.platformHttp.put( + `/artifactory/api/storage/${repository}/${path}?properties=${properties}` + ); + + if (updateResponse.status !== 204) { + console.error( + `Failed to set property for artifact: ${path} with status code: ${updateResponse.status}` + ); + throw new Error("failed to set property"); + } +} From ffdd1986dca2bf59df2b8b060856bb912bd5ef24 Mon Sep 17 00:00:00 2001 From: Nandagopal Nambiar Date: Wed, 28 May 2025 17:21:34 +0530 Subject: [PATCH 2/3] Fixed file formatting --- .../build-property-setter/manifest.json | 28 +- .../build-property-setter/package.json | 4 +- .../build-property-setter/tsconfig.json | 26 +- .../build-property-setter/types.ts | 4 +- .../build-property-setter/worker.spec.ts | 43 ++- .../build-property-setter/worker.ts | 354 +++++++++--------- 6 files changed, 234 insertions(+), 225 deletions(-) diff --git a/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/manifest.json b/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/manifest.json index 4093334..f54cec8 100644 --- a/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/manifest.json +++ b/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/manifest.json @@ -1,16 +1,16 @@ { - "name": "build-property-setter", - "description": "Removes 'latest=true' from previous build artifacts and sets it for current build artifacts after build info is saved.", - "filterCriteria": { - "artifactFilterCriteria": { - "repoKeys": [ - "example-repo-local" - ] - } - }, - "secrets": {}, - "sourceCodePath": "./worker.ts", - "action": "AFTER_BUILD_INFO_SAVE", - "enabled": false, - "debug": true + "name": "build-property-setter", + "description": "Removes 'latest=true' from previous build artifacts and sets it for current build artifacts after build info is saved.", + "filterCriteria": { + "artifactFilterCriteria": { + "repoKeys": [ + "example-repo-local" + ] + } + }, + "secrets": {}, + "sourceCodePath": "./worker.ts", + "action": "AFTER_BUILD_INFO_SAVE", + "enabled": false, + "debug": true } \ No newline at end of file diff --git a/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/package.json b/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/package.json index 8b0c78a..fb3865e 100644 --- a/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/package.json +++ b/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/package.json @@ -26,7 +26,9 @@ "clearMocks": true, "maxConcurrency": 1, "testRegex": "\\.spec\\.ts$", - "moduleDirectories": ["node_modules"], + "moduleDirectories": [ + "node_modules" + ], "collectCoverageFrom": [ "**/*.ts" ], diff --git a/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/tsconfig.json b/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/tsconfig.json index 21b69a5..2d19407 100644 --- a/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/tsconfig.json +++ b/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/tsconfig.json @@ -1,15 +1,15 @@ { - "compilerOptions": { - "module": "commonjs", - "declaration": true, - "target": "es2017", - "skipLibCheck": true, - "forceConsistentCasingInFileNames": false, - "noFallthroughCasesInSwitch": false, - "allowJs": true - }, - "include": [ - "**/*.ts", - "node_modules/@types/**/*.d.ts" - ] + "compilerOptions": { + "module": "commonjs", + "declaration": true, + "target": "es2017", + "skipLibCheck": true, + "forceConsistentCasingInFileNames": false, + "noFallthroughCasesInSwitch": false, + "allowJs": true + }, + "include": [ + "**/*.ts", + "node_modules/@types/**/*.d.ts" + ] } \ No newline at end of file diff --git a/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/types.ts b/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/types.ts index 9cf5668..70c3160 100644 --- a/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/types.ts +++ b/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/types.ts @@ -53,6 +53,6 @@ export interface AfterBuildInfoSaveRequest { } export interface ArtifactPathInfo { - repo: string; - path: string; + repo: string; + path: string; } diff --git a/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/worker.spec.ts b/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/worker.spec.ts index 8776574..c4923d0 100644 --- a/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/worker.spec.ts +++ b/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/worker.spec.ts @@ -1,6 +1,6 @@ -import { PlatformContext, PlatformClients, PlatformHttpClient, Status } from 'jfrog-workers'; -import { createMock, DeepMocked } from '@golevelup/ts-jest'; -import { AfterBuildInfoSaveRequest } from './types'; +import {PlatformContext, PlatformClients, PlatformHttpClient, Status} from 'jfrog-workers'; +import {createMock, DeepMocked} from '@golevelup/ts-jest'; +import {AfterBuildInfoSaveRequest} from './types'; import runWorker from './worker'; describe("build-property-setter worker", () => { @@ -13,10 +13,10 @@ describe("build-property-setter worker", () => { platformHttp: createMock({ get: jest.fn().mockResolvedValue({ status: 200, - data: { buildsNumbers: [], buildInfo: { modules: [] } } + data: {buildsNumbers: [], buildInfo: {modules: []}} }), - put: jest.fn().mockResolvedValue({ status: 204 }), - delete: jest.fn().mockResolvedValue({ status: 204 }) + put: jest.fn().mockResolvedValue({status: 204}), + delete: jest.fn().mockResolvedValue({status: 204}) }) }) }); @@ -32,7 +32,7 @@ describe("build-property-setter worker", () => { // Simulate no previous builds (context.clients.platformHttp.get as jest.Mock).mockResolvedValueOnce({ status: 200, - data: { buildsNumbers: [], buildInfo: { modules: [] } } + data: {buildsNumbers: [], buildInfo: {modules: []}} }); await expect(runWorker(context, request)).resolves.toEqual( @@ -47,8 +47,8 @@ describe("build-property-setter worker", () => { (context.clients.platformHttp.get as jest.Mock).mockResolvedValueOnce({ status: 200, data: { - buildsNumbers: [{ uri: '/12356' }], - buildInfo: { modules: [] } + buildsNumbers: [{uri: '/12356'}], + buildInfo: {modules: []} } }); @@ -60,7 +60,13 @@ describe("build-property-setter worker", () => { { id: 'module1', artifacts: [ - { name: 'artifact1.jar', sha1: 'abc', md5: 'def', type: 'jar', remotePath: 'repo/path/artifact1.jar' } + { + name: 'artifact1.jar', + sha1: 'abc', + md5: 'def', + type: 'jar', + remotePath: 'repo/path/artifact1.jar' + } ] } ] @@ -77,7 +83,7 @@ describe("build-property-setter worker", () => { // Mock: No previous builds (context.clients.platformHttp.get as jest.Mock).mockResolvedValueOnce({ status: 200, - data: { buildsNumbers: [], buildInfo: { modules: [] } } + data: {buildsNumbers: [], buildInfo: {modules: []}} }); // Mock: Current build details with one artifact (context.clients.platformHttp.get as jest.Mock).mockResolvedValueOnce({ @@ -115,8 +121,8 @@ describe("build-property-setter worker", () => { (context.clients.platformHttp.get as jest.Mock).mockResolvedValueOnce({ status: 200, data: { - buildsNumbers: [{ uri: '/12356' }], - buildInfo: { modules: [] } + buildsNumbers: [{uri: '/12356'}], + buildInfo: {modules: []} } }); @@ -125,13 +131,14 @@ describe("build-property-setter worker", () => { data: { buildInfo: { modules: [ - { id: 'module1' } // no artifacts property + {id: 'module1'} // no artifacts property ] } } }); - const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => { }); + const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => { + }); await runWorker(context, request); expect(consoleWarnSpy).toHaveBeenCalledWith("A module was skipped as no artifact array was found"); consoleWarnSpy.mockRestore(); @@ -146,7 +153,7 @@ describe("build-property-setter worker", () => { it('should return fail status if set property API call fails', async () => { (context.clients.platformHttp.get as jest.Mock).mockResolvedValueOnce({ status: 200, - data: { buildsNumbers: [], buildInfo: { modules: [] } } + data: {buildsNumbers: [], buildInfo: {modules: []}} }); // Mock: Current build details with one artifact (context.clients.platformHttp.get as jest.Mock).mockResolvedValueOnce({ @@ -191,8 +198,8 @@ describe("build-property-setter worker", () => { (context.clients.platformHttp.get as jest.Mock).mockResolvedValueOnce({ status: 200, data: { - buildsNumbers: [{ uri: '/12356' }], - buildInfo: { modules: [] } + buildsNumbers: [{uri: '/12356'}], + buildInfo: {modules: []} } }); diff --git a/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/worker.ts b/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/worker.ts index 92607c2..c86ba5e 100644 --- a/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/worker.ts +++ b/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/worker.ts @@ -1,229 +1,229 @@ -import { PlatformContext, Status } from "jfrog-workers"; -import { AfterBuildInfoSaveRequest, ArtifactPathInfo } from "./types"; +import {PlatformContext, Status} from "jfrog-workers"; +import {AfterBuildInfoSaveRequest, ArtifactPathInfo} from "./types"; export default async function oldBuildCleanup( - context: PlatformContext, - data: AfterBuildInfoSaveRequest + context: PlatformContext, + data: AfterBuildInfoSaveRequest ): Promise<{ message: string; executionStatus: Status }> { - try { - const buildName = data.build.name; - const currentBuildNumber = data.build.number; - const previousBuildResponse = await context.clients.platformHttp.get( - `/artifactory/api/build/${buildName}` - ); - - const previousBuildNumbers = new Set(); + try { + const buildName = data.build.name; + const currentBuildNumber = data.build.number; + const previousBuildResponse = await context.clients.platformHttp.get( + `/artifactory/api/build/${buildName}` + ); + + const previousBuildNumbers = new Set(); + + for (const build of previousBuildResponse.data.buildsNumbers) { + const buildNumber = build.uri.replace(/^\/+/, ""); + if (buildNumber != currentBuildNumber) { + previousBuildNumbers.add(buildNumber); + } + } - for (const build of previousBuildResponse.data.buildsNumbers) { - const buildNumber = build.uri.replace(/^\/+/, ""); - if (buildNumber != currentBuildNumber) { - previousBuildNumbers.add(buildNumber); - } - } + if (previousBuildNumbers.size >= 1) { + await processPropertyCleanupForPreviousBuilds( + context, + buildName, + previousBuildNumbers + ); + } - if (previousBuildNumbers.size >= 1) { - await processPropertyCleanupForPreviousBuilds( - context, - buildName, - previousBuildNumbers - ); + await processCurrentBuildForSettingProperty( + context, + buildName, + currentBuildNumber + ); + } catch (error) { + console.error( + `Request failed with status code ${error.status || ""} caused by: ${error.message + }` + ); + return { + message: "Error occurred", + executionStatus: Status.STATUS_FAIL, + }; } - - await processCurrentBuildForSettingProperty( - context, - buildName, - currentBuildNumber - ); - } catch (error) { - console.error( - `Request failed with status code ${error.status || ""} caused by: ${error.message - }` - ); return { - message: "Error occurred", - executionStatus: Status.STATUS_FAIL, + message: "Successfully set property for artifacts", + executionStatus: Status.STATUS_SUCCESS, }; - } - return { - message: "Successfully set property for artifacts", - executionStatus: Status.STATUS_SUCCESS, - }; } async function processPropertyCleanupForPreviousBuilds( - context: PlatformContext, - buildName: string, - previousBuildNumbers: Set + context: PlatformContext, + buildName: string, + previousBuildNumbers: Set ) { - console.info( - "Finding all artifacts with latest set from the build numbers: " + - Array.from(previousBuildNumbers) - ); - const artifactsForPropertyRemoval = new Map(); - - for (const buildNumber of previousBuildNumbers) { - await fetchAndExtractBuildDetailsForPropertyRemoval( - context, - buildName, - buildNumber, - artifactsForPropertyRemoval + console.info( + "Finding all artifacts with latest set from the build numbers: " + + Array.from(previousBuildNumbers) ); - } + const artifactsForPropertyRemoval = new Map(); + + for (const buildNumber of previousBuildNumbers) { + await fetchAndExtractBuildDetailsForPropertyRemoval( + context, + buildName, + buildNumber, + artifactsForPropertyRemoval + ); + } - for (const artifactInfo of artifactsForPropertyRemoval.values()) { - await deleteProperty(context, artifactInfo.repo, artifactInfo.path); - } + for (const artifactInfo of artifactsForPropertyRemoval.values()) { + await deleteProperty(context, artifactInfo.repo, artifactInfo.path); + } } async function processCurrentBuildForSettingProperty( - context: PlatformContext, - buildName: string, - currentBuildNumber: string + context: PlatformContext, + buildName: string, + currentBuildNumber: string ) { - console.info( - `Setting property latest to value true for artifacts in build number ${currentBuildNumber}` - ); - const artifactSet = new Set(); - - await fetchAndExtractBuildDetailsForPropertyUpdate( - context, - buildName, - currentBuildNumber, - artifactSet - ); - - for (const artifactInfo of artifactSet) { - await setProperty(context, artifactInfo.repo, artifactInfo.path); - } + console.info( + `Setting property latest to value true for artifacts in build number ${currentBuildNumber}` + ); + const artifactSet = new Set(); + + await fetchAndExtractBuildDetailsForPropertyUpdate( + context, + buildName, + currentBuildNumber, + artifactSet + ); + + for (const artifactInfo of artifactSet) { + await setProperty(context, artifactInfo.repo, artifactInfo.path); + } } async function populateArtifactsForPropertyRemoval( - modules: any, - artifactsForPropertyRemoval: Map + modules: any, + artifactsForPropertyRemoval: Map ) { - for (const module of modules) { - if (Array.isArray(module.artifacts)) { - for (const artifact of module.artifacts) { - const artifactFullPath = - artifact.originalDeploymentRepo + "/" + artifact.path; - if (!artifactsForPropertyRemoval.has(artifactFullPath)) { - const artifactPathInfo: ArtifactPathInfo = { - repo: artifact.originalDeploymentRepo, - path: artifact.path, - }; - artifactsForPropertyRemoval.set(artifactFullPath, artifactPathInfo); + for (const module of modules) { + if (Array.isArray(module.artifacts)) { + for (const artifact of module.artifacts) { + const artifactFullPath = + artifact.originalDeploymentRepo + "/" + artifact.path; + if (!artifactsForPropertyRemoval.has(artifactFullPath)) { + const artifactPathInfo: ArtifactPathInfo = { + repo: artifact.originalDeploymentRepo, + path: artifact.path, + }; + artifactsForPropertyRemoval.set(artifactFullPath, artifactPathInfo); + } + } + } else { + console.warn("A module was skipped as no artifact array was found"); } - } - } else { - console.warn("A module was skipped as no artifact array was found"); } - } } async function populateArtifactsForPropertyUpdate( - modules: any, - artifactsForPropertyUpdate: Set + modules: any, + artifactsForPropertyUpdate: Set ) { - for (const module of modules) { - if (Array.isArray(module.artifacts)) { - for (const artifact of module.artifacts) { - const artifactPathInfo: ArtifactPathInfo = { - repo: artifact.originalDeploymentRepo, - path: artifact.path, - }; - artifactsForPropertyUpdate.add(artifactPathInfo); - } - } else { - console.warn(`A module was skipped as no artifact array was found`); + for (const module of modules) { + if (Array.isArray(module.artifacts)) { + for (const artifact of module.artifacts) { + const artifactPathInfo: ArtifactPathInfo = { + repo: artifact.originalDeploymentRepo, + path: artifact.path, + }; + artifactsForPropertyUpdate.add(artifactPathInfo); + } + } else { + console.warn(`A module was skipped as no artifact array was found`); + } } - } } async function fetchAndExtractBuildDetailsForPropertyRemoval( - context: PlatformContext, - buildName: string, - buildNumber: string, - artifactsForPropertyRemoval: Map + context: PlatformContext, + buildName: string, + buildNumber: string, + artifactsForPropertyRemoval: Map ) { - const buildResponse = await context.clients.platformHttp.get( - `/artifactory/api/build/${buildName}/${buildNumber}` - ); - if (buildResponse.status === 200) { - const buildData = buildResponse.data; - const modules = buildData.buildInfo.modules; - if (Array.isArray(modules)) { - populateArtifactsForPropertyRemoval(modules, artifactsForPropertyRemoval); + const buildResponse = await context.clients.platformHttp.get( + `/artifactory/api/build/${buildName}/${buildNumber}` + ); + if (buildResponse.status === 200) { + const buildData = buildResponse.data; + const modules = buildData.buildInfo.modules; + if (Array.isArray(modules)) { + populateArtifactsForPropertyRemoval(modules, artifactsForPropertyRemoval); + } else { + console.warn( + "No modules found in the build data or modules is not an array" + ); + } } else { - console.warn( - "No modules found in the build data or modules is not an array" - ); + console.warn( + `Failed to retrieve build data, status code: ${buildResponse.status}` + ); + throw new Error(`build fetch failed for build number: ${buildNumber}`); } - } else { - console.warn( - `Failed to retrieve build data, status code: ${buildResponse.status}` - ); - throw new Error(`build fetch failed for build number: ${buildNumber}`); - } } async function fetchAndExtractBuildDetailsForPropertyUpdate( - context: PlatformContext, - buildName: string, - buildNumber: string, - artifactsForPropertyUpdate: Set + context: PlatformContext, + buildName: string, + buildNumber: string, + artifactsForPropertyUpdate: Set ) { - const buildResponse = await context.clients.platformHttp.get( - `/artifactory/api/build/${buildName}/${buildNumber}` - ); - if (buildResponse.status === 200) { - const buildData = buildResponse.data; - const modules = buildData.buildInfo.modules; - if (Array.isArray(modules)) { - populateArtifactsForPropertyUpdate(modules, artifactsForPropertyUpdate); + const buildResponse = await context.clients.platformHttp.get( + `/artifactory/api/build/${buildName}/${buildNumber}` + ); + if (buildResponse.status === 200) { + const buildData = buildResponse.data; + const modules = buildData.buildInfo.modules; + if (Array.isArray(modules)) { + populateArtifactsForPropertyUpdate(modules, artifactsForPropertyUpdate); + } else { + console.warn( + "No modules found in the build data or modules is not an array" + ); + } } else { - console.warn( - "No modules found in the build data or modules is not an array" - ); + console.warn( + `Failed to retrieve build data, status code: ${buildResponse.status}` + ); + throw new Error(`build fetch failed for build number: ${buildNumber}`); } - } else { - console.warn( - `Failed to retrieve build data, status code: ${buildResponse.status}` - ); - throw new Error(`build fetch failed for build number: ${buildNumber}`); - } } async function deleteProperty( - context: PlatformContext, - repository: string, - path: string + context: PlatformContext, + repository: string, + path: string ) { - const updateResponse = await context.clients.platformHttp.delete( - `/artifactory/api/storage/${repository}/${path}?properties=latest` - ); - - if (updateResponse.status !== 204) { - console.error( - `Failed to delete properties for ${path}, status code: ${updateResponse.status}` + const updateResponse = await context.clients.platformHttp.delete( + `/artifactory/api/storage/${repository}/${path}?properties=latest` ); - throw new Error("failed to remove property from an artifact"); - } + + if (updateResponse.status !== 204) { + console.error( + `Failed to delete properties for ${path}, status code: ${updateResponse.status}` + ); + throw new Error("failed to remove property from an artifact"); + } } async function setProperty( - context: PlatformContext, - repository: string, - path: string + context: PlatformContext, + repository: string, + path: string ) { - const properties = `latest=true`; - const updateResponse = await context.clients.platformHttp.put( - `/artifactory/api/storage/${repository}/${path}?properties=${properties}` - ); - - if (updateResponse.status !== 204) { - console.error( - `Failed to set property for artifact: ${path} with status code: ${updateResponse.status}` + const properties = `latest=true`; + const updateResponse = await context.clients.platformHttp.put( + `/artifactory/api/storage/${repository}/${path}?properties=${properties}` ); - throw new Error("failed to set property"); - } + + if (updateResponse.status !== 204) { + console.error( + `Failed to set property for artifact: ${path} with status code: ${updateResponse.status}` + ); + throw new Error("failed to set property"); + } } From 5e1ee92bd9a8e32528f9885ca7ecf22ac59faaa3 Mon Sep 17 00:00:00 2001 From: Nandagopal Nambiar Date: Mon, 2 Jun 2025 11:34:14 +0530 Subject: [PATCH 3/3] Corrected method name --- .../AFTER_BUILD_INFO_SAVE/build-property-setter/worker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/worker.ts b/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/worker.ts index c86ba5e..d25f34a 100644 --- a/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/worker.ts +++ b/samples/artifactory/AFTER_BUILD_INFO_SAVE/build-property-setter/worker.ts @@ -1,7 +1,7 @@ import {PlatformContext, Status} from "jfrog-workers"; import {AfterBuildInfoSaveRequest, ArtifactPathInfo} from "./types"; -export default async function oldBuildCleanup( +export default async function setBuildProperty( context: PlatformContext, data: AfterBuildInfoSaveRequest ): Promise<{ message: string; executionStatus: Status }> {