diff --git a/.github/scripts/manifest_verification/check_sources.js b/.github/scripts/manifest_verification/check_sources.js new file mode 100644 index 0000000000..31293432f8 --- /dev/null +++ b/.github/scripts/manifest_verification/check_sources.js @@ -0,0 +1,159 @@ +// This script checks that the manifest sourceDependencies are reachable. + +const core = require('@actions/core'); +const fs = require('fs-extra'); + +const results = { + success: 'found_matching_commit_or_tag', + warn: 'found_matching_branch', + fail: 'no_matching_tag_or_branch' +} + +function is_sha(item) { + return /^[0-9a-f]{7}$/.test(item) || /^[0-9a-f]{40}$/.test(item) +} + +function isRcOrMaster(branchName) { + return /v[0-9]\.x\/(rc|master)/i.test(branchName); +} + +async function main() { + + if (process.env['BASE_REF'] == null) { + core.setFailed('This script requires the BASE_REF env to bet set.'); + return; + } + + if (process.env['GITHUB_TOKEN'] == null) { + core.setFailed('This script requires the GITHUB_TOKEN env to be set.'); + return; + } + + const baseRef = process.env['BASE_REF'].trim(); + + const github = require('@actions/github') + const octokit = github.getOctokit(process.env['GITHUB_TOKEN']); + + // expect script to be run from repo root + const sourceDeps = fs.readJSONSync('./manifest.json.template').sourceDependencies; + + /** + * Source dep structure is below: + * + * [ + * { + * "componentGroup": "Performance Timing Utility", + * "entries": [{ + * "repository": "perf-timing", + * "tag": "master", + * "destinations": ["Zowe CLI Package"] + * }] + * }, + * { ...same structure as prior...} + * ] + */ + + const analyzedRepos = []; + + for (const dep of sourceDeps) { + for (const entry of dep.entries) { + const repo = entry.repository; + const tag = entry.tag; + + // octokit ref/commit_sha APIs work for branches/tags, and we only want to test when its an actual hash + if (is_sha(tag)) { + const isCommit = await octokit.rest.repos.getCommit({ + owner: 'zowe', + repo: repo, + ref: tag + }).then((resp) => { + if (resp.status < 400) { + return true; + } + return false; + }) + + // Pinning repos with a commit is ok + if (isCommit) { + analyzedRepos.push({repository: repo, tag: tag, result: results.success}); + continue; + } + } + + // If not a commit, check repo tags + const tags = await octokit.rest.repos.listTags({ + owner: 'zowe', + repo: repo, + }).then((resp) => { + if (resp.status < 400) { + return resp.data; + } + return []; + }) + + const knownTag = tags.find((item) => item.name === tag); + if (knownTag != null && knownTag.name.trim().length > 0) { + analyzedRepos.push({repository: repo, tag: tag, result: results.success}); + continue; + } + + // if we didn't find tag, look at branches + // 2 REST Requests, unset protected was operating as protected=false + const protBranches = await octokit.rest.repos.listBranches({ + owner: 'zowe', + repo: repo, + protected: true + }).then((resp) => { + if (resp.status < 400) { + return resp.data; + } + return []; + }) + const unProtBranches = await octokit.rest.repos.listBranches({ + owner: 'zowe', + repo: repo, + protected: false + }).then((resp) => { + if (resp.status < 400) { + return resp.data; + } + return []; + }) + + const branches = [...protBranches, ...unProtBranches]; + + const knownBranch = branches.find((item) => item.name === tag); + if (knownBranch != null && knownBranch.name.trim().length > 0) { + analyzedRepos.push({repository: repo, tag: tag, result: results.warn}); + continue; + } + + // if we didn't find commit, tag or branch + analyzedRepos.push({repository: repo, tag: tag, result: results.fail}); + } + } + + let didFail = false; + + const failRepos = analyzedRepos.filter((item) => item.result === results.fail); + if (failRepos != null && failRepos.length > 0) { + core.warning('There are manifest sourceDependencies without a matching tag or branch. Review the output and update the manifest.'); + core.warning('The following repositories do not have a matching commit hash, tag or branch: ' + JSON.stringify(failRepos, null, {indent: 4})) + didFail = true; + } + + const warnRepos = analyzedRepos.filter((item) => item.result === results.warn) ; + if (warnRepos != null && warnRepos.length > 0) { + if (isRcOrMaster(baseRef)) { + core.warning('Merges to RC and master require tags or commit hashes instead of branches for sourceDependencies.') + didFail = true + } + core.warning('The following repositories have a branch instead of tag: ' + JSON.stringify(warnRepos, null, {indent: 4})) + } + + if (didFail) { + core.setFailed('The manifest validation was not successful. Review the warning output for more details.'); + } + +} +main() diff --git a/.github/scripts/manifest_verification/package-lock.json b/.github/scripts/manifest_verification/package-lock.json new file mode 100644 index 0000000000..784fe7954d --- /dev/null +++ b/.github/scripts/manifest_verification/package-lock.json @@ -0,0 +1,291 @@ +{ + "name": "manifest_verification", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "manifest_verification", + "version": "1.0.0", + "license": "EPL-2.0", + "dependencies": { + "@actions/core": "1.10.1", + "@actions/github": "^6.0.0", + "fs-extra": "11.2.0" + } + }, + "node_modules/@actions/core": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.1.tgz", + "integrity": "sha512-3lBR9EDAY+iYIpTnTIXmWcNbX3T2kCkAEQGIQx4NVQ0575nk2k3GRZDTPQG+vVtS2izSLmINlxXf0uLtnrTP+g==", + "dependencies": { + "@actions/http-client": "^2.0.1", + "uuid": "^8.3.2" + } + }, + "node_modules/@actions/github": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@actions/github/-/github-6.0.0.tgz", + "integrity": "sha512-alScpSVnYmjNEXboZjarjukQEzgCRmjMv6Xj47fsdnqGS73bjJNDpiiXmp8jr0UZLdUB6d9jW63IcmddUP+l0g==", + "dependencies": { + "@actions/http-client": "^2.2.0", + "@octokit/core": "^5.0.1", + "@octokit/plugin-paginate-rest": "^9.0.0", + "@octokit/plugin-rest-endpoint-methods": "^10.0.0" + } + }, + "node_modules/@actions/http-client": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.1.tgz", + "integrity": "sha512-KhC/cZsq7f8I4LfZSJKgCvEwfkE8o1538VoBeoGzokVLLnbFDEAdFD3UhoMklxo2un9NJVBdANOresx7vTHlHw==", + "dependencies": { + "tunnel": "^0.0.6", + "undici": "^5.25.4" + } + }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "engines": { + "node": ">=14" + } + }, + "node_modules/@octokit/auth-token": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", + "integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/core": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.0.tgz", + "integrity": "sha512-1LFfa/qnMQvEOAdzlQymH0ulepxbxnCYAKJZfMci/5XJyIHWgEYnDmgnKakbTh7CH2tFQ5O60oYDvns4i9RAIg==", + "dependencies": { + "@octokit/auth-token": "^4.0.0", + "@octokit/graphql": "^7.1.0", + "@octokit/request": "^8.3.1", + "@octokit/request-error": "^5.1.0", + "@octokit/types": "^13.0.0", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/endpoint": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.5.tgz", + "integrity": "sha512-ekqR4/+PCLkEBF6qgj8WqJfvDq65RH85OAgrtnVp1mSxaXF03u2xW/hUdweGS5654IlC0wkNYC18Z50tSYTAFw==", + "dependencies": { + "@octokit/types": "^13.1.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/graphql": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.1.0.tgz", + "integrity": "sha512-r+oZUH7aMFui1ypZnAvZmn0KSqAUgE1/tUXIWaqUCa1758ts/Jio84GZuzsvUkme98kv0WFY8//n0J1Z+vsIsQ==", + "dependencies": { + "@octokit/request": "^8.3.0", + "@octokit/types": "^13.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "22.1.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.1.0.tgz", + "integrity": "sha512-pGUdSP+eEPfZiQHNkZI0U01HLipxncisdJQB4G//OAmfeO8sqTQ9KRa0KF03TUPCziNsoXUrTg4B2Q1EX++T0Q==" + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.2.1.tgz", + "integrity": "sha512-wfGhE/TAkXZRLjksFXuDZdmGnJQHvtU/joFQdweXUgzo1XwvBCD4o4+75NtFfjfLK5IwLf9vHTfSiU3sLRYpRw==", + "dependencies": { + "@octokit/types": "^12.6.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" + } + }, + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/openapi-types": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", + "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==" + }, + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", + "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", + "dependencies": { + "@octokit/openapi-types": "^20.0.0" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-10.4.1.tgz", + "integrity": "sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg==", + "dependencies": { + "@octokit/types": "^12.6.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/openapi-types": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", + "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==" + }, + "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/types": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", + "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", + "dependencies": { + "@octokit/openapi-types": "^20.0.0" + } + }, + "node_modules/@octokit/request": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.0.tgz", + "integrity": "sha512-9Bb014e+m2TgBeEJGEbdplMVWwPmL1FPtggHQRkV+WVsMggPtEkLKPlcVYm/o8xKLkpJ7B+6N8WfQMtDLX2Dpw==", + "dependencies": { + "@octokit/endpoint": "^9.0.1", + "@octokit/request-error": "^5.1.0", + "@octokit/types": "^13.1.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/request-error": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.0.tgz", + "integrity": "sha512-GETXfE05J0+7H2STzekpKObFe765O5dlAKUTLNGeH+x47z7JjXHfsHKo5z21D/o/IOZTUEI6nyWyR+bZVP/n5Q==", + "dependencies": { + "@octokit/types": "^13.1.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/types": { + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.4.1.tgz", + "integrity": "sha512-Y73oOAzRBAUzR/iRAbGULzpNkX8vaxKCqEtg6K74Ff3w9f5apFnWtE/2nade7dMWWW3bS5Kkd6DJS4HF04xreg==", + "dependencies": { + "@octokit/openapi-types": "^22.1.0" + } + }, + "node_modules/before-after-hook": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", + "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==" + }, + "node_modules/deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" + }, + "node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } + }, + "node_modules/undici": { + "version": "5.28.4", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", + "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, + "node_modules/universal-user-agent": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", + "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + } + } +} diff --git a/.github/scripts/manifest_verification/package.json b/.github/scripts/manifest_verification/package.json new file mode 100644 index 0000000000..3d10210d27 --- /dev/null +++ b/.github/scripts/manifest_verification/package.json @@ -0,0 +1,16 @@ +{ + "name": "manifest_verification", + "version": "1.0.0", + "description": "Script to verify Zowe manifest sourceDependencies", + "main": "check_sources.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "EPL-2.0", + "dependencies": { + "@actions/core": "1.10.1", + "@actions/github": "6.0.0", + "fs-extra": "11.2.0" + } +} diff --git a/.github/workflows/manifest-source-check.yml b/.github/workflows/manifest-source-check.yml new file mode 100644 index 0000000000..bde7c53b9f --- /dev/null +++ b/.github/workflows/manifest-source-check.yml @@ -0,0 +1,25 @@ +name: Manifest SourceDependencies Verification + +permissions: read-all + +on: + pull_request: + types: [opened, synchronize] + +jobs: + check-manifest: + runs-on: ubuntu-latest + steps: + + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install Dependencies for script to work + run: npm install + working-directory: .github/scripts/manifest_verification + + - name: Run Check + run: node .github/scripts/manifest_verification/check_sources.js + env: + GITHUB_TOKEN: ${{ github.token }} + BASE_REF: ${{ github.event.pull_request.base.ref }} diff --git a/manifest.json.template b/manifest.json.template index d31681b84e..abd6ba1afc 100644 --- a/manifest.json.template +++ b/manifest.json.template @@ -142,14 +142,7 @@ } }, "sourceDependencies": [ - { - "componentGroup": "Imperative CLI Framework for Zowe", - "entries": [{ - "repository": "imperative", - "tag": "master", - "destinations": ["Zowe CLI Package"] - }] - }, { + { "componentGroup": "Zowe API Mediation Layer", "entries": [{ "repository": "api-layer", @@ -281,7 +274,7 @@ "componentGroup": "Zowe Desktop Eclipse Orion-based React Editor", "entries": [{ "repository": "orion-editor-component", - "tag": "master", + "tag": "v2.x/master", "destinations": ["Zowe PAX"] }] }, {