├── .gitignore ├── api ├── .gitignore ├── tsconfig.json └── package.json ├── samples ├── artifactory │ ├── GENERIC_EVENT │ │ ├── repoStats │ │ │ ├── payload-example.json │ │ │ ├── manifest.json │ │ │ ├── tsconfig.json │ │ │ ├── package.json │ │ │ ├── README.md │ │ │ ├── worker.spec.ts │ │ │ └── worker.ts │ │ ├── delete-deprecated │ │ │ ├── payload-example.json │ │ │ ├── manifest.json │ │ │ ├── tsconfig.json │ │ │ ├── package.json │ │ │ ├── worker.spec.ts │ │ │ └── README.md │ │ ├── oldBuildCleanup │ │ │ ├── payload-example.json │ │ │ ├── manifest.json │ │ │ ├── tsconfig.json │ │ │ ├── package.json │ │ │ ├── worker.spec.ts │ │ │ ├── README.md │ │ │ └── worker.ts │ │ ├── delete-empty-dirs │ │ │ ├── payload-example.json │ │ │ ├── tsconfig.json │ │ │ ├── manifest.json │ │ │ ├── package.json │ │ │ ├── worker.spec.ts │ │ │ └── README.md │ │ ├── remote-backup │ │ │ ├── payload-example.json │ │ │ ├── manifest.json │ │ │ ├── tsconfig.json │ │ │ └── package.json │ │ ├── clean-docker-images │ │ │ ├── payload-example.json │ │ │ ├── manifest.json │ │ │ ├── tsconfig.json │ │ │ ├── package.json │ │ │ └── worker.spec.ts │ │ ├── delete-by-property-value │ │ │ ├── payload-example.json │ │ │ ├── tsconfig.json │ │ │ ├── manifest.json │ │ │ ├── package.json │ │ │ └── worker.spec.ts │ │ ├── artifact-cleanup │ │ │ ├── payload-example.json │ │ │ ├── manifest.json │ │ │ ├── tsconfig.json │ │ │ ├── package.json │ │ │ ├── worker.spec.ts │ │ │ └── README.md │ │ ├── staging │ │ │ ├── package.json │ │ │ ├── tsconfig.json │ │ │ └── manifest.json │ │ └── README.md │ ├── AFTER_CREATE │ │ └── copy-deb-and-rpm │ │ │ ├── manifest.json │ │ │ ├── tsconfig.json │ │ │ ├── worker.spec.ts │ │ │ ├── package.json │ │ │ ├── types.ts │ │ │ └── README.md │ ├── BEFORE_UPLOAD │ │ ├── repo-quota │ │ │ ├── tsconfig.json │ │ │ ├── manifest.json │ │ │ ├── package.json │ │ │ ├── worker.spec.ts │ │ │ └── types.ts │ │ ├── restrict-overwrite │ │ │ ├── tsconfig.json │ │ │ ├── manifest.json │ │ │ ├── package.json │ │ │ ├── worker.spec.ts │ │ │ ├── worker.ts │ │ │ ├── README.md │ │ │ └── types.ts │ │ ├── allow-upload-by-pattern │ │ │ ├── tsconfig.json │ │ │ ├── manifest.json │ │ │ ├── package.json │ │ │ ├── worker.spec.ts │ │ │ ├── worker.ts │ │ │ ├── README.md │ │ │ └── types.ts │ │ └── check-project-repository │ │ │ ├── tsconfig.json │ │ │ ├── manifest.json │ │ │ ├── package.json │ │ │ ├── worker.spec.ts │ │ │ ├── payload.json │ │ │ └── types.ts │ ├── BEFORE_COPY │ │ └── restrict-overwrite │ │ │ ├── tsconfig.json │ │ │ ├── manifest.json │ │ │ ├── package.json │ │ │ ├── worker.spec.ts │ │ │ ├── worker.ts │ │ │ ├── README.md │ │ │ └── types.ts │ ├── BEFORE_CREATE │ │ └── restrict-overwrite │ │ │ ├── tsconfig.json │ │ │ ├── manifest.json │ │ │ ├── package.json │ │ │ ├── worker.spec.ts │ │ │ ├── worker.ts │ │ │ ├── types.ts │ │ │ └── README.md │ ├── BEFORE_MOVE │ │ └── restrict-overwrite │ │ │ ├── tsconfig.json │ │ │ ├── manifest.json │ │ │ ├── package.json │ │ │ ├── worker.spec.ts │ │ │ ├── worker.ts │ │ │ ├── types.ts │ │ │ └── README.md │ ├── AFTER_DOWNLOAD │ │ └── report-download-to-external-endpoint │ │ │ ├── tsconfig.json │ │ │ ├── manifest.json │ │ │ ├── worker.spec.ts │ │ │ ├── package.json │ │ │ ├── worker.ts │ │ │ ├── types.ts │ │ │ └── README.md │ ├── BEFORE_DOWNLOAD │ │ ├── restrict-download-by-property-value │ │ │ ├── tsconfig.json │ │ │ ├── manifest.json │ │ │ ├── package.json │ │ │ ├── worker.spec.ts │ │ │ ├── worker.ts │ │ │ ├── types.ts │ │ │ └── README.md │ │ ├── resctrict-download-if-no-xray-scan-since │ │ │ ├── tsconfig.json │ │ │ ├── manifest.json │ │ │ ├── package.json │ │ │ ├── worker.spec.ts │ │ │ ├── README.md │ │ │ └── types.ts │ │ └── restrict-download-on-xray-critical-issues │ │ │ ├── tsconfig.json │ │ │ ├── manifest.json │ │ │ ├── package.json │ │ │ ├── worker.spec.ts │ │ │ └── types.ts │ ├── BEFORE_PROPERTY_CREATE │ │ └── property-creation-permission │ │ │ ├── tsconfig.json │ │ │ ├── manifest.json │ │ │ ├── package.json │ │ │ ├── worker.spec.ts │ │ │ ├── worker.ts │ │ │ ├── README.md │ │ │ └── types.ts │ ├── eventCombinationWorkers │ │ └── mavenSnapshotCleanupWhenRelease │ │ │ ├── AFTER_MOVE │ │ │ └── mavenSnapshotCleanupWhenRelease_afterMove │ │ │ │ ├── tsconfig.json │ │ │ │ ├── manifest.json │ │ │ │ ├── worker.spec.ts │ │ │ │ ├── package.json │ │ │ │ └── worker.ts │ │ │ └── AFTER_CREATE │ │ │ └── mavenSnapshotCleanupWhenRelease_afterCreate │ │ │ ├── tsconfig.json │ │ │ ├── manifest.json │ │ │ ├── worker.spec.ts │ │ │ ├── package.json │ │ │ └── worker.ts │ └── README.md ├── access │ └── BEFORE_CREATE_TOKEN │ │ └── token-validation │ │ ├── manifest.json │ │ ├── tsconfig.json │ │ ├── README.md │ │ ├── package.json │ │ ├── worker.spec.ts │ │ └── types.ts └── runtime │ └── AFTER_WORKLOAD_STATE_CHANGE │ └── runtime-alerting │ ├── tsconfig.json │ ├── manifest.json │ ├── worker.spec.ts │ ├── package.json │ └── types.ts └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.tgz 3 | dist -------------------------------------------------------------------------------- /api/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | .npmrc -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/repoStats/payload-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": "./src/stats.txt" 3 | } -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/delete-deprecated/payload-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "repos": ["example-repo-local", "another-repo"], 3 | "dryRun": true 4 | } -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/oldBuildCleanup/payload-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "buildName": "test", 3 | "buildNumber": 10, 4 | "cleanArtifacts": true 5 | } -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/delete-empty-dirs/payload-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": [ "example-repo-local/dir1", "libs-release-local/dir2" ], 3 | "dryRun": true 4 | } -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/remote-backup/payload-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "backups": { 3 | "backup-remote-cache": "backup-local" 4 | }, 5 | "dryRun": true 6 | } -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/clean-docker-images/payload-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "dockerRepos": ["example-docker-local"], 3 | "byDownloadDate": false, 4 | "dryRun": true 5 | } 6 | -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/delete-by-property-value/payload-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "repos": [ "example-repo-local" ], 3 | "properties": { 4 | "property1": 15, 5 | "property2": 17 6 | }, 7 | "dryRun": true 8 | } -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/artifact-cleanup/payload-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "repos": [ 3 | "example-repo-local" 4 | ], 5 | "timeUnit": "minute", 6 | "timeInterval": 1, 7 | "dryRun": true, 8 | "disablePropertiesSupport": true, 9 | "limit": 100, 10 | "concurrency": 10 11 | } -------------------------------------------------------------------------------- /samples/access/BEFORE_CREATE_TOKEN/token-validation/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "token-validation", 3 | "description": "Validate tokens before they are created", 4 | "secrets": {}, 5 | "sourceCodePath": "./worker.ts", 6 | "action": "BEFORE_CREATE_TOKEN", 7 | "enabled": true, 8 | "debug": true 9 | } -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/repoStats/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "repoStats", 3 | "description": "This Worker displays certain statistics about requested files.", 4 | "secrets": {}, 5 | "sourceCodePath": "./worker.ts", 6 | "action": "GENERIC_EVENT", 7 | "enabled": false, 8 | "debug": true 9 | } -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/oldBuildCleanup/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "oldBuildCleanup", 3 | "description": "Deletes all old builds that are older than buildNumber you provide", 4 | "secrets": {}, 5 | "sourceCodePath": "./worker.ts", 6 | "action": "GENERIC_EVENT", 7 | "enabled": false, 8 | "debug": true 9 | } -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/delete-deprecated/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "delete-deprecated", 3 | "description": "Deletes all artifacts marked with the property analysis.deprecated=true", 4 | "secrets": {}, 5 | "sourceCodePath": "./worker.ts", 6 | "action": "GENERIC_EVENT", 7 | "enabled": false, 8 | "debug": true 9 | } -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/staging/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "staging", 3 | "version": "1.0.0", 4 | "description": "Returns staging data based on strategy name", 5 | "main": "worker.ts", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Shashank Taliwal", 10 | "license": "ISC", 11 | "dependencies": { 12 | "jfrog-workers": "^0.8.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/remote-backup/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "remote-backup", 3 | "description": "This worker copies files from a remote cache to a local 'backup' repository. This ensures that cached artifacts are still available, even after they're removed from the cache.", 4 | "secrets": {}, 5 | "sourceCodePath": "./worker.ts", 6 | "action": "GENERIC_EVENT", 7 | "enabled": false, 8 | "debug": true 9 | } -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/artifact-cleanup/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "artifact-cleanup", 3 | "description": "This worker code deletes all artifacts that have not been downloaded for the past 'n time units',which is by default 1 month. It can be run manually from the REST API, using a worker generic event.", 4 | "secrets": {}, 5 | "sourceCodePath": "./worker.ts", 6 | "action": "GENERIC_EVENT", 7 | "enabled": false, 8 | "debug": true 9 | } -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/clean-docker-images/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clean-docker-images", 3 | "description": "This worker script is designed to clean Docker repositories hosted in Artifactory based on configurable cleanup policies. It is useful for managing storage and maintaining repository hygiene.", 4 | "secrets": {}, 5 | "sourceCodePath": "./worker.ts", 6 | "action": "GENERIC_EVENT", 7 | "enabled": false, 8 | "debug": true 9 | } -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/repoStats/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2017", 6 | "skipLibCheck": true, 7 | "forceConsistentCasingInFileNames": false, 8 | "noFallthroughCasesInSwitch": false, 9 | "allowJs": true 10 | }, 11 | "include": [ 12 | "**/*.ts", 13 | "node_modules/@types/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/staging/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2017", 6 | "skipLibCheck": true, 7 | "forceConsistentCasingInFileNames": false, 8 | "noFallthroughCasesInSwitch": false, 9 | "allowJs": true 10 | }, 11 | "include": [ 12 | "**/*.ts", 13 | "node_modules/@types/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /samples/access/BEFORE_CREATE_TOKEN/token-validation/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2017", 6 | "skipLibCheck": true, 7 | "forceConsistentCasingInFileNames": false, 8 | "noFallthroughCasesInSwitch": false, 9 | "allowJs": true 10 | }, 11 | "include": [ 12 | "**/*.ts", 13 | "node_modules/@types/**/*.d.ts" 14 | ] 15 | } -------------------------------------------------------------------------------- /samples/artifactory/AFTER_CREATE/copy-deb-and-rpm/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "copy-deb-and-rpm", 3 | "description": "Copy deb and rpm", 4 | "filterCriteria": { 5 | "artifactFilterCriteria": { 6 | "repoKeys": [ 7 | "example-repo-local" 8 | ] 9 | } 10 | }, 11 | "secrets": {}, 12 | "sourceCodePath": "./worker.ts", 13 | "action": "AFTER_CREATE", 14 | "enabled": false, 15 | "debug": true 16 | } -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_UPLOAD/repo-quota/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2017", 6 | "skipLibCheck": true, 7 | "forceConsistentCasingInFileNames": false, 8 | "noFallthroughCasesInSwitch": false, 9 | "allowJs": true 10 | }, 11 | "include": [ 12 | "**/*.ts", 13 | "node_modules/@types/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/remote-backup/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2017", 6 | "skipLibCheck": true, 7 | "forceConsistentCasingInFileNames": false, 8 | "noFallthroughCasesInSwitch": false, 9 | "allowJs": true 10 | }, 11 | "include": [ 12 | "**/*.ts", 13 | "node_modules/@types/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /samples/artifactory/AFTER_CREATE/copy-deb-and-rpm/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2017", 6 | "skipLibCheck": true, 7 | "forceConsistentCasingInFileNames": false, 8 | "noFallthroughCasesInSwitch": false, 9 | "allowJs": true 10 | }, 11 | "include": [ 12 | "**/*.ts", 13 | "node_modules/@types/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_COPY/restrict-overwrite/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2017", 6 | "skipLibCheck": true, 7 | "forceConsistentCasingInFileNames": false, 8 | "noFallthroughCasesInSwitch": false, 9 | "allowJs": true 10 | }, 11 | "include": [ 12 | "**/*.ts", 13 | "node_modules/@types/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_CREATE/restrict-overwrite/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2017", 6 | "skipLibCheck": true, 7 | "forceConsistentCasingInFileNames": false, 8 | "noFallthroughCasesInSwitch": false, 9 | "allowJs": true 10 | }, 11 | "include": [ 12 | "**/*.ts", 13 | "node_modules/@types/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_MOVE/restrict-overwrite/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2017", 6 | "skipLibCheck": true, 7 | "forceConsistentCasingInFileNames": false, 8 | "noFallthroughCasesInSwitch": false, 9 | "allowJs": true 10 | }, 11 | "include": [ 12 | "**/*.ts", 13 | "node_modules/@types/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_UPLOAD/restrict-overwrite/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2017", 6 | "skipLibCheck": true, 7 | "forceConsistentCasingInFileNames": false, 8 | "noFallthroughCasesInSwitch": false, 9 | "allowJs": true 10 | }, 11 | "include": [ 12 | "**/*.ts", 13 | "node_modules/@types/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/artifact-cleanup/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2017", 6 | "skipLibCheck": true, 7 | "forceConsistentCasingInFileNames": false, 8 | "noFallthroughCasesInSwitch": false, 9 | "allowJs": true 10 | }, 11 | "include": [ 12 | "**/*.ts", 13 | "node_modules/@types/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/clean-docker-images/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2017", 6 | "skipLibCheck": true, 7 | "forceConsistentCasingInFileNames": false, 8 | "noFallthroughCasesInSwitch": false, 9 | "allowJs": true 10 | }, 11 | "include": [ 12 | "**/*.ts", 13 | "node_modules/@types/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/delete-deprecated/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2017", 6 | "skipLibCheck": true, 7 | "forceConsistentCasingInFileNames": false, 8 | "noFallthroughCasesInSwitch": false, 9 | "allowJs": true 10 | }, 11 | "include": [ 12 | "**/*.ts", 13 | "node_modules/@types/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/delete-empty-dirs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2017", 6 | "skipLibCheck": true, 7 | "forceConsistentCasingInFileNames": false, 8 | "noFallthroughCasesInSwitch": false, 9 | "allowJs": true 10 | }, 11 | "include": [ 12 | "**/*.ts", 13 | "node_modules/@types/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/oldBuildCleanup/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2017", 6 | "skipLibCheck": true, 7 | "forceConsistentCasingInFileNames": false, 8 | "noFallthroughCasesInSwitch": false, 9 | "allowJs": true 10 | }, 11 | "include": [ 12 | "**/*.ts", 13 | "node_modules/@types/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_UPLOAD/allow-upload-by-pattern/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2017", 6 | "skipLibCheck": true, 7 | "forceConsistentCasingInFileNames": false, 8 | "noFallthroughCasesInSwitch": false, 9 | "allowJs": true 10 | }, 11 | "include": [ 12 | "**/*.ts", 13 | "node_modules/@types/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_UPLOAD/check-project-repository/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2017", 6 | "skipLibCheck": true, 7 | "forceConsistentCasingInFileNames": false, 8 | "noFallthroughCasesInSwitch": false, 9 | "allowJs": true 10 | }, 11 | "include": [ 12 | "**/*.ts", 13 | "node_modules/@types/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/delete-by-property-value/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2017", 6 | "skipLibCheck": true, 7 | "forceConsistentCasingInFileNames": false, 8 | "noFallthroughCasesInSwitch": false, 9 | "allowJs": true 10 | }, 11 | "include": [ 12 | "**/*.ts", 13 | "node_modules/@types/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /samples/access/BEFORE_CREATE_TOKEN/token-validation/README.md: -------------------------------------------------------------------------------- 1 | # Before Create Token example 2 | 3 | The worker enforces 3 checks before allowing tokens creation. 4 | 5 | * The number of tokens owned by the subject should not exceeds a specific count. 6 | * If it is a user token that is been created its expiry should not exceeds 1 month (the duration can be parameterized) 7 | * If it is a service token that is been created its expiry should not exceeds 1 year (the duration can be parameterized) 8 | -------------------------------------------------------------------------------- /samples/runtime/AFTER_WORKLOAD_STATE_CHANGE/runtime-alerting/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2017", 6 | "skipLibCheck": true, 7 | "forceConsistentCasingInFileNames": false, 8 | "noFallthroughCasesInSwitch": false, 9 | "allowJs": true 10 | }, 11 | "include": [ 12 | "**/*.ts", 13 | "node_modules/@types/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /samples/artifactory/AFTER_DOWNLOAD/report-download-to-external-endpoint/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2017", 6 | "skipLibCheck": true, 7 | "forceConsistentCasingInFileNames": false, 8 | "noFallthroughCasesInSwitch": false, 9 | "allowJs": true 10 | }, 11 | "include": [ 12 | "**/*.ts", 13 | "node_modules/@types/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_DOWNLOAD/restrict-download-by-property-value/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2017", 6 | "skipLibCheck": true, 7 | "forceConsistentCasingInFileNames": false, 8 | "noFallthroughCasesInSwitch": false, 9 | "allowJs": true 10 | }, 11 | "include": [ 12 | "**/*.ts", 13 | "node_modules/@types/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_PROPERTY_CREATE/property-creation-permission/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2017", 6 | "skipLibCheck": true, 7 | "forceConsistentCasingInFileNames": false, 8 | "noFallthroughCasesInSwitch": false, 9 | "allowJs": true 10 | }, 11 | "include": [ 12 | "**/*.ts", 13 | "node_modules/@types/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_DOWNLOAD/resctrict-download-if-no-xray-scan-since/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2017", 6 | "skipLibCheck": true, 7 | "forceConsistentCasingInFileNames": false, 8 | "noFallthroughCasesInSwitch": false, 9 | "allowJs": true 10 | }, 11 | "include": [ 12 | "**/*.ts", 13 | "node_modules/@types/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_DOWNLOAD/restrict-download-on-xray-critical-issues/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2017", 6 | "skipLibCheck": true, 7 | "forceConsistentCasingInFileNames": false, 8 | "noFallthroughCasesInSwitch": false, 9 | "allowJs": true 10 | }, 11 | "include": [ 12 | "**/*.ts", 13 | "node_modules/@types/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/delete-empty-dirs/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "delete-empty-dirs", 3 | "description": "This worker deletes all empty directories found inside any of the given set of paths in your Artifactory repositories. The worker traverses specified directories recursively, checking for empty directories, and deletes them if they are found.", 4 | "secrets": {}, 5 | "sourceCodePath": "./worker.ts", 6 | "action": "GENERIC_EVENT", 7 | "enabled": false, 8 | "debug": true 9 | } -------------------------------------------------------------------------------- /api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "NodeNext", 4 | "moduleResolution": "NodeNext", 5 | "declaration": true, 6 | "target": "es2017", 7 | "skipLibCheck": true, 8 | "forceConsistentCasingInFileNames": false, 9 | "noFallthroughCasesInSwitch": false, 10 | "lib": [ 11 | "ES2021" 12 | ], 13 | "outDir": "./dist" 14 | }, 15 | "include": [ 16 | "**/*.ts", 17 | "node_modules/@types/**/*.d.ts" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /samples/artifactory/eventCombinationWorkers/mavenSnapshotCleanupWhenRelease/AFTER_MOVE/mavenSnapshotCleanupWhenRelease_afterMove/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2017", 6 | "skipLibCheck": true, 7 | "forceConsistentCasingInFileNames": false, 8 | "noFallthroughCasesInSwitch": false, 9 | "allowJs": true 10 | }, 11 | "include": [ 12 | "**/*.ts", 13 | "node_modules/@types/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /samples/artifactory/eventCombinationWorkers/mavenSnapshotCleanupWhenRelease/AFTER_CREATE/mavenSnapshotCleanupWhenRelease_afterCreate/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2017", 6 | "skipLibCheck": true, 7 | "forceConsistentCasingInFileNames": false, 8 | "noFallthroughCasesInSwitch": false, 9 | "allowJs": true 10 | }, 11 | "include": [ 12 | "**/*.ts", 13 | "node_modules/@types/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /samples/runtime/AFTER_WORKLOAD_STATE_CHANGE/runtime-alerting/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "runtime-alerting", 3 | "description": "Run a script on AFTER_WORKLOAD_STATE_CHANGE", 4 | "sourceCodePath": "./worker.ts", 5 | "action": "AFTER_WORKLOAD_STATE_CHANGE", 6 | "enabled": false, 7 | "debug": false, 8 | "projectKey": "", 9 | "secrets": { 10 | "FAB": "dCsFdZIc1kvG8lYrI6LS6bOlQogC9ozh7vPykzqfzCAHb+OUizCw7sNcOMF46JnIV6l2zNWhpOl6ZNPX+L+A9laLq5egrkGXfPcLkVin1U4qj2ab" 11 | }, 12 | "filterCriteria": {}, 13 | "application": "runtime" 14 | } 15 | -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/delete-by-property-value/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "delete-by-property-value", 3 | "description": "This worker is designed to delete artifacts from specified repositories based on a property and its value. It allows you to define thresholds for properties and delete artifacts with property values below these thresholds. The worker also supports a dry-run mode to simulate the operation without making any changes.", 4 | "secrets": {}, 5 | "sourceCodePath": "./worker.ts", 6 | "action": "GENERIC_EVENT", 7 | "enabled": false, 8 | "debug": true 9 | } -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/staging/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "staging-strategy-calculator", 3 | "description": "This JFrog Worker calculates the staging strategy for Artifactory builds based on configurable strategies. The worker supports multiple strategies such as 'Gradle', 'Simple Maven', and 'Detailed Maven'. It fetches build information from Artifactory and determines the appropriate release version, target repository, tagging details, and more.", 4 | "secrets": {}, 5 | "sourceCodePath": "./worker.ts", 6 | "action": "GENERIC_EVENT", 7 | "enabled": false, 8 | "debug": true 9 | } -------------------------------------------------------------------------------- /samples/artifactory/eventCombinationWorkers/mavenSnapshotCleanupWhenRelease/AFTER_MOVE/mavenSnapshotCleanupWhenRelease_afterMove/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mavenSnapshotCleanupWhenRelease_afterMove", 3 | "description": "Deletes Maven SNAPSHOT artifacts when the release version is published", 4 | "filterCriteria": { 5 | "artifactFilterCriteria": { 6 | "repoKeys": [ 7 | "example-repo-local" 8 | ] 9 | } 10 | }, 11 | "secrets": {}, 12 | "sourceCodePath": "./worker.ts", 13 | "action": "AFTER_MOVE", 14 | "enabled": false, 15 | "debug": true 16 | } -------------------------------------------------------------------------------- /samples/artifactory/eventCombinationWorkers/mavenSnapshotCleanupWhenRelease/AFTER_CREATE/mavenSnapshotCleanupWhenRelease_afterCreate/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mavenSnapshotCleanupWhenRelease_afterCreate", 3 | "description": "Deletes Maven SNAPSHOT artifacts when the release version is published", 4 | "filterCriteria": { 5 | "artifactFilterCriteria": { 6 | "repoKeys": [ 7 | "example-repo-local" 8 | ] 9 | } 10 | }, 11 | "secrets": {}, 12 | "sourceCodePath": "./worker.ts", 13 | "action": "AFTER_CREATE", 14 | "enabled": false, 15 | "debug": true 16 | } -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_PROPERTY_CREATE/property-creation-permission/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "property-creation-permission", 3 | "description": "This worker is triggered by the `BEFORE_PROPERTY_CREATE` event in JFrog Artifactory. Its primary purpose is to enforce a policy where only users with admin privileges can create artifact properties.", 4 | "secrets": {}, 5 | "filterCriteria": { 6 | "artifactFilterCriteria": { 7 | "repoKeys": [ 8 | "example-repo-local" 9 | ] 10 | } 11 | }, 12 | "sourceCodePath": "./worker.ts", 13 | "action": "BEFORE_PROPERTY_CREATE", 14 | "enabled": true, 15 | "debug": true 16 | } -------------------------------------------------------------------------------- /samples/artifactory/AFTER_DOWNLOAD/report-download-to-external-endpoint/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "report-download-to-external-endpoint", 3 | "description": "This worker listens to the `AFTER_DOWNLOAD` event in JFrog Artifactory and notifies an external endpoint when a file is downloaded. The worker logs the artifact's details, the repository, and the user responsible for the download.", 4 | "filterCriteria": { 5 | "artifactFilterCriteria": { 6 | "repoKeys": [ 7 | "example-repo-local" 8 | ] 9 | } 10 | }, 11 | "secrets": {}, 12 | "sourceCodePath": "./worker.ts", 13 | "action": "AFTER_DOWNLOAD", 14 | "enabled": false, 15 | "debug": true 16 | } -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_UPLOAD/restrict-overwrite/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "restrict-overwrite", 3 | "description": "This worker script ensures that overwrites are restricted during the upload of artifacts to configured repositories in JFrog Artifactory. The worker validates if an artifact already exists at the target location and stops the upload process if the file or folder is present.", 4 | "filterCriteria": { 5 | "artifactFilterCriteria": { 6 | "repoKeys": [ 7 | "example-repo-local" 8 | ] 9 | } 10 | }, 11 | "secrets": {}, 12 | "sourceCodePath": "./worker.ts", 13 | "action": "BEFORE_UPLOAD", 14 | "enabled": false, 15 | "debug": true 16 | } -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_COPY/restrict-overwrite/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "restrict-overwrite", 3 | "description": "The \"Restrict Overwrite: Before Copy\" worker script enforces immutability by preventing overwrites of artifacts during the \"copy\" operation in JFrog Artifactory. It ensures that artifacts cannot be accidentally replaced, maintaining consistency and integrity across deployments.", 4 | "filterCriteria": { 5 | "artifactFilterCriteria": { 6 | "repoKeys": [ 7 | "example-repo-local" 8 | ] 9 | } 10 | }, 11 | "secrets": {}, 12 | "sourceCodePath": "./worker.ts", 13 | "action": "BEFORE_COPY", 14 | "enabled": false, 15 | "debug": true 16 | } -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_MOVE/restrict-overwrite/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "restrict-overwrite", 3 | "description": "The \"Restrict Overwrite: Before Move\" worker script enforces immutability by preventing overwrites of artifacts during the \"move\" operation in JFrog Artifactory. It ensures that artifacts cannot be accidentally replaced, maintaining consistency and integrity across deployments.", 4 | "filterCriteria": { 5 | "artifactFilterCriteria": { 6 | "repoKeys": [ 7 | "example-repo-local" 8 | ] 9 | } 10 | }, 11 | "secrets": {}, 12 | "sourceCodePath": "./worker.ts", 13 | "action": "BEFORE_MOVE", 14 | "enabled": false, 15 | "debug": true 16 | } -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_CREATE/restrict-overwrite/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "restrict-overwrite", 3 | "description": "The \"Restrict Overwrite: Before Create\" worker script enforces immutability by preventing overwrites of artifacts during the \"create\" operation in JFrog Artifactory. It ensures that artifacts cannot be accidentally replaced, maintaining consistency and integrity across deployments.", 4 | "filterCriteria": { 5 | "artifactFilterCriteria": { 6 | "repoKeys": [ 7 | "example-repo-local" 8 | ] 9 | } 10 | }, 11 | "secrets": {}, 12 | "sourceCodePath": "./worker.ts", 13 | "action": "BEFORE_CREATE", 14 | "enabled": false, 15 | "debug": true 16 | } -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_UPLOAD/repo-quota/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "repo-quota", 3 | "description": "The 'Artifactory Storage Quota Worker' is a script designed to enforce storage limits for repository paths. It allows administrators to configure quotas for artifact storage under specific paths within a repository. If the combined size of existing artifacts and a new upload exceeds the defined quota, the upload is rejected.", 4 | "filterCriteria": { 5 | "artifactFilterCriteria": { 6 | "repoKeys": [ 7 | "example-repo-local" 8 | ] 9 | } 10 | }, 11 | "secrets": {}, 12 | "sourceCodePath": "./worker.ts", 13 | "action": "BEFORE_UPLOAD", 14 | "enabled": false, 15 | "debug": true 16 | } -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_UPLOAD/allow-upload-by-pattern/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "allow-upload-by-pattern", 3 | "description": "This worker script ensures that artifact uploads are allowed only if the target path matches a predefined pattern. This pattern is specified using a regular expression (`authorizedPathRegEx`). The script validates the repository path (`repoPath`) and blocks uploads that do not comply with the pattern.", 4 | "filterCriteria": { 5 | "artifactFilterCriteria": { 6 | "repoKeys": [ 7 | "example-repo-local" 8 | ] 9 | } 10 | }, 11 | "secrets": {}, 12 | "sourceCodePath": "./worker.ts", 13 | "action": "BEFORE_UPLOAD", 14 | "enabled": false, 15 | "debug": true 16 | } -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_UPLOAD/check-project-repository/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "check-project-repository", 3 | "description": "This worker is designed to intercept the upload of Docker image manifests and ensure that they target the correct project repository. It validates that the Docker image manifest contains a specific label (`org.jfrog.artifactory.projectKey`) and that the target repository is prefixed with the same project key.", 4 | "filterCriteria": { 5 | "artifactFilterCriteria": { 6 | "repoKeys": [ 7 | "docker-local" 8 | ] 9 | } 10 | }, 11 | "secrets": {}, 12 | "sourceCodePath": "./worker.ts", 13 | "action": "BEFORE_UPLOAD", 14 | "enabled": true, 15 | "debug": true 16 | } -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_DOWNLOAD/resctrict-download-if-no-xray-scan-since/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "resctrict-download-if-no-xray-scan-since", 3 | "description": "This worker is triggered by the `BEFORE_DOWNLOAD` event of Artifactory. Its primary purpose is to restrict downloads of artifacts that have not undergone an Xray scan within a specified threshold duration. This ensures artifacts comply with security policies by enforcing timely scans.", 4 | "filterCriteria": { 5 | "artifactFilterCriteria": { 6 | "repoKeys": [ 7 | "example-repo-local" 8 | ] 9 | } 10 | }, 11 | "secrets": {}, 12 | "sourceCodePath": "./worker.ts", 13 | "action": "BEFORE_DOWNLOAD", 14 | "enabled": false, 15 | "debug": true 16 | } -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_DOWNLOAD/restrict-download-by-property-value/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "restrict-download-by-property-value", 3 | "description": "This worker is triggered by the `BEFORE_DOWNLOAD` event of Artifactory. Its primary purpose is to block artifact downloads if the artifact contains certain forbidden properties with restricted values. This ensures compliance with organizational policies by preventing downloads of restricted artifacts.", 4 | "filterCriteria": { 5 | "artifactFilterCriteria": { 6 | "repoKeys": [ 7 | "example-repo-local" 8 | ] 9 | } 10 | }, 11 | "secrets": {}, 12 | "sourceCodePath": "./worker.ts", 13 | "action": "BEFORE_DOWNLOAD", 14 | "enabled": false, 15 | "debug": true 16 | } -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_DOWNLOAD/restrict-download-on-xray-critical-issues/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "restrict-download-on-xray-critical-issues", 3 | "description": "This worker is triggered by the `BEFORE_DOWNLOAD` event of Artifactory. Its primary purpose is to block artifact downloads if the number of critical security issues in JFrog Xray exceeds a defined threshold. The worker ensures compliance with security policies by preventing downloads of artifacts with excessive vulnerabilities.", 4 | "filterCriteria": { 5 | "artifactFilterCriteria": { 6 | "repoKeys": [ 7 | "example-repo-local" 8 | ] 9 | } 10 | }, 11 | "secrets": {}, 12 | "sourceCodePath": "./worker.ts", 13 | "action": "BEFORE_DOWNLOAD", 14 | "enabled": false, 15 | "debug": true 16 | } -------------------------------------------------------------------------------- /api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jfrog-workers", 3 | "version": "0.8.0", 4 | "description": "JFrog workers interfaces", 5 | "homepage": "https://github.com/jfrog/workers-sample", 6 | "contributors": [ 7 | { 8 | "name": "HERVE ESTEGUET", 9 | "githubUsername": "ehl-jf", 10 | "url": "https://github.com/ehl-jf" 11 | } 12 | ], 13 | "main": "./dist/index.js", 14 | "types": "./dist/index.d.ts", 15 | "files": [ 16 | "./dist" 17 | ], 18 | "author": "JFrog", 19 | "license": "Apache-2.0", 20 | "scripts": { 21 | "clean": "rm -rf dist", 22 | "build": "npm run clean && tsc" 23 | }, 24 | "exports": { 25 | ".": { 26 | "types": "./dist/index.d.ts", 27 | "default": "./dist/index.js" 28 | }, 29 | "./package.json": "./package.json" 30 | }, 31 | "repository": { 32 | "type": "git", 33 | "url": "git+https://github.com/jfrog/workers-sample.git" 34 | }, 35 | "dependencies": { 36 | "axios": "1.6.7" 37 | }, 38 | "devDependencies": { 39 | "typescript": "^5.4.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /samples/artifactory/AFTER_CREATE/copy-deb-and-rpm/worker.spec.ts: -------------------------------------------------------------------------------- 1 | import { PlatformContext, PlatformClients, PlatformHttpClient } from 'jfrog-workers'; 2 | import { createMock, DeepMocked } from '@golevelup/ts-jest'; 3 | import { AfterCreateRequest } from './types'; 4 | import runWorker from './worker'; 5 | 6 | describe("copy-deb-and-rpm tests", () => { 7 | let context: DeepMocked; 8 | let request: DeepMocked; 9 | 10 | beforeEach(() => { 11 | context = createMock({ 12 | clients: createMock({ 13 | platformHttp: createMock({ 14 | get: jest.fn().mockResolvedValue({ status: 200 }) 15 | }) 16 | }) 17 | }); 18 | request = createMock(); 19 | }) 20 | 21 | it('should run', async () => { 22 | await expect(runWorker(context, request)).resolves.toEqual(expect.objectContaining({ 23 | message: 'proceed' 24 | })) 25 | }) 26 | }); -------------------------------------------------------------------------------- /samples/artifactory/AFTER_DOWNLOAD/report-download-to-external-endpoint/worker.spec.ts: -------------------------------------------------------------------------------- 1 | import { PlatformContext, PlatformClients, PlatformHttpClient } from 'jfrog-workers'; 2 | import { createMock, DeepMocked } from '@golevelup/ts-jest'; 3 | import { AfterDownloadRequest } from './types'; 4 | import runWorker from './worker'; 5 | 6 | describe("report-download-to-external-endpoint tests", () => { 7 | let context: DeepMocked; 8 | let request: DeepMocked; 9 | 10 | beforeEach(() => { 11 | context = createMock({ 12 | clients: createMock({ 13 | platformHttp: createMock({ 14 | get: jest.fn().mockResolvedValue({ status: 200 }) 15 | }) 16 | }) 17 | }); 18 | request = createMock(); 19 | }) 20 | 21 | it('should run', async () => { 22 | await expect(runWorker(context, request)).resolves.toEqual(expect.objectContaining({ 23 | message: 'proceed' 24 | })) 25 | }) 26 | }); -------------------------------------------------------------------------------- /samples/runtime/AFTER_WORKLOAD_STATE_CHANGE/runtime-alerting/worker.spec.ts: -------------------------------------------------------------------------------- 1 | import { PlatformContext, PlatformClients, PlatformHttpClient } from 'jfrog-workers'; 2 | import { AfterWorkloadStateChangeRequest } from './types'; 3 | import { createMock, DeepMocked } from '@golevelup/ts-jest'; 4 | import runWorker from './worker'; 5 | 6 | describe("runtime-alerting tests", () => { 7 | let context: DeepMocked; 8 | let request: DeepMocked; 9 | 10 | beforeEach(() => { 11 | context = createMock({ 12 | clients: createMock({ 13 | platformHttp: createMock({ 14 | get: jest.fn().mockResolvedValue({ status: 200 }) 15 | }) 16 | }) 17 | }); 18 | request = createMock(); 19 | }) 20 | 21 | it('should run', async () => { 22 | await expect(runWorker(context, request)).resolves.toEqual(expect.objectContaining({ 23 | message: expect.anything(), 24 | })) 25 | }) 26 | }); -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_UPLOAD/repo-quota/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "repo-quota", 3 | "description": "Run a script on BEFORE_UPLOAD", 4 | "version": "1.0.1", 5 | "scripts": { 6 | "deploy": "jf worker deploy", 7 | "undeploy": "jf worker rm \"repo-quota\"", 8 | "test": "jest" 9 | }, 10 | "license": "ISC", 11 | "devDependencies": { 12 | "jfrog-workers": "^0.8.0", 13 | "@golevelup/ts-jest": "^0.4.0", 14 | "@types/jest": "^29.5.12", 15 | "jest": "^29.7.0", 16 | "jest-jasmine2": "^29.7.0", 17 | "ts-jest": "^29.1.2" 18 | }, 19 | "jest": { 20 | "moduleFileExtensions": [ 21 | "ts", 22 | "js" 23 | ], 24 | "rootDir": ".", 25 | "testEnvironment": "node", 26 | "clearMocks": true, 27 | "maxConcurrency": 1, 28 | "testRegex": "\\.spec\\.ts$", 29 | "moduleDirectories": ["node_modules"], 30 | "collectCoverageFrom": [ 31 | "**/*.ts" 32 | ], 33 | "coverageDirectory": "../coverage", 34 | "transform": { 35 | "^.+\\.(t|j)s$": "ts-jest" 36 | }, 37 | "testRunner": "jest-jasmine2", 38 | "verbose": true 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /samples/artifactory/eventCombinationWorkers/mavenSnapshotCleanupWhenRelease/AFTER_MOVE/mavenSnapshotCleanupWhenRelease_afterMove/worker.spec.ts: -------------------------------------------------------------------------------- 1 | import { PlatformContext, AfterMoveRequest, PlatformClients, PlatformHttpClient } from 'jfrog-workers'; 2 | import { createMock, DeepMocked } from '@golevelup/ts-jest'; 3 | import runWorker from './worker'; 4 | 5 | describe("mavenSnapshotCleanupWhenRelease_afterMove", () => { 6 | let context: DeepMocked; 7 | let request: DeepMocked; 8 | 9 | beforeEach(() => { 10 | context = createMock({ 11 | clients: createMock({ 12 | platformHttp: createMock({ 13 | get: jest.fn().mockResolvedValue({ status: 200 }) 14 | }) 15 | }) 16 | }); 17 | request = createMock(); 18 | }) 19 | 20 | it('should run', async () => { 21 | await expect(runWorker(context, request)).resolves.toEqual(expect.objectContaining({ 22 | message: 'proceed' 23 | })) 24 | }) 25 | }); -------------------------------------------------------------------------------- /samples/artifactory/eventCombinationWorkers/mavenSnapshotCleanupWhenRelease/AFTER_CREATE/mavenSnapshotCleanupWhenRelease_afterCreate/worker.spec.ts: -------------------------------------------------------------------------------- 1 | import { PlatformContext, AfterCreateRequest, PlatformClients, PlatformHttpClient } from 'jfrog-workers'; 2 | import { createMock, DeepMocked } from '@golevelup/ts-jest'; 3 | import runWorker from './worker'; 4 | 5 | describe("mavenSnapshotCleanupWhenRelease_afterCreate", () => { 6 | let context: DeepMocked; 7 | let request: DeepMocked; 8 | 9 | beforeEach(() => { 10 | context = createMock({ 11 | clients: createMock({ 12 | platformHttp: createMock({ 13 | get: jest.fn().mockResolvedValue({ status: 200 }) 14 | }) 15 | }) 16 | }); 17 | request = createMock(); 18 | }) 19 | 20 | it('should run', async () => { 21 | await expect(runWorker(context, request)).resolves.toEqual(expect.objectContaining({ 22 | message: 'proceed' 23 | })) 24 | }) 25 | }); -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/oldBuildCleanup/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "oldBuildCleanup", 3 | "description": "Run a script on GENERIC_EVENT", 4 | "version": "1.0.1", 5 | "scripts": { 6 | "deploy": "jf worker deploy", 7 | "undeploy": "jf worker rm \"oldBuildCleanup\"", 8 | "test": "jest" 9 | }, 10 | "license": "ISC", 11 | "devDependencies": { 12 | "jfrog-workers": "^0.8.0", 13 | "@golevelup/ts-jest": "^0.4.0", 14 | "@types/jest": "^29.5.12", 15 | "jest": "^29.7.0", 16 | "jest-jasmine2": "^29.7.0", 17 | "ts-jest": "^29.1.2" 18 | }, 19 | "jest": { 20 | "moduleFileExtensions": [ 21 | "ts", 22 | "js" 23 | ], 24 | "rootDir": ".", 25 | "testEnvironment": "node", 26 | "clearMocks": true, 27 | "maxConcurrency": 1, 28 | "testRegex": "\\.spec\\.ts$", 29 | "moduleDirectories": ["node_modules"], 30 | "collectCoverageFrom": [ 31 | "**/*.ts" 32 | ], 33 | "coverageDirectory": "../coverage", 34 | "transform": { 35 | "^.+\\.(t|j)s$": "ts-jest" 36 | }, 37 | "testRunner": "jest-jasmine2", 38 | "verbose": true 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_COPY/restrict-overwrite/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "restrict-overwrite", 3 | "description": "Run a script on BEFORE_COPY", 4 | "version": "1.0.0", 5 | "scripts": { 6 | "deploy": "jf worker deploy", 7 | "undeploy": "jf worker rm \"restrict-overwrite\"", 8 | "test": "jest" 9 | }, 10 | "license": "ISC", 11 | "devDependencies": { 12 | "jfrog-workers": "^0.8.0", 13 | "@golevelup/ts-jest": "^0.4.0", 14 | "@types/jest": "^29.5.12", 15 | "jest": "^29.7.0", 16 | "jest-jasmine2": "^29.7.0", 17 | "ts-jest": "^29.1.2" 18 | }, 19 | "jest": { 20 | "moduleFileExtensions": [ 21 | "ts", 22 | "js" 23 | ], 24 | "rootDir": ".", 25 | "testEnvironment": "node", 26 | "clearMocks": true, 27 | "maxConcurrency": 1, 28 | "testRegex": "\\.spec\\.ts$", 29 | "moduleDirectories": ["node_modules"], 30 | "collectCoverageFrom": [ 31 | "**/*.ts" 32 | ], 33 | "coverageDirectory": "../coverage", 34 | "transform": { 35 | "^.+\\.(t|j)s$": "ts-jest" 36 | }, 37 | "testRunner": "jest-jasmine2", 38 | "verbose": true 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_MOVE/restrict-overwrite/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "restrict-overwrite", 3 | "description": "Run a script on BEFORE_MOVE", 4 | "version": "1.0.0", 5 | "scripts": { 6 | "deploy": "jf worker deploy", 7 | "undeploy": "jf worker rm \"restrict-overwrite\"", 8 | "test": "jest" 9 | }, 10 | "license": "ISC", 11 | "devDependencies": { 12 | "jfrog-workers": "^0.8.0", 13 | "@golevelup/ts-jest": "^0.4.0", 14 | "@types/jest": "^29.5.12", 15 | "jest": "^29.7.0", 16 | "jest-jasmine2": "^29.7.0", 17 | "ts-jest": "^29.1.2" 18 | }, 19 | "jest": { 20 | "moduleFileExtensions": [ 21 | "ts", 22 | "js" 23 | ], 24 | "rootDir": ".", 25 | "testEnvironment": "node", 26 | "clearMocks": true, 27 | "maxConcurrency": 1, 28 | "testRegex": "\\.spec\\.ts$", 29 | "moduleDirectories": ["node_modules"], 30 | "collectCoverageFrom": [ 31 | "**/*.ts" 32 | ], 33 | "coverageDirectory": "../coverage", 34 | "transform": { 35 | "^.+\\.(t|j)s$": "ts-jest" 36 | }, 37 | "testRunner": "jest-jasmine2", 38 | "verbose": true 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/artifact-cleanup/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "artifact-cleanup", 3 | "description": "Run a script on GENERIC_EVENT", 4 | "version": "1.0.0", 5 | "scripts": { 6 | "deploy": "jf worker deploy", 7 | "undeploy": "jf worker rm \"artifact-cleanup\"", 8 | "test": "jest" 9 | }, 10 | "license": "ISC", 11 | "devDependencies": { 12 | "jfrog-workers": "^0.8.0", 13 | "@golevelup/ts-jest": "^0.4.0", 14 | "@types/jest": "^29.5.12", 15 | "jest": "^29.7.0", 16 | "jest-jasmine2": "^29.7.0", 17 | "ts-jest": "^29.1.2" 18 | }, 19 | "jest": { 20 | "moduleFileExtensions": [ 21 | "ts", 22 | "js" 23 | ], 24 | "rootDir": ".", 25 | "testEnvironment": "node", 26 | "clearMocks": true, 27 | "maxConcurrency": 1, 28 | "testRegex": "\\.spec\\.ts$", 29 | "moduleDirectories": ["node_modules"], 30 | "collectCoverageFrom": [ 31 | "**/*.ts" 32 | ], 33 | "coverageDirectory": "../coverage", 34 | "transform": { 35 | "^.+\\.(t|j)s$": "ts-jest" 36 | }, 37 | "testRunner": "jest-jasmine2", 38 | "verbose": true 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /samples/access/BEFORE_CREATE_TOKEN/token-validation/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "token-validation", 3 | "description": "Run a script on BEFORE_CREATE_TOKEN", 4 | "version": "1.0.0", 5 | "scripts": { 6 | "deploy": "jf worker deploy", 7 | "undeploy": "jf worker rm \"token-validation\"", 8 | "test": "jest" 9 | }, 10 | "license": "ISC", 11 | "devDependencies": { 12 | "jfrog-workers": "^0.8.0", 13 | "@golevelup/ts-jest": "^0.4.0", 14 | "@types/jest": "^29.5.12", 15 | "jest": "^29.7.0", 16 | "jest-jasmine2": "^29.7.0", 17 | "ts-jest": "^29.1.2" 18 | }, 19 | "jest": { 20 | "moduleFileExtensions": [ 21 | "ts", 22 | "js" 23 | ], 24 | "rootDir": ".", 25 | "testEnvironment": "node", 26 | "clearMocks": true, 27 | "maxConcurrency": 1, 28 | "testRegex": "\\.spec\\.ts$", 29 | "moduleDirectories": ["node_modules"], 30 | "collectCoverageFrom": [ 31 | "**/*.ts" 32 | ], 33 | "coverageDirectory": "../coverage", 34 | "transform": { 35 | "^.+\\.(t|j)s$": "ts-jest" 36 | }, 37 | "testRunner": "jest-jasmine2", 38 | "verbose": true 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_CREATE/restrict-overwrite/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "restrict-overwrite", 3 | "description": "Run a script on BEFORE_CREATE", 4 | "version": "1.0.0", 5 | "scripts": { 6 | "deploy": "jf worker deploy", 7 | "undeploy": "jf worker rm \"restrict-overwrite\"", 8 | "test": "jest" 9 | }, 10 | "license": "ISC", 11 | "devDependencies": { 12 | "jfrog-workers": "^0.8.0", 13 | "@golevelup/ts-jest": "^0.4.0", 14 | "@types/jest": "^29.5.12", 15 | "jest": "^29.7.0", 16 | "jest-jasmine2": "^29.7.0", 17 | "ts-jest": "^29.1.2" 18 | }, 19 | "jest": { 20 | "moduleFileExtensions": [ 21 | "ts", 22 | "js" 23 | ], 24 | "rootDir": ".", 25 | "testEnvironment": "node", 26 | "clearMocks": true, 27 | "maxConcurrency": 1, 28 | "testRegex": "\\.spec\\.ts$", 29 | "moduleDirectories": ["node_modules"], 30 | "collectCoverageFrom": [ 31 | "**/*.ts" 32 | ], 33 | "coverageDirectory": "../coverage", 34 | "transform": { 35 | "^.+\\.(t|j)s$": "ts-jest" 36 | }, 37 | "testRunner": "jest-jasmine2", 38 | "verbose": true 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_UPLOAD/restrict-overwrite/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "restrict-overwrite", 3 | "description": "Run a script on BEFORE_UPLOAD", 4 | "version": "1.0.1", 5 | "scripts": { 6 | "deploy": "jf worker deploy", 7 | "undeploy": "jf worker rm \"restrict-overwrite\"", 8 | "test": "jest" 9 | }, 10 | "license": "ISC", 11 | "devDependencies": { 12 | "jfrog-workers": "^0.8.0", 13 | "@golevelup/ts-jest": "^0.4.0", 14 | "@types/jest": "^29.5.12", 15 | "jest": "^29.7.0", 16 | "jest-jasmine2": "^29.7.0", 17 | "ts-jest": "^29.1.2" 18 | }, 19 | "jest": { 20 | "moduleFileExtensions": [ 21 | "ts", 22 | "js" 23 | ], 24 | "rootDir": ".", 25 | "testEnvironment": "node", 26 | "clearMocks": true, 27 | "maxConcurrency": 1, 28 | "testRegex": "\\.spec\\.ts$", 29 | "moduleDirectories": ["node_modules"], 30 | "collectCoverageFrom": [ 31 | "**/*.ts" 32 | ], 33 | "coverageDirectory": "../coverage", 34 | "transform": { 35 | "^.+\\.(t|j)s$": "ts-jest" 36 | }, 37 | "testRunner": "jest-jasmine2", 38 | "verbose": true 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/delete-deprecated/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "delete-deprecated", 3 | "description": "Run a script on GENERIC_EVENT", 4 | "version": "1.0.1", 5 | "scripts": { 6 | "deploy": "jf worker deploy", 7 | "undeploy": "jf worker rm \"delete-deprecated\"", 8 | "test": "jest" 9 | }, 10 | "license": "ISC", 11 | "devDependencies": { 12 | "jfrog-workers": "^0.8.0", 13 | "@golevelup/ts-jest": "^0.4.0", 14 | "@types/jest": "^29.5.12", 15 | "jest": "^29.7.0", 16 | "jest-jasmine2": "^29.7.0", 17 | "ts-jest": "^29.1.2" 18 | }, 19 | "jest": { 20 | "moduleFileExtensions": [ 21 | "ts", 22 | "js" 23 | ], 24 | "rootDir": ".", 25 | "testEnvironment": "node", 26 | "clearMocks": true, 27 | "maxConcurrency": 1, 28 | "testRegex": "\\.spec\\.ts$", 29 | "moduleDirectories": ["node_modules"], 30 | "collectCoverageFrom": [ 31 | "**/*.ts" 32 | ], 33 | "coverageDirectory": "../coverage", 34 | "transform": { 35 | "^.+\\.(t|j)s$": "ts-jest" 36 | }, 37 | "testRunner": "jest-jasmine2", 38 | "verbose": true 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/delete-empty-dirs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "delete-empty-dirs", 3 | "description": "Run a script on GENERIC_EVENT", 4 | "version": "1.0.1", 5 | "scripts": { 6 | "deploy": "jf worker deploy", 7 | "undeploy": "jf worker rm \"delete-empty-dirs\"", 8 | "test": "jest" 9 | }, 10 | "license": "ISC", 11 | "devDependencies": { 12 | "jfrog-workers": "^0.8.0", 13 | "@golevelup/ts-jest": "^0.4.0", 14 | "@types/jest": "^29.5.12", 15 | "jest": "^29.7.0", 16 | "jest-jasmine2": "^29.7.0", 17 | "ts-jest": "^29.1.2" 18 | }, 19 | "jest": { 20 | "moduleFileExtensions": [ 21 | "ts", 22 | "js" 23 | ], 24 | "rootDir": ".", 25 | "testEnvironment": "node", 26 | "clearMocks": true, 27 | "maxConcurrency": 1, 28 | "testRegex": "\\.spec\\.ts$", 29 | "moduleDirectories": ["node_modules"], 30 | "collectCoverageFrom": [ 31 | "**/*.ts" 32 | ], 33 | "coverageDirectory": "../coverage", 34 | "transform": { 35 | "^.+\\.(t|j)s$": "ts-jest" 36 | }, 37 | "testRunner": "jest-jasmine2", 38 | "verbose": true 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/clean-docker-images/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clean-docker-images", 3 | "description": "Run a script on GENERIC_EVENT", 4 | "version": "1.0.1", 5 | "scripts": { 6 | "deploy": "jf worker deploy", 7 | "undeploy": "jf worker rm \"clean-docker-images\"", 8 | "test": "jest" 9 | }, 10 | "license": "ISC", 11 | "devDependencies": { 12 | "jfrog-workers": "^0.8.0", 13 | "@golevelup/ts-jest": "^0.4.0", 14 | "@types/jest": "^29.5.12", 15 | "jest": "^29.7.0", 16 | "jest-jasmine2": "^29.7.0", 17 | "ts-jest": "^29.1.2" 18 | }, 19 | "jest": { 20 | "moduleFileExtensions": [ 21 | "ts", 22 | "js" 23 | ], 24 | "rootDir": ".", 25 | "testEnvironment": "node", 26 | "clearMocks": true, 27 | "maxConcurrency": 1, 28 | "testRegex": "\\.spec\\.ts$", 29 | "moduleDirectories": ["node_modules"], 30 | "collectCoverageFrom": [ 31 | "**/*.ts" 32 | ], 33 | "coverageDirectory": "../coverage", 34 | "transform": { 35 | "^.+\\.(t|j)s$": "ts-jest" 36 | }, 37 | "testRunner": "jest-jasmine2", 38 | "verbose": true 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_UPLOAD/allow-upload-by-pattern/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "allow-upload-by-pattern", 3 | "description": "Run a script on BEFORE_UPLOAD", 4 | "version": "1.0.1", 5 | "scripts": { 6 | "deploy": "jf worker deploy", 7 | "undeploy": "jf worker rm \"allow-upload-by-pattern\"", 8 | "test": "jest" 9 | }, 10 | "license": "ISC", 11 | "devDependencies": { 12 | "jfrog-workers": "^0.8.0", 13 | "@golevelup/ts-jest": "^0.4.0", 14 | "@types/jest": "^29.5.12", 15 | "jest": "^29.7.0", 16 | "jest-jasmine2": "^29.7.0", 17 | "ts-jest": "^29.1.2" 18 | }, 19 | "jest": { 20 | "moduleFileExtensions": [ 21 | "ts", 22 | "js" 23 | ], 24 | "rootDir": ".", 25 | "testEnvironment": "node", 26 | "clearMocks": true, 27 | "maxConcurrency": 1, 28 | "testRegex": "\\.spec\\.ts$", 29 | "moduleDirectories": ["node_modules"], 30 | "collectCoverageFrom": [ 31 | "**/*.ts" 32 | ], 33 | "coverageDirectory": "../coverage", 34 | "transform": { 35 | "^.+\\.(t|j)s$": "ts-jest" 36 | }, 37 | "testRunner": "jest-jasmine2", 38 | "verbose": true 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_UPLOAD/check-project-repository/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redirect-to-project-repo", 3 | "description": "Run a script on BEFORE_UPLOAD", 4 | "version": "1.0.1", 5 | "scripts": { 6 | "deploy": "jf worker deploy", 7 | "undeploy": "jf worker rm \"redirect-to-project-repo\"", 8 | "test": "jest" 9 | }, 10 | "license": "ISC", 11 | "devDependencies": { 12 | "jfrog-workers": "^0.8.0", 13 | "@golevelup/ts-jest": "^0.4.0", 14 | "@types/jest": "^29.5.12", 15 | "jest": "^29.7.0", 16 | "jest-jasmine2": "^29.7.0", 17 | "ts-jest": "^29.1.2" 18 | }, 19 | "jest": { 20 | "moduleFileExtensions": [ 21 | "ts", 22 | "js" 23 | ], 24 | "rootDir": ".", 25 | "testEnvironment": "node", 26 | "clearMocks": true, 27 | "maxConcurrency": 1, 28 | "testRegex": "\\.spec\\.ts$", 29 | "moduleDirectories": ["node_modules"], 30 | "collectCoverageFrom": [ 31 | "**/*.ts" 32 | ], 33 | "coverageDirectory": "../coverage", 34 | "transform": { 35 | "^.+\\.(t|j)s$": "ts-jest" 36 | }, 37 | "testRunner": "jest-jasmine2", 38 | "verbose": true 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/delete-by-property-value/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "delete-by-property-value", 3 | "description": "Run a script on GENERIC_EVENT", 4 | "version": "1.0.1", 5 | "scripts": { 6 | "deploy": "jf worker deploy", 7 | "undeploy": "jf worker rm \"delete-by-property-value\"", 8 | "test": "jest" 9 | }, 10 | "license": "ISC", 11 | "devDependencies": { 12 | "jfrog-workers": "^0.8.0", 13 | "@golevelup/ts-jest": "^0.4.0", 14 | "@types/jest": "^29.5.12", 15 | "jest": "^29.7.0", 16 | "jest-jasmine2": "^29.7.0", 17 | "ts-jest": "^29.1.2" 18 | }, 19 | "jest": { 20 | "moduleFileExtensions": [ 21 | "ts", 22 | "js" 23 | ], 24 | "rootDir": ".", 25 | "testEnvironment": "node", 26 | "clearMocks": true, 27 | "maxConcurrency": 1, 28 | "testRegex": "\\.spec\\.ts$", 29 | "moduleDirectories": ["node_modules"], 30 | "collectCoverageFrom": [ 31 | "**/*.ts" 32 | ], 33 | "coverageDirectory": "../coverage", 34 | "transform": { 35 | "^.+\\.(t|j)s$": "ts-jest" 36 | }, 37 | "testRunner": "jest-jasmine2", 38 | "verbose": true 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /samples/runtime/AFTER_WORKLOAD_STATE_CHANGE/runtime-alerting/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "runtime-alerting", 3 | "description": "Run a script on AFTER_WORKLOAD_STATE_CHANGE", 4 | "version": "1.0.0", 5 | "scripts": { 6 | "deploy": "jf worker deploy", 7 | "undeploy": "jf worker rm \"runtime-alerting\"", 8 | "test": "jest" 9 | }, 10 | "license": "ISC", 11 | "devDependencies": { 12 | "jfrog-workers": "^0.8.0", 13 | "@golevelup/ts-jest": "^0.4.0", 14 | "@types/jest": "^29.5.12", 15 | "jest": "^29.7.0", 16 | "jest-jasmine2": "^29.7.0", 17 | "ts-jest": "^29.1.2" 18 | }, 19 | "jest": { 20 | "moduleFileExtensions": [ 21 | "ts", 22 | "js" 23 | ], 24 | "rootDir": ".", 25 | "testEnvironment": "node", 26 | "clearMocks": true, 27 | "maxConcurrency": 1, 28 | "testRegex": "\\.spec\\.ts$", 29 | "moduleDirectories": ["node_modules"], 30 | "collectCoverageFrom": [ 31 | "**/*.ts" 32 | ], 33 | "coverageDirectory": "../coverage", 34 | "transform": { 35 | "^.+\\.(t|j)s$": "ts-jest" 36 | }, 37 | "testRunner": "jest-jasmine2", 38 | "verbose": true 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_PROPERTY_CREATE/property-creation-permission/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-property-permission", 3 | "description": "Run a script on BEFORE_CREATE_TOKEN", 4 | "version": "1.0.0", 5 | "scripts": { 6 | "deploy": "jf worker deploy", 7 | "undeploy": "jf worker rm \"create-property-permission\"", 8 | "test": "jest" 9 | }, 10 | "license": "ISC", 11 | "devDependencies": { 12 | "jfrog-workers": "^0.8.0", 13 | "@golevelup/ts-jest": "^0.4.0", 14 | "@types/jest": "^29.5.12", 15 | "jest": "^29.7.0", 16 | "jest-jasmine2": "^29.7.0", 17 | "ts-jest": "^29.1.2" 18 | }, 19 | "jest": { 20 | "moduleFileExtensions": [ 21 | "ts", 22 | "js" 23 | ], 24 | "rootDir": ".", 25 | "testEnvironment": "node", 26 | "clearMocks": true, 27 | "maxConcurrency": 1, 28 | "testRegex": "\\.spec\\.ts$", 29 | "moduleDirectories": ["node_modules"], 30 | "collectCoverageFrom": [ 31 | "**/*.ts" 32 | ], 33 | "coverageDirectory": "../coverage", 34 | "transform": { 35 | "^.+\\.(t|j)s$": "ts-jest" 36 | }, 37 | "testRunner": "jest-jasmine2", 38 | "verbose": true 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/remote-backup/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "remote-backup", 3 | "description": "Run a script on GENERIC_EVENT", 4 | "version": "1.0.0", 5 | "scripts": { 6 | "deploy": "jf worker deploy", 7 | "undeploy": "jf worker rm \"remote-backup\"", 8 | "test": "jest" 9 | }, 10 | "license": "ISC", 11 | "devDependencies": { 12 | "@golevelup/ts-jest": "^0.4.0", 13 | "@types/jest": "^29.5.12", 14 | "jest": "^29.7.0", 15 | "jest-jasmine2": "^29.7.0", 16 | "jfrog-workers": "^0.8.0", 17 | "ts-jest": "^29.1.2", 18 | "typescript": "^5.6.3" 19 | }, 20 | "jest": { 21 | "moduleFileExtensions": [ 22 | "ts", 23 | "js" 24 | ], 25 | "rootDir": ".", 26 | "testEnvironment": "node", 27 | "clearMocks": true, 28 | "maxConcurrency": 1, 29 | "testRegex": "\\.spec\\.ts$", 30 | "moduleDirectories": [ 31 | "node_modules" 32 | ], 33 | "collectCoverageFrom": [ 34 | "**/*.ts" 35 | ], 36 | "coverageDirectory": "../coverage", 37 | "transform": { 38 | "^.+\\.(t|j)s$": "ts-jest" 39 | }, 40 | "testRunner": "jest-jasmine2", 41 | "verbose": true 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /samples/artifactory/AFTER_DOWNLOAD/report-download-to-external-endpoint/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "report-download-to-external-endpoint", 3 | "description": "Run a script on AFTER_DOWNLOAD", 4 | "version": "1.0.0", 5 | "scripts": { 6 | "deploy": "jf worker deploy", 7 | "undeploy": "jf worker rm \"report-download-to-external-endpoint\"", 8 | "test": "jest" 9 | }, 10 | "license": "ISC", 11 | "devDependencies": { 12 | "jfrog-workers": "^0.8.0", 13 | "@golevelup/ts-jest": "^0.4.0", 14 | "@types/jest": "^29.5.12", 15 | "jest": "^29.7.0", 16 | "jest-jasmine2": "^29.7.0", 17 | "ts-jest": "^29.1.2" 18 | }, 19 | "jest": { 20 | "moduleFileExtensions": [ 21 | "ts", 22 | "js" 23 | ], 24 | "rootDir": ".", 25 | "testEnvironment": "node", 26 | "clearMocks": true, 27 | "maxConcurrency": 1, 28 | "testRegex": "\\.spec\\.ts$", 29 | "moduleDirectories": ["node_modules"], 30 | "collectCoverageFrom": [ 31 | "**/*.ts" 32 | ], 33 | "coverageDirectory": "../coverage", 34 | "transform": { 35 | "^.+\\.(t|j)s$": "ts-jest" 36 | }, 37 | "testRunner": "jest-jasmine2", 38 | "verbose": true 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /samples/access/BEFORE_CREATE_TOKEN/token-validation/worker.spec.ts: -------------------------------------------------------------------------------- 1 | import { PlatformContext, PlatformClients, PlatformHttpClient } from 'jfrog-workers'; 2 | import { createMock, DeepMocked } from '@golevelup/ts-jest'; 3 | import { BeforeCreateTokenRequest, CreateTokenStatus } from './types'; 4 | import runWorker from './worker'; 5 | 6 | describe("token-validation tests", () => { 7 | let context: DeepMocked; 8 | let request: DeepMocked; 9 | 10 | beforeEach(() => { 11 | context = createMock({ 12 | clients: createMock({ 13 | platformHttp: createMock({ 14 | get: jest.fn().mockResolvedValue({ status: 200 }) 15 | }) 16 | }) 17 | }); 18 | request = createMock(); 19 | }) 20 | 21 | it('should run', async () => { 22 | await expect(runWorker(context, request)).resolves.toEqual(expect.objectContaining({ 23 | message: 'Overwritten by worker-service if an error occurs.', 24 | status: CreateTokenStatus.CREATE_TOKEN_PROCEED 25 | })) 26 | }) 27 | }); -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_DOWNLOAD/resctrict-download-if-no-xray-scan-since/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "resctrict-download-if-no-xray-scan-since", 3 | "description": "Run a script on BEFORE_DOWNLOAD", 4 | "version": "1.0.0", 5 | "scripts": { 6 | "deploy": "jf worker deploy", 7 | "undeploy": "jf worker rm \"resctrict-download-if-no-xray-scan-since\"", 8 | "test": "jest" 9 | }, 10 | "license": "ISC", 11 | "devDependencies": { 12 | "jfrog-workers": "^0.8.0", 13 | "@golevelup/ts-jest": "^0.4.0", 14 | "@types/jest": "^29.5.12", 15 | "jest": "^29.7.0", 16 | "jest-jasmine2": "^29.7.0", 17 | "ts-jest": "^29.1.2" 18 | }, 19 | "jest": { 20 | "moduleFileExtensions": [ 21 | "ts", 22 | "js" 23 | ], 24 | "rootDir": ".", 25 | "testEnvironment": "node", 26 | "clearMocks": true, 27 | "maxConcurrency": 1, 28 | "testRegex": "\\.spec\\.ts$", 29 | "moduleDirectories": ["node_modules"], 30 | "collectCoverageFrom": [ 31 | "**/*.ts" 32 | ], 33 | "coverageDirectory": "../coverage", 34 | "transform": { 35 | "^.+\\.(t|j)s$": "ts-jest" 36 | }, 37 | "testRunner": "jest-jasmine2", 38 | "verbose": true 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_DOWNLOAD/restrict-download-on-xray-critical-issues/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "restrict-download-on-xray-critical-issues", 3 | "description": "Run a script on BEFORE_DOWNLOAD", 4 | "version": "1.0.0", 5 | "scripts": { 6 | "deploy": "jf worker deploy", 7 | "undeploy": "jf worker rm \"restrict-download-on-xray-critical-issues\"", 8 | "test": "jest" 9 | }, 10 | "license": "ISC", 11 | "devDependencies": { 12 | "jfrog-workers": "^0.8.0", 13 | "@golevelup/ts-jest": "^0.4.0", 14 | "@types/jest": "^29.5.12", 15 | "jest": "^29.7.0", 16 | "jest-jasmine2": "^29.7.0", 17 | "ts-jest": "^29.1.2" 18 | }, 19 | "jest": { 20 | "moduleFileExtensions": [ 21 | "ts", 22 | "js" 23 | ], 24 | "rootDir": ".", 25 | "testEnvironment": "node", 26 | "clearMocks": true, 27 | "maxConcurrency": 1, 28 | "testRegex": "\\.spec\\.ts$", 29 | "moduleDirectories": ["node_modules"], 30 | "collectCoverageFrom": [ 31 | "**/*.ts" 32 | ], 33 | "coverageDirectory": "../coverage", 34 | "transform": { 35 | "^.+\\.(t|j)s$": "ts-jest" 36 | }, 37 | "testRunner": "jest-jasmine2", 38 | "verbose": true 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_DOWNLOAD/restrict-download-by-property-value/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "restrict-download-by-property-value", 3 | "description": "Run a script on BEFORE_DOWNLOAD", 4 | "version": "1.1.0", 5 | "scripts": { 6 | "deploy": "jf worker deploy", 7 | "undeploy": "jf worker rm \"restrict-download-by-property-value\"", 8 | "test": "jest" 9 | }, 10 | "license": "ISC", 11 | "devDependencies": { 12 | "jfrog-workers": "^0.8.0", 13 | "@golevelup/ts-jest": "^0.4.0", 14 | "@types/jest": "^29.5.12", 15 | "jest": "^29.7.0", 16 | "jest-jasmine2": "^29.7.0", 17 | "ts-jest": "^29.1.2" 18 | }, 19 | "jest": { 20 | "moduleFileExtensions": [ 21 | "ts", 22 | "js" 23 | ], 24 | "rootDir": ".", 25 | "testEnvironment": "node", 26 | "clearMocks": true, 27 | "maxConcurrency": 1, 28 | "testRegex": "\\.spec\\.ts$", 29 | "moduleDirectories": [ 30 | "node_modules" 31 | ], 32 | "collectCoverageFrom": [ 33 | "**/*.ts" 34 | ], 35 | "coverageDirectory": "../coverage", 36 | "transform": { 37 | "^.+\\.(t|j)s$": "ts-jest" 38 | }, 39 | "testRunner": "jest-jasmine2", 40 | "verbose": true 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_DOWNLOAD/restrict-download-by-property-value/worker.spec.ts: -------------------------------------------------------------------------------- 1 | import { PlatformContext, PlatformClients, PlatformHttpClient } from 'jfrog-workers'; 2 | import { createMock, DeepMocked } from '@golevelup/ts-jest'; 3 | import { BeforeDownloadRequest, DownloadStatus } from './types'; 4 | import runWorker from './worker'; 5 | 6 | describe("restrict-download-by-property-value tests", () => { 7 | let context: DeepMocked; 8 | let request: DeepMocked; 9 | 10 | beforeEach(() => { 11 | context = createMock({ 12 | clients: createMock({ 13 | platformHttp: createMock({ 14 | get: jest.fn().mockResolvedValue({ status: 200 }) 15 | }) 16 | }) 17 | }); 18 | request = createMock(); 19 | }) 20 | 21 | it('should run', async () => { 22 | await expect(runWorker(context, request)).resolves.toEqual(expect.objectContaining({ 23 | message: 'Overwritten by worker-service if an error occurs.', 24 | status: DownloadStatus.DOWNLOAD_PROCEED 25 | })) 26 | }) 27 | }); -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/repoStats/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "repoStats", 3 | "description": "This Worker displays certain statistics about requested files.", 4 | "version": "1.0.0", 5 | "scripts": { 6 | "deploy": "jf worker deploy", 7 | "undeploy": "jf worker rm \"repoStats\"", 8 | "test": "jest" 9 | }, 10 | "license": "ISC", 11 | "devDependencies": { 12 | "jfrog-workers": "^0.8.0", 13 | "@golevelup/ts-jest": "^0.4.0", 14 | "@types/jest": "^29.5.12", 15 | "jest": "^29.7.0", 16 | "jest-jasmine2": "^29.7.0", 17 | "ts-jest": "^29.1.2" 18 | }, 19 | "jest": { 20 | "moduleFileExtensions": [ 21 | "ts", 22 | "js" 23 | ], 24 | "rootDir": ".", 25 | "testEnvironment": "node", 26 | "clearMocks": true, 27 | "maxConcurrency": 1, 28 | "testRegex": "\\.spec\\.ts$", 29 | "moduleDirectories": ["node_modules"], 30 | "collectCoverageFrom": [ 31 | "**/*.ts" 32 | ], 33 | "coverageDirectory": "../coverage", 34 | "transform": { 35 | "^.+\\.(t|j)s$": "ts-jest" 36 | }, 37 | "testRunner": "jest-jasmine2", 38 | "verbose": true 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_DOWNLOAD/resctrict-download-if-no-xray-scan-since/worker.spec.ts: -------------------------------------------------------------------------------- 1 | import { PlatformContext, PlatformClients, PlatformHttpClient } from 'jfrog-workers'; 2 | import { createMock, DeepMocked } from '@golevelup/ts-jest'; 3 | import { BeforeDownloadRequest, DownloadStatus } from './types'; 4 | import runWorker from './worker'; 5 | 6 | describe("resctrict-download-if-no-xray-scan-since tests", () => { 7 | let context: DeepMocked; 8 | let request: DeepMocked; 9 | 10 | beforeEach(() => { 11 | context = createMock({ 12 | clients: createMock({ 13 | platformHttp: createMock({ 14 | get: jest.fn().mockResolvedValue({ status: 200 }) 15 | }) 16 | }) 17 | }); 18 | request = createMock(); 19 | }) 20 | 21 | it('should run', async () => { 22 | await expect(runWorker(context, request)).resolves.toEqual(expect.objectContaining({ 23 | message: 'Overwritten by worker-service if an error occurs.', 24 | status: DownloadStatus.DOWNLOAD_PROCEED 25 | })) 26 | }) 27 | }); -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_DOWNLOAD/restrict-download-on-xray-critical-issues/worker.spec.ts: -------------------------------------------------------------------------------- 1 | import { PlatformContext, PlatformClients, PlatformHttpClient } from 'jfrog-workers'; 2 | import { createMock, DeepMocked } from '@golevelup/ts-jest'; 3 | import { BeforeDownloadRequest, DownloadStatus } from './types'; 4 | import runWorker from './worker'; 5 | 6 | describe("restrict-download-on-xray-critical-issues tests", () => { 7 | let context: DeepMocked; 8 | let request: DeepMocked; 9 | 10 | beforeEach(() => { 11 | context = createMock({ 12 | clients: createMock({ 13 | platformHttp: createMock({ 14 | get: jest.fn().mockResolvedValue({ status: 200 }) 15 | }) 16 | }) 17 | }); 18 | request = createMock(); 19 | }) 20 | 21 | it('should run', async () => { 22 | await expect(runWorker(context, request)).resolves.toEqual(expect.objectContaining({ 23 | message: 'Overwritten by worker-service if an error occurs.', 24 | status: DownloadStatus.DOWNLOAD_PROCEED 25 | })) 26 | }) 27 | }); -------------------------------------------------------------------------------- /samples/artifactory/eventCombinationWorkers/mavenSnapshotCleanupWhenRelease/AFTER_MOVE/mavenSnapshotCleanupWhenRelease_afterMove/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mavenSnapshotCleanupWhenRelease_afterMove", 3 | "description": "Run a script on AFTER_MOVE", 4 | "version": "1.0.1", 5 | "scripts": { 6 | "deploy": "jf worker deploy", 7 | "undeploy": "jf worker rm \"mavenSnapshotCleanupWhenRelease_afterMove\"", 8 | "test": "jest" 9 | }, 10 | "license": "ISC", 11 | "devDependencies": { 12 | "jfrog-workers": "^0.8.0", 13 | "@golevelup/ts-jest": "^0.4.0", 14 | "@types/jest": "^29.5.12", 15 | "jest": "^29.7.0", 16 | "jest-jasmine2": "^29.7.0", 17 | "ts-jest": "^29.1.2" 18 | }, 19 | "jest": { 20 | "moduleFileExtensions": [ 21 | "ts", 22 | "js" 23 | ], 24 | "rootDir": ".", 25 | "testEnvironment": "node", 26 | "clearMocks": true, 27 | "maxConcurrency": 1, 28 | "testRegex": "\\.spec\\.ts$", 29 | "moduleDirectories": ["node_modules"], 30 | "collectCoverageFrom": [ 31 | "**/*.ts" 32 | ], 33 | "coverageDirectory": "../coverage", 34 | "transform": { 35 | "^.+\\.(t|j)s$": "ts-jest" 36 | }, 37 | "testRunner": "jest-jasmine2", 38 | "verbose": true 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_PROPERTY_CREATE/property-creation-permission/worker.spec.ts: -------------------------------------------------------------------------------- 1 | import { PlatformContext, PlatformClients, PlatformHttpClient } from 'jfrog-workers'; 2 | import { createMock, DeepMocked } from '@golevelup/ts-jest'; 3 | import { BeforePropertyCreateRequest, BeforePropertyCreateStatus } from './types'; 4 | import runWorker from './worker'; 5 | 6 | describe("create-property-permission tests", () => { 7 | let context: DeepMocked; 8 | let request: DeepMocked; 9 | 10 | beforeEach(() => { 11 | context = createMock({ 12 | clients: createMock({ 13 | platformHttp: createMock({ 14 | get: jest.fn().mockResolvedValue({ status: 200 }) 15 | }) 16 | }) 17 | }); 18 | request = createMock(); 19 | }) 20 | 21 | it('should run', async () => { 22 | await expect(runWorker(context, request)).resolves.toEqual(expect.objectContaining({ 23 | message: 'Overwritten by worker-service if an error occurs.', 24 | status: BeforePropertyCreateStatus.BEFORE_PROPERTY_CREATE_PROCEED 25 | })) 26 | }) 27 | }); -------------------------------------------------------------------------------- /samples/artifactory/eventCombinationWorkers/mavenSnapshotCleanupWhenRelease/AFTER_CREATE/mavenSnapshotCleanupWhenRelease_afterCreate/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mavenSnapshotCleanupWhenRelease_afterCreate", 3 | "description": "Run a script on AFTER_CREATE", 4 | "version": "1.0.1", 5 | "scripts": { 6 | "deploy": "jf worker deploy", 7 | "undeploy": "jf worker rm \"mavenSnapshotCleanupWhenRelease_afterCreate\"", 8 | "test": "jest" 9 | }, 10 | "license": "ISC", 11 | "devDependencies": { 12 | "jfrog-workers": "^0.8.0", 13 | "@golevelup/ts-jest": "^0.4.0", 14 | "@types/jest": "^29.5.12", 15 | "jest": "^29.7.0", 16 | "jest-jasmine2": "^29.7.0", 17 | "ts-jest": "^29.1.2" 18 | }, 19 | "jest": { 20 | "moduleFileExtensions": [ 21 | "ts", 22 | "js" 23 | ], 24 | "rootDir": ".", 25 | "testEnvironment": "node", 26 | "clearMocks": true, 27 | "maxConcurrency": 1, 28 | "testRegex": "\\.spec\\.ts$", 29 | "moduleDirectories": ["node_modules"], 30 | "collectCoverageFrom": [ 31 | "**/*.ts" 32 | ], 33 | "coverageDirectory": "../coverage", 34 | "transform": { 35 | "^.+\\.(t|j)s$": "ts-jest" 36 | }, 37 | "testRunner": "jest-jasmine2", 38 | "verbose": true 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_PROPERTY_CREATE/property-creation-permission/worker.ts: -------------------------------------------------------------------------------- 1 | import { PlatformContext } from 'jfrog-workers'; 2 | import { BeforePropertyCreateRequest, BeforePropertyCreateResponse, BeforePropertyCreateStatus } from './types'; 3 | 4 | /** 5 | * This worker is used to intercept the creation of a property. 6 | * It checks if the user is an admin and allows the creation of the property. 7 | * Only admins are allowed to create properties. 8 | */ 9 | export default async (context: PlatformContext, data: BeforePropertyCreateRequest): Promise> => { 10 | if (isAdmin(data)) { 11 | return { 12 | message: "Permission granted to admin", 13 | status: BeforePropertyCreateStatus.BEFORE_PROPERTY_CREATE_PROCEED 14 | }; 15 | } 16 | 17 | return { 18 | message: "Only admins are allowed to create properties", 19 | status: BeforePropertyCreateStatus.BEFORE_PROPERTY_CREATE_STOP 20 | } 21 | }; 22 | 23 | /** 24 | * Checks if the user is an admin. 25 | * 26 | * @param data The request data 27 | * @returns true if the user is an admin, false otherwise 28 | */ 29 | function isAdmin(data: BeforePropertyCreateRequest): boolean { 30 | return data.userContext.id.endsWith("/users/admin"); 31 | } -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/oldBuildCleanup/worker.spec.ts: -------------------------------------------------------------------------------- 1 | import { PlatformContext, PlatformClients, PlatformHttpClient } from 'jfrog-workers'; 2 | import { createMock, DeepMocked } from '@golevelup/ts-jest'; 3 | import runWorker from './worker'; 4 | 5 | describe("oldBuildCleanup", () => { 6 | let context: DeepMocked; 7 | const request: void = undefined; 8 | 9 | beforeEach(() => { 10 | context = createMock({ 11 | clients: createMock({ 12 | platformHttp: createMock({ 13 | get: jest.fn().mockResolvedValue({ 14 | status: 200, 15 | data: [ 16 | { type: 'a' }, 17 | { type: 'a' }, 18 | { type: 'b' }, 19 | { type: 'c' } 20 | ], 21 | }) 22 | }) 23 | }) 24 | }); 25 | }) 26 | 27 | it('should run', async () => { 28 | await expect(runWorker(context, request)).resolves.toEqual(expect.objectContaining({ 29 | repositories: expect.objectContaining({ 30 | a: 2, b: 1, c: 1 31 | }) 32 | })) 33 | }) 34 | }); -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/delete-deprecated/worker.spec.ts: -------------------------------------------------------------------------------- 1 | import { PlatformContext, PlatformClients, PlatformHttpClient } from 'jfrog-workers'; 2 | import { createMock, DeepMocked } from '@golevelup/ts-jest'; 3 | import runWorker from './worker'; 4 | 5 | describe("delete-depreacted", () => { 6 | let context: DeepMocked; 7 | const request: void = undefined; 8 | 9 | beforeEach(() => { 10 | context = createMock({ 11 | clients: createMock({ 12 | platformHttp: createMock({ 13 | get: jest.fn().mockResolvedValue({ 14 | status: 200, 15 | data: [ 16 | { type: 'a' }, 17 | { type: 'a' }, 18 | { type: 'b' }, 19 | { type: 'c' } 20 | ], 21 | }) 22 | }) 23 | }) 24 | }); 25 | }) 26 | 27 | it('should run', async () => { 28 | await expect(runWorker(context, request)).resolves.toEqual(expect.objectContaining({ 29 | repositories: expect.objectContaining({ 30 | a: 2, b: 1, c: 1 31 | }) 32 | })) 33 | }) 34 | }); -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_COPY/restrict-overwrite/worker.spec.ts: -------------------------------------------------------------------------------- 1 | import { PlatformContext, PlatformClients, PlatformHttpClient } from 'jfrog-workers'; 2 | import { createMock, DeepMocked } from '@golevelup/ts-jest'; 3 | import { BeforeCopyRequest, ActionStatus } from './types'; 4 | import runWorker from './worker'; 5 | 6 | describe("restrict-overwrite tests", () => { 7 | let context: DeepMocked; 8 | let request: DeepMocked; 9 | 10 | beforeEach(() => { 11 | context = createMock({ 12 | clients: createMock({ 13 | platformHttp: createMock({ 14 | get: jest.fn().mockResolvedValue({ status: 200 }) 15 | }) 16 | }) 17 | }); 18 | request = createMock({ 19 | metadata: { repoPath: { key: 'my-repo', path: 'artifact.txt' } } 20 | }); 21 | }) 22 | 23 | it('should run', async () => { 24 | await expect(runWorker(context, request)).resolves.toEqual(expect.objectContaining({ 25 | message: 'Overwritten by worker-service if an error occurs.', 26 | status: ActionStatus.PROCEED, 27 | modifiedRepoPath: { key: 'my-repo', path: 'artifact.txt' } 28 | })) 29 | }) 30 | }); -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_MOVE/restrict-overwrite/worker.spec.ts: -------------------------------------------------------------------------------- 1 | import { PlatformContext, PlatformClients, PlatformHttpClient } from 'jfrog-workers'; 2 | import { createMock, DeepMocked } from '@golevelup/ts-jest'; 3 | import { BeforeMoveRequest, ActionStatus } from './types'; 4 | import runWorker from './worker'; 5 | 6 | describe("restrict-overwrite tests", () => { 7 | let context: DeepMocked; 8 | let request: DeepMocked; 9 | 10 | beforeEach(() => { 11 | context = createMock({ 12 | clients: createMock({ 13 | platformHttp: createMock({ 14 | get: jest.fn().mockResolvedValue({ status: 200 }) 15 | }) 16 | }) 17 | }); 18 | request = createMock({ 19 | metadata: { repoPath: { key: 'my-repo', path: 'artifact.txt' } } 20 | }); 21 | }) 22 | 23 | it('should run', async () => { 24 | await expect(runWorker(context, request)).resolves.toEqual(expect.objectContaining({ 25 | message: 'Overwritten by worker-service if an error occurs.', 26 | status: ActionStatus.PROCEED, 27 | modifiedRepoPath: { key: 'my-repo', path: 'artifact.txt' } 28 | })) 29 | }) 30 | }); -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_UPLOAD/repo-quota/worker.spec.ts: -------------------------------------------------------------------------------- 1 | import { PlatformContext, PlatformClients, PlatformHttpClient } from 'jfrog-workers'; 2 | import { createMock, DeepMocked } from '@golevelup/ts-jest'; 3 | import { BeforeUploadRequest, UploadStatus } from './types'; 4 | import runWorker from './worker'; 5 | 6 | describe("repo-quota tests", () => { 7 | let context: DeepMocked; 8 | let request: DeepMocked; 9 | 10 | beforeEach(() => { 11 | context = createMock({ 12 | clients: createMock({ 13 | platformHttp: createMock({ 14 | get: jest.fn().mockResolvedValue({ status: 200 }) 15 | }) 16 | }) 17 | }); 18 | request = createMock({ 19 | metadata: { repoPath: { key: 'my-repo', path: 'artifact.txt' } } 20 | }); 21 | }) 22 | 23 | it('should run', async () => { 24 | await expect(runWorker(context, request)).resolves.toEqual(expect.objectContaining({ 25 | message: 'Overwritten by worker-service if an error occurs.', 26 | status: UploadStatus.UPLOAD_PROCEED, 27 | modifiedRepoPath: { key: 'my-repo', path: 'artifact.txt' } 28 | })) 29 | }) 30 | }); -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/artifact-cleanup/worker.spec.ts: -------------------------------------------------------------------------------- 1 | import { PlatformContext, PlatformClients, PlatformHttpClient } from 'jfrog-workers'; 2 | import { createMock, DeepMocked } from '@golevelup/ts-jest'; 3 | import runWorker from './worker'; 4 | 5 | describe("artifact-cleanup tests", () => { 6 | let context: DeepMocked; 7 | const request: void = undefined; 8 | 9 | beforeEach(() => { 10 | context = createMock({ 11 | clients: createMock({ 12 | platformHttp: createMock({ 13 | get: jest.fn().mockResolvedValue({ 14 | status: 200, 15 | data: [ 16 | { type: 'a' }, 17 | { type: 'a' }, 18 | { type: 'b' }, 19 | { type: 'c' } 20 | ], 21 | }) 22 | }) 23 | }) 24 | }); 25 | }) 26 | 27 | it('should run', async () => { 28 | await expect(runWorker(context, request)).resolves.toEqual(expect.objectContaining({ 29 | repositories: expect.objectContaining({ 30 | a: 2, b: 1, c: 1 31 | }) 32 | })) 33 | }) 34 | }); -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/delete-empty-dirs/worker.spec.ts: -------------------------------------------------------------------------------- 1 | import { PlatformContext, PlatformClients, PlatformHttpClient } from 'jfrog-workers'; 2 | import { createMock, DeepMocked } from '@golevelup/ts-jest'; 3 | import runWorker from './worker'; 4 | 5 | describe("delete-empty-dirs tests", () => { 6 | let context: DeepMocked; 7 | const request: void = undefined; 8 | 9 | beforeEach(() => { 10 | context = createMock({ 11 | clients: createMock({ 12 | platformHttp: createMock({ 13 | get: jest.fn().mockResolvedValue({ 14 | status: 200, 15 | data: [ 16 | { type: 'a' }, 17 | { type: 'a' }, 18 | { type: 'b' }, 19 | { type: 'c' } 20 | ], 21 | }) 22 | }) 23 | }) 24 | }); 25 | }) 26 | 27 | it('should run', async () => { 28 | await expect(runWorker(context, request)).resolves.toEqual(expect.objectContaining({ 29 | repositories: expect.objectContaining({ 30 | a: 2, b: 1, c: 1 31 | }) 32 | })) 33 | }) 34 | }); -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/clean-docker-images/worker.spec.ts: -------------------------------------------------------------------------------- 1 | import { PlatformContext, PlatformClients, PlatformHttpClient } from 'jfrog-workers'; 2 | import { createMock, DeepMocked } from '@golevelup/ts-jest'; 3 | import runWorker from './worker'; 4 | 5 | describe("clean-docker-images tests", () => { 6 | let context: DeepMocked; 7 | const request: void = undefined; 8 | 9 | beforeEach(() => { 10 | context = createMock({ 11 | clients: createMock({ 12 | platformHttp: createMock({ 13 | get: jest.fn().mockResolvedValue({ 14 | status: 200, 15 | data: [ 16 | { type: 'a' }, 17 | { type: 'a' }, 18 | { type: 'b' }, 19 | { type: 'c' } 20 | ], 21 | }) 22 | }) 23 | }) 24 | }); 25 | }) 26 | 27 | it('should run', async () => { 28 | await expect(runWorker(context, request)).resolves.toEqual(expect.objectContaining({ 29 | repositories: expect.objectContaining({ 30 | a: 2, b: 1, c: 1 31 | }) 32 | })) 33 | }) 34 | }); -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_CREATE/restrict-overwrite/worker.spec.ts: -------------------------------------------------------------------------------- 1 | import { PlatformContext, PlatformClients, PlatformHttpClient } from 'jfrog-workers'; 2 | import { createMock, DeepMocked } from '@golevelup/ts-jest'; 3 | import { BeforeCreateRequest, ActionStatus } from './types'; 4 | import runWorker from './worker'; 5 | 6 | describe("restrict-overwrite tests", () => { 7 | let context: DeepMocked; 8 | let request: DeepMocked; 9 | 10 | beforeEach(() => { 11 | context = createMock({ 12 | clients: createMock({ 13 | platformHttp: createMock({ 14 | get: jest.fn().mockResolvedValue({ status: 200 }) 15 | }) 16 | }) 17 | }); 18 | request = createMock({ 19 | metadata: { repoPath: { key: 'my-repo', path: 'artifact.txt' } } 20 | }); 21 | }) 22 | 23 | it('should run', async () => { 24 | await expect(runWorker(context, request)).resolves.toEqual(expect.objectContaining({ 25 | message: 'Overwritten by worker-service if an error occurs.', 26 | status: ActionStatus.PROCEED, 27 | modifiedRepoPath: { key: 'my-repo', path: 'artifact.txt' } 28 | })) 29 | }) 30 | }); -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_UPLOAD/restrict-overwrite/worker.spec.ts: -------------------------------------------------------------------------------- 1 | import { PlatformContext, PlatformClients, PlatformHttpClient } from 'jfrog-workers'; 2 | import { createMock, DeepMocked } from '@golevelup/ts-jest'; 3 | import { BeforeUploadRequest, UploadStatus } from './types'; 4 | import runWorker from './worker'; 5 | 6 | describe("restrict-overwrite tests", () => { 7 | let context: DeepMocked; 8 | let request: DeepMocked; 9 | 10 | beforeEach(() => { 11 | context = createMock({ 12 | clients: createMock({ 13 | platformHttp: createMock({ 14 | get: jest.fn().mockResolvedValue({ status: 200 }) 15 | }) 16 | }) 17 | }); 18 | request = createMock({ 19 | metadata: { repoPath: { key: 'my-repo', path: 'artifact.txt' } } 20 | }); 21 | }) 22 | 23 | it('should run', async () => { 24 | await expect(runWorker(context, request)).resolves.toEqual(expect.objectContaining({ 25 | message: 'Overwritten by worker-service if an error occurs.', 26 | status: UploadStatus.UPLOAD_PROCEED, 27 | modifiedRepoPath: { key: 'my-repo', path: 'artifact.txt' } 28 | })) 29 | }) 30 | }); -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/delete-by-property-value/worker.spec.ts: -------------------------------------------------------------------------------- 1 | import { PlatformContext, PlatformClients, PlatformHttpClient } from 'jfrog-workers'; 2 | import { createMock, DeepMocked } from '@golevelup/ts-jest'; 3 | import runWorker from './worker'; 4 | 5 | describe("delete-by-property-value tests", () => { 6 | let context: DeepMocked; 7 | const request: void = undefined; 8 | 9 | beforeEach(() => { 10 | context = createMock({ 11 | clients: createMock({ 12 | platformHttp: createMock({ 13 | get: jest.fn().mockResolvedValue({ 14 | status: 200, 15 | data: [ 16 | { type: 'a' }, 17 | { type: 'a' }, 18 | { type: 'b' }, 19 | { type: 'c' } 20 | ], 21 | }) 22 | }) 23 | }) 24 | }); 25 | }) 26 | 27 | it('should run', async () => { 28 | await expect(runWorker(context, request)).resolves.toEqual(expect.objectContaining({ 29 | repositories: expect.objectContaining({ 30 | a: 2, b: 1, c: 1 31 | }) 32 | })) 33 | }) 34 | }); -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_UPLOAD/allow-upload-by-pattern/worker.spec.ts: -------------------------------------------------------------------------------- 1 | import { PlatformContext, PlatformClients, PlatformHttpClient } from 'jfrog-workers'; 2 | import { createMock, DeepMocked } from '@golevelup/ts-jest'; 3 | import { BeforeUploadRequest, UploadStatus } from './types'; 4 | import runWorker from './worker'; 5 | 6 | describe("allow-upload-by-pattern tests", () => { 7 | let context: DeepMocked; 8 | let request: DeepMocked; 9 | 10 | beforeEach(() => { 11 | context = createMock({ 12 | clients: createMock({ 13 | platformHttp: createMock({ 14 | get: jest.fn().mockResolvedValue({ status: 200 }) 15 | }) 16 | }) 17 | }); 18 | request = createMock({ 19 | metadata: { repoPath: { key: 'my-repo', path: 'artifact.txt' } } 20 | }); 21 | }) 22 | 23 | it('should run', async () => { 24 | await expect(runWorker(context, request)).resolves.toEqual(expect.objectContaining({ 25 | message: 'Overwritten by worker-service if an error occurs.', 26 | status: UploadStatus.UPLOAD_PROCEED, 27 | modifiedRepoPath: { key: 'my-repo', path: 'artifact.txt' } 28 | })) 29 | }) 30 | }); -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/repoStats/README.md: -------------------------------------------------------------------------------- 1 | Artifactory Repo Stats Worker 2 | ============================== 3 | 4 | This Worker displays certain statistics about requested files. 5 | Given one or more repo name, this worker will show the number of artifacts and 6 | the combined filesize of those artifacts at each of the repos. 7 | 8 | Payload 9 | ------- 10 | 11 | The worker expects a JSON object of repo keys as a comma separated string. For example, if you'd like to 12 | get repo stat of `example-repo` and also `local-repo`, your payload would be: 13 | 14 | ```json 15 | { 16 | "paths": "example-repo,local-repo", 17 | } 18 | ``` 19 | 20 | 21 | Usage 22 | ----- 23 | 24 | The worker can be executed using as a Generic event. 25 | 26 | 27 | ```shell 28 | curl -X POST "http:///worker/api/v1/execute/RepoStats" --header 'Content-Type: application/json' --header 'Authorization: Bearer ' --data '{ "paths": "example-repo-local"}' 29 | EOF 30 | ``` 31 | sample response: 32 | ```json 33 | { 34 | "data": { 35 | "message": "Path exists: example-repo-local", 36 | "stats": [ 37 | { 38 | "repoPath": "example-repo-local", 39 | "count": 3, 40 | "usedSpaceInBytes": 5131510, 41 | "usedSpace": "4.89 MB" 42 | } 43 | ] 44 | }, 45 | "executionStatus": "STATUS_SUCCESS" 46 | } 47 | ``` 48 | -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_UPLOAD/check-project-repository/worker.spec.ts: -------------------------------------------------------------------------------- 1 | import { PlatformContext, PlatformClients, PlatformHttpClient } from 'jfrog-workers'; 2 | import { createMock, DeepMocked } from '@golevelup/ts-jest'; 3 | import { BeforeUploadRequest, UploadStatus } from './types'; 4 | import runWorker from './worker'; 5 | 6 | describe("redirect-to-project-repo tests", () => { 7 | let context: DeepMocked; 8 | let request: DeepMocked; 9 | 10 | beforeEach(() => { 11 | context = createMock({ 12 | clients: createMock({ 13 | platformHttp: createMock({ 14 | get: jest.fn().mockResolvedValue({ status: 200 }) 15 | }) 16 | }) 17 | }); 18 | request = createMock({ 19 | metadata: { repoPath: { key: 'my-repo', path: 'artifact.txt' } } 20 | }); 21 | }) 22 | 23 | it('should run', async () => { 24 | await expect(runWorker(context, request)).resolves.toEqual(expect.objectContaining({ 25 | message: 'Overwritten by worker-service if an error occurs.', 26 | status: UploadStatus.UPLOAD_PROCEED, 27 | modifiedRepoPath: { key: 'my-repo', path: 'artifact.txt' } 28 | })) 29 | }) 30 | }); -------------------------------------------------------------------------------- /samples/artifactory/AFTER_CREATE/copy-deb-and-rpm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "copy-deb-and-rpm", 3 | "description": "This worker script is designed to handle package copy operations between repositories in a JFrog Artifactory environment. It monitors specified repositories for newly created `.deb` and `.rpm` files, parses the file paths using a predefined layout, and copies the files to designated target repositories.", 4 | "version": "1.0.1", 5 | "scripts": { 6 | "deploy": "jf worker deploy", 7 | "undeploy": "jf worker rm \"copy-deb-and-rpm\"", 8 | "test": "jest" 9 | }, 10 | "license": "ISC", 11 | "devDependencies": { 12 | "jfrog-workers": "^0.8.0", 13 | "@golevelup/ts-jest": "^0.4.0", 14 | "@types/jest": "^29.5.12", 15 | "jest": "^29.7.0", 16 | "jest-jasmine2": "^29.7.0", 17 | "ts-jest": "^29.1.2" 18 | }, 19 | "jest": { 20 | "moduleFileExtensions": [ 21 | "ts", 22 | "js" 23 | ], 24 | "rootDir": ".", 25 | "testEnvironment": "node", 26 | "clearMocks": true, 27 | "maxConcurrency": 1, 28 | "testRegex": "\\.spec\\.ts$", 29 | "moduleDirectories": ["node_modules"], 30 | "collectCoverageFrom": [ 31 | "**/*.ts" 32 | ], 33 | "coverageDirectory": "../coverage", 34 | "transform": { 35 | "^.+\\.(t|j)s$": "ts-jest" 36 | }, 37 | "testRunner": "jest-jasmine2", 38 | "verbose": true 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /samples/runtime/AFTER_WORKLOAD_STATE_CHANGE/runtime-alerting/types.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface WorkloadChangedEvent { 3 | change_type: string 4 | workload_changed_object: WorkloadChangedObject 5 | image_tags_object: ImageTagsObject[] 6 | } 7 | 8 | export interface WorkloadChangedObject { 9 | name: string 10 | namespace: string 11 | cluster: string 12 | nodes: string[] 13 | risks: string[] 14 | vulnerabilities_count: number 15 | } 16 | 17 | export interface ImageTagsObject { 18 | name: string 19 | registry: string 20 | repository_path: string 21 | architecture: string 22 | tag: string 23 | sha256: string 24 | risks: string[] 25 | vulnerabilities?: Vulnerability[] 26 | malicious_packages: any 27 | deployed_by: string 28 | build_info: BuildInfo 29 | } 30 | 31 | export interface Vulnerability { 32 | package_type: string 33 | xray_id: string 34 | cve_id: string 35 | severity: string 36 | cvss_v2: string 37 | cvss_v3: string 38 | last_fetched: string 39 | issue_kind: number 40 | applicability: string 41 | components: Component[] 42 | } 43 | 44 | export interface Component { 45 | component_id: string 46 | name: string 47 | version: string 48 | } 49 | 50 | export interface BuildInfo { 51 | build_owner: string 52 | build_name: string 53 | build_number: string 54 | build_repository: string 55 | } 56 | 57 | export type AfterWorkloadStateChangeRequest = WorkloadChangedEvent; 58 | -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/repoStats/worker.spec.ts: -------------------------------------------------------------------------------- 1 | import { PlatformContext, PlatformClients, PlatformHttpClient } from 'jfrog-workers'; 2 | import { createMock, DeepMocked } from '@golevelup/ts-jest'; 3 | import runWorker from './worker'; 4 | 5 | describe("repo-stats tests", () => { 6 | let context: DeepMocked; 7 | const request: void = undefined; 8 | 9 | beforeEach(() => { 10 | context = createMock({ 11 | clients: createMock({ 12 | platformHttp: createMock({ 13 | get: jest.fn().mockResolvedValue({ 14 | status: 200, 15 | // update the data to match the content of the actual data 16 | data: [ 17 | { type: 'a' }, 18 | { type: 'a' }, 19 | { type: 'b' }, 20 | { type: 'c' } 21 | ], 22 | }) 23 | }) 24 | }) 25 | }); 26 | }) 27 | 28 | it('should run', async () => { 29 | await expect(runWorker(context, request)).resolves.toEqual(expect.objectContaining({ 30 | repositories: expect.objectContaining({ 31 | // update the expected values to match the data 32 | a: 2, b: 1, c: 1 33 | }) 34 | })) 35 | }) 36 | }); -------------------------------------------------------------------------------- /samples/artifactory/AFTER_DOWNLOAD/report-download-to-external-endpoint/worker.ts: -------------------------------------------------------------------------------- 1 | import { PlatformContext } from 'jfrog-workers'; 2 | import { AfterDownloadRequest, AfterDownloadResponse } from './types'; 3 | import { AxiosRequestConfig } from 'axios'; 4 | 5 | export default async (context: PlatformContext, data: AfterDownloadRequest): Promise => { 6 | // MODIFY THOSE TWO CONSTANTS TO FIT YOUR NEEDS 7 | const URL = 'https://'; 8 | const SECRET_NAME = 'myBearerToken'; 9 | 10 | let message = 'Download activity successfully logged'; 11 | try { 12 | const downloadLog = `The artifact '${data.metadata.repoPath.path}' has been downloaded by the ${data.userContext.isToken ? 'token' : 'userid'}: ${data.userContext.id} from the repository '${data.metadata.repoPath.key}'.`; 13 | const res = await context.clients.axios.post(URL, { 14 | downloadLog 15 | }, { 16 | headers: { 17 | Authorization: `bearer ${context.secrets.get(SECRET_NAME)}` 18 | } 19 | }); 20 | if (res.status === 200) { 21 | console.log("Successfuly logged download"); 22 | } else { 23 | console.warn(`Failed to log download activity. Status code : ${res.status}`); 24 | message = 'Failed to log download activity'; 25 | } 26 | } catch (error) { 27 | console.error(`Failed to log download activity, caused by : ${error.message}`) 28 | message = 'Failed to log download activity'; 29 | } 30 | 31 | return { 32 | message 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_UPLOAD/allow-upload-by-pattern/worker.ts: -------------------------------------------------------------------------------- 1 | import { PlatformContext } from 'jfrog-workers'; 2 | import { BeforeUploadRequest, BeforeUploadResponse, UploadStatus } from './types'; 3 | 4 | export default async (context: PlatformContext, data: BeforeUploadRequest): Promise => { 5 | // This RegExp will match all repopaths that start with 'org/company/' and end with the extension .jar OR .war 6 | // For instance those paths will match the regex : 7 | // - org/company/src/app.jar 8 | // - org/company/package1/subPackage/webapp.war 9 | const authorizedPathRegEx = /^org\/company\/(?:\w+.\/)+[\w\-\.]+\.(?:jar|war)$/; 10 | let status: UploadStatus = UploadStatus.UPLOAD_UNSPECIFIED; 11 | let message = ""; 12 | 13 | try { 14 | if (authorizedPathRegEx.exec(data.metadata.repoPath.path)) { 15 | status = UploadStatus.UPLOAD_PROCEED; 16 | message = `RepoPath '${data.metadata.repoPath.path}' is acceptable for the repository '${data.metadata.repoPath.key}'`; 17 | } else { 18 | status = UploadStatus.UPLOAD_STOP; 19 | message = `RepoPath '${data.metadata.repoPath.path}' does not match the regex ${authorizedPathRegEx} for the repository '${data.metadata.repoPath.key}'`; 20 | } 21 | } catch(error) { 22 | status = UploadStatus.UPLOAD_WARN; 23 | console.error(`could not check the path: ${error}`); 24 | message = `An error occurred during the check. Proceed with warning.`; 25 | } 26 | 27 | return { 28 | status, 29 | message, 30 | modifiedRepoPath: data.metadata.repoPath 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_UPLOAD/check-project-repository/payload.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "repoPath": { 4 | "key": "docker-local", 5 | "path": "my-image/1.0.0/manifest.json", 6 | "id": "docker-local:my-image/1.0.0/manifest.json" 7 | }, 8 | "contentLength": -1, 9 | "lastModified": 1724072721599, 10 | "disableRedirect": true, 11 | "repoType": 1 12 | }, 13 | "headers": { 14 | "X-Checksum-Sha256": { 15 | "value": [ 16 | "27e7e0544d18d4ea7cd6a01d097c263e87e1115548b6e5930354acf8b7b97713" 17 | ] 18 | } 19 | }, 20 | "userContext": { 21 | "id": "jfac@01j53gsv4ycnf91zvtctac087v/users/admin", 22 | "isToken": true 23 | }, 24 | "artifactProperties": { 25 | "docker.manifest": { 26 | "value": [ 27 | "1.0.0" 28 | ] 29 | }, 30 | "sha256": { 31 | "value": [ 32 | "27e7e0544d18d4ea7cd6a01d097c263e87e1115548b6e5930354acf8b7b97713" 33 | ] 34 | }, 35 | "oci.artifact.type": { 36 | "value": [ 37 | "application/vnd.docker.container.image.v1+json" 38 | ] 39 | }, 40 | "docker.repoName": { 41 | "value": [ 42 | "my-image" 43 | ] 44 | }, 45 | "docker.manifest.digest": { 46 | "value": [ 47 | "sha256:27e7e0544d18d4ea7cd6a01d097c263e87e1115548b6e5930354acf8b7b97713" 48 | ] 49 | }, 50 | "docker.label.com.jfrog.artifactory.projectKey": { 51 | "value": [ 52 | "project-1" 53 | ] 54 | }, 55 | "docker.manifest.type": { 56 | "value": [ 57 | "application/vnd.docker.distribution.manifest.v2+json" 58 | ] 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/oldBuildCleanup/README.md: -------------------------------------------------------------------------------- 1 | # oldBuildCleanup Worker 2 | 3 | ## Overview 4 | 5 | The `oldBuildCleanup` worker is designed to delete outdated builds from JFrog Artifactory based on a specified build name and build number threshold. This worker helps maintain a clean and efficient build repository by removing older builds and optionally their artifacts. 6 | 7 | ## Features 8 | 9 | - Deletes builds up to a specified build number. 10 | - Supports optional deletion of build artifacts. 11 | - Logs the cleanup process for visibility and tracking. 12 | 13 | ## How It Works 14 | 15 | The worker performs the following steps: 16 | 17 | 1. Fetches all builds for a given build name. 18 | 2. Identifies builds that are older than or equal to the specified build number. 19 | 3. Deletes matching builds and optionally their artifacts. 20 | 21 | ## Parameters 22 | 23 | The worker accepts the following parameters: 24 | 25 | | Parameter | Type | Description | 26 | |----------------|--------|----------------------------------------------------------| 27 | | `buildName` | string | The name of the build to clean up. | 28 | | `buildNumber` | number | The build number threshold; all builds up to this number will be deleted. | 29 | | `cleanArtifacts` | boolean | When `true`, deletes the associated build artifacts as well. | 30 | 31 | ## Example Usage 32 | 33 | ### Input Parameters 34 | 35 | ```json 36 | { 37 | "buildName": "example-build", 38 | "buildNumber": 10, 39 | "cleanArtifacts": true 40 | } 41 | 42 | ### Execution Link: 43 | 44 | ```bash 45 | curl -u your-username:your-api-key \ 46 | -X POST \ 47 | "${baseUrl}/worker/api/v1/execute/oldBuildCleanup" \ 48 | -H "Content-Type: application/json" \ 49 | -d '{ 50 | "buildName": "example-build", 51 | "buildNumber": 10, 52 | "cleanArtifacts": true 53 | }' 54 | ``` 55 | -------------------------------------------------------------------------------- /samples/access/BEFORE_CREATE_TOKEN/token-validation/types.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface BeforeCreateTokenRequest { 3 | /** The spec of the token to create */ 4 | tokenSpec: 5 | | TokenSpec 6 | | undefined; 7 | /** The user context which sends the request */ 8 | userContext: 9 | | UserContext 10 | | undefined; 11 | } 12 | 13 | export interface TokenSpec { 14 | /** The subject the token belongs to */ 15 | subject: string; 16 | /** The owner of the token */ 17 | owner: string; 18 | /** A list of application specific scopes to grant the user in the generated token */ 19 | scope: string[]; 20 | /** The audience (i.e. services) this token is aimed for. These services are expected to accept this token. */ 21 | audience: string[]; 22 | /** Specific expiry in seconds - i.e. for how long the token should be accepted */ 23 | expiresIn: number; 24 | /** Set whether the generated token also has a refresh token. */ 25 | refreshable: boolean; 26 | /** Optional payload to put in the token */ 27 | extension: string; 28 | /** Optional free text to put in the token */ 29 | description: string; 30 | /** Set whether the generated token also has a reference token. */ 31 | includeReferenceToken: boolean; 32 | } 33 | 34 | export interface UserContext { 35 | /** The username or subject */ 36 | id: string; 37 | /** Is the context an accessToken */ 38 | isToken: boolean; 39 | /** The realm of the user */ 40 | realm: string; 41 | } 42 | 43 | export interface BeforeCreateTokenResponse { 44 | /** The instruction of how to proceed */ 45 | status: CreateTokenStatus; 46 | /** Message to print to the log, in case of an error it will be printed as a warning */ 47 | message: string; 48 | } 49 | 50 | export enum CreateTokenStatus { 51 | CREATE_TOKEN_UNSPECIFIED = 0, 52 | CREATE_TOKEN_PROCEED = 1, 53 | CREATE_TOKEN_STOP = 2, 54 | CREATE_TOKEN_WARN = 3, 55 | UNRECOGNIZED = -1, 56 | } 57 | -------------------------------------------------------------------------------- /samples/artifactory/eventCombinationWorkers/mavenSnapshotCleanupWhenRelease/AFTER_CREATE/mavenSnapshotCleanupWhenRelease_afterCreate/worker.ts: -------------------------------------------------------------------------------- 1 | import { PlatformContext } from 'jfrog-workers'; 2 | import { AfterCreateRequest, AfterCreateResponse } from './types'; 3 | 4 | export default async (context: PlatformContext, data: AfterCreateRequest): Promise => { 5 | let message = 'proceed'; 6 | try { 7 | const repoKey = data.metadata.repoPath.key; 8 | const itemPath = data.metadata.repoPath.path; 9 | 10 | if (itemPath.endsWith('.pom')) { 11 | await cleanupSnapshots(context, repoKey, itemPath); 12 | } 13 | } catch (error) { 14 | console.error(`Error in afterCreate: ${error.message}`); 15 | message = 'error'; 16 | } 17 | return { message }; 18 | }; 19 | 20 | async function cleanupSnapshots(context: PlatformContext, releaseRepo: string, pomPath: string) { 21 | console.log(`Checking for snapshot cleanup: ${releaseRepo}, ${pomPath}`); 22 | 23 | const snapshotRepo = await findSnapshotRepo(context, releaseRepo); 24 | if (!snapshotRepo) return; 25 | 26 | const snapshotPath = pomPath.replace(/\/[^/]+\.pom$/, '-SNAPSHOT/'); 27 | const snapshotFullPath = `${snapshotRepo}/${snapshotPath}`; 28 | 29 | console.log(`Deleting snapshot: ${snapshotFullPath}`); 30 | await context.clients.platformHttp.delete(`/artifactory/${snapshotFullPath}`); 31 | } 32 | 33 | async function findSnapshotRepo(context: PlatformContext, releaseRepo: string): Promise { 34 | try { 35 | const repoConfig = await context.clients.platformHttp.get(`/artifactory/api/repositories/${releaseRepo}`); 36 | if (repoConfig.data?.packageType === 'maven' && repoConfig.data.handleReleases) { 37 | return repoConfig.data.key.replace(/-releases$/, '-snapshots'); 38 | } 39 | } catch (error) { 40 | console.error(`Failed to fetch repository configuration for ${releaseRepo}: ${error.message}`); 41 | } 42 | return null; 43 | } 44 | -------------------------------------------------------------------------------- /samples/artifactory/eventCombinationWorkers/mavenSnapshotCleanupWhenRelease/AFTER_MOVE/mavenSnapshotCleanupWhenRelease_afterMove/worker.ts: -------------------------------------------------------------------------------- 1 | import { PlatformContext } from 'jfrog-workers'; 2 | import { AfterMoveRequest, AfterMoveResponse } from './types'; 3 | 4 | export default async (context: PlatformContext, data: AfterMoveRequest): Promise => { 5 | let message = 'proceed'; 6 | try { 7 | const targetRepoKey = data.target.repoPath.key; 8 | const targetPath = data.target.repoPath.path; 9 | 10 | if (targetPath.endsWith('.pom')) { 11 | await cleanupSnapshots(context, targetRepoKey, targetPath); 12 | } 13 | } catch (error) { 14 | console.error(`Error in afterMove: ${error.message}`); 15 | message = 'error'; 16 | } 17 | return { message }; 18 | }; 19 | 20 | async function cleanupSnapshots(context: PlatformContext, releaseRepo: string, pomPath: string) { 21 | console.log(`Checking for snapshot cleanup after move: ${releaseRepo}, ${pomPath}`); 22 | 23 | const snapshotRepo = await findSnapshotRepo(context, releaseRepo); 24 | if (!snapshotRepo) return; 25 | 26 | const snapshotPath = pomPath.replace(/\/[^/]+\.pom$/, '-SNAPSHOT/'); 27 | const snapshotFullPath = `${snapshotRepo}/${snapshotPath}`; 28 | 29 | console.log(`Deleting snapshot after move: ${snapshotFullPath}`); 30 | await context.clients.platformHttp.delete(`/artifactory/${snapshotFullPath}`); 31 | } 32 | 33 | async function findSnapshotRepo(context: PlatformContext, releaseRepo: string): Promise { 34 | try { 35 | const repoConfig = await context.clients.platformHttp.get(`/artifactory/api/repositories/${releaseRepo}`); 36 | if (repoConfig.data?.packageType === 'maven' && repoConfig.data.handleReleases) { 37 | return repoConfig.data.key.replace(/-releases$/, '-snapshots'); 38 | } 39 | } catch (error) { 40 | console.error(`Failed to fetch repository configuration for ${releaseRepo}: ${error.message}`); 41 | } 42 | return null; 43 | } 44 | -------------------------------------------------------------------------------- /samples/artifactory/AFTER_CREATE/copy-deb-and-rpm/types.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface AfterCreateRequest { 3 | /** Various immutable upload metadata */ 4 | metadata: 5 | | UploadMetadata 6 | | undefined; 7 | /** The immutable request headers */ 8 | headers: { [key: string]: Header }; 9 | /** The user context which sends the request */ 10 | userContext: UserContext | undefined; 11 | } 12 | 13 | export interface UploadMetadata { 14 | /** The repoPath object of the request */ 15 | repoPath: 16 | | RepoPath 17 | | undefined; 18 | /** The deploy request content length */ 19 | contentLength: number; 20 | /** Last modification time that occurred */ 21 | lastModified: number; 22 | /** Is the request trusting the server checksums */ 23 | trustServerChecksums: boolean; 24 | /** The url that points to artifactory */ 25 | servletContextUrl: string; 26 | /** Is it a request that skips jar indexing */ 27 | skipJarIndexing: boolean; 28 | /** Is redirect disabled on this request */ 29 | disableRedirect: boolean; 30 | /** Repository type */ 31 | repoType: RepoType; 32 | } 33 | 34 | export interface AfterCreateResponse { 35 | /** Message to print to the log, in case of an error it will be printed as a warning */ 36 | message: string; 37 | } 38 | 39 | export interface RepoPath { 40 | /** The repo key */ 41 | key: string; 42 | /** The path itself */ 43 | path: string; 44 | /** The key:path combination */ 45 | id: string; 46 | /** Is the path the root */ 47 | isRoot: boolean; 48 | /** Is the path a folder */ 49 | isFolder: boolean; 50 | } 51 | 52 | export interface Header { 53 | value: string[]; 54 | } 55 | 56 | export interface UserContext { 57 | /** The username or subject */ 58 | id: string; 59 | /** Is the context an accessToken */ 60 | isToken: boolean; 61 | /** The realm of the user */ 62 | realm: string; 63 | } 64 | 65 | export enum RepoType { 66 | REPO_TYPE_UNSPECIFIED = 0, 67 | REPO_TYPE_LOCAL = 1, 68 | REPO_TYPE_REMOTE = 2, 69 | REPO_TYPE_FEDERATED = 3, 70 | UNRECOGNIZED = -1, 71 | } 72 | -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/README.md: -------------------------------------------------------------------------------- 1 | # Generic Event Workers 2 | 3 | ## Overview 4 | 5 | Generic Event Workers are specialized JFrog workers that are not tied to specific Artifactory events. Instead, they are executed on demand via API calls or manual invocation. These workers are highly customizable, allowing you to perform tasks that are not bound to predefined triggers. 6 | 7 | ## Key Features 8 | 9 | - **Manual Execution:** Can be triggered manually or through a specific API call. 10 | - **Custom Logic:** Supports flexible implementation for a variety of use cases. 11 | - **Independent Operation:** Operates independently of Artifactory's standard event-driven workflow. 12 | 13 | ## Use Cases 14 | 15 | 1. **Custom Maintenance Tasks:** Perform one-off operations such as cleaning up repositories or updating artifact metadata. 16 | 2. **Scheduled Jobs:** Execute pre-scheduled tasks using external job schedulers. 17 | 3. **Custom API Integration:** Enable workflows that require external API calls or integrations. 18 | 19 | ## Example Worker Logic 20 | 21 | Here’s a simple example of a Generic Event Worker: 22 | 23 | ```typescript 24 | import { PlatformContext, GenericEventRequest, GenericEventResponse } from 'jfrog-workers'; 25 | 26 | export default async (context: PlatformContext, data: GenericEventRequest): Promise => { 27 | try { 28 | console.log("Worker triggered manually with data:", data); 29 | return { 30 | message: "Worker executed successfully", 31 | status: "SUCCESS" 32 | }; 33 | } catch (error) { 34 | console.error("Worker execution failed:", error.message); 35 | return { 36 | message: "Worker execution failed", 37 | status: "FAILURE" 38 | }; 39 | } 40 | }; 41 | ``` 42 | 43 | ## Recommendations 44 | 45 | - **Authentication:** Ensure only authorized users or systems can trigger the worker. 46 | - **Input Validation:** Validate the input payload to prevent errors or misuse. 47 | - **Logging and Monitoring:** Log all executions for auditing and debugging purposes. 48 | 49 | ## License 50 | 51 | This worker script and documentation are provided under the MIT License. 52 | -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/repoStats/worker.ts: -------------------------------------------------------------------------------- 1 | import { PlatformContext } from 'jfrog-workers'; 2 | 3 | interface RepoStatsPayload { 4 | paths: string 5 | }; 6 | 7 | interface RepoStatsResponse { 8 | error: string | undefined, 9 | message: string | undefined 10 | stats: RepoStat[], 11 | }; 12 | 13 | interface RepoStat { 14 | repoPath: string, 15 | count: number, 16 | usedSpace: string, 17 | usedSpaceInBytes: number 18 | } 19 | 20 | export default async (context: PlatformContext, data: RepoStatsPayload): Promise => { 21 | 22 | const response: RepoStatsResponse = { 23 | error: undefined, 24 | message: undefined, 25 | stats: [] 26 | }; 27 | 28 | if (!data.paths) { 29 | response.error = `repoKey is missing`; 30 | return response; 31 | } 32 | 33 | try { 34 | const storageinfo = await context.clients.platformHttp.get('/artifactory/api/storageinfo'); 35 | if (storageinfo.status !== 200) { 36 | response.error = `Request is successful but returned an unexpected status : ${storageinfo.status}`; 37 | return response; 38 | } 39 | const repositoriesSummaryList = storageinfo.data?.repositoriesSummaryList; 40 | if (!repositoriesSummaryList) { 41 | response.error = `No repositories summary available ${JSON.stringify(storageinfo.data)}`; 42 | return response; 43 | } 44 | 45 | const repoKeys: Set = new Set(data.paths.split(",")); 46 | let repoFound: string = ""; 47 | for (const repoData of repositoriesSummaryList) { 48 | if (repoKeys.has(repoData.repoKey)) { 49 | response.stats.push({ 50 | "repoPath": repoData.repoKey, 51 | "count": repoData.itemsCount, 52 | "usedSpaceInBytes": repoData.usedSpaceInBytes, 53 | "usedSpace": repoData.usedSpace 54 | }) 55 | repoFound += `${repoData.repoKey},`; 56 | } 57 | } 58 | repoFound = repoFound.slice(0, -1); 59 | response.message = repoFound ? `Path exists: ${repoFound}` : `No Path exists`; 60 | } catch (error) { 61 | response.error = `Request failed with status code ${error.status ?? ''} caused by : ${error.message}`; 62 | console.error(response.error); 63 | } 64 | return response; 65 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JFrog Workers Samples 2 | 3 | ## JFrog Workers 4 | 5 | JFrog Workers is a service in the JFrog Platform that provides a serverless execution environment. 6 | You can create workers that react to events in the JFrog Platform similar to AWS Lambda services. 7 | Workers service provides more flexibility to accomplish your use cases. 8 | You can use these workers to perform certain tasks that extend the capabilities of the JFrog Platform according to your requirements. 9 | 10 | See the full documentation [here](https://jfrog.com/help/r/jfrog-platform-administration-documentation/workers). 11 | 12 | ## Using the samples 13 | 14 | This repository contains a collection of sample workers for common use cases. Feel free to use, modify, and extend these samples to accomplish your use cases. 15 | 16 | We have created these TypeScript samples based on [Artifactory User Plugin Samples](https://github.com/jfrog/artifactory-user-plugins). 17 | 18 | Please submit a pull request if you have other valuable samples. 19 | 20 | The samples are located under the _samples_ directory with the following layout: 21 | ``` 22 | /samples 23 | / // Must match the product name used in the action metadata 24 | / 25 | / 26 | /README.md // (optional) The Worker doc 27 | /worker.ts // The Worker code 28 | /manifest.json // Metadata of the Worker 29 | /payload-example.json // (optional) Defines an example of the event’s payload content. 30 | ``` 31 | 32 | Each sample folder has been generated with the [JFrog CLI](https://docs.jfrog-applications.jfrog.io/jfrog-applications/jfrog-cli). 33 | 34 | The worker code resides in a file named _worker.ts_. 35 | 36 | For an introduction on how to develop JFrog Workers using the JFrog CLI checkout [this blogpost](https://jfrog.com/blog/doing-devops-your-way-on-saas-solutions-connecting-jfrog-cli-to-your-jfrog-workers/). 37 | 38 | You can also refer to the [documentation](https://jfrog.com/help/r/jfrog-platform-administration-documentation/workers) to discover how to setup and use workers. 39 | 40 | ## Contributing 41 | 42 | Feel free to contribute new samples, and please create issues if you need our support. 43 | 44 | --- 45 | 46 | Copyright © 2023-* JFrog Ltd. 47 | -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_COPY/restrict-overwrite/worker.ts: -------------------------------------------------------------------------------- 1 | import { PlatformContext } from 'jfrog-workers'; 2 | import { BeforeCopyRequest, BeforeCopyResponse, RepoPath, ActionStatus } from './types'; 3 | 4 | 5 | export default async function (context: PlatformContext, data: BeforeCopyRequest): Promise> { 6 | try { 7 | return restrictOverwrite(context, data); 8 | } catch (x) { 9 | return { status: ActionStatus.STOP, message: x.message }; 10 | } 11 | } 12 | 13 | async function restrictOverwrite(context: PlatformContext, data: BeforeCopyRequest): Promise> { 14 | const existingItem = await getExistingItemInfo(context, data.targetRepoPath); 15 | 16 | if (existingItem && !(data.metadata.repoPath.isFolder && existingItem.isFolder)) { 17 | return { 18 | status: ActionStatus.STOP, 19 | message: `${data.metadata.repoPath.id} already exists`, 20 | }; 21 | } 22 | 23 | return { status: ActionStatus.PROCEED }; 24 | } 25 | 26 | async function getExistingItemInfo(context: PlatformContext, repoPath: RepoPath): Promise { 27 | const pathParts = repoPath.path.split('/') 28 | const query = { 29 | repo: repoPath.key, 30 | path: pathParts.length === 1 ? '.' : pathParts.slice(0, pathParts.length - 1).join('/'), 31 | name: pathParts[pathParts.length - 1] 32 | }; 33 | const aqlResult = await runAql(context, `items.find(${JSON.stringify(query)}).include("type").limit(1)`) 34 | if (aqlResult.length) { 35 | return { isFolder: aqlResult[0].type === 'folder' }; 36 | } 37 | return undefined; 38 | } 39 | 40 | async function runAql(context: PlatformContext, query: string) { 41 | console.log(`Running AQL: ${query}`) 42 | try { 43 | const queryResponse = await context.clients.platformHttp.post( 44 | '/artifactory/api/search/aql', 45 | query, 46 | { 47 | 'Content-Type': 'text/plain' 48 | }); 49 | return (queryResponse.data.results || []) as Array; 50 | } catch (x) { 51 | console.log(`AQL query failed: ${x.message}`); 52 | } 53 | return []; 54 | } 55 | 56 | interface RepoItemInfo { 57 | isFolder: boolean 58 | } -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_MOVE/restrict-overwrite/worker.ts: -------------------------------------------------------------------------------- 1 | import { PlatformContext } from 'jfrog-workers'; 2 | import { BeforeMoveRequest, BeforeMoveResponse, RepoPath, ActionStatus } from './types'; 3 | 4 | 5 | export default async function (context: PlatformContext, data: BeforeMoveRequest): Promise> { 6 | try { 7 | return restrictOverwrite(context, data); 8 | } catch (x) { 9 | return { status: ActionStatus.STOP, message: x.message }; 10 | } 11 | } 12 | 13 | async function restrictOverwrite(context: PlatformContext, data: BeforeMoveRequest): Promise> { 14 | const existingItem = await getExistingItemInfo(context, data.targetRepoPath); 15 | 16 | if (existingItem && !(data.metadata.repoPath.isFolder && existingItem.isFolder)) { 17 | return { 18 | status: ActionStatus.STOP, 19 | message: `${data.metadata.repoPath.id} already exists`, 20 | }; 21 | } 22 | 23 | return { status: ActionStatus.PROCEED }; 24 | } 25 | 26 | async function getExistingItemInfo(context: PlatformContext, repoPath: RepoPath): Promise { 27 | const pathParts = repoPath.path.split('/') 28 | const query = { 29 | repo: repoPath.key, 30 | path: pathParts.length === 1 ? '.' : pathParts.slice(0, pathParts.length - 1).join('/'), 31 | name: pathParts[pathParts.length - 1] 32 | }; 33 | const aqlResult = await runAql(context, `items.find(${JSON.stringify(query)}).include("type").limit(1)`) 34 | if (aqlResult.length) { 35 | return { isFolder: aqlResult[0].type === 'folder' }; 36 | } 37 | return undefined; 38 | } 39 | 40 | async function runAql(context: PlatformContext, query: string) { 41 | console.log(`Running AQL: ${query}`) 42 | try { 43 | const queryResponse = await context.clients.platformHttp.post( 44 | '/artifactory/api/search/aql', 45 | query, 46 | { 47 | 'Content-Type': 'text/plain' 48 | }); 49 | return (queryResponse.data.results || []) as Array; 50 | } catch (x) { 51 | console.log(`AQL query failed: ${x.message}`); 52 | } 53 | return []; 54 | } 55 | 56 | interface RepoItemInfo { 57 | isFolder: boolean 58 | } -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_CREATE/restrict-overwrite/worker.ts: -------------------------------------------------------------------------------- 1 | import { PlatformContext } from 'jfrog-workers'; 2 | import { BeforeCreateRequest, BeforeCreateResponse, RepoPath, ActionStatus } from './types'; 3 | 4 | 5 | export default async function (context: PlatformContext, data: BeforeCreateRequest): Promise> { 6 | try { 7 | return restrictOverwrite(context, data); 8 | } catch (x) { 9 | return { status: ActionStatus.STOP, message: x.message }; 10 | } 11 | } 12 | 13 | async function restrictOverwrite(context: PlatformContext, data: BeforeCreateRequest): Promise> { 14 | const existingItem = await getExistingItemInfo(context, data.metadata.repoPath); 15 | 16 | if (existingItem && !(data.metadata.repoPath.isFolder && existingItem.isFolder)) { 17 | return { 18 | status: ActionStatus.STOP, 19 | message: `${data.metadata.repoPath.id} already exists`, 20 | }; 21 | } 22 | 23 | return { status: ActionStatus.PROCEED }; 24 | } 25 | 26 | async function getExistingItemInfo(context: PlatformContext, repoPath: RepoPath): Promise { 27 | const pathParts = repoPath.path.split('/') 28 | const query = { 29 | repo: repoPath.key, 30 | path: pathParts.length === 1 ? '.' : pathParts.slice(0, pathParts.length - 1).join('/'), 31 | name: pathParts[pathParts.length - 1] 32 | }; 33 | const aqlResult = await runAql(context, `items.find(${JSON.stringify(query)}).include("type").limit(1)`) 34 | if (aqlResult.length) { 35 | return { isFolder: aqlResult[0].type === 'folder' }; 36 | } 37 | return undefined; 38 | } 39 | 40 | async function runAql(context: PlatformContext, query: string) { 41 | console.log(`Running AQL: ${query}`) 42 | try { 43 | const queryResponse = await context.clients.platformHttp.post( 44 | '/artifactory/api/search/aql', 45 | query, 46 | { 47 | 'Content-Type': 'text/plain' 48 | }); 49 | return (queryResponse.data.results || []) as Array; 50 | } catch (x) { 51 | console.log(`AQL query failed: ${x.message}`); 52 | } 53 | return []; 54 | } 55 | 56 | interface RepoItemInfo { 57 | isFolder: boolean 58 | } -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_MOVE/restrict-overwrite/types.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface BeforeMoveRequest { 3 | /** Various immutable upload metadata */ 4 | metadata: UploadMetadata | undefined; 5 | /** The immutable target repository path */ 6 | targetRepoPath: RepoPath | undefined; 7 | /** The user context which sends the request */ 8 | userContext: UserContext | undefined; 9 | /** The moved artifacts properties */ 10 | properties: { [key: string]: ArtifactProperties }; 11 | } 12 | 13 | 14 | export interface UploadMetadata { 15 | /** The repoPath object of the request */ 16 | repoPath: RepoPath | undefined; 17 | /** The deploy request content length */ 18 | contentLength: number; 19 | /** Last modification time that occurred */ 20 | lastModified: number; 21 | /** Is the request trusting the server checksums */ 22 | trustServerChecksums: boolean; 23 | /** The url that points to artifactory */ 24 | servletContextUrl: string; 25 | /** Is it a request that skips jar indexing */ 26 | skipJarIndexing: boolean; 27 | /** Is redirect disabled on this request */ 28 | disableRedirect: boolean; 29 | /** Repository type */ 30 | repoType: RepoType; 31 | } 32 | 33 | export interface BeforeMoveResponse { 34 | /** Message to print to the log, in case of an error it will be printed as a warning */ 35 | message: string; 36 | /** The instruction of how to proceed */ 37 | status: ActionStatus; 38 | } 39 | 40 | export interface RepoPath { 41 | /** The repo key */ 42 | key: string; 43 | /** The path itself */ 44 | path: string; 45 | /** The key:path combination */ 46 | id: string; 47 | /** Is the path the root */ 48 | isRoot: boolean; 49 | /** Is the path a folder */ 50 | isFolder: boolean; 51 | } 52 | 53 | export interface UserContext { 54 | /** The username or subject */ 55 | id: string; 56 | /** Is the context an accessToken */ 57 | isToken: boolean; 58 | /** The realm of the user */ 59 | realm: string; 60 | } 61 | 62 | export interface ArtifactProperties { 63 | /** The property values */ 64 | value: string[]; 65 | } 66 | 67 | export enum RepoType { 68 | REPO_TYPE_UNSPECIFIED = 0, 69 | REPO_TYPE_LOCAL = 1, 70 | REPO_TYPE_REMOTE = 2, 71 | REPO_TYPE_FEDERATED = 3, 72 | UNRECOGNIZED = -1, 73 | } 74 | 75 | export enum ActionStatus { 76 | UNSPECIFIED = 0, 77 | PROCEED = 1, 78 | STOP = 2, 79 | WARN = 3, 80 | } 81 | -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_UPLOAD/restrict-overwrite/worker.ts: -------------------------------------------------------------------------------- 1 | import { PlatformContext } from 'jfrog-workers'; 2 | import { BeforeUploadRequest, BeforeUploadResponse, UploadStatus, RepoPath } from './types'; 3 | 4 | export default async function (context: PlatformContext, data: BeforeUploadRequest): Promise> { 5 | try { 6 | return restrictOverwrite(context, data); 7 | } catch (x) { 8 | return { status: UploadStatus.UPLOAD_STOP, message: x.message }; 9 | } 10 | } 11 | 12 | async function restrictOverwrite(context: PlatformContext, data: BeforeUploadRequest): Promise> { 13 | if (!data.metadata || !data.metadata.repoPath) { 14 | return { status: UploadStatus.UPLOAD_PROCEED }; 15 | } 16 | 17 | const existingItem = await getExistingItemInfo(context, data.metadata.repoPath); 18 | 19 | if (existingItem && !(data.metadata.repoPath.isFolder && existingItem.isFolder)) { 20 | return { 21 | status: UploadStatus.UPLOAD_STOP, 22 | message: `${data.metadata.repoPath.id} already exists`, 23 | }; 24 | } 25 | 26 | return { status: UploadStatus.UPLOAD_PROCEED }; 27 | } 28 | 29 | async function getExistingItemInfo(context: PlatformContext, repoPath: RepoPath): Promise { 30 | const pathParts = repoPath.path.split('/') 31 | const query = { 32 | repo: repoPath.key, 33 | path: pathParts.length === 1 ? '.' : pathParts.slice(0, pathParts.length - 1).join('/'), 34 | name: pathParts[pathParts.length - 1] 35 | }; 36 | const aqlResult = await runAql(context, `items.find(${JSON.stringify(query)}).include("type").limit(1)`) 37 | if (aqlResult.length) { 38 | return { isFolder: aqlResult[0].type === 'folder' }; 39 | } 40 | return undefined; 41 | } 42 | 43 | async function runAql(context: PlatformContext, query: string) { 44 | console.log(`Running AQL: ${query}`) 45 | try { 46 | const queryResponse = await context.clients.platformHttp.post( 47 | '/artifactory/api/search/aql', 48 | query, 49 | { 50 | 'Content-Type': 'text/plain' 51 | }); 52 | return (queryResponse.data.results || []) as Array; 53 | } catch (x) { 54 | console.log(`AQL query failed: ${x.message}`); 55 | } 56 | return []; 57 | } 58 | 59 | interface RepoItemInfo { 60 | isFolder: boolean 61 | } 62 | -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_DOWNLOAD/restrict-download-by-property-value/worker.ts: -------------------------------------------------------------------------------- 1 | import { PlatformContext } from 'jfrog-workers'; 2 | import { BeforeDownloadRequest, BeforeDownloadResponse, DownloadStatus } from './types'; 3 | 4 | export default async (context: PlatformContext, data: BeforeDownloadRequest): Promise => { 5 | let forbiddenProperties: ForbiddenProperty[] = []; 6 | forbiddenProperties.push({ 7 | key: "FORBIDDEN", 8 | values: ["true"] 9 | }); 10 | 11 | let status: DownloadStatus = DownloadStatus.DOWNLOAD_PROCEED; 12 | let message = 'Allowing Download'; 13 | 14 | try { 15 | const res = await context.clients.platformHttp.get(`/artifactory/api/storage/${data.repoPath.key}/${data.repoPath.path}?properties`); 16 | const artifactData: { 17 | "properties": { 18 | [key: string]: string[] 19 | }, 20 | "uri": string 21 | } = res.data; 22 | const properties = artifactData.properties; 23 | for(const forbiddenProperty of forbiddenProperties) { 24 | if(isPropertyPresent(forbiddenProperty, properties)) { 25 | status = DownloadStatus.DOWNLOAD_STOP; 26 | message = `Stopping Download because forbiddenProperty ${forbiddenProperty.key} is present with forbidden values`; 27 | break; 28 | } 29 | } 30 | } catch (error) { 31 | console.log(`Got error: ${JSON.stringify(error)}`); 32 | message = `Download proceed with a warning. Could not check if artifact is forbidden or not.`; 33 | status = DownloadStatus.DOWNLOAD_WARN; 34 | } 35 | 36 | return { 37 | status, 38 | message, 39 | headers: {} 40 | } 41 | } 42 | 43 | type ForbiddenProperty = { 44 | key: string, 45 | values: string[] 46 | }; 47 | 48 | type ArtifactProperties = { 49 | [key: string]: string[] 50 | } 51 | 52 | function isPropertyPresent(forbiddenProperty: ForbiddenProperty, artifactProperties: ArtifactProperties): boolean { 53 | const artifactPropertyKeys = new Set(Object.keys(artifactProperties)); 54 | if(artifactPropertyKeys.has(forbiddenProperty.key)) { 55 | const artifactPropertyValues = new Set(artifactProperties[forbiddenProperty.key]); 56 | for(const forbiddenValue of forbiddenProperty.values) { 57 | if(artifactPropertyValues.has(forbiddenValue)){ 58 | return true; 59 | } 60 | } 61 | } 62 | return false; 63 | } -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_UPLOAD/restrict-overwrite/README.md: -------------------------------------------------------------------------------- 1 | # Artifactory Restrict Overwrite (Before Upload) 2 | 3 | ## Overview 4 | 5 | This worker script ensures that overwrites are restricted during the upload of artifacts to configured repositories in JFrog Artifactory. The worker validates if an artifact already exists at the target location and stops the upload process if the file or folder is present. 6 | 7 | This is particularly useful to prevent accidental overwrites or modifications to artifacts that should remain immutable. 8 | 9 | ## Features 10 | 11 | - **Pre-upload Validation**: Ensures that no existing artifacts are overwritten during the upload process. 12 | - **Detailed Status Messaging**: 13 | - If the artifact already exists, the upload is stopped with a message indicating the existing file's name. 14 | - If no conflict exists, the upload proceeds as usual. 15 | - **Support for Folders**: Special handling ensures folders are not mistakenly overwritten. 16 | - **Extensible Design**: Built to integrate seamlessly with Artifactory's worker framework. 17 | 18 | ## Configuration 19 | 20 | No additional configuration is required. The script operates by intercepting the upload request and verifying the target location for any existing artifacts. 21 | 22 | ## Response Codes 23 | 24 | The worker returns the following status codes in its response: 25 | 26 | - **`status: 1`**: ndicates that no artifact exists at the target location. The upload is allowed to proceed:: 27 | ```json 28 | { 29 | "status": 1, 30 | } 31 | ``` 32 | 33 | - **`status: 2`**: Indicates that an artifact with the same name already exists. The upload is stopped, and a message is provided: 34 | ```json 35 | { 36 | "status": 2, 37 | "message": "example-repo:path/to/file already exists" 38 | } 39 | ``` 40 | 41 | ## Notes 42 | 43 | - **Immutability**: This script enforces immutability for existing artifacts, ensuring consistency across deployments. 44 | - **Folder Handling**: When the target is a folder, the script prevents overwrites only if the folder already exists and is not empty. 45 | - **Error Logging**: All operations, including failed attempts, are logged for debugging and auditing purposes. 46 | 47 | ## Limitations 48 | 49 | - This worker script is specific to the `before upload` trigger. Similar restrictions for other operations like `create`, `copy`, and `move` require separate implementations. 50 | - The script currently supports artifacts in flat repositories. For nested or deeply structured repositories, ensure paths are correctly configured. 51 | 52 | -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_PROPERTY_CREATE/property-creation-permission/README.md: -------------------------------------------------------------------------------- 1 | # Admin-Only Property Creation 2 | 3 | ## Overview 4 | This worker is triggered by the `BEFORE_PROPERTY_CREATE` event in JFrog Artifactory. Its primary purpose is to enforce a policy where only users with admin privileges can create artifact properties. 5 | 6 | 7 | ## Functionality 8 | 9 | - **Admin Check:** Ensures that only users with admin privileges can create artifact properties. 10 | - **Access Restriction:** Non-admin users attempting to create properties will have their request denied. 11 | - **Event Interception:** Intercepts the `BEFORE_PROPERTY_CREATE` event to implement this logic. 12 | 13 | ## Key Features 14 | 15 | 1. **Permission Validation:** 16 | - Checks the user context to determine if the user is an admin. 17 | - Grants or denies property creation based on user privileges. 18 | 19 | 2. **Custom Responses:** 20 | - Returns appropriate messages for both allowed and denied actions. 21 | - Prevents unauthorized users from modifying artifact properties. 22 | 23 | ## Worker Logic 24 | 25 | ### Admin Validation 26 | The worker determines if the user is an admin by checking their user ID. It assumes that admin user IDs end with `/users/admin`. 27 | 28 | ### Decision Making 29 | - **Admin Users:** 30 | - Permission is granted for property creation. 31 | - Responds with `BEFORE_PROPERTY_CREATE_PROCEED`. 32 | 33 | - **Non-Admin Users:** 34 | - Permission is denied for property creation. 35 | - Responds with `BEFORE_PROPERTY_CREATE_STOP`. 36 | 37 | ### Responses 38 | The worker sends back one of the following responses: 39 | 40 | #### Property Creation Allowed 41 | ```json 42 | { 43 | "message": "Permission granted to admin", 44 | "status": "BEFORE_PROPERTY_CREATE_PROCEED" 45 | } 46 | ``` 47 | 48 | #### Property Creation Not Allowed 49 | ``` json 50 | { 51 | "message": "Only admins are allowed to create properties", 52 | "status": "BEFORE_PROPERTY_CREATE_STOP" 53 | } 54 | ``` 55 | 56 | ## Recommendations 57 | 58 | 1. **User Context Validation:** Ensure that admin user IDs are configured to end with /users/admin. 59 | 2. **Auditing:** Periodically review logs for denied requests to ensure policy compliance. 60 | 3. **Testing:** Test the worker in a staging environment before deploying it in production. 61 | 62 | ## Error Scenarios 63 | 64 | - **Invalid User IDs:** If the user ID format does not conform to the expected convention, the worker will deny property creation. 65 | - **Unexpected Failures:** The worker logs any errors encountered during execution for further debugging. 66 | -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_CREATE/restrict-overwrite/types.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface BeforeCreateRequest { 3 | /** Various immutable upload metadata */ 4 | metadata: 5 | | UploadMetadata 6 | | undefined; 7 | itemInfo: ItemInfo | undefined; 8 | /** The user context which sends the request */ 9 | userContext: UserContext | undefined; 10 | } 11 | 12 | export interface UploadMetadata { 13 | /** The repoPath object of the request */ 14 | repoPath: 15 | | RepoPath 16 | | undefined; 17 | /** The deploy request content length */ 18 | contentLength: number; 19 | /** Last modification time that occurred */ 20 | lastModified: number; 21 | /** Is the request trusting the server checksums */ 22 | trustServerChecksums: boolean; 23 | /** The url that points to artifactory */ 24 | servletContextUrl: string; 25 | /** Is it a request that skips jar indexing */ 26 | skipJarIndexing: boolean; 27 | /** Is redirect disabled on this request */ 28 | disableRedirect: boolean; 29 | /** Repository type */ 30 | repoType: RepoType; 31 | } 32 | 33 | export interface BeforeCreateResponse { 34 | /** Message to print to the log, in case of an error it will be printed as a warning */ 35 | message: string; 36 | /** The instruction of how to proceed */ 37 | status: ActionStatus; 38 | } 39 | 40 | export interface RepoPath { 41 | /** The repo key */ 42 | key: string; 43 | /** The path itself */ 44 | path: string; 45 | /** The key:path combination */ 46 | id: string; 47 | /** Is the path the root */ 48 | isRoot: boolean; 49 | /** Is the path a folder */ 50 | isFolder: boolean; 51 | } 52 | 53 | export interface ItemInfo { 54 | /** The repoPath object of the request */ 55 | repoPath: RepoPath; 56 | /** Name of the Artifact **/ 57 | name: string; 58 | /** Time of creation of the artifact **/ 59 | created: number; 60 | /** Last modification time that occurred */ 61 | lastModified: number; 62 | } 63 | 64 | export interface UserContext { 65 | /** The username or subject */ 66 | id: string; 67 | /** Is the context an accessToken */ 68 | isToken: boolean; 69 | /** The realm of the user */ 70 | realm: string; 71 | } 72 | 73 | export enum RepoType { 74 | REPO_TYPE_UNSPECIFIED = 0, 75 | REPO_TYPE_LOCAL = 1, 76 | REPO_TYPE_REMOTE = 2, 77 | REPO_TYPE_FEDERATED = 3, 78 | UNRECOGNIZED = -1, 79 | } 80 | 81 | export enum ActionStatus { 82 | UNSPECIFIED = 0, 83 | PROCEED = 1, 84 | STOP = 2, 85 | WARN = 3, 86 | UNRECOGNIZED = -1, 87 | } -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_PROPERTY_CREATE/property-creation-permission/types.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface BeforePropertyCreateRequest { 3 | metadata: UploadMetadata | undefined; 4 | userContext: UserContext | undefined; 5 | itemInfo: ItemInfo | undefined; 6 | name: string; 7 | values: string[]; 8 | } 9 | 10 | export interface UploadMetadata { 11 | /** The repoPath object of the request */ 12 | repoPath: RepoPath | undefined; 13 | /** The deploy request content length */ 14 | contentLength: number; 15 | /** Last modification time that occurred */ 16 | lastModified: number; 17 | /** Is the request trusting the server checksums */ 18 | trustServerChecksums: boolean; 19 | /** The url that points to artifactory */ 20 | servletContextUrl: string; 21 | /** Is it a request that skips jar indexing */ 22 | skipJarIndexing: boolean; 23 | /** Is redirect disabled on this request */ 24 | disableRedirect: boolean; 25 | /** Repository type */ 26 | repoType: RepoType; 27 | } 28 | 29 | export interface RepoPath { 30 | /** The repo key */ 31 | key: string; 32 | /** The path itself */ 33 | path: string; 34 | /** The key:path combination */ 35 | id: string; 36 | /** Is the path the root */ 37 | isRoot: boolean; 38 | /** Is the path a folder */ 39 | isFolder: boolean; 40 | } 41 | 42 | export interface UserContext { 43 | /** The username or subject */ 44 | id: string; 45 | /** Is the context an accessToken */ 46 | isToken: boolean; 47 | /** The realm of the user */ 48 | realm: string; 49 | } 50 | 51 | export interface ItemInfo { 52 | /** The repoPath object of the request */ 53 | repoPath: RepoPath; 54 | /** Name of the Artifact **/ 55 | name: string; 56 | /** Time of creation of the artifact **/ 57 | created: number; 58 | /** Last modification time that occurred */ 59 | lastModified: number; 60 | } 61 | 62 | export interface BeforePropertyCreateResponse { 63 | /** The instruction of how to proceed */ 64 | status: BeforePropertyCreateStatus; 65 | /** Message to print to the log, in case of an error it will be printed as a warning */ 66 | message: string; 67 | } 68 | 69 | export enum BeforePropertyCreateStatus { 70 | BEFORE_PROPERTY_CREATE_UNSPECIFIED = 0, 71 | BEFORE_PROPERTY_CREATE_PROCEED = 1, 72 | BEFORE_PROPERTY_CREATE_STOP = 2, 73 | BEFORE_PROPERTY_CREATE_WARN = 3, 74 | UNRECOGNIZED = -1, 75 | } 76 | 77 | export enum RepoType { 78 | REPO_TYPE_UNSPECIFIED = 0, 79 | REPO_TYPE_LOCAL = 1, 80 | REPO_TYPE_REMOTE = 2, 81 | REPO_TYPE_FEDERATED = 3, 82 | UNRECOGNIZED = -1, 83 | } 84 | -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_COPY/restrict-overwrite/README.md: -------------------------------------------------------------------------------- 1 | # Artifactory Restrict Overwrite: Before Copy 2 | 3 | ## Overview 4 | The "Restrict Overwrite: Before Copy" worker script enforces immutability by preventing overwrites of artifacts during the "copy" operation in JFrog Artifactory. It ensures that artifacts cannot be accidentally replaced, maintaining consistency and integrity across deployments. 5 | 6 | ## Features 7 | - **Overwrite Protection**: Blocks attempts to copy artifacts if an artifact with the same name already exists in the target path. 8 | - **Folder Handling**: If the target is a folder, the script allows the operation only when the folder is empty. 9 | - **Error Logging**: Logs all operations, including failed attempts, for debugging and auditing purposes. 10 | 11 | ## Behavior 12 | - If the target artifact already exists: 13 | - The script stops the operation and returns a message indicating the conflict. 14 | - If the target artifact does not exist: 15 | - The operation proceeds without interruption. 16 | 17 | ## Example Responses 18 | ### File Does Not Exist 19 | ```json 20 | { 21 | "status": 1 22 | } 23 | ``` 24 | ### File Already Exists 25 | ```json 26 | { 27 | "status": 2, 28 | "message": "example-repo:file.txt already exists" 29 | } 30 | ``` 31 | ## Usage 32 | 33 | ### Execution Flow 34 | 1. **Trigger Point**: This worker script is triggered before the "copy" operation in Artifactory. 35 | 36 | 2. **AQL Query**: The script runs an AQL query to check if the target artifact exists in the specified repository path. 37 | 38 | 3. **Decision**: Based on the existence of the target artifact: 39 | - **Proceed**: If the artifact does not exist. 40 | - **Stop**: If the artifact already exists, with an appropriate error message. 41 | 42 | ### Implementation Notes 43 | - **Logging**: All operations and exceptions are logged for traceability. 44 | 45 | - **AQL Query**: The AQL query fetches information about the target artifact, including its type (file or folder). 46 | 47 | ## Limitations 48 | - **Specific to Copy**: This script applies only to the "before copy" trigger. Separate implementations are required for "before create," "before upload," and "before move" operations. 49 | 50 | - **Flat Repositories**: The script supports flat repository structures. For deeply nested repositories, ensure the paths are correctly configured. 51 | 52 | ## Notes 53 | - **Immutability Enforcement**: Ensures existing artifacts remain unchanged unless explicitly deleted beforehand. 54 | 55 | - **Folder Handling**: Prevents overwrites only if the folder already exists and is not empty. 56 | 57 | - **Error Logging**: Provides detailed logs for troubleshooting and auditing. -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_MOVE/restrict-overwrite/README.md: -------------------------------------------------------------------------------- 1 | # Artifactory Restrict Overwrite: Before Move 2 | 3 | ## Overview 4 | The "Restrict Overwrite: Before Move" worker script enforces immutability by preventing overwrites of artifacts during the "move" operation in JFrog Artifactory. It ensures that artifacts cannot be accidentally replaced, maintaining consistency and integrity across deployments. 5 | 6 | ## Features 7 | - **Overwrite Protection**: Blocks attempts to move artifacts if an artifact with the same name already exists in the target path. 8 | - **Folder Handling**: If the target is a folder, the script allows the operation only when the folder is empty. 9 | - **Error Logging**: Logs all operations, including failed attempts, for debugging and auditing purposes. 10 | 11 | ## Behavior 12 | - If the target artifact already exists: 13 | - The script stops the operation and returns a message indicating the conflict. 14 | - If the target artifact does not exist: 15 | - The operation proceeds without interruption. 16 | 17 | ## Example Responses 18 | ### File Does Not Exist 19 | ```json 20 | { 21 | "status": 1 22 | } 23 | ``` 24 | 25 | ### File Already Exists 26 | ```json 27 | { 28 | "status": 2, 29 | "message": "example-repo:file.txt already exists" 30 | } 31 | ``` 32 | ## Usage 33 | 34 | ### Execution Flow 35 | 1. **Trigger Point**: This worker script is triggered before the "move" operation in Artifactory. 36 | 37 | 2. **AQL Query**: The script runs an AQL query to check if the target artifact exists in the specified repository path. 38 | 39 | 3. **Decision**: Based on the existence of the target artifact: 40 | - **Proceed**: If the artifact does not exist. 41 | - **Stop**: If the artifact already exists, with an appropriate error message. 42 | 43 | ### Implementation Notes 44 | - **Logging**: All operations and exceptions are logged for traceability. 45 | 46 | - **AQL Query**: The AQL query fetches information about the target artifact, including its type (file or folder). 47 | 48 | ## Limitations 49 | - **Specific to Move**: This script applies only to the "before move" trigger. Separate implementations are required for "before create," "before upload," and "before copy" operations. 50 | 51 | - **Flat Repositories**: The script supports flat repository structures. For deeply nested repositories, ensure the paths are correctly configured. 52 | 53 | ## Notes 54 | - **Immutability Enforcement**: Ensures existing artifacts remain unchanged unless explicitly deleted beforehand. 55 | 56 | - **Folder Handling**: Prevents overwrites only if the folder already exists and is not empty. 57 | 58 | - **Error Logging**: Provides detailed logs for troubleshooting and auditing. -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_CREATE/restrict-overwrite/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Artifactory Restrict Overwrite: Before Create 3 | 4 | ## Overview 5 | 6 | The "Restrict Overwrite: Before Create" worker script enforces immutability by preventing overwrites of artifacts during the "create" operation in JFrog Artifactory. It ensures that artifacts cannot be accidentally replaced, maintaining consistency and integrity across deployments. 7 | 8 | ## Features 9 | 10 | - **Overwrite Protection**: Blocks attempts to create artifacts if an artifact with the same name already exists in the target path. 11 | - **Folder Handling**: If the target is a folder, the script allows the operation only when the folder is empty. 12 | - **Error Logging**: Logs all operations, including failed attempts, for debugging and auditing purposes. 13 | 14 | ## Behavior 15 | 16 | - If the target artifact already exists: 17 | - The script stops the operation and returns a message indicating the conflict. 18 | - If the target artifact does not exist: 19 | - The operation proceeds without interruption. 20 | 21 | ## Example Responses 22 | 23 | ### File Does Not Exist 24 | ```json 25 | { 26 | "status": 1 27 | } 28 | ``` 29 | 30 | ### File Already Exists 31 | ```json 32 | { 33 | "status": 2, 34 | "message": "example-repo:file.txt already exists" 35 | } 36 | ``` 37 | 38 | ## Usage 39 | 40 | ### Execution Flow 41 | 1. **Trigger Point**: This worker script is triggered before the "create" operation in Artifactory. 42 | 2. **AQL Query**: The script runs an AQL query to check if the target artifact exists in the specified repository path. 43 | 3. **Decision**: Based on the existence of the target artifact: 44 | - **Proceed**: If the artifact does not exist. 45 | - **Stop**: If the artifact already exists, with an appropriate error message. 46 | 47 | ### Implementation Notes 48 | - **Logging**: All operations and exceptions are logged for traceability. 49 | - **AQL Query**: The AQL query fetches information about the target artifact, including its type (file or folder). 50 | 51 | ## Limitations 52 | 53 | - **Specific to Create**: This script applies only to the "before create" trigger. Separate implementations are required for "before upload," "before copy," and "before move" operations. 54 | - **Flat Repositories**: The script supports flat repository structures. For deeply nested repositories, ensure the paths are correctly configured. 55 | 56 | ## Notes 57 | 58 | - **Immutability Enforcement**: Ensures existing artifacts remain unchanged unless explicitly deleted beforehand. 59 | - **Folder Handling**: Prevents overwrites only if the folder already exists and is not empty. 60 | - **Error Logging**: Provides detailed logs for troubleshooting and auditing. 61 | -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_UPLOAD/allow-upload-by-pattern/README.md: -------------------------------------------------------------------------------- 1 | # Allow Upload by Pattern 2 | 3 | ## Overview 4 | 5 | This worker script ensures that artifact uploads are allowed only if the target path matches a predefined pattern. This pattern is specified using a regular expression (`authorizedPathRegEx`). The script validates the repository path (`repoPath`) and blocks uploads that do not comply with the pattern. 6 | 7 | ## Functionality 8 | 9 | - **Path Matching**: The worker validates repository paths against the regular expression: 10 | ``` 11 | /^org\/company\/(?:\w+.\/)+[\w\-\.]+\.(?:jar|war)$/ 12 | ``` 13 | This regex allows paths that: 14 | - Start with `org/company/` 15 | - Contain subdirectories separated by `/` 16 | - End with `.jar` or `.war` 17 | 18 | - **Allowed Paths**: Examples of acceptable paths include: 19 | - `org/company/src/app.jar` 20 | - `org/company/package1/subPackage/webapp.war` 21 | 22 | - **Blocked Paths**: Paths that do not match the pattern, such as `text.txt`, are disallowed. 23 | 24 | ## Response Examples 25 | 26 | ### When Path Matches the Pattern 27 | ```json 28 | { 29 | "status": 1, 30 | "message": "RepoPath 'org/company/src/app.jar' is acceptable for the repository 'FIXTURE'", 31 | "modifiedRepoPath": { 32 | "key": "FIXTURE", 33 | "path": "org/company/src/helloworld.jar", 34 | "id": "FIXTURE:org/company/src/helloworld.jar" 35 | } 36 | } 37 | ``` 38 | 39 | ### When Path Does Not Match the Pattern 40 | ```json 41 | { 42 | "status": 2, 43 | "message": "RepoPath 'file.txt' does not match the regex /^org\\/company\\/(?:\\w+.\\/)+[\\w\\-\\.]+\\.(?:jar|war)$/ for the repository 'FIXTURE'", 44 | "modifiedRepoPath": { 45 | "key": "FIXTURE", 46 | "path": "text.txt", 47 | "id": "FIXTURE:text.txt" 48 | } 49 | } 50 | ``` 51 | 52 | ## Error Handling 53 | 54 | If an error occurs during the path validation process, the script logs the error and allows the upload with a warning: 55 | 56 | ```json 57 | { 58 | "status": 3, 59 | "message": "An error occurred during the check. Proceed with warning.", 60 | "modifiedRepoPath": { 61 | "key": "FIXTURE", 62 | "path": "example/path.txt", 63 | "id": "FIXTURE:example/path.txt" 64 | } 65 | } 66 | ``` 67 | 68 | ## Notes 69 | 70 | 1. **Custom Patterns**: Modify the `authorizedPathRegEx` variable to enforce different patterns based on project requirements. 71 | 2. **Security**: This script helps maintain a controlled upload environment by enforcing consistent repository paths. 72 | 3. **Error Logging**: All failed attempts are logged for auditing and debugging purposes. 73 | 4. **Extensions**: Although the current pattern focuses on `.jar` and `.war` files, it can be extended to support additional file types. 74 | -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/artifact-cleanup/README.md: -------------------------------------------------------------------------------- 1 | Artifact Cleanup Worker 2 | ======================== 3 | 4 | This worker code deletes all artifacts that have not been downloaded for the past *n time units*, 5 | which is by default 1 month. It can be run manually from the REST API, using a worker generic event. 6 | 7 | **Note:** 8 | 9 | If you're trying to clean Docker images, this plugin may lead to unexpectedly partial or broken cleans. It is recommended to instead use the [cleanDockerImages](https://github.com/jfrog/workers-sample/tree/main/samples/artifactory/GENERIC_EVENT/clean-docker-images) plugin for this purpose. 10 | 11 | Expected JSON Payload 12 | ---------- 13 | 14 | - `timeUnit`: The unit of the time interval. *year*, *month*, *day*, *hour* or *minute* are allowed values. Default *month*. 15 | - `timeInterval`: The time interval to look back before deleting an artifact. Default *1*. 16 | - `repos`: A list of repositories to clean. This parameter is required. 17 | - `dryRun`: If this parameter is passed, artifacts will not actually be deleted. Default *false*. 18 | - `disablePropertiesSupport`: Disable the support of Artifactory Properties (see below *Artifactory Properties support* section). Default *false*. 19 | - `limit`: The maximum number of artifacts to delete during the cleanup. Default *100*. 20 | - `concurrency`: The number of artifacts to delete in parallel (can impact performances). Default *10*. 21 | 22 | An example file could contain the following json: 23 | 24 | ```json 25 | { 26 | "repos": [ 27 | "libs-release-local" 28 | ], 29 | "timeUnit": "day", 30 | "timeInterval": 3, 31 | "dryRun": true, 32 | "disablePropertiesSupport": true, 33 | "limit": 100, 34 | "concurrency": 10 35 | } 36 | ``` 37 | 38 | Artifactory Properties support 39 | ---------- 40 | 41 | Some Artifactory [Properties](https://www.jfrog.com/confluence/display/RTF/Properties) are supported if defined on *artifacts* or *folders*: 42 | 43 | - `cleanup.skip`: Skip the artifact deletion if property defined on artifact's path ; artifact itself or in a parent folder(s). 44 | 45 | Executing 46 | --------- 47 | 48 | To execute the code as a worker generic event: 49 | 50 | - Create a new generic worker with the given code (eg: my-worker) 51 | - Example of execution using JFrog CLI 52 | 53 | ```bash 54 | jf worker exec my-worker - < days." 44 | } 45 | ``` 46 | - **Explanation:** Indicates the artifact's last scan is outdated, and the download is blocked. 47 | 48 | ### Warning Response 49 | ```json 50 | { 51 | "status": "DOWNLOAD_WARN", 52 | "message": "Could not check for Xray scans because Xray is not available. Proceeding download with warning." 53 | } 54 | ``` 55 | - **Explanation:** Indicates that the worker encountered an error (e.g., Xray unavailability), but the download is allowed with a warning. 56 | 57 | ## Error Handling 58 | 59 | - **Xray Unavailability:** Issues a warning and allows download if Xray is not operational. 60 | - **Scan Check Failure:** Logs the error and proceeds with a warning. 61 | 62 | ## Recommendations 63 | 64 | 1. **Threshold Adjustment:** 65 | - Update the threshold days or date as per organizational requirements. 66 | 67 | 2. **Monitoring and Logging:** 68 | - Regularly review logs for `DOWNLOAD_WARN` responses to address recurring issues. 69 | 70 | 3. **Testing:** 71 | - Validate the worker functionality in a staging environment before deploying to production. -------------------------------------------------------------------------------- /samples/artifactory/README.md: -------------------------------------------------------------------------------- 1 | # Artifactory Event-Driven Workers 2 | 3 | Artifactory workers allow you to automate tasks, enforce policies, and integrate with external systems based on specific events such as uploading, downloading, or managing artifacts and properties. 4 | 5 | ### Available Workers 6 | 7 | This repository includes sample workers for the following Artifactory events: 8 | 9 | - **Before Download** 10 | - **Triggers**: Before an artifact is downloaded. 11 | - **Use Cases**: Enforce download policies, log access. 12 | 13 | - **After Download** 14 | - **Triggers**: After an artifact is downloaded. 15 | - **Use Cases**: Audit and log download activities. 16 | 17 | - **Before Upload** 18 | - **Triggers**: Before an artifact is uploaded. 19 | - **Use Cases**: Validate artifacts, enforce naming conventions. 20 | - **After Create** 21 | - **Triggers**: After an artifact or folder is created. 22 | - **Use Cases**: Automate post-creation tasks like indexing. 23 | 24 | - **Before Property Create** 25 | - **Triggers**: Before a property is added to an artifact. 26 | - **Use Cases**: Enforce property-related policies. 27 | 28 | 29 | ### Generic Events 30 | 31 | In addition to Artifactory-specific events, workers can respond to **Generic Events**. These are custom-defined events that don't directly map to Artifactory actions, offering flexibility to trigger and manage custom workflows or integrations within your JFrog Platform. 32 | 33 | #### Use Cases for Generic Events: 34 | - **Custom Automation**: Integrate with third-party systems or automate unique workflows. 35 | - **Observability**: Log or monitor specific behaviors in your application. 36 | - **Multi-Step Processes**: Orchestrate tasks across Artifactory or other parts of the JFrog Platform. 37 | 38 | Each worker includes TypeScript code and detailed instructions for deployment and configuration. 39 | 40 | --- 41 | 42 | ## Getting Started 43 | 44 | To use or customize the sample workers, follow these steps: 45 | 46 | ### 1. Clone the Repository 47 | ``` 48 | git clone https://github.com/jfrog/workers-sample.git 49 | ``` 50 | 51 | ### 2. Navigate to the Artifactory Samples Directory 52 | ``` 53 | cd workers-sample/samples/artifactory 54 | ``` 55 | 56 | ### 3. Install Dependencies 57 | Go to the specific worker directory and install required dependencies: 58 | ``` 59 | cd specific-worker-directory 60 | npm install 61 | ``` 62 | 63 | ### 4. Modify the Worker Logic 64 | > **Note** 65 | > 66 | > **Use Localized Types**: If creating new types, define them in the same file to avoid dependency issues. 67 | 68 | Update the worker code as needed. 69 | 70 | 71 | ### 5. Test the Worker 72 | Trigger the relevant event (for example, upload an artifact for a **Before Upload** worker) to ensure it works correctly. 73 | 74 | ### 6. Deploy the Worker 75 | > **Note** 76 | > 77 | > **Remove All Imports**: Before deployment, remove all imports from the script to prevent compilation errors. 78 | 79 | Deploy the worker using the JFrog CLI or the Workers UI. 80 | 81 | 82 | ### 7. Validate the Deployment 83 | - Ensure the worker is associated with the correct event. 84 | - Test in a staging environment before deploying to production. 85 | 86 | --- -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/oldBuildCleanup/worker.ts: -------------------------------------------------------------------------------- 1 | import { PlatformContext } from 'jfrog-workers'; 2 | 3 | class CancelException extends Error { 4 | constructor(message: string) { 5 | super(message); 6 | } 7 | } 8 | 9 | export default async function oldBuildCleanup(context: PlatformContext, data: BuildCleanupPayload) { 10 | try { 11 | checkInput(data); 12 | data.cleanArtifacts = data.cleanArtifacts ?? false; 13 | const { buildName, buildNumber, cleanArtifacts } = data; 14 | console.info(`Starting build cleanup for ${buildName} up to build number ${buildNumber}, cleanArtifacts: ${cleanArtifacts}`); 15 | 16 | const builds = await getBuilds(context, buildName); 17 | let buildList: BuildInfo[] = []; 18 | let deleteCount = 0; 19 | 20 | for (const build of builds) { 21 | if (parseInt(build.number, 10) <= buildNumber) { 22 | console.warn(`Deleting build: ${buildName}#${build.number}, including artifacts: ${cleanArtifacts}`); 23 | try { 24 | await deleteBuild(context, buildName, build.number, cleanArtifacts); 25 | deleteCount++; 26 | } catch (error) { 27 | console.error(`Failed to delete build: ${buildName}#${build.number}`, error); 28 | } 29 | } 30 | } 31 | return { status: 'STATUS_SUCCESS', message: `${deleteCount} build(s) deleted` }; 32 | 33 | } catch (x) { 34 | console.log(x.message); 35 | return { status: 'STATUS_FAILURE', message: x.message }; 36 | } 37 | } 38 | 39 | function checkInput(data: BuildCleanupPayload) { 40 | if (!data.buildName) { 41 | throw new CancelException('buildName is a mandatory parameter.'); 42 | } 43 | if (isNaN(data.buildNumber)) { 44 | throw new CancelException('buildNumber must be an integer.'); 45 | } 46 | } 47 | 48 | async function getBuilds(context: PlatformContext, buildName: string): Promise { 49 | console.info(`Fetching builds for ${buildName}`); 50 | try { 51 | const response = await context.clients.platformHttp.get(`/artifactory/api/build/${buildName}`); 52 | return response.data.builds || []; 53 | } catch (error) { 54 | console.error(`Failed to fetch builds: ${error.message}`); 55 | return []; 56 | } 57 | } 58 | 59 | async function deleteBuild(context: PlatformContext, buildName: string, buildNumber: string, cleanArtifacts: boolean) { 60 | console.info(`Deleting build ${buildName}#${buildNumber}, cleanArtifacts: ${cleanArtifacts}`); 61 | try { 62 | const url = `/artifactory/api/build/${buildName}/${buildNumber}?artifacts=${cleanArtifacts}`; 63 | await context.clients.platformHttp.delete(url); 64 | console.info(`Successfully deleted build ${buildName}#${buildNumber}`); 65 | } catch (error) { 66 | console.error(`Failed to delete build ${buildName}#${buildNumber}: ${error.message}`); 67 | } 68 | } 69 | 70 | interface BuildCleanupPayload { 71 | buildName: string; 72 | buildNumber: number; 73 | cleanArtifacts: boolean; 74 | } 75 | 76 | interface BuildInfo { 77 | uri: string; 78 | number: string; 79 | } -------------------------------------------------------------------------------- /samples/artifactory/AFTER_DOWNLOAD/report-download-to-external-endpoint/README.md: -------------------------------------------------------------------------------- 1 | # Log Download activity to an external endpoint 2 | 3 | ## Overview 4 | This worker listens to the `AFTER_DOWNLOAD` event in JFrog Artifactory and notifies an external endpoint when a file is downloaded. The worker logs the artifact's details, the repository, and the user responsible for the download. 5 | 6 | ## Functionality 7 | 8 | 1. **Trigger:** Activated after a download event occurs in Artifactory. 9 | 2. **Notification:** Sends a log containing details about the downloaded artifact to a specified external endpoint. 10 | 3. **Authentication:** Uses a bearer token stored in a secret to authenticate with the external endpoint. 11 | 4. **Error Handling:** Logs and updates the response message if the notification fails. 12 | 13 | ## Configuration 14 | 15 | To customize the worker, you need to update two constants in the source code: 16 | 17 | - `URL`: The endpoint where the download log will be sent. 18 | Example: 19 | ```typescript 20 | const URL = 'https://'; 21 | ``` 22 | 23 | - `SECRET_NAME`: The name of the secret that contains the bearer token for authentication. 24 | Example: 25 | ```typescript 26 | const SECRET_NAME = 'myBearerToken'; 27 | ``` 28 | 29 | ## How It Works 30 | 31 | 1. **Download Event:** The worker is triggered when an artifact is downloaded. 32 | 2. **Log Creation:** The worker creates a log message with the following details: 33 | - Artifact path 34 | - Repository key 35 | - User ID or token used for the download 36 | 3. **Notification:** Sends the log message as a POST request to the external endpoint. 37 | 4. **Response Handling:** 38 | - Logs success if the notification is successfully sent. 39 | - Logs a warning or error if the notification fails and updates the response message accordingly. 40 | 41 | ## Example Log Message 42 | 43 | ```plaintext 44 | The artifact '' has been downloaded by the : from the repository ''. 45 | ``` 46 | 47 | ## Response Messages 48 | 49 | - **Successful Notification:** 50 | - Message: `Download activity successfully logged` 51 | 52 | - **Failed Notification:** 53 | - Message: `Failed to log download activity` 54 | 55 | ## Dependencies 56 | 57 | This worker relies on the following JFrog Workers APIs: 58 | - `PlatformContext` for accessing platform resources (e.g., secrets, HTTP clients). 59 | - `AfterDownloadRequest` and `AfterDownloadResponse` for handling download event payloads and responses. 60 | 61 | ## Recommendations 62 | 63 | 1. **Endpoint Configuration:** Ensure the external endpoint is correctly set and accessible. 64 | 2. **Bearer Token Secret:** Store the bearer token securely as a secret in the JFrog platform. 65 | 3. **Monitoring:** Regularly monitor logs for failed notifications to identify issues. 66 | 4. **Testing:** Test the worker in a staging environment before deploying it in production. 67 | 68 | ## Error Scenarios 69 | 70 | - **Network Errors:** Occur if the external endpoint is unreachable. 71 | - **Authentication Failures:** Occur if the bearer token is invalid or missing. 72 | - **Invalid URL:** Occurs if the provided endpoint URL is incorrect. 73 | -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/delete-empty-dirs/README.md: -------------------------------------------------------------------------------- 1 | Artifactory Delete Empty Dirs 2 | ========================================= 3 | ## Overview 4 | This worker deletes all empty directories found inside any of the given set of paths in your Artifactory repositories. The worker traverses specified directories recursively, checking for empty directories, and deletes them if they are found. 5 | 6 | ## Parameters 7 | 8 | This worker takes one parameter: 9 | 10 | - **`paths`**: A comma-separated list of paths to search for empty directories. Each path is in the form `repository-name/path/to/dir`. The worker will look through these directories for any empty folders and remove them. 11 | 12 | Example: 13 | - `example-docker-local/dir1` 14 | - `example-docker-local/dir2` 15 | 16 | ## Execution 17 | 18 | ### Using Inline Payload 19 | 20 | To execute the worker with an inline payload, use the following command: 21 | 22 | ```bash 23 | jf worker exec my-worker - <: ` 55 | 56 | 5. **Exclude Patterns**: 57 | Add the following exclude patterns to the worker to ensure unnecessary files are ignored: repodata/** tmp/** dists/** 58 | 59 | ## Example Log Output 60 | 61 | ``` 62 | INFO: Copying package: rpm/rpm-4.20.0-6-omv2490.aarch64.rpm to rpm-dest/rpm/rpm-4.20.0-6-SNAPSHOT.aarch64.rpm 63 | INFO: Copy success 64 | WARN: Unable to rename Timestamp to Snapshot for debian/dummy-package.deb 65 | ERROR: Unable to copy artifact to debian-dest: File not found 66 | ``` 67 | 68 | ## Notes 69 | - Ensure that source repositories are added to the worker's filters. 70 | - Only files matching the expected layout regex will be processed. 71 | - Don't forget to add appropriate permissions for accessing and modifying repositories. 72 | -------------------------------------------------------------------------------- /samples/artifactory/GENERIC_EVENT/delete-deprecated/README.md: -------------------------------------------------------------------------------- 1 | # delete-deprecated Worker 2 | 3 | ## Overview 4 | The `delete-deprecated` worker is designed to delete artifacts from JFrog Artifactory repositories based on the property `analysis.deprecated=true`. This worker ensures that deprecated artifacts can be efficiently removed, helping to maintain clean and relevant repositories. 5 | 6 | ## Features 7 | - Deletes artifacts with the property `analysis.deprecated=true`. 8 | - Supports specifying multiple repositories for the deletion operation. 9 | - Includes a dry-run mode to simulate deletions without removing any artifacts. 10 | 11 | ## How It Works 12 | The worker performs the following steps: 13 | 1. Searches for artifacts in the specified repositories with the property `analysis.deprecated=true`. 14 | 2. Deletes matching artifacts if the `dryRun` parameter is set to `false`. 15 | 3. Logs the results, including the number of artifacts deleted or found in dry-run mode. 16 | 17 | ## Parameters 18 | The worker accepts the following parameters: 19 | 20 | | Parameter | Type | Description | 21 | |------------|---------------|-------------------------------------------------------------------------------------------------| 22 | | `repos` | `Array` | A list of repository names where the worker will search for deprecated artifacts. | 23 | | `dryRun` | `boolean` | When `true`, no artifacts are deleted. The worker only logs the artifacts that would be deleted. | 24 | 25 | ## Example Usage 26 | 27 | ### Input Parameters 28 | ```json 29 | { 30 | "repos": ["example-repo-local", "another-repo"], 31 | "dryRun": true 32 | } 33 | ``` 34 | 35 | ### Output 36 | - **Dry Run Mode**: 37 | ``` 38 | Found: example-repo-local/path/to/artifact.jar 39 | Dry run: Would delete example-repo-local/path/to/artifact.jar 40 | No files were actually deleted. 41 | ``` 42 | - **Deletion Mode**: 43 | ``` 44 | Found: example-repo-local/path/to/artifact.jar 45 | Deleting: example-repo-local/path/to/artifact.jar 46 | Deleted: example-repo-local/path/to/artifact.jar 47 | Total deleted: 1 48 | ``` 49 | - **Execution link**: 50 | ``` 51 | curl -u your-username:your-api-key \ 52 | -X POST \ 53 | "${baseUrl}/worker/api/v1/execute/delete-deprecated-worker" \ 54 | -H "Content-Type: application/json" \ 55 | -d '{ 56 | "repos": ["example-repo-local", "another-repo"], 57 | "dryRun": true #optional and by default true 58 | }' 59 | ``` 60 | ## How to Deploy 61 | 1. Build and deploy the worker using your preferred CI/CD pipeline or manual process. 62 | 2. Ensure the worker has the appropriate permissions to access and delete artifacts in the specified repositories. 63 | 3. Configure the worker to run with the required parameters via your orchestration platform. 64 | 65 | ## Best Practices 66 | - **Dry Run First**: Always run the worker with `dryRun=true` initially to verify the artifacts that will be deleted. 67 | - **Monitor Logs**: Review logs to ensure the worker is correctly identifying and deleting artifacts. 68 | - **Use with Care**: This worker will permanently delete artifacts. Ensure backups are in place if necessary. 69 | 70 | ## Limitations 71 | - The worker only processes artifacts with the exact property `analysis.deprecated=true`. 72 | - Ensure the property is correctly set on the target artifacts before running the worker. -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_DOWNLOAD/restrict-download-by-property-value/types.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface BeforeDownloadRequest { 3 | /** Various immutable download metadata */ 4 | metadata: 5 | | DownloadMetadata 6 | | undefined; 7 | /** The immutable request headers */ 8 | headers: { [key: string]: Header }; 9 | /** The user context which sends the request */ 10 | userContext: 11 | | UserContext 12 | | undefined; 13 | /** The response repoPath */ 14 | repoPath: RepoPath | undefined; 15 | } 16 | 17 | export interface DownloadMetadata { 18 | /** The repoPath object of the request */ 19 | repoPath: 20 | | RepoPath 21 | | undefined; 22 | /** The original repo path in case a virtual repo is involved */ 23 | originalRepoPath: 24 | | RepoPath 25 | | undefined; 26 | /** The file name from path */ 27 | name: string; 28 | /** Is it a head request */ 29 | headOnly: boolean; 30 | /** Is it a checksum request */ 31 | checksum: boolean; 32 | /** Is it a recursive request */ 33 | recursive: boolean; 34 | /** When a modification has occurred */ 35 | modificationTime: number; 36 | /** Is it a directory request */ 37 | directoryRequest: boolean; 38 | /** Is it a metadata request */ 39 | metadata: boolean; 40 | /** Last modification time that occurred */ 41 | lastModified: number; 42 | /** If a modification happened since the last modification time */ 43 | ifModifiedSince: number; 44 | /** The url that points to artifactory */ 45 | servletContextUrl: string; 46 | /** The request URI */ 47 | uri: string; 48 | /** The client address */ 49 | clientAddress: string; 50 | /** The resource path of the requested zip */ 51 | zipResourcePath: string; 52 | /** Is the request a zip resource request */ 53 | zipResourceRequest: boolean; 54 | /** should replace the head request with get */ 55 | replaceHeadRequestWithGet: boolean; 56 | /** Repository type */ 57 | repoType: RepoType; 58 | } 59 | 60 | export interface RepoPath { 61 | /** The repo key */ 62 | key: string; 63 | /** The path itself */ 64 | path: string; 65 | /** The key:path combination */ 66 | id: string; 67 | /** Is the path the root */ 68 | isRoot: boolean; 69 | /** Is the path a folder */ 70 | isFolder: boolean; 71 | } 72 | 73 | export interface Header { 74 | value: string[]; 75 | } 76 | 77 | export interface UserContext { 78 | /** The username or subject */ 79 | id: string; 80 | /** Is the context an accessToken */ 81 | isToken: boolean; 82 | /** The realm of the user */ 83 | realm: string; 84 | } 85 | 86 | export enum RepoType { 87 | REPO_TYPE_UNSPECIFIED = 0, 88 | REPO_TYPE_LOCAL = 1, 89 | REPO_TYPE_REMOTE = 2, 90 | REPO_TYPE_FEDERATED = 3, 91 | UNRECOGNIZED = -1, 92 | } 93 | 94 | export interface BeforeDownloadResponse { 95 | /** The instruction of how to proceed */ 96 | status: DownloadStatus; 97 | /** Message to print to the log, in case of an error it will be printed as a warning */ 98 | message: string; 99 | } 100 | 101 | export enum DownloadStatus { 102 | DOWNLOAD_UNSPECIFIED = 0, 103 | DOWNLOAD_PROCEED = 1, 104 | DOWNLOAD_STOP = 2, 105 | DOWNLOAD_WARN = 3, 106 | UNRECOGNIZED = -1, 107 | } 108 | -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_DOWNLOAD/resctrict-download-if-no-xray-scan-since/types.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface BeforeDownloadRequest { 3 | /** Various immutable download metadata */ 4 | metadata: 5 | | DownloadMetadata 6 | | undefined; 7 | /** The immutable request headers */ 8 | headers: { [key: string]: Header }; 9 | /** The user context which sends the request */ 10 | userContext: 11 | | UserContext 12 | | undefined; 13 | /** The response repoPath */ 14 | repoPath: RepoPath | undefined; 15 | } 16 | 17 | export interface DownloadMetadata { 18 | /** The repoPath object of the request */ 19 | repoPath: 20 | | RepoPath 21 | | undefined; 22 | /** The original repo path in case a virtual repo is involved */ 23 | originalRepoPath: 24 | | RepoPath 25 | | undefined; 26 | /** The file name from path */ 27 | name: string; 28 | /** Is it a head request */ 29 | headOnly: boolean; 30 | /** Is it a checksum request */ 31 | checksum: boolean; 32 | /** Is it a recursive request */ 33 | recursive: boolean; 34 | /** When a modification has occurred */ 35 | modificationTime: number; 36 | /** Is it a directory request */ 37 | directoryRequest: boolean; 38 | /** Is it a metadata request */ 39 | metadata: boolean; 40 | /** Last modification time that occurred */ 41 | lastModified: number; 42 | /** If a modification happened since the last modification time */ 43 | ifModifiedSince: number; 44 | /** The url that points to artifactory */ 45 | servletContextUrl: string; 46 | /** The request URI */ 47 | uri: string; 48 | /** The client address */ 49 | clientAddress: string; 50 | /** The resource path of the requested zip */ 51 | zipResourcePath: string; 52 | /** Is the request a zip resource request */ 53 | zipResourceRequest: boolean; 54 | /** should replace the head request with get */ 55 | replaceHeadRequestWithGet: boolean; 56 | /** Repository type */ 57 | repoType: RepoType; 58 | } 59 | 60 | export interface RepoPath { 61 | /** The repo key */ 62 | key: string; 63 | /** The path itself */ 64 | path: string; 65 | /** The key:path combination */ 66 | id: string; 67 | /** Is the path the root */ 68 | isRoot: boolean; 69 | /** Is the path a folder */ 70 | isFolder: boolean; 71 | } 72 | 73 | export interface Header { 74 | value: string[]; 75 | } 76 | 77 | export interface UserContext { 78 | /** The username or subject */ 79 | id: string; 80 | /** Is the context an accessToken */ 81 | isToken: boolean; 82 | /** The realm of the user */ 83 | realm: string; 84 | } 85 | 86 | export enum RepoType { 87 | REPO_TYPE_UNSPECIFIED = 0, 88 | REPO_TYPE_LOCAL = 1, 89 | REPO_TYPE_REMOTE = 2, 90 | REPO_TYPE_FEDERATED = 3, 91 | UNRECOGNIZED = -1, 92 | } 93 | 94 | export interface BeforeDownloadResponse { 95 | /** The instruction of how to proceed */ 96 | status: DownloadStatus; 97 | /** Message to print to the log, in case of an error it will be printed as a warning */ 98 | message: string; 99 | } 100 | 101 | export enum DownloadStatus { 102 | DOWNLOAD_UNSPECIFIED = 0, 103 | DOWNLOAD_PROCEED = 1, 104 | DOWNLOAD_STOP = 2, 105 | DOWNLOAD_WARN = 3, 106 | UNRECOGNIZED = -1, 107 | } 108 | -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_DOWNLOAD/restrict-download-on-xray-critical-issues/types.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface BeforeDownloadRequest { 3 | /** Various immutable download metadata */ 4 | metadata: 5 | | DownloadMetadata 6 | | undefined; 7 | /** The immutable request headers */ 8 | headers: { [key: string]: Header }; 9 | /** The user context which sends the request */ 10 | userContext: 11 | | UserContext 12 | | undefined; 13 | /** The response repoPath */ 14 | repoPath: RepoPath | undefined; 15 | } 16 | 17 | export interface DownloadMetadata { 18 | /** The repoPath object of the request */ 19 | repoPath: 20 | | RepoPath 21 | | undefined; 22 | /** The original repo path in case a virtual repo is involved */ 23 | originalRepoPath: 24 | | RepoPath 25 | | undefined; 26 | /** The file name from path */ 27 | name: string; 28 | /** Is it a head request */ 29 | headOnly: boolean; 30 | /** Is it a checksum request */ 31 | checksum: boolean; 32 | /** Is it a recursive request */ 33 | recursive: boolean; 34 | /** When a modification has occurred */ 35 | modificationTime: number; 36 | /** Is it a directory request */ 37 | directoryRequest: boolean; 38 | /** Is it a metadata request */ 39 | metadata: boolean; 40 | /** Last modification time that occurred */ 41 | lastModified: number; 42 | /** If a modification happened since the last modification time */ 43 | ifModifiedSince: number; 44 | /** The url that points to artifactory */ 45 | servletContextUrl: string; 46 | /** The request URI */ 47 | uri: string; 48 | /** The client address */ 49 | clientAddress: string; 50 | /** The resource path of the requested zip */ 51 | zipResourcePath: string; 52 | /** Is the request a zip resource request */ 53 | zipResourceRequest: boolean; 54 | /** should replace the head request with get */ 55 | replaceHeadRequestWithGet: boolean; 56 | /** Repository type */ 57 | repoType: RepoType; 58 | } 59 | 60 | export interface RepoPath { 61 | /** The repo key */ 62 | key: string; 63 | /** The path itself */ 64 | path: string; 65 | /** The key:path combination */ 66 | id: string; 67 | /** Is the path the root */ 68 | isRoot: boolean; 69 | /** Is the path a folder */ 70 | isFolder: boolean; 71 | } 72 | 73 | export interface Header { 74 | value: string[]; 75 | } 76 | 77 | export interface UserContext { 78 | /** The username or subject */ 79 | id: string; 80 | /** Is the context an accessToken */ 81 | isToken: boolean; 82 | /** The realm of the user */ 83 | realm: string; 84 | } 85 | 86 | export enum RepoType { 87 | REPO_TYPE_UNSPECIFIED = 0, 88 | REPO_TYPE_LOCAL = 1, 89 | REPO_TYPE_REMOTE = 2, 90 | REPO_TYPE_FEDERATED = 3, 91 | UNRECOGNIZED = -1, 92 | } 93 | 94 | export interface BeforeDownloadResponse { 95 | /** The instruction of how to proceed */ 96 | status: DownloadStatus; 97 | /** Message to print to the log, in case of an error it will be printed as a warning */ 98 | message: string; 99 | } 100 | 101 | export enum DownloadStatus { 102 | DOWNLOAD_UNSPECIFIED = 0, 103 | DOWNLOAD_PROCEED = 1, 104 | DOWNLOAD_STOP = 2, 105 | DOWNLOAD_WARN = 3, 106 | UNRECOGNIZED = -1, 107 | } 108 | -------------------------------------------------------------------------------- /samples/artifactory/BEFORE_DOWNLOAD/restrict-download-by-property-value/README.md: -------------------------------------------------------------------------------- 1 | Restrict Download by Property Value 2 | ===================================== 3 | 4 | This worker is triggered by the `BEFORE_DOWNLOAD` event of Artifactory. Its primary purpose is to block artifact downloads if the artifact contains certain forbidden properties with restricted values. This ensures compliance with organizational policies by preventing downloads of restricted artifacts. 5 | 6 | Functionality 7 | ------------- 8 | - **Forbidden Properties Check:** Blocks download if an artifact contains a forbidden property with restricted values. 9 | - **Error Handling:** Issues a warning and allows download if the properties cannot be verified due to an error. 10 | - **Headers:** Optionally modifies response headers if required. 11 | 12 | Worker Logic 13 | ------------ 14 | 1. **Property Fetching**: Queries Artifactory for artifact properties using the repository key and artifact path. 15 | 2. **Decision Making**: 16 | - Blocks the download if a forbidden property with restricted values is present. 17 | - Allows the download if no forbidden properties are found. 18 | - Issues a warning and allows download for errors or unexpected results. 19 | 20 | Payload 21 | ------- 22 | The worker operates on the `BEFORE_DOWNLOAD` event payload provided by Artifactory. It uses metadata such as the artifact's repository path and key to fetch properties. 23 | 24 | Configuration 25 | ------------- 26 | - **Forbidden Properties**: Define the list of forbidden properties and their restricted values in the worker script. Example: 27 | ```typescript 28 | let forbiddenProperties: ForbiddenProperty[] = []; 29 | forbiddenProperties.push({ 30 | key: "FORBIDDEN", 31 | values: ["true"] 32 | }); 33 | ``` 34 | 35 | Possible Responses 36 | ------------------ 37 | 38 | ### Download Proceed 39 | - **Structure:** 40 | ```json 41 | { 42 | "status": "DOWNLOAD_PROCEED", 43 | "message": "Allowing Download" 44 | } 45 | ``` 46 | - **Explanation:** 47 | - Indicates the artifact does not contain any forbidden properties, and the download is allowed. 48 | 49 | ### Download Stopped 50 | - **Structure:** 51 | ```json 52 | { 53 | "status": "DOWNLOAD_STOP", 54 | "message": "Stopping Download because forbiddenProperty is present with forbidden values" 55 | } 56 | ``` 57 | - **Explanation:** 58 | - Indicates the artifact contains a forbidden property, and the download is blocked. 59 | 60 | ### Warning Response 61 | - **Structure:** 62 | ```json 63 | { 64 | "status": "DOWNLOAD_WARN", 65 | "message": "Download proceed with a warning. Could not check if artifact is forbidden or not." 66 | } 67 | ``` 68 | - **Explanation:** 69 | - Indicates that the worker encountered an error (e.g., API failure), but the download is allowed with a warning. 70 | 71 | ### Headers 72 | - The worker response can optionally include modified headers. The `headers` field in the response is currently empty but can be populated if required. 73 | 74 | Error Handling 75 | -------------- 76 | - **Property Fetch Failure:** Allows download with a warning message. 77 | - **Unexpected Errors:** Logs errors and proceeds with a warning. 78 | 79 | Recommendations 80 | --------------- 81 | 1. **Forbidden Properties Definition**: 82 | - Update the list of forbidden properties and their restricted values as per organizational policies. 83 | 84 | 2. **Monitoring and Logging**: 85 | - Review logs for `DOWNLOAD_WARN` responses to identify and address recurring issues. 86 | 87 | 3. **Testing**: 88 | - Validate the worker functionality in a staging environment before deploying to production. 89 | --------------------------------------------------------------------------------